/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* * JS bytecode generation. */ #include "frontend/BytecodeEmitter.h" #include "mozilla/Casting.h" // mozilla::AssertedCast #include "mozilla/DebugOnly.h" // mozilla::DebugOnly #include "mozilla/FloatingPoint.h" // mozilla::NumberEqualsInt32, mozilla::NumberIsInt32 #include "mozilla/Maybe.h" // mozilla::{Maybe,Nothing,Some} #include "mozilla/PodOperations.h" // mozilla::PodCopy #include "mozilla/Sprintf.h" // SprintfLiteral #include "mozilla/Unused.h" // mozilla::Unused #include "mozilla/Variant.h" // mozilla::AsVariant #include #include #include #include "jstypes.h" // JS_BIT #include "ds/Nestable.h" // Nestable #include "frontend/AbstractScopePtr.h" // ScopeIndex #include "frontend/BytecodeControlStructures.h" // NestableControl, BreakableControl, LabelControl, LoopControl, TryFinallyControl #include "frontend/CallOrNewEmitter.h" // CallOrNewEmitter #include "frontend/CForEmitter.h" // CForEmitter #include "frontend/DefaultEmitter.h" // DefaultEmitter #include "frontend/DoWhileEmitter.h" // DoWhileEmitter #include "frontend/ElemOpEmitter.h" // ElemOpEmitter #include "frontend/EmitterScope.h" // EmitterScope #include "frontend/ExpressionStatementEmitter.h" // ExpressionStatementEmitter #include "frontend/ForInEmitter.h" // ForInEmitter #include "frontend/ForOfEmitter.h" // ForOfEmitter #include "frontend/ForOfLoopControl.h" // ForOfLoopControl #include "frontend/FunctionEmitter.h" // FunctionEmitter, FunctionScriptEmitter, FunctionParamsEmitter #include "frontend/IfEmitter.h" // IfEmitter, InternalIfEmitter, CondEmitter #include "frontend/LabelEmitter.h" // LabelEmitter #include "frontend/LexicalScopeEmitter.h" // LexicalScopeEmitter #include "frontend/ModuleSharedContext.h" // ModuleSharedContext #include "frontend/NameAnalysisTypes.h" // PrivateNameKind #include "frontend/NameFunctions.h" // NameFunctions #include "frontend/NameOpEmitter.h" // NameOpEmitter #include "frontend/ObjectEmitter.h" // PropertyEmitter, ObjectEmitter, ClassEmitter #include "frontend/OptionalEmitter.h" // OptionalEmitter #include "frontend/ParseNode.h" // ParseNodeKind, ParseNode and subclasses #include "frontend/Parser.h" // Parser #include "frontend/ParserAtom.h" // ParserAtomsTable #include "frontend/PropOpEmitter.h" // PropOpEmitter #include "frontend/SourceNotes.h" // SrcNote, SrcNoteType, SrcNoteWriter #include "frontend/SwitchEmitter.h" // SwitchEmitter #include "frontend/TDZCheckCache.h" // TDZCheckCache #include "frontend/TryEmitter.h" // TryEmitter #include "frontend/WhileEmitter.h" // WhileEmitter #include "js/CompileOptions.h" // TransitiveCompileOptions, CompileOptions #include "js/friend/ErrorMessages.h" // JSMSG_* #include "js/friend/StackLimits.h" // CheckRecursionLimit #include "util/StringBuffer.h" // StringBuffer #include "vm/AsyncFunctionResolveKind.h" // AsyncFunctionResolveKind #include "vm/BytecodeUtil.h" // JOF_*, IsArgOp, IsLocalOp, SET_UINT24, SET_ICINDEX, BytecodeFallsThrough, BytecodeIsJumpTarget #include "vm/FunctionPrefixKind.h" // FunctionPrefixKind #include "vm/GeneratorObject.h" // AbstractGeneratorObject #include "vm/JSAtom.h" // JSAtom, js_*_str #include "vm/JSContext.h" // JSContext #include "vm/JSFunction.h" // JSFunction, #include "vm/JSScript.h" // JSScript, ScriptSourceObject, MemberInitializers, BaseScript #include "vm/Opcodes.h" // JSOp, JSOpLength_* #include "vm/SharedStencil.h" // ScopeNote #include "vm/ThrowMsgKind.h" // ThrowMsgKind #include "wasm/AsmJS.h" // IsAsmJSModule #include "vm/JSObject-inl.h" // JSObject using namespace js; using namespace js::frontend; using mozilla::AssertedCast; using mozilla::AsVariant; using mozilla::DebugOnly; using mozilla::Maybe; using mozilla::Nothing; using mozilla::NumberEqualsInt32; using mozilla::NumberIsInt32; using mozilla::PodCopy; using mozilla::Some; using mozilla::Unused; static bool ParseNodeRequiresSpecialLineNumberNotes(ParseNode* pn) { // The few node types listed below are exceptions to the usual // location-source-note-emitting code in BytecodeEmitter::emitTree(). // Single-line `while` loops and C-style `for` loops require careful // handling to avoid strange stepping behavior. // Functions usually shouldn't have location information (bug 1431202). ParseNodeKind kind = pn->getKind(); return kind == ParseNodeKind::WhileStmt || kind == ParseNodeKind::ForStmt || kind == ParseNodeKind::Function; } static bool NeedsFieldInitializer(ParseNode* member, bool isStatic) { return member->is() && member->as().isStatic() == isStatic; } static bool NeedsMethodInitializer(ParseNode* member, bool isStatic) { if (isStatic) { return false; } return member->is() && member->as().name().isKind(ParseNodeKind::PrivateName) && !member->as().isStatic(); } BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, SharedContext* sc, CompilationStencil& stencil, CompilationState& compilationState, EmitterMode emitterMode) : sc(sc), cx(sc->cx_), parent(parent), bytecodeSection_(cx, sc->extent().lineno, sc->extent().column), perScriptData_(cx, stencil, compilationState), stencil(stencil), compilationState(compilationState), emitterMode(emitterMode) {} BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, BCEParserHandle* handle, SharedContext* sc, CompilationStencil& stencil, CompilationState& compilationState, EmitterMode emitterMode) : BytecodeEmitter(parent, sc, stencil, compilationState, emitterMode) { parser = handle; instrumentationKinds = parser->options().instrumentationKinds; } BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, const EitherParser& parser, SharedContext* sc, CompilationStencil& stencil, CompilationState& compilationState, EmitterMode emitterMode) : BytecodeEmitter(parent, sc, stencil, compilationState, emitterMode) { ep_.emplace(parser); this->parser = ep_.ptr(); instrumentationKinds = this->parser->options().instrumentationKinds; } void BytecodeEmitter::initFromBodyPosition(TokenPos bodyPosition) { setScriptStartOffsetIfUnset(bodyPosition.begin); setFunctionBodyEndPos(bodyPosition.end); } bool BytecodeEmitter::init() { if (!parent) { if (!stencil.prepareStorageFor(cx, compilationState)) { return false; } } return perScriptData_.init(cx); } bool BytecodeEmitter::init(TokenPos bodyPosition) { initFromBodyPosition(bodyPosition); return init(); } 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(const ParserAtom* name) { return innermostEmitterScope()->lookup(this, name); } Maybe BytecodeEmitter::locationOfNameBoundInScope( const ParserAtom* name, EmitterScope* target) { return innermostEmitterScope()->locationBoundInScope(name, target); } template Maybe BytecodeEmitter::locationOfNameBoundInScopeType( const ParserAtom* name, EmitterScope* source) { EmitterScope* aScope = source; while (!aScope->scope(this).is()) { aScope = aScope->enclosingInFrame(); } return source->locationBoundInScope(name, aScope); } bool BytecodeEmitter::markStepBreakpoint() { if (skipBreakpointSrcNotes()) { return true; } if (!emitInstrumentation(InstrumentationKind::Breakpoint)) { return false; } if (!newSrcNote(SrcNoteType::StepSep)) { return false; } if (!newSrcNote(SrcNoteType::Breakpoint)) { return false; } // We track the location of the most recent separator for use in // markSimpleBreakpoint. Note that this means that the position must already // be set before markStepBreakpoint is called. bytecodeSection().updateSeparatorPosition(); return true; } bool BytecodeEmitter::markSimpleBreakpoint() { if (skipBreakpointSrcNotes()) { return true; } // If a breakable call ends up being the same location as the most recent // expression start, we need to skip marking it breakable in order to avoid // having two breakpoints with the same line/column position. // Note: This assumes that the position for the call has already been set. if (!bytecodeSection().isDuplicateLocation()) { if (!emitInstrumentation(InstrumentationKind::Breakpoint)) { return false; } if (!newSrcNote(SrcNoteType::Breakpoint)) { return false; } } return true; } bool BytecodeEmitter::emitCheck(JSOp op, ptrdiff_t delta, BytecodeOffset* offset) { size_t oldLength = bytecodeSection().code().length(); *offset = BytecodeOffset(oldLength); size_t newLength = oldLength + size_t(delta); if (MOZ_UNLIKELY(newLength > MaxBytecodeLength)) { ReportAllocationOverflow(cx); return false; } if (!bytecodeSection().code().growByUninitialized(delta)) { return false; } if (BytecodeOpHasIC(op)) { // Even if every bytecode op is a JOF_IC op and the function has ARGC_LIMIT // arguments, numICEntries cannot overflow. static_assert(MaxBytecodeLength + 1 /* this */ + ARGC_LIMIT <= UINT32_MAX, "numICEntries must not overflow"); bytecodeSection().incrementNumICEntries(); } return true; } #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)); BytecodeOffset offset; if (!emitCheck(op, 1, &offset)) { return false; } jsbytecode* code = bytecodeSection().code(offset); code[0] = jsbytecode(op); bytecodeSection().updateDepth(offset); return true; } bool BytecodeEmitter::emit2(JSOp op, uint8_t op1) { MOZ_ASSERT(checkStrictOrSloppy(op)); BytecodeOffset offset; if (!emitCheck(op, 2, &offset)) { return false; } jsbytecode* code = bytecodeSection().code(offset); code[0] = jsbytecode(op); code[1] = jsbytecode(op1); bytecodeSection().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)); BytecodeOffset offset; if (!emitCheck(op, 3, &offset)) { return false; } jsbytecode* code = bytecodeSection().code(offset); code[0] = jsbytecode(op); code[1] = op1; code[2] = op2; bytecodeSection().updateDepth(offset); return true; } bool BytecodeEmitter::emitN(JSOp op, size_t extra, BytecodeOffset* offset) { MOZ_ASSERT(checkStrictOrSloppy(op)); ptrdiff_t length = 1 + ptrdiff_t(extra); BytecodeOffset off; if (!emitCheck(op, length, &off)) { return false; } jsbytecode* code = bytecodeSection().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) { bytecodeSection().updateDepth(off); } if (offset) { *offset = off; } return true; } bool BytecodeEmitter::emitJumpTargetOp(JSOp op, BytecodeOffset* off) { MOZ_ASSERT(BytecodeIsJumpTarget(op)); // Record the current IC-entry index at start of this op. uint32_t numEntries = bytecodeSection().numICEntries(); size_t n = GetOpLength(op) - 1; MOZ_ASSERT(GetOpLength(op) >= 1 + ICINDEX_LEN); if (!emitN(op, n, off)) { return false; } SET_ICINDEX(bytecodeSection().code(*off), numEntries); return true; } bool BytecodeEmitter::emitJumpTarget(JumpTarget* target) { BytecodeOffset off = bytecodeSection().offset(); // Alias consecutive jump targets. if (bytecodeSection().lastTargetOffset().valid() && off == bytecodeSection().lastTargetOffset() + BytecodeOffsetDiff(JSOpLength_JumpTarget)) { target->offset = bytecodeSection().lastTargetOffset(); return true; } target->offset = off; bytecodeSection().setLastTargetOffset(off); BytecodeOffset opOff; return emitJumpTargetOp(JSOp::JumpTarget, &opOff); } bool BytecodeEmitter::emitJumpNoFallthrough(JSOp op, JumpList* jump) { BytecodeOffset offset; if (!emitCheck(op, 5, &offset)) { return false; } jsbytecode* code = bytecodeSection().code(offset); code[0] = jsbytecode(op); MOZ_ASSERT(!jump->offset.valid() || (0 <= jump->offset.value() && jump->offset < offset)); jump->push(bytecodeSection().code(BytecodeOffset(0)), offset); bytecodeSection().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; } void BytecodeEmitter::patchJumpsToTarget(JumpList jump, JumpTarget target) { MOZ_ASSERT( !jump.offset.valid() || (0 <= jump.offset.value() && jump.offset <= bytecodeSection().offset())); MOZ_ASSERT(0 <= target.offset.value() && target.offset <= bytecodeSection().offset()); MOZ_ASSERT_IF( jump.offset.valid() && target.offset + BytecodeOffsetDiff(4) <= bytecodeSection().offset(), BytecodeIsJumpTarget(JSOp(*bytecodeSection().code(target.offset)))); jump.patchAll(bytecodeSection().code(BytecodeOffset(0)), target); } bool BytecodeEmitter::emitJumpTargetAndPatch(JumpList jump) { if (!jump.offset.valid()) { return true; } JumpTarget target; if (!emitJumpTarget(&target)) { return false; } patchJumpsToTarget(jump, target); return true; } bool BytecodeEmitter::emitCall(JSOp op, uint16_t argc, const Maybe& sourceCoordOffset) { if (sourceCoordOffset.isSome()) { if (!updateSourceCoordNotes(*sourceCoordOffset)) { return false; } } return emit3(op, ARGC_LO(argc), ARGC_HI(argc)); } bool BytecodeEmitter::emitCall(JSOp op, uint16_t argc, ParseNode* pn) { return emitCall(op, argc, pn ? Some(pn->pn_pos.begin) : Nothing()); } bool BytecodeEmitter::emitDupAt(unsigned slotFromTop, unsigned count) { MOZ_ASSERT(slotFromTop < unsigned(bytecodeSection().stackDepth())); MOZ_ASSERT(slotFromTop + 1 >= count); if (slotFromTop == 0 && count == 1) { return emit1(JSOp::Dup); } if (slotFromTop == 1 && count == 2) { return emit1(JSOp::Dup2); } if (slotFromTop >= Bit(24)) { reportError(nullptr, JSMSG_TOO_MANY_LOCALS); return false; } for (unsigned i = 0; i < count; i++) { BytecodeOffset off; if (!emitN(JSOp::DupAt, 3, &off)) { return false; } jsbytecode* pc = bytecodeSection().code(off); SET_UINT24(pc, slotFromTop); } return true; } bool BytecodeEmitter::emitPopN(unsigned n) { MOZ_ASSERT(n != 0); if (n == 1) { return emit1(JSOp::Pop); } // 2 JSOp::Pop instructions (2 bytes) are shorter than JSOp::PopN (3 bytes). if (n == 2) { return emit1(JSOp::Pop) && emit1(JSOp::Pop); } return emitUint16Operand(JSOp::PopN, n); } bool BytecodeEmitter::emitPickN(uint8_t n) { MOZ_ASSERT(n != 0); if (n == 1) { return emit1(JSOp::Swap); } return emit2(JSOp::Pick, n); } bool BytecodeEmitter::emitUnpickN(uint8_t n) { MOZ_ASSERT(n != 0); if (n == 1) { return emit1(JSOp::Swap); } return emit2(JSOp::Unpick, n); } bool BytecodeEmitter::emitCheckIsObj(CheckIsObjectKind kind) { return emit2(JSOp::CheckIsObj, uint8_t(kind)); } bool BytecodeEmitter::emitBuiltinObject(BuiltinObjectKind kind) { return emit2(JSOp::BuiltinObject, uint8_t(kind)); } /* Updates line number notes, not column notes. */ bool BytecodeEmitter::updateLineNumberNotes(uint32_t offset) { if (skipLocationSrcNotes()) { return true; } ErrorReporter* er = &parser->errorReporter(); bool onThisLine; if (!er->isOnThisLine(offset, bytecodeSection().currentLine(), &onThisLine)) { er->errorNoOffset(JSMSG_OUT_OF_MEMORY); return false; } if (!onThisLine) { unsigned line = er->lineAt(offset); unsigned delta = line - bytecodeSection().currentLine(); // If we use a `SetLine` note below, we want it to be relative to the // scripts initial line number for better chance of sharing. unsigned initialLine = sc->extent().lineno; MOZ_ASSERT(line >= initialLine); /* * Encode any change in the current source line number by using * either several SrcNoteType::NewLine notes or just one * SrcNoteType::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 * SrcNoteType::SetLine. */ bytecodeSection().setCurrentLine(line, offset); if (delta >= SrcNote::SetLine::lengthFor(line, initialLine)) { if (!newSrcNote2(SrcNoteType::SetLine, SrcNote::SetLine::toOperand(line, initialLine))) { return false; } } else { do { if (!newSrcNote(SrcNoteType::NewLine)) { return false; } } while (--delta != 0); } bytecodeSection().updateSeparatorPositionIfPresent(); } 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; } if (skipLocationSrcNotes()) { return true; } uint32_t columnIndex = parser->errorReporter().columnAt(offset); MOZ_ASSERT(columnIndex <= ColumnLimit); // Assert colspan is always representable. static_assert((0 - ptrdiff_t(ColumnLimit)) >= SrcNote::ColSpan::MinColSpan); static_assert((ptrdiff_t(ColumnLimit) - 0) <= SrcNote::ColSpan::MaxColSpan); ptrdiff_t colspan = ptrdiff_t(columnIndex) - ptrdiff_t(bytecodeSection().lastColumn()); if (colspan != 0) { if (!newSrcNote2(SrcNoteType::ColSpan, SrcNote::ColSpan::toOperand(colspan))) { return false; } bytecodeSection().setLastColumn(columnIndex, offset); bytecodeSection().updateSeparatorPositionIfPresent(); } return true; } Maybe BytecodeEmitter::getOffsetForLoop(ParseNode* nextpn) { if (!nextpn) { return Nothing(); } // 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->is()) { nextpn = nextpn->as().scopeBody(); } if (nextpn->isKind(ParseNodeKind::StatementList)) { if (ParseNode* firstStatement = nextpn->as().head()) { nextpn = firstStatement; } } return Some(nextpn->pn_pos.begin); } bool BytecodeEmitter::emitUint16Operand(JSOp op, uint32_t operand) { MOZ_ASSERT(operand <= UINT16_MAX); if (!emit3(op, UINT16_LO(operand), UINT16_HI(operand))) { return false; } return true; } bool BytecodeEmitter::emitUint32Operand(JSOp op, uint32_t operand) { BytecodeOffset off; if (!emitN(op, 4, &off)) { return false; } SET_UINT32(bytecodeSection().code(off), operand); return true; } namespace { class NonLocalExitControl { public: enum Kind { // IteratorClose is handled especially inside the exception unwinder. Throw, // A 'continue' statement does not call IteratorClose for the loop it // is continuing, i.e. excluding the target loop. Continue, // A 'break' or 'return' statement does call IteratorClose for the // loop it is breaking out of or returning from, i.e. including the // target loop. Break, Return }; private: BytecodeEmitter* bce_; const uint32_t savedScopeNoteIndex_; const int savedDepth_; uint32_t openScopeNoteIndex_; Kind kind_; NonLocalExitControl(const NonLocalExitControl&) = delete; MOZ_MUST_USE bool leaveScope(EmitterScope* scope); public: NonLocalExitControl(BytecodeEmitter* bce, Kind kind) : bce_(bce), savedScopeNoteIndex_(bce->bytecodeSection().scopeNoteList().length()), savedDepth_(bce->bytecodeSection().stackDepth()), openScopeNoteIndex_(bce->innermostEmitterScope()->noteIndex()), kind_(kind) {} ~NonLocalExitControl() { for (uint32_t n = savedScopeNoteIndex_; n < bce_->bytecodeSection().scopeNoteList().length(); n++) { bce_->bytecodeSection().scopeNoteList().recordEnd( n, bce_->bytecodeSection().offset()); } bce_->bytecodeSection().setStackDepth(savedDepth_); } MOZ_MUST_USE bool prepareForNonLocalJump(NestableControl* target); MOZ_MUST_USE bool prepareForNonLocalJumpToOutermost() { return prepareForNonLocalJump(nullptr); } }; bool NonLocalExitControl::leaveScope(EmitterScope* es) { if (!es->leave(bce_, /* nonLocal = */ true)) { return false; } // As we pop each scope due to the non-local jump, emit notes that // record the extent of the enclosing scope. These notes will have // their ends recorded in ~NonLocalExitControl(). GCThingIndex enclosingScopeIndex = ScopeNote::NoScopeIndex; if (es->enclosingInFrame()) { enclosingScopeIndex = es->enclosingInFrame()->index(); } if (!bce_->bytecodeSection().scopeNoteList().append( enclosingScopeIndex, bce_->bytecodeSection().offset(), openScopeNoteIndex_)) { return false; } openScopeNoteIndex_ = bce_->bytecodeSection().scopeNoteList().length() - 1; return true; } /* * Emit additional bytecode(s) for non-local jumps. */ bool NonLocalExitControl::prepareForNonLocalJump(NestableControl* target) { EmitterScope* es = bce_->innermostEmitterScope(); int npops = 0; AutoCheckUnstableEmitterScope cues(bce_); // For 'continue', 'break', and 'return' statements, emit IteratorClose // bytecode inline. 'continue' statements do not call IteratorClose for // the loop they are continuing. bool emitIteratorClose = kind_ == Continue || kind_ == Break || kind_ == Return; bool emitIteratorCloseAtTarget = emitIteratorClose && kind_ != Continue; auto flushPops = [&npops](BytecodeEmitter* bce) { if (npops && !bce->emitPopN(npops)) { return false; } npops = 0; return true; }; // If we are closing multiple for-of loops, the resulting FOR_OF_ITERCLOSE // trynotes must be appropriately nested. Each FOR_OF_ITERCLOSE starts when // we close the corresponding for-of iterator, and continues until the // actual jump. Vector forOfIterCloseScopeStarts(bce_->cx); // 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_->emitGoSub(&finallyControl.gosubs)) { // [stack] ... return false; } } break; } case StatementKind::ForOfLoop: if (emitIteratorClose) { if (!flushPops(bce_)) { return false; } BytecodeOffset tryNoteStart; ForOfLoopControl& loopinfo = control->as(); if (!loopinfo.emitPrepareForNonLocalJumpFromScope( bce_, *es, /* isTarget = */ false, &tryNoteStart)) { // [stack] ... return false; } if (!forOfIterCloseScopeStarts.append(tryNoteStart)) { return false; } } else { // The iterator next method, the iterator, and the current // value are on the stack. npops += 3; } break; case StatementKind::ForInLoop: if (!flushPops(bce_)) { return false; } // The iterator and the current value are on the stack. if (!bce_->emit1(JSOp::EndIter)) { // [stack] ... return false; } break; default: break; } } if (!flushPops(bce_)) { return false; } if (target && emitIteratorCloseAtTarget && target->is()) { BytecodeOffset tryNoteStart; ForOfLoopControl& loopinfo = target->as(); if (!loopinfo.emitPrepareForNonLocalJumpFromScope(bce_, *es, /* isTarget = */ true, &tryNoteStart)) { // [stack] ... UNDEF UNDEF UNDEF return false; } if (!forOfIterCloseScopeStarts.append(tryNoteStart)) { return false; } } EmitterScope* targetEmitterScope = target ? target->emitterScope() : bce_->varEmitterScope; for (; es != targetEmitterScope; es = es->enclosingInFrame()) { if (!leaveScope(es)) { return false; } } // Close FOR_OF_ITERCLOSE trynotes. BytecodeOffset end = bce_->bytecodeSection().offset(); for (BytecodeOffset start : forOfIterCloseScopeStarts) { if (!bce_->addTryNote(TryNoteKind::ForOfIterClose, 0, start, end)) { return false; } } return true; } } // anonymous namespace bool BytecodeEmitter::emitGoto(NestableControl* target, JumpList* jumplist, GotoKind kind) { NonLocalExitControl nle(this, kind == GotoKind::Continue ? NonLocalExitControl::Continue : NonLocalExitControl::Break); if (!nle.prepareForNonLocalJump(target)) { return false; } return emitJump(JSOp::Goto, jumplist); } AbstractScopePtr BytecodeEmitter::innermostScope() const { return innermostEmitterScope()->scope(this); } ScopeIndex BytecodeEmitter::innermostScopeIndex() const { return *innermostEmitterScope()->scopeIndex(this); } bool BytecodeEmitter::emitGCIndexOp(JSOp op, GCThingIndex index) { MOZ_ASSERT(checkStrictOrSloppy(op)); constexpr size_t OpLength = 1 + GCTHING_INDEX_LEN; MOZ_ASSERT(GetOpLength(op) == OpLength); BytecodeOffset offset; if (!emitCheck(op, OpLength, &offset)) { return false; } jsbytecode* code = bytecodeSection().code(offset); code[0] = jsbytecode(op); SET_GCTHING_INDEX(code, index); bytecodeSection().updateDepth(offset); return true; } bool BytecodeEmitter::emitAtomOp(JSOp op, const ParserAtom* atom, ShouldInstrument shouldInstrument) { MOZ_ASSERT(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->parserNames().dotGenerator); GCThingIndex index; if (!makeAtomIndex(atom, &index)) { return false; } return emitAtomOp(op, index, shouldInstrument); } bool BytecodeEmitter::emitAtomOp(JSOp op, GCThingIndex atomIndex, ShouldInstrument shouldInstrument) { MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM); if (shouldInstrument != ShouldInstrument::No && !emitInstrumentationForOpcode(op, atomIndex)) { return false; } return emitGCIndexOp(op, atomIndex); } bool BytecodeEmitter::emitInternedScopeOp(GCThingIndex index, JSOp op) { MOZ_ASSERT(JOF_OPTYPE(op) == JOF_SCOPE); MOZ_ASSERT(index < perScriptData().gcThingList().length()); return emitGCIndexOp(op, index); } bool BytecodeEmitter::emitInternedObjectOp(GCThingIndex index, JSOp op) { MOZ_ASSERT(JOF_OPTYPE(op) == JOF_OBJECT); MOZ_ASSERT(index < perScriptData().gcThingList().length()); return emitGCIndexOp(op, index); } bool BytecodeEmitter::emitObjectPairOp(GCThingIndex index1, GCThingIndex index2, JSOp op) { MOZ_ASSERT(index1 + 1 == index2, "object pair indices must be adjacent"); return emitInternedObjectOp(index1, op); } bool BytecodeEmitter::emitRegExp(GCThingIndex index) { return emitGCIndexOp(JSOp::RegExp, index); } bool BytecodeEmitter::emitLocalOp(JSOp op, uint32_t slot) { MOZ_ASSERT(JOF_OPTYPE(op) != JOF_ENVCOORD); MOZ_ASSERT(IsLocalOp(op)); BytecodeOffset off; if (!emitN(op, LOCALNO_LEN, &off)) { return false; } SET_LOCALNO(bytecodeSection().code(off), slot); return true; } bool BytecodeEmitter::emitArgOp(JSOp op, uint16_t slot) { MOZ_ASSERT(IsArgOp(op)); BytecodeOffset off; if (!emitN(op, ARGNO_LEN, &off)) { return false; } SET_ARGNO(bytecodeSection().code(off), slot); return true; } bool BytecodeEmitter::emitEnvCoordOp(JSOp op, EnvironmentCoordinate ec) { MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ENVCOORD); constexpr size_t N = ENVCOORD_HOPS_LEN + ENVCOORD_SLOT_LEN; MOZ_ASSERT(GetOpLength(op) == 1 + N); BytecodeOffset off; if (!emitN(op, N, &off)) { return false; } jsbytecode* pc = bytecodeSection().code(off); SET_ENVCOORD_HOPS(pc, ec.hops()); pc += ENVCOORD_HOPS_LEN; SET_ENVCOORD_SLOT(pc, ec.slot()); pc += ENVCOORD_SLOT_LEN; return true; } 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) { if (!CheckRecursionLimit(cx)) { return false; } restart: switch (pn->getKind()) { // Trivial cases with no side effects. case ParseNodeKind::EmptyStmt: case ParseNodeKind::TrueExpr: case ParseNodeKind::FalseExpr: case ParseNodeKind::NullExpr: case ParseNodeKind::RawUndefinedExpr: case ParseNodeKind::Elision: case ParseNodeKind::Generator: MOZ_ASSERT(pn->is()); *answer = false; return true; case ParseNodeKind::ObjectPropertyName: case ParseNodeKind::PrivateName: // no side effects, unlike // ParseNodeKind::Name case ParseNodeKind::StringExpr: case ParseNodeKind::TemplateStringExpr: MOZ_ASSERT(pn->is()); *answer = false; return true; case ParseNodeKind::RegExpExpr: MOZ_ASSERT(pn->is()); *answer = false; return true; case ParseNodeKind::NumberExpr: MOZ_ASSERT(pn->is()); *answer = false; return true; case ParseNodeKind::BigIntExpr: MOZ_ASSERT(pn->is()); *answer = false; return true; // |this| can throw in derived class constructors, including nested arrow // functions or eval. case ParseNodeKind::ThisExpr: MOZ_ASSERT(pn->is()); *answer = sc->needsThisTDZChecks(); return true; // Trivial binary nodes with more token pos holders. case ParseNodeKind::NewTargetExpr: case ParseNodeKind::ImportMetaExpr: { MOZ_ASSERT(pn->as().left()->isKind(ParseNodeKind::PosHolder)); MOZ_ASSERT( pn->as().right()->isKind(ParseNodeKind::PosHolder)); *answer = false; return true; } case ParseNodeKind::BreakStmt: MOZ_ASSERT(pn->is()); *answer = true; return true; case ParseNodeKind::ContinueStmt: MOZ_ASSERT(pn->is()); *answer = true; return true; case ParseNodeKind::DebuggerStmt: MOZ_ASSERT(pn->is()); *answer = true; return true; // Watch out for getters! case ParseNodeKind::OptionalDotExpr: case ParseNodeKind::DotExpr: MOZ_ASSERT(pn->is()); *answer = true; return true; // Unary cases with side effects only if the child has them. case ParseNodeKind::TypeOfExpr: case ParseNodeKind::VoidExpr: case ParseNodeKind::NotExpr: return checkSideEffects(pn->as().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 ParseNodeKind::ComputedName: MOZ_ASSERT(pn->is()); *answer = true; return true; // Looking up or evaluating the associated name could throw. case ParseNodeKind::TypeOfNameExpr: MOZ_ASSERT(pn->is()); *answer = true; return true; // This unary case has side effects on the enclosing object, 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, because an object literal having a mutated prototype only // produces a value, without affecting anything else. case ParseNodeKind::MutateProto: return checkSideEffects(pn->as().kid(), answer); // Unary cases with obvious side effects. case ParseNodeKind::PreIncrementExpr: case ParseNodeKind::PostIncrementExpr: case ParseNodeKind::PreDecrementExpr: case ParseNodeKind::PostDecrementExpr: case ParseNodeKind::ThrowStmt: MOZ_ASSERT(pn->is()); *answer = true; return true; // These might invoke valueOf/toString, even with a subexpression without // side effects! Consider |+{ valueOf: null, toString: null }|. case ParseNodeKind::BitNotExpr: case ParseNodeKind::PosExpr: case ParseNodeKind::NegExpr: MOZ_ASSERT(pn->is()); *answer = true; return true; // This invokes the (user-controllable) iterator protocol. case ParseNodeKind::Spread: MOZ_ASSERT(pn->is()); *answer = true; return true; case ParseNodeKind::InitialYield: case ParseNodeKind::YieldStarExpr: case ParseNodeKind::YieldExpr: case ParseNodeKind::AwaitExpr: MOZ_ASSERT(pn->is()); *answer = true; return true; // Deletion generally has side effects, even if isolated cases have none. case ParseNodeKind::DeleteNameExpr: case ParseNodeKind::DeletePropExpr: case ParseNodeKind::DeleteElemExpr: case ParseNodeKind::DeleteOptionalChainExpr: MOZ_ASSERT(pn->is()); *answer = true; return true; // Deletion of a non-Reference expression has side effects only through // evaluating the expression. case ParseNodeKind::DeleteExpr: { ParseNode* expr = pn->as().kid(); return checkSideEffects(expr, answer); } case ParseNodeKind::ExpressionStmt: return checkSideEffects(pn->as().kid(), answer); // Binary cases with obvious side effects. case ParseNodeKind::InitExpr: *answer = true; return true; case ParseNodeKind::AssignExpr: case ParseNodeKind::AddAssignExpr: case ParseNodeKind::SubAssignExpr: case ParseNodeKind::CoalesceAssignExpr: case ParseNodeKind::OrAssignExpr: case ParseNodeKind::AndAssignExpr: case ParseNodeKind::BitOrAssignExpr: case ParseNodeKind::BitXorAssignExpr: case ParseNodeKind::BitAndAssignExpr: case ParseNodeKind::LshAssignExpr: case ParseNodeKind::RshAssignExpr: case ParseNodeKind::UrshAssignExpr: case ParseNodeKind::MulAssignExpr: case ParseNodeKind::DivAssignExpr: case ParseNodeKind::ModAssignExpr: case ParseNodeKind::PowAssignExpr: MOZ_ASSERT(pn->is()); *answer = true; return true; case ParseNodeKind::SetThis: MOZ_ASSERT(pn->is()); *answer = true; return true; case ParseNodeKind::StatementList: // Strict equality operations and short circuit operators are well-behaved // and perform no conversions. case ParseNodeKind::CoalesceExpr: case ParseNodeKind::OrExpr: case ParseNodeKind::AndExpr: case ParseNodeKind::StrictEqExpr: case ParseNodeKind::StrictNeExpr: // Any subexpression of a comma expression could be effectful. case ParseNodeKind::CommaExpr: MOZ_ASSERT(!pn->as().empty()); [[fallthrough]]; // Subcomponents of a literal may be effectful. case ParseNodeKind::ArrayExpr: case ParseNodeKind::ObjectExpr: for (ParseNode* item : pn->as().contents()) { 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 ParseNodeKind::BitOrExpr: case ParseNodeKind::BitXorExpr: case ParseNodeKind::BitAndExpr: case ParseNodeKind::EqExpr: case ParseNodeKind::NeExpr: case ParseNodeKind::LtExpr: case ParseNodeKind::LeExpr: case ParseNodeKind::GtExpr: case ParseNodeKind::GeExpr: case ParseNodeKind::InstanceOfExpr: case ParseNodeKind::InExpr: case ParseNodeKind::LshExpr: case ParseNodeKind::RshExpr: case ParseNodeKind::UrshExpr: case ParseNodeKind::AddExpr: case ParseNodeKind::SubExpr: case ParseNodeKind::MulExpr: case ParseNodeKind::DivExpr: case ParseNodeKind::ModExpr: case ParseNodeKind::PowExpr: MOZ_ASSERT(pn->as().count() >= 2); *answer = true; return true; case ParseNodeKind::PropertyDefinition: case ParseNodeKind::Case: { BinaryNode* node = &pn->as(); if (!checkSideEffects(node->left(), answer)) { return false; } if (*answer) { return true; } return checkSideEffects(node->right(), answer); } // More getters. case ParseNodeKind::OptionalElemExpr: case ParseNodeKind::ElemExpr: MOZ_ASSERT(pn->is()); *answer = true; return true; // These affect visible names in this code, or in other code. case ParseNodeKind::ImportDecl: case ParseNodeKind::ExportFromStmt: case ParseNodeKind::ExportDefaultStmt: MOZ_ASSERT(pn->is()); *answer = true; return true; // Likewise. case ParseNodeKind::ExportStmt: MOZ_ASSERT(pn->is()); *answer = true; return true; case ParseNodeKind::CallImportExpr: MOZ_ASSERT(pn->is()); *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 ParseNodeKind::DoWhileStmt: case ParseNodeKind::WhileStmt: case ParseNodeKind::ForStmt: MOZ_ASSERT(pn->is()); *answer = true; return true; // Declarations affect the name set of the relevant scope. case ParseNodeKind::VarStmt: case ParseNodeKind::ConstDecl: case ParseNodeKind::LetDecl: MOZ_ASSERT(pn->is()); *answer = true; return true; case ParseNodeKind::IfStmt: case ParseNodeKind::ConditionalExpr: { TernaryNode* node = &pn->as(); if (!checkSideEffects(node->kid1(), answer)) { return false; } if (*answer) { return true; } if (!checkSideEffects(node->kid2(), answer)) { return false; } if (*answer) { return true; } if ((pn = node->kid3())) { goto restart; } return true; } // Function calls can invoke non-local code. case ParseNodeKind::NewExpr: case ParseNodeKind::CallExpr: case ParseNodeKind::OptionalCallExpr: case ParseNodeKind::TaggedTemplateExpr: case ParseNodeKind::SuperCallExpr: MOZ_ASSERT(pn->is()); *answer = true; return true; // Function arg lists can contain arbitrary expressions. Technically // this only causes side-effects if one of the arguments does, but since // the call being made will always trigger side-effects, it isn't needed. case ParseNodeKind::Arguments: MOZ_ASSERT(pn->is()); *answer = true; return true; case ParseNodeKind::OptionalChain: MOZ_ASSERT(pn->is()); *answer = true; return true; case ParseNodeKind::PipelineExpr: MOZ_ASSERT(pn->as().count() >= 2); *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 ParseNodeKind::ClassDecl: MOZ_ASSERT(pn->is()); *answer = true; return true; // |with| calls |ToObject| on its expression and so throws if that value // is null/undefined. case ParseNodeKind::WithStmt: MOZ_ASSERT(pn->is()); *answer = true; return true; case ParseNodeKind::ReturnStmt: MOZ_ASSERT(pn->is()); *answer = true; return true; case ParseNodeKind::Name: MOZ_ASSERT(pn->is()); *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 ParseNodeKind::Shorthand: MOZ_ASSERT(pn->is()); *answer = true; return true; case ParseNodeKind::Function: MOZ_ASSERT(pn->is()); /* * 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 ParseNodeKind::Module: *answer = false; return true; case ParseNodeKind::TryStmt: { TryNode* tryNode = &pn->as(); if (!checkSideEffects(tryNode->body(), answer)) { return false; } if (*answer) { return true; } if (LexicalScopeNode* catchScope = tryNode->catchScope()) { if (!checkSideEffects(catchScope, answer)) { return false; } if (*answer) { return true; } } if (ParseNode* finallyBlock = tryNode->finallyBlock()) { if (!checkSideEffects(finallyBlock, answer)) { return false; } } return true; } case ParseNodeKind::Catch: { BinaryNode* catchClause = &pn->as(); if (ParseNode* name = catchClause->left()) { if (!checkSideEffects(name, answer)) { return false; } if (*answer) { return true; } } return checkSideEffects(catchClause->right(), answer); } case ParseNodeKind::SwitchStmt: { SwitchStatement* switchStmt = &pn->as(); if (!checkSideEffects(&switchStmt->discriminant(), answer)) { return false; } return *answer || checkSideEffects(&switchStmt->lexicalForCaseList(), answer); } case ParseNodeKind::LabelStmt: return checkSideEffects(pn->as().statement(), answer); case ParseNodeKind::LexicalScope: return checkSideEffects(pn->as().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 ParseNodeKind::TemplateStringListExpr: { ListNode* list = &pn->as(); MOZ_ASSERT(!list->empty()); MOZ_ASSERT((list->count() % 2) == 1, "template strings must alternate template and substitution " "parts"); *answer = list->count() > 1; return true; } // This should be unreachable but is left as-is for now. case ParseNodeKind::ParamsBody: *answer = true; return true; case ParseNodeKind::ForIn: // by ParseNodeKind::For case ParseNodeKind::ForOf: // by ParseNodeKind::For case ParseNodeKind::ForHead: // by ParseNodeKind::For case ParseNodeKind::ClassMethod: // by ParseNodeKind::ClassDecl case ParseNodeKind::ClassField: // by ParseNodeKind::ClassDecl case ParseNodeKind::ClassNames: // by ParseNodeKind::ClassDecl case ParseNodeKind::ClassMemberList: // by ParseNodeKind::ClassDecl case ParseNodeKind::ImportSpecList: // by ParseNodeKind::Import case ParseNodeKind::ImportSpec: // by ParseNodeKind::Import case ParseNodeKind::ExportBatchSpecStmt: // by ParseNodeKind::Export case ParseNodeKind::ExportSpecList: // by ParseNodeKind::Export case ParseNodeKind::ExportSpec: // by ParseNodeKind::Export case ParseNodeKind::CallSiteObj: // by ParseNodeKind::TaggedTemplate case ParseNodeKind::PosHolder: // by ParseNodeKind::NewTarget case ParseNodeKind::SuperBase: // by ParseNodeKind::Elem and others case ParseNodeKind::PropertyNameExpr: // by ParseNodeKind::Dot MOZ_CRASH("handled by parent nodes"); case ParseNodeKind::LastUnused: case ParseNodeKind::Limit: MOZ_CRASH("invalid node kind"); } MOZ_CRASH( "invalid, unenumerated ParseNodeKind value encountered in " "BytecodeEmitter::checkSideEffects"); } bool BytecodeEmitter::isInLoop() { return findInnermostNestableControl(); } bool BytecodeEmitter::checkSingletonContext() { MOZ_ASSERT_IF(sc->treatAsRunOnce(), sc->isTopLevelContext()); return sc->treatAsRunOnce() && !isInLoop(); } 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; } size_t BytecodeEmitter::countThisEnvironmentHops() { unsigned numHops = 0; for (BytecodeEmitter* current = this; current; current = current->parent) { for (EmitterScope* es = current->innermostEmitterScope(); es; es = es->enclosingInFrame()) { if (es->scope(current).is()) { if (!es->scope(current).isArrow()) { // The Parser is responsible for marking the environment as either // closed-over or used-by-eval which ensure that is must exist. MOZ_ASSERT(es->scope(current).hasEnvironment()); return numHops; } } if (es->scope(current).hasEnvironment()) { numHops++; } } } // The "this" environment exists outside of the compilation, but the // `ScopeContext` recorded the number of additional hops needed, so add // those in now. MOZ_ASSERT(sc->allowSuperProperty()); numHops += compilationState.scopeContext.enclosingThisEnvironmentHops; return numHops; } bool BytecodeEmitter::emitThisEnvironmentCallee() { // Get the innermost enclosing function that has a |this| binding. // Directly load callee from the frame if possible. if (sc->isFunctionBox() && !sc->asFunctionBox()->isArrow()) { return emit1(JSOp::Callee); } // We have to load the callee from the environment chain. size_t numHops = countThisEnvironmentHops(); static_assert( ENVCOORD_HOPS_LIMIT - 1 <= UINT8_MAX, "JSOp::EnvCallee operand size should match ENVCOORD_HOPS_LIMIT"); // Note: we need to check numHops here because we don't call // checkEnvironmentChainLength in all cases (like 'eval'). if (numHops >= ENVCOORD_HOPS_LIMIT - 1) { reportError(nullptr, JSMSG_TOO_DEEP, js_function_str); return false; } return emit2(JSOp::EnvCallee, numHops); } bool BytecodeEmitter::emitSuperBase() { if (!emitThisEnvironmentCallee()) { return false; } return emit1(JSOp::SuperBase); } void BytecodeEmitter::reportNeedMoreArgsError(ParseNode* pn, const char* errorName, const char* requiredArgs, const char* pluralizer, const ListNode* argsList) { char actualArgsStr[40]; SprintfLiteral(actualArgsStr, "%u", argsList->count()); reportError(pn, JSMSG_MORE_ARGS_NEEDED, errorName, requiredArgs, pluralizer, actualArgsStr); } void BytecodeEmitter::reportError(ParseNode* pn, unsigned errorNumber, ...) { uint32_t offset = pn ? pn->pn_pos.begin : *scriptStartOffset; va_list args; va_start(args, errorNumber); parser->errorReporter().errorWithNotesAtVA(nullptr, AsVariant(offset), errorNumber, &args); va_end(args); } void BytecodeEmitter::reportError(const Maybe& maybeOffset, unsigned errorNumber, ...) { uint32_t offset = maybeOffset ? *maybeOffset : *scriptStartOffset; va_list args; va_start(args, errorNumber); parser->errorReporter().errorWithNotesAtVA(nullptr, AsVariant(offset), errorNumber, &args); va_end(args); } bool BytecodeEmitter::addObjLiteralData(ObjLiteralWriter& writer, GCThingIndex* outIndex) { size_t len = writer.getCode().size(); auto* code = stencil.alloc.newArrayUninitialized(len); if (!code) { js::ReportOutOfMemory(cx); return false; } memcpy(code, writer.getCode().data(), len); ObjLiteralIndex objIndex(stencil.objLiteralData.length()); if (uint32_t(objIndex) >= TaggedScriptThingIndex::IndexLimit) { ReportAllocationOverflow(cx); return false; } if (!stencil.objLiteralData.emplaceBack(code, len, writer.getFlags())) { js::ReportOutOfMemory(cx); return false; } return perScriptData().gcThingList().append(objIndex, outIndex); } bool BytecodeEmitter::iteratorResultShape(GCThingIndex* outShape) { ObjLiteralFlags flags; ObjLiteralWriter writer; writer.beginObject(flags); using WellKnownName = js::frontend::WellKnownParserAtoms; for (auto name : {&WellKnownName::value, &WellKnownName::done}) { const ParserAtom* propName = cx->parserNames().*name; writer.setPropName(propName); if (!writer.propWithUndefinedValue(cx)) { return false; } } return addObjLiteralData(writer, outShape); } bool BytecodeEmitter::emitPrepareIteratorResult() { GCThingIndex shape; if (!iteratorResultShape(&shape)) { return false; } return emitGCIndexOp(JSOp::NewObject, shape); } bool BytecodeEmitter::emitFinishIteratorResult(bool done) { if (!emitAtomOp(JSOp::InitProp, cx->parserNames().value)) { return false; } if (!emit1(done ? JSOp::True : JSOp::False)) { return false; } if (!emitAtomOp(JSOp::InitProp, cx->parserNames().done)) { return false; } return true; } bool BytecodeEmitter::emitGetNameAtLocation(const ParserAtom* name, const NameLocation& loc) { NameOpEmitter noe(this, name, loc, NameOpEmitter::Kind::Get); if (!noe.emitGet()) { return false; } return true; } bool BytecodeEmitter::emitGetName(NameNode* name) { MOZ_ASSERT(name->isKind(ParseNodeKind::Name)); const ParserAtom* nameAtom = name->name(); return emitGetName(nameAtom); } bool BytecodeEmitter::emitGetPrivateName(NameNode* name) { MOZ_ASSERT(name->isKind(ParseNodeKind::PrivateName)); return emitGetPrivateName(name->name()); } bool BytecodeEmitter::emitGetPrivateName(const ParserAtom* nameAtom) { // The parser ensures the private name is present on the environment chain. NameLocation location = lookupName(nameAtom); MOZ_ASSERT(location.kind() == NameLocation::Kind::FrameSlot || location.kind() == NameLocation::Kind::EnvironmentCoordinate || location.kind() == NameLocation::Kind::Dynamic); return emitGetNameAtLocation(nameAtom, location); } bool BytecodeEmitter::emitTDZCheckIfNeeded(const ParserAtom* name, const NameLocation& loc, ValueIsOnStack isOnStack) { // 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 the value is not on the stack, we have to load it first. if (isOnStack == ValueIsOnStack::No) { if (loc.kind() == NameLocation::Kind::FrameSlot) { if (!emitLocalOp(JSOp::GetLocal, loc.frameSlot())) { return false; } } else { if (!emitEnvCoordOp(JSOp::GetAliasedVar, loc.environmentCoordinate())) { return false; } } } // Emit the lexical check. if (loc.kind() == NameLocation::Kind::FrameSlot) { if (!emitLocalOp(JSOp::CheckLexical, loc.frameSlot())) { return false; } } else { if (!emitEnvCoordOp(JSOp::CheckAliasedLexical, loc.environmentCoordinate())) { return false; } } // Pop the value if needed. if (isOnStack == ValueIsOnStack::No) { if (!emit1(JSOp::Pop)) { return false; } } return innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ); } bool BytecodeEmitter::emitPropLHS(PropertyAccess* prop) { MOZ_ASSERT(!prop->isSuper()); ParseNode* expr = &prop->expression(); if (!expr->is() || expr->as().isSuper()) { // The non-optimized case. return emitTree(expr); } // If the object operand is also a dotted property reference, reverse the // list linked via expression() temporarily so we can iterate over it from // the bottom up (reversing again as we go), to avoid excessive recursion. PropertyAccess* pndot = &expr->as(); ParseNode* pnup = nullptr; ParseNode* pndown; for (;;) { // Reverse pndot->expression() to point up, not down. pndown = &pndot->expression(); pndot->setExpression(pnup); if (!pndown->is() || pndown->as().isSuper()) { break; } pnup = pndot; pndot = &pndown->as(); } // pndown is a primary expression, not a dotted property reference. if (!emitTree(pndown)) { return false; } while (true) { // Walk back up the list, emitting annotated name ops. if (!emitAtomOp(JSOp::GetProp, pndot->key().atom(), ShouldInstrument::Yes)) { return false; } // Reverse the pndot->expression() link again. pnup = pndot->maybeExpression(); pndot->setExpression(pndown); pndown = pndot; if (!pnup) { break; } pndot = &pnup->as(); } return true; } bool BytecodeEmitter::emitPropIncDec(UnaryNode* incDec) { PropertyAccess* prop = &incDec->kid()->as(); bool isSuper = prop->isSuper(); ParseNodeKind kind = incDec->getKind(); PropOpEmitter poe( this, kind == ParseNodeKind::PostIncrementExpr ? PropOpEmitter::Kind::PostIncrement : kind == ParseNodeKind::PreIncrementExpr ? PropOpEmitter::Kind::PreIncrement : kind == ParseNodeKind::PostDecrementExpr ? PropOpEmitter::Kind::PostDecrement : PropOpEmitter::Kind::PreDecrement, isSuper ? PropOpEmitter::ObjKind::Super : PropOpEmitter::ObjKind::Other); if (!poe.prepareForObj()) { return false; } if (isSuper) { UnaryNode* base = &prop->expression().as(); if (!emitGetThisForSuperBase(base)) { // [stack] THIS return false; } } else { if (!emitPropLHS(prop)) { // [stack] OBJ return false; } } if (!poe.emitIncDec(prop->key().atom())) { // [stack] RESULT return false; } return true; } bool BytecodeEmitter::emitNameIncDec(UnaryNode* incDec) { MOZ_ASSERT(incDec->kid()->isKind(ParseNodeKind::Name)); ParseNodeKind kind = incDec->getKind(); NameNode* name = &incDec->kid()->as(); const ParserAtom* nameAtom = name->atom(); NameOpEmitter noe(this, nameAtom, kind == ParseNodeKind::PostIncrementExpr ? NameOpEmitter::Kind::PostIncrement : kind == ParseNodeKind::PreIncrementExpr ? NameOpEmitter::Kind::PreIncrement : kind == ParseNodeKind::PostDecrementExpr ? NameOpEmitter::Kind::PostDecrement : NameOpEmitter::Kind::PreDecrement); if (!noe.emitIncDec()) { return false; } return true; } bool BytecodeEmitter::emitElemOpBase(JSOp op, ShouldInstrument shouldInstrument) { GCThingIndex unused; if (shouldInstrument != ShouldInstrument::No && !emitInstrumentationForOpcode(op, unused)) { return false; } if (!emit1(op)) { return false; } return true; } bool BytecodeEmitter::emitElemObjAndKey(PropertyByValue* elem, bool isSuper, ElemOpEmitter& eoe) { if (isSuper) { if (!eoe.prepareForObj()) { // [stack] return false; } UnaryNode* base = &elem->expression().as(); if (!emitGetThisForSuperBase(base)) { // [stack] THIS return false; } if (!eoe.prepareForKey()) { // [stack] THIS return false; } if (!emitTree(&elem->key())) { // [stack] THIS KEY return false; } return true; } if (!eoe.prepareForObj()) { // [stack] return false; } if (!emitTree(&elem->expression())) { // [stack] OBJ return false; } if (!eoe.prepareForKey()) { // [stack] OBJ? OBJ return false; } if (!emitTree(&elem->key())) { // [stack] OBJ? OBJ KEY return false; } return true; } bool BytecodeEmitter::emitElemIncDec(UnaryNode* incDec) { PropertyByValue* elemExpr = &incDec->kid()->as(); bool isSuper = elemExpr->isSuper(); bool isPrivate = elemExpr->key().isKind(ParseNodeKind::PrivateName); ParseNodeKind kind = incDec->getKind(); ElemOpEmitter eoe( this, kind == ParseNodeKind::PostIncrementExpr ? ElemOpEmitter::Kind::PostIncrement : kind == ParseNodeKind::PreIncrementExpr ? ElemOpEmitter::Kind::PreIncrement : kind == ParseNodeKind::PostDecrementExpr ? ElemOpEmitter::Kind::PostDecrement : ElemOpEmitter::Kind::PreDecrement, isSuper ? ElemOpEmitter::ObjKind::Super : ElemOpEmitter::ObjKind::Other, isPrivate ? NameVisibility::Private : NameVisibility::Public); if (!emitElemObjAndKey(elemExpr, isSuper, eoe)) { // [stack] # if Super // [stack] THIS KEY // [stack] # otherwise // [stack] OBJ KEY return false; } if (!eoe.emitIncDec()) { // [stack] RESULT return false; } return true; } bool BytecodeEmitter::emitCallIncDec(UnaryNode* incDec) { MOZ_ASSERT(incDec->isKind(ParseNodeKind::PreIncrementExpr) || incDec->isKind(ParseNodeKind::PostIncrementExpr) || incDec->isKind(ParseNodeKind::PreDecrementExpr) || incDec->isKind(ParseNodeKind::PostDecrementExpr)); ParseNode* call = incDec->kid(); MOZ_ASSERT(call->isKind(ParseNodeKind::CallExpr)); if (!emitTree(call)) { // [stack] CALLRESULT return false; } if (!emit1(JSOp::ToNumeric)) { // [stack] N return false; } // The increment/decrement has no side effects, so proceed to throw for // invalid assignment target. return emit2(JSOp::ThrowMsg, uint8_t(ThrowMsgKind::AssignToCall)); } bool BytecodeEmitter::emitDouble(double d) { BytecodeOffset offset; if (!emitCheck(JSOp::Double, 9, &offset)) { return false; } jsbytecode* code = bytecodeSection().code(offset); code[0] = jsbytecode(JSOp::Double); SET_INLINE_VALUE(code, DoubleValue(d)); bytecodeSection().updateDepth(offset); return true; } 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 < Bit(16)) { if (!emitUint16Operand(JSOp::Uint16, u)) { return false; } } else if (u < Bit(24)) { BytecodeOffset off; if (!emitN(JSOp::Uint24, 3, &off)) { return false; } SET_UINT24(bytecodeSection().code(off), u); } else { BytecodeOffset off; if (!emitN(JSOp::Int32, 4, &off)) { return false; } SET_INT32(bytecodeSection().code(off), ival); } return true; } return emitDouble(dval); } /* * 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(SwitchStatement* switchStmt) { LexicalScopeNode& lexical = switchStmt->lexicalForCaseList(); MOZ_ASSERT(lexical.isKind(ParseNodeKind::LexicalScope)); ListNode* cases = &lexical.scopeBody()->as(); MOZ_ASSERT(cases->isKind(ParseNodeKind::StatementList)); SwitchEmitter se(this); if (!se.emitDiscriminant(Some(switchStmt->discriminant().pn_pos.begin))) { return false; } if (!markStepBreakpoint()) { return false; } if (!emitTree(&switchStmt->discriminant())) { return false; } // Enter the scope before pushing the switch BreakableControl since all // breaks are under this scope. if (!lexical.isEmptyScope()) { if (!se.emitLexical(lexical.scopeBindings())) { return false; } // 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->hasTopLevelFunctionDeclarations()) { for (ParseNode* item : cases->contents()) { CaseClause* caseClause = &item->as(); ListNode* statements = caseClause->statementList(); if (statements->hasTopLevelFunctionDeclarations()) { if (!emitHoistedFunctionsInList(statements)) { return false; } } } } } else { MOZ_ASSERT(!cases->hasTopLevelFunctionDeclarations()); } SwitchEmitter::TableGenerator tableGen(this); uint32_t caseCount = cases->count() - (switchStmt->hasDefault() ? 1 : 0); if (caseCount == 0) { tableGen.finish(0); } else { for (ParseNode* item : cases->contents()) { CaseClause* caseClause = &item->as(); if (caseClause->isDefault()) { continue; } ParseNode* caseValue = caseClause->caseExpression(); if (caseValue->getKind() != ParseNodeKind::NumberExpr) { tableGen.setInvalid(); break; } int32_t i; if (!NumberEqualsInt32(caseValue->as().value(), &i)) { tableGen.setInvalid(); break; } if (!tableGen.addNumber(i)) { return false; } } tableGen.finish(caseCount); } if (!se.validateCaseCount(caseCount)) { return false; } bool isTableSwitch = tableGen.isValid(); if (isTableSwitch) { if (!se.emitTable(tableGen)) { return false; } } else { if (!se.emitCond()) { return false; } // Emit code for evaluating cases and jumping to case statements. for (ParseNode* item : cases->contents()) { CaseClause* caseClause = &item->as(); if (caseClause->isDefault()) { continue; } if (!se.prepareForCaseValue()) { return false; } ParseNode* caseValue = caseClause->caseExpression(); // If the expression is a literal, suppress line number emission so // that debugging works more naturally. if (!emitTree( caseValue, ValueUsage::WantValue, caseValue->isLiteral() ? SUPPRESS_LINENOTE : EMIT_LINENOTE)) { return false; } if (!se.emitCaseJump()) { return false; } } } // Emit code for each case's statements. for (ParseNode* item : cases->contents()) { CaseClause* caseClause = &item->as(); if (caseClause->isDefault()) { if (!se.emitDefaultBody()) { return false; } } else { if (isTableSwitch) { ParseNode* caseValue = caseClause->caseExpression(); MOZ_ASSERT(caseValue->isKind(ParseNodeKind::NumberExpr)); NumericLiteral* literal = &caseValue->as(); #ifdef DEBUG // Use NumberEqualsInt32 here because switches compare using // strict equality, which will equate -0 and +0. In contrast // NumberIsInt32 would return false for -0. int32_t v; MOZ_ASSERT(mozilla::NumberEqualsInt32(literal->value(), &v)); #endif int32_t i = int32_t(literal->value()); if (!se.emitCaseBody(i, tableGen)) { return false; } } else { if (!se.emitCaseBody()) { return false; } } } if (!emitTree(caseClause->statementList())) { return false; } } if (!se.emitEnd()) { return false; } return true; } bool BytecodeEmitter::allocateResumeIndex(BytecodeOffset offset, uint32_t* resumeIndex) { static constexpr uint32_t MaxResumeIndex = BitMask(24); static_assert( MaxResumeIndex < uint32_t(AbstractGeneratorObject::RESUME_INDEX_RUNNING), "resumeIndex should not include magic AbstractGeneratorObject " "resumeIndex values"); static_assert( MaxResumeIndex <= INT32_MAX / sizeof(uintptr_t), "resumeIndex * sizeof(uintptr_t) must fit in an int32. JIT code relies " "on this when loading resume entries from BaselineScript"); *resumeIndex = bytecodeSection().resumeOffsetList().length(); if (*resumeIndex > MaxResumeIndex) { reportError(nullptr, JSMSG_TOO_MANY_RESUME_INDEXES); return false; } return bytecodeSection().resumeOffsetList().append(offset.value()); } bool BytecodeEmitter::allocateResumeIndexRange( mozilla::Span offsets, uint32_t* firstResumeIndex) { *firstResumeIndex = 0; for (size_t i = 0, len = offsets.size(); i < len; i++) { uint32_t resumeIndex; if (!allocateResumeIndex(offsets[i], &resumeIndex)) { return false; } if (i == 0) { *firstResumeIndex = resumeIndex; } } return true; } bool BytecodeEmitter::emitYieldOp(JSOp op) { // All yield operations pop or suspend the current frame. if (!emitInstrumentation(InstrumentationKind::Exit)) { return false; } if (op == JSOp::FinalYieldRval) { return emit1(JSOp::FinalYieldRval); } MOZ_ASSERT(op == JSOp::InitialYield || op == JSOp::Yield || op == JSOp::Await); BytecodeOffset off; if (!emitN(op, 3, &off)) { return false; } if (op == JSOp::InitialYield || op == JSOp::Yield) { bytecodeSection().addNumYields(); } uint32_t resumeIndex; if (!allocateResumeIndex(bytecodeSection().offset(), &resumeIndex)) { return false; } SET_RESUMEINDEX(bytecodeSection().code(off), resumeIndex); if (!emitInstrumentation(InstrumentationKind::Entry)) { return false; } BytecodeOffset unusedOffset; return emitJumpTargetOp(JSOp::AfterYield, &unusedOffset); } bool BytecodeEmitter::emitPushResumeKind(GeneratorResumeKind kind) { return emit2(JSOp::ResumeKind, uint8_t(kind)); } bool BytecodeEmitter::emitSetThis(BinaryNode* setThisNode) { // ParseNodeKind::SetThis is used to update |this| after a super() call // in a derived class constructor. MOZ_ASSERT(setThisNode->isKind(ParseNodeKind::SetThis)); MOZ_ASSERT(setThisNode->left()->isKind(ParseNodeKind::Name)); const ParserAtom* name = setThisNode->left()->as().name(); // 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; } NameOpEmitter noe(this, name, lexicalLoc, NameOpEmitter::Kind::Initialize); if (!noe.prepareForRhs()) { // [stack] return false; } // Emit the new |this| value. if (!emitTree(setThisNode->right())) { // [stack] NEWTHIS 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 (!emitGetName(name)) { // [stack] NEWTHIS THIS return false; } if (!emit1(JSOp::CheckThisReinit)) { // [stack] NEWTHIS THIS return false; } if (!emit1(JSOp::Pop)) { // [stack] NEWTHIS return false; } if (!noe.emitAssignment()) { // [stack] NEWTHIS return false; } if (!emitInitializeInstanceMembers()) { return false; } return true; } bool BytecodeEmitter::defineHoistedTopLevelFunctions(ParseNode* body) { MOZ_ASSERT(inPrologue()); MOZ_ASSERT(sc->isGlobalContext() || (sc->isEvalContext() && !sc->strict())); MOZ_ASSERT(body->is() || body->is()); if (body->is()) { body = body->as().scopeBody(); MOZ_ASSERT(body->is()); } if (!body->as().hasTopLevelFunctionDeclarations()) { return true; } return emitHoistedFunctionsInList(&body->as()); } // For Global and sloppy-Eval scripts, this performs most of the steps of the // spec's [GlobalDeclarationInstantiation] and [EvalDeclarationInstantiation] // operations. // // Note that while strict-Eval is handled in the same part of the spec, it never // fails for global-redeclaration checks so those scripts initialize directly in // their bytecode. bool BytecodeEmitter::emitDeclarationInstantiation(ParseNode* body) { if (sc->isModuleContext()) { // ES Modules have dedicated variable and lexial environments and therefore // do not have to perform redeclaration checks. We initialize their bindings // elsewhere in bytecode. return true; } if (sc->isEvalContext() && sc->strict()) { // Strict Eval has a dedicated variables (and lexical) environment and // therefore does not have to perform redeclaration checks. We initialize // their bindings elsewhere in the bytecode. return true; } // If we have no variables bindings, then we are done! if (sc->isGlobalContext()) { if (!sc->asGlobalContext()->bindings) { return true; } } else { MOZ_ASSERT(sc->isEvalContext()); if (!sc->asEvalContext()->bindings) { return true; } } #if DEBUG // There should be no emitted functions yet. for (const auto& thing : perScriptData().gcThingList().objects()) { MOZ_ASSERT(thing.isEmptyGlobalScope() || thing.isScope()); } #endif // Emit the hoisted functions to gc-things list. There is no bytecode // generated yet to bind them. if (!defineHoistedTopLevelFunctions(body)) { return false; } // Save the last GCThingIndex emitted. The hoisted functions are contained in // the gc-things list up until this point. This set of gc-things also contain // initial scopes (of which there must be at least one). MOZ_ASSERT(perScriptData().gcThingList().length() > 0); GCThingIndex lastFun = GCThingIndex(perScriptData().gcThingList().length() - 1); #if DEBUG for (const auto& thing : perScriptData().gcThingList().objects()) { MOZ_ASSERT(thing.isEmptyGlobalScope() || thing.isScope() || thing.isFunction()); } #endif // Check for declaration conflicts and initialize the bindings. if (!emitGCIndexOp(JSOp::GlobalOrEvalDeclInstantiation, lastFun)) { return false; } return true; } bool BytecodeEmitter::emitScript(ParseNode* body) { AutoFrontendTraceLog traceLog(cx, TraceLogger_BytecodeEmission, parser->errorReporter(), body); setScriptStartOffsetIfUnset(body->pn_pos.begin); MOZ_ASSERT(inPrologue()); TDZCheckCache tdzCache(this); EmitterScope emitterScope(this); Maybe topLevelAwait; if (sc->isGlobalContext()) { if (!emitterScope.enterGlobal(this, sc->asGlobalContext())) { return false; } } else if (sc->isEvalContext()) { if (!emitterScope.enterEval(this, sc->asEvalContext())) { return false; } } else { MOZ_ASSERT(sc->isModuleContext()); if (!emitterScope.enterModule(this, sc->asModuleContext())) { return false; } if (sc->asModuleContext()->isAsync()) { topLevelAwait.emplace(this); } } setFunctionBodyEndPos(body->pn_pos.end); bool isSloppyEval = sc->isEvalContext() && !sc->strict(); if (isSloppyEval && body->is() && !body->as().isEmptyScope()) { // Sloppy eval scripts may emit hoisted functions bindings with a // `JSOp::GlobalOrEvalDeclInstantiation` opcode below. If this eval needs a // top-level lexical environment, we must ensure that environment is created // before those functions are created and bound. // // This differs from the global-script case below because the global-lexical // environment exists outside the script itself. In the case of strict eval // scripts, the `emitterScope` above is already sufficient. EmitterScope lexicalEmitterScope(this); LexicalScopeNode* scope = &body->as(); if (!lexicalEmitterScope.enterLexical(this, ScopeKind::Lexical, scope->scopeBindings())) { return false; } if (!emitDeclarationInstantiation(scope->scopeBody())) { return false; } if (!switchToMain()) { return false; } ParseNode* scopeBody = scope->scopeBody(); if (!emitLexicalScopeBody(scopeBody, EMIT_LINENOTE)) { return false; } if (!updateSourceCoordNotes(scopeBody->pn_pos.end)) { return false; } if (!lexicalEmitterScope.leave(this)) { return false; } } else { if (!emitDeclarationInstantiation(body)) { return false; } if (topLevelAwait) { if (!topLevelAwait->prepareForModule()) { return false; } } if (!switchToMain()) { return false; } if (topLevelAwait) { if (!topLevelAwait->prepareForBody()) { return false; } } if (!emitTree(body)) { // [stack] return false; } if (!updateSourceCoordNotes(body->pn_pos.end)) { return false; } } if (topLevelAwait) { if (!topLevelAwait->emitEnd()) { return false; } } if (!markSimpleBreakpoint()) { return false; } if (!emitReturnRval()) { return false; } if (!emitterScope.leave(this)) { return false; } if (!NameFunctions(cx, compilationState.parserAtoms, body)) { return false; } // Create a Stencil and convert it into a JSScript. return intoScriptStencil(CompilationStencil::TopLevelIndex); } js::UniquePtr BytecodeEmitter::createImmutableScriptData( JSContext* cx) { uint32_t nslots; if (!getNslots(&nslots)) { return nullptr; } bool isFunction = sc->isFunctionBox(); uint16_t funLength = isFunction ? sc->asFunctionBox()->length() : 0; return ImmutableScriptData::new_( cx, mainOffset(), maxFixedSlots, nslots, bodyScopeIndex, bytecodeSection().numICEntries(), isFunction, funLength, bytecodeSection().code(), bytecodeSection().notes(), bytecodeSection().resumeOffsetList().span(), bytecodeSection().scopeNoteList().span(), bytecodeSection().tryNoteList().span()); } bool BytecodeEmitter::getNslots(uint32_t* nslots) { uint64_t nslots64 = maxFixedSlots + static_cast(bytecodeSection().maxStackDepth()); if (nslots64 > UINT32_MAX) { reportError(nullptr, JSMSG_NEED_DIET, js_script_str); return false; } *nslots = nslots64; return true; } bool BytecodeEmitter::emitFunctionScript(FunctionNode* funNode) { MOZ_ASSERT(inPrologue()); ListNode* paramsBody = &funNode->body()->as(); MOZ_ASSERT(paramsBody->isKind(ParseNodeKind::ParamsBody)); FunctionBox* funbox = sc->asFunctionBox(); AutoFrontendTraceLog traceLog(cx, TraceLogger_BytecodeEmission, parser->errorReporter(), funbox); setScriptStartOffsetIfUnset(paramsBody->pn_pos.begin); // [stack] FunctionScriptEmitter fse(this, funbox, Some(paramsBody->pn_pos.begin), Some(paramsBody->pn_pos.end)); if (!fse.prepareForParameters()) { // [stack] return false; } if (!emitFunctionFormalParameters(paramsBody)) { // [stack] return false; } if (!fse.prepareForBody()) { // [stack] return false; } if (!emitTree(paramsBody->last())) { // [stack] return false; } if (!fse.emitEndBody()) { // [stack] return false; } if (funbox->index() == CompilationStencil::TopLevelIndex) { if (!NameFunctions(cx, compilationState.parserAtoms, funNode)) { return false; } } return fse.intoStencil(); } bool BytecodeEmitter::emitDestructuringLHSRef(ParseNode* target, size_t* emitted) { *emitted = 0; if (target->isKind(ParseNodeKind::Spread)) { target = target->as().kid(); } else if (target->isKind(ParseNodeKind::AssignExpr)) { target = target->as().left(); } // No need to recur into ParseNodeKind::Array and // ParseNodeKind::Object subpatterns here, since // emitSetOrInitializeDestructuring does the recursion when // setting or initializing value. Getting reference doesn't recur. if (target->isKind(ParseNodeKind::Name) || target->isKind(ParseNodeKind::ArrayExpr) || target->isKind(ParseNodeKind::ObjectExpr)) { return true; } #ifdef DEBUG int depth = bytecodeSection().stackDepth(); #endif switch (target->getKind()) { case ParseNodeKind::DotExpr: { PropertyAccess* prop = &target->as(); bool isSuper = prop->isSuper(); PropOpEmitter poe(this, PropOpEmitter::Kind::SimpleAssignment, isSuper ? PropOpEmitter::ObjKind::Super : PropOpEmitter::ObjKind::Other); if (!poe.prepareForObj()) { return false; } if (isSuper) { UnaryNode* base = &prop->expression().as(); if (!emitGetThisForSuperBase(base)) { // [stack] THIS SUPERBASE return false; } // SUPERBASE is pushed onto THIS in poe.prepareForRhs below. *emitted = 2; } else { if (!emitTree(&prop->expression())) { // [stack] OBJ return false; } *emitted = 1; } if (!poe.prepareForRhs()) { // [stack] # if Super // [stack] THIS SUPERBASE // [stack] # otherwise // [stack] OBJ return false; } break; } case ParseNodeKind::ElemExpr: { PropertyByValue* elem = &target->as(); bool isSuper = elem->isSuper(); bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); ElemOpEmitter eoe( this, ElemOpEmitter::Kind::SimpleAssignment, isSuper ? ElemOpEmitter::ObjKind::Super : ElemOpEmitter::ObjKind::Other, isPrivate ? NameVisibility::Private : NameVisibility::Public); if (!emitElemObjAndKey(elem, isSuper, eoe)) { // [stack] # if Super // [stack] THIS KEY // [stack] # otherwise // [stack] OBJ KEY return false; } if (isSuper) { // SUPERBASE is pushed onto KEY in eoe.prepareForRhs below. *emitted = 3; } else { *emitted = 2; } if (!eoe.prepareForRhs()) { // [stack] # if Super // [stack] THIS KEY SUPERBASE // [stack] # otherwise // [stack] OBJ KEY return false; } break; } case ParseNodeKind::CallExpr: MOZ_ASSERT_UNREACHABLE( "Parser::reportIfNotValidSimpleAssignmentTarget " "rejects function calls as assignment " "targets in destructuring assignments"); break; default: MOZ_CRASH("emitDestructuringLHSRef: bad lhs kind"); } MOZ_ASSERT(bytecodeSection().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(ParseNodeKind::Spread)) { target = target->as().kid(); } else if (target->isKind(ParseNodeKind::AssignExpr)) { target = target->as().left(); } if (target->isKind(ParseNodeKind::ArrayExpr) || target->isKind(ParseNodeKind::ObjectExpr)) { if (!emitDestructuringOps(&target->as(), 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 ParseNodeKind::Name: { const ParserAtom* name = target->as().name(); NameLocation loc = lookupName(name); NameOpEmitter::Kind kind; switch (flav) { case DestructuringFlavor::Declaration: kind = NameOpEmitter::Kind::Initialize; break; case DestructuringFlavor::Assignment: kind = NameOpEmitter::Kind::SimpleAssignment; break; } NameOpEmitter noe(this, name, loc, kind); if (!noe.prepareForRhs()) { // [stack] V ENV? return false; } if (noe.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. if (!emit1(JSOp::Swap)) { // [stack] ENV V return false; } } else { // In cases of emitting a frame slot or environment slot, // nothing needs be done. } if (!noe.emitAssignment()) { // [stack] V return false; } break; } case ParseNodeKind::DotExpr: { // The reference is already pushed by emitDestructuringLHSRef. // [stack] # if Super // [stack] THIS SUPERBASE VAL // [stack] # otherwise // [stack] OBJ VAL PropertyAccess* prop = &target->as(); bool isSuper = prop->isSuper(); PropOpEmitter poe(this, PropOpEmitter::Kind::SimpleAssignment, isSuper ? PropOpEmitter::ObjKind::Super : PropOpEmitter::ObjKind::Other); if (!poe.skipObjAndRhs()) { return false; } // [stack] # VAL if (!poe.emitAssignment(prop->key().atom())) { return false; } break; } case ParseNodeKind::ElemExpr: { // The reference is already pushed by emitDestructuringLHSRef. // [stack] # if Super // [stack] THIS KEY SUPERBASE VAL // [stack] # otherwise // [stack] OBJ KEY VAL PropertyByValue* elem = &target->as(); bool isSuper = elem->isSuper(); bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); ElemOpEmitter eoe( this, ElemOpEmitter::Kind::SimpleAssignment, isSuper ? ElemOpEmitter::ObjKind::Super : ElemOpEmitter::ObjKind::Other, isPrivate ? NameVisibility::Private : NameVisibility::Public); if (!eoe.skipObjAndKeyAndRhs()) { return false; } if (!eoe.emitAssignment()) { // [stack] VAL return false; } break; } case ParseNodeKind::CallExpr: 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)) { // [stack] # empty return false; } } return true; } bool BytecodeEmitter::emitIteratorNext( const Maybe& callSourceCoordOffset, IteratorKind iterKind /* = IteratorKind::Sync */, bool allowSelfHosted /* = false */) { // TODO: migrate Module code to cpp, to avoid having the extra check here. MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting || (sc->isModuleContext() && sc->asModuleContext()->isAsync()), ".next() iteration is prohibited in non-module self-hosted code " "because it" "can run user-modifiable iteration code"); // [stack] ... NEXT ITER MOZ_ASSERT(bytecodeSection().stackDepth() >= 2); if (!emitCall(JSOp::Call, 0, callSourceCoordOffset)) { // [stack] ... RESULT return false; } if (iterKind == IteratorKind::Async) { if (!emitAwaitInInnermostScope()) { // [stack] ... RESULT return false; } } if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) { // [stack] ... RESULT return false; } return true; } bool BytecodeEmitter::emitPushNotUndefinedOrNull() { // [stack] V MOZ_ASSERT(bytecodeSection().stackDepth() > 0); if (!emit1(JSOp::Dup)) { // [stack] V V return false; } if (!emit1(JSOp::Undefined)) { // [stack] V V UNDEFINED return false; } if (!emit1(JSOp::StrictNe)) { // [stack] V NEQ return false; } JumpList undefinedOrNullJump; if (!emitJump(JSOp::And, &undefinedOrNullJump)) { // [stack] V NEQ return false; } if (!emit1(JSOp::Pop)) { // [stack] V return false; } if (!emit1(JSOp::Dup)) { // [stack] V V return false; } if (!emit1(JSOp::Null)) { // [stack] V V NULL return false; } if (!emit1(JSOp::StrictNe)) { // [stack] V NEQ return false; } if (!emitJumpTargetAndPatch(undefinedOrNullJump)) { // [stack] V NOT-UNDEF-OR-NULL return false; } return true; } bool BytecodeEmitter::emitIteratorCloseInScope( EmitterScope& currentScope, IteratorKind iterKind /* = IteratorKind::Sync */, CompletionKind completionKind /* = CompletionKind::Normal */, bool allowSelfHosted /* = false */) { MOZ_ASSERT( allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting, ".close() on iterators is prohibited in self-hosted code because it " "can run user-modifiable iteration code"); // Generate inline logic corresponding to IteratorClose (ES2021 7.4.6) and // AsyncIteratorClose (ES2021 7.4.7). Steps numbers apply to both operations. // // Callers need to ensure that the iterator object is at the top of the // stack. // For non-Throw completions, we emit the equivalent of: // // var returnMethod = GetMethod(iterator, "return"); // if (returnMethod !== undefined) { // var innerResult = [Await] Call(returnMethod, iterator); // CheckIsObj(innerResult); // } // // Whereas for Throw completions, we emit: // // try { // var returnMethod = GetMethod(iterator, "return"); // if (returnMethod !== undefined) { // [Await] Call(returnMethod, iterator); // } // } catch {} Maybe tryCatch; if (completionKind == CompletionKind::Throw) { tryCatch.emplace(this, TryEmitter::Kind::TryCatch, TryEmitter::ControlKind::NonSyntactic); if (!tryCatch->emitTry()) { // [stack] ... ITER return false; } } if (!emit1(JSOp::Dup)) { // [stack] ... ITER ITER return false; } // Steps 1-2 are assertions, step 3 is implicit. // Step 4. // // Get the "return" method. if (!emitAtomOp(JSOp::GetProp, cx->parserNames().return_)) { // [stack] ... ITER RET return false; } // Step 5. // // Do nothing if "return" is undefined or null. InternalIfEmitter ifReturnMethodIsDefined(this); if (!emitPushNotUndefinedOrNull()) { // [stack] ... ITER RET NOT-UNDEF-OR-NULL return false; } if (!ifReturnMethodIsDefined.emitThenElse()) { // [stack] ... ITER RET return false; } // Steps 5.c, 7. // // Call the "return" method. if (!emit1(JSOp::Swap)) { // [stack] ... RET ITER return false; } if (!emitCall(JSOp::Call, 0)) { // [stack] ... RESULT return false; } // 7.4.7 AsyncIteratorClose, step 5.d. if (iterKind == IteratorKind::Async) { if (completionKind != CompletionKind::Throw) { // Await clobbers rval, so save the current rval. if (!emit1(JSOp::GetRval)) { // [stack] ... RESULT RVAL return false; } if (!emit1(JSOp::Swap)) { // [stack] ... RVAL RESULT return false; } } if (!emitAwaitInScope(currentScope)) { // [stack] ... RVAL? RESULT return false; } if (completionKind != CompletionKind::Throw) { if (!emit1(JSOp::Swap)) { // [stack] ... RESULT RVAL return false; } if (!emit1(JSOp::SetRval)) { // [stack] ... RESULT return false; } } } // Step 6 (Handled in caller). // Step 8. if (completionKind != CompletionKind::Throw) { // Check that the "return" result is an object. if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) { // [stack] ... RESULT return false; } } if (!ifReturnMethodIsDefined.emitElse()) { // [stack] ... ITER RET return false; } if (!emit1(JSOp::Pop)) { // [stack] ... ITER return false; } if (!ifReturnMethodIsDefined.emitEnd()) { return false; } if (completionKind == CompletionKind::Throw) { if (!tryCatch->emitCatch()) { // [stack] ... ITER EXC return false; } // Just ignore the exception thrown by call and await. if (!emit1(JSOp::Pop)) { // [stack] ... ITER return false; } if (!tryCatch->emitEnd()) { // [stack] ... ITER return false; } } // Step 9 (Handled in caller). return emit1(JSOp::Pop); // [stack] ... } template bool BytecodeEmitter::wrapWithDestructuringTryNote(int32_t iterDepth, InnerEmitter emitter) { MOZ_ASSERT(bytecodeSection().stackDepth() >= iterDepth); // Pad a nop at the beginning of the bytecode covered by the trynote so // that when unwinding environments, we may unwind to the scope // corresponding to the pc *before* the start, in case the first bytecode // emitted by |emitter| is the start of an inner scope. See comment above // UnwindEnvironmentToTryPc. if (!emit1(JSOp::TryDestructuring)) { return false; } BytecodeOffset start = bytecodeSection().offset(); if (!emitter(this)) { return false; } BytecodeOffset end = bytecodeSection().offset(); if (start != end) { return addTryNote(TryNoteKind::Destructuring, iterDepth, start, end); } return true; } bool BytecodeEmitter::emitDefault(ParseNode* defaultExpr, ParseNode* pattern) { // [stack] VALUE DefaultEmitter de(this); if (!de.prepareForDefault()) { // [stack] return false; } if (!emitInitializer(defaultExpr, pattern)) { // [stack] DEFAULTVALUE return false; } if (!de.emitEnd()) { // [stack] VALUE/DEFAULTVALUE return false; } return true; } bool BytecodeEmitter::emitAnonymousFunctionWithName(ParseNode* node, const ParserAtom* name) { MOZ_ASSERT(node->isDirectRHSAnonFunction()); if (node->is()) { // Function doesn't have 'name' property at this point. // Set function's name at compile time. if (!setFunName(node->as().funbox(), name)) { return false; } return emitTree(node); } MOZ_ASSERT(node->is()); return emitClass(&node->as(), ClassNameKind::InferredName, name); } bool BytecodeEmitter::emitAnonymousFunctionWithComputedName( ParseNode* node, FunctionPrefixKind prefixKind) { MOZ_ASSERT(node->isDirectRHSAnonFunction()); if (node->is()) { if (!emitTree(node)) { // [stack] NAME FUN return false; } if (!emitDupAt(1)) { // [stack] NAME FUN NAME return false; } if (!emit2(JSOp::SetFunName, uint8_t(prefixKind))) { // [stack] NAME FUN return false; } return true; } MOZ_ASSERT(node->is()); MOZ_ASSERT(prefixKind == FunctionPrefixKind::None); return emitClass(&node->as(), ClassNameKind::ComputedName); } bool BytecodeEmitter::setFunName(FunctionBox* funbox, const ParserAtom* name) { // The inferred name may already be set if this function is an interpreted // lazy function and we OOM'ed after we set the inferred name the first // time. if (funbox->hasInferredName()) { MOZ_ASSERT(!funbox->emitBytecode); MOZ_ASSERT(funbox->displayAtom() == name); return true; } funbox->setInferredName(name); return true; } bool BytecodeEmitter::emitInitializer(ParseNode* initializer, ParseNode* pattern) { if (initializer->isDirectRHSAnonFunction()) { MOZ_ASSERT(!pattern->isInParens()); const ParserAtom* name = pattern->as().name(); if (!emitAnonymousFunctionWithName(initializer, name)) { return false; } } else { if (!emitTree(initializer)) { return false; } } return true; } bool BytecodeEmitter::emitDestructuringOpsArray(ListNode* pattern, DestructuringFlavor flav) { MOZ_ASSERT(pattern->isKind(ParseNodeKind::ArrayExpr)); MOZ_ASSERT(bytecodeSection().stackDepth() != 0); // Here's pseudo code for |let [a, b, , c=y, ...d] = x;| // // Lines that are annotated "covered by trynote" mean that upon throwing // an exception, IteratorClose is called on iter only if done is false. // // let x, y; // let a, b, c, d; // let iter, next, lref, result, done, value; // stack values // // iter = x[Symbol.iterator](); // next = iter.next; // // // ==== emitted by loop for a ==== // lref = GetReference(a); // covered by trynote // // result = Call(next, iter); // done = result.done; // // if (done) // value = undefined; // else // value = result.value; // // SetOrInitialize(lref, value); // covered by trynote // // // ==== emitted by loop for b ==== // lref = GetReference(b); // covered by trynote // // if (done) { // value = undefined; // } else { // result = Call(next, iter); // done = result.done; // if (done) // value = undefined; // else // value = result.value; // } // // SetOrInitialize(lref, value); // covered by trynote // // // ==== emitted by loop for elision ==== // if (done) { // value = undefined; // } else { // result = Call(next, iter); // done = result.done; // if (done) // value = undefined; // else // value = result.value; // } // // // ==== emitted by loop for c ==== // lref = GetReference(c); // covered by trynote // // if (done) { // value = undefined; // } else { // result = Call(next, iter); // done = result.done; // if (done) // value = undefined; // else // value = result.value; // } // // if (value === undefined) // value = y; // covered by trynote // // SetOrInitialize(lref, value); // covered by trynote // // // ==== emitted by loop for d ==== // lref = GetReference(d); // covered by trynote // // if (done) // value = []; // else // value = [...iter]; // // SetOrInitialize(lref, value); // covered by trynote // // // === emitted after loop === // if (!done) // IteratorClose(iter); // Use an iterator to destructure the RHS, instead of index lookup. We // must leave the *original* value on the stack. if (!emit1(JSOp::Dup)) { // [stack] ... OBJ OBJ return false; } if (!emitIterator()) { // [stack] ... OBJ NEXT ITER return false; } // For an empty pattern [], call IteratorClose unconditionally. Nothing // else needs to be done. if (!pattern->head()) { if (!emit1(JSOp::Swap)) { // [stack] ... OBJ ITER NEXT return false; } if (!emit1(JSOp::Pop)) { // [stack] ... OBJ ITER return false; } return emitIteratorCloseInInnermostScope(); // [stack] ... OBJ } // Push an initial FALSE value for DONE. if (!emit1(JSOp::False)) { // [stack] ... OBJ NEXT ITER FALSE return false; } // TryNoteKind::Destructuring expects the iterator and the done value // to be the second to top and the top of the stack, respectively. // IteratorClose is called upon exception only if done is false. int32_t tryNoteDepth = bytecodeSection().stackDepth(); for (ParseNode* member : pattern->contents()) { bool isFirst = member == pattern->head(); DebugOnly hasNext = !!member->pn_next; size_t emitted = 0; // Spec requires LHS reference to be evaluated first. ParseNode* lhsPattern = member; if (lhsPattern->isKind(ParseNodeKind::AssignExpr)) { lhsPattern = lhsPattern->as().left(); } bool isElision = lhsPattern->isKind(ParseNodeKind::Elision); if (!isElision) { auto emitLHSRef = [lhsPattern, &emitted](BytecodeEmitter* bce) { return bce->emitDestructuringLHSRef(lhsPattern, &emitted); // [stack] ... OBJ NEXT ITER DONE LREF* }; if (!wrapWithDestructuringTryNote(tryNoteDepth, emitLHSRef)) { return false; } } // Pick the DONE value to the top of the stack. if (emitted) { if (!emitPickN(emitted)) { // [stack] ... OBJ NEXT ITER LREF* DONE return false; } } if (isFirst) { // If this element is the first, DONE is always FALSE, so pop it. // // Non-first elements should emit if-else depending on the // member pattern, below. if (!emit1(JSOp::Pop)) { // [stack] ... OBJ NEXT ITER LREF* return false; } } if (member->isKind(ParseNodeKind::Spread)) { InternalIfEmitter ifThenElse(this); if (!isFirst) { // If spread is not the first element of the pattern, // iterator can already be completed. // [stack] ... OBJ NEXT ITER LREF* DONE if (!ifThenElse.emitThenElse()) { // [stack] ... OBJ NEXT ITER LREF* return false; } if (!emitUint32Operand(JSOp::NewArray, 0)) { // [stack] ... OBJ NEXT ITER LREF* ARRAY return false; } if (!ifThenElse.emitElse()) { // [stack] ... OBJ NEXT ITER LREF* return false; } } // If iterator is not completed, create a new array with the rest // of the iterator. if (!emitDupAt(emitted + 1, 2)) { // [stack] ... OBJ NEXT ITER LREF* NEXT return false; } if (!emitUint32Operand(JSOp::NewArray, 0)) { // [stack] ... OBJ NEXT ITER LREF* NEXT ITER ARRAY return false; } if (!emitNumberOp(0)) { // [stack] ... OBJ NEXT ITER LREF* NEXT ITER ARRAY INDEX return false; } if (!emitSpread()) { // [stack] ... OBJ NEXT ITER LREF* ARRAY INDEX return false; } if (!emit1(JSOp::Pop)) { // [stack] ... OBJ NEXT ITER LREF* ARRAY return false; } if (!isFirst) { if (!ifThenElse.emitEnd()) { return false; } MOZ_ASSERT(ifThenElse.pushed() == 1); } // At this point the iterator is done. Unpick a TRUE value for DONE above // ITER. if (!emit1(JSOp::True)) { // [stack] ... OBJ NEXT ITER LREF* ARRAY TRUE return false; } if (!emit2(JSOp::Unpick, emitted + 1)) { // [stack] ... OBJ NEXT ITER TRUE LREF* ARRAY return false; } auto emitAssignment = [member, flav](BytecodeEmitter* bce) { return bce->emitSetOrInitializeDestructuring(member, flav); // [stack] ... OBJ NEXT ITER TRUE }; if (!wrapWithDestructuringTryNote(tryNoteDepth, emitAssignment)) { return false; } MOZ_ASSERT(!hasNext); break; } ParseNode* pndefault = nullptr; if (member->isKind(ParseNodeKind::AssignExpr)) { pndefault = member->as().right(); } MOZ_ASSERT(!member->isKind(ParseNodeKind::Spread)); InternalIfEmitter ifAlreadyDone(this); if (!isFirst) { // [stack] ... OBJ NEXT ITER LREF* DONE if (!ifAlreadyDone.emitThenElse()) { // [stack] ... OBJ NEXT ITER LREF* return false; } if (!emit1(JSOp::Undefined)) { // [stack] ... OBJ NEXT ITER LREF* UNDEF return false; } if (!emit1(JSOp::NopDestructuring)) { // [stack] ... OBJ NEXT ITER LREF* UNDEF return false; } // The iterator is done. Unpick a TRUE value for DONE above ITER. if (!emit1(JSOp::True)) { // [stack] ... OBJ NEXT ITER LREF* UNDEF TRUE return false; } if (!emit2(JSOp::Unpick, emitted + 1)) { // [stack] ... OBJ NEXT ITER TRUE LREF* UNDEF return false; } if (!ifAlreadyDone.emitElse()) { // [stack] ... OBJ NEXT ITER LREF* return false; } } if (!emitDupAt(emitted + 1, 2)) { // [stack] ... OBJ NEXT ITER LREF* NEXT return false; } if (!emitIteratorNext(Some(pattern->pn_pos.begin))) { // [stack] ... OBJ NEXT ITER LREF* RESULT return false; } if (!emit1(JSOp::Dup)) { // [stack] ... OBJ NEXT ITER LREF* RESULT RESULT return false; } if (!emitAtomOp(JSOp::GetProp, cx->parserNames().done)) { // [stack] ... OBJ NEXT ITER LREF* RESULT DONE return false; } if (!emit1(JSOp::Dup)) { // [stack] ... OBJ NEXT ITER LREF* RESULT DONE DONE return false; } if (!emit2(JSOp::Unpick, emitted + 2)) { // [stack] ... OBJ NEXT ITER DONE LREF* RESULT DONE return false; } InternalIfEmitter ifDone(this); if (!ifDone.emitThenElse()) { // [stack] ... OBJ NEXT ITER DONE LREF* RESULT return false; } if (!emit1(JSOp::Pop)) { // [stack] ... OBJ NEXT ITER DONE LREF* return false; } if (!emit1(JSOp::Undefined)) { // [stack] ... OBJ NEXT ITER DONE LREF* UNDEF return false; } if (!emit1(JSOp::NopDestructuring)) { // [stack] ... OBJ NEXT ITER DONE LREF* UNDEF return false; } if (!ifDone.emitElse()) { // [stack] ... OBJ NEXT ITER DONE LREF* RESULT return false; } if (!emitAtomOp(JSOp::GetProp, cx->parserNames().value)) { // [stack] ... OBJ NEXT ITER DONE LREF* VALUE return false; } if (!ifDone.emitEnd()) { return false; } MOZ_ASSERT(ifDone.pushed() == 0); if (!isFirst) { if (!ifAlreadyDone.emitEnd()) { return false; } MOZ_ASSERT(ifAlreadyDone.pushed() == 2); } if (pndefault) { auto emitDefault = [pndefault, lhsPattern](BytecodeEmitter* bce) { return bce->emitDefault(pndefault, lhsPattern); // [stack] ... OBJ NEXT ITER DONE LREF* VALUE }; if (!wrapWithDestructuringTryNote(tryNoteDepth, emitDefault)) { return false; } } if (!isElision) { auto emitAssignment = [lhsPattern, flav](BytecodeEmitter* bce) { return bce->emitSetOrInitializeDestructuring(lhsPattern, flav); // [stack] ... OBJ NEXT ITER DONE }; if (!wrapWithDestructuringTryNote(tryNoteDepth, emitAssignment)) { return false; } } else { if (!emit1(JSOp::Pop)) { // [stack] ... OBJ NEXT ITER DONE return false; } } } // The last DONE value is on top of the stack. If not DONE, call // IteratorClose. // [stack] ... OBJ NEXT ITER DONE InternalIfEmitter ifDone(this); if (!ifDone.emitThenElse()) { // [stack] ... OBJ NEXT ITER return false; } if (!emitPopN(2)) { // [stack] ... OBJ return false; } if (!ifDone.emitElse()) { // [stack] ... OBJ NEXT ITER return false; } if (!emit1(JSOp::Swap)) { // [stack] ... OBJ ITER NEXT return false; } if (!emit1(JSOp::Pop)) { // [stack] ... OBJ ITER return false; } if (!emitIteratorCloseInInnermostScope()) { // [stack] ... OBJ return false; } if (!ifDone.emitEnd()) { return false; } return true; } bool BytecodeEmitter::emitComputedPropertyName(UnaryNode* computedPropName) { MOZ_ASSERT(computedPropName->isKind(ParseNodeKind::ComputedName)); return emitTree(computedPropName->kid()) && emit1(JSOp::ToPropertyKey); } bool BytecodeEmitter::emitDestructuringOpsObject(ListNode* pattern, DestructuringFlavor flav) { MOZ_ASSERT(pattern->isKind(ParseNodeKind::ObjectExpr)); // [stack] ... RHS MOZ_ASSERT(bytecodeSection().stackDepth() > 0); if (!emit1(JSOp::CheckObjCoercible)) { // [stack] ... RHS return false; } bool needsRestPropertyExcludedSet = pattern->count() > 1 && pattern->last()->isKind(ParseNodeKind::Spread); if (needsRestPropertyExcludedSet) { if (!emitDestructuringObjRestExclusionSet(pattern)) { // [stack] ... RHS SET return false; } if (!emit1(JSOp::Swap)) { // [stack] ... SET RHS return false; } } for (ParseNode* member : pattern->contents()) { ParseNode* subpattern; if (member->isKind(ParseNodeKind::MutateProto) || member->isKind(ParseNodeKind::Spread)) { subpattern = member->as().kid(); } else { MOZ_ASSERT(member->isKind(ParseNodeKind::PropertyDefinition) || member->isKind(ParseNodeKind::Shorthand)); subpattern = member->as().right(); } ParseNode* lhs = subpattern; MOZ_ASSERT_IF(member->isKind(ParseNodeKind::Spread), !lhs->isKind(ParseNodeKind::AssignExpr)); if (lhs->isKind(ParseNodeKind::AssignExpr)) { lhs = lhs->as().left(); } size_t emitted; if (!emitDestructuringLHSRef(lhs, &emitted)) { // [stack] ... SET? RHS LREF* return false; } // Duplicate the value being destructured to use as a reference base. if (!emitDupAt(emitted)) { // [stack] ... SET? RHS LREF* RHS return false; } if (member->isKind(ParseNodeKind::Spread)) { if (!updateSourceCoordNotes(member->pn_pos.begin)) { return false; } if (!emit1(JSOp::NewInit)) { // [stack] ... SET? RHS LREF* RHS TARGET return false; } if (!emit1(JSOp::Dup)) { // [stack] ... SET? RHS LREF* RHS TARGET TARGET return false; } if (!emit2(JSOp::Pick, 2)) { // [stack] ... SET? RHS LREF* TARGET TARGET RHS return false; } if (needsRestPropertyExcludedSet) { if (!emit2(JSOp::Pick, emitted + 4)) { // [stack] ... RHS LREF* TARGET TARGET RHS SET return false; } } CopyOption option = needsRestPropertyExcludedSet ? CopyOption::Filtered : CopyOption::Unfiltered; if (!emitCopyDataProperties(option)) { // [stack] ... RHS LREF* TARGET return false; } // Destructure TARGET per this member's lhs. if (!emitSetOrInitializeDestructuring(lhs, flav)) { // [stack] ... RHS return false; } MOZ_ASSERT(member == pattern->last(), "Rest property is always last"); break; } // 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(ParseNodeKind::MutateProto)) { if (!emitAtomOp(JSOp::GetProp, cx->parserNames().proto)) { // [stack] ... SET? RHS LREF* PROP return false; } needsGetElem = false; } else { MOZ_ASSERT(member->isKind(ParseNodeKind::PropertyDefinition) || member->isKind(ParseNodeKind::Shorthand)); ParseNode* key = member->as().left(); if (key->isKind(ParseNodeKind::NumberExpr)) { if (!emitNumberOp(key->as().value())) { // [stack]... SET? RHS LREF* RHS KEY return false; } } else if (key->isKind(ParseNodeKind::BigIntExpr)) { if (!emitBigIntOp(&key->as())) { // [stack]... SET? RHS LREF* RHS KEY return false; } } else if (key->isKind(ParseNodeKind::ObjectPropertyName) || key->isKind(ParseNodeKind::StringExpr)) { if (!emitAtomOp(JSOp::GetProp, key->as().atom(), ShouldInstrument::Yes)) { // [stack] ... SET? RHS LREF* PROP return false; } needsGetElem = false; } else { if (!emitComputedPropertyName(&key->as())) { // [stack] ... SET? RHS LREF* RHS KEY return false; } // Add the computed property key to the exclusion set. if (needsRestPropertyExcludedSet) { if (!emitDupAt(emitted + 3)) { // [stack] ... SET RHS LREF* RHS KEY SET return false; } if (!emitDupAt(1)) { // [stack] ... SET RHS LREF* RHS KEY SET KEY return false; } if (!emit1(JSOp::Undefined)) { // [stack] ... SET RHS LREF* RHS KEY SET KEY UNDEFINED return false; } if (!emit1(JSOp::InitElem)) { // [stack] ... SET RHS LREF* RHS KEY SET return false; } if (!emit1(JSOp::Pop)) { // [stack] ... SET RHS LREF* RHS KEY return false; } } } } // Get the property value if not done already. if (needsGetElem && !emitElemOpBase(JSOp::GetElem)) { // [stack] ... SET? RHS LREF* PROP return false; } if (subpattern->isKind(ParseNodeKind::AssignExpr)) { if (!emitDefault(subpattern->as().right(), lhs)) { // [stack] ... SET? RHS LREF* VALUE return false; } } // Destructure PROP per this member's lhs. if (!emitSetOrInitializeDestructuring(subpattern, flav)) { // [stack] ... SET? RHS return false; } } return true; } static bool IsDestructuringRestExclusionSetObjLiteralCompatible( ListNode* pattern) { int32_t propCount = 0; for (ParseNode* member : pattern->contents()) { if (member->isKind(ParseNodeKind::Spread)) { MOZ_ASSERT(!member->pn_next, "unexpected trailing element after spread"); break; } propCount++; if (member->isKind(ParseNodeKind::MutateProto)) { continue; } ParseNode* key = member->as().left(); if (key->isKind(ParseNodeKind::ObjectPropertyName) || key->isKind(ParseNodeKind::StringExpr)) { continue; } // Number and BigInt keys aren't yet supported. Computed property names need // to be added dynamically. MOZ_ASSERT(key->isKind(ParseNodeKind::NumberExpr) || key->isKind(ParseNodeKind::BigIntExpr) || key->isKind(ParseNodeKind::ComputedName)); return false; } if (propCount >= PropertyTree::MAX_HEIGHT) { // JSOp::NewObject cannot accept dictionary-mode objects. return false; } return true; } bool BytecodeEmitter::emitDestructuringObjRestExclusionSet(ListNode* pattern) { MOZ_ASSERT(pattern->isKind(ParseNodeKind::ObjectExpr)); MOZ_ASSERT(pattern->last()->isKind(ParseNodeKind::Spread)); // See if we can use ObjLiteral to construct the exclusion set object. if (IsDestructuringRestExclusionSetObjLiteralCompatible(pattern)) { if (!emitDestructuringRestExclusionSetObjLiteral(pattern)) { // [stack] OBJ return false; } } else { // Take the slow but sure way and start off with a blank object. if (!emit1(JSOp::NewInit)) { // [stack] OBJ return false; } } const ParserAtom* pnatom = nullptr; for (ParseNode* member : pattern->contents()) { if (member->isKind(ParseNodeKind::Spread)) { MOZ_ASSERT(!member->pn_next, "unexpected trailing element after spread"); break; } bool isIndex = false; if (member->isKind(ParseNodeKind::MutateProto)) { pnatom = cx->parserNames().proto; } else { ParseNode* key = member->as().left(); if (key->isKind(ParseNodeKind::NumberExpr)) { if (!emitNumberOp(key->as().value())) { return false; } isIndex = true; } else if (key->isKind(ParseNodeKind::BigIntExpr)) { if (!emitBigIntOp(&key->as())) { return false; } isIndex = true; } else if (key->isKind(ParseNodeKind::ObjectPropertyName) || key->isKind(ParseNodeKind::StringExpr)) { pnatom = key->as().atom(); } else { // Otherwise this is a computed property name which needs to // be added dynamically. MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName)); continue; } } // Initialize elements with |undefined|. if (!emit1(JSOp::Undefined)) { return false; } if (isIndex) { if (!emit1(JSOp::InitElem)) { return false; } } else { if (!emitAtomOp(JSOp::InitProp, pnatom)) { return false; } } } return true; } bool BytecodeEmitter::emitDestructuringOps(ListNode* pattern, DestructuringFlavor flav) { if (pattern->isKind(ParseNodeKind::ArrayExpr)) { return emitDestructuringOpsArray(pattern, flav); } return emitDestructuringOpsObject(pattern, flav); } bool BytecodeEmitter::emitTemplateString(ListNode* templateString) { bool pushedString = false; for (ParseNode* item : templateString->contents()) { bool isString = (item->getKind() == ParseNodeKind::StringExpr || item->getKind() == ParseNodeKind::TemplateStringExpr); // 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 && item->as().atom()->length() == 0) { continue; } if (!isString) { // We update source notes before emitting the expression if (!updateSourceCoordNotes(item->pn_pos.begin)) { return false; } } if (!emitTree(item)) { 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(JSOp::String, cx->parserNames().empty)) { return false; } } return true; } bool BytecodeEmitter::emitDeclarationList(ListNode* declList) { for (ParseNode* decl : declList->contents()) { ParseNode* pattern; ParseNode* initializer; if (decl->isKind(ParseNodeKind::Name)) { pattern = decl; initializer = nullptr; } else { AssignmentNode* assignNode = &decl->as(); pattern = assignNode->left(); initializer = assignNode->right(); } if (pattern->isKind(ParseNodeKind::Name)) { // initializer can be null here. if (!emitSingleDeclaration(declList, &pattern->as(), initializer)) { return false; } } else { MOZ_ASSERT(pattern->isKind(ParseNodeKind::ArrayExpr) || pattern->isKind(ParseNodeKind::ObjectExpr)); MOZ_ASSERT(initializer != nullptr); if (!updateSourceCoordNotes(initializer->pn_pos.begin)) { return false; } if (!markStepBreakpoint()) { return false; } if (!emitTree(initializer)) { return false; } if (!emitDestructuringOps(&pattern->as(), DestructuringFlavor::Declaration)) { return false; } if (!emit1(JSOp::Pop)) { return false; } } } return true; } bool BytecodeEmitter::emitSingleDeclaration(ListNode* declList, NameNode* decl, ParseNode* initializer) { MOZ_ASSERT(decl->isKind(ParseNodeKind::Name)); // Nothing to do for initializer-less 'var' declarations, as there's no TDZ. if (!initializer && declList->isKind(ParseNodeKind::VarStmt)) { return true; } const ParserAtom* nameAtom = decl->name(); NameOpEmitter noe(this, nameAtom, NameOpEmitter::Kind::Initialize); if (!noe.prepareForRhs()) { // [stack] ENV? return false; } if (!initializer) { // Lexical declarations are initialized to undefined without an // initializer. MOZ_ASSERT(declList->isKind(ParseNodeKind::LetDecl), "var declarations without initializers handled above, " "and const declarations must have initializers"); if (!emit1(JSOp::Undefined)) { // [stack] ENV? UNDEF return false; } } else { MOZ_ASSERT(initializer); if (!updateSourceCoordNotes(initializer->pn_pos.begin)) { return false; } if (!markStepBreakpoint()) { return false; } if (!emitInitializer(initializer, decl)) { // [stack] ENV? V return false; } } if (!noe.emitAssignment()) { // [stack] V return false; } if (!emit1(JSOp::Pop)) { // [stack] return false; } return true; } bool BytecodeEmitter::emitAssignmentRhs(ParseNode* rhs, const ParserAtom* anonFunctionName) { if (rhs->isDirectRHSAnonFunction()) { if (anonFunctionName) { return emitAnonymousFunctionWithName(rhs, anonFunctionName); } return emitAnonymousFunctionWithComputedName(rhs, FunctionPrefixKind::None); } return emitTree(rhs); } // The RHS value to assign is already on the stack, i.e., the next enumeration // value in a for-in or for-of loop. Offset is the location in the stack of the // already-emitted rhs. If we emitted a BIND[G]NAME, then the scope is on the // top of the stack and we need to dig one deeper to get the right RHS value. bool BytecodeEmitter::emitAssignmentRhs(uint8_t offset) { if (offset != 1) { return emitPickN(offset - 1); } return true; } static inline JSOp CompoundAssignmentParseNodeKindToJSOp(ParseNodeKind pnk) { switch (pnk) { case ParseNodeKind::InitExpr: return JSOp::Nop; case ParseNodeKind::AssignExpr: return JSOp::Nop; case ParseNodeKind::AddAssignExpr: return JSOp::Add; case ParseNodeKind::SubAssignExpr: return JSOp::Sub; case ParseNodeKind::BitOrAssignExpr: return JSOp::BitOr; case ParseNodeKind::BitXorAssignExpr: return JSOp::BitXor; case ParseNodeKind::BitAndAssignExpr: return JSOp::BitAnd; case ParseNodeKind::LshAssignExpr: return JSOp::Lsh; case ParseNodeKind::RshAssignExpr: return JSOp::Rsh; case ParseNodeKind::UrshAssignExpr: return JSOp::Ursh; case ParseNodeKind::MulAssignExpr: return JSOp::Mul; case ParseNodeKind::DivAssignExpr: return JSOp::Div; case ParseNodeKind::ModAssignExpr: return JSOp::Mod; case ParseNodeKind::PowAssignExpr: return JSOp::Pow; case ParseNodeKind::CoalesceAssignExpr: case ParseNodeKind::OrAssignExpr: case ParseNodeKind::AndAssignExpr: // Short-circuit assignment operators are handled elsewhere. [[fallthrough]]; default: MOZ_CRASH("unexpected compound assignment op"); } } bool BytecodeEmitter::emitAssignmentOrInit(ParseNodeKind kind, ParseNode* lhs, ParseNode* rhs) { JSOp compoundOp = CompoundAssignmentParseNodeKindToJSOp(kind); bool isCompound = compoundOp != JSOp::Nop; bool isInit = kind == ParseNodeKind::InitExpr; MOZ_ASSERT_IF(isInit, lhs->isKind(ParseNodeKind::DotExpr) || lhs->isKind(ParseNodeKind::ElemExpr)); // |name| is used within NameOpEmitter, so its lifetime must surpass |noe|. const ParserAtom* name = nullptr; Maybe noe; Maybe poe; Maybe eoe; // Deal with non-name assignments. uint8_t offset = 1; // Purpose of anonFunctionName: // // In normal name assignments (`f = function(){}`), an anonymous function gets // an inferred name based on the left-hand side name node. // // In normal property assignments (`obj.x = function(){}`), the anonymous // function does not have a computed name, and rhs->isDirectRHSAnonFunction() // will be false (and anonFunctionName will not be used). However, in field // initializers (`class C { x = function(){} }`), field initialization is // implemented via a property or elem assignment (where we are now), and // rhs->isDirectRHSAnonFunction() is set - so we'll assign the name of the // function. const ParserAtom* anonFunctionName = nullptr; switch (lhs->getKind()) { case ParseNodeKind::Name: { name = lhs->as().name(); anonFunctionName = name; noe.emplace(this, name, isCompound ? NameOpEmitter::Kind::CompoundAssignment : NameOpEmitter::Kind::SimpleAssignment); break; } case ParseNodeKind::DotExpr: { PropertyAccess* prop = &lhs->as(); bool isSuper = prop->isSuper(); poe.emplace(this, isCompound ? PropOpEmitter::Kind::CompoundAssignment : isInit ? PropOpEmitter::Kind::PropInit : PropOpEmitter::Kind::SimpleAssignment, isSuper ? PropOpEmitter::ObjKind::Super : PropOpEmitter::ObjKind::Other); if (!poe->prepareForObj()) { return false; } anonFunctionName = prop->name(); if (isSuper) { UnaryNode* base = &prop->expression().as(); if (!emitGetThisForSuperBase(base)) { // [stack] THIS SUPERBASE return false; } // SUPERBASE is pushed onto THIS later in poe->emitGet below. offset += 2; } else { if (!emitTree(&prop->expression())) { // [stack] OBJ return false; } offset += 1; } break; } case ParseNodeKind::ElemExpr: { PropertyByValue* elem = &lhs->as(); bool isSuper = elem->isSuper(); bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); eoe.emplace(this, isCompound ? ElemOpEmitter::Kind::CompoundAssignment : isInit ? ElemOpEmitter::Kind::PropInit : ElemOpEmitter::Kind::SimpleAssignment, isSuper ? ElemOpEmitter::ObjKind::Super : ElemOpEmitter::ObjKind::Other, isPrivate ? NameVisibility::Private : NameVisibility::Public); if (!emitElemObjAndKey(elem, isSuper, *eoe)) { // [stack] # if Super // [stack] THIS KEY // [stack] # otherwise // [stack] OBJ KEY return false; } if (isSuper) { // SUPERBASE is pushed onto KEY in eoe->emitGet below. offset += 3; } else { offset += 2; } break; } case ParseNodeKind::ArrayExpr: case ParseNodeKind::ObjectExpr: break; case ParseNodeKind::CallExpr: if (!emitTree(lhs)) { return false; } // Assignment to function calls is forbidden, but we have to make the // call first. Now we can throw. if (!emit2(JSOp::ThrowMsg, uint8_t(ThrowMsgKind::AssignToCall))) { return false; } // Rebalance the stack to placate stack-depth assertions. if (!emit1(JSOp::Pop)) { return false; } break; default: MOZ_ASSERT(0); } if (isCompound) { MOZ_ASSERT(rhs); switch (lhs->getKind()) { case ParseNodeKind::DotExpr: { PropertyAccess* prop = &lhs->as(); if (!poe->emitGet(prop->key().atom())) { // [stack] # if Super // [stack] THIS SUPERBASE PROP // [stack] # otherwise // [stack] OBJ PROP return false; } break; } case ParseNodeKind::ElemExpr: { if (!eoe->emitGet()) { // [stack] KEY THIS OBJ ELEM return false; } break; } case ParseNodeKind::CallExpr: // 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)) { // [stack] NULL return false; } break; default:; } } switch (lhs->getKind()) { case ParseNodeKind::Name: if (!noe->prepareForRhs()) { // [stack] ENV? VAL? return false; } offset += noe->emittedBindOp(); break; case ParseNodeKind::DotExpr: if (!poe->prepareForRhs()) { // [stack] # if Simple Assignment with Super // [stack] THIS SUPERBASE // [stack] # if Simple Assignment with other // [stack] OBJ // [stack] # if Compound Assignment with Super // [stack] THIS SUPERBASE PROP // [stack] # if Compound Assignment with other // [stack] OBJ PROP return false; } break; case ParseNodeKind::ElemExpr: if (!eoe->prepareForRhs()) { // [stack] # if Simple Assignment with Super // [stack] THIS KEY SUPERBASE // [stack] # if Simple Assignment with other // [stack] OBJ KEY // [stack] # if Compound Assignment with Super // [stack] THIS KEY SUPERBASE ELEM // [stack] # if Compound Assignment with other // [stack] OBJ KEY ELEM return false; } break; default: break; } if (rhs) { if (!emitAssignmentRhs(rhs, anonFunctionName)) { // [stack] ... VAL? RHS return false; } } else { // Assumption: Things with pre-emitted RHS values never need to be named. if (!emitAssignmentRhs(offset)) { // [stack] ... VAL? RHS return false; } } /* If += etc., emit the binary operator with a source note. */ if (isCompound) { if (!newSrcNote(SrcNoteType::AssignOp)) { return false; } if (!emit1(compoundOp)) { // [stack] ... VAL return false; } } /* Finally, emit the specialized assignment bytecode. */ switch (lhs->getKind()) { case ParseNodeKind::Name: { if (!noe->emitAssignment()) { // [stack] VAL return false; } break; } case ParseNodeKind::DotExpr: { PropertyAccess* prop = &lhs->as(); if (!poe->emitAssignment(prop->key().atom())) { // [stack] VAL return false; } break; } case ParseNodeKind::CallExpr: // We threw above, so nothing to do here. break; case ParseNodeKind::ElemExpr: { if (!eoe->emitAssignment()) { // [stack] VAL return false; } break; } case ParseNodeKind::ArrayExpr: case ParseNodeKind::ObjectExpr: if (!emitDestructuringOps(&lhs->as(), DestructuringFlavor::Assignment)) { return false; } break; default: MOZ_ASSERT(0); } return true; } bool BytecodeEmitter::emitShortCircuitAssignment(AssignmentNode* node) { TDZCheckCache tdzCache(this); JSOp op; switch (node->getKind()) { case ParseNodeKind::CoalesceAssignExpr: op = JSOp::Coalesce; break; case ParseNodeKind::OrAssignExpr: op = JSOp::Or; break; case ParseNodeKind::AndAssignExpr: op = JSOp::And; break; default: MOZ_CRASH("Unexpected ParseNodeKind"); } ParseNode* lhs = node->left(); ParseNode* rhs = node->right(); // |name| is used within NameOpEmitter, so its lifetime must surpass |noe|. const ParserAtom* name = nullptr; // Select the appropriate emitter based on the left-hand side. Maybe noe; Maybe poe; Maybe eoe; int32_t depth = bytecodeSection().stackDepth(); // Number of values pushed onto the stack in addition to the lhs value. int32_t numPushed; // Evaluate the left-hand side expression and compute any stack values needed // for the assignment. switch (lhs->getKind()) { case ParseNodeKind::Name: { name = lhs->as().name(); noe.emplace(this, name, NameOpEmitter::Kind::CompoundAssignment); if (!noe->prepareForRhs()) { // [stack] ENV? LHS return false; } numPushed = noe->emittedBindOp(); break; } case ParseNodeKind::DotExpr: { PropertyAccess* prop = &lhs->as(); bool isSuper = prop->isSuper(); poe.emplace(this, PropOpEmitter::Kind::CompoundAssignment, isSuper ? PropOpEmitter::ObjKind::Super : PropOpEmitter::ObjKind::Other); if (!poe->prepareForObj()) { return false; } if (isSuper) { UnaryNode* base = &prop->expression().as(); if (!emitGetThisForSuperBase(base)) { // [stack] THIS SUPERBASE return false; } } else { if (!emitTree(&prop->expression())) { // [stack] OBJ return false; } } if (!poe->emitGet(prop->key().atom())) { // [stack] # if Super // [stack] THIS SUPERBASE LHS // [stack] # otherwise // [stack] OBJ LHS return false; } if (!poe->prepareForRhs()) { // [stack] # if Super // [stack] THIS SUPERBASE LHS // [stack] # otherwise // [stack] OBJ LHS return false; } numPushed = 1 + isSuper; break; } case ParseNodeKind::ElemExpr: { PropertyByValue* elem = &lhs->as(); bool isSuper = elem->isSuper(); bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); eoe.emplace(this, ElemOpEmitter::Kind::CompoundAssignment, isSuper ? ElemOpEmitter::ObjKind::Super : ElemOpEmitter::ObjKind::Other, isPrivate ? NameVisibility::Private : NameVisibility::Public); if (!emitElemObjAndKey(elem, isSuper, *eoe)) { // [stack] # if Super // [stack] THIS KEY // [stack] # otherwise // [stack] OBJ KEY return false; } if (!eoe->emitGet()) { // [stack] # if Super // [stack] THIS KEY SUPERBASE LHS // [stack] # otherwise // [stack] OBJ KEY LHS return false; } if (!eoe->prepareForRhs()) { // [stack] # if Super // [stack] THIS KEY SUPERBASE LHS // [stack] # otherwise // [stack] OBJ KEY LHS return false; } numPushed = 2 + isSuper; break; } default: MOZ_CRASH(); } MOZ_ASSERT(bytecodeSection().stackDepth() == depth + numPushed + 1); // Test for the short-circuit condition. JumpList jump; if (!emitJump(op, &jump)) { // [stack] ... LHS return false; } // The short-circuit condition wasn't fulfilled, pop the left-hand side value // which was kept on the stack. if (!emit1(JSOp::Pop)) { // [stack] ... return false; } if (!emitAssignmentRhs(rhs, name)) { // [stack] ... RHS return false; } // Perform the actual assignment. switch (lhs->getKind()) { case ParseNodeKind::Name: { if (!noe->emitAssignment()) { // [stack] RHS return false; } break; } case ParseNodeKind::DotExpr: { PropertyAccess* prop = &lhs->as(); if (!poe->emitAssignment(prop->key().atom())) { // [stack] RHS return false; } break; } case ParseNodeKind::ElemExpr: { if (!eoe->emitAssignment()) { // [stack] RHS return false; } break; } default: MOZ_CRASH(); } MOZ_ASSERT(bytecodeSection().stackDepth() == depth + 1); // Join with the short-circuit jump and pop anything left on the stack. if (numPushed > 0) { JumpList jumpAroundPop; if (!emitJump(JSOp::Goto, &jumpAroundPop)) { // [stack] RHS return false; } if (!emitJumpTargetAndPatch(jump)) { // [stack] ... LHS return false; } // Reconstruct the stack depth after the jump. bytecodeSection().setStackDepth(depth + 1 + numPushed); // Move the left-hand side value to the bottom and pop the rest. if (!emitUnpickN(numPushed)) { // [stack] LHS ... return false; } if (!emitPopN(numPushed)) { // [stack] LHS return false; } if (!emitJumpTargetAndPatch(jumpAroundPop)) { // [stack] LHS | RHS return false; } } else { if (!emitJumpTargetAndPatch(jump)) { // [stack] LHS | RHS return false; } } MOZ_ASSERT(bytecodeSection().stackDepth() == depth + 1); return true; } bool BytecodeEmitter::emitCallSiteObjectArray(ListNode* cookedOrRaw, GCThingIndex* outArrayIndex) { uint32_t count = cookedOrRaw->count(); ParseNode* pn = cookedOrRaw->head(); // The first element of a call-site node is the raw-values list. Skip over it. if (cookedOrRaw->isKind(ParseNodeKind::CallSiteObj)) { MOZ_ASSERT(pn->isKind(ParseNodeKind::ArrayExpr)); pn = pn->pn_next; count--; } else { MOZ_ASSERT(cookedOrRaw->isKind(ParseNodeKind::ArrayExpr)); } ObjLiteralWriter writer; ObjLiteralFlags flags(ObjLiteralFlag::Array); writer.beginObject(flags); writer.beginDenseArrayElements(); size_t idx; for (idx = 0; pn; idx++, pn = pn->pn_next) { MOZ_ASSERT(pn->isKind(ParseNodeKind::TemplateStringExpr) || pn->isKind(ParseNodeKind::RawUndefinedExpr)); if (!emitObjLiteralValue(writer, pn)) { return false; } } MOZ_ASSERT(idx == count); return addObjLiteralData(writer, outArrayIndex); } bool BytecodeEmitter::emitCallSiteObject(CallSiteNode* callSiteObj) { GCThingIndex cookedIndex; if (!emitCallSiteObjectArray(callSiteObj, &cookedIndex)) { return false; } GCThingIndex rawIndex; if (!emitCallSiteObjectArray(callSiteObj->rawNodes(), &rawIndex)) { return false; } MOZ_ASSERT(sc->hasCallSiteObj()); return emitObjectPairOp(cookedIndex, rawIndex, JSOp::CallSiteObj); } bool BytecodeEmitter::emitCatch(BinaryNode* catchClause) { // We must be nested under a try-finally statement. MOZ_ASSERT(innermostNestableControl->is()); ParseNode* param = catchClause->left(); if (!param) { // Catch parameter was omitted; just discard the exception. if (!emit1(JSOp::Pop)) { return false; } } else { switch (param->getKind()) { case ParseNodeKind::ArrayExpr: case ParseNodeKind::ObjectExpr: if (!emitDestructuringOps(¶m->as(), DestructuringFlavor::Declaration)) { return false; } if (!emit1(JSOp::Pop)) { return false; } break; case ParseNodeKind::Name: if (!emitLexicalInitialization(¶m->as())) { return false; } if (!emit1(JSOp::Pop)) { return false; } break; default: MOZ_ASSERT(0); } } /* Emit the catch body. */ return emitTree(catchClause->right()); } // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See the // comment on EmitSwitch. MOZ_NEVER_INLINE bool BytecodeEmitter::emitTry(TryNode* tryNode) { LexicalScopeNode* catchScope = tryNode->catchScope(); ParseNode* finallyNode = tryNode->finallyBlock(); TryEmitter::Kind kind; if (catchScope) { if (finallyNode) { kind = TryEmitter::Kind::TryCatchFinally; } else { kind = TryEmitter::Kind::TryCatch; } } else { MOZ_ASSERT(finallyNode); kind = TryEmitter::Kind::TryFinally; } TryEmitter tryCatch(this, kind, TryEmitter::ControlKind::Syntactic); if (!tryCatch.emitTry()) { return false; } if (!emitTree(tryNode->body())) { return false; } // If this try has a catch block, emit it. if (catchScope) { // The emitted code for a catch block looks like: // // [pushlexicalenv] only if any local aliased // exception // setlocal 0; pop assign or possibly destructure exception // < catch block contents > // debugleaveblock // [poplexicalenv] only if any local aliased // if there is a finally block: // gosub // goto if (!tryCatch.emitCatch()) { return false; } // Emit the lexical scope and catch body. if (!emitTree(catchScope)) { return false; } } // Emit the finally handler, if there is one. if (finallyNode) { if (!tryCatch.emitFinally(Some(finallyNode->pn_pos.begin))) { return false; } if (!emitTree(finallyNode)) { return false; } } if (!tryCatch.emitEnd()) { return false; } return true; } MOZ_MUST_USE bool BytecodeEmitter::emitGoSub(JumpList* jump) { // Emit the following: // // False // ResumeIndex // Gosub // resumeOffset: // JumpTarget // // The order is important: the Baseline Interpreter relies on JSOp::JumpTarget // setting the frame's ICEntry when resuming at resumeOffset. if (!emit1(JSOp::False)) { return false; } BytecodeOffset off; if (!emitN(JSOp::ResumeIndex, 3, &off)) { return false; } if (!emitJumpNoFallthrough(JSOp::Gosub, jump)) { return false; } uint32_t resumeIndex; if (!allocateResumeIndex(bytecodeSection().offset(), &resumeIndex)) { return false; } SET_RESUMEINDEX(bytecodeSection().code(off), resumeIndex); JumpTarget target; return emitJumpTarget(&target); } bool BytecodeEmitter::emitIf(TernaryNode* ifNode) { IfEmitter ifThenElse(this); if (!ifThenElse.emitIf(Some(ifNode->kid1()->pn_pos.begin))) { return false; } if_again: ParseNode* testNode = ifNode->kid1(); auto conditionKind = IfEmitter::ConditionKind::Positive; if (testNode->isKind(ParseNodeKind::NotExpr)) { testNode = testNode->as().kid(); conditionKind = IfEmitter::ConditionKind::Negative; } if (!markStepBreakpoint()) { return false; } // Emit code for the condition before pushing stmtInfo. // NOTE: NotExpr of testNode may be unwrapped, and in that case the negation // is handled by conditionKind. if (!emitTree(testNode)) { return false; } ParseNode* elseNode = ifNode->kid3(); if (elseNode) { if (!ifThenElse.emitThenElse(conditionKind)) { return false; } } else { if (!ifThenElse.emitThen(conditionKind)) { return false; } } /* Emit code for the then part. */ if (!emitTree(ifNode->kid2())) { return false; } if (elseNode) { if (elseNode->isKind(ParseNodeKind::IfStmt)) { ifNode = &elseNode->as(); if (!ifThenElse.emitElseIf(Some(ifNode->kid1()->pn_pos.begin))) { return false; } goto if_again; } if (!ifThenElse.emitElse()) { return false; } /* Emit code for the else part. */ if (!emitTree(elseNode)) { return false; } } if (!ifThenElse.emitEnd()) { return false; } return true; } bool BytecodeEmitter::emitHoistedFunctionsInList(ListNode* stmtList) { MOZ_ASSERT(stmtList->hasTopLevelFunctionDeclarations()); // We can call this multiple times for sloppy eval scopes. if (stmtList->emittedTopLevelFunctionDeclarations()) { return true; } stmtList->setEmittedTopLevelFunctionDeclarations(); for (ParseNode* stmt : stmtList->contents()) { ParseNode* maybeFun = stmt; if (!sc->strict()) { while (maybeFun->isKind(ParseNodeKind::LabelStmt)) { maybeFun = maybeFun->as().statement(); } } if (maybeFun->is() && maybeFun->as().functionIsHoisted()) { if (!emitTree(maybeFun)) { return false; } } } return true; } bool BytecodeEmitter::emitLexicalScopeBody( ParseNode* body, EmitLineNumberNote emitLineNote /* = EMIT_LINENOTE */) { if (body->isKind(ParseNodeKind::StatementList) && body->as().hasTopLevelFunctionDeclarations()) { // 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->as())) { return false; } } // Line notes were updated by emitLexicalScope or emitScript. return emitTree(body, ValueUsage::WantValue, 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( LexicalScopeNode* lexicalScope) { LexicalScopeEmitter lse(this); ParseNode* body = lexicalScope->scopeBody(); if (lexicalScope->isEmptyScope()) { if (!lse.emitEmptyScope()) { return false; } if (!emitLexicalScopeBody(body)) { return false; } if (!lse.emitEnd()) { return false; } return true; } // We are about to emit some bytecode for what the spec calls "declaration // instantiation". Assign these instructions to the opening `{` of the // block. (Using the location of each declaration we're instantiating is // too weird when stepping in the debugger.) if (!ParseNodeRequiresSpecialLineNumberNotes(body)) { if (!updateSourceCoordNotes(lexicalScope->pn_pos.begin)) { return false; } } ScopeKind kind; if (body->isKind(ParseNodeKind::Catch)) { BinaryNode* catchNode = &body->as(); kind = (!catchNode->left() || catchNode->left()->isKind(ParseNodeKind::Name)) ? ScopeKind::SimpleCatch : ScopeKind::Catch; } else { kind = lexicalScope->kind(); } if (!lse.emitScope(kind, lexicalScope->scopeBindings())) { return false; } if (body->isKind(ParseNodeKind::ForStmt)) { // 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->as(), &lse.emitterScope())) { return false; } } else { if (!emitLexicalScopeBody(body, SUPPRESS_LINENOTE)) { return false; } } if (!lse.emitEnd()) { return false; } return true; } bool BytecodeEmitter::emitWith(BinaryNode* withNode) { // Ensure that the column of the 'with' is set properly. if (!updateSourceCoordNotes(withNode->left()->pn_pos.begin)) { return false; } if (!markStepBreakpoint()) { return false; } if (!emitTree(withNode->left())) { return false; } EmitterScope emitterScope(this); if (!emitterScope.enterWith(this)) { return false; } if (!emitTree(withNode->right())) { return false; } return emitterScope.leave(this); } bool BytecodeEmitter::emitCopyDataProperties(CopyOption option) { DebugOnly depth = bytecodeSection().stackDepth(); uint32_t argc; if (option == CopyOption::Filtered) { MOZ_ASSERT(depth > 2); // [stack] TARGET SOURCE SET argc = 3; if (!emitAtomOp(JSOp::GetIntrinsic, cx->parserNames().CopyDataProperties)) { // [stack] TARGET SOURCE SET COPYDATAPROPERTIES return false; } } else { MOZ_ASSERT(depth > 1); // [stack] TARGET SOURCE argc = 2; if (!emitAtomOp(JSOp::GetIntrinsic, cx->parserNames().CopyDataPropertiesUnfiltered)) { // [stack] TARGET SOURCE COPYDATAPROPERTIES return false; } } if (!emit1(JSOp::Undefined)) { // [stack] TARGET SOURCE SET? COPYDATAPROPERTIES // UNDEFINED return false; } if (!emit2(JSOp::Pick, argc + 1)) { // [stack] SOURCE SET? COPYDATAPROPERTIES UNDEFINED // TARGET return false; } if (!emit2(JSOp::Pick, argc + 1)) { // [stack] SET? COPYDATAPROPERTIES UNDEFINED TARGET // SOURCE return false; } if (option == CopyOption::Filtered) { if (!emit2(JSOp::Pick, argc + 1)) { // [stack] COPYDATAPROPERTIES UNDEFINED TARGET SOURCE SET return false; } } if (!emitCall(JSOp::CallIgnoresRv, argc)) { // [stack] IGNORED return false; } if (!emit1(JSOp::Pop)) { // [stack] return false; } MOZ_ASSERT(depth - int(argc) == bytecodeSection().stackDepth()); return true; } bool BytecodeEmitter::emitBigIntOp(BigIntLiteral* bigint) { GCThingIndex index; if (!perScriptData().gcThingList().append(bigint, &index)) { return false; } return emitGCIndexOp(JSOp::BigInt, index); } bool BytecodeEmitter::emitIterator() { // Convert iterable to iterator. if (!emit1(JSOp::Dup)) { // [stack] OBJ OBJ return false; } if (!emit2(JSOp::Symbol, uint8_t(JS::SymbolCode::iterator))) { // [stack] OBJ OBJ @@ITERATOR return false; } if (!emitElemOpBase(JSOp::GetElem)) { // [stack] OBJ ITERFN return false; } if (!emit1(JSOp::Swap)) { // [stack] ITERFN OBJ return false; } if (!emitCall(JSOp::CallIter, 0)) { // [stack] ITER return false; } if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) { // [stack] ITER return false; } if (!emit1(JSOp::Dup)) { // [stack] ITER ITER return false; } if (!emitAtomOp(JSOp::GetProp, cx->parserNames().next)) { // [stack] ITER NEXT return false; } if (!emit1(JSOp::Swap)) { // [stack] NEXT ITER return false; } return true; } bool BytecodeEmitter::emitAsyncIterator() { // Convert iterable to iterator. if (!emit1(JSOp::Dup)) { // [stack] OBJ OBJ return false; } if (!emit2(JSOp::Symbol, uint8_t(JS::SymbolCode::asyncIterator))) { // [stack] OBJ OBJ @@ASYNCITERATOR return false; } if (!emitElemOpBase(JSOp::GetElem)) { // [stack] OBJ ITERFN return false; } InternalIfEmitter ifAsyncIterIsUndefined(this); if (!emitPushNotUndefinedOrNull()) { // [stack] OBJ ITERFN !UNDEF-OR-NULL return false; } if (!ifAsyncIterIsUndefined.emitThenElse( IfEmitter::ConditionKind::Negative)) { // [stack] OBJ ITERFN return false; } if (!emit1(JSOp::Pop)) { // [stack] OBJ return false; } if (!emit1(JSOp::Dup)) { // [stack] OBJ OBJ return false; } if (!emit2(JSOp::Symbol, uint8_t(JS::SymbolCode::iterator))) { // [stack] OBJ OBJ @@ITERATOR return false; } if (!emitElemOpBase(JSOp::GetElem)) { // [stack] OBJ ITERFN return false; } if (!emit1(JSOp::Swap)) { // [stack] ITERFN OBJ return false; } if (!emitCall(JSOp::CallIter, 0)) { // [stack] ITER return false; } if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) { // [stack] ITER return false; } if (!emit1(JSOp::Dup)) { // [stack] ITER ITER return false; } if (!emitAtomOp(JSOp::GetProp, cx->parserNames().next)) { // [stack] ITER SYNCNEXT return false; } if (!emit1(JSOp::ToAsyncIter)) { // [stack] ITER return false; } if (!ifAsyncIterIsUndefined.emitElse()) { // [stack] OBJ ITERFN return false; } if (!emit1(JSOp::Swap)) { // [stack] ITERFN OBJ return false; } if (!emitCall(JSOp::CallIter, 0)) { // [stack] ITER return false; } if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) { // [stack] ITER return false; } if (!ifAsyncIterIsUndefined.emitEnd()) { // [stack] ITER return false; } if (!emit1(JSOp::Dup)) { // [stack] ITER ITER return false; } if (!emitAtomOp(JSOp::GetProp, cx->parserNames().next)) { // [stack] ITER NEXT return false; } if (!emit1(JSOp::Swap)) { // [stack] NEXT ITER return false; } return true; } bool BytecodeEmitter::emitSpread(bool allowSelfHosted) { LoopControl loopInfo(this, StatementKind::Spread); if (!loopInfo.emitLoopHead(this, Nothing())) { // [stack] NEXT ITER ARR I return false; } { #ifdef DEBUG auto loopDepth = bytecodeSection().stackDepth(); #endif // Spread operations can't contain |continue|, so don't bother setting loop // and enclosing "update" offsets, as we do with for-loops. if (!emitDupAt(3, 2)) { // [stack] NEXT ITER ARR I NEXT return false; } if (!emitIteratorNext(Nothing(), IteratorKind::Sync, allowSelfHosted)) { // [stack] NEXT ITER ARR I RESULT return false; } if (!emit1(JSOp::Dup)) { // [stack] NEXT ITER ARR I RESULT RESULT return false; } if (!emitAtomOp(JSOp::GetProp, cx->parserNames().done)) { // [stack] NEXT ITER ARR I RESULT DONE return false; } if (!emitJump(JSOp::IfNe, &loopInfo.breaks)) { // [stack] NEXT ITER ARR I RESULT return false; } // Emit code to assign result.value to the iteration variable. if (!emitAtomOp(JSOp::GetProp, cx->parserNames().value)) { // [stack] NEXT ITER ARR I VALUE return false; } if (!emit1(JSOp::InitElemInc)) { // [stack] NEXT ITER ARR (I+1) return false; } if (!loopInfo.emitLoopEnd(this, JSOp::Goto, TryNoteKind::ForOf)) { // [stack] NEXT ITER ARR (I+1) return false; } MOZ_ASSERT(bytecodeSection().stackDepth() == loopDepth); } // When we leave the loop body and jump to this point, the result value is // still on the stack. Account for that by updating the stack depth // manually. bytecodeSection().setStackDepth(bytecodeSection().stackDepth() + 1); // No continues should occur in spreads. MOZ_ASSERT(!loopInfo.continues.offset.valid()); if (!emit2(JSOp::Pick, 4)) { // [stack] ITER ARR FINAL_INDEX RESULT NEXT return false; } if (!emit2(JSOp::Pick, 4)) { // [stack] ARR FINAL_INDEX RESULT NEXT ITER return false; } return emitPopN(3); // [stack] ARR FINAL_INDEX } bool BytecodeEmitter::emitInitializeForInOrOfTarget(TernaryNode* forHead) { MOZ_ASSERT(forHead->isKind(ParseNodeKind::ForIn) || forHead->isKind(ParseNodeKind::ForOf)); MOZ_ASSERT(bytecodeSection().stackDepth() >= 1, "must have a per-iteration value for initializing"); ParseNode* target = forHead->kid1(); MOZ_ASSERT(!forHead->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->astGenerator().isDeclarationList(target)) { return emitAssignmentOrInit(ParseNodeKind::AssignExpr, target, nullptr); // [stack] ... 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->astGenerator().singleBindingFromDeclaration( &target->as()); NameNode* nameNode = nullptr; if (target->isKind(ParseNodeKind::Name)) { nameNode = &target->as(); } else if (target->isKind(ParseNodeKind::AssignExpr) || target->isKind(ParseNodeKind::InitExpr)) { BinaryNode* assignNode = &target->as(); if (assignNode->left()->is()) { nameNode = &assignNode->left()->as(); } } if (nameNode) { const ParserAtom* nameAtom = nameNode->name(); NameOpEmitter noe(this, nameAtom, NameOpEmitter::Kind::Initialize); if (!noe.prepareForRhs()) { return false; } if (noe.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(bytecodeSection().stackDepth() >= 2); if (!emit1(JSOp::Swap)) { return false; } } else { // In cases of emitting a frame slot or environment slot, // nothing needs be done. MOZ_ASSERT(bytecodeSection().stackDepth() >= 1); } if (!noe.emitAssignment()) { return false; } // The caller handles removing the iteration value from the stack. return true; } MOZ_ASSERT( !target->isKind(ParseNodeKind::AssignExpr) && !target->isKind(ParseNodeKind::InitExpr), "for-in/of loop destructuring declarations can't have initializers"); MOZ_ASSERT(target->isKind(ParseNodeKind::ArrayExpr) || target->isKind(ParseNodeKind::ObjectExpr)); return emitDestructuringOps(&target->as(), DestructuringFlavor::Declaration); } bool BytecodeEmitter::emitForOf(ForNode* forOfLoop, const EmitterScope* headLexicalEmitterScope) { MOZ_ASSERT(forOfLoop->isKind(ParseNodeKind::ForStmt)); TernaryNode* forOfHead = forOfLoop->head(); MOZ_ASSERT(forOfHead->isKind(ParseNodeKind::ForOf)); unsigned iflags = forOfLoop->iflags(); IteratorKind iterKind = (iflags & JSITER_FORAWAITOF) ? IteratorKind::Async : IteratorKind::Sync; MOZ_ASSERT_IF(iterKind == IteratorKind::Async, sc->isSuspendableContext()); MOZ_ASSERT_IF(iterKind == IteratorKind::Async, sc->asSuspendableContext()->isAsync()); ParseNode* forHeadExpr = forOfHead->kid3(); // Certain builtins (e.g. Array.from) are implemented in self-hosting // as for-of loops. ForOfEmitter forOf(this, headLexicalEmitterScope, allowSelfHostedIter(forHeadExpr), iterKind); if (!forOf.emitIterated()) { // [stack] return false; } if (!updateSourceCoordNotes(forHeadExpr->pn_pos.begin)) { return false; } if (!markStepBreakpoint()) { return false; } if (!emitTree(forHeadExpr)) { // [stack] ITERABLE return false; } if (headLexicalEmitterScope) { DebugOnly forOfTarget = forOfHead->kid1(); MOZ_ASSERT(forOfTarget->isKind(ParseNodeKind::LetDecl) || forOfTarget->isKind(ParseNodeKind::ConstDecl)); } if (!forOf.emitInitialize(Some(forOfHead->pn_pos.begin))) { // [stack] NEXT ITER VALUE return false; } if (!emitInitializeForInOrOfTarget(forOfHead)) { // [stack] NEXT ITER VALUE return false; } if (!forOf.emitBody()) { // [stack] NEXT ITER UNDEF return false; } // Perform the loop body. ParseNode* forBody = forOfLoop->body(); if (!emitTree(forBody)) { // [stack] NEXT ITER UNDEF return false; } if (!forOf.emitEnd(Some(forHeadExpr->pn_pos.begin))) { // [stack] return false; } return true; } bool BytecodeEmitter::emitForIn(ForNode* forInLoop, const EmitterScope* headLexicalEmitterScope) { TernaryNode* forInHead = forInLoop->head(); MOZ_ASSERT(forInHead->isKind(ParseNodeKind::ForIn)); ForInEmitter forIn(this, headLexicalEmitterScope); // Annex B: Evaluate the var-initializer expression if present. // |for (var i = initializer in expr) { ... }| ParseNode* forInTarget = forInHead->kid1(); if (parser->astGenerator().isDeclarationList(forInTarget)) { ParseNode* decl = parser->astGenerator().singleBindingFromDeclaration( &forInTarget->as()); if (decl->isKind(ParseNodeKind::AssignExpr) || decl->isKind(ParseNodeKind::InitExpr)) { BinaryNode* assignNode = &decl->as(); if (assignNode->left()->is()) { NameNode* nameNode = &assignNode->left()->as(); ParseNode* initializer = assignNode->right(); MOZ_ASSERT( forInTarget->isKind(ParseNodeKind::VarStmt), "for-in initializers are only permitted for |var| declarations"); if (!updateSourceCoordNotes(decl->pn_pos.begin)) { return false; } const ParserAtom* nameAtom = nameNode->name(); NameOpEmitter noe(this, nameAtom, NameOpEmitter::Kind::Initialize); if (!noe.prepareForRhs()) { return false; } if (!emitInitializer(initializer, nameNode)) { return false; } if (!noe.emitAssignment()) { return false; } // Pop the initializer. if (!emit1(JSOp::Pop)) { return false; } } } } if (!forIn.emitIterated()) { // [stack] return false; } // Evaluate the expression being iterated. ParseNode* expr = forInHead->kid3(); if (!updateSourceCoordNotes(expr->pn_pos.begin)) { return false; } if (!markStepBreakpoint()) { return false; } if (!emitTree(expr)) { // [stack] EXPR return false; } MOZ_ASSERT(forInLoop->iflags() == 0); MOZ_ASSERT_IF(headLexicalEmitterScope, forInTarget->isKind(ParseNodeKind::LetDecl) || forInTarget->isKind(ParseNodeKind::ConstDecl)); if (!forIn.emitInitialize()) { // [stack] ITER ITERVAL return false; } if (!emitInitializeForInOrOfTarget(forInHead)) { // [stack] ITER ITERVAL return false; } if (!forIn.emitBody()) { // [stack] ITER ITERVAL return false; } // Perform the loop body. ParseNode* forBody = forInLoop->body(); if (!emitTree(forBody)) { // [stack] ITER ITERVAL return false; } if (!forIn.emitEnd(Some(forInHead->pn_pos.begin))) { // [stack] return false; } return true; } /* C-style `for (init; cond; update) ...` loop. */ bool BytecodeEmitter::emitCStyleFor( ForNode* forNode, const EmitterScope* headLexicalEmitterScope) { TernaryNode* forHead = forNode->head(); ParseNode* forBody = forNode->body(); ParseNode* init = forHead->kid1(); ParseNode* cond = forHead->kid2(); ParseNode* update = forHead->kid3(); bool isLet = init && init->isKind(ParseNodeKind::LetDecl); CForEmitter cfor(this, isLet ? headLexicalEmitterScope : nullptr); if (!cfor.emitInit(init ? Some(init->pn_pos.begin) : Nothing())) { // [stack] return false; } // If the head of this for-loop declared any lexical variables, the parser // wrapped this ParseNodeKind::For node in a ParseNodeKind::LexicalScope // representing the implicit scope of those variables. By the time we get // here, we have already entered that scope. So far, so good. if (init) { // 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 (init->isForLoopDeclaration()) { if (!emitTree(init)) { // [stack] return false; } } else { if (!updateSourceCoordNotes(init->pn_pos.begin)) { return false; } if (!markStepBreakpoint()) { return false; } // 'init' is an expression, not a declaration. emitTree left its // value on the stack. if (!emitTree(init, ValueUsage::IgnoreValue)) { // [stack] VAL return false; } if (!emit1(JSOp::Pop)) { // [stack] return false; } } } if (!cfor.emitCond(cond ? Some(cond->pn_pos.begin) : Nothing())) { // [stack] return false; } if (cond) { if (!updateSourceCoordNotes(cond->pn_pos.begin)) { return false; } if (!markStepBreakpoint()) { return false; } if (!emitTree(cond)) { // [stack] VAL return false; } } if (!cfor.emitBody(cond ? CForEmitter::Cond::Present : CForEmitter::Cond::Missing)) { // [stack] return false; } if (!emitTree(forBody)) { // [stack] return false; } if (!cfor.emitUpdate( update ? CForEmitter::Update::Present : CForEmitter::Update::Missing, update ? Some(update->pn_pos.begin) : Nothing())) { // [stack] return false; } // Check for update code to do before the condition (if any). if (update) { if (!updateSourceCoordNotes(update->pn_pos.begin)) { return false; } if (!markStepBreakpoint()) { return false; } if (!emitTree(update, ValueUsage::IgnoreValue)) { // [stack] VAL return false; } } if (!cfor.emitEnd(Some(forNode->pn_pos.begin))) { // [stack] return false; } return true; } bool BytecodeEmitter::emitFor(ForNode* forNode, const EmitterScope* headLexicalEmitterScope) { if (forNode->head()->isKind(ParseNodeKind::ForHead)) { return emitCStyleFor(forNode, headLexicalEmitterScope); } if (!updateLineNumberNotes(forNode->pn_pos.begin)) { return false; } if (forNode->head()->isKind(ParseNodeKind::ForIn)) { return emitForIn(forNode, headLexicalEmitterScope); } MOZ_ASSERT(forNode->head()->isKind(ParseNodeKind::ForOf)); return emitForOf(forNode, headLexicalEmitterScope); } MOZ_NEVER_INLINE bool BytecodeEmitter::emitFunction( FunctionNode* funNode, bool needsProto /* = false */, ListNode* classContentsIfConstructor /* = nullptr */) { FunctionBox* funbox = funNode->funbox(); MOZ_ASSERT((classContentsIfConstructor != nullptr) == funbox->isClassConstructor()); // [stack] FunctionEmitter fe(this, funbox, funNode->syntaxKind(), funNode->functionIsHoisted() ? FunctionEmitter::IsHoisted::Yes : FunctionEmitter::IsHoisted::No); // 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()) { if (!fe.emitAgain()) { // [stack] return false; } MOZ_ASSERT(funNode->functionIsHoisted()); return true; } if (funbox->isInterpreted()) { // Compute the field initializers data and update the funbox. // // NOTE: For a lazy function, this will be applied to any existing function // in UpdateEmittedInnerFunctions(). if (classContentsIfConstructor) { mozilla::Maybe memberInitializers = setupMemberInitializers(classContentsIfConstructor, FieldPlacement::Instance); if (!memberInitializers) { ReportAllocationOverflow(cx); return false; } funbox->setMemberInitializers(*memberInitializers); } if (!funbox->emitBytecode) { return fe.emitLazy(); // [stack] FUN? } if (!fe.prepareForNonLazy()) { // [stack] return false; } BytecodeEmitter bce2(this, parser, funbox, stencil, compilationState, emitterMode); if (!bce2.init(funNode->pn_pos)) { return false; } /* We measured the max scope depth when we parsed the function. */ if (!bce2.emitFunctionScript(funNode)) { return false; } if (!fe.emitNonLazyEnd()) { // [stack] FUN? return false; } return true; } if (!fe.emitAsmJSModule()) { // [stack] return false; } return true; } bool BytecodeEmitter::emitDo(BinaryNode* doNode) { ParseNode* bodyNode = doNode->left(); DoWhileEmitter doWhile(this); if (!doWhile.emitBody(Some(doNode->pn_pos.begin), getOffsetForLoop(bodyNode))) { return false; } if (!emitTree(bodyNode)) { return false; } if (!doWhile.emitCond()) { return false; } ParseNode* condNode = doNode->right(); if (!updateSourceCoordNotes(condNode->pn_pos.begin)) { return false; } if (!markStepBreakpoint()) { return false; } if (!emitTree(condNode)) { return false; } if (!doWhile.emitEnd()) { return false; } return true; } bool BytecodeEmitter::emitWhile(BinaryNode* whileNode) { ParseNode* bodyNode = whileNode->right(); WhileEmitter wh(this); ParseNode* condNode = whileNode->left(); if (!wh.emitCond(Some(whileNode->pn_pos.begin), getOffsetForLoop(condNode), Some(whileNode->pn_pos.end))) { return false; } if (!updateSourceCoordNotes(condNode->pn_pos.begin)) { return false; } if (!markStepBreakpoint()) { return false; } if (!emitTree(condNode)) { return false; } if (!wh.emitBody()) { return false; } if (!emitTree(bodyNode)) { return false; } if (!wh.emitEnd()) { return false; } return true; } bool BytecodeEmitter::emitBreak(const ParserName* label) { BreakableControl* target; 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); } else { auto isNotLabel = [](BreakableControl* control) { return !control->is(); }; target = findInnermostNestableControl(isNotLabel); } return emitGoto(target, &target->breaks, GotoKind::Break); } bool BytecodeEmitter::emitContinue(const ParserName* 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, GotoKind::Continue); } bool BytecodeEmitter::emitGetFunctionThis(NameNode* thisName) { MOZ_ASSERT(sc->hasFunctionThisBinding()); MOZ_ASSERT(thisName->isName(cx->parserNames().dotThis)); return emitGetFunctionThis(Some(thisName->pn_pos.begin)); } bool BytecodeEmitter::emitGetFunctionThis( const mozilla::Maybe& offset) { if (offset) { if (!updateLineNumberNotes(*offset)) { return false; } } if (!emitGetName(cx->parserNames().dotThis)) { // [stack] THIS return false; } if (sc->needsThisTDZChecks()) { if (!emit1(JSOp::CheckThis)) { // [stack] THIS return false; } } return true; } bool BytecodeEmitter::emitGetThisForSuperBase(UnaryNode* superBase) { MOZ_ASSERT(superBase->isKind(ParseNodeKind::SuperBase)); NameNode* nameNode = &superBase->kid()->as(); return emitGetFunctionThis(nameNode); // [stack] THIS } bool BytecodeEmitter::emitThisLiteral(ThisLiteral* pn) { if (ParseNode* kid = pn->kid()) { NameNode* thisName = &kid->as(); return emitGetFunctionThis(thisName); // [stack] THIS } if (sc->thisBinding() == ThisBinding::Module) { return emit1(JSOp::Undefined); // [stack] UNDEF } MOZ_ASSERT(sc->thisBinding() == ThisBinding::Global); return emit1(JSOp::GlobalThis); // [stack] THIS } bool BytecodeEmitter::emitCheckDerivedClassConstructorReturn() { MOZ_ASSERT(lookupName(cx->parserNames().dotThis).hasKnownSlot()); if (!emitGetName(cx->parserNames().dotThis)) { return false; } if (!emit1(JSOp::CheckReturn)) { return false; } return true; } bool BytecodeEmitter::emitReturn(UnaryNode* returnNode) { if (!updateSourceCoordNotes(returnNode->pn_pos.begin)) { return false; } bool needsIteratorResult = sc->isFunctionBox() && sc->asFunctionBox()->needsIteratorResult(); if (needsIteratorResult) { if (!emitPrepareIteratorResult()) { return false; } } if (!updateSourceCoordNotes(returnNode->pn_pos.begin)) { return false; } if (!markStepBreakpoint()) { return false; } /* Push a return value */ if (ParseNode* expr = returnNode->kid()) { if (!emitTree(expr)) { return false; } if (sc->asSuspendableContext()->isAsync() && sc->asSuspendableContext()->isGenerator()) { if (!emitAwaitInInnermostScope()) { return false; } } } else { /* No explicit return value provided */ if (!emit1(JSOp::Undefined)) { return false; } } if (needsIteratorResult) { 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. 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. */ BytecodeOffset top = bytecodeSection().offset(); bool needsFinalYield = sc->isFunctionBox() && sc->asFunctionBox()->needsFinalYield(); bool isDerivedClassConstructor = sc->isFunctionBox() && sc->asFunctionBox()->isDerivedClassConstructor(); if (!emit1((needsFinalYield || 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, NonLocalExitControl::Return); if (!nle.prepareForNonLocalJumpToOutermost()) { return false; } if (needsFinalYield) { // We know that .generator is on the function scope, as we just exited // all nested scopes. NameLocation loc = *locationOfNameBoundInScopeType( cx->parserNames().dotGenerator, varEmitterScope); // Resolve the return value before emitting the final yield. if (sc->asFunctionBox()->needsPromiseResult()) { if (!emit1(JSOp::GetRval)) { // [stack] RVAL return false; } if (!emitGetNameAtLocation(cx->parserNames().dotGenerator, loc)) { // [stack] RVAL GEN return false; } if (!emit2(JSOp::AsyncResolve, uint8_t(AsyncFunctionResolveKind::Fulfill))) { // [stack] PROMISE return false; } if (!emit1(JSOp::SetRval)) { // [stack] return false; } } if (!emitGetNameAtLocation(cx->parserNames().dotGenerator, loc)) { return false; } if (!emitYieldOp(JSOp::FinalYieldRval)) { return false; } } else if (isDerivedClassConstructor) { MOZ_ASSERT(JSOp(bytecodeSection().code()[top.value()]) == JSOp::SetRval); if (!emitReturnRval()) { return false; } } else if (top + BytecodeOffsetDiff(JSOpLength_Return) != bytecodeSection().offset() || // If we are instrumenting, make sure we use RetRval and add any // instrumentation for the frame exit. instrumentationKinds) { bytecodeSection().code()[top.value()] = jsbytecode(JSOp::SetRval); if (!emitReturnRval()) { return false; } } return true; } bool BytecodeEmitter::emitGetDotGeneratorInScope(EmitterScope& currentScope) { if (!sc->isFunction() && sc->isModuleContext() && sc->asModuleContext()->isAsync()) { NameLocation loc = *locationOfNameBoundInScopeType( cx->parserNames().dotGenerator, ¤tScope); return emitGetNameAtLocation(cx->parserNames().dotGenerator, loc); } NameLocation loc = *locationOfNameBoundInScopeType( cx->parserNames().dotGenerator, ¤tScope); return emitGetNameAtLocation(cx->parserNames().dotGenerator, loc); } bool BytecodeEmitter::emitInitialYield(UnaryNode* yieldNode) { if (!emitTree(yieldNode->kid())) { return false; } if (!emitYieldOp(JSOp::InitialYield)) { // [stack] RVAL GENERATOR RESUMEKIND return false; } if (!emit1(JSOp::CheckResumeKind)) { // [stack] RVAL return false; } if (!emit1(JSOp::Pop)) { // [stack] return false; } return true; } bool BytecodeEmitter::emitYield(UnaryNode* yieldNode) { MOZ_ASSERT(sc->isFunctionBox()); MOZ_ASSERT(sc->asFunctionBox()->isGenerator()); MOZ_ASSERT(yieldNode->isKind(ParseNodeKind::YieldExpr)); bool needsIteratorResult = sc->asFunctionBox()->needsIteratorResult(); if (needsIteratorResult) { if (!emitPrepareIteratorResult()) { // [stack] ITEROBJ return false; } } if (ParseNode* expr = yieldNode->kid()) { if (!emitTree(expr)) { // [stack] ITEROBJ? VAL return false; } } else { if (!emit1(JSOp::Undefined)) { // [stack] ITEROBJ? UNDEFINED return false; } } // 25.5.3.7 AsyncGeneratorYield step 5. if (sc->asSuspendableContext()->isAsync()) { MOZ_ASSERT(!needsIteratorResult); if (!emitAwaitInInnermostScope()) { // [stack] RESULT return false; } } if (needsIteratorResult) { if (!emitFinishIteratorResult(false)) { // [stack] ITEROBJ return false; } } if (!emitGetDotGeneratorInInnermostScope()) { // [stack] # if needsIteratorResult // [stack] ITEROBJ .GENERATOR // [stack] # else // [stack] RESULT .GENERATOR return false; } if (!emitYieldOp(JSOp::Yield)) { // [stack] YIELDRESULT GENERATOR RESUMEKIND return false; } if (!emit1(JSOp::CheckResumeKind)) { // [stack] YIELDRESULT return false; } return true; } bool BytecodeEmitter::emitAwaitInInnermostScope(UnaryNode* awaitNode) { MOZ_ASSERT(sc->isSuspendableContext()); MOZ_ASSERT(awaitNode->isKind(ParseNodeKind::AwaitExpr)); if (!emitTree(awaitNode->kid())) { return false; } return emitAwaitInInnermostScope(); } bool BytecodeEmitter::emitAwaitInScope(EmitterScope& currentScope) { if (!emit1(JSOp::CanSkipAwait)) { // [stack] VALUE CANSKIP return false; } if (!emit1(JSOp::MaybeExtractAwaitValue)) { // [stack] VALUE_OR_RESOLVED CANSKIP return false; } InternalIfEmitter ifCanSkip(this); if (!ifCanSkip.emitThen(IfEmitter::ConditionKind::Negative)) { // [stack] VALUE_OR_RESOLVED return false; } if (sc->asSuspendableContext()->needsPromiseResult()) { if (!emitGetDotGeneratorInScope(currentScope)) { // [stack] VALUE GENERATOR return false; } if (!emit1(JSOp::AsyncAwait)) { // [stack] PROMISE return false; } } if (!emitGetDotGeneratorInScope(currentScope)) { // [stack] VALUE|PROMISE GENERATOR return false; } if (!emitYieldOp(JSOp::Await)) { // [stack] RESOLVED GENERATOR RESUMEKIND return false; } if (!emit1(JSOp::CheckResumeKind)) { // [stack] RESOLVED return false; } if (!ifCanSkip.emitEnd()) { return false; } MOZ_ASSERT(ifCanSkip.popped() == 0); return true; } // ES2019 draft rev 49b781ec80117b60f73327ef3054703a3111e40c // 14.4.14 Runtime Semantics: Evaluation // YieldExpression : yield* AssignmentExpression bool BytecodeEmitter::emitYieldStar(ParseNode* iter) { MOZ_ASSERT(sc->isSuspendableContext()); MOZ_ASSERT(sc->asSuspendableContext()->isGenerator()); // Step 1. IteratorKind iterKind = sc->asSuspendableContext()->isAsync() ? IteratorKind::Async : IteratorKind::Sync; bool needsIteratorResult = sc->asSuspendableContext()->needsIteratorResult(); // Steps 2-5. if (!emitTree(iter)) { // [stack] ITERABLE return false; } if (iterKind == IteratorKind::Async) { if (!emitAsyncIterator()) { // [stack] NEXT ITER return false; } } else { if (!emitIterator()) { // [stack] NEXT ITER return false; } } // Step 6. // Start with NormalCompletion(undefined). if (!emit1(JSOp::Undefined)) { // [stack] NEXT ITER RECEIVED return false; } if (!emitPushResumeKind(GeneratorResumeKind::Next)) { // [stack] NEXT ITER RECEIVED RESUMEKIND return false; } const int32_t startDepth = bytecodeSection().stackDepth(); MOZ_ASSERT(startDepth >= 4); // Step 7 is a loop. LoopControl loopInfo(this, StatementKind::YieldStar); if (!loopInfo.emitLoopHead(this, Nothing())) { // [stack] NEXT ITER RECEIVED RESUMEKIND return false; } // Step 7.a. Check for Normal completion. if (!emit1(JSOp::Dup)) { // [stack] NEXT ITER RECEIVED RESUMEKIND RESUMEKIND return false; } if (!emitPushResumeKind(GeneratorResumeKind::Next)) { // [stack] NEXT ITER RECEIVED RESUMEKIND RESUMEKIND NORMAL return false; } if (!emit1(JSOp::StrictEq)) { // [stack] NEXT ITER RECEIVED RESUMEKIND IS_NORMAL return false; } InternalIfEmitter ifKind(this); if (!ifKind.emitThenElse()) { // [stack] NEXT ITER RECEIVED RESUMEKIND return false; } { if (!emit1(JSOp::Pop)) { // [stack] NEXT ITER RECEIVED return false; } // Step 7.a.i. // result = iter.next(received) if (!emit2(JSOp::Unpick, 2)) { // [stack] RECEIVED NEXT ITER return false; } if (!emit1(JSOp::Dup2)) { // [stack] RECEIVED NEXT ITER NEXT ITER return false; } if (!emit2(JSOp::Pick, 4)) { // [stack] NEXT ITER NEXT ITER RECEIVED return false; } if (!emitCall(JSOp::Call, 1, iter)) { // [stack] NEXT ITER RESULT return false; } // Step 7.a.ii. if (iterKind == IteratorKind::Async) { if (!emitAwaitInInnermostScope()) { // [stack] NEXT ITER RESULT return false; } } // Step 7.a.iii. if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) { // [stack] NEXT ITER RESULT return false; } // Bytecode for steps 7.a.iv-vii is emitted after the ifKind if-else because // it's shared with other branches. } // Step 7.b. Check for Throw completion. if (!ifKind.emitElseIf(Nothing())) { // [stack] NEXT ITER RECEIVED RESUMEKIND return false; } if (!emit1(JSOp::Dup)) { // [stack] NEXT ITER RECEIVED RESUMEKIND RESUMEKIND return false; } if (!emitPushResumeKind(GeneratorResumeKind::Throw)) { // [stack] NEXT ITER RECEIVED RESUMEKIND RESUMEKIND THROW return false; } if (!emit1(JSOp::StrictEq)) { // [stack] NEXT ITER RECEIVED RESUMEKIND IS_THROW return false; } if (!ifKind.emitThenElse()) { // [stack] NEXT ITER RECEIVED RESUMEKIND return false; } { if (!emit1(JSOp::Pop)) { // [stack] NEXT ITER RECEIVED return false; } // Step 7.b.i. if (!emitDupAt(1)) { // [stack] NEXT ITER RECEIVED ITER return false; } if (!emit1(JSOp::Dup)) { // [stack] NEXT ITER RECEIVED ITER ITER return false; } if (!emitAtomOp(JSOp::GetProp, cx->parserNames().throw_)) { // [stack] NEXT ITER RECEIVED ITER THROW return false; } // Step 7.b.ii. InternalIfEmitter ifThrowMethodIsNotDefined(this); if (!emitPushNotUndefinedOrNull()) { // [stack] NEXT ITER RECEIVED ITER THROW // [stack] NOT-UNDEF-OR_NULL return false; } if (!ifThrowMethodIsNotDefined.emitThenElse()) { // [stack] NEXT ITER RECEIVED ITER THROW return false; } // Step 7.b.ii.1. // RESULT = ITER.throw(EXCEPTION) if (!emit1(JSOp::Swap)) { // [stack] NEXT ITER RECEIVED THROW ITER return false; } if (!emit2(JSOp::Pick, 2)) { // [stack] NEXT ITER THROW ITER RECEIVED return false; } if (!emitCall(JSOp::Call, 1, iter)) { // [stack] NEXT ITER RESULT return false; } // Step 7.b.ii.2. if (iterKind == IteratorKind::Async) { if (!emitAwaitInInnermostScope()) { // [stack] NEXT ITER RESULT return false; } } // Step 7.b.ii.4. if (!emitCheckIsObj(CheckIsObjectKind::IteratorThrow)) { // [stack] NEXT ITER RESULT return false; } // Bytecode for steps 7.b.ii.5-8 is emitted after the ifKind if-else because // it's shared with other branches. // Step 7.b.iii. if (!ifThrowMethodIsNotDefined.emitElse()) { // [stack] NEXT ITER RECEIVED ITER THROW return false; } if (!emit1(JSOp::Pop)) { // [stack] NEXT ITER RECEIVED ITER return false; } // Steps 7.b.iii.1-4. // // If the iterator does not have a "throw" method, it calls IteratorClose // and then throws a TypeError. if (!emitIteratorCloseInInnermostScope(iterKind, CompletionKind::Normal, allowSelfHostedIter(iter))) { // [stack] NEXT ITER RECEIVED ITER return false; } // Steps 7.b.iii.5-6. if (!emit2(JSOp::ThrowMsg, uint8_t(ThrowMsgKind::IteratorNoThrow))) { // [stack] NEXT ITER RECEIVED ITER // [stack] # throw return false; } if (!ifThrowMethodIsNotDefined.emitEnd()) { return false; } } // Step 7.c. It must be a Return completion. if (!ifKind.emitElse()) { // [stack] NEXT ITER RECEIVED RESUMEKIND return false; } { if (!emit1(JSOp::Pop)) { // [stack] NEXT ITER RECEIVED return false; } // Step 7.c.i. // // Call iterator.return() for receiving a "forced return" completion from // the generator. // Step 7.c.ii. // // Get the "return" method. if (!emitDupAt(1)) { // [stack] NEXT ITER RECEIVED ITER return false; } if (!emit1(JSOp::Dup)) { // [stack] NEXT ITER RECEIVED ITER ITER return false; } if (!emitAtomOp(JSOp::GetProp, cx->parserNames().return_)) { // [stack] NEXT ITER RECEIVED ITER RET return false; } // Step 7.c.iii. // // Do nothing if "return" is undefined or null. InternalIfEmitter ifReturnMethodIsDefined(this); if (!emitPushNotUndefinedOrNull()) { // [stack] NEXT ITER RECEIVED ITER RET NOT-UNDEF-OR_NULL return false; } // Step 7.c.iv. // // Call "return" with the argument passed to Generator.prototype.return. if (!ifReturnMethodIsDefined.emitThenElse()) { // [stack] NEXT ITER RECEIVED ITER RET return false; } if (!emit1(JSOp::Swap)) { // [stack] NEXT ITER RECEIVED RET ITER return false; } if (!emit2(JSOp::Pick, 2)) { // [stack] NEXT ITER RET ITER RECEIVED return false; } if (needsIteratorResult) { if (!emitAtomOp(JSOp::GetProp, cx->parserNames().value)) { // [stack] NEXT ITER RET ITER VAL return false; } } if (!emitCall(JSOp::Call, 1)) { // [stack] NEXT ITER RESULT return false; } // Step 7.c.v. if (iterKind == IteratorKind::Async) { if (!emitAwaitInInnermostScope()) { // [stack] NEXT ITER RESULT return false; } } // Step 7.c.vi. if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) { // [stack] NEXT ITER RESULT return false; } // Check if the returned object from iterator.return() is done. If not, // continue yielding. // Steps 7.c.vii-viii. InternalIfEmitter ifReturnDone(this); if (!emit1(JSOp::Dup)) { // [stack] NEXT ITER RESULT RESULT return false; } if (!emitAtomOp(JSOp::GetProp, cx->parserNames().done)) { // [stack] NEXT ITER RESULT DONE return false; } if (!ifReturnDone.emitThenElse()) { // [stack] NEXT ITER RESULT return false; } // Step 7.c.viii.1. if (!emitAtomOp(JSOp::GetProp, cx->parserNames().value)) { // [stack] NEXT ITER VALUE return false; } if (needsIteratorResult) { if (!emitPrepareIteratorResult()) { // [stack] NEXT ITER VALUE RESULT return false; } if (!emit1(JSOp::Swap)) { // [stack] NEXT ITER RESULT VALUE return false; } if (!emitFinishIteratorResult(true)) { // [stack] NEXT ITER RESULT return false; } } if (!ifReturnDone.emitElse()) { // [stack] NEXT ITER RESULT return false; } // Jump to continue label for steps 7.c.ix-x. if (!emitJump(JSOp::Goto, &loopInfo.continues)) { // [stack] NEXT ITER RESULT return false; } if (!ifReturnDone.emitEnd()) { // [stack] NEXT ITER RESULT return false; } // Step 7.c.iii. if (!ifReturnMethodIsDefined.emitElse()) { // [stack] NEXT ITER RECEIVED ITER RET return false; } if (!emitPopN(2)) { // [stack] NEXT ITER RECEIVED return false; } if (iterKind == IteratorKind::Async) { // Step 7.c.iii.1. if (!emitAwaitInInnermostScope()) { // [stack] NEXT ITER RECEIVED return false; } } if (!ifReturnMethodIsDefined.emitEnd()) { // [stack] NEXT ITER RECEIVED return false; } // Perform a "forced generator return". // // Step 7.c.iii.2. // Step 7.c.viii.2. if (!emitGetDotGeneratorInInnermostScope()) { // [stack] NEXT ITER RESULT GENOBJ return false; } if (!emitPushResumeKind(GeneratorResumeKind::Return)) { // [stack] NEXT ITER RESULT GENOBJ RESUMEKIND return false; } if (!emit1(JSOp::CheckResumeKind)) { // [stack] NEXT ITER RESULT GENOBJ RESUMEKIND return false; } } if (!ifKind.emitEnd()) { // [stack] NEXT ITER RESULT return false; } // Shared tail for Normal/Throw completions. // // Steps 7.a.iv-v. // Steps 7.b.ii.5-6. // // [stack] NEXT ITER RESULT // if (result.done) break; if (!emit1(JSOp::Dup)) { // [stack] NEXT ITER RESULT RESULT return false; } if (!emitAtomOp(JSOp::GetProp, cx->parserNames().done)) { // [stack] NEXT ITER RESULT DONE return false; } if (!emitJump(JSOp::IfNe, &loopInfo.breaks)) { // [stack] NEXT ITER RESULT return false; } // Steps 7.a.vi-vii. // Steps 7.b.ii.7-8. // Steps 7.c.ix-x. if (!loopInfo.emitContinueTarget(this)) { // [stack] NEXT ITER RESULT return false; } if (iterKind == IteratorKind::Async) { if (!emitAtomOp(JSOp::GetProp, cx->parserNames().value)) { // [stack] NEXT ITER RESULT return false; } if (!emitAwaitInInnermostScope()) { // [stack] NEXT ITER RESULT return false; } } if (!emitGetDotGeneratorInInnermostScope()) { // [stack] NEXT ITER RESULT GENOBJ return false; } if (!emitYieldOp(JSOp::Yield)) { // [stack] NEXT ITER RVAL GENOBJ RESUMEKIND return false; } if (!emit1(JSOp::Swap)) { // [stack] NEXT ITER RVAL RESUMEKIND GENOBJ return false; } if (!emit1(JSOp::Pop)) { // [stack] NEXT ITER RVAL RESUMEKIND return false; } if (!loopInfo.emitLoopEnd(this, JSOp::Goto, TryNoteKind::Loop)) { // [stack] NEXT ITER RVAL RESUMEKIND return false; } // Jumps to this point have 3 (instead of 4) values on the stack. MOZ_ASSERT(bytecodeSection().stackDepth() == startDepth); bytecodeSection().setStackDepth(startDepth - 1); // [stack] NEXT ITER RESULT // Step 7.a.v.1. // Step 7.b.ii.6.a. // // result.value if (!emit2(JSOp::Unpick, 2)) { // [stack] RESULT NEXT ITER return false; } if (!emitPopN(2)) { // [stack] RESULT return false; } if (!emitAtomOp(JSOp::GetProp, cx->parserNames().value)) { // [stack] VALUE return false; } MOZ_ASSERT(bytecodeSection().stackDepth() == startDepth - 3); return true; } bool BytecodeEmitter::emitStatementList(ListNode* stmtList) { for (ParseNode* stmt : stmtList->contents()) { if (!emitTree(stmt)) { return false; } } return true; } bool BytecodeEmitter::emitExpressionStatement(UnaryNode* exprStmt) { MOZ_ASSERT(exprStmt->isKind(ParseNodeKind::ExpressionStmt)); /* * 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->isTopLevelContext()) { useful = wantval = !sc->noScriptRval(); } /* Don't eliminate expressions with side effects. */ ParseNode* expr = exprStmt->kid(); if (!useful) { if (!checkSideEffects(expr, &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() >= bytecodeSection().offset()) { useful = true; } } if (useful) { ValueUsage valueUsage = wantval ? ValueUsage::WantValue : ValueUsage::IgnoreValue; ExpressionStatementEmitter ese(this, valueUsage); if (!ese.prepareForExpr(Some(exprStmt->pn_pos.begin))) { return false; } if (!markStepBreakpoint()) { return false; } if (!emitTree(expr, valueUsage)) { return false; } if (!ese.emitEnd()) { return false; } } return true; } bool BytecodeEmitter::emitDeleteName(UnaryNode* deleteNode) { MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteNameExpr)); NameNode* nameExpr = &deleteNode->kid()->as(); MOZ_ASSERT(nameExpr->isKind(ParseNodeKind::Name)); return emitAtomOp(JSOp::DelName, nameExpr->atom()); } bool BytecodeEmitter::emitDeleteProperty(UnaryNode* deleteNode) { MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeletePropExpr)); PropertyAccess* propExpr = &deleteNode->kid()->as(); PropOpEmitter poe(this, PropOpEmitter::Kind::Delete, propExpr->as().isSuper() ? PropOpEmitter::ObjKind::Super : PropOpEmitter::ObjKind::Other); if (propExpr->isSuper()) { // The expression |delete super.foo;| has to evaluate |super.foo|, // which could throw if |this| hasn't yet been set by a |super(...)| // call or the super-base is not an object, before throwing a // ReferenceError for attempting to delete a super-reference. UnaryNode* base = &propExpr->expression().as(); if (!emitGetThisForSuperBase(base)) { // [stack] THIS return false; } } else { if (!poe.prepareForObj()) { return false; } if (!emitPropLHS(propExpr)) { // [stack] OBJ return false; } } if (!poe.emitDelete(propExpr->key().atom())) { // [stack] # if Super // [stack] THIS // [stack] # otherwise // [stack] SUCCEEDED return false; } return true; } bool BytecodeEmitter::emitDeleteElement(UnaryNode* deleteNode) { MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteElemExpr)); PropertyByValue* elemExpr = &deleteNode->kid()->as(); bool isSuper = elemExpr->isSuper(); DebugOnly isPrivate = elemExpr->key().isKind(ParseNodeKind::PrivateName); MOZ_ASSERT(!isPrivate); ElemOpEmitter eoe( this, ElemOpEmitter::Kind::Delete, isSuper ? ElemOpEmitter::ObjKind::Super : ElemOpEmitter::ObjKind::Other, NameVisibility::Public); // Can't delete a private name. if (isSuper) { // The expression |delete super[foo];| has to evaluate |super[foo]|, // which could throw if |this| hasn't yet been set by a |super(...)| // call, or trigger side-effects when evaluating ToPropertyKey(foo), // or also throw when the super-base is not an object, before throwing // a ReferenceError for attempting to delete a super-reference. if (!eoe.prepareForObj()) { // [stack] return false; } UnaryNode* base = &elemExpr->expression().as(); if (!emitGetThisForSuperBase(base)) { // [stack] THIS return false; } if (!eoe.prepareForKey()) { // [stack] THIS return false; } if (!emitTree(&elemExpr->key())) { // [stack] THIS KEY return false; } } else { if (!emitElemObjAndKey(elemExpr, false, eoe)) { // [stack] OBJ KEY return false; } } if (!eoe.emitDelete()) { // [stack] # if Super // [stack] THIS // [stack] # otherwise // [stack] SUCCEEDED return false; } return true; } bool BytecodeEmitter::emitDeleteExpression(UnaryNode* deleteNode) { MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteExpr)); ParseNode* expression = deleteNode->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); } bool BytecodeEmitter::emitDeleteOptionalChain(UnaryNode* deleteNode) { MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteOptionalChainExpr)); OptionalEmitter oe(this, bytecodeSection().stackDepth()); ParseNode* kid = deleteNode->kid(); switch (kid->getKind()) { case ParseNodeKind::ElemExpr: case ParseNodeKind::OptionalElemExpr: { auto* elemExpr = &kid->as(); if (!emitDeleteElementInOptChain(elemExpr, oe)) { // [stack] # If shortcircuit // [stack] UNDEFINED-OR-NULL // [stack] # otherwise // [stack] SUCCEEDED return false; } break; } case ParseNodeKind::DotExpr: case ParseNodeKind::OptionalDotExpr: { auto* propExpr = &kid->as(); if (!emitDeletePropertyInOptChain(propExpr, oe)) { // [stack] # If shortcircuit // [stack] UNDEFINED-OR-NULL // [stack] # otherwise // [stack] SUCCEEDED return false; } break; } default: MOZ_ASSERT_UNREACHABLE("Unrecognized optional delete ParseNodeKind"); } if (!oe.emitOptionalJumpTarget(JSOp::True)) { // [stack] # If shortcircuit // [stack] TRUE // [stack] # otherwise // [stack] SUCCEEDED return false; } return true; } bool BytecodeEmitter::emitDeletePropertyInOptChain(PropertyAccessBase* propExpr, OptionalEmitter& oe) { MOZ_ASSERT_IF(propExpr->is(), !propExpr->as().isSuper()); PropOpEmitter poe(this, PropOpEmitter::Kind::Delete, PropOpEmitter::ObjKind::Other); if (!poe.prepareForObj()) { // [stack] return false; } if (!emitOptionalTree(&propExpr->expression(), oe)) { // [stack] OBJ return false; } if (propExpr->isKind(ParseNodeKind::OptionalDotExpr)) { if (!oe.emitJumpShortCircuit()) { // [stack] # if Jump // [stack] UNDEFINED-OR-NULL // [stack] # otherwise // [stack] OBJ return false; } } if (!poe.emitDelete(propExpr->key().atom())) { // [stack] SUCCEEDED return false; } return true; } bool BytecodeEmitter::emitDeleteElementInOptChain(PropertyByValueBase* elemExpr, OptionalEmitter& oe) { MOZ_ASSERT_IF(elemExpr->is(), !elemExpr->as().isSuper()); ElemOpEmitter eoe(this, ElemOpEmitter::Kind::Delete, ElemOpEmitter::ObjKind::Other, NameVisibility::Public); if (!eoe.prepareForObj()) { // [stack] return false; } if (!emitOptionalTree(&elemExpr->expression(), oe)) { // [stack] OBJ return false; } if (elemExpr->isKind(ParseNodeKind::OptionalElemExpr)) { if (!oe.emitJumpShortCircuit()) { // [stack] # if Jump // [stack] UNDEFINED-OR-NULL // [stack] # otherwise // [stack] OBJ return false; } } if (!eoe.prepareForKey()) { // [stack] OBJ return false; } if (!emitTree(&elemExpr->key())) { // [stack] OBJ KEY return false; } if (!eoe.emitDelete()) { // [stack] SUCCEEDED return false; } return true; } static const char* SelfHostedCallFunctionName(const ParserAtom* name, JSContext* cx) { if (name == cx->parserNames().callFunction) { return "callFunction"; } if (name == cx->parserNames().callContentFunction) { return "callContentFunction"; } if (name == cx->parserNames().constructContentFunction) { return "constructContentFunction"; } MOZ_CRASH("Unknown self-hosted call function name"); } bool BytecodeEmitter::emitSelfHostedCallFunction(CallNode* callNode) { // 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. NameNode* calleeNode = &callNode->left()->as(); ListNode* argsList = &callNode->right()->as(); const char* errorName = SelfHostedCallFunctionName(calleeNode->name(), cx); if (argsList->count() < 2) { reportNeedMoreArgsError(calleeNode, errorName, "2", "s", argsList); return false; } JSOp callOp = callNode->callOp(); if (callOp != JSOp::Call) { reportError(callNode, JSMSG_NOT_CONSTRUCTOR, errorName); return false; } bool constructing = calleeNode->name() == cx->parserNames().constructContentFunction; ParseNode* funNode = argsList->head(); if (constructing) { callOp = JSOp::New; } else if (funNode->isName(cx->parserNames().std_Function_apply)) { callOp = JSOp::FunApply; } if (!emitTree(funNode)) { return false; } #ifdef DEBUG if (emitterMode == BytecodeEmitter::SelfHosting && calleeNode->name() == cx->parserNames().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::IsConstructing)) { 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 = argsList->count() - 2; if (!emitCall(callOp, argc)) { return false; } return true; } bool BytecodeEmitter::emitSelfHostedResumeGenerator(BinaryNode* callNode) { ListNode* argsList = &callNode->right()->as(); // Syntax: resumeGenerator(gen, value, 'next'|'throw'|'return') if (argsList->count() != 3) { reportNeedMoreArgsError(callNode, "resumeGenerator", "3", "s", argsList); return false; } ParseNode* genNode = argsList->head(); if (!emitTree(genNode)) { // [stack] GENERATOR return false; } ParseNode* valNode = genNode->pn_next; if (!emitTree(valNode)) { // [stack] GENERATOR VALUE return false; } ParseNode* kindNode = valNode->pn_next; MOZ_ASSERT(kindNode->isKind(ParseNodeKind::StringExpr)); GeneratorResumeKind kind = ParserAtomToResumeKind(cx, kindNode->as().atom()); MOZ_ASSERT(!kindNode->pn_next); if (!emitPushResumeKind(kind)) { // [stack] GENERATOR VALUE RESUMEKIND return false; } if (!emit1(JSOp::Resume)) { // [stack] RVAL return false; } return true; } bool BytecodeEmitter::emitSelfHostedForceInterpreter() { // JSScript::hasForceInterpreterOp() relies on JSOp::ForceInterpreter being // the first bytecode op in the script. MOZ_ASSERT(bytecodeSection().code().empty()); if (!emit1(JSOp::ForceInterpreter)) { return false; } if (!emit1(JSOp::Undefined)) { return false; } return true; } bool BytecodeEmitter::emitSelfHostedAllowContentIter(BinaryNode* callNode) { ListNode* argsList = &callNode->right()->as(); if (argsList->count() != 1) { reportNeedMoreArgsError(callNode, "allowContentIter", "1", "", argsList); return false; } // We're just here as a sentinel. Pass the value through directly. return emitTree(argsList->head()); } bool BytecodeEmitter::emitSelfHostedDefineDataProperty(BinaryNode* callNode) { ListNode* argsList = &callNode->right()->as(); // Only optimize when 3 arguments are passed. MOZ_ASSERT(argsList->count() == 3); ParseNode* objNode = argsList->head(); if (!emitTree(objNode)) { return false; } ParseNode* idNode = objNode->pn_next; if (!emitTree(idNode)) { return false; } ParseNode* valNode = idNode->pn_next; if (!emitTree(valNode)) { return false; } // This will leave the object on the stack instead of pushing |undefined|, // but that's fine because the self-hosted code doesn't use the return // value. return emit1(JSOp::InitElem); } bool BytecodeEmitter::emitSelfHostedHasOwn(BinaryNode* callNode) { ListNode* argsList = &callNode->right()->as(); if (argsList->count() != 2) { reportNeedMoreArgsError(callNode, "hasOwn", "2", "s", argsList); return false; } ParseNode* idNode = argsList->head(); if (!emitTree(idNode)) { return false; } ParseNode* objNode = idNode->pn_next; if (!emitTree(objNode)) { return false; } return emit1(JSOp::HasOwn); } bool BytecodeEmitter::emitSelfHostedGetPropertySuper(BinaryNode* callNode) { ListNode* argsList = &callNode->right()->as(); if (argsList->count() != 3) { reportNeedMoreArgsError(callNode, "getPropertySuper", "3", "s", argsList); return false; } ParseNode* objNode = argsList->head(); ParseNode* idNode = objNode->pn_next; ParseNode* receiverNode = idNode->pn_next; if (!emitTree(receiverNode)) { return false; } if (!emitTree(idNode)) { return false; } if (!emitTree(objNode)) { return false; } return emitElemOpBase(JSOp::GetElemSuper); } bool BytecodeEmitter::emitSelfHostedToNumeric(BinaryNode* callNode) { ListNode* argsList = &callNode->right()->as(); if (argsList->count() != 1) { reportNeedMoreArgsError(callNode, "ToNumeric", "1", "", argsList); return false; } ParseNode* argNode = argsList->head(); if (!emitTree(argNode)) { return false; } return emit1(JSOp::ToNumeric); } bool BytecodeEmitter::emitSelfHostedToString(BinaryNode* callNode) { ListNode* argsList = &callNode->right()->as(); if (argsList->count() != 1) { reportNeedMoreArgsError(callNode, "ToString", "1", "", argsList); return false; } ParseNode* argNode = argsList->head(); if (!emitTree(argNode)) { return false; } return emit1(JSOp::ToString); } bool BytecodeEmitter::emitSelfHostedGetBuiltinConstructorOrPrototype( BinaryNode* callNode, bool isConstructor) { ListNode* argsList = &callNode->right()->as(); if (argsList->count() != 1) { const char* name = isConstructor ? "GetBuiltinConstructor" : "GetBuiltinPrototype"; reportNeedMoreArgsError(callNode, name, "1", "", argsList); return false; } ParseNode* argNode = argsList->head(); if (!argNode->isKind(ParseNodeKind::StringExpr)) { reportError(callNode, JSMSG_UNEXPECTED_TYPE, "built-in name", "not a string constant"); return false; } const ParserAtom* name = argNode->as().atom(); BuiltinObjectKind kind; if (isConstructor) { kind = BuiltinConstructorForName(cx, name); } else { kind = BuiltinPrototypeForName(cx, name); } if (kind == BuiltinObjectKind::None) { reportError(callNode, JSMSG_UNEXPECTED_TYPE, "built-in name", "not a valid built-in"); return false; } return emitBuiltinObject(kind); } bool BytecodeEmitter::emitSelfHostedGetBuiltinConstructor( BinaryNode* callNode) { return emitSelfHostedGetBuiltinConstructorOrPrototype( callNode, /* isConstructor = */ true); } bool BytecodeEmitter::emitSelfHostedGetBuiltinPrototype(BinaryNode* callNode) { return emitSelfHostedGetBuiltinConstructorOrPrototype( callNode, /* isConstructor = */ false); } #ifdef DEBUG bool BytecodeEmitter::checkSelfHostedUnsafeGetReservedSlot( BinaryNode* callNode) { ListNode* argsList = &callNode->right()->as(); if (argsList->count() != 2) { reportNeedMoreArgsError(callNode, "UnsafeGetReservedSlot", "2", "", argsList); return false; } ParseNode* objNode = argsList->head(); ParseNode* slotNode = objNode->pn_next; // Ensure that the slot argument is fixed, this is required by the JITs. if (!slotNode->isKind(ParseNodeKind::NumberExpr)) { reportError(callNode, JSMSG_UNEXPECTED_TYPE, "slot argument", "not a constant"); return false; } return true; } bool BytecodeEmitter::checkSelfHostedUnsafeSetReservedSlot( BinaryNode* callNode) { ListNode* argsList = &callNode->right()->as(); if (argsList->count() != 3) { reportNeedMoreArgsError(callNode, "UnsafeSetReservedSlot", "3", "", argsList); return false; } ParseNode* objNode = argsList->head(); ParseNode* slotNode = objNode->pn_next; // Ensure that the slot argument is fixed, this is required by the JITs. if (!slotNode->isKind(ParseNodeKind::NumberExpr)) { reportError(callNode, JSMSG_UNEXPECTED_TYPE, "slot argument", "not a constant"); return false; } return true; } #endif bool BytecodeEmitter::isRestParameter(ParseNode* expr) { if (!sc->isFunctionBox()) { return false; } FunctionBox* funbox = sc->asFunctionBox(); if (!funbox->hasRest()) { return false; } if (!expr->isKind(ParseNodeKind::Name)) { return allowSelfHostedIter(expr) && isRestParameter( expr->as().right()->as().head()); } const ParserAtom* name = expr->as().name(); Maybe paramLoc = locationOfNameBoundInFunctionScope(name); if (paramLoc && lookupName(name) == *paramLoc) { FunctionScope::ParserData* bindings = funbox->functionScopeBindings(); if (bindings->slotInfo.nonPositionalFormalStart > 0) { auto index = bindings ->trailingNames[bindings->slotInfo.nonPositionalFormalStart - 1] .name(); if (index.isNull()) { // Rest parameter name can be null when the rest destructuring syntax is // used: `function f(...[]) {}`. return false; } const ParserAtom* paramName = compilationState.getParserAtomAt(cx, index); return name == paramName; } } return false; } /* A version of emitCalleeAndThis for the optional cases: * * a?.() * * a?.b() * * a?.["b"]() * * (a?.b)() * * See emitCallOrNew and emitOptionalCall for more context. */ bool BytecodeEmitter::emitOptionalCalleeAndThis(ParseNode* callee, CallNode* call, CallOrNewEmitter& cone, OptionalEmitter& oe) { if (!CheckRecursionLimit(cx)) { return false; } switch (ParseNodeKind kind = callee->getKind()) { case ParseNodeKind::Name: { const ParserAtom* nameAtom = callee->as().name(); if (!cone.emitNameCallee(nameAtom)) { // [stack] CALLEE THIS return false; } break; } case ParseNodeKind::OptionalDotExpr: { MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); OptionalPropertyAccess* prop = &callee->as(); bool isSuper = false; PropOpEmitter& poe = cone.prepareForPropCallee(isSuper); if (!emitOptionalDotExpression(prop, poe, isSuper, oe)) { // [stack] CALLEE THIS return false; } break; } case ParseNodeKind::DotExpr: { MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); PropertyAccess* prop = &callee->as(); bool isSuper = prop->isSuper(); PropOpEmitter& poe = cone.prepareForPropCallee(isSuper); if (!emitOptionalDotExpression(prop, poe, isSuper, oe)) { // [stack] CALLEE THIS return false; } break; } case ParseNodeKind::OptionalElemExpr: { OptionalPropertyByValue* elem = &callee->as(); bool isSuper = false; bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); ElemOpEmitter& eoe = cone.prepareForElemCallee(isSuper, isPrivate); if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) { // [stack] CALLEE THIS return false; } break; } case ParseNodeKind::ElemExpr: { PropertyByValue* elem = &callee->as(); bool isSuper = elem->isSuper(); bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); ElemOpEmitter& eoe = cone.prepareForElemCallee(isSuper, isPrivate); if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) { // [stack] CALLEE THIS return false; } break; } case ParseNodeKind::Function: if (!cone.prepareForFunctionCallee()) { return false; } if (!emitOptionalTree(callee, oe)) { // [stack] CALLEE return false; } break; case ParseNodeKind::OptionalChain: { return emitCalleeAndThisForOptionalChain(&callee->as(), call, cone); } default: MOZ_RELEASE_ASSERT(kind != ParseNodeKind::SuperBase); if (!cone.prepareForOtherCallee()) { return false; } if (!emitOptionalTree(callee, oe)) { // [stack] CALLEE return false; } break; } if (!cone.emitThis()) { // [stack] CALLEE THIS return false; } return true; } bool BytecodeEmitter::emitCalleeAndThis(ParseNode* callee, ParseNode* call, CallOrNewEmitter& cone) { switch (callee->getKind()) { case ParseNodeKind::Name: { const ParserAtom* nameAtom = callee->as().name(); if (!cone.emitNameCallee(nameAtom)) { // [stack] CALLEE THIS return false; } break; } case ParseNodeKind::DotExpr: { MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); PropertyAccess* prop = &callee->as(); bool isSuper = prop->isSuper(); PropOpEmitter& poe = cone.prepareForPropCallee(isSuper); if (!poe.prepareForObj()) { return false; } if (isSuper) { UnaryNode* base = &prop->expression().as(); if (!emitGetThisForSuperBase(base)) { // [stack] THIS return false; } } else { if (!emitPropLHS(prop)) { // [stack] OBJ return false; } } if (!poe.emitGet(prop->key().atom())) { // [stack] CALLEE THIS? return false; } break; } case ParseNodeKind::ElemExpr: { MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); PropertyByValue* elem = &callee->as(); bool isSuper = elem->isSuper(); bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); ElemOpEmitter& eoe = cone.prepareForElemCallee(isSuper, isPrivate); if (!emitElemObjAndKey(elem, isSuper, eoe)) { // [stack] # if Super // [stack] THIS? THIS KEY // [stack] # otherwise // [stack] OBJ? OBJ KEY return false; } if (!eoe.emitGet()) { // [stack] CALLEE? THIS return false; } break; } case ParseNodeKind::Function: if (!cone.prepareForFunctionCallee()) { return false; } if (!emitTree(callee)) { // [stack] CALLEE return false; } break; case ParseNodeKind::SuperBase: MOZ_ASSERT(call->isKind(ParseNodeKind::SuperCallExpr)); MOZ_ASSERT(parser->astGenerator().isSuperBase(callee)); if (!cone.emitSuperCallee()) { // [stack] CALLEE THIS return false; } break; case ParseNodeKind::OptionalChain: { return emitCalleeAndThisForOptionalChain(&callee->as(), &call->as(), cone); } default: if (!cone.prepareForOtherCallee()) { return false; } if (!emitTree(callee)) { return false; } break; } if (!cone.emitThis()) { // [stack] CALLEE THIS return false; } return true; } bool BytecodeEmitter::emitPipeline(ListNode* node) { MOZ_ASSERT(node->count() >= 2); if (!emitTree(node->head())) { // [stack] ARG return false; } ParseNode* callee = node->head()->pn_next; CallOrNewEmitter cone(this, JSOp::Call, CallOrNewEmitter::ArgumentsKind::Other, ValueUsage::WantValue); do { if (!emitCalleeAndThis(callee, node, cone)) { // [stack] ARG CALLEE THIS return false; } if (!emit2(JSOp::Pick, 2)) { // [stack] CALLEE THIS ARG return false; } if (!cone.emitEnd(1, Some(node->pn_pos.begin))) { // [stack] RVAL return false; } cone.reset(); } while ((callee = callee->pn_next)); return true; } ParseNode* BytecodeEmitter::getCoordNode(ParseNode* callNode, ParseNode* calleeNode, JSOp op, ListNode* argsList) { ParseNode* coordNode = callNode; if (op == JSOp::Call || op == JSOp::SpreadCall || op == JSOp::FunCall || op == JSOp::FunApply) { // Default to using the location of the `(` itself. // obj[expr]() // expression // ^ // column coord coordNode = argsList; switch (calleeNode->getKind()) { case ParseNodeKind::DotExpr: // Use the position of a property access identifier. // // obj().aprop() // expression // ^ // column coord // // Note: Because of the constant folding logic in FoldElement, // this case also applies for constant string properties. // // obj()['aprop']() // expression // ^ // column coord coordNode = &calleeNode->as().key(); break; case ParseNodeKind::Name: { // Use the start of callee name unless it is at a separator // or has no args. // // 2 + obj() // expression // ^ // column coord // if (argsList->empty() || !bytecodeSection().atSeparator(calleeNode->pn_pos.begin)) { // Use the start of callee names. coordNode = calleeNode; } break; } default: break; } } return coordNode; } bool BytecodeEmitter::emitArguments(ListNode* argsList, bool isCall, bool isSpread, CallOrNewEmitter& cone) { uint32_t argc = argsList->count(); if (argc >= ARGC_LIMIT) { reportError(argsList, isCall ? JSMSG_TOO_MANY_FUN_ARGS : JSMSG_TOO_MANY_CON_ARGS); return false; } if (!isSpread) { if (!cone.prepareForNonSpreadArguments()) { // [stack] CALLEE THIS return false; } for (ParseNode* arg : argsList->contents()) { if (!emitTree(arg)) { // [stack] CALLEE THIS ARG* return false; } } } else { if (cone.wantSpreadOperand()) { UnaryNode* spreadNode = &argsList->head()->as(); if (!emitTree(spreadNode->kid())) { // [stack] CALLEE THIS ARG0 return false; } } if (!cone.emitSpreadArgumentsTest()) { // [stack] CALLEE THIS return false; } if (!emitArray(argsList->head(), argc)) { // [stack] CALLEE THIS ARR return false; } } return true; } bool BytecodeEmitter::emitOptionalCall(CallNode* callNode, OptionalEmitter& oe, ValueUsage valueUsage) { /* * A modified version of emitCallOrNew that handles optional calls. * * These include the following: * a?.() * a.b?.() * a.["b"]?.() * (a?.b)?.() * * See CallOrNewEmitter for more context. */ ParseNode* calleeNode = callNode->left(); ListNode* argsList = &callNode->right()->as(); bool isSpread = JOF_OPTYPE(callNode->callOp()) == JOF_BYTE; JSOp op = callNode->callOp(); uint32_t argc = argsList->count(); CallOrNewEmitter cone( this, op, isSpread && (argc == 1) && isRestParameter(argsList->head()->as().kid()) ? CallOrNewEmitter::ArgumentsKind::SingleSpreadRest : CallOrNewEmitter::ArgumentsKind::Other, valueUsage); ParseNode* coordNode = getCoordNode(callNode, calleeNode, op, argsList); if (!emitOptionalCalleeAndThis(calleeNode, callNode, cone, oe)) { // [stack] CALLEE THIS return false; } if (callNode->isKind(ParseNodeKind::OptionalCallExpr)) { if (!oe.emitJumpShortCircuitForCall()) { // [stack] CALLEE THIS return false; } } if (!emitArguments(argsList, /* isCall = */ true, isSpread, cone)) { // [stack] CALLEE THIS ARGS... return false; } if (!cone.emitEnd(argc, Some(coordNode->pn_pos.begin))) { // [stack] RVAL return false; } return true; } bool BytecodeEmitter::emitCallOrNew( CallNode* callNode, ValueUsage valueUsage /* = ValueUsage::WantValue */) { /* * Emit callable invocation or operator new (constructor call) code. * First, emit code for the left operand to evaluate the callable or * constructable object expression. * * 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). */ bool isCall = callNode->isKind(ParseNodeKind::CallExpr) || callNode->isKind(ParseNodeKind::TaggedTemplateExpr); ParseNode* calleeNode = callNode->left(); ListNode* argsList = &callNode->right()->as(); bool isSpread = JOF_OPTYPE(callNode->callOp()) == JOF_BYTE; if (calleeNode->isKind(ParseNodeKind::Name) && emitterMode == BytecodeEmitter::SelfHosting && !isSpread) { // Calls to "forceInterpreter", "callFunction", // "callContentFunction", or "resumeGenerator" in self-hosted // code generate inline bytecode. const ParserName* calleeName = calleeNode->as().name(); if (calleeName == cx->parserNames().callFunction || calleeName == cx->parserNames().callContentFunction || calleeName == cx->parserNames().constructContentFunction) { return emitSelfHostedCallFunction(callNode); } if (calleeName == cx->parserNames().resumeGenerator) { return emitSelfHostedResumeGenerator(callNode); } if (calleeName == cx->parserNames().forceInterpreter) { return emitSelfHostedForceInterpreter(); } if (calleeName == cx->parserNames().allowContentIter) { return emitSelfHostedAllowContentIter(callNode); } if (calleeName == cx->parserNames().defineDataPropertyIntrinsic && argsList->count() == 3) { return emitSelfHostedDefineDataProperty(callNode); } if (calleeName == cx->parserNames().hasOwn) { return emitSelfHostedHasOwn(callNode); } if (calleeName == cx->parserNames().getPropertySuper) { return emitSelfHostedGetPropertySuper(callNode); } if (calleeName == cx->parserNames().ToNumeric) { return emitSelfHostedToNumeric(callNode); } if (calleeName == cx->parserNames().ToString) { return emitSelfHostedToString(callNode); } if (calleeName == cx->parserNames().GetBuiltinConstructor) { return emitSelfHostedGetBuiltinConstructor(callNode); } if (calleeName == cx->parserNames().GetBuiltinPrototype) { return emitSelfHostedGetBuiltinPrototype(callNode); } #ifdef DEBUG if (calleeName == cx->parserNames().UnsafeGetReservedSlot || calleeName == cx->parserNames().UnsafeGetObjectFromReservedSlot || calleeName == cx->parserNames().UnsafeGetInt32FromReservedSlot || calleeName == cx->parserNames().UnsafeGetStringFromReservedSlot || calleeName == cx->parserNames().UnsafeGetBooleanFromReservedSlot) { // Make sure that this call is correct, but don't emit any special code. if (!checkSelfHostedUnsafeGetReservedSlot(callNode)) { return false; } } if (calleeName == cx->parserNames().UnsafeSetReservedSlot) { // Make sure that this call is correct, but don't emit any special code. if (!checkSelfHostedUnsafeSetReservedSlot(callNode)) { return false; } } #endif // Fall through } JSOp op = callNode->callOp(); uint32_t argc = argsList->count(); CallOrNewEmitter cone( this, op, isSpread && (argc == 1) && isRestParameter(argsList->head()->as().kid()) ? CallOrNewEmitter::ArgumentsKind::SingleSpreadRest : CallOrNewEmitter::ArgumentsKind::Other, valueUsage); if (!emitCalleeAndThis(calleeNode, callNode, cone)) { // [stack] CALLEE THIS return false; } if (!emitArguments(argsList, isCall, isSpread, cone)) { // [stack] CALLEE THIS ARGS... return false; } ParseNode* coordNode = getCoordNode(callNode, calleeNode, op, argsList); if (!cone.emitEnd(argc, Some(coordNode->pn_pos.begin))) { // [stack] RVAL return false; } return true; } // This list must be kept in the same order in several places: // - The binary operators in ParseNode.h , // - the binary operators in TokenKind.h // - the precedence list in Parser.cpp static const JSOp ParseNodeKindToJSOp[] = { // JSOp::Nop is for pipeline operator which does not emit its own JSOp // but has highest precedence in binary operators JSOp::Nop, JSOp::Coalesce, JSOp::Or, JSOp::And, JSOp::BitOr, JSOp::BitXor, JSOp::BitAnd, JSOp::StrictEq, JSOp::Eq, JSOp::StrictNe, JSOp::Ne, JSOp::Lt, JSOp::Le, JSOp::Gt, JSOp::Ge, JSOp::Instanceof, JSOp::In, JSOp::Lsh, JSOp::Rsh, JSOp::Ursh, JSOp::Add, JSOp::Sub, JSOp::Mul, JSOp::Div, JSOp::Mod, JSOp::Pow}; static inline JSOp BinaryOpParseNodeKindToJSOp(ParseNodeKind pnk) { MOZ_ASSERT(pnk >= ParseNodeKind::BinOpFirst); MOZ_ASSERT(pnk <= ParseNodeKind::BinOpLast); int parseNodeFirst = size_t(ParseNodeKind::BinOpFirst); #ifdef DEBUG int jsopArraySize = std::size(ParseNodeKindToJSOp); int parseNodeKindListSize = size_t(ParseNodeKind::BinOpLast) - parseNodeFirst + 1; MOZ_ASSERT(jsopArraySize == parseNodeKindListSize); #endif return ParseNodeKindToJSOp[size_t(pnk) - parseNodeFirst]; } bool BytecodeEmitter::emitRightAssociative(ListNode* node) { // ** is the only right-associative operator. MOZ_ASSERT(node->isKind(ParseNodeKind::PowExpr)); // Right-associative operator chain. for (ParseNode* subexpr : node->contents()) { if (!emitTree(subexpr)) { return false; } } for (uint32_t i = 0; i < node->count() - 1; i++) { if (!emit1(JSOp::Pow)) { return false; } } return true; } bool BytecodeEmitter::emitLeftAssociative(ListNode* node) { // Left-associative operator chain. if (!emitTree(node->head())) { return false; } JSOp op = BinaryOpParseNodeKindToJSOp(node->getKind()); ParseNode* nextExpr = node->head()->pn_next; do { if (!emitTree(nextExpr)) { return false; } if (!emit1(op)) { return false; } } while ((nextExpr = nextExpr->pn_next)); return true; } /* * Special `emitTree` for Optional Chaining case. * Examples of this are `emitOptionalChain`, `emitDeleteOptionalChain` and * `emitCalleeAndThisForOptionalChain`. */ bool BytecodeEmitter::emitOptionalTree( ParseNode* pn, OptionalEmitter& oe, ValueUsage valueUsage /* = ValueUsage::WantValue */) { if (!CheckRecursionLimit(cx)) { return false; } ParseNodeKind kind = pn->getKind(); switch (kind) { case ParseNodeKind::OptionalDotExpr: { OptionalPropertyAccess* prop = &pn->as(); bool isSuper = false; PropOpEmitter poe(this, PropOpEmitter::Kind::Get, PropOpEmitter::ObjKind::Other); if (!emitOptionalDotExpression(prop, poe, isSuper, oe)) { return false; } break; } case ParseNodeKind::DotExpr: { PropertyAccess* prop = &pn->as(); bool isSuper = prop->isSuper(); PropOpEmitter poe(this, PropOpEmitter::Kind::Get, isSuper ? PropOpEmitter::ObjKind::Super : PropOpEmitter::ObjKind::Other); if (!emitOptionalDotExpression(prop, poe, isSuper, oe)) { return false; } break; } case ParseNodeKind::OptionalElemExpr: { OptionalPropertyByValue* elem = &pn->as(); bool isSuper = false; bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); ElemOpEmitter eoe( this, ElemOpEmitter::Kind::Get, ElemOpEmitter::ObjKind::Other, isPrivate ? NameVisibility::Private : NameVisibility::Public); if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) { return false; } break; } case ParseNodeKind::ElemExpr: { PropertyByValue* elem = &pn->as(); bool isSuper = elem->isSuper(); bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); ElemOpEmitter eoe( this, ElemOpEmitter::Kind::Get, isSuper ? ElemOpEmitter::ObjKind::Super : ElemOpEmitter::ObjKind::Other, isPrivate ? NameVisibility::Private : NameVisibility::Public); if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) { return false; } break; } case ParseNodeKind::CallExpr: case ParseNodeKind::OptionalCallExpr: if (!emitOptionalCall(&pn->as(), oe, valueUsage)) { return false; } break; // List of accepted ParseNodeKinds that might appear only at the beginning // of an Optional Chain. // For example, a taggedTemplateExpr node might occur if we have // `test`?.b, with `test` as the taggedTemplateExpr ParseNode. default: #ifdef DEBUG // https://tc39.es/ecma262/#sec-primary-expression bool isPrimaryExpression = kind == ParseNodeKind::ThisExpr || kind == ParseNodeKind::Name || kind == ParseNodeKind::PrivateName || kind == ParseNodeKind::NullExpr || kind == ParseNodeKind::TrueExpr || kind == ParseNodeKind::FalseExpr || kind == ParseNodeKind::NumberExpr || kind == ParseNodeKind::BigIntExpr || kind == ParseNodeKind::StringExpr || kind == ParseNodeKind::ArrayExpr || kind == ParseNodeKind::ObjectExpr || kind == ParseNodeKind::Function || kind == ParseNodeKind::ClassDecl || kind == ParseNodeKind::RegExpExpr || kind == ParseNodeKind::TemplateStringExpr || kind == ParseNodeKind::TemplateStringListExpr || kind == ParseNodeKind::RawUndefinedExpr || pn->isInParens(); // https://tc39.es/ecma262/#sec-left-hand-side-expressions bool isMemberExpression = isPrimaryExpression || kind == ParseNodeKind::TaggedTemplateExpr || kind == ParseNodeKind::NewExpr || kind == ParseNodeKind::NewTargetExpr || kind == ParseNodeKind::ImportMetaExpr; bool isCallExpression = kind == ParseNodeKind::SetThis || kind == ParseNodeKind::CallImportExpr; MOZ_ASSERT(isMemberExpression || isCallExpression, "Unknown ParseNodeKind for OptionalChain"); #endif return emitTree(pn); } return true; } // Handle the case of a call made on a OptionalChainParseNode. // For example `(a?.b)()` and `(a?.b)?.()`. bool BytecodeEmitter::emitCalleeAndThisForOptionalChain( UnaryNode* optionalChain, CallNode* callNode, CallOrNewEmitter& cone) { ParseNode* calleeNode = optionalChain->kid(); // Create a new OptionalEmitter, in order to emit the right bytecode // in isolation. OptionalEmitter oe(this, bytecodeSection().stackDepth()); if (!emitOptionalCalleeAndThis(calleeNode, callNode, cone, oe)) { // [stack] CALLEE THIS return false; } // complete the jump if necessary. This will set both the "this" value // and the "callee" value to undefined, if the callee is undefined. It // does not matter much what the this value is, the function call will // fail if it is not optional, and be set to undefined otherwise. if (!oe.emitOptionalJumpTarget(JSOp::Undefined, OptionalEmitter::Kind::Reference)) { // [stack] # If shortcircuit // [stack] UNDEFINED UNDEFINED // [stack] # otherwise // [stack] CALLEE THIS return false; } return true; } bool BytecodeEmitter::emitOptionalChain(UnaryNode* optionalChain, ValueUsage valueUsage) { ParseNode* expr = optionalChain->kid(); OptionalEmitter oe(this, bytecodeSection().stackDepth()); if (!emitOptionalTree(expr, oe, valueUsage)) { // [stack] VAL return false; } if (!oe.emitOptionalJumpTarget(JSOp::Undefined)) { // [stack] # If shortcircuit // [stack] UNDEFINED // [stack] # otherwise // [stack] VAL return false; } return true; } bool BytecodeEmitter::emitOptionalDotExpression(PropertyAccessBase* prop, PropOpEmitter& poe, bool isSuper, OptionalEmitter& oe) { if (!poe.prepareForObj()) { // [stack] return false; } if (isSuper) { UnaryNode* base = &prop->expression().as(); if (!emitGetThisForSuperBase(base)) { // [stack] OBJ return false; } } else { if (!emitOptionalTree(&prop->expression(), oe)) { // [stack] OBJ return false; } } if (prop->isKind(ParseNodeKind::OptionalDotExpr)) { MOZ_ASSERT(!isSuper); if (!oe.emitJumpShortCircuit()) { // [stack] # if Jump // [stack] UNDEFINED-OR-NULL // [stack] # otherwise // [stack] OBJ return false; } } if (!poe.emitGet(prop->key().atom())) { // [stack] PROP return false; } return true; } bool BytecodeEmitter::emitOptionalElemExpression(PropertyByValueBase* elem, ElemOpEmitter& eoe, bool isSuper, OptionalEmitter& oe) { if (!eoe.prepareForObj()) { // [stack] return false; } if (isSuper) { UnaryNode* base = &elem->expression().as(); if (!emitGetThisForSuperBase(base)) { // [stack] OBJ return false; } } else { if (!emitOptionalTree(&elem->expression(), oe)) { // [stack] OBJ return false; } } if (elem->isKind(ParseNodeKind::OptionalElemExpr)) { MOZ_ASSERT(!isSuper); if (!oe.emitJumpShortCircuit()) { // [stack] # if Jump // [stack] UNDEFINED-OR-NULL // [stack] # otherwise // [stack] OBJ return false; } } if (!eoe.prepareForKey()) { // [stack] OBJ? OBJ return false; } if (!emitTree(&elem->key())) { // [stack] OBJ? OBJ KEY return false; } if (!eoe.emitGet()) { // [stack] ELEM return false; } return true; } bool BytecodeEmitter::emitShortCircuit(ListNode* node) { MOZ_ASSERT(node->isKind(ParseNodeKind::OrExpr) || node->isKind(ParseNodeKind::CoalesceExpr) || node->isKind(ParseNodeKind::AndExpr)); /* * 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* expr = node->head(); if (!emitTree(expr)) { return false; } JSOp op; switch (node->getKind()) { case ParseNodeKind::OrExpr: op = JSOp::Or; break; case ParseNodeKind::CoalesceExpr: op = JSOp::Coalesce; break; case ParseNodeKind::AndExpr: op = JSOp::And; break; default: MOZ_CRASH("Unexpected ParseNodeKind"); } JumpList jump; if (!emitJump(op, &jump)) { return false; } if (!emit1(JSOp::Pop)) { return false; } /* Emit nodes between the head and the tail. */ while ((expr = expr->pn_next)->pn_next) { if (!emitTree(expr)) { return false; } if (!emitJump(op, &jump)) { return false; } if (!emit1(JSOp::Pop)) { return false; } } if (!emitTree(expr)) { return false; } if (!emitJumpTargetAndPatch(jump)) { return false; } return true; } bool BytecodeEmitter::emitSequenceExpr( ListNode* node, ValueUsage valueUsage /* = ValueUsage::WantValue */) { for (ParseNode* child = node->head();; child = child->pn_next) { if (!updateSourceCoordNotes(child->pn_pos.begin)) { return false; } if (!emitTree(child, child->pn_next ? ValueUsage::IgnoreValue : valueUsage)) { 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(UnaryNode* incDec) { switch (incDec->kid()->getKind()) { case ParseNodeKind::DotExpr: return emitPropIncDec(incDec); case ParseNodeKind::ElemExpr: return emitElemIncDec(incDec); case ParseNodeKind::CallExpr: return emitCallIncDec(incDec); default: return emitNameIncDec(incDec); } } // 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* labeledStmt) { const ParserAtom* name = labeledStmt->label(); LabelEmitter label(this); label.emitLabel(name); if (!emitTree(labeledStmt->statement())) { return false; } if (!label.emitEnd()) { return false; } return true; } bool BytecodeEmitter::emitConditionalExpression( ConditionalExpression& conditional, ValueUsage valueUsage /* = ValueUsage::WantValue */) { CondEmitter cond(this); if (!cond.emitCond()) { return false; } ParseNode* conditionNode = &conditional.condition(); auto conditionKind = IfEmitter::ConditionKind::Positive; if (conditionNode->isKind(ParseNodeKind::NotExpr)) { conditionNode = conditionNode->as().kid(); conditionKind = IfEmitter::ConditionKind::Negative; } // NOTE: NotExpr of conditionNode may be unwrapped, and in that case the // negation is handled by conditionKind. if (!emitTree(conditionNode)) { return false; } if (!cond.emitThenElse(conditionKind)) { return false; } if (!emitTree(&conditional.thenExpression(), valueUsage)) { return false; } if (!cond.emitElse()) { return false; } if (!emitTree(&conditional.elseExpression(), valueUsage)) { return false; } if (!cond.emitEnd()) { return false; } MOZ_ASSERT(cond.pushed() == 1); return true; } // Check for an object-literal property list that can be handled by the // ObjLiteral writer. We ensure that for each `prop: value` pair, the key is a // constant name or numeric index, there is no accessor specified, and the value // can be encoded by an ObjLiteral instruction (constant number, string, // boolean, null/undefined). void BytecodeEmitter::isPropertyListObjLiteralCompatible(ListNode* obj, bool* withValues, bool* withoutValues) { bool keysOK = true; bool valuesOK = true; int propCount = 0; for (ParseNode* propdef : obj->contents()) { if (!propdef->is()) { keysOK = false; break; } propCount++; BinaryNode* prop = &propdef->as(); ParseNode* key = prop->left(); ParseNode* value = prop->right(); // Computed keys not OK (ObjLiteral data stores constant keys). if (key->isKind(ParseNodeKind::ComputedName)) { keysOK = false; break; } // BigIntExprs should have been lowered to computed names at parse // time, and so should be excluded above. MOZ_ASSERT(!key->isKind(ParseNodeKind::BigIntExpr)); // Numeric keys OK as long as they are integers and in range. if (key->isKind(ParseNodeKind::NumberExpr)) { double numValue = key->as().value(); int32_t i = 0; if (!NumberIsInt32(numValue, &i)) { keysOK = false; break; } if (!ObjLiteralWriter::arrayIndexInRange(i)) { keysOK = false; break; } } MOZ_ASSERT(key->isKind(ParseNodeKind::ObjectPropertyName) || key->isKind(ParseNodeKind::StringExpr) || key->isKind(ParseNodeKind::NumberExpr)); AccessorType accessorType = prop->is() ? prop->as().accessorType() : AccessorType::None; if (accessorType != AccessorType::None) { keysOK = false; break; } if (!isRHSObjLiteralCompatible(value)) { valuesOK = false; } } if (propCount >= PropertyTree::MAX_HEIGHT) { // JSOp::NewObject cannot accept dictionary-mode objects. keysOK = false; } *withValues = keysOK && valuesOK; *withoutValues = keysOK; } bool BytecodeEmitter::isArrayObjLiteralCompatible(ParseNode* arrayHead) { for (ParseNode* elem = arrayHead; elem; elem = elem->pn_next) { if (elem->isKind(ParseNodeKind::Spread)) { return false; } if (!isRHSObjLiteralCompatible(elem)) { return false; } } return true; } bool BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListType type) { // [stack] CTOR? OBJ size_t curFieldKeyIndex = 0; size_t curStaticFieldKeyIndex = 0; for (ParseNode* propdef : obj->contents()) { if (propdef->is()) { MOZ_ASSERT(type == ClassBody); // Only handle computing field keys here: the .initializers lambda array // is created elsewhere. ClassField* field = &propdef->as(); if (field->name().getKind() == ParseNodeKind::ComputedName) { const ParserName* fieldKeys = field->isStatic() ? cx->parserNames().dotStaticFieldKeys : cx->parserNames().dotFieldKeys; if (!emitGetName(fieldKeys)) { // [stack] CTOR? OBJ ARRAY return false; } ParseNode* nameExpr = field->name().as().kid(); if (!emitTree(nameExpr, ValueUsage::WantValue, EMIT_LINENOTE)) { // [stack] CTOR? OBJ ARRAY KEY return false; } if (!emit1(JSOp::ToPropertyKey)) { // [stack] CTOR? OBJ ARRAY KEY return false; } size_t fieldKeysIndex; if (field->isStatic()) { fieldKeysIndex = curStaticFieldKeyIndex++; } else { fieldKeysIndex = curFieldKeyIndex++; } if (!emitUint32Operand(JSOp::InitElemArray, fieldKeysIndex)) { // [stack] CTOR? OBJ ARRAY return false; } if (!emit1(JSOp::Pop)) { // [stack] CTOR? OBJ return false; } } continue; } if (propdef->is()) { // Constructors are sometimes wrapped in LexicalScopeNodes. As we // already handled emitting the constructor, skip it. MOZ_ASSERT(propdef->as().scopeBody()->isKind( ParseNodeKind::ClassMethod)); continue; } // Handle __proto__: v specially because *only* this form, and no other // involving "__proto__", performs [[Prototype]] mutation. if (propdef->isKind(ParseNodeKind::MutateProto)) { // [stack] OBJ MOZ_ASSERT(type == ObjectLiteral); if (!pe.prepareForProtoValue(Some(propdef->pn_pos.begin))) { // [stack] OBJ return false; } if (!emitTree(propdef->as().kid())) { // [stack] OBJ PROTO return false; } if (!pe.emitMutateProto()) { // [stack] OBJ return false; } continue; } if (propdef->isKind(ParseNodeKind::Spread)) { MOZ_ASSERT(type == ObjectLiteral); // [stack] OBJ if (!pe.prepareForSpreadOperand(Some(propdef->pn_pos.begin))) { // [stack] OBJ OBJ return false; } if (!emitTree(propdef->as().kid())) { // [stack] OBJ OBJ VAL return false; } if (!pe.emitSpread()) { // [stack] OBJ return false; } continue; } BinaryNode* prop = &propdef->as(); ParseNode* key = prop->left(); ParseNode* propVal = prop->right(); AccessorType accessorType; if (prop->is()) { if (!prop->as().isStatic() && key->isKind(ParseNodeKind::PrivateName)) { // Private instance methods are separately added to the .initializers // array. continue; } accessorType = prop->as().accessorType(); } else if (prop->is()) { accessorType = prop->as().accessorType(); } else { accessorType = AccessorType::None; } auto emitValue = [this, &key, &propVal, accessorType, &pe]() { // [stack] CTOR? OBJ CTOR? KEY? if (propVal->isDirectRHSAnonFunction()) { if (key->isKind(ParseNodeKind::NumberExpr)) { MOZ_ASSERT(accessorType == AccessorType::None); const ParserAtom* keyAtom = key->as().toAtom( cx, compilationState.parserAtoms); if (!keyAtom) { return false; } if (!emitAnonymousFunctionWithName(propVal, keyAtom)) { // [stack] CTOR? OBJ CTOR? KEY VAL return false; } } else if (key->isKind(ParseNodeKind::ObjectPropertyName) || key->isKind(ParseNodeKind::StringExpr)) { MOZ_ASSERT(accessorType == AccessorType::None); const ParserAtom* keyAtom = key->as().atom(); if (!emitAnonymousFunctionWithName(propVal, keyAtom)) { // [stack] CTOR? OBJ CTOR? VAL return false; } } else { MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName) || key->isKind(ParseNodeKind::BigIntExpr)); // If a function name is a BigInt, then treat it as a computed name // equivalent to `[ToString(B)]` for some big-int value `B`. if (key->isKind(ParseNodeKind::BigIntExpr)) { MOZ_ASSERT(accessorType == AccessorType::None); if (!emit1(JSOp::ToString)) { return false; } } FunctionPrefixKind prefix = accessorType == AccessorType::None ? FunctionPrefixKind::None : accessorType == AccessorType::Getter ? FunctionPrefixKind::Get : FunctionPrefixKind::Set; if (!emitAnonymousFunctionWithComputedName(propVal, prefix)) { // [stack] CTOR? OBJ CTOR? KEY VAL return false; } } } else { if (!emitTree(propVal)) { // [stack] CTOR? OBJ CTOR? KEY? VAL return false; } } if (propVal->is() && propVal->as().funbox()->needsHomeObject()) { if (!pe.emitInitHomeObject()) { // [stack] CTOR? OBJ CTOR? KEY? FUN return false; } } return true; }; PropertyEmitter::Kind kind = (type == ClassBody && propdef->as().isStatic()) ? PropertyEmitter::Kind::Static : PropertyEmitter::Kind::Prototype; if (key->isKind(ParseNodeKind::NumberExpr) || key->isKind(ParseNodeKind::BigIntExpr)) { // [stack] CTOR? OBJ if (!pe.prepareForIndexPropKey(Some(propdef->pn_pos.begin), kind)) { // [stack] CTOR? OBJ CTOR? return false; } if (key->isKind(ParseNodeKind::NumberExpr)) { if (!emitNumberOp(key->as().value())) { // [stack] CTOR? OBJ CTOR? KEY return false; } } else { if (!emitBigIntOp(&key->as())) { // [stack] CTOR? OBJ CTOR? KEY return false; } } if (!pe.prepareForIndexPropValue()) { // [stack] CTOR? OBJ CTOR? KEY return false; } if (!emitValue()) { // [stack] CTOR? OBJ CTOR? KEY VAL return false; } if (!pe.emitInitIndexOrComputed(accessorType)) { return false; } continue; } if (key->isKind(ParseNodeKind::ObjectPropertyName) || key->isKind(ParseNodeKind::StringExpr)) { // [stack] CTOR? OBJ // emitClass took care of constructor already. if (type == ClassBody && key->as().atom() == cx->parserNames().constructor && !propdef->as().isStatic()) { continue; } if (!pe.prepareForPropValue(Some(propdef->pn_pos.begin), kind)) { // [stack] CTOR? OBJ CTOR? return false; } if (!emitValue()) { // [stack] CTOR? OBJ CTOR? VAL return false; } const ParserAtom* keyAtom = key->as().atom(); if (!pe.emitInit(accessorType, keyAtom)) { return false; } continue; } MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName) || key->isKind(ParseNodeKind::PrivateName)); // [stack] CTOR? OBJ if (!pe.prepareForComputedPropKey(Some(propdef->pn_pos.begin), kind)) { // [stack] CTOR? OBJ CTOR? return false; } if (key->is()) { if (!emitGetPrivateName(&key->as())) { // [stack] CTOR? OBJ CTOR? KEY return false; } } else { if (!emitTree(key->as().kid())) { // [stack] CTOR? OBJ CTOR? KEY return false; } } if (!pe.prepareForComputedPropValue()) { // [stack] CTOR? OBJ CTOR? KEY return false; } if (!emitValue()) { // [stack] CTOR? OBJ CTOR? KEY VAL return false; } if (!pe.emitInitIndexOrComputed(accessorType)) { return false; } if (key->isKind(ParseNodeKind::PrivateName) && key->as().privateNameKind() == PrivateNameKind::Setter) { if (!emitGetPrivateName(&key->as())) { // [stack] THIS NAME return false; } if (!emitAtomOp(JSOp::GetIntrinsic, cx->parserNames().NoPrivateGetter)) { // [stack] THIS NAME FUN return false; } if (!emit1(JSOp::InitHiddenElemGetter)) { // [stack] THIS return false; } } } return true; } bool BytecodeEmitter::emitPropertyListObjLiteral(ListNode* obj, ObjLiteralFlags flags, bool useObjLiteralValues) { ObjLiteralWriter writer; writer.beginObject(flags); bool singleton = flags.contains(ObjLiteralFlag::Singleton); for (ParseNode* propdef : obj->contents()) { BinaryNode* prop = &propdef->as(); ParseNode* key = prop->left(); if (key->is()) { writer.setPropName(key->as().atom()); } else { double numValue = key->as().value(); int32_t i = 0; DebugOnly numIsInt = NumberIsInt32(numValue, &i); // checked previously. MOZ_ASSERT(numIsInt); MOZ_ASSERT( ObjLiteralWriter::arrayIndexInRange(i)); // checked previously. writer.setPropIndex(i); } if (useObjLiteralValues) { MOZ_ASSERT(singleton); ParseNode* value = prop->right(); if (!emitObjLiteralValue(writer, value)) { return false; } } else { if (!writer.propWithUndefinedValue(cx)) { return false; } } } GCThingIndex index; if (!addObjLiteralData(writer, &index)) { return false; } // JSOp::Object may only be used by (top-level) run-once scripts. MOZ_ASSERT_IF(singleton, stencil.input.options.isRunOnce); JSOp op = singleton ? JSOp::Object : JSOp::NewObject; if (!emitGCIndexOp(op, index)) { // [stack] OBJ return false; } return true; } bool BytecodeEmitter::emitDestructuringRestExclusionSetObjLiteral( ListNode* pattern) { ObjLiteralFlags flags; ObjLiteralWriter writer; writer.beginObject(flags); for (ParseNode* member : pattern->contents()) { if (member->isKind(ParseNodeKind::Spread)) { MOZ_ASSERT(!member->pn_next, "unexpected trailing element after spread"); break; } const ParserAtom* atom = nullptr; if (member->isKind(ParseNodeKind::MutateProto)) { atom = cx->parserNames().proto; } else { ParseNode* key = member->as().left(); atom = key->as().atom(); } writer.setPropName(atom); if (!writer.propWithUndefinedValue(cx)) { return false; } } GCThingIndex index; if (!addObjLiteralData(writer, &index)) { return false; } // If we want to squeeze out a little more performance, we could switch to the // `JSOp::Object` opcode, because the exclusion set object is never exposed to // the user, so it's safe to bake the object into the bytecode. But first we // need to make sure this won't interfere with XDR, cf. the // `RealmBehaviors::singletonsAsTemplates_` flag. if (!emitGCIndexOp(JSOp::NewObject, index)) { // [stack] OBJ return false; } return true; } bool BytecodeEmitter::emitObjLiteralArray(ParseNode* arrayHead) { MOZ_ASSERT(checkSingletonContext()); ObjLiteralWriter writer; ObjLiteralFlags flags(ObjLiteralFlag::Array, ObjLiteralFlag::Singleton); writer.beginObject(flags); writer.beginDenseArrayElements(); for (ParseNode* elem = arrayHead; elem; elem = elem->pn_next) { if (!emitObjLiteralValue(writer, elem)) { return false; } } GCThingIndex index; if (!addObjLiteralData(writer, &index)) { return false; } if (!emitGCIndexOp(JSOp::Object, index)) { // [stack] OBJ return false; } return true; } bool BytecodeEmitter::isRHSObjLiteralCompatible(ParseNode* value) { return value->isKind(ParseNodeKind::NumberExpr) || value->isKind(ParseNodeKind::TrueExpr) || value->isKind(ParseNodeKind::FalseExpr) || value->isKind(ParseNodeKind::NullExpr) || value->isKind(ParseNodeKind::RawUndefinedExpr) || value->isKind(ParseNodeKind::StringExpr) || value->isKind(ParseNodeKind::TemplateStringExpr); } bool BytecodeEmitter::emitObjLiteralValue(ObjLiteralWriter& writer, ParseNode* value) { MOZ_ASSERT(isRHSObjLiteralCompatible(value)); if (value->isKind(ParseNodeKind::NumberExpr)) { double numValue = value->as().value(); int32_t i = 0; js::Value v; if (NumberIsInt32(numValue, &i)) { v.setInt32(i); } else { v.setDouble(numValue); } if (!writer.propWithConstNumericValue(cx, v)) { return false; } } else if (value->isKind(ParseNodeKind::TrueExpr)) { if (!writer.propWithTrueValue(cx)) { return false; } } else if (value->isKind(ParseNodeKind::FalseExpr)) { if (!writer.propWithFalseValue(cx)) { return false; } } else if (value->isKind(ParseNodeKind::NullExpr)) { if (!writer.propWithNullValue(cx)) { return false; } } else if (value->isKind(ParseNodeKind::RawUndefinedExpr)) { if (!writer.propWithUndefinedValue(cx)) { return false; } } else if (value->isKind(ParseNodeKind::StringExpr) || value->isKind(ParseNodeKind::TemplateStringExpr)) { if (!writer.propWithAtomValue(cx, value->as().atom())) { return false; } } else { MOZ_CRASH("Unexpected parse node"); } return true; } mozilla::Maybe BytecodeEmitter::setupMemberInitializers( ListNode* classMembers, FieldPlacement placement) { bool isStatic = placement == FieldPlacement::Static; size_t numFields = std::count_if( classMembers->contents().begin(), classMembers->contents().end(), [&isStatic](ParseNode* member) { return NeedsFieldInitializer(member, isStatic); }); size_t numPrivateMethods = std::count_if( classMembers->contents().begin(), classMembers->contents().end(), [&isStatic](ParseNode* member) { return NeedsMethodInitializer(member, isStatic); }); // If there are more initializers than can be represented, return invalid. if (numFields + numPrivateMethods > MemberInitializers::MaxInitializers) { return Nothing(); } return Some(MemberInitializers(numFields + numPrivateMethods)); } // Purpose of .fieldKeys: // Computed field names (`["x"] = 2;`) must be ran at class-evaluation time, // not object construction time. The transformation to do so is roughly as // follows: // // class C { // [keyExpr] = valueExpr; // } // --> // let .fieldKeys = [keyExpr]; // let .initializers = [ // () => { // this[.fieldKeys[0]] = valueExpr; // } // ]; // class C { // constructor() { // .initializers[0](); // } // } // // BytecodeEmitter::emitCreateFieldKeys does `let .fieldKeys = [...];` // BytecodeEmitter::emitPropertyList fills in the elements of the array. // See GeneralParser::fieldInitializer for the `this[.fieldKeys[0]]` part. bool BytecodeEmitter::emitCreateFieldKeys(ListNode* obj, FieldPlacement placement) { bool isStatic = placement == FieldPlacement::Static; auto isFieldWithComputedName = [isStatic](ParseNode* propdef) { return propdef->is() && propdef->as().isStatic() == isStatic && propdef->as().name().getKind() == ParseNodeKind::ComputedName; }; size_t numFieldKeys = std::count_if( obj->contents().begin(), obj->contents().end(), isFieldWithComputedName); if (numFieldKeys == 0) { return true; } const ParserName* fieldKeys = isStatic ? cx->parserNames().dotStaticFieldKeys : cx->parserNames().dotFieldKeys; NameOpEmitter noe(this, fieldKeys, NameOpEmitter::Kind::Initialize); if (!noe.prepareForRhs()) { return false; } if (!emitUint32Operand(JSOp::NewArray, numFieldKeys)) { // [stack] ARRAY return false; } if (!noe.emitAssignment()) { // [stack] ARRAY return false; } if (!emit1(JSOp::Pop)) { // [stack] return false; } return true; } bool BytecodeEmitter::emitCreateMemberInitializers(ClassEmitter& ce, ListNode* obj, FieldPlacement placement) { // FieldPlacement::Instance // [stack] HOMEOBJ HERITAGE? // // FieldPlacement::Static // [stack] CTOR HOMEOBJ mozilla::Maybe memberInitializers = setupMemberInitializers(obj, placement); if (!memberInitializers) { ReportAllocationOverflow(cx); return false; } size_t numInitializers = memberInitializers->numMemberInitializers; if (numInitializers == 0) { return true; } bool isStatic = placement == FieldPlacement::Static; if (!ce.prepareForMemberInitializers(numInitializers, isStatic)) { // [stack] HOMEOBJ HERITAGE? ARRAY // or: // [stack] CTOR HOMEOBJ ARRAY return false; } // Private methods could be used in the field initializers, // so we stamp them onto the instance first. // Static private methods aren't implemented, so skip this step // if emitting static initializers. if (!isStatic && !emitPrivateMethodInitializers(ce, obj)) { return false; } for (ParseNode* propdef : obj->contents()) { if (!propdef->is()) { continue; } if (propdef->as().isStatic() != isStatic) { continue; } FunctionNode* initializer = propdef->as().initializer(); if (!ce.prepareForMemberInitializer()) { return false; } if (!emitTree(initializer)) { // [stack] HOMEOBJ HERITAGE? ARRAY LAMBDA // or: // [stack] CTOR HOMEOBJ ARRAY LAMBDA return false; } if (initializer->funbox()->needsHomeObject()) { MOZ_ASSERT(initializer->funbox()->allowSuperProperty()); if (!ce.emitMemberInitializerHomeObject(isStatic)) { // [stack] HOMEOBJ HERITAGE? ARRAY LAMBDA // or: // [stack] CTOR HOMEOBJ ARRAY LAMBDA return false; } } if (!ce.emitStoreMemberInitializer()) { // [stack] HOMEOBJ HERITAGE? ARRAY // or: // [stack] CTOR HOMEOBJ ARRAY return false; } } if (!ce.emitMemberInitializersEnd()) { // [stack] HOMEOBJ HERITAGE? // or: // [stack] CTOR HOMEOBJ return false; } return true; } bool BytecodeEmitter::emitPrivateMethodInitializers(ClassEmitter& ce, ListNode* obj) { for (ParseNode* propdef : obj->contents()) { if (!propdef->is() || propdef->as().isStatic()) { continue; } ParseNode* propName = &propdef->as().name(); if (!propName->isKind(ParseNodeKind::PrivateName)) { continue; } if (!ce.prepareForMemberInitializer()) { // [stack] HOMEOBJ HERITAGE? ARRAY // or: // [stack] CTOR HOMEOBJ ARRAY return false; } // Synthesize a name for the lexical variable that will store the // private method body. StringBuffer storedMethodName(cx); if (!storedMethodName.append(propName->as().atom())) { return false; } AccessorType accessorType = propdef->as().accessorType(); switch (accessorType) { case AccessorType::None: if (!storedMethodName.append(".method")) { return false; } break; case AccessorType::Getter: if (!storedMethodName.append(".getter")) { return false; } break; case AccessorType::Setter: if (!storedMethodName.append(".setter")) { return false; } break; default: MOZ_CRASH("Invalid private method accessor type"); } const ParserAtom* storedMethodAtom = storedMethodName.finishParserAtom(compilationState.parserAtoms); // Emit the private method body and store it as a lexical var. if (!emitFunction(&propdef->as().method())) { // [stack] HOMEOBJ HERITAGE? ARRAY METHOD // or: // [stack] CTOR HOMEOBJ ARRAY METHOD return false; } // The private method body needs to access the home object, // and the CE knows where that is on the stack. if (!ce.emitMemberInitializerHomeObject(false)) { // [stack] HOMEOBJ HERITAGE? ARRAY METHOD // or: // [stack] CTOR HOMEOBJ ARRAY METHOD return false; } if (!emitLexicalInitialization(storedMethodAtom)) { // [stack] HOMEOBJ HERITAGE? ARRAY METHOD // or: // [stack] CTOR HOMEOBJ ARRAY METHOD return false; } if (!emit1(JSOp::Pop)) { // [stack] HOMEOBJ HERITAGE? ARRAY // or: // [stack] CTOR HOMEOBJ ARRAY return false; } if (!emitPrivateMethodInitializer(ce, propdef, propName, storedMethodAtom, accessorType)) { // [stack] HOMEOBJ HERITAGE? ARRAY // or: // [stack] CTOR HOMEOBJ ARRAY return false; } // Store the emitted initializer function into the .initializers array. if (!ce.emitStoreMemberInitializer()) { // [stack] HOMEOBJ HERITAGE? ARRAY // or: // [stack] CTOR HOMEOBJ ARRAY return false; } } return true; } bool BytecodeEmitter::emitPrivateMethodInitializer( ClassEmitter& ce, ParseNode* prop, ParseNode* propName, const ParserAtom* storedMethodAtom, AccessorType accessorType) { // Emit the synthesized initializer function. FunctionNode* funNode = prop->as().initializerIfPrivate(); MOZ_ASSERT(funNode); FunctionBox* funbox = funNode->funbox(); FunctionEmitter fe(this, funbox, funNode->syntaxKind(), FunctionEmitter::IsHoisted::No); if (!fe.prepareForNonLazy()) { // [stack] return false; } BytecodeEmitter bce2(this, parser, funbox, stencil, compilationState, emitterMode); if (!bce2.init(funNode->pn_pos)) { return false; } ListNode* paramsBody = &funNode->body()->as(); FunctionScriptEmitter fse(&bce2, funbox, Nothing(), Nothing()); if (!fse.prepareForParameters()) { // [stack] return false; } if (!bce2.emitFunctionFormalParameters(paramsBody)) { // [stack] return false; } if (!fse.prepareForBody()) { // [stack] return false; } if (!bce2.emit1(JSOp::FunctionThis)) { // [stack] THIS return false; } if (!bce2.emitGetPrivateName(&propName->as())) { // [stack] THIS NAME return false; } if (!bce2.emitGetName(storedMethodAtom)) { // [stack] THIS NAME METHOD return false; } PrivateNameKind kind = propName->as().privateNameKind(); switch (kind) { case PrivateNameKind::Method: if (!bce2.emit1(JSOp::InitLockedElem)) { // [stack] THIS return false; } break; case PrivateNameKind::Setter: if (!bce2.emit1(JSOp::InitHiddenElemSetter)) { // [stack] THIS return false; } if (!bce2.emitGetPrivateName(&propName->as())) { // [stack] THIS NAME return false; } if (!bce2.emitAtomOp(JSOp::GetIntrinsic, cx->parserNames().NoPrivateGetter)) { // [stack] THIS NAME FUN return false; } if (!bce2.emit1(JSOp::InitHiddenElemGetter)) { // [stack] THIS return false; } break; case PrivateNameKind::Getter: case PrivateNameKind::GetterSetter: if (accessorType == AccessorType::Getter) { if (!bce2.emit1(JSOp::InitHiddenElemGetter)) { // [stack] THIS return false; } } else { if (!bce2.emit1(JSOp::InitHiddenElemSetter)) { // [stack] THIS return false; } } break; default: MOZ_CRASH("Invalid op"); } // Pop remaining THIS. if (!bce2.emit1(JSOp::Pop)) { // [stack] return false; } if (!fse.emitEndBody()) { // [stack] return false; } if (!fse.intoStencil()) { return false; } if (!fe.emitNonLazyEnd()) { // [stack] HOMEOBJ HERITAGE? ARRAY FUN // or: // [stack] CTOR HOMEOBJ ARRAY FUN return false; } return true; } const MemberInitializers& BytecodeEmitter::findMemberInitializersForCall() { for (BytecodeEmitter* current = this; current; current = current->parent) { if (current->sc->isFunctionBox()) { FunctionBox* funbox = current->sc->asFunctionBox(); if (funbox->isArrow()) { continue; } // If we found a non-arrow / non-constructor we were never allowed to // expect fields in the first place. MOZ_RELEASE_ASSERT(funbox->isClassConstructor()); MOZ_ASSERT(funbox->memberInitializers().valid); return funbox->memberInitializers(); } } MOZ_RELEASE_ASSERT(compilationState.scopeContext.memberInitializers); return *compilationState.scopeContext.memberInitializers; } bool BytecodeEmitter::emitInitializeInstanceMembers() { const MemberInitializers& memberInitializers = findMemberInitializersForCall(); size_t numInitializers = memberInitializers.numMemberInitializers; if (numInitializers == 0) { return true; } if (!emitGetName(cx->parserNames().dotInitializers)) { // [stack] ARRAY return false; } for (size_t index = 0; index < numInitializers; index++) { if (index < numInitializers - 1) { // We Dup to keep the array around (it is consumed in the bytecode // below) for next iterations of this loop, except for the last // iteration, which avoids an extra Pop at the end of the loop. if (!emit1(JSOp::Dup)) { // [stack] ARRAY ARRAY return false; } } if (!emitNumberOp(index)) { // [stack] ARRAY? ARRAY INDEX return false; } if (!emit1(JSOp::GetElem)) { // [stack] ARRAY? FUNC return false; } // This is guaranteed to run after super(), so we don't need TDZ checks. if (!emitGetName(cx->parserNames().dotThis)) { // [stack] ARRAY? FUNC THIS return false; } if (!emitCall(JSOp::CallIgnoresRv, 0)) { // [stack] ARRAY? RVAL return false; } if (!emit1(JSOp::Pop)) { // [stack] ARRAY? return false; } } return true; } bool BytecodeEmitter::emitInitializeStaticFields(ListNode* classMembers) { auto isStaticField = [](ParseNode* propdef) { return propdef->is() && propdef->as().isStatic(); }; size_t numFields = std::count_if(classMembers->contents().begin(), classMembers->contents().end(), isStaticField); if (numFields == 0) { return true; } if (!emitGetName(cx->parserNames().dotStaticInitializers)) { // [stack] CTOR ARRAY return false; } for (size_t fieldIndex = 0; fieldIndex < numFields; fieldIndex++) { bool hasNext = fieldIndex < numFields - 1; if (hasNext) { // We Dup to keep the array around (it is consumed in the bytecode below) // for next iterations of this loop, except for the last iteration, which // avoids an extra Pop at the end of the loop. if (!emit1(JSOp::Dup)) { // [stack] CTOR ARRAY ARRAY return false; } } if (!emitNumberOp(fieldIndex)) { // [stack] CTOR ARRAY? ARRAY INDEX return false; } if (!emit1(JSOp::GetElem)) { // [stack] CTOR ARRAY? FUNC return false; } if (!emitDupAt(1 + hasNext)) { // [stack] CTOR ARRAY? FUNC CTOR return false; } if (!emitCall(JSOp::CallIgnoresRv, 0)) { // [stack] CTOR ARRAY? RVAL return false; } if (!emit1(JSOp::Pop)) { // [stack] CTOR ARRAY? return false; } } // Overwrite |.staticInitializers| and |.staticFieldKeys| with undefined to // avoid keeping the arrays alive indefinitely. auto clearStaticFieldSlot = [&](const ParserName* name) { NameOpEmitter noe(this, name, NameOpEmitter::Kind::SimpleAssignment); if (!noe.prepareForRhs()) { // [stack] ENV? VAL? return false; } if (!emit1(JSOp::Undefined)) { // [stack] ENV? VAL? UNDEFINED return false; } if (!noe.emitAssignment()) { // [stack] VAL return false; } if (!emit1(JSOp::Pop)) { // [stack] return false; } return true; }; if (!clearStaticFieldSlot(cx->parserNames().dotStaticInitializers)) { return false; } auto isStaticFieldWithComputedName = [](ParseNode* propdef) { return propdef->is() && propdef->as().isStatic() && propdef->as().name().getKind() == ParseNodeKind::ComputedName; }; if (std::any_of(classMembers->contents().begin(), classMembers->contents().end(), isStaticFieldWithComputedName)) { if (!clearStaticFieldSlot(cx->parserNames().dotStaticFieldKeys)) { 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(ListNode* objNode) { // Note: this method uses the ObjLiteralWriter and emits ObjLiteralStencil // objects into the GCThingList, which will evaluate them into real GC objects // during JSScript::fullyInitFromEmitter. Eventually we want OBJLITERAL to be // a real opcode, but for now, performance constraints limit us to evaluating // object literals at the end of parse, when we're allowed to allocate GC // things. // // There are four cases here, in descending order of preference: // // 1. The list of property names is "normal" and constant (no computed // values, no integer indices), the values are all simple constants // (numbers, booleans, strings), *and* this occurs in a run-once // (singleton) context. In this case, we can emit ObjLiteral // instructions to build an object with values, and the object will be // attached to a JSOp::Object opcode, whose semantics are for the backend // to simply steal the object from the script. // // 2. The list of property names is "normal" and constant as above, *and* this // occurs in a run-once (singleton) context, but some values are complex // (computed expressions, sub-objects, functions, etc.). In this case, we // can still use JSOp::Object (because singleton context), but the object // has |undefined| property values and InitProp ops are emitted to set the // values. // // 3. The list of property names is "normal" and constant as above, but this // occurs in a non-run-once (non-singleton) context. In this case, we can // use the ObjLiteral functionality to describe an *empty* object (all // values left undefined) with the right fields, which will become a // JSOp::NewObject opcode using this template object to speed the creation // of the object each time it executes (stealing its shape, etc.). The // emitted bytecode still needs InitProp ops to set the values in this // case. // // 4. Any other case. As a fallback, we use NewInit to create a new, empty // object (i.e., `{}`) and then emit bytecode to initialize its properties // one-by-one. bool useObjLiteral = false; bool useObjLiteralValues = false; isPropertyListObjLiteralCompatible(objNode, &useObjLiteralValues, &useObjLiteral); // [stack] // ObjectEmitter oe(this); if (useObjLiteral) { bool singleton = checkSingletonContext() && !objNode->hasNonConstInitializer() && objNode->head(); ObjLiteralFlags flags; if (singleton) { // Case 1 or 2. flags += ObjLiteralFlag::Singleton; } else { // Case 3. useObjLiteralValues = false; } // Use an ObjLiteral op. This will record ObjLiteral insns in the // objLiteralWriter's buffer and add a fixup to the list of ObjLiteral // fixups so that at GC-publish time at the end of parse, the full (case 1 // or 2) or template-without-values (case 3) object can be allocated and // the bytecode can be patched to refer to it. if (!emitPropertyListObjLiteral(objNode, flags, useObjLiteralValues)) { // [stack] OBJ return false; } // Put the ObjectEmitter in the right state. This tells it that there will // already be an object on the stack as a result of the (eventual) // NewObject or Object op, and prepares it to emit values if needed. if (!oe.emitObjectWithTemplateOnStack()) { // [stack] OBJ return false; } if (!useObjLiteralValues) { // Case 2 or 3 above: we still need to emit bytecode to fill in the // object's property values. if (!emitPropertyList(objNode, oe, ObjectLiteral)) { // [stack] OBJ return false; } } } else { // Case 4 above: no ObjLiteral use, just bytecode to build the object from // scratch. if (!oe.emitObject(objNode->count())) { // [stack] OBJ return false; } if (!emitPropertyList(objNode, oe, ObjectLiteral)) { // [stack] OBJ return false; } } if (!oe.emitEnd()) { // [stack] OBJ return false; } return true; } bool BytecodeEmitter::emitArrayLiteral(ListNode* array) { // Emit JSOp::Object if the array consists entirely of primitive values and we // are in a singleton context. if (checkSingletonContext() && !array->hasNonConstInitializer() && array->head() && isArrayObjLiteralCompatible(array->head())) { return emitObjLiteralArray(array->head()); } return emitArray(array->head(), array->count()); } bool BytecodeEmitter::emitArray(ParseNode* arrayHead, uint32_t count) { /* * 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::InitElemArray 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. */ uint32_t nspread = 0; for (ParseNode* elem = arrayHead; elem; elem = elem->pn_next) { if (elem->isKind(ParseNodeKind::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_NewArray, " "BaselineCompiler::emit_InitElemArray, " "and DoSetElemFallback's handling of JSOp::InitElemArray"); 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(JSOp::NewArray, count - nspread)) { // [stack] ARRAY return false; } ParseNode* elem = arrayHead; uint32_t index; bool afterSpread = false; for (index = 0; elem; index++, elem = elem->pn_next) { if (!afterSpread && elem->isKind(ParseNodeKind::Spread)) { afterSpread = true; if (!emitNumberOp(index)) { // [stack] ARRAY INDEX return false; } } if (!updateSourceCoordNotes(elem->pn_pos.begin)) { return false; } bool allowSelfHostedIterFlag = false; if (elem->isKind(ParseNodeKind::Elision)) { if (!emit1(JSOp::Hole)) { return false; } } else { ParseNode* expr; if (elem->isKind(ParseNodeKind::Spread)) { expr = elem->as().kid(); allowSelfHostedIterFlag = allowSelfHostedIter(expr); } else { expr = elem; } if (!emitTree(expr, ValueUsage::WantValue, EMIT_LINENOTE)) { // [stack] ARRAY INDEX? VALUE return false; } } if (elem->isKind(ParseNodeKind::Spread)) { if (!emitIterator()) { // [stack] ARRAY INDEX NEXT ITER return false; } if (!emit2(JSOp::Pick, 3)) { // [stack] INDEX NEXT ITER ARRAY return false; } if (!emit2(JSOp::Pick, 3)) { // [stack] NEXT ITER ARRAY INDEX return false; } if (!emitSpread(allowSelfHostedIterFlag)) { // [stack] ARRAY INDEX return false; } } else if (afterSpread) { if (!emit1(JSOp::InitElemInc)) { return false; } } else { if (!emitUint32Operand(JSOp::InitElemArray, index)) { return false; } } } MOZ_ASSERT(index == count); if (afterSpread) { if (!emit1(JSOp::Pop)) { // [stack] ARRAY return false; } } return true; } static inline JSOp UnaryOpParseNodeKindToJSOp(ParseNodeKind pnk) { switch (pnk) { case ParseNodeKind::ThrowStmt: return JSOp::Throw; case ParseNodeKind::VoidExpr: return JSOp::Void; case ParseNodeKind::NotExpr: return JSOp::Not; case ParseNodeKind::BitNotExpr: return JSOp::BitNot; case ParseNodeKind::PosExpr: return JSOp::Pos; case ParseNodeKind::NegExpr: return JSOp::Neg; default: MOZ_CRASH("unexpected unary op"); } } bool BytecodeEmitter::emitUnary(UnaryNode* unaryNode) { if (!updateSourceCoordNotes(unaryNode->pn_pos.begin)) { return false; } if (!emitTree(unaryNode->kid())) { return false; } return emit1(UnaryOpParseNodeKindToJSOp(unaryNode->getKind())); } bool BytecodeEmitter::emitTypeof(UnaryNode* typeofNode, JSOp op) { MOZ_ASSERT(op == JSOp::Typeof || op == JSOp::TypeofExpr); if (!updateSourceCoordNotes(typeofNode->pn_pos.begin)) { return false; } if (!emitTree(typeofNode->kid())) { return false; } return emit1(op); } bool BytecodeEmitter::emitFunctionFormalParameters(ListNode* paramsBody) { ParseNode* funBody = paramsBody->last(); FunctionBox* funbox = sc->asFunctionBox(); bool hasRest = funbox->hasRest(); FunctionParamsEmitter fpe(this, funbox); for (ParseNode* arg = paramsBody->head(); arg != funBody; arg = arg->pn_next) { ParseNode* bindingElement = arg; ParseNode* initializer = nullptr; if (arg->isKind(ParseNodeKind::AssignExpr) || arg->isKind(ParseNodeKind::InitExpr)) { bindingElement = arg->as().left(); initializer = arg->as().right(); } bool hasInitializer = !!initializer; bool isRest = hasRest && arg->pn_next == funBody; bool isDestructuring = !bindingElement->isKind(ParseNodeKind::Name); // Left-hand sides are either simple names or destructuring patterns. MOZ_ASSERT(bindingElement->isKind(ParseNodeKind::Name) || bindingElement->isKind(ParseNodeKind::ArrayExpr) || bindingElement->isKind(ParseNodeKind::ObjectExpr)); auto emitDefaultInitializer = [this, &initializer, &bindingElement]() { // [stack] if (!this->emitInitializer(initializer, bindingElement)) { // [stack] DEFAULT return false; } return true; }; auto emitDestructuring = [this, &bindingElement]() { // [stack] ARG if (!this->emitDestructuringOps(&bindingElement->as(), DestructuringFlavor::Declaration)) { // [stack] ARG return false; } return true; }; if (isRest) { if (isDestructuring) { if (!fpe.prepareForDestructuringRest()) { // [stack] return false; } if (!emitDestructuring()) { // [stack] return false; } if (!fpe.emitDestructuringRestEnd()) { // [stack] return false; } } else { const ParserName* paramName = bindingElement->as().name(); if (!fpe.emitRest(paramName)) { // [stack] return false; } } continue; } if (isDestructuring) { if (hasInitializer) { if (!fpe.prepareForDestructuringDefaultInitializer()) { // [stack] return false; } if (!emitDefaultInitializer()) { // [stack] return false; } if (!fpe.prepareForDestructuringDefault()) { // [stack] return false; } if (!emitDestructuring()) { // [stack] return false; } if (!fpe.emitDestructuringDefaultEnd()) { // [stack] return false; } } else { if (!fpe.prepareForDestructuring()) { // [stack] return false; } if (!emitDestructuring()) { // [stack] return false; } if (!fpe.emitDestructuringEnd()) { // [stack] return false; } } continue; } if (hasInitializer) { if (!fpe.prepareForDefault()) { // [stack] return false; } if (!emitDefaultInitializer()) { // [stack] return false; } const ParserAtom* paramName = bindingElement->as().name(); if (!fpe.emitDefaultEnd(paramName)) { // [stack] return false; } continue; } const ParserAtom* paramName = bindingElement->as().name(); if (!fpe.emitSimple(paramName)) { // [stack] return false; } } return true; } bool BytecodeEmitter::emitInitializeFunctionSpecialNames() { FunctionBox* funbox = sc->asFunctionBox(); // [stack] auto emitInitializeFunctionSpecialName = [](BytecodeEmitter* bce, const ParserName* name, JSOp op) { // A special name must be slotful, either on the frame or on the // call environment. MOZ_ASSERT(bce->lookupName(name).hasKnownSlot()); NameOpEmitter noe(bce, name, NameOpEmitter::Kind::Initialize); if (!noe.prepareForRhs()) { // [stack] return false; } if (!bce->emit1(op)) { // [stack] THIS/ARGUMENTS return false; } if (!noe.emitAssignment()) { // [stack] THIS/ARGUMENTS return false; } if (!bce->emit1(JSOp::Pop)) { // [stack] return false; } return true; }; // Do nothing if the function doesn't have an arguments binding. if (funbox->argumentsHasVarBinding()) { if (!emitInitializeFunctionSpecialName(this, cx->parserNames().arguments, JSOp::Arguments)) { // [stack] 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->functionHasThisBinding()) { if (!emitInitializeFunctionSpecialName(this, cx->parserNames().dotThis, JSOp::FunctionThis)) { return false; } } // Do nothing if the function doesn't implicitly return a promise result. if (funbox->needsPromiseResult()) { if (!emitInitializeFunctionSpecialName(this, cx->parserNames().dotGenerator, JSOp::Generator)) { // [stack] return false; } } return true; } bool BytecodeEmitter::emitLexicalInitialization(NameNode* name) { return emitLexicalInitialization(name->name()); } bool BytecodeEmitter::emitLexicalInitialization(const ParserAtom* name) { NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize); if (!noe.prepareForRhs()) { return false; } // 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. MOZ_ASSERT(noe.loc().isLexical()); MOZ_ASSERT(!noe.emittedBindOp()); if (!noe.emitAssignment()) { return false; } return true; } static MOZ_ALWAYS_INLINE ParseNode* FindConstructor(JSContext* cx, ListNode* classMethods) { for (ParseNode* classElement : classMethods->contents()) { ParseNode* unwrappedElement = classElement; if (unwrappedElement->is()) { unwrappedElement = unwrappedElement->as().scopeBody(); } if (unwrappedElement->is()) { ClassMethod& method = unwrappedElement->as(); ParseNode& methodName = method.name(); if (!method.isStatic() && (methodName.isKind(ParseNodeKind::ObjectPropertyName) || methodName.isKind(ParseNodeKind::StringExpr)) && methodName.as().atom() == cx->parserNames().constructor) { return classElement; } } } return nullptr; } template bool BytecodeEmitter::emitNewPrivateNames(ListNode* classMembers) { for (ParseNode* classElement : classMembers->contents()) { if (!classElement->is()) { continue; } ParseNode* elementName = &classElement->as().name(); if (!elementName->isKind(ParseNodeKind::PrivateName)) { continue; } const ParserAtom* privateName = elementName->as().name(); // TODO: Add a new bytecode to create private names. if (!emitAtomOp(JSOp::GetIntrinsic, cx->parserNames().NewPrivateName)) { // [stack] HERITAGE NEWPRIVATENAME return false; } // Push `undefined` as `this` parameter for call. if (!emit1(JSOp::Undefined)) { // [stack] HERITAGE NEWPRIVATENAME UNDEFINED return false; } if (!emitAtomOp(JSOp::String, privateName)) { // [stack] HERITAGE NEWPRIVATENAME UNDEFINED NAME return false; } int argc = 1; if (!emitCall(JSOp::Call, argc)) { // [stack] HERITAGE PRIVATENAME return false; } // Add a binding for #name => privatename if (!emitLexicalInitialization(privateName)) { // [stack] HERITAGE PRIVATENAME return false; } // Pop Private name off the stack. if (!emit1(JSOp::Pop)) { // [stack] HERITAGE return false; } } return true; } // This follows ES6 14.5.14 (ClassDefinitionEvaluation) and ES6 14.5.15 // (BindingClassDeclarationEvaluation). bool BytecodeEmitter::emitClass( ClassNode* classNode, ClassNameKind nameKind /* = ClassNameKind::BindingName */, const ParserAtom* nameForAnonymousClass /* = nullptr */) { MOZ_ASSERT((nameKind == ClassNameKind::InferredName) == bool(nameForAnonymousClass)); ParseNode* heritageExpression = classNode->heritage(); ListNode* classMembers = classNode->memberList(); ParseNode* constructor = FindConstructor(cx, classMembers); // If |nameKind != ClassNameKind::ComputedName| // [stack] // Else // [stack] NAME ClassEmitter ce(this); const ParserAtom* innerName = nullptr; ClassEmitter::Kind kind = ClassEmitter::Kind::Expression; if (ClassNames* names = classNode->names()) { MOZ_ASSERT(nameKind == ClassNameKind::BindingName); innerName = names->innerBinding()->name(); MOZ_ASSERT(innerName); if (names->outerBinding()) { MOZ_ASSERT(names->outerBinding()->name()); MOZ_ASSERT(names->outerBinding()->name() == innerName); kind = ClassEmitter::Kind::Declaration; } } if (LexicalScopeNode* scopeBindings = classNode->scopeBindings()) { if (!ce.emitScope(scopeBindings->scopeBindings())) { // [stack] return false; } } bool isDerived = !!heritageExpression; if (isDerived) { if (!updateSourceCoordNotes(classNode->pn_pos.begin)) { return false; } if (!markStepBreakpoint()) { return false; } if (!emitTree(heritageExpression)) { // [stack] HERITAGE return false; } } // The class body scope holds any private names. Those mustn't be visible in // the heritage expression and hence the scope must be emitted after the // heritage expression. if (LexicalScopeNode* bodyScopeBindings = classNode->bodyScopeBindings()) { if (!ce.emitBodyScope(bodyScopeBindings->scopeBindings())) { // [stack] HERITAGE return false; } if (!emitNewPrivateNames(classMembers)) { return false; } if (!emitNewPrivateNames(classMembers)) { return false; } } bool hasNameOnStack = nameKind == ClassNameKind::ComputedName; if (isDerived) { if (!ce.emitDerivedClass(innerName, nameForAnonymousClass, hasNameOnStack)) { // [stack] HERITAGE HOMEOBJ return false; } } else { if (!ce.emitClass(innerName, nameForAnonymousClass, hasNameOnStack)) { // [stack] HOMEOBJ return false; } } // Stack currently has HOMEOBJ followed by optional HERITAGE. When HERITAGE // is not used, an implicit value of %FunctionPrototype% is implied. if (constructor) { // See |Parser::classMember(...)| for the reason why |.initializers| is // created within its own scope. Maybe lse; FunctionNode* ctor; if (constructor->is()) { LexicalScopeNode* constructorScope = &constructor->as(); // The constructor scope should only contain the |.initializers| binding. MOZ_ASSERT(!constructorScope->isEmptyScope()); MOZ_ASSERT(constructorScope->scopeBindings()->slotInfo.length == 1); MOZ_ASSERT(constructorScope->scopeBindings()->trailingNames[0].name() == cx->parserNames().dotInitializers->toIndex()); auto needsInitializer = [](ParseNode* propdef) { return NeedsFieldInitializer(propdef, false) || NeedsMethodInitializer(propdef, false); }; // As an optimization omit the |.initializers| binding when no instance // fields or private methods are present. bool needsInitializers = std::any_of(classMembers->contents().begin(), classMembers->contents().end(), needsInitializer); if (needsInitializers) { lse.emplace(this); if (!lse->emitScope(ScopeKind::Lexical, constructorScope->scopeBindings())) { return false; } // Any class with field initializers will have a constructor if (!emitCreateMemberInitializers(ce, classMembers, FieldPlacement::Instance)) { return false; } } ctor = &constructorScope->scopeBody()->as().method(); } else { // The |.initializers| binding is never emitted when in self-hosting mode. MOZ_ASSERT(emitterMode == BytecodeEmitter::SelfHosting); ctor = &constructor->as().method(); } bool needsHomeObject = ctor->funbox()->needsHomeObject(); // HERITAGE is consumed inside emitFunction. if (nameKind == ClassNameKind::InferredName) { if (!setFunName(ctor->funbox(), nameForAnonymousClass)) { return false; } } if (!emitFunction(ctor, isDerived, classMembers)) { // [stack] HOMEOBJ CTOR return false; } if (lse.isSome()) { if (!lse->emitEnd()) { return false; } lse.reset(); } if (!ce.emitInitConstructor(needsHomeObject)) { // [stack] CTOR HOMEOBJ return false; } } else { if (!ce.emitInitDefaultConstructor(classNode->pn_pos.begin, classNode->pn_pos.end)) { // [stack] CTOR HOMEOBJ return false; } } if (!emitCreateFieldKeys(classMembers, FieldPlacement::Instance)) { return false; } if (!emitCreateMemberInitializers(ce, classMembers, FieldPlacement::Static)) { return false; } if (!emitCreateFieldKeys(classMembers, FieldPlacement::Static)) { return false; } if (!emitPropertyList(classMembers, ce, ClassBody)) { // [stack] CTOR HOMEOBJ return false; } if (!ce.emitBinding()) { // [stack] CTOR return false; } if (!emitInitializeStaticFields(classMembers)) { // [stack] CTOR return false; } if (!ce.emitEnd(kind)) { // [stack] # class declaration // [stack] // [stack] # class expression // [stack] CTOR return false; } return true; } bool BytecodeEmitter::emitExportDefault(BinaryNode* exportNode) { MOZ_ASSERT(exportNode->isKind(ParseNodeKind::ExportDefaultStmt)); ParseNode* valueNode = exportNode->left(); if (valueNode->isDirectRHSAnonFunction()) { MOZ_ASSERT(exportNode->right()); if (!emitAnonymousFunctionWithName(valueNode, cx->parserNames().default_)) { return false; } } else { if (!emitTree(valueNode)) { return false; } } if (ParseNode* binding = exportNode->right()) { if (!emitLexicalInitialization(&binding->as())) { return false; } if (!emit1(JSOp::Pop)) { return false; } } return true; } MOZ_NEVER_INLINE bool BytecodeEmitter::emitInstrumentationSlow( InstrumentationKind kind, const std::function& pushOperandsCallback) { MOZ_ASSERT(instrumentationKinds); if (!(instrumentationKinds & (uint32_t)kind)) { return true; } // Instrumentation is emitted in the form of a call to the realm's // instrumentation callback, guarded by a test of whether instrumentation is // currently active in the realm. The callback is invoked with the kind of // operation which is executing, the current script's instrumentation ID, // and the offset of the bytecode location after the instrumentation. Some // operation kinds have more arguments, which will be pushed by // pushOperandsCallback. unsigned initialDepth = bytecodeSection().stackDepth(); InternalIfEmitter ifEmitter(this); if (!emit1(JSOp::InstrumentationActive)) { return false; } // [stack] ACTIVE if (!ifEmitter.emitThen()) { return false; } // [stack] // Push the instrumentation callback for the current realm as the callee. if (!emit1(JSOp::InstrumentationCallback)) { return false; } // [stack] CALLBACK // Push undefined for the call's |this| value. if (!emit1(JSOp::Undefined)) { return false; } // [stack] CALLBACK UNDEFINED const ParserAtom* atom = RealmInstrumentation::getInstrumentationKindName( cx, compilationState.parserAtoms, kind); if (!atom) { return false; } if (!emitAtomOp(JSOp::String, atom)) { return false; } // [stack] CALLBACK UNDEFINED KIND if (!emit1(JSOp::InstrumentationScriptId)) { return false; } // [stack] CALLBACK UNDEFINED KIND SCRIPT // Push the offset of the bytecode location following the instrumentation. BytecodeOffset updateOffset; if (!emitN(JSOp::Int32, 4, &updateOffset)) { return false; } // [stack] CALLBACK UNDEFINED KIND SCRIPT OFFSET unsigned numPushed = bytecodeSection().stackDepth() - initialDepth; if (pushOperandsCallback && !pushOperandsCallback(numPushed)) { return false; } // [stack] CALLBACK UNDEFINED KIND SCRIPT OFFSET ...EXTRA_ARGS unsigned argc = bytecodeSection().stackDepth() - initialDepth - 2; if (!emitCall(JSOp::CallIgnoresRv, argc)) { return false; } // [stack] RV if (!emit1(JSOp::Pop)) { return false; } // [stack] if (!ifEmitter.emitEnd()) { return false; } SET_INT32(bytecodeSection().code(updateOffset), bytecodeSection().code().length()); return true; } MOZ_NEVER_INLINE bool BytecodeEmitter::emitInstrumentationForOpcodeSlow( JSOp op, GCThingIndex atomIndex) { MOZ_ASSERT(instrumentationKinds); switch (op) { case JSOp::GetProp: return emitInstrumentationSlow( InstrumentationKind::GetProperty, [=](uint32_t pushed) { return emitDupAt(pushed) && emitAtomOp(JSOp::String, atomIndex); }); case JSOp::SetProp: case JSOp::StrictSetProp: return emitInstrumentationSlow( InstrumentationKind::SetProperty, [=](uint32_t pushed) { return emitDupAt(pushed + 1) && emitAtomOp(JSOp::String, atomIndex) && emitDupAt(pushed + 2); }); case JSOp::GetElem: return emitInstrumentationSlow( InstrumentationKind::GetElement, [=](uint32_t pushed) { return emitDupAt(pushed + 1, 2); }); case JSOp::SetElem: case JSOp::StrictSetElem: return emitInstrumentationSlow( InstrumentationKind::SetElement, [=](uint32_t pushed) { return emitDupAt(pushed + 2, 3); }); default: return true; } } bool BytecodeEmitter::emitTree( ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::WantValue */, EmitLineNumberNote emitLineNote /* = EMIT_LINENOTE */) { if (!CheckRecursionLimit(cx)) { return false; } /* 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 ParseNodeKind::Function: if (!emitFunction(&pn->as())) { return false; } break; case ParseNodeKind::ParamsBody: MOZ_ASSERT_UNREACHABLE( "ParamsBody should be handled in emitFunctionScript."); break; case ParseNodeKind::IfStmt: if (!emitIf(&pn->as())) { return false; } break; case ParseNodeKind::SwitchStmt: if (!emitSwitch(&pn->as())) { return false; } break; case ParseNodeKind::WhileStmt: if (!emitWhile(&pn->as())) { return false; } break; case ParseNodeKind::DoWhileStmt: if (!emitDo(&pn->as())) { return false; } break; case ParseNodeKind::ForStmt: if (!emitFor(&pn->as())) { return false; } break; case ParseNodeKind::BreakStmt: // Ensure that the column of the 'break' is set properly. if (!updateSourceCoordNotes(pn->pn_pos.begin)) { return false; } if (!markStepBreakpoint()) { return false; } if (!emitBreak(pn->as().label())) { return false; } break; case ParseNodeKind::ContinueStmt: // Ensure that the column of the 'continue' is set properly. if (!updateSourceCoordNotes(pn->pn_pos.begin)) { return false; } if (!markStepBreakpoint()) { return false; } if (!emitContinue(pn->as().label())) { return false; } break; case ParseNodeKind::WithStmt: if (!emitWith(&pn->as())) { return false; } break; case ParseNodeKind::TryStmt: if (!emitTry(&pn->as())) { return false; } break; case ParseNodeKind::Catch: if (!emitCatch(&pn->as())) { return false; } break; case ParseNodeKind::VarStmt: if (!emitDeclarationList(&pn->as())) { return false; } break; case ParseNodeKind::ReturnStmt: if (!emitReturn(&pn->as())) { return false; } break; case ParseNodeKind::YieldStarExpr: if (!emitYieldStar(pn->as().kid())) { return false; } break; case ParseNodeKind::Generator: if (!emit1(JSOp::Generator)) { return false; } break; case ParseNodeKind::InitialYield: if (!emitInitialYield(&pn->as())) { return false; } break; case ParseNodeKind::YieldExpr: if (!emitYield(&pn->as())) { return false; } break; case ParseNodeKind::AwaitExpr: if (!emitAwaitInInnermostScope(&pn->as())) { return false; } break; case ParseNodeKind::StatementList: if (!emitStatementList(&pn->as())) { return false; } break; case ParseNodeKind::EmptyStmt: break; case ParseNodeKind::ExpressionStmt: if (!emitExpressionStatement(&pn->as())) { return false; } break; case ParseNodeKind::LabelStmt: if (!emitLabeledStatement(&pn->as())) { return false; } break; case ParseNodeKind::CommaExpr: if (!emitSequenceExpr(&pn->as(), valueUsage)) { return false; } break; case ParseNodeKind::InitExpr: case ParseNodeKind::AssignExpr: case ParseNodeKind::AddAssignExpr: case ParseNodeKind::SubAssignExpr: case ParseNodeKind::BitOrAssignExpr: case ParseNodeKind::BitXorAssignExpr: case ParseNodeKind::BitAndAssignExpr: case ParseNodeKind::LshAssignExpr: case ParseNodeKind::RshAssignExpr: case ParseNodeKind::UrshAssignExpr: case ParseNodeKind::MulAssignExpr: case ParseNodeKind::DivAssignExpr: case ParseNodeKind::ModAssignExpr: case ParseNodeKind::PowAssignExpr: { BinaryNode* assignNode = &pn->as(); if (!emitAssignmentOrInit(assignNode->getKind(), assignNode->left(), assignNode->right())) { return false; } break; } case ParseNodeKind::CoalesceAssignExpr: case ParseNodeKind::OrAssignExpr: case ParseNodeKind::AndAssignExpr: if (!emitShortCircuitAssignment(&pn->as())) { return false; } break; case ParseNodeKind::ConditionalExpr: if (!emitConditionalExpression(pn->as(), valueUsage)) { return false; } break; case ParseNodeKind::OrExpr: case ParseNodeKind::CoalesceExpr: case ParseNodeKind::AndExpr: if (!emitShortCircuit(&pn->as())) { return false; } break; case ParseNodeKind::AddExpr: case ParseNodeKind::SubExpr: case ParseNodeKind::BitOrExpr: case ParseNodeKind::BitXorExpr: case ParseNodeKind::BitAndExpr: case ParseNodeKind::StrictEqExpr: case ParseNodeKind::EqExpr: case ParseNodeKind::StrictNeExpr: case ParseNodeKind::NeExpr: case ParseNodeKind::LtExpr: case ParseNodeKind::LeExpr: case ParseNodeKind::GtExpr: case ParseNodeKind::GeExpr: case ParseNodeKind::InExpr: case ParseNodeKind::InstanceOfExpr: case ParseNodeKind::LshExpr: case ParseNodeKind::RshExpr: case ParseNodeKind::UrshExpr: case ParseNodeKind::MulExpr: case ParseNodeKind::DivExpr: case ParseNodeKind::ModExpr: if (!emitLeftAssociative(&pn->as())) { return false; } break; case ParseNodeKind::PowExpr: if (!emitRightAssociative(&pn->as())) { return false; } break; case ParseNodeKind::PipelineExpr: if (!emitPipeline(&pn->as())) { return false; } break; case ParseNodeKind::TypeOfNameExpr: if (!emitTypeof(&pn->as(), JSOp::Typeof)) { return false; } break; case ParseNodeKind::TypeOfExpr: if (!emitTypeof(&pn->as(), JSOp::TypeofExpr)) { return false; } break; case ParseNodeKind::ThrowStmt: if (!updateSourceCoordNotes(pn->pn_pos.begin)) { return false; } if (!markStepBreakpoint()) { return false; } [[fallthrough]]; case ParseNodeKind::VoidExpr: case ParseNodeKind::NotExpr: case ParseNodeKind::BitNotExpr: case ParseNodeKind::PosExpr: case ParseNodeKind::NegExpr: if (!emitUnary(&pn->as())) { return false; } break; case ParseNodeKind::PreIncrementExpr: case ParseNodeKind::PreDecrementExpr: case ParseNodeKind::PostIncrementExpr: case ParseNodeKind::PostDecrementExpr: if (!emitIncOrDec(&pn->as())) { return false; } break; case ParseNodeKind::DeleteNameExpr: if (!emitDeleteName(&pn->as())) { return false; } break; case ParseNodeKind::DeletePropExpr: if (!emitDeleteProperty(&pn->as())) { return false; } break; case ParseNodeKind::DeleteElemExpr: if (!emitDeleteElement(&pn->as())) { return false; } break; case ParseNodeKind::DeleteExpr: if (!emitDeleteExpression(&pn->as())) { return false; } break; case ParseNodeKind::DeleteOptionalChainExpr: if (!emitDeleteOptionalChain(&pn->as())) { return false; } break; case ParseNodeKind::OptionalChain: if (!emitOptionalChain(&pn->as(), valueUsage)) { return false; } break; case ParseNodeKind::DotExpr: { PropertyAccess* prop = &pn->as(); bool isSuper = prop->isSuper(); PropOpEmitter poe(this, PropOpEmitter::Kind::Get, isSuper ? PropOpEmitter::ObjKind::Super : PropOpEmitter::ObjKind::Other); if (!poe.prepareForObj()) { return false; } if (isSuper) { UnaryNode* base = &prop->expression().as(); if (!emitGetThisForSuperBase(base)) { // [stack] THIS return false; } } else { if (!emitPropLHS(prop)) { // [stack] OBJ return false; } } if (!poe.emitGet(prop->key().atom())) { // [stack] PROP return false; } break; } case ParseNodeKind::ElemExpr: { PropertyByValue* elem = &pn->as(); bool isSuper = elem->isSuper(); bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); ElemOpEmitter eoe( this, ElemOpEmitter::Kind::Get, isSuper ? ElemOpEmitter::ObjKind::Super : ElemOpEmitter::ObjKind::Other, isPrivate ? NameVisibility::Private : NameVisibility::Public); if (!emitElemObjAndKey(elem, isSuper, eoe)) { // [stack] # if Super // [stack] THIS KEY // [stack] # otherwise // [stack] OBJ KEY return false; } if (!eoe.emitGet()) { // [stack] ELEM return false; } break; } case ParseNodeKind::NewExpr: case ParseNodeKind::TaggedTemplateExpr: case ParseNodeKind::CallExpr: case ParseNodeKind::SuperCallExpr: if (!emitCallOrNew(&pn->as(), valueUsage)) { return false; } break; case ParseNodeKind::LexicalScope: if (!emitLexicalScope(&pn->as())) { return false; } break; case ParseNodeKind::ConstDecl: case ParseNodeKind::LetDecl: if (!emitDeclarationList(&pn->as())) { return false; } break; case ParseNodeKind::ImportDecl: MOZ_ASSERT(sc->isModuleContext()); break; case ParseNodeKind::ExportStmt: { MOZ_ASSERT(sc->isModuleContext()); UnaryNode* node = &pn->as(); ParseNode* decl = node->kid(); if (decl->getKind() != ParseNodeKind::ExportSpecList) { if (!emitTree(decl)) { return false; } } break; } case ParseNodeKind::ExportDefaultStmt: MOZ_ASSERT(sc->isModuleContext()); if (!emitExportDefault(&pn->as())) { return false; } break; case ParseNodeKind::ExportFromStmt: MOZ_ASSERT(sc->isModuleContext()); break; case ParseNodeKind::CallSiteObj: if (!emitCallSiteObject(&pn->as())) { return false; } break; case ParseNodeKind::ArrayExpr: if (!emitArrayLiteral(&pn->as())) { return false; } break; case ParseNodeKind::ObjectExpr: if (!emitObject(&pn->as())) { return false; } break; case ParseNodeKind::Name: if (!emitGetName(&pn->as())) { return false; } break; case ParseNodeKind::PrivateName: if (!emitGetPrivateName(&pn->as())) { return false; } break; case ParseNodeKind::TemplateStringListExpr: if (!emitTemplateString(&pn->as())) { return false; } break; case ParseNodeKind::TemplateStringExpr: case ParseNodeKind::StringExpr: if (!emitAtomOp(JSOp::String, pn->as().atom())) { return false; } break; case ParseNodeKind::NumberExpr: if (!emitNumberOp(pn->as().value())) { return false; } break; case ParseNodeKind::BigIntExpr: if (!emitBigIntOp(&pn->as())) { return false; } break; case ParseNodeKind::RegExpExpr: { GCThingIndex index; if (!perScriptData().gcThingList().append(&pn->as(), &index)) { return false; } if (!emitRegExp(index)) { return false; } break; } case ParseNodeKind::TrueExpr: if (!emit1(JSOp::True)) { return false; } break; case ParseNodeKind::FalseExpr: if (!emit1(JSOp::False)) { return false; } break; case ParseNodeKind::NullExpr: if (!emit1(JSOp::Null)) { return false; } break; case ParseNodeKind::RawUndefinedExpr: if (!emit1(JSOp::Undefined)) { return false; } break; case ParseNodeKind::ThisExpr: if (!emitThisLiteral(&pn->as())) { return false; } break; case ParseNodeKind::DebuggerStmt: if (!updateSourceCoordNotes(pn->pn_pos.begin)) { return false; } if (!markStepBreakpoint()) { return false; } if (!emit1(JSOp::Debugger)) { return false; } break; case ParseNodeKind::ClassDecl: if (!emitClass(&pn->as())) { return false; } break; case ParseNodeKind::NewTargetExpr: if (!emit1(JSOp::NewTarget)) { return false; } break; case ParseNodeKind::ImportMetaExpr: if (!emit1(JSOp::ImportMeta)) { return false; } break; case ParseNodeKind::CallImportExpr: if (!emitTree(pn->as().right()) || !emit1(JSOp::DynamicImport)) { return false; } break; case ParseNodeKind::SetThis: if (!emitSetThis(&pn->as())) { return false; } break; case ParseNodeKind::PropertyNameExpr: case ParseNodeKind::PosHolder: MOZ_FALLTHROUGH_ASSERT( "Should never try to emit ParseNodeKind::PosHolder or ::Property"); default: MOZ_ASSERT(0); } return true; } static bool AllocSrcNote(JSContext* cx, SrcNotesVector& notes, unsigned size, unsigned* index) { size_t oldLength = notes.length(); if (MOZ_UNLIKELY(oldLength + size > MaxSrcNotesLength)) { ReportAllocationOverflow(cx); return false; } if (!notes.growByUninitialized(size)) { return false; } *index = oldLength; return true; } bool BytecodeEmitter::addTryNote(TryNoteKind kind, uint32_t stackDepth, BytecodeOffset start, BytecodeOffset end) { MOZ_ASSERT(!inPrologue()); return bytecodeSection().tryNoteList().append(kind, stackDepth, start, end); } bool BytecodeEmitter::newSrcNote(SrcNoteType type, unsigned* indexp) { // Non-gettable source notes such as column/lineno and debugger should not be // emitted for prologue / self-hosted. MOZ_ASSERT_IF(skipLocationSrcNotes() || skipBreakpointSrcNotes(), type <= SrcNoteType::LastGettable); SrcNotesVector& notes = bytecodeSection().notes(); unsigned index; /* * 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. */ BytecodeOffset offset = bytecodeSection().offset(); ptrdiff_t delta = (offset - bytecodeSection().lastNoteOffset()).value(); bytecodeSection().setLastNoteOffset(offset); auto allocator = [&](unsigned size) -> SrcNote* { if (!AllocSrcNote(cx, notes, size, &index)) { return nullptr; } return ¬es[index]; }; if (!SrcNoteWriter::writeNote(type, delta, allocator)) { 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 (!newSrcNoteOperand(offset)) { return false; } if (indexp) { *indexp = index; } return true; } bool BytecodeEmitter::newSrcNoteOperand(ptrdiff_t operand) { if (!SrcNote::isRepresentableOperand(operand)) { reportError(nullptr, JSMSG_NEED_DIET, js_script_str); return false; } SrcNotesVector& notes = bytecodeSection().notes(); auto allocator = [&](unsigned size) -> SrcNote* { unsigned index; if (!AllocSrcNote(cx, notes, size, &index)) { return nullptr; } return ¬es[index]; }; return SrcNoteWriter::writeOperand(operand, allocator); } bool BytecodeEmitter::intoScriptStencil(ScriptIndex scriptIndex) { js::UniquePtr immutableScriptData = createImmutableScriptData(cx); if (!immutableScriptData) { return false; } MOZ_ASSERT(outermostScope().hasOnChain(ScopeKind::NonSyntactic) == sc->hasNonSyntacticScope()); auto& things = perScriptData().gcThingList().objects(); if (!compilationState.appendGCThings(cx, scriptIndex, things)) { return false; } // Hand over the ImmutableScriptData instance generated by BCE. auto* sharedData = SharedImmutableScriptData::createWith(cx, std::move(immutableScriptData)); if (!sharedData) { return false; } // De-duplicate the bytecode within the runtime. if (!stencil.sharedData.addAndShare(cx, scriptIndex, sharedData)) { return false; } ScriptStencil& script = compilationState.scriptData[scriptIndex]; script.setHasSharedData(); // Update flags specific to functions. if (sc->isFunctionBox()) { FunctionBox* funbox = sc->asFunctionBox(); MOZ_ASSERT(&script == &funbox->functionStencil()); funbox->copyUpdatedImmutableFlags(); MOZ_ASSERT(script.isFunction()); } else { ScriptStencilExtra& scriptExtra = compilationState.scriptExtra[scriptIndex]; sc->copyScriptFields(script); sc->copyScriptExtraFields(scriptExtra); } return true; } bool BytecodeEmitter::allowSelfHostedIter(ParseNode* parseNode) { return emitterMode == BytecodeEmitter::SelfHosting && parseNode->isKind(ParseNodeKind::CallExpr) && parseNode->as().left()->isName( cx->parserNames().allowContentIter); }