1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 /* JS shell. */
8
9 #include "mozilla/ArrayUtils.h"
10 #include "mozilla/Atomics.h"
11 #include "mozilla/Attributes.h"
12 #include "mozilla/DebugOnly.h"
13 #include "mozilla/EnumSet.h"
14 #include "mozilla/IntegerPrintfMacros.h"
15 #include "mozilla/mozalloc.h"
16 #include "mozilla/PodOperations.h"
17 #include "mozilla/ScopeExit.h"
18 #include "mozilla/Sprintf.h"
19 #include "mozilla/TimeStamp.h"
20 #include "mozilla/UniquePtrExtensions.h" // UniqueFreePtr
21 #include "mozilla/Utf8.h"
22 #include "mozilla/Variant.h"
23
24 #include <algorithm>
25 #include <chrono>
26 #ifdef XP_WIN
27 # include <direct.h>
28 # include <process.h>
29 #endif
30 #include <errno.h>
31 #include <fcntl.h>
32 #if defined(XP_WIN)
33 # include <io.h> /* for isatty() */
34 #endif
35 #include <locale.h>
36 #if defined(MALLOC_H)
37 # include MALLOC_H /* for malloc_usable_size, malloc_size, _msize */
38 #endif
39 #include <ctime>
40 #include <math.h>
41 #ifndef __wasi__
42 # include <signal.h>
43 #endif
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <sys/stat.h>
48 #include <sys/types.h>
49 #include <utility>
50 #ifdef XP_UNIX
51 # ifndef __wasi__
52 # include <sys/mman.h>
53 # include <sys/wait.h>
54 # endif
55 # include <sys/stat.h>
56 # include <unistd.h>
57 #endif
58 #ifdef XP_LINUX
59 # include <sys/prctl.h>
60 #endif
61
62 #include "jsapi.h"
63 #include "jsfriendapi.h"
64 #include "jstypes.h"
65 #ifndef JS_WITHOUT_NSPR
66 # include "prerror.h"
67 # include "prlink.h"
68 #endif
69
70 #include "builtin/Array.h"
71 #include "builtin/MapObject.h"
72 #include "builtin/ModuleObject.h"
73 #include "builtin/RegExp.h"
74 #include "builtin/TestingFunctions.h"
75 #include "builtin/TestingUtility.h" // js::ParseCompileOptions
76 #include "debugger/DebugAPI.h"
77 #include "frontend/BytecodeCompilation.h"
78 #include "frontend/BytecodeCompiler.h"
79 #include "frontend/BytecodeEmitter.h"
80 #include "frontend/CompilationStencil.h"
81 #ifdef JS_ENABLE_SMOOSH
82 # include "frontend/Frontend2.h"
83 #endif
84 #include "frontend/ModuleSharedContext.h"
85 #include "frontend/Parser.h"
86 #include "frontend/SourceNotes.h" // SrcNote, SrcNoteType, SrcNoteIterator
87 #include "gc/PublicIterators.h"
88 #ifdef DEBUG
89 # include "irregexp/RegExpAPI.h"
90 #endif
91 #include "gc/GC-inl.h" // ZoneCellIter
92
93 #ifdef JS_SIMULATOR_ARM
94 # include "jit/arm/Simulator-arm.h"
95 #endif
96 #ifdef JS_SIMULATOR_MIPS32
97 # include "jit/mips32/Simulator-mips32.h"
98 #endif
99 #ifdef JS_SIMULATOR_MIPS64
100 # include "jit/mips64/Simulator-mips64.h"
101 #endif
102 #include "jit/CacheIRHealth.h"
103 #include "jit/InlinableNatives.h"
104 #include "jit/Ion.h"
105 #include "jit/JitcodeMap.h"
106 #include "jit/shared/CodeGenerator-shared.h"
107 #include "js/Array.h" // JS::NewArrayObject
108 #include "js/ArrayBuffer.h" // JS::{CreateMappedArrayBufferContents,NewMappedArrayBufferWithContents,IsArrayBufferObject,GetArrayBufferLengthAndData}
109 #include "js/BuildId.h" // JS::BuildIdCharVector, JS::SetProcessBuildIdOp
110 #include "js/CharacterEncoding.h" // JS::StringIsASCII
111 #include "js/CompilationAndEvaluation.h"
112 #include "js/CompileOptions.h"
113 #include "js/ContextOptions.h" // JS::ContextOptions{,Ref}
114 #include "js/Debug.h"
115 #include "js/Equality.h" // JS::SameValue
116 #include "js/ErrorReport.h" // JS::PrintError
117 #include "js/Exception.h" // JS::StealPendingExceptionStack
118 #include "js/experimental/CodeCoverage.h" // js::EnableCodeCoverage
119 #include "js/experimental/CTypes.h" // JS::InitCTypesClass
120 #include "js/experimental/Intl.h" // JS::AddMoz{DateTimeFormat,DisplayNames}Constructor
121 #include "js/experimental/JitInfo.h" // JSJit{Getter,Setter,Method}CallArgs, JSJitGetterInfo, JSJit{Getter,Setter}Op, JSJitInfo
122 #include "js/experimental/JSStencil.h" // JS::Stencil, JS::CompileToStencilOffThread, JS::FinishOffThreadCompileToStencil
123 #include "js/experimental/SourceHook.h" // js::{Set,Forget,}SourceHook
124 #include "js/experimental/TypedData.h" // JS_NewUint8Array
125 #include "js/friend/DumpFunctions.h" // JS::FormatStackDump
126 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
127 #include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit
128 #include "js/friend/WindowProxy.h" // js::IsWindowProxy, js::SetWindowProxyClass, js::ToWindowProxyIfWindow, js::ToWindowIfWindowProxy
129 #include "js/GCAPI.h" // JS::AutoCheckCannotGC
130 #include "js/GCVector.h"
131 #include "js/Initialization.h"
132 #include "js/JSON.h"
133 #include "js/MemoryFunctions.h"
134 #include "js/Modules.h" // JS::GetModulePrivate, JS::SetModule{DynamicImport,Metadata,Resolve}Hook, JS::SetModulePrivate
135 #include "js/Object.h" // JS::GetClass, JS::GetCompartment, JS::GetReservedSlot, JS::SetReservedSlot
136 #include "js/OffThreadScriptCompilation.h" // JS::SetUseOffThreadParseGlobal, js::UseOffThreadParseGlobal
137 #include "js/Printf.h"
138 #include "js/PropertySpec.h"
139 #include "js/Realm.h"
140 #include "js/RegExp.h" // JS::ObjectIsRegExp
141 #include "js/SourceText.h"
142 #include "js/StableStringChars.h"
143 #include "js/StructuredClone.h"
144 #include "js/SweepingAPI.h"
145 #include "js/Transcoding.h" // JS::TranscodeBuffer, JS::TranscodeRange
146 #include "js/Warnings.h" // JS::SetWarningReporter
147 #include "js/WasmModule.h" // JS::WasmModule
148 #include "js/Wrapper.h"
149 #include "proxy/DeadObjectProxy.h" // js::IsDeadProxyObject
150 #include "shell/jsoptparse.h"
151 #include "shell/jsshell.h"
152 #include "shell/OSObject.h"
153 #include "shell/ShellModuleObjectWrapper.h"
154 #include "shell/WasmTesting.h"
155 #include "threading/ConditionVariable.h"
156 #include "threading/ExclusiveData.h"
157 #include "threading/LockGuard.h"
158 #include "threading/Thread.h"
159 #include "util/CompleteFile.h" // js::FileContents, js::ReadCompleteFile
160 #include "util/DifferentialTesting.h"
161 #include "util/StringBuffer.h"
162 #include "util/Text.h"
163 #include "util/Windows.h"
164 #include "vm/ArgumentsObject.h"
165 #include "vm/Compression.h"
166 #include "vm/ErrorObject.h"
167 #include "vm/HelperThreads.h"
168 #include "vm/JSAtom.h"
169 #include "vm/JSContext.h"
170 #include "vm/JSFunction.h"
171 #include "vm/JSObject.h"
172 #include "vm/JSScript.h"
173 #include "vm/ModuleBuilder.h" // js::ModuleBuilder
174 #include "vm/Monitor.h"
175 #include "vm/MutexIDs.h"
176 #include "vm/Printer.h" // QuoteString
177 #include "vm/PromiseObject.h" // js::PromiseObject
178 #include "vm/Shape.h"
179 #include "vm/SharedArrayObject.h"
180 #include "vm/StencilObject.h" // js::StencilObject
181 #include "vm/Time.h"
182 #include "vm/ToSource.h" // js::ValueToSource
183 #include "vm/TypedArrayObject.h"
184 #include "vm/WrapperObject.h"
185 #include "wasm/WasmJS.h"
186
187 #include "vm/Compartment-inl.h"
188 #include "vm/ErrorObject-inl.h"
189 #include "vm/Interpreter-inl.h"
190 #include "vm/JSObject-inl.h"
191 #include "vm/Realm-inl.h"
192 #include "vm/Stack-inl.h"
193
194 using namespace js;
195 using namespace js::cli;
196 using namespace js::shell;
197
198 using JS::AutoStableStringChars;
199 using JS::CompileOptions;
200
201 using js::shell::RCFile;
202
203 using mozilla::ArrayEqual;
204 using mozilla::AsVariant;
205 using mozilla::Atomic;
206 using mozilla::MakeScopeExit;
207 using mozilla::Maybe;
208 using mozilla::Nothing;
209 using mozilla::NumberEqualsInt32;
210 using mozilla::TimeDuration;
211 using mozilla::TimeStamp;
212 using mozilla::Utf8Unit;
213 using mozilla::Variant;
214
215 #ifdef FUZZING_JS_FUZZILLI
216 # define REPRL_CRFD 100
217 # define REPRL_CWFD 101
218 # define REPRL_DRFD 102
219 # define REPRL_DWFD 103
220
221 # define SHM_SIZE 0x100000
222 # define MAX_EDGES ((SHM_SIZE - 4) * 8)
223
224 struct shmem_data {
225 uint32_t num_edges;
226 unsigned char edges[];
227 };
228
229 struct shmem_data* __shmem;
230
231 uint32_t *__edges_start, *__edges_stop;
__sanitizer_cov_reset_edgeguards()232 void __sanitizer_cov_reset_edgeguards() {
233 uint64_t N = 0;
234 for (uint32_t* x = __edges_start; x < __edges_stop && N < MAX_EDGES; x++)
235 *x = ++N;
236 }
237
__sanitizer_cov_trace_pc_guard_init(uint32_t * start,uint32_t * stop)238 extern "C" void __sanitizer_cov_trace_pc_guard_init(uint32_t* start,
239 uint32_t* stop) {
240 // Avoid duplicate initialization
241 if (start == stop || *start) return;
242
243 if (__edges_start != NULL || __edges_stop != NULL) {
244 fprintf(stderr,
245 "Coverage instrumentation is only supported for a single module\n");
246 _exit(-1);
247 }
248
249 __edges_start = start;
250 __edges_stop = stop;
251
252 // Map the shared memory region
253 const char* shm_key = getenv("SHM_ID");
254 if (!shm_key) {
255 puts("[COV] no shared memory bitmap available, skipping");
256 __shmem = (struct shmem_data*)malloc(SHM_SIZE);
257 } else {
258 int fd = shm_open(shm_key, O_RDWR, S_IREAD | S_IWRITE);
259 if (fd <= -1) {
260 fprintf(stderr, "Failed to open shared memory region: %s\n",
261 strerror(errno));
262 _exit(-1);
263 }
264
265 __shmem = (struct shmem_data*)mmap(0, SHM_SIZE, PROT_READ | PROT_WRITE,
266 MAP_SHARED, fd, 0);
267 if (__shmem == MAP_FAILED) {
268 fprintf(stderr, "Failed to mmap shared memory region\n");
269 _exit(-1);
270 }
271 }
272
273 __sanitizer_cov_reset_edgeguards();
274
275 __shmem->num_edges = stop - start;
276 printf("[COV] edge counters initialized. Shared memory: %s with %u edges\n",
277 shm_key, __shmem->num_edges);
278 }
279
__sanitizer_cov_trace_pc_guard(uint32_t * guard)280 extern "C" void __sanitizer_cov_trace_pc_guard(uint32_t* guard) {
281 // There's a small race condition here: if this function executes in two
282 // threads for the same edge at the same time, the first thread might disable
283 // the edge (by setting the guard to zero) before the second thread fetches
284 // the guard value (and thus the index). However, our instrumentation ignores
285 // the first edge (see libcoverage.c) and so the race is unproblematic.
286 uint32_t index = *guard;
287 // If this function is called before coverage instrumentation is properly
288 // initialized we want to return early.
289 if (!index) return;
290 __shmem->edges[index / 8] |= 1 << (index % 8);
291 *guard = 0;
292 }
293 #endif /* FUZZING_JS_FUZZILLI */
294
295 enum JSShellExitCode {
296 EXITCODE_RUNTIME_ERROR = 3,
297 EXITCODE_FILE_NOT_FOUND = 4,
298 EXITCODE_OUT_OF_MEMORY = 5,
299 EXITCODE_TIMEOUT = 6
300 };
301
302 /*
303 * Limit the timeout to 30 minutes to prevent an overflow on platfoms
304 * that represent the time internally in microseconds using 32-bit int.
305 */
306 static const double MAX_TIMEOUT_SECONDS = 1800.0;
307
308 // Not necessarily in sync with the browser
309 #ifdef ENABLE_SHARED_MEMORY
310 # define SHARED_MEMORY_DEFAULT 1
311 #else
312 # define SHARED_MEMORY_DEFAULT 0
313 #endif
314
315 // Fuzzing support for JS runtime fuzzing
316 #ifdef FUZZING_INTERFACES
317 # include "shell/jsrtfuzzing/jsrtfuzzing.h"
318 static bool fuzzDoDebug = !!getenv("MOZ_FUZZ_DEBUG");
319 static bool fuzzHaveModule = !!getenv("FUZZER");
320 #endif // FUZZING_INTERFACES
321
322 // Code to support GCOV code coverage measurements on standalone shell
323 #ifdef MOZ_CODE_COVERAGE
324 # if defined(__GNUC__) && !defined(__clang__)
325 extern "C" void __gcov_dump();
326 extern "C" void __gcov_reset();
327
counters_dump(int)328 void counters_dump(int) { __gcov_dump(); }
329
counters_reset(int)330 void counters_reset(int) { __gcov_reset(); }
331 # else
counters_dump(int)332 void counters_dump(int) { /* Do nothing */
333 }
334
counters_reset(int)335 void counters_reset(int) { /* Do nothing */
336 }
337 # endif
338
InstallCoverageSignalHandlers()339 static void InstallCoverageSignalHandlers() {
340 # ifndef XP_WIN
341 fprintf(stderr, "[CodeCoverage] Setting handlers for process %d.\n",
342 getpid());
343
344 struct sigaction dump_sa;
345 dump_sa.sa_handler = counters_dump;
346 dump_sa.sa_flags = SA_RESTART;
347 sigemptyset(&dump_sa.sa_mask);
348 mozilla::DebugOnly<int> r1 = sigaction(SIGUSR1, &dump_sa, nullptr);
349 MOZ_ASSERT(r1 == 0, "Failed to install GCOV SIGUSR1 handler");
350
351 struct sigaction reset_sa;
352 reset_sa.sa_handler = counters_reset;
353 reset_sa.sa_flags = SA_RESTART;
354 sigemptyset(&reset_sa.sa_mask);
355 mozilla::DebugOnly<int> r2 = sigaction(SIGUSR2, &reset_sa, nullptr);
356 MOZ_ASSERT(r2 == 0, "Failed to install GCOV SIGUSR2 handler");
357 # endif
358 }
359 #endif
360
361 // An off-thread parse or decode job.
362 class js::shell::OffThreadJob {
363 enum State {
364 RUNNING, // Working; no token.
365 DONE, // Finished; have token.
366 CANCELLED // Cancelled due to error.
367 };
368
369 public:
370 using Source = mozilla::Variant<JS::UniqueTwoByteChars, JS::TranscodeBuffer>;
371
372 OffThreadJob(ShellContext* sc, ScriptKind kind, bool useOffThreadParseGlobal,
373 Source&& source);
374 ~OffThreadJob();
375
376 void cancel();
377 void markDone(JS::OffThreadToken* newToken);
378 JS::OffThreadToken* waitUntilDone(JSContext* cx);
379
sourceChars()380 char16_t* sourceChars() { return source.as<UniqueTwoByteChars>().get(); }
xdrBuffer()381 JS::TranscodeBuffer& xdrBuffer() { return source.as<JS::TranscodeBuffer>(); }
382
383 public:
384 const int32_t id;
385 const ScriptKind kind;
386 const bool useOffThreadParseGlobal;
387
388 private:
389 js::Monitor& monitor;
390 State state;
391 JS::OffThreadToken* token;
392 Source source;
393 };
394
NewOffThreadJob(JSContext * cx,ScriptKind kind,CompileOptions & options,OffThreadJob::Source && source)395 static OffThreadJob* NewOffThreadJob(JSContext* cx, ScriptKind kind,
396 CompileOptions& options,
397 OffThreadJob::Source&& source) {
398 ShellContext* sc = GetShellContext(cx);
399 UniquePtr<OffThreadJob> job(cx->new_<OffThreadJob>(
400 sc, kind, options.useOffThreadParseGlobal, std::move(source)));
401 if (!job) {
402 return nullptr;
403 }
404
405 if (!sc->offThreadJobs.append(job.get())) {
406 job->cancel();
407 JS_ReportErrorASCII(cx, "OOM adding off-thread job");
408 return nullptr;
409 }
410
411 return job.release();
412 }
413
GetSingleOffThreadJob(JSContext * cx,ScriptKind kind)414 static OffThreadJob* GetSingleOffThreadJob(JSContext* cx, ScriptKind kind) {
415 ShellContext* sc = GetShellContext(cx);
416 const auto& jobs = sc->offThreadJobs;
417 if (jobs.empty()) {
418 JS_ReportErrorASCII(cx, "No off-thread jobs are pending");
419 return nullptr;
420 }
421
422 if (jobs.length() > 1) {
423 JS_ReportErrorASCII(
424 cx, "Multiple off-thread jobs are pending: must specify job ID");
425 return nullptr;
426 }
427
428 OffThreadJob* job = jobs[0];
429 if (job->kind != kind) {
430 JS_ReportErrorASCII(cx, "Off-thread job is the wrong kind");
431 return nullptr;
432 }
433
434 return job;
435 }
436
LookupOffThreadJobByID(JSContext * cx,ScriptKind kind,int32_t id)437 static OffThreadJob* LookupOffThreadJobByID(JSContext* cx, ScriptKind kind,
438 int32_t id) {
439 if (id <= 0) {
440 JS_ReportErrorASCII(cx, "Bad off-thread job ID");
441 return nullptr;
442 }
443
444 ShellContext* sc = GetShellContext(cx);
445 const auto& jobs = sc->offThreadJobs;
446 if (jobs.empty()) {
447 JS_ReportErrorASCII(cx, "No off-thread jobs are pending");
448 return nullptr;
449 }
450
451 OffThreadJob* job = nullptr;
452 for (auto someJob : jobs) {
453 if (someJob->id == id) {
454 job = someJob;
455 break;
456 }
457 }
458
459 if (!job) {
460 JS_ReportErrorASCII(cx, "Off-thread job not found");
461 return nullptr;
462 }
463
464 if (job->kind != kind) {
465 JS_ReportErrorASCII(cx, "Off-thread job is the wrong kind");
466 return nullptr;
467 }
468
469 return job;
470 }
471
LookupOffThreadJobForArgs(JSContext * cx,ScriptKind kind,const CallArgs & args,size_t arg)472 static OffThreadJob* LookupOffThreadJobForArgs(JSContext* cx, ScriptKind kind,
473 const CallArgs& args,
474 size_t arg) {
475 // If the optional ID argument isn't present, get the single pending job.
476 if (args.length() <= arg) {
477 return GetSingleOffThreadJob(cx, kind);
478 }
479
480 // Lookup the job using the specified ID.
481 int32_t id = 0;
482 RootedValue value(cx, args[arg]);
483 if (!ToInt32(cx, value, &id)) {
484 return nullptr;
485 }
486
487 return LookupOffThreadJobByID(cx, kind, id);
488 }
489
DeleteOffThreadJob(JSContext * cx,OffThreadJob * job)490 static void DeleteOffThreadJob(JSContext* cx, OffThreadJob* job) {
491 ShellContext* sc = GetShellContext(cx);
492 for (size_t i = 0; i < sc->offThreadJobs.length(); i++) {
493 if (sc->offThreadJobs[i] == job) {
494 sc->offThreadJobs.erase(&sc->offThreadJobs[i]);
495 js_delete(job);
496 return;
497 }
498 }
499
500 MOZ_CRASH("Off-thread job not found");
501 }
502
CancelOffThreadJobsForContext(JSContext * cx)503 static void CancelOffThreadJobsForContext(JSContext* cx) {
504 // Parse jobs may be blocked waiting on GC.
505 gc::FinishGC(cx);
506
507 // Wait for jobs belonging to this context.
508 ShellContext* sc = GetShellContext(cx);
509 while (!sc->offThreadJobs.empty()) {
510 OffThreadJob* job = sc->offThreadJobs.popCopy();
511 job->waitUntilDone(cx);
512 js_delete(job);
513 }
514 }
515
CancelOffThreadJobsForRuntime(JSContext * cx)516 static void CancelOffThreadJobsForRuntime(JSContext* cx) {
517 // Parse jobs may be blocked waiting on GC.
518 gc::FinishGC(cx);
519
520 // Cancel jobs belonging to this runtime.
521 CancelOffThreadParses(cx->runtime());
522 ShellContext* sc = GetShellContext(cx);
523 while (!sc->offThreadJobs.empty()) {
524 js_delete(sc->offThreadJobs.popCopy());
525 }
526 }
527
528 mozilla::Atomic<int32_t> gOffThreadJobSerial(1);
529
OffThreadJob(ShellContext * sc,ScriptKind kind,bool useOffThreadParseGlobal,Source && source)530 OffThreadJob::OffThreadJob(ShellContext* sc, ScriptKind kind,
531 bool useOffThreadParseGlobal, Source&& source)
532 : id(gOffThreadJobSerial++),
533 kind(kind),
534 useOffThreadParseGlobal(useOffThreadParseGlobal),
535 monitor(sc->offThreadMonitor),
536 state(RUNNING),
537 token(nullptr),
538 source(std::move(source)) {
539 MOZ_RELEASE_ASSERT(id > 0, "Off-thread job IDs exhausted");
540 }
541
~OffThreadJob()542 OffThreadJob::~OffThreadJob() { MOZ_ASSERT(state != RUNNING); }
543
cancel()544 void OffThreadJob::cancel() {
545 MOZ_ASSERT(state == RUNNING);
546 MOZ_ASSERT(!token);
547
548 state = CANCELLED;
549 }
550
markDone(JS::OffThreadToken * newToken)551 void OffThreadJob::markDone(JS::OffThreadToken* newToken) {
552 AutoLockMonitor alm(monitor);
553 MOZ_ASSERT(state == RUNNING);
554 MOZ_ASSERT(!token);
555 MOZ_ASSERT(newToken);
556
557 token = newToken;
558 state = DONE;
559 alm.notifyAll();
560 }
561
waitUntilDone(JSContext * cx)562 JS::OffThreadToken* OffThreadJob::waitUntilDone(JSContext* cx) {
563 AutoLockMonitor alm(monitor);
564 MOZ_ASSERT(state != CANCELLED);
565
566 while (state != DONE) {
567 alm.wait();
568 }
569
570 MOZ_ASSERT(token);
571 return token;
572 }
573
574 struct ShellCompartmentPrivate {
575 GCPtrObject grayRoot;
576 };
577
578 struct MOZ_STACK_CLASS EnvironmentPreparer
579 : public js::ScriptEnvironmentPreparer {
EnvironmentPreparerEnvironmentPreparer580 explicit EnvironmentPreparer(JSContext* cx) {
581 js::SetScriptEnvironmentPreparer(cx, this);
582 }
583 void invoke(JS::HandleObject global, Closure& closure) override;
584 };
585
586 const char* shell::selfHostedXDRPath = nullptr;
587 bool shell::encodeSelfHostedCode = false;
588 bool shell::enableCodeCoverage = false;
589 bool shell::enableDisassemblyDumps = false;
590 bool shell::offthreadCompilation = false;
591 bool shell::enableAsmJS = false;
592 bool shell::enableWasm = false;
593 bool shell::enableSharedMemory = SHARED_MEMORY_DEFAULT;
594 bool shell::enableWasmBaseline = false;
595 bool shell::enableWasmOptimizing = false;
596
597 #define WASM_DEFAULT_FEATURE(NAME, ...) bool shell::enableWasm##NAME = true;
598 #define WASM_EXPERIMENTAL_FEATURE(NAME, ...) \
599 bool shell::enableWasm##NAME = false;
600 JS_FOR_WASM_FEATURES(WASM_DEFAULT_FEATURE, WASM_EXPERIMENTAL_FEATURE);
601 #undef WASM_DEFAULT_FEATURE
602 #undef WASM_EXPERIMENTAL_FEATURE
603
604 #ifdef ENABLE_WASM_SIMD_WORMHOLE
605 bool shell::enableWasmSimdWormhole = false;
606 #endif
607 bool shell::enableWasmVerbose = false;
608 bool shell::enableTestWasmAwaitTier2 = false;
609 bool shell::enableSourcePragmas = true;
610 bool shell::enableAsyncStacks = false;
611 bool shell::enableAsyncStackCaptureDebuggeeOnly = false;
612 bool shell::enableStreams = false;
613 bool shell::enableReadableByteStreams = false;
614 bool shell::enableBYOBStreamReaders = false;
615 bool shell::enableWritableStreams = false;
616 bool shell::enableReadableStreamPipeTo = false;
617 bool shell::enableWeakRefs = false;
618 bool shell::enableToSource = false;
619 bool shell::enablePropertyErrorMessageFix = false;
620 bool shell::enableIteratorHelpers = false;
621 bool shell::enablePrivateClassFields = false;
622 bool shell::enablePrivateClassMethods = false;
623 bool shell::enableTopLevelAwait = true;
624 bool shell::enableErgonomicBrandChecks = true;
625 bool shell::useOffThreadParseGlobal = true;
626 bool shell::enableClassStaticBlocks = false;
627 #ifdef JS_GC_ZEAL
628 uint32_t shell::gZealBits = 0;
629 uint32_t shell::gZealFrequency = 0;
630 #endif
631 bool shell::printTiming = false;
632 RCFile* shell::gErrFile = nullptr;
633 RCFile* shell::gOutFile = nullptr;
634 bool shell::reportWarnings = true;
635 bool shell::compileOnly = false;
636 bool shell::disableOOMFunctions = false;
637 bool shell::defaultToSameCompartment = true;
638
639 bool shell::useFdlibmForSinCosTan = false;
640
641 #ifdef DEBUG
642 bool shell::dumpEntrainedVariables = false;
643 bool shell::OOM_printAllocationCount = false;
644 #endif
645
646 UniqueChars shell::processWideModuleLoadPath;
647
648 static bool SetTimeoutValue(JSContext* cx, double t);
649
650 static void KillWatchdog(JSContext* cx);
651
652 static bool ScheduleWatchdog(JSContext* cx, double t);
653
654 static void CancelExecution(JSContext* cx);
655
656 enum class ShellGlobalKind {
657 GlobalObject,
658 WindowProxy,
659 };
660
661 static JSObject* NewGlobalObject(JSContext* cx, JS::RealmOptions& options,
662 JSPrincipals* principals, ShellGlobalKind kind,
663 bool immutablePrototype);
664
665 /*
666 * A toy WindowProxy class for the shell. This is intended for testing code
667 * where global |this| is a WindowProxy. All requests are forwarded to the
668 * underlying global and no navigation is supported.
669 */
670 const JSClass ShellWindowProxyClass =
671 PROXY_CLASS_DEF("ShellWindowProxy", JSCLASS_HAS_RESERVED_SLOTS(1));
672
NewShellWindowProxy(JSContext * cx,JS::HandleObject global)673 JSObject* NewShellWindowProxy(JSContext* cx, JS::HandleObject global) {
674 MOZ_ASSERT(global->is<GlobalObject>());
675
676 js::WrapperOptions options;
677 options.setClass(&ShellWindowProxyClass);
678
679 JSAutoRealm ar(cx, global);
680 JSObject* obj =
681 js::Wrapper::New(cx, global, &js::Wrapper::singleton, options);
682 MOZ_ASSERT_IF(obj, js::IsWindowProxy(obj));
683 return obj;
684 }
685
686 /*
687 * A toy principals type for the shell.
688 *
689 * In the shell, a principal is simply a 32-bit mask: P subsumes Q if the
690 * set bits in P are a superset of those in Q. Thus, the principal 0 is
691 * subsumed by everything, and the principal ~0 subsumes everything.
692 *
693 * As a special case, a null pointer as a principal is treated like 0xffff.
694 *
695 * The 'newGlobal' function takes an option indicating which principal the
696 * new global should have; 'evaluate' does for the new code.
697 */
698 class ShellPrincipals final : public JSPrincipals {
699 uint32_t bits;
700
getBits(JSPrincipals * p)701 static uint32_t getBits(JSPrincipals* p) {
702 if (!p) {
703 return 0xffff;
704 }
705 return static_cast<ShellPrincipals*>(p)->bits;
706 }
707
708 public:
ShellPrincipals(uint32_t bits,int32_t refcount=0)709 explicit ShellPrincipals(uint32_t bits, int32_t refcount = 0) : bits(bits) {
710 this->refcount = refcount;
711 }
712
write(JSContext * cx,JSStructuredCloneWriter * writer)713 bool write(JSContext* cx, JSStructuredCloneWriter* writer) override {
714 // The shell doesn't have a read principals hook, so it doesn't really
715 // matter what we write here, but we have to write something so the
716 // fuzzer is happy.
717 return JS_WriteUint32Pair(writer, bits, 0);
718 }
719
isSystemOrAddonPrincipal()720 bool isSystemOrAddonPrincipal() override { return true; }
721
destroy(JSPrincipals * principals)722 static void destroy(JSPrincipals* principals) {
723 MOZ_ASSERT(principals != &fullyTrusted);
724 MOZ_ASSERT(principals->refcount == 0);
725 js_delete(static_cast<const ShellPrincipals*>(principals));
726 }
727
subsumes(JSPrincipals * first,JSPrincipals * second)728 static bool subsumes(JSPrincipals* first, JSPrincipals* second) {
729 uint32_t firstBits = getBits(first);
730 uint32_t secondBits = getBits(second);
731 return (firstBits | secondBits) == firstBits;
732 }
733
734 static JSSecurityCallbacks securityCallbacks;
735
736 // Fully-trusted principals singleton.
737 static ShellPrincipals fullyTrusted;
738 };
739
740 JSSecurityCallbacks ShellPrincipals::securityCallbacks = {
741 nullptr, // contentSecurityPolicyAllows
742 subsumes};
743
744 // The fully-trusted principal subsumes all other principals.
745 ShellPrincipals ShellPrincipals::fullyTrusted(-1, 1);
746
747 #ifdef EDITLINE
748 extern "C" {
749 extern MOZ_EXPORT char* readline(const char* prompt);
750 extern MOZ_EXPORT void add_history(char* line);
751 } // extern "C"
752 #endif
753
ShellContext(JSContext * cx)754 ShellContext::ShellContext(JSContext* cx)
755 : isWorker(false),
756 lastWarningEnabled(false),
757 trackUnhandledRejections(true),
758 timeoutInterval(-1.0),
759 startTime(PRMJ_Now()),
760 serviceInterrupt(false),
761 haveInterruptFunc(false),
762 interruptFunc(cx, NullValue()),
763 lastWarning(cx, NullValue()),
764 promiseRejectionTrackerCallback(cx, NullValue()),
765 unhandledRejectedPromises(cx),
766 watchdogLock(mutexid::ShellContextWatchdog),
767 exitCode(0),
768 quitting(false),
769 readLineBufPos(0),
770 errFilePtr(nullptr),
771 outFilePtr(nullptr),
772 offThreadMonitor(mutexid::ShellOffThreadState),
773 finalizationRegistryCleanupCallbacks(cx) {}
774
~ShellContext()775 ShellContext::~ShellContext() { MOZ_ASSERT(offThreadJobs.empty()); }
776
GetShellContext(JSContext * cx)777 ShellContext* js::shell::GetShellContext(JSContext* cx) {
778 ShellContext* sc = static_cast<ShellContext*>(JS_GetContextPrivate(cx));
779 MOZ_ASSERT(sc);
780 return sc;
781 }
782
TraceGrayRoots(JSTracer * trc,void * data)783 static void TraceGrayRoots(JSTracer* trc, void* data) {
784 JSRuntime* rt = trc->runtime();
785 for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
786 for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) {
787 auto priv = static_cast<ShellCompartmentPrivate*>(
788 JS_GetCompartmentPrivate(comp.get()));
789 if (priv) {
790 TraceNullableEdge(trc, &priv->grayRoot, "test gray root");
791 }
792 }
793 }
794 }
795
GetLine(FILE * file,const char * prompt)796 static mozilla::UniqueFreePtr<char[]> GetLine(FILE* file, const char* prompt) {
797 #ifdef EDITLINE
798 /*
799 * Use readline only if file is stdin, because there's no way to specify
800 * another handle. Are other filehandles interactive?
801 */
802 if (file == stdin) {
803 mozilla::UniqueFreePtr<char[]> linep(readline(prompt));
804 /*
805 * We set it to zero to avoid complaining about inappropriate ioctl
806 * for device in the case of EOF. Looks like errno == 251 if line is
807 * finished with EOF and errno == 25 (EINVAL on Mac) if there is
808 * nothing left to read.
809 */
810 if (errno == 251 || errno == 25 || errno == EINVAL) {
811 errno = 0;
812 }
813 if (!linep) {
814 return nullptr;
815 }
816 if (linep[0] != '\0') {
817 add_history(linep.get());
818 }
819 return linep;
820 }
821 #endif
822
823 size_t len = 0;
824 if (*prompt != '\0' && gOutFile->isOpen()) {
825 fprintf(gOutFile->fp, "%s", prompt);
826 fflush(gOutFile->fp);
827 }
828
829 size_t size = 80;
830 mozilla::UniqueFreePtr<char[]> buffer(static_cast<char*>(malloc(size)));
831 if (!buffer) {
832 return nullptr;
833 }
834
835 char* current = buffer.get();
836 do {
837 while (true) {
838 if (fgets(current, size - len, file)) {
839 break;
840 }
841 if (errno != EINTR) {
842 return nullptr;
843 }
844 }
845
846 len += strlen(current);
847 char* t = buffer.get() + len - 1;
848 if (*t == '\n') {
849 /* Line was read. We remove '\n' and exit. */
850 *t = '\0';
851 break;
852 }
853
854 if (len + 1 == size) {
855 size = size * 2;
856 char* raw = buffer.release();
857 char* tmp = static_cast<char*>(realloc(raw, size));
858 if (!tmp) {
859 free(raw);
860 return nullptr;
861 }
862 buffer.reset(tmp);
863 }
864 current = buffer.get() + len;
865 } while (true);
866 return buffer;
867 }
868
ShellInterruptCallback(JSContext * cx)869 static bool ShellInterruptCallback(JSContext* cx) {
870 ShellContext* sc = GetShellContext(cx);
871 if (!sc->serviceInterrupt) {
872 return true;
873 }
874
875 // Reset serviceInterrupt. CancelExecution or InterruptIf will set it to
876 // true to distinguish watchdog or user triggered interrupts.
877 // Do this first to prevent other interrupts that may occur while the
878 // user-supplied callback is executing from re-entering the handler.
879 sc->serviceInterrupt = false;
880
881 bool result;
882 if (sc->haveInterruptFunc) {
883 bool wasAlreadyThrowing = cx->isExceptionPending();
884 JS::AutoSaveExceptionState savedExc(cx);
885 JSAutoRealm ar(cx, &sc->interruptFunc.toObject());
886 RootedValue rval(cx);
887
888 // Report any exceptions thrown by the JS interrupt callback, but do
889 // *not* keep it on the cx. The interrupt handler is invoked at points
890 // that are not expected to throw catchable exceptions, like at
891 // JSOp::RetRval.
892 //
893 // If the interrupted JS code was already throwing, any exceptions
894 // thrown by the interrupt handler are silently swallowed.
895 {
896 Maybe<AutoReportException> are;
897 if (!wasAlreadyThrowing) {
898 are.emplace(cx);
899 }
900 result = JS_CallFunctionValue(cx, nullptr, sc->interruptFunc,
901 JS::HandleValueArray::empty(), &rval);
902 }
903 savedExc.restore();
904
905 if (rval.isBoolean()) {
906 result = rval.toBoolean();
907 } else {
908 result = false;
909 }
910 } else {
911 result = false;
912 }
913
914 if (!result && sc->exitCode == 0) {
915 static const char msg[] = "Script terminated by interrupt handler.\n";
916 fputs(msg, stderr);
917
918 sc->exitCode = EXITCODE_TIMEOUT;
919 }
920
921 return result;
922 }
923
924 /*
925 * Some UTF-8 files, notably those written using Notepad, have a Unicode
926 * Byte-Order-Mark (BOM) as their first character. This is useless (byte-order
927 * is meaningless for UTF-8) but causes a syntax error unless we skip it.
928 */
SkipUTF8BOM(FILE * file)929 static void SkipUTF8BOM(FILE* file) {
930 int ch1 = fgetc(file);
931 int ch2 = fgetc(file);
932 int ch3 = fgetc(file);
933
934 // Skip the BOM
935 if (ch1 == 0xEF && ch2 == 0xBB && ch3 == 0xBF) {
936 return;
937 }
938
939 // No BOM - revert
940 if (ch3 != EOF) {
941 ungetc(ch3, file);
942 }
943 if (ch2 != EOF) {
944 ungetc(ch2, file);
945 }
946 if (ch1 != EOF) {
947 ungetc(ch1, file);
948 }
949 }
950
invoke(HandleObject global,Closure & closure)951 void EnvironmentPreparer::invoke(HandleObject global, Closure& closure) {
952 MOZ_ASSERT(JS_IsGlobalObject(global));
953
954 JSContext* cx = TlsContext.get();
955 MOZ_ASSERT(!JS_IsExceptionPending(cx));
956
957 AutoRealm ar(cx, global);
958 AutoReportException are(cx);
959 if (!closure(cx)) {
960 return;
961 }
962 }
963
CreateScriptPrivate(JSContext * cx,HandleString path)964 JSObject* js::shell::CreateScriptPrivate(JSContext* cx, HandleString path) {
965 RootedObject info(cx, JS_NewPlainObject(cx));
966 if (!info) {
967 return nullptr;
968 }
969
970 if (path) {
971 RootedValue pathValue(cx, StringValue(path));
972 if (!JS_DefineProperty(cx, info, "path", pathValue, JSPROP_ENUMERATE)) {
973 return nullptr;
974 }
975 }
976
977 return info;
978 }
979
RegisterScriptPathWithModuleLoader(JSContext * cx,HandleScript script,const char * filename)980 static bool RegisterScriptPathWithModuleLoader(JSContext* cx,
981 HandleScript script,
982 const char* filename) {
983 // Set the private value associated with a script to a object containing the
984 // script's filename so that the module loader can use it to resolve
985 // relative imports.
986
987 RootedString path(cx, JS_NewStringCopyZ(cx, filename));
988 if (!path) {
989 return false;
990 }
991
992 MOZ_ASSERT(JS::GetScriptPrivate(script).isUndefined());
993 RootedObject infoObject(cx, CreateScriptPrivate(cx, path));
994 if (!infoObject) {
995 return false;
996 }
997
998 JS::SetScriptPrivate(script, ObjectValue(*infoObject));
999 return true;
1000 }
1001
1002 enum class CompileUtf8 {
1003 InflateToUtf16,
1004 DontInflate,
1005 };
1006
RunFile(JSContext * cx,const char * filename,FILE * file,CompileUtf8 compileMethod,bool compileOnly)1007 [[nodiscard]] static bool RunFile(JSContext* cx, const char* filename,
1008 FILE* file, CompileUtf8 compileMethod,
1009 bool compileOnly) {
1010 SkipUTF8BOM(file);
1011
1012 int64_t t1 = PRMJ_Now();
1013 RootedScript script(cx);
1014
1015 {
1016 CompileOptions options(cx);
1017 options.setIntroductionType("js shell file")
1018 .setFileAndLine(filename, 1)
1019 .setIsRunOnce(true)
1020 .setNoScriptRval(true);
1021
1022 if (compileMethod == CompileUtf8::DontInflate) {
1023 script = JS::CompileUtf8File(cx, options, file);
1024 } else {
1025 fprintf(stderr, "(compiling '%s' after inflating to UTF-16)\n", filename);
1026
1027 FileContents buffer(cx);
1028 if (!ReadCompleteFile(cx, file, buffer)) {
1029 return false;
1030 }
1031
1032 size_t length = buffer.length();
1033 auto chars = UniqueTwoByteChars(
1034 UTF8CharsToNewTwoByteCharsZ(
1035 cx,
1036 JS::UTF8Chars(reinterpret_cast<const char*>(buffer.begin()),
1037 buffer.length()),
1038 &length, js::MallocArena)
1039 .get());
1040 if (!chars) {
1041 return false;
1042 }
1043
1044 JS::SourceText<char16_t> source;
1045 if (!source.init(cx, std::move(chars), length)) {
1046 return false;
1047 }
1048
1049 script = JS::Compile(cx, options, source);
1050 }
1051
1052 if (!script) {
1053 return false;
1054 }
1055 }
1056
1057 if (!RegisterScriptPathWithModuleLoader(cx, script, filename)) {
1058 return false;
1059 }
1060
1061 #ifdef DEBUG
1062 if (dumpEntrainedVariables) {
1063 AnalyzeEntrainedVariables(cx, script);
1064 }
1065 #endif
1066 if (!compileOnly) {
1067 if (!JS_ExecuteScript(cx, script)) {
1068 return false;
1069 }
1070 int64_t t2 = PRMJ_Now() - t1;
1071 if (printTiming) {
1072 printf("runtime = %.3f ms\n", double(t2) / PRMJ_USEC_PER_MSEC);
1073 }
1074 }
1075 return true;
1076 }
1077
RunModule(JSContext * cx,const char * filename,bool compileOnly)1078 [[nodiscard]] static bool RunModule(JSContext* cx, const char* filename,
1079 bool compileOnly) {
1080 ShellContext* sc = GetShellContext(cx);
1081
1082 RootedString path(cx, JS_NewStringCopyZ(cx, filename));
1083 if (!path) {
1084 return false;
1085 }
1086
1087 path = ResolvePath(cx, path, RootRelative);
1088 if (!path) {
1089 return false;
1090 }
1091
1092 return sc->moduleLoader->loadRootModule(cx, path);
1093 }
1094
ShellCleanupFinalizationRegistryCallback(JSFunction * doCleanup,JSObject * incumbentGlobal,void * data)1095 static void ShellCleanupFinalizationRegistryCallback(JSFunction* doCleanup,
1096 JSObject* incumbentGlobal,
1097 void* data) {
1098 // In the browser this queues a task. Shell jobs correspond to microtasks so
1099 // we arrange for cleanup to happen after all jobs/microtasks have run. The
1100 // incumbent global is ignored in the shell.
1101
1102 auto sc = static_cast<ShellContext*>(data);
1103 AutoEnterOOMUnsafeRegion oomUnsafe;
1104 if (!sc->finalizationRegistryCleanupCallbacks.append(doCleanup)) {
1105 oomUnsafe.crash("ShellCleanupFinalizationRegistryCallback");
1106 }
1107 }
1108
1109 // Run any FinalizationRegistry cleanup tasks and return whether any ran.
MaybeRunFinalizationRegistryCleanupTasks(JSContext * cx)1110 static bool MaybeRunFinalizationRegistryCleanupTasks(JSContext* cx) {
1111 ShellContext* sc = GetShellContext(cx);
1112 MOZ_ASSERT(!sc->quitting);
1113
1114 Rooted<ShellContext::FunctionVector> callbacks(cx);
1115 std::swap(callbacks.get(), sc->finalizationRegistryCleanupCallbacks.get());
1116
1117 bool ranTasks = false;
1118
1119 RootedFunction callback(cx);
1120 for (JSFunction* f : callbacks) {
1121 callback = f;
1122
1123 AutoRealm ar(cx, f);
1124
1125 {
1126 AutoReportException are(cx);
1127 RootedValue unused(cx);
1128 (void)JS_CallFunction(cx, nullptr, callback, HandleValueArray::empty(),
1129 &unused);
1130 }
1131
1132 ranTasks = true;
1133
1134 if (sc->quitting) {
1135 break;
1136 }
1137 }
1138
1139 return ranTasks;
1140 }
1141
EnqueueJob(JSContext * cx,unsigned argc,Value * vp)1142 static bool EnqueueJob(JSContext* cx, unsigned argc, Value* vp) {
1143 CallArgs args = CallArgsFromVp(argc, vp);
1144
1145 if (!IsFunctionObject(args.get(0))) {
1146 JS_ReportErrorASCII(cx, "EnqueueJob's first argument must be a function");
1147 return false;
1148 }
1149
1150 args.rval().setUndefined();
1151
1152 RootedObject job(cx, &args[0].toObject());
1153 return js::EnqueueJob(cx, job);
1154 }
1155
RunShellJobs(JSContext * cx)1156 static void RunShellJobs(JSContext* cx) {
1157 ShellContext* sc = GetShellContext(cx);
1158 if (sc->quitting) {
1159 return;
1160 }
1161
1162 while (true) {
1163 // Run microtasks.
1164 js::RunJobs(cx);
1165 if (sc->quitting) {
1166 return;
1167 }
1168
1169 // Run tasks (only finalization registry clean tasks are possible).
1170 bool ranTasks = MaybeRunFinalizationRegistryCleanupTasks(cx);
1171 if (!ranTasks) {
1172 break;
1173 }
1174 }
1175 }
1176
DrainJobQueue(JSContext * cx,unsigned argc,Value * vp)1177 static bool DrainJobQueue(JSContext* cx, unsigned argc, Value* vp) {
1178 CallArgs args = CallArgsFromVp(argc, vp);
1179
1180 if (GetShellContext(cx)->quitting) {
1181 JS_ReportErrorASCII(
1182 cx, "Mustn't drain the job queue when the shell is quitting");
1183 return false;
1184 }
1185
1186 RunShellJobs(cx);
1187
1188 if (GetShellContext(cx)->quitting) {
1189 return false;
1190 }
1191
1192 args.rval().setUndefined();
1193 return true;
1194 }
1195
GlobalOfFirstJobInQueue(JSContext * cx,unsigned argc,Value * vp)1196 static bool GlobalOfFirstJobInQueue(JSContext* cx, unsigned argc, Value* vp) {
1197 CallArgs args = CallArgsFromVp(argc, vp);
1198
1199 RootedObject job(cx, cx->internalJobQueue->maybeFront());
1200 if (!job) {
1201 JS_ReportErrorASCII(cx, "Job queue is empty");
1202 return false;
1203 }
1204
1205 RootedObject global(cx, &job->nonCCWGlobal());
1206 if (!cx->compartment()->wrap(cx, &global)) {
1207 return false;
1208 }
1209
1210 args.rval().setObject(*global);
1211 return true;
1212 }
1213
TrackUnhandledRejections(JSContext * cx,JS::HandleObject promise,JS::PromiseRejectionHandlingState state)1214 static bool TrackUnhandledRejections(JSContext* cx, JS::HandleObject promise,
1215 JS::PromiseRejectionHandlingState state) {
1216 ShellContext* sc = GetShellContext(cx);
1217 if (!sc->trackUnhandledRejections) {
1218 return true;
1219 }
1220
1221 #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
1222 if (cx->runningOOMTest) {
1223 // When OOM happens, we cannot reliably track the set of unhandled
1224 // promise rejections. Throw error only when simulated OOM is used
1225 // *and* promises are used in the test.
1226 JS_ReportErrorASCII(
1227 cx,
1228 "Can't track unhandled rejections while running simulated OOM "
1229 "test. Call ignoreUnhandledRejections before using oomTest etc.");
1230 return false;
1231 }
1232 #endif
1233
1234 if (!sc->unhandledRejectedPromises) {
1235 sc->unhandledRejectedPromises = SetObject::create(cx);
1236 if (!sc->unhandledRejectedPromises) {
1237 return false;
1238 }
1239 }
1240
1241 RootedValue promiseVal(cx, ObjectValue(*promise));
1242
1243 AutoRealm ar(cx, sc->unhandledRejectedPromises);
1244 if (!cx->compartment()->wrap(cx, &promiseVal)) {
1245 return false;
1246 }
1247
1248 switch (state) {
1249 case JS::PromiseRejectionHandlingState::Unhandled:
1250 if (!SetObject::add(cx, sc->unhandledRejectedPromises, promiseVal)) {
1251 return false;
1252 }
1253 break;
1254 case JS::PromiseRejectionHandlingState::Handled:
1255 bool deleted = false;
1256 if (!SetObject::delete_(cx, sc->unhandledRejectedPromises, promiseVal,
1257 &deleted)) {
1258 return false;
1259 }
1260 // We can't MOZ_ASSERT(deleted) here, because it's possible we failed to
1261 // add the promise in the first place, due to OOM.
1262 break;
1263 }
1264
1265 return true;
1266 }
1267
ForwardingPromiseRejectionTrackerCallback(JSContext * cx,bool mutedErrors,JS::HandleObject promise,JS::PromiseRejectionHandlingState state,void * data)1268 static void ForwardingPromiseRejectionTrackerCallback(
1269 JSContext* cx, bool mutedErrors, JS::HandleObject promise,
1270 JS::PromiseRejectionHandlingState state, void* data) {
1271 AutoReportException are(cx);
1272
1273 if (!TrackUnhandledRejections(cx, promise, state)) {
1274 return;
1275 }
1276
1277 RootedValue callback(cx,
1278 GetShellContext(cx)->promiseRejectionTrackerCallback);
1279 if (callback.isNull()) {
1280 return;
1281 }
1282
1283 AutoRealm ar(cx, &callback.toObject());
1284
1285 FixedInvokeArgs<2> args(cx);
1286 args[0].setObject(*promise);
1287 args[1].setInt32(static_cast<int32_t>(state));
1288
1289 if (!JS_WrapValue(cx, args[0])) {
1290 return;
1291 }
1292
1293 RootedValue rval(cx);
1294 (void)Call(cx, callback, UndefinedHandleValue, args, &rval);
1295 }
1296
SetPromiseRejectionTrackerCallback(JSContext * cx,unsigned argc,Value * vp)1297 static bool SetPromiseRejectionTrackerCallback(JSContext* cx, unsigned argc,
1298 Value* vp) {
1299 CallArgs args = CallArgsFromVp(argc, vp);
1300
1301 if (!IsFunctionObject(args.get(0))) {
1302 JS_ReportErrorASCII(
1303 cx,
1304 "setPromiseRejectionTrackerCallback expects a function as its sole "
1305 "argument");
1306 return false;
1307 }
1308
1309 GetShellContext(cx)->promiseRejectionTrackerCallback = args[0];
1310
1311 args.rval().setUndefined();
1312 return true;
1313 }
1314
1315 // clang-format off
1316 #define LIT(NAME) #NAME,
1317 static const char* telemetryNames[JS_TELEMETRY_END + 1] = {
1318 MAP_JS_TELEMETRY(LIT)
1319 "JS_TELEMETRY_END"
1320 };
1321 #undef LIT
1322 // clang-format on
1323
1324 // Telemetry can be executed from multiple threads, and the callback is
1325 // responsible to avoid contention on the recorded telemetry data.
1326 static Mutex* telemetryLock = nullptr;
1327 class MOZ_RAII AutoLockTelemetry : public LockGuard<Mutex> {
1328 using Base = LockGuard<Mutex>;
1329
1330 public:
AutoLockTelemetry()1331 AutoLockTelemetry() : Base(*telemetryLock) { MOZ_ASSERT(telemetryLock); }
1332 };
1333
1334 using TelemetryData = uint32_t;
1335 using TelemetryVec = Vector<TelemetryData, 0, SystemAllocPolicy>;
1336 static mozilla::Array<TelemetryVec, JS_TELEMETRY_END> telemetryResults;
AccumulateTelemetryDataCallback(int id,uint32_t sample,const char * key)1337 static void AccumulateTelemetryDataCallback(int id, uint32_t sample,
1338 const char* key) {
1339 AutoLockTelemetry alt;
1340 // We ignore OOMs while writting teleemtry data.
1341 if (telemetryResults[id].append(sample)) {
1342 return;
1343 }
1344 }
1345
WriteTelemetryDataToDisk(const char * dir)1346 static void WriteTelemetryDataToDisk(const char* dir) {
1347 const int pathLen = 260;
1348 char fileName[pathLen];
1349 Fprinter output;
1350 auto initOutput = [&](const char* name) -> bool {
1351 if (SprintfLiteral(fileName, "%s%s.csv", dir, name) >= pathLen) {
1352 return false;
1353 }
1354 FILE* file = fopen(fileName, "a");
1355 if (!file) {
1356 return false;
1357 }
1358 output.init(file);
1359 return true;
1360 };
1361
1362 for (size_t id = 0; id < JS_TELEMETRY_END; id++) {
1363 auto clear = MakeScopeExit([&] { telemetryResults[id].clearAndFree(); });
1364 if (!initOutput(telemetryNames[id])) {
1365 continue;
1366 }
1367 for (uint32_t data : telemetryResults[id]) {
1368 output.printf("%u\n", data);
1369 }
1370 output.finish();
1371 }
1372 }
1373
1374 #undef MAP_TELEMETRY
1375
BoundToAsyncStack(JSContext * cx,unsigned argc,Value * vp)1376 static bool BoundToAsyncStack(JSContext* cx, unsigned argc, Value* vp) {
1377 CallArgs args = CallArgsFromVp(argc, vp);
1378
1379 RootedValue function(cx, GetFunctionNativeReserved(&args.callee(), 0));
1380 RootedObject options(
1381 cx, &GetFunctionNativeReserved(&args.callee(), 1).toObject());
1382
1383 RootedSavedFrame stack(cx, nullptr);
1384 bool isExplicit;
1385
1386 RootedValue v(cx);
1387
1388 if (!JS_GetProperty(cx, options, "stack", &v)) {
1389 return false;
1390 }
1391 if (!v.isObject() || !v.toObject().is<SavedFrame>()) {
1392 JS_ReportErrorASCII(cx,
1393 "The 'stack' property must be a SavedFrame object.");
1394 return false;
1395 }
1396 stack = &v.toObject().as<SavedFrame>();
1397
1398 if (!JS_GetProperty(cx, options, "cause", &v)) {
1399 return false;
1400 }
1401 RootedString causeString(cx, ToString(cx, v));
1402 if (!causeString) {
1403 MOZ_ASSERT(cx->isExceptionPending());
1404 return false;
1405 }
1406
1407 UniqueChars cause = JS_EncodeStringToUTF8(cx, causeString);
1408 if (!cause) {
1409 MOZ_ASSERT(cx->isExceptionPending());
1410 return false;
1411 }
1412
1413 if (!JS_GetProperty(cx, options, "explicit", &v)) {
1414 return false;
1415 }
1416 isExplicit = v.isUndefined() ? true : ToBoolean(v);
1417
1418 auto kind =
1419 (isExplicit ? JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::EXPLICIT
1420 : JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::IMPLICIT);
1421
1422 JS::AutoSetAsyncStackForNewCalls asasfnckthxbye(cx, stack, cause.get(), kind);
1423 return Call(cx, UndefinedHandleValue, function, JS::HandleValueArray::empty(),
1424 args.rval());
1425 }
1426
BindToAsyncStack(JSContext * cx,unsigned argc,Value * vp)1427 static bool BindToAsyncStack(JSContext* cx, unsigned argc, Value* vp) {
1428 CallArgs args = CallArgsFromVp(argc, vp);
1429
1430 if (args.length() != 2) {
1431 JS_ReportErrorASCII(cx, "bindToAsyncStack takes exactly two arguments.");
1432 return false;
1433 }
1434
1435 if (!args[0].isObject() || !IsCallable(args[0])) {
1436 JS_ReportErrorASCII(
1437 cx, "bindToAsyncStack's first argument should be a function.");
1438 return false;
1439 }
1440
1441 if (!args[1].isObject()) {
1442 JS_ReportErrorASCII(
1443 cx, "bindToAsyncStack's second argument should be an object.");
1444 return false;
1445 }
1446
1447 RootedFunction bound(cx, NewFunctionWithReserved(cx, BoundToAsyncStack, 0, 0,
1448 "bindToAsyncStack thunk"));
1449 if (!bound) {
1450 return false;
1451 }
1452 SetFunctionNativeReserved(bound, 0, args[0]);
1453 SetFunctionNativeReserved(bound, 1, args[1]);
1454
1455 args.rval().setObject(*bound);
1456 return true;
1457 }
1458
1459 #ifdef JS_HAS_INTL_API
AddIntlExtras(JSContext * cx,unsigned argc,Value * vp)1460 static bool AddIntlExtras(JSContext* cx, unsigned argc, Value* vp) {
1461 CallArgs args = CallArgsFromVp(argc, vp);
1462 if (!args.get(0).isObject()) {
1463 JS_ReportErrorASCII(cx, "addIntlExtras must be passed an object");
1464 return false;
1465 }
1466 JS::RootedObject intl(cx, &args[0].toObject());
1467
1468 static const JSFunctionSpec funcs[] = {
1469 JS_SELF_HOSTED_FN("getCalendarInfo", "Intl_getCalendarInfo", 1, 0),
1470 JS_SELF_HOSTED_FN("getLocaleInfo", "Intl_getLocaleInfo", 1, 0),
1471 JS_SELF_HOSTED_FN("getDisplayNames", "Intl_getDisplayNames", 2, 0),
1472 JS_FS_END};
1473
1474 if (!JS_DefineFunctions(cx, intl, funcs)) {
1475 return false;
1476 }
1477
1478 if (!JS::AddMozDateTimeFormatConstructor(cx, intl)) {
1479 return false;
1480 }
1481
1482 if (!JS::AddMozDisplayNamesConstructor(cx, intl)) {
1483 return false;
1484 }
1485
1486 args.rval().setUndefined();
1487 return true;
1488 }
1489 #endif // JS_HAS_INTL_API
1490
EvalUtf8AndPrint(JSContext * cx,const char * bytes,size_t length,int lineno,bool compileOnly)1491 [[nodiscard]] static bool EvalUtf8AndPrint(JSContext* cx, const char* bytes,
1492 size_t length, int lineno,
1493 bool compileOnly) {
1494 // Eval.
1495 JS::CompileOptions options(cx);
1496 options.setIntroductionType("js shell interactive")
1497 .setIsRunOnce(true)
1498 .setFileAndLine("typein", lineno);
1499
1500 JS::SourceText<Utf8Unit> srcBuf;
1501 if (!srcBuf.init(cx, bytes, length, JS::SourceOwnership::Borrowed)) {
1502 return false;
1503 }
1504
1505 RootedScript script(cx, JS::Compile(cx, options, srcBuf));
1506 if (!script) {
1507 return false;
1508 }
1509 if (compileOnly) {
1510 return true;
1511 }
1512 RootedValue result(cx);
1513 if (!JS_ExecuteScript(cx, script, &result)) {
1514 return false;
1515 }
1516
1517 if (!result.isUndefined() && gOutFile->isOpen()) {
1518 // Print.
1519 RootedString str(cx, JS_ValueToSource(cx, result));
1520 if (!str) {
1521 return false;
1522 }
1523
1524 UniqueChars utf8chars = JS_EncodeStringToUTF8(cx, str);
1525 if (!utf8chars) {
1526 return false;
1527 }
1528 fprintf(gOutFile->fp, "%s\n", utf8chars.get());
1529 }
1530 return true;
1531 }
1532
ReadEvalPrintLoop(JSContext * cx,FILE * in,bool compileOnly)1533 [[nodiscard]] static bool ReadEvalPrintLoop(JSContext* cx, FILE* in,
1534 bool compileOnly) {
1535 ShellContext* sc = GetShellContext(cx);
1536 int lineno = 1;
1537 bool hitEOF = false;
1538
1539 do {
1540 /*
1541 * Accumulate lines until we get a 'compilable unit' - one that either
1542 * generates an error (before running out of source) or that compiles
1543 * cleanly. This should be whenever we get a complete statement that
1544 * coincides with the end of a line.
1545 */
1546 int startline = lineno;
1547 typedef Vector<char, 32> CharBuffer;
1548 RootedObject globalLexical(cx, &cx->global()->lexicalEnvironment());
1549 CharBuffer buffer(cx);
1550 do {
1551 ScheduleWatchdog(cx, -1);
1552 sc->serviceInterrupt = false;
1553 errno = 0;
1554
1555 mozilla::UniqueFreePtr<char[]> line =
1556 GetLine(in, startline == lineno ? "js> " : "");
1557 if (!line) {
1558 if (errno) {
1559 /*
1560 * Use Latin1 variant here because strerror(errno)'s
1561 * encoding depends on the user's C locale.
1562 */
1563 JS_ReportErrorLatin1(cx, "%s", strerror(errno));
1564 return false;
1565 }
1566 hitEOF = true;
1567 break;
1568 }
1569
1570 if (!buffer.append(line.get(), strlen(line.get())) ||
1571 !buffer.append('\n')) {
1572 return false;
1573 }
1574
1575 lineno++;
1576 if (!ScheduleWatchdog(cx, sc->timeoutInterval)) {
1577 hitEOF = true;
1578 break;
1579 }
1580 } while (!JS_Utf8BufferIsCompilableUnit(cx, cx->global(), buffer.begin(),
1581 buffer.length()));
1582
1583 if (hitEOF && buffer.empty()) {
1584 break;
1585 }
1586
1587 {
1588 // Report exceptions but keep going.
1589 AutoReportException are(cx);
1590 (void)EvalUtf8AndPrint(cx, buffer.begin(), buffer.length(), startline,
1591 compileOnly);
1592 }
1593
1594 // If a let or const fail to initialize they will remain in an unusable
1595 // without further intervention. This call cleans up the global scope,
1596 // setting uninitialized lexicals to undefined so that they may still
1597 // be used. This behavior is _only_ acceptable in the context of the repl.
1598 if (JS::ForceLexicalInitialization(cx, globalLexical) &&
1599 gErrFile->isOpen()) {
1600 fputs(
1601 "Warning: According to the standard, after the above exception,\n"
1602 "Warning: the global bindings should be permanently uninitialized.\n"
1603 "Warning: We have non-standard-ly initialized them to `undefined`"
1604 "for you.\nWarning: This nicety only happens in the JS shell.\n",
1605 stderr);
1606 }
1607
1608 RunShellJobs(cx);
1609 } while (!hitEOF && !sc->quitting);
1610
1611 if (gOutFile->isOpen()) {
1612 fprintf(gOutFile->fp, "\n");
1613 }
1614
1615 return true;
1616 }
1617
1618 enum FileKind {
1619 FileScript, // UTF-8, directly parsed as such
1620 FileScriptUtf16, // FileScript, but inflate to UTF-16 before parsing
1621 FileModule,
1622 };
1623
ReportCantOpenErrorUnknownEncoding(JSContext * cx,const char * filename)1624 static void ReportCantOpenErrorUnknownEncoding(JSContext* cx,
1625 const char* filename) {
1626 /*
1627 * Filenames are in some random system encoding. *Probably* it's UTF-8,
1628 * but no guarantees.
1629 *
1630 * strerror(errno)'s encoding, in contrast, depends on the user's C locale.
1631 *
1632 * Latin-1 is possibly wrong for both of these -- but at least if it's
1633 * wrong it'll produce mojibake *safely*. Run with Latin-1 til someone
1634 * complains.
1635 */
1636 JS_ReportErrorNumberLatin1(cx, my_GetErrorMessage, nullptr, JSSMSG_CANT_OPEN,
1637 filename, strerror(errno));
1638 }
1639
Process(JSContext * cx,const char * filename,bool forceTTY,FileKind kind)1640 [[nodiscard]] static bool Process(JSContext* cx, const char* filename,
1641 bool forceTTY, FileKind kind) {
1642 FILE* file;
1643 if (forceTTY || !filename || strcmp(filename, "-") == 0) {
1644 file = stdin;
1645 } else {
1646 file = fopen(filename, "rb");
1647 if (!file) {
1648 ReportCantOpenErrorUnknownEncoding(cx, filename);
1649 return false;
1650 }
1651 }
1652 AutoCloseFile autoClose(file);
1653
1654 if (!forceTTY && !isatty(fileno(file))) {
1655 // It's not interactive - just execute it.
1656 switch (kind) {
1657 case FileScript:
1658 if (!RunFile(cx, filename, file, CompileUtf8::DontInflate,
1659 compileOnly)) {
1660 return false;
1661 }
1662 break;
1663 case FileScriptUtf16:
1664 if (!RunFile(cx, filename, file, CompileUtf8::InflateToUtf16,
1665 compileOnly)) {
1666 return false;
1667 }
1668 break;
1669 case FileModule:
1670 if (!RunModule(cx, filename, compileOnly)) {
1671 return false;
1672 }
1673 break;
1674 default:
1675 MOZ_CRASH("Impossible FileKind!");
1676 }
1677 } else {
1678 // It's an interactive filehandle; drop into read-eval-print loop.
1679 MOZ_ASSERT(kind == FileScript);
1680 if (!ReadEvalPrintLoop(cx, file, compileOnly)) {
1681 return false;
1682 }
1683 }
1684 return true;
1685 }
1686
1687 #ifdef XP_WIN
1688 # define GET_FD_FROM_FILE(a) int(_get_osfhandle(fileno(a)))
1689 #else
1690 # define GET_FD_FROM_FILE(a) fileno(a)
1691 #endif
1692
freeExternalCallback(void * contents,void * userData)1693 static void freeExternalCallback(void* contents, void* userData) {
1694 MOZ_ASSERT(!userData);
1695 js_free(contents);
1696 }
1697
CreateExternalArrayBuffer(JSContext * cx,unsigned argc,Value * vp)1698 static bool CreateExternalArrayBuffer(JSContext* cx, unsigned argc, Value* vp) {
1699 CallArgs args = CallArgsFromVp(argc, vp);
1700 if (args.length() != 1) {
1701 JS_ReportErrorNumberASCII(
1702 cx, my_GetErrorMessage, nullptr,
1703 args.length() < 1 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS,
1704 "createExternalArrayBuffer");
1705 return false;
1706 }
1707
1708 int32_t bytes = 0;
1709 if (!ToInt32(cx, args[0], &bytes)) {
1710 return false;
1711 }
1712
1713 if (bytes <= 0) {
1714 JS_ReportErrorASCII(cx, "Size must be positive");
1715 return false;
1716 }
1717
1718 void* buffer = js_malloc(bytes);
1719 if (!buffer) {
1720 JS_ReportOutOfMemory(cx);
1721 return false;
1722 }
1723
1724 RootedObject arrayBuffer(
1725 cx, JS::NewExternalArrayBuffer(cx, bytes, buffer, &freeExternalCallback));
1726 if (!arrayBuffer) {
1727 return false;
1728 }
1729
1730 args.rval().setObject(*arrayBuffer);
1731 return true;
1732 }
1733
CreateMappedArrayBuffer(JSContext * cx,unsigned argc,Value * vp)1734 static bool CreateMappedArrayBuffer(JSContext* cx, unsigned argc, Value* vp) {
1735 CallArgs args = CallArgsFromVp(argc, vp);
1736
1737 if (args.length() < 1 || args.length() > 3) {
1738 JS_ReportErrorNumberASCII(
1739 cx, my_GetErrorMessage, nullptr,
1740 args.length() < 1 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS,
1741 "createMappedArrayBuffer");
1742 return false;
1743 }
1744
1745 RootedString rawFilenameStr(cx, JS::ToString(cx, args[0]));
1746 if (!rawFilenameStr) {
1747 return false;
1748 }
1749 // It's a little bizarre to resolve relative to the script, but for testing
1750 // I need a file at a known location, and the only good way I know of to do
1751 // that right now is to include it in the repo alongside the test script.
1752 // Bug 944164 would introduce an alternative.
1753 JSString* filenameStr = ResolvePath(cx, rawFilenameStr, ScriptRelative);
1754 if (!filenameStr) {
1755 return false;
1756 }
1757 UniqueChars filename = JS_EncodeStringToLatin1(cx, filenameStr);
1758 if (!filename) {
1759 return false;
1760 }
1761
1762 uint32_t offset = 0;
1763 if (args.length() >= 2) {
1764 if (!JS::ToUint32(cx, args[1], &offset)) {
1765 return false;
1766 }
1767 }
1768
1769 bool sizeGiven = false;
1770 uint32_t size;
1771 if (args.length() >= 3) {
1772 if (!JS::ToUint32(cx, args[2], &size)) {
1773 return false;
1774 }
1775 sizeGiven = true;
1776 if (size == 0) {
1777 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1778 JSMSG_BAD_ARRAY_LENGTH);
1779 return false;
1780 }
1781 }
1782
1783 FILE* file = fopen(filename.get(), "rb");
1784 if (!file) {
1785 ReportCantOpenErrorUnknownEncoding(cx, filename.get());
1786 return false;
1787 }
1788 AutoCloseFile autoClose(file);
1789
1790 struct stat st;
1791 if (fstat(fileno(file), &st) < 0) {
1792 JS_ReportErrorASCII(cx, "Unable to stat file");
1793 return false;
1794 }
1795
1796 if ((st.st_mode & S_IFMT) != S_IFREG) {
1797 JS_ReportErrorASCII(cx, "Path is not a regular file");
1798 return false;
1799 }
1800
1801 if (!sizeGiven) {
1802 if (off_t(offset) >= st.st_size) {
1803 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1804 JSMSG_OFFSET_LARGER_THAN_FILESIZE);
1805 return false;
1806 }
1807 size = st.st_size - offset;
1808 }
1809
1810 void* contents =
1811 JS::CreateMappedArrayBufferContents(GET_FD_FROM_FILE(file), offset, size);
1812 if (!contents) {
1813 JS_ReportErrorASCII(cx,
1814 "failed to allocate mapped array buffer contents "
1815 "(possibly due to bad alignment)");
1816 return false;
1817 }
1818
1819 RootedObject obj(cx,
1820 JS::NewMappedArrayBufferWithContents(cx, size, contents));
1821 if (!obj) {
1822 return false;
1823 }
1824
1825 args.rval().setObject(*obj);
1826 return true;
1827 }
1828
1829 #undef GET_FD_FROM_FILE
1830
AddPromiseReactions(JSContext * cx,unsigned argc,Value * vp)1831 static bool AddPromiseReactions(JSContext* cx, unsigned argc, Value* vp) {
1832 CallArgs args = CallArgsFromVp(argc, vp);
1833
1834 if (args.length() != 3) {
1835 JS_ReportErrorNumberASCII(
1836 cx, my_GetErrorMessage, nullptr,
1837 args.length() < 3 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS,
1838 "addPromiseReactions");
1839 return false;
1840 }
1841
1842 RootedObject promise(cx);
1843 if (args[0].isObject()) {
1844 promise = &args[0].toObject();
1845 }
1846
1847 if (!promise || !JS::IsPromiseObject(promise)) {
1848 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
1849 JSSMSG_INVALID_ARGS, "addPromiseReactions");
1850 return false;
1851 }
1852
1853 RootedObject onResolve(cx);
1854 if (args[1].isObject()) {
1855 onResolve = &args[1].toObject();
1856 }
1857
1858 RootedObject onReject(cx);
1859 if (args[2].isObject()) {
1860 onReject = &args[2].toObject();
1861 }
1862
1863 if (!onResolve || !onResolve->is<JSFunction>() || !onReject ||
1864 !onReject->is<JSFunction>()) {
1865 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
1866 JSSMSG_INVALID_ARGS, "addPromiseReactions");
1867 return false;
1868 }
1869
1870 return JS::AddPromiseReactions(cx, promise, onResolve, onReject);
1871 }
1872
IgnoreUnhandledRejections(JSContext * cx,unsigned argc,Value * vp)1873 static bool IgnoreUnhandledRejections(JSContext* cx, unsigned argc, Value* vp) {
1874 CallArgs args = CallArgsFromVp(argc, vp);
1875
1876 ShellContext* sc = GetShellContext(cx);
1877 sc->trackUnhandledRejections = false;
1878
1879 args.rval().setUndefined();
1880 return true;
1881 }
1882
Options(JSContext * cx,unsigned argc,Value * vp)1883 static bool Options(JSContext* cx, unsigned argc, Value* vp) {
1884 CallArgs args = CallArgsFromVp(argc, vp);
1885
1886 JS::ContextOptions oldContextOptions = JS::ContextOptionsRef(cx);
1887 for (unsigned i = 0; i < args.length(); i++) {
1888 RootedString str(cx, JS::ToString(cx, args[i]));
1889 if (!str) {
1890 return false;
1891 }
1892
1893 RootedLinearString opt(cx, str->ensureLinear(cx));
1894 if (!opt) {
1895 return false;
1896 }
1897
1898 if (StringEqualsLiteral(opt, "throw_on_asmjs_validation_failure")) {
1899 JS::ContextOptionsRef(cx).toggleThrowOnAsmJSValidationFailure();
1900 } else if (StringEqualsLiteral(opt, "strict_mode")) {
1901 JS::ContextOptionsRef(cx).toggleStrictMode();
1902 } else {
1903 UniqueChars optChars = QuoteString(cx, opt, '"');
1904 if (!optChars) {
1905 return false;
1906 }
1907
1908 JS_ReportErrorASCII(cx,
1909 "unknown option name %s."
1910 " The valid names are "
1911 "throw_on_asmjs_validation_failure and strict_mode.",
1912 optChars.get());
1913 return false;
1914 }
1915 }
1916
1917 UniqueChars names = DuplicateString("");
1918 bool found = false;
1919 if (names && oldContextOptions.throwOnAsmJSValidationFailure()) {
1920 names = JS_sprintf_append(std::move(names), "%s%s", found ? "," : "",
1921 "throw_on_asmjs_validation_failure");
1922 found = true;
1923 }
1924 if (names && oldContextOptions.strictMode()) {
1925 names = JS_sprintf_append(std::move(names), "%s%s", found ? "," : "",
1926 "strict_mode");
1927 found = true;
1928 }
1929 if (!names) {
1930 JS_ReportOutOfMemory(cx);
1931 return false;
1932 }
1933
1934 JSString* str = JS_NewStringCopyZ(cx, names.get());
1935 if (!str) {
1936 return false;
1937 }
1938 args.rval().setString(str);
1939 return true;
1940 }
1941
LoadScript(JSContext * cx,unsigned argc,Value * vp,bool scriptRelative)1942 static bool LoadScript(JSContext* cx, unsigned argc, Value* vp,
1943 bool scriptRelative) {
1944 CallArgs args = CallArgsFromVp(argc, vp);
1945
1946 RootedString str(cx);
1947 for (unsigned i = 0; i < args.length(); i++) {
1948 str = JS::ToString(cx, args[i]);
1949 if (!str) {
1950 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
1951 JSSMSG_INVALID_ARGS, "load");
1952 return false;
1953 }
1954
1955 str = ResolvePath(cx, str, scriptRelative ? ScriptRelative : RootRelative);
1956 if (!str) {
1957 JS_ReportErrorASCII(cx, "unable to resolve path");
1958 return false;
1959 }
1960
1961 UniqueChars filename = JS_EncodeStringToLatin1(cx, str);
1962 if (!filename) {
1963 return false;
1964 }
1965
1966 errno = 0;
1967
1968 CompileOptions opts(cx);
1969 opts.setIntroductionType("js shell load")
1970 .setIsRunOnce(true)
1971 .setNoScriptRval(true);
1972
1973 RootedValue unused(cx);
1974 if (!(compileOnly
1975 ? JS::CompileUtf8Path(cx, opts, filename.get()) != nullptr
1976 : JS::EvaluateUtf8Path(cx, opts, filename.get(), &unused))) {
1977 return false;
1978 }
1979 }
1980
1981 args.rval().setUndefined();
1982 return true;
1983 }
1984
Load(JSContext * cx,unsigned argc,Value * vp)1985 static bool Load(JSContext* cx, unsigned argc, Value* vp) {
1986 return LoadScript(cx, argc, vp, false);
1987 }
1988
LoadScriptRelativeToScript(JSContext * cx,unsigned argc,Value * vp)1989 static bool LoadScriptRelativeToScript(JSContext* cx, unsigned argc,
1990 Value* vp) {
1991 return LoadScript(cx, argc, vp, true);
1992 }
1993
ParseDebugMetadata(JSContext * cx,HandleObject opts,MutableHandleValue privateValue,MutableHandleString elementAttributeName)1994 static bool ParseDebugMetadata(JSContext* cx, HandleObject opts,
1995 MutableHandleValue privateValue,
1996 MutableHandleString elementAttributeName) {
1997 RootedValue v(cx);
1998 RootedString s(cx);
1999
2000 if (!JS_GetProperty(cx, opts, "element", &v)) {
2001 return false;
2002 }
2003 if (v.isObject()) {
2004 RootedObject infoObject(cx, CreateScriptPrivate(cx));
2005 if (!infoObject) {
2006 return false;
2007 }
2008 RootedValue elementValue(cx, v);
2009 if (!JS_WrapValue(cx, &elementValue)) {
2010 return false;
2011 }
2012 if (!JS_DefineProperty(cx, infoObject, "element", elementValue, 0)) {
2013 return false;
2014 }
2015 privateValue.set(ObjectValue(*infoObject));
2016 }
2017
2018 if (!JS_GetProperty(cx, opts, "elementAttributeName", &v)) {
2019 return false;
2020 }
2021 if (!v.isUndefined()) {
2022 s = ToString(cx, v);
2023 if (!s) {
2024 return false;
2025 }
2026 elementAttributeName.set(s);
2027 }
2028
2029 return true;
2030 }
2031
my_LargeAllocFailCallback()2032 static void my_LargeAllocFailCallback() {
2033 JSContext* cx = TlsContext.get();
2034 if (!cx || cx->isHelperThreadContext()) {
2035 return;
2036 }
2037
2038 MOZ_ASSERT(!JS::RuntimeHeapIsBusy());
2039
2040 JS::PrepareForFullGC(cx);
2041 cx->runtime()->gc.gc(JS::GCOptions::Shrink,
2042 JS::GCReason::SHARED_MEMORY_LIMIT);
2043 }
2044
2045 static const uint32_t CacheEntry_SOURCE = 0;
2046 static const uint32_t CacheEntry_BYTECODE = 1;
2047 static const uint32_t CacheEntry_KIND = 2;
2048 static const uint32_t CacheEntry_OPTIONS = 3;
2049
2050 enum class BytecodeCacheKind : uint32_t {
2051 Undefined = 0,
2052 Script,
2053 Stencil,
2054 };
2055
2056 // Some compile options can't be combined differently between save and load.
2057 //
2058 // CacheEntries store a CacheOption set, and on load an exception is thrown
2059 // if the entries are incompatible.
2060
2061 enum CacheOptions : uint32_t {
2062 IsRunOnce,
2063 NoScriptRval,
2064 Global,
2065 NonSyntactic,
2066 SourceIsLazy,
2067 ForceFullParse,
2068 };
2069
2070 struct CacheOptionSet : public mozilla::EnumSet<CacheOptions> {
2071 using mozilla::EnumSet<CacheOptions>::EnumSet;
2072
CacheOptionSetCacheOptionSet2073 explicit CacheOptionSet(const CompileOptions& options) : EnumSet() {
2074 initFromOptions(options);
2075 }
2076
initFromOptionsCacheOptionSet2077 void initFromOptions(const CompileOptions& options) {
2078 if (options.noScriptRval) {
2079 *this += CacheOptions::NoScriptRval;
2080 }
2081 if (options.isRunOnce) {
2082 *this += CacheOptions::IsRunOnce;
2083 }
2084 if (options.sourceIsLazy) {
2085 *this += CacheOptions::SourceIsLazy;
2086 }
2087 if (options.forceFullParse()) {
2088 *this += CacheOptions::ForceFullParse;
2089 }
2090 if (options.nonSyntacticScope) {
2091 *this += CacheOptions::NonSyntactic;
2092 }
2093 }
2094 };
2095
CacheOptionsCompatible(const CacheOptionSet & a,const CacheOptionSet & b)2096 static bool CacheOptionsCompatible(const CacheOptionSet& a,
2097 const CacheOptionSet& b) {
2098 // If the options are identical, they are trivially compatible.
2099 return a == b;
2100 }
2101
2102 static const JSClass CacheEntry_class = {"CacheEntryObject",
2103 JSCLASS_HAS_RESERVED_SLOTS(4)};
2104
CacheEntry(JSContext * cx,unsigned argc,JS::Value * vp)2105 static bool CacheEntry(JSContext* cx, unsigned argc, JS::Value* vp) {
2106 CallArgs args = CallArgsFromVp(argc, vp);
2107
2108 if (args.length() != 1 || !args[0].isString()) {
2109 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
2110 JSSMSG_INVALID_ARGS, "CacheEntry");
2111 return false;
2112 }
2113
2114 RootedObject obj(cx, JS_NewObject(cx, &CacheEntry_class));
2115 if (!obj) {
2116 return false;
2117 }
2118
2119 JS::SetReservedSlot(obj, CacheEntry_SOURCE, args[0]);
2120 JS::SetReservedSlot(obj, CacheEntry_BYTECODE, UndefinedValue());
2121 JS::SetReservedSlot(obj, CacheEntry_KIND,
2122 Int32Value(int32_t(BytecodeCacheKind::Undefined)));
2123
2124 // Fill in empty option set.
2125 CacheOptionSet defaultOptions;
2126 JS::SetReservedSlot(obj, CacheEntry_OPTIONS,
2127 Int32Value(defaultOptions.serialize()));
2128
2129 args.rval().setObject(*obj);
2130 return true;
2131 }
2132
CacheEntry_isCacheEntry(JSObject * cache)2133 static bool CacheEntry_isCacheEntry(JSObject* cache) {
2134 return cache->hasClass(&CacheEntry_class);
2135 }
2136
CacheEntry_getSource(JSContext * cx,HandleObject cache)2137 static JSString* CacheEntry_getSource(JSContext* cx, HandleObject cache) {
2138 MOZ_ASSERT(CacheEntry_isCacheEntry(cache));
2139 Value v = JS::GetReservedSlot(cache, CacheEntry_SOURCE);
2140 if (!v.isString()) {
2141 JS_ReportErrorASCII(
2142 cx, "CacheEntry_getSource: Unexpected type of source reserved slot.");
2143 return nullptr;
2144 }
2145
2146 return v.toString();
2147 }
2148
CacheEntry_getKind(JSContext * cx,HandleObject cache)2149 static BytecodeCacheKind CacheEntry_getKind(JSContext* cx, HandleObject cache) {
2150 MOZ_ASSERT(CacheEntry_isCacheEntry(cache));
2151 Value v = JS::GetReservedSlot(cache, CacheEntry_KIND);
2152 return BytecodeCacheKind(v.toInt32());
2153 }
2154
CacheEntry_setKind(JSContext * cx,HandleObject cache,BytecodeCacheKind kind)2155 static void CacheEntry_setKind(JSContext* cx, HandleObject cache,
2156 BytecodeCacheKind kind) {
2157 MOZ_ASSERT(CacheEntry_isCacheEntry(cache));
2158 JS::SetReservedSlot(cache, CacheEntry_KIND, Int32Value(int32_t(kind)));
2159 }
2160
CacheEntry_compatible(JSContext * cx,HandleObject cache,const CacheOptionSet & currentOptionSet)2161 static bool CacheEntry_compatible(JSContext* cx, HandleObject cache,
2162 const CacheOptionSet& currentOptionSet) {
2163 CacheOptionSet cacheEntryOptions;
2164 MOZ_ASSERT(CacheEntry_isCacheEntry(cache));
2165 Value v = JS::GetReservedSlot(cache, CacheEntry_OPTIONS);
2166 cacheEntryOptions.deserialize(v.toInt32());
2167 if (!CacheOptionsCompatible(cacheEntryOptions, currentOptionSet)) {
2168 JS_ReportErrorASCII(cx,
2169 "CacheEntry_compatible: Incompatible cache contents");
2170 return false;
2171 }
2172 return true;
2173 }
2174
CacheEntry_getBytecode(JSContext * cx,HandleObject cache,size_t * length)2175 static uint8_t* CacheEntry_getBytecode(JSContext* cx, HandleObject cache,
2176 size_t* length) {
2177 MOZ_ASSERT(CacheEntry_isCacheEntry(cache));
2178 Value v = JS::GetReservedSlot(cache, CacheEntry_BYTECODE);
2179 if (!v.isObject() || !v.toObject().is<ArrayBufferObject>()) {
2180 JS_ReportErrorASCII(
2181 cx,
2182 "CacheEntry_getBytecode: Unexpected type of bytecode reserved slot.");
2183 return nullptr;
2184 }
2185
2186 ArrayBufferObject* arrayBuffer = &v.toObject().as<ArrayBufferObject>();
2187 *length = arrayBuffer->byteLength();
2188 return arrayBuffer->dataPointer();
2189 }
2190
CacheEntry_setBytecode(JSContext * cx,HandleObject cache,const CacheOptionSet & cacheOptions,uint8_t * buffer,uint32_t length)2191 static bool CacheEntry_setBytecode(JSContext* cx, HandleObject cache,
2192 const CacheOptionSet& cacheOptions,
2193 uint8_t* buffer, uint32_t length) {
2194 MOZ_ASSERT(CacheEntry_isCacheEntry(cache));
2195
2196 using BufferContents = ArrayBufferObject::BufferContents;
2197
2198 BufferContents contents = BufferContents::createMalloced(buffer);
2199 Rooted<ArrayBufferObject*> arrayBuffer(
2200 cx, ArrayBufferObject::createForContents(cx, length, contents));
2201 if (!arrayBuffer) {
2202 return false;
2203 }
2204
2205 JS::SetReservedSlot(cache, CacheEntry_BYTECODE, ObjectValue(*arrayBuffer));
2206 JS::SetReservedSlot(cache, CacheEntry_OPTIONS,
2207 Int32Value(cacheOptions.serialize()));
2208 return true;
2209 }
2210
ConvertTranscodeResultToJSException(JSContext * cx,JS::TranscodeResult rv)2211 static bool ConvertTranscodeResultToJSException(JSContext* cx,
2212 JS::TranscodeResult rv) {
2213 switch (rv) {
2214 case JS::TranscodeResult::Ok:
2215 return true;
2216
2217 default:
2218 [[fallthrough]];
2219 case JS::TranscodeResult::Failure:
2220 MOZ_ASSERT(!cx->isExceptionPending());
2221 JS_ReportErrorASCII(cx, "generic warning");
2222 return false;
2223 case JS::TranscodeResult::Failure_BadBuildId:
2224 MOZ_ASSERT(!cx->isExceptionPending());
2225 JS_ReportErrorASCII(cx, "the build-id does not match");
2226 return false;
2227 case JS::TranscodeResult::Failure_RunOnceNotSupported:
2228 MOZ_ASSERT(!cx->isExceptionPending());
2229 JS_ReportErrorASCII(cx, "run-once script are not supported by XDR");
2230 return false;
2231 case JS::TranscodeResult::Failure_AsmJSNotSupported:
2232 MOZ_ASSERT(!cx->isExceptionPending());
2233 JS_ReportErrorASCII(cx, "Asm.js is not supported by XDR");
2234 return false;
2235 case JS::TranscodeResult::Failure_BadDecode:
2236 MOZ_ASSERT(!cx->isExceptionPending());
2237 JS_ReportErrorASCII(cx, "XDR data corruption");
2238 return false;
2239 case JS::TranscodeResult::Failure_WrongCompileOption:
2240 MOZ_ASSERT(!cx->isExceptionPending());
2241 JS_ReportErrorASCII(
2242 cx, "Compile options differs from Compile options of the encoding");
2243 return false;
2244 case JS::TranscodeResult::Failure_NotInterpretedFun:
2245 MOZ_ASSERT(!cx->isExceptionPending());
2246 JS_ReportErrorASCII(cx,
2247 "Only interepreted functions are supported by XDR");
2248 return false;
2249
2250 case JS::TranscodeResult::Throw:
2251 MOZ_ASSERT(cx->isExceptionPending());
2252 return false;
2253 }
2254 }
2255
Evaluate(JSContext * cx,unsigned argc,Value * vp)2256 static bool Evaluate(JSContext* cx, unsigned argc, Value* vp) {
2257 CallArgs args = CallArgsFromVp(argc, vp);
2258
2259 if (args.length() < 1 || args.length() > 2) {
2260 JS_ReportErrorNumberASCII(
2261 cx, my_GetErrorMessage, nullptr,
2262 args.length() < 1 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS,
2263 "evaluate");
2264 return false;
2265 }
2266
2267 RootedString code(cx, nullptr);
2268 RootedObject cacheEntry(cx, nullptr);
2269 if (args[0].isString()) {
2270 code = args[0].toString();
2271 } else if (args[0].isObject() &&
2272 CacheEntry_isCacheEntry(&args[0].toObject())) {
2273 cacheEntry = &args[0].toObject();
2274 code = CacheEntry_getSource(cx, cacheEntry);
2275 if (!code) {
2276 return false;
2277 }
2278 }
2279
2280 if (!code || (args.length() == 2 && args[1].isPrimitive())) {
2281 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
2282 JSSMSG_INVALID_ARGS, "evaluate");
2283 return false;
2284 }
2285
2286 CompileOptions options(cx);
2287 UniqueChars fileNameBytes;
2288 RootedString displayURL(cx);
2289 RootedString sourceMapURL(cx);
2290 RootedObject global(cx, nullptr);
2291 bool catchTermination = false;
2292 bool loadBytecode = false;
2293 bool saveBytecode = false;
2294 bool saveIncrementalBytecode = false;
2295 bool transcodeOnly = false;
2296 bool assertEqBytecode = false;
2297 JS::RootedObjectVector envChain(cx);
2298 RootedObject callerGlobal(cx, cx->global());
2299 CacheOptionSet optionSet;
2300
2301 options.setIntroductionType("js shell evaluate")
2302 .setFileAndLine("@evaluate", 1)
2303 .setdeferDebugMetadata();
2304
2305 global = JS::CurrentGlobalOrNull(cx);
2306 MOZ_ASSERT(global);
2307
2308 RootedValue privateValue(cx);
2309 RootedString elementAttributeName(cx);
2310
2311 if (args.length() == 2) {
2312 RootedObject opts(cx, &args[1].toObject());
2313 if (!js::ParseCompileOptions(cx, options, opts, &fileNameBytes)) {
2314 return false;
2315 }
2316 if (!ParseDebugMetadata(cx, opts, &privateValue, &elementAttributeName)) {
2317 return false;
2318 }
2319
2320 RootedValue v(cx);
2321 if (!JS_GetProperty(cx, opts, "displayURL", &v)) {
2322 return false;
2323 }
2324 if (!v.isUndefined()) {
2325 displayURL = ToString(cx, v);
2326 if (!displayURL) {
2327 return false;
2328 }
2329 }
2330
2331 if (!JS_GetProperty(cx, opts, "sourceMapURL", &v)) {
2332 return false;
2333 }
2334 if (!v.isUndefined()) {
2335 sourceMapURL = ToString(cx, v);
2336 if (!sourceMapURL) {
2337 return false;
2338 }
2339 }
2340
2341 if (!JS_GetProperty(cx, opts, "catchTermination", &v)) {
2342 return false;
2343 }
2344 if (!v.isUndefined()) {
2345 catchTermination = ToBoolean(v);
2346 }
2347
2348 if (!JS_GetProperty(cx, opts, "loadBytecode", &v)) {
2349 return false;
2350 }
2351 if (!v.isUndefined()) {
2352 loadBytecode = ToBoolean(v);
2353 }
2354
2355 if (!JS_GetProperty(cx, opts, "saveBytecode", &v)) {
2356 return false;
2357 }
2358 if (!v.isUndefined()) {
2359 saveBytecode = ToBoolean(v);
2360 }
2361
2362 if (!JS_GetProperty(cx, opts, "saveIncrementalBytecode", &v)) {
2363 return false;
2364 }
2365 if (!v.isUndefined()) {
2366 saveIncrementalBytecode = ToBoolean(v);
2367 }
2368
2369 if (!JS_GetProperty(cx, opts, "transcodeOnly", &v)) {
2370 return false;
2371 }
2372 if (!v.isUndefined()) {
2373 transcodeOnly = ToBoolean(v);
2374 }
2375
2376 if (!JS_GetProperty(cx, opts, "assertEqBytecode", &v)) {
2377 return false;
2378 }
2379 if (!v.isUndefined()) {
2380 assertEqBytecode = ToBoolean(v);
2381 }
2382
2383 if (!JS_GetProperty(cx, opts, "envChainObject", &v)) {
2384 return false;
2385 }
2386 if (!v.isUndefined()) {
2387 if (!v.isObject()) {
2388 JS_ReportErrorNumberASCII(
2389 cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
2390 "\"envChainObject\" passed to evaluate()", "not an object");
2391 return false;
2392 } else if (v.toObject().is<GlobalObject>()) {
2393 JS_ReportErrorASCII(
2394 cx,
2395 "\"envChainObject\" passed to evaluate() should not be a global");
2396 return false;
2397 } else if (!envChain.append(&v.toObject())) {
2398 JS_ReportOutOfMemory(cx);
2399 return false;
2400 }
2401 }
2402
2403 // We cannot load or save the bytecode if we have no object where the
2404 // bytecode cache is stored.
2405 if (loadBytecode || saveBytecode || saveIncrementalBytecode) {
2406 if (!cacheEntry) {
2407 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
2408 JSSMSG_INVALID_ARGS, "evaluate");
2409 return false;
2410 }
2411 if (saveIncrementalBytecode && saveBytecode) {
2412 JS_ReportErrorASCII(
2413 cx,
2414 "saveIncrementalBytecode and saveBytecode cannot be used"
2415 " at the same time.");
2416 return false;
2417 }
2418 if (saveIncrementalBytecode && js::UseOffThreadParseGlobal()) {
2419 JS_ReportErrorASCII(
2420 cx, "saveIncrementalBytecode cannot be used with legacy XDR.");
2421 return false;
2422 }
2423 }
2424
2425 if (saveIncrementalBytecode && assertEqBytecode) {
2426 JS_ReportErrorASCII(
2427 cx, "saveIncrementalBytecode cannot be used with assertEqBytecode.");
2428 return false;
2429 }
2430
2431 // NOTE: Check custom "global" after handling all CompileOption-related
2432 // properties.
2433 if (!JS_GetProperty(cx, opts, "global", &v)) {
2434 return false;
2435 }
2436 if (!v.isUndefined()) {
2437 if (v.isObject()) {
2438 global = js::CheckedUnwrapDynamic(&v.toObject(), cx,
2439 /* stopAtWindowProxy = */ false);
2440 if (!global) {
2441 return false;
2442 }
2443 }
2444 if (!global || !(JS::GetClass(global)->flags & JSCLASS_IS_GLOBAL)) {
2445 JS_ReportErrorNumberASCII(
2446 cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
2447 "\"global\" passed to evaluate()", "not a global object");
2448 return false;
2449 }
2450
2451 // Validate the consistency between the passed CompileOptions and
2452 // global's option.
2453 {
2454 JSAutoRealm ar(cx, global);
2455 JS::CompileOptions globalOptions(cx);
2456
2457 // If the passed global discards source, delazification isn't allowed.
2458 // The CompileOption should discard source as well.
2459 if (globalOptions.discardSource && !options.discardSource) {
2460 JS_ReportErrorASCII(cx, "discardSource option mismatch");
2461 return false;
2462 }
2463 }
2464 }
2465 }
2466
2467 if (envChain.length() != 0) {
2468 {
2469 // Wrap the envChainObject list into target realm.
2470 JSAutoRealm ar(cx, global);
2471 for (size_t i = 0; i < envChain.length(); ++i) {
2472 if (!JS_WrapObject(cx, envChain[i])) {
2473 return false;
2474 }
2475 }
2476 }
2477
2478 options.setNonSyntacticScope(true);
2479 }
2480
2481 optionSet.initFromOptions(options);
2482
2483 AutoStableStringChars codeChars(cx);
2484 if (!codeChars.initTwoByte(cx, code)) {
2485 return false;
2486 }
2487
2488 JS::TranscodeBuffer loadBuffer;
2489 JS::TranscodeBuffer saveBuffer;
2490 BytecodeCacheKind loadCacheKind = BytecodeCacheKind::Undefined;
2491 BytecodeCacheKind saveCacheKind = BytecodeCacheKind::Undefined;
2492
2493 if (loadBytecode) {
2494 size_t loadLength = 0;
2495 uint8_t* loadData = nullptr;
2496
2497 if (!CacheEntry_compatible(cx, cacheEntry, optionSet)) {
2498 return false;
2499 }
2500
2501 loadData = CacheEntry_getBytecode(cx, cacheEntry, &loadLength);
2502 if (!loadData) {
2503 return false;
2504 }
2505 if (!loadBuffer.append(loadData, loadLength)) {
2506 JS_ReportOutOfMemory(cx);
2507 return false;
2508 }
2509 loadCacheKind = CacheEntry_getKind(cx, cacheEntry);
2510 }
2511
2512 {
2513 JSAutoRealm ar(cx, global);
2514 RootedScript script(cx);
2515
2516 {
2517 if (loadBytecode) {
2518 JS::TranscodeResult rv;
2519 if (saveIncrementalBytecode) {
2520 bool useStencilXDR = !options.useOffThreadParseGlobal;
2521 if (useStencilXDR) {
2522 if (CacheEntry_getKind(cx, cacheEntry) !=
2523 BytecodeCacheKind::Stencil) {
2524 // This can happen.
2525 JS_ReportErrorASCII(
2526 cx,
2527 "if both loadBytecode and saveIncrementalBytecode are set "
2528 "and --off-thread-parse-global isn't used, bytecode should "
2529 "have been saved with saveIncrementalBytecode");
2530 return false;
2531 }
2532 } else {
2533 if (CacheEntry_getKind(cx, cacheEntry) !=
2534 BytecodeCacheKind::Script) {
2535 // NOTE: This shouldn't happen unless the cache is used across
2536 // processes with different --no-off-thread-parse-global option.
2537 JS_ReportErrorASCII(
2538 cx,
2539 "if both loadBytecode and saveIncrementalBytecode are set "
2540 "and --off-thread-parse-global is used, bytecode "
2541 "should have been saved with saveBytecode");
2542 return false;
2543 }
2544 }
2545
2546 rv = JS::DecodeScriptAndStartIncrementalEncoding(cx, options,
2547 loadBuffer, &script);
2548 if (!ConvertTranscodeResultToJSException(cx, rv)) {
2549 return false;
2550 }
2551 } else if (loadCacheKind == BytecodeCacheKind::Script) {
2552 rv = JS::DecodeScript(cx, options, loadBuffer, &script);
2553 if (!ConvertTranscodeResultToJSException(cx, rv)) {
2554 return false;
2555 }
2556 } else {
2557 MOZ_ASSERT(loadCacheKind == BytecodeCacheKind::Stencil);
2558 rv = JS::DecodeScriptMaybeStencil(cx, options, loadBuffer, &script);
2559 if (!ConvertTranscodeResultToJSException(cx, rv)) {
2560 return false;
2561 }
2562 }
2563 } else {
2564 mozilla::Range<const char16_t> chars = codeChars.twoByteRange();
2565 JS::SourceText<char16_t> srcBuf;
2566 if (!srcBuf.init(cx, chars.begin().get(), chars.length(),
2567 JS::SourceOwnership::Borrowed)) {
2568 return false;
2569 }
2570
2571 if (saveIncrementalBytecode) {
2572 script = JS::CompileAndStartIncrementalEncoding(cx, options, srcBuf);
2573 if (!script) {
2574 return false;
2575 }
2576 } else {
2577 script = JS::Compile(cx, options, srcBuf);
2578 if (!script) {
2579 return false;
2580 }
2581 }
2582 }
2583 }
2584
2585 if (displayURL && !script->scriptSource()->hasDisplayURL()) {
2586 UniqueTwoByteChars chars = JS_CopyStringCharsZ(cx, displayURL);
2587 if (!chars) {
2588 return false;
2589 }
2590 if (!script->scriptSource()->setDisplayURL(cx, std::move(chars))) {
2591 return false;
2592 }
2593 }
2594 if (sourceMapURL && !script->scriptSource()->hasSourceMapURL()) {
2595 UniqueTwoByteChars chars = JS_CopyStringCharsZ(cx, sourceMapURL);
2596 if (!chars) {
2597 return false;
2598 }
2599 if (!script->scriptSource()->setSourceMapURL(cx, std::move(chars))) {
2600 return false;
2601 }
2602 }
2603
2604 if (!JS::UpdateDebugMetadata(cx, script, options, privateValue,
2605 elementAttributeName, nullptr, nullptr)) {
2606 return false;
2607 }
2608
2609 if (!transcodeOnly) {
2610 if (!(envChain.empty()
2611 ? JS_ExecuteScript(cx, script, args.rval())
2612 : JS_ExecuteScript(cx, envChain, script, args.rval()))) {
2613 if (catchTermination && !JS_IsExceptionPending(cx)) {
2614 JSAutoRealm ar1(cx, callerGlobal);
2615 JSString* str = JS_NewStringCopyZ(cx, "terminated");
2616 if (!str) {
2617 return false;
2618 }
2619 args.rval().setString(str);
2620 return true;
2621 }
2622 return false;
2623 }
2624 }
2625
2626 // Encode the bytecode after the execution of the script.
2627 if (saveBytecode) {
2628 JS::TranscodeResult rv = JS::EncodeScript(cx, saveBuffer, script);
2629 if (!ConvertTranscodeResultToJSException(cx, rv)) {
2630 return false;
2631 }
2632 saveCacheKind = BytecodeCacheKind::Script;
2633 }
2634
2635 // Serialize the encoded bytecode, recorded before the execution, into a
2636 // buffer which can be deserialized linearly.
2637 if (saveIncrementalBytecode) {
2638 if (!FinishIncrementalEncoding(cx, script, saveBuffer)) {
2639 return false;
2640 }
2641 if (options.useStencilXDR) {
2642 saveCacheKind = BytecodeCacheKind::Stencil;
2643 } else {
2644 saveCacheKind = BytecodeCacheKind::Script;
2645 }
2646 }
2647 }
2648
2649 if (saveBytecode || saveIncrementalBytecode) {
2650 // If we are both loading and saving, we assert that we are going to
2651 // replace the current bytecode by the same stream of bytes.
2652 if (loadBytecode && assertEqBytecode) {
2653 if (saveBuffer.length() != loadBuffer.length()) {
2654 char loadLengthStr[16];
2655 SprintfLiteral(loadLengthStr, "%zu", loadBuffer.length());
2656 char saveLengthStr[16];
2657 SprintfLiteral(saveLengthStr, "%zu", saveBuffer.length());
2658
2659 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
2660 JSSMSG_CACHE_EQ_SIZE_FAILED, loadLengthStr,
2661 saveLengthStr);
2662 return false;
2663 }
2664
2665 if (!ArrayEqual(loadBuffer.begin(), saveBuffer.begin(),
2666 loadBuffer.length())) {
2667 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
2668 JSSMSG_CACHE_EQ_CONTENT_FAILED);
2669 return false;
2670 }
2671 }
2672
2673 size_t saveLength = saveBuffer.length();
2674 if (saveLength >= INT32_MAX) {
2675 JS_ReportErrorASCII(cx, "Cannot save large cache entry content");
2676 return false;
2677 }
2678 uint8_t* saveData = saveBuffer.extractOrCopyRawBuffer();
2679 if (!CacheEntry_setBytecode(cx, cacheEntry, optionSet, saveData,
2680 saveLength)) {
2681 js_free(saveData);
2682 return false;
2683 }
2684 MOZ_ASSERT(saveCacheKind != BytecodeCacheKind::Undefined);
2685 CacheEntry_setKind(cx, cacheEntry, saveCacheKind);
2686 }
2687
2688 return JS_WrapValue(cx, args.rval());
2689 }
2690
FileAsString(JSContext * cx,JS::HandleString pathnameStr)2691 JSString* js::shell::FileAsString(JSContext* cx, JS::HandleString pathnameStr) {
2692 UniqueChars pathname = JS_EncodeStringToLatin1(cx, pathnameStr);
2693 if (!pathname) {
2694 return nullptr;
2695 }
2696
2697 FILE* file;
2698
2699 file = fopen(pathname.get(), "rb");
2700 if (!file) {
2701 ReportCantOpenErrorUnknownEncoding(cx, pathname.get());
2702 return nullptr;
2703 }
2704
2705 AutoCloseFile autoClose(file);
2706
2707 struct stat st;
2708 if (fstat(fileno(file), &st) != 0) {
2709 JS_ReportErrorUTF8(cx, "can't stat %s", pathname.get());
2710 return nullptr;
2711 }
2712
2713 if ((st.st_mode & S_IFMT) != S_IFREG) {
2714 JS_ReportErrorUTF8(cx, "can't read non-regular file %s", pathname.get());
2715 return nullptr;
2716 }
2717
2718 if (fseek(file, 0, SEEK_END) != 0) {
2719 pathname = JS_EncodeStringToUTF8(cx, pathnameStr);
2720 if (!pathname) {
2721 return nullptr;
2722 }
2723 JS_ReportErrorUTF8(cx, "can't seek end of %s", pathname.get());
2724 return nullptr;
2725 }
2726
2727 long endPos = ftell(file);
2728 if (endPos < 0) {
2729 JS_ReportErrorUTF8(cx, "can't read length of %s", pathname.get());
2730 return nullptr;
2731 }
2732
2733 size_t len = endPos;
2734 if (fseek(file, 0, SEEK_SET) != 0) {
2735 pathname = JS_EncodeStringToUTF8(cx, pathnameStr);
2736 if (!pathname) {
2737 return nullptr;
2738 }
2739 JS_ReportErrorUTF8(cx, "can't seek start of %s", pathname.get());
2740 return nullptr;
2741 }
2742
2743 UniqueChars buf(js_pod_malloc<char>(len + 1));
2744 if (!buf) {
2745 JS_ReportErrorUTF8(cx, "out of memory reading %s", pathname.get());
2746 return nullptr;
2747 }
2748
2749 size_t cc = fread(buf.get(), 1, len, file);
2750 if (cc != len) {
2751 if (ptrdiff_t(cc) < 0) {
2752 ReportCantOpenErrorUnknownEncoding(cx, pathname.get());
2753 } else {
2754 pathname = JS_EncodeStringToUTF8(cx, pathnameStr);
2755 if (!pathname) {
2756 return nullptr;
2757 }
2758 JS_ReportErrorUTF8(cx, "can't read %s: short read", pathname.get());
2759 }
2760 return nullptr;
2761 }
2762
2763 UniqueTwoByteChars ucbuf(
2764 JS::LossyUTF8CharsToNewTwoByteCharsZ(cx, JS::UTF8Chars(buf.get(), len),
2765 &len, js::MallocArena)
2766 .get());
2767 if (!ucbuf) {
2768 pathname = JS_EncodeStringToUTF8(cx, pathnameStr);
2769 if (!pathname) {
2770 return nullptr;
2771 }
2772 JS_ReportErrorUTF8(cx, "Invalid UTF-8 in file '%s'", pathname.get());
2773 return nullptr;
2774 }
2775
2776 return JS_NewUCStringCopyN(cx, ucbuf.get(), len);
2777 }
2778
2779 /*
2780 * Function to run scripts and return compilation + execution time. Semantics
2781 * are closely modelled after the equivalent function in WebKit, as this is used
2782 * to produce benchmark timings by SunSpider.
2783 */
Run(JSContext * cx,unsigned argc,Value * vp)2784 static bool Run(JSContext* cx, unsigned argc, Value* vp) {
2785 CallArgs args = CallArgsFromVp(argc, vp);
2786 if (args.length() != 1) {
2787 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
2788 JSSMSG_INVALID_ARGS, "run");
2789 return false;
2790 }
2791
2792 RootedString str(cx, JS::ToString(cx, args[0]));
2793 if (!str) {
2794 return false;
2795 }
2796 args[0].setString(str);
2797
2798 str = FileAsString(cx, str);
2799 if (!str) {
2800 return false;
2801 }
2802
2803 AutoStableStringChars chars(cx);
2804 if (!chars.initTwoByte(cx, str)) {
2805 return false;
2806 }
2807
2808 JS::SourceText<char16_t> srcBuf;
2809 if (!srcBuf.init(cx, chars.twoByteRange().begin().get(), str->length(),
2810 JS::SourceOwnership::Borrowed)) {
2811 return false;
2812 }
2813
2814 RootedScript script(cx);
2815 int64_t startClock = PRMJ_Now();
2816 {
2817 /* FIXME: This should use UTF-8 (bug 987069). */
2818 UniqueChars filename = JS_EncodeStringToLatin1(cx, str);
2819 if (!filename) {
2820 return false;
2821 }
2822
2823 JS::CompileOptions options(cx);
2824 options.setIntroductionType("js shell run")
2825 .setFileAndLine(filename.get(), 1)
2826 .setIsRunOnce(true)
2827 .setNoScriptRval(true);
2828
2829 script = JS::Compile(cx, options, srcBuf);
2830 if (!script) {
2831 return false;
2832 }
2833 }
2834
2835 if (!JS_ExecuteScript(cx, script)) {
2836 return false;
2837 }
2838
2839 int64_t endClock = PRMJ_Now();
2840
2841 args.rval().setDouble((endClock - startClock) / double(PRMJ_USEC_PER_MSEC));
2842 return true;
2843 }
2844
js_fgets(char * buf,int size,FILE * file)2845 static int js_fgets(char* buf, int size, FILE* file) {
2846 int n, i, c;
2847 bool crflag;
2848
2849 n = size - 1;
2850 if (n < 0) {
2851 return -1;
2852 }
2853
2854 // Use the fastest available getc.
2855 auto fast_getc =
2856 #if defined(HAVE_GETC_UNLOCKED)
2857 getc_unlocked
2858 #elif defined(HAVE__GETC_NOLOCK)
2859 _getc_nolock
2860 #else
2861 getc
2862 #endif
2863 ;
2864
2865 crflag = false;
2866 for (i = 0; i < n && (c = fast_getc(file)) != EOF; i++) {
2867 buf[i] = c;
2868 if (c == '\n') { // any \n ends a line
2869 i++; // keep the \n; we know there is room for \0
2870 break;
2871 }
2872 if (crflag) { // \r not followed by \n ends line at the \r
2873 ungetc(c, file);
2874 break; // and overwrite c in buf with \0
2875 }
2876 crflag = (c == '\r');
2877 }
2878
2879 buf[i] = '\0';
2880 return i;
2881 }
2882
2883 /*
2884 * function readline()
2885 * Provides a hook for scripts to read a line from stdin.
2886 */
ReadLine(JSContext * cx,unsigned argc,Value * vp)2887 static bool ReadLine(JSContext* cx, unsigned argc, Value* vp) {
2888 CallArgs args = CallArgsFromVp(argc, vp);
2889
2890 static constexpr size_t BUFSIZE = 256;
2891 FILE* from = stdin;
2892 size_t buflength = 0;
2893 size_t bufsize = BUFSIZE;
2894 char* buf = (char*)JS_malloc(cx, bufsize);
2895 if (!buf) {
2896 return false;
2897 }
2898
2899 bool sawNewline = false;
2900 size_t gotlength;
2901 while ((gotlength = js_fgets(buf + buflength, bufsize - buflength, from)) >
2902 0) {
2903 buflength += gotlength;
2904
2905 /* Are we done? */
2906 if (buf[buflength - 1] == '\n') {
2907 buf[buflength - 1] = '\0';
2908 sawNewline = true;
2909 break;
2910 } else if (buflength < bufsize - 1) {
2911 break;
2912 }
2913
2914 /* Else, grow our buffer for another pass. */
2915 char* tmp;
2916 bufsize *= 2;
2917 if (bufsize > buflength) {
2918 tmp = static_cast<char*>(JS_realloc(cx, buf, bufsize / 2, bufsize));
2919 } else {
2920 JS_ReportOutOfMemory(cx);
2921 tmp = nullptr;
2922 }
2923
2924 if (!tmp) {
2925 JS_free(cx, buf);
2926 return false;
2927 }
2928
2929 buf = tmp;
2930 }
2931
2932 /* Treat the empty string specially. */
2933 if (buflength == 0) {
2934 args.rval().set(feof(from) ? NullValue() : JS_GetEmptyStringValue(cx));
2935 JS_free(cx, buf);
2936 return true;
2937 }
2938
2939 /* Shrink the buffer to the real size. */
2940 char* tmp = static_cast<char*>(JS_realloc(cx, buf, bufsize, buflength));
2941 if (!tmp) {
2942 JS_free(cx, buf);
2943 return false;
2944 }
2945
2946 buf = tmp;
2947
2948 /*
2949 * Turn buf into a JSString. Note that buflength includes the trailing null
2950 * character.
2951 */
2952 JSString* str =
2953 JS_NewStringCopyN(cx, buf, sawNewline ? buflength - 1 : buflength);
2954 JS_free(cx, buf);
2955 if (!str) {
2956 return false;
2957 }
2958
2959 args.rval().setString(str);
2960 return true;
2961 }
2962
2963 /*
2964 * function readlineBuf()
2965 * Provides a hook for scripts to emulate readline() using a string object.
2966 */
ReadLineBuf(JSContext * cx,unsigned argc,Value * vp)2967 static bool ReadLineBuf(JSContext* cx, unsigned argc, Value* vp) {
2968 CallArgs args = CallArgsFromVp(argc, vp);
2969 ShellContext* sc = GetShellContext(cx);
2970
2971 if (!args.length()) {
2972 if (!sc->readLineBuf) {
2973 JS_ReportErrorASCII(cx,
2974 "No source buffer set. You must initially "
2975 "call readlineBuf with an argument.");
2976 return false;
2977 }
2978
2979 char* currentBuf = sc->readLineBuf.get() + sc->readLineBufPos;
2980 size_t buflen = strlen(currentBuf);
2981
2982 if (!buflen) {
2983 args.rval().setNull();
2984 return true;
2985 }
2986
2987 size_t len = 0;
2988 while (len < buflen) {
2989 if (currentBuf[len] == '\n') {
2990 break;
2991 }
2992 len++;
2993 }
2994
2995 JSString* str = JS_NewStringCopyUTF8N(cx, JS::UTF8Chars(currentBuf, len));
2996 if (!str) {
2997 return false;
2998 }
2999
3000 if (currentBuf[len] == '\0') {
3001 sc->readLineBufPos += len;
3002 } else {
3003 sc->readLineBufPos += len + 1;
3004 }
3005
3006 args.rval().setString(str);
3007 return true;
3008 }
3009
3010 if (args.length() == 1) {
3011 sc->readLineBuf = nullptr;
3012 sc->readLineBufPos = 0;
3013
3014 RootedString str(cx, JS::ToString(cx, args[0]));
3015 if (!str) {
3016 return false;
3017 }
3018 sc->readLineBuf = JS_EncodeStringToUTF8(cx, str);
3019 if (!sc->readLineBuf) {
3020 return false;
3021 }
3022
3023 args.rval().setUndefined();
3024 return true;
3025 }
3026
3027 JS_ReportErrorASCII(cx, "Must specify at most one argument");
3028 return false;
3029 }
3030
PutStr(JSContext * cx,unsigned argc,Value * vp)3031 static bool PutStr(JSContext* cx, unsigned argc, Value* vp) {
3032 CallArgs args = CallArgsFromVp(argc, vp);
3033
3034 if (args.length() != 0) {
3035 if (!gOutFile->isOpen()) {
3036 JS_ReportErrorASCII(cx, "output file is closed");
3037 return false;
3038 }
3039
3040 RootedString str(cx, JS::ToString(cx, args[0]));
3041 if (!str) {
3042 return false;
3043 }
3044 UniqueChars bytes = JS_EncodeStringToUTF8(cx, str);
3045 if (!bytes) {
3046 return false;
3047 }
3048 fputs(bytes.get(), gOutFile->fp);
3049 fflush(gOutFile->fp);
3050 }
3051
3052 args.rval().setUndefined();
3053 return true;
3054 }
3055
Now(JSContext * cx,unsigned argc,Value * vp)3056 static bool Now(JSContext* cx, unsigned argc, Value* vp) {
3057 CallArgs args = CallArgsFromVp(argc, vp);
3058 double now = PRMJ_Now() / double(PRMJ_USEC_PER_MSEC);
3059 args.rval().setDouble(now);
3060 return true;
3061 }
3062
CpuNow(JSContext * cx,unsigned argc,Value * vp)3063 static bool CpuNow(JSContext* cx, unsigned argc, Value* vp) {
3064 CallArgs args = CallArgsFromVp(argc, vp);
3065 double now = double(std::clock()) / double(CLOCKS_PER_SEC);
3066 args.rval().setDouble(now);
3067 return true;
3068 }
3069
PrintInternal(JSContext * cx,const CallArgs & args,RCFile * file)3070 static bool PrintInternal(JSContext* cx, const CallArgs& args, RCFile* file) {
3071 if (!file->isOpen()) {
3072 JS_ReportErrorASCII(cx, "output file is closed");
3073 return false;
3074 }
3075
3076 for (unsigned i = 0; i < args.length(); i++) {
3077 RootedString str(cx, JS::ToString(cx, args[i]));
3078 if (!str) {
3079 return false;
3080 }
3081 UniqueChars bytes = JS_EncodeStringToUTF8(cx, str);
3082 if (!bytes) {
3083 return false;
3084 }
3085 fprintf(file->fp, "%s%s", i ? " " : "", bytes.get());
3086 }
3087
3088 fputc('\n', file->fp);
3089 fflush(file->fp);
3090
3091 args.rval().setUndefined();
3092 return true;
3093 }
3094
Print(JSContext * cx,unsigned argc,Value * vp)3095 static bool Print(JSContext* cx, unsigned argc, Value* vp) {
3096 CallArgs args = CallArgsFromVp(argc, vp);
3097 #ifdef FUZZING_INTERFACES
3098 if (fuzzHaveModule && !fuzzDoDebug) {
3099 // When fuzzing and not debugging, suppress any print() output,
3100 // as it slows down fuzzing and makes libFuzzer's output hard
3101 // to read.
3102 args.rval().setUndefined();
3103 return true;
3104 }
3105 #endif // FUZZING_INTERFACES
3106 return PrintInternal(cx, args, gOutFile);
3107 }
3108
PrintErr(JSContext * cx,unsigned argc,Value * vp)3109 static bool PrintErr(JSContext* cx, unsigned argc, Value* vp) {
3110 CallArgs args = CallArgsFromVp(argc, vp);
3111 return PrintInternal(cx, args, gErrFile);
3112 }
3113
3114 static bool Help(JSContext* cx, unsigned argc, Value* vp);
3115
Quit(JSContext * cx,unsigned argc,Value * vp)3116 static bool Quit(JSContext* cx, unsigned argc, Value* vp) {
3117 ShellContext* sc = GetShellContext(cx);
3118
3119 // Print a message to stderr in differential testing to help jsfunfuzz
3120 // find uncatchable-exception bugs.
3121 if (js::SupportDifferentialTesting()) {
3122 fprintf(stderr, "quit called\n");
3123 }
3124
3125 CallArgs args = CallArgsFromVp(argc, vp);
3126 int32_t code;
3127 if (!ToInt32(cx, args.get(0), &code)) {
3128 return false;
3129 }
3130
3131 // The fuzzers check the shell's exit code and assume a value >= 128 means
3132 // the process crashed (for instance, SIGSEGV will result in code 139). On
3133 // POSIX platforms, the exit code is 8-bit and negative values can also
3134 // result in an exit code >= 128. We restrict the value to range [0, 127] to
3135 // avoid false positives.
3136 if (code < 0 || code >= 128) {
3137 JS_ReportErrorASCII(cx, "quit exit code should be in range 0-127");
3138 return false;
3139 }
3140
3141 js::StopDrainingJobQueue(cx);
3142 sc->exitCode = code;
3143 sc->quitting = true;
3144 return false;
3145 }
3146
StartTimingMutator(JSContext * cx,unsigned argc,Value * vp)3147 static bool StartTimingMutator(JSContext* cx, unsigned argc, Value* vp) {
3148 CallArgs args = CallArgsFromVp(argc, vp);
3149 if (args.length() > 0) {
3150 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
3151 JSSMSG_TOO_MANY_ARGS, "startTimingMutator");
3152 return false;
3153 }
3154
3155 if (!cx->runtime()->gc.stats().startTimingMutator()) {
3156 JS_ReportErrorASCII(
3157 cx, "StartTimingMutator should only be called from outside of GC");
3158 return false;
3159 }
3160
3161 args.rval().setUndefined();
3162 return true;
3163 }
3164
StopTimingMutator(JSContext * cx,unsigned argc,Value * vp)3165 static bool StopTimingMutator(JSContext* cx, unsigned argc, Value* vp) {
3166 CallArgs args = CallArgsFromVp(argc, vp);
3167 if (args.length() > 0) {
3168 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
3169 JSSMSG_TOO_MANY_ARGS, "stopTimingMutator");
3170 return false;
3171 }
3172
3173 double mutator_ms, gc_ms;
3174 if (!cx->runtime()->gc.stats().stopTimingMutator(mutator_ms, gc_ms)) {
3175 JS_ReportErrorASCII(cx,
3176 "stopTimingMutator called when not timing the mutator");
3177 return false;
3178 }
3179 double total_ms = mutator_ms + gc_ms;
3180 if (total_ms > 0 && gOutFile->isOpen()) {
3181 fprintf(gOutFile->fp, "Mutator: %.3fms (%.1f%%), GC: %.3fms (%.1f%%)\n",
3182 mutator_ms, mutator_ms / total_ms * 100.0, gc_ms,
3183 gc_ms / total_ms * 100.0);
3184 }
3185
3186 args.rval().setUndefined();
3187 return true;
3188 }
3189
ToSource(JSContext * cx,HandleValue vp,UniqueChars * bytes)3190 static const char* ToSource(JSContext* cx, HandleValue vp, UniqueChars* bytes) {
3191 RootedString str(cx, JS_ValueToSource(cx, vp));
3192 if (str) {
3193 *bytes = JS_EncodeStringToUTF8(cx, str);
3194 if (*bytes) {
3195 return bytes->get();
3196 }
3197 }
3198 JS_ClearPendingException(cx);
3199 return "<<error converting value to string>>";
3200 }
3201
AssertEq(JSContext * cx,unsigned argc,Value * vp)3202 static bool AssertEq(JSContext* cx, unsigned argc, Value* vp) {
3203 CallArgs args = CallArgsFromVp(argc, vp);
3204 if (!(args.length() == 2 || (args.length() == 3 && args[2].isString()))) {
3205 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
3206 (args.length() < 2) ? JSSMSG_NOT_ENOUGH_ARGS
3207 : (args.length() == 3) ? JSSMSG_INVALID_ARGS
3208 : JSSMSG_TOO_MANY_ARGS,
3209 "assertEq");
3210 return false;
3211 }
3212
3213 bool same;
3214 if (!JS::SameValue(cx, args[0], args[1], &same)) {
3215 return false;
3216 }
3217 if (!same) {
3218 UniqueChars bytes0, bytes1;
3219 const char* actual = ToSource(cx, args[0], &bytes0);
3220 const char* expected = ToSource(cx, args[1], &bytes1);
3221 if (args.length() == 2) {
3222 JS_ReportErrorNumberUTF8(cx, my_GetErrorMessage, nullptr,
3223 JSSMSG_ASSERT_EQ_FAILED, actual, expected);
3224 } else {
3225 RootedString message(cx, args[2].toString());
3226 UniqueChars bytes2 = QuoteString(cx, message);
3227 if (!bytes2) {
3228 return false;
3229 }
3230 JS_ReportErrorNumberUTF8(cx, my_GetErrorMessage, nullptr,
3231 JSSMSG_ASSERT_EQ_FAILED_MSG, actual, expected,
3232 bytes2.get());
3233 }
3234 return false;
3235 }
3236 args.rval().setUndefined();
3237 return true;
3238 }
3239
GetTopScript(JSContext * cx)3240 static JSScript* GetTopScript(JSContext* cx) {
3241 NonBuiltinScriptFrameIter iter(cx);
3242 return iter.done() ? nullptr : iter.script();
3243 }
3244
GetScriptAndPCArgs(JSContext * cx,CallArgs & args,MutableHandleScript scriptp,int32_t * ip)3245 static bool GetScriptAndPCArgs(JSContext* cx, CallArgs& args,
3246 MutableHandleScript scriptp, int32_t* ip) {
3247 RootedScript script(cx, GetTopScript(cx));
3248 *ip = 0;
3249 if (!args.get(0).isUndefined()) {
3250 HandleValue v = args[0];
3251 unsigned intarg = 0;
3252 if (v.isObject() && JS::GetClass(&v.toObject()) == &JSFunction::class_) {
3253 script = TestingFunctionArgumentToScript(cx, v);
3254 if (!script) {
3255 return false;
3256 }
3257 intarg++;
3258 }
3259 if (!args.get(intarg).isUndefined()) {
3260 if (!JS::ToInt32(cx, args[intarg], ip)) {
3261 return false;
3262 }
3263 if ((uint32_t)*ip >= script->length()) {
3264 JS_ReportErrorASCII(cx, "Invalid PC");
3265 return false;
3266 }
3267 }
3268 }
3269
3270 scriptp.set(script);
3271
3272 return true;
3273 }
3274
LineToPC(JSContext * cx,unsigned argc,Value * vp)3275 static bool LineToPC(JSContext* cx, unsigned argc, Value* vp) {
3276 CallArgs args = CallArgsFromVp(argc, vp);
3277
3278 if (args.length() == 0) {
3279 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
3280 JSSMSG_LINE2PC_USAGE);
3281 return false;
3282 }
3283
3284 RootedScript script(cx, GetTopScript(cx));
3285 int32_t lineArg = 0;
3286 if (args[0].isObject() && args[0].toObject().is<JSFunction>()) {
3287 script = TestingFunctionArgumentToScript(cx, args[0]);
3288 if (!script) {
3289 return false;
3290 }
3291 lineArg++;
3292 }
3293
3294 uint32_t lineno;
3295 if (!ToUint32(cx, args.get(lineArg), &lineno)) {
3296 return false;
3297 }
3298
3299 jsbytecode* pc = LineNumberToPC(script, lineno);
3300 if (!pc) {
3301 return false;
3302 }
3303 args.rval().setInt32(script->pcToOffset(pc));
3304 return true;
3305 }
3306
PCToLine(JSContext * cx,unsigned argc,Value * vp)3307 static bool PCToLine(JSContext* cx, unsigned argc, Value* vp) {
3308 CallArgs args = CallArgsFromVp(argc, vp);
3309 RootedScript script(cx);
3310 int32_t i;
3311 unsigned lineno;
3312
3313 if (!GetScriptAndPCArgs(cx, args, &script, &i)) {
3314 return false;
3315 }
3316 lineno = PCToLineNumber(script, script->offsetToPC(i));
3317 if (!lineno) {
3318 return false;
3319 }
3320 args.rval().setInt32(lineno);
3321 return true;
3322 }
3323
3324 #if defined(DEBUG) || defined(JS_JITSPEW)
3325
SrcNotes(JSContext * cx,HandleScript script,Sprinter * sp)3326 [[nodiscard]] static bool SrcNotes(JSContext* cx, HandleScript script,
3327 Sprinter* sp) {
3328 if (!sp->put("\nSource notes:\n") ||
3329 !sp->jsprintf("%4s %4s %6s %5s %6s %-10s %s\n", "ofs", "line", "column",
3330 "pc", "delta", "desc", "args") ||
3331 !sp->put("---- ---- ------ ----- ------ ---------- ------\n")) {
3332 return false;
3333 }
3334
3335 unsigned offset = 0;
3336 unsigned lineno = script->lineno();
3337 unsigned column = script->column();
3338 SrcNote* notes = script->notes();
3339 for (SrcNoteIterator iter(notes); !iter.atEnd(); ++iter) {
3340 auto sn = *iter;
3341
3342 unsigned delta = sn->delta();
3343 offset += delta;
3344 SrcNoteType type = sn->type();
3345 const char* name = sn->name();
3346 if (!sp->jsprintf("%3u: %4u %6u %5u [%4u] %-10s", unsigned(sn - notes),
3347 lineno, column, offset, delta, name)) {
3348 return false;
3349 }
3350
3351 switch (type) {
3352 case SrcNoteType::Null:
3353 case SrcNoteType::AssignOp:
3354 case SrcNoteType::Breakpoint:
3355 case SrcNoteType::StepSep:
3356 case SrcNoteType::XDelta:
3357 break;
3358
3359 case SrcNoteType::ColSpan: {
3360 uint32_t colspan = SrcNote::ColSpan::getSpan(sn);
3361 if (!sp->jsprintf(" colspan %u", colspan)) {
3362 return false;
3363 }
3364 column += colspan;
3365 break;
3366 }
3367
3368 case SrcNoteType::SetLine:
3369 lineno = SrcNote::SetLine::getLine(sn, script->lineno());
3370 if (!sp->jsprintf(" lineno %u", lineno)) {
3371 return false;
3372 }
3373 column = 0;
3374 break;
3375
3376 case SrcNoteType::NewLine:
3377 ++lineno;
3378 column = 0;
3379 break;
3380
3381 default:
3382 MOZ_ASSERT_UNREACHABLE("unrecognized srcnote");
3383 }
3384 if (!sp->put("\n")) {
3385 return false;
3386 }
3387 }
3388
3389 return true;
3390 }
3391
Notes(JSContext * cx,unsigned argc,Value * vp)3392 static bool Notes(JSContext* cx, unsigned argc, Value* vp) {
3393 CallArgs args = CallArgsFromVp(argc, vp);
3394 Sprinter sprinter(cx);
3395 if (!sprinter.init()) {
3396 return false;
3397 }
3398
3399 for (unsigned i = 0; i < args.length(); i++) {
3400 RootedScript script(cx, TestingFunctionArgumentToScript(cx, args[i]));
3401 if (!script) {
3402 return false;
3403 }
3404
3405 if (!SrcNotes(cx, script, &sprinter)) {
3406 return false;
3407 }
3408 }
3409
3410 JSString* str = JS_NewStringCopyZ(cx, sprinter.string());
3411 if (!str) {
3412 return false;
3413 }
3414 args.rval().setString(str);
3415 return true;
3416 }
3417
TryNoteName(TryNoteKind kind)3418 static const char* TryNoteName(TryNoteKind kind) {
3419 switch (kind) {
3420 case TryNoteKind::Catch:
3421 return "catch";
3422 case TryNoteKind::Finally:
3423 return "finally";
3424 case TryNoteKind::ForIn:
3425 return "for-in";
3426 case TryNoteKind::ForOf:
3427 return "for-of";
3428 case TryNoteKind::Loop:
3429 return "loop";
3430 case TryNoteKind::ForOfIterClose:
3431 return "for-of-iterclose";
3432 case TryNoteKind::Destructuring:
3433 return "destructuring";
3434 }
3435
3436 MOZ_CRASH("Bad TryNoteKind");
3437 }
3438
TryNotes(JSContext * cx,HandleScript script,Sprinter * sp)3439 [[nodiscard]] static bool TryNotes(JSContext* cx, HandleScript script,
3440 Sprinter* sp) {
3441 if (!sp->put(
3442 "\nException table:\nkind stack start end\n")) {
3443 return false;
3444 }
3445
3446 for (const TryNote& tn : script->trynotes()) {
3447 if (!sp->jsprintf(" %-16s %6u %8u %8u\n", TryNoteName(tn.kind()),
3448 tn.stackDepth, tn.start, tn.start + tn.length)) {
3449 return false;
3450 }
3451 }
3452 return true;
3453 }
3454
ScopeNotes(JSContext * cx,HandleScript script,Sprinter * sp)3455 [[nodiscard]] static bool ScopeNotes(JSContext* cx, HandleScript script,
3456 Sprinter* sp) {
3457 if (!sp->put("\nScope notes:\n index parent start end\n")) {
3458 return false;
3459 }
3460
3461 for (const ScopeNote& note : script->scopeNotes()) {
3462 if (note.index == ScopeNote::NoScopeIndex) {
3463 if (!sp->jsprintf("%8s ", "(none)")) {
3464 return false;
3465 }
3466 } else {
3467 if (!sp->jsprintf("%8u ", note.index.index)) {
3468 return false;
3469 }
3470 }
3471 if (note.parent == ScopeNote::NoScopeIndex) {
3472 if (!sp->jsprintf("%8s ", "(none)")) {
3473 return false;
3474 }
3475 } else {
3476 if (!sp->jsprintf("%8u ", note.parent)) {
3477 return false;
3478 }
3479 }
3480 if (!sp->jsprintf("%8u %8u\n", note.start, note.start + note.length)) {
3481 return false;
3482 }
3483 }
3484 return true;
3485 }
3486
GCThings(JSContext * cx,HandleScript script,Sprinter * sp)3487 [[nodiscard]] static bool GCThings(JSContext* cx, HandleScript script,
3488 Sprinter* sp) {
3489 if (!sp->put("\nGC things:\n index type value\n")) {
3490 return false;
3491 }
3492
3493 size_t i = 0;
3494 for (JS::GCCellPtr gcThing : script->gcthings()) {
3495 if (!sp->jsprintf("%8zu ", i)) {
3496 return false;
3497 }
3498 if (gcThing.is<BigInt>()) {
3499 if (!sp->put("BigInt ")) {
3500 return false;
3501 }
3502 gcThing.as<BigInt>().dump(*sp);
3503 if (!sp->put("\n")) {
3504 return false;
3505 }
3506 } else if (gcThing.is<Scope>()) {
3507 if (!sp->put("Scope ")) {
3508 return false;
3509 }
3510 Rooted<Scope*> scope(cx, &gcThing.as<Scope>());
3511 if (!Scope::dumpForDisassemble(cx, scope, *sp,
3512 " ")) {
3513 return false;
3514 }
3515 if (!sp->put("\n")) {
3516 return false;
3517 }
3518 } else if (gcThing.is<JSObject>()) {
3519 JSObject* obj = &gcThing.as<JSObject>();
3520 if (obj->is<JSFunction>()) {
3521 if (!sp->put("Function ")) {
3522 return false;
3523 }
3524 RootedFunction fun(cx, &obj->as<JSFunction>());
3525 if (fun->displayAtom()) {
3526 Rooted<JSAtom*> name(cx, fun->displayAtom());
3527 UniqueChars utf8chars = JS_EncodeStringToUTF8(cx, name);
3528 if (!utf8chars) {
3529 return false;
3530 }
3531 if (!sp->put(utf8chars.get())) {
3532 return false;
3533 }
3534 } else {
3535 if (!sp->put("(anonymous)")) {
3536 return false;
3537 }
3538 }
3539
3540 if (fun->hasBaseScript()) {
3541 BaseScript* script = fun->baseScript();
3542 if (!sp->jsprintf(" @ %u:%u\n", script->lineno(), script->column())) {
3543 return false;
3544 }
3545 } else {
3546 if (!sp->put(" (no script)\n")) {
3547 return false;
3548 }
3549 }
3550 } else {
3551 if (obj->is<RegExpObject>()) {
3552 if (!sp->put("RegExp ")) {
3553 return false;
3554 }
3555 } else {
3556 if (!sp->put("Object ")) {
3557 return false;
3558 }
3559 }
3560
3561 RootedValue objValue(cx, ObjectValue(*obj));
3562 RootedString str(cx, ValueToSource(cx, objValue));
3563 if (!str) {
3564 return false;
3565 }
3566 UniqueChars utf8chars = JS_EncodeStringToUTF8(cx, str);
3567 if (!utf8chars) {
3568 return false;
3569 }
3570 if (!sp->put(utf8chars.get())) {
3571 return false;
3572 }
3573
3574 if (!sp->put("\n")) {
3575 return false;
3576 }
3577 }
3578 } else if (gcThing.is<JSString>()) {
3579 if (!sp->put("Atom ")) {
3580 return false;
3581 }
3582 RootedAtom atom(cx, &gcThing.as<JSString>().asAtom());
3583 UniqueChars chars = QuoteString(cx, atom, '"');
3584 if (!chars) {
3585 return false;
3586 }
3587 if (!sp->put(chars.get())) {
3588 return false;
3589 }
3590 if (!sp->put("\n")) {
3591 return false;
3592 }
3593 } else {
3594 if (!sp->put("Unknown\n")) {
3595 return false;
3596 }
3597 }
3598 i++;
3599 }
3600
3601 return true;
3602 }
3603
DisassembleScript(JSContext * cx,HandleScript script,HandleFunction fun,bool lines,bool recursive,bool sourceNotes,bool gcThings,Sprinter * sp)3604 [[nodiscard]] static bool DisassembleScript(JSContext* cx, HandleScript script,
3605 HandleFunction fun, bool lines,
3606 bool recursive, bool sourceNotes,
3607 bool gcThings, Sprinter* sp) {
3608 if (fun) {
3609 if (!sp->put("flags:")) {
3610 return false;
3611 }
3612 if (fun->isLambda()) {
3613 if (!sp->put(" LAMBDA")) {
3614 return false;
3615 }
3616 }
3617 if (fun->needsCallObject()) {
3618 if (!sp->put(" NEEDS_CALLOBJECT")) {
3619 return false;
3620 }
3621 }
3622 if (fun->needsExtraBodyVarEnvironment()) {
3623 if (!sp->put(" NEEDS_EXTRABODYVARENV")) {
3624 return false;
3625 }
3626 }
3627 if (fun->needsNamedLambdaEnvironment()) {
3628 if (!sp->put(" NEEDS_NAMEDLAMBDAENV")) {
3629 return false;
3630 }
3631 }
3632 if (fun->isConstructor()) {
3633 if (!sp->put(" CONSTRUCTOR")) {
3634 return false;
3635 }
3636 }
3637 if (fun->isSelfHostedBuiltin()) {
3638 if (!sp->put(" SELF_HOSTED")) {
3639 return false;
3640 }
3641 }
3642 if (fun->isArrow()) {
3643 if (!sp->put(" ARROW")) {
3644 return false;
3645 }
3646 }
3647 if (!sp->put("\n")) {
3648 return false;
3649 }
3650 }
3651
3652 if (!Disassemble(cx, script, lines, sp)) {
3653 return false;
3654 }
3655 if (sourceNotes) {
3656 if (!SrcNotes(cx, script, sp)) {
3657 return false;
3658 }
3659 }
3660 if (!TryNotes(cx, script, sp)) {
3661 return false;
3662 }
3663 if (!ScopeNotes(cx, script, sp)) {
3664 return false;
3665 }
3666 if (gcThings) {
3667 if (!GCThings(cx, script, sp)) {
3668 return false;
3669 }
3670 }
3671
3672 if (recursive) {
3673 for (JS::GCCellPtr gcThing : script->gcthings()) {
3674 if (!gcThing.is<JSObject>()) {
3675 continue;
3676 }
3677
3678 JSObject* obj = &gcThing.as<JSObject>();
3679 if (obj->is<JSFunction>()) {
3680 if (!sp->put("\n")) {
3681 return false;
3682 }
3683
3684 RootedFunction fun(cx, &obj->as<JSFunction>());
3685 if (fun->isInterpreted()) {
3686 RootedScript script(cx, JSFunction::getOrCreateScript(cx, fun));
3687 if (!script || !DisassembleScript(cx, script, fun, lines, recursive,
3688 sourceNotes, gcThings, sp)) {
3689 return false;
3690 }
3691 } else {
3692 if (!sp->put("[native code]\n")) {
3693 return false;
3694 }
3695 }
3696 }
3697 }
3698 }
3699
3700 return true;
3701 }
3702
3703 namespace {
3704
3705 struct DisassembleOptionParser {
3706 unsigned argc;
3707 Value* argv;
3708 bool lines;
3709 bool recursive;
3710 bool sourceNotes;
3711 bool gcThings;
3712
DisassembleOptionParser__anon14ecce4c0311::DisassembleOptionParser3713 DisassembleOptionParser(unsigned argc, Value* argv)
3714 : argc(argc),
3715 argv(argv),
3716 lines(false),
3717 recursive(false),
3718 sourceNotes(true),
3719 gcThings(false) {}
3720
parse__anon14ecce4c0311::DisassembleOptionParser3721 bool parse(JSContext* cx) {
3722 /* Read options off early arguments */
3723 while (argc > 0 && argv[0].isString()) {
3724 JSString* str = argv[0].toString();
3725 JSLinearString* linearStr = JS_EnsureLinearString(cx, str);
3726 if (!linearStr) {
3727 return false;
3728 }
3729 if (JS_LinearStringEqualsLiteral(linearStr, "-l")) {
3730 lines = true;
3731 } else if (JS_LinearStringEqualsLiteral(linearStr, "-r")) {
3732 recursive = true;
3733 } else if (JS_LinearStringEqualsLiteral(linearStr, "-S")) {
3734 sourceNotes = false;
3735 } else if (JS_LinearStringEqualsLiteral(linearStr, "-g")) {
3736 gcThings = true;
3737 } else {
3738 break;
3739 }
3740 argv++;
3741 argc--;
3742 }
3743 return true;
3744 }
3745 };
3746
3747 } /* anonymous namespace */
3748
DisassembleToSprinter(JSContext * cx,unsigned argc,Value * vp,Sprinter * sprinter)3749 static bool DisassembleToSprinter(JSContext* cx, unsigned argc, Value* vp,
3750 Sprinter* sprinter) {
3751 CallArgs args = CallArgsFromVp(argc, vp);
3752 DisassembleOptionParser p(args.length(), args.array());
3753 if (!p.parse(cx)) {
3754 return false;
3755 }
3756
3757 if (p.argc == 0) {
3758 /* Without arguments, disassemble the current script. */
3759 RootedScript script(cx, GetTopScript(cx));
3760 if (script) {
3761 JSAutoRealm ar(cx, script);
3762 if (!Disassemble(cx, script, p.lines, sprinter)) {
3763 return false;
3764 }
3765 if (!SrcNotes(cx, script, sprinter)) {
3766 return false;
3767 }
3768 if (!TryNotes(cx, script, sprinter)) {
3769 return false;
3770 }
3771 if (!ScopeNotes(cx, script, sprinter)) {
3772 return false;
3773 }
3774 if (p.gcThings) {
3775 if (!GCThings(cx, script, sprinter)) {
3776 return false;
3777 }
3778 }
3779 }
3780 } else {
3781 for (unsigned i = 0; i < p.argc; i++) {
3782 RootedFunction fun(cx);
3783 RootedScript script(cx);
3784 RootedValue value(cx, p.argv[i]);
3785 if (value.isObject() && value.toObject().is<ShellModuleObjectWrapper>()) {
3786 script = value.toObject()
3787 .as<ShellModuleObjectWrapper>()
3788 .get()
3789 ->maybeScript();
3790 } else {
3791 script = TestingFunctionArgumentToScript(cx, value, fun.address());
3792 }
3793 if (!script) {
3794 return false;
3795 }
3796 if (!DisassembleScript(cx, script, fun, p.lines, p.recursive,
3797 p.sourceNotes, p.gcThings, sprinter)) {
3798 return false;
3799 }
3800 }
3801 }
3802
3803 return !sprinter->hadOutOfMemory();
3804 }
3805
DisassembleToString(JSContext * cx,unsigned argc,Value * vp)3806 static bool DisassembleToString(JSContext* cx, unsigned argc, Value* vp) {
3807 CallArgs args = CallArgsFromVp(argc, vp);
3808 Sprinter sprinter(cx);
3809 if (!sprinter.init()) {
3810 return false;
3811 }
3812 if (!DisassembleToSprinter(cx, args.length(), vp, &sprinter)) {
3813 return false;
3814 }
3815
3816 JS::ConstUTF8CharsZ utf8chars(sprinter.string(), strlen(sprinter.string()));
3817 JSString* str = JS_NewStringCopyUTF8Z(cx, utf8chars);
3818 if (!str) {
3819 return false;
3820 }
3821 args.rval().setString(str);
3822 return true;
3823 }
3824
Disassemble(JSContext * cx,unsigned argc,Value * vp)3825 static bool Disassemble(JSContext* cx, unsigned argc, Value* vp) {
3826 CallArgs args = CallArgsFromVp(argc, vp);
3827
3828 if (!gOutFile->isOpen()) {
3829 JS_ReportErrorASCII(cx, "output file is closed");
3830 return false;
3831 }
3832
3833 Sprinter sprinter(cx);
3834 if (!sprinter.init()) {
3835 return false;
3836 }
3837 if (!DisassembleToSprinter(cx, args.length(), vp, &sprinter)) {
3838 return false;
3839 }
3840
3841 fprintf(gOutFile->fp, "%s\n", sprinter.string());
3842 args.rval().setUndefined();
3843 return true;
3844 }
3845
DisassFile(JSContext * cx,unsigned argc,Value * vp)3846 static bool DisassFile(JSContext* cx, unsigned argc, Value* vp) {
3847 CallArgs args = CallArgsFromVp(argc, vp);
3848
3849 if (!gOutFile->isOpen()) {
3850 JS_ReportErrorASCII(cx, "output file is closed");
3851 return false;
3852 }
3853
3854 /* Support extra options at the start, just like Disassemble. */
3855 DisassembleOptionParser p(args.length(), args.array());
3856 if (!p.parse(cx)) {
3857 return false;
3858 }
3859
3860 if (!p.argc) {
3861 args.rval().setUndefined();
3862 return true;
3863 }
3864
3865 // We should change DisassembleOptionParser to store CallArgs.
3866 JSString* str = JS::ToString(cx, HandleValue::fromMarkedLocation(&p.argv[0]));
3867 if (!str) {
3868 return false;
3869 }
3870 UniqueChars filename = JS_EncodeStringToLatin1(cx, str);
3871 if (!filename) {
3872 return false;
3873 }
3874 RootedScript script(cx);
3875
3876 {
3877 CompileOptions options(cx);
3878 options.setIntroductionType("js shell disFile")
3879 .setFileAndLine(filename.get(), 1)
3880 .setIsRunOnce(true)
3881 .setNoScriptRval(true);
3882
3883 script = JS::CompileUtf8Path(cx, options, filename.get());
3884 if (!script) {
3885 return false;
3886 }
3887 }
3888
3889 Sprinter sprinter(cx);
3890 if (!sprinter.init()) {
3891 return false;
3892 }
3893 if (!DisassembleScript(cx, script, nullptr, p.lines, p.recursive,
3894 p.sourceNotes, p.gcThings, &sprinter)) {
3895 return false;
3896 }
3897
3898 fprintf(gOutFile->fp, "%s\n", sprinter.string());
3899
3900 args.rval().setUndefined();
3901 return true;
3902 }
3903
DisassWithSrc(JSContext * cx,unsigned argc,Value * vp)3904 static bool DisassWithSrc(JSContext* cx, unsigned argc, Value* vp) {
3905 CallArgs args = CallArgsFromVp(argc, vp);
3906
3907 if (!gOutFile->isOpen()) {
3908 JS_ReportErrorASCII(cx, "output file is closed");
3909 return false;
3910 }
3911
3912 const size_t lineBufLen = 512;
3913 unsigned len, line1, line2, bupline;
3914 char linebuf[lineBufLen];
3915 static const char sep[] = ";-------------------------";
3916
3917 RootedScript script(cx);
3918 for (unsigned i = 0; i < args.length(); i++) {
3919 script = TestingFunctionArgumentToScript(cx, args[i]);
3920 if (!script) {
3921 return false;
3922 }
3923
3924 if (!script->filename()) {
3925 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
3926 JSSMSG_FILE_SCRIPTS_ONLY);
3927 return false;
3928 }
3929
3930 FILE* file = fopen(script->filename(), "rb");
3931 if (!file) {
3932 /* FIXME: script->filename() should become UTF-8 (bug 987069). */
3933 ReportCantOpenErrorUnknownEncoding(cx, script->filename());
3934 return false;
3935 }
3936 auto closeFile = MakeScopeExit([file] { fclose(file); });
3937
3938 jsbytecode* pc = script->code();
3939 jsbytecode* end = script->codeEnd();
3940
3941 Sprinter sprinter(cx);
3942 if (!sprinter.init()) {
3943 return false;
3944 }
3945
3946 /* burn the leading lines */
3947 line2 = PCToLineNumber(script, pc);
3948 for (line1 = 0; line1 < line2 - 1; line1++) {
3949 char* tmp = fgets(linebuf, lineBufLen, file);
3950 if (!tmp) {
3951 /* FIXME: This should use UTF-8 (bug 987069). */
3952 JS_ReportErrorLatin1(cx, "failed to read %s fully", script->filename());
3953 return false;
3954 }
3955 }
3956
3957 bupline = 0;
3958 while (pc < end) {
3959 line2 = PCToLineNumber(script, pc);
3960
3961 if (line2 < line1) {
3962 if (bupline != line2) {
3963 bupline = line2;
3964 if (!sprinter.jsprintf("%s %3u: BACKUP\n", sep, line2)) {
3965 return false;
3966 }
3967 }
3968 } else {
3969 if (bupline && line1 == line2) {
3970 if (!sprinter.jsprintf("%s %3u: RESTORE\n", sep, line2)) {
3971 return false;
3972 }
3973 }
3974 bupline = 0;
3975 while (line1 < line2) {
3976 if (!fgets(linebuf, lineBufLen, file)) {
3977 /*
3978 * FIXME: script->filename() should become UTF-8
3979 * (bug 987069).
3980 */
3981 JS_ReportErrorNumberLatin1(cx, my_GetErrorMessage, nullptr,
3982 JSSMSG_UNEXPECTED_EOF,
3983 script->filename());
3984 return false;
3985 }
3986 line1++;
3987 if (!sprinter.jsprintf("%s %3u: %s", sep, line1, linebuf)) {
3988 return false;
3989 }
3990 }
3991 }
3992
3993 len =
3994 Disassemble1(cx, script, pc, script->pcToOffset(pc), true, &sprinter);
3995 if (!len) {
3996 return false;
3997 }
3998
3999 pc += len;
4000 }
4001
4002 fprintf(gOutFile->fp, "%s\n", sprinter.string());
4003 }
4004
4005 args.rval().setUndefined();
4006 return true;
4007 }
4008
4009 #endif /* defined(DEBUG) || defined(JS_JITSPEW) */
4010
4011 #ifdef JS_CACHEIR_SPEW
CacheIRHealthReport(JSContext * cx,unsigned argc,Value * vp)4012 static bool CacheIRHealthReport(JSContext* cx, unsigned argc, Value* vp) {
4013 CallArgs args = CallArgsFromVp(argc, vp);
4014
4015 js::jit::CacheIRHealth cih;
4016 RootedScript script(cx);
4017
4018 // In the case that we are calling this function from the shell and
4019 // the environment variable is not set, AutoSpewChannel automatically
4020 // sets and unsets the proper channel for the duration of spewing
4021 // a health report.
4022 AutoSpewChannel channel(cx, SpewChannel::CacheIRHealthReport, script);
4023 if (!argc) {
4024 // Calling CacheIRHealthReport without any arguments will create health
4025 // reports for all scripts in the zone.
4026 for (auto base = cx->zone()->cellIter<BaseScript>(); !base.done();
4027 base.next()) {
4028 if (!base->hasJitScript() || base->selfHosted()) {
4029 continue;
4030 }
4031
4032 script = base->asJSScript();
4033 cih.healthReportForScript(cx, script, js::jit::SpewContext::Shell);
4034 }
4035 } else {
4036 RootedValue value(cx, args.get(0));
4037
4038 if (value.isObject() && value.toObject().is<ShellModuleObjectWrapper>()) {
4039 script =
4040 value.toObject().as<ShellModuleObjectWrapper>().get()->maybeScript();
4041 } else {
4042 script = TestingFunctionArgumentToScript(cx, args.get(0));
4043 }
4044
4045 if (!script) {
4046 return false;
4047 }
4048
4049 cih.healthReportForScript(cx, script, js::jit::SpewContext::Shell);
4050 }
4051
4052 args.rval().setUndefined();
4053 return true;
4054 }
4055 #endif /* JS_CACHEIR_SPEW */
4056
4057 /* Pretend we can always preserve wrappers for dummy DOM objects. */
DummyPreserveWrapperCallback(JSContext * cx,HandleObject obj)4058 static bool DummyPreserveWrapperCallback(JSContext* cx, HandleObject obj) {
4059 return true;
4060 }
4061
DummyHasReleasedWrapperCallback(HandleObject obj)4062 static bool DummyHasReleasedWrapperCallback(HandleObject obj) { return true; }
4063
Intern(JSContext * cx,unsigned argc,Value * vp)4064 static bool Intern(JSContext* cx, unsigned argc, Value* vp) {
4065 CallArgs args = CallArgsFromVp(argc, vp);
4066
4067 JSString* str = JS::ToString(cx, args.get(0));
4068 if (!str) {
4069 return false;
4070 }
4071
4072 AutoStableStringChars strChars(cx);
4073 if (!strChars.initTwoByte(cx, str)) {
4074 return false;
4075 }
4076
4077 mozilla::Range<const char16_t> chars = strChars.twoByteRange();
4078
4079 if (!JS_AtomizeAndPinUCStringN(cx, chars.begin().get(), chars.length())) {
4080 return false;
4081 }
4082
4083 args.rval().setUndefined();
4084 return true;
4085 }
4086
4087 #ifdef FUZZING_JS_FUZZILLI
4088 // We have to assume that the fuzzer will be able to call this function e.g. by
4089 // enumerating the properties of the global object and eval'ing them. As such
4090 // this function is implemented in a way that requires passing some magic value
4091 // as first argument (with the idea being that the fuzzer won't be able to
4092 // generate this value) which then also acts as a selector for the operation
4093 // to perform.
Fuzzilli(JSContext * cx,unsigned argc,Value * vp)4094 static bool Fuzzilli(JSContext* cx, unsigned argc, Value* vp) {
4095 CallArgs args = CallArgsFromVp(argc, vp);
4096
4097 RootedString arg(cx, JS::ToString(cx, args.get(0)));
4098 if (!arg) {
4099 return false;
4100 }
4101 RootedLinearString operation(cx, StringToLinearString(cx, arg));
4102 if (!operation) {
4103 return false;
4104 }
4105
4106 if (StringEqualsAscii(operation, "FUZZILLI_CRASH")) {
4107 int type;
4108 if (!ToInt32(cx, args.get(1), &type)) {
4109 return false;
4110 }
4111
4112 // With this, we can test the various ways the JS shell can crash and make
4113 // sure that Fuzzilli is able to detect all of these failures properly.
4114 switch (type) {
4115 case 0:
4116 *((int*)0x41414141) = 0x1337;
4117 break;
4118 case 1:
4119 MOZ_RELEASE_ASSERT(false);
4120 break;
4121 case 2:
4122 MOZ_ASSERT(false);
4123 break;
4124 case 3:
4125 __asm__("int3");
4126 break;
4127 default:
4128 exit(1);
4129 }
4130 } else if (StringEqualsAscii(operation, "FUZZILLI_PRINT")) {
4131 static FILE* fzliout = fdopen(REPRL_DWFD, "w");
4132 if (!fzliout) {
4133 fprintf(
4134 stderr,
4135 "Fuzzer output channel not available, printing to stdout instead\n");
4136 fzliout = stdout;
4137 }
4138
4139 RootedString str(cx, JS::ToString(cx, args.get(1)));
4140 if (!str) {
4141 return false;
4142 }
4143 UniqueChars bytes = JS_EncodeStringToUTF8(cx, str);
4144 if (!bytes) {
4145 return false;
4146 }
4147 fprintf(fzliout, "%s\n", bytes.get());
4148 fflush(fzliout);
4149 }
4150
4151 args.rval().setUndefined();
4152 return true;
4153 }
4154
FuzzilliReprlGetAndRun(JSContext * cx)4155 static bool FuzzilliReprlGetAndRun(JSContext* cx) {
4156 size_t scriptSize = 0;
4157
4158 unsigned action;
4159 MOZ_RELEASE_ASSERT(read(REPRL_CRFD, &action, 4) == 4);
4160 if (action == 'cexe') {
4161 MOZ_RELEASE_ASSERT(read(REPRL_CRFD, &scriptSize, 8) == 8);
4162 } else {
4163 fprintf(stderr, "Unknown action: %u\n", action);
4164 _exit(-1);
4165 }
4166
4167 CompileOptions options(cx);
4168 options.setIntroductionType("reprl")
4169 .setFileAndLine("reprl", 1)
4170 .setIsRunOnce(true)
4171 .setNoScriptRval(true);
4172
4173 char* scriptSrc = static_cast<char*>(js_malloc(scriptSize));
4174
4175 char* ptr = scriptSrc;
4176 size_t remaining = scriptSize;
4177 while (remaining > 0) {
4178 ssize_t rv = read(REPRL_DRFD, ptr, remaining);
4179 if (rv <= 0) {
4180 fprintf(stderr, "Failed to load script\n");
4181 _exit(-1);
4182 }
4183 remaining -= rv;
4184 ptr += rv;
4185 }
4186
4187 JS::SourceText<Utf8Unit> srcBuf;
4188 if (!srcBuf.init(cx, scriptSrc, scriptSize,
4189 JS::SourceOwnership::TakeOwnership)) {
4190 return false;
4191 }
4192
4193 RootedScript script(cx, JS::Compile(cx, options, srcBuf));
4194 if (!script) {
4195 return false;
4196 }
4197
4198 if (!JS_ExecuteScript(cx, script)) {
4199 return false;
4200 }
4201
4202 return true;
4203 }
4204
4205 #endif /* FUZZING_JS_FUZZILLI */
4206
FuzzilliUseReprlMode(OptionParser * op)4207 static bool FuzzilliUseReprlMode(OptionParser* op) {
4208 #ifdef FUZZING_JS_FUZZILLI
4209 // Check if we should use REPRL mode
4210 bool reprl_mode = op->getBoolOption("reprl");
4211 if (reprl_mode) {
4212 // Check in with parent
4213 char helo[] = "HELO";
4214 if (write(REPRL_CWFD, helo, 4) != 4 || read(REPRL_CRFD, helo, 4) != 4) {
4215 reprl_mode = false;
4216 }
4217
4218 if (memcmp(helo, "HELO", 4) != 0) {
4219 fprintf(stderr, "Invalid response from parent\n");
4220 _exit(-1);
4221 }
4222 }
4223 return reprl_mode;
4224 #else
4225 return false;
4226 #endif /* FUZZING_JS_FUZZILLI */
4227 }
4228
Crash(JSContext * cx,unsigned argc,Value * vp)4229 static bool Crash(JSContext* cx, unsigned argc, Value* vp) {
4230 CallArgs args = CallArgsFromVp(argc, vp);
4231 if (args.length() == 0) {
4232 MOZ_CRASH("forced crash");
4233 }
4234 RootedString message(cx, JS::ToString(cx, args[0]));
4235 if (!message) {
4236 return false;
4237 }
4238 UniqueChars utf8chars = JS_EncodeStringToUTF8(cx, message);
4239 if (!utf8chars) {
4240 return false;
4241 }
4242 if (args.get(1).isObject()) {
4243 RootedValue v(cx);
4244 RootedObject opts(cx, &args[1].toObject());
4245 if (!JS_GetProperty(cx, opts, "suppress_minidump", &v)) {
4246 return false;
4247 }
4248 if (v.isBoolean() && v.toBoolean()) {
4249 js::NoteIntentionalCrash();
4250 }
4251 }
4252 #ifndef DEBUG
4253 MOZ_ReportCrash(utf8chars.get(), __FILE__, __LINE__);
4254 #endif
4255 MOZ_CRASH_UNSAFE(utf8chars.get());
4256 }
4257
GetSLX(JSContext * cx,unsigned argc,Value * vp)4258 static bool GetSLX(JSContext* cx, unsigned argc, Value* vp) {
4259 CallArgs args = CallArgsFromVp(argc, vp);
4260 RootedScript script(cx);
4261
4262 script = TestingFunctionArgumentToScript(cx, args.get(0));
4263 if (!script) {
4264 return false;
4265 }
4266 args.rval().setInt32(GetScriptLineExtent(script));
4267 return true;
4268 }
4269
ThrowError(JSContext * cx,unsigned argc,Value * vp)4270 static bool ThrowError(JSContext* cx, unsigned argc, Value* vp) {
4271 JS_ReportErrorASCII(cx, "This is an error");
4272 return false;
4273 }
4274
CopyErrorReportToObject(JSContext * cx,JSErrorReport * report,HandleObject obj)4275 static bool CopyErrorReportToObject(JSContext* cx, JSErrorReport* report,
4276 HandleObject obj) {
4277 RootedString nameStr(cx);
4278 if (report->exnType == JSEXN_WARN) {
4279 nameStr = JS_NewStringCopyZ(cx, "Warning");
4280 if (!nameStr) {
4281 return false;
4282 }
4283 } else {
4284 nameStr = GetErrorTypeName(cx, report->exnType);
4285 // GetErrorTypeName doesn't set an exception, but
4286 // can fail for InternalError or non-error objects.
4287 if (!nameStr) {
4288 nameStr = cx->runtime()->emptyString;
4289 }
4290 }
4291 RootedValue nameVal(cx, StringValue(nameStr));
4292 if (!DefineDataProperty(cx, obj, cx->names().name, nameVal)) {
4293 return false;
4294 }
4295
4296 RootedString messageStr(cx, report->newMessageString(cx));
4297 if (!messageStr) {
4298 return false;
4299 }
4300 RootedValue messageVal(cx, StringValue(messageStr));
4301 if (!DefineDataProperty(cx, obj, cx->names().message, messageVal)) {
4302 return false;
4303 }
4304
4305 RootedValue linenoVal(cx, Int32Value(report->lineno));
4306 if (!DefineDataProperty(cx, obj, cx->names().lineNumber, linenoVal)) {
4307 return false;
4308 }
4309
4310 RootedValue columnVal(cx, Int32Value(report->column));
4311 if (!DefineDataProperty(cx, obj, cx->names().columnNumber, columnVal)) {
4312 return false;
4313 }
4314
4315 RootedObject notesArray(cx, CreateErrorNotesArray(cx, report));
4316 if (!notesArray) {
4317 return false;
4318 }
4319
4320 RootedValue notesArrayVal(cx, ObjectValue(*notesArray));
4321 return DefineDataProperty(cx, obj, cx->names().notes, notesArrayVal);
4322 }
4323
CreateErrorReport(JSContext * cx,unsigned argc,Value * vp)4324 static bool CreateErrorReport(JSContext* cx, unsigned argc, Value* vp) {
4325 CallArgs args = CallArgsFromVp(argc, vp);
4326
4327 // We don't have a stack here, so just initialize with null.
4328 JS::ExceptionStack exnStack(cx, args.get(0), nullptr);
4329 JS::ErrorReportBuilder report(cx);
4330 if (!report.init(cx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) {
4331 return false;
4332 }
4333
4334 MOZ_ASSERT(!report.report()->isWarning());
4335
4336 RootedObject obj(cx, JS_NewPlainObject(cx));
4337 if (!obj) {
4338 return false;
4339 }
4340
4341 RootedString toString(cx,
4342 NewStringCopyUTF8Z<CanGC>(cx, report.toStringResult()));
4343 if (!toString) {
4344 return false;
4345 }
4346
4347 if (!JS_DefineProperty(cx, obj, "toStringResult", toString,
4348 JSPROP_ENUMERATE)) {
4349 return false;
4350 }
4351
4352 if (!CopyErrorReportToObject(cx, report.report(), obj)) {
4353 return false;
4354 }
4355
4356 args.rval().setObject(*obj);
4357 return true;
4358 }
4359
4360 #define LAZY_STANDARD_CLASSES
4361
4362 /* A class for easily testing the inner/outer object callbacks. */
4363 typedef struct ComplexObject {
4364 bool isInner;
4365 bool frozen;
4366 JSObject* inner;
4367 JSObject* outer;
4368 } ComplexObject;
4369
sandbox_enumerate(JSContext * cx,JS::HandleObject obj,JS::MutableHandleIdVector properties,bool enumerableOnly)4370 static bool sandbox_enumerate(JSContext* cx, JS::HandleObject obj,
4371 JS::MutableHandleIdVector properties,
4372 bool enumerableOnly) {
4373 RootedValue v(cx);
4374
4375 if (!JS_GetProperty(cx, obj, "lazy", &v)) {
4376 return false;
4377 }
4378
4379 if (!ToBoolean(v)) {
4380 return true;
4381 }
4382
4383 return JS_NewEnumerateStandardClasses(cx, obj, properties, enumerableOnly);
4384 }
4385
sandbox_resolve(JSContext * cx,HandleObject obj,HandleId id,bool * resolvedp)4386 static bool sandbox_resolve(JSContext* cx, HandleObject obj, HandleId id,
4387 bool* resolvedp) {
4388 RootedValue v(cx);
4389 if (!JS_GetProperty(cx, obj, "lazy", &v)) {
4390 return false;
4391 }
4392
4393 if (ToBoolean(v)) {
4394 return JS_ResolveStandardClass(cx, obj, id, resolvedp);
4395 }
4396 return true;
4397 }
4398
4399 static const JSClassOps sandbox_classOps = {
4400 nullptr, // addProperty
4401 nullptr, // delProperty
4402 nullptr, // enumerate
4403 sandbox_enumerate, // newEnumerate
4404 sandbox_resolve, // resolve
4405 nullptr, // mayResolve
4406 nullptr, // finalize
4407 nullptr, // call
4408 nullptr, // hasInstance
4409 nullptr, // construct
4410 JS_GlobalObjectTraceHook, // trace
4411 };
4412
4413 static const JSClass sandbox_class = {"sandbox", JSCLASS_GLOBAL_FLAGS,
4414 &sandbox_classOps};
4415
SetStandardRealmOptions(JS::RealmOptions & options)4416 static void SetStandardRealmOptions(JS::RealmOptions& options) {
4417 options.creationOptions()
4418 .setSharedMemoryAndAtomicsEnabled(enableSharedMemory)
4419 .setCoopAndCoepEnabled(false)
4420 .setStreamsEnabled(enableStreams)
4421 .setReadableByteStreamsEnabled(enableReadableByteStreams)
4422 .setBYOBStreamReadersEnabled(enableBYOBStreamReaders)
4423 .setWritableStreamsEnabled(enableWritableStreams)
4424 .setReadableStreamPipeToEnabled(enableReadableStreamPipeTo)
4425 .setWeakRefsEnabled(enableWeakRefs
4426 ? JS::WeakRefSpecifier::EnabledWithCleanupSome
4427 : JS::WeakRefSpecifier::Disabled)
4428 .setToSourceEnabled(enableToSource)
4429 .setPropertyErrorMessageFixEnabled(enablePropertyErrorMessageFix)
4430 .setIteratorHelpersEnabled(enableIteratorHelpers);
4431 }
4432
CheckRealmOptions(JSContext * cx,JS::RealmOptions & options,JSPrincipals * principals)4433 [[nodiscard]] static bool CheckRealmOptions(JSContext* cx,
4434 JS::RealmOptions& options,
4435 JSPrincipals* principals) {
4436 JS::RealmCreationOptions& creationOptions = options.creationOptions();
4437 if (creationOptions.compartmentSpecifier() !=
4438 JS::CompartmentSpecifier::ExistingCompartment) {
4439 return true;
4440 }
4441
4442 JS::Compartment* comp = creationOptions.compartment();
4443
4444 // All realms in a compartment must be either system or non-system.
4445 bool isSystem =
4446 principals && principals == cx->runtime()->trustedPrincipals();
4447 if (isSystem != IsSystemCompartment(comp)) {
4448 JS_ReportErrorASCII(cx,
4449 "Cannot create system and non-system realms in the "
4450 "same compartment");
4451 return false;
4452 }
4453
4454 // Debugger visibility is per-compartment, not per-realm, so make sure the
4455 // requested visibility matches the existing compartment's.
4456 if (creationOptions.invisibleToDebugger() != comp->invisibleToDebugger()) {
4457 JS_ReportErrorASCII(cx,
4458 "All the realms in a compartment must have "
4459 "the same debugger visibility");
4460 return false;
4461 }
4462
4463 return true;
4464 }
4465
NewSandbox(JSContext * cx,bool lazy)4466 static JSObject* NewSandbox(JSContext* cx, bool lazy) {
4467 JS::RealmOptions options;
4468 SetStandardRealmOptions(options);
4469
4470 if (defaultToSameCompartment) {
4471 options.creationOptions().setExistingCompartment(cx->global());
4472 } else {
4473 options.creationOptions().setNewCompartmentAndZone();
4474 }
4475
4476 JSPrincipals* principals = nullptr;
4477 if (!CheckRealmOptions(cx, options, principals)) {
4478 return nullptr;
4479 }
4480
4481 RootedObject obj(cx,
4482 JS_NewGlobalObject(cx, &sandbox_class, principals,
4483 JS::DontFireOnNewGlobalHook, options));
4484 if (!obj) {
4485 return nullptr;
4486 }
4487
4488 {
4489 JSAutoRealm ar(cx, obj);
4490 if (!lazy && !JS::InitRealmStandardClasses(cx)) {
4491 return nullptr;
4492 }
4493
4494 RootedValue value(cx, BooleanValue(lazy));
4495 if (!JS_DefineProperty(cx, obj, "lazy", value,
4496 JSPROP_PERMANENT | JSPROP_READONLY)) {
4497 return nullptr;
4498 }
4499
4500 JS_FireOnNewGlobalObject(cx, obj);
4501 }
4502
4503 if (!cx->compartment()->wrap(cx, &obj)) {
4504 return nullptr;
4505 }
4506 return obj;
4507 }
4508
EvalInContext(JSContext * cx,unsigned argc,Value * vp)4509 static bool EvalInContext(JSContext* cx, unsigned argc, Value* vp) {
4510 CallArgs args = CallArgsFromVp(argc, vp);
4511 if (!args.requireAtLeast(cx, "evalcx", 1)) {
4512 return false;
4513 }
4514
4515 RootedString str(cx, ToString(cx, args[0]));
4516 if (!str) {
4517 return false;
4518 }
4519
4520 RootedObject sobj(cx);
4521 if (args.hasDefined(1)) {
4522 sobj = ToObject(cx, args[1]);
4523 if (!sobj) {
4524 return false;
4525 }
4526 }
4527
4528 AutoStableStringChars strChars(cx);
4529 if (!strChars.initTwoByte(cx, str)) {
4530 return false;
4531 }
4532
4533 mozilla::Range<const char16_t> chars = strChars.twoByteRange();
4534 size_t srclen = chars.length();
4535 const char16_t* src = chars.begin().get();
4536
4537 bool lazy = false;
4538 if (srclen == 4) {
4539 if (src[0] == 'l' && src[1] == 'a' && src[2] == 'z' && src[3] == 'y') {
4540 lazy = true;
4541 srclen = 0;
4542 }
4543 }
4544
4545 if (!sobj) {
4546 sobj = NewSandbox(cx, lazy);
4547 if (!sobj) {
4548 return false;
4549 }
4550 }
4551
4552 if (srclen == 0) {
4553 args.rval().setObject(*sobj);
4554 return true;
4555 }
4556
4557 JS::AutoFilename filename;
4558 unsigned lineno;
4559
4560 DescribeScriptedCaller(cx, &filename, &lineno);
4561 {
4562 sobj = UncheckedUnwrap(sobj, true);
4563
4564 JSAutoRealm ar(cx, sobj);
4565
4566 sobj = ToWindowIfWindowProxy(sobj);
4567
4568 if (!JS_IsGlobalObject(sobj)) {
4569 JS_ReportErrorASCII(cx, "Invalid scope argument to evalcx");
4570 return false;
4571 }
4572
4573 JS::CompileOptions opts(cx);
4574 opts.setFileAndLine(filename.get(), lineno);
4575
4576 JS::SourceText<char16_t> srcBuf;
4577 if (!srcBuf.init(cx, src, srclen, JS::SourceOwnership::Borrowed) ||
4578 !JS::Evaluate(cx, opts, srcBuf, args.rval())) {
4579 return false;
4580 }
4581 }
4582
4583 if (!cx->compartment()->wrap(cx, args.rval())) {
4584 return false;
4585 }
4586
4587 return true;
4588 }
4589
EnsureGeckoProfilingStackInstalled(JSContext * cx,ShellContext * sc)4590 static bool EnsureGeckoProfilingStackInstalled(JSContext* cx,
4591 ShellContext* sc) {
4592 if (cx->geckoProfiler().infraInstalled()) {
4593 MOZ_ASSERT(sc->geckoProfilingStack);
4594 return true;
4595 }
4596
4597 MOZ_ASSERT(!sc->geckoProfilingStack);
4598 sc->geckoProfilingStack = MakeUnique<ProfilingStack>();
4599 if (!sc->geckoProfilingStack) {
4600 JS_ReportOutOfMemory(cx);
4601 return false;
4602 }
4603
4604 SetContextProfilingStack(cx, sc->geckoProfilingStack.get());
4605 return true;
4606 }
4607
4608 struct WorkerInput {
4609 JSRuntime* parentRuntime;
4610 UniqueTwoByteChars chars;
4611 size_t length;
4612
WorkerInputWorkerInput4613 WorkerInput(JSRuntime* parentRuntime, UniqueTwoByteChars chars, size_t length)
4614 : parentRuntime(parentRuntime), chars(std::move(chars)), length(length) {}
4615
4616 ~WorkerInput() = default;
4617 };
4618
DestroyShellCompartmentPrivate(JSFreeOp * fop,JS::Compartment * compartment)4619 static void DestroyShellCompartmentPrivate(JSFreeOp* fop,
4620 JS::Compartment* compartment) {
4621 auto priv = static_cast<ShellCompartmentPrivate*>(
4622 JS_GetCompartmentPrivate(compartment));
4623 js_delete(priv);
4624 }
4625
4626 static void SetWorkerContextOptions(JSContext* cx);
4627 static bool ShellBuildId(JS::BuildIdCharVector* buildId);
4628
ShellSourceElementCallback(JSContext * cx,JS::HandleValue privateValue)4629 static JSObject* ShellSourceElementCallback(JSContext* cx,
4630 JS::HandleValue privateValue) {
4631 if (!privateValue.isObject()) {
4632 return nullptr;
4633 }
4634
4635 // Due to nukeCCW shenanigans in the shell, we need to check for dead-proxy
4636 // objects that may have replaced an CCW. Otherwise the GetProperty below
4637 // would throw an exception which we do not want to support in this callback.
4638 if (js::IsDeadProxyObject(&privateValue.toObject())) {
4639 return nullptr;
4640 }
4641
4642 RootedObject infoObject(cx,
4643 CheckedUnwrapStatic(privateValue.toObjectOrNull()));
4644 AutoRealm ar(cx, infoObject);
4645
4646 RootedValue elementValue(cx);
4647 if (!JS_GetProperty(cx, infoObject, "element", &elementValue)) {
4648 // This shouldn't happen in the shell, as ParseDebugMetadata always
4649 // creates the infoObject with this property. In any case, this callback
4650 // must not leave an exception pending, so:
4651 MOZ_CRASH("error getting source element");
4652 }
4653
4654 if (elementValue.isObject()) {
4655 return &elementValue.toObject();
4656 }
4657
4658 return nullptr;
4659 }
4660
4661 static constexpr size_t gWorkerStackSize = 2 * 128 * sizeof(size_t) * 1024;
4662
WorkerMain(WorkerInput * input)4663 static void WorkerMain(WorkerInput* input) {
4664 MOZ_ASSERT(input->parentRuntime);
4665
4666 JSContext* cx = JS_NewContext(8L * 1024L * 1024L, input->parentRuntime);
4667 if (!cx) {
4668 return;
4669 }
4670
4671 ShellContext* sc = js_new<ShellContext>(cx);
4672 if (!sc) {
4673 return;
4674 }
4675
4676 auto guard = mozilla::MakeScopeExit([&] {
4677 CancelOffThreadJobsForContext(cx);
4678 sc->markObservers.reset();
4679 JS_SetContextPrivate(cx, nullptr);
4680 js_delete(sc);
4681 JS_DestroyContext(cx);
4682 js_delete(input);
4683 });
4684
4685 sc->isWorker = true;
4686
4687 JS_SetContextPrivate(cx, sc);
4688 JS_SetGrayGCRootsTracer(cx, TraceGrayRoots, nullptr);
4689 SetWorkerContextOptions(cx);
4690
4691 JS_SetFutexCanWait(cx);
4692 JS::SetWarningReporter(cx, WarningReporter);
4693 js::SetPreserveWrapperCallbacks(cx, DummyPreserveWrapperCallback,
4694 DummyHasReleasedWrapperCallback);
4695 JS_InitDestroyPrincipalsCallback(cx, ShellPrincipals::destroy);
4696 JS_SetDestroyCompartmentCallback(cx, DestroyShellCompartmentPrivate);
4697 JS::SetSourceElementCallback(cx, ShellSourceElementCallback);
4698
4699 js::SetWindowProxyClass(cx, &ShellWindowProxyClass);
4700
4701 js::UseInternalJobQueues(cx);
4702
4703 JS::SetHostCleanupFinalizationRegistryCallback(
4704 cx, ShellCleanupFinalizationRegistryCallback, sc);
4705
4706 if (!JS::InitSelfHostedCode(cx)) {
4707 return;
4708 }
4709
4710 EnvironmentPreparer environmentPreparer(cx);
4711
4712 do {
4713 JS::RealmOptions realmOptions;
4714 SetStandardRealmOptions(realmOptions);
4715
4716 RootedObject global(cx, NewGlobalObject(cx, realmOptions, nullptr,
4717 ShellGlobalKind::WindowProxy,
4718 /* immutablePrototype = */ true));
4719 if (!global) {
4720 break;
4721 }
4722
4723 JSAutoRealm ar(cx, global);
4724
4725 JS::ConstUTF8CharsZ path(processWideModuleLoadPath.get(),
4726 strlen(processWideModuleLoadPath.get()));
4727 RootedString moduleLoadPath(cx, JS_NewStringCopyUTF8Z(cx, path));
4728 if (!moduleLoadPath) {
4729 return;
4730 }
4731 sc->moduleLoader = js::MakeUnique<ModuleLoader>();
4732 if (!sc->moduleLoader || !sc->moduleLoader->init(cx, moduleLoadPath)) {
4733 return;
4734 }
4735
4736 JS::CompileOptions options(cx);
4737 options.setFileAndLine("<string>", 1).setIsRunOnce(true);
4738
4739 AutoReportException are(cx);
4740 JS::SourceText<char16_t> srcBuf;
4741 if (!srcBuf.init(cx, input->chars.get(), input->length,
4742 JS::SourceOwnership::Borrowed)) {
4743 break;
4744 }
4745
4746 RootedScript script(cx, JS::Compile(cx, options, srcBuf));
4747 if (!script) {
4748 break;
4749 }
4750 RootedValue result(cx);
4751 JS_ExecuteScript(cx, script, &result);
4752 } while (0);
4753
4754 KillWatchdog(cx);
4755 JS_SetGrayGCRootsTracer(cx, nullptr, nullptr);
4756 }
4757
4758 // Workers can spawn other workers, so we need a lock to access workerThreads.
4759 static Mutex* workerThreadsLock = nullptr;
4760 static Vector<js::Thread*, 0, SystemAllocPolicy> workerThreads;
4761
4762 class MOZ_RAII AutoLockWorkerThreads : public LockGuard<Mutex> {
4763 using Base = LockGuard<Mutex>;
4764
4765 public:
AutoLockWorkerThreads()4766 AutoLockWorkerThreads() : Base(*workerThreadsLock) {
4767 MOZ_ASSERT(workerThreadsLock);
4768 }
4769 };
4770
EvalInWorker(JSContext * cx,unsigned argc,Value * vp)4771 static bool EvalInWorker(JSContext* cx, unsigned argc, Value* vp) {
4772 if (!CanUseExtraThreads()) {
4773 JS_ReportErrorASCII(cx, "Can't create threads with --no-threads");
4774 return false;
4775 }
4776
4777 CallArgs args = CallArgsFromVp(argc, vp);
4778 if (!args.get(0).isString()) {
4779 JS_ReportErrorASCII(cx, "Invalid arguments");
4780 return false;
4781 }
4782
4783 #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
4784 if (cx->runningOOMTest) {
4785 JS_ReportErrorASCII(
4786 cx, "Can't create threads while running simulated OOM test");
4787 return false;
4788 }
4789 #endif
4790
4791 if (!args[0].toString()->ensureLinear(cx)) {
4792 return false;
4793 }
4794
4795 if (!workerThreadsLock) {
4796 workerThreadsLock = js_new<Mutex>(mutexid::ShellWorkerThreads);
4797 if (!workerThreadsLock) {
4798 ReportOutOfMemory(cx);
4799 return false;
4800 }
4801 }
4802
4803 JSLinearString* str = &args[0].toString()->asLinear();
4804
4805 UniqueTwoByteChars chars(js_pod_malloc<char16_t>(str->length()));
4806 if (!chars) {
4807 ReportOutOfMemory(cx);
4808 return false;
4809 }
4810
4811 CopyChars(chars.get(), *str);
4812
4813 WorkerInput* input = js_new<WorkerInput>(JS_GetParentRuntime(cx),
4814 std::move(chars), str->length());
4815 if (!input) {
4816 ReportOutOfMemory(cx);
4817 return false;
4818 }
4819
4820 Thread* thread;
4821 {
4822 AutoEnterOOMUnsafeRegion oomUnsafe;
4823 thread = js_new<Thread>(
4824 Thread::Options().setStackSize(gWorkerStackSize + 512 * 1024));
4825 if (!thread || !thread->init(WorkerMain, input)) {
4826 oomUnsafe.crash("EvalInWorker");
4827 }
4828 }
4829
4830 AutoLockWorkerThreads alwt;
4831 if (!workerThreads.append(thread)) {
4832 ReportOutOfMemory(cx);
4833 thread->join();
4834 js_delete(thread);
4835 return false;
4836 }
4837
4838 args.rval().setUndefined();
4839 return true;
4840 }
4841
ShapeOf(JSContext * cx,unsigned argc,JS::Value * vp)4842 static bool ShapeOf(JSContext* cx, unsigned argc, JS::Value* vp) {
4843 CallArgs args = CallArgsFromVp(argc, vp);
4844 if (!args.get(0).isObject()) {
4845 JS_ReportErrorASCII(cx, "shapeOf: object expected");
4846 return false;
4847 }
4848 JSObject* obj = &args[0].toObject();
4849 args.rval().set(JS_NumberValue(double(uintptr_t(obj->shape()) >> 3)));
4850 return true;
4851 }
4852
Sleep_fn(JSContext * cx,unsigned argc,Value * vp)4853 static bool Sleep_fn(JSContext* cx, unsigned argc, Value* vp) {
4854 ShellContext* sc = GetShellContext(cx);
4855 CallArgs args = CallArgsFromVp(argc, vp);
4856
4857 TimeDuration duration = TimeDuration::FromSeconds(0.0);
4858 if (args.length() > 0) {
4859 double t_secs;
4860 if (!ToNumber(cx, args[0], &t_secs)) {
4861 return false;
4862 }
4863 if (mozilla::IsNaN(t_secs)) {
4864 JS_ReportErrorASCII(cx, "sleep interval is not a number");
4865 return false;
4866 }
4867
4868 duration = TimeDuration::FromSeconds(std::max(0.0, t_secs));
4869 const TimeDuration MAX_TIMEOUT_INTERVAL =
4870 TimeDuration::FromSeconds(MAX_TIMEOUT_SECONDS);
4871 if (duration > MAX_TIMEOUT_INTERVAL) {
4872 JS_ReportErrorASCII(cx, "Excessive sleep interval");
4873 return false;
4874 }
4875 }
4876 {
4877 LockGuard<Mutex> guard(sc->watchdogLock);
4878 TimeStamp toWakeup = TimeStamp::Now() + duration;
4879 for (;;) {
4880 sc->sleepWakeup.wait_for(guard, duration);
4881 if (sc->serviceInterrupt) {
4882 break;
4883 }
4884 auto now = TimeStamp::Now();
4885 if (now >= toWakeup) {
4886 break;
4887 }
4888 duration = toWakeup - now;
4889 }
4890 }
4891 args.rval().setUndefined();
4892 return !sc->serviceInterrupt;
4893 }
4894
KillWatchdog(JSContext * cx)4895 static void KillWatchdog(JSContext* cx) {
4896 ShellContext* sc = GetShellContext(cx);
4897 Maybe<Thread> thread;
4898
4899 {
4900 LockGuard<Mutex> guard(sc->watchdogLock);
4901 std::swap(sc->watchdogThread, thread);
4902 if (thread) {
4903 // The watchdog thread becoming Nothing is its signal to exit.
4904 sc->watchdogWakeup.notify_one();
4905 }
4906 }
4907 if (thread) {
4908 thread->join();
4909 }
4910
4911 MOZ_ASSERT(!sc->watchdogThread);
4912 }
4913
WatchdogMain(JSContext * cx)4914 static void WatchdogMain(JSContext* cx) {
4915 ThisThread::SetName("JS Watchdog");
4916
4917 ShellContext* sc = GetShellContext(cx);
4918
4919 {
4920 LockGuard<Mutex> guard(sc->watchdogLock);
4921 while (sc->watchdogThread) {
4922 auto now = TimeStamp::Now();
4923 if (sc->watchdogTimeout && now >= sc->watchdogTimeout.value()) {
4924 /*
4925 * The timeout has just expired. Request an interrupt callback
4926 * outside the lock.
4927 */
4928 sc->watchdogTimeout = Nothing();
4929 {
4930 UnlockGuard<Mutex> unlock(guard);
4931 CancelExecution(cx);
4932 }
4933
4934 /* Wake up any threads doing sleep. */
4935 sc->sleepWakeup.notify_all();
4936 } else {
4937 if (sc->watchdogTimeout) {
4938 /*
4939 * Time hasn't expired yet. Simulate an interrupt callback
4940 * which doesn't abort execution.
4941 */
4942 JS_RequestInterruptCallback(cx);
4943 }
4944
4945 TimeDuration sleepDuration = sc->watchdogTimeout
4946 ? TimeDuration::FromSeconds(0.1)
4947 : TimeDuration::Forever();
4948 sc->watchdogWakeup.wait_for(guard, sleepDuration);
4949 }
4950 }
4951 }
4952 }
4953
ScheduleWatchdog(JSContext * cx,double t)4954 static bool ScheduleWatchdog(JSContext* cx, double t) {
4955 ShellContext* sc = GetShellContext(cx);
4956
4957 if (t <= 0) {
4958 LockGuard<Mutex> guard(sc->watchdogLock);
4959 sc->watchdogTimeout = Nothing();
4960 return true;
4961 }
4962
4963 #ifdef __wasi__
4964 return false;
4965 #endif
4966
4967 auto interval = TimeDuration::FromSeconds(t);
4968 auto timeout = TimeStamp::Now() + interval;
4969 LockGuard<Mutex> guard(sc->watchdogLock);
4970 if (!sc->watchdogThread) {
4971 MOZ_ASSERT(!sc->watchdogTimeout);
4972 sc->watchdogThread.emplace();
4973 AutoEnterOOMUnsafeRegion oomUnsafe;
4974 if (!sc->watchdogThread->init(WatchdogMain, cx)) {
4975 oomUnsafe.crash("watchdogThread.init");
4976 }
4977 } else if (!sc->watchdogTimeout || timeout < sc->watchdogTimeout.value()) {
4978 sc->watchdogWakeup.notify_one();
4979 }
4980 sc->watchdogTimeout = Some(timeout);
4981 return true;
4982 }
4983
KillWorkerThreads(JSContext * cx)4984 static void KillWorkerThreads(JSContext* cx) {
4985 MOZ_ASSERT_IF(!CanUseExtraThreads(), workerThreads.empty());
4986
4987 if (!workerThreadsLock) {
4988 MOZ_ASSERT(workerThreads.empty());
4989 return;
4990 }
4991
4992 while (true) {
4993 // We need to leave the AutoLockWorkerThreads scope before we call
4994 // js::Thread::join, to avoid deadlocks when AutoLockWorkerThreads is
4995 // used by the worker thread.
4996 Thread* thread;
4997 {
4998 AutoLockWorkerThreads alwt;
4999 if (workerThreads.empty()) {
5000 break;
5001 }
5002 thread = workerThreads.popCopy();
5003 }
5004 thread->join();
5005 js_delete(thread);
5006 }
5007
5008 workerThreads.clearAndFree();
5009
5010 js_delete(workerThreadsLock);
5011 workerThreadsLock = nullptr;
5012 }
5013
CancelExecution(JSContext * cx)5014 static void CancelExecution(JSContext* cx) {
5015 ShellContext* sc = GetShellContext(cx);
5016 sc->serviceInterrupt = true;
5017 JS_RequestInterruptCallback(cx);
5018 }
5019
SetTimeoutValue(JSContext * cx,double t)5020 static bool SetTimeoutValue(JSContext* cx, double t) {
5021 if (mozilla::IsNaN(t)) {
5022 JS_ReportErrorASCII(cx, "timeout is not a number");
5023 return false;
5024 }
5025 const TimeDuration MAX_TIMEOUT_INTERVAL =
5026 TimeDuration::FromSeconds(MAX_TIMEOUT_SECONDS);
5027 if (TimeDuration::FromSeconds(t) > MAX_TIMEOUT_INTERVAL) {
5028 JS_ReportErrorASCII(cx, "Excessive timeout value");
5029 return false;
5030 }
5031 GetShellContext(cx)->timeoutInterval = t;
5032 if (!ScheduleWatchdog(cx, t)) {
5033 JS_ReportErrorASCII(cx, "Failed to create the watchdog");
5034 return false;
5035 }
5036 return true;
5037 }
5038
Timeout(JSContext * cx,unsigned argc,Value * vp)5039 static bool Timeout(JSContext* cx, unsigned argc, Value* vp) {
5040 ShellContext* sc = GetShellContext(cx);
5041 CallArgs args = CallArgsFromVp(argc, vp);
5042
5043 if (args.length() == 0) {
5044 args.rval().setNumber(sc->timeoutInterval);
5045 return true;
5046 }
5047
5048 if (args.length() > 2) {
5049 JS_ReportErrorASCII(cx, "Wrong number of arguments");
5050 return false;
5051 }
5052
5053 double t;
5054 if (!ToNumber(cx, args[0], &t)) {
5055 return false;
5056 }
5057
5058 if (args.length() > 1) {
5059 RootedValue value(cx, args[1]);
5060 if (!value.isObject() || !value.toObject().is<JSFunction>()) {
5061 JS_ReportErrorASCII(cx, "Second argument must be a timeout function");
5062 return false;
5063 }
5064 sc->interruptFunc = value;
5065 sc->haveInterruptFunc = true;
5066 }
5067
5068 args.rval().setUndefined();
5069 return SetTimeoutValue(cx, t);
5070 }
5071
InterruptIf(JSContext * cx,unsigned argc,Value * vp)5072 static bool InterruptIf(JSContext* cx, unsigned argc, Value* vp) {
5073 CallArgs args = CallArgsFromVp(argc, vp);
5074
5075 if (args.length() != 1) {
5076 JS_ReportErrorASCII(cx, "Wrong number of arguments");
5077 return false;
5078 }
5079
5080 if (ToBoolean(args[0])) {
5081 GetShellContext(cx)->serviceInterrupt = true;
5082 JS_RequestInterruptCallback(cx);
5083 }
5084
5085 args.rval().setUndefined();
5086 return true;
5087 }
5088
InvokeInterruptCallbackWrapper(JSContext * cx,unsigned argc,Value * vp)5089 static bool InvokeInterruptCallbackWrapper(JSContext* cx, unsigned argc,
5090 Value* vp) {
5091 CallArgs args = CallArgsFromVp(argc, vp);
5092 if (args.length() != 1) {
5093 JS_ReportErrorASCII(cx, "Wrong number of arguments");
5094 return false;
5095 }
5096
5097 GetShellContext(cx)->serviceInterrupt = true;
5098 JS_RequestInterruptCallback(cx);
5099 bool interruptRv = CheckForInterrupt(cx);
5100
5101 // The interrupt handler could have set a pending exception. Since we call
5102 // back into JS, don't have it see the pending exception. If we have an
5103 // uncatchable exception that's not propagating a debug mode forced
5104 // return, return.
5105 if (!interruptRv && !cx->isExceptionPending() &&
5106 !cx->isPropagatingForcedReturn()) {
5107 return false;
5108 }
5109
5110 JS::AutoSaveExceptionState savedExc(cx);
5111
5112 FixedInvokeArgs<1> iargs(cx);
5113
5114 iargs[0].setBoolean(interruptRv);
5115
5116 RootedValue rv(cx);
5117 if (!js::Call(cx, args[0], UndefinedHandleValue, iargs, &rv)) {
5118 return false;
5119 }
5120
5121 args.rval().setUndefined();
5122 return interruptRv;
5123 }
5124
SetInterruptCallback(JSContext * cx,unsigned argc,Value * vp)5125 static bool SetInterruptCallback(JSContext* cx, unsigned argc, Value* vp) {
5126 CallArgs args = CallArgsFromVp(argc, vp);
5127
5128 if (args.length() != 1) {
5129 JS_ReportErrorASCII(cx, "Wrong number of arguments");
5130 return false;
5131 }
5132
5133 RootedValue value(cx, args[0]);
5134 if (!value.isObject() || !value.toObject().is<JSFunction>()) {
5135 JS_ReportErrorASCII(cx, "Argument must be a function");
5136 return false;
5137 }
5138 GetShellContext(cx)->interruptFunc = value;
5139 GetShellContext(cx)->haveInterruptFunc = true;
5140
5141 args.rval().setUndefined();
5142 return true;
5143 }
5144
5145 #ifdef DEBUG
5146 // var s0 = "A".repeat(10*1024);
5147 // interruptRegexp(/a(bc|bd)/, s0);
5148 // first arg is regexp
5149 // second arg is string
InterruptRegexp(JSContext * cx,unsigned argc,Value * vp)5150 static bool InterruptRegexp(JSContext* cx, unsigned argc, Value* vp) {
5151 CallArgs args = CallArgsFromVp(argc, vp);
5152 ShellContext* sc = GetShellContext(cx);
5153 RootedObject callee(cx, &args.callee());
5154
5155 if (args.length() != 2) {
5156 ReportUsageErrorASCII(cx, callee, "Wrong number of arguments.");
5157 return false;
5158 }
5159 if (!(args[0].isObject() && args[0].toObject().is<RegExpObject>())) {
5160 ReportUsageErrorASCII(cx, callee,
5161 "First argument must be a regular expression.");
5162 return false;
5163 }
5164 if (!args[1].isString()) {
5165 ReportUsageErrorASCII(cx, callee, "Second argument must be a String.");
5166 return false;
5167 }
5168 // Set interrupt flags
5169 sc->serviceInterrupt = true;
5170 js::irregexp::IsolateSetShouldSimulateInterrupt(cx->isolate);
5171
5172 RootedObject regexp(cx, &args[0].toObject());
5173 RootedString string(cx, args[1].toString());
5174 int32_t lastIndex = 0;
5175
5176 return js::RegExpMatcherRaw(cx, regexp, string, lastIndex, nullptr,
5177 args.rval());
5178 }
5179 #endif
5180
SetJitCompilerOption(JSContext * cx,unsigned argc,Value * vp)5181 static bool SetJitCompilerOption(JSContext* cx, unsigned argc, Value* vp) {
5182 CallArgs args = CallArgsFromVp(argc, vp);
5183 RootedObject callee(cx, &args.callee());
5184
5185 if (args.length() != 2) {
5186 ReportUsageErrorASCII(cx, callee, "Wrong number of arguments.");
5187 return false;
5188 }
5189
5190 if (!args[0].isString()) {
5191 ReportUsageErrorASCII(cx, callee, "First argument must be a String.");
5192 return false;
5193 }
5194
5195 if (!args[1].isInt32()) {
5196 ReportUsageErrorASCII(cx, callee, "Second argument must be an Int32.");
5197 return false;
5198 }
5199
5200 // Disallow setting JIT options when there are worker threads, to avoid
5201 // races.
5202 if (workerThreadsLock) {
5203 ReportUsageErrorASCII(
5204 cx, callee, "Can't set JIT options when there are worker threads.");
5205 return false;
5206 }
5207
5208 JSLinearString* strArg = JS_EnsureLinearString(cx, args[0].toString());
5209 if (!strArg) {
5210 return false;
5211 }
5212
5213 #define JIT_COMPILER_MATCH(key, string) \
5214 else if (JS_LinearStringEqualsLiteral(strArg, string)) opt = \
5215 JSJITCOMPILER_##key;
5216
5217 JSJitCompilerOption opt = JSJITCOMPILER_NOT_AN_OPTION;
5218 if (false) {
5219 }
5220 JIT_COMPILER_OPTIONS(JIT_COMPILER_MATCH);
5221 #undef JIT_COMPILER_MATCH
5222
5223 if (opt == JSJITCOMPILER_NOT_AN_OPTION) {
5224 ReportUsageErrorASCII(
5225 cx, callee,
5226 "First argument does not name a valid option (see jsapi.h).");
5227 return false;
5228 }
5229
5230 int32_t number = args[1].toInt32();
5231 if (number < 0) {
5232 number = -1;
5233 }
5234
5235 // Disallow enabling or disabling the Baseline Interpreter at runtime.
5236 // Enabling is a problem because the Baseline Interpreter code is only
5237 // present if the interpreter was enabled when the JitRuntime was created.
5238 // To support disabling we would have to discard all JitScripts. Furthermore,
5239 // we really want JitOptions to be immutable after startup so it's better to
5240 // use shell flags.
5241 if (opt == JSJITCOMPILER_BASELINE_INTERPRETER_ENABLE &&
5242 bool(number) != jit::IsBaselineInterpreterEnabled()) {
5243 JS_ReportErrorASCII(cx,
5244 "Enabling or disabling the Baseline Interpreter at "
5245 "runtime is not supported.");
5246 return false;
5247 }
5248
5249 // Throw if disabling the JITs and there's JIT code on the stack, to avoid
5250 // assertion failures.
5251 if ((opt == JSJITCOMPILER_BASELINE_ENABLE ||
5252 opt == JSJITCOMPILER_ION_ENABLE) &&
5253 number == 0) {
5254 js::jit::JitActivationIterator iter(cx);
5255 if (!iter.done()) {
5256 JS_ReportErrorASCII(cx,
5257 "Can't turn off JITs with JIT code on the stack.");
5258 return false;
5259 }
5260 }
5261
5262 // Throw if trying to disable all the Wasm compilers. The logic here is that
5263 // if we're trying to disable a compiler that is currently enabled and that is
5264 // the last compiler enabled then we must throw.
5265 //
5266 // Note that this check does not prevent an error from being thrown later.
5267 // Actual compiler availability is dynamic and depends on other conditions,
5268 // such as other options set and whether a debugger is present.
5269 if ((opt == JSJITCOMPILER_WASM_JIT_BASELINE ||
5270 opt == JSJITCOMPILER_WASM_JIT_OPTIMIZING) &&
5271 number == 0) {
5272 uint32_t baseline, optimizing;
5273 MOZ_ALWAYS_TRUE(JS_GetGlobalJitCompilerOption(
5274 cx, JSJITCOMPILER_WASM_JIT_BASELINE, &baseline));
5275 MOZ_ALWAYS_TRUE(JS_GetGlobalJitCompilerOption(
5276 cx, JSJITCOMPILER_WASM_JIT_OPTIMIZING, &optimizing));
5277 if (baseline + optimizing == 1) {
5278 if ((opt == JSJITCOMPILER_WASM_JIT_BASELINE && baseline) ||
5279 (opt == JSJITCOMPILER_WASM_JIT_OPTIMIZING && optimizing)) {
5280 JS_ReportErrorASCII(
5281 cx,
5282 "Disabling all the Wasm compilers at runtime is not supported.");
5283 return false;
5284 }
5285 }
5286 }
5287
5288 // JIT compiler options are process-wide, so we have to stop off-thread
5289 // compilations for all runtimes to avoid races.
5290 WaitForAllHelperThreads();
5291
5292 // Only release JIT code for the current runtime because there's no good
5293 // way to discard code for other runtimes.
5294 ReleaseAllJITCode(cx->runtime()->defaultFreeOp());
5295
5296 JS_SetGlobalJitCompilerOption(cx, opt, uint32_t(number));
5297
5298 args.rval().setUndefined();
5299 return true;
5300 }
5301
EnableLastWarning(JSContext * cx,unsigned argc,Value * vp)5302 static bool EnableLastWarning(JSContext* cx, unsigned argc, Value* vp) {
5303 ShellContext* sc = GetShellContext(cx);
5304 CallArgs args = CallArgsFromVp(argc, vp);
5305
5306 sc->lastWarningEnabled = true;
5307 sc->lastWarning.setNull();
5308
5309 args.rval().setUndefined();
5310 return true;
5311 }
5312
DisableLastWarning(JSContext * cx,unsigned argc,Value * vp)5313 static bool DisableLastWarning(JSContext* cx, unsigned argc, Value* vp) {
5314 ShellContext* sc = GetShellContext(cx);
5315 CallArgs args = CallArgsFromVp(argc, vp);
5316
5317 sc->lastWarningEnabled = false;
5318 sc->lastWarning.setNull();
5319
5320 args.rval().setUndefined();
5321 return true;
5322 }
5323
GetLastWarning(JSContext * cx,unsigned argc,Value * vp)5324 static bool GetLastWarning(JSContext* cx, unsigned argc, Value* vp) {
5325 ShellContext* sc = GetShellContext(cx);
5326 CallArgs args = CallArgsFromVp(argc, vp);
5327
5328 if (!sc->lastWarningEnabled) {
5329 JS_ReportErrorASCII(cx, "Call enableLastWarning first.");
5330 return false;
5331 }
5332
5333 if (!JS_WrapValue(cx, &sc->lastWarning)) {
5334 return false;
5335 }
5336
5337 args.rval().set(sc->lastWarning);
5338 return true;
5339 }
5340
ClearLastWarning(JSContext * cx,unsigned argc,Value * vp)5341 static bool ClearLastWarning(JSContext* cx, unsigned argc, Value* vp) {
5342 ShellContext* sc = GetShellContext(cx);
5343 CallArgs args = CallArgsFromVp(argc, vp);
5344
5345 if (!sc->lastWarningEnabled) {
5346 JS_ReportErrorASCII(cx, "Call enableLastWarning first.");
5347 return false;
5348 }
5349
5350 sc->lastWarning.setNull();
5351
5352 args.rval().setUndefined();
5353 return true;
5354 }
5355
5356 #if defined(DEBUG) || defined(JS_JITSPEW)
StackDump(JSContext * cx,unsigned argc,Value * vp)5357 static bool StackDump(JSContext* cx, unsigned argc, Value* vp) {
5358 CallArgs args = CallArgsFromVp(argc, vp);
5359
5360 if (!gOutFile->isOpen()) {
5361 JS_ReportErrorASCII(cx, "output file is closed");
5362 return false;
5363 }
5364
5365 bool showArgs = ToBoolean(args.get(0));
5366 bool showLocals = ToBoolean(args.get(1));
5367 bool showThisProps = ToBoolean(args.get(2));
5368
5369 JS::UniqueChars buf =
5370 JS::FormatStackDump(cx, showArgs, showLocals, showThisProps);
5371 if (!buf) {
5372 fputs("Failed to format JavaScript stack for dump\n", gOutFile->fp);
5373 JS_ClearPendingException(cx);
5374 } else {
5375 fputs(buf.get(), gOutFile->fp);
5376 }
5377
5378 args.rval().setUndefined();
5379 return true;
5380 }
5381 #endif
5382
StackPointerInfo(JSContext * cx,unsigned argc,Value * vp)5383 static bool StackPointerInfo(JSContext* cx, unsigned argc, Value* vp) {
5384 CallArgs args = CallArgsFromVp(argc, vp);
5385
5386 // Copy the truncated stack pointer to the result. This value is not used
5387 // as a pointer but as a way to measure frame-size from JS.
5388 args.rval().setInt32(int32_t(reinterpret_cast<size_t>(&args) & 0xfffffff));
5389 return true;
5390 }
5391
Elapsed(JSContext * cx,unsigned argc,Value * vp)5392 static bool Elapsed(JSContext* cx, unsigned argc, Value* vp) {
5393 CallArgs args = CallArgsFromVp(argc, vp);
5394 if (args.length() == 0) {
5395 double d = PRMJ_Now() - GetShellContext(cx)->startTime;
5396 args.rval().setDouble(d);
5397 return true;
5398 }
5399 JS_ReportErrorASCII(cx, "Wrong number of arguments");
5400 return false;
5401 }
5402
Compile(JSContext * cx,unsigned argc,Value * vp)5403 static bool Compile(JSContext* cx, unsigned argc, Value* vp) {
5404 CallArgs args = CallArgsFromVp(argc, vp);
5405 if (!args.requireAtLeast(cx, "compile", 1)) {
5406 return false;
5407 }
5408 if (!args[0].isString()) {
5409 const char* typeName = InformalValueTypeName(args[0]);
5410 JS_ReportErrorASCII(cx, "expected string to compile, got %s", typeName);
5411 return false;
5412 }
5413
5414 RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
5415 RootedString scriptContents(cx, args[0].toString());
5416
5417 AutoStableStringChars stableChars(cx);
5418 if (!stableChars.initTwoByte(cx, scriptContents)) {
5419 return false;
5420 }
5421
5422 JS::CompileOptions options(cx);
5423 options.setIntroductionType("js shell compile")
5424 .setFileAndLine("<string>", 1)
5425 .setIsRunOnce(true)
5426 .setNoScriptRval(true)
5427 .setdeferDebugMetadata();
5428 RootedValue privateValue(cx);
5429 RootedString elementAttributeName(cx);
5430
5431 if (args.length() >= 2) {
5432 if (args[1].isPrimitive()) {
5433 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
5434 JSSMSG_INVALID_ARGS, "compile");
5435 return false;
5436 }
5437
5438 RootedObject opts(cx, &args[1].toObject());
5439 if (!js::ParseCompileOptions(cx, options, opts, nullptr)) {
5440 return false;
5441 }
5442 if (!ParseDebugMetadata(cx, opts, &privateValue, &elementAttributeName)) {
5443 return false;
5444 }
5445 }
5446
5447 JS::SourceText<char16_t> srcBuf;
5448 if (!srcBuf.init(cx, stableChars.twoByteRange().begin().get(),
5449 scriptContents->length(), JS::SourceOwnership::Borrowed)) {
5450 return false;
5451 }
5452
5453 RootedScript script(cx, JS::Compile(cx, options, srcBuf));
5454 if (!script) {
5455 return false;
5456 }
5457
5458 if (!JS::UpdateDebugMetadata(cx, script, options, privateValue,
5459 elementAttributeName, nullptr, nullptr)) {
5460 return false;
5461 }
5462
5463 args.rval().setUndefined();
5464 return true;
5465 }
5466
EnsureShellCompartmentPrivate(JSContext * cx)5467 static ShellCompartmentPrivate* EnsureShellCompartmentPrivate(JSContext* cx) {
5468 Compartment* comp = cx->compartment();
5469 auto priv =
5470 static_cast<ShellCompartmentPrivate*>(JS_GetCompartmentPrivate(comp));
5471 if (!priv) {
5472 priv = cx->new_<ShellCompartmentPrivate>();
5473 JS_SetCompartmentPrivate(cx->compartment(), priv);
5474 }
5475 return priv;
5476 }
5477
ParseModule(JSContext * cx,unsigned argc,Value * vp)5478 static bool ParseModule(JSContext* cx, unsigned argc, Value* vp) {
5479 CallArgs args = CallArgsFromVp(argc, vp);
5480 if (!args.requireAtLeast(cx, "parseModule", 1)) {
5481 return false;
5482 }
5483
5484 if (!args[0].isString()) {
5485 const char* typeName = InformalValueTypeName(args[0]);
5486 JS_ReportErrorASCII(cx, "expected string to compile, got %s", typeName);
5487 return false;
5488 }
5489
5490 JSString* scriptContents = args[0].toString();
5491
5492 UniqueChars filename;
5493 CompileOptions options(cx);
5494 if (args.length() > 1) {
5495 if (!args[1].isString()) {
5496 const char* typeName = InformalValueTypeName(args[1]);
5497 JS_ReportErrorASCII(cx, "expected filename string, got %s", typeName);
5498 return false;
5499 }
5500
5501 RootedString str(cx, args[1].toString());
5502 filename = JS_EncodeStringToLatin1(cx, str);
5503 if (!filename) {
5504 return false;
5505 }
5506
5507 options.setFileAndLine(filename.get(), 1);
5508 } else {
5509 options.setFileAndLine("<string>", 1);
5510 }
5511 options.setModule();
5512
5513 AutoStableStringChars stableChars(cx);
5514 if (!stableChars.initTwoByte(cx, scriptContents)) {
5515 return false;
5516 }
5517
5518 const char16_t* chars = stableChars.twoByteRange().begin().get();
5519 JS::SourceText<char16_t> srcBuf;
5520 if (!srcBuf.init(cx, chars, scriptContents->length(),
5521 JS::SourceOwnership::Borrowed)) {
5522 return false;
5523 }
5524
5525 RootedObject module(cx, frontend::CompileModule(cx, options, srcBuf));
5526 if (!module) {
5527 return false;
5528 }
5529
5530 Rooted<ShellModuleObjectWrapper*> wrapper(
5531 cx, ShellModuleObjectWrapper::create(cx, module.as<ModuleObject>()));
5532 if (!wrapper) {
5533 return false;
5534 }
5535 args.rval().setObject(*wrapper);
5536 return true;
5537 }
5538
5539 // A JSObject that holds XDRBuffer.
5540 class XDRBufferObject : public NativeObject {
5541 static const size_t VECTOR_SLOT = 0;
5542 static const unsigned RESERVED_SLOTS = 1;
5543
5544 public:
5545 static const JSClassOps classOps_;
5546 static const JSClass class_;
5547
5548 [[nodiscard]] inline static XDRBufferObject* create(
5549 JSContext* cx, JS::TranscodeBuffer&& buf);
5550
data() const5551 JS::TranscodeBuffer* data() const {
5552 Value value = getReservedSlot(VECTOR_SLOT);
5553 auto buf = static_cast<JS::TranscodeBuffer*>(value.toPrivate());
5554 MOZ_ASSERT(buf);
5555 return buf;
5556 }
5557
hasData() const5558 bool hasData() const {
5559 // Data may not be present if we hit OOM in initialization.
5560 return !getReservedSlot(VECTOR_SLOT).isUndefined();
5561 }
5562
5563 static void finalize(JSFreeOp* fop, JSObject* obj);
5564 };
5565
5566 /*static */ const JSClassOps XDRBufferObject::classOps_ = {
5567 nullptr, // addProperty
5568 nullptr, // delProperty
5569 nullptr, // enumerate
5570 nullptr, // newEnumerate
5571 nullptr, // resolve
5572 nullptr, // mayResolve
5573 XDRBufferObject::finalize, // finalize
5574 nullptr, // call
5575 nullptr, // hasInstance
5576 nullptr, // construct
5577 nullptr, // trace
5578 };
5579
5580 /*static */ const JSClass XDRBufferObject::class_ = {
5581 "XDRBufferObject",
5582 JSCLASS_HAS_RESERVED_SLOTS(XDRBufferObject::RESERVED_SLOTS) |
5583 JSCLASS_BACKGROUND_FINALIZE,
5584 &XDRBufferObject::classOps_};
5585
create(JSContext * cx,JS::TranscodeBuffer && buf)5586 XDRBufferObject* XDRBufferObject::create(JSContext* cx,
5587 JS::TranscodeBuffer&& buf) {
5588 XDRBufferObject* bufObj =
5589 NewObjectWithGivenProto<XDRBufferObject>(cx, nullptr);
5590 if (!bufObj) {
5591 return nullptr;
5592 }
5593
5594 auto heapBuf = cx->make_unique<JS::TranscodeBuffer>(std::move(buf));
5595 if (!heapBuf) {
5596 return nullptr;
5597 }
5598
5599 size_t len = heapBuf->length();
5600 InitReservedSlot(bufObj, VECTOR_SLOT, heapBuf.release(), len,
5601 MemoryUse::XDRBufferElements);
5602
5603 return bufObj;
5604 }
5605
finalize(JSFreeOp * fop,JSObject * obj)5606 void XDRBufferObject::finalize(JSFreeOp* fop, JSObject* obj) {
5607 XDRBufferObject* buf = &obj->as<XDRBufferObject>();
5608 if (buf->hasData()) {
5609 fop->delete_(buf, buf->data(), buf->data()->length(),
5610 MemoryUse::XDRBufferElements);
5611 }
5612 }
5613
CodeModule(JSContext * cx,unsigned argc,Value * vp)5614 static bool CodeModule(JSContext* cx, unsigned argc, Value* vp) {
5615 CallArgs args = CallArgsFromVp(argc, vp);
5616 if (!args.requireAtLeast(cx, "codeModule", 1)) {
5617 return false;
5618 }
5619
5620 if (!args[0].isObject() ||
5621 !args[0].toObject().is<ShellModuleObjectWrapper>()) {
5622 const char* typeName = InformalValueTypeName(args[0]);
5623 JS_ReportErrorASCII(cx, "expected module object, got %s", typeName);
5624 return false;
5625 }
5626
5627 RootedModuleObject modObject(
5628 cx, args[0].toObject().as<ShellModuleObjectWrapper>().get());
5629 if (modObject->status() >= MODULE_STATUS_LINKING) {
5630 JS_ReportErrorASCII(cx, "cannot encode module after instantiation.");
5631 return false;
5632 }
5633
5634 JS::TranscodeBuffer buf;
5635 XDREncoder xdrEncoder_(cx, buf);
5636 XDRResult res = xdrEncoder_.codeModuleObject(&modObject);
5637 if (res.isErr()) {
5638 return false;
5639 }
5640
5641 XDRBufferObject* xdrBuf = XDRBufferObject::create(cx, std::move(buf));
5642 if (!xdrBuf) {
5643 return false;
5644 }
5645 args.rval().setObject(*xdrBuf);
5646 return true;
5647 }
5648
DecodeModule(JSContext * cx,unsigned argc,Value * vp)5649 static bool DecodeModule(JSContext* cx, unsigned argc, Value* vp) {
5650 CallArgs args = CallArgsFromVp(argc, vp);
5651 if (!args.requireAtLeast(cx, "decodeModule", 1)) {
5652 return false;
5653 }
5654
5655 if (!args[0].isObject() || !args[0].toObject().is<XDRBufferObject>()) {
5656 const char* typeName = InformalValueTypeName(args[0]);
5657 JS_ReportErrorASCII(cx, "expected XDRBufferObject to compile, got %s",
5658 typeName);
5659 return false;
5660 }
5661
5662 JS::CompileOptions options(cx);
5663 options.setModule();
5664
5665 XDRDecoder xdrDecoder_(cx, &options,
5666 *args[0].toObject().as<XDRBufferObject>().data());
5667 RootedModuleObject modObject(cx, nullptr);
5668 XDRResult res = xdrDecoder_.codeModuleObject(&modObject);
5669 if (res.isErr()) {
5670 return false;
5671 }
5672
5673 if (!ModuleObject::Freeze(cx, modObject)) {
5674 return false;
5675 }
5676
5677 Rooted<ShellModuleObjectWrapper*> wrapper(
5678 cx, ShellModuleObjectWrapper::create(cx, modObject));
5679 if (!wrapper) {
5680 return false;
5681 }
5682 args.rval().setObject(*wrapper);
5683 return true;
5684 }
5685
RegisterModule(JSContext * cx,unsigned argc,Value * vp)5686 static bool RegisterModule(JSContext* cx, unsigned argc, Value* vp) {
5687 CallArgs args = CallArgsFromVp(argc, vp);
5688 if (!args.requireAtLeast(cx, "registerModule", 2)) {
5689 return false;
5690 }
5691
5692 if (!args[0].isString()) {
5693 const char* typeName = InformalValueTypeName(args[0]);
5694 JS_ReportErrorASCII(cx, "expected string, got %s", typeName);
5695 return false;
5696 }
5697
5698 if (!args[1].isObject() ||
5699 !args[1].toObject().is<ShellModuleObjectWrapper>()) {
5700 const char* typeName = InformalValueTypeName(args[1]);
5701 JS_ReportErrorASCII(cx, "expected module, got %s", typeName);
5702 return false;
5703 }
5704
5705 ShellContext* sc = GetShellContext(cx);
5706 RootedModuleObject module(
5707 cx, args[1].toObject().as<ShellModuleObjectWrapper>().get());
5708
5709 RootedAtom specifier(cx, AtomizeString(cx, args[0].toString()));
5710 if (!specifier) {
5711 return false;
5712 }
5713
5714 RootedObject moduleRequest(cx, ModuleRequestObject::create(cx, specifier));
5715 if (!moduleRequest) {
5716 return false;
5717 }
5718
5719 if (!sc->moduleLoader->registerTestModule(cx, moduleRequest, module)) {
5720 return false;
5721 }
5722
5723 Rooted<ShellModuleObjectWrapper*> wrapper(
5724 cx, ShellModuleObjectWrapper::create(cx, module));
5725 if (!wrapper) {
5726 return false;
5727 }
5728 args.rval().setObject(*wrapper);
5729 return true;
5730 }
5731
GetModuleEnvironment(JSContext * cx,HandleModuleObject module)5732 static ModuleEnvironmentObject* GetModuleEnvironment(
5733 JSContext* cx, HandleModuleObject module) {
5734 // Use the initial environment so that tests can check bindings exists
5735 // before they have been instantiated.
5736 RootedModuleEnvironmentObject env(cx, &module->initialEnvironment());
5737 MOZ_ASSERT(env);
5738 return env;
5739 }
5740
GetModuleEnvironmentNames(JSContext * cx,unsigned argc,Value * vp)5741 static bool GetModuleEnvironmentNames(JSContext* cx, unsigned argc, Value* vp) {
5742 CallArgs args = CallArgsFromVp(argc, vp);
5743 if (args.length() != 1) {
5744 JS_ReportErrorASCII(cx, "Wrong number of arguments");
5745 return false;
5746 }
5747
5748 if (!args[0].isObject() ||
5749 !args[0].toObject().is<ShellModuleObjectWrapper>()) {
5750 JS_ReportErrorASCII(cx,
5751 "First argument should be a ShellModuleObjectWrapper");
5752 return false;
5753 }
5754
5755 RootedModuleObject module(
5756 cx, args[0].toObject().as<ShellModuleObjectWrapper>().get());
5757 if (module->hadEvaluationError()) {
5758 JS_ReportErrorASCII(cx, "Module environment unavailable");
5759 return false;
5760 }
5761
5762 RootedModuleEnvironmentObject env(cx, GetModuleEnvironment(cx, module));
5763 Rooted<IdVector> ids(cx, IdVector(cx));
5764 if (!JS_Enumerate(cx, env, &ids)) {
5765 return false;
5766 }
5767
5768 // The "*namespace*" binding is a detail of current implementation so hide
5769 // it to give stable results in tests.
5770 ids.eraseIfEqual(NameToId(cx->names().starNamespaceStar));
5771
5772 uint32_t length = ids.length();
5773 RootedArrayObject array(cx, NewDenseFullyAllocatedArray(cx, length));
5774 if (!array) {
5775 return false;
5776 }
5777
5778 array->setDenseInitializedLength(length);
5779 for (uint32_t i = 0; i < length; i++) {
5780 array->initDenseElement(i, StringValue(JSID_TO_STRING(ids[i])));
5781 }
5782
5783 args.rval().setObject(*array);
5784 return true;
5785 }
5786
GetModuleEnvironmentValue(JSContext * cx,unsigned argc,Value * vp)5787 static bool GetModuleEnvironmentValue(JSContext* cx, unsigned argc, Value* vp) {
5788 CallArgs args = CallArgsFromVp(argc, vp);
5789 if (args.length() != 2) {
5790 JS_ReportErrorASCII(cx, "Wrong number of arguments");
5791 return false;
5792 }
5793
5794 if (!args[0].isObject() ||
5795 !args[0].toObject().is<ShellModuleObjectWrapper>()) {
5796 JS_ReportErrorASCII(cx,
5797 "First argument should be a ShellModuleObjectWrapper");
5798 return false;
5799 }
5800
5801 if (!args[1].isString()) {
5802 JS_ReportErrorASCII(cx, "Second argument should be a string");
5803 return false;
5804 }
5805
5806 RootedModuleObject module(
5807 cx, args[0].toObject().as<ShellModuleObjectWrapper>().get());
5808 if (module->hadEvaluationError()) {
5809 JS_ReportErrorASCII(cx, "Module environment unavailable");
5810 return false;
5811 }
5812
5813 RootedModuleEnvironmentObject env(cx, GetModuleEnvironment(cx, module));
5814 RootedString name(cx, args[1].toString());
5815 RootedId id(cx);
5816 if (!JS_StringToId(cx, name, &id)) {
5817 return false;
5818 }
5819
5820 if (!GetProperty(cx, env, env, id, args.rval())) {
5821 return false;
5822 }
5823
5824 if (args.rval().isMagic(JS_UNINITIALIZED_LEXICAL)) {
5825 ReportRuntimeLexicalError(cx, JSMSG_UNINITIALIZED_LEXICAL, id);
5826 return false;
5827 }
5828
5829 return true;
5830 }
5831
5832 enum class DumpType {
5833 ParseNode,
5834 Stencil,
5835 };
5836
5837 template <typename Unit>
DumpAST(JSContext * cx,const JS::ReadOnlyCompileOptions & options,const Unit * units,size_t length,js::frontend::CompilationState & compilationState,js::frontend::ParseGoal goal)5838 static bool DumpAST(JSContext* cx, const JS::ReadOnlyCompileOptions& options,
5839 const Unit* units, size_t length,
5840 js::frontend::CompilationState& compilationState,
5841 js::frontend::ParseGoal goal) {
5842 using namespace js::frontend;
5843
5844 Parser<FullParseHandler, Unit> parser(cx, options, units, length,
5845 /* foldConstants = */ false,
5846 compilationState,
5847 /* syntaxParser = */ nullptr);
5848 if (!parser.checkOptions()) {
5849 return false;
5850 }
5851
5852 // Emplace the top-level stencil.
5853 MOZ_ASSERT(compilationState.scriptData.length() ==
5854 CompilationStencil::TopLevelIndex);
5855 if (!compilationState.appendScriptStencilAndData(cx)) {
5856 return false;
5857 }
5858
5859 js::frontend::ParseNode* pn;
5860 if (goal == frontend::ParseGoal::Script) {
5861 pn = parser.parse();
5862 } else {
5863 if (!GlobalObject::ensureModulePrototypesCreated(cx, cx->global())) {
5864 return false;
5865 }
5866
5867 ModuleBuilder builder(cx, &parser);
5868
5869 SourceExtent extent = SourceExtent::makeGlobalExtent(length);
5870 ModuleSharedContext modulesc(cx, options, builder, extent);
5871 pn = parser.moduleBody(&modulesc);
5872 }
5873
5874 if (!pn) {
5875 return false;
5876 }
5877
5878 #if defined(DEBUG)
5879 js::Fprinter out(stderr);
5880 DumpParseTree(&parser, pn, out);
5881 #endif
5882
5883 return true;
5884 }
5885
5886 template <typename Unit>
DumpStencil(JSContext * cx,const JS::ReadOnlyCompileOptions & options,const Unit * units,size_t length,js::frontend::ParseGoal goal)5887 [[nodiscard]] static bool DumpStencil(JSContext* cx,
5888 const JS::ReadOnlyCompileOptions& options,
5889 const Unit* units, size_t length,
5890 js::frontend::ParseGoal goal) {
5891 Rooted<frontend::CompilationInput> input(cx,
5892 frontend::CompilationInput(options));
5893
5894 JS::SourceText<Unit> srcBuf;
5895 if (!srcBuf.init(cx, units, length, JS::SourceOwnership::Borrowed)) {
5896 return false;
5897 }
5898
5899 UniquePtr<frontend::ExtensibleCompilationStencil> stencil;
5900 if (goal == frontend::ParseGoal::Script) {
5901 stencil = frontend::CompileGlobalScriptToExtensibleStencil(
5902 cx, input.get(), srcBuf, ScopeKind::Global);
5903 } else {
5904 stencil = frontend::ParseModuleToExtensibleStencil(cx, input.get(), srcBuf);
5905 }
5906
5907 if (!stencil) {
5908 return false;
5909 }
5910
5911 #if defined(DEBUG) || defined(JS_JITSPEW)
5912 {
5913 frontend::BorrowingCompilationStencil borrowingStencil(*stencil);
5914 borrowingStencil.dump();
5915 }
5916 #endif
5917
5918 return true;
5919 }
5920
FrontendTest(JSContext * cx,unsigned argc,Value * vp,const char * funcName,DumpType dumpType)5921 static bool FrontendTest(JSContext* cx, unsigned argc, Value* vp,
5922 const char* funcName, DumpType dumpType) {
5923 using namespace js::frontend;
5924
5925 CallArgs args = CallArgsFromVp(argc, vp);
5926
5927 if (!args.requireAtLeast(cx, funcName, 1)) {
5928 return false;
5929 }
5930 if (!args[0].isString()) {
5931 const char* typeName = InformalValueTypeName(args[0]);
5932 JS_ReportErrorASCII(cx, "expected string to parse, got %s", typeName);
5933 return false;
5934 }
5935
5936 frontend::ParseGoal goal = frontend::ParseGoal::Script;
5937 #ifdef JS_ENABLE_SMOOSH
5938 bool smoosh = false;
5939 #endif
5940
5941 CompileOptions options(cx);
5942 options.setIntroductionType("js shell parse")
5943 .setFileAndLine("<string>", 1)
5944 .setIsRunOnce(true)
5945 .setNoScriptRval(true);
5946
5947 if (args.length() >= 2) {
5948 if (!args[1].isObject()) {
5949 const char* typeName = InformalValueTypeName(args[1]);
5950 JS_ReportErrorASCII(cx, "expected object (options) to parse, got %s",
5951 typeName);
5952 return false;
5953 }
5954 RootedObject objOptions(cx, &args[1].toObject());
5955
5956 RootedValue optionModule(cx);
5957 if (!JS_GetProperty(cx, objOptions, "module", &optionModule)) {
5958 return false;
5959 }
5960
5961 if (optionModule.isBoolean()) {
5962 if (optionModule.toBoolean()) {
5963 goal = frontend::ParseGoal::Module;
5964 }
5965 } else if (!optionModule.isUndefined()) {
5966 const char* typeName = InformalValueTypeName(optionModule);
5967 JS_ReportErrorASCII(cx, "option `module` should be a boolean, got %s",
5968 typeName);
5969 return false;
5970 }
5971 if (!js::ParseCompileOptions(cx, options, objOptions, nullptr)) {
5972 return false;
5973 }
5974
5975 #ifdef JS_ENABLE_SMOOSH
5976 bool found = false;
5977 if (!JS_HasProperty(cx, objOptions, "rustFrontend", &found)) {
5978 return false;
5979 }
5980 if (found) {
5981 JS_ReportErrorASCII(cx, "'rustFrontend' option is renamed to 'smoosh'");
5982 return false;
5983 }
5984
5985 RootedValue optionSmoosh(cx);
5986 if (!JS_GetProperty(cx, objOptions, "smoosh", &optionSmoosh)) {
5987 return false;
5988 }
5989
5990 if (optionSmoosh.isBoolean()) {
5991 smoosh = optionSmoosh.toBoolean();
5992 } else if (!optionSmoosh.isUndefined()) {
5993 const char* typeName = InformalValueTypeName(optionSmoosh);
5994 JS_ReportErrorASCII(cx, "option `smoosh` should be a boolean, got %s",
5995 typeName);
5996 return false;
5997 }
5998 #endif // JS_ENABLE_SMOOSH
5999 }
6000
6001 JSString* scriptContents = args[0].toString();
6002 RootedLinearString linearString(cx, scriptContents->ensureLinear(cx));
6003 if (!linearString) {
6004 return false;
6005 }
6006
6007 bool isAscii = false;
6008 if (linearString->hasLatin1Chars()) {
6009 JS::AutoCheckCannotGC nogc;
6010 isAscii = JS::StringIsASCII(mozilla::Span(
6011 reinterpret_cast<const char*>(linearString->latin1Chars(nogc)),
6012 linearString->length()));
6013 }
6014
6015 AutoStableStringChars stableChars(cx);
6016 if (isAscii) {
6017 if (!stableChars.init(cx, scriptContents)) {
6018 return false;
6019 }
6020 MOZ_ASSERT(stableChars.isLatin1());
6021 } else {
6022 if (!stableChars.initTwoByte(cx, scriptContents)) {
6023 return false;
6024 }
6025 }
6026
6027 size_t length = scriptContents->length();
6028 #ifdef JS_ENABLE_SMOOSH
6029 if (dumpType == DumpType::ParseNode) {
6030 if (smoosh) {
6031 if (isAscii) {
6032 const Latin1Char* chars = stableChars.latin1Range().begin().get();
6033
6034 if (goal == frontend::ParseGoal::Script) {
6035 if (!SmooshParseScript(cx, chars, length)) {
6036 return false;
6037 }
6038 } else {
6039 if (!SmooshParseModule(cx, chars, length)) {
6040 return false;
6041 }
6042 }
6043 args.rval().setUndefined();
6044 return true;
6045 }
6046 JS_ReportErrorASCII(cx,
6047 "SmooshMonkey does not support non-ASCII chars yet");
6048 return false;
6049 }
6050 }
6051 #endif // JS_ENABLE_SMOOSH
6052
6053 if (goal == frontend::ParseGoal::Module) {
6054 // See frontend::CompileModule.
6055 options.setForceStrictMode();
6056 options.allowHTMLComments = false;
6057 }
6058
6059 if (dumpType == DumpType::Stencil) {
6060 #ifdef JS_ENABLE_SMOOSH
6061 if (smoosh) {
6062 if (isAscii) {
6063 if (goal == frontend::ParseGoal::Script) {
6064 const Latin1Char* latin1 = stableChars.latin1Range().begin().get();
6065 auto utf8 = reinterpret_cast<const mozilla::Utf8Unit*>(latin1);
6066 JS::SourceText<Utf8Unit> srcBuf;
6067 if (!srcBuf.init(cx, utf8, length, JS::SourceOwnership::Borrowed)) {
6068 return false;
6069 }
6070
6071 Rooted<frontend::CompilationInput> input(
6072 cx, frontend::CompilationInput(options));
6073 UniquePtr<frontend::ExtensibleCompilationStencil> stencil;
6074 if (!Smoosh::tryCompileGlobalScriptToExtensibleStencil(
6075 cx, input.get(), srcBuf, stencil)) {
6076 return false;
6077 }
6078 if (!stencil) {
6079 JS_ReportErrorASCII(cx, "SmooshMonkey failed to parse");
6080 return false;
6081 }
6082
6083 # ifdef DEBUG
6084 {
6085 frontend::BorrowingCompilationStencil borrowingStencil(*stencil);
6086 borrowingStencil.dump();
6087 }
6088 # endif
6089 } else {
6090 JS_ReportErrorASCII(cx,
6091 "SmooshMonkey does not support module stencil");
6092 return false;
6093 }
6094 args.rval().setUndefined();
6095 return true;
6096 }
6097 JS_ReportErrorASCII(cx,
6098 "SmooshMonkey does not support non-ASCII chars yet");
6099 return false;
6100 }
6101 #endif // JS_ENABLE_SMOOSH
6102
6103 if (isAscii) {
6104 const Latin1Char* latin1 = stableChars.latin1Range().begin().get();
6105 auto utf8 = reinterpret_cast<const mozilla::Utf8Unit*>(latin1);
6106 if (!DumpStencil<mozilla::Utf8Unit>(cx, options, utf8, length, goal)) {
6107 return false;
6108 }
6109 } else {
6110 MOZ_ASSERT(stableChars.isTwoByte());
6111 const char16_t* chars = stableChars.twoByteRange().begin().get();
6112 if (!DumpStencil<char16_t>(cx, options, chars, length, goal)) {
6113 return false;
6114 }
6115 }
6116
6117 args.rval().setUndefined();
6118 return true;
6119 }
6120
6121 Rooted<frontend::CompilationInput> input(cx,
6122 frontend::CompilationInput(options));
6123 if (goal == frontend::ParseGoal::Script) {
6124 if (!input.get().initForGlobal(cx)) {
6125 return false;
6126 }
6127 } else {
6128 if (!input.get().initForModule(cx)) {
6129 return false;
6130 }
6131 }
6132
6133 LifoAllocScope allocScope(&cx->tempLifoAlloc());
6134 frontend::CompilationState compilationState(cx, allocScope, input.get());
6135 if (!compilationState.init(cx)) {
6136 return false;
6137 }
6138
6139 if (isAscii) {
6140 const Latin1Char* latin1 = stableChars.latin1Range().begin().get();
6141 auto utf8 = reinterpret_cast<const mozilla::Utf8Unit*>(latin1);
6142 if (!DumpAST<mozilla::Utf8Unit>(cx, options, utf8, length, compilationState,
6143 goal)) {
6144 return false;
6145 }
6146 } else {
6147 MOZ_ASSERT(stableChars.isTwoByte());
6148 const char16_t* chars = stableChars.twoByteRange().begin().get();
6149 if (!DumpAST<char16_t>(cx, options, chars, length, compilationState,
6150 goal)) {
6151 return false;
6152 }
6153 }
6154 args.rval().setUndefined();
6155 return true;
6156 }
6157
DumpStencil(JSContext * cx,unsigned argc,Value * vp)6158 static bool DumpStencil(JSContext* cx, unsigned argc, Value* vp) {
6159 return FrontendTest(cx, argc, vp, "dumpStencil", DumpType::Stencil);
6160 }
6161
Parse(JSContext * cx,unsigned argc,Value * vp)6162 static bool Parse(JSContext* cx, unsigned argc, Value* vp) {
6163 // Parse returns local scope information with variables ordered
6164 // differently, depending on the underlying JIT implementation.
6165 if (js::SupportDifferentialTesting()) {
6166 JS_ReportErrorASCII(cx,
6167 "Function not available in differential testing mode.");
6168 return false;
6169 }
6170
6171 return FrontendTest(cx, argc, vp, "parse", DumpType::ParseNode);
6172 }
6173
SyntaxParse(JSContext * cx,unsigned argc,Value * vp)6174 static bool SyntaxParse(JSContext* cx, unsigned argc, Value* vp) {
6175 using namespace js::frontend;
6176
6177 CallArgs args = CallArgsFromVp(argc, vp);
6178
6179 if (!args.requireAtLeast(cx, "syntaxParse", 1)) {
6180 return false;
6181 }
6182 if (!args[0].isString()) {
6183 const char* typeName = InformalValueTypeName(args[0]);
6184 JS_ReportErrorASCII(cx, "expected string to parse, got %s", typeName);
6185 return false;
6186 }
6187
6188 JSString* scriptContents = args[0].toString();
6189
6190 CompileOptions options(cx);
6191 options.setIntroductionType("js shell syntaxParse")
6192 .setFileAndLine("<string>", 1);
6193
6194 AutoStableStringChars stableChars(cx);
6195 if (!stableChars.initTwoByte(cx, scriptContents)) {
6196 return false;
6197 }
6198
6199 const char16_t* chars = stableChars.twoByteRange().begin().get();
6200 size_t length = scriptContents->length();
6201
6202 Rooted<frontend::CompilationInput> input(cx,
6203 frontend::CompilationInput(options));
6204 if (!input.get().initForGlobal(cx)) {
6205 return false;
6206 }
6207
6208 LifoAllocScope allocScope(&cx->tempLifoAlloc());
6209 frontend::CompilationState compilationState(cx, allocScope, input.get());
6210 if (!compilationState.init(cx)) {
6211 return false;
6212 }
6213
6214 Parser<frontend::SyntaxParseHandler, char16_t> parser(
6215 cx, options, chars, length,
6216 /* foldConstants = */ false, compilationState,
6217 /* syntaxParser = */ nullptr);
6218 if (!parser.checkOptions()) {
6219 return false;
6220 }
6221
6222 bool succeeded = parser.parse();
6223 if (cx->isExceptionPending()) {
6224 return false;
6225 }
6226
6227 if (!succeeded && !parser.hadAbortedSyntaxParse()) {
6228 // If no exception is posted, either there was an OOM or a language
6229 // feature unhandled by the syntax parser was encountered.
6230 MOZ_ASSERT(cx->runtime()->hadOutOfMemory);
6231 return false;
6232 }
6233
6234 args.rval().setBoolean(succeeded);
6235 return true;
6236 }
6237
OffThreadCompileScriptCallback(JS::OffThreadToken * token,void * callbackData)6238 static void OffThreadCompileScriptCallback(JS::OffThreadToken* token,
6239 void* callbackData) {
6240 auto job = static_cast<OffThreadJob*>(callbackData);
6241 job->markDone(token);
6242 }
6243
OffThreadCompileScript(JSContext * cx,unsigned argc,Value * vp)6244 static bool OffThreadCompileScript(JSContext* cx, unsigned argc, Value* vp) {
6245 if (!CanUseExtraThreads()) {
6246 JS_ReportErrorASCII(cx,
6247 "Can't use offThreadCompileScript with --no-threads");
6248 return false;
6249 }
6250
6251 CallArgs args = CallArgsFromVp(argc, vp);
6252
6253 if (!args.requireAtLeast(cx, "offThreadCompileScript", 1)) {
6254 return false;
6255 }
6256 if (!args[0].isString()) {
6257 const char* typeName = InformalValueTypeName(args[0]);
6258 JS_ReportErrorASCII(cx, "expected string to parse, got %s", typeName);
6259 return false;
6260 }
6261
6262 UniqueChars fileNameBytes;
6263 CompileOptions options(cx);
6264 options.setIntroductionType("js shell offThreadCompileScript")
6265 .setFileAndLine("<string>", 1)
6266 .setdeferDebugMetadata();
6267
6268 if (args.length() >= 2) {
6269 if (args[1].isPrimitive()) {
6270 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
6271 JSSMSG_INVALID_ARGS, "evaluate");
6272 return false;
6273 }
6274
6275 // Offthread compilation requires that the debug metadata be set when the
6276 // script is collected from offthread, rather than when compiled.
6277 RootedObject opts(cx, &args[1].toObject());
6278 if (!js::ParseCompileOptions(cx, options, opts, &fileNameBytes)) {
6279 return false;
6280 }
6281 }
6282
6283 // These option settings must override whatever the caller requested.
6284 options.setIsRunOnce(true).setSourceIsLazy(false);
6285
6286 // We assume the caller wants caching if at all possible, ignoring
6287 // heuristics that make sense for a real browser.
6288 options.forceAsync = true;
6289
6290 JSString* scriptContents = args[0].toString();
6291 AutoStableStringChars stableChars(cx);
6292 if (!stableChars.initTwoByte(cx, scriptContents)) {
6293 return false;
6294 }
6295
6296 size_t length = scriptContents->length();
6297 const char16_t* chars = stableChars.twoByteChars();
6298
6299 // Make sure we own the string's chars, so that they are not freed before
6300 // the compilation is finished.
6301 UniqueTwoByteChars ownedChars;
6302 if (stableChars.maybeGiveOwnershipToCaller()) {
6303 ownedChars.reset(const_cast<char16_t*>(chars));
6304 } else {
6305 ownedChars.reset(cx->pod_malloc<char16_t>(length));
6306 if (!ownedChars) {
6307 return false;
6308 }
6309
6310 mozilla::PodCopy(ownedChars.get(), chars, length);
6311 }
6312
6313 if (!JS::CanCompileOffThread(cx, options, length)) {
6314 JS_ReportErrorASCII(cx, "cannot compile code on worker thread");
6315 return false;
6316 }
6317
6318 OffThreadJob* job =
6319 NewOffThreadJob(cx, ScriptKind::Script, options,
6320 OffThreadJob::Source(std::move(ownedChars)));
6321 if (!job) {
6322 return false;
6323 }
6324
6325 JS::SourceText<char16_t> srcBuf;
6326 if (!srcBuf.init(cx, job->sourceChars(), length,
6327 JS::SourceOwnership::Borrowed) ||
6328 !JS::CompileOffThread(cx, options, srcBuf, OffThreadCompileScriptCallback,
6329 job)) {
6330 job->cancel();
6331 DeleteOffThreadJob(cx, job);
6332 return false;
6333 }
6334
6335 args.rval().setInt32(job->id);
6336 return true;
6337 }
6338
runOffThreadScript(JSContext * cx,unsigned argc,Value * vp)6339 static bool runOffThreadScript(JSContext* cx, unsigned argc, Value* vp) {
6340 CallArgs args = CallArgsFromVp(argc, vp);
6341
6342 OffThreadJob* job =
6343 LookupOffThreadJobForArgs(cx, ScriptKind::Script, args, 0);
6344 if (!job) {
6345 return false;
6346 }
6347
6348 if (job->useOffThreadParseGlobal &&
6349 OffThreadParsingMustWaitForGC(cx->runtime())) {
6350 gc::FinishGC(cx);
6351 }
6352
6353 JS::OffThreadToken* token = job->waitUntilDone(cx);
6354 MOZ_ASSERT(token);
6355
6356 RootedScript script(cx, JS::FinishOffThreadScript(cx, token));
6357 DeleteOffThreadJob(cx, job);
6358 if (!script) {
6359 return false;
6360 }
6361
6362 RootedValue privateValue(cx);
6363 RootedString elementAttributeName(cx);
6364
6365 if (args.length() >= 2) {
6366 if (args[1].isPrimitive()) {
6367 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
6368 JSSMSG_INVALID_ARGS, "compile");
6369 return false;
6370 }
6371
6372 RootedObject opts(cx, &args[1].toObject());
6373 if (!ParseDebugMetadata(cx, opts, &privateValue, &elementAttributeName)) {
6374 return false;
6375 }
6376 }
6377
6378 CompileOptions dummyOptions(cx);
6379 if (!JS::UpdateDebugMetadata(cx, script, dummyOptions, privateValue,
6380 elementAttributeName, nullptr, nullptr)) {
6381 return false;
6382 }
6383
6384 return JS_ExecuteScript(cx, script, args.rval());
6385 }
6386
OffThreadCompileToStencil(JSContext * cx,unsigned argc,Value * vp)6387 static bool OffThreadCompileToStencil(JSContext* cx, unsigned argc, Value* vp) {
6388 if (!CanUseExtraThreads()) {
6389 JS_ReportErrorASCII(
6390 cx, "Can't use offThreadCompileToStencil with --no-threads");
6391 return false;
6392 }
6393
6394 CallArgs args = CallArgsFromVp(argc, vp);
6395
6396 if (!args.requireAtLeast(cx, "offThreadCompileToStencil", 1)) {
6397 return false;
6398 }
6399 if (!args[0].isString()) {
6400 const char* typeName = InformalValueTypeName(args[0]);
6401 JS_ReportErrorASCII(cx, "expected string to parse, got %s", typeName);
6402 return false;
6403 }
6404
6405 UniqueChars fileNameBytes;
6406 CompileOptions options(cx);
6407 options.setIntroductionType("js shell offThreadCompileToStencil")
6408 .setFileAndLine("<string>", 1)
6409 .setdeferDebugMetadata();
6410
6411 if (args.length() >= 2) {
6412 if (args[1].isPrimitive()) {
6413 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
6414 JSSMSG_INVALID_ARGS, "evaluate");
6415 return false;
6416 }
6417
6418 // Offthread compilation requires that the debug metadata be set when the
6419 // script is collected from offthread, rather than when compiled.
6420 RootedObject opts(cx, &args[1].toObject());
6421 if (!js::ParseCompileOptions(cx, options, opts, &fileNameBytes)) {
6422 return false;
6423 }
6424 }
6425
6426 // This option setting must override whatever the caller requested.
6427 options.setIsRunOnce(true);
6428
6429 // We assume the caller wants caching if at all possible, ignoring
6430 // heuristics that make sense for a real browser.
6431 options.forceAsync = true;
6432
6433 JSString* scriptContents = args[0].toString();
6434 AutoStableStringChars stableChars(cx);
6435 if (!stableChars.initTwoByte(cx, scriptContents)) {
6436 return false;
6437 }
6438
6439 size_t length = scriptContents->length();
6440 const char16_t* chars = stableChars.twoByteChars();
6441
6442 // Make sure we own the string's chars, so that they are not freed before
6443 // the compilation is finished.
6444 UniqueTwoByteChars ownedChars;
6445 if (stableChars.maybeGiveOwnershipToCaller()) {
6446 ownedChars.reset(const_cast<char16_t*>(chars));
6447 } else {
6448 ownedChars.reset(cx->pod_malloc<char16_t>(length));
6449 if (!ownedChars) {
6450 return false;
6451 }
6452
6453 mozilla::PodCopy(ownedChars.get(), chars, length);
6454 }
6455
6456 if (!JS::CanCompileOffThread(cx, options, length)) {
6457 JS_ReportErrorASCII(cx, "cannot compile code on worker thread");
6458 return false;
6459 }
6460
6461 OffThreadJob* job =
6462 NewOffThreadJob(cx, ScriptKind::ScriptStencil, options,
6463 OffThreadJob::Source(std::move(ownedChars)));
6464 if (!job) {
6465 return false;
6466 }
6467
6468 JS::SourceText<char16_t> srcBuf;
6469 if (!srcBuf.init(cx, job->sourceChars(), length,
6470 JS::SourceOwnership::Borrowed) ||
6471 !JS::CompileToStencilOffThread(cx, options, srcBuf,
6472 OffThreadCompileScriptCallback, job)) {
6473 job->cancel();
6474 DeleteOffThreadJob(cx, job);
6475 return false;
6476 }
6477
6478 args.rval().setInt32(job->id);
6479 return true;
6480 }
6481
FinishOffThreadCompileToStencil(JSContext * cx,unsigned argc,Value * vp)6482 static bool FinishOffThreadCompileToStencil(JSContext* cx, unsigned argc,
6483 Value* vp) {
6484 CallArgs args = CallArgsFromVp(argc, vp);
6485
6486 OffThreadJob* job =
6487 LookupOffThreadJobForArgs(cx, ScriptKind::ScriptStencil, args, 0);
6488 if (!job) {
6489 return false;
6490 }
6491
6492 MOZ_ASSERT(!job->useOffThreadParseGlobal);
6493
6494 JS::OffThreadToken* token = job->waitUntilDone(cx);
6495 MOZ_ASSERT(token);
6496
6497 RefPtr<JS::Stencil> stencil = JS::FinishOffThreadCompileToStencil(cx, token);
6498 DeleteOffThreadJob(cx, job);
6499 if (!stencil) {
6500 return false;
6501 }
6502 RootedObject stencilObj(cx,
6503 js::StencilObject::create(cx, std::move(stencil)));
6504 if (!stencilObj) {
6505 return false;
6506 }
6507
6508 args.rval().setObject(*stencilObj);
6509 return true;
6510 }
6511
OffThreadCompileModule(JSContext * cx,unsigned argc,Value * vp)6512 static bool OffThreadCompileModule(JSContext* cx, unsigned argc, Value* vp) {
6513 CallArgs args = CallArgsFromVp(argc, vp);
6514
6515 if (args.length() != 1 || !args[0].isString()) {
6516 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
6517 JSSMSG_INVALID_ARGS, "offThreadCompileModule");
6518 return false;
6519 }
6520
6521 UniqueChars fileNameBytes;
6522 CompileOptions options(cx);
6523 options.setIntroductionType("js shell offThreadCompileModule")
6524 .setFileAndLine("<string>", 1);
6525 options.setIsRunOnce(true).setSourceIsLazy(false);
6526 options.forceAsync = true;
6527
6528 JSString* scriptContents = args[0].toString();
6529 AutoStableStringChars stableChars(cx);
6530 if (!stableChars.initTwoByte(cx, scriptContents)) {
6531 return false;
6532 }
6533
6534 size_t length = scriptContents->length();
6535 const char16_t* chars = stableChars.twoByteChars();
6536
6537 // Make sure we own the string's chars, so that they are not freed before
6538 // the compilation is finished.
6539 UniqueTwoByteChars ownedChars;
6540 if (stableChars.maybeGiveOwnershipToCaller()) {
6541 ownedChars.reset(const_cast<char16_t*>(chars));
6542 } else {
6543 ownedChars.reset(cx->pod_malloc<char16_t>(length));
6544 if (!ownedChars) {
6545 return false;
6546 }
6547
6548 mozilla::PodCopy(ownedChars.get(), chars, length);
6549 }
6550
6551 if (!JS::CanCompileOffThread(cx, options, length)) {
6552 JS_ReportErrorASCII(cx, "cannot compile code on worker thread");
6553 return false;
6554 }
6555
6556 OffThreadJob* job =
6557 NewOffThreadJob(cx, ScriptKind::Module, options,
6558 OffThreadJob::Source(std::move(ownedChars)));
6559 if (!job) {
6560 return false;
6561 }
6562
6563 JS::SourceText<char16_t> srcBuf;
6564 if (!srcBuf.init(cx, job->sourceChars(), length,
6565 JS::SourceOwnership::Borrowed) ||
6566 !JS::CompileOffThreadModule(cx, options, srcBuf,
6567 OffThreadCompileScriptCallback, job)) {
6568 job->cancel();
6569 DeleteOffThreadJob(cx, job);
6570 return false;
6571 }
6572
6573 args.rval().setInt32(job->id);
6574 return true;
6575 }
6576
FinishOffThreadModule(JSContext * cx,unsigned argc,Value * vp)6577 static bool FinishOffThreadModule(JSContext* cx, unsigned argc, Value* vp) {
6578 CallArgs args = CallArgsFromVp(argc, vp);
6579
6580 OffThreadJob* job =
6581 LookupOffThreadJobForArgs(cx, ScriptKind::Module, args, 0);
6582 if (!job) {
6583 return false;
6584 }
6585
6586 if (job->useOffThreadParseGlobal &&
6587 OffThreadParsingMustWaitForGC(cx->runtime())) {
6588 gc::FinishGC(cx);
6589 }
6590
6591 JS::OffThreadToken* token = job->waitUntilDone(cx);
6592 MOZ_ASSERT(token);
6593
6594 RootedObject module(cx, JS::FinishOffThreadModule(cx, token));
6595 DeleteOffThreadJob(cx, job);
6596 if (!module) {
6597 return false;
6598 }
6599
6600 Rooted<ShellModuleObjectWrapper*> wrapper(
6601 cx, ShellModuleObjectWrapper::create(cx, module.as<ModuleObject>()));
6602 if (!wrapper) {
6603 return false;
6604 }
6605 args.rval().setObject(*wrapper);
6606 return true;
6607 }
6608
OffThreadDecodeScript(JSContext * cx,unsigned argc,Value * vp)6609 static bool OffThreadDecodeScript(JSContext* cx, unsigned argc, Value* vp) {
6610 if (!CanUseExtraThreads()) {
6611 JS_ReportErrorASCII(cx,
6612 "Can't use offThreadDecodeScript with --no-threads");
6613 return false;
6614 }
6615
6616 CallArgs args = CallArgsFromVp(argc, vp);
6617
6618 if (!args.requireAtLeast(cx, "offThreadDecodeScript", 1)) {
6619 return false;
6620 }
6621 if (!args[0].isObject() || !CacheEntry_isCacheEntry(&args[0].toObject())) {
6622 const char* typeName = InformalValueTypeName(args[0]);
6623 JS_ReportErrorASCII(cx, "expected cache entry, got %s", typeName);
6624 return false;
6625 }
6626 RootedObject cacheEntry(cx, &args[0].toObject());
6627
6628 UniqueChars fileNameBytes;
6629 CompileOptions options(cx);
6630 options.setIntroductionType("js shell offThreadDecodeScript")
6631 .setFileAndLine("<string>", 1);
6632 // NOTE: If --off-thread-parse-global is not used, input can be either script
6633 // for saveBytecode, or stencil for saveIncrementalBytecode.
6634 options.useOffThreadParseGlobal =
6635 CacheEntry_getKind(cx, cacheEntry) == BytecodeCacheKind::Script;
6636 options.useStencilXDR =
6637 CacheEntry_getKind(cx, cacheEntry) == BytecodeCacheKind::Stencil;
6638
6639 // In browser, we always use off-thread parse global for decode task, for
6640 // performance reason.
6641 // (see ScriptLoader::AttemptAsyncScriptCompile)
6642 //
6643 // This code should be removed, and the above code should be used, once
6644 // bug 1687973 gets fixed.
6645 options.useOffThreadParseGlobal = true;
6646
6647 if (args.length() >= 2) {
6648 if (args[1].isPrimitive()) {
6649 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
6650 JSSMSG_INVALID_ARGS, "evaluate");
6651 return false;
6652 }
6653
6654 RootedObject opts(cx, &args[1].toObject());
6655 if (!js::ParseCompileOptions(cx, options, opts, &fileNameBytes)) {
6656 return false;
6657 }
6658 }
6659
6660 // This option setting must override whatever the caller requested, and
6661 // this should match `Evaluate` that encodes the script.
6662 options.setIsRunOnce(false);
6663
6664 // We assume the caller wants caching if at all possible, ignoring
6665 // heuristics that make sense for a real browser.
6666 options.forceAsync = true;
6667
6668 JS::TranscodeBuffer loadBuffer;
6669 size_t loadLength = 0;
6670 uint8_t* loadData = nullptr;
6671 loadData = CacheEntry_getBytecode(cx, cacheEntry, &loadLength);
6672 if (!loadData) {
6673 return false;
6674 }
6675 if (!loadBuffer.append(loadData, loadLength)) {
6676 JS_ReportOutOfMemory(cx);
6677 return false;
6678 }
6679
6680 if (!JS::CanDecodeOffThread(cx, options, loadLength)) {
6681 JS_ReportErrorASCII(cx, "cannot compile code on worker thread");
6682 return false;
6683 }
6684
6685 OffThreadJob* job =
6686 NewOffThreadJob(cx, ScriptKind::DecodeScript, options,
6687 OffThreadJob::Source(std::move(loadBuffer)));
6688 if (!job) {
6689 return false;
6690 }
6691
6692 if (!JS::DecodeOffThreadScript(cx, options, job->xdrBuffer(), 0,
6693 OffThreadCompileScriptCallback, job)) {
6694 job->cancel();
6695 DeleteOffThreadJob(cx, job);
6696 return false;
6697 }
6698
6699 args.rval().setInt32(job->id);
6700 return true;
6701 }
6702
runOffThreadDecodedScript(JSContext * cx,unsigned argc,Value * vp)6703 static bool runOffThreadDecodedScript(JSContext* cx, unsigned argc, Value* vp) {
6704 CallArgs args = CallArgsFromVp(argc, vp);
6705
6706 OffThreadJob* job =
6707 LookupOffThreadJobForArgs(cx, ScriptKind::DecodeScript, args, 0);
6708 if (!job) {
6709 return false;
6710 }
6711
6712 if (job->useOffThreadParseGlobal &&
6713 OffThreadParsingMustWaitForGC(cx->runtime())) {
6714 gc::FinishGC(cx);
6715 }
6716
6717 JS::OffThreadToken* token = job->waitUntilDone(cx);
6718 MOZ_ASSERT(token);
6719
6720 RootedScript script(cx, JS::FinishOffThreadScriptDecoder(cx, token));
6721 DeleteOffThreadJob(cx, job);
6722 if (!script) {
6723 return false;
6724 }
6725
6726 return JS_ExecuteScript(cx, script, args.rval());
6727 }
6728
6729 class AutoCStringVector {
6730 Vector<char*> argv_;
6731
6732 public:
AutoCStringVector(JSContext * cx)6733 explicit AutoCStringVector(JSContext* cx) : argv_(cx) {}
~AutoCStringVector()6734 ~AutoCStringVector() {
6735 for (size_t i = 0; i < argv_.length(); i++) {
6736 js_free(argv_[i]);
6737 }
6738 }
append(UniqueChars && arg)6739 bool append(UniqueChars&& arg) {
6740 if (!argv_.append(arg.get())) {
6741 return false;
6742 }
6743
6744 // Now owned by this vector.
6745 (void)arg.release();
6746 return true;
6747 }
get() const6748 char* const* get() const { return argv_.begin(); }
length() const6749 size_t length() const { return argv_.length(); }
operator [](size_t i) const6750 char* operator[](size_t i) const { return argv_[i]; }
replace(size_t i,UniqueChars arg)6751 void replace(size_t i, UniqueChars arg) {
6752 js_free(argv_[i]);
6753 argv_[i] = arg.release();
6754 }
6755 };
6756
6757 #if defined(XP_WIN)
EscapeForShell(JSContext * cx,AutoCStringVector & argv)6758 static bool EscapeForShell(JSContext* cx, AutoCStringVector& argv) {
6759 // Windows will break arguments in argv by various spaces, so we wrap each
6760 // argument in quotes and escape quotes within. Even with quotes, \ will be
6761 // treated like an escape character, so inflate each \ to \\.
6762
6763 for (size_t i = 0; i < argv.length(); i++) {
6764 if (!argv[i]) {
6765 continue;
6766 }
6767
6768 size_t newLen = 3; // quotes before and after and null-terminator
6769 for (char* p = argv[i]; *p; p++) {
6770 newLen++;
6771 if (*p == '\"' || *p == '\\') {
6772 newLen++;
6773 }
6774 }
6775
6776 auto escaped = cx->make_pod_array<char>(newLen);
6777 if (!escaped) {
6778 return false;
6779 }
6780
6781 char* src = argv[i];
6782 char* dst = escaped.get();
6783 *dst++ = '\"';
6784 while (*src) {
6785 if (*src == '\"' || *src == '\\') {
6786 *dst++ = '\\';
6787 }
6788 *dst++ = *src++;
6789 }
6790 *dst++ = '\"';
6791 *dst++ = '\0';
6792 MOZ_ASSERT(escaped.get() + newLen == dst);
6793
6794 argv.replace(i, std::move(escaped));
6795 }
6796 return true;
6797 }
6798 #endif
6799
6800 #ifndef __wasi__
ReadAll(int fd,wasm::Bytes * bytes)6801 static bool ReadAll(int fd, wasm::Bytes* bytes) {
6802 size_t lastLength = bytes->length();
6803 while (true) {
6804 static const int ChunkSize = 64 * 1024;
6805 if (!bytes->growBy(ChunkSize)) {
6806 return false;
6807 }
6808
6809 intptr_t readCount;
6810 while (true) {
6811 readCount = read(fd, bytes->begin() + lastLength, ChunkSize);
6812 if (readCount >= 0) {
6813 break;
6814 }
6815 if (errno != EINTR) {
6816 return false;
6817 }
6818 }
6819
6820 if (readCount < ChunkSize) {
6821 bytes->shrinkTo(lastLength + readCount);
6822 if (readCount == 0) {
6823 return true;
6824 }
6825 }
6826
6827 lastLength = bytes->length();
6828 }
6829 }
6830
WriteAll(int fd,const uint8_t * bytes,size_t length)6831 static bool WriteAll(int fd, const uint8_t* bytes, size_t length) {
6832 while (length > 0) {
6833 int written = write(fd, bytes, length);
6834 if (written < 0) {
6835 if (errno == EINTR) {
6836 continue;
6837 }
6838 return false;
6839 }
6840 MOZ_ASSERT(unsigned(written) <= length);
6841 length -= written;
6842 bytes += written;
6843 }
6844
6845 return true;
6846 }
6847
6848 class AutoPipe {
6849 int fds_[2];
6850
6851 public:
AutoPipe()6852 AutoPipe() {
6853 fds_[0] = -1;
6854 fds_[1] = -1;
6855 }
6856
~AutoPipe()6857 ~AutoPipe() {
6858 if (fds_[0] != -1) {
6859 close(fds_[0]);
6860 }
6861 if (fds_[1] != -1) {
6862 close(fds_[1]);
6863 }
6864 }
6865
init()6866 bool init() {
6867 # ifdef XP_WIN
6868 return !_pipe(fds_, 4096, O_BINARY);
6869 # else
6870 return !pipe(fds_);
6871 # endif
6872 }
6873
reader() const6874 int reader() const {
6875 MOZ_ASSERT(fds_[0] != -1);
6876 return fds_[0];
6877 }
6878
writer() const6879 int writer() const {
6880 MOZ_ASSERT(fds_[1] != -1);
6881 return fds_[1];
6882 }
6883
closeReader()6884 void closeReader() {
6885 MOZ_ASSERT(fds_[0] != -1);
6886 close(fds_[0]);
6887 fds_[0] = -1;
6888 }
6889
closeWriter()6890 void closeWriter() {
6891 MOZ_ASSERT(fds_[1] != -1);
6892 close(fds_[1]);
6893 fds_[1] = -1;
6894 }
6895 };
6896 #endif // __wasi__
6897
6898 int shell::sArgc;
6899 char** shell::sArgv;
6900
6901 #ifndef __wasi__
6902 static const char sWasmCompileAndSerializeFlag[] =
6903 "--wasm-compile-and-serialize";
6904 static Vector<const char*, 5, js::SystemAllocPolicy> sCompilerProcessFlags;
6905
CompileAndSerializeInSeparateProcess(JSContext * cx,const uint8_t * bytecode,size_t bytecodeLength,wasm::Bytes * serialized)6906 static bool CompileAndSerializeInSeparateProcess(JSContext* cx,
6907 const uint8_t* bytecode,
6908 size_t bytecodeLength,
6909 wasm::Bytes* serialized) {
6910 AutoPipe stdIn, stdOut;
6911 if (!stdIn.init() || !stdOut.init()) {
6912 return false;
6913 }
6914
6915 AutoCStringVector argv(cx);
6916
6917 UniqueChars argv0 = DuplicateString(cx, sArgv[0]);
6918 if (!argv0 || !argv.append(std::move(argv0))) {
6919 return false;
6920 }
6921
6922 // Put compiler flags first since they must precede the non-option
6923 // file-descriptor args (passed on Windows, below).
6924 for (unsigned i = 0; i < sCompilerProcessFlags.length(); i++) {
6925 UniqueChars flags = DuplicateString(cx, sCompilerProcessFlags[i]);
6926 if (!flags || !argv.append(std::move(flags))) {
6927 return false;
6928 }
6929 }
6930
6931 UniqueChars arg;
6932
6933 arg = DuplicateString(sWasmCompileAndSerializeFlag);
6934 if (!arg || !argv.append(std::move(arg))) {
6935 return false;
6936 }
6937
6938 # ifdef XP_WIN
6939 // The spawned process will have all the stdIn/stdOut file handles open, but
6940 // without the power of fork, we need some other way to communicate the
6941 // integer fd values so we encode them in argv and WasmCompileAndSerialize()
6942 // has a matching #ifdef XP_WIN to parse them out. Communicate both ends of
6943 // both pipes so the child process can closed the unused ends.
6944
6945 arg = JS_smprintf("%d", stdIn.reader());
6946 if (!arg || !argv.append(std::move(arg))) {
6947 return false;
6948 }
6949
6950 arg = JS_smprintf("%d", stdIn.writer());
6951 if (!arg || !argv.append(std::move(arg))) {
6952 return false;
6953 }
6954
6955 arg = JS_smprintf("%d", stdOut.reader());
6956 if (!arg || !argv.append(std::move(arg))) {
6957 return false;
6958 }
6959
6960 arg = JS_smprintf("%d", stdOut.writer());
6961 if (!arg || !argv.append(std::move(arg))) {
6962 return false;
6963 }
6964 # endif
6965
6966 // Required by both _spawnv and exec.
6967 if (!argv.append(nullptr)) {
6968 return false;
6969 }
6970
6971 # ifdef XP_WIN
6972 if (!EscapeForShell(cx, argv)) {
6973 return false;
6974 }
6975
6976 int childPid = _spawnv(P_NOWAIT, sArgv[0], argv.get());
6977 if (childPid == -1) {
6978 return false;
6979 }
6980 # else
6981 pid_t childPid = fork();
6982 switch (childPid) {
6983 case -1:
6984 return false;
6985 case 0:
6986 // In the child process. Redirect stdin/stdout to the respective ends of
6987 // the pipes. Closing stdIn.writer() is necessary for stdin to hit EOF.
6988 // This case statement must not return before exec() takes over. Rather,
6989 // exit(-1) is used to return failure to the parent process.
6990 if (dup2(stdIn.reader(), STDIN_FILENO) == -1) {
6991 exit(-1);
6992 }
6993 if (dup2(stdOut.writer(), STDOUT_FILENO) == -1) {
6994 exit(-1);
6995 }
6996 close(stdIn.reader());
6997 close(stdIn.writer());
6998 close(stdOut.reader());
6999 close(stdOut.writer());
7000 execv(sArgv[0], argv.get());
7001 exit(-1);
7002 }
7003 # endif
7004
7005 // In the parent process. Closing stdOut.writer() is necessary for
7006 // stdOut.reader() below to hit EOF.
7007 stdIn.closeReader();
7008 stdOut.closeWriter();
7009
7010 if (!WriteAll(stdIn.writer(), bytecode, bytecodeLength)) {
7011 return false;
7012 }
7013
7014 stdIn.closeWriter();
7015
7016 if (!ReadAll(stdOut.reader(), serialized)) {
7017 return false;
7018 }
7019
7020 stdOut.closeReader();
7021
7022 int status;
7023 # ifdef XP_WIN
7024 if (_cwait(&status, childPid, WAIT_CHILD) == -1) {
7025 return false;
7026 }
7027 # else
7028 while (true) {
7029 if (waitpid(childPid, &status, 0) >= 0) {
7030 break;
7031 }
7032 if (errno != EINTR) {
7033 return false;
7034 }
7035 }
7036 # endif
7037
7038 return status == 0;
7039 }
7040
WasmCompileAndSerialize(JSContext * cx)7041 static bool WasmCompileAndSerialize(JSContext* cx) {
7042 MOZ_ASSERT(wasm::CodeCachingAvailable(cx));
7043
7044 # ifdef XP_WIN
7045 // See CompileAndSerializeInSeparateProcess for why we've had to smuggle
7046 // these fd values through argv. Closing the writing ends is necessary for
7047 // the reading ends to hit EOF.
7048 int flagIndex = 0;
7049 for (; flagIndex < sArgc; flagIndex++) {
7050 if (!strcmp(sArgv[flagIndex], sWasmCompileAndSerializeFlag)) {
7051 break;
7052 }
7053 }
7054 MOZ_RELEASE_ASSERT(flagIndex < sArgc);
7055
7056 int fdsIndex = flagIndex + 1;
7057 MOZ_RELEASE_ASSERT(fdsIndex + 4 == sArgc);
7058
7059 int stdInReader = atoi(sArgv[fdsIndex + 0]);
7060 int stdInWriter = atoi(sArgv[fdsIndex + 1]);
7061 int stdOutReader = atoi(sArgv[fdsIndex + 2]);
7062 int stdOutWriter = atoi(sArgv[fdsIndex + 3]);
7063
7064 int stdIn = stdInReader;
7065 close(stdInWriter);
7066 close(stdOutReader);
7067 int stdOut = stdOutWriter;
7068 # else
7069 int stdIn = STDIN_FILENO;
7070 int stdOut = STDOUT_FILENO;
7071 # endif
7072
7073 wasm::MutableBytes bytecode = js_new<wasm::ShareableBytes>();
7074 if (!ReadAll(stdIn, &bytecode->bytes)) {
7075 return false;
7076 }
7077
7078 wasm::Bytes serialized;
7079 if (!wasm::CompileAndSerialize(*bytecode, &serialized)) {
7080 return false;
7081 }
7082
7083 if (!WriteAll(stdOut, serialized.begin(), serialized.length())) {
7084 return false;
7085 }
7086
7087 return true;
7088 }
7089
WasmCompileInSeparateProcess(JSContext * cx,unsigned argc,Value * vp)7090 static bool WasmCompileInSeparateProcess(JSContext* cx, unsigned argc,
7091 Value* vp) {
7092 if (!wasm::CodeCachingAvailable(cx)) {
7093 JS_ReportErrorASCII(cx, "WebAssembly caching not supported");
7094 return false;
7095 }
7096
7097 CallArgs args = CallArgsFromVp(argc, vp);
7098 if (!args.requireAtLeast(cx, "wasmCompileInSeparateProcess", 1)) {
7099 return false;
7100 }
7101
7102 SharedMem<uint8_t*> bytecode;
7103 size_t numBytes;
7104 if (!args[0].isObject() ||
7105 !IsBufferSource(&args[0].toObject(), &bytecode, &numBytes)) {
7106 RootedObject callee(cx, &args.callee());
7107 ReportUsageErrorASCII(cx, callee, "Argument must be a buffer source");
7108 return false;
7109 }
7110
7111 wasm::Bytes serialized;
7112 if (!CompileAndSerializeInSeparateProcess(cx, bytecode.unwrap(), numBytes,
7113 &serialized)) {
7114 if (!cx->isExceptionPending()) {
7115 JS_ReportErrorASCII(cx, "creating and executing child process");
7116 }
7117 return false;
7118 }
7119
7120 RootedObject module(cx);
7121 if (!wasm::DeserializeModule(cx, serialized, &module)) {
7122 return false;
7123 }
7124
7125 args.rval().setObject(*module);
7126 return true;
7127 }
7128 #endif // __wasi__
7129
DecompileFunction(JSContext * cx,unsigned argc,Value * vp)7130 static bool DecompileFunction(JSContext* cx, unsigned argc, Value* vp) {
7131 CallArgs args = CallArgsFromVp(argc, vp);
7132 if (args.length() < 1 || !args[0].isObject() ||
7133 !args[0].toObject().is<JSFunction>()) {
7134 args.rval().setUndefined();
7135 return true;
7136 }
7137 RootedFunction fun(cx, &args[0].toObject().as<JSFunction>());
7138 JSString* result = JS_DecompileFunction(cx, fun);
7139 if (!result) {
7140 return false;
7141 }
7142 args.rval().setString(result);
7143 return true;
7144 }
7145
DecompileThisScript(JSContext * cx,unsigned argc,Value * vp)7146 static bool DecompileThisScript(JSContext* cx, unsigned argc, Value* vp) {
7147 CallArgs args = CallArgsFromVp(argc, vp);
7148
7149 NonBuiltinScriptFrameIter iter(cx);
7150 if (iter.done()) {
7151 args.rval().setString(cx->runtime()->emptyString);
7152 return true;
7153 }
7154
7155 {
7156 JSAutoRealm ar(cx, iter.script());
7157
7158 RootedScript script(cx, iter.script());
7159 JSString* result = JS_DecompileScript(cx, script);
7160 if (!result) {
7161 return false;
7162 }
7163
7164 args.rval().setString(result);
7165 }
7166
7167 return JS_WrapValue(cx, args.rval());
7168 }
7169
ValueToSource(JSContext * cx,unsigned argc,Value * vp)7170 static bool ValueToSource(JSContext* cx, unsigned argc, Value* vp) {
7171 CallArgs args = CallArgsFromVp(argc, vp);
7172
7173 JSString* str = ValueToSource(cx, args.get(0));
7174 if (!str) {
7175 return false;
7176 }
7177
7178 args.rval().setString(str);
7179 return true;
7180 }
7181
ThisFilename(JSContext * cx,unsigned argc,Value * vp)7182 static bool ThisFilename(JSContext* cx, unsigned argc, Value* vp) {
7183 CallArgs args = CallArgsFromVp(argc, vp);
7184
7185 JS::AutoFilename filename;
7186 if (!DescribeScriptedCaller(cx, &filename) || !filename.get()) {
7187 args.rval().setString(cx->runtime()->emptyString);
7188 return true;
7189 }
7190
7191 JSString* str = JS_NewStringCopyZ(cx, filename.get());
7192 if (!str) {
7193 return false;
7194 }
7195
7196 args.rval().setString(str);
7197 return true;
7198 }
7199
WrapWithProto(JSContext * cx,unsigned argc,Value * vp)7200 static bool WrapWithProto(JSContext* cx, unsigned argc, Value* vp) {
7201 CallArgs args = CallArgsFromVp(argc, vp);
7202 Value obj = args.get(0);
7203 Value proto = args.get(1);
7204 if (!obj.isObject() || !proto.isObjectOrNull()) {
7205 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
7206 JSSMSG_INVALID_ARGS, "wrapWithProto");
7207 return false;
7208 }
7209
7210 // Disallow constructing (deeply) nested wrapper chains, to avoid running
7211 // out of stack space in isCallable/isConstructor. See bug 1126105.
7212 if (IsWrapper(&obj.toObject())) {
7213 JS_ReportErrorASCII(cx, "wrapWithProto cannot wrap a wrapper");
7214 return false;
7215 }
7216
7217 WrapperOptions options(cx);
7218 options.setProto(proto.toObjectOrNull());
7219 JSObject* wrapped = Wrapper::New(cx, &obj.toObject(),
7220 &Wrapper::singletonWithPrototype, options);
7221 if (!wrapped) {
7222 return false;
7223 }
7224
7225 args.rval().setObject(*wrapped);
7226 return true;
7227 }
7228
NewGlobal(JSContext * cx,unsigned argc,Value * vp)7229 static bool NewGlobal(JSContext* cx, unsigned argc, Value* vp) {
7230 JS::RealmOptions options;
7231 JS::RealmCreationOptions& creationOptions = options.creationOptions();
7232 JS::RealmBehaviors& behaviors = options.behaviors();
7233 ShellGlobalKind kind = ShellGlobalKind::WindowProxy;
7234 bool immutablePrototype = true;
7235
7236 SetStandardRealmOptions(options);
7237
7238 // Default to creating the global in the current compartment unless
7239 // --more-compartments is used.
7240 if (defaultToSameCompartment) {
7241 creationOptions.setExistingCompartment(cx->global());
7242 } else {
7243 creationOptions.setNewCompartmentAndZone();
7244 }
7245
7246 JS::AutoHoldPrincipals principals(cx);
7247
7248 CallArgs args = CallArgsFromVp(argc, vp);
7249 if (args.length() == 1 && args[0].isObject()) {
7250 RootedObject opts(cx, &args[0].toObject());
7251 RootedValue v(cx);
7252
7253 if (!JS_GetProperty(cx, opts, "invisibleToDebugger", &v)) {
7254 return false;
7255 }
7256 if (v.isBoolean()) {
7257 creationOptions.setInvisibleToDebugger(v.toBoolean());
7258 }
7259
7260 if (!JS_GetProperty(cx, opts, "sameZoneAs", &v)) {
7261 return false;
7262 }
7263 if (v.isObject()) {
7264 creationOptions.setNewCompartmentInExistingZone(
7265 UncheckedUnwrap(&v.toObject()));
7266 }
7267
7268 if (!JS_GetProperty(cx, opts, "sameCompartmentAs", &v)) {
7269 return false;
7270 }
7271 if (v.isObject()) {
7272 creationOptions.setExistingCompartment(UncheckedUnwrap(&v.toObject()));
7273 }
7274
7275 if (!JS_GetProperty(cx, opts, "newCompartment", &v)) {
7276 return false;
7277 }
7278 if (v.isBoolean() && v.toBoolean()) {
7279 creationOptions.setNewCompartmentAndZone();
7280 }
7281
7282 if (!JS_GetProperty(cx, opts, "discardSource", &v)) {
7283 return false;
7284 }
7285 if (v.isBoolean()) {
7286 behaviors.setDiscardSource(v.toBoolean());
7287 }
7288
7289 if (!JS_GetProperty(cx, opts, "useWindowProxy", &v)) {
7290 return false;
7291 }
7292 if (v.isBoolean()) {
7293 kind = v.toBoolean() ? ShellGlobalKind::WindowProxy
7294 : ShellGlobalKind::GlobalObject;
7295 }
7296
7297 if (!JS_GetProperty(cx, opts, "immutablePrototype", &v)) {
7298 return false;
7299 }
7300 if (v.isBoolean()) {
7301 immutablePrototype = v.toBoolean();
7302 }
7303
7304 if (!JS_GetProperty(cx, opts, "enableWritableStreams", &v)) {
7305 return false;
7306 }
7307 if (v.isBoolean()) {
7308 creationOptions.setWritableStreamsEnabled(v.toBoolean());
7309 }
7310
7311 if (!JS_GetProperty(cx, opts, "enableReadableStreamPipeTo", &v)) {
7312 return false;
7313 }
7314 if (v.isBoolean()) {
7315 creationOptions.setReadableStreamPipeToEnabled(v.toBoolean());
7316 }
7317
7318 if (!JS_GetProperty(cx, opts, "systemPrincipal", &v)) {
7319 return false;
7320 }
7321 if (v.isBoolean()) {
7322 principals.reset(&ShellPrincipals::fullyTrusted);
7323 }
7324
7325 if (!JS_GetProperty(cx, opts, "principal", &v)) {
7326 return false;
7327 }
7328 if (!v.isUndefined()) {
7329 uint32_t bits;
7330 if (!ToUint32(cx, v, &bits)) {
7331 return false;
7332 }
7333 JSPrincipals* newPrincipals = cx->new_<ShellPrincipals>(bits);
7334 if (!newPrincipals) {
7335 return false;
7336 }
7337 principals.reset(newPrincipals);
7338 }
7339
7340 if (!JS_GetProperty(cx, opts, "enableCoopAndCoep", &v)) {
7341 return false;
7342 }
7343 if (v.isBoolean()) {
7344 creationOptions.setCoopAndCoepEnabled(v.toBoolean());
7345 }
7346
7347 // On the web, the SharedArrayBuffer constructor is not installed as a
7348 // global property in pages that aren't isolated in a separate process (and
7349 // thus can't allow the structured cloning of shared memory). Specify false
7350 // for this option to reproduce this behavior.
7351 if (!JS_GetProperty(cx, opts, "defineSharedArrayBufferConstructor", &v)) {
7352 return false;
7353 }
7354 if (v.isBoolean()) {
7355 creationOptions.setDefineSharedArrayBufferConstructor(v.toBoolean());
7356 }
7357 }
7358
7359 if (!CheckRealmOptions(cx, options, principals.get())) {
7360 return false;
7361 }
7362
7363 RootedObject global(cx, NewGlobalObject(cx, options, principals.get(), kind,
7364 immutablePrototype));
7365 if (!global) {
7366 return false;
7367 }
7368
7369 RootedObject wrapped(cx, ToWindowProxyIfWindow(global));
7370 if (!JS_WrapObject(cx, &wrapped)) {
7371 return false;
7372 }
7373
7374 args.rval().setObject(*wrapped);
7375 return true;
7376 }
7377
NukeCCW(JSContext * cx,unsigned argc,Value * vp)7378 static bool NukeCCW(JSContext* cx, unsigned argc, Value* vp) {
7379 CallArgs args = CallArgsFromVp(argc, vp);
7380
7381 if (args.length() != 1 || !args[0].isObject() ||
7382 !IsCrossCompartmentWrapper(&args[0].toObject())) {
7383 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
7384 JSSMSG_INVALID_ARGS, "nukeCCW");
7385 return false;
7386 }
7387
7388 NukeCrossCompartmentWrapper(cx, &args[0].toObject());
7389 args.rval().setUndefined();
7390 return true;
7391 }
7392
NukeAllCCWs(JSContext * cx,unsigned argc,Value * vp)7393 static bool NukeAllCCWs(JSContext* cx, unsigned argc, Value* vp) {
7394 CallArgs args = CallArgsFromVp(argc, vp);
7395
7396 if (args.length() != 0) {
7397 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
7398 JSSMSG_INVALID_ARGS, "nukeAllCCWs");
7399 return false;
7400 }
7401
7402 NukeCrossCompartmentWrappers(cx, AllCompartments(), cx->realm(),
7403 NukeWindowReferences, NukeAllReferences);
7404 args.rval().setUndefined();
7405 return true;
7406 }
7407
RecomputeWrappers(JSContext * cx,unsigned argc,Value * vp)7408 static bool RecomputeWrappers(JSContext* cx, unsigned argc, Value* vp) {
7409 CallArgs args = CallArgsFromVp(argc, vp);
7410
7411 if (args.length() > 2) {
7412 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
7413 JSSMSG_INVALID_ARGS, "recomputeWrappers");
7414 return false;
7415 }
7416
7417 JS::Compartment* sourceComp = nullptr;
7418 if (args.get(0).isObject()) {
7419 sourceComp = JS::GetCompartment(UncheckedUnwrap(&args[0].toObject()));
7420 }
7421
7422 JS::Compartment* targetComp = nullptr;
7423 if (args.get(1).isObject()) {
7424 targetComp = JS::GetCompartment(UncheckedUnwrap(&args[1].toObject()));
7425 }
7426
7427 struct SingleOrAllCompartments final : public CompartmentFilter {
7428 JS::Compartment* comp;
7429 explicit SingleOrAllCompartments(JS::Compartment* c) : comp(c) {}
7430 virtual bool match(JS::Compartment* c) const override {
7431 return !comp || comp == c;
7432 }
7433 };
7434
7435 if (!js::RecomputeWrappers(cx, SingleOrAllCompartments(sourceComp),
7436 SingleOrAllCompartments(targetComp))) {
7437 return false;
7438 }
7439
7440 args.rval().setUndefined();
7441 return true;
7442 }
7443
DumpObjectWrappers(JSContext * cx,unsigned argc,Value * vp)7444 static bool DumpObjectWrappers(JSContext* cx, unsigned argc, Value* vp) {
7445 CallArgs args = CallArgsFromVp(argc, vp);
7446
7447 bool printedHeader = false;
7448 for (ZonesIter zone(cx->runtime(), WithAtoms); !zone.done(); zone.next()) {
7449 bool printedZoneInfo = false;
7450 for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) {
7451 bool printedCompartmentInfo = false;
7452 for (Compartment::ObjectWrapperEnum e(comp); !e.empty(); e.popFront()) {
7453 JSObject* wrapper = e.front().value().unbarrieredGet();
7454 JSObject* wrapped = e.front().key();
7455 if (!printedHeader) {
7456 fprintf(stderr, "Cross-compartment object wrappers:\n");
7457 printedHeader = true;
7458 }
7459 if (!printedZoneInfo) {
7460 fprintf(stderr, " Zone %p:\n", zone.get());
7461 printedZoneInfo = true;
7462 }
7463 if (!printedCompartmentInfo) {
7464 fprintf(stderr, " Compartment %p:\n", comp.get());
7465 printedCompartmentInfo = true;
7466 }
7467 fprintf(stderr,
7468 " Object wrapper %p -> %p in zone %p compartment %p\n",
7469 wrapper, wrapped, wrapped->zone(), wrapped->compartment());
7470 }
7471 }
7472 }
7473
7474 if (!printedHeader) {
7475 fprintf(stderr, "No cross-compartment object wrappers.\n");
7476 }
7477
7478 args.rval().setUndefined();
7479 return true;
7480 }
7481
GetMaxArgs(JSContext * cx,unsigned argc,Value * vp)7482 static bool GetMaxArgs(JSContext* cx, unsigned argc, Value* vp) {
7483 CallArgs args = CallArgsFromVp(argc, vp);
7484 args.rval().setInt32(ARGS_LENGTH_MAX);
7485 return true;
7486 }
7487
IsHTMLDDA_Call(JSContext * cx,unsigned argc,Value * vp)7488 static bool IsHTMLDDA_Call(JSContext* cx, unsigned argc, Value* vp) {
7489 CallArgs args = CallArgsFromVp(argc, vp);
7490
7491 // These are the required conditions under which this object may be called
7492 // by test262 tests, and the required behavior under those conditions.
7493 if (args.length() == 0 ||
7494 (args[0].isString() && args[0].toString()->length() == 0)) {
7495 args.rval().setNull();
7496 return true;
7497 }
7498
7499 JS_ReportErrorASCII(
7500 cx, "IsHTMLDDA object is being called in an impermissible manner");
7501 return false;
7502 }
7503
CreateIsHTMLDDA(JSContext * cx,unsigned argc,Value * vp)7504 static bool CreateIsHTMLDDA(JSContext* cx, unsigned argc, Value* vp) {
7505 CallArgs args = CallArgsFromVp(argc, vp);
7506
7507 static const JSClassOps classOps = {
7508 nullptr, // addProperty
7509 nullptr, // delProperty
7510 nullptr, // enumerate
7511 nullptr, // newEnumerate
7512 nullptr, // resolve
7513 nullptr, // mayResolve
7514 nullptr, // finalize
7515 IsHTMLDDA_Call, // call
7516 nullptr, // hasInstance
7517 nullptr, // construct
7518 nullptr, // trace
7519 };
7520
7521 static const JSClass cls = {
7522 "IsHTMLDDA",
7523 JSCLASS_EMULATES_UNDEFINED,
7524 &classOps,
7525 };
7526
7527 JSObject* obj = JS_NewObject(cx, &cls);
7528 if (!obj) {
7529 return false;
7530 }
7531 args.rval().setObject(*obj);
7532 return true;
7533 }
7534
GetSelfHostedValue(JSContext * cx,unsigned argc,Value * vp)7535 static bool GetSelfHostedValue(JSContext* cx, unsigned argc, Value* vp) {
7536 CallArgs args = CallArgsFromVp(argc, vp);
7537
7538 if (args.length() != 1 || !args[0].isString()) {
7539 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
7540 JSSMSG_INVALID_ARGS, "getSelfHostedValue");
7541 return false;
7542 }
7543 RootedAtom srcAtom(cx, ToAtom<CanGC>(cx, args[0]));
7544 if (!srcAtom) {
7545 return false;
7546 }
7547 RootedPropertyName srcName(cx, srcAtom->asPropertyName());
7548 return GlobalObject::getIntrinsicValue(cx, cx->global(), srcName,
7549 args.rval());
7550 }
7551
7552 class ShellSourceHook : public SourceHook {
7553 // The function we should call to lazily retrieve source code.
7554 PersistentRootedFunction fun;
7555
7556 public:
ShellSourceHook(JSContext * cx,JSFunction & fun)7557 ShellSourceHook(JSContext* cx, JSFunction& fun) : fun(cx, &fun) {}
7558
load(JSContext * cx,const char * filename,char16_t ** twoByteSource,char ** utf8Source,size_t * length)7559 bool load(JSContext* cx, const char* filename, char16_t** twoByteSource,
7560 char** utf8Source, size_t* length) override {
7561 MOZ_ASSERT((twoByteSource != nullptr) != (utf8Source != nullptr),
7562 "must be called requesting only one of UTF-8 or UTF-16 source");
7563
7564 RootedString str(cx, JS_NewStringCopyZ(cx, filename));
7565 if (!str) {
7566 return false;
7567 }
7568 RootedValue filenameValue(cx, StringValue(str));
7569
7570 RootedValue result(cx);
7571 if (!Call(cx, UndefinedHandleValue, fun, HandleValueArray(filenameValue),
7572 &result)) {
7573 return false;
7574 }
7575
7576 str = JS::ToString(cx, result);
7577 if (!str) {
7578 return false;
7579 }
7580
7581 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
7582 if (!linear) {
7583 return false;
7584 }
7585
7586 if (twoByteSource) {
7587 *length = JS_GetStringLength(linear);
7588
7589 *twoByteSource = cx->pod_malloc<char16_t>(*length);
7590 if (!*twoByteSource) {
7591 return false;
7592 }
7593
7594 CopyChars(*twoByteSource, *linear);
7595 } else {
7596 MOZ_ASSERT(utf8Source != nullptr);
7597
7598 *length = JS::GetDeflatedUTF8StringLength(linear);
7599
7600 *utf8Source = cx->pod_malloc<char>(*length);
7601 if (!*utf8Source) {
7602 return false;
7603 }
7604
7605 mozilla::DebugOnly<size_t> dstLen = JS::DeflateStringToUTF8Buffer(
7606 linear, mozilla::Span(*utf8Source, *length));
7607 MOZ_ASSERT(dstLen == *length);
7608 }
7609
7610 return true;
7611 }
7612 };
7613
WithSourceHook(JSContext * cx,unsigned argc,Value * vp)7614 static bool WithSourceHook(JSContext* cx, unsigned argc, Value* vp) {
7615 CallArgs args = CallArgsFromVp(argc, vp);
7616 RootedObject callee(cx, &args.callee());
7617
7618 if (args.length() != 2) {
7619 ReportUsageErrorASCII(cx, callee, "Wrong number of arguments.");
7620 return false;
7621 }
7622
7623 if (!args[0].isObject() || !args[0].toObject().is<JSFunction>() ||
7624 !args[1].isObject() || !args[1].toObject().is<JSFunction>()) {
7625 ReportUsageErrorASCII(cx, callee,
7626 "First and second arguments must be functions.");
7627 return false;
7628 }
7629
7630 mozilla::UniquePtr<ShellSourceHook> hook =
7631 mozilla::MakeUnique<ShellSourceHook>(cx,
7632 args[0].toObject().as<JSFunction>());
7633 if (!hook) {
7634 return false;
7635 }
7636
7637 mozilla::UniquePtr<SourceHook> savedHook = js::ForgetSourceHook(cx);
7638 js::SetSourceHook(cx, std::move(hook));
7639
7640 RootedObject fun(cx, &args[1].toObject());
7641 bool result = Call(cx, UndefinedHandleValue, fun,
7642 JS::HandleValueArray::empty(), args.rval());
7643 js::SetSourceHook(cx, std::move(savedHook));
7644 return result;
7645 }
7646
PrintProfilerEvents_Callback(const char * msg,const char * details)7647 static void PrintProfilerEvents_Callback(const char* msg, const char* details) {
7648 fprintf(stderr, "PROFILER EVENT: %s %s\n", msg, details);
7649 }
7650
PrintProfilerEvents(JSContext * cx,unsigned argc,Value * vp)7651 static bool PrintProfilerEvents(JSContext* cx, unsigned argc, Value* vp) {
7652 CallArgs args = CallArgsFromVp(argc, vp);
7653 if (cx->runtime()->geckoProfiler().enabled()) {
7654 js::RegisterContextProfilingEventMarker(cx, &PrintProfilerEvents_Callback);
7655 }
7656 args.rval().setUndefined();
7657 return true;
7658 }
7659
7660 #ifdef SINGLESTEP_PROFILING
SingleStepCallback(void * arg,jit::Simulator * sim,void * pc)7661 static void SingleStepCallback(void* arg, jit::Simulator* sim, void* pc) {
7662 JSContext* cx = reinterpret_cast<JSContext*>(arg);
7663
7664 // If profiling is not enabled, don't do anything.
7665 if (!cx->runtime()->geckoProfiler().enabled()) {
7666 return;
7667 }
7668
7669 JS::ProfilingFrameIterator::RegisterState state;
7670 state.pc = pc;
7671 # if defined(JS_SIMULATOR_ARM)
7672 state.sp = (void*)sim->get_register(jit::Simulator::sp);
7673 state.lr = (void*)sim->get_register(jit::Simulator::lr);
7674 state.fp = (void*)sim->get_register(jit::Simulator::fp);
7675 # elif defined(JS_SIMULATOR_MIPS64) || defined(JS_SIMULATOR_MIPS32)
7676 state.sp = (void*)sim->getRegister(jit::Simulator::sp);
7677 state.lr = (void*)sim->getRegister(jit::Simulator::ra);
7678 state.fp = (void*)sim->getRegister(jit::Simulator::fp);
7679 # else
7680 # error "NYI: Single-step profiling support"
7681 # endif
7682
7683 mozilla::DebugOnly<void*> lastStackAddress = nullptr;
7684 StackChars stack;
7685 uint32_t frameNo = 0;
7686 AutoEnterOOMUnsafeRegion oomUnsafe;
7687 for (JS::ProfilingFrameIterator i(cx, state); !i.done(); ++i) {
7688 MOZ_ASSERT(i.stackAddress() != nullptr);
7689 MOZ_ASSERT(lastStackAddress <= i.stackAddress());
7690 lastStackAddress = i.stackAddress();
7691 JS::ProfilingFrameIterator::Frame frames[16];
7692 uint32_t nframes = i.extractStack(frames, 0, 16);
7693 for (uint32_t i = 0; i < nframes; i++) {
7694 if (frameNo > 0) {
7695 if (!stack.append(",", 1)) {
7696 oomUnsafe.crash("stack.append");
7697 }
7698 }
7699 if (!stack.append(frames[i].label, strlen(frames[i].label))) {
7700 oomUnsafe.crash("stack.append");
7701 }
7702 frameNo++;
7703 }
7704 }
7705
7706 ShellContext* sc = GetShellContext(cx);
7707
7708 // Only append the stack if it differs from the last stack.
7709 if (sc->stacks.empty() || sc->stacks.back().length() != stack.length() ||
7710 !ArrayEqual(sc->stacks.back().begin(), stack.begin(), stack.length())) {
7711 if (!sc->stacks.append(std::move(stack))) {
7712 oomUnsafe.crash("stacks.append");
7713 }
7714 }
7715 }
7716 #endif
7717
EnableSingleStepProfiling(JSContext * cx,unsigned argc,Value * vp)7718 static bool EnableSingleStepProfiling(JSContext* cx, unsigned argc, Value* vp) {
7719 #ifdef SINGLESTEP_PROFILING
7720 CallArgs args = CallArgsFromVp(argc, vp);
7721
7722 jit::Simulator* sim = cx->simulator();
7723 sim->enable_single_stepping(SingleStepCallback, cx);
7724
7725 args.rval().setUndefined();
7726 return true;
7727 #else
7728 JS_ReportErrorASCII(cx, "single-step profiling not enabled on this platform");
7729 return false;
7730 #endif
7731 }
7732
DisableSingleStepProfiling(JSContext * cx,unsigned argc,Value * vp)7733 static bool DisableSingleStepProfiling(JSContext* cx, unsigned argc,
7734 Value* vp) {
7735 #ifdef SINGLESTEP_PROFILING
7736 CallArgs args = CallArgsFromVp(argc, vp);
7737
7738 jit::Simulator* sim = cx->simulator();
7739 sim->disable_single_stepping();
7740
7741 ShellContext* sc = GetShellContext(cx);
7742
7743 RootedValueVector elems(cx);
7744 for (size_t i = 0; i < sc->stacks.length(); i++) {
7745 JSString* stack =
7746 JS_NewUCStringCopyN(cx, sc->stacks[i].begin(), sc->stacks[i].length());
7747 if (!stack) {
7748 return false;
7749 }
7750 if (!elems.append(StringValue(stack))) {
7751 return false;
7752 }
7753 }
7754
7755 JSObject* array = JS::NewArrayObject(cx, elems);
7756 if (!array) {
7757 return false;
7758 }
7759
7760 sc->stacks.clear();
7761 args.rval().setObject(*array);
7762 return true;
7763 #else
7764 JS_ReportErrorASCII(cx, "single-step profiling not enabled on this platform");
7765 return false;
7766 #endif
7767 }
7768
IsLatin1(JSContext * cx,unsigned argc,Value * vp)7769 static bool IsLatin1(JSContext* cx, unsigned argc, Value* vp) {
7770 CallArgs args = CallArgsFromVp(argc, vp);
7771 bool isLatin1 =
7772 args.get(0).isString() && args[0].toString()->hasLatin1Chars();
7773 args.rval().setBoolean(isLatin1);
7774 return true;
7775 }
7776
EnableGeckoProfiling(JSContext * cx,unsigned argc,Value * vp)7777 static bool EnableGeckoProfiling(JSContext* cx, unsigned argc, Value* vp) {
7778 CallArgs args = CallArgsFromVp(argc, vp);
7779
7780 if (!EnsureGeckoProfilingStackInstalled(cx, GetShellContext(cx))) {
7781 return false;
7782 }
7783
7784 cx->runtime()->geckoProfiler().enableSlowAssertions(false);
7785 cx->runtime()->geckoProfiler().enable(true);
7786
7787 args.rval().setUndefined();
7788 return true;
7789 }
7790
EnableGeckoProfilingWithSlowAssertions(JSContext * cx,unsigned argc,Value * vp)7791 static bool EnableGeckoProfilingWithSlowAssertions(JSContext* cx, unsigned argc,
7792 Value* vp) {
7793 CallArgs args = CallArgsFromVp(argc, vp);
7794 args.rval().setUndefined();
7795
7796 if (cx->runtime()->geckoProfiler().enabled()) {
7797 // If profiling already enabled with slow assertions disabled,
7798 // this is a no-op.
7799 if (cx->runtime()->geckoProfiler().slowAssertionsEnabled()) {
7800 return true;
7801 }
7802
7803 // Slow assertions are off. Disable profiling before re-enabling
7804 // with slow assertions on.
7805 cx->runtime()->geckoProfiler().enable(false);
7806 }
7807
7808 if (!EnsureGeckoProfilingStackInstalled(cx, GetShellContext(cx))) {
7809 return false;
7810 }
7811
7812 cx->runtime()->geckoProfiler().enableSlowAssertions(true);
7813 cx->runtime()->geckoProfiler().enable(true);
7814
7815 return true;
7816 }
7817
DisableGeckoProfiling(JSContext * cx,unsigned argc,Value * vp)7818 static bool DisableGeckoProfiling(JSContext* cx, unsigned argc, Value* vp) {
7819 CallArgs args = CallArgsFromVp(argc, vp);
7820 args.rval().setUndefined();
7821
7822 if (!cx->runtime()->geckoProfiler().enabled()) {
7823 return true;
7824 }
7825
7826 cx->runtime()->geckoProfiler().enable(false);
7827 return true;
7828 }
7829
7830 // Global mailbox that is used to communicate a shareable object value from one
7831 // worker to another.
7832 //
7833 // These object types are shareable:
7834 //
7835 // - SharedArrayBuffer
7836 // - WasmMemoryObject (when constructed with shared:true)
7837 // - WasmModuleObject
7838 //
7839 // For the SharedArrayBuffer and WasmMemoryObject we transmit the underlying
7840 // SharedArrayRawBuffer ("SARB"). For the WasmModuleObject we transmit the
7841 // underlying JS::WasmModule. The transmitted types are refcounted. When they
7842 // are in the mailbox their reference counts are at least 1, accounting for the
7843 // reference from the mailbox.
7844 //
7845 // The lock guards the mailbox variable and prevents a race where two workers
7846 // try to set the mailbox at the same time to replace an object that is only
7847 // referenced from the mailbox: the workers will both decrement the reference
7848 // count on the old object, and one of those decrements will be on a garbage
7849 // object. We could implement this with atomics and a CAS loop but it's not
7850 // worth the bother.
7851 //
7852 // Note that if a thread reads the mailbox repeatedly it will get distinct
7853 // objects on each read. The alternatives are to cache created objects locally,
7854 // but this retains storage we don't need to retain, or to somehow clear the
7855 // mailbox locally, but this creates a coordination headache. Buyer beware.
7856
7857 enum class MailboxTag {
7858 Empty,
7859 SharedArrayBuffer,
7860 WasmMemory,
7861 WasmModule,
7862 Number,
7863 };
7864
7865 struct SharedObjectMailbox {
7866 union Value {
7867 struct {
7868 SharedArrayRawBuffer* buffer;
7869 size_t length;
7870 } sarb;
7871 JS::WasmModule* module;
7872 double number;
7873
Value()7874 Value() : number(0.0) {}
7875 };
7876
7877 MailboxTag tag = MailboxTag::Empty;
7878 Value val;
7879 };
7880
7881 typedef ExclusiveData<SharedObjectMailbox> SOMailbox;
7882
7883 // Never null after successful initialization.
7884 static SOMailbox* sharedObjectMailbox;
7885
InitSharedObjectMailbox()7886 static bool InitSharedObjectMailbox() {
7887 sharedObjectMailbox = js_new<SOMailbox>(mutexid::ShellObjectMailbox);
7888 return sharedObjectMailbox != nullptr;
7889 }
7890
DestructSharedObjectMailbox()7891 static void DestructSharedObjectMailbox() {
7892 // All workers need to have terminated at this point.
7893
7894 {
7895 auto mbx = sharedObjectMailbox->lock();
7896 switch (mbx->tag) {
7897 case MailboxTag::Empty:
7898 case MailboxTag::Number:
7899 break;
7900 case MailboxTag::SharedArrayBuffer:
7901 case MailboxTag::WasmMemory:
7902 mbx->val.sarb.buffer->dropReference();
7903 break;
7904 case MailboxTag::WasmModule:
7905 mbx->val.module->Release();
7906 break;
7907 default:
7908 MOZ_CRASH();
7909 }
7910 }
7911
7912 js_delete(sharedObjectMailbox);
7913 sharedObjectMailbox = nullptr;
7914 }
7915
GetSharedObject(JSContext * cx,unsigned argc,Value * vp)7916 static bool GetSharedObject(JSContext* cx, unsigned argc, Value* vp) {
7917 CallArgs args = CallArgsFromVp(argc, vp);
7918 RootedObject newObj(cx);
7919
7920 {
7921 auto mbx = sharedObjectMailbox->lock();
7922 switch (mbx->tag) {
7923 case MailboxTag::Empty: {
7924 break;
7925 }
7926 case MailboxTag::Number: {
7927 args.rval().setNumber(mbx->val.number);
7928 return true;
7929 }
7930 case MailboxTag::SharedArrayBuffer:
7931 case MailboxTag::WasmMemory: {
7932 // Flag was set in the sender; ensure it is set in the receiver.
7933 MOZ_ASSERT(
7934 cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled());
7935
7936 // The protocol for creating a SAB requires the refcount to be
7937 // incremented prior to the SAB creation.
7938
7939 SharedArrayRawBuffer* buf = mbx->val.sarb.buffer;
7940 size_t length = mbx->val.sarb.length;
7941 if (!buf->addReference()) {
7942 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
7943 JSMSG_SC_SAB_REFCNT_OFLO);
7944 return false;
7945 }
7946
7947 // If the allocation fails we must decrement the refcount before
7948 // returning.
7949
7950 Rooted<ArrayBufferObjectMaybeShared*> maybesab(
7951 cx, SharedArrayBufferObject::New(cx, buf, length));
7952 if (!maybesab) {
7953 buf->dropReference();
7954 return false;
7955 }
7956
7957 // At this point the SAB was created successfully and it owns the
7958 // refcount-increase on the buffer that we performed above. So even
7959 // if we fail to allocate along any path below we must not decrement
7960 // the refcount; the garbage collector must be allowed to handle
7961 // that via finalization of the orphaned SAB object.
7962
7963 if (mbx->tag == MailboxTag::SharedArrayBuffer) {
7964 newObj = maybesab;
7965 } else {
7966 if (!GlobalObject::ensureConstructor(cx, cx->global(),
7967 JSProto_WebAssembly)) {
7968 return false;
7969 }
7970 RootedObject proto(
7971 cx, &cx->global()->getPrototype(JSProto_WasmMemory).toObject());
7972 newObj = WasmMemoryObject::create(cx, maybesab, proto);
7973 MOZ_ASSERT_IF(newObj, newObj->as<WasmMemoryObject>().isShared());
7974 if (!newObj) {
7975 return false;
7976 }
7977 }
7978
7979 break;
7980 }
7981 case MailboxTag::WasmModule: {
7982 // Flag was set in the sender; ensure it is set in the receiver.
7983 MOZ_ASSERT(
7984 cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled());
7985
7986 if (!GlobalObject::ensureConstructor(cx, cx->global(),
7987 JSProto_WebAssembly)) {
7988 return false;
7989 }
7990
7991 // WasmModuleObject::create() increments the refcount on the module
7992 // and signals an error and returns null if that fails.
7993 newObj = mbx->val.module->createObject(cx);
7994 if (!newObj) {
7995 return false;
7996 }
7997 break;
7998 }
7999 default: {
8000 MOZ_CRASH();
8001 }
8002 }
8003 }
8004
8005 args.rval().setObjectOrNull(newObj);
8006 return true;
8007 }
8008
SetSharedObject(JSContext * cx,unsigned argc,Value * vp)8009 static bool SetSharedObject(JSContext* cx, unsigned argc, Value* vp) {
8010 CallArgs args = CallArgsFromVp(argc, vp);
8011
8012 MailboxTag tag = MailboxTag::Empty;
8013 SharedObjectMailbox::Value value;
8014
8015 // Increase refcounts when we obtain the value to avoid operating on dead
8016 // storage during self-assignment.
8017
8018 if (args.get(0).isObject()) {
8019 RootedObject obj(cx, &args[0].toObject());
8020 if (obj->is<SharedArrayBufferObject>()) {
8021 Rooted<SharedArrayBufferObject*> sab(cx,
8022 &obj->as<SharedArrayBufferObject>());
8023 tag = MailboxTag::SharedArrayBuffer;
8024 value.sarb.buffer = sab->rawBufferObject();
8025 value.sarb.length = sab->byteLength();
8026 if (!value.sarb.buffer->addReference()) {
8027 JS_ReportErrorASCII(cx,
8028 "Reference count overflow on SharedArrayBuffer");
8029 return false;
8030 }
8031 } else if (obj->is<WasmMemoryObject>()) {
8032 // Here we must transmit sab.byteLength() as the length; the SARB has its
8033 // own notion of the length which may be greater, and that's fine.
8034 if (obj->as<WasmMemoryObject>().isShared()) {
8035 Rooted<SharedArrayBufferObject*> sab(
8036 cx, &obj->as<WasmMemoryObject>()
8037 .buffer()
8038 .as<SharedArrayBufferObject>());
8039 tag = MailboxTag::WasmMemory;
8040 value.sarb.buffer = sab->rawBufferObject();
8041 value.sarb.length = sab->byteLength();
8042 if (!value.sarb.buffer->addReference()) {
8043 JS_ReportErrorASCII(cx,
8044 "Reference count overflow on SharedArrayBuffer");
8045 return false;
8046 }
8047 } else {
8048 JS_ReportErrorASCII(cx, "Invalid argument to SetSharedObject");
8049 return false;
8050 }
8051 } else if (JS::IsWasmModuleObject(obj)) {
8052 tag = MailboxTag::WasmModule;
8053 value.module = JS::GetWasmModule(obj).forget().take();
8054 } else {
8055 JS_ReportErrorASCII(cx, "Invalid argument to SetSharedObject");
8056 return false;
8057 }
8058 } else if (args.get(0).isNumber()) {
8059 tag = MailboxTag::Number;
8060 value.number = args.get(0).toNumber();
8061 // Nothing
8062 } else if (args.get(0).isNullOrUndefined()) {
8063 // Nothing
8064 } else {
8065 JS_ReportErrorASCII(cx, "Invalid argument to SetSharedObject");
8066 return false;
8067 }
8068
8069 {
8070 auto mbx = sharedObjectMailbox->lock();
8071
8072 switch (mbx->tag) {
8073 case MailboxTag::Empty:
8074 case MailboxTag::Number:
8075 break;
8076 case MailboxTag::SharedArrayBuffer:
8077 case MailboxTag::WasmMemory:
8078 mbx->val.sarb.buffer->dropReference();
8079 break;
8080 case MailboxTag::WasmModule:
8081 mbx->val.module->Release();
8082 break;
8083 default:
8084 MOZ_CRASH();
8085 }
8086
8087 mbx->tag = tag;
8088 mbx->val = value;
8089 }
8090
8091 args.rval().setUndefined();
8092 return true;
8093 }
8094
8095 typedef Vector<uint8_t, 0, SystemAllocPolicy> Uint8Vector;
8096
8097 class StreamCacheEntry : public AtomicRefCounted<StreamCacheEntry>,
8098 public JS::OptimizedEncodingListener {
8099 typedef AtomicRefCounted<StreamCacheEntry> AtomicBase;
8100
8101 Uint8Vector bytes_;
8102 ExclusiveData<Uint8Vector> optimized_;
8103
8104 public:
StreamCacheEntry(Uint8Vector && original)8105 explicit StreamCacheEntry(Uint8Vector&& original)
8106 : bytes_(std::move(original)),
8107 optimized_(mutexid::ShellStreamCacheEntryState) {}
8108
8109 // Implement JS::OptimizedEncodingListener:
8110
AddRef()8111 MozExternalRefCountType MOZ_XPCOM_ABI AddRef() override {
8112 AtomicBase::AddRef();
8113 return 1; // unused
8114 }
Release()8115 MozExternalRefCountType MOZ_XPCOM_ABI Release() override {
8116 AtomicBase::Release();
8117 return 0; // unused
8118 }
8119
bytes() const8120 const Uint8Vector& bytes() const { return bytes_; }
8121
storeOptimizedEncoding(JS::UniqueOptimizedEncodingBytes src)8122 void storeOptimizedEncoding(JS::UniqueOptimizedEncodingBytes src) override {
8123 MOZ_ASSERT(src->length() > 0);
8124
8125 // Tolerate races since a single StreamCacheEntry object can be used as
8126 // the source of multiple streaming compilations.
8127 auto dstBytes = optimized_.lock();
8128 if (dstBytes->length() > 0) {
8129 return;
8130 }
8131
8132 if (!dstBytes->resize(src->length())) {
8133 return;
8134 }
8135 memcpy(dstBytes->begin(), src->begin(), src->length());
8136 }
8137
hasOptimizedEncoding() const8138 bool hasOptimizedEncoding() const { return !optimized_.lock()->empty(); }
optimizedEncoding() const8139 const Uint8Vector& optimizedEncoding() const {
8140 return optimized_.lock().get();
8141 }
8142 };
8143
8144 typedef RefPtr<StreamCacheEntry> StreamCacheEntryPtr;
8145
8146 class StreamCacheEntryObject : public NativeObject {
8147 static const unsigned CACHE_ENTRY_SLOT = 0;
8148 static const JSClassOps classOps_;
8149 static const JSPropertySpec properties_;
8150
finalize(JSFreeOp *,JSObject * obj)8151 static void finalize(JSFreeOp*, JSObject* obj) {
8152 obj->as<StreamCacheEntryObject>().cache().Release();
8153 }
8154
cachedGetter(JSContext * cx,unsigned argc,Value * vp)8155 static bool cachedGetter(JSContext* cx, unsigned argc, Value* vp) {
8156 CallArgs args = CallArgsFromVp(argc, vp);
8157 if (!args.thisv().isObject() ||
8158 !args.thisv().toObject().is<StreamCacheEntryObject>()) {
8159 return false;
8160 }
8161
8162 StreamCacheEntryObject& obj =
8163 args.thisv().toObject().as<StreamCacheEntryObject>();
8164 args.rval().setBoolean(obj.cache().hasOptimizedEncoding());
8165 return true;
8166 }
getBuffer(JSContext * cx,unsigned argc,Value * vp)8167 static bool getBuffer(JSContext* cx, unsigned argc, Value* vp) {
8168 CallArgs args = CallArgsFromVp(argc, vp);
8169 if (!args.thisv().isObject() ||
8170 !args.thisv().toObject().is<StreamCacheEntryObject>()) {
8171 return false;
8172 }
8173
8174 auto& bytes =
8175 args.thisv().toObject().as<StreamCacheEntryObject>().cache().bytes();
8176 RootedArrayBufferObject buffer(
8177 cx, ArrayBufferObject::createZeroed(cx, bytes.length()));
8178 if (!buffer) {
8179 return false;
8180 }
8181
8182 memcpy(buffer->dataPointer(), bytes.begin(), bytes.length());
8183
8184 args.rval().setObject(*buffer);
8185 return true;
8186 }
8187
8188 public:
8189 static const unsigned RESERVED_SLOTS = 1;
8190 static const JSClass class_;
8191 static const JSPropertySpec properties[];
8192
construct(JSContext * cx,unsigned argc,Value * vp)8193 static bool construct(JSContext* cx, unsigned argc, Value* vp) {
8194 CallArgs args = CallArgsFromVp(argc, vp);
8195 if (!args.requireAtLeast(cx, "streamCacheEntry", 1)) {
8196 return false;
8197 }
8198
8199 SharedMem<uint8_t*> ptr;
8200 size_t numBytes;
8201 if (!args[0].isObject() ||
8202 !IsBufferSource(&args[0].toObject(), &ptr, &numBytes)) {
8203 RootedObject callee(cx, &args.callee());
8204 ReportUsageErrorASCII(cx, callee, "Argument must be an ArrayBuffer");
8205 return false;
8206 }
8207
8208 Uint8Vector bytes;
8209 if (!bytes.resize(numBytes)) {
8210 return false;
8211 }
8212
8213 memcpy(bytes.begin(), ptr.unwrap(), numBytes);
8214
8215 RefPtr<StreamCacheEntry> cache =
8216 cx->new_<StreamCacheEntry>(std::move(bytes));
8217 if (!cache) {
8218 return false;
8219 }
8220
8221 RootedNativeObject obj(
8222 cx, NewObjectWithGivenProto<StreamCacheEntryObject>(cx, nullptr));
8223 if (!obj) {
8224 return false;
8225 }
8226 obj->initReservedSlot(CACHE_ENTRY_SLOT,
8227 PrivateValue(cache.forget().take()));
8228
8229 if (!JS_DefineProperty(cx, obj, "cached", cachedGetter, nullptr, 0)) {
8230 return false;
8231 }
8232 if (!JS_DefineFunction(cx, obj, "getBuffer", getBuffer, 0, 0)) {
8233 return false;
8234 }
8235
8236 args.rval().setObject(*obj);
8237 return true;
8238 }
8239
cache() const8240 StreamCacheEntry& cache() const {
8241 return *(StreamCacheEntry*)getReservedSlot(CACHE_ENTRY_SLOT).toPrivate();
8242 }
8243 };
8244
8245 const JSClassOps StreamCacheEntryObject::classOps_ = {
8246 nullptr, // addProperty
8247 nullptr, // delProperty
8248 nullptr, // enumerate
8249 nullptr, // newEnumerate
8250 nullptr, // resolve
8251 nullptr, // mayResolve
8252 StreamCacheEntryObject::finalize, // finalize
8253 nullptr, // call
8254 nullptr, // hasInstance
8255 nullptr, // construct
8256 nullptr, // trace
8257 };
8258
8259 const JSClass StreamCacheEntryObject::class_ = {
8260 "StreamCacheEntryObject",
8261 JSCLASS_HAS_RESERVED_SLOTS(StreamCacheEntryObject::RESERVED_SLOTS) |
8262 JSCLASS_BACKGROUND_FINALIZE,
8263 &StreamCacheEntryObject::classOps_};
8264
8265 struct BufferStreamJob {
8266 Variant<Uint8Vector, StreamCacheEntryPtr> source;
8267 Thread thread;
8268 JS::StreamConsumer* consumer;
8269
BufferStreamJobBufferStreamJob8270 BufferStreamJob(Uint8Vector&& source, JS::StreamConsumer* consumer)
8271 : source(AsVariant<Uint8Vector>(std::move(source))), consumer(consumer) {}
BufferStreamJobBufferStreamJob8272 BufferStreamJob(StreamCacheEntry& source, JS::StreamConsumer* consumer)
8273 : source(AsVariant<StreamCacheEntryPtr>(&source)), consumer(consumer) {}
8274 };
8275
8276 struct BufferStreamState {
8277 Vector<UniquePtr<BufferStreamJob>, 0, SystemAllocPolicy> jobs;
8278 size_t delayMillis;
8279 size_t chunkSize;
8280 bool shutdown;
8281
BufferStreamStateBufferStreamState8282 BufferStreamState() : delayMillis(1), chunkSize(10), shutdown(false) {}
8283
~BufferStreamStateBufferStreamState8284 ~BufferStreamState() { MOZ_ASSERT(jobs.empty()); }
8285 };
8286
8287 static ExclusiveWaitableData<BufferStreamState>* bufferStreamState;
8288
BufferStreamMain(BufferStreamJob * job)8289 static void BufferStreamMain(BufferStreamJob* job) {
8290 const uint8_t* bytes;
8291 size_t byteLength;
8292 JS::OptimizedEncodingListener* listener;
8293 if (job->source.is<StreamCacheEntryPtr>()) {
8294 StreamCacheEntry& cache = *job->source.as<StreamCacheEntryPtr>();
8295 if (cache.hasOptimizedEncoding()) {
8296 const Uint8Vector& optimized = cache.optimizedEncoding();
8297 job->consumer->consumeOptimizedEncoding(optimized.begin(),
8298 optimized.length());
8299 goto done;
8300 }
8301
8302 bytes = cache.bytes().begin();
8303 byteLength = cache.bytes().length();
8304 listener = &cache;
8305 } else {
8306 bytes = job->source.as<Uint8Vector>().begin();
8307 byteLength = job->source.as<Uint8Vector>().length();
8308 listener = nullptr;
8309 }
8310
8311 size_t byteOffset;
8312 byteOffset = 0;
8313 while (true) {
8314 if (byteOffset == byteLength) {
8315 job->consumer->streamEnd(listener);
8316 break;
8317 }
8318
8319 bool shutdown;
8320 size_t delayMillis;
8321 size_t chunkSize;
8322 {
8323 auto state = bufferStreamState->lock();
8324 shutdown = state->shutdown;
8325 delayMillis = state->delayMillis;
8326 chunkSize = state->chunkSize;
8327 }
8328
8329 if (shutdown) {
8330 job->consumer->streamError(JSMSG_STREAM_CONSUME_ERROR);
8331 break;
8332 }
8333
8334 ThisThread::SleepMilliseconds(delayMillis);
8335
8336 chunkSize = std::min(chunkSize, byteLength - byteOffset);
8337
8338 if (!job->consumer->consumeChunk(bytes + byteOffset, chunkSize)) {
8339 break;
8340 }
8341
8342 byteOffset += chunkSize;
8343 }
8344
8345 done:
8346 auto state = bufferStreamState->lock();
8347 size_t jobIndex = 0;
8348 while (state->jobs[jobIndex].get() != job) {
8349 jobIndex++;
8350 }
8351 job->thread.detach(); // quiet assert in ~Thread() called by erase().
8352 state->jobs.erase(state->jobs.begin() + jobIndex);
8353 if (state->jobs.empty()) {
8354 state.notify_all(/* jobs empty */);
8355 }
8356 }
8357
EnsureLatin1CharsLinearString(JSContext * cx,HandleValue value,UniqueChars * result)8358 static bool EnsureLatin1CharsLinearString(JSContext* cx, HandleValue value,
8359 UniqueChars* result) {
8360 if (!value.isString()) {
8361 result->reset(nullptr);
8362 return true;
8363 }
8364 RootedString str(cx, value.toString());
8365 if (!str->isLinear() || !str->hasLatin1Chars()) {
8366 JS_ReportErrorASCII(cx,
8367 "only latin1 chars and linear strings are expected");
8368 return false;
8369 }
8370
8371 // Use JS_EncodeStringToLatin1 to null-terminate.
8372 *result = JS_EncodeStringToLatin1(cx, str);
8373 return !!*result;
8374 }
8375
ConsumeBufferSource(JSContext * cx,JS::HandleObject obj,JS::MimeType,JS::StreamConsumer * consumer)8376 static bool ConsumeBufferSource(JSContext* cx, JS::HandleObject obj,
8377 JS::MimeType, JS::StreamConsumer* consumer) {
8378 {
8379 RootedValue url(cx);
8380 if (!JS_GetProperty(cx, obj, "url", &url)) {
8381 return false;
8382 }
8383 UniqueChars urlChars;
8384 if (!EnsureLatin1CharsLinearString(cx, url, &urlChars)) {
8385 return false;
8386 }
8387
8388 RootedValue mapUrl(cx);
8389 if (!JS_GetProperty(cx, obj, "sourceMappingURL", &mapUrl)) {
8390 return false;
8391 }
8392 UniqueChars mapUrlChars;
8393 if (!EnsureLatin1CharsLinearString(cx, mapUrl, &mapUrlChars)) {
8394 return false;
8395 }
8396
8397 consumer->noteResponseURLs(urlChars.get(), mapUrlChars.get());
8398 }
8399
8400 UniquePtr<BufferStreamJob> job;
8401
8402 SharedMem<uint8_t*> dataPointer;
8403 size_t byteLength;
8404 if (IsBufferSource(obj, &dataPointer, &byteLength)) {
8405 Uint8Vector bytes;
8406 if (!bytes.resize(byteLength)) {
8407 JS_ReportOutOfMemory(cx);
8408 return false;
8409 }
8410
8411 memcpy(bytes.begin(), dataPointer.unwrap(), byteLength);
8412 job = cx->make_unique<BufferStreamJob>(std::move(bytes), consumer);
8413 } else if (obj->is<StreamCacheEntryObject>()) {
8414 job = cx->make_unique<BufferStreamJob>(
8415 obj->as<StreamCacheEntryObject>().cache(), consumer);
8416 } else {
8417 JS_ReportErrorASCII(
8418 cx,
8419 "shell streaming consumes a buffer source (buffer or view) "
8420 "or StreamCacheEntryObject");
8421 return false;
8422 }
8423 if (!job) {
8424 return false;
8425 }
8426
8427 BufferStreamJob* jobPtr = job.get();
8428
8429 {
8430 auto state = bufferStreamState->lock();
8431 MOZ_ASSERT(!state->shutdown);
8432 if (!state->jobs.append(std::move(job))) {
8433 JS_ReportOutOfMemory(cx);
8434 return false;
8435 }
8436 }
8437
8438 {
8439 AutoEnterOOMUnsafeRegion oomUnsafe;
8440 if (!jobPtr->thread.init(BufferStreamMain, jobPtr)) {
8441 oomUnsafe.crash("ConsumeBufferSource");
8442 }
8443 }
8444
8445 return true;
8446 }
8447
ReportStreamError(JSContext * cx,size_t errorNumber)8448 static void ReportStreamError(JSContext* cx, size_t errorNumber) {
8449 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber);
8450 }
8451
SetBufferStreamParams(JSContext * cx,unsigned argc,Value * vp)8452 static bool SetBufferStreamParams(JSContext* cx, unsigned argc, Value* vp) {
8453 CallArgs args = CallArgsFromVp(argc, vp);
8454 if (!args.requireAtLeast(cx, "setBufferStreamParams", 2)) {
8455 return false;
8456 }
8457
8458 double delayMillis;
8459 if (!ToNumber(cx, args[0], &delayMillis)) {
8460 return false;
8461 }
8462
8463 double chunkSize;
8464 if (!ToNumber(cx, args[1], &chunkSize)) {
8465 return false;
8466 }
8467
8468 {
8469 auto state = bufferStreamState->lock();
8470 state->delayMillis = delayMillis;
8471 state->chunkSize = chunkSize;
8472 }
8473
8474 args.rval().setUndefined();
8475 return true;
8476 }
8477
ShutdownBufferStreams()8478 static void ShutdownBufferStreams() {
8479 auto state = bufferStreamState->lock();
8480 state->shutdown = true;
8481 while (!state->jobs.empty()) {
8482 state.wait(/* jobs empty */);
8483 }
8484 state->jobs.clearAndFree();
8485 }
8486
DumpScopeChain(JSContext * cx,unsigned argc,Value * vp)8487 static bool DumpScopeChain(JSContext* cx, unsigned argc, Value* vp) {
8488 CallArgs args = CallArgsFromVp(argc, vp);
8489 RootedObject callee(cx, &args.callee());
8490
8491 if (js::SupportDifferentialTesting()) {
8492 ReportUsageErrorASCII(
8493 cx, callee, "Function not available in differential testing mode.");
8494 return false;
8495 }
8496
8497 if (args.length() != 1) {
8498 ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
8499 return false;
8500 }
8501
8502 if (!args[0].isObject() ||
8503 !(args[0].toObject().is<JSFunction>() ||
8504 args[0].toObject().is<ShellModuleObjectWrapper>())) {
8505 ReportUsageErrorASCII(
8506 cx, callee, "Argument must be an interpreted function or a module");
8507 return false;
8508 }
8509
8510 RootedObject obj(cx, &args[0].toObject());
8511 RootedScript script(cx);
8512
8513 if (obj->is<JSFunction>()) {
8514 RootedFunction fun(cx, &obj->as<JSFunction>());
8515 if (!fun->isInterpreted()) {
8516 ReportUsageErrorASCII(cx, callee,
8517 "Argument must be an interpreted function");
8518 return false;
8519 }
8520 script = JSFunction::getOrCreateScript(cx, fun);
8521 if (!script) {
8522 return false;
8523 }
8524 } else {
8525 script = obj->as<ShellModuleObjectWrapper>().get()->maybeScript();
8526 if (!script) {
8527 JS_ReportErrorASCII(cx, "module does not have an associated script");
8528 return false;
8529 }
8530 }
8531
8532 script->bodyScope()->dump();
8533
8534 args.rval().setUndefined();
8535 return true;
8536 }
8537
8538 // For testing gray marking, grayRoot() will heap-allocate an address
8539 // where we can store a JSObject*, and create a new object if one doesn't
8540 // already exist.
8541 //
8542 // Note that EnsureGrayRoot() will blacken the returned object, so it will not
8543 // actually end up marked gray until the following GC clears the black bit
8544 // (assuming nothing is holding onto it.)
8545 //
8546 // The idea is that you can set up a whole graph of objects to be marked gray,
8547 // hanging off of the object returned from grayRoot(). Then you GC to clear the
8548 // black bits and set the gray bits.
8549 //
8550 // To test grayness, register the objects of interest with addMarkObservers(),
8551 // which takes an Array of objects (which will be marked black at the time
8552 // they're passed in). Their mark bits may be retrieved at any time with
8553 // getMarks(), in the form of an array of strings with each index corresponding
8554 // to the original objects passed to addMarkObservers().
8555
EnsureGrayRoot(JSContext * cx,unsigned argc,Value * vp)8556 static bool EnsureGrayRoot(JSContext* cx, unsigned argc, Value* vp) {
8557 CallArgs args = CallArgsFromVp(argc, vp);
8558
8559 auto priv = EnsureShellCompartmentPrivate(cx);
8560 if (!priv) {
8561 return false;
8562 }
8563
8564 if (!priv->grayRoot) {
8565 if (!(priv->grayRoot = NewTenuredDenseEmptyArray(cx, nullptr))) {
8566 return false;
8567 }
8568 }
8569
8570 // Barrier to enforce the invariant that JS does not touch gray objects.
8571 JSObject* obj = priv->grayRoot;
8572 JS::ExposeObjectToActiveJS(obj);
8573
8574 args.rval().setObject(*obj);
8575 return true;
8576 }
8577
EnsureMarkBitObservers(JSContext * cx)8578 static MarkBitObservers* EnsureMarkBitObservers(JSContext* cx) {
8579 ShellContext* sc = GetShellContext(cx);
8580 if (!sc->markObservers) {
8581 auto* observers =
8582 cx->new_<MarkBitObservers>(cx->runtime(), NonshrinkingGCObjectVector());
8583 if (!observers) {
8584 return nullptr;
8585 }
8586 sc->markObservers.reset(observers);
8587 }
8588 return sc->markObservers.get();
8589 }
8590
ClearMarkObservers(JSContext * cx,unsigned argc,Value * vp)8591 static bool ClearMarkObservers(JSContext* cx, unsigned argc, Value* vp) {
8592 CallArgs args = CallArgsFromVp(argc, vp);
8593
8594 auto markObservers = EnsureMarkBitObservers(cx);
8595 if (!markObservers) {
8596 return false;
8597 }
8598
8599 markObservers->get().clear();
8600
8601 args.rval().setUndefined();
8602 return true;
8603 }
8604
AddMarkObservers(JSContext * cx,unsigned argc,Value * vp)8605 static bool AddMarkObservers(JSContext* cx, unsigned argc, Value* vp) {
8606 CallArgs args = CallArgsFromVp(argc, vp);
8607
8608 auto markObservers = EnsureMarkBitObservers(cx);
8609 if (!markObservers) {
8610 return false;
8611 }
8612
8613 if (!args.get(0).isObject()) {
8614 JS_ReportErrorASCII(cx, "argument must be an Array of objects");
8615 return false;
8616 }
8617
8618 RootedObject observersArg(cx, &args[0].toObject());
8619 uint64_t length;
8620 if (!GetLengthProperty(cx, observersArg, &length)) {
8621 return false;
8622 }
8623
8624 if (length > UINT32_MAX) {
8625 JS_ReportErrorASCII(cx, "Invalid length for observers array");
8626 return false;
8627 }
8628
8629 RootedValue value(cx);
8630 RootedObject object(cx);
8631 for (uint32_t i = 0; i < length; i++) {
8632 if (!JS_GetElement(cx, observersArg, i, &value)) {
8633 return false;
8634 }
8635
8636 if (!value.isObject()) {
8637 JS_ReportErrorASCII(cx, "argument must be an Array of objects");
8638 return false;
8639 }
8640
8641 object = &value.toObject();
8642 if (gc::IsInsideNursery(object)) {
8643 // WeakCaches are not swept during a minor GC. To prevent
8644 // nursery-allocated contents from having the mark bits be deceptively
8645 // black until the second GC, they would need to be marked weakly (cf
8646 // NurseryAwareHashMap). It is simpler to evict the nursery to prevent
8647 // nursery objects from being observed.
8648 cx->runtime()->gc.evictNursery();
8649 }
8650
8651 if (!markObservers->get().append(object)) {
8652 return false;
8653 }
8654 }
8655
8656 args.rval().setInt32(length);
8657 return true;
8658 }
8659
GetMarks(JSContext * cx,unsigned argc,Value * vp)8660 static bool GetMarks(JSContext* cx, unsigned argc, Value* vp) {
8661 CallArgs args = CallArgsFromVp(argc, vp);
8662
8663 auto& observers = GetShellContext(cx)->markObservers;
8664 if (!observers) {
8665 args.rval().setUndefined();
8666 return true;
8667 }
8668
8669 size_t length = observers->get().length();
8670 Rooted<ArrayObject*> ret(cx, js::NewDenseEmptyArray(cx));
8671 if (!ret) {
8672 return false;
8673 }
8674
8675 for (uint32_t i = 0; i < length; i++) {
8676 const char* color;
8677 JSObject* obj = observers->get()[i];
8678 if (!obj) {
8679 color = "dead";
8680 } else {
8681 gc::TenuredCell* cell = &obj->asTenured();
8682 if (cell->isMarkedGray()) {
8683 color = "gray";
8684 } else if (cell->isMarkedBlack()) {
8685 color = "black";
8686 } else {
8687 color = "unmarked";
8688 }
8689 }
8690 JSString* s = JS_NewStringCopyZ(cx, color);
8691 if (!s) {
8692 return false;
8693 }
8694 if (!NewbornArrayPush(cx, ret, StringValue(s))) {
8695 return false;
8696 }
8697 }
8698
8699 args.rval().setObject(*ret);
8700 return true;
8701 }
8702
8703 namespace js {
8704 namespace shell {
8705
8706 class ShellAutoEntryMonitor : JS::dbg::AutoEntryMonitor {
8707 Vector<UniqueChars, 1, js::SystemAllocPolicy> log;
8708 bool oom;
8709 bool enteredWithoutExit;
8710
8711 public:
ShellAutoEntryMonitor(JSContext * cx)8712 explicit ShellAutoEntryMonitor(JSContext* cx)
8713 : AutoEntryMonitor(cx), oom(false), enteredWithoutExit(false) {}
8714
~ShellAutoEntryMonitor()8715 ~ShellAutoEntryMonitor() { MOZ_ASSERT(!enteredWithoutExit); }
8716
Entry(JSContext * cx,JSFunction * function,JS::HandleValue asyncStack,const char * asyncCause)8717 void Entry(JSContext* cx, JSFunction* function, JS::HandleValue asyncStack,
8718 const char* asyncCause) override {
8719 MOZ_ASSERT(!enteredWithoutExit);
8720 enteredWithoutExit = true;
8721
8722 RootedString displayId(cx, JS_GetFunctionDisplayId(function));
8723 if (displayId) {
8724 UniqueChars displayIdStr = JS_EncodeStringToUTF8(cx, displayId);
8725 if (!displayIdStr) {
8726 // We report OOM in buildResult.
8727 cx->recoverFromOutOfMemory();
8728 oom = true;
8729 return;
8730 }
8731 oom = !log.append(std::move(displayIdStr));
8732 return;
8733 }
8734
8735 oom = !log.append(DuplicateString("anonymous"));
8736 }
8737
Entry(JSContext * cx,JSScript * script,JS::HandleValue asyncStack,const char * asyncCause)8738 void Entry(JSContext* cx, JSScript* script, JS::HandleValue asyncStack,
8739 const char* asyncCause) override {
8740 MOZ_ASSERT(!enteredWithoutExit);
8741 enteredWithoutExit = true;
8742
8743 UniqueChars label(JS_smprintf("eval:%s", JS_GetScriptFilename(script)));
8744 oom = !label || !log.append(std::move(label));
8745 }
8746
Exit(JSContext * cx)8747 void Exit(JSContext* cx) override {
8748 MOZ_ASSERT(enteredWithoutExit);
8749 enteredWithoutExit = false;
8750 }
8751
buildResult(JSContext * cx,MutableHandleValue resultValue)8752 bool buildResult(JSContext* cx, MutableHandleValue resultValue) {
8753 if (oom) {
8754 JS_ReportOutOfMemory(cx);
8755 return false;
8756 }
8757
8758 RootedObject result(cx, JS::NewArrayObject(cx, log.length()));
8759 if (!result) {
8760 return false;
8761 }
8762
8763 for (size_t i = 0; i < log.length(); i++) {
8764 char* name = log[i].get();
8765 RootedString string(cx, Atomize(cx, name, strlen(name)));
8766 if (!string) {
8767 return false;
8768 }
8769 RootedValue value(cx, StringValue(string));
8770 if (!JS_SetElement(cx, result, i, value)) {
8771 return false;
8772 }
8773 }
8774
8775 resultValue.setObject(*result.get());
8776 return true;
8777 }
8778 };
8779
8780 } // namespace shell
8781 } // namespace js
8782
EntryPoints(JSContext * cx,unsigned argc,Value * vp)8783 static bool EntryPoints(JSContext* cx, unsigned argc, Value* vp) {
8784 CallArgs args = CallArgsFromVp(argc, vp);
8785
8786 if (args.length() != 1) {
8787 JS_ReportErrorASCII(cx, "Wrong number of arguments");
8788 return false;
8789 }
8790
8791 RootedObject opts(cx, ToObject(cx, args[0]));
8792 if (!opts) {
8793 return false;
8794 }
8795
8796 // { function: f } --- Call f.
8797 {
8798 RootedValue fun(cx), dummy(cx);
8799
8800 if (!JS_GetProperty(cx, opts, "function", &fun)) {
8801 return false;
8802 }
8803 if (!fun.isUndefined()) {
8804 js::shell::ShellAutoEntryMonitor sarep(cx);
8805 if (!Call(cx, UndefinedHandleValue, fun, JS::HandleValueArray::empty(),
8806 &dummy)) {
8807 return false;
8808 }
8809 return sarep.buildResult(cx, args.rval());
8810 }
8811 }
8812
8813 // { object: o, property: p, value: v } --- Fetch o[p], or if
8814 // v is present, assign o[p] = v.
8815 {
8816 RootedValue objectv(cx), propv(cx), valuev(cx);
8817
8818 if (!JS_GetProperty(cx, opts, "object", &objectv) ||
8819 !JS_GetProperty(cx, opts, "property", &propv))
8820 return false;
8821 if (!objectv.isUndefined() && !propv.isUndefined()) {
8822 RootedObject object(cx, ToObject(cx, objectv));
8823 if (!object) {
8824 return false;
8825 }
8826
8827 RootedString string(cx, ToString(cx, propv));
8828 if (!string) {
8829 return false;
8830 }
8831 RootedId id(cx);
8832 if (!JS_StringToId(cx, string, &id)) {
8833 return false;
8834 }
8835
8836 if (!JS_GetProperty(cx, opts, "value", &valuev)) {
8837 return false;
8838 }
8839
8840 js::shell::ShellAutoEntryMonitor sarep(cx);
8841
8842 if (!valuev.isUndefined()) {
8843 if (!JS_SetPropertyById(cx, object, id, valuev)) {
8844 return false;
8845 }
8846 } else {
8847 if (!JS_GetPropertyById(cx, object, id, &valuev)) {
8848 return false;
8849 }
8850 }
8851
8852 return sarep.buildResult(cx, args.rval());
8853 }
8854 }
8855
8856 // { ToString: v } --- Apply JS::ToString to v.
8857 {
8858 RootedValue v(cx);
8859
8860 if (!JS_GetProperty(cx, opts, "ToString", &v)) {
8861 return false;
8862 }
8863 if (!v.isUndefined()) {
8864 js::shell::ShellAutoEntryMonitor sarep(cx);
8865 if (!JS::ToString(cx, v)) {
8866 return false;
8867 }
8868 return sarep.buildResult(cx, args.rval());
8869 }
8870 }
8871
8872 // { ToNumber: v } --- Apply JS::ToNumber to v.
8873 {
8874 RootedValue v(cx);
8875 double dummy;
8876
8877 if (!JS_GetProperty(cx, opts, "ToNumber", &v)) {
8878 return false;
8879 }
8880 if (!v.isUndefined()) {
8881 js::shell::ShellAutoEntryMonitor sarep(cx);
8882 if (!JS::ToNumber(cx, v, &dummy)) {
8883 return false;
8884 }
8885 return sarep.buildResult(cx, args.rval());
8886 }
8887 }
8888
8889 // { eval: code } --- Apply ToString and then Evaluate to code.
8890 {
8891 RootedValue code(cx), dummy(cx);
8892
8893 if (!JS_GetProperty(cx, opts, "eval", &code)) {
8894 return false;
8895 }
8896 if (!code.isUndefined()) {
8897 RootedString codeString(cx, ToString(cx, code));
8898 if (!codeString) {
8899 return false;
8900 }
8901
8902 AutoStableStringChars stableChars(cx);
8903 if (!stableChars.initTwoByte(cx, codeString)) {
8904 return false;
8905 }
8906 JS::SourceText<char16_t> srcBuf;
8907 if (!srcBuf.init(cx, stableChars.twoByteRange().begin().get(),
8908 codeString->length(), JS::SourceOwnership::Borrowed)) {
8909 return false;
8910 }
8911
8912 CompileOptions options(cx);
8913 options.setIntroductionType("entryPoint eval")
8914 .setFileAndLine("entryPoint eval", 1);
8915
8916 js::shell::ShellAutoEntryMonitor sarep(cx);
8917 if (!JS::Evaluate(cx, options, srcBuf, &dummy)) {
8918 return false;
8919 }
8920 return sarep.buildResult(cx, args.rval());
8921 }
8922 }
8923
8924 JS_ReportErrorASCII(cx, "bad 'params' object");
8925 return false;
8926 }
8927
8928 #ifndef __wasi__
WasmTextToBinary(JSContext * cx,unsigned argc,Value * vp)8929 static bool WasmTextToBinary(JSContext* cx, unsigned argc, Value* vp) {
8930 CallArgs args = CallArgsFromVp(argc, vp);
8931 RootedObject callee(cx, &args.callee());
8932
8933 if (!args.requireAtLeast(cx, "wasmTextToBinary", 1)) {
8934 return false;
8935 }
8936
8937 if (!args[0].isString()) {
8938 ReportUsageErrorASCII(cx, callee, "First argument must be a String");
8939 return false;
8940 }
8941
8942 size_t textLen = args[0].toString()->length();
8943
8944 AutoStableStringChars twoByteChars(cx);
8945 if (!twoByteChars.initTwoByte(cx, args[0].toString())) {
8946 return false;
8947 }
8948
8949 wasm::Bytes bytes;
8950 UniqueChars error;
8951 if (!wasm::TextToBinary(twoByteChars.twoByteChars(), textLen, &bytes,
8952 &error)) {
8953 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_TEXT_FAIL,
8954 error.get() ? error.get() : "out of memory");
8955 return false;
8956 }
8957
8958 RootedObject binary(cx, JS_NewUint8Array(cx, bytes.length()));
8959 if (!binary) {
8960 return false;
8961 }
8962
8963 memcpy(binary->as<TypedArrayObject>().dataPointerUnshared(), bytes.begin(),
8964 bytes.length());
8965
8966 args.rval().setObject(*binary);
8967 return true;
8968 }
8969
WasmCodeOffsets(JSContext * cx,unsigned argc,Value * vp)8970 static bool WasmCodeOffsets(JSContext* cx, unsigned argc, Value* vp) {
8971 CallArgs args = CallArgsFromVp(argc, vp);
8972 RootedObject callee(cx, &args.callee());
8973
8974 if (!args.requireAtLeast(cx, "wasmCodeOffsets", 1)) {
8975 return false;
8976 }
8977
8978 if (!args.get(0).isObject()) {
8979 JS_ReportErrorASCII(cx, "argument is not an object");
8980 return false;
8981 }
8982
8983 SharedMem<uint8_t*> bytes;
8984 size_t byteLength;
8985
8986 JSObject* bufferObject = &args[0].toObject();
8987 JSObject* unwrappedBufferObject = CheckedUnwrapStatic(bufferObject);
8988 if (!unwrappedBufferObject ||
8989 !IsBufferSource(unwrappedBufferObject, &bytes, &byteLength)) {
8990 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
8991 JSMSG_WASM_BAD_BUF_ARG);
8992 return false;
8993 }
8994
8995 wasm::Uint32Vector offsets;
8996 wasm::CodeOffsets(bytes.unwrap(), byteLength, &offsets);
8997
8998 RootedObject jsOffsets(cx, JS::NewArrayObject(cx, offsets.length()));
8999 if (!jsOffsets) {
9000 return false;
9001 }
9002 for (size_t i = 0; i < offsets.length(); i++) {
9003 uint32_t offset = offsets[i];
9004 RootedValue offsetVal(cx, NumberValue(offset));
9005 if (!JS_SetElement(cx, jsOffsets, i, offsetVal)) {
9006 return false;
9007 }
9008 }
9009 args.rval().setObject(*jsOffsets);
9010 return true;
9011 }
9012
9013 # ifndef __AFL_HAVE_MANUAL_CONTROL
9014 # define __AFL_LOOP(x) true
9015 # endif
9016
WasmLoop(JSContext * cx,unsigned argc,Value * vp)9017 static bool WasmLoop(JSContext* cx, unsigned argc, Value* vp) {
9018 CallArgs args = CallArgsFromVp(argc, vp);
9019 RootedObject callee(cx, &args.callee());
9020
9021 if (args.length() < 1 || args.length() > 2) {
9022 ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
9023 return false;
9024 }
9025
9026 if (!args[0].isString()) {
9027 ReportUsageErrorASCII(cx, callee, "First argument must be a String");
9028 return false;
9029 }
9030
9031 RootedObject importObj(cx);
9032 if (!args.get(1).isUndefined()) {
9033 if (!args.get(1).isObject()) {
9034 ReportUsageErrorASCII(cx, callee,
9035 "Second argument, if present, must be an Object");
9036 return false;
9037 }
9038 importObj = &args[1].toObject();
9039 }
9040
9041 RootedString givenPath(cx, args[0].toString());
9042 RootedString filename(cx, ResolvePath(cx, givenPath, RootRelative));
9043 if (!filename) {
9044 return false;
9045 }
9046
9047 while (__AFL_LOOP(1000)) {
9048 Rooted<JSObject*> ret(cx, FileAsTypedArray(cx, filename));
9049 if (!ret) {
9050 return false;
9051 }
9052
9053 Rooted<TypedArrayObject*> typedArray(cx, &ret->as<TypedArrayObject>());
9054 RootedWasmInstanceObject instanceObj(cx);
9055 // No additional compile options here, we don't need them for this use case.
9056 RootedValue maybeOptions(cx);
9057 if (!wasm::Eval(cx, typedArray, importObj, maybeOptions, &instanceObj)) {
9058 // Clear any pending exceptions, we don't care about them
9059 cx->clearPendingException();
9060 }
9061 }
9062
9063 # ifdef __AFL_HAVE_MANUAL_CONTROL // to silence unreachable code warning
9064 return true;
9065 # endif
9066 }
9067 #endif // __wasi__
9068
9069 static constexpr uint32_t DOM_OBJECT_SLOT = 0;
9070 static constexpr uint32_t DOM_OBJECT_SLOT2 = 1;
9071
9072 static const JSClass* GetDomClass();
9073
9074 static JSObject* GetDOMPrototype(JSContext* cx, JSObject* global);
9075
9076 static const JSClass TransplantableDOMObjectClass = {
9077 "TransplantableDOMObject",
9078 JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(1)};
9079
9080 static const JSClass TransplantableDOMProxyObjectClass =
9081 PROXY_CLASS_DEF("TransplantableDOMProxyObject",
9082 JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(1));
9083
9084 class TransplantableDOMProxyHandler final : public ForwardingProxyHandler {
9085 public:
9086 static const TransplantableDOMProxyHandler singleton;
9087 static const char family;
9088
TransplantableDOMProxyHandler()9089 constexpr TransplantableDOMProxyHandler() : ForwardingProxyHandler(&family) {}
9090
9091 // These two proxy traps are called in |js::DeadProxyTargetValue|, which in
9092 // turn is called when nuking proxies. Because this proxy can temporarily be
9093 // without an object in its private slot, see |EnsureExpandoObject|, the
9094 // default implementation inherited from ForwardingProxyHandler can't be used,
9095 // since it tries to derive the callable/constructible value from the target.
isCallable(JSObject * obj) const9096 bool isCallable(JSObject* obj) const override { return false; }
isConstructor(JSObject * obj) const9097 bool isConstructor(JSObject* obj) const override { return false; }
9098
9099 // Simplified implementation of |DOMProxyHandler::GetAndClearExpandoObject|.
GetAndClearExpandoObject(JSObject * obj)9100 static JSObject* GetAndClearExpandoObject(JSObject* obj) {
9101 const Value& v = GetProxyPrivate(obj);
9102 if (v.isUndefined()) {
9103 return nullptr;
9104 }
9105
9106 JSObject* expandoObject = &v.toObject();
9107 SetProxyPrivate(obj, UndefinedValue());
9108 return expandoObject;
9109 }
9110
9111 // Simplified implementation of |DOMProxyHandler::EnsureExpandoObject|.
EnsureExpandoObject(JSContext * cx,JS::HandleObject obj)9112 static JSObject* EnsureExpandoObject(JSContext* cx, JS::HandleObject obj) {
9113 const Value& v = GetProxyPrivate(obj);
9114 if (v.isObject()) {
9115 return &v.toObject();
9116 }
9117 MOZ_ASSERT(v.isUndefined());
9118
9119 JSObject* expando = JS_NewObjectWithGivenProto(cx, nullptr, nullptr);
9120 if (!expando) {
9121 return nullptr;
9122 }
9123 SetProxyPrivate(obj, ObjectValue(*expando));
9124 return expando;
9125 }
9126 };
9127
9128 const TransplantableDOMProxyHandler TransplantableDOMProxyHandler::singleton;
9129 const char TransplantableDOMProxyHandler::family = 0;
9130
9131 enum TransplantObjectSlots {
9132 TransplantSourceObject = 0,
9133 };
9134
TransplantObject(JSContext * cx,unsigned argc,Value * vp)9135 static bool TransplantObject(JSContext* cx, unsigned argc, Value* vp) {
9136 CallArgs args = CallArgsFromVp(argc, vp);
9137 RootedFunction callee(cx, &args.callee().as<JSFunction>());
9138
9139 if (args.length() != 1 || !args[0].isObject()) {
9140 JS_ReportErrorASCII(cx, "transplant() must be called with an object");
9141 return false;
9142 }
9143
9144 // |newGlobal| needs to be a GlobalObject.
9145 RootedObject newGlobal(
9146 cx, js::CheckedUnwrapDynamic(&args[0].toObject(), cx,
9147 /* stopAtWindowProxy = */ false));
9148 if (!newGlobal) {
9149 ReportAccessDenied(cx);
9150 return false;
9151 }
9152 if (!JS_IsGlobalObject(newGlobal)) {
9153 JS_ReportErrorNumberASCII(
9154 cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
9155 "\"global\" passed to transplant()", "not a global object");
9156 return false;
9157 }
9158
9159 const Value& reserved =
9160 GetFunctionNativeReserved(callee, TransplantSourceObject);
9161 RootedObject source(cx, CheckedUnwrapStatic(&reserved.toObject()));
9162 if (!source) {
9163 ReportAccessDenied(cx);
9164 return false;
9165 }
9166 MOZ_ASSERT(source->getClass()->isDOMClass());
9167
9168 // The following steps aim to replicate the behavior of UpdateReflectorGlobal
9169 // in dom/bindings/BindingUtils.cpp. In detail:
9170 // 1. Check the recursion depth using checkConservative.
9171 // 2. Enter the target compartment.
9172 // 3. Clone the source object using JS_CloneObject.
9173 // 4. Check if new wrappers can be created if source and target are in
9174 // different compartments.
9175 // 5. Copy all properties from source to a temporary holder object.
9176 // 6. Actually transplant the object.
9177 // 7. And finally copy the properties back to the source object.
9178 //
9179 // As an extension to the algorithm in UpdateReflectorGlobal, we also allow
9180 // to transplant an object into the same compartment as the source object to
9181 // cover all operations supported by JS_TransplantObject.
9182
9183 AutoCheckRecursionLimit recursion(cx);
9184 if (!recursion.checkConservative(cx)) {
9185 return false;
9186 }
9187
9188 bool isProxy = IsProxy(source);
9189 RootedObject expandoObject(cx);
9190 if (isProxy) {
9191 expandoObject =
9192 TransplantableDOMProxyHandler::GetAndClearExpandoObject(source);
9193 }
9194
9195 JSAutoRealm ar(cx, newGlobal);
9196
9197 RootedObject proto(cx);
9198 if (JS::GetClass(source) == GetDomClass()) {
9199 proto = GetDOMPrototype(cx, newGlobal);
9200 } else {
9201 proto = JS::GetRealmObjectPrototype(cx);
9202 }
9203 if (!proto) {
9204 return false;
9205 }
9206
9207 RootedObject target(cx, JS_CloneObject(cx, source, proto));
9208 if (!target) {
9209 return false;
9210 }
9211
9212 if (JS::GetCompartment(source) != JS::GetCompartment(target) &&
9213 !AllowNewWrapper(JS::GetCompartment(source), target)) {
9214 JS_ReportErrorASCII(cx, "Cannot transplant into nuked compartment");
9215 return false;
9216 }
9217
9218 RootedObject copyFrom(cx, isProxy ? expandoObject : source);
9219 RootedObject propertyHolder(cx,
9220 JS_NewObjectWithGivenProto(cx, nullptr, nullptr));
9221 if (!propertyHolder) {
9222 return false;
9223 }
9224
9225 if (!JS_CopyOwnPropertiesAndPrivateFields(cx, propertyHolder, copyFrom)) {
9226 return false;
9227 }
9228
9229 JS::SetReservedSlot(target, DOM_OBJECT_SLOT,
9230 JS::GetReservedSlot(source, DOM_OBJECT_SLOT));
9231 JS::SetReservedSlot(source, DOM_OBJECT_SLOT, JS::PrivateValue(nullptr));
9232 if (JS::GetClass(source) == GetDomClass()) {
9233 JS::SetReservedSlot(target, DOM_OBJECT_SLOT2,
9234 JS::GetReservedSlot(source, DOM_OBJECT_SLOT2));
9235 JS::SetReservedSlot(source, DOM_OBJECT_SLOT2, UndefinedValue());
9236 }
9237
9238 source = JS_TransplantObject(cx, source, target);
9239 if (!source) {
9240 return false;
9241 }
9242
9243 RootedObject copyTo(cx);
9244 if (isProxy) {
9245 copyTo = TransplantableDOMProxyHandler::EnsureExpandoObject(cx, source);
9246 if (!copyTo) {
9247 return false;
9248 }
9249 } else {
9250 copyTo = source;
9251 }
9252 if (!JS_CopyOwnPropertiesAndPrivateFields(cx, copyTo, propertyHolder)) {
9253 return false;
9254 }
9255
9256 args.rval().setUndefined();
9257 return true;
9258 }
9259
TransplantableObject(JSContext * cx,unsigned argc,Value * vp)9260 static bool TransplantableObject(JSContext* cx, unsigned argc, Value* vp) {
9261 CallArgs args = CallArgsFromVp(argc, vp);
9262 RootedObject callee(cx, &args.callee());
9263
9264 if (args.length() > 1) {
9265 ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
9266 return false;
9267 }
9268
9269 bool createProxy = false;
9270 RootedObject source(cx);
9271 if (args.length() == 1 && !args[0].isUndefined()) {
9272 if (!args[0].isObject()) {
9273 ReportUsageErrorASCII(cx, callee, "Argument must be an object");
9274 return false;
9275 }
9276
9277 RootedObject options(cx, &args[0].toObject());
9278 RootedValue value(cx);
9279
9280 if (!JS_GetProperty(cx, options, "proxy", &value)) {
9281 return false;
9282 }
9283 createProxy = JS::ToBoolean(value);
9284
9285 if (!JS_GetProperty(cx, options, "object", &value)) {
9286 return false;
9287 }
9288 if (!value.isUndefined()) {
9289 if (!value.isObject()) {
9290 ReportUsageErrorASCII(cx, callee, "'object' option must be an object");
9291 return false;
9292 }
9293
9294 source = &value.toObject();
9295 if (JS::GetClass(source) != GetDomClass()) {
9296 ReportUsageErrorASCII(cx, callee, "Object not a FakeDOMObject");
9297 return false;
9298 }
9299
9300 // |source| must be a tenured object to be transplantable.
9301 if (gc::IsInsideNursery(source)) {
9302 JS_GC(cx);
9303
9304 MOZ_ASSERT(!gc::IsInsideNursery(source),
9305 "Live objects should be tenured after one GC, because "
9306 "the nursery has only a single generation");
9307 }
9308 }
9309 }
9310
9311 if (!source) {
9312 if (!createProxy) {
9313 source = NewBuiltinClassInstance(cx, &TransplantableDOMObjectClass,
9314 TenuredObject);
9315 if (!source) {
9316 return false;
9317 }
9318
9319 JS::SetReservedSlot(source, DOM_OBJECT_SLOT, JS::PrivateValue(nullptr));
9320 } else {
9321 JSObject* expando = JS_NewPlainObject(cx);
9322 if (!expando) {
9323 return false;
9324 }
9325 RootedValue expandoVal(cx, ObjectValue(*expando));
9326
9327 ProxyOptions options;
9328 options.setClass(&TransplantableDOMProxyObjectClass);
9329 options.setLazyProto(true);
9330
9331 source = NewProxyObject(cx, &TransplantableDOMProxyHandler::singleton,
9332 expandoVal, nullptr, options);
9333 if (!source) {
9334 return false;
9335 }
9336
9337 SetProxyReservedSlot(source, DOM_OBJECT_SLOT, JS::PrivateValue(nullptr));
9338 }
9339 }
9340
9341 jsid emptyId = NameToId(cx->names().empty);
9342 RootedObject transplant(
9343 cx, NewFunctionByIdWithReserved(cx, TransplantObject, 0, 0, emptyId));
9344 if (!transplant) {
9345 return false;
9346 }
9347
9348 SetFunctionNativeReserved(transplant, TransplantSourceObject,
9349 ObjectValue(*source));
9350
9351 RootedObject result(cx, JS_NewPlainObject(cx));
9352 if (!result) {
9353 return false;
9354 }
9355
9356 RootedValue sourceVal(cx, ObjectValue(*source));
9357 RootedValue transplantVal(cx, ObjectValue(*transplant));
9358 if (!JS_DefineProperty(cx, result, "object", sourceVal, 0) ||
9359 !JS_DefineProperty(cx, result, "transplant", transplantVal, 0)) {
9360 return false;
9361 }
9362
9363 args.rval().setObject(*result);
9364 return true;
9365 }
9366
9367 // clang-format off
9368 static const JSFunctionSpecWithHelp shell_functions[] = {
9369 JS_FN_HELP("options", Options, 0, 0,
9370 "options([option ...])",
9371 " Get or toggle JavaScript options."),
9372
9373 JS_FN_HELP("load", Load, 1, 0,
9374 "load(['foo.js' ...])",
9375 " Load files named by string arguments. Filename is relative to the\n"
9376 " current working directory."),
9377
9378 JS_FN_HELP("loadRelativeToScript", LoadScriptRelativeToScript, 1, 0,
9379 "loadRelativeToScript(['foo.js' ...])",
9380 " Load files named by string arguments. Filename is relative to the\n"
9381 " calling script."),
9382
9383 JS_FN_HELP("evaluate", Evaluate, 2, 0,
9384 "evaluate(code[, options])",
9385 " Evaluate code as though it were the contents of a file.\n"
9386 " options is an optional object that may have these properties:\n"
9387 " isRunOnce: use the isRunOnce compiler option (default: false)\n"
9388 " noScriptRval: use the no-script-rval compiler option (default: false)\n"
9389 " fileName: filename for error messages and debug info\n"
9390 " skipFileNameValidation: skip the filename-validation callback\n"
9391 " lineNumber: starting line number for error messages and debug info\n"
9392 " columnNumber: starting column number for error messages and debug info\n"
9393 " global: global in which to execute the code\n"
9394 " newContext: if true, create and use a new cx (default: false)\n"
9395 " catchTermination: if true, catch termination (failure without\n"
9396 " an exception value, as for slow scripts or out-of-memory)\n"
9397 " and return 'terminated'\n"
9398 " element: if present with value |v|, convert |v| to an object |o| and\n"
9399 " mark the source as being attached to the DOM element |o|. If the\n"
9400 " property is omitted or |v| is null, don't attribute the source to\n"
9401 " any DOM element.\n"
9402 " elementAttributeName: if present and not undefined, the name of\n"
9403 " property of 'element' that holds this code. This is what\n"
9404 " Debugger.Source.prototype.elementAttributeName returns.\n"
9405 " sourceMapURL: if present with value |v|, convert |v| to a string, and\n"
9406 " provide that as the code's source map URL. If omitted, attach no\n"
9407 " source map URL to the code (although the code may provide one itself,\n"
9408 " via a //#sourceMappingURL comment).\n"
9409 " sourceIsLazy: if present and true, indicates that, after compilation, \n"
9410 " script source should not be cached by the JS engine and should be \n"
9411 " lazily loaded from the embedding as-needed.\n"
9412 " forceFullParse: if present and true, disable syntax-parse.\n"
9413 " loadBytecode: if true, and if the source is a CacheEntryObject,\n"
9414 " the bytecode would be loaded and decoded from the cache entry instead\n"
9415 " of being parsed, then it would be executed as usual.\n"
9416 " saveBytecode: if true, and if the source is a CacheEntryObject,\n"
9417 " the bytecode would be encoded and saved into the cache entry after\n"
9418 " the script execution.\n"
9419 " The encoded bytecode's kind is 'script'\n"
9420 " saveIncrementalBytecode: if true, and if the source is a\n"
9421 " CacheEntryObject, the bytecode would be incrementally encoded and\n"
9422 " saved into the cache entry.\n"
9423 " If --off-thread-parse-global is not used, the encoded bytecode's\n"
9424 " kind is 'stencil'. If not, the encoded bytecode's kind is 'script'\n"
9425 " If both loadBytecode and saveIncrementalBytecode are set,\n"
9426 " and --off-thread-parse-global is not used, the input bytecode's\n"
9427 " kind should be 'stencil'."
9428 " transcodeOnly: if true, do not execute the script.\n"
9429 " assertEqBytecode: if true, and if both loadBytecode and either\n"
9430 " saveBytecode or saveIncrementalBytecode is true, then the loaded\n"
9431 " bytecode and the encoded bytecode are compared.\n"
9432 " and an assertion is raised if they differ.\n"
9433 " envChainObject: object to put on the scope chain, with its fields added\n"
9434 " as var bindings, akin to how elements are added to the environment in\n"
9435 " event handlers in Gecko.\n"
9436 ),
9437
9438 JS_FN_HELP("run", Run, 1, 0,
9439 "run('foo.js')",
9440 " Run the file named by the first argument, returning the number of\n"
9441 " of milliseconds spent compiling and executing it."),
9442
9443 JS_FN_HELP("readline", ReadLine, 0, 0,
9444 "readline()",
9445 " Read a single line from stdin."),
9446
9447 JS_FN_HELP("readlineBuf", ReadLineBuf, 1, 0,
9448 "readlineBuf([ buf ])",
9449 " Emulate readline() on the specified string. The first call with a string\n"
9450 " argument sets the source buffer. Subsequent calls without an argument\n"
9451 " then read from this buffer line by line.\n"),
9452
9453 JS_FN_HELP("print", Print, 0, 0,
9454 "print([exp ...])",
9455 " Evaluate and print expressions to stdout."),
9456
9457 JS_FN_HELP("printErr", PrintErr, 0, 0,
9458 "printErr([exp ...])",
9459 " Evaluate and print expressions to stderr."),
9460
9461 JS_FN_HELP("putstr", PutStr, 0, 0,
9462 "putstr([exp])",
9463 " Evaluate and print expression without newline."),
9464
9465 JS_FN_HELP("dateNow", Now, 0, 0,
9466 "dateNow()",
9467 " Return the current time with sub-ms precision."),
9468
9469 JS_FN_HELP("help", Help, 0, 0,
9470 "help([function or interface object or /pattern/])",
9471 " Display usage and help messages."),
9472
9473 JS_FN_HELP("quit", Quit, 0, 0,
9474 "quit()",
9475 " Quit the shell."),
9476
9477 JS_FN_HELP("assertEq", AssertEq, 2, 0,
9478 "assertEq(actual, expected[, msg])",
9479 " Throw if the first two arguments are not the same (both +0 or both -0,\n"
9480 " both NaN, or non-zero and ===)."),
9481
9482 JS_FN_HELP("startTimingMutator", StartTimingMutator, 0, 0,
9483 "startTimingMutator()",
9484 " Start accounting time to mutator vs GC."),
9485
9486 JS_FN_HELP("stopTimingMutator", StopTimingMutator, 0, 0,
9487 "stopTimingMutator()",
9488 " Stop accounting time to mutator vs GC and dump the results."),
9489
9490 JS_FN_HELP("throwError", ThrowError, 0, 0,
9491 "throwError()",
9492 " Throw an error from JS_ReportError."),
9493
9494 JS_FN_HELP("createErrorReport", CreateErrorReport, 1, 0,
9495 "createErrorReport(value)",
9496 " Create an JS::ErrorReportBuilder object from the given value and serialize\n"
9497 " to an object."),
9498
9499 #if defined(DEBUG) || defined(JS_JITSPEW)
9500 JS_FN_HELP("disassemble", DisassembleToString, 1, 0,
9501 "disassemble([fun/code])",
9502 " Return the disassembly for the given function or code.\n"
9503 " All disassembly functions take these options as leading string arguments:\n"
9504 " \"-r\" (disassemble recursively)\n"
9505 " \"-l\" (show line numbers)\n"
9506 " \"-S\" (omit source notes)"),
9507
9508 JS_FN_HELP("dis", Disassemble, 1, 0,
9509 "dis([fun/code])",
9510 " Disassemble functions into bytecodes."),
9511
9512 JS_FN_HELP("disfile", DisassFile, 1, 0,
9513 "disfile('foo.js')",
9514 " Disassemble script file into bytecodes.\n"),
9515
9516 JS_FN_HELP("dissrc", DisassWithSrc, 1, 0,
9517 "dissrc([fun/code])",
9518 " Disassemble functions with source lines."),
9519
9520 JS_FN_HELP("notes", Notes, 1, 0,
9521 "notes([fun])",
9522 " Show source notes for functions."),
9523
9524 JS_FN_HELP("stackDump", StackDump, 3, 0,
9525 "stackDump(showArgs, showLocals, showThisProps)",
9526 " Tries to print a lot of information about the current stack. \n"
9527 " Similar to the DumpJSStack() function in the browser."),
9528
9529 #endif
9530
9531 JS_FN_HELP("intern", Intern, 1, 0,
9532 "intern(str)",
9533 " Internalize str in the atom table."),
9534
9535 JS_FN_HELP("getslx", GetSLX, 1, 0,
9536 "getslx(obj)",
9537 " Get script line extent."),
9538
9539 JS_FN_HELP("evalcx", EvalInContext, 1, 0,
9540 "evalcx(s[, o])",
9541 " Evaluate s in optional sandbox object o.\n"
9542 " if (s == '' && !o) return new o with eager standard classes\n"
9543 " if (s == 'lazy' && !o) return new o with lazy standard classes"),
9544
9545 JS_FN_HELP("evalInWorker", EvalInWorker, 1, 0,
9546 "evalInWorker(str)",
9547 " Evaluate 'str' in a separate thread with its own runtime.\n"),
9548
9549 JS_FN_HELP("getSharedObject", GetSharedObject, 0, 0,
9550 "getSharedObject()",
9551 " Retrieve the shared object from the cross-worker mailbox.\n"
9552 " The object retrieved may not be identical to the object that was\n"
9553 " installed, but it references the same shared memory.\n"
9554 " getSharedObject performs an ordering memory barrier.\n"),
9555
9556 JS_FN_HELP("setSharedObject", SetSharedObject, 0, 0,
9557 "setSharedObject(obj)",
9558 " Install the shared object in the cross-worker mailbox. The object\n"
9559 " may be null. setSharedObject performs an ordering memory barrier.\n"),
9560
9561 JS_FN_HELP("getSharedArrayBuffer", GetSharedObject, 0, 0,
9562 "getSharedArrayBuffer()",
9563 " Obsolete alias for getSharedObject().\n"),
9564
9565 JS_FN_HELP("setSharedArrayBuffer", SetSharedObject, 0, 0,
9566 "setSharedArrayBuffer(obj)",
9567 " Obsolete alias for setSharedObject(obj).\n"),
9568
9569 JS_FN_HELP("shapeOf", ShapeOf, 1, 0,
9570 "shapeOf(obj)",
9571 " Get the shape of obj (an implementation detail)."),
9572
9573 #ifdef DEBUG
9574 JS_FN_HELP("arrayInfo", ArrayInfo, 1, 0,
9575 "arrayInfo(a1, a2, ...)",
9576 " Report statistics about arrays."),
9577 #endif
9578
9579 JS_FN_HELP("sleep", Sleep_fn, 1, 0,
9580 "sleep(dt)",
9581 " Sleep for dt seconds."),
9582
9583 JS_FN_HELP("compile", Compile, 1, 0,
9584 "compile(code, [options])",
9585 " Compiles a string to bytecode, potentially throwing.\n"
9586 " If present, |options| may have CompileOptions-related properties of\n"
9587 " evaluate function"),
9588
9589 JS_FN_HELP("parseModule", ParseModule, 1, 0,
9590 "parseModule(code)",
9591 " Parses source text as a module and returns a ModuleObject wrapper object."),
9592
9593 JS_FN_HELP("codeModule", CodeModule, 1, 0,
9594 "codeModule(module)",
9595 " Takes an uninstantiated ModuleObject wrapper and returns a XDR bytecode\n"
9596 " representation of that ModuleObject."),
9597
9598 JS_FN_HELP("decodeModule", DecodeModule, 1, 0,
9599 "decodeModule(code)",
9600 " Takes a XDR bytecode representation of an uninstantiated\n"
9601 " ModuleObject and returns a ModuleObject wrapper."),
9602
9603 JS_FN_HELP("registerModule", RegisterModule, 2, 0,
9604 "registerModule(specifier, module)",
9605 " Register a module with the module loader, so that subsequent import from\n"
9606 " |specifier| will resolve to |module|. Returns |module|."),
9607
9608 JS_FN_HELP("getModuleEnvironmentNames", GetModuleEnvironmentNames, 1, 0,
9609 "getModuleEnvironmentNames(module)",
9610 " Get the list of a module environment's bound names for a specified module.\n"),
9611
9612 JS_FN_HELP("getModuleEnvironmentValue", GetModuleEnvironmentValue, 2, 0,
9613 "getModuleEnvironmentValue(module, name)",
9614 " Get the value of a bound name in a module environment.\n"),
9615
9616 JS_FN_HELP("dumpStencil", DumpStencil, 1, 0,
9617 "dumpStencil(code, [options])",
9618 " Parses a string and returns string that represents stencil.\n"
9619 " If present, |options| may have properties saying how the code should be\n"
9620 " compiled:\n"
9621 " module: if present and true, compile the source as module.\n"
9622 " smoosh: if present and true, use SmooshMonkey.\n"
9623 " CompileOptions-related properties of evaluate function's option can also\n"
9624 " be used."),
9625
9626 JS_FN_HELP("parse", Parse, 1, 0,
9627 "parse(code, [options])",
9628 " Parses a string, potentially throwing. If present, |options| may\n"
9629 " have properties saying how the code should be compiled:\n"
9630 " module: if present and true, compile the source as module.\n"
9631 " smoosh: if present and true, use SmooshMonkey.\n"
9632 " CompileOptions-related properties of evaluate function's option can also\n"
9633 " be used. except forceFullParse. This function always use full parse."),
9634
9635 JS_FN_HELP("syntaxParse", SyntaxParse, 1, 0,
9636 "syntaxParse(code)",
9637 " Check the syntax of a string, returning success value"),
9638
9639 JS_FN_HELP("offThreadCompileScript", OffThreadCompileScript, 1, 0,
9640 "offThreadCompileScript(code[, options])",
9641 " Compile |code| on a helper thread, returning a job ID.\n"
9642 " To wait for the compilation to finish and run the code, call\n"
9643 " |runOffThreadScript| passing the job ID. If present, |options| may\n"
9644 " have properties saying how the code should be compiled:\n"
9645 " noScriptRval: use the no-script-rval compiler option (default: false)\n"
9646 " fileName: filename for error messages and debug info\n"
9647 " lineNumber: starting line number for error messages and debug info\n"
9648 " columnNumber: starting column number for error messages and debug info\n"
9649 " element: if present with value |v|, convert |v| to an object |o| and\n"
9650 " mark the source as being attached to the DOM element |o|. If the\n"
9651 " property is omitted or |v| is null, don't attribute the source to\n"
9652 " any DOM element.\n"
9653 " elementAttributeName: if present and not undefined, the name of\n"
9654 " property of 'element' that holds this code. This is what\n"
9655 " Debugger.Source.prototype.elementAttributeName returns."),
9656
9657 JS_FN_HELP("runOffThreadScript", runOffThreadScript, 0, 0,
9658 "runOffThreadScript([jobID])",
9659 " Wait for an off-thread compilation job to complete. The job ID can be\n"
9660 " ommitted if there is only one job pending. If an error occurred,\n"
9661 " throw the appropriate exception; otherwise, run the script and return\n"
9662 " its value."),
9663
9664 JS_FN_HELP("offThreadCompileModule", OffThreadCompileModule, 1, 0,
9665 "offThreadCompileModule(code)",
9666 " Compile |code| on a helper thread, returning a job ID. To wait for the\n"
9667 " compilation to finish and and get the module record object call\n"
9668 " |finishOffThreadModule| passing the job ID."),
9669
9670 JS_FN_HELP("finishOffThreadModule", FinishOffThreadModule, 0, 0,
9671 "finishOffThreadModule([jobID])",
9672 " Wait for an off-thread compilation job to complete. The job ID can be\n"
9673 " ommitted if there is only one job pending. If an error occurred,\n"
9674 " throw the appropriate exception; otherwise, return the module record object."),
9675
9676 JS_FN_HELP("offThreadDecodeScript", OffThreadDecodeScript, 1, 0,
9677 "offThreadDecodeScript(cacheEntry[, options])",
9678 " Decode |code| on a helper thread, returning a job ID. To wait for the\n"
9679 " decoding to finish and run the code, call |runOffThreadDecodeScript| passing\n"
9680 " the job ID. If present, |options| may have properties saying how the code\n"
9681 " should be compiled (see also offThreadCompileScript)."),
9682
9683 JS_FN_HELP("runOffThreadDecodedScript", runOffThreadDecodedScript, 0, 0,
9684 "runOffThreadDecodedScript([jobID])",
9685 " Wait for off-thread decoding to complete. The job ID can be ommitted if there\n"
9686 " is only one job pending. If an error occurred, throw the appropriate\n"
9687 " exception; otherwise, run the script and return its value."),
9688
9689 JS_FN_HELP("offThreadCompileToStencil", OffThreadCompileToStencil, 1, 0,
9690 "offThreadCompileToStencil(code[, options])",
9691 " Compile |code| on a helper thread, returning a job ID. To wait for the\n"
9692 " compilation to finish and get the stencil object, call\n"
9693 " |finishOffThreadCompileToStencil| passing the job ID."),
9694
9695 JS_FN_HELP("finishOffThreadCompileToStencil", FinishOffThreadCompileToStencil, 0, 0,
9696 "finishOffThreadCompileToStencil([jobID])",
9697 " Wait for an off-thread compilation job to complete. The job ID can be\n"
9698 " ommitted if there is only one job pending. If an error occurred,\n"
9699 " throw the appropriate exception; otherwise, return the stencil object,"
9700 " that can be passed to |evalStencil|."),
9701
9702 JS_FN_HELP("timeout", Timeout, 1, 0,
9703 "timeout([seconds], [func])",
9704 " Get/Set the limit in seconds for the execution time for the current context.\n"
9705 " When the timeout expires the current interrupt callback is invoked.\n"
9706 " The timeout is used just once. If the callback returns a falsy value, the\n"
9707 " script is aborted. A negative value for seconds (this is the default) cancels\n"
9708 " any pending timeout.\n"
9709 " If a second argument is provided, it is installed as the interrupt handler,\n"
9710 " exactly as if by |setInterruptCallback|.\n"),
9711
9712 JS_FN_HELP("interruptIf", InterruptIf, 1, 0,
9713 "interruptIf(cond)",
9714 " Requests interrupt callback if cond is true. If a callback function is set via\n"
9715 " |timeout| or |setInterruptCallback|, it will be called. No-op otherwise."),
9716
9717 JS_FN_HELP("invokeInterruptCallback", InvokeInterruptCallbackWrapper, 0, 0,
9718 "invokeInterruptCallback(fun)",
9719 " Forcefully set the interrupt flag and invoke the interrupt handler. If a\n"
9720 " callback function is set via |timeout| or |setInterruptCallback|, it will\n"
9721 " be called. Before returning, fun is called with the return value of the\n"
9722 " interrupt handler."),
9723
9724 JS_FN_HELP("setInterruptCallback", SetInterruptCallback, 1, 0,
9725 "setInterruptCallback(func)",
9726 " Sets func as the interrupt callback function.\n"
9727 " Calling this function will replace any callback set by |timeout|.\n"
9728 " If the callback returns a falsy value, the script is aborted.\n"),
9729
9730 JS_FN_HELP("setJitCompilerOption", SetJitCompilerOption, 2, 0,
9731 "setJitCompilerOption(<option>, <number>)",
9732 " Set a compiler option indexed in JSCompileOption enum to a number.\n"),
9733 #ifdef DEBUG
9734 JS_FN_HELP("interruptRegexp", InterruptRegexp, 2, 0,
9735 "interruptRegexp(<regexp>, <string>)",
9736 " Interrrupt the execution of regular expression.\n"),
9737 #endif
9738 JS_FN_HELP("enableLastWarning", EnableLastWarning, 0, 0,
9739 "enableLastWarning()",
9740 " Enable storing the last warning."),
9741
9742 JS_FN_HELP("disableLastWarning", DisableLastWarning, 0, 0,
9743 "disableLastWarning()",
9744 " Disable storing the last warning."),
9745
9746 JS_FN_HELP("getLastWarning", GetLastWarning, 0, 0,
9747 "getLastWarning()",
9748 " Returns an object that represents the last warning."),
9749
9750 JS_FN_HELP("clearLastWarning", ClearLastWarning, 0, 0,
9751 "clearLastWarning()",
9752 " Clear the last warning."),
9753
9754 JS_FN_HELP("elapsed", Elapsed, 0, 0,
9755 "elapsed()",
9756 " Execution time elapsed for the current thread."),
9757
9758 JS_FN_HELP("decompileFunction", DecompileFunction, 1, 0,
9759 "decompileFunction(func)",
9760 " Decompile a function."),
9761
9762 JS_FN_HELP("decompileThis", DecompileThisScript, 0, 0,
9763 "decompileThis()",
9764 " Decompile the currently executing script."),
9765
9766 JS_FN_HELP("valueToSource", ValueToSource, 1, 0,
9767 "valueToSource(value)",
9768 " Format a value for inspection."),
9769
9770 JS_FN_HELP("thisFilename", ThisFilename, 0, 0,
9771 "thisFilename()",
9772 " Return the filename of the current script"),
9773
9774 JS_FN_HELP("newGlobal", NewGlobal, 1, 0,
9775 "newGlobal([options])",
9776 " Return a new global object/realm. The new global is created in the\n"
9777 " 'newGlobal' function object's compartment and zone, unless the\n"
9778 " '--more-compartments' command-line flag was given, in which case new\n"
9779 " globals get a fresh compartment and zone. If options is given, it may\n"
9780 " have any of the following properties:\n"
9781 " sameCompartmentAs: If an object, the global will be in the same\n"
9782 " compartment and zone as the given object.\n"
9783 " sameZoneAs: The global will be in a new compartment in the same zone\n"
9784 " as the given object.\n"
9785 " newCompartment: If true, the global will always be created in a new\n"
9786 " compartment and zone.\n"
9787 " invisibleToDebugger: If true, the global will be invisible to the\n"
9788 " debugger (default false)\n"
9789 " discardSource: If true, discard source after compiling a script\n"
9790 " (default false).\n"
9791 " useWindowProxy: the global will be created with a WindowProxy attached. In this\n"
9792 " case, the WindowProxy will be returned.\n"
9793 " immutablePrototype: whether the global's prototype is immutable.\n"
9794 " principal: if present, its value converted to a number must be an\n"
9795 " integer that fits in 32 bits; use that as the new realm's\n"
9796 " principal. Shell principals are toys, meant only for testing; one\n"
9797 " shell principal subsumes another if its set bits are a superset of\n"
9798 " the other's. Thus, a principal of 0 subsumes nothing, while a\n"
9799 " principals of ~0 subsumes all other principals. The absence of a\n"
9800 " principal is treated as if its bits were 0xffff, for subsumption\n"
9801 " purposes. If this property is omitted, supply no principal.\n"
9802 " systemPrincipal: If true, use the shell's trusted principals for the\n"
9803 " new realm. This creates a realm that's marked as a 'system' realm."),
9804
9805 JS_FN_HELP("nukeCCW", NukeCCW, 1, 0,
9806 "nukeCCW(wrapper)",
9807 " Nuke a CrossCompartmentWrapper, which turns it into a DeadProxyObject."),
9808
9809 JS_FN_HELP("nukeAllCCWs", NukeAllCCWs, 0, 0,
9810 "nukeAllCCWs()",
9811 " Like nukeCCW, but for all CrossCompartmentWrappers targeting the current realm."),
9812
9813 JS_FN_HELP("recomputeWrappers", RecomputeWrappers, 2, 0,
9814 "recomputeWrappers([src, [target]])",
9815 " Recompute all cross-compartment wrappers. src and target are both optional\n"
9816 " and can be used to filter source or target compartments: the unwrapped\n"
9817 " object's compartment is used as CompartmentFilter.\n"),
9818
9819 JS_FN_HELP("dumpObjectWrappers", DumpObjectWrappers, 2, 0,
9820 "dumpObjectWrappers()",
9821 " Print information about cross-compartment object wrappers.\n"),
9822
9823 JS_FN_HELP("wrapWithProto", WrapWithProto, 2, 0,
9824 "wrapWithProto(obj)",
9825 " Wrap an object into a noop wrapper with prototype semantics."),
9826
9827 JS_FN_HELP("createExternalArrayBuffer", CreateExternalArrayBuffer, 1, 0,
9828 "createExternalArrayBuffer(size)",
9829 " Create an array buffer that has external data of size."),
9830
9831 JS_FN_HELP("createMappedArrayBuffer", CreateMappedArrayBuffer, 1, 0,
9832 "createMappedArrayBuffer(filename, [offset, [size]])",
9833 " Create an array buffer that mmaps the given file."),
9834
9835 JS_FN_HELP("addPromiseReactions", AddPromiseReactions, 3, 0,
9836 "addPromiseReactions(promise, onResolve, onReject)",
9837 " Calls the JS::AddPromiseReactions JSAPI function with the given arguments."),
9838
9839 JS_FN_HELP("ignoreUnhandledRejections", IgnoreUnhandledRejections, 0, 0,
9840 "ignoreUnhandledRejections()",
9841 " By default, js shell tracks unhandled promise rejections and reports\n"
9842 " them at the end of the exectuion. If a testcase isn't interested\n"
9843 " in those rejections, call this to stop tracking and reporting."),
9844
9845 JS_FN_HELP("getMaxArgs", GetMaxArgs, 0, 0,
9846 "getMaxArgs()",
9847 " Return the maximum number of supported args for a call."),
9848
9849 JS_FN_HELP("createIsHTMLDDA", CreateIsHTMLDDA, 0, 0,
9850 "createIsHTMLDDA()",
9851 " Return an object |obj| that \"looks like\" the |document.all| object in\n"
9852 " browsers in certain ways: |typeof obj === \"undefined\"|, |obj == null|\n"
9853 " and |obj == undefined| (vice versa for !=), |ToBoolean(obj) === false|,\n"
9854 " and when called with no arguments or the single argument \"\" returns\n"
9855 " null. (Calling |obj| any other way crashes or throws an exception.)\n"
9856 " This function implements the exact requirements of the $262.IsHTMLDDA\n"
9857 " property in test262."),
9858
9859 JS_FN_HELP("cacheEntry", CacheEntry, 1, 0,
9860 "cacheEntry(code)",
9861 " Return a new opaque object which emulates a cache entry of a script. This\n"
9862 " object encapsulates the code and its cached content. The cache entry is filled\n"
9863 " and read by the \"evaluate\" function by using it in place of the source, and\n"
9864 " by setting \"saveBytecode\" and \"loadBytecode\" options."),
9865
9866 JS_FN_HELP("streamCacheEntry", StreamCacheEntryObject::construct, 1, 0,
9867 "streamCacheEntry(buffer)",
9868 " Create a shell-only object that holds wasm bytecode and can be streaming-\n"
9869 " compiled and cached by WebAssembly.{compile,instantiate}Streaming(). On a\n"
9870 " second compilation of the same cache entry, the cached code will be used."),
9871
9872 JS_FN_HELP("printProfilerEvents", PrintProfilerEvents, 0, 0,
9873 "printProfilerEvents()",
9874 " Register a callback with the profiler that prints javascript profiler events\n"
9875 " to stderr. Callback is only registered if profiling is enabled."),
9876
9877 JS_FN_HELP("enableSingleStepProfiling", EnableSingleStepProfiling, 0, 0,
9878 "enableSingleStepProfiling()",
9879 " This function will fail on platforms that don't support single-step profiling\n"
9880 " (currently ARM and MIPS64 support it). When enabled, at every instruction a\n"
9881 " backtrace will be recorded and stored in an array. Adjacent duplicate backtraces\n"
9882 " are discarded."),
9883
9884 JS_FN_HELP("disableSingleStepProfiling", DisableSingleStepProfiling, 0, 0,
9885 "disableSingleStepProfiling()",
9886 " Return the array of backtraces recorded by enableSingleStepProfiling."),
9887
9888 JS_FN_HELP("enableGeckoProfiling", EnableGeckoProfiling, 0, 0,
9889 "enableGeckoProfiling()",
9890 " Enables Gecko Profiler instrumentation and corresponding assertions, with slow\n"
9891 " assertions disabled.\n"),
9892
9893 JS_FN_HELP("enableGeckoProfilingWithSlowAssertions", EnableGeckoProfilingWithSlowAssertions, 0, 0,
9894 "enableGeckoProfilingWithSlowAssertions()",
9895 " Enables Gecko Profiler instrumentation and corresponding assertions, with slow\n"
9896 " assertions enabled.\n"),
9897
9898 JS_FN_HELP("disableGeckoProfiling", DisableGeckoProfiling, 0, 0,
9899 "disableGeckoProfiling()",
9900 " Disables Gecko Profiler instrumentation"),
9901
9902 JS_FN_HELP("isLatin1", IsLatin1, 1, 0,
9903 "isLatin1(s)",
9904 " Return true iff the string's characters are stored as Latin1."),
9905
9906 JS_FN_HELP("stackPointerInfo", StackPointerInfo, 0, 0,
9907 "stackPointerInfo()",
9908 " Return an int32 value which corresponds to the offset of the latest stack\n"
9909 " pointer, such that one can take the differences of 2 to estimate a frame-size."),
9910
9911 JS_FN_HELP("entryPoints", EntryPoints, 1, 0,
9912 "entryPoints(params)",
9913 "Carry out some JSAPI operation as directed by |params|, and return an array of\n"
9914 "objects describing which JavaScript entry points were invoked as a result.\n"
9915 "|params| is an object whose properties indicate what operation to perform. Here\n"
9916 "are the recognized groups of properties:\n"
9917 "\n"
9918 "{ function }: Call the object |params.function| with no arguments.\n"
9919 "\n"
9920 "{ object, property }: Fetch the property named |params.property| of\n"
9921 "|params.object|.\n"
9922 "\n"
9923 "{ ToString }: Apply JS::ToString to |params.toString|.\n"
9924 "\n"
9925 "{ ToNumber }: Apply JS::ToNumber to |params.toNumber|.\n"
9926 "\n"
9927 "{ eval }: Apply JS::Evaluate to |params.eval|.\n"
9928 "\n"
9929 "The return value is an array of strings, with one element for each\n"
9930 "JavaScript invocation that occurred as a result of the given\n"
9931 "operation. Each element is the name of the function invoked, or the\n"
9932 "string 'eval:FILENAME' if the code was invoked by 'eval' or something\n"
9933 "similar.\n"),
9934
9935 JS_FN_HELP("enqueueJob", EnqueueJob, 1, 0,
9936 "enqueueJob(fn)",
9937 " Enqueue 'fn' on the shell's job queue."),
9938
9939 JS_FN_HELP("globalOfFirstJobInQueue", GlobalOfFirstJobInQueue, 0, 0,
9940 "globalOfFirstJobInQueue()",
9941 " Returns the global of the first item in the job queue. Throws an exception\n"
9942 " if the queue is empty.\n"),
9943
9944 JS_FN_HELP("drainJobQueue", DrainJobQueue, 0, 0,
9945 "drainJobQueue()",
9946 "Take jobs from the shell's job queue in FIFO order and run them until the\n"
9947 "queue is empty.\n"),
9948
9949 JS_FN_HELP("setPromiseRejectionTrackerCallback", SetPromiseRejectionTrackerCallback, 1, 0,
9950 "setPromiseRejectionTrackerCallback()",
9951 "Sets the callback to be invoked whenever a Promise rejection is unhandled\n"
9952 "or a previously-unhandled rejection becomes handled."),
9953
9954 JS_FN_HELP("dumpScopeChain", DumpScopeChain, 1, 0,
9955 "dumpScopeChain(obj)",
9956 " Prints the scope chain of an interpreted function or a module."),
9957
9958 JS_FN_HELP("grayRoot", EnsureGrayRoot, 0, 0,
9959 "grayRoot()",
9960 " Create a gray root Array, if needed, for the current compartment, and\n"
9961 " return it."),
9962
9963 JS_FN_HELP("addMarkObservers", AddMarkObservers, 1, 0,
9964 "addMarkObservers(array_of_objects)",
9965 " Register an array of objects whose mark bits will be tested by calls to\n"
9966 " getMarks. The objects will be in calling compartment. Objects from\n"
9967 " multiple compartments may be monitored by calling this function in\n"
9968 " different compartments."),
9969
9970 JS_FN_HELP("clearMarkObservers", ClearMarkObservers, 1, 0,
9971 "clearMarkObservers()",
9972 " Clear out the list of objects whose mark bits will be tested.\n"),
9973
9974 JS_FN_HELP("getMarks", GetMarks, 0, 0,
9975 "getMarks()",
9976 " Return an array of strings representing the current state of the mark\n"
9977 " bits ('gray' or 'black', or 'dead' if the object has been collected)\n"
9978 " for the objects registered via addMarkObservers. Note that some of the\n"
9979 " objects tested may be from different compartments than the one in which\n"
9980 " this function runs."),
9981
9982 JS_FN_HELP("bindToAsyncStack", BindToAsyncStack, 2, 0,
9983 "bindToAsyncStack(fn, { stack, cause, explicit })",
9984 " Returns a new function that calls 'fn' with no arguments, passing\n"
9985 " 'undefined' as the 'this' value, and supplies an async stack for the\n"
9986 " call as described by the second argument, an object with the following\n"
9987 " properties (which are not optional, unless specified otherwise):\n"
9988 "\n"
9989 " stack: A SavedFrame object, like that returned by 'saveStack'. Stacks\n"
9990 " captured during calls to the returned function capture this as\n"
9991 " their async stack parent, accessible via a SavedFrame's\n"
9992 " 'asyncParent' property.\n"
9993 "\n"
9994 " cause: A string, supplied as the async cause on the top frame of\n"
9995 " captured async stacks.\n"
9996 "\n"
9997 " explicit: A boolean value, indicating whether the given 'stack' should\n"
9998 " always supplant the returned function's true callers (true),\n"
9999 " or only when there are no other JavaScript frames on the stack\n"
10000 " below it (false). If omitted, this is treated as 'true'."),
10001
10002 #ifdef JS_HAS_INTL_API
10003 JS_FN_HELP("addIntlExtras", AddIntlExtras, 1, 0,
10004 "addIntlExtras(obj)",
10005 "Adds various not-yet-standardized Intl functions as properties on the\n"
10006 "provided object (this should generally be Intl itself). The added\n"
10007 "functions and their behavior are experimental: don't depend upon them\n"
10008 "unless you're willing to update your code if these experimental APIs change\n"
10009 "underneath you."),
10010 #endif // JS_HAS_INTL_API
10011
10012 #ifndef __wasi__
10013 JS_FN_HELP("wasmCompileInSeparateProcess", WasmCompileInSeparateProcess, 1, 0,
10014 "wasmCompileInSeparateProcess(buffer)",
10015 " Compile the given buffer in a separate process, serialize the resulting\n"
10016 " wasm::Module into bytes, and deserialize those bytes in the current\n"
10017 " process, returning the resulting WebAssembly.Module."),
10018
10019 JS_FN_HELP("wasmTextToBinary", WasmTextToBinary, 1, 0,
10020 "wasmTextToBinary(str)",
10021 " Translates the given text wasm module into its binary encoding."),
10022
10023 JS_FN_HELP("wasmCodeOffsets", WasmCodeOffsets, 1, 0,
10024 "wasmCodeOffsets(binary)",
10025 " Decodes the given wasm binary to find the offsets of every instruction in the"
10026 " code section."),
10027 #endif // __wasi__
10028
10029 JS_FN_HELP("transplantableObject", TransplantableObject, 0, 0,
10030 "transplantableObject([options])",
10031 " Returns the pair {object, transplant}. |object| is an object which can be\n"
10032 " transplanted into a new object when the |transplant| function, which must\n"
10033 " be invoked with a global object, is called.\n"
10034 " |object| is swapped with a cross-compartment wrapper if the global object\n"
10035 " is in a different compartment.\n"
10036 "\n"
10037 " If options is given, it may have any of the following properties:\n"
10038 " proxy: Create a DOM Proxy object instead of a plain DOM object.\n"
10039 " object: Don't create a new DOM object, but instead use the supplied\n"
10040 " FakeDOMObject."),
10041
10042 JS_FN_HELP("cpuNow", CpuNow, /* nargs= */ 0, /* flags = */ 0,
10043 "cpuNow()",
10044 " Returns the approximate processor time used by the process since an arbitrary epoch, in seconds.\n"
10045 " Only the difference between two calls to `cpuNow()` is meaningful."),
10046
10047 #ifdef FUZZING_JS_FUZZILLI
10048 JS_FN_HELP("fuzzilli", Fuzzilli, 0, 0,
10049 "fuzzilli(operation, arg)",
10050 " Exposes functionality used by the Fuzzilli JavaScript fuzzer."),
10051 #endif
10052
10053 JS_FS_HELP_END
10054 };
10055 // clang-format on
10056
10057 // clang-format off
10058 static const JSFunctionSpecWithHelp fuzzing_unsafe_functions[] = {
10059 JS_FN_HELP("getSelfHostedValue", GetSelfHostedValue, 1, 0,
10060 "getSelfHostedValue()",
10061 " Get a self-hosted value by its name. Note that these values don't get \n"
10062 " cached, so repeatedly getting the same value creates multiple distinct clones."),
10063
10064 JS_FN_HELP("line2pc", LineToPC, 0, 0,
10065 "line2pc([fun,] line)",
10066 " Map line number to PC."),
10067
10068 JS_FN_HELP("pc2line", PCToLine, 0, 0,
10069 "pc2line(fun[, pc])",
10070 " Map PC to line number."),
10071
10072 JS_INLINABLE_FN_HELP("assertFloat32", testingFunc_assertFloat32, 2, 0, TestAssertFloat32,
10073 "assertFloat32(value, isFloat32)",
10074 " In IonMonkey only, asserts that value has (resp. hasn't) the MIRType::Float32 if isFloat32 is true (resp. false)."),
10075
10076 JS_INLINABLE_FN_HELP("assertRecoveredOnBailout", testingFunc_assertRecoveredOnBailout, 2, 0,
10077 TestAssertRecoveredOnBailout,
10078 "assertRecoveredOnBailout(var)",
10079 " In IonMonkey only, asserts that variable has RecoveredOnBailout flag."),
10080
10081 JS_FN_HELP("withSourceHook", WithSourceHook, 1, 0,
10082 "withSourceHook(hook, fun)",
10083 " Set this JS runtime's lazy source retrieval hook (that is, the hook\n"
10084 " used to find sources compiled with |CompileOptions::LAZY_SOURCE|) to\n"
10085 " |hook|; call |fun| with no arguments; and then restore the runtime's\n"
10086 " original hook. Return or throw whatever |fun| did. |hook| gets\n"
10087 " passed the requested code's URL, and should return a string.\n"
10088 "\n"
10089 " Notes:\n"
10090 "\n"
10091 " 1) SpiderMonkey may assert if the returned code isn't close enough\n"
10092 " to the script's real code, so this function is not fuzzer-safe.\n"
10093 "\n"
10094 " 2) The runtime can have only one source retrieval hook active at a\n"
10095 " time. If |fun| is not careful, |hook| could be asked to retrieve the\n"
10096 " source code for compilations that occurred long before it was set,\n"
10097 " and that it knows nothing about. The reverse applies as well: the\n"
10098 " original hook, that we reinstate after the call to |fun| completes,\n"
10099 " might be asked for the source code of compilations that |fun|\n"
10100 " performed, and which, presumably, only |hook| knows how to find.\n"),
10101
10102 JS_FN_HELP("crash", Crash, 0, 0,
10103 "crash([message, [{disable_minidump:true}]])",
10104 " Crashes the process with a MOZ_CRASH, optionally providing a message.\n"
10105 " An options object may be passed as the second argument. If the key\n"
10106 " 'suppress_minidump' is set to true, then a minidump will not be\n"
10107 " generated by the crash (which only has an effect if the breakpad\n"
10108 " dumping library is loaded.)"),
10109
10110 #ifndef __wasi__
10111 JS_FN_HELP("wasmLoop", WasmLoop, 2, 0,
10112 "wasmLoop(filename, imports)",
10113 " Performs an AFL-style persistent loop reading data from the given file and passing it\n"
10114 " to the 'wasmEval' function together with the specified imports object."),
10115 #endif // __wasi__
10116
10117 JS_FN_HELP("setBufferStreamParams", SetBufferStreamParams, 2, 0,
10118 "setBufferStreamParams(delayMillis, chunkByteSize)",
10119 " Set the delay time (between calls to StreamConsumer::consumeChunk) and chunk\n"
10120 " size (in bytes)."),
10121
10122 #ifdef JS_CACHEIR_SPEW
10123 JS_FN_HELP("cacheIRHealthReport", CacheIRHealthReport, 0, 0,
10124 "cacheIRHealthReport()",
10125 " Show health rating of CacheIR stubs."),
10126 #endif
10127
10128 JS_FS_HELP_END
10129 };
10130 // clang-format on
10131
10132 // clang-format off
10133 static const JSFunctionSpecWithHelp performance_functions[] = {
10134 JS_FN_HELP("now", Now, 0, 0,
10135 "now()",
10136 " Return the current time with sub-ms precision.\n"
10137 " This function is an alias of the dateNow() function."),
10138 JS_FS_HELP_END
10139 };
10140 // clang-format on
10141
10142 // clang-format off
10143 static const JSFunctionSpecWithHelp console_functions[] = {
10144 JS_FN_HELP("log", Print, 0, 0,
10145 "log([exp ...])",
10146 " Evaluate and print expressions to stdout.\n"
10147 " This function is an alias of the print() function."),
10148 JS_FS_HELP_END
10149 };
10150 // clang-format on
10151
DefineConsole(JSContext * cx,HandleObject global)10152 bool DefineConsole(JSContext* cx, HandleObject global) {
10153 RootedObject obj(cx, JS_NewPlainObject(cx));
10154 return obj && JS_DefineFunctionsWithHelp(cx, obj, console_functions) &&
10155 JS_DefineProperty(cx, global, "console", obj, 0);
10156 }
10157
10158 #ifdef MOZ_PROFILING
10159 # define PROFILING_FUNCTION_COUNT 5
10160 # ifdef MOZ_CALLGRIND
10161 # define CALLGRIND_FUNCTION_COUNT 3
10162 # else
10163 # define CALLGRIND_FUNCTION_COUNT 0
10164 # endif
10165 # ifdef MOZ_VTUNE
10166 # define VTUNE_FUNCTION_COUNT 4
10167 # else
10168 # define VTUNE_FUNCTION_COUNT 0
10169 # endif
10170 # define EXTERNAL_FUNCTION_COUNT \
10171 (PROFILING_FUNCTION_COUNT + CALLGRIND_FUNCTION_COUNT + VTUNE_FUNCTION_COUNT)
10172 #else
10173 # define EXTERNAL_FUNCTION_COUNT 0
10174 #endif
10175
10176 #undef PROFILING_FUNCTION_COUNT
10177 #undef CALLGRIND_FUNCTION_COUNT
10178 #undef VTUNE_FUNCTION_COUNT
10179 #undef EXTERNAL_FUNCTION_COUNT
10180
PrintHelpString(JSContext * cx,HandleValue v)10181 static bool PrintHelpString(JSContext* cx, HandleValue v) {
10182 RootedString str(cx, v.toString());
10183 MOZ_ASSERT(gOutFile->isOpen());
10184
10185 UniqueChars bytes = JS_EncodeStringToUTF8(cx, str);
10186 if (!bytes) {
10187 return false;
10188 }
10189
10190 fprintf(gOutFile->fp, "%s\n", bytes.get());
10191 return true;
10192 }
10193
PrintHelp(JSContext * cx,HandleObject obj)10194 static bool PrintHelp(JSContext* cx, HandleObject obj) {
10195 RootedValue usage(cx);
10196 if (!JS_GetProperty(cx, obj, "usage", &usage)) {
10197 return false;
10198 }
10199 RootedValue help(cx);
10200 if (!JS_GetProperty(cx, obj, "help", &help)) {
10201 return false;
10202 }
10203
10204 if (!usage.isString() || !help.isString()) {
10205 return true;
10206 }
10207
10208 return PrintHelpString(cx, usage) && PrintHelpString(cx, help);
10209 }
10210
PrintEnumeratedHelp(JSContext * cx,HandleObject obj,HandleObject pattern,bool brief)10211 static bool PrintEnumeratedHelp(JSContext* cx, HandleObject obj,
10212 HandleObject pattern, bool brief) {
10213 RootedIdVector idv(cx);
10214 if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &idv)) {
10215 return false;
10216 }
10217
10218 Rooted<RegExpObject*> regex(cx);
10219 if (pattern) {
10220 regex = &UncheckedUnwrap(pattern)->as<RegExpObject>();
10221 }
10222
10223 for (size_t i = 0; i < idv.length(); i++) {
10224 RootedValue v(cx);
10225 RootedId id(cx, idv[i]);
10226 if (!JS_GetPropertyById(cx, obj, id, &v)) {
10227 return false;
10228 }
10229 if (!v.isObject()) {
10230 continue;
10231 }
10232
10233 RootedObject funcObj(cx, &v.toObject());
10234 if (regex) {
10235 // Only pay attention to objects with a 'help' property, which will
10236 // either be documented functions or interface objects.
10237 if (!JS_GetProperty(cx, funcObj, "help", &v)) {
10238 return false;
10239 }
10240 if (!v.isString()) {
10241 continue;
10242 }
10243
10244 // For functions, match against the name. For interface objects,
10245 // match against the usage string.
10246 if (!JS_GetProperty(cx, funcObj, "name", &v)) {
10247 return false;
10248 }
10249 if (!v.isString()) {
10250 if (!JS_GetProperty(cx, funcObj, "usage", &v)) {
10251 return false;
10252 }
10253 if (!v.isString()) {
10254 continue;
10255 }
10256 }
10257
10258 size_t ignored = 0;
10259 if (!JSString::ensureLinear(cx, v.toString())) {
10260 return false;
10261 }
10262 RootedLinearString input(cx, &v.toString()->asLinear());
10263 if (!ExecuteRegExpLegacy(cx, nullptr, regex, input, &ignored, true, &v)) {
10264 return false;
10265 }
10266 if (v.isNull()) {
10267 continue;
10268 }
10269 }
10270
10271 if (!PrintHelp(cx, funcObj)) {
10272 return false;
10273 }
10274 }
10275
10276 return true;
10277 }
10278
Help(JSContext * cx,unsigned argc,Value * vp)10279 static bool Help(JSContext* cx, unsigned argc, Value* vp) {
10280 if (!gOutFile->isOpen()) {
10281 JS_ReportErrorASCII(cx, "output file is closed");
10282 return false;
10283 }
10284
10285 CallArgs args = CallArgsFromVp(argc, vp);
10286 args.rval().setUndefined();
10287 RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
10288
10289 // help() - display the version and dump out help for all functions on the
10290 // global.
10291 if (args.length() == 0) {
10292 fprintf(gOutFile->fp, "%s\n", JS_GetImplementationVersion());
10293
10294 if (!PrintEnumeratedHelp(cx, global, nullptr, false)) {
10295 return false;
10296 }
10297 return true;
10298 }
10299
10300 RootedValue v(cx);
10301
10302 if (args[0].isPrimitive()) {
10303 // help("foo")
10304 JS_ReportErrorASCII(cx, "primitive arg");
10305 return false;
10306 }
10307
10308 RootedObject obj(cx, &args[0].toObject());
10309 if (!obj) {
10310 return true;
10311 }
10312 bool isRegexp;
10313 if (!JS::ObjectIsRegExp(cx, obj, &isRegexp)) {
10314 return false;
10315 }
10316
10317 if (isRegexp) {
10318 // help(/pattern/)
10319 return PrintEnumeratedHelp(cx, global, obj, false);
10320 }
10321
10322 // help(function)
10323 // help(namespace_obj)
10324 return PrintHelp(cx, obj);
10325 }
10326
10327 static const JSErrorFormatString jsShell_ErrorFormatString[JSShellErr_Limit] = {
10328 #define MSG_DEF(name, count, exception, format) \
10329 {#name, format, count, JSEXN_ERR},
10330 #include "jsshell.msg"
10331 #undef MSG_DEF
10332 };
10333
my_GetErrorMessage(void * userRef,const unsigned errorNumber)10334 const JSErrorFormatString* js::shell::my_GetErrorMessage(
10335 void* userRef, const unsigned errorNumber) {
10336 if (errorNumber == 0 || errorNumber >= JSShellErr_Limit) {
10337 return nullptr;
10338 }
10339
10340 return &jsShell_ErrorFormatString[errorNumber];
10341 }
10342
CreateLastWarningObject(JSContext * cx,JSErrorReport * report)10343 static bool CreateLastWarningObject(JSContext* cx, JSErrorReport* report) {
10344 RootedObject warningObj(cx, JS_NewObject(cx, nullptr));
10345 if (!warningObj) {
10346 return false;
10347 }
10348
10349 if (!CopyErrorReportToObject(cx, report, warningObj)) {
10350 return false;
10351 }
10352
10353 GetShellContext(cx)->lastWarning.setObject(*warningObj);
10354 return true;
10355 }
10356
ErrorFilePointer()10357 static FILE* ErrorFilePointer() {
10358 if (gErrFile->isOpen()) {
10359 return gErrFile->fp;
10360 }
10361
10362 fprintf(stderr, "error file is closed; falling back to stderr\n");
10363 return stderr;
10364 }
10365
PrintStackTrace(JSContext * cx,HandleObject stackObj)10366 bool shell::PrintStackTrace(JSContext* cx, HandleObject stackObj) {
10367 if (!stackObj || !stackObj->is<SavedFrame>()) {
10368 return true;
10369 }
10370
10371 JSPrincipals* principals = stackObj->nonCCWRealm()->principals();
10372 RootedString stackStr(cx);
10373 if (!BuildStackString(cx, principals, stackObj, &stackStr, 2)) {
10374 return false;
10375 }
10376
10377 UniqueChars stack = JS_EncodeStringToUTF8(cx, stackStr);
10378 if (!stack) {
10379 return false;
10380 }
10381
10382 FILE* fp = ErrorFilePointer();
10383 fputs("Stack:\n", fp);
10384 fputs(stack.get(), fp);
10385
10386 return true;
10387 }
10388
~AutoReportException()10389 js::shell::AutoReportException::~AutoReportException() {
10390 if (!JS_IsExceptionPending(cx)) {
10391 return;
10392 }
10393
10394 auto printError = [](JSContext* cx, auto& report, const auto& exnStack,
10395 const char* prefix = nullptr) {
10396 if (!report.init(cx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) {
10397 fprintf(stderr, "out of memory initializing JS::ErrorReportBuilder\n");
10398 fflush(stderr);
10399 JS_ClearPendingException(cx);
10400 return false;
10401 }
10402
10403 MOZ_ASSERT(!report.report()->isWarning());
10404
10405 FILE* fp = ErrorFilePointer();
10406 if (prefix) {
10407 fputs(prefix, fp);
10408 }
10409 JS::PrintError(fp, report, reportWarnings);
10410 JS_ClearPendingException(cx);
10411
10412 if (!PrintStackTrace(cx, exnStack.stack())) {
10413 fputs("(Unable to print stack trace)\n", fp);
10414 JS_ClearPendingException(cx);
10415 }
10416
10417 return true;
10418 };
10419
10420 // Get exception object and stack before printing and clearing exception.
10421 JS::ExceptionStack exnStack(cx);
10422 if (!JS::StealPendingExceptionStack(cx, &exnStack)) {
10423 fprintf(stderr, "out of memory while stealing exception\n");
10424 fflush(stderr);
10425 JS_ClearPendingException(cx);
10426 return;
10427 }
10428
10429 ShellContext* sc = GetShellContext(cx);
10430 JS::ErrorReportBuilder report(cx);
10431 if (!printError(cx, report, exnStack)) {
10432 // Return if we couldn't initialize the error report.
10433 return;
10434 }
10435
10436 // Print the error's cause, if available.
10437 if (exnStack.exception().isObject()) {
10438 JSObject* exception = &exnStack.exception().toObject();
10439 if (exception->is<ErrorObject>()) {
10440 auto* error = &exception->as<ErrorObject>();
10441 if (auto maybeCause = error->getCause()) {
10442 RootedValue cause(cx, maybeCause.value());
10443
10444 RootedObject causeStack(cx);
10445 if (cause.isObject()) {
10446 RootedObject causeObj(cx, &cause.toObject());
10447 causeStack = JS::ExceptionStackOrNull(causeObj);
10448 }
10449
10450 JS::ExceptionStack exnStack(cx, cause, causeStack);
10451 JS::ErrorReportBuilder report(cx);
10452 printError(cx, report, exnStack, "Caused by: ");
10453 }
10454 }
10455 }
10456
10457 #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
10458 // Don't quit the shell if an unhandled exception is reported during OOM
10459 // testing.
10460 if (cx->runningOOMTest) {
10461 return;
10462 }
10463 #endif
10464
10465 if (report.report()->errorNumber == JSMSG_OUT_OF_MEMORY) {
10466 sc->exitCode = EXITCODE_OUT_OF_MEMORY;
10467 } else {
10468 sc->exitCode = EXITCODE_RUNTIME_ERROR;
10469 }
10470 }
10471
WarningReporter(JSContext * cx,JSErrorReport * report)10472 void js::shell::WarningReporter(JSContext* cx, JSErrorReport* report) {
10473 ShellContext* sc = GetShellContext(cx);
10474 FILE* fp = ErrorFilePointer();
10475
10476 MOZ_ASSERT(report->isWarning());
10477
10478 if (sc->lastWarningEnabled) {
10479 JS::AutoSaveExceptionState savedExc(cx);
10480 if (!CreateLastWarningObject(cx, report)) {
10481 fputs("Unhandled error happened while creating last warning object.\n",
10482 fp);
10483 fflush(fp);
10484 }
10485 savedExc.restore();
10486 }
10487
10488 // Print the warning.
10489 JS::PrintError(fp, report, reportWarnings);
10490 }
10491
global_enumerate(JSContext * cx,JS::HandleObject obj,JS::MutableHandleIdVector properties,bool enumerableOnly)10492 static bool global_enumerate(JSContext* cx, JS::HandleObject obj,
10493 JS::MutableHandleIdVector properties,
10494 bool enumerableOnly) {
10495 #ifdef LAZY_STANDARD_CLASSES
10496 return JS_NewEnumerateStandardClasses(cx, obj, properties, enumerableOnly);
10497 #else
10498 return true;
10499 #endif
10500 }
10501
global_resolve(JSContext * cx,HandleObject obj,HandleId id,bool * resolvedp)10502 static bool global_resolve(JSContext* cx, HandleObject obj, HandleId id,
10503 bool* resolvedp) {
10504 #ifdef LAZY_STANDARD_CLASSES
10505 if (!JS_ResolveStandardClass(cx, obj, id, resolvedp)) {
10506 return false;
10507 }
10508 #endif
10509 return true;
10510 }
10511
global_mayResolve(const JSAtomState & names,jsid id,JSObject * maybeObj)10512 static bool global_mayResolve(const JSAtomState& names, jsid id,
10513 JSObject* maybeObj) {
10514 return JS_MayResolveStandardClass(names, id, maybeObj);
10515 }
10516
10517 static const JSClassOps global_classOps = {
10518 nullptr, // addProperty
10519 nullptr, // delProperty
10520 nullptr, // enumerate
10521 global_enumerate, // newEnumerate
10522 global_resolve, // resolve
10523 global_mayResolve, // mayResolve
10524 nullptr, // finalize
10525 nullptr, // call
10526 nullptr, // hasInstance
10527 nullptr, // construct
10528 JS_GlobalObjectTraceHook, // trace
10529 };
10530
10531 static constexpr uint32_t DOM_PROTOTYPE_SLOT = JSCLASS_GLOBAL_SLOT_COUNT;
10532 static constexpr uint32_t DOM_GLOBAL_SLOTS = 1;
10533
10534 static const JSClass global_class = {
10535 "global",
10536 JSCLASS_GLOBAL_FLAGS | JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(DOM_GLOBAL_SLOTS),
10537 &global_classOps};
10538
10539 /*
10540 * Define a FakeDOMObject constructor. It returns an object with a getter,
10541 * setter and method with attached JitInfo. This object can be used to test
10542 * IonMonkey DOM optimizations in the shell.
10543 */
10544
10545 /* Fow now just use to a constant we can check. */
10546 static const void* DOM_PRIVATE_VALUE = (void*)0x1234;
10547
10548 static bool dom_genericGetter(JSContext* cx, unsigned argc, JS::Value* vp);
10549
10550 static bool dom_genericSetter(JSContext* cx, unsigned argc, JS::Value* vp);
10551
10552 static bool dom_genericMethod(JSContext* cx, unsigned argc, JS::Value* vp);
10553
dom_get_x(JSContext * cx,HandleObject obj,void * self,JSJitGetterCallArgs args)10554 static bool dom_get_x(JSContext* cx, HandleObject obj, void* self,
10555 JSJitGetterCallArgs args) {
10556 MOZ_ASSERT(JS::GetClass(obj) == GetDomClass());
10557 MOZ_ASSERT(self == DOM_PRIVATE_VALUE);
10558 args.rval().set(JS_NumberValue(double(3.14)));
10559 return true;
10560 }
10561
dom_set_x(JSContext * cx,HandleObject obj,void * self,JSJitSetterCallArgs args)10562 static bool dom_set_x(JSContext* cx, HandleObject obj, void* self,
10563 JSJitSetterCallArgs args) {
10564 MOZ_ASSERT(JS::GetClass(obj) == GetDomClass());
10565 MOZ_ASSERT(self == DOM_PRIVATE_VALUE);
10566 return true;
10567 }
10568
dom_get_slot(JSContext * cx,HandleObject obj,void * self,JSJitGetterCallArgs args)10569 static bool dom_get_slot(JSContext* cx, HandleObject obj, void* self,
10570 JSJitGetterCallArgs args) {
10571 MOZ_ASSERT(JS::GetClass(obj) == GetDomClass());
10572 MOZ_ASSERT(self == DOM_PRIVATE_VALUE);
10573
10574 Value v = JS::GetReservedSlot(obj, DOM_OBJECT_SLOT2);
10575 MOZ_ASSERT(v.toInt32() == 42);
10576 args.rval().set(v);
10577 return true;
10578 }
10579
dom_get_global(JSContext * cx,HandleObject obj,void * self,JSJitGetterCallArgs args)10580 static bool dom_get_global(JSContext* cx, HandleObject obj, void* self,
10581 JSJitGetterCallArgs args) {
10582 MOZ_ASSERT(JS::GetClass(obj) == GetDomClass());
10583 MOZ_ASSERT(self == DOM_PRIVATE_VALUE);
10584
10585 // Return the current global (instead of obj->global()) to test cx->realm
10586 // switching in the JIT.
10587 args.rval().setObject(*ToWindowProxyIfWindow(cx->global()));
10588
10589 return true;
10590 }
10591
dom_set_global(JSContext * cx,HandleObject obj,void * self,JSJitSetterCallArgs args)10592 static bool dom_set_global(JSContext* cx, HandleObject obj, void* self,
10593 JSJitSetterCallArgs args) {
10594 MOZ_ASSERT(JS::GetClass(obj) == GetDomClass());
10595 MOZ_ASSERT(self == DOM_PRIVATE_VALUE);
10596
10597 // Throw an exception if our argument is not the current global. This lets
10598 // us test cx->realm switching.
10599 if (!args[0].isObject() ||
10600 ToWindowIfWindowProxy(&args[0].toObject()) != cx->global()) {
10601 JS_ReportErrorASCII(cx, "Setter not called with matching global argument");
10602 return false;
10603 }
10604
10605 return true;
10606 }
10607
dom_doFoo(JSContext * cx,HandleObject obj,void * self,const JSJitMethodCallArgs & args)10608 static bool dom_doFoo(JSContext* cx, HandleObject obj, void* self,
10609 const JSJitMethodCallArgs& args) {
10610 MOZ_ASSERT(JS::GetClass(obj) == GetDomClass());
10611 MOZ_ASSERT(self == DOM_PRIVATE_VALUE);
10612 MOZ_ASSERT(cx->realm() == args.callee().as<JSFunction>().realm());
10613
10614 /* Just return args.length(). */
10615 args.rval().setInt32(args.length());
10616 return true;
10617 }
10618
10619 static const JSJitInfo dom_x_getterinfo = {
10620 {(JSJitGetterOp)dom_get_x},
10621 {0}, /* protoID */
10622 {0}, /* depth */
10623 JSJitInfo::Getter,
10624 JSJitInfo::AliasNone, /* aliasSet */
10625 JSVAL_TYPE_UNKNOWN, /* returnType */
10626 true, /* isInfallible. False in setters. */
10627 true, /* isMovable */
10628 true, /* isEliminatable */
10629 false, /* isAlwaysInSlot */
10630 false, /* isLazilyCachedInSlot */
10631 false, /* isTypedMethod */
10632 0 /* slotIndex */
10633 };
10634
10635 static const JSJitInfo dom_x_setterinfo = {
10636 {(JSJitGetterOp)dom_set_x},
10637 {0}, /* protoID */
10638 {0}, /* depth */
10639 JSJitInfo::Setter,
10640 JSJitInfo::AliasEverything, /* aliasSet */
10641 JSVAL_TYPE_UNKNOWN, /* returnType */
10642 false, /* isInfallible. False in setters. */
10643 false, /* isMovable. */
10644 false, /* isEliminatable. */
10645 false, /* isAlwaysInSlot */
10646 false, /* isLazilyCachedInSlot */
10647 false, /* isTypedMethod */
10648 0 /* slotIndex */
10649 };
10650
10651 static const JSJitInfo dom_slot_getterinfo = {
10652 {(JSJitGetterOp)dom_get_slot},
10653 {0}, /* protoID */
10654 {0}, /* depth */
10655 JSJitInfo::Getter,
10656 JSJitInfo::AliasNone, /* aliasSet */
10657 JSVAL_TYPE_INT32, /* returnType */
10658 false, /* isInfallible. False in setters. */
10659 true, /* isMovable */
10660 true, /* isEliminatable */
10661 true, /* isAlwaysInSlot */
10662 false, /* isLazilyCachedInSlot */
10663 false, /* isTypedMethod */
10664 DOM_OBJECT_SLOT2 /* slotIndex */
10665 };
10666
10667 // Note: this getter uses AliasEverything and is marked as fallible and
10668 // non-movable (1) to prevent Ion from getting too clever optimizing it and
10669 // (2) it's nice to have a few different kinds of getters in the shell.
10670 static const JSJitInfo dom_global_getterinfo = {
10671 {(JSJitGetterOp)dom_get_global},
10672 {0}, /* protoID */
10673 {0}, /* depth */
10674 JSJitInfo::Getter,
10675 JSJitInfo::AliasEverything, /* aliasSet */
10676 JSVAL_TYPE_OBJECT, /* returnType */
10677 false, /* isInfallible. False in setters. */
10678 false, /* isMovable */
10679 false, /* isEliminatable */
10680 false, /* isAlwaysInSlot */
10681 false, /* isLazilyCachedInSlot */
10682 false, /* isTypedMethod */
10683 0 /* slotIndex */
10684 };
10685
10686 static const JSJitInfo dom_global_setterinfo = {
10687 {(JSJitGetterOp)dom_set_global},
10688 {0}, /* protoID */
10689 {0}, /* depth */
10690 JSJitInfo::Setter,
10691 JSJitInfo::AliasEverything, /* aliasSet */
10692 JSVAL_TYPE_UNKNOWN, /* returnType */
10693 false, /* isInfallible. False in setters. */
10694 false, /* isMovable. */
10695 false, /* isEliminatable. */
10696 false, /* isAlwaysInSlot */
10697 false, /* isLazilyCachedInSlot */
10698 false, /* isTypedMethod */
10699 0 /* slotIndex */
10700 };
10701
10702 static const JSJitInfo doFoo_methodinfo = {
10703 {(JSJitGetterOp)dom_doFoo},
10704 {0}, /* protoID */
10705 {0}, /* depth */
10706 JSJitInfo::Method,
10707 JSJitInfo::AliasEverything, /* aliasSet */
10708 JSVAL_TYPE_UNKNOWN, /* returnType */
10709 false, /* isInfallible. False in setters. */
10710 false, /* isMovable */
10711 false, /* isEliminatable */
10712 false, /* isAlwaysInSlot */
10713 false, /* isLazilyCachedInSlot */
10714 false, /* isTypedMethod */
10715 0 /* slotIndex */
10716 };
10717
10718 static const JSPropertySpec dom_props[] = {
10719 JSPropertySpec::nativeAccessors("x", JSPROP_ENUMERATE, dom_genericGetter,
10720 &dom_x_getterinfo, dom_genericSetter,
10721 &dom_x_setterinfo),
10722 JSPropertySpec::nativeAccessors("slot", JSPROP_ENUMERATE, dom_genericGetter,
10723 &dom_slot_getterinfo),
10724 JSPropertySpec::nativeAccessors("global", JSPROP_ENUMERATE,
10725 dom_genericGetter, &dom_global_getterinfo,
10726 dom_genericSetter, &dom_global_setterinfo),
10727 JS_PS_END};
10728
10729 static const JSFunctionSpec dom_methods[] = {
10730 JS_FNINFO("doFoo", dom_genericMethod, &doFoo_methodinfo, 3,
10731 JSPROP_ENUMERATE),
10732 JS_FS_END};
10733
10734 static const JSClass dom_class = {
10735 "FakeDOMObject", JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(2)};
10736
GetDomClass()10737 static const JSClass* GetDomClass() { return &dom_class; }
10738
dom_genericGetter(JSContext * cx,unsigned argc,JS::Value * vp)10739 static bool dom_genericGetter(JSContext* cx, unsigned argc, JS::Value* vp) {
10740 CallArgs args = CallArgsFromVp(argc, vp);
10741
10742 if (!args.thisv().isObject()) {
10743 args.rval().setUndefined();
10744 return true;
10745 }
10746
10747 RootedObject obj(cx, &args.thisv().toObject());
10748 if (JS::GetClass(obj) != &dom_class) {
10749 args.rval().set(UndefinedValue());
10750 return true;
10751 }
10752
10753 JS::Value val = JS::GetReservedSlot(obj, DOM_OBJECT_SLOT);
10754
10755 const JSJitInfo* info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
10756 MOZ_ASSERT(info->type() == JSJitInfo::Getter);
10757 JSJitGetterOp getter = info->getter;
10758 return getter(cx, obj, val.toPrivate(), JSJitGetterCallArgs(args));
10759 }
10760
dom_genericSetter(JSContext * cx,unsigned argc,JS::Value * vp)10761 static bool dom_genericSetter(JSContext* cx, unsigned argc, JS::Value* vp) {
10762 CallArgs args = CallArgsFromVp(argc, vp);
10763
10764 if (args.length() < 1 || !args.thisv().isObject()) {
10765 args.rval().setUndefined();
10766 return true;
10767 }
10768
10769 RootedObject obj(cx, &args.thisv().toObject());
10770 if (JS::GetClass(obj) != &dom_class) {
10771 args.rval().set(UndefinedValue());
10772 return true;
10773 }
10774
10775 JS::Value val = JS::GetReservedSlot(obj, DOM_OBJECT_SLOT);
10776
10777 const JSJitInfo* info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
10778 MOZ_ASSERT(info->type() == JSJitInfo::Setter);
10779 JSJitSetterOp setter = info->setter;
10780 if (!setter(cx, obj, val.toPrivate(), JSJitSetterCallArgs(args))) {
10781 return false;
10782 }
10783 args.rval().set(UndefinedValue());
10784 return true;
10785 }
10786
dom_genericMethod(JSContext * cx,unsigned argc,JS::Value * vp)10787 static bool dom_genericMethod(JSContext* cx, unsigned argc, JS::Value* vp) {
10788 CallArgs args = CallArgsFromVp(argc, vp);
10789
10790 if (!args.thisv().isObject()) {
10791 args.rval().setUndefined();
10792 return true;
10793 }
10794
10795 RootedObject obj(cx, &args.thisv().toObject());
10796 if (JS::GetClass(obj) != &dom_class) {
10797 args.rval().set(UndefinedValue());
10798 return true;
10799 }
10800
10801 JS::Value val = JS::GetReservedSlot(obj, DOM_OBJECT_SLOT);
10802
10803 const JSJitInfo* info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
10804 MOZ_ASSERT(info->type() == JSJitInfo::Method);
10805 JSJitMethodOp method = info->method;
10806 return method(cx, obj, val.toPrivate(), JSJitMethodCallArgs(args));
10807 }
10808
InitDOMObject(HandleObject obj)10809 static void InitDOMObject(HandleObject obj) {
10810 JS::SetReservedSlot(obj, DOM_OBJECT_SLOT,
10811 PrivateValue(const_cast<void*>(DOM_PRIVATE_VALUE)));
10812 JS::SetReservedSlot(obj, DOM_OBJECT_SLOT2, Int32Value(42));
10813 }
10814
GetDOMPrototype(JSContext * cx,JSObject * global)10815 static JSObject* GetDOMPrototype(JSContext* cx, JSObject* global) {
10816 MOZ_ASSERT(JS_IsGlobalObject(global));
10817 if (JS::GetClass(global) != &global_class) {
10818 JS_ReportErrorASCII(cx, "Can't get FakeDOMObject prototype in sandbox");
10819 return nullptr;
10820 }
10821
10822 const JS::Value& slot = JS::GetReservedSlot(global, DOM_PROTOTYPE_SLOT);
10823 MOZ_ASSERT(slot.isObject());
10824 return &slot.toObject();
10825 }
10826
dom_constructor(JSContext * cx,unsigned argc,JS::Value * vp)10827 static bool dom_constructor(JSContext* cx, unsigned argc, JS::Value* vp) {
10828 CallArgs args = CallArgsFromVp(argc, vp);
10829
10830 RootedObject callee(cx, &args.callee());
10831 RootedValue protov(cx);
10832 if (!GetProperty(cx, callee, callee, cx->names().prototype, &protov)) {
10833 return false;
10834 }
10835
10836 if (!protov.isObject()) {
10837 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_PROTOTYPE,
10838 "FakeDOMObject");
10839 return false;
10840 }
10841
10842 RootedObject proto(cx, &protov.toObject());
10843 RootedObject domObj(cx, JS_NewObjectWithGivenProto(cx, &dom_class, proto));
10844 if (!domObj) {
10845 return false;
10846 }
10847
10848 InitDOMObject(domObj);
10849
10850 args.rval().setObject(*domObj);
10851 return true;
10852 }
10853
InstanceClassHasProtoAtDepth(const JSClass * clasp,uint32_t protoID,uint32_t depth)10854 static bool InstanceClassHasProtoAtDepth(const JSClass* clasp, uint32_t protoID,
10855 uint32_t depth) {
10856 // Only the (fake) DOM object supports any JIT optimizations.
10857 return clasp == GetDomClass();
10858 }
10859
ShellBuildId(JS::BuildIdCharVector * buildId)10860 static bool ShellBuildId(JS::BuildIdCharVector* buildId) {
10861 // The browser embeds the date into the buildid and the buildid is embedded
10862 // in the binary, so every 'make' necessarily builds a new firefox binary.
10863 // Fortunately, the actual firefox executable is tiny -- all the code is in
10864 // libxul.so and other shared modules -- so this isn't a big deal. Not so
10865 // for the statically-linked JS shell. To avoid recompiling js.cpp and
10866 // re-linking 'js' on every 'make', we use a constant buildid and rely on
10867 // the shell user to manually clear any caches between cache-breaking updates.
10868 const char buildid[] = "JS-shell";
10869 return buildId->append(buildid, sizeof(buildid));
10870 }
10871
TimesAccessed(JSContext * cx,unsigned argc,Value * vp)10872 static bool TimesAccessed(JSContext* cx, unsigned argc, Value* vp) {
10873 static int32_t accessed = 0;
10874 CallArgs args = CallArgsFromVp(argc, vp);
10875 args.rval().setInt32(++accessed);
10876 return true;
10877 }
10878
10879 static const JSPropertySpec TestingProperties[] = {
10880 JS_PSG("timesAccessed", TimesAccessed, 0), JS_PS_END};
10881
NewGlobalObject(JSContext * cx,JS::RealmOptions & options,JSPrincipals * principals,ShellGlobalKind kind,bool immutablePrototype)10882 static JSObject* NewGlobalObject(JSContext* cx, JS::RealmOptions& options,
10883 JSPrincipals* principals, ShellGlobalKind kind,
10884 bool immutablePrototype) {
10885 RootedObject glob(cx,
10886 JS_NewGlobalObject(cx, &global_class, principals,
10887 JS::DontFireOnNewGlobalHook, options));
10888 if (!glob) {
10889 return nullptr;
10890 }
10891
10892 {
10893 JSAutoRealm ar(cx, glob);
10894
10895 if (kind == ShellGlobalKind::WindowProxy) {
10896 RootedObject proxy(cx, NewShellWindowProxy(cx, glob));
10897 if (!proxy) {
10898 return nullptr;
10899 }
10900 js::SetWindowProxy(cx, glob, proxy);
10901 }
10902
10903 #ifndef LAZY_STANDARD_CLASSES
10904 if (!JS::InitRealmStandardClasses(cx)) {
10905 return nullptr;
10906 }
10907 #endif
10908
10909 if (immutablePrototype) {
10910 bool succeeded;
10911 if (!JS_SetImmutablePrototype(cx, glob, &succeeded)) {
10912 return nullptr;
10913 }
10914 MOZ_ASSERT(succeeded,
10915 "a fresh, unexposed global object is always capable of "
10916 "having its [[Prototype]] be immutable");
10917 }
10918
10919 #ifdef JS_HAS_CTYPES
10920 if (!fuzzingSafe && !JS::InitCTypesClass(cx, glob)) {
10921 return nullptr;
10922 }
10923 #endif
10924 if (!JS_InitReflectParse(cx, glob)) {
10925 return nullptr;
10926 }
10927 if (!JS_DefineDebuggerObject(cx, glob)) {
10928 return nullptr;
10929 }
10930 if (!JS_DefineFunctionsWithHelp(cx, glob, shell_functions) ||
10931 !JS_DefineProfilingFunctions(cx, glob)) {
10932 return nullptr;
10933 }
10934 if (!js::DefineTestingFunctions(cx, glob, fuzzingSafe,
10935 disableOOMFunctions)) {
10936 return nullptr;
10937 }
10938 if (!JS_DefineProperties(cx, glob, TestingProperties)) {
10939 return nullptr;
10940 }
10941
10942 if (!fuzzingSafe) {
10943 if (!JS_DefineFunctionsWithHelp(cx, glob, fuzzing_unsafe_functions)) {
10944 return nullptr;
10945 }
10946 if (!DefineConsole(cx, glob)) {
10947 return nullptr;
10948 }
10949 }
10950
10951 if (!DefineOS(cx, glob, fuzzingSafe, &gOutFile, &gErrFile)) {
10952 return nullptr;
10953 }
10954
10955 RootedObject performanceObj(cx, JS_NewObject(cx, nullptr));
10956 if (!performanceObj) {
10957 return nullptr;
10958 }
10959 if (!JS_DefineFunctionsWithHelp(cx, performanceObj,
10960 performance_functions)) {
10961 return nullptr;
10962 }
10963 RootedObject mozMemoryObj(cx, JS_NewObject(cx, nullptr));
10964 if (!mozMemoryObj) {
10965 return nullptr;
10966 }
10967 RootedObject gcObj(cx, gc::NewMemoryInfoObject(cx));
10968 if (!gcObj) {
10969 return nullptr;
10970 }
10971 if (!JS_DefineProperty(cx, glob, "performance", performanceObj,
10972 JSPROP_ENUMERATE)) {
10973 return nullptr;
10974 }
10975 if (!JS_DefineProperty(cx, performanceObj, "mozMemory", mozMemoryObj,
10976 JSPROP_ENUMERATE)) {
10977 return nullptr;
10978 }
10979 if (!JS_DefineProperty(cx, mozMemoryObj, "gc", gcObj, JSPROP_ENUMERATE)) {
10980 return nullptr;
10981 }
10982
10983 /* Initialize FakeDOMObject. */
10984 static const js::DOMCallbacks DOMcallbacks = {InstanceClassHasProtoAtDepth};
10985 SetDOMCallbacks(cx, &DOMcallbacks);
10986
10987 RootedObject domProto(
10988 cx, JS_InitClass(cx, glob, nullptr, &dom_class, dom_constructor, 0,
10989 dom_props, dom_methods, nullptr, nullptr));
10990 if (!domProto) {
10991 return nullptr;
10992 }
10993
10994 // FakeDOMObject.prototype is the only DOM object which needs to retrieved
10995 // in the shell; store it directly instead of creating a separate layer
10996 // (ProtoAndIfaceCache) as done in the browser.
10997 JS::SetReservedSlot(glob, DOM_PROTOTYPE_SLOT, ObjectValue(*domProto));
10998
10999 /* Initialize FakeDOMObject.prototype */
11000 InitDOMObject(domProto);
11001
11002 if (!DefineToStringTag(cx, glob, cx->names().global)) {
11003 return nullptr;
11004 }
11005
11006 JS_FireOnNewGlobalObject(cx, glob);
11007 }
11008
11009 return glob;
11010 }
11011
BindScriptArgs(JSContext * cx,OptionParser * op)11012 static bool BindScriptArgs(JSContext* cx, OptionParser* op) {
11013 AutoReportException are(cx);
11014
11015 MultiStringRange msr = op->getMultiStringArg("scriptArgs");
11016 RootedObject scriptArgs(cx);
11017 scriptArgs = JS::NewArrayObject(cx, 0);
11018 if (!scriptArgs) {
11019 return false;
11020 }
11021
11022 if (!JS_DefineProperty(cx, cx->global(), "scriptArgs", scriptArgs, 0)) {
11023 return false;
11024 }
11025
11026 for (size_t i = 0; !msr.empty(); msr.popFront(), ++i) {
11027 const char* scriptArg = msr.front();
11028 JS::RootedString str(cx, JS_NewStringCopyZ(cx, scriptArg));
11029 if (!str || !JS_DefineElement(cx, scriptArgs, i, str, JSPROP_ENUMERATE)) {
11030 return false;
11031 }
11032 }
11033
11034 const char* scriptPath = op->getStringArg("script");
11035 RootedValue scriptPathValue(cx);
11036 if (scriptPath) {
11037 RootedString scriptPathString(cx, JS_NewStringCopyZ(cx, scriptPath));
11038 if (!scriptPathString) {
11039 return false;
11040 }
11041 scriptPathValue = StringValue(scriptPathString);
11042 } else {
11043 scriptPathValue = UndefinedValue();
11044 }
11045
11046 if (!JS_DefineProperty(cx, cx->global(), "scriptPath", scriptPathValue, 0)) {
11047 return false;
11048 }
11049
11050 return true;
11051 }
11052
OptionFailure(const char * option,const char * str)11053 static bool OptionFailure(const char* option, const char* str) {
11054 fprintf(stderr, "Unrecognized option for %s: %s\n", option, str);
11055 return false;
11056 }
11057
ProcessArgs(JSContext * cx,OptionParser * op)11058 [[nodiscard]] static bool ProcessArgs(JSContext* cx, OptionParser* op) {
11059 ShellContext* sc = GetShellContext(cx);
11060
11061 #ifdef JS_ENABLE_SMOOSH
11062 if (op->getBoolOption("smoosh")) {
11063 JS::ContextOptionsRef(cx).setTrySmoosh(true);
11064 js::frontend::InitSmoosh();
11065 }
11066
11067 if (const char* filename = op->getStringOption("not-implemented-watchfile")) {
11068 FILE* out = fopen(filename, "a");
11069 MOZ_RELEASE_ASSERT(out);
11070 setbuf(out, nullptr); // Make unbuffered
11071 cx->runtime()->parserWatcherFile.init(out);
11072 JS::ContextOptionsRef(cx).setTrackNotImplemented(true);
11073 }
11074 #endif
11075
11076 /* |scriptArgs| gets bound on the global before any code is run. */
11077 if (!BindScriptArgs(cx, op)) {
11078 return false;
11079 }
11080
11081 RootedString moduleLoadPath(cx);
11082 if (const char* option = op->getStringOption("module-load-path")) {
11083 RootedString jspath(cx, JS_NewStringCopyZ(cx, option));
11084 if (!jspath) {
11085 return false;
11086 }
11087
11088 moduleLoadPath = js::shell::ResolvePath(cx, jspath, RootRelative);
11089 } else {
11090 UniqueChars cwd = js::shell::GetCWD();
11091 moduleLoadPath = JS_NewStringCopyZ(cx, cwd.get());
11092 }
11093
11094 if (!moduleLoadPath) {
11095 return false;
11096 }
11097
11098 processWideModuleLoadPath = JS_EncodeStringToUTF8(cx, moduleLoadPath);
11099 if (!processWideModuleLoadPath) {
11100 MOZ_ASSERT(cx->isExceptionPending());
11101 return false;
11102 }
11103
11104 sc->moduleLoader = js::MakeUnique<ModuleLoader>();
11105 if (!sc->moduleLoader || !sc->moduleLoader->init(cx, moduleLoadPath)) {
11106 return false;
11107 }
11108
11109 MultiStringRange filePaths = op->getMultiStringOption('f');
11110 MultiStringRange utf16FilePaths = op->getMultiStringOption('u');
11111 MultiStringRange codeChunks = op->getMultiStringOption('e');
11112 MultiStringRange modulePaths = op->getMultiStringOption('m');
11113
11114 #ifdef FUZZING_JS_FUZZILLI
11115 // Check for REPRL file source
11116 if (op->getBoolOption("reprl")) {
11117 return FuzzilliReprlGetAndRun(cx);
11118 }
11119 #endif /* FUZZING_JS_FUZZILLI */
11120
11121 if (filePaths.empty() && utf16FilePaths.empty() && codeChunks.empty() &&
11122 modulePaths.empty() && !op->getStringArg("script")) {
11123 // Always use the interactive shell when -i is used. Without -i we let
11124 // Process figure it out based on isatty.
11125 bool forceTTY = op->getBoolOption('i');
11126 return Process(cx, nullptr, forceTTY, FileScript);
11127 }
11128
11129 while (!filePaths.empty() || !utf16FilePaths.empty() || !codeChunks.empty() ||
11130 !modulePaths.empty()) {
11131 size_t fpArgno = filePaths.empty() ? SIZE_MAX : filePaths.argno();
11132 size_t ufpArgno =
11133 utf16FilePaths.empty() ? SIZE_MAX : utf16FilePaths.argno();
11134 size_t ccArgno = codeChunks.empty() ? SIZE_MAX : codeChunks.argno();
11135 size_t mpArgno = modulePaths.empty() ? SIZE_MAX : modulePaths.argno();
11136
11137 if (fpArgno < ufpArgno && fpArgno < ccArgno && fpArgno < mpArgno) {
11138 char* path = filePaths.front();
11139 if (!Process(cx, path, false, FileScript)) {
11140 return false;
11141 }
11142
11143 filePaths.popFront();
11144 continue;
11145 }
11146
11147 if (ufpArgno < fpArgno && ufpArgno < ccArgno && ufpArgno < mpArgno) {
11148 char* path = utf16FilePaths.front();
11149 if (!Process(cx, path, false, FileScriptUtf16)) {
11150 return false;
11151 }
11152
11153 utf16FilePaths.popFront();
11154 continue;
11155 }
11156
11157 if (ccArgno < fpArgno && ccArgno < ufpArgno && ccArgno < mpArgno) {
11158 const char* code = codeChunks.front();
11159
11160 JS::CompileOptions opts(cx);
11161 opts.setFileAndLine("-e", 1);
11162
11163 JS::SourceText<Utf8Unit> srcBuf;
11164 if (!srcBuf.init(cx, code, strlen(code), JS::SourceOwnership::Borrowed)) {
11165 return false;
11166 }
11167
11168 RootedValue rval(cx);
11169 if (!JS::Evaluate(cx, opts, srcBuf, &rval)) {
11170 return false;
11171 }
11172
11173 codeChunks.popFront();
11174 if (sc->quitting) {
11175 break;
11176 }
11177
11178 continue;
11179 }
11180
11181 MOZ_ASSERT(mpArgno < fpArgno && mpArgno < ufpArgno && mpArgno < ccArgno);
11182
11183 char* path = modulePaths.front();
11184 if (!Process(cx, path, false, FileModule)) {
11185 return false;
11186 }
11187
11188 modulePaths.popFront();
11189 }
11190
11191 if (sc->quitting) {
11192 return false;
11193 }
11194
11195 /* The |script| argument is processed after all options. */
11196 if (const char* path = op->getStringArg("script")) {
11197 if (!Process(cx, path, false, FileScript)) {
11198 return false;
11199 }
11200 }
11201
11202 if (op->getBoolOption('i')) {
11203 if (!Process(cx, nullptr, true, FileScript)) {
11204 return false;
11205 }
11206 }
11207
11208 return true;
11209 }
11210
SetContextOptions(JSContext * cx,const OptionParser & op)11211 static bool SetContextOptions(JSContext* cx, const OptionParser& op) {
11212 enableAsmJS = !op.getBoolOption("no-asmjs");
11213
11214 enableWasm = true;
11215 enableWasmBaseline = true;
11216 enableWasmOptimizing = true;
11217
11218 if (const char* str = op.getStringOption("wasm-compiler")) {
11219 if (strcmp(str, "none") == 0) {
11220 enableWasm = false;
11221 } else if (strcmp(str, "baseline") == 0) {
11222 MOZ_ASSERT(enableWasmBaseline);
11223 enableWasmOptimizing = false;
11224 } else if (strcmp(str, "optimizing") == 0 ||
11225 strcmp(str, "optimized") == 0) {
11226 enableWasmBaseline = false;
11227 MOZ_ASSERT(enableWasmOptimizing);
11228 } else if (strcmp(str, "baseline+optimizing") == 0 ||
11229 strcmp(str, "baseline+optimized") == 0) {
11230 MOZ_ASSERT(enableWasmBaseline);
11231 MOZ_ASSERT(enableWasmOptimizing);
11232 #ifdef ENABLE_WASM_CRANELIFT
11233 } else if (strcmp(str, "cranelift") == 0) {
11234 enableWasmBaseline = false;
11235 enableWasmOptimizing = true;
11236 } else if (strcmp(str, "baseline+cranelift") == 0) {
11237 MOZ_ASSERT(enableWasmBaseline);
11238 enableWasmOptimizing = true;
11239 #else
11240 } else if (strcmp(str, "ion") == 0) {
11241 enableWasmBaseline = false;
11242 enableWasmOptimizing = true;
11243 } else if (strcmp(str, "baseline+ion") == 0) {
11244 MOZ_ASSERT(enableWasmBaseline);
11245 enableWasmOptimizing = true;
11246 #endif
11247 } else {
11248 return OptionFailure("wasm-compiler", str);
11249 }
11250 }
11251
11252 #define WASM_DEFAULT_FEATURE(NAME, LOWER_NAME, COMPILE_PRED, COMPILER_PRED, \
11253 FLAG_PRED, SHELL, ...) \
11254 enableWasm##NAME = !op.getBoolOption("no-wasm-" SHELL);
11255 #define WASM_EXPERIMENTAL_FEATURE(NAME, LOWER_NAME, COMPILE_PRED, \
11256 COMPILER_PRED, FLAG_PRED, SHELL, ...) \
11257 enableWasm##NAME = op.getBoolOption("wasm-" SHELL);
11258 JS_FOR_WASM_FEATURES(WASM_DEFAULT_FEATURE, WASM_EXPERIMENTAL_FEATURE);
11259 #undef WASM_DEFAULT_FEATURE
11260 #undef WASM_EXPERIMENTAL_FEATURE
11261
11262 #ifdef ENABLE_WASM_SIMD_WORMHOLE
11263 enableWasmSimdWormhole = op.getBoolOption("wasm-simd-wormhole");
11264 #endif
11265 enableWasmVerbose = op.getBoolOption("wasm-verbose");
11266 enableTestWasmAwaitTier2 = op.getBoolOption("test-wasm-await-tier2");
11267 enableSourcePragmas = !op.getBoolOption("no-source-pragmas");
11268 enableAsyncStacks = !op.getBoolOption("no-async-stacks");
11269 enableAsyncStackCaptureDebuggeeOnly =
11270 op.getBoolOption("async-stacks-capture-debuggee-only");
11271 enableStreams = !op.getBoolOption("no-streams");
11272 enableReadableByteStreams = op.getBoolOption("enable-readable-byte-streams");
11273 enableBYOBStreamReaders = op.getBoolOption("enable-byob-stream-readers");
11274 enableWritableStreams = op.getBoolOption("enable-writable-streams");
11275 enableReadableStreamPipeTo = op.getBoolOption("enable-readablestream-pipeto");
11276 enableWeakRefs = !op.getBoolOption("disable-weak-refs");
11277 enableToSource = !op.getBoolOption("disable-tosource");
11278 enablePropertyErrorMessageFix =
11279 !op.getBoolOption("disable-property-error-message-fix");
11280 enableIteratorHelpers = op.getBoolOption("enable-iterator-helpers");
11281 enablePrivateClassFields = !op.getBoolOption("disable-private-fields");
11282 enablePrivateClassMethods = !op.getBoolOption("disable-private-methods");
11283 enableErgonomicBrandChecks =
11284 !op.getBoolOption("disable-ergonomic-brand-checks");
11285 enableTopLevelAwait = op.getBoolOption("enable-top-level-await");
11286 enableClassStaticBlocks = op.getBoolOption("enable-class-static-blocks");
11287 useOffThreadParseGlobal = op.getBoolOption("off-thread-parse-global");
11288 useFdlibmForSinCosTan = op.getBoolOption("use-fdlibm-for-sin-cos-tan");
11289
11290 JS::ContextOptionsRef(cx)
11291 .setAsmJS(enableAsmJS)
11292 .setWasm(enableWasm)
11293 .setWasmForTrustedPrinciples(enableWasm)
11294 .setWasmBaseline(enableWasmBaseline)
11295 #if defined(ENABLE_WASM_CRANELIFT)
11296 .setWasmCranelift(enableWasmOptimizing)
11297 .setWasmIon(false)
11298 #else
11299 .setWasmCranelift(false)
11300 .setWasmIon(enableWasmOptimizing)
11301 #endif
11302
11303 #define WASM_FEATURE(NAME, ...) .setWasm##NAME(enableWasm##NAME)
11304 JS_FOR_WASM_FEATURES(WASM_FEATURE, WASM_FEATURE)
11305 #undef WASM_FEATURE
11306
11307 #ifdef ENABLE_WASM_SIMD_WORMHOLE
11308 .setWasmSimdWormhole(enableWasmSimdWormhole)
11309 #endif
11310 .setWasmVerbose(enableWasmVerbose)
11311 .setTestWasmAwaitTier2(enableTestWasmAwaitTier2)
11312 .setSourcePragmas(enableSourcePragmas)
11313 .setAsyncStack(enableAsyncStacks)
11314 .setAsyncStackCaptureDebuggeeOnly(enableAsyncStackCaptureDebuggeeOnly)
11315 .setPrivateClassFields(enablePrivateClassFields)
11316 .setPrivateClassMethods(enablePrivateClassMethods)
11317 .setErgnomicBrandChecks(enableErgonomicBrandChecks)
11318 .setTopLevelAwait(enableTopLevelAwait)
11319 .setClassStaticBlocks(enableClassStaticBlocks);
11320
11321 JS::SetUseOffThreadParseGlobal(useOffThreadParseGlobal);
11322 JS::SetUseFdlibmForSinCosTan(useFdlibmForSinCosTan);
11323
11324 // Check --fast-warmup first because it sets default warm-up thresholds. These
11325 // thresholds can then be overridden below by --ion-eager and other flags.
11326 if (op.getBoolOption("fast-warmup")) {
11327 jit::JitOptions.setFastWarmUp();
11328 }
11329
11330 if (op.getBoolOption("no-ion-for-main-context")) {
11331 JS::ContextOptionsRef(cx).setDisableIon();
11332 }
11333
11334 if (const char* str = op.getStringOption("cache-ir-stubs")) {
11335 if (strcmp(str, "on") == 0) {
11336 jit::JitOptions.disableCacheIR = false;
11337 } else if (strcmp(str, "off") == 0) {
11338 jit::JitOptions.disableCacheIR = true;
11339 } else {
11340 return OptionFailure("cache-ir-stubs", str);
11341 }
11342 }
11343
11344 if (const char* str = op.getStringOption("spectre-mitigations")) {
11345 if (strcmp(str, "on") == 0) {
11346 jit::JitOptions.spectreIndexMasking = true;
11347 jit::JitOptions.spectreObjectMitigations = true;
11348 jit::JitOptions.spectreStringMitigations = true;
11349 jit::JitOptions.spectreValueMasking = true;
11350 jit::JitOptions.spectreJitToCxxCalls = true;
11351 } else if (strcmp(str, "off") == 0) {
11352 jit::JitOptions.spectreIndexMasking = false;
11353 jit::JitOptions.spectreObjectMitigations = false;
11354 jit::JitOptions.spectreStringMitigations = false;
11355 jit::JitOptions.spectreValueMasking = false;
11356 jit::JitOptions.spectreJitToCxxCalls = false;
11357 } else {
11358 return OptionFailure("spectre-mitigations", str);
11359 }
11360 }
11361
11362 if (const char* str = op.getStringOption("ion-scalar-replacement")) {
11363 if (strcmp(str, "on") == 0) {
11364 jit::JitOptions.disableScalarReplacement = false;
11365 } else if (strcmp(str, "off") == 0) {
11366 jit::JitOptions.disableScalarReplacement = true;
11367 } else {
11368 return OptionFailure("ion-scalar-replacement", str);
11369 }
11370 }
11371
11372 if (op.getStringOption("ion-shared-stubs")) {
11373 // Dead option, preserved for now for potential fuzzer interaction.
11374 }
11375
11376 if (const char* str = op.getStringOption("ion-gvn")) {
11377 if (strcmp(str, "off") == 0) {
11378 jit::JitOptions.disableGvn = true;
11379 } else if (strcmp(str, "on") != 0 && strcmp(str, "optimistic") != 0 &&
11380 strcmp(str, "pessimistic") != 0) {
11381 // We accept "pessimistic" and "optimistic" as synonyms for "on"
11382 // for backwards compatibility.
11383 return OptionFailure("ion-gvn", str);
11384 }
11385 }
11386
11387 if (const char* str = op.getStringOption("ion-licm")) {
11388 if (strcmp(str, "on") == 0) {
11389 jit::JitOptions.disableLicm = false;
11390 } else if (strcmp(str, "off") == 0) {
11391 jit::JitOptions.disableLicm = true;
11392 } else {
11393 return OptionFailure("ion-licm", str);
11394 }
11395 }
11396
11397 if (const char* str = op.getStringOption("ion-edgecase-analysis")) {
11398 if (strcmp(str, "on") == 0) {
11399 jit::JitOptions.disableEdgeCaseAnalysis = false;
11400 } else if (strcmp(str, "off") == 0) {
11401 jit::JitOptions.disableEdgeCaseAnalysis = true;
11402 } else {
11403 return OptionFailure("ion-edgecase-analysis", str);
11404 }
11405 }
11406
11407 if (const char* str = op.getStringOption("ion-pruning")) {
11408 if (strcmp(str, "on") == 0) {
11409 jit::JitOptions.disablePruning = false;
11410 } else if (strcmp(str, "off") == 0) {
11411 jit::JitOptions.disablePruning = true;
11412 } else {
11413 return OptionFailure("ion-pruning", str);
11414 }
11415 }
11416
11417 if (const char* str = op.getStringOption("ion-range-analysis")) {
11418 if (strcmp(str, "on") == 0) {
11419 jit::JitOptions.disableRangeAnalysis = false;
11420 } else if (strcmp(str, "off") == 0) {
11421 jit::JitOptions.disableRangeAnalysis = true;
11422 } else {
11423 return OptionFailure("ion-range-analysis", str);
11424 }
11425 }
11426
11427 if (const char* str = op.getStringOption("ion-sink")) {
11428 if (strcmp(str, "on") == 0) {
11429 jit::JitOptions.disableSink = false;
11430 } else if (strcmp(str, "off") == 0) {
11431 jit::JitOptions.disableSink = true;
11432 } else {
11433 return OptionFailure("ion-sink", str);
11434 }
11435 }
11436
11437 if (const char* str = op.getStringOption("ion-instruction-reordering")) {
11438 if (strcmp(str, "on") == 0) {
11439 jit::JitOptions.disableInstructionReordering = false;
11440 } else if (strcmp(str, "off") == 0) {
11441 jit::JitOptions.disableInstructionReordering = true;
11442 } else {
11443 return OptionFailure("ion-instruction-reordering", str);
11444 }
11445 }
11446
11447 if (op.getBoolOption("ion-check-range-analysis")) {
11448 jit::JitOptions.checkRangeAnalysis = true;
11449 }
11450
11451 if (op.getBoolOption("ion-extra-checks")) {
11452 jit::JitOptions.runExtraChecks = true;
11453 }
11454
11455 if (const char* str = op.getStringOption("ion-inlining")) {
11456 if (strcmp(str, "on") == 0) {
11457 jit::JitOptions.disableInlining = false;
11458 } else if (strcmp(str, "off") == 0) {
11459 jit::JitOptions.disableInlining = true;
11460 } else {
11461 return OptionFailure("ion-inlining", str);
11462 }
11463 }
11464
11465 if (const char* str = op.getStringOption("ion-osr")) {
11466 if (strcmp(str, "on") == 0) {
11467 jit::JitOptions.osr = true;
11468 } else if (strcmp(str, "off") == 0) {
11469 jit::JitOptions.osr = false;
11470 } else {
11471 return OptionFailure("ion-osr", str);
11472 }
11473 }
11474
11475 if (const char* str = op.getStringOption("ion-limit-script-size")) {
11476 if (strcmp(str, "on") == 0) {
11477 jit::JitOptions.limitScriptSize = true;
11478 } else if (strcmp(str, "off") == 0) {
11479 jit::JitOptions.limitScriptSize = false;
11480 } else {
11481 return OptionFailure("ion-limit-script-size", str);
11482 }
11483 }
11484
11485 int32_t warmUpThreshold = op.getIntOption("ion-warmup-threshold");
11486 if (warmUpThreshold >= 0) {
11487 jit::JitOptions.setNormalIonWarmUpThreshold(warmUpThreshold);
11488 }
11489
11490 warmUpThreshold = op.getIntOption("baseline-warmup-threshold");
11491 if (warmUpThreshold >= 0) {
11492 jit::JitOptions.baselineJitWarmUpThreshold = warmUpThreshold;
11493 }
11494
11495 warmUpThreshold = op.getIntOption("trial-inlining-warmup-threshold");
11496 if (warmUpThreshold >= 0) {
11497 jit::JitOptions.trialInliningWarmUpThreshold = warmUpThreshold;
11498 }
11499
11500 warmUpThreshold = op.getIntOption("regexp-warmup-threshold");
11501 if (warmUpThreshold >= 0) {
11502 jit::JitOptions.regexpWarmUpThreshold = warmUpThreshold;
11503 }
11504
11505 if (op.getBoolOption("baseline-eager")) {
11506 jit::JitOptions.setEagerBaselineCompilation();
11507 }
11508
11509 if (op.getBoolOption("blinterp")) {
11510 jit::JitOptions.baselineInterpreter = true;
11511 }
11512
11513 if (op.getBoolOption("no-blinterp")) {
11514 jit::JitOptions.baselineInterpreter = false;
11515 }
11516
11517 warmUpThreshold = op.getIntOption("blinterp-warmup-threshold");
11518 if (warmUpThreshold >= 0) {
11519 jit::JitOptions.baselineInterpreterWarmUpThreshold = warmUpThreshold;
11520 }
11521
11522 if (op.getBoolOption("blinterp-eager")) {
11523 jit::JitOptions.baselineInterpreterWarmUpThreshold = 0;
11524 }
11525
11526 if (op.getBoolOption("no-baseline")) {
11527 jit::JitOptions.baselineJit = false;
11528 }
11529
11530 if (op.getBoolOption("no-warp-async")) {
11531 jit::JitOptions.warpAsync = false;
11532 }
11533
11534 if (op.getBoolOption("no-warp-generator")) {
11535 jit::JitOptions.warpGenerator = false;
11536 }
11537
11538 if (op.getBoolOption("warp-async")) {
11539 jit::JitOptions.warpAsync = true;
11540 }
11541
11542 if (op.getBoolOption("warp-generator")) {
11543 jit::JitOptions.warpGenerator = true;
11544 }
11545
11546 if (op.getBoolOption("no-ion")) {
11547 jit::JitOptions.ion = false;
11548 }
11549
11550 if (op.getBoolOption("no-native-regexp")) {
11551 jit::JitOptions.nativeRegExp = false;
11552 }
11553
11554 if (op.getBoolOption("trace-regexp-parser")) {
11555 jit::JitOptions.traceRegExpParser = true;
11556 }
11557 if (op.getBoolOption("trace-regexp-assembler")) {
11558 jit::JitOptions.traceRegExpAssembler = true;
11559 }
11560 if (op.getBoolOption("trace-regexp-interpreter")) {
11561 jit::JitOptions.traceRegExpInterpreter = true;
11562 }
11563 if (op.getBoolOption("trace-regexp-peephole")) {
11564 jit::JitOptions.traceRegExpPeephole = true;
11565 }
11566
11567 int32_t inliningEntryThreshold = op.getIntOption("inlining-entry-threshold");
11568 if (inliningEntryThreshold > 0) {
11569 jit::JitOptions.inliningEntryThreshold = inliningEntryThreshold;
11570 }
11571
11572 int32_t smallFunctionLength = op.getIntOption("small-function-length");
11573 if (smallFunctionLength > 0) {
11574 jit::JitOptions.smallFunctionMaxBytecodeLength = smallFunctionLength;
11575 }
11576
11577 if (const char* str = op.getStringOption("ion-regalloc")) {
11578 jit::JitOptions.forcedRegisterAllocator = jit::LookupRegisterAllocator(str);
11579 if (!jit::JitOptions.forcedRegisterAllocator.isSome()) {
11580 return OptionFailure("ion-regalloc", str);
11581 }
11582 }
11583
11584 if (op.getBoolOption("ion-eager")) {
11585 jit::JitOptions.setEagerIonCompilation();
11586 }
11587
11588 offthreadCompilation = true;
11589 if (const char* str = op.getStringOption("ion-offthread-compile")) {
11590 if (strcmp(str, "off") == 0) {
11591 offthreadCompilation = false;
11592 } else if (strcmp(str, "on") != 0) {
11593 return OptionFailure("ion-offthread-compile", str);
11594 }
11595 }
11596 cx->runtime()->setOffthreadIonCompilationEnabled(offthreadCompilation);
11597
11598 if (op.getStringOption("ion-parallel-compile")) {
11599 fprintf(stderr,
11600 "--ion-parallel-compile is deprecated. Please use "
11601 "--ion-offthread-compile instead.\n");
11602 return false;
11603 }
11604
11605 if (const char* str = op.getStringOption("shared-memory")) {
11606 if (strcmp(str, "off") == 0) {
11607 enableSharedMemory = false;
11608 } else if (strcmp(str, "on") == 0) {
11609 enableSharedMemory = true;
11610 } else {
11611 return OptionFailure("shared-memory", str);
11612 }
11613 }
11614
11615 if (op.getBoolOption("no-large-arraybuffers")) {
11616 JS::SetLargeArrayBuffersEnabled(false);
11617 }
11618
11619 if (op.getBoolOption("disable-bailout-loop-check")) {
11620 jit::JitOptions.disableBailoutLoopCheck = true;
11621 }
11622
11623 #if defined(JS_CODEGEN_ARM)
11624 if (const char* str = op.getStringOption("arm-hwcap")) {
11625 jit::ParseARMHwCapFlags(str);
11626 jit::ComputeJitSupportFlags();
11627 }
11628
11629 int32_t fill = op.getIntOption("arm-asm-nop-fill");
11630 if (fill >= 0) {
11631 jit::Assembler::NopFill = fill;
11632 }
11633
11634 int32_t poolMaxOffset = op.getIntOption("asm-pool-max-offset");
11635 if (poolMaxOffset >= 5 && poolMaxOffset <= 1024) {
11636 jit::Assembler::AsmPoolMaxOffset = poolMaxOffset;
11637 }
11638 #endif
11639
11640 #if defined(JS_SIMULATOR_ARM)
11641 if (op.getBoolOption("arm-sim-icache-checks")) {
11642 jit::SimulatorProcess::ICacheCheckingDisableCount = 0;
11643 }
11644
11645 int32_t stopAt = op.getIntOption("arm-sim-stop-at");
11646 if (stopAt >= 0) {
11647 jit::Simulator::StopSimAt = stopAt;
11648 }
11649 #elif defined(JS_SIMULATOR_MIPS32) || defined(JS_SIMULATOR_MIPS64)
11650 if (op.getBoolOption("mips-sim-icache-checks")) {
11651 jit::SimulatorProcess::ICacheCheckingDisableCount = 0;
11652 }
11653
11654 int32_t stopAt = op.getIntOption("mips-sim-stop-at");
11655 if (stopAt >= 0) {
11656 jit::Simulator::StopSimAt = stopAt;
11657 }
11658 #endif
11659
11660 reportWarnings = op.getBoolOption('w');
11661 compileOnly = op.getBoolOption('c');
11662 printTiming = op.getBoolOption('b');
11663 enableDisassemblyDumps = op.getBoolOption('D');
11664 cx->runtime()->profilingScripts =
11665 enableCodeCoverage || enableDisassemblyDumps;
11666
11667 #ifdef DEBUG
11668 dumpEntrainedVariables = op.getBoolOption("dump-entrained-variables");
11669 #endif
11670
11671 #ifdef JS_GC_ZEAL
11672 const char* zealStr = op.getStringOption("gc-zeal");
11673 if (zealStr) {
11674 if (!cx->runtime()->gc.parseAndSetZeal(zealStr)) {
11675 return false;
11676 }
11677 uint32_t nextScheduled;
11678 cx->runtime()->gc.getZealBits(&gZealBits, &gZealFrequency, &nextScheduled);
11679 }
11680 #endif
11681
11682 return true;
11683 }
11684
SetWorkerContextOptions(JSContext * cx)11685 static void SetWorkerContextOptions(JSContext* cx) {
11686 // Copy option values from the main thread.
11687 JS::ContextOptionsRef(cx)
11688 .setAsmJS(enableAsmJS)
11689 .setWasm(enableWasm)
11690 .setWasmBaseline(enableWasmBaseline)
11691 #if defined(ENABLE_WASM_CRANELIFT)
11692 .setWasmCranelift(enableWasmOptimizing)
11693 .setWasmIon(false)
11694 #else
11695 .setWasmCranelift(false)
11696 .setWasmIon(enableWasmOptimizing)
11697 #endif
11698 #define WASM_FEATURE(NAME, ...) .setWasm##NAME(enableWasm##NAME)
11699 JS_FOR_WASM_FEATURES(WASM_FEATURE, WASM_FEATURE)
11700 #undef WASM_FEATURE
11701
11702 #ifdef ENABLE_WASM_SIMD_WORMHOLE
11703 .setWasmSimdWormhole(enableWasmSimdWormhole)
11704 #endif
11705 .setWasmVerbose(enableWasmVerbose)
11706 .setTestWasmAwaitTier2(enableTestWasmAwaitTier2)
11707 .setSourcePragmas(enableSourcePragmas);
11708
11709 cx->runtime()->setOffthreadIonCompilationEnabled(offthreadCompilation);
11710 cx->runtime()->profilingScripts =
11711 enableCodeCoverage || enableDisassemblyDumps;
11712
11713 #ifdef JS_GC_ZEAL
11714 if (gZealBits && gZealFrequency) {
11715 for (size_t i = 0; i < size_t(gc::ZealMode::Count); i++) {
11716 if (gZealBits & (1 << i)) {
11717 cx->runtime()->gc.setZeal(i, gZealFrequency);
11718 }
11719 }
11720 }
11721 #endif
11722
11723 JS_SetNativeStackQuota(cx, gWorkerStackSize);
11724 }
11725
PrintUnhandledRejection(JSContext * cx,Handle<PromiseObject * > promise)11726 [[nodiscard]] static bool PrintUnhandledRejection(
11727 JSContext* cx, Handle<PromiseObject*> promise) {
11728 RootedValue reason(cx, promise->reason());
11729 RootedObject site(cx, promise->resolutionSite());
11730
11731 RootedString str(cx, JS_ValueToSource(cx, reason));
11732 if (!str) {
11733 return false;
11734 }
11735
11736 UniqueChars utf8chars = JS_EncodeStringToUTF8(cx, str);
11737 if (!utf8chars) {
11738 return false;
11739 }
11740
11741 FILE* fp = ErrorFilePointer();
11742 fprintf(fp, "Unhandled rejection: %s\n", utf8chars.get());
11743
11744 if (!site) {
11745 fputs("(no stack trace available)\n", stderr);
11746 return true;
11747 }
11748
11749 JSPrincipals* principals = cx->realm()->principals();
11750 RootedString stackStr(cx);
11751 if (!BuildStackString(cx, principals, site, &stackStr, 2)) {
11752 return false;
11753 }
11754
11755 UniqueChars stack = JS_EncodeStringToUTF8(cx, stackStr);
11756 if (!stack) {
11757 return false;
11758 }
11759
11760 fputs("Stack:\n", fp);
11761 fputs(stack.get(), fp);
11762
11763 return true;
11764 }
11765
ReportUnhandledRejections(JSContext * cx)11766 [[nodiscard]] static bool ReportUnhandledRejections(JSContext* cx) {
11767 ShellContext* sc = GetShellContext(cx);
11768 if (!sc->trackUnhandledRejections) {
11769 return true;
11770 }
11771
11772 if (!sc->unhandledRejectedPromises) {
11773 return true;
11774 }
11775
11776 AutoRealm ar(cx, sc->unhandledRejectedPromises);
11777
11778 if (!SetObject::size(cx, sc->unhandledRejectedPromises)) {
11779 return true;
11780 }
11781
11782 sc->exitCode = EXITCODE_RUNTIME_ERROR;
11783
11784 RootedValue iter(cx);
11785 if (!SetObject::iterator(cx, SetObject::IteratorKind::Values,
11786 sc->unhandledRejectedPromises, &iter)) {
11787 return false;
11788 }
11789
11790 Rooted<SetIteratorObject*> iterObj(cx,
11791 &iter.toObject().as<SetIteratorObject>());
11792 JSObject* obj = SetIteratorObject::createResult(cx);
11793 if (!obj) {
11794 return false;
11795 }
11796
11797 RootedArrayObject resultObj(cx, &obj->as<ArrayObject>());
11798 while (true) {
11799 bool done = SetIteratorObject::next(iterObj, resultObj);
11800 if (done) {
11801 break;
11802 }
11803
11804 RootedObject obj(cx, &resultObj->getDenseElement(0).toObject());
11805 Rooted<PromiseObject*> promise(cx, obj->maybeUnwrapIf<PromiseObject>());
11806 if (!promise) {
11807 FILE* fp = ErrorFilePointer();
11808 fputs(
11809 "Unhandled rejection: dead proxy found in unhandled "
11810 "rejections set\n",
11811 fp);
11812 continue;
11813 }
11814
11815 AutoRealm ar2(cx, promise);
11816
11817 if (!PrintUnhandledRejection(cx, promise)) {
11818 return false;
11819 }
11820 }
11821
11822 sc->unhandledRejectedPromises = nullptr;
11823
11824 return true;
11825 }
11826
Shell(JSContext * cx,OptionParser * op)11827 static int Shell(JSContext* cx, OptionParser* op) {
11828 if (JS::TraceLoggerSupported()) {
11829 JS::StartTraceLogger(cx);
11830 }
11831 #ifdef JS_STRUCTURED_SPEW
11832 cx->spewer().enableSpewing();
11833 #endif
11834
11835 auto exitShell = MakeScopeExit([&] {
11836 if (JS::TraceLoggerSupported()) {
11837 JS::SpewTraceLoggerForCurrentProcess();
11838 JS::StopTraceLogger(cx);
11839 }
11840 #ifdef JS_STRUCTURED_SPEW
11841 cx->spewer().disableSpewing();
11842 #endif
11843 });
11844
11845 if (op->getBoolOption("wasm-compile-and-serialize")) {
11846 #ifdef __wasi__
11847 MOZ_CRASH("WASI doesn't support wasm");
11848 #else
11849 if (!WasmCompileAndSerialize(cx)) {
11850 // Errors have been printed directly to stderr.
11851 MOZ_ASSERT(!cx->isExceptionPending());
11852 return -1;
11853 }
11854 #endif
11855 return EXIT_SUCCESS;
11856 }
11857
11858 #ifdef MOZ_CODE_COVERAGE
11859 InstallCoverageSignalHandlers();
11860 #endif
11861
11862 Maybe<JS::AutoDisableGenerationalGC> noggc;
11863 if (op->getBoolOption("no-ggc")) {
11864 noggc.emplace(cx);
11865 }
11866
11867 Maybe<AutoDisableCompactingGC> nocgc;
11868 if (op->getBoolOption("no-cgc")) {
11869 nocgc.emplace(cx);
11870 }
11871
11872 if (op->getBoolOption("fuzzing-safe")) {
11873 fuzzingSafe = true;
11874 } else {
11875 fuzzingSafe =
11876 (getenv("MOZ_FUZZING_SAFE") && getenv("MOZ_FUZZING_SAFE")[0] != '0');
11877 }
11878
11879 #ifdef DEBUG
11880 if (op->getBoolOption("differential-testing")) {
11881 JS::SetSupportDifferentialTesting(true);
11882 }
11883 #endif
11884
11885 if (op->getBoolOption("disable-oom-functions")) {
11886 disableOOMFunctions = true;
11887 }
11888
11889 if (op->getBoolOption("more-compartments")) {
11890 defaultToSameCompartment = false;
11891 }
11892
11893 bool reprl_mode = FuzzilliUseReprlMode(op);
11894
11895 // Begin REPRL Loop
11896 int result = EXIT_SUCCESS;
11897 do {
11898 JS::RealmOptions options;
11899 SetStandardRealmOptions(options);
11900 RootedObject glob(
11901 cx, NewGlobalObject(cx, options, nullptr, ShellGlobalKind::WindowProxy,
11902 /* immutablePrototype = */ true));
11903 if (!glob) {
11904 return 1;
11905 }
11906
11907 JSAutoRealm ar(cx, glob);
11908
11909 #ifdef FUZZING_INTERFACES
11910 if (fuzzHaveModule) {
11911 return FuzzJSRuntimeStart(cx, &sArgc, &sArgv);
11912 }
11913 #endif
11914
11915 ShellContext* sc = GetShellContext(cx);
11916 sc->exitCode = 0;
11917 result = EXIT_SUCCESS;
11918 {
11919 AutoReportException are(cx);
11920 if (!ProcessArgs(cx, op) && !sc->quitting) {
11921 result = EXITCODE_RUNTIME_ERROR;
11922 }
11923 }
11924
11925 /*
11926 * The job queue must be drained even on error to finish outstanding async
11927 * tasks before the main thread JSRuntime is torn down. Drain after
11928 * uncaught exceptions have been reported since draining runs callbacks.
11929 */
11930 RunShellJobs(cx);
11931
11932 // Only if there's no other error, report unhandled rejections.
11933 if (!result && !sc->exitCode) {
11934 AutoReportException are(cx);
11935 if (!ReportUnhandledRejections(cx)) {
11936 FILE* fp = ErrorFilePointer();
11937 fputs("Error while printing unhandled rejection\n", fp);
11938 }
11939 }
11940
11941 if (sc->exitCode) {
11942 result = sc->exitCode;
11943 }
11944
11945 #ifdef FUZZING_JS_FUZZILLI
11946 if (reprl_mode) {
11947 fflush(stdout);
11948 fflush(stderr);
11949 // Send return code to parent and reset edge counters.
11950 int status = (result & 0xff) << 8;
11951 MOZ_RELEASE_ASSERT(write(REPRL_CWFD, &status, 4) == 4);
11952 __sanitizer_cov_reset_edgeguards();
11953 }
11954 #endif
11955
11956 if (enableDisassemblyDumps) {
11957 AutoReportException are(cx);
11958 if (!js::DumpRealmPCCounts(cx)) {
11959 result = EXITCODE_OUT_OF_MEMORY;
11960 }
11961 }
11962
11963 // End REPRL loop
11964 } while (reprl_mode);
11965
11966 return result;
11967 }
11968
11969 // Used to allocate memory when jemalloc isn't yet initialized.
JS_DECLARE_NEW_METHODS(SystemAlloc_New,malloc,static)11970 JS_DECLARE_NEW_METHODS(SystemAlloc_New, malloc, static)
11971
11972 static void SetOutputFile(const char* const envVar, RCFile* defaultOut,
11973 RCFile** outFileP) {
11974 RCFile* outFile;
11975
11976 const char* outPath = getenv(envVar);
11977 FILE* newfp;
11978 if (outPath && *outPath && (newfp = fopen(outPath, "w"))) {
11979 outFile = SystemAlloc_New<RCFile>(newfp);
11980 } else {
11981 outFile = defaultOut;
11982 }
11983
11984 if (!outFile) {
11985 MOZ_CRASH("Failed to allocate output file");
11986 }
11987
11988 outFile->acquire();
11989 *outFileP = outFile;
11990 }
11991
PreInit()11992 static void PreInit() {
11993 #ifdef XP_WIN
11994 const char* crash_option = getenv("XRE_NO_WINDOWS_CRASH_DIALOG");
11995 if (crash_option && crash_option[0] == '1') {
11996 // Disable the segfault dialog. We want to fail the tests immediately
11997 // instead of hanging automation.
11998 UINT newMode = SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX;
11999 UINT prevMode = SetErrorMode(newMode);
12000 SetErrorMode(prevMode | newMode);
12001 }
12002 #endif
12003 }
12004
12005 #ifndef JS_WITHOUT_NSPR
12006 class AutoLibraryLoader {
12007 Vector<PRLibrary*, 4, SystemAllocPolicy> libraries;
12008
12009 public:
~AutoLibraryLoader()12010 ~AutoLibraryLoader() {
12011 for (auto dll : libraries) {
12012 PR_UnloadLibrary(dll);
12013 }
12014 }
12015
load(const char * path)12016 PRLibrary* load(const char* path) {
12017 PRLibSpec libSpec;
12018 libSpec.type = PR_LibSpec_Pathname;
12019 libSpec.value.pathname = path;
12020 PRLibrary* dll = PR_LoadLibraryWithFlags(libSpec, PR_LD_NOW | PR_LD_GLOBAL);
12021 if (!dll) {
12022 fprintf(stderr, "LoadLibrary '%s' failed with code %d\n", path,
12023 PR_GetError());
12024 MOZ_CRASH("Failed to load library");
12025 }
12026
12027 MOZ_ALWAYS_TRUE(libraries.append(dll));
12028 return dll;
12029 }
12030 };
12031 #endif
12032
ReadSelfHostedXDRFile(JSContext * cx,FileContents & buf)12033 static bool ReadSelfHostedXDRFile(JSContext* cx, FileContents& buf) {
12034 FILE* file = fopen(selfHostedXDRPath, "rb");
12035 if (!file) {
12036 fprintf(stderr, "Can't open self-hosted stencil XDR file.\n");
12037 return false;
12038 }
12039 AutoCloseFile autoClose(file);
12040
12041 struct stat st;
12042 if (fstat(fileno(file), &st) < 0) {
12043 fprintf(stderr, "Unable to stat self-hosted stencil XDR file.\n");
12044 return false;
12045 }
12046
12047 if (st.st_size >= INT32_MAX) {
12048 fprintf(stderr, "self-hosted stencil XDR file too large.\n");
12049 return false;
12050 }
12051 uint32_t filesize = uint32_t(st.st_size);
12052
12053 if (!buf.growBy(filesize)) {
12054 return false;
12055 }
12056 size_t cc = fread(buf.begin(), 1, filesize, file);
12057 if (cc != filesize) {
12058 fprintf(stderr, "Short read on self-hosted stencil XDR file.\n");
12059 return false;
12060 }
12061
12062 return true;
12063 }
12064
WriteSelfHostedXDRFile(JSContext * cx,JS::SelfHostedCache buffer)12065 static bool WriteSelfHostedXDRFile(JSContext* cx, JS::SelfHostedCache buffer) {
12066 FILE* file = fopen(selfHostedXDRPath, "wb");
12067 if (!file) {
12068 JS_ReportErrorUTF8(cx, "Can't open self-hosted stencil XDR file.");
12069 return false;
12070 }
12071 AutoCloseFile autoClose(file);
12072
12073 size_t cc = fwrite(buffer.Elements(), 1, buffer.LengthBytes(), file);
12074 if (cc != buffer.LengthBytes()) {
12075 JS_ReportErrorUTF8(cx, "Short write on self-hosted stencil XDR file.");
12076 return false;
12077 }
12078
12079 return true;
12080 }
12081
main(int argc,char ** argv)12082 int main(int argc, char** argv) {
12083 PreInit();
12084
12085 sArgc = argc;
12086 sArgv = argv;
12087
12088 int result;
12089
12090 setlocale(LC_ALL, "");
12091
12092 // Special-case stdout and stderr. We bump their refcounts to prevent them
12093 // from getting closed and then having some printf fail somewhere.
12094 RCFile rcStdout(stdout);
12095 rcStdout.acquire();
12096 RCFile rcStderr(stderr);
12097 rcStderr.acquire();
12098
12099 SetOutputFile("JS_STDOUT", &rcStdout, &gOutFile);
12100 SetOutputFile("JS_STDERR", &rcStderr, &gErrFile);
12101
12102 // Start the engine.
12103 if (const char* message = JS_InitWithFailureDiagnostic()) {
12104 fprintf(gErrFile->fp, "JS_Init failed: %s\n", message);
12105 return 1;
12106 }
12107
12108 // `selfHostedXDRBuffer` contains XDR buffer of the self-hosted JS.
12109 // A part of it is borrowed by ImmutableScriptData of the self-hosted scripts.
12110 //
12111 // This buffer's should outlive JS_Shutdown.
12112 Maybe<FileContents> selfHostedXDRBuffer;
12113
12114 auto shutdownEngine = MakeScopeExit([] { JS_ShutDown(); });
12115
12116 OptionParser op("Usage: {progname} [options] [[script] scriptArgs*]");
12117
12118 op.setDescription(
12119 "The SpiderMonkey shell provides a command line interface to the "
12120 "JavaScript engine. Code and file options provided via the command line "
12121 "are "
12122 "run left to right. If provided, the optional script argument is run "
12123 "after "
12124 "all options have been processed. Just-In-Time compilation modes may be "
12125 "enabled via "
12126 "command line options.");
12127 op.setDescriptionWidth(72);
12128 op.setHelpWidth(80);
12129 op.setVersion(JS_GetImplementationVersion());
12130
12131 if (!op.addMultiStringOption(
12132 'f', "file", "PATH",
12133 "File path to run, parsing file contents as UTF-8") ||
12134 !op.addMultiStringOption(
12135 'u', "utf16-file", "PATH",
12136 "File path to run, inflating the file's UTF-8 contents to UTF-16 and "
12137 "then parsing that") ||
12138 !op.addMultiStringOption('m', "module", "PATH", "Module path to run") ||
12139 !op.addMultiStringOption('e', "execute", "CODE", "Inline code to run") ||
12140 !op.addStringOption('\0', "selfhosted-xdr-path", "[filename]",
12141 "Read/Write selfhosted script data from/to the given "
12142 "XDR file") ||
12143 !op.addStringOption('\0', "selfhosted-xdr-mode", "(encode,decode,off)",
12144 "Whether to encode/decode data of the file provided"
12145 "with --selfhosted-xdr-path.") ||
12146 !op.addBoolOption('i', "shell", "Enter prompt after running code") ||
12147 !op.addBoolOption('c', "compileonly",
12148 "Only compile, don't run (syntax checking mode)") ||
12149 !op.addBoolOption('w', "warnings", "Emit warnings") ||
12150 !op.addBoolOption('W', "nowarnings", "Don't emit warnings") ||
12151 !op.addBoolOption('D', "dump-bytecode",
12152 "Dump bytecode with exec count for all scripts") ||
12153 !op.addBoolOption('b', "print-timing",
12154 "Print sub-ms runtime for each file that's run") ||
12155 !op.addBoolOption('\0', "code-coverage",
12156 "Enable code coverage instrumentation.") ||
12157 !op.addBoolOption(
12158 '\0', "disable-parser-deferred-alloc",
12159 "Disable deferred allocation of GC objects until after parser") ||
12160 #ifdef DEBUG
12161 !op.addBoolOption('O', "print-alloc",
12162 "Print the number of allocations at exit") ||
12163 #endif
12164 !op.addOptionalStringArg("script",
12165 "A script to execute (after all options)") ||
12166 !op.addOptionalMultiStringArg(
12167 "scriptArgs",
12168 "String arguments to bind as |scriptArgs| in the "
12169 "shell's global") ||
12170 !op.addIntOption(
12171 '\0', "cpu-count", "COUNT",
12172 "Set the number of CPUs (hardware threads) to COUNT, the "
12173 "default is the actual number of CPUs. The total number of "
12174 "background helper threads is the CPU count plus some constant.",
12175 -1) ||
12176 !op.addIntOption('\0', "thread-count", "COUNT", "Alias for --cpu-count.",
12177 -1) ||
12178 !op.addBoolOption('\0', "ion", "Enable IonMonkey (default)") ||
12179 !op.addBoolOption('\0', "no-ion", "Disable IonMonkey") ||
12180 !op.addBoolOption('\0', "no-ion-for-main-context",
12181 "Disable IonMonkey for the main context only") ||
12182 !op.addIntOption('\0', "inlining-entry-threshold", "COUNT",
12183 "The minimum stub entry count before trial-inlining a"
12184 " call",
12185 -1) ||
12186 !op.addIntOption('\0', "small-function-length", "COUNT",
12187 "The maximum bytecode length of a 'small function' for "
12188 "the purpose of inlining.",
12189 -1) ||
12190 !op.addBoolOption('\0', "no-asmjs", "Disable asm.js compilation") ||
12191 !op.addStringOption(
12192 '\0', "wasm-compiler", "[option]",
12193 "Choose to enable a subset of the wasm compilers, valid options are "
12194 "'none', 'baseline', 'ion', 'cranelift', 'optimizing', "
12195 "'baseline+ion', "
12196 "'baseline+cranelift', 'baseline+optimizing'. Choosing 'ion' when "
12197 "Ion is "
12198 "not available or 'cranelift' when Cranelift is not available will "
12199 "fail; "
12200 "use 'optimizing' for cross-compiler compatibility.") ||
12201 !op.addBoolOption('\0', "wasm-verbose",
12202 "Enable WebAssembly verbose logging") ||
12203 !op.addBoolOption('\0', "disable-wasm-huge-memory",
12204 "Disable WebAssembly huge memory") ||
12205 !op.addBoolOption('\0', "test-wasm-await-tier2",
12206 "Forcibly activate tiering and block "
12207 "instantiation on completion of tier2") ||
12208 #define WASM_DEFAULT_FEATURE(NAME, LOWER_NAME, COMPILE_PRED, COMPILER_PRED, \
12209 FLAG_PRED, SHELL, ...) \
12210 !op.addBoolOption('\0', "no-wasm-" SHELL, "Disable wasm " SHELL "feature.") ||
12211 #define WASM_EXPERIMENTAL_FEATURE(NAME, LOWER_NAME, COMPILE_PRED, \
12212 COMPILER_PRED, FLAG_PRED, SHELL, ...) \
12213 !op.addBoolOption('\0', "wasm-" SHELL, \
12214 "Enable experimental wasm " SHELL "feature.") ||
12215 JS_FOR_WASM_FEATURES(WASM_DEFAULT_FEATURE, WASM_EXPERIMENTAL_FEATURE)
12216 #undef WASM_DEFAULT_FEATURE
12217 #undef WASM_EXPERIMENTAL_FEATURE
12218 #ifdef ENABLE_WASM_SIMD_WORMHOLE
12219 !op.addBoolOption('\0', "wasm-simd-wormhole",
12220 "Enable wasm SIMD wormhole (UTSL)") ||
12221 #else
12222 !op.addBoolOption('\0', "wasm-simd-wormhole", "No-op") ||
12223 #endif
12224 !op.addBoolOption('\0', "no-native-regexp",
12225 "Disable native regexp compilation") ||
12226 !op.addIntOption(
12227 '\0', "regexp-warmup-threshold", "COUNT",
12228 "Wait for COUNT invocations before compiling regexps to native code "
12229 "(default 10)",
12230 -1) ||
12231 !op.addBoolOption('\0', "trace-regexp-parser", "Trace regexp parsing") ||
12232 !op.addBoolOption('\0', "trace-regexp-assembler",
12233 "Trace regexp assembler") ||
12234 !op.addBoolOption('\0', "trace-regexp-interpreter",
12235 "Trace regexp interpreter") ||
12236 !op.addBoolOption('\0', "trace-regexp-peephole",
12237 "Trace regexp peephole optimization") ||
12238 !op.addBoolOption('\0', "enable-streams",
12239 "Enable WHATWG Streams (default)") ||
12240 !op.addBoolOption('\0', "no-streams", "Disable WHATWG Streams") ||
12241 !op.addBoolOption('\0', "enable-readable-byte-streams",
12242 "Enable support for WHATWG ReadableStreams of type "
12243 "'bytes'") ||
12244 !op.addBoolOption('\0', "enable-byob-stream-readers",
12245 "Enable support for getting BYOB readers for WHATWG "
12246 "ReadableStreams of type \"bytes\"") ||
12247 !op.addBoolOption('\0', "enable-writable-streams",
12248 "Enable support for WHATWG WritableStreams") ||
12249 !op.addBoolOption('\0', "enable-readablestream-pipeto",
12250 "Enable support for "
12251 "WHATWG ReadableStream.prototype.pipeTo") ||
12252 !op.addBoolOption('\0', "disable-weak-refs", "Disable weak references") ||
12253 !op.addBoolOption('\0', "disable-tosource", "Disable toSource/uneval") ||
12254 !op.addBoolOption('\0', "disable-property-error-message-fix",
12255 "Disable fix for the error message when accessing "
12256 "property of null or undefined") ||
12257 !op.addBoolOption('\0', "enable-iterator-helpers",
12258 "Enable iterator helpers") ||
12259 !op.addBoolOption('\0', "disable-private-fields",
12260 "Disable private class fields") ||
12261 !op.addBoolOption('\0', "disable-private-methods",
12262 "Disable private class methods") ||
12263 !op.addBoolOption(
12264 '\0', "enable-ergonomic-brand-checks",
12265 "Enable ergonomic brand checks for private class fields (no-op)") ||
12266 !op.addBoolOption(
12267 '\0', "disable-ergonomic-brand-checks",
12268 "Disable ergonomic brand checks for private class fields") ||
12269 !op.addBoolOption('\0', "enable-top-level-await",
12270 "Enable top-level await") ||
12271 !op.addBoolOption('\0', "enable-class-static-blocks",
12272 "Enable class static blocks") ||
12273 !op.addBoolOption('\0', "off-thread-parse-global",
12274 "Use parseGlobal in all off-thread compilation") ||
12275 !op.addBoolOption('\0', "no-large-arraybuffers",
12276 "Disallow creating ArrayBuffers larger than 2 GB on "
12277 "64-bit platforms") ||
12278 !op.addStringOption('\0', "shared-memory", "on/off",
12279 "SharedArrayBuffer and Atomics "
12280 #if SHARED_MEMORY_DEFAULT
12281 "(default: on, off to disable)"
12282 #else
12283 "(default: off, on to enable)"
12284 #endif
12285 ) ||
12286 !op.addStringOption('\0', "spectre-mitigations", "on/off",
12287 "Whether Spectre mitigations are enabled (default: "
12288 "off, on to enable)") ||
12289 !op.addStringOption('\0', "cache-ir-stubs", "on/off/call",
12290 "Use CacheIR stubs (default: on, off to disable, "
12291 "call to enable work-in-progress call ICs)") ||
12292 !op.addStringOption('\0', "ion-shared-stubs", "on/off",
12293 "Use shared stubs (default: on, off to disable)") ||
12294 !op.addStringOption('\0', "ion-scalar-replacement", "on/off",
12295 "Scalar Replacement (default: on, off to disable)") ||
12296 !op.addStringOption('\0', "ion-gvn", "[mode]",
12297 "Specify Ion global value numbering:\n"
12298 " off: disable GVN\n"
12299 " on: enable GVN (default)\n") ||
12300 !op.addStringOption(
12301 '\0', "ion-licm", "on/off",
12302 "Loop invariant code motion (default: on, off to disable)") ||
12303 !op.addStringOption('\0', "ion-edgecase-analysis", "on/off",
12304 "Find edge cases where Ion can avoid bailouts "
12305 "(default: on, off to disable)") ||
12306 !op.addStringOption('\0', "ion-pruning", "on/off",
12307 "Branch pruning (default: on, off to disable)") ||
12308 !op.addStringOption('\0', "ion-range-analysis", "on/off",
12309 "Range analysis (default: on, off to disable)") ||
12310 !op.addStringOption('\0', "ion-sink", "on/off",
12311 "Sink code motion (default: off, on to enable)") ||
12312 !op.addStringOption('\0', "ion-optimization-levels", "on/off",
12313 "No-op for fuzzing") ||
12314 !op.addStringOption('\0', "ion-loop-unrolling", "on/off",
12315 "(NOP for fuzzers)") ||
12316 !op.addStringOption(
12317 '\0', "ion-instruction-reordering", "on/off",
12318 "Instruction reordering (default: off, on to enable)") ||
12319 !op.addBoolOption('\0', "ion-check-range-analysis",
12320 "Range analysis checking") ||
12321 !op.addBoolOption('\0', "ion-extra-checks",
12322 "Perform extra dynamic validation checks") ||
12323 !op.addStringOption(
12324 '\0', "ion-inlining", "on/off",
12325 "Inline methods where possible (default: on, off to disable)") ||
12326 !op.addStringOption(
12327 '\0', "ion-osr", "on/off",
12328 "On-Stack Replacement (default: on, off to disable)") ||
12329 !op.addBoolOption('\0', "disable-bailout-loop-check",
12330 "Turn off bailout loop check") ||
12331 !op.addBoolOption('\0', "scalar-replace-arguments",
12332 "Use scalar replacement to optimize ArgumentsObject") ||
12333 !op.addStringOption(
12334 '\0', "ion-limit-script-size", "on/off",
12335 "Don't compile very large scripts (default: on, off to disable)") ||
12336 !op.addIntOption('\0', "ion-warmup-threshold", "COUNT",
12337 "Wait for COUNT calls or iterations before compiling "
12338 "at the normal optimization level (default: 1000)",
12339 -1) ||
12340 !op.addIntOption('\0', "ion-full-warmup-threshold", "COUNT",
12341 "No-op for fuzzing", -1) ||
12342 !op.addStringOption(
12343 '\0', "ion-regalloc", "[mode]",
12344 "Specify Ion register allocation:\n"
12345 " backtracking: Priority based backtracking register allocation "
12346 "(default)\n"
12347 " testbed: Backtracking allocator with experimental features\n"
12348 " stupid: Simple block local register allocation") ||
12349 !op.addBoolOption(
12350 '\0', "ion-eager",
12351 "Always ion-compile methods (implies --baseline-eager)") ||
12352 !op.addBoolOption('\0', "fast-warmup",
12353 "Reduce warmup thresholds for each tier.") ||
12354 !op.addStringOption('\0', "ion-offthread-compile", "on/off",
12355 "Compile scripts off thread (default: on)") ||
12356 !op.addStringOption('\0', "ion-parallel-compile", "on/off",
12357 "--ion-parallel compile is deprecated. Use "
12358 "--ion-offthread-compile.") ||
12359 !op.addBoolOption('\0', "baseline",
12360 "Enable baseline compiler (default)") ||
12361 !op.addBoolOption('\0', "no-baseline", "Disable baseline compiler") ||
12362 !op.addBoolOption('\0', "no-warp-async",
12363 "Don't Warp compile Async Functions") ||
12364 !op.addBoolOption('\0', "no-warp-generator",
12365 "Warp compile Generator Functions") ||
12366 !op.addBoolOption('\0', "warp-async", "Warp compile Async Functions") ||
12367 !op.addBoolOption('\0', "warp-generator",
12368 "Don't Warp compile Generator Functions") ||
12369 !op.addBoolOption('\0', "baseline-eager",
12370 "Always baseline-compile methods") ||
12371 !op.addIntOption(
12372 '\0', "baseline-warmup-threshold", "COUNT",
12373 "Wait for COUNT calls or iterations before baseline-compiling "
12374 "(default: 10)",
12375 -1) ||
12376 !op.addBoolOption('\0', "blinterp",
12377 "Enable Baseline Interpreter (default)") ||
12378 !op.addBoolOption('\0', "no-blinterp", "Disable Baseline Interpreter") ||
12379 !op.addBoolOption('\0', "blinterp-eager",
12380 "Always Baseline-interpret scripts") ||
12381 !op.addIntOption(
12382 '\0', "blinterp-warmup-threshold", "COUNT",
12383 "Wait for COUNT calls or iterations before Baseline-interpreting "
12384 "(default: 10)",
12385 -1) ||
12386 !op.addIntOption(
12387 '\0', "trial-inlining-warmup-threshold", "COUNT",
12388 "Wait for COUNT calls or iterations before trial-inlining "
12389 "(default: 500)",
12390 -1) ||
12391 !op.addBoolOption(
12392 '\0', "non-writable-jitcode",
12393 "(NOP for fuzzers) Allocate JIT code as non-writable memory.") ||
12394 !op.addBoolOption(
12395 '\0', "no-sse3",
12396 "Pretend CPU does not support SSE3 instructions and above "
12397 "to test JIT codegen (no-op on platforms other than x86 and x64).") ||
12398 !op.addBoolOption(
12399 '\0', "no-ssse3",
12400 "Pretend CPU does not support SSSE3 [sic] instructions and above "
12401 "to test JIT codegen (no-op on platforms other than x86 and x64).") ||
12402 !op.addBoolOption(
12403 '\0', "no-sse41",
12404 "Pretend CPU does not support SSE4.1 instructions "
12405 "to test JIT codegen (no-op on platforms other than x86 and x64).") ||
12406 !op.addBoolOption('\0', "no-sse4", "Alias for --no-sse41") ||
12407 !op.addBoolOption(
12408 '\0', "no-sse42",
12409 "Pretend CPU does not support SSE4.2 instructions "
12410 "to test JIT codegen (no-op on platforms other than x86 and x64).") ||
12411 !op.addBoolOption('\0', "enable-avx",
12412 "AVX is disabled by default. Enable AVX. "
12413 "(no-op on platforms other than x86 and x64).") ||
12414 !op.addBoolOption('\0', "no-avx",
12415 "No-op. AVX is currently disabled by default.") ||
12416 !op.addBoolOption('\0', "more-compartments",
12417 "Make newGlobal default to creating a new "
12418 "compartment.") ||
12419 !op.addBoolOption('\0', "fuzzing-safe",
12420 "Don't expose functions that aren't safe for "
12421 "fuzzers to call") ||
12422 #ifdef DEBUG
12423 !op.addBoolOption('\0', "differential-testing",
12424 "Avoid random/undefined behavior that disturbs "
12425 "differential testing (correctness fuzzing)") ||
12426 #endif
12427 !op.addBoolOption('\0', "disable-oom-functions",
12428 "Disable functions that cause "
12429 "artificial OOMs") ||
12430 !op.addBoolOption('\0', "no-threads", "Disable helper threads") ||
12431 #ifdef DEBUG
12432 !op.addBoolOption('\0', "dump-entrained-variables",
12433 "Print variables which are "
12434 "unnecessarily entrained by inner functions") ||
12435 #endif
12436 !op.addBoolOption('\0', "no-ggc", "Disable Generational GC") ||
12437 !op.addBoolOption('\0', "no-cgc", "Disable Compacting GC") ||
12438 !op.addBoolOption('\0', "no-incremental-gc", "Disable Incremental GC") ||
12439 !op.addStringOption('\0', "nursery-strings", "on/off",
12440 "Allocate strings in the nursery") ||
12441 !op.addStringOption('\0', "nursery-bigints", "on/off",
12442 "Allocate BigInts in the nursery") ||
12443 !op.addIntOption('\0', "available-memory", "SIZE",
12444 "Select GC settings based on available memory (MB)",
12445 0) ||
12446 !op.addStringOption('\0', "arm-hwcap", "[features]",
12447 "Specify ARM code generation features, or 'help' to "
12448 "list all features.") ||
12449 !op.addIntOption('\0', "arm-asm-nop-fill", "SIZE",
12450 "Insert the given number of NOP instructions at all "
12451 "possible pool locations.",
12452 0) ||
12453 !op.addIntOption('\0', "asm-pool-max-offset", "OFFSET",
12454 "The maximum pc relative OFFSET permitted in pool "
12455 "reference instructions.",
12456 1024) ||
12457 !op.addBoolOption('\0', "arm-sim-icache-checks",
12458 "Enable icache flush checks in the ARM "
12459 "simulator.") ||
12460 !op.addIntOption('\0', "arm-sim-stop-at", "NUMBER",
12461 "Stop the ARM simulator after the given "
12462 "NUMBER of instructions.",
12463 -1) ||
12464 !op.addBoolOption('\0', "mips-sim-icache-checks",
12465 "Enable icache flush checks in the MIPS "
12466 "simulator.") ||
12467 !op.addIntOption('\0', "mips-sim-stop-at", "NUMBER",
12468 "Stop the MIPS simulator after the given "
12469 "NUMBER of instructions.",
12470 -1) ||
12471 !op.addIntOption('\0', "nursery-size", "SIZE-MB",
12472 "Set the maximum nursery size in MB",
12473 JS::DefaultNurseryMaxBytes / 1024 / 1024) ||
12474 #ifdef JS_GC_ZEAL
12475 !op.addStringOption('z', "gc-zeal", "LEVEL(;LEVEL)*[,N]",
12476 gc::ZealModeHelpText) ||
12477 #else
12478 !op.addStringOption('z', "gc-zeal", "LEVEL(;LEVEL)*[,N]",
12479 "option ignored in non-gc-zeal builds") ||
12480 #endif
12481 !op.addStringOption('\0', "module-load-path", "DIR",
12482 "Set directory to load modules from") ||
12483 !op.addBoolOption('\0', "no-source-pragmas",
12484 "Disable source(Mapping)URL pragma parsing") ||
12485 !op.addBoolOption('\0', "no-async-stacks", "Disable async stacks") ||
12486 !op.addBoolOption('\0', "async-stacks-capture-debuggee-only",
12487 "Limit async stack capture to only debuggees") ||
12488 !op.addMultiStringOption('\0', "dll", "LIBRARY",
12489 "Dynamically load LIBRARY") ||
12490 !op.addBoolOption('\0', "suppress-minidump",
12491 "Suppress crash minidumps") ||
12492 #ifdef JS_ENABLE_SMOOSH
12493 !op.addBoolOption('\0', "smoosh", "Use SmooshMonkey") ||
12494 !op.addStringOption('\0', "not-implemented-watchfile", "[filename]",
12495 "Track NotImplemented errors in the new frontend") ||
12496 #else
12497 !op.addBoolOption('\0', "smoosh", "No-op") ||
12498 #endif
12499 !op.addBoolOption('\0', "wasm-compile-and-serialize",
12500 "Compile the wasm bytecode from stdin and serialize "
12501 "the results to stdout") ||
12502 #ifdef FUZZING_JS_FUZZILLI
12503 !op.addBoolOption('\0', "reprl", "Enable REPRL mode for fuzzing") ||
12504 #endif
12505 !op.addStringOption('\0', "telemetry-dir", "[directory]",
12506 "Output telemetry results in a directory") ||
12507 !op.addBoolOption('\0', "use-fdlibm-for-sin-cos-tan",
12508 "Use fdlibm for Math.sin, Math.cos, and Math.tan")) {
12509 return EXIT_FAILURE;
12510 }
12511
12512 op.setArgTerminatesOptions("script", true);
12513 op.setArgCapturesRest("scriptArgs");
12514
12515 switch (op.parseArgs(argc, argv)) {
12516 case OptionParser::EarlyExit:
12517 return EXIT_SUCCESS;
12518 case OptionParser::ParseError:
12519 op.printHelp(argv[0]);
12520 return EXIT_FAILURE;
12521 case OptionParser::Fail:
12522 return EXIT_FAILURE;
12523 case OptionParser::Okay:
12524 break;
12525 }
12526
12527 if (op.getHelpOption()) {
12528 return EXIT_SUCCESS;
12529 }
12530
12531 // Record aggregated telemetry data on disk. Do this as early as possible such
12532 // that the telemetry is recording both before starting the context and after
12533 // closing it.
12534 if (op.getStringOption("telemetry-dir")) {
12535 if (!telemetryLock) {
12536 telemetryLock = js_new<Mutex>(mutexid::ShellTelemetry);
12537 if (!telemetryLock) {
12538 return EXIT_FAILURE;
12539 }
12540 }
12541 }
12542 auto writeTelemetryResults = MakeScopeExit([&op] {
12543 if (const char* dir = op.getStringOption("telemetry-dir")) {
12544 WriteTelemetryDataToDisk(dir);
12545 js_free(telemetryLock);
12546 telemetryLock = nullptr;
12547 }
12548 });
12549
12550 /*
12551 * Allow dumping on Linux with the fuzzing flag set, even when running with
12552 * the suid/sgid flag set on the shell.
12553 */
12554 #ifdef XP_LINUX
12555 if (op.getBoolOption("fuzzing-safe")) {
12556 prctl(PR_SET_DUMPABLE, 1);
12557 }
12558 #endif
12559
12560 #ifdef DEBUG
12561 /*
12562 * Process OOM options as early as possible so that we can observe as many
12563 * allocations as possible.
12564 */
12565 OOM_printAllocationCount = op.getBoolOption('O');
12566 #endif
12567
12568 if (op.getBoolOption("no-threads")) {
12569 js::DisableExtraThreads();
12570 }
12571
12572 enableCodeCoverage = op.getBoolOption("code-coverage");
12573 if (enableCodeCoverage) {
12574 js::EnableCodeCoverage();
12575 }
12576
12577 if (const char* xdr = op.getStringOption("selfhosted-xdr-path")) {
12578 shell::selfHostedXDRPath = xdr;
12579 }
12580 if (const char* opt = op.getStringOption("selfhosted-xdr-mode")) {
12581 if (strcmp(opt, "encode") == 0) {
12582 shell::encodeSelfHostedCode = true;
12583 } else if (strcmp(opt, "decode") == 0) {
12584 shell::encodeSelfHostedCode = false;
12585 } else if (strcmp(opt, "off") == 0) {
12586 shell::selfHostedXDRPath = nullptr;
12587 } else {
12588 MOZ_CRASH(
12589 "invalid option value for --selfhosted-xdr-mode, must be "
12590 "encode/decode");
12591 }
12592 }
12593
12594 #ifdef JS_WITHOUT_NSPR
12595 if (!op.getMultiStringOption("dll").empty()) {
12596 fprintf(stderr, "Error: --dll requires NSPR support!\n");
12597 return EXIT_FAILURE;
12598 }
12599 #else
12600 AutoLibraryLoader loader;
12601 MultiStringRange dllPaths = op.getMultiStringOption("dll");
12602 while (!dllPaths.empty()) {
12603 char* path = dllPaths.front();
12604 loader.load(path);
12605 dllPaths.popFront();
12606 }
12607 #endif
12608
12609 if (op.getBoolOption("suppress-minidump")) {
12610 js::NoteIntentionalCrash();
12611 }
12612
12613 if (!InitSharedObjectMailbox()) {
12614 return 1;
12615 }
12616
12617 JS::SetProcessBuildIdOp(ShellBuildId);
12618
12619 // The fake CPU count must be set before initializing the Runtime,
12620 // which spins up the thread pool.
12621 int32_t cpuCount = op.getIntOption("cpu-count"); // What we're really setting
12622 if (cpuCount < 0) {
12623 cpuCount = op.getIntOption("thread-count"); // Legacy name
12624 }
12625 if (cpuCount >= 0 && !SetFakeCPUCount(cpuCount)) {
12626 return 1;
12627 }
12628
12629 /* Use the same parameters as the browser in xpcjsruntime.cpp. */
12630 JSContext* const cx = JS_NewContext(JS::DefaultHeapMaxBytes);
12631 if (!cx) {
12632 return 1;
12633 }
12634
12635 // Register telemetry callbacks, if needed.
12636 if (telemetryLock) {
12637 JS_SetAccumulateTelemetryCallback(cx, AccumulateTelemetryDataCallback);
12638 }
12639
12640 size_t nurseryBytes = op.getIntOption("nursery-size") * 1024L * 1024L;
12641 if (nurseryBytes == 0) {
12642 fprintf(stderr, "Error: --nursery-size parameter must be non-zero.\n");
12643 fprintf(stderr,
12644 "The nursery can be disabled by passing the --no-ggc option.\n");
12645 return EXIT_FAILURE;
12646 }
12647 JS_SetGCParameter(cx, JSGC_MAX_NURSERY_BYTES, nurseryBytes);
12648
12649 auto destroyCx = MakeScopeExit([cx] { JS_DestroyContext(cx); });
12650
12651 UniquePtr<ShellContext> sc = MakeUnique<ShellContext>(cx);
12652 if (!sc) {
12653 return 1;
12654 }
12655 auto destroyShellContext = MakeScopeExit([cx, &sc] {
12656 // Must clear out some of sc's pointer containers before JS_DestroyContext.
12657 sc->markObservers.reset();
12658
12659 JS_SetContextPrivate(cx, nullptr);
12660 sc.reset();
12661 });
12662
12663 JS_SetContextPrivate(cx, sc.get());
12664 JS_SetGrayGCRootsTracer(cx, TraceGrayRoots, nullptr);
12665 auto resetGrayGCRootsTracer =
12666 MakeScopeExit([cx] { JS_SetGrayGCRootsTracer(cx, nullptr, nullptr); });
12667
12668 // Waiting is allowed on the shell's main thread, for now.
12669 JS_SetFutexCanWait(cx);
12670 JS::SetWarningReporter(cx, WarningReporter);
12671 if (!SetContextOptions(cx, op)) {
12672 return 1;
12673 }
12674
12675 JS_SetGCParameter(cx, JSGC_MAX_BYTES, 0xffffffff);
12676
12677 size_t availMem = op.getIntOption("available-memory");
12678 if (availMem > 0) {
12679 JS_SetGCParametersBasedOnAvailableMemory(cx, availMem);
12680 }
12681
12682 JS_SetTrustedPrincipals(cx, &ShellPrincipals::fullyTrusted);
12683 JS_SetSecurityCallbacks(cx, &ShellPrincipals::securityCallbacks);
12684 JS_InitDestroyPrincipalsCallback(cx, ShellPrincipals::destroy);
12685 JS_SetDestroyCompartmentCallback(cx, DestroyShellCompartmentPrivate);
12686 JS::SetSourceElementCallback(cx, ShellSourceElementCallback);
12687
12688 js::SetWindowProxyClass(cx, &ShellWindowProxyClass);
12689
12690 JS_AddInterruptCallback(cx, ShellInterruptCallback);
12691
12692 bufferStreamState = js_new<ExclusiveWaitableData<BufferStreamState>>(
12693 mutexid::BufferStreamState);
12694 if (!bufferStreamState) {
12695 return 1;
12696 }
12697 auto shutdownBufferStreams = MakeScopeExit([] {
12698 ShutdownBufferStreams();
12699 js_delete(bufferStreamState);
12700 });
12701 JS::InitConsumeStreamCallback(cx, ConsumeBufferSource, ReportStreamError);
12702
12703 JS::SetPromiseRejectionTrackerCallback(
12704 cx, ForwardingPromiseRejectionTrackerCallback);
12705
12706 JS::dbg::SetDebuggerMallocSizeOf(cx, moz_malloc_size_of);
12707
12708 js::UseInternalJobQueues(cx);
12709
12710 JS::SetHostCleanupFinalizationRegistryCallback(
12711 cx, ShellCleanupFinalizationRegistryCallback, sc.get());
12712
12713 auto shutdownShellThreads = MakeScopeExit([cx] {
12714 KillWatchdog(cx);
12715 KillWorkerThreads(cx);
12716 DestructSharedObjectMailbox();
12717 CancelOffThreadJobsForRuntime(cx);
12718 });
12719
12720 if (const char* opt = op.getStringOption("nursery-strings")) {
12721 if (strcmp(opt, "on") == 0) {
12722 cx->runtime()->gc.nursery().enableStrings();
12723 } else if (strcmp(opt, "off") == 0) {
12724 cx->runtime()->gc.nursery().disableStrings();
12725 } else {
12726 MOZ_CRASH("invalid option value for --nursery-strings, must be on/off");
12727 }
12728 }
12729
12730 if (const char* opt = op.getStringOption("nursery-bigints")) {
12731 if (strcmp(opt, "on") == 0) {
12732 cx->runtime()->gc.nursery().enableBigInts();
12733 } else if (strcmp(opt, "off") == 0) {
12734 cx->runtime()->gc.nursery().disableBigInts();
12735 } else {
12736 MOZ_CRASH("invalid option value for --nursery-bigints, must be on/off");
12737 }
12738 }
12739
12740 // The file content should stay alive as long as Worker thread can be
12741 // initialized.
12742 JS::SelfHostedCache xdrSpan = nullptr;
12743 JS::SelfHostedWriter xdrWriter = nullptr;
12744 if (selfHostedXDRPath) {
12745 if (encodeSelfHostedCode) {
12746 xdrWriter = WriteSelfHostedXDRFile;
12747 } else {
12748 selfHostedXDRBuffer.emplace(cx);
12749 if (ReadSelfHostedXDRFile(cx, *selfHostedXDRBuffer)) {
12750 MOZ_ASSERT(selfHostedXDRBuffer->length() > 0);
12751 JS::SelfHostedCache span(selfHostedXDRBuffer->begin(),
12752 selfHostedXDRBuffer->end());
12753 xdrSpan = span;
12754 } else {
12755 fprintf(stderr, "Falling back on parsing source.\n");
12756 selfHostedXDRPath = nullptr;
12757 }
12758 }
12759 }
12760
12761 #ifndef __wasi__
12762 // This must be set before self-hosted code is initialized, as self-hosted
12763 // code reads the property and the property may not be changed later.
12764 bool disabledHugeMemory = false;
12765 if (op.getBoolOption("disable-wasm-huge-memory")) {
12766 disabledHugeMemory = JS::DisableWasmHugeMemory();
12767 MOZ_RELEASE_ASSERT(disabledHugeMemory);
12768 }
12769 #endif
12770
12771 if (!JS::InitSelfHostedCode(cx, xdrSpan, xdrWriter)) {
12772 return 1;
12773 }
12774
12775 EnvironmentPreparer environmentPreparer(cx);
12776
12777 bool incrementalGC = !op.getBoolOption("no-incremental-gc");
12778 JS_SetGCParameter(cx, JSGC_INCREMENTAL_GC_ENABLED, incrementalGC);
12779 JS_SetGCParameter(cx, JSGC_SLICE_TIME_BUDGET_MS, 10);
12780
12781 JS_SetGCParameter(cx, JSGC_PER_ZONE_GC_ENABLED, true);
12782
12783 JS::SetProcessLargeAllocationFailureCallback(my_LargeAllocFailCallback);
12784
12785 js::SetPreserveWrapperCallbacks(cx, DummyPreserveWrapperCallback,
12786 DummyHasReleasedWrapperCallback);
12787
12788 // Fish around in `op` for various important compiler-configuration flags
12789 // and make sure they get handed on to any child processes we might create.
12790 // See bug 1700900. Semantically speaking, this is all rather dubious:
12791 //
12792 // * What set of flags need to be propagated in order to guarantee that the
12793 // child produces code that is "compatible" (in whatever sense) with that
12794 // produced by the parent? This isn't always easy to determine.
12795 //
12796 // * There's nothing that ensures that flags given to the child are
12797 // presented in the same order that they exist in the parent's `argv[]`.
12798 // That could be a problem in the case where two flags with contradictory
12799 // meanings are given, and they are presented to the child in the opposite
12800 // order. For example: --wasm-compiler=optimizing --wasm-compiler=baseline.
12801
12802 #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
12803 if (op.getBoolOption("no-sse3")) {
12804 js::jit::CPUInfo::SetSSE3Disabled();
12805 if (!sCompilerProcessFlags.append("--no-sse3")) {
12806 return EXIT_FAILURE;
12807 }
12808 }
12809 if (op.getBoolOption("no-ssse3")) {
12810 js::jit::CPUInfo::SetSSSE3Disabled();
12811 if (!sCompilerProcessFlags.append("--no-ssse3")) {
12812 return EXIT_FAILURE;
12813 }
12814 }
12815 if (op.getBoolOption("no-sse4") || op.getBoolOption("no-sse41")) {
12816 js::jit::CPUInfo::SetSSE41Disabled();
12817 if (!sCompilerProcessFlags.append("--no-sse41")) {
12818 return EXIT_FAILURE;
12819 }
12820 }
12821 if (op.getBoolOption("no-sse42")) {
12822 js::jit::CPUInfo::SetSSE42Disabled();
12823 if (!sCompilerProcessFlags.append("--no-sse42")) {
12824 return EXIT_FAILURE;
12825 }
12826 }
12827 if (op.getBoolOption("enable-avx")) {
12828 js::jit::CPUInfo::SetAVXEnabled();
12829 if (!sCompilerProcessFlags.append("--enable-avx")) {
12830 return EXIT_FAILURE;
12831 }
12832 // Disable AVX completely for now. We're not supporting AVX and things
12833 // break easily if asking for AVX.
12834 fprintf(stderr, "Error: AVX encodings are currently disabled\n");
12835 return EXIT_FAILURE;
12836 }
12837 #endif
12838
12839 #ifndef __wasi__
12840 // --disable-wasm-huge-memory needs to be propagated. See bug 1518210.
12841 if (disabledHugeMemory &&
12842 !sCompilerProcessFlags.append("--disable-wasm-huge-memory")) {
12843 return EXIT_FAILURE;
12844 }
12845
12846 // Also the following are to be propagated.
12847 const char* to_propagate[] = {
12848 # define WASM_DEFAULT_FEATURE(NAME, LOWER_NAME, COMPILE_PRED, COMPILER_PRED, \
12849 FLAG_PRED, SHELL, ...) \
12850 "--no-wasm-" SHELL,
12851 # define WASM_EXPERIMENTAL_FEATURE(NAME, LOWER_NAME, COMPILE_PRED, \
12852 COMPILER_PRED, FLAG_PRED, SHELL, ...) \
12853 "--wasm-" SHELL,
12854 JS_FOR_WASM_FEATURES(WASM_DEFAULT_FEATURE, WASM_EXPERIMENTAL_FEATURE)
12855 # undef WASM_DEFAULT_FEATURE
12856 # undef WASM_EXPERIMENTAL_FEATURE
12857 // Feature selection options
12858 "--wasm-simd-wormhole",
12859 // Compiler selection options
12860 "--test-wasm-await-tier2", NULL};
12861 for (const char** p = &to_propagate[0]; *p; p++) {
12862 if (op.getBoolOption(&(*p)[2] /* 2 => skip the leading '--' */)) {
12863 if (!sCompilerProcessFlags.append(*p)) {
12864 return EXIT_FAILURE;
12865 }
12866 }
12867 }
12868
12869 // Also --wasm-compiler= is to be propagated. This is tricky because it is
12870 // necessary to reconstitute the --wasm-compiler=<whatever> string from its
12871 // pieces, without causing a leak. Hence it is copied into a static buffer.
12872 // This is thread-unsafe, but we're in `main()` and on the process' root
12873 // thread. Also, we do this only once -- it wouldn't work properly if we
12874 // handled multiple --wasm-compiler= flags in a loop.
12875 const char* wasm_compiler = op.getStringOption("wasm-compiler");
12876 if (wasm_compiler) {
12877 size_t n_needed =
12878 2 + strlen("wasm-compiler") + 1 + strlen(wasm_compiler) + 1;
12879 const size_t n_avail = 128;
12880 static char buf[n_avail];
12881 // `n_needed` depends on the compiler name specified. However, it can't
12882 // be arbitrarily long, since previous flag-checking should have limited
12883 // it to a set of known possibilities: "baseline", "ion", "cranelift",
12884 // "baseline+ion", "baseline+cranelift", etc. Still, assert this for
12885 // safety.
12886 MOZ_RELEASE_ASSERT(n_needed < n_avail);
12887 memset(buf, 0, sizeof(buf));
12888 SprintfBuf(buf, n_avail, "--%s=%s", "wasm-compiler", wasm_compiler);
12889 if (!sCompilerProcessFlags.append(buf)) {
12890 return EXIT_FAILURE;
12891 }
12892 }
12893 #endif // __wasi__
12894
12895 result = Shell(cx, &op);
12896
12897 #ifdef DEBUG
12898 if (OOM_printAllocationCount) {
12899 printf("OOM max count: %" PRIu64 "\n", js::oom::simulator.counter());
12900 }
12901 #endif
12902
12903 return result;
12904 }
12905