diff options
Diffstat (limited to '')
-rw-r--r-- | js/src/vm/FrameIter.h | 586 |
1 files changed, 586 insertions, 0 deletions
diff --git a/js/src/vm/FrameIter.h b/js/src/vm/FrameIter.h new file mode 100644 index 0000000000..c3561e0247 --- /dev/null +++ b/js/src/vm/FrameIter.h @@ -0,0 +1,586 @@ +/* -*- 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/Attributes.h" // MOZ_IMPLICIT, MOZ_RAII +#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/Value.h" // JS::Value +#include "vm/Activation.h" // js::InterpreterActivation +#include "vm/Stack.h" // js::{AbstractFramePtr,MaybeCheckAliasing} +#include "wasm/WasmFrameIter.h" // js::wasm::{ExitReason,RegisterState,WasmFrameIter} + +struct JSPrincipals; + +namespace JS { + +class JS_PUBLIC_API Compartment; +class JS_PUBLIC_API Realm; + +} // namespace JS + +namespace js { + +class ArgumentsObject; +class CallObject; + +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(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 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; + + bool inPrologue() 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& 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(); + } + + 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& 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 |