/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
using JS::AutoStableStringChars; using JS::CompileOptions;
using js::shell::RCFile;
using mozilla::ArrayEqual; using mozilla::AsVariant; using mozilla::Atomic; using mozilla::MakeScopeExit; using mozilla::Maybe; using mozilla::Nothing; using mozilla::NumberEqualsInt32; using mozilla::TimeDuration; using mozilla::TimeStamp; using mozilla::Utf8Unit; using mozilla::Variant;
extern"C"void __sanitizer_cov_trace_pc_guard(uint32_t* guard) { // There's a small race condition here: if this function executes in two // threads for the same edge at the same time, the first thread might disable // the edge (by setting the guard to zero) before the second thread fetches // the guard value (and thus the index). However, our instrumentation ignores // the first edge (see libcoverage.c) and so the race is unproblematic.
uint32_t index = *guard; // If this function is called before coverage instrumentation is properly // initialized we want to return early. if (!index) return;
__shmem->edges[index / 8] |= 1 << (index % 8);
*guard = 0;
} #endif/* FUZZING_JS_FUZZILLI */
struct ShellLogModule { // Since ShellLogModules have references to their levels created // we can't move them.
ShellLogModule(ShellLogModule&&) = delete;
// If asserts related to this ever fail, simply bump this number. // // This is used to construct a mozilla::Array, which is used because a // ShellLogModule cannot move once constructed to avoid invalidating // a levelRef. staticconstint MAX_LOG_MODULES = 64; staticint initialized_modules = 0;
mozilla::Array<mozilla::Maybe<ShellLogModule>, MAX_LOG_MODULES> logModules;
JS::OpaqueLogger GetLoggerByName(constchar* name) { // Check for pre-existing module for (auto& logger : logModules) { if (logger) { if (logger->name == name) { return logger.ptr();
}
} // We've seen all initialized, not there, break out. if (!logger) break;
}
// Not found, allocate a new module.
MOZ_RELEASE_ASSERT(initialized_modules < MAX_LOG_MODULES - 1); auto index = initialized_modules++;
logModules[index].emplace(name); return logModules[index].ptr();
}
staticvoid ToLower(constchar* src, char* dest, size_t len) { for (size_t c = 0; c < len; c++) {
dest[c] = (char)(tolower(src[c]));
}
}
// Run this after initialiation! void ParseLoggerOptions() { char* mixedCaseOpts = getenv("MOZ_LOG"); if (!mixedCaseOpts) { return;
}
// Copy into a new buffer and lower case to do case insensitive matching. // // Done this way rather than just using strcasestr because Windows doesn't // have strcasestr as part of its base C library.
size_t len = strlen(mixedCaseOpts);
mozilla::UniqueFreePtr<char[]> logOpts( static_cast<char*>(calloc(len + 1, 1))); if (!logOpts) { return;
}
ToLower(mixedCaseOpts, logOpts.get(), len);
// This is a really permissive parser, but will suffice! for (auto& logger : logModules) { if (logger) { // Lowercase the logger name for strstr
size_t len = strlen(logger->name);
mozilla::UniqueFreePtr<char[]> lowerName( static_cast<char*>(calloc(len + 1, 1)));
ToLower(logger->name, lowerName.get(), len);
if (char* needle = strstr(logOpts.get(), lowerName.get())) { // If the string to enable a logger is present, but no level is provided // then default to Debug level. int logLevel = static_cast<int>(mozilla::LogLevel::Debug);
if (char* colon = strchr(needle, ':')) { // Parse character after colon as log level. if (*(colon + 1)) {
logLevel = atoi(colon + 1);
}
}
/* * Limit the timeout to 30 minutes to prevent an overflow on platfoms * that represent the time internally in microseconds using 32-bit int.
*/ staticconstdouble MAX_TIMEOUT_SECONDS = 1800.0;
// Not necessarily in sync with the browser #ifdef ENABLE_SHARED_MEMORY # define SHARED_MEMORY_DEFAULT 1 #else # define SHARED_MEMORY_DEFAULT 0 #endif
// Fuzzing support for JS runtime fuzzing #ifdef FUZZING_INTERFACES # include "shell/jsrtfuzzing/jsrtfuzzing.h"
MOZ_RUNINIT staticbool fuzzDoDebug = !!getenv("MOZ_FUZZ_DEBUG");
MOZ_RUNINIT staticbool fuzzHaveModule = !!getenv("FUZZER"); #endif// FUZZING_INTERFACES
// Code to support GCOV code coverage measurements on standalone shell #ifdef MOZ_CODE_COVERAGE # ifdefined(__GNUC__) && !defined(__clang__) extern"C"void __gcov_dump(); extern"C"void __gcov_reset();
template <typename T> static OffThreadJob* NewOffThreadJob(JSContext* cx, OffThreadJob::Kind kind,
JS::ReadOnlyCompileOptions& options,
T&& source) {
ShellContext* sc = GetShellContext(cx); if (sc->isWorker) { // Off-thread compilation/decode is used by main-thread, in order to improve // the responsiveness. It's not used by worker in browser, and there's not // much reason to support worker here.
JS_ReportErrorASCII(cx, "Off-thread job is not supported in worker"); return nullptr;
}
static OffThreadJob* GetSingleOffThreadJob(JSContext* cx) {
ShellContext* sc = GetShellContext(cx); constauto& jobs = sc->offThreadJobs; if (jobs.empty()) {
JS_ReportErrorASCII(cx, "No off-thread jobs are pending"); return nullptr;
}
if (jobs.length() > 1) {
JS_ReportErrorASCII(
cx, "Multiple off-thread jobs are pending: must specify job ID"); return nullptr;
}
return jobs[0];
}
static OffThreadJob* LookupOffThreadJobByID(JSContext* cx, int32_t id) { if (id <= 0) {
JS_ReportErrorASCII(cx, "Bad off-thread job ID"); return nullptr;
}
ShellContext* sc = GetShellContext(cx); constauto& jobs = sc->offThreadJobs; if (jobs.empty()) {
JS_ReportErrorASCII(cx, "No off-thread jobs are pending"); return nullptr;
}
if (!job) {
JS_ReportErrorASCII(cx, "Off-thread job not found"); return nullptr;
}
return job;
}
static OffThreadJob* LookupOffThreadJobForArgs(JSContext* cx, const CallArgs& args,
size_t arg) { // If the optional ID argument isn't present, get the single pending job. if (args.length() <= arg) { return GetSingleOffThreadJob(cx);
}
// Lookup the job using the specified ID.
int32_t id = 0;
RootedValue value(cx, args[arg]); if (!ToInt32(cx, value, &id)) { return nullptr;
}
return LookupOffThreadJobByID(cx, id);
}
staticvoid DeleteOffThreadJob(JSContext* cx, OffThreadJob* job) {
ShellContext* sc = GetShellContext(cx); for (size_t i = 0; i < sc->offThreadJobs.length(); i++) { if (sc->offThreadJobs[i] == job) {
sc->offThreadJobs.erase(&sc->offThreadJobs[i]);
js_delete(job); return;
}
}
/* * A toy WindowProxy class for the shell. This is intended for testing code * where global |this| is a WindowProxy. All requests are forwarded to the * underlying global and no navigation is supported.
*/ const JSClass ShellWindowProxyClass =
PROXY_CLASS_DEF("ShellWindowProxy", JSCLASS_HAS_RESERVED_SLOTS(1));
/* * A toy principals type for the shell. * * In the shell, a principal is simply a 32-bit mask: P subsumes Q if the * set bits in P are a superset of those in Q. Thus, the principal 0 is * subsumed by everything, and the principal ~0 subsumes everything. * * As a special case, a null pointer as a principal is treated like 0xffff. * * The 'newGlobal' function takes an option indicating which principal the * new global should have; 'evaluate' does for the new code.
*/ class ShellPrincipals final : public JSPrincipals {
uint32_t bits;
bool write(JSContext* cx, JSStructuredCloneWriter* writer) override { // The shell doesn't have a read principals hook, so it doesn't really // matter what we write here, but we have to write something so the // fuzzer is happy. return JS_WriteUint32Pair(writer, bits, 0);
}
if (array) { // Trace the array elements as part of root marking. for (uint32_t i = 0; i < array->getDenseInitializedLength(); i++) {
Value& value = const_cast<Value&>(array->getDenseElement(i));
TraceManuallyBarrieredEdge(trc, &value, "shell root array element");
}
}
}
}
}
static mozilla::UniqueFreePtr<char[]> GetLine(FILE* file, constchar* prompt) { #ifdef EDITLINE /* * Use readline only if file is stdin, because there's no way to specify * another handle. Are other filehandles interactive?
*/ if (file == stdin) {
mozilla::UniqueFreePtr<char[]> linep(readline(prompt)); /* * We set it to zero to avoid complaining about inappropriate ioctl * for device in the case of EOF. Looks like errno == 251 if line is * finished with EOF and errno == 25 (EINVAL on Mac) if there is * nothing left to read.
*/ if (errno == 251 || errno == 25 || errno == EINVAL) {
errno = 0;
} if (!linep) { return nullptr;
} if (linep[0] != '\0') {
add_history(linep.get());
} return linep;
} #endif
size_t len = 0; if (*prompt != '\0' && gOutFile->isOpen()) {
fprintf(gOutFile->fp, "%s", prompt);
fflush(gOutFile->fp);
}
// Reset serviceInterrupt. CancelExecution or InterruptIf will set it to // true to distinguish watchdog or user triggered interrupts. // Do this first to prevent other interrupts that may occur while the // user-supplied callback is executing from re-entering the handler.
sc->serviceInterrupt = false;
// Report any exceptions thrown by the JS interrupt callback, but do // *not* keep it on the cx. The interrupt handler is invoked at points // that are not expected to throw catchable exceptions, like at // JSOp::RetRval. // // If the interrupted JS code was already throwing, any exceptions // thrown by the interrupt handler are silently swallowed.
{
Maybe<AutoReportException> are; if (!wasAlreadyThrowing) {
are.emplace(cx);
}
result = JS_CallFunctionValue(cx, nullptr, sc->interruptFunc,
JS::HandleValueArray::empty(), &rval);
}
savedExc.restore();
if (rval.isBoolean()) {
result = rval.toBoolean();
} else {
result = false;
}
} else {
result = false;
}
if (!result && sc->exitCode == 0) { staticconstchar msg[] = "Script terminated by interrupt handler.\n";
fputs(msg, stderr);
sc->exitCode = EXITCODE_TIMEOUT;
}
return result;
}
staticvoid GCSliceCallback(JSContext* cx, JS::GCProgress progress, const JS::GCDescription& desc) { if (progress == JS::GC_CYCLE_END) { #ifdefined(MOZ_MEMORY) // We call this here to match the browser's DOMGCSliceCallback.
jemalloc_free_dirty_pages(); #endif
}
}
/* * Some UTF-8 files, notably those written using Notepad, have a Unicode * Byte-Order-Mark (BOM) as their first character. This is useless (byte-order * is meaningless for UTF-8) but causes a syntax error unless we skip it.
*/ staticvoid SkipUTF8BOM(FILE* file) { int ch1 = fgetc(file); int ch2 = fgetc(file); int ch3 = fgetc(file);
// Skip the BOM if (ch1 == 0xEF && ch2 == 0xBB && ch3 == 0xBF) { return;
}
// No BOM - revert if (ch3 != EOF) {
ungetc(ch3, file);
} if (ch2 != EOF) {
ungetc(ch2, file);
} if (ch1 != EOF) {
ungetc(ch1, file);
}
}
staticbool RegisterScriptPathWithModuleLoader(JSContext* cx,
HandleScript script, constchar* filename) { // Set the private value associated with a script to a object containing the // script's filename so that the module loader can use it to resolve // relative imports.
RootedString path(cx, NewStringCopyUTF8(cx, filename)); if (!path) { returnfalse;
}
MOZ_ASSERT(JS::GetScriptPrivate(script).isUndefined());
RootedObject infoObject(cx, js::CreateScriptPrivate(cx, path)); if (!infoObject) { returnfalse;
}
staticvoid ShellCleanupFinalizationRegistryCallback(JSFunction* doCleanup,
JSObject* incumbentGlobal, void* data) { // In the browser this queues a task. Shell jobs correspond to microtasks so // we arrange for cleanup to happen after all jobs/microtasks have run. The // incumbent global is ignored in the shell.
auto sc = static_cast<ShellContext*>(data);
AutoEnterOOMUnsafeRegion oomUnsafe; if (!sc->finalizationRegistryCleanupCallbacks.append(doCleanup)) {
oomUnsafe.crash("ShellCleanupFinalizationRegistryCallback");
}
}
// Run any FinalizationRegistry cleanup tasks and return whether any ran. staticbool MaybeRunFinalizationRegistryCleanupTasks(JSContext* cx) {
ShellContext* sc = GetShellContext(cx);
MOZ_ASSERT(!sc->quitting);
#ifdefined(DEBUG) || defined(JS_OOM_BREAKPOINT) if (cx->runningOOMTest) { // When OOM happens, we cannot reliably track the set of unhandled // promise rejections. Throw error only when simulated OOM is used // *and* promises are used in the test.
JS_ReportErrorASCII(
cx, "Can't track unhandled rejections while running simulated OOM " "test. Call ignoreUnhandledRejections before using oomTest etc."); returnfalse;
} #endif
if (!sc->unhandledRejectedPromises) {
sc->unhandledRejectedPromises = SetObject::create(cx); if (!sc->unhandledRejectedPromises) { returnfalse;
}
}
AutoRealm ar(cx, sc->unhandledRejectedPromises); if (!cx->compartment()->wrap(cx, &promiseVal)) { returnfalse;
}
switch (state) { case JS::PromiseRejectionHandlingState::Unhandled: if (!sc->unhandledRejectedPromises->add(cx, promiseVal)) { returnfalse;
} break; case JS::PromiseRejectionHandlingState::Handled: bool deleted = false; if (!sc->unhandledRejectedPromises->delete_(cx, promiseVal, &deleted)) { returnfalse;
} // We can't MOZ_ASSERT(deleted) here, because it's possible we failed to // add the promise in the first place, due to OOM. break;
}
if (!IsFunctionObject(args.get(0))) {
JS_ReportErrorASCII(
cx, "setPromiseRejectionTrackerCallback expects a function as its sole " "argument"); returnfalse;
}
// clang-format off staticconstchar* telemetryNames[static_cast<int>(JSMetric::Count)] = { #define LIT(NAME, _) #NAME,
FOR_EACH_JS_METRIC(LIT) #undef LIT
}; // clang-format on
// Telemetry can be executed from multiple threads, and the callback is // responsible to avoid contention on the recorded telemetry data. static Mutex* telemetryLock = nullptr; class MOZ_RAII AutoLockTelemetry : public LockGuard<Mutex> { using Base = LockGuard<Mutex>;
for (size_t id = 0; id < size_t(JSMetric::Count); id++) { auto clear = MakeScopeExit([&] { telemetryResults[id].clearAndFree(); }); if (!initOutput(telemetryNames[id])) { continue;
} for (uint32_t data : telemetryResults[id]) {
output.printf("%u\n", data);
}
output.finish();
}
}
#undef MAP_TELEMETRY
// Use Counter introspection
MOZ_RUNINIT static Mutex useCounterLock(mutexid::ShellUseCounters); class MOZ_RAII AutoLockUseCounters : public LockGuard<Mutex> { using Base = LockGuard<Mutex>;
// Make a private copy holding the lock then release, because we can't // hold this mutex while doing JS_DefineProperty, which holds MemoryTracker // mutex.
UseCounterArray local;
{
AutoLockUseCounters aluc;
local = useCounterResults;
}
if (!JS_GetProperty(cx, options, "stack", &v)) { returnfalse;
} if (!v.isObject() || !v.toObject().is<SavedFrame>()) {
JS_ReportErrorASCII(cx, "The 'stack' property must be a SavedFrame object."); returnfalse;
}
stack = &v.toObject().as<SavedFrame>();
if (!JS_GetProperty(cx, options, "cause", &v)) { returnfalse;
}
RootedString causeString(cx, ToString(cx, v)); if (!causeString) { returnfalse;
}
UniqueChars cause = JS_EncodeStringToUTF8(cx, causeString); if (!cause) {
MOZ_ASSERT(cx->isExceptionPending()); returnfalse;
}
do { /* * Accumulate lines until we get a 'compilable unit' - one that either * generates an error (before running out of source) or that compiles * cleanly. This should be whenever we get a complete statement that * coincides with the end of a line.
*/ int startline = lineno; using CharBuffer = Vector<char, 32>;
RootedObject globalLexical(cx, &cx->global()->lexicalEnvironment());
CharBuffer buffer(cx); do {
ScheduleWatchdog(cx, -1);
sc->serviceInterrupt = false;
errno = 0;
mozilla::UniqueFreePtr<char[]> line =
GetLine(in, startline == lineno ? "js> " : ""); if (!line) { if (errno) { if (UniqueChars error = SystemErrorMessage(cx, errno)) {
JS_ReportErrorUTF8(cx, "%s", error.get());
} returnfalse;
}
hitEOF = true; break;
}
if (!buffer.append(line.get(), strlen(line.get())) ||
!buffer.append('\n')) { returnfalse;
}
lineno++; if (!ScheduleWatchdog(cx, sc->timeoutInterval)) {
hitEOF = true; break;
}
} while (!JS_Utf8BufferIsCompilableUnit(cx, cx->global(), buffer.begin(),
buffer.length()));
// If a let or const fail to initialize they will remain in an unusable // without further intervention. This call cleans up the global scope, // setting uninitialized lexicals to undefined so that they may still // be used. This behavior is _only_ acceptable in the context of the repl. if (JS::ForceLexicalInitialization(cx, globalLexical) &&
gErrFile->isOpen()) {
fputs( "Warning: According to the standard, after the above exception,\n" "Warning: the global bindings should be permanently uninitialized.\n" "Warning: We have non-standard-ly initialized them to `undefined`" "for you.\nWarning: This nicety only happens in the JS shell.\n",
stderr);
}
RunShellJobs(cx);
} while (!hitEOF && !sc->quitting);
if (gOutFile->isOpen()) {
fprintf(gOutFile->fp, "\n");
}
returntrue;
}
enum FileKind {
PreludeScript, // UTF-8 script, fully-parsed, to avoid conflicting // configurations.
FileScript, // UTF-8, directly parsed as such
FileScriptUtf16, // FileScript, but inflate to UTF-16 before parsing
FileModule,
};
RootedString rawFilenameStr(cx, JS::ToString(cx, args[0])); if (!rawFilenameStr) { returnfalse;
} // It's a little bizarre to resolve relative to the script, but for testing // I need a file at a known location, and the only good way I know of to do // that right now is to include it in the repo alongside the test script. // Bug 944164 would introduce an alternative.
Rooted<JSString*> filenameStr(
cx, ResolvePath(cx, rawFilenameStr, ScriptRelative)); if (!filenameStr) { returnfalse;
}
UniqueChars filename = JS_EncodeStringToUTF8(cx, filenameStr); if (!filename) { returnfalse;
}
uint32_t offset = 0; if (args.length() >= 2) { if (!JS::ToUint32(cx, args[1], &offset)) { returnfalse;
}
}
struct stat st; if (fstat(fileno(file), &st) < 0) {
JS_ReportErrorASCII(cx, "Unable to stat file"); returnfalse;
}
if ((st.st_mode & S_IFMT) != S_IFREG) {
JS_ReportErrorASCII(cx, "Path is not a regular file"); returnfalse;
}
if (!sizeGiven) { if (off_t(offset) >= st.st_size) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_OFFSET_LARGER_THAN_FILESIZE); returnfalse;
}
size = st.st_size - offset;
}
void* contents =
JS::CreateMappedArrayBufferContents(GET_FD_FROM_FILE(file), offset, size); if (!contents) {
JS_ReportErrorASCII(cx, "failed to allocate mapped array buffer contents " "(possibly due to bad alignment)"); returnfalse;
}
RootedObject obj(cx,
JS::NewMappedArrayBufferWithContents(cx, size, contents)); if (!obj) { returnfalse;
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.62Bemerkung:
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.