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