Issue #2142 - Parse and process static class fields

Based-on: m-c 1535804/{6,7}
This commit is contained in:
Martok 2023-04-11 00:00:59 +02:00 committed by roytam1
commit c3b19191f3
10 changed files with 426 additions and 145 deletions

View file

@ -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<ClassField>()) {
MOZ_ASSERT(type == ClassBody);
@ -7666,7 +7668,9 @@ BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListTy
// is created elsewhere.
ClassField* field = &propdef->as<ClassField>();
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<ClassField>(); });
bool isStatic = placement == FieldPlacement::Static;
size_t numFields = classMembers->count_if([isStatic](ParseNode* propdef) {
return propdef->is<ClassField>()&&
propdef->as<ClassField>().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<ClassField>() &&
propdef->as<ClassField>().isStatic() == isStatic &&
propdef->as<ClassField>().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<ClassField>())
if (!propdef->is<ClassField>() ||
propdef->as<ClassField>().isStatic() != isStatic)
continue;
FunctionNode* initializer = propdef->as<ClassField>().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<ClassField>()&&
propdef->as<ClassField>().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<ClassField>() &&
propdef->as<ClassField>().isStatic() &&
propdef->as<ClassField>().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<ClassField>();
});
return propdef->is<ClassField>() &&
!propdef->as<ClassField>().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<ClassMethod>().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]

View file

@ -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

View file

@ -467,11 +467,11 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS)
return new_<ClassMethod>(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_<ClassField>(name, initializer);
return new_<ClassField>(name, initializer, isStatic);
}
MOZ_MUST_USE bool addClassMemberDefinition(ListNodeType memberList, Node member)

View file

@ -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<uint32_t>& classStart,
const Maybe<uint32_t>& 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

View file

@ -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<uint32_t>& classStart,
const mozilla::Maybe<uint32_t>& 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:

View file

@ -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<FunctionNode>(); }
bool isStatic() const {
return pn_u.binary.isStatic;
}
};
class SwitchStatement : public BinaryNode

View file

@ -7419,7 +7419,7 @@ Parser<ParseHandler>::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<ParseHandler>::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<ParseHandler>::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<ParseHandler>::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<ParseHandler>::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<ParseHandler>::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<ParseHandler>::synthesizeConstructor(HandleAtom className, uint32_t class
template <class ParseHandler>
typename ParseHandler::FunctionNodeType
Parser<ParseHandler>::fieldInitializerOpt(HandleAtom propAtom, size_t& numFieldKeys)
Parser<ParseHandler>::fieldInitializerOpt(HandleAtom propAtom, ClassFields& classFields, bool isStatic)
{
bool hasInitializer = false;
if (!tokenStream.matchToken(&hasInitializer, TOK_ASSIGN))
@ -8052,17 +8070,18 @@ Parser<ParseHandler>::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();

View file

@ -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);

View file

@ -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; }

View file

@ -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") \