Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=2 sw=2 et tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ctypes/Library.h"
#include "jsapi.h"
#include "prerror.h"
#include "prlink.h"
#include "ctypes/CTypes.h"
#include "js/CharacterEncoding.h"
#include "js/ErrorReport.h"
#include "js/experimental/CTypes.h" // JS::CTypesCallbacks
#include "js/MemoryFunctions.h"
#include "js/Object.h" // JS::GetReservedSlot
#include "js/PropertyAndElement.h" // JS_DefineFunctions
#include "js/PropertySpec.h"
#include "js/StableStringChars.h"
#include "js/ValueArray.h"
#include "vm/JSObject.h"
using JS::AutoStableStringChars;
namespace js::ctypes {
/*******************************************************************************
** JSAPI function prototypes
*******************************************************************************/
namespace Library {
static void Finalize(JS::GCContext* gcx, JSObject* obj);
static bool Close(JSContext* cx, unsigned argc, Value* vp);
static bool Declare(JSContext* cx, unsigned argc, Value* vp);
} // namespace Library
/*******************************************************************************
** JSObject implementation
*******************************************************************************/
static const JSClassOps sLibraryClassOps = {
nullptr, // addProperty
nullptr, // delProperty
nullptr, // enumerate
nullptr, // newEnumerate
nullptr, // resolve
nullptr, // mayResolve
Library::Finalize, // finalize
nullptr, // call
nullptr, // construct
nullptr, // trace
};
static const JSClass sLibraryClass = {
"Library",
JSCLASS_HAS_RESERVED_SLOTS(LIBRARY_SLOTS) | JSCLASS_FOREGROUND_FINALIZE,
&sLibraryClassOps};
#define CTYPESFN_FLAGS (JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)
static const JSFunctionSpec sLibraryFunctions[] = {
JS_FN("close", Library::Close, 0, CTYPESFN_FLAGS),
JS_FN("declare", Library::Declare, 0, CTYPESFN_FLAGS), JS_FS_END};
bool Library::Name(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1) {
JS_ReportErrorASCII(cx, "libraryName takes one argument");
return false;
}
Value arg = args[0];
JSString* str = nullptr;
if (arg.isString()) {
str = arg.toString();
} else {
JS_ReportErrorASCII(cx, "name argument must be a string");
return false;
}
AutoString resultString;
AppendString(cx, resultString, MOZ_DLL_PREFIX);
AppendString(cx, resultString, str);
AppendString(cx, resultString, MOZ_DLL_SUFFIX);
if (!resultString) {
return false;
}
auto resultStr = resultString.finish();
JSString* result =
JS_NewUCStringCopyN(cx, resultStr.begin(), resultStr.length());
if (!result) {
return false;
}
args.rval().setString(result);
return true;
}
JSObject* Library::Create(JSContext* cx, HandleValue path,
const JS::CTypesCallbacks* callbacks) {
RootedObject libraryObj(cx, JS_NewObject(cx, &sLibraryClass));
if (!libraryObj) {
return nullptr;
}
// initialize the library
JS_SetReservedSlot(libraryObj, SLOT_LIBRARY, PrivateValue(nullptr));
// attach API functions
if (!JS_DefineFunctions(cx, libraryObj, sLibraryFunctions)) {
return nullptr;
}
if (!path.isString()) {
JS_ReportErrorASCII(cx, "open takes a string argument");
return nullptr;
}
PRLibSpec libSpec;
Rooted<JSLinearString*> pathStr(cx,
JS_EnsureLinearString(cx, path.toString()));
if (!pathStr) {
return nullptr;
}
#ifdef XP_WIN
// On Windows, converting to native charset may corrupt path string.
// So, we have to use Unicode path directly.
JS::UniqueTwoByteChars pathZeroTerminated(JS_CopyStringCharsZ(cx, pathStr));
if (!pathZeroTerminated) {
return nullptr;
}
char16ptr_t pathChars = pathZeroTerminated.get();
libSpec.value.pathname_u = pathChars;
libSpec.type = PR_LibSpec_PathnameU;
#else
// Convert to platform native charset if the appropriate callback has been
// provided.
JS::UniqueChars pathBytes;
if (callbacks && callbacks->unicodeToNative) {
AutoStableStringChars pathStrChars(cx);
if (!pathStrChars.initTwoByte(cx, pathStr)) {
return nullptr;
}
pathBytes.reset(callbacks->unicodeToNative(cx, pathStrChars.twoByteChars(),
pathStr->length()));
if (!pathBytes) {
return nullptr;
}
} else {
// Fallback: assume the platform native charset is UTF-8. This is true
// for Mac OS X, Android, and probably Linux.
if (!ReportErrorIfUnpairedSurrogatePresent(cx, pathStr)) {
return nullptr;
}
size_t nbytes = JS::GetDeflatedUTF8StringLength(pathStr);
pathBytes.reset(static_cast<char*>(JS_malloc(cx, nbytes + 1)));
if (!pathBytes) {
return nullptr;
}
nbytes = JS::DeflateStringToUTF8Buffer(
pathStr, mozilla::Span(pathBytes.get(), nbytes));
pathBytes[nbytes] = 0;
}
libSpec.value.pathname = pathBytes.get();
libSpec.type = PR_LibSpec_Pathname;
#endif
PRLibrary* library = PR_LoadLibraryWithFlags(libSpec, PR_LD_NOW);
if (!library) {
constexpr size_t MaxErrorLength = 1024;
char error[MaxErrorLength] = "Cannot get error from NSPR.";
uint32_t errorLen = PR_GetErrorTextLength();
if (errorLen && errorLen < MaxErrorLength) {
PR_GetErrorText(error);
}
if (JS::UniqueChars errorUtf8 = JS::EncodeNarrowToUtf8(cx, error)) {
if (JS::UniqueChars pathChars = JS_EncodeStringToUTF8(cx, pathStr)) {
JS_ReportErrorUTF8(cx, "couldn't open library %s: %s", pathChars.get(),
errorUtf8.get());
}
}
return nullptr;
}
// stash the library
JS_SetReservedSlot(libraryObj, SLOT_LIBRARY, PrivateValue(library));
return libraryObj;
}
bool Library::IsLibrary(JSObject* obj) { return obj->hasClass(&sLibraryClass); }
PRLibrary* Library::GetLibrary(JSObject* obj) {
MOZ_ASSERT(IsLibrary(obj));
Value slot = JS::GetReservedSlot(obj, SLOT_LIBRARY);
return static_cast<PRLibrary*>(slot.toPrivate());
}
static void UnloadLibrary(JSObject* obj) {
PRLibrary* library = Library::GetLibrary(obj);
if (library) {
PR_UnloadLibrary(library);
}
}
void Library::Finalize(JS::GCContext* gcx, JSObject* obj) {
UnloadLibrary(obj);
}
bool Library::Open(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
JSObject* ctypesObj = GetThisObject(cx, args, "ctypes.open");
if (!ctypesObj) {
return false;
}
if (!IsCTypesGlobal(ctypesObj)) {
JS_ReportErrorASCII(cx, "not a ctypes object");
return false;
}
if (args.length() != 1 || args[0].isUndefined()) {
JS_ReportErrorASCII(cx, "open requires a single argument");
return false;
}
JSObject* library = Create(cx, args[0], GetCallbacks(ctypesObj));
if (!library) {
return false;
}
args.rval().setObject(*library);
return true;
}
bool Library::Close(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject obj(cx, GetThisObject(cx, args, "ctypes.close"));
if (!obj) {
return false;
}
if (!IsLibrary(obj)) {
JS_ReportErrorASCII(cx, "not a library");
return false;
}
if (args.length() != 0) {
JS_ReportErrorASCII(cx, "close doesn't take any arguments");
return false;
}
// delete our internal objects
UnloadLibrary(obj);
JS_SetReservedSlot(obj, SLOT_LIBRARY, PrivateValue(nullptr));
args.rval().setUndefined();
return true;
}
bool Library::Declare(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject obj(cx, GetThisObject(cx, args, "ctypes.declare"));
if (!obj) {
return false;
}
if (!IsLibrary(obj)) {
JS_ReportErrorASCII(cx, "not a library");
return false;
}
PRLibrary* library = GetLibrary(obj);
if (!library) {
JS_ReportErrorASCII(cx, "library not open");
return false;
}
// 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 PointerType to a FunctionType, the result will
// be a function pointer, as with 1).
if (args.length() < 2) {
JS_ReportErrorASCII(cx, "declare requires at least two arguments");
return false;
}
if (!args[0].isString()) {
JS_ReportErrorASCII(cx, "first argument must be a string");
return false;
}
RootedObject fnObj(cx, nullptr);
RootedObject typeObj(cx);
bool isFunction = args.length() > 2;
if (isFunction) {
// Case 1).
// Create a FunctionType representing the function.
fnObj = FunctionType::CreateInternal(
cx, args[1], args[2],
HandleValueArray::subarray(args, 3, args.length() - 3));
if (!fnObj) {
return false;
}
// Make a function pointer type.
typeObj = PointerType::CreateInternal(cx, fnObj);
if (!typeObj) {
return false;
}
} else {
// Case 2).
if (args[1].isPrimitive() || !CType::IsCType(args[1].toObjectOrNull()) ||
!CType::IsSizeDefined(args[1].toObjectOrNull())) {
JS_ReportErrorASCII(cx, "second argument must be a type of defined size");
return false;
}
typeObj = args[1].toObjectOrNull();
if (CType::GetTypeCode(typeObj) == TYPE_pointer) {
fnObj = PointerType::GetBaseType(typeObj);
isFunction = fnObj && CType::GetTypeCode(fnObj) == TYPE_function;
}
}
void* data;
PRFuncPtr fnptr;
RootedString nameStr(cx, args[0].toString());
AutoCString symbol;
if (isFunction) {
// Build the symbol, with mangling if necessary.
FunctionType::BuildSymbolName(cx, nameStr, fnObj, symbol);
AppendString(cx, symbol, "\0");
if (!symbol) {
return false;
}
// Look up the function symbol.
fnptr = PR_FindFunctionSymbol(library, symbol.finish().begin());
if (!fnptr) {
JS_ReportErrorASCII(cx, "couldn't find function symbol in library");
return false;
}
data = &fnptr;
} else {
// 'typeObj' is another data type. Look up the data symbol.
AppendString(cx, symbol, nameStr);
AppendString(cx, symbol, "\0");
if (!symbol) {
return false;
}
data = PR_FindSymbol(library, symbol.finish().begin());
if (!data) {
JS_ReportErrorASCII(cx, "couldn't find symbol in library");
return false;
}
}
RootedObject result(cx, CData::Create(cx, typeObj, obj, data, isFunction));
if (!result) {
return false;
}
if (isFunction) {
JS_SetReservedSlot(result, SLOT_FUNNAME, StringValue(nameStr));
}
args.rval().setObject(*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.
if (isFunction && !JS_FreezeObject(cx, result)) {
return false;
}
return true;
}
} // namespace js::ctypes