Currently, we do this via isRuntimeCodeGenEnabled whose single argument is equivalent to codeString in "Dynamic Code Brand Checks" spec [1]. We extend this hook to accept new parameters from that spec and adjust PerformEval and CreateDynamicFunction accordingly. We don't change the behavior for PerformShadowRealmEval [2] and WASM, i.e. we keep dummy parameters. [1] https://tc39.es/proposal-dynamic-code-brand-checks [2] https://github.com/tc39/proposal-shadowrealm/issues/414 Differential Revision: https://phabricator.services.mozilla.com/D229588
1518 lines
43 KiB
C++
1518 lines
43 KiB
C++
/* -*- 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/Utf8.h" // mozilla::ConvertUtf16ToUtf8
|
|
|
|
#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 "jsapi.h" // JS_SetNativeStackQuota
|
|
#include "jsexn.h"
|
|
#include "jstypes.h"
|
|
|
|
#include "builtin/RegExp.h" // js::RegExpSearcherLastLimitSentinel
|
|
#ifdef MOZ_EXECUTION_TRACING
|
|
# include "debugger/ExecutionTracer.h"
|
|
#endif
|
|
#include "frontend/FrontendContext.h"
|
|
#include "gc/GC.h"
|
|
#include "gc/PublicIterators.h" // js::RealmsIter
|
|
#include "irregexp/RegExpAPI.h"
|
|
#include "jit/Simulator.h"
|
|
#include "js/CallAndConstruct.h" // JS::Call
|
|
#include "js/CharacterEncoding.h"
|
|
#include "js/ContextOptions.h" // JS::ContextOptions
|
|
#include "js/ErrorInterceptor.h" // JSErrorInterceptor
|
|
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
|
|
#include "js/friend/StackLimits.h" // js::ReportOverRecursed
|
|
#include "js/MemoryCallbacks.h"
|
|
#include "js/Prefs.h"
|
|
#include "js/Printf.h"
|
|
#include "js/PropertyAndElement.h" // JS_GetProperty
|
|
#include "js/Stack.h" // JS::NativeStackSize, JS::NativeStackLimit, JS::NativeStackLimitMin
|
|
#include "util/DiagnosticAssertions.h"
|
|
#include "util/DifferentialTesting.h"
|
|
#include "util/DoubleToString.h"
|
|
#include "util/NativeStack.h"
|
|
#include "util/Text.h"
|
|
#include "util/WindowsWrapper.h"
|
|
#include "vm/BytecodeUtil.h" // JSDVG_IGNORE_STACK
|
|
#include "vm/ErrorObject.h"
|
|
#include "vm/ErrorReporting.h"
|
|
#include "vm/FrameIter.h"
|
|
#include "vm/JSFunction.h"
|
|
#include "vm/JSObject.h"
|
|
#include "vm/PlainObject.h" // js::PlainObject
|
|
#include "vm/Realm.h"
|
|
#include "vm/StringType.h" // StringToNewUTF8CharsZ
|
|
#include "vm/ToSource.h" // js::ValueToSource
|
|
|
|
#include "vm/Compartment-inl.h"
|
|
#include "vm/Stack-inl.h"
|
|
|
|
using namespace js;
|
|
|
|
#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() {
|
|
TlsContext.set(this);
|
|
nativeStackBase_.emplace(GetNativeStackBase());
|
|
|
|
if (!fx.initInstance()) {
|
|
return false;
|
|
}
|
|
|
|
#ifdef JS_SIMULATOR
|
|
simulator_ = jit::Simulator::Create();
|
|
if (!simulator_) {
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
isolate = irregexp::CreateIsolate(this);
|
|
if (!isolate) {
|
|
return false;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
// Set the initialized_ last, so that ProtectedData checks will allow us to
|
|
// initialize this context before it becomes the runtime's active context.
|
|
initialized_ = true;
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
static void InitDefaultStackQuota(JSContext* cx) {
|
|
// Initialize stack quota to a reasonable default. Embedders can override this
|
|
// by calling JS_SetNativeStackQuota.
|
|
//
|
|
// NOTE: Firefox overrides these values. For the main thread this happens in
|
|
// XPCJSContext::Initialize.
|
|
|
|
#if defined(MOZ_ASAN) || (defined(DEBUG) && !defined(XP_WIN))
|
|
static constexpr JS::NativeStackSize MaxStackSize =
|
|
2 * 128 * sizeof(size_t) * 1024;
|
|
#else
|
|
static constexpr JS::NativeStackSize MaxStackSize =
|
|
128 * sizeof(size_t) * 1024;
|
|
#endif
|
|
JS_SetNativeStackQuota(cx, MaxStackSize);
|
|
}
|
|
|
|
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()) {
|
|
js_delete(cx);
|
|
js_delete(runtime);
|
|
return nullptr;
|
|
}
|
|
|
|
if (!runtime->init(cx, maxBytes)) {
|
|
runtime->destroyRuntime();
|
|
js_delete(cx);
|
|
js_delete(runtime);
|
|
return nullptr;
|
|
}
|
|
|
|
// Initialize stack quota last because simulators rely on the JSRuntime having
|
|
// been initialized.
|
|
InitDefaultStackQuota(cx);
|
|
|
|
return cx;
|
|
}
|
|
|
|
void js::DestroyContext(JSContext* cx) {
|
|
JS_AbortIfWrongThread(cx);
|
|
|
|
MOZ_ASSERT(!cx->realm(), "Shouldn't destroy context with active realm");
|
|
MOZ_ASSERT(!cx->activation(), "Shouldn't destroy context with activations");
|
|
|
|
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) {
|
|
return true;
|
|
}
|
|
} while (!!(cursor = cursor->link));
|
|
return false;
|
|
}
|
|
|
|
static void MaybeReportOutOfMemoryForDifferentialTesting() {
|
|
/*
|
|
* 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");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Since memory has been exhausted, avoid the normal error-handling path which
|
|
* allocates an error object, report and callstack. Instead simply throw the
|
|
* static atom "out of memory".
|
|
*
|
|
* Furthermore, callers of ReportOutOfMemory (viz., malloc) assume a GC does
|
|
* not occur, so GC must be avoided or suppressed.
|
|
*/
|
|
void JSContext::onOutOfMemory() {
|
|
runtime()->hadOutOfMemory = true;
|
|
gc::AutoSuppressGC suppressGC(this);
|
|
|
|
/* Report the oom. */
|
|
if (JS::OutOfMemoryCallback oomCallback = runtime()->oomCallback) {
|
|
oomCallback(this, runtime()->oomCallbackData);
|
|
}
|
|
|
|
// If we OOM early in process startup, this may be unavailable so just return
|
|
// instead of crashing unexpectedly.
|
|
if (MOZ_UNLIKELY(!runtime()->hasInitializedSelfHosting())) {
|
|
return;
|
|
}
|
|
|
|
RootedValue oomMessage(this, StringValue(names().out_of_memory_));
|
|
setPendingException(oomMessage, nullptr);
|
|
MOZ_ASSERT(status == JS::ExceptionStatus::Throwing);
|
|
status = JS::ExceptionStatus::OutOfMemory;
|
|
|
|
reportResourceExhaustion();
|
|
}
|
|
|
|
JS_PUBLIC_API void js::ReportOutOfMemory(JSContext* cx) {
|
|
MaybeReportOutOfMemoryForDifferentialTesting();
|
|
|
|
cx->onOutOfMemory();
|
|
}
|
|
|
|
JS_PUBLIC_API void js::ReportLargeOutOfMemory(JSContext* cx) {
|
|
js::ReportOutOfMemory(cx);
|
|
}
|
|
|
|
JS_PUBLIC_API void js::ReportOutOfMemory(FrontendContext* fc) {
|
|
MaybeReportOutOfMemoryForDifferentialTesting();
|
|
|
|
fc->onOutOfMemory();
|
|
}
|
|
|
|
static void MaybeReportOverRecursedForDifferentialTesting() {
|
|
/*
|
|
* 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");
|
|
}
|
|
}
|
|
|
|
void JSContext::onOverRecursed() {
|
|
// Try to construct an over-recursed error and then update the exception
|
|
// status to `OverRecursed`. Creating the error can fail, so check there
|
|
// is a reasonable looking exception pending before updating status.
|
|
JS_ReportErrorNumberASCII(this, GetErrorMessage, nullptr,
|
|
JSMSG_OVER_RECURSED);
|
|
if (isExceptionPending() && !isThrowingOutOfMemory()) {
|
|
MOZ_ASSERT(unwrappedException().isObject());
|
|
MOZ_ASSERT(status == JS::ExceptionStatus::Throwing);
|
|
status = JS::ExceptionStatus::OverRecursed;
|
|
}
|
|
|
|
reportResourceExhaustion();
|
|
}
|
|
|
|
JS_PUBLIC_API void js::ReportOverRecursed(JSContext* maybecx) {
|
|
MaybeReportOverRecursedForDifferentialTesting();
|
|
|
|
if (!maybecx) {
|
|
return;
|
|
}
|
|
|
|
maybecx->onOverRecursed();
|
|
}
|
|
|
|
JS_PUBLIC_API void js::ReportOverRecursed(FrontendContext* fc) {
|
|
MaybeReportOverRecursedForDifferentialTesting();
|
|
|
|
fc->onOverRecursed();
|
|
}
|
|
|
|
void js::ReportOversizedAllocation(JSContext* cx, const unsigned errorNumber) {
|
|
// The JIT may optimize away allocations if it determines that they aren't
|
|
// used. This can affect whether we throw an exception when the size of an
|
|
// allocation exceeds implementation-defined limits (eg JSString::MAX_LENGTH).
|
|
// These errors aren't interesting for the purposes of differential fuzzing.
|
|
// We print a message so that fuzzers can detect this case. To simplify
|
|
// tooling updates, we use the same message as ReportOutOfMemory.
|
|
if (js::SupportDifferentialTesting()) {
|
|
fprintf(stderr, "ReportOutOfMemory called\n");
|
|
}
|
|
|
|
gc::AutoSuppressGC suppressGC(cx);
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errorNumber);
|
|
|
|
cx->reportResourceExhaustion();
|
|
}
|
|
|
|
void js::ReportAllocationOverflow(JSContext* cx) {
|
|
if (js::SupportDifferentialTesting()) {
|
|
fprintf(stderr, "ReportAllocationOverflow called\n");
|
|
}
|
|
|
|
if (!cx) {
|
|
return;
|
|
}
|
|
|
|
cx->reportAllocationOverflow();
|
|
}
|
|
|
|
void js::ReportAllocationOverflow(FrontendContext* fc) {
|
|
fc->onAllocationOverflow();
|
|
}
|
|
|
|
/* |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(FILE* file, JS::ConstUTF8CharsZ toStringResult,
|
|
T* report, PrintErrorKind kind) {
|
|
UniqueChars prefix;
|
|
if (report->filename) {
|
|
prefix = JS_smprintf("%s:", report->filename.c_str());
|
|
}
|
|
|
|
if (report->lineno) {
|
|
prefix = JS_smprintf("%s%u:%u ", prefix ? prefix.get() : "", report->lineno,
|
|
report->column.oneOriginValue());
|
|
}
|
|
|
|
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);
|
|
}
|
|
(void)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(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(file, toStringResult, report, kind);
|
|
|
|
if (report->notes) {
|
|
for (auto&& note : *report->notes) {
|
|
PrintSingleError(file, JS::ConstUTF8CharsZ(), note.get(),
|
|
PrintErrorKind::Note);
|
|
}
|
|
}
|
|
}
|
|
|
|
JS_PUBLIC_API void JS::PrintError(FILE* file, JSErrorReport* report,
|
|
bool reportWarnings) {
|
|
PrintErrorImpl(file, JS::ConstUTF8CharsZ(), report, reportWarnings);
|
|
}
|
|
|
|
JS_PUBLIC_API void JS::PrintError(FILE* file,
|
|
const JS::ErrorReportBuilder& builder,
|
|
bool reportWarnings) {
|
|
PrintErrorImpl(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, Handle<PropertyName*> name) {
|
|
RootedId id(cx, NameToId(name));
|
|
ReportIsNotDefined(cx, id);
|
|
}
|
|
|
|
const char* NullOrUndefinedToCharZ(HandleValue v) {
|
|
MOZ_ASSERT(v.isNullOrUndefined());
|
|
return v.isNull() ? "null" : "undefined";
|
|
}
|
|
|
|
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(), "undefined") == 0 ||
|
|
strcmp(bytes.get(), "null") == 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 (!JS::Prefs::property_error_message_fix()) {
|
|
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(), "undefined") == 0 ||
|
|
strcmp(bytes.get(), "null") == 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) {
|
|
Rooted<ArrayObject*> notesArray(cx, NewDenseEmptyArray(cx));
|
|
if (!notesArray) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (!report->notes) {
|
|
return notesArray;
|
|
}
|
|
|
|
for (auto&& note : *report->notes) {
|
|
Rooted<PlainObject*> noteObj(cx, NewPlainObject(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 (const char* filename = note->filename.c_str()) {
|
|
JS::UTF8Chars utf8chars(filename, strlen(filename));
|
|
Rooted<JSString*> filenameStr(cx, NewStringCopyUTF8N(cx, utf8chars));
|
|
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.oneOriginValue()));
|
|
if (!DefineDataProperty(cx, noteObj, cx->names().columnNumber, columnVal)) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (!NewbornArrayPush(cx, notesArray, ObjectValue(*noteObj))) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
return notesArray;
|
|
}
|
|
|
|
void JSContext::recoverFromOutOfMemory() {
|
|
if (isExceptionPending()) {
|
|
MOZ_ASSERT(isThrowingOutOfMemory());
|
|
clearPendingException();
|
|
}
|
|
}
|
|
|
|
void JSContext::reportAllocationOverflow() {
|
|
gc::AutoSuppressGC suppressGC(this);
|
|
JS_ReportErrorNumberASCII(this, GetErrorMessage, nullptr,
|
|
JSMSG_ALLOC_OVERFLOW);
|
|
}
|
|
|
|
JS::StackKind JSContext::stackKindForCurrentPrincipal() {
|
|
return runningWithTrustedPrincipals() ? JS::StackForTrustedScript
|
|
: JS::StackForUntrustedScript;
|
|
}
|
|
|
|
JS::NativeStackLimit JSContext::stackLimitForCurrentPrincipal() {
|
|
return stackLimit(stackKindForCurrentPrincipal());
|
|
}
|
|
|
|
JS_PUBLIC_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;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
JSObject* InternalJobQueue::copyJobs(JSContext* cx) {
|
|
Rooted<ArrayObject*> jobs(cx, NewDenseEmptyArray(cx));
|
|
if (!jobs) {
|
|
return nullptr;
|
|
}
|
|
|
|
for (const JSObject* unwrappedJob : queue.get()) {
|
|
RootedObject job(cx, const_cast<JSObject*>(unwrappedJob));
|
|
if (!cx->compartment()->wrap(cx, &job)) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (!NewbornArrayPush(cx, jobs, ObjectValue(*job))) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
return jobs;
|
|
}
|
|
|
|
JS_PUBLIC_API JSObject* js::GetJobsInInternalJobQueue(JSContext* cx) {
|
|
MOZ_ASSERT(cx->internalJobQueue.ref());
|
|
return cx->internalJobQueue->copyJobs(cx);
|
|
}
|
|
#endif
|
|
|
|
JS_PUBLIC_API bool js::EnqueueJob(JSContext* cx, JS::HandleObject job) {
|
|
MOZ_ASSERT(cx->jobQueue);
|
|
return cx->jobQueue->enqueuePromiseJob(cx, nullptr, job, nullptr, nullptr);
|
|
}
|
|
|
|
JS_PUBLIC_API void js::StopDrainingJobQueue(JSContext* cx) {
|
|
MOZ_ASSERT(cx->internalJobQueue.ref());
|
|
cx->internalJobQueue->interrupt();
|
|
}
|
|
|
|
JS_PUBLIC_API void js::RestartDrainingJobQueue(JSContext* cx) {
|
|
MOZ_ASSERT(cx->internalJobQueue.ref());
|
|
cx->internalJobQueue->uninterrupt();
|
|
}
|
|
|
|
JS_PUBLIC_API void js::RunJobs(JSContext* cx) {
|
|
MOZ_ASSERT(cx->jobQueue);
|
|
MOZ_ASSERT(cx->isEvaluatingModule == 0);
|
|
cx->jobQueue->runJobs(cx);
|
|
JS::ClearKeptObjects(cx);
|
|
}
|
|
|
|
bool InternalJobQueue::getHostDefinedData(
|
|
JSContext* cx, JS::MutableHandle<JSObject*> data) const {
|
|
data.set(nullptr);
|
|
return true;
|
|
}
|
|
|
|
bool InternalJobQueue::enqueuePromiseJob(JSContext* cx,
|
|
JS::HandleObject promise,
|
|
JS::HandleObject job,
|
|
JS::HandleObject allocationSite,
|
|
JS::HandleObject hostDefinedData) {
|
|
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;
|
|
}
|
|
|
|
// Always clear the exception, because
|
|
// PrepareScriptEnvironmentAndInvoke will assert that we don't have
|
|
// one.
|
|
RootedValue exn(cx);
|
|
bool success = cx->getPendingException(&exn);
|
|
cx->clearPendingException();
|
|
if (success) {
|
|
js::ReportExceptionClosure reportExn(exn);
|
|
PrepareScriptEnvironmentAndInvoke(cx, cx->global(), reportExn);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
draining_ = false;
|
|
|
|
if (interrupted_) {
|
|
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() {
|
|
MOZ_ASSERT(isThrowingOutOfMemory());
|
|
return mozilla::Err(JS::OOM());
|
|
}
|
|
|
|
mozilla::GenericErrorResult<JS::Error> JSContext::alreadyReportedError() {
|
|
return mozilla::Err(JS::Error());
|
|
}
|
|
|
|
JSContext::JSContext(JSRuntime* runtime, const JS::ContextOptions& options)
|
|
: RootingContext(runtime ? &runtime->gc.nursery() : nullptr),
|
|
runtime_(runtime),
|
|
options_(this, options),
|
|
measuringExecutionTime_(this, false),
|
|
jitActivation(this, nullptr),
|
|
isolate(this, nullptr),
|
|
activation_(this, nullptr),
|
|
profilingActivation_(nullptr),
|
|
noExecuteDebuggerTop(this, nullptr),
|
|
#ifdef JS_CHECK_UNSAFE_CALL_WITH_ABI
|
|
inUnsafeCallWithABI(this, false),
|
|
hasAutoUnsafeCallWithABI(this, false),
|
|
#endif
|
|
#ifdef DEBUG
|
|
liveArraySortDataInstances(this, 0),
|
|
#endif
|
|
#ifdef JS_SIMULATOR
|
|
simulator_(this, nullptr),
|
|
#endif
|
|
dtoaState(this, nullptr),
|
|
suppressGC(this, 0),
|
|
#ifdef FUZZING_JS_FUZZILLI
|
|
executionHash(1),
|
|
executionHashInputs(0),
|
|
#endif
|
|
#ifdef DEBUG
|
|
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),
|
|
#ifdef DEBUG
|
|
regExpSearcherLastLimit(this, RegExpSearcherLastLimitSentinel),
|
|
#else
|
|
regExpSearcherLastLimit(this, 0),
|
|
#endif
|
|
isEvaluatingModule(this, 0),
|
|
frontendCollectionPool_(this),
|
|
suppressProfilerSampling(false),
|
|
tempLifoAlloc_(this, (size_t)TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE,
|
|
js::MallocArena),
|
|
debuggerMutations(this, 0),
|
|
status(this, JS::ExceptionStatus::None),
|
|
unwrappedException_(this),
|
|
unwrappedExceptionStack_(this),
|
|
#ifdef DEBUG
|
|
hadResourceExhaustion_(this, false),
|
|
hadUncatchableException_(this, false),
|
|
#endif
|
|
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(JS::NativeStackLimitMin),
|
|
jitStackLimitNoInterrupt(this, JS::NativeStackLimitMin),
|
|
jobQueue(this, nullptr),
|
|
internalJobQueue(this),
|
|
canSkipEnqueuingJobs(this, false),
|
|
promiseRejectionTrackerCallback(this, nullptr),
|
|
promiseRejectionTrackerCallbackData(this, nullptr),
|
|
insideExclusiveDebuggerOnEval(this, nullptr) {
|
|
MOZ_ASSERT(static_cast<JS::RootingContext*>(this) ==
|
|
JS::RootingContext::get(this));
|
|
}
|
|
|
|
#ifdef ENABLE_WASM_JSPI
|
|
bool js::IsSuspendableStackActive(JSContext* cx) {
|
|
return cx->wasm().suspendableStackLimit != JS::NativeStackLimitMin;
|
|
}
|
|
|
|
JS::NativeStackLimit js::GetSuspendableStackLimit(JSContext* cx) {
|
|
return cx->wasm().suspendableStackLimit;
|
|
}
|
|
#endif
|
|
|
|
JSContext::~JSContext() {
|
|
#ifdef DEBUG
|
|
// Clear the initialized_ first, so that ProtectedData checks will allow us to
|
|
// destroy this context even if the runtime is already gone.
|
|
initialized_ = false;
|
|
#endif
|
|
|
|
/* Free the stuff hanging off of cx. */
|
|
MOZ_ASSERT(!resolvingList);
|
|
|
|
// Ensure we didn't leak memory for the ArraySortData vector.
|
|
MOZ_ASSERT(liveArraySortDataInstances == 0);
|
|
|
|
if (dtoaState) {
|
|
DestroyDtoaState(dtoaState);
|
|
}
|
|
|
|
fx.destroyInstance();
|
|
|
|
#ifdef JS_SIMULATOR
|
|
js::jit::Simulator::Destroy(simulator_);
|
|
#endif
|
|
|
|
if (isolate) {
|
|
irregexp::DestroyIsolate(isolate.ref());
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
#if defined(NIGHTLY_BUILD)
|
|
static bool IsOutOfMemoryException(JSContext* cx, const Value& v) {
|
|
return v == StringValue(cx->names().out_of_memory_);
|
|
}
|
|
#endif
|
|
|
|
void JSContext::setPendingException(HandleValue v, Handle<SavedFrame*> 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->status = JS::ExceptionStatus::Throwing;
|
|
this->unwrappedException() = v;
|
|
this->unwrappedExceptionStack() = stack;
|
|
}
|
|
|
|
void JSContext::setPendingException(HandleValue value,
|
|
ShouldCaptureStack captureStack) {
|
|
Rooted<SavedFrame*> nstack(this);
|
|
if (captureStack == ShouldCaptureStack::Always ||
|
|
realm()->shouldCaptureStackForThrow()) {
|
|
RootedObject stack(this);
|
|
if (!CaptureStack(this, &stack)) {
|
|
clearPendingException();
|
|
}
|
|
if (stack) {
|
|
nstack = &stack->as<SavedFrame>();
|
|
}
|
|
}
|
|
setPendingException(value, nstack);
|
|
}
|
|
|
|
bool JSContext::getPendingException(MutableHandleValue rval) {
|
|
MOZ_ASSERT(isExceptionPending());
|
|
|
|
RootedValue exception(this, unwrappedException());
|
|
if (zone()->isAtomsZone()) {
|
|
rval.set(exception);
|
|
return true;
|
|
}
|
|
|
|
Rooted<SavedFrame*> stack(this, unwrappedExceptionStack());
|
|
JS::ExceptionStatus prevStatus = status;
|
|
clearPendingException();
|
|
if (!compartment()->wrap(this, &exception)) {
|
|
return false;
|
|
}
|
|
this->check(exception);
|
|
setPendingException(exception, stack);
|
|
status = prevStatus;
|
|
|
|
rval.set(exception);
|
|
return true;
|
|
}
|
|
|
|
bool JSContext::getPendingExceptionStack(MutableHandleValue rval) {
|
|
MOZ_ASSERT(isExceptionPending());
|
|
|
|
Rooted<SavedFrame*> exceptionStack(this, unwrappedExceptionStack());
|
|
if (!exceptionStack) {
|
|
rval.setNull();
|
|
return true;
|
|
}
|
|
if (zone()->isAtomsZone()) {
|
|
rval.setObject(*exceptionStack);
|
|
return true;
|
|
}
|
|
|
|
RootedValue stack(this, ObjectValue(*exceptionStack));
|
|
RootedValue exception(this, unwrappedException());
|
|
JS::ExceptionStatus prevStatus = status;
|
|
clearPendingException();
|
|
if (!compartment()->wrap(this, &exception) ||
|
|
!compartment()->wrap(this, &stack)) {
|
|
return false;
|
|
}
|
|
this->check(stack);
|
|
setPendingException(exception, exceptionStack);
|
|
status = prevStatus;
|
|
|
|
rval.set(stack);
|
|
return true;
|
|
}
|
|
|
|
SavedFrame* JSContext::getPendingExceptionStack() {
|
|
return unwrappedExceptionStack();
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
const JS::Value& JSContext::getPendingExceptionUnwrapped() {
|
|
MOZ_ASSERT(isExceptionPending());
|
|
return unwrappedException();
|
|
}
|
|
#endif
|
|
|
|
bool JSContext::isClosingGenerator() {
|
|
return isExceptionPending() &&
|
|
unwrappedException().isMagic(JS_GENERATOR_CLOSING);
|
|
}
|
|
|
|
bool JSContext::isThrowingDebuggeeWouldRun() {
|
|
return isExceptionPending() && unwrappedException().isObject() &&
|
|
unwrappedException().toObject().is<ErrorObject>() &&
|
|
unwrappedException().toObject().as<ErrorObject>().type() ==
|
|
JSEXN_DEBUGGEEWOULDRUN;
|
|
}
|
|
|
|
bool JSContext::isRuntimeCodeGenEnabled(
|
|
JS::RuntimeCode kind, JS::Handle<JSString*> codeString,
|
|
JS::CompilationType compilationType,
|
|
JS::Handle<JS::StackGCVector<JSString*>> parameterStrings,
|
|
JS::Handle<JSString*> bodyString,
|
|
JS::Handle<JS::StackGCVector<JS::Value>> parameterArgs,
|
|
JS::Handle<JS::Value> bodyArg, bool* outCanCompileStrings) {
|
|
// Make sure that the CSP callback is installed and that it permits runtime
|
|
// code generation.
|
|
if (JSCSPEvalChecker allows =
|
|
runtime()->securityCallbacks->contentSecurityPolicyAllows) {
|
|
return allows(this, kind, codeString, compilationType, parameterStrings,
|
|
bodyString, parameterArgs, bodyArg, outCanCompileStrings);
|
|
}
|
|
|
|
// Default implementation from the "Dynamic Code Brand Checks" spec.
|
|
// https://tc39.es/proposal-dynamic-code-brand-checks/#sec-hostensurecancompilestrings
|
|
*outCanCompileStrings = true;
|
|
return true;
|
|
}
|
|
|
|
bool JSContext::getCodeForEval(HandleObject code,
|
|
JS::MutableHandle<JSString*> outCode) {
|
|
if (JSCodeForEvalOp gets = runtime()->securityCallbacks->codeForEvalGets) {
|
|
return gets(this, code, outCode);
|
|
}
|
|
// Default implementation from the "Dynamic Code Brand Checks" spec.
|
|
// https://tc39.es/proposal-dynamic-code-brand-checks/#sec-hostgetcodeforeval
|
|
outCode.set(nullptr);
|
|
return true;
|
|
}
|
|
|
|
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) +
|
|
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);
|
|
if (isolate) {
|
|
irregexp::TraceIsolate(trc, isolate.ref());
|
|
}
|
|
#ifdef ENABLE_WASM_JSPI
|
|
wasm().promiseIntegration.trace(trc);
|
|
#endif
|
|
}
|
|
|
|
JS::NativeStackLimit 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(); }
|
|
|
|
JSScript* JSContext::currentScript(jsbytecode** ppc,
|
|
AllowCrossRealm allowCrossRealm) {
|
|
if (ppc) {
|
|
*ppc = nullptr;
|
|
}
|
|
|
|
// Fast path: there are no JS frames on the stack if there's no activation.
|
|
if (!activation()) {
|
|
return nullptr;
|
|
}
|
|
|
|
FrameIter iter(this);
|
|
if (iter.done() || !iter.hasScript()) {
|
|
return nullptr;
|
|
}
|
|
|
|
JSScript* script = iter.script();
|
|
if (allowCrossRealm == AllowCrossRealm::DontAllow &&
|
|
script->realm() != realm()) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (ppc) {
|
|
*ppc = iter.pc();
|
|
}
|
|
return script;
|
|
}
|
|
|
|
#ifdef JS_CRASH_DIAGNOSTICS
|
|
void ContextChecks::check(AbstractFramePtr frame, int argIndex) {
|
|
if (frame) {
|
|
check(frame.realm(), argIndex);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void AutoEnterOOMUnsafeRegion::crash_impl(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).
|
|
fprintf(stderr, "Hit MOZ_CRASH(%s) at %s:%d\n", msgbuf, __FILE__, __LINE__);
|
|
#endif
|
|
MOZ_CRASH_UNSAFE(msgbuf);
|
|
}
|
|
|
|
mozilla::Atomic<AutoEnterOOMUnsafeRegion::AnnotateOOMAllocationSizeCallback,
|
|
mozilla::Relaxed>
|
|
AutoEnterOOMUnsafeRegion::annotateOOMSizeCallback(nullptr);
|
|
|
|
void AutoEnterOOMUnsafeRegion::crash_impl(size_t size, const char* reason) {
|
|
{
|
|
JS::AutoSuppressGCAnalysis suppress;
|
|
if (annotateOOMSizeCallback) {
|
|
annotateOOMSizeCallback(size);
|
|
}
|
|
}
|
|
crash_impl(reason);
|
|
}
|
|
|
|
void ExternalValueArray::trace(JSTracer* trc) {
|
|
if (Value* vp = begin()) {
|
|
TraceRootRange(trc, length(), vp, "js::ExternalValueArray");
|
|
}
|
|
}
|
|
|
|
#ifdef MOZ_EXECUTION_TRACING
|
|
|
|
bool JSContext::enableExecutionTracing() {
|
|
if (!executionTracer_) {
|
|
for (RealmsIter realm(runtime()); !realm.done(); realm.next()) {
|
|
if (realm->debuggerObservesCoverage()) {
|
|
JS_ReportErrorNumberASCII(
|
|
this, GetErrorMessage, nullptr,
|
|
JSMSG_DEBUG_EXCLUSIVE_EXECUTION_TRACE_COVERAGE);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
executionTracer_ = js::MakeUnique<ExecutionTracer>();
|
|
|
|
if (!executionTracer_) {
|
|
return false;
|
|
}
|
|
|
|
if (!executionTracer_->init()) {
|
|
executionTracer_ = nullptr;
|
|
return false;
|
|
}
|
|
|
|
for (RealmsIter realm(runtime()); !realm.done(); realm.next()) {
|
|
if (realm->isSystem()) {
|
|
continue;
|
|
}
|
|
realm->enableExecutionTracing();
|
|
}
|
|
}
|
|
|
|
executionTracerSuspended_ = false;
|
|
return true;
|
|
}
|
|
|
|
void JSContext::cleanUpExecutionTracingState() {
|
|
MOZ_ASSERT(executionTracer_);
|
|
|
|
for (RealmsIter realm(runtime()); !realm.done(); realm.next()) {
|
|
if (realm->isSystem()) {
|
|
continue;
|
|
}
|
|
realm->disableExecutionTracing();
|
|
}
|
|
|
|
caches().tracingCaches.clearAll();
|
|
}
|
|
|
|
void JSContext::disableExecutionTracing() {
|
|
if (executionTracer_) {
|
|
cleanUpExecutionTracingState();
|
|
executionTracer_ = nullptr;
|
|
}
|
|
}
|
|
|
|
void JSContext::suspendExecutionTracing() {
|
|
if (executionTracer_) {
|
|
cleanUpExecutionTracingState();
|
|
executionTracerSuspended_ = true;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef JS_CHECK_UNSAFE_CALL_WITH_ABI
|
|
|
|
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 // JS_CHECK_UNSAFE_CALL_WITH_ABI
|
|
|
|
#ifdef __wasi__
|
|
JS_PUBLIC_API void js::IncWasiRecursionDepth(JSContext* cx) {
|
|
++JS::RootingContext::get(cx)->wasiRecursionDepth;
|
|
}
|
|
|
|
JS_PUBLIC_API void js::DecWasiRecursionDepth(JSContext* cx) {
|
|
MOZ_ASSERT(JS::RootingContext::get(cx)->wasiRecursionDepth > 0);
|
|
--JS::RootingContext::get(cx)->wasiRecursionDepth;
|
|
}
|
|
|
|
JS_PUBLIC_API bool js::CheckWasiRecursionLimit(JSContext* cx) {
|
|
// WASI has two limits:
|
|
// 1) The stack pointer in linear memory that grows to zero. See
|
|
// --stack-first in js/src/shell/moz.build.
|
|
// 2) The JS::RootingContext::wasiRecursionDepth that counts recursion depth.
|
|
// Here we should check both.
|
|
if (JS::RootingContext::get(cx)->wasiRecursionDepth >=
|
|
JS::RootingContext::wasiRecursionDepthLimit) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
#endif // __wasi__
|