/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * 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_Stack_h #define vm_Stack_h #include "mozilla/Atomics.h" #include "mozilla/Maybe.h" #include "mozilla/MemoryReporting.h" #include "mozilla/Variant.h" #include "jsfun.h" #include "jsscript.h" #include "jsutil.h" #include "gc/Rooting.h" #include "jit/JitFrameIterator.h" #ifdef CHECK_OSIPOINT_REGISTERS #include "jit/Registers.h" // for RegisterDump #endif #include "js/RootingAPI.h" #include "vm/ArgumentsObject.h" #include "vm/SavedFrame.h" #include "wasm/WasmFrameIterator.h" struct JSCompartment; namespace JS { namespace dbg { #ifdef JS_BROKEN_GCC_ATTRIBUTE_WARNING #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wattributes" #endif // JS_BROKEN_GCC_ATTRIBUTE_WARNING class JS_PUBLIC_API(AutoEntryMonitor); #ifdef JS_BROKEN_GCC_ATTRIBUTE_WARNING #pragma GCC diagnostic pop #endif // JS_BROKEN_GCC_ATTRIBUTE_WARNING } // namespace dbg } // namespace JS namespace js { class InterpreterRegs; class CallObject; class FrameIter; class EnvironmentObject; class ScriptFrameIter; class SPSProfiler; class InterpreterFrame; class LexicalEnvironmentObject; class EnvironmentIter; class EnvironmentCoordinate; class SavedFrame; namespace jit { class CommonFrameLayout; } namespace wasm { class Instance; } // VM stack layout // // A JSRuntime's stack consists of a linked list of activations. Every activation // contains a number of scripted frames that are either running in the interpreter // (InterpreterActivation) or JIT code (JitActivation). The frames inside a single // activation are contiguous: whenever C++ calls back into JS, a new activation is // pushed. // // Every activation is tied to a single JSContext and JSCompartment. This means we // can reconstruct a given context's stack by skipping activations belonging to other // contexts. This happens whenever an embedding enters the JS engine on cx1 and // then, from a native called by the JS engine, reenters the VM on cx2. // Interpreter frames (InterpreterFrame) // // Each interpreter script activation (global or function code) is given a // fixed-size header (js::InterpreterFrame). The frame contains bookkeeping // information about the activation and links to the previous frame. // // The values after an InterpreterFrame in memory are its locals followed by its // expression stack. InterpreterFrame::argv_ points to the frame's arguments. // Missing formal arguments are padded with |undefined|, so the number of // arguments is always >= the number of formals. // // The top of an activation's current frame's expression stack is pointed to by // the activation's "current regs", which contains the stack pointer 'sp'. In // the interpreter, sp is adjusted as individual values are pushed and popped // from the stack and the InterpreterRegs struct (pointed to by the // InterpreterActivation) is a local var of js::Interpret. enum MaybeCheckAliasing { CHECK_ALIASING = true, DONT_CHECK_ALIASING = false }; enum MaybeCheckTDZ { CheckTDZ = true, DontCheckTDZ = false }; /*****************************************************************************/ namespace jit { class BaselineFrame; class RematerializedFrame; } // namespace jit /* * Pointer to either a ScriptFrameIter::Data, an InterpreterFrame, or a Baseline * JIT frame. * * The Debugger may cache ScriptFrameIter::Data as a bookmark to reconstruct a * ScriptFrameIter without doing a full stack walk. * * There is no way to directly create such an AbstractFramePtr. To do so, the * user must call ScriptFrameIter::copyDataAsAbstractFramePtr(). * * ScriptFrameIter::abstractFramePtr() will never return an AbstractFramePtr * that is in fact a ScriptFrameIter::Data. * * To recover a ScriptFrameIter settled at the location pointed to by an * AbstractFramePtr, use the THIS_FRAME_ITER macro in Debugger.cpp. As an * aside, no asScriptFrameIterData() is provided because C++ is stupid and * cannot forward declare inner classes. */ class AbstractFramePtr { friend class FrameIter; uintptr_t ptr_; enum { Tag_ScriptFrameIterData = 0x0, Tag_InterpreterFrame = 0x1, Tag_BaselineFrame = 0x2, Tag_RematerializedFrame = 0x3, TagMask = 0x3 }; public: AbstractFramePtr() : ptr_(0) {} MOZ_IMPLICIT AbstractFramePtr(InterpreterFrame* fp) : ptr_(fp ? uintptr_t(fp) | Tag_InterpreterFrame : 0) { MOZ_ASSERT_IF(fp, asInterpreterFrame() == fp); } MOZ_IMPLICIT AbstractFramePtr(jit::BaselineFrame* fp) : ptr_(fp ? uintptr_t(fp) | Tag_BaselineFrame : 0) { MOZ_ASSERT_IF(fp, asBaselineFrame() == fp); } MOZ_IMPLICIT AbstractFramePtr(jit::RematerializedFrame* fp) : ptr_(fp ? uintptr_t(fp) | Tag_RematerializedFrame : 0) { MOZ_ASSERT_IF(fp, asRematerializedFrame() == fp); } static AbstractFramePtr FromRaw(void* raw) { AbstractFramePtr frame; frame.ptr_ = uintptr_t(raw); return frame; } bool isScriptFrameIterData() const { return !!ptr_ && (ptr_ & TagMask) == Tag_ScriptFrameIterData; } bool isInterpreterFrame() const { return (ptr_ & TagMask) == Tag_InterpreterFrame; } InterpreterFrame* asInterpreterFrame() const { MOZ_ASSERT(isInterpreterFrame()); InterpreterFrame* res = (InterpreterFrame*)(ptr_ & ~TagMask); MOZ_ASSERT(res); return res; } bool isBaselineFrame() const { return (ptr_ & TagMask) == Tag_BaselineFrame; } jit::BaselineFrame* asBaselineFrame() const { MOZ_ASSERT(isBaselineFrame()); jit::BaselineFrame* res = (jit::BaselineFrame*)(ptr_ & ~TagMask); MOZ_ASSERT(res); return res; } bool isRematerializedFrame() const { return (ptr_ & TagMask) == Tag_RematerializedFrame; } jit::RematerializedFrame* asRematerializedFrame() const { MOZ_ASSERT(isRematerializedFrame()); jit::RematerializedFrame* res = (jit::RematerializedFrame*)(ptr_ & ~TagMask); MOZ_ASSERT(res); return res; } void* raw() const { return reinterpret_cast(ptr_); } bool operator ==(const AbstractFramePtr& other) const { return ptr_ == other.ptr_; } bool operator !=(const AbstractFramePtr& other) const { return ptr_ != other.ptr_; } explicit operator bool() const { return !!ptr_; } inline JSObject* environmentChain() const; inline CallObject& callObj() const; inline bool initFunctionEnvironmentObjects(JSContext* cx); inline bool pushVarEnvironment(JSContext* cx, HandleScope scope); template inline void pushOnEnvironmentChain(SpecificEnvironment& env); template inline void popOffEnvironmentChain(); inline JSCompartment* compartment() const; inline bool hasInitialEnvironment() const; inline bool isGlobalFrame() const; inline bool isModuleFrame() const; inline bool isEvalFrame() const; inline bool isDebuggerEvalFrame() const; inline bool hasCachedSavedFrame() const; inline void setHasCachedSavedFrame(); inline JSScript* script() const; inline JSFunction* callee() const; inline Value calleev() const; inline Value& thisArgument() const; inline Value newTarget() const; inline bool debuggerNeedsCheckPrimitiveReturn() const; inline bool isFunctionFrame() const; inline bool isNonStrictDirectEvalFrame() const; inline bool isStrictEvalFrame() const; inline unsigned numActualArgs() const; inline unsigned numFormalArgs() const; inline Value* argv() const; inline bool hasArgs() const; inline bool hasArgsObj() const; inline ArgumentsObject& argsObj() const; inline void initArgsObj(ArgumentsObject& argsobj) const; inline bool createSingleton() const; inline Value& unaliasedLocal(uint32_t i); inline Value& unaliasedFormal(unsigned i, MaybeCheckAliasing checkAliasing = CHECK_ALIASING); inline Value& unaliasedActual(unsigned i, MaybeCheckAliasing checkAliasing = CHECK_ALIASING); template inline void unaliasedForEachActual(JSContext* cx, Op op); inline bool prevUpToDate() const; inline void setPrevUpToDate() const; inline void unsetPrevUpToDate() const; inline bool isDebuggee() const; inline void setIsDebuggee(); inline void unsetIsDebuggee(); inline HandleValue returnValue() const; inline void setReturnValue(const Value& rval) const; friend void GDBTestInitAbstractFramePtr(AbstractFramePtr&, void*); friend void GDBTestInitAbstractFramePtr(AbstractFramePtr&, InterpreterFrame*); friend void GDBTestInitAbstractFramePtr(AbstractFramePtr&, jit::BaselineFrame*); friend void GDBTestInitAbstractFramePtr(AbstractFramePtr&, jit::RematerializedFrame*); }; class NullFramePtr : public AbstractFramePtr { public: NullFramePtr() : AbstractFramePtr() { } }; enum MaybeConstruct { NO_CONSTRUCT = false, CONSTRUCT = true }; /*****************************************************************************/ class InterpreterFrame { enum Flags : uint32_t { CONSTRUCTING = 0x1, /* frame is for a constructor invocation */ RESUMED_GENERATOR = 0x2, /* frame is for a resumed generator invocation */ /* Function prologue state */ HAS_INITIAL_ENV = 0x4, /* callobj created for function or var env for eval */ HAS_ARGS_OBJ = 0x8, /* ArgumentsObject created for needsArgsObj script */ /* Lazy frame initialization */ HAS_RVAL = 0x10, /* frame has rval_ set */ /* Debugger state */ PREV_UP_TO_DATE = 0x20, /* see DebugScopes::updateLiveScopes */ /* * See comment above 'isDebuggee' in jscompartment.h for explanation of * invariants of debuggee compartments, scripts, and frames. */ DEBUGGEE = 0x40, /* Execution is being observed by Debugger */ /* Used in tracking calls and profiling (see vm/SPSProfiler.cpp) */ HAS_PUSHED_SPS_FRAME = 0x80, /* SPS was notified of entry */ /* * If set, we entered one of the JITs and ScriptFrameIter should skip * this frame. */ RUNNING_IN_JIT = 0x100, /* Miscellaneous state. */ CREATE_SINGLETON = 0x200, /* Constructed |this| object should be singleton. */ /* * If set, this frame has been on the stack when * |js::SavedStacks::saveCurrentStack| was called, and so there is a * |js::SavedFrame| object cached for this frame. */ HAS_CACHED_SAVED_FRAME = 0x400, }; mutable uint32_t flags_; /* bits described by Flags */ uint32_t nactual_; /* number of actual arguments, for function frames */ JSScript* script_; /* the script we're executing */ JSObject* envChain_; /* current environment chain */ Value rval_; /* if HAS_RVAL, return value of the frame */ ArgumentsObject* argsObj_; /* if HAS_ARGS_OBJ, the call's arguments object */ /* * Previous frame and its pc and sp. Always nullptr for * InterpreterActivation's entry frame, always non-nullptr for inline * frames. */ InterpreterFrame* prev_; jsbytecode* prevpc_; Value* prevsp_; void* unused; /* * For an eval-in-frame DEBUGGER_EVAL frame, the frame in whose scope * we're evaluating code. Iteration treats this as our previous frame. */ AbstractFramePtr evalInFramePrev_; Value* argv_; /* If hasArgs(), points to frame's arguments. */ LifoAlloc::Mark mark_; /* Used to release memory for this frame. */ static void staticAsserts() { JS_STATIC_ASSERT(offsetof(InterpreterFrame, rval_) % sizeof(Value) == 0); JS_STATIC_ASSERT(sizeof(InterpreterFrame) % sizeof(Value) == 0); } /* * The utilities are private since they are not able to assert that only * unaliased vars/formals are accessed. Normal code should prefer the * InterpreterFrame::unaliased* members (or InterpreterRegs::stackDepth for * the usual "depth is at least" assertions). */ Value* slots() const { return (Value*)(this + 1); } Value* base() const { return slots() + script()->nfixed(); } friend class FrameIter; friend class InterpreterRegs; friend class InterpreterStack; friend class jit::BaselineFrame; /* * Frame initialization, called by InterpreterStack operations after acquiring * the raw memory for the frame: */ /* Used for Invoke and Interpret. */ void initCallFrame(JSContext* cx, InterpreterFrame* prev, jsbytecode* prevpc, Value* prevsp, JSFunction& callee, JSScript* script, Value* argv, uint32_t nactual, MaybeConstruct constructing); /* Used for global and eval frames. */ void initExecuteFrame(JSContext* cx, HandleScript script, AbstractFramePtr prev, const Value& newTargetValue, HandleObject envChain); public: /* * Frame prologue/epilogue * * Every stack frame must have 'prologue' called before executing the * first op and 'epilogue' called after executing the last op and before * popping the frame (whether the exit is exceptional or not). * * For inline JS calls/returns, it is easy to call the prologue/epilogue * exactly once. When calling JS from C++, Invoke/Execute push the stack * frame but do *not* call the prologue/epilogue. That means Interpret * must call the prologue/epilogue for the entry frame. This scheme * simplifies jit compilation. * * An important corner case is what happens when an error occurs (OOM, * over-recursed) after pushing the stack frame but before 'prologue' is * called or completes fully. To simplify usage, 'epilogue' does not assume * 'prologue' has completed and handles all the intermediate state details. */ bool prologue(JSContext* cx); void epilogue(JSContext* cx, jsbytecode* pc); bool checkReturn(JSContext* cx, HandleValue thisv); bool initFunctionEnvironmentObjects(JSContext* cx); /* * Initialize locals of newly-pushed frame to undefined. */ void initLocals(); /* * Stack frame type * * A stack frame may have one of four types, which determines which * members of the frame may be accessed and other invariants: * * global frame: execution of global code * function frame: execution of function code * module frame: execution of a module * eval frame: execution of eval code */ bool isGlobalFrame() const { return script_->isGlobalCode(); } bool isModuleFrame() const { return script_->module(); } bool isEvalFrame() const { return script_->isForEval(); } bool isFunctionFrame() const { return script_->functionNonDelazifying(); } inline bool isStrictEvalFrame() const { return isEvalFrame() && script()->strict(); } bool isNonStrictEvalFrame() const { return isEvalFrame() && !script()->strict(); } bool isNonGlobalEvalFrame() const; bool isNonStrictDirectEvalFrame() const { return isNonStrictEvalFrame() && isNonGlobalEvalFrame(); } /* * Previous frame * * A frame's 'prev' frame is either null or the previous frame pointed to * by cx->regs->fp when this frame was pushed. Often, given two prev-linked * frames, the next-frame is a function or eval that was called by the * prev-frame, but not always: the prev-frame may have called a native that * reentered the VM through JS_CallFunctionValue on the same context * (without calling JS_SaveFrameChain) which pushed the next-frame. Thus, * 'prev' has little semantic meaning and basically just tells the VM what * to set cx->regs->fp to when this frame is popped. */ InterpreterFrame* prev() const { return prev_; } AbstractFramePtr evalInFramePrev() const { MOZ_ASSERT(isEvalFrame()); return evalInFramePrev_; } /* * (Unaliased) locals and arguments * * Only non-eval function frames have arguments. The arguments pushed by * the caller are the 'actual' arguments. The declared arguments of the * callee are the 'formal' arguments. When the caller passes less actual * arguments, missing formal arguments are padded with |undefined|. * * When a local/formal variable is aliased (accessed by nested closures, * environment operations, or 'arguments'), the canonical location for * that value is the slot of an environment object. Aliased locals don't * have stack slots assigned to them. These functions assert that * accesses to stack values are unaliased. */ inline Value& unaliasedLocal(uint32_t i); bool hasArgs() const { return isFunctionFrame(); } inline Value& unaliasedFormal(unsigned i, MaybeCheckAliasing = CHECK_ALIASING); inline Value& unaliasedActual(unsigned i, MaybeCheckAliasing = CHECK_ALIASING); template inline void unaliasedForEachActual(Op op); unsigned numFormalArgs() const { MOZ_ASSERT(hasArgs()); return callee().nargs(); } unsigned numActualArgs() const { MOZ_ASSERT(hasArgs()); return nactual_; } /* Watch out, this exposes a pointer to the unaliased formal arg array. */ Value* argv() const { MOZ_ASSERT(hasArgs()); return argv_; } /* * Arguments object * * If a non-eval function has script->needsArgsObj, an arguments object is * created in the prologue and stored in the local variable for the * 'arguments' binding (script->argumentsLocal). Since this local is * mutable, the arguments object can be overwritten and we can "lose" the * arguments object. Thus, InterpreterFrame keeps an explicit argsObj_ field so * that the original arguments object is always available. */ ArgumentsObject& argsObj() const; void initArgsObj(ArgumentsObject& argsobj); JSObject* createRestParameter(JSContext* cx); /* * Environment chain * * In theory, the environment chain would contain an object for every * lexical scope. However, only objects that are required for dynamic * lookup are actually created. * * Given that an InterpreterFrame corresponds roughly to a ES Execution * Context (ES 10.3), InterpreterFrame::varObj corresponds to the * VariableEnvironment component of a Exection Context. Intuitively, the * variables object is where new bindings (variables and functions) are * stored. One might expect that this is either the Call object or * envChain.globalObj for function or global code, respectively, however * the JSAPI allows calls of Execute to specify a variables object on the * environment chain other than the call/global object. This allows * embeddings to run multiple scripts under the same global, each time * using a new variables object to collect and discard the script's global * variables. */ inline HandleObject environmentChain() const; inline EnvironmentObject& aliasedEnvironment(EnvironmentCoordinate ec) const; inline GlobalObject& global() const; inline CallObject& callObj() const; inline JSObject& varObj() const; inline LexicalEnvironmentObject& extensibleLexicalEnvironment() const; template inline void pushOnEnvironmentChain(SpecificEnvironment& env); template inline void popOffEnvironmentChain(); inline void replaceInnermostEnvironment(EnvironmentObject& env); // Push a VarEnvironmentObject for function frames of functions that have // parameter expressions with closed over var bindings. bool pushVarEnvironment(JSContext* cx, HandleScope scope); /* * For lexical envs with aliased locals, these interfaces push and pop * entries on the environment chain. The "freshen" operation replaces the * current lexical env with a fresh copy of it, to implement semantics * providing distinct bindings per iteration of a for(;;) loop whose head * has a lexical declaration. The "recreate" operation replaces the * current lexical env with a copy of it containing uninitialized * bindings, to implement semantics providing distinct bindings per * iteration of a for-in/of loop. */ bool pushLexicalEnvironment(JSContext* cx, Handle scope); bool freshenLexicalEnvironment(JSContext* cx); bool recreateLexicalEnvironment(JSContext* cx); /* * Script * * All frames have an associated JSScript which holds the bytecode being * executed for the frame. */ JSScript* script() const { return script_; } /* Return the previous frame's pc. */ jsbytecode* prevpc() { MOZ_ASSERT(prev_); return prevpc_; } /* Return the previous frame's sp. */ Value* prevsp() { MOZ_ASSERT(prev_); return prevsp_; } /* * Return the 'this' argument passed to a non-eval function frame. This is * not necessarily the frame's this-binding, for instance non-strict * functions will box primitive 'this' values and thisArgument() will * return the original, unboxed Value. */ Value& thisArgument() const { MOZ_ASSERT(isFunctionFrame()); return argv()[-1]; } /* * Callee * * Only function frames have a callee. An eval frame in a function has the * same callee as its containing function frame. */ JSFunction& callee() const { MOZ_ASSERT(isFunctionFrame()); return calleev().toObject().as(); } const Value& calleev() const { MOZ_ASSERT(isFunctionFrame()); return argv()[-2]; } /* * New Target * * Only function frames have a meaningful newTarget. An eval frame in a * function will have a copy of the newTarget of the enclosing function * frame. */ Value newTarget() const { if (isEvalFrame()) return ((Value*)this)[-1]; MOZ_ASSERT(isFunctionFrame()); if (callee().isArrow()) return callee().getExtendedSlot(FunctionExtended::ARROW_NEWTARGET_SLOT); if (isConstructing()) { unsigned pushedArgs = Max(numFormalArgs(), numActualArgs()); return argv()[pushedArgs]; } return UndefinedValue(); } /* Profiler flags */ bool hasPushedSPSFrame() { return !!(flags_ & HAS_PUSHED_SPS_FRAME); } void setPushedSPSFrame() { flags_ |= HAS_PUSHED_SPS_FRAME; } void unsetPushedSPSFrame() { flags_ &= ~HAS_PUSHED_SPS_FRAME; } /* Return value */ bool hasReturnValue() const { return flags_ & HAS_RVAL; } MutableHandleValue returnValue() { if (!hasReturnValue()) rval_.setUndefined(); return MutableHandleValue::fromMarkedLocation(&rval_); } void markReturnValue() { flags_ |= HAS_RVAL; } void setReturnValue(const Value& v) { rval_ = v; markReturnValue(); } void clearReturnValue() { rval_.setUndefined(); markReturnValue(); } void resumeGeneratorFrame(JSObject* envChain) { MOZ_ASSERT(script()->isGenerator()); MOZ_ASSERT(isFunctionFrame()); flags_ |= HAS_INITIAL_ENV; envChain_ = envChain; } /* * Other flags */ bool isConstructing() const { return !!(flags_ & CONSTRUCTING); } void setResumedGenerator() { flags_ |= RESUMED_GENERATOR; } bool isResumedGenerator() const { return !!(flags_ & RESUMED_GENERATOR); } /* * These two queries should not be used in general: the presence/absence of * the call/args object is determined by the static(ish) properties of the * JSFunction/JSScript. These queries should only be performed when probing * a stack frame that may be in the middle of the prologue (during which * time the call/args object are created). */ inline bool hasInitialEnvironment() const; bool hasInitialEnvironmentUnchecked() const { return flags_ & HAS_INITIAL_ENV; } bool hasArgsObj() const { MOZ_ASSERT(script()->needsArgsObj()); return flags_ & HAS_ARGS_OBJ; } void setCreateSingleton() { MOZ_ASSERT(isConstructing()); flags_ |= CREATE_SINGLETON; } bool createSingleton() const { MOZ_ASSERT(isConstructing()); return flags_ & CREATE_SINGLETON; } /* * Debugger eval frames. * * - If evalInFramePrev_ is non-null, frame was created for an "eval in * frame" call, which can push a successor to any live frame; so its * logical "prev" frame is not necessarily the previous frame in memory. * Iteration should treat evalInFramePrev_ as this frame's previous frame. * * - Don't bother to JIT it, because it's probably short-lived. * * - It is required to have a environment chain object outside the * js::EnvironmentObject hierarchy: either a global object, or a * DebugEnvironmentProxy. */ bool isDebuggerEvalFrame() const { return isEvalFrame() && !!evalInFramePrev_; } bool prevUpToDate() const { return !!(flags_ & PREV_UP_TO_DATE); } void setPrevUpToDate() { flags_ |= PREV_UP_TO_DATE; } void unsetPrevUpToDate() { flags_ &= ~PREV_UP_TO_DATE; } bool isDebuggee() const { return !!(flags_ & DEBUGGEE); } void setIsDebuggee() { flags_ |= DEBUGGEE; } inline void unsetIsDebuggee(); bool hasCachedSavedFrame() const { return flags_ & HAS_CACHED_SAVED_FRAME; } void setHasCachedSavedFrame() { flags_ |= HAS_CACHED_SAVED_FRAME; } public: void trace(JSTracer* trc, Value* sp, jsbytecode* pc); void traceValues(JSTracer* trc, unsigned start, unsigned end); // Entered Baseline/Ion from the interpreter. bool runningInJit() const { return !!(flags_ & RUNNING_IN_JIT); } void setRunningInJit() { flags_ |= RUNNING_IN_JIT; } void clearRunningInJit() { flags_ &= ~RUNNING_IN_JIT; } }; /*****************************************************************************/ class InterpreterRegs { public: Value* sp; jsbytecode* pc; private: InterpreterFrame* fp_; public: InterpreterFrame* fp() const { return fp_; } unsigned stackDepth() const { MOZ_ASSERT(sp >= fp_->base()); return sp - fp_->base(); } Value* spForStackDepth(unsigned depth) const { MOZ_ASSERT(fp_->script()->nfixed() + depth <= fp_->script()->nslots()); return fp_->base() + depth; } /* For generators. */ void rebaseFromTo(const InterpreterRegs& from, InterpreterFrame& to) { fp_ = &to; sp = to.slots() + (from.sp - from.fp_->slots()); pc = from.pc; MOZ_ASSERT(fp_); } void popInlineFrame() { pc = fp_->prevpc(); unsigned spForNewTarget = fp_->isResumedGenerator() ? 0 : fp_->isConstructing(); sp = fp_->prevsp() - fp_->numActualArgs() - 1 - spForNewTarget; fp_ = fp_->prev(); MOZ_ASSERT(fp_); } void prepareToRun(InterpreterFrame& fp, JSScript* script) { pc = script->code(); sp = fp.slots() + script->nfixed(); fp_ = &fp; } void setToEndOfScript(); MutableHandleValue stackHandleAt(int i) { return MutableHandleValue::fromMarkedLocation(&sp[i]); } HandleValue stackHandleAt(int i) const { return HandleValue::fromMarkedLocation(&sp[i]); } friend void GDBTestInitInterpreterRegs(InterpreterRegs&, js::InterpreterFrame*, JS::Value*, uint8_t*); }; /*****************************************************************************/ class InterpreterStack { friend class InterpreterActivation; static const size_t DEFAULT_CHUNK_SIZE = 4 * 1024; LifoAlloc allocator_; // Number of interpreter frames on the stack, for over-recursion checks. static const size_t MAX_FRAMES = 50 * 1000; static const size_t MAX_FRAMES_TRUSTED = MAX_FRAMES + 1000; size_t frameCount_; inline uint8_t* allocateFrame(JSContext* cx, size_t size); inline InterpreterFrame* getCallFrame(JSContext* cx, const CallArgs& args, HandleScript script, MaybeConstruct constructing, Value** pargv); void releaseFrame(InterpreterFrame* fp) { frameCount_--; allocator_.release(fp->mark_); } public: InterpreterStack() : allocator_(DEFAULT_CHUNK_SIZE), frameCount_(0) { } ~InterpreterStack() { MOZ_ASSERT(frameCount_ == 0); } // For execution of eval or global code. InterpreterFrame* pushExecuteFrame(JSContext* cx, HandleScript script, const Value& newTargetValue, HandleObject envChain, AbstractFramePtr evalInFrame); // Called to invoke a function. InterpreterFrame* pushInvokeFrame(JSContext* cx, const CallArgs& args, MaybeConstruct constructing); // The interpreter can push light-weight, "inline" frames without entering a // new InterpreterActivation or recursively calling Interpret. bool pushInlineFrame(JSContext* cx, InterpreterRegs& regs, const CallArgs& args, HandleScript script, MaybeConstruct constructing); void popInlineFrame(InterpreterRegs& regs); bool resumeGeneratorCallFrame(JSContext* cx, InterpreterRegs& regs, HandleFunction callee, HandleValue newTarget, HandleObject envChain); inline void purge(JSRuntime* rt); size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { return allocator_.sizeOfExcludingThis(mallocSizeOf); } }; void MarkInterpreterActivations(JSRuntime* rt, JSTracer* trc); /*****************************************************************************/ /** Base class for all function call args. */ class AnyInvokeArgs : public JS::CallArgs { }; /** Base class for all function construction args. */ class AnyConstructArgs : public JS::CallArgs { // Only js::Construct (or internal methods that call the qualified CallArgs // versions) should do these things! void setCallee(const Value& v) = delete; void setThis(const Value& v) = delete; MutableHandleValue newTarget() const = delete; MutableHandleValue rval() const = delete; }; namespace detail { /** Function call/construct args of statically-unknown count. */ template class GenericArgsBase : public mozilla::Conditional::Type { protected: AutoValueVector v_; explicit GenericArgsBase(JSContext* cx) : v_(cx) {} public: bool init(JSContext* cx, unsigned argc) { if (argc > ARGS_LENGTH_MAX) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TOO_MANY_ARGUMENTS); return false; } // callee, this, arguments[, new.target iff constructing] size_t len = 2 + argc + uint32_t(Construct); MOZ_ASSERT(len > argc); // no overflow if (!v_.resize(len)) return false; *static_cast(this) = CallArgsFromVp(argc, v_.begin()); this->constructing_ = Construct; if (Construct) this->CallArgs::setThis(MagicValue(JS_IS_CONSTRUCTING)); return true; } }; /** Function call/construct args of statically-known count. */ template class FixedArgsBase : public mozilla::Conditional::Type { static_assert(N <= ARGS_LENGTH_MAX, "o/~ too many args o/~"); protected: JS::AutoValueArray<2 + N + uint32_t(Construct)> v_; explicit FixedArgsBase(JSContext* cx) : v_(cx) { *static_cast(this) = CallArgsFromVp(N, v_.begin()); this->constructing_ = Construct; if (Construct) this->CallArgs::setThis(MagicValue(JS_IS_CONSTRUCTING)); } }; } // namespace detail /** Function call args of statically-unknown count. */ class InvokeArgs : public detail::GenericArgsBase { using Base = detail::GenericArgsBase; public: explicit InvokeArgs(JSContext* cx) : Base(cx) {} }; /** Function call args of statically-known count. */ template class FixedInvokeArgs : public detail::FixedArgsBase { using Base = detail::FixedArgsBase; public: explicit FixedInvokeArgs(JSContext* cx) : Base(cx) {} }; /** Function construct args of statically-unknown count. */ class ConstructArgs : public detail::GenericArgsBase { using Base = detail::GenericArgsBase; public: explicit ConstructArgs(JSContext* cx) : Base(cx) {} }; /** Function call args of statically-known count. */ template class FixedConstructArgs : public detail::FixedArgsBase { using Base = detail::FixedArgsBase; public: explicit FixedConstructArgs(JSContext* cx) : Base(cx) {} }; template inline bool FillArgumentsFromArraylike(JSContext* cx, Args& args, const Arraylike& arraylike) { uint32_t len = arraylike.length(); if (!args.init(cx, len)) return false; for (uint32_t i = 0; i < len; i++) args[i].set(arraylike[i]); return true; } template <> struct DefaultHasher { typedef AbstractFramePtr Lookup; static js::HashNumber hash(const Lookup& key) { return size_t(key.raw()); } static bool match(const AbstractFramePtr& k, const Lookup& l) { return k == l; } }; /*****************************************************************************/ // SavedFrame caching to minimize stack walking. // // SavedFrames are hash consed to minimize expensive (with regards to both space // and time) allocations in the face of many stack frames that tend to share the // same older tail frames. Despite that, in scenarios where we are frequently // saving the same or similar stacks, such as when the Debugger's allocation // site tracking is enabled, these older stack frames still get walked // repeatedly just to create the lookup structs to find their corresponding // SavedFrames in the hash table. This stack walking is slow, and we would like // to minimize it. // // We have reserved a bit on most of SpiderMonkey's various frame // representations (the exceptions being asm and inlined ion frames). As we // create SavedFrame objects for live stack frames in SavedStacks::insertFrames, // we set this bit and append the SavedFrame object to the cache. As we walk the // stack, if we encounter a frame that has this bit set, that indicates that we // have already captured a SavedFrame object for the given stack frame (but not // necessarily the current pc) during a previous call to insertFrames. We know // that the frame's parent was also captured and has its bit set as well, but // additionally we know the parent was captured at its current pc. For the // parent, rather than continuing the expensive stack walk, we do a quick and // cache-friendly linear search through the frame cache. Upon finishing search // through the frame cache, stale entries are removed. // // The frame cache maintains the invariant that its first E[0] .. E[j-1] // entries are live and sorted from oldest to younger frames, where 0 < j < n // and n = the length of the cache. When searching the cache, we require // that we are considering the youngest live frame whose bit is set. Every // cache entry E[i] where i >= j is a stale entry. Consider the following // scenario: // // P > Q > R > S Initial stack, bits not set. // P* > Q* > R* > S* Capture a SavedFrame stack, set bits. // P* > Q* > R* Return from S. // P* > Q* Return from R. // P* > Q* > T Call T, its bit is not set. // // The frame cache was populated with [P, Q, R, S] when we captured a // SavedFrame stack, but because we returned from frames R and S, their // entries in the frame cache are now stale. This fact is unbeknownst to us // because we do not observe frame pops. Upon capturing a second stack, we // start stack walking at the youngest frame T, which does not have its bit // set and must take the hash table lookup slow path rather than the frame // cache short circuit. Next we proceed to Q and find that it has its bit // set, and it is therefore the youngest live frame with its bit set. We // search through the frame cache from oldest to youngest and find the cache // entry matching Q. We know that T is the next younger live frame from Q // and that T does not have an entry in the frame cache because its bit was // not set. Therefore, we have found entry E[j-1] and the subsequent entries // are stale and should be purged from the frame cache. // // We have a LiveSavedFrameCache for each activation to minimize the number of // entries that must be scanned through, and to avoid the headaches of // maintaining a cache for each compartment and invalidating stale cache entries // in the presence of cross-compartment calls. class LiveSavedFrameCache { public: using FramePtr = mozilla::Variant; private: struct Entry { FramePtr framePtr; jsbytecode* pc; HeapPtr savedFrame; Entry(FramePtr& framePtr, jsbytecode* pc, SavedFrame* savedFrame) : framePtr(framePtr) , pc(pc) , savedFrame(savedFrame) { } }; using EntryVector = Vector; EntryVector* frames; LiveSavedFrameCache(const LiveSavedFrameCache&) = delete; LiveSavedFrameCache& operator=(const LiveSavedFrameCache&) = delete; public: explicit LiveSavedFrameCache() : frames(nullptr) { } LiveSavedFrameCache(LiveSavedFrameCache&& rhs) : frames(rhs.frames) { MOZ_ASSERT(this != &rhs, "self-move disallowed"); rhs.frames = nullptr; } ~LiveSavedFrameCache() { if (frames) { js_delete(frames); frames = nullptr; } } bool initialized() const { return !!frames; } bool init(JSContext* cx) { frames = js_new(); if (!frames) { JS_ReportOutOfMemory(cx); return false; } return true; } static mozilla::Maybe getFramePtr(FrameIter& iter); void trace(JSTracer* trc); void find(JSContext* cx, FrameIter& frameIter, MutableHandleSavedFrame frame) const; bool insert(JSContext* cx, FramePtr& framePtr, jsbytecode* pc, HandleSavedFrame savedFrame); }; static_assert(sizeof(LiveSavedFrameCache) == sizeof(uintptr_t), "Every js::Activation has a LiveSavedFrameCache, so we need to be pretty careful " "about avoiding bloat. If you're adding members to LiveSavedFrameCache, maybe you " "should consider figuring out a way to make js::Activation have a " "LiveSavedFrameCache* instead of a Rooted."); /*****************************************************************************/ class InterpreterActivation; class WasmActivation; namespace jit { class JitActivation; } // namespace jit // This class is separate from Activation, because it calls JSCompartment::wrap() // which can GC and walk the stack. It's not safe to do that within the // JitActivation constructor. class MOZ_RAII ActivationEntryMonitor { JSContext* cx_; // The entry point monitor that was set on cx_->runtime() when this // ActivationEntryMonitor was created. JS::dbg::AutoEntryMonitor* entryMonitor_; explicit ActivationEntryMonitor(JSContext* cx); ActivationEntryMonitor(const ActivationEntryMonitor& other) = delete; void operator=(const ActivationEntryMonitor& other) = delete; Value asyncStack(JSContext* cx); public: ActivationEntryMonitor(JSContext* cx, InterpreterFrame* entryFrame); ActivationEntryMonitor(JSContext* cx, jit::CalleeToken entryToken); inline ~ActivationEntryMonitor(); }; class Activation { protected: JSContext* cx_; JSCompartment* compartment_; Activation* prev_; Activation* prevProfiling_; // Counter incremented by JS::HideScriptedCaller and decremented by // JS::UnhideScriptedCaller. If > 0 for the top activation, // DescribeScriptedCaller will return null instead of querying that // activation, which should prompt the caller to consult embedding-specific // data structures instead. size_t hideScriptedCallerCount_; // The cache of SavedFrame objects we have already captured when walking // this activation's stack. Rooted frameCache_; // Youngest saved frame of an async stack that will be iterated during stack // capture in place of the actual stack of previous activations. Note that // the stack of this activation is captured entirely before this is used. // // Usually this is nullptr, meaning that normal stack capture will occur. // When this is set, the stack of any previous activation is ignored. Rooted asyncStack_; // Value of asyncCause to be attached to asyncStack_. const char* asyncCause_; // True if the async call was explicitly requested, e.g. via // callFunctionWithAsyncStack. bool asyncCallIsExplicit_; enum Kind { Interpreter, Jit, Wasm }; Kind kind_; inline Activation(JSContext* cx, Kind kind); inline ~Activation(); public: JSContext* cx() const { return cx_; } JSCompartment* compartment() const { return compartment_; } Activation* prev() const { return prev_; } Activation* prevProfiling() const { return prevProfiling_; } inline Activation* mostRecentProfiling(); bool isInterpreter() const { return kind_ == Interpreter; } bool isJit() const { return kind_ == Jit; } bool isWasm() const { return kind_ == Wasm; } inline bool isProfiling() const; void registerProfiling(); void unregisterProfiling(); InterpreterActivation* asInterpreter() const { MOZ_ASSERT(isInterpreter()); return (InterpreterActivation*)this; } jit::JitActivation* asJit() const { MOZ_ASSERT(isJit()); return (jit::JitActivation*)this; } WasmActivation* asWasm() const { MOZ_ASSERT(isWasm()); return (WasmActivation*)this; } void hideScriptedCaller() { hideScriptedCallerCount_++; } void unhideScriptedCaller() { MOZ_ASSERT(hideScriptedCallerCount_ > 0); hideScriptedCallerCount_--; } bool scriptedCallerIsHidden() const { return hideScriptedCallerCount_ > 0; } static size_t offsetOfPrevProfiling() { return offsetof(Activation, prevProfiling_); } SavedFrame* asyncStack() { return asyncStack_; } const char* asyncCause() const { return asyncCause_; } bool asyncCallIsExplicit() const { return asyncCallIsExplicit_; } inline LiveSavedFrameCache* getLiveSavedFrameCache(JSContext* cx); private: Activation(const Activation& other) = delete; void operator=(const Activation& other) = delete; }; // This variable holds a special opcode value which is greater than all normal // opcodes, and is chosen such that the bitwise or of this value with any // opcode is this value. static const jsbytecode EnableInterruptsPseudoOpcode = -1; static_assert(EnableInterruptsPseudoOpcode >= JSOP_LIMIT, "EnableInterruptsPseudoOpcode must be greater than any opcode"); static_assert(EnableInterruptsPseudoOpcode == jsbytecode(-1), "EnableInterruptsPseudoOpcode must be the maximum jsbytecode value"); class InterpreterFrameIterator; class RunState; class InterpreterActivation : public Activation { friend class js::InterpreterFrameIterator; InterpreterRegs regs_; InterpreterFrame* entryFrame_; size_t opMask_; // For debugger interrupts, see js::Interpret. #ifdef DEBUG size_t oldFrameCount_; #endif public: inline InterpreterActivation(RunState& state, JSContext* cx, InterpreterFrame* entryFrame); inline ~InterpreterActivation(); inline bool pushInlineFrame(const CallArgs& args, HandleScript script, MaybeConstruct constructing); inline void popInlineFrame(InterpreterFrame* frame); inline bool resumeGeneratorFrame(HandleFunction callee, HandleValue newTarget, HandleObject envChain); InterpreterFrame* current() const { return regs_.fp(); } InterpreterRegs& regs() { return regs_; } InterpreterFrame* entryFrame() const { return entryFrame_; } size_t opMask() const { return opMask_; } bool isProfiling() const { return false; } // If this js::Interpret frame is running |script|, enable interrupts. void enableInterruptsIfRunning(JSScript* script) { if (regs_.fp()->script() == script) enableInterruptsUnconditionally(); } void enableInterruptsUnconditionally() { opMask_ = EnableInterruptsPseudoOpcode; } void clearInterruptsMask() { opMask_ = 0; } }; // Iterates over a thread's activation list. If given a runtime, iterate over // the runtime's main thread's activation list. class ActivationIterator { uint8_t* jitTop_; protected: Activation* activation_; private: void settle(); public: explicit ActivationIterator(JSRuntime* rt); ActivationIterator& operator++(); Activation* operator->() const { return activation_; } Activation* activation() const { return activation_; } uint8_t* jitTop() const { MOZ_ASSERT(activation_->isJit()); return jitTop_; } bool done() const { return activation_ == nullptr; } }; namespace jit { class BailoutFrameInfo; // A JitActivation is used for frames running in Baseline or Ion. class JitActivation : public Activation { uint8_t* prevJitTop_; JitActivation* prevJitActivation_; bool active_; // Rematerialized Ion frames which has info copied out of snapshots. Maps // frame pointers (i.e. jitTop) to a vector of rematerializations of all // inline frames associated with that frame. // // This table is lazily initialized by calling getRematerializedFrame. typedef GCVector RematerializedFrameVector; typedef HashMap RematerializedFrameTable; RematerializedFrameTable* rematerializedFrames_; // This vector is used to remember the outcome of the evaluation of recover // instructions. // // RInstructionResults are appended into this vector when Snapshot values // have to be read, or when the evaluation has to run before some mutating // code. Each RInstructionResults belongs to one frame which has to bailout // as soon as we get back to it. typedef Vector IonRecoveryMap; IonRecoveryMap ionRecovery_; // If we are bailing out from Ion, then this field should be a non-null // pointer which references the BailoutFrameInfo used to walk the inner // frames. This field is used for all newly constructed JitFrameIterators to // read the innermost frame information from this bailout data instead of // reading it from the stack. BailoutFrameInfo* bailoutData_; // When profiling is enabled, these fields will be updated to reflect the // last pushed frame for this activation, and if that frame has been // left for a call, the native code site of the call. mozilla::Atomic lastProfilingFrame_; mozilla::Atomic lastProfilingCallSite_; static_assert(sizeof(mozilla::Atomic) == sizeof(void*), "Atomic should have same memory format as underlying type."); void clearRematerializedFrames(); #ifdef CHECK_OSIPOINT_REGISTERS protected: // Used to verify that live registers don't change between a VM call and // the OsiPoint that follows it. Protected to silence Clang warning. uint32_t checkRegs_; RegisterDump regs_; #endif public: explicit JitActivation(JSContext* cx, bool active = true); ~JitActivation(); bool isActive() const { return active_; } void setActive(JSContext* cx, bool active = true); bool isProfiling() const; uint8_t* prevJitTop() const { return prevJitTop_; } JitActivation* prevJitActivation() const { return prevJitActivation_; } static size_t offsetOfPrevJitTop() { return offsetof(JitActivation, prevJitTop_); } static size_t offsetOfPrevJitActivation() { return offsetof(JitActivation, prevJitActivation_); } static size_t offsetOfActiveUint8() { MOZ_ASSERT(sizeof(bool) == 1); return offsetof(JitActivation, active_); } #ifdef CHECK_OSIPOINT_REGISTERS void setCheckRegs(bool check) { checkRegs_ = check; } static size_t offsetOfCheckRegs() { return offsetof(JitActivation, checkRegs_); } static size_t offsetOfRegs() { return offsetof(JitActivation, regs_); } #endif // Look up a rematerialized frame keyed by the fp, rematerializing the // frame if one doesn't already exist. A frame can only be rematerialized // if an IonFrameIterator pointing to the nearest uninlined frame can be // provided, as values need to be read out of snapshots. // // The inlineDepth must be within bounds of the frame pointed to by iter. RematerializedFrame* getRematerializedFrame(JSContext* cx, const JitFrameIterator& iter, size_t inlineDepth = 0); // Look up a rematerialized frame by the fp. If inlineDepth is out of // bounds of what has been rematerialized, nullptr is returned. RematerializedFrame* lookupRematerializedFrame(uint8_t* top, size_t inlineDepth = 0); // Remove all rematerialized frames associated with the fp top from the // Debugger. void removeRematerializedFramesFromDebugger(JSContext* cx, uint8_t* top); bool hasRematerializedFrame(uint8_t* top, size_t inlineDepth = 0) { return !!lookupRematerializedFrame(top, inlineDepth); } // Remove a previous rematerialization by fp. void removeRematerializedFrame(uint8_t* top); void markRematerializedFrames(JSTracer* trc); // Register the results of on Ion frame recovery. bool registerIonFrameRecovery(RInstructionResults&& results); // Return the pointer to the Ion frame recovery, if it is already registered. RInstructionResults* maybeIonFrameRecovery(JitFrameLayout* fp); // If an Ion frame recovery exists for the |fp| frame exists, then remove it // from the activation. void removeIonFrameRecovery(JitFrameLayout* fp); void markIonRecovery(JSTracer* trc); // Return the bailout information if it is registered. const BailoutFrameInfo* bailoutData() const { return bailoutData_; } // Register the bailout data when it is constructed. void setBailoutData(BailoutFrameInfo* bailoutData); // Unregister the bailout data when the frame is reconstructed. void cleanBailoutData(); static size_t offsetOfLastProfilingFrame() { return offsetof(JitActivation, lastProfilingFrame_); } void* lastProfilingFrame() { return lastProfilingFrame_; } void setLastProfilingFrame(void* ptr) { lastProfilingFrame_ = ptr; } static size_t offsetOfLastProfilingCallSite() { return offsetof(JitActivation, lastProfilingCallSite_); } void* lastProfilingCallSite() { return lastProfilingCallSite_; } void setLastProfilingCallSite(void* ptr) { lastProfilingCallSite_ = ptr; } }; // A filtering of the ActivationIterator to only stop at JitActivations. class JitActivationIterator : public ActivationIterator { void settle() { while (!done() && !activation_->isJit()) ActivationIterator::operator++(); } public: explicit JitActivationIterator(JSRuntime* rt) : ActivationIterator(rt) { settle(); } JitActivationIterator& operator++() { ActivationIterator::operator++(); settle(); return *this; } }; } // namespace jit // Iterates over the frames of a single InterpreterActivation. class InterpreterFrameIterator { InterpreterActivation* activation_; InterpreterFrame* fp_; jsbytecode* pc_; 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_; } Value* sp() const { MOZ_ASSERT(!done()); return sp_; } InterpreterFrameIterator& operator++(); bool done() const { return fp_ == nullptr; } }; // A WasmActivation is part of two activation linked lists: // - the normal Activation list used by FrameIter // - a list of only WasmActivations that is signal-safe since it is accessed // from the profiler at arbitrary points // // An eventual goal is to remove WasmActivation and to run asm code in a // JitActivation interleaved with Ion/Baseline jit code. This would allow // efficient calls back and forth but requires that we can walk the stack for // all kinds of jit code. class WasmActivation : public Activation { WasmActivation* prevWasm_; void* entrySP_; void* resumePC_; uint8_t* fp_; wasm::ExitReason exitReason_; public: explicit WasmActivation(JSContext* cx); ~WasmActivation(); WasmActivation* prevWasm() const { return prevWasm_; } bool isProfiling() const { return true; } // Returns a pointer to the base of the innermost stack frame of wasm code // in this activation. uint8_t* fp() const { return fp_; } // Returns the reason why wasm code called out of wasm code. wasm::ExitReason exitReason() const { return exitReason_; } // Read by JIT code: static unsigned offsetOfContext() { return offsetof(WasmActivation, cx_); } static unsigned offsetOfResumePC() { return offsetof(WasmActivation, resumePC_); } // Written by JIT code: static unsigned offsetOfEntrySP() { return offsetof(WasmActivation, entrySP_); } static unsigned offsetOfFP() { return offsetof(WasmActivation, fp_); } static unsigned offsetOfExitReason() { return offsetof(WasmActivation, exitReason_); } // Read/written from SIGSEGV handler: void setResumePC(void* pc) { resumePC_ = pc; } void* resumePC() const { return resumePC_; } }; // A FrameIter walks over the runtime'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, INTERP, JIT, WASM }; // 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_; jit::JitFrameIterator jitFrames_; unsigned ionInlineFrameNo_; wasm::FrameIterator wasmFrames_; 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++(); JSCompartment* compartment() const; Activation* activation() const { return data_.activations_.activation(); } bool isInterp() const { MOZ_ASSERT(!done()); return data_.state_ == INTERP; } bool isJit() const { MOZ_ASSERT(!done()); return data_.state_ == JIT; } bool isWasm() const { MOZ_ASSERT(!done()); return data_.state_ == WASM; } inline bool isIon() const; inline bool isBaseline() const; inline bool isPhysicalIonFrame() const; bool isEvalFrame() const; bool isFunctionFrame() const; bool hasArgs() const { return isFunctionFrame(); } // These two methods may not be called with asm frames. inline bool hasCachedSavedFrame() const; inline void setHasCachedSavedFrame(); ScriptSource* scriptSource() const; const char* filename() const; const char16_t* displayURL() const; unsigned computeLine(uint32_t* column = nullptr) const; JSAtom* functionDisplayAtom() const; bool mutedErrors() const; bool hasScript() const { return !isWasm(); } // ----------------------------------------------------------- // 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, HandleFunction fun) const; unsigned numActualArgs() const; unsigned numFormalArgs() const; Value unaliasedActual(unsigned i, MaybeCheckAliasing = CHECK_ALIASING) const; template inline void unaliasedForEachActual(JSContext* cx, Op op); JSObject* environmentChain(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). Value thisArgument(JSContext* cx) const; Value newTarget() const; Value returnValue() const; void setReturnValue(const Value& v); // These are only valid for the top frame. size_t numFrameSlots() const; 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 false otherwise. bool hasUsableAbstractFramePtr() const; // ----------------------------------------------------------- // The following functions can only be called when isInterp(), // isBaseline(), or isIon(). Further, abstractFramePtr() can // only be called when hasUsableAbstractFramePtr(). // ----------------------------------------------------------- AbstractFramePtr abstractFramePtr() const; AbstractFramePtr copyDataAsAbstractFramePtr() const; Data* copyData() const; // This can only be called when isInterp(): inline InterpreterFrame* interpFrame() const; // This can only be called when isPhysicalIonFrame(): inline jit::CommonFrameLayout* physicalIonFrame() const; // This is used to provide a raw interface for debugging. void* rawFramePtr() const; private: Data data_; jit::InlineFrameIterator ionInlineFrames_; void popActivation(); void popInterpreterFrame(); void nextJitFrame(); void popJitFrame(); void popWasmFrame(); 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()); if (data_.state_ == INTERP) return interpFrame()->script(); MOZ_ASSERT(data_.state_ == JIT); if (data_.jitFrames_.isIonJS()) return ionInlineFrames_.script(); return data_.jitFrames_.script(); } inline bool FrameIter::isIon() const { return isJit() && data_.jitFrames_.isIonJS(); } inline bool FrameIter::isBaseline() const { return isJit() && data_.jitFrames_.isBaselineJS(); } inline InterpreterFrame* FrameIter::interpFrame() const { MOZ_ASSERT(data_.state_ == INTERP); return data_.interpFrames_.frame(); } inline bool FrameIter::isPhysicalIonFrame() const { return isJit() && data_.jitFrames_.isIonScripted() && ionInlineFrames_.frameNo() == 0; } inline jit::CommonFrameLayout* FrameIter::physicalIonFrame() const { MOZ_ASSERT(isPhysicalIonFrame()); return data_.jitFrames_.current(); } } /* namespace js */ #endif /* vm_Stack_h */