Source code

Revision control

Copy as Markdown

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/. */
#include "frontend/FrontendContext.h"
#ifdef _WIN32
# include <windows.h>
# include <process.h> // GetCurrentThreadId
#else
# include <pthread.h> // pthread_self
#endif
#include "gc/GC.h"
#include "js/AllocPolicy.h" // js::ReportOutOfMemory
#include "js/experimental/CompileScript.h"
#include "js/friend/StackLimits.h" // js::ReportOverRecursed, js::MinimumStackLimitMargin, js::StackLimitMargin
#include "js/Modules.h"
#include "util/DifferentialTesting.h"
#include "util/NativeStack.h" // GetNativeStackBase
#include "vm/JSContext.h"
using namespace js;
void FrontendErrors::clearErrors() {
error.reset();
warnings.clear();
overRecursed = false;
outOfMemory = false;
allocationOverflow = false;
}
void FrontendErrors::clearWarnings() { warnings.clear(); }
void FrontendAllocator::reportAllocationOverflow() {
fc_->onAllocationOverflow();
}
void* FrontendAllocator::onOutOfMemory(AllocFunction allocFunc,
arena_id_t arena, size_t nbytes,
void* reallocPtr) {
return fc_->onOutOfMemory(allocFunc, arena, nbytes, reallocPtr);
}
FrontendContext::~FrontendContext() {
if (ownNameCollectionPool_) {
MOZ_ASSERT(nameCollectionPool_);
js_delete(nameCollectionPool_);
}
}
void FrontendContext::setStackQuota(JS::NativeStackSize stackSize) {
#ifdef __wasi__
stackLimit_ = JS::WASINativeStackLimit;
#else // __wasi__
if (stackSize == 0) {
stackLimit_ = JS::NativeStackLimitMax;
} else {
stackLimit_ = JS::GetNativeStackLimit(GetNativeStackBase(), stackSize - 1);
}
#endif // !__wasi__
#ifdef DEBUG
setNativeStackLimitThread();
#endif
}
JS_PUBLIC_API void JS::SetNativeStackQuota(JS::FrontendContext* fc,
JS::NativeStackSize stackSize) {
fc->setStackQuota(stackSize);
}
JS_PUBLIC_API JS::NativeStackSize JS::ThreadStackQuotaForSize(
size_t stackSize) {
// Set the stack quota to 10% less that the actual size.
static constexpr double RatioWithoutMargin = 0.9;
MOZ_ASSERT(double(stackSize) * (1 - RatioWithoutMargin) >
js::MinimumStackLimitMargin);
return JS::NativeStackSize(double(stackSize) * RatioWithoutMargin);
}
bool FrontendContext::allocateOwnedPool() {
MOZ_ASSERT(!nameCollectionPool_);
nameCollectionPool_ = js_new<frontend::NameCollectionPool>();
if (!nameCollectionPool_) {
return false;
}
ownNameCollectionPool_ = true;
return true;
}
bool FrontendContext::hadErrors() const {
// All errors must be reported to FrontendContext.
MOZ_ASSERT_IF(maybeCx_, !maybeCx_->isExceptionPending());
return errors_.hadErrors();
}
JS_PUBLIC_API bool JS::HadFrontendErrors(JS::FrontendContext* fc) {
return fc->hadErrors();
}
void FrontendContext::clearErrors() {
MOZ_ASSERT(!maybeCx_);
return errors_.clearErrors();
}
JS_PUBLIC_API void JS::ClearFrontendErrors(JS::FrontendContext* fc) {
fc->clearErrors();
}
void FrontendContext::clearWarnings() { return errors_.clearWarnings(); }
void* FrontendContext::onOutOfMemory(AllocFunction allocFunc, arena_id_t arena,
size_t nbytes, void* reallocPtr) {
addPendingOutOfMemory();
return nullptr;
}
void FrontendContext::onAllocationOverflow() {
errors_.allocationOverflow = true;
}
void FrontendContext::onOutOfMemory() { addPendingOutOfMemory(); }
void FrontendContext::onOverRecursed() { errors_.overRecursed = true; }
void FrontendContext::recoverFromOutOfMemory() {
MOZ_ASSERT_IF(maybeCx_, !maybeCx_->isThrowingOutOfMemory());
errors_.outOfMemory = false;
}
const JSErrorFormatString* FrontendContext::gcSafeCallback(
JSErrorCallback callback, void* userRef, const unsigned errorNumber) {
mozilla::Maybe<gc::AutoSuppressGC> suppressGC;
if (maybeCx_) {
suppressGC.emplace(maybeCx_);
}
return callback(userRef, errorNumber);
}
void FrontendContext::reportError(CompileError&& err) {
if (errors_.error) {
errors_.error.reset();
}
// When compiling off thread, save the error so that the thread finishing the
// parse can report it later.
errors_.error.emplace(std::move(err));
}
bool FrontendContext::reportWarning(CompileError&& err) {
if (!errors_.warnings.append(std::move(err))) {
ReportOutOfMemory();
return false;
}
return true;
}
void FrontendContext::ReportOutOfMemory() {
/*
* 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 (SupportDifferentialTesting()) {
fprintf(stderr, "ReportOutOfMemory called\n");
}
addPendingOutOfMemory();
}
void FrontendContext::addPendingOutOfMemory() { errors_.outOfMemory = true; }
void FrontendContext::setCurrentJSContext(JSContext* cx) {
MOZ_ASSERT(!nameCollectionPool_);
maybeCx_ = cx;
nameCollectionPool_ = &cx->frontendCollectionPool();
scriptDataTableHolder_ = &cx->runtime()->scriptDataTableHolder();
stackLimit_ = cx->stackLimitForCurrentPrincipal();
#ifdef DEBUG
setNativeStackLimitThread();
#endif
}
bool FrontendContext::convertToRuntimeError(
JSContext* cx, Warning warning /* = Warning::Report */) {
// Report out of memory errors eagerly, or errors could be malformed.
if (hadOutOfMemory()) {
js::ReportOutOfMemory(cx);
return false;
}
if (maybeError()) {
if (!maybeError()->throwError(cx)) {
return false;
}
}
if (warning == Warning::Report) {
for (CompileError& error : warnings()) {
if (!error.throwError(cx)) {
return false;
}
}
}
if (hadOverRecursed()) {
js::ReportOverRecursed(cx);
}
if (hadAllocationOverflow()) {
js::ReportAllocationOverflow(cx);
}
MOZ_ASSERT(!extraBindingsAreNotUsed(),
"extraBindingsAreNotUsed shouldn't escape from FrontendContext");
return true;
}
JS_PUBLIC_API bool JS::ConvertFrontendErrorsToRuntimeErrors(
JSContext* cx, JS::FrontendContext* fc,
const JS::ReadOnlyCompileOptions& options) {
return fc->convertToRuntimeError(cx);
}
#ifdef DEBUG
static size_t GetTid() {
# if defined(_WIN32)
return size_t(GetCurrentThreadId());
# elif defined(__wasm__)
return 1;
# else
return size_t(pthread_self());
# endif
}
void FrontendContext::setNativeStackLimitThread() {
stackLimitThreadId_.emplace(GetTid());
}
void FrontendContext::assertNativeStackLimitThread() {
if (!stackLimitThreadId_.isSome()) {
return;
}
MOZ_ASSERT(*stackLimitThreadId_ == GetTid());
}
#endif
#ifdef __wasi__
void FrontendContext::incWasiRecursionDepth() {
if (maybeCx_) {
IncWasiRecursionDepth(maybeCx_);
}
}
void FrontendContext::decWasiRecursionDepth() {
if (maybeCx_) {
DecWasiRecursionDepth(maybeCx_);
}
}
bool FrontendContext::checkWasiRecursionLimit() {
if (maybeCx_) {
return CheckWasiRecursionLimit(maybeCx_);
}
return true;
}
JS_PUBLIC_API void js::IncWasiRecursionDepth(FrontendContext* fc) {
fc->incWasiRecursionDepth();
}
JS_PUBLIC_API void js::DecWasiRecursionDepth(FrontendContext* fc) {
fc->decWasiRecursionDepth();
}
JS_PUBLIC_API bool js::CheckWasiRecursionLimit(FrontendContext* fc) {
return fc->checkWasiRecursionLimit();
}
#endif // __wasi__
FrontendContext* js::NewFrontendContext() {
UniquePtr<FrontendContext> fc = MakeUnique<FrontendContext>();
if (!fc) {
return nullptr;
}
if (!fc->allocateOwnedPool()) {
return nullptr;
}
return fc.release();
}
JS_PUBLIC_API FrontendContext* JS::NewFrontendContext() {
MOZ_ASSERT(JS::detail::libraryInitState == JS::detail::InitState::Running,
"must call JS_Init prior to creating any FrontendContexts");
return js::NewFrontendContext();
}
void js::DestroyFrontendContext(FrontendContext* fc) { js_delete_poison(fc); }
JS_PUBLIC_API void JS::DestroyFrontendContext(FrontendContext* fc) {
return js::DestroyFrontendContext(fc);
}
#ifdef DEBUG
void FrontendContext::checkAndUpdateFrontendContextRecursionLimit(void* sp) {
// For the js::MinimumStackLimitMargin to be effective, it should be larger
// than the largest stack space which might be consumed by successive calls
// to AutoCheckRecursionLimit::check.
//
// This function asserts that this property holds by recalling the stack
// pointer of the previous call and comparing the consumed stack size with
// the minimum margin.
//
// If this property does not hold, either the stack limit should be increased
// or more calls to check for recursion should be added.
if (previousStackPointer_ != nullptr) {
# if JS_STACK_GROWTH_DIRECTION > 0
if (sp > previousStackPointer_) {
size_t diff = uintptr_t(sp) - uintptr_t(previousStackPointer_);
MOZ_ASSERT(diff < js::MinimumStackLimitMargin);
}
# else
if (sp < previousStackPointer_) {
size_t diff = uintptr_t(previousStackPointer_) - uintptr_t(sp);
MOZ_ASSERT(diff < js::MinimumStackLimitMargin);
}
# endif
}
previousStackPointer_ = sp;
}
void js::CheckAndUpdateFrontendContextRecursionLimit(FrontendContext* fc,
void* sp) {
fc->checkAndUpdateFrontendContextRecursionLimit(sp);
}
#endif
JS_PUBLIC_API const JSErrorReport* JS::GetFrontendErrorReport(
JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& options) {
if (!fc->maybeError().isSome()) {
return nullptr;
}
return fc->maybeError().ptr();
}
JS_PUBLIC_API bool JS::HadFrontendOverRecursed(JS::FrontendContext* fc) {
return fc->hadOverRecursed();
}
JS_PUBLIC_API bool JS::HadFrontendOutOfMemory(JS::FrontendContext* fc) {
return fc->hadOutOfMemory();
}
JS_PUBLIC_API bool JS::HadFrontendAllocationOverflow(JS::FrontendContext* fc) {
return fc->hadAllocationOverflow();
}
JS_PUBLIC_API size_t JS::GetFrontendWarningCount(JS::FrontendContext* fc) {
return fc->warnings().length();
}
JS_PUBLIC_API const JSErrorReport* JS::GetFrontendWarningAt(
JS::FrontendContext* fc, size_t index,
const JS::ReadOnlyCompileOptions& options) {
return &fc->warnings()[index];
}