Bug 699156: Support TypedArrays in XPConnect. r=bholley,evilpie
authorAntti Haapala <antti@haapala.name>
Sat, 03 Dec 2011 09:33:20 -0500
changeset 81352 d662c4cfabae
parent 81233 a68c96c1d8e0
child 81353 ffdf85abb789
push id21566
push userkhuey@mozilla.com
push date2011-12-03 21:27 +0000
treeherdermozilla-central@c2102c45c8da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley, evilpie
bugs699156
milestone11.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 699156: Support TypedArrays in XPConnect. r=bholley,evilpie
js/xpconnect/src/XPCConvert.cpp
js/xpconnect/src/XPCWrappedNative.cpp
js/xpconnect/src/xpcprivate.h
js/xpconnect/tests/components/js/xpctest_params.js
js/xpconnect/tests/components/native/xpctest_params.cpp
js/xpconnect/tests/idl/xpctest_params.idl
js/xpconnect/tests/unit/test_params.js
--- a/js/xpconnect/src/XPCConvert.cpp
+++ b/js/xpconnect/src/XPCConvert.cpp
@@ -53,16 +53,18 @@
 #include "nsWrapperCache.h"
 #include "WrapperFactory.h"
 #include "AccessCheck.h"
 #include "nsJSUtils.h"
 
 #include "dombindings.h"
 #include "nsWrapperCacheInlines.h"
 
+#include "jstypedarray.h"
+
 using namespace mozilla;
 
 //#define STRICT_CHECK_OF_UNICODE
 #ifdef STRICT_CHECK_OF_UNICODE
 #define ILLEGAL_RANGE(c) (0!=((c) & 0xFF80))
 #else // STRICT_CHECK_OF_UNICODE
 #define ILLEGAL_RANGE(c) (0!=((c) & 0xFF00))
 #endif // STRICT_CHECK_OF_UNICODE
@@ -1684,23 +1686,179 @@ XPCConvert::NativeArray2JS(XPCLazyCallCo
     return true;
 
 failure:
     return false;
 
 #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();
 
     // No Action, FRee memory, RElease object
     enum CleanupMode {na, fr, re};
 
     CleanupMode cleanupMode;
 
@@ -1726,23 +1884,29 @@ XPCConvert::JSArray2Native(XPCCallContex
 
     if (!JSVAL_IS_OBJECT(s)) {
         if (pErr)
             *pErr = NS_ERROR_XPC_CANT_CONVERT_PRIMITIVE_TO_ARRAY;
         return false;
     }
 
     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;
         return false;
     }
 
     if (pErr)
         *pErr = NS_ERROR_XPC_BAD_CONVERT_JS;
@@ -1776,17 +1940,17 @@ XPCConvert::JSArray2Native(XPCCallContex
     case nsXPTType::T_I32           : POPULATE(na, int32);          break;
     case nsXPTType::T_I64           : POPULATE(na, int64);          break;
     case nsXPTType::T_U8            : POPULATE(na, uint8);          break;
     case nsXPTType::T_U16           : POPULATE(na, uint16);         break;
     case nsXPTType::T_U32           : POPULATE(na, uint32);         break;
     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;
     case nsXPTType::T_IID           : POPULATE(fr, nsID*);          break;
     case nsXPTType::T_DOMSTRING     : NS_ERROR("bad type"); goto failure;
     case nsXPTType::T_CHAR_STR      : POPULATE(fr, char*);          break;
     case nsXPTType::T_WCHAR_STR     : POPULATE(fr, jschar*);        break;
     case nsXPTType::T_INTERFACE     : POPULATE(re, nsISupports*);   break;
@@ -1869,17 +2033,17 @@ XPCConvert::NativeStringWithSize2JS(JSCo
     }
     return true;
 }
 
 // static
 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");
 
     JSContext* cx = ccx.GetJSContext();
 
     JSUint32 len;
 
--- a/js/xpconnect/src/XPCWrappedNative.cpp
+++ b/js/xpconnect/src/XPCWrappedNative.cpp
@@ -2429,18 +2429,17 @@ CallMethodHelper::GatherAndConvertResult
                 return false;
         }
 
         nsID param_iid;
         if (datum_type.IsInterfacePointer() &&
             !GetInterfaceTypeFromParam(i, datum_type, &param_iid))
             return false;
 
-        uintN err;
-
+        nsresult err;
         if (isArray) {
             XPCLazyCallContext lccx(mCallContext);
             if (!XPCConvert::NativeArray2JS(lccx, &v, (const void**)&dp->val,
                                             datum_type, &param_iid,
                                             array_count, &err)) {
                 // XXX need exception scheme for arrays to indicate bad element
                 ThrowBadParam(err, i, mCallContext);
                 return false;
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -3322,17 +3322,24 @@ public:
      */
     static JSBool NativeArray2JS(XPCLazyCallContext& ccx,
                                  jsval* d, const void** s,
                                  const nsXPTType& type, const nsID* iid,
                                  JSUint32 count, nsresult* pErr);
 
     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,
                                           const nsXPTType& type,
                                           JSUint32 count,
                                           nsresult* pErr);
 
     static JSBool JSStringWithSize2Native(XPCCallContext& ccx, void* d, jsval s,
--- a/js/xpconnect/tests/components/js/xpctest_params.js
+++ b/js/xpconnect/tests/components/js/xpctest_params.js
@@ -92,17 +92,17 @@ TestParams.prototype = {
   testWchar: f,
   testWstring: f,
   testDOMString: f,
   testAString: f,
   testAUTF8String: f,
   testACString: f,
   testJsval: f,
   testShortArray: f_is,
-  testLongLongArray: f_is,
+  testDoubleArray: f_is,
   testStringArray: f_is,
   testWstringArray: f_is,
   testInterfaceArray: f_is,
   testSizedString: f_is,
   testSizedWstring: f_is,
   testInterfaceIs: f_is,
   testInterfaceIsArray: f_size_and_iid,
 };
--- a/js/xpconnect/tests/components/native/xpctest_params.cpp
+++ b/js/xpconnect/tests/components/native/xpctest_params.cpp
@@ -241,24 +241,24 @@ NS_IMETHODIMP nsXPCTestParams::TestJsval
  *                      out unsigned long rvLength, [array, size_is (rvLength), retval] out short rv); */
 NS_IMETHODIMP nsXPCTestParams::TestShortArray(PRUint32 aLength, PRInt16 *a,
                                               PRUint32 *bLength NS_INOUTPARAM, PRInt16 **b NS_INOUTPARAM,
                                               PRUint32 *rvLength NS_OUTPARAM, PRInt16 **rv NS_OUTPARAM)
 {
     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,
  *                       inout unsigned long bLength, [array, size_is (bLength)] inout string b,
  *                       out unsigned long rvLength, [array, size_is (rvLength), retval] out string rv); */
 NS_IMETHODIMP nsXPCTestParams::TestStringArray(PRUint32 aLength, const char * *a,
                                                PRUint32 *bLength NS_INOUTPARAM, char * **b NS_INOUTPARAM,
                                                PRUint32 *rvLength NS_OUTPARAM, char * **rv NS_OUTPARAM)
--- a/js/xpconnect/tests/idl/xpctest_params.idl
+++ b/js/xpconnect/tests/idl/xpctest_params.idl
@@ -42,17 +42,17 @@
  * covered by the intersection of return values and inout).
  */
 
 #include "nsISupports.idl"
 
 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
   boolean               testBoolean(in boolean a, inout boolean b);
   octet                 testOctet(in octet a, inout octet b);
   short                 testShort(in short a, inout short b);
   long                  testLong(in long a, inout long b);
   long long             testLongLong(in long long a, inout long long b);
@@ -74,19 +74,19 @@ interface nsIXPCTestParams : nsISupports
   //
   // Dependent parameters use the same types as above, but are handled much differently.
   //
 
   // Test arrays.
   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);
   void                  testWstringArray(in unsigned long aLength, [array, size_is(aLength)] in wstring a,
                                          inout unsigned long bLength, [array, size_is(bLength)] inout wstring b,
                                          out unsigned long rvLength, [retval, array, size_is(rvLength)] out wstring rv);
   void                  testInterfaceArray(in unsigned long aLength, [array, size_is(aLength)] in nsIXPCTestInterfaceA a,
                                            inout unsigned long bLength, [array, size_is(bLength)] inout nsIXPCTestInterfaceA b,
--- a/js/xpconnect/tests/unit/test_params.js
+++ b/js/xpconnect/tests/unit/test_params.js
@@ -114,16 +114,32 @@ function test_component(contractid) {
     do_check_true(arrayComparator(interfaceComparator)(rv, val2));
     do_check_true(standardComparator(rvSize.value, val2Size));
     do_check_true(dotEqualsComparator(rvIID.value, val2IID));
     do_check_true(arrayComparator(interfaceComparator)(val1, b.value));
     do_check_true(standardComparator(val1Size, bSize.value));
     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) {
     var a = val1;
     var b = {value: ""};
     o[name].call(o, a, b);
     do_check_eq(val1, b.value);
   }
@@ -160,30 +176,46 @@ function test_component(contractid) {
   function makeB() {
     var b = Cc["@mozilla.org/js/xpc/test/js/InterfaceB;1"].createInstance(Ci['nsIXPCTestInterfaceB']);
     b.name = 'testB' + numBsMade++;
     return b;
   };
 
   // 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,
                                ["we", "are", "being", "sooo", "international", "right", "now"], 7, arrayComparator(standardComparator));
   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);
   doIsTest("testSizedWstring", ssTests[2], ssTests[2].length, ssTests[3], ssTests[3].length, standardComparator);
 
   // Test iid_is.
   doIsTest("testInterfaceIs", makeA(), Ci['nsIXPCTestInterfaceA'],
                               makeB(), Ci['nsIXPCTestInterfaceB'],
                               interfaceComparator, dotEqualsComparator);
 
   // 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);
 }