Bug 1535804 - Part 7: Parse and process static class fields. r=arai! draft
authorAndré Bargull <andre.bargull@gmail.com>
Mon, 18 Nov 2019 00:24:01 -0800
changeset 2648927 8375b2ca043bd5e8f76b258b1f518c0350531020
parent 2648926 e95a6c42be0dfcc9f04eb1531210bbf22986ed38
child 2648928 3585c148c82d3db8c60fcd20919b014c0c3fa0ce
push id490869
push userandre.bargull@gmail.com
push dateSat, 22 Feb 2020 09:32:51 +0000
treeherdertry@ca279eef8570 [default view] [failures only]
reviewersarai
bugs1535804
milestone75.0a1
Bug 1535804 - Part 7: Parse and process static class fields. r=arai! Parser: - Instead of unconditionally throwing an exception for static class fields, only throw an exception when the static class field is named "prototype" per the early error restriction for static class fields. ObjectEmitter: - Split `ClassEmitter::emitBinding` from `ClassEmitter::emitEnd` to install the static class fields after installing the inner binding, but before the outer class binding. BytecodeEmitter: - `emitInitializeStaticFields` is modelled after `emitInitializeInstanceFields` except that the this-binding for the initialiser function isn't `dotThis`, but instead the class constructor (which is dup'ed from the stack). - To ensure the static class fields keys and initialiser aren't kept alive indefinitely, `emitInitializeStaticFields` overwrites both arrays with `undefined` after installing the static class fields. Differential Revision: https://phabricator.services.mozilla.com/D53640
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/ObjectEmitter.cpp
js/src/frontend/ObjectEmitter.h
js/src/frontend/Parser.cpp
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -8923,16 +8923,122 @@ bool BytecodeEmitter::emitInitializeInst
       //            [stack] ARRAY?
       return false;
     }
   }
 
   return true;
 }
 
+bool BytecodeEmitter::emitInitializeStaticFields(ListNode* classMembers) {
+  auto isStaticField = [](ParseNode* propdef) {
+    return propdef->is<ClassField>() && propdef->as<ClassField>().isStatic();
+  };
+  size_t numFields =
+      std::count_if(classMembers->contents().begin(),
+                    classMembers->contents().end(), isStaticField);
+
+  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 (hasNext) {
+      // We Dup to keep the array around (it is consumed in the bytecode below)
+      // for next iterations of this loop, except for the last iteration, which
+      // avoids an extra Pop at the end of the loop.
+      if (!emit1(JSOp::Dup)) {
+        //          [stack] CTOR ARRAY ARRAY
+        return false;
+      }
+    }
+
+    if (!emitNumberOp(fieldIndex)) {
+      //            [stack] CTOR ARRAY? ARRAY INDEX
+      return false;
+    }
+
+    // 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::CallIgnoresRv, 0)) {
+      //            [stack] CTOR ARRAY? RVAL
+      return false;
+    }
+
+    if (!emit1(JSOp::Pop)) {
+      //            [stack] CTOR ARRAY?
+      return false;
+    }
+  }
+
+  // Overwrite |.staticInitializers| and |.staticFieldKeys| with undefined to
+  // avoid keeping the arrays alive indefinitely.
+  auto clearStaticFieldSlot = [&](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() ==
+               ParseNodeKind::ComputedName;
+  };
+
+  if (std::any_of(classMembers->contents().begin(),
+                  classMembers->contents().end(),
+                  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 BytecodeEmitter::emitObject(ListNode* objNode,
                                                   bool isInner) {
   bool isSingletonContext = !objNode->hasNonConstInitializer() &&
                             objNode->head() && checkSingletonContext();
 
   // Note: this method uses the ObjLiteralWriter and emits
@@ -9642,21 +9748,39 @@ bool BytecodeEmitter::emitClass(
       return false;
     }
   }
 
   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)) {
     //              [stack] CTOR HOMEOBJ
     return false;
   }
 
+  if (!ce.emitBinding()) {
+    //              [stack] CTOR
+    return false;
+  }
+
+  if (!emitInitializeStaticFields(classMembers)) {
+    //              [stack] CTOR
+    return false;
+  }
+
   if (!ce.emitEnd(kind)) {
     //              [stack] # class declaration
     //              [stack]
     //              [stack] # class expression
     //              [stack] CTOR
     return false;
   }
 
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -521,16 +521,17 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
   mozilla::Maybe<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
   // instead be emitted using EmitVarOp. In special cases, when the caller
   // definitely knows that a given local slot is unaliased, this function may be
   // used as a non-asserting version of emitUint16Operand.
   MOZ_MUST_USE bool emitLocalOp(JSOp op, uint32_t slot);
 
--- a/js/src/frontend/ObjectEmitter.cpp
+++ b/js/src/frontend/ObjectEmitter.cpp
@@ -808,73 +808,78 @@ bool ClassEmitter::emitFieldInitializers
     classState_ = ClassState::InstanceFieldInitializersEnd;
   } else {
     classState_ = ClassState::StaticFieldInitializersEnd;
   }
 #endif
   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::InstanceFieldInitializersEnd ||
              classState_ == ClassState::StaticFieldInitializersEnd);
   //                [stack] CTOR HOMEOBJ
 
   if (!bce_->emit1(JSOp::Pop)) {
     //              [stack] CTOR
     return false;
   }
 
   if (name_) {
-    MOZ_ASSERT(tdzCache_.isSome());
     MOZ_ASSERT(innerScope_.isSome());
 
     if (!bce_->emitLexicalInitialization(name_)) {
       //            [stack] CTOR
       return false;
     }
+  }
 
-    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
     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
   //                [stack] CTOR
 
   strictMode_.restore();
 
 #ifdef DEBUG
--- a/js/src/frontend/ObjectEmitter.h
+++ b/js/src/frontend/ObjectEmitter.h
@@ -676,19 +676,29 @@ class MOZ_STACK_CLASS ClassEmitter : pub
   //     |               | emitFieldInitializersEnd
   //     |               |
   //     |      +--------v-------------------+
   //     |      | StaticFieldInitializersEnd |
   //     |      +----------------------------+
   //     |               |
   //     +<--------------+
   //     |
-  //     | (do PropertyEmitter operation)  emitEnd  +-----+
-  //     +-------------------------------+--------->| End |
-  //                                                +-----+
+  //     | (do PropertyEmitter operation)
+  //     +--------------------------------+
+  //                                      |
+  //     +-------------+    emitBinding   |
+  //     |  BoundName  |<-----------------+
+  //     +--+----------+
+  //        |
+  //        | emitEnd
+  //        |
+  //     +--v----+
+  //     |  End  |
+  //     +-------+
+  //
   // clang-format on
   enum class ClassState {
     // The initial state.
     Start,
 
     // After calling emitScope.
     Scope,
 
@@ -705,16 +715,19 @@ class MOZ_STACK_CLASS ClassEmitter : pub
     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
@@ -794,16 +807,18 @@ class MOZ_STACK_CLASS ClassEmitter : pub
 
   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:
   MOZ_MUST_USE bool initProtoAndCtor();
 };
 
 } /* namespace frontend */
 } /* namespace js */
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -7002,18 +7002,20 @@ bool GeneralParser<ParseHandler, Unit>::
 
   if (propType == PropertyType::Field) {
     if (!options().fieldsEnabledOption) {
       errorAt(propNameOffset, JSMSG_FIELDS_NOT_SUPPORTED);
       return false;
     }
 
     if (isStatic) {
-      errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF);
-      return false;
+      if (propAtom == cx_->names().prototype) {
+        errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF);
+        return false;
+      }
     }
 
     if (propAtom == cx_->names().constructor) {
       errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF);
       return false;
     }
 
     if (!abortIfSyntaxParser()) {