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