diff options
author | Martok <martok@martoks-place.de> | 2023-04-06 03:10:50 +0200 |
---|---|---|
committer | Martok <martok@martoks-place.de> | 2023-05-01 17:16:17 +0200 |
commit | 3cbe292dc61a77fc1d1d55b46bf7898ec1acaef7 (patch) | |
tree | 6dccc14463a403a4d37b9286d7a34ff405d5fdf6 | |
parent | 7647230e93f4e9c97e38eddf72351aeceb830b26 (diff) | |
download | uxp-3cbe292dc61a77fc1d1d55b46bf7898ec1acaef7.tar.gz |
Issue #2142 - Implement syntax for public/private fields and computed field names
This state still has the initializers scoped on .initializers local variable, which will be changed later.
Based-on: m-c 1499448, 1530084, 1530832, 1529448 (partial), 1532921, 1528039, 1528038, 1535166, 1550628,
1535166, 1550628, 1541641, 1547133, 1540787, 1535804/9
-rw-r--r-- | js/src/builtin/ReflectParse.cpp | 87 | ||||
-rw-r--r-- | js/src/frontend/BytecodeCompiler.h | 10 | ||||
-rw-r--r-- | js/src/frontend/BytecodeEmitter.cpp | 296 | ||||
-rw-r--r-- | js/src/frontend/BytecodeEmitter.h | 9 | ||||
-rw-r--r-- | js/src/frontend/FoldConstants.cpp | 18 | ||||
-rw-r--r-- | js/src/frontend/FullParseHandler.h | 46 | ||||
-rw-r--r-- | js/src/frontend/NameFunctions.cpp | 20 | ||||
-rw-r--r-- | js/src/frontend/ParseNode.cpp | 11 | ||||
-rw-r--r-- | js/src/frontend/ParseNode.h | 76 | ||||
-rw-r--r-- | js/src/frontend/Parser.cpp | 775 | ||||
-rw-r--r-- | js/src/frontend/Parser.h | 38 | ||||
-rw-r--r-- | js/src/frontend/SharedContext.h | 10 | ||||
-rw-r--r-- | js/src/frontend/SyntaxParseHandler.h | 9 | ||||
-rw-r--r-- | js/src/frontend/TokenKind.h | 2 | ||||
-rw-r--r-- | js/src/frontend/TokenStream.cpp | 178 | ||||
-rw-r--r-- | js/src/frontend/TokenStream.h | 10 | ||||
-rw-r--r-- | js/src/js.msg | 4 | ||||
-rw-r--r-- | js/src/jsapi.cpp | 2 | ||||
-rw-r--r-- | js/src/jsast.tbl | 1 | ||||
-rw-r--r-- | js/src/jsscript.cpp | 24 | ||||
-rw-r--r-- | js/src/jsscript.h | 39 | ||||
-rw-r--r-- | js/src/vm/CommonPropertyNames.h | 2 | ||||
-rw-r--r-- | js/src/vm/Opcodes.h | 4 | ||||
-rw-r--r-- | js/src/wasm/AsmJS.cpp | 2 |
24 files changed, 1383 insertions, 290 deletions
diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index 4aa7f1640b..2902d2b724 100644 --- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -539,9 +539,11 @@ class NodeBuilder MOZ_MUST_USE bool classDefinition(bool expr, HandleValue name, HandleValue heritage, HandleValue block, TokenPos* pos, MutableHandleValue dst); - MOZ_MUST_USE bool classMethods(NodeVector& methods, MutableHandleValue dst); + MOZ_MUST_USE bool classMembers(NodeVector& members, MutableHandleValue dst); MOZ_MUST_USE bool classMethod(HandleValue name, HandleValue body, PropKind kind, bool isStatic, TokenPos* pos, MutableHandleValue dst); + MOZ_MUST_USE bool classField(HandleValue name, HandleValue initializer, + TokenPos* pos, MutableHandleValue dst); /* * expressions @@ -1721,9 +1723,23 @@ NodeBuilder::classMethod(HandleValue name, HandleValue body, PropKind kind, bool } bool -NodeBuilder::classMethods(NodeVector& methods, MutableHandleValue dst) +NodeBuilder::classField(HandleValue name, HandleValue initializer, + TokenPos* pos, MutableHandleValue dst) { - return newArray(methods, dst); + RootedValue cb(cx, callbacks[AST_CLASS_FIELD]); + if (!cb.isNull()) + return callback(cb, name, initializer, pos, dst); + + return newNode(AST_CLASS_FIELD, pos, + "name", name, + "init", initializer, + dst); +} + +bool +NodeBuilder::classMembers(NodeVector& members, MutableHandleValue dst) +{ + return newArray(members, dst); } bool @@ -1853,6 +1869,7 @@ class ASTSerializer bool property(ParseNode* pn, MutableHandleValue dst); bool classMethod(ClassMethod* classMethod, MutableHandleValue dst); + bool classField(ClassField* classField, MutableHandleValue dst); bool optIdentifier(HandleAtom atom, TokenPos* pos, MutableHandleValue dst) { if (!atom) { @@ -2457,7 +2474,7 @@ ASTSerializer::classDefinition(ClassNode* pn, bool expr, MutableHandleValue dst) } return optExpression(pn->heritage(), &heritage) && - statement(pn->methodList(), &classBody) && + statement(pn->memberList(), &classBody) && builder.classDefinition(expr, className, heritage, classBody, &pn->pn_pos, dst); } @@ -2676,24 +2693,34 @@ ASTSerializer::statement(ParseNode* pn, MutableHandleValue dst) case PNK_CLASS: return classDefinition(&pn->as<ClassNode>(), false, dst); - case PNK_CLASSMETHODLIST: + case PNK_CLASSMEMBERLIST: { - ListNode* methodList = &pn->as<ListNode>(); - NodeVector methods(cx); - if (!methods.reserve(methodList->count())) + ListNode* memberList = &pn->as<ListNode>(); + NodeVector members(cx); + if (!members.reserve(memberList->count())) return false; - for (ParseNode* item : methodList->contents()) { - ClassMethod* method = &item->as<ClassMethod>(); - MOZ_ASSERT(methodList->pn_pos.encloses(method->pn_pos)); + for (ParseNode* item : memberList->contents()) { + if (item->is<ClassField>()) { + ClassField* field = &item->as<ClassField>(); + MOZ_ASSERT(memberList->pn_pos.encloses(field->pn_pos)); - RootedValue prop(cx); - if (!classMethod(method, &prop)) - return false; - methods.infallibleAppend(prop); + RootedValue prop(cx); + if (!classField(field, &prop)) + return false; + members.infallibleAppend(prop); + } else { + ClassMethod* method = &item->as<ClassMethod>(); + MOZ_ASSERT(memberList->pn_pos.encloses(method->pn_pos)); + + RootedValue prop(cx); + if (!classMethod(method, &prop)) + return false; + members.infallibleAppend(prop); + } } - return builder.classMethods(methods, dst); + return builder.classMembers(members, dst); } case PNK_NOP: @@ -2733,6 +2760,34 @@ ASTSerializer::classMethod(ClassMethod* classMethod, MutableHandleValue dst) } bool +ASTSerializer::classField(ClassField* classField, MutableHandleValue dst) +{ + RootedValue key(cx), val(cx); + // Dig through the lambda and get to the actual expression + if (classField->initializer()) { + ParseNode* value = classField->initializer() + ->body() + ->head()->as<LexicalScopeNode>() + .scopeBody()->as<ListNode>() + .head()->as<UnaryNode>() + .kid()->as<AssignmentNode>() + .right(); + // RawUndefinedExpr is the node we use for "there is no initializer". If one + // writes, literally, `x = undefined;`, it will not be a RawUndefinedExpr + // node, but rather a variable reference. + // Behavior for "there is no initializer" should be { ..., "init": null } + if (value->getKind() != PNK_RAW_UNDEFINED) { + if (!expression(value, &val)) + return false; + } else { + val.setNull(); + } + } + return propertyName(&classField->name(), &key) && + builder.classField(key, val, &classField->pn_pos, dst); +} + +bool ASTSerializer::leftAssociate(ListNode* node, MutableHandleValue dst) { MOZ_ASSERT(!node->empty()); diff --git a/js/src/frontend/BytecodeCompiler.h b/js/src/frontend/BytecodeCompiler.h index 6d03f8f9fb..471e9e36d9 100644 --- a/js/src/frontend/BytecodeCompiler.h +++ b/js/src/frontend/BytecodeCompiler.h @@ -110,14 +110,22 @@ CreateScriptSourceObject(ExclusiveContext* cx, const ReadOnlyCompileOptions& opt bool IsIdentifier(JSLinearString* str); +bool +IsIdentifierNameOrPrivateName(JSLinearString* str); + /* * As above, but taking chars + length. */ bool -IsIdentifier(const char* chars, size_t length); +IsIdentifier(const Latin1Char* chars, size_t length); bool IsIdentifier(const char16_t* chars, size_t length); +bool +IsIdentifierNameOrPrivateName(const Latin1Char* chars, size_t length); +bool +IsIdentifierNameOrPrivateName(const char16_t* chars, size_t length); + /* True if str is a keyword. Defined in TokenStream.cpp. */ bool IsKeyword(JSLinearString* str); diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 51ebd81969..c18b5d933f 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -174,6 +174,10 @@ BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, innermostNestableControl(nullptr), innermostEmitterScope_(nullptr), innermostTDZCheckCache(nullptr), + fieldInitializers_(parent + ? parent->fieldInitializers_ + : lazyScript ? lazyScript->getFieldInitializers() + : FieldInitializers::Invalid()), #ifdef DEBUG unstableEmitterScope(false), #endif @@ -1058,6 +1062,7 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) return true; case PNK_OBJECT_PROPERTY_NAME: + case PNK_PRIVATE_NAME: // no side effects, unlike PNK_NAME case PNK_STRING: case PNK_TEMPLATE_STRING: MOZ_ASSERT(pn->is<NameNode>()); @@ -1515,8 +1520,9 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) case PNK_FOROF: // by PNK_FOR/PNK_COMPREHENSIONFOR case PNK_FORHEAD: // by PNK_FOR/PNK_COMPREHENSIONFOR case PNK_CLASSMETHOD: // by PNK_CLASS + case PNK_CLASSFIELD: // by PNK_CLASS case PNK_CLASSNAMES: // by PNK_CLASS - case PNK_CLASSMETHODLIST: // by PNK_CLASS + case PNK_CLASSMEMBERLIST: // by PNK_CLASS case PNK_IMPORT_SPEC_LIST: // by PNK_IMPORT case PNK_IMPORT_SPEC: // by PNK_IMPORT case PNK_EXPORT_BATCH_SPEC:// by PNK_EXPORT @@ -2381,6 +2387,64 @@ BytecodeEmitter::emitScript(ParseNode* body) return true; } +bool BytecodeEmitter::emitInitializeInstanceFields() +{ + MOZ_ASSERT(fieldInitializers_.valid); + size_t numFields = fieldInitializers_.numFieldInitializers; + + if (numFields == 0) { + return true; + } + + if (!emitGetName(cx->names().dotInitializers)) { + // [stack] ARRAY + return false; + } + + for (size_t fieldIndex = 0; fieldIndex < numFields; fieldIndex++) { + 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] ARRAY ARRAY + return false; + } + } + + if (!emitNumberOp(fieldIndex)) { + // [stack] 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 `this`, and the + // receiver of this getelem is `.initializers`) + if (!emit1(JSOP_GETELEM)) { + // [stack] ARRAY? FUNC + return false; + } + + // This is guaranteed to run after super(), so we don't need TDZ checks. + if (!emitGetName(cx->names().dotThis)) { + // [stack] ARRAY? FUNC THIS + return false; + } + + if (!emitCall(JSOP_CALL_IGNORES_RV, 0)) { + // [stack] ARRAY? RVAL + return false; + } + + if (!emit1(JSOP_POP)) { + // [stack] ARRAY? + return false; + } + } + + return true; +} + bool BytecodeEmitter::emitFunctionScript(FunctionNode* funNode) { @@ -7718,6 +7782,12 @@ bool BytecodeEmitter::emitPropertyList(ListNode* obj, MutableHandlePlainObject objp, PropListType type) { for (ParseNode* propdef : obj->contents()) { + if (propdef->is<ClassField>()) { + // Skip over class fields and emit them at the end. This is needed + // because they're all emitted into a single array, which is then stored + // into a local variable + continue; + } if (!updateSourceCoordNotes(propdef->pn_pos.begin)) return false; @@ -7877,9 +7947,186 @@ BytecodeEmitter::emitPropertyList(ListNode* obj, MutableHandlePlainObject objp, return false; } } + + if (obj->getKind() == PNK_CLASSMEMBERLIST) { + if (!emitCreateFieldKeys(obj)) + return false; + if (!emitCreateFieldInitializers(obj)) + return false; + } + + return true; +} + +FieldInitializers +BytecodeEmitter::setupFieldInitializers(ListNode* classMembers) +{ + size_t numFields = 0; + + for (ParseNode* propdef : classMembers->contents()) { + if (propdef->is<ClassField>()) { + FunctionNode* initializer = propdef->as<ClassField>().initializer(); + // Don't include fields without initializers. + if (initializer != nullptr) { + numFields++; + } + continue; + } + } + + return FieldInitializers(numFields); +} + +// Purpose of .fieldKeys: +// Computed field names (`["x"] = 2;`) must be ran at class-evaluation time, not +// object construction time. The transformation to do so is roughly as follows: +// +// class C { +// [keyExpr] = valueExpr; +// } +// --> +// let .fieldKeys = [keyExpr]; +// let .initializers = [ +// () => { +// this[.fieldKeys[0]] = valueExpr; +// } +// ]; +// class C { +// constructor() { +// .initializers[0](); +// } +// } +// +// BytecodeEmitter::emitCreateFieldKeys does `let .fieldKeys = [keyExpr, ...];` +// See Parser::fieldInitializer for the `this[.fieldKeys[0]]` part. +bool +BytecodeEmitter::emitCreateFieldKeys(ListNode* obj) +{ + size_t numFieldKeys = 0; + for (ParseNode* propdef : obj->contents()) { + if (propdef->is<ClassField>()) { + ClassField* field = &propdef->as<ClassField>(); + if (field->name().getKind() == PNK_COMPUTED_NAME) { + numFieldKeys++; + } + } + } + + if (numFieldKeys == 0) + return true; + + NameOpEmitter noe(this, cx->names().dotFieldKeys, + NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) + return false; + + if (!emitUint32Operand(JSOP_NEWARRAY, numFieldKeys)) { + // [stack] ARRAY + return false; + } + + size_t curFieldKeyIndex = 0; + for (ParseNode* propdef : obj->contents()) { + if (propdef->is<ClassField>()) { + ClassField* field = &propdef->as<ClassField>(); + if (field->name().getKind() == PNK_COMPUTED_NAME) { + ParseNode* nameExpr = field->name().as<UnaryNode>().kid(); + + if (!emitTree(nameExpr)) { + // [stack] ARRAY KEY + return false; + } + + if (!emit1(JSOP_TOID)) { + // [stack] ARRAY KEY + return false; + } + + if (!emitUint32Operand(JSOP_INITELEM_ARRAY, curFieldKeyIndex)) { + // [stack] ARRAY + return false; + } + + curFieldKeyIndex++; + } + } + } + MOZ_ASSERT(curFieldKeyIndex == numFieldKeys); + + if (!noe.emitAssignment()) { + // [stack] ARRAY + return false; + } + + if (!emit1(JSOP_POP)) { + // [stack] + return false; + } + + return true; +} + +bool +BytecodeEmitter::emitCreateFieldInitializers(ListNode* obj) +{ + const FieldInitializers& fieldInitializers = fieldInitializers_; + MOZ_ASSERT(fieldInitializers.valid); + size_t numFields = fieldInitializers.numFieldInitializers; + + if (numFields == 0) + return true; + + // .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. + + NameOpEmitter noe(this, cx->names().dotInitializers, + NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + return false; + } + + if (!emitUint32Operand(JSOP_NEWARRAY, numFields)) { + // [stack] CTOR? OBJ ARRAY + return false; + } + + size_t curFieldIndex = 0; + for (ParseNode* propdef : obj->contents()) { + if (propdef->is<ClassField>()) { + FunctionNode* initializer = propdef->as<ClassField>().initializer(); + if (initializer == nullptr) { + continue; + } + + if (!emitTree(initializer)) { + // [stack] CTOR? OBJ ARRAY LAMBDA + return false; + } + + if (!emitUint32Operand(JSOP_INITELEM_ARRAY, curFieldIndex)) { + // [stack] CTOR? OBJ ARRAY + return false; + } + + curFieldIndex++; + } + } + + if (!noe.emitAssignment()) { + // [stack] CTOR? OBJ ARRAY + return false; + } + + if (!emit1(JSOP_POP)) { + // [stack] CTOR? OBJ + 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 @@ -8414,6 +8661,11 @@ BytecodeEmitter::emitFunctionBody(ParseNode* funBody) { FunctionBox* funbox = sc->asFunctionBox(); + if (funbox->function()->kind() == JSFunction::FunctionKind::ClassConstructor) { + if (!emitInitializeInstanceFields()) + return false; + } + if (!emitTree(funBody)) return false; @@ -8484,6 +8736,21 @@ BytecodeEmitter::emitLexicalInitialization(ParseNode* pn) return true; } +class AutoResetFieldInitializers +{ + BytecodeEmitter* bce; + FieldInitializers oldFieldInfo; + + public: + AutoResetFieldInitializers(BytecodeEmitter* bce, FieldInitializers newFieldInfo) + : bce(bce), oldFieldInfo(bce->fieldInitializers_) + { + bce->fieldInitializers_ = newFieldInfo; + } + + ~AutoResetFieldInitializers() { bce->fieldInitializers_ = oldFieldInfo; } +}; + // This follows ES6 14.5.14 (ClassDefinitionEvaluation) and ES6 14.5.15 // (BindingClassDeclarationEvaluation). bool @@ -8491,21 +8758,26 @@ BytecodeEmitter::emitClass(ClassNode* classNode) { ClassNames* names = classNode->names(); ParseNode* heritageExpression = classNode->heritage(); - ListNode* classMethods = classNode->methodList(); + ListNode* classMembers = classNode->memberList(); FunctionNode* constructor = nullptr; - for (ParseNode* mn : classMethods->contents()) { - ClassMethod& method = mn->as<ClassMethod>(); - ParseNode& methodName = method.name(); - if (!method.isStatic() && - (methodName.isKind(PNK_OBJECT_PROPERTY_NAME) || methodName.isKind(PNK_STRING)) && - methodName.as<NameNode>().atom() == cx->names().constructor) - { - constructor = &method.method(); - break; + for (ParseNode* mn : classMembers->contents()) { + if (mn->is<ClassMethod>()) { + ClassMethod& method = mn->as<ClassMethod>(); + ParseNode& methodName = method.name(); + if (!method.isStatic() && + (methodName.isKind(PNK_OBJECT_PROPERTY_NAME) || methodName.isKind(PNK_STRING)) && + methodName.as<NameNode>().atom() == cx->names().constructor) + { + constructor = &method.method(); + break; + } } } + // set this->fieldInitializers_ + AutoResetFieldInitializers _innermostClassAutoReset(this, setupFieldInitializers(classMembers)); + bool savedStrictness = sc->setLocalStrictMode(true); Maybe<TDZCheckCache> tdzCache; @@ -8577,7 +8849,7 @@ BytecodeEmitter::emitClass(ClassNode* classNode) return false; RootedPlainObject obj(cx); - if (!emitPropertyList(classMethods, &obj, ClassBody)) + if (!emitPropertyList(classMembers, &obj, ClassBody)) return false; if (!emit1(JSOP_POP)) diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 9cbf6ee38b..27f4e92cbd 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -177,6 +177,9 @@ struct MOZ_STACK_CLASS BytecodeEmitter EmitterScope* innermostEmitterScope_; TDZCheckCache* innermostTDZCheckCache; + /* field info for enclosing class */ + FieldInitializers fieldInitializers_; + #ifdef DEBUG bool unstableEmitterScope; @@ -415,6 +418,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter // encompasses the entire source. MOZ_MUST_USE bool emitScript(ParseNode* body); + MOZ_MUST_USE bool emitInitializeInstanceFields(); + // Emit function code for the tree rooted at body. MOZ_MUST_USE bool emitFunctionScript(FunctionNode* funNode); @@ -514,6 +519,10 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitPropertyList(ListNode* obj, MutableHandlePlainObject objp, PropListType type); + FieldInitializers setupFieldInitializers(ListNode* classMembers); + MOZ_MUST_USE bool emitCreateFieldKeys(ListNode* obj); + MOZ_MUST_USE bool emitCreateFieldInitializers(ListNode* obj); + // 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 diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index 35e4b86e0b..eda40a0836 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -377,6 +377,7 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result) case PNK_OPTELEM: case PNK_OPTCALL: case PNK_NAME: + case PNK_PRIVATE_NAME: case PNK_TEMPLATE_STRING: case PNK_TEMPLATE_STRING_LIST: case PNK_TAGGED_TEMPLATE: @@ -400,8 +401,8 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result) case PNK_FORIN: case PNK_FOROF: case PNK_FORHEAD: - case PNK_CLASSMETHOD: - case PNK_CLASSMETHODLIST: + case PNK_CLASSFIELD: + case PNK_CLASSMEMBERLIST: case PNK_CLASSNAMES: case PNK_NEWTARGET: case PNK_IMPORT_META: @@ -1679,6 +1680,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo return true; case PNK_OBJECT_PROPERTY_NAME: + case PNK_PRIVATE_NAME: case PNK_STRING: case PNK_TEMPLATE_STRING: MOZ_ASSERT(pn->is<NameNode>()); @@ -1810,7 +1812,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo case PNK_OBJECT: case PNK_ARRAYCOMP: case PNK_STATEMENTLIST: - case PNK_CLASSMETHODLIST: + case PNK_CLASSMEMBERLIST: case PNK_CATCHLIST: case PNK_TEMPLATE_STRING_LIST: case PNK_VAR: @@ -1902,6 +1904,16 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo Fold(cx, node->unsafeRightReference(), parser, inGenexpLambda); } + case PNK_CLASSFIELD: { + ClassField* node = &pn->as<ClassField>(); + if (node->initializer()) { + if (!Fold(cx, node->unsafeRightReference(), parser, inGenexpLambda)) { + return false; + } + } + return true; + } + case PNK_NEWTARGET: case PNK_IMPORT_META:{ #ifdef DEBUG diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index a9f7c6de09..3f52e88d54 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -367,11 +367,11 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) return literal; } - ClassNodeType newClass(Node name, Node heritage, Node methodBlock, const TokenPos& pos) { - return new_<ClassNode>(name, heritage, methodBlock, pos); + ClassNodeType newClass(Node name, Node heritage, Node memberBlock, const TokenPos& pos) { + return new_<ClassNode>(name, heritage, memberBlock, pos); } - ListNodeType newClassMethodList(uint32_t begin) { - return new_<ListNode>(PNK_CLASSMETHODLIST, TokenPos(begin, begin + 1)); + ListNodeType newClassMemberList(uint32_t begin) { + return new_<ListNode>(PNK_CLASSMEMBERLIST, TokenPos(begin, begin + 1)); } ClassNamesType newClassNames(Node outer, Node inner, const TokenPos& pos) { return new_<ClassNames>(outer, inner, pos); @@ -457,19 +457,28 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) return true; } - MOZ_MUST_USE bool addClassMethodDefinition(ListNodeType methodList, Node key, FunctionNodeType funNode, + MOZ_MUST_USE bool addClassMethodDefinition(ListNodeType memberList, Node key, FunctionNodeType funNode, JSOp op, bool isStatic) { - MOZ_ASSERT(methodList->isKind(PNK_CLASSMETHODLIST)); - MOZ_ASSERT(key->isKind(PNK_NUMBER) || - key->isKind(PNK_OBJECT_PROPERTY_NAME) || - key->isKind(PNK_STRING) || - key->isKind(PNK_COMPUTED_NAME)); + MOZ_ASSERT(memberList->isKind(PNK_CLASSMEMBERLIST)); + MOZ_ASSERT(isUsableAsObjectPropertyName(key)); ClassMethod* classMethod = new_<ClassMethod>(key, funNode, op, isStatic); if (!classMethod) return false; - methodList->append(classMethod); + memberList->append(classMethod); + return true; + } + + MOZ_MUST_USE bool addClassFieldDefinition(ListNodeType memberList, Node name, FunctionNodeType initializer) + { + MOZ_ASSERT(memberList->isKind(PNK_CLASSMEMBERLIST)); + MOZ_ASSERT(isUsableAsObjectPropertyName(name)); + + ParseNode* classField = new_<ClassField>(name, initializer); + if (!classField) + return false; + memberList->append(classField); return true; } @@ -732,8 +741,8 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) pn->setDirectRHSAnonFunction(true); } - FunctionNodeType newFunction(FunctionSyntaxKind syntaxKind) { - return new_<FunctionNode>(syntaxKind, pos()); + FunctionNodeType newFunction(FunctionSyntaxKind syntaxKind, const TokenPos& pos) { + return new_<FunctionNode>(syntaxKind, pos); } bool setComprehensionLambdaBody(FunctionNodeType funNode, ListNodeType body) { @@ -819,6 +828,13 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) return node->isKind(PNK_SUPERBASE); } + bool isUsableAsObjectPropertyName(ParseNode* node) { + return node->isKind(PNK_NUMBER) || + node->isKind(PNK_OBJECT_PROPERTY_NAME) || + node->isKind(PNK_STRING) || + node->isKind(PNK_COMPUTED_NAME); + } + inline MOZ_MUST_USE bool finishInitializerAssignment(NameNodeType nameNode, Node init); void setBeginPosition(Node pn, Node oth) { @@ -853,9 +869,9 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) return new_<ListNode>(kind, op, pos()); } - ListNodeType newList(ParseNodeKind kind, uint32_t begin, JSOp op = JSOP_NOP) { + ListNodeType newList(ParseNodeKind kind, const TokenPos& pos, JSOp op = JSOP_NOP) { MOZ_ASSERT(!isDeclarationKind(kind)); - return new_<ListNode>(kind, op, TokenPos(begin, begin + 1)); + return new_<ListNode>(kind, op, pos); } ListNodeType newList(ParseNodeKind kind, Node kid, JSOp op = JSOP_NOP) { diff --git a/js/src/frontend/NameFunctions.cpp b/js/src/frontend/NameFunctions.cpp index ed3361c33a..6ddb554ad7 100644 --- a/js/src/frontend/NameFunctions.cpp +++ b/js/src/frontend/NameFunctions.cpp @@ -83,6 +83,7 @@ class NameResolver } case PNK_NAME: + case PNK_PRIVATE_NAME: *foundName = true; return buf->append(n->as<NameNode>().atom()); @@ -136,6 +137,7 @@ class NameResolver return cur; switch (cur->getKind()) { + case PNK_PRIVATE_NAME: case PNK_NAME: return cur; /* found the initialized declaration */ case PNK_THIS: return cur; /* Setting a property of 'this'. */ case PNK_FUNCTION: return nullptr; /* won't find an assignment or declaration */ @@ -404,6 +406,7 @@ class NameResolver break; case PNK_OBJECT_PROPERTY_NAME: + case PNK_PRIVATE_NAME: case PNK_STRING: case PNK_TEMPLATE_STRING: MOZ_ASSERT(cur->is<NameNode>()); @@ -498,6 +501,19 @@ class NameResolver break; } + case PNK_CLASSFIELD: { + ClassField* node = &cur->as<ClassField>(); + if (!resolve(&node->name(), prefix)) { + return false; + } + if (ParseNode* init = node->initializer()) { + if (!resolve(init, prefix)) { + return false; + } + } + break; + } + case PNK_ELEM: { PropertyByValue* elem = &cur->as<PropertyByValue>(); if (!elem->isSuper() && !resolve(&elem->expression(), prefix)) @@ -643,7 +659,7 @@ class NameResolver if (!resolve(heritage, prefix)) return false; } - if (!resolve(classNode->methodList(), prefix)) + if (!resolve(classNode->memberList(), prefix)) return false; break; } @@ -757,7 +773,7 @@ class NameResolver } case PNK_OBJECT: - case PNK_CLASSMETHODLIST: + case PNK_CLASSMEMBERLIST: for (ParseNode* element : cur->as<ListNode>().contents()) { if (!resolve(element, prefix)) return false; diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index a19bdfc6eb..00bbd7afee 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -370,6 +370,14 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) return PushResult::Recyclable; } + case PNK_CLASSFIELD: { + BinaryNode* bn = &pn->as<BinaryNode>(); + stack->push(bn->left()); + if (bn->right()) + stack->push(bn->right()); + return PushResult::Recyclable; + } + // Ternary nodes with all children non-null. case PNK_CONDITIONAL: { TernaryNode* tn = &pn->as<TernaryNode>(); @@ -494,7 +502,7 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) case PNK_IMPORT_SPEC_LIST: case PNK_EXPORT_SPEC_LIST: case PNK_PARAMSBODY: - case PNK_CLASSMETHODLIST: + case PNK_CLASSMEMBERLIST: return PushListNodeChildren(&pn->as<ListNode>(), stack); // Array comprehension nodes are lists with a single child: @@ -881,6 +889,7 @@ NameNode::dump(int indent) } case PNK_NAME: + case PNK_PRIVATE_NAME: // atom() already includes the '#', no need to specially include it. case PNK_PROPERTYNAME: { if (!atom()) { fprintf(stderr, "#<null name>"); diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 2f6311eace..fffda6a73d 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -49,6 +49,7 @@ class ObjectBox; F(CALL) \ F(ARGUMENTS) \ F(NAME) \ + F(PRIVATE_NAME) \ F(OBJECT_PROPERTY_NAME) \ F(COMPUTED_NAME) \ F(NUMBER) \ @@ -116,7 +117,8 @@ class ObjectBox; F(MUTATEPROTO) \ F(CLASS) \ F(CLASSMETHOD) \ - F(CLASSMETHODLIST) \ + F(CLASSFIELD) \ + F(CLASSMEMBERLIST) \ F(CLASSNAMES) \ F(NEWTARGET) \ F(POSHOLDER) \ @@ -251,19 +253,22 @@ IsTypeofKind(ParseNodeKind kind) * kid1: PNK_CLASSNAMES for class name. can be null for anonymous class. * kid2: expression after `extends`. null if no expression * kid3: either of - * * PNK_CLASSMETHODLIST, if anonymous class - * * PNK_LEXICALSCOPE which contains PNK_CLASSMETHODLIST as scopeBody, + * * PNK_CLASSMEMBERLIST, if anonymous class + * * PNK_LEXICALSCOPE which contains PNK_CLASSMEMBERLIST as scopeBody, * if named class * PNK_CLASSNAMES (ClassNames) * left: Name node for outer binding, or null if the class is an expression * that doesn't create an outer binding * right: Name node for inner binding - * PNK_CLASSMETHODLIST (ListNode) - * head: list of N PNK_CLASSMETHOD nodes + * PNK_CLASSMEMBERLIST (ListNode) + * head: list of N PNK_CLASSMETHOD or PNK_CLASSFIELD nodes * count: N >= 0 * PNK_CLASSMETHOD (ClassMethod) * name: propertyName * method: methodDefinition + * PNK_CLASSFIELD (ClassField) + * name: fieldName + * initializer: field initializer or null * PNK_MODULE (ModuleNode) * body: statement list of the module * @@ -567,6 +572,7 @@ enum ParseNodeArity macro(AssignmentNode, AssignmentNodeType, asAssignment) \ macro(CaseClause, CaseClauseType, asCaseClause) \ macro(ClassMethod, ClassMethodType, asClassMethod) \ + macro(ClassField, ClassFieldType, asClassField) \ macro(ClassNames, ClassNamesType, asClassNames) \ macro(ForNode, ForNodeType, asFor) \ macro(PropertyAccess, PropertyAccessType, asPropertyAccess) \ @@ -780,6 +786,12 @@ class ParseNode } name; struct { private: + friend class ClassField; + ParseNode* name; + ParseNode* initializer; /* field initializer - optional */ + } field; + struct { + private: friend class RegExpLiteral; ObjectBox* objbox; } regexp; @@ -1233,7 +1245,7 @@ class ListNode : public ParseNode MOZ_MUST_USE bool hasNonConstInitializer() const { MOZ_ASSERT(isKind(PNK_ARRAY) || isKind(PNK_OBJECT) || - isKind(PNK_CLASSMETHODLIST)); + isKind(PNK_CLASSMEMBERLIST)); return pn_u.list.xflags & hasNonConstInitializerBit; } @@ -1250,7 +1262,7 @@ class ListNode : public ParseNode void setHasNonConstInitializer() { MOZ_ASSERT(isKind(PNK_ARRAY) || isKind(PNK_OBJECT) || - isKind(PNK_CLASSMETHODLIST)); + isKind(PNK_CLASSMEMBERLIST)); pn_u.list.xflags |= hasNonConstInitializerBit; } @@ -1874,9 +1886,9 @@ class NullLiteral : public NullaryNode } }; -// This is only used internally, currently just for tagged templates. -// It represents the value 'undefined' (aka `void 0`), like NullLiteral -// represents the value 'null'. +// This is only used internally, currently just for tagged templates and the +// initial value of fields without initializers. It represents the value +// 'undefined' (aka `void 0`), like NullLiteral represents the value 'null'. class RawUndefinedLiteral : public NullaryNode { public: @@ -2124,6 +2136,30 @@ class ClassMethod : public BinaryNode } }; + +class ClassField : public BinaryNode +{ + public: + ClassField(ParseNode* name, ParseNode* initializer) + : BinaryNode(PNK_CLASSFIELD, JSOP_NOP, + initializer == nullptr ? name->pn_pos : TokenPos::box(name->pn_pos, initializer->pn_pos), + name, initializer) + { + } + + static bool test(const ParseNode& node) { + bool match = node.isKind(PNK_CLASSFIELD); + MOZ_ASSERT_IF(match, node.isArity(PN_BINARY)); + return match; + } + + ParseNode& name() const { return *left(); } + + FunctionNode* initializer() const { + return right() ? &right()->as<FunctionNode>() : nullptr; + } +}; + class SwitchStatement : public BinaryNode { public: @@ -2207,13 +2243,13 @@ class ClassNames : public BinaryNode class ClassNode : public TernaryNode { public: - ClassNode(ParseNode* names, ParseNode* heritage, ParseNode* methodsOrBlock, + ClassNode(ParseNode* names, ParseNode* heritage, ParseNode* membersOrBlock, const TokenPos& pos) - : TernaryNode(PNK_CLASS, JSOP_NOP, names, heritage, methodsOrBlock, pos) + : TernaryNode(PNK_CLASS, JSOP_NOP, names, heritage, membersOrBlock, pos) { MOZ_ASSERT_IF(names, names->is<ClassNames>()); - MOZ_ASSERT(methodsOrBlock->is<LexicalScopeNode>() || - methodsOrBlock->isKind(PNK_CLASSMETHODLIST)); + MOZ_ASSERT(membersOrBlock->is<LexicalScopeNode>() || + membersOrBlock->isKind(PNK_CLASSMEMBERLIST)); } static bool test(const ParseNode& node) { @@ -2228,13 +2264,13 @@ class ClassNode : public TernaryNode ParseNode* heritage() const { return kid2(); } - ListNode* methodList() const { - ParseNode* methodsOrBlock = kid3(); - if (methodsOrBlock->isKind(PNK_CLASSMETHODLIST)) - return &methodsOrBlock->as<ListNode>(); + ListNode* memberList() const { + ParseNode* membersOrBlock = kid3(); + if (membersOrBlock->isKind(PNK_CLASSMEMBERLIST)) + return &membersOrBlock->as<ListNode>(); - ListNode* list = &methodsOrBlock->as<LexicalScopeNode>().scopeBody()->as<ListNode>(); - MOZ_ASSERT(list->isKind(PNK_CLASSMETHODLIST)); + ListNode* list = &membersOrBlock->as<LexicalScopeNode>().scopeBody()->as<ListNode>(); + MOZ_ASSERT(list->isKind(PNK_CLASSMEMBERLIST)); return list; } Handle<LexicalScope::Data*> scopeBindings() const { diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index a4d761a2a1..77fa337fb7 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -514,6 +514,7 @@ FunctionBox::initWithEnclosingParseContext(ParseContext* enclosing, FunctionSynt allowNewTarget_ = sc->allowNewTarget(); allowSuperProperty_ = sc->allowSuperProperty(); allowSuperCall_ = sc->allowSuperCall(); + allowArguments_ = sc->allowArguments(); needsThisTDZChecks_ = sc->needsThisTDZChecks(); thisBinding_ = sc->thisBinding(); } else { @@ -550,6 +551,16 @@ FunctionBox::initWithEnclosingParseContext(ParseContext* enclosing, FunctionSynt } void +FunctionBox::initFieldInitializer(ParseContext* enclosing, bool hasHeritage) +{ + this->initWithEnclosingParseContext(enclosing, FunctionSyntaxKind::Expression); + allowSuperProperty_ = false; + allowSuperCall_ = false; + allowArguments_ = false; + needsThisTDZChecks_ = hasHeritage; +} + +void FunctionBox::initWithEnclosingScope(Scope* enclosingScope) { if (!function()->isArrow()) { @@ -2294,7 +2305,7 @@ Parser<ParseHandler>::hasUsedFunctionSpecialName(HandlePropertyName name) template <typename ParseHandler> bool -Parser<ParseHandler>::declareFunctionThis() +Parser<ParseHandler>::declareFunctionThis(bool canSkipLazyClosedOverBindings) { // The asm.js validator does all its own symbol-table management so, as an // optimization, avoid doing any work here. @@ -2307,10 +2318,11 @@ Parser<ParseHandler>::declareFunctionThis() HandlePropertyName dotThis = context->names().dotThis; bool declareThis; - if (handler.canSkipLazyClosedOverBindings()) + if (canSkipLazyClosedOverBindings) declareThis = funbox->function()->lazyScript()->hasThisBinding(); else - declareThis = hasUsedFunctionSpecialName(dotThis) || funbox->isDerivedClassConstructor(); + declareThis = hasUsedFunctionSpecialName(dotThis) || + funbox->function()->kind() == JSFunction::FunctionKind::ClassConstructor; if (declareThis) { ParseContext::Scope& funScope = pc->functionScope(); @@ -2539,7 +2551,7 @@ Parser<FullParseHandler>::standaloneFunction(HandleFunction fun, tokenStream.ungetToken(); } - FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Statement); + FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Statement, pos()); if (!funNode) return null(); @@ -2585,7 +2597,7 @@ Parser<FullParseHandler>::standaloneFunction(HandleFunction fun, template <typename ParseHandler> bool -Parser<ParseHandler>::declareFunctionArgumentsObject() +Parser<ParseHandler>::declareFunctionArgumentsObject(bool canSkipLazyClosedOverBindings) { FunctionBox* funbox = pc->functionBox(); ParseContext::Scope& funScope = pc->functionScope(); @@ -2597,7 +2609,7 @@ Parser<ParseHandler>::declareFunctionArgumentsObject() HandlePropertyName argumentsName = context->names().arguments; bool tryDeclareArguments; - if (handler.canSkipLazyClosedOverBindings()) + if (canSkipLazyClosedOverBindings) tryDeclareArguments = funbox->function()->lazyScript()->shouldDeclareArguments(); else tryDeclareArguments = hasUsedFunctionSpecialName(argumentsName); @@ -2672,6 +2684,10 @@ Parser<ParseHandler>::functionBody(InHandling inHandling, YieldHandling yieldHan uint32_t startYieldOffset = pc->lastYieldOffset; #endif + // One might expect noteUsedName(".initializers") here when parsing a + // constructor. See Parser<ParseHandler>::classDefinition on why + // it's not here. + Node body; if (type == StatementListBody) { bool inheritedStrict = pc->sc()->strict(); @@ -2756,9 +2772,10 @@ Parser<ParseHandler>::functionBody(InHandling inHandling, YieldHandling yieldHan // finishing up the scope so these special bindings get marked as closed // over if necessary. Arrow functions don't have these bindings. if (kind != FunctionSyntaxKind::Arrow) { - if (!declareFunctionArgumentsObject()) + bool canSkipLazyClosedOverBindings = handler.canSkipLazyClosedOverBindings(); + if (!declareFunctionArgumentsObject(canSkipLazyClosedOverBindings)) return null(); - if (!declareFunctionThis()) + if (!declareFunctionThis(canSkipLazyClosedOverBindings)) return null(); } @@ -2769,7 +2786,7 @@ template <typename ParseHandler> JSFunction* Parser<ParseHandler>::newFunction(HandleAtom atom, FunctionSyntaxKind kind, GeneratorKind generatorKind, FunctionAsyncKind asyncKind, - HandleObject proto) + HandleObject proto /* = nullptr */) { MOZ_ASSERT_IF(kind == FunctionSyntaxKind::Statement, atom != nullptr); @@ -3594,7 +3611,7 @@ Parser<FullParseHandler>::standaloneLazyFunction(HandleFunction fun, bool strict syntaxKind = FunctionSyntaxKind::Arrow; } - FunctionNodeType funNode = handler.newFunction(syntaxKind); + FunctionNodeType funNode = handler.newFunction(syntaxKind, pos()); if (!funNode) return null(); @@ -3878,7 +3895,7 @@ Parser<ParseHandler>::functionStmt(uint32_t toStringStart, YieldHandling yieldHa return null(); } - FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Statement); + FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Statement, pos()); if (!funNode) return null(); @@ -3918,7 +3935,7 @@ Parser<ParseHandler>::functionExpr(uint32_t toStringStart, InvokedPrediction inv tokenStream.ungetToken(); } - FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Expression); + FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Expression, pos()); if (!funNode) return null(); @@ -4442,7 +4459,7 @@ Parser<ParseHandler>::objectBindingPattern(DeclarationKind kind, YieldHandling y TokenPos namePos = tokenStream.nextToken().pos; PropertyType propType; - Node propName = propertyName(yieldHandling, declKind, literal, &propType, &propAtom); + Node propName = propertyName(yieldHandling, PropertyNameInPattern, declKind, literal, &propType, &propAtom); if (!propName) return null(); if (propType == PropertyType::Normal) { @@ -7378,6 +7395,224 @@ JSOpFromPropertyType(PropertyType propType) } template <typename ParseHandler> +bool +Parser<ParseHandler>::classMember(YieldHandling yieldHandling, DefaultHandling defaultHandling, + const ParseContext::ClassStatement& classStmt, + HandlePropertyName className, + uint32_t classStartOffset, bool hasHeritage, + size_t& numFields, size_t& numFieldKeys, + ListNodeType& classMembers, bool* done) +{ + *done = false; + + TokenKind tt; + if (!tokenStream.getToken(&tt)) + return false; + + if (tt == TOK_RC) { + *done = true; + return true; + } + + if (tt == TOK_SEMI) + return true; + + bool isStatic = false; + if (tt == TOK_STATIC) { + if (!tokenStream.peekToken(&tt)) + return false; + if (tt == TOK_RC) { + tokenStream.consumeKnownToken(tt); + error(JSMSG_UNEXPECTED_TOKEN, "property name", TokenKindToDesc(tt)); + return false; + } + + if (tt != TOK_LP) { + isStatic = true; + } else { + tokenStream.ungetToken(); + } + } else { + tokenStream.ungetToken(); + } + + uint32_t propNameOffset; + if (!tokenStream.peekOffset(&propNameOffset)) + return false; + + RootedAtom propAtom(context); + PropertyType propType; + Node propName = propertyName(yieldHandling, PropertyNameInClass, /* maybeDecl = */ Nothing(), + classMembers, &propType, &propAtom); + if (!propName) + return false; + + 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 == context->names().constructor) { + errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF); + return false; + } + + if (!abortIfSyntaxParser()) + return false; + + numFields++; + + FunctionNodeType initializer = fieldInitializerOpt(yieldHandling, hasHeritage, propName, + propAtom, numFieldKeys); + if (!initializer) + return false; + + if (!tokenStream.getToken(&tt)) { + return false; + } + + // TODO(khyperia): Implement ASI + if (tt != TOK_SEMI) { + error(JSMSG_MISSING_SEMI_FIELD); + return false; + } + + return handler.addClassFieldDefinition(classMembers, propName, initializer); + } + + if (propType != PropertyType::Getter && propType != PropertyType::Setter && + propType != PropertyType::Method && propType != PropertyType::GeneratorMethod && + propType != PropertyType::AsyncMethod && propType != PropertyType::AsyncGeneratorMethod && + propType != PropertyType::Constructor && propType != PropertyType::DerivedConstructor) + { + errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF); + return false; + } + + if (propType == PropertyType::Getter) + propType = PropertyType::GetterNoExpressionClosure; + if (propType == PropertyType::Setter) + propType = PropertyType::SetterNoExpressionClosure; + + bool isConstructor = !isStatic && propAtom == context->names().constructor; + if (isConstructor) { + if (propType != PropertyType::Method) { + errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF); + return false; + } + if (classStmt.constructorBox) { + errorAt(propNameOffset, JSMSG_DUPLICATE_PROPERTY, "constructor"); + return false; + } + propType = hasHeritage ? PropertyType::DerivedConstructor + : PropertyType::Constructor; + } else if (isStatic && propAtom == context->names().prototype) { + errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF); + return false; + } + + RootedAtom funName(context); + switch (propType) { + case PropertyType::GetterNoExpressionClosure: + case PropertyType::SetterNoExpressionClosure: + if (!tokenStream.isCurrentTokenType(TOK_RB)) { + funName = prefixAccessorName(propType, propAtom); + if (!funName) + return false; + } + break; + case PropertyType::Constructor: + case PropertyType::DerivedConstructor: + funName = className; + break; + default: + if (!tokenStream.isCurrentTokenType(TOK_RB)) + funName = propAtom; + } + + // Calling toString on constructors need to return the source text for + // the entire class. The end offset is unknown at this point in + // parsing and will be amended when class parsing finishes below. + FunctionNodeType funNode = methodDefinition(isConstructor ? classStartOffset : propNameOffset, + propType, funName); + if (!funNode) + return false; + + handler.checkAndSetIsDirectRHSAnonFunction(funNode); + + JSOp op = JSOpFromPropertyType(propType); + return handler.addClassMethodDefinition(classMembers, propName, funNode, op, isStatic); +} + +template <typename ParseHandler> +bool +Parser<ParseHandler>::finishClassConstructor(const ParseContext::ClassStatement& classStmt, + HandlePropertyName className, uint32_t classStartOffset, + uint32_t classEndOffset, size_t numFields, + 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. + if (classStmt.constructorBox == nullptr && numFields > 0) { + // synthesizeConstructor assigns to classStmt.constructorBox + FunctionNodeType synthesizedCtor = synthesizeConstructor(className, classStartOffset); + if (!synthesizedCtor) { + return false; + } + + MOZ_ASSERT(classStmt.constructorBox != nullptr); + + // Note: the *function* has the name of the class, but the *property* + // containing the function has the name "constructor" + Node constructorNameNode = handler.newObjectLiteralPropertyName(context->names().constructor, pos()); + if (!constructorNameNode) { + return false; + } + + if (!handler.addClassMethodDefinition(classMembers, constructorNameNode, + synthesizedCtor, JSOP_INITPROP, + /* isStatic = */ false)) { + return false; + } + } + + if (FunctionBox* ctorbox = classStmt.constructorBox) { + // Amend the toStringEnd offset for the constructor now that we've + // finished parsing the class. + ctorbox->toStringEnd = classEndOffset; + + if (numFields > 0) { + // Field initialization need access to `this`. + ctorbox->setHasThisBinding(); + } + + // Set the same information, but on the lazyScript. + if (ctorbox->function()->isInterpretedLazy()) { + ctorbox->function()->lazyScript()->setToStringEnd(classEndOffset); + + if (numFields > 0) { + ctorbox->function()->lazyScript()->setHasThisBinding(); + } + + // Field initializers can be retrieved if the class and constructor are + // being compiled at the same time, but we need to stash the field + // information if the constructor is being compiled lazily. + FieldInitializers fieldInfo(numFields); + ctorbox->function()->lazyScript()->setFieldInitializers(fieldInfo); + } + } + + return true; +} + +template <typename ParseHandler> typename ParseHandler::ClassNodeType Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling, ClassContext classContext, @@ -7392,14 +7627,14 @@ Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling, if (!tokenStream.getToken(&tt)) return null(); - RootedPropertyName name(context); + RootedPropertyName className(context); if (TokenKindIsPossibleIdentifier(tt)) { - name = bindingIdentifier(yieldHandling); - if (!name) + className = bindingIdentifier(yieldHandling); + if (!className) return null(); } else if (classContext == ClassStatement) { if (defaultHandling == AllowDefaultName) { - name = context->names().starDefaultStar; + className = context->names().starDefaultStar; tokenStream.ungetToken(); } else { // Class statements must have a bound name @@ -7411,188 +7646,122 @@ Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling, tokenStream.ungetToken(); } - // Push a ParseContext::ClassStatement to keep track of the constructor - // funbox. - ParseContext::ClassStatement classStmt(pc); - - RootedAtom propAtom(context); - - // A named class creates a new lexical scope with a const binding of the - // class name for the "inner name". - Maybe<ParseContext::Statement> innerScopeStmt; - Maybe<ParseContext::Scope> innerScope; - if (name) { - innerScopeStmt.emplace(pc, StatementKind::Block); - innerScope.emplace(this); - if (!innerScope->init(pc)) - return null(); - } - // Because the binding definitions keep track of their blockId, we need to // create at least the inner binding later. Keep track of the name's position // in order to provide it for the nodes created later. TokenPos namePos = pos(); + // Push a ParseContext::ClassStatement to keep track of the constructor + // funbox. + ParseContext::ClassStatement classStmt(pc); + + NameNodeType innerName; + Node nameNode = null(); Node classHeritage = null(); - bool hasHeritage; - if (!tokenStream.matchToken(&hasHeritage, TOK_EXTENDS)) - return null(); - if (hasHeritage) { - if (!tokenStream.getToken(&tt)) - return null(); - classHeritage = optionalExpr(yieldHandling, TripledotProhibited, tt); - if (!classHeritage) + LexicalScopeNodeType classBlock = null(); + uint32_t classEndOffset; + { + // A named class creates a new lexical scope with a const binding of the + // class name for the "inner name". + ParseContext::Statement innerScopeStmt(pc, StatementKind::Block); + ParseContext::Scope innerScope(this); + if (!innerScope.init(pc)) return null(); - } - if (!mustMatchToken(TOK_LC, JSMSG_CURLY_BEFORE_CLASS)) { - return null(); - } - - ListNodeType classMethods = handler.newClassMethodList(pos().begin); - if (!classMethods) - return null(); - - Maybe<DeclarationKind> declKind = Nothing(); - for (;;) { - TokenKind tt; - if (!tokenStream.getToken(&tt)) + bool hasHeritage; + if (!tokenStream.matchToken(&hasHeritage, TOK_EXTENDS)) return null(); - if (tt == TOK_RC) - break; - - if (tt == TOK_SEMI) - continue; - - bool isStatic = false; - if (tt == TOK_STATIC) { - if (!tokenStream.peekToken(&tt)) + if (hasHeritage) { + if (!tokenStream.getToken(&tt)) return null(); - if (tt == TOK_RC) { - tokenStream.consumeKnownToken(tt); - error(JSMSG_UNEXPECTED_TOKEN, "property name", TokenKindToDesc(tt)); + classHeritage = optionalExpr(yieldHandling, TripledotProhibited, tt); + if (!classHeritage) return null(); - } - - if (tt != TOK_LP) { - isStatic = true; - } else { - tokenStream.ungetToken(); - } - } else { - tokenStream.ungetToken(); } - uint32_t nameOffset; - if (!tokenStream.peekOffset(&nameOffset)) - return null(); - - PropertyType propType; - Node propName = propertyName(yieldHandling, declKind, classMethods, &propType, &propAtom); - if (!propName) + if (!mustMatchToken(TOK_LC, JSMSG_CURLY_BEFORE_CLASS)) { return null(); + } - if (propType != PropertyType::Getter && propType != PropertyType::Setter && - propType != PropertyType::Method && propType != PropertyType::GeneratorMethod && - propType != PropertyType::AsyncMethod && propType != PropertyType::AsyncGeneratorMethod && - propType != PropertyType::Constructor && propType != PropertyType::DerivedConstructor) - { - errorAt(nameOffset, JSMSG_BAD_METHOD_DEF); + ListNodeType classMembers = handler.newClassMemberList(pos().begin); + if (!classMembers) return null(); - } - if (propType == PropertyType::Getter) - propType = PropertyType::GetterNoExpressionClosure; - if (propType == PropertyType::Setter) - propType = PropertyType::SetterNoExpressionClosure; + size_t numFields = 0; + size_t numFieldKeys = 0; + for (;;) { + bool done; + if (!classMember(yieldHandling, defaultHandling, classStmt, className, + classStartOffset, hasHeritage, numFields, numFieldKeys, + classMembers, &done)) + return null(); + if (done) + break; + } - bool isConstructor = !isStatic && propAtom == context->names().constructor; - if (isConstructor) { - if (propType != PropertyType::Method) { - errorAt(nameOffset, JSMSG_BAD_METHOD_DEF); + if (numFields > 0) { + // .initializers is always closed over by the constructor when there are + // fields with initializers. However, there's some strange circumstances + // which prevents us from using the normal noteUsedName() system. We + // cannot call noteUsedName(".initializers") when parsing the constructor, + // because .initializers should be marked as used *only if* there are + // fields with initializers. Even if we haven't seen any fields yet, + // there may be fields after the constructor. + // Consider the following class: + // + // class C { + // constructor() { + // // do we noteUsedName(".initializers") here? + // } + // // ... because there might be some fields down here. + // } + // + // So, instead, at the end of class parsing (where we are now), we do some + // tricks to pretend that noteUsedName(".initializers") was called in the + // constructor. + if (!usedNames.markAsAlwaysClosedOver(context, context->names().dotInitializers, + pc->scriptId(), + pc->innermostScope()->id())) return null(); - } - if (classStmt.constructorBox) { - errorAt(nameOffset, JSMSG_DUPLICATE_PROPERTY, "constructor"); + if (!noteDeclaredName(context->names().dotInitializers, + DeclarationKind::Var, namePos)) return null(); - } - propType = hasHeritage ? PropertyType::DerivedConstructor : PropertyType::Constructor; - } else if (isStatic && propAtom == context->names().prototype) { - errorAt(nameOffset, JSMSG_BAD_METHOD_DEF); - return null(); } - RootedAtom funName(context); - switch (propType) { - case PropertyType::GetterNoExpressionClosure: - case PropertyType::SetterNoExpressionClosure: - if (!tokenStream.isCurrentTokenType(TOK_RB)) { - funName = prefixAccessorName(propType, propAtom); - if (!funName) - return null(); - } - break; - case PropertyType::Constructor: - case PropertyType::DerivedConstructor: - funName = name; - break; - default: - if (!tokenStream.isCurrentTokenType(TOK_RB)) - funName = propAtom; + if (numFieldKeys > 0) { + if (!noteDeclaredName(context->names().dotFieldKeys, DeclarationKind::Let, namePos)) + return null(); } - - // Calling toString on constructors need to return the source text for - // the entire class. The end offset is unknown at this point in - // parsing and will be amended when class parsing finishes below. - FunctionNodeType funNode = methodDefinition(isConstructor ? classStartOffset : nameOffset, - propType, funName); - if (!funNode) - return null(); - - handler.checkAndSetIsDirectRHSAnonFunction(funNode); - - JSOp op = JSOpFromPropertyType(propType); - if (!handler.addClassMethodDefinition(classMethods, propName, funNode, op, isStatic)) + classEndOffset = pos().end; + if (!finishClassConstructor(classStmt, className, classStartOffset, + classEndOffset, numFields, classMembers)) return null(); - } - - // Amend the toStringEnd offset for the constructor now that we've - // finished parsing the class. - uint32_t classEndOffset = pos().end; - if (FunctionBox* ctorbox = classStmt.constructorBox) { - if (ctorbox->function()->isInterpretedLazy()) - ctorbox->function()->lazyScript()->setToStringEnd(classEndOffset); - ctorbox->toStringEnd = classEndOffset; - } - Node nameNode = null(); - Node methodsOrBlock = classMethods; - if (name) { - // The inner name is immutable. - if (!noteDeclaredName(name, DeclarationKind::Const, namePos)) - return null(); + if (className) { + // The inner name is immutable. + if (!noteDeclaredName(className, DeclarationKind::Const, namePos)) + return null(); - NameNodeType innerName = newName(name, namePos); - if (!innerName) - return null(); + innerName = newName(className, namePos); + if (!innerName) + return null(); + } - LexicalScopeNodeType classBlock = finishLexicalScope(*innerScope, classMethods); + classBlock = finishLexicalScope(innerScope, classMembers); if (!classBlock) return null(); - methodsOrBlock = classBlock; - // Pop the inner scope. - innerScope.reset(); - innerScopeStmt.reset(); + } + if (className) { NameNodeType outerName = null(); if (classContext == ClassStatement) { // The outer name is mutable. - if (!noteDeclaredName(name, DeclarationKind::Let, namePos)) + if (!noteDeclaredName(className, DeclarationKind::Let, namePos)) return null(); - outerName = newName(name, namePos); + outerName = newName(className, namePos); if (!outerName) return null(); } @@ -7604,11 +7773,265 @@ Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling, MOZ_ALWAYS_TRUE(setLocalStrictMode(savedStrictness)); - return handler.newClass(nameNode, classHeritage, methodsOrBlock, + return handler.newClass(nameNode, classHeritage, classBlock, TokenPos(classStartOffset, classEndOffset)); } template <class ParseHandler> +typename ParseHandler::FunctionNodeType +Parser<ParseHandler>::synthesizeConstructor(HandleAtom className, uint32_t classNameOffset) +{ + FunctionSyntaxKind functionSyntaxKind = FunctionSyntaxKind::ClassConstructor; + + // Create the function object. + RootedFunction fun(context, newFunction(className, functionSyntaxKind, + GeneratorKind::NotGenerator, + FunctionAsyncKind::SyncFunction)); + if (!fun) + return null(); + + // Create the top-level field initializer node. + FunctionNodeType funNode = handler.newFunction(functionSyntaxKind, pos()); + if (!funNode) + return null(); + + // Create the FunctionBox and link it to the function object. + Directives directives(true); + FunctionBox* funbox = newFunctionBox(funNode, fun, classNameOffset, + directives, GeneratorKind::NotGenerator, + FunctionAsyncKind::SyncFunction, false); + if (!funbox) + return null(); + funbox->initWithEnclosingParseContext(pc, functionSyntaxKind); + handler.setFunctionBox(funNode, funbox); + funbox->setEnd(pos().end); + + // Push a ParseContext on to the stack. + ParseContext funpc(this, funbox, /* newDirectives = */ nullptr); + if (!funpc.init()) + return null(); + + TokenPos synthesizedBodyPos = TokenPos(classNameOffset, classNameOffset + 1); + // Create a ListNode for the parameters + body (there are no parameters). + ListNodeType argsbody = handler.newList(PNK_PARAMSBODY, synthesizedBodyPos); + if (!argsbody) + return null(); + handler.setFunctionFormalParametersAndBody(funNode, argsbody); + funbox->function()->setArgCount(0); + funbox->setStart(tokenStream); + + // Push a LexicalScope on to the stack. + ParseContext::Scope lexicalScope(this); + if (!lexicalScope.init(pc)) + return null(); + + auto stmtList = handler.newStatementList(synthesizedBodyPos); + if (!stmtList) + return null(); + + if (!noteUsedName(context->names().dotThis)) + return null(); + + // One might expect a noteUsedName(".initializers") here. See comment in + // GeneralParser<ParseHandler, Unit>::classDefinition on why it's not here. + + bool canSkipLazyClosedOverBindings = handler.canSkipLazyClosedOverBindings(); + if (!declareFunctionThis(canSkipLazyClosedOverBindings)) + return null(); + + auto initializerBody = finishLexicalScope(lexicalScope, stmtList); + if (!initializerBody) + return null(); + handler.setBeginPosition(initializerBody, stmtList); + handler.setEndPosition(initializerBody, stmtList); + + handler.setFunctionBody(funNode, initializerBody); + + if (!finishFunction()) + return null(); + + // This function is asserted to set classStmt->constructorBox - however, it's + // not directly set in this function, but rather in + // initWithEnclosingParseContext. + + return funNode; +} + +template <class ParseHandler> +typename ParseHandler::FunctionNodeType +Parser<ParseHandler>::fieldInitializerOpt(YieldHandling yieldHandling, bool hasHeritage, + Node propName, HandleAtom propAtom, size_t& numFieldKeys) +{ + bool hasInitializer = false; + if (!tokenStream.matchToken(&hasInitializer, TOK_ASSIGN)) + return null(); + + TokenPos firstTokenPos; + if (hasInitializer) { + firstTokenPos = pos(); + } else { + // the location of the "initializer" should be a zero-width span: + // class C { + // x /* here */ ; + // } + uint32_t endPos = pos().end; + firstTokenPos = TokenPos(endPos, endPos); + } + + // Create the function object. + RootedFunction fun(context, + newFunction(propAtom, FunctionSyntaxKind::Expression, + GeneratorKind::NotGenerator, + FunctionAsyncKind::SyncFunction)); + if (!fun) + return null(); + + // Create the top-level field initializer node. + FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Expression, firstTokenPos); + if (!funNode) + return null(); + + // Create the FunctionBox and link it to the function object. + Directives directives(true); + FunctionBox* funbox = newFunctionBox(funNode, fun, firstTokenPos.begin, directives, + GeneratorKind::NotGenerator, + FunctionAsyncKind::SyncFunction, false); + if (!funbox) + return null(); + funbox->initFieldInitializer(pc, hasHeritage); + handler.setFunctionBox(funNode, funbox); + funbox->setStart(tokenStream, firstTokenPos); + + // Push a SourceParseContext on to the stack. + ParseContext* outerpc = pc; + ParseContext funpc(this, funbox, /* newDirectives = */ nullptr); + if (!funpc.init()) + return null(); + + // Push a VarScope on to the stack. + ParseContext::VarScope varScope(this); + if (!varScope.init(pc)) + return null(); + + // Push a LexicalScope on to the stack. + ParseContext::Scope lexicalScope(this); + if (!lexicalScope.init(pc)) + return null(); + + Node initializerExpr; + TokenPos wholeInitializerPos; + if (hasInitializer) { + // Parse the expression for the field initializer. + { + AutoAwaitIsKeyword<ParseHandler> awaitIsKeyword(this, AwaitIsName); + initializerExpr = assignExpr(InAllowed, YieldIsName, TripledotProhibited); + if (!initializerExpr) + return null(); + } + wholeInitializerPos = pos(); + wholeInitializerPos.begin = firstTokenPos.begin; + } else { + initializerExpr = handler.newRawUndefinedLiteral(firstTokenPos); + if (!initializerExpr) + return null(); + wholeInitializerPos = firstTokenPos; + } + + // Update the end position of the parse node. + handler.setEndPosition(funNode, wholeInitializerPos.end); + funbox->setEnd(pos().end); + + // Create a ListNode for the parameters + body (there are no parameters) + ListNodeType argsbody = handler.newList(PNK_PARAMSBODY, wholeInitializerPos); + if (!argsbody) + return null(); + handler.setFunctionFormalParametersAndBody(funNode, argsbody); + funbox->function()->setArgCount(0); + + funbox->usesThis = true; + NameNodeType thisName = newThisName(); + if (!thisName) + return null(); + + // Build `this.field` expression. + ThisLiteralType propAssignThis = handler.newThisLiteral(wholeInitializerPos, thisName); + if (!propAssignThis) + return null(); + + Node propAssignFieldAccess; + uint32_t indexValue; + if (!propAtom) { + // See BytecodeEmitter::emitCreateFieldKeys for an explanation of what + // .fieldKeys means and its purpose. + Node dotFieldKeys = newInternalDotName(context->names().dotFieldKeys); + if (!dotFieldKeys) + return null(); + + double fieldKeyIndex = numFieldKeys; + numFieldKeys++; + Node fieldKeyIndexNode = handler.newNumber(fieldKeyIndex, DecimalPoint::NoDecimal, wholeInitializerPos); + if (!fieldKeyIndexNode) + return null(); + + Node fieldKeyValue = handler.newPropertyByValue(dotFieldKeys, fieldKeyIndexNode, wholeInitializerPos.end); + if (!fieldKeyValue) + return null(); + + propAssignFieldAccess = handler.newPropertyByValue(propAssignThis, fieldKeyValue, wholeInitializerPos.end); + if (!propAssignFieldAccess) + return null(); + } else if (propAtom->isIndex(&indexValue)) { + propAssignFieldAccess = handler.newPropertyByValue(propAssignThis, propName, wholeInitializerPos.end); + if (!propAssignFieldAccess) + return null(); + } else { + NameNodeType propAssignName = handler.newPropertyName(propAtom->asPropertyName(), wholeInitializerPos); + if (!propAssignName) + return null(); + + propAssignFieldAccess = handler.newPropertyAccess(propAssignThis, propAssignName); + if (!propAssignFieldAccess) + return null(); + } + + // Synthesize an assignment expression for the property. + AssignmentNodeType initializerAssignment = handler.newAssignment(PNK_ASSIGN, + propAssignFieldAccess, initializerExpr, + JSOP_NOP); + if (!initializerAssignment) + return null(); + + bool canSkipLazyClosedOverBindings = handler.canSkipLazyClosedOverBindings(); + if (!declareFunctionThis(canSkipLazyClosedOverBindings)) + return null(); + + UnaryNodeType exprStatement = handler.newExprStatement(initializerAssignment, wholeInitializerPos.end); + if (!exprStatement) + return null(); + + ListNodeType statementList = handler.newStatementList(wholeInitializerPos); + if (!statementList) + return null(); + handler.addStatementToList(statementList, exprStatement); + + // Set the function's body to the field assignment. + LexicalScopeNodeType initializerBody = finishLexicalScope(lexicalScope, statementList); + if (!initializerBody) { + return null(); + } + + handler.setFunctionBody(funNode, initializerBody); + + if (!finishFunction()) + return null(); + + if (!leaveInnerFunction(outerpc)) + return null(); + + return funNode; +} + +template <class ParseHandler> bool Parser<ParseHandler>::nextTokenContinuesLetDeclaration(TokenKind next, YieldHandling yieldHandling) { @@ -8543,7 +8966,7 @@ Parser<ParseHandler>::assignExpr(InHandling inHandling, YieldHandling yieldHandl } } - FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Arrow); + FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Arrow, pos()); if (!funNode) return null(); @@ -8940,7 +9363,7 @@ template <typename ParseHandler> typename ParseHandler::Node Parser<ParseHandler>::generatorComprehensionLambda(unsigned begin) { - FunctionNodeType genfn = handler.newFunction(FunctionSyntaxKind::Expression); + FunctionNodeType genfn = handler.newFunction(FunctionSyntaxKind::Expression, pos()); if (!genfn) return null(); @@ -9698,7 +10121,12 @@ Parser<ParseHandler>::checkLabelOrIdentifierReference(PropertyName* ident, tt = hint; } - if (tt == TOK_NAME) + if (!pc->sc()->allowArguments() && ident == context->names().arguments) { + error(JSMSG_BAD_ARGUMENTS); + return false; + } + + if (tt == TOK_NAME || tt == TOK_PRIVATE_NAME) return true; if (TokenKindIsContextualKeyword(tt)) { if (tt == TOK_YIELD) { @@ -10068,6 +10496,7 @@ DoubleToAtom(ExclusiveContext* cx, double value) template <typename ParseHandler> typename ParseHandler::Node Parser<ParseHandler>::propertyName(YieldHandling yieldHandling, + PropertyNameContext propertyNameContext, const Maybe<DeclarationKind>& maybeDecl, ListNodeType propList, PropertyType* propType, MutableHandleAtom propAtom) { @@ -10226,6 +10655,16 @@ Parser<ParseHandler>::propertyName(YieldHandling yieldHandling, return propName; } + if (propertyNameContext == PropertyNameInClass && (tt == TOK_SEMI || tt == TOK_ASSIGN)) { + if (isGenerator || isAsync) { + error(JSMSG_BAD_PROP_ID); + return null(); + } + tokenStream.ungetToken(); + *propType = PropertyType::Field; + return propName; + } + if (TokenKindIsPossibleIdentifierName(ltok) && (tt == TOK_COMMA || tt == TOK_RC || tt == TOK_ASSIGN)) { @@ -10326,7 +10765,7 @@ Parser<ParseHandler>::objectLiteral(YieldHandling yieldHandling, PossibleError* TokenPos namePos = tokenStream.nextToken().pos; PropertyType propType; - Node propName = propertyName(yieldHandling, declKind, literal, &propType, &propAtom); + Node propName = propertyName(yieldHandling, PropertyNameInLiteral, declKind, literal, &propType, &propAtom); if (!propName) return null(); @@ -10557,7 +10996,7 @@ Parser<ParseHandler>::methodDefinition(uint32_t toStringStart, PropertyType prop YieldHandling yieldHandling = GetYieldHandling(generatorKind); - FunctionNodeType funNode = handler.newFunction(syntaxKind); + FunctionNodeType funNode = handler.newFunction(syntaxKind, pos()); if (!funNode) return null(); diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index bd111e54fe..f7feefcaab 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -583,7 +583,8 @@ enum class PropertyType { AsyncMethod, AsyncGeneratorMethod, Constructor, - DerivedConstructor + DerivedConstructor, + Field, }; // Specify a value for an ES6 grammar parametrization. We have no enum for @@ -720,6 +721,15 @@ class UsedNameTracker MOZ_MUST_USE bool noteUse(ExclusiveContext* cx, JSAtom* name, uint32_t scriptId, uint32_t scopeId); + MOZ_MUST_USE bool markAsAlwaysClosedOver(ExclusiveContext* cx, JSAtom* name, + uint32_t scriptId, uint32_t scopeId) { + // This marks a variable as always closed over: + // UsedNameInfo::noteBoundInScope only checks if scriptId and scopeId are + // greater than the current scriptId/scopeId, so do a simple increment to + // make that so. + return noteUse(cx, name, scriptId + 1, scopeId + 1); + } + struct RewindToken { private: @@ -1171,7 +1181,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) */ JSFunction* newFunction(HandleAtom atom, FunctionSyntaxKind kind, GeneratorKind generatorKind, FunctionAsyncKind asyncKind, - HandleObject proto); + HandleObject proto = nullptr); void trace(JSTracer* trc); @@ -1478,6 +1488,24 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) enum ClassContext { ClassStatement, ClassExpression }; ClassNodeType classDefinition(YieldHandling yieldHandling, ClassContext classContext, DefaultHandling defaultHandling); + MOZ_MUST_USE bool classMember(YieldHandling yieldHandling, + DefaultHandling defaultHandling, + const ParseContext::ClassStatement& classStmt, + HandlePropertyName className, + uint32_t classStartOffset, bool hasHeritage, + size_t& numFields, + size_t& numFieldKeys, + ListNodeType& classMembers, bool* done); + MOZ_MUST_USE bool finishClassConstructor( + const ParseContext::ClassStatement& classStmt, + HandlePropertyName className, uint32_t classStartOffset, + uint32_t classEndOffset, size_t numFieldsWithInitializers, + ListNodeType& classMembers); + + FunctionNodeType fieldInitializerOpt(YieldHandling yieldHandling, bool hasHeritage, + Node name, HandleAtom atom, size_t& numFieldKeys); + FunctionNodeType synthesizeConstructor(HandleAtom className, + uint32_t classNameOffset); bool checkLabelOrIdentifierReference(PropertyName* ident, uint32_t offset, @@ -1522,8 +1550,8 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) bool matchInOrOf(bool* isForInp, bool* isForOfp); bool hasUsedFunctionSpecialName(HandlePropertyName name); - bool declareFunctionArgumentsObject(); - bool declareFunctionThis(); + bool declareFunctionArgumentsObject(bool canSkipLazyClosedOverBindings); + bool declareFunctionThis(bool canSkipLazyClosedOverBindings); NameNodeType newInternalDotName(HandlePropertyName name); NameNodeType newThisName(); NameNodeType newDotGeneratorName(); @@ -1596,7 +1624,9 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) mozilla::Maybe<LexicalScope::Data*> newLexicalScopeData(ParseContext::Scope& scope); LexicalScopeNodeType finishLexicalScope(ParseContext::Scope& scope, Node body); + enum PropertyNameContext { PropertyNameInLiteral, PropertyNameInPattern, PropertyNameInClass }; Node propertyName(YieldHandling yieldHandling, + PropertyNameContext propertyNameContext, const mozilla::Maybe<DeclarationKind>& maybeDecl, ListNodeType propList, PropertyType* propType, MutableHandleAtom propAtom); UnaryNodeType computedPropertyName(YieldHandling yieldHandling, diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h index 81eb0885b0..edb2b93788 100644 --- a/js/src/frontend/SharedContext.h +++ b/js/src/frontend/SharedContext.h @@ -243,6 +243,7 @@ class SharedContext bool allowNewTarget_; bool allowSuperProperty_; bool allowSuperCall_; + bool allowArguments_; bool inWith_; bool needsThisTDZChecks_; @@ -262,6 +263,7 @@ class SharedContext allowNewTarget_(false), allowSuperProperty_(false), allowSuperCall_(false), + allowArguments_(true), inWith_(false), needsThisTDZChecks_(false) { } @@ -286,6 +288,7 @@ class SharedContext bool allowNewTarget() const { return allowNewTarget_; } bool allowSuperProperty() const { return allowSuperProperty_; } bool allowSuperCall() const { return allowSuperCall_; } + bool allowArguments() const { return allowArguments_; } bool inWith() const { return inWith_; } bool needsThisTDZChecks() const { return needsThisTDZChecks_; } @@ -452,6 +455,7 @@ class FunctionBox : public ObjectBox, public SharedContext void initFromLazyFunction(); void initStandaloneFunction(Scope* enclosingScope); void initWithEnclosingParseContext(ParseContext* enclosing, FunctionSyntaxKind kind); + void initFieldInitializer(ParseContext* enclosing, bool hasHeritage); ObjectBox* toObjectBox() override { return this; } JSFunction* function() const { return &object->as<JSFunction>(); } @@ -563,7 +567,11 @@ class FunctionBox : public ObjectBox, public SharedContext } void setStart(const TokenStream& tokenStream) { - bufStart = tokenStream.currentToken().pos.begin; + setStart(tokenStream, tokenStream.currentToken().pos); + } + + void setStart(const TokenStream& tokenStream, const TokenPos& tokenPos) { + bufStart = tokenPos.begin; tokenStream.srcCoords.lineNumAndColumnIndex(bufStart, &startLine, &startColumn); } diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index d8bc3e4959..607cff48f3 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -312,7 +312,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) Node newGenExp(Node callee, Node args) { return NodeGeneric; } ListNodeType newObjectLiteral(uint32_t begin) { return NodeUnparenthesizedObject; } - ListNodeType newClassMethodList(uint32_t begin) { return NodeGeneric; } + ListNodeType newClassMemberList(uint32_t begin) { return NodeGeneric; } ClassNamesType newClassNames(Node outer, Node inner, const TokenPos& pos) { return NodeGeneric; } ClassNodeType newClass(Node name, Node heritage, Node methodBlock, const TokenPos& pos) { return NodeGeneric; } @@ -331,7 +331,8 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) MOZ_MUST_USE bool addShorthand(ListNodeType literal, NameNodeType name, NameNodeType expr) { return true; } 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 bool addClassMethodDefinition(ListNodeType literal, Node name, FunctionNodeType funNode, JSOp op, bool isStatic) { return true; } + MOZ_MUST_USE bool addClassMethodDefinition(ListNodeType memberList, Node key, FunctionNodeType funNode, JSOp op, bool isStatic) { return true; } + MOZ_MUST_USE bool addClassFieldDefinition(ListNodeType memberList, Node name, FunctionNodeType initializer) { return true; } UnaryNodeType newYieldExpression(uint32_t begin, Node value) { return NodeGeneric; } UnaryNodeType newYieldStarExpression(uint32_t begin, Node value) { return NodeGeneric; } UnaryNodeType newAwaitExpression(uint32_t begin, Node value) { return NodeGeneric; } @@ -417,7 +418,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) void checkAndSetIsDirectRHSAnonFunction(Node pn) {} - FunctionNodeType newFunction(FunctionSyntaxKind syntaxKind) { return NodeFunctionDefinition; } + FunctionNodeType newFunction(FunctionSyntaxKind syntaxKind, const TokenPos& pos) { return NodeFunctionDefinition; } bool setComprehensionLambdaBody(FunctionNodeType funNode, ListNodeType body) { return true; } void setFunctionFormalParametersAndBody(FunctionNodeType funNode, ListNodeType paramsBody) {} @@ -468,7 +469,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) MOZ_ASSERT(kind != PNK_CONST); return NodeGeneric; } - ListNodeType newList(ParseNodeKind kind, uint32_t begin, JSOp op = JSOP_NOP) { + ListNodeType newList(ParseNodeKind kind, const TokenPos& pos, JSOp op = JSOP_NOP) { return newList(kind, op); } ListNodeType newList(ParseNodeKind kind, Node kid, JSOp op = JSOP_NOP) { diff --git a/js/src/frontend/TokenKind.h b/js/src/frontend/TokenKind.h index 745e1b6987..f6f947f608 100644 --- a/js/src/frontend/TokenKind.h +++ b/js/src/frontend/TokenKind.h @@ -71,6 +71,7 @@ macro(LP, "'('") \ macro(RP, "')'") \ macro(NAME, "identifier") \ + macro(PRIVATE_NAME, "private identifier") \ macro(NUMBER, "numeric literal") \ macro(STRING, "string literal") \ \ @@ -322,6 +323,7 @@ inline MOZ_MUST_USE bool TokenKindIsPossibleIdentifier(TokenKind tt) { return tt == TOK_NAME || + tt == TOK_PRIVATE_NAME || TokenKindIsContextualKeyword(tt) || TokenKindIsStrictReservedWord(tt); } diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp index a537c5f275..e358faef09 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -93,18 +93,35 @@ FindReservedWord(const CharT* s, size_t length) } static const ReservedWordInfo* -FindReservedWord(JSLinearString* str) +FindReservedWord(JSLinearString* str, js::frontend::NameVisibility* visibility) { JS::AutoCheckCannotGC nogc; - return str->hasLatin1Chars() - ? FindReservedWord(str->latin1Chars(nogc), str->length()) - : FindReservedWord(str->twoByteChars(nogc), str->length()); + if (str->hasLatin1Chars()) { + const JS::Latin1Char* chars = str->latin1Chars(nogc); + size_t length = str->length(); + if (length > 0 && chars[0] == '#') { + *visibility = js::frontend::NameVisibility::Private; + return nullptr; + } + *visibility = js::frontend::NameVisibility::Public; + return FindReservedWord(chars, length); + } + + const char16_t* chars = str->twoByteChars(nogc); + size_t length = str->length(); + if (length > 0 && chars[0] == '#') { + *visibility = js::frontend::NameVisibility::Private; + return nullptr; + } + *visibility = js::frontend::NameVisibility::Public; + return FindReservedWord(chars, length); } template <typename CharT> static bool IsIdentifier(const CharT* chars, size_t length) { + // Generic version for latin1 in char* and UCS-2 in char16_t* if (length == 0) return false; @@ -138,15 +155,53 @@ GetSingleCodePoint(const char16_t** p, const char16_t* end) return codePoint; } -static bool -IsIdentifierMaybeNonBMP(const char16_t* chars, size_t length) +namespace js { + +namespace frontend { + +// Latin1 Variants + +bool +IsIdentifier(const Latin1Char* chars, size_t length) { - if (IsIdentifier(chars, length)) - return true; + return ::IsIdentifier(chars, length); +} +bool +IsIdentifierNameOrPrivateName(const Latin1Char* chars, size_t length) +{ if (length == 0) return false; + if (char16_t(*chars) == '#') { + ++chars; + --length; + } + + return IsIdentifier(chars, length); +} + +// UTF-16 Versions + +bool +IsIdentifier(const char16_t* chars, size_t length) +{ + return ::IsIdentifier(chars, length); +} + +bool +IsIdentifierMaybeNonBMP(const char16_t* chars, size_t length) +{ + if (length == 0) + return false; + // XXX Revisit if this is still faster. + // Assumption is that iterating the string twice in the rare worst case (not a valid UCS-2 + // identifier, but valid in UTF-16) is on average better than parsing UTF-16 code points + // individually for every input. + if (IsIdentifier(chars, length)) { + return true; + } + const char16_t* p = chars; const char16_t* end = chars + length; uint32_t codePoint; @@ -165,55 +220,74 @@ IsIdentifierMaybeNonBMP(const char16_t* chars, size_t length) } bool -frontend::IsIdentifier(JSLinearString* str) +IsIdentifierNameOrPrivateNameMaybeNonBMP(const char16_t* chars, size_t length) { - JS::AutoCheckCannotGC nogc; - return str->hasLatin1Chars() - ? ::IsIdentifier(str->latin1Chars(nogc), str->length()) - : ::IsIdentifierMaybeNonBMP(str->twoByteChars(nogc), str->length()); + if (length == 0) + return false; + + // '#' is always just one character in either UCS-2 or UTF-16, so compare it directly. + if (char16_t(*chars) == '#') { + ++chars; + --length; + } + + return IsIdentifierMaybeNonBMP(chars, length); } bool -frontend::IsIdentifier(const char* chars, size_t length) +IsIdentifier(JSLinearString* str) { - return ::IsIdentifier(chars, length); + JS::AutoCheckCannotGC nogc; + if (str->hasLatin1Chars()) { + return IsIdentifier(str->latin1Chars(nogc), str->length()); + + } + return IsIdentifierMaybeNonBMP(str->twoByteChars(nogc), str->length()); } bool -frontend::IsIdentifier(const char16_t* chars, size_t length) +IsIdentifierNameOrPrivateName(JSLinearString* str) { - return ::IsIdentifier(chars, length); + JS::AutoCheckCannotGC nogc; + if (str->hasLatin1Chars()) { + return IsIdentifierNameOrPrivateName(str->latin1Chars(nogc), str->length()); + + } + return IsIdentifierNameOrPrivateNameMaybeNonBMP(str->twoByteChars(nogc), str->length()); } bool -frontend::IsKeyword(JSLinearString* str) +IsKeyword(JSLinearString* str) { - if (const ReservedWordInfo* rw = FindReservedWord(str)) + NameVisibility visibility; + if (const ReservedWordInfo* rw = FindReservedWord(str, &visibility)) return TokenKindIsKeyword(rw->tokentype); return false; } TokenKind -frontend::ReservedWordTokenKind(PropertyName* str) +ReservedWordTokenKind(PropertyName* str) { - if (const ReservedWordInfo* rw = FindReservedWord(str)) + NameVisibility visibility; + if (const ReservedWordInfo* rw = FindReservedWord(str, &visibility)) return rw->tokentype; - return TOK_NAME; + return visibility == NameVisibility::Private ? TOK_PRIVATE_NAME : TOK_NAME; } const char* -frontend::ReservedWordToCharZ(PropertyName* str) +ReservedWordToCharZ(PropertyName* str) { - if (const ReservedWordInfo* rw = FindReservedWord(str)) + NameVisibility visibility; + if (const ReservedWordInfo* rw = FindReservedWord(str, &visibility)) return ReservedWordToCharZ(rw->tokentype); return nullptr; } const char* -frontend::ReservedWordToCharZ(TokenKind tt) +ReservedWordToCharZ(TokenKind tt) { MOZ_ASSERT(tt != TOK_NAME); switch (tt) { @@ -226,6 +300,10 @@ frontend::ReservedWordToCharZ(TokenKind tt) return nullptr; } +} // namespace frontend + +} // namespace js + PropertyName* TokenStream::reservedWordToPropertyName(TokenKind tt) const { @@ -592,7 +670,7 @@ TokenStream::TokenBuf::findEOLMax(size_t start, size_t max) if (n >= max) break; n++; - + // This stops at U+2028 LINE SEPARATOR or U+2029 PARAGRAPH SEPARATOR in // string and template literals. These code points do affect line and // column coordinates, even as they encode their literal values. @@ -1302,6 +1380,7 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) bool hasExp; DecimalPoint decimalPoint; const char16_t* identStart; + NameVisibility identVisibility; bool hadUnicodeEscape; // Check if in the middle of a template string. Have to get this out of @@ -1346,6 +1425,7 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) if (unicode::IsUnicodeIDStart(char16_t(c))) { identStart = userbuf.addressOfNextRawChar() - 1; hadUnicodeEscape = false; + identVisibility = NameVisibility::Public; goto identifier; } @@ -1356,6 +1436,7 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) { identStart = userbuf.addressOfNextRawChar() - 2; hadUnicodeEscape = false; + identVisibility = NameVisibility::Public; goto identifier; } } @@ -1404,6 +1485,7 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) tp = newToken(-1); identStart = userbuf.addressOfNextRawChar() - 1; hadUnicodeEscape = false; + identVisibility = NameVisibility::Public; identifier: for (;;) { @@ -1445,11 +1527,14 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) length = userbuf.addressOfNextRawChar() - identStart; } - // Represent reserved words as reserved word tokens. - if (!hadUnicodeEscape) { - if (const ReservedWordInfo* rw = FindReservedWord(chars, length)) { - tp->type = rw->tokentype; - goto out; + // Private identifiers start with a '#', and so cannot be reserved words. + if (identVisibility == NameVisibility::Public) { + // Represent reserved words as reserved word tokens. + if (!hadUnicodeEscape) { + if (const ReservedWordInfo* rw = FindReservedWord(chars, length)) { + tp->type = rw->tokentype; + goto out; + } } } @@ -1457,7 +1542,17 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) if (!atom) { goto error; } - tp->type = TOK_NAME; + if (identVisibility == NameVisibility::Private) { + MOZ_ASSERT(identStart[0] == '#', "Private identifier starts with #"); + tp->type = TOK_PRIVATE_NAME; + + if (!options().fieldsEnabledOption) { + reportError(JSMSG_FIELDS_NOT_SUPPORTED); + goto error; + } + } else { + tp->type = TOK_NAME; + } tp->setName(atom->asPropertyName()); goto out; } @@ -1762,11 +1857,28 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) if (escapeLength > 0) { identStart = userbuf.addressOfNextRawChar() - escapeLength - 1; hadUnicodeEscape = true; + identVisibility = NameVisibility::Public; goto identifier; } goto badchar; } + case '#': { + // TODO: This does not handle escaped private property names due to being extremely difficult + // in the current state of the tokenizer. If #1351107 is ported, it becomes straightforward. + c = getCharIgnoreEOL(); + // '$' and '_' are not in IsUnicodeIDStart + c1kind = FirstCharKind(firstCharKinds[c]); + if (c1kind == Ident || unicode::IsUnicodeIDStart(char16_t(c))) { + identStart = userbuf.addressOfNextRawChar() - 2; + hadUnicodeEscape = false; + identVisibility = NameVisibility::Private; + goto identifier; + } + ungetCharIgnoreEOL(c); + goto badchar; + } + case '|': if (matchChar('|')) tp->type = TOK_OR; @@ -2235,7 +2347,7 @@ TokenStream::getStringOrTemplateToken(int untilChar, Token** tp) updateFlagsForEOL(); } else if (c == LINE_SEPARATOR || c == PARA_SEPARATOR) { // U+2028 LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR encode - // their literal values in template literals and (as of the + // their literal values in template literals and (as of the // JSON superset proposal) string literals, but they still count // as line terminators when computing line/column coordinates. updateLineInfoForEOL(); diff --git a/js/src/frontend/TokenStream.h b/js/src/frontend/TokenStream.h index 067f11c8d8..e7a3f7b808 100644 --- a/js/src/frontend/TokenStream.h +++ b/js/src/frontend/TokenStream.h @@ -92,6 +92,8 @@ enum class InvalidEscapeType { Octal }; +enum class NameVisibility { Public, Private }; + class TokenStream; struct Token @@ -185,7 +187,7 @@ struct Token // Mutators void setName(PropertyName* name) { - MOZ_ASSERT(type == TOK_NAME); + MOZ_ASSERT(type == TOK_NAME || type == TOK_PRIVATE_NAME); u.name = name; } @@ -211,7 +213,7 @@ struct Token // Type-safe accessors PropertyName* name() const { - MOZ_ASSERT(type == TOK_NAME); + MOZ_ASSERT(type == TOK_NAME || type == TOK_PRIVATE_NAME); return u.name->JSAtom::asPropertyName(); // poor-man's type verification } @@ -344,7 +346,7 @@ class MOZ_STACK_CLASS TokenStream public: PropertyName* currentName() const { - if (isCurrentTokenType(TOK_NAME)) { + if (isCurrentTokenType(TOK_NAME) || isCurrentTokenType(TOK_PRIVATE_NAME)) { return currentToken().name(); } @@ -353,7 +355,7 @@ class MOZ_STACK_CLASS TokenStream } bool currentNameHasEscapes() const { - if (isCurrentTokenType(TOK_NAME)) { + if (isCurrentTokenType(TOK_NAME) || isCurrentTokenType(TOK_PRIVATE_NAME)) { TokenPos pos = currentToken().pos; return (pos.end - pos.begin) != currentToken().name()->length(); } diff --git a/js/src/js.msg b/js/src/js.msg index 413469b808..8e08e213db 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -216,6 +216,7 @@ MSG_DEF(JSMSG_BAD_SWITCH, 0, JSEXN_SYNTAXERR, "invalid switch state MSG_DEF(JSMSG_BAD_SUPER, 0, JSEXN_SYNTAXERR, "invalid use of keyword 'super'") MSG_DEF(JSMSG_BAD_SUPERPROP, 1, JSEXN_SYNTAXERR, "use of super {0} accesses only valid within methods or eval code within methods") MSG_DEF(JSMSG_BAD_SUPERCALL, 0, JSEXN_SYNTAXERR, "super() is only valid in derived class constructors") +MSG_DEF(JSMSG_BAD_ARGUMENTS, 0, JSEXN_SYNTAXERR, "arguments is not valid in fields") MSG_DEF(JSMSG_BRACKET_AFTER_ARRAY_COMPREHENSION, 0, JSEXN_SYNTAXERR, "missing ] after array comprehension") MSG_DEF(JSMSG_BRACKET_AFTER_LIST, 0, JSEXN_SYNTAXERR, "missing ] after element list") MSG_DEF(JSMSG_BRACKET_IN_INDEX, 0, JSEXN_SYNTAXERR, "missing ] in index expression") @@ -264,6 +265,7 @@ MSG_DEF(JSMSG_FROM_AFTER_IMPORT_CLAUSE, 0, JSEXN_SYNTAXERR, "missing keyword 'fr MSG_DEF(JSMSG_FROM_AFTER_EXPORT_STAR, 0, JSEXN_SYNTAXERR, "missing keyword 'from' after export *") MSG_DEF(JSMSG_GARBAGE_AFTER_INPUT, 2, JSEXN_SYNTAXERR, "unexpected garbage after {0}, starting with {1}") MSG_DEF(JSMSG_IDSTART_AFTER_NUMBER, 0, JSEXN_SYNTAXERR, "identifier starts immediately after numeric literal") +MSG_DEF(JSMSG_MISSING_PRIVATE_NAME, 0, JSEXN_SYNTAXERR, "'#' not followed by identifier") MSG_DEF(JSMSG_ILLEGAL_CHARACTER, 0, JSEXN_SYNTAXERR, "illegal character") MSG_DEF(JSMSG_IMPORT_META_OUTSIDE_MODULE, 0, JSEXN_SYNTAXERR, "import.meta may only appear in a module") MSG_DEF(JSMSG_IMPORT_DECL_AT_TOP_LEVEL, 0, JSEXN_SYNTAXERR, "import declarations may only appear at top level of a module") @@ -360,6 +362,8 @@ MSG_DEF(JSMSG_BAD_NEWTARGET, 0, JSEXN_SYNTAXERR, "new.target only allo MSG_DEF(JSMSG_BAD_NEW_OPTIONAL, 0, JSEXN_SYNTAXERR, "new keyword cannot be used with an optional chain") MSG_DEF(JSMSG_BAD_OPTIONAL_TEMPLATE, 0, JSEXN_SYNTAXERR, "tagged template cannot be used with optional chain") MSG_DEF(JSMSG_ESCAPED_KEYWORD, 0, JSEXN_SYNTAXERR, "keywords must be written literally, without embedded escapes") +MSG_DEF(JSMSG_MISSING_SEMI_FIELD, 0, JSEXN_SYNTAXERR, "missing ; after field definition") +MSG_DEF(JSMSG_FIELDS_NOT_SUPPORTED, 0, JSEXN_SYNTAXERR, "fields are not currently supported") // asm.js MSG_DEF(JSMSG_USE_ASM_TYPE_FAIL, 1, JSEXN_TYPEERR, "asm.js type error: {0}") diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 98a940904d..6b2a719d7e 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -4430,7 +4430,7 @@ JS::CompileFunction(JSContext* cx, AutoObjectVector& envChain, return false; // If name is not valid identifier - if (!js::frontend::IsIdentifier(name, nameLen)) + if (!js::frontend::IsIdentifier(reinterpret_cast<const Latin1Char*>(name), nameLen)) isInvalidName = true; } diff --git a/js/src/jsast.tbl b/js/src/jsast.tbl index 24814c3973..ba6b60b68c 100644 --- a/js/src/jsast.tbl +++ b/js/src/jsast.tbl @@ -86,4 +86,5 @@ ASTDEF(AST_COMPUTED_NAME, "ComputedName", "computedNam ASTDEF(AST_CLASS_STMT, "ClassStatement", "classStatement") ASTDEF(AST_CLASS_METHOD, "ClassMethod", "classMethod") +ASTDEF(AST_CLASS_FIELD, "ClassField", "classField") /* AST_LIMIT = last + 1 */ diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index ddb33a4de0..1a148578a1 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -238,6 +238,7 @@ XDRRelazificationInfo(XDRState<mode>* xdr, HandleFunction fun, HandleScript scri uint32_t toStringEnd = script->toStringEnd(); uint32_t lineno = script->lineno(); uint32_t column = script->column(); + uint32_t numFieldInitializers; if (mode == XDR_ENCODE) { packedFields = lazy->packedFields(); @@ -251,10 +252,17 @@ XDRRelazificationInfo(XDRState<mode>* xdr, HandleFunction fun, HandleScript scri // relazify scripts with inner functions. See // JSFunction::createScriptForLazilyInterpretedFunction. MOZ_ASSERT(lazy->numInnerFunctions() == 0); + if (fun->kind() == JSFunction::FunctionKind::ClassConstructor) { + numFieldInitializers = (uint32_t)lazy->getFieldInitializers().numFieldInitializers; + } else { + numFieldInitializers = UINT32_MAX; + } } if (!xdr->codeUint64(&packedFields)) return false; + if (!xdr->codeUint32(&numFieldInitializers)) + return false; if (mode == XDR_DECODE) { RootedScriptSource sourceObject(cx, &script->scriptSourceUnwrap()); @@ -265,6 +273,9 @@ XDRRelazificationInfo(XDRState<mode>* xdr, HandleFunction fun, HandleScript scri return false; lazy->setToStringEnd(toStringEnd); + if (numFieldInitializers != UINT32_MAX) { + lazy->setFieldInitializers(FieldInitializers((size_t)numFieldInitializers)); + } // As opposed to XDRLazyScript, we need to restore the runtime bits // of the script, as we are trying to match the fact this function @@ -975,6 +986,7 @@ js::XDRLazyScript(XDRState<mode>* xdr, HandleScope enclosingScope, uint32_t lineno; uint32_t column; uint64_t packedFields; + uint32_t numFieldInitializers; if (mode == XDR_ENCODE) { // Note: it's possible the LazyScript has a non-null script_ pointer @@ -990,13 +1002,19 @@ js::XDRLazyScript(XDRState<mode>* xdr, HandleScope enclosingScope, lineno = lazy->lineno(); column = lazy->column(); packedFields = lazy->packedFields(); + if (fun->kind() == JSFunction::FunctionKind::ClassConstructor) { + numFieldInitializers = (uint32_t)lazy->getFieldInitializers().numFieldInitializers; + } else { + numFieldInitializers = UINT32_MAX; + } } if (!xdr->codeUint32(&begin) || !xdr->codeUint32(&end) || !xdr->codeUint32(&toStringStart) || !xdr->codeUint32(&toStringEnd) || !xdr->codeUint32(&lineno) || !xdr->codeUint32(&column) || - !xdr->codeUint64(&packedFields)) + !xdr->codeUint64(&packedFields) || + !xdr->codeUint32(&numFieldInitializers)) { return false; } @@ -1007,6 +1025,9 @@ js::XDRLazyScript(XDRState<mode>* xdr, HandleScope enclosingScope, if (!lazy) return false; lazy->setToStringEnd(toStringEnd); + if (numFieldInitializers != UINT32_MAX) { + lazy->setFieldInitializers(FieldInitializers((size_t)numFieldInitializers)); + } fun->initLazyScript(lazy); } } @@ -4110,6 +4131,7 @@ LazyScript::LazyScript(JSFunction* fun, void* table, uint64_t packedFields, sourceObject_(nullptr), table_(table), packedFields_(packedFields), + fieldInitializers_(FieldInitializers::Invalid()), begin_(begin), end_(end), toStringStart_(toStringStart), diff --git a/js/src/jsscript.h b/js/src/jsscript.h index e2225f0cda..fbdd658b79 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -1999,11 +1999,39 @@ static_assert(sizeof(JSScript) % js::gc::CellSize == 0, namespace js { +struct FieldInitializers +{ +#ifdef DEBUG + bool valid; +#endif + // This struct will eventually have a vector of constant values for optimizing + // field initializers. + size_t numFieldInitializers; + + explicit FieldInitializers(size_t numFieldInitializers) + : +#ifdef DEBUG + valid(true), +#endif + numFieldInitializers(numFieldInitializers) { + } + + static FieldInitializers Invalid() { return FieldInitializers(); } + + private: + FieldInitializers() + : +#ifdef DEBUG + valid(false), +#endif + numFieldInitializers(0) { + } +}; + // Information about a script which may be (or has been) lazily compiled to // bytecode from its source. class LazyScript : public gc::TenuredCell { - private: // If non-nullptr, the script has been compiled and this is a forwarding // pointer to the result. This is a weak pointer: after relazification, we // can collect the script if there are no other pointers to it. @@ -2030,7 +2058,6 @@ class LazyScript : public gc::TenuredCell uint32_t padding; #endif - private: static const uint32_t NumClosedOverBindingsBits = 20; static const uint32_t NumInnerFunctionsBits = 20; @@ -2072,6 +2099,8 @@ class LazyScript : public gc::TenuredCell uint64_t packedFields_; }; + FieldInitializers fieldInitializers_; + // Source location for the script. // See the comment in JSScript for the details. uint32_t begin_; @@ -2297,6 +2326,12 @@ class LazyScript : public gc::TenuredCell p_.hasThisBinding = true; } + void setFieldInitializers(FieldInitializers fieldInitializers) { + fieldInitializers_ = fieldInitializers; + } + + FieldInitializers getFieldInitializers() const { return fieldInitializers_; } + const char* filename() const { return scriptSource()->filename(); } diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index 3304ae0305..f69dfe3ca9 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -102,6 +102,8 @@ macro(dotAll, dotAll, "dotAll") \ macro(dotGenerator, dotGenerator, ".generator") \ macro(dotThis, dotThis, ".this") \ + macro(dotInitializers, dotInitializers, ".initializers") \ + macro(dotFieldKeys, dotFieldKeys, ".fieldKeys") \ macro(each, each, "each") \ macro(elementType, elementType, "elementType") \ macro(else, else_, "else") \ diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index 8624b5345e..f6856f2290 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -1890,6 +1890,7 @@ macro(JSOP_UNPICK, 183,"unpick", NULL, 2, 0, 0, JOF_UINT8) \ /* * Pops the top of stack value, pushes property of it onto the stack. + * Requires the value under 'obj' to be the receiver of the following call. * * Like JSOP_GETPROP but for call context. * Category: Literals @@ -1974,7 +1975,8 @@ \ /* * Pops the top two values on the stack as 'propval' and 'obj', pushes - * 'propval' property of 'obj' onto the stack. + * 'propval' property of 'obj' onto the stack. Requires the value under + * 'obj' to be the receiver of the following call. * * Like JSOP_GETELEM but for call context. * Category: Literals diff --git a/js/src/wasm/AsmJS.cpp b/js/src/wasm/AsmJS.cpp index eb74cd4afa..4e00a25206 100644 --- a/js/src/wasm/AsmJS.cpp +++ b/js/src/wasm/AsmJS.cpp @@ -7046,7 +7046,7 @@ ParseFunction(ModuleValidator& m, FunctionNode** funNodeOut, unsigned* line) return false; FunctionSyntaxKind syntaxKind = FunctionSyntaxKind::Statement; - FunctionNode* funNode = m.parser().handler.newFunction(syntaxKind); + FunctionNode* funNode = m.parser().handler.newFunction(syntaxKind, m.parser().pos()); if (!funNode) return false; |