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