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