author | Shu-yu Guo <shu@rfrn.org> |
Wed, 14 Jan 2015 22:57:35 -0800 | |
changeset 223946 | fb00dedf441c67ada7051bfa1734638bf29f047b |
parent 223945 | 7cd551e7aa2a2e21531bcb5fc54f639746b79309 |
child 223947 | f7498b5f244b58f9a23cb736a5cb531a2ea476a7 |
push id | 28112 |
push user | cbook@mozilla.com |
push date | Thu, 15 Jan 2015 13:19:02 +0000 |
treeherder | mozilla-central@206bf1a98cd7 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | luke |
bugs | 963879 |
milestone | 38.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/js/src/builtin/Eval.cpp +++ b/js/src/builtin/Eval.cpp @@ -302,16 +302,21 @@ EvalKernel(JSContext *cx, const CallArgs evalType == DIRECT_EVAL ? CALLED_FROM_JSOP_EVAL : NOT_CALLED_FROM_JSOP_EVAL); const char *introducerFilename = filename; if (maybeScript && maybeScript->scriptSource()->introducerFilename()) introducerFilename = maybeScript->scriptSource()->introducerFilename(); + RootedObject enclosing(cx); + if (evalType == DIRECT_EVAL) + enclosing = callerScript->innermostStaticScope(pc); + Rooted<StaticEvalObject *> staticScope(cx, StaticEvalObject::create(cx, enclosing)); + CompileOptions options(cx); options.setFileAndLine(filename, 1) .setCompileAndGo(true) .setForEval(true) .setNoScriptRval(false) .setMutedErrors(mutedErrors) .setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset) .maybeMakeStrictMode(evalType == DIRECT_EVAL && IsStrictEvalPC(pc)); @@ -321,21 +326,24 @@ EvalKernel(JSContext *cx, const CallArgs return false; const char16_t *chars = flatChars.twoByteRange().start().get(); SourceBufferHolder::Ownership ownership = flatChars.maybeGiveOwnershipToCaller() ? SourceBufferHolder::GiveOwnership : SourceBufferHolder::NoOwnership; SourceBufferHolder srcBuf(chars, flatStr->length(), ownership); JSScript *compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(), - scopeobj, callerScript, options, - srcBuf, flatStr, staticLevel); + scopeobj, callerScript, staticScope, + options, srcBuf, flatStr, staticLevel); if (!compiled) return false; + if (compiled->strict()) + staticScope->setStrict(); + esg.setNewScript(compiled); } return ExecuteKernel(cx, esg.script(), *scopeobj, thisv, ExecuteType(evalType), NullFramePtr() /* evalInFrame */, args.rval().address()); } bool @@ -376,16 +384,21 @@ js::DirectEvalStringFromIon(JSContext *c uint32_t pcOffset; DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno, &pcOffset, &mutedErrors, CALLED_FROM_JSOP_EVAL); const char *introducerFilename = filename; if (maybeScript && maybeScript->scriptSource()->introducerFilename()) introducerFilename = maybeScript->scriptSource()->introducerFilename(); + RootedObject enclosing(cx, callerScript->innermostStaticScope(pc)); + Rooted<StaticEvalObject *> staticScope(cx, StaticEvalObject::create(cx, enclosing)); + if (!staticScope) + return false; + CompileOptions options(cx); options.setFileAndLine(filename, 1) .setCompileAndGo(true) .setForEval(true) .setNoScriptRval(false) .setMutedErrors(mutedErrors) .setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset) .maybeMakeStrictMode(IsStrictEvalPC(pc)); @@ -395,21 +408,24 @@ js::DirectEvalStringFromIon(JSContext *c return false; const char16_t *chars = flatChars.twoByteRange().start().get(); SourceBufferHolder::Ownership ownership = flatChars.maybeGiveOwnershipToCaller() ? SourceBufferHolder::GiveOwnership : SourceBufferHolder::NoOwnership; SourceBufferHolder srcBuf(chars, flatStr->length(), ownership); JSScript *compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(), - scopeobj, callerScript, options, - srcBuf, flatStr, staticLevel); + scopeobj, callerScript, staticScope, + options, srcBuf, flatStr, staticLevel); if (!compiled) return false; + if (compiled->strict()) + staticScope->setStrict(); + esg.setNewScript(compiled); } // Primitive 'this' values should have been filtered out by Ion. If boxed, // the calling frame cannot be updated to store the new object. MOZ_ASSERT(thisValue.isObject() || thisValue.isUndefined() || thisValue.isNull()); return ExecuteKernel(cx, esg.script(), *scopeobj, thisValue, ExecuteType(DIRECT_EVAL),
--- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -205,16 +205,17 @@ frontend::CreateScriptSourceObject(Exclu } return sso; } JSScript * frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject scopeChain, HandleScript evalCaller, + Handle<StaticEvalObject *> evalStaticScope, const ReadOnlyCompileOptions &options, SourceBufferHolder &srcBuf, JSString *source_ /* = nullptr */, unsigned staticLevel /* = 0 */, SourceCompressionTask *extraSct /* = nullptr */) { MOZ_ASSERT(srcBuf.get()); @@ -279,33 +280,33 @@ frontend::CompileScript(ExclusiveContext if (!parser.checkOptions()) return nullptr; Directives directives(options.strictOption); GlobalSharedContext globalsc(cx, scopeChain, directives, options.extraWarningsOption); bool savedCallerFun = evalCaller && evalCaller->functionOrCallerFunction(); - Rooted<JSScript*> script(cx, JSScript::Create(cx, NullPtr(), savedCallerFun, + Rooted<JSScript*> script(cx, JSScript::Create(cx, evalStaticScope, savedCallerFun, options, staticLevel, sourceObject, 0, srcBuf.length())); if (!script) return nullptr; // We can specialize a bit for the given scope chain if that scope chain is the global object. JSObject *globalScope = scopeChain && scopeChain == &scopeChain->global() ? (JSObject*) scopeChain : nullptr; MOZ_ASSERT_IF(globalScope, globalScope->isNative()); MOZ_ASSERT_IF(globalScope, JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(globalScope->getClass())); BytecodeEmitter::EmitterMode emitterMode = options.selfHostingMode ? BytecodeEmitter::SelfHosting : BytecodeEmitter::Normal; BytecodeEmitter bce(/* parent = */ nullptr, &parser, &globalsc, script, /* lazyScript = */ js::NullPtr(), options.forEval, - evalCaller, !!globalScope, options.lineno, emitterMode); + evalCaller, evalStaticScope, !!globalScope, options.lineno, emitterMode); if (!bce.init()) return nullptr; // Syntax parsing may cause us to restart processing of top level // statements in the script. Use Maybe<> so that the parse context can be // reset when this occurs. Maybe<ParseContext<FullParseHandler> > pc; @@ -511,16 +512,17 @@ frontend::CompileLazyFunction(JSContext script->setDirectlyInsideEval(); if (lazy->usesArgumentsApplyAndThis()) script->setUsesArgumentsApplyAndThis(); if (lazy->hasBeenCloned()) script->setHasBeenCloned(); BytecodeEmitter bce(/* parent = */ nullptr, &parser, pn->pn_funbox, script, lazy, options.forEval, /* evalCaller = */ js::NullPtr(), + /* evalStaticScope = */ js::NullPtr(), /* hasGlobalScope = */ true, options.lineno, BytecodeEmitter::LazyFunction); if (!bce.init()) return false; return EmitFunctionScript(cx, &bce, pn->pn_body); } @@ -643,16 +645,17 @@ CompileFunctionBody(JSContext *cx, Mutab * consumers of JS::CompileFunction, namely * EventListenerManager::CompileEventHandlerInternal, passes in a * nullptr environment. This compiled function is never used, but * instead is cloned immediately onto the right scope chain. */ BytecodeEmitter funbce(/* parent = */ nullptr, &parser, fn->pn_funbox, script, /* lazyScript = */ js::NullPtr(), /* insideEval = */ false, /* evalCaller = */ js::NullPtr(), + /* evalStaticScope = */ js::NullPtr(), fun->environment() && fun->environment()->is<GlobalObject>(), options.lineno); if (!funbce.init()) return false; if (!EmitFunctionScript(cx, &funbce, fn->pn_body)) return false; } else {
--- a/js/src/frontend/BytecodeCompiler.h +++ b/js/src/frontend/BytecodeCompiler.h @@ -12,23 +12,25 @@ class JSLinearString; namespace js { class AutoNameVector; class LazyScript; class LifoAlloc; class ScriptSourceObject; +class StaticEvalObject; struct SourceCompressionTask; namespace frontend { JSScript * CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject scopeChain, HandleScript evalCaller, + Handle<StaticEvalObject *> evalStaticScope, const ReadOnlyCompileOptions &options, SourceBufferHolder &srcBuf, JSString *source_ = nullptr, unsigned staticLevel = 0, SourceCompressionTask *extraSct = nullptr); bool CompileLazyFunction(JSContext *cx, Handle<LazyScript*> lazy, const char16_t *chars, size_t length); /*
--- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -111,26 +111,28 @@ struct LoopStmtInfo : public StmtInfoBCE }; } // anonymous namespace BytecodeEmitter::BytecodeEmitter(BytecodeEmitter *parent, Parser<FullParseHandler> *parser, SharedContext *sc, HandleScript script, Handle<LazyScript *> lazyScript, bool insideEval, HandleScript evalCaller, + Handle<StaticEvalObject *> staticEvalScope, bool hasGlobalScope, uint32_t lineNum, EmitterMode emitterMode) : sc(sc), parent(parent), script(sc->context, script), lazyScript(sc->context, lazyScript), prolog(sc->context, lineNum), main(sc->context, lineNum), current(&main), parser(parser), evalCaller(evalCaller), + evalStaticScope(staticEvalScope), topStmt(nullptr), topScopeStmt(nullptr), staticScope(sc->context), atomIndices(sc->context), firstLine(lineNum), localsToFrameSlots_(sc->context), stackDepth(0), maxStackDepth(0), arrayCompDepth(0), @@ -797,17 +799,20 @@ PushLoopStatement(BytecodeEmitter *bce, static JSObject * EnclosingStaticScope(BytecodeEmitter *bce) { if (bce->staticScope) return bce->staticScope; if (!bce->sc->isFunctionBox()) { MOZ_ASSERT(!bce->parent); - return nullptr; + + // Top-level eval scripts have a placeholder static scope so that + // StaticScopeIter may iterate through evals. + return bce->evalStaticScope; } return bce->sc->asFunctionBox()->function(); } #ifdef DEBUG static bool AllLocalsAliased(StaticBlockObject &obj) @@ -1586,18 +1591,18 @@ TryConvertFreeName(BytecodeEmitter *bce, hops++; if (funbox->function()->isNamedLambda()) hops++; } if (bce->script->directlyInsideEval()) return false; RootedObject outerScope(bce->sc->context, bce->script->enclosingStaticScope()); for (StaticScopeIter<CanGC> ssi(bce->sc->context, outerScope); !ssi.done(); ssi++) { - if (ssi.type() != StaticScopeIter<CanGC>::FUNCTION) { - if (ssi.type() == StaticScopeIter<CanGC>::BLOCK) { + if (ssi.type() != StaticScopeIter<CanGC>::Function) { + if (ssi.type() == StaticScopeIter<CanGC>::Block) { // Use generic ops if a catch block is encountered. return false; } if (ssi.hasDynamicScopeObject()) hops++; continue; } RootedScript script(bce->sc->context, ssi.funScript()); @@ -5334,19 +5339,17 @@ EmitFunc(ExclusiveContext *cx, BytecodeE fun->isInterpreted() && (bce->checkSingletonContext() || (!bce->isInLoop() && bce->isRunOnceLambda())); if (!JSFunction::setTypeForScriptedFunction(cx, fun, singleton)) return false; if (fun->isInterpretedLazy()) { if (!fun->lazyScript()->sourceObject()) { - JSObject *scope = bce->staticScope; - if (!scope && bce->sc->isFunctionBox()) - scope = bce->sc->asFunctionBox()->function(); + JSObject *scope = EnclosingStaticScope(bce); JSObject *source = bce->script->sourceObject(); fun->lazyScript()->setParent(scope, &source->as<ScriptSourceObject>()); } if (bce->emittingRunOnceLambda) fun->lazyScript()->setTreatAsRunOnce(); } else { SharedContext *outersc = bce->sc; @@ -5372,18 +5375,18 @@ EmitFunc(ExclusiveContext *cx, BytecodeE funbox->bufStart, funbox->bufEnd)); if (!script) return false; script->bindings = funbox->bindings; uint32_t lineNum = bce->parser->tokenStream.srcCoords.lineNum(pn->pn_pos.begin); BytecodeEmitter bce2(bce, bce->parser, funbox, script, /* lazyScript = */ js::NullPtr(), - bce->insideEval, bce->evalCaller, bce->hasGlobalScope, lineNum, - bce->emitterMode); + bce->insideEval, bce->evalCaller, bce->evalStaticScope, + bce->hasGlobalScope, lineNum, bce->emitterMode); if (!bce2.init()) return false; /* We measured the max scope depth when we parsed the function. */ if (!EmitFunctionScript(cx, &bce2, pn->pn_body)) return false; if (funbox->usesArguments && funbox->usesApply && funbox->usesThis)
--- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -14,16 +14,19 @@ #include "jscntxt.h" #include "jsopcode.h" #include "jsscript.h" #include "frontend/ParseMaps.h" #include "frontend/SourceNotes.h" namespace js { + +class StaticEvalObject; + namespace frontend { class FullParseHandler; class ObjectBox; class ParseNode; template <typename ParseHandler> class Parser; class SharedContext; class TokenStream; @@ -111,16 +114,18 @@ struct BytecodeEmitter {} }; EmitSection prolog, main, *current; /* the parser */ Parser<FullParseHandler> *const parser; HandleScript evalCaller; /* scripted caller info for eval and dbgapi */ + Handle<StaticEvalObject *> evalStaticScope; + /* compile time scope for eval; does not imply stmt stack */ StmtInfoBCE *topStmt; /* top of statement info stack */ StmtInfoBCE *topScopeStmt; /* top lexical scope statement */ Rooted<NestedScopeObject *> staticScope; /* compile time scope chain */ OwnedAtomIndexMapPtr atomIndices; /* literals indexed for mapping */ unsigned firstLine; /* first line, for JSScript::initFromEmitter */ @@ -192,17 +197,18 @@ struct BytecodeEmitter /* * Note that BytecodeEmitters are magic: they own the arena "top-of-stack" * space above their tempMark points. This means that you cannot alloc from * tempLifoAlloc and save the pointer beyond the next BytecodeEmitter * destruction. */ BytecodeEmitter(BytecodeEmitter *parent, Parser<FullParseHandler> *parser, SharedContext *sc, HandleScript script, Handle<LazyScript *> lazyScript, - bool insideEval, HandleScript evalCaller, bool hasGlobalScope, + bool insideEval, HandleScript evalCaller, + Handle<StaticEvalObject *> evalStaticScope, bool hasGlobalScope, uint32_t lineNum, EmitterMode emitterMode = Normal); bool init(); bool updateLocalsToFrameSlots(); bool isAliasedName(ParseNode *pn); MOZ_ALWAYS_INLINE bool makeAtomIndex(JSAtom *atom, jsatomid *indexp) {
--- a/js/src/jit/BaselineFrame.cpp +++ b/js/src/jit/BaselineFrame.cpp @@ -58,17 +58,17 @@ BaselineFrame::trace(JSTracer *trc, JitF JSScript *script = this->script(); size_t nfixed = script->nfixed(); size_t nlivefixed = script->nbodyfixed(); if (nfixed != nlivefixed) { jsbytecode *pc; frameIterator.baselineScriptAndPc(nullptr, &pc); - NestedScopeObject *staticScope = script->getStaticScope(pc); + NestedScopeObject *staticScope = script->getStaticBlockScope(pc); while (staticScope && !staticScope->is<StaticBlockObject>()) staticScope = staticScope->enclosingNestedScope(); if (staticScope) { StaticBlockObject &blockObj = staticScope->as<StaticBlockObject>(); nlivefixed = blockObj.localOffset() + blockObj.numVariables(); } }
--- a/js/src/jit/CompileInfo.h +++ b/js/src/jit/CompileInfo.h @@ -173,17 +173,17 @@ class CompileInfo // function to ensure that we do not try to embed a nursery pointer in // jit-code. Precisely because it can flow in from anywhere, it's not // guaranteed to be non-lazy. Hence, don't access its script! if (fun_) { fun_ = fun_->nonLazyScript()->functionNonDelazifying(); MOZ_ASSERT(fun_->isTenured()); } - osrStaticScope_ = osrPc ? script->getStaticScope(osrPc) : nullptr; + osrStaticScope_ = osrPc ? script->getStaticBlockScope(osrPc) : nullptr; nimplicit_ = StartArgSlot(script) /* scope chain and argument obj */ + (fun ? 1 : 0); /* this */ nargs_ = fun ? fun->nargs() : 0; nbodyfixed_ = script->nbodyfixed(); nlocals_ = script->nfixed(); fixedLexicalBegin_ = script->fixedLexicalBegin(); nstack_ = script->nslots() - script->nfixed();
--- a/js/src/jit/JitFrames.cpp +++ b/js/src/jit/JitFrames.cpp @@ -581,17 +581,17 @@ HandleExceptionBaseline(JSContext *cx, c HandleClosingGeneratorReturn(cx, frame, pc, *unwoundScopeToPc, rfe, calledDebugEpilogue); return; } JSTryNote *tn = script->trynotes()->vector; JSTryNote *tnEnd = tn + script->trynotes()->length; uint32_t pcOffset = uint32_t(pc - script->main()); - ScopeIter si(frame.baselineFrame(), pc, cx); + ScopeIter si(cx, frame.baselineFrame(), pc); for (; tn != tnEnd; ++tn) { if (pcOffset < tn->start) continue; if (pcOffset >= tn->start + tn->length) continue; // Skip if the try note's stack depth exceeds the frame's stack depth. // See the big comment in TryNoteIter::settle for more info.
--- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -780,18 +780,18 @@ DebugEpilogueOnBaselineReturn(JSContext return true; } bool DebugEpilogue(JSContext *cx, BaselineFrame *frame, jsbytecode *pc, bool ok) { // Unwind scope chain to stack depth 0. - ScopeIter si(frame, pc, cx); - UnwindAllScopes(cx, si); + ScopeIter si(cx, frame, pc); + UnwindAllScopesInFrame(cx, si); jsbytecode *unwindPc = frame->script()->main(); frame->setOverridePc(unwindPc); // If Debugger::onLeaveFrame returns |true| we have to return the frame's // return value. If it returns |false|, the debugger threw an exception. // In both cases we have to pop debug scopes. ok = Debugger::onLeaveFrame(cx, frame, ok);
--- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -3471,16 +3471,35 @@ CreateScopeObjectsForScopeChain(JSContex dynamicEnclosingScope = dynamicWith; } dynamicScopeObj.set(dynamicEnclosingScope); staticScopeObj.set(staticEnclosingScope); return true; } +static bool +IsFunctionCloneable(HandleFunction fun, HandleObject dynamicScope) +{ + if (!fun->isInterpreted()) + return true; + + // If a function was compiled to be lexically nested inside some other + // script, we cannot clone it without breaking the compiler's assumptions. + JSObject *scope = fun->nonLazyScript()->enclosingStaticScope(); + if (scope && (!scope->is<StaticEvalObject>() || + scope->as<StaticEvalObject>().isDirect() || + scope->as<StaticEvalObject>().isStrict())) + { + return false; + } + + return !fun->nonLazyScript()->compileAndGo() || dynamicScope->is<GlobalObject>(); +} + static JSObject * CloneFunctionObject(JSContext *cx, HandleObject funobj, HandleObject dynamicScope) { AssertHeapIsIdle(cx); CHECK_REQUEST(cx); assertSameCompartment(cx, dynamicScope); MOZ_ASSERT(dynamicScope); // Note that funobj can be in a different compartment. @@ -3493,23 +3512,18 @@ CloneFunctionObject(JSContext *cx, Handl } RootedFunction fun(cx, &funobj->as<JSFunction>()); if (fun->isInterpretedLazy()) { AutoCompartment ac(cx, funobj); if (!fun->getOrCreateScript(cx)) return nullptr; } - /* - * If a function was compiled to be lexically nested inside some other - * script, we cannot clone it without breaking the compiler's assumptions. - */ - if (fun->isInterpreted() && (fun->nonLazyScript()->enclosingStaticScope() || - (fun->nonLazyScript()->compileAndGo() && !dynamicScope->is<GlobalObject>()))) - { + + if (!IsFunctionCloneable(fun, dynamicScope)) { JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_CLONE_FUNOBJ_SCOPE); return nullptr; } if (fun->isBoundFunction()) { JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_CLONE_OBJECT); return nullptr; } @@ -4014,17 +4028,18 @@ JS::Compile(JSContext *cx, HandleObject SourceBufferHolder &srcBuf, MutableHandleScript script) { MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment())); AssertHeapIsIdle(cx); CHECK_REQUEST(cx); assertSameCompartment(cx, obj); AutoLastFrameCheck lfc(cx); - script.set(frontend::CompileScript(cx, &cx->tempLifoAlloc(), obj, NullPtr(), options, srcBuf)); + script.set(frontend::CompileScript(cx, &cx->tempLifoAlloc(), obj, NullPtr(), NullPtr(), + options, srcBuf)); return !!script; } bool JS::Compile(JSContext *cx, HandleObject obj, const ReadOnlyCompileOptions &options, const char16_t *chars, size_t length, MutableHandleScript script) { SourceBufferHolder srcBuf(chars, length, SourceBufferHolder::NoOwnership); @@ -4414,17 +4429,17 @@ Evaluate(JSContext *cx, HandleObject obj CHECK_REQUEST(cx); assertSameCompartment(cx, obj); AutoLastFrameCheck lfc(cx); options.setCompileAndGo(obj->is<GlobalObject>()); SourceCompressionTask sct(cx); RootedScript script(cx, frontend::CompileScript(cx, &cx->tempLifoAlloc(), - obj, NullPtr(), options, + obj, NullPtr(), NullPtr(), options, srcBuf, nullptr, 0, &sct)); if (!script) return false; MOZ_ASSERT(script->getVersion() == options.version); bool result = Execute(cx, script, *obj, options.noScriptRval ? nullptr : rval.address());
--- a/js/src/jsfriendapi.cpp +++ b/js/src/jsfriendapi.cpp @@ -392,17 +392,17 @@ js::GetOutermostEnclosingFunctionOfScrip if (iter.done()) return nullptr; if (!iter.isFunctionFrame()) return nullptr; RootedFunction curr(cx, iter.callee(cx)); for (StaticScopeIter<NoGC> i(curr); !i.done(); i++) { - if (i.type() == StaticScopeIter<NoGC>::FUNCTION) + if (i.type() == StaticScopeIter<NoGC>::Function) curr = &i.fun(); } return curr; } JS_FRIEND_API(JSFunction *) js::DefineFunctionWithReserved(JSContext *cx, JSObject *objArg, const char *name, JSNative call, unsigned nargs, unsigned attrs)
--- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -3971,17 +3971,17 @@ js_DumpInterpreterFrame(JSContext *cx, I fputc('\n', stderr); fprintf(stderr, "file %s line %u\n", i.script()->filename(), (unsigned) i.script()->lineno()); if (jsbytecode *pc = i.pc()) { fprintf(stderr, " pc = %p\n", pc); fprintf(stderr, " current op: %s\n", js_CodeName[*pc]); - MaybeDumpObject("staticScope", i.script()->getStaticScope(pc)); + MaybeDumpObject("staticScope", i.script()->getStaticBlockScope(pc)); } MaybeDumpValue("this", i.thisv(cx)); if (!i.isJit()) { fprintf(stderr, " rval: "); dumpValue(i.interpFrame()->returnValue()); fputc('\n', stderr); }
--- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -1687,17 +1687,17 @@ ExpressionDecompiler::getLocal(uint32_t if (local < script->nbodyfixed()) { for (BindingIter bi(script); bi; bi++) { if (bi->kind() != Binding::ARGUMENT && !bi->aliased() && bi.frameIndex() == local) return bi->name(); } MOZ_CRASH("No binding"); } - for (NestedScopeObject *chain = script->getStaticScope(pc); + for (NestedScopeObject *chain = script->getStaticBlockScope(pc); chain; chain = chain->enclosingNestedScope()) { if (!chain->is<StaticBlockObject>()) continue; StaticBlockObject &block = chain->as<StaticBlockObject>(); if (local < block.localOffset()) continue;
--- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -976,20 +976,21 @@ js::XDRScript(XDRState<mode> *xdr, Handl funEnclosingScope = function->nonLazyScript()->enclosingStaticScope(); else { MOZ_ASSERT(function->isAsmJSNative()); JS_ReportError(cx, "AsmJS modules are not yet supported in XDR serialization."); return false; } StaticScopeIter<NoGC> ssi(funEnclosingScope); - if (ssi.done() || ssi.type() == StaticScopeIter<NoGC>::FUNCTION) { + + if (ssi.done() || ssi.type() == StaticScopeIter<NoGC>::Function) { MOZ_ASSERT(ssi.done() == !fun); funEnclosingScopeIndex = UINT32_MAX; - } else if (ssi.type() == StaticScopeIter<NoGC>::BLOCK) { + } else if (ssi.type() == StaticScopeIter<NoGC>::Block) { funEnclosingScopeIndex = FindScopeObjectIndex(script, ssi.block()); MOZ_ASSERT(funEnclosingScopeIndex < i); } else { funEnclosingScopeIndex = FindScopeObjectIndex(script, ssi.staticWith()); MOZ_ASSERT(funEnclosingScopeIndex < i); } } @@ -3012,19 +3013,19 @@ js::CloneScript(JSContext *cx, HandleObj if (innerFun->isInterpretedLazy()) { AutoCompartment ac(cx, innerFun); if (!innerFun->getOrCreateScript(cx)) return nullptr; } RootedObject staticScope(cx, innerFun->nonLazyScript()->enclosingStaticScope()); StaticScopeIter<CanGC> ssi(cx, staticScope); RootedObject enclosingScope(cx); - if (ssi.done() || ssi.type() == StaticScopeIter<CanGC>::FUNCTION) + if (ssi.done() || ssi.type() == StaticScopeIter<CanGC>::Function) enclosingScope = fun; - else if (ssi.type() == StaticScopeIter<CanGC>::BLOCK) + else if (ssi.type() == StaticScopeIter<CanGC>::Block) enclosingScope = objects[FindScopeObjectIndex(src, ssi.block())]; else enclosingScope = objects[FindScopeObjectIndex(src, ssi.staticWith())]; clone = CloneFunctionAndScript(cx, enclosingScope, innerFun); } } else { /* @@ -3162,20 +3163,28 @@ bool js::CloneFunctionScript(JSContext *cx, HandleFunction original, HandleFunction clone, NewObjectKind newKind /* = GenericObject */) { MOZ_ASSERT(clone->isInterpreted()); RootedScript script(cx, clone->nonLazyScript()); MOZ_ASSERT(script); MOZ_ASSERT(script->compartment() == original->compartment()); - MOZ_ASSERT_IF(script->compartment() != cx->compartment(), - !script->enclosingStaticScope()); - + + // The only scripts with enclosing static scopes that may be cloned across + // compartments are non-strict, indirect eval scripts, as their dynamic + // scope chains terminate in the global scope immediately. RootedObject scope(cx, script->enclosingStaticScope()); + if (script->compartment() != cx->compartment() && scope) { + MOZ_ASSERT(!scope->as<StaticEvalObject>().isDirect() && + !scope->as<StaticEvalObject>().isStrict()); + scope = StaticEvalObject::create(cx, NullPtr()); + if (!scope) + return false; + } clone->mutableScript().init(nullptr); JSScript *cscript = CloneScript(cx, scope, clone, script, newKind); if (!cscript) return false; clone->setScript(cscript); @@ -3464,17 +3473,17 @@ LazyScript::markChildren(JSTracer *trc) void LazyScript::finalize(FreeOp *fop) { if (table_) fop->free_(table_); } NestedScopeObject * -JSScript::getStaticScope(jsbytecode *pc) +JSScript::getStaticBlockScope(jsbytecode *pc) { MOZ_ASSERT(containsPC(pc)); if (!hasBlockScopes()) return nullptr; if (pc < main()) return nullptr; @@ -3518,16 +3527,32 @@ JSScript::getStaticScope(jsbytecode *pc) } else { top = mid; } } return blockChain; } +JSObject * +JSScript::innermostStaticScopeInScript(jsbytecode *pc) +{ + if (JSObject *scope = getStaticBlockScope(pc)) + return scope; + return functionNonDelazifying(); +} + +JSObject * +JSScript::innermostStaticScope(jsbytecode *pc) +{ + if (JSObject *scope = innermostStaticScopeInScript(pc)) + return scope; + return enclosingStaticScope(); +} + void JSScript::setArgumentsHasVarBinding() { argsHasVarBinding_ = true; needsArgsAnalysis_ = true; } void @@ -3832,17 +3857,17 @@ LazyScript::hasUncompiledEnclosingScript JSFunction &fun = enclosingScope()->as<JSFunction>(); return fun.isInterpreted() && (!fun.mutableScript() || !fun.nonLazyScript()->code()); } uint32_t LazyScript::staticLevel(JSContext *cx) const { for (StaticScopeIter<NoGC> ssi(enclosingScope()); !ssi.done(); ssi++) { - if (ssi.type() == StaticScopeIter<NoGC>::FUNCTION) + if (ssi.type() == StaticScopeIter<NoGC>::Function) return ssi.funScript()->staticLevel() + 1; } return 1; } void JSScript::updateBaselineOrIonRaw(JSContext *maybecx) {
--- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -1615,17 +1615,25 @@ class JSScript : public js::gc::TenuredC inline js::RegExpObject *getRegExp(jsbytecode *pc); const js::Value &getConst(size_t index) { js::ConstArray *arr = consts(); MOZ_ASSERT(index < arr->length); return arr->vector[index]; } - js::NestedScopeObject *getStaticScope(jsbytecode *pc); + js::NestedScopeObject *getStaticBlockScope(jsbytecode *pc); + + // Returns the innermost static scope at pc if it falls within the extent + // of the script. Returns nullptr otherwise. + JSObject *innermostStaticScopeInScript(jsbytecode *pc); + + // As innermostStaticScopeInScript, but returns the enclosing static scope + // if the innermost static scope falls without the extent of the script. + JSObject *innermostStaticScope(jsbytecode *pc); /* * The isEmpty method tells whether this script has code that computes any * result (not return value, result AKA normal completion value) other than * JSVAL_VOID, or any other effects. */ bool isEmpty() const { if (length() > 3)
--- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -5904,42 +5904,47 @@ DebuggerFrame_setOnPop(JSContext *cx, un * * If |frame| is non-nullptr, evaluate as for a direct eval in that frame; |env| * must be either |frame|'s DebugScopeObject, or some extension of that * environment; either way, |frame|'s scope is where newly declared variables * go. In this case, |frame| must have a computed 'this' value, equal to |thisv|. */ bool js::EvaluateInEnv(JSContext *cx, Handle<Env*> env, HandleValue thisv, AbstractFramePtr frame, - mozilla::Range<const char16_t> chars, const char *filename, unsigned lineno, - MutableHandleValue rval) + jsbytecode *pc, mozilla::Range<const char16_t> chars, const char *filename, + unsigned lineno, MutableHandleValue rval) { assertSameCompartment(cx, env, frame); MOZ_ASSERT_IF(frame, thisv.get() == frame.thisValue()); + MOZ_ASSERT_IF(frame, pc); MOZ_ASSERT(!IsPoisonedPtr(chars.start().get())); /* * NB: This function breaks the assumption that the compiler can see all * calls and properly compute a static level. In practice, any non-zero * static level will suffice. + * + * Pass in NullPtr for evalStaticScope, as ScopeIter should stop at any + * non-ScopeObject boundaries, and we are putting a DebugScopeProxy on the + * scope chain. */ CompileOptions options(cx); options.setCompileAndGo(true) .setForEval(true) .setNoScriptRval(false) .setFileAndLine(filename, lineno) .setCanLazilyParse(false) .setIntroductionType("debugger eval") .maybeMakeStrictMode(frame ? frame.script()->strict() : false); RootedScript callerScript(cx, frame ? frame.script() : nullptr); SourceBufferHolder srcBuf(chars.start().get(), chars.length(), SourceBufferHolder::NoOwnership); RootedScript script(cx, frontend::CompileScript(cx, &cx->tempLifoAlloc(), env, callerScript, - options, srcBuf, - /* source = */ nullptr, + /* evalStaticScope = */ js::NullPtr(), + options, srcBuf, /* source = */ nullptr, /* staticLevel = */ frame ? 1 : 0)); if (!script) return false; script->setActiveEval(); ExecuteType type = !frame ? EXECUTE_DEBUG_GLOBAL : EXECUTE_DEBUG; return ExecuteKernel(cx, script, *env, thisv, type, frame, rval.address()); } @@ -6067,22 +6072,23 @@ DebuggerGenericEval(JSContext *cx, const } } env = nenv; } /* Run the code and produce the completion value. */ RootedValue rval(cx); AbstractFramePtr frame = iter ? iter->abstractFramePtr() : NullFramePtr(); + jsbytecode *pc = iter ? iter->pc() : nullptr; AutoStableStringChars stableChars(cx); if (!stableChars.initTwoByte(cx, flat)) return false; mozilla::Range<const char16_t> chars = stableChars.twoByteRange(); - bool ok = EvaluateInEnv(cx, env, thisv, frame, chars, url ? url : "debugger eval code", + bool ok = EvaluateInEnv(cx, env, thisv, frame, pc, chars, url ? url : "debugger eval code", lineNumber, &rval); return dbg->receiveCompletionValue(ac, ok, rval, vp); } static bool DebuggerFrame_eval(JSContext *cx, unsigned argc, Value *vp) { THIS_FRAME_ITER(cx, argc, vp, "eval", args, thisobj, _, iter);
--- a/js/src/vm/Debugger.h +++ b/js/src/vm/Debugger.h @@ -907,16 +907,16 @@ Debugger::onLogAllocationSite(JSContext GlobalObject::DebuggerVector *dbgs = cx->global()->getDebuggers(); if (!dbgs || dbgs->empty()) return true; return Debugger::slowPathOnLogAllocationSite(cx, frame, when, *dbgs); } extern bool EvaluateInEnv(JSContext *cx, Handle<Env*> env, HandleValue thisv, AbstractFramePtr frame, - mozilla::Range<const char16_t> chars, const char *filename, unsigned lineno, - MutableHandleValue rval); + jsbytecode *pc, mozilla::Range<const char16_t> chars, const char *filename, + unsigned lineno, MutableHandleValue rval); bool ReportObjectRequired(JSContext *cx); } /* namespace js */ #endif /* vm_Debugger_h */
--- a/js/src/vm/HelperThreads.cpp +++ b/js/src/vm/HelperThreads.cpp @@ -1185,17 +1185,17 @@ HelperThread::handleParseWorkload() { AutoUnlockHelperThreadState unlock; PerThreadData::AutoEnterRuntime enter(threadData.ptr(), parseTask->exclusiveContextGlobal->runtimeFromAnyThread()); SourceBufferHolder srcBuf(parseTask->chars, parseTask->length, SourceBufferHolder::NoOwnership); parseTask->script = frontend::CompileScript(parseTask->cx, &parseTask->alloc, - NullPtr(), NullPtr(), + NullPtr(), NullPtr(), NullPtr(), parseTask->options, srcBuf); } // The callback is invoked while we are still off the main thread. parseTask->callback(parseTask, parseTask->callbackData); // FinishOffThreadScript will need to be called on the script to
--- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -881,52 +881,51 @@ js::EnterWithOperation(JSContext *cx, Ab static void PopScope(JSContext *cx, ScopeIter &si) { switch (si.type()) { case ScopeIter::Block: if (cx->compartment()->isDebuggee()) DebugScopes::onPopBlock(cx, si); if (si.staticBlock().needsClone()) - si.frame().popBlock(cx); + si.initialFrame().popBlock(cx); break; case ScopeIter::With: - si.frame().popWith(cx); + si.initialFrame().popWith(cx); break; case ScopeIter::Call: - case ScopeIter::StrictEvalScope: + case ScopeIter::Eval: break; } } // Unwind scope chain and iterator to match the static scope corresponding to // the given bytecode position. void js::UnwindScope(JSContext *cx, ScopeIter &si, jsbytecode *pc) { - if (si.done()) + if (!si.withinInitialFrame()) return; - Rooted<NestedScopeObject *> staticScope(cx, si.frame().script()->getStaticScope(pc)); - - for (; si.staticScope() != staticScope; ++si) + RootedObject staticScope(cx, si.initialFrame().script()->innermostStaticScope(pc)); + for (; si.maybeStaticScope() != staticScope; ++si) PopScope(cx, si); } // Unwind all scopes. This is needed because block scopes may cover the // first bytecode at a script's main(). e.g., // // function f() { { let i = 0; } } // // will have no pc location distinguishing the first block scope from the // outermost function scope. void -js::UnwindAllScopes(JSContext *cx, ScopeIter &si) +js::UnwindAllScopesInFrame(JSContext *cx, ScopeIter &si) { - for (; !si.done(); ++si) + for (; si.withinInitialFrame(); ++si) PopScope(cx, si); } // Compute the pc needed to unwind the scope to the beginning of a try // block. We cannot unwind to *after* the JSOP_TRY, because that might be the // first opcode of an inner scope, with the same problem as above. e.g., // // try { { let x; } } @@ -937,24 +936,24 @@ jsbytecode * js::UnwindScopeToTryPc(JSScript *script, JSTryNote *tn) { return script->main() + tn->start - js_CodeSpec[JSOP_TRY].length; } static void ForcedReturn(JSContext *cx, ScopeIter &si, InterpreterRegs ®s) { - UnwindAllScopes(cx, si); + UnwindAllScopesInFrame(cx, si); regs.setToEndOfScript(); } static void ForcedReturn(JSContext *cx, InterpreterRegs ®s) { - ScopeIter si(regs.fp(), regs.pc, cx); + ScopeIter si(cx, regs.fp(), regs.pc); ForcedReturn(cx, si, regs); } void js::UnwindForUncatchableException(JSContext *cx, const InterpreterRegs ®s) { /* c.f. the regular (catchable) TryNoteIter loop in HandleError. */ for (TryNoteIter tni(cx, regs); !tni.done(); ++tni) { @@ -1033,17 +1032,17 @@ enum HandleErrorContinuation FinallyContinuation }; static HandleErrorContinuation HandleError(JSContext *cx, InterpreterRegs ®s) { MOZ_ASSERT(regs.fp()->script()->containsPC(regs.pc)); - ScopeIter si(regs.fp(), regs.pc, cx); + ScopeIter si(cx, regs.fp(), regs.pc); bool ok = false; again: if (cx->isExceptionPending()) { /* Call debugger throw hooks. */ RootedValue exception(cx); if (!cx->getPendingException(&exception)) goto again; @@ -3352,31 +3351,31 @@ CASE(JSOP_PUSHBLOCKSCOPE) } END_CASE(JSOP_PUSHBLOCKSCOPE) CASE(JSOP_POPBLOCKSCOPE) { #ifdef DEBUG // Pop block from scope chain. MOZ_ASSERT(*(REGS.pc - JSOP_DEBUGLEAVEBLOCK_LENGTH) == JSOP_DEBUGLEAVEBLOCK); - NestedScopeObject *scope = script->getStaticScope(REGS.pc - JSOP_DEBUGLEAVEBLOCK_LENGTH); + NestedScopeObject *scope = script->getStaticBlockScope(REGS.pc - JSOP_DEBUGLEAVEBLOCK_LENGTH); MOZ_ASSERT(scope && scope->is<StaticBlockObject>()); StaticBlockObject &blockObj = scope->as<StaticBlockObject>(); MOZ_ASSERT(blockObj.needsClone()); #endif // Pop block from scope chain. REGS.fp()->popBlock(cx); } END_CASE(JSOP_POPBLOCKSCOPE) CASE(JSOP_DEBUGLEAVEBLOCK) { - MOZ_ASSERT(script->getStaticScope(REGS.pc)); - MOZ_ASSERT(script->getStaticScope(REGS.pc)->is<StaticBlockObject>()); + MOZ_ASSERT(script->getStaticBlockScope(REGS.pc)); + MOZ_ASSERT(script->getStaticBlockScope(REGS.pc)->is<StaticBlockObject>()); // FIXME: This opcode should not be necessary. The debugger shouldn't need // help from bytecode to do its job. See bug 927782. if (MOZ_UNLIKELY(cx->compartment()->isDebuggee())) DebugScopes::onPopBlock(cx, REGS.fp(), REGS.pc); } END_CASE(JSOP_DEBUGLEAVEBLOCK) @@ -4092,17 +4091,17 @@ js::ReportUninitializedLexical(JSContext name = bi->name(); break; } } // Failing that, it must be a block-local let. if (!name) { // Skip to the right scope. - Rooted<NestedScopeObject *> scope(cx, script->getStaticScope(pc)); + Rooted<NestedScopeObject *> scope(cx, script->getStaticBlockScope(pc)); MOZ_ASSERT(scope && scope->is<StaticBlockObject>()); Rooted<StaticBlockObject *> block(cx, &scope->as<StaticBlockObject>()); while (slot < block->localOffset()) block = &block->enclosingNestedScope()->as<StaticBlockObject>(); // Translate the frame slot to the block slot, then find the name // of the slot. uint32_t blockSlot = block->localIndexToSlot(slot);
--- a/js/src/vm/Interpreter.h +++ b/js/src/vm/Interpreter.h @@ -241,17 +241,17 @@ HasInstance(JSContext *cx, HandleObject // Unwind scope chain and iterator to match the static scope corresponding to // the given bytecode position. extern void UnwindScope(JSContext *cx, ScopeIter &si, jsbytecode *pc); // Unwind all scopes. extern void -UnwindAllScopes(JSContext *cx, ScopeIter &si); +UnwindAllScopesInFrame(JSContext *cx, ScopeIter &si); // Compute the pc needed to unwind the scope to the beginning of the block // pointed to by the try note. extern jsbytecode * UnwindScopeToTryPc(JSScript *script, JSTryNote *tn); /* * Unwind for an uncatchable exception. This means not running finalizers, etc;
--- a/js/src/vm/ScopeObject-inl.h +++ b/js/src/vm/ScopeObject-inl.h @@ -81,79 +81,95 @@ StaticScopeIter<allowGC>::operator++(int if (obj->template is<NestedScopeObject>()) { obj = obj->template as<NestedScopeObject>().enclosingScopeForStaticScopeIter(); } else if (onNamedLambda || !obj->template as<JSFunction>().isNamedLambda()) { onNamedLambda = false; obj = obj->template as<JSFunction>().nonLazyScript()->enclosingStaticScope(); } else { onNamedLambda = true; } - MOZ_ASSERT_IF(obj, obj->template is<NestedScopeObject>() || obj->template is<JSFunction>()); + MOZ_ASSERT_IF(obj, obj->template is<NestedScopeObject>() || + obj->template is<StaticEvalObject>() || + obj->template is<JSFunction>()); MOZ_ASSERT_IF(onNamedLambda, obj->template is<JSFunction>()); } template <AllowGC allowGC> inline bool StaticScopeIter<allowGC>::hasDynamicScopeObject() const { return obj->template is<StaticBlockObject>() ? obj->template as<StaticBlockObject>().needsClone() - : (obj->template is<StaticWithObject>() || - obj->template as<JSFunction>().isHeavyweight()); + : (obj->template is<StaticEvalObject>() + ? obj->template as<StaticEvalObject>().isStrict() + : (obj->template is<StaticWithObject>() || + obj->template as<JSFunction>().isHeavyweight())); } template <AllowGC allowGC> inline Shape * StaticScopeIter<allowGC>::scopeShape() const { MOZ_ASSERT(hasDynamicScopeObject()); - MOZ_ASSERT(type() != NAMED_LAMBDA); - if (type() == BLOCK) + MOZ_ASSERT(type() != NamedLambda && type() != Eval); + if (type() == Block) return block().lastProperty(); return funScript()->callObjShape(); } template <AllowGC allowGC> inline typename StaticScopeIter<allowGC>::Type StaticScopeIter<allowGC>::type() const { if (onNamedLambda) - return NAMED_LAMBDA; + return NamedLambda; return obj->template is<StaticBlockObject>() - ? BLOCK - : (obj->template is<StaticWithObject>() ? WITH : FUNCTION); + ? Block + : (obj->template is<StaticWithObject>() + ? With + : (obj->template is<StaticEvalObject>() + ? Eval + : Function)); } template <AllowGC allowGC> inline StaticBlockObject & StaticScopeIter<allowGC>::block() const { - MOZ_ASSERT(type() == BLOCK); + MOZ_ASSERT(type() == Block); return obj->template as<StaticBlockObject>(); } template <AllowGC allowGC> inline StaticWithObject & StaticScopeIter<allowGC>::staticWith() const { - MOZ_ASSERT(type() == WITH); + MOZ_ASSERT(type() == With); return obj->template as<StaticWithObject>(); } template <AllowGC allowGC> +inline StaticEvalObject & +StaticScopeIter<allowGC>::eval() const +{ + MOZ_ASSERT(type() == Eval); + return obj->template as<StaticEvalObject>(); +} + +template <AllowGC allowGC> inline JSScript * StaticScopeIter<allowGC>::funScript() const { - MOZ_ASSERT(type() == FUNCTION); + MOZ_ASSERT(type() == Function); return obj->template as<JSFunction>().nonLazyScript(); } template <AllowGC allowGC> inline JSFunction & StaticScopeIter<allowGC>::fun() const { - MOZ_ASSERT(type() == FUNCTION); + MOZ_ASSERT(type() == Function); return obj->template as<JSFunction>(); } } /* namespace js */ #endif /* vm_ScopeObject_inl_h */
--- a/js/src/vm/ScopeObject.cpp +++ b/js/src/vm/ScopeObject.cpp @@ -29,32 +29,21 @@ using namespace js::types; using mozilla::PodZero; typedef Rooted<ArgumentsObject *> RootedArgumentsObject; typedef MutableHandle<ArgumentsObject *> MutableHandleArgumentsObject; /*****************************************************************************/ -static JSObject * -InnermostStaticScope(JSScript *script, jsbytecode *pc) -{ - MOZ_ASSERT(script->containsPC(pc)); - MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_SCOPECOORD); - - NestedScopeObject *scope = script->getStaticScope(pc); - if (scope) - return scope; - return script->functionNonDelazifying(); -} - Shape * js::ScopeCoordinateToStaticScopeShape(JSScript *script, jsbytecode *pc) { - StaticScopeIter<NoGC> ssi(InnermostStaticScope(script, pc)); + MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_SCOPECOORD); + StaticScopeIter<NoGC> ssi(script->innermostStaticScopeInScript(pc)); uint32_t hops = ScopeCoordinate(pc).hops(); while (true) { MOZ_ASSERT(!ssi.done()); if (ssi.hasDynamicScopeObject()) { if (!hops) break; hops--; } @@ -108,27 +97,28 @@ js::ScopeCoordinateName(ScopeCoordinateN if (!JSID_IS_ATOM(id)) return script->runtimeFromAnyThread()->commonNames->empty; return JSID_TO_ATOM(id)->asPropertyName(); } JSScript * js::ScopeCoordinateFunctionScript(JSScript *script, jsbytecode *pc) { - StaticScopeIter<NoGC> ssi(InnermostStaticScope(script, pc)); + MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_SCOPECOORD); + StaticScopeIter<NoGC> ssi(script->innermostStaticScopeInScript(pc)); uint32_t hops = ScopeCoordinate(pc).hops(); while (true) { if (ssi.hasDynamicScopeObject()) { if (!hops) break; hops--; } ssi++; } - if (ssi.type() != StaticScopeIter<NoGC>::FUNCTION) + if (ssi.type() != StaticScopeIter<NoGC>::Function) return nullptr; return ssi.funScript(); } /*****************************************************************************/ void ScopeObject::setEnclosingScope(HandleObject obj) @@ -616,16 +606,45 @@ const Class DynamicWithObject::class_ = with_DeleteGeneric, nullptr, nullptr, /* watch/unwatch */ nullptr, /* getElements */ nullptr, /* enumerate (native enumeration of target doesn't work) */ with_ThisObject, } }; +/* static */ StaticEvalObject * +StaticEvalObject::create(JSContext *cx, HandleObject enclosing) +{ + RootedTypeObject type(cx, cx->getNewType(&class_, TaggedProto(nullptr))); + if (!type) + return nullptr; + + RootedShape shape(cx, EmptyShape::getInitialShape(cx, &class_, TaggedProto(nullptr), + cx->global(), nullptr, FINALIZE_KIND, + BaseShape::DELEGATE)); + if (!shape) + return nullptr; + + RootedNativeObject obj(cx, MaybeNativeObject(JSObject::create(cx, FINALIZE_KIND, + gc::TenuredHeap, shape, type))); + if (!obj) + return nullptr; + + obj->as<StaticEvalObject>().initEnclosingNestedScope(enclosing); + obj->setFixedSlot(STRICT_SLOT, BooleanValue(false)); + return &obj->as<StaticEvalObject>(); +} + +const Class StaticEvalObject::class_ = { + "StaticEval", + JSCLASS_HAS_RESERVED_SLOTS(StaticEvalObject::RESERVED_SLOTS) | + JSCLASS_IS_ANONYMOUS +}; + /*****************************************************************************/ ClonedBlockObject * ClonedBlockObject::create(JSContext *cx, Handle<StaticBlockObject *> block, AbstractFramePtr frame) { assertSameCompartment(cx, frame); MOZ_ASSERT(block->getClass() == &BlockObject::class_); @@ -1059,224 +1078,191 @@ const Class UninitializedLexicalObject:: // Any name atom for a function which will be added as a DeclEnv object to the // scope chain above call objects for fun. static inline JSAtom * CallObjectLambdaName(JSFunction &fun) { return fun.isNamedLambda() ? fun.atom() : nullptr; } -ScopeIter::ScopeIter(const ScopeIter &si, JSContext *cx +ScopeIter::ScopeIter(JSContext *cx, const ScopeIter &si MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) - : cx(cx), - frame_(si.frame_), - cur_(cx, si.cur_), - staticScope_(cx, si.staticScope_), - type_(si.type_), - hasScopeObject_(si.hasScopeObject_) + : ssi_(cx, si.ssi_), + scope_(cx, si.scope_), + frame_(si.frame_) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; } -ScopeIter::ScopeIter(JSObject &enclosingScope, JSContext *cx +ScopeIter::ScopeIter(JSContext *cx, JSObject *scope, JSObject *staticScope MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) - : cx(cx), - frame_(NullFramePtr()), - cur_(cx, &enclosingScope), - staticScope_(cx, nullptr), - type_(Type(-1)) + : ssi_(cx, staticScope), + scope_(cx, scope), + frame_(NullFramePtr()) { + settle(); MOZ_GUARD_OBJECT_NOTIFIER_INIT; } -ScopeIter::ScopeIter(AbstractFramePtr frame, jsbytecode *pc, JSContext *cx +ScopeIter::ScopeIter(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) - : cx(cx), - frame_(frame), - cur_(cx, frame.scopeChain()), - staticScope_(cx, frame.script()->getStaticScope(pc)) + : ssi_(cx, frame.script()->innermostStaticScope(pc)), + scope_(cx, frame.scopeChain()), + frame_(frame) { assertSameCompartment(cx, frame); settle(); MOZ_GUARD_OBJECT_NOTIFIER_INIT; } -ScopeIter::ScopeIter(const ScopeIterVal &val, JSContext *cx - MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) - : cx(cx), - frame_(val.frame_), - cur_(cx, val.cur_), - staticScope_(cx, val.staticScope_), - type_(val.type_), - hasScopeObject_(val.hasScopeObject_) +void +ScopeIter::settle() { - assertSameCompartment(cx, val.frame_); - MOZ_GUARD_OBJECT_NOTIFIER_INIT; + // For named lambdas, DeclEnvObject scopes are always attached to their + // CallObjects. Skip it here, as they are special cased in users of + // ScopeIter. + if (!ssi_.done() && ssi_.type() == StaticScopeIter<CanGC>::NamedLambda) + ssi_++; + + // Check for trying to iterate a heavyweight function frame before + // the prologue has created the CallObject, in which case we have to skip. + if (frame_ && frame_.isNonEvalFunctionFrame() && + frame_.fun()->isHeavyweight() && !frame_.hasCallObj()) + { + MOZ_ASSERT(ssi_.type() == StaticScopeIter<CanGC>::Function); + ssi_++; + } + + // Check if we have left the extent of the initial frame. This check must + // come between the named lambda check above and the direct eval check + // below. We must check the static scope after skipping the named lambda, + // as an SSI settled on a named lambda scope has no static scope. We must + // check the static scope before skipping the direct eval, as by then we + // would have already left the frame. + if (frame_ && (ssi_.done() || maybeStaticScope() == frame_.script()->enclosingStaticScope())) + frame_ = NullFramePtr(); + +#ifdef DEBUG + if (!ssi_.done() && hasScopeObject()) { + switch (ssi_.type()) { + case StaticScopeIter<CanGC>::Function: + MOZ_ASSERT(scope_->as<CallObject>().callee().nonLazyScript() == ssi_.funScript()); + break; + case StaticScopeIter<CanGC>::Block: + MOZ_ASSERT(scope_->as<ClonedBlockObject>().staticBlock() == staticBlock()); + break; + case StaticScopeIter<CanGC>::With: + MOZ_ASSERT(scope_->as<DynamicWithObject>().staticScope() == &staticWith()); + break; + case StaticScopeIter<CanGC>::Eval: + MOZ_ASSERT(scope_->as<CallObject>().isForEval()); + break; + case StaticScopeIter<CanGC>::NamedLambda: + MOZ_CRASH("named lambda static scopes should have been skipped"); + } + } +#endif +} + +ScopeIter & +ScopeIter::operator++() +{ + if (hasScopeObject()) { + scope_ = &scope_->as<ScopeObject>().enclosingScope(); + if (scope_->is<DeclEnvObject>()) + scope_ = &scope_->as<DeclEnvObject>().enclosingScope(); + } + + ssi_++; + settle(); + + return *this; +} + +ScopeIter::Type +ScopeIter::type() const +{ + MOZ_ASSERT(!done()); + + switch (ssi_.type()) { + case StaticScopeIter<CanGC>::Function: + return Call; + case StaticScopeIter<CanGC>::Block: + return Block; + case StaticScopeIter<CanGC>::With: + return With; + case StaticScopeIter<CanGC>::Eval: + return Eval; + case StaticScopeIter<CanGC>::NamedLambda: + MOZ_CRASH("named lambda static scopes should have been skipped"); + default: + MOZ_CRASH("bad SSI type"); + } } ScopeObject & ScopeIter::scope() const { MOZ_ASSERT(hasScopeObject()); - return cur_->as<ScopeObject>(); + return scope_->as<ScopeObject>(); } -ScopeIter & -ScopeIter::operator++() -{ - MOZ_ASSERT(!done()); - switch (type_) { - case Call: - if (hasScopeObject_) { - cur_ = &cur_->as<CallObject>().enclosingScope(); - if (CallObjectLambdaName(*frame_.fun())) - cur_ = &cur_->as<DeclEnvObject>().enclosingScope(); - } - frame_ = NullFramePtr(); - break; - case Block: - MOZ_ASSERT(staticScope_ && staticScope_->is<StaticBlockObject>()); - staticScope_ = staticScope_->as<StaticBlockObject>().enclosingNestedScope(); - if (hasScopeObject_) - cur_ = &cur_->as<ClonedBlockObject>().enclosingScope(); - settle(); - break; - case With: - MOZ_ASSERT(staticScope_ && staticScope_->is<StaticWithObject>()); - MOZ_ASSERT(hasScopeObject_); - staticScope_ = staticScope_->as<StaticWithObject>().enclosingNestedScope(); - cur_ = &cur_->as<DynamicWithObject>().enclosingScope(); - settle(); - break; - case StrictEvalScope: - if (hasScopeObject_) - cur_ = &cur_->as<CallObject>().enclosingScope(); - frame_ = NullFramePtr(); - break; - } - return *this; -} - -void -ScopeIter::settle() +JSObject * +ScopeIter::maybeStaticScope() const { - /* - * Given an iterator state (cur_, staticScope_), figure out which (potentially - * optimized) scope the iterator should report. Thus, the result is a pair - * (type_, hasScopeObject_) where hasScopeObject_ indicates whether the - * scope object has been optimized away and does not exist on the scope - * chain. Beware: while ScopeIter iterates over the scopes of a single - * frame, the scope chain (pointed to by cur_) continues into the scopes of - * enclosing frames. Thus, it is important not to look at cur_ until it is - * certain that cur_ points to a scope object in the current frame. In - * particular, there are three tricky corner cases: - * - non-heavyweight functions; - * - non-strict direct eval. - * - heavyweight functions observed before the prologue has finished; - * In all cases, cur_ can already be pointing into an enclosing frame's - * scope chain. Furthermore, in the first two cases: even if cur_ points - * into an enclosing frame's scope chain, the current frame may still have - * uncloned blocks. In the last case, since we haven't entered the - * function, we simply return a ScopeIter where done() == true. - * - * Note: DebugScopeObject falls nicely into this plan: since they are only - * ever introduced as the *enclosing* scope of a frame, they should never - * show up in scope iteration and fall into the final non-scope case. - */ - if (frame_.isNonEvalFunctionFrame() && !frame_.fun()->isHeavyweight()) { - if (staticScope_) { - // If staticScope_ were a StaticWithObject, the function would be - // heavyweight. - MOZ_ASSERT(staticScope_->is<StaticBlockObject>()); - type_ = Block; - hasScopeObject_ = staticScope_->as<StaticBlockObject>().needsClone(); - } else { - type_ = Call; - hasScopeObject_ = false; - } - } else if (frame_.isNonStrictDirectEvalFrame() && cur_ == frame_.evalPrevScopeChain(cx)) { - if (staticScope_) { - MOZ_ASSERT(staticScope_->is<StaticBlockObject>()); - MOZ_ASSERT(!staticScope_->as<StaticBlockObject>().needsClone()); - type_ = Block; - hasScopeObject_ = false; - } else { - frame_ = NullFramePtr(); - } - } else if (frame_.isNonEvalFunctionFrame() && !frame_.hasCallObj()) { - MOZ_ASSERT(cur_ == frame_.fun()->environment()); - frame_ = NullFramePtr(); - } else if (frame_.isStrictEvalFrame() && !frame_.hasCallObj()) { - MOZ_ASSERT(cur_ == frame_.evalPrevScopeChain(cx)); - frame_ = NullFramePtr(); - } else if (staticScope_) { - if (staticScope_->is<StaticWithObject>()) { - MOZ_ASSERT(cur_); - MOZ_ASSERT(cur_->as<DynamicWithObject>().staticScope() == staticScope_); - type_ = With; - hasScopeObject_ = true; - } else { - type_ = Block; - hasScopeObject_ = staticScope_->as<StaticBlockObject>().needsClone(); - MOZ_ASSERT_IF(hasScopeObject_, - cur_->as<ClonedBlockObject>().staticBlock() == *staticScope_); - } - } else if (cur_->is<CallObject>()) { - CallObject &callobj = cur_->as<CallObject>(); - type_ = callobj.isForEval() ? StrictEvalScope : Call; - hasScopeObject_ = true; - MOZ_ASSERT_IF(type_ == Call, callobj.callee().nonLazyScript() == frame_.script()); - } else { - MOZ_ASSERT(!cur_->is<ScopeObject>() || - (cur_->is<DynamicWithObject>() && - !cur_->as<DynamicWithObject>().isSyntactic())); - MOZ_ASSERT(frame_.isGlobalFrame() || frame_.isDebuggerEvalFrame()); - frame_ = NullFramePtr(); + if (ssi_.done()) + return nullptr; + + switch (ssi_.type()) { + case StaticScopeIter<CanGC>::Function: + return &fun(); + case StaticScopeIter<CanGC>::Block: + return &staticBlock(); + case StaticScopeIter<CanGC>::With: + return &staticWith(); + case StaticScopeIter<CanGC>::Eval: + return &staticEval(); + case StaticScopeIter<CanGC>::NamedLambda: + MOZ_CRASH("named lambda static scopes should have been skipped"); + default: + MOZ_CRASH("bad SSI type"); } } /* static */ HashNumber -ScopeIterKey::hash(ScopeIterKey si) +MissingScopeKey::hash(MissingScopeKey sk) { - /* hasScopeObject_ is determined by the other fields. */ - return size_t(si.frame_.raw()) ^ size_t(si.cur_) ^ size_t(si.staticScope_) ^ si.type_; + return size_t(sk.frame_.raw()) ^ size_t(sk.staticScope_); } /* static */ bool -ScopeIterKey::match(ScopeIterKey si1, ScopeIterKey si2) +MissingScopeKey::match(MissingScopeKey sk1, MissingScopeKey sk2) { - /* hasScopeObject_ is determined by the other fields. */ - return si1.frame_ == si2.frame_ && - (!si1.frame_ || - (si1.cur_ == si2.cur_ && - si1.staticScope_ == si2.staticScope_ && - si1.type_ == si2.type_)); + return sk1.frame_ == sk2.frame_ && sk1.staticScope_ == sk2.staticScope_; } void -ScopeIterVal::sweep() +LiveScopeVal::sweep() { - /* We need to update possibly moved pointers on sweep. */ - MOZ_ALWAYS_FALSE(IsObjectAboutToBeFinalizedFromAnyThread(cur_.unsafeGet())); if (staticScope_) MOZ_ALWAYS_FALSE(IsObjectAboutToBeFinalizedFromAnyThread(staticScope_.unsafeGet())); } // Live ScopeIter values may be added to DebugScopes::liveScopes, as // ScopeIterVal instances. They need to have write barriers when they are added // to the hash table, but no barriers when rehashing inside GC. It's a nasty // hack, but the important thing is that ScopeIterKey and ScopeIterVal need to // alias each other. -void ScopeIterVal::staticAsserts() { - static_assert(sizeof(ScopeIterVal) == sizeof(ScopeIterKey), - "ScopeIterVal must be same size of ScopeIterKey"); - static_assert(offsetof(ScopeIterVal, cur_) == offsetof(ScopeIterKey, cur_), - "ScopeIterVal.cur_ must alias ScopeIterKey.cur_"); - static_assert(offsetof(ScopeIterVal, staticScope_) == offsetof(ScopeIterKey, staticScope_), - "ScopeIterVal.staticScope_ must alias ScopeIterKey.staticScope_"); +void +LiveScopeVal::staticAsserts() +{ + static_assert(sizeof(LiveScopeVal) == sizeof(MissingScopeKey), + "LiveScopeVal must be same size of MissingScopeKey"); + static_assert(offsetof(LiveScopeVal, staticScope_) == offsetof(MissingScopeKey, staticScope_), + "LiveScopeVal.staticScope_ must alias MissingScopeKey.staticScope_"); } /*****************************************************************************/ namespace { /* * DebugScopeProxy is the handler for DebugScopeObject proxy objects. Having a @@ -1331,17 +1317,17 @@ class DebugScopeProxy : public BaseProxy * - ACCESS_LOST if the value has been lost to the debugger */ bool handleUnaliasedAccess(JSContext *cx, Handle<DebugScopeObject*> debugScope, Handle<ScopeObject*> scope, jsid id, Action action, MutableHandleValue vp, AccessResult *accessResult) const { MOZ_ASSERT(&debugScope->scope() == scope); *accessResult = ACCESS_GENERIC; - ScopeIterVal *maybeLiveScope = DebugScopes::hasLiveScope(*scope); + LiveScopeVal *maybeLiveScope = DebugScopes::hasLiveScope(*scope); /* Handle unaliased formals, vars, lets, and consts at function scope. */ if (scope->is<CallObject>() && !scope->as<CallObject>().isForEval()) { CallObject &callobj = scope->as<CallObject>(); RootedScript script(cx, callobj.callee().nonLazyScript()); if (!script->ensureHasTypes(cx) || !script->ensureHasAnalyzedArgsUsage(cx)) return false; @@ -1510,17 +1496,17 @@ class DebugScopeProxy : public BaseProxy * Create a missing arguments object. If the function returns true but * argsObj is null, it means the scope is dead. */ static bool createMissingArguments(JSContext *cx, ScopeObject &scope, MutableHandleArgumentsObject argsObj) { argsObj.set(nullptr); - ScopeIterVal *maybeScope = DebugScopes::hasLiveScope(scope); + LiveScopeVal *maybeScope = DebugScopes::hasLiveScope(scope); if (!maybeScope) return true; argsObj.set(ArgumentsObject::createUnexpected(cx, maybeScope->frame())); return !!argsObj; } public: @@ -1890,50 +1876,23 @@ js_IsDebugScopeSlow(ProxyObject *proxy) /* static */ MOZ_ALWAYS_INLINE void DebugScopes::proxiedScopesPostWriteBarrier(JSRuntime *rt, ObjectWeakMap *map, const PreBarrieredObject &key) { if (key && IsInsideNursery(key)) rt->gc.storeBuffer.putGeneric(UnbarrieredRef(map, key.get())); } -class DebugScopes::MissingScopesRef : public gc::BufferableRef -{ - MissingScopeMap *map; - ScopeIterKey key; - - public: - MissingScopesRef(MissingScopeMap *m, const ScopeIterKey &k) : map(m), key(k) {} - - void mark(JSTracer *trc) { - ScopeIterKey prior = key; - MissingScopeMap::Ptr p = map->lookup(key); - if (!p) - return; - trc->setTracingLocation(&const_cast<ScopeIterKey &>(p->key()).enclosingScope()); - Mark(trc, &key.enclosingScope(), "MissingScopesRef"); - map->rekeyIfMoved(prior, key); - } -}; - -/* static */ MOZ_ALWAYS_INLINE void -DebugScopes::missingScopesPostWriteBarrier(JSRuntime *rt, MissingScopeMap *map, - const ScopeIterKey &key) -{ - if (key.enclosingScope() && IsInsideNursery(key.enclosingScope())) - rt->gc.storeBuffer.putGeneric(MissingScopesRef(map, key)); -} - /* static */ MOZ_ALWAYS_INLINE void DebugScopes::liveScopesPostWriteBarrier(JSRuntime *rt, LiveScopeMap *map, ScopeObject *key) { // As above. Otherwise, barriers could fire during GC when moving the // value. typedef HashMap<ScopeObject *, - ScopeIterKey, + MissingScopeKey, DefaultHasher<ScopeObject *>, RuntimeAllocPolicy> UnbarrieredLiveScopeMap; typedef gc::HashKeyRef<UnbarrieredLiveScopeMap, ScopeObject *> Ref; if (key && IsInsideNursery(key)) rt->gc.storeBuffer.putGeneric(Ref(reinterpret_cast<UnbarrieredLiveScopeMap *>(map), key)); } DebugScopes::DebugScopes(JSContext *cx) @@ -1991,28 +1950,21 @@ DebugScopes::sweep(JSRuntime *rt) * liveness; we should assume that anything could be marked. * * Thus, we must explicitly remove the entries from both liveScopes * and missingScopes here. */ liveScopes.remove(&(*debugScope)->scope()); e.removeFront(); } else { - ScopeIterKey key = e.front().key(); - bool needsUpdate = false; - if (IsForwarded(key.cur())) { - key.updateCur(&gc::Forwarded(key.cur())->as<NativeObject>()); - needsUpdate = true; + MissingScopeKey key = e.front().key(); + if (IsForwarded(key.staticScope())) { + key.updateStaticScope(Forwarded(key.staticScope())); + e.rekeyFront(key); } - if (key.staticScope() && IsForwarded(key.staticScope())) { - key.updateStaticScope(Forwarded(key.staticScope())); - needsUpdate = true; - } - if (needsUpdate) - e.rekeyFront(key); } } for (LiveScopeMap::Enum e(liveScopes); !e.empty(); e.popFront()) { ScopeObject *scope = e.front().key(); e.front().value().sweep(); @@ -2036,23 +1988,21 @@ DebugScopes::checkHashTablesAfterMovingG * postbarriers have worked and that no hashtable keys (or values) are left * pointing into the nursery. */ for (ObjectWeakMap::Range r = proxiedScopes.all(); !r.empty(); r.popFront()) { CheckGCThingAfterMovingGC(r.front().key().get()); CheckGCThingAfterMovingGC(r.front().value().get()); } for (MissingScopeMap::Range r = missingScopes.all(); !r.empty(); r.popFront()) { - CheckGCThingAfterMovingGC(r.front().key().cur()); CheckGCThingAfterMovingGC(r.front().key().staticScope()); CheckGCThingAfterMovingGC(r.front().value().get()); } for (LiveScopeMap::Range r = liveScopes.all(); !r.empty(); r.popFront()) { CheckGCThingAfterMovingGC(r.front().key()); - CheckGCThingAfterMovingGC(r.front().value().cur_.get()); CheckGCThingAfterMovingGC(r.front().value().staticScope_.get()); } } #endif /* * Unfortunately, GetDebugScopeForFrame needs to work even outside debug mode * (in particular, JS_GetFrameScopeChain does not require debug mode). Since @@ -2126,46 +2076,47 @@ DebugScopeObject * DebugScopes::hasDebugScope(JSContext *cx, const ScopeIter &si) { MOZ_ASSERT(!si.hasScopeObject()); DebugScopes *scopes = cx->compartment()->debugScopes; if (!scopes) return nullptr; - if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(ScopeIterKey(si))) { + if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(MissingScopeKey(si))) { MOZ_ASSERT(CanUseDebugScopeMaps(cx)); return p->value(); } return nullptr; } bool DebugScopes::addDebugScope(JSContext *cx, const ScopeIter &si, DebugScopeObject &debugScope) { MOZ_ASSERT(!si.hasScopeObject()); MOZ_ASSERT(cx->compartment() == debugScope.compartment()); - MOZ_ASSERT_IF(si.frame().isFunctionFrame(), !si.frame().callee()->isGenerator()); + MOZ_ASSERT_IF(si.withinInitialFrame() && si.initialFrame().isFunctionFrame(), + !si.initialFrame().callee()->isGenerator()); if (!CanUseDebugScopeMaps(cx)) return true; DebugScopes *scopes = ensureCompartmentData(cx); if (!scopes) return false; - MOZ_ASSERT(!scopes->missingScopes.has(ScopeIterKey(si))); - if (!scopes->missingScopes.put(ScopeIterKey(si), ReadBarriered<DebugScopeObject*>(&debugScope))) { + MissingScopeKey key(si); + MOZ_ASSERT(!scopes->missingScopes.has(key)); + if (!scopes->missingScopes.put(key, ReadBarriered<DebugScopeObject*>(&debugScope))) { js_ReportOutOfMemory(cx); return false; } - missingScopesPostWriteBarrier(cx->runtime(), &scopes->missingScopes, ScopeIterKey(si)); MOZ_ASSERT(!scopes->liveScopes.has(&debugScope.scope())); - if (!scopes->liveScopes.put(&debugScope.scope(), ScopeIterVal(si))) { + if (!scopes->liveScopes.put(&debugScope.scope(), LiveScopeVal(si))) { js_ReportOutOfMemory(cx); return false; } liveScopesPostWriteBarrier(cx->runtime(), &scopes->liveScopes, &debugScope.scope()); return true; } @@ -2191,18 +2142,18 @@ DebugScopes::onPopCall(AbstractFramePtr if (frame.fun()->isGenerator()) return; CallObject &callobj = frame.scopeChain()->as<CallObject>(); scopes->liveScopes.remove(&callobj); if (ObjectWeakMap::Ptr p = scopes->proxiedScopes.lookup(&callobj)) debugScope = &p->value()->as<DebugScopeObject>(); } else { - ScopeIter si(frame, frame.script()->main(), cx); - if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(ScopeIterKey(si))) { + ScopeIter si(cx, frame, frame.script()->main()); + if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(MissingScopeKey(si))) { debugScope = p->value(); scopes->liveScopes.remove(&debugScope->scope().as<CallObject>()); scopes->missingScopes.remove(p); } } /* * When the JS stack frame is popped, the values of unaliased variables @@ -2254,37 +2205,38 @@ void DebugScopes::onPopBlock(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc) { assertSameCompartment(cx, frame); DebugScopes *scopes = cx->compartment()->debugScopes; if (!scopes) return; - ScopeIter si(frame, pc, cx); + ScopeIter si(cx, frame, pc); onPopBlock(cx, si); } void DebugScopes::onPopBlock(JSContext *cx, const ScopeIter &si) { DebugScopes *scopes = cx->compartment()->debugScopes; if (!scopes) return; + MOZ_ASSERT(si.withinInitialFrame()); MOZ_ASSERT(si.type() == ScopeIter::Block); if (si.staticBlock().needsClone()) { ClonedBlockObject &clone = si.scope().as<ClonedBlockObject>(); - clone.copyUnaliasedValues(si.frame()); + clone.copyUnaliasedValues(si.initialFrame()); scopes->liveScopes.remove(&clone); } else { - if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(ScopeIterKey(si))) { + if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(MissingScopeKey(si))) { ClonedBlockObject &clone = p->value()->scope().as<ClonedBlockObject>(); - clone.copyUnaliasedValues(si.frame()); + clone.copyUnaliasedValues(si.initialFrame()); scopes->liveScopes.remove(&clone); scopes->missingScopes.remove(p); } } } void DebugScopes::onPopWith(AbstractFramePtr frame) @@ -2345,38 +2297,38 @@ DebugScopes::updateLiveScopes(JSContext continue; if (frame.isFunctionFrame() && frame.callee()->isGenerator()) continue; if (!frame.isDebuggee()) continue; - for (ScopeIter si(frame, i.pc(), cx); !si.done(); ++si) { + for (ScopeIter si(cx, frame, i.pc()); si.withinInitialFrame(); ++si) { if (si.hasScopeObject()) { MOZ_ASSERT(si.scope().compartment() == cx->compartment()); DebugScopes *scopes = ensureCompartmentData(cx); if (!scopes) return false; - if (!scopes->liveScopes.put(&si.scope(), ScopeIterVal(si))) + if (!scopes->liveScopes.put(&si.scope(), LiveScopeVal(si))) return false; liveScopesPostWriteBarrier(cx->runtime(), &scopes->liveScopes, &si.scope()); } } if (frame.prevUpToDate()) return true; MOZ_ASSERT(frame.scopeChain()->compartment()->isDebuggee()); frame.setPrevUpToDate(); } return true; } -ScopeIterVal* +LiveScopeVal * DebugScopes::hasLiveScope(ScopeObject &scope) { DebugScopes *scopes = scope.compartment()->debugScopes; if (!scopes) return nullptr; if (LiveScopeMap::Ptr p = scopes->liveScopes.lookup(&scope)) return &p->value(); @@ -2412,42 +2364,44 @@ DebugScopes::unsetPrevUpToDateUntil(JSCo /* static */ void DebugScopes::forwardLiveFrame(JSContext *cx, AbstractFramePtr from, AbstractFramePtr to) { DebugScopes *scopes = cx->compartment()->debugScopes; if (!scopes) return; for (MissingScopeMap::Enum e(scopes->missingScopes); !e.empty(); e.popFront()) { - ScopeIterKey key = e.front().key(); + MissingScopeKey key = e.front().key(); if (key.frame() == from) { key.updateFrame(to); e.rekeyFront(key); } } for (LiveScopeMap::Enum e(scopes->liveScopes); !e.empty(); e.popFront()) { - ScopeIterVal &val = e.front().value(); + LiveScopeVal &val = e.front().value(); if (val.frame() == from) val.updateFrame(to); } } /*****************************************************************************/ static JSObject * GetDebugScope(JSContext *cx, const ScopeIter &si); static DebugScopeObject * -GetDebugScopeForScope(JSContext *cx, Handle<ScopeObject*> scope, const ScopeIter &enclosing) +GetDebugScopeForScope(JSContext *cx, const ScopeIter &si) { + Rooted<ScopeObject *> scope(cx, &si.scope()); if (DebugScopeObject *debugScope = DebugScopes::hasDebugScope(cx, *scope)) return debugScope; - RootedObject enclosingDebug(cx, GetDebugScope(cx, enclosing)); + ScopeIter copy(cx, si); + RootedObject enclosingDebug(cx, GetDebugScope(cx, ++copy)); if (!enclosingDebug) return nullptr; JSObject &maybeDecl = scope->enclosingScope(); if (maybeDecl.is<DeclEnvObject>()) { MOZ_ASSERT(CallObjectLambdaName(scope->as<CallObject>().callee())); enclosingDebug = DebugScopeObject::create(cx, maybeDecl.as<DeclEnvObject>(), enclosingDebug); if (!enclosingDebug) @@ -2462,20 +2416,26 @@ GetDebugScopeForScope(JSContext *cx, Han return nullptr; return debugScope; } static DebugScopeObject * GetDebugScopeForMissing(JSContext *cx, const ScopeIter &si) { + MOZ_ASSERT(!si.hasScopeObject() && si.canHaveScopeObject()); + + // FIXMEshu completely optimized-out scopes + if (!si.withinInitialFrame()) + MOZ_CRASH("NYI"); + if (DebugScopeObject *debugScope = DebugScopes::hasDebugScope(cx, si)) return debugScope; - ScopeIter copy(si, cx); + ScopeIter copy(cx, si); RootedObject enclosingDebug(cx, GetDebugScope(cx, ++copy)); if (!enclosingDebug) return nullptr; /* * Create the missing scope object. For block objects, this takes care of * storing variable values after the stack frame has been popped. For call * objects, we only use the pretend call object to access callee, bindings @@ -2485,117 +2445,106 @@ GetDebugScopeForMissing(JSContext *cx, c * Note: to preserve scopeChain depth invariants, these lazily-reified * scopes must not be put on the frame's scope chain; instead, they are * maintained via DebugScopes hooks. */ DebugScopeObject *debugScope = nullptr; switch (si.type()) { case ScopeIter::Call: { // Generators should always reify their scopes. - MOZ_ASSERT(!si.frame().callee()->isGenerator()); - Rooted<CallObject*> callobj(cx, CallObject::createForFunction(cx, si.frame())); + MOZ_ASSERT(!si.initialFrame().callee()->isGenerator()); + Rooted<CallObject*> callobj(cx, CallObject::createForFunction(cx, si.initialFrame())); if (!callobj) return nullptr; if (callobj->enclosingScope().is<DeclEnvObject>()) { MOZ_ASSERT(CallObjectLambdaName(callobj->callee())); DeclEnvObject &declenv = callobj->enclosingScope().as<DeclEnvObject>(); enclosingDebug = DebugScopeObject::create(cx, declenv, enclosingDebug); if (!enclosingDebug) return nullptr; } debugScope = DebugScopeObject::create(cx, *callobj, enclosingDebug); break; } case ScopeIter::Block: { // Generators should always reify their scopes. - MOZ_ASSERT_IF(si.frame().isFunctionFrame(), !si.frame().callee()->isGenerator()); + MOZ_ASSERT_IF(si.initialFrame().isFunctionFrame(), + !si.initialFrame().callee()->isGenerator()); Rooted<StaticBlockObject *> staticBlock(cx, &si.staticBlock()); - ClonedBlockObject *block = ClonedBlockObject::create(cx, staticBlock, si.frame()); + ClonedBlockObject *block = ClonedBlockObject::create(cx, staticBlock, si.initialFrame()); if (!block) return nullptr; debugScope = DebugScopeObject::create(cx, *block, enclosingDebug); break; } case ScopeIter::With: - case ScopeIter::StrictEvalScope: + case ScopeIter::Eval: MOZ_CRASH("should already have a scope"); } if (!debugScope) return nullptr; if (!DebugScopes::addDebugScope(cx, si, *debugScope)) return nullptr; return debugScope; } static JSObject * -GetDebugScope(JSContext *cx, JSObject &obj) +GetDebugScopeForNonScopeObject(const ScopeIter &si) { - /* - * As an engine invariant (maintained internally and asserted by Execute), - * ScopeObjects and non-ScopeObjects cannot be interleaved on the scope - * chain; every scope chain must start with zero or more ScopeObjects and - * terminate with one or more non-ScopeObjects (viz., GlobalObject). - */ - if (!obj.is<ScopeObject>()) { + JSObject &enclosing = si.enclosingScope(); + MOZ_ASSERT(!enclosing.is<ScopeObject>()); #ifdef DEBUG - JSObject *o = &obj; - while ((o = o->enclosingScope())) - MOZ_ASSERT(!o->is<ScopeObject>()); + JSObject *o = &enclosing; + while ((o = o->enclosingScope())) + MOZ_ASSERT(!o->is<ScopeObject>()); #endif - return &obj; - } - - Rooted<ScopeObject*> scope(cx, &obj.as<ScopeObject>()); - if (ScopeIterVal *maybeLiveScope = DebugScopes::hasLiveScope(*scope)) { - ScopeIter si(*maybeLiveScope, cx); - return GetDebugScope(cx, si); - } - ScopeIter si(scope->enclosingScope(), cx); - return GetDebugScopeForScope(cx, scope, si); + return &enclosing; } static JSObject * GetDebugScope(JSContext *cx, const ScopeIter &si) { JS_CHECK_RECURSION(cx, return nullptr); if (si.done()) - return GetDebugScope(cx, si.enclosingScope()); - - if (!si.hasScopeObject()) + return GetDebugScopeForNonScopeObject(si); + + if (si.hasScopeObject()) + return GetDebugScopeForScope(cx, si); + + if (si.canHaveScopeObject()) return GetDebugScopeForMissing(cx, si); - Rooted<ScopeObject*> scope(cx, &si.scope()); - - ScopeIter copy(si, cx); - return GetDebugScopeForScope(cx, scope, ++copy); + ScopeIter copy(cx, si); + return GetDebugScope(cx, ++copy); } JSObject * js::GetDebugScopeForFunction(JSContext *cx, HandleFunction fun) { assertSameCompartment(cx, fun); MOZ_ASSERT(CanUseDebugScopeMaps(cx)); if (!DebugScopes::updateLiveScopes(cx)) return nullptr; - return GetDebugScope(cx, *fun->environment()); + ScopeIter si(cx, fun->environment(), fun->nonLazyScript()->enclosingStaticScope()); + return GetDebugScope(cx, si); } JSObject * js::GetDebugScopeForFrame(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc) { assertSameCompartment(cx, frame); if (CanUseDebugScopeMaps(cx) && !DebugScopes::updateLiveScopes(cx)) return nullptr; - ScopeIter si(frame, pc, cx); + ScopeIter si(cx, frame, pc); return GetDebugScope(cx, si); } // See declaration and documentation in jsfriendapi.h JS_FRIEND_API(JSObject *) js::GetObjectEnvironmentObjectForFunction(JSFunction *fun) { if (!fun->isInterpreted())
--- a/js/src/vm/ScopeObject.h +++ b/js/src/vm/ScopeObject.h @@ -15,16 +15,17 @@ #include "vm/ArgumentsObject.h" #include "vm/ProxyObject.h" namespace js { namespace frontend { struct Definition; } class StaticWithObject; +class StaticEvalObject; /*****************************************************************************/ /* * All function scripts have an "enclosing static scope" that refers to the * innermost enclosing let or function in the program text. This allows full * reconstruction of the lexical scope for debugging or compiling efficient * access to variables in enclosing scopes. The static scope is represented at @@ -37,30 +38,16 @@ class StaticWithObject; * There is an additional scope for named lambdas. E.g., in: * * (function f() { var x; function g() { } }) * * g's innermost enclosing scope will first be the function scope containing * 'x', enclosed by a scope containing only the name 'f'. (This separate scope * is necessary due to the fact that declarations in the function scope shadow * (dynamically, in the case of 'eval') the lambda name.) - * - * There are two limitations to the current lexical nesting information: - * - * - 'with' is completely absent; this isn't a problem for the current use - * cases since 'with' causes every static scope to be on the dynamic scope - * chain (so the debugger can find everything) and inhibits all upvar - * optimization. - * - * - The "enclosing static scope" chain stops at 'eval'. For example in: - * let (x) { eval("function f() {}") } - * f does not have an enclosing static scope. This is fine for current uses - * for the same reason as 'with'. - * - * (See also AssertDynamicScopeMatchesStaticScope.) */ template <AllowGC allowGC> class StaticScopeIter { typename MaybeRooted<JSObject*, allowGC>::RootType obj; bool onNamedLambda; public: @@ -68,43 +55,60 @@ class StaticScopeIter : obj(cx, obj), onNamedLambda(false) { static_assert(allowGC == CanGC, "the context-accepting constructor should only be used " "in CanGC code"); MOZ_ASSERT_IF(obj, obj->is<StaticBlockObject>() || obj->is<StaticWithObject>() || + obj->is<StaticEvalObject>() || obj->is<JSFunction>()); } + StaticScopeIter(ExclusiveContext *cx, const StaticScopeIter<CanGC> &ssi) + : obj(cx, ssi.obj), onNamedLambda(ssi.onNamedLambda) + { + JS_STATIC_ASSERT(allowGC == CanGC); + } + explicit StaticScopeIter(JSObject *obj) : obj((ExclusiveContext *) nullptr, obj), onNamedLambda(false) { static_assert(allowGC == NoGC, "the constructor not taking a context should only be " "used in NoGC code"); MOZ_ASSERT_IF(obj, obj->is<StaticBlockObject>() || obj->is<StaticWithObject>() || + obj->is<StaticEvalObject>() || obj->is<JSFunction>()); } + explicit StaticScopeIter(const StaticScopeIter<NoGC> &ssi) + : obj((ExclusiveContext *) nullptr, ssi.obj), onNamedLambda(ssi.onNamedLambda) + { + static_assert(allowGC == NoGC, + "the constructor not taking a context should only be " + "used in NoGC code"); + } + bool done() const; void operator++(int); /* Return whether this static scope will be on the dynamic scope chain. */ bool hasDynamicScopeObject() const; Shape *scopeShape() const; - enum Type { WITH, BLOCK, FUNCTION, NAMED_LAMBDA }; + enum Type { Function, Block, With, NamedLambda, Eval }; Type type() const; StaticBlockObject &block() const; StaticWithObject &staticWith() const; + StaticEvalObject &eval() const; JSScript *funScript() const; JSFunction &fun() const; }; /*****************************************************************************/ /* * A "scope coordinate" describes how to get from head of the scope chain to a @@ -452,16 +456,45 @@ class DynamicWithObject : public NestedS return OBJECT_SLOT; } static inline size_t thisSlot() { return THIS_SLOT; } }; +// Static eval scope template objects on the static scope. Created at the +// time of compiling the eval script, and set as its static enclosing scope. +class StaticEvalObject : public NestedScopeObject +{ + static const uint32_t STRICT_SLOT = 1; + + public: + static const unsigned RESERVED_SLOTS = 2; + static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT2_BACKGROUND; + + static const Class class_; + + static StaticEvalObject *create(JSContext *cx, HandleObject enclosing); + + void setStrict() { + setReservedSlot(STRICT_SLOT, BooleanValue(true)); + } + + bool isStrict() const { + return getReservedSlot(STRICT_SLOT).isTrue(); + } + + // Indirect evals terminate in the global at run time, and has no static + // enclosing scope. + bool isDirect() const { + return getReservedSlot(SCOPE_CHAIN_SLOT).isObject(); + } +}; + class BlockObject : public NestedScopeObject { protected: static const unsigned DEPTH_SLOT = 1; public: static const unsigned RESERVED_SLOTS = 2; static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT4_BACKGROUND; @@ -661,162 +694,129 @@ bool XDRStaticWithObject(XDRState<mode> *xdr, HandleObject enclosingScope, MutableHandle<StaticWithObject*> objp); extern JSObject * CloneNestedScopeObject(JSContext *cx, HandleObject enclosingScope, Handle<NestedScopeObject*> src); /*****************************************************************************/ -class ScopeIterKey; -class ScopeIterVal; - -/* - * A scope iterator describes the active scopes enclosing the current point of - * execution for a single frame, proceeding from inner to outer. Here, "frame" - * means a single activation of: a function, eval, or global code. By design, - * ScopeIter exposes *all* scopes, even those that have been optimized away - * (i.e., no ScopeObject was created when entering the scope and thus there is - * no ScopeObject on fp->scopeChain representing the scope). - * - * Note: ScopeIter iterates over all scopes *within* a frame which means that - * all scopes are ScopeObjects. In particular, the GlobalObject enclosing - * global code (and any random objects passed as scopes to Execute) will not - * be included. - */ +// A scope iterator describes the active scopes starting from a dynamic scope, +// static scope pair. This pair may be derived from the current point of +// execution in a frame. If derived in such a fashion, the ScopeIter tracks +// whether the current scope is within the extent of this initial frame. +// Here, "frame" means a single activation of: a function, eval, or global +// code. class ScopeIter { - friend class ScopeIterKey; - friend class ScopeIterVal; - - public: - enum Type { Call, Block, With, StrictEvalScope }; - - private: - JSContext *cx; + StaticScopeIter<CanGC> ssi_; + RootedObject scope_; AbstractFramePtr frame_; - RootedObject cur_; - Rooted<NestedScopeObject *> staticScope_; - Type type_; - bool hasScopeObject_; void settle(); - /* ScopeIter does not have value semantics. */ + // No value semantics. ScopeIter(const ScopeIter &si) = delete; - ScopeIter(JSContext *cx) = delete; - public: - - /* Constructing from a copy of an existing ScopeIter. */ - ScopeIter(const ScopeIter &si, JSContext *cx + // Constructing from a copy of an existing ScopeIter. + ScopeIter(JSContext *cx, const ScopeIter &si MOZ_GUARD_OBJECT_NOTIFIER_PARAM); - /* Constructing from AbstractFramePtr places ScopeIter on the innermost scope. */ - ScopeIter(AbstractFramePtr frame, jsbytecode *pc, JSContext *cx + // Constructing from a dynamic scope, static scope pair. All scopes are + // considered not to be withinInitialFrame, since no frame is given. + ScopeIter(JSContext *cx, JSObject *scope, JSObject *staticScope MOZ_GUARD_OBJECT_NOTIFIER_PARAM); - /* - * Without a stack frame, the resulting ScopeIter is done() with - * enclosingScope() as given. - */ - ScopeIter(JSObject &enclosingScope, JSContext *cx + // Constructing from a frame. Places the ScopeIter on the innermost scope + // at pc. + ScopeIter(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc MOZ_GUARD_OBJECT_NOTIFIER_PARAM); - ScopeIter(const ScopeIterVal &hashVal, JSContext *cx - MOZ_GUARD_OBJECT_NOTIFIER_PARAM); - - bool done() const { return !frame_; } - - /* If done(): */ - - JSObject &enclosingScope() const { MOZ_ASSERT(done()); return *cur_; } - - /* If !done(): */ - + inline bool done() const; ScopeIter &operator++(); - AbstractFramePtr frame() const { MOZ_ASSERT(!done()); return frame_; } - Type type() const { MOZ_ASSERT(!done()); return type_; } - bool hasScopeObject() const { MOZ_ASSERT(!done()); return hasScopeObject_; } + // If done(): + inline JSObject &enclosingScope() const; + + // If !done(): + enum Type { Call, Block, With, Eval }; + Type type() const; + + inline bool hasScopeObject() const; + inline bool canHaveScopeObject() const; ScopeObject &scope() const; - NestedScopeObject* staticScope() const { return staticScope_; } - StaticBlockObject &staticBlock() const { - MOZ_ASSERT(type() == Block); - return staticScope_->as<StaticBlockObject>(); - } + JSObject *maybeStaticScope() const; + StaticBlockObject &staticBlock() const { return ssi_.block(); } + StaticWithObject &staticWith() const { return ssi_.staticWith(); } + StaticEvalObject &staticEval() const { return ssi_.eval(); } + JSFunction &fun() const { return ssi_.fun(); } + + bool withinInitialFrame() const { return !!frame_; } + AbstractFramePtr initialFrame() const { MOZ_ASSERT(withinInitialFrame()); return frame_; } + AbstractFramePtr maybeInitialFrame() const { return frame_; } MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; -class ScopeIterKey +// The key in MissingScopeMap, used to map live frames to their synthesized +// scopes. +class MissingScopeKey { - friend class ScopeIterVal; + friend class LiveScopeVal; AbstractFramePtr frame_; - JSObject *cur_; - NestedScopeObject *staticScope_; - ScopeIter::Type type_; - bool hasScopeObject_; + JSObject *staticScope_; public: - explicit ScopeIterKey(const ScopeIter &si) - : frame_(si.frame()), cur_(si.cur_), staticScope_(si.staticScope_), type_(si.type_), - hasScopeObject_(si.hasScopeObject_) {} + explicit MissingScopeKey(const ScopeIter &si) + : frame_(si.maybeInitialFrame()), + staticScope_(si.maybeStaticScope()) + { } AbstractFramePtr frame() const { return frame_; } - JSObject *cur() const { return cur_; } - NestedScopeObject *staticScope() const { return staticScope_; } - ScopeIter::Type type() const { return type_; } - bool hasScopeObject() const { return hasScopeObject_; } - JSObject *enclosingScope() const { return cur_; } - JSObject *&enclosingScope() { return cur_; } + JSObject *staticScope() const { return staticScope_; } - void updateCur(JSObject *obj) { cur_ = obj; } - void updateStaticScope(NestedScopeObject *obj) { staticScope_ = obj; } + void updateStaticScope(JSObject *obj) { staticScope_ = obj; } void updateFrame(AbstractFramePtr frame) { frame_ = frame; } - /* For use as hash policy */ - typedef ScopeIterKey Lookup; - static HashNumber hash(ScopeIterKey si); - static bool match(ScopeIterKey si1, ScopeIterKey si2); - bool operator!=(const ScopeIterKey &other) const { - return frame_ != other.frame_ || - cur_ != other.cur_ || - staticScope_ != other.staticScope_ || - type_ != other.type_; + // For use as hash policy. + typedef MissingScopeKey Lookup; + static HashNumber hash(MissingScopeKey sk); + static bool match(MissingScopeKey sk1, MissingScopeKey sk2); + bool operator!=(const MissingScopeKey &other) const { + return frame_ != other.frame_ || staticScope_ != other.staticScope_; } - static void rekey(ScopeIterKey &k, const ScopeIterKey& newKey) { + static void rekey(MissingScopeKey &k, const MissingScopeKey& newKey) { k = newKey; } }; -class ScopeIterVal +// The value in LiveScopeMap, mapped from by live scope objects. +class LiveScopeVal { - friend class ScopeIter; friend class DebugScopes; + friend class MissingScopeKey; AbstractFramePtr frame_; - RelocatablePtrObject cur_; - RelocatablePtrNestedScopeObject staticScope_; - ScopeIter::Type type_; - bool hasScopeObject_; + RelocatablePtrObject staticScope_; void sweep(); - static void staticAsserts(); public: - explicit ScopeIterVal(const ScopeIter &si) - : frame_(si.frame()), cur_(si.cur_), staticScope_(si.staticScope_), type_(si.type_), - hasScopeObject_(si.hasScopeObject_) {} + explicit LiveScopeVal(const ScopeIter &si) + : frame_(si.initialFrame()), + staticScope_(si.maybeStaticScope()) + { } AbstractFramePtr frame() const { return frame_; } + JSObject *staticScope() const { return staticScope_; } + void updateFrame(AbstractFramePtr frame) { frame_ = frame; } }; /*****************************************************************************/ /* * Debug scope objects * @@ -889,39 +889,36 @@ class DebugScopes ObjectWeakMap proxiedScopes; static MOZ_ALWAYS_INLINE void proxiedScopesPostWriteBarrier(JSRuntime *rt, ObjectWeakMap *map, const PreBarrieredObject &key); /* * The map from live frames which have optimized-away scopes to the * corresponding debug scopes. */ - typedef HashMap<ScopeIterKey, + typedef HashMap<MissingScopeKey, ReadBarrieredDebugScopeObject, - ScopeIterKey, + MissingScopeKey, RuntimeAllocPolicy> MissingScopeMap; MissingScopeMap missingScopes; - class MissingScopesRef; - static MOZ_ALWAYS_INLINE void missingScopesPostWriteBarrier(JSRuntime *rt, MissingScopeMap *map, - const ScopeIterKey &key); /* * The map from scope objects of live frames to the live frame. This map * updated lazily whenever the debugger needs the information. In between * two lazy updates, liveScopes becomes incomplete (but not invalid, onPop* * removes scopes as they are popped). Thus, two consecutive debugger lazy * updates of liveScopes need only fill in the new scopes. */ typedef HashMap<ScopeObject *, - ScopeIterVal, + LiveScopeVal, DefaultHasher<ScopeObject *>, RuntimeAllocPolicy> LiveScopeMap; LiveScopeMap liveScopes; static MOZ_ALWAYS_INLINE void liveScopesPostWriteBarrier(JSRuntime *rt, LiveScopeMap *map, - ScopeObject *key); + ScopeObject *key); public: explicit DebugScopes(JSContext *c); ~DebugScopes(); private: bool init(); @@ -936,17 +933,17 @@ class DebugScopes static DebugScopeObject *hasDebugScope(JSContext *cx, ScopeObject &scope); static bool addDebugScope(JSContext *cx, ScopeObject &scope, DebugScopeObject &debugScope); static DebugScopeObject *hasDebugScope(JSContext *cx, const ScopeIter &si); static bool addDebugScope(JSContext *cx, const ScopeIter &si, DebugScopeObject &debugScope); static bool updateLiveScopes(JSContext *cx); - static ScopeIterVal *hasLiveScope(ScopeObject &scope); + static LiveScopeVal *hasLiveScope(ScopeObject &scope); static void unsetPrevUpToDateUntil(JSContext *cx, AbstractFramePtr frame); // When a frame bails out from Ion to Baseline, there might be missing // scopes keyed on, and live scopes containing, the old // RematerializedFrame. Forward those values to the new BaselineFrame. static void forwardLiveFrame(JSContext *cx, AbstractFramePtr from, AbstractFramePtr to); // In debug-mode, these must be called whenever exiting a scope that might @@ -960,24 +957,29 @@ class DebugScopes }; } /* namespace js */ template<> inline bool JSObject::is<js::NestedScopeObject>() const { - return is<js::BlockObject>() || is<js::StaticWithObject>() || is<js::DynamicWithObject>(); + return is<js::BlockObject>() || + is<js::StaticWithObject>() || + is<js::DynamicWithObject>() || + is<js::StaticEvalObject>(); } template<> inline bool JSObject::is<js::ScopeObject>() const { - return is<js::CallObject>() || is<js::DeclEnvObject>() || is<js::NestedScopeObject>() || + return is<js::CallObject>() || + is<js::DeclEnvObject>() || + is<js::NestedScopeObject>() || is<js::UninitializedLexicalObject>(); } template<> inline bool JSObject::is<js::DebugScopeObject>() const { extern bool js_IsDebugScopeSlow(js::ProxyObject *proxy); @@ -1022,16 +1024,47 @@ ScopeObject::aliasedVar(ScopeCoordinate inline NestedScopeObject * NestedScopeObject::enclosingNestedScope() const { JSObject *obj = getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull(); return obj && obj->is<NestedScopeObject>() ? &obj->as<NestedScopeObject>() : nullptr; } +inline bool +ScopeIter::done() const +{ + return ssi_.done(); +} + +inline bool +ScopeIter::hasScopeObject() const +{ + return ssi_.hasDynamicScopeObject(); +} + +inline bool +ScopeIter::canHaveScopeObject() const +{ + // Non-strict eval scopes cannot have dynamic scope objects. + return !ssi_.done() && (type() != Eval || staticEval().isStrict()); +} + +inline JSObject & +ScopeIter::enclosingScope() const +{ + // As an engine invariant (maintained internally and asserted by Execute), + // ScopeObjects and non-ScopeObjects cannot be interleaved on the scope + // chain; every scope chain must start with zero or more ScopeObjects and + // terminate with one or more non-ScopeObjects (viz., GlobalObject). + MOZ_ASSERT(done()); + MOZ_ASSERT(!scope_->is<ScopeObject>()); + return *scope_; +} + #ifdef DEBUG bool AnalyzeEntrainedVariables(JSContext *cx, HandleScript script); #endif } // namespace js #endif /* vm_ScopeObject_h */
--- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -141,40 +141,43 @@ InterpreterFrame::createRestParameter(JS static inline void AssertDynamicScopeMatchesStaticScope(JSContext *cx, JSScript *script, JSObject *scope) { #ifdef DEBUG RootedObject enclosingScope(cx, script->enclosingStaticScope()); for (StaticScopeIter<NoGC> i(enclosingScope); !i.done(); i++) { if (i.hasDynamicScopeObject()) { switch (i.type()) { - case StaticScopeIter<NoGC>::BLOCK: + 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: + case StaticScopeIter<NoGC>::With: MOZ_ASSERT(&i.staticWith() == scope->as<DynamicWithObject>().staticScope()); scope = &scope->as<DynamicWithObject>().enclosingScope(); break; - case StaticScopeIter<NoGC>::FUNCTION: - MOZ_ASSERT(scope->as<CallObject>().callee().nonLazyScript() == i.funScript()); + case StaticScopeIter<NoGC>::NamedLambda: + scope = &scope->as<DeclEnvObject>().enclosingScope(); + break; + case StaticScopeIter<NoGC>::Eval: scope = &scope->as<CallObject>().enclosingScope(); break; - case StaticScopeIter<NoGC>::NAMED_LAMBDA: - scope = &scope->as<DeclEnvObject>().enclosingScope(); - break; } } } - /* - * Ideally, we'd MOZ_ASSERT(!scope->is<ScopeObject>()) but the enclosing - * lexical scope chain stops at eval() boundaries. See StaticScopeIter - * comment. - */ + // The scope chain is always ended by one or more non-syntactic + // ScopeObjects (viz. GlobalObject or a non-syntactic WithObject). + MOZ_ASSERT(!scope->is<ScopeObject>() || + (scope->is<DynamicWithObject>() && + !scope->as<DynamicWithObject>().isSyntactic())); #endif } bool InterpreterFrame::initFunctionScopeObjects(JSContext *cx) { CallObject *callobj = CallObject::createForFunction(cx, this); if (!callobj) @@ -347,17 +350,17 @@ InterpreterFrame::markValues(JSTracer *t { MOZ_ASSERT(sp >= slots()); JSScript *script = this->script(); size_t nfixed = script->nfixed(); size_t nlivefixed = script->nbodyfixed(); if (nfixed != nlivefixed) { - NestedScopeObject *staticScope = script->getStaticScope(pc); + NestedScopeObject *staticScope = script->getStaticBlockScope(pc); while (staticScope && !staticScope->is<StaticBlockObject>()) staticScope = staticScope->enclosingNestedScope(); if (staticScope) { StaticBlockObject &blockObj = staticScope->as<StaticBlockObject>(); nlivefixed = blockObj.localOffset() + blockObj.numVariables(); } }