tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

Library.cpp (11928B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=2 sw=2 et tw=80:
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "ctypes/Library.h"
      8 
      9 #include "jsapi.h"
     10 #include "prerror.h"
     11 #include "prlink.h"
     12 
     13 #include "ctypes/CTypes.h"
     14 #include "js/CharacterEncoding.h"
     15 #include "js/ErrorReport.h"
     16 #include "js/experimental/CTypes.h"  // JS::CTypesCallbacks
     17 #include "js/MemoryFunctions.h"
     18 #include "js/Object.h"              // JS::GetReservedSlot
     19 #include "js/PropertyAndElement.h"  // JS_DefineFunctions
     20 #include "js/PropertySpec.h"
     21 #include "js/StableStringChars.h"
     22 #include "js/ValueArray.h"
     23 #include "vm/JSObject.h"
     24 
     25 #ifdef XP_WIN
     26 #  include "mozilla/Char16.h"
     27 #endif
     28 
     29 using JS::AutoStableStringChars;
     30 
     31 namespace js::ctypes {
     32 
     33 /*******************************************************************************
     34 ** JSAPI function prototypes
     35 *******************************************************************************/
     36 
     37 namespace Library {
     38 static void Finalize(JS::GCContext* gcx, JSObject* obj);
     39 
     40 static bool Close(JSContext* cx, unsigned argc, Value* vp);
     41 static bool Declare(JSContext* cx, unsigned argc, Value* vp);
     42 }  // namespace Library
     43 
     44 /*******************************************************************************
     45 ** JSObject implementation
     46 *******************************************************************************/
     47 
     48 static const JSClassOps sLibraryClassOps = {
     49    nullptr,            // addProperty
     50    nullptr,            // delProperty
     51    nullptr,            // enumerate
     52    nullptr,            // newEnumerate
     53    nullptr,            // resolve
     54    nullptr,            // mayResolve
     55    Library::Finalize,  // finalize
     56    nullptr,            // call
     57    nullptr,            // construct
     58    nullptr,            // trace
     59 };
     60 
     61 static const JSClass sLibraryClass = {
     62    "Library",
     63    JSCLASS_HAS_RESERVED_SLOTS(LIBRARY_SLOTS) | JSCLASS_FOREGROUND_FINALIZE,
     64    &sLibraryClassOps,
     65 };
     66 
     67 #define CTYPESFN_FLAGS (JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)
     68 
     69 static const JSFunctionSpec sLibraryFunctions[] = {
     70    JS_FN("close", Library::Close, 0, CTYPESFN_FLAGS),
     71    JS_FN("declare", Library::Declare, 0, CTYPESFN_FLAGS),
     72    JS_FS_END,
     73 };
     74 
     75 bool Library::Name(JSContext* cx, unsigned argc, Value* vp) {
     76  CallArgs args = CallArgsFromVp(argc, vp);
     77  if (args.length() != 1) {
     78    JS_ReportErrorASCII(cx, "libraryName takes one argument");
     79    return false;
     80  }
     81 
     82  Value arg = args[0];
     83  JSString* str = nullptr;
     84  if (arg.isString()) {
     85    str = arg.toString();
     86  } else {
     87    JS_ReportErrorASCII(cx, "name argument must be a string");
     88    return false;
     89  }
     90 
     91  AutoString resultString;
     92  AppendString(cx, resultString, MOZ_DLL_PREFIX);
     93  AppendString(cx, resultString, str);
     94  AppendString(cx, resultString, MOZ_DLL_SUFFIX);
     95  if (!resultString) {
     96    return false;
     97  }
     98  auto resultStr = resultString.finish();
     99 
    100  JSString* result =
    101      JS_NewUCStringCopyN(cx, resultStr.begin(), resultStr.length());
    102  if (!result) {
    103    return false;
    104  }
    105 
    106  args.rval().setString(result);
    107  return true;
    108 }
    109 
    110 JSObject* Library::Create(JSContext* cx, HandleValue path,
    111                          const JS::CTypesCallbacks* callbacks) {
    112  RootedObject libraryObj(cx, JS_NewObject(cx, &sLibraryClass));
    113  if (!libraryObj) {
    114    return nullptr;
    115  }
    116 
    117  // initialize the library
    118  JS_SetReservedSlot(libraryObj, SLOT_LIBRARY, PrivateValue(nullptr));
    119 
    120  // attach API functions
    121  if (!JS_DefineFunctions(cx, libraryObj, sLibraryFunctions)) {
    122    return nullptr;
    123  }
    124 
    125  if (!path.isString()) {
    126    JS_ReportErrorASCII(cx, "open takes a string argument");
    127    return nullptr;
    128  }
    129 
    130  PRLibSpec libSpec;
    131  Rooted<JSLinearString*> pathStr(cx,
    132                                  JS_EnsureLinearString(cx, path.toString()));
    133  if (!pathStr) {
    134    return nullptr;
    135  }
    136 #ifdef XP_WIN
    137  // On Windows, converting to native charset may corrupt path string.
    138  // So, we have to use Unicode path directly.
    139  JS::UniqueTwoByteChars pathZeroTerminated(JS_CopyStringCharsZ(cx, pathStr));
    140  if (!pathZeroTerminated) {
    141    return nullptr;
    142  }
    143  char16ptr_t pathChars = pathZeroTerminated.get();
    144  libSpec.value.pathname_u = pathChars;
    145  libSpec.type = PR_LibSpec_PathnameU;
    146 #else
    147  // Convert to platform native charset if the appropriate callback has been
    148  // provided.
    149  JS::UniqueChars pathBytes;
    150  if (callbacks && callbacks->unicodeToNative) {
    151    AutoStableStringChars pathStrChars(cx);
    152    if (!pathStrChars.initTwoByte(cx, pathStr)) {
    153      return nullptr;
    154    }
    155 
    156    pathBytes.reset(callbacks->unicodeToNative(cx, pathStrChars.twoByteChars(),
    157                                               pathStr->length()));
    158    if (!pathBytes) {
    159      return nullptr;
    160    }
    161  } else {
    162    // Fallback: assume the platform native charset is UTF-8. This is true
    163    // for Mac OS X, Android, and probably Linux.
    164    if (!ReportErrorIfUnpairedSurrogatePresent(cx, pathStr)) {
    165      return nullptr;
    166    }
    167 
    168    size_t nbytes = JS::GetDeflatedUTF8StringLength(pathStr);
    169 
    170    pathBytes.reset(static_cast<char*>(JS_malloc(cx, nbytes + 1)));
    171    if (!pathBytes) {
    172      return nullptr;
    173    }
    174 
    175    nbytes = JS::DeflateStringToUTF8Buffer(
    176        pathStr, mozilla::Span(pathBytes.get(), nbytes));
    177    pathBytes[nbytes] = 0;
    178  }
    179 
    180  libSpec.value.pathname = pathBytes.get();
    181  libSpec.type = PR_LibSpec_Pathname;
    182 #endif
    183 
    184  PRLibrary* library = PR_LoadLibraryWithFlags(libSpec, PR_LD_NOW);
    185 
    186  if (!library) {
    187    constexpr size_t MaxErrorLength = 1024;
    188    char error[MaxErrorLength] = "Cannot get error from NSPR.";
    189    uint32_t errorLen = PR_GetErrorTextLength();
    190    if (errorLen && errorLen < MaxErrorLength) {
    191      PR_GetErrorText(error);
    192    }
    193 
    194    if (JS::UniqueChars errorUtf8 = JS::EncodeNarrowToUtf8(cx, error)) {
    195      if (JS::UniqueChars pathChars = JS_EncodeStringToUTF8(cx, pathStr)) {
    196        JS_ReportErrorUTF8(cx, "couldn't open library %s: %s", pathChars.get(),
    197                           errorUtf8.get());
    198      }
    199    }
    200    return nullptr;
    201  }
    202 
    203  // stash the library
    204  JS_SetReservedSlot(libraryObj, SLOT_LIBRARY, PrivateValue(library));
    205 
    206  return libraryObj;
    207 }
    208 
    209 bool Library::IsLibrary(JSObject* obj) { return obj->hasClass(&sLibraryClass); }
    210 
    211 PRLibrary* Library::GetLibrary(JSObject* obj) {
    212  MOZ_ASSERT(IsLibrary(obj));
    213 
    214  Value slot = JS::GetReservedSlot(obj, SLOT_LIBRARY);
    215  return static_cast<PRLibrary*>(slot.toPrivate());
    216 }
    217 
    218 static void UnloadLibrary(JSObject* obj) {
    219  PRLibrary* library = Library::GetLibrary(obj);
    220  if (library) {
    221    PR_UnloadLibrary(library);
    222  }
    223 }
    224 
    225 void Library::Finalize(JS::GCContext* gcx, JSObject* obj) {
    226  UnloadLibrary(obj);
    227 }
    228 
    229 bool Library::Open(JSContext* cx, unsigned argc, Value* vp) {
    230  CallArgs args = CallArgsFromVp(argc, vp);
    231  JSObject* ctypesObj = GetThisObject(cx, args, "ctypes.open");
    232  if (!ctypesObj) {
    233    return false;
    234  }
    235 
    236  if (!IsCTypesGlobal(ctypesObj)) {
    237    JS_ReportErrorASCII(cx, "not a ctypes object");
    238    return false;
    239  }
    240 
    241  if (args.length() != 1 || args[0].isUndefined()) {
    242    JS_ReportErrorASCII(cx, "open requires a single argument");
    243    return false;
    244  }
    245 
    246  JSObject* library = Create(cx, args[0], GetCallbacks(ctypesObj));
    247  if (!library) {
    248    return false;
    249  }
    250 
    251  args.rval().setObject(*library);
    252  return true;
    253 }
    254 
    255 bool Library::Close(JSContext* cx, unsigned argc, Value* vp) {
    256  CallArgs args = CallArgsFromVp(argc, vp);
    257 
    258  RootedObject obj(cx, GetThisObject(cx, args, "ctypes.close"));
    259  if (!obj) {
    260    return false;
    261  }
    262 
    263  if (!IsLibrary(obj)) {
    264    JS_ReportErrorASCII(cx, "not a library");
    265    return false;
    266  }
    267 
    268  if (args.length() != 0) {
    269    JS_ReportErrorASCII(cx, "close doesn't take any arguments");
    270    return false;
    271  }
    272 
    273  // delete our internal objects
    274  UnloadLibrary(obj);
    275  JS_SetReservedSlot(obj, SLOT_LIBRARY, PrivateValue(nullptr));
    276 
    277  args.rval().setUndefined();
    278  return true;
    279 }
    280 
    281 bool Library::Declare(JSContext* cx, unsigned argc, Value* vp) {
    282  CallArgs args = CallArgsFromVp(argc, vp);
    283 
    284  RootedObject obj(cx, GetThisObject(cx, args, "ctypes.declare"));
    285  if (!obj) {
    286    return false;
    287  }
    288 
    289  if (!IsLibrary(obj)) {
    290    JS_ReportErrorASCII(cx, "not a library");
    291    return false;
    292  }
    293 
    294  PRLibrary* library = GetLibrary(obj);
    295  if (!library) {
    296    JS_ReportErrorASCII(cx, "library not open");
    297    return false;
    298  }
    299 
    300  // We allow two API variants:
    301  // 1) library.declare(name, abi, returnType, argType1, ...)
    302  //    declares a function with the given properties, and resolves the symbol
    303  //    address in the library.
    304  // 2) library.declare(name, type)
    305  //    declares a symbol of 'type', and resolves it. The object that comes
    306  //    back will be of type 'type', and will point into the symbol data.
    307  //    This data will be both readable and writable via the usual CData
    308  //    accessors. If 'type' is a PointerType to a FunctionType, the result will
    309  //    be a function pointer, as with 1).
    310  if (args.length() < 2) {
    311    JS_ReportErrorASCII(cx, "declare requires at least two arguments");
    312    return false;
    313  }
    314 
    315  if (!args[0].isString()) {
    316    JS_ReportErrorASCII(cx, "first argument must be a string");
    317    return false;
    318  }
    319 
    320  RootedObject fnObj(cx, nullptr);
    321  RootedObject typeObj(cx);
    322  bool isFunction = args.length() > 2;
    323  if (isFunction) {
    324    // Case 1).
    325    // Create a FunctionType representing the function.
    326    fnObj = FunctionType::CreateInternal(
    327        cx, args[1], args[2],
    328        HandleValueArray::subarray(args, 3, args.length() - 3));
    329    if (!fnObj) {
    330      return false;
    331    }
    332 
    333    // Make a function pointer type.
    334    typeObj = PointerType::CreateInternal(cx, fnObj);
    335    if (!typeObj) {
    336      return false;
    337    }
    338  } else {
    339    // Case 2).
    340    if (args[1].isPrimitive() || !CType::IsCType(args[1].toObjectOrNull()) ||
    341        !CType::IsSizeDefined(args[1].toObjectOrNull())) {
    342      JS_ReportErrorASCII(cx, "second argument must be a type of defined size");
    343      return false;
    344    }
    345 
    346    typeObj = args[1].toObjectOrNull();
    347    if (CType::GetTypeCode(typeObj) == TYPE_pointer) {
    348      fnObj = PointerType::GetBaseType(typeObj);
    349      isFunction = fnObj && CType::GetTypeCode(fnObj) == TYPE_function;
    350    }
    351  }
    352 
    353  void* data;
    354  PRFuncPtr fnptr;
    355  RootedString nameStr(cx, args[0].toString());
    356  AutoCString symbol;
    357  if (isFunction) {
    358    // Build the symbol, with mangling if necessary.
    359    FunctionType::BuildSymbolName(cx, nameStr, fnObj, symbol);
    360    AppendString(cx, symbol, "\0");
    361    if (!symbol) {
    362      return false;
    363    }
    364 
    365    // Look up the function symbol.
    366    fnptr = PR_FindFunctionSymbol(library, symbol.finish().begin());
    367    if (!fnptr) {
    368      JS_ReportErrorASCII(cx, "couldn't find function symbol in library");
    369      return false;
    370    }
    371    data = &fnptr;
    372 
    373  } else {
    374    // 'typeObj' is another data type. Look up the data symbol.
    375    AppendString(cx, symbol, nameStr);
    376    AppendString(cx, symbol, "\0");
    377    if (!symbol) {
    378      return false;
    379    }
    380 
    381    data = PR_FindSymbol(library, symbol.finish().begin());
    382    if (!data) {
    383      JS_ReportErrorASCII(cx, "couldn't find symbol in library");
    384      return false;
    385    }
    386  }
    387 
    388  RootedObject result(cx, CData::Create(cx, typeObj, obj, data, isFunction));
    389  if (!result) {
    390    return false;
    391  }
    392 
    393  if (isFunction) {
    394    JS_SetReservedSlot(result, SLOT_FUNNAME, StringValue(nameStr));
    395  }
    396 
    397  args.rval().setObject(*result);
    398 
    399  // Seal the CData object, to prevent modification of the function pointer.
    400  // This permanently associates this object with the library, and avoids
    401  // having to do things like reset SLOT_REFERENT when someone tries to
    402  // change the pointer value.
    403  // XXX This will need to change when bug 541212 is fixed -- CData::ValueSetter
    404  // could be called on a sealed object.
    405  if (isFunction && !JS_FreezeObject(cx, result)) {
    406    return false;
    407  }
    408 
    409  return true;
    410 }
    411 
    412 }  // namespace js::ctypes