/* -*- 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/. */ #ifndef js_friend_StackLimits_h #define js_friend_StackLimits_h #include "mozilla/Attributes.h" // MOZ_ALWAYS_INLINE, MOZ_COLD #include "mozilla/Likely.h" // MOZ_LIKELY #include // size_t #include // uintptr_t #include "jstypes.h" // JS_PUBLIC_API #include "js/HeapAPI.h" // JS::StackKind, JS::StackForTrustedScript, JS::StackForUntrustedScript #include "js/RootingAPI.h" // JS::RootingContext #include "js/Utility.h" // JS_STACK_OOM_POSSIBLY_FAIL struct JS_PUBLIC_API JSContext; #ifndef JS_STACK_GROWTH_DIRECTION # ifdef __hppa # define JS_STACK_GROWTH_DIRECTION (1) # else # define JS_STACK_GROWTH_DIRECTION (-1) # endif #endif namespace js { // AutoCheckRecursionLimit can be used to check whether we're close to using up // the C++ stack. // // Typical usage is like this: // // AutoCheckRecursionLimit recursion(cx); // if (!recursion.check(cx)) { // return false; // } // // The check* functions return |false| if we are close to the stack limit. // They also report an overrecursion error, except for the DontReport variants. // // The checkSystem variant gives us a little extra space so we can ensure that // crucial code is able to run. // // checkConservative allows less space than any other check, including a safety // buffer (as in, it uses the untrusted limit and subtracts a little more from // it). class MOZ_RAII AutoCheckRecursionLimit { [[nodiscard]] MOZ_ALWAYS_INLINE bool checkLimitImpl(uintptr_t limit, void* sp) const; MOZ_ALWAYS_INLINE uintptr_t getStackLimitSlow(JSContext* cx) const; MOZ_ALWAYS_INLINE uintptr_t getStackLimitHelper(JSContext* cx, JS::StackKind kind, int extraAllowance) const; JS_PUBLIC_API bool runningWithTrustedPrincipals(JSContext* cx) const; #ifdef __wasi__ // The JSContext outlives AutoCheckRecursionLimit so it is safe to use raw // pointer here. JSContext* cx_; #endif // __wasi__ public: explicit MOZ_ALWAYS_INLINE AutoCheckRecursionLimit(JSContext* cx) { #ifdef __wasi__ cx_ = cx; ++JS::RootingContext::get(cx_)->wasiRecursionDepth; #endif // __wasi__ } MOZ_ALWAYS_INLINE ~AutoCheckRecursionLimit() { #ifdef __wasi__ MOZ_ASSERT(JS::RootingContext::get(cx_)->wasiRecursionDepth > 0); --JS::RootingContext::get(cx_)->wasiRecursionDepth; #endif // __wasi__ } AutoCheckRecursionLimit(const AutoCheckRecursionLimit&) = delete; void operator=(const AutoCheckRecursionLimit&) = delete; [[nodiscard]] MOZ_ALWAYS_INLINE bool check(JSContext* cx) const; [[nodiscard]] MOZ_ALWAYS_INLINE bool checkDontReport(JSContext* cx) const; [[nodiscard]] MOZ_ALWAYS_INLINE bool checkWithExtra(JSContext* cx, size_t extra) const; [[nodiscard]] MOZ_ALWAYS_INLINE bool checkWithStackPointerDontReport( JSContext* cx, void* sp) const; [[nodiscard]] MOZ_ALWAYS_INLINE bool checkConservative(JSContext* cx) const; [[nodiscard]] MOZ_ALWAYS_INLINE bool checkConservativeDontReport( JSContext* cx) const; [[nodiscard]] MOZ_ALWAYS_INLINE bool checkSystem(JSContext* cx) const; [[nodiscard]] MOZ_ALWAYS_INLINE bool checkSystemDontReport( JSContext* cx) const; }; extern MOZ_COLD JS_PUBLIC_API void ReportOverRecursed(JSContext* maybecx); MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkLimitImpl(uintptr_t limit, void* sp) const { JS_STACK_OOM_POSSIBLY_FAIL(); #ifdef __wasi__ // 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 wasiRecursionDepth_ that counts recursion // depth. Here we should check both. if (JS::RootingContext::get(cx_)->wasiRecursionDepth >= JS::RootingContext::wasiRecursionDepthLimit) { return false; } #endif // __wasi__ #if JS_STACK_GROWTH_DIRECTION > 0 return MOZ_LIKELY(uintptr_t(sp) < limit); #else return MOZ_LIKELY(uintptr_t(sp) > limit); #endif } MOZ_ALWAYS_INLINE uintptr_t AutoCheckRecursionLimit::getStackLimitSlow(JSContext* cx) const { JS::StackKind kind = runningWithTrustedPrincipals(cx) ? JS::StackForTrustedScript : JS::StackForUntrustedScript; return getStackLimitHelper(cx, kind, 0); } MOZ_ALWAYS_INLINE uintptr_t AutoCheckRecursionLimit::getStackLimitHelper( JSContext* cx, JS::StackKind kind, int extraAllowance) const { uintptr_t limit = JS::RootingContext::get(cx)->nativeStackLimit[kind]; #if JS_STACK_GROWTH_DIRECTION > 0 limit += extraAllowance; #else limit -= extraAllowance; #endif return limit; } MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::check(JSContext* cx) const { if (MOZ_UNLIKELY(!checkDontReport(cx))) { ReportOverRecursed(cx); return false; } return true; } MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkDontReport( JSContext* cx) const { int stackDummy; return checkWithStackPointerDontReport(cx, &stackDummy); } MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkWithStackPointerDontReport( JSContext* cx, void* sp) const { // getStackLimitSlow(cx) is pretty slow because it has to do an uninlined // call to runningWithTrustedPrincipals to determine which stack limit to // use. To work around this, check the untrusted limit first to avoid the // overhead in most cases. uintptr_t untrustedLimit = getStackLimitHelper(cx, JS::StackForUntrustedScript, 0); if (MOZ_LIKELY(checkLimitImpl(untrustedLimit, sp))) { return true; } return checkLimitImpl(getStackLimitSlow(cx), sp); } MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkWithExtra( JSContext* cx, size_t extra) const { char stackDummy; char* sp = &stackDummy; #if JS_STACK_GROWTH_DIRECTION > 0 sp += extra; #else sp -= extra; #endif if (MOZ_UNLIKELY(!checkWithStackPointerDontReport(cx, sp))) { ReportOverRecursed(cx); return false; } return true; } MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkSystem( JSContext* cx) const { if (MOZ_UNLIKELY(!checkSystemDontReport(cx))) { ReportOverRecursed(cx); return false; } return true; } MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkSystemDontReport( JSContext* cx) const { uintptr_t limit = getStackLimitHelper(cx, JS::StackForSystemCode, 0); int stackDummy; return checkLimitImpl(limit, &stackDummy); } MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkConservative( JSContext* cx) const { if (MOZ_UNLIKELY(!checkConservativeDontReport(cx))) { ReportOverRecursed(cx); return false; } return true; } MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkConservativeDontReport( JSContext* cx) const { uintptr_t limit = getStackLimitHelper(cx, JS::StackForUntrustedScript, -1024 * int(sizeof(size_t))); int stackDummy; return checkLimitImpl(limit, &stackDummy); } } // namespace js #endif // js_friend_StackLimits_h