michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * vim: set ts=2 sw=2 et tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "ctypes/Library.h" michael@0: michael@0: #include "prlink.h" michael@0: michael@0: #include "ctypes/CTypes.h" michael@0: michael@0: namespace js { michael@0: namespace ctypes { michael@0: michael@0: /******************************************************************************* michael@0: ** JSAPI function prototypes michael@0: *******************************************************************************/ michael@0: michael@0: namespace Library michael@0: { michael@0: static void Finalize(JSFreeOp *fop, JSObject* obj); michael@0: michael@0: static bool Close(JSContext* cx, unsigned argc, jsval* vp); michael@0: static bool Declare(JSContext* cx, unsigned argc, jsval* vp); michael@0: } michael@0: michael@0: /******************************************************************************* michael@0: ** JSObject implementation michael@0: *******************************************************************************/ michael@0: michael@0: typedef Rooted RootedFlatString; michael@0: michael@0: static const JSClass sLibraryClass = { michael@0: "Library", michael@0: JSCLASS_HAS_RESERVED_SLOTS(LIBRARY_SLOTS), michael@0: JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, michael@0: JS_EnumerateStub,JS_ResolveStub, JS_ConvertStub, Library::Finalize michael@0: }; michael@0: michael@0: #define CTYPESFN_FLAGS \ michael@0: (JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT) michael@0: michael@0: static const JSFunctionSpec sLibraryFunctions[] = { michael@0: JS_FN("close", Library::Close, 0, CTYPESFN_FLAGS), michael@0: JS_FN("declare", Library::Declare, 0, CTYPESFN_FLAGS), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: bool michael@0: Library::Name(JSContext* cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() != 1) { michael@0: JS_ReportError(cx, "libraryName takes one argument"); michael@0: return false; michael@0: } michael@0: michael@0: Value arg = args[0]; michael@0: JSString* str = nullptr; michael@0: if (JSVAL_IS_STRING(arg)) { michael@0: str = JSVAL_TO_STRING(arg); michael@0: } michael@0: else { michael@0: JS_ReportError(cx, "name argument must be a string"); michael@0: return false; michael@0: } michael@0: michael@0: AutoString resultString; michael@0: AppendString(resultString, DLL_PREFIX); michael@0: AppendString(resultString, str); michael@0: AppendString(resultString, DLL_SUFFIX); michael@0: michael@0: JSString *result = JS_NewUCStringCopyN(cx, resultString.begin(), michael@0: resultString.length()); michael@0: if (!result) michael@0: return false; michael@0: michael@0: args.rval().setString(result); michael@0: return true; michael@0: } michael@0: michael@0: JSObject* michael@0: Library::Create(JSContext* cx, jsval path_, JSCTypesCallbacks* callbacks) michael@0: { michael@0: RootedValue path(cx, path_); michael@0: RootedObject libraryObj(cx, michael@0: JS_NewObject(cx, &sLibraryClass, NullPtr(), NullPtr())); michael@0: if (!libraryObj) michael@0: return nullptr; michael@0: michael@0: // initialize the library michael@0: JS_SetReservedSlot(libraryObj, SLOT_LIBRARY, PRIVATE_TO_JSVAL(nullptr)); michael@0: michael@0: // attach API functions michael@0: if (!JS_DefineFunctions(cx, libraryObj, sLibraryFunctions)) michael@0: return nullptr; michael@0: michael@0: if (!JSVAL_IS_STRING(path)) { michael@0: JS_ReportError(cx, "open takes a string argument"); michael@0: return nullptr; michael@0: } michael@0: michael@0: PRLibSpec libSpec; michael@0: RootedFlatString pathStr(cx, JS_FlattenString(cx, JSVAL_TO_STRING(path))); michael@0: if (!pathStr) michael@0: return nullptr; michael@0: #ifdef XP_WIN michael@0: // On Windows, converting to native charset may corrupt path string. michael@0: // So, we have to use Unicode path directly. michael@0: char16ptr_t pathChars = JS_GetFlatStringChars(pathStr); michael@0: if (!pathChars) michael@0: return nullptr; michael@0: michael@0: libSpec.value.pathname_u = pathChars; michael@0: libSpec.type = PR_LibSpec_PathnameU; michael@0: #else michael@0: // Convert to platform native charset if the appropriate callback has been michael@0: // provided. michael@0: char* pathBytes; michael@0: if (callbacks && callbacks->unicodeToNative) { michael@0: pathBytes = michael@0: callbacks->unicodeToNative(cx, pathStr->chars(), pathStr->length()); michael@0: if (!pathBytes) michael@0: return nullptr; michael@0: michael@0: } else { michael@0: // Fallback: assume the platform native charset is UTF-8. This is true michael@0: // for Mac OS X, Android, and probably Linux. michael@0: size_t nbytes = michael@0: GetDeflatedUTF8StringLength(cx, pathStr->chars(), pathStr->length()); michael@0: if (nbytes == (size_t) -1) michael@0: return nullptr; michael@0: michael@0: pathBytes = static_cast(JS_malloc(cx, nbytes + 1)); michael@0: if (!pathBytes) michael@0: return nullptr; michael@0: michael@0: ASSERT_OK(DeflateStringToUTF8Buffer(cx, pathStr->chars(), michael@0: pathStr->length(), pathBytes, &nbytes)); michael@0: pathBytes[nbytes] = 0; michael@0: } michael@0: michael@0: libSpec.value.pathname = pathBytes; michael@0: libSpec.type = PR_LibSpec_Pathname; michael@0: #endif michael@0: michael@0: PRLibrary* library = PR_LoadLibraryWithFlags(libSpec, 0); michael@0: michael@0: if (!library) { michael@0: #ifdef XP_WIN michael@0: JS_ReportError(cx, "couldn't open library %hs", pathChars); michael@0: #else michael@0: JS_ReportError(cx, "couldn't open library %s", pathBytes); michael@0: JS_free(cx, pathBytes); michael@0: #endif michael@0: return nullptr; michael@0: } michael@0: michael@0: #ifndef XP_WIN michael@0: JS_free(cx, pathBytes); michael@0: #endif michael@0: michael@0: // stash the library michael@0: JS_SetReservedSlot(libraryObj, SLOT_LIBRARY, PRIVATE_TO_JSVAL(library)); michael@0: michael@0: return libraryObj; michael@0: } michael@0: michael@0: bool michael@0: Library::IsLibrary(JSObject* obj) michael@0: { michael@0: return JS_GetClass(obj) == &sLibraryClass; michael@0: } michael@0: michael@0: PRLibrary* michael@0: Library::GetLibrary(JSObject* obj) michael@0: { michael@0: JS_ASSERT(IsLibrary(obj)); michael@0: michael@0: jsval slot = JS_GetReservedSlot(obj, SLOT_LIBRARY); michael@0: return static_cast(JSVAL_TO_PRIVATE(slot)); michael@0: } michael@0: michael@0: static void michael@0: UnloadLibrary(JSObject* obj) michael@0: { michael@0: PRLibrary* library = Library::GetLibrary(obj); michael@0: if (library) michael@0: PR_UnloadLibrary(library); michael@0: } michael@0: michael@0: void michael@0: Library::Finalize(JSFreeOp *fop, JSObject* obj) michael@0: { michael@0: UnloadLibrary(obj); michael@0: } michael@0: michael@0: bool michael@0: Library::Open(JSContext* cx, unsigned argc, jsval *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: JSObject* ctypesObj = JS_THIS_OBJECT(cx, vp); michael@0: if (!ctypesObj) michael@0: return false; michael@0: if (!IsCTypesGlobal(ctypesObj)) { michael@0: JS_ReportError(cx, "not a ctypes object"); michael@0: return false; michael@0: } michael@0: michael@0: if (args.length() != 1 || args[0].isUndefined()) { michael@0: JS_ReportError(cx, "open requires a single argument"); michael@0: return false; michael@0: } michael@0: michael@0: JSObject* library = Create(cx, args[0], GetCallbacks(ctypesObj)); michael@0: if (!library) michael@0: return false; michael@0: michael@0: args.rval().setObject(*library); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: Library::Close(JSContext* cx, unsigned argc, jsval* vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: JSObject* obj = JS_THIS_OBJECT(cx, vp); michael@0: if (!obj) michael@0: return false; michael@0: if (!IsLibrary(obj)) { michael@0: JS_ReportError(cx, "not a library"); michael@0: return false; michael@0: } michael@0: michael@0: if (args.length() != 0) { michael@0: JS_ReportError(cx, "close doesn't take any arguments"); michael@0: return false; michael@0: } michael@0: michael@0: // delete our internal objects michael@0: UnloadLibrary(obj); michael@0: JS_SetReservedSlot(obj, SLOT_LIBRARY, PRIVATE_TO_JSVAL(nullptr)); michael@0: michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: Library::Declare(JSContext* cx, unsigned argc, jsval* vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); michael@0: if (!obj) michael@0: return false; michael@0: if (!IsLibrary(obj)) { michael@0: JS_ReportError(cx, "not a library"); michael@0: return false; michael@0: } michael@0: michael@0: PRLibrary* library = GetLibrary(obj); michael@0: if (!library) { michael@0: JS_ReportError(cx, "library not open"); michael@0: return false; michael@0: } michael@0: michael@0: // We allow two API variants: michael@0: // 1) library.declare(name, abi, returnType, argType1, ...) michael@0: // declares a function with the given properties, and resolves the symbol michael@0: // address in the library. michael@0: // 2) library.declare(name, type) michael@0: // declares a symbol of 'type', and resolves it. The object that comes michael@0: // back will be of type 'type', and will point into the symbol data. michael@0: // This data will be both readable and writable via the usual CData michael@0: // accessors. If 'type' is a PointerType to a FunctionType, the result will michael@0: // be a function pointer, as with 1). michael@0: if (args.length() < 2) { michael@0: JS_ReportError(cx, "declare requires at least two arguments"); michael@0: return false; michael@0: } michael@0: michael@0: if (!args[0].isString()) { michael@0: JS_ReportError(cx, "first argument must be a string"); michael@0: return false; michael@0: } michael@0: michael@0: RootedObject fnObj(cx, nullptr); michael@0: RootedObject typeObj(cx); michael@0: bool isFunction = args.length() > 2; michael@0: if (isFunction) { michael@0: // Case 1). michael@0: // Create a FunctionType representing the function. michael@0: fnObj = FunctionType::CreateInternal(cx, michael@0: args[1], args[2], &args.array()[3], args.length() - 3); michael@0: if (!fnObj) michael@0: return false; michael@0: michael@0: // Make a function pointer type. michael@0: typeObj = PointerType::CreateInternal(cx, fnObj); michael@0: if (!typeObj) michael@0: return false; michael@0: } else { michael@0: // Case 2). michael@0: if (args[1].isPrimitive() || michael@0: !CType::IsCType(args[1].toObjectOrNull()) || michael@0: !CType::IsSizeDefined(args[1].toObjectOrNull())) { michael@0: JS_ReportError(cx, "second argument must be a type of defined size"); michael@0: return false; michael@0: } michael@0: michael@0: typeObj = args[1].toObjectOrNull(); michael@0: if (CType::GetTypeCode(typeObj) == TYPE_pointer) { michael@0: fnObj = PointerType::GetBaseType(typeObj); michael@0: isFunction = fnObj && CType::GetTypeCode(fnObj) == TYPE_function; michael@0: } michael@0: } michael@0: michael@0: void* data; michael@0: PRFuncPtr fnptr; michael@0: JSString* nameStr = args[0].toString(); michael@0: AutoCString symbol; michael@0: if (isFunction) { michael@0: // Build the symbol, with mangling if necessary. michael@0: FunctionType::BuildSymbolName(nameStr, fnObj, symbol); michael@0: AppendString(symbol, "\0"); michael@0: michael@0: // Look up the function symbol. michael@0: fnptr = PR_FindFunctionSymbol(library, symbol.begin()); michael@0: if (!fnptr) { michael@0: JS_ReportError(cx, "couldn't find function symbol in library"); michael@0: return false; michael@0: } michael@0: data = &fnptr; michael@0: michael@0: } else { michael@0: // 'typeObj' is another data type. Look up the data symbol. michael@0: AppendString(symbol, nameStr); michael@0: AppendString(symbol, "\0"); michael@0: michael@0: data = PR_FindSymbol(library, symbol.begin()); michael@0: if (!data) { michael@0: JS_ReportError(cx, "couldn't find symbol in library"); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: RootedObject result(cx, CData::Create(cx, typeObj, obj, data, isFunction)); michael@0: if (!result) michael@0: return false; michael@0: michael@0: args.rval().setObject(*result); michael@0: michael@0: // Seal the CData object, to prevent modification of the function pointer. michael@0: // This permanently associates this object with the library, and avoids michael@0: // having to do things like reset SLOT_REFERENT when someone tries to michael@0: // change the pointer value. michael@0: // XXX This will need to change when bug 541212 is fixed -- CData::ValueSetter michael@0: // could be called on a sealed object. michael@0: if (isFunction && !JS_FreezeObject(cx, result)) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: } michael@0: } michael@0: