| /* -*- 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/. */ |
| |
| #include "vm/Stack-inl.h" |
| |
| #include "mozilla/PodOperations.h" |
| |
| #include "jscntxt.h" |
| |
| #include "asmjs/AsmJSFrameIterator.h" |
| #include "asmjs/AsmJSModule.h" |
| #include "gc/Marking.h" |
| #include "jit/BaselineFrame.h" |
| #include "jit/JitcodeMap.h" |
| #include "jit/JitCompartment.h" |
| #include "js/GCAPI.h" |
| #include "vm/Debugger.h" |
| #include "vm/Opcodes.h" |
| |
| #include "jit/JitFrameIterator-inl.h" |
| #include "vm/Interpreter-inl.h" |
| #include "vm/Probes-inl.h" |
| #include "vm/ScopeObject-inl.h" |
| |
| using namespace js; |
| |
| using mozilla::Maybe; |
| using mozilla::PodCopy; |
| |
| /*****************************************************************************/ |
| |
| void |
| InterpreterFrame::initExecuteFrame(JSContext* cx, HandleScript script, AbstractFramePtr evalInFramePrev, |
| const Value& newTargetValue, HandleObject scopeChain, |
| ExecuteType type) |
| { |
| /* |
| * See encoding of ExecuteType. When GLOBAL isn't set, we are executing a |
| * script in the context of another frame and the frame type is determined |
| * by the context. |
| */ |
| flags_ = type | HAS_SCOPECHAIN; |
| |
| JSObject* callee = nullptr; |
| |
| // newTarget = NullValue is an initial sentinel for "please fill me in from the stack". |
| // It should never be passed from Ion code. |
| RootedValue newTarget(cx, newTargetValue); |
| if (!(flags_ & (GLOBAL | MODULE))) { |
| if (evalInFramePrev) { |
| MOZ_ASSERT(evalInFramePrev.isFunctionFrame() || evalInFramePrev.isGlobalFrame()); |
| if (evalInFramePrev.isFunctionFrame()) { |
| callee = evalInFramePrev.callee(); |
| if (newTarget.isNull()) |
| newTarget = evalInFramePrev.newTarget(); |
| flags_ |= FUNCTION; |
| } else { |
| flags_ |= GLOBAL; |
| } |
| } else { |
| FrameIter iter(cx); |
| MOZ_ASSERT(iter.isFunctionFrame() || iter.isGlobalFrame()); |
| MOZ_ASSERT(!iter.isAsmJS()); |
| if (iter.isFunctionFrame()) { |
| if (newTarget.isNull()) |
| newTarget = iter.newTarget(); |
| callee = iter.callee(cx); |
| flags_ |= FUNCTION; |
| } else { |
| flags_ |= GLOBAL; |
| } |
| } |
| } |
| |
| Value* dstvp = (Value*)this - 2; |
| |
| if (isFunctionFrame()) { |
| dstvp[1] = ObjectValue(*callee); |
| exec.fun = &callee->as<JSFunction>(); |
| u.evalScript = script; |
| } else { |
| MOZ_ASSERT(isGlobalFrame() || isModuleFrame()); |
| dstvp[1] = NullValue(); |
| exec.script = script; |
| #ifdef DEBUG |
| u.evalScript = (JSScript*)0xbad; |
| #endif |
| } |
| dstvp[0] = newTarget; |
| |
| scopeChain_ = scopeChain.get(); |
| prev_ = nullptr; |
| prevpc_ = nullptr; |
| prevsp_ = nullptr; |
| |
| MOZ_ASSERT_IF(evalInFramePrev, isDebuggerEvalFrame()); |
| evalInFramePrev_ = evalInFramePrev; |
| |
| if (script->isDebuggee()) |
| setIsDebuggee(); |
| |
| #ifdef DEBUG |
| Debug_SetValueRangeToCrashOnTouch(&rval_, 1); |
| #endif |
| } |
| |
| bool |
| InterpreterFrame::isNonGlobalEvalFrame() const |
| { |
| return isEvalFrame() && |
| script()->enclosingStaticScope()->as<StaticEvalObject>().isNonGlobal(); |
| } |
| |
| bool |
| InterpreterFrame::copyRawFrameSlots(AutoValueVector* vec) |
| { |
| if (!vec->resize(numFormalArgs() + script()->nfixed())) |
| return false; |
| PodCopy(vec->begin(), argv(), numFormalArgs()); |
| PodCopy(vec->begin() + numFormalArgs(), slots(), script()->nfixed()); |
| return true; |
| } |
| |
| JSObject* |
| InterpreterFrame::createRestParameter(JSContext* cx) |
| { |
| MOZ_ASSERT(fun()->hasRest()); |
| unsigned nformal = fun()->nargs() - 1, nactual = numActualArgs(); |
| unsigned nrest = (nactual > nformal) ? nactual - nformal : 0; |
| Value* restvp = argv() + nformal; |
| return ObjectGroup::newArrayObject(cx, restvp, nrest, GenericObject, |
| ObjectGroup::NewArrayKind::UnknownIndex); |
| } |
| |
| static inline void |
| AssertDynamicScopeMatchesStaticScope(JSContext* cx, JSScript* script, JSObject* scope) |
| { |
| #ifdef DEBUG |
| RootedObject originalScope(cx, scope); |
| RootedObject enclosingScope(cx, script->enclosingStaticScope()); |
| for (StaticScopeIter<NoGC> i(enclosingScope); !i.done(); i++) { |
| if (i.type() == StaticScopeIter<NoGC>::NonSyntactic) { |
| while (scope->is<DynamicWithObject>() || |
| scope->is<NonSyntacticVariablesObject>() || |
| (scope->is<ClonedBlockObject>() && |
| !scope->as<ClonedBlockObject>().isSyntactic())) |
| { |
| MOZ_ASSERT(!IsSyntacticScope(scope)); |
| scope = &scope->as<ScopeObject>().enclosingScope(); |
| } |
| } else if (i.hasSyntacticDynamicScopeObject()) { |
| switch (i.type()) { |
| case StaticScopeIter<NoGC>::Module: |
| MOZ_ASSERT(scope->as<ModuleEnvironmentObject>().module().script() == i.moduleScript()); |
| scope = &scope->as<ModuleEnvironmentObject>().enclosingScope(); |
| break; |
| case StaticScopeIter<NoGC>::Function: |
| MOZ_ASSERT(scope->as<CallObject>().callee().nonLazyScript() == i.funScript()); |
| scope = &scope->as<CallObject>().enclosingScope(); |
| break; |
| case StaticScopeIter<NoGC>::Block: |
| MOZ_ASSERT(&i.block() == scope->as<ClonedBlockObject>().staticScope()); |
| scope = &scope->as<ClonedBlockObject>().enclosingScope(); |
| break; |
| case StaticScopeIter<NoGC>::With: |
| MOZ_ASSERT(&i.staticWith() == scope->as<DynamicWithObject>().staticScope()); |
| scope = &scope->as<DynamicWithObject>().enclosingScope(); |
| break; |
| case StaticScopeIter<NoGC>::NamedLambda: |
| scope = &scope->as<DeclEnvObject>().enclosingScope(); |
| break; |
| case StaticScopeIter<NoGC>::Eval: |
| scope = &scope->as<CallObject>().enclosingScope(); |
| break; |
| case StaticScopeIter<NoGC>::NonSyntactic: |
| MOZ_CRASH("NonSyntactic should not have a syntactic scope"); |
| break; |
| } |
| } |
| } |
| |
| // In the case of a non-syntactic scope chain, the immediate parent of the |
| // outermost non-syntactic scope may be the global lexical scope, or, if |
| // called from Debugger, a DebugScopeObject. |
| // |
| // In the case of a syntactic scope chain, the outermost scope is always a |
| // GlobalObject. |
| MOZ_ASSERT(scope->is<GlobalObject>() || IsGlobalLexicalScope(scope) || |
| scope->is<DebugScopeObject>()); |
| #endif |
| } |
| |
| bool |
| InterpreterFrame::initFunctionScopeObjects(JSContext* cx) |
| { |
| CallObject* callobj = CallObject::createForFunction(cx, this); |
| if (!callobj) |
| return false; |
| pushOnScopeChain(*callobj); |
| flags_ |= HAS_CALL_OBJ; |
| return true; |
| } |
| |
| bool |
| InterpreterFrame::prologue(JSContext* cx) |
| { |
| RootedScript script(cx, this->script()); |
| |
| MOZ_ASSERT(isModuleFrame() == !!script->module()); |
| MOZ_ASSERT(cx->interpreterRegs().pc == script->code()); |
| |
| if (isEvalFrame()) { |
| if (script->strict()) { |
| CallObject* callobj = CallObject::createForStrictEval(cx, this); |
| if (!callobj) |
| return false; |
| pushOnScopeChain(*callobj); |
| flags_ |= HAS_CALL_OBJ; |
| } else { |
| // Non-strict eval may introduce var bindings that conflict with |
| // lexical bindings in an enclosing lexical scope. |
| RootedObject varObjRoot(cx, &varObj()); |
| if (!CheckEvalDeclarationConflicts(cx, script, scopeChain(), varObjRoot)) |
| return false; |
| } |
| return probes::EnterScript(cx, script, nullptr, this); |
| } |
| |
| if (isGlobalFrame()) { |
| Rooted<ClonedBlockObject*> lexicalScope(cx); |
| RootedObject varObjRoot(cx); |
| if (script->hasNonSyntacticScope()) { |
| lexicalScope = &extensibleLexicalScope(); |
| varObjRoot = &varObj(); |
| } else { |
| lexicalScope = &cx->global()->lexicalScope(); |
| varObjRoot = cx->global(); |
| } |
| if (!CheckGlobalDeclarationConflicts(cx, script, lexicalScope, varObjRoot)) |
| return false; |
| return probes::EnterScript(cx, script, nullptr, this); |
| } |
| |
| AssertDynamicScopeMatchesStaticScope(cx, script, scopeChain()); |
| |
| if (isModuleFrame()) |
| return probes::EnterScript(cx, script, nullptr, this); |
| |
| MOZ_ASSERT(isNonEvalFunctionFrame()); |
| if (fun()->needsCallObject() && !initFunctionScopeObjects(cx)) |
| return false; |
| |
| if (isConstructing()) { |
| if (script->isDerivedClassConstructor()) { |
| MOZ_ASSERT(callee().isClassConstructor()); |
| thisArgument() = MagicValue(JS_UNINITIALIZED_LEXICAL); |
| } else if (thisArgument().isPrimitive()) { |
| RootedObject callee(cx, &this->callee()); |
| RootedObject newTarget(cx, &this->newTarget().toObject()); |
| JSObject* obj = CreateThisForFunction(cx, callee, newTarget, |
| createSingleton() ? SingletonObject : GenericObject); |
| if (!obj) |
| return false; |
| thisArgument() = ObjectValue(*obj); |
| } |
| } |
| |
| return probes::EnterScript(cx, script, script->functionNonDelazifying(), this); |
| } |
| |
| void |
| InterpreterFrame::epilogue(JSContext* cx) |
| { |
| RootedScript script(cx, this->script()); |
| probes::ExitScript(cx, script, script->functionNonDelazifying(), hasPushedSPSFrame()); |
| |
| if (isEvalFrame()) { |
| if (isStrictEvalFrame()) { |
| MOZ_ASSERT_IF(hasCallObj(), scopeChain()->as<CallObject>().isForEval()); |
| if (MOZ_UNLIKELY(cx->compartment()->isDebuggee())) |
| DebugScopes::onPopStrictEvalScope(this); |
| } else if (isNonGlobalEvalFrame()) { |
| MOZ_ASSERT_IF(isDebuggerEvalFrame(), !IsSyntacticScope(scopeChain())); |
| } |
| return; |
| } |
| |
| if (isGlobalFrame()) { |
| // Confusingly, global frames may run in non-global scopes (that is, |
| // not directly under the GlobalObject and its lexical scope). |
| // |
| // Gecko often runs global scripts under custom scopes. See users of |
| // CreateNonSyntacticScopeChain. |
| MOZ_ASSERT(IsGlobalLexicalScope(scopeChain()) || !IsSyntacticScope(scopeChain())); |
| return; |
| } |
| |
| if (isModuleFrame()) |
| return; |
| |
| MOZ_ASSERT(isNonEvalFunctionFrame()); |
| |
| if (fun()->needsCallObject()) { |
| MOZ_ASSERT_IF(hasCallObj() && !fun()->isGenerator(), |
| scopeChain()->as<CallObject>().callee().nonLazyScript() == script); |
| } else { |
| AssertDynamicScopeMatchesStaticScope(cx, script, scopeChain()); |
| } |
| |
| if (MOZ_UNLIKELY(cx->compartment()->isDebuggee())) |
| DebugScopes::onPopCall(this, cx); |
| |
| if (!fun()->isGenerator() && |
| isConstructing() && |
| thisArgument().isObject() && |
| returnValue().isPrimitive()) |
| { |
| setReturnValue(thisArgument()); |
| } |
| } |
| |
| bool |
| InterpreterFrame::checkReturn(JSContext* cx, HandleValue thisv) |
| { |
| MOZ_ASSERT(script()->isDerivedClassConstructor()); |
| MOZ_ASSERT(isFunctionFrame()); |
| MOZ_ASSERT(callee().isClassConstructor()); |
| |
| HandleValue retVal = returnValue(); |
| if (retVal.isObject()) |
| return true; |
| |
| if (!retVal.isUndefined()) { |
| ReportValueError(cx, JSMSG_BAD_DERIVED_RETURN, JSDVG_IGNORE_STACK, retVal, nullptr); |
| return false; |
| } |
| |
| if (thisv.isMagic(JS_UNINITIALIZED_LEXICAL)) |
| return ThrowUninitializedThis(cx, this); |
| |
| setReturnValue(thisv); |
| return true; |
| } |
| |
| bool |
| InterpreterFrame::pushBlock(JSContext* cx, StaticBlockObject& block) |
| { |
| MOZ_ASSERT(block.needsClone()); |
| |
| Rooted<StaticBlockObject*> blockHandle(cx, &block); |
| ClonedBlockObject* clone = ClonedBlockObject::create(cx, blockHandle, this); |
| if (!clone) |
| return false; |
| |
| pushOnScopeChain(*clone); |
| |
| return true; |
| } |
| |
| bool |
| InterpreterFrame::freshenBlock(JSContext* cx) |
| { |
| MOZ_ASSERT(flags_ & HAS_SCOPECHAIN); |
| Rooted<ClonedBlockObject*> block(cx, &scopeChain_->as<ClonedBlockObject>()); |
| ClonedBlockObject* fresh = ClonedBlockObject::clone(cx, block); |
| if (!fresh) |
| return false; |
| |
| replaceInnermostScope(*fresh); |
| return true; |
| } |
| |
| void |
| InterpreterFrame::popBlock(JSContext* cx) |
| { |
| MOZ_ASSERT(scopeChain_->is<ClonedBlockObject>()); |
| popOffScopeChain(); |
| } |
| |
| void |
| InterpreterFrame::popWith(JSContext* cx) |
| { |
| if (MOZ_UNLIKELY(cx->compartment()->isDebuggee())) |
| DebugScopes::onPopWith(this); |
| |
| MOZ_ASSERT(scopeChain()->is<DynamicWithObject>()); |
| popOffScopeChain(); |
| } |
| |
| void |
| InterpreterFrame::mark(JSTracer* trc) |
| { |
| /* |
| * Normally we would use MarkRoot here, except that generators also take |
| * this path. However, generators use a special write barrier when the stack |
| * frame is copied to the floating frame. Therefore, no barrier is needed. |
| */ |
| if (flags_ & HAS_SCOPECHAIN) |
| TraceManuallyBarrieredEdge(trc, &scopeChain_, "scope chain"); |
| if (flags_ & HAS_ARGS_OBJ) |
| TraceManuallyBarrieredEdge(trc, &argsObj_, "arguments"); |
| if (isFunctionFrame()) { |
| TraceManuallyBarrieredEdge(trc, &exec.fun, "fun"); |
| if (isEvalFrame()) |
| TraceManuallyBarrieredEdge(trc, &u.evalScript, "eval script"); |
| } else { |
| TraceManuallyBarrieredEdge(trc, &exec.script, "script"); |
| } |
| if (trc->isMarkingTracer()) |
| script()->compartment()->zone()->active = true; |
| if (hasReturnValue()) |
| TraceManuallyBarrieredEdge(trc, &rval_, "rval"); |
| } |
| |
| void |
| InterpreterFrame::markValues(JSTracer* trc, unsigned start, unsigned end) |
| { |
| if (start < end) |
| TraceRootRange(trc, end - start, slots() + start, "vm_stack"); |
| } |
| |
| void |
| InterpreterFrame::markValues(JSTracer* trc, Value* sp, jsbytecode* pc) |
| { |
| MOZ_ASSERT(sp >= slots()); |
| |
| JSScript* script = this->script(); |
| size_t nfixed = script->nfixed(); |
| size_t nlivefixed = script->calculateLiveFixed(pc); |
| |
| if (nfixed == nlivefixed) { |
| // All locals are live. |
| markValues(trc, 0, sp - slots()); |
| } else { |
| // Mark operand stack. |
| markValues(trc, nfixed, sp - slots()); |
| |
| // Clear dead block-scoped locals. |
| while (nfixed > nlivefixed) |
| unaliasedLocal(--nfixed).setMagic(JS_UNINITIALIZED_LEXICAL); |
| |
| // Mark live locals. |
| markValues(trc, 0, nlivefixed); |
| } |
| |
| if (hasArgs()) { |
| // Mark callee, |this| and arguments. |
| unsigned argc = Max(numActualArgs(), numFormalArgs()); |
| TraceRootRange(trc, argc + 2 + isConstructing(), argv_ - 2, "fp argv"); |
| } else { |
| // Mark callee and newTarget |
| TraceRootRange(trc, 2, ((Value*)this) - 2, "stack callee and newTarget"); |
| } |
| } |
| |
| static void |
| MarkInterpreterActivation(JSTracer* trc, InterpreterActivation* act) |
| { |
| for (InterpreterFrameIterator frames(act); !frames.done(); ++frames) { |
| InterpreterFrame* fp = frames.frame(); |
| fp->markValues(trc, frames.sp(), frames.pc()); |
| fp->mark(trc); |
| } |
| } |
| |
| void |
| js::MarkInterpreterActivations(JSRuntime* rt, JSTracer* trc) |
| { |
| for (ActivationIterator iter(rt); !iter.done(); ++iter) { |
| Activation* act = iter.activation(); |
| if (act->isInterpreter()) |
| MarkInterpreterActivation(trc, act->asInterpreter()); |
| } |
| } |
| |
| /*****************************************************************************/ |
| |
| // Unlike the other methods of this class, this method is defined here so that |
| // we don't have to #include jsautooplen.h in vm/Stack.h. |
| void |
| InterpreterRegs::setToEndOfScript() |
| { |
| sp = fp()->base(); |
| pc = fp()->script()->lastPC(); |
| } |
| |
| /*****************************************************************************/ |
| |
| InterpreterFrame* |
| InterpreterStack::pushInvokeFrame(JSContext* cx, const CallArgs& args, InitialFrameFlags initial) |
| { |
| LifoAlloc::Mark mark = allocator_.mark(); |
| |
| RootedFunction fun(cx, &args.callee().as<JSFunction>()); |
| RootedScript script(cx, fun->nonLazyScript()); |
| |
| InterpreterFrame::Flags flags = ToFrameFlags(initial); |
| Value* argv; |
| InterpreterFrame* fp = getCallFrame(cx, args, script, &flags, &argv); |
| if (!fp) |
| return nullptr; |
| |
| fp->mark_ = mark; |
| fp->initCallFrame(cx, nullptr, nullptr, nullptr, *fun, script, argv, args.length(), flags); |
| return fp; |
| } |
| |
| InterpreterFrame* |
| InterpreterStack::pushExecuteFrame(JSContext* cx, HandleScript script, const Value& newTargetValue, |
| HandleObject scopeChain, ExecuteType type, |
| AbstractFramePtr evalInFrame) |
| { |
| LifoAlloc::Mark mark = allocator_.mark(); |
| |
| unsigned nvars = 2 /* callee, newTarget */ + script->nslots(); |
| uint8_t* buffer = allocateFrame(cx, sizeof(InterpreterFrame) + nvars * sizeof(Value)); |
| if (!buffer) |
| return nullptr; |
| |
| InterpreterFrame* fp = reinterpret_cast<InterpreterFrame*>(buffer + 2 * sizeof(Value)); |
| fp->mark_ = mark; |
| fp->initExecuteFrame(cx, script, evalInFrame, newTargetValue, scopeChain, type); |
| fp->initLocals(); |
| |
| return fp; |
| } |
| |
| /*****************************************************************************/ |
| |
| void |
| FrameIter::popActivation() |
| { |
| ++data_.activations_; |
| settleOnActivation(); |
| } |
| |
| void |
| FrameIter::popInterpreterFrame() |
| { |
| MOZ_ASSERT(data_.state_ == INTERP); |
| |
| ++data_.interpFrames_; |
| |
| if (data_.interpFrames_.done()) |
| popActivation(); |
| else |
| data_.pc_ = data_.interpFrames_.pc(); |
| } |
| |
| void |
| FrameIter::settleOnActivation() |
| { |
| while (true) { |
| if (data_.activations_.done()) { |
| data_.state_ = DONE; |
| return; |
| } |
| |
| Activation* activation = data_.activations_.activation(); |
| |
| // If JS_SaveFrameChain was called, stop iterating here (unless |
| // GO_THROUGH_SAVED is set). |
| if (data_.savedOption_ == STOP_AT_SAVED && activation->hasSavedFrameChain()) { |
| data_.state_ = DONE; |
| return; |
| } |
| |
| // Skip activations from another context if needed. |
| MOZ_ASSERT(activation->cx()); |
| MOZ_ASSERT(data_.cx_); |
| if (data_.contextOption_ == CURRENT_CONTEXT && activation->cx() != data_.cx_) { |
| ++data_.activations_; |
| continue; |
| } |
| |
| // If the caller supplied principals, only show activations which are subsumed (of the same |
| // origin or of an origin accessible) by these principals. |
| if (data_.principals_) { |
| JSContext* cx = data_.cx_->asJSContext(); |
| if (JSSubsumesOp subsumes = cx->runtime()->securityCallbacks->subsumes) { |
| if (!subsumes(data_.principals_, activation->compartment()->principals())) { |
| ++data_.activations_; |
| continue; |
| } |
| } |
| } |
| |
| if (activation->isJit()) { |
| data_.jitFrames_ = jit::JitFrameIterator(data_.activations_); |
| |
| // Stop at the first scripted frame. |
| while (!data_.jitFrames_.isScripted() && !data_.jitFrames_.done()) |
| ++data_.jitFrames_; |
| |
| // It's possible to have an JitActivation with no scripted frames, |
| // for instance if we hit an over-recursion during bailout. |
| if (data_.jitFrames_.done()) { |
| ++data_.activations_; |
| continue; |
| } |
| |
| nextJitFrame(); |
| data_.state_ = JIT; |
| return; |
| } |
| |
| if (activation->isAsmJS()) { |
| data_.asmJSFrames_ = AsmJSFrameIterator(*data_.activations_->asAsmJS()); |
| |
| if (data_.asmJSFrames_.done()) { |
| ++data_.activations_; |
| continue; |
| } |
| |
| data_.state_ = ASMJS; |
| return; |
| } |
| |
| MOZ_ASSERT(activation->isInterpreter()); |
| |
| InterpreterActivation* interpAct = activation->asInterpreter(); |
| data_.interpFrames_ = InterpreterFrameIterator(interpAct); |
| |
| // If we OSR'ed into JIT code, skip the interpreter frame so that |
| // the same frame is not reported twice. |
| if (data_.interpFrames_.frame()->runningInJit()) { |
| ++data_.interpFrames_; |
| if (data_.interpFrames_.done()) { |
| ++data_.activations_; |
| continue; |
| } |
| } |
| |
| MOZ_ASSERT(!data_.interpFrames_.frame()->runningInJit()); |
| data_.pc_ = data_.interpFrames_.pc(); |
| data_.state_ = INTERP; |
| return; |
| } |
| } |
| |
| FrameIter::Data::Data(JSContext* cx, SavedOption savedOption, |
| ContextOption contextOption, DebuggerEvalOption debuggerEvalOption, |
| JSPrincipals* principals) |
| : cx_(cx), |
| savedOption_(savedOption), |
| contextOption_(contextOption), |
| debuggerEvalOption_(debuggerEvalOption), |
| principals_(principals), |
| pc_(nullptr), |
| interpFrames_(nullptr), |
| activations_(cx->runtime()), |
| jitFrames_(), |
| ionInlineFrameNo_(0), |
| asmJSFrames_() |
| { |
| } |
| |
| FrameIter::Data::Data(const FrameIter::Data& other) |
| : cx_(other.cx_), |
| savedOption_(other.savedOption_), |
| contextOption_(other.contextOption_), |
| debuggerEvalOption_(other.debuggerEvalOption_), |
| principals_(other.principals_), |
| state_(other.state_), |
| pc_(other.pc_), |
| interpFrames_(other.interpFrames_), |
| activations_(other.activations_), |
| jitFrames_(other.jitFrames_), |
| ionInlineFrameNo_(other.ionInlineFrameNo_), |
| asmJSFrames_(other.asmJSFrames_) |
| { |
| } |
| |
| FrameIter::FrameIter(JSContext* cx, SavedOption savedOption) |
| : data_(cx, savedOption, CURRENT_CONTEXT, FOLLOW_DEBUGGER_EVAL_PREV_LINK, nullptr), |
| ionInlineFrames_(cx, (js::jit::JitFrameIterator*) nullptr) |
| { |
| // settleOnActivation can only GC if principals are given. |
| JS::AutoSuppressGCAnalysis nogc; |
| settleOnActivation(); |
| } |
| |
| FrameIter::FrameIter(JSContext* cx, ContextOption contextOption, |
| SavedOption savedOption, DebuggerEvalOption debuggerEvalOption) |
| : data_(cx, savedOption, contextOption, debuggerEvalOption, nullptr), |
| ionInlineFrames_(cx, (js::jit::JitFrameIterator*) nullptr) |
| { |
| // settleOnActivation can only GC if principals are given. |
| JS::AutoSuppressGCAnalysis nogc; |
| settleOnActivation(); |
| } |
| |
| FrameIter::FrameIter(JSContext* cx, ContextOption contextOption, |
| SavedOption savedOption, DebuggerEvalOption debuggerEvalOption, |
| JSPrincipals* principals) |
| : data_(cx, savedOption, contextOption, debuggerEvalOption, principals), |
| ionInlineFrames_(cx, (js::jit::JitFrameIterator*) nullptr) |
| { |
| settleOnActivation(); |
| } |
| |
| FrameIter::FrameIter(const FrameIter& other) |
| : data_(other.data_), |
| ionInlineFrames_(other.data_.cx_, |
| data_.jitFrames_.isIonScripted() ? &other.ionInlineFrames_ : nullptr) |
| { |
| } |
| |
| FrameIter::FrameIter(const Data& data) |
| : data_(data), |
| ionInlineFrames_(data.cx_, data_.jitFrames_.isIonScripted() ? &data_.jitFrames_ : nullptr) |
| { |
| MOZ_ASSERT(data.cx_); |
| |
| if (data_.jitFrames_.isIonScripted()) { |
| while (ionInlineFrames_.frameNo() != data.ionInlineFrameNo_) |
| ++ionInlineFrames_; |
| } |
| } |
| |
| void |
| FrameIter::nextJitFrame() |
| { |
| if (data_.jitFrames_.isIonScripted()) { |
| ionInlineFrames_.resetOn(&data_.jitFrames_); |
| data_.pc_ = ionInlineFrames_.pc(); |
| } else { |
| MOZ_ASSERT(data_.jitFrames_.isBaselineJS()); |
| data_.jitFrames_.baselineScriptAndPc(nullptr, &data_.pc_); |
| } |
| } |
| |
| void |
| FrameIter::popJitFrame() |
| { |
| MOZ_ASSERT(data_.state_ == JIT); |
| |
| if (data_.jitFrames_.isIonScripted() && ionInlineFrames_.more()) { |
| ++ionInlineFrames_; |
| data_.pc_ = ionInlineFrames_.pc(); |
| return; |
| } |
| |
| ++data_.jitFrames_; |
| while (!data_.jitFrames_.done() && !data_.jitFrames_.isScripted()) |
| ++data_.jitFrames_; |
| |
| if (!data_.jitFrames_.done()) { |
| nextJitFrame(); |
| return; |
| } |
| |
| popActivation(); |
| } |
| |
| void |
| FrameIter::popAsmJSFrame() |
| { |
| MOZ_ASSERT(data_.state_ == ASMJS); |
| |
| ++data_.asmJSFrames_; |
| if (data_.asmJSFrames_.done()) |
| popActivation(); |
| } |
| |
| FrameIter& |
| FrameIter::operator++() |
| { |
| switch (data_.state_) { |
| case DONE: |
| MOZ_CRASH("Unexpected state"); |
| case INTERP: |
| if (interpFrame()->isDebuggerEvalFrame() && |
| interpFrame()->evalInFramePrev() && |
| data_.debuggerEvalOption_ == FOLLOW_DEBUGGER_EVAL_PREV_LINK) |
| { |
| AbstractFramePtr eifPrev = interpFrame()->evalInFramePrev(); |
| |
| // Eval-in-frame can cross contexts and works across saved frame |
| // chains. |
| ContextOption prevContextOption = data_.contextOption_; |
| SavedOption prevSavedOption = data_.savedOption_; |
| data_.contextOption_ = ALL_CONTEXTS; |
| data_.savedOption_ = GO_THROUGH_SAVED; |
| |
| popInterpreterFrame(); |
| |
| while (!hasUsableAbstractFramePtr() || abstractFramePtr() != eifPrev) { |
| if (data_.state_ == JIT) |
| popJitFrame(); |
| else |
| popInterpreterFrame(); |
| } |
| |
| data_.contextOption_ = prevContextOption; |
| data_.savedOption_ = prevSavedOption; |
| data_.cx_ = data_.activations_->cx(); |
| break; |
| } |
| popInterpreterFrame(); |
| break; |
| case JIT: |
| popJitFrame(); |
| break; |
| case ASMJS: |
| popAsmJSFrame(); |
| break; |
| } |
| return *this; |
| } |
| |
| FrameIter::Data* |
| FrameIter::copyData() const |
| { |
| Data* data = data_.cx_->new_<Data>(data_); |
| if (!data) |
| return nullptr; |
| |
| MOZ_ASSERT(data_.state_ != ASMJS); |
| if (data && data_.jitFrames_.isIonScripted()) |
| data->ionInlineFrameNo_ = ionInlineFrames_.frameNo(); |
| // Give the copied Data the cx of the current activation, which may be |
| // different than the cx that the current FrameIter was constructed |
| // with. This ensures that when we instantiate another FrameIter with the |
| // copied data, its cx is still alive. |
| data->cx_ = activation()->cx(); |
| return data; |
| } |
| |
| AbstractFramePtr |
| FrameIter::copyDataAsAbstractFramePtr() const |
| { |
| AbstractFramePtr frame; |
| if (Data* data = copyData()) |
| frame.ptr_ = uintptr_t(data); |
| return frame; |
| } |
| |
| void* |
| FrameIter::rawFramePtr() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| case ASMJS: |
| return nullptr; |
| case JIT: |
| return data_.jitFrames_.fp(); |
| case INTERP: |
| return interpFrame(); |
| } |
| MOZ_CRASH("Unexpected state"); |
| } |
| |
| JSCompartment* |
| FrameIter::compartment() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case INTERP: |
| case JIT: |
| case ASMJS: |
| return data_.activations_->compartment(); |
| } |
| MOZ_CRASH("Unexpected state"); |
| } |
| |
| bool |
| FrameIter::isFunctionFrame() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case INTERP: |
| return interpFrame()->isFunctionFrame(); |
| case JIT: |
| MOZ_ASSERT(data_.jitFrames_.isScripted()); |
| if (data_.jitFrames_.isBaselineJS()) |
| return data_.jitFrames_.isFunctionFrame(); |
| return ionInlineFrames_.isFunctionFrame(); |
| case ASMJS: |
| return true; |
| } |
| MOZ_CRASH("Unexpected state"); |
| } |
| |
| bool |
| FrameIter::isGlobalFrame() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case INTERP: |
| return interpFrame()->isGlobalFrame(); |
| case JIT: |
| if (data_.jitFrames_.isBaselineJS()) |
| return data_.jitFrames_.baselineFrame()->isGlobalFrame(); |
| MOZ_ASSERT(!script()->isForEval()); |
| return !script()->functionNonDelazifying(); |
| case ASMJS: |
| return false; |
| } |
| MOZ_CRASH("Unexpected state"); |
| } |
| |
| bool |
| FrameIter::isEvalFrame() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case INTERP: |
| return interpFrame()->isEvalFrame(); |
| case JIT: |
| if (data_.jitFrames_.isBaselineJS()) |
| return data_.jitFrames_.baselineFrame()->isEvalFrame(); |
| MOZ_ASSERT(!script()->isForEval()); |
| return false; |
| case ASMJS: |
| return false; |
| } |
| MOZ_CRASH("Unexpected state"); |
| } |
| |
| bool |
| FrameIter::isNonEvalFunctionFrame() const |
| { |
| MOZ_ASSERT(!done()); |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case INTERP: |
| return interpFrame()->isNonEvalFunctionFrame(); |
| case JIT: |
| return !isEvalFrame() && isFunctionFrame(); |
| case ASMJS: |
| return true; |
| } |
| MOZ_CRASH("Unexpected state"); |
| } |
| |
| JSAtom* |
| FrameIter::functionDisplayAtom() const |
| { |
| MOZ_ASSERT(isNonEvalFunctionFrame()); |
| |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case INTERP: |
| case JIT: |
| return calleeTemplate()->displayAtom(); |
| case ASMJS: |
| return data_.asmJSFrames_.functionDisplayAtom(); |
| } |
| |
| MOZ_CRASH("Unexpected state"); |
| } |
| |
| ScriptSource* |
| FrameIter::scriptSource() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case INTERP: |
| case JIT: |
| return script()->scriptSource(); |
| case ASMJS: |
| return data_.activations_->asAsmJS()->module().scriptSource(); |
| } |
| |
| MOZ_CRASH("Unexpected state"); |
| } |
| |
| const char* |
| FrameIter::scriptFilename() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case INTERP: |
| case JIT: |
| return script()->filename(); |
| case ASMJS: |
| return data_.activations_->asAsmJS()->module().scriptSource()->filename(); |
| } |
| |
| MOZ_CRASH("Unexpected state"); |
| } |
| |
| const char16_t* |
| FrameIter::scriptDisplayURL() const |
| { |
| ScriptSource* ss = scriptSource(); |
| return ss->hasDisplayURL() ? ss->displayURL() : nullptr; |
| } |
| |
| unsigned |
| FrameIter::computeLine(uint32_t* column) const |
| { |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case INTERP: |
| case JIT: |
| return PCToLineNumber(script(), pc(), column); |
| case ASMJS: |
| return data_.asmJSFrames_.computeLine(column); |
| } |
| |
| MOZ_CRASH("Unexpected state"); |
| } |
| |
| bool |
| FrameIter::mutedErrors() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case INTERP: |
| case JIT: |
| return script()->mutedErrors(); |
| case ASMJS: |
| return data_.activations_->asAsmJS()->module().scriptSource()->mutedErrors(); |
| } |
| |
| MOZ_CRASH("Unexpected state"); |
| } |
| |
| bool |
| FrameIter::isConstructing() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| case ASMJS: |
| break; |
| case JIT: |
| if (data_.jitFrames_.isIonScripted()) |
| return ionInlineFrames_.isConstructing(); |
| MOZ_ASSERT(data_.jitFrames_.isBaselineJS()); |
| return data_.jitFrames_.isConstructing(); |
| case INTERP: |
| return interpFrame()->isConstructing(); |
| } |
| |
| MOZ_CRASH("Unexpected state"); |
| } |
| |
| bool |
| FrameIter::ensureHasRematerializedFrame(JSContext* cx) |
| { |
| MOZ_ASSERT(isIon()); |
| return !!activation()->asJit()->getRematerializedFrame(cx, data_.jitFrames_); |
| } |
| |
| bool |
| FrameIter::hasUsableAbstractFramePtr() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| case ASMJS: |
| return false; |
| case JIT: |
| if (data_.jitFrames_.isBaselineJS()) |
| return true; |
| |
| MOZ_ASSERT(data_.jitFrames_.isIonScripted()); |
| return !!activation()->asJit()->lookupRematerializedFrame(data_.jitFrames_.fp(), |
| ionInlineFrames_.frameNo()); |
| break; |
| case INTERP: |
| return true; |
| } |
| MOZ_CRASH("Unexpected state"); |
| } |
| |
| AbstractFramePtr |
| FrameIter::abstractFramePtr() const |
| { |
| MOZ_ASSERT(hasUsableAbstractFramePtr()); |
| switch (data_.state_) { |
| case DONE: |
| case ASMJS: |
| break; |
| case JIT: { |
| if (data_.jitFrames_.isBaselineJS()) |
| return data_.jitFrames_.baselineFrame(); |
| |
| MOZ_ASSERT(data_.jitFrames_.isIonScripted()); |
| return activation()->asJit()->lookupRematerializedFrame(data_.jitFrames_.fp(), |
| ionInlineFrames_.frameNo()); |
| break; |
| } |
| case INTERP: |
| MOZ_ASSERT(interpFrame()); |
| return AbstractFramePtr(interpFrame()); |
| } |
| MOZ_CRASH("Unexpected state"); |
| } |
| |
| void |
| FrameIter::updatePcQuadratic() |
| { |
| switch (data_.state_) { |
| case DONE: |
| case ASMJS: |
| break; |
| case INTERP: { |
| InterpreterFrame* frame = interpFrame(); |
| InterpreterActivation* activation = data_.activations_->asInterpreter(); |
| |
| // Look for the current frame. |
| data_.interpFrames_ = InterpreterFrameIterator(activation); |
| while (data_.interpFrames_.frame() != frame) |
| ++data_.interpFrames_; |
| |
| // Update the pc. |
| MOZ_ASSERT(data_.interpFrames_.frame() == frame); |
| data_.pc_ = data_.interpFrames_.pc(); |
| return; |
| } |
| case JIT: |
| if (data_.jitFrames_.isBaselineJS()) { |
| jit::BaselineFrame* frame = data_.jitFrames_.baselineFrame(); |
| jit::JitActivation* activation = data_.activations_->asJit(); |
| |
| // ActivationIterator::jitTop_ may be invalid, so create a new |
| // activation iterator. |
| data_.activations_ = ActivationIterator(data_.cx_->runtime()); |
| while (data_.activations_.activation() != activation) |
| ++data_.activations_; |
| |
| // Look for the current frame. |
| data_.jitFrames_ = jit::JitFrameIterator(data_.activations_); |
| while (!data_.jitFrames_.isBaselineJS() || data_.jitFrames_.baselineFrame() != frame) |
| ++data_.jitFrames_; |
| |
| // Update the pc. |
| MOZ_ASSERT(data_.jitFrames_.baselineFrame() == frame); |
| data_.jitFrames_.baselineScriptAndPc(nullptr, &data_.pc_); |
| return; |
| } |
| break; |
| } |
| MOZ_CRASH("Unexpected state"); |
| } |
| |
| JSFunction* |
| FrameIter::calleeTemplate() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| case ASMJS: |
| break; |
| case INTERP: |
| MOZ_ASSERT(isFunctionFrame()); |
| return &interpFrame()->callee(); |
| case JIT: |
| if (data_.jitFrames_.isBaselineJS()) |
| return data_.jitFrames_.callee(); |
| MOZ_ASSERT(data_.jitFrames_.isIonScripted()); |
| return ionInlineFrames_.calleeTemplate(); |
| } |
| MOZ_CRASH("Unexpected state"); |
| } |
| |
| JSFunction* |
| FrameIter::callee(JSContext* cx) const |
| { |
| switch (data_.state_) { |
| case DONE: |
| case ASMJS: |
| break; |
| case INTERP: |
| return calleeTemplate(); |
| case JIT: |
| if (data_.jitFrames_.isIonScripted()) { |
| jit::MaybeReadFallback recover(cx, activation()->asJit(), &data_.jitFrames_); |
| return ionInlineFrames_.callee(recover); |
| } |
| MOZ_ASSERT(data_.jitFrames_.isBaselineJS()); |
| return calleeTemplate(); |
| } |
| MOZ_CRASH("Unexpected state"); |
| } |
| |
| bool |
| FrameIter::matchCallee(JSContext* cx, HandleFunction fun) const |
| { |
| RootedFunction currentCallee(cx, calleeTemplate()); |
| |
| // As we do not know if the calleeTemplate is the real function, or the |
| // template from which it would be cloned, we compare properties which are |
| // stable across the cloning of JSFunctions. |
| if (((currentCallee->flags() ^ fun->flags()) & JSFunction::STABLE_ACROSS_CLONES) != 0 || |
| currentCallee->nargs() != fun->nargs()) |
| { |
| return false; |
| } |
| |
| // Use the same condition as |js::CloneFunctionObject|, to know if we should |
| // expect both functions to have the same JSScript. If so, and if they are |
| // different, then they cannot be equal. |
| RootedObject global(cx, &fun->global()); |
| bool useSameScript = CanReuseScriptForClone(fun->compartment(), currentCallee, global); |
| if (useSameScript && |
| (currentCallee->hasScript() != fun->hasScript() || |
| currentCallee->nonLazyScript() != fun->nonLazyScript())) |
| { |
| return false; |
| } |
| |
| // If none of the previous filters worked, then take the risk of |
| // invalidating the frame to identify the JSFunction. |
| return callee(cx) == fun; |
| } |
| |
| unsigned |
| FrameIter::numActualArgs() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| case ASMJS: |
| break; |
| case INTERP: |
| MOZ_ASSERT(isFunctionFrame()); |
| return interpFrame()->numActualArgs(); |
| case JIT: |
| if (data_.jitFrames_.isIonScripted()) |
| return ionInlineFrames_.numActualArgs(); |
| |
| MOZ_ASSERT(data_.jitFrames_.isBaselineJS()); |
| return data_.jitFrames_.numActualArgs(); |
| } |
| MOZ_CRASH("Unexpected state"); |
| } |
| |
| unsigned |
| FrameIter::numFormalArgs() const |
| { |
| return script()->functionNonDelazifying()->nargs(); |
| } |
| |
| Value |
| FrameIter::unaliasedActual(unsigned i, MaybeCheckAliasing checkAliasing) const |
| { |
| return abstractFramePtr().unaliasedActual(i, checkAliasing); |
| } |
| |
| JSObject* |
| FrameIter::scopeChain(JSContext* cx) const |
| { |
| switch (data_.state_) { |
| case DONE: |
| case ASMJS: |
| break; |
| case JIT: |
| if (data_.jitFrames_.isIonScripted()) { |
| jit::MaybeReadFallback recover(cx, activation()->asJit(), &data_.jitFrames_); |
| return ionInlineFrames_.scopeChain(recover); |
| } |
| return data_.jitFrames_.baselineFrame()->scopeChain(); |
| case INTERP: |
| return interpFrame()->scopeChain(); |
| } |
| MOZ_CRASH("Unexpected state"); |
| } |
| |
| CallObject& |
| FrameIter::callObj(JSContext* cx) const |
| { |
| MOZ_ASSERT(calleeTemplate()->needsCallObject()); |
| |
| JSObject* pobj = scopeChain(cx); |
| while (!pobj->is<CallObject>()) |
| pobj = pobj->enclosingScope(); |
| return pobj->as<CallObject>(); |
| } |
| |
| bool |
| FrameIter::hasArgsObj() const |
| { |
| return abstractFramePtr().hasArgsObj(); |
| } |
| |
| ArgumentsObject& |
| FrameIter::argsObj() const |
| { |
| MOZ_ASSERT(hasArgsObj()); |
| return abstractFramePtr().argsObj(); |
| } |
| |
| Value |
| FrameIter::thisArgument(JSContext* cx) const |
| { |
| MOZ_ASSERT(isNonEvalFunctionFrame()); |
| |
| switch (data_.state_) { |
| case DONE: |
| case ASMJS: |
| break; |
| case JIT: |
| if (data_.jitFrames_.isIonScripted()) { |
| jit::MaybeReadFallback recover(cx, activation()->asJit(), &data_.jitFrames_); |
| return ionInlineFrames_.thisArgument(recover); |
| } |
| return data_.jitFrames_.baselineFrame()->thisArgument(); |
| case INTERP: |
| return interpFrame()->thisArgument(); |
| } |
| MOZ_CRASH("Unexpected state"); |
| } |
| |
| Value |
| FrameIter::newTarget() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| case ASMJS: |
| break; |
| case INTERP: |
| return interpFrame()->newTarget(); |
| case JIT: |
| MOZ_ASSERT(data_.jitFrames_.isBaselineJS()); |
| return data_.jitFrames_.baselineFrame()->newTarget(); |
| } |
| MOZ_CRASH("Unexpected state"); |
| } |
| |
| Value |
| FrameIter::returnValue() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| case ASMJS: |
| break; |
| case JIT: |
| if (data_.jitFrames_.isBaselineJS()) |
| return data_.jitFrames_.baselineFrame()->returnValue(); |
| break; |
| case INTERP: |
| return interpFrame()->returnValue(); |
| } |
| MOZ_CRASH("Unexpected state"); |
| } |
| |
| void |
| FrameIter::setReturnValue(const Value& v) |
| { |
| switch (data_.state_) { |
| case DONE: |
| case ASMJS: |
| break; |
| case JIT: |
| if (data_.jitFrames_.isBaselineJS()) { |
| data_.jitFrames_.baselineFrame()->setReturnValue(v); |
| return; |
| } |
| break; |
| case INTERP: |
| interpFrame()->setReturnValue(v); |
| return; |
| } |
| MOZ_CRASH("Unexpected state"); |
| } |
| |
| size_t |
| FrameIter::numFrameSlots() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| case ASMJS: |
| break; |
| case JIT: { |
| if (data_.jitFrames_.isIonScripted()) { |
| return ionInlineFrames_.snapshotIterator().numAllocations() - |
| ionInlineFrames_.script()->nfixed(); |
| } |
| jit::BaselineFrame* frame = data_.jitFrames_.baselineFrame(); |
| return frame->numValueSlots() - data_.jitFrames_.script()->nfixed(); |
| } |
| case INTERP: |
| MOZ_ASSERT(data_.interpFrames_.sp() >= interpFrame()->base()); |
| return data_.interpFrames_.sp() - interpFrame()->base(); |
| } |
| MOZ_CRASH("Unexpected state"); |
| } |
| |
| Value |
| FrameIter::frameSlotValue(size_t index) const |
| { |
| switch (data_.state_) { |
| case DONE: |
| case ASMJS: |
| break; |
| case JIT: |
| if (data_.jitFrames_.isIonScripted()) { |
| jit::SnapshotIterator si(ionInlineFrames_.snapshotIterator()); |
| index += ionInlineFrames_.script()->nfixed(); |
| return si.maybeReadAllocByIndex(index); |
| } |
| |
| index += data_.jitFrames_.script()->nfixed(); |
| return *data_.jitFrames_.baselineFrame()->valueSlot(index); |
| case INTERP: |
| return interpFrame()->base()[index]; |
| } |
| MOZ_CRASH("Unexpected state"); |
| } |
| |
| #ifdef DEBUG |
| bool |
| js::SelfHostedFramesVisible() |
| { |
| static bool checked = false; |
| static bool visible = false; |
| if (!checked) { |
| checked = true; |
| char* env = js_sb_getenv("MOZ_SHOW_ALL_JS_FRAMES"); |
| visible = !!env; |
| } |
| return visible; |
| } |
| #endif |
| |
| void |
| NonBuiltinFrameIter::settle() |
| { |
| if (!SelfHostedFramesVisible()) { |
| while (!done() && hasScript() && script()->selfHosted()) |
| FrameIter::operator++(); |
| } |
| } |
| |
| void |
| NonBuiltinScriptFrameIter::settle() |
| { |
| if (!SelfHostedFramesVisible()) { |
| while (!done() && script()->selfHosted()) |
| ScriptFrameIter::operator++(); |
| } |
| } |
| |
| ActivationEntryMonitor::ActivationEntryMonitor(JSContext* cx) |
| : cx_(cx), entryMonitor_(cx->runtime()->entryMonitor) |
| { |
| cx->runtime()->entryMonitor = nullptr; |
| } |
| |
| Value |
| ActivationEntryMonitor::asyncStack(JSContext* cx) |
| { |
| RootedValue stack(cx, ObjectOrNullValue(cx->runtime()->asyncStackForNewActivations)); |
| if (!cx->compartment()->wrap(cx, &stack)) { |
| cx->clearPendingException(); |
| return UndefinedValue(); |
| } |
| return stack; |
| } |
| |
| ActivationEntryMonitor::ActivationEntryMonitor(JSContext* cx, InterpreterFrame* entryFrame) |
| : ActivationEntryMonitor(cx) |
| { |
| if (entryMonitor_) { |
| // The InterpreterFrame is not yet part of an Activation, so it won't |
| // be traced if we trigger GC here. Suppress GC to avoid this. |
| gc::AutoSuppressGC suppressGC(cx); |
| RootedValue stack(cx, asyncStack(cx)); |
| RootedString asyncCause(cx, cx->runtime()->asyncCauseForNewActivations); |
| if (entryFrame->isFunctionFrame()) |
| entryMonitor_->Entry(cx, entryFrame->fun(), stack, asyncCause); |
| else |
| entryMonitor_->Entry(cx, entryFrame->script(), stack, asyncCause); |
| } |
| } |
| |
| ActivationEntryMonitor::ActivationEntryMonitor(JSContext* cx, jit::CalleeToken entryToken) |
| : ActivationEntryMonitor(cx) |
| { |
| if (entryMonitor_) { |
| // The CalleeToken is not traced at this point and we also don't want |
| // a GC to discard the code we're about to enter, so we suppress GC. |
| gc::AutoSuppressGC suppressGC(cx); |
| RootedValue stack(cx, asyncStack(cx)); |
| RootedString asyncCause(cx, cx->runtime()->asyncCauseForNewActivations); |
| if (jit::CalleeTokenIsFunction(entryToken)) |
| entryMonitor_->Entry(cx_, jit::CalleeTokenToFunction(entryToken), stack, asyncCause); |
| else |
| entryMonitor_->Entry(cx_, jit::CalleeTokenToScript(entryToken), stack, asyncCause); |
| } |
| } |
| |
| /*****************************************************************************/ |
| |
| jit::JitActivation::JitActivation(JSContext* cx, bool active) |
| : Activation(cx, Jit), |
| active_(active), |
| isLazyLinkExitFrame_(false), |
| rematerializedFrames_(nullptr), |
| ionRecovery_(cx), |
| bailoutData_(nullptr), |
| lastProfilingFrame_(nullptr), |
| lastProfilingCallSite_(nullptr) |
| { |
| if (active) { |
| prevJitTop_ = cx->runtime()->jitTop; |
| prevJitJSContext_ = cx->runtime()->jitJSContext; |
| prevJitActivation_ = cx->runtime()->jitActivation; |
| cx->runtime()->jitJSContext = cx; |
| cx->runtime()->jitActivation = this; |
| |
| registerProfiling(); |
| } else { |
| prevJitTop_ = nullptr; |
| prevJitJSContext_ = nullptr; |
| prevJitActivation_ = nullptr; |
| } |
| } |
| |
| jit::JitActivation::~JitActivation() |
| { |
| if (active_) { |
| if (isProfiling()) |
| unregisterProfiling(); |
| |
| cx_->runtime()->jitTop = prevJitTop_; |
| cx_->runtime()->jitJSContext = prevJitJSContext_; |
| cx_->runtime()->jitActivation = prevJitActivation_; |
| } |
| |
| // All reocvered value are taken from activation during the bailout. |
| MOZ_ASSERT(ionRecovery_.empty()); |
| |
| // The BailoutFrameInfo should have unregistered itself from the |
| // JitActivations. |
| MOZ_ASSERT(!bailoutData_); |
| |
| clearRematerializedFrames(); |
| js_delete(rematerializedFrames_); |
| } |
| |
| bool |
| jit::JitActivation::isProfiling() const |
| { |
| // All JitActivations can be profiled. |
| return true; |
| } |
| |
| void |
| jit::JitActivation::setBailoutData(jit::BailoutFrameInfo* bailoutData) |
| { |
| MOZ_ASSERT(!bailoutData_); |
| bailoutData_ = bailoutData; |
| } |
| |
| void |
| jit::JitActivation::cleanBailoutData() |
| { |
| MOZ_ASSERT(bailoutData_); |
| bailoutData_ = nullptr; |
| } |
| |
| // setActive() is inlined in GenerateFFIIonExit() with explicit masm instructions so |
| // changes to the logic here need to be reflected in GenerateFFIIonExit() in the enable |
| // and disable activation instruction sequences. |
| void |
| jit::JitActivation::setActive(JSContext* cx, bool active) |
| { |
| // Only allowed to deactivate/activate if activation is top. |
| // (Not tested and will probably fail in other situations.) |
| MOZ_ASSERT(cx->runtime()->activation_ == this); |
| MOZ_ASSERT(active != active_); |
| |
| if (active) { |
| *((volatile bool*) active_) = true; |
| prevJitTop_ = cx->runtime()->jitTop; |
| prevJitJSContext_ = cx->runtime()->jitJSContext; |
| prevJitActivation_ = cx->runtime()->jitActivation; |
| cx->runtime()->jitJSContext = cx; |
| cx->runtime()->jitActivation = this; |
| |
| registerProfiling(); |
| |
| } else { |
| unregisterProfiling(); |
| |
| cx->runtime()->jitTop = prevJitTop_; |
| cx->runtime()->jitJSContext = prevJitJSContext_; |
| cx->runtime()->jitActivation = prevJitActivation_; |
| |
| *((volatile bool*) active_) = false; |
| } |
| } |
| |
| void |
| jit::JitActivation::removeRematerializedFrame(uint8_t* top) |
| { |
| if (!rematerializedFrames_) |
| return; |
| |
| if (RematerializedFrameTable::Ptr p = rematerializedFrames_->lookup(top)) { |
| RematerializedFrame::FreeInVector(p->value()); |
| rematerializedFrames_->remove(p); |
| } |
| } |
| |
| void |
| jit::JitActivation::clearRematerializedFrames() |
| { |
| if (!rematerializedFrames_) |
| return; |
| |
| for (RematerializedFrameTable::Enum e(*rematerializedFrames_); !e.empty(); e.popFront()) { |
| RematerializedFrame::FreeInVector(e.front().value()); |
| e.removeFront(); |
| } |
| } |
| |
| jit::RematerializedFrame* |
| jit::JitActivation::getRematerializedFrame(JSContext* cx, const JitFrameIterator& iter, size_t inlineDepth) |
| { |
| MOZ_ASSERT(iter.activation() == this); |
| MOZ_ASSERT(iter.isIonScripted()); |
| |
| if (!rematerializedFrames_) { |
| rematerializedFrames_ = cx->new_<RematerializedFrameTable>(cx); |
| if (!rematerializedFrames_) |
| return nullptr; |
| if (!rematerializedFrames_->init()) { |
| rematerializedFrames_ = nullptr; |
| ReportOutOfMemory(cx); |
| return nullptr; |
| } |
| } |
| |
| uint8_t* top = iter.fp(); |
| RematerializedFrameTable::AddPtr p = rematerializedFrames_->lookupForAdd(top); |
| if (!p) { |
| RematerializedFrameVector empty(cx); |
| if (!rematerializedFrames_->add(p, top, Move(empty))) { |
| ReportOutOfMemory(cx); |
| return nullptr; |
| } |
| |
| // The unit of rematerialization is an uninlined frame and its inlined |
| // frames. Since inlined frames do not exist outside of snapshots, it |
| // is impossible to synchronize their rematerialized copies to |
| // preserve identity. Therefore, we always rematerialize an uninlined |
| // frame and all its inlined frames at once. |
| InlineFrameIterator inlineIter(cx, &iter); |
| MaybeReadFallback recover(cx, this, &iter); |
| |
| // Frames are often rematerialized with the cx inside a Debugger's |
| // compartment. To recover slots and to create CallObjects, we need to |
| // be in the activation's compartment. |
| AutoCompartment ac(cx, compartment_); |
| |
| if (!RematerializedFrame::RematerializeInlineFrames(cx, top, inlineIter, recover, |
| p->value())) |
| { |
| return nullptr; |
| } |
| |
| // See comment in unsetPrevUpToDateUntil. |
| DebugScopes::unsetPrevUpToDateUntil(cx, p->value()[inlineDepth]); |
| } |
| |
| return p->value()[inlineDepth]; |
| } |
| |
| jit::RematerializedFrame* |
| jit::JitActivation::lookupRematerializedFrame(uint8_t* top, size_t inlineDepth) |
| { |
| if (!rematerializedFrames_) |
| return nullptr; |
| if (RematerializedFrameTable::Ptr p = rematerializedFrames_->lookup(top)) |
| return inlineDepth < p->value().length() ? p->value()[inlineDepth] : nullptr; |
| return nullptr; |
| } |
| |
| void |
| jit::JitActivation::removeRematerializedFramesFromDebugger(JSContext* cx, uint8_t* top) |
| { |
| // Ion bailout can fail due to overrecursion and OOM. In such cases we |
| // cannot honor any further Debugger hooks on the frame, and need to |
| // ensure that its Debugger.Frame entry is cleaned up. |
| if (!cx->compartment()->isDebuggee() || !rematerializedFrames_) |
| return; |
| if (RematerializedFrameTable::Ptr p = rematerializedFrames_->lookup(top)) { |
| for (uint32_t i = 0; i < p->value().length(); i++) |
| Debugger::handleUnrecoverableIonBailoutError(cx, p->value()[i]); |
| } |
| } |
| |
| void |
| jit::JitActivation::markRematerializedFrames(JSTracer* trc) |
| { |
| if (!rematerializedFrames_) |
| return; |
| for (RematerializedFrameTable::Enum e(*rematerializedFrames_); !e.empty(); e.popFront()) |
| RematerializedFrame::MarkInVector(trc, e.front().value()); |
| } |
| |
| bool |
| jit::JitActivation::registerIonFrameRecovery(RInstructionResults&& results) |
| { |
| // Check that there is no entry in the vector yet. |
| MOZ_ASSERT(!maybeIonFrameRecovery(results.frame())); |
| if (!ionRecovery_.append(mozilla::Move(results))) |
| return false; |
| |
| return true; |
| } |
| |
| jit::RInstructionResults* |
| jit::JitActivation::maybeIonFrameRecovery(JitFrameLayout* fp) |
| { |
| for (RInstructionResults* it = ionRecovery_.begin(); it != ionRecovery_.end(); ) { |
| if (it->frame() == fp) |
| return it; |
| } |
| |
| return nullptr; |
| } |
| |
| void |
| jit::JitActivation::removeIonFrameRecovery(JitFrameLayout* fp) |
| { |
| RInstructionResults* elem = maybeIonFrameRecovery(fp); |
| if (!elem) |
| return; |
| |
| ionRecovery_.erase(elem); |
| } |
| |
| void |
| jit::JitActivation::markIonRecovery(JSTracer* trc) |
| { |
| for (RInstructionResults* it = ionRecovery_.begin(); it != ionRecovery_.end(); it++) |
| it->trace(trc); |
| } |
| |
| AsmJSActivation::AsmJSActivation(JSContext* cx, AsmJSModule& module) |
| : Activation(cx, AsmJS), |
| module_(module), |
| entrySP_(nullptr), |
| resumePC_(nullptr), |
| fp_(nullptr), |
| packedExitReason_(wasm::ExitReason(wasm::ExitReason::None).pack()) |
| { |
| (void) entrySP_; // squelch GCC warning |
| |
| prevAsmJSForModule_ = module.activation(); |
| module.activation() = this; |
| |
| prevAsmJS_ = cx->runtime()->asmJSActivationStack_; |
| cx->runtime()->asmJSActivationStack_ = this; |
| |
| // Now that the AsmJSActivation is fully initialized, make it visible to |
| // asynchronous profiling. |
| registerProfiling(); |
| } |
| |
| AsmJSActivation::~AsmJSActivation() |
| { |
| // Hide this activation from the profiler before is is destroyed. |
| unregisterProfiling(); |
| |
| MOZ_ASSERT(fp_ == nullptr); |
| |
| MOZ_ASSERT(module_.activation() == this); |
| module_.activation() = prevAsmJSForModule_; |
| |
| JSContext* cx = cx_->asJSContext(); |
| MOZ_ASSERT(cx->runtime()->asmJSActivationStack_ == this); |
| |
| cx->runtime()->asmJSActivationStack_ = prevAsmJS_; |
| } |
| |
| InterpreterFrameIterator& |
| InterpreterFrameIterator::operator++() |
| { |
| MOZ_ASSERT(!done()); |
| if (fp_ != activation_->entryFrame_) { |
| pc_ = fp_->prevpc(); |
| sp_ = fp_->prevsp(); |
| fp_ = fp_->prev(); |
| } else { |
| pc_ = nullptr; |
| sp_ = nullptr; |
| fp_ = nullptr; |
| } |
| return *this; |
| } |
| |
| void |
| Activation::registerProfiling() |
| { |
| MOZ_ASSERT(isProfiling()); |
| cx_->runtime()->profilingActivation_ = this; |
| } |
| |
| void |
| Activation::unregisterProfiling() |
| { |
| MOZ_ASSERT(isProfiling()); |
| MOZ_ASSERT(cx_->runtime()->profilingActivation_ == this); |
| |
| // There may be a non-active jit activation in the linked list. Skip past it. |
| Activation* prevProfiling = prevProfiling_; |
| while (prevProfiling && prevProfiling->isJit() && !prevProfiling->asJit()->isActive()) |
| prevProfiling = prevProfiling->prevProfiling_; |
| |
| cx_->runtime()->profilingActivation_ = prevProfiling; |
| } |
| |
| ActivationIterator::ActivationIterator(JSRuntime* rt) |
| : jitTop_(rt->jitTop), |
| activation_(rt->activation_) |
| { |
| settle(); |
| } |
| |
| ActivationIterator& |
| ActivationIterator::operator++() |
| { |
| MOZ_ASSERT(activation_); |
| if (activation_->isJit() && activation_->asJit()->isActive()) |
| jitTop_ = activation_->asJit()->prevJitTop(); |
| activation_ = activation_->prev(); |
| settle(); |
| return *this; |
| } |
| |
| void |
| ActivationIterator::settle() |
| { |
| // Stop at the next active activation. No need to update jitTop_, since |
| // we don't iterate over an active jit activation. |
| while (!done() && activation_->isJit() && !activation_->asJit()->isActive()) |
| activation_ = activation_->prev(); |
| } |
| |
| JS::ProfilingFrameIterator::ProfilingFrameIterator(JSRuntime* rt, const RegisterState& state, |
| uint32_t sampleBufferGen) |
| : rt_(rt), |
| sampleBufferGen_(sampleBufferGen), |
| activation_(nullptr), |
| savedPrevJitTop_(nullptr) |
| { |
| if (!rt->spsProfiler.enabled()) |
| MOZ_CRASH("ProfilingFrameIterator called when spsProfiler not enabled for runtime."); |
| |
| if (!rt->profilingActivation()) |
| return; |
| |
| // If profiler sampling is not enabled, skip. |
| if (!rt_->isProfilerSamplingEnabled()) |
| return; |
| |
| activation_ = rt->profilingActivation(); |
| |
| MOZ_ASSERT(activation_->isProfiling()); |
| |
| static_assert(sizeof(AsmJSProfilingFrameIterator) <= StorageSpace && |
| sizeof(jit::JitProfilingFrameIterator) <= StorageSpace, |
| "Need to increase storage"); |
| |
| iteratorConstruct(state); |
| settle(); |
| } |
| |
| JS::ProfilingFrameIterator::~ProfilingFrameIterator() |
| { |
| if (!done()) { |
| MOZ_ASSERT(activation_->isProfiling()); |
| iteratorDestroy(); |
| } |
| } |
| |
| void |
| JS::ProfilingFrameIterator::operator++() |
| { |
| MOZ_ASSERT(!done()); |
| MOZ_ASSERT(activation_->isAsmJS() || activation_->isJit()); |
| |
| if (activation_->isAsmJS()) { |
| ++asmJSIter(); |
| settle(); |
| return; |
| } |
| |
| ++jitIter(); |
| settle(); |
| } |
| |
| void |
| JS::ProfilingFrameIterator::settle() |
| { |
| while (iteratorDone()) { |
| iteratorDestroy(); |
| activation_ = activation_->prevProfiling(); |
| |
| // Skip past any non-active jit activations in the list. |
| while (activation_ && activation_->isJit() && !activation_->asJit()->isActive()) |
| activation_ = activation_->prevProfiling(); |
| |
| if (!activation_) |
| return; |
| iteratorConstruct(); |
| } |
| } |
| |
| void |
| JS::ProfilingFrameIterator::iteratorConstruct(const RegisterState& state) |
| { |
| MOZ_ASSERT(!done()); |
| MOZ_ASSERT(activation_->isAsmJS() || activation_->isJit()); |
| |
| if (activation_->isAsmJS()) { |
| new (storage_.addr()) AsmJSProfilingFrameIterator(*activation_->asAsmJS(), state); |
| // Set savedPrevJitTop_ to the actual jitTop_ from the runtime. |
| savedPrevJitTop_ = activation_->cx()->runtime()->jitTop; |
| return; |
| } |
| |
| MOZ_ASSERT(activation_->asJit()->isActive()); |
| new (storage_.addr()) jit::JitProfilingFrameIterator(rt_, state); |
| } |
| |
| void |
| JS::ProfilingFrameIterator::iteratorConstruct() |
| { |
| MOZ_ASSERT(!done()); |
| MOZ_ASSERT(activation_->isAsmJS() || activation_->isJit()); |
| |
| if (activation_->isAsmJS()) { |
| new (storage_.addr()) AsmJSProfilingFrameIterator(*activation_->asAsmJS()); |
| return; |
| } |
| |
| MOZ_ASSERT(activation_->asJit()->isActive()); |
| MOZ_ASSERT(savedPrevJitTop_ != nullptr); |
| new (storage_.addr()) jit::JitProfilingFrameIterator(savedPrevJitTop_); |
| } |
| |
| void |
| JS::ProfilingFrameIterator::iteratorDestroy() |
| { |
| MOZ_ASSERT(!done()); |
| MOZ_ASSERT(activation_->isAsmJS() || activation_->isJit()); |
| |
| if (activation_->isAsmJS()) { |
| asmJSIter().~AsmJSProfilingFrameIterator(); |
| return; |
| } |
| |
| // Save prevjitTop for later use |
| savedPrevJitTop_ = activation_->asJit()->prevJitTop(); |
| jitIter().~JitProfilingFrameIterator(); |
| } |
| |
| bool |
| JS::ProfilingFrameIterator::iteratorDone() |
| { |
| MOZ_ASSERT(!done()); |
| MOZ_ASSERT(activation_->isAsmJS() || activation_->isJit()); |
| |
| if (activation_->isAsmJS()) |
| return asmJSIter().done(); |
| |
| return jitIter().done(); |
| } |
| |
| void* |
| JS::ProfilingFrameIterator::stackAddress() const |
| { |
| MOZ_ASSERT(!done()); |
| MOZ_ASSERT(activation_->isAsmJS() || activation_->isJit()); |
| |
| if (activation_->isAsmJS()) |
| return asmJSIter().stackAddress(); |
| |
| return jitIter().stackAddress(); |
| } |
| |
| Maybe<JS::ProfilingFrameIterator::Frame> |
| JS::ProfilingFrameIterator::getPhysicalFrameAndEntry(jit::JitcodeGlobalEntry* entry) const |
| { |
| void* stackAddr = stackAddress(); |
| |
| if (isAsmJS()) { |
| Frame frame; |
| frame.kind = Frame_AsmJS; |
| frame.stackAddress = stackAddr; |
| frame.returnAddress = nullptr; |
| frame.activation = activation_; |
| frame.label = nullptr; |
| return mozilla::Some(frame); |
| } |
| |
| MOZ_ASSERT(isJit()); |
| |
| // Look up an entry for the return address. |
| void* returnAddr = jitIter().returnAddressToFp(); |
| jit::JitcodeGlobalTable* table = rt_->jitRuntime()->getJitcodeGlobalTable(); |
| if (hasSampleBufferGen()) |
| table->lookupForSampler(returnAddr, entry, rt_, sampleBufferGen_); |
| else |
| table->lookupInfallible(returnAddr, entry, rt_); |
| |
| MOZ_ASSERT(entry->isIon() || entry->isIonCache() || entry->isBaseline() || entry->isDummy()); |
| |
| // Dummy frames produce no stack frames. |
| if (entry->isDummy()) |
| return mozilla::Nothing(); |
| |
| Frame frame; |
| frame.kind = entry->isBaseline() ? Frame_Baseline : Frame_Ion; |
| frame.stackAddress = stackAddr; |
| frame.returnAddress = returnAddr; |
| frame.activation = activation_; |
| frame.label = nullptr; |
| return mozilla::Some(frame); |
| } |
| |
| uint32_t |
| JS::ProfilingFrameIterator::extractStack(Frame* frames, uint32_t offset, uint32_t end) const |
| { |
| if (offset >= end) |
| return 0; |
| |
| jit::JitcodeGlobalEntry entry; |
| Maybe<Frame> physicalFrame = getPhysicalFrameAndEntry(&entry); |
| |
| // Dummy frames produce no stack frames. |
| if (physicalFrame.isNothing()) |
| return 0; |
| |
| if (isAsmJS()) { |
| frames[offset] = physicalFrame.value(); |
| frames[offset].label = asmJSIter().label(); |
| return 1; |
| } |
| |
| // Extract the stack for the entry. Assume maximum inlining depth is <64 |
| const char* labels[64]; |
| uint32_t depth = entry.callStackAtAddr(rt_, jitIter().returnAddressToFp(), labels, 64); |
| MOZ_ASSERT(depth < 64); |
| for (uint32_t i = 0; i < depth; i++) { |
| if (offset + i >= end) |
| return i; |
| frames[offset + i] = physicalFrame.value(); |
| frames[offset + i].label = labels[i]; |
| } |
| |
| return depth; |
| } |
| |
| Maybe<JS::ProfilingFrameIterator::Frame> |
| JS::ProfilingFrameIterator::getPhysicalFrameWithoutLabel() const |
| { |
| jit::JitcodeGlobalEntry unused; |
| return getPhysicalFrameAndEntry(&unused); |
| } |
| |
| bool |
| JS::ProfilingFrameIterator::isAsmJS() const |
| { |
| MOZ_ASSERT(!done()); |
| return activation_->isAsmJS(); |
| } |
| |
| bool |
| JS::ProfilingFrameIterator::isJit() const |
| { |
| return activation_->isJit(); |
| } |