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