1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is js-ctypes.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * The Mozilla Foundation <http://www.mozilla.org/>.
19 : * Portions created by the Initial Developer are Copyright (C) 2009
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Mark Finkle <mark.finkle@gmail.com>, <mfinkle@mozilla.com>
24 : * Fredrik Larsson <nossralf@gmail.com>
25 : * Dan Witte <dwitte@mozilla.com>
26 : *
27 : * Alternatively, the contents of this file may be used under the terms of
28 : * either the GNU General Public License Version 2 or later (the "GPL"), or
29 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 : * in which case the provisions of the GPL or the LGPL are applicable instead
31 : * of those above. If you wish to allow use of your version of this file only
32 : * under the terms of either the GPL or the LGPL, and not to allow others to
33 : * use your version of this file under the terms of the MPL, indicate your
34 : * decision by deleting the provisions above and replace them with the notice
35 : * and other provisions required by the GPL or the LGPL. If you do not delete
36 : * the provisions above, a recipient may use your version of this file under
37 : * the terms of any one of the MPL, the GPL or the LGPL.
38 : *
39 : * ***** END LICENSE BLOCK ***** */
40 :
41 : #include "jscntxt.h"
42 : #include "jsstr.h"
43 : #include "Library.h"
44 : #include "CTypes.h"
45 : #include "prlink.h"
46 :
47 : namespace js {
48 : namespace ctypes {
49 :
50 : /*******************************************************************************
51 : ** JSAPI function prototypes
52 : *******************************************************************************/
53 :
54 : namespace Library
55 : {
56 : static void Finalize(JSFreeOp *fop, JSObject* obj);
57 :
58 : static JSBool Close(JSContext* cx, unsigned argc, jsval* vp);
59 : static JSBool Declare(JSContext* cx, unsigned argc, jsval* vp);
60 : }
61 :
62 : /*******************************************************************************
63 : ** JSObject implementation
64 : *******************************************************************************/
65 :
66 : static JSClass sLibraryClass = {
67 : "Library",
68 : JSCLASS_HAS_RESERVED_SLOTS(LIBRARY_SLOTS),
69 : JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
70 : JS_EnumerateStub,JS_ResolveStub, JS_ConvertStub, Library::Finalize
71 : };
72 :
73 : #define CTYPESFN_FLAGS \
74 : (JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)
75 :
76 : static JSFunctionSpec sLibraryFunctions[] = {
77 : JS_FN("close", Library::Close, 0, CTYPESFN_FLAGS),
78 : JS_FN("declare", Library::Declare, 0, CTYPESFN_FLAGS),
79 : JS_FS_END
80 : };
81 :
82 : JSBool
83 0 : Library::Name(JSContext* cx, unsigned argc, jsval *vp)
84 : {
85 0 : if (argc != 1) {
86 0 : JS_ReportError(cx, "libraryName takes one argument");
87 0 : return JS_FALSE;
88 : }
89 :
90 0 : jsval arg = JS_ARGV(cx, vp)[0];
91 0 : JSString* str = NULL;
92 0 : if (JSVAL_IS_STRING(arg)) {
93 0 : str = JSVAL_TO_STRING(arg);
94 : }
95 : else {
96 0 : JS_ReportError(cx, "name argument must be a string");
97 0 : return JS_FALSE;
98 : }
99 :
100 0 : AutoString resultString;
101 0 : AppendString(resultString, DLL_PREFIX);
102 0 : AppendString(resultString, str);
103 0 : AppendString(resultString, DLL_SUFFIX);
104 :
105 0 : JSString *result = JS_NewUCStringCopyN(cx, resultString.begin(),
106 0 : resultString.length());
107 0 : if (!result)
108 0 : return JS_FALSE;
109 :
110 0 : JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(result));
111 0 : return JS_TRUE;
112 : }
113 :
114 : JSObject*
115 0 : Library::Create(JSContext* cx, jsval path, JSCTypesCallbacks* callbacks)
116 : {
117 0 : JSObject* libraryObj = JS_NewObject(cx, &sLibraryClass, NULL, NULL);
118 0 : if (!libraryObj)
119 0 : return NULL;
120 0 : js::AutoObjectRooter root(cx, libraryObj);
121 :
122 : // initialize the library
123 0 : JS_SetReservedSlot(libraryObj, SLOT_LIBRARY, PRIVATE_TO_JSVAL(NULL));
124 :
125 : // attach API functions
126 0 : if (!JS_DefineFunctions(cx, libraryObj, sLibraryFunctions))
127 0 : return NULL;
128 :
129 0 : if (!JSVAL_IS_STRING(path)) {
130 0 : JS_ReportError(cx, "open takes a string argument");
131 0 : return NULL;
132 : }
133 :
134 : PRLibSpec libSpec;
135 0 : JSFlatString* pathStr = JS_FlattenString(cx, JSVAL_TO_STRING(path));
136 0 : if (!pathStr)
137 0 : return NULL;
138 : #ifdef XP_WIN
139 : // On Windows, converting to native charset may corrupt path string.
140 : // So, we have to use Unicode path directly.
141 : const PRUnichar* pathChars = JS_GetFlatStringChars(pathStr);
142 : if (!pathChars)
143 : return NULL;
144 :
145 : libSpec.value.pathname_u = pathChars;
146 : libSpec.type = PR_LibSpec_PathnameU;
147 : #else
148 : // Convert to platform native charset if the appropriate callback has been
149 : // provided.
150 : char* pathBytes;
151 0 : if (callbacks && callbacks->unicodeToNative) {
152 : pathBytes =
153 0 : callbacks->unicodeToNative(cx, pathStr->chars(), pathStr->length());
154 0 : if (!pathBytes)
155 0 : return NULL;
156 :
157 : } else {
158 : // Fallback: assume the platform native charset is UTF-8. This is true
159 : // for Mac OS X, Android, and probably Linux.
160 : size_t nbytes =
161 0 : GetDeflatedUTF8StringLength(cx, pathStr->chars(), pathStr->length());
162 0 : if (nbytes == (size_t) -1)
163 0 : return NULL;
164 :
165 0 : pathBytes = static_cast<char*>(JS_malloc(cx, nbytes + 1));
166 0 : if (!pathBytes)
167 0 : return NULL;
168 :
169 : ASSERT_OK(DeflateStringToUTF8Buffer(cx, pathStr->chars(),
170 0 : pathStr->length(), pathBytes, &nbytes));
171 0 : pathBytes[nbytes] = 0;
172 : }
173 :
174 0 : libSpec.value.pathname = pathBytes;
175 0 : libSpec.type = PR_LibSpec_Pathname;
176 : #endif
177 :
178 0 : PRLibrary* library = PR_LoadLibraryWithFlags(libSpec, 0);
179 : #ifndef XP_WIN
180 0 : JS_free(cx, pathBytes);
181 : #endif
182 0 : if (!library) {
183 0 : JS_ReportError(cx, "couldn't open library");
184 0 : return NULL;
185 : }
186 :
187 : // stash the library
188 0 : JS_SetReservedSlot(libraryObj, SLOT_LIBRARY, PRIVATE_TO_JSVAL(library));
189 :
190 0 : return libraryObj;
191 : }
192 :
193 : bool
194 0 : Library::IsLibrary(JSObject* obj)
195 : {
196 0 : return JS_GetClass(obj) == &sLibraryClass;
197 : }
198 :
199 : PRLibrary*
200 0 : Library::GetLibrary(JSObject* obj)
201 : {
202 0 : JS_ASSERT(IsLibrary(obj));
203 :
204 0 : jsval slot = JS_GetReservedSlot(obj, SLOT_LIBRARY);
205 0 : return static_cast<PRLibrary*>(JSVAL_TO_PRIVATE(slot));
206 : }
207 :
208 : static void
209 0 : UnloadLibrary(JSObject* obj)
210 : {
211 0 : PRLibrary* library = Library::GetLibrary(obj);
212 0 : if (library)
213 0 : PR_UnloadLibrary(library);
214 0 : }
215 :
216 : void
217 0 : Library::Finalize(JSFreeOp *fop, JSObject* obj)
218 : {
219 0 : UnloadLibrary(obj);
220 0 : }
221 :
222 : JSBool
223 0 : Library::Open(JSContext* cx, unsigned argc, jsval *vp)
224 : {
225 0 : JSObject* ctypesObj = JS_THIS_OBJECT(cx, vp);
226 0 : if (!ctypesObj || !IsCTypesGlobal(ctypesObj)) {
227 0 : JS_ReportError(cx, "not a ctypes object");
228 0 : return JS_FALSE;
229 : }
230 :
231 0 : if (argc != 1 || JSVAL_IS_VOID(JS_ARGV(cx, vp)[0])) {
232 0 : JS_ReportError(cx, "open requires a single argument");
233 0 : return JS_FALSE;
234 : }
235 :
236 0 : JSObject* library = Create(cx, JS_ARGV(cx, vp)[0], GetCallbacks(ctypesObj));
237 0 : if (!library)
238 0 : return JS_FALSE;
239 :
240 0 : JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(library));
241 0 : return JS_TRUE;
242 : }
243 :
244 : JSBool
245 0 : Library::Close(JSContext* cx, unsigned argc, jsval* vp)
246 : {
247 0 : JSObject* obj = JS_THIS_OBJECT(cx, vp);
248 0 : if (!obj || !IsLibrary(obj)) {
249 0 : JS_ReportError(cx, "not a library");
250 0 : return JS_FALSE;
251 : }
252 :
253 0 : if (argc != 0) {
254 0 : JS_ReportError(cx, "close doesn't take any arguments");
255 0 : return JS_FALSE;
256 : }
257 :
258 : // delete our internal objects
259 0 : UnloadLibrary(obj);
260 0 : JS_SetReservedSlot(obj, SLOT_LIBRARY, PRIVATE_TO_JSVAL(NULL));
261 :
262 0 : JS_SET_RVAL(cx, vp, JSVAL_VOID);
263 0 : return JS_TRUE;
264 : }
265 :
266 : JSBool
267 0 : Library::Declare(JSContext* cx, unsigned argc, jsval* vp)
268 : {
269 0 : JSObject* obj = JS_THIS_OBJECT(cx, vp);
270 0 : if (!obj || !IsLibrary(obj)) {
271 0 : JS_ReportError(cx, "not a library");
272 0 : return JS_FALSE;
273 : }
274 :
275 0 : PRLibrary* library = GetLibrary(obj);
276 0 : if (!library) {
277 0 : JS_ReportError(cx, "library not open");
278 0 : return JS_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 (argc < 2) {
292 0 : JS_ReportError(cx, "declare requires at least two arguments");
293 0 : return JS_FALSE;
294 : }
295 :
296 0 : jsval* argv = JS_ARGV(cx, vp);
297 0 : if (!JSVAL_IS_STRING(argv[0])) {
298 0 : JS_ReportError(cx, "first argument must be a string");
299 0 : return JS_FALSE;
300 : }
301 :
302 0 : JSObject* fnObj = NULL;
303 : JSObject* typeObj;
304 0 : js::AutoObjectRooter root(cx);
305 0 : bool isFunction = argc > 2;
306 0 : if (isFunction) {
307 : // Case 1).
308 : // Create a FunctionType representing the function.
309 : fnObj = FunctionType::CreateInternal(cx,
310 0 : argv[1], argv[2], &argv[3], argc - 3);
311 0 : if (!fnObj)
312 0 : return JS_FALSE;
313 0 : root.setObject(fnObj);
314 :
315 : // Make a function pointer type.
316 0 : typeObj = PointerType::CreateInternal(cx, fnObj);
317 0 : if (!typeObj)
318 0 : return JS_FALSE;
319 0 : root.setObject(typeObj);
320 :
321 : } else {
322 : // Case 2).
323 0 : if (JSVAL_IS_PRIMITIVE(argv[1]) ||
324 0 : !CType::IsCType(JSVAL_TO_OBJECT(argv[1])) ||
325 0 : !CType::IsSizeDefined(JSVAL_TO_OBJECT(argv[1]))) {
326 0 : JS_ReportError(cx, "second argument must be a type of defined size");
327 0 : return JS_FALSE;
328 : }
329 :
330 0 : typeObj = JSVAL_TO_OBJECT(argv[1]);
331 0 : if (CType::GetTypeCode(typeObj) == TYPE_pointer) {
332 0 : fnObj = PointerType::GetBaseType(typeObj);
333 0 : isFunction = fnObj && CType::GetTypeCode(fnObj) == TYPE_function;
334 : }
335 : }
336 :
337 : void* data;
338 : PRFuncPtr fnptr;
339 0 : JSString* nameStr = JSVAL_TO_STRING(argv[0]);
340 0 : AutoCString symbol;
341 0 : if (isFunction) {
342 : // Build the symbol, with mangling if necessary.
343 0 : FunctionType::BuildSymbolName(nameStr, fnObj, symbol);
344 0 : AppendString(symbol, "\0");
345 :
346 : // Look up the function symbol.
347 0 : fnptr = PR_FindFunctionSymbol(library, symbol.begin());
348 0 : if (!fnptr) {
349 0 : JS_ReportError(cx, "couldn't find function symbol in library");
350 0 : return JS_FALSE;
351 : }
352 0 : data = &fnptr;
353 :
354 : } else {
355 : // 'typeObj' is another data type. Look up the data symbol.
356 0 : AppendString(symbol, nameStr);
357 0 : AppendString(symbol, "\0");
358 :
359 0 : data = PR_FindSymbol(library, symbol.begin());
360 0 : if (!data) {
361 0 : JS_ReportError(cx, "couldn't find symbol in library");
362 0 : return JS_FALSE;
363 : }
364 : }
365 :
366 0 : JSObject* result = CData::Create(cx, typeObj, obj, data, isFunction);
367 0 : if (!result)
368 0 : return JS_FALSE;
369 :
370 0 : JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(result));
371 :
372 : // Seal the CData object, to prevent modification of the function pointer.
373 : // This permanently associates this object with the library, and avoids
374 : // having to do things like reset SLOT_REFERENT when someone tries to
375 : // change the pointer value.
376 : // XXX This will need to change when bug 541212 is fixed -- CData::ValueSetter
377 : // could be called on a sealed object.
378 0 : if (isFunction && !JS_FreezeObject(cx, result))
379 0 : return JS_FALSE;
380 :
381 0 : return JS_TRUE;
382 : }
383 :
384 : }
385 : }
386 :
|