Bug 1842773 - Part 1: Add prefs for resizable ArrayBuffers and growable SharedArrayBu...
[gecko.git] / js / src / shell / js.cpp
blob60bd72cfd08b1b117ba91a834769c1a4dd6b75e5
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/. */
7 /* JS shell. */
9 #include "mozilla/AlreadyAddRefed.h" // mozilla::already_AddRefed
10 #include "mozilla/ArrayUtils.h"
11 #include "mozilla/Assertions.h" // MOZ_ASSERT, MOZ_ASSERT_IF, MOZ_RELEASE_ASSERT, MOZ_CRASH
12 #include "mozilla/Atomics.h"
13 #include "mozilla/Attributes.h"
14 #include "mozilla/DebugOnly.h"
15 #include "mozilla/EnumSet.h"
16 #include "mozilla/IntegerPrintfMacros.h"
17 #include "mozilla/mozalloc.h"
18 #include "mozilla/PodOperations.h"
19 #include "mozilla/RandomNum.h"
20 #include "mozilla/RefPtr.h"
21 #include "mozilla/ScopeExit.h"
22 #include "mozilla/Sprintf.h"
23 #include "mozilla/TimeStamp.h"
24 #include "mozilla/UniquePtrExtensions.h" // UniqueFreePtr
25 #include "mozilla/Utf8.h"
26 #include "mozilla/Variant.h"
28 #include <algorithm>
29 #include <chrono>
30 #ifdef XP_WIN
31 # include <direct.h>
32 # include <process.h>
33 #endif
34 #include <errno.h>
35 #include <fcntl.h>
36 #if defined(XP_WIN)
37 # include <io.h> /* for isatty() */
38 #endif
39 #include <locale.h>
40 #if defined(MALLOC_H)
41 # include MALLOC_H /* for malloc_usable_size, malloc_size, _msize */
42 #endif
43 #include <ctime>
44 #include <math.h>
45 #ifndef __wasi__
46 # include <signal.h>
47 #endif
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <sys/stat.h>
52 #include <sys/types.h>
53 #include <utility>
54 #ifdef XP_UNIX
55 # ifndef __wasi__
56 # include <sys/mman.h>
57 # include <sys/wait.h>
58 # endif
59 # include <sys/stat.h>
60 # include <unistd.h>
61 #endif
62 #ifdef XP_LINUX
63 # include <sys/prctl.h>
64 #endif
66 #include "jsapi.h"
67 #include "jsfriendapi.h"
68 #include "jstypes.h"
69 #ifndef JS_WITHOUT_NSPR
70 # include "prerror.h"
71 # include "prlink.h"
72 #endif
74 #include "builtin/Array.h"
75 #include "builtin/MapObject.h"
76 #include "builtin/ModuleObject.h"
77 #include "builtin/RegExp.h"
78 #include "builtin/TestingFunctions.h"
79 #include "builtin/TestingUtility.h" // js::ParseCompileOptions, js::ParseDebugMetadata, js::CreateScriptPrivate
80 #include "debugger/DebugAPI.h"
81 #include "frontend/BytecodeCompiler.h" // frontend::{CompileGlobalScriptToExtensibleStencil, CompileModule, ParseModuleToExtensibleStencil}
82 #include "frontend/CompilationStencil.h"
83 #ifdef JS_ENABLE_SMOOSH
84 # include "frontend/Frontend2.h"
85 #endif
86 #include "frontend/FrontendContext.h" // AutoReportFrontendContext
87 #include "frontend/ModuleSharedContext.h"
88 #include "frontend/Parser.h"
89 #include "frontend/ScopeBindingCache.h" // js::frontend::ScopeBindingCache
90 #include "gc/GC.h"
91 #include "gc/PublicIterators.h"
92 #ifdef DEBUG
93 # include "irregexp/RegExpAPI.h"
94 #endif
96 #ifdef JS_SIMULATOR_ARM
97 # include "jit/arm/Simulator-arm.h"
98 #endif
99 #ifdef JS_SIMULATOR_MIPS32
100 # include "jit/mips32/Simulator-mips32.h"
101 #endif
102 #ifdef JS_SIMULATOR_MIPS64
103 # include "jit/mips64/Simulator-mips64.h"
104 #endif
105 #ifdef JS_SIMULATOR_LOONG64
106 # include "jit/loong64/Simulator-loong64.h"
107 #endif
108 #ifdef JS_SIMULATOR_RISCV64
109 # include "jit/riscv64/Simulator-riscv64.h"
110 #endif
111 #include "jit/CacheIRHealth.h"
112 #include "jit/InlinableNatives.h"
113 #include "jit/Ion.h"
114 #include "jit/JitcodeMap.h"
115 #include "jit/JitZone.h"
116 #include "jit/shared/CodeGenerator-shared.h"
117 #include "js/Array.h" // JS::NewArrayObject
118 #include "js/ArrayBuffer.h" // JS::{CreateMappedArrayBufferContents,NewMappedArrayBufferWithContents,IsArrayBufferObject,GetArrayBufferLengthAndData}
119 #include "js/BuildId.h" // JS::BuildIdCharVector, JS::SetProcessBuildIdOp
120 #include "js/CallAndConstruct.h" // JS::Call, JS::IsCallable, JS_CallFunction, JS_CallFunctionValue
121 #include "js/CharacterEncoding.h" // JS::StringIsASCII
122 #include "js/CompilationAndEvaluation.h"
123 #include "js/CompileOptions.h" // JS::ReadOnlyCompileOptions, JS::CompileOptions, JS::OwningCompileOptions, JS::DecodeOptions, JS::InstantiateOptions
124 #include "js/ContextOptions.h" // JS::ContextOptions{,Ref}
125 #include "js/Debug.h"
126 #include "js/Equality.h" // JS::SameValue
127 #include "js/ErrorReport.h" // JS::PrintError
128 #include "js/Exception.h" // JS::StealPendingExceptionStack
129 #include "js/experimental/CodeCoverage.h" // js::EnableCodeCoverage
130 #include "js/experimental/CompileScript.h" // JS::NewFrontendContext, JS::DestroyFrontendContext, JS::HadFrontendErrors, JS::ConvertFrontendErrorsToRuntimeErrors, JS::CompileGlobalScriptToStencil, JS::CompileModuleScriptToStencil, JS::CompilationStorage
131 #include "js/experimental/CTypes.h" // JS::InitCTypesClass
132 #include "js/experimental/Intl.h" // JS::AddMoz{DateTimeFormat,DisplayNames}Constructor
133 #include "js/experimental/JitInfo.h" // JSJit{Getter,Setter,Method}CallArgs, JSJitGetterInfo, JSJit{Getter,Setter}Op, JSJitInfo
134 #include "js/experimental/JSStencil.h" // JS::Stencil, JS::DecodeStencil
135 #include "js/experimental/SourceHook.h" // js::{Set,Forget,}SourceHook
136 #include "js/experimental/TypedData.h" // JS_NewUint8Array
137 #include "js/friend/DumpFunctions.h" // JS::FormatStackDump
138 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
139 #include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit
140 #include "js/friend/WindowProxy.h" // js::IsWindowProxy, js::SetWindowProxyClass, js::ToWindowProxyIfWindow, js::ToWindowIfWindowProxy
141 #include "js/GCAPI.h" // JS::AutoCheckCannotGC
142 #include "js/GCVector.h"
143 #include "js/GlobalObject.h"
144 #include "js/Initialization.h"
145 #include "js/Interrupt.h"
146 #include "js/JSON.h"
147 #include "js/MemoryCallbacks.h"
148 #include "js/MemoryFunctions.h"
149 #include "js/Modules.h" // JS::GetModulePrivate, JS::SetModule{DynamicImport,Metadata,Resolve}Hook, JS::SetModulePrivate
150 #include "js/Object.h" // JS::GetClass, JS::GetCompartment, JS::GetReservedSlot, JS::SetReservedSlot
151 #include "js/Principals.h"
152 #include "js/Printer.h" // QuoteString
153 #include "js/Printf.h"
154 #include "js/PropertyAndElement.h" // JS_DefineElement, JS_DefineFunction, JS_DefineFunctions, JS_DefineProperties, JS_DefineProperty, JS_GetElement, JS_GetProperty, JS_GetPropertyById, JS_HasProperty, JS_SetElement, JS_SetProperty, JS_SetPropertyById
155 #include "js/PropertySpec.h"
156 #include "js/Realm.h"
157 #include "js/RegExp.h" // JS::ObjectIsRegExp
158 #include "js/ScriptPrivate.h"
159 #include "js/SourceText.h" // JS::SourceText
160 #include "js/StableStringChars.h"
161 #include "js/Stack.h"
162 #include "js/StreamConsumer.h"
163 #include "js/StructuredClone.h"
164 #include "js/Transcoding.h" // JS::TranscodeBuffer, JS::TranscodeRange, JS::IsTranscodeFailureResult
165 #include "js/Warnings.h" // JS::SetWarningReporter
166 #include "js/WasmModule.h" // JS::WasmModule
167 #include "js/Wrapper.h"
168 #include "proxy/DeadObjectProxy.h" // js::IsDeadProxyObject
169 #include "shell/jsoptparse.h"
170 #include "shell/jsshell.h"
171 #include "shell/OSObject.h"
172 #include "shell/ShellModuleObjectWrapper.h"
173 #include "shell/WasmTesting.h"
174 #include "threading/ConditionVariable.h"
175 #include "threading/ExclusiveData.h"
176 #include "threading/LockGuard.h"
177 #include "threading/Thread.h"
178 #include "util/CompleteFile.h" // js::FileContents, js::ReadCompleteFile
179 #include "util/DifferentialTesting.h"
180 #include "util/StringBuffer.h"
181 #include "util/Text.h"
182 #include "util/WindowsWrapper.h"
183 #include "vm/ArgumentsObject.h"
184 #include "vm/Compression.h"
185 #include "vm/ErrorObject.h"
186 #include "vm/ErrorReporting.h"
187 #include "vm/HelperThreads.h"
188 #include "vm/JSAtomUtils.h" // AtomizeUTF8Chars, AtomizeString, ToAtom
189 #include "vm/JSContext.h"
190 #include "vm/JSFunction.h"
191 #include "vm/JSObject.h"
192 #include "vm/JSScript.h"
193 #include "vm/ModuleBuilder.h" // js::ModuleBuilder
194 #include "vm/Modules.h"
195 #include "vm/Monitor.h"
196 #include "vm/MutexIDs.h"
197 #include "vm/PromiseObject.h" // js::PromiseObject
198 #include "vm/Shape.h"
199 #include "vm/SharedArrayObject.h"
200 #include "vm/StencilObject.h" // js::StencilObject
201 #include "vm/Time.h"
202 #include "vm/ToSource.h" // js::ValueToSource
203 #include "vm/TypedArrayObject.h"
204 #include "vm/WrapperObject.h"
205 #include "wasm/WasmFeatures.h"
206 #include "wasm/WasmJS.h"
208 #include "vm/Compartment-inl.h"
209 #include "vm/ErrorObject-inl.h"
210 #include "vm/Interpreter-inl.h"
211 #include "vm/JSObject-inl.h"
212 #include "vm/Realm-inl.h"
213 #include "vm/Stack-inl.h"
215 using namespace js;
216 using namespace js::cli;
217 using namespace js::shell;
219 using JS::AutoStableStringChars;
220 using JS::CompileOptions;
222 using js::shell::RCFile;
224 using mozilla::ArrayEqual;
225 using mozilla::AsVariant;
226 using mozilla::Atomic;
227 using mozilla::MakeScopeExit;
228 using mozilla::Maybe;
229 using mozilla::Nothing;
230 using mozilla::NumberEqualsInt32;
231 using mozilla::TimeDuration;
232 using mozilla::TimeStamp;
233 using mozilla::Utf8Unit;
234 using mozilla::Variant;
236 bool InitOptionParser(OptionParser& op);
237 bool SetGlobalOptionsPreJSInit(const OptionParser& op);
238 bool SetGlobalOptionsPostJSInit(const OptionParser& op);
239 bool SetContextOptions(JSContext* cx, const OptionParser& op);
240 bool SetContextWasmOptions(JSContext* cx, const OptionParser& op);
241 bool SetContextJITOptions(JSContext* cx, const OptionParser& op);
242 bool SetContextGCOptions(JSContext* cx, const OptionParser& op);
243 bool InitModuleLoader(JSContext* cx, const OptionParser& op);
245 #ifdef FUZZING_JS_FUZZILLI
246 # define REPRL_CRFD 100
247 # define REPRL_CWFD 101
248 # define REPRL_DRFD 102
249 # define REPRL_DWFD 103
251 # define SHM_SIZE 0x100000
252 # define MAX_EDGES ((SHM_SIZE - 4) * 8)
254 struct shmem_data {
255 uint32_t num_edges;
256 unsigned char edges[];
259 struct shmem_data* __shmem;
261 uint32_t *__edges_start, *__edges_stop;
262 void __sanitizer_cov_reset_edgeguards() {
263 uint64_t N = 0;
264 for (uint32_t* x = __edges_start; x < __edges_stop && N < MAX_EDGES; x++)
265 *x = ++N;
268 extern "C" void __sanitizer_cov_trace_pc_guard_init(uint32_t* start,
269 uint32_t* stop) {
270 // Avoid duplicate initialization
271 if (start == stop || *start) return;
273 if (__edges_start != NULL || __edges_stop != NULL) {
274 fprintf(stderr,
275 "Coverage instrumentation is only supported for a single module\n");
276 _exit(-1);
279 __edges_start = start;
280 __edges_stop = stop;
282 // Map the shared memory region
283 const char* shm_key = getenv("SHM_ID");
284 if (!shm_key) {
285 puts("[COV] no shared memory bitmap available, skipping");
286 __shmem = (struct shmem_data*)malloc(SHM_SIZE);
287 } else {
288 int fd = shm_open(shm_key, O_RDWR, S_IREAD | S_IWRITE);
289 if (fd <= -1) {
290 fprintf(stderr, "Failed to open shared memory region: %s\n",
291 strerror(errno));
292 _exit(-1);
295 __shmem = (struct shmem_data*)mmap(0, SHM_SIZE, PROT_READ | PROT_WRITE,
296 MAP_SHARED, fd, 0);
297 if (__shmem == MAP_FAILED) {
298 fprintf(stderr, "Failed to mmap shared memory region\n");
299 _exit(-1);
303 __sanitizer_cov_reset_edgeguards();
305 __shmem->num_edges = stop - start;
306 printf("[COV] edge counters initialized. Shared memory: %s with %u edges\n",
307 shm_key, __shmem->num_edges);
310 extern "C" void __sanitizer_cov_trace_pc_guard(uint32_t* guard) {
311 // There's a small race condition here: if this function executes in two
312 // threads for the same edge at the same time, the first thread might disable
313 // the edge (by setting the guard to zero) before the second thread fetches
314 // the guard value (and thus the index). However, our instrumentation ignores
315 // the first edge (see libcoverage.c) and so the race is unproblematic.
316 uint32_t index = *guard;
317 // If this function is called before coverage instrumentation is properly
318 // initialized we want to return early.
319 if (!index) return;
320 __shmem->edges[index / 8] |= 1 << (index % 8);
321 *guard = 0;
323 #endif /* FUZZING_JS_FUZZILLI */
325 enum JSShellExitCode {
326 EXITCODE_RUNTIME_ERROR = 3,
327 EXITCODE_FILE_NOT_FOUND = 4,
328 EXITCODE_OUT_OF_MEMORY = 5,
329 EXITCODE_TIMEOUT = 6
333 * Limit the timeout to 30 minutes to prevent an overflow on platfoms
334 * that represent the time internally in microseconds using 32-bit int.
336 static const double MAX_TIMEOUT_SECONDS = 1800.0;
338 // Not necessarily in sync with the browser
339 #ifdef ENABLE_SHARED_MEMORY
340 # define SHARED_MEMORY_DEFAULT 1
341 #else
342 # define SHARED_MEMORY_DEFAULT 0
343 #endif
345 // Fuzzing support for JS runtime fuzzing
346 #ifdef FUZZING_INTERFACES
347 # include "shell/jsrtfuzzing/jsrtfuzzing.h"
348 static bool fuzzDoDebug = !!getenv("MOZ_FUZZ_DEBUG");
349 static bool fuzzHaveModule = !!getenv("FUZZER");
350 #endif // FUZZING_INTERFACES
352 // Code to support GCOV code coverage measurements on standalone shell
353 #ifdef MOZ_CODE_COVERAGE
354 # if defined(__GNUC__) && !defined(__clang__)
355 extern "C" void __gcov_dump();
356 extern "C" void __gcov_reset();
358 void counters_dump(int) { __gcov_dump(); }
360 void counters_reset(int) { __gcov_reset(); }
361 # else
362 void counters_dump(int) { /* Do nothing */
365 void counters_reset(int) { /* Do nothing */
367 # endif
369 static void InstallCoverageSignalHandlers() {
370 # ifndef XP_WIN
371 fprintf(stderr, "[CodeCoverage] Setting handlers for process %d.\n",
372 getpid());
374 struct sigaction dump_sa;
375 dump_sa.sa_handler = counters_dump;
376 dump_sa.sa_flags = SA_RESTART;
377 sigemptyset(&dump_sa.sa_mask);
378 mozilla::DebugOnly<int> r1 = sigaction(SIGUSR1, &dump_sa, nullptr);
379 MOZ_ASSERT(r1 == 0, "Failed to install GCOV SIGUSR1 handler");
381 struct sigaction reset_sa;
382 reset_sa.sa_handler = counters_reset;
383 reset_sa.sa_flags = SA_RESTART;
384 sigemptyset(&reset_sa.sa_mask);
385 mozilla::DebugOnly<int> r2 = sigaction(SIGUSR2, &reset_sa, nullptr);
386 MOZ_ASSERT(r2 == 0, "Failed to install GCOV SIGUSR2 handler");
387 # endif
389 #endif
391 // An off-thread parse or decode job.
392 class js::shell::OffThreadJob {
393 static constexpr size_t kCompileStackQuota = 128 * sizeof(size_t) * 1024;
394 static constexpr size_t kThreadStackQuota =
395 kCompileStackQuota + 128 * sizeof(size_t) * 1024;
397 enum State {
398 RUNNING, // Working; no stencil.
399 DONE, // Finished; have stencil.
400 CANCELLED // Cancelled due to error.
403 public:
404 enum class Kind {
405 CompileScript,
406 CompileModule,
407 Decode,
410 OffThreadJob(ShellContext* sc, Kind kind, JS::SourceText<char16_t>&& srcBuf);
411 OffThreadJob(ShellContext* sc, Kind kind, JS::TranscodeBuffer&& xdrBuf);
413 ~OffThreadJob();
415 bool init(JSContext* cx, const JS::ReadOnlyCompileOptions& options);
416 bool dispatch();
418 static void OffThreadMain(OffThreadJob* self);
419 void run();
421 void cancel();
422 void waitUntilDone();
424 already_AddRefed<JS::Stencil> stealStencil(JSContext* cx);
426 public:
427 const int32_t id;
429 private:
430 Kind kind_;
431 State state_;
433 JS::FrontendContext* fc_ = nullptr;
434 JS::OwningCompileOptions options_;
436 UniquePtr<Thread> thread_;
438 JS::SourceText<char16_t> srcBuf_;
439 JS::TranscodeBuffer xdrBuf_;
441 RefPtr<JS::Stencil> stencil_;
443 JS::TranscodeResult transcodeResult_ = JS::TranscodeResult::Ok;
446 template <typename T>
447 static OffThreadJob* NewOffThreadJob(JSContext* cx, OffThreadJob::Kind kind,
448 JS::ReadOnlyCompileOptions& options,
449 T&& source) {
450 ShellContext* sc = GetShellContext(cx);
451 if (sc->isWorker) {
452 // Off-thread compilation/decode is used by main-thread, in order to improve
453 // the responsiveness. It's not used by worker in browser, and there's not
454 // much reason to support worker here.
455 JS_ReportErrorASCII(cx, "Off-thread job is not supported in worker");
456 return nullptr;
459 UniquePtr<OffThreadJob> job(
460 cx->new_<OffThreadJob>(sc, kind, std::move(source)));
461 if (!job) {
462 return nullptr;
465 if (!job->init(cx, options)) {
466 return nullptr;
469 if (!sc->offThreadJobs.append(job.get())) {
470 job->cancel();
471 JS_ReportErrorASCII(cx, "OOM adding off-thread job");
472 return nullptr;
475 return job.release();
478 static OffThreadJob* GetSingleOffThreadJob(JSContext* cx) {
479 ShellContext* sc = GetShellContext(cx);
480 const auto& jobs = sc->offThreadJobs;
481 if (jobs.empty()) {
482 JS_ReportErrorASCII(cx, "No off-thread jobs are pending");
483 return nullptr;
486 if (jobs.length() > 1) {
487 JS_ReportErrorASCII(
488 cx, "Multiple off-thread jobs are pending: must specify job ID");
489 return nullptr;
492 return jobs[0];
495 static OffThreadJob* LookupOffThreadJobByID(JSContext* cx, int32_t id) {
496 if (id <= 0) {
497 JS_ReportErrorASCII(cx, "Bad off-thread job ID");
498 return nullptr;
501 ShellContext* sc = GetShellContext(cx);
502 const auto& jobs = sc->offThreadJobs;
503 if (jobs.empty()) {
504 JS_ReportErrorASCII(cx, "No off-thread jobs are pending");
505 return nullptr;
508 OffThreadJob* job = nullptr;
509 for (auto someJob : jobs) {
510 if (someJob->id == id) {
511 job = someJob;
512 break;
516 if (!job) {
517 JS_ReportErrorASCII(cx, "Off-thread job not found");
518 return nullptr;
521 return job;
524 static OffThreadJob* LookupOffThreadJobForArgs(JSContext* cx,
525 const CallArgs& args,
526 size_t arg) {
527 // If the optional ID argument isn't present, get the single pending job.
528 if (args.length() <= arg) {
529 return GetSingleOffThreadJob(cx);
532 // Lookup the job using the specified ID.
533 int32_t id = 0;
534 RootedValue value(cx, args[arg]);
535 if (!ToInt32(cx, value, &id)) {
536 return nullptr;
539 return LookupOffThreadJobByID(cx, id);
542 static void DeleteOffThreadJob(JSContext* cx, OffThreadJob* job) {
543 ShellContext* sc = GetShellContext(cx);
544 for (size_t i = 0; i < sc->offThreadJobs.length(); i++) {
545 if (sc->offThreadJobs[i] == job) {
546 sc->offThreadJobs.erase(&sc->offThreadJobs[i]);
547 js_delete(job);
548 return;
552 MOZ_CRASH("Off-thread job not found");
555 static void CancelOffThreadJobsForRuntime(JSContext* cx) {
556 ShellContext* sc = GetShellContext(cx);
557 while (!sc->offThreadJobs.empty()) {
558 OffThreadJob* job = sc->offThreadJobs.popCopy();
559 job->waitUntilDone();
560 js_delete(job);
564 mozilla::Atomic<int32_t> gOffThreadJobSerial(1);
566 OffThreadJob::OffThreadJob(ShellContext* sc, Kind kind,
567 JS::SourceText<char16_t>&& srcBuf)
568 : id(gOffThreadJobSerial++),
569 kind_(kind),
570 state_(RUNNING),
571 options_(JS::OwningCompileOptions::ForFrontendContext()),
572 srcBuf_(std::move(srcBuf)) {
573 MOZ_RELEASE_ASSERT(id > 0, "Off-thread job IDs exhausted");
576 OffThreadJob::OffThreadJob(ShellContext* sc, Kind kind,
577 JS::TranscodeBuffer&& xdrBuf)
578 : id(gOffThreadJobSerial++),
579 kind_(kind),
580 state_(RUNNING),
581 options_(JS::OwningCompileOptions::ForFrontendContext()),
582 xdrBuf_(std::move(xdrBuf)) {
583 MOZ_RELEASE_ASSERT(id > 0, "Off-thread job IDs exhausted");
586 OffThreadJob::~OffThreadJob() {
587 if (fc_) {
588 JS::DestroyFrontendContext(fc_);
590 MOZ_ASSERT(state_ != RUNNING);
593 bool OffThreadJob::init(JSContext* cx,
594 const JS::ReadOnlyCompileOptions& options) {
595 fc_ = JS::NewFrontendContext();
596 if (!fc_) {
597 ReportOutOfMemory(cx);
598 state_ = CANCELLED;
599 return false;
602 if (!options_.copy(cx, options)) {
603 state_ = CANCELLED;
604 return false;
607 return true;
610 bool OffThreadJob::dispatch() {
611 thread_ =
612 js::MakeUnique<Thread>(Thread::Options().setStackSize(kThreadStackQuota));
613 if (!thread_) {
614 state_ = CANCELLED;
615 return false;
618 if (!thread_->init(OffThreadJob::OffThreadMain, this)) {
619 state_ = CANCELLED;
620 thread_ = nullptr;
621 return false;
624 return true;
627 /* static */ void OffThreadJob::OffThreadMain(OffThreadJob* self) {
628 self->run();
631 void OffThreadJob::run() {
632 MOZ_ASSERT(state_ == RUNNING);
633 MOZ_ASSERT(!stencil_);
635 JS::SetNativeStackQuota(fc_, kCompileStackQuota);
637 switch (kind_) {
638 case Kind::CompileScript: {
639 JS::CompilationStorage compileStorage;
640 stencil_ = JS::CompileGlobalScriptToStencil(fc_, options_, srcBuf_,
641 compileStorage);
642 break;
644 case Kind::CompileModule: {
645 JS::CompilationStorage compileStorage;
646 stencil_ = JS::CompileModuleScriptToStencil(fc_, options_, srcBuf_,
647 compileStorage);
648 break;
650 case Kind::Decode: {
651 JS::DecodeOptions decodeOptions(options_);
652 JS::TranscodeRange range(xdrBuf_.begin(), xdrBuf_.length());
653 transcodeResult_ = JS::DecodeStencil(fc_, decodeOptions, range,
654 getter_AddRefs(stencil_));
655 break;
659 state_ = DONE;
662 void OffThreadJob::cancel() {
663 MOZ_ASSERT(state_ == RUNNING);
664 MOZ_ASSERT(!stencil_);
665 MOZ_ASSERT(!thread_, "cannot cancel after starting a thread");
667 state_ = CANCELLED;
670 void OffThreadJob::waitUntilDone() {
671 MOZ_ASSERT(state_ != CANCELLED);
672 thread_->join();
675 already_AddRefed<JS::Stencil> OffThreadJob::stealStencil(JSContext* cx) {
676 JS::FrontendContext* fc = fc_;
677 fc_ = nullptr;
678 auto destroyFrontendContext =
679 mozilla::MakeScopeExit([&]() { JS::DestroyFrontendContext(fc); });
681 MOZ_ASSERT(fc);
683 if (JS::HadFrontendErrors(fc)) {
684 (void)JS::ConvertFrontendErrorsToRuntimeErrors(cx, fc, options_);
685 return nullptr;
688 if (!stencil_ && JS::IsTranscodeFailureResult(transcodeResult_)) {
689 JS_ReportErrorASCII(cx, "failed to decode cache");
690 return nullptr;
693 // Report warnings.
694 if (!JS::ConvertFrontendErrorsToRuntimeErrors(cx, fc, options_)) {
695 return nullptr;
698 return stencil_.forget();
701 struct ShellCompartmentPrivate {
702 GCPtr<ArrayObject*> blackRoot;
703 GCPtr<ArrayObject*> grayRoot;
706 struct MOZ_STACK_CLASS EnvironmentPreparer
707 : public js::ScriptEnvironmentPreparer {
708 explicit EnvironmentPreparer(JSContext* cx) {
709 js::SetScriptEnvironmentPreparer(cx, this);
711 void invoke(JS::HandleObject global, Closure& closure) override;
714 const char* shell::selfHostedXDRPath = nullptr;
715 bool shell::encodeSelfHostedCode = false;
716 bool shell::enableCodeCoverage = false;
717 bool shell::enableDisassemblyDumps = false;
718 bool shell::offthreadCompilation = false;
719 JS::DelazificationOption shell::defaultDelazificationMode =
720 JS::DelazificationOption::OnDemandOnly;
721 bool shell::enableAsmJS = false;
722 bool shell::enableWasm = false;
723 bool shell::enableSharedMemory = SHARED_MEMORY_DEFAULT;
724 bool shell::enableWasmBaseline = false;
725 bool shell::enableWasmOptimizing = false;
727 #define WASM_FEATURE(NAME, _, STAGE, ...) \
728 bool shell::enableWasm##NAME = STAGE != WasmFeatureStage::Experimental;
729 JS_FOR_WASM_FEATURES(WASM_FEATURE);
730 #undef WASM_FEATURE
732 bool shell::enableWasmVerbose = false;
733 bool shell::enableTestWasmAwaitTier2 = false;
734 bool shell::enableSourcePragmas = true;
735 bool shell::enableAsyncStacks = false;
736 bool shell::enableAsyncStackCaptureDebuggeeOnly = false;
737 bool shell::enableWeakRefs = false;
738 bool shell::enableToSource = false;
739 bool shell::enablePropertyErrorMessageFix = false;
740 bool shell::enableIteratorHelpers = false;
741 bool shell::enableShadowRealms = false;
742 // Pref for String.prototype.{is,to}WellFormed() methods.
743 bool shell::enableWellFormedUnicodeStrings = true;
744 bool shell::enableArrayGrouping = false;
745 #ifdef NIGHTLY_BUILD
746 // Pref for new Set.prototype methods.
747 bool shell::enableNewSetMethods = false;
748 // Pref for ArrayBuffer.prototype.transfer{,ToFixedLength}() methods.
749 bool shell::enableSymbolsAsWeakMapKeys = false;
750 // Pref for resizable ArrayBuffers and growable SharedArrayBuffers.
751 bool shell::enableArrayBufferResizable = false;
752 #endif
753 bool shell::enableArrayBufferTransfer = true;
754 bool shell::enableImportAttributes = false;
755 bool shell::enableImportAttributesAssertSyntax = false;
756 bool shell::enableDestructuringFuse = true;
757 #ifdef JS_GC_ZEAL
758 uint32_t shell::gZealBits = 0;
759 uint32_t shell::gZealFrequency = 0;
760 #endif
761 bool shell::printTiming = false;
762 RCFile* shell::gErrFile = nullptr;
763 RCFile* shell::gOutFile = nullptr;
764 bool shell::reportWarnings = true;
765 bool shell::compileOnly = false;
766 bool shell::disableOOMFunctions = false;
767 bool shell::defaultToSameCompartment = true;
769 bool shell::useFdlibmForSinCosTan = false;
771 #ifdef DEBUG
772 bool shell::dumpEntrainedVariables = false;
773 bool shell::OOM_printAllocationCount = false;
774 #endif
776 UniqueChars shell::processWideModuleLoadPath;
778 static bool SetTimeoutValue(JSContext* cx, double t);
780 static void KillWatchdog(JSContext* cx);
782 static bool ScheduleWatchdog(JSContext* cx, double t);
784 static void CancelExecution(JSContext* cx);
786 enum class ShellGlobalKind {
787 GlobalObject,
788 WindowProxy,
791 static JSObject* NewGlobalObject(JSContext* cx, JS::RealmOptions& options,
792 JSPrincipals* principals, ShellGlobalKind kind,
793 bool immutablePrototype);
796 * A toy WindowProxy class for the shell. This is intended for testing code
797 * where global |this| is a WindowProxy. All requests are forwarded to the
798 * underlying global and no navigation is supported.
800 const JSClass ShellWindowProxyClass =
801 PROXY_CLASS_DEF("ShellWindowProxy", JSCLASS_HAS_RESERVED_SLOTS(1));
803 JSObject* NewShellWindowProxy(JSContext* cx, JS::HandleObject global) {
804 MOZ_ASSERT(global->is<GlobalObject>());
806 js::WrapperOptions options;
807 options.setClass(&ShellWindowProxyClass);
809 JSAutoRealm ar(cx, global);
810 JSObject* obj =
811 js::Wrapper::New(cx, global, &js::Wrapper::singleton, options);
812 MOZ_ASSERT_IF(obj, js::IsWindowProxy(obj));
813 return obj;
817 * A toy principals type for the shell.
819 * In the shell, a principal is simply a 32-bit mask: P subsumes Q if the
820 * set bits in P are a superset of those in Q. Thus, the principal 0 is
821 * subsumed by everything, and the principal ~0 subsumes everything.
823 * As a special case, a null pointer as a principal is treated like 0xffff.
825 * The 'newGlobal' function takes an option indicating which principal the
826 * new global should have; 'evaluate' does for the new code.
828 class ShellPrincipals final : public JSPrincipals {
829 uint32_t bits;
831 static uint32_t getBits(JSPrincipals* p) {
832 if (!p) {
833 return 0xffff;
835 return static_cast<ShellPrincipals*>(p)->bits;
838 public:
839 explicit ShellPrincipals(uint32_t bits, int32_t refcount = 0) : bits(bits) {
840 this->refcount = refcount;
843 bool write(JSContext* cx, JSStructuredCloneWriter* writer) override {
844 // The shell doesn't have a read principals hook, so it doesn't really
845 // matter what we write here, but we have to write something so the
846 // fuzzer is happy.
847 return JS_WriteUint32Pair(writer, bits, 0);
850 bool isSystemOrAddonPrincipal() override { return true; }
852 static void destroy(JSPrincipals* principals) {
853 MOZ_ASSERT(principals != &fullyTrusted);
854 MOZ_ASSERT(principals->refcount == 0);
855 js_delete(static_cast<const ShellPrincipals*>(principals));
858 static bool subsumes(JSPrincipals* first, JSPrincipals* second) {
859 uint32_t firstBits = getBits(first);
860 uint32_t secondBits = getBits(second);
861 return (firstBits | secondBits) == firstBits;
864 static JSSecurityCallbacks securityCallbacks;
866 // Fully-trusted principals singleton.
867 static ShellPrincipals fullyTrusted;
870 JSSecurityCallbacks ShellPrincipals::securityCallbacks = {
871 nullptr, // contentSecurityPolicyAllows
872 subsumes};
874 // The fully-trusted principal subsumes all other principals.
875 ShellPrincipals ShellPrincipals::fullyTrusted(-1, 1);
877 #ifdef EDITLINE
878 extern "C" {
879 extern MOZ_EXPORT char* readline(const char* prompt);
880 extern MOZ_EXPORT void add_history(char* line);
881 } // extern "C"
882 #endif
884 ShellContext::ShellContext(JSContext* cx, IsWorkerEnum isWorker_)
885 : cx_(nullptr),
886 isWorker(isWorker_),
887 lastWarningEnabled(false),
888 trackUnhandledRejections(true),
889 timeoutInterval(-1.0),
890 startTime(PRMJ_Now()),
891 serviceInterrupt(false),
892 haveInterruptFunc(false),
893 interruptFunc(cx, NullValue()),
894 lastWarning(cx, NullValue()),
895 promiseRejectionTrackerCallback(cx, NullValue()),
896 unhandledRejectedPromises(cx),
897 watchdogLock(mutexid::ShellContextWatchdog),
898 exitCode(0),
899 quitting(false),
900 readLineBufPos(0),
901 errFilePtr(nullptr),
902 outFilePtr(nullptr),
903 offThreadMonitor(mutexid::ShellOffThreadState),
904 finalizationRegistryCleanupCallbacks(cx) {}
906 ShellContext* js::shell::GetShellContext(JSContext* cx) {
907 ShellContext* sc = static_cast<ShellContext*>(JS_GetContextPrivate(cx));
908 MOZ_ASSERT(sc);
909 return sc;
912 static void TraceRootArrays(JSTracer* trc, gc::MarkColor color) {
913 JSRuntime* rt = trc->runtime();
914 for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
915 for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) {
916 auto priv = static_cast<ShellCompartmentPrivate*>(
917 JS_GetCompartmentPrivate(comp.get()));
918 if (!priv) {
919 continue;
922 GCPtr<ArrayObject*>& array =
923 (color == gc::MarkColor::Black) ? priv->blackRoot : priv->grayRoot;
924 TraceNullableEdge(trc, &array, "shell root array");
926 if (array) {
927 // Trace the array elements as part of root marking.
928 for (uint32_t i = 0; i < array->getDenseInitializedLength(); i++) {
929 Value& value = const_cast<Value&>(array->getDenseElement(i));
930 TraceManuallyBarrieredEdge(trc, &value, "shell root array element");
937 static void TraceBlackRoots(JSTracer* trc, void* data) {
938 TraceRootArrays(trc, gc::MarkColor::Black);
941 static bool TraceGrayRoots(JSTracer* trc, SliceBudget& budget, void* data) {
942 TraceRootArrays(trc, gc::MarkColor::Gray);
943 return true;
946 static inline JSString* NewStringCopyUTF8(JSContext* cx, const char* chars) {
947 return JS_NewStringCopyUTF8N(cx, JS::UTF8Chars(chars, strlen(chars)));
950 static mozilla::UniqueFreePtr<char[]> GetLine(FILE* file, const char* prompt) {
951 #ifdef EDITLINE
953 * Use readline only if file is stdin, because there's no way to specify
954 * another handle. Are other filehandles interactive?
956 if (file == stdin) {
957 mozilla::UniqueFreePtr<char[]> linep(readline(prompt));
959 * We set it to zero to avoid complaining about inappropriate ioctl
960 * for device in the case of EOF. Looks like errno == 251 if line is
961 * finished with EOF and errno == 25 (EINVAL on Mac) if there is
962 * nothing left to read.
964 if (errno == 251 || errno == 25 || errno == EINVAL) {
965 errno = 0;
967 if (!linep) {
968 return nullptr;
970 if (linep[0] != '\0') {
971 add_history(linep.get());
973 return linep;
975 #endif
977 size_t len = 0;
978 if (*prompt != '\0' && gOutFile->isOpen()) {
979 fprintf(gOutFile->fp, "%s", prompt);
980 fflush(gOutFile->fp);
983 size_t size = 80;
984 mozilla::UniqueFreePtr<char[]> buffer(static_cast<char*>(malloc(size)));
985 if (!buffer) {
986 return nullptr;
989 char* current = buffer.get();
990 do {
991 while (true) {
992 if (fgets(current, size - len, file)) {
993 break;
995 if (errno != EINTR) {
996 return nullptr;
1000 len += strlen(current);
1001 char* t = buffer.get() + len - 1;
1002 if (*t == '\n') {
1003 /* Line was read. We remove '\n' and exit. */
1004 *t = '\0';
1005 break;
1008 if (len + 1 == size) {
1009 size = size * 2;
1010 char* raw = buffer.release();
1011 char* tmp = static_cast<char*>(realloc(raw, size));
1012 if (!tmp) {
1013 free(raw);
1014 return nullptr;
1016 buffer.reset(tmp);
1018 current = buffer.get() + len;
1019 } while (true);
1020 return buffer;
1023 static bool ShellInterruptCallback(JSContext* cx) {
1024 ShellContext* sc = GetShellContext(cx);
1025 if (!sc->serviceInterrupt) {
1026 return true;
1029 // Reset serviceInterrupt. CancelExecution or InterruptIf will set it to
1030 // true to distinguish watchdog or user triggered interrupts.
1031 // Do this first to prevent other interrupts that may occur while the
1032 // user-supplied callback is executing from re-entering the handler.
1033 sc->serviceInterrupt = false;
1035 bool result;
1036 if (sc->haveInterruptFunc) {
1037 bool wasAlreadyThrowing = cx->isExceptionPending();
1038 JS::AutoSaveExceptionState savedExc(cx);
1039 JSAutoRealm ar(cx, &sc->interruptFunc.toObject());
1040 RootedValue rval(cx);
1042 // Report any exceptions thrown by the JS interrupt callback, but do
1043 // *not* keep it on the cx. The interrupt handler is invoked at points
1044 // that are not expected to throw catchable exceptions, like at
1045 // JSOp::RetRval.
1047 // If the interrupted JS code was already throwing, any exceptions
1048 // thrown by the interrupt handler are silently swallowed.
1050 Maybe<AutoReportException> are;
1051 if (!wasAlreadyThrowing) {
1052 are.emplace(cx);
1054 result = JS_CallFunctionValue(cx, nullptr, sc->interruptFunc,
1055 JS::HandleValueArray::empty(), &rval);
1057 savedExc.restore();
1059 if (rval.isBoolean()) {
1060 result = rval.toBoolean();
1061 } else {
1062 result = false;
1064 } else {
1065 result = false;
1068 if (!result && sc->exitCode == 0) {
1069 static const char msg[] = "Script terminated by interrupt handler.\n";
1070 fputs(msg, stderr);
1072 sc->exitCode = EXITCODE_TIMEOUT;
1075 return result;
1078 static void GCSliceCallback(JSContext* cx, JS::GCProgress progress,
1079 const JS::GCDescription& desc) {
1080 if (progress == JS::GC_CYCLE_END) {
1081 #if defined(MOZ_MEMORY)
1082 // We call this here to match the browser's DOMGCSliceCallback.
1083 jemalloc_free_dirty_pages();
1084 #endif
1089 * Some UTF-8 files, notably those written using Notepad, have a Unicode
1090 * Byte-Order-Mark (BOM) as their first character. This is useless (byte-order
1091 * is meaningless for UTF-8) but causes a syntax error unless we skip it.
1093 static void SkipUTF8BOM(FILE* file) {
1094 int ch1 = fgetc(file);
1095 int ch2 = fgetc(file);
1096 int ch3 = fgetc(file);
1098 // Skip the BOM
1099 if (ch1 == 0xEF && ch2 == 0xBB && ch3 == 0xBF) {
1100 return;
1103 // No BOM - revert
1104 if (ch3 != EOF) {
1105 ungetc(ch3, file);
1107 if (ch2 != EOF) {
1108 ungetc(ch2, file);
1110 if (ch1 != EOF) {
1111 ungetc(ch1, file);
1115 void EnvironmentPreparer::invoke(HandleObject global, Closure& closure) {
1116 MOZ_ASSERT(JS_IsGlobalObject(global));
1118 JSContext* cx = TlsContext.get();
1119 MOZ_ASSERT(!JS_IsExceptionPending(cx));
1121 AutoRealm ar(cx, global);
1122 AutoReportException are(cx);
1123 if (!closure(cx)) {
1124 return;
1128 static bool RegisterScriptPathWithModuleLoader(JSContext* cx,
1129 HandleScript script,
1130 const char* filename) {
1131 // Set the private value associated with a script to a object containing the
1132 // script's filename so that the module loader can use it to resolve
1133 // relative imports.
1135 RootedString path(cx, NewStringCopyUTF8(cx, filename));
1136 if (!path) {
1137 return false;
1140 MOZ_ASSERT(JS::GetScriptPrivate(script).isUndefined());
1141 RootedObject infoObject(cx, js::CreateScriptPrivate(cx, path));
1142 if (!infoObject) {
1143 return false;
1146 JS::SetScriptPrivate(script, ObjectValue(*infoObject));
1147 return true;
1150 enum class CompileUtf8 {
1151 InflateToUtf16,
1152 DontInflate,
1155 [[nodiscard]] static bool RunFile(JSContext* cx, const char* filename,
1156 FILE* file, CompileUtf8 compileMethod,
1157 bool compileOnly, bool fullParse) {
1158 SkipUTF8BOM(file);
1160 int64_t t1 = PRMJ_Now();
1161 RootedScript script(cx);
1164 CompileOptions options(cx);
1165 options.setIntroductionType("js shell file")
1166 .setFileAndLine(filename, 1)
1167 .setIsRunOnce(true)
1168 .setNoScriptRval(true);
1170 if (fullParse) {
1171 options.setForceFullParse();
1172 } else {
1173 options.setEagerDelazificationStrategy(defaultDelazificationMode);
1176 if (compileMethod == CompileUtf8::DontInflate) {
1177 script = JS::CompileUtf8File(cx, options, file);
1178 } else {
1179 fprintf(stderr, "(compiling '%s' after inflating to UTF-16)\n", filename);
1181 FileContents buffer(cx);
1182 if (!ReadCompleteFile(cx, file, buffer)) {
1183 return false;
1186 size_t length = buffer.length();
1187 auto chars = UniqueTwoByteChars(
1188 UTF8CharsToNewTwoByteCharsZ(
1190 JS::UTF8Chars(reinterpret_cast<const char*>(buffer.begin()),
1191 buffer.length()),
1192 &length, js::MallocArena)
1193 .get());
1194 if (!chars) {
1195 return false;
1198 JS::SourceText<char16_t> source;
1199 if (!source.init(cx, std::move(chars), length)) {
1200 return false;
1203 script = JS::Compile(cx, options, source);
1206 if (!script) {
1207 return false;
1211 if (!RegisterScriptPathWithModuleLoader(cx, script, filename)) {
1212 return false;
1215 #ifdef DEBUG
1216 if (dumpEntrainedVariables) {
1217 AnalyzeEntrainedVariables(cx, script);
1219 #endif
1220 if (!compileOnly) {
1221 if (!JS_ExecuteScript(cx, script)) {
1222 return false;
1224 int64_t t2 = PRMJ_Now() - t1;
1225 if (printTiming) {
1226 printf("runtime = %.3f ms\n", double(t2) / PRMJ_USEC_PER_MSEC);
1229 return true;
1232 [[nodiscard]] static bool RunModule(JSContext* cx, const char* filename,
1233 bool compileOnly) {
1234 ShellContext* sc = GetShellContext(cx);
1236 RootedString path(cx, NewStringCopyUTF8(cx, filename));
1237 if (!path) {
1238 return false;
1241 path = ResolvePath(cx, path, RootRelative);
1242 if (!path) {
1243 return false;
1246 return sc->moduleLoader->loadRootModule(cx, path);
1249 static void ShellCleanupFinalizationRegistryCallback(JSFunction* doCleanup,
1250 JSObject* incumbentGlobal,
1251 void* data) {
1252 // In the browser this queues a task. Shell jobs correspond to microtasks so
1253 // we arrange for cleanup to happen after all jobs/microtasks have run. The
1254 // incumbent global is ignored in the shell.
1256 auto sc = static_cast<ShellContext*>(data);
1257 AutoEnterOOMUnsafeRegion oomUnsafe;
1258 if (!sc->finalizationRegistryCleanupCallbacks.append(doCleanup)) {
1259 oomUnsafe.crash("ShellCleanupFinalizationRegistryCallback");
1263 // Run any FinalizationRegistry cleanup tasks and return whether any ran.
1264 static bool MaybeRunFinalizationRegistryCleanupTasks(JSContext* cx) {
1265 ShellContext* sc = GetShellContext(cx);
1266 MOZ_ASSERT(!sc->quitting);
1268 Rooted<ShellContext::FunctionVector> callbacks(cx);
1269 std::swap(callbacks.get(), sc->finalizationRegistryCleanupCallbacks.get());
1271 bool ranTasks = false;
1273 RootedFunction callback(cx);
1274 for (JSFunction* f : callbacks) {
1275 callback = f;
1277 JS::ExposeObjectToActiveJS(callback);
1278 AutoRealm ar(cx, callback);
1281 AutoReportException are(cx);
1282 RootedValue unused(cx);
1283 (void)JS_CallFunction(cx, nullptr, callback, HandleValueArray::empty(),
1284 &unused);
1287 ranTasks = true;
1289 if (sc->quitting) {
1290 break;
1294 return ranTasks;
1297 static bool EnqueueJob(JSContext* cx, unsigned argc, Value* vp) {
1298 CallArgs args = CallArgsFromVp(argc, vp);
1300 if (!IsFunctionObject(args.get(0))) {
1301 JS_ReportErrorASCII(cx, "EnqueueJob's first argument must be a function");
1302 return false;
1305 args.rval().setUndefined();
1307 RootedObject job(cx, &args[0].toObject());
1308 return js::EnqueueJob(cx, job);
1311 static void RunShellJobs(JSContext* cx) {
1312 ShellContext* sc = GetShellContext(cx);
1313 if (sc->quitting) {
1314 return;
1317 while (true) {
1318 // Run microtasks.
1319 js::RunJobs(cx);
1320 if (sc->quitting) {
1321 return;
1324 // Run tasks (only finalization registry clean tasks are possible).
1325 bool ranTasks = MaybeRunFinalizationRegistryCleanupTasks(cx);
1326 if (!ranTasks) {
1327 break;
1332 static bool DrainJobQueue(JSContext* cx, unsigned argc, Value* vp) {
1333 CallArgs args = CallArgsFromVp(argc, vp);
1335 if (GetShellContext(cx)->quitting) {
1336 JS_ReportErrorASCII(
1337 cx, "Mustn't drain the job queue when the shell is quitting");
1338 return false;
1341 RunShellJobs(cx);
1343 if (GetShellContext(cx)->quitting) {
1344 return false;
1347 args.rval().setUndefined();
1348 return true;
1351 static bool GlobalOfFirstJobInQueue(JSContext* cx, unsigned argc, Value* vp) {
1352 CallArgs args = CallArgsFromVp(argc, vp);
1354 RootedObject job(cx, cx->internalJobQueue->maybeFront());
1355 if (!job) {
1356 JS_ReportErrorASCII(cx, "Job queue is empty");
1357 return false;
1360 RootedObject global(cx, &job->nonCCWGlobal());
1361 if (!cx->compartment()->wrap(cx, &global)) {
1362 return false;
1365 args.rval().setObject(*global);
1366 return true;
1369 static bool TrackUnhandledRejections(JSContext* cx, JS::HandleObject promise,
1370 JS::PromiseRejectionHandlingState state) {
1371 ShellContext* sc = GetShellContext(cx);
1372 if (!sc->trackUnhandledRejections) {
1373 return true;
1376 #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
1377 if (cx->runningOOMTest) {
1378 // When OOM happens, we cannot reliably track the set of unhandled
1379 // promise rejections. Throw error only when simulated OOM is used
1380 // *and* promises are used in the test.
1381 JS_ReportErrorASCII(
1383 "Can't track unhandled rejections while running simulated OOM "
1384 "test. Call ignoreUnhandledRejections before using oomTest etc.");
1385 return false;
1387 #endif
1389 if (!sc->unhandledRejectedPromises) {
1390 sc->unhandledRejectedPromises = SetObject::create(cx);
1391 if (!sc->unhandledRejectedPromises) {
1392 return false;
1396 RootedValue promiseVal(cx, ObjectValue(*promise));
1398 AutoRealm ar(cx, sc->unhandledRejectedPromises);
1399 if (!cx->compartment()->wrap(cx, &promiseVal)) {
1400 return false;
1403 switch (state) {
1404 case JS::PromiseRejectionHandlingState::Unhandled:
1405 if (!SetObject::add(cx, sc->unhandledRejectedPromises, promiseVal)) {
1406 return false;
1408 break;
1409 case JS::PromiseRejectionHandlingState::Handled:
1410 bool deleted = false;
1411 if (!SetObject::delete_(cx, sc->unhandledRejectedPromises, promiseVal,
1412 &deleted)) {
1413 return false;
1415 // We can't MOZ_ASSERT(deleted) here, because it's possible we failed to
1416 // add the promise in the first place, due to OOM.
1417 break;
1420 return true;
1423 static void ForwardingPromiseRejectionTrackerCallback(
1424 JSContext* cx, bool mutedErrors, JS::HandleObject promise,
1425 JS::PromiseRejectionHandlingState state, void* data) {
1426 AutoReportException are(cx);
1428 if (!TrackUnhandledRejections(cx, promise, state)) {
1429 return;
1432 RootedValue callback(cx,
1433 GetShellContext(cx)->promiseRejectionTrackerCallback);
1434 if (callback.isNull()) {
1435 return;
1438 AutoRealm ar(cx, &callback.toObject());
1440 FixedInvokeArgs<2> args(cx);
1441 args[0].setObject(*promise);
1442 args[1].setInt32(static_cast<int32_t>(state));
1444 if (!JS_WrapValue(cx, args[0])) {
1445 return;
1448 RootedValue rval(cx);
1449 (void)Call(cx, callback, UndefinedHandleValue, args, &rval);
1452 static bool SetPromiseRejectionTrackerCallback(JSContext* cx, unsigned argc,
1453 Value* vp) {
1454 CallArgs args = CallArgsFromVp(argc, vp);
1456 if (!IsFunctionObject(args.get(0))) {
1457 JS_ReportErrorASCII(
1459 "setPromiseRejectionTrackerCallback expects a function as its sole "
1460 "argument");
1461 return false;
1464 GetShellContext(cx)->promiseRejectionTrackerCallback = args[0];
1466 args.rval().setUndefined();
1467 return true;
1470 // clang-format off
1471 static const char* telemetryNames[static_cast<int>(JSMetric::Count)] = {
1472 #define LIT(NAME, _) #NAME,
1473 FOR_EACH_JS_METRIC(LIT)
1474 #undef LIT
1476 // clang-format on
1478 // Telemetry can be executed from multiple threads, and the callback is
1479 // responsible to avoid contention on the recorded telemetry data.
1480 static Mutex* telemetryLock = nullptr;
1481 class MOZ_RAII AutoLockTelemetry : public LockGuard<Mutex> {
1482 using Base = LockGuard<Mutex>;
1484 public:
1485 AutoLockTelemetry() : Base(*telemetryLock) { MOZ_ASSERT(telemetryLock); }
1488 using TelemetryData = uint32_t;
1489 using TelemetryVec = Vector<TelemetryData, 0, SystemAllocPolicy>;
1490 static mozilla::Array<TelemetryVec, size_t(JSMetric::Count)> telemetryResults;
1491 static void AccumulateTelemetryDataCallback(JSMetric id, uint32_t sample) {
1492 AutoLockTelemetry alt;
1493 // We ignore OOMs while writting teleemtry data.
1494 if (telemetryResults[static_cast<int>(id)].append(sample)) {
1495 return;
1499 static void WriteTelemetryDataToDisk(const char* dir) {
1500 const int pathLen = 260;
1501 char fileName[pathLen];
1502 Fprinter output;
1503 auto initOutput = [&](const char* name) -> bool {
1504 if (SprintfLiteral(fileName, "%s%s.csv", dir, name) >= pathLen) {
1505 return false;
1507 FILE* file = fopen(fileName, "a");
1508 if (!file) {
1509 return false;
1511 output.init(file);
1512 return true;
1515 for (size_t id = 0; id < size_t(JSMetric::Count); id++) {
1516 auto clear = MakeScopeExit([&] { telemetryResults[id].clearAndFree(); });
1517 if (!initOutput(telemetryNames[id])) {
1518 continue;
1520 for (uint32_t data : telemetryResults[id]) {
1521 output.printf("%u\n", data);
1523 output.finish();
1527 #undef MAP_TELEMETRY
1529 static bool BoundToAsyncStack(JSContext* cx, unsigned argc, Value* vp) {
1530 CallArgs args = CallArgsFromVp(argc, vp);
1532 RootedValue function(cx, GetFunctionNativeReserved(&args.callee(), 0));
1533 RootedObject options(
1534 cx, &GetFunctionNativeReserved(&args.callee(), 1).toObject());
1536 Rooted<SavedFrame*> stack(cx, nullptr);
1537 bool isExplicit;
1539 RootedValue v(cx);
1541 if (!JS_GetProperty(cx, options, "stack", &v)) {
1542 return false;
1544 if (!v.isObject() || !v.toObject().is<SavedFrame>()) {
1545 JS_ReportErrorASCII(cx,
1546 "The 'stack' property must be a SavedFrame object.");
1547 return false;
1549 stack = &v.toObject().as<SavedFrame>();
1551 if (!JS_GetProperty(cx, options, "cause", &v)) {
1552 return false;
1554 RootedString causeString(cx, ToString(cx, v));
1555 if (!causeString) {
1556 MOZ_ASSERT(cx->isExceptionPending());
1557 return false;
1560 UniqueChars cause = JS_EncodeStringToUTF8(cx, causeString);
1561 if (!cause) {
1562 MOZ_ASSERT(cx->isExceptionPending());
1563 return false;
1566 if (!JS_GetProperty(cx, options, "explicit", &v)) {
1567 return false;
1569 isExplicit = v.isUndefined() ? true : ToBoolean(v);
1571 auto kind =
1572 (isExplicit ? JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::EXPLICIT
1573 : JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::IMPLICIT);
1575 JS::AutoSetAsyncStackForNewCalls asasfnckthxbye(cx, stack, cause.get(), kind);
1576 return Call(cx, UndefinedHandleValue, function, JS::HandleValueArray::empty(),
1577 args.rval());
1580 static bool BindToAsyncStack(JSContext* cx, unsigned argc, Value* vp) {
1581 CallArgs args = CallArgsFromVp(argc, vp);
1583 if (args.length() != 2) {
1584 JS_ReportErrorASCII(cx, "bindToAsyncStack takes exactly two arguments.");
1585 return false;
1588 if (!args[0].isObject() || !IsCallable(args[0])) {
1589 JS_ReportErrorASCII(
1590 cx, "bindToAsyncStack's first argument should be a function.");
1591 return false;
1594 if (!args[1].isObject()) {
1595 JS_ReportErrorASCII(
1596 cx, "bindToAsyncStack's second argument should be an object.");
1597 return false;
1600 RootedFunction bound(cx, NewFunctionWithReserved(cx, BoundToAsyncStack, 0, 0,
1601 "bindToAsyncStack thunk"));
1602 if (!bound) {
1603 return false;
1605 SetFunctionNativeReserved(bound, 0, args[0]);
1606 SetFunctionNativeReserved(bound, 1, args[1]);
1608 args.rval().setObject(*bound);
1609 return true;
1612 #ifdef JS_HAS_INTL_API
1613 static bool AddIntlExtras(JSContext* cx, unsigned argc, Value* vp) {
1614 CallArgs args = CallArgsFromVp(argc, vp);
1615 if (!args.get(0).isObject()) {
1616 JS_ReportErrorASCII(cx, "addIntlExtras must be passed an object");
1617 return false;
1619 JS::RootedObject intl(cx, &args[0].toObject());
1621 static const JSFunctionSpec funcs[] = {
1622 JS_SELF_HOSTED_FN("getCalendarInfo", "Intl_getCalendarInfo", 1, 0),
1623 JS_FS_END};
1625 if (!JS_DefineFunctions(cx, intl, funcs)) {
1626 return false;
1629 if (!JS::AddMozDateTimeFormatConstructor(cx, intl)) {
1630 return false;
1633 if (!JS::AddMozDisplayNamesConstructor(cx, intl)) {
1634 return false;
1637 args.rval().setUndefined();
1638 return true;
1640 #endif // JS_HAS_INTL_API
1642 [[nodiscard]] static bool EvalUtf8AndPrint(JSContext* cx, const char* bytes,
1643 size_t length, int lineno,
1644 bool compileOnly) {
1645 // Eval.
1646 JS::CompileOptions options(cx);
1647 options.setIntroductionType("js shell interactive")
1648 .setIsRunOnce(true)
1649 .setFileAndLine("typein", lineno)
1650 .setEagerDelazificationStrategy(defaultDelazificationMode);
1652 JS::SourceText<Utf8Unit> srcBuf;
1653 if (!srcBuf.init(cx, bytes, length, JS::SourceOwnership::Borrowed)) {
1654 return false;
1657 RootedScript script(cx, JS::Compile(cx, options, srcBuf));
1658 if (!script) {
1659 return false;
1661 if (compileOnly) {
1662 return true;
1664 RootedValue result(cx);
1665 if (!JS_ExecuteScript(cx, script, &result)) {
1666 return false;
1669 if (!result.isUndefined() && gOutFile->isOpen()) {
1670 // Print.
1671 RootedString str(cx, JS_ValueToSource(cx, result));
1672 if (!str) {
1673 return false;
1676 UniqueChars utf8chars = JS_EncodeStringToUTF8(cx, str);
1677 if (!utf8chars) {
1678 return false;
1680 fprintf(gOutFile->fp, "%s\n", utf8chars.get());
1682 return true;
1685 [[nodiscard]] static bool ReadEvalPrintLoop(JSContext* cx, FILE* in,
1686 bool compileOnly) {
1687 ShellContext* sc = GetShellContext(cx);
1688 int lineno = 1;
1689 bool hitEOF = false;
1691 do {
1693 * Accumulate lines until we get a 'compilable unit' - one that either
1694 * generates an error (before running out of source) or that compiles
1695 * cleanly. This should be whenever we get a complete statement that
1696 * coincides with the end of a line.
1698 int startline = lineno;
1699 typedef Vector<char, 32> CharBuffer;
1700 RootedObject globalLexical(cx, &cx->global()->lexicalEnvironment());
1701 CharBuffer buffer(cx);
1702 do {
1703 ScheduleWatchdog(cx, -1);
1704 sc->serviceInterrupt = false;
1705 errno = 0;
1707 mozilla::UniqueFreePtr<char[]> line =
1708 GetLine(in, startline == lineno ? "js> " : "");
1709 if (!line) {
1710 if (errno) {
1711 if (UniqueChars error = SystemErrorMessage(cx, errno)) {
1712 JS_ReportErrorUTF8(cx, "%s", error.get());
1714 return false;
1716 hitEOF = true;
1717 break;
1720 if (!buffer.append(line.get(), strlen(line.get())) ||
1721 !buffer.append('\n')) {
1722 return false;
1725 lineno++;
1726 if (!ScheduleWatchdog(cx, sc->timeoutInterval)) {
1727 hitEOF = true;
1728 break;
1730 } while (!JS_Utf8BufferIsCompilableUnit(cx, cx->global(), buffer.begin(),
1731 buffer.length()));
1733 if (hitEOF && buffer.empty()) {
1734 break;
1738 // Report exceptions but keep going.
1739 AutoReportException are(cx);
1740 (void)EvalUtf8AndPrint(cx, buffer.begin(), buffer.length(), startline,
1741 compileOnly);
1744 // If a let or const fail to initialize they will remain in an unusable
1745 // without further intervention. This call cleans up the global scope,
1746 // setting uninitialized lexicals to undefined so that they may still
1747 // be used. This behavior is _only_ acceptable in the context of the repl.
1748 if (JS::ForceLexicalInitialization(cx, globalLexical) &&
1749 gErrFile->isOpen()) {
1750 fputs(
1751 "Warning: According to the standard, after the above exception,\n"
1752 "Warning: the global bindings should be permanently uninitialized.\n"
1753 "Warning: We have non-standard-ly initialized them to `undefined`"
1754 "for you.\nWarning: This nicety only happens in the JS shell.\n",
1755 stderr);
1758 RunShellJobs(cx);
1759 } while (!hitEOF && !sc->quitting);
1761 if (gOutFile->isOpen()) {
1762 fprintf(gOutFile->fp, "\n");
1765 return true;
1768 enum FileKind {
1769 PreludeScript, // UTF-8 script, fully-parsed, to avoid conflicting
1770 // configurations.
1771 FileScript, // UTF-8, directly parsed as such
1772 FileScriptUtf16, // FileScript, but inflate to UTF-16 before parsing
1773 FileModule,
1776 [[nodiscard]] static bool Process(JSContext* cx, const char* filename,
1777 bool forceTTY, FileKind kind) {
1778 FILE* file;
1779 if (forceTTY || !filename || strcmp(filename, "-") == 0) {
1780 file = stdin;
1781 } else {
1782 file = OpenFile(cx, filename, "rb");
1783 if (!file) {
1784 return false;
1787 AutoCloseFile autoClose(file);
1789 bool fullParse = false;
1790 if (!forceTTY && !isatty(fileno(file))) {
1791 // It's not interactive - just execute it.
1792 switch (kind) {
1793 case PreludeScript:
1794 fullParse = true;
1795 if (!RunFile(cx, filename, file, CompileUtf8::DontInflate, compileOnly,
1796 fullParse)) {
1797 return false;
1799 break;
1800 case FileScript:
1801 if (!RunFile(cx, filename, file, CompileUtf8::DontInflate, compileOnly,
1802 fullParse)) {
1803 return false;
1805 break;
1806 case FileScriptUtf16:
1807 if (!RunFile(cx, filename, file, CompileUtf8::InflateToUtf16,
1808 compileOnly, fullParse)) {
1809 return false;
1811 break;
1812 case FileModule:
1813 if (!RunModule(cx, filename, compileOnly)) {
1814 return false;
1816 break;
1817 default:
1818 MOZ_CRASH("Impossible FileKind!");
1820 } else {
1821 // It's an interactive filehandle; drop into read-eval-print loop.
1822 MOZ_ASSERT(kind == FileScript);
1823 if (!ReadEvalPrintLoop(cx, file, compileOnly)) {
1824 return false;
1827 #ifdef FUZZING_JS_FUZZILLI
1828 fprintf(stderr, "executionHash is 0x%x with %d inputs\n", cx->executionHash,
1829 cx->executionHashInputs);
1830 #endif
1831 return true;
1834 #ifdef XP_WIN
1835 # define GET_FD_FROM_FILE(a) int(_get_osfhandle(fileno(a)))
1836 #else
1837 # define GET_FD_FROM_FILE(a) fileno(a)
1838 #endif
1840 static void freeExternalCallback(void* contents, void* userData) {
1841 MOZ_ASSERT(!userData);
1842 js_free(contents);
1845 static bool CreateExternalArrayBuffer(JSContext* cx, unsigned argc, Value* vp) {
1846 CallArgs args = CallArgsFromVp(argc, vp);
1847 if (args.length() != 1) {
1848 JS_ReportErrorNumberASCII(
1849 cx, my_GetErrorMessage, nullptr,
1850 args.length() < 1 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS,
1851 "createExternalArrayBuffer");
1852 return false;
1855 int32_t bytes = 0;
1856 if (!ToInt32(cx, args[0], &bytes)) {
1857 return false;
1860 if (bytes < 0) {
1861 JS_ReportErrorASCII(cx, "Size must be non-negative");
1862 return false;
1865 void* buffer = js_calloc(bytes);
1866 if (!buffer) {
1867 JS_ReportOutOfMemory(cx);
1868 return false;
1871 UniquePtr<void, JS::BufferContentsDeleter> ptr{buffer,
1872 {&freeExternalCallback}};
1873 RootedObject arrayBuffer(
1874 cx, JS::NewExternalArrayBuffer(cx, bytes, std::move(ptr)));
1875 if (!arrayBuffer) {
1876 return false;
1879 args.rval().setObject(*arrayBuffer);
1880 return true;
1883 static bool CreateMappedArrayBuffer(JSContext* cx, unsigned argc, Value* vp) {
1884 CallArgs args = CallArgsFromVp(argc, vp);
1886 if (args.length() < 1 || args.length() > 3) {
1887 JS_ReportErrorNumberASCII(
1888 cx, my_GetErrorMessage, nullptr,
1889 args.length() < 1 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS,
1890 "createMappedArrayBuffer");
1891 return false;
1894 RootedString rawFilenameStr(cx, JS::ToString(cx, args[0]));
1895 if (!rawFilenameStr) {
1896 return false;
1898 // It's a little bizarre to resolve relative to the script, but for testing
1899 // I need a file at a known location, and the only good way I know of to do
1900 // that right now is to include it in the repo alongside the test script.
1901 // Bug 944164 would introduce an alternative.
1902 Rooted<JSString*> filenameStr(
1903 cx, ResolvePath(cx, rawFilenameStr, ScriptRelative));
1904 if (!filenameStr) {
1905 return false;
1907 UniqueChars filename = JS_EncodeStringToUTF8(cx, filenameStr);
1908 if (!filename) {
1909 return false;
1912 uint32_t offset = 0;
1913 if (args.length() >= 2) {
1914 if (!JS::ToUint32(cx, args[1], &offset)) {
1915 return false;
1919 bool sizeGiven = false;
1920 uint32_t size;
1921 if (args.length() >= 3) {
1922 if (!JS::ToUint32(cx, args[2], &size)) {
1923 return false;
1925 sizeGiven = true;
1926 if (size == 0) {
1927 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1928 JSMSG_BAD_ARRAY_LENGTH);
1929 return false;
1933 FILE* file = OpenFile(cx, filename.get(), "rb");
1934 if (!file) {
1935 return false;
1937 AutoCloseFile autoClose(file);
1939 struct stat st;
1940 if (fstat(fileno(file), &st) < 0) {
1941 JS_ReportErrorASCII(cx, "Unable to stat file");
1942 return false;
1945 if ((st.st_mode & S_IFMT) != S_IFREG) {
1946 JS_ReportErrorASCII(cx, "Path is not a regular file");
1947 return false;
1950 if (!sizeGiven) {
1951 if (off_t(offset) >= st.st_size) {
1952 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1953 JSMSG_OFFSET_LARGER_THAN_FILESIZE);
1954 return false;
1956 size = st.st_size - offset;
1959 void* contents =
1960 JS::CreateMappedArrayBufferContents(GET_FD_FROM_FILE(file), offset, size);
1961 if (!contents) {
1962 JS_ReportErrorASCII(cx,
1963 "failed to allocate mapped array buffer contents "
1964 "(possibly due to bad alignment)");
1965 return false;
1968 RootedObject obj(cx,
1969 JS::NewMappedArrayBufferWithContents(cx, size, contents));
1970 if (!obj) {
1971 return false;
1974 args.rval().setObject(*obj);
1975 return true;
1978 #undef GET_FD_FROM_FILE
1980 class UserBufferObject : public NativeObject {
1981 static const uint32_t BUFFER_SLOT = 0;
1982 static const uint32_t BYTE_LENGTH_SLOT = 1;
1983 static const uint32_t RESERVED_SLOTS = 2;
1985 static constexpr auto BufferMemoryUse = MemoryUse::Embedding1;
1987 static void finalize(JS::GCContext* gcx, JSObject* obj);
1989 public:
1990 static const JSClassOps classOps_;
1991 static const JSClass class_;
1993 [[nodiscard]] static UserBufferObject* create(JSContext* cx,
1994 size_t byteLength);
1996 void* buffer() const {
1997 auto& buffer = getReservedSlot(BUFFER_SLOT);
1998 if (buffer.isUndefined()) {
1999 return nullptr;
2001 return buffer.toPrivate();
2004 size_t byteLength() const {
2005 return size_t(getReservedSlot(BYTE_LENGTH_SLOT).toPrivate());
2009 const JSClassOps UserBufferObject::classOps_ = {
2010 nullptr, // addProperty
2011 nullptr, // delProperty
2012 nullptr, // enumerate
2013 nullptr, // newEnumerate
2014 nullptr, // resolve
2015 nullptr, // mayResolve
2016 UserBufferObject::finalize, // finalize
2017 nullptr, // call
2018 nullptr, // construct
2019 nullptr, // trace
2022 const JSClass UserBufferObject::class_ = {
2023 "UserBufferObject",
2024 JSCLASS_HAS_RESERVED_SLOTS(UserBufferObject::RESERVED_SLOTS) |
2025 JSCLASS_BACKGROUND_FINALIZE,
2026 &UserBufferObject::classOps_,
2029 UserBufferObject* UserBufferObject::create(JSContext* cx, size_t byteLength) {
2030 void* buffer = js_calloc(byteLength);
2031 if (!buffer) {
2032 JS_ReportOutOfMemory(cx);
2033 return nullptr;
2035 UniquePtr<void, JS::FreePolicy> ptr(buffer);
2037 auto* userBuffer = NewObjectWithGivenProto<UserBufferObject>(cx, nullptr);
2038 if (!userBuffer) {
2039 return nullptr;
2042 InitReservedSlot(userBuffer, BUFFER_SLOT, ptr.release(), byteLength,
2043 BufferMemoryUse);
2044 userBuffer->initReservedSlot(BYTE_LENGTH_SLOT, PrivateValue(byteLength));
2046 return userBuffer;
2049 void UserBufferObject::finalize(JS::GCContext* gcx, JSObject* obj) {
2050 auto* userBuffer = &obj->as<UserBufferObject>();
2051 if (auto* buffer = userBuffer->buffer()) {
2052 gcx->free_(userBuffer, buffer, userBuffer->byteLength(), BufferMemoryUse);
2056 static bool CreateUserArrayBuffer(JSContext* cx, unsigned argc, Value* vp) {
2057 CallArgs args = CallArgsFromVp(argc, vp);
2058 if (args.length() != 1) {
2059 JS_ReportErrorNumberASCII(
2060 cx, my_GetErrorMessage, nullptr,
2061 args.length() < 1 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS,
2062 "createUserArrayBuffer");
2063 return false;
2066 int32_t bytes = 0;
2067 if (!ToInt32(cx, args[0], &bytes)) {
2068 return false;
2070 if (bytes < 0) {
2071 JS_ReportErrorASCII(cx, "Size must be non-negative");
2072 return false;
2075 Rooted<UserBufferObject*> userBuffer(cx, UserBufferObject::create(cx, bytes));
2076 if (!userBuffer) {
2077 return false;
2080 Rooted<JSObject*> arrayBuffer(
2081 cx, JS::NewArrayBufferWithUserOwnedContents(cx, userBuffer->byteLength(),
2082 userBuffer->buffer()));
2083 if (!arrayBuffer) {
2084 return false;
2087 // Create a strong reference from |arrayBuffer| to |userBuffer|. This ensures
2088 // |userBuffer| can't outlive |arrayBuffer|. That way we don't have to worry
2089 // about detaching the ArrayBuffer object when |userBuffer| gets finalized.
2090 // The reference is made through a private name, because we don't want to
2091 // expose |userBuffer| to user-code.
2093 auto* privateName = NewPrivateName(cx, cx->names().empty_.toHandle());
2094 if (!privateName) {
2095 return false;
2098 Rooted<PropertyKey> id(cx, PropertyKey::Symbol(privateName));
2099 Rooted<JS::Value> userBufferVal(cx, ObjectValue(*userBuffer));
2100 if (!js::DefineDataProperty(cx, arrayBuffer, id, userBufferVal, 0)) {
2101 return false;
2104 args.rval().setObject(*arrayBuffer);
2105 return true;
2108 static bool AddPromiseReactions(JSContext* cx, unsigned argc, Value* vp) {
2109 CallArgs args = CallArgsFromVp(argc, vp);
2111 if (args.length() != 3) {
2112 JS_ReportErrorNumberASCII(
2113 cx, my_GetErrorMessage, nullptr,
2114 args.length() < 3 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS,
2115 "addPromiseReactions");
2116 return false;
2119 RootedObject promise(cx);
2120 if (args[0].isObject()) {
2121 promise = &args[0].toObject();
2124 if (!promise || !JS::IsPromiseObject(promise)) {
2125 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
2126 JSSMSG_INVALID_ARGS, "addPromiseReactions");
2127 return false;
2130 RootedObject onResolve(cx);
2131 if (args[1].isObject()) {
2132 onResolve = &args[1].toObject();
2135 RootedObject onReject(cx);
2136 if (args[2].isObject()) {
2137 onReject = &args[2].toObject();
2140 if (!onResolve || !onResolve->is<JSFunction>() || !onReject ||
2141 !onReject->is<JSFunction>()) {
2142 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
2143 JSSMSG_INVALID_ARGS, "addPromiseReactions");
2144 return false;
2147 return JS::AddPromiseReactions(cx, promise, onResolve, onReject);
2150 static bool IgnoreUnhandledRejections(JSContext* cx, unsigned argc, Value* vp) {
2151 CallArgs args = CallArgsFromVp(argc, vp);
2153 ShellContext* sc = GetShellContext(cx);
2154 sc->trackUnhandledRejections = false;
2156 args.rval().setUndefined();
2157 return true;
2160 static bool Options(JSContext* cx, unsigned argc, Value* vp) {
2161 CallArgs args = CallArgsFromVp(argc, vp);
2163 JS::ContextOptions oldContextOptions = JS::ContextOptionsRef(cx);
2164 for (unsigned i = 0; i < args.length(); i++) {
2165 RootedString str(cx, JS::ToString(cx, args[i]));
2166 if (!str) {
2167 return false;
2170 Rooted<JSLinearString*> opt(cx, str->ensureLinear(cx));
2171 if (!opt) {
2172 return false;
2175 if (StringEqualsLiteral(opt, "throw_on_asmjs_validation_failure")) {
2176 JS::ContextOptionsRef(cx).toggleThrowOnAsmJSValidationFailure();
2177 } else {
2178 UniqueChars optChars = QuoteString(cx, opt, '"');
2179 if (!optChars) {
2180 return false;
2183 JS_ReportErrorASCII(cx,
2184 "unknown option name %s."
2185 " The valid name is "
2186 "throw_on_asmjs_validation_failure.",
2187 optChars.get());
2188 return false;
2192 UniqueChars names = DuplicateString("");
2193 bool found = false;
2194 if (names && oldContextOptions.throwOnAsmJSValidationFailure()) {
2195 names = JS_sprintf_append(std::move(names), "%s%s", found ? "," : "",
2196 "throw_on_asmjs_validation_failure");
2197 found = true;
2199 if (!names) {
2200 JS_ReportOutOfMemory(cx);
2201 return false;
2204 JSString* str = JS_NewStringCopyZ(cx, names.get());
2205 if (!str) {
2206 return false;
2208 args.rval().setString(str);
2209 return true;
2212 static bool LoadScript(JSContext* cx, unsigned argc, Value* vp,
2213 bool scriptRelative) {
2214 CallArgs args = CallArgsFromVp(argc, vp);
2216 RootedString str(cx);
2217 for (unsigned i = 0; i < args.length(); i++) {
2218 str = JS::ToString(cx, args[i]);
2219 if (!str) {
2220 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
2221 JSSMSG_INVALID_ARGS, "load");
2222 return false;
2225 str = ResolvePath(cx, str, scriptRelative ? ScriptRelative : RootRelative);
2226 if (!str) {
2227 JS_ReportErrorASCII(cx, "unable to resolve path");
2228 return false;
2231 UniqueChars filename = JS_EncodeStringToUTF8(cx, str);
2232 if (!filename) {
2233 return false;
2236 errno = 0;
2238 CompileOptions opts(cx);
2239 opts.setIntroductionType("js shell load")
2240 .setIsRunOnce(true)
2241 .setNoScriptRval(true)
2242 .setEagerDelazificationStrategy(defaultDelazificationMode);
2244 RootedValue unused(cx);
2245 if (!(compileOnly
2246 ? JS::CompileUtf8Path(cx, opts, filename.get()) != nullptr
2247 : JS::EvaluateUtf8Path(cx, opts, filename.get(), &unused))) {
2248 return false;
2252 args.rval().setUndefined();
2253 return true;
2256 static bool Load(JSContext* cx, unsigned argc, Value* vp) {
2257 return LoadScript(cx, argc, vp, false);
2260 static bool LoadScriptRelativeToScript(JSContext* cx, unsigned argc,
2261 Value* vp) {
2262 return LoadScript(cx, argc, vp, true);
2265 static void my_LargeAllocFailCallback() {
2266 JSContext* cx = TlsContext.get();
2267 if (!cx) {
2268 return;
2271 MOZ_ASSERT(!JS::RuntimeHeapIsBusy());
2273 JS::PrepareForFullGC(cx);
2274 cx->runtime()->gc.gc(JS::GCOptions::Shrink,
2275 JS::GCReason::SHARED_MEMORY_LIMIT);
2278 static const uint32_t CacheEntry_SOURCE = 0;
2279 static const uint32_t CacheEntry_BYTECODE = 1;
2280 static const uint32_t CacheEntry_OPTIONS = 2;
2282 // Some compile options can't be combined differently between save and load.
2284 // CacheEntries store a CacheOption set, and on load an exception is thrown
2285 // if the entries are incompatible.
2287 enum CacheOptions : uint32_t {
2288 IsRunOnce,
2289 NoScriptRval,
2290 Global,
2291 NonSyntactic,
2292 SourceIsLazy,
2293 ForceFullParse,
2296 struct CacheOptionSet : public mozilla::EnumSet<CacheOptions> {
2297 using mozilla::EnumSet<CacheOptions>::EnumSet;
2299 explicit CacheOptionSet(const CompileOptions& options) : EnumSet() {
2300 initFromOptions(options);
2303 void initFromOptions(const CompileOptions& options) {
2304 if (options.noScriptRval) {
2305 *this += CacheOptions::NoScriptRval;
2307 if (options.isRunOnce) {
2308 *this += CacheOptions::IsRunOnce;
2310 if (options.sourceIsLazy) {
2311 *this += CacheOptions::SourceIsLazy;
2313 if (options.forceFullParse()) {
2314 *this += CacheOptions::ForceFullParse;
2316 if (options.nonSyntacticScope) {
2317 *this += CacheOptions::NonSyntactic;
2322 static bool CacheOptionsCompatible(const CacheOptionSet& a,
2323 const CacheOptionSet& b) {
2324 // If the options are identical, they are trivially compatible.
2325 return a == b;
2328 static const JSClass CacheEntry_class = {"CacheEntryObject",
2329 JSCLASS_HAS_RESERVED_SLOTS(3)};
2331 static bool CacheEntry(JSContext* cx, unsigned argc, JS::Value* vp) {
2332 CallArgs args = CallArgsFromVp(argc, vp);
2334 if (args.length() != 1 || !args[0].isString()) {
2335 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
2336 JSSMSG_INVALID_ARGS, "CacheEntry");
2337 return false;
2340 RootedObject obj(cx, JS_NewObject(cx, &CacheEntry_class));
2341 if (!obj) {
2342 return false;
2345 JS::SetReservedSlot(obj, CacheEntry_SOURCE, args[0]);
2346 JS::SetReservedSlot(obj, CacheEntry_BYTECODE, UndefinedValue());
2348 // Fill in empty option set.
2349 CacheOptionSet defaultOptions;
2350 JS::SetReservedSlot(obj, CacheEntry_OPTIONS,
2351 Int32Value(defaultOptions.serialize()));
2353 args.rval().setObject(*obj);
2354 return true;
2357 static bool CacheEntry_isCacheEntry(JSObject* cache) {
2358 return cache->hasClass(&CacheEntry_class);
2361 static JSString* CacheEntry_getSource(JSContext* cx, HandleObject cache) {
2362 MOZ_ASSERT(CacheEntry_isCacheEntry(cache));
2363 Value v = JS::GetReservedSlot(cache, CacheEntry_SOURCE);
2364 if (!v.isString()) {
2365 JS_ReportErrorASCII(
2366 cx, "CacheEntry_getSource: Unexpected type of source reserved slot.");
2367 return nullptr;
2370 return v.toString();
2373 static bool CacheEntry_compatible(JSContext* cx, HandleObject cache,
2374 const CacheOptionSet& currentOptionSet) {
2375 CacheOptionSet cacheEntryOptions;
2376 MOZ_ASSERT(CacheEntry_isCacheEntry(cache));
2377 Value v = JS::GetReservedSlot(cache, CacheEntry_OPTIONS);
2378 cacheEntryOptions.deserialize(v.toInt32());
2379 if (!CacheOptionsCompatible(cacheEntryOptions, currentOptionSet)) {
2380 JS_ReportErrorASCII(cx,
2381 "CacheEntry_compatible: Incompatible cache contents");
2382 return false;
2384 return true;
2387 static uint8_t* CacheEntry_getBytecode(JSContext* cx, HandleObject cache,
2388 size_t* length) {
2389 MOZ_ASSERT(CacheEntry_isCacheEntry(cache));
2390 Value v = JS::GetReservedSlot(cache, CacheEntry_BYTECODE);
2391 if (!v.isObject() || !v.toObject().is<ArrayBufferObject>()) {
2392 JS_ReportErrorASCII(
2394 "CacheEntry_getBytecode: Unexpected type of bytecode reserved slot.");
2395 return nullptr;
2398 ArrayBufferObject* arrayBuffer = &v.toObject().as<ArrayBufferObject>();
2399 *length = arrayBuffer->byteLength();
2400 return arrayBuffer->dataPointer();
2403 static bool CacheEntry_setBytecode(JSContext* cx, HandleObject cache,
2404 const CacheOptionSet& cacheOptions,
2405 uint8_t* buffer, uint32_t length) {
2406 MOZ_ASSERT(CacheEntry_isCacheEntry(cache));
2408 using BufferContents = ArrayBufferObject::BufferContents;
2410 BufferContents contents = BufferContents::createMallocedUnknownArena(buffer);
2411 Rooted<ArrayBufferObject*> arrayBuffer(
2412 cx, ArrayBufferObject::createForContents(cx, length, contents));
2413 if (!arrayBuffer) {
2414 return false;
2417 JS::SetReservedSlot(cache, CacheEntry_BYTECODE, ObjectValue(*arrayBuffer));
2418 JS::SetReservedSlot(cache, CacheEntry_OPTIONS,
2419 Int32Value(cacheOptions.serialize()));
2420 return true;
2423 static bool ConvertTranscodeResultToJSException(JSContext* cx,
2424 JS::TranscodeResult rv) {
2425 switch (rv) {
2426 case JS::TranscodeResult::Ok:
2427 return true;
2429 default:
2430 [[fallthrough]];
2431 case JS::TranscodeResult::Failure:
2432 MOZ_ASSERT(!cx->isExceptionPending());
2433 JS_ReportErrorASCII(cx, "generic warning");
2434 return false;
2435 case JS::TranscodeResult::Failure_BadBuildId:
2436 MOZ_ASSERT(!cx->isExceptionPending());
2437 JS_ReportErrorASCII(cx, "the build-id does not match");
2438 return false;
2439 case JS::TranscodeResult::Failure_AsmJSNotSupported:
2440 MOZ_ASSERT(!cx->isExceptionPending());
2441 JS_ReportErrorASCII(cx, "Asm.js is not supported by XDR");
2442 return false;
2443 case JS::TranscodeResult::Failure_BadDecode:
2444 MOZ_ASSERT(!cx->isExceptionPending());
2445 JS_ReportErrorASCII(cx, "XDR data corruption");
2446 return false;
2448 case JS::TranscodeResult::Throw:
2449 MOZ_ASSERT(cx->isExceptionPending());
2450 return false;
2454 static bool Evaluate(JSContext* cx, unsigned argc, Value* vp) {
2455 CallArgs args = CallArgsFromVp(argc, vp);
2457 if (args.length() < 1 || args.length() > 2) {
2458 JS_ReportErrorNumberASCII(
2459 cx, my_GetErrorMessage, nullptr,
2460 args.length() < 1 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS,
2461 "evaluate");
2462 return false;
2465 RootedString code(cx, nullptr);
2466 RootedObject cacheEntry(cx, nullptr);
2467 if (args[0].isString()) {
2468 code = args[0].toString();
2469 } else if (args[0].isObject() &&
2470 CacheEntry_isCacheEntry(&args[0].toObject())) {
2471 cacheEntry = &args[0].toObject();
2472 code = CacheEntry_getSource(cx, cacheEntry);
2473 if (!code) {
2474 return false;
2478 if (!code || (args.length() == 2 && args[1].isPrimitive())) {
2479 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
2480 JSSMSG_INVALID_ARGS, "evaluate");
2481 return false;
2484 RootedObject opts(cx);
2485 if (args.length() == 2) {
2486 if (!args[1].isObject()) {
2487 JS_ReportErrorASCII(cx, "evaluate: The 2nd argument must be an object");
2488 return false;
2491 opts = &args[1].toObject();
2494 RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
2495 MOZ_ASSERT(global);
2497 // Check "global" property before everything to use the given global's
2498 // option as the default value.
2499 Maybe<CompileOptions> maybeOptions;
2500 if (opts) {
2501 RootedValue v(cx);
2502 if (!JS_GetProperty(cx, opts, "global", &v)) {
2503 return false;
2505 if (!v.isUndefined()) {
2506 if (v.isObject()) {
2507 global = js::CheckedUnwrapDynamic(&v.toObject(), cx,
2508 /* stopAtWindowProxy = */ false);
2509 if (!global) {
2510 return false;
2513 if (!global || !(JS::GetClass(global)->flags & JSCLASS_IS_GLOBAL)) {
2514 JS_ReportErrorNumberASCII(
2515 cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
2516 "\"global\" passed to evaluate()", "not a global object");
2517 return false;
2520 JSAutoRealm ar(cx, global);
2521 maybeOptions.emplace(cx);
2524 if (!maybeOptions) {
2525 // If "global" property is not given, use the current global's option as
2526 // the default value.
2527 maybeOptions.emplace(cx);
2530 CompileOptions& options = maybeOptions.ref();
2531 UniqueChars fileNameBytes;
2532 RootedString displayURL(cx);
2533 RootedString sourceMapURL(cx);
2534 bool catchTermination = false;
2535 bool loadBytecode = false;
2536 bool saveIncrementalBytecode = false;
2537 bool execute = true;
2538 bool assertEqBytecode = false;
2539 JS::RootedObjectVector envChain(cx);
2540 RootedObject callerGlobal(cx, cx->global());
2542 options.setIntroductionType("js shell evaluate")
2543 .setFileAndLine("@evaluate", 1)
2544 .setDeferDebugMetadata();
2546 RootedValue privateValue(cx);
2547 RootedString elementAttributeName(cx);
2549 if (opts) {
2550 if (!js::ParseCompileOptions(cx, options, opts, &fileNameBytes)) {
2551 return false;
2553 if (!ParseDebugMetadata(cx, opts, &privateValue, &elementAttributeName)) {
2554 return false;
2556 if (!ParseSourceOptions(cx, opts, &displayURL, &sourceMapURL)) {
2557 return false;
2560 RootedValue v(cx);
2561 if (!JS_GetProperty(cx, opts, "catchTermination", &v)) {
2562 return false;
2564 if (!v.isUndefined()) {
2565 catchTermination = ToBoolean(v);
2568 if (!JS_GetProperty(cx, opts, "loadBytecode", &v)) {
2569 return false;
2571 if (!v.isUndefined()) {
2572 loadBytecode = ToBoolean(v);
2575 if (!JS_GetProperty(cx, opts, "saveIncrementalBytecode", &v)) {
2576 return false;
2578 if (!v.isUndefined()) {
2579 saveIncrementalBytecode = ToBoolean(v);
2582 if (!JS_GetProperty(cx, opts, "execute", &v)) {
2583 return false;
2585 if (!v.isUndefined()) {
2586 execute = ToBoolean(v);
2589 if (!JS_GetProperty(cx, opts, "assertEqBytecode", &v)) {
2590 return false;
2592 if (!v.isUndefined()) {
2593 assertEqBytecode = ToBoolean(v);
2596 if (!JS_GetProperty(cx, opts, "envChainObject", &v)) {
2597 return false;
2599 if (!v.isUndefined()) {
2600 if (!v.isObject()) {
2601 JS_ReportErrorNumberASCII(
2602 cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
2603 "\"envChainObject\" passed to evaluate()", "not an object");
2604 return false;
2607 JSObject* obj = &v.toObject();
2608 if (obj->isUnqualifiedVarObj()) {
2609 JS_ReportErrorASCII(
2611 "\"envChainObject\" passed to evaluate() should not be an "
2612 "unqualified variables object");
2613 return false;
2616 if (!envChain.append(obj)) {
2617 return false;
2621 // We cannot load or save the bytecode if we have no object where the
2622 // bytecode cache is stored.
2623 if (loadBytecode || saveIncrementalBytecode) {
2624 if (!cacheEntry) {
2625 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
2626 JSSMSG_INVALID_ARGS, "evaluate");
2627 return false;
2632 if (envChain.length() != 0) {
2633 // Wrap the envChainObject list into target realm.
2634 JSAutoRealm ar(cx, global);
2635 for (size_t i = 0; i < envChain.length(); ++i) {
2636 if (!JS_WrapObject(cx, envChain[i])) {
2637 return false;
2641 options.setNonSyntacticScope(true);
2644 // The `loadBuffer` we use below outlives the Stencil we generate so we can
2645 // use its contents directly in the Stencil.
2646 options.borrowBuffer = true;
2648 // We need to track the options used to generate bytecode for a CacheEntry to
2649 // avoid mismatches. This is primarily a concern when fuzzing the jsshell.
2650 CacheOptionSet cacheOptions;
2651 cacheOptions.initFromOptions(options);
2653 JS::TranscodeBuffer loadBuffer;
2654 JS::TranscodeBuffer saveBuffer;
2656 if (loadBytecode) {
2657 size_t loadLength = 0;
2658 uint8_t* loadData = nullptr;
2660 if (!CacheEntry_compatible(cx, cacheEntry, cacheOptions)) {
2661 return false;
2664 loadData = CacheEntry_getBytecode(cx, cacheEntry, &loadLength);
2665 if (!loadData) {
2666 return false;
2668 if (!loadBuffer.append(loadData, loadLength)) {
2669 JS_ReportOutOfMemory(cx);
2670 return false;
2675 JSAutoRealm ar(cx, global);
2676 RefPtr<JS::Stencil> stencil;
2678 if (loadBytecode) {
2679 JS::TranscodeRange range(loadBuffer.begin(), loadBuffer.length());
2680 JS::DecodeOptions decodeOptions(options);
2682 JS::TranscodeResult rv =
2683 JS::DecodeStencil(cx, decodeOptions, range, getter_AddRefs(stencil));
2684 if (JS::IsTranscodeFailureResult(rv)) {
2685 JS_ReportErrorASCII(cx, "failed to decode cache");
2686 return false;
2689 if (!ConvertTranscodeResultToJSException(cx, rv)) {
2690 return false;
2692 } else {
2693 AutoStableStringChars linearChars(cx);
2694 if (!linearChars.initTwoByte(cx, code)) {
2695 return false;
2698 JS::SourceText<char16_t> srcBuf;
2699 if (!srcBuf.initMaybeBorrowed(cx, linearChars)) {
2700 return false;
2703 stencil = JS::CompileGlobalScriptToStencil(cx, options, srcBuf);
2704 if (!stencil) {
2705 return false;
2709 if (!js::ValidateLazinessOfStencilAndGlobal(cx, *stencil)) {
2710 return false;
2713 JS::InstantiateOptions instantiateOptions(options);
2714 RootedScript script(
2715 cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil));
2716 if (!script) {
2717 return false;
2720 AutoReportFrontendContext fc(cx);
2721 if (!SetSourceOptions(cx, &fc, script->scriptSource(), displayURL,
2722 sourceMapURL)) {
2723 return false;
2726 if (!JS::UpdateDebugMetadata(cx, script, instantiateOptions, privateValue,
2727 elementAttributeName, nullptr, nullptr)) {
2728 return false;
2731 if (saveIncrementalBytecode) {
2732 if (!JS::StartIncrementalEncoding(cx, std::move(stencil))) {
2733 return false;
2737 if (execute) {
2738 if (!(envChain.empty()
2739 ? JS_ExecuteScript(cx, script, args.rval())
2740 : JS_ExecuteScript(cx, envChain, script, args.rval()))) {
2741 if (catchTermination && !JS_IsExceptionPending(cx)) {
2742 JSAutoRealm ar1(cx, callerGlobal);
2743 JSString* str = JS_NewStringCopyZ(cx, "terminated");
2744 if (!str) {
2745 return false;
2747 args.rval().setString(str);
2748 return true;
2750 return false;
2754 // Serialize the encoded bytecode, recorded before the execution, into a
2755 // buffer which can be deserialized linearly.
2756 if (saveIncrementalBytecode) {
2757 if (!FinishIncrementalEncoding(cx, script, saveBuffer)) {
2758 return false;
2763 if (saveIncrementalBytecode) {
2764 // If we are both loading and saving, we assert that we are going to
2765 // replace the current bytecode by the same stream of bytes.
2766 if (loadBytecode && assertEqBytecode) {
2767 if (saveBuffer.length() != loadBuffer.length()) {
2768 char loadLengthStr[16];
2769 SprintfLiteral(loadLengthStr, "%zu", loadBuffer.length());
2770 char saveLengthStr[16];
2771 SprintfLiteral(saveLengthStr, "%zu", saveBuffer.length());
2773 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
2774 JSSMSG_CACHE_EQ_SIZE_FAILED, loadLengthStr,
2775 saveLengthStr);
2776 return false;
2779 if (!ArrayEqual(loadBuffer.begin(), saveBuffer.begin(),
2780 loadBuffer.length())) {
2781 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
2782 JSSMSG_CACHE_EQ_CONTENT_FAILED);
2783 return false;
2787 size_t saveLength = saveBuffer.length();
2788 if (saveLength >= INT32_MAX) {
2789 JS_ReportErrorASCII(cx, "Cannot save large cache entry content");
2790 return false;
2792 uint8_t* saveData = saveBuffer.extractOrCopyRawBuffer();
2793 if (!CacheEntry_setBytecode(cx, cacheEntry, cacheOptions, saveData,
2794 saveLength)) {
2795 js_free(saveData);
2796 return false;
2800 return JS_WrapValue(cx, args.rval());
2803 JSString* js::shell::FileAsString(JSContext* cx, JS::HandleString pathnameStr) {
2804 UniqueChars pathname = JS_EncodeStringToUTF8(cx, pathnameStr);
2805 if (!pathname) {
2806 return nullptr;
2809 FILE* file = OpenFile(cx, pathname.get(), "rb");
2810 if (!file) {
2811 return nullptr;
2814 AutoCloseFile autoClose(file);
2816 struct stat st;
2817 if (fstat(fileno(file), &st) != 0) {
2818 JS_ReportErrorUTF8(cx, "can't stat %s", pathname.get());
2819 return nullptr;
2822 if ((st.st_mode & S_IFMT) != S_IFREG) {
2823 JS_ReportErrorUTF8(cx, "can't read non-regular file %s", pathname.get());
2824 return nullptr;
2827 size_t len;
2828 if (!FileSize(cx, pathname.get(), file, &len)) {
2829 return nullptr;
2832 UniqueChars buf(js_pod_malloc<char>(len + 1));
2833 if (!buf) {
2834 JS_ReportErrorUTF8(cx, "out of memory reading %s", pathname.get());
2835 return nullptr;
2838 if (!ReadFile(cx, pathname.get(), file, buf.get(), len)) {
2839 return nullptr;
2842 UniqueTwoByteChars ucbuf(
2843 JS::LossyUTF8CharsToNewTwoByteCharsZ(cx, JS::UTF8Chars(buf.get(), len),
2844 &len, js::MallocArena)
2845 .get());
2846 if (!ucbuf) {
2847 JS_ReportErrorUTF8(cx, "Invalid UTF-8 in file '%s'", pathname.get());
2848 return nullptr;
2851 return JS_NewUCStringCopyN(cx, ucbuf.get(), len);
2855 * Function to run scripts and return compilation + execution time. Semantics
2856 * are closely modelled after the equivalent function in WebKit, as this is used
2857 * to produce benchmark timings by SunSpider.
2859 static bool Run(JSContext* cx, unsigned argc, Value* vp) {
2860 CallArgs args = CallArgsFromVp(argc, vp);
2861 if (args.length() != 1) {
2862 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
2863 JSSMSG_INVALID_ARGS, "run");
2864 return false;
2867 RootedString str(cx, JS::ToString(cx, args[0]));
2868 if (!str) {
2869 return false;
2871 args[0].setString(str);
2873 str = FileAsString(cx, str);
2874 if (!str) {
2875 return false;
2878 AutoStableStringChars linearChars(cx);
2879 if (!linearChars.initTwoByte(cx, str)) {
2880 return false;
2883 JS::SourceText<char16_t> srcBuf;
2884 if (!srcBuf.initMaybeBorrowed(cx, linearChars)) {
2885 return false;
2888 RootedScript script(cx);
2889 int64_t startClock = PRMJ_Now();
2891 UniqueChars filename = JS_EncodeStringToUTF8(cx, str);
2892 if (!filename) {
2893 return false;
2896 JS::CompileOptions options(cx);
2897 options.setIntroductionType("js shell run")
2898 .setFileAndLine(filename.get(), 1)
2899 .setIsRunOnce(true)
2900 .setNoScriptRval(true)
2901 .setEagerDelazificationStrategy(defaultDelazificationMode);
2903 script = JS::Compile(cx, options, srcBuf);
2904 if (!script) {
2905 return false;
2909 if (!JS_ExecuteScript(cx, script)) {
2910 return false;
2913 int64_t endClock = PRMJ_Now();
2915 args.rval().setDouble((endClock - startClock) / double(PRMJ_USEC_PER_MSEC));
2916 return true;
2919 static int js_fgets(char* buf, int size, FILE* file) {
2920 int n, i, c;
2921 bool crflag;
2923 n = size - 1;
2924 if (n < 0) {
2925 return -1;
2928 // Use the fastest available getc.
2929 auto fast_getc =
2930 #if defined(HAVE_GETC_UNLOCKED)
2931 getc_unlocked
2932 #elif defined(HAVE__GETC_NOLOCK)
2933 _getc_nolock
2934 #else
2935 getc
2936 #endif
2939 crflag = false;
2940 for (i = 0; i < n && (c = fast_getc(file)) != EOF; i++) {
2941 buf[i] = c;
2942 if (c == '\n') { // any \n ends a line
2943 i++; // keep the \n; we know there is room for \0
2944 break;
2946 if (crflag) { // \r not followed by \n ends line at the \r
2947 ungetc(c, file);
2948 break; // and overwrite c in buf with \0
2950 crflag = (c == '\r');
2953 buf[i] = '\0';
2954 return i;
2958 * function readline()
2959 * Provides a hook for scripts to read a line from stdin.
2961 static bool ReadLine(JSContext* cx, unsigned argc, Value* vp) {
2962 CallArgs args = CallArgsFromVp(argc, vp);
2964 static constexpr size_t BUFSIZE = 256;
2965 FILE* from = stdin;
2966 size_t buflength = 0;
2967 size_t bufsize = BUFSIZE;
2968 char* buf = (char*)JS_malloc(cx, bufsize);
2969 if (!buf) {
2970 return false;
2973 bool sawNewline = false;
2974 size_t gotlength;
2975 while ((gotlength = js_fgets(buf + buflength, bufsize - buflength, from)) >
2976 0) {
2977 buflength += gotlength;
2979 /* Are we done? */
2980 if (buf[buflength - 1] == '\n') {
2981 buf[buflength - 1] = '\0';
2982 sawNewline = true;
2983 break;
2984 } else if (buflength < bufsize - 1) {
2985 break;
2988 /* Else, grow our buffer for another pass. */
2989 char* tmp;
2990 bufsize *= 2;
2991 if (bufsize > buflength) {
2992 tmp = static_cast<char*>(JS_realloc(cx, buf, bufsize / 2, bufsize));
2993 } else {
2994 JS_ReportOutOfMemory(cx);
2995 tmp = nullptr;
2998 if (!tmp) {
2999 JS_free(cx, buf);
3000 return false;
3003 buf = tmp;
3006 /* Treat the empty string specially. */
3007 if (buflength == 0) {
3008 args.rval().set(feof(from) ? NullValue() : JS_GetEmptyStringValue(cx));
3009 JS_free(cx, buf);
3010 return true;
3013 /* Shrink the buffer to the real size. */
3014 char* tmp = static_cast<char*>(JS_realloc(cx, buf, bufsize, buflength));
3015 if (!tmp) {
3016 JS_free(cx, buf);
3017 return false;
3020 buf = tmp;
3023 * Turn buf into a JSString. Note that buflength includes the trailing null
3024 * character.
3026 JSString* str =
3027 JS_NewStringCopyN(cx, buf, sawNewline ? buflength - 1 : buflength);
3028 JS_free(cx, buf);
3029 if (!str) {
3030 return false;
3033 args.rval().setString(str);
3034 return true;
3038 * function readlineBuf()
3039 * Provides a hook for scripts to emulate readline() using a string object.
3041 static bool ReadLineBuf(JSContext* cx, unsigned argc, Value* vp) {
3042 CallArgs args = CallArgsFromVp(argc, vp);
3043 ShellContext* sc = GetShellContext(cx);
3045 if (!args.length()) {
3046 if (!sc->readLineBuf) {
3047 JS_ReportErrorASCII(cx,
3048 "No source buffer set. You must initially "
3049 "call readlineBuf with an argument.");
3050 return false;
3053 char* currentBuf = sc->readLineBuf.get() + sc->readLineBufPos;
3054 size_t buflen = strlen(currentBuf);
3056 if (!buflen) {
3057 args.rval().setNull();
3058 return true;
3061 size_t len = 0;
3062 while (len < buflen) {
3063 if (currentBuf[len] == '\n') {
3064 break;
3066 len++;
3069 JSString* str = JS_NewStringCopyUTF8N(cx, JS::UTF8Chars(currentBuf, len));
3070 if (!str) {
3071 return false;
3074 if (currentBuf[len] == '\0') {
3075 sc->readLineBufPos += len;
3076 } else {
3077 sc->readLineBufPos += len + 1;
3080 args.rval().setString(str);
3081 return true;
3084 if (args.length() == 1) {
3085 sc->readLineBuf = nullptr;
3086 sc->readLineBufPos = 0;
3088 RootedString str(cx, JS::ToString(cx, args[0]));
3089 if (!str) {
3090 return false;
3092 sc->readLineBuf = JS_EncodeStringToUTF8(cx, str);
3093 if (!sc->readLineBuf) {
3094 return false;
3097 args.rval().setUndefined();
3098 return true;
3101 JS_ReportErrorASCII(cx, "Must specify at most one argument");
3102 return false;
3105 static bool PutStr(JSContext* cx, unsigned argc, Value* vp) {
3106 CallArgs args = CallArgsFromVp(argc, vp);
3108 if (args.length() != 0) {
3109 if (!gOutFile->isOpen()) {
3110 JS_ReportErrorASCII(cx, "output file is closed");
3111 return false;
3114 RootedString str(cx, JS::ToString(cx, args[0]));
3115 if (!str) {
3116 return false;
3118 UniqueChars bytes = JS_EncodeStringToUTF8(cx, str);
3119 if (!bytes) {
3120 return false;
3122 fputs(bytes.get(), gOutFile->fp);
3123 fflush(gOutFile->fp);
3126 args.rval().setUndefined();
3127 return true;
3130 static bool Now(JSContext* cx, unsigned argc, Value* vp) {
3131 CallArgs args = CallArgsFromVp(argc, vp);
3132 double now = PRMJ_Now() / double(PRMJ_USEC_PER_MSEC);
3133 args.rval().setDouble(now);
3134 return true;
3137 static bool CpuNow(JSContext* cx, unsigned argc, Value* vp) {
3138 CallArgs args = CallArgsFromVp(argc, vp);
3139 double now = double(std::clock()) / double(CLOCKS_PER_SEC);
3140 args.rval().setDouble(now);
3141 return true;
3144 static bool PrintInternal(JSContext* cx, const CallArgs& args, RCFile* file) {
3145 if (!file->isOpen()) {
3146 JS_ReportErrorASCII(cx, "output file is closed");
3147 return false;
3150 for (unsigned i = 0; i < args.length(); i++) {
3151 RootedString str(cx, JS::ToString(cx, args[i]));
3152 if (!str) {
3153 return false;
3155 UniqueChars bytes = JS_EncodeStringToUTF8(cx, str);
3156 if (!bytes) {
3157 return false;
3159 fprintf(file->fp, "%s%s", i ? " " : "", bytes.get());
3162 fputc('\n', file->fp);
3163 fflush(file->fp);
3165 args.rval().setUndefined();
3166 return true;
3169 static bool Print(JSContext* cx, unsigned argc, Value* vp) {
3170 CallArgs args = CallArgsFromVp(argc, vp);
3171 #ifdef FUZZING_INTERFACES
3172 if (fuzzHaveModule && !fuzzDoDebug) {
3173 // When fuzzing and not debugging, suppress any print() output,
3174 // as it slows down fuzzing and makes libFuzzer's output hard
3175 // to read.
3176 args.rval().setUndefined();
3177 return true;
3179 #endif // FUZZING_INTERFACES
3180 return PrintInternal(cx, args, gOutFile);
3183 static bool PrintErr(JSContext* cx, unsigned argc, Value* vp) {
3184 CallArgs args = CallArgsFromVp(argc, vp);
3185 return PrintInternal(cx, args, gErrFile);
3188 static bool Help(JSContext* cx, unsigned argc, Value* vp);
3190 static bool Quit(JSContext* cx, unsigned argc, Value* vp) {
3191 ShellContext* sc = GetShellContext(cx);
3193 // Print a message to stderr in differential testing to help jsfunfuzz
3194 // find uncatchable-exception bugs.
3195 if (js::SupportDifferentialTesting()) {
3196 fprintf(stderr, "quit called\n");
3199 CallArgs args = CallArgsFromVp(argc, vp);
3200 int32_t code;
3201 if (!ToInt32(cx, args.get(0), &code)) {
3202 return false;
3205 // The fuzzers check the shell's exit code and assume a value >= 128 means
3206 // the process crashed (for instance, SIGSEGV will result in code 139). On
3207 // POSIX platforms, the exit code is 8-bit and negative values can also
3208 // result in an exit code >= 128. We restrict the value to range [0, 127] to
3209 // avoid false positives.
3210 if (code < 0 || code >= 128) {
3211 JS_ReportErrorASCII(cx, "quit exit code should be in range 0-127");
3212 return false;
3215 js::StopDrainingJobQueue(cx);
3216 sc->exitCode = code;
3217 sc->quitting = true;
3218 return false;
3221 static bool StartTimingMutator(JSContext* cx, unsigned argc, Value* vp) {
3222 CallArgs args = CallArgsFromVp(argc, vp);
3223 if (args.length() > 0) {
3224 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
3225 JSSMSG_TOO_MANY_ARGS, "startTimingMutator");
3226 return false;
3229 if (!cx->runtime()->gc.stats().startTimingMutator()) {
3230 JS_ReportErrorASCII(
3231 cx, "StartTimingMutator should only be called from outside of GC");
3232 return false;
3235 args.rval().setUndefined();
3236 return true;
3239 static bool StopTimingMutator(JSContext* cx, unsigned argc, Value* vp) {
3240 CallArgs args = CallArgsFromVp(argc, vp);
3241 if (args.length() > 0) {
3242 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
3243 JSSMSG_TOO_MANY_ARGS, "stopTimingMutator");
3244 return false;
3247 double mutator_ms, gc_ms;
3248 if (!cx->runtime()->gc.stats().stopTimingMutator(mutator_ms, gc_ms)) {
3249 JS_ReportErrorASCII(cx,
3250 "stopTimingMutator called when not timing the mutator");
3251 return false;
3253 double total_ms = mutator_ms + gc_ms;
3254 if (total_ms > 0 && gOutFile->isOpen()) {
3255 fprintf(gOutFile->fp, "Mutator: %.3fms (%.1f%%), GC: %.3fms (%.1f%%)\n",
3256 mutator_ms, mutator_ms / total_ms * 100.0, gc_ms,
3257 gc_ms / total_ms * 100.0);
3260 args.rval().setUndefined();
3261 return true;
3264 static const char* ToSource(JSContext* cx, HandleValue vp, UniqueChars* bytes) {
3265 RootedString str(cx, JS_ValueToSource(cx, vp));
3266 if (str) {
3267 *bytes = JS_EncodeStringToUTF8(cx, str);
3268 if (*bytes) {
3269 return bytes->get();
3272 JS_ClearPendingException(cx);
3273 return "<<error converting value to string>>";
3276 static bool AssertEq(JSContext* cx, unsigned argc, Value* vp) {
3277 CallArgs args = CallArgsFromVp(argc, vp);
3278 if (!(args.length() == 2 || (args.length() == 3 && args[2].isString()))) {
3279 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
3280 (args.length() < 2) ? JSSMSG_NOT_ENOUGH_ARGS
3281 : (args.length() == 3) ? JSSMSG_INVALID_ARGS
3282 : JSSMSG_TOO_MANY_ARGS,
3283 "assertEq");
3284 return false;
3287 bool same;
3288 if (!JS::SameValue(cx, args[0], args[1], &same)) {
3289 return false;
3291 if (!same) {
3292 UniqueChars bytes0, bytes1;
3293 const char* actual = ToSource(cx, args[0], &bytes0);
3294 const char* expected = ToSource(cx, args[1], &bytes1);
3295 if (args.length() == 2) {
3296 JS_ReportErrorNumberUTF8(cx, my_GetErrorMessage, nullptr,
3297 JSSMSG_ASSERT_EQ_FAILED, actual, expected);
3298 } else {
3299 RootedString message(cx, args[2].toString());
3300 UniqueChars bytes2 = QuoteString(cx, message);
3301 if (!bytes2) {
3302 return false;
3304 JS_ReportErrorNumberUTF8(cx, my_GetErrorMessage, nullptr,
3305 JSSMSG_ASSERT_EQ_FAILED_MSG, actual, expected,
3306 bytes2.get());
3308 return false;
3310 args.rval().setUndefined();
3311 return true;
3314 static JSScript* GetTopScript(JSContext* cx) {
3315 NonBuiltinScriptFrameIter iter(cx);
3316 return iter.done() ? nullptr : iter.script();
3319 static bool GetScriptAndPCArgs(JSContext* cx, CallArgs& args,
3320 MutableHandleScript scriptp, int32_t* ip) {
3321 RootedScript script(cx, GetTopScript(cx));
3322 *ip = 0;
3323 if (!args.get(0).isUndefined()) {
3324 HandleValue v = args[0];
3325 unsigned intarg = 0;
3326 if (v.isObject() && JS::GetClass(&v.toObject())->isJSFunction()) {
3327 script = TestingFunctionArgumentToScript(cx, v);
3328 if (!script) {
3329 return false;
3331 intarg++;
3333 if (!args.get(intarg).isUndefined()) {
3334 if (!JS::ToInt32(cx, args[intarg], ip)) {
3335 return false;
3337 if ((uint32_t)*ip >= script->length()) {
3338 JS_ReportErrorASCII(cx, "Invalid PC");
3339 return false;
3344 scriptp.set(script);
3346 return true;
3349 static bool LineToPC(JSContext* cx, unsigned argc, Value* vp) {
3350 CallArgs args = CallArgsFromVp(argc, vp);
3352 if (args.length() == 0) {
3353 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
3354 JSSMSG_LINE2PC_USAGE);
3355 return false;
3358 RootedScript script(cx, GetTopScript(cx));
3359 int32_t lineArg = 0;
3360 if (args[0].isObject() && args[0].toObject().is<JSFunction>()) {
3361 script = TestingFunctionArgumentToScript(cx, args[0]);
3362 if (!script) {
3363 return false;
3365 lineArg++;
3368 uint32_t lineno;
3369 if (!ToUint32(cx, args.get(lineArg), &lineno)) {
3370 return false;
3373 jsbytecode* pc = LineNumberToPC(script, lineno);
3374 if (!pc) {
3375 return false;
3377 args.rval().setInt32(script->pcToOffset(pc));
3378 return true;
3381 static bool PCToLine(JSContext* cx, unsigned argc, Value* vp) {
3382 CallArgs args = CallArgsFromVp(argc, vp);
3383 RootedScript script(cx);
3384 int32_t i;
3385 unsigned lineno;
3387 if (!GetScriptAndPCArgs(cx, args, &script, &i)) {
3388 return false;
3390 lineno = PCToLineNumber(script, script->offsetToPC(i));
3391 if (!lineno) {
3392 return false;
3394 args.rval().setInt32(lineno);
3395 return true;
3398 #if defined(DEBUG) || defined(JS_JITSPEW)
3400 static bool Notes(JSContext* cx, unsigned argc, Value* vp) {
3401 CallArgs args = CallArgsFromVp(argc, vp);
3402 JSSprinter sprinter(cx);
3403 if (!sprinter.init()) {
3404 return false;
3407 for (unsigned i = 0; i < args.length(); i++) {
3408 RootedScript script(cx, TestingFunctionArgumentToScript(cx, args[i]));
3409 if (!script) {
3410 return false;
3413 if (!JSScript::dumpSrcNotes(cx, script, &sprinter)) {
3414 return false;
3418 JSString* str = sprinter.release(cx);
3419 if (!str) {
3420 return false;
3422 args.rval().setString(str);
3423 return true;
3426 namespace {
3428 struct DisassembleOptionParser {
3429 unsigned argc;
3430 Value* argv;
3431 JSScript::DumpOptions options;
3433 DisassembleOptionParser(unsigned argc, Value* argv)
3434 : argc(argc), argv(argv) {}
3436 bool parse(JSContext* cx) {
3437 options.recursive = false;
3439 /* Read options off early arguments */
3440 while (argc > 0 && argv[0].isString()) {
3441 JSString* str = argv[0].toString();
3442 JSLinearString* linearStr = JS_EnsureLinearString(cx, str);
3443 if (!linearStr) {
3444 return false;
3446 if (JS_LinearStringEqualsLiteral(linearStr, "-r")) {
3447 options.recursive = true;
3448 } else {
3449 break;
3451 argv++;
3452 argc--;
3454 return true;
3458 } /* anonymous namespace */
3460 static bool DisassembleToSprinter(JSContext* cx, unsigned argc, Value* vp,
3461 StringPrinter* sp) {
3462 CallArgs args = CallArgsFromVp(argc, vp);
3463 DisassembleOptionParser p(args.length(), args.array());
3464 if (!p.parse(cx)) {
3465 return false;
3468 if (p.argc == 0) {
3469 /* Without arguments, disassemble the current script. */
3470 RootedScript script(cx, GetTopScript(cx));
3471 if (script) {
3472 JSAutoRealm ar(cx, script);
3473 if (!JSScript::dump(cx, script, p.options, sp)) {
3474 return false;
3477 } else {
3478 for (unsigned i = 0; i < p.argc; i++) {
3479 RootedFunction fun(cx);
3480 RootedScript script(cx);
3481 RootedValue value(cx, p.argv[i]);
3482 if (value.isObject() && value.toObject().is<ShellModuleObjectWrapper>()) {
3483 script = value.toObject()
3484 .as<ShellModuleObjectWrapper>()
3485 .get()
3486 ->maybeScript();
3487 } else {
3488 script = TestingFunctionArgumentToScript(cx, value, fun.address());
3490 if (!script) {
3491 return false;
3494 if (!JSScript::dump(cx, script, p.options, sp)) {
3495 return false;
3500 return true;
3503 static bool DisassembleToString(JSContext* cx, unsigned argc, Value* vp) {
3504 CallArgs args = CallArgsFromVp(argc, vp);
3505 JSSprinter sprinter(cx);
3506 if (!sprinter.init()) {
3507 return false;
3509 if (!DisassembleToSprinter(cx, args.length(), vp, &sprinter)) {
3510 return false;
3513 JSString* str = sprinter.release(cx);
3514 if (!str) {
3515 return false;
3517 args.rval().setString(str);
3518 return true;
3521 static bool Disassemble(JSContext* cx, unsigned argc, Value* vp) {
3522 CallArgs args = CallArgsFromVp(argc, vp);
3524 if (!gOutFile->isOpen()) {
3525 JS_ReportErrorASCII(cx, "output file is closed");
3526 return false;
3529 Sprinter sprinter(cx);
3530 if (!sprinter.init()) {
3531 return false;
3533 if (!DisassembleToSprinter(cx, args.length(), vp, &sprinter)) {
3534 return false;
3537 JS::UniqueChars str = sprinter.release();
3538 if (!str) {
3539 return false;
3541 fprintf(gOutFile->fp, "%s\n", str.get());
3542 args.rval().setUndefined();
3543 return true;
3546 static bool DisassFile(JSContext* cx, unsigned argc, Value* vp) {
3547 CallArgs args = CallArgsFromVp(argc, vp);
3549 if (!gOutFile->isOpen()) {
3550 JS_ReportErrorASCII(cx, "output file is closed");
3551 return false;
3554 /* Support extra options at the start, just like Disassemble. */
3555 DisassembleOptionParser p(args.length(), args.array());
3556 if (!p.parse(cx)) {
3557 return false;
3560 if (!p.argc) {
3561 args.rval().setUndefined();
3562 return true;
3565 // We should change DisassembleOptionParser to store CallArgs.
3566 Rooted<JSString*> str(
3567 cx, JS::ToString(cx, HandleValue::fromMarkedLocation(&p.argv[0])));
3568 if (!str) {
3569 return false;
3571 UniqueChars filename = JS_EncodeStringToUTF8(cx, str);
3572 if (!filename) {
3573 return false;
3575 RootedScript script(cx);
3578 CompileOptions options(cx);
3579 options.setIntroductionType("js shell disFile")
3580 .setFileAndLine(filename.get(), 1)
3581 .setIsRunOnce(true)
3582 .setNoScriptRval(true)
3583 .setEagerDelazificationStrategy(defaultDelazificationMode);
3585 script = JS::CompileUtf8Path(cx, options, filename.get());
3586 if (!script) {
3587 return false;
3591 Sprinter sprinter(cx);
3592 if (!sprinter.init()) {
3593 return false;
3595 if (JSScript::dump(cx, script, p.options, &sprinter)) {
3596 return false;
3599 JS::UniqueChars chars = sprinter.release();
3600 if (!chars) {
3601 return false;
3603 fprintf(gOutFile->fp, "%s\n", chars.get());
3605 args.rval().setUndefined();
3606 return true;
3609 static bool DisassWithSrc(JSContext* cx, unsigned argc, Value* vp) {
3610 CallArgs args = CallArgsFromVp(argc, vp);
3612 if (!gOutFile->isOpen()) {
3613 JS_ReportErrorASCII(cx, "output file is closed");
3614 return false;
3617 const size_t lineBufLen = 512;
3618 unsigned len, line1, line2, bupline;
3619 char linebuf[lineBufLen];
3620 static const char sep[] = ";-------------------------";
3622 RootedScript script(cx);
3623 for (unsigned i = 0; i < args.length(); i++) {
3624 script = TestingFunctionArgumentToScript(cx, args[i]);
3625 if (!script) {
3626 return false;
3629 if (!script->filename()) {
3630 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
3631 JSSMSG_FILE_SCRIPTS_ONLY);
3632 return false;
3635 FILE* file = OpenFile(cx, script->filename(), "rb");
3636 if (!file) {
3637 return false;
3639 auto closeFile = MakeScopeExit([file] { fclose(file); });
3641 jsbytecode* pc = script->code();
3642 jsbytecode* end = script->codeEnd();
3644 Sprinter sprinter(cx);
3645 if (!sprinter.init()) {
3646 return false;
3649 /* burn the leading lines */
3650 line2 = PCToLineNumber(script, pc);
3651 for (line1 = 0; line1 < line2 - 1; line1++) {
3652 char* tmp = fgets(linebuf, lineBufLen, file);
3653 if (!tmp) {
3654 JS_ReportErrorUTF8(cx, "failed to read %s fully", script->filename());
3655 return false;
3659 bupline = 0;
3660 while (pc < end) {
3661 line2 = PCToLineNumber(script, pc);
3663 if (line2 < line1) {
3664 if (bupline != line2) {
3665 bupline = line2;
3666 sprinter.printf("%s %3u: BACKUP\n", sep, line2);
3668 } else {
3669 if (bupline && line1 == line2) {
3670 sprinter.printf("%s %3u: RESTORE\n", sep, line2);
3672 bupline = 0;
3673 while (line1 < line2) {
3674 if (!fgets(linebuf, lineBufLen, file)) {
3675 JS_ReportErrorNumberUTF8(cx, my_GetErrorMessage, nullptr,
3676 JSSMSG_UNEXPECTED_EOF, script->filename());
3677 return false;
3679 line1++;
3680 sprinter.printf("%s %3u: %s", sep, line1, linebuf);
3684 len =
3685 Disassemble1(cx, script, pc, script->pcToOffset(pc), true, &sprinter);
3686 if (!len) {
3687 return false;
3690 pc += len;
3693 JS::UniqueChars str = sprinter.release();
3694 if (!str) {
3695 return false;
3697 fprintf(gOutFile->fp, "%s\n", str.get());
3700 args.rval().setUndefined();
3701 return true;
3704 #endif /* defined(DEBUG) || defined(JS_JITSPEW) */
3706 #ifdef JS_CACHEIR_SPEW
3707 static bool CacheIRHealthReport(JSContext* cx, unsigned argc, Value* vp) {
3708 CallArgs args = CallArgsFromVp(argc, vp);
3710 js::jit::CacheIRHealth cih;
3711 RootedScript script(cx);
3713 // In the case that we are calling this function from the shell and
3714 // the environment variable is not set, AutoSpewChannel automatically
3715 // sets and unsets the proper channel for the duration of spewing
3716 // a health report.
3717 AutoSpewChannel channel(cx, SpewChannel::CacheIRHealthReport, script);
3718 if (!argc) {
3719 // Calling CacheIRHealthReport without any arguments will create health
3720 // reports for all scripts in the zone.
3721 if (jit::JitZone* jitZone = cx->zone()->jitZone()) {
3722 jitZone->forEachJitScript([&](jit::JitScript* jitScript) {
3723 script = jitScript->owningScript();
3724 if (!script->selfHosted()) {
3725 cih.healthReportForScript(cx, script, js::jit::SpewContext::Shell);
3729 } else {
3730 RootedValue value(cx, args.get(0));
3732 if (value.isObject() && value.toObject().is<ShellModuleObjectWrapper>()) {
3733 script =
3734 value.toObject().as<ShellModuleObjectWrapper>().get()->maybeScript();
3735 } else {
3736 script = TestingFunctionArgumentToScript(cx, args.get(0));
3739 if (!script) {
3740 return false;
3743 cih.healthReportForScript(cx, script, js::jit::SpewContext::Shell);
3746 args.rval().setUndefined();
3747 return true;
3749 #endif /* JS_CACHEIR_SPEW */
3751 /* Pretend we can always preserve wrappers for dummy DOM objects. */
3752 static bool DummyPreserveWrapperCallback(JSContext* cx, HandleObject obj) {
3753 return true;
3756 static bool DummyHasReleasedWrapperCallback(HandleObject obj) { return true; }
3758 #ifdef FUZZING_JS_FUZZILLI
3759 static bool fuzzilli_hash(JSContext* cx, unsigned argc, Value* vp) {
3760 CallArgs args = CallArgsFromVp(argc, vp);
3761 args.rval().setUndefined();
3763 if (argc != 1) {
3764 return true;
3766 uint32_t hash;
3767 JS::Handle<JS::Value> v = args.get(0);
3768 if (v.isInt32()) {
3769 int32_t i = v.toInt32();
3770 hash = FuzzilliHashDouble((double)i);
3771 } else if (v.isDouble()) {
3772 double d = v.toDouble();
3773 d = JS::CanonicalizeNaN(d);
3774 hash = FuzzilliHashDouble(d);
3775 } else if (v.isNull()) {
3776 hash = FuzzilliHashDouble(1.0);
3777 } else if (v.isUndefined()) {
3778 hash = FuzzilliHashDouble(2.0);
3779 } else if (v.isBoolean()) {
3780 hash = FuzzilliHashDouble(3.0 + v.toBoolean());
3781 } else if (v.isBigInt()) {
3782 JS::BigInt* bigInt = v.toBigInt();
3783 hash = FuzzilliHashBigInt(bigInt);
3784 } else if (v.isObject()) {
3785 JSObject& obj = v.toObject();
3786 FuzzilliHashObject(cx, &obj);
3787 return true;
3788 } else {
3789 hash = 0;
3792 cx->executionHashInputs += 1;
3793 cx->executionHash = mozilla::RotateLeft(cx->executionHash + hash, 1);
3794 return true;
3797 // We have to assume that the fuzzer will be able to call this function e.g. by
3798 // enumerating the properties of the global object and eval'ing them. As such
3799 // this function is implemented in a way that requires passing some magic value
3800 // as first argument (with the idea being that the fuzzer won't be able to
3801 // generate this value) which then also acts as a selector for the operation
3802 // to perform.
3803 static bool Fuzzilli(JSContext* cx, unsigned argc, Value* vp) {
3804 CallArgs args = CallArgsFromVp(argc, vp);
3806 RootedString arg(cx, JS::ToString(cx, args.get(0)));
3807 if (!arg) {
3808 return false;
3810 Rooted<JSLinearString*> operation(cx, StringToLinearString(cx, arg));
3811 if (!operation) {
3812 return false;
3815 if (StringEqualsAscii(operation, "FUZZILLI_CRASH")) {
3816 int type;
3817 if (!ToInt32(cx, args.get(1), &type)) {
3818 return false;
3821 // With this, we can test the various ways the JS shell can crash and make
3822 // sure that Fuzzilli is able to detect all of these failures properly.
3823 switch (type) {
3824 case 0:
3825 *((int*)0x41414141) = 0x1337;
3826 break;
3827 case 1:
3828 MOZ_RELEASE_ASSERT(false);
3829 break;
3830 case 2:
3831 MOZ_ASSERT(false);
3832 break;
3833 case 3:
3834 __asm__("int3");
3835 break;
3836 default:
3837 exit(1);
3839 } else if (StringEqualsAscii(operation, "FUZZILLI_PRINT")) {
3840 static FILE* fzliout = fdopen(REPRL_DWFD, "w");
3841 if (!fzliout) {
3842 fprintf(
3843 stderr,
3844 "Fuzzer output channel not available, printing to stdout instead\n");
3845 fzliout = stdout;
3848 RootedString str(cx, JS::ToString(cx, args.get(1)));
3849 if (!str) {
3850 return false;
3852 UniqueChars bytes = JS_EncodeStringToUTF8(cx, str);
3853 if (!bytes) {
3854 return false;
3856 fprintf(fzliout, "%s\n", bytes.get());
3857 fflush(fzliout);
3858 } else if (StringEqualsAscii(operation, "FUZZILLI_RANDOM")) {
3859 // This is an entropy source which can be called during fuzzing.
3860 // Its currently used to tests whether Fuzzilli detects non-deterministic
3861 // behavior.
3862 args.rval().setInt32(static_cast<uint32_t>(mozilla::RandomUint64OrDie()));
3863 return true;
3866 args.rval().setUndefined();
3867 return true;
3870 static bool FuzzilliReprlGetAndRun(JSContext* cx) {
3871 size_t scriptSize = 0;
3873 unsigned action;
3874 MOZ_RELEASE_ASSERT(read(REPRL_CRFD, &action, 4) == 4);
3875 if (action == 'cexe') {
3876 MOZ_RELEASE_ASSERT(read(REPRL_CRFD, &scriptSize, 8) == 8);
3877 } else {
3878 fprintf(stderr, "Unknown action: %u\n", action);
3879 _exit(-1);
3882 CompileOptions options(cx);
3883 options.setIntroductionType("reprl")
3884 .setFileAndLine("reprl", 1)
3885 .setIsRunOnce(true)
3886 .setNoScriptRval(true)
3887 .setEagerDelazificationStrategy(defaultDelazificationMode);
3889 char* scriptSrc = static_cast<char*>(js_malloc(scriptSize));
3891 char* ptr = scriptSrc;
3892 size_t remaining = scriptSize;
3893 while (remaining > 0) {
3894 ssize_t rv = read(REPRL_DRFD, ptr, remaining);
3895 if (rv <= 0) {
3896 fprintf(stderr, "Failed to load script\n");
3897 _exit(-1);
3899 remaining -= rv;
3900 ptr += rv;
3903 JS::SourceText<Utf8Unit> srcBuf;
3904 if (!srcBuf.init(cx, scriptSrc, scriptSize,
3905 JS::SourceOwnership::TakeOwnership)) {
3906 return false;
3909 RootedScript script(cx, JS::Compile(cx, options, srcBuf));
3910 if (!script) {
3911 return false;
3914 if (!JS_ExecuteScript(cx, script)) {
3915 return false;
3918 return true;
3921 #endif /* FUZZING_JS_FUZZILLI */
3923 static bool FuzzilliUseReprlMode(OptionParser* op) {
3924 #ifdef FUZZING_JS_FUZZILLI
3925 // Check if we should use REPRL mode
3926 bool reprl_mode = op->getBoolOption("reprl");
3927 if (reprl_mode) {
3928 // Check in with parent
3929 char helo[] = "HELO";
3930 if (write(REPRL_CWFD, helo, 4) != 4 || read(REPRL_CRFD, helo, 4) != 4) {
3931 reprl_mode = false;
3934 if (memcmp(helo, "HELO", 4) != 0) {
3935 fprintf(stderr, "Invalid response from parent\n");
3936 _exit(-1);
3939 return reprl_mode;
3940 #else
3941 return false;
3942 #endif /* FUZZING_JS_FUZZILLI */
3945 static bool Crash(JSContext* cx, unsigned argc, Value* vp) {
3946 CallArgs args = CallArgsFromVp(argc, vp);
3947 if (args.length() == 0) {
3948 MOZ_CRASH("forced crash");
3950 RootedString message(cx, JS::ToString(cx, args[0]));
3951 if (!message) {
3952 return false;
3954 UniqueChars utf8chars = JS_EncodeStringToUTF8(cx, message);
3955 if (!utf8chars) {
3956 return false;
3958 if (args.get(1).isObject()) {
3959 RootedValue v(cx);
3960 RootedObject opts(cx, &args[1].toObject());
3961 if (!JS_GetProperty(cx, opts, "suppress_minidump", &v)) {
3962 return false;
3964 if (v.isBoolean() && v.toBoolean()) {
3965 js::NoteIntentionalCrash();
3968 #ifndef DEBUG
3969 MOZ_ReportCrash(utf8chars.get(), __FILE__, __LINE__);
3970 #endif
3971 MOZ_CRASH_UNSAFE(utf8chars.get());
3974 static bool GetSLX(JSContext* cx, unsigned argc, Value* vp) {
3975 CallArgs args = CallArgsFromVp(argc, vp);
3976 RootedScript script(cx);
3978 script = TestingFunctionArgumentToScript(cx, args.get(0));
3979 if (!script) {
3980 return false;
3982 args.rval().setInt32(GetScriptLineExtent(script));
3983 return true;
3986 static bool ThrowError(JSContext* cx, unsigned argc, Value* vp) {
3987 JS_ReportErrorASCII(cx, "This is an error");
3988 return false;
3991 static bool CopyErrorReportToObject(JSContext* cx, JSErrorReport* report,
3992 HandleObject obj) {
3993 RootedString nameStr(cx);
3994 if (report->exnType == JSEXN_WARN) {
3995 nameStr = JS_NewStringCopyZ(cx, "Warning");
3996 if (!nameStr) {
3997 return false;
3999 } else {
4000 nameStr = GetErrorTypeName(cx, report->exnType);
4001 // GetErrorTypeName doesn't set an exception, but
4002 // can fail for InternalError or non-error objects.
4003 if (!nameStr) {
4004 nameStr = cx->runtime()->emptyString;
4007 RootedValue nameVal(cx, StringValue(nameStr));
4008 if (!DefineDataProperty(cx, obj, cx->names().name, nameVal)) {
4009 return false;
4012 RootedString messageStr(cx, report->newMessageString(cx));
4013 if (!messageStr) {
4014 return false;
4016 RootedValue messageVal(cx, StringValue(messageStr));
4017 if (!DefineDataProperty(cx, obj, cx->names().message, messageVal)) {
4018 return false;
4021 RootedValue linenoVal(cx, Int32Value(report->lineno));
4022 if (!DefineDataProperty(cx, obj, cx->names().lineNumber, linenoVal)) {
4023 return false;
4026 RootedValue columnVal(cx, Int32Value(report->column.oneOriginValue()));
4027 if (!DefineDataProperty(cx, obj, cx->names().columnNumber, columnVal)) {
4028 return false;
4031 RootedObject notesArray(cx, CreateErrorNotesArray(cx, report));
4032 if (!notesArray) {
4033 return false;
4036 RootedValue notesArrayVal(cx, ObjectValue(*notesArray));
4037 return DefineDataProperty(cx, obj, cx->names().notes, notesArrayVal);
4040 static bool CreateErrorReport(JSContext* cx, unsigned argc, Value* vp) {
4041 CallArgs args = CallArgsFromVp(argc, vp);
4043 // We don't have a stack here, so just initialize with null.
4044 JS::ExceptionStack exnStack(cx, args.get(0), nullptr);
4045 JS::ErrorReportBuilder report(cx);
4046 if (!report.init(cx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) {
4047 return false;
4050 MOZ_ASSERT(!report.report()->isWarning());
4052 RootedObject obj(cx, JS_NewPlainObject(cx));
4053 if (!obj) {
4054 return false;
4057 RootedString toString(cx, NewStringCopyUTF8Z(cx, report.toStringResult()));
4058 if (!toString) {
4059 return false;
4062 if (!JS_DefineProperty(cx, obj, "toStringResult", toString,
4063 JSPROP_ENUMERATE)) {
4064 return false;
4067 if (!CopyErrorReportToObject(cx, report.report(), obj)) {
4068 return false;
4071 args.rval().setObject(*obj);
4072 return true;
4075 #define LAZY_STANDARD_CLASSES
4077 /* A class for easily testing the inner/outer object callbacks. */
4078 typedef struct ComplexObject {
4079 bool isInner;
4080 bool frozen;
4081 JSObject* inner;
4082 JSObject* outer;
4083 } ComplexObject;
4085 static bool sandbox_enumerate(JSContext* cx, JS::HandleObject obj,
4086 JS::MutableHandleIdVector properties,
4087 bool enumerableOnly) {
4088 RootedValue v(cx);
4090 if (!JS_GetProperty(cx, obj, "lazy", &v)) {
4091 return false;
4094 if (!ToBoolean(v)) {
4095 return true;
4098 return JS_NewEnumerateStandardClasses(cx, obj, properties, enumerableOnly);
4101 static bool sandbox_resolve(JSContext* cx, HandleObject obj, HandleId id,
4102 bool* resolvedp) {
4103 RootedValue v(cx);
4104 if (!JS_GetProperty(cx, obj, "lazy", &v)) {
4105 return false;
4108 if (ToBoolean(v)) {
4109 return JS_ResolveStandardClass(cx, obj, id, resolvedp);
4111 return true;
4114 static const JSClassOps sandbox_classOps = {
4115 nullptr, // addProperty
4116 nullptr, // delProperty
4117 nullptr, // enumerate
4118 sandbox_enumerate, // newEnumerate
4119 sandbox_resolve, // resolve
4120 nullptr, // mayResolve
4121 nullptr, // finalize
4122 nullptr, // call
4123 nullptr, // construct
4124 JS_GlobalObjectTraceHook, // trace
4127 static const JSClass sandbox_class = {"sandbox", JSCLASS_GLOBAL_FLAGS,
4128 &sandbox_classOps};
4130 static void SetStandardRealmOptions(JS::RealmOptions& options) {
4131 options.creationOptions()
4132 .setSharedMemoryAndAtomicsEnabled(enableSharedMemory)
4133 .setCoopAndCoepEnabled(false)
4134 .setWeakRefsEnabled(enableWeakRefs
4135 ? JS::WeakRefSpecifier::EnabledWithCleanupSome
4136 : JS::WeakRefSpecifier::Disabled)
4137 .setToSourceEnabled(enableToSource)
4138 .setPropertyErrorMessageFixEnabled(enablePropertyErrorMessageFix)
4139 .setIteratorHelpersEnabled(enableIteratorHelpers)
4140 .setShadowRealmsEnabled(enableShadowRealms)
4141 .setWellFormedUnicodeStringsEnabled(enableWellFormedUnicodeStrings)
4142 .setArrayGroupingEnabled(enableArrayGrouping)
4143 .setArrayBufferTransferEnabled(enableArrayBufferTransfer)
4144 #ifdef NIGHTLY_BUILD
4145 .setNewSetMethodsEnabled(enableNewSetMethods)
4146 .setSymbolsAsWeakMapKeysEnabled(enableSymbolsAsWeakMapKeys)
4147 .setArrayBufferResizableEnabled(enableArrayBufferResizable)
4148 .setSharedArrayBufferGrowableEnabled(enableArrayBufferResizable)
4149 #endif
4153 [[nodiscard]] static bool CheckRealmOptions(JSContext* cx,
4154 JS::RealmOptions& options,
4155 JSPrincipals* principals) {
4156 JS::RealmCreationOptions& creationOptions = options.creationOptions();
4157 if (creationOptions.compartmentSpecifier() !=
4158 JS::CompartmentSpecifier::ExistingCompartment) {
4159 return true;
4162 JS::Compartment* comp = creationOptions.compartment();
4164 // All realms in a compartment must be either system or non-system.
4165 bool isSystem =
4166 principals && principals == cx->runtime()->trustedPrincipals();
4167 if (isSystem != IsSystemCompartment(comp)) {
4168 JS_ReportErrorASCII(cx,
4169 "Cannot create system and non-system realms in the "
4170 "same compartment");
4171 return false;
4174 // Debugger visibility is per-compartment, not per-realm, so make sure the
4175 // requested visibility matches the existing compartment's.
4176 if (creationOptions.invisibleToDebugger() != comp->invisibleToDebugger()) {
4177 JS_ReportErrorASCII(cx,
4178 "All the realms in a compartment must have "
4179 "the same debugger visibility");
4180 return false;
4183 return true;
4186 static JSObject* NewSandbox(JSContext* cx, bool lazy) {
4187 JS::RealmOptions options;
4188 SetStandardRealmOptions(options);
4190 if (defaultToSameCompartment) {
4191 options.creationOptions().setExistingCompartment(cx->global());
4192 } else {
4193 options.creationOptions().setNewCompartmentAndZone();
4196 JSPrincipals* principals = nullptr;
4197 if (!CheckRealmOptions(cx, options, principals)) {
4198 return nullptr;
4201 RootedObject obj(cx,
4202 JS_NewGlobalObject(cx, &sandbox_class, principals,
4203 JS::DontFireOnNewGlobalHook, options));
4204 if (!obj) {
4205 return nullptr;
4209 JSAutoRealm ar(cx, obj);
4210 if (!lazy && !JS::InitRealmStandardClasses(cx)) {
4211 return nullptr;
4214 RootedValue value(cx, BooleanValue(lazy));
4215 if (!JS_DefineProperty(cx, obj, "lazy", value,
4216 JSPROP_PERMANENT | JSPROP_READONLY)) {
4217 return nullptr;
4220 JS_FireOnNewGlobalObject(cx, obj);
4223 if (!cx->compartment()->wrap(cx, &obj)) {
4224 return nullptr;
4226 return obj;
4229 static bool EvalInContext(JSContext* cx, unsigned argc, Value* vp) {
4230 CallArgs args = CallArgsFromVp(argc, vp);
4231 if (!args.requireAtLeast(cx, "evalcx", 1)) {
4232 return false;
4235 RootedString str(cx, ToString(cx, args[0]));
4236 if (!str) {
4237 return false;
4240 RootedObject sobj(cx);
4241 if (args.hasDefined(1)) {
4242 sobj = ToObject(cx, args[1]);
4243 if (!sobj) {
4244 return false;
4248 AutoStableStringChars strChars(cx);
4249 if (!strChars.initTwoByte(cx, str)) {
4250 return false;
4253 mozilla::Range<const char16_t> chars = strChars.twoByteRange();
4254 size_t srclen = chars.length();
4255 const char16_t* src = chars.begin().get();
4257 bool lazy = false;
4258 if (srclen == 4) {
4259 if (src[0] == 'l' && src[1] == 'a' && src[2] == 'z' && src[3] == 'y') {
4260 lazy = true;
4261 srclen = 0;
4265 if (!sobj) {
4266 sobj = NewSandbox(cx, lazy);
4267 if (!sobj) {
4268 return false;
4272 if (srclen == 0) {
4273 args.rval().setObject(*sobj);
4274 return true;
4277 JS::AutoFilename filename;
4278 uint32_t lineno;
4280 DescribeScriptedCaller(cx, &filename, &lineno);
4282 sobj = UncheckedUnwrap(sobj, true);
4284 JSAutoRealm ar(cx, sobj);
4286 sobj = ToWindowIfWindowProxy(sobj);
4288 if (!JS_IsGlobalObject(sobj)) {
4289 JS_ReportErrorASCII(cx, "Invalid scope argument to evalcx");
4290 return false;
4293 JS::CompileOptions opts(cx);
4294 opts.setFileAndLine(filename.get(), lineno)
4295 .setEagerDelazificationStrategy(defaultDelazificationMode);
4297 JS::SourceText<char16_t> srcBuf;
4298 if (!srcBuf.init(cx, src, srclen, JS::SourceOwnership::Borrowed) ||
4299 !JS::Evaluate(cx, opts, srcBuf, args.rval())) {
4300 return false;
4304 if (!cx->compartment()->wrap(cx, args.rval())) {
4305 return false;
4308 return true;
4311 static bool EnsureGeckoProfilingStackInstalled(JSContext* cx,
4312 ShellContext* sc) {
4313 if (cx->geckoProfiler().infraInstalled()) {
4314 MOZ_ASSERT(sc->geckoProfilingStack);
4315 return true;
4318 MOZ_ASSERT(!sc->geckoProfilingStack);
4319 sc->geckoProfilingStack = MakeUnique<ProfilingStack>();
4320 if (!sc->geckoProfilingStack) {
4321 JS_ReportOutOfMemory(cx);
4322 return false;
4325 SetContextProfilingStack(cx, sc->geckoProfilingStack.get());
4326 return true;
4329 struct WorkerInput {
4330 JSRuntime* parentRuntime;
4331 UniqueTwoByteChars chars;
4332 size_t length;
4334 WorkerInput(JSRuntime* parentRuntime, UniqueTwoByteChars chars, size_t length)
4335 : parentRuntime(parentRuntime), chars(std::move(chars)), length(length) {}
4338 static void DestroyShellCompartmentPrivate(JS::GCContext* gcx,
4339 JS::Compartment* compartment) {
4340 auto priv = static_cast<ShellCompartmentPrivate*>(
4341 JS_GetCompartmentPrivate(compartment));
4342 js_delete(priv);
4345 static void SetWorkerContextOptions(JSContext* cx);
4346 static bool ShellBuildId(JS::BuildIdCharVector* buildId);
4348 static constexpr size_t gWorkerStackSize = 2 * 128 * sizeof(size_t) * 1024;
4350 static void WorkerMain(UniquePtr<WorkerInput> input) {
4351 MOZ_ASSERT(input->parentRuntime);
4353 JSContext* cx = JS_NewContext(8L * 1024L * 1024L, input->parentRuntime);
4354 if (!cx) {
4355 return;
4357 auto destroyContext = MakeScopeExit([cx] { JS_DestroyContext(cx); });
4359 UniquePtr<ShellContext> sc =
4360 MakeUnique<ShellContext>(cx, ShellContext::Worker);
4361 if (!sc || !sc->registerWithCx(cx)) {
4362 return;
4365 if (!JS::InitSelfHostedCode(cx)) {
4366 return;
4369 EnvironmentPreparer environmentPreparer(cx);
4371 do {
4372 JS::RealmOptions realmOptions;
4373 SetStandardRealmOptions(realmOptions);
4375 RootedObject global(cx, NewGlobalObject(cx, realmOptions, nullptr,
4376 ShellGlobalKind::WindowProxy,
4377 /* immutablePrototype = */ true));
4378 if (!global) {
4379 break;
4382 JSAutoRealm ar(cx, global);
4384 JS::ConstUTF8CharsZ path(processWideModuleLoadPath.get(),
4385 strlen(processWideModuleLoadPath.get()));
4386 RootedString moduleLoadPath(cx, JS_NewStringCopyUTF8Z(cx, path));
4387 if (!moduleLoadPath) {
4388 return;
4390 sc->moduleLoader = js::MakeUnique<ModuleLoader>();
4391 if (!sc->moduleLoader || !sc->moduleLoader->init(cx, moduleLoadPath)) {
4392 return;
4395 JS::CompileOptions options(cx);
4396 options.setFileAndLine("<string>", 1)
4397 .setIsRunOnce(true)
4398 .setEagerDelazificationStrategy(defaultDelazificationMode);
4400 AutoReportException are(cx);
4401 JS::SourceText<char16_t> srcBuf;
4402 if (!srcBuf.init(cx, input->chars.get(), input->length,
4403 JS::SourceOwnership::Borrowed)) {
4404 break;
4407 RootedScript script(cx, JS::Compile(cx, options, srcBuf));
4408 if (!script) {
4409 break;
4411 RootedValue result(cx);
4412 JS_ExecuteScript(cx, script, &result);
4413 } while (0);
4415 KillWatchdog(cx);
4418 // Workers can spawn other workers, so we need a lock to access workerThreads.
4419 static Mutex* workerThreadsLock = nullptr;
4420 static Vector<UniquePtr<js::Thread>, 0, SystemAllocPolicy> workerThreads;
4422 class MOZ_RAII AutoLockWorkerThreads : public LockGuard<Mutex> {
4423 using Base = LockGuard<Mutex>;
4425 public:
4426 AutoLockWorkerThreads() : Base(*workerThreadsLock) {
4427 MOZ_ASSERT(workerThreadsLock);
4431 static bool EvalInWorker(JSContext* cx, unsigned argc, Value* vp) {
4432 if (!CanUseExtraThreads()) {
4433 JS_ReportErrorASCII(cx, "Can't create threads with --no-threads");
4434 return false;
4437 CallArgs args = CallArgsFromVp(argc, vp);
4438 if (!args.get(0).isString()) {
4439 JS_ReportErrorASCII(cx, "Invalid arguments");
4440 return false;
4443 #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
4444 if (cx->runningOOMTest) {
4445 JS_ReportErrorASCII(
4446 cx, "Can't create threads while running simulated OOM test");
4447 return false;
4449 #endif
4451 if (!args[0].toString()->ensureLinear(cx)) {
4452 return false;
4455 if (!workerThreadsLock) {
4456 workerThreadsLock = js_new<Mutex>(mutexid::ShellWorkerThreads);
4457 if (!workerThreadsLock) {
4458 ReportOutOfMemory(cx);
4459 return false;
4463 JSLinearString* str = &args[0].toString()->asLinear();
4465 UniqueTwoByteChars chars(js_pod_malloc<char16_t>(str->length()));
4466 if (!chars) {
4467 ReportOutOfMemory(cx);
4468 return false;
4471 CopyChars(chars.get(), *str);
4473 auto input = js::MakeUnique<WorkerInput>(JS_GetParentRuntime(cx),
4474 std::move(chars), str->length());
4475 if (!input) {
4476 ReportOutOfMemory(cx);
4477 return false;
4480 UniquePtr<Thread> thread;
4482 AutoEnterOOMUnsafeRegion oomUnsafe;
4483 thread = js::MakeUnique<Thread>(
4484 Thread::Options().setStackSize(gWorkerStackSize + 512 * 1024));
4485 if (!thread || !thread->init(WorkerMain, std::move(input))) {
4486 oomUnsafe.crash("EvalInWorker");
4490 AutoLockWorkerThreads alwt;
4491 if (!workerThreads.append(std::move(thread))) {
4492 ReportOutOfMemory(cx);
4493 thread->join();
4494 return false;
4497 args.rval().setUndefined();
4498 return true;
4501 static bool ShapeOf(JSContext* cx, unsigned argc, JS::Value* vp) {
4502 CallArgs args = CallArgsFromVp(argc, vp);
4503 if (!args.get(0).isObject()) {
4504 JS_ReportErrorASCII(cx, "shapeOf: object expected");
4505 return false;
4507 JSObject* obj = &args[0].toObject();
4508 args.rval().set(JS_NumberValue(double(uintptr_t(obj->shape()) >> 3)));
4509 return true;
4512 static bool Sleep_fn(JSContext* cx, unsigned argc, Value* vp) {
4513 ShellContext* sc = GetShellContext(cx);
4514 CallArgs args = CallArgsFromVp(argc, vp);
4516 TimeDuration duration = TimeDuration::FromSeconds(0.0);
4517 if (args.length() > 0) {
4518 double t_secs;
4519 if (!ToNumber(cx, args[0], &t_secs)) {
4520 return false;
4522 if (std::isnan(t_secs)) {
4523 JS_ReportErrorASCII(cx, "sleep interval is not a number");
4524 return false;
4527 duration = TimeDuration::FromSeconds(std::max(0.0, t_secs));
4528 const TimeDuration MAX_TIMEOUT_INTERVAL =
4529 TimeDuration::FromSeconds(MAX_TIMEOUT_SECONDS);
4530 if (duration > MAX_TIMEOUT_INTERVAL) {
4531 JS_ReportErrorASCII(cx, "Excessive sleep interval");
4532 return false;
4536 LockGuard<Mutex> guard(sc->watchdogLock);
4537 TimeStamp toWakeup = TimeStamp::Now() + duration;
4538 for (;;) {
4539 sc->sleepWakeup.wait_for(guard, duration);
4540 if (sc->serviceInterrupt) {
4541 break;
4543 auto now = TimeStamp::Now();
4544 if (now >= toWakeup) {
4545 break;
4547 duration = toWakeup - now;
4550 args.rval().setUndefined();
4551 return !sc->serviceInterrupt;
4554 static void KillWatchdog(JSContext* cx) {
4555 ShellContext* sc = GetShellContext(cx);
4556 Maybe<Thread> thread;
4559 LockGuard<Mutex> guard(sc->watchdogLock);
4560 std::swap(sc->watchdogThread, thread);
4561 if (thread) {
4562 // The watchdog thread becoming Nothing is its signal to exit.
4563 sc->watchdogWakeup.notify_one();
4566 if (thread) {
4567 thread->join();
4570 MOZ_ASSERT(!sc->watchdogThread);
4573 static void WatchdogMain(JSContext* cx) {
4574 ThisThread::SetName("JS Watchdog");
4576 ShellContext* sc = GetShellContext(cx);
4579 LockGuard<Mutex> guard(sc->watchdogLock);
4580 while (sc->watchdogThread) {
4581 auto now = TimeStamp::Now();
4582 if (sc->watchdogTimeout && now >= sc->watchdogTimeout.value()) {
4584 * The timeout has just expired. Request an interrupt callback
4585 * outside the lock.
4587 sc->watchdogTimeout = Nothing();
4589 UnlockGuard<Mutex> unlock(guard);
4590 CancelExecution(cx);
4593 /* Wake up any threads doing sleep. */
4594 sc->sleepWakeup.notify_all();
4595 } else {
4596 if (sc->watchdogTimeout) {
4598 * Time hasn't expired yet. Simulate an interrupt callback
4599 * which doesn't abort execution.
4601 JS_RequestInterruptCallback(cx);
4604 TimeDuration sleepDuration = sc->watchdogTimeout
4605 ? TimeDuration::FromSeconds(0.1)
4606 : TimeDuration::Forever();
4607 sc->watchdogWakeup.wait_for(guard, sleepDuration);
4613 static bool ScheduleWatchdog(JSContext* cx, double t) {
4614 ShellContext* sc = GetShellContext(cx);
4616 if (t <= 0) {
4617 LockGuard<Mutex> guard(sc->watchdogLock);
4618 sc->watchdogTimeout = Nothing();
4619 return true;
4622 #ifdef __wasi__
4623 return false;
4624 #endif
4626 auto interval = TimeDuration::FromSeconds(t);
4627 auto timeout = TimeStamp::Now() + interval;
4628 LockGuard<Mutex> guard(sc->watchdogLock);
4629 if (!sc->watchdogThread) {
4630 MOZ_ASSERT(!sc->watchdogTimeout);
4631 sc->watchdogThread.emplace();
4632 AutoEnterOOMUnsafeRegion oomUnsafe;
4633 if (!sc->watchdogThread->init(WatchdogMain, cx)) {
4634 oomUnsafe.crash("watchdogThread.init");
4636 } else if (!sc->watchdogTimeout || timeout < sc->watchdogTimeout.value()) {
4637 sc->watchdogWakeup.notify_one();
4639 sc->watchdogTimeout = Some(timeout);
4640 return true;
4643 static void KillWorkerThreads(JSContext* cx) {
4644 MOZ_ASSERT_IF(!CanUseExtraThreads(), workerThreads.empty());
4646 if (!workerThreadsLock) {
4647 MOZ_ASSERT(workerThreads.empty());
4648 return;
4651 while (true) {
4652 // We need to leave the AutoLockWorkerThreads scope before we call
4653 // js::Thread::join, to avoid deadlocks when AutoLockWorkerThreads is
4654 // used by the worker thread.
4655 UniquePtr<Thread> thread;
4657 AutoLockWorkerThreads alwt;
4658 if (workerThreads.empty()) {
4659 break;
4661 thread = std::move(workerThreads.back());
4662 workerThreads.popBack();
4664 thread->join();
4667 workerThreads.clearAndFree();
4669 js_delete(workerThreadsLock);
4670 workerThreadsLock = nullptr;
4673 static void CancelExecution(JSContext* cx) {
4674 ShellContext* sc = GetShellContext(cx);
4675 sc->serviceInterrupt = true;
4676 JS_RequestInterruptCallback(cx);
4679 static bool SetTimeoutValue(JSContext* cx, double t) {
4680 if (std::isnan(t)) {
4681 JS_ReportErrorASCII(cx, "timeout is not a number");
4682 return false;
4684 const TimeDuration MAX_TIMEOUT_INTERVAL =
4685 TimeDuration::FromSeconds(MAX_TIMEOUT_SECONDS);
4686 if (TimeDuration::FromSeconds(t) > MAX_TIMEOUT_INTERVAL) {
4687 JS_ReportErrorASCII(cx, "Excessive timeout value");
4688 return false;
4690 GetShellContext(cx)->timeoutInterval = t;
4691 if (!ScheduleWatchdog(cx, t)) {
4692 JS_ReportErrorASCII(cx, "Failed to create the watchdog");
4693 return false;
4695 return true;
4698 static bool Timeout(JSContext* cx, unsigned argc, Value* vp) {
4699 ShellContext* sc = GetShellContext(cx);
4700 CallArgs args = CallArgsFromVp(argc, vp);
4702 if (args.length() == 0) {
4703 args.rval().setNumber(sc->timeoutInterval);
4704 return true;
4707 if (args.length() > 2) {
4708 JS_ReportErrorASCII(cx, "Wrong number of arguments");
4709 return false;
4712 double t;
4713 if (!ToNumber(cx, args[0], &t)) {
4714 return false;
4717 if (args.length() > 1) {
4718 RootedValue value(cx, args[1]);
4719 if (!value.isObject() || !value.toObject().is<JSFunction>()) {
4720 JS_ReportErrorASCII(cx, "Second argument must be a timeout function");
4721 return false;
4723 sc->interruptFunc = value;
4724 sc->haveInterruptFunc = true;
4727 args.rval().setUndefined();
4728 return SetTimeoutValue(cx, t);
4731 static bool InterruptIf(JSContext* cx, unsigned argc, Value* vp) {
4732 CallArgs args = CallArgsFromVp(argc, vp);
4734 if (args.length() != 1) {
4735 JS_ReportErrorASCII(cx, "Wrong number of arguments");
4736 return false;
4739 if (ToBoolean(args[0])) {
4740 GetShellContext(cx)->serviceInterrupt = true;
4741 JS_RequestInterruptCallback(cx);
4744 args.rval().setUndefined();
4745 return true;
4748 static bool InvokeInterruptCallbackWrapper(JSContext* cx, unsigned argc,
4749 Value* vp) {
4750 CallArgs args = CallArgsFromVp(argc, vp);
4751 if (args.length() != 1) {
4752 JS_ReportErrorASCII(cx, "Wrong number of arguments");
4753 return false;
4756 GetShellContext(cx)->serviceInterrupt = true;
4757 JS_RequestInterruptCallback(cx);
4758 bool interruptRv = CheckForInterrupt(cx);
4760 // The interrupt handler could have set a pending exception. Since we call
4761 // back into JS, don't have it see the pending exception. If we have an
4762 // uncatchable exception that's not propagating a debug mode forced
4763 // return, return.
4764 if (!interruptRv && !cx->isExceptionPending() &&
4765 !cx->isPropagatingForcedReturn()) {
4766 return false;
4769 JS::AutoSaveExceptionState savedExc(cx);
4771 FixedInvokeArgs<1> iargs(cx);
4773 iargs[0].setBoolean(interruptRv);
4775 RootedValue rv(cx);
4776 if (!js::Call(cx, args[0], UndefinedHandleValue, iargs, &rv)) {
4777 return false;
4780 args.rval().setUndefined();
4781 return interruptRv;
4784 static bool SetInterruptCallback(JSContext* cx, unsigned argc, Value* vp) {
4785 CallArgs args = CallArgsFromVp(argc, vp);
4787 if (args.length() != 1) {
4788 JS_ReportErrorASCII(cx, "Wrong number of arguments");
4789 return false;
4792 RootedValue value(cx, args[0]);
4793 if (!value.isObject() || !value.toObject().is<JSFunction>()) {
4794 JS_ReportErrorASCII(cx, "Argument must be a function");
4795 return false;
4797 GetShellContext(cx)->interruptFunc = value;
4798 GetShellContext(cx)->haveInterruptFunc = true;
4800 args.rval().setUndefined();
4801 return true;
4804 #ifdef DEBUG
4805 // var s0 = "A".repeat(10*1024);
4806 // interruptRegexp(/a(bc|bd)/, s0);
4807 // first arg is regexp
4808 // second arg is string
4809 static bool InterruptRegexp(JSContext* cx, unsigned argc, Value* vp) {
4810 CallArgs args = CallArgsFromVp(argc, vp);
4811 ShellContext* sc = GetShellContext(cx);
4812 RootedObject callee(cx, &args.callee());
4814 if (args.length() != 2) {
4815 ReportUsageErrorASCII(cx, callee, "Wrong number of arguments.");
4816 return false;
4818 if (!(args[0].isObject() && args[0].toObject().is<RegExpObject>())) {
4819 ReportUsageErrorASCII(cx, callee,
4820 "First argument must be a regular expression.");
4821 return false;
4823 if (!args[1].isString()) {
4824 ReportUsageErrorASCII(cx, callee, "Second argument must be a String.");
4825 return false;
4827 // Set interrupt flags
4828 sc->serviceInterrupt = true;
4829 js::irregexp::IsolateSetShouldSimulateInterrupt(cx->isolate);
4831 RootedObject regexp(cx, &args[0].toObject());
4832 RootedString string(cx, args[1].toString());
4833 int32_t lastIndex = 0;
4835 return js::RegExpMatcherRaw(cx, regexp, string, lastIndex, nullptr,
4836 args.rval());
4838 #endif
4840 static bool CheckRegExpSyntax(JSContext* cx, unsigned argc, Value* vp) {
4841 CallArgs args = CallArgsFromVp(argc, vp);
4842 RootedObject callee(cx, &args.callee());
4844 if (args.length() != 1) {
4845 ReportUsageErrorASCII(cx, callee, "Wrong number of arguments.");
4846 return false;
4848 if (!args[0].isString()) {
4849 ReportUsageErrorASCII(cx, callee, "First argument must be a string.");
4850 return false;
4853 RootedString string(cx, args[0].toString());
4854 AutoStableStringChars stableChars(cx);
4855 if (!stableChars.initTwoByte(cx, string)) {
4856 return false;
4859 const char16_t* chars = stableChars.twoByteRange().begin().get();
4860 size_t length = string->length();
4862 Rooted<JS::Value> error(cx);
4863 if (!JS::CheckRegExpSyntax(cx, chars, length, JS::RegExpFlag::NoFlags,
4864 &error)) {
4865 return false;
4868 args.rval().set(error);
4869 return true;
4872 static bool SetJitCompilerOption(JSContext* cx, unsigned argc, Value* vp) {
4873 CallArgs args = CallArgsFromVp(argc, vp);
4874 RootedObject callee(cx, &args.callee());
4876 if (args.length() != 2) {
4877 ReportUsageErrorASCII(cx, callee, "Wrong number of arguments.");
4878 return false;
4881 if (!args[0].isString()) {
4882 ReportUsageErrorASCII(cx, callee, "First argument must be a String.");
4883 return false;
4886 if (!args[1].isInt32()) {
4887 ReportUsageErrorASCII(cx, callee, "Second argument must be an Int32.");
4888 return false;
4891 // Disallow setting JIT options when there are worker threads, to avoid
4892 // races.
4893 if (workerThreadsLock) {
4894 ReportUsageErrorASCII(
4895 cx, callee, "Can't set JIT options when there are worker threads.");
4896 return false;
4899 JSLinearString* strArg = JS_EnsureLinearString(cx, args[0].toString());
4900 if (!strArg) {
4901 return false;
4904 #define JIT_COMPILER_MATCH(key, string) \
4905 else if (JS_LinearStringEqualsLiteral(strArg, string)) opt = \
4906 JSJITCOMPILER_##key;
4908 JSJitCompilerOption opt = JSJITCOMPILER_NOT_AN_OPTION;
4909 if (false) {
4911 JIT_COMPILER_OPTIONS(JIT_COMPILER_MATCH);
4912 #undef JIT_COMPILER_MATCH
4914 if (opt == JSJITCOMPILER_NOT_AN_OPTION) {
4915 ReportUsageErrorASCII(
4916 cx, callee,
4917 "First argument does not name a valid option (see jsapi.h).");
4918 return false;
4921 int32_t number = args[1].toInt32();
4922 if (number < 0) {
4923 number = -1;
4926 // Disallow enabling or disabling the Baseline Interpreter at runtime.
4927 // Enabling is a problem because the Baseline Interpreter code is only
4928 // present if the interpreter was enabled when the JitRuntime was created.
4929 // To support disabling we would have to discard all JitScripts. Furthermore,
4930 // we really want JitOptions to be immutable after startup so it's better to
4931 // use shell flags.
4932 if (opt == JSJITCOMPILER_BASELINE_INTERPRETER_ENABLE &&
4933 bool(number) != jit::IsBaselineInterpreterEnabled()) {
4934 JS_ReportErrorASCII(cx,
4935 "Enabling or disabling the Baseline Interpreter at "
4936 "runtime is not supported.");
4937 return false;
4940 // Throw if disabling the JITs and there's JIT code on the stack, to avoid
4941 // assertion failures.
4942 if ((opt == JSJITCOMPILER_BASELINE_ENABLE ||
4943 opt == JSJITCOMPILER_ION_ENABLE) &&
4944 number == 0) {
4945 js::jit::JitActivationIterator iter(cx);
4946 if (!iter.done()) {
4947 JS_ReportErrorASCII(cx,
4948 "Can't turn off JITs with JIT code on the stack.");
4949 return false;
4953 // Changing code memory protection settings at runtime is not supported. Don't
4954 // throw if not changing the setting because some jit-tests depend on that.
4955 if (opt == JSJITCOMPILER_WRITE_PROTECT_CODE) {
4956 uint32_t writeProtect;
4957 MOZ_ALWAYS_TRUE(JS_GetGlobalJitCompilerOption(
4958 cx, JSJITCOMPILER_WRITE_PROTECT_CODE, &writeProtect));
4959 if (bool(number) != writeProtect) {
4960 JS_ReportErrorASCII(cx, "Can't change code write protection at runtime");
4961 return false;
4963 return true;
4966 // Throw if trying to disable all the Wasm compilers. The logic here is that
4967 // if we're trying to disable a compiler that is currently enabled and that is
4968 // the last compiler enabled then we must throw.
4970 // Note that this check does not prevent an error from being thrown later.
4971 // Actual compiler availability is dynamic and depends on other conditions,
4972 // such as other options set and whether a debugger is present.
4973 if ((opt == JSJITCOMPILER_WASM_JIT_BASELINE ||
4974 opt == JSJITCOMPILER_WASM_JIT_OPTIMIZING) &&
4975 number == 0) {
4976 uint32_t baseline, optimizing;
4977 MOZ_ALWAYS_TRUE(JS_GetGlobalJitCompilerOption(
4978 cx, JSJITCOMPILER_WASM_JIT_BASELINE, &baseline));
4979 MOZ_ALWAYS_TRUE(JS_GetGlobalJitCompilerOption(
4980 cx, JSJITCOMPILER_WASM_JIT_OPTIMIZING, &optimizing));
4981 if (baseline + optimizing == 1) {
4982 if ((opt == JSJITCOMPILER_WASM_JIT_BASELINE && baseline) ||
4983 (opt == JSJITCOMPILER_WASM_JIT_OPTIMIZING && optimizing)) {
4984 JS_ReportErrorASCII(
4986 "Disabling all the Wasm compilers at runtime is not supported.");
4987 return false;
4992 // JIT compiler options are process-wide, so we have to stop off-thread
4993 // compilations for all runtimes to avoid races.
4994 WaitForAllHelperThreads();
4996 // Only release JIT code for the current runtime because there's no good
4997 // way to discard code for other runtimes.
4998 ReleaseAllJITCode(cx->gcContext());
5000 JS_SetGlobalJitCompilerOption(cx, opt, uint32_t(number));
5002 args.rval().setUndefined();
5003 return true;
5006 static bool EnableLastWarning(JSContext* cx, unsigned argc, Value* vp) {
5007 ShellContext* sc = GetShellContext(cx);
5008 CallArgs args = CallArgsFromVp(argc, vp);
5010 sc->lastWarningEnabled = true;
5011 sc->lastWarning.setNull();
5013 args.rval().setUndefined();
5014 return true;
5017 static bool DisableLastWarning(JSContext* cx, unsigned argc, Value* vp) {
5018 ShellContext* sc = GetShellContext(cx);
5019 CallArgs args = CallArgsFromVp(argc, vp);
5021 sc->lastWarningEnabled = false;
5022 sc->lastWarning.setNull();
5024 args.rval().setUndefined();
5025 return true;
5028 static bool GetLastWarning(JSContext* cx, unsigned argc, Value* vp) {
5029 ShellContext* sc = GetShellContext(cx);
5030 CallArgs args = CallArgsFromVp(argc, vp);
5032 if (!sc->lastWarningEnabled) {
5033 JS_ReportErrorASCII(cx, "Call enableLastWarning first.");
5034 return false;
5037 if (!JS_WrapValue(cx, &sc->lastWarning)) {
5038 return false;
5041 args.rval().set(sc->lastWarning);
5042 return true;
5045 static bool ClearLastWarning(JSContext* cx, unsigned argc, Value* vp) {
5046 ShellContext* sc = GetShellContext(cx);
5047 CallArgs args = CallArgsFromVp(argc, vp);
5049 if (!sc->lastWarningEnabled) {
5050 JS_ReportErrorASCII(cx, "Call enableLastWarning first.");
5051 return false;
5054 sc->lastWarning.setNull();
5056 args.rval().setUndefined();
5057 return true;
5060 #if defined(DEBUG) || defined(JS_JITSPEW)
5061 static bool StackDump(JSContext* cx, unsigned argc, Value* vp) {
5062 CallArgs args = CallArgsFromVp(argc, vp);
5064 if (!gOutFile->isOpen()) {
5065 JS_ReportErrorASCII(cx, "output file is closed");
5066 return false;
5069 bool showArgs = ToBoolean(args.get(0));
5070 bool showLocals = ToBoolean(args.get(1));
5071 bool showThisProps = ToBoolean(args.get(2));
5073 JS::UniqueChars buf =
5074 JS::FormatStackDump(cx, showArgs, showLocals, showThisProps);
5075 if (!buf) {
5076 fputs("Failed to format JavaScript stack for dump\n", gOutFile->fp);
5077 JS_ClearPendingException(cx);
5078 } else {
5079 fputs(buf.get(), gOutFile->fp);
5082 args.rval().setUndefined();
5083 return true;
5085 #endif
5087 static bool StackPointerInfo(JSContext* cx, unsigned argc, Value* vp) {
5088 CallArgs args = CallArgsFromVp(argc, vp);
5090 // Copy the truncated stack pointer to the result. This value is not used
5091 // as a pointer but as a way to measure frame-size from JS.
5092 args.rval().setInt32(int32_t(reinterpret_cast<size_t>(&args) & 0xfffffff));
5093 return true;
5096 static bool Elapsed(JSContext* cx, unsigned argc, Value* vp) {
5097 CallArgs args = CallArgsFromVp(argc, vp);
5098 if (args.length() == 0) {
5099 double d = PRMJ_Now() - GetShellContext(cx)->startTime;
5100 args.rval().setDouble(d);
5101 return true;
5103 JS_ReportErrorASCII(cx, "Wrong number of arguments");
5104 return false;
5107 static ShellCompartmentPrivate* EnsureShellCompartmentPrivate(JSContext* cx) {
5108 Compartment* comp = cx->compartment();
5109 auto priv =
5110 static_cast<ShellCompartmentPrivate*>(JS_GetCompartmentPrivate(comp));
5111 if (!priv) {
5112 priv = cx->new_<ShellCompartmentPrivate>();
5113 JS_SetCompartmentPrivate(cx->compartment(), priv);
5115 return priv;
5118 static bool ParseModule(JSContext* cx, unsigned argc, Value* vp) {
5119 CallArgs args = CallArgsFromVp(argc, vp);
5120 if (!args.requireAtLeast(cx, "parseModule", 1)) {
5121 return false;
5124 if (!args[0].isString()) {
5125 const char* typeName = InformalValueTypeName(args[0]);
5126 JS_ReportErrorASCII(cx, "expected string to compile, got %s", typeName);
5127 return false;
5130 JSString* scriptContents = args[0].toString();
5132 UniqueChars filename;
5133 CompileOptions options(cx);
5134 if (args.length() > 1) {
5135 if (!args[1].isString()) {
5136 const char* typeName = InformalValueTypeName(args[1]);
5137 JS_ReportErrorASCII(cx, "expected filename string, got %s", typeName);
5138 return false;
5141 RootedString str(cx, args[1].toString());
5142 filename = JS_EncodeStringToUTF8(cx, str);
5143 if (!filename) {
5144 return false;
5147 options.setFileAndLine(filename.get(), 1);
5148 } else {
5149 options.setFileAndLine("<string>", 1);
5151 options.setModule();
5153 AutoStableStringChars linearChars(cx);
5154 if (!linearChars.initTwoByte(cx, scriptContents)) {
5155 return false;
5158 JS::SourceText<char16_t> srcBuf;
5159 if (!srcBuf.initMaybeBorrowed(cx, linearChars)) {
5160 return false;
5163 AutoReportFrontendContext fc(cx);
5164 RootedObject module(cx, frontend::CompileModule(cx, &fc, options, srcBuf));
5165 if (!module) {
5166 return false;
5169 Rooted<ShellModuleObjectWrapper*> wrapper(
5170 cx, ShellModuleObjectWrapper::create(cx, module.as<ModuleObject>()));
5171 if (!wrapper) {
5172 return false;
5174 args.rval().setObject(*wrapper);
5175 return true;
5178 // A JSObject that holds XDRBuffer.
5179 class XDRBufferObject : public NativeObject {
5180 static const size_t VECTOR_SLOT = 0;
5181 static const unsigned RESERVED_SLOTS = 1;
5183 public:
5184 static const JSClassOps classOps_;
5185 static const JSClass class_;
5187 [[nodiscard]] inline static XDRBufferObject* create(
5188 JSContext* cx, JS::TranscodeBuffer&& buf);
5190 JS::TranscodeBuffer* data() const {
5191 Value value = getReservedSlot(VECTOR_SLOT);
5192 auto buf = static_cast<JS::TranscodeBuffer*>(value.toPrivate());
5193 MOZ_ASSERT(buf);
5194 return buf;
5197 bool hasData() const {
5198 // Data may not be present if we hit OOM in initialization.
5199 return !getReservedSlot(VECTOR_SLOT).isUndefined();
5202 static void finalize(JS::GCContext* gcx, JSObject* obj);
5205 /*static */ const JSClassOps XDRBufferObject::classOps_ = {
5206 nullptr, // addProperty
5207 nullptr, // delProperty
5208 nullptr, // enumerate
5209 nullptr, // newEnumerate
5210 nullptr, // resolve
5211 nullptr, // mayResolve
5212 XDRBufferObject::finalize, // finalize
5213 nullptr, // call
5214 nullptr, // construct
5215 nullptr, // trace
5218 /*static */ const JSClass XDRBufferObject::class_ = {
5219 "XDRBufferObject",
5220 JSCLASS_HAS_RESERVED_SLOTS(XDRBufferObject::RESERVED_SLOTS) |
5221 JSCLASS_BACKGROUND_FINALIZE,
5222 &XDRBufferObject::classOps_};
5224 XDRBufferObject* XDRBufferObject::create(JSContext* cx,
5225 JS::TranscodeBuffer&& buf) {
5226 XDRBufferObject* bufObj =
5227 NewObjectWithGivenProto<XDRBufferObject>(cx, nullptr);
5228 if (!bufObj) {
5229 return nullptr;
5232 auto heapBuf = cx->make_unique<JS::TranscodeBuffer>(std::move(buf));
5233 if (!heapBuf) {
5234 return nullptr;
5237 size_t len = heapBuf->length();
5238 InitReservedSlot(bufObj, VECTOR_SLOT, heapBuf.release(), len,
5239 MemoryUse::XDRBufferElements);
5241 return bufObj;
5244 void XDRBufferObject::finalize(JS::GCContext* gcx, JSObject* obj) {
5245 XDRBufferObject* buf = &obj->as<XDRBufferObject>();
5246 if (buf->hasData()) {
5247 gcx->delete_(buf, buf->data(), buf->data()->length(),
5248 MemoryUse::XDRBufferElements);
5252 static bool InstantiateModuleStencil(JSContext* cx, uint32_t argc, Value* vp) {
5253 CallArgs args = CallArgsFromVp(argc, vp);
5255 if (!args.requireAtLeast(cx, "instantiateModuleStencil", 1)) {
5256 return false;
5259 /* Prepare the input byte array. */
5260 if (!args[0].isObject()) {
5261 JS_ReportErrorASCII(cx,
5262 "instantiateModuleStencil: Stencil object expected");
5263 return false;
5265 Rooted<js::StencilObject*> stencilObj(
5266 cx, args[0].toObject().maybeUnwrapIf<js::StencilObject>());
5267 if (!stencilObj) {
5268 JS_ReportErrorASCII(cx,
5269 "instantiateModuleStencil: Stencil object expected");
5270 return false;
5273 if (!stencilObj->stencil()->isModule()) {
5274 JS_ReportErrorASCII(cx,
5275 "instantiateModuleStencil: Module stencil expected");
5276 return false;
5279 CompileOptions options(cx);
5280 UniqueChars fileNameBytes;
5281 if (args.length() == 2) {
5282 if (!args[1].isObject()) {
5283 JS_ReportErrorASCII(
5284 cx, "instantiateModuleStencil: The 2nd argument must be an object");
5285 return false;
5288 RootedObject opts(cx, &args[1].toObject());
5289 if (!js::ParseCompileOptions(cx, options, opts, &fileNameBytes)) {
5290 return false;
5294 /* Prepare the CompilationStencil for decoding. */
5295 AutoReportFrontendContext fc(cx);
5296 Rooted<frontend::CompilationInput> input(cx,
5297 frontend::CompilationInput(options));
5298 if (!input.get().initForModule(&fc)) {
5299 return false;
5302 if (!js::ValidateLazinessOfStencilAndGlobal(cx, *stencilObj->stencil())) {
5303 return false;
5306 /* Instantiate the stencil. */
5307 Rooted<frontend::CompilationGCOutput> output(cx);
5308 if (!frontend::CompilationStencil::instantiateStencils(
5309 cx, input.get(), *stencilObj->stencil(), output.get())) {
5310 return false;
5313 Rooted<ModuleObject*> modObject(cx, output.get().module);
5314 Rooted<ShellModuleObjectWrapper*> wrapper(
5315 cx, ShellModuleObjectWrapper::create(cx, modObject));
5316 if (!wrapper) {
5317 return false;
5319 args.rval().setObject(*wrapper);
5320 return true;
5323 static bool InstantiateModuleStencilXDR(JSContext* cx, uint32_t argc,
5324 Value* vp) {
5325 CallArgs args = CallArgsFromVp(argc, vp);
5327 if (!args.requireAtLeast(cx, "instantiateModuleStencilXDR", 1)) {
5328 return false;
5331 /* Prepare the input byte array. */
5332 if (!args[0].isObject()) {
5333 JS_ReportErrorASCII(
5334 cx, "instantiateModuleStencilXDR: Stencil XDR object expected");
5335 return false;
5337 Rooted<StencilXDRBufferObject*> xdrObj(
5338 cx, args[0].toObject().maybeUnwrapIf<StencilXDRBufferObject>());
5339 if (!xdrObj) {
5340 JS_ReportErrorASCII(
5341 cx, "instantiateModuleStencilXDR: Stencil XDR object expected");
5342 return false;
5344 MOZ_ASSERT(xdrObj->hasBuffer());
5346 CompileOptions options(cx);
5347 UniqueChars fileNameBytes;
5348 if (args.length() == 2) {
5349 if (!args[1].isObject()) {
5350 JS_ReportErrorASCII(
5352 "instantiateModuleStencilXDR: The 2nd argument must be an object");
5353 return false;
5356 RootedObject opts(cx, &args[1].toObject());
5357 if (!js::ParseCompileOptions(cx, options, opts, &fileNameBytes)) {
5358 return false;
5362 /* Prepare the CompilationStencil for decoding. */
5363 AutoReportFrontendContext fc(cx);
5364 Rooted<frontend::CompilationInput> input(cx,
5365 frontend::CompilationInput(options));
5366 if (!input.get().initForModule(&fc)) {
5367 return false;
5369 frontend::CompilationStencil stencil(nullptr);
5371 /* Deserialize the stencil from XDR. */
5372 JS::TranscodeRange xdrRange(xdrObj->buffer(), xdrObj->bufferLength());
5373 bool succeeded = false;
5374 if (!stencil.deserializeStencils(&fc, options, xdrRange, &succeeded)) {
5375 return false;
5377 if (!succeeded) {
5378 fc.clearAutoReport();
5379 JS_ReportErrorASCII(cx, "Decoding failure");
5380 return false;
5383 if (!stencil.isModule()) {
5384 fc.clearAutoReport();
5385 JS_ReportErrorASCII(cx,
5386 "instantiateModuleStencilXDR: Module stencil expected");
5387 return false;
5390 if (!js::ValidateLazinessOfStencilAndGlobal(cx, stencil)) {
5391 return false;
5394 /* Instantiate the stencil. */
5395 Rooted<frontend::CompilationGCOutput> output(cx);
5396 if (!frontend::CompilationStencil::instantiateStencils(
5397 cx, input.get(), stencil, output.get())) {
5398 return false;
5401 Rooted<ModuleObject*> modObject(cx, output.get().module);
5402 Rooted<ShellModuleObjectWrapper*> wrapper(
5403 cx, ShellModuleObjectWrapper::create(cx, modObject));
5404 if (!wrapper) {
5405 return false;
5407 args.rval().setObject(*wrapper);
5408 return true;
5411 static bool RegisterModule(JSContext* cx, unsigned argc, Value* vp) {
5412 CallArgs args = CallArgsFromVp(argc, vp);
5413 if (!args.requireAtLeast(cx, "registerModule", 2)) {
5414 return false;
5417 if (!args[0].isString()) {
5418 const char* typeName = InformalValueTypeName(args[0]);
5419 JS_ReportErrorASCII(cx, "expected string, got %s", typeName);
5420 return false;
5423 if (!args[1].isObject() ||
5424 !args[1].toObject().is<ShellModuleObjectWrapper>()) {
5425 const char* typeName = InformalValueTypeName(args[1]);
5426 JS_ReportErrorASCII(cx, "expected module, got %s", typeName);
5427 return false;
5430 ShellContext* sc = GetShellContext(cx);
5431 Rooted<ModuleObject*> module(
5432 cx, args[1].toObject().as<ShellModuleObjectWrapper>().get());
5434 Rooted<JSAtom*> specifier(cx, AtomizeString(cx, args[0].toString()));
5435 if (!specifier) {
5436 return false;
5439 RootedObject moduleRequest(
5440 cx, ModuleRequestObject::create(cx, specifier, nullptr));
5441 if (!moduleRequest) {
5442 return false;
5445 if (!sc->moduleLoader->registerTestModule(cx, moduleRequest, module)) {
5446 return false;
5449 Rooted<ShellModuleObjectWrapper*> wrapper(
5450 cx, ShellModuleObjectWrapper::create(cx, module));
5451 if (!wrapper) {
5452 return false;
5454 args.rval().setObject(*wrapper);
5455 return true;
5458 static bool ClearModules(JSContext* cx, unsigned argc, Value* vp) {
5459 CallArgs args = CallArgsFromVp(argc, vp);
5460 ShellContext* sc = GetShellContext(cx);
5461 sc->moduleLoader->clearModules(cx);
5462 args.rval().setUndefined();
5463 return true;
5466 static bool ModuleLink(JSContext* cx, unsigned argc, Value* vp) {
5467 CallArgs args = CallArgsFromVp(argc, vp);
5469 if (args.length() != 1 || !args[0].isObject()) {
5470 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_ARGS,
5471 "moduleLink");
5472 return false;
5475 RootedObject object(cx, UncheckedUnwrap(&args[0].toObject()));
5476 if (!object->is<ShellModuleObjectWrapper>()) {
5477 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_ARGS,
5478 "moduleLink");
5479 return false;
5482 AutoRealm ar(cx, object);
5484 Rooted<ModuleObject*> module(cx,
5485 object->as<ShellModuleObjectWrapper>().get());
5486 if (!js::ModuleLink(cx, module)) {
5487 return false;
5490 args.rval().setUndefined();
5491 return true;
5494 static bool ModuleEvaluate(JSContext* cx, unsigned argc, Value* vp) {
5495 CallArgs args = CallArgsFromVp(argc, vp);
5497 if (args.length() != 1 || !args[0].isObject()) {
5498 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_ARGS,
5499 "moduleEvaluate");
5500 return false;
5503 RootedObject object(cx, UncheckedUnwrap(&args[0].toObject()));
5504 if (!object->is<ShellModuleObjectWrapper>()) {
5505 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_ARGS,
5506 "moduleEvaluate");
5507 return false;
5511 AutoRealm ar(cx, object);
5513 Rooted<ModuleObject*> module(cx,
5514 object->as<ShellModuleObjectWrapper>().get());
5515 if (!js::ModuleEvaluate(cx, module, args.rval())) {
5516 return false;
5520 return JS_WrapValue(cx, args.rval());
5523 static ModuleEnvironmentObject* GetModuleInitialEnvironment(
5524 JSContext* cx, Handle<ModuleObject*> module) {
5525 // Use the initial environment so that tests can check bindings exists
5526 // before they have been instantiated.
5527 Rooted<ModuleEnvironmentObject*> env(cx, &module->initialEnvironment());
5528 MOZ_ASSERT(env);
5529 return env;
5532 static bool GetModuleEnvironmentNames(JSContext* cx, unsigned argc, Value* vp) {
5533 CallArgs args = CallArgsFromVp(argc, vp);
5534 if (args.length() != 1) {
5535 JS_ReportErrorASCII(cx, "Wrong number of arguments");
5536 return false;
5539 if (!args[0].isObject() ||
5540 !args[0].toObject().is<ShellModuleObjectWrapper>()) {
5541 JS_ReportErrorASCII(cx,
5542 "First argument should be a ShellModuleObjectWrapper");
5543 return false;
5546 Rooted<ModuleObject*> module(
5547 cx, args[0].toObject().as<ShellModuleObjectWrapper>().get());
5548 if (module->hadEvaluationError()) {
5549 JS_ReportErrorASCII(cx, "Module environment unavailable");
5550 return false;
5553 Rooted<ModuleEnvironmentObject*> env(cx,
5554 GetModuleInitialEnvironment(cx, module));
5555 Rooted<IdVector> ids(cx, IdVector(cx));
5556 if (!JS_Enumerate(cx, env, &ids)) {
5557 return false;
5560 // The "*namespace*" binding is a detail of current implementation so hide
5561 // it to give stable results in tests.
5562 ids.eraseIfEqual(NameToId(cx->names().star_namespace_star_));
5564 uint32_t length = ids.length();
5565 Rooted<ArrayObject*> array(cx, NewDenseFullyAllocatedArray(cx, length));
5566 if (!array) {
5567 return false;
5570 array->setDenseInitializedLength(length);
5571 for (uint32_t i = 0; i < length; i++) {
5572 array->initDenseElement(i, StringValue(ids[i].toString()));
5575 args.rval().setObject(*array);
5576 return true;
5579 static bool GetModuleEnvironmentValue(JSContext* cx, unsigned argc, Value* vp) {
5580 CallArgs args = CallArgsFromVp(argc, vp);
5581 if (args.length() != 2) {
5582 JS_ReportErrorASCII(cx, "Wrong number of arguments");
5583 return false;
5586 if (!args[0].isObject() ||
5587 !args[0].toObject().is<ShellModuleObjectWrapper>()) {
5588 JS_ReportErrorASCII(cx,
5589 "First argument should be a ShellModuleObjectWrapper");
5590 return false;
5593 if (!args[1].isString()) {
5594 JS_ReportErrorASCII(cx, "Second argument should be a string");
5595 return false;
5598 Rooted<ModuleObject*> module(
5599 cx, args[0].toObject().as<ShellModuleObjectWrapper>().get());
5600 if (module->hadEvaluationError()) {
5601 JS_ReportErrorASCII(cx, "Module environment unavailable");
5602 return false;
5605 Rooted<ModuleEnvironmentObject*> env(cx,
5606 GetModuleInitialEnvironment(cx, module));
5607 RootedString name(cx, args[1].toString());
5608 RootedId id(cx);
5609 if (!JS_StringToId(cx, name, &id)) {
5610 return false;
5613 if (!GetProperty(cx, env, env, id, args.rval())) {
5614 return false;
5617 if (args.rval().isMagic(JS_UNINITIALIZED_LEXICAL)) {
5618 ReportRuntimeLexicalError(cx, JSMSG_UNINITIALIZED_LEXICAL, id);
5619 return false;
5622 return true;
5625 enum class DumpType {
5626 ParseNode,
5627 Stencil,
5630 template <typename Unit>
5631 static bool DumpAST(JSContext* cx, const JS::ReadOnlyCompileOptions& options,
5632 const Unit* units, size_t length,
5633 js::frontend::CompilationState& compilationState,
5634 js::frontend::ParseGoal goal) {
5635 using namespace js::frontend;
5637 AutoReportFrontendContext fc(cx);
5638 Parser<FullParseHandler, Unit> parser(&fc, options, units, length,
5639 /* foldConstants = */ false,
5640 compilationState,
5641 /* syntaxParser = */ nullptr);
5642 if (!parser.checkOptions()) {
5643 return false;
5646 // Emplace the top-level stencil.
5647 MOZ_ASSERT(compilationState.scriptData.length() ==
5648 CompilationStencil::TopLevelIndex);
5649 if (!compilationState.appendScriptStencilAndData(&fc)) {
5650 return false;
5653 js::frontend::ParseNode* pn;
5654 if (goal == frontend::ParseGoal::Script) {
5655 pn = parser.parse().unwrapOr(nullptr);
5656 } else {
5657 ModuleBuilder builder(&fc, &parser);
5659 SourceExtent extent = SourceExtent::makeGlobalExtent(length);
5660 ModuleSharedContext modulesc(&fc, options, builder, extent);
5661 pn = parser.moduleBody(&modulesc).unwrapOr(nullptr);
5664 if (!pn) {
5665 return false;
5668 #if defined(DEBUG)
5669 js::Fprinter out(stderr);
5670 DumpParseTree(&parser, pn, out);
5671 #endif
5673 return true;
5676 template <typename Unit>
5677 [[nodiscard]] static bool DumpStencil(JSContext* cx,
5678 const JS::ReadOnlyCompileOptions& options,
5679 const Unit* units, size_t length,
5680 js::frontend::ParseGoal goal) {
5681 Rooted<frontend::CompilationInput> input(cx,
5682 frontend::CompilationInput(options));
5684 JS::SourceText<Unit> srcBuf;
5685 if (!srcBuf.init(cx, units, length, JS::SourceOwnership::Borrowed)) {
5686 return false;
5689 AutoReportFrontendContext fc(cx);
5690 js::frontend::NoScopeBindingCache scopeCache;
5691 UniquePtr<frontend::ExtensibleCompilationStencil> stencil;
5692 if (goal == frontend::ParseGoal::Script) {
5693 stencil = frontend::CompileGlobalScriptToExtensibleStencil(
5694 cx, &fc, input.get(), &scopeCache, srcBuf, ScopeKind::Global);
5695 } else {
5696 stencil = frontend::ParseModuleToExtensibleStencil(
5697 cx, &fc, cx->tempLifoAlloc(), input.get(), &scopeCache, srcBuf);
5700 if (!stencil) {
5701 return false;
5704 #if defined(DEBUG) || defined(JS_JITSPEW)
5705 stencil->dump();
5706 #endif
5708 return true;
5711 static bool FrontendTest(JSContext* cx, unsigned argc, Value* vp,
5712 const char* funcName, DumpType dumpType) {
5713 using namespace js::frontend;
5715 CallArgs args = CallArgsFromVp(argc, vp);
5717 if (!args.requireAtLeast(cx, funcName, 1)) {
5718 return false;
5720 if (!args[0].isString()) {
5721 const char* typeName = InformalValueTypeName(args[0]);
5722 JS_ReportErrorASCII(cx, "expected string to parse, got %s", typeName);
5723 return false;
5726 frontend::ParseGoal goal = frontend::ParseGoal::Script;
5727 #ifdef JS_ENABLE_SMOOSH
5728 bool smoosh = false;
5729 #endif
5731 CompileOptions options(cx);
5732 options.setIntroductionType("js shell parse")
5733 .setFileAndLine("<string>", 1)
5734 .setIsRunOnce(true)
5735 .setNoScriptRval(true);
5737 if (args.length() >= 2) {
5738 if (!args[1].isObject()) {
5739 JS_ReportErrorASCII(cx, "The 2nd argument must be an object");
5740 return false;
5743 RootedObject objOptions(cx, &args[1].toObject());
5745 RootedValue optionModule(cx);
5746 if (!JS_GetProperty(cx, objOptions, "module", &optionModule)) {
5747 return false;
5750 if (optionModule.isBoolean()) {
5751 if (optionModule.toBoolean()) {
5752 goal = frontend::ParseGoal::Module;
5754 } else if (!optionModule.isUndefined()) {
5755 const char* typeName = InformalValueTypeName(optionModule);
5756 JS_ReportErrorASCII(cx, "option `module` should be a boolean, got %s",
5757 typeName);
5758 return false;
5760 if (!js::ParseCompileOptions(cx, options, objOptions, nullptr)) {
5761 return false;
5764 #ifdef JS_ENABLE_SMOOSH
5765 bool found = false;
5766 if (!JS_HasProperty(cx, objOptions, "rustFrontend", &found)) {
5767 return false;
5769 if (found) {
5770 JS_ReportErrorASCII(cx, "'rustFrontend' option is renamed to 'smoosh'");
5771 return false;
5774 RootedValue optionSmoosh(cx);
5775 if (!JS_GetProperty(cx, objOptions, "smoosh", &optionSmoosh)) {
5776 return false;
5779 if (optionSmoosh.isBoolean()) {
5780 smoosh = optionSmoosh.toBoolean();
5781 } else if (!optionSmoosh.isUndefined()) {
5782 const char* typeName = InformalValueTypeName(optionSmoosh);
5783 JS_ReportErrorASCII(cx, "option `smoosh` should be a boolean, got %s",
5784 typeName);
5785 return false;
5787 #endif // JS_ENABLE_SMOOSH
5790 JSString* scriptContents = args[0].toString();
5791 Rooted<JSLinearString*> linearString(cx, scriptContents->ensureLinear(cx));
5792 if (!linearString) {
5793 return false;
5796 bool isAscii = false;
5797 if (linearString->hasLatin1Chars()) {
5798 JS::AutoCheckCannotGC nogc;
5799 isAscii = JS::StringIsASCII(mozilla::Span(
5800 reinterpret_cast<const char*>(linearString->latin1Chars(nogc)),
5801 linearString->length()));
5804 AutoStableStringChars stableChars(cx);
5805 if (isAscii) {
5806 if (!stableChars.init(cx, scriptContents)) {
5807 return false;
5809 MOZ_ASSERT(stableChars.isLatin1());
5810 } else {
5811 if (!stableChars.initTwoByte(cx, scriptContents)) {
5812 return false;
5816 size_t length = scriptContents->length();
5817 #ifdef JS_ENABLE_SMOOSH
5818 if (dumpType == DumpType::ParseNode) {
5819 if (smoosh) {
5820 if (isAscii) {
5821 const Latin1Char* chars = stableChars.latin1Range().begin().get();
5823 if (goal == frontend::ParseGoal::Script) {
5824 if (!SmooshParseScript(cx, chars, length)) {
5825 return false;
5827 } else {
5828 if (!SmooshParseModule(cx, chars, length)) {
5829 return false;
5832 args.rval().setUndefined();
5833 return true;
5835 JS_ReportErrorASCII(cx,
5836 "SmooshMonkey does not support non-ASCII chars yet");
5837 return false;
5840 #endif // JS_ENABLE_SMOOSH
5842 if (goal == frontend::ParseGoal::Module) {
5843 // See frontend::CompileModule.
5844 options.setForceStrictMode();
5845 options.allowHTMLComments = false;
5848 if (dumpType == DumpType::Stencil) {
5849 #ifdef JS_ENABLE_SMOOSH
5850 if (smoosh) {
5851 if (isAscii) {
5852 if (goal == frontend::ParseGoal::Script) {
5853 const Latin1Char* latin1 = stableChars.latin1Range().begin().get();
5854 auto utf8 = reinterpret_cast<const mozilla::Utf8Unit*>(latin1);
5855 JS::SourceText<Utf8Unit> srcBuf;
5856 if (!srcBuf.init(cx, utf8, length, JS::SourceOwnership::Borrowed)) {
5857 return false;
5860 AutoReportFrontendContext fc(cx);
5861 Rooted<frontend::CompilationInput> input(
5862 cx, frontend::CompilationInput(options));
5863 UniquePtr<frontend::ExtensibleCompilationStencil> stencil;
5864 if (!Smoosh::tryCompileGlobalScriptToExtensibleStencil(
5865 cx, &fc, input.get(), srcBuf, stencil)) {
5866 return false;
5868 if (!stencil) {
5869 fc.clearAutoReport();
5870 JS_ReportErrorASCII(cx, "SmooshMonkey failed to parse");
5871 return false;
5874 # ifdef DEBUG
5876 frontend::BorrowingCompilationStencil borrowingStencil(*stencil);
5877 borrowingStencil.dump();
5879 # endif
5880 } else {
5881 JS_ReportErrorASCII(cx,
5882 "SmooshMonkey does not support module stencil");
5883 return false;
5885 args.rval().setUndefined();
5886 return true;
5888 JS_ReportErrorASCII(cx,
5889 "SmooshMonkey does not support non-ASCII chars yet");
5890 return false;
5892 #endif // JS_ENABLE_SMOOSH
5894 if (isAscii) {
5895 const Latin1Char* latin1 = stableChars.latin1Range().begin().get();
5896 auto utf8 = reinterpret_cast<const mozilla::Utf8Unit*>(latin1);
5897 if (!DumpStencil<mozilla::Utf8Unit>(cx, options, utf8, length, goal)) {
5898 return false;
5900 } else {
5901 MOZ_ASSERT(stableChars.isTwoByte());
5902 const char16_t* chars = stableChars.twoByteRange().begin().get();
5903 if (!DumpStencil<char16_t>(cx, options, chars, length, goal)) {
5904 return false;
5908 args.rval().setUndefined();
5909 return true;
5912 AutoReportFrontendContext fc(cx);
5913 Rooted<frontend::CompilationInput> input(cx,
5914 frontend::CompilationInput(options));
5915 if (goal == frontend::ParseGoal::Script) {
5916 if (!input.get().initForGlobal(&fc)) {
5917 return false;
5919 } else {
5920 if (!input.get().initForModule(&fc)) {
5921 return false;
5925 LifoAllocScope allocScope(&cx->tempLifoAlloc());
5926 frontend::NoScopeBindingCache scopeCache;
5927 frontend::CompilationState compilationState(&fc, allocScope, input.get());
5928 if (!compilationState.init(&fc, &scopeCache)) {
5929 return false;
5932 if (isAscii) {
5933 const Latin1Char* latin1 = stableChars.latin1Range().begin().get();
5934 auto utf8 = reinterpret_cast<const mozilla::Utf8Unit*>(latin1);
5935 if (!DumpAST<mozilla::Utf8Unit>(cx, options, utf8, length, compilationState,
5936 goal)) {
5937 return false;
5939 } else {
5940 MOZ_ASSERT(stableChars.isTwoByte());
5941 const char16_t* chars = stableChars.twoByteRange().begin().get();
5942 if (!DumpAST<char16_t>(cx, options, chars, length, compilationState,
5943 goal)) {
5944 return false;
5947 args.rval().setUndefined();
5948 return true;
5951 static bool DumpStencil(JSContext* cx, unsigned argc, Value* vp) {
5952 return FrontendTest(cx, argc, vp, "dumpStencil", DumpType::Stencil);
5955 static bool Parse(JSContext* cx, unsigned argc, Value* vp) {
5956 // Parse returns local scope information with variables ordered
5957 // differently, depending on the underlying JIT implementation.
5958 if (js::SupportDifferentialTesting()) {
5959 JS_ReportErrorASCII(cx,
5960 "Function not available in differential testing mode.");
5961 return false;
5964 return FrontendTest(cx, argc, vp, "parse", DumpType::ParseNode);
5967 static bool SyntaxParse(JSContext* cx, unsigned argc, Value* vp) {
5968 using namespace js::frontend;
5970 CallArgs args = CallArgsFromVp(argc, vp);
5972 if (!args.requireAtLeast(cx, "syntaxParse", 1)) {
5973 return false;
5975 if (!args[0].isString()) {
5976 const char* typeName = InformalValueTypeName(args[0]);
5977 JS_ReportErrorASCII(cx, "expected string to parse, got %s", typeName);
5978 return false;
5981 JSString* scriptContents = args[0].toString();
5983 CompileOptions options(cx);
5984 options.setIntroductionType("js shell syntaxParse")
5985 .setFileAndLine("<string>", 1);
5987 AutoStableStringChars stableChars(cx);
5988 if (!stableChars.initTwoByte(cx, scriptContents)) {
5989 return false;
5992 const char16_t* chars = stableChars.twoByteRange().begin().get();
5993 size_t length = scriptContents->length();
5995 AutoReportFrontendContext fc(cx);
5996 Rooted<frontend::CompilationInput> input(cx,
5997 frontend::CompilationInput(options));
5998 if (!input.get().initForGlobal(&fc)) {
5999 return false;
6002 LifoAllocScope allocScope(&cx->tempLifoAlloc());
6003 frontend::NoScopeBindingCache scopeCache;
6004 frontend::CompilationState compilationState(&fc, allocScope, input.get());
6005 if (!compilationState.init(&fc, &scopeCache)) {
6006 return false;
6009 Parser<frontend::SyntaxParseHandler, char16_t> parser(
6010 &fc, options, chars, length,
6011 /* foldConstants = */ false, compilationState,
6012 /* syntaxParser = */ nullptr);
6013 if (!parser.checkOptions()) {
6014 return false;
6017 bool succeeded = parser.parse().isOk();
6018 if (fc.hadErrors()) {
6019 return false;
6022 if (!succeeded && !parser.hadAbortedSyntaxParse()) {
6023 // If no exception is posted, either there was an OOM or a language
6024 // feature unhandled by the syntax parser was encountered.
6025 MOZ_ASSERT(fc.hadOutOfMemory());
6026 return false;
6029 args.rval().setBoolean(succeeded);
6030 return true;
6033 static bool OffThreadCompileToStencil(JSContext* cx, unsigned argc, Value* vp) {
6034 if (!CanUseExtraThreads()) {
6035 JS_ReportErrorASCII(
6036 cx, "Can't use offThreadCompileToStencil with --no-threads");
6037 return false;
6040 CallArgs args = CallArgsFromVp(argc, vp);
6042 if (!args.requireAtLeast(cx, "offThreadCompileToStencil", 1)) {
6043 return false;
6045 if (!args[0].isString()) {
6046 const char* typeName = InformalValueTypeName(args[0]);
6047 JS_ReportErrorASCII(cx, "expected string to parse, got %s", typeName);
6048 return false;
6051 UniqueChars fileNameBytes;
6052 CompileOptions options(cx);
6053 options.setIntroductionType("js shell offThreadCompileToStencil")
6054 .setFileAndLine("<string>", 1);
6056 if (args.length() >= 2) {
6057 if (!args[1].isObject()) {
6058 JS_ReportErrorASCII(
6059 cx, "offThreadCompileToStencil: The 2nd argument must be an object");
6060 return false;
6063 // Offthread compilation requires that the debug metadata be set when the
6064 // script is collected from offthread, rather than when compiled.
6065 RootedObject opts(cx, &args[1].toObject());
6066 if (!js::ParseCompileOptions(cx, options, opts, &fileNameBytes)) {
6067 return false;
6071 // This option setting must override whatever the caller requested.
6072 options.setIsRunOnce(true);
6074 JSString* scriptContents = args[0].toString();
6075 AutoStableStringChars stableChars(cx);
6076 if (!stableChars.initTwoByte(cx, scriptContents)) {
6077 return false;
6080 size_t length = scriptContents->length();
6081 const char16_t* chars = stableChars.twoByteChars();
6083 // Make sure we own the string's chars, so that they are not freed before
6084 // the compilation is finished.
6085 UniqueTwoByteChars ownedChars;
6086 if (stableChars.maybeGiveOwnershipToCaller()) {
6087 ownedChars.reset(const_cast<char16_t*>(chars));
6088 } else {
6089 ownedChars.reset(cx->pod_malloc<char16_t>(length));
6090 if (!ownedChars) {
6091 return false;
6094 mozilla::PodCopy(ownedChars.get(), chars, length);
6097 if (!cx->runtime()->canUseParallelParsing() || !js::CanUseExtraThreads()) {
6098 JS_ReportErrorASCII(cx, "cannot compile code on helper thread");
6099 return false;
6102 JS::SourceText<char16_t> srcBuf;
6103 if (!srcBuf.init(cx, std::move(ownedChars), length)) {
6104 return false;
6107 OffThreadJob* job = NewOffThreadJob(cx, OffThreadJob::Kind::CompileScript,
6108 options, std::move(srcBuf));
6109 if (!job) {
6110 return false;
6113 if (!job->dispatch()) {
6114 ReportOutOfMemory(cx);
6115 DeleteOffThreadJob(cx, job);
6116 return false;
6119 args.rval().setInt32(job->id);
6120 return true;
6123 static bool FinishOffThreadStencil(JSContext* cx, unsigned argc, Value* vp) {
6124 CallArgs args = CallArgsFromVp(argc, vp);
6126 OffThreadJob* job = LookupOffThreadJobForArgs(cx, args, 0);
6127 if (!job) {
6128 return false;
6131 job->waitUntilDone();
6133 RefPtr<JS::Stencil> stencil = job->stealStencil(cx);
6134 DeleteOffThreadJob(cx, job);
6135 if (!stencil) {
6136 return false;
6138 RootedObject stencilObj(cx,
6139 js::StencilObject::create(cx, std::move(stencil)));
6140 if (!stencilObj) {
6141 return false;
6144 args.rval().setObject(*stencilObj);
6145 return true;
6148 static bool OffThreadCompileModuleToStencil(JSContext* cx, unsigned argc,
6149 Value* vp) {
6150 CallArgs args = CallArgsFromVp(argc, vp);
6152 if (!args.requireAtLeast(cx, "offThreadCompileModuleToStencil", 1)) {
6153 return false;
6155 if (!args[0].isString()) {
6156 const char* typeName = InformalValueTypeName(args[0]);
6157 JS_ReportErrorASCII(cx, "expected string to parse, got %s", typeName);
6158 return false;
6161 UniqueChars fileNameBytes;
6162 CompileOptions options(cx);
6163 options.setIntroductionType("js shell offThreadCompileModuleToStencil")
6164 .setFileAndLine("<string>", 1);
6166 if (args.length() >= 2) {
6167 if (!args[1].isObject()) {
6168 JS_ReportErrorASCII(cx,
6169 "offThreadCompileModuleToStencil: The 2nd argument "
6170 "must be an object");
6171 return false;
6174 // Offthread compilation requires that the debug metadata be set when the
6175 // script is collected from offthread, rather than when compiled.
6176 RootedObject opts(cx, &args[1].toObject());
6177 if (!js::ParseCompileOptions(cx, options, opts, &fileNameBytes)) {
6178 return false;
6182 options.setIsRunOnce(true).setSourceIsLazy(false);
6184 JSString* scriptContents = args[0].toString();
6185 AutoStableStringChars stableChars(cx);
6186 if (!stableChars.initTwoByte(cx, scriptContents)) {
6187 return false;
6190 size_t length = scriptContents->length();
6191 const char16_t* chars = stableChars.twoByteChars();
6193 // Make sure we own the string's chars, so that they are not freed before
6194 // the compilation is finished.
6195 UniqueTwoByteChars ownedChars;
6196 if (stableChars.maybeGiveOwnershipToCaller()) {
6197 ownedChars.reset(const_cast<char16_t*>(chars));
6198 } else {
6199 ownedChars.reset(cx->pod_malloc<char16_t>(length));
6200 if (!ownedChars) {
6201 return false;
6204 mozilla::PodCopy(ownedChars.get(), chars, length);
6207 if (!cx->runtime()->canUseParallelParsing() || !js::CanUseExtraThreads()) {
6208 JS_ReportErrorASCII(cx, "cannot compile code on worker thread");
6209 return false;
6212 JS::SourceText<char16_t> srcBuf;
6213 if (!srcBuf.init(cx, std::move(ownedChars), length)) {
6214 return false;
6217 OffThreadJob* job = NewOffThreadJob(cx, OffThreadJob::Kind::CompileModule,
6218 options, std::move(srcBuf));
6219 if (!job) {
6220 return false;
6223 if (!job->dispatch()) {
6224 ReportOutOfMemory(cx);
6225 DeleteOffThreadJob(cx, job);
6226 return false;
6229 args.rval().setInt32(job->id);
6230 return true;
6233 static bool OffThreadDecodeStencil(JSContext* cx, unsigned argc, Value* vp) {
6234 if (!CanUseExtraThreads()) {
6235 JS_ReportErrorASCII(cx,
6236 "Can't use offThreadDecodeStencil with --no-threads");
6237 return false;
6240 CallArgs args = CallArgsFromVp(argc, vp);
6242 if (!args.requireAtLeast(cx, "offThreadDecodeStencil", 1)) {
6243 return false;
6245 if (!args[0].isObject() || !CacheEntry_isCacheEntry(&args[0].toObject())) {
6246 const char* typeName = InformalValueTypeName(args[0]);
6247 JS_ReportErrorASCII(cx, "expected cache entry, got %s", typeName);
6248 return false;
6250 RootedObject cacheEntry(cx, &args[0].toObject());
6252 UniqueChars fileNameBytes;
6253 CompileOptions options(cx);
6254 options.setIntroductionType("js shell offThreadDecodeStencil")
6255 .setFileAndLine("<string>", 1);
6257 if (args.length() >= 2) {
6258 if (!args[1].isObject()) {
6259 JS_ReportErrorASCII(
6260 cx, "offThreadDecodeStencil: The 2nd argument must be an object");
6261 return false;
6264 RootedObject opts(cx, &args[1].toObject());
6265 if (!js::ParseCompileOptions(cx, options, opts, &fileNameBytes)) {
6266 return false;
6270 // This option setting must override whatever the caller requested, and
6271 // this should match `Evaluate` that encodes the script.
6272 options.setIsRunOnce(false);
6274 JS::TranscodeBuffer loadBuffer;
6275 size_t loadLength = 0;
6276 uint8_t* loadData = nullptr;
6277 loadData = CacheEntry_getBytecode(cx, cacheEntry, &loadLength);
6278 if (!loadData) {
6279 return false;
6281 if (!loadBuffer.append(loadData, loadLength)) {
6282 JS_ReportOutOfMemory(cx);
6283 return false;
6286 if (!cx->runtime()->canUseParallelParsing() || !js::CanUseExtraThreads()) {
6287 JS_ReportErrorASCII(cx, "cannot compile code on worker thread");
6288 return false;
6291 OffThreadJob* job = NewOffThreadJob(cx, OffThreadJob::Kind::Decode, options,
6292 std::move(loadBuffer));
6293 if (!job) {
6294 return false;
6297 if (!job->dispatch()) {
6298 ReportOutOfMemory(cx);
6299 DeleteOffThreadJob(cx, job);
6300 return false;
6303 args.rval().setInt32(job->id);
6304 return true;
6307 class AutoCStringVector {
6308 Vector<char*> argv_;
6310 public:
6311 explicit AutoCStringVector(JSContext* cx) : argv_(cx) {}
6312 ~AutoCStringVector() {
6313 for (size_t i = 0; i < argv_.length(); i++) {
6314 js_free(argv_[i]);
6317 bool append(UniqueChars&& arg) {
6318 if (!argv_.append(arg.get())) {
6319 return false;
6322 // Now owned by this vector.
6323 (void)arg.release();
6324 return true;
6326 char* const* get() const { return argv_.begin(); }
6327 size_t length() const { return argv_.length(); }
6328 char* operator[](size_t i) const { return argv_[i]; }
6329 void replace(size_t i, UniqueChars arg) {
6330 js_free(argv_[i]);
6331 argv_[i] = arg.release();
6335 #if defined(XP_WIN)
6336 static bool EscapeForShell(JSContext* cx, AutoCStringVector& argv) {
6337 // Windows will break arguments in argv by various spaces, so we wrap each
6338 // argument in quotes and escape quotes within. Even with quotes, \ will be
6339 // treated like an escape character, so inflate each \ to \\.
6341 for (size_t i = 0; i < argv.length(); i++) {
6342 if (!argv[i]) {
6343 continue;
6346 size_t newLen = 3; // quotes before and after and null-terminator
6347 for (char* p = argv[i]; *p; p++) {
6348 newLen++;
6349 if (*p == '\"' || *p == '\\') {
6350 newLen++;
6354 auto escaped = cx->make_pod_array<char>(newLen);
6355 if (!escaped) {
6356 return false;
6359 char* src = argv[i];
6360 char* dst = escaped.get();
6361 *dst++ = '\"';
6362 while (*src) {
6363 if (*src == '\"' || *src == '\\') {
6364 *dst++ = '\\';
6366 *dst++ = *src++;
6368 *dst++ = '\"';
6369 *dst++ = '\0';
6370 MOZ_ASSERT(escaped.get() + newLen == dst);
6372 argv.replace(i, std::move(escaped));
6374 return true;
6376 #endif
6378 #ifndef __wasi__
6379 static bool ReadAll(int fd, wasm::Bytes* bytes) {
6380 size_t lastLength = bytes->length();
6381 while (true) {
6382 static const int ChunkSize = 64 * 1024;
6383 if (!bytes->growBy(ChunkSize)) {
6384 return false;
6387 intptr_t readCount;
6388 while (true) {
6389 readCount = read(fd, bytes->begin() + lastLength, ChunkSize);
6390 if (readCount >= 0) {
6391 break;
6393 if (errno != EINTR) {
6394 return false;
6398 if (readCount < ChunkSize) {
6399 bytes->shrinkTo(lastLength + readCount);
6400 if (readCount == 0) {
6401 return true;
6405 lastLength = bytes->length();
6409 static bool WriteAll(int fd, const uint8_t* bytes, size_t length) {
6410 while (length > 0) {
6411 int written = write(fd, bytes, length);
6412 if (written < 0) {
6413 if (errno == EINTR) {
6414 continue;
6416 return false;
6418 MOZ_ASSERT(unsigned(written) <= length);
6419 length -= written;
6420 bytes += written;
6423 return true;
6426 class AutoPipe {
6427 int fds_[2];
6429 public:
6430 AutoPipe() {
6431 fds_[0] = -1;
6432 fds_[1] = -1;
6435 ~AutoPipe() {
6436 if (fds_[0] != -1) {
6437 close(fds_[0]);
6439 if (fds_[1] != -1) {
6440 close(fds_[1]);
6444 bool init() {
6445 # ifdef XP_WIN
6446 return !_pipe(fds_, 4096, O_BINARY);
6447 # else
6448 return !pipe(fds_);
6449 # endif
6452 int reader() const {
6453 MOZ_ASSERT(fds_[0] != -1);
6454 return fds_[0];
6457 int writer() const {
6458 MOZ_ASSERT(fds_[1] != -1);
6459 return fds_[1];
6462 void closeReader() {
6463 MOZ_ASSERT(fds_[0] != -1);
6464 close(fds_[0]);
6465 fds_[0] = -1;
6468 void closeWriter() {
6469 MOZ_ASSERT(fds_[1] != -1);
6470 close(fds_[1]);
6471 fds_[1] = -1;
6474 #endif // __wasi__
6476 int shell::sArgc;
6477 char** shell::sArgv;
6479 #ifndef __wasi__
6480 static const char sWasmCompileAndSerializeFlag[] =
6481 "--wasm-compile-and-serialize";
6482 static Vector<const char*, 5, js::SystemAllocPolicy> sCompilerProcessFlags;
6484 static bool CompileAndSerializeInSeparateProcess(JSContext* cx,
6485 const uint8_t* bytecode,
6486 size_t bytecodeLength,
6487 wasm::Bytes* serialized) {
6488 AutoPipe stdIn, stdOut;
6489 if (!stdIn.init() || !stdOut.init()) {
6490 return false;
6493 AutoCStringVector argv(cx);
6495 UniqueChars argv0 = DuplicateString(cx, sArgv[0]);
6496 if (!argv0 || !argv.append(std::move(argv0))) {
6497 return false;
6500 // Put compiler flags first since they must precede the non-option
6501 // file-descriptor args (passed on Windows, below).
6502 for (unsigned i = 0; i < sCompilerProcessFlags.length(); i++) {
6503 UniqueChars flags = DuplicateString(cx, sCompilerProcessFlags[i]);
6504 if (!flags || !argv.append(std::move(flags))) {
6505 return false;
6509 UniqueChars arg;
6511 arg = DuplicateString(sWasmCompileAndSerializeFlag);
6512 if (!arg || !argv.append(std::move(arg))) {
6513 return false;
6516 # ifdef XP_WIN
6517 // The spawned process will have all the stdIn/stdOut file handles open, but
6518 // without the power of fork, we need some other way to communicate the
6519 // integer fd values so we encode them in argv and WasmCompileAndSerialize()
6520 // has a matching #ifdef XP_WIN to parse them out. Communicate both ends of
6521 // both pipes so the child process can closed the unused ends.
6523 arg = JS_smprintf("%d", stdIn.reader());
6524 if (!arg || !argv.append(std::move(arg))) {
6525 return false;
6528 arg = JS_smprintf("%d", stdIn.writer());
6529 if (!arg || !argv.append(std::move(arg))) {
6530 return false;
6533 arg = JS_smprintf("%d", stdOut.reader());
6534 if (!arg || !argv.append(std::move(arg))) {
6535 return false;
6538 arg = JS_smprintf("%d", stdOut.writer());
6539 if (!arg || !argv.append(std::move(arg))) {
6540 return false;
6542 # endif
6544 // Required by both _spawnv and exec.
6545 if (!argv.append(nullptr)) {
6546 return false;
6549 # ifdef XP_WIN
6550 if (!EscapeForShell(cx, argv)) {
6551 return false;
6554 int childPid = _spawnv(P_NOWAIT, sArgv[0], argv.get());
6555 if (childPid == -1) {
6556 return false;
6558 # else
6559 pid_t childPid = fork();
6560 switch (childPid) {
6561 case -1:
6562 return false;
6563 case 0:
6564 // In the child process. Redirect stdin/stdout to the respective ends of
6565 // the pipes. Closing stdIn.writer() is necessary for stdin to hit EOF.
6566 // This case statement must not return before exec() takes over. Rather,
6567 // exit(-1) is used to return failure to the parent process.
6568 if (dup2(stdIn.reader(), STDIN_FILENO) == -1) {
6569 exit(-1);
6571 if (dup2(stdOut.writer(), STDOUT_FILENO) == -1) {
6572 exit(-1);
6574 close(stdIn.reader());
6575 close(stdIn.writer());
6576 close(stdOut.reader());
6577 close(stdOut.writer());
6578 execv(sArgv[0], argv.get());
6579 exit(-1);
6581 # endif
6583 // In the parent process. Closing stdOut.writer() is necessary for
6584 // stdOut.reader() below to hit EOF.
6585 stdIn.closeReader();
6586 stdOut.closeWriter();
6588 if (!WriteAll(stdIn.writer(), bytecode, bytecodeLength)) {
6589 return false;
6592 stdIn.closeWriter();
6594 if (!ReadAll(stdOut.reader(), serialized)) {
6595 return false;
6598 stdOut.closeReader();
6600 int status;
6601 # ifdef XP_WIN
6602 if (_cwait(&status, childPid, WAIT_CHILD) == -1) {
6603 return false;
6605 # else
6606 while (true) {
6607 if (waitpid(childPid, &status, 0) >= 0) {
6608 break;
6610 if (errno != EINTR) {
6611 return false;
6614 # endif
6616 return status == 0;
6619 static bool WasmCompileAndSerialize(JSContext* cx) {
6620 MOZ_ASSERT(wasm::CodeCachingAvailable(cx));
6622 # ifdef XP_WIN
6623 // See CompileAndSerializeInSeparateProcess for why we've had to smuggle
6624 // these fd values through argv. Closing the writing ends is necessary for
6625 // the reading ends to hit EOF.
6626 int flagIndex = 0;
6627 for (; flagIndex < sArgc; flagIndex++) {
6628 if (!strcmp(sArgv[flagIndex], sWasmCompileAndSerializeFlag)) {
6629 break;
6632 MOZ_RELEASE_ASSERT(flagIndex < sArgc);
6634 int fdsIndex = flagIndex + 1;
6635 MOZ_RELEASE_ASSERT(fdsIndex + 4 == sArgc);
6637 int stdInReader = atoi(sArgv[fdsIndex + 0]);
6638 int stdInWriter = atoi(sArgv[fdsIndex + 1]);
6639 int stdOutReader = atoi(sArgv[fdsIndex + 2]);
6640 int stdOutWriter = atoi(sArgv[fdsIndex + 3]);
6642 int stdIn = stdInReader;
6643 close(stdInWriter);
6644 close(stdOutReader);
6645 int stdOut = stdOutWriter;
6646 # else
6647 int stdIn = STDIN_FILENO;
6648 int stdOut = STDOUT_FILENO;
6649 # endif
6651 wasm::MutableBytes bytecode = js_new<wasm::ShareableBytes>();
6652 if (!ReadAll(stdIn, &bytecode->bytes)) {
6653 return false;
6656 wasm::Bytes serialized;
6657 if (!wasm::CompileAndSerialize(cx, *bytecode, &serialized)) {
6658 return false;
6661 if (!WriteAll(stdOut, serialized.begin(), serialized.length())) {
6662 return false;
6665 return true;
6668 static bool WasmCompileInSeparateProcess(JSContext* cx, unsigned argc,
6669 Value* vp) {
6670 if (!wasm::CodeCachingAvailable(cx)) {
6671 JS_ReportErrorASCII(cx, "WebAssembly caching not supported");
6672 return false;
6675 CallArgs args = CallArgsFromVp(argc, vp);
6676 if (!args.requireAtLeast(cx, "wasmCompileInSeparateProcess", 1)) {
6677 return false;
6680 SharedMem<uint8_t*> bytecode;
6681 size_t numBytes;
6682 if (!args[0].isObject() ||
6683 !IsBufferSource(&args[0].toObject(), &bytecode, &numBytes)) {
6684 RootedObject callee(cx, &args.callee());
6685 ReportUsageErrorASCII(cx, callee, "Argument must be a buffer source");
6686 return false;
6689 wasm::Bytes serialized;
6690 if (!CompileAndSerializeInSeparateProcess(cx, bytecode.unwrap(), numBytes,
6691 &serialized)) {
6692 if (!cx->isExceptionPending()) {
6693 JS_ReportErrorASCII(cx, "creating and executing child process");
6695 return false;
6698 RootedObject module(cx);
6699 if (!wasm::DeserializeModule(cx, serialized, &module)) {
6700 return false;
6703 args.rval().setObject(*module);
6704 return true;
6706 #endif // __wasi__
6708 static bool DecompileFunction(JSContext* cx, unsigned argc, Value* vp) {
6709 CallArgs args = CallArgsFromVp(argc, vp);
6710 if (args.length() < 1 || !args[0].isObject() ||
6711 !args[0].toObject().is<JSFunction>()) {
6712 args.rval().setUndefined();
6713 return true;
6715 RootedFunction fun(cx, &args[0].toObject().as<JSFunction>());
6716 JSString* result = JS_DecompileFunction(cx, fun);
6717 if (!result) {
6718 return false;
6720 args.rval().setString(result);
6721 return true;
6724 static bool DecompileThisScript(JSContext* cx, unsigned argc, Value* vp) {
6725 CallArgs args = CallArgsFromVp(argc, vp);
6727 NonBuiltinScriptFrameIter iter(cx);
6728 if (iter.done()) {
6729 args.rval().setString(cx->runtime()->emptyString);
6730 return true;
6734 JSAutoRealm ar(cx, iter.script());
6736 RootedScript script(cx, iter.script());
6737 JSString* result = JS_DecompileScript(cx, script);
6738 if (!result) {
6739 return false;
6742 args.rval().setString(result);
6745 return JS_WrapValue(cx, args.rval());
6748 static bool ValueToSource(JSContext* cx, unsigned argc, Value* vp) {
6749 CallArgs args = CallArgsFromVp(argc, vp);
6751 JSString* str = ValueToSource(cx, args.get(0));
6752 if (!str) {
6753 return false;
6756 args.rval().setString(str);
6757 return true;
6760 static bool ThisFilename(JSContext* cx, unsigned argc, Value* vp) {
6761 CallArgs args = CallArgsFromVp(argc, vp);
6763 JS::AutoFilename filename;
6764 if (!DescribeScriptedCaller(cx, &filename) || !filename.get()) {
6765 args.rval().setString(cx->runtime()->emptyString);
6766 return true;
6769 JSString* str = NewStringCopyUTF8(cx, filename.get());
6770 if (!str) {
6771 return false;
6774 args.rval().setString(str);
6775 return true;
6778 static bool WrapWithProto(JSContext* cx, unsigned argc, Value* vp) {
6779 CallArgs args = CallArgsFromVp(argc, vp);
6780 Value obj = args.get(0);
6781 Value proto = args.get(1);
6782 if (!obj.isObject() || !proto.isObjectOrNull()) {
6783 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
6784 JSSMSG_INVALID_ARGS, "wrapWithProto");
6785 return false;
6788 // Disallow constructing (deeply) nested wrapper chains, to avoid running
6789 // out of stack space in isCallable/isConstructor. See bug 1126105.
6790 if (IsWrapper(&obj.toObject())) {
6791 JS_ReportErrorASCII(cx, "wrapWithProto cannot wrap a wrapper");
6792 return false;
6795 WrapperOptions options(cx);
6796 options.setProto(proto.toObjectOrNull());
6797 JSObject* wrapped = Wrapper::New(cx, &obj.toObject(),
6798 &Wrapper::singletonWithPrototype, options);
6799 if (!wrapped) {
6800 return false;
6803 args.rval().setObject(*wrapped);
6804 return true;
6807 static bool NewGlobal(JSContext* cx, unsigned argc, Value* vp) {
6808 CallArgs args = CallArgsFromVp(argc, vp);
6809 RootedObject callee(cx, &args.callee());
6811 JS::RealmOptions options;
6812 JS::RealmCreationOptions& creationOptions = options.creationOptions();
6813 JS::RealmBehaviors& behaviors = options.behaviors();
6814 ShellGlobalKind kind = ShellGlobalKind::WindowProxy;
6815 bool immutablePrototype = true;
6817 SetStandardRealmOptions(options);
6819 // Default to creating the global in the current compartment unless
6820 // --more-compartments is used.
6821 if (defaultToSameCompartment) {
6822 creationOptions.setExistingCompartment(cx->global());
6823 } else {
6824 creationOptions.setNewCompartmentAndZone();
6827 JS::AutoHoldPrincipals principals(cx);
6829 if (args.length() == 1 && args[0].isObject()) {
6830 RootedObject opts(cx, &args[0].toObject());
6831 RootedValue v(cx);
6833 if (!JS_GetProperty(cx, opts, "invisibleToDebugger", &v)) {
6834 return false;
6836 if (v.isBoolean()) {
6837 creationOptions.setInvisibleToDebugger(v.toBoolean());
6840 if (!JS_GetProperty(cx, opts, "sameZoneAs", &v)) {
6841 return false;
6843 if (v.isObject()) {
6844 creationOptions.setNewCompartmentInExistingZone(
6845 UncheckedUnwrap(&v.toObject()));
6848 if (!JS_GetProperty(cx, opts, "sameCompartmentAs", &v)) {
6849 return false;
6851 if (v.isObject()) {
6852 creationOptions.setExistingCompartment(UncheckedUnwrap(&v.toObject()));
6855 if (!JS_GetProperty(cx, opts, "newCompartment", &v)) {
6856 return false;
6858 if (v.isBoolean()) {
6859 if (v.toBoolean()) {
6860 creationOptions.setNewCompartmentAndZone();
6861 } else {
6862 creationOptions.setExistingCompartment(cx->global());
6866 if (!JS_GetProperty(cx, opts, "discardSource", &v)) {
6867 return false;
6869 if (v.isBoolean()) {
6870 behaviors.setDiscardSource(v.toBoolean());
6873 if (!JS_GetProperty(cx, opts, "useWindowProxy", &v)) {
6874 return false;
6876 if (v.isBoolean()) {
6877 kind = v.toBoolean() ? ShellGlobalKind::WindowProxy
6878 : ShellGlobalKind::GlobalObject;
6881 if (!JS_GetProperty(cx, opts, "immutablePrototype", &v)) {
6882 return false;
6884 if (v.isBoolean()) {
6885 immutablePrototype = v.toBoolean();
6888 if (!JS_GetProperty(cx, opts, "systemPrincipal", &v)) {
6889 return false;
6891 if (v.isBoolean()) {
6892 principals.reset(&ShellPrincipals::fullyTrusted);
6895 if (!JS_GetProperty(cx, opts, "principal", &v)) {
6896 return false;
6898 if (!v.isUndefined()) {
6899 uint32_t bits;
6900 if (!ToUint32(cx, v, &bits)) {
6901 return false;
6903 JSPrincipals* newPrincipals = cx->new_<ShellPrincipals>(bits);
6904 if (!newPrincipals) {
6905 return false;
6907 principals.reset(newPrincipals);
6910 if (!JS_GetProperty(cx, opts, "enableCoopAndCoep", &v)) {
6911 return false;
6913 if (v.isBoolean()) {
6914 creationOptions.setCoopAndCoepEnabled(v.toBoolean());
6917 if (!JS_GetProperty(cx, opts, "freezeBuiltins", &v)) {
6918 return false;
6920 if (v.isBoolean()) {
6921 creationOptions.setFreezeBuiltins(v.toBoolean());
6924 // On the web, the SharedArrayBuffer constructor is not installed as a
6925 // global property in pages that aren't isolated in a separate process (and
6926 // thus can't allow the structured cloning of shared memory). Specify false
6927 // for this option to reproduce this behavior.
6928 if (!JS_GetProperty(cx, opts, "defineSharedArrayBufferConstructor", &v)) {
6929 return false;
6931 if (v.isBoolean()) {
6932 creationOptions.setDefineSharedArrayBufferConstructor(v.toBoolean());
6935 if (!JS_GetProperty(cx, opts, "forceUTC", &v)) {
6936 return false;
6938 if (v.isBoolean()) {
6939 creationOptions.setForceUTC(v.toBoolean());
6942 if (!JS_GetProperty(cx, opts, "alwaysUseFdlibm", &v)) {
6943 return false;
6945 if (v.isBoolean()) {
6946 creationOptions.setAlwaysUseFdlibm(v.toBoolean());
6949 if (!JS_GetProperty(cx, opts, "locale", &v)) {
6950 return false;
6952 if (v.isString()) {
6953 RootedString str(cx, v.toString());
6954 UniqueChars locale = StringToLocale(cx, callee, str);
6955 if (!locale) {
6956 return false;
6958 creationOptions.setLocaleCopyZ(locale.get());
6962 if (!CheckRealmOptions(cx, options, principals.get())) {
6963 return false;
6966 RootedObject global(cx, NewGlobalObject(cx, options, principals.get(), kind,
6967 immutablePrototype));
6968 if (!global) {
6969 return false;
6972 RootedObject wrapped(cx, ToWindowProxyIfWindow(global));
6973 if (!JS_WrapObject(cx, &wrapped)) {
6974 return false;
6977 args.rval().setObject(*wrapped);
6978 return true;
6981 static bool NukeAllCCWs(JSContext* cx, unsigned argc, Value* vp) {
6982 CallArgs args = CallArgsFromVp(argc, vp);
6984 if (args.length() != 0) {
6985 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
6986 JSSMSG_INVALID_ARGS, "nukeAllCCWs");
6987 return false;
6990 NukeCrossCompartmentWrappers(cx, AllCompartments(), cx->realm(),
6991 NukeWindowReferences, NukeAllReferences);
6992 args.rval().setUndefined();
6993 return true;
6996 static bool RecomputeWrappers(JSContext* cx, unsigned argc, Value* vp) {
6997 CallArgs args = CallArgsFromVp(argc, vp);
6999 if (args.length() > 2) {
7000 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
7001 JSSMSG_INVALID_ARGS, "recomputeWrappers");
7002 return false;
7005 JS::Compartment* sourceComp = nullptr;
7006 if (args.get(0).isObject()) {
7007 sourceComp = JS::GetCompartment(UncheckedUnwrap(&args[0].toObject()));
7010 JS::Compartment* targetComp = nullptr;
7011 if (args.get(1).isObject()) {
7012 targetComp = JS::GetCompartment(UncheckedUnwrap(&args[1].toObject()));
7015 struct SingleOrAllCompartments final : public CompartmentFilter {
7016 JS::Compartment* comp;
7017 explicit SingleOrAllCompartments(JS::Compartment* c) : comp(c) {}
7018 virtual bool match(JS::Compartment* c) const override {
7019 return !comp || comp == c;
7023 if (!js::RecomputeWrappers(cx, SingleOrAllCompartments(sourceComp),
7024 SingleOrAllCompartments(targetComp))) {
7025 return false;
7028 args.rval().setUndefined();
7029 return true;
7032 static bool DumpObjectWrappers(JSContext* cx, unsigned argc, Value* vp) {
7033 CallArgs args = CallArgsFromVp(argc, vp);
7035 bool printedHeader = false;
7036 for (ZonesIter zone(cx->runtime(), WithAtoms); !zone.done(); zone.next()) {
7037 bool printedZoneInfo = false;
7038 for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) {
7039 bool printedCompartmentInfo = false;
7040 for (Compartment::ObjectWrapperEnum e(comp); !e.empty(); e.popFront()) {
7041 JSObject* wrapper = e.front().value().unbarrieredGet();
7042 JSObject* wrapped = e.front().key();
7043 if (!printedHeader) {
7044 fprintf(stderr, "Cross-compartment object wrappers:\n");
7045 printedHeader = true;
7047 if (!printedZoneInfo) {
7048 fprintf(stderr, " Zone %p:\n", zone.get());
7049 printedZoneInfo = true;
7051 if (!printedCompartmentInfo) {
7052 fprintf(stderr, " Compartment %p:\n", comp.get());
7053 printedCompartmentInfo = true;
7055 fprintf(stderr,
7056 " Object wrapper %p -> %p in zone %p compartment %p\n",
7057 wrapper, wrapped, wrapped->zone(), wrapped->compartment());
7062 if (!printedHeader) {
7063 fprintf(stderr, "No cross-compartment object wrappers.\n");
7066 args.rval().setUndefined();
7067 return true;
7070 static bool GetMaxArgs(JSContext* cx, unsigned argc, Value* vp) {
7071 CallArgs args = CallArgsFromVp(argc, vp);
7072 args.rval().setInt32(ARGS_LENGTH_MAX);
7073 return true;
7076 static bool IsHTMLDDA_Call(JSContext* cx, unsigned argc, Value* vp) {
7077 CallArgs args = CallArgsFromVp(argc, vp);
7079 // These are the required conditions under which this object may be called
7080 // by test262 tests, and the required behavior under those conditions.
7081 if (args.length() == 0 ||
7082 (args[0].isString() && args[0].toString()->length() == 0)) {
7083 args.rval().setNull();
7084 return true;
7087 JS_ReportErrorASCII(
7088 cx, "IsHTMLDDA object is being called in an impermissible manner");
7089 return false;
7092 static bool CreateIsHTMLDDA(JSContext* cx, unsigned argc, Value* vp) {
7093 CallArgs args = CallArgsFromVp(argc, vp);
7095 static const JSClassOps classOps = {
7096 nullptr, // addProperty
7097 nullptr, // delProperty
7098 nullptr, // enumerate
7099 nullptr, // newEnumerate
7100 nullptr, // resolve
7101 nullptr, // mayResolve
7102 nullptr, // finalize
7103 IsHTMLDDA_Call, // call
7104 nullptr, // construct
7105 nullptr, // trace
7108 static const JSClass cls = {
7109 "IsHTMLDDA",
7110 JSCLASS_EMULATES_UNDEFINED,
7111 &classOps,
7114 JSObject* obj = JS_NewObject(cx, &cls);
7115 if (!obj) {
7116 return false;
7118 args.rval().setObject(*obj);
7119 return true;
7122 static bool GetSelfHostedValue(JSContext* cx, unsigned argc, Value* vp) {
7123 CallArgs args = CallArgsFromVp(argc, vp);
7125 if (args.length() != 1 || !args[0].isString()) {
7126 JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
7127 JSSMSG_INVALID_ARGS, "getSelfHostedValue");
7128 return false;
7130 Rooted<JSAtom*> srcAtom(cx, ToAtom<CanGC>(cx, args[0]));
7131 if (!srcAtom) {
7132 return false;
7134 Rooted<PropertyName*> srcName(cx, srcAtom->asPropertyName());
7135 return GlobalObject::getIntrinsicValue(cx, cx->global(), srcName,
7136 args.rval());
7139 class ShellSourceHook : public SourceHook {
7140 // The function we should call to lazily retrieve source code.
7141 PersistentRootedFunction fun;
7143 public:
7144 ShellSourceHook(JSContext* cx, JSFunction& fun) : fun(cx, &fun) {}
7146 bool load(JSContext* cx, const char* filename, char16_t** twoByteSource,
7147 char** utf8Source, size_t* length) override {
7148 MOZ_ASSERT((twoByteSource != nullptr) != (utf8Source != nullptr),
7149 "must be called requesting only one of UTF-8 or UTF-16 source");
7151 RootedString str(cx);
7152 if (filename) {
7153 str = NewStringCopyUTF8(cx, filename);
7154 if (!str) {
7155 return false;
7157 } else {
7158 str = JS_GetEmptyString(cx);
7160 RootedValue filenameValue(cx, StringValue(str));
7162 RootedValue result(cx);
7163 if (!Call(cx, UndefinedHandleValue, fun, HandleValueArray(filenameValue),
7164 &result)) {
7165 return false;
7168 str = JS::ToString(cx, result);
7169 if (!str) {
7170 return false;
7173 Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
7174 if (!linear) {
7175 return false;
7178 if (twoByteSource) {
7179 *length = JS_GetStringLength(linear);
7181 *twoByteSource = cx->pod_malloc<char16_t>(*length);
7182 if (!*twoByteSource) {
7183 return false;
7186 CopyChars(*twoByteSource, *linear);
7187 } else {
7188 MOZ_ASSERT(utf8Source != nullptr);
7190 *length = JS::GetDeflatedUTF8StringLength(linear);
7192 *utf8Source = cx->pod_malloc<char>(*length);
7193 if (!*utf8Source) {
7194 return false;
7197 mozilla::DebugOnly<size_t> dstLen = JS::DeflateStringToUTF8Buffer(
7198 linear, mozilla::Span(*utf8Source, *length));
7199 MOZ_ASSERT(dstLen == *length);
7202 return true;
7206 static bool WithSourceHook(JSContext* cx, unsigned argc, Value* vp) {
7207 CallArgs args = CallArgsFromVp(argc, vp);
7208 RootedObject callee(cx, &args.callee());
7210 if (args.length() != 2) {
7211 ReportUsageErrorASCII(cx, callee, "Wrong number of arguments.");
7212 return false;
7215 if (!args[0].isObject() || !args[0].toObject().is<JSFunction>() ||
7216 !args[1].isObject() || !args[1].toObject().is<JSFunction>()) {
7217 ReportUsageErrorASCII(cx, callee,
7218 "First and second arguments must be functions.");
7219 return false;
7222 mozilla::UniquePtr<ShellSourceHook> hook =
7223 mozilla::MakeUnique<ShellSourceHook>(cx,
7224 args[0].toObject().as<JSFunction>());
7225 if (!hook) {
7226 return false;
7229 mozilla::UniquePtr<SourceHook> savedHook = js::ForgetSourceHook(cx);
7230 js::SetSourceHook(cx, std::move(hook));
7232 RootedObject fun(cx, &args[1].toObject());
7233 bool result = Call(cx, UndefinedHandleValue, fun,
7234 JS::HandleValueArray::empty(), args.rval());
7235 js::SetSourceHook(cx, std::move(savedHook));
7236 return result;
7239 static void PrintProfilerEvents_Callback(const char* msg, const char* details) {
7240 fprintf(stderr, "PROFILER EVENT: %s %s\n", msg, details);
7243 static bool PrintProfilerEvents(JSContext* cx, unsigned argc, Value* vp) {
7244 CallArgs args = CallArgsFromVp(argc, vp);
7245 if (cx->runtime()->geckoProfiler().enabled()) {
7246 js::RegisterContextProfilingEventMarker(cx, &PrintProfilerEvents_Callback);
7248 args.rval().setUndefined();
7249 return true;
7252 #ifdef SINGLESTEP_PROFILING
7253 static void SingleStepCallback(void* arg, jit::Simulator* sim, void* pc) {
7254 JSContext* cx = reinterpret_cast<JSContext*>(arg);
7256 // If profiling is not enabled, don't do anything.
7257 if (!cx->runtime()->geckoProfiler().enabled()) {
7258 return;
7261 JS::ProfilingFrameIterator::RegisterState state;
7262 state.pc = pc;
7263 # if defined(JS_SIMULATOR_ARM)
7264 state.sp = (void*)sim->get_register(jit::Simulator::sp);
7265 state.lr = (void*)sim->get_register(jit::Simulator::lr);
7266 state.fp = (void*)sim->get_register(jit::Simulator::fp);
7267 state.tempFP = (void*)sim->get_register(jit::Simulator::r7);
7268 # elif defined(JS_SIMULATOR_MIPS64) || defined(JS_SIMULATOR_MIPS32)
7269 state.sp = (void*)sim->getRegister(jit::Simulator::sp);
7270 state.lr = (void*)sim->getRegister(jit::Simulator::ra);
7271 state.fp = (void*)sim->getRegister(jit::Simulator::fp);
7272 # elif defined(JS_SIMULATOR_LOONG64)
7273 state.sp = (void*)sim->getRegister(jit::Simulator::sp);
7274 state.lr = (void*)sim->getRegister(jit::Simulator::ra);
7275 state.fp = (void*)sim->getRegister(jit::Simulator::fp);
7276 # else
7277 # error "NYI: Single-step profiling support"
7278 # endif
7280 mozilla::DebugOnly<void*> lastStackAddress = nullptr;
7281 StackChars stack;
7282 uint32_t frameNo = 0;
7283 AutoEnterOOMUnsafeRegion oomUnsafe;
7284 for (JS::ProfilingFrameIterator i(cx, state); !i.done(); ++i) {
7285 MOZ_ASSERT(i.stackAddress() != nullptr);
7286 MOZ_ASSERT(lastStackAddress <= i.stackAddress());
7287 lastStackAddress = i.stackAddress();
7288 JS::ProfilingFrameIterator::Frame frames[16];
7289 uint32_t nframes = i.extractStack(frames, 0, 16);
7290 for (uint32_t i = 0; i < nframes; i++) {
7291 // Assert endStackAddress never exceeds sp (bug 1782188).
7292 MOZ_ASSERT(frames[i].endStackAddress >= state.sp);
7293 if (frameNo > 0) {
7294 if (!stack.append(",", 1)) {
7295 oomUnsafe.crash("stack.append");
7298 if (!stack.append(frames[i].label, strlen(frames[i].label))) {
7299 oomUnsafe.crash("stack.append");
7301 frameNo++;
7305 ShellContext* sc = GetShellContext(cx);
7307 // Only append the stack if it differs from the last stack.
7308 if (sc->stacks.empty() || sc->stacks.back().length() != stack.length() ||
7309 !ArrayEqual(sc->stacks.back().begin(), stack.begin(), stack.length())) {
7310 if (!sc->stacks.append(std::move(stack))) {
7311 oomUnsafe.crash("stacks.append");
7315 #endif
7317 static bool EnableSingleStepProfiling(JSContext* cx, unsigned argc, Value* vp) {
7318 #ifdef SINGLESTEP_PROFILING
7319 CallArgs args = CallArgsFromVp(argc, vp);
7321 jit::Simulator* sim = cx->simulator();
7322 sim->enable_single_stepping(SingleStepCallback, cx);
7324 args.rval().setUndefined();
7325 return true;
7326 #else
7327 JS_ReportErrorASCII(cx, "single-step profiling not enabled on this platform");
7328 return false;
7329 #endif
7332 static bool DisableSingleStepProfiling(JSContext* cx, unsigned argc,
7333 Value* vp) {
7334 #ifdef SINGLESTEP_PROFILING
7335 CallArgs args = CallArgsFromVp(argc, vp);
7337 jit::Simulator* sim = cx->simulator();
7338 sim->disable_single_stepping();
7340 ShellContext* sc = GetShellContext(cx);
7342 RootedValueVector elems(cx);
7343 for (size_t i = 0; i < sc->stacks.length(); i++) {
7344 JSString* stack =
7345 JS_NewUCStringCopyN(cx, sc->stacks[i].begin(), sc->stacks[i].length());
7346 if (!stack) {
7347 return false;
7349 if (!elems.append(StringValue(stack))) {
7350 return false;
7354 JSObject* array = JS::NewArrayObject(cx, elems);
7355 if (!array) {
7356 return false;
7359 sc->stacks.clear();
7360 args.rval().setObject(*array);
7361 return true;
7362 #else
7363 JS_ReportErrorASCII(cx, "single-step profiling not enabled on this platform");
7364 return false;
7365 #endif
7368 static bool IsLatin1(JSContext* cx, unsigned argc, Value* vp) {
7369 CallArgs args = CallArgsFromVp(argc, vp);
7370 bool isLatin1 =
7371 args.get(0).isString() && args[0].toString()->hasLatin1Chars();
7372 args.rval().setBoolean(isLatin1);
7373 return true;
7376 static bool EnableGeckoProfiling(JSContext* cx, unsigned argc, Value* vp) {
7377 CallArgs args = CallArgsFromVp(argc, vp);
7379 if (!EnsureGeckoProfilingStackInstalled(cx, GetShellContext(cx))) {
7380 return false;
7383 cx->runtime()->geckoProfiler().enableSlowAssertions(false);
7384 cx->runtime()->geckoProfiler().enable(true);
7386 args.rval().setUndefined();
7387 return true;
7390 static bool EnableGeckoProfilingWithSlowAssertions(JSContext* cx, unsigned argc,
7391 Value* vp) {
7392 CallArgs args = CallArgsFromVp(argc, vp);
7393 args.rval().setUndefined();
7395 if (cx->runtime()->geckoProfiler().enabled()) {
7396 // If profiling already enabled with slow assertions disabled,
7397 // this is a no-op.
7398 if (cx->runtime()->geckoProfiler().slowAssertionsEnabled()) {
7399 return true;
7402 // Slow assertions are off. Disable profiling before re-enabling
7403 // with slow assertions on.
7404 cx->runtime()->geckoProfiler().enable(false);
7407 if (!EnsureGeckoProfilingStackInstalled(cx, GetShellContext(cx))) {
7408 return false;
7411 cx->runtime()->geckoProfiler().enableSlowAssertions(true);
7412 cx->runtime()->geckoProfiler().enable(true);
7414 return true;
7417 static bool DisableGeckoProfiling(JSContext* cx, unsigned argc, Value* vp) {
7418 CallArgs args = CallArgsFromVp(argc, vp);
7419 args.rval().setUndefined();
7421 if (!cx->runtime()->geckoProfiler().enabled()) {
7422 return true;
7425 cx->runtime()->geckoProfiler().enable(false);
7426 return true;
7429 // Global mailbox that is used to communicate a shareable object value from one
7430 // worker to another.
7432 // These object types are shareable:
7434 // - SharedArrayBuffer
7435 // - WasmMemoryObject (when constructed with shared:true)
7436 // - WasmModuleObject
7438 // For the SharedArrayBuffer and WasmMemoryObject we transmit the underlying
7439 // SharedArrayRawBuffer ("SARB"). For the WasmModuleObject we transmit the
7440 // underlying JS::WasmModule. The transmitted types are refcounted. When they
7441 // are in the mailbox their reference counts are at least 1, accounting for the
7442 // reference from the mailbox.
7444 // The lock guards the mailbox variable and prevents a race where two workers
7445 // try to set the mailbox at the same time to replace an object that is only
7446 // referenced from the mailbox: the workers will both decrement the reference
7447 // count on the old object, and one of those decrements will be on a garbage
7448 // object. We could implement this with atomics and a CAS loop but it's not
7449 // worth the bother.
7451 // Note that if a thread reads the mailbox repeatedly it will get distinct
7452 // objects on each read. The alternatives are to cache created objects locally,
7453 // but this retains storage we don't need to retain, or to somehow clear the
7454 // mailbox locally, but this creates a coordination headache. Buyer beware.
7456 enum class MailboxTag {
7457 Empty,
7458 SharedArrayBuffer,
7459 WasmMemory,
7460 WasmModule,
7461 Number,
7464 struct SharedObjectMailbox {
7465 union Value {
7466 struct {
7467 SharedArrayRawBuffer* buffer;
7468 size_t length;
7469 bool isHugeMemory; // For a WasmMemory tag, otherwise false
7470 } sarb;
7471 JS::WasmModule* module;
7472 double number;
7474 Value() : number(0.0) {}
7477 MailboxTag tag = MailboxTag::Empty;
7478 Value val;
7481 typedef ExclusiveData<SharedObjectMailbox> SOMailbox;
7483 // Never null after successful initialization.
7484 static SOMailbox* sharedObjectMailbox;
7486 static bool InitSharedObjectMailbox() {
7487 sharedObjectMailbox = js_new<SOMailbox>(mutexid::ShellObjectMailbox);
7488 return sharedObjectMailbox != nullptr;
7491 static void DestructSharedObjectMailbox() {
7492 // All workers need to have terminated at this point.
7495 auto mbx = sharedObjectMailbox->lock();
7496 switch (mbx->tag) {
7497 case MailboxTag::Empty:
7498 case MailboxTag::Number:
7499 break;
7500 case MailboxTag::SharedArrayBuffer:
7501 case MailboxTag::WasmMemory:
7502 mbx->val.sarb.buffer->dropReference();
7503 break;
7504 case MailboxTag::WasmModule:
7505 mbx->val.module->Release();
7506 break;
7507 default:
7508 MOZ_CRASH();
7512 js_delete(sharedObjectMailbox);
7513 sharedObjectMailbox = nullptr;
7516 static bool GetSharedObject(JSContext* cx, unsigned argc, Value* vp) {
7517 CallArgs args = CallArgsFromVp(argc, vp);
7518 RootedObject newObj(cx);
7521 auto mbx = sharedObjectMailbox->lock();
7522 switch (mbx->tag) {
7523 case MailboxTag::Empty: {
7524 break;
7526 case MailboxTag::Number: {
7527 args.rval().setNumber(mbx->val.number);
7528 return true;
7530 case MailboxTag::SharedArrayBuffer:
7531 case MailboxTag::WasmMemory: {
7532 // Flag was set in the sender; ensure it is set in the receiver.
7533 MOZ_ASSERT(
7534 cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled());
7536 // The protocol for creating a SAB requires the refcount to be
7537 // incremented prior to the SAB creation.
7539 SharedArrayRawBuffer* buf = mbx->val.sarb.buffer;
7540 size_t length = mbx->val.sarb.length;
7541 if (!buf->addReference()) {
7542 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
7543 JSMSG_SC_SAB_REFCNT_OFLO);
7544 return false;
7547 // If the allocation fails we must decrement the refcount before
7548 // returning.
7550 Rooted<ArrayBufferObjectMaybeShared*> maybesab(
7551 cx, SharedArrayBufferObject::New(cx, buf, length));
7552 if (!maybesab) {
7553 buf->dropReference();
7554 return false;
7557 // At this point the SAB was created successfully and it owns the
7558 // refcount-increase on the buffer that we performed above. So even
7559 // if we fail to allocate along any path below we must not decrement
7560 // the refcount; the garbage collector must be allowed to handle
7561 // that via finalization of the orphaned SAB object.
7563 if (mbx->tag == MailboxTag::SharedArrayBuffer) {
7564 newObj = maybesab;
7565 } else {
7566 if (!GlobalObject::ensureConstructor(cx, cx->global(),
7567 JSProto_WebAssembly)) {
7568 return false;
7570 RootedObject proto(cx,
7571 &cx->global()->getPrototype(JSProto_WasmMemory));
7572 newObj = WasmMemoryObject::create(cx, maybesab,
7573 mbx->val.sarb.isHugeMemory, proto);
7574 MOZ_ASSERT_IF(newObj, newObj->as<WasmMemoryObject>().isShared());
7575 if (!newObj) {
7576 return false;
7580 break;
7582 case MailboxTag::WasmModule: {
7583 // Flag was set in the sender; ensure it is set in the receiver.
7584 MOZ_ASSERT(
7585 cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled());
7587 if (!GlobalObject::ensureConstructor(cx, cx->global(),
7588 JSProto_WebAssembly)) {
7589 return false;
7592 // WasmModuleObject::create() increments the refcount on the module
7593 // and signals an error and returns null if that fails.
7594 newObj = mbx->val.module->createObject(cx);
7595 if (!newObj) {
7596 return false;
7598 break;
7600 default: {
7601 MOZ_CRASH();
7606 args.rval().setObjectOrNull(newObj);
7607 return true;
7610 static bool SetSharedObject(JSContext* cx, unsigned argc, Value* vp) {
7611 CallArgs args = CallArgsFromVp(argc, vp);
7613 MailboxTag tag = MailboxTag::Empty;
7614 SharedObjectMailbox::Value value;
7616 // Increase refcounts when we obtain the value to avoid operating on dead
7617 // storage during self-assignment.
7619 if (args.get(0).isObject()) {
7620 RootedObject obj(cx, &args[0].toObject());
7621 if (obj->is<SharedArrayBufferObject>()) {
7622 Rooted<SharedArrayBufferObject*> sab(cx,
7623 &obj->as<SharedArrayBufferObject>());
7624 tag = MailboxTag::SharedArrayBuffer;
7625 value.sarb.buffer = sab->rawBufferObject();
7626 value.sarb.length = sab->byteLength();
7627 value.sarb.isHugeMemory = false;
7628 if (!value.sarb.buffer->addReference()) {
7629 JS_ReportErrorASCII(cx,
7630 "Reference count overflow on SharedArrayBuffer");
7631 return false;
7633 } else if (obj->is<WasmMemoryObject>()) {
7634 // Here we must transmit sab.byteLength() as the length; the SARB has its
7635 // own notion of the length which may be greater, and that's fine.
7636 if (obj->as<WasmMemoryObject>().isShared()) {
7637 Rooted<SharedArrayBufferObject*> sab(
7638 cx, &obj->as<WasmMemoryObject>()
7639 .buffer()
7640 .as<SharedArrayBufferObject>());
7641 tag = MailboxTag::WasmMemory;
7642 value.sarb.buffer = sab->rawBufferObject();
7643 value.sarb.length = sab->byteLength();
7644 value.sarb.isHugeMemory = obj->as<WasmMemoryObject>().isHuge();
7645 if (!value.sarb.buffer->addReference()) {
7646 JS_ReportErrorASCII(cx,
7647 "Reference count overflow on SharedArrayBuffer");
7648 return false;
7650 } else {
7651 JS_ReportErrorASCII(cx, "Invalid argument to SetSharedObject");
7652 return false;
7654 } else if (JS::IsWasmModuleObject(obj)) {
7655 tag = MailboxTag::WasmModule;
7656 value.module = JS::GetWasmModule(obj).forget().take();
7657 } else {
7658 JS_ReportErrorASCII(cx, "Invalid argument to SetSharedObject");
7659 return false;
7661 } else if (args.get(0).isNumber()) {
7662 tag = MailboxTag::Number;
7663 value.number = args.get(0).toNumber();
7664 // Nothing
7665 } else if (args.get(0).isNullOrUndefined()) {
7666 // Nothing
7667 } else {
7668 JS_ReportErrorASCII(cx, "Invalid argument to SetSharedObject");
7669 return false;
7673 auto mbx = sharedObjectMailbox->lock();
7675 switch (mbx->tag) {
7676 case MailboxTag::Empty:
7677 case MailboxTag::Number:
7678 break;
7679 case MailboxTag::SharedArrayBuffer:
7680 case MailboxTag::WasmMemory:
7681 mbx->val.sarb.buffer->dropReference();
7682 break;
7683 case MailboxTag::WasmModule:
7684 mbx->val.module->Release();
7685 break;
7686 default:
7687 MOZ_CRASH();
7690 mbx->tag = tag;
7691 mbx->val = value;
7694 args.rval().setUndefined();
7695 return true;
7698 typedef Vector<uint8_t, 0, SystemAllocPolicy> Uint8Vector;
7700 class StreamCacheEntry : public AtomicRefCounted<StreamCacheEntry>,
7701 public JS::OptimizedEncodingListener {
7702 typedef AtomicRefCounted<StreamCacheEntry> AtomicBase;
7704 Uint8Vector bytes_;
7705 ExclusiveData<Uint8Vector> optimized_;
7707 public:
7708 explicit StreamCacheEntry(Uint8Vector&& original)
7709 : bytes_(std::move(original)),
7710 optimized_(mutexid::ShellStreamCacheEntryState) {}
7712 // Implement JS::OptimizedEncodingListener:
7714 MozExternalRefCountType MOZ_XPCOM_ABI AddRef() override {
7715 AtomicBase::AddRef();
7716 return 1; // unused
7718 MozExternalRefCountType MOZ_XPCOM_ABI Release() override {
7719 AtomicBase::Release();
7720 return 0; // unused
7723 const Uint8Vector& bytes() const { return bytes_; }
7725 void storeOptimizedEncoding(const uint8_t* srcBytes,
7726 size_t srcLength) override {
7727 MOZ_ASSERT(srcLength > 0);
7729 // Tolerate races since a single StreamCacheEntry object can be used as
7730 // the source of multiple streaming compilations.
7731 auto dstBytes = optimized_.lock();
7732 if (dstBytes->length() > 0) {
7733 return;
7736 if (!dstBytes->resize(srcLength)) {
7737 return;
7739 memcpy(dstBytes->begin(), srcBytes, srcLength);
7742 bool hasOptimizedEncoding() const { return !optimized_.lock()->empty(); }
7743 const Uint8Vector& optimizedEncoding() const {
7744 return optimized_.lock().get();
7748 typedef RefPtr<StreamCacheEntry> StreamCacheEntryPtr;
7750 class StreamCacheEntryObject : public NativeObject {
7751 static const unsigned CACHE_ENTRY_SLOT = 0;
7752 static const JSClassOps classOps_;
7753 static const JSPropertySpec properties_;
7755 static void finalize(JS::GCContext* gcx, JSObject* obj) {
7756 obj->as<StreamCacheEntryObject>().cache().Release();
7759 static bool cachedGetter(JSContext* cx, unsigned argc, Value* vp) {
7760 CallArgs args = CallArgsFromVp(argc, vp);
7761 if (!args.thisv().isObject() ||
7762 !args.thisv().toObject().is<StreamCacheEntryObject>()) {
7763 return false;
7766 StreamCacheEntryObject& obj =
7767 args.thisv().toObject().as<StreamCacheEntryObject>();
7768 args.rval().setBoolean(obj.cache().hasOptimizedEncoding());
7769 return true;
7771 static bool getBuffer(JSContext* cx, unsigned argc, Value* vp) {
7772 CallArgs args = CallArgsFromVp(argc, vp);
7773 if (!args.thisv().isObject() ||
7774 !args.thisv().toObject().is<StreamCacheEntryObject>()) {
7775 return false;
7778 auto& bytes =
7779 args.thisv().toObject().as<StreamCacheEntryObject>().cache().bytes();
7780 auto* buffer = ArrayBufferObject::createZeroed(cx, bytes.length());
7781 if (!buffer) {
7782 return false;
7785 memcpy(buffer->dataPointer(), bytes.begin(), bytes.length());
7787 args.rval().setObject(*buffer);
7788 return true;
7791 public:
7792 static const unsigned RESERVED_SLOTS = 1;
7793 static const JSClass class_;
7794 static const JSPropertySpec properties[];
7796 static bool construct(JSContext* cx, unsigned argc, Value* vp) {
7797 CallArgs args = CallArgsFromVp(argc, vp);
7798 if (!args.requireAtLeast(cx, "streamCacheEntry", 1)) {
7799 return false;
7802 SharedMem<uint8_t*> ptr;
7803 size_t numBytes;
7804 if (!args[0].isObject() ||
7805 !IsBufferSource(&args[0].toObject(), &ptr, &numBytes)) {
7806 RootedObject callee(cx, &args.callee());
7807 ReportUsageErrorASCII(cx, callee, "Argument must be an ArrayBuffer");
7808 return false;
7811 Uint8Vector bytes;
7812 if (!bytes.resize(numBytes)) {
7813 return false;
7816 memcpy(bytes.begin(), ptr.unwrap(), numBytes);
7818 RefPtr<StreamCacheEntry> cache =
7819 cx->new_<StreamCacheEntry>(std::move(bytes));
7820 if (!cache) {
7821 return false;
7824 Rooted<NativeObject*> obj(
7825 cx, NewObjectWithGivenProto<StreamCacheEntryObject>(cx, nullptr));
7826 if (!obj) {
7827 return false;
7829 obj->initReservedSlot(CACHE_ENTRY_SLOT,
7830 PrivateValue(cache.forget().take()));
7832 if (!JS_DefineProperty(cx, obj, "cached", cachedGetter, nullptr, 0)) {
7833 return false;
7835 if (!JS_DefineFunction(cx, obj, "getBuffer", getBuffer, 0, 0)) {
7836 return false;
7839 args.rval().setObject(*obj);
7840 return true;
7843 StreamCacheEntry& cache() const {
7844 return *(StreamCacheEntry*)getReservedSlot(CACHE_ENTRY_SLOT).toPrivate();
7848 const JSClassOps StreamCacheEntryObject::classOps_ = {
7849 nullptr, // addProperty
7850 nullptr, // delProperty
7851 nullptr, // enumerate
7852 nullptr, // newEnumerate
7853 nullptr, // resolve
7854 nullptr, // mayResolve
7855 StreamCacheEntryObject::finalize, // finalize
7856 nullptr, // call
7857 nullptr, // construct
7858 nullptr, // trace
7861 const JSClass StreamCacheEntryObject::class_ = {
7862 "StreamCacheEntryObject",
7863 JSCLASS_HAS_RESERVED_SLOTS(StreamCacheEntryObject::RESERVED_SLOTS) |
7864 JSCLASS_BACKGROUND_FINALIZE,
7865 &StreamCacheEntryObject::classOps_};
7867 struct BufferStreamJob {
7868 Variant<Uint8Vector, StreamCacheEntryPtr> source;
7869 Thread thread;
7870 JS::StreamConsumer* consumer;
7872 BufferStreamJob(Uint8Vector&& source, JS::StreamConsumer* consumer)
7873 : source(AsVariant<Uint8Vector>(std::move(source))), consumer(consumer) {}
7874 BufferStreamJob(StreamCacheEntry& source, JS::StreamConsumer* consumer)
7875 : source(AsVariant<StreamCacheEntryPtr>(&source)), consumer(consumer) {}
7878 struct BufferStreamState {
7879 Vector<UniquePtr<BufferStreamJob>, 0, SystemAllocPolicy> jobs;
7880 size_t delayMillis;
7881 size_t chunkSize;
7882 bool shutdown;
7884 BufferStreamState() : delayMillis(1), chunkSize(10), shutdown(false) {}
7886 ~BufferStreamState() { MOZ_ASSERT(jobs.empty()); }
7889 static ExclusiveWaitableData<BufferStreamState>* bufferStreamState;
7891 static void BufferStreamMain(BufferStreamJob* job) {
7892 const uint8_t* bytes;
7893 size_t byteLength;
7894 JS::OptimizedEncodingListener* listener;
7895 if (job->source.is<StreamCacheEntryPtr>()) {
7896 StreamCacheEntry& cache = *job->source.as<StreamCacheEntryPtr>();
7897 if (cache.hasOptimizedEncoding()) {
7898 const Uint8Vector& optimized = cache.optimizedEncoding();
7899 job->consumer->consumeOptimizedEncoding(optimized.begin(),
7900 optimized.length());
7901 goto done;
7904 bytes = cache.bytes().begin();
7905 byteLength = cache.bytes().length();
7906 listener = &cache;
7907 } else {
7908 bytes = job->source.as<Uint8Vector>().begin();
7909 byteLength = job->source.as<Uint8Vector>().length();
7910 listener = nullptr;
7913 size_t byteOffset;
7914 byteOffset = 0;
7915 while (true) {
7916 if (byteOffset == byteLength) {
7917 job->consumer->streamEnd(listener);
7918 break;
7921 bool shutdown;
7922 size_t delayMillis;
7923 size_t chunkSize;
7925 auto state = bufferStreamState->lock();
7926 shutdown = state->shutdown;
7927 delayMillis = state->delayMillis;
7928 chunkSize = state->chunkSize;
7931 if (shutdown) {
7932 job->consumer->streamError(JSMSG_STREAM_CONSUME_ERROR);
7933 break;
7936 ThisThread::SleepMilliseconds(delayMillis);
7938 chunkSize = std::min(chunkSize, byteLength - byteOffset);
7940 if (!job->consumer->consumeChunk(bytes + byteOffset, chunkSize)) {
7941 break;
7944 byteOffset += chunkSize;
7947 done:
7948 auto state = bufferStreamState->lock();
7949 size_t jobIndex = 0;
7950 while (state->jobs[jobIndex].get() != job) {
7951 jobIndex++;
7953 job->thread.detach(); // quiet assert in ~Thread() called by erase().
7954 state->jobs.erase(state->jobs.begin() + jobIndex);
7955 if (state->jobs.empty()) {
7956 state.notify_all(/* jobs empty */);
7960 static bool ConsumeBufferSource(JSContext* cx, JS::HandleObject obj,
7961 JS::MimeType, JS::StreamConsumer* consumer) {
7963 RootedValue url(cx);
7964 if (!JS_GetProperty(cx, obj, "url", &url)) {
7965 return false;
7967 UniqueChars urlChars;
7968 if (url.isString()) {
7969 Rooted<JSString*> str(cx, url.toString());
7970 urlChars = JS_EncodeStringToUTF8(cx, str);
7971 if (!urlChars) {
7972 return false;
7976 RootedValue mapUrl(cx);
7977 if (!JS_GetProperty(cx, obj, "sourceMappingURL", &mapUrl)) {
7978 return false;
7980 UniqueChars mapUrlChars;
7981 if (mapUrl.isString()) {
7982 Rooted<JSString*> str(cx, mapUrl.toString());
7983 mapUrlChars = JS_EncodeStringToUTF8(cx, str);
7984 if (!mapUrlChars) {
7985 return false;
7989 consumer->noteResponseURLs(urlChars.get(), mapUrlChars.get());
7992 UniquePtr<BufferStreamJob> job;
7994 SharedMem<uint8_t*> dataPointer;
7995 size_t byteLength;
7996 if (IsBufferSource(obj, &dataPointer, &byteLength)) {
7997 Uint8Vector bytes;
7998 if (!bytes.resize(byteLength)) {
7999 JS_ReportOutOfMemory(cx);
8000 return false;
8003 memcpy(bytes.begin(), dataPointer.unwrap(), byteLength);
8004 job = cx->make_unique<BufferStreamJob>(std::move(bytes), consumer);
8005 } else if (obj->is<StreamCacheEntryObject>()) {
8006 job = cx->make_unique<BufferStreamJob>(
8007 obj->as<StreamCacheEntryObject>().cache(), consumer);
8008 } else {
8009 JS_ReportErrorASCII(
8011 "shell streaming consumes a buffer source (buffer or view) "
8012 "or StreamCacheEntryObject");
8013 return false;
8015 if (!job) {
8016 return false;
8019 BufferStreamJob* jobPtr = job.get();
8022 auto state = bufferStreamState->lock();
8023 MOZ_ASSERT(!state->shutdown);
8024 if (!state->jobs.append(std::move(job))) {
8025 JS_ReportOutOfMemory(cx);
8026 return false;
8031 AutoEnterOOMUnsafeRegion oomUnsafe;
8032 if (!jobPtr->thread.init(BufferStreamMain, jobPtr)) {
8033 oomUnsafe.crash("ConsumeBufferSource");
8037 return true;
8040 static void ReportStreamError(JSContext* cx, size_t errorNumber) {
8041 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber);
8044 static bool SetBufferStreamParams(JSContext* cx, unsigned argc, Value* vp) {
8045 CallArgs args = CallArgsFromVp(argc, vp);
8046 if (!args.requireAtLeast(cx, "setBufferStreamParams", 2)) {
8047 return false;
8050 double delayMillis;
8051 if (!ToNumber(cx, args[0], &delayMillis)) {
8052 return false;
8055 double chunkSize;
8056 if (!ToNumber(cx, args[1], &chunkSize)) {
8057 return false;
8061 auto state = bufferStreamState->lock();
8062 state->delayMillis = delayMillis;
8063 state->chunkSize = chunkSize;
8066 args.rval().setUndefined();
8067 return true;
8070 static void ShutdownBufferStreams() {
8071 auto state = bufferStreamState->lock();
8072 state->shutdown = true;
8073 while (!state->jobs.empty()) {
8074 state.wait(/* jobs empty */);
8076 state->jobs.clearAndFree();
8079 static bool DumpScopeChain(JSContext* cx, unsigned argc, Value* vp) {
8080 CallArgs args = CallArgsFromVp(argc, vp);
8081 RootedObject callee(cx, &args.callee());
8083 if (js::SupportDifferentialTesting()) {
8084 ReportUsageErrorASCII(
8085 cx, callee, "Function not available in differential testing mode.");
8086 return false;
8089 if (args.length() != 1) {
8090 ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
8091 return false;
8094 if (!args[0].isObject() ||
8095 !(args[0].toObject().is<JSFunction>() ||
8096 args[0].toObject().is<ShellModuleObjectWrapper>())) {
8097 ReportUsageErrorASCII(
8098 cx, callee, "Argument must be an interpreted function or a module");
8099 return false;
8102 RootedObject obj(cx, &args[0].toObject());
8103 RootedScript script(cx);
8105 if (obj->is<JSFunction>()) {
8106 RootedFunction fun(cx, &obj->as<JSFunction>());
8107 if (!fun->isInterpreted()) {
8108 ReportUsageErrorASCII(cx, callee,
8109 "Argument must be an interpreted function");
8110 return false;
8112 script = JSFunction::getOrCreateScript(cx, fun);
8113 if (!script) {
8114 return false;
8116 } else {
8117 script = obj->as<ShellModuleObjectWrapper>().get()->maybeScript();
8118 if (!script) {
8119 JS_ReportErrorASCII(cx, "module does not have an associated script");
8120 return false;
8124 script->bodyScope()->dump();
8126 args.rval().setUndefined();
8127 return true;
8130 // For testing GC marking, blackRoot() and grayRoot() will heap-allocate an
8131 // array whose elements (as well as the array itself) will be marked as roots in
8132 // subsequent GCs.
8134 // Note that EnsureGrayRoot() will blacken the returned object, so it will not
8135 // actually end up marked gray until the following GC clears the black bit
8136 // (assuming nothing is holding onto it.)
8138 // The idea is that you can set up a whole graph of objects to be marked gray,
8139 // hanging off of the object returned from grayRoot(). Then you GC to clear the
8140 // black bits and set the gray bits.
8142 // To test grayness, register the objects of interest with addMarkObservers(),
8143 // which takes an Array of objects (which will be marked black at the time
8144 // they're passed in). Their mark bits may be retrieved at any time with
8145 // getMarks(), in the form of an array of strings with each index corresponding
8146 // to the original objects passed to addMarkObservers().
8148 static bool EnsureRootArray(JSContext* cx, gc::MarkColor color, unsigned argc,
8149 Value* vp) {
8150 CallArgs args = CallArgsFromVp(argc, vp);
8152 auto priv = EnsureShellCompartmentPrivate(cx);
8153 if (!priv) {
8154 return false;
8157 GCPtr<ArrayObject*>& root =
8158 (color == gc::MarkColor::Black) ? priv->blackRoot : priv->grayRoot;
8160 if (!root && !(root = NewTenuredDenseEmptyArray(cx))) {
8161 return false;
8164 // Barrier to enforce the invariant that JS does not touch gray objects.
8165 JSObject* obj = root;
8166 JS::ExposeObjectToActiveJS(obj);
8168 args.rval().setObject(*obj);
8169 return true;
8172 static bool EnsureBlackRoot(JSContext* cx, unsigned argc, Value* vp) {
8173 return EnsureRootArray(cx, gc::MarkColor::Black, argc, vp);
8176 static bool EnsureGrayRoot(JSContext* cx, unsigned argc, Value* vp) {
8177 return EnsureRootArray(cx, gc::MarkColor::Gray, argc, vp);
8180 static MarkBitObservers* EnsureMarkBitObservers(JSContext* cx) {
8181 ShellContext* sc = GetShellContext(cx);
8182 if (!sc->markObservers) {
8183 auto* observers =
8184 cx->new_<MarkBitObservers>(cx->runtime(), NonshrinkingGCObjectVector());
8185 if (!observers) {
8186 return nullptr;
8188 sc->markObservers.reset(observers);
8190 return sc->markObservers.get();
8193 static bool ClearMarkObservers(JSContext* cx, unsigned argc, Value* vp) {
8194 CallArgs args = CallArgsFromVp(argc, vp);
8196 auto markObservers = EnsureMarkBitObservers(cx);
8197 if (!markObservers) {
8198 return false;
8201 markObservers->get().clear();
8203 args.rval().setUndefined();
8204 return true;
8207 static bool AddMarkObservers(JSContext* cx, unsigned argc, Value* vp) {
8208 CallArgs args = CallArgsFromVp(argc, vp);
8210 auto markObservers = EnsureMarkBitObservers(cx);
8211 if (!markObservers) {
8212 return false;
8215 if (!args.get(0).isObject()) {
8216 JS_ReportErrorASCII(cx, "argument must be an Array of objects");
8217 return false;
8220 RootedObject observersArg(cx, &args[0].toObject());
8221 uint64_t length;
8222 if (!GetLengthProperty(cx, observersArg, &length)) {
8223 return false;
8226 if (length > UINT32_MAX) {
8227 JS_ReportErrorASCII(cx, "Invalid length for observers array");
8228 return false;
8231 RootedValue value(cx);
8232 RootedObject object(cx);
8233 for (uint32_t i = 0; i < length; i++) {
8234 if (!JS_GetElement(cx, observersArg, i, &value)) {
8235 return false;
8238 if (!value.isObject()) {
8239 JS_ReportErrorASCII(cx, "argument must be an Array of objects");
8240 return false;
8243 object = &value.toObject();
8244 if (gc::IsInsideNursery(object)) {
8245 // WeakCaches are not swept during a minor GC. To prevent
8246 // nursery-allocated contents from having the mark bits be deceptively
8247 // black until the second GC, they would need to be marked weakly (cf
8248 // NurseryAwareHashMap). It is simpler to evict the nursery to prevent
8249 // nursery objects from being observed.
8250 cx->runtime()->gc.evictNursery();
8253 if (!markObservers->get().append(object)) {
8254 return false;
8258 args.rval().setInt32(length);
8259 return true;
8262 static bool GetMarks(JSContext* cx, unsigned argc, Value* vp) {
8263 CallArgs args = CallArgsFromVp(argc, vp);
8265 auto& observers = GetShellContext(cx)->markObservers;
8266 if (!observers) {
8267 args.rval().setUndefined();
8268 return true;
8271 size_t length = observers->get().length();
8272 Rooted<ArrayObject*> ret(cx, js::NewDenseEmptyArray(cx));
8273 if (!ret) {
8274 return false;
8277 for (uint32_t i = 0; i < length; i++) {
8278 const char* color;
8279 JSObject* obj = observers->get()[i];
8280 if (!obj) {
8281 color = "dead";
8282 } else if (obj->zone()->isGCPreparing()) {
8283 color = "unmarked";
8284 } else {
8285 gc::TenuredCell* cell = &obj->asTenured();
8286 if (cell->isMarkedGray()) {
8287 color = "gray";
8288 } else if (cell->isMarkedBlack()) {
8289 color = "black";
8290 } else {
8291 color = "unmarked";
8294 JSString* s = JS_NewStringCopyZ(cx, color);
8295 if (!s) {
8296 return false;
8298 if (!NewbornArrayPush(cx, ret, StringValue(s))) {
8299 return false;
8303 args.rval().setObject(*ret);
8304 return true;
8307 namespace js {
8308 namespace shell {
8310 class ShellAutoEntryMonitor : JS::dbg::AutoEntryMonitor {
8311 Vector<UniqueChars, 1, js::SystemAllocPolicy> log;
8312 bool oom;
8313 bool enteredWithoutExit;
8315 public:
8316 explicit ShellAutoEntryMonitor(JSContext* cx)
8317 : AutoEntryMonitor(cx), oom(false), enteredWithoutExit(false) {}
8319 ~ShellAutoEntryMonitor() { MOZ_ASSERT(!enteredWithoutExit); }
8321 void Entry(JSContext* cx, JSFunction* function, JS::HandleValue asyncStack,
8322 const char* asyncCause) override {
8323 MOZ_ASSERT(!enteredWithoutExit);
8324 enteredWithoutExit = true;
8326 RootedString displayId(cx, JS_GetMaybePartialFunctionDisplayId(function));
8327 if (displayId) {
8328 UniqueChars displayIdStr = JS_EncodeStringToUTF8(cx, displayId);
8329 if (!displayIdStr) {
8330 // We report OOM in buildResult.
8331 cx->recoverFromOutOfMemory();
8332 oom = true;
8333 return;
8335 oom = !log.append(std::move(displayIdStr));
8336 return;
8339 oom = !log.append(DuplicateString("anonymous"));
8342 void Entry(JSContext* cx, JSScript* script, JS::HandleValue asyncStack,
8343 const char* asyncCause) override {
8344 MOZ_ASSERT(!enteredWithoutExit);
8345 enteredWithoutExit = true;
8347 UniqueChars label(JS_smprintf("eval:%s", JS_GetScriptFilename(script)));
8348 oom = !label || !log.append(std::move(label));
8351 void Exit(JSContext* cx) override {
8352 MOZ_ASSERT(enteredWithoutExit);
8353 enteredWithoutExit = false;
8356 bool buildResult(JSContext* cx, MutableHandleValue resultValue) {
8357 if (oom) {
8358 JS_ReportOutOfMemory(cx);
8359 return false;
8362 RootedObject result(cx, JS::NewArrayObject(cx, log.length()));
8363 if (!result) {
8364 return false;
8367 for (size_t i = 0; i < log.length(); i++) {
8368 char* name = log[i].get();
8369 RootedString string(cx, AtomizeUTF8Chars(cx, name, strlen(name)));
8370 if (!string) {
8371 return false;
8373 RootedValue value(cx, StringValue(string));
8374 if (!JS_SetElement(cx, result, i, value)) {
8375 return false;
8379 resultValue.setObject(*result.get());
8380 return true;
8384 } // namespace shell
8385 } // namespace js
8387 static bool EntryPoints(JSContext* cx, unsigned argc, Value* vp) {
8388 CallArgs args = CallArgsFromVp(argc, vp);
8390 if (args.length() != 1) {
8391 JS_ReportErrorASCII(cx, "Wrong number of arguments");
8392 return false;
8395 RootedObject opts(cx, ToObject(cx, args[0]));
8396 if (!opts) {
8397 return false;
8400 // { function: f } --- Call f.
8402 RootedValue fun(cx), dummy(cx);
8404 if (!JS_GetProperty(cx, opts, "function", &fun)) {
8405 return false;
8407 if (!fun.isUndefined()) {
8408 js::shell::ShellAutoEntryMonitor sarep(cx);
8409 if (!Call(cx, UndefinedHandleValue, fun, JS::HandleValueArray::empty(),
8410 &dummy)) {
8411 return false;
8413 return sarep.buildResult(cx, args.rval());
8417 // { object: o, property: p, value: v } --- Fetch o[p], or if
8418 // v is present, assign o[p] = v.
8420 RootedValue objectv(cx), propv(cx), valuev(cx);
8422 if (!JS_GetProperty(cx, opts, "object", &objectv) ||
8423 !JS_GetProperty(cx, opts, "property", &propv))
8424 return false;
8425 if (!objectv.isUndefined() && !propv.isUndefined()) {
8426 RootedObject object(cx, ToObject(cx, objectv));
8427 if (!object) {
8428 return false;
8431 RootedString string(cx, ToString(cx, propv));
8432 if (!string) {
8433 return false;
8435 RootedId id(cx);
8436 if (!JS_StringToId(cx, string, &id)) {
8437 return false;
8440 if (!JS_GetProperty(cx, opts, "value", &valuev)) {
8441 return false;
8444 js::shell::ShellAutoEntryMonitor sarep(cx);
8446 if (!valuev.isUndefined()) {
8447 if (!JS_SetPropertyById(cx, object, id, valuev)) {
8448 return false;
8450 } else {
8451 if (!JS_GetPropertyById(cx, object, id, &valuev)) {
8452 return false;
8456 return sarep.buildResult(cx, args.rval());
8460 // { ToString: v } --- Apply JS::ToString to v.
8462 RootedValue v(cx);
8464 if (!JS_GetProperty(cx, opts, "ToString", &v)) {
8465 return false;
8467 if (!v.isUndefined()) {
8468 js::shell::ShellAutoEntryMonitor sarep(cx);
8469 if (!JS::ToString(cx, v)) {
8470 return false;
8472 return sarep.buildResult(cx, args.rval());
8476 // { ToNumber: v } --- Apply JS::ToNumber to v.
8478 RootedValue v(cx);
8479 double dummy;
8481 if (!JS_GetProperty(cx, opts, "ToNumber", &v)) {
8482 return false;
8484 if (!v.isUndefined()) {
8485 js::shell::ShellAutoEntryMonitor sarep(cx);
8486 if (!JS::ToNumber(cx, v, &dummy)) {
8487 return false;
8489 return sarep.buildResult(cx, args.rval());
8493 // { eval: code } --- Apply ToString and then Evaluate to code.
8495 RootedValue code(cx), dummy(cx);
8497 if (!JS_GetProperty(cx, opts, "eval", &code)) {
8498 return false;
8500 if (!code.isUndefined()) {
8501 RootedString codeString(cx, ToString(cx, code));
8502 if (!codeString) {
8503 return false;
8506 AutoStableStringChars linearChars(cx);
8507 if (!linearChars.initTwoByte(cx, codeString)) {
8508 return false;
8510 JS::SourceText<char16_t> srcBuf;
8511 if (!srcBuf.initMaybeBorrowed(cx, linearChars)) {
8512 return false;
8515 CompileOptions options(cx);
8516 options.setIntroductionType("entryPoint eval")
8517 .setFileAndLine("entryPoint eval", 1);
8519 js::shell::ShellAutoEntryMonitor sarep(cx);
8520 if (!JS::Evaluate(cx, options, srcBuf, &dummy)) {
8521 return false;
8523 return sarep.buildResult(cx, args.rval());
8527 JS_ReportErrorASCII(cx, "bad 'params' object");
8528 return false;
8531 #ifndef __wasi__
8532 static bool WasmTextToBinary(JSContext* cx, unsigned argc, Value* vp) {
8533 CallArgs args = CallArgsFromVp(argc, vp);
8534 RootedObject callee(cx, &args.callee());
8536 if (!args.requireAtLeast(cx, "wasmTextToBinary", 1)) {
8537 return false;
8540 if (!args[0].isString()) {
8541 ReportUsageErrorASCII(cx, callee, "First argument must be a String");
8542 return false;
8545 size_t textLen = args[0].toString()->length();
8547 AutoStableStringChars twoByteChars(cx);
8548 if (!twoByteChars.initTwoByte(cx, args[0].toString())) {
8549 return false;
8552 wasm::Bytes bytes;
8553 UniqueChars error;
8554 if (!wasm::TextToBinary(twoByteChars.twoByteChars(), textLen, &bytes,
8555 &error)) {
8556 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_TEXT_FAIL,
8557 error.get() ? error.get() : "out of memory");
8558 return false;
8561 RootedObject binary(cx, JS_NewUint8Array(cx, bytes.length()));
8562 if (!binary) {
8563 return false;
8566 memcpy(binary->as<TypedArrayObject>().dataPointerUnshared(), bytes.begin(),
8567 bytes.length());
8569 args.rval().setObject(*binary);
8570 return true;
8573 # ifndef __AFL_HAVE_MANUAL_CONTROL
8574 # define __AFL_LOOP(x) true
8575 # endif
8577 static bool WasmLoop(JSContext* cx, unsigned argc, Value* vp) {
8578 CallArgs args = CallArgsFromVp(argc, vp);
8579 RootedObject callee(cx, &args.callee());
8581 if (args.length() < 1 || args.length() > 2) {
8582 ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
8583 return false;
8586 if (!args[0].isString()) {
8587 ReportUsageErrorASCII(cx, callee, "First argument must be a String");
8588 return false;
8591 RootedObject importObj(cx);
8592 if (!args.get(1).isUndefined()) {
8593 if (!args.get(1).isObject()) {
8594 ReportUsageErrorASCII(cx, callee,
8595 "Second argument, if present, must be an Object");
8596 return false;
8598 importObj = &args[1].toObject();
8601 RootedString givenPath(cx, args[0].toString());
8602 RootedString filename(cx, ResolvePath(cx, givenPath, RootRelative));
8603 if (!filename) {
8604 return false;
8607 while (__AFL_LOOP(1000)) {
8608 Rooted<JSObject*> ret(cx, FileAsTypedArray(cx, filename));
8609 if (!ret) {
8610 return false;
8613 Rooted<TypedArrayObject*> typedArray(cx, &ret->as<TypedArrayObject>());
8614 Rooted<WasmInstanceObject*> instanceObj(cx);
8615 if (!wasm::Eval(cx, typedArray, importObj, &instanceObj)) {
8616 // Clear any pending exceptions, we don't care about them
8617 cx->clearPendingException();
8621 # ifdef __AFL_HAVE_MANUAL_CONTROL // to silence unreachable code warning
8622 return true;
8623 # endif
8625 #endif // __wasi__
8627 static constexpr uint32_t DOM_OBJECT_SLOT = 0;
8628 static constexpr uint32_t DOM_OBJECT_SLOT2 = 1;
8630 static const JSClass* GetDomClass();
8632 static JSObject* GetDOMPrototype(JSContext* cx, JSObject* global);
8634 static const JSClass TransplantableDOMObjectClass = {
8635 "TransplantableDOMObject",
8636 JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(1)};
8638 static const JSClass TransplantableDOMProxyObjectClass =
8639 PROXY_CLASS_DEF("TransplantableDOMProxyObject",
8640 JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(1));
8642 class TransplantableDOMProxyHandler final : public ForwardingProxyHandler {
8643 public:
8644 static const TransplantableDOMProxyHandler singleton;
8645 static const char family;
8647 constexpr TransplantableDOMProxyHandler() : ForwardingProxyHandler(&family) {}
8649 // These two proxy traps are called in |js::DeadProxyTargetValue|, which in
8650 // turn is called when nuking proxies. Because this proxy can temporarily be
8651 // without an object in its private slot, see |EnsureExpandoObject|, the
8652 // default implementation inherited from ForwardingProxyHandler can't be used,
8653 // since it tries to derive the callable/constructible value from the target.
8654 bool isCallable(JSObject* obj) const override { return false; }
8655 bool isConstructor(JSObject* obj) const override { return false; }
8657 // Simplified implementation of |DOMProxyHandler::GetAndClearExpandoObject|.
8658 static JSObject* GetAndClearExpandoObject(JSObject* obj) {
8659 const Value& v = GetProxyPrivate(obj);
8660 if (v.isUndefined()) {
8661 return nullptr;
8664 JSObject* expandoObject = &v.toObject();
8665 SetProxyPrivate(obj, UndefinedValue());
8666 return expandoObject;
8669 // Simplified implementation of |DOMProxyHandler::EnsureExpandoObject|.
8670 static JSObject* EnsureExpandoObject(JSContext* cx, JS::HandleObject obj) {
8671 const Value& v = GetProxyPrivate(obj);
8672 if (v.isObject()) {
8673 return &v.toObject();
8675 MOZ_ASSERT(v.isUndefined());
8677 JSObject* expando = JS_NewObjectWithGivenProto(cx, nullptr, nullptr);
8678 if (!expando) {
8679 return nullptr;
8681 SetProxyPrivate(obj, ObjectValue(*expando));
8682 return expando;
8686 const TransplantableDOMProxyHandler TransplantableDOMProxyHandler::singleton;
8687 const char TransplantableDOMProxyHandler::family = 0;
8689 enum TransplantObjectSlots {
8690 TransplantSourceObject = 0,
8693 static bool TransplantObject(JSContext* cx, unsigned argc, Value* vp) {
8694 CallArgs args = CallArgsFromVp(argc, vp);
8695 RootedFunction callee(cx, &args.callee().as<JSFunction>());
8697 if (args.length() != 1 || !args[0].isObject()) {
8698 JS_ReportErrorASCII(cx, "transplant() must be called with an object");
8699 return false;
8702 // |newGlobal| needs to be a GlobalObject.
8703 RootedObject newGlobal(
8704 cx, js::CheckedUnwrapDynamic(&args[0].toObject(), cx,
8705 /* stopAtWindowProxy = */ false));
8706 if (!newGlobal) {
8707 ReportAccessDenied(cx);
8708 return false;
8710 if (!JS_IsGlobalObject(newGlobal)) {
8711 JS_ReportErrorNumberASCII(
8712 cx, GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
8713 "\"global\" passed to transplant()", "not a global object");
8714 return false;
8717 const Value& reserved =
8718 GetFunctionNativeReserved(callee, TransplantSourceObject);
8719 RootedObject source(cx, CheckedUnwrapStatic(&reserved.toObject()));
8720 if (!source) {
8721 ReportAccessDenied(cx);
8722 return false;
8724 if (JS_IsDeadWrapper(source)) {
8725 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
8726 return false;
8728 MOZ_ASSERT(source->getClass()->isDOMClass());
8730 // The following steps aim to replicate the behavior of UpdateReflectorGlobal
8731 // in dom/bindings/BindingUtils.cpp. In detail:
8732 // 1. Check the recursion depth using checkConservative.
8733 // 2. Enter the target compartment.
8734 // 3. Clone the source object using JS_CloneObject.
8735 // 4. Check if new wrappers can be created if source and target are in
8736 // different compartments.
8737 // 5. Copy all properties from source to a temporary holder object.
8738 // 6. Actually transplant the object.
8739 // 7. And finally copy the properties back to the source object.
8741 // As an extension to the algorithm in UpdateReflectorGlobal, we also allow
8742 // to transplant an object into the same compartment as the source object to
8743 // cover all operations supported by JS_TransplantObject.
8745 AutoCheckRecursionLimit recursion(cx);
8746 if (!recursion.checkConservative(cx)) {
8747 return false;
8750 bool isProxy = IsProxy(source);
8751 RootedObject expandoObject(cx);
8752 if (isProxy) {
8753 expandoObject =
8754 TransplantableDOMProxyHandler::GetAndClearExpandoObject(source);
8757 JSAutoRealm ar(cx, newGlobal);
8759 RootedObject proto(cx);
8760 if (JS::GetClass(source) == GetDomClass()) {
8761 proto = GetDOMPrototype(cx, newGlobal);
8762 } else {
8763 proto = JS::GetRealmObjectPrototype(cx);
8765 if (!proto) {
8766 return false;
8769 RootedObject target(cx, JS_CloneObject(cx, source, proto));
8770 if (!target) {
8771 return false;
8774 if (JS::GetCompartment(source) != JS::GetCompartment(target) &&
8775 !AllowNewWrapper(JS::GetCompartment(source), target)) {
8776 JS_ReportErrorASCII(cx, "Cannot transplant into nuked compartment");
8777 return false;
8780 RootedObject copyFrom(cx, isProxy ? expandoObject : source);
8781 RootedObject propertyHolder(cx,
8782 JS_NewObjectWithGivenProto(cx, nullptr, nullptr));
8783 if (!propertyHolder) {
8784 return false;
8787 if (!JS_CopyOwnPropertiesAndPrivateFields(cx, propertyHolder, copyFrom)) {
8788 return false;
8791 JS::SetReservedSlot(target, DOM_OBJECT_SLOT,
8792 JS::GetReservedSlot(source, DOM_OBJECT_SLOT));
8793 JS::SetReservedSlot(source, DOM_OBJECT_SLOT, JS::PrivateValue(nullptr));
8794 if (JS::GetClass(source) == GetDomClass()) {
8795 JS::SetReservedSlot(target, DOM_OBJECT_SLOT2,
8796 JS::GetReservedSlot(source, DOM_OBJECT_SLOT2));
8797 JS::SetReservedSlot(source, DOM_OBJECT_SLOT2, UndefinedValue());
8800 source = JS_TransplantObject(cx, source, target);
8801 if (!source) {
8802 return false;
8805 RootedObject copyTo(cx);
8806 if (isProxy) {
8807 copyTo = TransplantableDOMProxyHandler::EnsureExpandoObject(cx, source);
8808 if (!copyTo) {
8809 return false;
8811 } else {
8812 copyTo = source;
8814 if (!JS_CopyOwnPropertiesAndPrivateFields(cx, copyTo, propertyHolder)) {
8815 return false;
8818 args.rval().setUndefined();
8819 return true;
8822 static bool TransplantableObject(JSContext* cx, unsigned argc, Value* vp) {
8823 CallArgs args = CallArgsFromVp(argc, vp);
8824 RootedObject callee(cx, &args.callee());
8826 if (args.length() > 1) {
8827 ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
8828 return false;
8831 bool createProxy = false;
8832 RootedObject source(cx);
8833 if (args.length() == 1 && !args[0].isUndefined()) {
8834 if (!args[0].isObject()) {
8835 ReportUsageErrorASCII(cx, callee, "Argument must be an object");
8836 return false;
8839 RootedObject options(cx, &args[0].toObject());
8840 RootedValue value(cx);
8842 if (!JS_GetProperty(cx, options, "proxy", &value)) {
8843 return false;
8845 createProxy = JS::ToBoolean(value);
8847 if (!JS_GetProperty(cx, options, "object", &value)) {
8848 return false;
8850 if (!value.isUndefined()) {
8851 if (!value.isObject()) {
8852 ReportUsageErrorASCII(cx, callee, "'object' option must be an object");
8853 return false;
8856 source = &value.toObject();
8857 if (JS::GetClass(source) != GetDomClass()) {
8858 ReportUsageErrorASCII(cx, callee, "Object not a FakeDOMObject");
8859 return false;
8862 // |source| must be a tenured object to be transplantable.
8863 if (gc::IsInsideNursery(source)) {
8864 JS_GC(cx);
8866 MOZ_ASSERT(!gc::IsInsideNursery(source),
8867 "Live objects should be tenured after one GC, because "
8868 "the nursery has only a single generation");
8873 if (!source) {
8874 if (!createProxy) {
8875 source = NewBuiltinClassInstance(cx, &TransplantableDOMObjectClass,
8876 TenuredObject);
8877 if (!source) {
8878 return false;
8881 JS::SetReservedSlot(source, DOM_OBJECT_SLOT, JS::PrivateValue(nullptr));
8882 } else {
8883 JSObject* expando = JS_NewPlainObject(cx);
8884 if (!expando) {
8885 return false;
8887 RootedValue expandoVal(cx, ObjectValue(*expando));
8889 ProxyOptions options;
8890 options.setClass(&TransplantableDOMProxyObjectClass);
8891 options.setLazyProto(true);
8893 source = NewProxyObject(cx, &TransplantableDOMProxyHandler::singleton,
8894 expandoVal, nullptr, options);
8895 if (!source) {
8896 return false;
8899 SetProxyReservedSlot(source, DOM_OBJECT_SLOT, JS::PrivateValue(nullptr));
8903 jsid emptyId = NameToId(cx->names().empty_);
8904 RootedObject transplant(
8905 cx, NewFunctionByIdWithReserved(cx, TransplantObject, 0, 0, emptyId));
8906 if (!transplant) {
8907 return false;
8910 SetFunctionNativeReserved(transplant, TransplantSourceObject,
8911 ObjectValue(*source));
8913 RootedObject result(cx, JS_NewPlainObject(cx));
8914 if (!result) {
8915 return false;
8918 RootedValue sourceVal(cx, ObjectValue(*source));
8919 RootedValue transplantVal(cx, ObjectValue(*transplant));
8920 if (!JS_DefineProperty(cx, result, "object", sourceVal, 0) ||
8921 !JS_DefineProperty(cx, result, "transplant", transplantVal, 0)) {
8922 return false;
8925 args.rval().setObject(*result);
8926 return true;
8929 #ifdef DEBUG
8930 static bool DebugGetQueuedJobs(JSContext* cx, unsigned argc, Value* vp) {
8931 CallArgs args = CallArgsFromVp(argc, vp);
8933 JSObject* jobs = js::GetJobsInInternalJobQueue(cx);
8934 if (!jobs) {
8935 return false;
8938 args.rval().setObject(*jobs);
8939 return true;
8941 #endif
8943 #ifdef FUZZING_INTERFACES
8944 extern "C" {
8945 size_t gluesmith(uint8_t* data, size_t size, uint8_t* out, size_t maxsize);
8948 static bool GetWasmSmithModule(JSContext* cx, unsigned argc, Value* vp) {
8949 CallArgs args = CallArgsFromVp(argc, vp);
8950 RootedObject callee(cx, &args.callee());
8952 if (args.length() != 1) {
8953 ReportUsageErrorASCII(cx, callee, "Wrong number of arguments");
8954 return false;
8957 if (!args[0].isObject() || !args[0].toObject().is<ArrayBufferObject>()) {
8958 ReportUsageErrorASCII(cx, callee, "Argument must be ArrayBuffer.");
8959 return false;
8962 ArrayBufferObject* arrayBuffer = &args[0].toObject().as<ArrayBufferObject>();
8963 size_t length = arrayBuffer->byteLength();
8964 uint8_t* data = arrayBuffer->dataPointer();
8966 const size_t maxModuleSize = 4096;
8967 uint8_t tmp[maxModuleSize];
8969 size_t outSize = gluesmith(data, length, tmp, maxModuleSize);
8970 if (!outSize) {
8971 JS_ReportErrorASCII(cx, "Generated module is too large.");
8972 return false;
8975 JS::Rooted<JSObject*> outArr(cx, JS_NewUint8ClampedArray(cx, outSize));
8976 if (!outArr) {
8977 return false;
8981 JS::AutoCheckCannotGC nogc;
8982 bool isShared;
8983 uint8_t* data = JS_GetUint8ClampedArrayData(outArr, &isShared, nogc);
8984 MOZ_RELEASE_ASSERT(!isShared);
8985 memcpy(data, tmp, outSize);
8988 args.rval().setObject(*outArr);
8989 return true;
8992 #endif
8994 static bool IsValidJSON(JSContext* cx, unsigned argc, Value* vp) {
8995 CallArgs args = CallArgsFromVp(argc, vp);
8996 RootedObject callee(cx, &args.callee());
8998 if (!args.get(0).isString()) {
8999 ReportUsageErrorASCII(cx, callee, "First argument must be a String");
9000 return false;
9003 JS::Rooted<JSLinearString*> input(cx, args[0].toString()->ensureLinear(cx));
9004 if (!input) {
9005 return false;
9008 bool result;
9009 if (input->hasLatin1Chars()) {
9010 JS::AutoCheckCannotGC nogc;
9011 result = JS::IsValidJSON(input->latin1Chars(nogc), input->length());
9012 } else {
9013 JS::AutoCheckCannotGC nogc;
9014 result = JS::IsValidJSON(input->twoByteChars(nogc), input->length());
9017 args.rval().setBoolean(result);
9018 return true;
9021 // clang-format off
9022 static const JSFunctionSpecWithHelp shell_functions[] = {
9023 JS_FN_HELP("options", Options, 0, 0,
9024 "options([option ...])",
9025 " Get or toggle JavaScript options."),
9027 JS_FN_HELP("load", Load, 1, 0,
9028 "load(['foo.js' ...])",
9029 " Load files named by string arguments. Filename is relative to the\n"
9030 " current working directory."),
9032 JS_FN_HELP("loadRelativeToScript", LoadScriptRelativeToScript, 1, 0,
9033 "loadRelativeToScript(['foo.js' ...])",
9034 " Load files named by string arguments. Filename is relative to the\n"
9035 " calling script."),
9037 JS_FN_HELP("evaluate", Evaluate, 2, 0,
9038 "evaluate(code[, options])",
9039 " Evaluate code as though it were the contents of a file.\n"
9040 " options is an optional object that may have these properties:\n"
9041 " isRunOnce: use the isRunOnce compiler option (default: false)\n"
9042 " noScriptRval: use the no-script-rval compiler option (default: false)\n"
9043 " fileName: filename for error messages and debug info\n"
9044 " skipFileNameValidation: skip the filename-validation callback\n"
9045 " lineNumber: starting line number for error messages and debug info\n"
9046 " columnNumber: starting column number for error messages and debug info\n"
9047 " global: global in which to execute the code\n"
9048 " newContext: if true, create and use a new cx (default: false)\n"
9049 " catchTermination: if true, catch termination (failure without\n"
9050 " an exception value, as for slow scripts or out-of-memory)\n"
9051 " and return 'terminated'\n"
9052 " element: if present with value |v|, convert |v| to an object |o| and\n"
9053 " mark the source as being attached to the DOM element |o|. If the\n"
9054 " property is omitted or |v| is null, don't attribute the source to\n"
9055 " any DOM element.\n"
9056 " elementAttributeName: if present and not undefined, the name of\n"
9057 " property of 'element' that holds this code. This is what\n"
9058 " Debugger.Source.prototype.elementAttributeName returns.\n"
9059 " sourceMapURL: if present with value |v|, convert |v| to a string, and\n"
9060 " provide that as the code's source map URL. If omitted, attach no\n"
9061 " source map URL to the code (although the code may provide one itself,\n"
9062 " via a //#sourceMappingURL comment).\n"
9063 " sourceIsLazy: if present and true, indicates that, after compilation, \n"
9064 " script source should not be cached by the JS engine and should be \n"
9065 " lazily loaded from the embedding as-needed.\n"
9066 " forceFullParse: if present and true, disable syntax-parse.\n"
9067 " loadBytecode: if true, and if the source is a CacheEntryObject,\n"
9068 " the bytecode would be loaded and decoded from the cache entry instead\n"
9069 " of being parsed, then it would be executed as usual.\n"
9070 " saveIncrementalBytecode: if true, and if the source is a\n"
9071 " CacheEntryObject, the bytecode would be incrementally encoded and\n"
9072 " saved into the cache entry.\n"
9073 " execute: if false, do not execute the script, but do parse and/or\n"
9074 " transcode.\n"
9075 " assertEqBytecode: if true, and if both loadBytecode and either\n"
9076 " saveIncrementalBytecode is true, then the loaded\n"
9077 " bytecode and the encoded bytecode are compared.\n"
9078 " and an assertion is raised if they differ.\n"
9079 " envChainObject: object to put on the scope chain, with its fields added\n"
9080 " as var bindings, akin to how elements are added to the environment in\n"
9081 " event handlers in Gecko.\n"
9084 JS_FN_HELP("run", Run, 1, 0,
9085 "run('foo.js')",
9086 " Run the file named by the first argument, returning the number of\n"
9087 " of milliseconds spent compiling and executing it."),
9089 JS_FN_HELP("readline", ReadLine, 0, 0,
9090 "readline()",
9091 " Read a single line from stdin."),
9093 JS_FN_HELP("readlineBuf", ReadLineBuf, 1, 0,
9094 "readlineBuf([ buf ])",
9095 " Emulate readline() on the specified string. The first call with a string\n"
9096 " argument sets the source buffer. Subsequent calls without an argument\n"
9097 " then read from this buffer line by line.\n"),
9099 JS_FN_HELP("print", Print, 0, 0,
9100 "print([exp ...])",
9101 " Evaluate and print expressions to stdout."),
9103 JS_FN_HELP("printErr", PrintErr, 0, 0,
9104 "printErr([exp ...])",
9105 " Evaluate and print expressions to stderr."),
9107 JS_FN_HELP("putstr", PutStr, 0, 0,
9108 "putstr([exp])",
9109 " Evaluate and print expression without newline."),
9111 JS_FN_HELP("dateNow", Now, 0, 0,
9112 "dateNow()",
9113 " Return the current time with sub-ms precision."),
9115 JS_FN_HELP("help", Help, 0, 0,
9116 "help([function or interface object or /pattern/])",
9117 " Display usage and help messages."),
9119 JS_FN_HELP("quit", Quit, 0, 0,
9120 "quit()",
9121 " Quit the shell."),
9123 JS_FN_HELP("assertEq", AssertEq, 2, 0,
9124 "assertEq(actual, expected[, msg])",
9125 " Throw if the first two arguments are not the same (both +0 or both -0,\n"
9126 " both NaN, or non-zero and ===)."),
9128 JS_FN_HELP("startTimingMutator", StartTimingMutator, 0, 0,
9129 "startTimingMutator()",
9130 " Start accounting time to mutator vs GC."),
9132 JS_FN_HELP("stopTimingMutator", StopTimingMutator, 0, 0,
9133 "stopTimingMutator()",
9134 " Stop accounting time to mutator vs GC and dump the results."),
9136 JS_FN_HELP("throwError", ThrowError, 0, 0,
9137 "throwError()",
9138 " Throw an error from JS_ReportError."),
9140 JS_FN_HELP("createErrorReport", CreateErrorReport, 1, 0,
9141 "createErrorReport(value)",
9142 " Create an JS::ErrorReportBuilder object from the given value and serialize\n"
9143 " to an object."),
9145 #if defined(DEBUG) || defined(JS_JITSPEW)
9146 JS_FN_HELP("disassemble", DisassembleToString, 1, 0,
9147 "disassemble([fun/code])",
9148 " Return the disassembly for the given function or code.\n"
9149 " All disassembly functions take these options as leading string arguments:\n"
9150 " \"-r\" (disassemble recursively)\n"
9151 " \"-l\" (show line numbers)\n"
9152 " \"-S\" (omit source notes)"),
9154 JS_FN_HELP("dis", Disassemble, 1, 0,
9155 "dis([fun/code])",
9156 " Disassemble functions into bytecodes."),
9158 JS_FN_HELP("disfile", DisassFile, 1, 0,
9159 "disfile('foo.js')",
9160 " Disassemble script file into bytecodes.\n"),
9162 JS_FN_HELP("dissrc", DisassWithSrc, 1, 0,
9163 "dissrc([fun/code])",
9164 " Disassemble functions with source lines."),
9166 JS_FN_HELP("notes", Notes, 1, 0,
9167 "notes([fun])",
9168 " Show source notes for functions."),
9170 JS_FN_HELP("stackDump", StackDump, 3, 0,
9171 "stackDump(showArgs, showLocals, showThisProps)",
9172 " Tries to print a lot of information about the current stack. \n"
9173 " Similar to the DumpJSStack() function in the browser."),
9175 #endif
9177 JS_FN_HELP("getslx", GetSLX, 1, 0,
9178 "getslx(obj)",
9179 " Get script line extent."),
9181 JS_FN_HELP("evalcx", EvalInContext, 1, 0,
9182 "evalcx(s[, o])",
9183 " Evaluate s in optional sandbox object o.\n"
9184 " if (s == '' && !o) return new o with eager standard classes\n"
9185 " if (s == 'lazy' && !o) return new o with lazy standard classes"),
9187 JS_FN_HELP("evalInWorker", EvalInWorker, 1, 0,
9188 "evalInWorker(str)",
9189 " Evaluate 'str' in a separate thread with its own runtime.\n"),
9191 JS_FN_HELP("getSharedObject", GetSharedObject, 0, 0,
9192 "getSharedObject()",
9193 " Retrieve the shared object from the cross-worker mailbox.\n"
9194 " The object retrieved may not be identical to the object that was\n"
9195 " installed, but it references the same shared memory.\n"
9196 " getSharedObject performs an ordering memory barrier.\n"),
9198 JS_FN_HELP("setSharedObject", SetSharedObject, 0, 0,
9199 "setSharedObject(obj)",
9200 " Install the shared object in the cross-worker mailbox. The object\n"
9201 " may be null. setSharedObject performs an ordering memory barrier.\n"),
9203 JS_FN_HELP("getSharedArrayBuffer", GetSharedObject, 0, 0,
9204 "getSharedArrayBuffer()",
9205 " Obsolete alias for getSharedObject().\n"),
9207 JS_FN_HELP("setSharedArrayBuffer", SetSharedObject, 0, 0,
9208 "setSharedArrayBuffer(obj)",
9209 " Obsolete alias for setSharedObject(obj).\n"),
9211 JS_FN_HELP("shapeOf", ShapeOf, 1, 0,
9212 "shapeOf(obj)",
9213 " Get the shape of obj (an implementation detail)."),
9215 #ifdef DEBUG
9216 JS_FN_HELP("arrayInfo", ArrayInfo, 1, 0,
9217 "arrayInfo(a1, a2, ...)",
9218 " Report statistics about arrays."),
9219 #endif
9221 JS_FN_HELP("sleep", Sleep_fn, 1, 0,
9222 "sleep(dt)",
9223 " Sleep for dt seconds."),
9225 JS_FN_HELP("parseModule", ParseModule, 1, 0,
9226 "parseModule(code)",
9227 " Parses source text as a module and returns a ModuleObject wrapper object."),
9229 JS_FN_HELP("instantiateModuleStencil", InstantiateModuleStencil, 1, 0,
9230 "instantiateModuleStencil(stencil, [options])",
9231 " Instantiates the given stencil as module, and return the module object."),
9233 JS_FN_HELP("instantiateModuleStencilXDR", InstantiateModuleStencilXDR, 1, 0,
9234 "instantiateModuleStencilXDR(stencil, [options])",
9235 " Reads the given stencil XDR object, instantiates the stencil as module, and"
9236 " return the module object."),
9238 JS_FN_HELP("registerModule", RegisterModule, 2, 0,
9239 "registerModule(specifier, module)",
9240 " Register a module with the module loader, so that subsequent import from\n"
9241 " |specifier| will resolve to |module|. Returns |module|."),
9243 JS_FN_HELP("clearModules", ClearModules, 0, 0,
9244 "clearModules()",
9245 " Clear knowledge of all loaded modules."),
9247 JS_FN_HELP("moduleLink", ModuleLink, 1, 0,
9248 "moduleLink(moduleOjbect)",
9249 " Link a module graph, performing the spec's Link method."),
9251 JS_FN_HELP("moduleEvaluate", ModuleEvaluate, 1, 0,
9252 "moduleEvaluate(moduleOjbect)",
9253 " Evaluate a module graph, performing the spec's Evaluate method."),
9255 JS_FN_HELP("getModuleEnvironmentNames", GetModuleEnvironmentNames, 1, 0,
9256 "getModuleEnvironmentNames(module)",
9257 " Get the list of a module environment's bound names for a specified module.\n"),
9259 JS_FN_HELP("getModuleEnvironmentValue", GetModuleEnvironmentValue, 2, 0,
9260 "getModuleEnvironmentValue(module, name)",
9261 " Get the value of a bound name in a module environment.\n"),
9263 JS_FN_HELP("dumpStencil", DumpStencil, 1, 0,
9264 "dumpStencil(code, [options])",
9265 " Parses a string and returns string that represents stencil.\n"
9266 " If present, |options| may have properties saying how the code should be\n"
9267 " compiled:\n"
9268 " module: if present and true, compile the source as module.\n"
9269 " smoosh: if present and true, use SmooshMonkey.\n"
9270 " CompileOptions-related properties of evaluate function's option can also\n"
9271 " be used."),
9273 JS_FN_HELP("parse", Parse, 1, 0,
9274 "parse(code, [options])",
9275 " Parses a string, potentially throwing. If present, |options| may\n"
9276 " have properties saying how the code should be compiled:\n"
9277 " module: if present and true, compile the source as module.\n"
9278 " smoosh: if present and true, use SmooshMonkey.\n"
9279 " CompileOptions-related properties of evaluate function's option can also\n"
9280 " be used. except forceFullParse. This function always use full parse."),
9282 JS_FN_HELP("syntaxParse", SyntaxParse, 1, 0,
9283 "syntaxParse(code)",
9284 " Check the syntax of a string, returning success value"),
9286 JS_FN_HELP("offThreadCompileModuleToStencil", OffThreadCompileModuleToStencil, 1, 0,
9287 "offThreadCompileModuleToStencil(code[, options])",
9288 " Compile |code| on a helper thread, returning a job ID. To wait for the\n"
9289 " compilation to finish and and get the module stencil object call\n"
9290 " |finishOffThreadStencil| passing the job ID."),
9292 JS_FN_HELP("offThreadDecodeStencil", OffThreadDecodeStencil, 1, 0,
9293 "offThreadDecodeStencil(cacheEntry[, options])",
9294 " Decode |code| on a helper thread, returning a job ID. To wait for the\n"
9295 " decoding to finish and run the code, call |finishOffThreadStencil| passing\n"
9296 " the job ID. If present, |options| may have properties saying how the code\n"
9297 " should be compiled (see also offThreadCompileToStencil)."),
9299 JS_FN_HELP("offThreadCompileToStencil", OffThreadCompileToStencil, 1, 0,
9300 "offThreadCompileToStencil(code[, options])",
9301 " Compile |code| on a helper thread, returning a job ID. To wait for the\n"
9302 " compilation to finish and get the stencil object, call\n"
9303 " |finishOffThreadStencil| passing the job ID. If present, \n"
9304 " |options| may have properties saying how the code should be compiled:\n"
9305 " noScriptRval: use the no-script-rval compiler option (default: false)\n"
9306 " fileName: filename for error messages and debug info\n"
9307 " lineNumber: starting line number for error messages and debug info\n"
9308 " columnNumber: starting column number for error messages and debug info\n"
9309 " element: if present with value |v|, convert |v| to an object |o| and\n"
9310 " mark the source as being attached to the DOM element |o|. If the\n"
9311 " property is omitted or |v| is null, don't attribute the source to\n"
9312 " any DOM element.\n"
9313 " elementAttributeName: if present and not undefined, the name of\n"
9314 " property of 'element' that holds this code. This is what\n"
9315 " Debugger.Source.prototype.elementAttributeName returns."),
9317 JS_FN_HELP("finishOffThreadStencil", FinishOffThreadStencil, 0, 0,
9318 "finishOffThreadStencil([jobID])",
9319 " Wait for an off-thread compilation or decode job to complete. The job ID\n"
9320 " can be ommitted if there is only one job pending. If an error occurred,\n"
9321 " throw the appropriate exception; otherwise, return the stencil object,"
9322 " that can be passed to |evalStencil|."),
9324 JS_FN_HELP("timeout", Timeout, 1, 0,
9325 "timeout([seconds], [func])",
9326 " Get/Set the limit in seconds for the execution time for the current context.\n"
9327 " When the timeout expires the current interrupt callback is invoked.\n"
9328 " The timeout is used just once. If the callback returns a falsy value, the\n"
9329 " script is aborted. A negative value for seconds (this is the default) cancels\n"
9330 " any pending timeout.\n"
9331 " If a second argument is provided, it is installed as the interrupt handler,\n"
9332 " exactly as if by |setInterruptCallback|.\n"),
9334 JS_FN_HELP("interruptIf", InterruptIf, 1, 0,
9335 "interruptIf(cond)",
9336 " Requests interrupt callback if cond is true. If a callback function is set via\n"
9337 " |timeout| or |setInterruptCallback|, it will be called. No-op otherwise."),
9339 JS_FN_HELP("invokeInterruptCallback", InvokeInterruptCallbackWrapper, 0, 0,
9340 "invokeInterruptCallback(fun)",
9341 " Forcefully set the interrupt flag and invoke the interrupt handler. If a\n"
9342 " callback function is set via |timeout| or |setInterruptCallback|, it will\n"
9343 " be called. Before returning, fun is called with the return value of the\n"
9344 " interrupt handler."),
9346 JS_FN_HELP("setInterruptCallback", SetInterruptCallback, 1, 0,
9347 "setInterruptCallback(func)",
9348 " Sets func as the interrupt callback function.\n"
9349 " Calling this function will replace any callback set by |timeout|.\n"
9350 " If the callback returns a falsy value, the script is aborted.\n"),
9352 JS_FN_HELP("setJitCompilerOption", SetJitCompilerOption, 2, 0,
9353 "setJitCompilerOption(<option>, <number>)",
9354 " Set a compiler option indexed in JSCompileOption enum to a number.\n"),
9355 #ifdef DEBUG
9356 JS_FN_HELP("interruptRegexp", InterruptRegexp, 2, 0,
9357 "interruptRegexp(<regexp>, <string>)",
9358 " Interrrupt the execution of regular expression.\n"),
9359 #endif
9360 JS_FN_HELP("checkRegExpSyntax", CheckRegExpSyntax, 1, 0,
9361 "checkRegExpSyntax(<string>)",
9362 " Return undefined if the string parses as a RegExp. If the string does not\n"
9363 " parse correctly, return the SyntaxError that occurred."),
9365 JS_FN_HELP("enableLastWarning", EnableLastWarning, 0, 0,
9366 "enableLastWarning()",
9367 " Enable storing the last warning."),
9368 JS_FN_HELP("disableLastWarning", DisableLastWarning, 0, 0,
9369 "disableLastWarning()",
9370 " Disable storing the last warning."),
9372 JS_FN_HELP("getLastWarning", GetLastWarning, 0, 0,
9373 "getLastWarning()",
9374 " Returns an object that represents the last warning."),
9376 JS_FN_HELP("clearLastWarning", ClearLastWarning, 0, 0,
9377 "clearLastWarning()",
9378 " Clear the last warning."),
9380 JS_FN_HELP("elapsed", Elapsed, 0, 0,
9381 "elapsed()",
9382 " Execution time elapsed for the current thread."),
9384 JS_FN_HELP("decompileFunction", DecompileFunction, 1, 0,
9385 "decompileFunction(func)",
9386 " Decompile a function."),
9388 JS_FN_HELP("decompileThis", DecompileThisScript, 0, 0,
9389 "decompileThis()",
9390 " Decompile the currently executing script."),
9392 JS_FN_HELP("valueToSource", ValueToSource, 1, 0,
9393 "valueToSource(value)",
9394 " Format a value for inspection."),
9396 JS_FN_HELP("thisFilename", ThisFilename, 0, 0,
9397 "thisFilename()",
9398 " Return the filename of the current script"),
9400 JS_FN_HELP("newGlobal", NewGlobal, 1, 0,
9401 "newGlobal([options])",
9402 " Return a new global object/realm. The new global is created in the\n"
9403 " 'newGlobal' function object's compartment and zone, unless the\n"
9404 " '--more-compartments' command-line flag was given, in which case new\n"
9405 " globals get a fresh compartment and zone. If options is given, it may\n"
9406 " have any of the following properties:\n"
9407 " sameCompartmentAs: If an object, the global will be in the same\n"
9408 " compartment and zone as the given object.\n"
9409 " sameZoneAs: The global will be in a new compartment in the same zone\n"
9410 " as the given object.\n"
9411 " newCompartment: If true, the global will always be created in a new\n"
9412 " compartment and zone.\n"
9413 " invisibleToDebugger: If true, the global will be invisible to the\n"
9414 " debugger (default false)\n"
9415 " discardSource: If true, discard source after compiling a script\n"
9416 " (default false).\n"
9417 " useWindowProxy: the global will be created with a WindowProxy attached. In this\n"
9418 " case, the WindowProxy will be returned.\n"
9419 " freezeBuiltins: certain builtin constructors will be frozen when created and\n"
9420 " their prototypes will be sealed. These constructors will be defined on the\n"
9421 " global as non-configurable and non-writable.\n"
9422 " immutablePrototype: whether the global's prototype is immutable.\n"
9423 " principal: if present, its value converted to a number must be an\n"
9424 " integer that fits in 32 bits; use that as the new realm's\n"
9425 " principal. Shell principals are toys, meant only for testing; one\n"
9426 " shell principal subsumes another if its set bits are a superset of\n"
9427 " the other's. Thus, a principal of 0 subsumes nothing, while a\n"
9428 " principals of ~0 subsumes all other principals. The absence of a\n"
9429 " principal is treated as if its bits were 0xffff, for subsumption\n"
9430 " purposes. If this property is omitted, supply no principal.\n"
9431 " systemPrincipal: If true, use the shell's trusted principals for the\n"
9432 " new realm. This creates a realm that's marked as a 'system' realm."),
9434 JS_FN_HELP("nukeAllCCWs", NukeAllCCWs, 0, 0,
9435 "nukeAllCCWs()",
9436 " Like nukeCCW, but for all CrossCompartmentWrappers targeting the current realm."),
9438 JS_FN_HELP("recomputeWrappers", RecomputeWrappers, 2, 0,
9439 "recomputeWrappers([src, [target]])",
9440 " Recompute all cross-compartment wrappers. src and target are both optional\n"
9441 " and can be used to filter source or target compartments: the unwrapped\n"
9442 " object's compartment is used as CompartmentFilter.\n"),
9444 JS_FN_HELP("dumpObjectWrappers", DumpObjectWrappers, 2, 0,
9445 "dumpObjectWrappers()",
9446 " Print information about cross-compartment object wrappers.\n"),
9448 JS_FN_HELP("wrapWithProto", WrapWithProto, 2, 0,
9449 "wrapWithProto(obj)",
9450 " Wrap an object into a noop wrapper with prototype semantics."),
9452 JS_FN_HELP("createExternalArrayBuffer", CreateExternalArrayBuffer, 1, 0,
9453 "createExternalArrayBuffer(size)",
9454 " Create an array buffer that has external data of size."),
9456 JS_FN_HELP("createMappedArrayBuffer", CreateMappedArrayBuffer, 1, 0,
9457 "createMappedArrayBuffer(filename, [offset, [size]])",
9458 " Create an array buffer that mmaps the given file."),
9460 JS_FN_HELP("createUserArrayBuffer", CreateUserArrayBuffer, 1, 0,
9461 "createUserArrayBuffer(size)",
9462 " Create an array buffer that uses user-controlled memory."),
9464 JS_FN_HELP("addPromiseReactions", AddPromiseReactions, 3, 0,
9465 "addPromiseReactions(promise, onResolve, onReject)",
9466 " Calls the JS::AddPromiseReactions JSAPI function with the given arguments."),
9468 JS_FN_HELP("ignoreUnhandledRejections", IgnoreUnhandledRejections, 0, 0,
9469 "ignoreUnhandledRejections()",
9470 " By default, js shell tracks unhandled promise rejections and reports\n"
9471 " them at the end of the exectuion. If a testcase isn't interested\n"
9472 " in those rejections, call this to stop tracking and reporting."),
9474 JS_FN_HELP("getMaxArgs", GetMaxArgs, 0, 0,
9475 "getMaxArgs()",
9476 " Return the maximum number of supported args for a call."),
9478 JS_FN_HELP("createIsHTMLDDA", CreateIsHTMLDDA, 0, 0,
9479 "createIsHTMLDDA()",
9480 " Return an object |obj| that \"looks like\" the |document.all| object in\n"
9481 " browsers in certain ways: |typeof obj === \"undefined\"|, |obj == null|\n"
9482 " and |obj == undefined| (vice versa for !=), |ToBoolean(obj) === false|,\n"
9483 " and when called with no arguments or the single argument \"\" returns\n"
9484 " null. (Calling |obj| any other way crashes or throws an exception.)\n"
9485 " This function implements the exact requirements of the $262.IsHTMLDDA\n"
9486 " property in test262."),
9488 JS_FN_HELP("cacheEntry", CacheEntry, 1, 0,
9489 "cacheEntry(code)",
9490 " Return a new opaque object which emulates a cache entry of a script. This\n"
9491 " object encapsulates the code and its cached content. The cache entry is filled\n"
9492 " and read by the \"evaluate\" function by using it in place of the source, and\n"
9493 " by setting \"saveIncrementalBytecode\" and \"loadBytecode\" options."),
9495 JS_FN_HELP("streamCacheEntry", StreamCacheEntryObject::construct, 1, 0,
9496 "streamCacheEntry(buffer)",
9497 " Create a shell-only object that holds wasm bytecode and can be streaming-\n"
9498 " compiled and cached by WebAssembly.{compile,instantiate}Streaming(). On a\n"
9499 " second compilation of the same cache entry, the cached code will be used."),
9501 JS_FN_HELP("printProfilerEvents", PrintProfilerEvents, 0, 0,
9502 "printProfilerEvents()",
9503 " Register a callback with the profiler that prints javascript profiler events\n"
9504 " to stderr. Callback is only registered if profiling is enabled."),
9506 JS_FN_HELP("enableSingleStepProfiling", EnableSingleStepProfiling, 0, 0,
9507 "enableSingleStepProfiling()",
9508 " This function will fail on platforms that don't support single-step profiling\n"
9509 " (currently ARM and MIPS64 support it). When enabled, at every instruction a\n"
9510 " backtrace will be recorded and stored in an array. Adjacent duplicate backtraces\n"
9511 " are discarded."),
9513 JS_FN_HELP("disableSingleStepProfiling", DisableSingleStepProfiling, 0, 0,
9514 "disableSingleStepProfiling()",
9515 " Return the array of backtraces recorded by enableSingleStepProfiling."),
9517 JS_FN_HELP("enableGeckoProfiling", EnableGeckoProfiling, 0, 0,
9518 "enableGeckoProfiling()",
9519 " Enables Gecko Profiler instrumentation and corresponding assertions, with slow\n"
9520 " assertions disabled.\n"),
9522 JS_FN_HELP("enableGeckoProfilingWithSlowAssertions", EnableGeckoProfilingWithSlowAssertions, 0, 0,
9523 "enableGeckoProfilingWithSlowAssertions()",
9524 " Enables Gecko Profiler instrumentation and corresponding assertions, with slow\n"
9525 " assertions enabled.\n"),
9527 JS_FN_HELP("disableGeckoProfiling", DisableGeckoProfiling, 0, 0,
9528 "disableGeckoProfiling()",
9529 " Disables Gecko Profiler instrumentation"),
9531 JS_FN_HELP("isLatin1", IsLatin1, 1, 0,
9532 "isLatin1(s)",
9533 " Return true iff the string's characters are stored as Latin1."),
9535 JS_FN_HELP("stackPointerInfo", StackPointerInfo, 0, 0,
9536 "stackPointerInfo()",
9537 " Return an int32 value which corresponds to the offset of the latest stack\n"
9538 " pointer, such that one can take the differences of 2 to estimate a frame-size."),
9540 JS_FN_HELP("entryPoints", EntryPoints, 1, 0,
9541 "entryPoints(params)",
9542 "Carry out some JSAPI operation as directed by |params|, and return an array of\n"
9543 "objects describing which JavaScript entry points were invoked as a result.\n"
9544 "|params| is an object whose properties indicate what operation to perform. Here\n"
9545 "are the recognized groups of properties:\n"
9546 "\n"
9547 "{ function }: Call the object |params.function| with no arguments.\n"
9548 "\n"
9549 "{ object, property }: Fetch the property named |params.property| of\n"
9550 "|params.object|.\n"
9551 "\n"
9552 "{ ToString }: Apply JS::ToString to |params.toString|.\n"
9553 "\n"
9554 "{ ToNumber }: Apply JS::ToNumber to |params.toNumber|.\n"
9555 "\n"
9556 "{ eval }: Apply JS::Evaluate to |params.eval|.\n"
9557 "\n"
9558 "The return value is an array of strings, with one element for each\n"
9559 "JavaScript invocation that occurred as a result of the given\n"
9560 "operation. Each element is the name of the function invoked, or the\n"
9561 "string 'eval:FILENAME' if the code was invoked by 'eval' or something\n"
9562 "similar.\n"),
9564 JS_FN_HELP("enqueueJob", EnqueueJob, 1, 0,
9565 "enqueueJob(fn)",
9566 " Enqueue 'fn' on the shell's job queue."),
9568 JS_FN_HELP("globalOfFirstJobInQueue", GlobalOfFirstJobInQueue, 0, 0,
9569 "globalOfFirstJobInQueue()",
9570 " Returns the global of the first item in the job queue. Throws an exception\n"
9571 " if the queue is empty.\n"),
9573 JS_FN_HELP("drainJobQueue", DrainJobQueue, 0, 0,
9574 "drainJobQueue()",
9575 "Take jobs from the shell's job queue in FIFO order and run them until the\n"
9576 "queue is empty.\n"),
9578 JS_FN_HELP("setPromiseRejectionTrackerCallback", SetPromiseRejectionTrackerCallback, 1, 0,
9579 "setPromiseRejectionTrackerCallback()",
9580 "Sets the callback to be invoked whenever a Promise rejection is unhandled\n"
9581 "or a previously-unhandled rejection becomes handled."),
9583 JS_FN_HELP("dumpScopeChain", DumpScopeChain, 1, 0,
9584 "dumpScopeChain(obj)",
9585 " Prints the scope chain of an interpreted function or a module."),
9587 JS_FN_HELP("blackRoot", EnsureBlackRoot, 0, 0,
9588 "blackRoot()",
9589 " Return an array in the current compartment whose elements will be marked\n"
9590 " as black roots by the GC."),
9592 JS_FN_HELP("grayRoot", EnsureGrayRoot, 0, 0,
9593 "grayRoot()",
9594 " Return an array in the current compartment whose elements will be marked\n"
9595 " as gray roots by the GC."),
9597 JS_FN_HELP("addMarkObservers", AddMarkObservers, 1, 0,
9598 "addMarkObservers(array_of_objects)",
9599 " Register an array of objects whose mark bits will be tested by calls to\n"
9600 " getMarks. The objects will be in calling compartment. Objects from\n"
9601 " multiple compartments may be monitored by calling this function in\n"
9602 " different compartments."),
9604 JS_FN_HELP("clearMarkObservers", ClearMarkObservers, 1, 0,
9605 "clearMarkObservers()",
9606 " Clear out the list of objects whose mark bits will be tested.\n"),
9608 JS_FN_HELP("getMarks", GetMarks, 0, 0,
9609 "getMarks()",
9610 " Return an array of strings representing the current state of the mark\n"
9611 " bits ('gray' or 'black', or 'dead' if the object has been collected)\n"
9612 " for the objects registered via addMarkObservers. Note that some of the\n"
9613 " objects tested may be from different compartments than the one in which\n"
9614 " this function runs."),
9616 JS_FN_HELP("bindToAsyncStack", BindToAsyncStack, 2, 0,
9617 "bindToAsyncStack(fn, { stack, cause, explicit })",
9618 " Returns a new function that calls 'fn' with no arguments, passing\n"
9619 " 'undefined' as the 'this' value, and supplies an async stack for the\n"
9620 " call as described by the second argument, an object with the following\n"
9621 " properties (which are not optional, unless specified otherwise):\n"
9622 "\n"
9623 " stack: A SavedFrame object, like that returned by 'saveStack'. Stacks\n"
9624 " captured during calls to the returned function capture this as\n"
9625 " their async stack parent, accessible via a SavedFrame's\n"
9626 " 'asyncParent' property.\n"
9627 "\n"
9628 " cause: A string, supplied as the async cause on the top frame of\n"
9629 " captured async stacks.\n"
9630 "\n"
9631 " explicit: A boolean value, indicating whether the given 'stack' should\n"
9632 " always supplant the returned function's true callers (true),\n"
9633 " or only when there are no other JavaScript frames on the stack\n"
9634 " below it (false). If omitted, this is treated as 'true'."),
9636 #ifdef JS_HAS_INTL_API
9637 JS_FN_HELP("addIntlExtras", AddIntlExtras, 1, 0,
9638 "addIntlExtras(obj)",
9639 "Adds various not-yet-standardized Intl functions as properties on the\n"
9640 "provided object (this should generally be Intl itself). The added\n"
9641 "functions and their behavior are experimental: don't depend upon them\n"
9642 "unless you're willing to update your code if these experimental APIs change\n"
9643 "underneath you."),
9644 #endif // JS_HAS_INTL_API
9646 #ifndef __wasi__
9647 JS_FN_HELP("wasmCompileInSeparateProcess", WasmCompileInSeparateProcess, 1, 0,
9648 "wasmCompileInSeparateProcess(buffer)",
9649 " Compile the given buffer in a separate process, serialize the resulting\n"
9650 " wasm::Module into bytes, and deserialize those bytes in the current\n"
9651 " process, returning the resulting WebAssembly.Module."),
9653 JS_FN_HELP("wasmTextToBinary", WasmTextToBinary, 1, 0,
9654 "wasmTextToBinary(str)",
9655 " Translates the given text wasm module into its binary encoding."),
9656 #endif // __wasi__
9658 JS_FN_HELP("transplantableObject", TransplantableObject, 0, 0,
9659 "transplantableObject([options])",
9660 " Returns the pair {object, transplant}. |object| is an object which can be\n"
9661 " transplanted into a new object when the |transplant| function, which must\n"
9662 " be invoked with a global object, is called.\n"
9663 " |object| is swapped with a cross-compartment wrapper if the global object\n"
9664 " is in a different compartment.\n"
9665 "\n"
9666 " If options is given, it may have any of the following properties:\n"
9667 " proxy: Create a DOM Proxy object instead of a plain DOM object.\n"
9668 " object: Don't create a new DOM object, but instead use the supplied\n"
9669 " FakeDOMObject."),
9671 JS_FN_HELP("cpuNow", CpuNow, /* nargs= */ 0, /* flags = */ 0,
9672 "cpuNow()",
9673 " Returns the approximate processor time used by the process since an arbitrary epoch, in seconds.\n"
9674 " Only the difference between two calls to `cpuNow()` is meaningful."),
9676 #ifdef FUZZING_JS_FUZZILLI
9677 JS_FN_HELP("fuzzilli", Fuzzilli, 0, 0,
9678 "fuzzilli(operation, arg)",
9679 " Exposes functionality used by the Fuzzilli JavaScript fuzzer."),
9680 #endif
9682 #ifdef FUZZING_INTERFACES
9683 JS_FN_HELP("getWasmSmithModule", GetWasmSmithModule, 1, 0,
9684 "getWasmSmithModule(arrayBuffer)",
9685 " Call wasm-smith to generate a random wasm module from the provided data."),
9686 #endif
9688 JS_FN_HELP("isValidJSON", IsValidJSON, 1, 0,
9689 "isValidJSON(source)",
9690 " Returns true if the given source is valid JSON."),
9692 JS_FS_HELP_END
9694 // clang-format on
9696 // clang-format off
9697 #ifdef FUZZING_JS_FUZZILLI
9698 static const JSFunctionSpec shell_function_fuzzilli_hash[] = {
9699 JS_INLINABLE_FN("fuzzilli_hash", fuzzilli_hash, 1, 0, FuzzilliHash),
9700 JS_FS_END
9702 #endif
9703 // clang-format on
9705 // clang-format off
9706 static const JSFunctionSpecWithHelp diff_testing_unsafe_functions[] = {
9708 JS_FS_HELP_END
9710 // clang-format on
9712 // clang-format off
9713 static const JSFunctionSpecWithHelp fuzzing_unsafe_functions[] = {
9714 JS_FN_HELP("getSelfHostedValue", GetSelfHostedValue, 1, 0,
9715 "getSelfHostedValue()",
9716 " Get a self-hosted value by its name. Note that these values don't get \n"
9717 " cached, so repeatedly getting the same value creates multiple distinct clones."),
9719 JS_FN_HELP("line2pc", LineToPC, 0, 0,
9720 "line2pc([fun,] line)",
9721 " Map line number to PC."),
9723 JS_FN_HELP("pc2line", PCToLine, 0, 0,
9724 "pc2line(fun[, pc])",
9725 " Map PC to line number."),
9727 JS_INLINABLE_FN_HELP("assertFloat32", testingFunc_assertFloat32, 2, 0, TestAssertFloat32,
9728 "assertFloat32(value, isFloat32)",
9729 " In IonMonkey only, asserts that value has (resp. hasn't) the MIRType::Float32 if isFloat32 is true (resp. false)."),
9731 JS_INLINABLE_FN_HELP("assertRecoveredOnBailout", testingFunc_assertRecoveredOnBailout, 2, 0,
9732 TestAssertRecoveredOnBailout,
9733 "assertRecoveredOnBailout(var)",
9734 " In IonMonkey only, asserts that variable has RecoveredOnBailout flag."),
9736 JS_FN_HELP("withSourceHook", WithSourceHook, 1, 0,
9737 "withSourceHook(hook, fun)",
9738 " Set this JS runtime's lazy source retrieval hook (that is, the hook\n"
9739 " used to find sources compiled with |CompileOptions::LAZY_SOURCE|) to\n"
9740 " |hook|; call |fun| with no arguments; and then restore the runtime's\n"
9741 " original hook. Return or throw whatever |fun| did. |hook| gets\n"
9742 " passed the requested code's URL, and should return a string.\n"
9743 "\n"
9744 " Notes:\n"
9745 "\n"
9746 " 1) SpiderMonkey may assert if the returned code isn't close enough\n"
9747 " to the script's real code, so this function is not fuzzer-safe.\n"
9748 "\n"
9749 " 2) The runtime can have only one source retrieval hook active at a\n"
9750 " time. If |fun| is not careful, |hook| could be asked to retrieve the\n"
9751 " source code for compilations that occurred long before it was set,\n"
9752 " and that it knows nothing about. The reverse applies as well: the\n"
9753 " original hook, that we reinstate after the call to |fun| completes,\n"
9754 " might be asked for the source code of compilations that |fun|\n"
9755 " performed, and which, presumably, only |hook| knows how to find.\n"),
9757 JS_FN_HELP("crash", Crash, 0, 0,
9758 "crash([message, [{disable_minidump:true}]])",
9759 " Crashes the process with a MOZ_CRASH, optionally providing a message.\n"
9760 " An options object may be passed as the second argument. If the key\n"
9761 " 'suppress_minidump' is set to true, then a minidump will not be\n"
9762 " generated by the crash (which only has an effect if the breakpad\n"
9763 " dumping library is loaded.)"),
9765 #ifndef __wasi__
9766 JS_FN_HELP("wasmLoop", WasmLoop, 2, 0,
9767 "wasmLoop(filename, imports)",
9768 " Performs an AFL-style persistent loop reading data from the given file and passing it\n"
9769 " to the 'wasmEval' function together with the specified imports object."),
9770 #endif // __wasi__
9772 JS_FN_HELP("setBufferStreamParams", SetBufferStreamParams, 2, 0,
9773 "setBufferStreamParams(delayMillis, chunkByteSize)",
9774 " Set the delay time (between calls to StreamConsumer::consumeChunk) and chunk\n"
9775 " size (in bytes)."),
9777 #ifdef JS_CACHEIR_SPEW
9778 JS_FN_HELP("cacheIRHealthReport", CacheIRHealthReport, 0, 0,
9779 "cacheIRHealthReport()",
9780 " Show health rating of CacheIR stubs."),
9781 #endif
9783 #ifdef DEBUG
9784 JS_FN_HELP("debugGetQueuedJobs", DebugGetQueuedJobs, 0, 0,
9785 "debugGetQueuedJobs()",
9786 " Returns an array of queued jobs."),
9787 #endif
9789 JS_FS_HELP_END
9791 // clang-format on
9793 // clang-format off
9794 static const JSFunctionSpecWithHelp performance_functions[] = {
9795 JS_FN_HELP("now", Now, 0, 0,
9796 "now()",
9797 " Return the current time with sub-ms precision.\n"
9798 " This function is an alias of the dateNow() function."),
9799 JS_FS_HELP_END
9801 // clang-format on
9803 // clang-format off
9804 static const JSFunctionSpecWithHelp console_functions[] = {
9805 JS_FN_HELP("log", Print, 0, 0,
9806 "log([exp ...])",
9807 " Evaluate and print expressions to stdout.\n"
9808 " This function is an alias of the print() function."),
9809 JS_FS_HELP_END
9811 // clang-format on
9813 bool DefineConsole(JSContext* cx, HandleObject global) {
9814 RootedObject obj(cx, JS_NewPlainObject(cx));
9815 return obj && JS_DefineFunctionsWithHelp(cx, obj, console_functions) &&
9816 JS_DefineProperty(cx, global, "console", obj, 0);
9819 #ifdef MOZ_PROFILING
9820 # define PROFILING_FUNCTION_COUNT 5
9821 # ifdef MOZ_CALLGRIND
9822 # define CALLGRIND_FUNCTION_COUNT 3
9823 # else
9824 # define CALLGRIND_FUNCTION_COUNT 0
9825 # endif
9826 # ifdef MOZ_VTUNE
9827 # define VTUNE_FUNCTION_COUNT 4
9828 # else
9829 # define VTUNE_FUNCTION_COUNT 0
9830 # endif
9831 # define EXTERNAL_FUNCTION_COUNT \
9832 (PROFILING_FUNCTION_COUNT + CALLGRIND_FUNCTION_COUNT + VTUNE_FUNCTION_COUNT)
9833 #else
9834 # define EXTERNAL_FUNCTION_COUNT 0
9835 #endif
9837 #undef PROFILING_FUNCTION_COUNT
9838 #undef CALLGRIND_FUNCTION_COUNT
9839 #undef VTUNE_FUNCTION_COUNT
9840 #undef EXTERNAL_FUNCTION_COUNT
9842 static bool PrintHelpString(JSContext* cx, HandleValue v) {
9843 RootedString str(cx, v.toString());
9844 MOZ_ASSERT(gOutFile->isOpen());
9846 UniqueChars bytes = JS_EncodeStringToUTF8(cx, str);
9847 if (!bytes) {
9848 return false;
9851 fprintf(gOutFile->fp, "%s\n", bytes.get());
9852 return true;
9855 static bool PrintHelp(JSContext* cx, HandleObject obj) {
9856 RootedValue usage(cx);
9857 if (!JS_GetProperty(cx, obj, "usage", &usage)) {
9858 return false;
9860 RootedValue help(cx);
9861 if (!JS_GetProperty(cx, obj, "help", &help)) {
9862 return false;
9865 if (!usage.isString() || !help.isString()) {
9866 return true;
9869 return PrintHelpString(cx, usage) && PrintHelpString(cx, help);
9872 static bool PrintEnumeratedHelp(JSContext* cx, HandleObject obj,
9873 HandleObject pattern, bool brief) {
9874 RootedIdVector idv(cx);
9875 if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &idv)) {
9876 return false;
9879 Rooted<RegExpObject*> regex(cx);
9880 if (pattern) {
9881 regex = &UncheckedUnwrap(pattern)->as<RegExpObject>();
9884 for (size_t i = 0; i < idv.length(); i++) {
9885 RootedValue v(cx);
9886 RootedId id(cx, idv[i]);
9887 if (!JS_GetPropertyById(cx, obj, id, &v)) {
9888 return false;
9890 if (!v.isObject()) {
9891 continue;
9894 RootedObject funcObj(cx, &v.toObject());
9895 if (regex) {
9896 // Only pay attention to objects with a 'help' property, which will
9897 // either be documented functions or interface objects.
9898 if (!JS_GetProperty(cx, funcObj, "help", &v)) {
9899 return false;
9901 if (!v.isString()) {
9902 continue;
9905 // For functions, match against the name. For interface objects,
9906 // match against the usage string.
9907 if (!JS_GetProperty(cx, funcObj, "name", &v)) {
9908 return false;
9910 if (!v.isString()) {
9911 if (!JS_GetProperty(cx, funcObj, "usage", &v)) {
9912 return false;
9914 if (!v.isString()) {
9915 continue;
9919 Rooted<JSString*> inputStr(cx, v.toString());
9920 if (!inputStr->ensureLinear(cx)) {
9921 return false;
9924 // Execute the regular expression in |regex|'s compartment.
9925 AutoRealm ar(cx, regex);
9926 if (!cx->compartment()->wrap(cx, &inputStr)) {
9927 return false;
9929 Rooted<JSLinearString*> input(cx, &inputStr->asLinear());
9930 size_t ignored = 0;
9931 if (!ExecuteRegExpLegacy(cx, nullptr, regex, input, &ignored, true, &v)) {
9932 return false;
9934 if (v.isNull()) {
9935 continue;
9939 if (!PrintHelp(cx, funcObj)) {
9940 return false;
9944 return true;
9947 static bool Help(JSContext* cx, unsigned argc, Value* vp) {
9948 if (!gOutFile->isOpen()) {
9949 JS_ReportErrorASCII(cx, "output file is closed");
9950 return false;
9953 CallArgs args = CallArgsFromVp(argc, vp);
9954 args.rval().setUndefined();
9955 RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
9957 // help() - display the version and dump out help for all functions on the
9958 // global.
9959 if (args.length() == 0) {
9960 fprintf(gOutFile->fp, "%s\n", JS_GetImplementationVersion());
9962 if (!PrintEnumeratedHelp(cx, global, nullptr, false)) {
9963 return false;
9965 return true;
9968 RootedValue v(cx);
9970 if (args[0].isPrimitive()) {
9971 // help("foo")
9972 JS_ReportErrorASCII(cx, "primitive arg");
9973 return false;
9976 RootedObject obj(cx, &args[0].toObject());
9977 if (!obj) {
9978 return true;
9980 bool isRegexp;
9981 if (!JS::ObjectIsRegExp(cx, obj, &isRegexp)) {
9982 return false;
9985 if (isRegexp) {
9986 // help(/pattern/)
9987 return PrintEnumeratedHelp(cx, global, obj, false);
9990 // help(function)
9991 // help(namespace_obj)
9992 return PrintHelp(cx, obj);
9995 static const JSErrorFormatString jsShell_ErrorFormatString[JSShellErr_Limit] = {
9996 #define MSG_DEF(name, count, exception, format) \
9997 {#name, format, count, JSEXN_ERR},
9998 #include "jsshell.msg"
9999 #undef MSG_DEF
10002 const JSErrorFormatString* js::shell::my_GetErrorMessage(
10003 void* userRef, const unsigned errorNumber) {
10004 if (errorNumber == 0 || errorNumber >= JSShellErr_Limit) {
10005 return nullptr;
10008 return &jsShell_ErrorFormatString[errorNumber];
10011 static bool CreateLastWarningObject(JSContext* cx, JSErrorReport* report) {
10012 RootedObject warningObj(cx, JS_NewObject(cx, nullptr));
10013 if (!warningObj) {
10014 return false;
10017 if (!CopyErrorReportToObject(cx, report, warningObj)) {
10018 return false;
10021 GetShellContext(cx)->lastWarning.setObject(*warningObj);
10022 return true;
10025 static FILE* ErrorFilePointer() {
10026 if (gErrFile->isOpen()) {
10027 return gErrFile->fp;
10030 fprintf(stderr, "error file is closed; falling back to stderr\n");
10031 return stderr;
10034 bool shell::PrintStackTrace(JSContext* cx, HandleObject stackObj) {
10035 if (!stackObj || !stackObj->is<SavedFrame>()) {
10036 return true;
10039 JSPrincipals* principals = stackObj->nonCCWRealm()->principals();
10040 RootedString stackStr(cx);
10041 if (!BuildStackString(cx, principals, stackObj, &stackStr, 2)) {
10042 return false;
10045 UniqueChars stack = JS_EncodeStringToUTF8(cx, stackStr);
10046 if (!stack) {
10047 return false;
10050 FILE* fp = ErrorFilePointer();
10051 fputs("Stack:\n", fp);
10052 fputs(stack.get(), fp);
10054 return true;
10057 js::shell::AutoReportException::~AutoReportException() {
10058 if (!JS_IsExceptionPending(cx)) {
10059 return;
10062 auto printError = [](JSContext* cx, auto& report, const auto& exnStack,
10063 const char* prefix = nullptr) {
10064 if (!report.init(cx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) {
10065 fprintf(stderr, "out of memory initializing JS::ErrorReportBuilder\n");
10066 fflush(stderr);
10067 JS_ClearPendingException(cx);
10068 return false;
10071 MOZ_ASSERT(!report.report()->isWarning());
10073 FILE* fp = ErrorFilePointer();
10074 if (prefix) {
10075 fputs(prefix, fp);
10077 JS::PrintError(fp, report, reportWarnings);
10078 JS_ClearPendingException(cx);
10080 // If possible, use the original error stack as the source of truth, because
10081 // finally block handlers may have overwritten the exception stack.
10082 RootedObject stack(cx, exnStack.stack());
10083 if (exnStack.exception().isObject()) {
10084 RootedObject exception(cx, &exnStack.exception().toObject());
10085 if (JSObject* exceptionStack = JS::ExceptionStackOrNull(exception)) {
10086 stack.set(exceptionStack);
10090 if (!PrintStackTrace(cx, stack)) {
10091 fputs("(Unable to print stack trace)\n", fp);
10092 JS_ClearPendingException(cx);
10095 return true;
10098 // Get exception object and stack before printing and clearing exception.
10099 JS::ExceptionStack exnStack(cx);
10100 if (!JS::StealPendingExceptionStack(cx, &exnStack)) {
10101 fprintf(stderr, "out of memory while stealing exception\n");
10102 fflush(stderr);
10103 JS_ClearPendingException(cx);
10104 return;
10107 ShellContext* sc = GetShellContext(cx);
10108 JS::ErrorReportBuilder report(cx);
10109 if (!printError(cx, report, exnStack)) {
10110 // Return if we couldn't initialize the error report.
10111 return;
10114 // Print the error's cause, if available.
10115 if (exnStack.exception().isObject()) {
10116 JSObject* exception = &exnStack.exception().toObject();
10117 if (exception->is<ErrorObject>()) {
10118 auto* error = &exception->as<ErrorObject>();
10119 if (auto maybeCause = error->getCause()) {
10120 RootedValue cause(cx, maybeCause.value());
10122 RootedObject causeStack(cx);
10123 if (cause.isObject()) {
10124 RootedObject causeObj(cx, &cause.toObject());
10125 causeStack = JS::ExceptionStackOrNull(causeObj);
10128 JS::ExceptionStack exnStack(cx, cause, causeStack);
10129 JS::ErrorReportBuilder report(cx);
10130 printError(cx, report, exnStack, "Caused by: ");
10135 #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
10136 // Don't quit the shell if an unhandled exception is reported during OOM
10137 // testing.
10138 if (cx->runningOOMTest) {
10139 return;
10141 #endif
10143 if (report.report()->errorNumber == JSMSG_OUT_OF_MEMORY) {
10144 sc->exitCode = EXITCODE_OUT_OF_MEMORY;
10145 } else {
10146 sc->exitCode = EXITCODE_RUNTIME_ERROR;
10150 void js::shell::WarningReporter(JSContext* cx, JSErrorReport* report) {
10151 ShellContext* sc = GetShellContext(cx);
10152 FILE* fp = ErrorFilePointer();
10154 MOZ_ASSERT(report->isWarning());
10156 if (sc->lastWarningEnabled) {
10157 JS::AutoSaveExceptionState savedExc(cx);
10158 if (!CreateLastWarningObject(cx, report)) {
10159 fputs("Unhandled error happened while creating last warning object.\n",
10160 fp);
10161 fflush(fp);
10163 savedExc.restore();
10166 // Print the warning.
10167 JS::PrintError(fp, report, reportWarnings);
10170 static bool global_enumerate(JSContext* cx, JS::HandleObject obj,
10171 JS::MutableHandleIdVector properties,
10172 bool enumerableOnly) {
10173 #ifdef LAZY_STANDARD_CLASSES
10174 return JS_NewEnumerateStandardClasses(cx, obj, properties, enumerableOnly);
10175 #else
10176 return true;
10177 #endif
10180 static bool global_resolve(JSContext* cx, HandleObject obj, HandleId id,
10181 bool* resolvedp) {
10182 #ifdef LAZY_STANDARD_CLASSES
10183 if (!JS_ResolveStandardClass(cx, obj, id, resolvedp)) {
10184 return false;
10186 #endif
10187 return true;
10190 static bool global_mayResolve(const JSAtomState& names, jsid id,
10191 JSObject* maybeObj) {
10192 return JS_MayResolveStandardClass(names, id, maybeObj);
10195 static const JSClassOps global_classOps = {
10196 nullptr, // addProperty
10197 nullptr, // delProperty
10198 nullptr, // enumerate
10199 global_enumerate, // newEnumerate
10200 global_resolve, // resolve
10201 global_mayResolve, // mayResolve
10202 nullptr, // finalize
10203 nullptr, // call
10204 nullptr, // construct
10205 JS_GlobalObjectTraceHook, // trace
10208 static constexpr uint32_t DOM_PROTOTYPE_SLOT = JSCLASS_GLOBAL_SLOT_COUNT;
10209 static constexpr uint32_t DOM_GLOBAL_SLOTS = 1;
10211 static const JSClass global_class = {
10212 "global",
10213 JSCLASS_GLOBAL_FLAGS | JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(DOM_GLOBAL_SLOTS),
10214 &global_classOps};
10217 * Define a FakeDOMObject constructor. It returns an object with a getter,
10218 * setter and method with attached JitInfo. This object can be used to test
10219 * IonMonkey DOM optimizations in the shell.
10222 /* Fow now just use to a constant we can check. */
10223 static const void* DOM_PRIVATE_VALUE = (void*)0x1234;
10225 static bool dom_genericGetter(JSContext* cx, unsigned argc, JS::Value* vp);
10227 static bool dom_genericSetter(JSContext* cx, unsigned argc, JS::Value* vp);
10229 static bool dom_genericMethod(JSContext* cx, unsigned argc, JS::Value* vp);
10231 static bool dom_get_x(JSContext* cx, HandleObject obj, void* self,
10232 JSJitGetterCallArgs args) {
10233 MOZ_ASSERT(JS::GetClass(obj) == GetDomClass());
10234 MOZ_ASSERT(self == DOM_PRIVATE_VALUE);
10235 args.rval().set(JS_NumberValue(double(3.14)));
10236 return true;
10239 static bool dom_set_x(JSContext* cx, HandleObject obj, void* self,
10240 JSJitSetterCallArgs args) {
10241 MOZ_ASSERT(JS::GetClass(obj) == GetDomClass());
10242 MOZ_ASSERT(self == DOM_PRIVATE_VALUE);
10243 return true;
10246 static bool dom_get_slot(JSContext* cx, HandleObject obj, void* self,
10247 JSJitGetterCallArgs args) {
10248 MOZ_ASSERT(JS::GetClass(obj) == GetDomClass());
10249 MOZ_ASSERT(self == DOM_PRIVATE_VALUE);
10251 Value v = JS::GetReservedSlot(obj, DOM_OBJECT_SLOT2);
10252 MOZ_ASSERT(v.toInt32() == 42);
10253 args.rval().set(v);
10254 return true;
10257 static bool dom_get_global(JSContext* cx, HandleObject obj, void* self,
10258 JSJitGetterCallArgs args) {
10259 MOZ_ASSERT(JS::GetClass(obj) == GetDomClass());
10260 MOZ_ASSERT(self == DOM_PRIVATE_VALUE);
10262 // Return the current global (instead of obj->global()) to test cx->realm
10263 // switching in the JIT.
10264 args.rval().setObject(*ToWindowProxyIfWindow(cx->global()));
10266 return true;
10269 static bool dom_set_global(JSContext* cx, HandleObject obj, void* self,
10270 JSJitSetterCallArgs args) {
10271 MOZ_ASSERT(JS::GetClass(obj) == GetDomClass());
10272 MOZ_ASSERT(self == DOM_PRIVATE_VALUE);
10274 // Throw an exception if our argument is not the current global. This lets
10275 // us test cx->realm switching.
10276 if (!args[0].isObject() ||
10277 ToWindowIfWindowProxy(&args[0].toObject()) != cx->global()) {
10278 JS_ReportErrorASCII(cx, "Setter not called with matching global argument");
10279 return false;
10282 return true;
10285 static bool dom_doFoo(JSContext* cx, HandleObject obj, void* self,
10286 const JSJitMethodCallArgs& args) {
10287 MOZ_ASSERT(JS::GetClass(obj) == GetDomClass());
10288 MOZ_ASSERT(self == DOM_PRIVATE_VALUE);
10289 MOZ_ASSERT(cx->realm() == args.callee().as<JSFunction>().realm());
10291 /* Just return args.length(). */
10292 args.rval().setInt32(args.length());
10293 return true;
10296 static const JSJitInfo dom_x_getterinfo = {
10297 {(JSJitGetterOp)dom_get_x},
10298 {0}, /* protoID */
10299 {0}, /* depth */
10300 JSJitInfo::Getter,
10301 JSJitInfo::AliasNone, /* aliasSet */
10302 JSVAL_TYPE_UNKNOWN, /* returnType */
10303 true, /* isInfallible. False in setters. */
10304 true, /* isMovable */
10305 true, /* isEliminatable */
10306 false, /* isAlwaysInSlot */
10307 false, /* isLazilyCachedInSlot */
10308 false, /* isTypedMethod */
10309 0 /* slotIndex */
10312 static const JSJitInfo dom_x_setterinfo = {
10313 {(JSJitGetterOp)dom_set_x},
10314 {0}, /* protoID */
10315 {0}, /* depth */
10316 JSJitInfo::Setter,
10317 JSJitInfo::AliasEverything, /* aliasSet */
10318 JSVAL_TYPE_UNKNOWN, /* returnType */
10319 false, /* isInfallible. False in setters. */
10320 false, /* isMovable. */
10321 false, /* isEliminatable. */
10322 false, /* isAlwaysInSlot */
10323 false, /* isLazilyCachedInSlot */
10324 false, /* isTypedMethod */
10325 0 /* slotIndex */
10328 static const JSJitInfo dom_slot_getterinfo = {
10329 {(JSJitGetterOp)dom_get_slot},
10330 {0}, /* protoID */
10331 {0}, /* depth */
10332 JSJitInfo::Getter,
10333 JSJitInfo::AliasNone, /* aliasSet */
10334 JSVAL_TYPE_INT32, /* returnType */
10335 false, /* isInfallible. False in setters. */
10336 true, /* isMovable */
10337 true, /* isEliminatable */
10338 true, /* isAlwaysInSlot */
10339 false, /* isLazilyCachedInSlot */
10340 false, /* isTypedMethod */
10341 DOM_OBJECT_SLOT2 /* slotIndex */
10344 // Note: this getter uses AliasEverything and is marked as fallible and
10345 // non-movable (1) to prevent Ion from getting too clever optimizing it and
10346 // (2) it's nice to have a few different kinds of getters in the shell.
10347 static const JSJitInfo dom_global_getterinfo = {
10348 {(JSJitGetterOp)dom_get_global},
10349 {0}, /* protoID */
10350 {0}, /* depth */
10351 JSJitInfo::Getter,
10352 JSJitInfo::AliasEverything, /* aliasSet */
10353 JSVAL_TYPE_OBJECT, /* returnType */
10354 false, /* isInfallible. False in setters. */
10355 false, /* isMovable */
10356 false, /* isEliminatable */
10357 false, /* isAlwaysInSlot */
10358 false, /* isLazilyCachedInSlot */
10359 false, /* isTypedMethod */
10360 0 /* slotIndex */
10363 static const JSJitInfo dom_global_setterinfo = {
10364 {(JSJitGetterOp)dom_set_global},
10365 {0}, /* protoID */
10366 {0}, /* depth */
10367 JSJitInfo::Setter,
10368 JSJitInfo::AliasEverything, /* aliasSet */
10369 JSVAL_TYPE_UNKNOWN, /* returnType */
10370 false, /* isInfallible. False in setters. */
10371 false, /* isMovable. */
10372 false, /* isEliminatable. */
10373 false, /* isAlwaysInSlot */
10374 false, /* isLazilyCachedInSlot */
10375 false, /* isTypedMethod */
10376 0 /* slotIndex */
10379 static const JSJitInfo doFoo_methodinfo = {
10380 {(JSJitGetterOp)dom_doFoo},
10381 {0}, /* protoID */
10382 {0}, /* depth */
10383 JSJitInfo::Method,
10384 JSJitInfo::AliasEverything, /* aliasSet */
10385 JSVAL_TYPE_UNKNOWN, /* returnType */
10386 false, /* isInfallible. False in setters. */
10387 false, /* isMovable */
10388 false, /* isEliminatable */
10389 false, /* isAlwaysInSlot */
10390 false, /* isLazilyCachedInSlot */
10391 false, /* isTypedMethod */
10392 0 /* slotIndex */
10395 static const JSPropertySpec dom_props[] = {
10396 JSPropertySpec::nativeAccessors("x", JSPROP_ENUMERATE, dom_genericGetter,
10397 &dom_x_getterinfo, dom_genericSetter,
10398 &dom_x_setterinfo),
10399 JSPropertySpec::nativeAccessors("slot", JSPROP_ENUMERATE, dom_genericGetter,
10400 &dom_slot_getterinfo),
10401 JSPropertySpec::nativeAccessors("global", JSPROP_ENUMERATE,
10402 dom_genericGetter, &dom_global_getterinfo,
10403 dom_genericSetter, &dom_global_setterinfo),
10404 JS_PS_END};
10406 static const JSFunctionSpec dom_methods[] = {
10407 JS_FNINFO("doFoo", dom_genericMethod, &doFoo_methodinfo, 3,
10408 JSPROP_ENUMERATE),
10409 JS_FS_END};
10411 static const JSClass dom_class = {
10412 "FakeDOMObject", JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(2)};
10414 static const JSClass* GetDomClass() { return &dom_class; }
10416 static bool dom_genericGetter(JSContext* cx, unsigned argc, JS::Value* vp) {
10417 CallArgs args = CallArgsFromVp(argc, vp);
10419 if (!args.thisv().isObject()) {
10420 args.rval().setUndefined();
10421 return true;
10424 RootedObject obj(cx, &args.thisv().toObject());
10425 if (JS::GetClass(obj) != &dom_class) {
10426 args.rval().set(UndefinedValue());
10427 return true;
10430 JS::Value val = JS::GetReservedSlot(obj, DOM_OBJECT_SLOT);
10432 const JSJitInfo* info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
10433 MOZ_ASSERT(info->type() == JSJitInfo::Getter);
10434 JSJitGetterOp getter = info->getter;
10435 return getter(cx, obj, val.toPrivate(), JSJitGetterCallArgs(args));
10438 static bool dom_genericSetter(JSContext* cx, unsigned argc, JS::Value* vp) {
10439 CallArgs args = CallArgsFromVp(argc, vp);
10441 if (args.length() < 1 || !args.thisv().isObject()) {
10442 args.rval().setUndefined();
10443 return true;
10446 RootedObject obj(cx, &args.thisv().toObject());
10447 if (JS::GetClass(obj) != &dom_class) {
10448 args.rval().set(UndefinedValue());
10449 return true;
10452 JS::Value val = JS::GetReservedSlot(obj, DOM_OBJECT_SLOT);
10454 const JSJitInfo* info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
10455 MOZ_ASSERT(info->type() == JSJitInfo::Setter);
10456 JSJitSetterOp setter = info->setter;
10457 if (!setter(cx, obj, val.toPrivate(), JSJitSetterCallArgs(args))) {
10458 return false;
10460 args.rval().set(UndefinedValue());
10461 return true;
10464 static bool dom_genericMethod(JSContext* cx, unsigned argc, JS::Value* vp) {
10465 CallArgs args = CallArgsFromVp(argc, vp);
10467 if (!args.thisv().isObject()) {
10468 args.rval().setUndefined();
10469 return true;
10472 RootedObject obj(cx, &args.thisv().toObject());
10473 if (JS::GetClass(obj) != &dom_class) {
10474 args.rval().set(UndefinedValue());
10475 return true;
10478 JS::Value val = JS::GetReservedSlot(obj, DOM_OBJECT_SLOT);
10480 const JSJitInfo* info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
10481 MOZ_ASSERT(info->type() == JSJitInfo::Method);
10482 JSJitMethodOp method = info->method;
10483 return method(cx, obj, val.toPrivate(), JSJitMethodCallArgs(args));
10486 static void InitDOMObject(HandleObject obj) {
10487 JS::SetReservedSlot(obj, DOM_OBJECT_SLOT,
10488 PrivateValue(const_cast<void*>(DOM_PRIVATE_VALUE)));
10489 JS::SetReservedSlot(obj, DOM_OBJECT_SLOT2, Int32Value(42));
10492 static JSObject* GetDOMPrototype(JSContext* cx, JSObject* global) {
10493 MOZ_ASSERT(JS_IsGlobalObject(global));
10494 if (JS::GetClass(global) != &global_class) {
10495 JS_ReportErrorASCII(cx, "Can't get FakeDOMObject prototype in sandbox");
10496 return nullptr;
10499 const JS::Value& slot = JS::GetReservedSlot(global, DOM_PROTOTYPE_SLOT);
10500 MOZ_ASSERT(slot.isObject());
10501 return &slot.toObject();
10504 static bool dom_constructor(JSContext* cx, unsigned argc, JS::Value* vp) {
10505 CallArgs args = CallArgsFromVp(argc, vp);
10507 RootedObject callee(cx, &args.callee());
10508 RootedValue protov(cx);
10509 if (!GetProperty(cx, callee, callee, cx->names().prototype, &protov)) {
10510 return false;
10513 if (!protov.isObject()) {
10514 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_PROTOTYPE,
10515 "FakeDOMObject");
10516 return false;
10519 RootedObject proto(cx, &protov.toObject());
10520 RootedObject domObj(cx, JS_NewObjectWithGivenProto(cx, &dom_class, proto));
10521 if (!domObj) {
10522 return false;
10525 InitDOMObject(domObj);
10527 args.rval().setObject(*domObj);
10528 return true;
10531 static bool InstanceClassHasProtoAtDepth(const JSClass* clasp, uint32_t protoID,
10532 uint32_t depth) {
10533 // Only the (fake) DOM object supports any JIT optimizations.
10534 return clasp == GetDomClass();
10537 static bool ShellBuildId(JS::BuildIdCharVector* buildId) {
10538 // The browser embeds the date into the buildid and the buildid is embedded
10539 // in the binary, so every 'make' necessarily builds a new firefox binary.
10540 // Fortunately, the actual firefox executable is tiny -- all the code is in
10541 // libxul.so and other shared modules -- so this isn't a big deal. Not so
10542 // for the statically-linked JS shell. To avoid recompiling js.cpp and
10543 // re-linking 'js' on every 'make', we use a constant buildid and rely on
10544 // the shell user to manually clear any caches between cache-breaking updates.
10545 const char buildid[] = "JS-shell";
10546 return buildId->append(buildid, sizeof(buildid));
10549 static bool TimesAccessed(JSContext* cx, unsigned argc, Value* vp) {
10550 static int32_t accessed = 0;
10551 CallArgs args = CallArgsFromVp(argc, vp);
10552 args.rval().setInt32(++accessed);
10553 return true;
10556 static const JSPropertySpec TestingProperties[] = {
10557 JS_PSG("timesAccessed", TimesAccessed, 0), JS_PS_END};
10559 static JSObject* NewGlobalObject(JSContext* cx, JS::RealmOptions& options,
10560 JSPrincipals* principals, ShellGlobalKind kind,
10561 bool immutablePrototype) {
10562 RootedObject glob(cx,
10563 JS_NewGlobalObject(cx, &global_class, principals,
10564 JS::DontFireOnNewGlobalHook, options));
10565 if (!glob) {
10566 return nullptr;
10570 JSAutoRealm ar(cx, glob);
10572 if (kind == ShellGlobalKind::WindowProxy) {
10573 RootedObject proxy(cx, NewShellWindowProxy(cx, glob));
10574 if (!proxy) {
10575 return nullptr;
10577 js::SetWindowProxy(cx, glob, proxy);
10580 #ifndef LAZY_STANDARD_CLASSES
10581 if (!JS::InitRealmStandardClasses(cx)) {
10582 return nullptr;
10584 #endif
10586 if (immutablePrototype) {
10587 bool succeeded;
10588 if (!JS_SetImmutablePrototype(cx, glob, &succeeded)) {
10589 return nullptr;
10591 MOZ_ASSERT(succeeded,
10592 "a fresh, unexposed global object is always capable of "
10593 "having its [[Prototype]] be immutable");
10596 #ifdef JS_HAS_CTYPES
10597 if (!fuzzingSafe && !JS::InitCTypesClass(cx, glob)) {
10598 return nullptr;
10600 #endif
10601 if (!JS_InitReflectParse(cx, glob)) {
10602 return nullptr;
10604 if (!JS_DefineDebuggerObject(cx, glob)) {
10605 return nullptr;
10607 if (!JS_DefineFunctionsWithHelp(cx, glob, shell_functions) ||
10608 !JS_DefineProfilingFunctions(cx, glob)) {
10609 return nullptr;
10611 #ifdef FUZZING_JS_FUZZILLI
10612 if (!JS_DefineFunctions(cx, glob, shell_function_fuzzilli_hash)) {
10613 return nullptr;
10615 #endif
10616 if (!js::DefineTestingFunctions(cx, glob, fuzzingSafe,
10617 disableOOMFunctions)) {
10618 return nullptr;
10620 if (!JS_DefineProperties(cx, glob, TestingProperties)) {
10621 return nullptr;
10624 if (!fuzzingSafe) {
10625 if (!JS_DefineFunctionsWithHelp(cx, glob, fuzzing_unsafe_functions)) {
10626 return nullptr;
10628 if (!DefineConsole(cx, glob)) {
10629 return nullptr;
10633 if (!DefineOS(cx, glob, fuzzingSafe, &gOutFile, &gErrFile)) {
10634 return nullptr;
10637 if (!js::SupportDifferentialTesting()) {
10638 if (!JS_DefineFunctionsWithHelp(cx, glob,
10639 diff_testing_unsafe_functions)) {
10640 return nullptr;
10643 RootedObject performanceObj(cx, JS_NewObject(cx, nullptr));
10644 if (!performanceObj) {
10645 return nullptr;
10647 if (!JS_DefineFunctionsWithHelp(cx, performanceObj,
10648 performance_functions)) {
10649 return nullptr;
10651 RootedObject mozMemoryObj(cx, JS_NewObject(cx, nullptr));
10652 if (!mozMemoryObj) {
10653 return nullptr;
10655 RootedObject gcObj(cx, gc::NewMemoryInfoObject(cx));
10656 if (!gcObj) {
10657 return nullptr;
10659 if (!JS_DefineProperty(cx, glob, "performance", performanceObj,
10660 JSPROP_ENUMERATE)) {
10661 return nullptr;
10663 if (!JS_DefineProperty(cx, performanceObj, "mozMemory", mozMemoryObj,
10664 JSPROP_ENUMERATE)) {
10665 return nullptr;
10667 if (!JS_DefineProperty(cx, mozMemoryObj, "gc", gcObj, JSPROP_ENUMERATE)) {
10668 return nullptr;
10672 /* Initialize FakeDOMObject. */
10673 static const js::DOMCallbacks DOMcallbacks = {InstanceClassHasProtoAtDepth};
10674 SetDOMCallbacks(cx, &DOMcallbacks);
10676 RootedObject domProto(
10677 cx, JS_InitClass(cx, glob, &dom_class, nullptr, "FakeDOMObject",
10678 dom_constructor, 0, dom_props, dom_methods, nullptr,
10679 nullptr));
10680 if (!domProto) {
10681 return nullptr;
10684 // FakeDOMObject.prototype is the only DOM object which needs to retrieved
10685 // in the shell; store it directly instead of creating a separate layer
10686 // (ProtoAndIfaceCache) as done in the browser.
10687 JS::SetReservedSlot(glob, DOM_PROTOTYPE_SLOT, ObjectValue(*domProto));
10689 /* Initialize FakeDOMObject.prototype */
10690 InitDOMObject(domProto);
10692 if (!DefineToStringTag(cx, glob, cx->names().global)) {
10693 return nullptr;
10696 JS_FireOnNewGlobalObject(cx, glob);
10699 return glob;
10702 static bool BindScriptArgs(JSContext* cx, OptionParser* op) {
10703 AutoReportException are(cx);
10705 MultiStringRange msr = op->getMultiStringArg("scriptArgs");
10706 RootedObject scriptArgs(cx);
10707 scriptArgs = JS::NewArrayObject(cx, 0);
10708 if (!scriptArgs) {
10709 return false;
10712 if (!JS_DefineProperty(cx, cx->global(), "scriptArgs", scriptArgs, 0)) {
10713 return false;
10716 for (size_t i = 0; !msr.empty(); msr.popFront(), ++i) {
10717 const char* scriptArg = msr.front();
10718 UniqueChars scriptArgUtf8 = JS::EncodeNarrowToUtf8(cx, scriptArg);
10719 if (!scriptArgUtf8) {
10720 return false;
10722 RootedString str(cx, NewStringCopyUTF8(cx, scriptArgUtf8.get()));
10723 if (!str || !JS_DefineElement(cx, scriptArgs, i, str, JSPROP_ENUMERATE)) {
10724 return false;
10728 RootedValue scriptPathValue(cx);
10729 if (const char* scriptPath = op->getStringArg("script")) {
10730 UniqueChars scriptPathUtf8 = JS::EncodeNarrowToUtf8(cx, scriptPath);
10731 if (!scriptPathUtf8) {
10732 return false;
10734 RootedString scriptPathString(cx,
10735 NewStringCopyUTF8(cx, scriptPathUtf8.get()));
10736 if (!scriptPathString) {
10737 return false;
10739 scriptPathValue = StringValue(scriptPathString);
10740 } else {
10741 scriptPathValue = UndefinedValue();
10744 if (!JS_DefineProperty(cx, cx->global(), "scriptPath", scriptPathValue, 0)) {
10745 return false;
10748 return true;
10751 static bool OptionFailure(const char* option, const char* str) {
10752 fprintf(stderr, "Unrecognized option for %s: %s\n", option, str);
10753 return false;
10756 template <typename... Ts>
10757 auto minVal(Ts... args);
10758 template <typename T>
10759 auto minVal(T a) {
10760 return a;
10763 template <typename T, typename... Ts>
10764 auto minVal(T a, Ts... args) {
10765 return std::min(a, minVal(args...));
10768 [[nodiscard]] static bool ProcessArgs(JSContext* cx, OptionParser* op) {
10769 ShellContext* sc = GetShellContext(cx);
10771 /* |scriptArgs| gets bound on the global before any code is run. */
10772 if (!BindScriptArgs(cx, op)) {
10773 return false;
10776 MultiStringRange filePaths = op->getMultiStringOption('f');
10777 MultiStringRange utf16FilePaths = op->getMultiStringOption('u');
10778 MultiStringRange preludePaths = op->getMultiStringOption('p');
10779 MultiStringRange codeChunks = op->getMultiStringOption('e');
10780 MultiStringRange modulePaths = op->getMultiStringOption('m');
10782 #ifdef FUZZING_JS_FUZZILLI
10783 // Check for REPRL file source
10784 if (op->getBoolOption("reprl")) {
10785 return FuzzilliReprlGetAndRun(cx);
10787 #endif /* FUZZING_JS_FUZZILLI */
10789 if (filePaths.empty() && utf16FilePaths.empty() && codeChunks.empty() &&
10790 modulePaths.empty() && !op->getStringArg("script")) {
10791 // Always use the interactive shell when -i is used. Without -i we let
10792 // Process figure it out based on isatty.
10793 bool forceTTY = op->getBoolOption('i');
10794 return Process(cx, nullptr, forceTTY, FileScript);
10797 while (!preludePaths.empty() || !filePaths.empty() ||
10798 !utf16FilePaths.empty() || !codeChunks.empty() ||
10799 !modulePaths.empty()) {
10800 size_t ppArgno = preludePaths.empty() ? SIZE_MAX : preludePaths.argno();
10801 size_t fpArgno = filePaths.empty() ? SIZE_MAX : filePaths.argno();
10802 size_t ufpArgno =
10803 utf16FilePaths.empty() ? SIZE_MAX : utf16FilePaths.argno();
10804 size_t ccArgno = codeChunks.empty() ? SIZE_MAX : codeChunks.argno();
10805 size_t mpArgno = modulePaths.empty() ? SIZE_MAX : modulePaths.argno();
10806 size_t minArgno = minVal(ppArgno, fpArgno, ufpArgno, ccArgno, mpArgno);
10808 if (ppArgno == minArgno) {
10809 UniqueChars path = JS::EncodeNarrowToUtf8(cx, preludePaths.front());
10810 if (!path) {
10811 return false;
10813 if (!Process(cx, path.get(), false, PreludeScript)) {
10814 return false;
10817 preludePaths.popFront();
10818 continue;
10821 if (fpArgno == minArgno) {
10822 UniqueChars path = JS::EncodeNarrowToUtf8(cx, filePaths.front());
10823 if (!path) {
10824 return false;
10826 if (!Process(cx, path.get(), false, FileScript)) {
10827 return false;
10830 filePaths.popFront();
10831 continue;
10834 if (ufpArgno == minArgno) {
10835 UniqueChars path = JS::EncodeNarrowToUtf8(cx, utf16FilePaths.front());
10836 if (!path) {
10837 return false;
10839 if (!Process(cx, path.get(), false, FileScriptUtf16)) {
10840 return false;
10843 utf16FilePaths.popFront();
10844 continue;
10847 if (ccArgno == minArgno) {
10848 UniqueChars code = JS::EncodeNarrowToUtf8(cx, codeChunks.front());
10849 if (!code) {
10850 return false;
10853 // Command line scripts are always parsed with full-parse to evaluate
10854 // conditions which might filter code coverage conditions.
10855 JS::CompileOptions opts(cx);
10856 opts.setFileAndLine("-e", 1).setForceFullParse();
10858 JS::SourceText<Utf8Unit> srcBuf;
10859 if (!srcBuf.init(cx, code.get(), strlen(code.get()),
10860 JS::SourceOwnership::Borrowed)) {
10861 return false;
10864 RootedValue rval(cx);
10865 if (!JS::Evaluate(cx, opts, srcBuf, &rval)) {
10866 return false;
10869 codeChunks.popFront();
10870 if (sc->quitting) {
10871 break;
10874 continue;
10877 MOZ_ASSERT(mpArgno == minArgno);
10879 UniqueChars path = JS::EncodeNarrowToUtf8(cx, modulePaths.front());
10880 if (!path) {
10881 return false;
10883 if (!Process(cx, path.get(), false, FileModule)) {
10884 return false;
10887 modulePaths.popFront();
10890 if (sc->quitting) {
10891 return false;
10894 /* The |script| argument is processed after all options. */
10895 if (const char* path = op->getStringArg("script")) {
10896 UniqueChars pathUtf8 = JS::EncodeNarrowToUtf8(cx, path);
10897 if (!pathUtf8) {
10898 return false;
10900 if (!Process(cx, pathUtf8.get(), false, FileScript)) {
10901 return false;
10905 if (op->getBoolOption('i')) {
10906 if (!Process(cx, nullptr, true, FileScript)) {
10907 return false;
10911 return true;
10914 static void SetWorkerContextOptions(JSContext* cx) {
10915 // Copy option values from the main thread.
10916 JS::ContextOptionsRef(cx)
10917 .setAsmJS(enableAsmJS)
10918 .setWasm(enableWasm)
10919 .setWasmBaseline(enableWasmBaseline)
10920 .setWasmIon(enableWasmOptimizing)
10921 #define WASM_FEATURE(NAME, ...) .setWasm##NAME(enableWasm##NAME)
10922 JS_FOR_WASM_FEATURES(WASM_FEATURE)
10923 #undef WASM_FEATURE
10925 .setWasmVerbose(enableWasmVerbose)
10926 .setTestWasmAwaitTier2(enableTestWasmAwaitTier2)
10927 .setSourcePragmas(enableSourcePragmas);
10929 cx->runtime()->setOffthreadIonCompilationEnabled(offthreadCompilation);
10930 cx->runtime()->profilingScripts =
10931 enableCodeCoverage || enableDisassemblyDumps;
10933 #ifdef JS_GC_ZEAL
10934 if (gZealBits && gZealFrequency) {
10935 for (size_t i = 0; i < size_t(gc::ZealMode::Count); i++) {
10936 if (gZealBits & (1 << i)) {
10937 cx->runtime()->gc.setZeal(i, gZealFrequency);
10941 #endif
10943 JS_SetNativeStackQuota(cx, gWorkerStackSize);
10946 [[nodiscard]] static bool PrintUnhandledRejection(
10947 JSContext* cx, Handle<PromiseObject*> promise) {
10948 RootedValue reason(cx, promise->reason());
10949 RootedObject site(cx, promise->resolutionSite());
10951 RootedString str(cx, JS_ValueToSource(cx, reason));
10952 if (!str) {
10953 return false;
10956 UniqueChars utf8chars = JS_EncodeStringToUTF8(cx, str);
10957 if (!utf8chars) {
10958 return false;
10961 FILE* fp = ErrorFilePointer();
10962 fprintf(fp, "Unhandled rejection: %s\n", utf8chars.get());
10964 if (!site) {
10965 fputs("(no stack trace available)\n", stderr);
10966 return true;
10969 JSPrincipals* principals = cx->realm()->principals();
10970 RootedString stackStr(cx);
10971 if (!BuildStackString(cx, principals, site, &stackStr, 2)) {
10972 return false;
10975 UniqueChars stack = JS_EncodeStringToUTF8(cx, stackStr);
10976 if (!stack) {
10977 return false;
10980 fputs("Stack:\n", fp);
10981 fputs(stack.get(), fp);
10983 return true;
10986 [[nodiscard]] static bool ReportUnhandledRejections(JSContext* cx) {
10987 ShellContext* sc = GetShellContext(cx);
10988 if (!sc->trackUnhandledRejections) {
10989 return true;
10992 if (!sc->unhandledRejectedPromises) {
10993 return true;
10996 AutoRealm ar(cx, sc->unhandledRejectedPromises);
10998 if (!SetObject::size(cx, sc->unhandledRejectedPromises)) {
10999 return true;
11002 sc->exitCode = EXITCODE_RUNTIME_ERROR;
11004 RootedValue iter(cx);
11005 if (!SetObject::iterator(cx, SetObject::IteratorKind::Values,
11006 sc->unhandledRejectedPromises, &iter)) {
11007 return false;
11010 Rooted<SetIteratorObject*> iterObj(cx,
11011 &iter.toObject().as<SetIteratorObject>());
11012 JSObject* obj = SetIteratorObject::createResult(cx);
11013 if (!obj) {
11014 return false;
11017 Rooted<ArrayObject*> resultObj(cx, &obj->as<ArrayObject>());
11018 while (true) {
11019 bool done = SetIteratorObject::next(iterObj, resultObj);
11020 if (done) {
11021 break;
11024 RootedObject obj(cx, &resultObj->getDenseElement(0).toObject());
11025 Rooted<PromiseObject*> promise(cx, obj->maybeUnwrapIf<PromiseObject>());
11026 if (!promise) {
11027 FILE* fp = ErrorFilePointer();
11028 fputs(
11029 "Unhandled rejection: dead proxy found in unhandled "
11030 "rejections set\n",
11031 fp);
11032 continue;
11035 AutoRealm ar2(cx, promise);
11037 if (!PrintUnhandledRejection(cx, promise)) {
11038 return false;
11042 sc->unhandledRejectedPromises = nullptr;
11044 return true;
11047 bool ShellContext::registerWithCx(JSContext* cx) {
11048 cx_ = cx;
11049 JS_SetContextPrivate(cx, this);
11051 if (isWorker) {
11052 SetWorkerContextOptions(cx);
11055 JS::SetWarningReporter(cx, WarningReporter);
11056 JS_SetFutexCanWait(cx);
11057 JS_InitDestroyPrincipalsCallback(cx, ShellPrincipals::destroy);
11058 JS_SetDestroyCompartmentCallback(cx, DestroyShellCompartmentPrivate);
11059 js::SetWindowProxyClass(cx, &ShellWindowProxyClass);
11061 js::UseInternalJobQueues(cx);
11063 js::SetPreserveWrapperCallbacks(cx, DummyPreserveWrapperCallback,
11064 DummyHasReleasedWrapperCallback);
11066 JS::SetHostCleanupFinalizationRegistryCallback(
11067 cx, ShellCleanupFinalizationRegistryCallback, this);
11068 JS_AddExtraGCRootsTracer(cx, TraceBlackRoots, nullptr);
11069 JS_SetGrayGCRootsTracer(cx, TraceGrayRoots, nullptr);
11071 return true;
11074 ShellContext::~ShellContext() {
11075 markObservers.reset();
11076 if (cx_) {
11077 JS_SetContextPrivate(cx_, nullptr);
11078 JS::SetHostCleanupFinalizationRegistryCallback(cx_, nullptr, nullptr);
11079 JS_SetGrayGCRootsTracer(cx_, nullptr, nullptr);
11080 JS_RemoveExtraGCRootsTracer(cx_, TraceBlackRoots, nullptr);
11082 MOZ_ASSERT(offThreadJobs.empty());
11085 static int Shell(JSContext* cx, OptionParser* op) {
11086 #ifdef JS_STRUCTURED_SPEW
11087 cx->spewer().enableSpewing();
11088 #endif
11090 auto exitShell = MakeScopeExit([&] {
11091 #ifdef JS_STRUCTURED_SPEW
11092 cx->spewer().disableSpewing();
11093 #endif
11096 #ifdef MOZ_CODE_COVERAGE
11097 InstallCoverageSignalHandlers();
11098 #endif
11100 Maybe<JS::AutoDisableGenerationalGC> noggc;
11101 if (op->getBoolOption("no-ggc")) {
11102 noggc.emplace(cx);
11105 Maybe<AutoDisableCompactingGC> nocgc;
11106 if (op->getBoolOption("no-cgc")) {
11107 nocgc.emplace(cx);
11110 if (op->getBoolOption("fuzzing-safe")) {
11111 fuzzingSafe = true;
11112 } else {
11113 fuzzingSafe =
11114 (getenv("MOZ_FUZZING_SAFE") && getenv("MOZ_FUZZING_SAFE")[0] != '0');
11117 #ifdef DEBUG
11118 if (op->getBoolOption("differential-testing")) {
11119 JS::SetSupportDifferentialTesting(true);
11121 #endif
11123 if (op->getBoolOption("disable-oom-functions")) {
11124 disableOOMFunctions = true;
11127 if (op->getBoolOption("more-compartments")) {
11128 defaultToSameCompartment = false;
11131 bool reprl_mode = FuzzilliUseReprlMode(op);
11133 // Begin REPRL Loop
11134 int result = EXIT_SUCCESS;
11135 do {
11136 JS::RealmOptions options;
11137 SetStandardRealmOptions(options);
11138 RootedObject glob(
11139 cx, NewGlobalObject(cx, options, nullptr, ShellGlobalKind::WindowProxy,
11140 /* immutablePrototype = */ true));
11141 if (!glob) {
11142 return 1;
11145 JSAutoRealm ar(cx, glob);
11147 ShellContext* sc = GetShellContext(cx);
11148 if (!sc->moduleLoader && !InitModuleLoader(cx, *op)) {
11149 return EXIT_FAILURE;
11152 #ifdef FUZZING_INTERFACES
11153 if (fuzzHaveModule) {
11154 return FuzzJSRuntimeStart(cx, &sArgc, &sArgv);
11156 #endif
11158 sc->exitCode = 0;
11159 result = EXIT_SUCCESS;
11161 AutoReportException are(cx);
11162 if (!ProcessArgs(cx, op) && !sc->quitting) {
11163 result = EXITCODE_RUNTIME_ERROR;
11168 * The job queue must be drained even on error to finish outstanding async
11169 * tasks before the main thread JSRuntime is torn down. Drain after
11170 * uncaught exceptions have been reported since draining runs callbacks.
11172 RunShellJobs(cx);
11174 // Only if there's no other error, report unhandled rejections.
11175 if (!result && !sc->exitCode) {
11176 AutoReportException are(cx);
11177 if (!ReportUnhandledRejections(cx)) {
11178 FILE* fp = ErrorFilePointer();
11179 fputs("Error while printing unhandled rejection\n", fp);
11183 if (sc->exitCode) {
11184 result = sc->exitCode;
11187 #ifdef FUZZING_JS_FUZZILLI
11188 if (reprl_mode) {
11189 fflush(stdout);
11190 fflush(stderr);
11191 // Send return code to parent and reset edge counters.
11192 struct {
11193 int status;
11194 uint32_t execHash;
11195 uint32_t execHashInputs;
11196 } s;
11197 s.status = (result & 0xff) << 8;
11198 s.execHash = cx->executionHash;
11199 s.execHashInputs = cx->executionHashInputs;
11200 MOZ_RELEASE_ASSERT(write(REPRL_CWFD, &s, 12) == 12);
11201 __sanitizer_cov_reset_edgeguards();
11202 cx->executionHash = 1;
11203 cx->executionHashInputs = 0;
11205 #endif
11207 if (enableDisassemblyDumps) {
11208 AutoReportException are(cx);
11209 if (!js::DumpRealmPCCounts(cx)) {
11210 result = EXITCODE_OUT_OF_MEMORY;
11214 // End REPRL loop
11215 } while (reprl_mode);
11217 return result;
11220 // Used to allocate memory when jemalloc isn't yet initialized.
11221 JS_DECLARE_NEW_METHODS(SystemAlloc_New, malloc, static)
11223 static void SetOutputFile(const char* const envVar, RCFile* defaultOut,
11224 RCFile** outFileP) {
11225 RCFile* outFile;
11227 const char* outPath = getenv(envVar);
11228 FILE* newfp;
11229 if (outPath && *outPath && (newfp = fopen(outPath, "w"))) {
11230 outFile = SystemAlloc_New<RCFile>(newfp);
11231 } else {
11232 outFile = defaultOut;
11235 if (!outFile) {
11236 MOZ_CRASH("Failed to allocate output file");
11239 outFile->acquire();
11240 *outFileP = outFile;
11243 static void PreInit() {
11244 #ifdef XP_WIN
11245 const char* crash_option = getenv("XRE_NO_WINDOWS_CRASH_DIALOG");
11246 if (crash_option && crash_option[0] == '1') {
11247 // Disable the segfault dialog. We want to fail the tests immediately
11248 // instead of hanging automation.
11249 UINT newMode = SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX;
11250 UINT prevMode = SetErrorMode(newMode);
11251 SetErrorMode(prevMode | newMode);
11253 #endif
11256 #ifndef JS_WITHOUT_NSPR
11257 class AutoLibraryLoader {
11258 Vector<PRLibrary*, 4, SystemAllocPolicy> libraries;
11260 public:
11261 ~AutoLibraryLoader() {
11262 for (auto dll : libraries) {
11263 PR_UnloadLibrary(dll);
11267 PRLibrary* load(const char* path) {
11268 PRLibSpec libSpec;
11269 libSpec.type = PR_LibSpec_Pathname;
11270 libSpec.value.pathname = path;
11271 PRLibrary* dll = PR_LoadLibraryWithFlags(libSpec, PR_LD_NOW | PR_LD_GLOBAL);
11272 if (!dll) {
11273 fprintf(stderr, "LoadLibrary '%s' failed with code %d\n", path,
11274 PR_GetError());
11275 MOZ_CRASH("Failed to load library");
11278 MOZ_ALWAYS_TRUE(libraries.append(dll));
11279 return dll;
11282 #endif
11284 static bool ReadSelfHostedXDRFile(JSContext* cx, FileContents& buf) {
11285 FILE* file = fopen(selfHostedXDRPath, "rb");
11286 if (!file) {
11287 fprintf(stderr, "Can't open self-hosted stencil XDR file.\n");
11288 return false;
11290 AutoCloseFile autoClose(file);
11292 struct stat st;
11293 if (fstat(fileno(file), &st) < 0) {
11294 fprintf(stderr, "Unable to stat self-hosted stencil XDR file.\n");
11295 return false;
11298 if (st.st_size >= INT32_MAX) {
11299 fprintf(stderr, "self-hosted stencil XDR file too large.\n");
11300 return false;
11302 uint32_t filesize = uint32_t(st.st_size);
11304 if (!buf.growBy(filesize)) {
11305 return false;
11307 size_t cc = fread(buf.begin(), 1, filesize, file);
11308 if (cc != filesize) {
11309 fprintf(stderr, "Short read on self-hosted stencil XDR file.\n");
11310 return false;
11313 return true;
11316 static bool WriteSelfHostedXDRFile(JSContext* cx, JS::SelfHostedCache buffer) {
11317 FILE* file = fopen(selfHostedXDRPath, "wb");
11318 if (!file) {
11319 JS_ReportErrorUTF8(cx, "Can't open self-hosted stencil XDR file.");
11320 return false;
11322 AutoCloseFile autoClose(file);
11324 size_t cc = fwrite(buffer.Elements(), 1, buffer.LengthBytes(), file);
11325 if (cc != buffer.LengthBytes()) {
11326 JS_ReportErrorUTF8(cx, "Short write on self-hosted stencil XDR file.");
11327 return false;
11330 return true;
11333 static bool SetGCParameterFromArg(JSContext* cx, char* arg) {
11334 char* c = strchr(arg, '=');
11335 if (!c) {
11336 fprintf(stderr,
11337 "Error: --gc-param argument '%s' must be of the form "
11338 "name=decimalValue\n",
11339 arg);
11340 return false;
11343 *c = '\0';
11344 const char* name = arg;
11345 const char* valueStr = c + 1;
11347 JSGCParamKey key;
11348 bool writable;
11349 if (!GetGCParameterInfo(name, &key, &writable)) {
11350 fprintf(stderr, "Error: Unknown GC parameter name '%s'\n", name);
11351 fprintf(stderr, "Writable GC parameter names are:\n");
11352 #define PRINT_WRITABLE_PARAM_NAME(name, _, writable) \
11353 if (writable) { \
11354 fprintf(stderr, " %s\n", name); \
11356 FOR_EACH_GC_PARAM(PRINT_WRITABLE_PARAM_NAME)
11357 #undef PRINT_WRITABLE_PARAM_NAME
11358 return false;
11361 if (!writable) {
11362 fprintf(stderr, "Error: GC parameter '%s' is not writable\n", name);
11363 return false;
11366 char* end = nullptr;
11367 unsigned long int value = strtoul(valueStr, &end, 10);
11368 if (end == valueStr || *end) {
11369 fprintf(stderr,
11370 "Error: Could not parse '%s' as decimal for GC parameter '%s'\n",
11371 valueStr, name);
11372 return false;
11375 uint32_t paramValue = uint32_t(value);
11376 if (value == ULONG_MAX || value != paramValue ||
11377 !cx->runtime()->gc.setParameter(cx, key, paramValue)) {
11378 fprintf(stderr, "Error: Value %s is out of range for GC parameter '%s'\n",
11379 valueStr, name);
11380 return false;
11383 return true;
11386 int main(int argc, char** argv) {
11387 PreInit();
11389 sArgc = argc;
11390 sArgv = argv;
11392 int result;
11394 setlocale(LC_ALL, "");
11396 // Special-case stdout and stderr. We bump their refcounts to prevent them
11397 // from getting closed and then having some printf fail somewhere.
11398 RCFile rcStdout(stdout);
11399 rcStdout.acquire();
11400 RCFile rcStderr(stderr);
11401 rcStderr.acquire();
11403 SetOutputFile("JS_STDOUT", &rcStdout, &gOutFile);
11404 SetOutputFile("JS_STDERR", &rcStderr, &gErrFile);
11406 // Use a larger jemalloc page cache. This should match the value for browser
11407 // foreground processes in ContentChild::RecvNotifyProcessPriorityChanged.
11408 moz_set_max_dirty_page_modifier(4);
11410 OptionParser op("Usage: {progname} [options] [[script] scriptArgs*]");
11411 if (!InitOptionParser(op)) {
11412 return EXIT_FAILURE;
11415 switch (op.parseArgs(argc, argv)) {
11416 case OptionParser::EarlyExit:
11417 return EXIT_SUCCESS;
11418 case OptionParser::ParseError:
11419 op.printHelp(argv[0]);
11420 return EXIT_FAILURE;
11421 case OptionParser::Fail:
11422 return EXIT_FAILURE;
11423 case OptionParser::Okay:
11424 break;
11427 if (op.getHelpOption()) {
11428 return EXIT_SUCCESS;
11431 if (!SetGlobalOptionsPreJSInit(op)) {
11432 return EXIT_FAILURE;
11435 // Start the engine.
11436 if (const char* message = JS_InitWithFailureDiagnostic()) {
11437 fprintf(gErrFile->fp, "JS_Init failed: %s\n", message);
11438 return 1;
11441 // `selfHostedXDRBuffer` contains XDR buffer of the self-hosted JS.
11442 // A part of it is borrowed by ImmutableScriptData of the self-hosted scripts.
11444 // This buffer should outlive JS_Shutdown.
11445 Maybe<FileContents> selfHostedXDRBuffer;
11447 auto shutdownEngine = MakeScopeExit([] { JS_ShutDown(); });
11449 if (!SetGlobalOptionsPostJSInit(op)) {
11450 return EXIT_FAILURE;
11453 // Record aggregated telemetry data on disk. Do this as early as possible such
11454 // that the telemetry is recording both before starting the context and after
11455 // closing it.
11456 auto writeTelemetryResults = MakeScopeExit([&op] {
11457 if (telemetryLock) {
11458 const char* dir = op.getStringOption("telemetry-dir");
11459 WriteTelemetryDataToDisk(dir);
11460 js_free(telemetryLock);
11461 telemetryLock = nullptr;
11465 if (!InitSharedObjectMailbox()) {
11466 return EXIT_FAILURE;
11469 JS::SetProcessBuildIdOp(ShellBuildId);
11471 /* Use the same parameters as the browser in xpcjsruntime.cpp. */
11472 JSContext* const cx = JS_NewContext(JS::DefaultHeapMaxBytes);
11473 if (!cx) {
11474 return 1;
11477 // Register telemetry callbacks, if needed.
11478 if (telemetryLock) {
11479 JS_SetAccumulateTelemetryCallback(cx, AccumulateTelemetryDataCallback);
11482 auto destroyCx = MakeScopeExit([cx] { JS_DestroyContext(cx); });
11484 UniquePtr<ShellContext> sc =
11485 MakeUnique<ShellContext>(cx, ShellContext::MainThread);
11486 if (!sc || !sc->registerWithCx(cx)) {
11487 return 1;
11490 if (!SetContextOptions(cx, op)) {
11491 return 1;
11494 JS_SetTrustedPrincipals(cx, &ShellPrincipals::fullyTrusted);
11495 JS_SetSecurityCallbacks(cx, &ShellPrincipals::securityCallbacks);
11497 JS_AddInterruptCallback(cx, ShellInterruptCallback);
11499 JS::SetGCSliceCallback(cx, GCSliceCallback);
11501 bufferStreamState = js_new<ExclusiveWaitableData<BufferStreamState>>(
11502 mutexid::BufferStreamState);
11503 if (!bufferStreamState) {
11504 return 1;
11506 auto shutdownBufferStreams = MakeScopeExit([] {
11507 ShutdownBufferStreams();
11508 js_delete(bufferStreamState);
11510 JS::InitConsumeStreamCallback(cx, ConsumeBufferSource, ReportStreamError);
11512 JS::SetPromiseRejectionTrackerCallback(
11513 cx, ForwardingPromiseRejectionTrackerCallback);
11515 JS::dbg::SetDebuggerMallocSizeOf(cx, moz_malloc_size_of);
11517 auto shutdownShellThreads = MakeScopeExit([cx] {
11518 KillWatchdog(cx);
11519 KillWorkerThreads(cx);
11520 DestructSharedObjectMailbox();
11521 CancelOffThreadJobsForRuntime(cx);
11524 // The file content should stay alive as long as Worker thread can be
11525 // initialized.
11526 JS::SelfHostedCache xdrSpan = nullptr;
11527 JS::SelfHostedWriter xdrWriter = nullptr;
11528 if (selfHostedXDRPath) {
11529 if (encodeSelfHostedCode) {
11530 xdrWriter = WriteSelfHostedXDRFile;
11531 } else {
11532 selfHostedXDRBuffer.emplace(cx);
11533 if (ReadSelfHostedXDRFile(cx, *selfHostedXDRBuffer)) {
11534 MOZ_ASSERT(selfHostedXDRBuffer->length() > 0);
11535 JS::SelfHostedCache span(selfHostedXDRBuffer->begin(),
11536 selfHostedXDRBuffer->end());
11537 xdrSpan = span;
11538 } else {
11539 fprintf(stderr, "Falling back on parsing source.\n");
11540 selfHostedXDRPath = nullptr;
11545 if (!JS::InitSelfHostedCode(cx, xdrSpan, xdrWriter)) {
11546 return 1;
11549 EnvironmentPreparer environmentPreparer(cx);
11551 JS::SetProcessLargeAllocationFailureCallback(my_LargeAllocFailCallback);
11553 if (op.getBoolOption("wasm-compile-and-serialize")) {
11554 #ifdef __wasi__
11555 MOZ_CRASH("WASI doesn't support wasm");
11556 #else
11557 if (!WasmCompileAndSerialize(cx)) {
11558 // Errors have been printed directly to stderr.
11559 MOZ_ASSERT(!cx->isExceptionPending());
11560 return EXIT_FAILURE;
11562 #endif
11563 return EXIT_SUCCESS;
11566 result = Shell(cx, &op);
11568 #ifdef DEBUG
11569 if (OOM_printAllocationCount) {
11570 printf("OOM max count: %" PRIu64 "\n", js::oom::simulator.counter());
11572 #endif
11574 return result;
11577 bool InitOptionParser(OptionParser& op) {
11578 op.setDescription(
11579 "The SpiderMonkey shell provides a command line interface to the "
11580 "JavaScript engine. Code and file options provided via the command line "
11581 "are "
11582 "run left to right. If provided, the optional script argument is run "
11583 "after "
11584 "all options have been processed. Just-In-Time compilation modes may be "
11585 "enabled via "
11586 "command line options.");
11587 op.setDescriptionWidth(72);
11588 op.setHelpWidth(80);
11589 op.setVersion(JS_GetImplementationVersion());
11591 if (!op.addMultiStringOption(
11592 'f', "file", "PATH",
11593 "File path to run, parsing file contents as UTF-8") ||
11594 !op.addMultiStringOption(
11595 'u', "utf16-file", "PATH",
11596 "File path to run, inflating the file's UTF-8 contents to UTF-16 and "
11597 "then parsing that") ||
11598 !op.addMultiStringOption('m', "module", "PATH", "Module path to run") ||
11599 !op.addMultiStringOption('p', "prelude", "PATH", "Prelude path to run") ||
11600 !op.addMultiStringOption('e', "execute", "CODE", "Inline code to run") ||
11601 !op.addStringOption('\0', "selfhosted-xdr-path", "[filename]",
11602 "Read/Write selfhosted script data from/to the given "
11603 "XDR file") ||
11604 !op.addStringOption('\0', "selfhosted-xdr-mode", "(encode,decode,off)",
11605 "Whether to encode/decode data of the file provided"
11606 "with --selfhosted-xdr-path.") ||
11607 !op.addBoolOption('i', "shell", "Enter prompt after running code") ||
11608 !op.addBoolOption('c', "compileonly",
11609 "Only compile, don't run (syntax checking mode)") ||
11610 !op.addBoolOption('w', "warnings", "Emit warnings") ||
11611 !op.addBoolOption('W', "nowarnings", "Don't emit warnings") ||
11612 !op.addBoolOption('D', "dump-bytecode",
11613 "Dump bytecode with exec count for all scripts") ||
11614 !op.addBoolOption('b', "print-timing",
11615 "Print sub-ms runtime for each file that's run") ||
11616 !op.addBoolOption('\0', "code-coverage",
11617 "Enable code coverage instrumentation.") ||
11618 !op.addBoolOption(
11619 '\0', "disable-parser-deferred-alloc",
11620 "Disable deferred allocation of GC objects until after parser") ||
11621 #ifdef DEBUG
11622 !op.addBoolOption('O', "print-alloc",
11623 "Print the number of allocations at exit") ||
11624 #endif
11625 !op.addOptionalStringArg("script",
11626 "A script to execute (after all options)") ||
11627 !op.addOptionalMultiStringArg(
11628 "scriptArgs",
11629 "String arguments to bind as |scriptArgs| in the "
11630 "shell's global") ||
11631 !op.addIntOption(
11632 '\0', "cpu-count", "COUNT",
11633 "Set the number of CPUs (hardware threads) to COUNT, the "
11634 "default is the actual number of CPUs. The total number of "
11635 "background helper threads is the CPU count plus some constant.",
11636 -1) ||
11637 !op.addIntOption('\0', "thread-count", "COUNT", "Alias for --cpu-count.",
11638 -1) ||
11639 !op.addBoolOption('\0', "ion", "Enable IonMonkey (default)") ||
11640 !op.addBoolOption('\0', "no-ion", "Disable IonMonkey") ||
11641 !op.addBoolOption('\0', "no-ion-for-main-context",
11642 "Disable IonMonkey for the main context only") ||
11643 !op.addIntOption('\0', "inlining-entry-threshold", "COUNT",
11644 "The minimum stub entry count before trial-inlining a"
11645 " call",
11646 -1) ||
11647 !op.addIntOption('\0', "small-function-length", "COUNT",
11648 "The maximum bytecode length of a 'small function' for "
11649 "the purpose of inlining.",
11650 -1) ||
11651 !op.addBoolOption('\0', "only-inline-selfhosted",
11652 "Only inline selfhosted functions") ||
11653 !op.addBoolOption('\0', "no-asmjs", "Disable asm.js compilation") ||
11654 !op.addStringOption(
11655 '\0', "wasm-compiler", "[option]",
11656 "Choose to enable a subset of the wasm compilers, valid options are "
11657 "'none', 'baseline', 'ion', 'optimizing', "
11658 "'baseline+ion', 'baseline+optimizing'.") ||
11659 !op.addBoolOption('\0', "wasm-verbose",
11660 "Enable WebAssembly verbose logging") ||
11661 !op.addBoolOption('\0', "disable-wasm-huge-memory",
11662 "Disable WebAssembly huge memory") ||
11663 !op.addBoolOption('\0', "test-wasm-await-tier2",
11664 "Forcibly activate tiering and block "
11665 "instantiation on completion of tier2") ||
11666 #define WASM_FEATURE(NAME, LOWER_NAME, STAGE, COMPILE_PRED, COMPILER_PRED, \
11667 FLAG_PRED, FLAG_FORCE_ON, FLAG_FUZZ_ON, SHELL, ...) \
11668 !op.addBoolOption('\0', "no-wasm-" SHELL, \
11669 STAGE == WasmFeatureStage::Experimental \
11670 ? "No-op." \
11671 : "Disable wasm " SHELL " feature.") || \
11672 !op.addBoolOption('\0', "wasm-" SHELL, \
11673 STAGE == WasmFeatureStage::Experimental \
11674 ? "Enable wasm " SHELL " feature." \
11675 : "No-op.") ||
11676 JS_FOR_WASM_FEATURES(WASM_FEATURE)
11677 #undef WASM_FEATURE
11678 !op.addBoolOption('\0', "no-native-regexp",
11679 "Disable native regexp compilation") ||
11680 !op.addIntOption(
11681 '\0', "regexp-warmup-threshold", "COUNT",
11682 "Wait for COUNT invocations before compiling regexps to native code "
11683 "(default 10)",
11684 -1) ||
11685 !op.addBoolOption('\0', "trace-regexp-parser", "Trace regexp parsing") ||
11686 !op.addBoolOption('\0', "trace-regexp-assembler",
11687 "Trace regexp assembler") ||
11688 !op.addBoolOption('\0', "trace-regexp-interpreter",
11689 "Trace regexp interpreter") ||
11690 !op.addBoolOption('\0', "trace-regexp-peephole",
11691 "Trace regexp peephole optimization") ||
11692 !op.addBoolOption('\0', "less-debug-code",
11693 "Emit less machine code for "
11694 "checking assertions under DEBUG.") ||
11695 !op.addBoolOption('\0', "disable-weak-refs", "Disable weak references") ||
11696 !op.addBoolOption('\0', "disable-tosource", "Disable toSource/uneval") ||
11697 !op.addBoolOption('\0', "disable-property-error-message-fix",
11698 "Disable fix for the error message when accessing "
11699 "property of null or undefined") ||
11700 !op.addBoolOption('\0', "enable-iterator-helpers",
11701 "Enable iterator helpers") ||
11702 !op.addBoolOption('\0', "enable-shadow-realms", "Enable ShadowRealms") ||
11703 !op.addBoolOption('\0', "disable-array-grouping",
11704 "Disable Object.groupBy and Map.groupBy") ||
11705 !op.addBoolOption('\0', "disable-well-formed-unicode-strings",
11706 "Disable String.prototype.{is,to}WellFormed() methods"
11707 "(Well-Formed Unicode Strings) (default: Enabled)") ||
11708 !op.addBoolOption('\0', "enable-new-set-methods",
11709 "Enable New Set methods") ||
11710 !op.addBoolOption('\0', "disable-arraybuffer-transfer",
11711 "Disable ArrayBuffer.prototype.transfer() methods") ||
11712 !op.addBoolOption('\0', "enable-symbols-as-weakmap-keys",
11713 "Enable Symbols As WeakMap keys") ||
11714 !op.addBoolOption(
11715 '\0', "enable-arraybuffer-resizable",
11716 "Enable resizable ArrayBuffers and growable SharedArrayBuffers") ||
11717 !op.addBoolOption('\0', "enable-top-level-await",
11718 "Enable top-level await") ||
11719 !op.addBoolOption('\0', "enable-class-static-blocks",
11720 "(no-op) Enable class static blocks") ||
11721 !op.addBoolOption('\0', "enable-import-assertions",
11722 "Enable import attributes with old assert syntax") ||
11723 !op.addBoolOption('\0', "enable-import-attributes",
11724 "Enable import attributes") ||
11725 !op.addBoolOption('\0', "disable-destructuring-fuse",
11726 "Disable Destructuring Fuse") ||
11727 !op.addStringOption('\0', "shared-memory", "on/off",
11728 "SharedArrayBuffer and Atomics "
11729 #if SHARED_MEMORY_DEFAULT
11730 "(default: on, off to disable)"
11731 #else
11732 "(default: off, on to enable)"
11733 #endif
11734 ) ||
11735 !op.addStringOption('\0', "spectre-mitigations", "on/off",
11736 "Whether Spectre mitigations are enabled (default: "
11737 "off, on to enable)") ||
11738 !op.addStringOption('\0', "write-protect-code", "on/off",
11739 "Whether the W^X policy is enforced to mark JIT code "
11740 "pages as either writable or executable but never "
11741 "both at the same time (default: on, off to "
11742 "disable)") ||
11743 !op.addStringOption('\0', "cache-ir-stubs", "on/off/call",
11744 "Use CacheIR stubs (default: on, off to disable, "
11745 "call to enable work-in-progress call ICs)") ||
11746 !op.addStringOption('\0', "ion-shared-stubs", "on/off",
11747 "Use shared stubs (default: on, off to disable)") ||
11748 !op.addStringOption('\0', "ion-scalar-replacement", "on/off",
11749 "Scalar Replacement (default: on, off to disable)") ||
11750 !op.addStringOption('\0', "ion-gvn", "[mode]",
11751 "Specify Ion global value numbering:\n"
11752 " off: disable GVN\n"
11753 " on: enable GVN (default)\n") ||
11754 !op.addStringOption(
11755 '\0', "ion-licm", "on/off",
11756 "Loop invariant code motion (default: on, off to disable)") ||
11757 !op.addStringOption('\0', "ion-edgecase-analysis", "on/off",
11758 "Find edge cases where Ion can avoid bailouts "
11759 "(default: on, off to disable)") ||
11760 !op.addStringOption('\0', "ion-pruning", "on/off",
11761 "Branch pruning (default: on, off to disable)") ||
11762 !op.addStringOption('\0', "ion-range-analysis", "on/off",
11763 "Range analysis (default: on, off to disable)") ||
11764 !op.addStringOption('\0', "ion-sink", "on/off",
11765 "Sink code motion (default: off, on to enable)") ||
11766 !op.addStringOption('\0', "ion-optimization-levels", "on/off",
11767 "No-op for fuzzing") ||
11768 !op.addStringOption('\0', "ion-loop-unrolling", "on/off",
11769 "(NOP for fuzzers)") ||
11770 !op.addStringOption(
11771 '\0', "ion-instruction-reordering", "on/off",
11772 "Instruction reordering (default: off, on to enable)") ||
11773 !op.addStringOption(
11774 '\0', "ion-optimize-shapeguards", "on/off",
11775 "Eliminate redundant shape guards (default: on, off to disable)") ||
11776 !op.addStringOption(
11777 '\0', "ion-optimize-gcbarriers", "on/off",
11778 "Eliminate redundant GC barriers (default: on, off to disable)") ||
11779 !op.addStringOption('\0', "ion-iterator-indices", "on/off",
11780 "Optimize property access in for-in loops "
11781 "(default: on, off to disable)") ||
11782 !op.addStringOption('\0', "ion-load-keys", "on/off",
11783 "Atomize property loads used as keys "
11784 "(default: on, off to disable)") ||
11785 !op.addBoolOption('\0', "ion-check-range-analysis",
11786 "Range analysis checking") ||
11787 !op.addBoolOption('\0', "ion-extra-checks",
11788 "Perform extra dynamic validation checks") ||
11789 !op.addStringOption(
11790 '\0', "ion-inlining", "on/off",
11791 "Inline methods where possible (default: on, off to disable)") ||
11792 !op.addStringOption(
11793 '\0', "ion-osr", "on/off",
11794 "On-Stack Replacement (default: on, off to disable)") ||
11795 !op.addBoolOption('\0', "disable-bailout-loop-check",
11796 "Turn off bailout loop check") ||
11797 !op.addBoolOption('\0', "enable-ic-frame-pointers",
11798 "Use frame pointers in all IC stubs") ||
11799 !op.addBoolOption('\0', "scalar-replace-arguments",
11800 "Use scalar replacement to optimize ArgumentsObject") ||
11801 !op.addStringOption(
11802 '\0', "ion-limit-script-size", "on/off",
11803 "Don't compile very large scripts (default: on, off to disable)") ||
11804 !op.addIntOption('\0', "ion-warmup-threshold", "COUNT",
11805 "Wait for COUNT calls or iterations before compiling "
11806 "at the normal optimization level (default: 1000)",
11807 -1) ||
11808 !op.addIntOption('\0', "ion-full-warmup-threshold", "COUNT",
11809 "No-op for fuzzing", -1) ||
11810 !op.addStringOption(
11811 '\0', "ion-regalloc", "[mode]",
11812 "Specify Ion register allocation:\n"
11813 " backtracking: Priority based backtracking register allocation "
11814 "(default)\n"
11815 " testbed: Backtracking allocator with experimental features\n"
11816 " stupid: Simple block local register allocation") ||
11817 !op.addBoolOption(
11818 '\0', "ion-eager",
11819 "Always ion-compile methods (implies --baseline-eager)") ||
11820 !op.addBoolOption('\0', "fast-warmup",
11821 "Reduce warmup thresholds for each tier.") ||
11822 !op.addStringOption('\0', "ion-offthread-compile", "on/off",
11823 "Compile scripts off thread (default: on)") ||
11824 !op.addStringOption('\0', "ion-parallel-compile", "on/off",
11825 "--ion-parallel compile is deprecated. Use "
11826 "--ion-offthread-compile.") ||
11827 !op.addBoolOption('\0', "baseline",
11828 "Enable baseline compiler (default)") ||
11829 !op.addBoolOption('\0', "no-baseline", "Disable baseline compiler") ||
11830 !op.addBoolOption('\0', "baseline-eager",
11831 "Always baseline-compile methods") ||
11832 #ifdef ENABLE_PORTABLE_BASELINE_INTERP
11833 !op.addBoolOption('\0', "portable-baseline-eager",
11834 "Always use the porbale baseline interpreter") ||
11835 !op.addBoolOption('\0', "portable-baseline",
11836 "Enable Portable Baseline Interpreter (default)") ||
11837 !op.addBoolOption('\0', "no-portable-baseline",
11838 "Disable Portable Baseline Interpreter") ||
11839 #endif
11840 !op.addIntOption(
11841 '\0', "baseline-warmup-threshold", "COUNT",
11842 "Wait for COUNT calls or iterations before baseline-compiling "
11843 "(default: 10)",
11844 -1) ||
11845 !op.addBoolOption('\0', "blinterp",
11846 "Enable Baseline Interpreter (default)") ||
11847 !op.addBoolOption('\0', "no-blinterp", "Disable Baseline Interpreter") ||
11848 !op.addBoolOption('\0', "disable-jithints",
11849 "Disable caching eager baseline compilation hints.") ||
11850 !op.addBoolOption(
11851 '\0', "emit-interpreter-entry",
11852 "Emit Interpreter entry trampolines (default under --enable-perf)") ||
11853 !op.addBoolOption(
11854 '\0', "no-emit-interpreter-entry",
11855 "Do not emit Interpreter entry trampolines (default).") ||
11856 !op.addBoolOption('\0', "blinterp-eager",
11857 "Always Baseline-interpret scripts") ||
11858 !op.addIntOption(
11859 '\0', "blinterp-warmup-threshold", "COUNT",
11860 "Wait for COUNT calls or iterations before Baseline-interpreting "
11861 "(default: 10)",
11862 -1) ||
11863 !op.addIntOption(
11864 '\0', "trial-inlining-warmup-threshold", "COUNT",
11865 "Wait for COUNT calls or iterations before trial-inlining "
11866 "(default: 500)",
11867 -1) ||
11868 !op.addStringOption(
11869 '\0', "monomorphic-inlining", "default/always/never",
11870 "Whether monomorphic inlining is used instead of trial inlining "
11871 "always, never, or based on heuristics (default)") ||
11872 !op.addBoolOption(
11873 '\0', "non-writable-jitcode",
11874 "(NOP for fuzzers) Allocate JIT code as non-writable memory.") ||
11875 !op.addBoolOption(
11876 '\0', "no-sse3",
11877 "Pretend CPU does not support SSE3 instructions and above "
11878 "to test JIT codegen (no-op on platforms other than x86 and x64).") ||
11879 !op.addBoolOption(
11880 '\0', "no-ssse3",
11881 "Pretend CPU does not support SSSE3 [sic] instructions and above "
11882 "to test JIT codegen (no-op on platforms other than x86 and x64).") ||
11883 !op.addBoolOption(
11884 '\0', "no-sse41",
11885 "Pretend CPU does not support SSE4.1 instructions "
11886 "to test JIT codegen (no-op on platforms other than x86 and x64).") ||
11887 !op.addBoolOption('\0', "no-sse4", "Alias for --no-sse41") ||
11888 !op.addBoolOption(
11889 '\0', "no-sse42",
11890 "Pretend CPU does not support SSE4.2 instructions "
11891 "to test JIT codegen (no-op on platforms other than x86 and x64).") ||
11892 #ifdef ENABLE_WASM_AVX
11893 !op.addBoolOption('\0', "enable-avx",
11894 "No-op. AVX is enabled by default, if available.") ||
11895 !op.addBoolOption(
11896 '\0', "no-avx",
11897 "Pretend CPU does not support AVX or AVX2 instructions "
11898 "to test JIT codegen (no-op on platforms other than x86 and x64).") ||
11899 #else
11900 !op.addBoolOption('\0', "enable-avx",
11901 "AVX is disabled by default. Enable AVX. "
11902 "(no-op on platforms other than x86 and x64).") ||
11903 !op.addBoolOption('\0', "no-avx",
11904 "No-op. AVX is currently disabled by default.") ||
11905 #endif
11906 !op.addBoolOption('\0', "more-compartments",
11907 "Make newGlobal default to creating a new "
11908 "compartment.") ||
11909 !op.addBoolOption('\0', "fuzzing-safe",
11910 "Don't expose functions that aren't safe for "
11911 "fuzzers to call") ||
11912 #ifdef DEBUG
11913 !op.addBoolOption('\0', "differential-testing",
11914 "Avoid random/undefined behavior that disturbs "
11915 "differential testing (correctness fuzzing)") ||
11916 #endif
11917 !op.addBoolOption('\0', "disable-oom-functions",
11918 "Disable functions that cause "
11919 "artificial OOMs") ||
11920 !op.addBoolOption('\0', "no-threads", "Disable helper threads") ||
11921 !op.addBoolOption(
11922 '\0', "no-jit-backend",
11923 "Disable the JIT backend completely for this process") ||
11924 #ifdef DEBUG
11925 !op.addBoolOption('\0', "dump-entrained-variables",
11926 "Print variables which are "
11927 "unnecessarily entrained by inner functions") ||
11928 #endif
11929 !op.addBoolOption('\0', "no-ggc", "Disable Generational GC") ||
11930 !op.addBoolOption('\0', "no-cgc", "Disable Compacting GC") ||
11931 !op.addBoolOption('\0', "no-incremental-gc", "Disable Incremental GC") ||
11932 !op.addBoolOption('\0', "no-parallel-marking",
11933 "Disable GC parallel marking") ||
11934 !op.addBoolOption('\0', "enable-parallel-marking",
11935 "Enable GC parallel marking") ||
11936 !op.addIntOption(
11937 '\0', "marking-threads", "COUNT",
11938 "Set the number of threads used for parallel marking to COUNT.", 0) ||
11939 !op.addStringOption('\0', "nursery-strings", "on/off",
11940 "Allocate strings in the nursery") ||
11941 !op.addStringOption('\0', "nursery-bigints", "on/off",
11942 "Allocate BigInts in the nursery") ||
11943 !op.addIntOption('\0', "available-memory", "SIZE",
11944 "Select GC settings based on available memory (MB)",
11945 0) ||
11946 !op.addStringOption('\0', "arm-hwcap", "[features]",
11947 "Specify ARM code generation features, or 'help' to "
11948 "list all features.") ||
11949 !op.addIntOption('\0', "arm-asm-nop-fill", "SIZE",
11950 "Insert the given number of NOP instructions at all "
11951 "possible pool locations.",
11952 0) ||
11953 !op.addIntOption('\0', "asm-pool-max-offset", "OFFSET",
11954 "The maximum pc relative OFFSET permitted in pool "
11955 "reference instructions.",
11956 1024) ||
11957 !op.addBoolOption('\0', "arm-sim-icache-checks",
11958 "Enable icache flush checks in the ARM "
11959 "simulator.") ||
11960 !op.addIntOption('\0', "arm-sim-stop-at", "NUMBER",
11961 "Stop the ARM simulator after the given "
11962 "NUMBER of instructions.",
11963 -1) ||
11964 !op.addBoolOption('\0', "mips-sim-icache-checks",
11965 "Enable icache flush checks in the MIPS "
11966 "simulator.") ||
11967 !op.addIntOption('\0', "mips-sim-stop-at", "NUMBER",
11968 "Stop the MIPS simulator after the given "
11969 "NUMBER of instructions.",
11970 -1) ||
11971 !op.addBoolOption('\0', "loong64-sim-icache-checks",
11972 "Enable icache flush checks in the LoongArch64 "
11973 "simulator.") ||
11974 !op.addIntOption('\0', "loong64-sim-stop-at", "NUMBER",
11975 "Stop the LoongArch64 simulator after the given "
11976 "NUMBER of instructions.",
11977 -1) ||
11978 #ifdef JS_CODEGEN_RISCV64
11979 !op.addBoolOption('\0', "riscv-debug", "debug print riscv info.") ||
11980 #endif
11981 #ifdef JS_SIMULATOR_RISCV64
11982 !op.addBoolOption('\0', "trace-sim", "print simulator info.") ||
11983 !op.addBoolOption('\0', "debug-sim", "debug simulator.") ||
11984 !op.addBoolOption('\0', "riscv-trap-to-simulator-debugger",
11985 "trap into simulator debuggger.") ||
11986 !op.addIntOption('\0', "riscv-sim-stop-at", "NUMBER",
11987 "Stop the riscv simulator after the given "
11988 "NUMBER of instructions.",
11989 -1) ||
11990 #endif
11991 !op.addIntOption('\0', "nursery-size", "SIZE-MB",
11992 "Set the maximum nursery size in MB",
11993 JS::DefaultNurseryMaxBytes / 1024 / 1024) ||
11994 #ifdef JS_GC_ZEAL
11995 !op.addStringOption('z', "gc-zeal", "LEVEL(;LEVEL)*[,N]",
11996 gc::ZealModeHelpText) ||
11997 #else
11998 !op.addStringOption('z', "gc-zeal", "LEVEL(;LEVEL)*[,N]",
11999 "option ignored in non-gc-zeal builds") ||
12000 #endif
12001 !op.addMultiStringOption('\0', "gc-param", "NAME=VALUE",
12002 "Set a named GC parameter") ||
12003 !op.addStringOption('\0', "module-load-path", "DIR",
12004 "Set directory to load modules from") ||
12005 !op.addBoolOption('\0', "no-source-pragmas",
12006 "Disable source(Mapping)URL pragma parsing") ||
12007 !op.addBoolOption('\0', "no-async-stacks", "Disable async stacks") ||
12008 !op.addBoolOption('\0', "async-stacks-capture-debuggee-only",
12009 "Limit async stack capture to only debuggees") ||
12010 !op.addMultiStringOption('\0', "dll", "LIBRARY",
12011 "Dynamically load LIBRARY") ||
12012 !op.addBoolOption('\0', "suppress-minidump",
12013 "Suppress crash minidumps") ||
12014 #ifdef JS_ENABLE_SMOOSH
12015 !op.addBoolOption('\0', "smoosh", "Use SmooshMonkey") ||
12016 !op.addStringOption('\0', "not-implemented-watchfile", "[filename]",
12017 "Track NotImplemented errors in the new frontend") ||
12018 #else
12019 !op.addBoolOption('\0', "smoosh", "No-op") ||
12020 #endif
12021 !op.addStringOption(
12022 '\0', "delazification-mode", "[option]",
12023 "Select one of the delazification mode for scripts given on the "
12024 "command line, valid options are: "
12025 "'on-demand', 'concurrent-df', 'eager', 'concurrent-df+on-demand'. "
12026 "Choosing 'concurrent-df+on-demand' will run both concurrent-df and "
12027 "on-demand delazification mode, and compare compilation outcome. ") ||
12028 !op.addBoolOption('\0', "wasm-compile-and-serialize",
12029 "Compile the wasm bytecode from stdin and serialize "
12030 "the results to stdout") ||
12031 #ifdef FUZZING_JS_FUZZILLI
12032 !op.addBoolOption('\0', "reprl", "Enable REPRL mode for fuzzing") ||
12033 #endif
12034 !op.addStringOption('\0', "telemetry-dir", "[directory]",
12035 "Output telemetry results in a directory") ||
12036 !op.addBoolOption('\0', "use-fdlibm-for-sin-cos-tan",
12037 "Use fdlibm for Math.sin, Math.cos, and Math.tan")) {
12038 return false;
12041 op.setArgTerminatesOptions("script", true);
12042 op.setArgCapturesRest("scriptArgs");
12044 return true;
12047 bool SetGlobalOptionsPreJSInit(const OptionParser& op) {
12048 // Note: DisableJitBackend must be called before JS_InitWithFailureDiagnostic.
12049 if (op.getBoolOption("no-jit-backend")) {
12050 JS::DisableJitBackend();
12053 #if defined(JS_CODEGEN_ARM)
12054 if (const char* str = op.getStringOption("arm-hwcap")) {
12055 jit::SetARMHwCapFlagsString(str);
12058 int32_t fill = op.getIntOption("arm-asm-nop-fill");
12059 if (fill >= 0) {
12060 jit::Assembler::NopFill = fill;
12063 int32_t poolMaxOffset = op.getIntOption("asm-pool-max-offset");
12064 if (poolMaxOffset >= 5 && poolMaxOffset <= 1024) {
12065 jit::Assembler::AsmPoolMaxOffset = poolMaxOffset;
12067 #endif
12069 // Fish around in `op` for various important compiler-configuration flags
12070 // and make sure they get handed on to any child processes we might create.
12071 // See bug 1700900. Semantically speaking, this is all rather dubious:
12073 // * What set of flags need to be propagated in order to guarantee that the
12074 // child produces code that is "compatible" (in whatever sense) with that
12075 // produced by the parent? This isn't always easy to determine.
12077 // * There's nothing that ensures that flags given to the child are
12078 // presented in the same order that they exist in the parent's `argv[]`.
12079 // That could be a problem in the case where two flags with contradictory
12080 // meanings are given, and they are presented to the child in the opposite
12081 // order. For example: --wasm-compiler=optimizing --wasm-compiler=baseline.
12083 #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
12084 MOZ_ASSERT(!js::jit::CPUFlagsHaveBeenComputed());
12086 if (op.getBoolOption("no-sse3")) {
12087 js::jit::CPUInfo::SetSSE3Disabled();
12088 if (!sCompilerProcessFlags.append("--no-sse3")) {
12089 return false;
12092 if (op.getBoolOption("no-ssse3")) {
12093 js::jit::CPUInfo::SetSSSE3Disabled();
12094 if (!sCompilerProcessFlags.append("--no-ssse3")) {
12095 return false;
12098 if (op.getBoolOption("no-sse4") || op.getBoolOption("no-sse41")) {
12099 js::jit::CPUInfo::SetSSE41Disabled();
12100 if (!sCompilerProcessFlags.append("--no-sse41")) {
12101 return false;
12104 if (op.getBoolOption("no-sse42")) {
12105 js::jit::CPUInfo::SetSSE42Disabled();
12106 if (!sCompilerProcessFlags.append("--no-sse42")) {
12107 return false;
12110 if (op.getBoolOption("no-avx")) {
12111 js::jit::CPUInfo::SetAVXDisabled();
12112 if (!sCompilerProcessFlags.append("--no-avx")) {
12113 return false;
12116 if (op.getBoolOption("enable-avx")) {
12117 js::jit::CPUInfo::SetAVXEnabled();
12118 if (!sCompilerProcessFlags.append("--enable-avx")) {
12119 return false;
12122 #endif
12124 return true;
12127 bool SetGlobalOptionsPostJSInit(const OptionParser& op) {
12128 if (op.getStringOption("telemetry-dir")) {
12129 MOZ_ASSERT(!telemetryLock);
12130 telemetryLock = js_new<Mutex>(mutexid::ShellTelemetry);
12131 if (!telemetryLock) {
12132 return false;
12136 // Allow dumping on Linux with the fuzzing flag set, even when running with
12137 // the suid/sgid flag set on the shell.
12138 #ifdef XP_LINUX
12139 if (op.getBoolOption("fuzzing-safe")) {
12140 prctl(PR_SET_DUMPABLE, 1);
12142 #endif
12144 #ifdef DEBUG
12146 * Process OOM options as early as possible so that we can observe as many
12147 * allocations as possible.
12149 OOM_printAllocationCount = op.getBoolOption('O');
12150 #endif
12152 if (op.getBoolOption("no-threads")) {
12153 js::DisableExtraThreads();
12156 enableCodeCoverage = op.getBoolOption("code-coverage");
12157 if (enableCodeCoverage) {
12158 js::EnableCodeCoverage();
12161 // If LCov is enabled, then the default delazification mode should be changed
12162 // to parse everything eagerly, such that we know the location of every
12163 // instruction, to report them in the LCov summary, even if there is no uses
12164 // of these instructions.
12166 // Note: code coverage can be enabled either using the --code-coverage command
12167 // line, or the JS_CODE_COVERAGE_OUTPUT_DIR environment variable, which is
12168 // processed by JS_InitWithFailureDiagnostic.
12169 if (coverage::IsLCovEnabled()) {
12170 defaultDelazificationMode =
12171 JS::DelazificationOption::ParseEverythingEagerly;
12174 if (const char* xdr = op.getStringOption("selfhosted-xdr-path")) {
12175 shell::selfHostedXDRPath = xdr;
12177 if (const char* opt = op.getStringOption("selfhosted-xdr-mode")) {
12178 if (strcmp(opt, "encode") == 0) {
12179 shell::encodeSelfHostedCode = true;
12180 } else if (strcmp(opt, "decode") == 0) {
12181 shell::encodeSelfHostedCode = false;
12182 } else if (strcmp(opt, "off") == 0) {
12183 shell::selfHostedXDRPath = nullptr;
12184 } else {
12185 MOZ_CRASH(
12186 "invalid option value for --selfhosted-xdr-mode, must be "
12187 "encode/decode");
12191 #ifdef JS_WITHOUT_NSPR
12192 if (!op.getMultiStringOption("dll").empty()) {
12193 fprintf(stderr, "Error: --dll requires NSPR support!\n");
12194 return false;
12196 #else
12197 AutoLibraryLoader loader;
12198 MultiStringRange dllPaths = op.getMultiStringOption("dll");
12199 while (!dllPaths.empty()) {
12200 char* path = dllPaths.front();
12201 loader.load(path);
12202 dllPaths.popFront();
12204 #endif
12206 if (op.getBoolOption("suppress-minidump")) {
12207 js::NoteIntentionalCrash();
12210 // The fake CPU count must be set before initializing the Runtime,
12211 // which spins up the thread pool.
12212 int32_t cpuCount = op.getIntOption("cpu-count"); // What we're really setting
12213 if (cpuCount < 0) {
12214 cpuCount = op.getIntOption("thread-count"); // Legacy name
12216 if (cpuCount >= 0 && !SetFakeCPUCount(cpuCount)) {
12217 return false;
12220 return true;
12223 bool SetContextOptions(JSContext* cx, const OptionParser& op) {
12224 if (!SetContextWasmOptions(cx, op) || !SetContextJITOptions(cx, op) ||
12225 !SetContextGCOptions(cx, op)) {
12226 return false;
12229 enableSourcePragmas = !op.getBoolOption("no-source-pragmas");
12230 enableAsyncStacks = !op.getBoolOption("no-async-stacks");
12231 enableAsyncStackCaptureDebuggeeOnly =
12232 op.getBoolOption("async-stacks-capture-debuggee-only");
12233 enableWeakRefs = !op.getBoolOption("disable-weak-refs");
12234 enableToSource = !op.getBoolOption("disable-tosource");
12235 enablePropertyErrorMessageFix =
12236 !op.getBoolOption("disable-property-error-message-fix");
12237 enableIteratorHelpers = op.getBoolOption("enable-iterator-helpers");
12238 enableShadowRealms = op.getBoolOption("enable-shadow-realms");
12239 enableWellFormedUnicodeStrings =
12240 !op.getBoolOption("disable-well-formed-unicode-strings");
12241 enableArrayGrouping = !op.getBoolOption("disable-array-grouping");
12242 #ifdef NIGHTLY_BUILD
12243 enableNewSetMethods = op.getBoolOption("enable-new-set-methods");
12244 enableSymbolsAsWeakMapKeys =
12245 op.getBoolOption("enable-symbols-as-weakmap-keys");
12246 enableArrayBufferResizable = op.getBoolOption("enable-arraybuffer-resizable");
12247 #endif
12248 enableArrayBufferTransfer = !op.getBoolOption("disable-arraybuffer-transfer");
12249 enableImportAttributesAssertSyntax =
12250 op.getBoolOption("enable-import-assertions");
12251 enableImportAttributes = op.getBoolOption("enable-import-attributes") ||
12252 enableImportAttributesAssertSyntax;
12253 enableDestructuringFuse = !op.getBoolOption("disable-destructuring-fuse");
12254 useFdlibmForSinCosTan = op.getBoolOption("use-fdlibm-for-sin-cos-tan");
12256 JS::ContextOptionsRef(cx)
12257 .setSourcePragmas(enableSourcePragmas)
12258 .setAsyncStack(enableAsyncStacks)
12259 .setAsyncStackCaptureDebuggeeOnly(enableAsyncStackCaptureDebuggeeOnly)
12260 .setImportAttributes(enableImportAttributes)
12261 .setImportAttributesAssertSyntax(enableImportAttributesAssertSyntax)
12262 .setEnableDestructuringFuse(enableDestructuringFuse);
12264 JS::SetUseFdlibmForSinCosTan(useFdlibmForSinCosTan);
12266 if (const char* str = op.getStringOption("shared-memory")) {
12267 if (strcmp(str, "off") == 0) {
12268 enableSharedMemory = false;
12269 } else if (strcmp(str, "on") == 0) {
12270 enableSharedMemory = true;
12271 } else {
12272 return OptionFailure("shared-memory", str);
12276 reportWarnings = op.getBoolOption('w');
12277 compileOnly = op.getBoolOption('c');
12278 printTiming = op.getBoolOption('b');
12279 enableDisassemblyDumps = op.getBoolOption('D');
12280 cx->runtime()->profilingScripts =
12281 enableCodeCoverage || enableDisassemblyDumps;
12283 #ifdef JS_ENABLE_SMOOSH
12284 if (op.getBoolOption("smoosh")) {
12285 JS::ContextOptionsRef(cx).setTrySmoosh(true);
12286 js::frontend::InitSmoosh();
12289 if (const char* filename = op.getStringOption("not-implemented-watchfile")) {
12290 FILE* out = fopen(filename, "a");
12291 MOZ_RELEASE_ASSERT(out);
12292 setbuf(out, nullptr); // Make unbuffered
12293 cx->runtime()->parserWatcherFile.init(out);
12294 JS::ContextOptionsRef(cx).setTrackNotImplemented(true);
12296 #endif
12298 if (const char* mode = op.getStringOption("delazification-mode")) {
12299 if (strcmp(mode, "on-demand") == 0) {
12300 defaultDelazificationMode = JS::DelazificationOption::OnDemandOnly;
12301 } else if (strcmp(mode, "concurrent-df") == 0) {
12302 defaultDelazificationMode =
12303 JS::DelazificationOption::ConcurrentDepthFirst;
12304 } else if (strcmp(mode, "eager") == 0) {
12305 defaultDelazificationMode =
12306 JS::DelazificationOption::ParseEverythingEagerly;
12307 } else if (strcmp(mode, "concurrent-df+on-demand") == 0 ||
12308 strcmp(mode, "on-demand+concurrent-df") == 0) {
12309 defaultDelazificationMode =
12310 JS::DelazificationOption::CheckConcurrentWithOnDemand;
12311 } else {
12312 return OptionFailure("delazification-mode", mode);
12316 return true;
12319 bool SetContextWasmOptions(JSContext* cx, const OptionParser& op) {
12320 enableAsmJS = !op.getBoolOption("no-asmjs");
12322 enableWasm = true;
12323 enableWasmBaseline = true;
12324 enableWasmOptimizing = true;
12326 if (const char* str = op.getStringOption("wasm-compiler")) {
12327 if (strcmp(str, "none") == 0) {
12328 enableWasm = false;
12329 } else if (strcmp(str, "baseline") == 0) {
12330 MOZ_ASSERT(enableWasmBaseline);
12331 enableWasmOptimizing = false;
12332 } else if (strcmp(str, "optimizing") == 0 ||
12333 strcmp(str, "optimized") == 0) {
12334 enableWasmBaseline = false;
12335 MOZ_ASSERT(enableWasmOptimizing);
12336 } else if (strcmp(str, "baseline+optimizing") == 0 ||
12337 strcmp(str, "baseline+optimized") == 0) {
12338 MOZ_ASSERT(enableWasmBaseline);
12339 MOZ_ASSERT(enableWasmOptimizing);
12340 } else if (strcmp(str, "ion") == 0) {
12341 enableWasmBaseline = false;
12342 enableWasmOptimizing = true;
12343 } else if (strcmp(str, "baseline+ion") == 0) {
12344 MOZ_ASSERT(enableWasmBaseline);
12345 enableWasmOptimizing = true;
12346 } else {
12347 return OptionFailure("wasm-compiler", str);
12351 #define WASM_FEATURE(NAME, LOWER_NAME, STAGE, COMPILE_PRED, COMPILER_PRED, \
12352 FLAG_PRED, FLAG_FORCE_ON, FLAG_FUZZ_ON, SHELL, ...) \
12353 if (STAGE == WasmFeatureStage::Experimental) { \
12354 enableWasm##NAME = op.getBoolOption("wasm-" SHELL); \
12355 } else { \
12356 enableWasm##NAME = !op.getBoolOption("no-wasm-" SHELL); \
12359 JS_FOR_WASM_FEATURES(WASM_FEATURE);
12360 #undef WASM_FEATURE
12362 enableWasmVerbose = op.getBoolOption("wasm-verbose");
12363 enableTestWasmAwaitTier2 = op.getBoolOption("test-wasm-await-tier2");
12365 JS::ContextOptionsRef(cx)
12366 .setAsmJS(enableAsmJS)
12367 .setWasm(enableWasm)
12368 .setWasmForTrustedPrinciples(enableWasm)
12369 .setWasmBaseline(enableWasmBaseline)
12370 .setWasmIon(enableWasmOptimizing)
12371 #define WASM_FEATURE(NAME, ...) .setWasm##NAME(enableWasm##NAME)
12372 JS_FOR_WASM_FEATURES(WASM_FEATURE)
12373 #undef WASM_FEATURE
12376 #ifndef __wasi__
12377 // This must be set before self-hosted code is initialized, as self-hosted
12378 // code reads the property and the property may not be changed later.
12379 bool disabledHugeMemory = false;
12380 if (op.getBoolOption("disable-wasm-huge-memory")) {
12381 disabledHugeMemory = JS::DisableWasmHugeMemory();
12382 MOZ_RELEASE_ASSERT(disabledHugeMemory);
12385 // --disable-wasm-huge-memory needs to be propagated. See bug 1518210.
12386 if (disabledHugeMemory &&
12387 !sCompilerProcessFlags.append("--disable-wasm-huge-memory")) {
12388 return false;
12391 // Also the following are to be propagated.
12392 const char* to_propagate[] = {
12393 # define WASM_FEATURE(NAME, LOWER_NAME, STAGE, COMPILE_PRED, COMPILER_PRED, \
12394 FLAG_PRED, FLAG_FORCE_ON, FLAG_FUZZ_ON, SHELL, ...) \
12395 STAGE == WasmFeatureStage::Experimental ? "--wasm-" SHELL \
12396 : "--no-wasm-" SHELL,
12397 JS_FOR_WASM_FEATURES(WASM_FEATURE)
12398 # undef WASM_FEATURE
12399 // Compiler selection options
12400 "--test-wasm-await-tier2",
12401 NULL};
12402 for (const char** p = &to_propagate[0]; *p; p++) {
12403 if (op.getBoolOption(&(*p)[2] /* 2 => skip the leading '--' */)) {
12404 if (!sCompilerProcessFlags.append(*p)) {
12405 return false;
12410 // Also --wasm-compiler= is to be propagated. This is tricky because it is
12411 // necessary to reconstitute the --wasm-compiler=<whatever> string from its
12412 // pieces, without causing a leak. Hence it is copied into a static buffer.
12413 // This is thread-unsafe, but we're in `main()` and on the process' root
12414 // thread. Also, we do this only once -- it wouldn't work properly if we
12415 // handled multiple --wasm-compiler= flags in a loop.
12416 const char* wasm_compiler = op.getStringOption("wasm-compiler");
12417 if (wasm_compiler) {
12418 size_t n_needed =
12419 2 + strlen("wasm-compiler") + 1 + strlen(wasm_compiler) + 1;
12420 const size_t n_avail = 128;
12421 static char buf[n_avail];
12422 // `n_needed` depends on the compiler name specified. However, it can't
12423 // be arbitrarily long, since previous flag-checking should have limited
12424 // it to a set of known possibilities: "baseline", "ion",
12425 // "baseline+ion", Still, assert this for safety.
12426 MOZ_RELEASE_ASSERT(n_needed < n_avail);
12427 memset(buf, 0, sizeof(buf));
12428 SprintfBuf(buf, n_avail, "--%s=%s", "wasm-compiler", wasm_compiler);
12429 if (!sCompilerProcessFlags.append(buf)) {
12430 return false;
12433 #endif // __wasi__
12435 return true;
12438 bool SetContextJITOptions(JSContext* cx, const OptionParser& op) {
12439 // Check --fast-warmup first because it sets default warm-up thresholds. These
12440 // thresholds can then be overridden below by --ion-eager and other flags.
12441 if (op.getBoolOption("fast-warmup")) {
12442 jit::JitOptions.setFastWarmUp();
12445 if (op.getBoolOption("no-ion-for-main-context")) {
12446 JS::ContextOptionsRef(cx).setDisableIon();
12449 if (const char* str = op.getStringOption("cache-ir-stubs")) {
12450 if (strcmp(str, "on") == 0) {
12451 jit::JitOptions.disableCacheIR = false;
12452 } else if (strcmp(str, "off") == 0) {
12453 jit::JitOptions.disableCacheIR = true;
12454 } else {
12455 return OptionFailure("cache-ir-stubs", str);
12459 if (const char* str = op.getStringOption("spectre-mitigations")) {
12460 if (strcmp(str, "on") == 0) {
12461 jit::JitOptions.spectreIndexMasking = true;
12462 jit::JitOptions.spectreObjectMitigations = true;
12463 jit::JitOptions.spectreStringMitigations = true;
12464 jit::JitOptions.spectreValueMasking = true;
12465 jit::JitOptions.spectreJitToCxxCalls = true;
12466 } else if (strcmp(str, "off") == 0) {
12467 jit::JitOptions.spectreIndexMasking = false;
12468 jit::JitOptions.spectreObjectMitigations = false;
12469 jit::JitOptions.spectreStringMitigations = false;
12470 jit::JitOptions.spectreValueMasking = false;
12471 jit::JitOptions.spectreJitToCxxCalls = false;
12472 } else {
12473 return OptionFailure("spectre-mitigations", str);
12477 if (const char* str = op.getStringOption("write-protect-code")) {
12478 if (strcmp(str, "on") == 0) {
12479 jit::JitOptions.maybeSetWriteProtectCode(true);
12480 } else if (strcmp(str, "off") == 0) {
12481 jit::JitOptions.maybeSetWriteProtectCode(false);
12482 } else {
12483 return OptionFailure("write-protect-code", str);
12487 if (const char* str = op.getStringOption("monomorphic-inlining")) {
12488 if (strcmp(str, "default") == 0) {
12489 jit::JitOptions.monomorphicInlining =
12490 jit::UseMonomorphicInlining::Default;
12491 } else if (strcmp(str, "always") == 0) {
12492 jit::JitOptions.monomorphicInlining = jit::UseMonomorphicInlining::Always;
12493 } else if (strcmp(str, "never") == 0) {
12494 jit::JitOptions.monomorphicInlining = jit::UseMonomorphicInlining::Never;
12495 } else {
12496 return OptionFailure("monomorphic-inlining", str);
12500 if (const char* str = op.getStringOption("ion-scalar-replacement")) {
12501 if (strcmp(str, "on") == 0) {
12502 jit::JitOptions.disableScalarReplacement = false;
12503 } else if (strcmp(str, "off") == 0) {
12504 jit::JitOptions.disableScalarReplacement = true;
12505 } else {
12506 return OptionFailure("ion-scalar-replacement", str);
12510 if (op.getStringOption("ion-shared-stubs")) {
12511 // Dead option, preserved for now for potential fuzzer interaction.
12514 if (const char* str = op.getStringOption("ion-gvn")) {
12515 if (strcmp(str, "off") == 0) {
12516 jit::JitOptions.disableGvn = true;
12517 } else if (strcmp(str, "on") != 0 && strcmp(str, "optimistic") != 0 &&
12518 strcmp(str, "pessimistic") != 0) {
12519 // We accept "pessimistic" and "optimistic" as synonyms for "on"
12520 // for backwards compatibility.
12521 return OptionFailure("ion-gvn", str);
12525 if (const char* str = op.getStringOption("ion-licm")) {
12526 if (strcmp(str, "on") == 0) {
12527 jit::JitOptions.disableLicm = false;
12528 } else if (strcmp(str, "off") == 0) {
12529 jit::JitOptions.disableLicm = true;
12530 } else {
12531 return OptionFailure("ion-licm", str);
12535 if (const char* str = op.getStringOption("ion-edgecase-analysis")) {
12536 if (strcmp(str, "on") == 0) {
12537 jit::JitOptions.disableEdgeCaseAnalysis = false;
12538 } else if (strcmp(str, "off") == 0) {
12539 jit::JitOptions.disableEdgeCaseAnalysis = true;
12540 } else {
12541 return OptionFailure("ion-edgecase-analysis", str);
12545 if (const char* str = op.getStringOption("ion-pruning")) {
12546 if (strcmp(str, "on") == 0) {
12547 jit::JitOptions.disablePruning = false;
12548 } else if (strcmp(str, "off") == 0) {
12549 jit::JitOptions.disablePruning = true;
12550 } else {
12551 return OptionFailure("ion-pruning", str);
12555 if (const char* str = op.getStringOption("ion-range-analysis")) {
12556 if (strcmp(str, "on") == 0) {
12557 jit::JitOptions.disableRangeAnalysis = false;
12558 } else if (strcmp(str, "off") == 0) {
12559 jit::JitOptions.disableRangeAnalysis = true;
12560 } else {
12561 return OptionFailure("ion-range-analysis", str);
12565 if (const char* str = op.getStringOption("ion-sink")) {
12566 if (strcmp(str, "on") == 0) {
12567 jit::JitOptions.disableSink = false;
12568 } else if (strcmp(str, "off") == 0) {
12569 jit::JitOptions.disableSink = true;
12570 } else {
12571 return OptionFailure("ion-sink", str);
12575 if (const char* str = op.getStringOption("ion-optimize-shapeguards")) {
12576 if (strcmp(str, "on") == 0) {
12577 jit::JitOptions.disableRedundantShapeGuards = false;
12578 } else if (strcmp(str, "off") == 0) {
12579 jit::JitOptions.disableRedundantShapeGuards = true;
12580 } else {
12581 return OptionFailure("ion-optimize-shapeguards", str);
12585 if (const char* str = op.getStringOption("ion-optimize-gcbarriers")) {
12586 if (strcmp(str, "on") == 0) {
12587 jit::JitOptions.disableRedundantGCBarriers = false;
12588 } else if (strcmp(str, "off") == 0) {
12589 jit::JitOptions.disableRedundantGCBarriers = true;
12590 } else {
12591 return OptionFailure("ion-optimize-gcbarriers", str);
12595 if (const char* str = op.getStringOption("ion-instruction-reordering")) {
12596 if (strcmp(str, "on") == 0) {
12597 jit::JitOptions.disableInstructionReordering = false;
12598 } else if (strcmp(str, "off") == 0) {
12599 jit::JitOptions.disableInstructionReordering = true;
12600 } else {
12601 return OptionFailure("ion-instruction-reordering", str);
12605 if (op.getBoolOption("ion-check-range-analysis")) {
12606 jit::JitOptions.checkRangeAnalysis = true;
12609 if (op.getBoolOption("ion-extra-checks")) {
12610 jit::JitOptions.runExtraChecks = true;
12613 if (const char* str = op.getStringOption("ion-inlining")) {
12614 if (strcmp(str, "on") == 0) {
12615 jit::JitOptions.disableInlining = false;
12616 } else if (strcmp(str, "off") == 0) {
12617 jit::JitOptions.disableInlining = true;
12618 } else {
12619 return OptionFailure("ion-inlining", str);
12623 if (const char* str = op.getStringOption("ion-osr")) {
12624 if (strcmp(str, "on") == 0) {
12625 jit::JitOptions.osr = true;
12626 } else if (strcmp(str, "off") == 0) {
12627 jit::JitOptions.osr = false;
12628 } else {
12629 return OptionFailure("ion-osr", str);
12633 if (const char* str = op.getStringOption("ion-limit-script-size")) {
12634 if (strcmp(str, "on") == 0) {
12635 jit::JitOptions.limitScriptSize = true;
12636 } else if (strcmp(str, "off") == 0) {
12637 jit::JitOptions.limitScriptSize = false;
12638 } else {
12639 return OptionFailure("ion-limit-script-size", str);
12643 int32_t warmUpThreshold = op.getIntOption("ion-warmup-threshold");
12644 if (warmUpThreshold >= 0) {
12645 jit::JitOptions.setNormalIonWarmUpThreshold(warmUpThreshold);
12648 warmUpThreshold = op.getIntOption("baseline-warmup-threshold");
12649 if (warmUpThreshold >= 0) {
12650 jit::JitOptions.baselineJitWarmUpThreshold = warmUpThreshold;
12653 warmUpThreshold = op.getIntOption("trial-inlining-warmup-threshold");
12654 if (warmUpThreshold >= 0) {
12655 jit::JitOptions.trialInliningWarmUpThreshold = warmUpThreshold;
12658 warmUpThreshold = op.getIntOption("regexp-warmup-threshold");
12659 if (warmUpThreshold >= 0) {
12660 jit::JitOptions.regexpWarmUpThreshold = warmUpThreshold;
12663 if (op.getBoolOption("baseline-eager")) {
12664 jit::JitOptions.setEagerBaselineCompilation();
12667 #ifdef ENABLE_PORTABLE_BASELINE_INTERP
12668 if (op.getBoolOption("portable-baseline-eager")) {
12669 jit::JitOptions.setEagerPortableBaselineInterpreter();
12671 if (op.getBoolOption("portable-baseline")) {
12672 jit::JitOptions.portableBaselineInterpreter = true;
12674 if (op.getBoolOption("no-portable-baseline")) {
12675 jit::JitOptions.portableBaselineInterpreter = false;
12677 #endif
12679 if (op.getBoolOption("blinterp")) {
12680 jit::JitOptions.baselineInterpreter = true;
12683 if (op.getBoolOption("no-blinterp")) {
12684 jit::JitOptions.baselineInterpreter = false;
12687 if (op.getBoolOption("disable-jithints")) {
12688 jit::JitOptions.disableJitHints = true;
12691 if (op.getBoolOption("emit-interpreter-entry")) {
12692 jit::JitOptions.emitInterpreterEntryTrampoline = true;
12695 if (op.getBoolOption("no-emit-interpreter-entry")) {
12696 jit::JitOptions.emitInterpreterEntryTrampoline = false;
12699 warmUpThreshold = op.getIntOption("blinterp-warmup-threshold");
12700 if (warmUpThreshold >= 0) {
12701 jit::JitOptions.baselineInterpreterWarmUpThreshold = warmUpThreshold;
12704 if (op.getBoolOption("blinterp-eager")) {
12705 jit::JitOptions.baselineInterpreterWarmUpThreshold = 0;
12708 if (op.getBoolOption("no-baseline")) {
12709 jit::JitOptions.baselineJit = false;
12712 if (op.getBoolOption("no-ion")) {
12713 jit::JitOptions.ion = false;
12716 if (op.getBoolOption("no-native-regexp")) {
12717 jit::JitOptions.nativeRegExp = false;
12720 if (op.getBoolOption("trace-regexp-parser")) {
12721 jit::JitOptions.trace_regexp_parser = true;
12723 if (op.getBoolOption("trace-regexp-assembler")) {
12724 jit::JitOptions.trace_regexp_assembler = true;
12726 if (op.getBoolOption("trace-regexp-interpreter")) {
12727 jit::JitOptions.trace_regexp_bytecodes = true;
12729 if (op.getBoolOption("trace-regexp-peephole")) {
12730 jit::JitOptions.trace_regexp_peephole_optimization = true;
12733 if (op.getBoolOption("less-debug-code")) {
12734 jit::JitOptions.lessDebugCode = true;
12737 int32_t inliningEntryThreshold = op.getIntOption("inlining-entry-threshold");
12738 if (inliningEntryThreshold > 0) {
12739 jit::JitOptions.inliningEntryThreshold = inliningEntryThreshold;
12742 int32_t smallFunctionLength = op.getIntOption("small-function-length");
12743 if (smallFunctionLength > 0) {
12744 jit::JitOptions.smallFunctionMaxBytecodeLength = smallFunctionLength;
12747 if (const char* str = op.getStringOption("ion-regalloc")) {
12748 jit::JitOptions.forcedRegisterAllocator = jit::LookupRegisterAllocator(str);
12749 if (!jit::JitOptions.forcedRegisterAllocator.isSome()) {
12750 return OptionFailure("ion-regalloc", str);
12754 if (op.getBoolOption("ion-eager")) {
12755 jit::JitOptions.setEagerIonCompilation();
12758 offthreadCompilation = true;
12759 if (const char* str = op.getStringOption("ion-offthread-compile")) {
12760 if (strcmp(str, "off") == 0) {
12761 offthreadCompilation = false;
12762 } else if (strcmp(str, "on") != 0) {
12763 return OptionFailure("ion-offthread-compile", str);
12766 cx->runtime()->setOffthreadIonCompilationEnabled(offthreadCompilation);
12768 if (op.getStringOption("ion-parallel-compile")) {
12769 fprintf(stderr,
12770 "--ion-parallel-compile is deprecated. Please use "
12771 "--ion-offthread-compile instead.\n");
12772 return false;
12775 if (op.getBoolOption("disable-bailout-loop-check")) {
12776 jit::JitOptions.disableBailoutLoopCheck = true;
12779 if (op.getBoolOption("only-inline-selfhosted")) {
12780 jit::JitOptions.onlyInlineSelfHosted = true;
12783 if (op.getBoolOption("enable-ic-frame-pointers")) {
12784 jit::JitOptions.enableICFramePointers = true;
12787 if (const char* str = op.getStringOption("ion-iterator-indices")) {
12788 if (strcmp(str, "on") == 0) {
12789 jit::JitOptions.disableIteratorIndices = false;
12790 } else if (strcmp(str, "off") == 0) {
12791 jit::JitOptions.disableIteratorIndices = true;
12792 } else {
12793 return OptionFailure("ion-iterator-indices", str);
12797 if (const char* str = op.getStringOption("ion-load-keys")) {
12798 if (strcmp(str, "on") == 0) {
12799 jit::JitOptions.disableMarkLoadsUsedAsPropertyKeys = false;
12800 } else if (strcmp(str, "off") == 0) {
12801 jit::JitOptions.disableMarkLoadsUsedAsPropertyKeys = true;
12802 } else {
12803 return OptionFailure("ion-load-keys", str);
12807 #if defined(JS_SIMULATOR_ARM)
12808 if (op.getBoolOption("arm-sim-icache-checks")) {
12809 jit::SimulatorProcess::ICacheCheckingDisableCount = 0;
12812 int32_t stopAt = op.getIntOption("arm-sim-stop-at");
12813 if (stopAt >= 0) {
12814 jit::Simulator::StopSimAt = stopAt;
12816 #elif defined(JS_SIMULATOR_MIPS32) || defined(JS_SIMULATOR_MIPS64)
12817 if (op.getBoolOption("mips-sim-icache-checks")) {
12818 jit::SimulatorProcess::ICacheCheckingDisableCount = 0;
12821 int32_t stopAt = op.getIntOption("mips-sim-stop-at");
12822 if (stopAt >= 0) {
12823 jit::Simulator::StopSimAt = stopAt;
12825 #elif defined(JS_SIMULATOR_LOONG64)
12826 if (op.getBoolOption("loong64-sim-icache-checks")) {
12827 jit::SimulatorProcess::ICacheCheckingDisableCount = 0;
12830 int32_t stopAt = op.getIntOption("loong64-sim-stop-at");
12831 if (stopAt >= 0) {
12832 jit::Simulator::StopSimAt = stopAt;
12834 #endif
12836 #ifdef DEBUG
12837 # ifdef JS_CODEGEN_RISCV64
12838 if (op.getBoolOption("riscv-debug")) {
12839 jit::Assembler::FLAG_riscv_debug = true;
12841 # endif
12842 # ifdef JS_SIMULATOR_RISCV64
12843 if (op.getBoolOption("trace-sim")) {
12844 jit::Simulator::FLAG_trace_sim = true;
12846 if (op.getBoolOption("debug-sim")) {
12847 jit::Simulator::FLAG_debug_sim = true;
12849 if (op.getBoolOption("riscv-trap-to-simulator-debugger")) {
12850 jit::Simulator::FLAG_riscv_trap_to_simulator_debugger = true;
12852 int32_t stopAt = op.getIntOption("riscv-sim-stop-at");
12853 if (stopAt >= 0) {
12854 jit::Simulator::StopSimAt = stopAt;
12856 # endif
12857 #endif
12859 return true;
12862 bool SetContextGCOptions(JSContext* cx, const OptionParser& op) {
12863 JS_SetGCParameter(cx, JSGC_MAX_BYTES, 0xffffffff);
12865 size_t nurseryBytes = op.getIntOption("nursery-size") * 1024L * 1024L;
12866 if (nurseryBytes == 0) {
12867 fprintf(stderr, "Error: --nursery-size parameter must be non-zero.\n");
12868 fprintf(stderr,
12869 "The nursery can be disabled by passing the --no-ggc option.\n");
12870 return false;
12872 JS_SetGCParameter(cx, JSGC_MAX_NURSERY_BYTES, nurseryBytes);
12874 size_t availMemMB = op.getIntOption("available-memory");
12875 if (availMemMB > 0) {
12876 JS_SetGCParametersBasedOnAvailableMemory(cx, availMemMB);
12879 if (const char* opt = op.getStringOption("nursery-strings")) {
12880 if (strcmp(opt, "on") == 0) {
12881 cx->runtime()->gc.nursery().enableStrings();
12882 } else if (strcmp(opt, "off") == 0) {
12883 cx->runtime()->gc.nursery().disableStrings();
12884 } else {
12885 MOZ_CRASH("invalid option value for --nursery-strings, must be on/off");
12889 if (const char* opt = op.getStringOption("nursery-bigints")) {
12890 if (strcmp(opt, "on") == 0) {
12891 cx->runtime()->gc.nursery().enableBigInts();
12892 } else if (strcmp(opt, "off") == 0) {
12893 cx->runtime()->gc.nursery().disableBigInts();
12894 } else {
12895 MOZ_CRASH("invalid option value for --nursery-bigints, must be on/off");
12899 bool incrementalGC = !op.getBoolOption("no-incremental-gc");
12900 JS_SetGCParameter(cx, JSGC_INCREMENTAL_GC_ENABLED, incrementalGC);
12902 #ifndef ANDROID
12903 bool parallelMarking = true;
12904 #else
12905 bool parallelMarking = false;
12906 #endif
12907 if (op.getBoolOption("enable-parallel-marking")) {
12908 parallelMarking = true;
12910 if (op.getBoolOption("no-parallel-marking")) {
12911 parallelMarking = false;
12913 JS_SetGCParameter(cx, JSGC_PARALLEL_MARKING_ENABLED, parallelMarking);
12915 int32_t markingThreads = op.getIntOption("marking-threads");
12916 if (markingThreads > 0) {
12917 JS_SetGCParameter(cx, JSGC_MARKING_THREAD_COUNT, markingThreads);
12920 JS_SetGCParameter(cx, JSGC_SLICE_TIME_BUDGET_MS, 5);
12922 JS_SetGCParameter(cx, JSGC_PER_ZONE_GC_ENABLED, true);
12924 for (MultiStringRange args = op.getMultiStringOption("gc-param");
12925 !args.empty(); args.popFront()) {
12926 if (!SetGCParameterFromArg(cx, args.front())) {
12927 return false;
12931 #ifdef DEBUG
12932 dumpEntrainedVariables = op.getBoolOption("dump-entrained-variables");
12933 #endif
12935 #ifdef JS_GC_ZEAL
12936 const char* zealStr = op.getStringOption("gc-zeal");
12937 if (zealStr) {
12938 if (!cx->runtime()->gc.parseAndSetZeal(zealStr)) {
12939 return false;
12941 uint32_t nextScheduled;
12942 cx->runtime()->gc.getZealBits(&gZealBits, &gZealFrequency, &nextScheduled);
12944 #endif
12946 return true;
12949 bool InitModuleLoader(JSContext* cx, const OptionParser& op) {
12950 RootedString moduleLoadPath(cx);
12951 if (const char* option = op.getStringOption("module-load-path")) {
12952 UniqueChars pathUtf8 = JS::EncodeNarrowToUtf8(cx, option);
12953 if (!pathUtf8) {
12954 return false;
12957 Rooted<JSString*> jspath(cx, NewStringCopyUTF8(cx, pathUtf8.get()));
12958 if (!jspath) {
12959 return false;
12962 moduleLoadPath = js::shell::ResolvePath(cx, jspath, RootRelative);
12964 processWideModuleLoadPath = JS_EncodeStringToUTF8(cx, moduleLoadPath);
12965 if (!processWideModuleLoadPath) {
12966 return false;
12968 } else {
12969 processWideModuleLoadPath = js::shell::GetCWD(cx);
12970 if (!processWideModuleLoadPath) {
12971 return false;
12974 moduleLoadPath = NewStringCopyUTF8(cx, processWideModuleLoadPath.get());
12975 if (!moduleLoadPath) {
12976 return false;
12980 ShellContext* sc = GetShellContext(cx);
12981 sc->moduleLoader = js::MakeUnique<ModuleLoader>();
12982 if (!sc->moduleLoader || !sc->moduleLoader->init(cx, moduleLoadPath)) {
12983 return false;
12986 return true;