/* -*- 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/ObjectEmitter.h" #include "mozilla/Assertions.h" // MOZ_ASSERT #include "jsatominlines.h" // AtomToId #include "jsgcinlines.h" // GetGCObjectKind #include "jsobjinlines.h" // NewBuiltinClassInstance #include "frontend/BytecodeEmitter.h" // BytecodeEmitter #include "frontend/SharedContext.h" // SharedContext #include "frontend/SourceNotes.h" // SRC_* #include "gc/Heap.h" // AllocKind #include "js/Id.h" // jsid #include "js/Value.h" // UndefinedHandleValue #include "vm/NativeObject.h" // NativeDefineDataProperty #include "vm/ObjectGroup.h" // TenuredObject #include "vm/Runtime.h" // JSAtomState (cx->names()) using namespace js; using namespace js::frontend; using mozilla::Maybe; PropertyEmitter::PropertyEmitter(BytecodeEmitter* bce) : bce_(bce), obj_(bce->cx) {} bool PropertyEmitter::prepareForProtoValue(const Maybe& keyPos) { MOZ_ASSERT(propertyState_ == PropertyState::Start || propertyState_ == PropertyState::Init); // [stack] CTOR? OBJ CTOR? if (keyPos) { if (!bce_->updateSourceCoordNotes(*keyPos)) return false; } #ifdef DEBUG propertyState_ = PropertyState::ProtoValue; #endif return true; } bool PropertyEmitter::emitMutateProto() { MOZ_ASSERT(propertyState_ == PropertyState::ProtoValue); // [stack] OBJ PROTO if (!bce_->emit1(JSOP_MUTATEPROTO)) { // [stack] OBJ return false; } obj_ = nullptr; #ifdef DEBUG propertyState_ = PropertyState::Init; #endif return true; } bool PropertyEmitter::prepareForSpreadOperand(const Maybe& spreadPos) { MOZ_ASSERT(propertyState_ == PropertyState::Start || propertyState_ == PropertyState::Init); // [stack] OBJ if (spreadPos) { if (!bce_->updateSourceCoordNotes(*spreadPos)) return false; } if (!bce_->emit1(JSOP_DUP)) { // [stack] OBJ OBJ return false; } #ifdef DEBUG propertyState_ = PropertyState::SpreadOperand; #endif return true; } bool PropertyEmitter::emitSpread() { MOZ_ASSERT(propertyState_ == PropertyState::SpreadOperand); // [stack] OBJ OBJ VAL if (!bce_->emitCopyDataProperties(BytecodeEmitter::CopyOption::Unfiltered)) { // [stack] OBJ return false; } obj_ = nullptr; #ifdef DEBUG propertyState_ = PropertyState::Init; #endif return true; } MOZ_ALWAYS_INLINE bool PropertyEmitter::prepareForProp(const Maybe& keyPos, bool isStatic, bool isIndexOrComputed) { isStatic_ = isStatic; isIndexOrComputed_ = isIndexOrComputed; // [stack] CTOR? OBJ if (keyPos) { if (!bce_->updateSourceCoordNotes(*keyPos)) return false; } if (isStatic_) { if (!bce_->emit1(JSOP_DUP2)) { // [stack] CTOR HOMEOBJ CTOR HOMEOBJ return false; } if (!bce_->emit1(JSOP_POP)) { // [stack] CTOR HOMEOBJ CTOR return false; } } return true; } bool PropertyEmitter::prepareForPropValue(const Maybe& keyPos, Kind kind /* = Kind::Prototype */) { MOZ_ASSERT(propertyState_ == PropertyState::Start || propertyState_ == PropertyState::Init); // [stack] CTOR? OBJ if (!prepareForProp(keyPos, /* isStatic_ = */ kind == Kind::Static, /* isIndexOrComputed = */ false)) { // [stack] CTOR? OBJ CTOR? return false; } #ifdef DEBUG propertyState_ = PropertyState::PropValue; #endif return true; } bool PropertyEmitter::prepareForIndexPropKey(const Maybe& keyPos, Kind kind /* = Kind::Prototype */) { MOZ_ASSERT(propertyState_ == PropertyState::Start || propertyState_ == PropertyState::Init); // [stack] CTOR? OBJ obj_ = nullptr; if (!prepareForProp(keyPos, /* isStatic_ = */ kind == Kind::Static, /* isIndexOrComputed = */ true)) { // [stack] CTOR? OBJ CTOR? return false; } #ifdef DEBUG propertyState_ = PropertyState::IndexKey; #endif return true; } bool PropertyEmitter::prepareForIndexPropValue() { MOZ_ASSERT(propertyState_ == PropertyState::IndexKey); // [stack] CTOR? OBJ CTOR? KEY #ifdef DEBUG propertyState_ = PropertyState::IndexValue; #endif return true; } bool PropertyEmitter::prepareForComputedPropKey(const Maybe& keyPos, Kind kind /* = Kind::Prototype */) { MOZ_ASSERT(propertyState_ == PropertyState::Start || propertyState_ == PropertyState::Init); // [stack] CTOR? OBJ obj_ = nullptr; if (!prepareForProp(keyPos, /* isStatic_ = */ kind == Kind::Static, /* isIndexOrComputed = */ true)) { // [stack] CTOR? OBJ CTOR? return false; } #ifdef DEBUG propertyState_ = PropertyState::ComputedKey; #endif return true; } bool PropertyEmitter::prepareForComputedPropValue() { MOZ_ASSERT(propertyState_ == PropertyState::ComputedKey); // [stack] CTOR? OBJ CTOR? KEY if (!bce_->emit1(JSOP_TOID)) { // [stack] CTOR? OBJ CTOR? KEY return false; } #ifdef DEBUG propertyState_ = PropertyState::ComputedValue; #endif return true; } bool PropertyEmitter::emitInitHomeObject(FunctionAsyncKind kind /* = FunctionAsyncKind::SyncFunction */) { MOZ_ASSERT(propertyState_ == PropertyState::PropValue || propertyState_ == PropertyState::IndexValue || propertyState_ == PropertyState::ComputedValue); // [stack] CTOR? HOMEOBJ CTOR? KEY? FUN bool isAsync = kind == FunctionAsyncKind::AsyncFunction; if (isAsync) { // [stack] CTOR? HOMEOBJ CTOR? KEY? UNWRAPPED WRAPPED if (!bce_->emit1(JSOP_SWAP)) { // [stack] CTOR? HOMEOBJ CTOR? KEY? WRAPPED UNWRAPPED return false; } } if (!bce_->emit2(JSOP_INITHOMEOBJECT, isIndexOrComputed_ + isAsync)) { // [stack] CTOR? HOMEOBJ CTOR? KEY? WRAPPED? FUN return false; } if (isAsync) { if (!bce_->emit1(JSOP_POP)) { // [stack] CTOR? HOMEOBJ CTOR? KEY? WRAPPED return false; } } #ifdef DEBUG if (propertyState_ == PropertyState::PropValue) { propertyState_ = PropertyState::InitHomeObj; } else if (propertyState_ == PropertyState::IndexValue) { propertyState_ = PropertyState::InitHomeObjForIndex; } else { propertyState_ = PropertyState::InitHomeObjForComputed; } #endif return true; } bool PropertyEmitter::emitInitProp(JS::Handle key, bool isPropertyAnonFunctionOrClass /* = false */, JS::Handle anonFunction /* = nullptr */) { return emitInit(isClass_ ? JSOP_INITHIDDENPROP : JSOP_INITPROP, key, isPropertyAnonFunctionOrClass, anonFunction); } bool PropertyEmitter::emitInitGetter(JS::Handle key) { obj_ = nullptr; return emitInit(isClass_ ? JSOP_INITHIDDENPROP_GETTER : JSOP_INITPROP_GETTER, key, false, nullptr); } bool PropertyEmitter::emitInitSetter(JS::Handle key) { obj_ = nullptr; return emitInit(isClass_ ? JSOP_INITHIDDENPROP_SETTER : JSOP_INITPROP_SETTER, key, false, nullptr); } bool PropertyEmitter::emitInitIndexProp(bool isPropertyAnonFunctionOrClass /* = false */) { return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM : JSOP_INITELEM, FunctionPrefixKind::None, isPropertyAnonFunctionOrClass); } bool PropertyEmitter::emitInitIndexGetter() { obj_ = nullptr; return emitInitIndexOrComputed( isClass_ ? JSOP_INITHIDDENELEM_GETTER : JSOP_INITELEM_GETTER, FunctionPrefixKind::Get, false); } bool PropertyEmitter::emitInitIndexSetter() { obj_ = nullptr; return emitInitIndexOrComputed( isClass_ ? JSOP_INITHIDDENELEM_SETTER : JSOP_INITELEM_SETTER, FunctionPrefixKind::Set, false); } bool PropertyEmitter::emitInitComputedProp(bool isPropertyAnonFunctionOrClass /* = false */) { return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM : JSOP_INITELEM, FunctionPrefixKind::None, isPropertyAnonFunctionOrClass); } bool PropertyEmitter::emitInitComputedGetter() { obj_ = nullptr; return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM_GETTER : JSOP_INITELEM_GETTER, FunctionPrefixKind::Get, true); } bool PropertyEmitter::emitInitComputedSetter() { obj_ = nullptr; return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM_SETTER : JSOP_INITELEM_SETTER, FunctionPrefixKind::Set, true); } bool PropertyEmitter::emitInit(JSOp op, JS::Handle key, bool isPropertyAnonFunctionOrClass, JS::Handle anonFunction) { MOZ_ASSERT(propertyState_ == PropertyState::PropValue || propertyState_ == PropertyState::InitHomeObj); MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITHIDDENPROP || op == JSOP_INITPROP_GETTER || op == JSOP_INITHIDDENPROP_GETTER || op == JSOP_INITPROP_SETTER || op == JSOP_INITHIDDENPROP_SETTER); // [stack] CTOR? OBJ CTOR? VAL uint32_t index; if (!bce_->makeAtomIndex(key, &index)) return false; if (obj_) { MOZ_ASSERT(!IsHiddenInitOp(op)); MOZ_ASSERT(!obj_->inDictionaryMode()); JS::RootedId id(bce_->cx, AtomToId(key)); if (!NativeDefineProperty(bce_->cx, obj_, id, UndefinedHandleValue, nullptr, nullptr, JSPROP_ENUMERATE)) return false; if (obj_->inDictionaryMode()) obj_ = nullptr; } if (isPropertyAnonFunctionOrClass) { MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITHIDDENPROP); if (anonFunction) { if (!bce_->setFunName(anonFunction, key)) return false; } else { // NOTE: This is setting the constructor's name of the class which is // the property value. Not of the enclosing class. if (!bce_->emitSetClassConstructorName(key)) { // [stack] CTOR? OBJ CTOR? FUN return false; } } } if (!bce_->emitIndex32(op, index)) { // [stack] CTOR? OBJ CTOR? return false; } if (!emitPopClassConstructor()) return false; #ifdef DEBUG propertyState_ = PropertyState::Init; #endif return true; } bool PropertyEmitter::emitInitIndexOrComputed(JSOp op, FunctionPrefixKind prefixKind, bool isPropertyAnonFunctionOrClass) { MOZ_ASSERT(propertyState_ == PropertyState::IndexValue || propertyState_ == PropertyState::InitHomeObjForIndex || propertyState_ == PropertyState::ComputedValue || propertyState_ == PropertyState::InitHomeObjForComputed); MOZ_ASSERT(op == JSOP_INITELEM || op == JSOP_INITHIDDENELEM || op == JSOP_INITELEM_GETTER || op == JSOP_INITHIDDENELEM_GETTER || op == JSOP_INITELEM_SETTER || op == JSOP_INITHIDDENELEM_SETTER); // [stack] CTOR? OBJ CTOR? KEY VAL if (isPropertyAnonFunctionOrClass) { if (!bce_->emitDupAt(1)) { // [stack] CTOR? OBJ CTOR? KEY FUN FUN return false; } if (!bce_->emit2(JSOP_SETFUNNAME, uint8_t(prefixKind))) { // [stack] CTOR? OBJ CTOR? KEY FUN return false; } } if (!bce_->emit1(op)) { // [stack] CTOR? OBJ CTOR? return false; } if (!emitPopClassConstructor()) return false; #ifdef DEBUG propertyState_ = PropertyState::Init; #endif return true; } bool PropertyEmitter::emitPopClassConstructor() { if (isStatic_) { // [stack] CTOR HOMEOBJ CTOR if (!bce_->emit1(JSOP_POP)) { // [stack] CTOR HOMEOBJ return false; } } return true; } ObjectEmitter::ObjectEmitter(BytecodeEmitter* bce) : PropertyEmitter(bce) {} bool ObjectEmitter::emitObject(size_t propertyCount) { MOZ_ASSERT(propertyState_ == PropertyState::Start); MOZ_ASSERT(objectState_ == ObjectState::Start); // [stack] // Emit code for {p:a, '%q':b, 2:c} that is equivalent to constructing // a new object and defining (in source order) each property on the object // (or mutating the object's [[Prototype]], in the case of __proto__). top_ = bce_->offset(); if (!bce_->emitNewInit(JSProto_Object)) { // [stack] OBJ return false; } // Try to construct the shape of the object as we go, so we can emit a // JSOP_NEWOBJECT with the final shape instead. // In the case of computed property names and indices, we cannot fix the // shape at bytecode compile time. When the shape cannot be determined, // |obj| is nulled out. // No need to do any guessing for the object kind, since we know the upper // bound of how many properties we plan to have. gc::AllocKind kind = gc::GetGCObjectKind(propertyCount); obj_ = NewBuiltinClassInstance(bce_->cx, kind, TenuredObject); if (!obj_) return false; #ifdef DEBUG objectState_ = ObjectState::Object; #endif return true; } bool ClassEmitter::prepareForFieldInitializers(size_t numFields) { MOZ_ASSERT(classState_ == ClassState::Class); // .initializers is a variable that stores an array of lambdas containing // code (the initializer) for each field. Upon an object's construction, // these lambdas will be called, defining the values. initializersAssignment_.emplace(bce_, bce_->cx->names().dotInitializers, NameOpEmitter::Kind::Initialize); if (!initializersAssignment_->prepareForRhs()) { return false; } if (!bce_->emitUint32Operand(JSOP_NEWARRAY, numFields)) { // [stack] HOMEOBJ HERITAGE? ARRAY return false; } MOZ_ASSERT(fieldIndex_ == 0); #ifdef DEBUG classState_ = ClassState::FieldInitializers; numFields_ = numFields; #endif return true; } bool ClassEmitter::emitFieldInitializerHomeObject() { MOZ_ASSERT(classState_ == ClassState::FieldInitializers); // [stack] OBJ HERITAGE? ARRAY METHOD if (!bce_->emit2(JSOP_INITHOMEOBJECT, isDerived_ ? 2 : 1)) { // [stack] OBJ HERITAGE? ARRAY METHOD return false; } #ifdef DEBUG classState_ = ClassState::FieldInitializerWithHomeObject; #endif return true; } bool ClassEmitter::emitStoreFieldInitializer() { MOZ_ASSERT(classState_ == ClassState::FieldInitializers || classState_ == ClassState::FieldInitializerWithHomeObject); MOZ_ASSERT(fieldIndex_ < numFields_); // [stack] HOMEOBJ HERITAGE? ARRAY METHOD if (!bce_->emitUint32Operand(JSOP_INITELEM_ARRAY, fieldIndex_)) { // [stack] HOMEOBJ HERITAGE? ARRAY return false; } fieldIndex_++; #ifdef DEBUG classState_ = ClassState::FieldInitializers; #endif return true; } bool ClassEmitter::emitFieldInitializersEnd() { MOZ_ASSERT(propertyState_ == PropertyState::Start || propertyState_ == PropertyState::Init); MOZ_ASSERT(classState_ == ClassState::FieldInitializers || classState_ == ClassState::FieldInitializerWithHomeObject); MOZ_ASSERT(fieldIndex_ == numFields_); if (!initializersAssignment_->emitAssignment()) { // [stack] HOMEOBJ HERITAGE? ARRAY return false; } initializersAssignment_.reset(); if (!bce_->emit1(JSOP_POP)) { // [stack] HOMEOBJ HERITAGE? return false; } #ifdef DEBUG classState_ = ClassState::FieldInitializersEnd; #endif return true; } bool ObjectEmitter::emitEnd() { MOZ_ASSERT(propertyState_ == PropertyState::Start || propertyState_ == PropertyState::Init); MOZ_ASSERT(objectState_ == ObjectState::Object); // [stack] OBJ if (obj_) { // The object survived and has a predictable shape: update the original // bytecode. if (!bce_->replaceNewInitWithNewObject(obj_, top_)) { // [stack] OBJ return false; } } #ifdef DEBUG objectState_ = ObjectState::End; #endif return true; } AutoSaveLocalStrictMode::AutoSaveLocalStrictMode(SharedContext* sc) : sc_(sc) { savedStrictness_ = sc_->setLocalStrictMode(true); } AutoSaveLocalStrictMode::~AutoSaveLocalStrictMode() { if (sc_) { restore(); } } void AutoSaveLocalStrictMode::restore() { MOZ_ALWAYS_TRUE(sc_->setLocalStrictMode(savedStrictness_)); sc_ = nullptr; } ClassEmitter::ClassEmitter(BytecodeEmitter* bce) : PropertyEmitter(bce), strictMode_(bce->sc), name_(bce->cx) { isClass_ = true; } bool ClassEmitter::emitScope(JS::Handle scopeBindings) { MOZ_ASSERT(propertyState_ == PropertyState::Start); MOZ_ASSERT(classState_ == ClassState::Start); tdzCache_.emplace(bce_); innerScope_.emplace(bce_); if (!innerScope_->enterLexical(bce_, ScopeKind::Lexical, scopeBindings)) return false; #ifdef DEBUG classState_ = ClassState::Scope; #endif return true; } bool ClassEmitter::emitClass(JS::Handle name) { MOZ_ASSERT(propertyState_ == PropertyState::Start); MOZ_ASSERT(classState_ == ClassState::Start || classState_ == ClassState::Scope); // [stack] setName(name); isDerived_ = false; if (!bce_->emitNewInit(JSProto_Object)) { // [stack] HOMEOBJ return false; } #ifdef DEBUG classState_ = ClassState::Class; #endif return true; } bool ClassEmitter::emitDerivedClass(JS::Handle name) { MOZ_ASSERT(propertyState_ == PropertyState::Start); MOZ_ASSERT(classState_ == ClassState::Start || classState_ == ClassState::Scope); // [stack] HERITAGE setName(name); isDerived_ = true; if (!bce_->emit1(JSOP_CLASSHERITAGE)) { // [stack] funcProto objProto return false; } if (!bce_->emit1(JSOP_OBJWITHPROTO)) { // [stack] funcProto HOMEOBJ return false; } // JSOP_CLASSHERITAGE leaves both protos on the stack. After // creating the prototype, swap it to the bottom to make the // constructor. if (!bce_->emit1(JSOP_SWAP)) { // [stack] HOMEOBJ funcProto return false; } #ifdef DEBUG classState_ = ClassState::Class; #endif return true; } void ClassEmitter::setName(JS::Handle name) { name_ = name; if (!name_) name_ = bce_->cx->names().empty; } bool ClassEmitter::emitInitConstructor(bool needsHomeObject) { MOZ_ASSERT(propertyState_ == PropertyState::Start); MOZ_ASSERT(classState_ == ClassState::Class || classState_ == ClassState::FieldInitializersEnd); // [stack] HOMEOBJ CTOR if (needsHomeObject) { if (!bce_->emit2(JSOP_INITHOMEOBJECT, 0)) { // [stack] HOMEOBJ CTOR return false; } } if (!initProtoAndCtor()) { // [stack] CTOR HOMEOBJ return false; } #ifdef DEBUG classState_ = ClassState::InitConstructor; #endif return true; } bool ClassEmitter::emitInitDefaultConstructor(const Maybe& classStart, const Maybe& classEnd) { MOZ_ASSERT(propertyState_ == PropertyState::Start); MOZ_ASSERT(classState_ == ClassState::Class || classState_ == ClassState::FieldInitializersEnd); if (classStart && classEnd) { // In the case of default class constructors, emit the start and end // offsets in the source buffer as source notes so that when we // actually make the constructor during execution, we can give it the // correct toString output. if (!bce_->newSrcNote3(SRC_CLASS_SPAN, ptrdiff_t(*classStart), ptrdiff_t(*classEnd))) { return false; } } if (isDerived_) { // [stack] HERITAGE PROTO if (!bce_->emitAtomOp(name_, JSOP_DERIVEDCONSTRUCTOR)) { // [stack] HOMEOBJ CTOR return false; } } else { // [stack] HOMEOBJ if (!bce_->emitAtomOp(name_, JSOP_CLASSCONSTRUCTOR)) { // [stack] HOMEOBJ CTOR return false; } } if (!initProtoAndCtor()) { // [stack] CTOR HOMEOBJ return false; } #ifdef DEBUG classState_ = ClassState::InitConstructor; #endif return true; } bool ClassEmitter::initProtoAndCtor() { // [stack] HOMEOBJ CTOR if (!bce_->emit1(JSOP_SWAP)) { // [stack] CTOR HOMEOBJ return false; } if (!bce_->emit1(JSOP_DUP2)) { // [stack] CTOR HOMEOBJ CTOR HOMEOBJ return false; } if (!bce_->emitAtomOp(bce_->cx->names().prototype, JSOP_INITLOCKEDPROP)) { // [stack] CTOR HOMEOBJ CTOR return false; } if (!bce_->emitAtomOp(bce_->cx->names().constructor, JSOP_INITHIDDENPROP)) { // [stack] CTOR HOMEOBJ return false; } return true; } bool ClassEmitter::emitEnd(Kind kind) { MOZ_ASSERT(propertyState_ == PropertyState::Start || propertyState_ == PropertyState::Init); MOZ_ASSERT(classState_ == ClassState::InitConstructor || classState_ == ClassState::FieldInitializersEnd); // [stack] CTOR HOMEOBJ if (!bce_->emit1(JSOP_POP)) { // [stack] CTOR return false; } if (name_ != bce_->cx->names().empty) { MOZ_ASSERT(tdzCache_.isSome()); MOZ_ASSERT(innerScope_.isSome()); if (!bce_->emitLexicalInitialization(name_)) { // [stack] CTOR return false; } // Pop the inner scope. if (!innerScope_->leave(bce_)) return false; innerScope_.reset(); if (kind == Kind::Declaration) { if (!bce_->emitLexicalInitialization(name_)) { // [stack] CTOR return false; } // Only class statements make outer bindings, and they do not leave // themselves on the stack. if (!bce_->emit1(JSOP_POP)) { // [stack] return false; } } tdzCache_.reset(); } else if (innerScope_.isSome()) { // [stack] CTOR MOZ_ASSERT(kind == Kind::Expression); MOZ_ASSERT(tdzCache_.isSome()); if (!innerScope_->leave(bce_)) return false; innerScope_.reset(); tdzCache_.reset(); }else { // [stack] CTOR MOZ_ASSERT(kind == Kind::Expression); MOZ_ASSERT(tdzCache_.isNothing()); } // [stack] # class declaration // [stack] // [stack] # class expression // [stack] CTOR strictMode_.restore(); #ifdef DEBUG classState_ = ClassState::End; #endif return true; }