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/. */
#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 "mozilla/Variant.h" // mozilla::Variant, mozilla::AsVariant
#include <stddef.h> // size_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/Stack.h" // JS::NativeStackLimit
#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 {
class FrontendContext;
#ifdef __wasi__
extern MOZ_COLD JS_PUBLIC_API void IncWasiRecursionDepth(JSContext* cx);
extern MOZ_COLD JS_PUBLIC_API void DecWasiRecursionDepth(JSContext* cx);
extern MOZ_COLD JS_PUBLIC_API bool CheckWasiRecursionLimit(JSContext* cx);
extern MOZ_COLD JS_PUBLIC_API void IncWasiRecursionDepth(FrontendContext* fc);
extern MOZ_COLD JS_PUBLIC_API void DecWasiRecursionDepth(FrontendContext* fc);
extern MOZ_COLD JS_PUBLIC_API bool CheckWasiRecursionLimit(FrontendContext* fc);
#endif // __wasi__
// The minimum margin for stack limit to ensure that the periodic
// AutoCheckRecursionLimit operation is sufficient.
//
// See FrontendContext::checkAndUpdateFrontendContextRecursionLimit and
// JS::ThreadStackQuotaForSize.
static constexpr size_t MinimumStackLimitMargin = 32 * 1024;
// 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(
JS::NativeStackLimit limit, void* sp) const;
MOZ_ALWAYS_INLINE JS::NativeStackLimit getStackLimitSlow(JSContext* cx) const;
MOZ_ALWAYS_INLINE JS::NativeStackLimit getStackLimitHelper(
JSContext* cx, JS::StackKind kind, int extraAllowance) const;
JS::NativeStackLimit getStackLimit(FrontendContext* fc) const;
JS_PUBLIC_API JS::StackKind stackKindForCurrentPrincipal(JSContext* cx) const;
#ifdef __wasi__
// The JSContext outlives AutoCheckRecursionLimit so it is safe to use raw
// pointer here.
mozilla::Variant<JSContext*, FrontendContext*> context_;
#endif // __wasi__
public:
explicit MOZ_ALWAYS_INLINE AutoCheckRecursionLimit(JSContext* cx)
#ifdef __wasi__
: context_(mozilla::AsVariant(cx))
#endif // __wasi__
{
#ifdef __wasi__
incWasiRecursionDepth();
#endif // __wasi__
}
explicit MOZ_ALWAYS_INLINE AutoCheckRecursionLimit(FrontendContext* fc)
#ifdef __wasi__
: context_(mozilla::AsVariant(fc))
#endif // __wasi__
{
#ifdef __wasi__
incWasiRecursionDepth();
#endif // __wasi__
}
MOZ_ALWAYS_INLINE ~AutoCheckRecursionLimit() {
#ifdef __wasi__
decWasiRecursionDepth();
#endif // __wasi__
}
#ifdef __wasi__
MOZ_ALWAYS_INLINE void incWasiRecursionDepth() {
if (context_.is<JSContext*>()) {
JSContext* cx = context_.as<JSContext*>();
IncWasiRecursionDepth(cx);
} else {
FrontendContext* fc = context_.as<FrontendContext*>();
IncWasiRecursionDepth(fc);
}
}
MOZ_ALWAYS_INLINE void decWasiRecursionDepth() {
if (context_.is<JSContext*>()) {
JSContext* cx = context_.as<JSContext*>();
DecWasiRecursionDepth(cx);
} else {
FrontendContext* fc = context_.as<FrontendContext*>();
DecWasiRecursionDepth(fc);
}
}
MOZ_ALWAYS_INLINE bool checkWasiRecursionLimit() const {
if (context_.is<JSContext*>()) {
JSContext* cx = context_.as<JSContext*>();
if (!CheckWasiRecursionLimit(cx)) {
return false;
}
} else {
FrontendContext* fc = context_.as<FrontendContext*>();
if (!CheckWasiRecursionLimit(fc)) {
return false;
}
}
return true;
}
#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 check(FrontendContext* fc) const;
[[nodiscard]] MOZ_ALWAYS_INLINE bool checkDontReport(JSContext* cx) const;
[[nodiscard]] MOZ_ALWAYS_INLINE bool checkDontReport(
FrontendContext* fc) 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 checkWithStackPointerDontReport(
FrontendContext* fc, 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 checkConservativeDontReport(
JS::NativeStackLimit limit) 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);
extern MOZ_COLD JS_PUBLIC_API void ReportOverRecursed(FrontendContext* fc);
MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkLimitImpl(
JS::NativeStackLimit limit, void* sp) const {
JS_STACK_OOM_POSSIBLY_FAIL();
#ifdef __wasi__
if (!checkWasiRecursionLimit()) {
return false;
}
#endif // __wasi__
#if JS_STACK_GROWTH_DIRECTION > 0
return MOZ_LIKELY(JS::NativeStackLimit(sp) < limit);
#else
return MOZ_LIKELY(JS::NativeStackLimit(sp) > limit);
#endif
}
#ifdef ENABLE_WASM_JSPI
bool IsSuspendableStackActive(JSContext* cx);
JS::NativeStackLimit GetSuspendableStackLimit(JSContext* cx);
#endif
MOZ_ALWAYS_INLINE JS::NativeStackLimit
AutoCheckRecursionLimit::getStackLimitSlow(JSContext* cx) const {
JS::StackKind kind = stackKindForCurrentPrincipal(cx);
#ifdef ENABLE_WASM_JSPI
if (IsSuspendableStackActive(cx)) {
MOZ_RELEASE_ASSERT(kind == JS::StackForUntrustedScript);
return GetSuspendableStackLimit(cx);
}
#endif
return getStackLimitHelper(cx, kind, 0);
}
MOZ_ALWAYS_INLINE JS::NativeStackLimit
AutoCheckRecursionLimit::getStackLimitHelper(JSContext* cx, JS::StackKind kind,
int extraAllowance) const {
JS::NativeStackLimit 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::check(
FrontendContext* fc) const {
if (MOZ_UNLIKELY(!checkDontReport(fc))) {
ReportOverRecursed(fc);
return false;
}
return true;
}
MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkDontReport(
JSContext* cx) const {
return checkWithStackPointerDontReport(cx, __builtin_frame_address(0));
}
MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkDontReport(
FrontendContext* fc) const {
return checkWithStackPointerDontReport(fc, __builtin_frame_address(0));
}
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 stackKindForCurrentPrincipal to determine which stack limit to
// use. To work around this, check the untrusted limit first to avoid the
// overhead in most cases.
JS::NativeStackLimit untrustedLimit =
getStackLimitHelper(cx, JS::StackForUntrustedScript, 0);
if (MOZ_LIKELY(checkLimitImpl(untrustedLimit, sp))) {
return true;
}
return checkLimitImpl(getStackLimitSlow(cx), sp);
}
#ifdef DEBUG
extern void CheckAndUpdateFrontendContextRecursionLimit(FrontendContext* fc,
void* sp);
#endif
MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkWithStackPointerDontReport(
FrontendContext* fc, void* sp) const {
#ifdef DEBUG
CheckAndUpdateFrontendContextRecursionLimit(fc, sp);
#endif
return checkLimitImpl(getStackLimit(fc), sp);
}
MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkWithExtra(
JSContext* cx, size_t extra) const {
char* sp = reinterpret_cast<char*>(__builtin_frame_address(0));
#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 {
JS::NativeStackLimit limit =
getStackLimitHelper(cx, JS::StackForSystemCode, 0);
return checkLimitImpl(limit, __builtin_frame_address(0));
}
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 {
JS::NativeStackLimit limit = getStackLimitHelper(
cx, JS::StackForUntrustedScript, -4096 * int(sizeof(size_t)));
return checkLimitImpl(limit, __builtin_frame_address(0));
}
MOZ_ALWAYS_INLINE bool AutoCheckRecursionLimit::checkConservativeDontReport(
JS::NativeStackLimit limit) const {
return checkLimitImpl(limit, __builtin_frame_address(0));
}
} // namespace js
#endif // js_friend_StackLimits_h