diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 47ea774f86..1524493cb0 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -5704,7 +5704,8 @@ BytecodeEmitter::emitFunction(FunctionNode* funNode, bool needsProto /* = false } if (classContentsIfConstructor) { - fun->lazyScript()->setFieldInitializers(setupFieldInitializers(classContentsIfConstructor)); + fun->lazyScript()->setFieldInitializers(setupFieldInitializers(classContentsIfConstructor, + FieldPlacement::Instance)); } return true; } @@ -5732,7 +5733,7 @@ BytecodeEmitter::emitFunction(FunctionNode* funNode, bool needsProto /* = false FieldInitializers fieldInitializers = FieldInitializers::Invalid(); if (classContentsIfConstructor) { - fieldInitializers = setupFieldInitializers(classContentsIfConstructor); + fieldInitializers = setupFieldInitializers(classContentsIfConstructor, FieldPlacement::Instance); } BytecodeEmitter bce2(this, parser, funbox, script, /* lazyScript = */ nullptr, @@ -7659,6 +7660,7 @@ BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListTy // [stack] CTOR? OBJ size_t curFieldKeyIndex = 0; + size_t curStaticFieldKeyIndex = 0; for (ParseNode* propdef : obj->contents()) { if (propdef->is()) { MOZ_ASSERT(type == ClassBody); @@ -7666,7 +7668,9 @@ BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListTy // is created elsewhere. ClassField* field = &propdef->as(); if (field->name().getKind() == PNK_COMPUTED_NAME) { - if (!emitGetName(cx->names().dotFieldKeys)) { + HandlePropertyName fieldKeys = field->isStatic() ? cx->names().dotStaticFieldKeys + : cx->names().dotFieldKeys; + if (!emitGetName(fieldKeys)) { // [stack] CTOR? OBJ ARRAY return false; } @@ -7683,7 +7687,9 @@ BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListTy return false; } - if (!emitUint32Operand(JSOP_INITELEM_ARRAY, curFieldKeyIndex)) { + size_t fieldKeysIndex = field->isStatic() ? curStaticFieldKeyIndex++ + : curFieldKeyIndex++; + if (!emitUint32Operand(JSOP_INITELEM_ARRAY, fieldKeysIndex)) { // [stack] ARRAY return false; } @@ -7692,8 +7698,6 @@ BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListTy // [stack] CTOR? OBJ return false; } - - curFieldKeyIndex++; } continue; } @@ -7940,9 +7944,13 @@ BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListTy } FieldInitializers -BytecodeEmitter::setupFieldInitializers(ListNode* classMembers) +BytecodeEmitter::setupFieldInitializers(ListNode* classMembers, FieldPlacement placement) { - size_t numFields = classMembers->count_if([](ParseNode* propdef) { return propdef->is(); }); + bool isStatic = placement == FieldPlacement::Static; + size_t numFields = classMembers->count_if([isStatic](ParseNode* propdef) { + return propdef->is()&& + propdef->as().isStatic() == isStatic; + }); return FieldInitializers(numFields); } @@ -7971,18 +7979,21 @@ BytecodeEmitter::setupFieldInitializers(ListNode* classMembers) // BytecodeEmitter::emitPropertyList fills in the elements of the array. // See Parser::fieldInitializer for the `this[.fieldKeys[0]]` part. bool -BytecodeEmitter::emitCreateFieldKeys(ListNode* obj) +BytecodeEmitter::emitCreateFieldKeys(ListNode* obj, FieldPlacement placement) { - size_t numFieldKeys = obj->count_if([](ParseNode* propdef) { + bool isStatic = placement == FieldPlacement::Static; + size_t numFieldKeys = obj->count_if([isStatic](ParseNode* propdef) { return propdef->is() && + propdef->as().isStatic() == isStatic && propdef->as().name().getKind() == PNK_COMPUTED_NAME; }); if (numFieldKeys == 0) return true; - NameOpEmitter noe(this, cx->names().dotFieldKeys, - NameOpEmitter::Kind::Initialize); + HandlePropertyName fieldKeys = isStatic ? cx->names().dotStaticFieldKeys + : cx->names().dotFieldKeys; + NameOpEmitter noe(this, fieldKeys, NameOpEmitter::Kind::Initialize); if (!noe.prepareForRhs()) return false; @@ -8005,45 +8016,64 @@ BytecodeEmitter::emitCreateFieldKeys(ListNode* obj) } bool -BytecodeEmitter::emitCreateFieldInitializers(ClassEmitter& ce, ListNode* obj) +BytecodeEmitter::emitCreateFieldInitializers(ClassEmitter& ce, ListNode* obj, + FieldPlacement placement) { - // [stack] HOMEOBJ HERITAGE? - FieldInitializers fieldInitializers = setupFieldInitializers(obj); + // FieldPlacement::Instance + // [stack] HOMEOBJ HERITAGE? + // + // FieldPlacement::Static + // [stack] CTOR HOMEOBJ + FieldInitializers fieldInitializers = setupFieldInitializers(obj, placement); MOZ_ASSERT(fieldInitializers.valid); size_t numFields = fieldInitializers.numFieldInitializers; if (numFields == 0) return true; - if (!ce.prepareForFieldInitializers(numFields)) { - // [stack] HOMEOBJ HERITAGE? ARRAY + bool isStatic = placement == FieldPlacement::Static; + if (!ce.prepareForFieldInitializers(numFields, isStatic)) { + // [stack] HOMEOBJ HERITAGE? ARRAY + // or: + // [stack] CTOR HOMEOBJ ARRAY return false; } for (ParseNode* propdef : obj->contents()) { - if (!propdef->is()) + if (!propdef->is() || + propdef->as().isStatic() != isStatic) continue; FunctionNode* initializer = propdef->as().initializer(); + if (!ce.prepareForFieldInitializer()) + 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()->function()->allowSuperProperty()); - if (!ce.emitFieldInitializerHomeObject()) { + if (!ce.emitFieldInitializerHomeObject(isStatic)) { // [stack] CTOR OBJ ARRAY LAMBDA + // or: + // [stack] CTOR HOMEOBJ ARRAY LAMBDA return false; } } if (!ce.emitStoreFieldInitializer()) { // [stack] HOMEOBJ HERITAGE? ARRAY + // or: + // [stack] CTOR HOMEOBJ ARRAY return false; } } if (!ce.emitFieldInitializersEnd()) { // [stack] HOMEOBJ HERITAGE? + // or: + // [stack] CTOR HOMEOBJ return false; } @@ -8139,6 +8169,108 @@ BytecodeEmitter::emitInitializeInstanceFields() return true; } +bool +BytecodeEmitter::emitInitializeStaticFields(ListNode* classMembers) +{ + size_t numFields = classMembers->count_if([](ParseNode* propdef) { + return propdef->is()&& + propdef->as().isStatic(); + }); + + if (numFields == 0) { + return true; + } + + if (!emitGetName(cx->names().dotStaticInitializers)) { + // [stack] CTOR ARRAY + return false; + } + + for (size_t fieldIndex = 0; fieldIndex < numFields; fieldIndex++) { + bool hasNext = fieldIndex < numFields - 1; + if (fieldIndex < numFields - 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] CTOR ARRAY ARRAY + return false; + } + } + + if (!emitNumberOp(fieldIndex)) { + // [stack] CTOR ARRAY? ARRAY INDEX + return false; + } + + // Don't use CALLELEM here, because the receiver of the call != the receiver + // of this getelem. (Specifically, the call receiver is `ctor`, and the + // receiver of this getelem is `.staticInitializers`) + if (!emit1(JSOP_GETELEM)) { + // [stack] CTOR ARRAY? FUNC + return false; + } + + if (!emitDupAt(1 + hasNext)) { + // [stack] CTOR ARRAY? FUNC CTOR + return false; + } + + if (!emitCall(JSOP_CALL_IGNORES_RV, 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 = [&](HandlePropertyName 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->names().dotStaticInitializers)) + return false; + + auto isStaticFieldWithComputedName = [](ParseNode* propdef) { + return propdef->is() && + propdef->as().isStatic() && + propdef->as().name().getKind() == PNK_COMPUTED_NAME; + }; + + if (classMembers->any_of(isStaticFieldWithComputedName)) { + if (!clearStaticFieldSlot(cx->names().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 @@ -8684,15 +8816,17 @@ BytecodeEmitter::emitClass(ClassNode* classNode) // As an optimization omit the |.initializers| binding when no instance // fields are present. bool hasInstanceFields = classMembers->any_of([](ParseNode* propdef) { - return propdef->is(); - }); + return propdef->is() && + !propdef->as().isStatic(); + }); if (hasInstanceFields) { lse.emplace(this); if (!lse->emitScope(ScopeKind::Lexical, constructorScope->scopeBindings())) return false; // Any class with field initializers will have a constructor - if (!emitCreateFieldInitializers(ce, classMembers)) + if (!emitCreateFieldInitializers(ce, classMembers, + FieldPlacement::Instance)) return false; } ctor = &constructorScope->scopeBody()->as().method(); @@ -8726,7 +8860,13 @@ BytecodeEmitter::emitClass(ClassNode* classNode) } } - if (!emitCreateFieldKeys(classMembers)) + if (!emitCreateFieldKeys(classMembers, FieldPlacement::Instance)) + return false; + + if (!emitCreateFieldInitializers(ce, classMembers, FieldPlacement::Static)) + return false; + + if (!emitCreateFieldKeys(classMembers, FieldPlacement::Static)) return false; if (!emitPropertyList(classMembers, ce, ClassBody)) { @@ -8734,6 +8874,16 @@ BytecodeEmitter::emitClass(ClassNode* classNode) return false; } + if (!ce.emitBinding()) { + // [stack] CTOR + return false; + } + + if (!emitInitializeStaticFields(classMembers)) { + // [stack] CTOR + return false; + } + if (!ce.emitEnd(kind)) { // [stack] # class declaration // [stack] diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 15a46ac7d0..7521e236dc 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -530,11 +530,13 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListType type); - FieldInitializers setupFieldInitializers(ListNode* classMembers); - MOZ_MUST_USE bool emitCreateFieldKeys(ListNode* obj); - MOZ_MUST_USE bool emitCreateFieldInitializers(ClassEmitter& ce, ListNode* obj); + enum class FieldPlacement { Instance, Static }; + FieldInitializers setupFieldInitializers(ListNode* classMembers, FieldPlacement placement); + MOZ_MUST_USE bool emitCreateFieldKeys(ListNode* obj, FieldPlacement placement); + MOZ_MUST_USE bool emitCreateFieldInitializers(ClassEmitter& ce, ListNode* obj, FieldPlacement placement); const FieldInitializers& findFieldInitializersForCall(); MOZ_MUST_USE bool emitInitializeInstanceFields(); + MOZ_MUST_USE bool emitInitializeStaticFields(ListNode* classMembers); // To catch accidental misuse, emitUint16Operand/emit3 assert that they are // not used to unconditionally emit JSOP_GETLOCAL. Variable access should diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index cede9a013b..4f3492af4d 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -467,11 +467,11 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) return new_(key, funNode, op, isStatic); } - MOZ_MUST_USE ClassField* newClassFieldDefinition(Node name, FunctionNodeType initializer) + MOZ_MUST_USE ClassField* newClassFieldDefinition(Node name, FunctionNodeType initializer, bool isStatic) { MOZ_ASSERT(isUsableAsObjectPropertyName(name)); - return new_(name, initializer); + return new_(name, initializer, isStatic); } MOZ_MUST_USE bool addClassMemberDefinition(ListNodeType memberList, Node member) diff --git a/js/src/frontend/ObjectEmitter.cpp b/js/src/frontend/ObjectEmitter.cpp index 0cb56c8f3e..bf597d0e95 100644 --- a/js/src/frontend/ObjectEmitter.cpp +++ b/js/src/frontend/ObjectEmitter.cpp @@ -484,51 +484,79 @@ bool ObjectEmitter::emitObject(size_t propertyCount) return true; } -bool ClassEmitter::prepareForFieldInitializers(size_t numFields) +bool ClassEmitter::prepareForFieldInitializers(size_t numFields, bool isStatic) { - MOZ_ASSERT(classState_ == ClassState::Class); + MOZ_ASSERT_IF(!isStatic, classState_ == ClassState::Class); + MOZ_ASSERT_IF(isStatic, classState_ == ClassState::InitConstructor); + MOZ_ASSERT(fieldState_ == FieldState::Start); // .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, + HandlePropertyName initializers = isStatic ? bce_->cx->names().dotStaticInitializers + : bce_->cx->names().dotInitializers; + initializersAssignment_.emplace(bce_, initializers, NameOpEmitter::Kind::Initialize); if (!initializersAssignment_->prepareForRhs()) { return false; } if (!bce_->emitUint32Operand(JSOP_NEWARRAY, numFields)) { - // [stack] HOMEOBJ HERITAGE? ARRAY + // [stack] ARRAY return false; } - MOZ_ASSERT(fieldIndex_ == 0); + fieldIndex_ = 0; #ifdef DEBUG - classState_ = ClassState::FieldInitializers; + if (isStatic) { + classState_ = ClassState::StaticFieldInitializers; + } else { + classState_ = ClassState::InstanceFieldInitializers; + } numFields_ = numFields; #endif return true; } -bool ClassEmitter::emitFieldInitializerHomeObject() +bool ClassEmitter::prepareForFieldInitializer() { - MOZ_ASSERT(classState_ == ClassState::FieldInitializers); - // [stack] OBJ HERITAGE? ARRAY METHOD - if (!bce_->emit2(JSOP_INITHOMEOBJECT, isDerived_ ? 2 : 1)) { - // [stack] OBJ HERITAGE? ARRAY METHOD + MOZ_ASSERT(classState_ == ClassState::InstanceFieldInitializers || + classState_ == ClassState::StaticFieldInitializers); + MOZ_ASSERT(fieldState_ == FieldState::Start); + +#ifdef DEBUG + fieldState_ = FieldState::Initializer; +#endif + return true; +} + +bool ClassEmitter::emitFieldInitializerHomeObject(bool isStatic) +{ + MOZ_ASSERT(fieldState_ == FieldState::Initializer); + // [stack] OBJ HERITAGE? ARRAY METHOD + // or: + // [stack] CTOR HOMEOBJ ARRAY METHOD + uint8_t ofs = isStatic ? 2 + // [stack] CTOR HOMEOBJ ARRAY METHOD CTOR + : isDerived_ ? 2 : 1; + // [stack] OBJ HERITAGE? ARRAY METHOD OBJ + if (!bce_->emit2(JSOP_INITHOMEOBJECT, ofs)) { + // [stack] OBJ HERITAGE? ARRAY METHOD + // or: + // [stack] CTOR HOMEOBJ ARRAY METHOD return false; } #ifdef DEBUG - classState_ = ClassState::FieldInitializerWithHomeObject; + fieldState_ = FieldState::InitializerWithHomeObject; #endif return true; } bool ClassEmitter::emitStoreFieldInitializer() { - MOZ_ASSERT(classState_ == ClassState::FieldInitializers || - classState_ == ClassState::FieldInitializerWithHomeObject); + MOZ_ASSERT(fieldState_ == FieldState::Initializer || + fieldState_ == FieldState::InitializerWithHomeObject); MOZ_ASSERT(fieldIndex_ < numFields_); // [stack] HOMEOBJ HERITAGE? ARRAY METHOD @@ -539,7 +567,7 @@ bool ClassEmitter::emitStoreFieldInitializer() fieldIndex_++; #ifdef DEBUG - classState_ = ClassState::FieldInitializers; + fieldState_ = FieldState::Start; #endif return true; } @@ -548,8 +576,9 @@ bool ClassEmitter::emitFieldInitializersEnd() { MOZ_ASSERT(propertyState_ == PropertyState::Start || propertyState_ == PropertyState::Init); - MOZ_ASSERT(classState_ == ClassState::FieldInitializers || - classState_ == ClassState::FieldInitializerWithHomeObject); + MOZ_ASSERT(classState_ == ClassState::InstanceFieldInitializers || + classState_ == ClassState::StaticFieldInitializers); + MOZ_ASSERT(fieldState_ == FieldState::Start); MOZ_ASSERT(fieldIndex_ == numFields_); if (!initializersAssignment_->emitAssignment()) { @@ -564,7 +593,11 @@ bool ClassEmitter::emitFieldInitializersEnd() } #ifdef DEBUG - classState_ = ClassState::FieldInitializersEnd; + if (classState_ == ClassState::InstanceFieldInitializers) { + classState_ = ClassState::InstanceFieldInitializersEnd; + } else { + classState_ = ClassState::StaticFieldInitializersEnd; + } #endif return true; } @@ -700,7 +733,7 @@ bool ClassEmitter::emitInitConstructor(bool needsHomeObject) { MOZ_ASSERT(propertyState_ == PropertyState::Start); MOZ_ASSERT(classState_ == ClassState::Class || - classState_ == ClassState::FieldInitializersEnd); + classState_ == ClassState::InstanceFieldInitializersEnd); // [stack] HOMEOBJ CTOR @@ -726,8 +759,7 @@ bool ClassEmitter::emitInitDefaultConstructor(const Maybe& classStart, const Maybe& classEnd) { MOZ_ASSERT(propertyState_ == PropertyState::Start); - MOZ_ASSERT(classState_ == ClassState::Class || - classState_ == ClassState::FieldInitializersEnd); + MOZ_ASSERT(classState_ == ClassState::Class); if (classStart && classEnd) { // In the case of default class constructors, emit the start and end @@ -789,12 +821,13 @@ bool ClassEmitter::initProtoAndCtor() return true; } -bool ClassEmitter::emitEnd(Kind kind) +bool ClassEmitter::emitBinding() { MOZ_ASSERT(propertyState_ == PropertyState::Start || propertyState_ == PropertyState::Init); MOZ_ASSERT(classState_ == ClassState::InitConstructor || - classState_ == ClassState::FieldInitializersEnd); + classState_ == ClassState::InstanceFieldInitializersEnd || + classState_ == ClassState::StaticFieldInitializersEnd); // [stack] CTOR HOMEOBJ @@ -804,49 +837,54 @@ bool ClassEmitter::emitEnd(Kind kind) } 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(); + // [stack] CTOR - 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; - } - } +#ifdef DEBUG + classState_ = ClassState::BoundName; +#endif + return true; +} - tdzCache_.reset(); - } else if (innerScope_.isSome()) { - // [stack] CTOR - MOZ_ASSERT(kind == Kind::Expression); +bool ClassEmitter::emitEnd(Kind kind) +{ + MOZ_ASSERT(classState_ == ClassState::BoundName); + // [stack] CTOR + + if (innerScope_.isSome()) { MOZ_ASSERT(tdzCache_.isSome()); if (!innerScope_->leave(bce_)) return false; innerScope_.reset(); tdzCache_.reset(); - }else { - // [stack] CTOR - + } else { MOZ_ASSERT(kind == Kind::Expression); MOZ_ASSERT(tdzCache_.isNothing()); } + if (kind == Kind::Declaration) { + MOZ_ASSERT(name_); + + 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; + } + } + // [stack] # class declaration // [stack] // [stack] # class expression diff --git a/js/src/frontend/ObjectEmitter.h b/js/src/frontend/ObjectEmitter.h index 38408f7650..96c9b49700 100644 --- a/js/src/frontend/ObjectEmitter.h +++ b/js/src/frontend/ObjectEmitter.h @@ -685,30 +685,20 @@ class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter // | // +-------------------------------+ // | - // | prepareForFieldInitializers - // +-----------------------------+ - // | | - // | | +-------------------+ - // | +--------------------->+--->| FieldInitializers |-+ - // | | +-------------------+ | - // | | | - // | | (emit initializer method) | - // | | +<--------------------------------------------+ - // | | | - // | | | emitFieldInitializerHomeObject +--------------------------------+ - // | | +-------------------------------->| FieldInitializerWithHomeObject |-+ - // | | | +--------------------------------+ | - // | | | | - // | | +------------------------------------------------------------------->+ - // | | | - // | | emitStoreFieldInitializer | - // | +<--+<-----------------------------------------------------------------------+ - // | | - // | | emitFieldInitializersEnd +----------------------+ - // | +-------------------------->| FieldInitializersEnd |-+ - // | +----------------------+ | - // | | - // |<------------------------------------------------------+ + // | prepareForFieldInitializers(isStatic = false) + // +---------------+ + // | | + // | +--------v------------------+ + // | | InstanceFieldInitializers | + // | +---------------------------+ + // | | + // | emitFieldInitializersEnd + // | | + // | +--------v---------------------+ + // | | InstanceFieldInitializersEnd | + // | +------------------------------+ + // | | + // +<--------------+ // | // | // | emitInitConstructor +-----------------+ @@ -717,11 +707,36 @@ class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter // | emitInitDefaultConstructor | | // +----------------------------+ | // | - // +---------------------------------------------------+ - // | - // | (do PropertyEmitter operation) emitEnd +-----+ - // +-------------------------------+--------->| End | - // +-----+ + // +-----------------------------------------------------+ + // | + // | prepareForFieldInitializers(isStatic = true) + // +---------------+ + // | | + // | +--------v----------------+ + // | | StaticFieldInitializers | + // | +-------------------------+ + // | | + // | | emitFieldInitializersEnd + // | | + // | +--------v-------------------+ + // | | StaticFieldInitializersEnd | + // | +----------------------------+ + // | | + // +<--------------+ + // | + // | (do PropertyEmitter operation) + // +--------------------------------+ + // | + // +-------------+ emitBinding | + // | BoundName |<-----------------+ + // +--+----------+ + // | + // | emitEnd + // | + // +--v----+ + // | End | + // +-------+ + // enum class ClassState { // The initial state. Start, @@ -735,20 +750,60 @@ class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter // After calling emitInitConstructor or emitInitDefaultConstructor. InitConstructor, - // After calling prepareForFieldInitializers - // and 0 or more calls to emitStoreFieldInitializer. - FieldInitializers, - - // After calling emitFieldInitializerHomeObject - FieldInitializerWithHomeObject, + // After calling prepareForFieldInitializers(isStatic = false). + InstanceFieldInitializers, // After calling emitFieldInitializersEnd. - FieldInitializersEnd, + InstanceFieldInitializersEnd, + + // After calling prepareForFieldInitializers(isStatic = true). + StaticFieldInitializers, + + // After calling emitFieldInitializersEnd. + StaticFieldInitializersEnd, + + // After calling emitBinding. + BoundName, // After calling emitEnd. End, }; ClassState classState_ = ClassState::Start; + + // The state of the fields emitter. + // + // clang-format off + // + // +-------+ + // | Start +<-----------------------------+ + // +-------+ | + // | | + // | prepareForFieldInitializer | emitStoreFieldInitializer + // v | + // +-------------+ | + // | Initializer +------------------------->+ + // +-------------+ | + // | | + // | emitFieldInitializerHomeObject | + // v | + // +---------------------------+ | + // | InitializerWithHomeObject +------------+ + // +---------------------------+ + // + // clang-format on + enum class FieldState { + // After calling prepareForFieldInitializers + // and 0 or more calls to emitStoreFieldInitializer. + Start, + + // After calling prepareForFieldInitializer + Initializer, + + // After calling emitFieldInitializerHomeObject + InitializerWithHomeObject, + }; + FieldState fieldState_ = FieldState::Start; + size_t numFields_ = 0; #endif @@ -783,11 +838,14 @@ class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter const mozilla::Maybe& classStart, const mozilla::Maybe& classEnd); - MOZ_MUST_USE bool prepareForFieldInitializers(size_t numFields); - MOZ_MUST_USE bool emitFieldInitializerHomeObject(); + MOZ_MUST_USE bool prepareForFieldInitializers(size_t numFields, bool isStatic); + MOZ_MUST_USE bool prepareForFieldInitializer(); + MOZ_MUST_USE bool emitFieldInitializerHomeObject(bool isStatic); MOZ_MUST_USE bool emitStoreFieldInitializer(); MOZ_MUST_USE bool emitFieldInitializersEnd(); + MOZ_MUST_USE bool emitBinding(); + MOZ_MUST_USE bool emitEnd(Kind kind); private: diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 554703da46..57a65a9e9b 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -761,6 +761,7 @@ class ParseNode private: friend class BinaryNode; friend class ForNode; + friend class ClassField; friend class ClassMethod; friend class PropertyAccessBase; friend class SwitchStatement; @@ -768,7 +769,7 @@ class ParseNode ParseNode* right; union { unsigned iflags; /* JSITER_* flags for PNK_{COMPREHENSION,}FOR node */ - bool isStatic; /* only for PNK_CLASSMETHOD */ + bool isStatic; /* only for PNK_CLASSMETHOD and PNK_CLASSFIELD */ bool hasDefault; /* only for PNK_SWITCH */ }; } binary; @@ -785,12 +786,6 @@ class ParseNode ParseNode* initOrStmt; /* var initializer, argument default, * or label statement target */ } name; - struct { - private: - friend class ClassField; - ParseNode* name; - ParseNode* initializer; /* field initializer - optional */ - } field; struct { private: friend class RegExpLiteral; @@ -2141,11 +2136,12 @@ class ClassMethod : public BinaryNode class ClassField : public BinaryNode { public: - ClassField(ParseNode* name, ParseNode* initializer) + ClassField(ParseNode* name, ParseNode* initializer, bool isStatic) : BinaryNode(PNK_CLASSFIELD, JSOP_NOP, TokenPos::box(name->pn_pos, initializer->pn_pos), name, initializer) { + pn_u.binary.isStatic = isStatic; } static bool test(const ParseNode& node) { @@ -2157,6 +2153,10 @@ class ClassField : public BinaryNode ParseNode& name() const { return *left(); } FunctionNode* initializer() const { return &right()->as(); } + + bool isStatic() const { + return pn_u.binary.isStatic; + } }; class SwitchStatement : public BinaryNode diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index b5044c6ca2..c319924fa7 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -7419,7 +7419,7 @@ Parser::classMember(YieldHandling yieldHandling, const ParseContext::ClassStatement& classStmt, HandlePropertyName className, uint32_t classStartOffset, bool hasHeritage, - size_t& numFields, size_t& numFieldKeys, + ClassFields& classFields, ListNodeType& classMembers, bool* done) { *done = false; @@ -7474,8 +7474,10 @@ Parser::classMember(YieldHandling yieldHandling, } if (isStatic) { - errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF); - return false; + if (propAtom == context->names().prototype) { + errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF); + return false; + } } if (propAtom == context->names().constructor) { @@ -7486,9 +7488,13 @@ Parser::classMember(YieldHandling yieldHandling, if (!abortIfSyntaxParser()) return false; - numFields++; + if (isStatic) { + classFields.staticFields++; + } else { + classFields.instanceFields++; + } - FunctionNodeType initializer = fieldInitializerOpt(propAtom, numFieldKeys); + FunctionNodeType initializer = fieldInitializerOpt(propAtom, classFields, isStatic); if (!initializer) return false; @@ -7496,7 +7502,7 @@ Parser::classMember(YieldHandling yieldHandling, return false; } - ClassFieldType field = handler.newClassFieldDefinition(propName, initializer); + ClassFieldType field = handler.newClassFieldDefinition(propName, initializer, isStatic); if (!field) return false; @@ -7613,12 +7619,12 @@ bool Parser::finishClassConstructor(const ParseContext::ClassStatement& classStmt, HandlePropertyName className, bool hasHeritage, uint32_t classStartOffset, uint32_t classEndOffset, - size_t numFields, - ListNodeType& classMembers) + const ClassFields& classFields, ListNodeType& classMembers) { // Fields cannot re-use the constructor obtained via JSOP_CLASSCONSTRUCTOR or // JSOP_DERIVEDCONSTRUCTOR due to needing to emit calls to the field // initializers in the constructor. So, synthesize a new one. + size_t numFields = classFields.instanceFields; if (classStmt.constructorBox == nullptr && numFields > 0) { MOZ_ASSERT(!options().selfHostingMode); // Unconditionally create the scope here, because it's always the @@ -7755,24 +7761,36 @@ Parser::classDefinition(YieldHandling yieldHandling, if (!classMembers) return null(); - size_t numFields = 0; - size_t numFieldKeys = 0; + ClassFields classFields{}; for (;;) { bool done; - if (!classMember(yieldHandling, classStmt, className, classStartOffset, - hasHeritage, numFields, numFieldKeys, classMembers, &done)) + if (!classMember(yieldHandling, classStmt, className, classStartOffset, hasHeritage, + classFields, classMembers, &done)) return null(); if (done) break; } - if (numFieldKeys > 0) { + if (classFields.instanceFieldKeys > 0) { if (!noteDeclaredName(context->names().dotFieldKeys, DeclarationKind::Let, namePos)) return null(); } + + if (classFields.staticFields > 0) { + if (!noteDeclaredName(context->names().dotStaticInitializers, + DeclarationKind::Let, namePos)) + return null(); + } + + if (classFields.staticFieldKeys > 0) { + if (!noteDeclaredName(context->names().dotStaticFieldKeys, + DeclarationKind::Let, namePos)) + return null(); + } + classEndOffset = pos().end; if (!finishClassConstructor(classStmt, className, hasHeritage, - classStartOffset, classEndOffset, numFields, classMembers)) + classStartOffset, classEndOffset, classFields, classMembers)) return null(); if (className) { @@ -7954,7 +7972,7 @@ Parser::synthesizeConstructor(HandleAtom className, uint32_t class template typename ParseHandler::FunctionNodeType -Parser::fieldInitializerOpt(HandleAtom propAtom, size_t& numFieldKeys) +Parser::fieldInitializerOpt(HandleAtom propAtom, ClassFields& classFields, bool isStatic) { bool hasInitializer = false; if (!tokenStream.matchToken(&hasInitializer, TOK_ASSIGN)) @@ -8052,17 +8070,18 @@ Parser::fieldInitializerOpt(HandleAtom propAtom, size_t& numFieldK if (!propAtom) { // See BytecodeEmitter::emitCreateFieldKeys for an explanation of what // .fieldKeys means and its purpose. - Node dotFieldKeys = newInternalDotName(context->names().dotFieldKeys); - if (!dotFieldKeys) + NameNodeType fieldKeysName = newInternalDotName(isStatic ? context->names().dotStaticFieldKeys + : context->names().dotFieldKeys); + if (!fieldKeysName) return null(); - double fieldKeyIndex = numFieldKeys; - numFieldKeys++; + double fieldKeyIndex = isStatic ? classFields.staticFieldKeys++ + : classFields.instanceFieldKeys++; Node fieldKeyIndexNode = handler.newNumber(fieldKeyIndex, DecimalPoint::NoDecimal, wholeInitializerPos); if (!fieldKeyIndexNode) return null(); - Node fieldKeyValue = handler.newPropertyByValue(dotFieldKeys, fieldKeyIndexNode, wholeInitializerPos.end); + Node fieldKeyValue = handler.newPropertyByValue(fieldKeysName, fieldKeyIndexNode, wholeInitializerPos.end); if (!fieldKeyValue) return null(); diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index fd0033ad7d..ec6595b908 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -1488,20 +1488,32 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) enum ClassContext { ClassStatement, ClassExpression }; ClassNodeType classDefinition(YieldHandling yieldHandling, ClassContext classContext, DefaultHandling defaultHandling); + struct ClassFields { + // The number of instance class fields. + size_t instanceFields = 0; + + // The number of instance class fields with computed property names. + size_t instanceFieldKeys = 0; + + // The number of static class fields. + size_t staticFields = 0; + + // The number of static class fields with computed property names. + size_t staticFieldKeys = 0; + }; MOZ_MUST_USE bool classMember(YieldHandling yieldHandling, const ParseContext::ClassStatement& classStmt, HandlePropertyName className, uint32_t classStartOffset, bool hasHeritage, - size_t& numFields, - size_t& numFieldKeys, + ClassFields& classFields, ListNodeType& classMembers, bool* done); MOZ_MUST_USE bool finishClassConstructor( const ParseContext::ClassStatement& classStmt, HandlePropertyName className, bool hasHeritage, uint32_t classStartOffset, uint32_t classEndOffset, - size_t numFieldsWithInitializers, ListNodeType& classMembers); + const ClassFields& classFields, ListNodeType& classMembers); - FunctionNodeType fieldInitializerOpt(HandleAtom atom, size_t& numFieldKeys); + FunctionNodeType fieldInitializerOpt(HandleAtom atom, ClassFields& classFields, bool isStatic); FunctionNodeType synthesizeConstructor(HandleAtom className, uint32_t classNameOffset, bool hasHeritage); diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index c1b6e989ab..b274ac642b 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -332,7 +332,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) MOZ_MUST_USE bool addSpreadProperty(ListNodeType literal, uint32_t begin, Node inner) { return true; } MOZ_MUST_USE bool addObjectMethodDefinition(ListNodeType literal, Node name, FunctionNodeType funNode, JSOp op) { return true; } MOZ_MUST_USE Node newClassMethodDefinition(Node key, FunctionNodeType funNode, JSOp op, bool isStatic) { return NodeGeneric; } - MOZ_MUST_USE Node newClassFieldDefinition(Node name, FunctionNodeType initializer) { return NodeGeneric; } + MOZ_MUST_USE Node newClassFieldDefinition(Node name, FunctionNodeType initializer, bool isStatic) { return NodeGeneric; } MOZ_MUST_USE bool addClassMemberDefinition(ListNodeType memberList, Node member) { return true; } UnaryNodeType newYieldExpression(uint32_t begin, Node value) { return NodeGeneric; } UnaryNodeType newYieldStarExpression(uint32_t begin, Node value) { return NodeGeneric; } diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index f7f324da5e..e1e9f56c31 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -105,6 +105,8 @@ macro(dotThis, dotThis, ".this") \ macro(dotInitializers, dotInitializers, ".initializers") \ macro(dotFieldKeys, dotFieldKeys, ".fieldKeys") \ + macro(dotStaticInitializers, dotStaticInitializers, ".staticInitializers") \ + macro(dotStaticFieldKeys, dotStaticFieldKeys, ".staticFieldKeys") \ macro(each, each, "each") \ macro(elementType, elementType, "elementType") \ macro(else, else_, "else") \