diff options
Diffstat (limited to '')
-rw-r--r-- | js/public/Stack.h | 234 |
1 files changed, 234 insertions, 0 deletions
diff --git a/js/public/Stack.h b/js/public/Stack.h new file mode 100644 index 0000000000..6f01b2b728 --- /dev/null +++ b/js/public/Stack.h @@ -0,0 +1,234 @@ +/* -*- 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_Stack_h +#define js_Stack_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Maybe.h" // mozilla::Maybe +#include "mozilla/Variant.h" // mozilla::Variant + +#include <stddef.h> // size_t +#include <stdint.h> // uint32_t, uintptr_t, UINTPTR_MAX +#include <utility> // std::move + +#include "jstypes.h" // JS_PUBLIC_API + +#include "js/Principals.h" // JSPrincipals, JS_HoldPrincipals, JS_DropPrincipals +#include "js/TypeDecls.h" // JSContext, Handle*, MutableHandle* + +namespace JS { + +using NativeStackSize = size_t; + +using NativeStackBase = uintptr_t; + +using NativeStackLimit = uintptr_t; + +#if JS_STACK_GROWTH_DIRECTION > 0 +constexpr NativeStackLimit NativeStackLimitMin = 0; +constexpr NativeStackLimit NativeStackLimitMax = UINTPTR_MAX; +#else +constexpr NativeStackLimit NativeStackLimitMin = UINTPTR_MAX; +constexpr NativeStackLimit NativeStackLimitMax = 0; +#endif + +#ifdef __wasi__ +// We build with the "stack-first" wasm-ld option, so the stack grows downward +// toward zero. Let's set a limit just a bit above this so that we catch an +// overflow before a Wasm trap occurs. +constexpr NativeStackLimit WASINativeStackLimit = 1024; +#endif // __wasi__ + +inline NativeStackLimit GetNativeStackLimit(NativeStackBase base, + NativeStackSize size) { +#if JS_STACK_GROWTH_DIRECTION > 0 + MOZ_ASSERT(base <= size_t(-1) - size); + return base + size - 1; +#else // stack grows up + MOZ_ASSERT(base >= size); + return base - (size - 1); +#endif // stack grows down +} + +} // namespace JS + +/** + * Set the size of the native stack that should not be exceed. To disable + * stack size checking pass 0. + * + * SpiderMonkey allows for a distinction between system code (such as GCs, which + * may incidentally be triggered by script but are not strictly performed on + * behalf of such script), trusted script (as determined by + * JS_SetTrustedPrincipals), and untrusted script. Each kind of code may have a + * different stack quota, allowing embedders to keep higher-priority machinery + * running in the face of scripted stack exhaustion by something else. + * + * The stack quotas for each kind of code should be monotonically descending, + * and may be specified with this function. If 0 is passed for a given kind + * of code, it defaults to the value of the next-highest-priority kind. + * + * This function may only be called immediately after the runtime is initialized + * and before any code is executed and/or interrupts requested. + */ +extern JS_PUBLIC_API void JS_SetNativeStackQuota( + JSContext* cx, JS::NativeStackSize systemCodeStackSize, + JS::NativeStackSize trustedScriptStackSize = 0, + JS::NativeStackSize untrustedScriptStackSize = 0); + +namespace js { + +enum class StackFormat { SpiderMonkey, V8, Default }; + +/* + * Sets the format used for stringifying Error stacks. + * + * The default format is StackFormat::SpiderMonkey. Use StackFormat::V8 + * in order to emulate V8's stack formatting. StackFormat::Default can't be + * used here. + */ +extern JS_PUBLIC_API void SetStackFormat(JSContext* cx, StackFormat format); + +extern JS_PUBLIC_API StackFormat GetStackFormat(JSContext* cx); + +} // namespace js + +namespace JS { + +/** + * Capture all frames. + */ +struct AllFrames {}; + +/** + * Capture at most this many frames. + */ +struct MaxFrames { + uint32_t maxFrames; + + explicit MaxFrames(uint32_t max) : maxFrames(max) { MOZ_ASSERT(max > 0); } +}; + +/** + * Capture the first frame with the given principals. By default, do not + * consider self-hosted frames with the given principals as satisfying the stack + * capture. + */ +struct JS_PUBLIC_API FirstSubsumedFrame { + JSContext* cx; + JSPrincipals* principals; + bool ignoreSelfHosted; + + /** + * Use the cx's current compartment's principals. + */ + explicit FirstSubsumedFrame(JSContext* cx, + bool ignoreSelfHostedFrames = true); + + explicit FirstSubsumedFrame(JSContext* ctx, JSPrincipals* p, + bool ignoreSelfHostedFrames = true) + : cx(ctx), principals(p), ignoreSelfHosted(ignoreSelfHostedFrames) { + if (principals) { + JS_HoldPrincipals(principals); + } + } + + // No copying because we want to avoid holding and dropping principals + // unnecessarily. + FirstSubsumedFrame(const FirstSubsumedFrame&) = delete; + FirstSubsumedFrame& operator=(const FirstSubsumedFrame&) = delete; + + FirstSubsumedFrame(FirstSubsumedFrame&& rhs) + : principals(rhs.principals), ignoreSelfHosted(rhs.ignoreSelfHosted) { + MOZ_ASSERT(this != &rhs, "self move disallowed"); + rhs.principals = nullptr; + } + + FirstSubsumedFrame& operator=(FirstSubsumedFrame&& rhs) { + new (this) FirstSubsumedFrame(std::move(rhs)); + return *this; + } + + ~FirstSubsumedFrame() { + if (principals) { + JS_DropPrincipals(cx, principals); + } + } +}; + +using StackCapture = mozilla::Variant<AllFrames, MaxFrames, FirstSubsumedFrame>; + +/** + * Capture the current call stack as a chain of SavedFrame JSObjects, and set + * |stackp| to the SavedFrame for the youngest stack frame, or nullptr if there + * are no JS frames on the stack. + * + * The |capture| parameter describes the portion of the JS stack to capture: + * + * * |JS::AllFrames|: Capture all frames on the stack. + * + * * |JS::MaxFrames|: Capture no more than |JS::MaxFrames::maxFrames| from the + * stack. + * + * * |JS::FirstSubsumedFrame|: Capture the first frame whose principals are + * subsumed by |JS::FirstSubsumedFrame::principals|. By default, do not + * consider self-hosted frames; this can be controlled via the + * |JS::FirstSubsumedFrame::ignoreSelfHosted| flag. Do not capture any async + * stack. + */ +extern JS_PUBLIC_API bool CaptureCurrentStack( + JSContext* cx, MutableHandleObject stackp, + StackCapture&& capture = StackCapture(AllFrames())); + +/** + * Returns true if capturing stack trace data to associate with an asynchronous + * operation is currently enabled for the current context realm. + * + * Users should check this state before capturing a stack that will be passed + * back to AutoSetAsyncStackForNewCalls later, in order to avoid capturing a + * stack for async use when we don't actually want to capture it. + */ +extern JS_PUBLIC_API bool IsAsyncStackCaptureEnabledForRealm(JSContext* cx); + +/* + * This is a utility function for preparing an async stack to be used + * by some other object. This may be used when you need to treat a + * given stack trace as an async parent. If you just need to capture + * the current stack, async parents and all, use CaptureCurrentStack + * instead. + * + * Here |asyncStack| is the async stack to prepare. It is copied into + * |cx|'s current compartment, and the newest frame is given + * |asyncCause| as its asynchronous cause. If |maxFrameCount| is + * |Some(n)|, capture at most the youngest |n| frames. The + * new stack object is written to |stackp|. Returns true on success, + * or sets an exception and returns |false| on error. + */ +extern JS_PUBLIC_API bool CopyAsyncStack( + JSContext* cx, HandleObject asyncStack, HandleString asyncCause, + MutableHandleObject stackp, const mozilla::Maybe<size_t>& maxFrameCount); + +/** + * Given a SavedFrame JSObject stack, stringify it in the same format as + * Error.prototype.stack. The stringified stack out parameter is placed in the + * cx's compartment. Defaults to the empty string. + * + * The same notes above about SavedFrame accessors applies here as well: cx + * doesn't need to be in stack's compartment, and stack can be null, a + * SavedFrame object, or a wrapper (CCW or Xray) around a SavedFrame object. + * SavedFrames not subsumed by |principals| are skipped. + * + * Optional indent parameter specifies the number of white spaces to indent + * each line. + */ +extern JS_PUBLIC_API bool BuildStackString( + JSContext* cx, JSPrincipals* principals, HandleObject stack, + MutableHandleString stringp, size_t indent = 0, + js::StackFormat stackFormat = js::StackFormat::Default); + +} // namespace JS + +#endif // js_Stack_h |