|
@@ -0,0 +1,2508 @@
|
|
|
+# HG changeset patch
|
|
|
+# User Tooru Fujisawa <arai_a@mac.com>
|
|
|
+# Date 1531358653 -32400
|
|
|
+# Thu Jul 12 10:24:13 2018 +0900
|
|
|
+# Node ID 73895cf7ece580c7acb74830d2afae111e652110
|
|
|
+# Parent 9392aa3091188fafba39e6fd4d3781b13eac6341
|
|
|
+Bug 1460489 - Part 2: Move EmitterScope to EmitterScope.{cpp.h}. r=jwalden
|
|
|
+
|
|
|
+diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp
|
|
|
+--- a/js/src/frontend/BytecodeEmitter.cpp
|
|
|
++++ b/js/src/frontend/BytecodeEmitter.cpp
|
|
|
+@@ -19,16 +19,17 @@
|
|
|
+ #include <string.h>
|
|
|
+
|
|
|
+ #include "jsapi.h"
|
|
|
+ #include "jsnum.h"
|
|
|
+ #include "jstypes.h"
|
|
|
+ #include "jsutil.h"
|
|
|
+
|
|
|
+ #include "ds/Nestable.h"
|
|
|
++#include "frontend/EmitterScope.h"
|
|
|
+ #include "frontend/Parser.h"
|
|
|
+ #include "frontend/TDZCheckCache.h"
|
|
|
+ #include "vm/BytecodeUtil.h"
|
|
|
+ #include "vm/Debugger.h"
|
|
|
+ #include "vm/GeneratorObject.h"
|
|
|
+ #include "vm/JSAtom.h"
|
|
|
+ #include "vm/JSContext.h"
|
|
|
+ #include "vm/JSFunction.h"
|
|
|
+@@ -308,1135 +309,16 @@ class TryFinallyControl : public Bytecod
|
|
|
+ emittingSubroutine_ = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ bool emittingSubroutine() const {
|
|
|
+ return emittingSubroutine_;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+-static inline void
|
|
|
+-MarkAllBindingsClosedOver(LexicalScope::Data& data)
|
|
|
+-{
|
|
|
+- TrailingNamesArray& names = data.trailingNames;
|
|
|
+- for (uint32_t i = 0; i < data.length; i++)
|
|
|
+- names[i] = BindingName(names[i].name(), true);
|
|
|
+-}
|
|
|
+-
|
|
|
+-// A scope that introduces bindings.
|
|
|
+-class BytecodeEmitter::EmitterScope : public Nestable<BytecodeEmitter::EmitterScope>
|
|
|
+-{
|
|
|
+- // The cache of bound names that may be looked up in the
|
|
|
+- // scope. Initially populated as the set of names this scope binds. As
|
|
|
+- // names are looked up in enclosing scopes, they are cached on the
|
|
|
+- // current scope.
|
|
|
+- PooledMapPtr<NameLocationMap> nameCache_;
|
|
|
+-
|
|
|
+- // If this scope's cache does not include free names, such as the
|
|
|
+- // global scope, the NameLocation to return.
|
|
|
+- Maybe<NameLocation> fallbackFreeNameLocation_;
|
|
|
+-
|
|
|
+- // True if there is a corresponding EnvironmentObject on the environment
|
|
|
+- // chain, false if all bindings are stored in frame slots on the stack.
|
|
|
+- bool hasEnvironment_;
|
|
|
+-
|
|
|
+- // The number of enclosing environments. Used for error checking.
|
|
|
+- uint8_t environmentChainLength_;
|
|
|
+-
|
|
|
+- // The next usable slot on the frame for not-closed over bindings.
|
|
|
+- //
|
|
|
+- // The initial frame slot when assigning slots to bindings is the
|
|
|
+- // enclosing scope's nextFrameSlot. For the first scope in a frame,
|
|
|
+- // the initial frame slot is 0.
|
|
|
+- uint32_t nextFrameSlot_;
|
|
|
+-
|
|
|
+- // The index in the BytecodeEmitter's interned scope vector, otherwise
|
|
|
+- // ScopeNote::NoScopeIndex.
|
|
|
+- uint32_t scopeIndex_;
|
|
|
+-
|
|
|
+- // If kind is Lexical, Catch, or With, the index in the BytecodeEmitter's
|
|
|
+- // block scope note list. Otherwise ScopeNote::NoScopeNote.
|
|
|
+- uint32_t noteIndex_;
|
|
|
+-
|
|
|
+- MOZ_MUST_USE bool ensureCache(BytecodeEmitter* bce) {
|
|
|
+- return nameCache_.acquire(bce->cx);
|
|
|
+- }
|
|
|
+-
|
|
|
+- template <typename BindingIter>
|
|
|
+- MOZ_MUST_USE bool checkSlotLimits(BytecodeEmitter* bce, const BindingIter& bi) {
|
|
|
+- if (bi.nextFrameSlot() >= LOCALNO_LIMIT ||
|
|
|
+- bi.nextEnvironmentSlot() >= ENVCOORD_SLOT_LIMIT)
|
|
|
+- {
|
|
|
+- bce->reportError(nullptr, JSMSG_TOO_MANY_LOCALS);
|
|
|
+- return false;
|
|
|
+- }
|
|
|
+- return true;
|
|
|
+- }
|
|
|
+-
|
|
|
+- MOZ_MUST_USE bool checkEnvironmentChainLength(BytecodeEmitter* bce) {
|
|
|
+- uint32_t hops;
|
|
|
+- if (EmitterScope* emitterScope = enclosing(&bce))
|
|
|
+- hops = emitterScope->environmentChainLength_;
|
|
|
+- else
|
|
|
+- hops = bce->sc->compilationEnclosingScope()->environmentChainLength();
|
|
|
+-
|
|
|
+- if (hops >= ENVCOORD_HOPS_LIMIT - 1) {
|
|
|
+- bce->reportError(nullptr, JSMSG_TOO_DEEP, js_function_str);
|
|
|
+- return false;
|
|
|
+- }
|
|
|
+-
|
|
|
+- environmentChainLength_ = mozilla::AssertedCast<uint8_t>(hops + 1);
|
|
|
+- return true;
|
|
|
+- }
|
|
|
+-
|
|
|
+- void updateFrameFixedSlots(BytecodeEmitter* bce, const BindingIter& bi) {
|
|
|
+- nextFrameSlot_ = bi.nextFrameSlot();
|
|
|
+- if (nextFrameSlot_ > bce->maxFixedSlots)
|
|
|
+- bce->maxFixedSlots = nextFrameSlot_;
|
|
|
+- MOZ_ASSERT_IF(bce->sc->isFunctionBox() &&
|
|
|
+- (bce->sc->asFunctionBox()->isGenerator() ||
|
|
|
+- bce->sc->asFunctionBox()->isAsync()),
|
|
|
+- bce->maxFixedSlots == 0);
|
|
|
+- }
|
|
|
+-
|
|
|
+- MOZ_MUST_USE bool putNameInCache(BytecodeEmitter* bce, JSAtom* name, NameLocation loc) {
|
|
|
+- NameLocationMap& cache = *nameCache_;
|
|
|
+- NameLocationMap::AddPtr p = cache.lookupForAdd(name);
|
|
|
+- MOZ_ASSERT(!p);
|
|
|
+- if (!cache.add(p, name, loc)) {
|
|
|
+- ReportOutOfMemory(bce->cx);
|
|
|
+- return false;
|
|
|
+- }
|
|
|
+- return true;
|
|
|
+- }
|
|
|
+-
|
|
|
+- Maybe<NameLocation> lookupInCache(BytecodeEmitter* bce, JSAtom* name) {
|
|
|
+- if (NameLocationMap::Ptr p = nameCache_->lookup(name))
|
|
|
+- return Some(p->value().wrapped);
|
|
|
+- if (fallbackFreeNameLocation_ && nameCanBeFree(bce, name))
|
|
|
+- return fallbackFreeNameLocation_;
|
|
|
+- return Nothing();
|
|
|
+- }
|
|
|
+-
|
|
|
+- friend bool BytecodeEmitter::needsImplicitThis();
|
|
|
+-
|
|
|
+- EmitterScope* enclosing(BytecodeEmitter** bce) const {
|
|
|
+- // There is an enclosing scope with access to the same frame.
|
|
|
+- if (EmitterScope* inFrame = enclosingInFrame())
|
|
|
+- return inFrame;
|
|
|
+-
|
|
|
+- // We are currently compiling the enclosing script, look in the
|
|
|
+- // enclosing BCE.
|
|
|
+- if ((*bce)->parent) {
|
|
|
+- *bce = (*bce)->parent;
|
|
|
+- return (*bce)->innermostEmitterScopeNoCheck();
|
|
|
+- }
|
|
|
+-
|
|
|
+- return nullptr;
|
|
|
+- }
|
|
|
+-
|
|
|
+- Scope* enclosingScope(BytecodeEmitter* bce) const {
|
|
|
+- if (EmitterScope* es = enclosing(&bce))
|
|
|
+- return es->scope(bce);
|
|
|
+-
|
|
|
+- // The enclosing script is already compiled or the current script is the
|
|
|
+- // global script.
|
|
|
+- return bce->sc->compilationEnclosingScope();
|
|
|
+- }
|
|
|
+-
|
|
|
+- static bool nameCanBeFree(BytecodeEmitter* bce, JSAtom* name) {
|
|
|
+- // '.generator' cannot be accessed by name.
|
|
|
+- return name != bce->cx->names().dotGenerator;
|
|
|
+- }
|
|
|
+-
|
|
|
+- static NameLocation searchInEnclosingScope(JSAtom* name, Scope* scope, uint8_t hops);
|
|
|
+- NameLocation searchAndCache(BytecodeEmitter* bce, JSAtom* name);
|
|
|
+-
|
|
|
+- template <typename ScopeCreator>
|
|
|
+- MOZ_MUST_USE bool internScope(BytecodeEmitter* bce, ScopeCreator createScope);
|
|
|
+- template <typename ScopeCreator>
|
|
|
+- MOZ_MUST_USE bool internBodyScope(BytecodeEmitter* bce, ScopeCreator createScope);
|
|
|
+- MOZ_MUST_USE bool appendScopeNote(BytecodeEmitter* bce);
|
|
|
+-
|
|
|
+- MOZ_MUST_USE bool deadZoneFrameSlotRange(BytecodeEmitter* bce, uint32_t slotStart,
|
|
|
+- uint32_t slotEnd);
|
|
|
+-
|
|
|
+- public:
|
|
|
+- explicit EmitterScope(BytecodeEmitter* bce)
|
|
|
+- : Nestable<EmitterScope>(&bce->innermostEmitterScope_),
|
|
|
+- nameCache_(bce->cx->frontendCollectionPool()),
|
|
|
+- hasEnvironment_(false),
|
|
|
+- environmentChainLength_(0),
|
|
|
+- nextFrameSlot_(0),
|
|
|
+- scopeIndex_(ScopeNote::NoScopeIndex),
|
|
|
+- noteIndex_(ScopeNote::NoScopeNoteIndex)
|
|
|
+- { }
|
|
|
+-
|
|
|
+- void dump(BytecodeEmitter* bce);
|
|
|
+-
|
|
|
+- MOZ_MUST_USE bool enterLexical(BytecodeEmitter* bce, ScopeKind kind,
|
|
|
+- Handle<LexicalScope::Data*> bindings);
|
|
|
+- MOZ_MUST_USE bool enterNamedLambda(BytecodeEmitter* bce, FunctionBox* funbox);
|
|
|
+- MOZ_MUST_USE bool enterFunction(BytecodeEmitter* bce, FunctionBox* funbox);
|
|
|
+- MOZ_MUST_USE bool enterFunctionExtraBodyVar(BytecodeEmitter* bce, FunctionBox* funbox);
|
|
|
+- MOZ_MUST_USE bool enterParameterExpressionVar(BytecodeEmitter* bce);
|
|
|
+- MOZ_MUST_USE bool enterGlobal(BytecodeEmitter* bce, GlobalSharedContext* globalsc);
|
|
|
+- MOZ_MUST_USE bool enterEval(BytecodeEmitter* bce, EvalSharedContext* evalsc);
|
|
|
+- MOZ_MUST_USE bool enterModule(BytecodeEmitter* module, ModuleSharedContext* modulesc);
|
|
|
+- MOZ_MUST_USE bool enterWith(BytecodeEmitter* bce);
|
|
|
+- MOZ_MUST_USE bool deadZoneFrameSlots(BytecodeEmitter* bce);
|
|
|
+-
|
|
|
+- MOZ_MUST_USE bool leave(BytecodeEmitter* bce, bool nonLocal = false);
|
|
|
+-
|
|
|
+- uint32_t index() const {
|
|
|
+- MOZ_ASSERT(scopeIndex_ != ScopeNote::NoScopeIndex, "Did you forget to intern a Scope?");
|
|
|
+- return scopeIndex_;
|
|
|
+- }
|
|
|
+-
|
|
|
+- uint32_t noteIndex() const {
|
|
|
+- return noteIndex_;
|
|
|
+- }
|
|
|
+-
|
|
|
+- Scope* scope(const BytecodeEmitter* bce) const {
|
|
|
+- return bce->scopeList.vector[index()];
|
|
|
+- }
|
|
|
+-
|
|
|
+- bool hasEnvironment() const {
|
|
|
+- return hasEnvironment_;
|
|
|
+- }
|
|
|
+-
|
|
|
+- // The first frame slot used.
|
|
|
+- uint32_t frameSlotStart() const {
|
|
|
+- if (EmitterScope* inFrame = enclosingInFrame())
|
|
|
+- return inFrame->nextFrameSlot_;
|
|
|
+- return 0;
|
|
|
+- }
|
|
|
+-
|
|
|
+- // The last frame slot used + 1.
|
|
|
+- uint32_t frameSlotEnd() const {
|
|
|
+- return nextFrameSlot_;
|
|
|
+- }
|
|
|
+-
|
|
|
+- EmitterScope* enclosingInFrame() const {
|
|
|
+- return Nestable<EmitterScope>::enclosing();
|
|
|
+- }
|
|
|
+-
|
|
|
+- NameLocation lookup(BytecodeEmitter* bce, JSAtom* name) {
|
|
|
+- if (Maybe<NameLocation> loc = lookupInCache(bce, name))
|
|
|
+- return *loc;
|
|
|
+- return searchAndCache(bce, name);
|
|
|
+- }
|
|
|
+-
|
|
|
+- Maybe<NameLocation> locationBoundInScope(JSAtom* name, EmitterScope* target);
|
|
|
+-};
|
|
|
+-
|
|
|
+-void
|
|
|
+-BytecodeEmitter::EmitterScope::dump(BytecodeEmitter* bce)
|
|
|
+-{
|
|
|
+- fprintf(stdout, "EmitterScope [%s] %p\n", ScopeKindString(scope(bce)->kind()), this);
|
|
|
+-
|
|
|
+- for (NameLocationMap::Range r = nameCache_->all(); !r.empty(); r.popFront()) {
|
|
|
+- const NameLocation& l = r.front().value();
|
|
|
+-
|
|
|
+- JSAutoByteString bytes;
|
|
|
+- if (!AtomToPrintableString(bce->cx, r.front().key(), &bytes))
|
|
|
+- return;
|
|
|
+- if (l.kind() != NameLocation::Kind::Dynamic)
|
|
|
+- fprintf(stdout, " %s %s ", BindingKindString(l.bindingKind()), bytes.ptr());
|
|
|
+- else
|
|
|
+- fprintf(stdout, " %s ", bytes.ptr());
|
|
|
+-
|
|
|
+- switch (l.kind()) {
|
|
|
+- case NameLocation::Kind::Dynamic:
|
|
|
+- fprintf(stdout, "dynamic\n");
|
|
|
+- break;
|
|
|
+- case NameLocation::Kind::Global:
|
|
|
+- fprintf(stdout, "global\n");
|
|
|
+- break;
|
|
|
+- case NameLocation::Kind::Intrinsic:
|
|
|
+- fprintf(stdout, "intrinsic\n");
|
|
|
+- break;
|
|
|
+- case NameLocation::Kind::NamedLambdaCallee:
|
|
|
+- fprintf(stdout, "named lambda callee\n");
|
|
|
+- break;
|
|
|
+- case NameLocation::Kind::Import:
|
|
|
+- fprintf(stdout, "import\n");
|
|
|
+- break;
|
|
|
+- case NameLocation::Kind::ArgumentSlot:
|
|
|
+- fprintf(stdout, "arg slot=%u\n", l.argumentSlot());
|
|
|
+- break;
|
|
|
+- case NameLocation::Kind::FrameSlot:
|
|
|
+- fprintf(stdout, "frame slot=%u\n", l.frameSlot());
|
|
|
+- break;
|
|
|
+- case NameLocation::Kind::EnvironmentCoordinate:
|
|
|
+- fprintf(stdout, "environment hops=%u slot=%u\n",
|
|
|
+- l.environmentCoordinate().hops(), l.environmentCoordinate().slot());
|
|
|
+- break;
|
|
|
+- case NameLocation::Kind::DynamicAnnexBVar:
|
|
|
+- fprintf(stdout, "dynamic annex b var\n");
|
|
|
+- break;
|
|
|
+- }
|
|
|
+- }
|
|
|
+-
|
|
|
+- fprintf(stdout, "\n");
|
|
|
+-}
|
|
|
+-
|
|
|
+-template <typename ScopeCreator>
|
|
|
+-bool
|
|
|
+-BytecodeEmitter::EmitterScope::internScope(BytecodeEmitter* bce, ScopeCreator createScope)
|
|
|
+-{
|
|
|
+- RootedScope enclosing(bce->cx, enclosingScope(bce));
|
|
|
+- Scope* scope = createScope(bce->cx, enclosing);
|
|
|
+- if (!scope)
|
|
|
+- return false;
|
|
|
+- hasEnvironment_ = scope->hasEnvironment();
|
|
|
+- scopeIndex_ = bce->scopeList.length();
|
|
|
+- return bce->scopeList.append(scope);
|
|
|
+-}
|
|
|
+-
|
|
|
+-template <typename ScopeCreator>
|
|
|
+-bool
|
|
|
+-BytecodeEmitter::EmitterScope::internBodyScope(BytecodeEmitter* bce, ScopeCreator createScope)
|
|
|
+-{
|
|
|
+- MOZ_ASSERT(bce->bodyScopeIndex == UINT32_MAX, "There can be only one body scope");
|
|
|
+- bce->bodyScopeIndex = bce->scopeList.length();
|
|
|
+- return internScope(bce, createScope);
|
|
|
+-}
|
|
|
+-
|
|
|
+-bool
|
|
|
+-BytecodeEmitter::EmitterScope::appendScopeNote(BytecodeEmitter* bce)
|
|
|
+-{
|
|
|
+- MOZ_ASSERT(ScopeKindIsInBody(scope(bce)->kind()) && enclosingInFrame(),
|
|
|
+- "Scope notes are not needed for body-level scopes.");
|
|
|
+- noteIndex_ = bce->scopeNoteList.length();
|
|
|
+- return bce->scopeNoteList.append(index(), bce->offset(), bce->inPrologue(),
|
|
|
+- enclosingInFrame() ? enclosingInFrame()->noteIndex()
|
|
|
+- : ScopeNote::NoScopeNoteIndex);
|
|
|
+-}
|
|
|
+-
|
|
|
+-#ifdef DEBUG
|
|
|
+-static bool
|
|
|
+-NameIsOnEnvironment(Scope* scope, JSAtom* name)
|
|
|
+-{
|
|
|
+- for (BindingIter bi(scope); bi; bi++) {
|
|
|
+- // If found, the name must already be on the environment or an import,
|
|
|
+- // or else there is a bug in the closed-over name analysis in the
|
|
|
+- // Parser.
|
|
|
+- if (bi.name() == name) {
|
|
|
+- BindingLocation::Kind kind = bi.location().kind();
|
|
|
+-
|
|
|
+- if (bi.hasArgumentSlot()) {
|
|
|
+- JSScript* script = scope->as<FunctionScope>().script();
|
|
|
+- if (!script->strict() && !script->functionHasParameterExprs()) {
|
|
|
+- // Check for duplicate positional formal parameters.
|
|
|
+- for (BindingIter bi2(bi); bi2 && bi2.hasArgumentSlot(); bi2++) {
|
|
|
+- if (bi2.name() == name)
|
|
|
+- kind = bi2.location().kind();
|
|
|
+- }
|
|
|
+- }
|
|
|
+- }
|
|
|
+-
|
|
|
+- return kind == BindingLocation::Kind::Global ||
|
|
|
+- kind == BindingLocation::Kind::Environment ||
|
|
|
+- kind == BindingLocation::Kind::Import;
|
|
|
+- }
|
|
|
+- }
|
|
|
+-
|
|
|
+- // If not found, assume it's on the global or dynamically accessed.
|
|
|
+- return true;
|
|
|
+-}
|
|
|
+-#endif
|
|
|
+-
|
|
|
+-/* static */ NameLocation
|
|
|
+-BytecodeEmitter::EmitterScope::searchInEnclosingScope(JSAtom* name, Scope* scope, uint8_t hops)
|
|
|
+-{
|
|
|
+- for (ScopeIter si(scope); si; si++) {
|
|
|
+- MOZ_ASSERT(NameIsOnEnvironment(si.scope(), name));
|
|
|
+-
|
|
|
+- bool hasEnv = si.hasSyntacticEnvironment();
|
|
|
+-
|
|
|
+- switch (si.kind()) {
|
|
|
+- case ScopeKind::Function:
|
|
|
+- if (hasEnv) {
|
|
|
+- JSScript* script = si.scope()->as<FunctionScope>().script();
|
|
|
+- if (script->funHasExtensibleScope())
|
|
|
+- return NameLocation::Dynamic();
|
|
|
+-
|
|
|
+- for (BindingIter bi(si.scope()); bi; bi++) {
|
|
|
+- if (bi.name() != name)
|
|
|
+- continue;
|
|
|
+-
|
|
|
+- BindingLocation bindLoc = bi.location();
|
|
|
+- if (bi.hasArgumentSlot() &&
|
|
|
+- !script->strict() &&
|
|
|
+- !script->functionHasParameterExprs())
|
|
|
+- {
|
|
|
+- // Check for duplicate positional formal parameters.
|
|
|
+- for (BindingIter bi2(bi); bi2 && bi2.hasArgumentSlot(); bi2++) {
|
|
|
+- if (bi2.name() == name)
|
|
|
+- bindLoc = bi2.location();
|
|
|
+- }
|
|
|
+- }
|
|
|
+-
|
|
|
+- MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment);
|
|
|
+- return NameLocation::EnvironmentCoordinate(bi.kind(), hops, bindLoc.slot());
|
|
|
+- }
|
|
|
+- }
|
|
|
+- break;
|
|
|
+-
|
|
|
+- case ScopeKind::FunctionBodyVar:
|
|
|
+- case ScopeKind::ParameterExpressionVar:
|
|
|
+- case ScopeKind::Lexical:
|
|
|
+- case ScopeKind::NamedLambda:
|
|
|
+- case ScopeKind::StrictNamedLambda:
|
|
|
+- case ScopeKind::SimpleCatch:
|
|
|
+- case ScopeKind::Catch:
|
|
|
+- if (hasEnv) {
|
|
|
+- for (BindingIter bi(si.scope()); bi; bi++) {
|
|
|
+- if (bi.name() != name)
|
|
|
+- continue;
|
|
|
+-
|
|
|
+- // The name must already have been marked as closed
|
|
|
+- // over. If this assertion is hit, there is a bug in the
|
|
|
+- // name analysis.
|
|
|
+- BindingLocation bindLoc = bi.location();
|
|
|
+- MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment);
|
|
|
+- return NameLocation::EnvironmentCoordinate(bi.kind(), hops, bindLoc.slot());
|
|
|
+- }
|
|
|
+- }
|
|
|
+- break;
|
|
|
+-
|
|
|
+- case ScopeKind::Module:
|
|
|
+- if (hasEnv) {
|
|
|
+- for (BindingIter bi(si.scope()); bi; bi++) {
|
|
|
+- if (bi.name() != name)
|
|
|
+- continue;
|
|
|
+-
|
|
|
+- BindingLocation bindLoc = bi.location();
|
|
|
+-
|
|
|
+- // Imports are on the environment but are indirect
|
|
|
+- // bindings and must be accessed dynamically instead of
|
|
|
+- // using an EnvironmentCoordinate.
|
|
|
+- if (bindLoc.kind() == BindingLocation::Kind::Import) {
|
|
|
+- MOZ_ASSERT(si.kind() == ScopeKind::Module);
|
|
|
+- return NameLocation::Import();
|
|
|
+- }
|
|
|
+-
|
|
|
+- MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment);
|
|
|
+- return NameLocation::EnvironmentCoordinate(bi.kind(), hops, bindLoc.slot());
|
|
|
+- }
|
|
|
+- }
|
|
|
+- break;
|
|
|
+-
|
|
|
+- case ScopeKind::Eval:
|
|
|
+- case ScopeKind::StrictEval:
|
|
|
+- // As an optimization, if the eval doesn't have its own var
|
|
|
+- // environment and its immediate enclosing scope is a global
|
|
|
+- // scope, all accesses are global.
|
|
|
+- if (!hasEnv && si.scope()->enclosing()->is<GlobalScope>())
|
|
|
+- return NameLocation::Global(BindingKind::Var);
|
|
|
+- return NameLocation::Dynamic();
|
|
|
+-
|
|
|
+- case ScopeKind::Global:
|
|
|
+- return NameLocation::Global(BindingKind::Var);
|
|
|
+-
|
|
|
+- case ScopeKind::With:
|
|
|
+- case ScopeKind::NonSyntactic:
|
|
|
+- return NameLocation::Dynamic();
|
|
|
+-
|
|
|
+- case ScopeKind::WasmInstance:
|
|
|
+- case ScopeKind::WasmFunction:
|
|
|
+- MOZ_CRASH("No direct eval inside wasm functions");
|
|
|
+- }
|
|
|
+-
|
|
|
+- if (hasEnv) {
|
|
|
+- MOZ_ASSERT(hops < ENVCOORD_HOPS_LIMIT - 1);
|
|
|
+- hops++;
|
|
|
+- }
|
|
|
+- }
|
|
|
+-
|
|
|
+- MOZ_CRASH("Malformed scope chain");
|
|
|
+-}
|
|
|
+-
|
|
|
+-NameLocation
|
|
|
+-BytecodeEmitter::EmitterScope::searchAndCache(BytecodeEmitter* bce, JSAtom* name)
|
|
|
+-{
|
|
|
+- Maybe<NameLocation> loc;
|
|
|
+- uint8_t hops = hasEnvironment() ? 1 : 0;
|
|
|
+- DebugOnly<bool> inCurrentScript = enclosingInFrame();
|
|
|
+-
|
|
|
+- // Start searching in the current compilation.
|
|
|
+- for (EmitterScope* es = enclosing(&bce); es; es = es->enclosing(&bce)) {
|
|
|
+- loc = es->lookupInCache(bce, name);
|
|
|
+- if (loc) {
|
|
|
+- if (loc->kind() == NameLocation::Kind::EnvironmentCoordinate)
|
|
|
+- *loc = loc->addHops(hops);
|
|
|
+- break;
|
|
|
+- }
|
|
|
+-
|
|
|
+- if (es->hasEnvironment())
|
|
|
+- hops++;
|
|
|
+-
|
|
|
+-#ifdef DEBUG
|
|
|
+- if (!es->enclosingInFrame())
|
|
|
+- inCurrentScript = false;
|
|
|
+-#endif
|
|
|
+- }
|
|
|
+-
|
|
|
+- // If the name is not found in the current compilation, walk the Scope
|
|
|
+- // chain encompassing the compilation.
|
|
|
+- if (!loc) {
|
|
|
+- inCurrentScript = false;
|
|
|
+- loc = Some(searchInEnclosingScope(name, bce->sc->compilationEnclosingScope(), hops));
|
|
|
+- }
|
|
|
+-
|
|
|
+- // Each script has its own frame. A free name that is accessed
|
|
|
+- // from an inner script must not be a frame slot access. If this
|
|
|
+- // assertion is hit, it is a bug in the free name analysis in the
|
|
|
+- // parser.
|
|
|
+- MOZ_ASSERT_IF(!inCurrentScript, loc->kind() != NameLocation::Kind::FrameSlot);
|
|
|
+-
|
|
|
+- // It is always correct to not cache the location. Ignore OOMs to make
|
|
|
+- // lookups infallible.
|
|
|
+- if (!putNameInCache(bce, name, *loc))
|
|
|
+- bce->cx->recoverFromOutOfMemory();
|
|
|
+-
|
|
|
+- return *loc;
|
|
|
+-}
|
|
|
+-
|
|
|
+-Maybe<NameLocation>
|
|
|
+-BytecodeEmitter::EmitterScope::locationBoundInScope(JSAtom* name, EmitterScope* target)
|
|
|
+-{
|
|
|
+- // The target scope must be an intra-frame enclosing scope of this
|
|
|
+- // one. Count the number of extra hops to reach it.
|
|
|
+- uint8_t extraHops = 0;
|
|
|
+- for (EmitterScope* es = this; es != target; es = es->enclosingInFrame()) {
|
|
|
+- if (es->hasEnvironment())
|
|
|
+- extraHops++;
|
|
|
+- }
|
|
|
+-
|
|
|
+- // Caches are prepopulated with bound names. So if the name is bound in a
|
|
|
+- // particular scope, it must already be in the cache. Furthermore, don't
|
|
|
+- // consult the fallback location as we only care about binding names.
|
|
|
+- Maybe<NameLocation> loc;
|
|
|
+- if (NameLocationMap::Ptr p = target->nameCache_->lookup(name)) {
|
|
|
+- NameLocation l = p->value().wrapped;
|
|
|
+- if (l.kind() == NameLocation::Kind::EnvironmentCoordinate)
|
|
|
+- loc = Some(l.addHops(extraHops));
|
|
|
+- else
|
|
|
+- loc = Some(l);
|
|
|
+- }
|
|
|
+- return loc;
|
|
|
+-}
|
|
|
+-
|
|
|
+-bool
|
|
|
+-BytecodeEmitter::EmitterScope::deadZoneFrameSlotRange(BytecodeEmitter* bce, uint32_t slotStart,
|
|
|
+- uint32_t slotEnd)
|
|
|
+-{
|
|
|
+- // Lexical bindings throw ReferenceErrors if they are used before
|
|
|
+- // initialization. See ES6 8.1.1.1.6.
|
|
|
+- //
|
|
|
+- // For completeness, lexical bindings are initialized in ES6 by calling
|
|
|
+- // InitializeBinding, after which touching the binding will no longer
|
|
|
+- // throw reference errors. See 13.1.11, 9.2.13, 13.6.3.4, 13.6.4.6,
|
|
|
+- // 13.6.4.8, 13.14.5, 15.1.8, and 15.2.0.15.
|
|
|
+- if (slotStart != slotEnd) {
|
|
|
+- if (!bce->emit1(JSOP_UNINITIALIZED))
|
|
|
+- return false;
|
|
|
+- for (uint32_t slot = slotStart; slot < slotEnd; slot++) {
|
|
|
+- if (!bce->emitLocalOp(JSOP_INITLEXICAL, slot))
|
|
|
+- return false;
|
|
|
+- }
|
|
|
+- if (!bce->emit1(JSOP_POP))
|
|
|
+- return false;
|
|
|
+- }
|
|
|
+-
|
|
|
+- return true;
|
|
|
+-}
|
|
|
+-
|
|
|
+-bool
|
|
|
+-BytecodeEmitter::EmitterScope::deadZoneFrameSlots(BytecodeEmitter* bce)
|
|
|
+-{
|
|
|
+- return deadZoneFrameSlotRange(bce, frameSlotStart(), frameSlotEnd());
|
|
|
+-}
|
|
|
+-
|
|
|
+-bool
|
|
|
+-BytecodeEmitter::EmitterScope::enterLexical(BytecodeEmitter* bce, ScopeKind kind,
|
|
|
+- Handle<LexicalScope::Data*> bindings)
|
|
|
+-{
|
|
|
+- MOZ_ASSERT(kind != ScopeKind::NamedLambda && kind != ScopeKind::StrictNamedLambda);
|
|
|
+- MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
|
|
|
+-
|
|
|
+- if (!ensureCache(bce))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- // Marks all names as closed over if the context requires it. This
|
|
|
+- // cannot be done in the Parser as we may not know if the context requires
|
|
|
+- // all bindings to be closed over until after parsing is finished. For
|
|
|
+- // example, legacy generators require all bindings to be closed over but
|
|
|
+- // it is unknown if a function is a legacy generator until the first
|
|
|
+- // 'yield' expression is parsed.
|
|
|
+- //
|
|
|
+- // This is not a problem with other scopes, as all other scopes with
|
|
|
+- // bindings are body-level. At the time of their creation, whether or not
|
|
|
+- // the context requires all bindings to be closed over is already known.
|
|
|
+- if (bce->sc->allBindingsClosedOver())
|
|
|
+- MarkAllBindingsClosedOver(*bindings);
|
|
|
+-
|
|
|
+- // Resolve bindings.
|
|
|
+- TDZCheckCache* tdzCache = bce->innermostTDZCheckCache;
|
|
|
+- uint32_t firstFrameSlot = frameSlotStart();
|
|
|
+- BindingIter bi(*bindings, firstFrameSlot, /* isNamedLambda = */ false);
|
|
|
+- for (; bi; bi++) {
|
|
|
+- if (!checkSlotLimits(bce, bi))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
|
|
|
+- if (!putNameInCache(bce, bi.name(), loc))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- if (!tdzCache->noteTDZCheck(bce, bi.name(), CheckTDZ))
|
|
|
+- return false;
|
|
|
+- }
|
|
|
+-
|
|
|
+- updateFrameFixedSlots(bce, bi);
|
|
|
+-
|
|
|
+- // Create and intern the VM scope.
|
|
|
+- auto createScope = [kind, bindings, firstFrameSlot](JSContext* cx,
|
|
|
+- HandleScope enclosing)
|
|
|
+- {
|
|
|
+- return LexicalScope::create(cx, kind, bindings, firstFrameSlot, enclosing);
|
|
|
+- };
|
|
|
+- if (!internScope(bce, createScope))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- if (ScopeKindIsInBody(kind) && hasEnvironment()) {
|
|
|
+- // After interning the VM scope we can get the scope index.
|
|
|
+- if (!bce->emitInternedScopeOp(index(), JSOP_PUSHLEXICALENV))
|
|
|
+- return false;
|
|
|
+- }
|
|
|
+-
|
|
|
+- // Lexical scopes need notes to be mapped from a pc.
|
|
|
+- if (!appendScopeNote(bce))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- // Put frame slots in TDZ. Environment slots are poisoned during
|
|
|
+- // environment creation.
|
|
|
+- //
|
|
|
+- // This must be done after appendScopeNote to be considered in the extent
|
|
|
+- // of the scope.
|
|
|
+- if (!deadZoneFrameSlotRange(bce, firstFrameSlot, frameSlotEnd()))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- return checkEnvironmentChainLength(bce);
|
|
|
+-}
|
|
|
+-
|
|
|
+-bool
|
|
|
+-BytecodeEmitter::EmitterScope::enterNamedLambda(BytecodeEmitter* bce, FunctionBox* funbox)
|
|
|
+-{
|
|
|
+- MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
|
|
|
+- MOZ_ASSERT(funbox->namedLambdaBindings());
|
|
|
+-
|
|
|
+- if (!ensureCache(bce))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- // See comment in enterLexical about allBindingsClosedOver.
|
|
|
+- if (funbox->allBindingsClosedOver())
|
|
|
+- MarkAllBindingsClosedOver(*funbox->namedLambdaBindings());
|
|
|
+-
|
|
|
+- BindingIter bi(*funbox->namedLambdaBindings(), LOCALNO_LIMIT, /* isNamedLambda = */ true);
|
|
|
+- MOZ_ASSERT(bi.kind() == BindingKind::NamedLambdaCallee);
|
|
|
+-
|
|
|
+- // The lambda name, if not closed over, is accessed via JSOP_CALLEE and
|
|
|
+- // not a frame slot. Do not update frame slot information.
|
|
|
+- NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
|
|
|
+- if (!putNameInCache(bce, bi.name(), loc))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- bi++;
|
|
|
+- MOZ_ASSERT(!bi, "There should be exactly one binding in a NamedLambda scope");
|
|
|
+-
|
|
|
+- auto createScope = [funbox](JSContext* cx, HandleScope enclosing) {
|
|
|
+- ScopeKind scopeKind =
|
|
|
+- funbox->strict() ? ScopeKind::StrictNamedLambda : ScopeKind::NamedLambda;
|
|
|
+- return LexicalScope::create(cx, scopeKind, funbox->namedLambdaBindings(),
|
|
|
+- LOCALNO_LIMIT, enclosing);
|
|
|
+- };
|
|
|
+- if (!internScope(bce, createScope))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- return checkEnvironmentChainLength(bce);
|
|
|
+-}
|
|
|
+-
|
|
|
+-bool
|
|
|
+-BytecodeEmitter::EmitterScope::enterParameterExpressionVar(BytecodeEmitter* bce)
|
|
|
+-{
|
|
|
+- MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
|
|
|
+-
|
|
|
+- if (!ensureCache(bce))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- // Parameter expressions var scopes have no pre-set bindings and are
|
|
|
+- // always extensible, as they are needed for eval.
|
|
|
+- fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
|
|
|
+-
|
|
|
+- // Create and intern the VM scope.
|
|
|
+- uint32_t firstFrameSlot = frameSlotStart();
|
|
|
+- auto createScope = [firstFrameSlot](JSContext* cx, HandleScope enclosing) {
|
|
|
+- return VarScope::create(cx, ScopeKind::ParameterExpressionVar,
|
|
|
+- /* data = */ nullptr, firstFrameSlot,
|
|
|
+- /* needsEnvironment = */ true, enclosing);
|
|
|
+- };
|
|
|
+- if (!internScope(bce, createScope))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- MOZ_ASSERT(hasEnvironment());
|
|
|
+- if (!bce->emitInternedScopeOp(index(), JSOP_PUSHVARENV))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- // The extra var scope needs a note to be mapped from a pc.
|
|
|
+- if (!appendScopeNote(bce))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- return checkEnvironmentChainLength(bce);
|
|
|
+-}
|
|
|
+-
|
|
|
+-bool
|
|
|
+-BytecodeEmitter::EmitterScope::enterFunction(BytecodeEmitter* bce, FunctionBox* funbox)
|
|
|
+-{
|
|
|
+- MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
|
|
|
+-
|
|
|
+- // If there are parameter expressions, there is an extra var scope.
|
|
|
+- if (!funbox->hasExtraBodyVarScope())
|
|
|
+- bce->setVarEmitterScope(this);
|
|
|
+-
|
|
|
+- if (!ensureCache(bce))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- // Resolve body-level bindings, if there are any.
|
|
|
+- auto bindings = funbox->functionScopeBindings();
|
|
|
+- Maybe<uint32_t> lastLexicalSlot;
|
|
|
+- if (bindings) {
|
|
|
+- NameLocationMap& cache = *nameCache_;
|
|
|
+-
|
|
|
+- BindingIter bi(*bindings, funbox->hasParameterExprs);
|
|
|
+- for (; bi; bi++) {
|
|
|
+- if (!checkSlotLimits(bce, bi))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
|
|
|
+- NameLocationMap::AddPtr p = cache.lookupForAdd(bi.name());
|
|
|
+-
|
|
|
+- // The only duplicate bindings that occur are simple formal
|
|
|
+- // parameters, in which case the last position counts, so update the
|
|
|
+- // location.
|
|
|
+- if (p) {
|
|
|
+- MOZ_ASSERT(bi.kind() == BindingKind::FormalParameter);
|
|
|
+- MOZ_ASSERT(!funbox->hasDestructuringArgs);
|
|
|
+- MOZ_ASSERT(!funbox->hasRest());
|
|
|
+- p->value() = loc;
|
|
|
+- continue;
|
|
|
+- }
|
|
|
+-
|
|
|
+- if (!cache.add(p, bi.name(), loc)) {
|
|
|
+- ReportOutOfMemory(bce->cx);
|
|
|
+- return false;
|
|
|
+- }
|
|
|
+- }
|
|
|
+-
|
|
|
+- updateFrameFixedSlots(bce, bi);
|
|
|
+- } else {
|
|
|
+- nextFrameSlot_ = 0;
|
|
|
+- }
|
|
|
+-
|
|
|
+- // If the function's scope may be extended at runtime due to sloppy direct
|
|
|
+- // eval and there is no extra var scope, any names beyond the function
|
|
|
+- // scope must be accessed dynamically as we don't know if the name will
|
|
|
+- // become a 'var' binding due to direct eval.
|
|
|
+- if (!funbox->hasParameterExprs && funbox->hasExtensibleScope())
|
|
|
+- fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
|
|
|
+-
|
|
|
+- // In case of parameter expressions, the parameters are lexical
|
|
|
+- // bindings and have TDZ.
|
|
|
+- if (funbox->hasParameterExprs && nextFrameSlot_) {
|
|
|
+- uint32_t paramFrameSlotEnd = 0;
|
|
|
+- for (BindingIter bi(*bindings, true); bi; bi++) {
|
|
|
+- if (!BindingKindIsLexical(bi.kind()))
|
|
|
+- break;
|
|
|
+-
|
|
|
+- NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
|
|
|
+- if (loc.kind() == NameLocation::Kind::FrameSlot) {
|
|
|
+- MOZ_ASSERT(paramFrameSlotEnd <= loc.frameSlot());
|
|
|
+- paramFrameSlotEnd = loc.frameSlot() + 1;
|
|
|
+- }
|
|
|
+- }
|
|
|
+-
|
|
|
+- if (!deadZoneFrameSlotRange(bce, 0, paramFrameSlotEnd))
|
|
|
+- return false;
|
|
|
+- }
|
|
|
+-
|
|
|
+- // Create and intern the VM scope.
|
|
|
+- auto createScope = [funbox](JSContext* cx, HandleScope enclosing) {
|
|
|
+- RootedFunction fun(cx, funbox->function());
|
|
|
+- return FunctionScope::create(cx, funbox->functionScopeBindings(),
|
|
|
+- funbox->hasParameterExprs,
|
|
|
+- funbox->needsCallObjectRegardlessOfBindings(),
|
|
|
+- fun, enclosing);
|
|
|
+- };
|
|
|
+- if (!internBodyScope(bce, createScope))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- return checkEnvironmentChainLength(bce);
|
|
|
+-}
|
|
|
+-
|
|
|
+-bool
|
|
|
+-BytecodeEmitter::EmitterScope::enterFunctionExtraBodyVar(BytecodeEmitter* bce, FunctionBox* funbox)
|
|
|
+-{
|
|
|
+- MOZ_ASSERT(funbox->hasParameterExprs);
|
|
|
+- MOZ_ASSERT(funbox->extraVarScopeBindings() ||
|
|
|
+- funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings());
|
|
|
+- MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
|
|
|
+-
|
|
|
+- // The extra var scope is never popped once it's entered. It replaces the
|
|
|
+- // function scope as the var emitter scope.
|
|
|
+- bce->setVarEmitterScope(this);
|
|
|
+-
|
|
|
+- if (!ensureCache(bce))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- // Resolve body-level bindings, if there are any.
|
|
|
+- uint32_t firstFrameSlot = frameSlotStart();
|
|
|
+- if (auto bindings = funbox->extraVarScopeBindings()) {
|
|
|
+- BindingIter bi(*bindings, firstFrameSlot);
|
|
|
+- for (; bi; bi++) {
|
|
|
+- if (!checkSlotLimits(bce, bi))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
|
|
|
+- if (!putNameInCache(bce, bi.name(), loc))
|
|
|
+- return false;
|
|
|
+- }
|
|
|
+-
|
|
|
+- updateFrameFixedSlots(bce, bi);
|
|
|
+- } else {
|
|
|
+- nextFrameSlot_ = firstFrameSlot;
|
|
|
+- }
|
|
|
+-
|
|
|
+- // If the extra var scope may be extended at runtime due to sloppy
|
|
|
+- // direct eval, any names beyond the var scope must be accessed
|
|
|
+- // dynamically as we don't know if the name will become a 'var' binding
|
|
|
+- // due to direct eval.
|
|
|
+- if (funbox->hasExtensibleScope())
|
|
|
+- fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
|
|
|
+-
|
|
|
+- // Create and intern the VM scope.
|
|
|
+- auto createScope = [funbox, firstFrameSlot](JSContext* cx, HandleScope enclosing) {
|
|
|
+- return VarScope::create(cx, ScopeKind::FunctionBodyVar,
|
|
|
+- funbox->extraVarScopeBindings(), firstFrameSlot,
|
|
|
+- funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings(),
|
|
|
+- enclosing);
|
|
|
+- };
|
|
|
+- if (!internScope(bce, createScope))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- if (hasEnvironment()) {
|
|
|
+- if (!bce->emitInternedScopeOp(index(), JSOP_PUSHVARENV))
|
|
|
+- return false;
|
|
|
+- }
|
|
|
+-
|
|
|
+- // The extra var scope needs a note to be mapped from a pc.
|
|
|
+- if (!appendScopeNote(bce))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- return checkEnvironmentChainLength(bce);
|
|
|
+-}
|
|
|
+-
|
|
|
+-class DynamicBindingIter : public BindingIter
|
|
|
+-{
|
|
|
+- public:
|
|
|
+- explicit DynamicBindingIter(GlobalSharedContext* sc)
|
|
|
+- : BindingIter(*sc->bindings)
|
|
|
+- { }
|
|
|
+-
|
|
|
+- explicit DynamicBindingIter(EvalSharedContext* sc)
|
|
|
+- : BindingIter(*sc->bindings, /* strict = */ false)
|
|
|
+- {
|
|
|
+- MOZ_ASSERT(!sc->strict());
|
|
|
+- }
|
|
|
+-
|
|
|
+- JSOp bindingOp() const {
|
|
|
+- switch (kind()) {
|
|
|
+- case BindingKind::Var:
|
|
|
+- return JSOP_DEFVAR;
|
|
|
+- case BindingKind::Let:
|
|
|
+- return JSOP_DEFLET;
|
|
|
+- case BindingKind::Const:
|
|
|
+- return JSOP_DEFCONST;
|
|
|
+- default:
|
|
|
+- MOZ_CRASH("Bad BindingKind");
|
|
|
+- }
|
|
|
+- }
|
|
|
+-};
|
|
|
+-
|
|
|
+-bool
|
|
|
+-BytecodeEmitter::EmitterScope::enterGlobal(BytecodeEmitter* bce, GlobalSharedContext* globalsc)
|
|
|
+-{
|
|
|
+- MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
|
|
|
+-
|
|
|
+- bce->setVarEmitterScope(this);
|
|
|
+-
|
|
|
+- if (!ensureCache(bce))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- if (bce->emitterMode == BytecodeEmitter::SelfHosting) {
|
|
|
+- // In self-hosting, it is incorrect to consult the global scope because
|
|
|
+- // self-hosted scripts are cloned into their target compartments before
|
|
|
+- // they are run. Instead of Global, Intrinsic is used for all names.
|
|
|
+- //
|
|
|
+- // Intrinsic lookups are redirected to the special intrinsics holder
|
|
|
+- // in the global object, into which any missing values are cloned
|
|
|
+- // lazily upon first access.
|
|
|
+- fallbackFreeNameLocation_ = Some(NameLocation::Intrinsic());
|
|
|
+-
|
|
|
+- auto createScope = [](JSContext* cx, HandleScope enclosing) {
|
|
|
+- MOZ_ASSERT(!enclosing);
|
|
|
+- return &cx->global()->emptyGlobalScope();
|
|
|
+- };
|
|
|
+- return internBodyScope(bce, createScope);
|
|
|
+- }
|
|
|
+-
|
|
|
+- // Resolve binding names and emit DEF{VAR,LET,CONST} prologue ops.
|
|
|
+- if (globalsc->bindings) {
|
|
|
+- for (DynamicBindingIter bi(globalsc); bi; bi++) {
|
|
|
+- NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
|
|
|
+- JSAtom* name = bi.name();
|
|
|
+- if (!putNameInCache(bce, name, loc))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- // Define the name in the prologue. Do not emit DEFVAR for
|
|
|
+- // functions that we'll emit DEFFUN for.
|
|
|
+- if (bi.isTopLevelFunction())
|
|
|
+- continue;
|
|
|
+-
|
|
|
+- if (!bce->emitAtomOp(name, bi.bindingOp()))
|
|
|
+- return false;
|
|
|
+- }
|
|
|
+- }
|
|
|
+-
|
|
|
+- // Note that to save space, we don't add free names to the cache for
|
|
|
+- // global scopes. They are assumed to be global vars in the syntactic
|
|
|
+- // global scope, dynamic accesses under non-syntactic global scope.
|
|
|
+- if (globalsc->scopeKind() == ScopeKind::Global)
|
|
|
+- fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var));
|
|
|
+- else
|
|
|
+- fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
|
|
|
+-
|
|
|
+- auto createScope = [globalsc](JSContext* cx, HandleScope enclosing) {
|
|
|
+- MOZ_ASSERT(!enclosing);
|
|
|
+- return GlobalScope::create(cx, globalsc->scopeKind(), globalsc->bindings);
|
|
|
+- };
|
|
|
+- return internBodyScope(bce, createScope);
|
|
|
+-}
|
|
|
+-
|
|
|
+-bool
|
|
|
+-BytecodeEmitter::EmitterScope::enterEval(BytecodeEmitter* bce, EvalSharedContext* evalsc)
|
|
|
+-{
|
|
|
+- MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
|
|
|
+-
|
|
|
+- bce->setVarEmitterScope(this);
|
|
|
+-
|
|
|
+- if (!ensureCache(bce))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- // For simplicity, treat all free name lookups in eval scripts as dynamic.
|
|
|
+- fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
|
|
|
+-
|
|
|
+- // Create the `var` scope. Note that there is also a lexical scope, created
|
|
|
+- // separately in emitScript().
|
|
|
+- auto createScope = [evalsc](JSContext* cx, HandleScope enclosing) {
|
|
|
+- ScopeKind scopeKind = evalsc->strict() ? ScopeKind::StrictEval : ScopeKind::Eval;
|
|
|
+- return EvalScope::create(cx, scopeKind, evalsc->bindings, enclosing);
|
|
|
+- };
|
|
|
+- if (!internBodyScope(bce, createScope))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- if (hasEnvironment()) {
|
|
|
+- if (!bce->emitInternedScopeOp(index(), JSOP_PUSHVARENV))
|
|
|
+- return false;
|
|
|
+- } else {
|
|
|
+- // Resolve binding names and emit DEFVAR prologue ops if we don't have
|
|
|
+- // an environment (i.e., a sloppy eval not in a parameter expression).
|
|
|
+- // Eval scripts always have their own lexical scope, but non-strict
|
|
|
+- // scopes may introduce 'var' bindings to the nearest var scope.
|
|
|
+- //
|
|
|
+- // TODO: We may optimize strict eval bindings in the future to be on
|
|
|
+- // the frame. For now, handle everything dynamically.
|
|
|
+- if (!hasEnvironment() && evalsc->bindings) {
|
|
|
+- for (DynamicBindingIter bi(evalsc); bi; bi++) {
|
|
|
+- MOZ_ASSERT(bi.bindingOp() == JSOP_DEFVAR);
|
|
|
+-
|
|
|
+- if (bi.isTopLevelFunction())
|
|
|
+- continue;
|
|
|
+-
|
|
|
+- if (!bce->emitAtomOp(bi.name(), JSOP_DEFVAR))
|
|
|
+- return false;
|
|
|
+- }
|
|
|
+- }
|
|
|
+-
|
|
|
+- // As an optimization, if the eval does not have its own var
|
|
|
+- // environment and is directly enclosed in a global scope, then all
|
|
|
+- // free name lookups are global.
|
|
|
+- if (scope(bce)->enclosing()->is<GlobalScope>())
|
|
|
+- fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var));
|
|
|
+- }
|
|
|
+-
|
|
|
+- return true;
|
|
|
+-}
|
|
|
+-
|
|
|
+-bool
|
|
|
+-BytecodeEmitter::EmitterScope::enterModule(BytecodeEmitter* bce, ModuleSharedContext* modulesc)
|
|
|
+-{
|
|
|
+- MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
|
|
|
+-
|
|
|
+- bce->setVarEmitterScope(this);
|
|
|
+-
|
|
|
+- if (!ensureCache(bce))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- // Resolve body-level bindings, if there are any.
|
|
|
+- TDZCheckCache* tdzCache = bce->innermostTDZCheckCache;
|
|
|
+- Maybe<uint32_t> firstLexicalFrameSlot;
|
|
|
+- if (ModuleScope::Data* bindings = modulesc->bindings) {
|
|
|
+- BindingIter bi(*bindings);
|
|
|
+- for (; bi; bi++) {
|
|
|
+- if (!checkSlotLimits(bce, bi))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
|
|
|
+- if (!putNameInCache(bce, bi.name(), loc))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- if (BindingKindIsLexical(bi.kind())) {
|
|
|
+- if (loc.kind() == NameLocation::Kind::FrameSlot && !firstLexicalFrameSlot)
|
|
|
+- firstLexicalFrameSlot = Some(loc.frameSlot());
|
|
|
+-
|
|
|
+- if (!tdzCache->noteTDZCheck(bce, bi.name(), CheckTDZ))
|
|
|
+- return false;
|
|
|
+- }
|
|
|
+- }
|
|
|
+-
|
|
|
+- updateFrameFixedSlots(bce, bi);
|
|
|
+- } else {
|
|
|
+- nextFrameSlot_ = 0;
|
|
|
+- }
|
|
|
+-
|
|
|
+- // Modules are toplevel, so any free names are global.
|
|
|
+- fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var));
|
|
|
+-
|
|
|
+- // Put lexical frame slots in TDZ. Environment slots are poisoned during
|
|
|
+- // environment creation.
|
|
|
+- if (firstLexicalFrameSlot) {
|
|
|
+- if (!deadZoneFrameSlotRange(bce, *firstLexicalFrameSlot, frameSlotEnd()))
|
|
|
+- return false;
|
|
|
+- }
|
|
|
+-
|
|
|
+- // Create and intern the VM scope.
|
|
|
+- auto createScope = [modulesc](JSContext* cx, HandleScope enclosing) {
|
|
|
+- return ModuleScope::create(cx, modulesc->bindings, modulesc->module(), enclosing);
|
|
|
+- };
|
|
|
+- if (!internBodyScope(bce, createScope))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- return checkEnvironmentChainLength(bce);
|
|
|
+-}
|
|
|
+-
|
|
|
+-bool
|
|
|
+-BytecodeEmitter::EmitterScope::enterWith(BytecodeEmitter* bce)
|
|
|
+-{
|
|
|
+- MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
|
|
|
+-
|
|
|
+- if (!ensureCache(bce))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- // 'with' make all accesses dynamic and unanalyzable.
|
|
|
+- fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
|
|
|
+-
|
|
|
+- auto createScope = [](JSContext* cx, HandleScope enclosing) {
|
|
|
+- return WithScope::create(cx, enclosing);
|
|
|
+- };
|
|
|
+- if (!internScope(bce, createScope))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- if (!bce->emitInternedScopeOp(index(), JSOP_ENTERWITH))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- if (!appendScopeNote(bce))
|
|
|
+- return false;
|
|
|
+-
|
|
|
+- return checkEnvironmentChainLength(bce);
|
|
|
+-}
|
|
|
+-
|
|
|
+-bool
|
|
|
+-BytecodeEmitter::EmitterScope::leave(BytecodeEmitter* bce, bool nonLocal)
|
|
|
+-{
|
|
|
+- // If we aren't leaving the scope due to a non-local jump (e.g., break),
|
|
|
+- // we must be the innermost scope.
|
|
|
+- MOZ_ASSERT_IF(!nonLocal, this == bce->innermostEmitterScopeNoCheck());
|
|
|
+-
|
|
|
+- ScopeKind kind = scope(bce)->kind();
|
|
|
+- switch (kind) {
|
|
|
+- case ScopeKind::Lexical:
|
|
|
+- case ScopeKind::SimpleCatch:
|
|
|
+- case ScopeKind::Catch:
|
|
|
+- if (!bce->emit1(hasEnvironment() ? JSOP_POPLEXICALENV : JSOP_DEBUGLEAVELEXICALENV))
|
|
|
+- return false;
|
|
|
+- break;
|
|
|
+-
|
|
|
+- case ScopeKind::With:
|
|
|
+- if (!bce->emit1(JSOP_LEAVEWITH))
|
|
|
+- return false;
|
|
|
+- break;
|
|
|
+-
|
|
|
+- case ScopeKind::ParameterExpressionVar:
|
|
|
+- MOZ_ASSERT(hasEnvironment());
|
|
|
+- if (!bce->emit1(JSOP_POPVARENV))
|
|
|
+- return false;
|
|
|
+- break;
|
|
|
+-
|
|
|
+- case ScopeKind::Function:
|
|
|
+- case ScopeKind::FunctionBodyVar:
|
|
|
+- case ScopeKind::NamedLambda:
|
|
|
+- case ScopeKind::StrictNamedLambda:
|
|
|
+- case ScopeKind::Eval:
|
|
|
+- case ScopeKind::StrictEval:
|
|
|
+- case ScopeKind::Global:
|
|
|
+- case ScopeKind::NonSyntactic:
|
|
|
+- case ScopeKind::Module:
|
|
|
+- break;
|
|
|
+-
|
|
|
+- case ScopeKind::WasmInstance:
|
|
|
+- case ScopeKind::WasmFunction:
|
|
|
+- MOZ_CRASH("No wasm function scopes in JS");
|
|
|
+- }
|
|
|
+-
|
|
|
+- // Finish up the scope if we are leaving it in LIFO fashion.
|
|
|
+- if (!nonLocal) {
|
|
|
+- // Popping scopes due to non-local jumps generate additional scope
|
|
|
+- // notes. See NonLocalExitControl::prepareForNonLocalJump.
|
|
|
+- if (ScopeKindIsInBody(kind)) {
|
|
|
+- // The extra function var scope is never popped once it's pushed,
|
|
|
+- // so its scope note extends until the end of any possible code.
|
|
|
+- uint32_t offset = kind == ScopeKind::FunctionBodyVar ? UINT32_MAX : bce->offset();
|
|
|
+- bce->scopeNoteList.recordEnd(noteIndex_, offset, bce->inPrologue());
|
|
|
+- }
|
|
|
+- }
|
|
|
+-
|
|
|
+- return true;
|
|
|
+-}
|
|
|
+-
|
|
|
+ // Class for emitting bytecode for blocks like try-catch-finally.
|
|
|
+ //
|
|
|
+ // Usage: (check for the return value is omitted for simplicity)
|
|
|
+ //
|
|
|
+ // `try { try_block } catch (ex) { catch_block }`
|
|
|
+ // TryEmitter tryCatch(this, TryEmitter::Kind::TryCatch,
|
|
|
+ // TryEmitter::ControlKind::Syntactic);
|
|
|
+ // tryCatch.emitTry();
|
|
|
+@@ -2217,18 +1099,16 @@ class MOZ_STACK_CLASS InternalIfEmitter
|
|
|
+ public:
|
|
|
+ explicit InternalIfEmitter(BytecodeEmitter* bce)
|
|
|
+ : IfEmitter(bce, Kind::NoLexicalAccessInBranch)
|
|
|
+ {}
|
|
|
+ };
|
|
|
+
|
|
|
+ class ForOfLoopControl : public LoopControl
|
|
|
+ {
|
|
|
+- using EmitterScope = BytecodeEmitter::EmitterScope;
|
|
|
+-
|
|
|
+ // The stack depth of the iterator.
|
|
|
+ int32_t iterDepth_;
|
|
|
+
|
|
|
+ // for-of loops, when throwing from non-iterator code (i.e. from the body
|
|
|
+ // or from evaluating the LHS of the loop condition), need to call
|
|
|
+ // IteratorClose. This is done by enclosing non-iterator code with
|
|
|
+ // try-catch and call IteratorClose in `catch` block.
|
|
|
+ // If IteratorClose itself throws, we must not re-call IteratorClose. Since
|
|
|
+@@ -2977,17 +1857,17 @@ class NonLocalExitControl
|
|
|
+ BytecodeEmitter* bce_;
|
|
|
+ const uint32_t savedScopeNoteIndex_;
|
|
|
+ const int savedDepth_;
|
|
|
+ uint32_t openScopeNoteIndex_;
|
|
|
+ Kind kind_;
|
|
|
+
|
|
|
+ NonLocalExitControl(const NonLocalExitControl&) = delete;
|
|
|
+
|
|
|
+- MOZ_MUST_USE bool leaveScope(BytecodeEmitter::EmitterScope* scope);
|
|
|
++ MOZ_MUST_USE bool leaveScope(EmitterScope* scope);
|
|
|
+
|
|
|
+ public:
|
|
|
+ NonLocalExitControl(BytecodeEmitter* bce, Kind kind)
|
|
|
+ : bce_(bce),
|
|
|
+ savedScopeNoteIndex_(bce->scopeNoteList.length()),
|
|
|
+ savedDepth_(bce->stackDepth),
|
|
|
+ openScopeNoteIndex_(bce->innermostEmitterScope()->noteIndex()),
|
|
|
+ kind_(kind)
|
|
|
+@@ -3002,17 +1882,17 @@ class NonLocalExitControl
|
|
|
+ MOZ_MUST_USE bool prepareForNonLocalJump(BytecodeEmitter::NestableControl* target);
|
|
|
+
|
|
|
+ MOZ_MUST_USE bool prepareForNonLocalJumpToOutermost() {
|
|
|
+ return prepareForNonLocalJump(nullptr);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ bool
|
|
|
+-NonLocalExitControl::leaveScope(BytecodeEmitter::EmitterScope* es)
|
|
|
++NonLocalExitControl::leaveScope(EmitterScope* es)
|
|
|
+ {
|
|
|
+ if (!es->leave(bce_, /* nonLocal = */ true))
|
|
|
+ return false;
|
|
|
+
|
|
|
+ // As we pop each scope due to the non-local jump, emit notes that
|
|
|
+ // record the extent of the enclosing scope. These notes will have
|
|
|
+ // their ends recorded in ~NonLocalExitControl().
|
|
|
+ uint32_t enclosingScopeIndex = ScopeNote::NoScopeIndex;
|
|
|
+@@ -3028,17 +1908,16 @@ NonLocalExitControl::leaveScope(Bytecode
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Emit additional bytecode(s) for non-local jumps.
|
|
|
+ */
|
|
|
+ bool
|
|
|
+ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* target)
|
|
|
+ {
|
|
|
+ using NestableControl = BytecodeEmitter::NestableControl;
|
|
|
+- using EmitterScope = BytecodeEmitter::EmitterScope;
|
|
|
+
|
|
|
+ EmitterScope* es = bce_->innermostEmitterScope();
|
|
|
+ int npops = 0;
|
|
|
+
|
|
|
+ AutoCheckUnstableEmitterScope cues(bce_);
|
|
|
+
|
|
|
+ // For 'continue', 'break', and 'return' statements, emit IteratorClose
|
|
|
+ // bytecode inline. 'continue' statements do not call IteratorClose for
|
|
|
+diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h
|
|
|
+--- a/js/src/frontend/BytecodeEmitter.h
|
|
|
++++ b/js/src/frontend/BytecodeEmitter.h
|
|
|
+@@ -170,22 +170,22 @@ enum class ValueUsage {
|
|
|
+ WantValue,
|
|
|
+
|
|
|
+ // Pass this when emitting an expression if the expression's value is
|
|
|
+ // definitely unused by later instructions. You must make sure the next
|
|
|
+ // instruction is JSOP_POP, a jump to a JSOP_POP, or something similar.
|
|
|
+ IgnoreValue
|
|
|
+ };
|
|
|
+
|
|
|
++class EmitterScope;
|
|
|
+ class TDZCheckCache;
|
|
|
+
|
|
|
+ struct MOZ_STACK_CLASS BytecodeEmitter
|
|
|
+ {
|
|
|
+ class NestableControl;
|
|
|
+- class EmitterScope;
|
|
|
+
|
|
|
+ SharedContext* const sc; /* context shared between parsing and bytecode generation */
|
|
|
+
|
|
|
+ JSContext* const cx;
|
|
|
+
|
|
|
+ BytecodeEmitter* const parent; /* enclosing function or global context */
|
|
|
+
|
|
|
+ Rooted<JSScript*> script; /* the JSScript we're ultimately producing */
|
|
|
+diff --git a/js/src/frontend/EmitterScope.cpp b/js/src/frontend/EmitterScope.cpp
|
|
|
+new file mode 100644
|
|
|
+--- /dev/null
|
|
|
++++ b/js/src/frontend/EmitterScope.cpp
|
|
|
+@@ -0,0 +1,1060 @@
|
|
|
++/* -*- 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 "frontend/EmitterScope.h"
|
|
|
++
|
|
|
++#include "frontend/BytecodeEmitter.h"
|
|
|
++#include "frontend/TDZCheckCache.h"
|
|
|
++
|
|
|
++using namespace js;
|
|
|
++using namespace js::frontend;
|
|
|
++
|
|
|
++using mozilla::DebugOnly;
|
|
|
++using mozilla::Maybe;
|
|
|
++using mozilla::Nothing;
|
|
|
++using mozilla::Some;
|
|
|
++
|
|
|
++EmitterScope::EmitterScope(BytecodeEmitter* bce)
|
|
|
++ : Nestable<EmitterScope>(&bce->innermostEmitterScope_),
|
|
|
++ nameCache_(bce->cx->frontendCollectionPool()),
|
|
|
++ hasEnvironment_(false),
|
|
|
++ environmentChainLength_(0),
|
|
|
++ nextFrameSlot_(0),
|
|
|
++ scopeIndex_(ScopeNote::NoScopeIndex),
|
|
|
++ noteIndex_(ScopeNote::NoScopeNoteIndex)
|
|
|
++{}
|
|
|
++
|
|
|
++static inline void
|
|
|
++MarkAllBindingsClosedOver(LexicalScope::Data& data)
|
|
|
++{
|
|
|
++ TrailingNamesArray& names = data.trailingNames;
|
|
|
++ for (uint32_t i = 0; i < data.length; i++)
|
|
|
++ names[i] = BindingName(names[i].name(), true);
|
|
|
++}
|
|
|
++
|
|
|
++bool
|
|
|
++EmitterScope::ensureCache(BytecodeEmitter* bce)
|
|
|
++{
|
|
|
++ return nameCache_.acquire(bce->cx);
|
|
|
++}
|
|
|
++
|
|
|
++template <typename BindingIter>
|
|
|
++bool
|
|
|
++EmitterScope::checkSlotLimits(BytecodeEmitter* bce, const BindingIter& bi)
|
|
|
++{
|
|
|
++ if (bi.nextFrameSlot() >= LOCALNO_LIMIT ||
|
|
|
++ bi.nextEnvironmentSlot() >= ENVCOORD_SLOT_LIMIT)
|
|
|
++ {
|
|
|
++ bce->reportError(nullptr, JSMSG_TOO_MANY_LOCALS);
|
|
|
++ return false;
|
|
|
++ }
|
|
|
++ return true;
|
|
|
++}
|
|
|
++
|
|
|
++bool
|
|
|
++EmitterScope::checkEnvironmentChainLength(BytecodeEmitter* bce)
|
|
|
++{
|
|
|
++ uint32_t hops;
|
|
|
++ if (EmitterScope* emitterScope = enclosing(&bce))
|
|
|
++ hops = emitterScope->environmentChainLength_;
|
|
|
++ else
|
|
|
++ hops = bce->sc->compilationEnclosingScope()->environmentChainLength();
|
|
|
++
|
|
|
++ if (hops >= ENVCOORD_HOPS_LIMIT - 1) {
|
|
|
++ bce->reportError(nullptr, JSMSG_TOO_DEEP, js_function_str);
|
|
|
++ return false;
|
|
|
++ }
|
|
|
++
|
|
|
++ environmentChainLength_ = mozilla::AssertedCast<uint8_t>(hops + 1);
|
|
|
++ return true;
|
|
|
++}
|
|
|
++
|
|
|
++void
|
|
|
++EmitterScope::updateFrameFixedSlots(BytecodeEmitter* bce, const BindingIter& bi)
|
|
|
++{
|
|
|
++ nextFrameSlot_ = bi.nextFrameSlot();
|
|
|
++ if (nextFrameSlot_ > bce->maxFixedSlots)
|
|
|
++ bce->maxFixedSlots = nextFrameSlot_;
|
|
|
++ MOZ_ASSERT_IF(bce->sc->isFunctionBox() &&
|
|
|
++ (bce->sc->asFunctionBox()->isGenerator() ||
|
|
|
++ bce->sc->asFunctionBox()->isAsync()),
|
|
|
++ bce->maxFixedSlots == 0);
|
|
|
++}
|
|
|
++
|
|
|
++bool
|
|
|
++EmitterScope::putNameInCache(BytecodeEmitter* bce, JSAtom* name, NameLocation loc)
|
|
|
++{
|
|
|
++ NameLocationMap& cache = *nameCache_;
|
|
|
++ NameLocationMap::AddPtr p = cache.lookupForAdd(name);
|
|
|
++ MOZ_ASSERT(!p);
|
|
|
++ if (!cache.add(p, name, loc)) {
|
|
|
++ ReportOutOfMemory(bce->cx);
|
|
|
++ return false;
|
|
|
++ }
|
|
|
++ return true;
|
|
|
++}
|
|
|
++
|
|
|
++Maybe<NameLocation>
|
|
|
++EmitterScope::lookupInCache(BytecodeEmitter* bce, JSAtom* name)
|
|
|
++{
|
|
|
++ if (NameLocationMap::Ptr p = nameCache_->lookup(name))
|
|
|
++ return Some(p->value().wrapped);
|
|
|
++ if (fallbackFreeNameLocation_ && nameCanBeFree(bce, name))
|
|
|
++ return fallbackFreeNameLocation_;
|
|
|
++ return Nothing();
|
|
|
++}
|
|
|
++
|
|
|
++EmitterScope*
|
|
|
++EmitterScope::enclosing(BytecodeEmitter** bce) const
|
|
|
++{
|
|
|
++ // There is an enclosing scope with access to the same frame.
|
|
|
++ if (EmitterScope* inFrame = enclosingInFrame())
|
|
|
++ return inFrame;
|
|
|
++
|
|
|
++ // We are currently compiling the enclosing script, look in the
|
|
|
++ // enclosing BCE.
|
|
|
++ if ((*bce)->parent) {
|
|
|
++ *bce = (*bce)->parent;
|
|
|
++ return (*bce)->innermostEmitterScopeNoCheck();
|
|
|
++ }
|
|
|
++
|
|
|
++ return nullptr;
|
|
|
++}
|
|
|
++
|
|
|
++Scope*
|
|
|
++EmitterScope::enclosingScope(BytecodeEmitter* bce) const
|
|
|
++{
|
|
|
++ if (EmitterScope* es = enclosing(&bce))
|
|
|
++ return es->scope(bce);
|
|
|
++
|
|
|
++ // The enclosing script is already compiled or the current script is the
|
|
|
++ // global script.
|
|
|
++ return bce->sc->compilationEnclosingScope();
|
|
|
++}
|
|
|
++
|
|
|
++/* static */ bool
|
|
|
++EmitterScope::nameCanBeFree(BytecodeEmitter* bce, JSAtom* name)
|
|
|
++{
|
|
|
++ // '.generator' cannot be accessed by name.
|
|
|
++ return name != bce->cx->names().dotGenerator;
|
|
|
++}
|
|
|
++
|
|
|
++#ifdef DEBUG
|
|
|
++static bool
|
|
|
++NameIsOnEnvironment(Scope* scope, JSAtom* name)
|
|
|
++{
|
|
|
++ for (BindingIter bi(scope); bi; bi++) {
|
|
|
++ // If found, the name must already be on the environment or an import,
|
|
|
++ // or else there is a bug in the closed-over name analysis in the
|
|
|
++ // Parser.
|
|
|
++ if (bi.name() == name) {
|
|
|
++ BindingLocation::Kind kind = bi.location().kind();
|
|
|
++
|
|
|
++ if (bi.hasArgumentSlot()) {
|
|
|
++ JSScript* script = scope->as<FunctionScope>().script();
|
|
|
++ if (!script->strict() && !script->functionHasParameterExprs()) {
|
|
|
++ // Check for duplicate positional formal parameters.
|
|
|
++ for (BindingIter bi2(bi); bi2 && bi2.hasArgumentSlot(); bi2++) {
|
|
|
++ if (bi2.name() == name)
|
|
|
++ kind = bi2.location().kind();
|
|
|
++ }
|
|
|
++ }
|
|
|
++ }
|
|
|
++
|
|
|
++ return kind == BindingLocation::Kind::Global ||
|
|
|
++ kind == BindingLocation::Kind::Environment ||
|
|
|
++ kind == BindingLocation::Kind::Import;
|
|
|
++ }
|
|
|
++ }
|
|
|
++
|
|
|
++ // If not found, assume it's on the global or dynamically accessed.
|
|
|
++ return true;
|
|
|
++}
|
|
|
++#endif
|
|
|
++
|
|
|
++/* static */ NameLocation
|
|
|
++EmitterScope::searchInEnclosingScope(JSAtom* name, Scope* scope, uint8_t hops)
|
|
|
++{
|
|
|
++ for (ScopeIter si(scope); si; si++) {
|
|
|
++ MOZ_ASSERT(NameIsOnEnvironment(si.scope(), name));
|
|
|
++
|
|
|
++ bool hasEnv = si.hasSyntacticEnvironment();
|
|
|
++
|
|
|
++ switch (si.kind()) {
|
|
|
++ case ScopeKind::Function:
|
|
|
++ if (hasEnv) {
|
|
|
++ JSScript* script = si.scope()->as<FunctionScope>().script();
|
|
|
++ if (script->funHasExtensibleScope())
|
|
|
++ return NameLocation::Dynamic();
|
|
|
++
|
|
|
++ for (BindingIter bi(si.scope()); bi; bi++) {
|
|
|
++ if (bi.name() != name)
|
|
|
++ continue;
|
|
|
++
|
|
|
++ BindingLocation bindLoc = bi.location();
|
|
|
++ if (bi.hasArgumentSlot() &&
|
|
|
++ !script->strict() &&
|
|
|
++ !script->functionHasParameterExprs())
|
|
|
++ {
|
|
|
++ // Check for duplicate positional formal parameters.
|
|
|
++ for (BindingIter bi2(bi); bi2 && bi2.hasArgumentSlot(); bi2++) {
|
|
|
++ if (bi2.name() == name)
|
|
|
++ bindLoc = bi2.location();
|
|
|
++ }
|
|
|
++ }
|
|
|
++
|
|
|
++ MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment);
|
|
|
++ return NameLocation::EnvironmentCoordinate(bi.kind(), hops, bindLoc.slot());
|
|
|
++ }
|
|
|
++ }
|
|
|
++ break;
|
|
|
++
|
|
|
++ case ScopeKind::FunctionBodyVar:
|
|
|
++ case ScopeKind::ParameterExpressionVar:
|
|
|
++ case ScopeKind::Lexical:
|
|
|
++ case ScopeKind::NamedLambda:
|
|
|
++ case ScopeKind::StrictNamedLambda:
|
|
|
++ case ScopeKind::SimpleCatch:
|
|
|
++ case ScopeKind::Catch:
|
|
|
++ if (hasEnv) {
|
|
|
++ for (BindingIter bi(si.scope()); bi; bi++) {
|
|
|
++ if (bi.name() != name)
|
|
|
++ continue;
|
|
|
++
|
|
|
++ // The name must already have been marked as closed
|
|
|
++ // over. If this assertion is hit, there is a bug in the
|
|
|
++ // name analysis.
|
|
|
++ BindingLocation bindLoc = bi.location();
|
|
|
++ MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment);
|
|
|
++ return NameLocation::EnvironmentCoordinate(bi.kind(), hops, bindLoc.slot());
|
|
|
++ }
|
|
|
++ }
|
|
|
++ break;
|
|
|
++
|
|
|
++ case ScopeKind::Module:
|
|
|
++ if (hasEnv) {
|
|
|
++ for (BindingIter bi(si.scope()); bi; bi++) {
|
|
|
++ if (bi.name() != name)
|
|
|
++ continue;
|
|
|
++
|
|
|
++ BindingLocation bindLoc = bi.location();
|
|
|
++
|
|
|
++ // Imports are on the environment but are indirect
|
|
|
++ // bindings and must be accessed dynamically instead of
|
|
|
++ // using an EnvironmentCoordinate.
|
|
|
++ if (bindLoc.kind() == BindingLocation::Kind::Import) {
|
|
|
++ MOZ_ASSERT(si.kind() == ScopeKind::Module);
|
|
|
++ return NameLocation::Import();
|
|
|
++ }
|
|
|
++
|
|
|
++ MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment);
|
|
|
++ return NameLocation::EnvironmentCoordinate(bi.kind(), hops, bindLoc.slot());
|
|
|
++ }
|
|
|
++ }
|
|
|
++ break;
|
|
|
++
|
|
|
++ case ScopeKind::Eval:
|
|
|
++ case ScopeKind::StrictEval:
|
|
|
++ // As an optimization, if the eval doesn't have its own var
|
|
|
++ // environment and its immediate enclosing scope is a global
|
|
|
++ // scope, all accesses are global.
|
|
|
++ if (!hasEnv && si.scope()->enclosing()->is<GlobalScope>())
|
|
|
++ return NameLocation::Global(BindingKind::Var);
|
|
|
++ return NameLocation::Dynamic();
|
|
|
++
|
|
|
++ case ScopeKind::Global:
|
|
|
++ return NameLocation::Global(BindingKind::Var);
|
|
|
++
|
|
|
++ case ScopeKind::With:
|
|
|
++ case ScopeKind::NonSyntactic:
|
|
|
++ return NameLocation::Dynamic();
|
|
|
++
|
|
|
++ case ScopeKind::WasmInstance:
|
|
|
++ case ScopeKind::WasmFunction:
|
|
|
++ MOZ_CRASH("No direct eval inside wasm functions");
|
|
|
++ }
|
|
|
++
|
|
|
++ if (hasEnv) {
|
|
|
++ MOZ_ASSERT(hops < ENVCOORD_HOPS_LIMIT - 1);
|
|
|
++ hops++;
|
|
|
++ }
|
|
|
++ }
|
|
|
++
|
|
|
++ MOZ_CRASH("Malformed scope chain");
|
|
|
++}
|
|
|
++
|
|
|
++NameLocation
|
|
|
++EmitterScope::searchAndCache(BytecodeEmitter* bce, JSAtom* name)
|
|
|
++{
|
|
|
++ Maybe<NameLocation> loc;
|
|
|
++ uint8_t hops = hasEnvironment() ? 1 : 0;
|
|
|
++ DebugOnly<bool> inCurrentScript = enclosingInFrame();
|
|
|
++
|
|
|
++ // Start searching in the current compilation.
|
|
|
++ for (EmitterScope* es = enclosing(&bce); es; es = es->enclosing(&bce)) {
|
|
|
++ loc = es->lookupInCache(bce, name);
|
|
|
++ if (loc) {
|
|
|
++ if (loc->kind() == NameLocation::Kind::EnvironmentCoordinate)
|
|
|
++ *loc = loc->addHops(hops);
|
|
|
++ break;
|
|
|
++ }
|
|
|
++
|
|
|
++ if (es->hasEnvironment())
|
|
|
++ hops++;
|
|
|
++
|
|
|
++#ifdef DEBUG
|
|
|
++ if (!es->enclosingInFrame())
|
|
|
++ inCurrentScript = false;
|
|
|
++#endif
|
|
|
++ }
|
|
|
++
|
|
|
++ // If the name is not found in the current compilation, walk the Scope
|
|
|
++ // chain encompassing the compilation.
|
|
|
++ if (!loc) {
|
|
|
++ inCurrentScript = false;
|
|
|
++ loc = Some(searchInEnclosingScope(name, bce->sc->compilationEnclosingScope(), hops));
|
|
|
++ }
|
|
|
++
|
|
|
++ // Each script has its own frame. A free name that is accessed
|
|
|
++ // from an inner script must not be a frame slot access. If this
|
|
|
++ // assertion is hit, it is a bug in the free name analysis in the
|
|
|
++ // parser.
|
|
|
++ MOZ_ASSERT_IF(!inCurrentScript, loc->kind() != NameLocation::Kind::FrameSlot);
|
|
|
++
|
|
|
++ // It is always correct to not cache the location. Ignore OOMs to make
|
|
|
++ // lookups infallible.
|
|
|
++ if (!putNameInCache(bce, name, *loc))
|
|
|
++ bce->cx->recoverFromOutOfMemory();
|
|
|
++
|
|
|
++ return *loc;
|
|
|
++}
|
|
|
++
|
|
|
++template <typename ScopeCreator>
|
|
|
++bool
|
|
|
++EmitterScope::internScope(BytecodeEmitter* bce, ScopeCreator createScope)
|
|
|
++{
|
|
|
++ RootedScope enclosing(bce->cx, enclosingScope(bce));
|
|
|
++ Scope* scope = createScope(bce->cx, enclosing);
|
|
|
++ if (!scope)
|
|
|
++ return false;
|
|
|
++ hasEnvironment_ = scope->hasEnvironment();
|
|
|
++ scopeIndex_ = bce->scopeList.length();
|
|
|
++ return bce->scopeList.append(scope);
|
|
|
++}
|
|
|
++
|
|
|
++template <typename ScopeCreator>
|
|
|
++bool
|
|
|
++EmitterScope::internBodyScope(BytecodeEmitter* bce, ScopeCreator createScope)
|
|
|
++{
|
|
|
++ MOZ_ASSERT(bce->bodyScopeIndex == UINT32_MAX, "There can be only one body scope");
|
|
|
++ bce->bodyScopeIndex = bce->scopeList.length();
|
|
|
++ return internScope(bce, createScope);
|
|
|
++}
|
|
|
++
|
|
|
++bool
|
|
|
++EmitterScope::appendScopeNote(BytecodeEmitter* bce)
|
|
|
++{
|
|
|
++ MOZ_ASSERT(ScopeKindIsInBody(scope(bce)->kind()) && enclosingInFrame(),
|
|
|
++ "Scope notes are not needed for body-level scopes.");
|
|
|
++ noteIndex_ = bce->scopeNoteList.length();
|
|
|
++ return bce->scopeNoteList.append(index(), bce->offset(), bce->inPrologue(),
|
|
|
++ enclosingInFrame() ? enclosingInFrame()->noteIndex()
|
|
|
++ : ScopeNote::NoScopeNoteIndex);
|
|
|
++}
|
|
|
++
|
|
|
++bool
|
|
|
++EmitterScope::deadZoneFrameSlotRange(BytecodeEmitter* bce, uint32_t slotStart, uint32_t slotEnd)
|
|
|
++{
|
|
|
++ // Lexical bindings throw ReferenceErrors if they are used before
|
|
|
++ // initialization. See ES6 8.1.1.1.6.
|
|
|
++ //
|
|
|
++ // For completeness, lexical bindings are initialized in ES6 by calling
|
|
|
++ // InitializeBinding, after which touching the binding will no longer
|
|
|
++ // throw reference errors. See 13.1.11, 9.2.13, 13.6.3.4, 13.6.4.6,
|
|
|
++ // 13.6.4.8, 13.14.5, 15.1.8, and 15.2.0.15.
|
|
|
++ if (slotStart != slotEnd) {
|
|
|
++ if (!bce->emit1(JSOP_UNINITIALIZED))
|
|
|
++ return false;
|
|
|
++ for (uint32_t slot = slotStart; slot < slotEnd; slot++) {
|
|
|
++ if (!bce->emitLocalOp(JSOP_INITLEXICAL, slot))
|
|
|
++ return false;
|
|
|
++ }
|
|
|
++ if (!bce->emit1(JSOP_POP))
|
|
|
++ return false;
|
|
|
++ }
|
|
|
++
|
|
|
++ return true;
|
|
|
++}
|
|
|
++
|
|
|
++void
|
|
|
++EmitterScope::dump(BytecodeEmitter* bce)
|
|
|
++{
|
|
|
++ fprintf(stdout, "EmitterScope [%s] %p\n", ScopeKindString(scope(bce)->kind()), this);
|
|
|
++
|
|
|
++ for (NameLocationMap::Range r = nameCache_->all(); !r.empty(); r.popFront()) {
|
|
|
++ const NameLocation& l = r.front().value();
|
|
|
++
|
|
|
++ JSAutoByteString bytes;
|
|
|
++ if (!AtomToPrintableString(bce->cx, r.front().key(), &bytes))
|
|
|
++ return;
|
|
|
++ if (l.kind() != NameLocation::Kind::Dynamic)
|
|
|
++ fprintf(stdout, " %s %s ", BindingKindString(l.bindingKind()), bytes.ptr());
|
|
|
++ else
|
|
|
++ fprintf(stdout, " %s ", bytes.ptr());
|
|
|
++
|
|
|
++ switch (l.kind()) {
|
|
|
++ case NameLocation::Kind::Dynamic:
|
|
|
++ fprintf(stdout, "dynamic\n");
|
|
|
++ break;
|
|
|
++ case NameLocation::Kind::Global:
|
|
|
++ fprintf(stdout, "global\n");
|
|
|
++ break;
|
|
|
++ case NameLocation::Kind::Intrinsic:
|
|
|
++ fprintf(stdout, "intrinsic\n");
|
|
|
++ break;
|
|
|
++ case NameLocation::Kind::NamedLambdaCallee:
|
|
|
++ fprintf(stdout, "named lambda callee\n");
|
|
|
++ break;
|
|
|
++ case NameLocation::Kind::Import:
|
|
|
++ fprintf(stdout, "import\n");
|
|
|
++ break;
|
|
|
++ case NameLocation::Kind::ArgumentSlot:
|
|
|
++ fprintf(stdout, "arg slot=%u\n", l.argumentSlot());
|
|
|
++ break;
|
|
|
++ case NameLocation::Kind::FrameSlot:
|
|
|
++ fprintf(stdout, "frame slot=%u\n", l.frameSlot());
|
|
|
++ break;
|
|
|
++ case NameLocation::Kind::EnvironmentCoordinate:
|
|
|
++ fprintf(stdout, "environment hops=%u slot=%u\n",
|
|
|
++ l.environmentCoordinate().hops(), l.environmentCoordinate().slot());
|
|
|
++ break;
|
|
|
++ case NameLocation::Kind::DynamicAnnexBVar:
|
|
|
++ fprintf(stdout, "dynamic annex b var\n");
|
|
|
++ break;
|
|
|
++ }
|
|
|
++ }
|
|
|
++
|
|
|
++ fprintf(stdout, "\n");
|
|
|
++}
|
|
|
++
|
|
|
++bool
|
|
|
++EmitterScope::enterLexical(BytecodeEmitter* bce, ScopeKind kind,
|
|
|
++ Handle<LexicalScope::Data*> bindings)
|
|
|
++{
|
|
|
++ MOZ_ASSERT(kind != ScopeKind::NamedLambda && kind != ScopeKind::StrictNamedLambda);
|
|
|
++ MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
|
|
|
++
|
|
|
++ if (!ensureCache(bce))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ // Marks all names as closed over if the context requires it. This
|
|
|
++ // cannot be done in the Parser as we may not know if the context requires
|
|
|
++ // all bindings to be closed over until after parsing is finished. For
|
|
|
++ // example, legacy generators require all bindings to be closed over but
|
|
|
++ // it is unknown if a function is a legacy generator until the first
|
|
|
++ // 'yield' expression is parsed.
|
|
|
++ //
|
|
|
++ // This is not a problem with other scopes, as all other scopes with
|
|
|
++ // bindings are body-level. At the time of their creation, whether or not
|
|
|
++ // the context requires all bindings to be closed over is already known.
|
|
|
++ if (bce->sc->allBindingsClosedOver())
|
|
|
++ MarkAllBindingsClosedOver(*bindings);
|
|
|
++
|
|
|
++ // Resolve bindings.
|
|
|
++ TDZCheckCache* tdzCache = bce->innermostTDZCheckCache;
|
|
|
++ uint32_t firstFrameSlot = frameSlotStart();
|
|
|
++ BindingIter bi(*bindings, firstFrameSlot, /* isNamedLambda = */ false);
|
|
|
++ for (; bi; bi++) {
|
|
|
++ if (!checkSlotLimits(bce, bi))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
|
|
|
++ if (!putNameInCache(bce, bi.name(), loc))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ if (!tdzCache->noteTDZCheck(bce, bi.name(), CheckTDZ))
|
|
|
++ return false;
|
|
|
++ }
|
|
|
++
|
|
|
++ updateFrameFixedSlots(bce, bi);
|
|
|
++
|
|
|
++ // Create and intern the VM scope.
|
|
|
++ auto createScope = [kind, bindings, firstFrameSlot](JSContext* cx,
|
|
|
++ HandleScope enclosing)
|
|
|
++ {
|
|
|
++ return LexicalScope::create(cx, kind, bindings, firstFrameSlot, enclosing);
|
|
|
++ };
|
|
|
++ if (!internScope(bce, createScope))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ if (ScopeKindIsInBody(kind) && hasEnvironment()) {
|
|
|
++ // After interning the VM scope we can get the scope index.
|
|
|
++ if (!bce->emitInternedScopeOp(index(), JSOP_PUSHLEXICALENV))
|
|
|
++ return false;
|
|
|
++ }
|
|
|
++
|
|
|
++ // Lexical scopes need notes to be mapped from a pc.
|
|
|
++ if (!appendScopeNote(bce))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ // Put frame slots in TDZ. Environment slots are poisoned during
|
|
|
++ // environment creation.
|
|
|
++ //
|
|
|
++ // This must be done after appendScopeNote to be considered in the extent
|
|
|
++ // of the scope.
|
|
|
++ if (!deadZoneFrameSlotRange(bce, firstFrameSlot, frameSlotEnd()))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ return checkEnvironmentChainLength(bce);
|
|
|
++}
|
|
|
++
|
|
|
++bool
|
|
|
++EmitterScope::enterNamedLambda(BytecodeEmitter* bce, FunctionBox* funbox)
|
|
|
++{
|
|
|
++ MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
|
|
|
++ MOZ_ASSERT(funbox->namedLambdaBindings());
|
|
|
++
|
|
|
++ if (!ensureCache(bce))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ // See comment in enterLexical about allBindingsClosedOver.
|
|
|
++ if (funbox->allBindingsClosedOver())
|
|
|
++ MarkAllBindingsClosedOver(*funbox->namedLambdaBindings());
|
|
|
++
|
|
|
++ BindingIter bi(*funbox->namedLambdaBindings(), LOCALNO_LIMIT, /* isNamedLambda = */ true);
|
|
|
++ MOZ_ASSERT(bi.kind() == BindingKind::NamedLambdaCallee);
|
|
|
++
|
|
|
++ // The lambda name, if not closed over, is accessed via JSOP_CALLEE and
|
|
|
++ // not a frame slot. Do not update frame slot information.
|
|
|
++ NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
|
|
|
++ if (!putNameInCache(bce, bi.name(), loc))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ bi++;
|
|
|
++ MOZ_ASSERT(!bi, "There should be exactly one binding in a NamedLambda scope");
|
|
|
++
|
|
|
++ auto createScope = [funbox](JSContext* cx, HandleScope enclosing) {
|
|
|
++ ScopeKind scopeKind =
|
|
|
++ funbox->strict() ? ScopeKind::StrictNamedLambda : ScopeKind::NamedLambda;
|
|
|
++ return LexicalScope::create(cx, scopeKind, funbox->namedLambdaBindings(),
|
|
|
++ LOCALNO_LIMIT, enclosing);
|
|
|
++ };
|
|
|
++ if (!internScope(bce, createScope))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ return checkEnvironmentChainLength(bce);
|
|
|
++}
|
|
|
++
|
|
|
++bool
|
|
|
++EmitterScope::enterFunction(BytecodeEmitter* bce, FunctionBox* funbox)
|
|
|
++{
|
|
|
++ MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
|
|
|
++
|
|
|
++ // If there are parameter expressions, there is an extra var scope.
|
|
|
++ if (!funbox->hasExtraBodyVarScope())
|
|
|
++ bce->setVarEmitterScope(this);
|
|
|
++
|
|
|
++ if (!ensureCache(bce))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ // Resolve body-level bindings, if there are any.
|
|
|
++ auto bindings = funbox->functionScopeBindings();
|
|
|
++ Maybe<uint32_t> lastLexicalSlot;
|
|
|
++ if (bindings) {
|
|
|
++ NameLocationMap& cache = *nameCache_;
|
|
|
++
|
|
|
++ BindingIter bi(*bindings, funbox->hasParameterExprs);
|
|
|
++ for (; bi; bi++) {
|
|
|
++ if (!checkSlotLimits(bce, bi))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
|
|
|
++ NameLocationMap::AddPtr p = cache.lookupForAdd(bi.name());
|
|
|
++
|
|
|
++ // The only duplicate bindings that occur are simple formal
|
|
|
++ // parameters, in which case the last position counts, so update the
|
|
|
++ // location.
|
|
|
++ if (p) {
|
|
|
++ MOZ_ASSERT(bi.kind() == BindingKind::FormalParameter);
|
|
|
++ MOZ_ASSERT(!funbox->hasDestructuringArgs);
|
|
|
++ MOZ_ASSERT(!funbox->hasRest());
|
|
|
++ p->value() = loc;
|
|
|
++ continue;
|
|
|
++ }
|
|
|
++
|
|
|
++ if (!cache.add(p, bi.name(), loc)) {
|
|
|
++ ReportOutOfMemory(bce->cx);
|
|
|
++ return false;
|
|
|
++ }
|
|
|
++ }
|
|
|
++
|
|
|
++ updateFrameFixedSlots(bce, bi);
|
|
|
++ } else {
|
|
|
++ nextFrameSlot_ = 0;
|
|
|
++ }
|
|
|
++
|
|
|
++ // If the function's scope may be extended at runtime due to sloppy direct
|
|
|
++ // eval and there is no extra var scope, any names beyond the function
|
|
|
++ // scope must be accessed dynamically as we don't know if the name will
|
|
|
++ // become a 'var' binding due to direct eval.
|
|
|
++ if (!funbox->hasParameterExprs && funbox->hasExtensibleScope())
|
|
|
++ fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
|
|
|
++
|
|
|
++ // In case of parameter expressions, the parameters are lexical
|
|
|
++ // bindings and have TDZ.
|
|
|
++ if (funbox->hasParameterExprs && nextFrameSlot_) {
|
|
|
++ uint32_t paramFrameSlotEnd = 0;
|
|
|
++ for (BindingIter bi(*bindings, true); bi; bi++) {
|
|
|
++ if (!BindingKindIsLexical(bi.kind()))
|
|
|
++ break;
|
|
|
++
|
|
|
++ NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
|
|
|
++ if (loc.kind() == NameLocation::Kind::FrameSlot) {
|
|
|
++ MOZ_ASSERT(paramFrameSlotEnd <= loc.frameSlot());
|
|
|
++ paramFrameSlotEnd = loc.frameSlot() + 1;
|
|
|
++ }
|
|
|
++ }
|
|
|
++
|
|
|
++ if (!deadZoneFrameSlotRange(bce, 0, paramFrameSlotEnd))
|
|
|
++ return false;
|
|
|
++ }
|
|
|
++
|
|
|
++ // Create and intern the VM scope.
|
|
|
++ auto createScope = [funbox](JSContext* cx, HandleScope enclosing) {
|
|
|
++ RootedFunction fun(cx, funbox->function());
|
|
|
++ return FunctionScope::create(cx, funbox->functionScopeBindings(),
|
|
|
++ funbox->hasParameterExprs,
|
|
|
++ funbox->needsCallObjectRegardlessOfBindings(),
|
|
|
++ fun, enclosing);
|
|
|
++ };
|
|
|
++ if (!internBodyScope(bce, createScope))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ return checkEnvironmentChainLength(bce);
|
|
|
++}
|
|
|
++
|
|
|
++bool
|
|
|
++EmitterScope::enterFunctionExtraBodyVar(BytecodeEmitter* bce, FunctionBox* funbox)
|
|
|
++{
|
|
|
++ MOZ_ASSERT(funbox->hasParameterExprs);
|
|
|
++ MOZ_ASSERT(funbox->extraVarScopeBindings() ||
|
|
|
++ funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings());
|
|
|
++ MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
|
|
|
++
|
|
|
++ // The extra var scope is never popped once it's entered. It replaces the
|
|
|
++ // function scope as the var emitter scope.
|
|
|
++ bce->setVarEmitterScope(this);
|
|
|
++
|
|
|
++ if (!ensureCache(bce))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ // Resolve body-level bindings, if there are any.
|
|
|
++ uint32_t firstFrameSlot = frameSlotStart();
|
|
|
++ if (auto bindings = funbox->extraVarScopeBindings()) {
|
|
|
++ BindingIter bi(*bindings, firstFrameSlot);
|
|
|
++ for (; bi; bi++) {
|
|
|
++ if (!checkSlotLimits(bce, bi))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
|
|
|
++ if (!putNameInCache(bce, bi.name(), loc))
|
|
|
++ return false;
|
|
|
++ }
|
|
|
++
|
|
|
++ updateFrameFixedSlots(bce, bi);
|
|
|
++ } else {
|
|
|
++ nextFrameSlot_ = firstFrameSlot;
|
|
|
++ }
|
|
|
++
|
|
|
++ // If the extra var scope may be extended at runtime due to sloppy
|
|
|
++ // direct eval, any names beyond the var scope must be accessed
|
|
|
++ // dynamically as we don't know if the name will become a 'var' binding
|
|
|
++ // due to direct eval.
|
|
|
++ if (funbox->hasExtensibleScope())
|
|
|
++ fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
|
|
|
++
|
|
|
++ // Create and intern the VM scope.
|
|
|
++ auto createScope = [funbox, firstFrameSlot](JSContext* cx, HandleScope enclosing) {
|
|
|
++ return VarScope::create(cx, ScopeKind::FunctionBodyVar,
|
|
|
++ funbox->extraVarScopeBindings(), firstFrameSlot,
|
|
|
++ funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings(),
|
|
|
++ enclosing);
|
|
|
++ };
|
|
|
++ if (!internScope(bce, createScope))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ if (hasEnvironment()) {
|
|
|
++ if (!bce->emitInternedScopeOp(index(), JSOP_PUSHVARENV))
|
|
|
++ return false;
|
|
|
++ }
|
|
|
++
|
|
|
++ // The extra var scope needs a note to be mapped from a pc.
|
|
|
++ if (!appendScopeNote(bce))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ return checkEnvironmentChainLength(bce);
|
|
|
++}
|
|
|
++
|
|
|
++bool
|
|
|
++EmitterScope::enterParameterExpressionVar(BytecodeEmitter* bce)
|
|
|
++{
|
|
|
++ MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
|
|
|
++
|
|
|
++ if (!ensureCache(bce))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ // Parameter expressions var scopes have no pre-set bindings and are
|
|
|
++ // always extensible, as they are needed for eval.
|
|
|
++ fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
|
|
|
++
|
|
|
++ // Create and intern the VM scope.
|
|
|
++ uint32_t firstFrameSlot = frameSlotStart();
|
|
|
++ auto createScope = [firstFrameSlot](JSContext* cx, HandleScope enclosing) {
|
|
|
++ return VarScope::create(cx, ScopeKind::ParameterExpressionVar,
|
|
|
++ /* data = */ nullptr, firstFrameSlot,
|
|
|
++ /* needsEnvironment = */ true, enclosing);
|
|
|
++ };
|
|
|
++ if (!internScope(bce, createScope))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ MOZ_ASSERT(hasEnvironment());
|
|
|
++ if (!bce->emitInternedScopeOp(index(), JSOP_PUSHVARENV))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ // The extra var scope needs a note to be mapped from a pc.
|
|
|
++ if (!appendScopeNote(bce))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ return checkEnvironmentChainLength(bce);
|
|
|
++}
|
|
|
++
|
|
|
++class DynamicBindingIter : public BindingIter
|
|
|
++{
|
|
|
++ public:
|
|
|
++ explicit DynamicBindingIter(GlobalSharedContext* sc)
|
|
|
++ : BindingIter(*sc->bindings)
|
|
|
++ { }
|
|
|
++
|
|
|
++ explicit DynamicBindingIter(EvalSharedContext* sc)
|
|
|
++ : BindingIter(*sc->bindings, /* strict = */ false)
|
|
|
++ {
|
|
|
++ MOZ_ASSERT(!sc->strict());
|
|
|
++ }
|
|
|
++
|
|
|
++ JSOp bindingOp() const {
|
|
|
++ switch (kind()) {
|
|
|
++ case BindingKind::Var:
|
|
|
++ return JSOP_DEFVAR;
|
|
|
++ case BindingKind::Let:
|
|
|
++ return JSOP_DEFLET;
|
|
|
++ case BindingKind::Const:
|
|
|
++ return JSOP_DEFCONST;
|
|
|
++ default:
|
|
|
++ MOZ_CRASH("Bad BindingKind");
|
|
|
++ }
|
|
|
++ }
|
|
|
++};
|
|
|
++
|
|
|
++bool
|
|
|
++EmitterScope::enterGlobal(BytecodeEmitter* bce, GlobalSharedContext* globalsc)
|
|
|
++{
|
|
|
++ MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
|
|
|
++
|
|
|
++ bce->setVarEmitterScope(this);
|
|
|
++
|
|
|
++ if (!ensureCache(bce))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ if (bce->emitterMode == BytecodeEmitter::SelfHosting) {
|
|
|
++ // In self-hosting, it is incorrect to consult the global scope because
|
|
|
++ // self-hosted scripts are cloned into their target compartments before
|
|
|
++ // they are run. Instead of Global, Intrinsic is used for all names.
|
|
|
++ //
|
|
|
++ // Intrinsic lookups are redirected to the special intrinsics holder
|
|
|
++ // in the global object, into which any missing values are cloned
|
|
|
++ // lazily upon first access.
|
|
|
++ fallbackFreeNameLocation_ = Some(NameLocation::Intrinsic());
|
|
|
++
|
|
|
++ auto createScope = [](JSContext* cx, HandleScope enclosing) {
|
|
|
++ MOZ_ASSERT(!enclosing);
|
|
|
++ return &cx->global()->emptyGlobalScope();
|
|
|
++ };
|
|
|
++ return internBodyScope(bce, createScope);
|
|
|
++ }
|
|
|
++
|
|
|
++ // Resolve binding names and emit DEF{VAR,LET,CONST} prologue ops.
|
|
|
++ if (globalsc->bindings) {
|
|
|
++ for (DynamicBindingIter bi(globalsc); bi; bi++) {
|
|
|
++ NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
|
|
|
++ JSAtom* name = bi.name();
|
|
|
++ if (!putNameInCache(bce, name, loc))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ // Define the name in the prologue. Do not emit DEFVAR for
|
|
|
++ // functions that we'll emit DEFFUN for.
|
|
|
++ if (bi.isTopLevelFunction())
|
|
|
++ continue;
|
|
|
++
|
|
|
++ if (!bce->emitAtomOp(name, bi.bindingOp()))
|
|
|
++ return false;
|
|
|
++ }
|
|
|
++ }
|
|
|
++
|
|
|
++ // Note that to save space, we don't add free names to the cache for
|
|
|
++ // global scopes. They are assumed to be global vars in the syntactic
|
|
|
++ // global scope, dynamic accesses under non-syntactic global scope.
|
|
|
++ if (globalsc->scopeKind() == ScopeKind::Global)
|
|
|
++ fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var));
|
|
|
++ else
|
|
|
++ fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
|
|
|
++
|
|
|
++ auto createScope = [globalsc](JSContext* cx, HandleScope enclosing) {
|
|
|
++ MOZ_ASSERT(!enclosing);
|
|
|
++ return GlobalScope::create(cx, globalsc->scopeKind(), globalsc->bindings);
|
|
|
++ };
|
|
|
++ return internBodyScope(bce, createScope);
|
|
|
++}
|
|
|
++
|
|
|
++bool
|
|
|
++EmitterScope::enterEval(BytecodeEmitter* bce, EvalSharedContext* evalsc)
|
|
|
++{
|
|
|
++ MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
|
|
|
++
|
|
|
++ bce->setVarEmitterScope(this);
|
|
|
++
|
|
|
++ if (!ensureCache(bce))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ // For simplicity, treat all free name lookups in eval scripts as dynamic.
|
|
|
++ fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
|
|
|
++
|
|
|
++ // Create the `var` scope. Note that there is also a lexical scope, created
|
|
|
++ // separately in emitScript().
|
|
|
++ auto createScope = [evalsc](JSContext* cx, HandleScope enclosing) {
|
|
|
++ ScopeKind scopeKind = evalsc->strict() ? ScopeKind::StrictEval : ScopeKind::Eval;
|
|
|
++ return EvalScope::create(cx, scopeKind, evalsc->bindings, enclosing);
|
|
|
++ };
|
|
|
++ if (!internBodyScope(bce, createScope))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ if (hasEnvironment()) {
|
|
|
++ if (!bce->emitInternedScopeOp(index(), JSOP_PUSHVARENV))
|
|
|
++ return false;
|
|
|
++ } else {
|
|
|
++ // Resolve binding names and emit DEFVAR prologue ops if we don't have
|
|
|
++ // an environment (i.e., a sloppy eval not in a parameter expression).
|
|
|
++ // Eval scripts always have their own lexical scope, but non-strict
|
|
|
++ // scopes may introduce 'var' bindings to the nearest var scope.
|
|
|
++ //
|
|
|
++ // TODO: We may optimize strict eval bindings in the future to be on
|
|
|
++ // the frame. For now, handle everything dynamically.
|
|
|
++ if (!hasEnvironment() && evalsc->bindings) {
|
|
|
++ for (DynamicBindingIter bi(evalsc); bi; bi++) {
|
|
|
++ MOZ_ASSERT(bi.bindingOp() == JSOP_DEFVAR);
|
|
|
++
|
|
|
++ if (bi.isTopLevelFunction())
|
|
|
++ continue;
|
|
|
++
|
|
|
++ if (!bce->emitAtomOp(bi.name(), JSOP_DEFVAR))
|
|
|
++ return false;
|
|
|
++ }
|
|
|
++ }
|
|
|
++
|
|
|
++ // As an optimization, if the eval does not have its own var
|
|
|
++ // environment and is directly enclosed in a global scope, then all
|
|
|
++ // free name lookups are global.
|
|
|
++ if (scope(bce)->enclosing()->is<GlobalScope>())
|
|
|
++ fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var));
|
|
|
++ }
|
|
|
++
|
|
|
++ return true;
|
|
|
++}
|
|
|
++
|
|
|
++bool
|
|
|
++EmitterScope::enterModule(BytecodeEmitter* bce, ModuleSharedContext* modulesc)
|
|
|
++{
|
|
|
++ MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
|
|
|
++
|
|
|
++ bce->setVarEmitterScope(this);
|
|
|
++
|
|
|
++ if (!ensureCache(bce))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ // Resolve body-level bindings, if there are any.
|
|
|
++ TDZCheckCache* tdzCache = bce->innermostTDZCheckCache;
|
|
|
++ Maybe<uint32_t> firstLexicalFrameSlot;
|
|
|
++ if (ModuleScope::Data* bindings = modulesc->bindings) {
|
|
|
++ BindingIter bi(*bindings);
|
|
|
++ for (; bi; bi++) {
|
|
|
++ if (!checkSlotLimits(bce, bi))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
|
|
|
++ if (!putNameInCache(bce, bi.name(), loc))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ if (BindingKindIsLexical(bi.kind())) {
|
|
|
++ if (loc.kind() == NameLocation::Kind::FrameSlot && !firstLexicalFrameSlot)
|
|
|
++ firstLexicalFrameSlot = Some(loc.frameSlot());
|
|
|
++
|
|
|
++ if (!tdzCache->noteTDZCheck(bce, bi.name(), CheckTDZ))
|
|
|
++ return false;
|
|
|
++ }
|
|
|
++ }
|
|
|
++
|
|
|
++ updateFrameFixedSlots(bce, bi);
|
|
|
++ } else {
|
|
|
++ nextFrameSlot_ = 0;
|
|
|
++ }
|
|
|
++
|
|
|
++ // Modules are toplevel, so any free names are global.
|
|
|
++ fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var));
|
|
|
++
|
|
|
++ // Put lexical frame slots in TDZ. Environment slots are poisoned during
|
|
|
++ // environment creation.
|
|
|
++ if (firstLexicalFrameSlot) {
|
|
|
++ if (!deadZoneFrameSlotRange(bce, *firstLexicalFrameSlot, frameSlotEnd()))
|
|
|
++ return false;
|
|
|
++ }
|
|
|
++
|
|
|
++ // Create and intern the VM scope.
|
|
|
++ auto createScope = [modulesc](JSContext* cx, HandleScope enclosing) {
|
|
|
++ return ModuleScope::create(cx, modulesc->bindings, modulesc->module(), enclosing);
|
|
|
++ };
|
|
|
++ if (!internBodyScope(bce, createScope))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ return checkEnvironmentChainLength(bce);
|
|
|
++}
|
|
|
++
|
|
|
++bool
|
|
|
++EmitterScope::enterWith(BytecodeEmitter* bce)
|
|
|
++{
|
|
|
++ MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
|
|
|
++
|
|
|
++ if (!ensureCache(bce))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ // 'with' make all accesses dynamic and unanalyzable.
|
|
|
++ fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
|
|
|
++
|
|
|
++ auto createScope = [](JSContext* cx, HandleScope enclosing) {
|
|
|
++ return WithScope::create(cx, enclosing);
|
|
|
++ };
|
|
|
++ if (!internScope(bce, createScope))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ if (!bce->emitInternedScopeOp(index(), JSOP_ENTERWITH))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ if (!appendScopeNote(bce))
|
|
|
++ return false;
|
|
|
++
|
|
|
++ return checkEnvironmentChainLength(bce);
|
|
|
++}
|
|
|
++
|
|
|
++bool
|
|
|
++EmitterScope::deadZoneFrameSlots(BytecodeEmitter* bce)
|
|
|
++{
|
|
|
++ return deadZoneFrameSlotRange(bce, frameSlotStart(), frameSlotEnd());
|
|
|
++}
|
|
|
++
|
|
|
++bool
|
|
|
++EmitterScope::leave(BytecodeEmitter* bce, bool nonLocal)
|
|
|
++{
|
|
|
++ // If we aren't leaving the scope due to a non-local jump (e.g., break),
|
|
|
++ // we must be the innermost scope.
|
|
|
++ MOZ_ASSERT_IF(!nonLocal, this == bce->innermostEmitterScopeNoCheck());
|
|
|
++
|
|
|
++ ScopeKind kind = scope(bce)->kind();
|
|
|
++ switch (kind) {
|
|
|
++ case ScopeKind::Lexical:
|
|
|
++ case ScopeKind::SimpleCatch:
|
|
|
++ case ScopeKind::Catch:
|
|
|
++ if (!bce->emit1(hasEnvironment() ? JSOP_POPLEXICALENV : JSOP_DEBUGLEAVELEXICALENV))
|
|
|
++ return false;
|
|
|
++ break;
|
|
|
++
|
|
|
++ case ScopeKind::With:
|
|
|
++ if (!bce->emit1(JSOP_LEAVEWITH))
|
|
|
++ return false;
|
|
|
++ break;
|
|
|
++
|
|
|
++ case ScopeKind::ParameterExpressionVar:
|
|
|
++ MOZ_ASSERT(hasEnvironment());
|
|
|
++ if (!bce->emit1(JSOP_POPVARENV))
|
|
|
++ return false;
|
|
|
++ break;
|
|
|
++
|
|
|
++ case ScopeKind::Function:
|
|
|
++ case ScopeKind::FunctionBodyVar:
|
|
|
++ case ScopeKind::NamedLambda:
|
|
|
++ case ScopeKind::StrictNamedLambda:
|
|
|
++ case ScopeKind::Eval:
|
|
|
++ case ScopeKind::StrictEval:
|
|
|
++ case ScopeKind::Global:
|
|
|
++ case ScopeKind::NonSyntactic:
|
|
|
++ case ScopeKind::Module:
|
|
|
++ break;
|
|
|
++
|
|
|
++ case ScopeKind::WasmInstance:
|
|
|
++ case ScopeKind::WasmFunction:
|
|
|
++ MOZ_CRASH("No wasm function scopes in JS");
|
|
|
++ }
|
|
|
++
|
|
|
++ // Finish up the scope if we are leaving it in LIFO fashion.
|
|
|
++ if (!nonLocal) {
|
|
|
++ // Popping scopes due to non-local jumps generate additional scope
|
|
|
++ // notes. See NonLocalExitControl::prepareForNonLocalJump.
|
|
|
++ if (ScopeKindIsInBody(kind)) {
|
|
|
++ // The extra function var scope is never popped once it's pushed,
|
|
|
++ // so its scope note extends until the end of any possible code.
|
|
|
++ uint32_t offset = kind == ScopeKind::FunctionBodyVar ? UINT32_MAX : bce->offset();
|
|
|
++ bce->scopeNoteList.recordEnd(noteIndex_, offset, bce->inPrologue());
|
|
|
++ }
|
|
|
++ }
|
|
|
++
|
|
|
++ return true;
|
|
|
++}
|
|
|
++
|
|
|
++Scope*
|
|
|
++EmitterScope::scope(const BytecodeEmitter* bce) const
|
|
|
++{
|
|
|
++ return bce->scopeList.vector[index()];
|
|
|
++}
|
|
|
++
|
|
|
++NameLocation
|
|
|
++EmitterScope::lookup(BytecodeEmitter* bce, JSAtom* name)
|
|
|
++{
|
|
|
++ if (Maybe<NameLocation> loc = lookupInCache(bce, name))
|
|
|
++ return *loc;
|
|
|
++ return searchAndCache(bce, name);
|
|
|
++}
|
|
|
++
|
|
|
++Maybe<NameLocation>
|
|
|
++EmitterScope::locationBoundInScope(JSAtom* name, EmitterScope* target)
|
|
|
++{
|
|
|
++ // The target scope must be an intra-frame enclosing scope of this
|
|
|
++ // one. Count the number of extra hops to reach it.
|
|
|
++ uint8_t extraHops = 0;
|
|
|
++ for (EmitterScope* es = this; es != target; es = es->enclosingInFrame()) {
|
|
|
++ if (es->hasEnvironment())
|
|
|
++ extraHops++;
|
|
|
++ }
|
|
|
++
|
|
|
++ // Caches are prepopulated with bound names. So if the name is bound in a
|
|
|
++ // particular scope, it must already be in the cache. Furthermore, don't
|
|
|
++ // consult the fallback location as we only care about binding names.
|
|
|
++ Maybe<NameLocation> loc;
|
|
|
++ if (NameLocationMap::Ptr p = target->nameCache_->lookup(name)) {
|
|
|
++ NameLocation l = p->value().wrapped;
|
|
|
++ if (l.kind() == NameLocation::Kind::EnvironmentCoordinate)
|
|
|
++ loc = Some(l.addHops(extraHops));
|
|
|
++ else
|
|
|
++ loc = Some(l);
|
|
|
++ }
|
|
|
++ return loc;
|
|
|
++}
|
|
|
+diff --git a/js/src/frontend/EmitterScope.h b/js/src/frontend/EmitterScope.h
|
|
|
+new file mode 100644
|
|
|
+--- /dev/null
|
|
|
++++ b/js/src/frontend/EmitterScope.h
|
|
|
+@@ -0,0 +1,150 @@
|
|
|
++/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
|
++ * vim: set ts=8 sts=4 et sw=4 tw=99:
|
|
|
++ * This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
++
|
|
|
++#ifndef frontend_EmitterScope_h
|
|
|
++#define frontend_EmitterScope_h
|
|
|
++
|
|
|
++#include "mozilla/Attributes.h"
|
|
|
++#include "mozilla/Maybe.h"
|
|
|
++
|
|
|
++#include <stdint.h>
|
|
|
++
|
|
|
++#include "ds/Nestable.h"
|
|
|
++#include "frontend/NameAnalysisTypes.h"
|
|
|
++#include "frontend/NameCollections.h"
|
|
|
++#include "frontend/ParseContext.h"
|
|
|
++#include "frontend/SharedContext.h"
|
|
|
++#include "js/TypeDecls.h"
|
|
|
++#include "vm/Scope.h"
|
|
|
++
|
|
|
++namespace js {
|
|
|
++namespace frontend {
|
|
|
++
|
|
|
++// A scope that introduces bindings.
|
|
|
++class EmitterScope : public Nestable<EmitterScope>
|
|
|
++{
|
|
|
++ // The cache of bound names that may be looked up in the
|
|
|
++ // scope. Initially populated as the set of names this scope binds. As
|
|
|
++ // names are looked up in enclosing scopes, they are cached on the
|
|
|
++ // current scope.
|
|
|
++ PooledMapPtr<NameLocationMap> nameCache_;
|
|
|
++
|
|
|
++ // If this scope's cache does not include free names, such as the
|
|
|
++ // global scope, the NameLocation to return.
|
|
|
++ mozilla::Maybe<NameLocation> fallbackFreeNameLocation_;
|
|
|
++
|
|
|
++ // True if there is a corresponding EnvironmentObject on the environment
|
|
|
++ // chain, false if all bindings are stored in frame slots on the stack.
|
|
|
++ bool hasEnvironment_;
|
|
|
++
|
|
|
++ // The number of enclosing environments. Used for error checking.
|
|
|
++ uint8_t environmentChainLength_;
|
|
|
++
|
|
|
++ // The next usable slot on the frame for not-closed over bindings.
|
|
|
++ //
|
|
|
++ // The initial frame slot when assigning slots to bindings is the
|
|
|
++ // enclosing scope's nextFrameSlot. For the first scope in a frame,
|
|
|
++ // the initial frame slot is 0.
|
|
|
++ uint32_t nextFrameSlot_;
|
|
|
++
|
|
|
++ // The index in the BytecodeEmitter's interned scope vector, otherwise
|
|
|
++ // ScopeNote::NoScopeIndex.
|
|
|
++ uint32_t scopeIndex_;
|
|
|
++
|
|
|
++ // If kind is Lexical, Catch, or With, the index in the BytecodeEmitter's
|
|
|
++ // block scope note list. Otherwise ScopeNote::NoScopeNote.
|
|
|
++ uint32_t noteIndex_;
|
|
|
++
|
|
|
++ MOZ_MUST_USE bool ensureCache(BytecodeEmitter* bce);
|
|
|
++
|
|
|
++ template <typename BindingIter>
|
|
|
++ MOZ_MUST_USE bool checkSlotLimits(BytecodeEmitter* bce, const BindingIter& bi);
|
|
|
++
|
|
|
++ MOZ_MUST_USE bool checkEnvironmentChainLength(BytecodeEmitter* bce);
|
|
|
++
|
|
|
++ void updateFrameFixedSlots(BytecodeEmitter* bce, const BindingIter& bi);
|
|
|
++
|
|
|
++ MOZ_MUST_USE bool putNameInCache(BytecodeEmitter* bce, JSAtom* name, NameLocation loc);
|
|
|
++
|
|
|
++ mozilla::Maybe<NameLocation> lookupInCache(BytecodeEmitter* bce, JSAtom* name);
|
|
|
++
|
|
|
++ EmitterScope* enclosing(BytecodeEmitter** bce) const;
|
|
|
++
|
|
|
++ Scope* enclosingScope(BytecodeEmitter* bce) const;
|
|
|
++
|
|
|
++ static bool nameCanBeFree(BytecodeEmitter* bce, JSAtom* name);
|
|
|
++
|
|
|
++ static NameLocation searchInEnclosingScope(JSAtom* name, Scope* scope, uint8_t hops);
|
|
|
++ NameLocation searchAndCache(BytecodeEmitter* bce, JSAtom* name);
|
|
|
++
|
|
|
++ template <typename ScopeCreator>
|
|
|
++ MOZ_MUST_USE bool internScope(BytecodeEmitter* bce, ScopeCreator createScope);
|
|
|
++ template <typename ScopeCreator>
|
|
|
++ MOZ_MUST_USE bool internBodyScope(BytecodeEmitter* bce, ScopeCreator createScope);
|
|
|
++ MOZ_MUST_USE bool appendScopeNote(BytecodeEmitter* bce);
|
|
|
++
|
|
|
++ MOZ_MUST_USE bool deadZoneFrameSlotRange(BytecodeEmitter* bce, uint32_t slotStart,
|
|
|
++ uint32_t slotEnd);
|
|
|
++
|
|
|
++ public:
|
|
|
++ explicit EmitterScope(BytecodeEmitter* bce);
|
|
|
++
|
|
|
++ void dump(BytecodeEmitter* bce);
|
|
|
++
|
|
|
++ MOZ_MUST_USE bool enterLexical(BytecodeEmitter* bce, ScopeKind kind,
|
|
|
++ Handle<LexicalScope::Data*> bindings);
|
|
|
++ MOZ_MUST_USE bool enterNamedLambda(BytecodeEmitter* bce, FunctionBox* funbox);
|
|
|
++ MOZ_MUST_USE bool enterFunction(BytecodeEmitter* bce, FunctionBox* funbox);
|
|
|
++ MOZ_MUST_USE bool enterFunctionExtraBodyVar(BytecodeEmitter* bce, FunctionBox* funbox);
|
|
|
++ MOZ_MUST_USE bool enterParameterExpressionVar(BytecodeEmitter* bce);
|
|
|
++ MOZ_MUST_USE bool enterGlobal(BytecodeEmitter* bce, GlobalSharedContext* globalsc);
|
|
|
++ MOZ_MUST_USE bool enterEval(BytecodeEmitter* bce, EvalSharedContext* evalsc);
|
|
|
++ MOZ_MUST_USE bool enterModule(BytecodeEmitter* module, ModuleSharedContext* modulesc);
|
|
|
++ MOZ_MUST_USE bool enterWith(BytecodeEmitter* bce);
|
|
|
++ MOZ_MUST_USE bool deadZoneFrameSlots(BytecodeEmitter* bce);
|
|
|
++
|
|
|
++ MOZ_MUST_USE bool leave(BytecodeEmitter* bce, bool nonLocal = false);
|
|
|
++
|
|
|
++ uint32_t index() const {
|
|
|
++ MOZ_ASSERT(scopeIndex_ != ScopeNote::NoScopeIndex, "Did you forget to intern a Scope?");
|
|
|
++ return scopeIndex_;
|
|
|
++ }
|
|
|
++
|
|
|
++ uint32_t noteIndex() const {
|
|
|
++ return noteIndex_;
|
|
|
++ }
|
|
|
++
|
|
|
++ Scope* scope(const BytecodeEmitter* bce) const;
|
|
|
++
|
|
|
++ bool hasEnvironment() const {
|
|
|
++ return hasEnvironment_;
|
|
|
++ }
|
|
|
++
|
|
|
++ // The first frame slot used.
|
|
|
++ uint32_t frameSlotStart() const {
|
|
|
++ if (EmitterScope* inFrame = enclosingInFrame())
|
|
|
++ return inFrame->nextFrameSlot_;
|
|
|
++ return 0;
|
|
|
++ }
|
|
|
++
|
|
|
++ // The last frame slot used + 1.
|
|
|
++ uint32_t frameSlotEnd() const {
|
|
|
++ return nextFrameSlot_;
|
|
|
++ }
|
|
|
++
|
|
|
++ EmitterScope* enclosingInFrame() const {
|
|
|
++ return Nestable<EmitterScope>::enclosing();
|
|
|
++ }
|
|
|
++
|
|
|
++ NameLocation lookup(BytecodeEmitter* bce, JSAtom* name);
|
|
|
++
|
|
|
++ mozilla::Maybe<NameLocation> locationBoundInScope(JSAtom* name, EmitterScope* target);
|
|
|
++};
|
|
|
++
|
|
|
++} /* namespace frontend */
|
|
|
++} /* namespace js */
|
|
|
++
|
|
|
++#endif /* frontend_EmitterScope_h */
|
|
|
+diff --git a/js/src/moz.build b/js/src/moz.build
|
|
|
+--- a/js/src/moz.build
|
|
|
++++ b/js/src/moz.build
|
|
|
+@@ -206,16 +206,17 @@ UNIFIED_SOURCES += [
|
|
|
+ 'builtin/WeakMapObject.cpp',
|
|
|
+ 'builtin/WeakSetObject.cpp',
|
|
|
+ 'devtools/sharkctl.cpp',
|
|
|
+ 'ds/Bitmap.cpp',
|
|
|
+ 'ds/LifoAlloc.cpp',
|
|
|
+ 'ds/MemoryProtectionExceptionHandler.cpp',
|
|
|
+ 'frontend/BytecodeCompiler.cpp',
|
|
|
+ 'frontend/BytecodeEmitter.cpp',
|
|
|
++ 'frontend/EmitterScope.cpp',
|
|
|
+ 'frontend/FoldConstants.cpp',
|
|
|
+ 'frontend/NameFunctions.cpp',
|
|
|
+ 'frontend/ParseNode.cpp',
|
|
|
+ 'frontend/TDZCheckCache.cpp',
|
|
|
+ 'frontend/TokenStream.cpp',
|
|
|
+ 'gc/Allocator.cpp',
|
|
|
+ 'gc/AtomMarking.cpp',
|
|
|
+ 'gc/Barrier.cpp',
|