/* -*- 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_Stack_h #define vm_Stack_h #include "mozilla/HashFunctions.h" #include "mozilla/MemoryReporting.h" #include #include #include "js/ErrorReport.h" #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* #include "js/RootingAPI.h" #include "js/TypeDecls.h" #include "js/ValueArray.h" #include "vm/ArgumentsObject.h" #include "vm/JSFunction.h" #include "vm/JSScript.h" #include "wasm/WasmDebugFrame.h" // js::wasm::DebugFrame namespace js { class InterpreterRegs; class CallObject; class FrameIter; class ClassBodyScope; class EnvironmentObject; class BlockLexicalEnvironmentObject; class ExtensibleLexicalEnvironmentObject; class GeckoProfilerRuntime; class InterpreterFrame; class EnvironmentIter; class EnvironmentCoordinate; namespace jit { class CommonFrameLayout; } namespace wasm { class Instance; } // namespace wasm // [SMDOC] 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 JS::Compartment. 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 }; } // namespace js /*****************************************************************************/ namespace js { namespace jit { class BaselineFrame; class RematerializedFrame; } // namespace jit /** * Pointer to a live JS or WASM stack frame. */ class AbstractFramePtr { friend class FrameIter; uintptr_t ptr_; enum { Tag_InterpreterFrame = 0x1, Tag_BaselineFrame = 0x2, Tag_RematerializedFrame = 0x3, Tag_WasmDebugFrame = 0x4, TagMask = 0x7 }; 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); } MOZ_IMPLICIT AbstractFramePtr(wasm::DebugFrame* fp) : ptr_(fp ? uintptr_t(fp) | Tag_WasmDebugFrame : 0) { static_assert(wasm::DebugFrame::Alignment >= TagMask, "aligned"); MOZ_ASSERT_IF(fp, asWasmDebugFrame() == fp); } 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; } bool isWasmDebugFrame() const { return (ptr_ & TagMask) == Tag_WasmDebugFrame; } wasm::DebugFrame* asWasmDebugFrame() const { MOZ_ASSERT(isWasmDebugFrame()); wasm::DebugFrame* res = (wasm::DebugFrame*)(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, Handle scope); template inline void pushOnEnvironmentChain(SpecificEnvironment& env); template inline void popOffEnvironmentChain(); inline JS::Realm* realm() const; inline bool hasInitialEnvironment() const; inline bool isGlobalFrame() const; inline bool isModuleFrame() const; inline bool isEvalFrame() const; inline bool isDebuggerEvalFrame() const; inline bool hasScript() const; inline JSScript* script() const; inline wasm::Instance* wasmInstance() const; inline GlobalObject* global() const; inline bool hasGlobal(const GlobalObject* global) const; inline JSFunction* callee() const; inline Value calleev() const; inline Value& thisArgument() const; inline bool isConstructing() const; inline bool debuggerNeedsCheckPrimitiveReturn() const; inline bool isFunctionFrame() const; inline bool isGeneratorFrame() const; inline bool saveGeneratorSlots(JSContext* cx, unsigned nslots, ArrayObject* dest) const; inline bool hasCachedSavedFrame() 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 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&, InterpreterFrame*); friend void GDBTestInitAbstractFramePtr(AbstractFramePtr&, jit::BaselineFrame*); friend void GDBTestInitAbstractFramePtr(AbstractFramePtr&, jit::RematerializedFrame*); friend void GDBTestInitAbstractFramePtr(AbstractFramePtr& frame, wasm::DebugFrame* ptr); }; 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 Realm.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/GeckoProfiler.cpp) */ HAS_PUSHED_PROF_FRAME = 0x80, /* Gecko Profiler was notified of entry */ /* * If set, we entered one of the JITs and ScriptFrameIter should skip * this frame. */ RUNNING_IN_JIT = 0x100, /* * 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 = 0x200, }; 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_; /* * 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() { static_assert(offsetof(InterpreterFrame, rval_) % sizeof(Value) == 0); 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(InterpreterFrame* prev, jsbytecode* prevpc, Value* prevsp, JSFunction& callee, JSScript* script, Value* argv, uint32_t nactual, MaybeConstruct constructing); /* Used for eval, module or global frames. */ void initExecuteFrame(JSContext* cx, HandleScript script, AbstractFramePtr prev, 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, MutableHandleValue result); 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_->isModule(); } bool isEvalFrame() const { return script_->isForEval(); } bool isFunctionFrame() const { return script_->isFunction(); } /* * 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); ArrayObject* 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), GetVariablesObject 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 EnvironmentObject& aliasedEnvironmentMaybeDebug( EnvironmentCoordinate ec) const; inline GlobalObject& global() const; inline CallObject& callObj() const; inline ExtensibleLexicalEnvironmentObject& extensibleLexicalEnvironment() const; template inline void pushOnEnvironmentChain(SpecificEnvironment& env); template inline void popOffEnvironmentChain(); inline void replaceInnermostEnvironment(BlockLexicalEnvironmentObject& env); // Push a VarEnvironmentObject for function frames of functions that have // parameter expressions with closed over var bindings. bool pushVarEnvironment(JSContext* cx, Handle 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); bool pushClassBodyEnvironment(JSContext* cx, Handle scope); /* * 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 true callee. An eval frame in a function has * the same callee as its containing function frame. An async module has to * create a wrapper callee to allow passing the script to generators for * pausing and resuming. */ JSFunction& callee() const { MOZ_ASSERT(isFunctionFrame()); return calleev().toObject().as(); } const Value& calleev() const { MOZ_ASSERT(isFunctionFrame()); return argv()[-2]; } /* * New Target * * Only non-arrow function frames have a meaningful newTarget. */ Value newTarget() const { MOZ_ASSERT(isFunctionFrame()); MOZ_ASSERT(!callee().isArrow()); if (isConstructing()) { unsigned pushedArgs = std::max(numFormalArgs(), numActualArgs()); return argv()[pushedArgs]; } return UndefinedValue(); } /* Profiler flags */ bool hasPushedGeckoProfilerFrame() { return !!(flags_ & HAS_PUSHED_PROF_FRAME); } void setPushedGeckoProfilerFrame() { flags_ |= HAS_PUSHED_PROF_FRAME; } void unsetPushedGeckoProfilerFrame() { flags_ &= ~HAS_PUSHED_PROF_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(); } // Copy values from this frame into a private Array, owned by the // GeneratorObject, for suspending. [[nodiscard]] inline bool saveGeneratorSlots(JSContext* cx, unsigned nslots, ArrayObject* dest) const; // Copy values from the Array into this stack frame, for resuming. inline void restoreGeneratorSlots(ArrayObject* src); void resumeGeneratorFrame(JSObject* envChain) { MOZ_ASSERT(script()->isGenerator() || script()->isAsync()); MOZ_ASSERT_IF(!script()->isModule(), 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; } /* * 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; } void clearHasCachedSavedFrame() { 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; } void popInlineFrame() { pc = fp_->prevpc(); unsigned spForNewTarget = fp_->isResumedGenerator() ? 0 : fp_->isConstructing(); // This code is called when resuming from async and generator code. // In the case of modules, we don't have arguments, so we can't use // numActualArgs, which asserts 'hasArgs'. unsigned nActualArgs = fp_->isModuleFrame() ? 0 : fp_->numActualArgs(); sp = fp_->prevsp() - nActualArgs - 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, module or global code. InterpreterFrame* pushExecuteFrame(JSContext* cx, HandleScript script, 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, HandleObject envChain); inline void purge(JSRuntime* rt); size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { return allocator_.sizeOfExcludingThis(mallocSizeOf); } }; void TraceInterpreterActivations(JSContext* cx, 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 std::conditional_t { protected: RootedValueVector v_; explicit GenericArgsBase(JSContext* cx) : v_(cx) {} public: bool init(JSContext* cx, uint64_t 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 std::conditional_t { // Add +1 here to avoid noisy warning on gcc when N=0 (0 <= unsigned). static_assert(N + 1 <= ARGS_LENGTH_MAX + 1, "o/~ too many args o/~"); protected: JS::RootedValueArray<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-unknown count. */ class InvokeArgsMaybeIgnoresReturnValue : public detail::GenericArgsBase { using Base = detail::GenericArgsBase; public: explicit InvokeArgsMaybeIgnoresReturnValue(JSContext* cx) : Base(cx) {} bool init(JSContext* cx, unsigned argc, bool ignoresReturnValue) { if (!Base::init(cx, argc)) { return false; } this->ignoresReturnValue_ = ignoresReturnValue; return true; } }; /** 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; } } // namespace js namespace mozilla { template <> struct DefaultHasher { using Lookup = js::AbstractFramePtr; static js::HashNumber hash(const Lookup& key) { return mozilla::HashGeneric(key.raw()); } static bool match(const js::AbstractFramePtr& k, const Lookup& l) { return k == l; } }; } // namespace mozilla #endif // vm_Stack_h