tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

FrontendContext.cpp (10660B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "frontend/FrontendContext.h"
      8 
      9 #ifdef _WIN32
     10 #  include "util/WindowsWrapper.h"
     11 #  include <process.h>  // GetCurrentThreadId
     12 #else
     13 #  include <pthread.h>  // pthread_self
     14 #endif
     15 
     16 #include "gc/GC.h"
     17 #include "js/AllocPolicy.h"  // js::ReportOutOfMemory
     18 #include "js/experimental/CompileScript.h"
     19 #include "js/friend/StackLimits.h"  // js::ReportOverRecursed, js::MinimumStackLimitMargin, js::StackLimitMargin
     20 #include "js/Modules.h"
     21 #include "util/DifferentialTesting.h"
     22 #include "util/NativeStack.h"  // GetNativeStackBase
     23 #include "vm/JSContext.h"
     24 
     25 using namespace js;
     26 
     27 void FrontendErrors::clearErrors() {
     28  error.reset();
     29  warnings.clear();
     30  overRecursed = false;
     31  outOfMemory = false;
     32  allocationOverflow = false;
     33 }
     34 
     35 void FrontendErrors::clearWarnings() { warnings.clear(); }
     36 
     37 void FrontendAllocator::reportAllocationOverflow() {
     38  fc_->onAllocationOverflow();
     39 }
     40 
     41 void* FrontendAllocator::onOutOfMemory(AllocFunction allocFunc,
     42                                       arena_id_t arena, size_t nbytes,
     43                                       void* reallocPtr) {
     44  return fc_->onOutOfMemory(allocFunc, arena, nbytes, reallocPtr);
     45 }
     46 
     47 FrontendContext::~FrontendContext() {
     48  if (ownNameCollectionPool_) {
     49    MOZ_ASSERT(nameCollectionPool_);
     50    js_delete(nameCollectionPool_);
     51  }
     52 }
     53 
     54 void FrontendContext::setStackQuota(JS::NativeStackSize stackSize) {
     55 #ifdef __wasi__
     56  stackLimit_ = JS::WASINativeStackLimit;
     57 #else   // __wasi__
     58  if (stackSize == 0) {
     59    stackLimit_ = JS::NativeStackLimitMax;
     60  } else {
     61    stackLimit_ = JS::GetNativeStackLimit(GetNativeStackBase(), stackSize - 1);
     62  }
     63 #endif  // !__wasi__
     64 
     65 #ifdef DEBUG
     66  setNativeStackLimitThread();
     67 #endif
     68 }
     69 
     70 JS_PUBLIC_API void JS::SetNativeStackQuota(JS::FrontendContext* fc,
     71                                           JS::NativeStackSize stackSize) {
     72  fc->setStackQuota(stackSize);
     73 }
     74 
     75 JS_PUBLIC_API JS::NativeStackSize JS::ThreadStackQuotaForSize(
     76    size_t stackSize) {
     77  // Set the stack quota to 10% less that the actual size.
     78  static constexpr double RatioWithoutMargin = 0.9;
     79 
     80  MOZ_ASSERT(double(stackSize) * (1 - RatioWithoutMargin) >
     81             js::MinimumStackLimitMargin);
     82 
     83  return JS::NativeStackSize(double(stackSize) * RatioWithoutMargin);
     84 }
     85 
     86 bool FrontendContext::allocateOwnedPool() {
     87  MOZ_ASSERT(!nameCollectionPool_);
     88 
     89  nameCollectionPool_ = js_new<frontend::NameCollectionPool>();
     90  if (!nameCollectionPool_) {
     91    return false;
     92  }
     93  ownNameCollectionPool_ = true;
     94  return true;
     95 }
     96 
     97 bool FrontendContext::hadErrors() const {
     98  // All errors must be reported to FrontendContext.
     99  MOZ_ASSERT_IF(maybeCx_, !maybeCx_->isExceptionPending());
    100 
    101  return errors_.hadErrors();
    102 }
    103 
    104 JS_PUBLIC_API bool JS::HadFrontendErrors(JS::FrontendContext* fc) {
    105  return fc->hadErrors();
    106 }
    107 
    108 void FrontendContext::clearErrors() {
    109  MOZ_ASSERT(!maybeCx_);
    110  return errors_.clearErrors();
    111 }
    112 
    113 JS_PUBLIC_API void JS::ClearFrontendErrors(JS::FrontendContext* fc) {
    114  fc->clearErrors();
    115 }
    116 
    117 void FrontendContext::clearWarnings() { return errors_.clearWarnings(); }
    118 
    119 void* FrontendContext::onOutOfMemory(AllocFunction allocFunc, arena_id_t arena,
    120                                     size_t nbytes, void* reallocPtr) {
    121  addPendingOutOfMemory();
    122  return nullptr;
    123 }
    124 
    125 void FrontendContext::onAllocationOverflow() {
    126  errors_.allocationOverflow = true;
    127 }
    128 
    129 void FrontendContext::onOutOfMemory() { addPendingOutOfMemory(); }
    130 
    131 void FrontendContext::onOverRecursed() { errors_.overRecursed = true; }
    132 
    133 void FrontendContext::recoverFromOutOfMemory() {
    134  MOZ_ASSERT_IF(maybeCx_, !maybeCx_->isThrowingOutOfMemory());
    135 
    136  errors_.outOfMemory = false;
    137 }
    138 
    139 const JSErrorFormatString* FrontendContext::gcSafeCallback(
    140    JSErrorCallback callback, void* userRef, const unsigned errorNumber) {
    141  mozilla::Maybe<gc::AutoSuppressGC> suppressGC;
    142  if (maybeCx_) {
    143    suppressGC.emplace(maybeCx_);
    144  }
    145 
    146  return callback(userRef, errorNumber);
    147 }
    148 
    149 void FrontendContext::reportError(CompileError&& err) {
    150  if (errors_.error) {
    151    errors_.error.reset();
    152  }
    153 
    154  // When compiling off thread, save the error so that the thread finishing the
    155  // parse can report it later.
    156  errors_.error.emplace(std::move(err));
    157 }
    158 
    159 bool FrontendContext::reportWarning(CompileError&& err) {
    160  if (!errors_.warnings.append(std::move(err))) {
    161    ReportOutOfMemory();
    162    return false;
    163  }
    164 
    165  return true;
    166 }
    167 
    168 void FrontendContext::ReportOutOfMemory() {
    169  /*
    170   * OOMs are non-deterministic, especially across different execution modes
    171   * (e.g. interpreter vs JIT). When doing differential testing, print to
    172   * stderr so that the fuzzers can detect this.
    173   */
    174  if (SupportDifferentialTesting()) {
    175    fprintf(stderr, "ReportOutOfMemory called\n");
    176  }
    177 
    178  addPendingOutOfMemory();
    179 }
    180 
    181 void FrontendContext::addPendingOutOfMemory() { errors_.outOfMemory = true; }
    182 
    183 void FrontendContext::setCurrentJSContext(JSContext* cx) {
    184  MOZ_ASSERT(!nameCollectionPool_);
    185 
    186  maybeCx_ = cx;
    187  nameCollectionPool_ = &cx->frontendCollectionPool();
    188  scriptDataTableHolder_ = &cx->runtime()->scriptDataTableHolder();
    189  stackLimit_ = cx->stackLimitForCurrentPrincipal();
    190 
    191 #ifdef DEBUG
    192  setNativeStackLimitThread();
    193 #endif
    194 }
    195 
    196 bool FrontendContext::convertToRuntimeError(
    197    JSContext* cx, Warning warning /* = Warning::Report */) {
    198  // Report out of memory errors eagerly, or errors could be malformed.
    199  if (hadOutOfMemory()) {
    200    js::ReportOutOfMemory(cx);
    201    return false;
    202  }
    203 
    204  if (maybeError()) {
    205    if (!maybeError()->throwError(cx)) {
    206      return false;
    207    }
    208  }
    209  if (warning == Warning::Report) {
    210    for (CompileError& error : warnings()) {
    211 #ifdef DEBUG
    212      bool hadException = cx->isExceptionPending();
    213 #endif
    214      if (!error.throwError(cx)) {
    215        return false;
    216      }
    217 
    218      // The warning reporter shouldn't clear the exception set by
    219      // other errors.
    220      MOZ_ASSERT_IF(hadException, cx->isExceptionPending());
    221    }
    222  }
    223  if (hadOverRecursed()) {
    224    js::ReportOverRecursed(cx);
    225  }
    226  if (hadAllocationOverflow()) {
    227    js::ReportAllocationOverflow(cx);
    228  }
    229 
    230  MOZ_ASSERT(!extraBindingsAreNotUsed(),
    231             "extraBindingsAreNotUsed shouldn't escape from FrontendContext");
    232  return true;
    233 }
    234 
    235 JS_PUBLIC_API bool JS::ConvertFrontendErrorsToRuntimeErrors(
    236    JSContext* cx, JS::FrontendContext* fc,
    237    const JS::ReadOnlyCompileOptions& options) {
    238  return fc->convertToRuntimeError(cx);
    239 }
    240 
    241 #ifdef DEBUG
    242 static size_t GetTid() {
    243 #  if defined(_WIN32)
    244  return size_t(GetCurrentThreadId());
    245 #  elif defined(__wasm__)
    246  return 1;
    247 #  else
    248  return size_t(pthread_self());
    249 #  endif
    250 }
    251 
    252 void FrontendContext::setNativeStackLimitThread() {
    253  stackLimitThreadId_.emplace(GetTid());
    254 }
    255 
    256 void FrontendContext::assertNativeStackLimitThread() {
    257  if (!stackLimitThreadId_.isSome()) {
    258    return;
    259  }
    260 
    261  MOZ_ASSERT(*stackLimitThreadId_ == GetTid());
    262 }
    263 #endif
    264 
    265 #ifdef __wasi__
    266 void FrontendContext::incWasiRecursionDepth() {
    267  if (maybeCx_) {
    268    IncWasiRecursionDepth(maybeCx_);
    269  }
    270 }
    271 
    272 void FrontendContext::decWasiRecursionDepth() {
    273  if (maybeCx_) {
    274    DecWasiRecursionDepth(maybeCx_);
    275  }
    276 }
    277 
    278 bool FrontendContext::checkWasiRecursionLimit() {
    279  if (maybeCx_) {
    280    return CheckWasiRecursionLimit(maybeCx_);
    281  }
    282  return true;
    283 }
    284 
    285 JS_PUBLIC_API void js::IncWasiRecursionDepth(FrontendContext* fc) {
    286  fc->incWasiRecursionDepth();
    287 }
    288 
    289 JS_PUBLIC_API void js::DecWasiRecursionDepth(FrontendContext* fc) {
    290  fc->decWasiRecursionDepth();
    291 }
    292 
    293 JS_PUBLIC_API bool js::CheckWasiRecursionLimit(FrontendContext* fc) {
    294  return fc->checkWasiRecursionLimit();
    295 }
    296 #endif  // __wasi__
    297 
    298 FrontendContext* js::NewFrontendContext() {
    299  UniquePtr<FrontendContext> fc = MakeUnique<FrontendContext>();
    300  if (!fc) {
    301    return nullptr;
    302  }
    303 
    304  if (!fc->allocateOwnedPool()) {
    305    return nullptr;
    306  }
    307 
    308  return fc.release();
    309 }
    310 
    311 JS_PUBLIC_API FrontendContext* JS::NewFrontendContext() {
    312  MOZ_ASSERT(JS::detail::libraryInitState == JS::detail::InitState::Running,
    313             "must call JS_Init prior to creating any FrontendContexts");
    314 
    315  return js::NewFrontendContext();
    316 }
    317 
    318 void js::DestroyFrontendContext(FrontendContext* fc) { js_delete_poison(fc); }
    319 
    320 JS_PUBLIC_API void JS::DestroyFrontendContext(FrontendContext* fc) {
    321  return js::DestroyFrontendContext(fc);
    322 }
    323 
    324 #ifdef DEBUG
    325 void FrontendContext::checkAndUpdateFrontendContextRecursionLimit(void* sp) {
    326  // For the js::MinimumStackLimitMargin to be effective, it should be larger
    327  // than the largest stack space which might be consumed by successive calls
    328  // to AutoCheckRecursionLimit::check.
    329  //
    330  // This function asserts that this property holds by recalling the stack
    331  // pointer of the previous call and comparing the consumed stack size with
    332  // the minimum margin.
    333  //
    334  // If this property does not hold, either the stack limit should be increased
    335  // or more calls to check for recursion should be added.
    336  if (previousStackPointer_ != nullptr) {
    337 #  if JS_STACK_GROWTH_DIRECTION > 0
    338    if (sp > previousStackPointer_) {
    339      size_t diff = uintptr_t(sp) - uintptr_t(previousStackPointer_);
    340      MOZ_ASSERT(diff < js::MinimumStackLimitMargin);
    341    }
    342 #  else
    343    if (sp < previousStackPointer_) {
    344      size_t diff = uintptr_t(previousStackPointer_) - uintptr_t(sp);
    345      MOZ_ASSERT(diff < js::MinimumStackLimitMargin);
    346    }
    347 #  endif
    348  }
    349  previousStackPointer_ = sp;
    350 }
    351 
    352 void js::CheckAndUpdateFrontendContextRecursionLimit(FrontendContext* fc,
    353                                                     void* sp) {
    354  fc->checkAndUpdateFrontendContextRecursionLimit(sp);
    355 }
    356 #endif
    357 
    358 JS_PUBLIC_API const JSErrorReport* JS::GetFrontendErrorReport(
    359    JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& options) {
    360  if (!fc->maybeError().isSome()) {
    361    return nullptr;
    362  }
    363  return fc->maybeError().ptr();
    364 }
    365 
    366 JS_PUBLIC_API bool JS::HadFrontendOverRecursed(JS::FrontendContext* fc) {
    367  return fc->hadOverRecursed();
    368 }
    369 
    370 JS_PUBLIC_API bool JS::HadFrontendOutOfMemory(JS::FrontendContext* fc) {
    371  return fc->hadOutOfMemory();
    372 }
    373 
    374 JS_PUBLIC_API bool JS::HadFrontendAllocationOverflow(JS::FrontendContext* fc) {
    375  return fc->hadAllocationOverflow();
    376 }
    377 
    378 JS_PUBLIC_API size_t JS::GetFrontendWarningCount(JS::FrontendContext* fc) {
    379  return fc->warnings().length();
    380 }
    381 
    382 JS_PUBLIC_API const JSErrorReport* JS::GetFrontendWarningAt(
    383    JS::FrontendContext* fc, size_t index,
    384    const JS::ReadOnlyCompileOptions& options) {
    385  return &fc->warnings()[index];
    386 }