Bug 795505 - Feed typed arrays to js-ctypes. r=jorendorff
authorDavid Rajchenbach-Teller <dteller@mozilla.com>
Sat, 06 Oct 2012 21:53:23 -0400
changeset 109540 7be4e7680b42ebb8dafbb8a84b252862d78a3175
parent 109539 834fb95232af9f4e02e365f86d320c662091c0fe
child 109541 03e4c6401c35ba6d598f7ab62c9ba5a505967564
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersjorendorff
bugs795505
milestone18.0a1
Bug 795505 - Feed typed arrays to js-ctypes. r=jorendorff
js/src/ctypes/CTypes.cpp
toolkit/components/ctypes/tests/unit/test_jsctypes.js.in
toolkit/components/ctypes/tests/xpcshellTestHarnessAdaptor.js
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -1989,16 +1989,57 @@ ConvertToJS(JSContext* cx,
   }
   case TYPE_function:
     JS_NOT_REACHED("cannot return a FunctionType");
   }
 
   return true;
 }
 
+// Determine if the contents of a typed array can be converted without
+// ambiguity to a C type. Elements of a Int8Array are converted to
+// ctypes.int8_t, UInt8Array to ctypes.uint8_t, etc.
+bool CanConvertTypedArrayItemTo(JSObject *baseType, JSObject *valObj, JSContext *cx) {
+  TypeCode baseTypeCode = CType::GetTypeCode(baseType);
+  if (baseTypeCode == TYPE_void_t) {
+    return true;
+  }
+  TypeCode elementTypeCode;
+  switch (JS_GetTypedArrayType(valObj, cx)) {
+  case TypedArray::TYPE_INT8:
+    elementTypeCode = TYPE_int8_t;
+    break;
+  case TypedArray::TYPE_UINT8:
+  case TypedArray::TYPE_UINT8_CLAMPED:
+    elementTypeCode = TYPE_uint8_t;
+    break;
+  case TypedArray::TYPE_INT16:
+    elementTypeCode = TYPE_int16_t;
+    break;
+  case TypedArray::TYPE_UINT16:
+    elementTypeCode = TYPE_uint16_t;
+    break;
+  case TypedArray::TYPE_INT32:
+    elementTypeCode = TYPE_int32_t;
+    break;
+  case TypedArray::TYPE_UINT32:
+    elementTypeCode = TYPE_uint32_t;
+    break;
+  case TypedArray::TYPE_FLOAT32:
+    elementTypeCode = TYPE_float32_t;
+    break;
+  case TypedArray::TYPE_FLOAT64:
+    elementTypeCode = TYPE_float64_t;
+    break;
+  default:
+    return false;
+  }
+  return elementTypeCode == baseTypeCode;
+}
+
 // Implicitly convert jsval 'val' to a C binary representation of CType
 // 'targetType', storing the result in 'buffer'. Adequate space must be
 // provided in 'buffer' by the caller. This function generally does minimal
 // coercion between types. There are two cases in which this function is used:
 // 1) The target buffer is internal to a CData object; we simply write data
 //    into it.
 // 2) We are converting an argument for an ffi call, in which case 'isArgument'
 //    will be true. This allows us to handle a special case: if necessary,
@@ -2188,16 +2229,26 @@ ImplicitConvert(JSContext* cx,
       }
       break;
     } else if (!JSVAL_IS_PRIMITIVE(val) && JS_IsArrayBufferObject(valObj, cx)) {
       // Convert ArrayBuffer to pointer without any copy.
       // Just as with C arrays, we make no effort to
       // keep the ArrayBuffer alive.
       *static_cast<void**>(buffer) = JS_GetArrayBufferData(valObj, cx);
       break;
+    } if (!JSVAL_IS_PRIMITIVE(val) && JS_IsTypedArrayObject(valObj, cx)) {
+      if(!CanConvertTypedArrayItemTo(baseType, valObj, cx)) {
+        return TypeError(cx, "typed array with the appropriate type", val);
+      }
+
+      // Convert TypedArray to pointer without any copy.
+      // Just as with C arrays, we make no effort to
+      // keep the TypedArray alive.
+      *static_cast<void**>(buffer) = JS_GetArrayBufferViewData(valObj, cx);
+      break;
     }
     return TypeError(cx, "pointer", val);
   }
   case TYPE_array: {
     JSObject* baseType = ArrayType::GetBaseType(targetType);
     size_t targetLength = ArrayType::GetLength(targetType);
 
     if (JSVAL_IS_STRING(val)) {
@@ -2277,29 +2328,43 @@ ImplicitConvert(JSContext* cx,
           return false;
       }
 
       memcpy(buffer, intermediate.get(), arraySize);
 
     } else if (!JSVAL_IS_PRIMITIVE(val) &&
                JS_IsArrayBufferObject(valObj, cx)) {
       // Check that array is consistent with type, then
-      // copy the array. As with C arrays, data is *not*
-      // copied back to the ArrayBuffer at the end of a
-      // function call, so do not expect this to work
-      // as an inout argument.
+      // copy the array.
       uint32_t sourceLength = JS_GetArrayBufferByteLength(valObj, cx);
       size_t elementSize = CType::GetSize(baseType);
       size_t arraySize = elementSize * targetLength;
       if (arraySize != size_t(sourceLength)) {
-        JS_ReportError(cx, "ArrayType length does not match source array length");
+        JS_ReportError(cx, "ArrayType length does not match source ArrayBuffer length");
         return false;
       }
       memcpy(buffer, JS_GetArrayBufferData(valObj, cx), sourceLength);
       break;
+    }  else if (!JSVAL_IS_PRIMITIVE(val) &&
+               JS_IsTypedArrayObject(valObj, cx)) {
+      // Check that array is consistent with type, then
+      // copy the array.
+      if(!CanConvertTypedArrayItemTo(baseType, valObj, cx)) {
+        return TypeError(cx, "typed array with the appropriate type", val);
+      }
+
+      uint32_t sourceLength = JS_GetTypedArrayByteLength(valObj, cx);
+      size_t elementSize = CType::GetSize(baseType);
+      size_t arraySize = elementSize * targetLength;
+      if (arraySize != size_t(sourceLength)) {
+        JS_ReportError(cx, "typed array length does not match source TypedArray length");
+        return false;
+      }
+      memcpy(buffer, JS_GetArrayBufferViewData(valObj, cx), sourceLength);
+      break;
     } else {
       // Don't implicitly convert to string. Users can implicitly convert
       // with `String(x)` or `""+x`.
       return TypeError(cx, "array", val);
     }
     break;
   }
   case TYPE_struct: {
--- a/toolkit/components/ctypes/tests/unit/test_jsctypes.js.in
+++ b/toolkit/components/ctypes/tests/unit/test_jsctypes.js.in
@@ -1747,27 +1747,91 @@ function run_PointerType_tests() {
   let z_t = ctypes.int32_t.array().ptr;
   do_check_eq(ptrValue(z_t()), 0);
   do_check_throws(function() { z_t().contents }, Error);
   z_t = ctypes.int32_t.array(0).ptr;
   do_check_eq(ptrValue(z_t()), 0);
   let z = ctypes.int32_t.array(0)().address();
   do_check_eq(z.contents.length, 0);
 
-  // Check that you can use an ArrayBuffer as a pointer
-  let c_arraybuffer = new ArrayBuffer(24);
-  let c_int32 = new Int32Array(c_arraybuffer);
-  for (let i = 0; i < 6; ++i) {
-    c_int32[i] = i;
-  }
-  let c_ptr = ctypes.int32_t.ptr(c_arraybuffer);
-  do_check_eq(c_ptr.contents, 0);
-  let c_array = ctypes.cast(c_ptr, ctypes.ArrayType(ctypes.int32_t, 6).ptr).contents;
-  for (let i = 0; i < 6; ++i) {
-    do_check_eq(c_array[i], c_int32[i]);
+  // Check that you can use an ArrayBuffer or a typed array as a pointer
+  let c_arraybuffer = new ArrayBuffer(256);
+  let typed_array_samples =
+       [
+         [new Int8Array(c_arraybuffer), ctypes.int8_t],
+         [new Uint8Array(c_arraybuffer), ctypes.uint8_t],
+         [new Int16Array(c_arraybuffer), ctypes.int16_t],
+         [new Uint16Array(c_arraybuffer), ctypes.uint16_t],
+         [new Int32Array(c_arraybuffer), ctypes.int32_t],
+         [new Uint32Array(c_arraybuffer), ctypes.uint32_t],
+         [new Float32Array(c_arraybuffer), ctypes.float32_t],
+         [new Float64Array(c_arraybuffer), ctypes.float64_t]
+        ];
+  for (let i = 0; i < typed_array_samples.length; ++i) {
+    for (let j = 0; j < typed_array_samples.length; ++j) {
+      let view = typed_array_samples[i][0];
+      let item_type = typed_array_samples[j][1];
+      let number_of_items = c_arraybuffer.byteLength / item_type.size;
+      let array_type = item_type.array(number_of_items);
+
+      if (i != j) {
+        do_print("Checking that typed array " + (view.constructor.name) +
+                 " canNOT be converted to " + item_type + " pointer/array");
+        do_check_throws(function() { item_type.ptr(view); }, TypeError);
+        do_check_throws(function() { array_type(view); }, TypeError);
+
+      } else {
+        do_print("Checking that typed array " + (view.constructor.name) +
+                 " can be converted to " + item_type + " pointer/array");
+        // Fill buffer using view
+        for (let k = 0; k < number_of_items; ++k) {
+          view[k] = k;
+        }
+
+        // Convert ArrayBuffer to pointer then array and check contents
+        let c_ptr = item_type.ptr(c_arraybuffer);
+        let c_array = ctypes.cast(c_ptr, array_type.ptr).contents;
+        for (let k = 0; k < number_of_items; ++k) {
+          do_check_eq(c_array[k], view[k]);
+        }
+
+        // Convert view to pointer then array and check contents
+        c_ptr = item_type.ptr(view);
+        c_array = ctypes.cast(c_ptr, array_type.ptr).contents;
+        for (let k = 0; k < number_of_items; ++k) {
+          do_check_eq(c_array[k], view[k]);
+        }
+
+        // Convert ArrayBuffer to array of the right size and check contents
+        c_array = array_type(c_arraybuffer);
+        for (let k = 0; k < number_of_items; ++k) {
+          do_check_eq(c_array[k], view[k]);
+        }
+
+        // Convert typed array to array of the right size and check contents
+        c_array = array_type(view);
+        for (let k = 0; k < number_of_items; ++k) {
+          do_check_eq(c_array[k], view[k]);
+        }
+
+        let array_type_too_large = item_type.array(number_of_items + 1);
+        let array_type_too_small = item_type.array(number_of_items - 1);
+
+        do_check_throws(function() { array_type_too_large(c_arraybuffer); }, Error);
+        do_check_throws(function() { array_type_too_small(c_arraybuffer); }, Error);
+        do_check_throws(function() { array_type_too_large(view); }, Error);
+        do_check_throws(function() { array_type_too_small(view); }, Error)
+
+        // Convert array to void*
+        ctypes.voidptr_t(c_arraybuffer);
+
+        // Convert view to void*
+        ctypes.voidptr_t(view);
+      }
+    }
   }
 }
 
 function run_FunctionType_tests() {
   run_type_ctor_class_tests(ctypes.FunctionType,
     ctypes.FunctionType(ctypes.default_abi, ctypes.void_t),
     ctypes.FunctionType(ctypes.default_abi, ctypes.void_t, [ ctypes.int32_t ]),
     [ "abi", "returnType", "argTypes", "isVariadic" ],
@@ -2005,35 +2069,16 @@ function run_ArrayType_tests() {
 
   c.value = c;
   do_check_eq(c[3], 4);
   do_check_throws(function() { c.value; }, Error);
   do_check_throws(function() { c.value = [1, 2, 3, 4, 5]; }, Error);
   do_check_throws(function() { c.value = [1, 2, 3, 4, 5, 6, 7]; }, Error);
   do_check_throws(function() { c.value = [1, 2, 7.4, 4, 5, 6]; }, TypeError);
   do_check_throws(function() { c.value = []; }, Error);
-
-  // Check that you can use an ArrayBuffer with the right size for an array
-  let c_arraybuffer = new ArrayBuffer(24);
-  let c_int32 = new Int32Array(c_arraybuffer);
-  for (let i = 0; i < 6; ++i) {
-    c_int32[i] = i + 101;
-  }
-  c.value = c_arraybuffer;
-  do_check_eq(c[3], 104);
-  do_check_eq(c.toSource(), "ctypes.int32_t.array(6)([101, 102, 103, 104, 105, 106])");
-  do_check_eq(c.toSource(), c.toString());
-  eval("let c2 = " + c.toSource());
-  do_check_eq(c2.constructor.name, "int32_t[6]");
-  do_check_eq(c2.length, 6);
-  do_check_eq(c2[3], c[3]);
-
-  // Check that you cannot use an ArrayBuffer with the wrong size for an array
-  c_arraybuffer = new ArrayBuffer(20);
-  do_check_throws(function () { c.value = c_arraybuffer; }, Error);
 }
 
 function run_type_toString_tests() {
   var c = ctypes;
 
   // Figure out whether we can create functions with ctypes.stdcall_abi and ctypes.winapi_abi.
   var haveStdCallABI;
   try {
--- a/toolkit/components/ctypes/tests/xpcshellTestHarnessAdaptor.js
+++ b/toolkit/components/ctypes/tests/xpcshellTestHarnessAdaptor.js
@@ -33,16 +33,20 @@ function do_check_eq(left, right, stack)
 function do_check_true(condition, stack) {
   do_check_eq(condition, true, stack);
 }
 
 function do_check_false(condition, stack) {
   do_check_eq(condition, false, stack);
 }
 
+function do_print(text) {
+  dump("INFO: " + text + "\n");
+}
+
 function FileFaker(path) {
   this._path = path;
 }
 FileFaker.prototype = {
   get path() {
     return this._path;
   },
   get parent() {