From 55185ebf5ee5a991c00b198ee88f4957d1153007 Mon Sep 17 00:00:00 2001 From: Antti Haapala Date: Sat, 3 Dec 2011 09:33:20 -0500 Subject: [PATCH] Bug 699156: Support TypedArrays in XPConnect. r=bholley,evilpie --- js/xpconnect/src/XPCConvert.cpp | 174 +++++++++++++++++- js/xpconnect/src/XPCWrappedNative.cpp | 3 +- js/xpconnect/src/xpcprivate.h | 9 +- .../tests/components/js/xpctest_params.js | 2 +- .../components/native/xpctest_params.cpp | 14 +- js/xpconnect/tests/idl/xpctest_params.idl | 8 +- js/xpconnect/tests/unit/test_params.js | 34 +++- 7 files changed, 223 insertions(+), 21 deletions(-) diff --git a/js/xpconnect/src/XPCConvert.cpp b/js/xpconnect/src/XPCConvert.cpp index 415360aa7e8..d16d22c3a8f 100644 --- a/js/xpconnect/src/XPCConvert.cpp +++ b/js/xpconnect/src/XPCConvert.cpp @@ -58,6 +58,8 @@ #include "dombindings.h" #include "nsWrapperCacheInlines.h" +#include "jstypedarray.h" + using namespace mozilla; //#define STRICT_CHECK_OF_UNICODE @@ -1689,13 +1691,169 @@ failure: #undef POPULATE } + + +// Check that the tag part of the type matches the type +// of the array. If the check succeeds, check that the size +// of the output does not exceed PR_UINT32_MAX bytes. Allocate +// the memory and copy the elements by memcpy. +static JSBool +CheckTargetAndPopulate(const nsXPTType& type, + PRUint8 requiredType, + size_t typeSize, + JSUint32 count, + JSObject* tArr, + void** output, + nsresult* pErr) +{ + // Check that the element type expected by the interface matches + // the type of the elements in the typed array exactly, including + // signedness. + if (type.TagPart() != requiredType) { + if (pErr) + *pErr = NS_ERROR_XPC_BAD_CONVERT_JS; + + return false; + } + + // Calulate the maximum number of elements that can fit in + // PR_UINT32_MAX bytes. + size_t max = PR_UINT32_MAX / typeSize; + + // This could overflow on 32-bit systems so check max first. + size_t byteSize = count * typeSize; + if (count > max || !(*output = nsMemory::Alloc(byteSize))) { + if (pErr) + *pErr = NS_ERROR_OUT_OF_MEMORY; + + return false; + } + + memcpy(*output, JS_GetTypedArrayData(tArr), byteSize); + return true; +} + +// Fast conversion of typed arrays to native using memcpy. +// No float or double canonicalization is done. Called by +// JSarray2Native whenever a TypedArray is met. ArrayBuffers +// are not accepted; create a properly typed array view on them +// first. The element type of array must match the XPCOM +// type in size, type and signedness exactly. As an exception, +// Uint8ClampedArray is allowed for arrays of uint8. + +// static +JSBool +XPCConvert::JSTypedArray2Native(XPCCallContext& ccx, + void** d, + JSObject* jsArray, + JSUint32 count, + const nsXPTType& type, + nsresult* pErr) +{ + NS_ABORT_IF_FALSE(jsArray, "bad param"); + NS_ABORT_IF_FALSE(d, "bad param"); + NS_ABORT_IF_FALSE(js_IsTypedArray(jsArray), "not a typed array"); + + // Check the actual length of the input array against the + // given size_is. + JSUint32 len = JS_GetTypedArrayLength(jsArray); + if (len < count) { + if (pErr) + *pErr = NS_ERROR_XPC_NOT_ENOUGH_ELEMENTS_IN_ARRAY; + + return false; + } + + void* output = nsnull; + + switch (JS_GetTypedArrayType(jsArray)) { + case js::TypedArray::TYPE_INT8: + if (!CheckTargetAndPopulate(nsXPTType::T_I8, type, + sizeof(int8), count, + jsArray, &output, pErr)) { + return false; + } + break; + + case js::TypedArray::TYPE_UINT8: + case js::TypedArray::TYPE_UINT8_CLAMPED: + if (!CheckTargetAndPopulate(nsXPTType::T_U8, type, + sizeof(uint8), count, + jsArray, &output, pErr)) { + return false; + } + break; + + case js::TypedArray::TYPE_INT16: + if (!CheckTargetAndPopulate(nsXPTType::T_I16, type, + sizeof(int16), count, + jsArray, &output, pErr)) { + return false; + } + break; + + case js::TypedArray::TYPE_UINT16: + if (!CheckTargetAndPopulate(nsXPTType::T_U16, type, + sizeof(uint16), count, + jsArray, &output, pErr)) { + return false; + } + break; + + case js::TypedArray::TYPE_INT32: + if (!CheckTargetAndPopulate(nsXPTType::T_I32, type, + sizeof(int32), count, + jsArray, &output, pErr)) { + return false; + } + break; + + case js::TypedArray::TYPE_UINT32: + if (!CheckTargetAndPopulate(nsXPTType::T_U32, type, + sizeof(uint32), count, + jsArray, &output, pErr)) { + return false; + } + break; + + case js::TypedArray::TYPE_FLOAT32: + if (!CheckTargetAndPopulate(nsXPTType::T_FLOAT, type, + sizeof(float), count, + jsArray, &output, pErr)) { + return false; + } + break; + + case js::TypedArray::TYPE_FLOAT64: + if (!CheckTargetAndPopulate(nsXPTType::T_DOUBLE, type, + sizeof(double), count, + jsArray, &output, pErr)) { + return false; + } + break; + + // Yet another array type was defined? It is not supported yet... + default: + if (pErr) + *pErr = NS_ERROR_XPC_BAD_CONVERT_JS; + + return false; + } + + *d = output; + if (pErr) + *pErr = NS_OK; + + return true; +} + // static JSBool XPCConvert::JSArray2Native(XPCCallContext& ccx, void** d, jsval s, JSUint32 count, const nsXPTType& type, - const nsID* iid, uintN* pErr) + const nsID* iid, nsresult* pErr) { - NS_PRECONDITION(d, "bad param"); + NS_ABORT_IF_FALSE(d, "bad param"); JSContext* cx = ccx.GetJSContext(); @@ -1731,13 +1889,19 @@ XPCConvert::JSArray2Native(XPCCallContext& ccx, void** d, jsval s, } jsarray = JSVAL_TO_OBJECT(s); + + // If this is a typed array, then do a fast conversion with memcpy. + if (js_IsTypedArray(jsarray)) { + return JSTypedArray2Native(ccx, d, jsarray, count, type, pErr); + } + if (!JS_IsArrayObject(cx, jsarray)) { if (pErr) *pErr = NS_ERROR_XPC_CANT_CONVERT_OBJECT_TO_ARRAY; return false; } - jsuint len; + JSUint32 len; if (!JS_GetArrayLength(cx, jsarray, &len) || len < count) { if (pErr) *pErr = NS_ERROR_XPC_NOT_ENOUGH_ELEMENTS_IN_ARRAY; @@ -1781,7 +1945,7 @@ XPCConvert::JSArray2Native(XPCCallContext& ccx, void** d, jsval s, case nsXPTType::T_U64 : POPULATE(na, uint64); break; case nsXPTType::T_FLOAT : POPULATE(na, float); break; case nsXPTType::T_DOUBLE : POPULATE(na, double); break; - case nsXPTType::T_BOOL : POPULATE(na, bool); break; + case nsXPTType::T_BOOL : POPULATE(na, bool); break; case nsXPTType::T_CHAR : POPULATE(na, char); break; case nsXPTType::T_WCHAR : POPULATE(na, jschar); break; case nsXPTType::T_VOID : NS_ERROR("bad type"); goto failure; @@ -1874,7 +2038,7 @@ XPCConvert::NativeStringWithSize2JS(JSContext* cx, JSBool XPCConvert::JSStringWithSize2Native(XPCCallContext& ccx, void* d, jsval s, JSUint32 count, const nsXPTType& type, - uintN* pErr) + nsresult* pErr) { NS_PRECONDITION(!JSVAL_IS_NULL(s), "bad param"); NS_PRECONDITION(d, "bad param"); diff --git a/js/xpconnect/src/XPCWrappedNative.cpp b/js/xpconnect/src/XPCWrappedNative.cpp index 237baf1e016..520de68508a 100644 --- a/js/xpconnect/src/XPCWrappedNative.cpp +++ b/js/xpconnect/src/XPCWrappedNative.cpp @@ -2434,8 +2434,7 @@ CallMethodHelper::GatherAndConvertResults() !GetInterfaceTypeFromParam(i, datum_type, ¶m_iid)) return false; - uintN err; - + nsresult err; if (isArray) { XPCLazyCallContext lccx(mCallContext); if (!XPCConvert::NativeArray2JS(lccx, &v, (const void**)&dp->val, diff --git a/js/xpconnect/src/xpcprivate.h b/js/xpconnect/src/xpcprivate.h index eb2b64c7852..92c5cc4c9c8 100644 --- a/js/xpconnect/src/xpcprivate.h +++ b/js/xpconnect/src/xpcprivate.h @@ -3327,7 +3327,14 @@ public: static JSBool JSArray2Native(XPCCallContext& ccx, void** d, jsval s, JSUint32 count, const nsXPTType& type, - const nsID* iid, uintN* pErr); + const nsID* iid, nsresult* pErr); + + static JSBool JSTypedArray2Native(XPCCallContext& ccx, + void** d, + JSObject* jsarray, + JSUint32 count, + const nsXPTType& type, + nsresult* pErr); static JSBool NativeStringWithSize2JS(JSContext* cx, jsval* d, const void* s, diff --git a/js/xpconnect/tests/components/js/xpctest_params.js b/js/xpconnect/tests/components/js/xpctest_params.js index c38a1360564..72700a8aa55 100644 --- a/js/xpconnect/tests/components/js/xpctest_params.js +++ b/js/xpconnect/tests/components/js/xpctest_params.js @@ -97,7 +97,7 @@ TestParams.prototype = { testACString: f, testJsval: f, testShortArray: f_is, - testLongLongArray: f_is, + testDoubleArray: f_is, testStringArray: f_is, testWstringArray: f_is, testInterfaceArray: f_is, diff --git a/js/xpconnect/tests/components/native/xpctest_params.cpp b/js/xpconnect/tests/components/native/xpctest_params.cpp index 103034df847..0e9bea415ee 100644 --- a/js/xpconnect/tests/components/native/xpctest_params.cpp +++ b/js/xpconnect/tests/components/native/xpctest_params.cpp @@ -246,14 +246,14 @@ NS_IMETHODIMP nsXPCTestParams::TestShortArray(PRUint32 aLength, PRInt16 *a, BUFFER_METHOD_IMPL(PRInt16, 0, TAKE_OWNERSHIP_NOOP); } -/* void testLongLongArray (in unsigned long aLength, [array, size_is (aLength)] in long long a, - * inout unsigned long bLength, [array, size_is (bLength)] inout long long b, - * out unsigned long rvLength, [array, size_is (rvLength), retval] out long long rv); */ -NS_IMETHODIMP nsXPCTestParams::TestLongLongArray(PRUint32 aLength, PRInt64 *a, - PRUint32 *bLength NS_INOUTPARAM, PRInt64 **b NS_INOUTPARAM, - PRUint32 *rvLength NS_OUTPARAM, PRInt64 **rv NS_OUTPARAM) +/* void testDoubleArray (in unsigned long aLength, [array, size_is (aLength)] in double a, + * inout unsigned long bLength, [array, size_is (bLength)] inout double b, + * out unsigned long rvLength, [array, size_is (rvLength), retval] out double rv); */ +NS_IMETHODIMP nsXPCTestParams::TestDoubleArray(PRUint32 aLength, double *a, + PRUint32 *bLength NS_INOUTPARAM, double **b NS_INOUTPARAM, + PRUint32 *rvLength NS_OUTPARAM, double **rv NS_OUTPARAM) { - BUFFER_METHOD_IMPL(PRInt64, 0, TAKE_OWNERSHIP_NOOP); + BUFFER_METHOD_IMPL(double, 0, TAKE_OWNERSHIP_NOOP); } /* void testStringArray (in unsigned long aLength, [array, size_is (aLength)] in string a, diff --git a/js/xpconnect/tests/idl/xpctest_params.idl b/js/xpconnect/tests/idl/xpctest_params.idl index 5a37a2753ae..592d312f805 100644 --- a/js/xpconnect/tests/idl/xpctest_params.idl +++ b/js/xpconnect/tests/idl/xpctest_params.idl @@ -47,7 +47,7 @@ interface nsIXPCTestInterfaceA; interface nsIXPCTestInterfaceB; -[scriptable, uuid(b94cd289-d0df-4d25-8995-facf687d921d)] +[scriptable, uuid(fe2b7433-ac3b-49ef-9344-b67228bfdd46)] interface nsIXPCTestParams : nsISupports { // These types correspond to the ones in typelib.py @@ -79,9 +79,9 @@ interface nsIXPCTestParams : nsISupports { void testShortArray(in unsigned long aLength, [array, size_is(aLength)] in short a, inout unsigned long bLength, [array, size_is(bLength)] inout short b, out unsigned long rvLength, [retval, array, size_is(rvLength)] out short rv); - void testLongLongArray(in unsigned long aLength, [array, size_is(aLength)] in long long a, - inout unsigned long bLength, [array, size_is(bLength)] inout long long b, - out unsigned long rvLength, [retval, array, size_is(rvLength)] out long long rv); + void testDoubleArray(in unsigned long aLength, [array, size_is(aLength)] in double a, + inout unsigned long bLength, [array, size_is(bLength)] inout double b, + out unsigned long rvLength, [retval, array, size_is(rvLength)] out double rv); void testStringArray(in unsigned long aLength, [array, size_is(aLength)] in string a, inout unsigned long bLength, [array, size_is(bLength)] inout string b, out unsigned long rvLength, [retval, array, size_is(rvLength)] out string rv); diff --git a/js/xpconnect/tests/unit/test_params.js b/js/xpconnect/tests/unit/test_params.js index 4e2b08a9b00..870e8077c99 100644 --- a/js/xpconnect/tests/unit/test_params.js +++ b/js/xpconnect/tests/unit/test_params.js @@ -119,6 +119,22 @@ function test_component(contractid) { do_check_true(dotEqualsComparator(val1IID, bIID.value)); } + // Check that the given call (type mismatch) results in an exception being thrown. + function doTypedArrayMismatchTest(name, val1, val1Size, val2, val2Size) { + var comparator = arrayComparator(standardComparator); + var error = false; + try { + doIsTest(name, val1, val1Size, val2, val2Size, comparator); + + // An exception was not thrown as would have been expected. + do_check_true(false); + } + catch (e) { + // An exception was thrown as expected. + do_check_true(true); + } + } + // Workaround for bug 687612 (inout parameters broken for dipper types). // We do a simple test of copying a into b, and ignore the rv. function doTestWorkaround(name, val1) { @@ -165,7 +181,8 @@ function test_component(contractid) { // Test arrays. doIsTest("testShortArray", [2, 4, 6], 3, [1, 3, 5, 7], 4, arrayComparator(standardComparator)); - doIsTest("testLongLongArray", [-10000000000], 1, [1, 3, 1234511234551], 3, arrayComparator(standardComparator)); + doIsTest("testDoubleArray", [-10, -0.5], 2, [1, 3, 1e11, -8e-5 ], 4, arrayComparator(fuzzComparator)); + doIsTest("testStringArray", ["mary", "hat", "hey", "lid", "tell", "lam"], 6, ["ids", "fleas", "woes", "wide", "has", "know", "!"], 7, arrayComparator(standardComparator)); doIsTest("testWstringArray", ["沒有語言", "的偉大嗎?]"], 2, @@ -173,6 +190,13 @@ function test_component(contractid) { doIsTest("testInterfaceArray", [makeA(), makeA()], 2, [makeA(), makeA(), makeA(), makeA(), makeA(), makeA()], 6, arrayComparator(interfaceComparator)); + // Test typed arrays and ArrayBuffer aliasing. + var arrayBuffer = new ArrayBuffer(16); + var int16Array = new Int16Array(arrayBuffer, 2, 3); + int16Array.set([-32768, 0, 32767]); + doIsTest("testShortArray", int16Array, 3, new Int16Array([1773, -32768, 32767, 7]), 4, arrayComparator(standardComparator)); + doIsTest("testDoubleArray", new Float64Array([-10, -0.5]), 2, new Float64Array([0, 3.2, 1.0e10, -8.33 ]), 4, arrayComparator(fuzzComparator)); + // Test sized strings. var ssTests = ["Tis not possible, I muttered", "give me back my free hardcore!", "quoth the server:", "4〠4"]; doIsTest("testSizedString", ssTests[0], ssTests[0].length, ssTests[1], ssTests[1].length, standardComparator); @@ -186,4 +210,12 @@ function test_component(contractid) { // Test arrays of iids. doIs2Test("testInterfaceIsArray", [makeA(), makeA(), makeA(), makeA(), makeA()], 5, Ci['nsIXPCTestInterfaceA'], [makeB(), makeB(), makeB()], 3, Ci['nsIXPCTestInterfaceB']); + + // Test incorrect (too big) array size parameter; this should throw NOT_ENOUGH_ELEMENTS. + doTypedArrayMismatchTest("testShortArray", Int16Array([-3, 7, 4]), 4, + Int16Array([1, -32, 6]), 3); + + // Test type mismatch (int16 <-> uint16); this should throw BAD_CONVERT_JS. + doTypedArrayMismatchTest("testShortArray", Uint16Array([0, 7, 4, 3]), 4, + Uint16Array([1, 5, 6]), 3); }