tor-browser

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

StackLimits.h (10906B)


      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 #ifndef js_friend_StackLimits_h
      8 #define js_friend_StackLimits_h
      9 
     10 #include "mozilla/Attributes.h"  // MOZ_ALWAYS_INLINE, MOZ_COLD
     11 #include "mozilla/Likely.h"      // MOZ_LIKELY
     12 #include "mozilla/Variant.h"     // mozilla::Variant, mozilla::AsVariant
     13 
     14 #include <stddef.h>  // size_t
     15 
     16 #include "jstypes.h"  // JS_PUBLIC_API
     17 
     18 #include "js/HeapAPI.h"  // JS::StackKind, JS::StackForTrustedScript, JS::StackForUntrustedScript
     19 #include "js/RootingAPI.h"  // JS::RootingContext
     20 #include "js/Stack.h"       // JS::NativeStackLimit
     21 #include "js/Utility.h"     // JS_STACK_OOM_POSSIBLY_FAIL
     22 
     23 struct JS_PUBLIC_API JSContext;
     24 
     25 #ifndef JS_STACK_GROWTH_DIRECTION
     26 #  ifdef __hppa
     27 #    define JS_STACK_GROWTH_DIRECTION (1)
     28 #  else
     29 #    define JS_STACK_GROWTH_DIRECTION (-1)
     30 #  endif
     31 #endif
     32 
     33 namespace js {
     34 
     35 class FrontendContext;
     36 
     37 #ifdef __wasi__
     38 extern MOZ_COLD JS_PUBLIC_API void IncWasiRecursionDepth(JSContext* cx);
     39 extern MOZ_COLD JS_PUBLIC_API void DecWasiRecursionDepth(JSContext* cx);
     40 extern MOZ_COLD JS_PUBLIC_API bool CheckWasiRecursionLimit(JSContext* cx);
     41 
     42 extern MOZ_COLD JS_PUBLIC_API void IncWasiRecursionDepth(FrontendContext* fc);
     43 extern MOZ_COLD JS_PUBLIC_API void DecWasiRecursionDepth(FrontendContext* fc);
     44 extern MOZ_COLD JS_PUBLIC_API bool CheckWasiRecursionLimit(FrontendContext* fc);
     45 #endif  // __wasi__
     46 
     47 // The minimum margin for stack limit to ensure that the periodic
     48 // AutoCheckRecursionLimit operation is sufficient.
     49 //
     50 // See FrontendContext::checkAndUpdateFrontendContextRecursionLimit and
     51 // JS::ThreadStackQuotaForSize.
     52 static constexpr size_t MinimumStackLimitMargin = 32 * 1024;
     53 
     54 // AutoCheckRecursionLimit can be used to check whether we're close to using up
     55 // the C++ stack.
     56 //
     57 // Typical usage is like this:
     58 //
     59 //   AutoCheckRecursionLimit recursion(cx);
     60 //   if (!recursion.check(cx)) {
     61 //     return false;
     62 //   }
     63 //
     64 // The check* functions return |false| if we are close to the stack limit.
     65 // They also report an overrecursion error, except for the DontReport variants.
     66 //
     67 // The checkSystem variant gives us a little extra space so we can ensure that
     68 // crucial code is able to run.
     69 //
     70 // checkConservative allows less space than any other check, including a safety
     71 // buffer (as in, it uses the untrusted limit and subtracts a little more from
     72 // it).
     73 class MOZ_RAII AutoCheckRecursionLimit {
     74  [[nodiscard]] MOZ_ALWAYS_INLINE bool checkLimitImpl(
     75      JS::NativeStackLimit limit, void* sp) const;
     76 
     77  MOZ_ALWAYS_INLINE JS::NativeStackLimit getStackLimitSlow(JSContext* cx) const;
     78  MOZ_ALWAYS_INLINE JS::NativeStackLimit getStackLimitHelper(
     79      JSContext* cx, JS::StackKind kind, int extraAllowance) const;
     80 
     81  JS::NativeStackLimit getStackLimit(FrontendContext* fc) const;
     82 
     83  JS_PUBLIC_API JS::StackKind stackKindForCurrentPrincipal(JSContext* cx) const;
     84 
     85 #ifdef __wasi__
     86  // The JSContext outlives AutoCheckRecursionLimit so it is safe to use raw
     87  // pointer here.
     88  mozilla::Variant<JSContext*, FrontendContext*> context_;
     89 #endif  // __wasi__
     90 
     91 public:
     92  explicit MOZ_ALWAYS_INLINE AutoCheckRecursionLimit(JSContext* cx)
     93 #ifdef __wasi__
     94      : context_(mozilla::AsVariant(cx))
     95 #endif  // __wasi__
     96  {
     97 #ifdef __wasi__
     98    incWasiRecursionDepth();
     99 #endif  // __wasi__
    100  }
    101 
    102  explicit MOZ_ALWAYS_INLINE AutoCheckRecursionLimit(FrontendContext* fc)
    103 #ifdef __wasi__
    104      : context_(mozilla::AsVariant(fc))
    105 #endif  // __wasi__
    106  {
    107 #ifdef __wasi__
    108    incWasiRecursionDepth();
    109 #endif  // __wasi__
    110  }
    111 
    112  MOZ_ALWAYS_INLINE ~AutoCheckRecursionLimit() {
    113 #ifdef __wasi__
    114    decWasiRecursionDepth();
    115 #endif  // __wasi__
    116  }
    117 
    118 #ifdef __wasi__
    119  MOZ_ALWAYS_INLINE void incWasiRecursionDepth() {
    120    if (context_.is<JSContext*>()) {
    121      JSContext* cx = context_.as<JSContext*>();
    122      IncWasiRecursionDepth(cx);
    123    } else {
    124      FrontendContext* fc = context_.as<FrontendContext*>();
    125      IncWasiRecursionDepth(fc);
    126    }
    127  }
    128 
    129  MOZ_ALWAYS_INLINE void decWasiRecursionDepth() {
    130    if (context_.is<JSContext*>()) {
    131      JSContext* cx = context_.as<JSContext*>();
    132      DecWasiRecursionDepth(cx);
    133    } else {
    134      FrontendContext* fc = context_.as<FrontendContext*>();
    135      DecWasiRecursionDepth(fc);
    136    }
    137  }
    138 
    139  MOZ_ALWAYS_INLINE bool checkWasiRecursionLimit() const {
    140    if (context_.is<JSContext*>()) {
    141      JSContext* cx = context_.as<JSContext*>();
    142      if (!CheckWasiRecursionLimit(cx)) {
    143        return false;
    144      }
    145    } else {
    146      FrontendContext* fc = context_.as<FrontendContext*>();
    147      if (!CheckWasiRecursionLimit(fc)) {
    148        return false;
    149      }
    150    }
    151 
    152    return true;
    153  }
    154 #endif  // __wasi__
    155 
    156  AutoCheckRecursionLimit(const AutoCheckRecursionLimit&) = delete;
    157  void operator=(const AutoCheckRecursionLimit&) = delete;
    158 
    159  [[nodiscard]] MOZ_ALWAYS_INLINE bool check(JSContext* cx) const;
    160  [[nodiscard]] MOZ_ALWAYS_INLINE bool check(FrontendContext* fc) const;
    161  [[nodiscard]] MOZ_ALWAYS_INLINE bool checkDontReport(JSContext* cx) const;
    162  [[nodiscard]] MOZ_ALWAYS_INLINE bool checkDontReport(
    163      FrontendContext* fc) const;
    164  [[nodiscard]] MOZ_ALWAYS_INLINE bool checkWithExtra(JSContext* cx,
    165                                                      size_t extra) const;
    166  [[nodiscard]] MOZ_ALWAYS_INLINE bool checkWithExtraDontReport(
    167      JSContext* cx, size_t extra) const;
    168  [[nodiscard]] MOZ_ALWAYS_INLINE bool checkWithStackPointerDontReport(
    169      JSContext* cx, void* sp) const;
    170  [[nodiscard]] MOZ_ALWAYS_INLINE bool checkWithStackPointerDontReport(
    171      FrontendContext* fc, void* sp) const;
    172 
    173  [[nodiscard]] MOZ_ALWAYS_INLINE bool checkConservative(JSContext* cx) const;
    174  [[nodiscard]] MOZ_ALWAYS_INLINE bool checkConservativeDontReport(
    175      JSContext* cx) const;
    176  [[nodiscard]] MOZ_ALWAYS_INLINE bool checkConservativeDontReport(
    177      JS::NativeStackLimit limit) const;
    178 
    179  [[nodiscard]] MOZ_ALWAYS_INLINE bool checkSystem(JSContext* cx) const;
    180  [[nodiscard]] MOZ_ALWAYS_INLINE bool checkSystemDontReport(
    181      JSContext* cx) const;
    182 };
    183 
    184 extern MOZ_COLD JS_PUBLIC_API void ReportOverRecursed(JSContext* maybecx);
    185 extern MOZ_COLD JS_PUBLIC_API void ReportOverRecursed(FrontendContext* fc);
    186 
    187 MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkLimitImpl(
    188    JS::NativeStackLimit limit, void* sp) const {
    189  JS_STACK_OOM_POSSIBLY_FAIL();
    190 
    191 #ifdef __wasi__
    192  if (!checkWasiRecursionLimit()) {
    193    return false;
    194  }
    195 #endif  // __wasi__
    196 
    197 #if JS_STACK_GROWTH_DIRECTION > 0
    198  return MOZ_LIKELY(JS::NativeStackLimit(sp) < limit);
    199 #else
    200  return MOZ_LIKELY(JS::NativeStackLimit(sp) > limit);
    201 #endif
    202 }
    203 
    204 MOZ_ALWAYS_INLINE JS::NativeStackLimit
    205 AutoCheckRecursionLimit::getStackLimitSlow(JSContext* cx) const {
    206  JS::StackKind kind = stackKindForCurrentPrincipal(cx);
    207  return getStackLimitHelper(cx, kind, 0);
    208 }
    209 
    210 MOZ_ALWAYS_INLINE JS::NativeStackLimit
    211 AutoCheckRecursionLimit::getStackLimitHelper(JSContext* cx, JS::StackKind kind,
    212                                             int extraAllowance) const {
    213  JS::NativeStackLimit limit =
    214      JS::RootingContext::get(cx)->nativeStackLimit[kind];
    215 #if JS_STACK_GROWTH_DIRECTION > 0
    216  limit += extraAllowance;
    217 #else
    218  limit -= extraAllowance;
    219 #endif
    220  return limit;
    221 }
    222 
    223 MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::check(JSContext* cx) const {
    224  if (MOZ_UNLIKELY(!checkDontReport(cx))) {
    225    ReportOverRecursed(cx);
    226    return false;
    227  }
    228  return true;
    229 }
    230 
    231 MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::check(
    232    FrontendContext* fc) const {
    233  if (MOZ_UNLIKELY(!checkDontReport(fc))) {
    234    ReportOverRecursed(fc);
    235    return false;
    236  }
    237  return true;
    238 }
    239 
    240 MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkDontReport(
    241    JSContext* cx) const {
    242  return checkWithStackPointerDontReport(cx, __builtin_frame_address(0));
    243 }
    244 
    245 MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkDontReport(
    246    FrontendContext* fc) const {
    247  return checkWithStackPointerDontReport(fc, __builtin_frame_address(0));
    248 }
    249 
    250 MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkWithStackPointerDontReport(
    251    JSContext* cx, void* sp) const {
    252  // getStackLimitSlow(cx) is pretty slow because it has to do an uninlined
    253  // call to stackKindForCurrentPrincipal to determine which stack limit to
    254  // use. To work around this, check the untrusted limit first to avoid the
    255  // overhead in most cases.
    256  JS::NativeStackLimit untrustedLimit =
    257      getStackLimitHelper(cx, JS::StackForUntrustedScript, 0);
    258  if (MOZ_LIKELY(checkLimitImpl(untrustedLimit, sp))) {
    259    return true;
    260  }
    261  return checkLimitImpl(getStackLimitSlow(cx), sp);
    262 }
    263 
    264 #ifdef DEBUG
    265 extern void CheckAndUpdateFrontendContextRecursionLimit(FrontendContext* fc,
    266                                                        void* sp);
    267 #endif
    268 
    269 MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkWithStackPointerDontReport(
    270    FrontendContext* fc, void* sp) const {
    271 #ifdef DEBUG
    272  CheckAndUpdateFrontendContextRecursionLimit(fc, sp);
    273 #endif
    274  return checkLimitImpl(getStackLimit(fc), sp);
    275 }
    276 
    277 MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkWithExtra(
    278    JSContext* cx, size_t extra) const {
    279  if (MOZ_UNLIKELY(!checkWithExtraDontReport(cx, extra))) {
    280    ReportOverRecursed(cx);
    281    return false;
    282  }
    283  return true;
    284 }
    285 
    286 MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkWithExtraDontReport(
    287    JSContext* cx, size_t extra) const {
    288  char* sp = reinterpret_cast<char*>(__builtin_frame_address(0));
    289 #if JS_STACK_GROWTH_DIRECTION > 0
    290  sp += extra;
    291 #else
    292  sp -= extra;
    293 #endif
    294  return checkWithStackPointerDontReport(cx, sp);
    295 }
    296 
    297 MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkSystem(
    298    JSContext* cx) const {
    299  if (MOZ_UNLIKELY(!checkSystemDontReport(cx))) {
    300    ReportOverRecursed(cx);
    301    return false;
    302  }
    303  return true;
    304 }
    305 
    306 MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkSystemDontReport(
    307    JSContext* cx) const {
    308  JS::NativeStackLimit limit =
    309      getStackLimitHelper(cx, JS::StackForSystemCode, 0);
    310  return checkLimitImpl(limit, __builtin_frame_address(0));
    311 }
    312 
    313 MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkConservative(
    314    JSContext* cx) const {
    315  if (MOZ_UNLIKELY(!checkConservativeDontReport(cx))) {
    316    ReportOverRecursed(cx);
    317    return false;
    318  }
    319  return true;
    320 }
    321 
    322 MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkConservativeDontReport(
    323    JSContext* cx) const {
    324  JS::NativeStackLimit limit = getStackLimitHelper(
    325      cx, JS::StackForUntrustedScript, -4096 * int(sizeof(size_t)));
    326  return checkLimitImpl(limit, __builtin_frame_address(0));
    327 }
    328 
    329 MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkConservativeDontReport(
    330    JS::NativeStackLimit limit) const {
    331  return checkLimitImpl(limit, __builtin_frame_address(0));
    332 }
    333 
    334 }  // namespace js
    335 
    336 #endif  // js_friend_StackLimits_h