/* -*- 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/. */ #include "frontend/ElemOpEmitter.h" #include "frontend/BytecodeEmitter.h" #include "frontend/SharedContext.h" #include "vm/Opcodes.h" #include "vm/ThrowMsgKind.h" // ThrowMsgKind using namespace js; using namespace js::frontend; ElemOpEmitter::ElemOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind, NameVisibility visibility) : bce_(bce), kind_(kind), objKind_(objKind), visibility_(visibility) { // Can't access private names of super! MOZ_ASSERT_IF(visibility == NameVisibility::Private, objKind != ObjKind::Super); } bool ElemOpEmitter::prepareForObj() { MOZ_ASSERT(state_ == State::Start); #ifdef DEBUG state_ = State::Obj; #endif return true; } bool ElemOpEmitter::prepareForKey() { MOZ_ASSERT(state_ == State::Obj); if (!isSuper() && isIncDec()) { if (!bce_->emit1(JSOp::CheckObjCoercible)) { // [stack] OBJ return false; } } if (isCall()) { if (!bce_->emit1(JSOp::Dup)) { // [stack] # if Super // [stack] THIS THIS // [stack] # otherwise // [stack] OBJ OBJ return false; } } #ifdef DEBUG state_ = State::Key; #endif return true; } bool ElemOpEmitter::emitPrivateGuard() { MOZ_ASSERT(state_ == State::Key); if (!isPrivate()) { return true; } if (isPropInit()) { // [stack] OBJ KEY if (!bce_->emitCheckPrivateField(ThrowCondition::ThrowHas, ThrowMsgKind::PrivateDoubleInit)) { // [stack] OBJ KEY BOOL return false; } } else { if (!bce_->emitCheckPrivateField(ThrowCondition::ThrowHasNot, isPrivateGet() ? ThrowMsgKind::MissingPrivateOnGet : ThrowMsgKind::MissingPrivateOnSet)) { // [stack] OBJ KEY BOOL return false; } } // CheckPrivate leaves the result of the HasOwnCheck on the stack. Pop it off. return bce_->emit1(JSOp::Pop); // [stack] OBJ KEY } bool ElemOpEmitter::emitGet() { MOZ_ASSERT(state_ == State::Key); if (isIncDec() || isCompoundAssignment()) { if (!bce_->emit1(JSOp::ToPropertyKey)) { // [stack] # if Super // [stack] THIS KEY // [stack] # otherwise // [stack] OBJ KEY return false; } } if (!emitPrivateGuard()) { return false; } if (isSuper()) { if (!bce_->emitSuperBase()) { // [stack] THIS? THIS KEY SUPERBASE return false; } } if (isIncDec() || isCompoundAssignment()) { if (isSuper()) { if (!bce_->emitDupAt(2, 3)) { // [stack] THIS KEY SUPERBASE THIS KEY SUPERBASE return false; } } else { if (!bce_->emit1(JSOp::Dup2)) { // [stack] OBJ KEY OBJ KEY return false; } } } JSOp op; if (isSuper()) { op = JSOp::GetElemSuper; } else { op = JSOp::GetElem; } if (!bce_->emitElemOpBase(op, ShouldInstrument::Yes)) { // [stack] # if Get // [stack] ELEM // [stack] # if Call // [stack] THIS ELEM // [stack] # if Inc/Dec/Assignment, with Super // [stack] THIS KEY SUPERBASE ELEM // [stack] # if Inc/Dec/Assignment, other // [stack] OBJ KEY ELEM return false; } if (isCall()) { if (!bce_->emit1(JSOp::Swap)) { // [stack] ELEM THIS return false; } } #ifdef DEBUG state_ = State::Get; #endif return true; } bool ElemOpEmitter::prepareForRhs() { MOZ_ASSERT(isSimpleAssignment() || isPropInit() || isCompoundAssignment()); MOZ_ASSERT_IF(isSimpleAssignment() || isPropInit(), state_ == State::Key); MOZ_ASSERT_IF(isCompoundAssignment(), state_ == State::Get); if (isSimpleAssignment() || isPropInit()) { if (!emitPrivateGuard()) { return false; } // For CompoundAssignment, SuperBase is already emitted by emitGet. if (isSuper()) { if (!bce_->emitSuperBase()) { // [stack] THIS KEY SUPERBASE return false; } } } #ifdef DEBUG state_ = State::Rhs; #endif return true; } bool ElemOpEmitter::skipObjAndKeyAndRhs() { MOZ_ASSERT(state_ == State::Start); MOZ_ASSERT(isSimpleAssignment() || isPropInit()); #ifdef DEBUG state_ = State::Rhs; #endif return true; } bool ElemOpEmitter::emitDelete() { MOZ_ASSERT(state_ == State::Key); MOZ_ASSERT(isDelete()); MOZ_ASSERT(!isPrivate()); if (isSuper()) { if (!bce_->emit1(JSOp::ToPropertyKey)) { // [stack] THIS KEY return false; } if (!bce_->emitSuperBase()) { // [stack] THIS KEY SUPERBASE return false; } // Unconditionally throw when attempting to delete a super-reference. if (!bce_->emit2(JSOp::ThrowMsg, uint8_t(ThrowMsgKind::CantDeleteSuper))) { // [stack] THIS KEY SUPERBASE return false; } // Another wrinkle: Balance the stack from the emitter's point of view. // Execution will not reach here, as the last bytecode threw. if (!bce_->emitPopN(2)) { // [stack] THIS return false; } } else { MOZ_ASSERT(!isPrivate()); JSOp op = bce_->sc->strict() ? JSOp::StrictDelElem : JSOp::DelElem; if (!bce_->emitElemOpBase(op)) { // SUCCEEDED return false; } } #ifdef DEBUG state_ = State::Delete; #endif return true; } bool ElemOpEmitter::emitAssignment() { MOZ_ASSERT(isSimpleAssignment() || isPropInit() || isCompoundAssignment()); MOZ_ASSERT(state_ == State::Rhs); MOZ_ASSERT_IF(isPropInit(), !isSuper()); JSOp setOp = isPropInit() ? JSOp::InitElem : isSuper() ? bce_->sc->strict() ? JSOp::StrictSetElemSuper : JSOp::SetElemSuper : bce_->sc->strict() ? JSOp::StrictSetElem : JSOp::SetElem; if (!bce_->emitElemOpBase(setOp, ShouldInstrument::Yes)) { // [stack] ELEM return false; } #ifdef DEBUG state_ = State::Assignment; #endif return true; } bool ElemOpEmitter::emitIncDec() { MOZ_ASSERT(state_ == State::Key); MOZ_ASSERT(isIncDec()); if (!emitGet()) { // [stack] ... ELEM return false; } MOZ_ASSERT(state_ == State::Get); JSOp incOp = isInc() ? JSOp::Inc : JSOp::Dec; if (!bce_->emit1(JSOp::ToNumeric)) { // [stack] ... N return false; } if (isPostIncDec()) { // [stack] OBJ KEY SUPERBASE? N if (!bce_->emit1(JSOp::Dup)) { // [stack] ... N N return false; } if (!bce_->emit2(JSOp::Unpick, 3 + isSuper())) { // [stack] N OBJ KEY SUPERBASE? N return false; } } if (!bce_->emit1(incOp)) { // [stack] ... N+1 return false; } JSOp setOp = isSuper() ? (bce_->sc->strict() ? JSOp::StrictSetElemSuper : JSOp::SetElemSuper) : (bce_->sc->strict() ? JSOp::StrictSetElem : JSOp::SetElem); if (!bce_->emitElemOpBase(setOp, ShouldInstrument::Yes)) { // [stack] N? N+1 return false; } if (isPostIncDec()) { if (!bce_->emit1(JSOp::Pop)) { // [stack] N return false; } } #ifdef DEBUG state_ = State::IncDec; #endif return true; }