Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2
* vim: set ts=8 sts=2 et sw=2 tw=80:
3
* This Source Code Form is subject to the terms of the Mozilla Public
4
* License, v. 2.0. If a copy of the MPL was not distributed with this
5
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
/* JS shell. */
8
9
#include "mozilla/ArrayUtils.h"
10
#include "mozilla/Atomics.h"
11
#include "mozilla/Attributes.h"
12
#include "mozilla/DebugOnly.h"
13
#include "mozilla/GuardObjects.h"
14
#include "mozilla/IntegerPrintfMacros.h"
15
#include "mozilla/mozalloc.h"
16
#include "mozilla/PodOperations.h"
17
#include "mozilla/ScopeExit.h"
18
#include "mozilla/Sprintf.h"
19
#include "mozilla/TimeStamp.h"
20
#include "mozilla/UniquePtrExtensions.h" // UniqueFreePtr
21
#include "mozilla/Unused.h"
22
#include "mozilla/Utf8.h"
23
#include "mozilla/Variant.h"
24
25
#include <algorithm>
26
#include <chrono>
27
#ifdef XP_WIN
28
# include <direct.h>
29
# include <process.h>
30
#endif
31
#include <errno.h>
32
#include <fcntl.h>
33
#if defined(XP_WIN)
34
# include <io.h> /* for isatty() */
35
#endif
36
#include <locale.h>
37
#if defined(MALLOC_H)
38
# include MALLOC_H /* for malloc_usable_size, malloc_size, _msize */
39
#endif
40
#include <ctime>
41
#include <math.h>
42
#include <signal.h>
43
#include <stdio.h>
44
#include <stdlib.h>
45
#include <string.h>
46
#include <sys/stat.h>
47
#include <sys/types.h>
48
#include <thread>
49
#include <utility>
50
#ifdef XP_UNIX
51
# include <sys/mman.h>
52
# include <sys/stat.h>
53
# include <sys/wait.h>
54
# include <unistd.h>
55
#endif
56
#ifdef XP_LINUX
57
# include <sys/prctl.h>
58
#endif
59
60
#include "jsapi.h"
61
#include "jsfriendapi.h"
62
#include "jstypes.h"
63
#ifndef JS_WITHOUT_NSPR
64
# include "prerror.h"
65
# include "prlink.h"
66
#endif
67
#include "shellmoduleloader.out.h"
68
69
#include "builtin/Array.h"
70
#include "builtin/MapObject.h"
71
#include "builtin/ModuleObject.h"
72
#include "builtin/RegExp.h"
73
#include "builtin/TestingFunctions.h"
74
#include "debugger/DebugAPI.h"
75
#if defined(JS_BUILD_BINAST)
76
# include "frontend/BinASTParser.h"
77
#endif // defined(JS_BUILD_BINAST)
78
#include "frontend/CompilationInfo.h"
79
#ifdef JS_ENABLE_SMOOSH
80
# include "frontend/Frontend2.h"
81
#endif
82
#include "frontend/ModuleSharedContext.h"
83
#include "frontend/Parser.h"
84
#include "frontend/SourceNotes.h" // SrcNote, SrcNoteType, SrcNoteIterator
85
#include "gc/PublicIterators.h"
86
#include "jit/arm/Simulator-arm.h"
87
#include "jit/InlinableNatives.h"
88
#include "jit/Ion.h"
89
#include "jit/JitcodeMap.h"
90
#include "jit/JitRealm.h"
91
#include "jit/shared/CodeGenerator-shared.h"
92
#include "js/Array.h" // JS::NewArrayObject
93
#include "js/ArrayBuffer.h" // JS::{CreateMappedArrayBufferContents,NewMappedArrayBufferWithContents,IsArrayBufferObject,GetArrayBufferLengthAndData}
94
#include "js/BuildId.h" // JS::BuildIdCharVector, JS::SetProcessBuildIdOp
95
#include "js/CharacterEncoding.h"
96
#include "js/CompilationAndEvaluation.h"
97
#include "js/CompileOptions.h"
98
#include "js/ContextOptions.h" // JS::ContextOptions{,Ref}
99
#include "js/Debug.h"
100
#include "js/Equality.h" // JS::SameValue
101
#include "js/experimental/SourceHook.h" // js::{Set,Forget,}SourceHook
102
#include "js/GCVector.h"
103
#include "js/Initialization.h"
104
#include "js/JSON.h"
105
#include "js/MemoryFunctions.h"
106
#include "js/Modules.h" // JS::GetModulePrivate, JS::SetModule{DynamicImport,Metadata,Resolve}Hook, JS::SetModulePrivate
107
#include "js/Printf.h"
108
#include "js/PropertySpec.h"
109
#include "js/Realm.h"
110
#include "js/RegExp.h" // JS::ObjectIsRegExp
111
#include "js/SourceText.h"
112
#include "js/StableStringChars.h"
113
#include "js/StructuredClone.h"
114
#include "js/SweepingAPI.h"
115
#include "js/Warnings.h" // JS::SetWarningReporter
116
#include "js/Wrapper.h"
117
#include "shell/jsoptparse.h"
118
#include "shell/jsshell.h"
119
#include "shell/OSObject.h"
120
#include "threading/ConditionVariable.h"
121
#include "threading/ExclusiveData.h"
122
#include "threading/LockGuard.h"
123
#include "threading/Thread.h"
124
#include "util/CompleteFile.h" // js::FileContents, js::ReadCompleteFile
125
#include "util/StringBuffer.h"
126
#include "util/Text.h"
127
#include "util/Windows.h"
128
#include "vm/ArgumentsObject.h"
129
#include "vm/Compression.h"
130
#include "vm/HelperThreads.h"
131
#include "vm/JSAtom.h"
132
#include "vm/JSContext.h"
133
#include "vm/JSFunction.h"
134
#include "vm/JSObject.h"
135
#include "vm/JSScript.h"
136
#include "vm/ModuleBuilder.h" // js::ModuleBuilder
137
#include "vm/Monitor.h"
138
#include "vm/MutexIDs.h"
139
#include "vm/Printer.h"
140
#include "vm/PromiseObject.h" // js::PromiseObject
141
#include "vm/Shape.h"
142
#include "vm/SharedArrayObject.h"
143
#include "vm/Time.h"
144
#include "vm/ToSource.h" // js::ValueToSource
145
#include "vm/TypedArrayObject.h"
146
#include "vm/WrapperObject.h"
147
#include "wasm/WasmJS.h"
148
149
#include "vm/Compartment-inl.h"
150
#include "vm/ErrorObject-inl.h"
151
#include "vm/Interpreter-inl.h"
152
#include "vm/JSObject-inl.h"
153
#include "vm/Realm-inl.h"
154
#include "vm/Stack-inl.h"
155
156
using namespace js;
157
using namespace js::cli;
158
using namespace js::shell;
159
160
using JS::AutoStableStringChars;
161
using JS::CompileOptions;
162
163
using js::shell::RCFile;
164
165
using mozilla::ArrayEqual;
166
using mozilla::ArrayLength;
167
using mozilla::AsVariant;
168
using mozilla::Atomic;
169
using mozilla::MakeScopeExit;
170
using mozilla::Maybe;
171
using mozilla::Nothing;
172
using mozilla::NumberEqualsInt32;
173
using mozilla::TimeDuration;
174
using mozilla::TimeStamp;
175
using mozilla::Utf8Unit;
176
using mozilla::Variant;
177
178
enum JSShellExitCode {
179
EXITCODE_RUNTIME_ERROR = 3,
180
EXITCODE_FILE_NOT_FOUND = 4,
181
EXITCODE_OUT_OF_MEMORY = 5,
182
EXITCODE_TIMEOUT = 6
183
};
184
185
// Define use of application-specific slots on the shell's global object.
186
enum GlobalAppSlot {
187
GlobalAppSlotModuleLoadHook, // Shell-specific; load a module graph
188
GlobalAppSlotModuleResolveHook, // HostResolveImportedModule
189
GlobalAppSlotModuleMetadataHook, // HostPopulateImportMeta
190
GlobalAppSlotModuleDynamicImportHook, // HostImportModuleDynamically
191
GlobalAppSlotCount
192
};
193
static_assert(GlobalAppSlotCount <= JSCLASS_GLOBAL_APPLICATION_SLOTS,
194
"Too many applications slots defined for shell global");
195
196
/*
197
* Note: This limit should match the stack limit set by the browser in
198
* js/xpconnect/src/XPCJSContext.cpp
199
*/
200
#if defined(MOZ_ASAN) || (defined(DEBUG) && !defined(XP_WIN))
201
static const size_t gMaxStackSize = 2 * 128 * sizeof(size_t) * 1024;
202
#else
203
static const size_t gMaxStackSize = 128 * sizeof(size_t) * 1024;
204
#endif
205
206
/*
207
* Limit the timeout to 30 minutes to prevent an overflow on platfoms
208
* that represent the time internally in microseconds using 32-bit int.
209
*/
210
static const double MAX_TIMEOUT_SECONDS = 1800.0;
211
212
// Not necessarily in sync with the browser
213
#ifdef ENABLE_SHARED_MEMORY
214
# define SHARED_MEMORY_DEFAULT 1
215
#else
216
# define SHARED_MEMORY_DEFAULT 0
217
#endif
218
219
// Fuzzing support for JS runtime fuzzing
220
#ifdef FUZZING_INTERFACES
221
# include "shell/jsrtfuzzing/jsrtfuzzing.h"
222
static bool fuzzDoDebug = !!getenv("MOZ_FUZZ_DEBUG");
223
static bool fuzzHaveModule = !!getenv("FUZZER");
224
#endif // FUZZING_INTERFACES
225
226
// Code to support GCOV code coverage measurements on standalone shell
227
#ifdef MOZ_CODE_COVERAGE
228
# if defined(__GNUC__) && !defined(__clang__)
229
extern "C" void __gcov_dump();
230
extern "C" void __gcov_reset();
231
232
void counters_dump(int) { __gcov_dump(); }
233
234
void counters_reset(int) { __gcov_reset(); }
235
# else
236
void counters_dump(int) { /* Do nothing */
237
}
238
239
void counters_reset(int) { /* Do nothing */
240
}
241
# endif
242
243
static void InstallCoverageSignalHandlers() {
244
# ifndef XP_WIN
245
fprintf(stderr, "[CodeCoverage] Setting handlers for process %d.\n",
246
getpid());
247
248
struct sigaction dump_sa;
249
dump_sa.sa_handler = counters_dump;
250
dump_sa.sa_flags = SA_RESTART;
251
sigemptyset(&dump_sa.sa_mask);
252
mozilla::DebugOnly<int> r1 = sigaction(SIGUSR1, &dump_sa, nullptr);
253
MOZ_ASSERT(r1 == 0, "Failed to install GCOV SIGUSR1 handler");
254
255
struct sigaction reset_sa;
256
reset_sa.sa_handler = counters_reset;
257
reset_sa.sa_flags = SA_RESTART;
258
sigemptyset(&reset_sa.sa_mask);
259
mozilla::DebugOnly<int> r2 = sigaction(SIGUSR2, &reset_sa, nullptr);
260
MOZ_ASSERT(r2 == 0, "Failed to install GCOV SIGUSR2 handler");
261
# endif
262
}
263
#endif
264
265
// An off-thread parse or decode job.
266
class js::shell::OffThreadJob {
267
enum State {
268
RUNNING, // Working; no token.
269
DONE, // Finished; have token.
270
CANCELLED // Cancelled due to error.
271
};
272
273
public:
274
using Source = mozilla::Variant<JS::UniqueTwoByteChars, JS::TranscodeBuffer>;
275
276
OffThreadJob(ShellContext* sc, ScriptKind kind, Source&& source);
277
~OffThreadJob();
278
279
void cancel();
280
void markDone(JS::OffThreadToken* newToken);
281
JS::OffThreadToken* waitUntilDone(JSContext* cx);
282
283
char16_t* sourceChars() { return source.as<UniqueTwoByteChars>().get(); }
284
JS::TranscodeBuffer& xdrBuffer() { return source.as<JS::TranscodeBuffer>(); }
285
286
public:
287
const int32_t id;
288
const ScriptKind kind;
289
290
private:
291
js::Monitor& monitor;
292
State state;
293
JS::OffThreadToken* token;
294
Source source;
295
};
296
297
static OffThreadJob* NewOffThreadJob(JSContext* cx, ScriptKind kind,
298
OffThreadJob::Source&& source) {
299
ShellContext* sc = GetShellContext(cx);
300
UniquePtr<OffThreadJob> job(
301
cx->new_<OffThreadJob>(sc, kind, std::move(source)));
302
if (!job) {
303
return nullptr;
304
}
305
306
if (!sc->offThreadJobs.append(job.get())) {
307
job->cancel();
308
JS_ReportErrorASCII(cx, "OOM adding off-thread job");
309
return nullptr;
310
}
311
312
return job.release();
313
}
314
315
static OffThreadJob* GetSingleOffThreadJob(JSContext* cx, ScriptKind kind) {
316
ShellContext* sc = GetShellContext(cx);
317
const auto& jobs = sc->offThreadJobs;
318
if (jobs.empty()) {
319
JS_ReportErrorASCII(cx, "No off-thread jobs are pending");
320
return nullptr;
321
}
322
323
if (jobs.length() > 1) {
324
JS_ReportErrorASCII(
325
cx, "Multiple off-thread jobs are pending: must specify job ID");
326
return nullptr;
327
}
328
329
OffThreadJob* job = jobs[0];
330
if (job->kind != kind) {
331
JS_ReportErrorASCII(cx, "Off-thread job is the wrong kind");
332
return nullptr;
333
}
334
335
return job;
336
}
337
338
static OffThreadJob* LookupOffThreadJobByID(JSContext* cx, ScriptKind kind,
339
int32_t id) {
340
if (id <= 0) {
341
JS_ReportErrorASCII(cx, "Bad off-thread job ID");
342
return nullptr;
343
}
344
345
ShellContext* sc = GetShellContext(cx);
346
const auto& jobs = sc->offThreadJobs;
347
if (jobs.empty()) {
348
JS_ReportErrorASCII(cx, "No off-thread jobs are pending");
349
return nullptr;
350
}
351
352
OffThreadJob* job = nullptr;
353
for (auto someJob : jobs) {
354
if (someJob->id == id) {
355
job = someJob;
356
break;
357
}
358
}
359
360
if (!job) {
361
JS_ReportErrorASCII(cx, "Off-thread job not found");
362
return nullptr;
363
}
364
365
if (job->kind != kind) {
366
JS_ReportErrorASCII(cx, "Off-thread job is the wrong kind");
367
return nullptr;
368
}
369
370
return job;
371
}
372
373
static OffThreadJob* LookupOffThreadJobForArgs(JSContext* cx, ScriptKind kind,
374
const CallArgs& args,
375
size_t arg) {
376
// If the optional ID argument isn't present, get the single pending job.
377
if (args.length() <= arg) {
378
return GetSingleOffThreadJob(cx, kind);
379
}
380
381
// Lookup the job using the specified ID.
382
int32_t id = 0;
383
RootedValue value(cx, args[arg]);
384
if (!ToInt32(cx, value, &id)) {
385
return nullptr;
386
}
387
388
return LookupOffThreadJobByID(cx, kind, id);
389
}
390
391
static void DeleteOffThreadJob(JSContext* cx, OffThreadJob* job) {
392
ShellContext* sc = GetShellContext(cx);
393
for (size_t i = 0; i < sc->offThreadJobs.length(); i++) {
394
if (sc->offThreadJobs[i] == job) {
395
sc->offThreadJobs.erase(&sc->offThreadJobs[i]);
396
js_delete(job);
397
return;
398
}
399
}
400
401
MOZ_CRASH("Off-thread job not found");
402
}
403
404
static void CancelOffThreadJobsForContext(JSContext* cx) {
405
// Parse jobs may be blocked waiting on GC.
406
gc::FinishGC(cx);
407
408
// Wait for jobs belonging to this context.
409
ShellContext* sc = GetShellContext(cx);
410
while (!sc->offThreadJobs.empty()) {
411
OffThreadJob* job = sc->offThreadJobs.popCopy();
412
job->waitUntilDone(cx);
413
js_delete(job);
414
}
415
}
416
417
static void CancelOffThreadJobsForRuntime(JSContext* cx) {
418
// Parse jobs may be blocked waiting on GC.
419
gc::FinishGC(cx);
420
421
// Cancel jobs belonging to this runtime.
422
CancelOffThreadParses(cx->runtime());
423
ShellContext* sc = GetShellContext(cx);
424
while (!sc->offThreadJobs.empty()) {
425
js_delete(sc->offThreadJobs.popCopy());
426
}
427
}
428
429
mozilla::Atomic<int32_t> gOffThreadJobSerial(1);
430
431
OffThreadJob::OffThreadJob(ShellContext* sc, ScriptKind kind, Source&& source)
432
: id(gOffThreadJobSerial++),
433
kind(kind),
434
monitor(sc->offThreadMonitor),
435
state(RUNNING),
436
token(nullptr),
437
source(std::move(source)) {
438
MOZ_RELEASE_ASSERT(id > 0, "Off-thread job IDs exhausted");
439
}
440
441
OffThreadJob::~OffThreadJob() { MOZ_ASSERT(state != RUNNING); }
442
443
void OffThreadJob::cancel() {
444
MOZ_ASSERT(state == RUNNING);
445
MOZ_ASSERT(!token);
446
447
state = CANCELLED;
448
}
449
450
void OffThreadJob::markDone(JS::OffThreadToken* newToken) {
451
AutoLockMonitor alm(monitor);
452
MOZ_ASSERT(state == RUNNING);
453
MOZ_ASSERT(!token);
454
MOZ_ASSERT(newToken);
455
456
token = newToken;
457
state = DONE;
458
alm.notifyAll();
459
}
460
461
JS::OffThreadToken* OffThreadJob::waitUntilDone(JSContext* cx) {
462
AutoLockMonitor alm(monitor);
463
MOZ_ASSERT(state != CANCELLED);
464
465
while (state != DONE) {
466
alm.wait();
467
}
468
469
MOZ_ASSERT(token);
470
return token;
471
}
472
473
struct ShellCompartmentPrivate {
474
GCPtrObject grayRoot;
475
};
476
477
struct MOZ_STACK_CLASS EnvironmentPreparer
478
: public js::ScriptEnvironmentPreparer {
479
explicit EnvironmentPreparer(JSContext* cx) {
480
js::SetScriptEnvironmentPreparer(cx, this);
481
}
482
void invoke(JS::HandleObject global, Closure& closure) override;
483
};
484
485
bool shell::enableDeferredMode = true;
486
bool shell::enableCodeCoverage = false;
487
bool shell::enableDisassemblyDumps = false;
488
bool shell::offthreadCompilation = false;
489
bool shell::enableAsmJS = false;
490
bool shell::enableWasm = false;
491
bool shell::enableSharedMemory = SHARED_MEMORY_DEFAULT;
492
bool shell::enableWasmBaseline = false;
493
bool shell::enableWasmIon = false;
494
bool shell::enableWasmCranelift = false;
495
#ifdef ENABLE_WASM_GC
496
bool shell::enableWasmGc = false;
497
#endif
498
#ifdef ENABLE_WASM_MULTI_VALUE
499
bool shell::enableWasmMultiValue = true;
500
#endif
501
bool shell::enableWasmVerbose = false;
502
bool shell::enableTestWasmAwaitTier2 = false;
503
#ifdef ENABLE_WASM_BIGINT
504
bool shell::enableWasmBigInt = true;
505
#endif
506
bool shell::enableAsyncStacks = false;
507
bool shell::enableStreams = false;
508
bool shell::enableReadableByteStreams = false;
509
bool shell::enableBYOBStreamReaders = false;
510
bool shell::enableWritableStreams = false;
511
bool shell::enableReadableStreamPipeTo = false;
512
bool shell::enableWeakRefs = false;
513
bool shell::enableToSource = false;
514
bool shell::enablePropertyErrorMessageFix = false;
515
#ifdef JS_GC_ZEAL
516
uint32_t shell::gZealBits = 0;
517
uint32_t shell::gZealFrequency = 0;
518
#endif
519
bool shell::printTiming = false;
520
RCFile* shell::gErrFile = nullptr;
521
RCFile* shell::gOutFile = nullptr;
522
bool shell::reportWarnings = true;
523
bool shell::compileOnly = false;
524
bool shell::fuzzingSafe = false;
525
bool shell::disableOOMFunctions = false;
526
bool shell::defaultToSameCompartment = true;
527
528
#ifdef DEBUG
529
bool shell::dumpEntrainedVariables = false;
530
bool shell::OOM_printAllocationCount = false;
531
#endif
532
533
static bool SetTimeoutValue(JSContext* cx, double t);
534
535
static void KillWatchdog(JSContext* cx);
536
537
static bool ScheduleWatchdog(JSContext* cx, double t);
538
539
static void CancelExecution(JSContext* cx);
540
541
enum class ShellGlobalKind {
542
GlobalObject,
543
WindowProxy,
544
};
545
546
static JSObject* NewGlobalObject(JSContext* cx, JS::RealmOptions& options,
547
JSPrincipals* principals,
548
ShellGlobalKind kind);
549
550
/*
551
* A toy WindowProxy class for the shell. This is intended for testing code
552
* where global |this| is a WindowProxy. All requests are forwarded to the
553
* underlying global and no navigation is supported.
554
*/
555
const JSClass ShellWindowProxyClass =
556
PROXY_CLASS_DEF("ShellWindowProxy", JSCLASS_HAS_RESERVED_SLOTS(1));
557
558
JSObject* NewShellWindowProxy(JSContext* cx, JS::HandleObject global) {
559
MOZ_ASSERT(global->is<GlobalObject>());
560
561
js::WrapperOptions options;
562
options.setClass(&ShellWindowProxyClass);
563
options.setSingleton(true);
564
565
JSAutoRealm ar(cx, global);
566
JSObject* obj =
567
js::Wrapper::New(cx, global, &js::Wrapper::singleton, options);
568
MOZ_ASSERT_IF(obj, js::IsWindowProxy(obj));
569
return obj;
570
}
571
572
/*
573
* A toy principals type for the shell.
574
*
575
* In the shell, a principal is simply a 32-bit mask: P subsumes Q if the
576
* set bits in P are a superset of those in Q. Thus, the principal 0 is
577
* subsumed by everything, and the principal ~0 subsumes everything.
578
*
579
* As a special case, a null pointer as a principal is treated like 0xffff.
580
*
581
* The 'newGlobal' function takes an option indicating which principal the
582
* new global should have; 'evaluate' does for the new code.
583
*/
584
class ShellPrincipals final : public JSPrincipals {
585
uint32_t bits;
586
587
static uint32_t getBits(JSPrincipals* p) {
588
if (!p) {
589
return 0xffff;
590
}
591
return static_cast<ShellPrincipals*>(p)->bits;
592
}
593
594
public:
595
explicit ShellPrincipals(uint32_t bits, int32_t refcount = 0) : bits(bits) {
596
this->refcount = refcount;
597
}
598
599
bool write(JSContext* cx, JSStructuredCloneWriter* writer) override {
600
// The shell doesn't have a read principals hook, so it doesn't really
601
// matter what we write here, but we have to write something so the
602
// fuzzer is happy.
603
return JS_WriteUint32Pair(writer, bits, 0);
604
}
605
606
bool isSystemOrAddonPrincipal() override { return true; }
607
608
static void destroy(JSPrincipals* principals) {
609
MOZ_ASSERT(principals != &fullyTrusted);
610
MOZ_ASSERT(principals->refcount == 0);
611
js_delete(static_cast<const ShellPrincipals*>(principals));
612
}
613
614
static bool subsumes(JSPrincipals* first, JSPrincipals* second) {
615
uint32_t firstBits = getBits(first);
616
uint32_t secondBits = getBits(second);
617
return (firstBits | secondBits) == firstBits;
618
}
619
620
static JSSecurityCallbacks securityCallbacks;
621
622
// Fully-trusted principals singleton.
623
static ShellPrincipals fullyTrusted;
624
};
625
626
JSSecurityCallbacks ShellPrincipals::securityCallbacks = {
627
nullptr, // contentSecurityPolicyAllows
628
subsumes};
629
630
// The fully-trusted principal subsumes all other principals.
631
ShellPrincipals ShellPrincipals::fullyTrusted(-1, 1);
632
633
#ifdef EDITLINE
634
extern "C" {
635
extern MOZ_EXPORT char* readline(const char* prompt);
636
extern MOZ_EXPORT void add_history(char* line);
637
} // extern "C"
638
#endif
639
640
ShellContext::ShellContext(JSContext* cx)
641
: isWorker(false),
642
lastWarningEnabled(false),
643
trackUnhandledRejections(true),
644
timeoutInterval(-1.0),
645
startTime(PRMJ_Now()),
646
serviceInterrupt(false),
647
haveInterruptFunc(false),
648
interruptFunc(cx, NullValue()),
649
lastWarning(cx, NullValue()),
650
promiseRejectionTrackerCallback(cx, NullValue()),
651
unhandledRejectedPromises(cx),
652
watchdogLock(mutexid::ShellContextWatchdog),
653
exitCode(0),
654
quitting(false),
655
readLineBufPos(0),
656
errFilePtr(nullptr),
657
outFilePtr(nullptr),
658
offThreadMonitor(mutexid::ShellOffThreadState),
659
finalizationRegistriesToCleanUp(cx) {}
660
661
ShellContext::~ShellContext() { MOZ_ASSERT(offThreadJobs.empty()); }
662
663
ShellContext* js::shell::GetShellContext(JSContext* cx) {
664
ShellContext* sc = static_cast<ShellContext*>(JS_GetContextPrivate(cx));
665
MOZ_ASSERT(sc);
666
return sc;
667
}
668
669
static void TraceGrayRoots(JSTracer* trc, void* data) {
670
JSRuntime* rt = trc->runtime();
671
for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
672
for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) {
673
auto priv = static_cast<ShellCompartmentPrivate*>(
674
JS_GetCompartmentPrivate(comp.get()));
675
if (priv) {
676
TraceNullableEdge(trc, &priv->grayRoot, "test gray root");
677
}
678
}
679
}
680
}
681
682
static mozilla::UniqueFreePtr<char[]> GetLine(FILE* file, const char* prompt) {
683
#ifdef EDITLINE
684
/*
685
* Use readline only if file is stdin, because there's no way to specify
686
* another handle. Are other filehandles interactive?
687
*/
688
if (file == stdin) {
689
mozilla::UniqueFreePtr<char[]> linep(readline(prompt));
690
/*
691
* We set it to zero to avoid complaining about inappropriate ioctl
692
* for device in the case of EOF. Looks like errno == 251 if line is
693
* finished with EOF and errno == 25 (EINVAL on Mac) if there is
694
* nothing left to read.
695
*/
696
if (errno == 251 || errno == 25 || errno == EINVAL) {
697
errno = 0;
698
}
699
if (!linep) {
700
return nullptr;
701
}
702
if (linep[0] != '\0') {
703
add_history(linep.get());
704
}
705
return linep;
706
}
707
#endif
708
709
size_t len = 0;
710
if (*prompt != '\0' && gOutFile->isOpen()) {
711
fprintf(gOutFile->fp, "%s", prompt);
712
fflush(gOutFile->fp);
713
}
714
715
size_t size = 80;
716
mozilla::UniqueFreePtr<char[]> buffer(static_cast<char*>(malloc(size)));
717
if (!buffer) {
718
return nullptr;
719
}
720
721
char* current = buffer.get();
722
do {
723
while (true) {
724
if (fgets(current, size - len, file)) {
725
break;
726
}
727
if (errno != EINTR) {
728
return nullptr;
729
}
730
}
731
732
len += strlen(current);
733
char* t = buffer.get() + len - 1;
734
if (*t == '\n') {
735
/* Line was read. We remove '\n' and exit. */
736
*t = '\0';
737
break;
738
}
739
740
if (len + 1 == size) {
741
size = size * 2;
742
char* raw = buffer.release();
743
char* tmp = static_cast<char*>(realloc(raw, size));
744
if (!tmp) {
745
free(raw);
746
return nullptr;
747
}
748
buffer.reset(tmp);
749
}
750
current = buffer.get() + len;
751
} while (true);
752
return buffer;
753
}
754
755
static bool ShellInterruptCallback(JSContext* cx) {
756
ShellContext* sc = GetShellContext(cx);
757
if (!sc->serviceInterrupt) {
758
return true;
759
}
760
761
// Reset serviceInterrupt. CancelExecution or InterruptIf will set it to
762
// true to distinguish watchdog or user triggered interrupts.
763
// Do this first to prevent other interrupts that may occur while the
764
// user-supplied callback is executing from re-entering the handler.
765
sc->serviceInterrupt = false;
766
767
bool result;
768
if (sc->haveInterruptFunc) {
769
bool wasAlreadyThrowing = cx->isExceptionPending();
770
JS::AutoSaveExceptionState savedExc(cx);
771
JSAutoRealm ar(cx, &sc->interruptFunc.toObject());
772
RootedValue rval(cx);
773
774
// Report any exceptions thrown by the JS interrupt callback, but do
775
// *not* keep it on the cx. The interrupt handler is invoked at points
776
// that are not expected to throw catchable exceptions, like at
777
// JSOp::RetRval.
778
//
779
// If the interrupted JS code was already throwing, any exceptions
780
// thrown by the interrupt handler are silently swallowed.
781
{
782
Maybe<AutoReportException> are;
783
if (!wasAlreadyThrowing) {
784
are.emplace(cx);
785
}
786
result = JS_CallFunctionValue(cx, nullptr, sc->interruptFunc,
787
JS::HandleValueArray::empty(), &rval);
788
}
789
savedExc.restore();
790
791
if (rval.isBoolean()) {
792
result = rval.toBoolean();
793
} else {
794
result = false;
795
}
796
} else {
797
result = false;
798
}
799
800
if (!result && sc->exitCode == 0) {
801
static const char msg[] = "Script terminated by interrupt handler.\n";
802
fputs(msg, stderr);
803
804
sc->exitCode = EXITCODE_TIMEOUT;
805
}
806
807
return result;
808
}
809
810
/*
811
* Some UTF-8 files, notably those written using Notepad, have a Unicode
812
* Byte-Order-Mark (BOM) as their first character. This is useless (byte-order
813
* is meaningless for UTF-8) but causes a syntax error unless we skip it.
814
*/
815
static void SkipUTF8BOM(FILE* file) {
816
int ch1 = fgetc(file);
817
int ch2 = fgetc(file);
818
int ch3 = fgetc(file);
819
820
// Skip the BOM
821
if (ch1 == 0xEF && ch2 == 0xBB && ch3 == 0xBF) {
822
return;
823
}
824
825
// No BOM - revert
826
if (ch3 != EOF) {
827
ungetc(ch3, file);
828
}
829
if (ch2 != EOF) {
830
ungetc(ch2, file);
831
}
832
if (ch1 != EOF) {
833
ungetc(ch1, file);
834
}
835
}
836
837
void EnvironmentPreparer::invoke(HandleObject global, Closure& closure) {
838
MOZ_ASSERT(JS_IsGlobalObject(global));
839
840
JSContext* cx = TlsContext.get();
841
MOZ_ASSERT(!JS_IsExceptionPending(cx));
842
843
AutoRealm ar(cx, global);
844
AutoReportException are(cx);
845
if (!closure(cx)) {
846
return;
847
}
848
}
849
850
static bool RegisterScriptPathWithModuleLoader(JSContext* cx,
851
HandleScript script,
852
const char* filename) {
853
// Set the private value associated with a script to a object containing the
854
// script's filename so that the module loader can use it to resolve
855
// relative imports.
856
857
RootedString path(cx, JS_NewStringCopyZ(cx, filename));
858
if (!path) {
859
return false;
860
}
861
862
RootedObject infoObject(cx, JS_NewPlainObject(cx));
863
if (!infoObject) {
864
return false;
865
}
866
867
RootedValue pathValue(cx, StringValue(path));
868
if (!JS_DefineProperty(cx, infoObject, "path", pathValue, 0)) {
869
return false;
870
}
871
872
JS::SetScriptPrivate(script, ObjectValue(*infoObject));
873
return true;
874
}
875
876
enum class CompileUtf8 {
877
InflateToUtf16,
878
DontInflate,
879
};
880
881
static MOZ_MUST_USE bool RunFile(JSContext* cx, const char* filename,
882
FILE* file, CompileUtf8 compileMethod,
883
bool compileOnly) {
884
SkipUTF8BOM(file);
885
886
int64_t t1 = PRMJ_Now();
887
RootedScript script(cx);
888
889
{
890
CompileOptions options(cx);
891
options.setIntroductionType("js shell file")
892
.setFileAndLine(filename, 1)
893
.setIsRunOnce(true)
894
.setNoScriptRval(true);
895
896
if (compileMethod == CompileUtf8::DontInflate) {
897
script = JS::CompileUtf8File(cx, options, file);
898
} else {
899
fprintf(stderr, "(compiling '%s' after inflating to UTF-16)\n", filename);
900
901
FileContents buffer(cx);
902
if (!ReadCompleteFile(cx, file, buffer)) {
903
return false;
904
}
905
906
size_t length = buffer.length();
907
auto chars = UniqueTwoByteChars(
908
UTF8CharsToNewTwoByteCharsZ(
909
cx,
910
UTF8Chars(reinterpret_cast<const char*>(buffer.begin()),
911
buffer.length()),
912
&length, js::MallocArena)
913
.get());
914
if (!chars) {
915
return false;
916
}
917
918
JS::SourceText<char16_t> source;
919
if (!source.init(cx, std::move(chars), length)) {
920
return false;
921
}
922
923
script = JS::Compile(cx, options, source);
924
}
925
926
if (!script) {
927
return false;
928
}
929
}
930
931
if (!RegisterScriptPathWithModuleLoader(cx, script, filename)) {
932
return false;
933
}
934
935
#ifdef DEBUG
936
if (dumpEntrainedVariables) {
937
AnalyzeEntrainedVariables(cx, script);
938
}
939
#endif
940
if (!compileOnly) {
941
if (!JS_ExecuteScript(cx, script)) {
942
return false;
943
}
944
int64_t t2 = PRMJ_Now() - t1;
945
if (printTiming) {
946
printf("runtime = %.3f ms\n", double(t2) / PRMJ_USEC_PER_MSEC);
947
}
948
}
949
return true;
950
}
951
952
#if defined(JS_BUILD_BINAST)
953
954
static MOZ_MUST_USE bool RunBinAST(JSContext* cx, const char* filename,
955
FILE* file, bool compileOnly,
956
JS::BinASTFormat format) {
957
RootedScript script(cx);
958
959
{
960
CompileOptions options(cx);
961
options.setFileAndLine(filename, 0)
962
.setIsRunOnce(true)
963
.setNoScriptRval(true);
964
965
script = JS::DecodeBinAST(cx, options, file, format);
966
if (!script) {
967
return false;
968
}
969
}
970
971
if (!RegisterScriptPathWithModuleLoader(cx, script, filename)) {
972
return false;
973
}
974
975
if (compileOnly) {
976
return true;
977
}
978
979
return JS_ExecuteScript(cx, script);
980
}
981
982
#endif // JS_BUILD_BINAST
983
984
static bool InitModuleLoader(JSContext* cx) {
985
// Decompress and evaluate the embedded module loader source to initialize
986
// the module loader for the current compartment.
987
988
uint32_t srcLen = moduleloader::GetRawScriptsSize();
989
auto src = cx->make_pod_array<char>(srcLen);
990
if (!src ||
991
!DecompressString(moduleloader::compressedSources,
992
moduleloader::GetCompressedSize(),
993
reinterpret_cast<unsigned char*>(src.get()), srcLen)) {
994
return false;
995
}
996
997
CompileOptions options(cx);
998
options.setIntroductionType("shell module loader");
999
options.setFileAndLine("shell/ModuleLoader.js", 1);
1000
options.setSelfHostingMode(false);
1001
options.setForceFullParse();
1002
options.setForceStrictMode();
1003
1004
JS::SourceText<Utf8Unit> srcBuf;
1005
if (!srcBuf.init(cx, std::move(src), srcLen)) {
1006
return false;
1007
}
1008
1009
RootedValue rv(cx);
1010
return JS::Evaluate(cx, options, srcBuf, &rv);
1011
}
1012
1013
static bool GetModuleImportHook(JSContext* cx,
1014
MutableHandleFunction resultOut) {
1015
Handle<GlobalObject*> global = cx->global();
1016
RootedValue hookValue(cx,
1017
global->getReservedSlot(GlobalAppSlotModuleLoadHook));
1018
if (hookValue.isUndefined()) {
1019
JS_ReportErrorASCII(cx, "Module load hook not set");
1020
return false;
1021
}
1022
1023
if (!hookValue.isObject() || !hookValue.toObject().is<JSFunction>()) {
1024
JS_ReportErrorASCII(cx, "Module load hook is not a function");
1025
return false;
1026
}
1027
1028
resultOut.set(&hookValue.toObject().as<JSFunction>());
1029
return true;
1030
}
1031
1032
static MOZ_MUST_USE bool RunModule(JSContext* cx, const char* filename,
1033
FILE* file, bool compileOnly) {
1034
// Execute a module by calling the module loader's import hook on the
1035
// resolved filename.
1036
1037
RootedFunction importFun(cx);
1038
if (!GetModuleImportHook(cx, &importFun)) {
1039
return false;
1040
}
1041
1042
RootedString path(cx, JS_NewStringCopyZ(cx, filename));
1043
if (!path) {
1044
return false;
1045
}
1046
1047
path = ResolvePath(cx, path, RootRelative);
1048
if (!path) {
1049
return false;
1050
}
1051
1052
JS::AutoValueArray<1> args(cx);
1053
args[0].setString(path);
1054
1055
RootedValue value(cx);
1056
return JS_CallFunction(cx, nullptr, importFun, args, &value);
1057
}
1058
1059
static void ShellCleanupFinalizationRegistryCallback(JSObject* registry,
1060
void* data) {
1061
// In the browser this queues a task. Shell jobs correspond to microtasks so
1062
// we arrange for cleanup to happen after all jobs/microtasks have run.
1063
auto sc = static_cast<ShellContext*>(data);
1064
AutoEnterOOMUnsafeRegion oomUnsafe;
1065
if (!sc->finalizationRegistriesToCleanUp.append(registry)) {
1066
oomUnsafe.crash("ShellCleanupFinalizationRegistryCallback");
1067
}
1068
}
1069
1070
// Run any FinalizationRegistry cleanup tasks and return whether any ran.
1071
static bool MaybeRunFinalizationRegistryCleanupTasks(JSContext* cx) {
1072
ShellContext* sc = GetShellContext(cx);
1073
MOZ_ASSERT(!sc->quitting);
1074
1075
Rooted<ShellContext::ObjectVector> registries(cx);
1076
std::swap(registries.get(), sc->finalizationRegistriesToCleanUp.get());
1077
1078
bool ranTasks = false;
1079
1080
RootedObject registry(cx);
1081
for (const auto& r : registries) {
1082
registry = r;
1083
1084
AutoRealm ar(cx, registry);
1085
1086
{
1087
AutoReportException are(cx);
1088
mozilla::Unused << JS::CleanupQueuedFinalizationRegistry(cx, registry);
1089
}
1090
1091
ranTasks = true;
1092
1093
if (sc->quitting) {
1094
break;
1095
}
1096
}
1097
1098
return ranTasks;
1099
}
1100
1101
static bool EnqueueJob(JSContext* cx, unsigned argc, Value* vp) {
1102
CallArgs args = CallArgsFromVp(argc, vp);
1103
1104
if (!IsFunctionObject(args.get(0))) {
1105
JS_ReportErrorASCII(cx, "EnqueueJob's first argument must be a function");
1106
return false;
1107
}
1108
1109
args.rval().setUndefined();
1110
1111
RootedObject job(cx, &args[0].toObject());
1112
return js::EnqueueJob(cx, job);
1113
}
1114
1115
static void RunShellJobs(JSContext* cx) {
1116
ShellContext* sc = GetShellContext(cx);
1117
if (sc->quitting) {
1118
return;
1119
}
1120
1121
while (true) {
1122
// Run microtasks.
1123
js::RunJobs(cx);
1124
if (sc->quitting) {
1125
return;
1126
}
1127
1128
// Run tasks (only finalization registry clean tasks are possible).
1129
bool ranTasks = MaybeRunFinalizationRegistryCleanupTasks(cx);
1130
if (!ranTasks) {
1131
break;
1132
}
1133
}
1134
}
1135
1136
static bool DrainJobQueue(JSContext* cx, unsigned argc, Value* vp) {
1137
CallArgs args = CallArgsFromVp(argc, vp);
1138
1139
if (GetShellContext(cx)->quitting) {
1140
JS_ReportErrorASCII(
1141
cx, "Mustn't drain the job queue when the shell is quitting");
1142
return false;
1143
}
1144
1145
RunShellJobs(cx);
1146
1147
if (GetShellContext(cx)->quitting) {
1148
return false;
1149
}
1150
1151
args.rval().setUndefined();
1152
return true;
1153
}
1154
1155
static bool GlobalOfFirstJobInQueue(JSContext* cx, unsigned argc, Value* vp) {
1156
CallArgs args = CallArgsFromVp(argc, vp);
1157
1158
RootedObject job(cx, cx->internalJobQueue->maybeFront());
1159
if (!job) {
1160
JS_ReportErrorASCII(cx, "Job queue is empty");
1161
return false;
1162
}
1163
1164
RootedObject global(cx, &job->nonCCWGlobal());
1165
if (!cx->compartment()->wrap(cx, &global)) {
1166
return false;
1167
}
1168
1169
args.rval().setObject(*global);
1170
return true;
1171
}
1172
1173
static bool TrackUnhandledRejections(JSContext* cx, JS::HandleObject promise,
1174
JS::PromiseRejectionHandlingState state) {
1175
ShellContext* sc = GetShellContext(cx);
1176
if (!sc->trackUnhandledRejections) {
1177
return true;
1178
}
1179
1180
#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
1181
if (cx->runningOOMTest) {
1182
// When OOM happens, we cannot reliably track the set of unhandled
1183
// promise rejections. Throw error only when simulated OOM is used
1184
// *and* promises are used in the test.
1185
JS_ReportErrorASCII(
1186
cx,
1187
"Can't track unhandled rejections while running simulated OOM "
1188
"test. Call ignoreUnhandledRejections before using oomTest etc.");
1189
return false;
1190
}
1191
#endif
1192
1193
if (!sc->unhandledRejectedPromises) {
1194
sc->unhandledRejectedPromises = SetObject::create(cx);
1195
if (!sc->unhandledRejectedPromises) {
1196
return false;
1197
}
1198
}
1199
1200
RootedValue promiseVal(cx, ObjectValue(*promise));
1201
1202
AutoRealm ar(cx, sc->unhandledRejectedPromises);
1203
if (!cx->compartment()->wrap(cx, &promiseVal)) {
1204
return false;
1205
}
1206
1207
switch (state) {
1208
case JS::PromiseRejectionHandlingState::Unhandled:
1209
if (!SetObject::add(cx, sc->unhandledRejectedPromises, promiseVal)) {
1210
return false;
1211
}
1212
break;
1213
case JS::PromiseRejectionHandlingState::Handled:
1214
bool deleted = false;
1215
if (!SetObject::delete_(cx, sc->unhandledRejectedPromises, promiseVal,
1216
&deleted)) {
1217
return false;
1218
}
1219
// We can't MOZ_ASSERT(deleted) here, because it's possible we failed to
1220
// add the promise in the first place, due to OOM.
1221
break;
1222
}
1223
1224
return true;
1225
}
1226
1227
static void ForwardingPromiseRejectionTrackerCallback(
1228
JSContext* cx, bool mutedErrors, JS::HandleObject promise,
1229
JS::PromiseRejectionHandlingState state, void* data) {
1230
AutoReportException are(cx);
1231
1232
if (!TrackUnhandledRejections(cx, promise, state)) {
1233
return;
1234
}
1235
1236
RootedValue callback(cx,
1237
GetShellContext(cx)->promiseRejectionTrackerCallback);
1238
if (callback.isNull()) {
1239
return;
1240
}
1241
1242
AutoRealm ar(cx, &callback.toObject());
1243
1244
FixedInvokeArgs<2> args(cx);
1245
args[0].setObject(*promise);
1246
args[1].setInt32(static_cast<int32_t>(state));
1247
1248
if (!JS_WrapValue(cx, args[0])) {
1249
return;
1250
}
1251
1252
RootedValue rval(cx);
1253
(void)Call(cx, callback, UndefinedHandleValue, args, &rval);
1254
}
1255
1256
static bool SetPromiseRejectionTrackerCallback(JSContext* cx, unsigned argc,
1257
Value* vp) {
1258
CallArgs args = CallArgsFromVp(argc, vp);
1259
1260
if (!IsFunctionObject(args.get(0))) {
1261
JS_ReportErrorASCII(
1262
cx,
1263
"setPromiseRejectionTrackerCallback expects a function as its sole "
1264
"argument");
1265
return false;
1266
}
1267
1268
GetShellContext(cx)->promiseRejectionTrackerCallback = args[0];
1269
1270
args.rval().setUndefined();
1271
return true;
1272
}
1273
1274
static bool BoundToAsyncStack(JSContext* cx, unsigned argc, Value* vp) {
1275
CallArgs args = CallArgsFromVp(argc, vp);
1276
1277
RootedValue function(cx, GetFunctionNativeReserved(&args.callee(), 0));
1278
RootedObject options(
1279
cx, &GetFunctionNativeReserved(&args.callee(), 1).toObject());
1280
1281
RootedSavedFrame stack(cx, nullptr);
1282
bool isExplicit;
1283
1284
RootedValue v(cx);
1285
1286
if (!JS_GetProperty(cx, options, "stack", &v)) {
1287
return false;
1288
}
1289
if (!v.isObject() || !v.toObject().is<SavedFrame>()) {
1290
JS_ReportErrorASCII(cx,
1291
"The 'stack' property must be a SavedFrame object.");
1292
return false;
1293
}
1294
stack = &v.toObject().as<SavedFrame>();
1295
1296
if (!JS_GetProperty(cx, options, "cause", &v)) {
1297
return false;
1298
}
1299
RootedString causeString(cx, ToString(cx, v));
1300
if (!causeString) {
1301
MOZ_ASSERT(cx->isExceptionPending());
1302
return false;
1303
}
1304
1305
UniqueChars cause = JS_EncodeStringToUTF8(cx, causeString);
1306
if (!cause) {
1307
MOZ_ASSERT(cx->isExceptionPending());
1308
return false;
1309
}
1310
1311
if (!JS_GetProperty(cx, options, "explicit", &v)) {
1312
return false;
1313
}
1314
isExplicit = v.isUndefined() ? true : ToBoolean(v);
1315
1316
auto kind =
1317
(isExplicit ? JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::EXPLICIT
1318
: JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::IMPLICIT);
1319
1320
JS::AutoSetAsyncStackForNewCalls asasfnckthxbye(cx, stack, cause.get(), kind);
1321
return Call(cx, UndefinedHandleValue, function, JS::HandleValueArray::empty(),
1322
args.rval());
1323
}
1324
1325
static bool BindToAsyncStack(JSContext* cx, unsigned argc, Value* vp) {
1326
CallArgs args = CallArgsFromVp(argc, vp);
1327
1328
if (args.length() != 2) {
1329
JS_ReportErrorASCII(cx, "bindToAsyncStack takes exactly two arguments.");
1330
return false;
1331
}
1332
1333
if (!args[0].isObject() || !IsCallable(args[0])) {
1334
JS_ReportErrorASCII(
1335
cx, "bindToAsyncStack's first argument should be a function.");
1336
return false;
1337
}
1338
1339
if (!args[1].isObject()) {
1340
JS_ReportErrorASCII(
1341
cx, "bindToAsyncStack's second argument should be an object.");
1342
return false;
1343
}
1344
1345
RootedFunction bound(cx, NewFunctionWithReserved(cx, BoundToAsyncStack, 0, 0,
1346
"bindToAsyncStack thunk"));
1347
if (!bound) {
1348
return false;
1349
}
1350
SetFunctionNativeReserved(bound, 0, args[0]);
1351
SetFunctionNativeReserved(bound, 1, args[1]);
1352
1353
args.rval().setObject(*bound);
1354
return true;
1355
}
1356
1357
#ifdef JS_HAS_INTL_API
1358
static bool AddIntlExtras(JSContext* cx, unsigned argc, Value* vp) {
1359
CallArgs args = CallArgsFromVp(argc, vp);
1360
if (!args.get(0).isObject()) {
1361
JS_ReportErrorASCII(cx, "addIntlExtras must be passed an object");
1362
return false;
1363
}
1364
JS::RootedObject intl(cx, &args[0].toObject());
1365
1366
static const JSFunctionSpec funcs[] = {
1367
JS_SELF_HOSTED_FN("getCalendarInfo", "Intl_getCalendarInfo", 1, 0),
1368
JS_SELF_HOSTED_FN("getLocaleInfo", "Intl_getLocaleInfo", 1, 0),
1369
JS_SELF_HOSTED_FN("getDisplayNames", "Intl_getDisplayNames", 2, 0),
1370
JS_FS_END};
1371
1372
if (!JS_DefineFunctions(cx, intl, funcs)) {
1373
return false;
1374
}
1375
1376
if (!js::AddMozDateTimeFormatConstructor(cx, intl)) {
1377
return false;
1378
}
1379
1380
if (!js::AddListFormatConstructor(cx, intl)) {
1381
return false;
1382
}
1383
1384
args.rval().setUndefined();
1385
return true;
1386
}
1387
#endif // JS_HAS_INTL_API
1388
1389
static MOZ_MUST_USE bool EvalUtf8AndPrint(JSContext* cx, const char* bytes,
1390
size_t length, int lineno,
1391
bool compileOnly) {
1392
// Eval.
1393
JS::CompileOptions options(cx);
1394
options.setIntroductionType("js shell interactive")
1395
.setIsRunOnce(true)
1396
.setFileAndLine("typein", lineno);
1397
1398
JS::SourceText<Utf8Unit> srcBuf;
1399
if (!srcBuf.init(cx, bytes, length, JS::SourceOwnership::Borrowed)) {
1400
return false;
1401
}
1402
1403
RootedScript script(cx, JS::Compile(cx, options, srcBuf));
1404
if (!script) {
1405
return false;
1406
}
1407
if (compileOnly) {
1408
return true;
1409
}
1410
RootedValue result(cx);
1411
if (!JS_ExecuteScript(cx, script, &result)) {
1412
return false;
1413
}
1414
1415
if (!result.isUndefined() && gOutFile->isOpen()) {
1416
// Print.
1417
RootedString str(cx, JS_ValueToSource(cx, result));
1418
if (!str) {
1419
return false;
1420
}
1421
1422
UniqueChars utf8chars = JS_EncodeStringToUTF8(cx, str);
1423
if (!utf8chars) {
1424
return false;
1425
}
1426
fprintf(gOutFile->fp, "%s\n", utf8chars.get());
1427
}
1428
return true;
1429
}
1430
1431
static MOZ_MUST_USE bool ReadEvalPrintLoop(JSContext* cx, FILE* in,
1432
bool compileOnly) {
1433
ShellContext* sc = GetShellContext(cx);
1434
int lineno = 1;
1435
bool hitEOF = false;
1436
1437
do {
1438
/*
1439
* Accumulate lines until we get a 'compilable unit' - one that either
1440
* generates an error (before running out of source) or that compiles
1441
* cleanly. This should be whenever we get a complete statement that
1442
* coincides with the end of a line.
1443
*/
1444
int startline = lineno;
1445
typedef Vector<char, 32> CharBuffer;
1446
RootedObject globalLexical(cx, &cx->global()->lexicalEnvironment());
1447
CharBuffer buffer(cx);
1448
do {
1449
ScheduleWatchdog(cx, -1);
1450
sc->serviceInterrupt = false;
1451
errno = 0;
1452
1453
mozilla::UniqueFreePtr<char[]> line =
1454
GetLine(in, startline == lineno ? "js> " : "");
1455
if (!line) {
1456
if (errno) {
1457
/*
1458
* Use Latin1 variant here because strerror(errno)'s
1459
* encoding depends on the user's C locale.
1460
*/
1461
JS_ReportErrorLatin1(cx, "%s", strerror(errno));
1462
return false;
1463
}
1464
hitEOF = true;
1465
break;
1466
}
1467
1468
if (!buffer.append(line.get(), strlen(line.get())) ||
1469
!buffer.append('\n')) {
1470
return false;
1471
}
1472
1473
lineno++;
1474
if (!ScheduleWatchdog(cx, sc->timeoutInterval)) {
1475
hitEOF = true;
1476
break;
1477
}
1478
} while (!JS_Utf8BufferIsCompilableUnit(cx, cx->global(), buffer.begin(),
1479
buffer.length()));
1480
1481
if (hitEOF && buffer.empty()) {
1482
break;
1483
}
1484
1485
{
1486
// Report exceptions but keep going.
1487
AutoReportException are(cx);
1488
mozilla::Unused << EvalUtf8AndPrint(cx, buffer.begin(), buffer.length(),
1489
startline, compileOnly);
1490
}
1491
1492
// If a let or const fail to initialize they will remain in an unusable
1493
// without further intervention. This call cleans up the global scope,
1494
// setting uninitialized lexicals to undefined so that they may still
1495
// be used. This behavior is _only_ acceptable in the context of the repl.
1496
if (JS::ForceLexicalInitialization(cx, globalLexical) &&
1497
gErrFile->isOpen()) {
1498
fputs(
1499
"Warning: According to the standard, after the above exception,\n"
1500
"Warning: the global bindings should be permanently uninitialized.\n"
1501
"Warning: We have non-standard-ly initialized them to `undefined`"
1502
"for you.\nWarning: This nicety only happens in the JS shell.\n",
1503
stderr);
1504
}
1505
1506
RunShellJobs(cx);
1507
} while (!hitEOF && !sc->quitting);
1508
1509
if (gOutFile->isOpen()) {
1510
fprintf(gOutFile->fp, "\n");
1511
}
1512
1513
return true;
1514
}
1515
1516
enum FileKind {
1517
FileScript, // UTF-8, directly parsed as such
1518
FileScriptUtf16, // FileScript, but inflate to UTF-16 before parsing
1519
FileModule,
1520
FileBinASTMultipart,
1521
FileBinASTContext,
1522
};
1523
1524
static void ReportCantOpenErrorUnknownEncoding(JSContext* cx,
1525
const char* filename) {
1526
/*
1527
* Filenames are in some random system encoding. *Probably* it's UTF-8,
1528
* but no guarantees.
1529
*
1530
* strerror(errno)'s encoding, in contrast, depends on the user's C locale.
1531
*
1532
* Latin-1 is possibly wrong for both of these -- but at least if it's
1533
* wrong it'll produce mojibake *safely*. Run with Latin-1 til someone
1534
* complains.
1535
*/
1536
JS_ReportErrorNumberLatin1(cx, my_GetErrorMessage, nullptr, JSSMSG_CANT_OPEN,
1537
filename, strerror(errno));
1538
}
1539
1540
static MOZ_MUST_USE bool Process(JSContext* cx, const char* filename,
1541
bool forceTTY, FileKind kind) {
1542
FILE* file;
1543
if (forceTTY || !filename || strcmp(filename, "-") == 0) {
1544
file = stdin;
1545
} else {
1546
file = fopen(filename, "rb");
1547
if (!file) {
1548
ReportCantOpenErrorUnknownEncoding(cx, filename);
1549
return false;
1550
}
1551
}
1552
AutoCloseFile autoClose(file);
1553
1554
if (!forceTTY && !isatty(fileno(file))) {
1555
// It's not interactive - just execute it.
1556
switch (kind) {
1557
case FileScript:
1558
if (!RunFile(cx, filename, file, CompileUtf8::DontInflate,
1559
compileOnly)) {
1560
return false;
1561
}
1562
break;
1563
case FileScriptUtf16:
1564
if (!RunFile(cx, filename, file, CompileUtf8::InflateToUtf16,
1565
compileOnly)) {
1566
return false;
1567
}
1568
break;
1569
case FileModule:
1570
if (!RunModule(cx, filename, file, compileOnly)) {
1571
return false;
1572
}
1573
break;
1574
#if defined(JS_BUILD_BINAST)
1575
case FileBinASTMultipart:
1576
if (!RunBinAST(cx, filename, file, compileOnly,
1577
JS::BinASTFormat::Multipart)) {
1578
return false;
1579
}
1580
break;
1581
case FileBinASTContext:
1582
if (!RunBinAST(cx, filename, file, compileOnly,
1583
JS::BinASTFormat::Context)) {
1584
return false;
1585
}
1586
break;
1587
#endif // JS_BUILD_BINAST
1588
default:
1589
MOZ_CRASH("Impossible FileKind!");
1590
}
1591
} else {
1592
// It's an interactive filehandle; drop into read-eval-print loop.
1593
MOZ_ASSERT(kind == FileScript);
1594
if (!ReadEvalPrintLoop(cx, file, compileOnly)) {
1595
return false;
1596
}
1597
}
1598
return true;
1599
}
1600
1601
#ifdef XP_WIN
1602
# define GET_FD_FROM_FILE(a) int(_get_osfhandle(fileno(a)))
1603
#else
1604
# define GET_FD_FROM_FILE(a) fileno(a)
1605
#endif
1606
1607
static bool CreateMappedArrayBuffer(JSContext* cx, unsigned argc, Value* vp) {
1608
CallArgs args = CallArgsFromVp(argc, vp);
1609
1610
if (args.length() < 1 || args.length() > 3) {
1611
JS_ReportErrorNumberASCII(
1612
cx, my_GetErrorMessage, nullptr,
1613
args.length() < 1 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS,
1614
"createMappedArrayBuffer");
1615
return false;
1616
}
1617
1618
RootedString rawFilenameStr(cx, JS::ToString(cx, args[0]));
1619
if (!rawFilenameStr) {
1620
return false;
1621
}
1622
// It's a little bizarre to resolve relative to the script, but for testing
1623
// I need a file at a known location, and the only good way I know of to do
1624
// that right now is to include it in the repo alongside the test script.
1625
// Bug 944164 would introduce an alternative.
1626
JSString* filenameStr = ResolvePath(cx, rawFilenameStr, ScriptRelative);
1627
if (!filenameStr) {
1628
return false;
1629
}
1630
UniqueChars filename = JS_EncodeStringToLatin1(cx, filenameStr);
1631
if (!filename) {
1632
return false;
1633
}
1634
1635
uint32_t offset = 0;
1636
if (args.length() >= 2) {
1637
if (!JS::ToUint32(cx, args[1], &offset)) {
1638
return false;
1639
}
1640
}
1641
1642
bool sizeGiven = false;
1643
uint32_t size;
1644
if (args.length() >= 3) {
1645
if (!JS::ToUint32(cx, args[2], &size)) {
1646
return false;
1647
}
1648
sizeGiven = true;
1649
if (size == 0) {
1650
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1651
JSMSG_BAD_ARRAY_LENGTH);
1652
return false;
1653
}
1654
}
1655
1656
FILE* file = fopen(filename.get(), "rb");
1657
if (!file) {
1658
ReportCantOpenErrorUnknownEncoding(cx, filename.get());
1659
return false;
1660
}
1661
AutoCloseFile autoClose(file);
1662
1663
struct stat st;
1664
if (fstat(fileno(file), &st) < 0) {
1665
JS_ReportErrorASCII(cx, "Unable to stat file");
1666
return false;
1667
}
1668
1669
if ((st.st_mode & S_IFMT) != S_IFREG) {
1670
JS_ReportErrorASCII(cx, "Path is not a regular file");
1671
return false;
1672
}
1673
1674
if (!sizeGiven) {
1675
if (off_t(offset) >= st.st_size) {
1676
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1677
JSMSG_OFFSET_LARGER_THAN_FILESIZE);
1678
return false;
1679
}
1680
size = st.st_size - offset;
1681
}
1682
1683
void* contents =
1684
JS::CreateMappedArrayBufferContents(GET_FD_FROM_FILE(file), offset, size);
1685
if (!contents) {
1686
JS_ReportErrorASCII(cx,
1687
"failed to allocate mapped array buffer contents "
1688
"(possibly due to bad alignment)");
1689
return false;
1690
}
1691
1692
RootedObject obj(cx,
1693
JS::NewMappedArrayBufferWithContents(cx, size, contents));
1694
if (!obj) {
1695
return false;
1696
}
1697
1698
args.rval().setObject(*obj);
1699
return true;
1700
}
1701
1702
#undef GET_FD_FROM_FILE
1703
1704
static bool AddPromiseReactions(JSContext* cx, unsigned argc, Value* vp) {
1705
CallArgs args = CallArgsFromVp(argc, vp);
1706
1707
if (args.length() != 3) {
1708
JS_ReportErrorNumberASCII(
1709
cx, my_GetErrorMessage, nullptr,
1710
args.length() < 3 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS,
1711
"addPromiseReactions");
1712
return false;
1713
}
1714
1715
RootedObject promise(cx);
1716
if (args[0].isObject()) {
1717
promise = &args[0].toObject();
1718
}
1719
1720
if (!promise || !JS::IsPromiseObject(promise)) {
1721
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
1722
JSSMSG_INVALID_ARGS, "addPromiseReactions");
1723
return false;
1724
}
1725
1726
RootedObject onResolve(cx);
1727
if (args[1].isObject()) {
1728
onResolve = &args[1].toObject();
1729
}
1730
1731
RootedObject onReject(cx);
1732
if (args[2].isObject()) {
1733
onReject = &args[2].toObject();
1734
}
1735
1736
if (!onResolve || !onResolve->is<JSFunction>() || !onReject ||
1737
!onReject->is<JSFunction>()) {
1738
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
1739
JSSMSG_INVALID_ARGS, "addPromiseReactions");
1740
return false;
1741
}
1742
1743
return JS::AddPromiseReactions(cx, promise, onResolve, onReject);
1744
}
1745
1746
static bool IgnoreUnhandledRejections(JSContext* cx, unsigned argc, Value* vp) {
1747
CallArgs args = CallArgsFromVp(argc, vp);
1748
1749
ShellContext* sc = GetShellContext(cx);
1750
sc->trackUnhandledRejections = false;
1751
1752
args.rval().setUndefined();
1753
return true;
1754
}
1755
1756
static bool Options(JSContext* cx, unsigned argc, Value* vp) {
1757
CallArgs args = CallArgsFromVp(argc, vp);
1758
1759
JS::ContextOptions oldContextOptions = JS::ContextOptionsRef(cx);
1760
for (unsigned i = 0; i < args.length(); i++) {
1761
RootedString str(cx, JS::ToString(cx, args[i]));
1762
if (!str) {
1763
return false;
1764
}
1765
1766
RootedLinearString opt(cx, str->ensureLinear(cx));
1767
if (!opt) {
1768
return false;
1769
}
1770
1771
if (StringEqualsLiteral(opt, "throw_on_asmjs_validation_failure")) {
1772
JS::ContextOptionsRef(cx).toggleThrowOnAsmJSValidationFailure();
1773
} else if (StringEqualsLiteral(opt, "strict_mode")) {
1774
JS::ContextOptionsRef(cx).toggleStrictMode();
1775
} else {
1776
UniqueChars optChars = QuoteString(cx, opt, '"');
1777
if (!optChars) {
1778
return false;
1779
}
1780
1781
JS_ReportErrorASCII(cx,
1782
"unknown option name %s."
1783
" The valid names are "
1784
"throw_on_asmjs_validation_failure and strict_mode.",
1785
optChars.get());
1786
return false;
1787
}
1788
}
1789
1790
UniqueChars names = DuplicateString("");
1791
bool found = false;
1792
if (names && oldContextOptions.throwOnAsmJSValidationFailure()) {
1793
names = JS_sprintf_append(std::move(names), "%s%s", found ? "," : "",
1794
"throw_on_asmjs_validation_failure");
1795
found = true;
1796
}
1797
if (names && oldContextOptions.strictMode()) {
1798
names = JS_sprintf_append(std::move(names), "%s%s", found ? "," : "",
1799
"strict_mode");
1800
found = true;
1801
}
1802
if (!names) {
1803
JS_ReportOutOfMemory(cx);
1804
return false;
1805
}
1806
1807
JSString* str = JS_NewStringCopyZ(cx, names.get());
1808
if (!str) {
1809
return false;
1810
}
1811
args.rval().setString(str);
1812
return true;
1813
}
1814
1815
static bool LoadScript(JSContext* cx, unsigned argc, Value* vp,
1816
bool scriptRelative) {
1817
CallArgs args = CallArgsFromVp(argc, vp);
1818
1819
RootedString str(cx);
1820
for (unsigned i = 0; i < args.length(); i++) {
1821
str = JS::ToString(cx, a