|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: |
|
2 * This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 /* JS shell. */ |
|
7 |
|
8 #include "mozilla/ArrayUtils.h" |
|
9 #include "mozilla/Atomics.h" |
|
10 #include "mozilla/DebugOnly.h" |
|
11 #include "mozilla/GuardObjects.h" |
|
12 #include "mozilla/PodOperations.h" |
|
13 |
|
14 #ifdef XP_WIN |
|
15 # include <direct.h> |
|
16 # include <process.h> |
|
17 #endif |
|
18 #include <errno.h> |
|
19 #include <fcntl.h> |
|
20 #if defined(XP_WIN) |
|
21 # include <io.h> /* for isatty() */ |
|
22 #endif |
|
23 #include <locale.h> |
|
24 #include <math.h> |
|
25 #include <signal.h> |
|
26 #include <stdio.h> |
|
27 #include <stdlib.h> |
|
28 #include <string.h> |
|
29 #include <sys/stat.h> |
|
30 #include <sys/types.h> |
|
31 #ifdef XP_UNIX |
|
32 # include <sys/mman.h> |
|
33 # include <sys/stat.h> |
|
34 # include <sys/wait.h> |
|
35 # include <unistd.h> |
|
36 #endif |
|
37 |
|
38 #include "jsapi.h" |
|
39 #include "jsarray.h" |
|
40 #include "jsatom.h" |
|
41 #include "jscntxt.h" |
|
42 #include "jsfun.h" |
|
43 #ifdef JS_THREADSAFE |
|
44 #include "jslock.h" |
|
45 #endif |
|
46 #include "jsobj.h" |
|
47 #include "jsprf.h" |
|
48 #include "jsscript.h" |
|
49 #include "jstypes.h" |
|
50 #include "jsutil.h" |
|
51 #ifdef XP_WIN |
|
52 # include "jswin.h" |
|
53 #endif |
|
54 #include "jsworkers.h" |
|
55 #include "jswrapper.h" |
|
56 #include "prmjtime.h" |
|
57 |
|
58 #include "builtin/TestingFunctions.h" |
|
59 #include "frontend/Parser.h" |
|
60 #include "jit/arm/Simulator-arm.h" |
|
61 #include "jit/Ion.h" |
|
62 #include "js/OldDebugAPI.h" |
|
63 #include "js/StructuredClone.h" |
|
64 #include "perf/jsperf.h" |
|
65 #include "shell/jsheaptools.h" |
|
66 #include "shell/jsoptparse.h" |
|
67 #include "vm/ArgumentsObject.h" |
|
68 #include "vm/Monitor.h" |
|
69 #include "vm/Shape.h" |
|
70 #include "vm/TypedArrayObject.h" |
|
71 #include "vm/WrapperObject.h" |
|
72 |
|
73 #include "jscompartmentinlines.h" |
|
74 #include "jsobjinlines.h" |
|
75 |
|
76 #ifdef XP_WIN |
|
77 # define PATH_MAX (MAX_PATH > _MAX_DIR ? MAX_PATH : _MAX_DIR) |
|
78 #else |
|
79 # include <libgen.h> |
|
80 #endif |
|
81 |
|
82 using namespace js; |
|
83 using namespace js::cli; |
|
84 |
|
85 using mozilla::ArrayLength; |
|
86 using mozilla::NumberEqualsInt32; |
|
87 using mozilla::Maybe; |
|
88 using mozilla::PodCopy; |
|
89 |
|
90 enum JSShellExitCode { |
|
91 EXITCODE_RUNTIME_ERROR = 3, |
|
92 EXITCODE_FILE_NOT_FOUND = 4, |
|
93 EXITCODE_OUT_OF_MEMORY = 5, |
|
94 EXITCODE_TIMEOUT = 6 |
|
95 }; |
|
96 |
|
97 enum PathResolutionMode { |
|
98 RootRelative, |
|
99 ScriptRelative |
|
100 }; |
|
101 |
|
102 static size_t gStackChunkSize = 8192; |
|
103 |
|
104 /* |
|
105 * Note: This limit should match the stack limit set by the browser in |
|
106 * js/xpconnect/src/XPCJSRuntime.cpp |
|
107 */ |
|
108 #if defined(MOZ_ASAN) || (defined(DEBUG) && !defined(XP_WIN)) |
|
109 static size_t gMaxStackSize = 2 * 128 * sizeof(size_t) * 1024; |
|
110 #else |
|
111 static size_t gMaxStackSize = 128 * sizeof(size_t) * 1024; |
|
112 #endif |
|
113 |
|
114 /* |
|
115 * Limit the timeout to 30 minutes to prevent an overflow on platfoms |
|
116 * that represent the time internally in microseconds using 32-bit int. |
|
117 */ |
|
118 static double MAX_TIMEOUT_INTERVAL = 1800.0; |
|
119 static double gTimeoutInterval = -1.0; |
|
120 static volatile bool gServiceInterrupt = false; |
|
121 static Maybe<JS::PersistentRootedValue> gInterruptFunc; |
|
122 |
|
123 static bool enableDisassemblyDumps = false; |
|
124 |
|
125 static bool printTiming = false; |
|
126 static const char *jsCacheDir = nullptr; |
|
127 static const char *jsCacheAsmJSPath = nullptr; |
|
128 static bool jsCachingEnabled = false; |
|
129 mozilla::Atomic<bool> jsCacheOpened(false); |
|
130 |
|
131 static bool |
|
132 SetTimeoutValue(JSContext *cx, double t); |
|
133 |
|
134 static bool |
|
135 InitWatchdog(JSRuntime *rt); |
|
136 |
|
137 static void |
|
138 KillWatchdog(); |
|
139 |
|
140 static bool |
|
141 ScheduleWatchdog(JSRuntime *rt, double t); |
|
142 |
|
143 static void |
|
144 CancelExecution(JSRuntime *rt); |
|
145 |
|
146 /* |
|
147 * Watchdog thread state. |
|
148 */ |
|
149 #ifdef JS_THREADSAFE |
|
150 |
|
151 static PRLock *gWatchdogLock = nullptr; |
|
152 static PRCondVar *gWatchdogWakeup = nullptr; |
|
153 static PRThread *gWatchdogThread = nullptr; |
|
154 static bool gWatchdogHasTimeout = false; |
|
155 static int64_t gWatchdogTimeout = 0; |
|
156 |
|
157 static PRCondVar *gSleepWakeup = nullptr; |
|
158 |
|
159 #else |
|
160 |
|
161 static JSRuntime *gRuntime = nullptr; |
|
162 |
|
163 #endif |
|
164 |
|
165 static int gExitCode = 0; |
|
166 static bool gQuitting = false; |
|
167 static bool gGotError = false; |
|
168 static FILE *gErrFile = nullptr; |
|
169 static FILE *gOutFile = nullptr; |
|
170 |
|
171 static bool reportWarnings = true; |
|
172 static bool compileOnly = false; |
|
173 static bool fuzzingSafe = false; |
|
174 |
|
175 #ifdef DEBUG |
|
176 static bool dumpEntrainedVariables = false; |
|
177 static bool OOM_printAllocationCount = false; |
|
178 #endif |
|
179 |
|
180 enum JSShellErrNum { |
|
181 #define MSG_DEF(name, number, count, exception, format) \ |
|
182 name = number, |
|
183 #include "jsshell.msg" |
|
184 #undef MSG_DEF |
|
185 JSShellErr_Limit |
|
186 }; |
|
187 |
|
188 static JSContext * |
|
189 NewContext(JSRuntime *rt); |
|
190 |
|
191 static void |
|
192 DestroyContext(JSContext *cx, bool withGC); |
|
193 |
|
194 static JSObject * |
|
195 NewGlobalObject(JSContext *cx, JS::CompartmentOptions &options, |
|
196 JSPrincipals *principals); |
|
197 |
|
198 static const JSErrorFormatString * |
|
199 my_GetErrorMessage(void *userRef, const char *locale, const unsigned errorNumber); |
|
200 |
|
201 |
|
202 /* |
|
203 * A toy principals type for the shell. |
|
204 * |
|
205 * In the shell, a principal is simply a 32-bit mask: P subsumes Q if the |
|
206 * set bits in P are a superset of those in Q. Thus, the principal 0 is |
|
207 * subsumed by everything, and the principal ~0 subsumes everything. |
|
208 * |
|
209 * As a special case, a null pointer as a principal is treated like 0xffff. |
|
210 * |
|
211 * The 'newGlobal' function takes an option indicating which principal the |
|
212 * new global should have; 'evaluate' does for the new code. |
|
213 */ |
|
214 class ShellPrincipals: public JSPrincipals { |
|
215 uint32_t bits; |
|
216 |
|
217 static uint32_t getBits(JSPrincipals *p) { |
|
218 if (!p) |
|
219 return 0xffff; |
|
220 return static_cast<ShellPrincipals *>(p)->bits; |
|
221 } |
|
222 |
|
223 public: |
|
224 ShellPrincipals(uint32_t bits, int32_t refcount = 0) : bits(bits) { |
|
225 this->refcount = refcount; |
|
226 } |
|
227 |
|
228 static void destroy(JSPrincipals *principals) { |
|
229 MOZ_ASSERT(principals != &fullyTrusted); |
|
230 MOZ_ASSERT(principals->refcount == 0); |
|
231 js_free(static_cast<ShellPrincipals *>(principals)); |
|
232 } |
|
233 |
|
234 static bool subsumes(JSPrincipals *first, JSPrincipals *second) { |
|
235 uint32_t firstBits = getBits(first); |
|
236 uint32_t secondBits = getBits(second); |
|
237 return (firstBits | secondBits) == firstBits; |
|
238 } |
|
239 |
|
240 static JSSecurityCallbacks securityCallbacks; |
|
241 |
|
242 // Fully-trusted principals singleton. |
|
243 static ShellPrincipals fullyTrusted; |
|
244 }; |
|
245 |
|
246 JSSecurityCallbacks ShellPrincipals::securityCallbacks = { |
|
247 nullptr, // contentSecurityPolicyAllows |
|
248 subsumes |
|
249 }; |
|
250 |
|
251 // The fully-trusted principal subsumes all other principals. |
|
252 ShellPrincipals ShellPrincipals::fullyTrusted(-1, 1); |
|
253 |
|
254 #ifdef EDITLINE |
|
255 extern "C" { |
|
256 extern JS_EXPORT_API(char *) readline(const char *prompt); |
|
257 extern JS_EXPORT_API(void) add_history(char *line); |
|
258 } // extern "C" |
|
259 #endif |
|
260 |
|
261 static char * |
|
262 GetLine(FILE *file, const char * prompt) |
|
263 { |
|
264 size_t size; |
|
265 char *buffer; |
|
266 #ifdef EDITLINE |
|
267 /* |
|
268 * Use readline only if file is stdin, because there's no way to specify |
|
269 * another handle. Are other filehandles interactive? |
|
270 */ |
|
271 if (file == stdin) { |
|
272 char *linep = readline(prompt); |
|
273 /* |
|
274 * We set it to zero to avoid complaining about inappropriate ioctl |
|
275 * for device in the case of EOF. Looks like errno == 251 if line is |
|
276 * finished with EOF and errno == 25 (EINVAL on Mac) if there is |
|
277 * nothing left to read. |
|
278 */ |
|
279 if (errno == 251 || errno == 25 || errno == EINVAL) |
|
280 errno = 0; |
|
281 if (!linep) |
|
282 return nullptr; |
|
283 if (linep[0] != '\0') |
|
284 add_history(linep); |
|
285 return linep; |
|
286 } |
|
287 #endif |
|
288 size_t len = 0; |
|
289 if (*prompt != '\0') { |
|
290 fprintf(gOutFile, "%s", prompt); |
|
291 fflush(gOutFile); |
|
292 } |
|
293 size = 80; |
|
294 buffer = (char *) malloc(size); |
|
295 if (!buffer) |
|
296 return nullptr; |
|
297 char *current = buffer; |
|
298 while (fgets(current, size - len, file)) { |
|
299 len += strlen(current); |
|
300 char *t = buffer + len - 1; |
|
301 if (*t == '\n') { |
|
302 /* Line was read. We remove '\n' and exit. */ |
|
303 *t = '\0'; |
|
304 return buffer; |
|
305 } |
|
306 if (len + 1 == size) { |
|
307 size = size * 2; |
|
308 char *tmp = (char *) js_realloc(buffer, size); |
|
309 if (!tmp) { |
|
310 free(buffer); |
|
311 return nullptr; |
|
312 } |
|
313 buffer = tmp; |
|
314 } |
|
315 current = buffer + len; |
|
316 } |
|
317 if (len && !ferror(file)) |
|
318 return buffer; |
|
319 free(buffer); |
|
320 return nullptr; |
|
321 } |
|
322 |
|
323 static char * |
|
324 JSStringToUTF8(JSContext *cx, JSString *str) |
|
325 { |
|
326 JSLinearString *linear = str->ensureLinear(cx); |
|
327 if (!linear) |
|
328 return nullptr; |
|
329 |
|
330 return TwoByteCharsToNewUTF8CharsZ(cx, linear->range()).c_str(); |
|
331 } |
|
332 |
|
333 /* State to store as JSContext private. */ |
|
334 struct JSShellContextData { |
|
335 /* Creation timestamp, used by the elapsed() shell builtin. */ |
|
336 int64_t startTime; |
|
337 }; |
|
338 |
|
339 static JSShellContextData * |
|
340 NewContextData() |
|
341 { |
|
342 /* Prevent creation of new contexts after we have been canceled. */ |
|
343 if (gServiceInterrupt) |
|
344 return nullptr; |
|
345 |
|
346 JSShellContextData *data = (JSShellContextData *) |
|
347 js_calloc(sizeof(JSShellContextData), 1); |
|
348 if (!data) |
|
349 return nullptr; |
|
350 data->startTime = PRMJ_Now(); |
|
351 return data; |
|
352 } |
|
353 |
|
354 static inline JSShellContextData * |
|
355 GetContextData(JSContext *cx) |
|
356 { |
|
357 JSShellContextData *data = (JSShellContextData *) JS_GetContextPrivate(cx); |
|
358 |
|
359 JS_ASSERT(data); |
|
360 return data; |
|
361 } |
|
362 |
|
363 static bool |
|
364 ShellInterruptCallback(JSContext *cx) |
|
365 { |
|
366 if (!gServiceInterrupt) |
|
367 return true; |
|
368 |
|
369 bool result; |
|
370 RootedValue interruptFunc(cx, gInterruptFunc.ref()); |
|
371 if (!interruptFunc.isNull()) { |
|
372 JS::AutoSaveExceptionState savedExc(cx); |
|
373 JSAutoCompartment ac(cx, &interruptFunc.toObject()); |
|
374 RootedValue rval(cx); |
|
375 if (!JS_CallFunctionValue(cx, JS::NullPtr(), interruptFunc, |
|
376 JS::HandleValueArray::empty(), &rval)) |
|
377 { |
|
378 return false; |
|
379 } |
|
380 if (rval.isBoolean()) |
|
381 result = rval.toBoolean(); |
|
382 else |
|
383 result = false; |
|
384 } else { |
|
385 result = false; |
|
386 } |
|
387 |
|
388 if (!result && gExitCode == 0) |
|
389 gExitCode = EXITCODE_TIMEOUT; |
|
390 |
|
391 return result; |
|
392 } |
|
393 |
|
394 /* |
|
395 * Some UTF-8 files, notably those written using Notepad, have a Unicode |
|
396 * Byte-Order-Mark (BOM) as their first character. This is useless (byte-order |
|
397 * is meaningless for UTF-8) but causes a syntax error unless we skip it. |
|
398 */ |
|
399 static void |
|
400 SkipUTF8BOM(FILE* file) |
|
401 { |
|
402 int ch1 = fgetc(file); |
|
403 int ch2 = fgetc(file); |
|
404 int ch3 = fgetc(file); |
|
405 |
|
406 // Skip the BOM |
|
407 if (ch1 == 0xEF && ch2 == 0xBB && ch3 == 0xBF) |
|
408 return; |
|
409 |
|
410 // No BOM - revert |
|
411 if (ch3 != EOF) |
|
412 ungetc(ch3, file); |
|
413 if (ch2 != EOF) |
|
414 ungetc(ch2, file); |
|
415 if (ch1 != EOF) |
|
416 ungetc(ch1, file); |
|
417 } |
|
418 |
|
419 static void |
|
420 RunFile(JSContext *cx, Handle<JSObject*> obj, const char *filename, FILE *file, bool compileOnly) |
|
421 { |
|
422 SkipUTF8BOM(file); |
|
423 |
|
424 // To support the UNIX #! shell hack, gobble the first line if it starts |
|
425 // with '#'. |
|
426 int ch = fgetc(file); |
|
427 if (ch == '#') { |
|
428 while ((ch = fgetc(file)) != EOF) { |
|
429 if (ch == '\n' || ch == '\r') |
|
430 break; |
|
431 } |
|
432 } |
|
433 ungetc(ch, file); |
|
434 |
|
435 int64_t t1 = PRMJ_Now(); |
|
436 RootedScript script(cx); |
|
437 |
|
438 { |
|
439 JS::AutoSaveContextOptions asco(cx); |
|
440 JS::ContextOptionsRef(cx).setNoScriptRval(true); |
|
441 |
|
442 CompileOptions options(cx); |
|
443 options.setIntroductionType("js shell file") |
|
444 .setUTF8(true) |
|
445 .setFileAndLine(filename, 1) |
|
446 .setCompileAndGo(true); |
|
447 |
|
448 gGotError = false; |
|
449 script = JS::Compile(cx, obj, options, file); |
|
450 JS_ASSERT_IF(!script, gGotError); |
|
451 } |
|
452 |
|
453 #ifdef DEBUG |
|
454 if (dumpEntrainedVariables) |
|
455 AnalyzeEntrainedVariables(cx, script); |
|
456 #endif |
|
457 if (script && !compileOnly) { |
|
458 if (!JS_ExecuteScript(cx, obj, script)) { |
|
459 if (!gQuitting && !gServiceInterrupt) |
|
460 gExitCode = EXITCODE_RUNTIME_ERROR; |
|
461 } |
|
462 int64_t t2 = PRMJ_Now() - t1; |
|
463 if (printTiming) |
|
464 printf("runtime = %.3f ms\n", double(t2) / PRMJ_USEC_PER_MSEC); |
|
465 } |
|
466 } |
|
467 |
|
468 static bool |
|
469 EvalAndPrint(JSContext *cx, Handle<JSObject*> global, const char *bytes, size_t length, |
|
470 int lineno, bool compileOnly, FILE *out) |
|
471 { |
|
472 // Eval. |
|
473 JS::CompileOptions options(cx); |
|
474 options.setIntroductionType("js shell interactive") |
|
475 .setUTF8(true) |
|
476 .setCompileAndGo(true) |
|
477 .setFileAndLine("typein", lineno); |
|
478 RootedScript script(cx); |
|
479 script = JS::Compile(cx, global, options, bytes, length); |
|
480 if (!script) |
|
481 return false; |
|
482 if (compileOnly) |
|
483 return true; |
|
484 RootedValue result(cx); |
|
485 if (!JS_ExecuteScript(cx, global, script, &result)) |
|
486 return false; |
|
487 |
|
488 if (!result.isUndefined()) { |
|
489 // Print. |
|
490 RootedString str(cx); |
|
491 str = JS_ValueToSource(cx, result); |
|
492 if (!str) |
|
493 return false; |
|
494 |
|
495 char *utf8chars = JSStringToUTF8(cx, str); |
|
496 if (!utf8chars) |
|
497 return false; |
|
498 fprintf(out, "%s\n", utf8chars); |
|
499 JS_free(cx, utf8chars); |
|
500 } |
|
501 return true; |
|
502 } |
|
503 |
|
504 static void |
|
505 ReadEvalPrintLoop(JSContext *cx, Handle<JSObject*> global, FILE *in, FILE *out, bool compileOnly) |
|
506 { |
|
507 int lineno = 1; |
|
508 bool hitEOF = false; |
|
509 |
|
510 do { |
|
511 /* |
|
512 * Accumulate lines until we get a 'compilable unit' - one that either |
|
513 * generates an error (before running out of source) or that compiles |
|
514 * cleanly. This should be whenever we get a complete statement that |
|
515 * coincides with the end of a line. |
|
516 */ |
|
517 int startline = lineno; |
|
518 typedef Vector<char, 32, ContextAllocPolicy> CharBuffer; |
|
519 CharBuffer buffer(cx); |
|
520 do { |
|
521 ScheduleWatchdog(cx->runtime(), -1); |
|
522 gServiceInterrupt = false; |
|
523 errno = 0; |
|
524 |
|
525 char *line = GetLine(in, startline == lineno ? "js> " : ""); |
|
526 if (!line) { |
|
527 if (errno) { |
|
528 JS_ReportError(cx, strerror(errno)); |
|
529 return; |
|
530 } |
|
531 hitEOF = true; |
|
532 break; |
|
533 } |
|
534 |
|
535 if (!buffer.append(line, strlen(line)) || !buffer.append('\n')) |
|
536 return; |
|
537 |
|
538 lineno++; |
|
539 if (!ScheduleWatchdog(cx->runtime(), gTimeoutInterval)) { |
|
540 hitEOF = true; |
|
541 break; |
|
542 } |
|
543 } while (!JS_BufferIsCompilableUnit(cx, global, buffer.begin(), buffer.length())); |
|
544 |
|
545 if (hitEOF && buffer.empty()) |
|
546 break; |
|
547 |
|
548 if (!EvalAndPrint(cx, global, buffer.begin(), buffer.length(), startline, compileOnly, |
|
549 out)) |
|
550 { |
|
551 // Catch the error, report it, and keep going. |
|
552 JS_ReportPendingException(cx); |
|
553 } |
|
554 } while (!hitEOF && !gQuitting); |
|
555 |
|
556 fprintf(out, "\n"); |
|
557 } |
|
558 |
|
559 class AutoCloseInputFile |
|
560 { |
|
561 private: |
|
562 FILE *f_; |
|
563 public: |
|
564 explicit AutoCloseInputFile(FILE *f) : f_(f) {} |
|
565 ~AutoCloseInputFile() { |
|
566 if (f_ && f_ != stdin) |
|
567 fclose(f_); |
|
568 } |
|
569 }; |
|
570 |
|
571 static void |
|
572 Process(JSContext *cx, JSObject *obj_, const char *filename, bool forceTTY) |
|
573 { |
|
574 RootedObject obj(cx, obj_); |
|
575 |
|
576 FILE *file; |
|
577 if (forceTTY || !filename || strcmp(filename, "-") == 0) { |
|
578 file = stdin; |
|
579 } else { |
|
580 file = fopen(filename, "r"); |
|
581 if (!file) { |
|
582 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, |
|
583 JSSMSG_CANT_OPEN, filename, strerror(errno)); |
|
584 gExitCode = EXITCODE_FILE_NOT_FOUND; |
|
585 return; |
|
586 } |
|
587 } |
|
588 AutoCloseInputFile autoClose(file); |
|
589 |
|
590 if (!forceTTY && !isatty(fileno(file))) { |
|
591 // It's not interactive - just execute it. |
|
592 RunFile(cx, obj, filename, file, compileOnly); |
|
593 } else { |
|
594 // It's an interactive filehandle; drop into read-eval-print loop. |
|
595 ReadEvalPrintLoop(cx, obj, file, gOutFile, compileOnly); |
|
596 } |
|
597 } |
|
598 |
|
599 static bool |
|
600 Version(JSContext *cx, unsigned argc, jsval *vp) |
|
601 { |
|
602 CallArgs args = CallArgsFromVp(argc, vp); |
|
603 JSVersion origVersion = JS_GetVersion(cx); |
|
604 if (args.length() == 0 || JSVAL_IS_VOID(args[0])) { |
|
605 /* Get version. */ |
|
606 args.rval().setInt32(origVersion); |
|
607 } else { |
|
608 /* Set version. */ |
|
609 int32_t v = -1; |
|
610 if (args[0].isInt32()) { |
|
611 v = args[0].toInt32(); |
|
612 } else if (args[0].isDouble()) { |
|
613 double fv = args[0].toDouble(); |
|
614 int32_t fvi; |
|
615 if (NumberEqualsInt32(fv, &fvi)) |
|
616 v = fvi; |
|
617 } |
|
618 if (v < 0 || v > JSVERSION_LATEST) { |
|
619 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "version"); |
|
620 return false; |
|
621 } |
|
622 JS_SetVersionForCompartment(js::GetContextCompartment(cx), JSVersion(v)); |
|
623 args.rval().setInt32(origVersion); |
|
624 } |
|
625 return true; |
|
626 } |
|
627 |
|
628 /* |
|
629 * Resolve a (possibly) relative filename to an absolute path. If |
|
630 * |scriptRelative| is true, then the result will be relative to the directory |
|
631 * containing the currently-running script, or the current working directory if |
|
632 * the currently-running script is "-e" (namely, you're using it from the |
|
633 * command line.) Otherwise, it will be relative to the current working |
|
634 * directory. |
|
635 */ |
|
636 static JSString * |
|
637 ResolvePath(JSContext *cx, HandleString filenameStr, PathResolutionMode resolveMode) |
|
638 { |
|
639 JSAutoByteString filename(cx, filenameStr); |
|
640 if (!filename) |
|
641 return nullptr; |
|
642 |
|
643 const char *pathname = filename.ptr(); |
|
644 if (pathname[0] == '/') |
|
645 return filenameStr; |
|
646 #ifdef XP_WIN |
|
647 // Various forms of absolute paths per http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx |
|
648 // "\..." |
|
649 if (pathname[0] == '\\') |
|
650 return filenameStr; |
|
651 // "C:\..." |
|
652 if (strlen(pathname) > 3 && isalpha(pathname[0]) && pathname[1] == ':' && pathname[2] == '\\') |
|
653 return filenameStr; |
|
654 // "\\..." |
|
655 if (strlen(pathname) > 2 && pathname[1] == '\\' && pathname[2] == '\\') |
|
656 return filenameStr; |
|
657 #endif |
|
658 |
|
659 /* Get the currently executing script's name. */ |
|
660 JS::AutoFilename scriptFilename; |
|
661 if (!DescribeScriptedCaller(cx, &scriptFilename)) |
|
662 return nullptr; |
|
663 |
|
664 if (!scriptFilename.get()) |
|
665 return nullptr; |
|
666 |
|
667 if (strcmp(scriptFilename.get(), "-e") == 0 || strcmp(scriptFilename.get(), "typein") == 0) |
|
668 resolveMode = RootRelative; |
|
669 |
|
670 static char buffer[PATH_MAX+1]; |
|
671 if (resolveMode == ScriptRelative) { |
|
672 #ifdef XP_WIN |
|
673 // The docs say it can return EINVAL, but the compiler says it's void |
|
674 _splitpath(scriptFilename.get(), nullptr, buffer, nullptr, nullptr); |
|
675 #else |
|
676 strncpy(buffer, scriptFilename.get(), PATH_MAX+1); |
|
677 if (buffer[PATH_MAX] != '\0') |
|
678 return nullptr; |
|
679 |
|
680 // dirname(buffer) might return buffer, or it might return a |
|
681 // statically-allocated string |
|
682 memmove(buffer, dirname(buffer), strlen(buffer) + 1); |
|
683 #endif |
|
684 } else { |
|
685 const char *cwd = getcwd(buffer, PATH_MAX); |
|
686 if (!cwd) |
|
687 return nullptr; |
|
688 } |
|
689 |
|
690 size_t len = strlen(buffer); |
|
691 buffer[len] = '/'; |
|
692 strncpy(buffer + len + 1, pathname, sizeof(buffer) - (len+1)); |
|
693 if (buffer[PATH_MAX] != '\0') |
|
694 return nullptr; |
|
695 |
|
696 return JS_NewStringCopyZ(cx, buffer); |
|
697 } |
|
698 |
|
699 static bool |
|
700 CreateMappedArrayBuffer(JSContext *cx, unsigned argc, Value *vp) |
|
701 { |
|
702 CallArgs args = CallArgsFromVp(argc, vp); |
|
703 |
|
704 if (args.length() < 1 || args.length() > 3) { |
|
705 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, |
|
706 args.length() < 1 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS, |
|
707 "createMappedArrayBuffer"); |
|
708 return false; |
|
709 } |
|
710 |
|
711 RootedString rawFilenameStr(cx, JS::ToString(cx, args[0])); |
|
712 if (!rawFilenameStr) |
|
713 return false; |
|
714 // It's a little bizarre to resolve relative to the script, but for testing |
|
715 // I need a file at a known location, and the only good way I know of to do |
|
716 // that right now is to include it in the repo alongside the test script. |
|
717 // Bug 944164 would introduce an alternative. |
|
718 JSString *filenameStr = ResolvePath(cx, rawFilenameStr, ScriptRelative); |
|
719 if (!filenameStr) |
|
720 return false; |
|
721 JSAutoByteString filename(cx, filenameStr); |
|
722 if (!filename) |
|
723 return false; |
|
724 |
|
725 uint32_t offset = 0; |
|
726 if (args.length() >= 2) { |
|
727 if (!JS::ToUint32(cx, args[1], &offset)) |
|
728 return false; |
|
729 } |
|
730 |
|
731 bool sizeGiven = false; |
|
732 uint32_t size; |
|
733 if (args.length() >= 3) { |
|
734 if (!JS::ToUint32(cx, args[2], &size)) |
|
735 return false; |
|
736 sizeGiven = true; |
|
737 if (offset > size) { |
|
738 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, |
|
739 JSMSG_ARG_INDEX_OUT_OF_RANGE, "2"); |
|
740 return false; |
|
741 } |
|
742 } |
|
743 |
|
744 FILE *file = fopen(filename.ptr(), "r"); |
|
745 if (!file) { |
|
746 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, |
|
747 JSSMSG_CANT_OPEN, filename.ptr(), strerror(errno)); |
|
748 return false; |
|
749 } |
|
750 AutoCloseInputFile autoClose(file); |
|
751 |
|
752 if (!sizeGiven) { |
|
753 struct stat st; |
|
754 if (fstat(fileno(file), &st) < 0) { |
|
755 JS_ReportError(cx, "Unable to stat file"); |
|
756 return false; |
|
757 } |
|
758 if (st.st_size < offset) { |
|
759 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, |
|
760 JSMSG_ARG_INDEX_OUT_OF_RANGE, "2"); |
|
761 return false; |
|
762 } |
|
763 size = st.st_size - offset; |
|
764 } |
|
765 |
|
766 void *contents = JS_CreateMappedArrayBufferContents(fileno(file), offset, size); |
|
767 if (!contents) { |
|
768 JS_ReportError(cx, "failed to allocate mapped array buffer contents (possibly due to bad alignment)"); |
|
769 return false; |
|
770 } |
|
771 |
|
772 RootedObject obj(cx, JS_NewMappedArrayBufferWithContents(cx, size, contents)); |
|
773 if (!obj) |
|
774 return false; |
|
775 |
|
776 args.rval().setObject(*obj); |
|
777 return true; |
|
778 } |
|
779 |
|
780 static bool |
|
781 Options(JSContext *cx, unsigned argc, jsval *vp) |
|
782 { |
|
783 CallArgs args = CallArgsFromVp(argc, vp); |
|
784 |
|
785 JS::ContextOptions oldContextOptions = JS::ContextOptionsRef(cx); |
|
786 for (unsigned i = 0; i < args.length(); i++) { |
|
787 JSString *str = JS::ToString(cx, args[i]); |
|
788 if (!str) |
|
789 return false; |
|
790 args[i].setString(str); |
|
791 |
|
792 JSAutoByteString opt(cx, str); |
|
793 if (!opt) |
|
794 return false; |
|
795 |
|
796 if (strcmp(opt.ptr(), "strict") == 0) |
|
797 JS::ContextOptionsRef(cx).toggleExtraWarnings(); |
|
798 else if (strcmp(opt.ptr(), "werror") == 0) |
|
799 JS::ContextOptionsRef(cx).toggleWerror(); |
|
800 else if (strcmp(opt.ptr(), "strict_mode") == 0) |
|
801 JS::ContextOptionsRef(cx).toggleStrictMode(); |
|
802 else { |
|
803 char* msg = JS_sprintf_append(nullptr, |
|
804 "unknown option name '%s'." |
|
805 " The valid names are strict," |
|
806 " werror, and strict_mode.", |
|
807 opt.ptr()); |
|
808 if (!msg) { |
|
809 JS_ReportOutOfMemory(cx); |
|
810 return false; |
|
811 } |
|
812 |
|
813 JS_ReportError(cx, msg); |
|
814 free(msg); |
|
815 return false; |
|
816 } |
|
817 } |
|
818 |
|
819 char *names = strdup(""); |
|
820 bool found = false; |
|
821 if (!names && oldContextOptions.extraWarnings()) { |
|
822 names = JS_sprintf_append(names, "%s%s", found ? "," : "", "strict"); |
|
823 found = true; |
|
824 } |
|
825 if (!names && oldContextOptions.werror()) { |
|
826 names = JS_sprintf_append(names, "%s%s", found ? "," : "", "werror"); |
|
827 found = true; |
|
828 } |
|
829 if (!names && oldContextOptions.strictMode()) { |
|
830 names = JS_sprintf_append(names, "%s%s", found ? "," : "", "strict_mode"); |
|
831 found = true; |
|
832 } |
|
833 if (!names) { |
|
834 JS_ReportOutOfMemory(cx); |
|
835 return false; |
|
836 } |
|
837 |
|
838 JSString *str = JS_NewStringCopyZ(cx, names); |
|
839 free(names); |
|
840 if (!str) |
|
841 return false; |
|
842 args.rval().setString(str); |
|
843 return true; |
|
844 } |
|
845 |
|
846 static bool |
|
847 LoadScript(JSContext *cx, unsigned argc, jsval *vp, bool scriptRelative) |
|
848 { |
|
849 CallArgs args = CallArgsFromVp(argc, vp); |
|
850 RootedObject thisobj(cx, JS_THIS_OBJECT(cx, vp)); |
|
851 if (!thisobj) |
|
852 return false; |
|
853 |
|
854 RootedString str(cx); |
|
855 for (unsigned i = 0; i < args.length(); i++) { |
|
856 str = JS::ToString(cx, args[i]); |
|
857 if (!str) { |
|
858 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "load"); |
|
859 return false; |
|
860 } |
|
861 str = ResolvePath(cx, str, scriptRelative ? ScriptRelative : RootRelative); |
|
862 if (!str) { |
|
863 JS_ReportError(cx, "unable to resolve path"); |
|
864 return false; |
|
865 } |
|
866 JSAutoByteString filename(cx, str); |
|
867 if (!filename) |
|
868 return false; |
|
869 errno = 0; |
|
870 CompileOptions opts(cx); |
|
871 opts.setIntroductionType("js shell load") |
|
872 .setUTF8(true) |
|
873 .setCompileAndGo(true) |
|
874 .setNoScriptRval(true); |
|
875 if ((compileOnly && !Compile(cx, thisobj, opts, filename.ptr())) || |
|
876 !Evaluate(cx, thisobj, opts, filename.ptr())) |
|
877 { |
|
878 return false; |
|
879 } |
|
880 } |
|
881 |
|
882 args.rval().setUndefined(); |
|
883 return true; |
|
884 } |
|
885 |
|
886 static bool |
|
887 Load(JSContext *cx, unsigned argc, jsval *vp) |
|
888 { |
|
889 return LoadScript(cx, argc, vp, false); |
|
890 } |
|
891 |
|
892 static bool |
|
893 LoadScriptRelativeToScript(JSContext *cx, unsigned argc, jsval *vp) |
|
894 { |
|
895 return LoadScript(cx, argc, vp, true); |
|
896 } |
|
897 |
|
898 // Populate |options| with the options given by |opts|'s properties. If we |
|
899 // need to convert a filename to a C string, let fileNameBytes own the |
|
900 // bytes. |
|
901 static bool |
|
902 ParseCompileOptions(JSContext *cx, CompileOptions &options, HandleObject opts, |
|
903 JSAutoByteString &fileNameBytes) |
|
904 { |
|
905 RootedValue v(cx); |
|
906 RootedString s(cx); |
|
907 |
|
908 if (!JS_GetProperty(cx, opts, "compileAndGo", &v)) |
|
909 return false; |
|
910 if (!v.isUndefined()) |
|
911 options.setCompileAndGo(ToBoolean(v)); |
|
912 |
|
913 if (!JS_GetProperty(cx, opts, "noScriptRval", &v)) |
|
914 return false; |
|
915 if (!v.isUndefined()) |
|
916 options.setNoScriptRval(ToBoolean(v)); |
|
917 |
|
918 if (!JS_GetProperty(cx, opts, "fileName", &v)) |
|
919 return false; |
|
920 if (v.isNull()) { |
|
921 options.setFile(nullptr); |
|
922 } else if (!v.isUndefined()) { |
|
923 s = ToString(cx, v); |
|
924 if (!s) |
|
925 return false; |
|
926 char *fileName = fileNameBytes.encodeLatin1(cx, s); |
|
927 if (!fileName) |
|
928 return false; |
|
929 options.setFile(fileName); |
|
930 } |
|
931 |
|
932 if (!JS_GetProperty(cx, opts, "element", &v)) |
|
933 return false; |
|
934 if (v.isObject()) |
|
935 options.setElement(&v.toObject()); |
|
936 |
|
937 if (!JS_GetProperty(cx, opts, "elementAttributeName", &v)) |
|
938 return false; |
|
939 if (!v.isUndefined()) { |
|
940 s = ToString(cx, v); |
|
941 if (!s) |
|
942 return false; |
|
943 options.setElementAttributeName(s); |
|
944 } |
|
945 |
|
946 if (!JS_GetProperty(cx, opts, "lineNumber", &v)) |
|
947 return false; |
|
948 if (!v.isUndefined()) { |
|
949 uint32_t u; |
|
950 if (!ToUint32(cx, v, &u)) |
|
951 return false; |
|
952 options.setLine(u); |
|
953 } |
|
954 |
|
955 if (!JS_GetProperty(cx, opts, "sourceIsLazy", &v)) |
|
956 return false; |
|
957 if (v.isBoolean()) |
|
958 options.setSourceIsLazy(v.toBoolean()); |
|
959 |
|
960 return true; |
|
961 } |
|
962 |
|
963 class AutoNewContext |
|
964 { |
|
965 private: |
|
966 JSContext *oldcx; |
|
967 JSContext *newcx; |
|
968 Maybe<JSAutoRequest> newRequest; |
|
969 Maybe<AutoCompartment> newCompartment; |
|
970 |
|
971 AutoNewContext(const AutoNewContext &) MOZ_DELETE; |
|
972 |
|
973 public: |
|
974 AutoNewContext() : oldcx(nullptr), newcx(nullptr) {} |
|
975 |
|
976 bool enter(JSContext *cx) { |
|
977 JS_ASSERT(!JS_IsExceptionPending(cx)); |
|
978 oldcx = cx; |
|
979 newcx = NewContext(JS_GetRuntime(cx)); |
|
980 if (!newcx) |
|
981 return false; |
|
982 JS::ContextOptionsRef(newcx).setDontReportUncaught(true); |
|
983 js::SetDefaultObjectForContext(newcx, JS::CurrentGlobalOrNull(cx)); |
|
984 |
|
985 newRequest.construct(newcx); |
|
986 newCompartment.construct(newcx, JS::CurrentGlobalOrNull(cx)); |
|
987 return true; |
|
988 } |
|
989 |
|
990 JSContext *get() { return newcx; } |
|
991 |
|
992 ~AutoNewContext() { |
|
993 if (newcx) { |
|
994 RootedValue exc(oldcx); |
|
995 bool throwing = JS_IsExceptionPending(newcx); |
|
996 if (throwing) |
|
997 JS_GetPendingException(newcx, &exc); |
|
998 newCompartment.destroy(); |
|
999 newRequest.destroy(); |
|
1000 if (throwing) |
|
1001 JS_SetPendingException(oldcx, exc); |
|
1002 DestroyContext(newcx, false); |
|
1003 } |
|
1004 } |
|
1005 }; |
|
1006 |
|
1007 static const uint32_t CacheEntry_SOURCE = 0; |
|
1008 static const uint32_t CacheEntry_BYTECODE = 1; |
|
1009 |
|
1010 static const JSClass CacheEntry_class = { |
|
1011 "CacheEntryObject", JSCLASS_HAS_RESERVED_SLOTS(2), |
|
1012 JS_PropertyStub, /* addProperty */ |
|
1013 JS_DeletePropertyStub, /* delProperty */ |
|
1014 JS_PropertyStub, /* getProperty */ |
|
1015 JS_StrictPropertyStub, /* setProperty */ |
|
1016 JS_EnumerateStub, |
|
1017 JS_ResolveStub, |
|
1018 JS_ConvertStub, |
|
1019 nullptr, /* finalize */ |
|
1020 nullptr, /* call */ |
|
1021 nullptr, /* hasInstance */ |
|
1022 nullptr, /* construct */ |
|
1023 nullptr, /* trace */ |
|
1024 JSCLASS_NO_INTERNAL_MEMBERS |
|
1025 }; |
|
1026 |
|
1027 static bool |
|
1028 CacheEntry(JSContext* cx, unsigned argc, JS::Value *vp) |
|
1029 { |
|
1030 CallArgs args = CallArgsFromVp(argc, vp); |
|
1031 |
|
1032 if (args.length() != 1 || !args[0].isString()) { |
|
1033 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "CacheEntry"); |
|
1034 return false; |
|
1035 } |
|
1036 |
|
1037 RootedObject obj(cx, JS_NewObject(cx, &CacheEntry_class, JS::NullPtr(), JS::NullPtr())); |
|
1038 if (!obj) |
|
1039 return false; |
|
1040 |
|
1041 SetReservedSlot(obj, CacheEntry_SOURCE, args[0]); |
|
1042 SetReservedSlot(obj, CacheEntry_BYTECODE, UndefinedValue()); |
|
1043 args.rval().setObject(*obj); |
|
1044 return true; |
|
1045 } |
|
1046 |
|
1047 static bool |
|
1048 CacheEntry_isCacheEntry(JSObject *cache) |
|
1049 { |
|
1050 return JS_GetClass(cache) == &CacheEntry_class; |
|
1051 } |
|
1052 |
|
1053 static JSString * |
|
1054 CacheEntry_getSource(HandleObject cache) |
|
1055 { |
|
1056 JS_ASSERT(CacheEntry_isCacheEntry(cache)); |
|
1057 Value v = JS_GetReservedSlot(cache, CacheEntry_SOURCE); |
|
1058 if (!v.isString()) |
|
1059 return nullptr; |
|
1060 |
|
1061 return v.toString(); |
|
1062 } |
|
1063 |
|
1064 static uint8_t * |
|
1065 CacheEntry_getBytecode(HandleObject cache, uint32_t *length) |
|
1066 { |
|
1067 JS_ASSERT(CacheEntry_isCacheEntry(cache)); |
|
1068 Value v = JS_GetReservedSlot(cache, CacheEntry_BYTECODE); |
|
1069 if (!v.isObject() || !v.toObject().is<ArrayBufferObject>()) |
|
1070 return nullptr; |
|
1071 |
|
1072 ArrayBufferObject *arrayBuffer = &v.toObject().as<ArrayBufferObject>(); |
|
1073 *length = arrayBuffer->byteLength(); |
|
1074 return arrayBuffer->dataPointer(); |
|
1075 } |
|
1076 |
|
1077 static bool |
|
1078 CacheEntry_setBytecode(JSContext *cx, HandleObject cache, uint8_t *buffer, uint32_t length) |
|
1079 { |
|
1080 JS_ASSERT(CacheEntry_isCacheEntry(cache)); |
|
1081 Rooted<ArrayBufferObject*> arrayBuffer(cx, ArrayBufferObject::create(cx, length, buffer)); |
|
1082 |
|
1083 if (!arrayBuffer || !ArrayBufferObject::ensureNonInline(cx, arrayBuffer)) |
|
1084 return false; |
|
1085 |
|
1086 SetReservedSlot(cache, CacheEntry_BYTECODE, OBJECT_TO_JSVAL(arrayBuffer)); |
|
1087 return true; |
|
1088 } |
|
1089 |
|
1090 class AutoSaveFrameChain |
|
1091 { |
|
1092 JSContext *cx_; |
|
1093 bool saved_; |
|
1094 |
|
1095 public: |
|
1096 AutoSaveFrameChain(JSContext *cx) |
|
1097 : cx_(cx), |
|
1098 saved_(false) |
|
1099 {} |
|
1100 |
|
1101 bool save() { |
|
1102 if (!JS_SaveFrameChain(cx_)) |
|
1103 return false; |
|
1104 saved_ = true; |
|
1105 return true; |
|
1106 } |
|
1107 |
|
1108 ~AutoSaveFrameChain() { |
|
1109 if (saved_) |
|
1110 JS_RestoreFrameChain(cx_); |
|
1111 } |
|
1112 }; |
|
1113 |
|
1114 static bool |
|
1115 Evaluate(JSContext *cx, unsigned argc, jsval *vp) |
|
1116 { |
|
1117 CallArgs args = CallArgsFromVp(argc, vp); |
|
1118 |
|
1119 if (args.length() < 1 || args.length() > 2) { |
|
1120 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, |
|
1121 args.length() < 1 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS, |
|
1122 "evaluate"); |
|
1123 return false; |
|
1124 } |
|
1125 |
|
1126 RootedString code(cx, nullptr); |
|
1127 RootedObject cacheEntry(cx, nullptr); |
|
1128 if (args[0].isString()) { |
|
1129 code = args[0].toString(); |
|
1130 } else if (args[0].isObject() && CacheEntry_isCacheEntry(&args[0].toObject())) { |
|
1131 cacheEntry = &args[0].toObject(); |
|
1132 code = CacheEntry_getSource(cacheEntry); |
|
1133 } |
|
1134 |
|
1135 if (!code || (args.length() == 2 && args[1].isPrimitive())) { |
|
1136 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "evaluate"); |
|
1137 return false; |
|
1138 } |
|
1139 |
|
1140 CompileOptions options(cx); |
|
1141 JSAutoByteString fileNameBytes; |
|
1142 bool newContext = false; |
|
1143 RootedString displayURL(cx); |
|
1144 RootedString sourceMapURL(cx); |
|
1145 RootedObject global(cx, nullptr); |
|
1146 bool catchTermination = false; |
|
1147 bool saveFrameChain = false; |
|
1148 bool loadBytecode = false; |
|
1149 bool saveBytecode = false; |
|
1150 bool assertEqBytecode = false; |
|
1151 RootedObject callerGlobal(cx, cx->global()); |
|
1152 |
|
1153 options.setIntroductionType("js shell evaluate") |
|
1154 .setFileAndLine("@evaluate", 1); |
|
1155 |
|
1156 global = JS_GetGlobalForObject(cx, &args.callee()); |
|
1157 if (!global) |
|
1158 return false; |
|
1159 |
|
1160 if (args.length() == 2) { |
|
1161 RootedObject opts(cx, &args[1].toObject()); |
|
1162 RootedValue v(cx); |
|
1163 |
|
1164 if (!ParseCompileOptions(cx, options, opts, fileNameBytes)) |
|
1165 return false; |
|
1166 |
|
1167 if (!JS_GetProperty(cx, opts, "newContext", &v)) |
|
1168 return false; |
|
1169 if (!v.isUndefined()) |
|
1170 newContext = ToBoolean(v); |
|
1171 |
|
1172 if (!JS_GetProperty(cx, opts, "displayURL", &v)) |
|
1173 return false; |
|
1174 if (!v.isUndefined()) { |
|
1175 displayURL = ToString(cx, v); |
|
1176 if (!displayURL) |
|
1177 return false; |
|
1178 } |
|
1179 |
|
1180 if (!JS_GetProperty(cx, opts, "sourceMapURL", &v)) |
|
1181 return false; |
|
1182 if (!v.isUndefined()) { |
|
1183 sourceMapURL = ToString(cx, v); |
|
1184 if (!sourceMapURL) |
|
1185 return false; |
|
1186 } |
|
1187 |
|
1188 if (!JS_GetProperty(cx, opts, "global", &v)) |
|
1189 return false; |
|
1190 if (!v.isUndefined()) { |
|
1191 if (v.isObject()) { |
|
1192 global = js::UncheckedUnwrap(&v.toObject()); |
|
1193 if (!global) |
|
1194 return false; |
|
1195 } |
|
1196 if (!global || !(JS_GetClass(global)->flags & JSCLASS_IS_GLOBAL)) { |
|
1197 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE, |
|
1198 "\"global\" passed to evaluate()", "not a global object"); |
|
1199 return false; |
|
1200 } |
|
1201 } |
|
1202 |
|
1203 if (!JS_GetProperty(cx, opts, "catchTermination", &v)) |
|
1204 return false; |
|
1205 if (!v.isUndefined()) |
|
1206 catchTermination = ToBoolean(v); |
|
1207 |
|
1208 if (!JS_GetProperty(cx, opts, "saveFrameChain", &v)) |
|
1209 return false; |
|
1210 if (!v.isUndefined()) |
|
1211 saveFrameChain = ToBoolean(v); |
|
1212 |
|
1213 if (!JS_GetProperty(cx, opts, "loadBytecode", &v)) |
|
1214 return false; |
|
1215 if (!v.isUndefined()) |
|
1216 loadBytecode = ToBoolean(v); |
|
1217 |
|
1218 if (!JS_GetProperty(cx, opts, "saveBytecode", &v)) |
|
1219 return false; |
|
1220 if (!v.isUndefined()) |
|
1221 saveBytecode = ToBoolean(v); |
|
1222 |
|
1223 if (!JS_GetProperty(cx, opts, "assertEqBytecode", &v)) |
|
1224 return false; |
|
1225 if (!v.isUndefined()) |
|
1226 assertEqBytecode = ToBoolean(v); |
|
1227 |
|
1228 // We cannot load or save the bytecode if we have no object where the |
|
1229 // bytecode cache is stored. |
|
1230 if (loadBytecode || saveBytecode) { |
|
1231 if (!cacheEntry) { |
|
1232 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, |
|
1233 "evaluate"); |
|
1234 return false; |
|
1235 } |
|
1236 } |
|
1237 } |
|
1238 |
|
1239 size_t codeLength; |
|
1240 const jschar *codeChars = JS_GetStringCharsAndLength(cx, code, &codeLength); |
|
1241 if (!codeChars) |
|
1242 return false; |
|
1243 |
|
1244 AutoNewContext ancx; |
|
1245 if (newContext) { |
|
1246 if (!ancx.enter(cx)) |
|
1247 return false; |
|
1248 cx = ancx.get(); |
|
1249 } |
|
1250 |
|
1251 uint32_t loadLength = 0; |
|
1252 uint8_t *loadBuffer = nullptr; |
|
1253 uint32_t saveLength = 0; |
|
1254 ScopedJSFreePtr<uint8_t> saveBuffer; |
|
1255 |
|
1256 if (loadBytecode) { |
|
1257 loadBuffer = CacheEntry_getBytecode(cacheEntry, &loadLength); |
|
1258 if (!loadBuffer) |
|
1259 return false; |
|
1260 } |
|
1261 |
|
1262 { |
|
1263 AutoSaveFrameChain asfc(cx); |
|
1264 if (saveFrameChain && !asfc.save()) |
|
1265 return false; |
|
1266 |
|
1267 JSAutoCompartment ac(cx, global); |
|
1268 RootedScript script(cx); |
|
1269 |
|
1270 if (!options.wrap(cx, cx->compartment())) |
|
1271 return false; |
|
1272 |
|
1273 { |
|
1274 JS::AutoSaveContextOptions asco(cx); |
|
1275 JS::ContextOptionsRef(cx).setNoScriptRval(options.noScriptRval); |
|
1276 if (saveBytecode) { |
|
1277 if (!JS::CompartmentOptionsRef(cx).getSingletonsAsTemplates()) { |
|
1278 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, |
|
1279 JSSMSG_CACHE_SINGLETON_FAILED); |
|
1280 return false; |
|
1281 } |
|
1282 JS::CompartmentOptionsRef(cx).cloneSingletonsOverride().set(true); |
|
1283 } |
|
1284 |
|
1285 if (loadBytecode) { |
|
1286 script = JS_DecodeScript(cx, loadBuffer, loadLength, options.originPrincipals(cx)); |
|
1287 } else { |
|
1288 script = JS::Compile(cx, global, options, codeChars, codeLength); |
|
1289 } |
|
1290 |
|
1291 if (!script) |
|
1292 return false; |
|
1293 } |
|
1294 |
|
1295 if (displayURL && !script->scriptSource()->hasDisplayURL()) { |
|
1296 const jschar *durl = JS_GetStringCharsZ(cx, displayURL); |
|
1297 if (!durl) |
|
1298 return false; |
|
1299 if (!script->scriptSource()->setDisplayURL(cx, durl)) |
|
1300 return false; |
|
1301 } |
|
1302 if (sourceMapURL && !script->scriptSource()->hasSourceMapURL()) { |
|
1303 const jschar *smurl = JS_GetStringCharsZ(cx, sourceMapURL); |
|
1304 if (!smurl) |
|
1305 return false; |
|
1306 if (!script->scriptSource()->setSourceMapURL(cx, smurl)) |
|
1307 return false; |
|
1308 } |
|
1309 if (!JS_ExecuteScript(cx, global, script, args.rval())) { |
|
1310 if (catchTermination && !JS_IsExceptionPending(cx)) { |
|
1311 JSAutoCompartment ac1(cx, callerGlobal); |
|
1312 JSString *str = JS_NewStringCopyZ(cx, "terminated"); |
|
1313 if (!str) |
|
1314 return false; |
|
1315 args.rval().setString(str); |
|
1316 return true; |
|
1317 } |
|
1318 return false; |
|
1319 } |
|
1320 |
|
1321 if (saveBytecode) { |
|
1322 saveBuffer = reinterpret_cast<uint8_t *>(JS_EncodeScript(cx, script, &saveLength)); |
|
1323 if (!saveBuffer) |
|
1324 return false; |
|
1325 } |
|
1326 } |
|
1327 |
|
1328 if (saveBytecode) { |
|
1329 // If we are both loading and saving, we assert that we are going to |
|
1330 // replace the current bytecode by the same stream of bytes. |
|
1331 if (loadBytecode && assertEqBytecode) { |
|
1332 if (saveLength != loadLength) { |
|
1333 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_CACHE_EQ_SIZE_FAILED, |
|
1334 loadLength, saveLength); |
|
1335 } else if (!mozilla::PodEqual(loadBuffer, saveBuffer.get(), loadLength)) { |
|
1336 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, |
|
1337 JSSMSG_CACHE_EQ_CONTENT_FAILED); |
|
1338 } |
|
1339 } |
|
1340 |
|
1341 if (!CacheEntry_setBytecode(cx, cacheEntry, saveBuffer, saveLength)) |
|
1342 return false; |
|
1343 |
|
1344 saveBuffer.forget(); |
|
1345 } |
|
1346 |
|
1347 return JS_WrapValue(cx, args.rval()); |
|
1348 } |
|
1349 |
|
1350 static JSString * |
|
1351 FileAsString(JSContext *cx, const char *pathname) |
|
1352 { |
|
1353 FILE *file; |
|
1354 RootedString str(cx); |
|
1355 size_t len, cc; |
|
1356 char *buf; |
|
1357 |
|
1358 file = fopen(pathname, "rb"); |
|
1359 if (!file) { |
|
1360 JS_ReportError(cx, "can't open %s: %s", pathname, strerror(errno)); |
|
1361 return nullptr; |
|
1362 } |
|
1363 AutoCloseInputFile autoClose(file); |
|
1364 |
|
1365 if (fseek(file, 0, SEEK_END) != 0) { |
|
1366 JS_ReportError(cx, "can't seek end of %s", pathname); |
|
1367 } else { |
|
1368 len = ftell(file); |
|
1369 if (fseek(file, 0, SEEK_SET) != 0) { |
|
1370 JS_ReportError(cx, "can't seek start of %s", pathname); |
|
1371 } else { |
|
1372 buf = (char*) JS_malloc(cx, len + 1); |
|
1373 if (buf) { |
|
1374 cc = fread(buf, 1, len, file); |
|
1375 if (cc != len) { |
|
1376 JS_ReportError(cx, "can't read %s: %s", pathname, |
|
1377 (ptrdiff_t(cc) < 0) ? strerror(errno) : "short read"); |
|
1378 } else { |
|
1379 jschar *ucbuf = |
|
1380 JS::UTF8CharsToNewTwoByteCharsZ(cx, JS::UTF8Chars(buf, len), &len).get(); |
|
1381 if (!ucbuf) { |
|
1382 JS_ReportError(cx, "Invalid UTF-8 in file '%s'", pathname); |
|
1383 gExitCode = EXITCODE_RUNTIME_ERROR; |
|
1384 return nullptr; |
|
1385 } |
|
1386 str = JS_NewUCStringCopyN(cx, ucbuf, len); |
|
1387 free(ucbuf); |
|
1388 } |
|
1389 JS_free(cx, buf); |
|
1390 } |
|
1391 } |
|
1392 } |
|
1393 |
|
1394 return str; |
|
1395 } |
|
1396 |
|
1397 static JSObject * |
|
1398 FileAsTypedArray(JSContext *cx, const char *pathname) |
|
1399 { |
|
1400 FILE *file = fopen(pathname, "rb"); |
|
1401 if (!file) { |
|
1402 JS_ReportError(cx, "can't open %s: %s", pathname, strerror(errno)); |
|
1403 return nullptr; |
|
1404 } |
|
1405 AutoCloseInputFile autoClose(file); |
|
1406 |
|
1407 RootedObject obj(cx); |
|
1408 if (fseek(file, 0, SEEK_END) != 0) { |
|
1409 JS_ReportError(cx, "can't seek end of %s", pathname); |
|
1410 } else { |
|
1411 size_t len = ftell(file); |
|
1412 if (fseek(file, 0, SEEK_SET) != 0) { |
|
1413 JS_ReportError(cx, "can't seek start of %s", pathname); |
|
1414 } else { |
|
1415 obj = JS_NewUint8Array(cx, len); |
|
1416 if (!obj) |
|
1417 return nullptr; |
|
1418 char *buf = (char *) obj->as<TypedArrayObject>().viewData(); |
|
1419 size_t cc = fread(buf, 1, len, file); |
|
1420 if (cc != len) { |
|
1421 JS_ReportError(cx, "can't read %s: %s", pathname, |
|
1422 (ptrdiff_t(cc) < 0) ? strerror(errno) : "short read"); |
|
1423 obj = nullptr; |
|
1424 } |
|
1425 } |
|
1426 } |
|
1427 return obj; |
|
1428 } |
|
1429 |
|
1430 /* |
|
1431 * Function to run scripts and return compilation + execution time. Semantics |
|
1432 * are closely modelled after the equivalent function in WebKit, as this is used |
|
1433 * to produce benchmark timings by SunSpider. |
|
1434 */ |
|
1435 static bool |
|
1436 Run(JSContext *cx, unsigned argc, jsval *vp) |
|
1437 { |
|
1438 CallArgs args = CallArgsFromVp(argc, vp); |
|
1439 if (args.length() != 1) { |
|
1440 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "run"); |
|
1441 return false; |
|
1442 } |
|
1443 |
|
1444 RootedObject thisobj(cx, JS_THIS_OBJECT(cx, vp)); |
|
1445 if (!thisobj) |
|
1446 return false; |
|
1447 |
|
1448 JSString *str = JS::ToString(cx, args[0]); |
|
1449 if (!str) |
|
1450 return false; |
|
1451 args[0].setString(str); |
|
1452 JSAutoByteString filename(cx, str); |
|
1453 if (!filename) |
|
1454 return false; |
|
1455 |
|
1456 const jschar *ucbuf = nullptr; |
|
1457 size_t buflen; |
|
1458 str = FileAsString(cx, filename.ptr()); |
|
1459 if (str) |
|
1460 ucbuf = JS_GetStringCharsAndLength(cx, str, &buflen); |
|
1461 if (!ucbuf) |
|
1462 return false; |
|
1463 |
|
1464 JS::Anchor<JSString *> a_str(str); |
|
1465 |
|
1466 RootedScript script(cx); |
|
1467 int64_t startClock = PRMJ_Now(); |
|
1468 { |
|
1469 JS::AutoSaveContextOptions asco(cx); |
|
1470 JS::ContextOptionsRef(cx).setNoScriptRval(true); |
|
1471 |
|
1472 JS::CompileOptions options(cx); |
|
1473 options.setIntroductionType("js shell run") |
|
1474 .setFileAndLine(filename.ptr(), 1) |
|
1475 .setCompileAndGo(true); |
|
1476 script = JS_CompileUCScript(cx, thisobj, ucbuf, buflen, options); |
|
1477 if (!script) |
|
1478 return false; |
|
1479 } |
|
1480 |
|
1481 if (!JS_ExecuteScript(cx, thisobj, script)) |
|
1482 return false; |
|
1483 |
|
1484 int64_t endClock = PRMJ_Now(); |
|
1485 |
|
1486 args.rval().setDouble((endClock - startClock) / double(PRMJ_USEC_PER_MSEC)); |
|
1487 return true; |
|
1488 } |
|
1489 |
|
1490 /* |
|
1491 * function readline() |
|
1492 * Provides a hook for scripts to read a line from stdin. |
|
1493 */ |
|
1494 static bool |
|
1495 ReadLine(JSContext *cx, unsigned argc, jsval *vp) |
|
1496 { |
|
1497 CallArgs args = CallArgsFromVp(argc, vp); |
|
1498 |
|
1499 #define BUFSIZE 256 |
|
1500 FILE *from = stdin; |
|
1501 size_t buflength = 0; |
|
1502 size_t bufsize = BUFSIZE; |
|
1503 char *buf = (char *) JS_malloc(cx, bufsize); |
|
1504 if (!buf) |
|
1505 return false; |
|
1506 |
|
1507 bool sawNewline = false; |
|
1508 size_t gotlength; |
|
1509 while ((gotlength = js_fgets(buf + buflength, bufsize - buflength, from)) > 0) { |
|
1510 buflength += gotlength; |
|
1511 |
|
1512 /* Are we done? */ |
|
1513 if (buf[buflength - 1] == '\n') { |
|
1514 buf[buflength - 1] = '\0'; |
|
1515 sawNewline = true; |
|
1516 break; |
|
1517 } else if (buflength < bufsize - 1) { |
|
1518 break; |
|
1519 } |
|
1520 |
|
1521 /* Else, grow our buffer for another pass. */ |
|
1522 char *tmp; |
|
1523 bufsize *= 2; |
|
1524 if (bufsize > buflength) { |
|
1525 tmp = (char *) JS_realloc(cx, buf, bufsize); |
|
1526 } else { |
|
1527 JS_ReportOutOfMemory(cx); |
|
1528 tmp = nullptr; |
|
1529 } |
|
1530 |
|
1531 if (!tmp) { |
|
1532 JS_free(cx, buf); |
|
1533 return false; |
|
1534 } |
|
1535 |
|
1536 buf = tmp; |
|
1537 } |
|
1538 |
|
1539 /* Treat the empty string specially. */ |
|
1540 if (buflength == 0) { |
|
1541 args.rval().set(feof(from) ? NullValue() : JS_GetEmptyStringValue(cx)); |
|
1542 JS_free(cx, buf); |
|
1543 return true; |
|
1544 } |
|
1545 |
|
1546 /* Shrink the buffer to the real size. */ |
|
1547 char *tmp = static_cast<char*>(JS_realloc(cx, buf, buflength)); |
|
1548 if (!tmp) { |
|
1549 JS_free(cx, buf); |
|
1550 return false; |
|
1551 } |
|
1552 |
|
1553 buf = tmp; |
|
1554 |
|
1555 /* |
|
1556 * Turn buf into a JSString. Note that buflength includes the trailing null |
|
1557 * character. |
|
1558 */ |
|
1559 JSString *str = JS_NewStringCopyN(cx, buf, sawNewline ? buflength - 1 : buflength); |
|
1560 JS_free(cx, buf); |
|
1561 if (!str) |
|
1562 return false; |
|
1563 |
|
1564 args.rval().setString(str); |
|
1565 return true; |
|
1566 } |
|
1567 |
|
1568 static bool |
|
1569 PutStr(JSContext *cx, unsigned argc, jsval *vp) |
|
1570 { |
|
1571 CallArgs args = CallArgsFromVp(argc, vp); |
|
1572 |
|
1573 if (args.length() != 0) { |
|
1574 JSString *str = JS::ToString(cx, args[0]); |
|
1575 if (!str) |
|
1576 return false; |
|
1577 char *bytes = JSStringToUTF8(cx, str); |
|
1578 if (!bytes) |
|
1579 return false; |
|
1580 fputs(bytes, gOutFile); |
|
1581 JS_free(cx, bytes); |
|
1582 fflush(gOutFile); |
|
1583 } |
|
1584 |
|
1585 args.rval().setUndefined(); |
|
1586 return true; |
|
1587 } |
|
1588 |
|
1589 static bool |
|
1590 Now(JSContext *cx, unsigned argc, jsval *vp) |
|
1591 { |
|
1592 CallArgs args = CallArgsFromVp(argc, vp); |
|
1593 double now = PRMJ_Now() / double(PRMJ_USEC_PER_MSEC); |
|
1594 args.rval().setDouble(now); |
|
1595 return true; |
|
1596 } |
|
1597 |
|
1598 static bool |
|
1599 PrintInternal(JSContext *cx, const CallArgs &args, FILE *file) |
|
1600 { |
|
1601 for (unsigned i = 0; i < args.length(); i++) { |
|
1602 JSString *str = JS::ToString(cx, args[i]); |
|
1603 if (!str) |
|
1604 return false; |
|
1605 char *bytes = JSStringToUTF8(cx, str); |
|
1606 if (!bytes) |
|
1607 return false; |
|
1608 fprintf(file, "%s%s", i ? " " : "", bytes); |
|
1609 JS_free(cx, bytes); |
|
1610 } |
|
1611 |
|
1612 fputc('\n', file); |
|
1613 fflush(file); |
|
1614 |
|
1615 args.rval().setUndefined(); |
|
1616 return true; |
|
1617 } |
|
1618 |
|
1619 static bool |
|
1620 Print(JSContext *cx, unsigned argc, jsval *vp) |
|
1621 { |
|
1622 CallArgs args = CallArgsFromVp(argc, vp); |
|
1623 return PrintInternal(cx, args, gOutFile); |
|
1624 } |
|
1625 |
|
1626 static bool |
|
1627 PrintErr(JSContext *cx, unsigned argc, jsval *vp) |
|
1628 { |
|
1629 CallArgs args = CallArgsFromVp(argc, vp); |
|
1630 return PrintInternal(cx, args, gErrFile); |
|
1631 } |
|
1632 |
|
1633 static bool |
|
1634 Help(JSContext *cx, unsigned argc, jsval *vp); |
|
1635 |
|
1636 static bool |
|
1637 Quit(JSContext *cx, unsigned argc, jsval *vp) |
|
1638 { |
|
1639 #ifdef JS_MORE_DETERMINISTIC |
|
1640 // Print a message to stderr in more-deterministic builds to help jsfunfuzz |
|
1641 // find uncatchable-exception bugs. |
|
1642 fprintf(stderr, "quit called\n"); |
|
1643 #endif |
|
1644 |
|
1645 CallArgs args = CallArgsFromVp(argc, vp); |
|
1646 JS_ConvertArguments(cx, args, "/ i", &gExitCode); |
|
1647 |
|
1648 gQuitting = true; |
|
1649 return false; |
|
1650 } |
|
1651 |
|
1652 static const char * |
|
1653 ToSource(JSContext *cx, MutableHandleValue vp, JSAutoByteString *bytes) |
|
1654 { |
|
1655 JSString *str = JS_ValueToSource(cx, vp); |
|
1656 if (str) { |
|
1657 vp.setString(str); |
|
1658 if (bytes->encodeLatin1(cx, str)) |
|
1659 return bytes->ptr(); |
|
1660 } |
|
1661 JS_ClearPendingException(cx); |
|
1662 return "<<error converting value to string>>"; |
|
1663 } |
|
1664 |
|
1665 static bool |
|
1666 AssertEq(JSContext *cx, unsigned argc, jsval *vp) |
|
1667 { |
|
1668 CallArgs args = CallArgsFromVp(argc, vp); |
|
1669 if (!(args.length() == 2 || (args.length() == 3 && args[2].isString()))) { |
|
1670 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, |
|
1671 (args.length() < 2) |
|
1672 ? JSSMSG_NOT_ENOUGH_ARGS |
|
1673 : (args.length() == 3) |
|
1674 ? JSSMSG_INVALID_ARGS |
|
1675 : JSSMSG_TOO_MANY_ARGS, |
|
1676 "assertEq"); |
|
1677 return false; |
|
1678 } |
|
1679 |
|
1680 bool same; |
|
1681 if (!JS_SameValue(cx, args[0], args[1], &same)) |
|
1682 return false; |
|
1683 if (!same) { |
|
1684 JSAutoByteString bytes0, bytes1; |
|
1685 const char *actual = ToSource(cx, args[0], &bytes0); |
|
1686 const char *expected = ToSource(cx, args[1], &bytes1); |
|
1687 if (args.length() == 2) { |
|
1688 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_ASSERT_EQ_FAILED, |
|
1689 actual, expected); |
|
1690 } else { |
|
1691 JSAutoByteString bytes2(cx, args[2].toString()); |
|
1692 if (!bytes2) |
|
1693 return false; |
|
1694 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_ASSERT_EQ_FAILED_MSG, |
|
1695 actual, expected, bytes2.ptr()); |
|
1696 } |
|
1697 return false; |
|
1698 } |
|
1699 args.rval().setUndefined(); |
|
1700 return true; |
|
1701 } |
|
1702 |
|
1703 static JSScript * |
|
1704 ValueToScript(JSContext *cx, jsval vArg, JSFunction **funp = nullptr) |
|
1705 { |
|
1706 RootedValue v(cx, vArg); |
|
1707 RootedFunction fun(cx, JS_ValueToFunction(cx, v)); |
|
1708 if (!fun) |
|
1709 return nullptr; |
|
1710 |
|
1711 // Unwrap bound functions. |
|
1712 while (fun->isBoundFunction()) { |
|
1713 JSObject *target = fun->getBoundFunctionTarget(); |
|
1714 if (target && target->is<JSFunction>()) |
|
1715 fun = &target->as<JSFunction>(); |
|
1716 else |
|
1717 break; |
|
1718 } |
|
1719 |
|
1720 if (!fun->isInterpreted()) { |
|
1721 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_SCRIPTS_ONLY); |
|
1722 return nullptr; |
|
1723 } |
|
1724 |
|
1725 JSScript *script = fun->getOrCreateScript(cx); |
|
1726 if (!script) |
|
1727 return nullptr; |
|
1728 |
|
1729 if (fun && funp) |
|
1730 *funp = fun; |
|
1731 |
|
1732 return script; |
|
1733 } |
|
1734 |
|
1735 static bool |
|
1736 SetDebug(JSContext *cx, unsigned argc, jsval *vp) |
|
1737 { |
|
1738 CallArgs args = CallArgsFromVp(argc, vp); |
|
1739 if (args.length() == 0 || !args[0].isBoolean()) { |
|
1740 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, |
|
1741 JSSMSG_NOT_ENOUGH_ARGS, "setDebug"); |
|
1742 return false; |
|
1743 } |
|
1744 |
|
1745 /* |
|
1746 * Debug mode can only be set when there is no JS code executing on the |
|
1747 * stack. Unfortunately, that currently means that this call will fail |
|
1748 * unless debug mode is already set to what you're trying to set it to. |
|
1749 * In the future, this restriction may be lifted. |
|
1750 */ |
|
1751 |
|
1752 bool ok = !!JS_SetDebugMode(cx, args[0].toBoolean()); |
|
1753 if (ok) |
|
1754 args.rval().setBoolean(true); |
|
1755 return ok; |
|
1756 } |
|
1757 |
|
1758 static JSScript * |
|
1759 GetTopScript(JSContext *cx) |
|
1760 { |
|
1761 NonBuiltinScriptFrameIter iter(cx); |
|
1762 return iter.done() ? nullptr : iter.script(); |
|
1763 } |
|
1764 |
|
1765 static bool |
|
1766 GetScriptAndPCArgs(JSContext *cx, unsigned argc, jsval *argv, MutableHandleScript scriptp, |
|
1767 int32_t *ip) |
|
1768 { |
|
1769 RootedScript script(cx, GetTopScript(cx)); |
|
1770 *ip = 0; |
|
1771 if (argc != 0) { |
|
1772 jsval v = argv[0]; |
|
1773 unsigned intarg = 0; |
|
1774 if (!JSVAL_IS_PRIMITIVE(v) && |
|
1775 JS_GetClass(&v.toObject()) == Jsvalify(&JSFunction::class_)) { |
|
1776 script = ValueToScript(cx, v); |
|
1777 if (!script) |
|
1778 return false; |
|
1779 intarg++; |
|
1780 } |
|
1781 if (argc > intarg) { |
|
1782 if (!JS::ToInt32(cx, HandleValue::fromMarkedLocation(&argv[intarg]), ip)) |
|
1783 return false; |
|
1784 if ((uint32_t)*ip >= script->length()) { |
|
1785 JS_ReportError(cx, "Invalid PC"); |
|
1786 return false; |
|
1787 } |
|
1788 } |
|
1789 } |
|
1790 |
|
1791 scriptp.set(script); |
|
1792 |
|
1793 return true; |
|
1794 } |
|
1795 |
|
1796 static JSTrapStatus |
|
1797 TrapHandler(JSContext *cx, JSScript *, jsbytecode *pc, jsval *rvalArg, |
|
1798 jsval closure) |
|
1799 { |
|
1800 RootedString str(cx, JSVAL_TO_STRING(closure)); |
|
1801 RootedValue rval(cx, *rvalArg); |
|
1802 |
|
1803 ScriptFrameIter iter(cx); |
|
1804 JS_ASSERT(!iter.done()); |
|
1805 |
|
1806 /* Debug-mode currently disables Ion compilation. */ |
|
1807 JSAbstractFramePtr frame(iter.abstractFramePtr().raw(), iter.pc()); |
|
1808 RootedScript script(cx, iter.script()); |
|
1809 |
|
1810 size_t length; |
|
1811 const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length); |
|
1812 if (!chars) |
|
1813 return JSTRAP_ERROR; |
|
1814 |
|
1815 if (!frame.evaluateUCInStackFrame(cx, chars, length, |
|
1816 script->filename(), |
|
1817 script->lineno(), |
|
1818 &rval)) |
|
1819 { |
|
1820 *rvalArg = rval; |
|
1821 return JSTRAP_ERROR; |
|
1822 } |
|
1823 *rvalArg = rval; |
|
1824 if (!rval.isUndefined()) |
|
1825 return JSTRAP_RETURN; |
|
1826 return JSTRAP_CONTINUE; |
|
1827 } |
|
1828 |
|
1829 static bool |
|
1830 Trap(JSContext *cx, unsigned argc, jsval *vp) |
|
1831 { |
|
1832 CallArgs args = CallArgsFromVp(argc, vp); |
|
1833 RootedScript script(cx); |
|
1834 int32_t i; |
|
1835 |
|
1836 if (args.length() == 0) { |
|
1837 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_TRAP_USAGE); |
|
1838 return false; |
|
1839 } |
|
1840 argc = args.length() - 1; |
|
1841 RootedString str(cx, JS::ToString(cx, args[argc])); |
|
1842 if (!str) |
|
1843 return false; |
|
1844 args[argc].setString(str); |
|
1845 if (!GetScriptAndPCArgs(cx, argc, args.array(), &script, &i)) |
|
1846 return false; |
|
1847 args.rval().setUndefined(); |
|
1848 RootedValue strValue(cx, StringValue(str)); |
|
1849 return JS_SetTrap(cx, script, script->offsetToPC(i), TrapHandler, strValue); |
|
1850 } |
|
1851 |
|
1852 static bool |
|
1853 Untrap(JSContext *cx, unsigned argc, jsval *vp) |
|
1854 { |
|
1855 CallArgs args = CallArgsFromVp(argc, vp); |
|
1856 RootedScript script(cx); |
|
1857 int32_t i; |
|
1858 |
|
1859 if (!GetScriptAndPCArgs(cx, args.length(), args.array(), &script, &i)) |
|
1860 return false; |
|
1861 JS_ClearTrap(cx, script, script->offsetToPC(i), nullptr, nullptr); |
|
1862 args.rval().setUndefined(); |
|
1863 return true; |
|
1864 } |
|
1865 |
|
1866 static JSTrapStatus |
|
1867 DebuggerAndThrowHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, |
|
1868 void *closure) |
|
1869 { |
|
1870 return TrapHandler(cx, script, pc, rval, STRING_TO_JSVAL((JSString *)closure)); |
|
1871 } |
|
1872 |
|
1873 static bool |
|
1874 SetDebuggerHandler(JSContext *cx, unsigned argc, jsval *vp) |
|
1875 { |
|
1876 CallArgs args = CallArgsFromVp(argc, vp); |
|
1877 if (args.length() == 0) { |
|
1878 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, |
|
1879 JSSMSG_NOT_ENOUGH_ARGS, "setDebuggerHandler"); |
|
1880 return false; |
|
1881 } |
|
1882 |
|
1883 JSString *str = JS::ToString(cx, args[0]); |
|
1884 if (!str) |
|
1885 return false; |
|
1886 |
|
1887 JS_SetDebuggerHandler(cx->runtime(), DebuggerAndThrowHandler, str); |
|
1888 args.rval().setUndefined(); |
|
1889 return true; |
|
1890 } |
|
1891 |
|
1892 static bool |
|
1893 SetThrowHook(JSContext *cx, unsigned argc, jsval *vp) |
|
1894 { |
|
1895 CallArgs args = CallArgsFromVp(argc, vp); |
|
1896 JSString *str; |
|
1897 if (args.length() == 0) { |
|
1898 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, |
|
1899 JSSMSG_NOT_ENOUGH_ARGS, "setThrowHook"); |
|
1900 return false; |
|
1901 } |
|
1902 |
|
1903 str = JS::ToString(cx, args[0]); |
|
1904 if (!str) |
|
1905 return false; |
|
1906 |
|
1907 JS_SetThrowHook(cx->runtime(), DebuggerAndThrowHandler, str); |
|
1908 args.rval().setUndefined(); |
|
1909 return true; |
|
1910 } |
|
1911 |
|
1912 static bool |
|
1913 LineToPC(JSContext *cx, unsigned argc, jsval *vp) |
|
1914 { |
|
1915 CallArgs args = CallArgsFromVp(argc, vp); |
|
1916 |
|
1917 if (args.length() == 0) { |
|
1918 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_LINE2PC_USAGE); |
|
1919 return false; |
|
1920 } |
|
1921 |
|
1922 RootedScript script(cx, GetTopScript(cx)); |
|
1923 int32_t lineArg = 0; |
|
1924 if (args[0].isObject() && args[0].toObject().is<JSFunction>()) { |
|
1925 script = ValueToScript(cx, args[0]); |
|
1926 if (!script) |
|
1927 return false; |
|
1928 lineArg++; |
|
1929 } |
|
1930 |
|
1931 uint32_t lineno; |
|
1932 if (!ToUint32(cx, args.get(lineArg), &lineno)) |
|
1933 return false; |
|
1934 |
|
1935 jsbytecode *pc = JS_LineNumberToPC(cx, script, lineno); |
|
1936 if (!pc) |
|
1937 return false; |
|
1938 args.rval().setInt32(script->pcToOffset(pc)); |
|
1939 return true; |
|
1940 } |
|
1941 |
|
1942 static bool |
|
1943 PCToLine(JSContext *cx, unsigned argc, jsval *vp) |
|
1944 { |
|
1945 CallArgs args = CallArgsFromVp(argc, vp); |
|
1946 RootedScript script(cx); |
|
1947 int32_t i; |
|
1948 unsigned lineno; |
|
1949 |
|
1950 if (!GetScriptAndPCArgs(cx, args.length(), args.array(), &script, &i)) |
|
1951 return false; |
|
1952 lineno = JS_PCToLineNumber(cx, script, script->offsetToPC(i)); |
|
1953 if (!lineno) |
|
1954 return false; |
|
1955 args.rval().setInt32(lineno); |
|
1956 return true; |
|
1957 } |
|
1958 |
|
1959 #ifdef DEBUG |
|
1960 |
|
1961 static void |
|
1962 UpdateSwitchTableBounds(JSContext *cx, HandleScript script, unsigned offset, |
|
1963 unsigned *start, unsigned *end) |
|
1964 { |
|
1965 jsbytecode *pc; |
|
1966 JSOp op; |
|
1967 ptrdiff_t jmplen; |
|
1968 int32_t low, high, n; |
|
1969 |
|
1970 pc = script->offsetToPC(offset); |
|
1971 op = JSOp(*pc); |
|
1972 switch (op) { |
|
1973 case JSOP_TABLESWITCH: |
|
1974 jmplen = JUMP_OFFSET_LEN; |
|
1975 pc += jmplen; |
|
1976 low = GET_JUMP_OFFSET(pc); |
|
1977 pc += JUMP_OFFSET_LEN; |
|
1978 high = GET_JUMP_OFFSET(pc); |
|
1979 pc += JUMP_OFFSET_LEN; |
|
1980 n = high - low + 1; |
|
1981 break; |
|
1982 |
|
1983 default: |
|
1984 /* [condswitch] switch does not have any jump or lookup tables. */ |
|
1985 JS_ASSERT(op == JSOP_CONDSWITCH); |
|
1986 return; |
|
1987 } |
|
1988 |
|
1989 *start = script->pcToOffset(pc); |
|
1990 *end = *start + (unsigned)(n * jmplen); |
|
1991 } |
|
1992 |
|
1993 static void |
|
1994 SrcNotes(JSContext *cx, HandleScript script, Sprinter *sp) |
|
1995 { |
|
1996 Sprint(sp, "\nSource notes:\n"); |
|
1997 Sprint(sp, "%4s %4s %5s %6s %-8s %s\n", |
|
1998 "ofs", "line", "pc", "delta", "desc", "args"); |
|
1999 Sprint(sp, "---- ---- ----- ------ -------- ------\n"); |
|
2000 unsigned offset = 0; |
|
2001 unsigned colspan = 0; |
|
2002 unsigned lineno = script->lineno(); |
|
2003 jssrcnote *notes = script->notes(); |
|
2004 unsigned switchTableEnd = 0, switchTableStart = 0; |
|
2005 for (jssrcnote *sn = notes; !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) { |
|
2006 unsigned delta = SN_DELTA(sn); |
|
2007 offset += delta; |
|
2008 SrcNoteType type = (SrcNoteType) SN_TYPE(sn); |
|
2009 const char *name = js_SrcNoteSpec[type].name; |
|
2010 Sprint(sp, "%3u: %4u %5u [%4u] %-8s", unsigned(sn - notes), lineno, offset, delta, name); |
|
2011 switch (type) { |
|
2012 case SRC_NULL: |
|
2013 case SRC_IF: |
|
2014 case SRC_CONTINUE: |
|
2015 case SRC_BREAK: |
|
2016 case SRC_BREAK2LABEL: |
|
2017 case SRC_SWITCHBREAK: |
|
2018 case SRC_ASSIGNOP: |
|
2019 case SRC_XDELTA: |
|
2020 break; |
|
2021 |
|
2022 case SRC_COLSPAN: |
|
2023 colspan = js_GetSrcNoteOffset(sn, 0); |
|
2024 if (colspan >= SN_COLSPAN_DOMAIN / 2) |
|
2025 colspan -= SN_COLSPAN_DOMAIN; |
|
2026 Sprint(sp, "%d", colspan); |
|
2027 break; |
|
2028 |
|
2029 case SRC_SETLINE: |
|
2030 lineno = js_GetSrcNoteOffset(sn, 0); |
|
2031 Sprint(sp, " lineno %u", lineno); |
|
2032 break; |
|
2033 |
|
2034 case SRC_NEWLINE: |
|
2035 ++lineno; |
|
2036 break; |
|
2037 |
|
2038 case SRC_FOR: |
|
2039 Sprint(sp, " cond %u update %u tail %u", |
|
2040 unsigned(js_GetSrcNoteOffset(sn, 0)), |
|
2041 unsigned(js_GetSrcNoteOffset(sn, 1)), |
|
2042 unsigned(js_GetSrcNoteOffset(sn, 2))); |
|
2043 break; |
|
2044 |
|
2045 case SRC_IF_ELSE: |
|
2046 Sprint(sp, " else %u", unsigned(js_GetSrcNoteOffset(sn, 0))); |
|
2047 break; |
|
2048 |
|
2049 case SRC_FOR_IN: |
|
2050 case SRC_FOR_OF: |
|
2051 Sprint(sp, " closingjump %u", unsigned(js_GetSrcNoteOffset(sn, 0))); |
|
2052 break; |
|
2053 |
|
2054 case SRC_COND: |
|
2055 case SRC_WHILE: |
|
2056 case SRC_NEXTCASE: |
|
2057 Sprint(sp, " offset %u", unsigned(js_GetSrcNoteOffset(sn, 0))); |
|
2058 break; |
|
2059 |
|
2060 case SRC_TABLESWITCH: { |
|
2061 JSOp op = JSOp(script->code()[offset]); |
|
2062 JS_ASSERT(op == JSOP_TABLESWITCH); |
|
2063 Sprint(sp, " length %u", unsigned(js_GetSrcNoteOffset(sn, 0))); |
|
2064 UpdateSwitchTableBounds(cx, script, offset, |
|
2065 &switchTableStart, &switchTableEnd); |
|
2066 break; |
|
2067 } |
|
2068 case SRC_CONDSWITCH: { |
|
2069 JSOp op = JSOp(script->code()[offset]); |
|
2070 JS_ASSERT(op == JSOP_CONDSWITCH); |
|
2071 Sprint(sp, " length %u", unsigned(js_GetSrcNoteOffset(sn, 0))); |
|
2072 unsigned caseOff = (unsigned) js_GetSrcNoteOffset(sn, 1); |
|
2073 if (caseOff) |
|
2074 Sprint(sp, " first case offset %u", caseOff); |
|
2075 UpdateSwitchTableBounds(cx, script, offset, |
|
2076 &switchTableStart, &switchTableEnd); |
|
2077 break; |
|
2078 } |
|
2079 |
|
2080 case SRC_TRY: |
|
2081 JS_ASSERT(JSOp(script->code()[offset]) == JSOP_TRY); |
|
2082 Sprint(sp, " offset to jump %u", unsigned(js_GetSrcNoteOffset(sn, 0))); |
|
2083 break; |
|
2084 |
|
2085 default: |
|
2086 JS_ASSERT(0); |
|
2087 break; |
|
2088 } |
|
2089 Sprint(sp, "\n"); |
|
2090 } |
|
2091 } |
|
2092 |
|
2093 static bool |
|
2094 Notes(JSContext *cx, unsigned argc, jsval *vp) |
|
2095 { |
|
2096 CallArgs args = CallArgsFromVp(argc, vp); |
|
2097 Sprinter sprinter(cx); |
|
2098 if (!sprinter.init()) |
|
2099 return false; |
|
2100 |
|
2101 for (unsigned i = 0; i < args.length(); i++) { |
|
2102 RootedScript script (cx, ValueToScript(cx, args[i])); |
|
2103 if (!script) |
|
2104 return false; |
|
2105 |
|
2106 SrcNotes(cx, script, &sprinter); |
|
2107 } |
|
2108 |
|
2109 JSString *str = JS_NewStringCopyZ(cx, sprinter.string()); |
|
2110 if (!str) |
|
2111 return false; |
|
2112 args.rval().setString(str); |
|
2113 return true; |
|
2114 } |
|
2115 |
|
2116 JS_STATIC_ASSERT(JSTRY_CATCH == 0); |
|
2117 JS_STATIC_ASSERT(JSTRY_FINALLY == 1); |
|
2118 JS_STATIC_ASSERT(JSTRY_ITER == 2); |
|
2119 |
|
2120 static const char* const TryNoteNames[] = { "catch", "finally", "iter", "loop" }; |
|
2121 |
|
2122 static bool |
|
2123 TryNotes(JSContext *cx, HandleScript script, Sprinter *sp) |
|
2124 { |
|
2125 JSTryNote *tn, *tnlimit; |
|
2126 |
|
2127 if (!script->hasTrynotes()) |
|
2128 return true; |
|
2129 |
|
2130 tn = script->trynotes()->vector; |
|
2131 tnlimit = tn + script->trynotes()->length; |
|
2132 Sprint(sp, "\nException table:\nkind stack start end\n"); |
|
2133 do { |
|
2134 JS_ASSERT(tn->kind < ArrayLength(TryNoteNames)); |
|
2135 Sprint(sp, " %-7s %6u %8u %8u\n", |
|
2136 TryNoteNames[tn->kind], tn->stackDepth, |
|
2137 tn->start, tn->start + tn->length); |
|
2138 } while (++tn != tnlimit); |
|
2139 return true; |
|
2140 } |
|
2141 |
|
2142 static bool |
|
2143 DisassembleScript(JSContext *cx, HandleScript script, HandleFunction fun, bool lines, |
|
2144 bool recursive, Sprinter *sp) |
|
2145 { |
|
2146 if (fun) { |
|
2147 Sprint(sp, "flags:"); |
|
2148 if (fun->isLambda()) |
|
2149 Sprint(sp, " LAMBDA"); |
|
2150 if (fun->isHeavyweight()) |
|
2151 Sprint(sp, " HEAVYWEIGHT"); |
|
2152 if (fun->isExprClosure()) |
|
2153 Sprint(sp, " EXPRESSION_CLOSURE"); |
|
2154 if (fun->isFunctionPrototype()) |
|
2155 Sprint(sp, " Function.prototype"); |
|
2156 if (fun->isSelfHostedBuiltin()) |
|
2157 Sprint(sp, " SELF_HOSTED"); |
|
2158 if (fun->isSelfHostedConstructor()) |
|
2159 Sprint(sp, " SELF_HOSTED_CTOR"); |
|
2160 if (fun->isArrow()) |
|
2161 Sprint(sp, " ARROW"); |
|
2162 Sprint(sp, "\n"); |
|
2163 } |
|
2164 |
|
2165 if (!js_Disassemble(cx, script, lines, sp)) |
|
2166 return false; |
|
2167 SrcNotes(cx, script, sp); |
|
2168 TryNotes(cx, script, sp); |
|
2169 |
|
2170 if (recursive && script->hasObjects()) { |
|
2171 ObjectArray *objects = script->objects(); |
|
2172 for (unsigned i = 0; i != objects->length; ++i) { |
|
2173 JSObject *obj = objects->vector[i]; |
|
2174 if (obj->is<JSFunction>()) { |
|
2175 Sprint(sp, "\n"); |
|
2176 RootedFunction fun(cx, &obj->as<JSFunction>()); |
|
2177 if (fun->isInterpreted()) { |
|
2178 RootedScript script(cx, fun->getOrCreateScript(cx)); |
|
2179 if (!script || !DisassembleScript(cx, script, fun, lines, recursive, sp)) |
|
2180 return false; |
|
2181 } else { |
|
2182 Sprint(sp, "[native code]\n"); |
|
2183 } |
|
2184 } |
|
2185 } |
|
2186 } |
|
2187 return true; |
|
2188 } |
|
2189 |
|
2190 namespace { |
|
2191 |
|
2192 struct DisassembleOptionParser { |
|
2193 unsigned argc; |
|
2194 jsval *argv; |
|
2195 bool lines; |
|
2196 bool recursive; |
|
2197 |
|
2198 DisassembleOptionParser(unsigned argc, jsval *argv) |
|
2199 : argc(argc), argv(argv), lines(false), recursive(false) {} |
|
2200 |
|
2201 bool parse(JSContext *cx) { |
|
2202 /* Read options off early arguments */ |
|
2203 while (argc > 0 && argv[0].isString()) { |
|
2204 JSString *str = argv[0].toString(); |
|
2205 JSFlatString *flatStr = JS_FlattenString(cx, str); |
|
2206 if (!flatStr) |
|
2207 return false; |
|
2208 if (JS_FlatStringEqualsAscii(flatStr, "-l")) |
|
2209 lines = true; |
|
2210 else if (JS_FlatStringEqualsAscii(flatStr, "-r")) |
|
2211 recursive = true; |
|
2212 else |
|
2213 break; |
|
2214 argv++, argc--; |
|
2215 } |
|
2216 return true; |
|
2217 } |
|
2218 }; |
|
2219 |
|
2220 } /* anonymous namespace */ |
|
2221 |
|
2222 static bool |
|
2223 DisassembleToSprinter(JSContext *cx, unsigned argc, jsval *vp, Sprinter *sprinter) |
|
2224 { |
|
2225 CallArgs args = CallArgsFromVp(argc, vp); |
|
2226 DisassembleOptionParser p(args.length(), args.array()); |
|
2227 if (!p.parse(cx)) |
|
2228 return false; |
|
2229 |
|
2230 if (p.argc == 0) { |
|
2231 /* Without arguments, disassemble the current script. */ |
|
2232 RootedScript script(cx, GetTopScript(cx)); |
|
2233 if (script) { |
|
2234 JSAutoCompartment ac(cx, script); |
|
2235 if (!js_Disassemble(cx, script, p.lines, sprinter)) |
|
2236 return false; |
|
2237 SrcNotes(cx, script, sprinter); |
|
2238 TryNotes(cx, script, sprinter); |
|
2239 } |
|
2240 } else { |
|
2241 for (unsigned i = 0; i < p.argc; i++) { |
|
2242 RootedFunction fun(cx); |
|
2243 RootedScript script (cx, ValueToScript(cx, p.argv[i], fun.address())); |
|
2244 if (!script) |
|
2245 return false; |
|
2246 if (!DisassembleScript(cx, script, fun, p.lines, p.recursive, sprinter)) |
|
2247 return false; |
|
2248 } |
|
2249 } |
|
2250 return true; |
|
2251 } |
|
2252 |
|
2253 static bool |
|
2254 DisassembleToString(JSContext *cx, unsigned argc, jsval *vp) |
|
2255 { |
|
2256 CallArgs args = CallArgsFromVp(argc, vp); |
|
2257 Sprinter sprinter(cx); |
|
2258 if (!sprinter.init()) |
|
2259 return false; |
|
2260 if (!DisassembleToSprinter(cx, args.length(), vp, &sprinter)) |
|
2261 return false; |
|
2262 |
|
2263 JSString *str = JS_NewStringCopyZ(cx, sprinter.string()); |
|
2264 if (!str) |
|
2265 return false; |
|
2266 args.rval().setString(str); |
|
2267 return true; |
|
2268 } |
|
2269 |
|
2270 static bool |
|
2271 Disassemble(JSContext *cx, unsigned argc, jsval *vp) |
|
2272 { |
|
2273 CallArgs args = CallArgsFromVp(argc, vp); |
|
2274 Sprinter sprinter(cx); |
|
2275 if (!sprinter.init()) |
|
2276 return false; |
|
2277 if (!DisassembleToSprinter(cx, args.length(), vp, &sprinter)) |
|
2278 return false; |
|
2279 |
|
2280 fprintf(stdout, "%s\n", sprinter.string()); |
|
2281 args.rval().setUndefined(); |
|
2282 return true; |
|
2283 } |
|
2284 |
|
2285 static bool |
|
2286 DisassFile(JSContext *cx, unsigned argc, jsval *vp) |
|
2287 { |
|
2288 CallArgs args = CallArgsFromVp(argc, vp); |
|
2289 |
|
2290 /* Support extra options at the start, just like Disassemble. */ |
|
2291 DisassembleOptionParser p(args.length(), args.array()); |
|
2292 if (!p.parse(cx)) |
|
2293 return false; |
|
2294 |
|
2295 if (!p.argc) { |
|
2296 args.rval().setUndefined(); |
|
2297 return true; |
|
2298 } |
|
2299 |
|
2300 RootedObject thisobj(cx, JS_THIS_OBJECT(cx, vp)); |
|
2301 if (!thisobj) |
|
2302 return false; |
|
2303 |
|
2304 // We should change DisassembleOptionParser to store CallArgs. |
|
2305 JSString *str = JS::ToString(cx, HandleValue::fromMarkedLocation(&p.argv[0])); |
|
2306 if (!str) |
|
2307 return false; |
|
2308 JSAutoByteString filename(cx, str); |
|
2309 if (!filename) |
|
2310 return false; |
|
2311 RootedScript script(cx); |
|
2312 |
|
2313 { |
|
2314 JS::AutoSaveContextOptions asco(cx); |
|
2315 JS::ContextOptionsRef(cx).setNoScriptRval(true); |
|
2316 |
|
2317 CompileOptions options(cx); |
|
2318 options.setIntroductionType("js shell disFile") |
|
2319 .setUTF8(true) |
|
2320 .setFileAndLine(filename.ptr(), 1) |
|
2321 .setCompileAndGo(true); |
|
2322 |
|
2323 script = JS::Compile(cx, thisobj, options, filename.ptr()); |
|
2324 if (!script) |
|
2325 return false; |
|
2326 } |
|
2327 |
|
2328 Sprinter sprinter(cx); |
|
2329 if (!sprinter.init()) |
|
2330 return false; |
|
2331 bool ok = DisassembleScript(cx, script, NullPtr(), p.lines, p.recursive, &sprinter); |
|
2332 if (ok) |
|
2333 fprintf(stdout, "%s\n", sprinter.string()); |
|
2334 if (!ok) |
|
2335 return false; |
|
2336 |
|
2337 args.rval().setUndefined(); |
|
2338 return true; |
|
2339 } |
|
2340 |
|
2341 static bool |
|
2342 DisassWithSrc(JSContext *cx, unsigned argc, jsval *vp) |
|
2343 { |
|
2344 CallArgs args = CallArgsFromVp(argc, vp); |
|
2345 |
|
2346 #define LINE_BUF_LEN 512 |
|
2347 unsigned len, line1, line2, bupline; |
|
2348 FILE *file; |
|
2349 char linebuf[LINE_BUF_LEN]; |
|
2350 jsbytecode *pc, *end; |
|
2351 static const char sep[] = ";-------------------------"; |
|
2352 |
|
2353 bool ok = true; |
|
2354 RootedScript script(cx); |
|
2355 for (unsigned i = 0; ok && i < args.length(); i++) { |
|
2356 script = ValueToScript(cx, args[i]); |
|
2357 if (!script) |
|
2358 return false; |
|
2359 |
|
2360 if (!script->filename()) { |
|
2361 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, |
|
2362 JSSMSG_FILE_SCRIPTS_ONLY); |
|
2363 return false; |
|
2364 } |
|
2365 |
|
2366 file = fopen(script->filename(), "r"); |
|
2367 if (!file) { |
|
2368 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, |
|
2369 JSSMSG_CANT_OPEN, script->filename(), |
|
2370 strerror(errno)); |
|
2371 return false; |
|
2372 } |
|
2373 |
|
2374 pc = script->code(); |
|
2375 end = script->codeEnd(); |
|
2376 |
|
2377 Sprinter sprinter(cx); |
|
2378 if (!sprinter.init()) { |
|
2379 ok = false; |
|
2380 goto bail; |
|
2381 } |
|
2382 |
|
2383 /* burn the leading lines */ |
|
2384 line2 = JS_PCToLineNumber(cx, script, pc); |
|
2385 for (line1 = 0; line1 < line2 - 1; line1++) { |
|
2386 char *tmp = fgets(linebuf, LINE_BUF_LEN, file); |
|
2387 if (!tmp) { |
|
2388 JS_ReportError(cx, "failed to read %s fully", script->filename()); |
|
2389 ok = false; |
|
2390 goto bail; |
|
2391 } |
|
2392 } |
|
2393 |
|
2394 bupline = 0; |
|
2395 while (pc < end) { |
|
2396 line2 = JS_PCToLineNumber(cx, script, pc); |
|
2397 |
|
2398 if (line2 < line1) { |
|
2399 if (bupline != line2) { |
|
2400 bupline = line2; |
|
2401 Sprint(&sprinter, "%s %3u: BACKUP\n", sep, line2); |
|
2402 } |
|
2403 } else { |
|
2404 if (bupline && line1 == line2) |
|
2405 Sprint(&sprinter, "%s %3u: RESTORE\n", sep, line2); |
|
2406 bupline = 0; |
|
2407 while (line1 < line2) { |
|
2408 if (!fgets(linebuf, LINE_BUF_LEN, file)) { |
|
2409 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, |
|
2410 JSSMSG_UNEXPECTED_EOF, |
|
2411 script->filename()); |
|
2412 ok = false; |
|
2413 goto bail; |
|
2414 } |
|
2415 line1++; |
|
2416 Sprint(&sprinter, "%s %3u: %s", sep, line1, linebuf); |
|
2417 } |
|
2418 } |
|
2419 |
|
2420 len = js_Disassemble1(cx, script, pc, script->pcToOffset(pc), true, &sprinter); |
|
2421 if (!len) { |
|
2422 ok = false; |
|
2423 goto bail; |
|
2424 } |
|
2425 pc += len; |
|
2426 } |
|
2427 |
|
2428 bail: |
|
2429 fclose(file); |
|
2430 } |
|
2431 args.rval().setUndefined(); |
|
2432 return ok; |
|
2433 #undef LINE_BUF_LEN |
|
2434 } |
|
2435 |
|
2436 static bool |
|
2437 DumpHeap(JSContext *cx, unsigned argc, jsval *vp) |
|
2438 { |
|
2439 CallArgs args = CallArgsFromVp(argc, vp); |
|
2440 |
|
2441 JSAutoByteString fileName; |
|
2442 if (args.hasDefined(0)) { |
|
2443 RootedString str(cx, JS::ToString(cx, args[0])); |
|
2444 if (!str) |
|
2445 return false; |
|
2446 |
|
2447 if (!fileName.encodeLatin1(cx, str)) |
|
2448 return false; |
|
2449 } |
|
2450 |
|
2451 RootedValue startThing(cx); |
|
2452 if (args.hasDefined(1)) { |
|
2453 if (!args[1].isGCThing()) { |
|
2454 JS_ReportError(cx, "dumpHeap: Second argument not a GC thing!"); |
|
2455 return false; |
|
2456 } |
|
2457 startThing = args[1]; |
|
2458 } |
|
2459 |
|
2460 RootedValue thingToFind(cx); |
|
2461 if (args.hasDefined(2)) { |
|
2462 if (!args[2].isGCThing()) { |
|
2463 JS_ReportError(cx, "dumpHeap: Third argument not a GC thing!"); |
|
2464 return false; |
|
2465 } |
|
2466 thingToFind = args[2]; |
|
2467 } |
|
2468 |
|
2469 size_t maxDepth = size_t(-1); |
|
2470 if (args.hasDefined(3)) { |
|
2471 uint32_t depth; |
|
2472 if (!ToUint32(cx, args[3], &depth)) |
|
2473 return false; |
|
2474 maxDepth = depth; |
|
2475 } |
|
2476 |
|
2477 RootedValue thingToIgnore(cx); |
|
2478 if (args.hasDefined(4)) { |
|
2479 if (!args[2].isGCThing()) { |
|
2480 JS_ReportError(cx, "dumpHeap: Fifth argument not a GC thing!"); |
|
2481 return false; |
|
2482 } |
|
2483 thingToIgnore = args[4]; |
|
2484 } |
|
2485 |
|
2486 |
|
2487 FILE *dumpFile = stdout; |
|
2488 if (fileName.length()) { |
|
2489 dumpFile = fopen(fileName.ptr(), "w"); |
|
2490 if (!dumpFile) { |
|
2491 JS_ReportError(cx, "dumpHeap: can't open %s: %s\n", |
|
2492 fileName.ptr(), strerror(errno)); |
|
2493 return false; |
|
2494 } |
|
2495 } |
|
2496 |
|
2497 bool ok = JS_DumpHeap(JS_GetRuntime(cx), dumpFile, |
|
2498 startThing.isUndefined() ? nullptr : startThing.toGCThing(), |
|
2499 startThing.isUndefined() ? JSTRACE_OBJECT : startThing.get().gcKind(), |
|
2500 thingToFind.isUndefined() ? nullptr : thingToFind.toGCThing(), |
|
2501 maxDepth, |
|
2502 thingToIgnore.isUndefined() ? nullptr : thingToIgnore.toGCThing()); |
|
2503 |
|
2504 if (dumpFile != stdout) |
|
2505 fclose(dumpFile); |
|
2506 |
|
2507 if (!ok) |
|
2508 JS_ReportOutOfMemory(cx); |
|
2509 |
|
2510 return ok; |
|
2511 } |
|
2512 |
|
2513 static bool |
|
2514 DumpObject(JSContext *cx, unsigned argc, jsval *vp) |
|
2515 { |
|
2516 CallArgs args = CallArgsFromVp(argc, vp); |
|
2517 RootedObject arg0(cx); |
|
2518 if (!JS_ConvertArguments(cx, args, "o", arg0.address())) |
|
2519 return false; |
|
2520 |
|
2521 js_DumpObject(arg0); |
|
2522 |
|
2523 args.rval().setUndefined(); |
|
2524 return true; |
|
2525 } |
|
2526 |
|
2527 #endif /* DEBUG */ |
|
2528 |
|
2529 static bool |
|
2530 BuildDate(JSContext *cx, unsigned argc, jsval *vp) |
|
2531 { |
|
2532 CallArgs args = CallArgsFromVp(argc, vp); |
|
2533 fprintf(gOutFile, "built on %s at %s\n", __DATE__, __TIME__); |
|
2534 args.rval().setUndefined(); |
|
2535 return true; |
|
2536 } |
|
2537 |
|
2538 static bool |
|
2539 Intern(JSContext *cx, unsigned argc, jsval *vp) |
|
2540 { |
|
2541 CallArgs args = CallArgsFromVp(argc, vp); |
|
2542 JSString *str = JS::ToString(cx, args.get(0)); |
|
2543 if (!str) |
|
2544 return false; |
|
2545 |
|
2546 size_t length; |
|
2547 const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length); |
|
2548 if (!chars) |
|
2549 return false; |
|
2550 |
|
2551 if (!JS_InternUCStringN(cx, chars, length)) |
|
2552 return false; |
|
2553 |
|
2554 args.rval().setUndefined(); |
|
2555 return true; |
|
2556 } |
|
2557 |
|
2558 static bool |
|
2559 Clone(JSContext *cx, unsigned argc, jsval *vp) |
|
2560 { |
|
2561 CallArgs args = CallArgsFromVp(argc, vp); |
|
2562 RootedObject parent(cx); |
|
2563 RootedObject funobj(cx); |
|
2564 |
|
2565 if (!args.length()) { |
|
2566 JS_ReportError(cx, "Invalid arguments to clone"); |
|
2567 return false; |
|
2568 } |
|
2569 |
|
2570 { |
|
2571 Maybe<JSAutoCompartment> ac; |
|
2572 RootedObject obj(cx, JSVAL_IS_PRIMITIVE(args[0]) ? nullptr : &args[0].toObject()); |
|
2573 |
|
2574 if (obj && obj->is<CrossCompartmentWrapperObject>()) { |
|
2575 obj = UncheckedUnwrap(obj); |
|
2576 ac.construct(cx, obj); |
|
2577 args[0].setObject(*obj); |
|
2578 } |
|
2579 if (obj && obj->is<JSFunction>()) { |
|
2580 funobj = obj; |
|
2581 } else { |
|
2582 JSFunction *fun = JS_ValueToFunction(cx, args[0]); |
|
2583 if (!fun) |
|
2584 return false; |
|
2585 funobj = JS_GetFunctionObject(fun); |
|
2586 } |
|
2587 } |
|
2588 if (funobj->compartment() != cx->compartment()) { |
|
2589 JSFunction *fun = &funobj->as<JSFunction>(); |
|
2590 if (fun->hasScript() && fun->nonLazyScript()->compileAndGo()) { |
|
2591 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE, |
|
2592 "function", "compile-and-go"); |
|
2593 return false; |
|
2594 } |
|
2595 } |
|
2596 |
|
2597 if (args.length() > 1) { |
|
2598 if (!JS_ValueToObject(cx, args[1], &parent)) |
|
2599 return false; |
|
2600 } else { |
|
2601 parent = JS_GetParent(&args.callee()); |
|
2602 } |
|
2603 |
|
2604 JSObject *clone = JS_CloneFunctionObject(cx, funobj, parent); |
|
2605 if (!clone) |
|
2606 return false; |
|
2607 args.rval().setObject(*clone); |
|
2608 return true; |
|
2609 } |
|
2610 |
|
2611 static bool |
|
2612 GetPDA(JSContext *cx, unsigned argc, jsval *vp) |
|
2613 { |
|
2614 CallArgs args = CallArgsFromVp(argc, vp); |
|
2615 RootedObject vobj(cx); |
|
2616 bool ok; |
|
2617 JSPropertyDescArray pda; |
|
2618 JSPropertyDesc *pd; |
|
2619 |
|
2620 if (!JS_ValueToObject(cx, args.get(0), &vobj)) |
|
2621 return false; |
|
2622 if (!vobj) { |
|
2623 args.rval().setUndefined(); |
|
2624 return true; |
|
2625 } |
|
2626 |
|
2627 RootedObject aobj(cx, JS_NewArrayObject(cx, 0)); |
|
2628 if (!aobj) |
|
2629 return false; |
|
2630 args.rval().setObject(*aobj); |
|
2631 |
|
2632 ok = !!JS_GetPropertyDescArray(cx, vobj, &pda); |
|
2633 if (!ok) |
|
2634 return false; |
|
2635 pd = pda.array; |
|
2636 |
|
2637 RootedObject pdobj(cx); |
|
2638 RootedValue id(cx); |
|
2639 RootedValue value(cx); |
|
2640 RootedValue flags(cx); |
|
2641 RootedValue alias(cx); |
|
2642 |
|
2643 for (uint32_t i = 0; i < pda.length; i++, pd++) { |
|
2644 pdobj = JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()); |
|
2645 if (!pdobj) { |
|
2646 ok = false; |
|
2647 break; |
|
2648 } |
|
2649 |
|
2650 /* Protect pdobj from GC by setting it as an element of aobj now */ |
|
2651 ok = !!JS_SetElement(cx, aobj, i, pdobj); |
|
2652 if (!ok) |
|
2653 break; |
|
2654 |
|
2655 id = pd->id; |
|
2656 value = pd->value; |
|
2657 flags.setInt32(pd->flags); |
|
2658 alias = pd->alias; |
|
2659 ok = JS_SetProperty(cx, pdobj, "id", id) && |
|
2660 JS_SetProperty(cx, pdobj, "value", value) && |
|
2661 JS_SetProperty(cx, pdobj, "flags", flags) && |
|
2662 JS_SetProperty(cx, pdobj, "alias", alias); |
|
2663 if (!ok) |
|
2664 break; |
|
2665 } |
|
2666 JS_PutPropertyDescArray(cx, &pda); |
|
2667 return ok; |
|
2668 } |
|
2669 |
|
2670 static bool |
|
2671 GetSLX(JSContext *cx, unsigned argc, jsval *vp) |
|
2672 { |
|
2673 CallArgs args = CallArgsFromVp(argc, vp); |
|
2674 RootedScript script(cx); |
|
2675 |
|
2676 script = ValueToScript(cx, args.get(0)); |
|
2677 if (!script) |
|
2678 return false; |
|
2679 args.rval().setInt32(js_GetScriptLineExtent(script)); |
|
2680 return true; |
|
2681 } |
|
2682 |
|
2683 static bool |
|
2684 ThrowError(JSContext *cx, unsigned argc, jsval *vp) |
|
2685 { |
|
2686 JS_ReportError(cx, "This is an error"); |
|
2687 return false; |
|
2688 } |
|
2689 |
|
2690 #define LAZY_STANDARD_CLASSES |
|
2691 |
|
2692 /* A class for easily testing the inner/outer object callbacks. */ |
|
2693 typedef struct ComplexObject { |
|
2694 bool isInner; |
|
2695 bool frozen; |
|
2696 JSObject *inner; |
|
2697 JSObject *outer; |
|
2698 } ComplexObject; |
|
2699 |
|
2700 static bool |
|
2701 sandbox_enumerate(JSContext *cx, HandleObject obj) |
|
2702 { |
|
2703 RootedValue v(cx); |
|
2704 |
|
2705 if (!JS_GetProperty(cx, obj, "lazy", &v)) |
|
2706 return false; |
|
2707 |
|
2708 if (!ToBoolean(v)) |
|
2709 return true; |
|
2710 |
|
2711 return JS_EnumerateStandardClasses(cx, obj); |
|
2712 } |
|
2713 |
|
2714 static bool |
|
2715 sandbox_resolve(JSContext *cx, HandleObject obj, HandleId id, MutableHandleObject objp) |
|
2716 { |
|
2717 RootedValue v(cx); |
|
2718 if (!JS_GetProperty(cx, obj, "lazy", &v)) |
|
2719 return false; |
|
2720 |
|
2721 if (ToBoolean(v)) { |
|
2722 bool resolved; |
|
2723 if (!JS_ResolveStandardClass(cx, obj, id, &resolved)) |
|
2724 return false; |
|
2725 if (resolved) { |
|
2726 objp.set(obj); |
|
2727 return true; |
|
2728 } |
|
2729 } |
|
2730 objp.set(nullptr); |
|
2731 return true; |
|
2732 } |
|
2733 |
|
2734 static const JSClass sandbox_class = { |
|
2735 "sandbox", |
|
2736 JSCLASS_NEW_RESOLVE | JSCLASS_GLOBAL_FLAGS, |
|
2737 JS_PropertyStub, JS_DeletePropertyStub, |
|
2738 JS_PropertyStub, JS_StrictPropertyStub, |
|
2739 sandbox_enumerate, (JSResolveOp)sandbox_resolve, |
|
2740 JS_ConvertStub, nullptr, |
|
2741 nullptr, nullptr, nullptr, |
|
2742 JS_GlobalObjectTraceHook |
|
2743 }; |
|
2744 |
|
2745 static JSObject * |
|
2746 NewSandbox(JSContext *cx, bool lazy) |
|
2747 { |
|
2748 RootedObject obj(cx, JS_NewGlobalObject(cx, &sandbox_class, nullptr, |
|
2749 JS::DontFireOnNewGlobalHook)); |
|
2750 if (!obj) |
|
2751 return nullptr; |
|
2752 |
|
2753 { |
|
2754 JSAutoCompartment ac(cx, obj); |
|
2755 if (!lazy && !JS_InitStandardClasses(cx, obj)) |
|
2756 return nullptr; |
|
2757 |
|
2758 RootedValue value(cx, BooleanValue(lazy)); |
|
2759 if (!JS_SetProperty(cx, obj, "lazy", value)) |
|
2760 return nullptr; |
|
2761 } |
|
2762 |
|
2763 JS_FireOnNewGlobalObject(cx, obj); |
|
2764 |
|
2765 if (!cx->compartment()->wrap(cx, &obj)) |
|
2766 return nullptr; |
|
2767 return obj; |
|
2768 } |
|
2769 |
|
2770 static bool |
|
2771 EvalInContext(JSContext *cx, unsigned argc, jsval *vp) |
|
2772 { |
|
2773 CallArgs args = CallArgsFromVp(argc, vp); |
|
2774 |
|
2775 RootedString str(cx); |
|
2776 RootedObject sobj(cx); |
|
2777 if (!JS_ConvertArguments(cx, args, "S / o", str.address(), sobj.address())) |
|
2778 return false; |
|
2779 |
|
2780 size_t srclen; |
|
2781 const jschar *src = JS_GetStringCharsAndLength(cx, str, &srclen); |
|
2782 if (!src) |
|
2783 return false; |
|
2784 |
|
2785 bool lazy = false; |
|
2786 if (srclen == 4) { |
|
2787 if (src[0] == 'l' && src[1] == 'a' && src[2] == 'z' && src[3] == 'y') { |
|
2788 lazy = true; |
|
2789 srclen = 0; |
|
2790 } |
|
2791 } |
|
2792 |
|
2793 if (!sobj) { |
|
2794 sobj = NewSandbox(cx, lazy); |
|
2795 if (!sobj) |
|
2796 return false; |
|
2797 } |
|
2798 |
|
2799 if (srclen == 0) { |
|
2800 args.rval().setObject(*sobj); |
|
2801 return true; |
|
2802 } |
|
2803 |
|
2804 JS::AutoFilename filename; |
|
2805 unsigned lineno; |
|
2806 |
|
2807 DescribeScriptedCaller(cx, &filename, &lineno); |
|
2808 { |
|
2809 Maybe<JSAutoCompartment> ac; |
|
2810 unsigned flags; |
|
2811 JSObject *unwrapped = UncheckedUnwrap(sobj, true, &flags); |
|
2812 if (flags & Wrapper::CROSS_COMPARTMENT) { |
|
2813 sobj = unwrapped; |
|
2814 ac.construct(cx, sobj); |
|
2815 } |
|
2816 |
|
2817 sobj = GetInnerObject(cx, sobj); |
|
2818 if (!sobj) |
|
2819 return false; |
|
2820 if (!(sobj->getClass()->flags & JSCLASS_IS_GLOBAL)) { |
|
2821 JS_ReportError(cx, "Invalid scope argument to evalcx"); |
|
2822 return false; |
|
2823 } |
|
2824 if (!JS_EvaluateUCScript(cx, sobj, src, srclen, |
|
2825 filename.get(), |
|
2826 lineno, |
|
2827 args.rval())) { |
|
2828 return false; |
|
2829 } |
|
2830 } |
|
2831 |
|
2832 if (!cx->compartment()->wrap(cx, args.rval())) |
|
2833 return false; |
|
2834 |
|
2835 return true; |
|
2836 } |
|
2837 |
|
2838 static bool |
|
2839 EvalInFrame(JSContext *cx, unsigned argc, jsval *vp) |
|
2840 { |
|
2841 CallArgs args = CallArgsFromVp(argc, vp); |
|
2842 if (!args.get(0).isInt32() || !args.get(1).isString()) { |
|
2843 JS_ReportError(cx, "Invalid arguments to evalInFrame"); |
|
2844 return false; |
|
2845 } |
|
2846 |
|
2847 uint32_t upCount = args[0].toInt32(); |
|
2848 RootedString str(cx, args[1].toString()); |
|
2849 bool saveCurrent = args.get(2).isBoolean() ? args[2].toBoolean() : false; |
|
2850 |
|
2851 /* This is a copy of CheckDebugMode. */ |
|
2852 if (!JS_GetDebugMode(cx)) { |
|
2853 JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, js_GetErrorMessage, |
|
2854 nullptr, JSMSG_NEED_DEBUG_MODE); |
|
2855 return false; |
|
2856 } |
|
2857 |
|
2858 /* Debug-mode currently disables Ion compilation. */ |
|
2859 ScriptFrameIter fi(cx); |
|
2860 for (uint32_t i = 0; i < upCount; ++i, ++fi) { |
|
2861 ScriptFrameIter next(fi); |
|
2862 ++next; |
|
2863 if (next.done()) |
|
2864 break; |
|
2865 } |
|
2866 |
|
2867 AutoSaveFrameChain sfc(cx); |
|
2868 mozilla::Maybe<AutoCompartment> ac; |
|
2869 if (saveCurrent) { |
|
2870 if (!sfc.save()) |
|
2871 return false; |
|
2872 ac.construct(cx, DefaultObjectForContextOrNull(cx)); |
|
2873 } |
|
2874 |
|
2875 size_t length; |
|
2876 const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length); |
|
2877 if (!chars) |
|
2878 return false; |
|
2879 |
|
2880 JSAbstractFramePtr frame(fi.abstractFramePtr().raw(), fi.pc()); |
|
2881 RootedScript fpscript(cx, frame.script()); |
|
2882 bool ok = !!frame.evaluateUCInStackFrame(cx, chars, length, |
|
2883 fpscript->filename(), |
|
2884 JS_PCToLineNumber(cx, fpscript, |
|
2885 fi.pc()), |
|
2886 MutableHandleValue::fromMarkedLocation(vp)); |
|
2887 return ok; |
|
2888 } |
|
2889 |
|
2890 #ifdef JS_THREADSAFE |
|
2891 struct WorkerInput |
|
2892 { |
|
2893 JSRuntime *runtime; |
|
2894 jschar *chars; |
|
2895 size_t length; |
|
2896 |
|
2897 WorkerInput(JSRuntime *runtime, jschar *chars, size_t length) |
|
2898 : runtime(runtime), chars(chars), length(length) |
|
2899 {} |
|
2900 |
|
2901 ~WorkerInput() { |
|
2902 js_free(chars); |
|
2903 } |
|
2904 }; |
|
2905 |
|
2906 static void |
|
2907 WorkerMain(void *arg) |
|
2908 { |
|
2909 WorkerInput *input = (WorkerInput *) arg; |
|
2910 |
|
2911 JSRuntime *rt = JS_NewRuntime(8L * 1024L * 1024L, |
|
2912 JS_USE_HELPER_THREADS, |
|
2913 input->runtime); |
|
2914 if (!rt) { |
|
2915 js_delete(input); |
|
2916 return; |
|
2917 } |
|
2918 |
|
2919 JSContext *cx = NewContext(rt); |
|
2920 if (!cx) { |
|
2921 JS_DestroyRuntime(rt); |
|
2922 js_delete(input); |
|
2923 return; |
|
2924 } |
|
2925 |
|
2926 do { |
|
2927 JSAutoRequest ar(cx); |
|
2928 |
|
2929 JS::CompartmentOptions compartmentOptions; |
|
2930 compartmentOptions.setVersion(JSVERSION_LATEST); |
|
2931 RootedObject global(cx, NewGlobalObject(cx, compartmentOptions, nullptr)); |
|
2932 if (!global) |
|
2933 break; |
|
2934 |
|
2935 JSAutoCompartment ac(cx, global); |
|
2936 |
|
2937 JS::CompileOptions options(cx); |
|
2938 options.setFileAndLine("<string>", 1) |
|
2939 .setCompileAndGo(true); |
|
2940 |
|
2941 RootedScript script(cx, JS::Compile(cx, global, options, |
|
2942 input->chars, input->length)); |
|
2943 if (!script) |
|
2944 break; |
|
2945 RootedValue result(cx); |
|
2946 JS_ExecuteScript(cx, global, script, &result); |
|
2947 } while (0); |
|
2948 |
|
2949 DestroyContext(cx, false); |
|
2950 JS_DestroyRuntime(rt); |
|
2951 |
|
2952 js_delete(input); |
|
2953 } |
|
2954 |
|
2955 Vector<PRThread *, 0, SystemAllocPolicy> workerThreads; |
|
2956 |
|
2957 static bool |
|
2958 EvalInWorker(JSContext *cx, unsigned argc, jsval *vp) |
|
2959 { |
|
2960 CallArgs args = CallArgsFromVp(argc, vp); |
|
2961 if (!args.get(0).isString()) { |
|
2962 JS_ReportError(cx, "Invalid arguments to evalInWorker"); |
|
2963 return false; |
|
2964 } |
|
2965 |
|
2966 if (!args[0].toString()->ensureLinear(cx)) |
|
2967 return false; |
|
2968 |
|
2969 JSLinearString *str = &args[0].toString()->asLinear(); |
|
2970 |
|
2971 jschar *chars = (jschar *) js_malloc(str->length() * sizeof(jschar)); |
|
2972 if (!chars) |
|
2973 return false; |
|
2974 PodCopy(chars, str->chars(), str->length()); |
|
2975 |
|
2976 WorkerInput *input = js_new<WorkerInput>(cx->runtime(), chars, str->length()); |
|
2977 if (!input) |
|
2978 return false; |
|
2979 |
|
2980 PRThread *thread = PR_CreateThread(PR_USER_THREAD, WorkerMain, input, |
|
2981 PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0); |
|
2982 if (!thread || !workerThreads.append(thread)) |
|
2983 return false; |
|
2984 |
|
2985 return true; |
|
2986 } |
|
2987 #endif |
|
2988 |
|
2989 static bool |
|
2990 ShapeOf(JSContext *cx, unsigned argc, JS::Value *vp) |
|
2991 { |
|
2992 CallArgs args = CallArgsFromVp(argc, vp); |
|
2993 if (!args.get(0).isObject()) { |
|
2994 JS_ReportError(cx, "shapeOf: object expected"); |
|
2995 return false; |
|
2996 } |
|
2997 JSObject *obj = &args[0].toObject(); |
|
2998 args.rval().set(JS_NumberValue(double(uintptr_t(obj->lastProperty()) >> 3))); |
|
2999 return true; |
|
3000 } |
|
3001 |
|
3002 /* |
|
3003 * If referent has an own property named id, copy that property to obj[id]. |
|
3004 * Since obj is native, this isn't totally transparent; properties of a |
|
3005 * non-native referent may be simplified to data properties. |
|
3006 */ |
|
3007 static bool |
|
3008 CopyProperty(JSContext *cx, HandleObject obj, HandleObject referent, HandleId id, |
|
3009 MutableHandleObject objp) |
|
3010 { |
|
3011 RootedShape shape(cx); |
|
3012 Rooted<PropertyDescriptor> desc(cx); |
|
3013 RootedObject obj2(cx); |
|
3014 |
|
3015 objp.set(nullptr); |
|
3016 if (referent->isNative()) { |
|
3017 if (!LookupNativeProperty(cx, referent, id, &obj2, &shape)) |
|
3018 return false; |
|
3019 if (obj2 != referent) |
|
3020 return true; |
|
3021 |
|
3022 if (shape->hasSlot()) { |
|
3023 desc.value().set(referent->nativeGetSlot(shape->slot())); |
|
3024 } else { |
|
3025 desc.value().setUndefined(); |
|
3026 } |
|
3027 |
|
3028 desc.setAttributes(shape->attributes()); |
|
3029 desc.setGetter(shape->getter()); |
|
3030 if (!desc.getter() && !desc.hasGetterObject()) |
|
3031 desc.setGetter(JS_PropertyStub); |
|
3032 desc.setSetter(shape->setter()); |
|
3033 if (!desc.setter() && !desc.hasSetterObject()) |
|
3034 desc.setSetter(JS_StrictPropertyStub); |
|
3035 } else if (referent->is<ProxyObject>()) { |
|
3036 if (!Proxy::getOwnPropertyDescriptor(cx, referent, id, &desc)) |
|
3037 return false; |
|
3038 if (!desc.object()) |
|
3039 return true; |
|
3040 } else { |
|
3041 if (!JSObject::lookupGeneric(cx, referent, id, objp, &shape)) |
|
3042 return false; |
|
3043 if (objp != referent) |
|
3044 return true; |
|
3045 RootedValue value(cx); |
|
3046 if (!JSObject::getGeneric(cx, referent, referent, id, &value) || |
|
3047 !JSObject::getGenericAttributes(cx, referent, id, &desc.attributesRef())) |
|
3048 { |
|
3049 return false; |
|
3050 } |
|
3051 desc.value().set(value); |
|
3052 desc.attributesRef() &= JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT; |
|
3053 desc.setGetter(JS_PropertyStub); |
|
3054 desc.setSetter(JS_StrictPropertyStub); |
|
3055 } |
|
3056 |
|
3057 objp.set(obj); |
|
3058 return DefineNativeProperty(cx, obj, id, desc.value(), desc.getter(), desc.setter(), |
|
3059 desc.attributes()); |
|
3060 } |
|
3061 |
|
3062 static bool |
|
3063 resolver_resolve(JSContext *cx, HandleObject obj, HandleId id, MutableHandleObject objp) |
|
3064 { |
|
3065 jsval v = JS_GetReservedSlot(obj, 0); |
|
3066 Rooted<JSObject*> vobj(cx, &v.toObject()); |
|
3067 return CopyProperty(cx, obj, vobj, id, objp); |
|
3068 } |
|
3069 |
|
3070 static bool |
|
3071 resolver_enumerate(JSContext *cx, HandleObject obj) |
|
3072 { |
|
3073 jsval v = JS_GetReservedSlot(obj, 0); |
|
3074 RootedObject referent(cx, JSVAL_TO_OBJECT(v)); |
|
3075 |
|
3076 AutoIdArray ida(cx, JS_Enumerate(cx, referent)); |
|
3077 bool ok = !!ida; |
|
3078 RootedObject ignore(cx); |
|
3079 for (size_t i = 0; ok && i < ida.length(); i++) { |
|
3080 Rooted<jsid> id(cx, ida[i]); |
|
3081 ok = CopyProperty(cx, obj, referent, id, &ignore); |
|
3082 } |
|
3083 return ok; |
|
3084 } |
|
3085 |
|
3086 static const JSClass resolver_class = { |
|
3087 "resolver", |
|
3088 JSCLASS_NEW_RESOLVE | JSCLASS_HAS_RESERVED_SLOTS(1), |
|
3089 JS_PropertyStub, JS_DeletePropertyStub, |
|
3090 JS_PropertyStub, JS_StrictPropertyStub, |
|
3091 resolver_enumerate, (JSResolveOp)resolver_resolve, |
|
3092 JS_ConvertStub |
|
3093 }; |
|
3094 |
|
3095 |
|
3096 static bool |
|
3097 Resolver(JSContext *cx, unsigned argc, jsval *vp) |
|
3098 { |
|
3099 CallArgs args = CallArgsFromVp(argc, vp); |
|
3100 |
|
3101 RootedObject referent(cx); |
|
3102 if (!JS_ValueToObject(cx, args.get(0), &referent)) |
|
3103 return false; |
|
3104 if (!referent) { |
|
3105 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO, |
|
3106 args.get(0).isNull() ? "null" : "undefined", "object"); |
|
3107 return false; |
|
3108 } |
|
3109 |
|
3110 RootedObject proto(cx, nullptr); |
|
3111 if (!args.get(1).isNullOrUndefined()) { |
|
3112 if (!JS_ValueToObject(cx, args.get(1), &proto)) |
|
3113 return false; |
|
3114 } |
|
3115 |
|
3116 RootedObject parent(cx, JS_GetParent(referent)); |
|
3117 JSObject *result = (args.length() > 1 |
|
3118 ? JS_NewObjectWithGivenProto |
|
3119 : JS_NewObject)(cx, &resolver_class, proto, parent); |
|
3120 if (!result) |
|
3121 return false; |
|
3122 |
|
3123 JS_SetReservedSlot(result, 0, ObjectValue(*referent)); |
|
3124 args.rval().setObject(*result); |
|
3125 return true; |
|
3126 } |
|
3127 |
|
3128 #ifdef JS_THREADSAFE |
|
3129 |
|
3130 /* |
|
3131 * Check that t1 comes strictly before t2. The function correctly deals with |
|
3132 * wrap-around between t2 and t1 assuming that t2 and t1 stays within INT32_MAX |
|
3133 * from each other. We use MAX_TIMEOUT_INTERVAL to enforce this restriction. |
|
3134 */ |
|
3135 static bool |
|
3136 IsBefore(int64_t t1, int64_t t2) |
|
3137 { |
|
3138 return int32_t(t1 - t2) < 0; |
|
3139 } |
|
3140 |
|
3141 static bool |
|
3142 Sleep_fn(JSContext *cx, unsigned argc, Value *vp) |
|
3143 { |
|
3144 CallArgs args = CallArgsFromVp(argc, vp); |
|
3145 int64_t t_ticks; |
|
3146 |
|
3147 if (args.length() == 0) { |
|
3148 t_ticks = 0; |
|
3149 } else { |
|
3150 double t_secs; |
|
3151 |
|
3152 if (!ToNumber(cx, args[0], &t_secs)) |
|
3153 return false; |
|
3154 |
|
3155 /* NB: The next condition also filter out NaNs. */ |
|
3156 if (!(t_secs <= MAX_TIMEOUT_INTERVAL)) { |
|
3157 JS_ReportError(cx, "Excessive sleep interval"); |
|
3158 return false; |
|
3159 } |
|
3160 t_ticks = (t_secs <= 0.0) |
|
3161 ? 0 |
|
3162 : int64_t(PRMJ_USEC_PER_SEC * t_secs); |
|
3163 } |
|
3164 PR_Lock(gWatchdogLock); |
|
3165 int64_t to_wakeup = PRMJ_Now() + t_ticks; |
|
3166 for (;;) { |
|
3167 PR_WaitCondVar(gSleepWakeup, t_ticks); |
|
3168 if (gServiceInterrupt) |
|
3169 break; |
|
3170 int64_t now = PRMJ_Now(); |
|
3171 if (!IsBefore(now, to_wakeup)) |
|
3172 break; |
|
3173 t_ticks = to_wakeup - now; |
|
3174 } |
|
3175 PR_Unlock(gWatchdogLock); |
|
3176 return !gServiceInterrupt; |
|
3177 } |
|
3178 |
|
3179 static bool |
|
3180 InitWatchdog(JSRuntime *rt) |
|
3181 { |
|
3182 JS_ASSERT(!gWatchdogThread); |
|
3183 gWatchdogLock = PR_NewLock(); |
|
3184 if (gWatchdogLock) { |
|
3185 gWatchdogWakeup = PR_NewCondVar(gWatchdogLock); |
|
3186 if (gWatchdogWakeup) { |
|
3187 gSleepWakeup = PR_NewCondVar(gWatchdogLock); |
|
3188 if (gSleepWakeup) |
|
3189 return true; |
|
3190 PR_DestroyCondVar(gWatchdogWakeup); |
|
3191 } |
|
3192 PR_DestroyLock(gWatchdogLock); |
|
3193 } |
|
3194 return false; |
|
3195 } |
|
3196 |
|
3197 static void |
|
3198 KillWatchdog() |
|
3199 { |
|
3200 PRThread *thread; |
|
3201 |
|
3202 PR_Lock(gWatchdogLock); |
|
3203 thread = gWatchdogThread; |
|
3204 if (thread) { |
|
3205 /* |
|
3206 * The watchdog thread is running, tell it to terminate waking it up |
|
3207 * if necessary. |
|
3208 */ |
|
3209 gWatchdogThread = nullptr; |
|
3210 PR_NotifyCondVar(gWatchdogWakeup); |
|
3211 } |
|
3212 PR_Unlock(gWatchdogLock); |
|
3213 if (thread) |
|
3214 PR_JoinThread(thread); |
|
3215 PR_DestroyCondVar(gSleepWakeup); |
|
3216 PR_DestroyCondVar(gWatchdogWakeup); |
|
3217 PR_DestroyLock(gWatchdogLock); |
|
3218 } |
|
3219 |
|
3220 static void |
|
3221 WatchdogMain(void *arg) |
|
3222 { |
|
3223 PR_SetCurrentThreadName("JS Watchdog"); |
|
3224 |
|
3225 JSRuntime *rt = (JSRuntime *) arg; |
|
3226 |
|
3227 PR_Lock(gWatchdogLock); |
|
3228 while (gWatchdogThread) { |
|
3229 int64_t now = PRMJ_Now(); |
|
3230 if (gWatchdogHasTimeout && !IsBefore(now, gWatchdogTimeout)) { |
|
3231 /* |
|
3232 * The timeout has just expired. Request an interrupt callback |
|
3233 * outside the lock. |
|
3234 */ |
|
3235 gWatchdogHasTimeout = false; |
|
3236 PR_Unlock(gWatchdogLock); |
|
3237 CancelExecution(rt); |
|
3238 PR_Lock(gWatchdogLock); |
|
3239 |
|
3240 /* Wake up any threads doing sleep. */ |
|
3241 PR_NotifyAllCondVar(gSleepWakeup); |
|
3242 } else { |
|
3243 if (gWatchdogHasTimeout) { |
|
3244 /* |
|
3245 * Time hasn't expired yet. Simulate an interrupt callback |
|
3246 * which doesn't abort execution. |
|
3247 */ |
|
3248 JS_RequestInterruptCallback(rt); |
|
3249 } |
|
3250 |
|
3251 uint64_t sleepDuration = PR_INTERVAL_NO_TIMEOUT; |
|
3252 if (gWatchdogHasTimeout) |
|
3253 sleepDuration = PR_TicksPerSecond() / 10; |
|
3254 mozilla::DebugOnly<PRStatus> status = |
|
3255 PR_WaitCondVar(gWatchdogWakeup, sleepDuration); |
|
3256 JS_ASSERT(status == PR_SUCCESS); |
|
3257 } |
|
3258 } |
|
3259 PR_Unlock(gWatchdogLock); |
|
3260 } |
|
3261 |
|
3262 static bool |
|
3263 ScheduleWatchdog(JSRuntime *rt, double t) |
|
3264 { |
|
3265 if (t <= 0) { |
|
3266 PR_Lock(gWatchdogLock); |
|
3267 gWatchdogHasTimeout = false; |
|
3268 PR_Unlock(gWatchdogLock); |
|
3269 return true; |
|
3270 } |
|
3271 |
|
3272 int64_t interval = int64_t(ceil(t * PRMJ_USEC_PER_SEC)); |
|
3273 int64_t timeout = PRMJ_Now() + interval; |
|
3274 PR_Lock(gWatchdogLock); |
|
3275 if (!gWatchdogThread) { |
|
3276 JS_ASSERT(!gWatchdogHasTimeout); |
|
3277 gWatchdogThread = PR_CreateThread(PR_USER_THREAD, |
|
3278 WatchdogMain, |
|
3279 rt, |
|
3280 PR_PRIORITY_NORMAL, |
|
3281 PR_GLOBAL_THREAD, |
|
3282 PR_JOINABLE_THREAD, |
|
3283 0); |
|
3284 if (!gWatchdogThread) { |
|
3285 PR_Unlock(gWatchdogLock); |
|
3286 return false; |
|
3287 } |
|
3288 } else if (!gWatchdogHasTimeout || IsBefore(timeout, gWatchdogTimeout)) { |
|
3289 PR_NotifyCondVar(gWatchdogWakeup); |
|
3290 } |
|
3291 gWatchdogHasTimeout = true; |
|
3292 gWatchdogTimeout = timeout; |
|
3293 PR_Unlock(gWatchdogLock); |
|
3294 return true; |
|
3295 } |
|
3296 |
|
3297 #else /* !JS_THREADSAFE */ |
|
3298 |
|
3299 #ifdef XP_WIN |
|
3300 static HANDLE gTimerHandle = 0; |
|
3301 |
|
3302 VOID CALLBACK |
|
3303 TimerCallback(PVOID lpParameter, BOOLEAN TimerOrWaitFired) |
|
3304 { |
|
3305 CancelExecution((JSRuntime *) lpParameter); |
|
3306 } |
|
3307 |
|
3308 #else |
|
3309 |
|
3310 static void |
|
3311 AlarmHandler(int sig) |
|
3312 { |
|
3313 CancelExecution(gRuntime); |
|
3314 } |
|
3315 |
|
3316 #endif |
|
3317 |
|
3318 static bool |
|
3319 InitWatchdog(JSRuntime *rt) |
|
3320 { |
|
3321 gRuntime = rt; |
|
3322 return true; |
|
3323 } |
|
3324 |
|
3325 static void |
|
3326 KillWatchdog() |
|
3327 { |
|
3328 ScheduleWatchdog(gRuntime, -1); |
|
3329 } |
|
3330 |
|
3331 static bool |
|
3332 ScheduleWatchdog(JSRuntime *rt, double t) |
|
3333 { |
|
3334 #ifdef XP_WIN |
|
3335 if (gTimerHandle) { |
|
3336 DeleteTimerQueueTimer(nullptr, gTimerHandle, nullptr); |
|
3337 gTimerHandle = 0; |
|
3338 } |
|
3339 if (t > 0 && |
|
3340 !CreateTimerQueueTimer(&gTimerHandle, |
|
3341 nullptr, |
|
3342 (WAITORTIMERCALLBACK)TimerCallback, |
|
3343 rt, |
|
3344 DWORD(ceil(t * 1000.0)), |
|
3345 0, |
|
3346 WT_EXECUTEINTIMERTHREAD | WT_EXECUTEONLYONCE)) { |
|
3347 gTimerHandle = 0; |
|
3348 return false; |
|
3349 } |
|
3350 #else |
|
3351 /* FIXME: use setitimer when available for sub-second resolution. */ |
|
3352 if (t <= 0) { |
|
3353 alarm(0); |
|
3354 signal(SIGALRM, nullptr); |
|
3355 } else { |
|
3356 signal(SIGALRM, AlarmHandler); /* set the Alarm signal capture */ |
|
3357 alarm(ceil(t)); |
|
3358 } |
|
3359 #endif |
|
3360 return true; |
|
3361 } |
|
3362 |
|
3363 #endif /* !JS_THREADSAFE */ |
|
3364 |
|
3365 static void |
|
3366 CancelExecution(JSRuntime *rt) |
|
3367 { |
|
3368 gServiceInterrupt = true; |
|
3369 JS_RequestInterruptCallback(rt); |
|
3370 |
|
3371 if (!gInterruptFunc.ref().get().isNull()) { |
|
3372 static const char msg[] = "Script runs for too long, terminating.\n"; |
|
3373 #if defined(XP_UNIX) && !defined(JS_THREADSAFE) |
|
3374 /* It is not safe to call fputs from signals. */ |
|
3375 /* Dummy assignment avoids GCC warning on "attribute warn_unused_result" */ |
|
3376 ssize_t dummy = write(2, msg, sizeof(msg) - 1); |
|
3377 (void)dummy; |
|
3378 #else |
|
3379 fputs(msg, stderr); |
|
3380 #endif |
|
3381 } |
|
3382 } |
|
3383 |
|
3384 static bool |
|
3385 SetTimeoutValue(JSContext *cx, double t) |
|
3386 { |
|
3387 /* NB: The next condition also filter out NaNs. */ |
|
3388 if (!(t <= MAX_TIMEOUT_INTERVAL)) { |
|
3389 JS_ReportError(cx, "Excessive timeout value"); |
|
3390 return false; |
|
3391 } |
|
3392 gTimeoutInterval = t; |
|
3393 if (!ScheduleWatchdog(cx->runtime(), t)) { |
|
3394 JS_ReportError(cx, "Failed to create the watchdog"); |
|
3395 return false; |
|
3396 } |
|
3397 return true; |
|
3398 } |
|
3399 |
|
3400 static bool |
|
3401 Timeout(JSContext *cx, unsigned argc, Value *vp) |
|
3402 { |
|
3403 CallArgs args = CallArgsFromVp(argc, vp); |
|
3404 |
|
3405 if (args.length() == 0) { |
|
3406 args.rval().setNumber(gTimeoutInterval); |
|
3407 return true; |
|
3408 } |
|
3409 |
|
3410 if (args.length() > 2) { |
|
3411 JS_ReportError(cx, "Wrong number of arguments"); |
|
3412 return false; |
|
3413 } |
|
3414 |
|
3415 double t; |
|
3416 if (!ToNumber(cx, args[0], &t)) |
|
3417 return false; |
|
3418 |
|
3419 if (args.length() > 1) { |
|
3420 RootedValue value(cx, args[1]); |
|
3421 if (!value.isObject() || !value.toObject().is<JSFunction>()) { |
|
3422 JS_ReportError(cx, "Second argument must be a timeout function"); |
|
3423 return false; |
|
3424 } |
|
3425 gInterruptFunc.ref() = value; |
|
3426 } |
|
3427 |
|
3428 args.rval().setUndefined(); |
|
3429 return SetTimeoutValue(cx, t); |
|
3430 } |
|
3431 |
|
3432 static bool |
|
3433 InterruptIf(JSContext *cx, unsigned argc, Value *vp) |
|
3434 { |
|
3435 CallArgs args = CallArgsFromVp(argc, vp); |
|
3436 |
|
3437 if (args.length() != 1) { |
|
3438 JS_ReportError(cx, "Wrong number of arguments"); |
|
3439 return false; |
|
3440 } |
|
3441 |
|
3442 if (ToBoolean(args[0])) { |
|
3443 gServiceInterrupt = true; |
|
3444 JS_RequestInterruptCallback(cx->runtime()); |
|
3445 } |
|
3446 |
|
3447 args.rval().setUndefined(); |
|
3448 return true; |
|
3449 } |
|
3450 |
|
3451 static bool |
|
3452 SetInterruptCallback(JSContext *cx, unsigned argc, Value *vp) |
|
3453 { |
|
3454 CallArgs args = CallArgsFromVp(argc, vp); |
|
3455 |
|
3456 if (args.length() != 1) { |
|
3457 JS_ReportError(cx, "Wrong number of arguments"); |
|
3458 return false; |
|
3459 } |
|
3460 |
|
3461 RootedValue value(cx, args[0]); |
|
3462 if (!value.isObject() || !value.toObject().is<JSFunction>()) { |
|
3463 JS_ReportError(cx, "Argument must be a function"); |
|
3464 return false; |
|
3465 } |
|
3466 gInterruptFunc.ref() = value; |
|
3467 |
|
3468 args.rval().setUndefined(); |
|
3469 return true; |
|
3470 } |
|
3471 |
|
3472 static bool |
|
3473 Elapsed(JSContext *cx, unsigned argc, jsval *vp) |
|
3474 { |
|
3475 CallArgs args = CallArgsFromVp(argc, vp); |
|
3476 if (args.length() == 0) { |
|
3477 double d = 0.0; |
|
3478 JSShellContextData *data = GetContextData(cx); |
|
3479 if (data) |
|
3480 d = PRMJ_Now() - data->startTime; |
|
3481 args.rval().setDouble(d); |
|
3482 return true; |
|
3483 } |
|
3484 JS_ReportError(cx, "Wrong number of arguments"); |
|
3485 return false; |
|
3486 } |
|
3487 |
|
3488 static bool |
|
3489 Parent(JSContext *cx, unsigned argc, jsval *vp) |
|
3490 { |
|
3491 CallArgs args = CallArgsFromVp(argc, vp); |
|
3492 if (args.length() != 1) { |
|
3493 JS_ReportError(cx, "Wrong number of arguments"); |
|
3494 return false; |
|
3495 } |
|
3496 |
|
3497 Value v = args[0]; |
|
3498 if (JSVAL_IS_PRIMITIVE(v)) { |
|
3499 JS_ReportError(cx, "Only objects have parents!"); |
|
3500 return false; |
|
3501 } |
|
3502 |
|
3503 Rooted<JSObject*> parent(cx, JS_GetParent(&v.toObject())); |
|
3504 args.rval().setObjectOrNull(parent); |
|
3505 |
|
3506 /* Outerize if necessary. Embrace the ugliness! */ |
|
3507 if (parent) { |
|
3508 if (JSObjectOp op = parent->getClass()->ext.outerObject) |
|
3509 args.rval().setObjectOrNull(op(cx, parent)); |
|
3510 } |
|
3511 |
|
3512 return true; |
|
3513 } |
|
3514 |
|
3515 static bool |
|
3516 Compile(JSContext *cx, unsigned argc, jsval *vp) |
|
3517 { |
|
3518 CallArgs args = CallArgsFromVp(argc, vp); |
|
3519 if (args.length() < 1) { |
|
3520 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, |
|
3521 "compile", "0", "s"); |
|
3522 return false; |
|
3523 } |
|
3524 if (!args[0].isString()) { |
|
3525 const char *typeName = JS_GetTypeName(cx, JS_TypeOfValue(cx, args[0])); |
|
3526 JS_ReportError(cx, "expected string to compile, got %s", typeName); |
|
3527 return false; |
|
3528 } |
|
3529 |
|
3530 RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); |
|
3531 JSString *scriptContents = args[0].toString(); |
|
3532 JS::AutoSaveContextOptions asco(cx); |
|
3533 JS::ContextOptionsRef(cx).setNoScriptRval(true); |
|
3534 JS::CompileOptions options(cx); |
|
3535 options.setIntroductionType("js shell compile") |
|
3536 .setFileAndLine("<string>", 1) |
|
3537 .setCompileAndGo(true); |
|
3538 bool ok = JS_CompileUCScript(cx, global, JS_GetStringCharsZ(cx, scriptContents), |
|
3539 JS_GetStringLength(scriptContents), options); |
|
3540 args.rval().setUndefined(); |
|
3541 return ok; |
|
3542 } |
|
3543 |
|
3544 static bool |
|
3545 Parse(JSContext *cx, unsigned argc, jsval *vp) |
|
3546 { |
|
3547 using namespace js::frontend; |
|
3548 |
|
3549 CallArgs args = CallArgsFromVp(argc, vp); |
|
3550 |
|
3551 if (args.length() < 1) { |
|
3552 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, |
|
3553 "parse", "0", "s"); |
|
3554 return false; |
|
3555 } |
|
3556 if (!args[0].isString()) { |
|
3557 const char *typeName = JS_GetTypeName(cx, JS_TypeOfValue(cx, args[0])); |
|
3558 JS_ReportError(cx, "expected string to parse, got %s", typeName); |
|
3559 return false; |
|
3560 } |
|
3561 |
|
3562 JSString *scriptContents = args[0].toString(); |
|
3563 CompileOptions options(cx); |
|
3564 options.setIntroductionType("js shell parse") |
|
3565 .setFileAndLine("<string>", 1) |
|
3566 .setCompileAndGo(false); |
|
3567 Parser<FullParseHandler> parser(cx, &cx->tempLifoAlloc(), options, |
|
3568 JS_GetStringCharsZ(cx, scriptContents), |
|
3569 JS_GetStringLength(scriptContents), |
|
3570 /* foldConstants = */ true, nullptr, nullptr); |
|
3571 |
|
3572 ParseNode *pn = parser.parse(nullptr); |
|
3573 if (!pn) |
|
3574 return false; |
|
3575 #ifdef DEBUG |
|
3576 DumpParseTree(pn); |
|
3577 fputc('\n', stderr); |
|
3578 #endif |
|
3579 args.rval().setUndefined(); |
|
3580 return true; |
|
3581 } |
|
3582 |
|
3583 static bool |
|
3584 SyntaxParse(JSContext *cx, unsigned argc, jsval *vp) |
|
3585 { |
|
3586 using namespace js::frontend; |
|
3587 |
|
3588 CallArgs args = CallArgsFromVp(argc, vp); |
|
3589 |
|
3590 if (args.length() < 1) { |
|
3591 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, |
|
3592 "parse", "0", "s"); |
|
3593 return false; |
|
3594 } |
|
3595 if (!args[0].isString()) { |
|
3596 const char *typeName = JS_GetTypeName(cx, JS_TypeOfValue(cx, args[0])); |
|
3597 JS_ReportError(cx, "expected string to parse, got %s", typeName); |
|
3598 return false; |
|
3599 } |
|
3600 |
|
3601 JSString *scriptContents = args[0].toString(); |
|
3602 CompileOptions options(cx); |
|
3603 options.setIntroductionType("js shell syntaxParse") |
|
3604 .setFileAndLine("<string>", 1) |
|
3605 .setCompileAndGo(false); |
|
3606 |
|
3607 const jschar *chars = JS_GetStringCharsZ(cx, scriptContents); |
|
3608 size_t length = JS_GetStringLength(scriptContents); |
|
3609 Parser<frontend::SyntaxParseHandler> parser(cx, &cx->tempLifoAlloc(), |
|
3610 options, chars, length, false, nullptr, nullptr); |
|
3611 |
|
3612 bool succeeded = parser.parse(nullptr); |
|
3613 if (cx->isExceptionPending()) |
|
3614 return false; |
|
3615 |
|
3616 if (!succeeded && !parser.hadAbortedSyntaxParse()) { |
|
3617 // If no exception is posted, either there was an OOM or a language |
|
3618 // feature unhandled by the syntax parser was encountered. |
|
3619 JS_ASSERT(cx->runtime()->hadOutOfMemory); |
|
3620 return false; |
|
3621 } |
|
3622 |
|
3623 args.rval().setBoolean(succeeded); |
|
3624 return true; |
|
3625 } |
|
3626 |
|
3627 #ifdef JS_THREADSAFE |
|
3628 |
|
3629 class OffThreadState { |
|
3630 public: |
|
3631 enum State { |
|
3632 IDLE, /* ready to work; no token, no source */ |
|
3633 COMPILING, /* working; no token, have source */ |
|
3634 DONE /* compilation done: have token and source */ |
|
3635 }; |
|
3636 |
|
3637 OffThreadState() : monitor(), state(IDLE), token() { } |
|
3638 bool init() { return monitor.init(); } |
|
3639 |
|
3640 bool startIfIdle(JSContext *cx, JSString *newSource) { |
|
3641 AutoLockMonitor alm(monitor); |
|
3642 if (state != IDLE) |
|
3643 return false; |
|
3644 |
|
3645 JS_ASSERT(!token); |
|
3646 |
|
3647 source.construct(cx, newSource); |
|
3648 |
|
3649 state = COMPILING; |
|
3650 return true; |
|
3651 } |
|
3652 |
|
3653 void abandon(JSContext *cx) { |
|
3654 AutoLockMonitor alm(monitor); |
|
3655 JS_ASSERT(state == COMPILING); |
|
3656 JS_ASSERT(!token); |
|
3657 JS_ASSERT(source.ref()); |
|
3658 |
|
3659 source.destroy(); |
|
3660 |
|
3661 state = IDLE; |
|
3662 } |
|
3663 |
|
3664 void markDone(void *newToken) { |
|
3665 AutoLockMonitor alm(monitor); |
|
3666 JS_ASSERT(state == COMPILING); |
|
3667 JS_ASSERT(!token); |
|
3668 JS_ASSERT(source.ref()); |
|
3669 JS_ASSERT(newToken); |
|
3670 |
|
3671 token = newToken; |
|
3672 state = DONE; |
|
3673 alm.notify(); |
|
3674 } |
|
3675 |
|
3676 void *waitUntilDone(JSContext *cx) { |
|
3677 AutoLockMonitor alm(monitor); |
|
3678 if (state == IDLE) |
|
3679 return nullptr; |
|
3680 |
|
3681 if (state == COMPILING) { |
|
3682 while (state != DONE) |
|
3683 alm.wait(); |
|
3684 } |
|
3685 |
|
3686 JS_ASSERT(source.ref()); |
|
3687 source.destroy(); |
|
3688 |
|
3689 JS_ASSERT(token); |
|
3690 void *holdToken = token; |
|
3691 token = nullptr; |
|
3692 state = IDLE; |
|
3693 return holdToken; |
|
3694 } |
|
3695 |
|
3696 private: |
|
3697 Monitor monitor; |
|
3698 State state; |
|
3699 void *token; |
|
3700 Maybe<PersistentRootedString> source; |
|
3701 }; |
|
3702 |
|
3703 static OffThreadState offThreadState; |
|
3704 |
|
3705 static void |
|
3706 OffThreadCompileScriptCallback(void *token, void *callbackData) |
|
3707 { |
|
3708 offThreadState.markDone(token); |
|
3709 } |
|
3710 |
|
3711 static bool |
|
3712 OffThreadCompileScript(JSContext *cx, unsigned argc, jsval *vp) |
|
3713 { |
|
3714 CallArgs args = CallArgsFromVp(argc, vp); |
|
3715 |
|
3716 if (args.length() < 1) { |
|
3717 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, |
|
3718 "offThreadCompileScript", "0", "s"); |
|
3719 return false; |
|
3720 } |
|
3721 if (!args[0].isString()) { |
|
3722 const char *typeName = JS_GetTypeName(cx, JS_TypeOfValue(cx, args[0])); |
|
3723 JS_ReportError(cx, "expected string to parse, got %s", typeName); |
|
3724 return false; |
|
3725 } |
|
3726 |
|
3727 JSAutoByteString fileNameBytes; |
|
3728 CompileOptions options(cx); |
|
3729 options.setIntroductionType("js shell offThreadCompileScript") |
|
3730 .setFileAndLine("<string>", 1); |
|
3731 |
|
3732 if (args.length() >= 2) { |
|
3733 if (args[1].isPrimitive()) { |
|
3734 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "evaluate"); |
|
3735 return false; |
|
3736 } |
|
3737 |
|
3738 RootedObject opts(cx, &args[1].toObject()); |
|
3739 if (!ParseCompileOptions(cx, options, opts, fileNameBytes)) |
|
3740 return false; |
|
3741 } |
|
3742 |
|
3743 // These option settings must override whatever the caller requested. |
|
3744 options.setCompileAndGo(true) |
|
3745 .setSourceIsLazy(false); |
|
3746 |
|
3747 // We assume the caller wants caching if at all possible, ignoring |
|
3748 // heuristics that make sense for a real browser. |
|
3749 options.forceAsync = true; |
|
3750 |
|
3751 JSString *scriptContents = args[0].toString(); |
|
3752 const jschar *chars = JS_GetStringCharsZ(cx, scriptContents); |
|
3753 if (!chars) |
|
3754 return false; |
|
3755 size_t length = JS_GetStringLength(scriptContents); |
|
3756 |
|
3757 if (!JS::CanCompileOffThread(cx, options, length)) { |
|
3758 JS_ReportError(cx, "cannot compile code on worker thread"); |
|
3759 return false; |
|
3760 } |
|
3761 |
|
3762 if (!offThreadState.startIfIdle(cx, scriptContents)) { |
|
3763 JS_ReportError(cx, "called offThreadCompileScript without calling runOffThreadScript" |
|
3764 " to receive prior off-thread compilation"); |
|
3765 return false; |
|
3766 } |
|
3767 |
|
3768 if (!JS::CompileOffThread(cx, options, chars, length, |
|
3769 OffThreadCompileScriptCallback, nullptr)) |
|
3770 { |
|
3771 offThreadState.abandon(cx); |
|
3772 return false; |
|
3773 } |
|
3774 |
|
3775 args.rval().setUndefined(); |
|
3776 return true; |
|
3777 } |
|
3778 |
|
3779 static bool |
|
3780 runOffThreadScript(JSContext *cx, unsigned argc, jsval *vp) |
|
3781 { |
|
3782 CallArgs args = CallArgsFromVp(argc, vp); |
|
3783 |
|
3784 void *token = offThreadState.waitUntilDone(cx); |
|
3785 if (!token) { |
|
3786 JS_ReportError(cx, "called runOffThreadScript when no compilation is pending"); |
|
3787 return false; |
|
3788 } |
|
3789 |
|
3790 RootedScript script(cx, JS::FinishOffThreadScript(cx, cx->runtime(), token)); |
|
3791 if (!script) |
|
3792 return false; |
|
3793 |
|
3794 return JS_ExecuteScript(cx, cx->global(), script, args.rval()); |
|
3795 } |
|
3796 |
|
3797 #endif // JS_THREADSAFE |
|
3798 |
|
3799 struct FreeOnReturn |
|
3800 { |
|
3801 JSContext *cx; |
|
3802 const char *ptr; |
|
3803 MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER |
|
3804 |
|
3805 FreeOnReturn(JSContext *cx, const char *ptr = nullptr |
|
3806 MOZ_GUARD_OBJECT_NOTIFIER_PARAM) |
|
3807 : cx(cx), ptr(ptr) |
|
3808 { |
|
3809 MOZ_GUARD_OBJECT_NOTIFIER_INIT; |
|
3810 } |
|
3811 |
|
3812 void init(const char *ptr) { |
|
3813 JS_ASSERT(!this->ptr); |
|
3814 this->ptr = ptr; |
|
3815 } |
|
3816 |
|
3817 ~FreeOnReturn() { |
|
3818 JS_free(cx, (void*)ptr); |
|
3819 } |
|
3820 }; |
|
3821 |
|
3822 static bool |
|
3823 ReadFile(JSContext *cx, unsigned argc, jsval *vp, bool scriptRelative) |
|
3824 { |
|
3825 CallArgs args = CallArgsFromVp(argc, vp); |
|
3826 |
|
3827 if (args.length() < 1 || args.length() > 2) { |
|
3828 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, |
|
3829 args.length() < 1 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS, |
|
3830 "snarf"); |
|
3831 return false; |
|
3832 } |
|
3833 |
|
3834 if (!args[0].isString() || (args.length() == 2 && !args[1].isString())) { |
|
3835 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "snarf"); |
|
3836 return false; |
|
3837 } |
|
3838 |
|
3839 RootedString givenPath(cx, args[0].toString()); |
|
3840 RootedString str(cx, ResolvePath(cx, givenPath, scriptRelative ? ScriptRelative : RootRelative)); |
|
3841 if (!str) |
|
3842 return false; |
|
3843 |
|
3844 JSAutoByteString filename(cx, str); |
|
3845 if (!filename) |
|
3846 return false; |
|
3847 |
|
3848 if (args.length() > 1) { |
|
3849 JSString *opt = JS::ToString(cx, args[1]); |
|
3850 if (!opt) |
|
3851 return false; |
|
3852 bool match; |
|
3853 if (!JS_StringEqualsAscii(cx, opt, "binary", &match)) |
|
3854 return false; |
|
3855 if (match) { |
|
3856 JSObject *obj; |
|
3857 if (!(obj = FileAsTypedArray(cx, filename.ptr()))) |
|
3858 return false; |
|
3859 args.rval().setObject(*obj); |
|
3860 return true; |
|
3861 } |
|
3862 } |
|
3863 |
|
3864 if (!(str = FileAsString(cx, filename.ptr()))) |
|
3865 return false; |
|
3866 args.rval().setString(str); |
|
3867 return true; |
|
3868 } |
|
3869 |
|
3870 static bool |
|
3871 Snarf(JSContext *cx, unsigned argc, jsval *vp) |
|
3872 { |
|
3873 return ReadFile(cx, argc, vp, false); |
|
3874 } |
|
3875 |
|
3876 static bool |
|
3877 ReadRelativeToScript(JSContext *cx, unsigned argc, jsval *vp) |
|
3878 { |
|
3879 return ReadFile(cx, argc, vp, true); |
|
3880 } |
|
3881 |
|
3882 static bool |
|
3883 redirect(JSContext *cx, FILE* fp, HandleString relFilename) |
|
3884 { |
|
3885 RootedString filename(cx, ResolvePath(cx, relFilename, RootRelative)); |
|
3886 if (!filename) |
|
3887 return false; |
|
3888 JSAutoByteString filenameABS(cx, filename); |
|
3889 if (!filenameABS) |
|
3890 return false; |
|
3891 if (freopen(filenameABS.ptr(), "wb", fp) == nullptr) { |
|
3892 JS_ReportError(cx, "cannot redirect to %s: %s", filenameABS.ptr(), strerror(errno)); |
|
3893 return false; |
|
3894 } |
|
3895 return true; |
|
3896 } |
|
3897 |
|
3898 static bool |
|
3899 RedirectOutput(JSContext *cx, unsigned argc, jsval *vp) |
|
3900 { |
|
3901 CallArgs args = CallArgsFromVp(argc, vp); |
|
3902 |
|
3903 if (args.length() < 1 || args.length() > 2) { |
|
3904 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "redirect"); |
|
3905 return false; |
|
3906 } |
|
3907 |
|
3908 if (args[0].isString()) { |
|
3909 RootedString stdoutPath(cx, args[0].toString()); |
|
3910 if (!stdoutPath) |
|
3911 return false; |
|
3912 if (!redirect(cx, stdout, stdoutPath)) |
|
3913 return false; |
|
3914 } |
|
3915 |
|
3916 if (args.length() > 1 && args[1].isString()) { |
|
3917 RootedString stderrPath(cx, args[1].toString()); |
|
3918 if (!stderrPath) |
|
3919 return false; |
|
3920 if (!redirect(cx, stderr, stderrPath)) |
|
3921 return false; |
|
3922 } |
|
3923 |
|
3924 args.rval().setUndefined(); |
|
3925 return true; |
|
3926 } |
|
3927 |
|
3928 static bool |
|
3929 System(JSContext *cx, unsigned argc, jsval *vp) |
|
3930 { |
|
3931 CallArgs args = CallArgsFromVp(argc, vp); |
|
3932 |
|
3933 if (args.length() == 0) { |
|
3934 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, |
|
3935 "system"); |
|
3936 return false; |
|
3937 } |
|
3938 |
|
3939 JSString *str = JS::ToString(cx, args[0]); |
|
3940 if (!str) |
|
3941 return false; |
|
3942 |
|
3943 JSAutoByteString command(cx, str); |
|
3944 if (!command) |
|
3945 return false; |
|
3946 |
|
3947 int result = system(command.ptr()); |
|
3948 args.rval().setInt32(result); |
|
3949 return true; |
|
3950 } |
|
3951 |
|
3952 static int sArgc; |
|
3953 static char **sArgv; |
|
3954 |
|
3955 class AutoCStringVector |
|
3956 { |
|
3957 Vector<char *> argv_; |
|
3958 public: |
|
3959 AutoCStringVector(JSContext *cx) : argv_(cx) {} |
|
3960 ~AutoCStringVector() { |
|
3961 for (size_t i = 0; i < argv_.length(); i++) |
|
3962 js_free(argv_[i]); |
|
3963 } |
|
3964 bool append(char *arg) { |
|
3965 if (!argv_.append(arg)) { |
|
3966 js_free(arg); |
|
3967 return false; |
|
3968 } |
|
3969 return true; |
|
3970 } |
|
3971 char* const* get() const { |
|
3972 return argv_.begin(); |
|
3973 } |
|
3974 size_t length() const { |
|
3975 return argv_.length(); |
|
3976 } |
|
3977 char *operator[](size_t i) const { |
|
3978 return argv_[i]; |
|
3979 } |
|
3980 void replace(size_t i, char *arg) { |
|
3981 js_free(argv_[i]); |
|
3982 argv_[i] = arg; |
|
3983 } |
|
3984 char *back() const { |
|
3985 return argv_.back(); |
|
3986 } |
|
3987 void replaceBack(char *arg) { |
|
3988 js_free(argv_.back()); |
|
3989 argv_.back() = arg; |
|
3990 } |
|
3991 }; |
|
3992 |
|
3993 #if defined(XP_WIN) |
|
3994 static bool |
|
3995 EscapeForShell(AutoCStringVector &argv) |
|
3996 { |
|
3997 // Windows will break arguments in argv by various spaces, so we wrap each |
|
3998 // argument in quotes and escape quotes within. Even with quotes, \ will be |
|
3999 // treated like an escape character, so inflate each \ to \\. |
|
4000 |
|
4001 for (size_t i = 0; i < argv.length(); i++) { |
|
4002 if (!argv[i]) |
|
4003 continue; |
|
4004 |
|
4005 size_t newLen = 3; // quotes before and after and null-terminator |
|
4006 for (char *p = argv[i]; *p; p++) { |
|
4007 newLen++; |
|
4008 if (*p == '\"' || *p == '\\') |
|
4009 newLen++; |
|
4010 } |
|
4011 |
|
4012 char *escaped = (char *)js_malloc(newLen); |
|
4013 if (!escaped) |
|
4014 return false; |
|
4015 |
|
4016 char *src = argv[i]; |
|
4017 char *dst = escaped; |
|
4018 *dst++ = '\"'; |
|
4019 while (*src) { |
|
4020 if (*src == '\"' || *src == '\\') |
|
4021 *dst++ = '\\'; |
|
4022 *dst++ = *src++; |
|
4023 } |
|
4024 *dst++ = '\"'; |
|
4025 *dst++ = '\0'; |
|
4026 JS_ASSERT(escaped + newLen == dst); |
|
4027 |
|
4028 argv.replace(i, escaped); |
|
4029 } |
|
4030 return true; |
|
4031 } |
|
4032 #endif |
|
4033 |
|
4034 static Vector<const char*, 4, js::SystemAllocPolicy> sPropagatedFlags; |
|
4035 |
|
4036 #ifdef DEBUG |
|
4037 #if (defined(JS_CPU_X86) || defined(JS_CPU_X64)) && defined(JS_ION) |
|
4038 static bool |
|
4039 PropagateFlagToNestedShells(const char *flag) |
|
4040 { |
|
4041 return sPropagatedFlags.append(flag); |
|
4042 } |
|
4043 #endif |
|
4044 #endif |
|
4045 |
|
4046 static bool |
|
4047 NestedShell(JSContext *cx, unsigned argc, jsval *vp) |
|
4048 { |
|
4049 CallArgs args = CallArgsFromVp(argc, vp); |
|
4050 |
|
4051 AutoCStringVector argv(cx); |
|
4052 |
|
4053 // The first argument to the shell is its path, which we assume is our own |
|
4054 // argv[0]. |
|
4055 if (sArgc < 1) { |
|
4056 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_NESTED_FAIL); |
|
4057 return false; |
|
4058 } |
|
4059 if (!argv.append(strdup(sArgv[0]))) |
|
4060 return false; |
|
4061 |
|
4062 // Propagate selected flags from the current shell |
|
4063 for (unsigned i = 0; i < sPropagatedFlags.length(); i++) { |
|
4064 char *cstr = strdup(sPropagatedFlags[i]); |
|
4065 if (!cstr || !argv.append(cstr)) |
|
4066 return false; |
|
4067 } |
|
4068 |
|
4069 // The arguments to nestedShell are stringified and append to argv. |
|
4070 RootedString str(cx); |
|
4071 for (unsigned i = 0; i < args.length(); i++) { |
|
4072 str = ToString(cx, args[i]); |
|
4073 if (!str || !argv.append(JS_EncodeString(cx, str))) |
|
4074 return false; |
|
4075 |
|
4076 // As a special case, if the caller passes "--js-cache", replace that |
|
4077 // with "--js-cache=$(jsCacheDir)" |
|
4078 if (!strcmp(argv.back(), "--js-cache") && jsCacheDir) { |
|
4079 char *newArg = JS_smprintf("--js-cache=%s", jsCacheDir); |
|
4080 if (!newArg) |
|
4081 return false; |
|
4082 argv.replaceBack(newArg); |
|
4083 } |
|
4084 } |
|
4085 |
|
4086 // execv assumes argv is null-terminated |
|
4087 if (!argv.append(nullptr)) |
|
4088 return false; |
|
4089 |
|
4090 int status = 0; |
|
4091 #if defined(XP_WIN) |
|
4092 if (!EscapeForShell(argv)) |
|
4093 return false; |
|
4094 status = _spawnv(_P_WAIT, sArgv[0], argv.get()); |
|
4095 #else |
|
4096 pid_t pid = fork(); |
|
4097 switch (pid) { |
|
4098 case -1: |
|
4099 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_NESTED_FAIL); |
|
4100 return false; |
|
4101 case 0: |
|
4102 (void)execv(sArgv[0], argv.get()); |
|
4103 exit(-1); |
|
4104 default: { |
|
4105 while (waitpid(pid, &status, 0) < 0 && errno == EINTR) |
|
4106 continue; |
|
4107 break; |
|
4108 } |
|
4109 } |
|
4110 #endif |
|
4111 |
|
4112 if (status != 0) { |
|
4113 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_NESTED_FAIL); |
|
4114 return false; |
|
4115 } |
|
4116 |
|
4117 args.rval().setUndefined(); |
|
4118 return true; |
|
4119 } |
|
4120 |
|
4121 static bool |
|
4122 DecompileFunctionSomehow(JSContext *cx, unsigned argc, Value *vp, |
|
4123 JSString *(*decompiler)(JSContext *, HandleFunction, unsigned)) |
|
4124 { |
|
4125 CallArgs args = CallArgsFromVp(argc, vp); |
|
4126 if (args.length() < 1 || !args[0].isObject() || !args[0].toObject().is<JSFunction>()) { |
|
4127 args.rval().setUndefined(); |
|
4128 return true; |
|
4129 } |
|
4130 RootedFunction fun(cx, &args[0].toObject().as<JSFunction>()); |
|
4131 JSString *result = decompiler(cx, fun, 0); |
|
4132 if (!result) |
|
4133 return false; |
|
4134 args.rval().setString(result); |
|
4135 return true; |
|
4136 } |
|
4137 |
|
4138 static bool |
|
4139 DecompileBody(JSContext *cx, unsigned argc, Value *vp) |
|
4140 { |
|
4141 return DecompileFunctionSomehow(cx, argc, vp, JS_DecompileFunctionBody); |
|
4142 } |
|
4143 |
|
4144 static bool |
|
4145 DecompileFunction(JSContext *cx, unsigned argc, Value *vp) |
|
4146 { |
|
4147 return DecompileFunctionSomehow(cx, argc, vp, JS_DecompileFunction); |
|
4148 } |
|
4149 |
|
4150 static bool |
|
4151 DecompileThisScript(JSContext *cx, unsigned argc, Value *vp) |
|
4152 { |
|
4153 CallArgs args = CallArgsFromVp(argc, vp); |
|
4154 |
|
4155 NonBuiltinScriptFrameIter iter(cx); |
|
4156 if (iter.done()) { |
|
4157 args.rval().setString(cx->runtime()->emptyString); |
|
4158 return true; |
|
4159 } |
|
4160 |
|
4161 { |
|
4162 JSAutoCompartment ac(cx, iter.script()); |
|
4163 |
|
4164 RootedScript script(cx, iter.script()); |
|
4165 JSString *result = JS_DecompileScript(cx, script, "test", 0); |
|
4166 if (!result) |
|
4167 return false; |
|
4168 |
|
4169 args.rval().setString(result); |
|
4170 } |
|
4171 |
|
4172 return JS_WrapValue(cx, args.rval()); |
|
4173 } |
|
4174 |
|
4175 static bool |
|
4176 ThisFilename(JSContext *cx, unsigned argc, Value *vp) |
|
4177 { |
|
4178 CallArgs args = CallArgsFromVp(argc, vp); |
|
4179 |
|
4180 JS::AutoFilename filename; |
|
4181 if (!DescribeScriptedCaller(cx, &filename) || !filename.get()) { |
|
4182 args.rval().setString(cx->runtime()->emptyString); |
|
4183 return true; |
|
4184 } |
|
4185 |
|
4186 JSString *str = JS_NewStringCopyZ(cx, filename.get()); |
|
4187 if (!str) |
|
4188 return false; |
|
4189 |
|
4190 args.rval().setString(str); |
|
4191 return true; |
|
4192 } |
|
4193 |
|
4194 static bool |
|
4195 Wrap(JSContext *cx, unsigned argc, jsval *vp) |
|
4196 { |
|
4197 CallArgs args = CallArgsFromVp(argc, vp); |
|
4198 Value v = args.get(0); |
|
4199 if (JSVAL_IS_PRIMITIVE(v)) { |
|
4200 args.rval().set(v); |
|
4201 return true; |
|
4202 } |
|
4203 |
|
4204 RootedObject obj(cx, JSVAL_TO_OBJECT(v)); |
|
4205 JSObject *wrapped = Wrapper::New(cx, obj, &obj->global(), |
|
4206 &Wrapper::singleton); |
|
4207 if (!wrapped) |
|
4208 return false; |
|
4209 |
|
4210 args.rval().setObject(*wrapped); |
|
4211 return true; |
|
4212 } |
|
4213 |
|
4214 static bool |
|
4215 WrapWithProto(JSContext *cx, unsigned argc, jsval *vp) |
|
4216 { |
|
4217 CallArgs args = CallArgsFromVp(argc, vp); |
|
4218 Value obj = UndefinedValue(), proto = UndefinedValue(); |
|
4219 if (args.length() == 2) { |
|
4220 obj = args[0]; |
|
4221 proto = args[1]; |
|
4222 } |
|
4223 if (!obj.isObject() || !proto.isObjectOrNull()) { |
|
4224 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, |
|
4225 "wrapWithProto"); |
|
4226 return false; |
|
4227 } |
|
4228 |
|
4229 WrapperOptions options(cx); |
|
4230 options.setProto(proto.toObjectOrNull()); |
|
4231 options.selectDefaultClass(obj.toObject().isCallable()); |
|
4232 JSObject *wrapped = Wrapper::New(cx, &obj.toObject(), &obj.toObject().global(), |
|
4233 &Wrapper::singletonWithPrototype, &options); |
|
4234 if (!wrapped) |
|
4235 return false; |
|
4236 |
|
4237 args.rval().setObject(*wrapped); |
|
4238 return true; |
|
4239 } |
|
4240 |
|
4241 static bool |
|
4242 NewGlobal(JSContext *cx, unsigned argc, jsval *vp) |
|
4243 { |
|
4244 JSPrincipals *principals = nullptr; |
|
4245 JS::CompartmentOptions options; |
|
4246 options.setVersion(JSVERSION_LATEST); |
|
4247 |
|
4248 CallArgs args = CallArgsFromVp(argc, vp); |
|
4249 if (args.length() == 1 && args[0].isObject()) { |
|
4250 RootedObject opts(cx, &args[0].toObject()); |
|
4251 RootedValue v(cx); |
|
4252 |
|
4253 if (!JS_GetProperty(cx, opts, "sameZoneAs", &v)) |
|
4254 return false; |
|
4255 if (v.isObject()) |
|
4256 options.setSameZoneAs(UncheckedUnwrap(&v.toObject())); |
|
4257 |
|
4258 if (!JS_GetProperty(cx, opts, "invisibleToDebugger", &v)) |
|
4259 return false; |
|
4260 if (v.isBoolean()) |
|
4261 options.setInvisibleToDebugger(v.toBoolean()); |
|
4262 |
|
4263 if (!JS_GetProperty(cx, opts, "principal", &v)) |
|
4264 return false; |
|
4265 if (!v.isUndefined()) { |
|
4266 uint32_t bits; |
|
4267 if (!ToUint32(cx, v, &bits)) |
|
4268 return false; |
|
4269 principals = cx->new_<ShellPrincipals>(bits); |
|
4270 if (!principals) |
|
4271 return false; |
|
4272 JS_HoldPrincipals(principals); |
|
4273 } |
|
4274 } |
|
4275 |
|
4276 RootedObject global(cx, NewGlobalObject(cx, options, principals)); |
|
4277 if (principals) |
|
4278 JS_DropPrincipals(cx->runtime(), principals); |
|
4279 if (!global) |
|
4280 return false; |
|
4281 |
|
4282 if (!JS_WrapObject(cx, &global)) |
|
4283 return false; |
|
4284 |
|
4285 args.rval().setObject(*global); |
|
4286 return true; |
|
4287 } |
|
4288 |
|
4289 static bool |
|
4290 EnableStackWalkingAssertion(JSContext *cx, unsigned argc, jsval *vp) |
|
4291 { |
|
4292 CallArgs args = CallArgsFromVp(argc, vp); |
|
4293 if (!args.get(0).isBoolean()) { |
|
4294 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, |
|
4295 "enableStackWalkingAssertion"); |
|
4296 return false; |
|
4297 } |
|
4298 |
|
4299 #ifdef DEBUG |
|
4300 cx->stackIterAssertionEnabled = args[0].toBoolean(); |
|
4301 #endif |
|
4302 |
|
4303 args.rval().setUndefined(); |
|
4304 return true; |
|
4305 } |
|
4306 |
|
4307 static bool |
|
4308 GetMaxArgs(JSContext *cx, unsigned argc, jsval *vp) |
|
4309 { |
|
4310 CallArgs args = CallArgsFromVp(argc, vp); |
|
4311 args.rval().setInt32(ARGS_LENGTH_MAX); |
|
4312 return true; |
|
4313 } |
|
4314 |
|
4315 static bool |
|
4316 ObjectEmulatingUndefined(JSContext *cx, unsigned argc, jsval *vp) |
|
4317 { |
|
4318 CallArgs args = CallArgsFromVp(argc, vp); |
|
4319 |
|
4320 static const JSClass cls = { |
|
4321 "ObjectEmulatingUndefined", |
|
4322 JSCLASS_EMULATES_UNDEFINED, |
|
4323 JS_PropertyStub, |
|
4324 JS_DeletePropertyStub, |
|
4325 JS_PropertyStub, |
|
4326 JS_StrictPropertyStub, |
|
4327 JS_EnumerateStub, |
|
4328 JS_ResolveStub, |
|
4329 JS_ConvertStub |
|
4330 }; |
|
4331 |
|
4332 RootedObject obj(cx, JS_NewObject(cx, &cls, JS::NullPtr(), JS::NullPtr())); |
|
4333 if (!obj) |
|
4334 return false; |
|
4335 args.rval().setObject(*obj); |
|
4336 return true; |
|
4337 } |
|
4338 |
|
4339 static bool |
|
4340 GetSelfHostedValue(JSContext *cx, unsigned argc, jsval *vp) |
|
4341 { |
|
4342 CallArgs args = CallArgsFromVp(argc, vp); |
|
4343 |
|
4344 if (args.length() != 1 || !args[0].isString()) { |
|
4345 JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, |
|
4346 "getSelfHostedValue"); |
|
4347 return false; |
|
4348 } |
|
4349 RootedAtom srcAtom(cx, ToAtom<CanGC>(cx, args[0])); |
|
4350 if (!srcAtom) |
|
4351 return false; |
|
4352 RootedPropertyName srcName(cx, srcAtom->asPropertyName()); |
|
4353 return cx->runtime()->cloneSelfHostedValue(cx, srcName, args.rval()); |
|
4354 } |
|
4355 |
|
4356 class ShellSourceHook: public SourceHook { |
|
4357 // The function we should call to lazily retrieve source code. |
|
4358 PersistentRootedFunction fun; |
|
4359 |
|
4360 public: |
|
4361 ShellSourceHook(JSContext *cx, JSFunction &fun) : fun(cx, &fun) {} |
|
4362 |
|
4363 bool load(JSContext *cx, const char *filename, jschar **src, size_t *length) { |
|
4364 RootedString str(cx, JS_NewStringCopyZ(cx, filename)); |
|
4365 if (!str) |
|
4366 return false; |
|
4367 RootedValue filenameValue(cx, StringValue(str)); |
|
4368 |
|
4369 RootedValue result(cx); |
|
4370 if (!Call(cx, UndefinedHandleValue, fun, filenameValue, &result)) |
|
4371 return false; |
|
4372 |
|
4373 str = JS::ToString(cx, result); |
|
4374 if (!str) |
|
4375 return false; |
|
4376 |
|
4377 *length = JS_GetStringLength(str); |
|
4378 *src = cx->pod_malloc<jschar>(*length); |
|
4379 if (!*src) |
|
4380 return false; |
|
4381 |
|
4382 const jschar *chars = JS_GetStringCharsZ(cx, str); |
|
4383 if (!chars) |
|
4384 return false; |
|
4385 |
|
4386 PodCopy(*src, chars, *length); |
|
4387 return true; |
|
4388 } |
|
4389 }; |
|
4390 |
|
4391 static bool |
|
4392 WithSourceHook(JSContext *cx, unsigned argc, jsval *vp) |
|
4393 { |
|
4394 CallArgs args = CallArgsFromVp(argc, vp); |
|
4395 RootedObject callee(cx, &args.callee()); |
|
4396 |
|
4397 if (args.length() != 2) { |
|
4398 ReportUsageError(cx, callee, "Wrong number of arguments."); |
|
4399 return false; |
|
4400 } |
|
4401 |
|
4402 if (!args[0].isObject() || !args[0].toObject().is<JSFunction>() |
|
4403 || !args[1].isObject() || !args[1].toObject().is<JSFunction>()) { |
|
4404 ReportUsageError(cx, callee, "First and second arguments must be functions."); |
|
4405 return false; |
|
4406 } |
|
4407 |
|
4408 ShellSourceHook *hook = new ShellSourceHook(cx, args[0].toObject().as<JSFunction>()); |
|
4409 if (!hook) |
|
4410 return false; |
|
4411 |
|
4412 SourceHook *savedHook = js::ForgetSourceHook(cx->runtime()); |
|
4413 js::SetSourceHook(cx->runtime(), hook); |
|
4414 RootedObject fun(cx, &args[1].toObject()); |
|
4415 bool result = Call(cx, UndefinedHandleValue, fun, JS::HandleValueArray::empty(), args.rval()); |
|
4416 js::SetSourceHook(cx->runtime(), savedHook); |
|
4417 return result; |
|
4418 } |
|
4419 |
|
4420 static bool |
|
4421 IsCachingEnabled(JSContext *cx, unsigned argc, Value *vp) |
|
4422 { |
|
4423 CallArgs args = CallArgsFromVp(argc, vp); |
|
4424 args.rval().setBoolean(jsCachingEnabled && jsCacheAsmJSPath != nullptr); |
|
4425 return true; |
|
4426 } |
|
4427 |
|
4428 static bool |
|
4429 SetCachingEnabled(JSContext *cx, unsigned argc, Value *vp) |
|
4430 { |
|
4431 CallArgs args = CallArgsFromVp(argc, vp); |
|
4432 jsCachingEnabled = ToBoolean(args.get(0)); |
|
4433 args.rval().setUndefined(); |
|
4434 return true; |
|
4435 } |
|
4436 |
|
4437 static void |
|
4438 PrintProfilerEvents_Callback(const char *msg) |
|
4439 { |
|
4440 fprintf(stderr, "PROFILER EVENT: %s\n", msg); |
|
4441 } |
|
4442 |
|
4443 static bool |
|
4444 PrintProfilerEvents(JSContext *cx, unsigned argc, Value *vp) |
|
4445 { |
|
4446 CallArgs args = CallArgsFromVp(argc, vp); |
|
4447 if (cx->runtime()->spsProfiler.enabled()) |
|
4448 js::RegisterRuntimeProfilingEventMarker(cx->runtime(), &PrintProfilerEvents_Callback); |
|
4449 args.rval().setUndefined(); |
|
4450 return true; |
|
4451 } |
|
4452 |
|
4453 static const JSFunctionSpecWithHelp shell_functions[] = { |
|
4454 JS_FN_HELP("version", Version, 0, 0, |
|
4455 "version([number])", |
|
4456 " Get or force a script compilation version number."), |
|
4457 |
|
4458 JS_FN_HELP("options", Options, 0, 0, |
|
4459 "options([option ...])", |
|
4460 " Get or toggle JavaScript options."), |
|
4461 |
|
4462 JS_FN_HELP("load", Load, 1, 0, |
|
4463 "load(['foo.js' ...])", |
|
4464 " Load files named by string arguments. Filename is relative to the\n" |
|
4465 " current working directory."), |
|
4466 |
|
4467 JS_FN_HELP("loadRelativeToScript", LoadScriptRelativeToScript, 1, 0, |
|
4468 "loadRelativeToScript(['foo.js' ...])", |
|
4469 " Load files named by string arguments. Filename is relative to the\n" |
|
4470 " calling script."), |
|
4471 |
|
4472 JS_FN_HELP("evaluate", Evaluate, 2, 0, |
|
4473 "evaluate(code[, options])", |
|
4474 " Evaluate code as though it were the contents of a file.\n" |
|
4475 " options is an optional object that may have these properties:\n" |
|
4476 " compileAndGo: use the compile-and-go compiler option (default: true)\n" |
|
4477 " noScriptRval: use the no-script-rval compiler option (default: false)\n" |
|
4478 " fileName: filename for error messages and debug info\n" |
|
4479 " lineNumber: starting line number for error messages and debug info\n" |
|
4480 " global: global in which to execute the code\n" |
|
4481 " newContext: if true, create and use a new cx (default: false)\n" |
|
4482 " saveFrameChain: if true, save the frame chain before evaluating code\n" |
|
4483 " and restore it afterwards\n" |
|
4484 " catchTermination: if true, catch termination (failure without\n" |
|
4485 " an exception value, as for slow scripts or out-of-memory)\n" |
|
4486 " and return 'terminated'\n" |
|
4487 " element: if present with value |v|, convert |v| to an object |o| and\n" |
|
4488 " mark the source as being attached to the DOM element |o|. If the\n" |
|
4489 " property is omitted or |v| is null, don't attribute the source to\n" |
|
4490 " any DOM element.\n" |
|
4491 " elementAttributeName: if present and not undefined, the name of\n" |
|
4492 " property of 'element' that holds this code. This is what\n" |
|
4493 " Debugger.Source.prototype.elementAttributeName returns.\n" |
|
4494 " sourceMapURL: if present with value |v|, convert |v| to a string, and\n" |
|
4495 " provide that as the code's source map URL. If omitted, attach no\n" |
|
4496 " source map URL to the code (although the code may provide one itself,\n" |
|
4497 " via a //#sourceMappingURL comment).\n" |
|
4498 " sourceIsLazy: if present and true, indicates that, after compilation, \n" |
|
4499 "script source should not be cached by the JS engine and should be \n" |
|
4500 "lazily loaded from the embedding as-needed.\n" |
|
4501 " loadBytecode: if true, and if the source is a CacheEntryObject,\n" |
|
4502 " the bytecode would be loaded and decoded from the cache entry instead\n" |
|
4503 " of being parsed, then it would be executed as usual.\n" |
|
4504 " saveBytecode: if true, and if the source is a CacheEntryObject,\n" |
|
4505 " the bytecode would be encoded and saved into the cache entry after\n" |
|
4506 " the script execution.\n" |
|
4507 " assertEqBytecode: if true, and if both loadBytecode and saveBytecode are \n" |
|
4508 " true, then the loaded bytecode and the encoded bytecode are compared.\n" |
|
4509 " and an assertion is raised if they differ.\n" |
|
4510 ), |
|
4511 |
|
4512 JS_FN_HELP("run", Run, 1, 0, |
|
4513 "run('foo.js')", |
|
4514 " Run the file named by the first argument, returning the number of\n" |
|
4515 " of milliseconds spent compiling and executing it."), |
|
4516 |
|
4517 JS_FN_HELP("readline", ReadLine, 0, 0, |
|
4518 "readline()", |
|
4519 " Read a single line from stdin."), |
|
4520 |
|
4521 JS_FN_HELP("print", Print, 0, 0, |
|
4522 "print([exp ...])", |
|
4523 " Evaluate and print expressions to stdout."), |
|
4524 |
|
4525 JS_FN_HELP("printErr", PrintErr, 0, 0, |
|
4526 "printErr([exp ...])", |
|
4527 " Evaluate and print expressions to stderr."), |
|
4528 |
|
4529 JS_FN_HELP("putstr", PutStr, 0, 0, |
|
4530 "putstr([exp])", |
|
4531 " Evaluate and print expression without newline."), |
|
4532 |
|
4533 JS_FN_HELP("dateNow", Now, 0, 0, |
|
4534 "dateNow()", |
|
4535 " Return the current time with sub-ms precision."), |
|
4536 |
|
4537 JS_FN_HELP("help", Help, 0, 0, |
|
4538 "help([name ...])", |
|
4539 " Display usage and help messages."), |
|
4540 |
|
4541 JS_FN_HELP("quit", Quit, 0, 0, |
|
4542 "quit()", |
|
4543 " Quit the shell."), |
|
4544 |
|
4545 JS_FN_HELP("assertEq", AssertEq, 2, 0, |
|
4546 "assertEq(actual, expected[, msg])", |
|
4547 " Throw if the first two arguments are not the same (both +0 or both -0,\n" |
|
4548 " both NaN, or non-zero and ===)."), |
|
4549 |
|
4550 JS_FN_HELP("setDebug", SetDebug, 1, 0, |
|
4551 "setDebug(debug)", |
|
4552 " Set debug mode."), |
|
4553 |
|
4554 JS_FN_HELP("setDebuggerHandler", SetDebuggerHandler, 1, 0, |
|
4555 "setDebuggerHandler(f)", |
|
4556 " Set handler for debugger keyword to f."), |
|
4557 |
|
4558 JS_FN_HELP("throwError", ThrowError, 0, 0, |
|
4559 "throwError()", |
|
4560 " Throw an error from JS_ReportError."), |
|
4561 |
|
4562 #ifdef DEBUG |
|
4563 JS_FN_HELP("disassemble", DisassembleToString, 1, 0, |
|
4564 "disassemble([fun])", |
|
4565 " Return the disassembly for the given function."), |
|
4566 |
|
4567 JS_FN_HELP("dis", Disassemble, 1, 0, |
|
4568 "dis([fun])", |
|
4569 " Disassemble functions into bytecodes."), |
|
4570 |
|
4571 JS_FN_HELP("disfile", DisassFile, 1, 0, |
|
4572 "disfile('foo.js')", |
|
4573 " Disassemble script file into bytecodes.\n" |
|
4574 " dis and disfile take these options as preceeding string arguments:\n" |
|
4575 " \"-r\" (disassemble recursively)\n" |
|
4576 " \"-l\" (show line numbers)"), |
|
4577 |
|
4578 JS_FN_HELP("dissrc", DisassWithSrc, 1, 0, |
|
4579 "dissrc([fun])", |
|
4580 " Disassemble functions with source lines."), |
|
4581 |
|
4582 JS_FN_HELP("dumpObject", DumpObject, 1, 0, |
|
4583 "dumpObject()", |
|
4584 " Dump an internal representation of an object."), |
|
4585 |
|
4586 JS_FN_HELP("notes", Notes, 1, 0, |
|
4587 "notes([fun])", |
|
4588 " Show source notes for functions."), |
|
4589 |
|
4590 JS_FN_HELP("findReferences", FindReferences, 1, 0, |
|
4591 "findReferences(target)", |
|
4592 " Walk the entire heap, looking for references to |target|, and return a\n" |
|
4593 " \"references object\" describing what we found.\n" |
|
4594 "\n" |
|
4595 " Each property of the references object describes one kind of reference. The\n" |
|
4596 " property's name is the label supplied to MarkObject, JS_CALL_TRACER, or what\n" |
|
4597 " have you, prefixed with \"edge: \" to avoid collisions with system properties\n" |
|
4598 " (like \"toString\" and \"__proto__\"). The property's value is an array of things\n" |
|
4599 " that refer to |thing| via that kind of reference. Ordinary references from\n" |
|
4600 " one object to another are named after the property name (with the \"edge: \"\n" |
|
4601 " prefix).\n" |
|
4602 "\n" |
|
4603 " Garbage collection roots appear as references from 'null'. We use the name\n" |
|
4604 " given to the root (with the \"edge: \" prefix) as the name of the reference.\n" |
|
4605 "\n" |
|
4606 " Note that the references object does record references from objects that are\n" |
|
4607 " only reachable via |thing| itself, not just the references reachable\n" |
|
4608 " themselves from roots that keep |thing| from being collected. (We could make\n" |
|
4609 " this distinction if it is useful.)\n" |
|
4610 "\n" |
|
4611 " If any references are found by the conservative scanner, the references\n" |
|
4612 " object will have a property named \"edge: machine stack\"; the referrers will\n" |
|
4613 " be 'null', because they are roots."), |
|
4614 |
|
4615 #endif |
|
4616 JS_FN_HELP("build", BuildDate, 0, 0, |
|
4617 "build()", |
|
4618 " Show build date and time."), |
|
4619 |
|
4620 JS_FN_HELP("intern", Intern, 1, 0, |
|
4621 "intern(str)", |
|
4622 " Internalize str in the atom table."), |
|
4623 |
|
4624 JS_FN_HELP("getpda", GetPDA, 1, 0, |
|
4625 "getpda(obj)", |
|
4626 " Get the property descriptors for obj."), |
|
4627 |
|
4628 JS_FN_HELP("getslx", GetSLX, 1, 0, |
|
4629 "getslx(obj)", |
|
4630 " Get script line extent."), |
|
4631 |
|
4632 JS_FN_HELP("evalcx", EvalInContext, 1, 0, |
|
4633 "evalcx(s[, o])", |
|
4634 " Evaluate s in optional sandbox object o.\n" |
|
4635 " if (s == '' && !o) return new o with eager standard classes\n" |
|
4636 " if (s == 'lazy' && !o) return new o with lazy standard classes"), |
|
4637 |
|
4638 JS_FN_HELP("evalInFrame", EvalInFrame, 2, 0, |
|
4639 "evalInFrame(n,str,save)", |
|
4640 " Evaluate 'str' in the nth up frame.\n" |
|
4641 " If 'save' (default false), save the frame chain."), |
|
4642 |
|
4643 #ifdef JS_THREADSAFE |
|
4644 JS_FN_HELP("evalInWorker", EvalInWorker, 1, 0, |
|
4645 "evalInWorker(str)", |
|
4646 " Evaluate 'str' in a separate thread with its own runtime.\n"), |
|
4647 #endif |
|
4648 |
|
4649 JS_FN_HELP("shapeOf", ShapeOf, 1, 0, |
|
4650 "shapeOf(obj)", |
|
4651 " Get the shape of obj (an implementation detail)."), |
|
4652 |
|
4653 JS_FN_HELP("resolver", Resolver, 1, 0, |
|
4654 "resolver(src[, proto])", |
|
4655 " Create object with resolve hook that copies properties\n" |
|
4656 " from src. If proto is omitted, use Object.prototype."), |
|
4657 |
|
4658 #ifdef DEBUG |
|
4659 JS_FN_HELP("arrayInfo", js_ArrayInfo, 1, 0, |
|
4660 "arrayInfo(a1, a2, ...)", |
|
4661 " Report statistics about arrays."), |
|
4662 #endif |
|
4663 |
|
4664 #ifdef JS_THREADSAFE |
|
4665 JS_FN_HELP("sleep", Sleep_fn, 1, 0, |
|
4666 "sleep(dt)", |
|
4667 " Sleep for dt seconds."), |
|
4668 #endif |
|
4669 |
|
4670 JS_FN_HELP("snarf", Snarf, 1, 0, |
|
4671 "snarf(filename, [\"binary\"])", |
|
4672 " Read filename into returned string. Filename is relative to the current\n" |
|
4673 " working directory."), |
|
4674 |
|
4675 JS_FN_HELP("read", Snarf, 1, 0, |
|
4676 "read(filename, [\"binary\"])", |
|
4677 " Synonym for snarf."), |
|
4678 |
|
4679 JS_FN_HELP("readRelativeToScript", ReadRelativeToScript, 1, 0, |
|
4680 "readRelativeToScript(filename, [\"binary\"])", |
|
4681 " Read filename into returned string. Filename is relative to the directory\n" |
|
4682 " containing the current script."), |
|
4683 |
|
4684 JS_FN_HELP("compile", Compile, 1, 0, |
|
4685 "compile(code)", |
|
4686 " Compiles a string to bytecode, potentially throwing."), |
|
4687 |
|
4688 JS_FN_HELP("parse", Parse, 1, 0, |
|
4689 "parse(code)", |
|
4690 " Parses a string, potentially throwing."), |
|
4691 |
|
4692 JS_FN_HELP("syntaxParse", SyntaxParse, 1, 0, |
|
4693 "syntaxParse(code)", |
|
4694 " Check the syntax of a string, returning success value"), |
|
4695 |
|
4696 #ifdef JS_THREADSAFE |
|
4697 JS_FN_HELP("offThreadCompileScript", OffThreadCompileScript, 1, 0, |
|
4698 "offThreadCompileScript(code[, options])", |
|
4699 " Compile |code| on a helper thread. To wait for the compilation to finish\n" |
|
4700 " and run the code, call |runOffThreadScript|. If present, |options| may\n" |
|
4701 " have properties saying how the code should be compiled:\n" |
|
4702 " noScriptRval: use the no-script-rval compiler option (default: false)\n" |
|
4703 " fileName: filename for error messages and debug info\n" |
|
4704 " lineNumber: starting line number for error messages and debug info\n" |
|
4705 " element: if present with value |v|, convert |v| to an object |o| and\n" |
|
4706 " mark the source as being attached to the DOM element |o|. If the\n" |
|
4707 " property is omitted or |v| is null, don't attribute the source to\n" |
|
4708 " any DOM element.\n" |
|
4709 " elementAttributeName: if present and not undefined, the name of\n" |
|
4710 " property of 'element' that holds this code. This is what\n" |
|
4711 " Debugger.Source.prototype.elementAttributeName returns.\n"), |
|
4712 |
|
4713 JS_FN_HELP("runOffThreadScript", runOffThreadScript, 0, 0, |
|
4714 "runOffThreadScript()", |
|
4715 " Wait for off-thread compilation to complete. If an error occurred,\n" |
|
4716 " throw the appropriate exception; otherwise, run the script and return\n" |
|
4717 " its value."), |
|
4718 |
|
4719 #endif |
|
4720 |
|
4721 JS_FN_HELP("timeout", Timeout, 1, 0, |
|
4722 "timeout([seconds], [func])", |
|
4723 " Get/Set the limit in seconds for the execution time for the current context.\n" |
|
4724 " A negative value (default) means that the execution time is unlimited.\n" |
|
4725 " If a second argument is provided, it will be invoked when the timer elapses.\n" |
|
4726 " Calling this function will replace any callback set by |setInterruptCallback|.\n"), |
|
4727 |
|
4728 JS_FN_HELP("interruptIf", InterruptIf, 1, 0, |
|
4729 "interruptIf(cond)", |
|
4730 " Requests interrupt callback if cond is true. If a callback function is set via\n" |
|
4731 " |timeout| or |setInterruptCallback|, it will be called. No-op otherwise."), |
|
4732 |
|
4733 JS_FN_HELP("setInterruptCallback", SetInterruptCallback, 1, 0, |
|
4734 "setInterruptCallback(func)", |
|
4735 " Sets func as the interrupt callback function.\n" |
|
4736 " Calling this function will replace any callback set by |timeout|.\n"), |
|
4737 |
|
4738 JS_FN_HELP("elapsed", Elapsed, 0, 0, |
|
4739 "elapsed()", |
|
4740 " Execution time elapsed for the current context."), |
|
4741 |
|
4742 JS_FN_HELP("decompileFunction", DecompileFunction, 1, 0, |
|
4743 "decompileFunction(func)", |
|
4744 " Decompile a function."), |
|
4745 |
|
4746 JS_FN_HELP("decompileBody", DecompileBody, 1, 0, |
|
4747 "decompileBody(func)", |
|
4748 " Decompile a function's body."), |
|
4749 |
|
4750 JS_FN_HELP("decompileThis", DecompileThisScript, 0, 0, |
|
4751 "decompileThis()", |
|
4752 " Decompile the currently executing script."), |
|
4753 |
|
4754 JS_FN_HELP("thisFilename", ThisFilename, 0, 0, |
|
4755 "thisFilename()", |
|
4756 " Return the filename of the current script"), |
|
4757 |
|
4758 JS_FN_HELP("wrap", Wrap, 1, 0, |
|
4759 "wrap(obj)", |
|
4760 " Wrap an object into a noop wrapper."), |
|
4761 |
|
4762 JS_FN_HELP("wrapWithProto", WrapWithProto, 2, 0, |
|
4763 "wrapWithProto(obj)", |
|
4764 " Wrap an object into a noop wrapper with prototype semantics."), |
|
4765 |
|
4766 JS_FN_HELP("newGlobal", NewGlobal, 1, 0, |
|
4767 "newGlobal([options])", |
|
4768 " Return a new global object in a new compartment. If options\n" |
|
4769 " is given, it may have any of the following properties:\n" |
|
4770 " sameZoneAs: the compartment will be in the same zone as the given object (defaults to a new zone)\n" |
|
4771 " invisibleToDebugger: the global will be invisible to the debugger (default false)\n" |
|
4772 " principal: if present, its value converted to a number must be an\n" |
|
4773 " integer that fits in 32 bits; use that as the new compartment's\n" |
|
4774 " principal. Shell principals are toys, meant only for testing; one\n" |
|
4775 " shell principal subsumes another if its set bits are a superset of\n" |
|
4776 " the other's. Thus, a principal of 0 subsumes nothing, while a\n" |
|
4777 " principals of ~0 subsumes all other principals. The absence of a\n" |
|
4778 " principal is treated as if its bits were 0xffff, for subsumption\n" |
|
4779 " purposes. If this property is omitted, supply no principal."), |
|
4780 |
|
4781 JS_FN_HELP("createMappedArrayBuffer", CreateMappedArrayBuffer, 1, 0, |
|
4782 "createMappedArrayBuffer(filename, [offset, [size]])", |
|
4783 " Create an array buffer that mmaps the given file."), |
|
4784 |
|
4785 JS_FN_HELP("enableStackWalkingAssertion", EnableStackWalkingAssertion, 1, 0, |
|
4786 "enableStackWalkingAssertion(enabled)", |
|
4787 " Enables or disables a particularly expensive assertion in stack-walking\n" |
|
4788 " code. If your test isn't ridiculously thorough, such that performing this\n" |
|
4789 " assertion increases test duration by an order of magnitude, you shouldn't\n" |
|
4790 " use this."), |
|
4791 |
|
4792 JS_FN_HELP("getMaxArgs", GetMaxArgs, 0, 0, |
|
4793 "getMaxArgs()", |
|
4794 " Return the maximum number of supported args for a call."), |
|
4795 |
|
4796 JS_FN_HELP("objectEmulatingUndefined", ObjectEmulatingUndefined, 0, 0, |
|
4797 "objectEmulatingUndefined()", |
|
4798 " Return a new object obj for which typeof obj === \"undefined\", obj == null\n" |
|
4799 " and obj == undefined (and vice versa for !=), and ToBoolean(obj) === false.\n"), |
|
4800 |
|
4801 JS_FN_HELP("isCachingEnabled", IsCachingEnabled, 0, 0, |
|
4802 "isCachingEnabled()", |
|
4803 " Return whether JS caching is enabled."), |
|
4804 |
|
4805 JS_FN_HELP("setCachingEnabled", SetCachingEnabled, 1, 0, |
|
4806 "setCachingEnabled(b)", |
|
4807 " Enable or disable JS caching."), |
|
4808 |
|
4809 JS_FN_HELP("cacheEntry", CacheEntry, 1, 0, |
|
4810 "cacheEntry(code)", |
|
4811 " Return a new opaque object which emulates a cache entry of a script. This\n" |
|
4812 " object encapsulates the code and its cached content. The cache entry is filled\n" |
|
4813 " and read by the \"evaluate\" function by using it in place of the source, and\n" |
|
4814 " by setting \"saveBytecode\" and \"loadBytecode\" options."), |
|
4815 |
|
4816 JS_FN_HELP("printProfilerEvents", PrintProfilerEvents, 0, 0, |
|
4817 "printProfilerEvents()", |
|
4818 " Register a callback with the profiler that prints javascript profiler events\n" |
|
4819 " to stderr. Callback is only registered if profiling is enabled."), |
|
4820 |
|
4821 JS_FS_HELP_END |
|
4822 }; |
|
4823 |
|
4824 static const JSFunctionSpecWithHelp fuzzing_unsafe_functions[] = { |
|
4825 JS_FN_HELP("clone", Clone, 1, 0, |
|
4826 "clone(fun[, scope])", |
|
4827 " Clone function object."), |
|
4828 |
|
4829 JS_FN_HELP("getSelfHostedValue", GetSelfHostedValue, 1, 0, |
|
4830 "getSelfHostedValue()", |
|
4831 " Get a self-hosted value by its name. Note that these values don't get \n" |
|
4832 " cached, so repeatedly getting the same value creates multiple distinct clones."), |
|
4833 |
|
4834 #ifdef DEBUG |
|
4835 JS_FN_HELP("dumpHeap", DumpHeap, 0, 0, |
|
4836 "dumpHeap([fileName[, start[, toFind[, maxDepth[, toIgnore]]]]])", |
|
4837 " Interface to JS_DumpHeap with output sent to file."), |
|
4838 #endif |
|
4839 |
|
4840 JS_FN_HELP("parent", Parent, 1, 0, |
|
4841 "parent(obj)", |
|
4842 " Returns the parent of obj."), |
|
4843 |
|
4844 JS_FN_HELP("line2pc", LineToPC, 0, 0, |
|
4845 "line2pc([fun,] line)", |
|
4846 " Map line number to PC."), |
|
4847 |
|
4848 JS_FN_HELP("pc2line", PCToLine, 0, 0, |
|
4849 "pc2line(fun[, pc])", |
|
4850 " Map PC to line number."), |
|
4851 |
|
4852 JS_FN_HELP("redirect", RedirectOutput, 2, 0, |
|
4853 "redirect(stdoutFilename[, stderrFilename])", |
|
4854 " Redirect stdout and/or stderr to the named file. Pass undefined to avoid\n" |
|
4855 " redirecting. Filenames are relative to the current working directory."), |
|
4856 |
|
4857 JS_FN_HELP("setThrowHook", SetThrowHook, 1, 0, |
|
4858 "setThrowHook(f)", |
|
4859 " Set throw hook to f."), |
|
4860 |
|
4861 JS_FN_HELP("system", System, 1, 0, |
|
4862 "system(command)", |
|
4863 " Execute command on the current host, returning result code."), |
|
4864 |
|
4865 JS_FN_HELP("nestedShell", NestedShell, 0, 0, |
|
4866 "nestedShell(shellArgs...)", |
|
4867 " Execute the given code in a new JS shell process, passing this nested shell\n" |
|
4868 " the arguments passed to nestedShell. argv[0] of the nested shell will be argv[0]\n" |
|
4869 " of the current shell (which is assumed to be the actual path to the shell.\n" |
|
4870 " arguments[0] (of the call to nestedShell) will be argv[1], arguments[1] will\n" |
|
4871 " be argv[2], etc."), |
|
4872 |
|
4873 JS_FN_HELP("trap", Trap, 3, 0, |
|
4874 "trap([fun, [pc,]] exp)", |
|
4875 " Trap bytecode execution."), |
|
4876 |
|
4877 JS_FN_HELP("assertFloat32", testingFunc_assertFloat32, 2, 0, |
|
4878 "assertFloat32(value, isFloat32)", |
|
4879 " In IonMonkey only, asserts that value has (resp. hasn't) the MIRType_Float32 if isFloat32 is true (resp. false)."), |
|
4880 |
|
4881 JS_FN_HELP("untrap", Untrap, 2, 0, |
|
4882 "untrap(fun[, pc])", |
|
4883 " Remove a trap."), |
|
4884 |
|
4885 JS_FN_HELP("withSourceHook", WithSourceHook, 1, 0, |
|
4886 "withSourceHook(hook, fun)", |
|
4887 " Set this JS runtime's lazy source retrieval hook (that is, the hook\n" |
|
4888 " used to find sources compiled with |CompileOptions::LAZY_SOURCE|) to\n" |
|
4889 " |hook|; call |fun| with no arguments; and then restore the runtime's\n" |
|
4890 " original hook. Return or throw whatever |fun| did. |hook| gets\n" |
|
4891 " passed the requested code's URL, and should return a string.\n" |
|
4892 "\n" |
|
4893 " Notes:\n" |
|
4894 "\n" |
|
4895 " 1) SpiderMonkey may assert if the returned code isn't close enough\n" |
|
4896 " to the script's real code, so this function is not fuzzer-safe.\n" |
|
4897 "\n" |
|
4898 " 2) The runtime can have only one source retrieval hook active at a\n" |
|
4899 " time. If |fun| is not careful, |hook| could be asked to retrieve the\n" |
|
4900 " source code for compilations that occurred long before it was set,\n" |
|
4901 " and that it knows nothing about. The reverse applies as well: the\n" |
|
4902 " original hook, that we reinstate after the call to |fun| completes,\n" |
|
4903 " might be asked for the source code of compilations that |fun|\n" |
|
4904 " performed, and which, presumably, only |hook| knows how to find.\n"), |
|
4905 |
|
4906 JS_FS_HELP_END |
|
4907 }; |
|
4908 |
|
4909 #ifdef MOZ_PROFILING |
|
4910 # define PROFILING_FUNCTION_COUNT 5 |
|
4911 # ifdef MOZ_CALLGRIND |
|
4912 # define CALLGRIND_FUNCTION_COUNT 3 |
|
4913 # else |
|
4914 # define CALLGRIND_FUNCTION_COUNT 0 |
|
4915 # endif |
|
4916 # ifdef MOZ_VTUNE |
|
4917 # define VTUNE_FUNCTION_COUNT 4 |
|
4918 # else |
|
4919 # define VTUNE_FUNCTION_COUNT 0 |
|
4920 # endif |
|
4921 # define EXTERNAL_FUNCTION_COUNT (PROFILING_FUNCTION_COUNT + CALLGRIND_FUNCTION_COUNT + VTUNE_FUNCTION_COUNT) |
|
4922 #else |
|
4923 # define EXTERNAL_FUNCTION_COUNT 0 |
|
4924 #endif |
|
4925 |
|
4926 #undef PROFILING_FUNCTION_COUNT |
|
4927 #undef CALLGRIND_FUNCTION_COUNT |
|
4928 #undef VTUNE_FUNCTION_COUNT |
|
4929 #undef EXTERNAL_FUNCTION_COUNT |
|
4930 |
|
4931 static bool |
|
4932 PrintHelpString(JSContext *cx, jsval v) |
|
4933 { |
|
4934 JSString *str = JSVAL_TO_STRING(v); |
|
4935 JS::Anchor<JSString *> a_str(str); |
|
4936 const jschar *chars = JS_GetStringCharsZ(cx, str); |
|
4937 if (!chars) |
|
4938 return false; |
|
4939 |
|
4940 for (const jschar *p = chars; *p; p++) |
|
4941 fprintf(gOutFile, "%c", char(*p)); |
|
4942 |
|
4943 fprintf(gOutFile, "\n"); |
|
4944 |
|
4945 return true; |
|
4946 } |
|
4947 |
|
4948 static bool |
|
4949 PrintHelp(JSContext *cx, HandleObject obj) |
|
4950 { |
|
4951 RootedValue usage(cx); |
|
4952 if (!JS_LookupProperty(cx, obj, "usage", &usage)) |
|
4953 return false; |
|
4954 RootedValue help(cx); |
|
4955 if (!JS_LookupProperty(cx, obj, "help", &help)) |
|
4956 return false; |
|
4957 |
|
4958 if (JSVAL_IS_VOID(usage) || JSVAL_IS_VOID(help)) |
|
4959 return true; |
|
4960 |
|
4961 return PrintHelpString(cx, usage) && PrintHelpString(cx, help); |
|
4962 } |
|
4963 |
|
4964 static bool |
|
4965 Help(JSContext *cx, unsigned argc, jsval *vp) |
|
4966 { |
|
4967 CallArgs args = CallArgsFromVp(argc, vp); |
|
4968 fprintf(gOutFile, "%s\n", JS_GetImplementationVersion()); |
|
4969 |
|
4970 RootedObject obj(cx); |
|
4971 if (args.length() == 0) { |
|
4972 RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); |
|
4973 AutoIdArray ida(cx, JS_Enumerate(cx, global)); |
|
4974 if (!ida) |
|
4975 return false; |
|
4976 |
|
4977 for (size_t i = 0; i < ida.length(); i++) { |
|
4978 RootedValue v(cx); |
|
4979 RootedId id(cx, ida[i]); |
|
4980 if (!JS_LookupPropertyById(cx, global, id, &v)) |
|
4981 return false; |
|
4982 if (JSVAL_IS_PRIMITIVE(v)) { |
|
4983 JS_ReportError(cx, "primitive arg"); |
|
4984 return false; |
|
4985 } |
|
4986 obj = JSVAL_TO_OBJECT(v); |
|
4987 if (!PrintHelp(cx, obj)) |
|
4988 return false; |
|
4989 } |
|
4990 } else { |
|
4991 for (unsigned i = 0; i < args.length(); i++) { |
|
4992 if (args[i].isPrimitive()) { |
|
4993 JS_ReportError(cx, "primitive arg"); |
|
4994 return false; |
|
4995 } |
|
4996 obj = args[i].toObjectOrNull(); |
|
4997 if (!PrintHelp(cx, obj)) |
|
4998 return false; |
|
4999 } |
|
5000 } |
|
5001 |
|
5002 args.rval().setUndefined(); |
|
5003 return true; |
|
5004 } |
|
5005 |
|
5006 static const JSErrorFormatString jsShell_ErrorFormatString[JSShellErr_Limit] = { |
|
5007 #define MSG_DEF(name, number, count, exception, format) \ |
|
5008 { format, count, JSEXN_ERR } , |
|
5009 #include "jsshell.msg" |
|
5010 #undef MSG_DEF |
|
5011 }; |
|
5012 |
|
5013 static const JSErrorFormatString * |
|
5014 my_GetErrorMessage(void *userRef, const char *locale, const unsigned errorNumber) |
|
5015 { |
|
5016 if (errorNumber == 0 || errorNumber >= JSShellErr_Limit) |
|
5017 return nullptr; |
|
5018 |
|
5019 return &jsShell_ErrorFormatString[errorNumber]; |
|
5020 } |
|
5021 |
|
5022 static void |
|
5023 my_ErrorReporter(JSContext *cx, const char *message, JSErrorReport *report) |
|
5024 { |
|
5025 gGotError = PrintError(cx, gErrFile, message, report, reportWarnings); |
|
5026 if (!JSREPORT_IS_WARNING(report->flags)) { |
|
5027 if (report->errorNumber == JSMSG_OUT_OF_MEMORY) { |
|
5028 gExitCode = EXITCODE_OUT_OF_MEMORY; |
|
5029 } else { |
|
5030 gExitCode = EXITCODE_RUNTIME_ERROR; |
|
5031 } |
|
5032 } |
|
5033 } |
|
5034 |
|
5035 static void |
|
5036 my_OOMCallback(JSContext *cx) |
|
5037 { |
|
5038 // If a script is running, the engine is about to throw the string "out of |
|
5039 // memory", which may or may not be caught. Otherwise the engine will just |
|
5040 // unwind and return null/false, with no exception set. |
|
5041 if (!JS_IsRunning(cx)) |
|
5042 gGotError = true; |
|
5043 } |
|
5044 |
|
5045 static bool |
|
5046 global_enumerate(JSContext *cx, HandleObject obj) |
|
5047 { |
|
5048 #ifdef LAZY_STANDARD_CLASSES |
|
5049 return JS_EnumerateStandardClasses(cx, obj); |
|
5050 #else |
|
5051 return true; |
|
5052 #endif |
|
5053 } |
|
5054 |
|
5055 static bool |
|
5056 global_resolve(JSContext *cx, HandleObject obj, HandleId id, MutableHandleObject objp) |
|
5057 { |
|
5058 #ifdef LAZY_STANDARD_CLASSES |
|
5059 bool resolved; |
|
5060 |
|
5061 if (!JS_ResolveStandardClass(cx, obj, id, &resolved)) |
|
5062 return false; |
|
5063 if (resolved) { |
|
5064 objp.set(obj); |
|
5065 return true; |
|
5066 } |
|
5067 #endif |
|
5068 |
|
5069 return true; |
|
5070 } |
|
5071 |
|
5072 static const JSClass global_class = { |
|
5073 "global", JSCLASS_NEW_RESOLVE | JSCLASS_GLOBAL_FLAGS, |
|
5074 JS_PropertyStub, JS_DeletePropertyStub, |
|
5075 JS_PropertyStub, JS_StrictPropertyStub, |
|
5076 global_enumerate, (JSResolveOp) global_resolve, |
|
5077 JS_ConvertStub, nullptr, |
|
5078 nullptr, nullptr, nullptr, |
|
5079 JS_GlobalObjectTraceHook |
|
5080 }; |
|
5081 |
|
5082 static bool |
|
5083 env_setProperty(JSContext *cx, HandleObject obj, HandleId id, bool strict, MutableHandleValue vp) |
|
5084 { |
|
5085 /* XXX porting may be easy, but these don't seem to supply setenv by default */ |
|
5086 #if !defined SOLARIS |
|
5087 int rv; |
|
5088 |
|
5089 RootedValue idvalue(cx, IdToValue(id)); |
|
5090 RootedString idstring(cx, ToString(cx, idvalue)); |
|
5091 JSAutoByteString idstr; |
|
5092 if (!idstr.encodeLatin1(cx, idstring)) |
|
5093 return false; |
|
5094 |
|
5095 RootedString value(cx, ToString(cx, vp)); |
|
5096 if (!value) |
|
5097 return false; |
|
5098 JSAutoByteString valstr; |
|
5099 if (!valstr.encodeLatin1(cx, value)) |
|
5100 return false; |
|
5101 |
|
5102 #if defined XP_WIN || defined HPUX || defined OSF1 |
|
5103 { |
|
5104 char *waste = JS_smprintf("%s=%s", idstr.ptr(), valstr.ptr()); |
|
5105 if (!waste) { |
|
5106 JS_ReportOutOfMemory(cx); |
|
5107 return false; |
|
5108 } |
|
5109 rv = putenv(waste); |
|
5110 #ifdef XP_WIN |
|
5111 /* |
|
5112 * HPUX9 at least still has the bad old non-copying putenv. |
|
5113 * |
|
5114 * Per mail from <s.shanmuganathan@digital.com>, OSF1 also has a putenv |
|
5115 * that will crash if you pass it an auto char array (so it must place |
|
5116 * its argument directly in the char *environ[] array). |
|
5117 */ |
|
5118 JS_smprintf_free(waste); |
|
5119 #endif |
|
5120 } |
|
5121 #else |
|
5122 rv = setenv(idstr.ptr(), valstr.ptr(), 1); |
|
5123 #endif |
|
5124 if (rv < 0) { |
|
5125 JS_ReportError(cx, "can't set env variable %s to %s", idstr.ptr(), valstr.ptr()); |
|
5126 return false; |
|
5127 } |
|
5128 vp.set(StringValue(value)); |
|
5129 #endif /* !defined SOLARIS */ |
|
5130 return true; |
|
5131 } |
|
5132 |
|
5133 static bool |
|
5134 env_enumerate(JSContext *cx, HandleObject obj) |
|
5135 { |
|
5136 static bool reflected; |
|
5137 char **evp, *name, *value; |
|
5138 RootedString valstr(cx); |
|
5139 bool ok; |
|
5140 |
|
5141 if (reflected) |
|
5142 return true; |
|
5143 |
|
5144 for (evp = (char **)JS_GetPrivate(obj); (name = *evp) != nullptr; evp++) { |
|
5145 value = strchr(name, '='); |
|
5146 if (!value) |
|
5147 continue; |
|
5148 *value++ = '\0'; |
|
5149 valstr = JS_NewStringCopyZ(cx, value); |
|
5150 ok = valstr && JS_DefineProperty(cx, obj, name, valstr, JSPROP_ENUMERATE); |
|
5151 value[-1] = '='; |
|
5152 if (!ok) |
|
5153 return false; |
|
5154 } |
|
5155 |
|
5156 reflected = true; |
|
5157 return true; |
|
5158 } |
|
5159 |
|
5160 static bool |
|
5161 env_resolve(JSContext *cx, HandleObject obj, HandleId id, MutableHandleObject objp) |
|
5162 { |
|
5163 RootedValue idvalue(cx, IdToValue(id)); |
|
5164 RootedString idstring(cx, ToString(cx, idvalue)); |
|
5165 JSAutoByteString idstr; |
|
5166 if (!idstr.encodeLatin1(cx, idstring)) |
|
5167 return false; |
|
5168 |
|
5169 const char *name = idstr.ptr(); |
|
5170 const char *value = getenv(name); |
|
5171 if (value) { |
|
5172 RootedString valstr(cx, JS_NewStringCopyZ(cx, value)); |
|
5173 if (!valstr) |
|
5174 return false; |
|
5175 if (!JS_DefineProperty(cx, obj, name, valstr, JSPROP_ENUMERATE)) |
|
5176 return false; |
|
5177 objp.set(obj); |
|
5178 } |
|
5179 return true; |
|
5180 } |
|
5181 |
|
5182 static const JSClass env_class = { |
|
5183 "environment", JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE, |
|
5184 JS_PropertyStub, JS_DeletePropertyStub, |
|
5185 JS_PropertyStub, env_setProperty, |
|
5186 env_enumerate, (JSResolveOp) env_resolve, |
|
5187 JS_ConvertStub |
|
5188 }; |
|
5189 |
|
5190 /* |
|
5191 * Define a FakeDOMObject constructor. It returns an object with a getter, |
|
5192 * setter and method with attached JitInfo. This object can be used to test |
|
5193 * IonMonkey DOM optimizations in the shell. |
|
5194 */ |
|
5195 static uint32_t DOM_OBJECT_SLOT = 0; |
|
5196 |
|
5197 static bool |
|
5198 dom_genericGetter(JSContext* cx, unsigned argc, JS::Value *vp); |
|
5199 |
|
5200 static bool |
|
5201 dom_genericSetter(JSContext* cx, unsigned argc, JS::Value *vp); |
|
5202 |
|
5203 static bool |
|
5204 dom_genericMethod(JSContext *cx, unsigned argc, JS::Value *vp); |
|
5205 |
|
5206 #ifdef DEBUG |
|
5207 static const JSClass *GetDomClass(); |
|
5208 #endif |
|
5209 |
|
5210 static bool |
|
5211 dom_get_x(JSContext* cx, HandleObject obj, void *self, JSJitGetterCallArgs args) |
|
5212 { |
|
5213 JS_ASSERT(JS_GetClass(obj) == GetDomClass()); |
|
5214 JS_ASSERT(self == (void *)0x1234); |
|
5215 args.rval().set(JS_NumberValue(double(3.14))); |
|
5216 return true; |
|
5217 } |
|
5218 |
|
5219 static bool |
|
5220 dom_set_x(JSContext* cx, HandleObject obj, void *self, JSJitSetterCallArgs args) |
|
5221 { |
|
5222 JS_ASSERT(JS_GetClass(obj) == GetDomClass()); |
|
5223 JS_ASSERT(self == (void *)0x1234); |
|
5224 return true; |
|
5225 } |
|
5226 |
|
5227 static bool |
|
5228 dom_doFoo(JSContext* cx, HandleObject obj, void *self, const JSJitMethodCallArgs& args) |
|
5229 { |
|
5230 JS_ASSERT(JS_GetClass(obj) == GetDomClass()); |
|
5231 JS_ASSERT(self == (void *)0x1234); |
|
5232 |
|
5233 /* Just return args.length(). */ |
|
5234 args.rval().setInt32(args.length()); |
|
5235 return true; |
|
5236 } |
|
5237 |
|
5238 static const JSJitInfo dom_x_getterinfo = { |
|
5239 { (JSJitGetterOp)dom_get_x }, |
|
5240 0, /* protoID */ |
|
5241 0, /* depth */ |
|
5242 JSJitInfo::AliasNone, /* aliasSet */ |
|
5243 JSJitInfo::Getter, |
|
5244 JSVAL_TYPE_UNKNOWN, /* returnType */ |
|
5245 true, /* isInfallible. False in setters. */ |
|
5246 true, /* isMovable */ |
|
5247 false, /* isInSlot */ |
|
5248 false, /* isTypedMethod */ |
|
5249 0 /* slotIndex */ |
|
5250 }; |
|
5251 |
|
5252 static const JSJitInfo dom_x_setterinfo = { |
|
5253 { (JSJitGetterOp)dom_set_x }, |
|
5254 0, /* protoID */ |
|
5255 0, /* depth */ |
|
5256 JSJitInfo::Setter, |
|
5257 JSJitInfo::AliasEverything, /* aliasSet */ |
|
5258 JSVAL_TYPE_UNKNOWN, /* returnType */ |
|
5259 false, /* isInfallible. False in setters. */ |
|
5260 false, /* isMovable. */ |
|
5261 false, /* isInSlot */ |
|
5262 false, /* isTypedMethod */ |
|
5263 0 /* slotIndex */ |
|
5264 }; |
|
5265 |
|
5266 static const JSJitInfo doFoo_methodinfo = { |
|
5267 { (JSJitGetterOp)dom_doFoo }, |
|
5268 0, /* protoID */ |
|
5269 0, /* depth */ |
|
5270 JSJitInfo::Method, |
|
5271 JSJitInfo::AliasEverything, /* aliasSet */ |
|
5272 JSVAL_TYPE_UNKNOWN, /* returnType */ |
|
5273 false, /* isInfallible. False in setters. */ |
|
5274 false, /* isMovable */ |
|
5275 false, /* isInSlot */ |
|
5276 false, /* isTypedMethod */ |
|
5277 0 /* slotIndex */ |
|
5278 }; |
|
5279 |
|
5280 static const JSPropertySpec dom_props[] = { |
|
5281 {"x", |
|
5282 JSPROP_SHARED | JSPROP_ENUMERATE | JSPROP_NATIVE_ACCESSORS, |
|
5283 { { (JSPropertyOp)dom_genericGetter, &dom_x_getterinfo } }, |
|
5284 { { (JSStrictPropertyOp)dom_genericSetter, &dom_x_setterinfo } } |
|
5285 }, |
|
5286 JS_PS_END |
|
5287 }; |
|
5288 |
|
5289 static const JSFunctionSpec dom_methods[] = { |
|
5290 JS_FNINFO("doFoo", dom_genericMethod, &doFoo_methodinfo, 3, JSPROP_ENUMERATE), |
|
5291 JS_FS_END |
|
5292 }; |
|
5293 |
|
5294 static const JSClass dom_class = { |
|
5295 "FakeDOMObject", JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(2), |
|
5296 JS_PropertyStub, /* addProperty */ |
|
5297 JS_DeletePropertyStub, /* delProperty */ |
|
5298 JS_PropertyStub, /* getProperty */ |
|
5299 JS_StrictPropertyStub, /* setProperty */ |
|
5300 JS_EnumerateStub, |
|
5301 JS_ResolveStub, |
|
5302 JS_ConvertStub, |
|
5303 nullptr, /* finalize */ |
|
5304 nullptr, /* call */ |
|
5305 nullptr, /* hasInstance */ |
|
5306 nullptr, /* construct */ |
|
5307 nullptr, /* trace */ |
|
5308 JSCLASS_NO_INTERNAL_MEMBERS |
|
5309 }; |
|
5310 |
|
5311 #ifdef DEBUG |
|
5312 static const JSClass *GetDomClass() { |
|
5313 return &dom_class; |
|
5314 } |
|
5315 #endif |
|
5316 |
|
5317 static bool |
|
5318 dom_genericGetter(JSContext *cx, unsigned argc, JS::Value *vp) |
|
5319 { |
|
5320 CallArgs args = CallArgsFromVp(argc, vp); |
|
5321 RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); |
|
5322 if (!obj) |
|
5323 return false; |
|
5324 |
|
5325 if (JS_GetClass(obj) != &dom_class) { |
|
5326 args.rval().set(UndefinedValue()); |
|
5327 return true; |
|
5328 } |
|
5329 |
|
5330 JS::Value val = js::GetReservedSlot(obj, DOM_OBJECT_SLOT); |
|
5331 |
|
5332 const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev()); |
|
5333 MOZ_ASSERT(info->type() == JSJitInfo::Getter); |
|
5334 JSJitGetterOp getter = info->getter; |
|
5335 return getter(cx, obj, val.toPrivate(), JSJitGetterCallArgs(args)); |
|
5336 } |
|
5337 |
|
5338 static bool |
|
5339 dom_genericSetter(JSContext* cx, unsigned argc, JS::Value* vp) |
|
5340 { |
|
5341 CallArgs args = CallArgsFromVp(argc, vp); |
|
5342 RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); |
|
5343 if (!obj) |
|
5344 return false; |
|
5345 |
|
5346 JS_ASSERT(args.length() == 1); |
|
5347 |
|
5348 if (JS_GetClass(obj) != &dom_class) { |
|
5349 args.rval().set(UndefinedValue()); |
|
5350 return true; |
|
5351 } |
|
5352 |
|
5353 JS::Value val = js::GetReservedSlot(obj, DOM_OBJECT_SLOT); |
|
5354 |
|
5355 const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev()); |
|
5356 MOZ_ASSERT(info->type() == JSJitInfo::Setter); |
|
5357 JSJitSetterOp setter = info->setter; |
|
5358 if (!setter(cx, obj, val.toPrivate(), JSJitSetterCallArgs(args))) |
|
5359 return false; |
|
5360 args.rval().set(UndefinedValue()); |
|
5361 return true; |
|
5362 } |
|
5363 |
|
5364 static bool |
|
5365 dom_genericMethod(JSContext* cx, unsigned argc, JS::Value *vp) |
|
5366 { |
|
5367 CallArgs args = CallArgsFromVp(argc, vp); |
|
5368 RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); |
|
5369 if (!obj) |
|
5370 return false; |
|
5371 |
|
5372 if (JS_GetClass(obj) != &dom_class) { |
|
5373 args.rval().set(UndefinedValue()); |
|
5374 return true; |
|
5375 } |
|
5376 |
|
5377 JS::Value val = js::GetReservedSlot(obj, DOM_OBJECT_SLOT); |
|
5378 |
|
5379 const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev()); |
|
5380 MOZ_ASSERT(info->type() == JSJitInfo::Method); |
|
5381 JSJitMethodOp method = info->method; |
|
5382 return method(cx, obj, val.toPrivate(), JSJitMethodCallArgs(args)); |
|
5383 } |
|
5384 |
|
5385 static void |
|
5386 InitDOMObject(HandleObject obj) |
|
5387 { |
|
5388 /* Fow now just initialize to a constant we can check. */ |
|
5389 SetReservedSlot(obj, DOM_OBJECT_SLOT, PRIVATE_TO_JSVAL((void *)0x1234)); |
|
5390 } |
|
5391 |
|
5392 static bool |
|
5393 dom_constructor(JSContext* cx, unsigned argc, JS::Value *vp) |
|
5394 { |
|
5395 CallArgs args = CallArgsFromVp(argc, vp); |
|
5396 |
|
5397 RootedObject callee(cx, &args.callee()); |
|
5398 RootedValue protov(cx); |
|
5399 if (!JSObject::getProperty(cx, callee, callee, cx->names().prototype, &protov)) |
|
5400 return false; |
|
5401 |
|
5402 if (!protov.isObject()) { |
|
5403 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_PROTOTYPE, "FakeDOMObject"); |
|
5404 return false; |
|
5405 } |
|
5406 |
|
5407 RootedObject proto(cx, &protov.toObject()); |
|
5408 RootedObject domObj(cx, JS_NewObject(cx, &dom_class, proto, JS::NullPtr())); |
|
5409 if (!domObj) |
|
5410 return false; |
|
5411 |
|
5412 InitDOMObject(domObj); |
|
5413 |
|
5414 args.rval().setObject(*domObj); |
|
5415 return true; |
|
5416 } |
|
5417 |
|
5418 static bool |
|
5419 InstanceClassHasProtoAtDepth(JSObject *protoObject, uint32_t protoID, uint32_t depth) |
|
5420 { |
|
5421 /* There's only a single (fake) DOM object in the shell, so just return true. */ |
|
5422 return true; |
|
5423 } |
|
5424 |
|
5425 class ScopedFileDesc |
|
5426 { |
|
5427 intptr_t fd_; |
|
5428 public: |
|
5429 enum LockType { READ_LOCK, WRITE_LOCK }; |
|
5430 ScopedFileDesc(int fd, LockType lockType) |
|
5431 : fd_(fd) |
|
5432 { |
|
5433 if (fd == -1) |
|
5434 return; |
|
5435 if (!jsCacheOpened.compareExchange(false, true)) { |
|
5436 close(fd_); |
|
5437 fd_ = -1; |
|
5438 return; |
|
5439 } |
|
5440 } |
|
5441 ~ScopedFileDesc() { |
|
5442 if (fd_ == -1) |
|
5443 return; |
|
5444 JS_ASSERT(jsCacheOpened == true); |
|
5445 jsCacheOpened = false; |
|
5446 close(fd_); |
|
5447 } |
|
5448 operator intptr_t() const { |
|
5449 return fd_; |
|
5450 } |
|
5451 intptr_t forget() { |
|
5452 intptr_t ret = fd_; |
|
5453 fd_ = -1; |
|
5454 return ret; |
|
5455 } |
|
5456 }; |
|
5457 |
|
5458 // To guard against corrupted cache files generated by previous crashes, write |
|
5459 // asmJSCacheCookie to the first uint32_t of the file only after the file is |
|
5460 // fully serialized and flushed to disk. |
|
5461 static const uint32_t asmJSCacheCookie = 0xabbadaba; |
|
5462 |
|
5463 static bool |
|
5464 ShellOpenAsmJSCacheEntryForRead(HandleObject global, const jschar *begin, const jschar *limit, |
|
5465 size_t *serializedSizeOut, const uint8_t **memoryOut, |
|
5466 intptr_t *handleOut) |
|
5467 { |
|
5468 if (!jsCachingEnabled || !jsCacheAsmJSPath) |
|
5469 return false; |
|
5470 |
|
5471 ScopedFileDesc fd(open(jsCacheAsmJSPath, O_RDWR), ScopedFileDesc::READ_LOCK); |
|
5472 if (fd == -1) |
|
5473 return false; |
|
5474 |
|
5475 // Get the size and make sure we can dereference at least one uint32_t. |
|
5476 off_t off = lseek(fd, 0, SEEK_END); |
|
5477 if (off == -1 || off < (off_t)sizeof(uint32_t)) |
|
5478 return false; |
|
5479 |
|
5480 // Map the file into memory. |
|
5481 void *memory; |
|
5482 #ifdef XP_WIN |
|
5483 HANDLE fdOsHandle = (HANDLE)_get_osfhandle(fd); |
|
5484 HANDLE fileMapping = CreateFileMapping(fdOsHandle, nullptr, PAGE_READWRITE, 0, 0, nullptr); |
|
5485 if (!fileMapping) |
|
5486 return false; |
|
5487 |
|
5488 memory = MapViewOfFile(fileMapping, FILE_MAP_READ, 0, 0, 0); |
|
5489 CloseHandle(fileMapping); |
|
5490 if (!memory) |
|
5491 return false; |
|
5492 #else |
|
5493 memory = mmap(nullptr, off, PROT_READ, MAP_SHARED, fd, 0); |
|
5494 if (memory == MAP_FAILED) |
|
5495 return false; |
|
5496 #endif |
|
5497 |
|
5498 // Perform check described by asmJSCacheCookie comment. |
|
5499 if (*(uint32_t *)memory != asmJSCacheCookie) { |
|
5500 #ifdef XP_WIN |
|
5501 UnmapViewOfFile(memory); |
|
5502 #else |
|
5503 munmap(memory, off); |
|
5504 #endif |
|
5505 return false; |
|
5506 } |
|
5507 |
|
5508 // The embedding added the cookie so strip it off of the buffer returned to |
|
5509 // the JS engine. |
|
5510 *serializedSizeOut = off - sizeof(uint32_t); |
|
5511 *memoryOut = (uint8_t *)memory + sizeof(uint32_t); |
|
5512 *handleOut = fd.forget(); |
|
5513 return true; |
|
5514 } |
|
5515 |
|
5516 static void |
|
5517 ShellCloseAsmJSCacheEntryForRead(HandleObject global, size_t serializedSize, const uint8_t *memory, |
|
5518 intptr_t handle) |
|
5519 { |
|
5520 // Undo the cookie adjustment done when opening the file. |
|
5521 memory -= sizeof(uint32_t); |
|
5522 serializedSize += sizeof(uint32_t); |
|
5523 |
|
5524 // Release the memory mapping and file. |
|
5525 #ifdef XP_WIN |
|
5526 UnmapViewOfFile(const_cast<uint8_t*>(memory)); |
|
5527 #else |
|
5528 munmap(const_cast<uint8_t*>(memory), serializedSize); |
|
5529 #endif |
|
5530 |
|
5531 JS_ASSERT(jsCacheOpened == true); |
|
5532 jsCacheOpened = false; |
|
5533 close(handle); |
|
5534 } |
|
5535 |
|
5536 static bool |
|
5537 ShellOpenAsmJSCacheEntryForWrite(HandleObject global, bool installed, |
|
5538 const jschar *begin, const jschar *end, |
|
5539 size_t serializedSize, uint8_t **memoryOut, intptr_t *handleOut) |
|
5540 { |
|
5541 if (!jsCachingEnabled || !jsCacheAsmJSPath) |
|
5542 return false; |
|
5543 |
|
5544 // Create the cache directory if it doesn't already exist. |
|
5545 struct stat dirStat; |
|
5546 if (stat(jsCacheDir, &dirStat) == 0) { |
|
5547 if (!(dirStat.st_mode & S_IFDIR)) |
|
5548 return false; |
|
5549 } else { |
|
5550 #ifdef XP_WIN |
|
5551 if (mkdir(jsCacheDir) != 0) |
|
5552 return false; |
|
5553 #else |
|
5554 if (mkdir(jsCacheDir, 0777) != 0) |
|
5555 return false; |
|
5556 #endif |
|
5557 } |
|
5558 |
|
5559 ScopedFileDesc fd(open(jsCacheAsmJSPath, O_CREAT|O_RDWR, 0660), ScopedFileDesc::WRITE_LOCK); |
|
5560 if (fd == -1) |
|
5561 return false; |
|
5562 |
|
5563 // Include extra space for the asmJSCacheCookie. |
|
5564 serializedSize += sizeof(uint32_t); |
|
5565 |
|
5566 // Resize the file to the appropriate size after zeroing their contents. |
|
5567 #ifdef XP_WIN |
|
5568 if (chsize(fd, 0)) |
|
5569 return false; |
|
5570 if (chsize(fd, serializedSize)) |
|
5571 return false; |
|
5572 #else |
|
5573 if (ftruncate(fd, 0)) |
|
5574 return false; |
|
5575 if (ftruncate(fd, serializedSize)) |
|
5576 return false; |
|
5577 #endif |
|
5578 |
|
5579 // Map the file into memory. |
|
5580 void *memory; |
|
5581 #ifdef XP_WIN |
|
5582 HANDLE fdOsHandle = (HANDLE)_get_osfhandle(fd); |
|
5583 HANDLE fileMapping = CreateFileMapping(fdOsHandle, nullptr, PAGE_READWRITE, 0, 0, nullptr); |
|
5584 if (!fileMapping) |
|
5585 return false; |
|
5586 |
|
5587 memory = MapViewOfFile(fileMapping, FILE_MAP_WRITE, 0, 0, 0); |
|
5588 CloseHandle(fileMapping); |
|
5589 if (!memory) |
|
5590 return false; |
|
5591 #else |
|
5592 memory = mmap(nullptr, serializedSize, PROT_WRITE, MAP_SHARED, fd, 0); |
|
5593 if (memory == MAP_FAILED) |
|
5594 return false; |
|
5595 #endif |
|
5596 |
|
5597 // The embedding added the cookie so strip it off of the buffer returned to |
|
5598 // the JS engine. The asmJSCacheCookie will be written on close, below. |
|
5599 JS_ASSERT(*(uint32_t *)memory == 0); |
|
5600 *memoryOut = (uint8_t *)memory + sizeof(uint32_t); |
|
5601 *handleOut = fd.forget(); |
|
5602 return true; |
|
5603 } |
|
5604 |
|
5605 static void |
|
5606 ShellCloseAsmJSCacheEntryForWrite(HandleObject global, size_t serializedSize, uint8_t *memory, |
|
5607 intptr_t handle) |
|
5608 { |
|
5609 // Undo the cookie adjustment done when opening the file. |
|
5610 memory -= sizeof(uint32_t); |
|
5611 serializedSize += sizeof(uint32_t); |
|
5612 |
|
5613 // Write the magic cookie value after flushing the entire cache entry. |
|
5614 #ifdef XP_WIN |
|
5615 FlushViewOfFile(memory, serializedSize); |
|
5616 FlushFileBuffers(HANDLE(_get_osfhandle(handle))); |
|
5617 #else |
|
5618 msync(memory, serializedSize, MS_SYNC); |
|
5619 #endif |
|
5620 |
|
5621 JS_ASSERT(*(uint32_t *)memory == 0); |
|
5622 *(uint32_t *)memory = asmJSCacheCookie; |
|
5623 |
|
5624 // Free the memory mapping and file. |
|
5625 #ifdef XP_WIN |
|
5626 UnmapViewOfFile(const_cast<uint8_t*>(memory)); |
|
5627 #else |
|
5628 munmap(memory, serializedSize); |
|
5629 #endif |
|
5630 |
|
5631 JS_ASSERT(jsCacheOpened == true); |
|
5632 jsCacheOpened = false; |
|
5633 close(handle); |
|
5634 } |
|
5635 |
|
5636 static bool |
|
5637 ShellBuildId(JS::BuildIdCharVector *buildId) |
|
5638 { |
|
5639 // The browser embeds the date into the buildid and the buildid is embedded |
|
5640 // in the binary, so every 'make' necessarily builds a new firefox binary. |
|
5641 // Fortunately, the actual firefox executable is tiny -- all the code is in |
|
5642 // libxul.so and other shared modules -- so this isn't a big deal. Not so |
|
5643 // for the statically-linked JS shell. To avoid recompmiling js.cpp and |
|
5644 // re-linking 'js' on every 'make', we use a constant buildid and rely on |
|
5645 // the shell user to manually clear the cache (deleting the dir passed to |
|
5646 // --js-cache) between cache-breaking updates. Note: jit_tests.py does this |
|
5647 // on every run). |
|
5648 const char buildid[] = "JS-shell"; |
|
5649 return buildId->append(buildid, sizeof(buildid)); |
|
5650 } |
|
5651 |
|
5652 static JS::AsmJSCacheOps asmJSCacheOps = { |
|
5653 ShellOpenAsmJSCacheEntryForRead, |
|
5654 ShellCloseAsmJSCacheEntryForRead, |
|
5655 ShellOpenAsmJSCacheEntryForWrite, |
|
5656 ShellCloseAsmJSCacheEntryForWrite, |
|
5657 ShellBuildId |
|
5658 }; |
|
5659 |
|
5660 /* |
|
5661 * Avoid a reentrancy hazard. |
|
5662 * |
|
5663 * The non-JS_THREADSAFE shell uses a signal handler to implement timeout(). |
|
5664 * The JS engine is not really reentrant, but JS_RequestInterruptCallback |
|
5665 * is mostly safe--the only danger is that we might interrupt JS_NewContext or |
|
5666 * JS_DestroyContext while the context list is being modified. Therefore we |
|
5667 * disable the signal handler around calls to those functions. |
|
5668 */ |
|
5669 #ifdef JS_THREADSAFE |
|
5670 # define WITH_SIGNALS_DISABLED(x) x |
|
5671 #else |
|
5672 # define WITH_SIGNALS_DISABLED(x) \ |
|
5673 JS_BEGIN_MACRO \ |
|
5674 ScheduleWatchdog(gRuntime, -1); \ |
|
5675 x; \ |
|
5676 ScheduleWatchdog(gRuntime, gTimeoutInterval); \ |
|
5677 JS_END_MACRO |
|
5678 #endif |
|
5679 |
|
5680 static JSContext * |
|
5681 NewContext(JSRuntime *rt) |
|
5682 { |
|
5683 JSContext *cx; |
|
5684 WITH_SIGNALS_DISABLED(cx = JS_NewContext(rt, gStackChunkSize)); |
|
5685 if (!cx) |
|
5686 return nullptr; |
|
5687 |
|
5688 JSShellContextData *data = NewContextData(); |
|
5689 if (!data) { |
|
5690 DestroyContext(cx, false); |
|
5691 return nullptr; |
|
5692 } |
|
5693 |
|
5694 JS_SetContextPrivate(cx, data); |
|
5695 JS_SetErrorReporter(cx, my_ErrorReporter); |
|
5696 return cx; |
|
5697 } |
|
5698 |
|
5699 static void |
|
5700 DestroyContext(JSContext *cx, bool withGC) |
|
5701 { |
|
5702 JSShellContextData *data = GetContextData(cx); |
|
5703 JS_SetContextPrivate(cx, nullptr); |
|
5704 free(data); |
|
5705 WITH_SIGNALS_DISABLED(withGC ? JS_DestroyContext(cx) : JS_DestroyContextNoGC(cx)); |
|
5706 } |
|
5707 |
|
5708 static JSObject * |
|
5709 NewGlobalObject(JSContext *cx, JS::CompartmentOptions &options, |
|
5710 JSPrincipals *principals) |
|
5711 { |
|
5712 RootedObject glob(cx, JS_NewGlobalObject(cx, &global_class, principals, |
|
5713 JS::DontFireOnNewGlobalHook, options)); |
|
5714 if (!glob) |
|
5715 return nullptr; |
|
5716 |
|
5717 { |
|
5718 JSAutoCompartment ac(cx, glob); |
|
5719 |
|
5720 #ifndef LAZY_STANDARD_CLASSES |
|
5721 if (!JS_InitStandardClasses(cx, glob)) |
|
5722 return nullptr; |
|
5723 #endif |
|
5724 |
|
5725 #ifdef JS_HAS_CTYPES |
|
5726 if (!JS_InitCTypesClass(cx, glob)) |
|
5727 return nullptr; |
|
5728 #endif |
|
5729 if (!JS_InitReflect(cx, glob)) |
|
5730 return nullptr; |
|
5731 if (!JS_DefineDebuggerObject(cx, glob)) |
|
5732 return nullptr; |
|
5733 if (!JS::RegisterPerfMeasurement(cx, glob)) |
|
5734 return nullptr; |
|
5735 if (!JS_DefineFunctionsWithHelp(cx, glob, shell_functions) || |
|
5736 !JS_DefineProfilingFunctions(cx, glob)) |
|
5737 { |
|
5738 return nullptr; |
|
5739 } |
|
5740 if (!js::DefineTestingFunctions(cx, glob, fuzzingSafe)) |
|
5741 return nullptr; |
|
5742 |
|
5743 if (!fuzzingSafe && !JS_DefineFunctionsWithHelp(cx, glob, fuzzing_unsafe_functions)) |
|
5744 return nullptr; |
|
5745 |
|
5746 /* Initialize FakeDOMObject. */ |
|
5747 static const js::DOMCallbacks DOMcallbacks = { |
|
5748 InstanceClassHasProtoAtDepth |
|
5749 }; |
|
5750 SetDOMCallbacks(cx->runtime(), &DOMcallbacks); |
|
5751 |
|
5752 RootedObject domProto(cx, JS_InitClass(cx, glob, js::NullPtr(), &dom_class, dom_constructor, |
|
5753 0, dom_props, dom_methods, nullptr, nullptr)); |
|
5754 if (!domProto) |
|
5755 return nullptr; |
|
5756 |
|
5757 /* Initialize FakeDOMObject.prototype */ |
|
5758 InitDOMObject(domProto); |
|
5759 } |
|
5760 |
|
5761 JS_FireOnNewGlobalObject(cx, glob); |
|
5762 |
|
5763 return glob; |
|
5764 } |
|
5765 |
|
5766 static bool |
|
5767 BindScriptArgs(JSContext *cx, JSObject *obj_, OptionParser *op) |
|
5768 { |
|
5769 RootedObject obj(cx, obj_); |
|
5770 |
|
5771 MultiStringRange msr = op->getMultiStringArg("scriptArgs"); |
|
5772 RootedObject scriptArgs(cx); |
|
5773 scriptArgs = JS_NewArrayObject(cx, 0); |
|
5774 if (!scriptArgs) |
|
5775 return false; |
|
5776 |
|
5777 if (!JS_DefineProperty(cx, obj, "scriptArgs", scriptArgs, 0)) |
|
5778 return false; |
|
5779 |
|
5780 for (size_t i = 0; !msr.empty(); msr.popFront(), ++i) { |
|
5781 const char *scriptArg = msr.front(); |
|
5782 JSString *str = JS_NewStringCopyZ(cx, scriptArg); |
|
5783 if (!str || |
|
5784 !JS_DefineElement(cx, scriptArgs, i, STRING_TO_JSVAL(str), nullptr, nullptr, |
|
5785 JSPROP_ENUMERATE)) { |
|
5786 return false; |
|
5787 } |
|
5788 } |
|
5789 |
|
5790 return true; |
|
5791 } |
|
5792 |
|
5793 // This function is currently only called from "#if defined(JS_ION)" chunks, |
|
5794 // so we're guarding the function definition with an #ifdef, too, to avoid |
|
5795 // build warning for unused function in non-ion-enabled builds: |
|
5796 #if defined(JS_ION) |
|
5797 static bool |
|
5798 OptionFailure(const char *option, const char *str) |
|
5799 { |
|
5800 fprintf(stderr, "Unrecognized option for %s: %s\n", option, str); |
|
5801 return false; |
|
5802 } |
|
5803 #endif /* JS_ION */ |
|
5804 |
|
5805 static int |
|
5806 ProcessArgs(JSContext *cx, JSObject *obj_, OptionParser *op) |
|
5807 { |
|
5808 RootedObject obj(cx, obj_); |
|
5809 |
|
5810 if (op->getBoolOption('s')) |
|
5811 JS::ContextOptionsRef(cx).toggleExtraWarnings(); |
|
5812 |
|
5813 if (op->getBoolOption('d')) { |
|
5814 JS_SetRuntimeDebugMode(JS_GetRuntime(cx), true); |
|
5815 JS_SetDebugMode(cx, true); |
|
5816 } |
|
5817 |
|
5818 /* |scriptArgs| gets bound on the global before any code is run. */ |
|
5819 if (!BindScriptArgs(cx, obj, op)) |
|
5820 return EXIT_FAILURE; |
|
5821 |
|
5822 MultiStringRange filePaths = op->getMultiStringOption('f'); |
|
5823 MultiStringRange codeChunks = op->getMultiStringOption('e'); |
|
5824 |
|
5825 if (filePaths.empty() && codeChunks.empty() && !op->getStringArg("script")) { |
|
5826 Process(cx, obj, nullptr, true); /* Interactive. */ |
|
5827 return gExitCode; |
|
5828 } |
|
5829 |
|
5830 while (!filePaths.empty() || !codeChunks.empty()) { |
|
5831 size_t fpArgno = filePaths.empty() ? -1 : filePaths.argno(); |
|
5832 size_t ccArgno = codeChunks.empty() ? -1 : codeChunks.argno(); |
|
5833 if (fpArgno < ccArgno) { |
|
5834 char *path = filePaths.front(); |
|
5835 Process(cx, obj, path, false); |
|
5836 if (gExitCode) |
|
5837 return gExitCode; |
|
5838 filePaths.popFront(); |
|
5839 } else { |
|
5840 const char *code = codeChunks.front(); |
|
5841 RootedValue rval(cx); |
|
5842 if (!JS_EvaluateScript(cx, obj, code, strlen(code), "-e", 1, &rval)) |
|
5843 return gExitCode ? gExitCode : EXITCODE_RUNTIME_ERROR; |
|
5844 codeChunks.popFront(); |
|
5845 } |
|
5846 } |
|
5847 |
|
5848 /* The |script| argument is processed after all options. */ |
|
5849 if (const char *path = op->getStringArg("script")) { |
|
5850 Process(cx, obj, path, false); |
|
5851 if (gExitCode) |
|
5852 return gExitCode; |
|
5853 } |
|
5854 |
|
5855 if (op->getBoolOption('i')) |
|
5856 Process(cx, obj, nullptr, true); |
|
5857 |
|
5858 return gExitCode ? gExitCode : EXIT_SUCCESS; |
|
5859 } |
|
5860 |
|
5861 static bool |
|
5862 SetRuntimeOptions(JSRuntime *rt, const OptionParser &op) |
|
5863 { |
|
5864 #if defined(JS_ION) |
|
5865 bool enableBaseline = !op.getBoolOption("no-baseline"); |
|
5866 bool enableIon = !op.getBoolOption("no-ion"); |
|
5867 bool enableAsmJS = !op.getBoolOption("no-asmjs"); |
|
5868 |
|
5869 JS::RuntimeOptionsRef(rt).setBaseline(enableBaseline) |
|
5870 .setIon(enableIon) |
|
5871 .setAsmJS(enableAsmJS); |
|
5872 |
|
5873 if (const char *str = op.getStringOption("ion-gvn")) { |
|
5874 if (strcmp(str, "off") == 0) { |
|
5875 jit::js_JitOptions.disableGvn = true; |
|
5876 } else if (strcmp(str, "pessimistic") == 0) { |
|
5877 jit::js_JitOptions.forceGvnKind = true; |
|
5878 jit::js_JitOptions.forcedGvnKind = jit::GVN_Pessimistic; |
|
5879 } else if (strcmp(str, "optimistic") == 0) { |
|
5880 jit::js_JitOptions.forceGvnKind = true; |
|
5881 jit::js_JitOptions.forcedGvnKind = jit::GVN_Optimistic; |
|
5882 } else { |
|
5883 return OptionFailure("ion-gvn", str); |
|
5884 } |
|
5885 } |
|
5886 |
|
5887 if (const char *str = op.getStringOption("ion-licm")) { |
|
5888 if (strcmp(str, "on") == 0) |
|
5889 jit::js_JitOptions.disableLicm = false; |
|
5890 else if (strcmp(str, "off") == 0) |
|
5891 jit::js_JitOptions.disableLicm = true; |
|
5892 else |
|
5893 return OptionFailure("ion-licm", str); |
|
5894 } |
|
5895 |
|
5896 if (const char *str = op.getStringOption("ion-edgecase-analysis")) { |
|
5897 if (strcmp(str, "on") == 0) |
|
5898 jit::js_JitOptions.disableEdgeCaseAnalysis = false; |
|
5899 else if (strcmp(str, "off") == 0) |
|
5900 jit::js_JitOptions.disableEdgeCaseAnalysis = true; |
|
5901 else |
|
5902 return OptionFailure("ion-edgecase-analysis", str); |
|
5903 } |
|
5904 |
|
5905 if (const char *str = op.getStringOption("ion-range-analysis")) { |
|
5906 if (strcmp(str, "on") == 0) |
|
5907 jit::js_JitOptions.disableRangeAnalysis = false; |
|
5908 else if (strcmp(str, "off") == 0) |
|
5909 jit::js_JitOptions.disableRangeAnalysis = true; |
|
5910 else |
|
5911 return OptionFailure("ion-range-analysis", str); |
|
5912 } |
|
5913 |
|
5914 if (op.getBoolOption("ion-check-range-analysis")) |
|
5915 jit::js_JitOptions.checkRangeAnalysis = true; |
|
5916 |
|
5917 if (const char *str = op.getStringOption("ion-inlining")) { |
|
5918 if (strcmp(str, "on") == 0) |
|
5919 jit::js_JitOptions.disableInlining = false; |
|
5920 else if (strcmp(str, "off") == 0) |
|
5921 jit::js_JitOptions.disableInlining = true; |
|
5922 else |
|
5923 return OptionFailure("ion-inlining", str); |
|
5924 } |
|
5925 |
|
5926 if (const char *str = op.getStringOption("ion-osr")) { |
|
5927 if (strcmp(str, "on") == 0) |
|
5928 jit::js_JitOptions.osr = true; |
|
5929 else if (strcmp(str, "off") == 0) |
|
5930 jit::js_JitOptions.osr = false; |
|
5931 else |
|
5932 return OptionFailure("ion-osr", str); |
|
5933 } |
|
5934 |
|
5935 if (const char *str = op.getStringOption("ion-limit-script-size")) { |
|
5936 if (strcmp(str, "on") == 0) |
|
5937 jit::js_JitOptions.limitScriptSize = true; |
|
5938 else if (strcmp(str, "off") == 0) |
|
5939 jit::js_JitOptions.limitScriptSize = false; |
|
5940 else |
|
5941 return OptionFailure("ion-limit-script-size", str); |
|
5942 } |
|
5943 |
|
5944 int32_t useCount = op.getIntOption("ion-uses-before-compile"); |
|
5945 if (useCount >= 0) |
|
5946 jit::js_JitOptions.setUsesBeforeCompile(useCount); |
|
5947 |
|
5948 useCount = op.getIntOption("baseline-uses-before-compile"); |
|
5949 if (useCount >= 0) |
|
5950 jit::js_JitOptions.baselineUsesBeforeCompile = useCount; |
|
5951 |
|
5952 if (op.getBoolOption("baseline-eager")) |
|
5953 jit::js_JitOptions.baselineUsesBeforeCompile = 0; |
|
5954 |
|
5955 if (const char *str = op.getStringOption("ion-regalloc")) { |
|
5956 if (strcmp(str, "lsra") == 0) { |
|
5957 jit::js_JitOptions.forceRegisterAllocator = true; |
|
5958 jit::js_JitOptions.forcedRegisterAllocator = jit::RegisterAllocator_LSRA; |
|
5959 } else if (strcmp(str, "backtracking") == 0) { |
|
5960 jit::js_JitOptions.forceRegisterAllocator = true; |
|
5961 jit::js_JitOptions.forcedRegisterAllocator = jit::RegisterAllocator_Backtracking; |
|
5962 } else if (strcmp(str, "stupid") == 0) { |
|
5963 jit::js_JitOptions.forceRegisterAllocator = true; |
|
5964 jit::js_JitOptions.forcedRegisterAllocator = jit::RegisterAllocator_Stupid; |
|
5965 } else { |
|
5966 return OptionFailure("ion-regalloc", str); |
|
5967 } |
|
5968 } |
|
5969 |
|
5970 if (op.getBoolOption("ion-eager")) |
|
5971 jit::js_JitOptions.setEagerCompilation(); |
|
5972 |
|
5973 if (op.getBoolOption("ion-compile-try-catch")) |
|
5974 jit::js_JitOptions.compileTryCatch = true; |
|
5975 |
|
5976 bool parallelCompilation = true; |
|
5977 if (const char *str = op.getStringOption("ion-parallel-compile")) { |
|
5978 if (strcmp(str, "off") == 0) |
|
5979 parallelCompilation = false; |
|
5980 else if (strcmp(str, "on") != 0) |
|
5981 return OptionFailure("ion-parallel-compile", str); |
|
5982 } |
|
5983 #ifdef JS_THREADSAFE |
|
5984 rt->setParallelIonCompilationEnabled(parallelCompilation); |
|
5985 #endif |
|
5986 |
|
5987 #endif // JS_ION |
|
5988 |
|
5989 #ifdef JS_ARM_SIMULATOR |
|
5990 if (op.getBoolOption("arm-sim-icache-checks")) |
|
5991 jit::Simulator::ICacheCheckingEnabled = true; |
|
5992 |
|
5993 int32_t stopAt = op.getIntOption("arm-sim-stop-at"); |
|
5994 if (stopAt >= 0) |
|
5995 jit::Simulator::StopSimAt = stopAt; |
|
5996 #endif |
|
5997 |
|
5998 reportWarnings = op.getBoolOption('w'); |
|
5999 compileOnly = op.getBoolOption('c'); |
|
6000 printTiming = op.getBoolOption('b'); |
|
6001 rt->profilingScripts = enableDisassemblyDumps = op.getBoolOption('D'); |
|
6002 |
|
6003 jsCacheDir = op.getStringOption("js-cache"); |
|
6004 if (jsCacheDir) { |
|
6005 if (op.getBoolOption("js-cache-per-process")) |
|
6006 jsCacheDir = JS_smprintf("%s/%u", jsCacheDir, (unsigned)getpid()); |
|
6007 jsCacheAsmJSPath = JS_smprintf("%s/asmjs.cache", jsCacheDir); |
|
6008 } |
|
6009 |
|
6010 #ifdef JS_THREADSAFE |
|
6011 int32_t threadCount = op.getIntOption("thread-count"); |
|
6012 if (threadCount >= 0) |
|
6013 SetFakeCPUCount(threadCount); |
|
6014 #endif /* JS_THREADSAFE */ |
|
6015 |
|
6016 #ifdef DEBUG |
|
6017 dumpEntrainedVariables = op.getBoolOption("dump-entrained-variables"); |
|
6018 #endif |
|
6019 |
|
6020 return true; |
|
6021 } |
|
6022 |
|
6023 static int |
|
6024 Shell(JSContext *cx, OptionParser *op, char **envp) |
|
6025 { |
|
6026 JSAutoRequest ar(cx); |
|
6027 |
|
6028 if (op->getBoolOption("fuzzing-safe")) |
|
6029 fuzzingSafe = true; |
|
6030 else |
|
6031 fuzzingSafe = (getenv("MOZ_FUZZING_SAFE") && getenv("MOZ_FUZZING_SAFE")[0] != '0'); |
|
6032 |
|
6033 RootedObject glob(cx); |
|
6034 JS::CompartmentOptions options; |
|
6035 options.setVersion(JSVERSION_LATEST); |
|
6036 glob = NewGlobalObject(cx, options, nullptr); |
|
6037 if (!glob) |
|
6038 return 1; |
|
6039 |
|
6040 JSAutoCompartment ac(cx, glob); |
|
6041 js::SetDefaultObjectForContext(cx, glob); |
|
6042 |
|
6043 JSObject *envobj = JS_DefineObject(cx, glob, "environment", &env_class, nullptr, 0); |
|
6044 if (!envobj) |
|
6045 return 1; |
|
6046 JS_SetPrivate(envobj, envp); |
|
6047 |
|
6048 int result = ProcessArgs(cx, glob, op); |
|
6049 |
|
6050 if (enableDisassemblyDumps) |
|
6051 JS_DumpCompartmentPCCounts(cx); |
|
6052 |
|
6053 if (op->getBoolOption("js-cache-per-process")) { |
|
6054 if (jsCacheAsmJSPath) |
|
6055 unlink(jsCacheAsmJSPath); |
|
6056 if (jsCacheDir) |
|
6057 rmdir(jsCacheDir); |
|
6058 } |
|
6059 |
|
6060 return result; |
|
6061 } |
|
6062 |
|
6063 static void |
|
6064 MaybeOverrideOutFileFromEnv(const char* const envVar, |
|
6065 FILE* defaultOut, |
|
6066 FILE** outFile) |
|
6067 { |
|
6068 const char* outPath = getenv(envVar); |
|
6069 if (!outPath || !*outPath || !(*outFile = fopen(outPath, "w"))) { |
|
6070 *outFile = defaultOut; |
|
6071 } |
|
6072 } |
|
6073 |
|
6074 /* Pretend we can always preserve wrappers for dummy DOM objects. */ |
|
6075 static bool |
|
6076 DummyPreserveWrapperCallback(JSContext *cx, JSObject *obj) |
|
6077 { |
|
6078 return true; |
|
6079 } |
|
6080 |
|
6081 int |
|
6082 main(int argc, char **argv, char **envp) |
|
6083 { |
|
6084 sArgc = argc; |
|
6085 sArgv = argv; |
|
6086 |
|
6087 JSRuntime *rt; |
|
6088 JSContext *cx; |
|
6089 int result; |
|
6090 #ifdef XP_WIN |
|
6091 { |
|
6092 const char *crash_option = getenv("XRE_NO_WINDOWS_CRASH_DIALOG"); |
|
6093 if (crash_option && strncmp(crash_option, "1", 1)) { |
|
6094 DWORD oldmode = SetErrorMode(SEM_NOGPFAULTERRORBOX); |
|
6095 SetErrorMode(oldmode | SEM_NOGPFAULTERRORBOX); |
|
6096 } |
|
6097 } |
|
6098 #endif |
|
6099 |
|
6100 #ifdef HAVE_SETLOCALE |
|
6101 setlocale(LC_ALL, ""); |
|
6102 #endif |
|
6103 |
|
6104 MaybeOverrideOutFileFromEnv("JS_STDERR", stderr, &gErrFile); |
|
6105 MaybeOverrideOutFileFromEnv("JS_STDOUT", stdout, &gOutFile); |
|
6106 |
|
6107 OptionParser op("Usage: {progname} [options] [[script] scriptArgs*]"); |
|
6108 |
|
6109 op.setDescription("The SpiderMonkey shell provides a command line interface to the " |
|
6110 "JavaScript engine. Code and file options provided via the command line are " |
|
6111 "run left to right. If provided, the optional script argument is run after " |
|
6112 "all options have been processed. Just-In-Time compilation modes may be enabled via " |
|
6113 "command line options."); |
|
6114 op.setDescriptionWidth(72); |
|
6115 op.setHelpWidth(80); |
|
6116 op.setVersion(JS_GetImplementationVersion()); |
|
6117 |
|
6118 if (!op.addMultiStringOption('f', "file", "PATH", "File path to run") |
|
6119 || !op.addMultiStringOption('e', "execute", "CODE", "Inline code to run") |
|
6120 || !op.addBoolOption('i', "shell", "Enter prompt after running code") |
|
6121 || !op.addBoolOption('m', "jm", "No-op (still used by fuzzers)") |
|
6122 || !op.addBoolOption('\0', "no-jm", "No-op (still used by fuzzers)") |
|
6123 || !op.addBoolOption('c', "compileonly", "Only compile, don't run (syntax checking mode)") |
|
6124 || !op.addBoolOption('w', "warnings", "Emit warnings") |
|
6125 || !op.addBoolOption('W', "nowarnings", "Don't emit warnings") |
|
6126 || !op.addBoolOption('s', "strict", "Check strictness") |
|
6127 || !op.addBoolOption('d', "debugjit", "Enable runtime debug mode for method JIT code") |
|
6128 || !op.addBoolOption('a', "always-mjit", "No-op (still used by fuzzers)") |
|
6129 || !op.addBoolOption('D', "dump-bytecode", "Dump bytecode with exec count for all scripts") |
|
6130 || !op.addBoolOption('b', "print-timing", "Print sub-ms runtime for each file that's run") |
|
6131 || !op.addStringOption('\0', "js-cache", "[path]", |
|
6132 "Enable the JS cache by specifying the path of the directory to use " |
|
6133 "to hold cache files") |
|
6134 || !op.addBoolOption('\0', "js-cache-per-process", |
|
6135 "Generate a separate cache sub-directory for this process inside " |
|
6136 "the cache directory specified by --js-cache. This cache directory " |
|
6137 "will be removed when the js shell exits. This is useful for running " |
|
6138 "tests in parallel.") |
|
6139 #ifdef DEBUG |
|
6140 || !op.addBoolOption('O', "print-alloc", "Print the number of allocations at exit") |
|
6141 #endif |
|
6142 || !op.addOptionalStringArg("script", "A script to execute (after all options)") |
|
6143 || !op.addOptionalMultiStringArg("scriptArgs", |
|
6144 "String arguments to bind as |scriptArgs| in the " |
|
6145 "shell's global") |
|
6146 #ifdef JS_THREADSAFE |
|
6147 || !op.addIntOption('\0', "thread-count", "COUNT", "Use COUNT auxiliary threads " |
|
6148 "(default: # of cores - 1)", -1) |
|
6149 #endif |
|
6150 || !op.addBoolOption('\0', "ion", "Enable IonMonkey (default)") |
|
6151 || !op.addBoolOption('\0', "no-ion", "Disable IonMonkey") |
|
6152 || !op.addBoolOption('\0', "no-asmjs", "Disable asm.js compilation") |
|
6153 || !op.addStringOption('\0', "ion-gvn", "[mode]", |
|
6154 "Specify Ion global value numbering:\n" |
|
6155 " off: disable GVN\n" |
|
6156 " pessimistic: use pessimistic GVN\n" |
|
6157 " optimistic: (default) use optimistic GVN") |
|
6158 || !op.addStringOption('\0', "ion-licm", "on/off", |
|
6159 "Loop invariant code motion (default: on, off to disable)") |
|
6160 || !op.addStringOption('\0', "ion-edgecase-analysis", "on/off", |
|
6161 "Find edge cases where Ion can avoid bailouts (default: on, off to disable)") |
|
6162 || !op.addStringOption('\0', "ion-range-analysis", "on/off", |
|
6163 "Range analysis (default: on, off to disable)") |
|
6164 || !op.addBoolOption('\0', "ion-check-range-analysis", |
|
6165 "Range analysis checking") |
|
6166 || !op.addStringOption('\0', "ion-inlining", "on/off", |
|
6167 "Inline methods where possible (default: on, off to disable)") |
|
6168 || !op.addStringOption('\0', "ion-osr", "on/off", |
|
6169 "On-Stack Replacement (default: on, off to disable)") |
|
6170 || !op.addStringOption('\0', "ion-limit-script-size", "on/off", |
|
6171 "Don't compile very large scripts (default: on, off to disable)") |
|
6172 || !op.addIntOption('\0', "ion-uses-before-compile", "COUNT", |
|
6173 "Wait for COUNT calls or iterations before compiling " |
|
6174 "(default: 1000)", -1) |
|
6175 || !op.addStringOption('\0', "ion-regalloc", "[mode]", |
|
6176 "Specify Ion register allocation:\n" |
|
6177 " lsra: Linear Scan register allocation (default)\n" |
|
6178 " backtracking: Priority based backtracking register allocation\n" |
|
6179 " stupid: Simple block local register allocation") |
|
6180 || !op.addBoolOption('\0', "ion-eager", "Always ion-compile methods (implies --baseline-eager)") |
|
6181 || !op.addBoolOption('\0', "ion-compile-try-catch", "Ion-compile try-catch statements") |
|
6182 || !op.addStringOption('\0', "ion-parallel-compile", "on/off", |
|
6183 "Compile scripts off thread (default: off)") |
|
6184 || !op.addBoolOption('\0', "baseline", "Enable baseline compiler (default)") |
|
6185 || !op.addBoolOption('\0', "no-baseline", "Disable baseline compiler") |
|
6186 || !op.addBoolOption('\0', "baseline-eager", "Always baseline-compile methods") |
|
6187 || !op.addIntOption('\0', "baseline-uses-before-compile", "COUNT", |
|
6188 "Wait for COUNT calls or iterations before baseline-compiling " |
|
6189 "(default: 10)", -1) |
|
6190 || !op.addBoolOption('\0', "no-fpu", "Pretend CPU does not support floating-point operations " |
|
6191 "to test JIT codegen (no-op on platforms other than x86).") |
|
6192 || !op.addBoolOption('\0', "no-sse3", "Pretend CPU does not support SSE3 instructions and above " |
|
6193 "to test JIT codegen (no-op on platforms other than x86 and x64).") |
|
6194 || !op.addBoolOption('\0', "no-sse4", "Pretend CPU does not support SSE4 instructions" |
|
6195 "to test JIT codegen (no-op on platforms other than x86 and x64).") |
|
6196 || !op.addBoolOption('\0', "fuzzing-safe", "Don't expose functions that aren't safe for " |
|
6197 "fuzzers to call") |
|
6198 #ifdef DEBUG |
|
6199 || !op.addBoolOption('\0', "dump-entrained-variables", "Print variables which are " |
|
6200 "unnecessarily entrained by inner functions") |
|
6201 #endif |
|
6202 #ifdef JSGC_GENERATIONAL |
|
6203 || !op.addBoolOption('\0', "no-ggc", "Disable Generational GC") |
|
6204 #endif |
|
6205 || !op.addIntOption('\0', "available-memory", "SIZE", |
|
6206 "Select GC settings based on available memory (MB)", 0) |
|
6207 #ifdef JS_ARM_SIMULATOR |
|
6208 || !op.addBoolOption('\0', "arm-sim-icache-checks", "Enable icache flush checks in the ARM " |
|
6209 "simulator.") |
|
6210 || !op.addIntOption('\0', "arm-sim-stop-at", "NUMBER", "Stop the ARM simulator after the given " |
|
6211 "NUMBER of instructions.", -1) |
|
6212 #endif |
|
6213 ) |
|
6214 { |
|
6215 return EXIT_FAILURE; |
|
6216 } |
|
6217 |
|
6218 op.setArgTerminatesOptions("script", true); |
|
6219 op.setArgCapturesRest("scriptArgs"); |
|
6220 |
|
6221 switch (op.parseArgs(argc, argv)) { |
|
6222 case OptionParser::ParseHelp: |
|
6223 return EXIT_SUCCESS; |
|
6224 case OptionParser::ParseError: |
|
6225 op.printHelp(argv[0]); |
|
6226 return EXIT_FAILURE; |
|
6227 case OptionParser::Fail: |
|
6228 return EXIT_FAILURE; |
|
6229 case OptionParser::Okay: |
|
6230 break; |
|
6231 } |
|
6232 |
|
6233 if (op.getHelpOption()) |
|
6234 return EXIT_SUCCESS; |
|
6235 |
|
6236 #ifdef DEBUG |
|
6237 /* |
|
6238 * Process OOM options as early as possible so that we can observe as many |
|
6239 * allocations as possible. |
|
6240 */ |
|
6241 OOM_printAllocationCount = op.getBoolOption('O'); |
|
6242 |
|
6243 #if defined(JS_CODEGEN_X86) && defined(JS_ION) |
|
6244 if (op.getBoolOption("no-fpu")) |
|
6245 JSC::MacroAssembler::SetFloatingPointDisabled(); |
|
6246 #endif |
|
6247 |
|
6248 #if (defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)) && defined(JS_ION) |
|
6249 if (op.getBoolOption("no-sse3")) { |
|
6250 JSC::MacroAssembler::SetSSE3Disabled(); |
|
6251 PropagateFlagToNestedShells("--no-sse3"); |
|
6252 } |
|
6253 if (op.getBoolOption("no-sse4")) { |
|
6254 JSC::MacroAssembler::SetSSE4Disabled(); |
|
6255 PropagateFlagToNestedShells("--no-sse4"); |
|
6256 } |
|
6257 #endif |
|
6258 |
|
6259 #endif // DEBUG |
|
6260 |
|
6261 // Start the engine. |
|
6262 if (!JS_Init()) |
|
6263 return 1; |
|
6264 |
|
6265 /* Use the same parameters as the browser in xpcjsruntime.cpp. */ |
|
6266 rt = JS_NewRuntime(32L * 1024L * 1024L, JS_USE_HELPER_THREADS); |
|
6267 if (!rt) |
|
6268 return 1; |
|
6269 |
|
6270 JS::SetOutOfMemoryCallback(rt, my_OOMCallback); |
|
6271 if (!SetRuntimeOptions(rt, op)) |
|
6272 return 1; |
|
6273 |
|
6274 gInterruptFunc.construct(rt, NullValue()); |
|
6275 |
|
6276 JS_SetGCParameter(rt, JSGC_MAX_BYTES, 0xffffffff); |
|
6277 #ifdef JSGC_GENERATIONAL |
|
6278 Maybe<JS::AutoDisableGenerationalGC> noggc; |
|
6279 if (op.getBoolOption("no-ggc")) |
|
6280 noggc.construct(rt); |
|
6281 #endif |
|
6282 |
|
6283 size_t availMem = op.getIntOption("available-memory"); |
|
6284 if (availMem > 0) |
|
6285 JS_SetGCParametersBasedOnAvailableMemory(rt, availMem); |
|
6286 |
|
6287 JS_SetTrustedPrincipals(rt, &ShellPrincipals::fullyTrusted); |
|
6288 JS_SetSecurityCallbacks(rt, &ShellPrincipals::securityCallbacks); |
|
6289 JS_InitDestroyPrincipalsCallback(rt, ShellPrincipals::destroy); |
|
6290 |
|
6291 JS_SetInterruptCallback(rt, ShellInterruptCallback); |
|
6292 JS::SetAsmJSCacheOps(rt, &asmJSCacheOps); |
|
6293 |
|
6294 JS_SetNativeStackQuota(rt, gMaxStackSize); |
|
6295 |
|
6296 #ifdef JS_THREADSAFE |
|
6297 if (!offThreadState.init()) |
|
6298 return 1; |
|
6299 #endif |
|
6300 |
|
6301 if (!InitWatchdog(rt)) |
|
6302 return 1; |
|
6303 |
|
6304 cx = NewContext(rt); |
|
6305 if (!cx) |
|
6306 return 1; |
|
6307 |
|
6308 JS_SetGCParameter(rt, JSGC_MODE, JSGC_MODE_INCREMENTAL); |
|
6309 JS_SetGCParameterForThread(cx, JSGC_MAX_CODE_CACHE_BYTES, 16 * 1024 * 1024); |
|
6310 |
|
6311 js::SetPreserveWrapperCallback(rt, DummyPreserveWrapperCallback); |
|
6312 |
|
6313 result = Shell(cx, &op, envp); |
|
6314 |
|
6315 #ifdef DEBUG |
|
6316 if (OOM_printAllocationCount) |
|
6317 printf("OOM max count: %u\n", OOM_counter); |
|
6318 #endif |
|
6319 |
|
6320 DestroyContext(cx, true); |
|
6321 |
|
6322 KillWatchdog(); |
|
6323 |
|
6324 gInterruptFunc.destroy(); |
|
6325 |
|
6326 #ifdef JS_THREADSAFE |
|
6327 for (size_t i = 0; i < workerThreads.length(); i++) |
|
6328 PR_JoinThread(workerThreads[i]); |
|
6329 #endif |
|
6330 |
|
6331 #ifdef JSGC_GENERATIONAL |
|
6332 if (!noggc.empty()) |
|
6333 noggc.destroy(); |
|
6334 #endif |
|
6335 |
|
6336 JS_DestroyRuntime(rt); |
|
6337 JS_ShutDown(); |
|
6338 return result; |
|
6339 } |