/* -*- 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/. */ /* * JS bytecode generation. */ #include "frontend/BytecodeEmitter.h" #include "mozilla/ArrayUtils.h" #include "mozilla/DebugOnly.h" #include "mozilla/FloatingPoint.h" #include "mozilla/Maybe.h" #include "mozilla/PodOperations.h" #include #include "jsapi.h" #include "jsatom.h" #include "jscntxt.h" #include "jsfun.h" #include "jsnum.h" #include "jsopcode.h" #include "jsscript.h" #include "jstypes.h" #include "jsutil.h" #include "frontend/Parser.h" #include "frontend/TokenStream.h" #include "vm/Debugger.h" #include "vm/GeneratorObject.h" #include "vm/Stack.h" #include "wasm/AsmJS.h" #include "jsatominlines.h" #include "jsobjinlines.h" #include "jsscriptinlines.h" #include "frontend/ParseNode-inl.h" #include "vm/EnvironmentObject-inl.h" #include "vm/NativeObject-inl.h" using namespace js; using namespace js::gc; using namespace js::frontend; using mozilla::AssertedCast; using mozilla::DebugOnly; using mozilla::Maybe; using mozilla::Nothing; using mozilla::NumberIsInt32; using mozilla::PodCopy; using mozilla::Some; class BreakableControl; class LabelControl; class LoopControl; class TryFinallyControl; static bool ParseNodeRequiresSpecialLineNumberNotes(ParseNode* pn) { return pn->getKind() == PNK_WHILE || pn->getKind() == PNK_FOR; } // A cache that tracks superfluous TDZ checks. // // Each basic block should have a TDZCheckCache in scope. Some NestableControl // subclasses contain a TDZCheckCache. class BytecodeEmitter::TDZCheckCache : public Nestable { PooledMapPtr cache_; MOZ_MUST_USE bool ensureCache(BytecodeEmitter* bce) { return cache_ || cache_.acquire(bce->cx); } public: explicit TDZCheckCache(BytecodeEmitter* bce) : Nestable(&bce->innermostTDZCheckCache), cache_(bce->cx->frontendCollectionPool()) { } Maybe needsTDZCheck(BytecodeEmitter* bce, JSAtom* name); MOZ_MUST_USE bool noteTDZCheck(BytecodeEmitter* bce, JSAtom* name, MaybeCheckTDZ check); }; class BytecodeEmitter::NestableControl : public Nestable { StatementKind kind_; // The innermost scope when this was pushed. EmitterScope* emitterScope_; protected: NestableControl(BytecodeEmitter* bce, StatementKind kind) : Nestable(&bce->innermostNestableControl), kind_(kind), emitterScope_(bce->innermostEmitterScope) { } public: using Nestable::enclosing; using Nestable::findNearest; StatementKind kind() const { return kind_; } EmitterScope* emitterScope() const { return emitterScope_; } template bool is() const; template T& as() { MOZ_ASSERT(this->is()); return static_cast(*this); } }; // Template specializations are disallowed in different namespaces; specialize // all the NestableControl subtypes up front. namespace js { namespace frontend { template <> bool BytecodeEmitter::NestableControl::is() const { return StatementKindIsUnlabeledBreakTarget(kind_) || kind_ == StatementKind::Label; } template <> bool BytecodeEmitter::NestableControl::is() const { return kind_ == StatementKind::Label; } template <> bool BytecodeEmitter::NestableControl::is() const { return StatementKindIsLoop(kind_); } template <> bool BytecodeEmitter::NestableControl::is() const { return kind_ == StatementKind::Try || kind_ == StatementKind::Finally; } } // namespace frontend } // namespace js class BreakableControl : public BytecodeEmitter::NestableControl { public: // Offset of the last break. JumpList breaks; BreakableControl(BytecodeEmitter* bce, StatementKind kind) : NestableControl(bce, kind) { MOZ_ASSERT(is()); } MOZ_MUST_USE bool patchBreaks(BytecodeEmitter* bce) { return bce->emitJumpTargetAndPatch(breaks); } }; class LabelControl : public BreakableControl { RootedAtom label_; // The code offset when this was pushed. Used for effectfulness checking. ptrdiff_t startOffset_; public: LabelControl(BytecodeEmitter* bce, JSAtom* label, ptrdiff_t startOffset) : BreakableControl(bce, StatementKind::Label), label_(bce->cx, label), startOffset_(startOffset) { } HandleAtom label() const { return label_; } ptrdiff_t startOffset() const { return startOffset_; } }; class LoopControl : public BreakableControl { // Loops' children are emitted in dominance order, so they can always // have a TDZCheckCache. BytecodeEmitter::TDZCheckCache tdzCache_; // Stack depth when this loop was pushed on the control stack. int32_t stackDepth_; // The loop nesting depth. Used as a hint to Ion. uint32_t loopDepth_; // Can we OSR into Ion from here? True unless there is non-loop state on the stack. bool canIonOsr_; public: // The target of continue statement jumps, e.g., the update portion of a // for(;;) loop. JumpTarget continueTarget; // Offset of the last continue in the loop. JumpList continues; LoopControl(BytecodeEmitter* bce, StatementKind loopKind) : BreakableControl(bce, loopKind), tdzCache_(bce), continueTarget({ -1 }) { MOZ_ASSERT(is()); LoopControl* enclosingLoop = findNearest(enclosing()); stackDepth_ = bce->stackDepth; loopDepth_ = enclosingLoop ? enclosingLoop->loopDepth_ + 1 : 1; int loopSlots; if (loopKind == StatementKind::Spread) loopSlots = 3; else if (loopKind == StatementKind::ForInLoop || loopKind == StatementKind::ForOfLoop) loopSlots = 2; else loopSlots = 0; MOZ_ASSERT(loopSlots <= stackDepth_); if (enclosingLoop) { canIonOsr_ = (enclosingLoop->canIonOsr_ && stackDepth_ == enclosingLoop->stackDepth_ + loopSlots); } else { canIonOsr_ = stackDepth_ == loopSlots; } } uint32_t loopDepth() const { return loopDepth_; } bool canIonOsr() const { return canIonOsr_; } MOZ_MUST_USE bool patchBreaksAndContinues(BytecodeEmitter* bce) { MOZ_ASSERT(continueTarget.offset != -1); if (!patchBreaks(bce)) return false; bce->patchJumpsToTarget(continues, continueTarget); return true; } }; class TryFinallyControl : public BytecodeEmitter::NestableControl { bool emittingSubroutine_; public: // The subroutine when emitting a finally block. JumpList gosubs; // Offset of the last catch guard, if any. JumpList guardJump; TryFinallyControl(BytecodeEmitter* bce, StatementKind kind) : NestableControl(bce, kind), emittingSubroutine_(false) { MOZ_ASSERT(is()); } void setEmittingSubroutine() { emittingSubroutine_ = true; } bool emittingSubroutine() const { return emittingSubroutine_; } }; static bool ScopeKindIsInBody(ScopeKind kind) { return kind == ScopeKind::Lexical || kind == ScopeKind::SimpleCatch || kind == ScopeKind::Catch || kind == ScopeKind::With || kind == ScopeKind::FunctionBodyVar || kind == ScopeKind::ParameterExpressionVar; } static inline void MarkAllBindingsClosedOver(LexicalScope::Data& data) { BindingName* names = data.names; 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 { // 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 nameCache_; // If this scope's cache does not include free names, such as the // global scope, the NameLocation to return. Maybe 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 MOZ_MUST_USE bool checkSlotLimits(BytecodeEmitter* bce, const BindingIter& bi) { if (bi.nextFrameSlot() >= LOCALNO_LIMIT || bi.nextEnvironmentSlot() >= ENVCOORD_SLOT_LIMIT) { return bce->reportError(nullptr, JSMSG_TOO_MANY_LOCALS); } 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) return bce->reportError(nullptr, JSMSG_TOO_DEEP, js_function_str); environmentChainLength_ = mozilla::AssertedCast(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->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 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)->innermostEmitterScope; } 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 MOZ_MUST_USE bool internScope(BytecodeEmitter* bce, ScopeCreator createScope); template 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(&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 bindings); MOZ_MUST_USE bool enterNamedLambda(BytecodeEmitter* bce, FunctionBox* funbox); MOZ_MUST_USE bool enterComprehensionFor(BytecodeEmitter* bce, Handle bindings); 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_; } uint32_t numFrameSlots() const { return frameSlotEnd() - frameSlotStart(); } EmitterScope* enclosingInFrame() const { return Nestable::enclosing(); } NameLocation lookup(BytecodeEmitter* bce, JSAtom* name) { if (Maybe loc = lookupInCache(bce, name)) return *loc; return searchAndCache(bce, name); } Maybe locationBoundInScope(BytecodeEmitter* bce, 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 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 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().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().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()) 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(); } if (hasEnv) { MOZ_ASSERT(hops < ENVCOORD_HOPS_LIMIT - 1); hops++; } } MOZ_CRASH("Malformed scope chain"); } NameLocation BytecodeEmitter::EmitterScope::searchAndCache(BytecodeEmitter* bce, JSAtom* name) { Maybe loc; uint8_t hops = hasEnvironment() ? 1 : 0; DebugOnly 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 BytecodeEmitter::EmitterScope::locationBoundInScope(BytecodeEmitter* bce, 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 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 bindings) { MOZ_ASSERT(kind != ScopeKind::NamedLambda && kind != ScopeKind::StrictNamedLambda); MOZ_ASSERT(this == bce->innermostEmitterScope); if (!ensureCache(bce)) return false; // Marks all names as closed over if the 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](ExclusiveContext* 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->innermostEmitterScope); 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](ExclusiveContext* 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::enterComprehensionFor(BytecodeEmitter* bce, Handle bindings) { if (!enterLexical(bce, ScopeKind::Lexical, bindings)) return false; // For comprehensions, initialize all lexical names up front to undefined // because they're now a dead feature and don't interact properly with // TDZ. auto nop = [](BytecodeEmitter*, const NameLocation&, bool) { return true; }; if (!bce->emit1(JSOP_UNDEFINED)) return false; RootedAtom name(bce->cx); for (BindingIter bi(*bindings, frameSlotStart(), /* isNamedLambda = */ false); bi; bi++) { name = bi.name(); if (!bce->emitInitializeName(name, nop)) return false; } if (!bce->emit1(JSOP_POP)) return false; return true; } bool BytecodeEmitter::EmitterScope::enterParameterExpressionVar(BytecodeEmitter* bce) { MOZ_ASSERT(this == bce->innermostEmitterScope); 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](ExclusiveContext* 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->innermostEmitterScope); // 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 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](ExclusiveContext* 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->innermostEmitterScope); // 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](ExclusiveContext* 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->innermostEmitterScope); 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 = [](ExclusiveContext* 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](ExclusiveContext* 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->innermostEmitterScope); 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](ExclusiveContext* 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()) fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var)); } return true; } bool BytecodeEmitter::EmitterScope::enterModule(BytecodeEmitter* bce, ModuleSharedContext* modulesc) { MOZ_ASSERT(this == bce->innermostEmitterScope); bce->setVarEmitterScope(this); if (!ensureCache(bce)) return false; // Resolve body-level bindings, if there are any. TDZCheckCache* tdzCache = bce->innermostTDZCheckCache; Maybe 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](ExclusiveContext* 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->innermostEmitterScope); if (!ensureCache(bce)) return false; // 'with' make all accesses dynamic and unanalyzable. fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); auto createScope = [](ExclusiveContext* 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->innermostEmitterScope); 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; } // 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; } Maybe BytecodeEmitter::TDZCheckCache::needsTDZCheck(BytecodeEmitter* bce, JSAtom* name) { if (!ensureCache(bce)) return Nothing(); CheckTDZMap::AddPtr p = cache_->lookupForAdd(name); if (p) return Some(p->value().wrapped); MaybeCheckTDZ rv = CheckTDZ; for (TDZCheckCache* it = enclosing(); it; it = it->enclosing()) { if (it->cache_) { if (CheckTDZMap::Ptr p2 = it->cache_->lookup(name)) { rv = p2->value(); break; } } } if (!cache_->add(p, name, rv)) { ReportOutOfMemory(bce->cx); return Nothing(); } return Some(rv); } bool BytecodeEmitter::TDZCheckCache::noteTDZCheck(BytecodeEmitter* bce, JSAtom* name, MaybeCheckTDZ check) { if (!ensureCache(bce)) return false; CheckTDZMap::AddPtr p = cache_->lookupForAdd(name); if (p) { MOZ_ASSERT(!check, "TDZ only needs to be checked once per binding per basic block."); p->value() = check; } else { if (!cache_->add(p, name, check)) return false; } return true; } BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, Parser* parser, SharedContext* sc, HandleScript script, Handle lazyScript, uint32_t lineNum, EmitterMode emitterMode) : sc(sc), cx(sc->context), parent(parent), script(cx, script), lazyScript(cx, lazyScript), prologue(cx, lineNum), main(cx, lineNum), current(&main), parser(parser), atomIndices(cx->frontendCollectionPool()), firstLine(lineNum), maxFixedSlots(0), maxStackDepth(0), stackDepth(0), arrayCompDepth(0), emitLevel(0), bodyScopeIndex(UINT32_MAX), varEmitterScope(nullptr), innermostNestableControl(nullptr), innermostEmitterScope(nullptr), innermostTDZCheckCache(nullptr), constList(cx), scopeList(cx), tryNoteList(cx), scopeNoteList(cx), yieldOffsetList(cx), typesetCount(0), hasSingletons(false), hasTryFinally(false), emittingRunOnceLambda(false), emitterMode(emitterMode), functionBodyEndPosSet(false) { MOZ_ASSERT_IF(emitterMode == LazyFunction, lazyScript); } BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, Parser* parser, SharedContext* sc, HandleScript script, Handle lazyScript, TokenPos bodyPosition, EmitterMode emitterMode) : BytecodeEmitter(parent, parser, sc, script, lazyScript, parser->tokenStream.srcCoords.lineNum(bodyPosition.begin), emitterMode) { setFunctionBodyEndPos(bodyPosition); } bool BytecodeEmitter::init() { return atomIndices.acquire(cx); } template bool */> BytecodeEmitter::NestableControl* BytecodeEmitter::findInnermostNestableControl(Predicate predicate) const { return NestableControl::findNearest(innermostNestableControl, predicate); } template T* BytecodeEmitter::findInnermostNestableControl() const { return NestableControl::findNearest(innermostNestableControl); } template bool */> T* BytecodeEmitter::findInnermostNestableControl(Predicate predicate) const { return NestableControl::findNearest(innermostNestableControl, predicate); } NameLocation BytecodeEmitter::lookupName(JSAtom* name) { return innermostEmitterScope->lookup(this, name); } Maybe BytecodeEmitter::locationOfNameBoundInScope(JSAtom* name, EmitterScope* target) { return innermostEmitterScope->locationBoundInScope(this, name, target); } Maybe BytecodeEmitter::locationOfNameBoundInFunctionScope(JSAtom* name, EmitterScope* source) { EmitterScope* funScope = source; while (!funScope->scope(this)->is()) funScope = funScope->enclosingInFrame(); return source->locationBoundInScope(this, name, funScope); } bool BytecodeEmitter::emitCheck(ptrdiff_t delta, ptrdiff_t* offset) { *offset = code().length(); // Start it off moderately large to avoid repeated resizings early on. // ~98% of cases fit within 1024 bytes. if (code().capacity() == 0 && !code().reserve(1024)) return false; if (!code().growBy(delta)) { ReportOutOfMemory(cx); return false; } return true; } void BytecodeEmitter::updateDepth(ptrdiff_t target) { jsbytecode* pc = code(target); int nuses = StackUses(nullptr, pc); int ndefs = StackDefs(nullptr, pc); stackDepth -= nuses; MOZ_ASSERT(stackDepth >= 0); stackDepth += ndefs; if ((uint32_t)stackDepth > maxStackDepth) maxStackDepth = stackDepth; } #ifdef DEBUG bool BytecodeEmitter::checkStrictOrSloppy(JSOp op) { if (IsCheckStrictOp(op) && !sc->strict()) return false; if (IsCheckSloppyOp(op) && sc->strict()) return false; return true; } #endif bool BytecodeEmitter::emit1(JSOp op) { MOZ_ASSERT(checkStrictOrSloppy(op)); ptrdiff_t offset; if (!emitCheck(1, &offset)) return false; jsbytecode* code = this->code(offset); code[0] = jsbytecode(op); updateDepth(offset); return true; } bool BytecodeEmitter::emit2(JSOp op, uint8_t op1) { MOZ_ASSERT(checkStrictOrSloppy(op)); ptrdiff_t offset; if (!emitCheck(2, &offset)) return false; jsbytecode* code = this->code(offset); code[0] = jsbytecode(op); code[1] = jsbytecode(op1); updateDepth(offset); return true; } bool BytecodeEmitter::emit3(JSOp op, jsbytecode op1, jsbytecode op2) { MOZ_ASSERT(checkStrictOrSloppy(op)); /* These should filter through emitVarOp. */ MOZ_ASSERT(!IsArgOp(op)); MOZ_ASSERT(!IsLocalOp(op)); ptrdiff_t offset; if (!emitCheck(3, &offset)) return false; jsbytecode* code = this->code(offset); code[0] = jsbytecode(op); code[1] = op1; code[2] = op2; updateDepth(offset); return true; } bool BytecodeEmitter::emitN(JSOp op, size_t extra, ptrdiff_t* offset) { MOZ_ASSERT(checkStrictOrSloppy(op)); ptrdiff_t length = 1 + ptrdiff_t(extra); ptrdiff_t off; if (!emitCheck(length, &off)) return false; jsbytecode* code = this->code(off); code[0] = jsbytecode(op); /* The remaining |extra| bytes are set by the caller */ /* * Don't updateDepth if op's use-count comes from the immediate * operand yet to be stored in the extra bytes after op. */ if (CodeSpec[op].nuses >= 0) updateDepth(off); if (offset) *offset = off; return true; } bool BytecodeEmitter::emitJumpTarget(JumpTarget* target) { ptrdiff_t off = offset(); // Alias consecutive jump targets. if (off == current->lastTarget.offset + ptrdiff_t(JSOP_JUMPTARGET_LENGTH)) { target->offset = current->lastTarget.offset; return true; } target->offset = off; current->lastTarget.offset = off; if (!emit1(JSOP_JUMPTARGET)) return false; return true; } void JumpList::push(jsbytecode* code, ptrdiff_t jumpOffset) { SET_JUMP_OFFSET(&code[jumpOffset], offset - jumpOffset); offset = jumpOffset; } void JumpList::patchAll(jsbytecode* code, JumpTarget target) { ptrdiff_t delta; for (ptrdiff_t jumpOffset = offset; jumpOffset != -1; jumpOffset += delta) { jsbytecode* pc = &code[jumpOffset]; MOZ_ASSERT(IsJumpOpcode(JSOp(*pc)) || JSOp(*pc) == JSOP_LABEL); delta = GET_JUMP_OFFSET(pc); MOZ_ASSERT(delta < 0); ptrdiff_t span = target.offset - jumpOffset; SET_JUMP_OFFSET(pc, span); } } bool BytecodeEmitter::emitJumpNoFallthrough(JSOp op, JumpList* jump) { ptrdiff_t offset; if (!emitCheck(5, &offset)) return false; jsbytecode* code = this->code(offset); code[0] = jsbytecode(op); MOZ_ASSERT(-1 <= jump->offset && jump->offset < offset); jump->push(this->code(0), offset); updateDepth(offset); return true; } bool BytecodeEmitter::emitJump(JSOp op, JumpList* jump) { if (!emitJumpNoFallthrough(op, jump)) return false; if (BytecodeFallsThrough(op)) { JumpTarget fallthrough; if (!emitJumpTarget(&fallthrough)) return false; } return true; } bool BytecodeEmitter::emitBackwardJump(JSOp op, JumpTarget target, JumpList* jump, JumpTarget* fallthrough) { if (!emitJumpNoFallthrough(op, jump)) return false; patchJumpsToTarget(*jump, target); // Unconditionally create a fallthrough for closing iterators, and as a // target for break statements. if (!emitJumpTarget(fallthrough)) return false; return true; } void BytecodeEmitter::patchJumpsToTarget(JumpList jump, JumpTarget target) { MOZ_ASSERT(-1 <= jump.offset && jump.offset <= offset()); MOZ_ASSERT(0 <= target.offset && target.offset <= offset()); MOZ_ASSERT_IF(jump.offset != -1 && target.offset + 4 <= offset(), BytecodeIsJumpTarget(JSOp(*code(target.offset)))); jump.patchAll(code(0), target); } bool BytecodeEmitter::emitJumpTargetAndPatch(JumpList jump) { if (jump.offset == -1) return true; JumpTarget target; if (!emitJumpTarget(&target)) return false; patchJumpsToTarget(jump, target); return true; } bool BytecodeEmitter::emitCall(JSOp op, uint16_t argc, ParseNode* pn) { if (pn && !updateSourceCoordNotes(pn->pn_pos.begin)) return false; return emit3(op, ARGC_HI(argc), ARGC_LO(argc)); } bool BytecodeEmitter::emitDupAt(unsigned slotFromTop) { MOZ_ASSERT(slotFromTop < unsigned(stackDepth)); if (slotFromTop >= JS_BIT(24)) { reportError(nullptr, JSMSG_TOO_MANY_LOCALS); return false; } ptrdiff_t off; if (!emitN(JSOP_DUPAT, 3, &off)) return false; jsbytecode* pc = code(off); SET_UINT24(pc, slotFromTop); return true; } bool BytecodeEmitter::emitCheckIsObj(CheckIsObjectKind kind) { return emit2(JSOP_CHECKISOBJ, uint8_t(kind)); } static inline unsigned LengthOfSetLine(unsigned line) { return 1 /* SN_SETLINE */ + (line > SN_4BYTE_OFFSET_MASK ? 4 : 1); } /* Updates line number notes, not column notes. */ bool BytecodeEmitter::updateLineNumberNotes(uint32_t offset) { TokenStream* ts = &parser->tokenStream; bool onThisLine; if (!ts->srcCoords.isOnThisLine(offset, currentLine(), &onThisLine)) return ts->reportError(JSMSG_OUT_OF_MEMORY); if (!onThisLine) { unsigned line = ts->srcCoords.lineNum(offset); unsigned delta = line - currentLine(); /* * Encode any change in the current source line number by using * either several SRC_NEWLINE notes or just one SRC_SETLINE note, * whichever consumes less space. * * NB: We handle backward line number deltas (possible with for * loops where the update part is emitted after the body, but its * line number is <= any line number in the body) here by letting * unsigned delta_ wrap to a very large number, which triggers a * SRC_SETLINE. */ current->currentLine = line; current->lastColumn = 0; if (delta >= LengthOfSetLine(line)) { if (!newSrcNote2(SRC_SETLINE, ptrdiff_t(line))) return false; } else { do { if (!newSrcNote(SRC_NEWLINE)) return false; } while (--delta != 0); } } return true; } /* Updates the line number and column number information in the source notes. */ bool BytecodeEmitter::updateSourceCoordNotes(uint32_t offset) { if (!updateLineNumberNotes(offset)) return false; uint32_t columnIndex = parser->tokenStream.srcCoords.columnIndex(offset); ptrdiff_t colspan = ptrdiff_t(columnIndex) - ptrdiff_t(current->lastColumn); if (colspan != 0) { // If the column span is so large that we can't store it, then just // discard this information. This can happen with minimized or otherwise // machine-generated code. Even gigantic column numbers are still // valuable if you have a source map to relate them to something real; // but it's better to fail soft here. if (!SN_REPRESENTABLE_COLSPAN(colspan)) return true; if (!newSrcNote2(SRC_COLSPAN, SN_COLSPAN_TO_OFFSET(colspan))) return false; current->lastColumn = columnIndex; } return true; } bool BytecodeEmitter::emitLoopHead(ParseNode* nextpn, JumpTarget* top) { if (nextpn) { /* * Try to give the JSOP_LOOPHEAD the same line number as the next * instruction. nextpn is often a block, in which case the next * instruction typically comes from the first statement inside. */ if (nextpn->isKind(PNK_LEXICALSCOPE)) nextpn = nextpn->scopeBody(); MOZ_ASSERT_IF(nextpn->isKind(PNK_STATEMENTLIST), nextpn->isArity(PN_LIST)); if (nextpn->isKind(PNK_STATEMENTLIST) && nextpn->pn_head) nextpn = nextpn->pn_head; if (!updateSourceCoordNotes(nextpn->pn_pos.begin)) return false; } *top = { offset() }; return emit1(JSOP_LOOPHEAD); } bool BytecodeEmitter::emitLoopEntry(ParseNode* nextpn, JumpList entryJump) { if (nextpn) { /* Update the line number, as for LOOPHEAD. */ if (nextpn->isKind(PNK_LEXICALSCOPE)) nextpn = nextpn->scopeBody(); MOZ_ASSERT_IF(nextpn->isKind(PNK_STATEMENTLIST), nextpn->isArity(PN_LIST)); if (nextpn->isKind(PNK_STATEMENTLIST) && nextpn->pn_head) nextpn = nextpn->pn_head; if (!updateSourceCoordNotes(nextpn->pn_pos.begin)) return false; } JumpTarget entry{ offset() }; patchJumpsToTarget(entryJump, entry); LoopControl& loopInfo = innermostNestableControl->as(); MOZ_ASSERT(loopInfo.loopDepth() > 0); uint8_t loopDepthAndFlags = PackLoopEntryDepthHintAndFlags(loopInfo.loopDepth(), loopInfo.canIonOsr()); return emit2(JSOP_LOOPENTRY, loopDepthAndFlags); } void BytecodeEmitter::checkTypeSet(JSOp op) { if (CodeSpec[op].format & JOF_TYPESET) { if (typesetCount < UINT16_MAX) typesetCount++; } } bool BytecodeEmitter::emitUint16Operand(JSOp op, uint32_t operand) { MOZ_ASSERT(operand <= UINT16_MAX); if (!emit3(op, UINT16_HI(operand), UINT16_LO(operand))) return false; checkTypeSet(op); return true; } bool BytecodeEmitter::emitUint32Operand(JSOp op, uint32_t operand) { ptrdiff_t off; if (!emitN(op, 4, &off)) return false; SET_UINT32(code(off), operand); checkTypeSet(op); return true; } bool BytecodeEmitter::flushPops(int* npops) { MOZ_ASSERT(*npops != 0); if (!emitUint16Operand(JSOP_POPN, *npops)) return false; *npops = 0; return true; } namespace { class NonLocalExitControl { BytecodeEmitter* bce_; const uint32_t savedScopeNoteIndex_; const int savedDepth_; uint32_t openScopeNoteIndex_; NonLocalExitControl(const NonLocalExitControl&) = delete; MOZ_MUST_USE bool leaveScope(BytecodeEmitter::EmitterScope* scope); public: explicit NonLocalExitControl(BytecodeEmitter* bce) : bce_(bce), savedScopeNoteIndex_(bce->scopeNoteList.length()), savedDepth_(bce->stackDepth), openScopeNoteIndex_(bce->innermostEmitterScope->noteIndex()) { } ~NonLocalExitControl() { for (uint32_t n = savedScopeNoteIndex_; n < bce_->scopeNoteList.length(); n++) bce_->scopeNoteList.recordEnd(n, bce_->offset(), bce_->inPrologue()); bce_->stackDepth = savedDepth_; } MOZ_MUST_USE bool prepareForNonLocalJump(BytecodeEmitter::NestableControl* target); MOZ_MUST_USE bool prepareForNonLocalJumpToOutermost() { return prepareForNonLocalJump(nullptr); } }; bool NonLocalExitControl::leaveScope(BytecodeEmitter::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; if (es->enclosingInFrame()) enclosingScopeIndex = es->enclosingInFrame()->index(); if (!bce_->scopeNoteList.append(enclosingScopeIndex, bce_->offset(), bce_->inPrologue(), openScopeNoteIndex_)) return false; openScopeNoteIndex_ = bce_->scopeNoteList.length() - 1; return true; } /* * 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; auto flushPops = [&npops](BytecodeEmitter* bce) { if (npops && !bce->flushPops(&npops)) return false; return true; }; // Walk the nestable control stack and patch jumps. for (NestableControl* control = bce_->innermostNestableControl; control != target; control = control->enclosing()) { // Walk the scope stack and leave the scopes we entered. Leaving a scope // may emit administrative ops like JSOP_POPLEXICALENV but never anything // that manipulates the stack. for (; es != control->emitterScope(); es = es->enclosingInFrame()) { if (!leaveScope(es)) return false; } switch (control->kind()) { case StatementKind::Finally: { TryFinallyControl& finallyControl = control->as(); if (finallyControl.emittingSubroutine()) { /* * There's a [exception or hole, retsub pc-index] pair and the * possible return value on the stack that we need to pop. */ npops += 3; } else { if (!flushPops(bce_)) return false; if (!bce_->emitJump(JSOP_GOSUB, &finallyControl.gosubs)) return false; } break; } case StatementKind::ForOfLoop: npops += 2; break; case StatementKind::ForInLoop: /* The iterator and the current value are on the stack. */ npops += 1; if (!flushPops(bce_)) return false; if (!bce_->emit1(JSOP_ENDITER)) return false; break; default: break; } } EmitterScope* targetEmitterScope = target ? target->emitterScope() : bce_->varEmitterScope; for (; es != targetEmitterScope; es = es->enclosingInFrame()) { if (!leaveScope(es)) return false; } return flushPops(bce_); } } // anonymous namespace bool BytecodeEmitter::emitGoto(NestableControl* target, JumpList* jumplist, SrcNoteType noteType) { NonLocalExitControl nle(this); if (!nle.prepareForNonLocalJump(target)) return false; if (noteType != SRC_NULL) { if (!newSrcNote(noteType)) return false; } return emitJump(JSOP_GOTO, jumplist); } Scope* BytecodeEmitter::innermostScope() const { return innermostEmitterScope->scope(this); } bool BytecodeEmitter::emitIndex32(JSOp op, uint32_t index) { MOZ_ASSERT(checkStrictOrSloppy(op)); const size_t len = 1 + UINT32_INDEX_LEN; MOZ_ASSERT(len == size_t(CodeSpec[op].length)); ptrdiff_t offset; if (!emitCheck(len, &offset)) return false; jsbytecode* code = this->code(offset); code[0] = jsbytecode(op); SET_UINT32_INDEX(code, index); checkTypeSet(op); updateDepth(offset); return true; } bool BytecodeEmitter::emitIndexOp(JSOp op, uint32_t index) { MOZ_ASSERT(checkStrictOrSloppy(op)); const size_t len = CodeSpec[op].length; MOZ_ASSERT(len >= 1 + UINT32_INDEX_LEN); ptrdiff_t offset; if (!emitCheck(len, &offset)) return false; jsbytecode* code = this->code(offset); code[0] = jsbytecode(op); SET_UINT32_INDEX(code, index); checkTypeSet(op); updateDepth(offset); return true; } bool BytecodeEmitter::emitAtomOp(JSAtom* atom, JSOp op) { MOZ_ASSERT(atom); MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM); // .generator lookups should be emitted as JSOP_GETALIASEDVAR instead of // JSOP_GETNAME etc, to bypass |with| objects on the scope chain. // It's safe to emit .this lookups though because |with| objects skip // those. MOZ_ASSERT_IF(op == JSOP_GETNAME || op == JSOP_GETGNAME, atom != cx->names().dotGenerator); if (op == JSOP_GETPROP && atom == cx->names().length) { /* Specialize length accesses for the interpreter. */ op = JSOP_LENGTH; } uint32_t index; if (!makeAtomIndex(atom, &index)) return false; return emitIndexOp(op, index); } bool BytecodeEmitter::emitAtomOp(ParseNode* pn, JSOp op) { MOZ_ASSERT(pn->pn_atom != nullptr); return emitAtomOp(pn->pn_atom, op); } bool BytecodeEmitter::emitInternedScopeOp(uint32_t index, JSOp op) { MOZ_ASSERT(JOF_OPTYPE(op) == JOF_SCOPE); MOZ_ASSERT(index < scopeList.length()); return emitIndex32(op, index); } bool BytecodeEmitter::emitInternedObjectOp(uint32_t index, JSOp op) { MOZ_ASSERT(JOF_OPTYPE(op) == JOF_OBJECT); MOZ_ASSERT(index < objectList.length); return emitIndex32(op, index); } bool BytecodeEmitter::emitObjectOp(ObjectBox* objbox, JSOp op) { return emitInternedObjectOp(objectList.add(objbox), op); } bool BytecodeEmitter::emitObjectPairOp(ObjectBox* objbox1, ObjectBox* objbox2, JSOp op) { uint32_t index = objectList.add(objbox1); objectList.add(objbox2); return emitInternedObjectOp(index, op); } bool BytecodeEmitter::emitRegExp(uint32_t index) { return emitIndex32(JSOP_REGEXP, index); } bool BytecodeEmitter::emitLocalOp(JSOp op, uint32_t slot) { MOZ_ASSERT(JOF_OPTYPE(op) != JOF_ENVCOORD); MOZ_ASSERT(IsLocalOp(op)); ptrdiff_t off; if (!emitN(op, LOCALNO_LEN, &off)) return false; SET_LOCALNO(code(off), slot); return true; } bool BytecodeEmitter::emitArgOp(JSOp op, uint16_t slot) { MOZ_ASSERT(IsArgOp(op)); ptrdiff_t off; if (!emitN(op, ARGNO_LEN, &off)) return false; SET_ARGNO(code(off), slot); return true; } bool BytecodeEmitter::emitEnvCoordOp(JSOp op, EnvironmentCoordinate ec) { MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ENVCOORD); unsigned n = ENVCOORD_HOPS_LEN + ENVCOORD_SLOT_LEN; MOZ_ASSERT(int(n) + 1 /* op */ == CodeSpec[op].length); ptrdiff_t off; if (!emitN(op, n, &off)) return false; jsbytecode* pc = code(off); SET_ENVCOORD_HOPS(pc, ec.hops()); pc += ENVCOORD_HOPS_LEN; SET_ENVCOORD_SLOT(pc, ec.slot()); pc += ENVCOORD_SLOT_LEN; checkTypeSet(op); return true; } static JSOp GetIncDecInfo(ParseNodeKind kind, bool* post) { MOZ_ASSERT(kind == PNK_POSTINCREMENT || kind == PNK_PREINCREMENT || kind == PNK_POSTDECREMENT || kind == PNK_PREDECREMENT); *post = kind == PNK_POSTINCREMENT || kind == PNK_POSTDECREMENT; return (kind == PNK_POSTINCREMENT || kind == PNK_PREINCREMENT) ? JSOP_ADD : JSOP_SUB; } JSOp BytecodeEmitter::strictifySetNameOp(JSOp op) { switch (op) { case JSOP_SETNAME: if (sc->strict()) op = JSOP_STRICTSETNAME; break; case JSOP_SETGNAME: if (sc->strict()) op = JSOP_STRICTSETGNAME; break; default:; } return op; } bool BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) { JS_CHECK_RECURSION(cx, return false); restart: switch (pn->getKind()) { // Trivial cases with no side effects. case PNK_NOP: case PNK_STRING: case PNK_TEMPLATE_STRING: case PNK_REGEXP: case PNK_TRUE: case PNK_FALSE: case PNK_NULL: case PNK_ELISION: case PNK_GENERATOR: case PNK_NUMBER: case PNK_OBJECT_PROPERTY_NAME: MOZ_ASSERT(pn->isArity(PN_NULLARY)); *answer = false; return true; // |this| can throw in derived class constructors, including nested arrow // functions or eval. case PNK_THIS: MOZ_ASSERT(pn->isArity(PN_UNARY)); *answer = sc->needsThisTDZChecks(); return true; // Trivial binary nodes with more token pos holders. case PNK_NEWTARGET: MOZ_ASSERT(pn->isArity(PN_BINARY)); MOZ_ASSERT(pn->pn_left->isKind(PNK_POSHOLDER)); MOZ_ASSERT(pn->pn_right->isKind(PNK_POSHOLDER)); *answer = false; return true; case PNK_BREAK: case PNK_CONTINUE: case PNK_DEBUGGER: MOZ_ASSERT(pn->isArity(PN_NULLARY)); *answer = true; return true; // Watch out for getters! case PNK_DOT: MOZ_ASSERT(pn->isArity(PN_NAME)); *answer = true; return true; // Unary cases with side effects only if the child has them. case PNK_TYPEOFEXPR: case PNK_VOID: case PNK_NOT: MOZ_ASSERT(pn->isArity(PN_UNARY)); return checkSideEffects(pn->pn_kid, answer); // Even if the name expression is effect-free, performing ToPropertyKey on // it might not be effect-free: // // RegExp.prototype.toString = () => { throw 42; }; // ({ [/regex/]: 0 }); // ToPropertyKey(/regex/) throws 42 // // function Q() { // ({ [new.target]: 0 }); // } // Q.toString = () => { throw 17; }; // new Q; // new.target will be Q, ToPropertyKey(Q) throws 17 case PNK_COMPUTED_NAME: MOZ_ASSERT(pn->isArity(PN_UNARY)); *answer = true; return true; // Looking up or evaluating the associated name could throw. case PNK_TYPEOFNAME: MOZ_ASSERT(pn->isArity(PN_UNARY)); *answer = true; return true; // These unary cases have side effects on the enclosing object/array, // sure. But that's not the question this function answers: it's // whether the operation may have a side effect on something *other* than // the result of the overall operation in which it's embedded. The // answer to that is no, for an object literal having a mutated prototype // and an array comprehension containing no other effectful operations // only produce a value, without affecting anything else. case PNK_MUTATEPROTO: case PNK_ARRAYPUSH: MOZ_ASSERT(pn->isArity(PN_UNARY)); return checkSideEffects(pn->pn_kid, answer); // Unary cases with obvious side effects. case PNK_PREINCREMENT: case PNK_POSTINCREMENT: case PNK_PREDECREMENT: case PNK_POSTDECREMENT: case PNK_THROW: MOZ_ASSERT(pn->isArity(PN_UNARY)); *answer = true; return true; // These might invoke valueOf/toString, even with a subexpression without // side effects! Consider |+{ valueOf: null, toString: null }|. case PNK_BITNOT: case PNK_POS: case PNK_NEG: MOZ_ASSERT(pn->isArity(PN_UNARY)); *answer = true; return true; // This invokes the (user-controllable) iterator protocol. case PNK_SPREAD: MOZ_ASSERT(pn->isArity(PN_UNARY)); *answer = true; return true; case PNK_YIELD_STAR: case PNK_YIELD: case PNK_AWAIT: MOZ_ASSERT(pn->isArity(PN_BINARY)); *answer = true; return true; // Deletion generally has side effects, even if isolated cases have none. case PNK_DELETENAME: case PNK_DELETEPROP: case PNK_DELETEELEM: MOZ_ASSERT(pn->isArity(PN_UNARY)); *answer = true; return true; // Deletion of a non-Reference expression has side effects only through // evaluating the expression. case PNK_DELETEEXPR: { MOZ_ASSERT(pn->isArity(PN_UNARY)); ParseNode* expr = pn->pn_kid; return checkSideEffects(expr, answer); } case PNK_SEMI: MOZ_ASSERT(pn->isArity(PN_UNARY)); if (ParseNode* expr = pn->pn_kid) return checkSideEffects(expr, answer); *answer = false; return true; // Binary cases with obvious side effects. case PNK_ASSIGN: case PNK_ADDASSIGN: case PNK_SUBASSIGN: case PNK_BITORASSIGN: case PNK_BITXORASSIGN: case PNK_BITANDASSIGN: case PNK_LSHASSIGN: case PNK_RSHASSIGN: case PNK_URSHASSIGN: case PNK_MULASSIGN: case PNK_DIVASSIGN: case PNK_MODASSIGN: case PNK_POWASSIGN: case PNK_SETTHIS: MOZ_ASSERT(pn->isArity(PN_BINARY)); *answer = true; return true; case PNK_STATEMENTLIST: case PNK_CATCHLIST: // Strict equality operations and logical operators are well-behaved and // perform no conversions. case PNK_OR: case PNK_AND: case PNK_STRICTEQ: case PNK_STRICTNE: // Any subexpression of a comma expression could be effectful. case PNK_COMMA: MOZ_ASSERT(pn->pn_count > 0); MOZ_FALLTHROUGH; // Subcomponents of a literal may be effectful. case PNK_ARRAY: case PNK_OBJECT: MOZ_ASSERT(pn->isArity(PN_LIST)); for (ParseNode* item = pn->pn_head; item; item = item->pn_next) { if (!checkSideEffects(item, answer)) return false; if (*answer) return true; } return true; // Most other binary operations (parsed as lists in SpiderMonkey) may // perform conversions triggering side effects. Math operations perform // ToNumber and may fail invoking invalid user-defined toString/valueOf: // |5 < { toString: null }|. |instanceof| throws if provided a // non-object constructor: |null instanceof null|. |in| throws if given // a non-object RHS: |5 in null|. case PNK_BITOR: case PNK_BITXOR: case PNK_BITAND: case PNK_EQ: case PNK_NE: case PNK_LT: case PNK_LE: case PNK_GT: case PNK_GE: case PNK_INSTANCEOF: case PNK_IN: case PNK_LSH: case PNK_RSH: case PNK_URSH: case PNK_ADD: case PNK_SUB: case PNK_STAR: case PNK_DIV: case PNK_MOD: case PNK_POW: MOZ_ASSERT(pn->isArity(PN_LIST)); MOZ_ASSERT(pn->pn_count >= 2); *answer = true; return true; case PNK_COLON: case PNK_CASE: MOZ_ASSERT(pn->isArity(PN_BINARY)); if (!checkSideEffects(pn->pn_left, answer)) return false; if (*answer) return true; return checkSideEffects(pn->pn_right, answer); // More getters. case PNK_ELEM: MOZ_ASSERT(pn->isArity(PN_BINARY)); *answer = true; return true; // These affect visible names in this code, or in other code. case PNK_IMPORT: case PNK_EXPORT_FROM: case PNK_EXPORT_DEFAULT: MOZ_ASSERT(pn->isArity(PN_BINARY)); *answer = true; return true; // Likewise. case PNK_EXPORT: MOZ_ASSERT(pn->isArity(PN_UNARY)); *answer = true; return true; // Every part of a loop might be effect-free, but looping infinitely *is* // an effect. (Language lawyer trivia: C++ says threads can be assumed // to exit or have side effects, C++14 [intro.multithread]p27, so a C++ // implementation's equivalent of the below could set |*answer = false;| // if all loop sub-nodes set |*answer = false|!) case PNK_DOWHILE: case PNK_WHILE: case PNK_FOR: case PNK_COMPREHENSIONFOR: MOZ_ASSERT(pn->isArity(PN_BINARY)); *answer = true; return true; // Declarations affect the name set of the relevant scope. case PNK_VAR: case PNK_CONST: case PNK_LET: MOZ_ASSERT(pn->isArity(PN_LIST)); *answer = true; return true; case PNK_IF: case PNK_CONDITIONAL: MOZ_ASSERT(pn->isArity(PN_TERNARY)); if (!checkSideEffects(pn->pn_kid1, answer)) return false; if (*answer) return true; if (!checkSideEffects(pn->pn_kid2, answer)) return false; if (*answer) return true; if ((pn = pn->pn_kid3)) goto restart; return true; // Function calls can invoke non-local code. case PNK_NEW: case PNK_CALL: case PNK_TAGGED_TEMPLATE: case PNK_SUPERCALL: MOZ_ASSERT(pn->isArity(PN_LIST)); *answer = true; return true; // Classes typically introduce names. Even if no name is introduced, // the heritage and/or class body (through computed property names) // usually have effects. case PNK_CLASS: MOZ_ASSERT(pn->isArity(PN_TERNARY)); *answer = true; return true; // |with| calls |ToObject| on its expression and so throws if that value // is null/undefined. case PNK_WITH: MOZ_ASSERT(pn->isArity(PN_BINARY)); *answer = true; return true; case PNK_RETURN: MOZ_ASSERT(pn->isArity(PN_BINARY)); *answer = true; return true; case PNK_NAME: MOZ_ASSERT(pn->isArity(PN_NAME)); *answer = true; return true; // Shorthands could trigger getters: the |x| in the object literal in // |with ({ get x() { throw 42; } }) ({ x });|, for example, triggers // one. (Of course, it isn't necessary to use |with| for a shorthand to // trigger a getter.) case PNK_SHORTHAND: MOZ_ASSERT(pn->isArity(PN_BINARY)); *answer = true; return true; case PNK_FUNCTION: MOZ_ASSERT(pn->isArity(PN_CODE)); /* * A named function, contrary to ES3, is no longer effectful, because * we bind its name lexically (using JSOP_CALLEE) instead of creating * an Object instance and binding a readonly, permanent property in it * (the object and binding can be detected and hijacked or captured). * This is a bug fix to ES3; it is fixed in ES3.1 drafts. */ *answer = false; return true; case PNK_MODULE: *answer = false; return true; // Generator expressions have no side effects on their own. case PNK_GENEXP: MOZ_ASSERT(pn->isArity(PN_LIST)); *answer = false; return true; case PNK_TRY: MOZ_ASSERT(pn->isArity(PN_TERNARY)); if (!checkSideEffects(pn->pn_kid1, answer)) return false; if (*answer) return true; if (ParseNode* catchList = pn->pn_kid2) { MOZ_ASSERT(catchList->isKind(PNK_CATCHLIST)); if (!checkSideEffects(catchList, answer)) return false; if (*answer) return true; } if (ParseNode* finallyBlock = pn->pn_kid3) { if (!checkSideEffects(finallyBlock, answer)) return false; } return true; case PNK_CATCH: MOZ_ASSERT(pn->isArity(PN_TERNARY)); if (!checkSideEffects(pn->pn_kid1, answer)) return false; if (*answer) return true; if (ParseNode* cond = pn->pn_kid2) { if (!checkSideEffects(cond, answer)) return false; if (*answer) return true; } return checkSideEffects(pn->pn_kid3, answer); case PNK_SWITCH: MOZ_ASSERT(pn->isArity(PN_BINARY)); if (!checkSideEffects(pn->pn_left, answer)) return false; return *answer || checkSideEffects(pn->pn_right, answer); case PNK_LABEL: MOZ_ASSERT(pn->isArity(PN_NAME)); return checkSideEffects(pn->expr(), answer); case PNK_LEXICALSCOPE: MOZ_ASSERT(pn->isArity(PN_SCOPE)); return checkSideEffects(pn->scopeBody(), answer); // We could methodically check every interpolated expression, but it's // probably not worth the trouble. Treat template strings as effect-free // only if they don't contain any substitutions. case PNK_TEMPLATE_STRING_LIST: MOZ_ASSERT(pn->isArity(PN_LIST)); MOZ_ASSERT(pn->pn_count > 0); MOZ_ASSERT((pn->pn_count % 2) == 1, "template strings must alternate template and substitution " "parts"); *answer = pn->pn_count > 1; return true; case PNK_ARRAYCOMP: MOZ_ASSERT(pn->isArity(PN_LIST)); MOZ_ASSERT(pn->pn_count == 1); return checkSideEffects(pn->pn_head, answer); // This should be unreachable but is left as-is for now. case PNK_PARAMSBODY: *answer = true; return true; case PNK_FORIN: // by PNK_FOR/PNK_COMPREHENSIONFOR case PNK_FOROF: // by PNK_FOR/PNK_COMPREHENSIONFOR case PNK_FORHEAD: // by PNK_FOR/PNK_COMPREHENSIONFOR case PNK_CLASSMETHOD: // by PNK_CLASS case PNK_CLASSNAMES: // by PNK_CLASS case PNK_CLASSMETHODLIST: // by PNK_CLASS case PNK_IMPORT_SPEC_LIST: // by PNK_IMPORT case PNK_IMPORT_SPEC: // by PNK_IMPORT case PNK_EXPORT_BATCH_SPEC:// by PNK_EXPORT case PNK_EXPORT_SPEC_LIST: // by PNK_EXPORT case PNK_EXPORT_SPEC: // by PNK_EXPORT case PNK_CALLSITEOBJ: // by PNK_TAGGED_TEMPLATE case PNK_POSHOLDER: // by PNK_NEWTARGET case PNK_SUPERBASE: // by PNK_ELEM and others MOZ_CRASH("handled by parent nodes"); case PNK_LIMIT: // invalid sentinel value MOZ_CRASH("invalid node kind"); } MOZ_CRASH("invalid, unenumerated ParseNodeKind value encountered in " "BytecodeEmitter::checkSideEffects"); } bool BytecodeEmitter::isInLoop() { return findInnermostNestableControl(); } bool BytecodeEmitter::checkSingletonContext() { if (!script->treatAsRunOnce() || sc->isFunctionBox() || isInLoop()) return false; hasSingletons = true; return true; } bool BytecodeEmitter::checkRunOnceContext() { return checkSingletonContext() || (!isInLoop() && isRunOnceLambda()); } bool BytecodeEmitter::needsImplicitThis() { // Short-circuit if there is an enclosing 'with' scope. if (sc->inWith()) return true; // Otherwise see if the current point is under a 'with'. for (EmitterScope* es = innermostEmitterScope; es; es = es->enclosingInFrame()) { if (es->scope(this)->kind() == ScopeKind::With) return true; } return false; } bool BytecodeEmitter::maybeSetDisplayURL() { if (tokenStream()->hasDisplayURL()) { if (!parser->ss->setDisplayURL(cx, tokenStream()->displayURL())) return false; } return true; } bool BytecodeEmitter::maybeSetSourceMap() { if (tokenStream()->hasSourceMapURL()) { MOZ_ASSERT(!parser->ss->hasSourceMapURL()); if (!parser->ss->setSourceMapURL(cx, tokenStream()->sourceMapURL())) return false; } /* * Source map URLs passed as a compile option (usually via a HTTP source map * header) override any source map urls passed as comment pragmas. */ if (parser->options().sourceMapURL()) { // Warn about the replacement, but use the new one. if (parser->ss->hasSourceMapURL()) { if(!parser->report(ParseWarning, false, nullptr, JSMSG_ALREADY_HAS_PRAGMA, parser->ss->filename(), "//# sourceMappingURL")) return false; } if (!parser->ss->setSourceMapURL(cx, parser->options().sourceMapURL())) return false; } return true; } void BytecodeEmitter::tellDebuggerAboutCompiledScript(ExclusiveContext* cx) { // Note: when parsing off thread the resulting scripts need to be handed to // the debugger after rejoining to the main thread. if (!cx->isJSContext()) return; // Lazy scripts are never top level (despite always being invoked with a // nullptr parent), and so the hook should never be fired. if (emitterMode != LazyFunction && !parent) { Debugger::onNewScript(cx->asJSContext(), script); } } inline TokenStream* BytecodeEmitter::tokenStream() { return &parser->tokenStream; } bool BytecodeEmitter::reportError(ParseNode* pn, unsigned errorNumber, ...) { TokenPos pos = pn ? pn->pn_pos : tokenStream()->currentToken().pos; va_list args; va_start(args, errorNumber); bool result = tokenStream()->reportCompileErrorNumberVA(pos.begin, JSREPORT_ERROR, errorNumber, args); va_end(args); return result; } bool BytecodeEmitter::reportStrictWarning(ParseNode* pn, unsigned errorNumber, ...) { TokenPos pos = pn ? pn->pn_pos : tokenStream()->currentToken().pos; va_list args; va_start(args, errorNumber); bool result = tokenStream()->reportStrictWarningErrorNumberVA(pos.begin, errorNumber, args); va_end(args); return result; } bool BytecodeEmitter::reportStrictModeError(ParseNode* pn, unsigned errorNumber, ...) { TokenPos pos = pn ? pn->pn_pos : tokenStream()->currentToken().pos; va_list args; va_start(args, errorNumber); bool result = tokenStream()->reportStrictModeErrorNumberVA(pos.begin, sc->strict(), errorNumber, args); va_end(args); return result; } bool BytecodeEmitter::emitNewInit(JSProtoKey key) { const size_t len = 1 + UINT32_INDEX_LEN; ptrdiff_t offset; if (!emitCheck(len, &offset)) return false; jsbytecode* code = this->code(offset); code[0] = JSOP_NEWINIT; code[1] = jsbytecode(key); code[2] = 0; code[3] = 0; code[4] = 0; checkTypeSet(JSOP_NEWINIT); updateDepth(offset); return true; } bool BytecodeEmitter::iteratorResultShape(unsigned* shape) { // No need to do any guessing for the object kind, since we know exactly how // many properties we plan to have. gc::AllocKind kind = gc::GetGCObjectKind(2); RootedPlainObject obj(cx, NewBuiltinClassInstance(cx, kind, TenuredObject)); if (!obj) return false; Rooted value_id(cx, AtomToId(cx->names().value)); Rooted done_id(cx, AtomToId(cx->names().done)); if (!NativeDefineProperty(cx, obj, value_id, UndefinedHandleValue, nullptr, nullptr, JSPROP_ENUMERATE)) { return false; } if (!NativeDefineProperty(cx, obj, done_id, UndefinedHandleValue, nullptr, nullptr, JSPROP_ENUMERATE)) { return false; } ObjectBox* objbox = parser->newObjectBox(obj); if (!objbox) return false; *shape = objectList.add(objbox); return true; } bool BytecodeEmitter::emitPrepareIteratorResult() { unsigned shape; if (!iteratorResultShape(&shape)) return false; return emitIndex32(JSOP_NEWOBJECT, shape); } bool BytecodeEmitter::emitFinishIteratorResult(bool done) { uint32_t value_id; if (!makeAtomIndex(cx->names().value, &value_id)) return false; uint32_t done_id; if (!makeAtomIndex(cx->names().done, &done_id)) return false; if (!emitIndex32(JSOP_INITPROP, value_id)) return false; if (!emit1(done ? JSOP_TRUE : JSOP_FALSE)) return false; if (!emitIndex32(JSOP_INITPROP, done_id)) return false; return true; } bool BytecodeEmitter::emitGetNameAtLocation(JSAtom* name, const NameLocation& loc, bool callContext) { switch (loc.kind()) { case NameLocation::Kind::Dynamic: if (!emitAtomOp(name, JSOP_GETNAME)) return false; break; case NameLocation::Kind::Global: if (!emitAtomOp(name, JSOP_GETGNAME)) return false; break; case NameLocation::Kind::Intrinsic: if (!emitAtomOp(name, JSOP_GETINTRINSIC)) return false; break; case NameLocation::Kind::NamedLambdaCallee: if (!emit1(JSOP_CALLEE)) return false; break; case NameLocation::Kind::Import: if (!emitAtomOp(name, JSOP_GETIMPORT)) return false; break; case NameLocation::Kind::ArgumentSlot: if (!emitArgOp(JSOP_GETARG, loc.argumentSlot())) return false; break; case NameLocation::Kind::FrameSlot: if (loc.isLexical()) { if (!emitTDZCheckIfNeeded(name, loc)) return false; } if (!emitLocalOp(JSOP_GETLOCAL, loc.frameSlot())) return false; break; case NameLocation::Kind::EnvironmentCoordinate: if (loc.isLexical()) { if (!emitTDZCheckIfNeeded(name, loc)) return false; } if (!emitEnvCoordOp(JSOP_GETALIASEDVAR, loc.environmentCoordinate())) return false; break; case NameLocation::Kind::DynamicAnnexBVar: MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization"); } // Need to provide |this| value for call. if (callContext) { switch (loc.kind()) { case NameLocation::Kind::Dynamic: { JSOp thisOp = needsImplicitThis() ? JSOP_IMPLICITTHIS : JSOP_GIMPLICITTHIS; if (!emitAtomOp(name, thisOp)) return false; break; } case NameLocation::Kind::Global: if (!emitAtomOp(name, JSOP_GIMPLICITTHIS)) return false; break; case NameLocation::Kind::Intrinsic: case NameLocation::Kind::NamedLambdaCallee: case NameLocation::Kind::Import: case NameLocation::Kind::ArgumentSlot: case NameLocation::Kind::FrameSlot: case NameLocation::Kind::EnvironmentCoordinate: if (!emit1(JSOP_UNDEFINED)) return false; break; case NameLocation::Kind::DynamicAnnexBVar: MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization"); } } return true; } bool BytecodeEmitter::emitGetName(ParseNode* pn, bool callContext) { return emitGetName(pn->name(), callContext); } template bool BytecodeEmitter::emitSetOrInitializeNameAtLocation(HandleAtom name, const NameLocation& loc, RHSEmitter emitRhs, bool initialize) { bool emittedBindOp = false; switch (loc.kind()) { case NameLocation::Kind::Dynamic: case NameLocation::Kind::Import: case NameLocation::Kind::DynamicAnnexBVar: { uint32_t atomIndex; if (!makeAtomIndex(name, &atomIndex)) return false; if (loc.kind() == NameLocation::Kind::DynamicAnnexBVar) { // Annex B vars always go on the nearest variable environment, // even if lexical environments in between contain same-named // bindings. if (!emit1(JSOP_BINDVAR)) return false; } else { if (!emitIndexOp(JSOP_BINDNAME, atomIndex)) return false; } emittedBindOp = true; if (!emitRhs(this, loc, emittedBindOp)) return false; if (!emitIndexOp(strictifySetNameOp(JSOP_SETNAME), atomIndex)) return false; break; } case NameLocation::Kind::Global: { JSOp op; uint32_t atomIndex; if (!makeAtomIndex(name, &atomIndex)) return false; if (loc.isLexical() && initialize) { // INITGLEXICAL always gets the global lexical scope. It doesn't // need a BINDGNAME. MOZ_ASSERT(innermostScope()->is()); op = JSOP_INITGLEXICAL; } else { if (!emitIndexOp(JSOP_BINDGNAME, atomIndex)) return false; emittedBindOp = true; op = strictifySetNameOp(JSOP_SETGNAME); } if (!emitRhs(this, loc, emittedBindOp)) return false; if (!emitIndexOp(op, atomIndex)) return false; break; } case NameLocation::Kind::Intrinsic: if (!emitRhs(this, loc, emittedBindOp)) return false; if (!emitAtomOp(name, JSOP_SETINTRINSIC)) return false; break; case NameLocation::Kind::NamedLambdaCallee: if (!emitRhs(this, loc, emittedBindOp)) return false; // Assigning to the named lambda is a no-op in sloppy mode but // throws in strict mode. if (sc->strict() && !emit1(JSOP_THROWSETCALLEE)) return false; break; case NameLocation::Kind::ArgumentSlot: { // If we assign to a positional formal parameter and the arguments // object is unmapped (strict mode or function with // default/rest/destructing args), parameters do not alias // arguments[i], and to make the arguments object reflect initial // parameter values prior to any mutation we create it eagerly // whenever parameters are (or might, in the case of calls to eval) // assigned. FunctionBox* funbox = sc->asFunctionBox(); if (funbox->argumentsHasLocalBinding() && !funbox->hasMappedArgsObj()) funbox->setDefinitelyNeedsArgsObj(); if (!emitRhs(this, loc, emittedBindOp)) return false; if (!emitArgOp(JSOP_SETARG, loc.argumentSlot())) return false; break; } case NameLocation::Kind::FrameSlot: { JSOp op = JSOP_SETLOCAL; if (!emitRhs(this, loc, emittedBindOp)) return false; if (loc.isLexical()) { if (initialize) { op = JSOP_INITLEXICAL; } else { if (loc.isConst()) op = JSOP_THROWSETCONST; if (!emitTDZCheckIfNeeded(name, loc)) return false; } } if (!emitLocalOp(op, loc.frameSlot())) return false; if (op == JSOP_INITLEXICAL) { if (!innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ)) return false; } break; } case NameLocation::Kind::EnvironmentCoordinate: { JSOp op = JSOP_SETALIASEDVAR; if (!emitRhs(this, loc, emittedBindOp)) return false; if (loc.isLexical()) { if (initialize) { op = JSOP_INITALIASEDLEXICAL; } else { if (loc.isConst()) op = JSOP_THROWSETALIASEDCONST; if (!emitTDZCheckIfNeeded(name, loc)) return false; } } if (loc.bindingKind() == BindingKind::NamedLambdaCallee) { // Assigning to the named lambda is a no-op in sloppy mode and throws // in strict mode. op = JSOP_THROWSETALIASEDCONST; if (sc->strict() && !emitEnvCoordOp(op, loc.environmentCoordinate())) return false; } else { if (!emitEnvCoordOp(op, loc.environmentCoordinate())) return false; } if (op == JSOP_INITALIASEDLEXICAL) { if (!innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ)) return false; } break; } } return true; } bool BytecodeEmitter::emitTDZCheckIfNeeded(JSAtom* name, const NameLocation& loc) { // Dynamic accesses have TDZ checks built into their VM code and should // never emit explicit TDZ checks. MOZ_ASSERT(loc.hasKnownSlot()); MOZ_ASSERT(loc.isLexical()); Maybe check = innermostTDZCheckCache->needsTDZCheck(this, name); if (!check) return false; // We've already emitted a check in this basic block. if (*check == DontCheckTDZ) return true; if (loc.kind() == NameLocation::Kind::FrameSlot) { if (!emitLocalOp(JSOP_CHECKLEXICAL, loc.frameSlot())) return false; } else { if (!emitEnvCoordOp(JSOP_CHECKALIASEDLEXICAL, loc.environmentCoordinate())) return false; } return innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ); } bool BytecodeEmitter::emitPropLHS(ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_DOT)); MOZ_ASSERT(!pn->as().isSuper()); ParseNode* pn2 = pn->pn_expr; /* * If the object operand is also a dotted property reference, reverse the * list linked via pn_expr temporarily so we can iterate over it from the * bottom up (reversing again as we go), to avoid excessive recursion. */ if (pn2->isKind(PNK_DOT) && !pn2->as().isSuper()) { ParseNode* pndot = pn2; ParseNode* pnup = nullptr; ParseNode* pndown; for (;;) { /* Reverse pndot->pn_expr to point up, not down. */ pndown = pndot->pn_expr; pndot->pn_expr = pnup; if (!pndown->isKind(PNK_DOT) || pndown->as().isSuper()) break; pnup = pndot; pndot = pndown; } /* pndown is a primary expression, not a dotted property reference. */ if (!emitTree(pndown)) return false; do { /* Walk back up the list, emitting annotated name ops. */ if (!emitAtomOp(pndot, JSOP_GETPROP)) return false; /* Reverse the pn_expr link again. */ pnup = pndot->pn_expr; pndot->pn_expr = pndown; pndown = pndot; } while ((pndot = pnup) != nullptr); return true; } // The non-optimized case. return emitTree(pn2); } bool BytecodeEmitter::emitSuperPropLHS(ParseNode* superBase, bool isCall) { if (!emitGetThisForSuperBase(superBase)) return false; if (isCall && !emit1(JSOP_DUP)) return false; if (!emit1(JSOP_SUPERBASE)) return false; return true; } bool BytecodeEmitter::emitPropOp(ParseNode* pn, JSOp op) { MOZ_ASSERT(pn->isArity(PN_NAME)); if (!emitPropLHS(pn)) return false; if (op == JSOP_CALLPROP && !emit1(JSOP_DUP)) return false; if (!emitAtomOp(pn, op)) return false; if (op == JSOP_CALLPROP && !emit1(JSOP_SWAP)) return false; return true; } bool BytecodeEmitter::emitSuperPropOp(ParseNode* pn, JSOp op, bool isCall) { ParseNode* base = &pn->as().expression(); if (!emitSuperPropLHS(base, isCall)) return false; if (!emitAtomOp(pn, op)) return false; if (isCall && !emit1(JSOP_SWAP)) return false; return true; } bool BytecodeEmitter::emitPropIncDec(ParseNode* pn) { MOZ_ASSERT(pn->pn_kid->isKind(PNK_DOT)); bool post; bool isSuper = pn->pn_kid->as().isSuper(); JSOp binop = GetIncDecInfo(pn->getKind(), &post); if (isSuper) { ParseNode* base = &pn->pn_kid->as().expression(); if (!emitSuperPropLHS(base)) // THIS OBJ return false; if (!emit1(JSOP_DUP2)) // THIS OBJ THIS OBJ return false; } else { if (!emitPropLHS(pn->pn_kid)) // OBJ return false; if (!emit1(JSOP_DUP)) // OBJ OBJ return false; } if (!emitAtomOp(pn->pn_kid, isSuper? JSOP_GETPROP_SUPER : JSOP_GETPROP)) // OBJ V return false; if (!emit1(JSOP_POS)) // OBJ N return false; if (post && !emit1(JSOP_DUP)) // OBJ N? N return false; if (!emit1(JSOP_ONE)) // OBJ N? N 1 return false; if (!emit1(binop)) // OBJ N? N+1 return false; if (post) { if (!emit2(JSOP_PICK, 2 + isSuper)) // N? N+1 OBJ return false; if (!emit1(JSOP_SWAP)) // N? OBJ N+1 return false; if (isSuper) { if (!emit2(JSOP_PICK, 3)) // N THIS N+1 OBJ return false; if (!emit1(JSOP_SWAP)) // N THIS OBJ N+1 return false; } } JSOp setOp = isSuper ? sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER : sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP; if (!emitAtomOp(pn->pn_kid, setOp)) // N? N+1 return false; if (post && !emit1(JSOP_POP)) // RESULT return false; return true; } bool BytecodeEmitter::emitNameIncDec(ParseNode* pn) { MOZ_ASSERT(pn->pn_kid->isKind(PNK_NAME)); bool post; JSOp binop = GetIncDecInfo(pn->getKind(), &post); auto emitRhs = [pn, post, binop](BytecodeEmitter* bce, const NameLocation& loc, bool emittedBindOp) { JSAtom* name = pn->pn_kid->name(); if (!bce->emitGetNameAtLocation(name, loc, false)) // SCOPE? V return false; if (!bce->emit1(JSOP_POS)) // SCOPE? N return false; if (post && !bce->emit1(JSOP_DUP)) // SCOPE? N? N return false; if (!bce->emit1(JSOP_ONE)) // SCOPE? N? N 1 return false; if (!bce->emit1(binop)) // SCOPE? N? N+1 return false; if (post && emittedBindOp) { if (!bce->emit2(JSOP_PICK, 2)) // N? N+1 SCOPE? return false; if (!bce->emit1(JSOP_SWAP)) // N? SCOPE? N+1 return false; } return true; }; if (!emitSetName(pn->pn_kid, emitRhs)) return false; if (post && !emit1(JSOP_POP)) return false; return true; } bool BytecodeEmitter::emitElemOperands(ParseNode* pn, EmitElemOption opts) { MOZ_ASSERT(pn->isArity(PN_BINARY)); if (!emitTree(pn->pn_left)) return false; if (opts == EmitElemOption::IncDec) { if (!emit1(JSOP_CHECKOBJCOERCIBLE)) return false; } else if (opts == EmitElemOption::Call) { if (!emit1(JSOP_DUP)) return false; } if (!emitTree(pn->pn_right)) return false; if (opts == EmitElemOption::Set) { if (!emit2(JSOP_PICK, 2)) return false; } else if (opts == EmitElemOption::IncDec || opts == EmitElemOption::CompoundAssign) { if (!emit1(JSOP_TOID)) return false; } return true; } bool BytecodeEmitter::emitSuperElemOperands(ParseNode* pn, EmitElemOption opts) { MOZ_ASSERT(pn->isKind(PNK_ELEM) && pn->as().isSuper()); // The ordering here is somewhat screwy. We need to evaluate the propval // first, by spec. Do a little dance to not emit more than one JSOP_THIS. // Since JSOP_THIS might throw in derived class constructors, we cannot // just push it earlier as the receiver. We have to swap it down instead. if (!emitTree(pn->pn_right)) return false; // We need to convert the key to an object id first, so that we do not do // it inside both the GETELEM and the SETELEM. if (opts == EmitElemOption::IncDec || opts == EmitElemOption::CompoundAssign) { if (!emit1(JSOP_TOID)) return false; } if (!emitGetThisForSuperBase(pn->pn_left)) return false; if (opts == EmitElemOption::Call) { if (!emit1(JSOP_SWAP)) return false; // We need another |this| on top, also if (!emitDupAt(1)) return false; } if (!emit1(JSOP_SUPERBASE)) return false; if (opts == EmitElemOption::Set && !emit2(JSOP_PICK, 3)) return false; return true; } bool BytecodeEmitter::emitElemOpBase(JSOp op) { if (!emit1(op)) return false; checkTypeSet(op); return true; } bool BytecodeEmitter::emitElemOp(ParseNode* pn, JSOp op) { EmitElemOption opts = EmitElemOption::Get; if (op == JSOP_CALLELEM) opts = EmitElemOption::Call; else if (op == JSOP_SETELEM || op == JSOP_STRICTSETELEM) opts = EmitElemOption::Set; return emitElemOperands(pn, opts) && emitElemOpBase(op); } bool BytecodeEmitter::emitSuperElemOp(ParseNode* pn, JSOp op, bool isCall) { EmitElemOption opts = EmitElemOption::Get; if (isCall) opts = EmitElemOption::Call; else if (op == JSOP_SETELEM_SUPER || op == JSOP_STRICTSETELEM_SUPER) opts = EmitElemOption::Set; if (!emitSuperElemOperands(pn, opts)) return false; if (!emitElemOpBase(op)) return false; if (isCall && !emit1(JSOP_SWAP)) return false; return true; } bool BytecodeEmitter::emitElemIncDec(ParseNode* pn) { MOZ_ASSERT(pn->pn_kid->isKind(PNK_ELEM)); bool isSuper = pn->pn_kid->as().isSuper(); // We need to convert the key to an object id first, so that we do not do // it inside both the GETELEM and the SETELEM. This is done by // emit(Super)ElemOperands. if (isSuper) { if (!emitSuperElemOperands(pn->pn_kid, EmitElemOption::IncDec)) return false; } else { if (!emitElemOperands(pn->pn_kid, EmitElemOption::IncDec)) return false; } bool post; JSOp binop = GetIncDecInfo(pn->getKind(), &post); JSOp getOp; if (isSuper) { // There's no such thing as JSOP_DUP3, so we have to be creative. // Note that pushing things again is no fewer JSOps. if (!emitDupAt(2)) // KEY THIS OBJ KEY return false; if (!emitDupAt(2)) // KEY THIS OBJ KEY THIS return false; if (!emitDupAt(2)) // KEY THIS OBJ KEY THIS OBJ return false; getOp = JSOP_GETELEM_SUPER; } else { // OBJ KEY if (!emit1(JSOP_DUP2)) // OBJ KEY OBJ KEY return false; getOp = JSOP_GETELEM; } if (!emitElemOpBase(getOp)) // OBJ KEY V return false; if (!emit1(JSOP_POS)) // OBJ KEY N return false; if (post && !emit1(JSOP_DUP)) // OBJ KEY N? N return false; if (!emit1(JSOP_ONE)) // OBJ KEY N? N 1 return false; if (!emit1(binop)) // OBJ KEY N? N+1 return false; if (post) { if (isSuper) { // We have one more value to rotate around, because of |this| // on the stack if (!emit2(JSOP_PICK, 4)) return false; } if (!emit2(JSOP_PICK, 3 + isSuper)) // KEY N N+1 OBJ return false; if (!emit2(JSOP_PICK, 3 + isSuper)) // N N+1 OBJ KEY return false; if (!emit2(JSOP_PICK, 2 + isSuper)) // N OBJ KEY N+1 return false; } JSOp setOp = isSuper ? (sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER) : (sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM); if (!emitElemOpBase(setOp)) // N? N+1 return false; if (post && !emit1(JSOP_POP)) // RESULT return false; return true; } bool BytecodeEmitter::emitCallIncDec(ParseNode* incDec) { MOZ_ASSERT(incDec->isKind(PNK_PREINCREMENT) || incDec->isKind(PNK_POSTINCREMENT) || incDec->isKind(PNK_PREDECREMENT) || incDec->isKind(PNK_POSTDECREMENT)); MOZ_ASSERT(incDec->pn_kid->isKind(PNK_CALL)); ParseNode* call = incDec->pn_kid; if (!emitTree(call)) // CALLRESULT return false; if (!emit1(JSOP_POS)) // N return false; // The increment/decrement has no side effects, so proceed to throw for // invalid assignment target. return emitUint16Operand(JSOP_THROWMSG, JSMSG_BAD_LEFTSIDE_OF_ASS); } bool BytecodeEmitter::emitNumberOp(double dval) { int32_t ival; if (NumberIsInt32(dval, &ival)) { if (ival == 0) return emit1(JSOP_ZERO); if (ival == 1) return emit1(JSOP_ONE); if ((int)(int8_t)ival == ival) return emit2(JSOP_INT8, uint8_t(int8_t(ival))); uint32_t u = uint32_t(ival); if (u < JS_BIT(16)) { if (!emitUint16Operand(JSOP_UINT16, u)) return false; } else if (u < JS_BIT(24)) { ptrdiff_t off; if (!emitN(JSOP_UINT24, 3, &off)) return false; SET_UINT24(code(off), u); } else { ptrdiff_t off; if (!emitN(JSOP_INT32, 4, &off)) return false; SET_INT32(code(off), ival); } return true; } if (!constList.append(DoubleValue(dval))) return false; return emitIndex32(JSOP_DOUBLE, constList.length() - 1); } /* * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. * LLVM is deciding to inline this function which uses a lot of stack space * into emitTree which is recursive and uses relatively little stack space. */ MOZ_NEVER_INLINE bool BytecodeEmitter::emitSwitch(ParseNode* pn) { ParseNode* cases = pn->pn_right; MOZ_ASSERT(cases->isKind(PNK_LEXICALSCOPE) || cases->isKind(PNK_STATEMENTLIST)); // Emit code for the discriminant. if (!emitTree(pn->pn_left)) return false; // Enter the scope before pushing the switch BreakableControl since all // breaks are under this scope. Maybe tdzCache; Maybe emitterScope; if (cases->isKind(PNK_LEXICALSCOPE)) { if (!cases->isEmptyScope()) { tdzCache.emplace(this); emitterScope.emplace(this); if (!emitterScope->enterLexical(this, ScopeKind::Lexical, cases->scopeBindings())) return false; } // Advance |cases| to refer to the switch case list. cases = cases->scopeBody(); // A switch statement may contain hoisted functions inside its // cases. The PNX_FUNCDEFS flag is propagated from the STATEMENTLIST // bodies of the cases to the case list. if (cases->pn_xflags & PNX_FUNCDEFS) { MOZ_ASSERT(emitterScope); for (ParseNode* caseNode = cases->pn_head; caseNode; caseNode = caseNode->pn_next) { if (caseNode->pn_right->pn_xflags & PNX_FUNCDEFS) { if (!emitHoistedFunctionsInList(caseNode->pn_right)) return false; } } } } // After entering the scope, push the switch control. BreakableControl controlInfo(this, StatementKind::Switch); ptrdiff_t top = offset(); // Switch bytecodes run from here till end of final case. uint32_t caseCount = cases->pn_count; if (caseCount > JS_BIT(16)) { parser->tokenStream.reportError(JSMSG_TOO_MANY_CASES); return false; } // Try for most optimal, fall back if not dense ints. JSOp switchOp = JSOP_TABLESWITCH; uint32_t tableLength = 0; int32_t low, high; bool hasDefault = false; CaseClause* firstCase = cases->pn_head ? &cases->pn_head->as() : nullptr; if (caseCount == 0 || (caseCount == 1 && (hasDefault = firstCase->isDefault()))) { caseCount = 0; low = 0; high = -1; } else { Vector intmap; int32_t intmapBitLength = 0; low = JSVAL_INT_MAX; high = JSVAL_INT_MIN; for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) { if (caseNode->isDefault()) { hasDefault = true; caseCount--; // one of the "cases" was the default continue; } if (switchOp == JSOP_CONDSWITCH) continue; MOZ_ASSERT(switchOp == JSOP_TABLESWITCH); ParseNode* caseValue = caseNode->caseExpression(); if (caseValue->getKind() != PNK_NUMBER) { switchOp = JSOP_CONDSWITCH; continue; } int32_t i; if (!NumberIsInt32(caseValue->pn_dval, &i)) { switchOp = JSOP_CONDSWITCH; continue; } if (unsigned(i + int(JS_BIT(15))) >= unsigned(JS_BIT(16))) { switchOp = JSOP_CONDSWITCH; continue; } if (i < low) low = i; if (i > high) high = i; // Check for duplicates, which require a JSOP_CONDSWITCH. // We bias i by 65536 if it's negative, and hope that's a rare // case (because it requires a malloc'd bitmap). if (i < 0) i += JS_BIT(16); if (i >= intmapBitLength) { size_t newLength = (i / JS_BITMAP_NBITS) + 1; if (!intmap.resize(newLength)) return false; intmapBitLength = newLength * JS_BITMAP_NBITS; } if (JS_TEST_BIT(intmap, i)) { switchOp = JSOP_CONDSWITCH; continue; } JS_SET_BIT(intmap, i); } // Compute table length and select condswitch instead if overlarge or // more than half-sparse. if (switchOp == JSOP_TABLESWITCH) { tableLength = uint32_t(high - low + 1); if (tableLength >= JS_BIT(16) || tableLength > 2 * caseCount) switchOp = JSOP_CONDSWITCH; } } // The note has one or two offsets: first tells total switch code length; // second (if condswitch) tells offset to first JSOP_CASE. unsigned noteIndex; size_t switchSize; if (switchOp == JSOP_CONDSWITCH) { // 0 bytes of immediate for unoptimized switch. switchSize = 0; if (!newSrcNote3(SRC_CONDSWITCH, 0, 0, ¬eIndex)) return false; } else { MOZ_ASSERT(switchOp == JSOP_TABLESWITCH); // 3 offsets (len, low, high) before the table, 1 per entry. switchSize = size_t(JUMP_OFFSET_LEN * (3 + tableLength)); if (!newSrcNote2(SRC_TABLESWITCH, 0, ¬eIndex)) return false; } // Emit switchOp followed by switchSize bytes of jump or lookup table. MOZ_ASSERT(top == offset()); if (!emitN(switchOp, switchSize)) return false; Vector table; JumpList condSwitchDefaultOff; if (switchOp == JSOP_CONDSWITCH) { unsigned caseNoteIndex; bool beforeCases = true; ptrdiff_t lastCaseOffset = -1; // The case conditions need their own TDZ cache since they might not // all execute. TDZCheckCache tdzCache(this); // Emit code for evaluating cases and jumping to case statements. for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) { ParseNode* caseValue = caseNode->caseExpression(); // If the expression is a literal, suppress line number emission so // that debugging works more naturally. if (caseValue) { if (!emitTree(caseValue, caseValue->isLiteral() ? SUPPRESS_LINENOTE : EMIT_LINENOTE)) { return false; } } if (!beforeCases) { // prevCase is the previous JSOP_CASE's bytecode offset. if (!setSrcNoteOffset(caseNoteIndex, 0, offset() - lastCaseOffset)) return false; } if (!caseValue) { // This is the default clause. continue; } if (!newSrcNote2(SRC_NEXTCASE, 0, &caseNoteIndex)) return false; // The case clauses are produced before any of the case body. The // JumpList is saved on the parsed tree, then later restored and // patched when generating the cases body. JumpList caseJump; if (!emitJump(JSOP_CASE, &caseJump)) return false; caseNode->setOffset(caseJump.offset); lastCaseOffset = caseJump.offset; if (beforeCases) { // Switch note's second offset is to first JSOP_CASE. unsigned noteCount = notes().length(); if (!setSrcNoteOffset(noteIndex, 1, lastCaseOffset - top)) return false; unsigned noteCountDelta = notes().length() - noteCount; if (noteCountDelta != 0) caseNoteIndex += noteCountDelta; beforeCases = false; } } // If we didn't have an explicit default (which could fall in between // cases, preventing us from fusing this setSrcNoteOffset with the call // in the loop above), link the last case to the implicit default for // the benefit of IonBuilder. if (!hasDefault && !beforeCases && !setSrcNoteOffset(caseNoteIndex, 0, offset() - lastCaseOffset)) { return false; } // Emit default even if no explicit default statement. if (!emitJump(JSOP_DEFAULT, &condSwitchDefaultOff)) return false; } else { MOZ_ASSERT(switchOp == JSOP_TABLESWITCH); // skip default offset. jsbytecode* pc = code(top + JUMP_OFFSET_LEN); // Fill in switch bounds, which we know fit in 16-bit offsets. SET_JUMP_OFFSET(pc, low); pc += JUMP_OFFSET_LEN; SET_JUMP_OFFSET(pc, high); pc += JUMP_OFFSET_LEN; if (tableLength != 0) { if (!table.growBy(tableLength)) return false; for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) { if (ParseNode* caseValue = caseNode->caseExpression()) { MOZ_ASSERT(caseValue->isKind(PNK_NUMBER)); int32_t i = int32_t(caseValue->pn_dval); MOZ_ASSERT(double(i) == caseValue->pn_dval); i -= low; MOZ_ASSERT(uint32_t(i) < tableLength); MOZ_ASSERT(!table[i]); table[i] = caseNode; } } } } JumpTarget defaultOffset{ -1 }; // Emit code for each case's statements. for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) { if (switchOp == JSOP_CONDSWITCH && !caseNode->isDefault()) { // The case offset got saved in the caseNode structure after // emitting the JSOP_CASE jump instruction above. JumpList caseCond; caseCond.offset = caseNode->offset(); if (!emitJumpTargetAndPatch(caseCond)) return false; } JumpTarget here; if (!emitJumpTarget(&here)) return false; if (caseNode->isDefault()) defaultOffset = here; // If this is emitted as a TABLESWITCH, we'll need to know this case's // offset later when emitting the table. Store it in the node's // pn_offset (giving the field a different meaning vs. how we used it // on the immediately preceding line of code). caseNode->setOffset(here.offset); TDZCheckCache tdzCache(this); if (!emitTree(caseNode->statementList())) return false; } if (!hasDefault) { // If no default case, offset for default is to end of switch. if (!emitJumpTarget(&defaultOffset)) return false; } MOZ_ASSERT(defaultOffset.offset != -1); // Set the default offset (to end of switch if no default). jsbytecode* pc; if (switchOp == JSOP_CONDSWITCH) { pc = nullptr; patchJumpsToTarget(condSwitchDefaultOff, defaultOffset); } else { MOZ_ASSERT(switchOp == JSOP_TABLESWITCH); pc = code(top); SET_JUMP_OFFSET(pc, defaultOffset.offset - top); pc += JUMP_OFFSET_LEN; } // Set the SRC_SWITCH note's offset operand to tell end of switch. if (!setSrcNoteOffset(noteIndex, 0, lastNonJumpTargetOffset() - top)) return false; if (switchOp == JSOP_TABLESWITCH) { // Skip over the already-initialized switch bounds. pc += 2 * JUMP_OFFSET_LEN; // Fill in the jump table, if there is one. for (uint32_t i = 0; i < tableLength; i++) { CaseClause* caseNode = table[i]; ptrdiff_t off = caseNode ? caseNode->offset() - top : 0; SET_JUMP_OFFSET(pc, off); pc += JUMP_OFFSET_LEN; } } // Patch breaks before leaving the scope, as all breaks are under the // lexical scope if it exists. if (!controlInfo.patchBreaks(this)) return false; if (emitterScope && !emitterScope->leave(this)) return false; return true; } bool BytecodeEmitter::isRunOnceLambda() { // The run once lambda flags set by the parser are approximate, and we look // at properties of the function itself before deciding to emit a function // as a run once lambda. if (!(parent && parent->emittingRunOnceLambda) && (emitterMode != LazyFunction || !lazyScript->treatAsRunOnce())) { return false; } FunctionBox* funbox = sc->asFunctionBox(); return !funbox->argumentsHasLocalBinding() && !funbox->isGenerator() && !funbox->function()->explicitName(); } bool BytecodeEmitter::emitYieldOp(JSOp op) { if (op == JSOP_FINALYIELDRVAL) return emit1(JSOP_FINALYIELDRVAL); MOZ_ASSERT(op == JSOP_INITIALYIELD || op == JSOP_YIELD); ptrdiff_t off; if (!emitN(op, 3, &off)) return false; uint32_t yieldIndex = yieldOffsetList.length(); if (yieldIndex >= JS_BIT(24)) { reportError(nullptr, JSMSG_TOO_MANY_YIELDS); return false; } SET_UINT24(code(off), yieldIndex); if (!yieldOffsetList.append(offset())) return false; return emit1(JSOP_DEBUGAFTERYIELD); } bool BytecodeEmitter::emitSetThis(ParseNode* pn) { // PNK_SETTHIS is used to update |this| after a super() call in a derived // class constructor. MOZ_ASSERT(pn->isKind(PNK_SETTHIS)); MOZ_ASSERT(pn->pn_left->isKind(PNK_NAME)); RootedAtom name(cx, pn->pn_left->name()); auto emitRhs = [&name, pn](BytecodeEmitter* bce, const NameLocation&, bool) { // Emit the new |this| value. if (!bce->emitTree(pn->pn_right)) return false; // Get the original |this| and throw if we already initialized // it. Do *not* use the NameLocation argument, as that's the special // lexical location below to deal with super() semantics. if (!bce->emitGetName(name)) return false; if (!bce->emit1(JSOP_CHECKTHISREINIT)) return false; if (!bce->emit1(JSOP_POP)) return false; return true; }; // The 'this' binding is not lexical, but due to super() semantics this // initialization needs to be treated as a lexical one. NameLocation loc = lookupName(name); NameLocation lexicalLoc; if (loc.kind() == NameLocation::Kind::FrameSlot) { lexicalLoc = NameLocation::FrameSlot(BindingKind::Let, loc.frameSlot()); } else if (loc.kind() == NameLocation::Kind::EnvironmentCoordinate) { EnvironmentCoordinate coord = loc.environmentCoordinate(); uint8_t hops = AssertedCast(coord.hops()); lexicalLoc = NameLocation::EnvironmentCoordinate(BindingKind::Let, hops, coord.slot()); } else { MOZ_ASSERT(loc.kind() == NameLocation::Kind::Dynamic); lexicalLoc = loc; } return emitSetOrInitializeNameAtLocation(name, lexicalLoc, emitRhs, true); } bool BytecodeEmitter::emitScript(ParseNode* body) { TDZCheckCache tdzCache(this); EmitterScope emitterScope(this); if (sc->isGlobalContext()) { switchToPrologue(); if (!emitterScope.enterGlobal(this, sc->asGlobalContext())) return false; switchToMain(); } else if (sc->isEvalContext()) { switchToPrologue(); if (!emitterScope.enterEval(this, sc->asEvalContext())) return false; switchToMain(); } else { MOZ_ASSERT(sc->isModuleContext()); if (!emitterScope.enterModule(this, sc->asModuleContext())) return false; } setFunctionBodyEndPos(body->pn_pos); if (sc->isEvalContext() && !sc->strict() && body->isKind(PNK_LEXICALSCOPE) && !body->isEmptyScope()) { // Sloppy eval scripts may need to emit DEFFUNs in the prologue. If there is // an immediately enclosed lexical scope, we need to enter the lexical // scope in the prologue for the DEFFUNs to pick up the right // environment chain. EmitterScope lexicalEmitterScope(this); switchToPrologue(); if (!lexicalEmitterScope.enterLexical(this, ScopeKind::Lexical, body->scopeBindings())) return false; switchToMain(); if (!emitLexicalScopeBody(body->scopeBody())) return false; if (!lexicalEmitterScope.leave(this)) return false; } else { if (!emitTree(body)) return false; } if (!emit1(JSOP_RETRVAL)) return false; if (!emitterScope.leave(this)) return false; if (!JSScript::fullyInitFromEmitter(cx, script, this)) return false; // URL and source map information must be set before firing // Debugger::onNewScript. if (!maybeSetDisplayURL() || !maybeSetSourceMap()) return false; tellDebuggerAboutCompiledScript(cx); return true; } bool BytecodeEmitter::emitFunctionScript(ParseNode* body) { FunctionBox* funbox = sc->asFunctionBox(); // The ordering of these EmitterScopes is important. The named lambda // scope needs to enclose the function scope needs to enclose the extra // var scope. Maybe namedLambdaEmitterScope; if (funbox->namedLambdaBindings()) { namedLambdaEmitterScope.emplace(this); if (!namedLambdaEmitterScope->enterNamedLambda(this, funbox)) return false; } /* * Emit a prologue for run-once scripts which will deoptimize JIT code * if the script ends up running multiple times via foo.caller related * shenanigans. * * Also mark the script so that initializers created within it may be * given more precise types. */ if (isRunOnceLambda()) { script->setTreatAsRunOnce(); MOZ_ASSERT(!script->hasRunOnce()); switchToPrologue(); if (!emit1(JSOP_RUNONCE)) return false; switchToMain(); } setFunctionBodyEndPos(body->pn_pos); if (!emitTree(body)) return false; if (!updateSourceCoordNotes(body->pn_pos.end)) return false; // Always end the script with a JSOP_RETRVAL. Some other parts of the // codebase depend on this opcode, // e.g. InterpreterRegs::setToEndOfScript. if (!emit1(JSOP_RETRVAL)) return false; if (namedLambdaEmitterScope) { if (!namedLambdaEmitterScope->leave(this)) return false; namedLambdaEmitterScope.reset(); } if (!JSScript::fullyInitFromEmitter(cx, script, this)) return false; // URL and source map information must be set before firing // Debugger::onNewScript. Only top-level functions need this, as compiling // the outer scripts of nested functions already processed the source. if (emitterMode != LazyFunction && !parent) { if (!maybeSetDisplayURL() || !maybeSetSourceMap()) return false; tellDebuggerAboutCompiledScript(cx); } return true; } template bool BytecodeEmitter::emitDestructuringDeclsWithEmitter(ParseNode* pattern, NameEmitter emitName) { if (pattern->isKind(PNK_ARRAY)) { for (ParseNode* element = pattern->pn_head; element; element = element->pn_next) { if (element->isKind(PNK_ELISION)) continue; ParseNode* target = element; if (element->isKind(PNK_SPREAD)) { target = element->pn_kid; } if (target->isKind(PNK_ASSIGN)) target = target->pn_left; if (target->isKind(PNK_NAME)) { if (!emitName(this, target)) return false; } else { if (!emitDestructuringDeclsWithEmitter(target, emitName)) return false; } } return true; } MOZ_ASSERT(pattern->isKind(PNK_OBJECT)); for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) { MOZ_ASSERT(member->isKind(PNK_MUTATEPROTO) || member->isKind(PNK_COLON) || member->isKind(PNK_SHORTHAND)); ParseNode* target = member->isKind(PNK_MUTATEPROTO) ? member->pn_kid : member->pn_right; if (target->isKind(PNK_ASSIGN)) target = target->pn_left; if (target->isKind(PNK_NAME)) { if (!emitName(this, target)) return false; } else { if (!emitDestructuringDeclsWithEmitter(target, emitName)) return false; } } return true; } bool BytecodeEmitter::emitDestructuringLHSRef(ParseNode* target, size_t* emitted) { *emitted = 0; if (target->isKind(PNK_SPREAD)) target = target->pn_kid; else if (target->isKind(PNK_ASSIGN)) target = target->pn_left; // No need to recur into PNK_ARRAY and PNK_OBJECT subpatterns here, since // emitSetOrInitializeDestructuring does the recursion when setting or // initializing value. Getting reference doesn't recur. if (target->isKind(PNK_NAME) || target->isKind(PNK_ARRAY) || target->isKind(PNK_OBJECT)) return true; #ifdef DEBUG int depth = stackDepth; #endif switch (target->getKind()) { case PNK_DOT: { if (target->as().isSuper()) { if (!emitSuperPropLHS(&target->as().expression())) return false; *emitted = 2; } else { if (!emitTree(target->pn_expr)) return false; *emitted = 1; } break; } case PNK_ELEM: { if (target->as().isSuper()) { if (!emitSuperElemOperands(target, EmitElemOption::Ref)) return false; *emitted = 3; } else { if (!emitElemOperands(target, EmitElemOption::Ref)) return false; *emitted = 2; } break; } case PNK_CALL: MOZ_ASSERT_UNREACHABLE("Parser::reportIfNotValidSimpleAssignmentTarget " "rejects function calls as assignment " "targets in destructuring assignments"); break; default: MOZ_CRASH("emitDestructuringLHSRef: bad lhs kind"); } MOZ_ASSERT(stackDepth == depth + int(*emitted)); return true; } bool BytecodeEmitter::emitSetOrInitializeDestructuring(ParseNode* target, DestructuringFlavor flav) { // Now emit the lvalue opcode sequence. If the lvalue is a nested // destructuring initialiser-form, call ourselves to handle it, then pop // the matched value. Otherwise emit an lvalue bytecode sequence followed // by an assignment op. if (target->isKind(PNK_SPREAD)) target = target->pn_kid; else if (target->isKind(PNK_ASSIGN)) target = target->pn_left; if (target->isKind(PNK_ARRAY) || target->isKind(PNK_OBJECT)) { if (!emitDestructuringOps(target, flav)) return false; // Per its post-condition, emitDestructuringOps has left the // to-be-destructured value on top of the stack. if (!emit1(JSOP_POP)) return false; } else { switch (target->getKind()) { case PNK_NAME: { auto emitSwapScopeAndRhs = [](BytecodeEmitter* bce, const NameLocation&, bool emittedBindOp) { if (emittedBindOp) { // This is like ordinary assignment, but with one // difference. // // In `a = b`, we first determine a binding for `a` (using // JSOP_BINDNAME or JSOP_BINDGNAME), then we evaluate `b`, // then a JSOP_SETNAME instruction. // // In `[a] = [b]`, per spec, `b` is evaluated first, then // we determine a binding for `a`. Then we need to do // assignment-- but the operands are on the stack in the // wrong order for JSOP_SETPROP, so we have to add a // JSOP_SWAP. // // In the cases where we are emitting a name op, emit a // swap because of this. return bce->emit1(JSOP_SWAP); } // In cases of emitting a frame slot or environment slot, // nothing needs be done. return true; }; RootedAtom name(cx, target->name()); switch (flav) { case DestructuringDeclaration: if (!emitInitializeName(name, emitSwapScopeAndRhs)) return false; break; case DestructuringFormalParameterInVarScope: { // If there's an parameter expression var scope, the // destructuring declaration needs to initialize the name in // the function scope. The innermost scope is the var scope, // and its enclosing scope is the function scope. EmitterScope* funScope = innermostEmitterScope->enclosingInFrame(); NameLocation paramLoc = *locationOfNameBoundInScope(name, funScope); if (!emitSetOrInitializeNameAtLocation(name, paramLoc, emitSwapScopeAndRhs, true)) return false; break; } case DestructuringAssignment: if (!emitSetName(name, emitSwapScopeAndRhs)) return false; break; } break; } case PNK_DOT: { // The reference is already pushed by emitDestructuringLHSRef. JSOp setOp; if (target->as().isSuper()) setOp = sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER; else setOp = sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP; if (!emitAtomOp(target, setOp)) return false; break; } case PNK_ELEM: { // The reference is already pushed by emitDestructuringLHSRef. if (target->as().isSuper()) { JSOp setOp = sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER; // emitDestructuringLHSRef already did emitSuperElemOperands // part of emitSuperElemOp. Perform remaining part here. if (!emitElemOpBase(setOp)) return false; } else { JSOp setOp = sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM; if (!emitElemOpBase(setOp)) return false; } break; } case PNK_CALL: MOZ_ASSERT_UNREACHABLE("Parser::reportIfNotValidSimpleAssignmentTarget " "rejects function calls as assignment " "targets in destructuring assignments"); break; default: MOZ_CRASH("emitSetOrInitializeDestructuring: bad lhs kind"); } // Pop the assigned value. if (!emit1(JSOP_POP)) return false; } return true; } bool BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted) { MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting, ".next() iteration is prohibited in self-hosted code because it " "can run user-modifiable iteration code"); if (!emit1(JSOP_DUP)) // ... ITER ITER return false; if (!emitAtomOp(cx->names().next, JSOP_CALLPROP)) // ... ITER NEXT return false; if (!emit1(JSOP_SWAP)) // ... NEXT ITER return false; if (!emitCall(JSOP_CALL, 0, pn)) // ... RESULT return false; if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) // ... RESULT return false; checkTypeSet(JSOP_CALL); return true; } bool BytecodeEmitter::emitDefault(ParseNode* defaultExpr, ParseNode* pattern) { if (!emit1(JSOP_DUP)) // VALUE VALUE return false; if (!emit1(JSOP_UNDEFINED)) // VALUE VALUE UNDEFINED return false; if (!emit1(JSOP_STRICTEQ)) // VALUE EQL? return false; // Emit source note to enable ion compilation. if (!newSrcNote(SRC_IF)) return false; JumpList jump; if (!emitJump(JSOP_IFEQ, &jump)) // VALUE return false; if (!emit1(JSOP_POP)) // . return false; if (!emitInitializerInBranch(defaultExpr, pattern)) // DEFAULTVALUE return false; if (!emitJumpTargetAndPatch(jump)) return false; return true; } bool BytecodeEmitter::setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name, FunctionPrefixKind prefixKind) { if (maybeFun->isKind(PNK_FUNCTION)) { // Function doesn't have 'name' property at this point. // Set function's name at compile time. RootedFunction fun(cx, maybeFun->pn_funbox->function()); // Single node can be emitted multiple times if it appears in // array destructuring default. If function already has a name, // just return. if (fun->hasCompileTimeName()) { #ifdef DEBUG RootedAtom funName(cx, NameToFunctionName(cx, name, prefixKind)); if (!funName) return false; MOZ_ASSERT(funName == maybeFun->pn_funbox->function()->compileTimeName()); #endif return true; } RootedAtom funName(cx, NameToFunctionName(cx, name, prefixKind)); if (!funName) return false; fun->setCompileTimeName(name); return true; } uint32_t nameIndex; if (!makeAtomIndex(name, &nameIndex)) return false; if (!emitIndexOp(JSOP_STRING, nameIndex)) // FUN NAME return false; uint8_t kind = uint8_t(prefixKind); if (!emit2(JSOP_SETFUNNAME, kind)) // FUN return false; return true; } bool BytecodeEmitter::emitInitializer(ParseNode* initializer, ParseNode* pattern) { if (!emitTree(initializer)) return false; if (!pattern->isInParens() && pattern->isKind(PNK_NAME) && initializer->isDirectRHSAnonFunction()) { RootedAtom name(cx, pattern->name()); if (!setOrEmitSetFunName(initializer, name, FunctionPrefixKind::None)) return false; } return true; } bool BytecodeEmitter::emitInitializerInBranch(ParseNode* initializer, ParseNode* pattern) { TDZCheckCache tdzCache(this); return emitInitializer(initializer, pattern); } class MOZ_STACK_CLASS IfThenElseEmitter { BytecodeEmitter* bce_; JumpList jumpAroundThen_; JumpList jumpsAroundElse_; unsigned noteIndex_; int32_t thenDepth_; #ifdef DEBUG int32_t pushed_; bool calculatedPushed_; #endif enum State { Start, If, Cond, IfElse, Else, End }; State state_; public: explicit IfThenElseEmitter(BytecodeEmitter* bce) : bce_(bce), noteIndex_(-1), thenDepth_(0), #ifdef DEBUG pushed_(0), calculatedPushed_(false), #endif state_(Start) {} ~IfThenElseEmitter() {} private: bool emitIf(State nextState) { MOZ_ASSERT(state_ == Start || state_ == Else); MOZ_ASSERT(nextState == If || nextState == IfElse || nextState == Cond); // Clear jumpAroundThen_ offset that points previous JSOP_IFEQ. if (state_ == Else) jumpAroundThen_ = JumpList(); // Emit an annotated branch-if-false around the then part. SrcNoteType type = nextState == If ? SRC_IF : nextState == IfElse ? SRC_IF_ELSE : SRC_COND; if (!bce_->newSrcNote(type, ¬eIndex_)) return false; if (!bce_->emitJump(JSOP_IFEQ, &jumpAroundThen_)) return false; // To restore stack depth in else part, save depth of the then part. #ifdef DEBUG // If DEBUG, this is also necessary to calculate |pushed_|. thenDepth_ = bce_->stackDepth; #else if (nextState == IfElse || nextState == Cond) thenDepth_ = bce_->stackDepth; #endif state_ = nextState; return true; } public: bool emitIf() { return emitIf(If); } bool emitCond() { return emitIf(Cond); } bool emitIfElse() { return emitIf(IfElse); } bool emitElse() { MOZ_ASSERT(state_ == IfElse || state_ == Cond); calculateOrCheckPushed(); // Emit a jump from the end of our then part around the else part. The // patchJumpsToTarget call at the bottom of this function will fix up // the offset with jumpsAroundElse value. if (!bce_->emitJump(JSOP_GOTO, &jumpsAroundElse_)) return false; // Ensure the branch-if-false comes here, then emit the else. if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_)) return false; // Annotate SRC_IF_ELSE or SRC_COND with the offset from branch to // jump, for IonMonkey's benefit. We can't just "back up" from the pc // of the else clause, because we don't know whether an extended // jump was required to leap from the end of the then clause over // the else clause. if (!bce_->setSrcNoteOffset(noteIndex_, 0, jumpsAroundElse_.offset - jumpAroundThen_.offset)) { return false; } // Restore stack depth of the then part. bce_->stackDepth = thenDepth_; state_ = Else; return true; } bool emitEnd() { MOZ_ASSERT(state_ == If || state_ == Else); calculateOrCheckPushed(); if (state_ == If) { // No else part, fixup the branch-if-false to come here. if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_)) return false; } // Patch all the jumps around else parts. if (!bce_->emitJumpTargetAndPatch(jumpsAroundElse_)) return false; state_ = End; return true; } void calculateOrCheckPushed() { #ifdef DEBUG if (!calculatedPushed_) { pushed_ = bce_->stackDepth - thenDepth_; calculatedPushed_ = true; } else { MOZ_ASSERT(pushed_ == bce_->stackDepth - thenDepth_); } #endif } #ifdef DEBUG int32_t pushed() const { return pushed_; } int32_t popped() const { return -pushed_; } #endif }; bool BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlavor flav) { MOZ_ASSERT(pattern->isKind(PNK_ARRAY)); MOZ_ASSERT(pattern->isArity(PN_LIST)); MOZ_ASSERT(this->stackDepth != 0); // Here's pseudo code for |let [a, b, , c=y, ...d] = x;| // // let x, y; // let a, b, c, d; // let iter, lref, result, done, value; // stack values // // iter = x[Symbol.iterator](); // // // ==== emitted by loop for a ==== // lref = GetReference(a); // // result = iter.next(); // done = result.done; // // if (done) // value = undefined; // else // value = result.value; // // SetOrInitialize(lref, value); // // // ==== emitted by loop for b ==== // lref = GetReference(b); // // if (done) { // value = undefined; // } else { // result = iter.next(); // done = result.done; // if (done) // value = undefined; // else // value = result.value; // } // // SetOrInitialize(lref, value); // // // ==== emitted by loop for elision ==== // if (done) { // value = undefined; // } else { // result = iter.next(); // done = result.done; // if (done) // value = undefined; // else // value = result.value; // } // // // ==== emitted by loop for c ==== // lref = GetReference(c); // // if (done) { // value = undefined; // } else { // result = iter.next(); // done = result.done; // if (done) // value = undefined; // else // value = result.value; // } // // if (value === undefined) // value = y; // // SetOrInitialize(lref, value); // // // ==== emitted by loop for d ==== // lref = GetReference(d); // // if (done) // value = []; // else // value = [...iter]; // // SetOrInitialize(lref, value); // Use an iterator to destructure the RHS, instead of index lookup. We // must leave the *original* value on the stack. if (!emit1(JSOP_DUP)) // ... OBJ OBJ return false; if (!emitIterator()) // ... OBJ ITER return false; for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) { bool isHead = member == pattern->pn_head; bool hasNext = !!member->pn_next; if (member->isKind(PNK_SPREAD)) { size_t emitted = 0; if (!emitDestructuringLHSRef(member, &emitted)) // ... OBJ ITER ?DONE *LREF return false; IfThenElseEmitter ifThenElse(this); if (!isHead) { // If spread is not the first element of the pattern, // iterator can already be completed. // ... OBJ ITER DONE *LREF if (emitted) { if (!emit2(JSOP_PICK, emitted)) // ... OBJ ITER *LREF DONE return false; } if (!ifThenElse.emitIfElse()) // ... OBJ ITER *LREF return false; if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ ITER *LREF ARRAY return false; if (!ifThenElse.emitElse()) // ... OBJ ITER *LREF return false; } // If iterator is not completed, create a new array with the rest // of the iterator. if (!emitDupAt(emitted)) // ... OBJ ITER *LREF ITER return false; if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ ITER *LREF ITER ARRAY return false; if (!emitNumberOp(0)) // ... OBJ ITER *LREF ITER ARRAY INDEX return false; if (!emitSpread()) // ... OBJ ITER *LREF ARRAY INDEX return false; if (!emit1(JSOP_POP)) // ... OBJ ITER *LREF ARRAY return false; if (!isHead) { if (!ifThenElse.emitEnd()) return false; MOZ_ASSERT(ifThenElse.pushed() == 1); } if (!emitSetOrInitializeDestructuring(member, flav)) // ... OBJ ITER return false; MOZ_ASSERT(!hasNext); break; } ParseNode* pndefault = nullptr; ParseNode* subpattern = member; if (subpattern->isKind(PNK_ASSIGN)) { pndefault = subpattern->pn_right; subpattern = subpattern->pn_left; } bool isElision = subpattern->isKind(PNK_ELISION); MOZ_ASSERT(!subpattern->isKind(PNK_SPREAD)); size_t emitted = 0; if (!isElision) { if (!emitDestructuringLHSRef(subpattern, &emitted)) // ... OBJ ITER ?DONE *LREF return false; } IfThenElseEmitter ifAlreadyDone(this); if (!isHead) { // If this element is not the first element of the pattern, // iterator can already be completed. // ... OBJ ITER DONE *LREF if (emitted) { if (hasNext) { if (!emitDupAt(emitted)) // ... OBJ ITER DONE *LREF DONE return false; } else { if (!emit2(JSOP_PICK, emitted)) // ... OBJ ITER *LREF DONE return false; } } else { if (hasNext) { // The position of LREF in the following stack comment // isn't accurate for the operation, but it's equivalent // since LREF is nothing if (!emit1(JSOP_DUP)) // ... OBJ ITER DONE *LREF DONE return false; } } if (!ifAlreadyDone.emitIfElse()) // ... OBJ ITER ?DONE *LREF return false; if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER ?DONE *LREF UNDEF return false; if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER ?DONE *LREF UNDEF return false; if (!ifAlreadyDone.emitElse()) // ... OBJ ITER ?DONE *LREF return false; if (hasNext) { if (emitted) { if (!emit2(JSOP_PICK, emitted)) // ... OBJ ITER *LREF DONE return false; } if (!emit1(JSOP_POP)) // ... OBJ ITER *LREF return false; } } if (emitted) { if (!emitDupAt(emitted)) // ... OBJ ITER *LREF ITER return false; } else { if (!emit1(JSOP_DUP)) // ... OBJ ITER *LREF ITER return false; } if (!emitIteratorNext(pattern)) // ... OBJ ITER *LREF RESULT return false; if (!emit1(JSOP_DUP)) // ... OBJ ITER *LREF RESULT RESULT return false; if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ... OBJ ITER *LREF RESULT DONE return false; if (hasNext) { if (!emit1(JSOP_DUP)) // ... OBJ ITER *LREF RESULT DONE DONE return false; if (!emit2(JSOP_UNPICK, emitted + 2)) // ... OBJ ITER DONE *LREF RESULT DONE return false; } IfThenElseEmitter ifDone(this); if (!ifDone.emitIfElse()) // ... OBJ ITER ?DONE *LREF RESULT return false; if (!emit1(JSOP_POP)) // ... OBJ ITER ?DONE *LREF return false; if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER ?DONE *LREF UNDEF return false; if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER ?DONE *LREF UNDEF return false; if (!ifDone.emitElse()) // ... OBJ ITER ?DONE *LREF RESULT return false; if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ... OBJ ITER ?DONE *LREF VALUE return false; if (!ifDone.emitEnd()) return false; MOZ_ASSERT(ifDone.pushed() == 0); if (!isHead) { if (!ifAlreadyDone.emitEnd()) return false; MOZ_ASSERT(ifAlreadyDone.pushed() == 1); } if (pndefault) { if (!emitDefault(pndefault, subpattern)) // ... OBJ ITER ?DONE *LREF VALUE return false; } if (!isElision) { if (!emitSetOrInitializeDestructuring(subpattern, flav)) // ... OBJ ITER ?DONE { return false; } } else { if (!emit1(JSOP_POP)) // ... OBJ ITER ?DONE return false; } } if (!emit1(JSOP_POP)) // ... OBJ return false; return true; } bool BytecodeEmitter::emitComputedPropertyName(ParseNode* computedPropName) { MOZ_ASSERT(computedPropName->isKind(PNK_COMPUTED_NAME)); return emitTree(computedPropName->pn_kid) && emit1(JSOP_TOID); } bool BytecodeEmitter::emitDestructuringOpsObject(ParseNode* pattern, DestructuringFlavor flav) { MOZ_ASSERT(pattern->isKind(PNK_OBJECT)); MOZ_ASSERT(pattern->isArity(PN_LIST)); MOZ_ASSERT(this->stackDepth > 0); // ... RHS if (!emitRequireObjectCoercible()) // ... RHS return false; for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) { ParseNode* subpattern; if (member->isKind(PNK_MUTATEPROTO)) subpattern = member->pn_kid; else subpattern = member->pn_right; ParseNode* lhs = subpattern; if (lhs->isKind(PNK_ASSIGN)) lhs = lhs->pn_left; size_t emitted; if (!emitDestructuringLHSRef(lhs, &emitted)) // ... RHS *LREF return false; // Duplicate the value being destructured to use as a reference base. if (emitted) { if (!emitDupAt(emitted)) // ... RHS *LREF RHS return false; } else { if (!emit1(JSOP_DUP)) // ... RHS RHS return false; } // Now push the property name currently being matched, which is the // current property name "label" on the left of a colon in the object // initialiser. bool needsGetElem = true; if (member->isKind(PNK_MUTATEPROTO)) { if (!emitAtomOp(cx->names().proto, JSOP_GETPROP)) // ... RHS *LREF PROP return false; needsGetElem = false; } else { MOZ_ASSERT(member->isKind(PNK_COLON) || member->isKind(PNK_SHORTHAND)); ParseNode* key = member->pn_left; if (key->isKind(PNK_NUMBER)) { if (!emitNumberOp(key->pn_dval)) // ... RHS *LREF RHS KEY return false; } else if (key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)) { PropertyName* name = key->pn_atom->asPropertyName(); // The parser already checked for atoms representing indexes and // used PNK_NUMBER instead, but also watch for ids which TI treats // as indexes for simplification of downstream analysis. jsid id = NameToId(name); if (id != IdToTypeId(id)) { if (!emitTree(key)) // ... RHS *LREF RHS KEY return false; } else { if (!emitAtomOp(name, JSOP_GETPROP)) // ... RHS *LREF PROP return false; needsGetElem = false; } } else { if (!emitComputedPropertyName(key)) // ... RHS *LREF RHS KEY return false; } } // Get the property value if not done already. if (needsGetElem && !emitElemOpBase(JSOP_GETELEM)) // ... RHS *LREF PROP return false; if (subpattern->isKind(PNK_ASSIGN)) { if (!emitDefault(subpattern->pn_right, lhs)) // ... RHS *LREF VALUE return false; } // Destructure PROP per this member's lhs. if (!emitSetOrInitializeDestructuring(subpattern, flav)) // ... RHS return false; } return true; } bool BytecodeEmitter::emitDestructuringOps(ParseNode* pattern, DestructuringFlavor flav) { if (pattern->isKind(PNK_ARRAY)) return emitDestructuringOpsArray(pattern, flav); return emitDestructuringOpsObject(pattern, flav); } bool BytecodeEmitter::emitTemplateString(ParseNode* pn) { MOZ_ASSERT(pn->isArity(PN_LIST)); bool pushedString = false; for (ParseNode* pn2 = pn->pn_head; pn2 != NULL; pn2 = pn2->pn_next) { bool isString = (pn2->getKind() == PNK_STRING || pn2->getKind() == PNK_TEMPLATE_STRING); // Skip empty strings. These are very common: a template string like // `${a}${b}` has three empty strings and without this optimization // we'd emit four JSOP_ADD operations instead of just one. if (isString && pn2->pn_atom->empty()) continue; if (!isString) { // We update source notes before emitting the expression if (!updateSourceCoordNotes(pn2->pn_pos.begin)) return false; } if (!emitTree(pn2)) return false; if (!isString) { // We need to convert the expression to a string if (!emit1(JSOP_TOSTRING)) return false; } if (pushedString) { // We've pushed two strings onto the stack. Add them together, leaving just one. if (!emit1(JSOP_ADD)) return false; } else { pushedString = true; } } if (!pushedString) { // All strings were empty, this can happen for something like `${""}`. // Just push an empty string. if (!emitAtomOp(cx->names().empty, JSOP_STRING)) return false; } return true; } bool BytecodeEmitter::emitDeclarationList(ParseNode* declList) { MOZ_ASSERT(declList->isArity(PN_LIST)); ParseNode* next; for (ParseNode* decl = declList->pn_head; decl; decl = next) { if (!updateSourceCoordNotes(decl->pn_pos.begin)) return false; next = decl->pn_next; if (decl->isKind(PNK_ASSIGN)) { MOZ_ASSERT(decl->isOp(JSOP_NOP)); ParseNode* pattern = decl->pn_left; MOZ_ASSERT(pattern->isKind(PNK_ARRAY) || pattern->isKind(PNK_OBJECT)); if (!emitTree(decl->pn_right)) return false; if (!emitDestructuringOps(pattern, DestructuringDeclaration)) return false; if (!emit1(JSOP_POP)) return false; } else { if (!emitSingleDeclaration(declList, decl, decl->expr())) return false; } } return true; } bool BytecodeEmitter::emitSingleDeclaration(ParseNode* declList, ParseNode* decl, ParseNode* initializer) { MOZ_ASSERT(decl->isKind(PNK_NAME)); // Nothing to do for initializer-less 'var' declarations, as there's no TDZ. if (!initializer && declList->isKind(PNK_VAR)) return true; auto emitRhs = [initializer, declList, decl](BytecodeEmitter* bce, const NameLocation&, bool) { if (!initializer) { // Lexical declarations are initialized to undefined without an // initializer. MOZ_ASSERT(declList->isKind(PNK_LET), "var declarations without initializers handled above, " "and const declarations must have initializers"); return bce->emit1(JSOP_UNDEFINED); } MOZ_ASSERT(initializer); return bce->emitInitializer(initializer, decl); }; if (!emitInitializeName(decl, emitRhs)) return false; // Pop the RHS. return emit1(JSOP_POP); } static bool EmitAssignmentRhs(BytecodeEmitter* bce, ParseNode* rhs, uint8_t offset) { // If there is a RHS tree, emit the tree. if (rhs) return bce->emitTree(rhs); // Otherwise the RHS value to assign is already on the stack, i.e., the // next enumeration value in a for-in or for-of loop. Depending on how // many other values have been pushed on the stack, we need to get the // already-pushed RHS value. if (offset != 1 && !bce->emit2(JSOP_PICK, offset - 1)) return false; return true; } bool BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp op, ParseNode* rhs) { // Name assignments are handled separately because choosing ops and when // to emit BINDNAME is involved and should avoid duplication. if (lhs->isKind(PNK_NAME)) { auto emitRhs = [op, lhs, rhs](BytecodeEmitter* bce, const NameLocation& lhsLoc, bool emittedBindOp) { // For compound assignments, first get the LHS value, then emit // the RHS and the op. if (op != JSOP_NOP) { if (lhsLoc.kind() == NameLocation::Kind::Dynamic) { // For dynamic accesses we can do better than a GETNAME // since the assignment already emitted a BINDNAME on the // top of the stack. As an optimization, use that to get // the name. if (!bce->emit1(JSOP_DUP)) return false; if (!bce->emitAtomOp(lhs, JSOP_GETXPROP)) return false; } else { if (!bce->emitGetNameAtLocation(lhs->name(), lhsLoc)) return false; } } // Emit the RHS. If we emitted a BIND[G]NAME, then the scope is on // the top of the stack and we need to pick the right RHS value. if (!EmitAssignmentRhs(bce, rhs, emittedBindOp ? 2 : 1)) return false; if (!lhs->isInParens() && op == JSOP_NOP && rhs && rhs->isDirectRHSAnonFunction()) { RootedAtom name(bce->cx, lhs->name()); if (!bce->setOrEmitSetFunName(rhs, name, FunctionPrefixKind::None)) return false; } // Emit the compound assignment op if there is one. if (op != JSOP_NOP && !bce->emit1(op)) return false; return true; }; return emitSetName(lhs, emitRhs); } // Deal with non-name assignments. uint32_t atomIndex = (uint32_t) -1; uint8_t offset = 1; switch (lhs->getKind()) { case PNK_DOT: if (lhs->as().isSuper()) { if (!emitSuperPropLHS(&lhs->as().expression())) return false; offset += 2; } else { if (!emitTree(lhs->expr())) return false; offset += 1; } if (!makeAtomIndex(lhs->pn_atom, &atomIndex)) return false; break; case PNK_ELEM: { MOZ_ASSERT(lhs->isArity(PN_BINARY)); EmitElemOption opt = op == JSOP_NOP ? EmitElemOption::Get : EmitElemOption::CompoundAssign; if (lhs->as().isSuper()) { if (!emitSuperElemOperands(lhs, opt)) return false; offset += 3; } else { if (!emitElemOperands(lhs, opt)) return false; offset += 2; } break; } case PNK_ARRAY: case PNK_OBJECT: break; case PNK_CALL: if (!emitTree(lhs)) return false; // Assignment to function calls is forbidden, but we have to make the // call first. Now we can throw. if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_BAD_LEFTSIDE_OF_ASS)) return false; // Rebalance the stack to placate stack-depth assertions. if (!emit1(JSOP_POP)) return false; break; default: MOZ_ASSERT(0); } if (op != JSOP_NOP) { MOZ_ASSERT(rhs); switch (lhs->getKind()) { case PNK_DOT: { JSOp getOp; if (lhs->as().isSuper()) { if (!emit1(JSOP_DUP2)) return false; getOp = JSOP_GETPROP_SUPER; } else { if (!emit1(JSOP_DUP)) return false; bool isLength = (lhs->pn_atom == cx->names().length); getOp = isLength ? JSOP_LENGTH : JSOP_GETPROP; } if (!emitIndex32(getOp, atomIndex)) return false; break; } case PNK_ELEM: { JSOp elemOp; if (lhs->as().isSuper()) { if (!emitDupAt(2)) return false; if (!emitDupAt(2)) return false; if (!emitDupAt(2)) return false; elemOp = JSOP_GETELEM_SUPER; } else { if (!emit1(JSOP_DUP2)) return false; elemOp = JSOP_GETELEM; } if (!emitElemOpBase(elemOp)) return false; break; } case PNK_CALL: // We just emitted a JSOP_THROWMSG and popped the call's return // value. Push a random value to make sure the stack depth is // correct. if (!emit1(JSOP_NULL)) return false; break; default:; } } if (!EmitAssignmentRhs(this, rhs, offset)) return false; /* If += etc., emit the binary operator with a source note. */ if (op != JSOP_NOP) { if (!newSrcNote(SRC_ASSIGNOP)) return false; if (!emit1(op)) return false; } /* Finally, emit the specialized assignment bytecode. */ switch (lhs->getKind()) { case PNK_DOT: { JSOp setOp = lhs->as().isSuper() ? (sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER) : (sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP); if (!emitIndexOp(setOp, atomIndex)) return false; break; } case PNK_CALL: // We threw above, so nothing to do here. break; case PNK_ELEM: { JSOp setOp = lhs->as().isSuper() ? sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER : sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM; if (!emit1(setOp)) return false; break; } case PNK_ARRAY: case PNK_OBJECT: if (!emitDestructuringOps(lhs, DestructuringAssignment)) return false; break; default: MOZ_ASSERT(0); } return true; } bool ParseNode::getConstantValue(ExclusiveContext* cx, AllowConstantObjects allowObjects, MutableHandleValue vp, Value* compare, size_t ncompare, NewObjectKind newKind) { MOZ_ASSERT(newKind == TenuredObject || newKind == SingletonObject); switch (getKind()) { case PNK_NUMBER: vp.setNumber(pn_dval); return true; case PNK_TEMPLATE_STRING: case PNK_STRING: vp.setString(pn_atom); return true; case PNK_TRUE: vp.setBoolean(true); return true; case PNK_FALSE: vp.setBoolean(false); return true; case PNK_NULL: vp.setNull(); return true; case PNK_CALLSITEOBJ: case PNK_ARRAY: { unsigned count; ParseNode* pn; if (allowObjects == DontAllowObjects) { vp.setMagic(JS_GENERIC_MAGIC); return true; } ObjectGroup::NewArrayKind arrayKind = ObjectGroup::NewArrayKind::Normal; if (allowObjects == ForCopyOnWriteArray) { arrayKind = ObjectGroup::NewArrayKind::CopyOnWrite; allowObjects = DontAllowObjects; } if (getKind() == PNK_CALLSITEOBJ) { count = pn_count - 1; pn = pn_head->pn_next; } else { MOZ_ASSERT(isOp(JSOP_NEWINIT) && !(pn_xflags & PNX_NONCONST)); count = pn_count; pn = pn_head; } AutoValueVector values(cx); if (!values.appendN(MagicValue(JS_ELEMENTS_HOLE), count)) return false; size_t idx; for (idx = 0; pn; idx++, pn = pn->pn_next) { if (!pn->getConstantValue(cx, allowObjects, values[idx], values.begin(), idx)) return false; if (values[idx].isMagic(JS_GENERIC_MAGIC)) { vp.setMagic(JS_GENERIC_MAGIC); return true; } } MOZ_ASSERT(idx == count); JSObject* obj = ObjectGroup::newArrayObject(cx, values.begin(), values.length(), newKind, arrayKind); if (!obj) return false; if (!CombineArrayElementTypes(cx, obj, compare, ncompare)) return false; vp.setObject(*obj); return true; } case PNK_OBJECT: { MOZ_ASSERT(isOp(JSOP_NEWINIT)); MOZ_ASSERT(!(pn_xflags & PNX_NONCONST)); if (allowObjects == DontAllowObjects) { vp.setMagic(JS_GENERIC_MAGIC); return true; } MOZ_ASSERT(allowObjects == AllowObjects); Rooted properties(cx, IdValueVector(cx)); RootedValue value(cx), idvalue(cx); for (ParseNode* pn = pn_head; pn; pn = pn->pn_next) { if (!pn->pn_right->getConstantValue(cx, allowObjects, &value)) return false; if (value.isMagic(JS_GENERIC_MAGIC)) { vp.setMagic(JS_GENERIC_MAGIC); return true; } ParseNode* pnid = pn->pn_left; if (pnid->isKind(PNK_NUMBER)) { idvalue = NumberValue(pnid->pn_dval); } else { MOZ_ASSERT(pnid->isKind(PNK_OBJECT_PROPERTY_NAME) || pnid->isKind(PNK_STRING)); MOZ_ASSERT(pnid->pn_atom != cx->names().proto); idvalue = StringValue(pnid->pn_atom); } RootedId id(cx); if (!ValueToId(cx, idvalue, &id)) return false; if (!properties.append(IdValuePair(id, value))) return false; } JSObject* obj = ObjectGroup::newPlainObject(cx, properties.begin(), properties.length(), newKind); if (!obj) return false; if (!CombinePlainObjectPropertyTypes(cx, obj, compare, ncompare)) return false; vp.setObject(*obj); return true; } default: MOZ_CRASH("Unexpected node"); } return false; } bool BytecodeEmitter::emitSingletonInitialiser(ParseNode* pn) { NewObjectKind newKind = (pn->getKind() == PNK_OBJECT) ? SingletonObject : TenuredObject; RootedValue value(cx); if (!pn->getConstantValue(cx, ParseNode::AllowObjects, &value, nullptr, 0, newKind)) return false; MOZ_ASSERT_IF(newKind == SingletonObject, value.toObject().isSingleton()); ObjectBox* objbox = parser->newObjectBox(&value.toObject()); if (!objbox) return false; return emitObjectOp(objbox, JSOP_OBJECT); } bool BytecodeEmitter::emitCallSiteObject(ParseNode* pn) { RootedValue value(cx); if (!pn->getConstantValue(cx, ParseNode::AllowObjects, &value)) return false; MOZ_ASSERT(value.isObject()); ObjectBox* objbox1 = parser->newObjectBox(&value.toObject()); if (!objbox1) return false; if (!pn->as().getRawArrayValue(cx, &value)) return false; MOZ_ASSERT(value.isObject()); ObjectBox* objbox2 = parser->newObjectBox(&value.toObject()); if (!objbox2) return false; return emitObjectPairOp(objbox1, objbox2, JSOP_CALLSITEOBJ); } /* See the SRC_FOR source note offsetBias comments later in this file. */ JS_STATIC_ASSERT(JSOP_NOP_LENGTH == 1); JS_STATIC_ASSERT(JSOP_POP_LENGTH == 1); namespace { class EmitLevelManager { BytecodeEmitter* bce; public: explicit EmitLevelManager(BytecodeEmitter* bce) : bce(bce) { bce->emitLevel++; } ~EmitLevelManager() { bce->emitLevel--; } }; } /* anonymous namespace */ bool BytecodeEmitter::emitCatch(ParseNode* pn) { // We must be nested under a try-finally statement. TryFinallyControl& controlInfo = innermostNestableControl->as(); /* Pick up the pending exception and bind it to the catch variable. */ if (!emit1(JSOP_EXCEPTION)) return false; /* * Dup the exception object if there is a guard for rethrowing to use * it later when rethrowing or in other catches. */ if (pn->pn_kid2 && !emit1(JSOP_DUP)) return false; ParseNode* pn2 = pn->pn_kid1; switch (pn2->getKind()) { case PNK_ARRAY: case PNK_OBJECT: if (!emitDestructuringOps(pn2, DestructuringDeclaration)) return false; if (!emit1(JSOP_POP)) return false; break; case PNK_NAME: if (!emitLexicalInitialization(pn2)) return false; if (!emit1(JSOP_POP)) return false; break; default: MOZ_ASSERT(0); } // If there is a guard expression, emit it and arrange to jump to the next // catch block if the guard expression is false. if (pn->pn_kid2) { if (!emitTree(pn->pn_kid2)) return false; // If the guard expression is false, fall through, pop the block scope, // and jump to the next catch block. Otherwise jump over that code and // pop the dupped exception. JumpList guardCheck; if (!emitJump(JSOP_IFNE, &guardCheck)) return false; { NonLocalExitControl nle(this); // Move exception back to cx->exception to prepare for // the next catch. if (!emit1(JSOP_THROWING)) return false; // Leave the scope for this catch block. if (!nle.prepareForNonLocalJump(&controlInfo)) return false; // Jump to the next handler added by emitTry. if (!emitJump(JSOP_GOTO, &controlInfo.guardJump)) return false; } // Back to normal control flow. if (!emitJumpTargetAndPatch(guardCheck)) return false; // Pop duplicated exception object as we no longer need it. if (!emit1(JSOP_POP)) return false; } /* Emit the catch body. */ return emitTree(pn->pn_kid3); } // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See the // comment on EmitSwitch. MOZ_NEVER_INLINE bool BytecodeEmitter::emitTry(ParseNode* pn) { // Track jumps-over-catches and gosubs-to-finally for later fixup. // // When a finally block is active, non-local jumps (including // jumps-over-catches) result in a GOSUB being written into the bytecode // stream and fixed-up later. // TryFinallyControl controlInfo(this, pn->pn_kid3 ? StatementKind::Finally : StatementKind::Try); // Since an exception can be thrown at any place inside the try block, // we need to restore the stack and the scope chain before we transfer // the control to the exception handler. // // For that we store in a try note associated with the catch or // finally block the stack depth upon the try entry. The interpreter // uses this depth to properly unwind the stack and the scope chain. // int depth = stackDepth; // Record the try location, then emit the try block. unsigned noteIndex; if (!newSrcNote(SRC_TRY, ¬eIndex)) return false; if (!emit1(JSOP_TRY)) return false; ptrdiff_t tryStart = offset(); if (!emitTree(pn->pn_kid1)) return false; MOZ_ASSERT(depth == stackDepth); // GOSUB to finally, if present. if (pn->pn_kid3) { if (!emitJump(JSOP_GOSUB, &controlInfo.gosubs)) return false; } // Source note points to the jump at the end of the try block. if (!setSrcNoteOffset(noteIndex, 0, offset() - tryStart + JSOP_TRY_LENGTH)) return false; // Emit jump over catch and/or finally. JumpList catchJump; if (!emitJump(JSOP_GOTO, &catchJump)) return false; JumpTarget tryEnd; if (!emitJumpTarget(&tryEnd)) return false; // If this try has a catch block, emit it. ParseNode* catchList = pn->pn_kid2; if (catchList) { MOZ_ASSERT(catchList->isKind(PNK_CATCHLIST)); // The emitted code for a catch block looks like: // // [pushlexicalenv] only if any local aliased // exception // if there is a catchguard: // dup // setlocal 0; pop assign or possibly destructure exception // if there is a catchguard: // < catchguard code > // ifne POST // debugleaveblock // [poplexicalenv] only if any local aliased // throwing pop exception to cx->exception // goto // POST: pop // < catch block contents > // debugleaveblock // [poplexicalenv] only if any local aliased // goto non-local; finally applies // // If there's no catch block without a catchguard, the last points to rethrow code. This code will [gosub] to the finally // code if appropriate, and is also used for the catch-all trynote for // capturing exceptions thrown from catch{} blocks. // for (ParseNode* pn3 = catchList->pn_head; pn3; pn3 = pn3->pn_next) { MOZ_ASSERT(this->stackDepth == depth); // Clear the frame's return value that might have been set by the // try block: // // eval("try { 1; throw 2 } catch(e) {}"); // undefined, not 1 if (!emit1(JSOP_UNDEFINED)) return false; if (!emit1(JSOP_SETRVAL)) return false; // Emit the lexical scope and catch body. MOZ_ASSERT(pn3->isKind(PNK_LEXICALSCOPE)); if (!emitTree(pn3)) return false; // gosub , if required. if (pn->pn_kid3) { if (!emitJump(JSOP_GOSUB, &controlInfo.gosubs)) return false; MOZ_ASSERT(this->stackDepth == depth); } // Jump over the remaining catch blocks. This will get fixed // up to jump to after catch/finally. if (!emitJump(JSOP_GOTO, &catchJump)) return false; // If this catch block had a guard clause, patch the guard jump to // come here. if (controlInfo.guardJump.offset != -1) { if (!emitJumpTargetAndPatch(controlInfo.guardJump)) return false; controlInfo.guardJump.offset = -1; // If this catch block is the last one, rethrow, delegating // execution of any finally block to the exception handler. if (!pn3->pn_next) { if (!emit1(JSOP_EXCEPTION)) return false; if (!emit1(JSOP_THROW)) return false; } } } } MOZ_ASSERT(this->stackDepth == depth); // Emit the finally handler, if there is one. JumpTarget finallyStart{ 0 }; if (pn->pn_kid3) { if (!emitJumpTarget(&finallyStart)) return false; // Fix up the gosubs that might have been emitted before non-local // jumps to the finally code. patchJumpsToTarget(controlInfo.gosubs, finallyStart); // Indicate that we're emitting a subroutine body. controlInfo.setEmittingSubroutine(); if (!updateSourceCoordNotes(pn->pn_kid3->pn_pos.begin)) return false; if (!emit1(JSOP_FINALLY)) return false; if (!emit1(JSOP_GETRVAL)) return false; // Clear the frame's return value to make break/continue return // correct value even if there's no other statement before them: // // eval("x: try { 1 } finally { break x; }"); // undefined, not 1 if (!emit1(JSOP_UNDEFINED)) return false; if (!emit1(JSOP_SETRVAL)) return false; if (!emitTree(pn->pn_kid3)) return false; if (!emit1(JSOP_SETRVAL)) return false; if (!emit1(JSOP_RETSUB)) return false; hasTryFinally = true; MOZ_ASSERT(this->stackDepth == depth); } // ReconstructPCStack needs a NOP here to mark the end of the last catch block. if (!emit1(JSOP_NOP)) return false; // Fix up the end-of-try/catch jumps to come here. if (!emitJumpTargetAndPatch(catchJump)) return false; // Add the try note last, to let post-order give us the right ordering // (first to last for a given nesting level, inner to outer by level). if (catchList && !tryNoteList.append(JSTRY_CATCH, depth, tryStart, tryEnd.offset)) return false; // If we've got a finally, mark try+catch region with additional // trynote to catch exceptions (re)thrown from a catch block or // for the try{}finally{} case. if (pn->pn_kid3 && !tryNoteList.append(JSTRY_FINALLY, depth, tryStart, finallyStart.offset)) return false; return true; } bool BytecodeEmitter::emitIf(ParseNode* pn) { IfThenElseEmitter ifThenElse(this); if_again: /* Emit code for the condition before pushing stmtInfo. */ if (!emitTreeInBranch(pn->pn_kid1)) return false; ParseNode* elseNode = pn->pn_kid3; if (elseNode) { if (!ifThenElse.emitIfElse()) return false; } else { if (!ifThenElse.emitIf()) return false; } /* Emit code for the then part. */ if (!emitTreeInBranch(pn->pn_kid2)) return false; if (elseNode) { if (!ifThenElse.emitElse()) return false; if (elseNode->isKind(PNK_IF)) { pn = elseNode; goto if_again; } /* Emit code for the else part. */ if (!emitTreeInBranch(elseNode)) return false; } if (!ifThenElse.emitEnd()) return false; return true; } bool BytecodeEmitter::emitHoistedFunctionsInList(ParseNode* list) { MOZ_ASSERT(list->pn_xflags & PNX_FUNCDEFS); for (ParseNode* pn = list->pn_head; pn; pn = pn->pn_next) { ParseNode* maybeFun = pn; if (!sc->strict()) { while (maybeFun->isKind(PNK_LABEL)) maybeFun = maybeFun->as().statement(); } if (maybeFun->isKind(PNK_FUNCTION) && maybeFun->functionIsHoisted()) { if (!emitTree(maybeFun)) return false; } } return true; } bool BytecodeEmitter::emitLexicalScopeBody(ParseNode* body, EmitLineNumberNote emitLineNote) { if (body->isKind(PNK_STATEMENTLIST) && body->pn_xflags & PNX_FUNCDEFS) { // This block contains function statements whose definitions are // hoisted to the top of the block. Emit these as a separate pass // before the rest of the block. if (!emitHoistedFunctionsInList(body)) return false; } // Line notes were updated by emitLexicalScope. return emitTree(body, emitLineNote); } // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See // the comment on emitSwitch. MOZ_NEVER_INLINE bool BytecodeEmitter::emitLexicalScope(ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_LEXICALSCOPE)); TDZCheckCache tdzCache(this); ParseNode* body = pn->scopeBody(); if (pn->isEmptyScope()) return emitLexicalScopeBody(body); // Update line number notes before emitting TDZ poison in // EmitterScope::enterLexical to avoid spurious pausing on seemingly // non-effectful lines in Debugger. // // For example, consider the following code. // // L1: { // L2: let x = 42; // L3: } // // If line number notes were not updated before the TDZ poison, the TDZ // poison bytecode sequence of 'uninitialized; initlexical' will have line // number L1, and the Debugger will pause there. if (!ParseNodeRequiresSpecialLineNumberNotes(body)) { ParseNode* pnForPos = body; if (body->isKind(PNK_STATEMENTLIST) && body->pn_head) pnForPos = body->pn_head; if (!updateLineNumberNotes(pnForPos->pn_pos.begin)) return false; } EmitterScope emitterScope(this); ScopeKind kind; if (body->isKind(PNK_CATCH)) kind = body->pn_kid1->isKind(PNK_NAME) ? ScopeKind::SimpleCatch : ScopeKind::Catch; else kind = ScopeKind::Lexical; if (!emitterScope.enterLexical(this, kind, pn->scopeBindings())) return false; if (body->isKind(PNK_FOR)) { // for loops need to emit {FRESHEN,RECREATE}LEXICALENV if there are // lexical declarations in the head. Signal this by passing a // non-nullptr lexical scope. if (!emitFor(body, &emitterScope)) return false; } else { if (!emitLexicalScopeBody(body, SUPPRESS_LINENOTE)) return false; } return emitterScope.leave(this); } bool BytecodeEmitter::emitWith(ParseNode* pn) { if (!emitTree(pn->pn_left)) return false; EmitterScope emitterScope(this); if (!emitterScope.enterWith(this)) return false; if (!emitTree(pn->pn_right)) return false; return emitterScope.leave(this); } bool BytecodeEmitter::emitRequireObjectCoercible() { // For simplicity, handle this in self-hosted code, at cost of 13 bytes of // bytecode versus 1 byte for a dedicated opcode. As more places need this // behavior, we may want to reconsider this tradeoff. #ifdef DEBUG auto depth = this->stackDepth; #endif MOZ_ASSERT(depth > 0); // VAL if (!emit1(JSOP_DUP)) // VAL VAL return false; // Note that "intrinsic" is a misnomer: we're calling a *self-hosted* // function that's not an intrinsic! But it nonetheless works as desired. if (!emitAtomOp(cx->names().RequireObjectCoercible, JSOP_GETINTRINSIC)) // VAL VAL REQUIREOBJECTCOERCIBLE { return false; } if (!emit1(JSOP_UNDEFINED)) // VAL VAL REQUIREOBJECTCOERCIBLE UNDEFINED return false; if (!emit2(JSOP_PICK, 2)) // VAL REQUIREOBJECTCOERCIBLE UNDEFINED VAL return false; if (!emitCall(JSOP_CALL, 1)) // VAL IGNORED return false; checkTypeSet(JSOP_CALL); if (!emit1(JSOP_POP)) // VAL return false; MOZ_ASSERT(depth == this->stackDepth); return true; } bool BytecodeEmitter::emitIterator() { // Convert iterable to iterator. if (!emit1(JSOP_DUP)) // OBJ OBJ return false; if (!emit2(JSOP_SYMBOL, uint8_t(JS::SymbolCode::iterator))) // OBJ OBJ @@ITERATOR return false; if (!emitElemOpBase(JSOP_CALLELEM)) // OBJ ITERFN return false; if (!emit1(JSOP_SWAP)) // ITERFN OBJ return false; if (!emitCall(JSOP_CALLITER, 0)) // ITER return false; checkTypeSet(JSOP_CALLITER); if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) // ITER return false; return true; } bool BytecodeEmitter::emitSpread(bool allowSelfHosted) { LoopControl loopInfo(this, StatementKind::Spread); // Jump down to the loop condition to minimize overhead assuming at least // one iteration, as the other loop forms do. Annotate so IonMonkey can // find the loop-closing jump. unsigned noteIndex; if (!newSrcNote(SRC_FOR_OF, ¬eIndex)) return false; // Jump down to the loop condition to minimize overhead, assuming at least // one iteration. (This is also what we do for loops; whether this // assumption holds for spreads is an unanswered question.) JumpList initialJump; if (!emitJump(JSOP_GOTO, &initialJump)) // ITER ARR I (during the goto) return false; JumpTarget top{ -1 }; if (!emitLoopHead(nullptr, &top)) // ITER ARR I return false; // When we enter the goto above, we have ITER ARR I on the stack. But when // we reach this point on the loop backedge (if spreading produces at least // one value), we've additionally pushed a RESULT iteration value. // Increment manually to reflect this. this->stackDepth++; JumpList beq; JumpTarget breakTarget{ -1 }; { #ifdef DEBUG auto loopDepth = this->stackDepth; #endif // Emit code to assign result.value to the iteration variable. if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER ARR I VALUE return false; if (!emit1(JSOP_INITELEM_INC)) // ITER ARR (I+1) return false; MOZ_ASSERT(this->stackDepth == loopDepth - 1); // Spread operations can't contain |continue|, so don't bother setting loop // and enclosing "update" offsets, as we do with for-loops. // COME FROM the beginning of the loop to here. if (!emitLoopEntry(nullptr, initialJump)) // ITER ARR I return false; if (!emitDupAt(2)) // ITER ARR I ITER return false; if (!emitIteratorNext(nullptr, allowSelfHosted)) // ITER ARR I RESULT return false; if (!emit1(JSOP_DUP)) // ITER ARR I RESULT RESULT return false; if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER ARR I RESULT DONE return false; if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) // ITER ARR I RESULT return false; MOZ_ASSERT(this->stackDepth == loopDepth); } // Let Ion know where the closing jump of this loop is. if (!setSrcNoteOffset(noteIndex, 0, beq.offset - initialJump.offset)) return false; // No breaks or continues should occur in spreads. MOZ_ASSERT(loopInfo.breaks.offset == -1); MOZ_ASSERT(loopInfo.continues.offset == -1); if (!tryNoteList.append(JSTRY_FOR_OF, stackDepth, top.offset, breakTarget.offset)) return false; if (!emit2(JSOP_PICK, 3)) // ARR FINAL_INDEX RESULT ITER return false; return emitUint16Operand(JSOP_POPN, 2); // ARR FINAL_INDEX } bool BytecodeEmitter::emitInitializeForInOrOfTarget(ParseNode* forHead) { MOZ_ASSERT(forHead->isKind(PNK_FORIN) || forHead->isKind(PNK_FOROF)); MOZ_ASSERT(forHead->isArity(PN_TERNARY)); MOZ_ASSERT(this->stackDepth >= 1, "must have a per-iteration value for initializing"); ParseNode* target = forHead->pn_kid1; MOZ_ASSERT(!forHead->pn_kid2); // If the for-in/of loop didn't have a variable declaration, per-loop // initialization is just assigning the iteration value to a target // expression. if (!parser->handler.isDeclarationList(target)) return emitAssignment(target, JSOP_NOP, nullptr); // ... ITERVAL // Otherwise, per-loop initialization is (possibly) declaration // initialization. If the declaration is a lexical declaration, it must be // initialized. If the declaration is a variable declaration, an // assignment to that name (which does *not* necessarily assign to the // variable!) must be generated. if (!updateSourceCoordNotes(target->pn_pos.begin)) return false; MOZ_ASSERT(target->isForLoopDeclaration()); target = parser->handler.singleBindingFromDeclaration(target); if (target->isKind(PNK_NAME)) { auto emitSwapScopeAndRhs = [](BytecodeEmitter* bce, const NameLocation&, bool emittedBindOp) { if (emittedBindOp) { // Per-iteration initialization in for-in/of loops computes the // iteration value *before* initializing. Thus the // initializing value may be buried under a bind-specific value // on the stack. Swap it to the top of the stack. MOZ_ASSERT(bce->stackDepth >= 2); return bce->emit1(JSOP_SWAP); } // In cases of emitting a frame slot or environment slot, // nothing needs be done. MOZ_ASSERT(bce->stackDepth >= 1); return true; }; // The caller handles removing the iteration value from the stack. return emitInitializeName(target, emitSwapScopeAndRhs); } MOZ_ASSERT(!target->isKind(PNK_ASSIGN), "for-in/of loop destructuring declarations can't have initializers"); MOZ_ASSERT(target->isKind(PNK_ARRAY) || target->isKind(PNK_OBJECT)); return emitDestructuringOps(target, DestructuringDeclaration); } bool BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitterScope) { MOZ_ASSERT(forOfLoop->isKind(PNK_FOR)); MOZ_ASSERT(forOfLoop->isArity(PN_BINARY)); ParseNode* forOfHead = forOfLoop->pn_left; MOZ_ASSERT(forOfHead->isKind(PNK_FOROF)); MOZ_ASSERT(forOfHead->isArity(PN_TERNARY)); // Evaluate the expression being iterated. ParseNode* forHeadExpr = forOfHead->pn_kid3; if (!emitTree(forHeadExpr)) // ITERABLE return false; if (!emitIterator()) // ITER return false; // For-of loops have both the iterator and the value on the stack. Push // undefined to balance the stack. if (!emit1(JSOP_UNDEFINED)) // ITER RESULT return false; LoopControl loopInfo(this, StatementKind::ForOfLoop); // Annotate so IonMonkey can find the loop-closing jump. unsigned noteIndex; if (!newSrcNote(SRC_FOR_OF, ¬eIndex)) return false; JumpList initialJump; if (!emitJump(JSOP_GOTO, &initialJump)) // ITER RESULT return false; JumpTarget top{ -1 }; if (!emitLoopHead(nullptr, &top)) // ITER RESULT return false; // If the loop had an escaping lexical declaration, replace the current // environment with an dead zoned one to implement TDZ semantics. if (headLexicalEmitterScope) { // The environment chain only includes an environment for the for-of // loop head *if* a scope binding is captured, thereby requiring // recreation each iteration. If a lexical scope exists for the head, // it must be the innermost one. If that scope has closed-over // bindings inducing an environment, recreate the current environment. DebugOnly forOfTarget = forOfHead->pn_kid1; MOZ_ASSERT(forOfTarget->isKind(PNK_LET) || forOfTarget->isKind(PNK_CONST)); MOZ_ASSERT(headLexicalEmitterScope == innermostEmitterScope); MOZ_ASSERT(headLexicalEmitterScope->scope(this)->kind() == ScopeKind::Lexical); if (headLexicalEmitterScope->hasEnvironment()) { if (!emit1(JSOP_RECREATELEXICALENV)) // ITER RESULT return false; } // For uncaptured bindings, put them back in TDZ. if (!headLexicalEmitterScope->deadZoneFrameSlots(this)) return false; } JumpList beq; JumpTarget breakTarget{ -1 }; { #ifdef DEBUG auto loopDepth = this->stackDepth; #endif // Emit code to assign result.value to the iteration variable. if (!emit1(JSOP_DUP)) // ITER RESULT RESULT return false; if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER RESULT VALUE return false; if (!emitInitializeForInOrOfTarget(forOfHead)) // ITER RESULT VALUE return false; if (!emit1(JSOP_POP)) // ITER RESULT return false; MOZ_ASSERT(this->stackDepth == loopDepth, "the stack must be balanced around the initializing " "operation"); // Perform the loop body. ParseNode* forBody = forOfLoop->pn_right; if (!emitTree(forBody)) // ITER RESULT return false; // Set offset for continues. loopInfo.continueTarget = { offset() }; if (!emitLoopEntry(forHeadExpr, initialJump)) // ITER RESULT return false; if (!emit1(JSOP_POP)) // ITER return false; if (!emit1(JSOP_DUP)) // ITER ITER return false; if (!emitIteratorNext(forOfHead)) // ITER RESULT return false; if (!emit1(JSOP_DUP)) // ITER RESULT RESULT return false; if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE return false; if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) return false; // ITER RESULT MOZ_ASSERT(this->stackDepth == loopDepth); } // Let Ion know where the closing jump of this loop is. if (!setSrcNoteOffset(noteIndex, 0, beq.offset - initialJump.offset)) return false; if (!loopInfo.patchBreaksAndContinues(this)) return false; if (!tryNoteList.append(JSTRY_FOR_OF, stackDepth, top.offset, breakTarget.offset)) return false; return emitUint16Operand(JSOP_POPN, 2); // } bool BytecodeEmitter::emitForIn(ParseNode* forInLoop, EmitterScope* headLexicalEmitterScope) { MOZ_ASSERT(forInLoop->isKind(PNK_FOR)); MOZ_ASSERT(forInLoop->isArity(PN_BINARY)); MOZ_ASSERT(forInLoop->isOp(JSOP_ITER)); ParseNode* forInHead = forInLoop->pn_left; MOZ_ASSERT(forInHead->isKind(PNK_FORIN)); MOZ_ASSERT(forInHead->isArity(PN_TERNARY)); // Annex B: Evaluate the var-initializer expression if present. // |for (var i = initializer in expr) { ... }| ParseNode* forInTarget = forInHead->pn_kid1; if (parser->handler.isDeclarationList(forInTarget)) { ParseNode* decl = parser->handler.singleBindingFromDeclaration(forInTarget); if (decl->isKind(PNK_NAME)) { if (ParseNode* initializer = decl->expr()) { MOZ_ASSERT(forInTarget->isKind(PNK_VAR), "for-in initializers are only permitted for |var| declarations"); if (!updateSourceCoordNotes(decl->pn_pos.begin)) return false; auto emitRhs = [decl, initializer](BytecodeEmitter* bce, const NameLocation&, bool) { return bce->emitInitializer(initializer, decl); }; if (!emitInitializeName(decl, emitRhs)) return false; // Pop the initializer. if (!emit1(JSOP_POP)) return false; } } } // Evaluate the expression being iterated. ParseNode* expr = forInHead->pn_kid3; if (!emitTree(expr)) // EXPR return false; // Convert the value to the appropriate sort of iterator object for the // loop variant (for-in, for-each-in, or destructuring for-in). unsigned iflags = forInLoop->pn_iflags; MOZ_ASSERT(0 == (iflags & ~(JSITER_FOREACH | JSITER_ENUMERATE))); if (!emit2(JSOP_ITER, AssertedCast(iflags))) // ITER return false; // For-in loops have both the iterator and the value on the stack. Push // undefined to balance the stack. if (!emit1(JSOP_UNDEFINED)) // ITER ITERVAL return false; LoopControl loopInfo(this, StatementKind::ForInLoop); /* Annotate so IonMonkey can find the loop-closing jump. */ unsigned noteIndex; if (!newSrcNote(SRC_FOR_IN, ¬eIndex)) return false; // Jump down to the loop condition to minimize overhead (assuming at least // one iteration, just like the other loop forms). JumpList initialJump; if (!emitJump(JSOP_GOTO, &initialJump)) // ITER ITERVAL return false; JumpTarget top{ -1 }; if (!emitLoopHead(nullptr, &top)) // ITER ITERVAL return false; // If the loop had an escaping lexical declaration, replace the current // environment with an dead zoned one to implement TDZ semantics. if (headLexicalEmitterScope) { // The environment chain only includes an environment for the for-in // loop head *if* a scope binding is captured, thereby requiring // recreation each iteration. If a lexical scope exists for the head, // it must be the innermost one. If that scope has closed-over // bindings inducing an environment, recreate the current environment. MOZ_ASSERT(forInTarget->isKind(PNK_LET) || forInTarget->isKind(PNK_CONST)); MOZ_ASSERT(headLexicalEmitterScope == innermostEmitterScope); MOZ_ASSERT(headLexicalEmitterScope->scope(this)->kind() == ScopeKind::Lexical); if (headLexicalEmitterScope->hasEnvironment()) { if (!emit1(JSOP_RECREATELEXICALENV)) // ITER ITERVAL return false; } // For uncaptured bindings, put them back in TDZ. if (!headLexicalEmitterScope->deadZoneFrameSlots(this)) return false; } { #ifdef DEBUG auto loopDepth = this->stackDepth; #endif MOZ_ASSERT(loopDepth >= 2); if (!emitInitializeForInOrOfTarget(forInHead)) // ITER ITERVAL return false; MOZ_ASSERT(this->stackDepth == loopDepth, "iterator and iterval must be left on the stack"); } // Perform the loop body. ParseNode* forBody = forInLoop->pn_right; if (!emitTree(forBody)) // ITER ITERVAL return false; // Set offset for continues. loopInfo.continueTarget = { offset() }; if (!emitLoopEntry(nullptr, initialJump)) // ITER ITERVAL return false; if (!emit1(JSOP_POP)) // ITER return false; if (!emit1(JSOP_MOREITER)) // ITER NEXTITERVAL? return false; if (!emit1(JSOP_ISNOITER)) // ITER NEXTITERVAL? ISNOITER return false; JumpList beq; JumpTarget breakTarget{ -1 }; if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) return false; // ITER NEXTITERVAL // Set the srcnote offset so we can find the closing jump. if (!setSrcNoteOffset(noteIndex, 0, beq.offset - initialJump.offset)) return false; if (!loopInfo.patchBreaksAndContinues(this)) return false; // Pop the enumeration value. if (!emit1(JSOP_POP)) // ITER return false; if (!tryNoteList.append(JSTRY_FOR_IN, this->stackDepth, top.offset, offset())) return false; return emit1(JSOP_ENDITER); // } /* C-style `for (init; cond; update) ...` loop. */ bool BytecodeEmitter::emitCStyleFor(ParseNode* pn, EmitterScope* headLexicalEmitterScope) { LoopControl loopInfo(this, StatementKind::ForLoop); ParseNode* forHead = pn->pn_left; ParseNode* forBody = pn->pn_right; // If the head of this for-loop declared any lexical variables, the parser // wrapped this PNK_FOR node in a PNK_LEXICALSCOPE representing the // implicit scope of those variables. By the time we get here, we have // already entered that scope. So far, so good. // // ### Scope freshening // // Each iteration of a `for (let V...)` loop creates a fresh loop variable // binding for V, even if the loop is a C-style `for(;;)` loop: // // var funcs = []; // for (let i = 0; i < 2; i++) // funcs.push(function() { return i; }); // assertEq(funcs[0](), 0); // the two closures capture... // assertEq(funcs[1](), 1); // ...two different `i` bindings // // This is implemented by "freshening" the implicit block -- changing the // scope chain to a fresh clone of the instantaneous block object -- each // iteration, just before evaluating the "update" in for(;;) loops. // // No freshening occurs in `for (const ...;;)` as there's no point: you // can't reassign consts. This is observable through the Debugger API. (The // ES6 spec also skips cloning the environment in this case.) bool forLoopRequiresFreshening = false; if (ParseNode* init = forHead->pn_kid1) { // Emit the `init` clause, whether it's an expression or a variable // declaration. (The loop variables were hoisted into an enclosing // scope, but we still need to emit code for the initializers.) if (!updateSourceCoordNotes(init->pn_pos.begin)) return false; if (!emitTree(init)) return false; if (!init->isForLoopDeclaration()) { // 'init' is an expression, not a declaration. emitTree left its // value on the stack. if (!emit1(JSOP_POP)) return false; } // ES 13.7.4.8 step 2. The initial freshening. // // If an initializer let-declaration may be captured during loop iteration, // the current scope has an environment. If so, freshen the current // environment to expose distinct bindings for each loop iteration. forLoopRequiresFreshening = init->isKind(PNK_LET) && headLexicalEmitterScope; if (forLoopRequiresFreshening) { // The environment chain only includes an environment for the for(;;) // loop head's let-declaration *if* a scope binding is captured, thus // requiring a fresh environment each iteration. If a lexical scope // exists for the head, it must be the innermost one. If that scope // has closed-over bindings inducing an environment, recreate the // current environment. MOZ_ASSERT(headLexicalEmitterScope == innermostEmitterScope); MOZ_ASSERT(headLexicalEmitterScope->scope(this)->kind() == ScopeKind::Lexical); if (headLexicalEmitterScope->hasEnvironment()) { if (!emit1(JSOP_FRESHENLEXICALENV)) return false; } } } /* * NB: the SRC_FOR note has offsetBias 1 (JSOP_NOP_LENGTH). * Use tmp to hold the biased srcnote "top" offset, which differs * from the top local variable by the length of the JSOP_GOTO * emitted in between tmp and top if this loop has a condition. */ unsigned noteIndex; if (!newSrcNote(SRC_FOR, ¬eIndex)) return false; if (!emit1(JSOP_NOP)) return false; ptrdiff_t tmp = offset(); JumpList jmp; if (forHead->pn_kid2) { /* Goto the loop condition, which branches back to iterate. */ if (!emitJump(JSOP_GOTO, &jmp)) return false; } /* Emit code for the loop body. */ JumpTarget top{ -1 }; if (!emitLoopHead(forBody, &top)) return false; if (jmp.offset == -1 && !emitLoopEntry(forBody, jmp)) return false; if (!emitTreeInBranch(forBody)) return false; // Set loop and enclosing "update" offsets, for continue. Note that we // continue to immediately *before* the block-freshening: continuing must // refresh the block. if (!emitJumpTarget(&loopInfo.continueTarget)) return false; // ES 13.7.4.8 step 3.e. The per-iteration freshening. if (forLoopRequiresFreshening) { MOZ_ASSERT(headLexicalEmitterScope == innermostEmitterScope); MOZ_ASSERT(headLexicalEmitterScope->scope(this)->kind() == ScopeKind::Lexical); if (headLexicalEmitterScope->hasEnvironment()) { if (!emit1(JSOP_FRESHENLEXICALENV)) return false; } } // Check for update code to do before the condition (if any). // The update code may not be executed at all; it needs its own TDZ cache. if (ParseNode* update = forHead->pn_kid3) { TDZCheckCache tdzCache(this); if (!updateSourceCoordNotes(update->pn_pos.begin)) return false; if (!emitTree(update)) return false; if (!emit1(JSOP_POP)) return false; /* Restore the absolute line number for source note readers. */ uint32_t lineNum = parser->tokenStream.srcCoords.lineNum(pn->pn_pos.end); if (currentLine() != lineNum) { if (!newSrcNote2(SRC_SETLINE, ptrdiff_t(lineNum))) return false; current->currentLine = lineNum; current->lastColumn = 0; } } ptrdiff_t tmp3 = offset(); if (forHead->pn_kid2) { /* Fix up the goto from top to target the loop condition. */ MOZ_ASSERT(jmp.offset >= 0); if (!emitLoopEntry(forHead->pn_kid2, jmp)) return false; if (!emitTree(forHead->pn_kid2)) return false; } else if (!forHead->pn_kid3) { // If there is no condition clause and no update clause, mark // the loop-ending "goto" with the location of the "for". // This ensures that the debugger will stop on each loop // iteration. if (!updateSourceCoordNotes(pn->pn_pos.begin)) return false; } /* Set the first note offset so we can find the loop condition. */ if (!setSrcNoteOffset(noteIndex, 0, tmp3 - tmp)) return false; if (!setSrcNoteOffset(noteIndex, 1, loopInfo.continueTarget.offset - tmp)) return false; /* If no loop condition, just emit a loop-closing jump. */ JumpList beq; JumpTarget breakTarget{ -1 }; if (!emitBackwardJump(forHead->pn_kid2 ? JSOP_IFNE : JSOP_GOTO, top, &beq, &breakTarget)) return false; /* The third note offset helps us find the loop-closing jump. */ if (!setSrcNoteOffset(noteIndex, 2, beq.offset - tmp)) return false; if (!tryNoteList.append(JSTRY_LOOP, stackDepth, top.offset, breakTarget.offset)) return false; if (!loopInfo.patchBreaksAndContinues(this)) return false; return true; } bool BytecodeEmitter::emitFor(ParseNode* pn, EmitterScope* headLexicalEmitterScope) { MOZ_ASSERT(pn->isKind(PNK_FOR)); if (pn->pn_left->isKind(PNK_FORHEAD)) return emitCStyleFor(pn, headLexicalEmitterScope); if (!updateLineNumberNotes(pn->pn_pos.begin)) return false; if (pn->pn_left->isKind(PNK_FORIN)) return emitForIn(pn, headLexicalEmitterScope); MOZ_ASSERT(pn->pn_left->isKind(PNK_FOROF)); return emitForOf(pn, headLexicalEmitterScope); } bool BytecodeEmitter::emitComprehensionForInOrOfVariables(ParseNode* pn, bool* lexicalScope) { // ES6 specifies that lexical for-loop variables get a fresh binding each // iteration, and that evaluation of the expression looped over occurs with // these variables dead zoned. But these rules only apply to *standard* // for-in/of loops, and we haven't extended these requirements to // comprehension syntax. *lexicalScope = pn->isKind(PNK_LEXICALSCOPE); if (*lexicalScope) { // This is initially-ES7-tracked syntax, now with considerably murkier // outlook. The scope work is done by the caller by instantiating an // EmitterScope. There's nothing to do here. } else { // This is legacy comprehension syntax. We'll have PNK_LET here, using // a lexical scope provided by/for the entire comprehension. Name // analysis assumes declarations initialize lets, but as we're handling // this declaration manually, we must also initialize manually to avoid // triggering dead zone checks. MOZ_ASSERT(pn->isKind(PNK_LET)); MOZ_ASSERT(pn->pn_count == 1); if (!emitDeclarationList(pn)) return false; } return true; } bool BytecodeEmitter::emitComprehensionForOf(ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_COMPREHENSIONFOR)); ParseNode* forHead = pn->pn_left; MOZ_ASSERT(forHead->isKind(PNK_FOROF)); ParseNode* forHeadExpr = forHead->pn_kid3; ParseNode* forBody = pn->pn_right; ParseNode* loopDecl = forHead->pn_kid1; bool lexicalScope = false; if (!emitComprehensionForInOrOfVariables(loopDecl, &lexicalScope)) return false; // For-of loops run with two values on the stack: the iterator and the // current result object. // Evaluate the expression to the right of 'of'. if (!emitTree(forHeadExpr)) // EXPR return false; if (!emitIterator()) // ITER return false; // Push a dummy result so that we properly enter iteration midstream. if (!emit1(JSOP_UNDEFINED)) // ITER RESULT return false; // Enter the block before the loop body, after evaluating the obj. // Initialize let bindings with undefined when entering, as the name // assigned to is a plain assignment. TDZCheckCache tdzCache(this); Maybe emitterScope; ParseNode* loopVariableName; if (lexicalScope) { loopVariableName = parser->handler.singleBindingFromDeclaration(loopDecl->pn_expr); emitterScope.emplace(this); if (!emitterScope->enterComprehensionFor(this, loopDecl->scopeBindings())) return false; } else { loopVariableName = parser->handler.singleBindingFromDeclaration(loopDecl); } LoopControl loopInfo(this, StatementKind::ForOfLoop); // Jump down to the loop condition to minimize overhead assuming at least // one iteration, as the other loop forms do. Annotate so IonMonkey can // find the loop-closing jump. unsigned noteIndex; if (!newSrcNote(SRC_FOR_OF, ¬eIndex)) return false; JumpList jmp; if (!emitJump(JSOP_GOTO, &jmp)) return false; JumpTarget top{ -1 }; if (!emitLoopHead(nullptr, &top)) return false; #ifdef DEBUG int loopDepth = this->stackDepth; #endif // Emit code to assign result.value to the iteration variable. if (!emit1(JSOP_DUP)) // ITER RESULT RESULT return false; if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER RESULT VALUE return false; if (!emitAssignment(loopVariableName, JSOP_NOP, nullptr)) // ITER RESULT VALUE return false; if (!emit1(JSOP_POP)) // ITER RESULT return false; // The stack should be balanced around the assignment opcode sequence. MOZ_ASSERT(this->stackDepth == loopDepth); // Emit code for the loop body. if (!emitTree(forBody)) return false; // Set offset for continues. loopInfo.continueTarget = { offset() }; if (!emitLoopEntry(forHeadExpr, jmp)) return false; if (!emit1(JSOP_POP)) // ITER return false; if (!emit1(JSOP_DUP)) // ITER ITER return false; if (!emitIteratorNext(forHead)) // ITER RESULT return false; if (!emit1(JSOP_DUP)) // ITER RESULT RESULT return false; if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE return false; JumpList beq; JumpTarget breakTarget{ -1 }; if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) // ITER RESULT return false; MOZ_ASSERT(this->stackDepth == loopDepth); // Let Ion know where the closing jump of this loop is. if (!setSrcNoteOffset(noteIndex, 0, beq.offset - jmp.offset)) return false; if (!loopInfo.patchBreaksAndContinues(this)) return false; if (!tryNoteList.append(JSTRY_FOR_OF, stackDepth, top.offset, breakTarget.offset)) return false; if (emitterScope) { if (!emitterScope->leave(this)) return false; emitterScope.reset(); } // Pop the result and the iter. return emitUint16Operand(JSOP_POPN, 2); // } bool BytecodeEmitter::emitComprehensionForIn(ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_COMPREHENSIONFOR)); ParseNode* forHead = pn->pn_left; MOZ_ASSERT(forHead->isKind(PNK_FORIN)); ParseNode* forBody = pn->pn_right; ParseNode* loopDecl = forHead->pn_kid1; bool lexicalScope = false; if (loopDecl && !emitComprehensionForInOrOfVariables(loopDecl, &lexicalScope)) return false; // Evaluate the expression to the right of 'in'. if (!emitTree(forHead->pn_kid3)) return false; /* * Emit a bytecode to convert top of stack value to the iterator * object depending on the loop variant (for-in, for-each-in, or * destructuring for-in). */ MOZ_ASSERT(pn->isOp(JSOP_ITER)); if (!emit2(JSOP_ITER, (uint8_t) pn->pn_iflags)) return false; // For-in loops have both the iterator and the value on the stack. Push // undefined to balance the stack. if (!emit1(JSOP_UNDEFINED)) return false; // Enter the block before the loop body, after evaluating the obj. // Initialize let bindings with undefined when entering, as the name // assigned to is a plain assignment. TDZCheckCache tdzCache(this); Maybe emitterScope; if (lexicalScope) { emitterScope.emplace(this); if (!emitterScope->enterComprehensionFor(this, loopDecl->scopeBindings())) return false; } LoopControl loopInfo(this, StatementKind::ForInLoop); /* Annotate so IonMonkey can find the loop-closing jump. */ unsigned noteIndex; if (!newSrcNote(SRC_FOR_IN, ¬eIndex)) return false; /* * Jump down to the loop condition to minimize overhead assuming at * least one iteration, as the other loop forms do. */ JumpList jmp; if (!emitJump(JSOP_GOTO, &jmp)) return false; JumpTarget top{ -1 }; if (!emitLoopHead(nullptr, &top)) return false; #ifdef DEBUG int loopDepth = this->stackDepth; #endif // Emit code to assign the enumeration value to the left hand side, but // also leave it on the stack. if (!emitAssignment(forHead->pn_kid2, JSOP_NOP, nullptr)) return false; /* The stack should be balanced around the assignment opcode sequence. */ MOZ_ASSERT(this->stackDepth == loopDepth); /* Emit code for the loop body. */ if (!emitTree(forBody)) return false; // Set offset for continues. loopInfo.continueTarget = { offset() }; if (!emitLoopEntry(nullptr, jmp)) return false; if (!emit1(JSOP_POP)) return false; if (!emit1(JSOP_MOREITER)) return false; if (!emit1(JSOP_ISNOITER)) return false; JumpList beq; JumpTarget breakTarget{ -1 }; if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) return false; /* Set the srcnote offset so we can find the closing jump. */ if (!setSrcNoteOffset(noteIndex, 0, beq.offset - jmp.offset)) return false; if (!loopInfo.patchBreaksAndContinues(this)) return false; // Pop the enumeration value. if (!emit1(JSOP_POP)) return false; JumpTarget endIter{ offset() }; if (!tryNoteList.append(JSTRY_FOR_IN, this->stackDepth, top.offset, endIter.offset)) return false; if (!emit1(JSOP_ENDITER)) return false; if (emitterScope) { if (!emitterScope->leave(this)) return false; emitterScope.reset(); } return true; } bool BytecodeEmitter::emitComprehensionFor(ParseNode* compFor) { MOZ_ASSERT(compFor->pn_left->isKind(PNK_FORIN) || compFor->pn_left->isKind(PNK_FOROF)); if (!updateLineNumberNotes(compFor->pn_pos.begin)) return false; return compFor->pn_left->isKind(PNK_FORIN) ? emitComprehensionForIn(compFor) : emitComprehensionForOf(compFor); } MOZ_NEVER_INLINE bool BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) { FunctionBox* funbox = pn->pn_funbox; RootedFunction fun(cx, funbox->function()); RootedAtom name(cx, fun->explicitName()); MOZ_ASSERT_IF(fun->isInterpretedLazy(), fun->lazyScript()); /* * Set the |wasEmitted| flag in the funbox once the function has been * emitted. Function definitions that need hoisting to the top of the * function will be seen by emitFunction in two places. */ if (funbox->wasEmitted) { // Annex B block-scoped functions are hoisted like any other // block-scoped function to the top of their scope. When their // definitions are seen for the second time, we need to emit the // assignment that assigns the function to the outer 'var' binding. if (funbox->isAnnexB) { auto emitRhs = [&name](BytecodeEmitter* bce, const NameLocation&, bool) { // The RHS is the value of the lexically bound name in the // innermost scope. return bce->emitGetName(name); }; // Get the location of the 'var' binding in the body scope. The // name must be found, else there is a bug in the Annex B handling // in Parser. // // In sloppy eval contexts, this location is dynamic. Maybe lhsLoc = locationOfNameBoundInScope(name, varEmitterScope); // If there are parameter expressions, the var name could be a // parameter. if (!lhsLoc && sc->isFunctionBox() && sc->asFunctionBox()->hasExtraBodyVarScope()) lhsLoc = locationOfNameBoundInScope(name, varEmitterScope->enclosingInFrame()); if (!lhsLoc) { lhsLoc = Some(NameLocation::DynamicAnnexBVar()); } else { MOZ_ASSERT(lhsLoc->bindingKind() == BindingKind::Var || lhsLoc->bindingKind() == BindingKind::FormalParameter || (lhsLoc->bindingKind() == BindingKind::Let && sc->asFunctionBox()->hasParameterExprs)); } if (!emitSetOrInitializeNameAtLocation(name, *lhsLoc, emitRhs, false)) return false; if (!emit1(JSOP_POP)) return false; } MOZ_ASSERT_IF(fun->hasScript(), fun->nonLazyScript()); MOZ_ASSERT(pn->functionIsHoisted()); return true; } funbox->wasEmitted = true; /* * Mark as singletons any function which will only be executed once, or * which is inner to a lambda we only expect to run once. In the latter * case, if the lambda runs multiple times then CloneFunctionObject will * make a deep clone of its contents. */ if (fun->isInterpreted()) { bool singleton = checkRunOnceContext(); if (!JSFunction::setTypeForScriptedFunction(cx, fun, singleton)) return false; SharedContext* outersc = sc; if (fun->isInterpretedLazy()) { // We need to update the static scope chain regardless of whether // the LazyScript has already been initialized, due to the case // where we previously successfully compiled an inner function's // lazy script but failed to compile the outer script after the // fact. If we attempt to compile the outer script again, the // static scope chain will be newly allocated and will mismatch // the previously compiled LazyScript's. ScriptSourceObject* source = &script->sourceObject()->as(); fun->lazyScript()->setEnclosingScopeAndSource(innermostScope(), source); if (emittingRunOnceLambda) fun->lazyScript()->setTreatAsRunOnce(); } else { MOZ_ASSERT_IF(outersc->strict(), funbox->strictScript); // Inherit most things (principals, version, etc) from the // parent. Use default values for the rest. Rooted parent(cx, script); MOZ_ASSERT(parent->getVersion() == parser->options().version); MOZ_ASSERT(parent->mutedErrors() == parser->options().mutedErrors()); const TransitiveCompileOptions& transitiveOptions = parser->options(); CompileOptions options(cx, transitiveOptions); Rooted sourceObject(cx, script->sourceObject()); Rooted script(cx, JSScript::Create(cx, options, sourceObject, funbox->bufStart, funbox->bufEnd)); if (!script) return false; BytecodeEmitter bce2(this, parser, funbox, script, /* lazyScript = */ nullptr, pn->pn_pos, emitterMode); if (!bce2.init()) return false; /* We measured the max scope depth when we parsed the function. */ if (!bce2.emitFunctionScript(pn->pn_body)) return false; if (funbox->isLikelyConstructorWrapper()) script->setLikelyConstructorWrapper(); } if (outersc->isFunctionBox()) outersc->asFunctionBox()->setHasInnerFunctions(); } else { MOZ_ASSERT(IsAsmJSModule(fun)); } /* Make the function object a literal in the outer script's pool. */ unsigned index = objectList.add(pn->pn_funbox); /* Non-hoisted functions simply emit their respective op. */ if (!pn->functionIsHoisted()) { /* JSOP_LAMBDA_ARROW is always preceded by a new.target */ MOZ_ASSERT(fun->isArrow() == (pn->getOp() == JSOP_LAMBDA_ARROW)); if (funbox->isAsync()) { MOZ_ASSERT(!needsProto); return emitAsyncWrapper(index, funbox->needsHomeObject(), fun->isArrow()); } if (fun->isArrow()) { if (sc->allowNewTarget()) { if (!emit1(JSOP_NEWTARGET)) return false; } else { if (!emit1(JSOP_NULL)) return false; } } if (needsProto) { MOZ_ASSERT(pn->getOp() == JSOP_LAMBDA); pn->setOp(JSOP_FUNWITHPROTO); } if (pn->getOp() == JSOP_DEFFUN) { if (!emitIndex32(JSOP_LAMBDA, index)) return false; return emit1(JSOP_DEFFUN); } return emitIndex32(pn->getOp(), index); } MOZ_ASSERT(!needsProto); bool topLevelFunction; if (sc->isFunctionBox() || (sc->isEvalContext() && sc->strict())) { // No nested functions inside other functions are top-level. topLevelFunction = false; } else { // In sloppy eval scripts, top-level functions in are accessed // dynamically. In global and module scripts, top-level functions are // those bound in the var scope. NameLocation loc = lookupName(name); topLevelFunction = loc.kind() == NameLocation::Kind::Dynamic || loc.bindingKind() == BindingKind::Var; } if (topLevelFunction) { if (sc->isModuleContext()) { // For modules, we record the function and instantiate the binding // during ModuleDeclarationInstantiation(), before the script is run. RootedModuleObject module(cx, sc->asModuleContext()->module()); if (!module->noteFunctionDeclaration(cx, name, fun)) return false; } else { MOZ_ASSERT(sc->isGlobalContext() || sc->isEvalContext()); MOZ_ASSERT(pn->getOp() == JSOP_NOP); switchToPrologue(); if (funbox->isAsync()) { if (!emitAsyncWrapper(index, fun->isMethod(), fun->isArrow())) return false; } else { if (!emitIndex32(JSOP_LAMBDA, index)) return false; } if (!emit1(JSOP_DEFFUN)) return false; if (!updateSourceCoordNotes(pn->pn_pos.begin)) return false; switchToMain(); } } else { // For functions nested within functions and blocks, make a lambda and // initialize the binding name of the function in the current scope. bool isAsync = funbox->isAsync(); auto emitLambda = [index, isAsync](BytecodeEmitter* bce, const NameLocation&, bool) { if (isAsync) { return bce->emitAsyncWrapper(index, /* needsHomeObject = */ false, /* isArrow = */ false); } return bce->emitIndexOp(JSOP_LAMBDA, index); }; if (!emitInitializeName(name, emitLambda)) return false; if (!emit1(JSOP_POP)) return false; } return true; } bool BytecodeEmitter::emitAsyncWrapperLambda(unsigned index, bool isArrow) { if (isArrow) { if (sc->allowNewTarget()) { if (!emit1(JSOP_NEWTARGET)) return false; } else { if (!emit1(JSOP_NULL)) return false; } if (!emitIndex32(JSOP_LAMBDA_ARROW, index)) return false; } else { if (!emitIndex32(JSOP_LAMBDA, index)) return false; } return true; } bool BytecodeEmitter::emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isArrow) { // needsHomeObject can be true for propertyList for extended class. // In that case push both unwrapped and wrapped function, in order to // initialize home object of unwrapped function, and set wrapped function // as a property. // // lambda // unwrapped // dup // unwrapped unwrapped // toasync // unwrapped wrapped // // Emitted code is surrounded by the following code. // // // classObj classCtor classProto // (emitted code) // classObj classCtor classProto unwrapped wrapped // swap // classObj classCtor classProto wrapped unwrapped // inithomeobject 1 // classObj classCtor classProto wrapped unwrapped // // initialize the home object of unwrapped // // with classProto here // pop // classObj classCtor classProto wrapped // inithiddenprop // classObj classCtor classProto wrapped // // initialize the property of the classProto // // with wrapped function here // pop // classObj classCtor classProto // // needsHomeObject is false for other cases, push wrapped function only. if (!emitAsyncWrapperLambda(index, isArrow)) return false; if (needsHomeObject) { if (!emit1(JSOP_DUP)) return false; } if (!emit1(JSOP_TOASYNC)) return false; return true; } bool BytecodeEmitter::emitDo(ParseNode* pn) { /* Emit an annotated nop so IonBuilder can recognize the 'do' loop. */ unsigned noteIndex; if (!newSrcNote(SRC_WHILE, ¬eIndex)) return false; if (!emit1(JSOP_NOP)) return false; unsigned noteIndex2; if (!newSrcNote(SRC_WHILE, ¬eIndex2)) return false; /* Compile the loop body. */ JumpTarget top; if (!emitLoopHead(pn->pn_left, &top)) return false; LoopControl loopInfo(this, StatementKind::DoLoop); JumpList empty; if (!emitLoopEntry(nullptr, empty)) return false; if (!emitTree(pn->pn_left)) return false; // Set the offset for continues. if (!emitJumpTarget(&loopInfo.continueTarget)) return false; /* Compile the loop condition, now that continues know where to go. */ if (!emitTree(pn->pn_right)) return false; JumpList beq; JumpTarget breakTarget{ -1 }; if (!emitBackwardJump(JSOP_IFNE, top, &beq, &breakTarget)) return false; if (!tryNoteList.append(JSTRY_LOOP, stackDepth, top.offset, breakTarget.offset)) return false; /* * Update the annotations with the update and back edge positions, for * IonBuilder. * * Be careful: We must set noteIndex2 before noteIndex in case the noteIndex * note gets bigger. */ if (!setSrcNoteOffset(noteIndex2, 0, beq.offset - top.offset)) return false; if (!setSrcNoteOffset(noteIndex, 0, 1 + (loopInfo.continueTarget.offset - top.offset))) return false; if (!loopInfo.patchBreaksAndContinues(this)) return false; return true; } bool BytecodeEmitter::emitWhile(ParseNode* pn) { /* * Minimize bytecodes issued for one or more iterations by jumping to * the condition below the body and closing the loop if the condition * is true with a backward branch. For iteration count i: * * i test at the top test at the bottom * = =============== ================== * 0 ifeq-pass goto; ifne-fail * 1 ifeq-fail; goto; ifne-pass goto; ifne-pass; ifne-fail * 2 2*(ifeq-fail; goto); ifeq-pass goto; 2*ifne-pass; ifne-fail * . . . * N N*(ifeq-fail; goto); ifeq-pass goto; N*ifne-pass; ifne-fail */ // If we have a single-line while, like "while (x) ;", we want to // emit the line note before the initial goto, so that the // debugger sees a single entry point. This way, if there is a // breakpoint on the line, it will only fire once; and "next"ing // will skip the whole loop. However, for the multi-line case we // want to emit the line note after the initial goto, so that // "cont" stops on each iteration -- but without a stop before the // first iteration. if (parser->tokenStream.srcCoords.lineNum(pn->pn_pos.begin) == parser->tokenStream.srcCoords.lineNum(pn->pn_pos.end) && !updateSourceCoordNotes(pn->pn_pos.begin)) return false; JumpTarget top{ -1 }; if (!emitJumpTarget(&top)) return false; LoopControl loopInfo(this, StatementKind::WhileLoop); loopInfo.continueTarget = top; unsigned noteIndex; if (!newSrcNote(SRC_WHILE, ¬eIndex)) return false; JumpList jmp; if (!emitJump(JSOP_GOTO, &jmp)) return false; if (!emitLoopHead(pn->pn_right, &top)) return false; if (!emitTreeInBranch(pn->pn_right)) return false; if (!emitLoopEntry(pn->pn_left, jmp)) return false; if (!emitTree(pn->pn_left)) return false; JumpList beq; JumpTarget breakTarget{ -1 }; if (!emitBackwardJump(JSOP_IFNE, top, &beq, &breakTarget)) return false; if (!tryNoteList.append(JSTRY_LOOP, stackDepth, top.offset, breakTarget.offset)) return false; if (!setSrcNoteOffset(noteIndex, 0, beq.offset - jmp.offset)) return false; if (!loopInfo.patchBreaksAndContinues(this)) return false; return true; } bool BytecodeEmitter::emitBreak(PropertyName* label) { BreakableControl* target; SrcNoteType noteType; if (label) { // Any statement with the matching label may be the break target. auto hasSameLabel = [label](LabelControl* labelControl) { return labelControl->label() == label; }; target = findInnermostNestableControl(hasSameLabel); noteType = SRC_BREAK2LABEL; } else { auto isNotLabel = [](BreakableControl* control) { return !control->is(); }; target = findInnermostNestableControl(isNotLabel); noteType = (target->kind() == StatementKind::Switch) ? SRC_SWITCHBREAK : SRC_BREAK; } return emitGoto(target, &target->breaks, noteType); } bool BytecodeEmitter::emitContinue(PropertyName* label) { LoopControl* target = nullptr; if (label) { // Find the loop statement enclosed by the matching label. NestableControl* control = innermostNestableControl; while (!control->is() || control->as().label() != label) { if (control->is()) target = &control->as(); control = control->enclosing(); } } else { target = findInnermostNestableControl(); } return emitGoto(target, &target->continues, SRC_CONTINUE); } bool BytecodeEmitter::emitGetFunctionThis(ParseNode* pn) { MOZ_ASSERT(sc->thisBinding() == ThisBinding::Function); MOZ_ASSERT(pn->isKind(PNK_NAME)); MOZ_ASSERT(pn->name() == cx->names().dotThis); if (!emitTree(pn)) return false; if (sc->needsThisTDZChecks() && !emit1(JSOP_CHECKTHIS)) return false; return true; } bool BytecodeEmitter::emitGetThisForSuperBase(ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_SUPERBASE)); return emitGetFunctionThis(pn->pn_kid); } bool BytecodeEmitter::emitThisLiteral(ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_THIS)); if (ParseNode* thisName = pn->pn_kid) return emitGetFunctionThis(thisName); if (sc->thisBinding() == ThisBinding::Module) return emit1(JSOP_UNDEFINED); MOZ_ASSERT(sc->thisBinding() == ThisBinding::Global); return emit1(JSOP_GLOBALTHIS); } bool BytecodeEmitter::emitCheckDerivedClassConstructorReturn() { MOZ_ASSERT(lookupName(cx->names().dotThis).hasKnownSlot()); if (!emitGetName(cx->names().dotThis)) return false; if (!emit1(JSOP_CHECKRETURN)) return false; return true; } bool BytecodeEmitter::emitReturn(ParseNode* pn) { if (!updateSourceCoordNotes(pn->pn_pos.begin)) return false; if (sc->isFunctionBox() && sc->asFunctionBox()->isStarGenerator()) { if (!emitPrepareIteratorResult()) return false; } /* Push a return value */ if (ParseNode* pn2 = pn->pn_kid) { if (!emitTree(pn2)) return false; } else { /* No explicit return value provided */ if (!emit1(JSOP_UNDEFINED)) return false; } if (sc->isFunctionBox() && sc->asFunctionBox()->isStarGenerator()) { if (!emitFinishIteratorResult(true)) return false; } // We know functionBodyEndPos is set because "return" is only // valid in a function, and so we've passed through // emitFunctionScript. MOZ_ASSERT(functionBodyEndPosSet); if (!updateSourceCoordNotes(functionBodyEndPos)) return false; /* * EmitNonLocalJumpFixup may add fixup bytecode to close open try * blocks having finally clauses and to exit intermingled let blocks. * We can't simply transfer control flow to our caller in that case, * because we must gosub to those finally clauses from inner to outer, * with the correct stack pointer (i.e., after popping any with, * for/in, etc., slots nested inside the finally's try). * * In this case we mutate JSOP_RETURN into JSOP_SETRVAL and add an * extra JSOP_RETRVAL after the fixups. */ ptrdiff_t top = offset(); bool isGenerator = sc->isFunctionBox() && sc->asFunctionBox()->isGenerator(); bool isDerivedClassConstructor = sc->isFunctionBox() && sc->asFunctionBox()->isDerivedClassConstructor(); if (!emit1((isGenerator || isDerivedClassConstructor) ? JSOP_SETRVAL : JSOP_RETURN)) return false; // Make sure that we emit this before popping the blocks in prepareForNonLocalJump, // to ensure that the error is thrown while the scope-chain is still intact. if (isDerivedClassConstructor) { if (!emitCheckDerivedClassConstructorReturn()) return false; } NonLocalExitControl nle(this); if (!nle.prepareForNonLocalJumpToOutermost()) return false; if (isGenerator) { // We know that .generator is on the function scope, as we just exited // all nested scopes. NameLocation loc = *locationOfNameBoundInFunctionScope(cx->names().dotGenerator, varEmitterScope); if (!emitGetNameAtLocation(cx->names().dotGenerator, loc)) return false; if (!emitYieldOp(JSOP_FINALYIELDRVAL)) return false; } else if (isDerivedClassConstructor) { MOZ_ASSERT(code()[top] == JSOP_SETRVAL); if (!emit1(JSOP_RETRVAL)) return false; } else if (top + static_cast(JSOP_RETURN_LENGTH) != offset()) { code()[top] = JSOP_SETRVAL; if (!emit1(JSOP_RETRVAL)) return false; } return true; } bool BytecodeEmitter::emitYield(ParseNode* pn) { MOZ_ASSERT(sc->isFunctionBox()); if (pn->getOp() == JSOP_YIELD) { if (sc->asFunctionBox()->isStarGenerator()) { if (!emitPrepareIteratorResult()) return false; } if (pn->pn_left) { if (!emitTree(pn->pn_left)) return false; } else { if (!emit1(JSOP_UNDEFINED)) return false; } if (sc->asFunctionBox()->isStarGenerator()) { if (!emitFinishIteratorResult(false)) return false; } } else { MOZ_ASSERT(pn->getOp() == JSOP_INITIALYIELD); } if (!emitTree(pn->pn_right)) return false; if (!emitYieldOp(pn->getOp())) return false; if (pn->getOp() == JSOP_INITIALYIELD && !emit1(JSOP_POP)) return false; return true; } bool BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) { MOZ_ASSERT(sc->isFunctionBox()); MOZ_ASSERT(sc->asFunctionBox()->isStarGenerator()); if (!emitTree(iter)) // ITERABLE return false; if (!emitIterator()) // ITER return false; // Initial send value is undefined. if (!emit1(JSOP_UNDEFINED)) // ITER RECEIVED return false; int depth = stackDepth; MOZ_ASSERT(depth >= 2); JumpList send; if (!emitJump(JSOP_GOTO, &send)) // goto send return false; // Try prologue. // ITER RESULT unsigned noteIndex; if (!newSrcNote(SRC_TRY, ¬eIndex)) return false; JumpTarget tryStart{ offset() }; if (!emit1(JSOP_TRY)) // tryStart: return false; MOZ_ASSERT(this->stackDepth == depth); // Load the generator object. if (!emitTree(gen)) // ITER RESULT GENOBJ return false; // Yield RESULT as-is, without re-boxing. if (!emitYieldOp(JSOP_YIELD)) // ITER RECEIVED return false; // Try epilogue. if (!setSrcNoteOffset(noteIndex, 0, offset() - tryStart.offset)) return false; if (!emitJump(JSOP_GOTO, &send)) // goto send return false; JumpTarget tryEnd; if (!emitJumpTarget(&tryEnd)) // tryEnd: return false; // Catch location. stackDepth = uint32_t(depth); // ITER RESULT if (!emit1(JSOP_POP)) // ITER return false; // THROW? = 'throw' in ITER if (!emit1(JSOP_EXCEPTION)) // ITER EXCEPTION return false; if (!emit1(JSOP_SWAP)) // EXCEPTION ITER return false; if (!emit1(JSOP_DUP)) // EXCEPTION ITER ITER return false; if (!emitAtomOp(cx->names().throw_, JSOP_STRING)) // EXCEPTION ITER ITER "throw" return false; if (!emit1(JSOP_SWAP)) // EXCEPTION ITER "throw" ITER return false; if (!emit1(JSOP_IN)) // EXCEPTION ITER THROW? return false; // if (THROW?) goto delegate JumpList checkThrow; if (!emitJump(JSOP_IFNE, &checkThrow)) // EXCEPTION ITER return false; if (!emit1(JSOP_POP)) // EXCEPTION return false; if (!emit1(JSOP_THROW)) // throw EXCEPTION return false; if (!emitJumpTargetAndPatch(checkThrow)) // delegate: return false; // RESULT = ITER.throw(EXCEPTION) // EXCEPTION ITER stackDepth = uint32_t(depth); if (!emit1(JSOP_DUP)) // EXCEPTION ITER ITER return false; if (!emit1(JSOP_DUP)) // EXCEPTION ITER ITER ITER return false; if (!emitAtomOp(cx->names().throw_, JSOP_CALLPROP)) // EXCEPTION ITER ITER THROW return false; if (!emit1(JSOP_SWAP)) // EXCEPTION ITER THROW ITER return false; if (!emit2(JSOP_PICK, 3)) // ITER THROW ITER EXCEPTION return false; if (!emitCall(JSOP_CALL, 1, iter)) // ITER RESULT return false; checkTypeSet(JSOP_CALL); MOZ_ASSERT(this->stackDepth == depth); JumpList checkResult; if (!emitJump(JSOP_GOTO, &checkResult)) // goto checkResult return false; // Catch epilogue. // This is a peace offering to ReconstructPCStack. See the note in EmitTry. if (!emit1(JSOP_NOP)) return false; if (!tryNoteList.append(JSTRY_CATCH, depth, tryStart.offset + JSOP_TRY_LENGTH, tryEnd.offset)) return false; // After the try/catch block: send the received value to the iterator. if (!emitJumpTargetAndPatch(send)) // send: return false; // Send location. // result = iter.next(received) // ITER RECEIVED if (!emit1(JSOP_SWAP)) // RECEIVED ITER return false; if (!emit1(JSOP_DUP)) // RECEIVED ITER ITER return false; if (!emit1(JSOP_DUP)) // RECEIVED ITER ITER ITER return false; if (!emitAtomOp(cx->names().next, JSOP_CALLPROP)) // RECEIVED ITER ITER NEXT return false; if (!emit1(JSOP_SWAP)) // RECEIVED ITER NEXT ITER return false; if (!emit2(JSOP_PICK, 3)) // ITER NEXT ITER RECEIVED return false; if (!emitCall(JSOP_CALL, 1, iter)) // ITER RESULT return false; if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) // ITER RESULT return false; checkTypeSet(JSOP_CALL); MOZ_ASSERT(this->stackDepth == depth); if (!emitJumpTargetAndPatch(checkResult)) // checkResult: return false; // if (!result.done) goto tryStart; // ITER RESULT if (!emit1(JSOP_DUP)) // ITER RESULT RESULT return false; if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE return false; // if (!DONE) goto tryStart; JumpList beq; JumpTarget breakTarget{ -1 }; if (!emitBackwardJump(JSOP_IFEQ, tryStart, &beq, &breakTarget)) // ITER RESULT return false; // result.value if (!emit1(JSOP_SWAP)) // RESULT ITER return false; if (!emit1(JSOP_POP)) // RESULT return false; if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // VALUE return false; MOZ_ASSERT(this->stackDepth == depth - 1); return true; } bool BytecodeEmitter::emitStatementList(ParseNode* pn) { MOZ_ASSERT(pn->isArity(PN_LIST)); for (ParseNode* pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) { if (!emitTree(pn2)) return false; } return true; } bool BytecodeEmitter::emitStatement(ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_SEMI)); ParseNode* pn2 = pn->pn_kid; if (!pn2) return true; if (!updateSourceCoordNotes(pn->pn_pos.begin)) return false; /* * Top-level or called-from-a-native JS_Execute/EvaluateScript, * debugger, and eval frames may need the value of the ultimate * expression statement as the script's result, despite the fact * that it appears useless to the compiler. * * API users may also set the JSOPTION_NO_SCRIPT_RVAL option when * calling JS_Compile* to suppress JSOP_SETRVAL. */ bool wantval = false; bool useful = false; if (sc->isFunctionBox()) MOZ_ASSERT(!script->noScriptRval()); else useful = wantval = !script->noScriptRval(); /* Don't eliminate expressions with side effects. */ if (!useful) { if (!checkSideEffects(pn2, &useful)) return false; /* * Don't eliminate apparently useless expressions if they are labeled * expression statements. The startOffset() test catches the case * where we are nesting in emitTree for a labeled compound statement. */ if (innermostNestableControl && innermostNestableControl->is() && innermostNestableControl->as().startOffset() >= offset()) { useful = true; } } if (useful) { JSOp op = wantval ? JSOP_SETRVAL : JSOP_POP; MOZ_ASSERT_IF(pn2->isKind(PNK_ASSIGN), pn2->isOp(JSOP_NOP)); if (!emitTree(pn2)) return false; if (!emit1(op)) return false; } else if (pn->isDirectivePrologueMember()) { // Don't complain about directive prologue members; just don't emit // their code. } else { if (JSAtom* atom = pn->isStringExprStatement()) { // Warn if encountering a non-directive prologue member string // expression statement, that is inconsistent with the current // directive prologue. That is, a script *not* starting with // "use strict" should warn for any "use strict" statements seen // later in the script, because such statements are misleading. const char* directive = nullptr; if (atom == cx->names().useStrict) { if (!sc->strictScript) directive = js_useStrict_str; } else if (atom == cx->names().useAsm) { if (sc->isFunctionBox()) { if (IsAsmJSModule(sc->asFunctionBox()->function())) directive = js_useAsm_str; } } if (directive) { if (!reportStrictWarning(pn2, JSMSG_CONTRARY_NONDIRECTIVE, directive)) return false; } } else { current->currentLine = parser->tokenStream.srcCoords.lineNum(pn2->pn_pos.begin); current->lastColumn = 0; if (!reportStrictWarning(pn2, JSMSG_USELESS_EXPR)) return false; } } return true; } bool BytecodeEmitter::emitDeleteName(ParseNode* node) { MOZ_ASSERT(node->isKind(PNK_DELETENAME)); MOZ_ASSERT(node->isArity(PN_UNARY)); ParseNode* nameExpr = node->pn_kid; MOZ_ASSERT(nameExpr->isKind(PNK_NAME)); return emitAtomOp(nameExpr, JSOP_DELNAME); } bool BytecodeEmitter::emitDeleteProperty(ParseNode* node) { MOZ_ASSERT(node->isKind(PNK_DELETEPROP)); MOZ_ASSERT(node->isArity(PN_UNARY)); ParseNode* propExpr = node->pn_kid; MOZ_ASSERT(propExpr->isKind(PNK_DOT)); if (propExpr->as().isSuper()) { // Still have to calculate the base, even though we are are going // to throw unconditionally, as calculating the base could also // throw. if (!emit1(JSOP_SUPERBASE)) return false; return emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER); } JSOp delOp = sc->strict() ? JSOP_STRICTDELPROP : JSOP_DELPROP; return emitPropOp(propExpr, delOp); } bool BytecodeEmitter::emitDeleteElement(ParseNode* node) { MOZ_ASSERT(node->isKind(PNK_DELETEELEM)); MOZ_ASSERT(node->isArity(PN_UNARY)); ParseNode* elemExpr = node->pn_kid; MOZ_ASSERT(elemExpr->isKind(PNK_ELEM)); if (elemExpr->as().isSuper()) { // Still have to calculate everything, even though we're gonna throw // since it may have side effects if (!emitTree(elemExpr->pn_right)) return false; if (!emit1(JSOP_SUPERBASE)) return false; if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER)) return false; // Another wrinkle: Balance the stack from the emitter's point of view. // Execution will not reach here, as the last bytecode threw. return emit1(JSOP_POP); } JSOp delOp = sc->strict() ? JSOP_STRICTDELELEM : JSOP_DELELEM; return emitElemOp(elemExpr, delOp); } bool BytecodeEmitter::emitDeleteExpression(ParseNode* node) { MOZ_ASSERT(node->isKind(PNK_DELETEEXPR)); MOZ_ASSERT(node->isArity(PN_UNARY)); ParseNode* expression = node->pn_kid; // If useless, just emit JSOP_TRUE; otherwise convert |delete | to // effectively |, true|. bool useful = false; if (!checkSideEffects(expression, &useful)) return false; if (useful) { if (!emitTree(expression)) return false; if (!emit1(JSOP_POP)) return false; } return emit1(JSOP_TRUE); } static const char * SelfHostedCallFunctionName(JSAtom* name, ExclusiveContext* cx) { if (name == cx->names().callFunction) return "callFunction"; if (name == cx->names().callContentFunction) return "callContentFunction"; if (name == cx->names().constructContentFunction) return "constructContentFunction"; MOZ_CRASH("Unknown self-hosted call function name"); } bool BytecodeEmitter::emitSelfHostedCallFunction(ParseNode* pn) { // Special-casing of callFunction to emit bytecode that directly // invokes the callee with the correct |this| object and arguments. // callFunction(fun, thisArg, arg0, arg1) thus becomes: // - emit lookup for fun // - emit lookup for thisArg // - emit lookups for arg0, arg1 // // argc is set to the amount of actually emitted args and the // emitting of args below is disabled by setting emitArgs to false. ParseNode* pn2 = pn->pn_head; const char* errorName = SelfHostedCallFunctionName(pn2->name(), cx); if (pn->pn_count < 3) { reportError(pn, JSMSG_MORE_ARGS_NEEDED, errorName, "2", "s"); return false; } JSOp callOp = pn->getOp(); if (callOp != JSOP_CALL) { reportError(pn, JSMSG_NOT_CONSTRUCTOR, errorName); return false; } bool constructing = pn2->name() == cx->names().constructContentFunction; ParseNode* funNode = pn2->pn_next; if (constructing) callOp = JSOP_NEW; else if (funNode->getKind() == PNK_NAME && funNode->name() == cx->names().std_Function_apply) callOp = JSOP_FUNAPPLY; if (!emitTree(funNode)) return false; #ifdef DEBUG if (emitterMode == BytecodeEmitter::SelfHosting && pn2->name() == cx->names().callFunction) { if (!emit1(JSOP_DEBUGCHECKSELFHOSTED)) return false; } #endif ParseNode* thisOrNewTarget = funNode->pn_next; if (constructing) { // Save off the new.target value, but here emit a proper |this| for a // constructing call. if (!emit1(JSOP_IS_CONSTRUCTING)) return false; } else { // It's |this|, emit it. if (!emitTree(thisOrNewTarget)) return false; } for (ParseNode* argpn = thisOrNewTarget->pn_next; argpn; argpn = argpn->pn_next) { if (!emitTree(argpn)) return false; } if (constructing) { if (!emitTree(thisOrNewTarget)) return false; } uint32_t argc = pn->pn_count - 3; if (!emitCall(callOp, argc)) return false; checkTypeSet(callOp); return true; } bool BytecodeEmitter::emitSelfHostedResumeGenerator(ParseNode* pn) { // Syntax: resumeGenerator(gen, value, 'next'|'throw'|'close') if (pn->pn_count != 4) { reportError(pn, JSMSG_MORE_ARGS_NEEDED, "resumeGenerator", "1", "s"); return false; } ParseNode* funNode = pn->pn_head; // The resumeGenerator node. ParseNode* genNode = funNode->pn_next; if (!emitTree(genNode)) return false; ParseNode* valNode = genNode->pn_next; if (!emitTree(valNode)) return false; ParseNode* kindNode = valNode->pn_next; MOZ_ASSERT(kindNode->isKind(PNK_STRING)); uint16_t operand = GeneratorObject::getResumeKind(cx, kindNode->pn_atom); MOZ_ASSERT(!kindNode->pn_next); if (!emitCall(JSOP_RESUME, operand)) return false; return true; } bool BytecodeEmitter::emitSelfHostedForceInterpreter(ParseNode* pn) { if (!emit1(JSOP_FORCEINTERPRETER)) return false; if (!emit1(JSOP_UNDEFINED)) return false; return true; } bool BytecodeEmitter::emitSelfHostedAllowContentSpread(ParseNode* pn) { if (pn->pn_count != 2) { reportError(pn, JSMSG_MORE_ARGS_NEEDED, "allowContentSpread", "1", ""); return false; } // We're just here as a sentinel. Pass the value through directly. return emitTree(pn->pn_head->pn_next); } bool BytecodeEmitter::isRestParameter(ParseNode* pn, bool* result) { if (!sc->isFunctionBox()) { *result = false; return true; } FunctionBox* funbox = sc->asFunctionBox(); RootedFunction fun(cx, funbox->function()); if (!funbox->hasRest()) { *result = false; return true; } if (!pn->isKind(PNK_NAME)) { if (emitterMode == BytecodeEmitter::SelfHosting && pn->isKind(PNK_CALL)) { ParseNode* pn2 = pn->pn_head; if (pn2->getKind() == PNK_NAME && pn2->name() == cx->names().allowContentSpread) return isRestParameter(pn2->pn_next, result); } *result = false; return true; } JSAtom* name = pn->name(); Maybe paramLoc = locationOfNameBoundInFunctionScope(name); if (paramLoc && lookupName(name) == *paramLoc) { FunctionScope::Data* bindings = funbox->functionScopeBindings(); if (bindings->nonPositionalFormalStart > 0) { // |paramName| can be nullptr when the rest destructuring syntax is // used: `function f(...[]) {}`. JSAtom* paramName = bindings->names[bindings->nonPositionalFormalStart - 1].name(); *result = paramName && name == paramName; return true; } } return true; } bool BytecodeEmitter::emitOptimizeSpread(ParseNode* arg0, JumpList* jmp, bool* emitted) { // Emit a pereparation code to optimize the spread call with a rest // parameter: // // function f(...args) { // g(...args); // } // // If the spread operand is a rest parameter and it's optimizable array, // skip spread operation and pass it directly to spread call operation. // See the comment in OptimizeSpreadCall in Interpreter.cpp for the // optimizable conditons. bool result = false; if (!isRestParameter(arg0, &result)) return false; if (!result) { *emitted = false; return true; } if (!emitTree(arg0)) return false; if (!emit1(JSOP_OPTIMIZE_SPREADCALL)) return false; if (!emitJump(JSOP_IFNE, jmp)) return false; if (!emit1(JSOP_POP)) return false; *emitted = true; return true; } bool BytecodeEmitter::emitCallOrNew(ParseNode* pn) { bool callop = pn->isKind(PNK_CALL) || pn->isKind(PNK_TAGGED_TEMPLATE); /* * Emit callable invocation or operator new (constructor call) code. * First, emit code for the left operand to evaluate the callable or * constructable object expression. * * For operator new, we emit JSOP_GETPROP instead of JSOP_CALLPROP, etc. * This is necessary to interpose the lambda-initialized method read * barrier -- see the code in jsinterp.cpp for JSOP_LAMBDA followed by * JSOP_{SET,INIT}PROP. * * Then (or in a call case that has no explicit reference-base * object) we emit JSOP_UNDEFINED to produce the undefined |this| * value required for calls (which non-strict mode functions * will box into the global object). */ uint32_t argc = pn->pn_count - 1; if (argc >= ARGC_LIMIT) { parser->tokenStream.reportError(callop ? JSMSG_TOO_MANY_FUN_ARGS : JSMSG_TOO_MANY_CON_ARGS); return false; } ParseNode* pn2 = pn->pn_head; bool spread = JOF_OPTYPE(pn->getOp()) == JOF_BYTE; switch (pn2->getKind()) { case PNK_NAME: if (emitterMode == BytecodeEmitter::SelfHosting && !spread) { // Calls to "forceInterpreter", "callFunction", // "callContentFunction", or "resumeGenerator" in self-hosted // code generate inline bytecode. if (pn2->name() == cx->names().callFunction || pn2->name() == cx->names().callContentFunction || pn2->name() == cx->names().constructContentFunction) { return emitSelfHostedCallFunction(pn); } if (pn2->name() == cx->names().resumeGenerator) return emitSelfHostedResumeGenerator(pn); if (pn2->name() == cx->names().forceInterpreter) return emitSelfHostedForceInterpreter(pn); if (pn2->name() == cx->names().allowContentSpread) return emitSelfHostedAllowContentSpread(pn); // Fall through. } if (!emitGetName(pn2, callop)) return false; break; case PNK_DOT: MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); if (pn2->as().isSuper()) { if (!emitSuperPropOp(pn2, JSOP_GETPROP_SUPER, /* isCall = */ callop)) return false; } else { if (!emitPropOp(pn2, callop ? JSOP_CALLPROP : JSOP_GETPROP)) return false; } break; case PNK_ELEM: MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); if (pn2->as().isSuper()) { if (!emitSuperElemOp(pn2, JSOP_GETELEM_SUPER, /* isCall = */ callop)) return false; } else { if (!emitElemOp(pn2, callop ? JSOP_CALLELEM : JSOP_GETELEM)) return false; if (callop) { if (!emit1(JSOP_SWAP)) return false; } } break; case PNK_FUNCTION: /* * Top level lambdas which are immediately invoked should be * treated as only running once. Every time they execute we will * create new types and scripts for their contents, to increase * the quality of type information within them and enable more * backend optimizations. Note that this does not depend on the * lambda being invoked at most once (it may be named or be * accessed via foo.caller indirection), as multiple executions * will just cause the inner scripts to be repeatedly cloned. */ MOZ_ASSERT(!emittingRunOnceLambda); if (checkRunOnceContext()) { emittingRunOnceLambda = true; if (!emitTree(pn2)) return false; emittingRunOnceLambda = false; } else { if (!emitTree(pn2)) return false; } callop = false; break; case PNK_SUPERBASE: MOZ_ASSERT(pn->isKind(PNK_SUPERCALL)); MOZ_ASSERT(parser->handler.isSuperBase(pn2)); if (!emit1(JSOP_SUPERFUN)) return false; break; default: if (!emitTree(pn2)) return false; callop = false; /* trigger JSOP_UNDEFINED after */ break; } bool isNewOp = pn->getOp() == JSOP_NEW || pn->getOp() == JSOP_SPREADNEW || pn->getOp() == JSOP_SUPERCALL || pn->getOp() == JSOP_SPREADSUPERCALL; // Emit room for |this|. if (!callop) { if (isNewOp) { if (!emit1(JSOP_IS_CONSTRUCTING)) return false; } else { if (!emit1(JSOP_UNDEFINED)) return false; } } /* * Emit code for each argument in order, then emit the JSOP_*CALL or * JSOP_NEW bytecode with a two-byte immediate telling how many args * were pushed on the operand stack. */ if (!spread) { for (ParseNode* pn3 = pn2->pn_next; pn3; pn3 = pn3->pn_next) { if (!emitTree(pn3)) return false; } if (isNewOp) { if (pn->isKind(PNK_SUPERCALL)) { if (!emit1(JSOP_NEWTARGET)) return false; } else { // Repush the callee as new.target if (!emitDupAt(argc + 1)) return false; } } } else { ParseNode* args = pn2->pn_next; JumpList jmp; bool optCodeEmitted = false; if (argc == 1) { if (!emitOptimizeSpread(args->pn_kid, &jmp, &optCodeEmitted)) return false; } if (!emitArray(args, argc, JSOP_SPREADCALLARRAY)) return false; if (optCodeEmitted) { if (!emitJumpTargetAndPatch(jmp)) return false; } if (isNewOp) { if (pn->isKind(PNK_SUPERCALL)) { if (!emit1(JSOP_NEWTARGET)) return false; } else { if (!emitDupAt(2)) return false; } } } if (!spread) { if (!emitCall(pn->getOp(), argc, pn)) return false; } else { if (!emit1(pn->getOp())) return false; } checkTypeSet(pn->getOp()); if (pn->isOp(JSOP_EVAL) || pn->isOp(JSOP_STRICTEVAL) || pn->isOp(JSOP_SPREADEVAL) || pn->isOp(JSOP_STRICTSPREADEVAL)) { uint32_t lineNum = parser->tokenStream.srcCoords.lineNum(pn->pn_pos.begin); if (!emitUint32Operand(JSOP_LINENO, lineNum)) return false; } return true; } bool BytecodeEmitter::emitRightAssociative(ParseNode* pn) { // ** is the only right-associative operator. MOZ_ASSERT(pn->isKind(PNK_POW)); MOZ_ASSERT(pn->isArity(PN_LIST)); // Right-associative operator chain. for (ParseNode* subexpr = pn->pn_head; subexpr; subexpr = subexpr->pn_next) { if (!emitTree(subexpr)) return false; } for (uint32_t i = 0; i < pn->pn_count - 1; i++) { if (!emit1(JSOP_POW)) return false; } return true; } bool BytecodeEmitter::emitLeftAssociative(ParseNode* pn) { MOZ_ASSERT(pn->isArity(PN_LIST)); // Left-associative operator chain. if (!emitTree(pn->pn_head)) return false; JSOp op = pn->getOp(); ParseNode* nextExpr = pn->pn_head->pn_next; do { if (!emitTree(nextExpr)) return false; if (!emit1(op)) return false; } while ((nextExpr = nextExpr->pn_next)); return true; } bool BytecodeEmitter::emitLogical(ParseNode* pn) { MOZ_ASSERT(pn->isArity(PN_LIST)); /* * JSOP_OR converts the operand on the stack to boolean, leaves the original * value on the stack and jumps if true; otherwise it falls into the next * bytecode, which pops the left operand and then evaluates the right operand. * The jump goes around the right operand evaluation. * * JSOP_AND converts the operand on the stack to boolean and jumps if false; * otherwise it falls into the right operand's bytecode. */ TDZCheckCache tdzCache(this); /* Left-associative operator chain: avoid too much recursion. */ ParseNode* pn2 = pn->pn_head; if (!emitTree(pn2)) return false; JSOp op = pn->getOp(); JumpList jump; if (!emitJump(op, &jump)) return false; if (!emit1(JSOP_POP)) return false; /* Emit nodes between the head and the tail. */ while ((pn2 = pn2->pn_next)->pn_next) { if (!emitTree(pn2)) return false; if (!emitJump(op, &jump)) return false; if (!emit1(JSOP_POP)) return false; } if (!emitTree(pn2)) return false; if (!emitJumpTargetAndPatch(jump)) return false; return true; } bool BytecodeEmitter::emitSequenceExpr(ParseNode* pn) { for (ParseNode* child = pn->pn_head; ; child = child->pn_next) { if (!updateSourceCoordNotes(child->pn_pos.begin)) return false; if (!emitTree(child)) return false; if (!child->pn_next) break; if (!emit1(JSOP_POP)) return false; } return true; } // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See // the comment on emitSwitch. MOZ_NEVER_INLINE bool BytecodeEmitter::emitIncOrDec(ParseNode* pn) { switch (pn->pn_kid->getKind()) { case PNK_DOT: return emitPropIncDec(pn); case PNK_ELEM: return emitElemIncDec(pn); case PNK_CALL: return emitCallIncDec(pn); default: return emitNameIncDec(pn); } return true; } // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See // the comment on emitSwitch. MOZ_NEVER_INLINE bool BytecodeEmitter::emitLabeledStatement(const LabeledStatement* pn) { /* * Emit a JSOP_LABEL instruction. The argument is the offset to the statement * following the labeled statement. */ uint32_t index; if (!makeAtomIndex(pn->label(), &index)) return false; JumpList top; if (!emitJump(JSOP_LABEL, &top)) return false; /* Emit code for the labeled statement. */ LabelControl controlInfo(this, pn->label(), offset()); if (!emitTree(pn->statement())) return false; /* Patch the JSOP_LABEL offset. */ JumpTarget brk{ lastNonJumpTargetOffset() }; patchJumpsToTarget(top, brk); if (!controlInfo.patchBreaks(this)) return false; return true; } bool BytecodeEmitter::emitConditionalExpression(ConditionalExpression& conditional) { /* Emit the condition, then branch if false to the else part. */ if (!emitTree(&conditional.condition())) return false; IfThenElseEmitter ifThenElse(this); if (!ifThenElse.emitCond()) return false; if (!emitTreeInBranch(&conditional.thenExpression())) return false; if (!ifThenElse.emitElse()) return false; if (!emitTreeInBranch(&conditional.elseExpression())) return false; if (!ifThenElse.emitEnd()) return false; MOZ_ASSERT(ifThenElse.pushed() == 1); return true; } bool BytecodeEmitter::emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp, PropListType type) { for (ParseNode* propdef = pn->pn_head; propdef; propdef = propdef->pn_next) { if (!updateSourceCoordNotes(propdef->pn_pos.begin)) return false; // Handle __proto__: v specially because *only* this form, and no other // involving "__proto__", performs [[Prototype]] mutation. if (propdef->isKind(PNK_MUTATEPROTO)) { MOZ_ASSERT(type == ObjectLiteral); if (!emitTree(propdef->pn_kid)) return false; objp.set(nullptr); if (!emit1(JSOP_MUTATEPROTO)) return false; continue; } bool extraPop = false; if (type == ClassBody && propdef->as().isStatic()) { extraPop = true; if (!emit1(JSOP_DUP2)) return false; if (!emit1(JSOP_POP)) return false; } /* Emit an index for t[2] for later consumption by JSOP_INITELEM. */ ParseNode* key = propdef->pn_left; bool isIndex = false; if (key->isKind(PNK_NUMBER)) { if (!emitNumberOp(key->pn_dval)) return false; isIndex = true; } else if (key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)) { // EmitClass took care of constructor already. if (type == ClassBody && key->pn_atom == cx->names().constructor && !propdef->as().isStatic()) { continue; } // The parser already checked for atoms representing indexes and // used PNK_NUMBER instead, but also watch for ids which TI treats // as indexes for simpliciation of downstream analysis. jsid id = NameToId(key->pn_atom->asPropertyName()); if (id != IdToTypeId(id)) { if (!emitTree(key)) return false; isIndex = true; } } else { if (!emitComputedPropertyName(key)) return false; isIndex = true; } /* Emit code for the property initializer. */ if (!emitTree(propdef->pn_right)) return false; JSOp op = propdef->getOp(); MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER); FunctionPrefixKind prefixKind = op == JSOP_INITPROP_GETTER ? FunctionPrefixKind::Get : op == JSOP_INITPROP_SETTER ? FunctionPrefixKind::Set : FunctionPrefixKind::None; if (op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER) objp.set(nullptr); if (propdef->pn_right->isKind(PNK_FUNCTION) && propdef->pn_right->pn_funbox->needsHomeObject()) { MOZ_ASSERT(propdef->pn_right->pn_funbox->function()->allowSuperProperty()); bool isAsync = propdef->pn_right->pn_funbox->isAsync(); if (isAsync) { if (!emit1(JSOP_SWAP)) return false; } if (!emit2(JSOP_INITHOMEOBJECT, isIndex + isAsync)) return false; if (isAsync) { if (!emit1(JSOP_POP)) return false; } } // Class methods are not enumerable. if (type == ClassBody) { switch (op) { case JSOP_INITPROP: op = JSOP_INITHIDDENPROP; break; case JSOP_INITPROP_GETTER: op = JSOP_INITHIDDENPROP_GETTER; break; case JSOP_INITPROP_SETTER: op = JSOP_INITHIDDENPROP_SETTER; break; default: MOZ_CRASH("Invalid op"); } } if (isIndex) { objp.set(nullptr); switch (op) { case JSOP_INITPROP: op = JSOP_INITELEM; break; case JSOP_INITHIDDENPROP: op = JSOP_INITHIDDENELEM; break; case JSOP_INITPROP_GETTER: op = JSOP_INITELEM_GETTER; break; case JSOP_INITHIDDENPROP_GETTER: op = JSOP_INITHIDDENELEM_GETTER; break; case JSOP_INITPROP_SETTER: op = JSOP_INITELEM_SETTER; break; case JSOP_INITHIDDENPROP_SETTER: op = JSOP_INITHIDDENELEM_SETTER; break; default: MOZ_CRASH("Invalid op"); } if (propdef->pn_right->isDirectRHSAnonFunction()) { if (!emitDupAt(1)) return false; if (!emit2(JSOP_SETFUNNAME, uint8_t(prefixKind))) return false; } if (!emit1(op)) return false; } else { MOZ_ASSERT(key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)); uint32_t index; if (!makeAtomIndex(key->pn_atom, &index)) return false; if (objp) { MOZ_ASSERT(type == ObjectLiteral); MOZ_ASSERT(!IsHiddenInitOp(op)); MOZ_ASSERT(!objp->inDictionaryMode()); Rooted id(cx, AtomToId(key->pn_atom)); RootedValue undefinedValue(cx, UndefinedValue()); if (!NativeDefineProperty(cx, objp, id, undefinedValue, nullptr, nullptr, JSPROP_ENUMERATE)) { return false; } if (objp->inDictionaryMode()) objp.set(nullptr); } if (propdef->pn_right->isDirectRHSAnonFunction()) { RootedAtom keyName(cx, key->pn_atom); if (!setOrEmitSetFunName(propdef->pn_right, keyName, prefixKind)) return false; } if (!emitIndex32(op, index)) return false; } if (extraPop) { if (!emit1(JSOP_POP)) return false; } } return true; } // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See // the comment on emitSwitch. MOZ_NEVER_INLINE bool BytecodeEmitter::emitObject(ParseNode* pn) { if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head && checkSingletonContext()) return emitSingletonInitialiser(pn); /* * Emit code for {p:a, '%q':b, 2:c} that is equivalent to constructing * a new object and defining (in source order) each property on the object * (or mutating the object's [[Prototype]], in the case of __proto__). */ ptrdiff_t offset = this->offset(); if (!emitNewInit(JSProto_Object)) return false; /* * Try to construct the shape of the object as we go, so we can emit a * JSOP_NEWOBJECT with the final shape instead. */ RootedPlainObject obj(cx); // No need to do any guessing for the object kind, since we know exactly // how many properties we plan to have. gc::AllocKind kind = gc::GetGCObjectKind(pn->pn_count); obj = NewBuiltinClassInstance(cx, kind, TenuredObject); if (!obj) return false; if (!emitPropertyList(pn, &obj, ObjectLiteral)) return false; if (obj) { /* * The object survived and has a predictable shape: update the original * bytecode. */ ObjectBox* objbox = parser->newObjectBox(obj); if (!objbox) return false; static_assert(JSOP_NEWINIT_LENGTH == JSOP_NEWOBJECT_LENGTH, "newinit and newobject must have equal length to edit in-place"); uint32_t index = objectList.add(objbox); jsbytecode* code = this->code(offset); code[0] = JSOP_NEWOBJECT; code[1] = jsbytecode(index >> 24); code[2] = jsbytecode(index >> 16); code[3] = jsbytecode(index >> 8); code[4] = jsbytecode(index); } return true; } bool BytecodeEmitter::emitArrayComp(ParseNode* pn) { if (!emitNewInit(JSProto_Array)) return false; /* * Pass the new array's stack index to the PNK_ARRAYPUSH case via * arrayCompDepth, then simply traverse the PNK_FOR node and * its kids under pn2 to generate this comprehension. */ MOZ_ASSERT(stackDepth > 0); uint32_t saveDepth = arrayCompDepth; arrayCompDepth = (uint32_t) (stackDepth - 1); if (!emitTree(pn->pn_head)) return false; arrayCompDepth = saveDepth; return true; } bool BytecodeEmitter::emitArrayLiteral(ParseNode* pn) { if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head) { if (checkSingletonContext()) { // Bake in the object entirely if it will only be created once. return emitSingletonInitialiser(pn); } // If the array consists entirely of primitive values, make a // template object with copy on write elements that can be reused // every time the initializer executes. if (emitterMode != BytecodeEmitter::SelfHosting && pn->pn_count != 0) { RootedValue value(cx); if (!pn->getConstantValue(cx, ParseNode::ForCopyOnWriteArray, &value)) return false; if (!value.isMagic(JS_GENERIC_MAGIC)) { // Note: the group of the template object might not yet reflect // that the object has copy on write elements. When the // interpreter or JIT compiler fetches the template, it should // use ObjectGroup::getOrFixupCopyOnWriteObject to make sure the // group for the template is accurate. We don't do this here as we // want to use ObjectGroup::allocationSiteGroup, which requires a // finished script. JSObject* obj = &value.toObject(); MOZ_ASSERT(obj->is() && obj->as().denseElementsAreCopyOnWrite()); ObjectBox* objbox = parser->newObjectBox(obj); if (!objbox) return false; return emitObjectOp(objbox, JSOP_NEWARRAY_COPYONWRITE); } } } return emitArray(pn->pn_head, pn->pn_count, JSOP_NEWARRAY); } bool BytecodeEmitter::emitArray(ParseNode* pn, uint32_t count, JSOp op) { /* * Emit code for [a, b, c] that is equivalent to constructing a new * array and in source order evaluating each element value and adding * it to the array, without invoking latent setters. We use the * JSOP_NEWINIT and JSOP_INITELEM_ARRAY bytecodes to ignore setters and * to avoid dup'ing and popping the array as each element is added, as * JSOP_SETELEM/JSOP_SETPROP would do. */ MOZ_ASSERT(op == JSOP_NEWARRAY || op == JSOP_SPREADCALLARRAY); uint32_t nspread = 0; for (ParseNode* elt = pn; elt; elt = elt->pn_next) { if (elt->isKind(PNK_SPREAD)) nspread++; } // Array literal's length is limited to NELEMENTS_LIMIT in parser. static_assert(NativeObject::MAX_DENSE_ELEMENTS_COUNT <= INT32_MAX, "array literals' maximum length must not exceed limits " "required by BaselineCompiler::emit_JSOP_NEWARRAY, " "BaselineCompiler::emit_JSOP_INITELEM_ARRAY, " "and DoSetElemFallback's handling of JSOP_INITELEM_ARRAY"); MOZ_ASSERT(count >= nspread); MOZ_ASSERT(count <= NativeObject::MAX_DENSE_ELEMENTS_COUNT, "the parser must throw an error if the array exceeds maximum " "length"); // For arrays with spread, this is a very pessimistic allocation, the // minimum possible final size. if (!emitUint32Operand(op, count - nspread)) // ARRAY return false; ParseNode* pn2 = pn; uint32_t index; bool afterSpread = false; for (index = 0; pn2; index++, pn2 = pn2->pn_next) { if (!afterSpread && pn2->isKind(PNK_SPREAD)) { afterSpread = true; if (!emitNumberOp(index)) // ARRAY INDEX return false; } if (!updateSourceCoordNotes(pn2->pn_pos.begin)) return false; bool allowSelfHostedSpread = false; if (pn2->isKind(PNK_ELISION)) { if (!emit1(JSOP_HOLE)) return false; } else { ParseNode* expr; if (pn2->isKind(PNK_SPREAD)) { expr = pn2->pn_kid; if (emitterMode == BytecodeEmitter::SelfHosting && expr->isKind(PNK_CALL) && expr->pn_head->name() == cx->names().allowContentSpread) { allowSelfHostedSpread = true; } } else { expr = pn2; } if (!emitTree(expr)) // ARRAY INDEX? VALUE return false; } if (pn2->isKind(PNK_SPREAD)) { if (!emitIterator()) // ARRAY INDEX ITER return false; if (!emit2(JSOP_PICK, 2)) // INDEX ITER ARRAY return false; if (!emit2(JSOP_PICK, 2)) // ITER ARRAY INDEX return false; if (!emitSpread(allowSelfHostedSpread)) // ARRAY INDEX return false; } else if (afterSpread) { if (!emit1(JSOP_INITELEM_INC)) return false; } else { if (!emitUint32Operand(JSOP_INITELEM_ARRAY, index)) return false; } } MOZ_ASSERT(index == count); if (afterSpread) { if (!emit1(JSOP_POP)) // ARRAY return false; } return true; } bool BytecodeEmitter::emitUnary(ParseNode* pn) { if (!updateSourceCoordNotes(pn->pn_pos.begin)) return false; /* Unary op, including unary +/-. */ JSOp op = pn->getOp(); ParseNode* pn2 = pn->pn_kid; if (!emitTree(pn2)) return false; return emit1(op); } bool BytecodeEmitter::emitTypeof(ParseNode* node, JSOp op) { MOZ_ASSERT(op == JSOP_TYPEOF || op == JSOP_TYPEOFEXPR); if (!updateSourceCoordNotes(node->pn_pos.begin)) return false; if (!emitTree(node->pn_kid)) return false; return emit1(op); } bool BytecodeEmitter::emitFunctionFormalParametersAndBody(ParseNode *pn) { MOZ_ASSERT(pn->isKind(PNK_PARAMSBODY)); ParseNode* funBody = pn->last(); FunctionBox* funbox = sc->asFunctionBox(); TDZCheckCache tdzCache(this); if (funbox->hasParameterExprs) { EmitterScope funEmitterScope(this); if (!funEmitterScope.enterFunction(this, funbox)) return false; if (!emitInitializeFunctionSpecialNames()) return false; if (!emitFunctionFormalParameters(pn)) return false; { Maybe extraVarEmitterScope; if (funbox->hasExtraBodyVarScope()) { extraVarEmitterScope.emplace(this); if (!extraVarEmitterScope->enterFunctionExtraBodyVar(this, funbox)) return false; // After emitting expressions for all parameters, copy over any // formal parameters which have been redeclared as vars. For // example, in the following, the var y in the body scope is 42: // // function f(x, y = 42) { var y; } // RootedAtom name(cx); if (funbox->extraVarScopeBindings() && funbox->functionScopeBindings()) { for (BindingIter bi(*funbox->functionScopeBindings(), true); bi; bi++) { name = bi.name(); // There may not be a var binding of the same name. if (!locationOfNameBoundInScope(name, extraVarEmitterScope.ptr())) continue; // The '.this' and '.generator' function special // bindings should never appear in the extra var // scope. 'arguments', however, may. MOZ_ASSERT(name != cx->names().dotThis && name != cx->names().dotGenerator); NameLocation paramLoc = *locationOfNameBoundInScope(name, &funEmitterScope); auto emitRhs = [&name, ¶mLoc](BytecodeEmitter* bce, const NameLocation&, bool) { return bce->emitGetNameAtLocation(name, paramLoc); }; if (!emitInitializeName(name, emitRhs)) return false; if (!emit1(JSOP_POP)) return false; } } } if (!emitFunctionBody(funBody)) return false; if (extraVarEmitterScope && !extraVarEmitterScope->leave(this)) return false; } return funEmitterScope.leave(this); } // No parameter expressions. Enter the function body scope and emit // everything. // // One caveat is that Debugger considers ops in the prologue to be // unreachable (i.e. cannot set a breakpoint on it). If there are no // parameter exprs, any unobservable environment ops (like pushing the // call object, setting '.this', etc) need to go in the prologue, else it // messes up breakpoint tests. EmitterScope emitterScope(this); switchToPrologue(); if (!emitterScope.enterFunction(this, funbox)) return false; if (!emitInitializeFunctionSpecialNames()) return false; switchToMain(); if (!emitFunctionFormalParameters(pn)) return false; if (!emitFunctionBody(funBody)) return false; return emitterScope.leave(this); } bool BytecodeEmitter::emitFunctionFormalParameters(ParseNode* pn) { ParseNode* funBody = pn->last(); FunctionBox* funbox = sc->asFunctionBox(); EmitterScope* funScope = innermostEmitterScope; bool hasParameterExprs = funbox->hasParameterExprs; bool hasRest = funbox->hasRest(); uint16_t argSlot = 0; for (ParseNode* arg = pn->pn_head; arg != funBody; arg = arg->pn_next, argSlot++) { ParseNode* bindingElement = arg; ParseNode* initializer = nullptr; if (arg->isKind(PNK_ASSIGN)) { bindingElement = arg->pn_left; initializer = arg->pn_right; } // Left-hand sides are either simple names or destructuring patterns. MOZ_ASSERT(bindingElement->isKind(PNK_NAME) || bindingElement->isKind(PNK_ARRAY) || bindingElement->isKind(PNK_ARRAYCOMP) || bindingElement->isKind(PNK_OBJECT)); // The rest parameter doesn't have an initializer. bool isRest = hasRest && arg->pn_next == funBody; MOZ_ASSERT_IF(isRest, !initializer); bool isDestructuring = !bindingElement->isKind(PNK_NAME); // ES 14.1.19 says if BindingElement contains an expression in the // production FormalParameter : BindingElement, it is evaluated in a // new var environment. This is needed to prevent vars from escaping // direct eval in parameter expressions. Maybe paramExprVarScope; if (funbox->hasDirectEvalInParameterExpr && (isDestructuring || initializer)) { paramExprVarScope.emplace(this); if (!paramExprVarScope->enterParameterExpressionVar(this)) return false; } // First push the RHS if there is a default expression or if it is // rest. if (initializer) { // If we have an initializer, emit the initializer and assign it // to the argument slot. TDZ is taken care of afterwards. MOZ_ASSERT(hasParameterExprs); if (!emitArgOp(JSOP_GETARG, argSlot)) return false; if (!emit1(JSOP_DUP)) return false; if (!emit1(JSOP_UNDEFINED)) return false; if (!emit1(JSOP_STRICTEQ)) return false; // Emit source note to enable Ion compilation. if (!newSrcNote(SRC_IF)) return false; JumpList jump; if (!emitJump(JSOP_IFEQ, &jump)) return false; if (!emit1(JSOP_POP)) return false; if (!emitInitializerInBranch(initializer, bindingElement)) return false; if (!emitJumpTargetAndPatch(jump)) return false; } else if (isRest) { if (!emit1(JSOP_REST)) return false; checkTypeSet(JSOP_REST); } // Initialize the parameter name. if (isDestructuring) { // If we had an initializer or the rest parameter, the value is // already on the stack. if (!initializer && !isRest && !emitArgOp(JSOP_GETARG, argSlot)) return false; // If there's an parameter expression var scope, the destructuring // declaration needs to initialize the name in the function scope, // which is not the innermost scope. if (!emitDestructuringOps(bindingElement, paramExprVarScope ? DestructuringFormalParameterInVarScope : DestructuringDeclaration)) { return false; } if (!emit1(JSOP_POP)) return false; } else { RootedAtom paramName(cx, bindingElement->name()); NameLocation paramLoc = *locationOfNameBoundInScope(paramName, funScope); if (hasParameterExprs) { auto emitRhs = [argSlot, initializer, isRest](BytecodeEmitter* bce, const NameLocation&, bool) { // If we had an initializer or a rest parameter, the value is // already on the stack. if (!initializer && !isRest) return bce->emitArgOp(JSOP_GETARG, argSlot); return true; }; if (!emitSetOrInitializeNameAtLocation(paramName, paramLoc, emitRhs, true)) return false; if (!emit1(JSOP_POP)) return false; } else if (isRest) { // The rest value is already on top of the stack. auto nop = [](BytecodeEmitter*, const NameLocation&, bool) { return true; }; if (!emitSetOrInitializeNameAtLocation(paramName, paramLoc, nop, true)) return false; if (!emit1(JSOP_POP)) return false; } } if (paramExprVarScope) { if (!paramExprVarScope->leave(this)) return false; } } return true; } bool BytecodeEmitter::emitInitializeFunctionSpecialNames() { FunctionBox* funbox = sc->asFunctionBox(); auto emitInitializeFunctionSpecialName = [](BytecodeEmitter* bce, HandlePropertyName name, JSOp op) { // A special name must be slotful, either on the frame or on the // call environment. MOZ_ASSERT(bce->lookupName(name).hasKnownSlot()); auto emitInitial = [op](BytecodeEmitter* bce, const NameLocation&, bool) { return bce->emit1(op); }; if (!bce->emitInitializeName(name, emitInitial)) return false; if (!bce->emit1(JSOP_POP)) return false; return true; }; // Do nothing if the function doesn't have an arguments binding. if (funbox->argumentsHasLocalBinding()) { if (!emitInitializeFunctionSpecialName(this, cx->names().arguments, JSOP_ARGUMENTS)) return false; } // Do nothing if the function doesn't have a this-binding (this // happens for instance if it doesn't use this/eval or if it's an // arrow function). if (funbox->hasThisBinding()) { if (!emitInitializeFunctionSpecialName(this, cx->names().dotThis, JSOP_FUNCTIONTHIS)) return false; } return true; } bool BytecodeEmitter::emitFunctionBody(ParseNode* funBody) { FunctionBox* funbox = sc->asFunctionBox(); if (!emitTree(funBody)) return false; if (funbox->isGenerator()) { // If we fall off the end of a generator, do a final yield. if (funbox->isStarGenerator() && !emitPrepareIteratorResult()) return false; if (!emit1(JSOP_UNDEFINED)) return false; if (sc->asFunctionBox()->isStarGenerator() && !emitFinishIteratorResult(true)) return false; if (!emit1(JSOP_SETRVAL)) return false; NameLocation loc = *locationOfNameBoundInFunctionScope(cx->names().dotGenerator); if (!emitGetNameAtLocation(cx->names().dotGenerator, loc)) return false; // No need to check for finally blocks, etc as in EmitReturn. if (!emitYieldOp(JSOP_FINALYIELDRVAL)) return false; } else { // Non-generator functions just return |undefined|. The // JSOP_RETRVAL emitted below will do that, except if the // script has a finally block: there can be a non-undefined // value in the return value slot. Make sure the return value // is |undefined|. if (hasTryFinally) { if (!emit1(JSOP_UNDEFINED)) return false; if (!emit1(JSOP_SETRVAL)) return false; } } if (funbox->isDerivedClassConstructor()) { if (!emitCheckDerivedClassConstructorReturn()) return false; } return true; } bool BytecodeEmitter::emitLexicalInitialization(ParseNode* pn) { // The caller has pushed the RHS to the top of the stack. Assert that the // name is lexical and no BIND[G]NAME ops were emitted. auto assertLexical = [](BytecodeEmitter*, const NameLocation& loc, bool emittedBindOp) { MOZ_ASSERT(loc.isLexical()); MOZ_ASSERT(!emittedBindOp); return true; }; return emitInitializeName(pn, assertLexical); } // This follows ES6 14.5.14 (ClassDefinitionEvaluation) and ES6 14.5.15 // (BindingClassDeclarationEvaluation). bool BytecodeEmitter::emitClass(ParseNode* pn) { ClassNode& classNode = pn->as(); ClassNames* names = classNode.names(); ParseNode* heritageExpression = classNode.heritage(); ParseNode* classMethods = classNode.methodList(); ParseNode* constructor = nullptr; for (ParseNode* mn = classMethods->pn_head; mn; mn = mn->pn_next) { ClassMethod& method = mn->as(); ParseNode& methodName = method.name(); if (!method.isStatic() && (methodName.isKind(PNK_OBJECT_PROPERTY_NAME) || methodName.isKind(PNK_STRING)) && methodName.pn_atom == cx->names().constructor) { constructor = &method.method(); break; } } bool savedStrictness = sc->setLocalStrictMode(true); Maybe tdzCache; Maybe emitterScope; if (names) { tdzCache.emplace(this); emitterScope.emplace(this); if (!emitterScope->enterLexical(this, ScopeKind::Lexical, classNode.scopeBindings())) return false; } // This is kind of silly. In order to the get the home object defined on // the constructor, we have to make it second, but we want the prototype // on top for EmitPropertyList, because we expect static properties to be // rarer. The result is a few more swaps than we would like. Such is life. if (heritageExpression) { if (!emitTree(heritageExpression)) return false; if (!emit1(JSOP_CLASSHERITAGE)) return false; if (!emit1(JSOP_OBJWITHPROTO)) return false; // JSOP_CLASSHERITAGE leaves both protos on the stack. After // creating the prototype, swap it to the bottom to make the // constructor. if (!emit1(JSOP_SWAP)) return false; } else { if (!emitNewInit(JSProto_Object)) return false; } if (constructor) { if (!emitFunction(constructor, !!heritageExpression)) return false; if (constructor->pn_funbox->needsHomeObject()) { if (!emit2(JSOP_INITHOMEOBJECT, 0)) return false; } } else { JSAtom *name = names ? names->innerBinding()->pn_atom : cx->names().empty; if (heritageExpression) { if (!emitAtomOp(name, JSOP_DERIVEDCONSTRUCTOR)) return false; } else { if (!emitAtomOp(name, JSOP_CLASSCONSTRUCTOR)) return false; } } if (!emit1(JSOP_SWAP)) return false; if (!emit1(JSOP_DUP2)) return false; if (!emitAtomOp(cx->names().prototype, JSOP_INITLOCKEDPROP)) return false; if (!emitAtomOp(cx->names().constructor, JSOP_INITHIDDENPROP)) return false; RootedPlainObject obj(cx); if (!emitPropertyList(classMethods, &obj, ClassBody)) return false; if (!emit1(JSOP_POP)) return false; if (names) { ParseNode* innerName = names->innerBinding(); if (!emitLexicalInitialization(innerName)) return false; // Pop the inner scope. if (!emitterScope->leave(this)) return false; emitterScope.reset(); ParseNode* outerName = names->outerBinding(); if (outerName) { if (!emitLexicalInitialization(outerName)) return false; // Only class statements make outer bindings, and they do not leave // themselves on the stack. if (!emit1(JSOP_POP)) return false; } } MOZ_ALWAYS_TRUE(sc->setLocalStrictMode(savedStrictness)); return true; } bool BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote) { JS_CHECK_RECURSION(cx, return false); EmitLevelManager elm(this); /* Emit notes to tell the current bytecode's source line number. However, a couple trees require special treatment; see the relevant emitter functions for details. */ if (emitLineNote == EMIT_LINENOTE && !ParseNodeRequiresSpecialLineNumberNotes(pn)) { if (!updateLineNumberNotes(pn->pn_pos.begin)) return false; } switch (pn->getKind()) { case PNK_FUNCTION: if (!emitFunction(pn)) return false; break; case PNK_PARAMSBODY: if (!emitFunctionFormalParametersAndBody(pn)) return false; break; case PNK_IF: if (!emitIf(pn)) return false; break; case PNK_SWITCH: if (!emitSwitch(pn)) return false; break; case PNK_WHILE: if (!emitWhile(pn)) return false; break; case PNK_DOWHILE: if (!emitDo(pn)) return false; break; case PNK_FOR: if (!emitFor(pn)) return false; break; case PNK_COMPREHENSIONFOR: if (!emitComprehensionFor(pn)) return false; break; case PNK_BREAK: if (!emitBreak(pn->as().label())) return false; break; case PNK_CONTINUE: if (!emitContinue(pn->as().label())) return false; break; case PNK_WITH: if (!emitWith(pn)) return false; break; case PNK_TRY: if (!emitTry(pn)) return false; break; case PNK_CATCH: if (!emitCatch(pn)) return false; break; case PNK_VAR: if (!emitDeclarationList(pn)) return false; break; case PNK_RETURN: if (!emitReturn(pn)) return false; break; case PNK_YIELD_STAR: if (!emitYieldStar(pn->pn_left, pn->pn_right)) return false; break; case PNK_GENERATOR: if (!emit1(JSOP_GENERATOR)) return false; break; case PNK_YIELD: case PNK_AWAIT: if (!emitYield(pn)) return false; break; case PNK_STATEMENTLIST: if (!emitStatementList(pn)) return false; break; case PNK_SEMI: if (!emitStatement(pn)) return false; break; case PNK_LABEL: if (!emitLabeledStatement(&pn->as())) return false; break; case PNK_COMMA: if (!emitSequenceExpr(pn)) return false; break; case PNK_ASSIGN: case PNK_ADDASSIGN: case PNK_SUBASSIGN: case PNK_BITORASSIGN: case PNK_BITXORASSIGN: case PNK_BITANDASSIGN: case PNK_LSHASSIGN: case PNK_RSHASSIGN: case PNK_URSHASSIGN: case PNK_MULASSIGN: case PNK_DIVASSIGN: case PNK_MODASSIGN: case PNK_POWASSIGN: if (!emitAssignment(pn->pn_left, pn->getOp(), pn->pn_right)) return false; break; case PNK_CONDITIONAL: if (!emitConditionalExpression(pn->as())) return false; break; case PNK_OR: case PNK_AND: if (!emitLogical(pn)) return false; break; case PNK_ADD: case PNK_SUB: case PNK_BITOR: case PNK_BITXOR: case PNK_BITAND: case PNK_STRICTEQ: case PNK_EQ: case PNK_STRICTNE: case PNK_NE: case PNK_LT: case PNK_LE: case PNK_GT: case PNK_GE: case PNK_IN: case PNK_INSTANCEOF: case PNK_LSH: case PNK_RSH: case PNK_URSH: case PNK_STAR: case PNK_DIV: case PNK_MOD: if (!emitLeftAssociative(pn)) return false; break; case PNK_POW: if (!emitRightAssociative(pn)) return false; break; case PNK_TYPEOFNAME: if (!emitTypeof(pn, JSOP_TYPEOF)) return false; break; case PNK_TYPEOFEXPR: if (!emitTypeof(pn, JSOP_TYPEOFEXPR)) return false; break; case PNK_THROW: case PNK_VOID: case PNK_NOT: case PNK_BITNOT: case PNK_POS: case PNK_NEG: if (!emitUnary(pn)) return false; break; case PNK_PREINCREMENT: case PNK_PREDECREMENT: case PNK_POSTINCREMENT: case PNK_POSTDECREMENT: if (!emitIncOrDec(pn)) return false; break; case PNK_DELETENAME: if (!emitDeleteName(pn)) return false; break; case PNK_DELETEPROP: if (!emitDeleteProperty(pn)) return false; break; case PNK_DELETEELEM: if (!emitDeleteElement(pn)) return false; break; case PNK_DELETEEXPR: if (!emitDeleteExpression(pn)) return false; break; case PNK_DOT: if (pn->as().isSuper()) { if (!emitSuperPropOp(pn, JSOP_GETPROP_SUPER)) return false; } else { if (!emitPropOp(pn, JSOP_GETPROP)) return false; } break; case PNK_ELEM: if (pn->as().isSuper()) { if (!emitSuperElemOp(pn, JSOP_GETELEM_SUPER)) return false; } else { if (!emitElemOp(pn, JSOP_GETELEM)) return false; } break; case PNK_NEW: case PNK_TAGGED_TEMPLATE: case PNK_CALL: case PNK_GENEXP: case PNK_SUPERCALL: if (!emitCallOrNew(pn)) return false; break; case PNK_LEXICALSCOPE: if (!emitLexicalScope(pn)) return false; break; case PNK_CONST: case PNK_LET: if (!emitDeclarationList(pn)) return false; break; case PNK_IMPORT: MOZ_ASSERT(sc->isModuleContext()); break; case PNK_EXPORT: MOZ_ASSERT(sc->isModuleContext()); if (pn->pn_kid->getKind() != PNK_EXPORT_SPEC_LIST) { if (!emitTree(pn->pn_kid)) return false; } break; case PNK_EXPORT_DEFAULT: MOZ_ASSERT(sc->isModuleContext()); if (!emitTree(pn->pn_kid)) return false; if (pn->pn_right) { if (!emitLexicalInitialization(pn->pn_right)) return false; if (!emit1(JSOP_POP)) return false; } break; case PNK_EXPORT_FROM: MOZ_ASSERT(sc->isModuleContext()); break; case PNK_ARRAYPUSH: /* * The array object's stack index is in arrayCompDepth. See below * under the array initialiser code generator for array comprehension * special casing. */ if (!emitTree(pn->pn_kid)) return false; if (!emitDupAt(this->stackDepth - 1 - arrayCompDepth)) return false; if (!emit1(JSOP_ARRAYPUSH)) return false; break; case PNK_CALLSITEOBJ: if (!emitCallSiteObject(pn)) return false; break; case PNK_ARRAY: if (!emitArrayLiteral(pn)) return false; break; case PNK_ARRAYCOMP: if (!emitArrayComp(pn)) return false; break; case PNK_OBJECT: if (!emitObject(pn)) return false; break; case PNK_NAME: if (!emitGetName(pn)) return false; break; case PNK_TEMPLATE_STRING_LIST: if (!emitTemplateString(pn)) return false; break; case PNK_TEMPLATE_STRING: case PNK_STRING: if (!emitAtomOp(pn, JSOP_STRING)) return false; break; case PNK_NUMBER: if (!emitNumberOp(pn->pn_dval)) return false; break; case PNK_REGEXP: if (!emitRegExp(objectList.add(pn->as().objbox()))) return false; break; case PNK_TRUE: case PNK_FALSE: case PNK_NULL: if (!emit1(pn->getOp())) return false; break; case PNK_THIS: if (!emitThisLiteral(pn)) return false; break; case PNK_DEBUGGER: if (!updateSourceCoordNotes(pn->pn_pos.begin)) return false; if (!emit1(JSOP_DEBUGGER)) return false; break; case PNK_NOP: MOZ_ASSERT(pn->getArity() == PN_NULLARY); break; case PNK_CLASS: if (!emitClass(pn)) return false; break; case PNK_NEWTARGET: if (!emit1(JSOP_NEWTARGET)) return false; break; case PNK_SETTHIS: if (!emitSetThis(pn)) return false; break; case PNK_POSHOLDER: MOZ_FALLTHROUGH_ASSERT("Should never try to emit PNK_POSHOLDER"); default: MOZ_ASSERT(0); } /* bce->emitLevel == 1 means we're last on the stack, so finish up. */ if (emitLevel == 1) { if (!updateSourceCoordNotes(pn->pn_pos.end)) return false; } return true; } bool BytecodeEmitter::emitTreeInBranch(ParseNode* pn) { // Code that may be conditionally executed always need their own TDZ // cache. TDZCheckCache tdzCache(this); return emitTree(pn); } static bool AllocSrcNote(ExclusiveContext* cx, SrcNotesVector& notes, unsigned* index) { // Start it off moderately large to avoid repeated resizings early on. // ~99% of cases fit within 256 bytes. if (notes.capacity() == 0 && !notes.reserve(256)) return false; if (!notes.growBy(1)) { ReportOutOfMemory(cx); return false; } *index = notes.length() - 1; return true; } bool BytecodeEmitter::newSrcNote(SrcNoteType type, unsigned* indexp) { SrcNotesVector& notes = this->notes(); unsigned index; if (!AllocSrcNote(cx, notes, &index)) return false; /* * Compute delta from the last annotated bytecode's offset. If it's too * big to fit in sn, allocate one or more xdelta notes and reset sn. */ ptrdiff_t offset = this->offset(); ptrdiff_t delta = offset - lastNoteOffset(); current->lastNoteOffset = offset; if (delta >= SN_DELTA_LIMIT) { do { ptrdiff_t xdelta = Min(delta, SN_XDELTA_MASK); SN_MAKE_XDELTA(¬es[index], xdelta); delta -= xdelta; if (!AllocSrcNote(cx, notes, &index)) return false; } while (delta >= SN_DELTA_LIMIT); } /* * Initialize type and delta, then allocate the minimum number of notes * needed for type's arity. Usually, we won't need more, but if an offset * does take two bytes, setSrcNoteOffset will grow notes. */ SN_MAKE_NOTE(¬es[index], type, delta); for (int n = (int)js_SrcNoteSpec[type].arity; n > 0; n--) { if (!newSrcNote(SRC_NULL)) return false; } if (indexp) *indexp = index; return true; } bool BytecodeEmitter::newSrcNote2(SrcNoteType type, ptrdiff_t offset, unsigned* indexp) { unsigned index; if (!newSrcNote(type, &index)) return false; if (!setSrcNoteOffset(index, 0, offset)) return false; if (indexp) *indexp = index; return true; } bool BytecodeEmitter::newSrcNote3(SrcNoteType type, ptrdiff_t offset1, ptrdiff_t offset2, unsigned* indexp) { unsigned index; if (!newSrcNote(type, &index)) return false; if (!setSrcNoteOffset(index, 0, offset1)) return false; if (!setSrcNoteOffset(index, 1, offset2)) return false; if (indexp) *indexp = index; return true; } bool BytecodeEmitter::addToSrcNoteDelta(jssrcnote* sn, ptrdiff_t delta) { /* * Called only from finishTakingSrcNotes to add to main script note * deltas, and only by a small positive amount. */ MOZ_ASSERT(current == &main); MOZ_ASSERT((unsigned) delta < (unsigned) SN_XDELTA_LIMIT); ptrdiff_t base = SN_DELTA(sn); ptrdiff_t limit = SN_IS_XDELTA(sn) ? SN_XDELTA_LIMIT : SN_DELTA_LIMIT; ptrdiff_t newdelta = base + delta; if (newdelta < limit) { SN_SET_DELTA(sn, newdelta); } else { jssrcnote xdelta; SN_MAKE_XDELTA(&xdelta, delta); if (!main.notes.insert(sn, xdelta)) return false; } return true; } bool BytecodeEmitter::setSrcNoteOffset(unsigned index, unsigned which, ptrdiff_t offset) { if (!SN_REPRESENTABLE_OFFSET(offset)) { parser->tokenStream.reportError(JSMSG_NEED_DIET, js_script_str); return false; } SrcNotesVector& notes = this->notes(); /* Find the offset numbered which (i.e., skip exactly which offsets). */ jssrcnote* sn = ¬es[index]; MOZ_ASSERT(SN_TYPE(sn) != SRC_XDELTA); MOZ_ASSERT((int) which < js_SrcNoteSpec[SN_TYPE(sn)].arity); for (sn++; which; sn++, which--) { if (*sn & SN_4BYTE_OFFSET_FLAG) sn += 3; } /* * See if the new offset requires four bytes either by being too big or if * the offset has already been inflated (in which case, we need to stay big * to not break the srcnote encoding if this isn't the last srcnote). */ if (offset > (ptrdiff_t)SN_4BYTE_OFFSET_MASK || (*sn & SN_4BYTE_OFFSET_FLAG)) { /* Maybe this offset was already set to a four-byte value. */ if (!(*sn & SN_4BYTE_OFFSET_FLAG)) { /* Insert three dummy bytes that will be overwritten shortly. */ jssrcnote dummy = 0; if (!(sn = notes.insert(sn, dummy)) || !(sn = notes.insert(sn, dummy)) || !(sn = notes.insert(sn, dummy))) { ReportOutOfMemory(cx); return false; } } *sn++ = (jssrcnote)(SN_4BYTE_OFFSET_FLAG | (offset >> 24)); *sn++ = (jssrcnote)(offset >> 16); *sn++ = (jssrcnote)(offset >> 8); } *sn = (jssrcnote)offset; return true; } bool BytecodeEmitter::finishTakingSrcNotes(uint32_t* out) { MOZ_ASSERT(current == &main); unsigned prologueCount = prologue.notes.length(); if (prologueCount && prologue.currentLine != firstLine) { switchToPrologue(); if (!newSrcNote2(SRC_SETLINE, ptrdiff_t(firstLine))) return false; switchToMain(); } else { /* * Either no prologue srcnotes, or no line number change over prologue. * We don't need a SRC_SETLINE, but we may need to adjust the offset * of the first main note, by adding to its delta and possibly even * prepending SRC_XDELTA notes to it to account for prologue bytecodes * that came at and after the last annotated bytecode. */ ptrdiff_t offset = prologueOffset() - prologue.lastNoteOffset; MOZ_ASSERT(offset >= 0); if (offset > 0 && main.notes.length() != 0) { /* NB: Use as much of the first main note's delta as we can. */ jssrcnote* sn = main.notes.begin(); ptrdiff_t delta = SN_IS_XDELTA(sn) ? SN_XDELTA_MASK - (*sn & SN_XDELTA_MASK) : SN_DELTA_MASK - (*sn & SN_DELTA_MASK); if (offset < delta) delta = offset; for (;;) { if (!addToSrcNoteDelta(sn, delta)) return false; offset -= delta; if (offset == 0) break; delta = Min(offset, SN_XDELTA_MASK); sn = main.notes.begin(); } } } // The prologue count might have changed, so we can't reuse prologueCount. // The + 1 is to account for the final SN_MAKE_TERMINATOR that is appended // when the notes are copied to their final destination by CopySrcNotes. *out = prologue.notes.length() + main.notes.length() + 1; return true; } void BytecodeEmitter::copySrcNotes(jssrcnote* destination, uint32_t nsrcnotes) { unsigned prologueCount = prologue.notes.length(); unsigned mainCount = main.notes.length(); unsigned totalCount = prologueCount + mainCount; MOZ_ASSERT(totalCount == nsrcnotes - 1); if (prologueCount) PodCopy(destination, prologue.notes.begin(), prologueCount); PodCopy(destination + prologueCount, main.notes.begin(), mainCount); SN_MAKE_TERMINATOR(&destination[totalCount]); } void CGConstList::finish(ConstArray* array) { MOZ_ASSERT(length() == array->length); for (unsigned i = 0; i < length(); i++) array->vector[i] = list[i]; } /* * Find the index of the given object for code generator. * * Since the emitter refers to each parsed object only once, for the index we * use the number of already indexed objects. We also add the object to a list * to convert the list to a fixed-size array when we complete code generation, * see js::CGObjectList::finish below. */ unsigned CGObjectList::add(ObjectBox* objbox) { MOZ_ASSERT(!objbox->emitLink); objbox->emitLink = lastbox; lastbox = objbox; return length++; } unsigned CGObjectList::indexOf(JSObject* obj) { MOZ_ASSERT(length > 0); unsigned index = length - 1; for (ObjectBox* box = lastbox; box->object != obj; box = box->emitLink) index--; return index; } void CGObjectList::finish(ObjectArray* array) { MOZ_ASSERT(length <= INDEX_LIMIT); MOZ_ASSERT(length == array->length); js::GCPtrObject* cursor = array->vector + array->length; ObjectBox* objbox = lastbox; do { --cursor; MOZ_ASSERT(!*cursor); MOZ_ASSERT(objbox->object->isTenured()); *cursor = objbox->object; } while ((objbox = objbox->emitLink) != nullptr); MOZ_ASSERT(cursor == array->vector); } ObjectBox* CGObjectList::find(uint32_t index) { MOZ_ASSERT(index < length); ObjectBox* box = lastbox; for (unsigned n = length - 1; n > index; n--) box = box->emitLink; return box; } void CGScopeList::finish(ScopeArray* array) { MOZ_ASSERT(length() <= INDEX_LIMIT); MOZ_ASSERT(length() == array->length); for (uint32_t i = 0; i < length(); i++) array->vector[i].init(vector[i]); } bool CGTryNoteList::append(JSTryNoteKind kind, uint32_t stackDepth, size_t start, size_t end) { MOZ_ASSERT(start <= end); MOZ_ASSERT(size_t(uint32_t(start)) == start); MOZ_ASSERT(size_t(uint32_t(end)) == end); JSTryNote note; note.kind = kind; note.stackDepth = stackDepth; note.start = uint32_t(start); note.length = uint32_t(end - start); return list.append(note); } void CGTryNoteList::finish(TryNoteArray* array) { MOZ_ASSERT(length() == array->length); for (unsigned i = 0; i < length(); i++) array->vector[i] = list[i]; } bool CGScopeNoteList::append(uint32_t scopeIndex, uint32_t offset, bool inPrologue, uint32_t parent) { CGScopeNote note; mozilla::PodZero(¬e); note.index = scopeIndex; note.start = offset; note.parent = parent; note.startInPrologue = inPrologue; return list.append(note); } void CGScopeNoteList::recordEnd(uint32_t index, uint32_t offset, bool inPrologue) { MOZ_ASSERT(index < length()); MOZ_ASSERT(list[index].length == 0); list[index].end = offset; list[index].endInPrologue = inPrologue; } void CGScopeNoteList::finish(ScopeNoteArray* array, uint32_t prologueLength) { MOZ_ASSERT(length() == array->length); for (unsigned i = 0; i < length(); i++) { if (!list[i].startInPrologue) list[i].start += prologueLength; if (!list[i].endInPrologue && list[i].end != UINT32_MAX) list[i].end += prologueLength; MOZ_ASSERT(list[i].end >= list[i].start); list[i].length = list[i].end - list[i].start; array->vector[i] = list[i]; } } void CGYieldOffsetList::finish(YieldOffsetArray& array, uint32_t prologueLength) { MOZ_ASSERT(length() == array.length()); for (unsigned i = 0; i < length(); i++) array[i] = prologueLength + list[i]; } /* * We should try to get rid of offsetBias (always 0 or 1, where 1 is * JSOP_{NOP,POP}_LENGTH), which is used only by SRC_FOR. */ const JSSrcNoteSpec js_SrcNoteSpec[] = { #define DEFINE_SRC_NOTE_SPEC(sym, name, arity) { name, arity }, FOR_EACH_SRC_NOTE_TYPE(DEFINE_SRC_NOTE_SPEC) #undef DEFINE_SRC_NOTE_SPEC }; static int SrcNoteArity(jssrcnote* sn) { MOZ_ASSERT(SN_TYPE(sn) < SRC_LAST); return js_SrcNoteSpec[SN_TYPE(sn)].arity; } JS_FRIEND_API(unsigned) js::SrcNoteLength(jssrcnote* sn) { unsigned arity; jssrcnote* base; arity = SrcNoteArity(sn); for (base = sn++; arity; sn++, arity--) { if (*sn & SN_4BYTE_OFFSET_FLAG) sn += 3; } return sn - base; } JS_FRIEND_API(ptrdiff_t) js::GetSrcNoteOffset(jssrcnote* sn, unsigned which) { /* Find the offset numbered which (i.e., skip exactly which offsets). */ MOZ_ASSERT(SN_TYPE(sn) != SRC_XDELTA); MOZ_ASSERT((int) which < SrcNoteArity(sn)); for (sn++; which; sn++, which--) { if (*sn & SN_4BYTE_OFFSET_FLAG) sn += 3; } if (*sn & SN_4BYTE_OFFSET_FLAG) { return (ptrdiff_t)(((uint32_t)(sn[0] & SN_4BYTE_OFFSET_MASK) << 24) | (sn[1] << 16) | (sn[2] << 8) | sn[3]); } return (ptrdiff_t)*sn; }