Bug 1726737 - Part 1: Add public header for stack operations. r=arai

Adds a new public header for any stack related functionality. This includes
JS stack as well as native stack related functions.

Differential Revision: https://phabricator.services.mozilla.com/D123192
This commit is contained in:
André Bargull 2021-08-20 18:10:32 +00:00
parent fa930a9e95
commit 700a62d471
16 changed files with 212 additions and 174 deletions

197
js/public/Stack.h Normal file
View File

@ -0,0 +1,197 @@
/* -*- 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"
#include "mozilla/Maybe.h"
#include "mozilla/Variant.h"
#include <stddef.h>
#include <stdint.h>
#include <utility>
#include "jstypes.h"
#include "js/Principals.h"
#include "js/TypeDecls.h"
/**
* 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, size_t systemCodeStackSize,
size_t trustedScriptStackSize = 0, size_t 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

View File

@ -20,6 +20,7 @@
#include "js/ForOfIterator.h" // JS::ForOfIterator
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/PropertySpec.h"
#include "js/Stack.h"
#include "vm/ArrayObject.h"
#include "vm/AsyncFunction.h"
#include "vm/AsyncIteration.h"

View File

@ -84,6 +84,7 @@
#include "js/RegExpFlags.h" // JS::RegExpFlag, JS::RegExpFlags
#include "js/SourceText.h"
#include "js/StableStringChars.h"
#include "js/Stack.h"
#include "js/String.h" // JS::GetLinearStringLength, JS::StringToLinearString
#include "js/StructuredClone.h"
#include "js/UbiNode.h"

View File

@ -11,6 +11,7 @@
#include "js/AllocPolicy.h"
#include "js/Initialization.h"
#include "js/RootingAPI.h"
#include "js/Stack.h"
#include "vm/JSContext.h"
#ifdef LIBFUZZER

View File

@ -11,6 +11,7 @@
#include "js/Exception.h"
#include "js/SavedFrameAPI.h"
#include "js/SourceText.h" // JS::Source{Ownership,Text}
#include "js/Stack.h"
#include "jsapi-tests/tests.h"
#include "util/Text.h"
#include "vm/ArrayObject.h"

View File

@ -55,6 +55,7 @@
#include "js/PropertySpec.h"
#include "js/Proxy.h"
#include "js/StableStringChars.h"
#include "js/Stack.h"
#include "js/String.h" // JS::MaxStringLength
#include "js/Symbol.h"
#include "js/Utility.h"

View File

@ -51,6 +51,7 @@
#include "js/RealmOptions.h"
#include "js/RefCounted.h"
#include "js/RootingAPI.h"
#include "js/Stack.h"
#include "js/String.h"
#include "js/TracingAPI.h"
#include "js/Transcoding.h"
@ -378,28 +379,6 @@ extern JS_PUBLIC_API bool IsProfileTimelineRecordingEnabled();
} // 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, size_t systemCodeStackSize,
size_t trustedScriptStackSize = 0, size_t untrustedScriptStackSize = 0);
/************************************************************************/
extern JS_PUBLIC_API bool JS_ValueToId(JSContext* cx, JS::HandleValue v,
@ -1277,27 +1256,6 @@ class MOZ_RAII AutoHideScriptedCaller {
JSContext* mContext;
};
} /* namespace JS */
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 {
/**
* Attempt to disable Wasm's usage of reserving a large virtual memory
* allocation to avoid bounds checking overhead. This must be called before any
@ -1369,137 +1327,6 @@ extern JS_PUBLIC_API void SetWaitCallback(JSRuntime* rt,
AfterWaitCallback afterWait,
size_t requiredMemory);
/**
* 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);
/**
* Return true iff the given object is either a SavedFrame object or wrapper
* around a SavedFrame object, and it is not the SavedFrame.prototype object.

View File

@ -36,6 +36,7 @@
#include "js/Object.h" // JS::GetBuiltinClass
#include "js/PropertyAndElement.h" // JS_GetProperty, JS_HasProperty
#include "js/SavedFrameAPI.h"
#include "js/Stack.h"
#include "js/UniquePtr.h"
#include "js/Value.h"
#include "js/Warnings.h" // JS::{,Set}WarningReporter

View File

@ -195,6 +195,7 @@ EXPORTS.js += [
"../public/SliceBudget.h",
"../public/SourceText.h",
"../public/StableStringChars.h",
"../public/Stack.h",
"../public/Stream.h",
"../public/String.h",
"../public/StructuredClone.h",

View File

@ -142,6 +142,7 @@
#include "js/RegExp.h" // JS::ObjectIsRegExp
#include "js/SourceText.h"
#include "js/StableStringChars.h"
#include "js/Stack.h"
#include "js/StructuredClone.h"
#include "js/SweepingAPI.h"
#include "js/Transcoding.h" // JS::TranscodeBuffer, JS::TranscodeRange

View File

@ -35,6 +35,7 @@
#include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit
#include "js/PropertySpec.h"
#include "js/RootingAPI.h"
#include "js/Stack.h"
#include "js/TypeDecls.h"
#include "js/Utility.h"
#include "js/Value.h"

View File

@ -25,6 +25,7 @@
#include "js/HelperThreadAPI.h"
#include "js/OffThreadScriptCompilation.h" // JS::OffThreadToken, JS::OffThreadCompileCallback
#include "js/SourceText.h"
#include "js/Stack.h"
#include "js/UniquePtr.h"
#include "js/Utility.h"
#include "threading/CpuCount.h"

View File

@ -46,6 +46,7 @@
#include "js/friend/StackLimits.h" // js::ReportOverRecursed
#include "js/Printf.h"
#include "js/PropertyAndElement.h" // JS_GetProperty
#include "js/Stack.h"
#include "util/DiagnosticAssertions.h"
#include "util/DifferentialTesting.h"
#include "util/DoubleToString.h"

View File

@ -47,6 +47,7 @@
#ifdef DEBUG
# include "js/Proxy.h" // For AutoEnterPolicy
#endif
#include "js/Stack.h"
#include "js/Stream.h" // JS::AbortSignalIsAborted
#include "js/Symbol.h"
#include "js/UniquePtr.h"

View File

@ -27,6 +27,7 @@
#include "js/PropertyAndElement.h" // JS_DefineProperty, JS_GetProperty
#include "js/PropertySpec.h"
#include "js/SavedFrameAPI.h"
#include "js/Stack.h"
#include "js/Vector.h"
#include "util/DifferentialTesting.h"
#include "util/StringBuffer.h"

View File

@ -12,6 +12,7 @@
#include "mozilla/Maybe.h"
#include "js/HashTable.h"
#include "js/Stack.h"
#include "js/Wrapper.h"
#include "vm/JSContext.h"
#include "vm/SavedFrame.h"