summaryrefslogtreecommitdiffstats
path: root/js/src/vm/FrameIter.h
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /js/src/vm/FrameIter.h
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--js/src/vm/FrameIter.h619
1 files changed, 619 insertions, 0 deletions
diff --git a/js/src/vm/FrameIter.h b/js/src/vm/FrameIter.h
new file mode 100644
index 0000000000..ebd36f0d70
--- /dev/null
+++ b/js/src/vm/FrameIter.h
@@ -0,0 +1,619 @@
+/* -*- 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 vm_FrameIter_h
+#define vm_FrameIter_h
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Atomics.h" // mozilla::Atomic, mozilla::Relaxed
+#include "mozilla/Attributes.h" // MOZ_IMPLICIT, MOZ_RAII
+#include "mozilla/Maybe.h" // mozilla::Maybe
+#include "mozilla/MaybeOneOf.h" // mozilla::MaybeOneOf
+
+#include <stddef.h> // size_t
+#include <stdint.h> // uint8_t, uint32_t, uintptr_t
+
+#include "jstypes.h" // JS_PUBLIC_API
+
+#include "jit/JSJitFrameIter.h" // js::jit::{InlineFrameIterator,JSJitFrameIter}
+#include "js/RootingAPI.h" // JS::Handle, JS::Rooted
+#include "js/TypeDecls.h" // jsbytecode, JSContext, JSAtom, JSFunction, JSObject, JSScript
+#include "js/UniquePtr.h" // js::UniquePtr
+#include "js/Value.h" // JS::Value
+#include "vm/Activation.h" // js::InterpreterActivation
+#include "vm/Stack.h" // js::{AbstractFramePtr,MaybeCheckAliasing}
+#include "wasm/WasmConstants.h" // js::wasm::Trap
+#include "wasm/WasmFrameIter.h" // js::wasm::{ExitReason,RegisterState,WasmFrameIter}
+#include "wasm/WasmTypes.h" // js::wasm::{Frame,TrapData}
+
+struct JSPrincipals;
+
+namespace JS {
+
+class JS_PUBLIC_API Compartment;
+class JS_PUBLIC_API Realm;
+
+} // namespace JS
+
+namespace js {
+
+class ArgumentsObject;
+class CallObject;
+class InterpreterFrame;
+
+namespace jit {
+class CommonFrameLayout;
+class JitActivation;
+} // namespace jit
+
+namespace wasm {
+class Instance;
+} // namespace wasm
+
+// Iterates over the frames of a single InterpreterActivation.
+class InterpreterFrameIterator {
+ InterpreterActivation* activation_;
+ InterpreterFrame* fp_;
+ jsbytecode* pc_;
+ JS::Value* sp_;
+
+ public:
+ explicit InterpreterFrameIterator(InterpreterActivation* activation)
+ : activation_(activation), fp_(nullptr), pc_(nullptr), sp_(nullptr) {
+ if (activation) {
+ fp_ = activation->current();
+ pc_ = activation->regs().pc;
+ sp_ = activation->regs().sp;
+ }
+ }
+
+ InterpreterFrame* frame() const {
+ MOZ_ASSERT(!done());
+ return fp_;
+ }
+ jsbytecode* pc() const {
+ MOZ_ASSERT(!done());
+ return pc_;
+ }
+ JS::Value* sp() const {
+ MOZ_ASSERT(!done());
+ return sp_;
+ }
+
+ InterpreterFrameIterator& operator++();
+
+ bool done() const { return fp_ == nullptr; }
+};
+
+// A JitFrameIter can iterate over all kind of frames emitted by our code
+// generators, be they composed of JS jit frames or wasm frames, interleaved or
+// not, in any order.
+//
+// In the following class:
+// - code generated for JS is referred to as JSJit.
+// - code generated for wasm is referred to as Wasm.
+// Also, Jit refers to any one of them.
+//
+// JitFrameIter uses JSJitFrameIter to iterate over JSJit code or a
+// WasmFrameIter to iterate over wasm code; only one of them is active at the
+// time. When a sub-iterator is done, the JitFrameIter knows how to stop, move
+// onto the next activation or move onto another kind of Jit code.
+//
+// For ease of use, there is also OnlyJSJitFrameIter, which skips all the
+// non-JSJit frames.
+//
+// Note it is allowed to get a handle to the internal frame iterator via
+// asJSJit() and asWasm(), but the user has to be careful not to have those be
+// used after JitFrameIter leaves the scope or the operator++ is called.
+//
+// In particular, this can handle the transition from wasm to jit and from jit
+// to wasm, since these can be interleaved in the same JitActivation.
+class JitFrameIter {
+ protected:
+ jit::JitActivation* act_ = nullptr;
+ mozilla::MaybeOneOf<jit::JSJitFrameIter, wasm::WasmFrameIter> iter_ = {};
+ bool mustUnwindActivation_ = false;
+
+ void settle();
+
+ public:
+ JitFrameIter() = default;
+
+ explicit JitFrameIter(jit::JitActivation* activation,
+ bool mustUnwindActivation = false);
+
+ explicit JitFrameIter(const JitFrameIter& another);
+ JitFrameIter& operator=(const JitFrameIter& another);
+
+ bool isSome() const { return !iter_.empty(); }
+ void reset() {
+ MOZ_ASSERT(isSome());
+ iter_.destroy();
+ }
+
+ bool isJSJit() const {
+ return isSome() && iter_.constructed<jit::JSJitFrameIter>();
+ }
+ jit::JSJitFrameIter& asJSJit() { return iter_.ref<jit::JSJitFrameIter>(); }
+ const jit::JSJitFrameIter& asJSJit() const {
+ return iter_.ref<jit::JSJitFrameIter>();
+ }
+
+ bool isWasm() const {
+ return isSome() && iter_.constructed<wasm::WasmFrameIter>();
+ }
+ wasm::WasmFrameIter& asWasm() { return iter_.ref<wasm::WasmFrameIter>(); }
+ const wasm::WasmFrameIter& asWasm() const {
+ return iter_.ref<wasm::WasmFrameIter>();
+ }
+
+ // Operations common to all frame iterators.
+ const jit::JitActivation* activation() const { return act_; }
+ bool done() const;
+ void operator++();
+
+ JS::Realm* realm() const;
+
+ // Returns the address of the next instruction that will execute in this
+ // frame, once control returns to this frame.
+ uint8_t* resumePCinCurrentFrame() const;
+
+ // Operations which have an effect only on JIT frames.
+ void skipNonScriptedJSFrames();
+
+ // Returns true iff this is a JIT frame with a self-hosted script. Note: be
+ // careful, JitFrameIter does not consider functions inlined by Ion.
+ bool isSelfHostedIgnoringInlining() const;
+};
+
+// A JitFrameIter that skips all the non-JSJit frames, skipping interleaved
+// frames of any another kind.
+
+class OnlyJSJitFrameIter : public JitFrameIter {
+ void settle() {
+ while (!done() && !isJSJit()) {
+ JitFrameIter::operator++();
+ }
+ }
+
+ public:
+ explicit OnlyJSJitFrameIter(jit::JitActivation* act);
+ explicit OnlyJSJitFrameIter(JSContext* cx);
+ explicit OnlyJSJitFrameIter(const ActivationIterator& cx);
+
+ void operator++() {
+ JitFrameIter::operator++();
+ settle();
+ }
+
+ const jit::JSJitFrameIter& frame() const { return asJSJit(); }
+};
+
+class ScriptSource;
+
+// A FrameIter walks over a context's stack of JS script activations,
+// abstracting over whether the JS scripts were running in the interpreter or
+// different modes of compiled code.
+//
+// FrameIter is parameterized by what it includes in the stack iteration:
+// - When provided, the optional JSPrincipal argument will cause FrameIter to
+// only show frames in globals whose JSPrincipals are subsumed (via
+// JSSecurityCallbacks::subsume) by the given JSPrincipal.
+//
+// Additionally, there are derived FrameIter types that automatically skip
+// certain frames:
+// - ScriptFrameIter only shows frames that have an associated JSScript
+// (currently everything other than wasm stack frames). When !hasScript(),
+// clients must stick to the portion of the
+// interface marked below.
+// - NonBuiltinScriptFrameIter additionally filters out builtin (self-hosted)
+// scripts.
+class FrameIter {
+ public:
+ enum DebuggerEvalOption {
+ FOLLOW_DEBUGGER_EVAL_PREV_LINK,
+ IGNORE_DEBUGGER_EVAL_PREV_LINK
+ };
+
+ enum State {
+ DONE, // when there are no more frames nor activations to unwind.
+ INTERP, // interpreter activation on the stack
+ JIT // jit or wasm activations on the stack
+ };
+
+ // Unlike ScriptFrameIter itself, ScriptFrameIter::Data can be allocated on
+ // the heap, so this structure should not contain any GC things.
+ struct Data {
+ JSContext* cx_;
+ DebuggerEvalOption debuggerEvalOption_;
+ JSPrincipals* principals_;
+
+ State state_;
+
+ jsbytecode* pc_;
+
+ InterpreterFrameIterator interpFrames_;
+ ActivationIterator activations_;
+
+ JitFrameIter jitFrames_;
+ unsigned ionInlineFrameNo_;
+
+ Data(JSContext* cx, DebuggerEvalOption debuggerEvalOption,
+ JSPrincipals* principals);
+ Data(const Data& other);
+ };
+
+ explicit FrameIter(JSContext* cx,
+ DebuggerEvalOption = FOLLOW_DEBUGGER_EVAL_PREV_LINK);
+ FrameIter(JSContext* cx, DebuggerEvalOption, JSPrincipals*);
+ FrameIter(const FrameIter& iter);
+ MOZ_IMPLICIT FrameIter(const Data& data);
+ MOZ_IMPLICIT FrameIter(AbstractFramePtr frame);
+
+ bool done() const { return data_.state_ == DONE; }
+
+ // -------------------------------------------------------
+ // The following functions can only be called when !done()
+ // -------------------------------------------------------
+
+ FrameIter& operator++();
+
+ JS::Realm* realm() const;
+ JS::Compartment* compartment() const;
+ Activation* activation() const { return data_.activations_.activation(); }
+
+ bool isInterp() const {
+ MOZ_ASSERT(!done());
+ return data_.state_ == INTERP;
+ }
+ bool isJSJit() const {
+ MOZ_ASSERT(!done());
+ return data_.state_ == JIT && data_.jitFrames_.isJSJit();
+ }
+ bool isWasm() const {
+ MOZ_ASSERT(!done());
+ return data_.state_ == JIT && data_.jitFrames_.isWasm();
+ }
+
+ inline bool isIon() const;
+ inline bool isBaseline() const;
+ inline bool isPhysicalJitFrame() const;
+
+ bool isEvalFrame() const;
+ bool isModuleFrame() const;
+ bool isFunctionFrame() const;
+ bool hasArgs() const { return isFunctionFrame(); }
+
+ ScriptSource* scriptSource() const;
+ const char* filename() const;
+ const char16_t* displayURL() const;
+ unsigned computeLine(uint32_t* column = nullptr) const;
+ JSAtom* maybeFunctionDisplayAtom() const;
+ bool mutedErrors() const;
+
+ bool hasScript() const { return !isWasm(); }
+
+ // -----------------------------------------------------------
+ // The following functions can only be called when isWasm()
+ // -----------------------------------------------------------
+
+ inline bool wasmDebugEnabled() const;
+ inline wasm::Instance* wasmInstance() const;
+ inline uint32_t wasmFuncIndex() const;
+ inline unsigned wasmBytecodeOffset() const;
+ void wasmUpdateBytecodeOffset();
+
+ // -----------------------------------------------------------
+ // The following functions can only be called when hasScript()
+ // -----------------------------------------------------------
+
+ inline JSScript* script() const;
+
+ bool isConstructing() const;
+ jsbytecode* pc() const {
+ MOZ_ASSERT(!done());
+ return data_.pc_;
+ }
+ void updatePcQuadratic();
+
+ // The function |calleeTemplate()| returns either the function from which
+ // the current |callee| was cloned or the |callee| if it can be read. As
+ // long as we do not have to investigate the environment chain or build a
+ // new frame, we should prefer to use |calleeTemplate| instead of
+ // |callee|, as requesting the |callee| might cause the invalidation of
+ // the frame. (see js::Lambda)
+ JSFunction* calleeTemplate() const;
+ JSFunction* callee(JSContext* cx) const;
+
+ JSFunction* maybeCallee(JSContext* cx) const {
+ return isFunctionFrame() ? callee(cx) : nullptr;
+ }
+
+ bool matchCallee(JSContext* cx, JS::Handle<JSFunction*> fun) const;
+
+ unsigned numActualArgs() const;
+ unsigned numFormalArgs() const;
+ JS::Value unaliasedActual(unsigned i,
+ MaybeCheckAliasing = CHECK_ALIASING) const;
+ template <class Op>
+ inline void unaliasedForEachActual(JSContext* cx, Op op);
+
+ JSObject* environmentChain(JSContext* cx) const;
+ bool hasInitialEnvironment(JSContext* cx) const;
+ CallObject& callObj(JSContext* cx) const;
+
+ bool hasArgsObj() const;
+ ArgumentsObject& argsObj() const;
+
+ // Get the original |this| value passed to this function. May not be the
+ // actual this-binding (for instance, derived class constructors will
+ // change their this-value later and non-strict functions will box
+ // primitives).
+ JS::Value thisArgument(JSContext* cx) const;
+
+ JS::Value newTarget() const;
+
+ JS::Value returnValue() const;
+ void setReturnValue(const JS::Value& v);
+
+ // These are only valid for the top frame.
+ size_t numFrameSlots() const;
+ JS::Value frameSlotValue(size_t index) const;
+
+ // Ensures that we have rematerialized the top frame and its associated
+ // inline frames. Can only be called when isIon().
+ bool ensureHasRematerializedFrame(JSContext* cx);
+
+ // True when isInterp() or isBaseline(). True when isIon() if it
+ // has a rematerialized frame. False otherwise.
+ bool hasUsableAbstractFramePtr() const;
+
+ // -----------------------------------------------------------
+ // The following functions can only be called when isInterp(),
+ // isBaseline(), isWasm() or isIon(). Further, abstractFramePtr() can
+ // only be called when hasUsableAbstractFramePtr().
+ // -----------------------------------------------------------
+
+ AbstractFramePtr abstractFramePtr() const;
+ Data* copyData() const;
+
+ // This can only be called when isInterp():
+ inline InterpreterFrame* interpFrame() const;
+
+ // This can only be called when isPhysicalJitFrame():
+ inline jit::CommonFrameLayout* physicalJitFrame() const;
+
+ // This is used to provide a raw interface for debugging.
+ void* rawFramePtr() const;
+
+ private:
+ Data data_;
+ jit::InlineFrameIterator ionInlineFrames_;
+
+ const jit::JSJitFrameIter& jsJitFrame() const {
+ return data_.jitFrames_.asJSJit();
+ }
+ const wasm::WasmFrameIter& wasmFrame() const {
+ return data_.jitFrames_.asWasm();
+ }
+
+ jit::JSJitFrameIter& jsJitFrame() { return data_.jitFrames_.asJSJit(); }
+ wasm::WasmFrameIter& wasmFrame() { return data_.jitFrames_.asWasm(); }
+
+ bool isIonScripted() const {
+ return isJSJit() && jsJitFrame().isIonScripted();
+ }
+
+ bool principalsSubsumeFrame() const;
+
+ void popActivation();
+ void popInterpreterFrame();
+ void nextJitFrame();
+ void popJitFrame();
+ void settleOnActivation();
+};
+
+class ScriptFrameIter : public FrameIter {
+ void settle() {
+ while (!done() && !hasScript()) {
+ FrameIter::operator++();
+ }
+ }
+
+ public:
+ explicit ScriptFrameIter(
+ JSContext* cx,
+ DebuggerEvalOption debuggerEvalOption = FOLLOW_DEBUGGER_EVAL_PREV_LINK)
+ : FrameIter(cx, debuggerEvalOption) {
+ settle();
+ }
+
+ ScriptFrameIter(JSContext* cx, DebuggerEvalOption debuggerEvalOption,
+ JSPrincipals* prin)
+ : FrameIter(cx, debuggerEvalOption, prin) {
+ settle();
+ }
+
+ ScriptFrameIter(const ScriptFrameIter& iter) : FrameIter(iter) { settle(); }
+ explicit ScriptFrameIter(const FrameIter::Data& data) : FrameIter(data) {
+ settle();
+ }
+ explicit ScriptFrameIter(AbstractFramePtr frame) : FrameIter(frame) {
+ settle();
+ }
+
+ ScriptFrameIter& operator++() {
+ FrameIter::operator++();
+ settle();
+ return *this;
+ }
+};
+
+#ifdef DEBUG
+bool SelfHostedFramesVisible();
+#else
+static inline bool SelfHostedFramesVisible() { return false; }
+#endif
+
+/* A filtering of the FrameIter to only stop at non-self-hosted scripts. */
+class NonBuiltinFrameIter : public FrameIter {
+ void settle();
+
+ public:
+ explicit NonBuiltinFrameIter(
+ JSContext* cx, FrameIter::DebuggerEvalOption debuggerEvalOption =
+ FrameIter::FOLLOW_DEBUGGER_EVAL_PREV_LINK)
+ : FrameIter(cx, debuggerEvalOption) {
+ settle();
+ }
+
+ NonBuiltinFrameIter(JSContext* cx,
+ FrameIter::DebuggerEvalOption debuggerEvalOption,
+ JSPrincipals* principals)
+ : FrameIter(cx, debuggerEvalOption, principals) {
+ settle();
+ }
+
+ NonBuiltinFrameIter(JSContext* cx, JSPrincipals* principals)
+ : FrameIter(cx, FrameIter::FOLLOW_DEBUGGER_EVAL_PREV_LINK, principals) {
+ settle();
+ }
+
+ explicit NonBuiltinFrameIter(const FrameIter::Data& data) : FrameIter(data) {}
+
+ NonBuiltinFrameIter& operator++() {
+ FrameIter::operator++();
+ settle();
+ return *this;
+ }
+};
+
+// A filtering of the ScriptFrameIter to only stop at non-self-hosted scripts.
+class NonBuiltinScriptFrameIter : public ScriptFrameIter {
+ void settle();
+
+ public:
+ explicit NonBuiltinScriptFrameIter(
+ JSContext* cx, ScriptFrameIter::DebuggerEvalOption debuggerEvalOption =
+ ScriptFrameIter::FOLLOW_DEBUGGER_EVAL_PREV_LINK)
+ : ScriptFrameIter(cx, debuggerEvalOption) {
+ settle();
+ }
+
+ NonBuiltinScriptFrameIter(
+ JSContext* cx, ScriptFrameIter::DebuggerEvalOption debuggerEvalOption,
+ JSPrincipals* principals)
+ : ScriptFrameIter(cx, debuggerEvalOption, principals) {
+ settle();
+ }
+
+ explicit NonBuiltinScriptFrameIter(const ScriptFrameIter::Data& data)
+ : ScriptFrameIter(data) {}
+
+ NonBuiltinScriptFrameIter& operator++() {
+ ScriptFrameIter::operator++();
+ settle();
+ return *this;
+ }
+};
+
+/*
+ * Blindly iterate over all frames in the current thread's stack. These frames
+ * can be from different contexts and compartments, so beware.
+ */
+class AllFramesIter : public FrameIter {
+ public:
+ explicit AllFramesIter(JSContext* cx)
+ : FrameIter(cx, ScriptFrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK) {}
+};
+
+/* Iterates over all script frame in the current thread's stack.
+ * See also AllFramesIter and ScriptFrameIter.
+ */
+class AllScriptFramesIter : public ScriptFrameIter {
+ public:
+ explicit AllScriptFramesIter(JSContext* cx)
+ : ScriptFrameIter(cx, ScriptFrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK) {}
+};
+
+/* Popular inline definitions. */
+
+inline JSScript* FrameIter::script() const {
+ MOZ_ASSERT(!done());
+ MOZ_ASSERT(hasScript());
+ if (data_.state_ == INTERP) {
+ return interpFrame()->script();
+ }
+ if (jsJitFrame().isIonJS()) {
+ return ionInlineFrames_.script();
+ }
+ return jsJitFrame().script();
+}
+
+inline bool FrameIter::wasmDebugEnabled() const {
+ MOZ_ASSERT(!done());
+ MOZ_ASSERT(isWasm());
+ return wasmFrame().debugEnabled();
+}
+
+inline wasm::Instance* FrameIter::wasmInstance() const {
+ MOZ_ASSERT(!done());
+ MOZ_ASSERT(isWasm());
+ return wasmFrame().instance();
+}
+
+inline unsigned FrameIter::wasmBytecodeOffset() const {
+ MOZ_ASSERT(!done());
+ MOZ_ASSERT(isWasm());
+ return wasmFrame().lineOrBytecode();
+}
+
+inline uint32_t FrameIter::wasmFuncIndex() const {
+ MOZ_ASSERT(!done());
+ MOZ_ASSERT(isWasm());
+ return wasmFrame().funcIndex();
+}
+
+inline bool FrameIter::isIon() const {
+ return isJSJit() && jsJitFrame().isIonJS();
+}
+
+inline bool FrameIter::isBaseline() const {
+ return isJSJit() && jsJitFrame().isBaselineJS();
+}
+
+inline InterpreterFrame* FrameIter::interpFrame() const {
+ MOZ_ASSERT(data_.state_ == INTERP);
+ return data_.interpFrames_.frame();
+}
+
+inline bool FrameIter::isPhysicalJitFrame() const {
+ if (!isJSJit()) {
+ return false;
+ }
+
+ auto& jitFrame = jsJitFrame();
+
+ if (jitFrame.isBaselineJS()) {
+ return true;
+ }
+
+ if (jitFrame.isIonScripted()) {
+ // Only the bottom of a group of inlined Ion frames is a physical frame.
+ return ionInlineFrames_.frameNo() == 0;
+ }
+
+ return false;
+}
+
+inline jit::CommonFrameLayout* FrameIter::physicalJitFrame() const {
+ MOZ_ASSERT(isPhysicalJitFrame());
+ return jsJitFrame().current();
+}
+
+} // namespace js
+
+#endif // vm_FrameIter_h