Bug 589413 - Failure to open libnss3.so when Firefox path contains UTF-8 characters. Part 1: ctypes implementation. r=Waldo, a=final+
authorDan Witte <dwitte@mozilla.com>
Mon, 13 Sep 2010 10:54:02 -0700
changeset 53704 07612386060bad4c29db8fe420cf36ee7b716b3e
parent 53703 fdc34f208b336f6bef9721c03b3cf12e01e4bd70
child 53705 313342d1b5a962f7430f63e183df6c3d7018f58c
push id15680
push userdwitte@mozilla.com
push dateMon, 13 Sep 2010 17:55:00 +0000
treeherdermozilla-central@7b0f220344a9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersWaldo, final
bugs589413
milestone2.0b6pre
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 589413 - Failure to open libnss3.so when Firefox path contains UTF-8 characters. Part 1: ctypes implementation. r=Waldo, a=final+
js/src/ctypes/CTypes.cpp
js/src/ctypes/CTypes.h
js/src/ctypes/Library.cpp
js/src/ctypes/Library.h
js/src/jsapi.h
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -215,16 +215,26 @@ namespace UInt64 {
   static JSBool Hi(JSContext* cx, uintN argc, jsval* vp);
   static JSBool Join(JSContext* cx, uintN argc, jsval* vp);
 }
 
 /*******************************************************************************
 ** JSClass definitions and initialization functions
 *******************************************************************************/
 
+// Class representing the 'ctypes' object itself. This exists to contain the
+// JSCTypesCallbacks set of function pointers.
+static JSClass sCTypesGlobalClass = {
+  "ctypes",
+  JSCLASS_HAS_RESERVED_SLOTS(CTYPESGLOBAL_SLOTS),
+  JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
+  JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
+  JSCLASS_NO_OPTIONAL_MEMBERS
+};
+
 static JSClass sCABIClass = {
   "CABI",
   JSCLASS_HAS_RESERVED_SLOTS(CABI_SLOTS),
   JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
   JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
   JSCLASS_NO_OPTIONAL_MEMBERS
 };
 
@@ -906,42 +916,75 @@ InitTypeClasses(JSContext* cx, JSObject*
     return false;
   if (!JS_DefineProperty(cx, parent, "voidptr_t", OBJECT_TO_JSVAL(typeObj),
          NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT))
     return false;
 
   return true;
 }
 
+bool
+IsCTypesGlobal(JSContext* cx, JSObject* obj)
+{
+  return JS_GET_CLASS(cx, obj) == &sCTypesGlobalClass;
+}
+
+// Get the JSCTypesCallbacks struct from the 'ctypes' object 'obj'.
+JSCTypesCallbacks*
+GetCallbacks(JSContext* cx, JSObject* obj)
+{
+  JS_ASSERT(IsCTypesGlobal(cx, obj));
+
+  jsval result;
+  ASSERT_OK(JS_GetReservedSlot(cx, obj, SLOT_CALLBACKS, &result));
+  if (JSVAL_IS_VOID(result))
+    return NULL;
+
+  return static_cast<JSCTypesCallbacks*>(JSVAL_TO_PRIVATE(result));
+}
+
 JS_BEGIN_EXTERN_C
 
 JS_PUBLIC_API(JSBool)
 JS_InitCTypesClass(JSContext* cx, JSObject* global)
 {
   // attach ctypes property to global object
-  JSObject* ctypes = JS_NewObject(cx, NULL, NULL, NULL);
+  JSObject* ctypes = JS_NewObject(cx, &sCTypesGlobalClass, NULL, NULL);
   if (!ctypes)
     return false;
 
   if (!JS_DefineProperty(cx, global, "ctypes", OBJECT_TO_JSVAL(ctypes),
-                         JS_PropertyStub, JS_PropertyStub, JSPROP_READONLY | JSPROP_PERMANENT)) {
+         JS_PropertyStub, JS_PropertyStub, JSPROP_READONLY | JSPROP_PERMANENT)) {
     return false;
   }
 
   if (!InitTypeClasses(cx, ctypes))
     return false;
 
   // attach API functions
   if (!JS_DefineFunctions(cx, ctypes, sModuleFunctions))
     return false;
 
   // Seal the ctypes object, to prevent modification.
   return JS_SealObject(cx, ctypes, JS_FALSE);
 }
 
+JS_PUBLIC_API(JSBool)
+JS_SetCTypesCallbacks(JSContext* cx,
+                      JSObject* ctypesObj,
+                      JSCTypesCallbacks* callbacks)
+{
+  JS_ASSERT(callbacks);
+  JS_ASSERT(IsCTypesGlobal(cx, ctypesObj));
+
+  // Set the callbacks on a reserved slot.
+  return JS_SetReservedSlot(cx, ctypesObj, SLOT_CALLBACKS,
+    PRIVATE_TO_JSVAL(callbacks));
+}
+
 JS_END_EXTERN_C
 
 /*******************************************************************************
 ** Type conversion functions
 *******************************************************************************/
 
 // Enforce some sanity checks on type widths and properties.
 // Where the architecture is 64-bit, make sure it's LP64 or LLP64. (ctypes.int
@@ -3051,19 +3094,17 @@ CType::PtrGetter(JSContext* cx, JSObject
   *vp = OBJECT_TO_JSVAL(pointerType);
   return JS_TRUE;
 }
 
 JSBool
 CType::CreateArray(JSContext* cx, uintN argc, jsval* vp)
 {
   JSObject* baseType = JS_THIS_OBJECT(cx, vp);
-  JS_ASSERT(baseType);
-
-  if (!CType::IsCType(cx, baseType)) {
+  if (!baseType || !CType::IsCType(cx, baseType)) {
     JS_ReportError(cx, "not a CType");
     return JS_FALSE;
   }
 
   // Construct and return a new ArrayType object.
   if (argc > 1) {
     JS_ReportError(cx, "array takes zero or one argument");
     return JS_FALSE;
@@ -3084,19 +3125,17 @@ CType::CreateArray(JSContext* cx, uintN 
   JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(result));
   return JS_TRUE;
 }
 
 JSBool
 CType::ToString(JSContext* cx, uintN argc, jsval* vp)
 {
   JSObject* obj = JS_THIS_OBJECT(cx, vp);
-  JS_ASSERT(obj);
-
-  if (!CType::IsCType(cx, obj)) {
+  if (!obj || !CType::IsCType(cx, obj)) {
     JS_ReportError(cx, "not a CType");
     return JS_FALSE;
   }
 
   AutoString type;
   AppendString(type, "type ");
   AppendString(type, GetName(cx, obj));
 
@@ -3107,19 +3146,17 @@ CType::ToString(JSContext* cx, uintN arg
   JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(result));
   return JS_TRUE;
 }
 
 JSBool
 CType::ToSource(JSContext* cx, uintN argc, jsval* vp)
 {
   JSObject* obj = JS_THIS_OBJECT(cx, vp);
-  JS_ASSERT(obj);
-
-  if (!CType::IsCType(cx, obj)) {
+  if (!obj || !CType::IsCType(cx, obj)) {
     JS_ReportError(cx, "not a CType");
     return JS_FALSE;
   }
 
   AutoString source;
   BuildTypeSource(cx, obj, false, source);
   JSString* result = NewUCString(cx, source);
   if (!result)
@@ -3302,19 +3339,17 @@ PointerType::TargetTypeGetter(JSContext*
   JS_ASSERT(JSVAL_IS_OBJECT(*vp));
   return JS_TRUE;
 }
 
 JSBool
 PointerType::IsNull(JSContext* cx, uintN argc, jsval* vp)
 {
   JSObject* obj = JS_THIS_OBJECT(cx, vp);
-  JS_ASSERT(obj);
-
-  if (!CData::IsCData(cx, obj)) {
+  if (!obj || !CData::IsCData(cx, obj)) {
     JS_ReportError(cx, "not a CData");
     return JS_FALSE;
   }
 
   // Get pointer type and base type.
   JSObject* typeObj = CData::GetCType(cx, obj);
   if (CType::GetTypeCode(cx, typeObj) != TYPE_pointer) {
     JS_ReportError(cx, "not a PointerType");
@@ -3788,19 +3823,17 @@ ArrayType::Setter(JSContext* cx, JSObjec
   char* data = static_cast<char*>(CData::GetData(cx, obj)) + elementSize * index;
   return ImplicitConvert(cx, *vp, baseType, data, false, NULL);
 }
 
 JSBool
 ArrayType::AddressOfElement(JSContext* cx, uintN argc, jsval* vp)
 {
   JSObject* obj = JS_THIS_OBJECT(cx, vp);
-  JS_ASSERT(obj);
-
-  if (!CData::IsCData(cx, obj)) {
+  if (!obj || !CData::IsCData(cx, obj)) {
     JS_ReportError(cx, "not a CData");
     return JS_FALSE;
   }
 
   JSObject* typeObj = CData::GetCType(cx, obj);
   if (CType::GetTypeCode(cx, typeObj) != TYPE_array) {
     JS_ReportError(cx, "not an ArrayType");
     return JS_FALSE;
@@ -4170,19 +4203,19 @@ StructType::BuildFFIType(JSContext* cx, 
   elements.forget();
   return ffiType.forget();
 }
 
 JSBool
 StructType::Define(JSContext* cx, uintN argc, jsval* vp)
 {
   JSObject* obj = JS_THIS_OBJECT(cx, vp);
-  JS_ASSERT(obj);
-
-  if (!CType::IsCType(cx, obj) || CType::GetTypeCode(cx, obj) != TYPE_struct) {
+  if (!obj ||
+      !CType::IsCType(cx, obj) ||
+      CType::GetTypeCode(cx, obj) != TYPE_struct) {
     JS_ReportError(cx, "not a StructType");
     return JS_FALSE;
   }
 
   if (CType::IsSizeDefined(cx, obj)) {
     JS_ReportError(cx, "StructType has already been defined");
     return JS_FALSE;
   }
@@ -4416,19 +4449,17 @@ StructType::FieldSetter(JSContext* cx, J
   char* data = static_cast<char*>(CData::GetData(cx, obj)) + field->mOffset;
   return ImplicitConvert(cx, *vp, field->mType, data, false, NULL);
 }
 
 JSBool
 StructType::AddressOfField(JSContext* cx, uintN argc, jsval* vp)
 {
   JSObject* obj = JS_THIS_OBJECT(cx, vp);
-  JS_ASSERT(obj);
-
-  if (!CData::IsCData(cx, obj)) {
+  if (!obj || !CData::IsCData(cx, obj)) {
     JS_ReportError(cx, "not a CData");
     return JS_FALSE;
   }
 
   JSObject* typeObj = CData::GetCType(cx, obj);
   if (CType::GetTypeCode(cx, typeObj) != TYPE_struct) {
     JS_ReportError(cx, "not a StructType");
     return JS_FALSE;
@@ -5564,19 +5595,17 @@ JSBool
 CData::Address(JSContext* cx, uintN argc, jsval* vp)
 {
   if (argc != 0) {
     JS_ReportError(cx, "address takes zero arguments");
     return JS_FALSE;
   }
 
   JSObject* obj = JS_THIS_OBJECT(cx, vp);
-  JS_ASSERT(obj);
-
-  if (!IsCData(cx, obj)) {
+  if (!obj || !IsCData(cx, obj)) {
     JS_ReportError(cx, "not a CData");
     return JS_FALSE;
   }
 
   JSObject* typeObj = CData::GetCType(cx, obj);
   JSObject* pointerType = PointerType::CreateInternal(cx, typeObj);
   if (!pointerType)
     return JS_FALSE;
@@ -5642,19 +5671,17 @@ JSBool
 CData::ReadString(JSContext* cx, uintN argc, jsval* vp)
 {
   if (argc != 0) {
     JS_ReportError(cx, "readString takes zero arguments");
     return JS_FALSE;
   }
 
   JSObject* obj = JS_THIS_OBJECT(cx, vp);
-  JS_ASSERT(obj);
-
-  if (!IsCData(cx, obj)) {
+  if (!obj || !IsCData(cx, obj)) {
     JS_ReportError(cx, "not a CData");
     return JS_FALSE;
   }
 
   // Make sure we are a pointer to, or an array of, an 8-bit or 16-bit
   // character or integer type.
   JSObject* baseType;
   JSObject* typeObj = GetCType(cx, obj);
@@ -5735,17 +5762,17 @@ JSBool
 CData::ToSource(JSContext* cx, uintN argc, jsval* vp)
 {
   if (argc != 0) {
     JS_ReportError(cx, "toSource takes zero arguments");
     return JS_FALSE;
   }
 
   JSObject* obj = JS_THIS_OBJECT(cx, vp);
-  if (!CData::IsCData(cx, obj)) {
+  if (!obj || !CData::IsCData(cx, obj)) {
     JS_ReportError(cx, "not a CData");
     return JS_FALSE;
   }
 
   JSObject* typeObj = CData::GetCType(cx, obj);
   void* data = CData::GetData(cx, obj);
 
   // Walk the types, building up the toSource() string.
@@ -5928,29 +5955,29 @@ Int64::IsInt64(JSContext* cx, JSObject* 
 {
   return JS_GET_CLASS(cx, obj) == &sInt64Class;
 }
 
 JSBool
 Int64::ToString(JSContext* cx, uintN argc, jsval* vp)
 {
   JSObject* obj = JS_THIS_OBJECT(cx, vp);
-  if (!Int64::IsInt64(cx, obj)) {
+  if (!obj || !Int64::IsInt64(cx, obj)) {
     JS_ReportError(cx, "not an Int64");
     return JS_FALSE;
   }
 
   return Int64Base::ToString(cx, obj, argc, vp, false);
 }
 
 JSBool
 Int64::ToSource(JSContext* cx, uintN argc, jsval* vp)
 {
   JSObject* obj = JS_THIS_OBJECT(cx, vp);
-  if (!Int64::IsInt64(cx, obj)) {
+  if (!obj || !Int64::IsInt64(cx, obj)) {
     JS_ReportError(cx, "not an Int64");
     return JS_FALSE;
   }
 
   return Int64Base::ToSource(cx, obj, argc, vp, false);
 }
 
 JSBool
@@ -6100,29 +6127,29 @@ UInt64::IsUInt64(JSContext* cx, JSObject
 {
   return JS_GET_CLASS(cx, obj) == &sUInt64Class;
 }
 
 JSBool
 UInt64::ToString(JSContext* cx, uintN argc, jsval* vp)
 {
   JSObject* obj = JS_THIS_OBJECT(cx, vp);
-  if (!UInt64::IsUInt64(cx, obj)) {
+  if (!obj || !UInt64::IsUInt64(cx, obj)) {
     JS_ReportError(cx, "not a UInt64");
     return JS_FALSE;
   }
 
   return Int64Base::ToString(cx, obj, argc, vp, true);
 }
 
 JSBool
 UInt64::ToSource(JSContext* cx, uintN argc, jsval* vp)
 {
   JSObject* obj = JS_THIS_OBJECT(cx, vp);
-  if (!UInt64::IsUInt64(cx, obj)) {
+  if (!obj || !UInt64::IsUInt64(cx, obj)) {
     JS_ReportError(cx, "not a UInt64");
     return JS_FALSE;
   }
 
   return Int64Base::ToSource(cx, obj, argc, vp, true);
 }
 
 JSBool
--- a/js/src/ctypes/CTypes.h
+++ b/js/src/ctypes/CTypes.h
@@ -332,31 +332,40 @@ struct ClosureInfo
   JSObject* thisObj;     // 'this' object to use for the JS function call
   JSObject* jsfnObj;     // JS function
   ffi_closure* closure;  // The C closure itself
 #ifdef DEBUG
   jsword cxThread;       // The thread on which the context may be used
 #endif
 };
 
+bool IsCTypesGlobal(JSContext* cx, JSObject* obj);
+
+JSCTypesCallbacks* GetCallbacks(JSContext* cx, JSObject* obj);
+
 JSBool InitTypeClasses(JSContext* cx, JSObject* parent);
 
 JSBool ConvertToJS(JSContext* cx, JSObject* typeObj, JSObject* dataObj,
   void* data, bool wantPrimitive, bool ownResult, jsval* result);
 
 JSBool ImplicitConvert(JSContext* cx, jsval val, JSObject* targetType,
   void* buffer, bool isArgument, bool* freePointer);
 
 JSBool ExplicitConvert(JSContext* cx, jsval val, JSObject* targetType,
   void* buffer);
 
 /*******************************************************************************
 ** JSClass reserved slot definitions
 *******************************************************************************/
 
+enum CTypesGlobalSlot {
+  SLOT_CALLBACKS = 0, // pointer to JSCTypesCallbacks struct
+  CTYPESGLOBAL_SLOTS
+};
+
 enum CABISlot {
   SLOT_ABICODE = 0, // ABICode of the CABI object
   CABI_SLOTS
 };
 
 enum CTypeProtoSlot {
   SLOT_POINTERPROTO      = 0,  // ctypes.PointerType.prototype object
   SLOT_ARRAYPROTO        = 1,  // ctypes.ArrayType.prototype object
@@ -439,16 +448,17 @@ namespace CType {
   size_t GetSize(JSContext* cx, JSObject* obj);
   bool GetSafeSize(JSContext* cx, JSObject* obj, size_t* result);
   bool IsSizeDefined(JSContext* cx, JSObject* obj);
   size_t GetAlignment(JSContext* cx, JSObject* obj);
   ffi_type* GetFFIType(JSContext* cx, JSObject* obj);
   JSString* GetName(JSContext* cx, JSObject* obj);
   JSObject* GetProtoFromCtor(JSContext* cx, JSObject* obj, CTypeProtoSlot slot);
   JSObject* GetProtoFromType(JSContext* cx, JSObject* obj, CTypeProtoSlot slot);
+  JSCTypesCallbacks* GetCallbacksFromType(JSContext* cx, JSObject* obj);
 }
 
 namespace PointerType {
   JSObject* CreateInternal(JSContext* cx, JSObject* baseType);
 
   JSObject* GetBaseType(JSContext* cx, JSObject* obj);
 }
 
--- a/js/src/ctypes/Library.cpp
+++ b/js/src/ctypes/Library.cpp
@@ -107,60 +107,77 @@ Library::Name(JSContext* cx, uintN argc,
   if (!result)
     return JS_FALSE;
 
   JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(result));
   return JS_TRUE;
 }
 
 JSObject*
-Library::Create(JSContext* cx, jsval aPath)
+Library::Create(JSContext* cx, jsval path, JSCTypesCallbacks* callbacks)
 {
   JSObject* libraryObj = JS_NewObject(cx, &sLibraryClass, NULL, NULL);
   if (!libraryObj)
     return NULL;
   js::AutoObjectRooter root(cx, libraryObj);
 
   // initialize the library
   if (!JS_SetReservedSlot(cx, libraryObj, SLOT_LIBRARY, PRIVATE_TO_JSVAL(NULL)))
     return NULL;
 
   // attach API functions
   if (!JS_DefineFunctions(cx, libraryObj, sLibraryFunctions))
     return NULL;
 
-  if (!JSVAL_IS_STRING(aPath)) {
+  if (!JSVAL_IS_STRING(path)) {
     JS_ReportError(cx, "open takes a string argument");
     return NULL;
   }
 
   PRLibSpec libSpec;
+  JSString* pathStr = JSVAL_TO_STRING(path);
 #ifdef XP_WIN
   // On Windows, converting to native charset may corrupt path string.
   // So, we have to use Unicode path directly.
-  const PRUnichar* path = reinterpret_cast<const PRUnichar*>(
-    JS_GetStringCharsZ(cx, JSVAL_TO_STRING(aPath)));
-  if (!path)
+  const PRUnichar* pathChars = reinterpret_cast<const PRUnichar*>(
+    JS_GetStringCharsZ(cx, pathStr));
+  if (!pathChars)
     return NULL;
 
-  libSpec.value.pathname_u = path;
+  libSpec.value.pathname_u = pathChars;
   libSpec.type = PR_LibSpec_PathnameU;
 #else
-  // Assume the JS string is not UTF-16, but is in the platform's native
-  // charset. (This basically means ASCII.) It would be nice to have a
-  // UTF-16 -> native charset implementation available. :(
-  const char* path = JS_GetStringBytesZ(cx, JSVAL_TO_STRING(aPath));
-  if (!path)
+  // Convert to platform native charset if the appropriate callback has been
+  // provided.
+  const char* pathBytes;
+  bool requireFree = false;
+  if (callbacks && callbacks->unicodeToNative) {
+    pathBytes = 
+      callbacks->unicodeToNative(cx, pathStr->chars(), pathStr->length());
+    requireFree = true;
+
+  } else {
+    // Fallback: sssume the platform native charset is UTF-8. This is true
+    // for Mac OS X, Android, and probably Linux.
+    pathBytes = JS_GetStringBytesZ(cx, pathStr);
+  }
+
+  if (!pathBytes)
     return NULL;
 
-  libSpec.value.pathname = path;
+  libSpec.value.pathname = pathBytes;
   libSpec.type = PR_LibSpec_Pathname;
 #endif
 
   PRLibrary* library = PR_LoadLibraryWithFlags(libSpec, 0);
+#ifndef XP_WIN
+  if (requireFree) {
+    JS_free(cx, const_cast<char*>(pathBytes));
+  }
+#endif
   if (!library) {
     JS_ReportError(cx, "couldn't open library");
     return NULL;
   }
 
   // stash the library
   if (!JS_SetReservedSlot(cx, libraryObj, SLOT_LIBRARY,
          PRIVATE_TO_JSVAL(library)))
@@ -192,34 +209,40 @@ Library::Finalize(JSContext* cx, JSObjec
   PRLibrary* library = GetLibrary(cx, obj);
   if (library)
     PR_UnloadLibrary(library);
 }
 
 JSBool
 Library::Open(JSContext* cx, uintN argc, jsval *vp)
 {
+  JSObject* ctypesObj = JS_THIS_OBJECT(cx, vp);
+  if (!ctypesObj || !IsCTypesGlobal(cx, ctypesObj)) {
+    JS_ReportError(cx, "not a ctypes object");
+    return JS_FALSE;
+  }
+
   if (argc != 1 || JSVAL_IS_VOID(JS_ARGV(cx, vp)[0])) {
     JS_ReportError(cx, "open requires a single argument");
     return JS_FALSE;
   }
 
-  JSObject* library = Create(cx, JS_ARGV(cx, vp)[0]);
+  JSObject* library = Create(cx, JS_ARGV(cx, vp)[0], GetCallbacks(cx, ctypesObj));
   if (!library)
     return JS_FALSE;
 
   JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(library));
   return JS_TRUE;
 }
 
 JSBool
 Library::Close(JSContext* cx, uintN argc, jsval* vp)
 {
   JSObject* obj = JS_THIS_OBJECT(cx, vp);
-  if (!IsLibrary(cx, obj)) {
+  if (!obj || !IsLibrary(cx, obj)) {
     JS_ReportError(cx, "not a library");
     return JS_FALSE;
   }
 
   if (argc != 0) {
     JS_ReportError(cx, "close doesn't take any arguments");
     return JS_FALSE;
   }
@@ -231,17 +254,17 @@ Library::Close(JSContext* cx, uintN argc
   JS_SET_RVAL(cx, vp, JSVAL_VOID);
   return JS_TRUE;
 }
 
 JSBool
 Library::Declare(JSContext* cx, uintN argc, jsval* vp)
 {
   JSObject* obj = JS_THIS_OBJECT(cx, vp);
-  if (!IsLibrary(cx, obj)) {
+  if (!obj || !IsLibrary(cx, obj)) {
     JS_ReportError(cx, "not a library");
     return JS_FALSE;
   }
 
   PRLibrary* library = GetLibrary(cx, obj);
   if (!library) {
     JS_ReportError(cx, "library not open");
     return JS_FALSE;
@@ -250,18 +273,18 @@ Library::Declare(JSContext* cx, uintN ar
   // 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). 
+  //    accessors. If 'type' is a PointerType to 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");
--- a/js/src/ctypes/Library.h
+++ b/js/src/ctypes/Library.h
@@ -51,17 +51,17 @@ enum LibrarySlot {
   SLOT_LIBRARY = 0,
   LIBRARY_SLOTS
 };
 
 namespace Library
 {
   JSBool Name(JSContext* cx, uintN argc, jsval *vp);
 
-  JSObject* Create(JSContext* cx, jsval aPath);
+  JSObject* Create(JSContext* cx, jsval path, JSCTypesCallbacks* callbacks);
 
   bool IsLibrary(JSContext* cx, JSObject* obj);
   PRLibrary* GetLibrary(JSContext* cx, JSObject* obj);
 
   JSBool Open(JSContext* cx, uintN argc, jsval* vp);
 }
 
 }
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1055,16 +1055,44 @@ JS_GetGlobalForScopeChain(JSContext *cx)
 
 #ifdef JS_HAS_CTYPES
 /*
  * Initialize the 'ctypes' object on a global variable 'obj'. The 'ctypes'
  * object will be sealed.
  */
 extern JS_PUBLIC_API(JSBool)
 JS_InitCTypesClass(JSContext *cx, JSObject *global);
+
+/*
+ * Convert a unicode string 'source' of length 'slen' to the platform native
+ * charset, returning a null-terminated string allocated with JS_malloc. On
+ * failure, this function should report an error.
+ */
+typedef char *
+(* JSCTypesUnicodeToNativeFun)(JSContext *cx, const jschar *source, size_t slen);
+
+/*
+ * Set of function pointers that ctypes can use for various internal functions.
+ * See JS_SetCTypesCallbacks below. Providing NULL for a function is safe,
+ * and will result in the applicable ctypes functionality not being available.
+ */
+struct JSCTypesCallbacks {
+    JSCTypesUnicodeToNativeFun unicodeToNative;
+};
+
+typedef struct JSCTypesCallbacks JSCTypesCallbacks;
+
+/*
+ * Set the callbacks on the provided 'ctypesObj' object. 'callbacks' should be a
+ * pointer to static data that exists for the lifetime of 'ctypesObj', but it
+ * may safely be altered after calling this function and without having
+ * to call this function again.
+ */
+extern JS_PUBLIC_API(JSBool)
+JS_SetCTypesCallbacks(JSContext *cx, JSObject *ctypesObj, JSCTypesCallbacks *callbacks);
 #endif
 
 /*
  * Macros to hide interpreter stack layout details from a JSFastNative using
  * its jsval *vp parameter. The stack layout underlying invocation can't change
  * without breaking source and binary compatibility (argv[-2] is well-known to
  * be the callee jsval, and argv[-1] is as well known to be |this|).
  *