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