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
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);
 }