Source code

Revision control

Other Tools

/* -*- 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/. */
/*
* JS execution context.
*/
#include "vm/JSContext-inl.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/Sprintf.h"
#include "mozilla/TextUtils.h"
#include "mozilla/Unused.h"
#include "mozilla/Utf8.h" // mozilla::ConvertUtf16ToUtf8
#include <stdarg.h>
#include <string.h>
#ifdef ANDROID
# include <android/log.h>
# include <fstream>
# include <string>
#endif // ANDROID
#ifdef XP_WIN
# include <processthreadsapi.h>
#endif // XP_WIN
#include "jsexn.h"
#include "jspubtd.h"
#include "jstypes.h"
#include "gc/FreeOp.h"
#include "gc/Marking.h"
#include "gc/PublicIterators.h"
#include "irregexp/RegExpAPI.h"
#include "jit/Ion.h"
#include "jit/PcScriptCache.h"
#include "jit/Simulator.h"
#include "js/CharacterEncoding.h"
#include "js/ContextOptions.h" // JS::ContextOptions
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/friend/StackLimits.h" // js::ReportOverRecursed
#include "js/Printf.h"
#include "util/DiagnosticAssertions.h"
#include "util/DifferentialTesting.h"
#include "util/DoubleToString.h"
#include "util/NativeStack.h"
#include "util/Text.h"
#include "util/Windows.h"
#include "vm/BytecodeUtil.h" // JSDVG_IGNORE_STACK
#include "vm/ErrorObject.h"
#include "vm/ErrorReporting.h"
#include "vm/HelperThreadState.h"
#include "vm/Iteration.h"
#include "vm/JSAtom.h"
#include "vm/JSFunction.h"
#include "vm/JSObject.h"
#include "vm/JSScript.h"
#include "vm/PlainObject.h" // js::PlainObject
#include "vm/Realm.h"
#include "vm/Shape.h"
#include "vm/StringType.h" // StringToNewUTF8CharsZ
#include "vm/ToSource.h" // js::ValueToSource
#include "vm/WellKnownAtom.h" // js_*_str
#include "vm/Compartment-inl.h"
#include "vm/JSObject-inl.h"
#include "vm/JSScript-inl.h"
#include "vm/Stack-inl.h"
using namespace js;
using mozilla::DebugOnly;
using mozilla::PodArrayZero;
#ifdef DEBUG
JSContext* js::MaybeGetJSContext() {
if (!TlsContext.init()) {
return nullptr;
}
return TlsContext.get();
}
#endif
bool js::AutoCycleDetector::init() {
MOZ_ASSERT(cyclic);
AutoCycleDetector::Vector& vector = cx->cycleDetectorVector();
for (JSObject* obj2 : vector) {
if (MOZ_UNLIKELY(obj == obj2)) {
return true;
}
}
if (!vector.append(obj)) {
return false;
}
cyclic = false;
return true;
}
js::AutoCycleDetector::~AutoCycleDetector() {
if (MOZ_LIKELY(!cyclic)) {
AutoCycleDetector::Vector& vec = cx->cycleDetectorVector();
MOZ_ASSERT(vec.back() == obj);
if (vec.length() > 1) {
vec.popBack();
} else {
// Avoid holding on to unused heap allocations.
vec.clearAndFree();
}
}
}
bool JSContext::init(ContextKind kind) {
// Skip most of the initialization if this thread will not be running JS.
if (kind == ContextKind::MainThread) {
TlsContext.set(this);
currentThread_ = ThreadId::ThisThreadId();
if (!fx.initInstance()) {
return false;
}
#ifdef JS_SIMULATOR
simulator_ = jit::Simulator::Create();
if (!simulator_) {
return false;
}
#endif
} else {
atomsZoneFreeLists_ = js_new<gc::FreeLists>();
if (!atomsZoneFreeLists_) {
return false;
}
}
isolate = irregexp::CreateIsolate(this);
if (!isolate) {
return false;
}
// Set the ContextKind last, so that ProtectedData checks will allow us to
// initialize this context before it becomes the runtime's active context.
kind_ = kind;
return true;
}
JSContext* js::NewContext(uint32_t maxBytes, JSRuntime* parentRuntime) {
AutoNoteSingleThreadedRegion anstr;
MOZ_RELEASE_ASSERT(!TlsContext.get());
#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
js::oom::SetThreadType(!parentRuntime ? js::THREAD_TYPE_MAIN
: js::THREAD_TYPE_WORKER);
#endif
JSRuntime* runtime = js_new<JSRuntime>(parentRuntime);
if (!runtime) {
return nullptr;
}
JSContext* cx = js_new<JSContext>(runtime, JS::ContextOptions());
if (!cx) {
js_delete(runtime);
return nullptr;
}
if (!cx->init(ContextKind::MainThread)) {
js_delete(cx);
js_delete(runtime);
return nullptr;
}
if (!runtime->init(cx, maxBytes)) {
runtime->destroyRuntime();
js_delete(cx);
js_delete(runtime);
return nullptr;
}
return cx;
}
void js::DestroyContext(JSContext* cx) {
JS_AbortIfWrongThread(cx);
cx->checkNoGCRooters();
// Cancel all off thread Ion compiles. Completed Ion compiles may try to
// interrupt this context. See HelperThread::handleIonWorkload.
CancelOffThreadIonCompile(cx->runtime());
cx->jobQueue = nullptr;
cx->internalJobQueue = nullptr;
SetContextProfilingStack(cx, nullptr);
JSRuntime* rt = cx->runtime();
// Flush promise tasks executing in helper threads early, before any parts
// of the JSRuntime that might be visible to helper threads are torn down.
rt->offThreadPromiseState.ref().shutdown(cx);
// Destroy the runtime along with its last context.
js::AutoNoteSingleThreadedRegion nochecks;
rt->destroyRuntime();
js_delete_poison(cx);
js_delete_poison(rt);
}
void JS::RootingContext::checkNoGCRooters() {
#ifdef DEBUG
for (auto const& stackRootPtr : stackRoots_) {
MOZ_ASSERT(stackRootPtr == nullptr);
}
#endif
}
bool AutoResolving::alreadyStartedSlow() const {
MOZ_ASSERT(link);
AutoResolving* cursor = link;
do {
MOZ_ASSERT(this != cursor);
if (object.get() == cursor->object && id.get() == cursor->id &&
kind == cursor->kind) {
return true;
}
} while (!!(cursor = cursor->link));
return false;
}
/*
* Since memory has been exhausted, avoid the normal error-handling path which
* allocates an error object, report and callstack. If code is running, simply
* throw the static atom "out of memory". If code is not running, call the
* error reporter directly.
*
* Furthermore, callers of ReportOutOfMemory (viz., malloc) assume a GC does
* not occur, so GC must be avoided or suppressed.
*/
JS_FRIEND_API void js::ReportOutOfMemory(JSContext* cx) {
/*
* OOMs are non-deterministic, especially across different execution modes
* (e.g. interpreter vs JIT). When doing differential testing, print to stderr
* so that the fuzzers can detect this.
*/
if (js::SupportDifferentialTesting()) {
fprintf(stderr, "ReportOutOfMemory called\n");
}
if (cx->isHelperThreadContext()) {
return cx->addPendingOutOfMemory();
}
cx->runtime()->hadOutOfMemory = true;
gc::AutoSuppressGC suppressGC(cx);
/* Report the oom. */
if (JS::OutOfMemoryCallback oomCallback = cx->runtime()->oomCallback) {
oomCallback(cx, cx->runtime()->oomCallbackData);
}
// If we OOM early in process startup, this may be unavailable so just return
// instead of crashing unexpectedly.
if (MOZ_UNLIKELY(!cx->runtime()->hasInitializedSelfHosting())) {
return;
}
RootedValue oomMessage(cx, StringValue(cx->names().outOfMemory));
cx->setPendingException(oomMessage, nullptr);
}
mozilla::GenericErrorResult<OOM> js::ReportOutOfMemoryResult(JSContext* cx) {
ReportOutOfMemory(cx);
return cx->alreadyReportedOOM();
}
void js::ReportOverRecursed(JSContext* maybecx, unsigned errorNumber) {
/*
* We cannot make stack depth deterministic across different
* implementations (e.g. JIT vs. interpreter will differ in
* their maximum stack depth).
* However, we can detect externally when we hit the maximum
* stack depth which is useful for external testing programs
* like fuzzers.
*/
if (js::SupportDifferentialTesting()) {
fprintf(stderr, "ReportOverRecursed called\n");
}
if (maybecx) {
if (!maybecx->isHelperThreadContext()) {
JS_ReportErrorNumberASCII(maybecx, GetErrorMessage, nullptr, errorNumber);
maybecx->overRecursed_ = true;
} else {
maybecx->addPendingOverRecursed();
}
#ifdef DEBUG
maybecx->hadOverRecursed_ = true;
#endif
}
}
JS_FRIEND_API void js::ReportOverRecursed(JSContext* maybecx) {
ReportOverRecursed(maybecx, JSMSG_OVER_RECURSED);
}
void js::ReportAllocationOverflow(JSContext* cx) {
if (!cx) {
return;
}
if (cx->isHelperThreadContext()) {
return;
}
gc::AutoSuppressGC suppressGC(cx);
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ALLOC_OVERFLOW);
}
/* |callee| requires a usage string provided by JS_DefineFunctionsWithHelp. */
void js::ReportUsageErrorASCII(JSContext* cx, HandleObject callee,
const char* msg) {
RootedValue usage(cx);
if (!JS_GetProperty(cx, callee, "usage", &usage)) {
return;
}
if (!usage.isString()) {
JS_ReportErrorASCII(cx, "%s", msg);
} else {
RootedString usageStr(cx, usage.toString());
UniqueChars str = JS_EncodeStringToUTF8(cx, usageStr);
if (!str) {
return;
}
JS_ReportErrorUTF8(cx, "%s. Usage: %s", msg, str.get());
}
}
enum class PrintErrorKind { Error, Warning, Note };
static void PrintErrorLine(FILE* file, const char* prefix,
JSErrorReport* report) {
if (const char16_t* linebuf = report->linebuf()) {
UniqueChars line;
size_t n;
{
size_t linebufLen = report->linebufLength();
// This function is only used for shell command-line sorts of stuff where
// performance doesn't really matter, so just encode into max-sized
// memory.
mozilla::CheckedInt<size_t> utf8Len(linebufLen);
utf8Len *= 3;
if (utf8Len.isValid()) {
line = UniqueChars(js_pod_malloc<char>(utf8Len.value()));
if (line) {
n = mozilla::ConvertUtf16toUtf8({linebuf, linebufLen},
{line.get(), utf8Len.value()});
}
}
}
const char* utf8buf;
if (line) {
utf8buf = line.get();
} else {
static const char unavailableStr[] = "<context unavailable>";
utf8buf = unavailableStr;
n = js_strlen(unavailableStr);
}
fputs(":\n", file);
if (prefix) {
fputs(prefix, file);
}
for (size_t i = 0; i < n; i++) {
fputc(utf8buf[i], file);
}
// linebuf/utf8buf usually ends with a newline. If not, add one here.
if (n == 0 || utf8buf[n - 1] != '\n') {
fputc('\n', file);
}
if (prefix) {
fputs(prefix, file);
}
n = report->tokenOffset();
for (size_t i = 0, j = 0; i < n; i++) {
if (utf8buf[i] == '\t') {
for (size_t k = (j + 8) & ~7; j < k; j++) {
fputc('.', file);
}
continue;
}
fputc('.', file);
j++;
}
fputc('^', file);
}
}
static void PrintErrorLine(FILE* file, const char* prefix,
JSErrorNotes::Note* note) {}
template <typename T>
static void PrintSingleError(JSContext* cx, FILE* file,
JS::ConstUTF8CharsZ toStringResult, T* report,
PrintErrorKind kind) {
UniqueChars prefix;
if (report->filename) {
prefix = JS_smprintf("%s:", report->filename);
}
if (report->lineno) {
prefix = JS_smprintf("%s%u:%u ", prefix ? prefix.get() : "", report->lineno,
report->column);
}
if (kind != PrintErrorKind::Error) {
const char* kindPrefix = nullptr;
switch (kind) {
case PrintErrorKind::Error:
MOZ_CRASH("unreachable");
case PrintErrorKind::Warning:
kindPrefix = "warning";
break;
case PrintErrorKind::Note:
kindPrefix = "note";
break;
}
prefix = JS_smprintf("%s%s: ", prefix ? prefix.get() : "", kindPrefix);
}
const char* message =
toStringResult ? toStringResult.c_str() : report->message().c_str();
/* embedded newlines -- argh! */
const char* ctmp;
while ((ctmp = strchr(message, '\n')) != 0) {
ctmp++;
if (prefix) {
fputs(prefix.get(), file);
}
mozilla::Unused << fwrite(message, 1, ctmp - message, file);
message = ctmp;
}
/* If there were no filename or lineno, the prefix might be empty */
if (prefix) {
fputs(prefix.get(), file);
}
fputs(message, file);
PrintErrorLine(file, prefix.get(), report);
fputc('\n', file);
fflush(file);
}
static void PrintErrorImpl(JSContext* cx, FILE* file,
JS::ConstUTF8CharsZ toStringResult,
JSErrorReport* report, bool reportWarnings) {
MOZ_ASSERT(report);
/* Conditionally ignore reported warnings. */
if (report->isWarning() && !reportWarnings) {
return;
}
PrintErrorKind kind = PrintErrorKind::Error;
if (report->isWarning()) {
kind = PrintErrorKind::Warning;
}
PrintSingleError(cx, file, toStringResult, report, kind);
if (report->notes) {
for (auto&& note : *report->notes) {
PrintSingleError(cx, file, JS::ConstUTF8CharsZ(), note.get(),
PrintErrorKind::Note);
}
}
}
JS_PUBLIC_API void JS::PrintError(JSContext* cx, FILE* file,
JSErrorReport* report, bool reportWarnings) {
PrintErrorImpl(cx, file, JS::ConstUTF8CharsZ(), report, reportWarnings);
}
JS_PUBLIC_API void JS::PrintError(JSContext* cx, FILE* file,
const JS::ErrorReportBuilder& builder,
bool reportWarnings) {
PrintErrorImpl(cx, file, builder.toStringResult(), builder.report(),
reportWarnings);
}
void js::ReportIsNotDefined(JSContext* cx, HandleId id) {
if (UniqueChars printable =
IdToPrintableUTF8(cx, id, IdToPrintableBehavior::IdIsIdentifier)) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_NOT_DEFINED,
printable.get());
}
}
void js::ReportIsNotDefined(JSContext* cx, HandlePropertyName name) {
RootedId id(cx, NameToId(name));
ReportIsNotDefined(cx, id);
}
const char* NullOrUndefinedToCharZ(HandleValue v) {
MOZ_ASSERT(v.isNullOrUndefined());
return v.isNull() ? js_null_str : js_undefined_str;
}
void js::ReportIsNullOrUndefinedForPropertyAccess(JSContext* cx, HandleValue v,
int vIndex) {
MOZ_ASSERT(v.isNullOrUndefined());
if (vIndex == JSDVG_IGNORE_STACK) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_CANT_CONVERT_TO, NullOrUndefinedToCharZ(v),
"object");
return;
}
UniqueChars bytes = DecompileValueGenerator(cx, vIndex, v, nullptr);
if (!bytes) {
return;
}
if (strcmp(bytes.get(), js_undefined_str) == 0 ||
strcmp(bytes.get(), js_null_str) == 0) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_NO_PROPERTIES,
bytes.get());
} else {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_UNEXPECTED_TYPE, bytes.get(),
NullOrUndefinedToCharZ(v));
}
}
void js::ReportIsNullOrUndefinedForPropertyAccess(JSContext* cx, HandleValue v,
int vIndex, HandleId key) {
MOZ_ASSERT(v.isNullOrUndefined());
if (!cx->realm()->creationOptions().getPropertyErrorMessageFixEnabled()) {
ReportIsNullOrUndefinedForPropertyAccess(cx, v, vIndex);
return;
}
RootedValue idVal(cx, IdToValue(key));
RootedString idStr(cx, ValueToSource(cx, idVal));
if (!idStr) {
return;
}
UniqueChars keyStr = StringToNewUTF8CharsZ(cx, *idStr);
if (!keyStr) {
return;
}
if (vIndex == JSDVG_IGNORE_STACK) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_PROPERTY_FAIL,
keyStr.get(), NullOrUndefinedToCharZ(v));
return;
}
UniqueChars bytes = DecompileValueGenerator(cx, vIndex, v, nullptr);
if (!bytes) {
return;
}
if (strcmp(bytes.get(), js_undefined_str) == 0 ||
strcmp(bytes.get(), js_null_str) == 0) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_PROPERTY_FAIL,
keyStr.get(), bytes.get());
return;
}
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_PROPERTY_FAIL_EXPR, keyStr.get(), bytes.get(),
NullOrUndefinedToCharZ(v));
}
bool js::ReportValueError(JSContext* cx, const unsigned errorNumber,
int spindex, HandleValue v, HandleString fallback,
const char* arg1, const char* arg2) {
MOZ_ASSERT(js_ErrorFormatString[errorNumber].argCount >= 1);
MOZ_ASSERT(js_ErrorFormatString[errorNumber].argCount <= 3);
UniqueChars bytes = DecompileValueGenerator(cx, spindex, v, fallback);
if (!bytes) {
return false;
}
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber,
bytes.get(), arg1, arg2);
return false;
}
JSObject* js::CreateErrorNotesArray(JSContext* cx, JSErrorReport* report) {
RootedArrayObject notesArray(cx, NewDenseEmptyArray(cx));
if (!notesArray) {
return nullptr;
}
if (!report->notes) {
return notesArray;
}
for (auto&& note : *report->notes) {
RootedPlainObject noteObj(cx, NewBuiltinClassInstance<PlainObject>(cx));
if (!noteObj) {
return nullptr;
}
RootedString messageStr(cx, note->newMessageString(cx));
if (!messageStr) {
return nullptr;
}
RootedValue messageVal(cx, StringValue(messageStr));
if (!DefineDataProperty(cx, noteObj, cx->names().message, messageVal)) {
return nullptr;
}
RootedValue filenameVal(cx);
if (note->filename) {
RootedString filenameStr(cx, NewStringCopyZ<CanGC>(cx, note->filename));
if (!filenameStr) {
return nullptr;
}
filenameVal = StringValue(filenameStr);
}
if (!DefineDataProperty(cx, noteObj, cx->names().fileName, filenameVal)) {
return nullptr;
}
RootedValue linenoVal(cx, Int32Value(note->lineno));
if (!DefineDataProperty(cx, noteObj, cx->names().lineNumber, linenoVal)) {
return nullptr;
}
RootedValue columnVal(cx, Int32Value(note->column));
if (!DefineDataProperty(cx, noteObj, cx->names().columnNumber, columnVal)) {
return nullptr;
}
if (!NewbornArrayPush(cx, notesArray, ObjectValue(*noteObj))) {
return nullptr;
}
}
return notesArray;
}
void JSContext::recoverFromOutOfMemory() {
if (isHelperThreadContext()) {
// Keep in sync with addPendingOutOfMemory.
if (ParseTask* task = parseTask()) {
task->outOfMemory = false;
}
} else {
if (isExceptionPending()) {
MOZ_ASSERT(isThrowingOutOfMemory());
clearPendingException();
}
}
}
JS_FRIEND_API bool js::UseInternalJobQueues(JSContext* cx) {
// Internal job queue handling must be set up very early. Self-hosting
// initialization is as good a marker for that as any.
MOZ_RELEASE_ASSERT(
!cx->runtime()->hasInitializedSelfHosting(),
"js::UseInternalJobQueues must be called early during runtime startup.");
MOZ_ASSERT(!cx->jobQueue);
auto queue = MakeUnique<InternalJobQueue>(cx);
if (!queue) {
return false;
}
cx->internalJobQueue = std::move(queue);
cx->jobQueue = cx->internalJobQueue.ref().get();
cx->runtime()->offThreadPromiseState.ref().initInternalDispatchQueue();
MOZ_ASSERT(cx->runtime()->offThreadPromiseState.ref().initialized());
return true;
}
JS_FRIEND_API bool js::EnqueueJob(JSContext* cx, JS::HandleObject job) {
MOZ_ASSERT(cx->jobQueue);
return cx->jobQueue->enqueuePromiseJob(cx, nullptr, job, nullptr, nullptr);
}
JS_FRIEND_API void js::StopDrainingJobQueue(JSContext* cx) {
MOZ_ASSERT(cx->internalJobQueue.ref());
cx->internalJobQueue->interrupt();
}
JS_FRIEND_API void js::RunJobs(JSContext* cx) {
MOZ_ASSERT(cx->jobQueue);
cx->jobQueue->runJobs(cx);
JS::ClearKeptObjects(cx);
}
JSObject* InternalJobQueue::getIncumbentGlobal(JSContext* cx) {
if (!cx->compartment()) {
return nullptr;
}
return cx->global();
}
bool InternalJobQueue::enqueuePromiseJob(JSContext* cx,
JS::HandleObject promise,
JS::HandleObject job,
JS::HandleObject allocationSite,
JS::HandleObject incumbentGlobal) {
MOZ_ASSERT(job);
if (!queue.pushBack(job)) {
ReportOutOfMemory(cx);
return false;
}
JS::JobQueueMayNotBeEmpty(cx);
return true;
}
void InternalJobQueue::runJobs(JSContext* cx) {
if (draining_ || interrupted_) {
return;
}
while (true) {
cx->runtime()->offThreadPromiseState.ref().internalDrain(cx);
// It doesn't make sense for job queue draining to be reentrant. At the
// same time we don't want to assert against it, because that'd make
// drainJobQueue unsafe for fuzzers. We do want fuzzers to test this,
// so we simply ignore nested calls of drainJobQueue.
draining_ = true;
RootedObject job(cx);
JS::HandleValueArray args(JS::HandleValueArray::empty());
RootedValue rval(cx);
// Execute jobs in a loop until we've reached the end of the queue.
while (!queue.empty()) {
// A previous job might have set this flag. E.g., the js shell
// sets it if the `quit` builtin function is called.
if (interrupted_) {
break;
}
job = queue.front();
queue.popFront();
// If the next job is the last job in the job queue, allow
// skipping the standard job queuing behavior.
if (queue.empty()) {
JS::JobQueueIsEmpty(cx);
}
AutoRealm ar(cx, &job->as<JSFunction>());
{
if (!JS::Call(cx, UndefinedHandleValue, job, args, &rval)) {
// Nothing we can do about uncatchable exceptions.
if (!cx->isExceptionPending()) {
continue;
}
RootedValue exn(cx);
if (cx->getPendingException(&exn)) {
/*
* Clear the exception, because
* PrepareScriptEnvironmentAndInvoke will assert that we don't
* have one.
*/
cx->clearPendingException();
js::ReportExceptionClosure reportExn(exn);
PrepareScriptEnvironmentAndInvoke(cx, cx->global(), reportExn);
}
}
}
}
draining_ = false;
if (interrupted_) {
interrupted_ = false;
break;
}
queue.clear();
// It's possible a job added a new off-thread promise task.
if (!cx->runtime()->offThreadPromiseState.ref().internalHasPending()) {
break;
}
}
}
bool InternalJobQueue::empty() const { return queue.empty(); }
JSObject* InternalJobQueue::maybeFront() const {
if (queue.empty()) {
return nullptr;
}
return queue.get().front();
}
class js::InternalJobQueue::SavedQueue : public JobQueue::SavedJobQueue {
public:
SavedQueue(JSContext* cx, Queue&& saved, bool draining)
: cx(cx), saved(cx, std::move(saved)), draining_(draining) {
MOZ_ASSERT(cx->internalJobQueue.ref());
}
~SavedQueue() {
MOZ_ASSERT(cx->internalJobQueue.ref());
cx->internalJobQueue->queue = std::move(saved.get());
cx->internalJobQueue->draining_ = draining_;
}
private:
JSContext* cx;
PersistentRooted<Queue> saved;
bool draining_;
};
js::UniquePtr<JS::JobQueue::SavedJobQueue> InternalJobQueue::saveJobQueue(
JSContext* cx) {
auto saved =
js::MakeUnique<SavedQueue>(cx, std::move(queue.get()), draining_);
if (!saved) {
// When MakeUnique's allocation fails, the SavedQueue constructor is never
// called, so this->queue is still initialized. (The move doesn't occur
// until the constructor gets called.)
ReportOutOfMemory(cx);
return nullptr;
}
queue = Queue(SystemAllocPolicy());
draining_ = false;
return saved;
}
mozilla::GenericErrorResult<OOM> JSContext::alreadyReportedOOM() {
#ifdef DEBUG
if (isHelperThreadContext()) {
// Keep in sync with addPendingOutOfMemory.
if (ParseTask* task = parseTask()) {
MOZ_ASSERT(task->outOfMemory);
}
} else {
MOZ_ASSERT(isThrowingOutOfMemory());
}
#endif
return mozilla::Err(JS::OOM());
}
mozilla::GenericErrorResult<JS::Error> JSContext::alreadyReportedError() {
return mozilla::Err(JS::Error());
}
JSContext::JSContext(JSRuntime* runtime, const JS::ContextOptions& options)
: runtime_(runtime),
kind_(ContextKind::Uninitialized),
nurserySuppressions_(this),
options_(this, options),
freeLists_(this, nullptr),
atomsZoneFreeLists_(this),
defaultFreeOp_(this, runtime, true),
freeUnusedMemory(false),
measuringExecutionTime_(this, false),
jitActivation(this, nullptr),
isolate(this, nullptr),
activation_(this, nullptr),
profilingActivation_(nullptr),
nativeStackBase(GetNativeStackBase()),
entryMonitor(this, nullptr),
noExecuteDebuggerTop(this, nullptr),
#ifdef DEBUG
inUnsafeCallWithABI(this, false),
hasAutoUnsafeCallWithABI(this, false),
#endif
#ifdef JS_SIMULATOR
simulator_(this, nullptr),
#endif
#ifdef JS_TRACE_LOGGING
traceLogger(nullptr),
#endif
dtoaState(this, nullptr),
suppressGC(this, 0),
#ifdef DEBUG
gcUse(this, GCUse::None),
gcSweepZone(this, nullptr),
isTouchingGrayThings(this, false),
noNurseryAllocationCheck(this, 0),
disableStrictProxyCheckingCount(this, 0),
#endif
#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
runningOOMTest(this, false),
#endif
inUnsafeRegion(this, 0),
generationalDisabled(this, 0),
compactingDisabledCount(this, 0),
frontendCollectionPool_(this),
suppressProfilerSampling(false),
tempLifoAlloc_(this, (size_t)TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
debuggerMutations(this, 0),
ionPcScriptCache(this, nullptr),
throwing(this, false),
unwrappedException_(this),
unwrappedExceptionStack_(this),
overRecursed_(this, false),
#ifdef DEBUG
hadOverRecursed_(this, false),
#endif
propagatingForcedReturn_(this, false),
reportGranularity(this, JS_DEFAULT_JITREPORT_GRANULARITY),
resolvingList(this, nullptr),
#ifdef DEBUG
enteredPolicy(this, nullptr),
#endif
generatingError(this, false),
cycleDetectorVector_(this, this),
data(nullptr),
asyncStackForNewActivations_(this),
asyncCauseForNewActivations(this, nullptr),
asyncCallIsExplicit(this, false),
interruptCallbacks_(this),
interruptCallbackDisabled(this, false),
interruptBits_(0),
inlinedICScript_(this, nullptr),
jitStackLimit(UINTPTR_MAX),
jitStackLimitNoInterrupt(this, UINTPTR_MAX),
jobQueue(this, nullptr),
internalJobQueue(this),
canSkipEnqueuingJobs(this, false),
promiseRejectionTrackerCallback(this, nullptr),
promiseRejectionTrackerCallbackData(this, nullptr),
#ifdef JS_STRUCTURED_SPEW
structuredSpewer_(),
#endif
insideDebuggerEvaluationWithOnNativeCallHook(this, nullptr) {
MOZ_ASSERT(static_cast<JS::RootingContext*>(this) ==
JS::RootingContext::get(this));
}
JSContext::~JSContext() {
// Clear the ContextKind first, so that ProtectedData checks will allow us to
// destroy this context even if the runtime is already gone.
kind_ = ContextKind::Uninitialized;
/* Free the stuff hanging off of cx. */
MOZ_ASSERT(!resolvingList);
if (dtoaState) {
DestroyDtoaState(dtoaState);
}
fx.destroyInstance();
#ifdef JS_SIMULATOR
js::jit::Simulator::Destroy(simulator_);
#endif
#ifdef JS_TRACE_LOGGING
if (traceLogger) {
DestroyTraceLogger(traceLogger);
}
#endif
if (isolate) {
irregexp::DestroyIsolate(isolate.ref());
}
js_delete(atomsZoneFreeLists_.ref());
TlsContext.set(nullptr);
}
void JSContext::setHelperThread(const AutoLockHelperThreadState& locked) {
MOZ_ASSERT(isHelperThreadContext());
MOZ_ASSERT_IF(!JSRuntime::hasLiveRuntimes(), !TlsContext.get());
MOZ_ASSERT(currentThread_ == ThreadId());
TlsContext.set(this);
currentThread_ = ThreadId::ThisThreadId();
}
void JSContext::clearHelperThread(const AutoLockHelperThreadState& locked) {
MOZ_ASSERT(isHelperThreadContext());
MOZ_ASSERT(TlsContext.get() == this);
MOZ_ASSERT(currentThread_ == ThreadId::ThisThreadId());
currentThread_ = ThreadId();
TlsContext.set(nullptr);
}
void JSContext::setRuntime(JSRuntime* rt) {
MOZ_ASSERT(!resolvingList);
MOZ_ASSERT(!compartment());
MOZ_ASSERT(!activation());
MOZ_ASSERT(!unwrappedException_.ref().initialized());
MOZ_ASSERT(!unwrappedExceptionStack_.ref().initialized());
MOZ_ASSERT(!asyncStackForNewActivations_.ref().initialized());
runtime_ = rt;
}
static bool IsOutOfMemoryException(JSContext* cx, const Value& v) {
return v == StringValue(cx->names().outOfMemory);
}
void JSContext::setPendingException(HandleValue v, HandleSavedFrame stack) {
#if defined(NIGHTLY_BUILD)
do {
// Do not intercept exceptions if we are already
// in the exception interceptor. That would lead
// to infinite recursion.
if (this->runtime()->errorInterception.isExecuting) {
break;
}
// Check whether we have an interceptor at all.
if (!this->runtime()->errorInterception.interceptor) {
break;
}
// Don't report OOM exceptions. The interceptor isn't interested in those
// and they can confuse the interceptor because OOM can be thrown when we
// are not in a realm (atom allocation, for example).
if (IsOutOfMemoryException(this, v)) {
break;
}
// Make sure that we do not call the interceptor from within
// the interceptor.
this->runtime()->errorInterception.isExecuting = true;
// The interceptor must be infallible.
const mozilla::DebugOnly<bool> wasExceptionPending =
this->isExceptionPending();
this->runtime()->errorInterception.interceptor->interceptError(this, v);
MOZ_ASSERT(wasExceptionPending == this->isExceptionPending());
this->runtime()->errorInterception.isExecuting = false;
} while (false);
#endif // defined(NIGHTLY_BUILD)
// overRecursed_ is set after the fact by ReportOverRecursed.
this->overRecursed_ = false;
this->throwing = true;
this->unwrappedException() = v;
this->unwrappedExceptionStack() = stack;
}
void JSContext::setPendingExceptionAndCaptureStack(HandleValue value) {
RootedObject stack(this);
if (!CaptureStack(this, &stack)) {
clearPendingException();
}
RootedSavedFrame nstack(this);
if (stack) {
nstack = &stack->as<SavedFrame>();
}
setPendingException(value, nstack);
}
bool JSContext::getPendingException(MutableHandleValue rval) {
MOZ_ASSERT(throwing);
rval.set(unwrappedException());
if (zone()->isAtomsZone()) {
return true;
}
RootedSavedFrame stack(this, unwrappedExceptionStack());
bool wasOverRecursed = overRecursed_;
clearPendingException();
if (!compartment()->wrap(this, rval)) {
return false;
}
this->check(rval);
setPendingException(rval, stack);
overRecursed_ = wasOverRecursed;
return true;
}
SavedFrame* JSContext::getPendingExceptionStack() {
return unwrappedExceptionStack();
}
bool JSContext::isThrowingOutOfMemory() {
return throwing && IsOutOfMemoryException(this, unwrappedException());
}
bool JSContext::isClosingGenerator() {
return throwing && unwrappedException().isMagic(JS_GENERATOR_CLOSING);
}
bool JSContext::isThrowingDebuggeeWouldRun() {
return throwing && unwrappedException().isObject() &&
unwrappedException().toObject().is<ErrorObject>() &&
unwrappedException().toObject().as<ErrorObject>().type() ==
JSEXN_DEBUGGEEWOULDRUN;
}
size_t JSContext::sizeOfExcludingThis(
mozilla::MallocSizeOf mallocSizeOf) const {
/*
* There are other JSContext members that could be measured; the following
* ones have been found by DMD to be worth measuring. More stuff may be
* added later.
*/
return cycleDetectorVector().sizeOfExcludingThis(mallocSizeOf) +
wasm_.sizeOfExcludingThis(mallocSizeOf) +
irregexp::IsolateSizeOfIncludingThis(isolate, mallocSizeOf);
}
size_t JSContext::sizeOfIncludingThis(
mozilla::MallocSizeOf mallocSizeOf) const {
return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf);
}
#ifdef DEBUG
bool JSContext::inAtomsZone() const { return zone_->isAtomsZone(); }
#endif
void JSContext::trace(JSTracer* trc) {
cycleDetectorVector().trace(trc);
geckoProfiler().trace(trc);
}
uintptr_t JSContext::stackLimitForJitCode(JS::StackKind kind) {
#ifdef JS_SIMULATOR
return simulator()->stackLimit();
#else
return stackLimit(kind);
#endif
}
void JSContext::resetJitStackLimit() {
// Note that, for now, we use the untrusted limit for ion. This is fine,
// because it's the most conservative limit, and if we hit it, we'll bail
// out of ion into the interpreter, which will do a proper recursion check.
#ifdef JS_SIMULATOR
jitStackLimit = jit::Simulator::StackLimit();
#else
jitStackLimit = nativeStackLimit[JS::StackForUntrustedScript];
#endif
jitStackLimitNoInterrupt = jitStackLimit;
}
void JSContext::initJitStackLimit() { resetJitStackLimit(); }
#ifdef JS_CRASH_DIAGNOSTICS
void ContextChecks::check(AbstractFramePtr frame, int argIndex) {
if (frame) {
check(frame.realm(), argIndex);
}
}
#endif
void AutoEnterOOMUnsafeRegion::crash(const char* reason) {
char msgbuf[1024];
js::NoteIntentionalCrash();
SprintfLiteral(msgbuf, "[unhandlable oom] %s", reason);
#ifndef DEBUG
// In non-DEBUG builds MOZ_CRASH normally doesn't print to stderr so we have
// to do this explicitly (the jit-test allow-unhandlable-oom annotation and
// fuzzers depend on it).
MOZ_ReportCrash(msgbuf, __FILE__, __LINE__);
#endif
MOZ_CRASH_UNSAFE(msgbuf);
}
mozilla::Atomic<AutoEnterOOMUnsafeRegion::AnnotateOOMAllocationSizeCallback,
mozilla::Relaxed>
AutoEnterOOMUnsafeRegion::annotateOOMSizeCallback(nullptr);
void AutoEnterOOMUnsafeRegion::crash(size_t size, const char* reason) {
{
JS::AutoSuppressGCAnalysis suppress;
if (annotateOOMSizeCallback) {
annotateOOMSizeCallback(size);
}
}
crash(reason);
}
void ExternalValueArray::trace(JSTracer* trc) {
if (Value* vp = begin()) {
TraceRootRange(trc, length(), vp, "js::ExternalValueArray");
}
}
#ifdef DEBUG
AutoUnsafeCallWithABI::AutoUnsafeCallWithABI(UnsafeABIStrictness strictness)
: cx_(TlsContext.get()),
nested_(cx_ ? cx_->hasAutoUnsafeCallWithABI : false),
nogc(cx_) {
if (!cx_) {
// This is a helper thread doing Ion or Wasm compilation - nothing to do.
return;
}
switch (strictness) {
case UnsafeABIStrictness::NoExceptions:
MOZ_ASSERT(!JS_IsExceptionPending(cx_));
checkForPendingException_ = true;
break;
case UnsafeABIStrictness::AllowPendingExceptions:
checkForPendingException_ = !JS_IsExceptionPending(cx_);
break;
case UnsafeABIStrictness::AllowThrownExceptions:
checkForPendingException_ = false;
break;
}
cx_->hasAutoUnsafeCallWithABI = true;
}
AutoUnsafeCallWithABI::~AutoUnsafeCallWithABI() {
if (!cx_) {
return;
}
MOZ_ASSERT(cx_->hasAutoUnsafeCallWithABI);
if (!nested_) {
cx_->hasAutoUnsafeCallWithABI = false;
cx_->inUnsafeCallWithABI = false;
}
MOZ_ASSERT_IF(checkForPendingException_, !JS_IsExceptionPending(cx_));
}
#endif