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 idunknown
push userunknown
push dateunknown
reviewersWaldo, final
bugs589413
milestone2.0b6pre
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|).
  *