Bug 556329 - Allow ctypes to load exported data symbols. r=jorendorff
authorDan Witte <dwitte@mozilla.com>
Wed, 14 Apr 2010 10:36:48 -0700
changeset 40895 e8aaf1428150a29bdc5db7647397b016f2e2ac43
parent 40894 639ee520ad1a9f135fd2b1e67f69056fd5bcbe4d
child 40896 47949ff2d05809de87ad75b90123f422d3d9c688
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs556329
milestone1.9.3a5pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
Bug 556329 - Allow ctypes to load exported data symbols. r=jorendorff
js/src/ctypes/CTypes.cpp
js/src/ctypes/Library.cpp
toolkit/components/ctypes/tests/jsctypes-test.cpp
toolkit/components/ctypes/tests/jsctypes-test.h
toolkit/components/ctypes/tests/unit/test_jsctypes.js.in
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -5072,40 +5072,38 @@ CClosure::ClosureStub(ffi_cif* cif, void
 // * 'typeObj' must be a CType of defined (but possibly zero) size.
 //
 // * If an object 'refObj' is supplied, the new CData object stores the
 //   referent object in a reserved slot for GC safety, such that 'refObj' will
 //   be held alive by the resulting CData object. 'refObj' may or may not be
 //   a CData object; merely an object we want to keep alive.
 //   * If 'refObj' is a CData object, 'ownResult' must be false.
 //   * Otherwise, 'refObj' is a Library or CClosure object, and 'ownResult'
-//     must be true.
+//     may be true or false.
 // * Otherwise 'refObj' is NULL. In this case, 'ownResult' may be true or false.
 //
 // * If 'ownResult' is true, the CData object will allocate an appropriately
 //   sized buffer, and free it upon finalization. If 'source' data is
 //   supplied, the data will be copied from 'source' into the buffer;
 //   otherwise, the entirety of the new buffer will be initialized to zero.
 // * If 'ownResult' is false, the new CData's buffer refers to a slice of
-//   another CData's buffer given by 'refObj'. 'source' data must be provided,
+//   another buffer kept alive by 'refObj'. 'source' data must be provided,
 //   and the new CData's buffer will refer to 'source'.
 JSObject*
 CData::Create(JSContext* cx,
               JSObject* typeObj,
               JSObject* refObj,
               void* source,
               bool ownResult)
 {
   JS_ASSERT(typeObj);
   JS_ASSERT(CType::IsCType(cx, typeObj));
   JS_ASSERT(CType::IsSizeDefined(cx, typeObj));
   JS_ASSERT(ownResult || source);
-  if (refObj) {
-    JS_ASSERT(CData::IsCData(cx, refObj) ? !ownResult : ownResult);
-  }
+  JS_ASSERT_IF(refObj && CData::IsCData(cx, refObj), !ownResult);
 
   // Get the 'prototype' property from the type.
   jsval slot;
   ASSERT_OK(JS_GetReservedSlot(cx, typeObj, SLOT_PROTO, &slot));
   JS_ASSERT(!JSVAL_IS_PRIMITIVE(slot));
 
   JSObject* proto = JSVAL_TO_OBJECT(slot);
   JSObject* parent = JS_GetParent(cx, typeObj);
--- a/js/src/ctypes/Library.cpp
+++ b/js/src/ctypes/Library.cpp
@@ -210,55 +210,98 @@ Library::Declare(JSContext* cx, uintN ar
   }
 
   PRLibrary* library = GetLibrary(cx, obj);
   if (!library) {
     JS_ReportError(cx, "library not open");
     return JS_FALSE;
   }
 
-  // we always need at least a method name, a call type and a return type
-  if (argc < 3) {
-    JS_ReportError(cx, "declare requires at least three arguments");
+  // We allow two API variants:
+  // 1) library.declare(name, abi, returnType, argType1, ...)
+  //    declares a function with the given properties, and resolves the symbol
+  //    address in the library.
+  // 2) library.declare(name, type)
+  //    declares a symbol of 'type', and resolves it. The object that comes
+  //    back will be of type 'type', and will point into the symbol data.
+  //    This data will be both readable and writable via the usual CData
+  //    accessors. If 'type' is a FunctionType, the result will be a function
+  //    pointer, as with 1). 
+  if (argc < 2) {
+    JS_ReportError(cx, "declare requires at least two arguments");
     return JS_FALSE;
   }
 
   jsval* argv = JS_ARGV(cx, vp);
   if (!JSVAL_IS_STRING(argv[0])) {
     JS_ReportError(cx, "first argument must be a string");
     return JS_FALSE;
   }
 
   const char* name = JS_GetStringBytesZ(cx, JSVAL_TO_STRING(argv[0]));
   if (!name)
     return JS_FALSE;
 
-  PRFuncPtr func = PR_FindFunctionSymbol(library, name);
-  if (!func) {
-    JS_ReportError(cx, "couldn't find function symbol in library");
-    return JS_FALSE;
+  JSObject* typeObj;
+  js::AutoValueRooter root(cx);
+  bool isFunction = argc > 2;
+  if (isFunction) {
+    // Case 1).
+    // Create a FunctionType representing the function.
+    typeObj = FunctionType::CreateInternal(cx,
+                argv[1], argv[2], &argv[3], argc - 3);
+    if (!typeObj)
+      return JS_FALSE;
+    root.setObject(typeObj);
+
+  } else {
+    // Case 2).
+    if (JSVAL_IS_PRIMITIVE(argv[1]) ||
+        !CType::IsCType(cx, JSVAL_TO_OBJECT(argv[1]))) {
+      JS_ReportError(cx, "second argument must be a type");
+      return JS_FALSE;
+    }
+
+    typeObj = JSVAL_TO_OBJECT(argv[1]);
+    isFunction = CType::GetTypeCode(cx, typeObj) == TYPE_function;
   }
 
-  // Create a FunctionType representing the function.
-  JSObject* typeObj = FunctionType::CreateInternal(cx,
-                        argv[1], argv[2], &argv[3], argc - 3);
-  if (!typeObj)
-    return JS_FALSE;
-  js::AutoValueRooter root(cx, typeObj);
+  void* data;
+  PRFuncPtr fnptr;
+  if (isFunction) {
+    // Look up the function symbol.
+    fnptr = PR_FindFunctionSymbol(library, name);
+    if (!fnptr) {
+      JS_ReportError(cx, "couldn't find function symbol in library");
+      return JS_FALSE;
+    }
+    data = &fnptr;
 
-  JSObject* fn = CData::Create(cx, typeObj, obj, &func, true);
-  if (!fn)
+  } else {
+    // 'typeObj' is another data type. Look up the data symbol.
+    data = PR_FindSymbol(library, name);
+    if (!data) {
+      JS_ReportError(cx, "couldn't find symbol in library");
+      return JS_FALSE;
+    }
+  }
+
+  JSObject* result = CData::Create(cx, typeObj, obj, data, isFunction);
+  if (!result)
     return JS_FALSE;
 
-  JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(fn));
+  JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(result));
 
   // Seal the CData object, to prevent modification of the function pointer.
   // This permanently associates this object with the library, and avoids
   // having to do things like reset SLOT_REFERENT when someone tries to
   // change the pointer value.
   // XXX This will need to change when bug 541212 is fixed -- CData::ValueSetter
   // could be called on a sealed object.
-  return JS_SealObject(cx, fn, JS_FALSE);
+  if (isFunction && !JS_SealObject(cx, result, JS_FALSE))
+    return JS_FALSE;
+
+  return JS_TRUE;
 }
 
 }
 }
 
--- a/toolkit/components/ctypes/tests/jsctypes-test.cpp
+++ b/toolkit/components/ctypes/tests/jsctypes-test.cpp
@@ -386,8 +386,11 @@ test_vector_add_va_cdecl(PRUint8 num_vec
     result[i] = 0;
   for (i = 0; i < num_vecs; ++i) {
     PRInt32* vec = va_arg(list, PRInt32*);
     for (PRUint8 j = 0; j < vec_len; ++j)
       result[j] += vec[j];
   }
   return result;
 }
+
+RECT data_rect = { -1, -2, 3, 4 };
+
--- a/toolkit/components/ctypes/tests/jsctypes-test.h
+++ b/toolkit/components/ctypes/tests/jsctypes-test.h
@@ -197,9 +197,11 @@ NS_EXTERN_C
   NS_EXPORT void* test_getfn();
 
   EXPORT_CDECL(PRInt32) test_sum_va_cdecl(PRUint8 n, ...);
   EXPORT_CDECL(PRUint8) test_count_true_va_cdecl(PRUint8 n, ...);
   EXPORT_CDECL(void) test_add_char_short_int_va_cdecl(PRUint32* result, ...);
   EXPORT_CDECL(PRInt32*) test_vector_add_va_cdecl(PRUint8 num_vecs,
                                                   PRUint8 vec_len,
                                                   PRInt32* result, ...);
+
+  NS_EXPORT extern RECT data_rect;
 }
--- a/toolkit/components/ctypes/tests/unit/test_jsctypes.js.in
+++ b/toolkit/components/ctypes/tests/unit/test_jsctypes.js.in
@@ -187,16 +187,17 @@ function run_test()
   // Test ctypes.cast.
   run_cast_tests();
 
   run_string_tests(library);
   run_struct_tests(library);
   run_function_tests(library);
   run_closure_tests(library);
   run_variadic_tests(library);
+  run_static_data_tests(library);
 
   // test library.close
   let test_void_t = library.declare("test_void_t_cdecl", ctypes.default_abi, ctypes.void_t);
   library.close();
   do_check_throws(function() { test_void_t(); }, Error);
   do_check_throws(function() {
     library.declare("test_void_t_cdecl", ctypes.default_abi, ctypes.void_t);
   }, Error);
@@ -2077,16 +2078,23 @@ function run_function_tests(library)
 
   // Test that library.declare() returns data of type FunctionType, and that
   // it is immutable.
   do_check_true(test_ansi_len.constructor.__proto__ === ctypes.FunctionType.prototype);
   do_check_eq(test_ansi_len.constructor.toSource(),
     "ctypes.FunctionType(ctypes.default_abi, ctypes.int32_t, [ctypes.char.ptr])");
   do_check_throws(function() { test_ansi_len.value = null; }, Error);
   do_check_eq(ptrValue(test_ansi_len), ptrValue(ptr));
+
+  // Test that the library.declare(name, functionType) form works.
+  let test_ansi_len_2 = library.declare("test_ansi_len", fn_t);
+  do_check_true(test_ansi_len_2.constructor === fn_t);
+  do_check_eq(ptrValue(test_ansi_len), ptrValue(test_ansi_len_2));
+  do_check_throws(function() { test_ansi_len_2.value = null; }, Error);
+  do_check_eq(ptrValue(test_ansi_len_2), ptrValue(ptr));
 }
 
 function run_closure_tests(library)
 {
   run_single_closure_tests(library, ctypes.default_abi, "cdecl");
 #ifdef _WIN32
 #ifndef _WIN64
   run_single_closure_tests(library, ctypes.stdcall_abi, "stdcall");
@@ -2206,16 +2214,52 @@ function run_variadic_tests(library) {
   let sum_notva_type = ctypes.FunctionType(sum_va_type.abi,
                                            sum_va_type.returnType,
                                            [ctypes.uint8_t]);
   do_check_throws(function() {
     sum_va_type().value = sum_notva_type();
   }, Error);
 }
 
+function run_static_data_tests(library)
+{
+  const rect_t = new ctypes.StructType("RECT",
+                       [{ top   : ctypes.int32_t },
+                        { left  : ctypes.int32_t },
+                        { bottom: ctypes.int32_t },
+                        { right : ctypes.int32_t }]);
+
+  let data_rect = library.declare("data_rect", rect_t);
+
+  // Test reading static data.
+  do_check_true(data_rect.constructor === rect_t);
+  do_check_eq(data_rect.top, -1);
+  do_check_eq(data_rect.left, -2);
+  do_check_eq(data_rect.bottom, 3);
+  do_check_eq(data_rect.right, 4);
+
+  // Test writing.
+  data_rect.top = 9;
+  data_rect.left = 8;
+  data_rect.bottom = -11;
+  data_rect.right = -12;
+  do_check_eq(data_rect.top, 9);
+  do_check_eq(data_rect.left, 8);
+  do_check_eq(data_rect.bottom, -11);
+  do_check_eq(data_rect.right, -12);
+
+  // Make sure it's been written, not copied.
+  let data_rect_2 = library.declare("data_rect", rect_t);
+  do_check_eq(data_rect_2.top, 9);
+  do_check_eq(data_rect_2.left, 8);
+  do_check_eq(data_rect_2.bottom, -11);
+  do_check_eq(data_rect_2.right, -12);
+  do_check_eq(ptrValue(data_rect.address()), ptrValue(data_rect_2.address()));
+}
+
 // bug 522360 - try loading system library without full path
 function run_load_system_library()
 {
 #ifdef XP_WIN
   let syslib = ctypes.open("user32.dll");
 #elifdef XP_MACOSX
   let syslib = ctypes.open("libm.dylib");
 #elifdef XP_UNIX