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