From 6abb54ff1146b108c4559b3707107553dcf46915 Mon Sep 17 00:00:00 2001 From: Martok Date: Wed, 26 Apr 2023 18:22:30 +0200 Subject: [PATCH 01/23] Issue #2142 - Reduce calls to FindReservedWord when checking for forbidden identifiers during parsing Based-on: m-c 1351913/{1,2} --- js/src/frontend/Parser.cpp | 149 ++++++++++++++++++-------------- js/src/frontend/Parser.h | 12 +-- js/src/frontend/TokenStream.cpp | 40 +++------ js/src/frontend/TokenStream.h | 23 +++-- 4 files changed, 122 insertions(+), 102 deletions(-) diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 7a5cef7d78..a4d761a2a1 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -994,12 +994,15 @@ Parser::parse() bool ParserBase::isValidStrictBinding(PropertyName* name) { - return name != context->names().eval && - name != context->names().arguments && - name != context->names().let && - name != context->names().static_ && - name != context->names().yield && - !IsStrictReservedWord(name); + TokenKind tt = ReservedWordTokenKind(name); + if (tt == TOK_NAME) { + return name != context->names().eval && + name != context->names().arguments; + } + return tt != TOK_LET && + tt != TOK_STATIC && + tt != TOK_YIELD && + !TokenKindIsStrictReservedWord(tt); } /* @@ -3725,6 +3728,7 @@ Parser::functionFormalParametersAndBody(InHandling inHandling, // The same goes when parsing |await| in arrow functions. YieldHandling bodyYieldHandling = GetYieldHandling(pc->generatorKind()); AwaitHandling bodyAwaitHandling = GetAwaitHandling(pc->asyncKind()); + bool inheritedStrict = pc->sc()->strict(); LexicalScopeNodeType body; { AutoAwaitIsKeyword awaitIsKeyword(this, bodyAwaitHandling); @@ -3733,9 +3737,15 @@ Parser::functionFormalParametersAndBody(InHandling inHandling, return false; } + // Revalidate the function name when we transitioned to strict mode. if ((kind == FunctionSyntaxKind::Statement || - kind == FunctionSyntaxKind::Expression) && fun->explicitName()) { - RootedPropertyName propertyName(context, fun->explicitName()->asPropertyName()); + kind == FunctionSyntaxKind::Expression) && fun->explicitName() + && !inheritedStrict && pc->sc()->strict()) + { + MOZ_ASSERT(pc->sc()->hasExplicitUseStrict(), + "strict mode should only change when a 'use strict' directive is present"); + + PropertyName* propertyName = fun->explicitName()->asPropertyName(); YieldHandling nameYieldHandling; if (kind == FunctionSyntaxKind::Expression) { // Named lambda has binding inside it. @@ -9675,75 +9685,80 @@ Parser::memberCall( template bool -Parser::checkLabelOrIdentifierReference(HandlePropertyName ident, +Parser::checkLabelOrIdentifierReference(PropertyName* ident, uint32_t offset, - YieldHandling yieldHandling) + YieldHandling yieldHandling, + TokenKind hint /* = TOK_LIMIT */) { - if (ident == context->names().yield) { - if (yieldHandling == YieldIsKeyword || - versionNumber() >= JSVERSION_1_7) - { - errorAt(offset, JSMSG_RESERVED_ID, "yield"); - return false; + TokenKind tt; + if (hint == TOK_LIMIT) { + tt = ReservedWordTokenKind(ident); + } else { + MOZ_ASSERT(hint == ReservedWordTokenKind(ident), "hint doesn't match actual token kind"); + tt = hint; + } + + if (tt == TOK_NAME) + return true; + if (TokenKindIsContextualKeyword(tt)) { + if (tt == TOK_YIELD) { + if (yieldHandling == YieldIsKeyword || versionNumber() >= JSVERSION_1_7) { + errorAt(offset, JSMSG_RESERVED_ID, "yield"); + return false; + } + if (pc->sc()->needStrictChecks()) { + if (!strictModeErrorAt(offset, JSMSG_RESERVED_ID, "yield")) + return false; + } + return true; + } + if (tt == TOK_AWAIT) { + if (awaitIsKeyword()) { + errorAt(offset, JSMSG_RESERVED_ID, "await"); + return false; + } + return true; } if (pc->sc()->needStrictChecks()) { - if (!strictModeErrorAt(offset, JSMSG_RESERVED_ID, "yield")) - return false; - } - - return true; - } - - if (ident == context->names().await) { - if (awaitIsKeyword()) { - errorAt(offset, JSMSG_RESERVED_ID, "await"); - return false; + if (tt == TOK_LET) { + if (!strictModeErrorAt(offset, JSMSG_RESERVED_ID, "let")) + return false; + return true; + } + if (tt == TOK_STATIC) { + if (!strictModeErrorAt(offset, JSMSG_RESERVED_ID, "static")) + return false; + return true; + } } return true; } - - if (IsKeyword(ident) || IsReservedWordLiteral(ident)) { - errorAt(offset, JSMSG_INVALID_ID, ReservedWordToCharZ(ident)); + if (TokenKindIsStrictReservedWord(tt)) { + if (pc->sc()->needStrictChecks()) { + if (!strictModeErrorAt(offset, JSMSG_RESERVED_ID, ReservedWordToCharZ(tt))) + return false; + } + return true; + } + if (TokenKindIsKeyword(tt) || TokenKindIsReservedWordLiteral(tt)) { + errorAt(offset, JSMSG_INVALID_ID, ReservedWordToCharZ(tt)); return false; } - - if (IsFutureReservedWord(ident)) { - errorAt(offset, JSMSG_RESERVED_ID, ReservedWordToCharZ(ident)); + if (TokenKindIsFutureReservedWord(tt)) { + errorAt(offset, JSMSG_RESERVED_ID, ReservedWordToCharZ(tt)); return false; } - - if (pc->sc()->needStrictChecks()) { - if (IsStrictReservedWord(ident)) { - if (!strictModeErrorAt(offset, JSMSG_RESERVED_ID, ReservedWordToCharZ(ident))) - return false; - return true; - } - - if (ident == context->names().let) { - if (!strictModeErrorAt(offset, JSMSG_RESERVED_ID, "let")) - return false; - return true; - } - - if (ident == context->names().static_) { - if (!strictModeErrorAt(offset, JSMSG_RESERVED_ID, "static")) - return false; - return true; - } - } - - return true; + MOZ_ASSERT_UNREACHABLE("Unexpected reserved word kind."); + return false; } template bool -Parser::checkBindingIdentifier(HandlePropertyName ident, +Parser::checkBindingIdentifier(PropertyName* ident, uint32_t offset, - YieldHandling yieldHandling) + YieldHandling yieldHandling, + TokenKind hint /* = TOK_LIMIT */) { - if (!checkLabelOrIdentifierReference(ident, offset, yieldHandling)) - return false; - if (pc->sc()->needStrictChecks()) { if (ident == context->names().arguments) { if (!strictModeErrorAt(offset, JSMSG_BAD_STRICT_ASSIGN, "arguments")) @@ -9758,7 +9773,7 @@ Parser::checkBindingIdentifier(HandlePropertyName ident, } } - return true; + return checkLabelOrIdentifierReference(ident, offset, yieldHandling, hint); } template @@ -9772,8 +9787,13 @@ Parser::labelOrIdentifierReference(YieldHandling yieldHandling) // // Use PropertyName* instead of TokenKind to reflect the normalization. + // Unless the name contains escapes, we can reuse the current TokenKind + // to determine if the name is a restricted identifier. + TokenKind hint = !tokenStream.currentNameHasEscapes() + ? tokenStream.currentToken().type + : TOK_LIMIT; RootedPropertyName ident(context, tokenStream.currentName()); - if (!checkLabelOrIdentifierReference(ident, pos().begin, yieldHandling)) + if (!checkLabelOrIdentifierReference(ident, pos().begin, yieldHandling, hint)) return nullptr; return ident; } @@ -9782,8 +9802,11 @@ template PropertyName* Parser::bindingIdentifier(YieldHandling yieldHandling) { + TokenKind hint = !tokenStream.currentNameHasEscapes() + ? tokenStream.currentToken().type + : TOK_LIMIT; RootedPropertyName ident(context, tokenStream.currentName()); - if (!checkBindingIdentifier(ident, pos().begin, yieldHandling)) + if (!checkBindingIdentifier(ident, pos().begin, yieldHandling, hint)) return nullptr; return ident; } diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 4dd9f64178..bd111e54fe 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -1479,17 +1479,19 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) ClassNodeType classDefinition(YieldHandling yieldHandling, ClassContext classContext, DefaultHandling defaultHandling); - bool checkLabelOrIdentifierReference(HandlePropertyName ident, + bool checkLabelOrIdentifierReference(PropertyName* ident, uint32_t offset, - YieldHandling yieldHandling); + YieldHandling yieldHandling, + TokenKind hint = TOK_LIMIT); - bool checkLocalExportName(HandlePropertyName ident, uint32_t offset) { + bool checkLocalExportName(PropertyName* ident, uint32_t offset) { return checkLabelOrIdentifierReference(ident, offset, YieldIsName); } - bool checkBindingIdentifier(HandlePropertyName ident, + bool checkBindingIdentifier(PropertyName* ident, uint32_t offset, - YieldHandling yieldHandling); + YieldHandling yieldHandling, + TokenKind hint = TOK_LIMIT); PropertyName* labelOrIdentifierReference(YieldHandling yieldHandling); diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp index 2539249ad9..a537c5f275 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -194,41 +194,29 @@ frontend::IsKeyword(JSLinearString* str) return false; } -bool -frontend::IsFutureReservedWord(JSLinearString* str) +TokenKind +frontend::ReservedWordTokenKind(PropertyName* str) { if (const ReservedWordInfo* rw = FindReservedWord(str)) - return TokenKindIsFutureReservedWord(rw->tokentype); + return rw->tokentype; - return false; -} - -bool -frontend::IsStrictReservedWord(JSLinearString* str) -{ - if (const ReservedWordInfo* rw = FindReservedWord(str)) - return TokenKindIsStrictReservedWord(rw->tokentype); - - return false; -} - -bool -frontend::IsReservedWordLiteral(JSLinearString* str) -{ - if (const ReservedWordInfo* rw = FindReservedWord(str)) - return TokenKindIsReservedWordLiteral(rw->tokentype); - - return false; + return TOK_NAME; } const char* frontend::ReservedWordToCharZ(PropertyName* str) { - const ReservedWordInfo* rw = FindReservedWord(str); - if (rw == nullptr) - return nullptr; + if (const ReservedWordInfo* rw = FindReservedWord(str)) + return ReservedWordToCharZ(rw->tokentype); - switch (rw->tokentype) { + return nullptr; +} + +const char* +frontend::ReservedWordToCharZ(TokenKind tt) +{ + MOZ_ASSERT(tt != TOK_NAME); + switch (tt) { #define EMIT_CASE(word, name, type) case type: return js_##word##_str; FOR_EACH_JAVASCRIPT_RESERVED_WORD(EMIT_CASE) #undef EMIT_CASE diff --git a/js/src/frontend/TokenStream.h b/js/src/frontend/TokenStream.h index 7129ae6d76..067f11c8d8 100644 --- a/js/src/frontend/TokenStream.h +++ b/js/src/frontend/TokenStream.h @@ -244,17 +244,14 @@ class CompileError : public JSErrorReport { void throwError(JSContext* cx); }; +extern TokenKind +ReservedWordTokenKind(PropertyName* str); + extern const char* ReservedWordToCharZ(PropertyName* str); -extern MOZ_MUST_USE bool -IsFutureReservedWord(JSLinearString* str); - -extern MOZ_MUST_USE bool -IsReservedWordLiteral(JSLinearString* str); - -extern MOZ_MUST_USE bool -IsStrictReservedWord(JSLinearString* str); +extern const char* +ReservedWordToCharZ(TokenKind tt); // Ideally, tokenizing would be entirely independent of context. But the // strict mode flag, which is in SharedContext, affects tokenizing, and @@ -355,6 +352,16 @@ class MOZ_STACK_CLASS TokenStream return reservedWordToPropertyName(currentToken().type); } + bool currentNameHasEscapes() const { + if (isCurrentTokenType(TOK_NAME)) { + TokenPos pos = currentToken().pos; + return (pos.end - pos.begin) != currentToken().name()->length(); + } + + MOZ_ASSERT(TokenKindIsPossibleIdentifierName(currentToken().type)); + return false; + } + PropertyName* nextName() const { if (nextToken().type != TOK_NAME) { return nextToken().name(); From afa7bddc9640c845895d8de3cecdc2f6ec63e4f0 Mon Sep 17 00:00:00 2001 From: Martok Date: Sat, 8 Apr 2023 04:47:37 +0200 Subject: [PATCH 02/23] Issue #2142 - Add internal option for fields, but always true This allows us to perform bisects on the following commits without causing runtime issues. Based-on: m-c 1529758 --- js/src/jsapi.cpp | 1 + js/src/jsapi.h | 2 ++ 2 files changed, 3 insertions(+) diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index c0bcad81ef..98a940904d 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -3873,6 +3873,7 @@ JS::TransitiveCompileOptions::copyPODTransitiveOptions(const TransitiveCompileOp forceAsync = rhs.forceAsync; installedFile = rhs.installedFile; sourceIsLazy = rhs.sourceIsLazy; + fieldsEnabledOption = rhs.fieldsEnabledOption; introductionType = rhs.introductionType; introductionLineno = rhs.introductionLineno; introductionOffset = rhs.introductionOffset; diff --git a/js/src/jsapi.h b/js/src/jsapi.h index a6a5429cf5..4640ab0b50 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -3820,6 +3820,7 @@ class JS_FRIEND_API(TransitiveCompileOptions) forceAsync(false), installedFile(false), sourceIsLazy(false), + fieldsEnabledOption(true), introductionType(nullptr), introductionLineno(0), introductionOffset(0), @@ -3855,6 +3856,7 @@ class JS_FRIEND_API(TransitiveCompileOptions) bool forceAsync; bool installedFile; // 'true' iff pre-compiling js file in packaged app bool sourceIsLazy; + bool fieldsEnabledOption; // |introductionType| is a statically allocated C string: // one of "eval", "Function", or "GeneratorFunction". From 51db22ff2f9592495ddebcdec75b94ff6320c8b6 Mon Sep 17 00:00:00 2001 From: Martok Date: Thu, 6 Apr 2023 03:10:50 +0200 Subject: [PATCH 03/23] 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 --- js/src/builtin/ReflectParse.cpp | 87 ++- js/src/frontend/BytecodeCompiler.h | 10 +- js/src/frontend/BytecodeEmitter.cpp | 296 +++++++++- js/src/frontend/BytecodeEmitter.h | 9 + js/src/frontend/FoldConstants.cpp | 18 +- js/src/frontend/FullParseHandler.h | 46 +- js/src/frontend/NameFunctions.cpp | 20 +- js/src/frontend/ParseNode.cpp | 11 +- js/src/frontend/ParseNode.h | 76 ++- js/src/frontend/Parser.cpp | 807 +++++++++++++++++++++------ js/src/frontend/Parser.h | 38 +- js/src/frontend/SharedContext.h | 10 +- js/src/frontend/SyntaxParseHandler.h | 9 +- js/src/frontend/TokenKind.h | 2 + js/src/frontend/TokenStream.cpp | 182 ++++-- js/src/frontend/TokenStream.h | 10 +- js/src/js.msg | 4 + js/src/jsapi.cpp | 2 +- js/src/jsast.tbl | 1 + js/src/jsscript.cpp | 24 +- js/src/jsscript.h | 39 +- js/src/vm/CommonPropertyNames.h | 2 + js/src/vm/Opcodes.h | 4 +- js/src/wasm/AsmJS.cpp | 2 +- 24 files changed, 1401 insertions(+), 308 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(), false, dst); - case PNK_CLASSMETHODLIST: + case PNK_CLASSMEMBERLIST: { - ListNode* methodList = &pn->as(); - NodeVector methods(cx); - if (!methods.reserve(methodList->count())) + ListNode* memberList = &pn->as(); + NodeVector members(cx); + if (!members.reserve(memberList->count())) return false; - for (ParseNode* item : methodList->contents()) { - ClassMethod* method = &item->as(); - MOZ_ASSERT(methodList->pn_pos.encloses(method->pn_pos)); + for (ParseNode* item : memberList->contents()) { + if (item->is()) { + ClassField* field = &item->as(); + 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(); + 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: @@ -2732,6 +2759,34 @@ ASTSerializer::classMethod(ClassMethod* classMethod, MutableHandleValue dst) builder.classMethod(key, val, kind, isStatic, &classMethod->pn_pos, 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() + .scopeBody()->as() + .head()->as() + .kid()->as() + .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) { diff --git a/js/src/frontend/BytecodeCompiler.h b/js/src/frontend/BytecodeCompiler.h index 6d03f8f9fb..0a4703ae1e 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); +static 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()); @@ -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()) { + // 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()) { + FunctionNode* initializer = propdef->as().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* field = &propdef->as(); + 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* field = &propdef->as(); + if (field->name().getKind() == PNK_COMPUTED_NAME) { + ParseNode* nameExpr = field->name().as().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()) { + FunctionNode* initializer = propdef->as().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(); - ParseNode& methodName = method.name(); - if (!method.isStatic() && - (methodName.isKind(PNK_OBJECT_PROPERTY_NAME) || methodName.isKind(PNK_STRING)) && - methodName.as().atom() == cx->names().constructor) - { - constructor = &method.method(); - break; + for (ParseNode* mn : classMembers->contents()) { + if (mn->is()) { + ClassMethod& method = mn->as(); + ParseNode& methodName = method.name(); + if (!method.isStatic() && + (methodName.isKind(PNK_OBJECT_PROPERTY_NAME) || methodName.isKind(PNK_STRING)) && + methodName.as().atom() == cx->names().constructor) + { + constructor = &method.method(); + break; + } } } + // set this->fieldInitializers_ + AutoResetFieldInitializers _innermostClassAutoReset(this, setupFieldInitializers(classMembers)); + bool savedStrictness = sc->setLocalStrictMode(true); Maybe 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& parser, bo return true; case PNK_OBJECT_PROPERTY_NAME: + case PNK_PRIVATE_NAME: case PNK_STRING: case PNK_TEMPLATE_STRING: MOZ_ASSERT(pn->is()); @@ -1810,7 +1812,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& 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& parser, bo Fold(cx, node->unsafeRightReference(), parser, inGenexpLambda); } + case PNK_CLASSFIELD: { + ClassField* node = &pn->as(); + 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_(name, heritage, methodBlock, pos); + ClassNodeType newClass(Node name, Node heritage, Node memberBlock, const TokenPos& pos) { + return new_(name, heritage, memberBlock, pos); } - ListNodeType newClassMethodList(uint32_t begin) { - return new_(PNK_CLASSMETHODLIST, TokenPos(begin, begin + 1)); + ListNodeType newClassMemberList(uint32_t begin) { + return new_(PNK_CLASSMEMBERLIST, TokenPos(begin, begin + 1)); } ClassNamesType newClassNames(Node outer, Node inner, const TokenPos& pos) { return new_(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_(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_(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_(syntaxKind, pos()); + FunctionNodeType newFunction(FunctionSyntaxKind syntaxKind, const TokenPos& pos) { + return new_(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_(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_(kind, op, TokenPos(begin, begin + 1)); + return new_(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().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()); @@ -498,6 +501,19 @@ class NameResolver break; } + case PNK_CLASSFIELD: { + ClassField* node = &cur->as(); + 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(); 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().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(); + 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(); @@ -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(), 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, "#"); 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) \ @@ -778,6 +784,12 @@ class ParseNode ParseNode* initOrStmt; /* var initializer, argument default, * or label statement target */ } name; + struct { + private: + friend class ClassField; + ParseNode* name; + ParseNode* initializer; /* field initializer - optional */ + } field; struct { private: friend class RegExpLiteral; @@ -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() : 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()); - MOZ_ASSERT(methodsOrBlock->is() || - methodsOrBlock->isKind(PNK_CLASSMETHODLIST)); + MOZ_ASSERT(membersOrBlock->is() || + 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* memberList() const { + ParseNode* membersOrBlock = kid3(); + if (membersOrBlock->isKind(PNK_CLASSMEMBERLIST)) + return &membersOrBlock->as(); - ListNode* list = &methodsOrBlock->as().scopeBody()->as(); - MOZ_ASSERT(list->isKind(PNK_CLASSMETHODLIST)); + ListNode* list = &membersOrBlock->as().scopeBody()->as(); + MOZ_ASSERT(list->isKind(PNK_CLASSMEMBERLIST)); return list; } Handle 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 { @@ -549,6 +550,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) { @@ -2294,7 +2305,7 @@ Parser::hasUsedFunctionSpecialName(HandlePropertyName name) template bool -Parser::declareFunctionThis() +Parser::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::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::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::standaloneFunction(HandleFunction fun, template bool -Parser::declareFunctionArgumentsObject() +Parser::declareFunctionArgumentsObject(bool canSkipLazyClosedOverBindings) { FunctionBox* funbox = pc->functionBox(); ParseContext::Scope& funScope = pc->functionScope(); @@ -2597,7 +2609,7 @@ Parser::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::functionBody(InHandling inHandling, YieldHandling yieldHan uint32_t startYieldOffset = pc->lastYieldOffset; #endif + // One might expect noteUsedName(".initializers") here when parsing a + // constructor. See Parser::classDefinition on why + // it's not here. + Node body; if (type == StatementListBody) { bool inheritedStrict = pc->sc()->strict(); @@ -2756,9 +2772,10 @@ Parser::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 JSFunction* Parser::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::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::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::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::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) { @@ -7377,6 +7394,224 @@ JSOpFromPropertyType(PropertyType propType) } } +template +bool +Parser::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 +bool +Parser::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::ClassNodeType Parser::classDefinition(YieldHandling yieldHandling, @@ -7392,14 +7627,14 @@ Parser::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::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 innerScopeStmt; - Maybe 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(); - 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) - return null(); - } - - if (!mustMatchToken(TOK_LC, JSMSG_CURLY_BEFORE_CLASS)) { - return null(); - } - - ListNodeType classMethods = handler.newClassMethodList(pos().begin); - if (!classMethods) - return null(); - - Maybe declKind = Nothing(); - for (;;) { - TokenKind tt; - if (!tokenStream.getToken(&tt)) - return null(); - if (tt == TOK_RC) - break; - - if (tt == TOK_SEMI) - continue; - - bool isStatic = false; - if (tt == TOK_STATIC) { - if (!tokenStream.peekToken(&tt)) - return null(); - if (tt == TOK_RC) { - tokenStream.consumeKnownToken(tt); - error(JSMSG_UNEXPECTED_TOKEN, "property name", TokenKindToDesc(tt)); - 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) - 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); - return null(); - } - - 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(nameOffset, JSMSG_BAD_METHOD_DEF); - return null(); - } - if (classStmt.constructorBox) { - errorAt(nameOffset, JSMSG_DUPLICATE_PROPERTY, "constructor"); - 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; - } - - // 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)) - 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; - } + // Push a ParseContext::ClassStatement to keep track of the constructor + // funbox. + ParseContext::ClassStatement classStmt(pc); + NameNodeType innerName; Node nameNode = null(); - Node methodsOrBlock = classMethods; - if (name) { - // The inner name is immutable. - if (!noteDeclaredName(name, DeclarationKind::Const, namePos)) + Node classHeritage = null(); + 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(); - NameNodeType innerName = newName(name, namePos); - if (!innerName) + 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) + return null(); + } + + if (!mustMatchToken(TOK_LC, JSMSG_CURLY_BEFORE_CLASS)) { + return null(); + } + + ListNodeType classMembers = handler.newClassMemberList(pos().begin); + if (!classMembers) return null(); - LexicalScopeNodeType classBlock = finishLexicalScope(*innerScope, classMethods); + 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; + } + + 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 (!noteDeclaredName(context->names().dotInitializers, + DeclarationKind::Var, namePos)) + return null(); + } + + if (numFieldKeys > 0) { + if (!noteDeclaredName(context->names().dotFieldKeys, DeclarationKind::Let, namePos)) + return null(); + } + classEndOffset = pos().end; + if (!finishClassConstructor(classStmt, className, classStartOffset, + classEndOffset, numFields, classMembers)) + return null(); + + if (className) { + // The inner name is immutable. + if (!noteDeclaredName(className, DeclarationKind::Const, namePos)) + return null(); + + innerName = newName(className, namePos); + if (!innerName) + return null(); + } + + 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,10 +7773,264 @@ Parser::classDefinition(YieldHandling yieldHandling, MOZ_ALWAYS_TRUE(setLocalStrictMode(savedStrictness)); - return handler.newClass(nameNode, classHeritage, methodsOrBlock, + return handler.newClass(nameNode, classHeritage, classBlock, TokenPos(classStartOffset, classEndOffset)); } +template +typename ParseHandler::FunctionNodeType +Parser::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::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 +typename ParseHandler::FunctionNodeType +Parser::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 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 bool Parser::nextTokenContinuesLetDeclaration(TokenKind next, YieldHandling yieldHandling) @@ -8543,7 +8966,7 @@ Parser::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::Node Parser::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::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::Node Parser::propertyName(YieldHandling yieldHandling, + PropertyNameContext propertyNameContext, const Maybe& maybeDecl, ListNodeType propList, PropertyType* propType, MutableHandleAtom propAtom) { @@ -10226,6 +10655,16 @@ Parser::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::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::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 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& 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(); } @@ -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..2c3371b465 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 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,14 +155,52 @@ GetSingleCodePoint(const char16_t** p, const char16_t* end) return codePoint; } +namespace js { + +namespace frontend { + +// Latin1 Variants + +bool +IsIdentifier(const Latin1Char* chars, size_t length) +{ + return ::IsIdentifier(chars, length); +} + +static 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); +} + static bool IsIdentifierMaybeNonBMP(const char16_t* chars, size_t length) { - if (IsIdentifier(chars, length)) - return true; - 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; @@ -164,56 +219,75 @@ IsIdentifierMaybeNonBMP(const char16_t* chars, size_t length) return true; } +static bool +IsIdentifierNameOrPrivateNameMaybeNonBMP(const char16_t* chars, size_t 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(JSLinearString* str) +IsIdentifier(JSLinearString* str) { JS::AutoCheckCannotGC nogc; - return str->hasLatin1Chars() - ? ::IsIdentifier(str->latin1Chars(nogc), str->length()) - : ::IsIdentifierMaybeNonBMP(str->twoByteChars(nogc), str->length()); + if (str->hasLatin1Chars()) { + return IsIdentifier(str->latin1Chars(nogc), str->length()); + + } + return IsIdentifierMaybeNonBMP(str->twoByteChars(nogc), str->length()); } bool -frontend::IsIdentifier(const char* 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::IsIdentifier(const char16_t* chars, size_t length) +IsKeyword(JSLinearString* str) { - return ::IsIdentifier(chars, length); -} - -bool -frontend::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(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* 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* 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* 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* 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* 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* 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 98fcff4203..a56d4a3830 100644 --- a/js/src/wasm/AsmJS.cpp +++ b/js/src/wasm/AsmJS.cpp @@ -7045,7 +7045,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; From ab7721e4df3fa718075d7f96c50e3e82e8ec9edf Mon Sep 17 00:00:00 2001 From: Martok Date: Fri, 7 Apr 2023 19:32:22 +0200 Subject: [PATCH 04/23] Issue #2142 - Improve TokenPos handling in BCE * Don't print bogus error locations on BCE internal errors * Do not use TokenPos in BytecodeEmitter::{setFunctionBodyEndPos,setScriptStartOffsetIfUnset} Based-on: m-c 1451826/1, 1530034, 1473796/2 --- js/src/frontend/BytecodeEmitter.cpp | 27 +++++++++++++++------------ js/src/frontend/BytecodeEmitter.h | 17 +++++++++++------ 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index c18b5d933f..442c0bcae6 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -190,8 +190,7 @@ BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, hasSingletons(false), hasTryFinally(false), emittingRunOnceLambda(false), - emitterMode(emitterMode), - functionBodyEndPosSet(false) + emitterMode(emitterMode) { MOZ_ASSERT_IF(emitterMode == LazyFunction, lazyScript); } @@ -204,7 +203,8 @@ BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, parser->tokenStream.srcCoords.lineNum(bodyPosition.begin), emitterMode) { - setFunctionBodyEndPos(bodyPosition); + setScriptStartOffsetIfUnset(bodyPosition.begin); + setFunctionBodyEndPos(bodyPosition.end); } bool @@ -1645,11 +1645,11 @@ BytecodeEmitter::tokenStream() bool BytecodeEmitter::reportError(ParseNode* pn, unsigned errorNumber, ...) { - TokenPos pos = pn ? pn->pn_pos : tokenStream().currentToken().pos; + uint32_t offset = pn ? pn->pn_pos.begin : *scriptStartOffset; va_list args; va_start(args, errorNumber); - bool result = tokenStream().reportCompileErrorNumberVA(nullptr, pos.begin, JSREPORT_ERROR, + bool result = tokenStream().reportCompileErrorNumberVA(nullptr, offset, JSREPORT_ERROR, errorNumber, args); va_end(args); return result; @@ -1658,7 +1658,7 @@ BytecodeEmitter::reportError(ParseNode* pn, unsigned errorNumber, ...) bool BytecodeEmitter::reportError(const mozilla::Maybe& maybeOffset, unsigned errorNumber, ...) { - uint32_t offset = maybeOffset ? *maybeOffset : tokenStream().currentToken().pos.begin; + uint32_t offset = maybeOffset ? *maybeOffset : *scriptStartOffset; va_list args; va_start(args, errorNumber); @@ -1671,11 +1671,11 @@ BytecodeEmitter::reportError(const mozilla::Maybe& maybeOffset, unsign bool BytecodeEmitter::reportExtraWarning(ParseNode* pn, unsigned errorNumber, ...) { - TokenPos pos = pn ? pn->pn_pos : tokenStream().currentToken().pos; + uint32_t offset = pn ? pn->pn_pos.begin : *scriptStartOffset; va_list args; va_start(args, errorNumber); - bool result = tokenStream().reportExtraWarningErrorNumberVA(nullptr, pos.begin, + bool result = tokenStream().reportExtraWarningErrorNumberVA(nullptr, offset, errorNumber, args); va_end(args); return result; @@ -2323,6 +2323,8 @@ BytecodeEmitter::emitSetThis(BinaryNode* setThisNode) bool BytecodeEmitter::emitScript(ParseNode* body) { + setScriptStartOffsetIfUnset(body->pn_pos.begin); + TDZCheckCache tdzCache(this); EmitterScope emitterScope(this); if (sc->isGlobalContext()) { @@ -2341,7 +2343,7 @@ BytecodeEmitter::emitScript(ParseNode* body) return false; } - setFunctionBodyEndPos(body->pn_pos); + setFunctionBodyEndPos(body->pn_pos.end); if (sc->isEvalContext() && !sc->strict() && body->is() && !body->as().isEmptyScope()) @@ -2451,6 +2453,8 @@ BytecodeEmitter::emitFunctionScript(FunctionNode* funNode) ParseNode* body = funNode->body(); FunctionBox* funbox = sc->asFunctionBox(); + setScriptStartOffsetIfUnset(body->pn_pos.begin); + // The ordering of these EmitterScopes is important. The named lambda // scope needs to enclose the function scope needs to enclose the extra // var scope. @@ -2480,7 +2484,7 @@ BytecodeEmitter::emitFunctionScript(FunctionNode* funNode) switchToMain(); } - setFunctionBodyEndPos(body->pn_pos); + setFunctionBodyEndPos(body->pn_pos.end); if (!emitTree(body)) return false; @@ -6235,8 +6239,7 @@ BytecodeEmitter::emitReturn(UnaryNode* returnNode) // We know functionBodyEndPos is set because "return" is only // valid in a function, and so we've passed through // emitFunctionScript. - MOZ_ASSERT(functionBodyEndPosSet); - if (!updateSourceCoordNotes(functionBodyEndPos)) + if (!updateSourceCoordNotes(*functionBodyEndPos)) return false; /* diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 27f4e92cbd..64c061594b 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -236,10 +236,10 @@ struct MOZ_STACK_CLASS BytecodeEmitter const EmitterMode emitterMode; + mozilla::Maybe scriptStartOffset; + // The end location of a function body that is being emitted. - uint32_t functionBodyEndPos; - // Whether functionBodyEndPos was set. - bool functionBodyEndPosSet; + mozilla::Maybe functionBodyEndPos; /* * Note that BytecodeEmitters are magic: they own the arena "top-of-stack" @@ -350,9 +350,14 @@ struct MOZ_STACK_CLASS BytecodeEmitter return lastOpcodeIsJumpTarget() ? current->lastTarget.offset : offset(); } - void setFunctionBodyEndPos(TokenPos pos) { - functionBodyEndPos = pos.end; - functionBodyEndPosSet = true; + void setFunctionBodyEndPos(uint32_t pos) { + functionBodyEndPos = mozilla::Some(pos); + } + + void setScriptStartOffsetIfUnset(uint32_t pos) { + if (scriptStartOffset.isNothing()) { + scriptStartOffset = mozilla::Some(pos); + } } bool reportError(ParseNode* pn, unsigned errorNumber, ...); From 1b89be6d0b9822318f03c235df780bd999f67fe8 Mon Sep 17 00:00:00 2001 From: Martok Date: Fri, 7 Apr 2023 18:59:43 +0200 Subject: [PATCH 05/23] Issue #2142 - Add PropertyEmitter, ObjectEmitter, ClassEmitter, LexicalScopeEmitter, DefaultEmitter Based-on: m-c 1501577, 1521696, 1501578, 1473796/1 --- js/src/frontend/BytecodeEmitter.cpp | 626 ++++++++++--------- js/src/frontend/BytecodeEmitter.h | 20 +- js/src/frontend/DefaultEmitter.cpp | 73 +++ js/src/frontend/DefaultEmitter.h | 65 ++ js/src/frontend/EmitterScope.cpp | 4 +- js/src/frontend/EmitterScope.h | 4 +- js/src/frontend/LexicalScopeEmitter.cpp | 60 ++ js/src/frontend/LexicalScopeEmitter.h | 99 +++ js/src/frontend/ObjectEmitter.cpp | 762 ++++++++++++++++++++++++ js/src/frontend/ObjectEmitter.h | 727 ++++++++++++++++++++++ js/src/moz.build | 3 + 11 files changed, 2139 insertions(+), 304 deletions(-) create mode 100644 js/src/frontend/DefaultEmitter.cpp create mode 100644 js/src/frontend/DefaultEmitter.h create mode 100644 js/src/frontend/LexicalScopeEmitter.cpp create mode 100644 js/src/frontend/LexicalScopeEmitter.h create mode 100644 js/src/frontend/ObjectEmitter.cpp create mode 100644 js/src/frontend/ObjectEmitter.h diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 442c0bcae6..b76d6746a0 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -30,11 +30,14 @@ #include "ds/Nestable.h" #include "frontend/BytecodeControlStructures.h" #include "frontend/CallOrNewEmitter.h" +#include "frontend/DefaultEmitter.h" // DefaultEmitter #include "frontend/ElemOpEmitter.h" #include "frontend/EmitterScope.h" #include "frontend/ForOfLoopControl.h" #include "frontend/IfEmitter.h" +#include "frontend/LexicalScopeEmitter.h" // LexicalScopeEmitter #include "frontend/NameOpEmitter.h" +#include "frontend/ObjectEmitter.h" // PropertyEmitter, ObjectEmitter, ClassEmitter #include "frontend/Parser.h" #include "frontend/PropOpEmitter.h" #include "frontend/SwitchEmitter.h" @@ -3004,62 +3007,70 @@ BytecodeEmitter::wrapWithDestructuringIteratorCloseTryNote(int32_t iterDepth, In bool BytecodeEmitter::emitDefault(ParseNode* defaultExpr, ParseNode* pattern) { - if (!emit1(JSOP_DUP)) // VALUE VALUE + // [stack] VALUE + + DefaultEmitter de(this); + if (!de.prepareForDefault()) { + // [stack] return false; - if (!emit1(JSOP_UNDEFINED)) // VALUE VALUE UNDEFINED + } + if (!emitInitializer(defaultExpr, pattern)) { + // [stack] DEFAULTVALUE return false; - if (!emit1(JSOP_STRICTEQ)) // VALUE EQL? - return false; - // Emit source note to enable ion compilation. - if (!newSrcNote(SRC_IF)) - return false; - JumpList jump; - if (!emitJump(JSOP_IFEQ, &jump)) // VALUE - return false; - if (!emit1(JSOP_POP)) // . - return false; - if (!emitInitializerInBranch(defaultExpr, pattern)) // DEFAULTVALUE - return false; - if (!emitJumpTargetAndPatch(jump)) + } + if (!de.emitEnd()) { + // [stack] VALUE/DEFAULTVALUE return false; + } return true; } bool -BytecodeEmitter::setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name, - FunctionPrefixKind prefixKind) +BytecodeEmitter::setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name) { if (maybeFun->is()) { // Function doesn't have 'name' property at this point. // Set function's name at compile time. - RootedFunction fun(cx, maybeFun->as().funbox()->function()); + return setFunName(maybeFun->as().funbox()->function(), name); + } - // Single node can be emitted multiple times if it appears in - // array destructuring default. If function already has a name, - // just return. - if (fun->hasCompileTimeName()) { + MOZ_ASSERT(maybeFun->isKind(PNK_CLASS)); + + return emitSetClassConstructorName(name); +} + +bool +BytecodeEmitter::setFunName(JSFunction* fun, JSAtom* name) +{ + // Single node can be emitted multiple times if it appears in + // array destructuring default. If function already has a name, + // just return. + if (fun->hasCompileTimeName()) { #ifdef DEBUG - RootedAtom funName(cx, NameToFunctionName(cx, name, prefixKind)); - if (!funName) - return false; - MOZ_ASSERT(funName == fun->compileTimeName()); -#endif - return true; - } - - RootedAtom funName(cx, NameToFunctionName(cx, name, prefixKind)); + RootedAtom funName(cx, name); if (!funName) return false; - fun->setCompileTimeName(name); + MOZ_ASSERT(funName == fun->compileTimeName()); +#endif return true; } + RootedAtom funName(cx, name); + if (!funName) + return false; + fun->setCompileTimeName(funName); + return true; +} + +bool +BytecodeEmitter::emitSetClassConstructorName(JSAtom* name) +{ uint32_t nameIndex; if (!makeAtomIndex(name, &nameIndex)) return false; if (!emitIndexOp(JSOP_STRING, nameIndex)) // FUN NAME return false; - uint8_t kind = uint8_t(prefixKind); + uint8_t kind = uint8_t(FunctionPrefixKind::None); if (!emit2(JSOP_SETFUNNAME, kind)) // FUN return false; return true; @@ -3082,13 +3093,6 @@ BytecodeEmitter::emitInitializer(ParseNode* initializer, ParseNode* pattern) return true; } -bool -BytecodeEmitter::emitInitializerInBranch(ParseNode* initializer, ParseNode* pattern) -{ - TDZCheckCache tdzCache(this); - return emitInitializer(initializer, pattern); -} - bool BytecodeEmitter::emitDestructuringOpsArray(ListNode* pattern, DestructuringFlavor flav) { @@ -4224,7 +4228,7 @@ BytecodeEmitter::emitCatch(TernaryNode* catchNode) break; case PNK_NAME: - if (!emitLexicalInitialization(pn2)) + if (!emitLexicalInitialization(&pn2->as())) return false; if (!emit1(JSOP_POP)) return false; @@ -4448,11 +4452,21 @@ BytecodeEmitter::emitLexicalScopeBody(ParseNode* body, EmitLineNumberNote emitLi MOZ_NEVER_INLINE bool BytecodeEmitter::emitLexicalScope(LexicalScopeNode* lexicalScope) { - TDZCheckCache tdzCache(this); + LexicalScopeEmitter lse(this); ParseNode* body = lexicalScope->scopeBody(); - if (lexicalScope->isEmptyScope()) - return emitLexicalScopeBody(body); + if (lexicalScope->isEmptyScope()) { + if (!lse.emitEmptyScope()) + return false; + + if (!emitLexicalScopeBody(body)) + return false; + + if (!lse.emitEnd()) + return false; + + return true; + } // Update line number notes before emitting TDZ poison in // EmitterScope::enterLexical to avoid spurious pausing on seemingly @@ -4478,7 +4492,6 @@ BytecodeEmitter::emitLexicalScope(LexicalScopeNode* lexicalScope) return false; } - EmitterScope emitterScope(this); ScopeKind kind; if (body->isKind(PNK_CATCH)) { TernaryNode* catchNode = &body->as(); @@ -4488,21 +4501,21 @@ BytecodeEmitter::emitLexicalScope(LexicalScopeNode* lexicalScope) } else kind = ScopeKind::Lexical; - if (!emitterScope.enterLexical(this, kind, lexicalScope->scopeBindings())) + if (!lse.emitScope(kind, lexicalScope->scopeBindings())) return false; if (body->isKind(PNK_FOR)) { // for loops need to emit {FRESHEN,RECREATE}LEXICALENV if there are // lexical declarations in the head. Signal this by passing a // non-nullptr lexical scope. - if (!emitFor(&body->as(), &emitterScope)) + if (!emitFor(&body->as(), &lse.emitterScope())) return false; } else { if (!emitLexicalScopeBody(body, SUPPRESS_LINENOTE)) return false; } - return emitterScope.leave(this); + return lse.emitEnd(); } bool @@ -4828,7 +4841,7 @@ BytecodeEmitter::emitInitializeForInOrOfTarget(TernaryNode* forHead) } bool -BytecodeEmitter::emitForOf(ForNode* forNode, EmitterScope* headLexicalEmitterScope) +BytecodeEmitter::emitForOf(ForNode* forNode, const EmitterScope* headLexicalEmitterScope) { MOZ_ASSERT(forNode->isKind(PNK_FOR)); @@ -5001,7 +5014,7 @@ BytecodeEmitter::emitForOf(ForNode* forNode, EmitterScope* headLexicalEmitterSco } bool -BytecodeEmitter::emitForIn(ForNode* forNode, EmitterScope* headLexicalEmitterScope) +BytecodeEmitter::emitForIn(ForNode* forNode, const EmitterScope* headLexicalEmitterScope) { MOZ_ASSERT(forNode->isKind(PNK_FOR)); MOZ_ASSERT(forNode->isOp(JSOP_ITER)); @@ -5150,7 +5163,7 @@ BytecodeEmitter::emitForIn(ForNode* forNode, EmitterScope* headLexicalEmitterSco /* C-style `for (init; cond; update) ...` loop. */ bool -BytecodeEmitter::emitCStyleFor(ForNode* forNode, EmitterScope* headLexicalEmitterScope) +BytecodeEmitter::emitCStyleFor(ForNode* forNode, const EmitterScope* headLexicalEmitterScope) { LoopControl loopInfo(this, StatementKind::ForLoop); @@ -5336,7 +5349,7 @@ BytecodeEmitter::emitCStyleFor(ForNode* forNode, EmitterScope* headLexicalEmitte } bool -BytecodeEmitter::emitFor(ForNode* forNode, EmitterScope* headLexicalEmitterScope) +BytecodeEmitter::emitFor(ForNode* forNode, const EmitterScope* headLexicalEmitterScope) { MOZ_ASSERT(forNode->isKind(PNK_FOR)); @@ -7782,8 +7795,10 @@ BytecodeEmitter::emitConditionalExpression(ConditionalExpression& conditional, } bool -BytecodeEmitter::emitPropertyList(ListNode* obj, MutableHandlePlainObject objp, PropListType type) +BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListType type) { + // [stack] CTOR? OBJ + for (ParseNode* propdef : obj->contents()) { if (propdef->is()) { // Skip over class fields and emit them at the end. This is needed @@ -7791,163 +7806,235 @@ BytecodeEmitter::emitPropertyList(ListNode* obj, MutableHandlePlainObject objp, // into a local variable continue; } - if (!updateSourceCoordNotes(propdef->pn_pos.begin)) - return false; // Handle __proto__: v specially because *only* this form, and no other // involving "__proto__", performs [[Prototype]] mutation. if (propdef->isKind(PNK_MUTATEPROTO)) { + // [stack] OBJ MOZ_ASSERT(type == ObjectLiteral); - if (!emitTree(propdef->as().kid())) + if (!pe.prepareForProtoValue(Some(propdef->pn_pos.begin))) { + // [stack] OBJ return false; - objp.set(nullptr); - if (!emit1(JSOP_MUTATEPROTO)) + } + if (!emitTree(propdef->as().kid())) { + // [stack] OBJ PROTO return false; + } + if (!pe.emitMutateProto()) { + // [stack] OBJ + return false; + } continue; } if (propdef->isKind(PNK_SPREAD)) { MOZ_ASSERT(type == ObjectLiteral); - - if (!emit1(JSOP_DUP)) + // [stack] OBJ + if (!pe.prepareForSpreadOperand(Some(propdef->pn_pos.begin))) { + // [stack] OBJ OBJ return false; - - if (!emitTree(propdef->as().kid())) + } + if (!emitTree(propdef->as().kid())) { + // [stack] OBJ OBJ VAL return false; - - if (!emitCopyDataProperties(CopyOption::Unfiltered)) + } + if (!pe.emitSpread()) { + // [stack] OBJ return false; - - objp.set(nullptr); + } continue; } - bool extraPop = false; - if (type == ClassBody && propdef->as().isStatic()) { - extraPop = true; - if (!emit1(JSOP_DUP2)) - return false; - if (!emit1(JSOP_POP)) - return false; - } - /* Emit an index for t[2] for later consumption by JSOP_INITELEM. */ - ParseNode* key = propdef->as().left(); - bool isIndex = false; - if (key->isKind(PNK_NUMBER)) { - if (!emitNumberOp(key->as().value())) - return false; - isIndex = true; - } else if (key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)) { - // EmitClass took care of constructor already. - if (type == ClassBody && key->as().atom() == cx->names().constructor && - !propdef->as().isStatic()) - { - continue; - } - } else { - MOZ_ASSERT(key->isKind(PNK_COMPUTED_NAME)); - if (!emitComputedPropertyName(&key->as())) - return false; - isIndex = true; - } - - /* Emit code for the property initializer. */ - ParseNode* propVal = propdef->as().right(); - if (!emitTree(propVal)) - return false; + BinaryNode* prop = &propdef->as(); + ParseNode* key = prop->left(); + ParseNode* propVal = prop->right(); + bool isPropertyAnonFunctionOrClass = propVal->isDirectRHSAnonFunction(); JSOp op = propdef->getOp(); - MOZ_ASSERT(op == JSOP_INITPROP || - op == JSOP_INITPROP_GETTER || + MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER); - FunctionPrefixKind prefixKind = op == JSOP_INITPROP_GETTER ? FunctionPrefixKind::Get - : op == JSOP_INITPROP_SETTER ? FunctionPrefixKind::Set - : FunctionPrefixKind::None; + auto emitValue = [this, &propVal, &pe]() { + // [stack] CTOR? OBJ CTOR? KEY? - if (op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER) - objp.set(nullptr); - - if (propVal->is() && - propVal->as().funbox()->needsHomeObject()) { - FunctionBox* funbox = propVal->as().funbox(); - MOZ_ASSERT(funbox->function()->allowSuperProperty()); - bool isAsync = funbox->isAsync(); - if (isAsync) { - if (!emit1(JSOP_SWAP)) - return false; - } - if (!emit2(JSOP_INITHOMEOBJECT, isIndex + isAsync)) + if (!emitTree(propVal)) { + // [stack] CTOR? OBJ CTOR? KEY? VAL return false; - if (isAsync) { - if (!emit1(JSOP_POP)) - return false; } - } - // Class methods are not enumerable. - if (type == ClassBody) { - switch (op) { - case JSOP_INITPROP: op = JSOP_INITHIDDENPROP; break; - case JSOP_INITPROP_GETTER: op = JSOP_INITHIDDENPROP_GETTER; break; - case JSOP_INITPROP_SETTER: op = JSOP_INITHIDDENPROP_SETTER; break; - default: MOZ_CRASH("Invalid op"); - } - } + if (propVal->is() && + propVal->as().funbox()->needsHomeObject()) { + FunctionBox* funbox = propVal->as().funbox(); + MOZ_ASSERT(funbox->function()->allowSuperProperty()); - if (isIndex) { - objp.set(nullptr); - switch (op) { - case JSOP_INITPROP: op = JSOP_INITELEM; break; - case JSOP_INITHIDDENPROP: op = JSOP_INITHIDDENELEM; break; - case JSOP_INITPROP_GETTER: op = JSOP_INITELEM_GETTER; break; - case JSOP_INITHIDDENPROP_GETTER: op = JSOP_INITHIDDENELEM_GETTER; break; - case JSOP_INITPROP_SETTER: op = JSOP_INITELEM_SETTER; break; - case JSOP_INITHIDDENPROP_SETTER: op = JSOP_INITHIDDENELEM_SETTER; break; - default: MOZ_CRASH("Invalid op"); - } - if (propVal->isDirectRHSAnonFunction()) { - if (!emitDupAt(1)) - return false; - if (!emit2(JSOP_SETFUNNAME, uint8_t(prefixKind))) - return false; - } - if (!emit1(op)) - return false; - } else { - MOZ_ASSERT(key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)); - - uint32_t index; - if (!makeAtomIndex(key->as().atom(), &index)) - return false; - - if (objp) { - MOZ_ASSERT(type == ObjectLiteral); - MOZ_ASSERT(!IsHiddenInitOp(op)); - MOZ_ASSERT(!objp->inDictionaryMode()); - Rooted id(cx, AtomToId(key->as().atom())); - if (!NativeDefineProperty(cx, objp, id, UndefinedHandleValue, nullptr, nullptr, - JSPROP_ENUMERATE)) - { + if (!pe.emitInitHomeObject(funbox->asyncKind())) { + // [stack] CTOR? OBJ CTOR? KEY? FUN return false; } - if (objp->inDictionaryMode()) - objp.set(nullptr); } + return true; + }; - if (propVal->isDirectRHSAnonFunction()) { - RootedAtom keyName(cx, key->as().atom()); - if (!setOrEmitSetFunName(propVal, keyName, prefixKind)) - return false; - } - if (!emitIndex32(op, index)) + PropertyEmitter::Kind kind = + (type == ClassBody && propdef->as().isStatic()) + ? PropertyEmitter::Kind::Static + : PropertyEmitter::Kind::Prototype; + + if (key->isKind(PNK_NUMBER)) { + // [stack] CTOR? OBJ + if (!pe.prepareForIndexPropKey(Some(propdef->pn_pos.begin), kind)) { + // [stack] CTOR? OBJ CTOR? return false; + } + if (!emitNumberOp(key->as().value())) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + if (!pe.prepareForIndexPropValue()) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + if (!emitValue()) { + // [stack] CTOR? OBJ CTOR? KEY VAL + return false; + } + + switch (op) { + case JSOP_INITPROP: + if (!pe.emitInitIndexProp(isPropertyAnonFunctionOrClass)) { + // [stack] CTOR? OBJ + return false; + } + break; + case JSOP_INITPROP_GETTER: + MOZ_ASSERT(!isPropertyAnonFunctionOrClass); + if (!pe.emitInitIndexGetter()) { + // [stack] CTOR? OBJ + return false; + } + break; + case JSOP_INITPROP_SETTER: + MOZ_ASSERT(!isPropertyAnonFunctionOrClass); + if (!pe.emitInitIndexSetter()) { + // [stack] CTOR? OBJ + return false; + } + break; + default: + MOZ_CRASH("Invalid op"); + } + continue; } - if (extraPop) { - if (!emit1(JSOP_POP)) + if (key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)) { + // EmitClass took care of constructor already. + if (type == ClassBody && key->as().atom() == cx->names().constructor && + !propdef->as().isStatic()) { + continue; + } + + if (!pe.prepareForPropValue(Some(propdef->pn_pos.begin), kind)) { + // [stack] CTOR? OBJ CTOR? return false; + } + if (!emitValue()) { + // [stack] CTOR? OBJ CTOR? VAL + return false; + } + + RootedFunction anonFunction(cx); + if (isPropertyAnonFunctionOrClass) { + MOZ_ASSERT(op == JSOP_INITPROP); + + if (propVal->is()) { + // When the value is function, we set the function's name + // at the compile-time, instead of emitting SETFUNNAME. + FunctionBox* funbox = propVal->as().funbox(); + anonFunction = funbox->function(); + } else { + // Only object literal can have a property where key is + // name and value is an anonymous class. + // + // ({ foo: class {} }); + MOZ_ASSERT(type == ObjectLiteral); + MOZ_ASSERT(propVal->isKind(PNK_CLASS)); + } + } + + RootedAtom keyAtom(cx, key->as().atom()); + switch (op) { + case JSOP_INITPROP: + if (!pe.emitInitProp(keyAtom, isPropertyAnonFunctionOrClass, + anonFunction)) { + // [stack] CTOR? OBJ + return false; + } + break; + case JSOP_INITPROP_GETTER: + MOZ_ASSERT(!isPropertyAnonFunctionOrClass); + if (!pe.emitInitGetter(keyAtom)) { + // [stack] CTOR? OBJ + return false; + } + break; + case JSOP_INITPROP_SETTER: + MOZ_ASSERT(!isPropertyAnonFunctionOrClass); + if (!pe.emitInitSetter(keyAtom)) { + // [stack] CTOR? OBJ + return false; + } + break; + default: MOZ_CRASH("Invalid op"); + } + + continue; + } + + MOZ_ASSERT(key->isKind(PNK_COMPUTED_NAME)); + + // [stack] CTOR? OBJ + + if (!pe.prepareForComputedPropKey(Some(propdef->pn_pos.begin), kind)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + if (!emitTree(key->as().kid())) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + if (!pe.prepareForComputedPropValue()) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + if (!emitValue()) { + // [stack] CTOR? OBJ CTOR? KEY VAL + return false; + } + + switch (op) { + case JSOP_INITPROP: + if (!pe.emitInitComputedProp(isPropertyAnonFunctionOrClass)) { + // [stack] CTOR? OBJ + return false; + } + break; + case JSOP_INITPROP_GETTER: + MOZ_ASSERT(isPropertyAnonFunctionOrClass); + if (!pe.emitInitComputedGetter()) { + // [stack] CTOR? OBJ + return false; + } + break; + case JSOP_INITPROP_SETTER: + MOZ_ASSERT(isPropertyAnonFunctionOrClass); + if (!pe.emitInitComputedSetter()) { + // [stack] CTOR? OBJ + return false; + } + break; + default: + MOZ_CRASH("Invalid op"); } } @@ -8138,38 +8225,21 @@ BytecodeEmitter::emitObject(ListNode* objNode) if (!objNode->hasNonConstInitializer() && objNode->head() && checkSingletonContext()) return emitSingletonInitialiser(objNode); - /* - * Emit code for {p:a, '%q':b, 2:c} that is equivalent to constructing - * a new object and defining (in source order) each property on the object - * (or mutating the object's [[Prototype]], in the case of __proto__). - */ - ptrdiff_t offset = this->offset(); - if (!emitNewInit(JSProto_Object)) + // [stack] + + ObjectEmitter oe(this); + if (!oe.emitObject(objNode->count())) { + // [stack] OBJ + return false; + } + if (!emitPropertyList(objNode, oe, ObjectLiteral)) { + // [stack] OBJ + return false; + } + if (!oe.emitEnd()) { + // [stack] OBJ return false; - - // Try to construct the shape of the object as we go, so we can emit a - // JSOP_NEWOBJECT with the final shape instead. - // In the case of computed property names and indices, we cannot fix the - // shape at bytecode compile time. When the shape cannot be determined, - // |obj| is nulled out. - - // No need to do any guessing for the object kind, since we know the upper - // bound of how many properties we plan to have. - gc::AllocKind kind = gc::GetGCObjectKind(objNode->count()); - RootedPlainObject obj(cx, NewBuiltinClassInstance(cx, kind, TenuredObject)); - if (!obj) - return false; - - if (!emitPropertyList(objNode, &obj, ObjectLiteral)) - return false; - - if (obj) { - // The object survived and has a predictable shape: update the original - // bytecode. - if (!replaceNewInitWithNewObject(obj, offset)) - return false; } - return true; } @@ -8536,6 +8606,7 @@ BytecodeEmitter::emitFunctionFormalParameters(ListNode* paramsBody) // If we have an initializer, emit the initializer and assign it // to the argument slot. TDZ is taken care of afterwards. MOZ_ASSERT(hasParameterExprs); + IfEmitter ifUndefined(this); if (!emitArgOp(JSOP_GETARG, argSlot)) return false; if (!emit1(JSOP_DUP)) @@ -8544,17 +8615,13 @@ BytecodeEmitter::emitFunctionFormalParameters(ListNode* paramsBody) return false; if (!emit1(JSOP_STRICTEQ)) return false; - // Emit source note to enable Ion compilation. - if (!newSrcNote(SRC_IF)) - return false; - JumpList jump; - if (!emitJump(JSOP_IFEQ, &jump)) + if (!ifUndefined.emitThen()) return false; if (!emit1(JSOP_POP)) return false; - if (!emitInitializerInBranch(initializer, bindingElement)) + if (!emitInitializer(initializer, bindingElement)) return false; - if (!emitJumpTargetAndPatch(jump)) + if (!ifUndefined.emitEnd()) return false; } else if (isRest) { if (!emit1(JSOP_REST)) @@ -8720,9 +8787,15 @@ BytecodeEmitter::emitFunctionBody(ParseNode* funBody) } bool -BytecodeEmitter::emitLexicalInitialization(ParseNode* pn) +BytecodeEmitter::emitLexicalInitialization(NameNode* pn) { - NameOpEmitter noe(this, pn->name(), NameOpEmitter::Kind::Initialize); + return emitLexicalInitialization(pn->name()); +} + +bool +BytecodeEmitter::emitLexicalInitialization(JSAtom* name) +{ + NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize); if (!noe.prepareForRhs()) { return false; } @@ -8738,7 +8811,6 @@ BytecodeEmitter::emitLexicalInitialization(ParseNode* pn) return true; } - class AutoResetFieldInitializers { BytecodeEmitter* bce; @@ -8781,106 +8853,78 @@ BytecodeEmitter::emitClass(ClassNode* classNode) // set this->fieldInitializers_ AutoResetFieldInitializers _innermostClassAutoReset(this, setupFieldInitializers(classMembers)); - bool savedStrictness = sc->setLocalStrictMode(true); + // [stack] + + ClassEmitter ce(this); + RootedAtom innerName(cx); + ClassEmitter::Kind kind = ClassEmitter::Kind::Expression; - Maybe tdzCache; - Maybe emitterScope; if (names) { - tdzCache.emplace(this); - emitterScope.emplace(this); - if (!emitterScope->enterLexical(this, ScopeKind::Lexical, classNode->scopeBindings())) + innerName = names->innerBinding()->as().atom(); + MOZ_ASSERT(innerName); + + if (names->outerBinding()) { + MOZ_ASSERT(names->outerBinding()->as().atom()); + MOZ_ASSERT(names->outerBinding()->as().atom() == innerName); + kind = ClassEmitter::Kind::Declaration; + } + + if (!ce.emitScopeForNamedClass(classNode->scopeBindings())) { + // [stack] return false; + } } // This is kind of silly. In order to the get the home object defined on // the constructor, we have to make it second, but we want the prototype // on top for EmitPropertyList, because we expect static properties to be // rarer. The result is a few more swaps than we would like. Such is life. - if (heritageExpression) { - if (!emitTree(heritageExpression)) + bool isDerived = !!heritageExpression; + if (isDerived) { + if (!emitTree(heritageExpression)) { + // [stack] HERITAGE return false; - if (!emit1(JSOP_CLASSHERITAGE)) - return false; - if (!emit1(JSOP_OBJWITHPROTO)) - return false; - - // JSOP_CLASSHERITAGE leaves both protos on the stack. After - // creating the prototype, swap it to the bottom to make the - // constructor. - if (!emit1(JSOP_SWAP)) + } + if (!ce.emitDerivedClass(innerName)) { + // [stack] HERITAGE HOMEOBJ return false; + } } else { - if (!emitNewInit(JSProto_Object)) + if (!ce.emitClass(innerName)) { + // [stack] HOMEOBJ return false; + } } if (constructor) { - if (!emitFunction(constructor, !!heritageExpression)) + bool needsHomeObject = constructor->funbox()->needsHomeObject(); + // HERITAGE is consumed inside emitFunction. + if (!emitFunction(constructor, isDerived)) { + // [stack] HOMEOBJ CTOR + return false; + } + if (!ce.emitInitConstructor(needsHomeObject)) { + // [stack] CTOR HOMEOBJ return false; - if (constructor->funbox()->needsHomeObject()) { - if (!emit2(JSOP_INITHOMEOBJECT, 0)) - return false; } } else { - // In the case of default class constructors, emit the start and end - // offsets in the source buffer as source notes so that when we - // actually make the constructor during execution, we can give it the - // correct toString output. - ptrdiff_t classStart = ptrdiff_t(classNode->pn_pos.begin); - ptrdiff_t classEnd = ptrdiff_t(classNode->pn_pos.end); - if (!newSrcNote3(SRC_CLASS_SPAN, classStart, classEnd)) + if (!ce.emitInitDefaultConstructor(Some(classNode->pn_pos.begin), + Some(classNode->pn_pos.end))) { + // [stack] CTOR HOMEOBJ return false; - - JSAtom *name = names ? names->innerBinding()->as().atom() : cx->names().empty; - if (heritageExpression) { - if (!emitAtomOp(name, JSOP_DERIVEDCONSTRUCTOR)) - return false; - } else { - if (!emitAtomOp(name, JSOP_CLASSCONSTRUCTOR)) - return false; } } - - if (!emit1(JSOP_SWAP)) + if (!emitPropertyList(classMembers, ce, ClassBody)) { + // [stack] CTOR HOMEOBJ + return false; + } + if (!ce.emitEnd(kind)) { + // [stack] # class declaration + // [stack] + // [stack] # class expression + // [stack] CTOR return false; - - if (!emit1(JSOP_DUP2)) - return false; - if (!emitAtomOp(cx->names().prototype, JSOP_INITLOCKEDPROP)) - return false; - if (!emitAtomOp(cx->names().constructor, JSOP_INITHIDDENPROP)) - return false; - - RootedPlainObject obj(cx); - if (!emitPropertyList(classMembers, &obj, ClassBody)) - return false; - - if (!emit1(JSOP_POP)) - return false; - - if (names) { - ParseNode* innerName = names->innerBinding(); - if (!emitLexicalInitialization(innerName)) - return false; - - // Pop the inner scope. - if (!emitterScope->leave(this)) - return false; - emitterScope.reset(); - - ParseNode* outerName = names->outerBinding(); - if (outerName) { - if (!emitLexicalInitialization(outerName)) - return false; - // Only class statements make outer bindings, and they do not leave - // themselves on the stack. - if (!emit1(JSOP_POP)) - return false; - } } - - MOZ_ALWAYS_TRUE(sc->setLocalStrictMode(savedStrictness)); - return true; } @@ -9230,7 +9274,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage:: if (!emitTree(ed->left())) return false; if (ed->right()) { - if (!emitLexicalInitialization(ed->right())) + if (!emitLexicalInitialization(&ed->right()->as())) return false; if (!emit1(JSOP_POP)) return false; diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 64c061594b..bbb42f367e 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -124,6 +124,7 @@ class CallOrNewEmitter; class ElemOpEmitter; class EmitterScope; class NestableControl; +class PropertyEmitter; class PropOpEmitter; class TDZCheckCache; @@ -521,7 +522,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitHoistedFunctionsInList(ListNode* stmtList); - MOZ_MUST_USE bool emitPropertyList(ListNode* obj, MutableHandlePlainObject objp, + MOZ_MUST_USE bool emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListType type); FieldInitializers setupFieldInitializers(ListNode* classMembers); @@ -695,11 +696,11 @@ struct MOZ_STACK_CLASS BytecodeEmitter // is called at compile time. MOZ_MUST_USE bool emitDefault(ParseNode* defaultExpr, ParseNode* pattern); - MOZ_MUST_USE bool setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name, - FunctionPrefixKind prefixKind = FunctionPrefixKind::None); + MOZ_MUST_USE bool setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name); + MOZ_MUST_USE bool setFunName(JSFunction* fun, JSAtom* name); + MOZ_MUST_USE bool emitSetClassConstructorName(JSAtom* name); MOZ_MUST_USE bool emitInitializer(ParseNode* initializer, ParseNode* pattern); - MOZ_MUST_USE bool emitInitializerInBranch(ParseNode* initializer, ParseNode* pattern); MOZ_MUST_USE bool emitCallSiteObject(CallSiteNode* callSiteObj); MOZ_MUST_USE bool emitTemplateString(ListNode* templateString); @@ -783,10 +784,10 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitDo(BinaryNode* doNode); MOZ_MUST_USE bool emitWhile(BinaryNode* whileNode); - MOZ_MUST_USE bool emitFor(ForNode* forNode, EmitterScope* headLexicalEmitterScope = nullptr); - MOZ_MUST_USE bool emitCStyleFor(ForNode* forNode, EmitterScope* headLexicalEmitterScope); - MOZ_MUST_USE bool emitForIn(ForNode* forNode, EmitterScope* headLexicalEmitterScope); - MOZ_MUST_USE bool emitForOf(ForNode* forNode, EmitterScope* headLexicalEmitterScope); + MOZ_MUST_USE bool emitFor(ForNode* forNode, const EmitterScope* headLexicalEmitterScope = nullptr); + MOZ_MUST_USE bool emitCStyleFor(ForNode* forNode, const EmitterScope* headLexicalEmitterScope); + MOZ_MUST_USE bool emitForIn(ForNode* forNode, const EmitterScope* headLexicalEmitterScope); + MOZ_MUST_USE bool emitForOf(ForNode* forNode, const EmitterScope* headLexicalEmitterScope); MOZ_MUST_USE bool emitInitializeForInOrOfTarget(TernaryNode* forHead); @@ -797,7 +798,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitFunctionFormalParameters(ListNode* paramsBody); MOZ_MUST_USE bool emitInitializeFunctionSpecialNames(); MOZ_MUST_USE bool emitFunctionBody(ParseNode* pn); - MOZ_MUST_USE bool emitLexicalInitialization(ParseNode* pn); + MOZ_MUST_USE bool emitLexicalInitialization(NameNode* pn); + MOZ_MUST_USE bool emitLexicalInitialization(JSAtom* name); // Emit bytecode for the spread operator. // diff --git a/js/src/frontend/DefaultEmitter.cpp b/js/src/frontend/DefaultEmitter.cpp new file mode 100644 index 0000000000..0abda3e450 --- /dev/null +++ b/js/src/frontend/DefaultEmitter.cpp @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "frontend/DefaultEmitter.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter +#include "vm/Opcodes.h" // JSOP_* + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; +using mozilla::Nothing; + +DefaultEmitter::DefaultEmitter(BytecodeEmitter* bce) : bce_(bce) {} + +bool DefaultEmitter::prepareForDefault() { + MOZ_ASSERT(state_ == State::Start); + + // [stack] VALUE + + ifUndefined_.emplace(bce_); + + if (!bce_->emit1(JSOP_DUP)) { + // [stack] VALUE VALUE + return false; + } + if (!bce_->emit1(JSOP_UNDEFINED)) { + // [stack] VALUE VALUE UNDEFINED + return false; + } + if (!bce_->emit1(JSOP_STRICTEQ)) { + // [stack] VALUE EQ? + return false; + } + + if (!ifUndefined_->emitThen()) { + // [stack] VALUE + return false; + } + + if (!bce_->emit1(JSOP_POP)) { + // [stack] + return false; + } + +#ifdef DEBUG + state_ = State::Default; +#endif + return true; +} + +bool DefaultEmitter::emitEnd() { + MOZ_ASSERT(state_ == State::Default); + + // [stack] DEFAULTVALUE + + if (!ifUndefined_->emitEnd()) { + // [stack] VALUE/DEFAULTVALUE + return false; + } + ifUndefined_.reset(); + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} diff --git a/js/src/frontend/DefaultEmitter.h b/js/src/frontend/DefaultEmitter.h new file mode 100644 index 0000000000..38acf0c17f --- /dev/null +++ b/js/src/frontend/DefaultEmitter.h @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef frontend_DefaultEmitter_h +#define frontend_DefaultEmitter_h + +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS, MOZ_MUST_USE +#include "mozilla/Maybe.h" // Maybe + +#include "frontend/IfEmitter.h" // IfEmitter + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting default parameter or default value. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `x = 10` in `function (x = 10) {}` +// // the value of arguments[0] is on the stack +// DefaultEmitter de(this); +// de.prepareForDefault(); +// emit(10); +// de.emitEnd(); +// +class MOZ_STACK_CLASS DefaultEmitter { + BytecodeEmitter* bce_; + + mozilla::Maybe ifUndefined_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ prepareForDefault +---------+ emitEnd +-----+ + // | Start |------------------>| Default |-------->| End | + // +-------+ +---------+ +-----+ + enum class State { + // The initial state. + Start, + + // After calling prepareForDefault. + Default, + + // After calling emitEnd. + End + }; + State state_ = State::Start; +#endif + + public: + explicit DefaultEmitter(BytecodeEmitter* bce); + + MOZ_MUST_USE bool prepareForDefault(); + MOZ_MUST_USE bool emitEnd(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_LabelEmitter_h */ diff --git a/js/src/frontend/EmitterScope.cpp b/js/src/frontend/EmitterScope.cpp index d6ebfe265f..76b06ba4c6 100644 --- a/js/src/frontend/EmitterScope.cpp +++ b/js/src/frontend/EmitterScope.cpp @@ -370,7 +370,7 @@ EmitterScope::appendScopeNote(BytecodeEmitter* bce) } bool -EmitterScope::deadZoneFrameSlotRange(BytecodeEmitter* bce, uint32_t slotStart, uint32_t slotEnd) +EmitterScope::deadZoneFrameSlotRange(BytecodeEmitter* bce, uint32_t slotStart, uint32_t slotEnd) const { // Lexical bindings throw ReferenceErrors if they are used before // initialization. See ES6 8.1.1.1.6. @@ -993,7 +993,7 @@ EmitterScope::enterWith(BytecodeEmitter* bce) } bool -EmitterScope::deadZoneFrameSlots(BytecodeEmitter* bce) +EmitterScope::deadZoneFrameSlots(BytecodeEmitter* bce) const { return deadZoneFrameSlotRange(bce, frameSlotStart(), frameSlotEnd()); } diff --git a/js/src/frontend/EmitterScope.h b/js/src/frontend/EmitterScope.h index cfee86a214..a2f54df0f9 100644 --- a/js/src/frontend/EmitterScope.h +++ b/js/src/frontend/EmitterScope.h @@ -86,7 +86,7 @@ class EmitterScope : public Nestable MOZ_MUST_USE bool appendScopeNote(BytecodeEmitter* bce); MOZ_MUST_USE bool deadZoneFrameSlotRange(BytecodeEmitter* bce, uint32_t slotStart, - uint32_t slotEnd); + uint32_t slotEnd) const; public: explicit EmitterScope(BytecodeEmitter* bce); @@ -105,7 +105,7 @@ class EmitterScope : public Nestable MOZ_MUST_USE bool enterEval(BytecodeEmitter* bce, EvalSharedContext* evalsc); MOZ_MUST_USE bool enterModule(BytecodeEmitter* module, ModuleSharedContext* modulesc); MOZ_MUST_USE bool enterWith(BytecodeEmitter* bce); - MOZ_MUST_USE bool deadZoneFrameSlots(BytecodeEmitter* bce); + MOZ_MUST_USE bool deadZoneFrameSlots(BytecodeEmitter* bce) const; MOZ_MUST_USE bool leave(BytecodeEmitter* bce, bool nonLocal = false); diff --git a/js/src/frontend/LexicalScopeEmitter.cpp b/js/src/frontend/LexicalScopeEmitter.cpp new file mode 100644 index 0000000000..053f1ea73d --- /dev/null +++ b/js/src/frontend/LexicalScopeEmitter.cpp @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "frontend/LexicalScopeEmitter.h" + +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter + +using namespace js; +using namespace js::frontend; + +LexicalScopeEmitter::LexicalScopeEmitter(BytecodeEmitter* bce) : bce_(bce) {} + +bool LexicalScopeEmitter::emitScope(ScopeKind kind, JS::Handle bindings) + +{ + MOZ_ASSERT(state_ == State::Start); + MOZ_ASSERT(bindings); + + tdzCache_.emplace(bce_); + emitterScope_.emplace(bce_); + if (!emitterScope_->enterLexical(bce_, kind, bindings)) + return false; + +#ifdef DEBUG + state_ = State::Scope; +#endif + return true; +} + +bool LexicalScopeEmitter::emitEmptyScope() +{ + MOZ_ASSERT(state_ == State::Start); + + tdzCache_.emplace(bce_); + +#ifdef DEBUG + state_ = State::Scope; +#endif + return true; +} + +bool LexicalScopeEmitter::emitEnd() +{ + MOZ_ASSERT(state_ == State::Scope); + + if (emitterScope_) { + if (!emitterScope_->leave(bce_)) + return false; + emitterScope_.reset(); + } + tdzCache_.reset(); + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} diff --git a/js/src/frontend/LexicalScopeEmitter.h b/js/src/frontend/LexicalScopeEmitter.h new file mode 100644 index 0000000000..2a2a76268b --- /dev/null +++ b/js/src/frontend/LexicalScopeEmitter.h @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef frontend_LexicalScopeEmitter_h +#define frontend_LexicalScopeEmitter_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS, MOZ_MUST_USE +#include "mozilla/Maybe.h" // Maybe + +#include "frontend/EmitterScope.h" // EmitterScope +#include "frontend/TDZCheckCache.h" // TDZCheckCache +#include "gc/Rooting.h" // JS::Handle +#include "vm/Scope.h" // ScopeKind, LexicalScope + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting bytecode for lexical scope. +// +// In addition to emitting code for entering and leaving a scope, this RAII +// guard affects the code emitted for `break` and other non-structured +// control flow. See NonLocalExitControl::prepareForNonLocalJump(). +// +// Usage: (check for the return value is omitted for simplicity) +// +// `{ ... }` -- lexical scope with no bindings +// LexicalScopeEmitter lse(this); +// lse.emitEmptyScope(); +// emit(scopeBody); +// lse.emitEnd(); +// +// `{ let a; body }` +// LexicalScopeEmitter lse(this); +// lse.emitScope(ScopeKind::Lexical, scopeBinding); +// emit(let_and_body); +// lse.emitEnd(); +// +// `catch (e) { body }` +// LexicalScopeEmitter lse(this); +// lse.emitScope(ScopeKind::SimpleCatch, scopeBinding); +// emit(body); +// lse.emitEnd(); +// +// `catch ([a, b]) { body }` +// LexicalScopeEmitter lse(this); +// lse.emitScope(ScopeKind::Catch, scopeBinding); +// emit(body); +// lse.emitEnd(); +class MOZ_STACK_CLASS LexicalScopeEmitter +{ + BytecodeEmitter* bce_; + + mozilla::Maybe tdzCache_; + mozilla::Maybe emitterScope_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ emitScope +-------+ emitEnd +-----+ + // | Start |----------->| Scope |--------->| End | + // +-------+ +-------+ +-----+ + enum class State { + // The initial state. + Start, + + // After calling emitScope/emitEmptyScope. + Scope, + + // After calling emitEnd. + End, + }; + State state_ = State::Start; +#endif + + public: + explicit LexicalScopeEmitter(BytecodeEmitter* bce); + + // Returns the scope object for non-empty scope. + const EmitterScope& emitterScope() const { + return *emitterScope_; + } + + MOZ_MUST_USE bool emitScope(ScopeKind kind, + JS::Handle bindings); + MOZ_MUST_USE bool emitEmptyScope(); + + MOZ_MUST_USE bool emitEnd(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_LexicalScopeEmitter_h */ diff --git a/js/src/frontend/ObjectEmitter.cpp b/js/src/frontend/ObjectEmitter.cpp new file mode 100644 index 0000000000..b89596a774 --- /dev/null +++ b/js/src/frontend/ObjectEmitter.cpp @@ -0,0 +1,762 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "frontend/ObjectEmitter.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include "jsatominlines.h" // AtomToId +#include "jsgcinlines.h" // GetGCObjectKind +#include "jsobjinlines.h" // NewBuiltinClassInstance + + +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter +#include "frontend/SharedContext.h" // SharedContext +#include "frontend/SourceNotes.h" // SRC_* +#include "gc/Heap.h" // AllocKind +#include "js/Id.h" // jsid +#include "js/Value.h" // UndefinedHandleValue +#include "vm/NativeObject.h" // NativeDefineDataProperty +#include "vm/ObjectGroup.h" // TenuredObject +#include "vm/Runtime.h" // JSAtomState (cx->names()) + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; + +PropertyEmitter::PropertyEmitter(BytecodeEmitter* bce) + : bce_(bce), obj_(bce->cx) {} + +bool PropertyEmitter::prepareForProtoValue(const Maybe& keyPos) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] CTOR? OBJ CTOR? + + if (keyPos) { + if (!bce_->updateSourceCoordNotes(*keyPos)) + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::ProtoValue; +#endif + return true; +} + +bool PropertyEmitter::emitMutateProto() +{ + MOZ_ASSERT(propertyState_ == PropertyState::ProtoValue); + + // [stack] OBJ PROTO + + if (!bce_->emit1(JSOP_MUTATEPROTO)) { + // [stack] OBJ + return false; + } + + obj_ = nullptr; +#ifdef DEBUG + propertyState_ = PropertyState::Init; +#endif + return true; +} + +bool PropertyEmitter::prepareForSpreadOperand(const Maybe& spreadPos) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] OBJ + + if (spreadPos) { + if (!bce_->updateSourceCoordNotes(*spreadPos)) + return false; + } + if (!bce_->emit1(JSOP_DUP)) { + // [stack] OBJ OBJ + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::SpreadOperand; +#endif + return true; +} + +bool PropertyEmitter::emitSpread() +{ + MOZ_ASSERT(propertyState_ == PropertyState::SpreadOperand); + + // [stack] OBJ OBJ VAL + + if (!bce_->emitCopyDataProperties(BytecodeEmitter::CopyOption::Unfiltered)) { + // [stack] OBJ + return false; + } + + obj_ = nullptr; +#ifdef DEBUG + propertyState_ = PropertyState::Init; +#endif + return true; +} + +MOZ_ALWAYS_INLINE bool PropertyEmitter::prepareForProp(const Maybe& keyPos, + bool isStatic, bool isIndexOrComputed) +{ + isStatic_ = isStatic; + isIndexOrComputed_ = isIndexOrComputed; + + // [stack] CTOR? OBJ + + if (keyPos) { + if (!bce_->updateSourceCoordNotes(*keyPos)) + return false; + } + + if (isStatic_) { + if (!bce_->emit1(JSOP_DUP2)) { + // [stack] CTOR HOMEOBJ CTOR HOMEOBJ + return false; + } + if (!bce_->emit1(JSOP_POP)) { + // [stack] CTOR HOMEOBJ CTOR + return false; + } + } + + return true; +} + +bool PropertyEmitter::prepareForPropValue(const Maybe& keyPos, + Kind kind /* = Kind::Prototype */) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] CTOR? OBJ + + if (!prepareForProp(keyPos, + /* isStatic_ = */ kind == Kind::Static, + /* isIndexOrComputed = */ false)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::PropValue; +#endif + return true; +} + +bool PropertyEmitter::prepareForIndexPropKey(const Maybe& keyPos, + Kind kind /* = Kind::Prototype */) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] CTOR? OBJ + + obj_ = nullptr; + + if (!prepareForProp(keyPos, + /* isStatic_ = */ kind == Kind::Static, + /* isIndexOrComputed = */ true)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::IndexKey; +#endif + return true; +} + +bool PropertyEmitter::prepareForIndexPropValue() +{ + MOZ_ASSERT(propertyState_ == PropertyState::IndexKey); + + // [stack] CTOR? OBJ CTOR? KEY + +#ifdef DEBUG + propertyState_ = PropertyState::IndexValue; +#endif + return true; +} + +bool PropertyEmitter::prepareForComputedPropKey(const Maybe& keyPos, + Kind kind /* = Kind::Prototype */) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] CTOR? OBJ + + obj_ = nullptr; + + if (!prepareForProp(keyPos, + /* isStatic_ = */ kind == Kind::Static, + /* isIndexOrComputed = */ true)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::ComputedKey; +#endif + return true; +} + +bool PropertyEmitter::prepareForComputedPropValue() +{ + MOZ_ASSERT(propertyState_ == PropertyState::ComputedKey); + + // [stack] CTOR? OBJ CTOR? KEY + + if (!bce_->emit1(JSOP_TOID)) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::ComputedValue; +#endif + return true; +} + +bool PropertyEmitter::emitInitHomeObject(FunctionAsyncKind kind /* = FunctionAsyncKind::SyncFunction */) +{ + MOZ_ASSERT(propertyState_ == PropertyState::PropValue || + propertyState_ == PropertyState::IndexValue || + propertyState_ == PropertyState::ComputedValue); + + // [stack] CTOR? HOMEOBJ CTOR? KEY? FUN + + bool isAsync = kind == FunctionAsyncKind::AsyncFunction; + if (isAsync) { + // [stack] CTOR? HOMEOBJ CTOR? KEY? UNWRAPPED WRAPPED + if (!bce_->emit1(JSOP_SWAP)) { + // [stack] CTOR? HOMEOBJ CTOR? KEY? WRAPPED UNWRAPPED + return false; + } + } + + if (!bce_->emit2(JSOP_INITHOMEOBJECT, isIndexOrComputed_ + isAsync)) { + // [stack] CTOR? HOMEOBJ CTOR? KEY? WRAPPED? FUN + return false; + } + if (isAsync) { + if (!bce_->emit1(JSOP_POP)) { + // [stack] CTOR? HOMEOBJ CTOR? KEY? WRAPPED + return false; + } + } + +#ifdef DEBUG + if (propertyState_ == PropertyState::PropValue) { + propertyState_ = PropertyState::InitHomeObj; + } else if (propertyState_ == PropertyState::IndexValue) { + propertyState_ = PropertyState::InitHomeObjForIndex; + } else { + propertyState_ = PropertyState::InitHomeObjForComputed; + } +#endif + return true; +} + +bool PropertyEmitter::emitInitProp(JS::Handle key, + bool isPropertyAnonFunctionOrClass /* = false */, + JS::Handle anonFunction /* = nullptr */) +{ + return emitInit(isClass_ ? JSOP_INITHIDDENPROP : JSOP_INITPROP, key, + isPropertyAnonFunctionOrClass, anonFunction); +} + +bool PropertyEmitter::emitInitGetter(JS::Handle key) +{ + obj_ = nullptr; + return emitInit(isClass_ ? JSOP_INITHIDDENPROP_GETTER : JSOP_INITPROP_GETTER, + key, false, nullptr); +} + +bool PropertyEmitter::emitInitSetter(JS::Handle key) +{ + obj_ = nullptr; + return emitInit(isClass_ ? JSOP_INITHIDDENPROP_SETTER : JSOP_INITPROP_SETTER, + key, false, nullptr); +} + +bool PropertyEmitter::emitInitIndexProp(bool isPropertyAnonFunctionOrClass /* = false */) +{ + return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM : JSOP_INITELEM, + FunctionPrefixKind::None, + isPropertyAnonFunctionOrClass); +} + +bool PropertyEmitter::emitInitIndexGetter() +{ + obj_ = nullptr; + return emitInitIndexOrComputed( + isClass_ ? JSOP_INITHIDDENELEM_GETTER : JSOP_INITELEM_GETTER, + FunctionPrefixKind::Get, false); +} + +bool PropertyEmitter::emitInitIndexSetter() +{ + obj_ = nullptr; + return emitInitIndexOrComputed( + isClass_ ? JSOP_INITHIDDENELEM_SETTER : JSOP_INITELEM_SETTER, + FunctionPrefixKind::Set, false); +} + +bool PropertyEmitter::emitInitComputedProp(bool isPropertyAnonFunctionOrClass /* = false */) +{ + return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM : JSOP_INITELEM, + FunctionPrefixKind::None, + isPropertyAnonFunctionOrClass); +} + +bool PropertyEmitter::emitInitComputedGetter() +{ + obj_ = nullptr; + return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM_GETTER : JSOP_INITELEM_GETTER, + FunctionPrefixKind::Get, true); +} + +bool PropertyEmitter::emitInitComputedSetter() +{ + obj_ = nullptr; + return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM_SETTER : JSOP_INITELEM_SETTER, + FunctionPrefixKind::Set, true); +} + +bool PropertyEmitter::emitInit(JSOp op, JS::Handle key, + bool isPropertyAnonFunctionOrClass, + JS::Handle anonFunction) +{ + MOZ_ASSERT(propertyState_ == PropertyState::PropValue || + propertyState_ == PropertyState::InitHomeObj); + + MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITHIDDENPROP || + op == JSOP_INITPROP_GETTER || op == JSOP_INITHIDDENPROP_GETTER || + op == JSOP_INITPROP_SETTER || op == JSOP_INITHIDDENPROP_SETTER); + + // [stack] CTOR? OBJ CTOR? VAL + + uint32_t index; + if (!bce_->makeAtomIndex(key, &index)) + return false; + + if (obj_) { + MOZ_ASSERT(!IsHiddenInitOp(op)); + MOZ_ASSERT(!obj_->inDictionaryMode()); + JS::RootedId id(bce_->cx, AtomToId(key)); + if (!NativeDefineProperty(bce_->cx, obj_, id, UndefinedHandleValue, nullptr, nullptr, + JSPROP_ENUMERATE)) + return false; + if (obj_->inDictionaryMode()) + obj_ = nullptr; + } + + if (isPropertyAnonFunctionOrClass) { + MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITHIDDENPROP); + + if (anonFunction) { + if (!bce_->setFunName(anonFunction, key)) + return false; + } else { + // NOTE: This is setting the constructor's name of the class which is + // the property value. Not of the enclosing class. + if (!bce_->emitSetClassConstructorName(key)) { + // [stack] CTOR? OBJ CTOR? FUN + return false; + } + } + } + + if (!bce_->emitIndex32(op, index)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + + if (!emitPopClassConstructor()) + return false; + +#ifdef DEBUG + propertyState_ = PropertyState::Init; +#endif + return true; +} + +bool PropertyEmitter::emitInitIndexOrComputed(JSOp op, FunctionPrefixKind prefixKind, + bool isPropertyAnonFunctionOrClass) +{ + MOZ_ASSERT(propertyState_ == PropertyState::IndexValue || + propertyState_ == PropertyState::InitHomeObjForIndex || + propertyState_ == PropertyState::ComputedValue || + propertyState_ == PropertyState::InitHomeObjForComputed); + + MOZ_ASSERT(op == JSOP_INITELEM || op == JSOP_INITHIDDENELEM || + op == JSOP_INITELEM_GETTER || op == JSOP_INITHIDDENELEM_GETTER || + op == JSOP_INITELEM_SETTER || op == JSOP_INITHIDDENELEM_SETTER); + + // [stack] CTOR? OBJ CTOR? KEY VAL + + if (isPropertyAnonFunctionOrClass) { + if (!bce_->emitDupAt(1)) { + // [stack] CTOR? OBJ CTOR? KEY FUN FUN + return false; + } + if (!bce_->emit2(JSOP_SETFUNNAME, uint8_t(prefixKind))) { + // [stack] CTOR? OBJ CTOR? KEY FUN + return false; + } + } + + if (!bce_->emit1(op)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + + if (!emitPopClassConstructor()) + return false; + +#ifdef DEBUG + propertyState_ = PropertyState::Init; +#endif + return true; +} + +bool PropertyEmitter::emitPopClassConstructor() +{ + if (isStatic_) { + // [stack] CTOR HOMEOBJ CTOR + + if (!bce_->emit1(JSOP_POP)) { + // [stack] CTOR HOMEOBJ + return false; + } + } + + return true; +} + +ObjectEmitter::ObjectEmitter(BytecodeEmitter* bce) : PropertyEmitter(bce) {} + +bool ObjectEmitter::emitObject(size_t propertyCount) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(objectState_ == ObjectState::Start); + + // [stack] + + // Emit code for {p:a, '%q':b, 2:c} that is equivalent to constructing + // a new object and defining (in source order) each property on the object + // (or mutating the object's [[Prototype]], in the case of __proto__). + top_ = bce_->offset(); + if (!bce_->emitNewInit(JSProto_Object)) { + // [stack] OBJ + return false; + } + + // Try to construct the shape of the object as we go, so we can emit a + // JSOP_NEWOBJECT with the final shape instead. + // In the case of computed property names and indices, we cannot fix the + // shape at bytecode compile time. When the shape cannot be determined, + // |obj| is nulled out. + + // No need to do any guessing for the object kind, since we know the upper + // bound of how many properties we plan to have. + gc::AllocKind kind = gc::GetGCObjectKind(propertyCount); + obj_ = NewBuiltinClassInstance(bce_->cx, kind, TenuredObject); + if (!obj_) + return false; + +#ifdef DEBUG + objectState_ = ObjectState::Object; +#endif + return true; +} + +bool ObjectEmitter::emitEnd() +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + MOZ_ASSERT(objectState_ == ObjectState::Object); + + // [stack] OBJ + + if (obj_) { + // The object survived and has a predictable shape: update the original + // bytecode. + if (!bce_->replaceNewInitWithNewObject(obj_, top_)) { + // [stack] OBJ + return false; + } + } + +#ifdef DEBUG + objectState_ = ObjectState::End; +#endif + return true; +} + +AutoSaveLocalStrictMode::AutoSaveLocalStrictMode(SharedContext* sc) : sc_(sc) +{ + savedStrictness_ = sc_->setLocalStrictMode(true); +} + +AutoSaveLocalStrictMode::~AutoSaveLocalStrictMode() +{ + if (sc_) { + restore(); + } +} + +void AutoSaveLocalStrictMode::restore() +{ + MOZ_ALWAYS_TRUE(sc_->setLocalStrictMode(savedStrictness_)); + sc_ = nullptr; +} + +ClassEmitter::ClassEmitter(BytecodeEmitter* bce) + : PropertyEmitter(bce), strictMode_(bce->sc), name_(bce->cx) +{ + isClass_ = true; +} + +bool ClassEmitter::emitScopeForNamedClass(JS::Handle scopeBindings) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Start); + + tdzCacheForInnerName_.emplace(bce_); + innerNameScope_.emplace(bce_); + if (!innerNameScope_->enterLexical(bce_, ScopeKind::Lexical, scopeBindings)) + return false; + +#ifdef DEBUG + classState_ = ClassState::Scope; +#endif + return true; +} + +bool ClassEmitter::emitClass(JS::Handle name) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Start || + classState_ == ClassState::Scope); + + // [stack] + + setName(name); + isDerived_ = false; + + if (!bce_->emitNewInit(JSProto_Object)) { + // [stack] HOMEOBJ + return false; + } + +#ifdef DEBUG + classState_ = ClassState::Class; +#endif + return true; +} + +bool ClassEmitter::emitDerivedClass(JS::Handle name) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Start || + classState_ == ClassState::Scope); + + // [stack] HERITAGE + + setName(name); + isDerived_ = true; + + if (!bce_->emit1(JSOP_CLASSHERITAGE)) { + // [stack] funcProto objProto + return false; + } + if (!bce_->emit1(JSOP_OBJWITHPROTO)) { + // [stack] funcProto HOMEOBJ + return false; + } + + // JSOP_CLASSHERITAGE leaves both protos on the stack. After + // creating the prototype, swap it to the bottom to make the + // constructor. + if (!bce_->emit1(JSOP_SWAP)) { + // [stack] HOMEOBJ funcProto + return false; + } + +#ifdef DEBUG + classState_ = ClassState::Class; +#endif + return true; +} + +void ClassEmitter::setName(JS::Handle name) +{ + name_ = name; + if (!name_) + name_ = bce_->cx->names().empty; +} + +bool ClassEmitter::emitInitConstructor(bool needsHomeObject) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Class); + + // [stack] HOMEOBJ CTOR + + if (needsHomeObject) { + if (!bce_->emit2(JSOP_INITHOMEOBJECT, 0)) { + // [stack] HOMEOBJ CTOR + return false; + } + } + + if (!initProtoAndCtor()) { + // [stack] CTOR HOMEOBJ + return false; + } + +#ifdef DEBUG + classState_ = ClassState::InitConstructor; +#endif + return true; +} + +bool ClassEmitter::emitInitDefaultConstructor(const Maybe& classStart, + const Maybe& classEnd) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Class); + + if (classStart && classEnd) { + // In the case of default class constructors, emit the start and end + // offsets in the source buffer as source notes so that when we + // actually make the constructor during execution, we can give it the + // correct toString output. + if (!bce_->newSrcNote3(SRC_CLASS_SPAN, ptrdiff_t(*classStart), + ptrdiff_t(*classEnd))) { + return false; + } + } + + if (isDerived_) { + // [stack] HERITAGE PROTO + if (!bce_->emitAtomOp(name_, JSOP_DERIVEDCONSTRUCTOR)) { + // [stack] HOMEOBJ CTOR + return false; + } + } else { + // [stack] HOMEOBJ + if (!bce_->emitAtomOp(name_, JSOP_CLASSCONSTRUCTOR)) { + // [stack] HOMEOBJ CTOR + return false; + } + } + + if (!initProtoAndCtor()) { + // [stack] CTOR HOMEOBJ + return false; + } + +#ifdef DEBUG + classState_ = ClassState::InitConstructor; +#endif + return true; +} + +bool ClassEmitter::initProtoAndCtor() +{ + // [stack] HOMEOBJ CTOR + + if (!bce_->emit1(JSOP_SWAP)) { + // [stack] CTOR HOMEOBJ + return false; + } + if (!bce_->emit1(JSOP_DUP2)) { + // [stack] CTOR HOMEOBJ CTOR HOMEOBJ + return false; + } + if (!bce_->emitAtomOp(bce_->cx->names().prototype, JSOP_INITLOCKEDPROP)) { + // [stack] CTOR HOMEOBJ CTOR + return false; + } + if (!bce_->emitAtomOp(bce_->cx->names().constructor, JSOP_INITHIDDENPROP)) { + // [stack] CTOR HOMEOBJ + return false; + } + + return true; +} + +bool ClassEmitter::emitEnd(Kind kind) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + MOZ_ASSERT(classState_ == ClassState::InitConstructor); + + // [stack] CTOR HOMEOBJ + + if (!bce_->emit1(JSOP_POP)) { + // [stack] CTOR + return false; + } + + if (name_ != bce_->cx->names().empty) { + MOZ_ASSERT(tdzCacheForInnerName_.isSome()); + MOZ_ASSERT(innerNameScope_.isSome()); + + if (!bce_->emitLexicalInitialization(name_)) { + // [stack] CTOR + return false; + } + + // Pop the inner scope. + if (!innerNameScope_->leave(bce_)) + return false; + innerNameScope_.reset(); + + if (kind == Kind::Declaration) { + if (!bce_->emitLexicalInitialization(name_)) { + // [stack] CTOR + return false; + } + // Only class statements make outer bindings, and they do not leave + // themselves on the stack. + if (!bce_->emit1(JSOP_POP)) { + // [stack] + return false; + } + } + + tdzCacheForInnerName_.reset(); + } else { + // [stack] CTOR + + MOZ_ASSERT(tdzCacheForInnerName_.isNothing()); + } + + // [stack] # class declaration + // [stack] + // [stack] # class expression + // [stack] CTOR + + strictMode_.restore(); + +#ifdef DEBUG + classState_ = ClassState::End; +#endif + return true; +} diff --git a/js/src/frontend/ObjectEmitter.h b/js/src/frontend/ObjectEmitter.h new file mode 100644 index 0000000000..85a6f81faa --- /dev/null +++ b/js/src/frontend/ObjectEmitter.h @@ -0,0 +1,727 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef frontend_ObjectEmitter_h +#define frontend_ObjectEmitter_h + +#include "mozilla/Attributes.h" // MOZ_MUST_USE, MOZ_STACK_CLASS, MOZ_ALWAYS_INLINE, MOZ_RAII +#include "mozilla/Maybe.h" // Maybe + +#include // size_t, ptrdiff_t +#include // uint32_t + +#include "jsopcode.h" // JSOp +#include "jsfun.h" // JSFunction +#include "jsscript.h" // FunctionAsyncKind + +#include "frontend/EmitterScope.h" // EmitterScope +#include "frontend/TDZCheckCache.h" // TDZCheckCache +#include "js/RootingAPI.h" // JS::Handle, JS::Rooted +#include "vm/String.h" // JSAtom +#include "vm/NativeObject.h" // PlainObject +#include "vm/Scope.h" // LexicalScope + +namespace js { + +namespace frontend { + +struct BytecodeEmitter; +class SharedContext; + +// Class for emitting bytecode for object and class properties. +// See ObjectEmitter and ClassEmitter for usage. +class MOZ_STACK_CLASS PropertyEmitter +{ + public: + enum class Kind { + // Prototype property. + Prototype, + + // Class static property. + Static + }; + + protected: + BytecodeEmitter* bce_; + + // True if the object is class. + // Set by ClassEmitter. + bool isClass_ = false; + + // True if the property is class static method. + bool isStatic_ = false; + + // True if the property has computed or index key. + bool isIndexOrComputed_ = false; + + // An object which keeps the shape of this object literal. + // This fields is reset to nullptr whenever the object literal turns out to + // have at least one numeric, computed, spread or __proto__ property, or + // the object becomes dictionary mode. + // This field is used only in ObjectEmitter. + JS::Rooted obj_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ + // | Start |-+ + // +-------+ | + // | + // +---------+ + // | + // | +------------------------------------------------------------+ + // | | | + // | | [normal property/method/accessor] | + // | v prepareForPropValue +-----------+ +------+ | + // +->+----------------------->| PropValue |-+ +->| Init |-+ + // | +-----------+ | | +------+ + // | | | + // | +----------------------------------+ +-----------+ + // | | | + // | +-+---------------------------------------+ | + // | | | | + // | | [method with super] | | + // | | emitInitHomeObject +-------------+ v | + // | +--------------------->| InitHomeObj |->+ | + // | +-------------+ | | + // | | | + // | +-------------------------------------- + | + // | | | + // | | emitInitProp | + // | | emitInitGetter | + // | | emitInitSetter | + // | +------------------------------------------------------>+ + // | ^ + // | [index property/method/accessor] | + // | prepareForIndexPropKey +----------+ | + // +-------------------------->| IndexKey |-+ | + // | +----------+ | | + // | | | + // | +-------------------------------------+ | + // | | | + // | | prepareForIndexPropValue +------------+ | + // | +------------------------->| IndexValue |-+ | + // | +------------+ | | + // | | | + // | +---------------------------------------+ | + // | | | + // | +-+--------------------------------------------------+ | + // | | | | + // | | [method with super] | | + // | | emitInitHomeObject +---------------------+ v | + // | +--------------------->| InitHomeObjForIndex |---->+ | + // | +---------------------+ | | + // | | | + // | +--------------------------------------------------+ | + // | | | + // | | emitInitIndexProp | + // | | emitInitIndexGetter | + // | | emitInitIndexSetter | + // | +---------------------------------------------------->+ + // | | + // | [computed property/method/accessor] | + // | prepareForComputedPropKey +-------------+ | + // +----------------------------->| ComputedKey |-+ | + // | +-------------+ | | + // | | | + // | +-------------------------------------------+ | + // | | | + // | | prepareForComputedPropValue +---------------+ | + // | +---------------------------->| ComputedValue |-+ | + // | +---------------+ | | + // | | | + // | +---------------------------------------------+ | + // | | | + // | +-+--------------------------------------------------+ | + // | | | | + // | | [method with super] | | + // | | emitInitHomeObject +------------------------+ v | + // | +--------------------->| InitHomeObjForComputed |->+ | + // | +------------------------+ | | + // | | | + // | +--------------------------------------------------+ | + // | | | + // | | emitInitComputedProp | + // | | emitInitComputedGetter | + // | | emitInitComputedSetter | + // | +---------------------------------------------------->+ + // | ^ + // | | + // | [__proto__] | + // | prepareForProtoValue +------------+ emitMutateProto | + // +------------------------>| ProtoValue |-------------------->+ + // | +------------+ ^ + // | | + // | [...prop] | + // | prepareForSpreadOperand +---------------+ emitSpread | + // +-------------------------->| SpreadOperand |----------------+ + // +---------------+ + enum class PropertyState { + // The initial state. + Start, + + // After calling prepareForPropValue. + PropValue, + + // After calling emitInitHomeObject, from PropValue. + InitHomeObj, + + // After calling prepareForIndexPropKey. + IndexKey, + + // prepareForIndexPropValue. + IndexValue, + + // After calling emitInitHomeObject, from IndexValue. + InitHomeObjForIndex, + + // After calling prepareForComputedPropKey. + ComputedKey, + + // prepareForComputedPropValue. + ComputedValue, + + // After calling emitInitHomeObject, from ComputedValue. + InitHomeObjForComputed, + + // After calling prepareForProtoValue. + ProtoValue, + + // After calling prepareForSpreadOperand. + SpreadOperand, + + // After calling one of emitInitProp, emitInitGetter, emitInitSetter, + // emitInitIndexOrComputedProp, emitInitIndexOrComputedGetter, + // emitInitIndexOrComputedSetter, emitMutateProto, or emitSpread. + Init, + }; + PropertyState propertyState_ = PropertyState::Start; +#endif + + public: + explicit PropertyEmitter(BytecodeEmitter* bce); + + // Parameters are the offset in the source code for each character below: + // + // { __proto__: protoValue } + // ^ + // | + // keyPos + MOZ_MUST_USE bool prepareForProtoValue( + const mozilla::Maybe& keyPos); + MOZ_MUST_USE bool emitMutateProto(); + + // { ...obj } + // ^ + // | + // spreadPos + MOZ_MUST_USE bool prepareForSpreadOperand( + const mozilla::Maybe& spreadPos); + MOZ_MUST_USE bool emitSpread(); + + // { key: value } + // ^ + // | + // keyPos + MOZ_MUST_USE bool prepareForPropValue(const mozilla::Maybe& keyPos, + Kind kind = Kind::Prototype); + + // { 1: value } + // ^ + // | + // keyPos + MOZ_MUST_USE bool prepareForIndexPropKey( + const mozilla::Maybe& keyPos, Kind kind = Kind::Prototype); + MOZ_MUST_USE bool prepareForIndexPropValue(); + + // { [ key ]: value } + // ^ + // | + // keyPos + MOZ_MUST_USE bool prepareForComputedPropKey( + const mozilla::Maybe& keyPos, Kind kind = Kind::Prototype); + MOZ_MUST_USE bool prepareForComputedPropValue(); + + MOZ_MUST_USE bool emitInitHomeObject( + FunctionAsyncKind kind = FunctionAsyncKind::SyncFunction); + + // @param key + // Property key + // @param isPropertyAnonFunctionOrClass + // True if the property value is an anonymous function or + // an anonymous class + // @param anonFunction + // The anonymous function object for property value + MOZ_MUST_USE bool emitInitProp( + JS::Handle key, bool isPropertyAnonFunctionOrClass = false, + JS::Handle anonFunction = nullptr); + MOZ_MUST_USE bool emitInitGetter(JS::Handle key); + MOZ_MUST_USE bool emitInitSetter(JS::Handle key); + + MOZ_MUST_USE bool emitInitIndexProp( + bool isPropertyAnonFunctionOrClass = false); + MOZ_MUST_USE bool emitInitIndexGetter(); + MOZ_MUST_USE bool emitInitIndexSetter(); + + MOZ_MUST_USE bool emitInitComputedProp( + bool isPropertyAnonFunctionOrClass = false); + MOZ_MUST_USE bool emitInitComputedGetter(); + MOZ_MUST_USE bool emitInitComputedSetter(); + + private: + MOZ_MUST_USE MOZ_ALWAYS_INLINE bool prepareForProp( + const mozilla::Maybe& keyPos, bool isStatic, bool isComputed); + + // @param op + // Opcode for initializing property + // @param prefixKind + // None, Get, or Set + // @param key + // Atom of the property if the property key is not computed + // @param isPropertyAnonFunctionOrClass + // True if the property is either an anonymous function or an + // anonymous class + // @param anonFunction + // Anonymous function object for the property + MOZ_MUST_USE bool emitInit(JSOp op, JS::Handle key, + bool isPropertyAnonFunctionOrClass, + JS::Handle anonFunction); + MOZ_MUST_USE bool emitInitIndexOrComputed(JSOp op, + FunctionPrefixKind prefixKind, + bool isPropertyAnonFunctionOrClass); + + MOZ_MUST_USE bool emitPopClassConstructor(); +}; + +// Class for emitting bytecode for object literal. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `{}` +// ObjectEmitter oe(this); +// oe.emitObject(0); +// oe.emitEnd(); +// +// `{ prop: 10 }` +// ObjectEmitter oe(this); +// oe.emitObject(1); +// +// oe.prepareForPropValue(Some(offset_of_prop)); +// emit(10); +// oe.emitInitProp(atom_of_prop); +// +// oe.emitEnd(); +// +// `{ prop: function() {} }`, when property value is anonymous function +// ObjectEmitter oe(this); +// oe.emitObject(1); +// +// oe.prepareForPropValue(Some(offset_of_prop)); +// emit(function); +// oe.emitInitProp(atom_of_prop, true, function_object); +// +// oe.emitEnd(); +// +// `{ get prop() { ... }, set prop(v) { ... } }` +// ObjectEmitter oe(this); +// oe.emitObject(2); +// +// oe.prepareForPropValue(Some(offset_of_prop)); +// emit(function_for_getter); +// oe.emitInitGetter(atom_of_prop); +// +// oe.prepareForPropValue(Some(offset_of_prop)); +// emit(function_for_setter); +// oe.emitInitSetter(atom_of_prop); +// +// oe.emitEnd(); +// +// `{ 1: 10, get 2() { ... }, set 3(v) { ... } }` +// ObjectEmitter oe(this); +// oe.emitObject(3); +// +// oe.prepareForIndexPropKey(Some(offset_of_prop)); +// emit(1); +// oe.prepareForIndexPropValue(); +// emit(10); +// oe.emitInitIndexedProp(atom_of_prop); +// +// oe.prepareForIndexPropKey(Some(offset_of_opening_bracket)); +// emit(2); +// oe.prepareForIndexPropValue(); +// emit(function_for_getter); +// oe.emitInitIndexGetter(); +// +// oe.prepareForIndexPropKey(Some(offset_of_opening_bracket)); +// emit(3); +// oe.prepareForIndexPropValue(); +// emit(function_for_setter); +// oe.emitInitIndexSetter(); +// +// oe.emitEnd(); +// +// `{ [prop1]: 10, get [prop2]() { ... }, set [prop3](v) { ... } }` +// ObjectEmitter oe(this); +// oe.emitObject(3); +// +// oe.prepareForComputedPropKey(Some(offset_of_opening_bracket)); +// emit(prop1); +// oe.prepareForComputedPropValue(); +// emit(10); +// oe.emitInitComputedProp(); +// +// oe.prepareForComputedPropKey(Some(offset_of_opening_bracket)); +// emit(prop2); +// oe.prepareForComputedPropValue(); +// emit(function_for_getter); +// oe.emitInitComputedGetter(); +// +// oe.prepareForComputedPropKey(Some(offset_of_opening_bracket)); +// emit(prop3); +// oe.prepareForComputedPropValue(); +// emit(function_for_setter); +// oe.emitInitComputedSetter(); +// +// oe.emitEnd(); +// +// `{ __proto__: obj }` +// ObjectEmitter oe(this); +// oe.emitObject(1); +// oe.prepareForProtoValue(Some(offset_of___proto__)); +// emit(obj); +// oe.emitMutateProto(); +// oe.emitEnd(); +// +// `{ ...obj }` +// ObjectEmitter oe(this); +// oe.emitObject(1); +// oe.prepareForSpreadOperand(Some(offset_of_triple_dots)); +// emit(obj); +// oe.emitSpread(); +// oe.emitEnd(); +// +class MOZ_STACK_CLASS ObjectEmitter : public PropertyEmitter +{ + private: + // The offset of JSOP_NEWINIT, which is replced by JSOP_NEWOBJECT later + // when the object is known to have a fixed shape. + ptrdiff_t top_ = 0; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ emitObject +--------+ + // | Start |----------->| Object |-+ + // +-------+ +--------+ | + // | + // +-----------------------------+ + // | + // | (do PropertyEmitter operation) emitEnd +-----+ + // +-------------------------------+--------->| End | + // +-----+ + enum class ObjectState { + // The initial state. + Start, + + // After calling emitObject. + Object, + + // After calling emitEnd. + End, + }; + ObjectState objectState_ = ObjectState::Start; +#endif + + public: + explicit ObjectEmitter(BytecodeEmitter* bce); + + MOZ_MUST_USE bool emitObject(size_t propertyCount); + MOZ_MUST_USE bool emitEnd(); +}; + +// Save and restore the strictness. +// Used by class declaration/expression to temporarily enable strict mode. +class MOZ_RAII AutoSaveLocalStrictMode +{ + SharedContext* sc_; + bool savedStrictness_; + + public: + explicit AutoSaveLocalStrictMode(SharedContext* sc); + ~AutoSaveLocalStrictMode(); + + // Force restore the strictness now. + void restore(); +}; + +// Class for emitting bytecode for JS class. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `class {}` +// ClassEmitter ce(this); +// ce.emitClass(); +// +// ce.emitInitDefaultConstructor(Some(offset_of_class), +// Some(offset_of_closing_bracket)); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class { constructor() { ... } }` +// ClassEmitter ce(this); +// ce.emitClass(); +// +// emit(function_for_constructor); +// ce.emitInitConstructor(/* needsHomeObject = */ false); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class X { constructor() { ... } }` +// ClassEmitter ce(this); +// ce.emitScopeForNamedClass(scopeBindingForName); +// ce.emitClass(atom_of_X); +// +// ce.emitInitDefaultConstructor(Some(offset_of_class), +// Some(offset_of_closing_bracket)); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class X { constructor() { ... } }` +// ClassEmitter ce(this); +// ce.emitScopeForNamedClass(scopeBindingForName); +// ce.emitClass(atom_of_X); +// +// emit(function_for_constructor); +// ce.emitInitConstructor(/* needsHomeObject = */ false); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class X extends Y { constructor() { ... } }` +// ClassEmitter ce(this); +// ce.emitScopeForNamedClass(scopeBindingForName); +// +// emit(Y); +// ce.emitDerivedClass(atom_of_X); +// +// emit(function_for_constructor); +// ce.emitInitConstructor(/* needsHomeObject = */ false); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class X extends Y { constructor() { ... super.f(); ... } }` +// ClassEmitter ce(this); +// ce.emitScopeForNamedClass(scopeBindingForName); +// +// emit(Y); +// ce.emitDerivedClass(atom_of_X); +// +// emit(function_for_constructor); +// // pass true if constructor contains super.prop access +// ce.emitInitConstructor(/* needsHomeObject = */ true); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `m() {}` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_m)); +// emit(function_for_m); +// ce.emitInitProp(atom_of_m); +// +// `m() { super.f(); }` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_m)); +// emit(function_for_m); +// ce.emitInitHomeObject(); +// ce.emitInitProp(atom_of_m); +// +// `async m() { super.f(); }` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_m)); +// emit(function_for_m); +// ce.emitInitHomeObject(FunctionAsyncKind::Async); +// ce.emitInitProp(atom_of_m); +// +// `get p() { super.f(); }` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_p)); +// emit(function_for_p); +// ce.emitInitHomeObject(); +// ce.emitInitGetter(atom_of_m); +// +// `static m() {}` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_m), +// PropertyEmitter::Kind::Static); +// emit(function_for_m); +// ce.emitInitProp(atom_of_m); +// +// `static get [p]() { super.f(); }` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForComputedPropValue(Some(offset_of_m), +// PropertyEmitter::Kind::Static); +// emit(p); +// ce.prepareForComputedPropValue(); +// emit(function_for_m); +// ce.emitInitHomeObject(); +// ce.emitInitComputedGetter(); +// +class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter +{ + public: + enum class Kind { + // Class expression. + Expression, + + // Class declaration. + Declaration, + }; + + private: + // Pseudocode for class declarations: + // + // class extends BaseExpression { + // constructor() { ... } + // ... + // } + // + // + // if defined { + // let heritage = BaseExpression; + // + // if (heritage !== null) { + // funProto = heritage; + // objProto = heritage.prototype; + // } else { + // funProto = %FunctionPrototype%; + // objProto = null; + // } + // } else { + // objProto = %ObjectPrototype%; + // } + // + // let homeObject = ObjectCreate(objProto); + // + // if defined { + // if defined { + // cons = DefineMethod(, proto=homeObject, + // funProto=funProto); + // } else { + // cons = DefineMethod(, proto=homeObject); + // } + // } else { + // if defined { + // cons = DefaultDerivedConstructor(proto=homeObject, + // funProto=funProto); + // } else { + // cons = DefaultConstructor(proto=homeObject); + // } + // } + // + // cons.prototype = homeObject; + // homeObject.constructor = cons; + // + // EmitPropertyList(...) + + bool isDerived_ = false; + + mozilla::Maybe tdzCacheForInnerName_; + mozilla::Maybe innerNameScope_; + AutoSaveLocalStrictMode strictMode_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ + // | Start |-+------------------------------------>+-+ + // +-------+ | ^ | + // | [named class] | | + // | emitScopeForNamedClass +-------+ | | + // +-------------------------->| Scope |-+ | + // +-------+ | + // | + // +-----------------------------------------------+ + // | + // | emitClass +-------+ + // +-+----------------->+->| Class |-+ + // | ^ +-------+ | + // | emitDerivedClass | | + // +------------------+ | + // | + // +-------------------------------+ + // | + // | + // | emitInitConstructor +-----------------+ + // +-+--------------------------->+->| InitConstructor |-+ + // | ^ +-----------------+ | + // | emitInitDefaultConstructor | | + // +----------------------------+ | + // | + // +---------------------------------------------------+ + // | + // | (do PropertyEmitter operation) emitEnd +-----+ + // +-------------------------------+--------->| End | + // +-----+ + enum class ClassState { + // The initial state. + Start, + + // After calling emitScopeForNamedClass. + Scope, + + // After calling emitClass or emitDerivedClass. + Class, + + // After calling emitInitConstructor or emitInitDefaultConstructor. + InitConstructor, + + // After calling emitEnd. + End, + }; + ClassState classState_ = ClassState::Start; +#endif + + JS::Rooted name_; + + public: + explicit ClassEmitter(BytecodeEmitter* bce); + + MOZ_MUST_USE bool emitScopeForNamedClass( + JS::Handle scopeBindings); + + // @param name + // Name of the class (nullptr if this is anonymous class) + MOZ_MUST_USE bool emitClass(JS::Handle name); + MOZ_MUST_USE bool emitDerivedClass(JS::Handle name); + + // @param needsHomeObject + // True if the constructor contains `super.foo` + MOZ_MUST_USE bool emitInitConstructor(bool needsHomeObject); + + // Parameters are the offset in the source code for each character below: + // + // class X { foo() {} } + // ^ ^ + // | | + // | classEnd + // | + // classStart + // + MOZ_MUST_USE bool emitInitDefaultConstructor( + const mozilla::Maybe& classStart, + const mozilla::Maybe& classEnd); + + MOZ_MUST_USE bool emitEnd(Kind kind); + + private: + void setName(JS::Handle name); + MOZ_MUST_USE bool initProtoAndCtor(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_ObjectEmitter_h */ diff --git a/js/src/moz.build b/js/src/moz.build index 3553dc9bf1..e078efc88a 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -140,14 +140,17 @@ main_deunified_sources = [ 'frontend/BytecodeControlStructures.cpp', 'frontend/BytecodeEmitter.cpp', 'frontend/CallOrNewEmitter.cpp', + 'frontend/DefaultEmitter.cpp', 'frontend/ElemOpEmitter.cpp', 'frontend/EmitterScope.cpp', 'frontend/FoldConstants.cpp', 'frontend/ForOfLoopControl.cpp', 'frontend/IfEmitter.cpp', 'frontend/JumpList.cpp', + 'frontend/LexicalScopeEmitter.cpp', 'frontend/NameFunctions.cpp', 'frontend/NameOpEmitter.cpp', + 'frontend/ObjectEmitter.cpp', 'frontend/ParseNode.cpp', 'frontend/PropOpEmitter.cpp', 'frontend/SwitchEmitter.cpp', From e6335ded8914e1a6cee139db872d71b2e0b746a9 Mon Sep 17 00:00:00 2001 From: Martok Date: Wed, 26 Apr 2023 19:24:13 +0200 Subject: [PATCH 06/23] Issue #2142 - Add FunctionEmitter, FunctionScriptEmitter, and FunctionParamsEmitter with current methods Based-on: m-c 1473796/3, 1473796/4 --- js/src/frontend/BytecodeEmitter.cpp | 742 +++++------------ js/src/frontend/BytecodeEmitter.h | 20 +- js/src/frontend/DestructuringFlavor.h | 31 + js/src/frontend/FunctionEmitter.cpp | 1083 +++++++++++++++++++++++++ js/src/frontend/FunctionEmitter.h | 451 ++++++++++ js/src/moz.build | 1 + 6 files changed, 1755 insertions(+), 573 deletions(-) create mode 100644 js/src/frontend/DestructuringFlavor.h create mode 100644 js/src/frontend/FunctionEmitter.cpp create mode 100644 js/src/frontend/FunctionEmitter.h diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index b76d6746a0..72ba7c6ed0 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -34,6 +34,7 @@ #include "frontend/ElemOpEmitter.h" #include "frontend/EmitterScope.h" #include "frontend/ForOfLoopControl.h" +#include "frontend/FunctionEmitter.h" // FunctionEmitter, FunctionScriptEmitter, FunctionParamsEmitter #include "frontend/IfEmitter.h" #include "frontend/LexicalScopeEmitter.h" // LexicalScopeEmitter #include "frontend/NameOpEmitter.h" @@ -2392,133 +2393,46 @@ 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) { - ParseNode* body = funNode->body(); + ListNode* paramsBody = &funNode->body()->as(); FunctionBox* funbox = sc->asFunctionBox(); - setScriptStartOffsetIfUnset(body->pn_pos.begin); + setScriptStartOffsetIfUnset(paramsBody->pn_pos.begin); - // The ordering of these EmitterScopes is important. The named lambda - // scope needs to enclose the function scope needs to enclose the extra - // var scope. + // [stack] - Maybe namedLambdaEmitterScope; - if (funbox->namedLambdaBindings()) { - namedLambdaEmitterScope.emplace(this); - if (!namedLambdaEmitterScope->enterNamedLambda(this, funbox)) - return false; + FunctionScriptEmitter fse(this, funbox, Some(paramsBody->pn_pos.begin), + Some(paramsBody->pn_pos.end)); + if (!fse.prepareForParameters()) { + // [stack] + return false; } - /* - * Emit a prologue for run-once scripts which will deoptimize JIT code - * if the script ends up running multiple times via foo.caller related - * shenanigans. - * - * Also mark the script so that initializers created within it may be - * given more precise types. - */ - if (isRunOnceLambda()) { - script->setTreatAsRunOnce(); - MOZ_ASSERT(!script->hasRunOnce()); - - switchToPrologue(); - if (!emit1(JSOP_RUNONCE)) - return false; - switchToMain(); + if (!emitFunctionFormalParameters(paramsBody)) { + // [stack] + return false; } - setFunctionBodyEndPos(body->pn_pos.end); - if (!emitTree(body)) + if (!fse.prepareForBody()) { + // [stack] return false; - - if (!updateSourceCoordNotes(body->pn_pos.end)) - return false; - - // Always end the script with a JSOP_RETRVAL. Some other parts of the - // codebase depend on this opcode, - // e.g. InterpreterRegs::setToEndOfScript. - if (!emit1(JSOP_RETRVAL)) - return false; - - if (namedLambdaEmitterScope) { - if (!namedLambdaEmitterScope->leave(this)) - return false; - namedLambdaEmitterScope.reset(); } - if (!JSScript::fullyInitFromEmitter(cx, script, this)) + if (!emitTree(paramsBody->last())) { + // [stack] return false; - - // URL and source map information must be set before firing - // Debugger::onNewScript. Only top-level functions need this, as compiling - // the outer scripts of nested functions already processed the source. - if (emitterMode != LazyFunction && !parent) { - if (!maybeSetDisplayURL() || !maybeSetSourceMap()) - return false; - - tellDebuggerAboutCompiledScript(cx); } + if (!fse.emitEndBody()) { + // [stack] + return false; + } + + if (!fse.initScript()) + return false; + return true; } @@ -5683,8 +5597,11 @@ BytecodeEmitter::emitFunction(FunctionNode* funNode, bool needsProto) { FunctionBox* funbox = funNode->funbox(); RootedFunction fun(cx, funbox->function()); - RootedAtom name(cx, fun->explicitName()); - MOZ_ASSERT_IF(fun->isInterpretedLazy(), fun->lazyScript()); + + // [stack] + + FunctionEmitter fe(this, funbox, funNode->syntaxKind(), + funNode->functionIsHoisted()); /* * Set the |wasEmitted| flag in the funbox once the function has been @@ -5692,43 +5609,9 @@ BytecodeEmitter::emitFunction(FunctionNode* funNode, bool needsProto) * function will be seen by emitFunction in two places. */ if (funbox->wasEmitted) { - // Annex B block-scoped functions are hoisted like any other - // block-scoped function to the top of their scope. When their - // definitions are seen for the second time, we need to emit the - // assignment that assigns the function to the outer 'var' binding. - if (funbox->isAnnexB) { - // Get the location of the 'var' binding in the body scope. The - // name must be found, else there is a bug in the Annex B handling - // in Parser. - // - // In sloppy eval contexts, this location is dynamic. - Maybe lhsLoc = locationOfNameBoundInScope(name, varEmitterScope); - - // If there are parameter expressions, the var name could be a - // parameter. - if (!lhsLoc && sc->isFunctionBox() && sc->asFunctionBox()->hasExtraBodyVarScope()) - lhsLoc = locationOfNameBoundInScope(name, varEmitterScope->enclosingInFrame()); - - if (!lhsLoc) { - lhsLoc = Some(NameLocation::DynamicAnnexBVar()); - } else { - MOZ_ASSERT(lhsLoc->bindingKind() == BindingKind::Var || - lhsLoc->bindingKind() == BindingKind::FormalParameter || - (lhsLoc->bindingKind() == BindingKind::Let && - sc->asFunctionBox()->hasParameterExprs)); - } - - NameOpEmitter noe(this, name, *lhsLoc, NameOpEmitter::Kind::SimpleAssignment); - if (!noe.prepareForRhs()) { - return false; - } - if (!emitGetName(name)) { - return false; - } - if (!noe.emitAssignment()) - return false; - if (!emit1(JSOP_POP)) - return false; + if (!fe.emitAgain()) { + // [stack] + return false; } MOZ_ASSERT_IF(fun->hasScript(), fun->nonLazyScript()); @@ -5736,172 +5619,62 @@ BytecodeEmitter::emitFunction(FunctionNode* funNode, bool needsProto) return true; } - funbox->wasEmitted = true; - /* - * Mark as singletons any function which will only be executed once, or - * which is inner to a lambda we only expect to run once. In the latter - * case, if the lambda runs multiple times then CloneFunctionObject will - * make a deep clone of its contents. - */ if (fun->isInterpreted()) { - bool singleton = checkRunOnceContext(); - if (!JSFunction::setTypeForScriptedFunction(cx, fun, singleton)) - return false; - - SharedContext* outersc = sc; if (fun->isInterpretedLazy()) { - // We need to update the static scope chain regardless of whether - // the LazyScript has already been initialized, due to the case - // where we previously successfully compiled an inner function's - // lazy script but failed to compile the outer script after the - // fact. If we attempt to compile the outer script again, the - // static scope chain will be newly allocated and will mismatch - // the previously compiled LazyScript's. - ScriptSourceObject* source = &script->sourceObject()->as(); - fun->lazyScript()->setEnclosingScopeAndSource(innermostScope(), source); - if (emittingRunOnceLambda) - fun->lazyScript()->setTreatAsRunOnce(); - } else { - MOZ_ASSERT_IF(outersc->strict(), funbox->strictScript); - - // Inherit most things (principals, version, etc) from the - // parent. Use default values for the rest. - Rooted parent(cx, script); - MOZ_ASSERT(parent->getVersion() == parser->options().version); - MOZ_ASSERT(parent->mutedErrors() == parser->options().mutedErrors()); - const TransitiveCompileOptions& transitiveOptions = parser->options(); - CompileOptions options(cx, transitiveOptions); - - Rooted sourceObject(cx, script->sourceObject()); - Rooted script(cx, JSScript::Create(cx, options, sourceObject, - funbox->bufStart, funbox->bufEnd, - funbox->toStringStart, - funbox->toStringEnd)); - if (!script) + if (!fe.emitLazy()) { + // [stack] FUN? return false; - - BytecodeEmitter bce2(this, parser, funbox, script, /* lazyScript = */ nullptr, - funNode->pn_pos, emitterMode); - if (!bce2.init()) - return false; - - /* We measured the max scope depth when we parsed the function. */ - if (!bce2.emitFunctionScript(funNode)) - return false; - - if (funbox->isLikelyConstructorWrapper()) - script->setLikelyConstructorWrapper(); - } - - if (outersc->isFunctionBox()) - outersc->asFunctionBox()->setHasInnerFunctions(); - } else { - MOZ_ASSERT(IsAsmJSModule(fun)); - } - - /* Make the function object a literal in the outer script's pool. */ - unsigned index = objectList.add(funNode->funbox()); - - /* Non-hoisted functions simply emit their respective op. */ - if (!funNode->functionIsHoisted()) { - /* JSOP_LAMBDA_ARROW is always preceded by a new.target */ - MOZ_ASSERT(fun->isArrow() == (funNode->syntaxKind() == FunctionSyntaxKind::Arrow)); - if (funbox->isAsync()) { - MOZ_ASSERT(!needsProto); - return emitAsyncWrapper(index, funbox->needsHomeObject(), fun->isArrow(), - fun->isStarGenerator()); - } - - if (fun->isArrow()) { - if (sc->allowNewTarget()) { - if (!emit1(JSOP_NEWTARGET)) - return false; - } else { - if (!emit1(JSOP_NULL)) - return false; } + + return true; } - if (needsProto) { - MOZ_ASSERT(funNode->syntaxKind() == FunctionSyntaxKind::DerivedClassConstructor); - return emitIndex32(JSOP_FUNWITHPROTO, index); - } - - // This is a FunctionExpression, ArrowFunctionExpression, or class - // constructor. Emit the single instruction (without location info). - JSOp op = funNode->syntaxKind() == FunctionSyntaxKind::Arrow - ? JSOP_LAMBDA_ARROW - : JSOP_LAMBDA; - return emitIndex32(op, index); - } - - MOZ_ASSERT(!needsProto); - - bool topLevelFunction; - if (sc->isFunctionBox() || (sc->isEvalContext() && sc->strict())) { - // No nested functions inside other functions are top-level. - topLevelFunction = false; - } else { - // In sloppy eval scripts, top-level functions in are accessed - // dynamically. In global and module scripts, top-level functions are - // those bound in the var scope. - NameLocation loc = lookupName(name); - topLevelFunction = loc.kind() == NameLocation::Kind::Dynamic || - loc.bindingKind() == BindingKind::Var; - } - - if (topLevelFunction) { - if (sc->isModuleContext()) { - // For modules, we record the function and instantiate the binding - // during ModuleInstantiate(), before the script is run. - - RootedModuleObject module(cx, sc->asModuleContext()->module()); - if (!module->noteFunctionDeclaration(cx, name, fun)) - return false; - } else { - MOZ_ASSERT(sc->isGlobalContext() || sc->isEvalContext()); - MOZ_ASSERT(funNode->syntaxKind() == FunctionSyntaxKind::Statement); - switchToPrologue(); - if (funbox->isAsync()) { - if (!emitAsyncWrapper(index, fun->isMethod(), fun->isArrow(), - fun->isStarGenerator())) - { - return false; - } - } else { - if (!emitIndex32(JSOP_LAMBDA, index)) - return false; - } - if (!emit1(JSOP_DEFFUN)) - return false; - if (!updateSourceCoordNotes(funNode->pn_pos.begin)) - return false; - switchToMain(); - } - } else { - // For functions nested within functions and blocks, make a lambda and - // initialize the binding name of the function in the current scope. - - NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize); - if (!noe.prepareForRhs()) { + if (!fe.prepareForNonLazy()) { + // [stack] return false; } - if (funbox->isAsync()) { - if (!emitAsyncWrapper(index, /* needsHomeObject = */ false, - /* isArrow = */ false, funbox->isStarGenerator())) - { - return false; - } - } else { - if (!emitIndexOp(JSOP_LAMBDA, index)) { - return false; - } + + // Inherit most things (principals, version, etc) from the + // parent. Use default values for the rest. + Rooted parent(cx, script); + MOZ_ASSERT(parent->getVersion() == parser->options().version); + MOZ_ASSERT(parent->mutedErrors() == parser->options().mutedErrors()); + const TransitiveCompileOptions& transitiveOptions = parser->options(); + CompileOptions options(cx, transitiveOptions); + + Rooted sourceObject(cx, script->sourceObject()); + Rooted script(cx, JSScript::Create(cx, options, sourceObject, + funbox->bufStart, funbox->bufEnd, + funbox->toStringStart, + funbox->toStringEnd)); + if (!script) + return false; + + BytecodeEmitter bce2(this, parser, funbox, script, /* lazyScript = */ nullptr, + funNode->pn_pos, emitterMode); + if (!bce2.init()) + return false; + + /* We measured the max scope depth when we parsed the function. */ + if (!bce2.emitFunctionScript(funNode)) + return false; + + if (funbox->isLikelyConstructorWrapper()) { + script->setLikelyConstructorWrapper(); } - if (!noe.emitAssignment()) - return false; - if (!emit1(JSOP_POP)) + + if (!fe.emitNonLazyEnd()) { + // [stack] FUN? return false; + } + + return true; + } + + if (!fe.emitAsmJSModule()) { + // [stack] + return false; } return true; @@ -8453,128 +8226,24 @@ BytecodeEmitter::emitTypeof(UnaryNode* typeofNode, JSOp op) return emit1(op); } -bool -BytecodeEmitter::emitFunctionFormalParametersAndBody(ListNode* paramsBody) -{ - MOZ_ASSERT(paramsBody->isKind(PNK_PARAMSBODY)); - - ParseNode* funBody = paramsBody->last(); - FunctionBox* funbox = sc->asFunctionBox(); - - TDZCheckCache tdzCache(this); - - if (funbox->hasParameterExprs) { - EmitterScope funEmitterScope(this); - if (!funEmitterScope.enterFunction(this, funbox)) - return false; - - if (!emitInitializeFunctionSpecialNames()) - return false; - - if (!emitFunctionFormalParameters(paramsBody)) - return false; - - { - Maybe extraVarEmitterScope; - - if (funbox->hasExtraBodyVarScope()) { - extraVarEmitterScope.emplace(this); - if (!extraVarEmitterScope->enterFunctionExtraBodyVar(this, funbox)) - return false; - - // After emitting expressions for all parameters, copy over any - // formal parameters which have been redeclared as vars. For - // example, in the following, the var y in the body scope is 42: - // - // function f(x, y = 42) { var y; } - // - RootedAtom name(cx); - if (funbox->extraVarScopeBindings() && funbox->functionScopeBindings()) { - for (BindingIter bi(*funbox->functionScopeBindings(), true); bi; bi++) { - name = bi.name(); - - // There may not be a var binding of the same name. - if (!locationOfNameBoundInScope(name, extraVarEmitterScope.ptr())) - continue; - - // The '.this' and '.generator' function special - // bindings should never appear in the extra var - // scope. 'arguments', however, may. - MOZ_ASSERT(name != cx->names().dotThis && - name != cx->names().dotGenerator); - - NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize); - if (!noe.prepareForRhs()) { - return false; - } - - NameLocation paramLoc = *locationOfNameBoundInScope(name, &funEmitterScope); - if (!emitGetNameAtLocation(name, paramLoc)) { - return false; - } - if (!noe.emitAssignment()) - return false; - if (!emit1(JSOP_POP)) - return false; - } - } - } - - if (!emitFunctionBody(funBody)) - return false; - - if (extraVarEmitterScope && !extraVarEmitterScope->leave(this)) - return false; - } - - return funEmitterScope.leave(this); - } - - // No parameter expressions. Enter the function body scope and emit - // everything. - // - // One caveat is that Debugger considers ops in the prologue to be - // unreachable (i.e. cannot set a breakpoint on it). If there are no - // parameter exprs, any unobservable environment ops (like pushing the - // call object, setting '.this', etc) need to go in the prologue, else it - // messes up breakpoint tests. - EmitterScope emitterScope(this); - - switchToPrologue(); - if (!emitterScope.enterFunction(this, funbox)) - return false; - - if (!emitInitializeFunctionSpecialNames()) - return false; - switchToMain(); - - if (!emitFunctionFormalParameters(paramsBody)) - return false; - - if (!emitFunctionBody(funBody)) - return false; - - return emitterScope.leave(this); -} - bool BytecodeEmitter::emitFunctionFormalParameters(ListNode* paramsBody) { ParseNode* funBody = paramsBody->last(); FunctionBox* funbox = sc->asFunctionBox(); - EmitterScope* funScope = innermostEmitterScope(); - - bool hasParameterExprs = funbox->hasParameterExprs; bool hasRest = funbox->hasRest(); - uint16_t argSlot = 0; - for (ParseNode* arg = paramsBody->head(); arg != funBody; arg = arg->pn_next, argSlot++) { + FunctionParamsEmitter fpe(this, funbox); + for (ParseNode* arg = paramsBody->head(); arg != funBody; arg = arg->pn_next) { ParseNode* bindingElement = arg; ParseNode* initializer = nullptr; if (arg->isKind(PNK_ASSIGN)) { bindingElement = arg->as().left(); initializer = arg->as().right(); } + bool hasInitializer = !!initializer; + bool isRest = hasRest && arg->pn_next == funBody; + bool isDestructuring = !bindingElement->isKind(PNK_NAME); // Left-hand sides are either simple names or destructuring patterns. MOZ_ASSERT(bindingElement->isKind(PNK_NAME) || @@ -8582,103 +8251,122 @@ BytecodeEmitter::emitFunctionFormalParameters(ListNode* paramsBody) bindingElement->isKind(PNK_ARRAYCOMP) || bindingElement->isKind(PNK_OBJECT)); - // The rest parameter doesn't have an initializer. - bool isRest = hasRest && arg->pn_next == funBody; - MOZ_ASSERT_IF(isRest, !initializer); + auto emitDefaultInitializer = [this, &initializer, &bindingElement]() { + // [stack] - bool isDestructuring = !bindingElement->isKind(PNK_NAME); + if (!this->emitInitializer(initializer, bindingElement)) { + // [stack] DEFAULT + return false; + } + return true; + }; - // ES 14.1.19 says if BindingElement contains an expression in the - // production FormalParameter : BindingElement, it is evaluated in a - // new var environment. This is needed to prevent vars from escaping - // direct eval in parameter expressions. - Maybe paramExprVarScope; - if (funbox->hasDirectEvalInParameterExpr && (isDestructuring || initializer)) { - paramExprVarScope.emplace(this); - if (!paramExprVarScope->enterParameterExpressionVar(this)) - return false; - } - - // First push the RHS if there is a default expression or if it is - // rest. - - if (initializer) { - // If we have an initializer, emit the initializer and assign it - // to the argument slot. TDZ is taken care of afterwards. - MOZ_ASSERT(hasParameterExprs); - IfEmitter ifUndefined(this); - if (!emitArgOp(JSOP_GETARG, argSlot)) - return false; - if (!emit1(JSOP_DUP)) - return false; - if (!emit1(JSOP_UNDEFINED)) - return false; - if (!emit1(JSOP_STRICTEQ)) - return false; - if (!ifUndefined.emitThen()) - return false; - if (!emit1(JSOP_POP)) - return false; - if (!emitInitializer(initializer, bindingElement)) - return false; - if (!ifUndefined.emitEnd()) - return false; - } else if (isRest) { - if (!emit1(JSOP_REST)) - return false; - } - - // Initialize the parameter name. - - if (isDestructuring) { - // If we had an initializer or the rest parameter, the value is - // already on the stack. - if (!initializer && !isRest && !emitArgOp(JSOP_GETARG, argSlot)) - return false; + auto emitDestructuring = [this, &fpe, &bindingElement]() { + // [stack] ARG // If there's an parameter expression var scope, the destructuring // declaration needs to initialize the name in the function scope, // which is not the innermost scope. - if (!emitDestructuringOps(&bindingElement->as(), - paramExprVarScope - ? DestructuringFormalParameterInVarScope - : DestructuringDeclaration)) - { + if (!this->emitDestructuringOps(&bindingElement->as(), + fpe.getDestructuringFlavor())) { + // [stack] ARG return false; } - if (!emit1(JSOP_POP)) - return false; - } else if (hasParameterExprs || isRest) { - RootedAtom paramName(cx, bindingElement->name()); - NameLocation paramLoc = *locationOfNameBoundInScope(paramName, funScope); + return true; + }; - NameOpEmitter noe(this, paramName, paramLoc, NameOpEmitter::Kind::Initialize); - if (!noe.prepareForRhs()) { - return false; - } - if (hasParameterExprs) { - // If we had an initializer or a rest parameter, the value is - // already on the stack. - if (!initializer && !isRest) { - if (!emitArgOp(JSOP_GETARG, argSlot)) - return false; + if (isRest) { + if (isDestructuring) { + if (!fpe.prepareForDestructuringRest()) { + // [stack] + return false; + } + if (!emitDestructuring()) { + // [stack] + return false; + } + if (!fpe.emitDestructuringRestEnd()) { + // [stack] + return false; + } + } else { + RootedAtom paramName(cx, bindingElement->as().name()); + if (!fpe.emitRest(paramName)) { + // [stack] + return false; + } + } + + continue; + } + + if (isDestructuring) { + if (hasInitializer) { + if (!fpe.prepareForDestructuringDefaultInitializer()) { + // [stack] + return false; + } + if (!emitDefaultInitializer()) { + // [stack] + return false; + } + if (!fpe.prepareForDestructuringDefault()) { + // [stack] + return false; + } + if (!emitDestructuring()) { + // [stack] + return false; + } + if (!fpe.emitDestructuringDefaultEnd()) { + // [stack] + return false; + } + } else { + if (!fpe.prepareForDestructuring()) { + // [stack] + return false; + } + if (!emitDestructuring()) { + // [stack] + return false; + } + if (!fpe.emitDestructuringEnd()) { + // [stack] + return false; } } - if (!noe.emitAssignment()) { - return false; - } - if (!emit1(JSOP_POP)) { - return false; - } + + continue; } - if (paramExprVarScope) { - if (!paramExprVarScope->leave(this)) + if (hasInitializer) { + if (!fpe.prepareForDefault()) { + // [stack] return false; + } + if (!emitDefaultInitializer()) { + // [stack] + return false; + } + RootedAtom paramName(cx, bindingElement->as().name()); + if (!fpe.emitDefaultEnd(paramName)) { + // [stack] + return false; + } + + continue; + } + + RootedAtom paramName(cx, bindingElement->as().name()); + if (!fpe.emitSimple(paramName)) { + // [stack] + return false; } } + return true; } @@ -8687,6 +8375,8 @@ BytecodeEmitter::emitInitializeFunctionSpecialNames() { FunctionBox* funbox = sc->asFunctionBox(); + // [stack] + auto emitInitializeFunctionSpecialName = [](BytecodeEmitter* bce, HandlePropertyName name, JSOp op) { @@ -8696,14 +8386,18 @@ BytecodeEmitter::emitInitializeFunctionSpecialNames() NameOpEmitter noe(bce, name, NameOpEmitter::Kind::Initialize); if (!noe.prepareForRhs()) { + // [stack] return false; } if (!bce->emit1(op)) { + // [stack] THIS/ARGUMENTS return false; } if (!noe.emitAssignment()) + // [stack] THIS/ARGUMENTS return false; if (!bce->emit1(JSOP_POP)) + // [stack] return false; return true; @@ -8712,6 +8406,7 @@ BytecodeEmitter::emitInitializeFunctionSpecialNames() // Do nothing if the function doesn't have an arguments binding. if (funbox->argumentsHasLocalBinding()) { if (!emitInitializeFunctionSpecialName(this, cx->names().arguments, JSOP_ARGUMENTS)) + // [stack] return false; } @@ -8726,66 +8421,6 @@ BytecodeEmitter::emitInitializeFunctionSpecialNames() return true; } -bool -BytecodeEmitter::emitFunctionBody(ParseNode* funBody) -{ - FunctionBox* funbox = sc->asFunctionBox(); - - if (funbox->function()->kind() == JSFunction::FunctionKind::ClassConstructor) { - if (!emitInitializeInstanceFields()) - return false; - } - - if (!emitTree(funBody)) - return false; - - if (funbox->needsFinalYield()) { - // If we fall off the end of a generator, do a final yield. - bool needsIteratorResult = funbox->needsIteratorResult(); - if (needsIteratorResult) { - if (!emitPrepareIteratorResult()) - return false; - } - - if (!emit1(JSOP_UNDEFINED)) - return false; - - if (needsIteratorResult) { - if (!emitFinishIteratorResult(true)) - return false; - } - - if (!emit1(JSOP_SETRVAL)) - return false; - - if (!emitGetDotGeneratorInInnermostScope()) - return false; - - // No need to check for finally blocks, etc as in EmitReturn. - if (!emitYieldOp(JSOP_FINALYIELDRVAL)) - return false; - } else { - // Non-generator functions just return |undefined|. The - // JSOP_RETRVAL emitted below will do that, except if the - // script has a finally block: there can be a non-undefined - // value in the return value slot. Make sure the return value - // is |undefined|. - if (hasTryFinally) { - if (!emit1(JSOP_UNDEFINED)) - return false; - if (!emit1(JSOP_SETRVAL)) - return false; - } - } - - if (funbox->isDerivedClassConstructor()) { - if (!emitCheckDerivedClassConstructorReturn()) - return false; - } - - return true; -} - bool BytecodeEmitter::emitLexicalInitialization(NameNode* pn) { @@ -8951,8 +8586,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage:: break; case PNK_PARAMSBODY: - if (!emitFunctionFormalParametersAndBody(&pn->as())) - return false; + MOZ_ASSERT_UNREACHABLE("ParamsBody should be handled in emitFunctionScript."); break; case PNK_IF: diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index bbb42f367e..42faa78007 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -14,6 +14,7 @@ #include "jsscript.h" #include "ds/InlineTable.h" +#include "frontend/DestructuringFlavor.h" #include "frontend/JumpList.h" #include "frontend/Parser.h" #include "frontend/SharedContext.h" @@ -424,8 +425,6 @@ 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); @@ -605,21 +604,6 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_NEVER_INLINE MOZ_MUST_USE bool emitSwitch(SwitchStatement* switchStmt); MOZ_NEVER_INLINE MOZ_MUST_USE bool emitTry(TryNode* tryNode); - enum DestructuringFlavor { - // Destructuring into a declaration. - DestructuringDeclaration, - - // Destructuring into a formal parameter, when the formal parameters - // contain an expression that might be evaluated, and thus require - // this destructuring to assign not into the innermost scope that - // contains the function body's vars, but into its enclosing scope for - // parameter expressions. - DestructuringFormalParameterInVarScope, - - // Destructuring as part of an AssignmentExpression. - DestructuringAssignment - }; - // emitDestructuringLHSRef emits the lhs expression's reference. // If the lhs expression is object property |OBJ.prop|, it emits |OBJ|. // If it's object element |OBJ[ELEM]|, it emits |OBJ| and |ELEM|. @@ -794,10 +778,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitBreak(PropertyName* label); MOZ_MUST_USE bool emitContinue(PropertyName* label); - MOZ_MUST_USE bool emitFunctionFormalParametersAndBody(ListNode* paramsBody); MOZ_MUST_USE bool emitFunctionFormalParameters(ListNode* paramsBody); MOZ_MUST_USE bool emitInitializeFunctionSpecialNames(); - MOZ_MUST_USE bool emitFunctionBody(ParseNode* pn); MOZ_MUST_USE bool emitLexicalInitialization(NameNode* pn); MOZ_MUST_USE bool emitLexicalInitialization(JSAtom* name); diff --git a/js/src/frontend/DestructuringFlavor.h b/js/src/frontend/DestructuringFlavor.h new file mode 100644 index 0000000000..1a4a20bb3c --- /dev/null +++ b/js/src/frontend/DestructuringFlavor.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef frontend_DestructuringFlavor_h +#define frontend_DestructuringFlavor_h + +namespace js { +namespace frontend { + +enum DestructuringFlavor { + // Destructuring into a declaration. + DestructuringDeclaration, + + // Destructuring into a formal parameter, when the formal parameters + // contain an expression that might be evaluated, and thus require + // this destructuring to assign not into the innermost scope that + // contains the function body's vars, but into its enclosing scope for + // parameter expressions. + DestructuringFormalParameterInVarScope, + + // Destructuring as part of an AssignmentExpression. + DestructuringAssignment +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_DestructuringFlavor_h */ \ No newline at end of file diff --git a/js/src/frontend/FunctionEmitter.cpp b/js/src/frontend/FunctionEmitter.cpp new file mode 100644 index 0000000000..f9e8faf714 --- /dev/null +++ b/js/src/frontend/FunctionEmitter.cpp @@ -0,0 +1,1083 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "frontend/FunctionEmitter.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include "jsscript.h" // JSScript + +#include "builtin/ModuleObject.h" // ModuleObject +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter +#include "frontend/NameAnalysisTypes.h" // NameLocation +#include "frontend/NameOpEmitter.h" // NameOpEmitter +#include "frontend/Parser.h" // BindingIter +#include "frontend/PropOpEmitter.h" // PropOpEmitter +#include "frontend/SharedContext.h" // SharedContext +#include "vm/AsyncFunction.h" // AsyncFunctionResolveKind +#include "vm/Opcodes.h" // JSOP_* +#include "vm/Scope.h" // BindingKind +#include "wasm/AsmJS.h" // IsAsmJSModule + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; +using mozilla::Some; + +FunctionEmitter::FunctionEmitter(BytecodeEmitter* bce_, FunctionBox* funbox, + FunctionSyntaxKind syntaxKind, + bool isHoisted) + : bce_(bce_), + funbox_(funbox), + fun_(bce_->cx, funbox_->function()), + name_(bce_->cx, fun_->explicitName()), + syntaxKind_(syntaxKind), + isHoisted_(isHoisted) +{ + MOZ_ASSERT_IF(fun_->isInterpretedLazy(), fun_->lazyScript()); +} + +bool FunctionEmitter::interpretedCommon() +{ + // Mark as singletons any function which will only be executed once, or + // which is inner to a lambda we only expect to run once. In the latter + // case, if the lambda runs multiple times then CloneFunctionObject will + // make a deep clone of its contents. + bool singleton = bce_->checkRunOnceContext(); + if (!JSFunction::setTypeForScriptedFunction(bce_->cx, fun_, singleton)) + return false; + + SharedContext* outersc = bce_->sc; + if (outersc->isFunctionBox()) + outersc->asFunctionBox()->setHasInnerFunctions(); + + return true; +} + +bool FunctionEmitter::prepareForNonLazy() +{ + MOZ_ASSERT(state_ == State::Start); + + MOZ_ASSERT(fun_->isInterpreted()); + MOZ_ASSERT(!fun_->isInterpretedLazy()); + MOZ_ASSERT(!funbox_->wasEmitted); + + // [stack] + + funbox_->wasEmitted = true; + + if (!interpretedCommon()) + return false; + + MOZ_ASSERT_IF(bce_->sc->strict(), funbox_->strictScript); + +#ifdef DEBUG + state_ = State::NonLazy; +#endif + return true; +} + +bool FunctionEmitter::emitNonLazyEnd() { + MOZ_ASSERT(state_ == State::NonLazy); + + // [stack] + + if (!emitFunction()) { + // [stack] FUN? + return false; + } + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +bool FunctionEmitter::emitLazy() { + MOZ_ASSERT(state_ == State::Start); + + MOZ_ASSERT(fun_->isInterpreted()); + MOZ_ASSERT(fun_->isInterpretedLazy()); + MOZ_ASSERT(!funbox_->wasEmitted); + + // [stack] + + funbox_->wasEmitted = true; + + if (!interpretedCommon()) + return false; + + // We need to update the static scope chain regardless of whether + // the LazyScript has already been initialized, due to the case + // where we previously successfully compiled an inner function's + // lazy script but failed to compile the outer script after the + // fact. If we attempt to compile the outer script again, the + // static scope chain will be newly allocated and will mismatch + // the previously compiled LazyScript's. + ScriptSourceObject* source = &bce_->script->sourceObject()->as(); + fun_->lazyScript()->setEnclosingScopeAndSource(bce_->innermostScope(), source); + if (bce_->emittingRunOnceLambda) + fun_->lazyScript()->setTreatAsRunOnce(); + + if (!emitFunction()) { + // [stack] FUN? + return false; + } + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +bool FunctionEmitter::emitAgain() +{ + MOZ_ASSERT(state_ == State::Start); + MOZ_ASSERT(funbox_->wasEmitted); + MOZ_ASSERT_IF(fun_->hasScript(), fun_->nonLazyScript()); + + // [stack] + + // Annex B block-scoped functions are hoisted like any other assignment + // that assigns the function to the outer 'var' binding. + if (!funbox_->isAnnexB) { +#ifdef DEBUG + state_ = State::End; +#endif + return true; + } + + // Get the location of the 'var' binding in the body scope. The + // name must be found, else there is a bug in the Annex B handling + // in Parser. + // + // In sloppy eval contexts, this location is dynamic. + Maybe lhsLoc = bce_->locationOfNameBoundInScope(name_, bce_->varEmitterScope); + + // If there are parameter expressions, the var name could be a + // parameter. + if (!lhsLoc && bce_->sc->isFunctionBox() && bce_->sc->asFunctionBox()->hasExtraBodyVarScope()) + lhsLoc = bce_->locationOfNameBoundInScope(name_, bce_->varEmitterScope->enclosingInFrame()); + + if (!lhsLoc) { + lhsLoc = Some(NameLocation::DynamicAnnexBVar()); + } else { + MOZ_ASSERT(lhsLoc->bindingKind() == BindingKind::Var || + lhsLoc->bindingKind() == BindingKind::FormalParameter || + (lhsLoc->bindingKind() == BindingKind::Let && + bce_->sc->asFunctionBox()->hasParameterExprs)); + } + + NameOpEmitter noe(bce_, name_, *lhsLoc, NameOpEmitter::Kind::SimpleAssignment); + if (!noe.prepareForRhs()) { + return false; + } + if (!bce_->emitGetName(name_)) { + return false; + } + if (!noe.emitAssignment()) + return false; + if (!bce_->emit1(JSOP_POP)) + return false; + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +bool FunctionEmitter::emitAsmJSModule() + { + MOZ_ASSERT(state_ == State::Start); + + MOZ_ASSERT(!funbox_->wasEmitted); + MOZ_ASSERT(IsAsmJSModule(fun_)); + + // [stack] + + funbox_->wasEmitted = true; + + if (!emitFunction()) { + // [stack] + return false; + } + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +bool FunctionEmitter::emitFunction() +{ + // Make the function object a literal in the outer script's pool. + unsigned index = bce_->objectList.add(funbox_); + + // [stack] + + if (!isHoisted_) { + return emitNonHoisted(index); + // [stack] FUN? + } + + bool topLevelFunction; + if (bce_->sc->isFunctionBox() || + (bce_->sc->isEvalContext() && bce_->sc->strict())) { + // No nested functions inside other functions are top-level. + topLevelFunction = false; + } else { + // In sloppy eval scripts, top-level functions are accessed dynamically. + // In global and module scripts, top-level functions are those bound in + // the var scope. + NameLocation loc = bce_->lookupName(name_); + topLevelFunction = loc.kind() == NameLocation::Kind::Dynamic || + loc.bindingKind() == BindingKind::Var; + } + + if (topLevelFunction) { + return emitTopLevelFunction(index); + // [stack] + } + + return emitHoisted(index); + // [stack] +} + +bool FunctionEmitter::emitNonHoisted(unsigned index) +{ + // Non-hoisted functions simply emit their respective op. + + // [stack] + + // JSOP_LAMBDA_ARROW is always preceded by a opcode that pushes new.target. + // See below. + MOZ_ASSERT(fun_->isArrow() == (syntaxKind_ == FunctionSyntaxKind::Arrow)); + + bool needsProto = syntaxKind_ == FunctionSyntaxKind::DerivedClassConstructor; + + if (funbox_->isAsync()) { + MOZ_ASSERT(!needsProto); + return bce_->emitAsyncWrapper(index, funbox_->needsHomeObject(), fun_->isArrow(), + fun_->isStarGenerator()); + } + + if (fun_->isArrow()) { + if (!emitNewTargetForArrow()) { + // [stack] NEW.TARGET/NULL + return false; + } + } + + if (needsProto) { + // [stack] PROTO + if (!bce_->emitIndex32(JSOP_FUNWITHPROTO, index)) { + // [stack] FUN + return false; + } + return true; + } + + // This is a FunctionExpression, ArrowFunctionExpression, or class + // constructor. Emit the single instruction (without location info). + JSOp op = syntaxKind_ == FunctionSyntaxKind::Arrow ? JSOP_LAMBDA_ARROW + : JSOP_LAMBDA; + if (!bce_->emitIndex32(op, index)) { + // [stack] FUN + return false; + } + + return true; +} + +bool FunctionEmitter::emitHoisted(unsigned index) +{ + MOZ_ASSERT(syntaxKind_ == FunctionSyntaxKind::Statement); + + // [stack] + + // For functions nested within functions and blocks, make a lambda and + // initialize the binding name of the function in the current scope. + + NameOpEmitter noe(bce_, name_, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + // [stack] + return false; + } + + if (funbox_->isAsync()) { + if (!bce_->emitAsyncWrapper(index, /* needsHomeObject = */ false, + /* isArrow = */ false, funbox_->isStarGenerator())) + { + return false; + } + } else { + if (!bce_->emitIndexOp(JSOP_LAMBDA, index)) { + return false; + } + } + + if (!noe.emitAssignment()) { + // [stack] FUN + return false; + } + + if (!bce_->emit1(JSOP_POP)) { + // [stack] + return false; + } + + return true; +} + +bool FunctionEmitter::emitTopLevelFunction(unsigned index) +{ + // [stack] + + if (bce_->sc->isModuleContext()) { + // For modules, we record the function and instantiate the binding + // during ModuleInstantiate(), before the script is run. + + JS::Rooted module(bce_->cx, + bce_->sc->asModuleContext()->module()); + if (!module->noteFunctionDeclaration(bce_->cx, name_, fun_)) + return false; + return true; + } + + MOZ_ASSERT(bce_->sc->isGlobalContext() || bce_->sc->isEvalContext()); + MOZ_ASSERT(syntaxKind_ == FunctionSyntaxKind::Statement); + + bce_->switchToPrologue(); + if (funbox_->isAsync()) { + if (!bce_->emitAsyncWrapper(index, fun_->isMethod(), fun_->isArrow(), + fun_->isStarGenerator())) + return false; + } else { + if (!bce_->emitIndex32(JSOP_LAMBDA, index)) + return false; + } + if (!bce_->emit1(JSOP_DEFFUN)) { + // [stack] + return false; + } + bce_->switchToMain(); + return true; +} + +bool FunctionEmitter::emitNewTargetForArrow() +{ + // [stack] + + if (bce_->sc->allowNewTarget()) { + if (!bce_->emit1(JSOP_NEWTARGET)) { + // [stack] NEW.TARGET + return false; + } + } else { + if (!bce_->emit1(JSOP_NULL)) { + // [stack] NULL + return false; + } + } + + return true; +} + +bool FunctionScriptEmitter::prepareForParameters() +{ + MOZ_ASSERT(state_ == State::Start); + + // [stack] + + if (paramStart_) { + bce_->setScriptStartOffsetIfUnset(*paramStart_); + } + + // The ordering of these EmitterScopes is important. The named lambda + // scope needs to enclose the function scope needs to enclose the extra + // var scope. + + if (funbox_->namedLambdaBindings()) { + namedLambdaEmitterScope_.emplace(bce_); + if (!namedLambdaEmitterScope_->enterNamedLambda(bce_, funbox_)) + return false; + } + + /* + * Emit a prologue for run-once scripts which will deoptimize JIT code + * if the script ends up running multiple times via foo.caller related + * shenanigans. + * + * Also mark the script so that initializers created within it may be + * given more precise types. + */ + if (bce_->isRunOnceLambda()) { + bce_->script->setTreatAsRunOnce(); + MOZ_ASSERT(!bce_->script->hasRunOnce()); + + bce_->switchToPrologue(); + if (!bce_->emit1(JSOP_RUNONCE)) + return false; + bce_->switchToMain(); + } + + if (bodyEnd_) { + bce_->setFunctionBodyEndPos(*bodyEnd_); + } + + if (paramStart_) { + if (!bce_->updateLineNumberNotes(*paramStart_)) + return false; + } + + tdzCache_.emplace(bce_); + functionEmitterScope_.emplace(bce_); + + if (funbox_->hasParameterExprs) { + // There's parameter exprs, emit them in the main section. + // + // One caveat is that Debugger considers ops in the prologue to be + // unreachable (i.e. cannot set a breakpoint on it). If there are no + // parameter exprs, any unobservable environment ops (like pushing the + // call object, setting '.this', etc) need to go in the prologue, else it + // messes up breakpoint tests. + bce_->switchToMain(); + } + + if (!functionEmitterScope_->enterFunction(bce_, funbox_)) + return false; + + if (!bce_->emitInitializeFunctionSpecialNames()) { + // [stack] + return false; + } + + if (!funbox_->hasParameterExprs) + bce_->switchToMain(); + +#ifdef DEBUG + state_ = State::Parameters; +#endif + return true; +} + +bool FunctionScriptEmitter::prepareForBody() +{ + MOZ_ASSERT(state_ == State::Parameters); + + // [stack] + + if (!emitExtraBodyVarScope()) { + // [stack] + return false; + } + + if (funbox_->function()->kind() == JSFunction::FunctionKind::ClassConstructor) { + if (!emitInitializeInstanceFields()) { + // [stack] + return false; + } + } + +#ifdef DEBUG + state_ = State::Body; +#endif + return true; +} + +bool FunctionScriptEmitter::emitExtraBodyVarScope() +{ + // [stack] + + if (!funbox_->hasExtraBodyVarScope()) { + return true; + } + + extraBodyVarEmitterScope_.emplace(bce_); + if (!extraBodyVarEmitterScope_->enterFunctionExtraBodyVar(bce_, funbox_)) + return false; + + if (!funbox_->extraVarScopeBindings() || !funbox_->functionScopeBindings()) + return true; + + // After emitting expressions for all parameters, copy over any formal + // parameters which have been redeclared as vars. For example, in the + // following, the var y in the body scope is 42: + // + // function f(x, y = 42) { var y; } + // + JS::Rooted name(bce_->cx); + for (BindingIter bi(*funbox_->functionScopeBindings(), true); bi; bi++) { + name = bi.name(); + + // There may not be a var binding of the same name. + if (!bce_->locationOfNameBoundInScope(name, extraBodyVarEmitterScope_.ptr())) { + continue; + } + + // The '.this' and '.generator' function special + // bindings should never appear in the extra var + // scope. 'arguments', however, may. + MOZ_ASSERT(name != bce_->cx->names().dotThis && + name != bce_->cx->names().dotGenerator); + + NameOpEmitter noe(bce_, name, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + // [stack] + return false; + } + + NameLocation paramLoc = *bce_->locationOfNameBoundInScope(name, functionEmitterScope_.ptr()); + if (!bce_->emitGetNameAtLocation(name, paramLoc)) { + // [stack] VAL + return false; + } + + if (!noe.emitAssignment()) { + // [stack] VAL + return false; + } + + if (!bce_->emit1(JSOP_POP)) { + // [stack] + return false; + } + } + + return true; +} + +bool FunctionScriptEmitter::emitInitializeInstanceFields() +{ + MOZ_ASSERT(bce_->fieldInitializers_.valid); + size_t numFields = bce_->fieldInitializers_.numFieldInitializers; + + if (numFields == 0) { + return true; + } + + if (!bce_->emitGetName(bce_->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 (!bce_->emit1(JSOP_DUP)) { + // [stack] ARRAY ARRAY + return false; + } + } + + if (!bce_->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 (!bce_->emit1(JSOP_GETELEM)) { + // [stack] ARRAY? FUNC + return false; + } + + // This is guaranteed to run after super(), so we don't need TDZ checks. + if (!bce_->emitGetName(bce_->cx->names().dotThis)) { + // [stack] ARRAY? FUNC THIS + return false; + } + + if (!bce_->emitCall(JSOP_CALL_IGNORES_RV, 0)) { + // [stack] ARRAY? RVAL + return false; + } + + if (!bce_->emit1(JSOP_POP)) { + // [stack] ARRAY? + return false; + } + } + + return true; +} + +bool FunctionScriptEmitter::emitEndBody() +{ + MOZ_ASSERT(state_ == State::Body); + + // [stack] + + if (funbox_->needsFinalYield()) { + // If we fall off the end of a generator, do a final yield. + bool needsIteratorResult = funbox_->needsIteratorResult(); + if (needsIteratorResult) { + if (!bce_->emitPrepareIteratorResult()) { + // [stack] RESULT + return false; + } + } + + if (!bce_->emit1(JSOP_UNDEFINED)) { + // [stack] RESULT? UNDEF + return false; + } + + if (needsIteratorResult) { + if (!bce_->emitFinishIteratorResult(true)) { + // [stack] RESULT + return false; + } + } + + if (!bce_->emit1(JSOP_SETRVAL)) { + // [stack] + return false; + } + + if (!bce_->emitGetDotGeneratorInInnermostScope()) { + // [stack] GEN + return false; + } + + // No need to check for finally blocks, etc as in EmitReturn. + if (!bce_->emitYieldOp(JSOP_FINALYIELDRVAL)) { + // [stack] + return false; + } + } else { + // Non-generator functions just return |undefined|. The + // JSOP_RETRVAL emitted below will do that, except if the + // script has a finally block: there can be a non-undefined + // value in the return value slot. Make sure the return value + // is |undefined|. + if (bce_->hasTryFinally) { + if (!bce_->emit1(JSOP_UNDEFINED)) { + // [stack] UNDEF + return false; + } + if (!bce_->emit1(JSOP_SETRVAL)) { + // [stack] + return false; + } + } + } + + if (funbox_->isDerivedClassConstructor()) { + if (!bce_->emitCheckDerivedClassConstructorReturn()) { + // [stack] + return false; + } + } + + if (extraBodyVarEmitterScope_) { + if (!extraBodyVarEmitterScope_->leave(bce_)) + return false; + + extraBodyVarEmitterScope_.reset(); + } + + if (!functionEmitterScope_->leave(bce_)) + return false; + functionEmitterScope_.reset(); + tdzCache_.reset(); + + if (bodyEnd_) { + if (!bce_->updateSourceCoordNotes(*bodyEnd_)) { + return false; + } + } + + // Always end the script with a JSOP_RETRVAL. Some other parts of the + // codebase depend on this opcode, + // e.g. InterpreterRegs::setToEndOfScript. + if (!bce_->emit1(JSOP_RETRVAL)) { + // [stack] + return false; + } + + if (namedLambdaEmitterScope_) { + if (!namedLambdaEmitterScope_->leave(bce_)) + return false; + namedLambdaEmitterScope_.reset(); + } + +#ifdef DEBUG + state_ = State::EndBody; +#endif + return true; +} + +bool FunctionScriptEmitter::initScript() +{ + MOZ_ASSERT(state_ == State::EndBody); + + if (!JSScript::fullyInitFromEmitter(bce_->cx, bce_->script, bce_)) { + return false; + } + + bce_->tellDebuggerAboutCompiledScript(bce_->cx); + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +FunctionParamsEmitter::FunctionParamsEmitter(BytecodeEmitter* bce_, + FunctionBox* funbox) + : bce_(bce_), + funbox_(funbox), + functionEmitterScope_(bce_->innermostEmitterScope()) {} + +bool FunctionParamsEmitter::emitSimple(JS::Handle paramName) +{ + MOZ_ASSERT(state_ == State::Start); + + // [stack] + + if (funbox_->hasParameterExprs) { + if (!bce_->emitArgOp(JSOP_GETARG, argSlot_)) { + // [stack] ARG + return false; + } + + if (!emitAssignment(paramName)) { + // [stack] + return false; + } + } + + argSlot_++; + return true; +} + +bool FunctionParamsEmitter::prepareForDefault() +{ + MOZ_ASSERT(state_ == State::Start); + + // [stack] + + if (!enterParameterExpressionVarScope()) { + return false; + } + + if (!prepareForInitializer()) { + // [stack] + return false; + } + +#ifdef DEBUG + state_ = State::Default; +#endif + return true; +} + +bool FunctionParamsEmitter::emitDefaultEnd(JS::Handle paramName) +{ + MOZ_ASSERT(state_ == State::Default); + + // [stack] DEFAULT + + if (!emitInitializerEnd()) { + // [stack] ARG/DEFAULT + return false; + } + if (!emitAssignment(paramName)) { + // [stack] + return false; + } + if (!leaveParameterExpressionVarScope()) { + return false; + } + + argSlot_++; + +#ifdef DEBUG + state_ = State::Start; +#endif + return true; +} + +bool FunctionParamsEmitter::prepareForDestructuring() +{ + MOZ_ASSERT(state_ == State::Start); + + // [stack] + + if (!enterParameterExpressionVarScope()) { + return false; + } + + if (!bce_->emitArgOp(JSOP_GETARG, argSlot_)) { + // [stack] ARG + return false; + } + +#ifdef DEBUG + state_ = State::Destructuring; +#endif + return true; +} + +bool FunctionParamsEmitter::emitDestructuringEnd() +{ + MOZ_ASSERT(state_ == State::Destructuring); + + // [stack] ARG + + if (!bce_->emit1(JSOP_POP)) { + // [stack] + return false; + } + + if (!leaveParameterExpressionVarScope()) { + return false; + } + + argSlot_++; + +#ifdef DEBUG + state_ = State::Start; +#endif + return true; +} + +bool FunctionParamsEmitter::prepareForDestructuringDefaultInitializer() +{ + MOZ_ASSERT(state_ == State::Start); + + // [stack] + + if (!enterParameterExpressionVarScope()) { + return false; + } + if (!prepareForInitializer()) { + // [stack] + return false; + } + +#ifdef DEBUG + state_ = State::DestructuringDefaultInitializer; +#endif + return true; +} + +bool FunctionParamsEmitter::prepareForDestructuringDefault() +{ + MOZ_ASSERT(state_ == State::DestructuringDefaultInitializer); + + // [stack] DEFAULT + + if (!emitInitializerEnd()) { + // [stack] ARG/DEFAULT + return false; + } + +#ifdef DEBUG + state_ = State::DestructuringDefault; +#endif + return true; +} + +bool FunctionParamsEmitter::emitDestructuringDefaultEnd() +{ + MOZ_ASSERT(state_ == State::DestructuringDefault); + + // [stack] ARG/DEFAULT + + if (!bce_->emit1(JSOP_POP)) { + // [stack] + return false; + } + + if (!leaveParameterExpressionVarScope()) { + return false; + } + + argSlot_++; + +#ifdef DEBUG + state_ = State::Start; +#endif + return true; +} + +bool FunctionParamsEmitter::emitRest(JS::Handle paramName) +{ + MOZ_ASSERT(state_ == State::Start); + + // [stack] + + if (!emitRestArray()) { + // [stack] REST + return false; + } + if (!emitAssignment(paramName)) { + // [stack] + return false; + } + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +bool FunctionParamsEmitter::prepareForDestructuringRest() + { + MOZ_ASSERT(state_ == State::Start); + + // [stack] + + if (!enterParameterExpressionVarScope()) { + return false; + } + if (!emitRestArray()) { + // [stack] REST + return false; + } + +#ifdef DEBUG + state_ = State::DestructuringRest; +#endif + return true; +} + +bool FunctionParamsEmitter::emitDestructuringRestEnd() +{ + MOZ_ASSERT(state_ == State::DestructuringRest); + + // [stack] REST + + if (!bce_->emit1(JSOP_POP)) { + // [stack] + return false; + } + + if (!leaveParameterExpressionVarScope()) + return false; + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +bool FunctionParamsEmitter::enterParameterExpressionVarScope() +{ + if (!funbox_->hasDirectEvalInParameterExpr) + return true; + + // ES 14.1.19 says if BindingElement contains an expression in the + // production FormalParameter : BindingElement, it is evaluated in a + // new var environment. This is needed to prevent vars from escaping + // direct eval in parameter expressions. + paramExprVarEmitterScope_.emplace(bce_); + if (!paramExprVarEmitterScope_->enterParameterExpressionVar(bce_)) + return false; + return true; +} + +bool FunctionParamsEmitter::leaveParameterExpressionVarScope() +{ + if (!paramExprVarEmitterScope_) + return true; + + if (!paramExprVarEmitterScope_->leave(bce_)) + return false; + paramExprVarEmitterScope_.reset(); + + return true; +} + +bool FunctionParamsEmitter::prepareForInitializer() +{ + // [stack] + + // If we have an initializer, emit the initializer and assign it + // to the argument slot. TDZ is taken care of afterwards. + MOZ_ASSERT(funbox_->hasParameterExprs); + if (!bce_->emitArgOp(JSOP_GETARG, argSlot_)) { + // [stack] ARG + return false; + } + default_.emplace(bce_); + if (!default_->prepareForDefault()) { + // [stack] + return false; + } + return true; +} + +bool FunctionParamsEmitter::emitInitializerEnd() +{ + // [stack] DEFAULT + + if (!default_->emitEnd()) { + // [stack] ARG/DEFAULT + return false; + } + default_.reset(); + return true; +} + +bool FunctionParamsEmitter::emitRestArray() +{ + // [stack] + + if (!bce_->emit1(JSOP_REST)) { + // [stack] REST + return false; + } + return true; +} + +bool FunctionParamsEmitter::emitAssignment(JS::Handle paramName) +{ + // [stack] ARG + + NameLocation paramLoc = *bce_->locationOfNameBoundInScope(paramName, functionEmitterScope_); + + // RHS is already pushed in the caller side. + // Make sure prepareForRhs doesn't touch stack. + MOZ_ASSERT(paramLoc.kind() == NameLocation::Kind::ArgumentSlot || + paramLoc.kind() == NameLocation::Kind::FrameSlot || + paramLoc.kind() == NameLocation::Kind::EnvironmentCoordinate); + + NameOpEmitter noe(bce_, paramName, paramLoc, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + // [stack] ARG + return false; + } + + if (!noe.emitAssignment()) { + // [stack] ARG + return false; + } + + if (!bce_->emit1(JSOP_POP)) { + // [stack] + return false; + } + + return true; +} + +DestructuringFlavor FunctionParamsEmitter::getDestructuringFlavor() +{ + MOZ_ASSERT(state_ == State::Destructuring || + state_ == State::DestructuringDefault || + state_ == State::DestructuringRest); + + return funbox_->hasDirectEvalInParameterExpr + ? DestructuringFormalParameterInVarScope + : DestructuringDeclaration; +} diff --git a/js/src/frontend/FunctionEmitter.h b/js/src/frontend/FunctionEmitter.h new file mode 100644 index 0000000000..ce068c6b56 --- /dev/null +++ b/js/src/frontend/FunctionEmitter.h @@ -0,0 +1,451 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef frontend_FunctionEmitter_h +#define frontend_FunctionEmitter_h + +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS, MOZ_MUST_USE + +#include // uint16_t, uint32_t + +#include "jsopcode.h" +#include "jsfun.h" // JSFunction + +#include "frontend/DefaultEmitter.h" // DefaultEmitter +#include "frontend/DestructuringFlavor.h" // DestructuringFlavor +#include "frontend/EmitterScope.h" // EmitterScope +#include "frontend/SharedContext.h" // FunctionBox +#include "frontend/TDZCheckCache.h" // TDZCheckCache +#include "gc/Rooting.h" // JS::Rooted, JS::Handle +#include "vm/String.h" // JSAtom + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting function declaration, expression, or method etc. +// +// This class handles the enclosing script's part (function object creation, +// declaration, etc). The content of the function script is handled by +// FunctionScriptEmitter and FunctionParamsEmitter. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `function f() {}`, non lazy script +// FunctionEmitter fe(this, funbox_for_f, FunctionSyntaxKind::Statement, +// false); +// fe.prepareForNonLazy(); +// +// // Emit script with FunctionScriptEmitter here. +// ... +// +// fe.emitNonLazyEnd(); +// +// `function f() {}`, lazy script +// FunctionEmitter fe(this, funbox_for_f, FunctionSyntaxKind::Statement, +// false); +// fe.emitLazy(); +// +// `function f() {}`, emitting hoisted function again +// // See emitAgain comment for more details +// FunctionEmitter fe(this, funbox_for_f, FunctionSyntaxKind::Statement, +// true); +// fe.emitAgain(); +// +// `function f() { "use asm"; }` +// FunctionEmitter fe(this, funbox_for_f, FunctionSyntaxKind::Statement, +// false); +// fe.emitAsmJSModule(); +// +class MOZ_STACK_CLASS FunctionEmitter { + private: + BytecodeEmitter* bce_; + + FunctionBox* funbox_; + + // Function linked from funbox_. + JS::Rooted fun_; + + // Function's explicit name. + JS::Rooted name_; + + FunctionSyntaxKind syntaxKind_; + bool isHoisted_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ + // | Start |-+ + // +-------+ | + // | + // +-------+ + // | + // | [non-lazy function] + // | prepareForNonLazy +---------+ emitNonLazyEnd +-----+ + // +--------------------->| NonLazy |---------------->+->| End | + // | +---------+ ^ +-----+ + // | | + // | [lazy function] | + // | emitLazy | + // +------------------------------------------------->+ + // | ^ + // | [emitting hoisted function again] | + // | emitAgain | + // +------------------------------------------------->+ + // | ^ + // | [asm.js module] | + // | emitAsmJSModule | + // +--------------------------------------------------+ + // + enum class State { + // The initial state. + Start, + + // After calling prepareForNonLazy. + NonLazy, + + // After calling emitNonLazyEnd, emitLazy, emitAgain, or emitAsmJSModule. + End + }; + State state_ = State::Start; +#endif + + public: + FunctionEmitter(BytecodeEmitter* bce, FunctionBox* funbox, + FunctionSyntaxKind syntaxKind, bool isHoisted); + + MOZ_MUST_USE bool prepareForNonLazy(); + MOZ_MUST_USE bool emitNonLazyEnd(); + + MOZ_MUST_USE bool emitLazy(); + + MOZ_MUST_USE bool emitAgain(); + + MOZ_MUST_USE bool emitAsmJSModule(); + + private: + // Common code for non-lazy and lazy functions. + MOZ_MUST_USE bool interpretedCommon(); + + // Emit the function declaration, expression, method etc. + // This leaves function object on the stack for expression etc, + // and doesn't for declaration. + MOZ_MUST_USE bool emitFunction(); + + // Helper methods used by emitFunction for each case. + // `index` is the object index of the function. + MOZ_MUST_USE bool emitNonHoisted(unsigned index); + MOZ_MUST_USE bool emitHoisted(unsigned index); + MOZ_MUST_USE bool emitTopLevelFunction(unsigned index); + MOZ_MUST_USE bool emitNewTargetForArrow(); +}; + +// Class for emitting function script. +// Parameters are handled by FunctionParamsEmitter. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `function f(a) { expr }` +// FunctionScriptEmitter fse(this, funbox_for_f, +// Some(offset_of_opening_paren), +// Some(offset_of_closing_brace)); +// fse.prepareForParameters(); +// +// // Emit parameters with FunctionParamsEmitter here. +// ... +// +// fse.prepareForBody(); +// emit(expr); +// fse.emitEnd(); +// +// // Do NameFunctions operation here if needed. +// +// fse.initScript(); +// +class MOZ_STACK_CLASS FunctionScriptEmitter { + private: + BytecodeEmitter* bce_; + + FunctionBox* funbox_; + + // Scope for the function name for a named lambda. + // None for anonymous function. + mozilla::Maybe namedLambdaEmitterScope_; + + // Scope for function body. + mozilla::Maybe functionEmitterScope_; + + // Scope for the extra body var. + // None if `funbox_->hasExtraBodyVarScope() == false`. + mozilla::Maybe extraBodyVarEmitterScope_; + + mozilla::Maybe tdzCache_; + + // See the comment for constructor. + mozilla::Maybe paramStart_; + mozilla::Maybe bodyEnd_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ prepareForParameters +------------+ + // | Start |---------------------->| Parameters |-+ + // +-------+ +------------+ | + // | + // +--------------------------------------------+ + // | + // | prepareForBody +------+ emitEndBody +---------+ + // +---------------->| Body |------------->| EndBody |-+ + // +------+ +---------+ | + // | + // +-------------------------------------------------+ + // | + // | initScript +-----+ + // +------------>| End | + // +-----+ + enum class State { + // The initial state. + Start, + + // After calling prepareForParameters. + Parameters, + + // After calling prepareForBody. + Body, + + // After calling emitEndBody. + EndBody, + + // After calling initScript. + End + }; + State state_ = State::Start; +#endif + + public: + // Parameters are the offset in the source code for each character below: + // + // function f(a, b, ...c) { ... } + // ^ ^ + // | | + // paramStart bodyEnd + // + // Can be Nothing() if not available. + FunctionScriptEmitter(BytecodeEmitter* bce, FunctionBox* funbox, + const mozilla::Maybe& paramStart, + const mozilla::Maybe& bodyEnd) + : bce_(bce), + funbox_(funbox), + paramStart_(paramStart), + bodyEnd_(bodyEnd) {} + + MOZ_MUST_USE bool prepareForParameters(); + MOZ_MUST_USE bool prepareForBody(); + MOZ_MUST_USE bool emitEndBody(); + + // Initialize JSScript for this function. + // WARNING: There shouldn't be any fallible operation for the function + // compilation after `initScript` call. + // See the comment inside JSScript::fullyInitFromEmitter for + // more details. + MOZ_MUST_USE bool initScript(); + + private: + MOZ_MUST_USE bool emitExtraBodyVarScope(); + MOZ_MUST_USE bool emitInitializeInstanceFields(); +}; + +// Class for emitting function parameters. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `function f(a, b=10, ...c) {}` +// FunctionParamsEmitter fpe(this, funbox_for_f); +// +// fpe.emitSimple(atom_of_a); +// +// fpe.prepareForDefault(); +// emit(10); +// fpe.emitDefaultEnd(atom_of_b); +// +// fpe.emitRest(atom_of_c); +// +// `function f([a], [b]=[1], ...[c]) {}` +// FunctionParamsEmitter fpe(this, funbox_for_f); +// +// fpe.prepareForDestructuring(); +// emit(destructuring_for_[a]); +// fpe.emitDestructuringEnd(); +// +// fpe.prepareForDestructuringDefaultInitializer(); +// emit([1]); +// fpe.prepareForDestructuringDefault(); +// emit(destructuring_for_[b]); +// fpe.emitDestructuringDefaultEnd(); +// +// fpe.prepareForDestructuringRest(); +// emit(destructuring_for_[c]); +// fpe.emitDestructuringRestEnd(); +// +class MOZ_STACK_CLASS FunctionParamsEmitter { + private: + BytecodeEmitter* bce_; + + FunctionBox* funbox_; + + // The pointer to `FunctionScriptEmitter::functionEmitterScope_`, + // passed via `BytecodeEmitter::innermostEmitterScope()`. + EmitterScope* functionEmitterScope_; + + // The slot for the current parameter. + // NOTE: after emitting rest parameter, this isn't incremented. + uint16_t argSlot_ = 0; + + // DefaultEmitter for default parameter. + mozilla::Maybe default_; + + // Scope for each parameter expression. + // Populated only when there's `eval` in parameters. + mozilla::Maybe paramExprVarEmitterScope_; + +#ifdef DEBUG + // The state of this emitter. + // + // +----------------------------------------------------------+ + // | | + // | +-------+ | + // +->| Start |-+ | + // +-------+ | | + // | | + // +------------+ | + // | | + // | [single binding, wihtout default] | + // | emitSimple | + // +--------------------------------------------------------->+ + // | ^ + // | [single binding, with default] | + // | prepareForDefault +---------+ emitDefaultEnd | + // +--------------------->| Default |------------------------>+ + // | +---------+ ^ + // | | + // | [destructuring, without default] | + // | prepareForDestructuring +---------------+ | + // +--------------------------->| Destructuring |-+ | + // | +---------------+ | | + // | | | + // | +-----------------------------------------+ | + // | | | + // | | emitDestructuringEnd | + // | +---------------------------------------------------->+ + // | ^ + // | [destructuring, with default] | + // | prepareForDestructuringDefaultInitializer | + // +---------------------------------------------+ | + // | | | + // | +----------------------------------------+ | + // | | | + // | | +---------------------------------+ | + // | +->| DestructuringDefaultInitializer |-+ | + // | +---------------------------------+ | | + // | | | + // | +------------------------------------+ | + // | | | + // | | prepareForDestructuringDefault | + // | +-------------------------------+ | + // | | | + // | +-----------------------------+ | + // | | | + // | | +----------------------+ | + // | +->| DestructuringDefault |-+ | + // | +----------------------+ | | + // | | | + // | +-------------------------+ | + // | | | + // | | emitDestructuringDefaultEnd | + // | +---------------------------------------------->+ + // | + // | [single binding rest] + // | emitRest +-----+ + // +--------------------------------------------------------->+->| End | + // | ^ +-----+ + // | [destructuring rest] | + // | prepareForDestructuringRest +-------------------+ | + // +-------------------------------->| DestructuringRest |-+ | + // +-------------------+ | | + // | | + // +----------------------------------------------------+ | + // | | + // | emitDestructuringRestEnd | + // +-------------------------------------------------------+ + // + enum class State { + // The initial state, or after emitting non-rest parameter. + Start, + + // After calling prepareForDefault. + Default, + + // After calling prepareForDestructuring. + Destructuring, + + // After calling prepareForDestructuringDefaultInitializer. + DestructuringDefaultInitializer, + + // After calling prepareForDestructuringDefault. + DestructuringDefault, + + // After calling prepareForDestructuringRest. + DestructuringRest, + + // After calling emitRest or emitDestructuringRestEnd. + End, + }; + State state_ = State::Start; +#endif + + public: + FunctionParamsEmitter(BytecodeEmitter* bce, FunctionBox* funbox); + + // paramName is used only when there's at least one expression in the + // paramerters (funbox_->hasParameterExprs == true). + MOZ_MUST_USE bool emitSimple(JS::Handle paramName); + + MOZ_MUST_USE bool prepareForDefault(); + MOZ_MUST_USE bool emitDefaultEnd(JS::Handle paramName); + + MOZ_MUST_USE bool prepareForDestructuring(); + MOZ_MUST_USE bool emitDestructuringEnd(); + + MOZ_MUST_USE bool prepareForDestructuringDefaultInitializer(); + MOZ_MUST_USE bool prepareForDestructuringDefault(); + MOZ_MUST_USE bool emitDestructuringDefaultEnd(); + + MOZ_MUST_USE bool emitRest(JS::Handle paramName); + + MOZ_MUST_USE bool prepareForDestructuringRest(); + MOZ_MUST_USE bool emitDestructuringRestEnd(); + + MOZ_MUST_USE DestructuringFlavor getDestructuringFlavor(); + + private: + // Enter/leave var scope for `eval` if necessary. + MOZ_MUST_USE bool enterParameterExpressionVarScope(); + MOZ_MUST_USE bool leaveParameterExpressionVarScope(); + + MOZ_MUST_USE bool prepareForInitializer(); + MOZ_MUST_USE bool emitInitializerEnd(); + + MOZ_MUST_USE bool emitRestArray(); + + MOZ_MUST_USE bool emitAssignment(JS::Handle paramName); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_FunctionEmitter_h */ diff --git a/js/src/moz.build b/js/src/moz.build index e078efc88a..b12d0a90cc 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -145,6 +145,7 @@ main_deunified_sources = [ 'frontend/EmitterScope.cpp', 'frontend/FoldConstants.cpp', 'frontend/ForOfLoopControl.cpp', + 'frontend/FunctionEmitter.cpp', 'frontend/IfEmitter.cpp', 'frontend/JumpList.cpp', 'frontend/LexicalScopeEmitter.cpp', From 235ca779493e23e40acbc52141059ef83d8aef43 Mon Sep 17 00:00:00 2001 From: Martok Date: Sat, 8 Apr 2023 03:47:06 +0200 Subject: [PATCH 07/23] Issue #2142 - Handle fields in derived classes * Don't name field initializer lambdas Based-on: m-c 1534721, 1551454, 1542448 --- js/src/frontend/BytecodeCompiler.cpp | 7 +- js/src/frontend/BytecodeEmitter.cpp | 193 ++++++++++++++++++++++----- js/src/frontend/BytecodeEmitter.h | 14 +- js/src/frontend/FullParseHandler.h | 4 +- js/src/frontend/FunctionEmitter.cpp | 71 ++-------- js/src/frontend/FunctionEmitter.h | 1 - js/src/frontend/ObjectEmitter.cpp | 27 ++-- js/src/frontend/ObjectEmitter.h | 33 ++--- js/src/frontend/ParseNode.h | 21 ++- js/src/frontend/Parser.cpp | 101 +++++++++++--- js/src/frontend/Parser.h | 11 +- js/src/jsscript.h | 67 +++++----- js/src/vm/CommonPropertyNames.h | 1 + 13 files changed, 361 insertions(+), 190 deletions(-) diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index 67423eb7ab..7f5b705f92 100644 --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -676,8 +676,13 @@ frontend::CompileLazyFunction(JSContext* cx, Handle lazy, const cha if (lazy->hasBeenCloned()) script->setHasBeenCloned(); + FieldInitializers fieldInitializers = FieldInitializers::Invalid(); + if (fun->kind() == JSFunction::FunctionKind::ClassConstructor) { + fieldInitializers = lazy->getFieldInitializers(); + } + BytecodeEmitter bce(/* parent = */ nullptr, &parser, pn->as().funbox(), script, lazy, - pn->pn_pos, BytecodeEmitter::LazyFunction); + pn->pn_pos, BytecodeEmitter::LazyFunction, fieldInitializers); if (!bce.init()) return false; diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 72ba7c6ed0..02400ce2a3 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -156,7 +156,8 @@ class MOZ_RAII OptionalEmitter BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, Parser* parser, SharedContext* sc, HandleScript script, Handle lazyScript, - uint32_t lineNum, EmitterMode emitterMode) + uint32_t lineNum, EmitterMode emitterMode, + FieldInitializers fieldInitializers /* = FieldInitializers::Invalid() */) : sc(sc), cx(sc->context), parent(parent), @@ -166,6 +167,7 @@ BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, main(cx, lineNum), current(&main), parser(parser), + fieldInitializers_(fieldInitializers), atomIndices(cx->frontendCollectionPool()), firstLine(lineNum), maxFixedSlots(0), @@ -178,10 +180,6 @@ BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, innermostNestableControl(nullptr), innermostEmitterScope_(nullptr), innermostTDZCheckCache(nullptr), - fieldInitializers_(parent - ? parent->fieldInitializers_ - : lazyScript ? lazyScript->getFieldInitializers() - : FieldInitializers::Invalid()), #ifdef DEBUG unstableEmitterScope(false), #endif @@ -202,10 +200,11 @@ BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, Parser* parser, SharedContext* sc, HandleScript script, Handle lazyScript, - TokenPos bodyPosition, EmitterMode emitterMode) + TokenPos bodyPosition, EmitterMode emitterMode, + FieldInitializers fieldInitializers) : BytecodeEmitter(parent, parser, sc, script, lazyScript, parser->tokenStream.srcCoords.lineNum(bodyPosition.begin), - emitterMode) + emitterMode, fieldInitializers) { setScriptStartOffsetIfUnset(bodyPosition.begin); setFunctionBodyEndPos(bodyPosition.end); @@ -2321,6 +2320,10 @@ BytecodeEmitter::emitSetThis(BinaryNode* setThisNode) return false; } + if (!emitInitializeInstanceFields(true)) { + return false; + } + return true; } @@ -2399,6 +2402,9 @@ BytecodeEmitter::emitFunctionScript(FunctionNode* funNode) ListNode* paramsBody = &funNode->body()->as(); FunctionBox* funbox = sc->asFunctionBox(); + MOZ_ASSERT(fieldInitializers_.valid == (funbox->function()->kind() == + JSFunction::FunctionKind::ClassConstructor)); + setScriptStartOffsetIfUnset(paramsBody->pn_pos.begin); // [stack] @@ -2433,6 +2439,8 @@ BytecodeEmitter::emitFunctionScript(FunctionNode* funNode) if (!fse.initScript()) return false; + script->setFieldInitializers(fieldInitializers_); + return true; } @@ -5593,11 +5601,14 @@ BytecodeEmitter::emitComprehensionFor(ForNode* forNode) } MOZ_NEVER_INLINE bool -BytecodeEmitter::emitFunction(FunctionNode* funNode, bool needsProto) +BytecodeEmitter::emitFunction(FunctionNode* funNode, bool needsProto /* = false */, + ListNode* classContentsIfConstructor /* = nullptr */) { FunctionBox* funbox = funNode->funbox(); RootedFunction fun(cx, funbox->function()); - + + MOZ_ASSERT((classContentsIfConstructor != nullptr) == (funbox->function()->kind() == + JSFunction::FunctionKind::ClassConstructor)); // [stack] FunctionEmitter fe(this, funbox, funNode->syntaxKind(), @@ -5627,6 +5638,9 @@ BytecodeEmitter::emitFunction(FunctionNode* funNode, bool needsProto) return false; } + if (classContentsIfConstructor) { + fun->lazyScript()->setFieldInitializers(setupFieldInitializers(classContentsIfConstructor)); + } return true; } @@ -5651,8 +5665,13 @@ BytecodeEmitter::emitFunction(FunctionNode* funNode, bool needsProto) if (!script) return false; + FieldInitializers fieldInitializers = FieldInitializers::Invalid(); + if (classContentsIfConstructor) { + fieldInitializers = setupFieldInitializers(classContentsIfConstructor); + } + BytecodeEmitter bce2(this, parser, funbox, script, /* lazyScript = */ nullptr, - funNode->pn_pos, emitterMode); + funNode->pn_pos, emitterMode, fieldInitializers); if (!bce2.init()) return false; @@ -5660,6 +5679,8 @@ BytecodeEmitter::emitFunction(FunctionNode* funNode, bool needsProto) if (!bce2.emitFunctionScript(funNode)) return false; + // fieldInitializers are copied to the JSScript inside BytecodeEmitter + if (funbox->isLikelyConstructorWrapper()) { script->setLikelyConstructorWrapper(); } @@ -7932,7 +7953,7 @@ BytecodeEmitter::emitCreateFieldKeys(ListNode* obj) bool BytecodeEmitter::emitCreateFieldInitializers(ListNode* obj) { - const FieldInitializers& fieldInitializers = fieldInitializers_; + FieldInitializers fieldInitializers = setupFieldInitializers(obj); MOZ_ASSERT(fieldInitializers.valid); size_t numFields = fieldInitializers.numFieldInitializers; @@ -7989,6 +8010,132 @@ BytecodeEmitter::emitCreateFieldInitializers(ListNode* obj) return true; } +const FieldInitializers& +BytecodeEmitter::findFieldInitializersForCall() +{ + for (BytecodeEmitter* current = this; current; current = current->parent) { + if (current->sc->isFunctionBox()) { + FunctionBox* box = current->sc->asFunctionBox(); + if (box->function()->kind() == JSFunction::FunctionKind::ClassConstructor) { + const FieldInitializers& fieldInitializers = current->getFieldInitializers(); + MOZ_ASSERT(fieldInitializers.valid); + return fieldInitializers; + } + } + } + + for (ScopeIter si(innermostScope()); si; si++) { + if (si.scope()->is()) { + JSFunction* fun = si.scope()->as().canonicalFunction(); + if (fun->kind() == JSFunction::FunctionKind::ClassConstructor) { + const FieldInitializers& fieldInitializers = fun->isInterpretedLazy() + ? fun->lazyScript()->getFieldInitializers() + : fun->nonLazyScript()->getFieldInitializers(); + MOZ_ASSERT(fieldInitializers.valid); + return fieldInitializers; + } + } + } + + MOZ_CRASH("Constructor for field initializers not found."); +} + +bool +BytecodeEmitter::emitCopyInitializersToLocalInitializers() +{ + MOZ_ASSERT(sc->asFunctionBox()->isDerivedClassConstructor()); + if (getFieldInitializers().numFieldInitializers == 0) + return true; + + NameOpEmitter noe(this, cx->names().dotLocalInitializers, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + // [stack] + return false; + } + + if (!emitGetName(cx->names().dotInitializers)) { + // [stack] .initializers + return false; + } + + if (!noe.emitAssignment()) { + // [stack] .initializers + return false; + } + + if (!emit1(JSOP_POP)) { + // [stack] + return false; + } + + return true; +} + +bool +BytecodeEmitter::emitInitializeInstanceFields(bool isSuperCall) +{ + const FieldInitializers& fieldInitializers = findFieldInitializersForCall(); + size_t numFields = fieldInitializers.numFieldInitializers; + + if (numFields == 0) { + return true; + } + + if (isSuperCall) { + if (!emitGetName(cx->names().dotLocalInitializers)) { + // [stack] ARRAY + return false; + } + } else { + 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; +} // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See // the comment on emitSwitch. @@ -8446,20 +8593,6 @@ BytecodeEmitter::emitLexicalInitialization(JSAtom* name) 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). @@ -8485,15 +8618,11 @@ BytecodeEmitter::emitClass(ClassNode* classNode) } } - // set this->fieldInitializers_ - AutoResetFieldInitializers _innermostClassAutoReset(this, setupFieldInitializers(classMembers)); - // [stack] ClassEmitter ce(this); RootedAtom innerName(cx); ClassEmitter::Kind kind = ClassEmitter::Kind::Expression; - if (names) { innerName = names->innerBinding()->as().atom(); MOZ_ASSERT(innerName); @@ -8503,8 +8632,10 @@ BytecodeEmitter::emitClass(ClassNode* classNode) MOZ_ASSERT(names->outerBinding()->as().atom() == innerName); kind = ClassEmitter::Kind::Declaration; } + } - if (!ce.emitScopeForNamedClass(classNode->scopeBindings())) { + if (!classNode->isEmptyScope()) { + if (!ce.emitScope(classNode->scopeBindings(), classNode->names() != nullptr)) { // [stack] return false; } @@ -8534,7 +8665,7 @@ BytecodeEmitter::emitClass(ClassNode* classNode) if (constructor) { bool needsHomeObject = constructor->funbox()->needsHomeObject(); // HERITAGE is consumed inside emitFunction. - if (!emitFunction(constructor, isDerived)) { + if (!emitFunction(constructor, isDerived, classMembers)) { // [stack] HOMEOBJ CTOR return false; } diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 42faa78007..8a196a4799 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -181,6 +181,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter /* field info for enclosing class */ FieldInitializers fieldInitializers_; + const FieldInitializers& getFieldInitializers() { return fieldInitializers_; } #ifdef DEBUG bool unstableEmitterScope; @@ -251,13 +252,15 @@ struct MOZ_STACK_CLASS BytecodeEmitter */ BytecodeEmitter(BytecodeEmitter* parent, Parser* parser, SharedContext* sc, HandleScript script, Handle lazyScript, uint32_t lineNum, - EmitterMode emitterMode = Normal); + EmitterMode emitterMode = Normal, + FieldInitializers fieldInitializers = FieldInitializers::Invalid()); // An alternate constructor that uses a TokenPos for the starting // line and that sets functionBodyEndPos as well. BytecodeEmitter(BytecodeEmitter* parent, Parser* parser, SharedContext* sc, HandleScript script, Handle lazyScript, - TokenPos bodyPosition, EmitterMode emitterMode = Normal); + TokenPos bodyPosition, EmitterMode emitterMode = Normal, + FieldInitializers fieldInitializers = FieldInitializers::Invalid()); MOZ_MUST_USE bool init(); @@ -514,7 +517,9 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitObjectPairOp(ObjectBox* objbox1, ObjectBox* objbox2, JSOp op); MOZ_MUST_USE bool emitRegExp(uint32_t index); - MOZ_NEVER_INLINE MOZ_MUST_USE bool emitFunction(FunctionNode* funNode, bool needsProto = false); + MOZ_NEVER_INLINE MOZ_MUST_USE bool emitFunction(FunctionNode* funNode, + bool needsProto = false, + ListNode* classContentsIfConstructor = nullptr); MOZ_NEVER_INLINE MOZ_MUST_USE bool emitObject(ListNode* objNode); MOZ_MUST_USE bool replaceNewInitWithNewObject(JSObject* obj, ptrdiff_t offset); @@ -527,6 +532,9 @@ struct MOZ_STACK_CLASS BytecodeEmitter FieldInitializers setupFieldInitializers(ListNode* classMembers); MOZ_MUST_USE bool emitCreateFieldKeys(ListNode* obj); MOZ_MUST_USE bool emitCreateFieldInitializers(ListNode* obj); + const FieldInitializers& findFieldInitializersForCall(); + MOZ_MUST_USE bool emitCopyInitializersToLocalInitializers(); + MOZ_MUST_USE bool emitInitializeInstanceFields(bool isSuperCall); // To catch accidental misuse, emitUint16Operand/emit3 assert that they are // not used to unconditionally emit JSOP_GETLOCAL. Variable access should diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index 3f52e88d54..d8ac80e2c6 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -367,7 +367,9 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) return literal; } - ClassNodeType newClass(Node name, Node heritage, Node memberBlock, const TokenPos& pos) { + ClassNodeType newClass(Node name, Node heritage, LexicalScopeNodeType memberBlock, + const TokenPos& pos) + { return new_(name, heritage, memberBlock, pos); } ListNodeType newClassMemberList(uint32_t begin) { diff --git a/js/src/frontend/FunctionEmitter.cpp b/js/src/frontend/FunctionEmitter.cpp index f9e8faf714..98dffe6b04 100644 --- a/js/src/frontend/FunctionEmitter.cpp +++ b/js/src/frontend/FunctionEmitter.cpp @@ -477,9 +477,16 @@ bool FunctionScriptEmitter::prepareForBody() } if (funbox_->function()->kind() == JSFunction::FunctionKind::ClassConstructor) { - if (!emitInitializeInstanceFields()) { - // [stack] - return false; + if (funbox_->isDerivedClassConstructor()) { + if (!bce_->emitCopyInitializersToLocalInitializers()) { + // [stack] + return false; + } + } else { + if (!bce_->emitInitializeInstanceFields(false)) { + // [stack] + return false; + } } } @@ -551,64 +558,6 @@ bool FunctionScriptEmitter::emitExtraBodyVarScope() return true; } -bool FunctionScriptEmitter::emitInitializeInstanceFields() -{ - MOZ_ASSERT(bce_->fieldInitializers_.valid); - size_t numFields = bce_->fieldInitializers_.numFieldInitializers; - - if (numFields == 0) { - return true; - } - - if (!bce_->emitGetName(bce_->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 (!bce_->emit1(JSOP_DUP)) { - // [stack] ARRAY ARRAY - return false; - } - } - - if (!bce_->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 (!bce_->emit1(JSOP_GETELEM)) { - // [stack] ARRAY? FUNC - return false; - } - - // This is guaranteed to run after super(), so we don't need TDZ checks. - if (!bce_->emitGetName(bce_->cx->names().dotThis)) { - // [stack] ARRAY? FUNC THIS - return false; - } - - if (!bce_->emitCall(JSOP_CALL_IGNORES_RV, 0)) { - // [stack] ARRAY? RVAL - return false; - } - - if (!bce_->emit1(JSOP_POP)) { - // [stack] ARRAY? - return false; - } - } - - return true; -} - bool FunctionScriptEmitter::emitEndBody() { MOZ_ASSERT(state_ == State::Body); diff --git a/js/src/frontend/FunctionEmitter.h b/js/src/frontend/FunctionEmitter.h index ce068c6b56..98d0ad1bb5 100644 --- a/js/src/frontend/FunctionEmitter.h +++ b/js/src/frontend/FunctionEmitter.h @@ -257,7 +257,6 @@ class MOZ_STACK_CLASS FunctionScriptEmitter { private: MOZ_MUST_USE bool emitExtraBodyVarScope(); - MOZ_MUST_USE bool emitInitializeInstanceFields(); }; // Class for emitting function parameters. diff --git a/js/src/frontend/ObjectEmitter.cpp b/js/src/frontend/ObjectEmitter.cpp index b89596a774..f421030e99 100644 --- a/js/src/frontend/ObjectEmitter.cpp +++ b/js/src/frontend/ObjectEmitter.cpp @@ -531,14 +531,16 @@ ClassEmitter::ClassEmitter(BytecodeEmitter* bce) isClass_ = true; } -bool ClassEmitter::emitScopeForNamedClass(JS::Handle scopeBindings) +bool ClassEmitter::emitScope(JS::Handle scopeBindings, bool hasName) { MOZ_ASSERT(propertyState_ == PropertyState::Start); MOZ_ASSERT(classState_ == ClassState::Start); - tdzCacheForInnerName_.emplace(bce_); - innerNameScope_.emplace(bce_); - if (!innerNameScope_->enterLexical(bce_, ScopeKind::Lexical, scopeBindings)) + if (hasName) + tdzCacheForInnerName_.emplace(bce_); + + innerScope_.emplace(bce_); + if (!innerScope_->enterLexical(bce_, ScopeKind::Lexical, scopeBindings)) return false; #ifdef DEBUG @@ -716,7 +718,7 @@ bool ClassEmitter::emitEnd(Kind kind) if (name_ != bce_->cx->names().empty) { MOZ_ASSERT(tdzCacheForInnerName_.isSome()); - MOZ_ASSERT(innerNameScope_.isSome()); + MOZ_ASSERT(innerScope_.isSome()); if (!bce_->emitLexicalInitialization(name_)) { // [stack] CTOR @@ -724,9 +726,9 @@ bool ClassEmitter::emitEnd(Kind kind) } // Pop the inner scope. - if (!innerNameScope_->leave(bce_)) + if (!innerScope_->leave(bce_)) return false; - innerNameScope_.reset(); + innerScope_.reset(); if (kind == Kind::Declaration) { if (!bce_->emitLexicalInitialization(name_)) { @@ -742,9 +744,18 @@ bool ClassEmitter::emitEnd(Kind kind) } tdzCacheForInnerName_.reset(); - } else { + } else if (innerScope_.isSome()) { + // [stack] CTOR + MOZ_ASSERT(kind == Kind::Expression); + MOZ_ASSERT(tdzCacheForInnerName_.isNothing()); + + if (!innerScope_->leave(bce_)) + return false; + innerScope_.reset(); + }else { // [stack] CTOR + MOZ_ASSERT(kind == Kind::Expression); MOZ_ASSERT(tdzCacheForInnerName_.isNothing()); } diff --git a/js/src/frontend/ObjectEmitter.h b/js/src/frontend/ObjectEmitter.h index 85a6f81faa..952d4cf156 100644 --- a/js/src/frontend/ObjectEmitter.h +++ b/js/src/frontend/ObjectEmitter.h @@ -464,6 +464,7 @@ class MOZ_RAII AutoSaveLocalStrictMode // // `class {}` // ClassEmitter ce(this); +// ce.emitScope(scopeBindings, false); // ce.emitClass(); // // ce.emitInitDefaultConstructor(Some(offset_of_class), @@ -473,6 +474,7 @@ class MOZ_RAII AutoSaveLocalStrictMode // // `class { constructor() { ... } }` // ClassEmitter ce(this); +// ce.emitScope(scopeBindings, false); // ce.emitClass(); // // emit(function_for_constructor); @@ -482,7 +484,7 @@ class MOZ_RAII AutoSaveLocalStrictMode // // `class X { constructor() { ... } }` // ClassEmitter ce(this); -// ce.emitScopeForNamedClass(scopeBindingForName); +// ce.emitScope(scopeBindings, true); // ce.emitClass(atom_of_X); // // ce.emitInitDefaultConstructor(Some(offset_of_class), @@ -492,7 +494,7 @@ class MOZ_RAII AutoSaveLocalStrictMode // // `class X { constructor() { ... } }` // ClassEmitter ce(this); -// ce.emitScopeForNamedClass(scopeBindingForName); +// ce.emitScope(scopeBindings, true); // ce.emitClass(atom_of_X); // // emit(function_for_constructor); @@ -502,7 +504,7 @@ class MOZ_RAII AutoSaveLocalStrictMode // // `class X extends Y { constructor() { ... } }` // ClassEmitter ce(this); -// ce.emitScopeForNamedClass(scopeBindingForName); +// ce.emitScope(scopeBindings, true); // // emit(Y); // ce.emitDerivedClass(atom_of_X); @@ -514,7 +516,7 @@ class MOZ_RAII AutoSaveLocalStrictMode // // `class X extends Y { constructor() { ... super.f(); ... } }` // ClassEmitter ce(this); -// ce.emitScopeForNamedClass(scopeBindingForName); +// ce.emitScope(scopeBindings, true); // // emit(Y); // ce.emitDerivedClass(atom_of_X); @@ -629,21 +631,21 @@ class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter bool isDerived_ = false; mozilla::Maybe tdzCacheForInnerName_; - mozilla::Maybe innerNameScope_; + mozilla::Maybe innerScope_; AutoSaveLocalStrictMode strictMode_; #ifdef DEBUG // The state of this emitter. // // +-------+ - // | Start |-+------------------------------------>+-+ - // +-------+ | ^ | - // | [named class] | | - // | emitScopeForNamedClass +-------+ | | - // +-------------------------->| Scope |-+ | - // +-------+ | - // | - // +-----------------------------------------------+ + // | Start |-+------------------------>+-+ + // +-------+ | ^ | + // | [has scope] | | + // | emitScope +-------+ | | + // +-------------->| Scope |-+ | + // +-------+ | + // | + // +-----------------------------------+ // | // | emitClass +-------+ // +-+----------------->+->| Class |-+ @@ -669,7 +671,7 @@ class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter // The initial state. Start, - // After calling emitScopeForNamedClass. + // After calling emitScope. Scope, // After calling emitClass or emitDerivedClass. @@ -689,8 +691,7 @@ class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter public: explicit ClassEmitter(BytecodeEmitter* bce); - MOZ_MUST_USE bool emitScopeForNamedClass( - JS::Handle scopeBindings); + MOZ_MUST_USE bool emitScope(JS::Handle scopeBindings, bool hasName); // @param name // Name of the class (nullptr if this is anonymous class) diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index fffda6a73d..da722a51e7 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -252,10 +252,7 @@ IsTypeofKind(ParseNodeKind kind) * PNK_CLASS (ClassNode) * kid1: PNK_CLASSNAMES for class name. can be null for anonymous class. * kid2: expression after `extends`. null if no expression - * kid3: either of - * * PNK_CLASSMEMBERLIST, if anonymous class - * * PNK_LEXICALSCOPE which contains PNK_CLASSMEMBERLIST as scopeBody, - * if named class + * kid3: PNK_LEXICALSCOPE which contains PNK_CLASSMEMBERLIST as scopeBody * PNK_CLASSNAMES (ClassNames) * left: Name node for outer binding, or null if the class is an expression * that doesn't create an outer binding @@ -2243,13 +2240,11 @@ class ClassNames : public BinaryNode class ClassNode : public TernaryNode { public: - ClassNode(ParseNode* names, ParseNode* heritage, ParseNode* membersOrBlock, + ClassNode(ParseNode* names, ParseNode* heritage, LexicalScopeNode* memberBlock, const TokenPos& pos) - : TernaryNode(PNK_CLASS, JSOP_NOP, names, heritage, membersOrBlock, pos) + : TernaryNode(PNK_CLASS, JSOP_NOP, names, heritage, memberBlock, pos) { MOZ_ASSERT_IF(names, names->is()); - MOZ_ASSERT(membersOrBlock->is() || - membersOrBlock->isKind(PNK_CLASSMEMBERLIST)); } static bool test(const ParseNode& node) { @@ -2265,14 +2260,14 @@ class ClassNode : public TernaryNode return kid2(); } ListNode* memberList() const { - ParseNode* membersOrBlock = kid3(); - if (membersOrBlock->isKind(PNK_CLASSMEMBERLIST)) - return &membersOrBlock->as(); - - ListNode* list = &membersOrBlock->as().scopeBody()->as(); + ListNode* list = &kid3()->as().scopeBody()->as(); MOZ_ASSERT(list->isKind(PNK_CLASSMEMBERLIST)); return list; } + bool isEmptyScope() const { + ParseNode* scope = kid3(); + return scope->as().isEmptyScope(); + } Handle scopeBindings() const { ParseNode* scope = kid3(); return scope->as().scopeBindings(); diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 77fa337fb7..8e09aea626 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -2779,6 +2779,12 @@ Parser::functionBody(InHandling inHandling, YieldHandling yieldHan return null(); } + if (kind == FunctionSyntaxKind::DerivedClassConstructor) { + if (!noteDeclaredName(context->names().dotLocalInitializers, + DeclarationKind::Var, pos())) + return null(); + } + return finishLexicalScope(pc->varScope(), body); } @@ -3600,7 +3606,11 @@ Parser::standaloneLazyFunction(HandleFunction fun, bool strict FunctionSyntaxKind syntaxKind = FunctionSyntaxKind::Statement; if (fun->isClassConstructor()) { - syntaxKind = FunctionSyntaxKind::ClassConstructor; + if (fun->isDerivedClassConstructor()) { + syntaxKind = FunctionSyntaxKind::DerivedClassConstructor; + } else { + syntaxKind = FunctionSyntaxKind::ClassConstructor; + } } else if (fun->isMethod()) { syntaxKind = FunctionSyntaxKind::Method; } else if (fun->isGetter()) { @@ -7468,7 +7478,7 @@ Parser::classMember(YieldHandling yieldHandling, DefaultHandling d numFields++; - FunctionNodeType initializer = fieldInitializerOpt(yieldHandling, hasHeritage, propName, + FunctionNodeType initializer = fieldInitializerOpt(yieldHandling, hasHeritage, propAtom, numFieldKeys); if (!initializer) return false; @@ -7553,8 +7563,9 @@ Parser::classMember(YieldHandling yieldHandling, DefaultHandling d template bool Parser::finishClassConstructor(const ParseContext::ClassStatement& classStmt, - HandlePropertyName className, uint32_t classStartOffset, - uint32_t classEndOffset, size_t numFields, + HandlePropertyName className, bool hasHeritage, + uint32_t classStartOffset, uint32_t classEndOffset, + size_t numFields, ListNodeType& classMembers) { // Fields cannot re-use the constructor obtained via JSOP_CLASSCONSTRUCTOR or @@ -7562,7 +7573,7 @@ Parser::finishClassConstructor(const ParseContext::ClassStatement& // 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); + FunctionNodeType synthesizedCtor = synthesizeConstructor(className, classStartOffset, hasHeritage); if (!synthesizedCtor) { return false; } @@ -7600,12 +7611,6 @@ Parser::finishClassConstructor(const ParseContext::ClassStatement& 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); } } @@ -7724,7 +7729,7 @@ Parser::classDefinition(YieldHandling yieldHandling, pc->innermostScope()->id())) return null(); if (!noteDeclaredName(context->names().dotInitializers, - DeclarationKind::Var, namePos)) + DeclarationKind::Let, namePos)) return null(); } @@ -7733,8 +7738,8 @@ Parser::classDefinition(YieldHandling yieldHandling, return null(); } classEndOffset = pos().end; - if (!finishClassConstructor(classStmt, className, classStartOffset, - classEndOffset, numFields, classMembers)) + if (!finishClassConstructor(classStmt, className, hasHeritage, + classStartOffset, classEndOffset, numFields, classMembers)) return null(); if (className) { @@ -7779,9 +7784,10 @@ Parser::classDefinition(YieldHandling yieldHandling, template typename ParseHandler::FunctionNodeType -Parser::synthesizeConstructor(HandleAtom className, uint32_t classNameOffset) +Parser::synthesizeConstructor(HandleAtom className, uint32_t classNameOffset, bool hasHeritage) { - FunctionSyntaxKind functionSyntaxKind = FunctionSyntaxKind::ClassConstructor; + FunctionSyntaxKind functionSyntaxKind = hasHeritage ? FunctionSyntaxKind::DerivedClassConstructor + : FunctionSyntaxKind::ClassConstructor; // Create the function object. RootedFunction fun(context, newFunction(className, functionSyntaxKind, @@ -7820,6 +7826,8 @@ Parser::synthesizeConstructor(HandleAtom className, uint32_t class funbox->function()->setArgCount(0); funbox->setStart(tokenStream); + pc->functionScope().useAsVarScope(pc); + // Push a LexicalScope on to the stack. ParseContext::Scope lexicalScope(this); if (!lexicalScope.init(pc)) @@ -7835,10 +7843,55 @@ Parser::synthesizeConstructor(HandleAtom className, uint32_t class // One might expect a noteUsedName(".initializers") here. See comment in // GeneralParser::classDefinition on why it's not here. + if (hasHeritage) { + if (!noteDeclaredName(context->names().dotLocalInitializers, + DeclarationKind::Var, synthesizedBodyPos)) + return null(); + } + bool canSkipLazyClosedOverBindings = handler.canSkipLazyClosedOverBindings(); if (!declareFunctionThis(canSkipLazyClosedOverBindings)) return null(); + if (hasHeritage) { + // {Goanna} Need a different this-NameNode for SuperBase and SetThis or the recycling + // by ParseNodeAllocator runs into all sorts of problems because the + // same ParseNode gets cleaned up twice. + // Parser::memberExpr does the same. + NameNodeType thisNameBase = newThisName(); + if (!thisNameBase) + return null(); + + UnaryNodeType superBase = handler.newSuperBase(thisNameBase, synthesizedBodyPos); + if (!superBase) + return null(); + + ListNodeType arguments = handler.newArguments(synthesizedBodyPos); + if (!arguments) + return null(); + + BinaryNodeType superCall = handler.newSuperCall(superBase, arguments, false); + if (!superCall) + return null(); + + NameNodeType thisName = newThisName(); + if (!thisName) + return null(); + + BinaryNodeType setThis = handler.newSetThis(thisName, superCall); + if (!setThis) + return null(); + + if (!noteUsedName(context->names().dotLocalInitializers)) + return null(); + + UnaryNodeType exprStatement = handler.newExprStatement(setThis, synthesizedBodyPos.end); + if (!exprStatement) + return null(); + + handler.addStatementToList(stmtList, exprStatement); + } + auto initializerBody = finishLexicalScope(lexicalScope, stmtList); if (!initializerBody) return null(); @@ -7860,7 +7913,7 @@ Parser::synthesizeConstructor(HandleAtom className, uint32_t class template typename ParseHandler::FunctionNodeType Parser::fieldInitializerOpt(YieldHandling yieldHandling, bool hasHeritage, - Node propName, HandleAtom propAtom, size_t& numFieldKeys) + HandleAtom propAtom, size_t& numFieldKeys) { bool hasInitializer = false; if (!tokenStream.matchToken(&hasInitializer, TOK_ASSIGN)) @@ -7878,9 +7931,9 @@ Parser::fieldInitializerOpt(YieldHandling yieldHandling, bool hasH firstTokenPos = TokenPos(endPos, endPos); } - // Create the function object. + // Create the anonymous function object. RootedFunction fun(context, - newFunction(propAtom, FunctionSyntaxKind::Expression, + newFunction(nullptr, FunctionSyntaxKind::Expression, GeneratorKind::NotGenerator, FunctionAsyncKind::SyncFunction)); if (!fun) @@ -7981,7 +8034,12 @@ Parser::fieldInitializerOpt(YieldHandling yieldHandling, bool hasH if (!propAssignFieldAccess) return null(); } else if (propAtom->isIndex(&indexValue)) { - propAssignFieldAccess = handler.newPropertyByValue(propAssignThis, propName, wholeInitializerPos.end); + // {Goanna} Can't reuse propName here, see comment in synthesizeConstructor + Node indexNode = handler.newNumber(indexValue, DecimalPoint::NoDecimal, wholeInitializerPos); + if (!indexNode) + return null(); + + propAssignFieldAccess = handler.newPropertyByValue(propAssignThis, indexNode, wholeInitializerPos.end); if (!propAssignFieldAccess) return null(); } else { @@ -9908,6 +9966,9 @@ Parser::memberExpr(YieldHandling yieldHandling, TripledotHandling nextMember = handler.newSetThis(thisName, nextMember); if (!nextMember) return null(); + + if (!noteUsedName(context->names().dotLocalInitializers)) + return null(); } else { nextMember = memberCall(tt, lhs, yieldHandling, possibleError); if (!nextMember) diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index f7feefcaab..5a0722afa7 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -1498,14 +1498,15 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) 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); + HandlePropertyName className, bool hasHeritage, + uint32_t classStartOffset, uint32_t classEndOffset, + size_t numFieldsWithInitializers, ListNodeType& classMembers); FunctionNodeType fieldInitializerOpt(YieldHandling yieldHandling, bool hasHeritage, - Node name, HandleAtom atom, size_t& numFieldKeys); + HandleAtom atom, size_t& numFieldKeys); FunctionNodeType synthesizeConstructor(HandleAtom className, - uint32_t classNameOffset); + uint32_t classNameOffset, + bool hasHeritage); bool checkLabelOrIdentifierReference(PropertyName* ident, uint32_t offset, diff --git a/js/src/jsscript.h b/js/src/jsscript.h index fbdd658b79..5534df5806 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -704,6 +704,35 @@ class ScriptSourceObject : public NativeObject enum GeneratorKind { NotGenerator, LegacyGenerator, StarGenerator }; enum FunctionAsyncKind { SyncFunction, AsyncFunction }; +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) { + } +}; + static inline unsigned GeneratorKindAsBits(GeneratorKind generatorKind) { return static_cast(generatorKind); @@ -855,6 +884,8 @@ class JSScript : public js::gc::TenuredCell private: js::SharedScriptData* scriptData_; + + js::FieldInitializers fieldInitializers_ = js::FieldInitializers::Invalid(); public: uint8_t* data; /* pointer to variable-length data array (see comment above Create() for details) */ @@ -1454,6 +1485,11 @@ class JSScript : public js::gc::TenuredCell return functionHasThisBinding_; } + void setFieldInitializers(js::FieldInitializers fieldInitializers) { + fieldInitializers_ = fieldInitializers; + } + const js::FieldInitializers& getFieldInitializers() const { return fieldInitializers_; } + /* * Arguments access (via JSOP_*ARG* opcodes) must access the canonical * location for the argument. If an arguments object exists AND it's mapped @@ -1999,35 +2035,6 @@ 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 @@ -2330,7 +2337,7 @@ class LazyScript : public gc::TenuredCell fieldInitializers_ = fieldInitializers; } - FieldInitializers getFieldInitializers() const { return fieldInitializers_; } + const 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 f69dfe3ca9..bc2e4204e3 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -103,6 +103,7 @@ macro(dotGenerator, dotGenerator, ".generator") \ macro(dotThis, dotThis, ".this") \ macro(dotInitializers, dotInitializers, ".initializers") \ + macro(dotLocalInitializers, dotLocalInitializers, ".localInitializers") \ macro(dotFieldKeys, dotFieldKeys, ".fieldKeys") \ macro(each, each, "each") \ macro(elementType, elementType, "elementType") \ From 014953c5329a6a10bd83cabeab34f4e8d257bc81 Mon Sep 17 00:00:00 2001 From: Martok Date: Sun, 9 Apr 2023 02:01:53 +0200 Subject: [PATCH 08/23] Issue #2142 - Factor out PropertyName parsing from Parser::propertyName() Based-on: m-c 1529772/{1,2} --- js/src/frontend/Parser.cpp | 235 +++++++++++++++++++------------------ js/src/frontend/Parser.h | 6 +- 2 files changed, 126 insertions(+), 115 deletions(-) diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 8e09aea626..aa0e005268 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -4469,7 +4469,8 @@ Parser::objectBindingPattern(DeclarationKind kind, YieldHandling y TokenPos namePos = tokenStream.nextToken().pos; PropertyType propType; - Node propName = propertyName(yieldHandling, PropertyNameInPattern, declKind, literal, &propType, &propAtom); + Node propName = propertyOrMethodName(yieldHandling, PropertyNameInPattern, declKind, + literal, &propType, &propAtom); if (!propName) return null(); if (propType == PropertyType::Normal) { @@ -7452,8 +7453,9 @@ Parser::classMember(YieldHandling yieldHandling, DefaultHandling d RootedAtom propAtom(context); PropertyType propType; - Node propName = propertyName(yieldHandling, PropertyNameInClass, /* maybeDecl = */ Nothing(), - classMembers, &propType, &propAtom); + Node propName = propertyOrMethodName(yieldHandling, PropertyNameInClass, + /* maybeDecl = */ Nothing(), + classMembers, &propType, &propAtom); if (!propName) return false; @@ -10557,42 +10559,118 @@ DoubleToAtom(ExclusiveContext* cx, double value) template typename ParseHandler::Node Parser::propertyName(YieldHandling yieldHandling, + PropertyNameContext propertyNameContext, + const Maybe& maybeDecl, ListNodeType propList, + MutableHandleAtom propAtom) +{ + // PropertyName[Yield, Await]: + // LiteralPropertyName + // ComputedPropertyName[?Yield, ?Await] + // + // LiteralPropertyName: + // IdentifierName + // StringLiteral + // NumericLiteral + TokenKind ltok = tokenStream.currentToken().type; + + propAtom.set(nullptr); + switch (ltok) { + case TOK_NUMBER: + propAtom.set(DoubleToAtom(context, tokenStream.currentToken().number())); + if (!propAtom.get()) + return null(); + return newNumber(tokenStream.currentToken()); + + case TOK_STRING: { + propAtom.set(tokenStream.currentToken().atom()); + uint32_t index; + if (propAtom->isIndex(&index)) { + return handler.newNumber(index, NoDecimal, pos()); + } + return stringLiteral(); + } + + case TOK_LB: + return computedPropertyName(yieldHandling, maybeDecl, propList); + + default: { + if (!TokenKindIsPossibleIdentifierName(ltok)) { + error(JSMSG_UNEXPECTED_TOKEN, "property name", TokenKindToDesc(ltok)); + return null(); + } + + propAtom.set(tokenStream.currentName()); + return handler.newObjectLiteralPropertyName(propAtom, pos()); + } + } +} + +// True if `kind` can be the first token of a PropertyName. +static bool +TokenKindCanStartPropertyName(TokenKind tt) +{ + return TokenKindIsPossibleIdentifierName(tt) || tt == TOK_STRING || + tt == TOK_NUMBER || tt == TOK_LB || + tt == TOK_MUL; +} + +template +typename ParseHandler::Node +Parser::propertyOrMethodName(YieldHandling yieldHandling, PropertyNameContext propertyNameContext, const Maybe& maybeDecl, ListNodeType propList, PropertyType* propType, MutableHandleAtom propAtom) { + // We're parsing an object literal, class, or destructuring pattern; + // propertyNameContext tells which one. This method parses any of the + // following, storing the corresponding PropertyType in `*propType` to tell + // the caller what we parsed: + // + // async [no LineTerminator here] PropertyName + // ==> PropertyType::AsyncMethod + // async [no LineTerminator here] * PropertyName + // ==> PropertyType::AsyncGeneratorMethod + // * PropertyName ==> PropertyType::GeneratorMethod + // get PropertyName ==> PropertyType::Getter + // set PropertyName ==> PropertyType::Setter + // PropertyName : ==> PropertyType::Normal + // PropertyName ==> see below + // + // In the last case, where there's not a `:` token to consume, we peek at + // (but don't consume) the next token to decide how to set `*propType`. + // + // `=` or `;` ==> PropertyType::Field (classes only) + // `=` ==> PropertyType::CoverInitializedName + // `,` or `}` ==> PropertyType::Shorthand + // `(` ==> PropertyType::Method + // + // The caller must check `*propType` and throw if whatever we parsed isn't + // allowed here (for example, a getter in a destructuring pattern). + // + // This method does *not* match `static` (allowed in classes) or `...` + // (allowed in object literals and patterns). The caller must take care of + // those before calling this method. + TokenKind ltok; if (!tokenStream.getToken(<ok)) return null(); MOZ_ASSERT(ltok != TOK_RC, "caller should have handled TOK_RC"); + // Accept `async` and/or `*`, indicating an async or generator method; + // or `get` or `set`, indicating an accessor. bool isGenerator = false; bool isAsync = false; + bool isGetter = false; + bool isSetter = false; if (ltok == TOK_ASYNC) { - // AsyncMethod[Yield, Await]: - // async [no LineTerminator here] PropertyName[?Yield, ?Await] ... - // - // AsyncGeneratorMethod[Yield, Await]: - // async [no LineTerminator here] * PropertyName[?Yield, ?Await] ... - // - // PropertyName: - // LiteralPropertyName - // ComputedPropertyName[?Yield, ?Await] - // - // LiteralPropertyName: - // IdentifierName - // StringLiteral - // NumericLiteral - // - // ComputedPropertyName[Yield, Await]: - // [ ... + // `async` is also a PropertyName by itself (it's a conditional keyword), + // so peek at the next token to see if we're really looking at a method. TokenKind tt = TOK_EOF; if (!tokenStream.peekTokenSameLine(&tt)) return null(); - if (tt == TOK_STRING || tt == TOK_NUMBER || tt == TOK_LB || - TokenKindIsPossibleIdentifierName(tt) || tt == TOK_MUL) + if (TokenKindCanStartPropertyName(tt)) { isAsync = true; tokenStream.consumeKnownToken(tt); @@ -10606,109 +10684,33 @@ Parser::propertyName(YieldHandling yieldHandling, return null(); } - propAtom.set(nullptr); - Node propName; - switch (ltok) { - case TOK_NUMBER: - propAtom.set(DoubleToAtom(context, tokenStream.currentToken().number())); - if (!propAtom.get()) - return null(); - propName = newNumber(tokenStream.currentToken()); - if (!propName) - return null(); - break; - - case TOK_STRING: { - propAtom.set(tokenStream.currentToken().atom()); - uint32_t index; - if (propAtom->isIndex(&index)) { - propName = handler.newNumber(index, NoDecimal, pos()); - if (!propName) - return null(); - break; - } - propName = stringLiteral(); - if (!propName) - return null(); - break; - } - - case TOK_LB: - propName = computedPropertyName(yieldHandling, maybeDecl, propList); - if (!propName) - return null(); - break; - - default: { - if (!TokenKindIsPossibleIdentifierName(ltok)) { - error(JSMSG_UNEXPECTED_TOKEN, "property name", TokenKindToDesc(ltok)); - return null(); - } - - propAtom.set(tokenStream.currentName()); - // Do not look for accessor syntax on generator or async methods. - if (isGenerator || isAsync || !(ltok == TOK_GET || ltok == TOK_SET)) { - propName = handler.newObjectLiteralPropertyName(propAtom, pos()); - if (!propName) - return null(); - break; - } - - *propType = ltok == TOK_GET ? PropertyType::Getter : PropertyType::Setter; - + if (!isAsync && !isGenerator && + (ltok == TOK_GET || ltok == TOK_SET)) { // We have parsed |get| or |set|. Look for an accessor property // name next. TokenKind tt; if (!tokenStream.peekToken(&tt)) return null(); - if (TokenKindIsPossibleIdentifierName(tt)) { + if (TokenKindCanStartPropertyName(tt)) { tokenStream.consumeKnownToken(tt); - - propAtom.set(tokenStream.currentName()); - return handler.newObjectLiteralPropertyName(propAtom, pos()); + isGetter = (ltok == TOK_GET); + isSetter = (ltok == TOK_SET); } - if (tt == TOK_STRING) { - tokenStream.consumeKnownToken(TOK_STRING); - - propAtom.set(tokenStream.currentToken().atom()); - - uint32_t index; - if (propAtom->isIndex(&index)) { - propAtom.set(DoubleToAtom(context, index)); - if (!propAtom.get()) - return null(); - return handler.newNumber(index, NoDecimal, pos()); - } - return stringLiteral(); - } - if (tt == TOK_NUMBER) { - tokenStream.consumeKnownToken(TOK_NUMBER); - - propAtom.set(DoubleToAtom(context, tokenStream.currentToken().number())); - if (!propAtom.get()) - return null(); - return newNumber(tokenStream.currentToken()); - } - if (tt == TOK_LB) { - tokenStream.consumeKnownToken(TOK_LB); - - return computedPropertyName(yieldHandling, maybeDecl, propList); - } - - // Not an accessor property after all. - propName = handler.newObjectLiteralPropertyName(propAtom.get(), pos()); - if (!propName) - return null(); - break; - } } + Node propName = propertyName(yieldHandling, propertyNameContext, maybeDecl, + propList, propAtom); + if (!propName) + return null(); + + // Grab the next token following the property/method name. + // (If this isn't a colon, we're going to either put it back or throw.) TokenKind tt; if (!tokenStream.getToken(&tt)) return null(); if (tt == TOK_COLON) { - if (isGenerator || isAsync) { + if (isGenerator || isAsync || isGetter || isSetter) { error(JSMSG_BAD_PROP_ID); return null(); } @@ -10717,7 +10719,7 @@ Parser::propertyName(YieldHandling yieldHandling, } if (propertyNameContext == PropertyNameInClass && (tt == TOK_SEMI || tt == TOK_ASSIGN)) { - if (isGenerator || isAsync) { + if (isGenerator || isAsync || isGetter || isSetter) { error(JSMSG_BAD_PROP_ID); return null(); } @@ -10729,7 +10731,7 @@ Parser::propertyName(YieldHandling yieldHandling, if (TokenKindIsPossibleIdentifierName(ltok) && (tt == TOK_COMMA || tt == TOK_RC || tt == TOK_ASSIGN)) { - if (isGenerator || isAsync) { + if (isGenerator || isAsync || isGetter || isSetter) { error(JSMSG_BAD_PROP_ID); return null(); } @@ -10748,6 +10750,10 @@ Parser::propertyName(YieldHandling yieldHandling, *propType = PropertyType::GeneratorMethod; else if (isAsync) *propType = PropertyType::AsyncMethod; + else if (isGetter) + *propType = PropertyType::Getter; + else if (isSetter) + *propType = PropertyType::Setter; else *propType = PropertyType::Method; return propName; @@ -10826,7 +10832,8 @@ Parser::objectLiteral(YieldHandling yieldHandling, PossibleError* TokenPos namePos = tokenStream.nextToken().pos; PropertyType propType; - Node propName = propertyName(yieldHandling, PropertyNameInLiteral, declKind, literal, &propType, &propAtom); + Node propName = propertyOrMethodName(yieldHandling, PropertyNameInLiteral, declKind, + literal, &propType, &propAtom); if (!propName) return null(); diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 5a0722afa7..f29d5aaeed 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -1629,7 +1629,11 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) Node propertyName(YieldHandling yieldHandling, PropertyNameContext propertyNameContext, const mozilla::Maybe& maybeDecl, ListNodeType propList, - PropertyType* propType, MutableHandleAtom propAtom); + MutableHandleAtom propAtom); + Node propertyOrMethodName(YieldHandling yieldHandling, + PropertyNameContext propertyNameContext, + const mozilla::Maybe& maybeDecl, ListNodeType propList, + PropertyType* propType, MutableHandleAtom propAtom); UnaryNodeType computedPropertyName(YieldHandling yieldHandling, const mozilla::Maybe& maybeDecl, ListNodeType literal); ListNodeType arrayInitializer(YieldHandling yieldHandling, PossibleError* possibleError); From 447261cf8746a4bb7440881716f465b0cadcaa93 Mon Sep 17 00:00:00 2001 From: Martok Date: Sun, 9 Apr 2023 04:03:27 +0200 Subject: [PATCH 09/23] Issue #2142 - Implement ASI for fields Based-on: m-c 1529772/{3,4} --- js/src/frontend/Parser.cpp | 36 ++++++++++++++++-------------------- js/src/js.msg | 1 - 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index aa0e005268..d184ae956a 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -7485,13 +7485,7 @@ Parser::classMember(YieldHandling yieldHandling, DefaultHandling d if (!initializer) return false; - if (!tokenStream.getToken(&tt)) { - return false; - } - - // TODO(khyperia): Implement ASI - if (tt != TOK_SEMI) { - error(JSMSG_MISSING_SEMI_FIELD); + if (!matchOrInsertSemicolonAfterExpression()) { return false; } @@ -10639,10 +10633,11 @@ Parser::propertyOrMethodName(YieldHandling yieldHandling, // In the last case, where there's not a `:` token to consume, we peek at // (but don't consume) the next token to decide how to set `*propType`. // - // `=` or `;` ==> PropertyType::Field (classes only) - // `=` ==> PropertyType::CoverInitializedName // `,` or `}` ==> PropertyType::Shorthand // `(` ==> PropertyType::Method + // `=`, not in a class ==> PropertyType::CoverInitializedName + // '=', in a class ==> PropertyType::Field + // any token, in a class ==> PropertyType::Field (ASI) // // The caller must check `*propType` and throw if whatever we parsed isn't // allowed here (for example, a getter in a destructuring pattern). @@ -10718,17 +10713,8 @@ Parser::propertyOrMethodName(YieldHandling yieldHandling, return propName; } - if (propertyNameContext == PropertyNameInClass && (tt == TOK_SEMI || tt == TOK_ASSIGN)) { - if (isGenerator || isAsync || isGetter || isSetter) { - error(JSMSG_BAD_PROP_ID); - return null(); - } - tokenStream.ungetToken(); - *propType = PropertyType::Field; - return propName; - } - - if (TokenKindIsPossibleIdentifierName(ltok) && + if (propertyNameContext != PropertyNameInClass && + TokenKindIsPossibleIdentifierName(ltok) && (tt == TOK_COMMA || tt == TOK_RC || tt == TOK_ASSIGN)) { if (isGenerator || isAsync || isGetter || isSetter) { @@ -10759,6 +10745,16 @@ Parser::propertyOrMethodName(YieldHandling yieldHandling, return propName; } + if (propertyNameContext == PropertyNameInClass) { + if (isGenerator || isAsync || isGetter || isSetter) { + error(JSMSG_BAD_PROP_ID); + return null(); + } + tokenStream.ungetToken(); + *propType = PropertyType::Field; + return propName; + } + error(JSMSG_COLON_AFTER_ID); return null(); } diff --git a/js/src/js.msg b/js/src/js.msg index 8e08e213db..2cae3ff125 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -362,7 +362,6 @@ 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 From 849ab4417c4f1a9f971f1f3d9fc072c8700cd71d Mon Sep 17 00:00:00 2001 From: Martok Date: Sun, 9 Apr 2023 04:42:04 +0200 Subject: [PATCH 10/23] Issue #2142 - Use JSOP_INITPROP for field initializers We don't want to call setters for field initialization. Based-on: m-c 1535471, 1547035 --- js/src/builtin/ReflectParse.cpp | 2 +- js/src/frontend/BytecodeEmitter.cpp | 50 +++++++++++++++++++++-------- js/src/frontend/BytecodeEmitter.h | 3 +- js/src/frontend/ElemOpEmitter.cpp | 19 ++++++----- js/src/frontend/ElemOpEmitter.h | 6 ++++ js/src/frontend/FoldConstants.cpp | 2 ++ js/src/frontend/ParseNode.cpp | 1 + js/src/frontend/ParseNode.h | 8 +++-- js/src/frontend/Parser.cpp | 12 +++---- js/src/frontend/PropOpEmitter.cpp | 20 ++++++------ js/src/frontend/PropOpEmitter.h | 6 ++++ 11 files changed, 89 insertions(+), 40 deletions(-) diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index 2902d2b724..242a4b8b0f 100644 --- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -2770,7 +2770,7 @@ ASTSerializer::classField(ClassField* classField, MutableHandleValue dst) ->head()->as() .scopeBody()->as() .head()->as() - .kid()->as() + .kid()->as() .right(); // RawUndefinedExpr is the node we use for "there is no initializer". If one // writes, literally, `x = undefined;`, it will not be a RawUndefinedExpr diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 02400ce2a3..5fb9ec7809 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -1214,6 +1214,10 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) return true; // Binary cases with obvious side effects. + case PNK_INITPROP: + *answer = true; + return true; + case PNK_ASSIGN: case PNK_ADDASSIGN: case PNK_SUBASSIGN: @@ -3697,9 +3701,14 @@ EmitAssignmentRhs(BytecodeEmitter* bce, ParseNode* rhs, uint8_t offset) } bool -BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs) +BytecodeEmitter::emitAssignmentOrInit(ParseNodeKind kind, JSOp compoundOp, + ParseNode* lhs, ParseNode* rhs) { bool isCompound = compoundOp != JSOP_NOP; + bool isInit = kind == PNK_INITPROP; + + MOZ_ASSERT_IF(isInit, lhs->isKind(PNK_DOT) || + lhs->isKind(PNK_ELEM)); // Name assignments are handled separately because choosing ops and when // to emit BINDNAME is involved and should avoid duplication. @@ -3754,7 +3763,8 @@ BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs) poe.emplace(this, isCompound ? PropOpEmitter::Kind::CompoundAssignment - : PropOpEmitter::Kind::SimpleAssignment, + : isInit ? PropOpEmitter::Kind::PropInit + : PropOpEmitter::Kind::SimpleAssignment, isSuper ? PropOpEmitter::ObjKind::Super : PropOpEmitter::ObjKind::Other); @@ -3781,7 +3791,8 @@ BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs) eoe.emplace(this, isCompound ? ElemOpEmitter::Kind::CompoundAssignment - : ElemOpEmitter::Kind::SimpleAssignment, + : isInit ? ElemOpEmitter::Kind::PropInit + : ElemOpEmitter::Kind::SimpleAssignment, isSuper ? ElemOpEmitter::ObjKind::Super : ElemOpEmitter::ObjKind::Other); @@ -4714,7 +4725,7 @@ BytecodeEmitter::emitInitializeForInOrOfTarget(TernaryNode* forHead) // initialization is just assigning the iteration value to a target // expression. if (!parser->handler.isDeclarationList(target)) - return emitAssignment(target, JSOP_NOP, nullptr); // ... ITERVAL + return emitAssignmentOrInit(PNK_ASSIGN, JSOP_NOP, target, nullptr); // ... ITERVAL // Otherwise, per-loop initialization is (possibly) declaration // initialization. If the declaration is a lexical declaration, it must be @@ -4728,8 +4739,19 @@ BytecodeEmitter::emitInitializeForInOrOfTarget(TernaryNode* forHead) MOZ_ASSERT(target->isForLoopDeclaration()); target = parser->handler.singleBindingFromDeclaration(&target->as()); + NameNode* nameNode = nullptr; if (target->isKind(PNK_NAME)) { - NameOpEmitter noe(this, target->name(), NameOpEmitter::Kind::Initialize); + nameNode = &target->as(); + } else if (target->isKind(PNK_ASSIGN) || + target->isKind(PNK_INITPROP)) { + BinaryNode* assignNode = &target->as(); + if (assignNode->left()->is()) { + nameNode = &assignNode->left()->as(); + } + } + + if (nameNode) { + NameOpEmitter noe(this, nameNode->name(), NameOpEmitter::Kind::Initialize); if (!noe.prepareForRhs()) { return false; } @@ -4755,7 +4777,7 @@ BytecodeEmitter::emitInitializeForInOrOfTarget(TernaryNode* forHead) return true; } - MOZ_ASSERT(!target->isKind(PNK_ASSIGN), + MOZ_ASSERT(!target->isKind(PNK_ASSIGN) && !target->isKind(PNK_INITPROP), "for-in/of loop destructuring declarations can't have initializers"); MOZ_ASSERT(target->isKind(PNK_ARRAY) || target->isKind(PNK_OBJECT)); @@ -5396,7 +5418,7 @@ BytecodeEmitter::emitComprehensionForOf(ForNode* forNode) // Notice: Comprehension for-of doesn't perform IteratorClose, since it's // not in the spec. - if (!emitAssignment(loopVariableName, JSOP_NOP, nullptr)) // ITER RESULT VALUE + if (!emitAssignmentOrInit(PNK_ASSIGN, JSOP_NOP, loopVariableName, nullptr)) // ITER RESULT VALUE return false; // Remove VALUE from the stack to release it. @@ -5533,7 +5555,7 @@ BytecodeEmitter::emitComprehensionForIn(ForNode* forNode) // Emit code to assign the enumeration value to the left hand side, but // also leave it on the stack. - if (!emitAssignment(forHead->kid2(), JSOP_NOP, nullptr)) + if (!emitAssignmentOrInit(PNK_ASSIGN, JSOP_NOP, forHead->kid2(), nullptr)) return false; /* The stack should be balanced around the assignment opcode sequence. */ @@ -8384,9 +8406,9 @@ BytecodeEmitter::emitFunctionFormalParameters(ListNode* paramsBody) for (ParseNode* arg = paramsBody->head(); arg != funBody; arg = arg->pn_next) { ParseNode* bindingElement = arg; ParseNode* initializer = nullptr; - if (arg->isKind(PNK_ASSIGN)) { - bindingElement = arg->as().left(); - initializer = arg->as().right(); + if (arg->isKind(PNK_ASSIGN) || arg->isKind(PNK_INITPROP)) { + bindingElement = arg->as().left(); + initializer = arg->as().right(); } bool hasInitializer = !!initializer; bool isRest = hasRest && arg->pn_next == funBody; @@ -8830,6 +8852,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage:: return false; break; + case PNK_INITPROP: case PNK_ASSIGN: case PNK_ADDASSIGN: case PNK_SUBASSIGN: @@ -8843,8 +8866,9 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage:: case PNK_DIVASSIGN: case PNK_MODASSIGN: case PNK_POWASSIGN: { - AssignmentNode* assignNode = &pn->as(); - if (!emitAssignment(assignNode->left(), assignNode->getOp(), assignNode->right())) + BinaryNode* assignNode = &pn->as(); + if (!emitAssignmentOrInit(assignNode->getKind(), assignNode->getOp(), + assignNode->left(), assignNode->right())) return false; break; } diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 8a196a4799..88594778b6 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -696,7 +696,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitCallSiteObject(CallSiteNode* callSiteObj); MOZ_MUST_USE bool emitTemplateString(ListNode* templateString); - MOZ_MUST_USE bool emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs); + MOZ_MUST_USE bool emitAssignmentOrInit(ParseNodeKind kind, JSOp compoundOp, + ParseNode* lhs, ParseNode* rhs); MOZ_MUST_USE bool emitReturn(UnaryNode* returnNode); MOZ_MUST_USE bool emitStatement(UnaryNode* exprStmt); diff --git a/js/src/frontend/ElemOpEmitter.cpp b/js/src/frontend/ElemOpEmitter.cpp index 2644072337..4bd8684ab2 100644 --- a/js/src/frontend/ElemOpEmitter.cpp +++ b/js/src/frontend/ElemOpEmitter.cpp @@ -132,11 +132,11 @@ ElemOpEmitter::emitGet() bool ElemOpEmitter::prepareForRhs() { - MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment()); - MOZ_ASSERT_IF(isSimpleAssignment(), state_ == State::Key); + MOZ_ASSERT(isSimpleAssignment() || isPropInit()|| isCompoundAssignment()); + MOZ_ASSERT_IF(isSimpleAssignment() || isPropInit(), state_ == State::Key); MOZ_ASSERT_IF(isCompoundAssignment(), state_ == State::Get); - if (isSimpleAssignment()) { + if (isSimpleAssignment() || isPropInit()) { // For CompoundAssignment, SUPERBASE is already emitted by emitGet. if (isSuper()) { if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS KEY SUPERBASE @@ -155,7 +155,7 @@ bool ElemOpEmitter::skipObjAndKeyAndRhs() { MOZ_ASSERT(state_ == State::Start); - MOZ_ASSERT(isSimpleAssignment()); + MOZ_ASSERT(isSimpleAssignment() || isPropInit()); #ifdef DEBUG state_ = State::Rhs; @@ -203,12 +203,15 @@ ElemOpEmitter::emitDelete() bool ElemOpEmitter::emitAssignment() { - MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment()); + MOZ_ASSERT(isSimpleAssignment() || isPropInit() || isCompoundAssignment()); MOZ_ASSERT(state_ == State::Rhs); - JSOp setOp = isSuper() - ? bce_->sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER - : bce_->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM; + MOZ_ASSERT_IF(isPropInit(), !isSuper()); + + JSOp setOp = isPropInit() ? JSOP_INITELEM + : isSuper() + ? bce_->sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER + : bce_->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM; if (!bce_->emitElemOpBase(setOp)) { // ELEM return false; } diff --git a/js/src/frontend/ElemOpEmitter.h b/js/src/frontend/ElemOpEmitter.h index 48b3eaa43c..5eccb920e7 100644 --- a/js/src/frontend/ElemOpEmitter.h +++ b/js/src/frontend/ElemOpEmitter.h @@ -129,6 +129,7 @@ class MOZ_STACK_CLASS ElemOpEmitter PostDecrement, PreDecrement, SimpleAssignment, + PropInit, CompoundAssignment }; enum class ObjKind { @@ -176,6 +177,7 @@ class MOZ_STACK_CLASS ElemOpEmitter // | +--------+ | // | +-------------------+ // | [SimpleAssignment] | + // | [PropInit] | // | prepareForRhs v +-----+ // +--------------------->+-------------->+->| Rhs |-+ // | ^ +-----+ | @@ -225,6 +227,10 @@ class MOZ_STACK_CLASS ElemOpEmitter return kind_ == Kind::SimpleAssignment; } + MOZ_MUST_USE bool isPropInit() const { + return kind_ == Kind::PropInit; + } + MOZ_MUST_USE bool isDelete() const { return kind_ == Kind::Delete; } diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index eda40a0836..bb8f0d11b6 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -351,6 +351,7 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result) case PNK_DIV: case PNK_MOD: case PNK_POW: + case PNK_INITPROP: case PNK_ASSIGN: case PNK_ADDASSIGN: case PNK_SUBASSIGN: @@ -1877,6 +1878,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& parser, bo case PNK_SWITCH: case PNK_COLON: + case PNK_INITPROP: case PNK_ASSIGN: case PNK_ADDASSIGN: case PNK_SUBASSIGN: diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index 00bbd7afee..de3ea60931 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -257,6 +257,7 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) // Binary nodes with two non-null children. // All assignment and compound assignment nodes qualify. + case PNK_INITPROP: case PNK_ASSIGN: case PNK_ADDASSIGN: case PNK_SUBASSIGN: diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index da722a51e7..5919cfa735 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -125,6 +125,7 @@ class ObjectBox; F(SUPERBASE) \ F(SUPERCALL) \ F(SETTHIS) \ + F(INITPROP) \ F(IMPORT_META) \ F(CALL_IMPORT) \ \ @@ -170,7 +171,7 @@ class ObjectBox; F(POW) \ \ /* Assignment operators (= += -= etc.). */ \ - /* ParseNode::isAssignment assumes all these are consecutive. */ \ + /* AssignmentNode::test assumes all these are consecutive. */ \ F(ASSIGN) \ F(ADDASSIGN) \ F(SUBASSIGN) \ @@ -385,7 +386,10 @@ IsTypeofKind(ParseNodeKind kind) * PNK_COMMA (ListNode) * head: list of N comma-separated exprs * count: N >= 2 - * PNK_ASSIGN (BinaryNode) + * PNK_INITPROP (BinaryNode) + * left: target of assignment, base-class setter will not be invoked + * right: value to assign + * PNK_ASSIGN (AssignmentNode) * left: target of assignment * right: value to assign * PNK_ADDASSIGN, PNK_SUBASSIGN, PNK_BITORASSIGN, PNK_BITXORASSIGN, diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index d184ae956a..bd7dc40bf7 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -8048,18 +8048,18 @@ Parser::fieldInitializerOpt(YieldHandling yieldHandling, bool hasH return null(); } - // Synthesize an assignment expression for the property. - AssignmentNodeType initializerAssignment = handler.newAssignment(PNK_ASSIGN, - propAssignFieldAccess, initializerExpr, - JSOP_NOP); - if (!initializerAssignment) + // Synthesize a property init. + AssignmentNodeType initializerPropInit = handler.newAssignment(PNK_INITPROP, + propAssignFieldAccess, initializerExpr, + JSOP_NOP); + if (!initializerPropInit) return null(); bool canSkipLazyClosedOverBindings = handler.canSkipLazyClosedOverBindings(); if (!declareFunctionThis(canSkipLazyClosedOverBindings)) return null(); - UnaryNodeType exprStatement = handler.newExprStatement(initializerAssignment, wholeInitializerPos.end); + UnaryNodeType exprStatement = handler.newExprStatement(initializerPropInit, wholeInitializerPos.end); if (!exprStatement) return null(); diff --git a/js/src/frontend/PropOpEmitter.cpp b/js/src/frontend/PropOpEmitter.cpp index fd00024e7c..7be5f17230 100644 --- a/js/src/frontend/PropOpEmitter.cpp +++ b/js/src/frontend/PropOpEmitter.cpp @@ -110,11 +110,11 @@ PropOpEmitter::emitGet(JSAtom* prop) bool PropOpEmitter::prepareForRhs() { - MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment()); - MOZ_ASSERT_IF(isSimpleAssignment(), state_ == State::Obj); + MOZ_ASSERT(isSimpleAssignment() || isPropInit() || isCompoundAssignment()); + MOZ_ASSERT_IF(isSimpleAssignment() || isPropInit(), state_ == State::Obj); MOZ_ASSERT_IF(isCompoundAssignment(), state_ == State::Get); - if (isSimpleAssignment()) { + if (isSimpleAssignment() || isPropInit()) { // For CompoundAssignment, SUPERBASE is already emitted by emitGet. if (isSuper()) { if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS SUPERBASE @@ -133,7 +133,7 @@ bool PropOpEmitter::skipObjAndRhs() { MOZ_ASSERT(state_ == State::Start); - MOZ_ASSERT(isSimpleAssignment()); + MOZ_ASSERT(isSimpleAssignment() || isPropInit()); #ifdef DEBUG state_ = State::Rhs; @@ -182,18 +182,20 @@ PropOpEmitter::emitDelete(JSAtom* prop) bool PropOpEmitter::emitAssignment(JSAtom* prop) { - MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment()); + MOZ_ASSERT(isSimpleAssignment() || isPropInit() || isCompoundAssignment()); MOZ_ASSERT(state_ == State::Rhs); - if (isSimpleAssignment()) { + if (isSimpleAssignment() || isPropInit()) { if (!prepareAtomIndex(prop)) { return false; } } - JSOp setOp = isSuper() - ? bce_->sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER - : bce_->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP; + MOZ_ASSERT_IF(isPropInit(), !isSuper()); + JSOp setOp = isPropInit() ? JSOP_INITPROP + : isSuper() + ? bce_->sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER + : bce_->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP; if (!bce_->emitAtomOp(propAtomIndex_, setOp)) { // VAL return false; } diff --git a/js/src/frontend/PropOpEmitter.h b/js/src/frontend/PropOpEmitter.h index 2c5a26ec45..2e6d6e5089 100644 --- a/js/src/frontend/PropOpEmitter.h +++ b/js/src/frontend/PropOpEmitter.h @@ -115,6 +115,7 @@ class MOZ_STACK_CLASS PropOpEmitter PostDecrement, PreDecrement, SimpleAssignment, + PropInit, CompoundAssignment }; enum class ObjKind { @@ -167,6 +168,7 @@ class MOZ_STACK_CLASS PropOpEmitter // | +--------+ | // | | // | [SimpleAssignment] | + // | [PropInit] | // | prepareForRhs | +-----+ // +--------------------->+-------------->+->| Rhs |-+ // | ^ +-----+ | @@ -217,6 +219,10 @@ class MOZ_STACK_CLASS PropOpEmitter return kind_ == Kind::SimpleAssignment; } + MOZ_MUST_USE bool isPropInit() const { + return kind_ == Kind::PropInit; + } + MOZ_MUST_USE bool isDelete() const { return kind_ == Kind::Delete; } From 8c6750014cd51b49ed80c2de0c3f2f08772d3966 Mon Sep 17 00:00:00 2001 From: Martok Date: Sun, 9 Apr 2023 19:56:58 +0200 Subject: [PATCH 11/23] Issue #2142 - Restrict contents of direct eval in fields Based-on: m-c 1542406 --- js/src/frontend/Parser.cpp | 24 ++++++++++++++++++------ js/src/frontend/Parser.h | 5 +++-- js/src/vm/Scope.cpp | 15 +++++++++++---- js/src/vm/Scope.h | 12 ++++++++++-- 4 files changed, 42 insertions(+), 14 deletions(-) diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index bd7dc40bf7..bc60503b25 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -214,12 +214,18 @@ SharedContext::computeAllowSyntax(Scope* scope) { for (ScopeIter si(scope); si; si++) { if (si.kind() == ScopeKind::Function) { - JSFunction* fun = si.scope()->as().canonicalFunction(); + FunctionScope* funScope = &si.scope()->as(); + JSFunction* fun = funScope->canonicalFunction(); if (fun->isArrow()) continue; allowNewTarget_ = true; allowSuperProperty_ = fun->allowSuperProperty(); allowSuperCall_ = fun->isDerivedClassConstructor(); + if (funScope->isFieldInitializer()) { + allowSuperProperty_ = false; + allowSuperCall_ = false; + allowArguments_ = false; + } return; } } @@ -1881,7 +1887,8 @@ Parser::newEvalScopeData(ParseContext::Scope& scope) template <> Maybe -Parser::newFunctionScopeData(ParseContext::Scope& scope, bool hasParameterExprs) +Parser::newFunctionScopeData(ParseContext::Scope& scope, bool hasParameterExprs, + bool isFieldInitializer) { Vector positionalFormals(context); Vector formals(context); @@ -1955,6 +1962,8 @@ Parser::newFunctionScopeData(ParseContext::Scope& scope, bool if (!bindings) return Nothing(); + bindings->isFieldInitializer = isFieldInitializer; + // The ordering here is important. See comments in FunctionScope. BindingName* start = bindings->trailingNames.start(); BindingName* cursor = start; @@ -2403,7 +2412,8 @@ Parser::finishFunctionScopes(bool isStandaloneFunction) template <> bool -Parser::finishFunction(bool isStandaloneFunction /* = false */) +Parser::finishFunction(bool isStandaloneFunction /* = false */, + bool isFieldInitializer /* = false */) { if (!finishFunctionScopes(isStandaloneFunction)) return false; @@ -2420,7 +2430,8 @@ Parser::finishFunction(bool isStandaloneFunction /* = false */ { Maybe bindings = newFunctionScopeData(pc->functionScope(), - hasParameterExprs); + hasParameterExprs, + isFieldInitializer); if (!bindings) return false; funbox->functionScopeBindings().set(*bindings); @@ -2438,7 +2449,8 @@ Parser::finishFunction(bool isStandaloneFunction /* = false */ template <> bool -Parser::finishFunction(bool isStandaloneFunction /* = false */) +Parser::finishFunction(bool isStandaloneFunction /* = false */, + bool isFieldInitializer /* = false */) { // The LazyScript for a lazily parsed function needs to know its set of // free variables and inner functions so that when it is fully parsed, we @@ -8076,7 +8088,7 @@ Parser::fieldInitializerOpt(YieldHandling yieldHandling, bool hasH handler.setFunctionBody(funNode, initializerBody); - if (!finishFunction()) + if (!finishFunction(false, true)) return null(); if (!leaveInnerFunction(outerpc)) diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index f29d5aaeed..5222d6398c 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -1572,7 +1572,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) bool tryAnnexB, Directives inheritedDirectives, Directives* newDirectives); bool finishFunctionScopes(bool isStandaloneFunction); - bool finishFunction(bool isStandaloneFunction = false); + bool finishFunction(bool isStandaloneFunction = false, bool isFieldInitializer = false); bool leaveInnerFunction(ParseContext* outerpc); bool matchOrInsertSemicolonHelper(TokenStream::Modifier modifier); @@ -1620,7 +1620,8 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) mozilla::Maybe newModuleScopeData(ParseContext::Scope& scope); mozilla::Maybe newEvalScopeData(ParseContext::Scope& scope); mozilla::Maybe newFunctionScopeData(ParseContext::Scope& scope, - bool hasParameterExprs); + bool hasParameterExprs, + bool isFieldInitializer); mozilla::Maybe newVarScopeData(ParseContext::Scope& scope); mozilla::Maybe newLexicalScopeData(ParseContext::Scope& scope); LexicalScopeNodeType finishLexicalScope(ParseContext::Scope& scope, Node body); diff --git a/js/src/vm/Scope.cpp b/js/src/vm/Scope.cpp index 5cb9abaecf..870add219f 100644 --- a/js/src/vm/Scope.cpp +++ b/js/src/vm/Scope.cpp @@ -610,12 +610,14 @@ FunctionScope::create(ExclusiveContext* cx, Handle dataArg, if (!data) return nullptr; - return createWithData(cx, &data, hasParameterExprs, needsEnvironment, fun, enclosing); + return createWithData(cx, &data, hasParameterExprs, dataArg ? dataArg->isFieldInitializer : false, + needsEnvironment, fun, enclosing); } /* static */ FunctionScope* FunctionScope::createWithData(ExclusiveContext* cx, MutableHandle> data, - bool hasParameterExprs, bool needsEnvironment, + bool hasParameterExprs, bool isFieldInitializer, + bool needsEnvironment, HandleFunction fun, HandleScope enclosing) { MOZ_ASSERT(data); @@ -636,6 +638,7 @@ FunctionScope::createWithData(ExclusiveContext* cx, MutableHandleisFieldInitializer = isFieldInitializer; data->hasParameterExprs = hasParameterExprs; data->canonicalFunction.init(fun); @@ -737,16 +740,20 @@ FunctionScope::XDR(XDRState* xdr, HandleFunction fun, HandleScope enclosin uint8_t needsEnvironment; uint8_t hasParameterExprs; + uint8_t isFieldInitializer; uint32_t nextFrameSlot; if (mode == XDR_ENCODE) { needsEnvironment = scope->hasEnvironment(); hasParameterExprs = data->hasParameterExprs; + isFieldInitializer = data->isFieldInitializer; nextFrameSlot = data->nextFrameSlot; } if (!xdr->codeUint8(&needsEnvironment)) return false; if (!xdr->codeUint8(&hasParameterExprs)) return false; + if (!xdr->codeUint8(&isFieldInitializer)) + return false; if (!xdr->codeUint16(&data->nonPositionalFormalStart)) return false; if (!xdr->codeUint16(&data->varStart)) @@ -761,8 +768,8 @@ FunctionScope::XDR(XDRState* xdr, HandleFunction fun, HandleScope enclosin MOZ_ASSERT(!data->nextFrameSlot); } - scope.set(createWithData(cx, &uniqueData.ref(), hasParameterExprs, needsEnvironment, fun, - enclosing)); + scope.set(createWithData(cx, &uniqueData.ref(), hasParameterExprs, !!isFieldInitializer, + needsEnvironment, fun, enclosing)); if (!scope) return false; diff --git a/js/src/vm/Scope.h b/js/src/vm/Scope.h index fc1419bb89..a1e52e3800 100644 --- a/js/src/vm/Scope.h +++ b/js/src/vm/Scope.h @@ -500,6 +500,9 @@ class FunctionScope : public Scope // bindings. bool hasParameterExprs = false; + // Anonymous functions used in field initializers are limited. + bool isFieldInitializer = false; + // Bindings are sorted by kind in both frames and environments. // // Positional formal parameter names are those that are not @@ -548,8 +551,9 @@ class FunctionScope : public Scope private: static FunctionScope* createWithData(ExclusiveContext* cx, MutableHandle> data, - bool hasParameterExprs, bool needsEnvironment, - HandleFunction fun, HandleScope enclosing); + bool hasParameterExprs, bool isFieldInitializer, + bool needsEnvironment, HandleFunction fun, + HandleScope enclosing); Data& data() { return *reinterpret_cast(data_); @@ -574,6 +578,10 @@ class FunctionScope : public Scope return data().hasParameterExprs; } + bool isFieldInitializer() const { + return data().isFieldInitializer; + } + uint32_t numPositionalFormalParameters() const { return data().nonPositionalFormalStart; } From bcb6203e4fe6b33580f1c4fd394032e771703162 Mon Sep 17 00:00:00 2001 From: Martok Date: Sun, 9 Apr 2023 19:54:45 +0200 Subject: [PATCH 12/23] Issue #2142 - Fix several scoping issues in field initializers Based-on: m-c 1540789, 1547130, 1547467 --- js/src/builtin/ReflectParse.cpp | 2 + js/src/frontend/BytecodeEmitter.cpp | 112 +++++++++++------------ js/src/frontend/BytecodeEmitter.h | 3 +- js/src/frontend/FullParseHandler.h | 32 ++++--- js/src/frontend/FunctionEmitter.cpp | 9 +- js/src/frontend/ObjectEmitter.cpp | 14 +-- js/src/frontend/ObjectEmitter.h | 16 ++-- js/src/frontend/ParseNode.h | 10 +-- js/src/frontend/Parser.cpp | 128 +++++++++++++-------------- js/src/frontend/SyntaxParseHandler.h | 5 +- js/src/vm/CommonPropertyNames.h | 1 - 11 files changed, 156 insertions(+), 176 deletions(-) diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index 242a4b8b0f..2b885574da 100644 --- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -2701,6 +2701,8 @@ ASTSerializer::statement(ParseNode* pn, MutableHandleValue dst) return false; for (ParseNode* item : memberList->contents()) { + if (item->is()) + item = item->as().scopeBody(); if (item->is()) { ClassField* field = &item->as(); MOZ_ASSERT(memberList->pn_pos.encloses(field->pn_pos)); diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 5fb9ec7809..9932a63b58 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -2324,7 +2324,7 @@ BytecodeEmitter::emitSetThis(BinaryNode* setThisNode) return false; } - if (!emitInitializeInstanceFields(true)) { + if (!emitInitializeInstanceFields()) { return false; } @@ -7623,6 +7623,13 @@ BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListTy continue; } + if (propdef->is()) { + // Constructors are sometimes wrapped in LexicalScopeNodes. As we already + // handled emitting the constructor, skip it. + MOZ_ASSERT(propdef->as().scopeBody()->isKind(PNK_CLASSMETHOD)); + continue; + } + // Handle __proto__: v specially because *only* this form, and no other // involving "__proto__", performs [[Prototype]] mutation. if (propdef->isKind(PNK_MUTATEPROTO)) { @@ -7854,14 +7861,7 @@ BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListTy } } - if (obj->getKind() == PNK_CLASSMEMBERLIST) { - if (!emitCreateFieldKeys(obj)) - return false; - if (!emitCreateFieldInitializers(obj)) - return false; - } - - return true; + return true; } FieldInitializers @@ -8063,38 +8063,7 @@ BytecodeEmitter::findFieldInitializersForCall() } bool -BytecodeEmitter::emitCopyInitializersToLocalInitializers() -{ - MOZ_ASSERT(sc->asFunctionBox()->isDerivedClassConstructor()); - if (getFieldInitializers().numFieldInitializers == 0) - return true; - - NameOpEmitter noe(this, cx->names().dotLocalInitializers, NameOpEmitter::Kind::Initialize); - if (!noe.prepareForRhs()) { - // [stack] - return false; - } - - if (!emitGetName(cx->names().dotInitializers)) { - // [stack] .initializers - return false; - } - - if (!noe.emitAssignment()) { - // [stack] .initializers - return false; - } - - if (!emit1(JSOP_POP)) { - // [stack] - return false; - } - - return true; -} - -bool -BytecodeEmitter::emitInitializeInstanceFields(bool isSuperCall) +BytecodeEmitter::emitInitializeInstanceFields() { const FieldInitializers& fieldInitializers = findFieldInitializersForCall(); size_t numFields = fieldInitializers.numFieldInitializers; @@ -8103,16 +8072,9 @@ BytecodeEmitter::emitInitializeInstanceFields(bool isSuperCall) return true; } - if (isSuperCall) { - if (!emitGetName(cx->names().dotLocalInitializers)) { - // [stack] ARRAY - return false; - } - } else { - if (!emitGetName(cx->names().dotInitializers)) { - // [stack] ARRAY - return false; - } + if (!emitGetName(cx->names().dotInitializers)) { + // [stack] ARRAY + return false; } for (size_t fieldIndex = 0; fieldIndex < numFields; fieldIndex++) { @@ -8625,16 +8587,19 @@ BytecodeEmitter::emitClass(ClassNode* classNode) ParseNode* heritageExpression = classNode->heritage(); ListNode* classMembers = classNode->memberList(); - FunctionNode* constructor = nullptr; - for (ParseNode* mn : classMembers->contents()) { - if (mn->is()) { - ClassMethod& method = mn->as(); + ParseNode* constructor = nullptr; + for (ParseNode* classElement : classMembers->contents()) { + ParseNode* unwrappedElement = classElement; + if (unwrappedElement->is()) + unwrappedElement = unwrappedElement->as().scopeBody(); + if (unwrappedElement->is()) { + ClassMethod& method = unwrappedElement->as(); ParseNode& methodName = method.name(); if (!method.isStatic() && (methodName.isKind(PNK_OBJECT_PROPERTY_NAME) || methodName.isKind(PNK_STRING)) && methodName.as().atom() == cx->names().constructor) { - constructor = &method.method(); + constructor = classElement; break; } } @@ -8656,8 +8621,8 @@ BytecodeEmitter::emitClass(ClassNode* classNode) } } - if (!classNode->isEmptyScope()) { - if (!ce.emitScope(classNode->scopeBindings(), classNode->names() != nullptr)) { + if (LexicalScopeNode* scopeBindings = classNode->scopeBindings()) { + if (!ce.emitScope(scopeBindings->scopeBindings())) { // [stack] return false; } @@ -8685,12 +8650,35 @@ BytecodeEmitter::emitClass(ClassNode* classNode) } if (constructor) { - bool needsHomeObject = constructor->funbox()->needsHomeObject(); + FunctionNode* ctor; + // .fieldKeys must be declared outside the scope .initializers is declared + // in, hence this extra scope. + Maybe lse; + if (constructor->is()) { + lse.emplace(this); + if (!lse->emitScope(ScopeKind::Lexical, constructor->as().scopeBindings())) + return false; + + // Any class with field initializers will have a constructor + if (!emitCreateFieldInitializers(classMembers)) + return false; + ctor = &constructor->as().scopeBody()->as().method(); + } else { + ctor = &constructor->as().method(); + } + + bool needsHomeObject = ctor->funbox()->needsHomeObject(); // HERITAGE is consumed inside emitFunction. - if (!emitFunction(constructor, isDerived, classMembers)) { + if (!emitFunction(ctor, isDerived, classMembers)) { // [stack] HOMEOBJ CTOR return false; } + if (lse.isSome()) { + if (!lse->emitEnd()) { + return false; + } + lse.reset(); + } if (!ce.emitInitConstructor(needsHomeObject)) { // [stack] CTOR HOMEOBJ return false; @@ -8706,6 +8694,10 @@ BytecodeEmitter::emitClass(ClassNode* classNode) // [stack] CTOR HOMEOBJ return false; } + + if (!emitCreateFieldKeys(classMembers)) + return false; + if (!ce.emitEnd(kind)) { // [stack] # class declaration // [stack] diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 88594778b6..95e28f213a 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -533,8 +533,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitCreateFieldKeys(ListNode* obj); MOZ_MUST_USE bool emitCreateFieldInitializers(ListNode* obj); const FieldInitializers& findFieldInitializersForCall(); - MOZ_MUST_USE bool emitCopyInitializersToLocalInitializers(); - MOZ_MUST_USE bool emitInitializeInstanceFields(bool isSuperCall); + MOZ_MUST_USE bool emitInitializeInstanceFields(); // To catch accidental misuse, emitUint16Operand/emit3 assert that they are // not used to unconditionally emit JSOP_GETLOCAL. Variable access should diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index d8ac80e2c6..345fd483a8 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -459,31 +459,35 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) return true; } - MOZ_MUST_USE bool addClassMethodDefinition(ListNodeType memberList, Node key, FunctionNodeType funNode, - JSOp op, bool isStatic) + MOZ_MUST_USE ClassMethod* newClassMethodDefinition(Node key, FunctionNodeType funNode, + JSOp op, bool isStatic) { - MOZ_ASSERT(memberList->isKind(PNK_CLASSMEMBERLIST)); MOZ_ASSERT(isUsableAsObjectPropertyName(key)); - ClassMethod* classMethod = new_(key, funNode, op, isStatic); - if (!classMethod) - return false; - memberList->append(classMethod); - return true; + return new_(key, funNode, op, isStatic); } - MOZ_MUST_USE bool addClassFieldDefinition(ListNodeType memberList, Node name, FunctionNodeType initializer) + MOZ_MUST_USE ClassField* newClassFieldDefinition(Node name, FunctionNodeType initializer) { - MOZ_ASSERT(memberList->isKind(PNK_CLASSMEMBERLIST)); MOZ_ASSERT(isUsableAsObjectPropertyName(name)); - ParseNode* classField = new_(name, initializer); - if (!classField) - return false; - memberList->append(classField); + return new_(name, initializer); + } + + MOZ_MUST_USE bool addClassMemberDefinition(ListNodeType memberList, Node member) + { + MOZ_ASSERT(memberList->isKind(PNK_CLASSMEMBERLIST)); + // Constructors can be surrounded by LexicalScopes. + MOZ_ASSERT(member->isKind(PNK_CLASSMETHOD) || + member->isKind(PNK_CLASSFIELD) || + (member->isKind(PNK_LEXICALSCOPE) && + member->as().scopeBody()->isKind(PNK_CLASSMETHOD))); + + addList(/* list = */ memberList, /* kid = */ member); return true; } + UnaryNodeType newInitialYieldExpression(uint32_t begin, Node gen) { TokenPos pos(begin, begin + 1); return new_(PNK_INITIALYIELD, JSOP_INITIALYIELD, pos, gen); diff --git a/js/src/frontend/FunctionEmitter.cpp b/js/src/frontend/FunctionEmitter.cpp index 98dffe6b04..16c0e1fb06 100644 --- a/js/src/frontend/FunctionEmitter.cpp +++ b/js/src/frontend/FunctionEmitter.cpp @@ -477,13 +477,8 @@ bool FunctionScriptEmitter::prepareForBody() } if (funbox_->function()->kind() == JSFunction::FunctionKind::ClassConstructor) { - if (funbox_->isDerivedClassConstructor()) { - if (!bce_->emitCopyInitializersToLocalInitializers()) { - // [stack] - return false; - } - } else { - if (!bce_->emitInitializeInstanceFields(false)) { + if (!funbox_->isDerivedClassConstructor()) { + if (!bce_->emitInitializeInstanceFields()) { // [stack] return false; } diff --git a/js/src/frontend/ObjectEmitter.cpp b/js/src/frontend/ObjectEmitter.cpp index f421030e99..37c03935ab 100644 --- a/js/src/frontend/ObjectEmitter.cpp +++ b/js/src/frontend/ObjectEmitter.cpp @@ -531,13 +531,12 @@ ClassEmitter::ClassEmitter(BytecodeEmitter* bce) isClass_ = true; } -bool ClassEmitter::emitScope(JS::Handle scopeBindings, bool hasName) +bool ClassEmitter::emitScope(JS::Handle scopeBindings) { MOZ_ASSERT(propertyState_ == PropertyState::Start); MOZ_ASSERT(classState_ == ClassState::Start); - if (hasName) - tdzCacheForInnerName_.emplace(bce_); + tdzCache_.emplace(bce_); innerScope_.emplace(bce_); if (!innerScope_->enterLexical(bce_, ScopeKind::Lexical, scopeBindings)) @@ -717,7 +716,7 @@ bool ClassEmitter::emitEnd(Kind kind) } if (name_ != bce_->cx->names().empty) { - MOZ_ASSERT(tdzCacheForInnerName_.isSome()); + MOZ_ASSERT(tdzCache_.isSome()); MOZ_ASSERT(innerScope_.isSome()); if (!bce_->emitLexicalInitialization(name_)) { @@ -743,20 +742,21 @@ bool ClassEmitter::emitEnd(Kind kind) } } - tdzCacheForInnerName_.reset(); + tdzCache_.reset(); } else if (innerScope_.isSome()) { // [stack] CTOR MOZ_ASSERT(kind == Kind::Expression); - MOZ_ASSERT(tdzCacheForInnerName_.isNothing()); + MOZ_ASSERT(tdzCache_.isSome()); if (!innerScope_->leave(bce_)) return false; innerScope_.reset(); + tdzCache_.reset(); }else { // [stack] CTOR MOZ_ASSERT(kind == Kind::Expression); - MOZ_ASSERT(tdzCacheForInnerName_.isNothing()); + MOZ_ASSERT(tdzCache_.isNothing()); } // [stack] # class declaration diff --git a/js/src/frontend/ObjectEmitter.h b/js/src/frontend/ObjectEmitter.h index 952d4cf156..dd983a5b72 100644 --- a/js/src/frontend/ObjectEmitter.h +++ b/js/src/frontend/ObjectEmitter.h @@ -464,7 +464,7 @@ class MOZ_RAII AutoSaveLocalStrictMode // // `class {}` // ClassEmitter ce(this); -// ce.emitScope(scopeBindings, false); +// ce.emitScope(scopeBindings); // ce.emitClass(); // // ce.emitInitDefaultConstructor(Some(offset_of_class), @@ -474,7 +474,7 @@ class MOZ_RAII AutoSaveLocalStrictMode // // `class { constructor() { ... } }` // ClassEmitter ce(this); -// ce.emitScope(scopeBindings, false); +// ce.emitScope(scopeBindings); // ce.emitClass(); // // emit(function_for_constructor); @@ -484,7 +484,7 @@ class MOZ_RAII AutoSaveLocalStrictMode // // `class X { constructor() { ... } }` // ClassEmitter ce(this); -// ce.emitScope(scopeBindings, true); +// ce.emitScope(scopeBindings); // ce.emitClass(atom_of_X); // // ce.emitInitDefaultConstructor(Some(offset_of_class), @@ -494,7 +494,7 @@ class MOZ_RAII AutoSaveLocalStrictMode // // `class X { constructor() { ... } }` // ClassEmitter ce(this); -// ce.emitScope(scopeBindings, true); +// ce.emitScope(scopeBindings); // ce.emitClass(atom_of_X); // // emit(function_for_constructor); @@ -504,7 +504,7 @@ class MOZ_RAII AutoSaveLocalStrictMode // // `class X extends Y { constructor() { ... } }` // ClassEmitter ce(this); -// ce.emitScope(scopeBindings, true); +// ce.emitScope(scopeBindings); // // emit(Y); // ce.emitDerivedClass(atom_of_X); @@ -516,7 +516,7 @@ class MOZ_RAII AutoSaveLocalStrictMode // // `class X extends Y { constructor() { ... super.f(); ... } }` // ClassEmitter ce(this); -// ce.emitScope(scopeBindings, true); +// ce.emitScope(scopeBindings); // // emit(Y); // ce.emitDerivedClass(atom_of_X); @@ -630,7 +630,7 @@ class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter bool isDerived_ = false; - mozilla::Maybe tdzCacheForInnerName_; + mozilla::Maybe tdzCache_; mozilla::Maybe innerScope_; AutoSaveLocalStrictMode strictMode_; @@ -691,7 +691,7 @@ class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter public: explicit ClassEmitter(BytecodeEmitter* bce); - MOZ_MUST_USE bool emitScope(JS::Handle scopeBindings, bool hasName); + MOZ_MUST_USE bool emitScope(JS::Handle scopeBindings); // @param name // Name of the class (nullptr if this is anonymous class) diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 5919cfa735..9a91ee9b3e 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -2268,13 +2268,9 @@ class ClassNode : public TernaryNode MOZ_ASSERT(list->isKind(PNK_CLASSMEMBERLIST)); return list; } - bool isEmptyScope() const { - ParseNode* scope = kid3(); - return scope->as().isEmptyScope(); - } - Handle scopeBindings() const { - ParseNode* scope = kid3(); - return scope->as().scopeBindings(); + LexicalScopeNode* scopeBindings() const { + LexicalScopeNode* scope = &kid3()->as(); + return scope->isEmptyScope() ? nullptr : scope; } }; diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index bc60503b25..e5daabc8d5 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -2791,12 +2791,6 @@ Parser::functionBody(InHandling inHandling, YieldHandling yieldHan return null(); } - if (kind == FunctionSyntaxKind::DerivedClassConstructor) { - if (!noteDeclaredName(context->names().dotLocalInitializers, - DeclarationKind::Var, pos())) - return null(); - } - return finishLexicalScope(pc->varScope(), body); } @@ -3689,6 +3683,12 @@ Parser::functionFormalParametersAndBody(InHandling inHandling, FunctionBox* funbox = pc->functionBox(); RootedFunction fun(context, funbox->function()); + if (kind == FunctionSyntaxKind::ClassConstructor || + kind == FunctionSyntaxKind::DerivedClassConstructor) { + if (!noteUsedName(context->names().dotInitializers)) + return false; + } + // See below for an explanation why arrow function parameters and arrow // function bodies are parsed with different yield/await settings. { @@ -7501,7 +7501,11 @@ Parser::classMember(YieldHandling yieldHandling, DefaultHandling d return false; } - return handler.addClassFieldDefinition(classMembers, propName, initializer); + ClassFieldType field = handler.newClassFieldDefinition(propName, initializer); + if (!field) + return false; + + return handler.addClassMemberDefinition(classMembers, field); } if (propType != PropertyType::Getter && propType != PropertyType::Setter && @@ -7554,6 +7558,19 @@ Parser::classMember(YieldHandling yieldHandling, DefaultHandling d funName = propAtom; } + // .fieldKeys must be declared outside the scope .initializers is declared in, + // hence this extra scope. + Maybe dotInitializersScope; + if (isConstructor && !options().selfHostingMode) { + dotInitializersScope.emplace(this); + if (!dotInitializersScope->init(pc)) + return false; + + if (!noteDeclaredName(context->names().dotInitializers, DeclarationKind::Let, pos())) + return false; + } + + // 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. @@ -7565,7 +7582,18 @@ Parser::classMember(YieldHandling yieldHandling, DefaultHandling d handler.checkAndSetIsDirectRHSAnonFunction(funNode); JSOp op = JSOpFromPropertyType(propType); - return handler.addClassMethodDefinition(classMembers, propName, funNode, op, isStatic); + Node method = handler.newClassMethodDefinition(propName, funNode, op, isStatic); + if (!method) + return false; + + if (dotInitializersScope.isSome()) { + method = finishLexicalScope(*dotInitializersScope, method); + if (!method) + return false; + dotInitializersScope.reset(); + } + + return handler.addClassMemberDefinition(classMembers, method); } template @@ -7580,6 +7608,16 @@ Parser::finishClassConstructor(const ParseContext::ClassStatement& // 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) { + MOZ_ASSERT(!options().selfHostingMode); + // Unconditionally create the scope here, because it's always the + // constructor. + ParseContext::Scope dotInitializersScope(this); + if (!dotInitializersScope.init(pc)) + return false; + + if (!noteDeclaredName(context->names().dotInitializers, DeclarationKind::Let, pos())) + return false; + // synthesizeConstructor assigns to classStmt.constructorBox FunctionNodeType synthesizedCtor = synthesizeConstructor(className, classStartOffset, hasHeritage); if (!synthesizedCtor) { @@ -7595,9 +7633,14 @@ Parser::finishClassConstructor(const ParseContext::ClassStatement& return false; } - if (!handler.addClassMethodDefinition(classMembers, constructorNameNode, - synthesizedCtor, JSOP_INITPROP, - /* isStatic = */ false)) { + ClassMethodType method = handler.newClassMethodDefinition(constructorNameNode, synthesizedCtor, + JSOP_INITPROP, /* isStatic = */ false); + if (!method) + return false; + + LexicalScopeNodeType scope = finishLexicalScope(dotInitializersScope, method); + + if (!handler.addClassMemberDefinition(classMembers, scope)) { return false; } } @@ -7712,35 +7755,6 @@ Parser::classDefinition(YieldHandling yieldHandling, break; } - 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 (!noteDeclaredName(context->names().dotInitializers, - DeclarationKind::Let, namePos)) - return null(); - } - if (numFieldKeys > 0) { if (!noteDeclaredName(context->names().dotFieldKeys, DeclarationKind::Let, namePos)) return null(); @@ -7836,11 +7850,6 @@ Parser::synthesizeConstructor(HandleAtom className, uint32_t class pc->functionScope().useAsVarScope(pc); - // 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(); @@ -7848,14 +7857,8 @@ Parser::synthesizeConstructor(HandleAtom className, uint32_t class if (!noteUsedName(context->names().dotThis)) return null(); - // One might expect a noteUsedName(".initializers") here. See comment in - // GeneralParser::classDefinition on why it's not here. - - if (hasHeritage) { - if (!noteDeclaredName(context->names().dotLocalInitializers, - DeclarationKind::Var, synthesizedBodyPos)) - return null(); - } + if (!noteUsedName(context->names().dotInitializers)) + return null(); bool canSkipLazyClosedOverBindings = handler.canSkipLazyClosedOverBindings(); if (!declareFunctionThis(canSkipLazyClosedOverBindings)) @@ -7890,9 +7893,6 @@ Parser::synthesizeConstructor(HandleAtom className, uint32_t class if (!setThis) return null(); - if (!noteUsedName(context->names().dotLocalInitializers)) - return null(); - UnaryNodeType exprStatement = handler.newExprStatement(setThis, synthesizedBodyPos.end); if (!exprStatement) return null(); @@ -7900,7 +7900,7 @@ Parser::synthesizeConstructor(HandleAtom className, uint32_t class handler.addStatementToList(stmtList, exprStatement); } - auto initializerBody = finishLexicalScope(lexicalScope, stmtList); + auto initializerBody = finishLexicalScope(pc->varScope(), stmtList); if (!initializerBody) return null(); handler.setBeginPosition(initializerBody, stmtList); @@ -7969,15 +7969,7 @@ Parser::fieldInitializerOpt(YieldHandling yieldHandling, bool hasH 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(); + pc->functionScope().useAsVarScope(pc); Node initializerExpr; TokenPos wholeInitializerPos; @@ -8081,7 +8073,7 @@ Parser::fieldInitializerOpt(YieldHandling yieldHandling, bool hasH handler.addStatementToList(statementList, exprStatement); // Set the function's body to the field assignment. - LexicalScopeNodeType initializerBody = finishLexicalScope(lexicalScope, statementList); + LexicalScopeNodeType initializerBody = finishLexicalScope(pc->varScope(), statementList); if (!initializerBody) { return null(); } @@ -9975,7 +9967,7 @@ Parser::memberExpr(YieldHandling yieldHandling, TripledotHandling if (!nextMember) return null(); - if (!noteUsedName(context->names().dotLocalInitializers)) + if (!noteUsedName(context->names().dotInitializers)) return null(); } else { nextMember = memberCall(tt, lhs, yieldHandling, possibleError); diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index 607cff48f3..c1b6e989ab 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -331,8 +331,9 @@ 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 memberList, Node key, FunctionNodeType funNode, JSOp op, bool isStatic) { return true; } - MOZ_MUST_USE bool addClassFieldDefinition(ListNodeType memberList, Node name, FunctionNodeType initializer) { return true; } + MOZ_MUST_USE Node newClassMethodDefinition(Node key, FunctionNodeType funNode, JSOp op, bool isStatic) { return NodeGeneric; } + MOZ_MUST_USE Node newClassFieldDefinition(Node name, FunctionNodeType initializer) { return NodeGeneric; } + MOZ_MUST_USE bool addClassMemberDefinition(ListNodeType memberList, Node member) { return true; } UnaryNodeType newYieldExpression(uint32_t begin, Node value) { return NodeGeneric; } UnaryNodeType newYieldStarExpression(uint32_t begin, Node value) { return NodeGeneric; } UnaryNodeType newAwaitExpression(uint32_t begin, Node value) { return NodeGeneric; } diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index bc2e4204e3..f69dfe3ca9 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -103,7 +103,6 @@ macro(dotGenerator, dotGenerator, ".generator") \ macro(dotThis, dotThis, ".this") \ macro(dotInitializers, dotInitializers, ".initializers") \ - macro(dotLocalInitializers, dotLocalInitializers, ".localInitializers") \ macro(dotFieldKeys, dotFieldKeys, ".fieldKeys") \ macro(each, each, "each") \ macro(elementType, elementType, "elementType") \ From 1031b1fc4371cf9feb814c874c6052c018a40488 Mon Sep 17 00:00:00 2001 From: Martok Date: Sun, 9 Apr 2023 21:16:52 +0200 Subject: [PATCH 13/23] Issue #2142 - Pass through arguments in synthesized constructors for derived classes Based-on: m-c 1552022 --- js/src/frontend/Parser.cpp | 28 ++++++++++++++++++++++++++-- js/src/vm/CommonPropertyNames.h | 1 + 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index e5daabc8d5..213af5d72b 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -7845,9 +7845,22 @@ Parser::synthesizeConstructor(HandleAtom className, uint32_t class if (!argsbody) return null(); handler.setFunctionFormalParametersAndBody(funNode, argsbody); - funbox->function()->setArgCount(0); funbox->setStart(tokenStream); + if (hasHeritage) { + // Synthesize the equivalent to `function f(...args)` + funbox->setHasRest(); + if (!notePositionalFormalParameter(funNode, context->names().args, + synthesizedBodyPos.begin, + /* disallowDuplicateParams = */ false, + /* duplicatedParam = */ nullptr)) { + return null(); + } + funbox->function()->setArgCount(1); + } else { + funbox->function()->setArgCount(0); + } + pc->functionScope().useAsVarScope(pc); auto stmtList = handler.newStatementList(synthesizedBodyPos); @@ -7881,7 +7894,18 @@ Parser::synthesizeConstructor(HandleAtom className, uint32_t class if (!arguments) return null(); - BinaryNodeType superCall = handler.newSuperCall(superBase, arguments, false); + NameNodeType argsNameNode = newName(context->names().args, synthesizedBodyPos); + if (!argsNameNode) + return null(); + if (!noteUsedName(context->names().args)) + return null(); + + UnaryNodeType spreadArgs = handler.newSpread(synthesizedBodyPos.begin, argsNameNode); + if (!spreadArgs) + return null(); + handler.addList(arguments, spreadArgs); + + BinaryNodeType superCall = handler.newSuperCall(superBase, arguments, /* isSpread = */ true); if (!superCall) return null(); diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index f69dfe3ca9..f7f324da5e 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -16,6 +16,7 @@ macro(anonymous, anonymous, "anonymous") \ macro(Any, Any, "Any") \ macro(apply, apply, "apply") \ + macro(args, args, "args") \ macro(arguments, arguments, "arguments") \ macro(ArrayBufferSpecies, ArrayBufferSpecies, "ArrayBufferSpecies") \ macro(ArrayIterator, ArrayIterator, "Array Iterator") \ From f374ab472de013524d959cdbbc6377f700ff66be Mon Sep 17 00:00:00 2001 From: Martok Date: Mon, 10 Apr 2023 03:48:49 +0200 Subject: [PATCH 14/23] Issue #2142 - Emit field keys in correct order Based-on: m-c 1552229 --- js/src/frontend/BytecodeEmitter.cpp | 76 ++++++++++++++++------------- 1 file changed, 41 insertions(+), 35 deletions(-) diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 9932a63b58..75d1fbfddb 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -7615,11 +7615,43 @@ BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListTy { // [stack] CTOR? OBJ + size_t curFieldKeyIndex = 0; for (ParseNode* propdef : obj->contents()) { if (propdef->is()) { - // 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 + MOZ_ASSERT(type == ClassBody); + // Only handle computing field keys here: the .initializers lambda array + // is created elsewhere. + ClassField* field = &propdef->as(); + if (field->name().getKind() == PNK_COMPUTED_NAME) { + if (!emitGetName(cx->names().dotFieldKeys)) { + // [stack] CTOR? OBJ ARRAY + return false; + } + + ParseNode* nameExpr = field->name().as().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; + } + + if (!emit1(JSOP_POP)) { + // [stack] CTOR? OBJ + return false; + } + + curFieldKeyIndex++; + } continue; } @@ -7903,7 +7935,8 @@ BytecodeEmitter::setupFieldInitializers(ListNode* classMembers) // } // } // -// BytecodeEmitter::emitCreateFieldKeys does `let .fieldKeys = [keyExpr, ...];` +// BytecodeEmitter::emitCreateFieldKeys does `let .fieldKeys = [...];` +// BytecodeEmitter::emitPropertyList fills in the elements of the array. // See Parser::fieldInitializer for the `this[.fieldKeys[0]]` part. bool BytecodeEmitter::emitCreateFieldKeys(ListNode* obj) @@ -7931,34 +7964,6 @@ BytecodeEmitter::emitCreateFieldKeys(ListNode* obj) return false; } - size_t curFieldKeyIndex = 0; - for (ParseNode* propdef : obj->contents()) { - if (propdef->is()) { - ClassField* field = &propdef->as(); - if (field->name().getKind() == PNK_COMPUTED_NAME) { - ParseNode* nameExpr = field->name().as().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; @@ -8690,14 +8695,15 @@ BytecodeEmitter::emitClass(ClassNode* classNode) return false; } } + + if (!emitCreateFieldKeys(classMembers)) + return false; + if (!emitPropertyList(classMembers, ce, ClassBody)) { // [stack] CTOR HOMEOBJ return false; } - if (!emitCreateFieldKeys(classMembers)) - return false; - if (!ce.emitEnd(kind)) { // [stack] # class declaration // [stack] From f0b06f5ad620f32b27f750cf92cd28f54f86403a Mon Sep 17 00:00:00 2001 From: Martok Date: Mon, 10 Apr 2023 02:06:10 +0200 Subject: [PATCH 15/23] Issue #2142 - Don't treat PNK_NAME specially emitAssignmentOrInit If there ever was a point where this structure "avoids duplication", at least since the *OpEmitter refactor emitting PNK_NAME differently makes it actually harder to follow. Mozilla makes the same change at a different time. --- js/src/frontend/BytecodeEmitter.cpp | 78 ++++++++++++++--------------- 1 file changed, 37 insertions(+), 41 deletions(-) diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 75d1fbfddb..21cfd44e24 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -3710,53 +3710,21 @@ BytecodeEmitter::emitAssignmentOrInit(ParseNodeKind kind, JSOp compoundOp, MOZ_ASSERT_IF(isInit, lhs->isKind(PNK_DOT) || lhs->isKind(PNK_ELEM)); - // Name assignments are handled separately because choosing ops and when - // to emit BINDNAME is involved and should avoid duplication. - if (lhs->isKind(PNK_NAME)) { - NameOpEmitter noe(this, - lhs->name(), - isCompound - ? NameOpEmitter::Kind::CompoundAssignment - : NameOpEmitter::Kind::SimpleAssignment); - if (!noe.prepareForRhs()) { // ENV? VAL? - return false; - } - - // Emit the RHS. If we emitted a BIND[G]NAME, then the scope is on - // the top of the stack and we need to pick the right RHS value. - uint8_t offset = noe.emittedBindOp() ? 2 : 1; - if (!EmitAssignmentRhs(this, rhs, offset)) { // ENV? VAL? RHS - return false; - } - // Assign inferred function name, unless the lhs is parenthesized - if (rhs && rhs->isDirectRHSAnonFunction() && !lhs->isInParens()) { - MOZ_ASSERT(!isCompound); - RootedAtom name(cx, lhs->name()); - if (!setOrEmitSetFunName(rhs, name)) { // ENV? VAL? RHS - return false; - } - } - - // Emit the compound assignment op if there is one. - if (isCompound) { - if (!emit1(compoundOp)) { // ENV? VAL - return false; - } - } - if (!noe.emitAssignment()) { // VAL - return false; - } - - return true; - } - + Maybe noe; Maybe poe; Maybe eoe; - // Deal with non-name assignments. uint8_t offset = 1; switch (lhs->getKind()) { + case PNK_NAME: { + noe.emplace(this, + lhs->name(), + isCompound + ? NameOpEmitter::Kind::CompoundAssignment + : NameOpEmitter::Kind::SimpleAssignment); + break; + } case PNK_DOT: { PropertyAccess* prop = &lhs->as(); bool isSuper = prop->isSuper(); @@ -3861,6 +3829,15 @@ BytecodeEmitter::emitAssignmentOrInit(ParseNodeKind kind, JSOp compoundOp, } switch (lhs->getKind()) { + case PNK_NAME: + if (!noe->prepareForRhs()) { // ENV? VAL? + return false; + } + // If we emitted a BIND[G]NAME, then the scope is on + // the top of the stack and we need to pick the right RHS value. + if (noe->emittedBindOp()) + offset += 1; + break; case PNK_DOT: if (!poe->prepareForRhs()) { // [Simple,Super] // // THIS SUPERBASE @@ -3892,6 +3869,17 @@ BytecodeEmitter::emitAssignmentOrInit(ParseNodeKind kind, JSOp compoundOp, if (!EmitAssignmentRhs(this, rhs, offset)) // ... VAL? RHS return false; + if (lhs->isKind(PNK_NAME)) { + // Assign inferred function name, unless the lhs is parenthesized + if (rhs && rhs->isDirectRHSAnonFunction() && !lhs->isInParens()) { + MOZ_ASSERT(!isCompound); + RootedAtom name(cx, lhs->name()); + if (!setOrEmitSetFunName(rhs, name)) { // ENV? VAL? RHS + return false; + } + } + } + /* If += etc., emit the binary operator with a source note. */ if (isCompound) { if (!newSrcNote(SRC_ASSIGNOP)) @@ -3902,6 +3890,14 @@ BytecodeEmitter::emitAssignmentOrInit(ParseNodeKind kind, JSOp compoundOp, /* Finally, emit the specialized assignment bytecode. */ switch (lhs->getKind()) { + case PNK_NAME: { + if (!noe->emitAssignment()) { // VAL + return false; + } + + noe.reset(); + break; + } case PNK_DOT: { PropertyAccess* prop = &lhs->as(); if (!poe->emitAssignment(prop->key().atom())) { // VAL From c2061149666185daf2298c99b8c18062ea8813a0 Mon Sep 17 00:00:00 2001 From: Martok Date: Mon, 10 Apr 2023 06:14:03 +0200 Subject: [PATCH 16/23] Issue #2142 - Set anonymous function name in field initializer This was done wildly different in m-c 1552875, in the interest of keeping |setOrEmitSetFunName| around it is implemented differently here. --- js/src/frontend/BytecodeEmitter.cpp | 75 +++++++++++++++++++++++------ js/src/frontend/BytecodeEmitter.h | 1 + js/src/frontend/Parser.cpp | 4 ++ 3 files changed, 66 insertions(+), 14 deletions(-) diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 21cfd44e24..4f0ec4962c 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -1234,7 +1234,7 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) MOZ_ASSERT(pn->is()); *answer = true; return true; - + case PNK_SETTHIS: MOZ_ASSERT(pn->is()); *answer = true; @@ -1385,7 +1385,7 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) MOZ_ASSERT(pn->is()); *answer = true; return true; - + case PNK_OPTCHAIN: MOZ_ASSERT(pn->is()); *answer = true; @@ -3002,6 +3002,21 @@ BytecodeEmitter::emitSetClassConstructorName(JSAtom* name) return true; } +bool +BytecodeEmitter::emitSetFunctionNameFromStack(uint8_t offset) +{ + // [stack] KEY ... FUN + if (!emitDupAt(offset)) + return false; + // [stack] KEY ... FUN KEY + uint8_t kind = uint8_t(FunctionPrefixKind::None); + if (!emit2(JSOP_SETFUNNAME, kind)) { + // [stack] KEY ... FUN + return false; + } + return true; +} + bool BytecodeEmitter::emitInitializer(ParseNode* initializer, ParseNode* pattern) { @@ -3715,6 +3730,23 @@ BytecodeEmitter::emitAssignmentOrInit(ParseNodeKind kind, JSOp compoundOp, Maybe eoe; uint8_t offset = 1; + // Anonymous functions get their inferred name in simple assignments: + // x = function() {}; // x.name === "x" + // In this case, rhs->isDirectRHSAnonFunction() from parsing the statement. + // To suppress this, put the variable in parentheses: + // (x) = function() {}; // x.name === undefined + // In normal property assignments (`obj.x = function(){}`), the anonymous + // function does not have a computed name and rhs->isDirectRHSAnonFunction()==false. + // However, in field initializers (`class C { x = function(){} }`), field + // initialization is implemented via a property or elem assignment *and* + // rhs->isDirectRHSAnonFunction() is set. In this case (detected by `isInit`), + // we'll assign the name of the function using the same plumbing as binding assignments. + // For PNK_NAME and PNK_DOT, the name is compile-time constant, and is stored in `anonFunctionName`. + // For PNK_ELEM, we grab it from the stack before emitting the actual assignment. + RootedAtom anonFunctionName(cx); + bool inferFunctionName = !isCompound && + rhs && rhs->isDirectRHSAnonFunction() && !lhs->isInParens() && + (lhs->isKind(PNK_NAME) || isInit); switch (lhs->getKind()) { case PNK_NAME: { @@ -3723,6 +3755,9 @@ BytecodeEmitter::emitAssignmentOrInit(ParseNodeKind kind, JSOp compoundOp, isCompound ? NameOpEmitter::Kind::CompoundAssignment : NameOpEmitter::Kind::SimpleAssignment); + if (inferFunctionName) { + anonFunctionName = lhs->name(); + } break; } case PNK_DOT: { @@ -3739,6 +3774,9 @@ BytecodeEmitter::emitAssignmentOrInit(ParseNodeKind kind, JSOp compoundOp, if (!poe->prepareForObj()) { return false; } + if (inferFunctionName) { + anonFunctionName = &prop->name(); + } if (isSuper) { UnaryNode* base = &prop->expression().as(); if (!emitGetThisForSuperBase(base)) { // THIS SUPERBASE @@ -3869,14 +3907,23 @@ BytecodeEmitter::emitAssignmentOrInit(ParseNodeKind kind, JSOp compoundOp, if (!EmitAssignmentRhs(this, rhs, offset)) // ... VAL? RHS return false; - if (lhs->isKind(PNK_NAME)) { - // Assign inferred function name, unless the lhs is parenthesized - if (rhs && rhs->isDirectRHSAnonFunction() && !lhs->isInParens()) { - MOZ_ASSERT(!isCompound); - RootedAtom name(cx, lhs->name()); - if (!setOrEmitSetFunName(rhs, name)) { // ENV? VAL? RHS + // Assign inferred function name + if (inferFunctionName) { + MOZ_ASSERT(!isCompound); + if (anonFunctionName) { + // Name is an atom known at compile time + MOZ_ASSERT_IF(!lhs->isKind(PNK_NAME), isInit); + if (!setOrEmitSetFunName(rhs, anonFunctionName)) { // ENV? VAL? RHS return false; } + } else if (lhs->isKind(PNK_ELEM)) { + // offset points to the SP relative to RHS. // [Simple,Super] offset = 4 + // Find KEY relative to that. // {offset} THIS KEY SUPERBASE FUN + // // [Simple,Other] offset = 3 + // // {offset} OBJ KEY FUN + MOZ_ASSERT(offset >= 2); + if (!emitSetFunctionNameFromStack(offset - 2)) + return false; } } @@ -6977,7 +7024,7 @@ BytecodeEmitter::isRestParameter(ParseNode* pn) FunctionBox* funbox = sc->asFunctionBox(); RootedFunction fun(cx, funbox->function()); - if (!funbox->hasRest()) + if (!funbox->hasRest()) return false; if (!pn->isKind(PNK_NAME)) { @@ -7750,7 +7797,7 @@ BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListTy // [stack] CTOR? OBJ CTOR? KEY VAL return false; } - + switch (op) { case JSOP_INITPROP: if (!pe.emitInitIndexProp(isPropertyAnonFunctionOrClass)) { @@ -7838,7 +7885,7 @@ BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListTy break; default: MOZ_CRASH("Invalid op"); } - + continue; } @@ -7896,7 +7943,7 @@ FieldInitializers BytecodeEmitter::setupFieldInitializers(ListNode* classMembers) { size_t numFields = 0; - + for (ParseNode* propdef : classMembers->contents()) { if (propdef->is()) { FunctionNode* initializer = propdef->as().initializer(); @@ -9220,7 +9267,7 @@ BytecodeEmitter::emitOptionalTree( ValueUsage valueUsage /* = ValueUsage::WantValue */) { JS_CHECK_RECURSION(cx, return false); - + ParseNodeKind kind = pn->getKind(); switch (kind) { case PNK_OPTDOT: { @@ -9477,7 +9524,7 @@ AllocSrcNote(ExclusiveContext* cx, SrcNotesVector& notes, unsigned* index) ReportAllocationOverflow(cx); return false; } - + if (!notes.growBy(1)) { ReportOutOfMemory(cx); return false; diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 95e28f213a..29e8862499 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -690,6 +690,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name); MOZ_MUST_USE bool setFunName(JSFunction* fun, JSAtom* name); MOZ_MUST_USE bool emitSetClassConstructorName(JSAtom* name); + MOZ_MUST_USE bool emitSetFunctionNameFromStack(uint8_t offset); MOZ_MUST_USE bool emitInitializer(ParseNode* initializer, ParseNode* pattern); diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 213af5d72b..80c7cd3b2f 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -8005,6 +8005,10 @@ Parser::fieldInitializerOpt(YieldHandling yieldHandling, bool hasH if (!initializerExpr) return null(); } + + // In `class { x = function() {} }`, the anon function can get a name. + handler.checkAndSetIsDirectRHSAnonFunction(initializerExpr); + wholeInitializerPos = pos(); wholeInitializerPos.begin = firstTokenPos.begin; } else { From e0b5528c29f32c831d46bc3cc0e961c93747853d Mon Sep 17 00:00:00 2001 From: Martok Date: Mon, 10 Apr 2023 15:40:27 +0200 Subject: [PATCH 17/23] Issue #2142 - Optimize .initializers scoping and emitter * Refactor code for emitting the .initializers array into ClassEmitter * Only emit .initializers scope when actually required * Remove unfinished code to handle non-present class field initialisers * Use predicate count_if and any_of of ListNode * Remove unnecessary parameters for class field parsing Based-on: m-c 1553744, 1555979, 1555037/1, 1535804/{1-5} --- js/src/builtin/ReflectParse.cpp | 34 ++++---- js/src/frontend/BytecodeEmitter.cpp | 116 ++++++++++++---------------- js/src/frontend/BytecodeEmitter.h | 3 +- js/src/frontend/FullParseHandler.h | 1 - js/src/frontend/ObjectEmitter.cpp | 74 +++++++++++++++++- js/src/frontend/ObjectEmitter.h | 47 +++++++++++ js/src/frontend/ParseNode.h | 6 +- js/src/frontend/Parser.cpp | 45 ++++++----- js/src/frontend/Parser.h | 4 +- js/src/frontend/SharedContext.h | 2 +- 10 files changed, 215 insertions(+), 117 deletions(-) diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index 2b885574da..6ca0c30e16 100644 --- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -2766,24 +2766,22 @@ 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() - .scopeBody()->as() - .head()->as() - .kid()->as() - .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(); - } + ParseNode* value = classField->initializer() + ->body() + ->head()->as() + .scopeBody()->as() + .head()->as() + .kid()->as() + .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); diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 4f0ec4962c..931b414f10 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -7942,18 +7942,7 @@ BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListTy FieldInitializers BytecodeEmitter::setupFieldInitializers(ListNode* classMembers) { - size_t numFields = 0; - - for (ParseNode* propdef : classMembers->contents()) { - if (propdef->is()) { - FunctionNode* initializer = propdef->as().initializer(); - // Don't include fields without initializers. - if (initializer != nullptr) { - numFields++; - } - continue; - } - } + size_t numFields = classMembers->count_if([](ParseNode* propdef) { return propdef->is(); }); return FieldInitializers(numFields); } @@ -7984,15 +7973,10 @@ BytecodeEmitter::setupFieldInitializers(ListNode* classMembers) bool BytecodeEmitter::emitCreateFieldKeys(ListNode* obj) { - size_t numFieldKeys = 0; - for (ParseNode* propdef : obj->contents()) { - if (propdef->is()) { - ClassField* field = &propdef->as(); - if (field->name().getKind() == PNK_COMPUTED_NAME) { - numFieldKeys++; - } - } - } + size_t numFieldKeys = obj->count_if([](ParseNode* propdef) { + return propdef->is() && + propdef->as().name().getKind() == PNK_COMPUTED_NAME; + }); if (numFieldKeys == 0) return true; @@ -8021,8 +8005,9 @@ BytecodeEmitter::emitCreateFieldKeys(ListNode* obj) } bool -BytecodeEmitter::emitCreateFieldInitializers(ListNode* obj) +BytecodeEmitter::emitCreateFieldInitializers(ClassEmitter& ce, ListNode* obj) { + // [stack] HOMEOBJ HERITAGE? FieldInitializers fieldInitializers = setupFieldInitializers(obj); MOZ_ASSERT(fieldInitializers.valid); size_t numFields = fieldInitializers.numFieldInitializers; @@ -8030,50 +8015,28 @@ BytecodeEmitter::emitCreateFieldInitializers(ListNode* obj) 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()) { + if (!ce.prepareForFieldInitializers(numFields)) { + // [stack] HOMEOBJ HERITAGE? ARRAY 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()) { - FunctionNode* initializer = propdef->as().initializer(); - if (initializer == nullptr) { - continue; - } + if (!propdef->is()) + continue; - if (!emitTree(initializer)) { - // [stack] CTOR? OBJ ARRAY LAMBDA - return false; - } - - if (!emitUint32Operand(JSOP_INITELEM_ARRAY, curFieldIndex)) { - // [stack] CTOR? OBJ ARRAY - return false; - } - - curFieldIndex++; + FunctionNode* initializer = propdef->as().initializer(); + if (!emitTree(initializer)) { + // [stack] HOMEOBJ HERITAGE? ARRAY LAMBDA + return false; + } + if (!ce.emitStoreFieldInitializer()) { + // [stack] HOMEOBJ HERITAGE? ARRAY + return false; } } - if (!noe.emitAssignment()) { - // [stack] CTOR? OBJ ARRAY - return false; - } - - if (!emit1(JSOP_POP)) { - // [stack] CTOR? OBJ + if (!ce.emitFieldInitializersEnd()) { + // [stack] HOMEOBJ HERITAGE? return false; } @@ -8698,20 +8661,37 @@ BytecodeEmitter::emitClass(ClassNode* classNode) } if (constructor) { - FunctionNode* ctor; - // .fieldKeys must be declared outside the scope .initializers is declared - // in, hence this extra scope. + // See |Parser::classMember(...)| for the reason why |.initializers| is + // created within its own scope. Maybe lse; + FunctionNode* ctor; if (constructor->is()) { - lse.emplace(this); - if (!lse->emitScope(ScopeKind::Lexical, constructor->as().scopeBindings())) - return false; + LexicalScopeNode* constructorScope = &constructor->as(); - // Any class with field initializers will have a constructor - if (!emitCreateFieldInitializers(classMembers)) - return false; - ctor = &constructor->as().scopeBody()->as().method(); + // The constructor scope should only contain the |.initializers| binding. + MOZ_ASSERT(!constructorScope->isEmptyScope()); + MOZ_ASSERT(constructorScope->scopeBindings()->length == 1); + MOZ_ASSERT(constructorScope->scopeBindings()->trailingNames[0].name() == + cx->names().dotInitializers); + + // As an optimization omit the |.initializers| binding when no instance + // fields are present. + bool hasInstanceFields = classMembers->any_of([](ParseNode* propdef) { + return propdef->is(); + }); + if (hasInstanceFields) { + lse.emplace(this); + if (!lse->emitScope(ScopeKind::Lexical, constructorScope->scopeBindings())) + return false; + + // Any class with field initializers will have a constructor + if (!emitCreateFieldInitializers(ce, classMembers)) + return false; + } + ctor = &constructorScope->scopeBody()->as().method(); } else { + // The |.initializers| binding is never emitted when in self-hosting mode. + MOZ_ASSERT(emitterMode == BytecodeEmitter::SelfHosting); ctor = &constructor->as().method(); } diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 29e8862499..15a46ac7d0 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -122,6 +122,7 @@ typedef Vector BytecodeVector; typedef Vector SrcNotesVector; class CallOrNewEmitter; +class ClassEmitter; class ElemOpEmitter; class EmitterScope; class NestableControl; @@ -531,7 +532,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter FieldInitializers setupFieldInitializers(ListNode* classMembers); MOZ_MUST_USE bool emitCreateFieldKeys(ListNode* obj); - MOZ_MUST_USE bool emitCreateFieldInitializers(ListNode* obj); + MOZ_MUST_USE bool emitCreateFieldInitializers(ClassEmitter& ce, ListNode* obj); const FieldInitializers& findFieldInitializersForCall(); MOZ_MUST_USE bool emitInitializeInstanceFields(); diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index 345fd483a8..cede9a013b 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -487,7 +487,6 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) return true; } - UnaryNodeType newInitialYieldExpression(uint32_t begin, Node gen) { TokenPos pos(begin, begin + 1); return new_(PNK_INITIALYIELD, JSOP_INITIALYIELD, pos, gen); diff --git a/js/src/frontend/ObjectEmitter.cpp b/js/src/frontend/ObjectEmitter.cpp index 37c03935ab..fc4ff4d303 100644 --- a/js/src/frontend/ObjectEmitter.cpp +++ b/js/src/frontend/ObjectEmitter.cpp @@ -484,6 +484,71 @@ bool ObjectEmitter::emitObject(size_t propertyCount) return true; } +bool ClassEmitter::prepareForFieldInitializers(size_t numFields) +{ + MOZ_ASSERT(classState_ == ClassState::Class); + + // .initializers is a variable that stores an array of lambdas containing + // code (the initializer) for each field. Upon an object's construction, + // these lambdas will be called, defining the values. + initializersAssignment_.emplace(bce_, bce_->cx->names().dotInitializers, + NameOpEmitter::Kind::Initialize); + if (!initializersAssignment_->prepareForRhs()) { + return false; + } + + if (!bce_->emitUint32Operand(JSOP_NEWARRAY, numFields)) { + // [stack] HOMEOBJ HERITAGE? ARRAY + return false; + } + + MOZ_ASSERT(fieldIndex_ == 0); +#ifdef DEBUG + classState_ = ClassState::FieldInitializers; + numFields_ = numFields; +#endif + return true; +} + +bool ClassEmitter::emitStoreFieldInitializer() +{ + MOZ_ASSERT(classState_ == ClassState::FieldInitializers); + MOZ_ASSERT(fieldIndex_ < numFields_); + // [stack] HOMEOBJ HERITAGE? ARRAY METHOD + + if (!bce_->emitUint32Operand(JSOP_INITELEM_ARRAY, fieldIndex_)) { + // [stack] HOMEOBJ HERITAGE? ARRAY + return false; + } + + fieldIndex_++; + return true; +} + +bool ClassEmitter::emitFieldInitializersEnd() +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + MOZ_ASSERT(classState_ == ClassState::FieldInitializers); + MOZ_ASSERT(fieldIndex_ == numFields_); + + if (!initializersAssignment_->emitAssignment()) { + // [stack] HOMEOBJ HERITAGE? ARRAY + return false; + } + initializersAssignment_.reset(); + + if (!bce_->emit1(JSOP_POP)) { + // [stack] HOMEOBJ HERITAGE? + return false; + } + +#ifdef DEBUG + classState_ = ClassState::FieldInitializersEnd; +#endif + return true; +} + bool ObjectEmitter::emitEnd() { MOZ_ASSERT(propertyState_ == PropertyState::Start || @@ -614,7 +679,8 @@ void ClassEmitter::setName(JS::Handle name) bool ClassEmitter::emitInitConstructor(bool needsHomeObject) { MOZ_ASSERT(propertyState_ == PropertyState::Start); - MOZ_ASSERT(classState_ == ClassState::Class); + MOZ_ASSERT(classState_ == ClassState::Class || + classState_ == ClassState::FieldInitializersEnd); // [stack] HOMEOBJ CTOR @@ -640,7 +706,8 @@ bool ClassEmitter::emitInitDefaultConstructor(const Maybe& classStart, const Maybe& classEnd) { MOZ_ASSERT(propertyState_ == PropertyState::Start); - MOZ_ASSERT(classState_ == ClassState::Class); + MOZ_ASSERT(classState_ == ClassState::Class || + classState_ == ClassState::FieldInitializersEnd); if (classStart && classEnd) { // In the case of default class constructors, emit the start and end @@ -706,7 +773,8 @@ bool ClassEmitter::emitEnd(Kind kind) { MOZ_ASSERT(propertyState_ == PropertyState::Start || propertyState_ == PropertyState::Init); - MOZ_ASSERT(classState_ == ClassState::InitConstructor); + MOZ_ASSERT(classState_ == ClassState::InitConstructor || + classState_ == ClassState::FieldInitializersEnd); // [stack] CTOR HOMEOBJ diff --git a/js/src/frontend/ObjectEmitter.h b/js/src/frontend/ObjectEmitter.h index dd983a5b72..423284a455 100644 --- a/js/src/frontend/ObjectEmitter.h +++ b/js/src/frontend/ObjectEmitter.h @@ -18,6 +18,7 @@ #include "jsscript.h" // FunctionAsyncKind #include "frontend/EmitterScope.h" // EmitterScope +#include "frontend/NameOpEmitter.h" // NameOpEmitter #include "frontend/TDZCheckCache.h" // TDZCheckCache #include "js/RootingAPI.h" // JS::Handle, JS::Rooted #include "vm/String.h" // JSAtom @@ -527,6 +528,23 @@ class MOZ_RAII AutoSaveLocalStrictMode // // ce.emitEnd(ClassEmitter::Kind::Expression); // +// `class X extends Y { field0 = expr0; ... }` +// ClassEmitter ce(this); +// ce.emitScope(scopeBindings); +// emit(Y); +// ce.emitDerivedClass(atom_of_X, nullptr, false); +// +// ce.prepareForFieldInitializers(fields.length()); +// for (auto field : fields) { +// emit(field.expr_method()); +// ce.emitStoreFieldInitializer(); +// } +// ce.emitFieldInitializersEnd(); +// +// emit(function_for_constructor); +// ce.emitInitConstructor(/* needsHomeObject = */ false); +// ce.emitEnd(ClassEmitter::Kind::Expression); +// // `m() {}` in class // // after emitInitConstructor/emitInitDefaultConstructor // ce.prepareForPropValue(Some(offset_of_m)); @@ -655,6 +673,21 @@ class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter // | // +-------------------------------+ // | + // | prepareForFieldInitializers +-------------------+ + // +----------------------------->| FieldInitializers |-+ + // | +-------------------+ | + // | | + // | +-------------------------------------------------+ + // | | + // | | (expr emitStoreFieldInitializer)* + // | | + // | | + // | | emitFieldInitializersEnd +----------------------+ + // | +-------------------------->| FieldInitializersEnd |-+ + // | +----------------------+ | + // | | + // |<------------------------------------------------------+ + // | // | // | emitInitConstructor +-----------------+ // +-+--------------------------->+->| InitConstructor |-+ @@ -680,13 +713,23 @@ class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter // After calling emitInitConstructor or emitInitDefaultConstructor. InitConstructor, + // After calling prepareForFieldInitializers + // and 0 or more calls to emitFieldInitializersEnd. + FieldInitializers, + + // After calling emitFieldInitializersEnd. + FieldInitializersEnd, + // After calling emitEnd. End, }; ClassState classState_ = ClassState::Start; + size_t numFields_ = 0; #endif JS::Rooted name_; + mozilla::Maybe initializersAssignment_; + size_t fieldIndex_ = 0; public: explicit ClassEmitter(BytecodeEmitter* bce); @@ -715,6 +758,10 @@ class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter const mozilla::Maybe& classStart, const mozilla::Maybe& classEnd); + MOZ_MUST_USE bool prepareForFieldInitializers(size_t numFields); + MOZ_MUST_USE bool emitStoreFieldInitializer(); + MOZ_MUST_USE bool emitFieldInitializersEnd(); + MOZ_MUST_USE bool emitEnd(Kind kind); private: diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 9a91ee9b3e..c4e6945e5a 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -2143,7 +2143,7 @@ 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), + TokenPos::box(name->pn_pos, initializer->pn_pos), name, initializer) { } @@ -2156,9 +2156,7 @@ class ClassField : public BinaryNode ParseNode& name() const { return *left(); } - FunctionNode* initializer() const { - return right() ? &right()->as() : nullptr; - } + FunctionNode* initializer() const { return &right()->as(); } }; class SwitchStatement : public BinaryNode diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 80c7cd3b2f..e74a8d3ee9 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -557,13 +557,10 @@ FunctionBox::initWithEnclosingParseContext(ParseContext* enclosing, FunctionSynt } void -FunctionBox::initFieldInitializer(ParseContext* enclosing, bool hasHeritage) +FunctionBox::initFieldInitializer(ParseContext* enclosing) { - this->initWithEnclosingParseContext(enclosing, FunctionSyntaxKind::Expression); - allowSuperProperty_ = false; - allowSuperCall_ = false; + this->initWithEnclosingParseContext(enclosing, FunctionSyntaxKind::Method); allowArguments_ = false; - needsThisTDZChecks_ = hasHeritage; } void @@ -7419,7 +7416,7 @@ JSOpFromPropertyType(PropertyType propType) template bool -Parser::classMember(YieldHandling yieldHandling, DefaultHandling defaultHandling, +Parser::classMember(YieldHandling yieldHandling, const ParseContext::ClassStatement& classStmt, HandlePropertyName className, uint32_t classStartOffset, bool hasHeritage, @@ -7492,8 +7489,7 @@ Parser::classMember(YieldHandling yieldHandling, DefaultHandling d numFields++; - FunctionNodeType initializer = fieldInitializerOpt(yieldHandling, hasHeritage, - propAtom, numFieldKeys); + FunctionNodeType initializer = fieldInitializerOpt(propAtom, numFieldKeys); if (!initializer) return false; @@ -7558,8 +7554,25 @@ Parser::classMember(YieldHandling yieldHandling, DefaultHandling d funName = propAtom; } - // .fieldKeys must be declared outside the scope .initializers is declared in, - // hence this extra scope. + // When |super()| is invoked, we search for the nearest scope containing + // |.initializers| to initialize the class fields. This set-up precludes + // declaring |.initializers| in the class scope, because in some syntactic + // contexts |super()| can appear nested in a class, while actually belonging + // to an outer class definition. + // + // Example: + // class Outer extends Base { + // field = 1; + // constructor() { + // class Inner { + // field = 2; + // + // // The super() call in the computed property name mustn't access + // // Inner's |.initializers| array, but instead Outer's. + // [super()]() {} + // } + // } + // } Maybe dotInitializersScope; if (isConstructor && !options().selfHostingMode) { dotInitializersScope.emplace(this); @@ -7747,9 +7760,8 @@ Parser::classDefinition(YieldHandling yieldHandling, size_t numFieldKeys = 0; for (;;) { bool done; - if (!classMember(yieldHandling, defaultHandling, classStmt, className, - classStartOffset, hasHeritage, numFields, numFieldKeys, - classMembers, &done)) + if (!classMember(yieldHandling, classStmt, className, classStartOffset, + hasHeritage, numFields, numFieldKeys, classMembers, &done)) return null(); if (done) break; @@ -7831,7 +7843,6 @@ Parser::synthesizeConstructor(HandleAtom className, uint32_t class if (!funbox) return null(); funbox->initWithEnclosingParseContext(pc, functionSyntaxKind); - handler.setFunctionBox(funNode, funbox); funbox->setEnd(pos().end); // Push a ParseContext on to the stack. @@ -7944,8 +7955,7 @@ Parser::synthesizeConstructor(HandleAtom className, uint32_t class template typename ParseHandler::FunctionNodeType -Parser::fieldInitializerOpt(YieldHandling yieldHandling, bool hasHeritage, - HandleAtom propAtom, size_t& numFieldKeys) +Parser::fieldInitializerOpt(HandleAtom propAtom, size_t& numFieldKeys) { bool hasInitializer = false; if (!tokenStream.matchToken(&hasInitializer, TOK_ASSIGN)) @@ -7983,8 +7993,7 @@ Parser::fieldInitializerOpt(YieldHandling yieldHandling, bool hasH FunctionAsyncKind::SyncFunction, false); if (!funbox) return null(); - funbox->initFieldInitializer(pc, hasHeritage); - handler.setFunctionBox(funNode, funbox); + funbox->initFieldInitializer(pc); funbox->setStart(tokenStream, firstTokenPos); // Push a SourceParseContext on to the stack. diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 5222d6398c..fd0033ad7d 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -1489,7 +1489,6 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) 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, @@ -1502,8 +1501,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) uint32_t classStartOffset, uint32_t classEndOffset, size_t numFieldsWithInitializers, ListNodeType& classMembers); - FunctionNodeType fieldInitializerOpt(YieldHandling yieldHandling, bool hasHeritage, - HandleAtom atom, size_t& numFieldKeys); + FunctionNodeType fieldInitializerOpt(HandleAtom atom, size_t& numFieldKeys); FunctionNodeType synthesizeConstructor(HandleAtom className, uint32_t classNameOffset, bool hasHeritage); diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h index edb2b93788..8aa068c1fc 100644 --- a/js/src/frontend/SharedContext.h +++ b/js/src/frontend/SharedContext.h @@ -455,7 +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); + void initFieldInitializer(ParseContext* enclosing); ObjectBox* toObjectBox() override { return this; } JSFunction* function() const { return &object->as(); } From b187006a7ea4f45c8b1ff4fed954df7d6f38f310 Mon Sep 17 00:00:00 2001 From: Martok Date: Mon, 10 Apr 2023 18:19:26 +0200 Subject: [PATCH 18/23] Issue #2142 - Support SuperProperty in field initializers Based-on: m-c 1555037/2 --- js/src/frontend/BytecodeEmitter.cpp | 7 +++++ js/src/frontend/ObjectEmitter.cpp | 24 +++++++++++++-- js/src/frontend/ObjectEmitter.h | 46 ++++++++++++++++++++++------- js/src/frontend/ParseNode.h | 2 +- js/src/frontend/Parser.cpp | 9 ++++-- 5 files changed, 72 insertions(+), 16 deletions(-) diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 931b414f10..47ea774f86 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -8029,6 +8029,13 @@ BytecodeEmitter::emitCreateFieldInitializers(ClassEmitter& ce, ListNode* obj) // [stack] HOMEOBJ HERITAGE? ARRAY LAMBDA return false; } + if (initializer->funbox()->needsHomeObject()) { + MOZ_ASSERT(initializer->funbox()->function()->allowSuperProperty()); + if (!ce.emitFieldInitializerHomeObject()) { + // [stack] CTOR OBJ ARRAY LAMBDA + return false; + } + } if (!ce.emitStoreFieldInitializer()) { // [stack] HOMEOBJ HERITAGE? ARRAY return false; diff --git a/js/src/frontend/ObjectEmitter.cpp b/js/src/frontend/ObjectEmitter.cpp index fc4ff4d303..0cb56c8f3e 100644 --- a/js/src/frontend/ObjectEmitter.cpp +++ b/js/src/frontend/ObjectEmitter.cpp @@ -510,9 +510,25 @@ bool ClassEmitter::prepareForFieldInitializers(size_t numFields) return true; } -bool ClassEmitter::emitStoreFieldInitializer() +bool ClassEmitter::emitFieldInitializerHomeObject() { MOZ_ASSERT(classState_ == ClassState::FieldInitializers); + // [stack] OBJ HERITAGE? ARRAY METHOD + if (!bce_->emit2(JSOP_INITHOMEOBJECT, isDerived_ ? 2 : 1)) { + // [stack] OBJ HERITAGE? ARRAY METHOD + return false; + } + +#ifdef DEBUG + classState_ = ClassState::FieldInitializerWithHomeObject; +#endif + return true; +} + +bool ClassEmitter::emitStoreFieldInitializer() +{ + MOZ_ASSERT(classState_ == ClassState::FieldInitializers || + classState_ == ClassState::FieldInitializerWithHomeObject); MOZ_ASSERT(fieldIndex_ < numFields_); // [stack] HOMEOBJ HERITAGE? ARRAY METHOD @@ -522,6 +538,9 @@ bool ClassEmitter::emitStoreFieldInitializer() } fieldIndex_++; +#ifdef DEBUG + classState_ = ClassState::FieldInitializers; +#endif return true; } @@ -529,7 +548,8 @@ bool ClassEmitter::emitFieldInitializersEnd() { MOZ_ASSERT(propertyState_ == PropertyState::Start || propertyState_ == PropertyState::Init); - MOZ_ASSERT(classState_ == ClassState::FieldInitializers); + MOZ_ASSERT(classState_ == ClassState::FieldInitializers || + classState_ == ClassState::FieldInitializerWithHomeObject); MOZ_ASSERT(fieldIndex_ == numFields_); if (!initializersAssignment_->emitAssignment()) { diff --git a/js/src/frontend/ObjectEmitter.h b/js/src/frontend/ObjectEmitter.h index 423284a455..38408f7650 100644 --- a/js/src/frontend/ObjectEmitter.h +++ b/js/src/frontend/ObjectEmitter.h @@ -536,7 +536,7 @@ class MOZ_RAII AutoSaveLocalStrictMode // // ce.prepareForFieldInitializers(fields.length()); // for (auto field : fields) { -// emit(field.expr_method()); +// emit(field.initializer_method()); // ce.emitStoreFieldInitializer(); // } // ce.emitFieldInitializersEnd(); @@ -545,6 +545,18 @@ class MOZ_RAII AutoSaveLocalStrictMode // ce.emitInitConstructor(/* needsHomeObject = */ false); // ce.emitEnd(ClassEmitter::Kind::Expression); // +// `class X { field0 = super.method(); ... }` +// // after emitClass/emitDerivedClass +// ce.prepareForFieldInitializers(1); +// for (auto field : fields) { +// emit(field.initializer_method()); +// if (field.initializer_contains_super_or_eval()) { +// ce.emitFieldInitializerHomeObject(); +// } +// ce.emitStoreFieldInitializer(); +// } +// ce.emitFieldInitializersEnd(); +// // `m() {}` in class // // after emitInitConstructor/emitInitDefaultConstructor // ce.prepareForPropValue(Some(offset_of_m)); @@ -673,14 +685,24 @@ class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter // | // +-------------------------------+ // | - // | prepareForFieldInitializers +-------------------+ - // +----------------------------->| FieldInitializers |-+ - // | +-------------------+ | - // | | - // | +-------------------------------------------------+ - // | | - // | | (expr emitStoreFieldInitializer)* - // | | + // | prepareForFieldInitializers + // +-----------------------------+ + // | | + // | | +-------------------+ + // | +--------------------->+--->| FieldInitializers |-+ + // | | +-------------------+ | + // | | | + // | | (emit initializer method) | + // | | +<--------------------------------------------+ + // | | | + // | | | emitFieldInitializerHomeObject +--------------------------------+ + // | | +-------------------------------->| FieldInitializerWithHomeObject |-+ + // | | | +--------------------------------+ | + // | | | | + // | | +------------------------------------------------------------------->+ + // | | | + // | | emitStoreFieldInitializer | + // | +<--+<-----------------------------------------------------------------------+ // | | // | | emitFieldInitializersEnd +----------------------+ // | +-------------------------->| FieldInitializersEnd |-+ @@ -714,9 +736,12 @@ class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter InitConstructor, // After calling prepareForFieldInitializers - // and 0 or more calls to emitFieldInitializersEnd. + // and 0 or more calls to emitStoreFieldInitializer. FieldInitializers, + // After calling emitFieldInitializerHomeObject + FieldInitializerWithHomeObject, + // After calling emitFieldInitializersEnd. FieldInitializersEnd, @@ -759,6 +784,7 @@ class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter const mozilla::Maybe& classEnd); MOZ_MUST_USE bool prepareForFieldInitializers(size_t numFields); + MOZ_MUST_USE bool emitFieldInitializerHomeObject(); MOZ_MUST_USE bool emitStoreFieldInitializer(); MOZ_MUST_USE bool emitFieldInitializersEnd(); diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index c4e6945e5a..554703da46 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -625,7 +625,7 @@ enum class FunctionSyntaxKind Expression, // A non-arrow function expression. Statement, // A named function appearing as a Statement. Arrow, - Method, + Method, // Method of a class or object. Field initializers also desugar to methods. ClassConstructor, DerivedClassConstructor, Getter, diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index e74a8d3ee9..b5044c6ca2 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -222,7 +222,6 @@ SharedContext::computeAllowSyntax(Scope* scope) allowSuperProperty_ = fun->allowSuperProperty(); allowSuperCall_ = fun->isDerivedClassConstructor(); if (funScope->isFieldInitializer()) { - allowSuperProperty_ = false; allowSuperCall_ = false; allowArguments_ = false; } @@ -7975,14 +7974,14 @@ Parser::fieldInitializerOpt(HandleAtom propAtom, size_t& numFieldK // Create the anonymous function object. RootedFunction fun(context, - newFunction(nullptr, FunctionSyntaxKind::Expression, + newFunction(nullptr, FunctionSyntaxKind::Method, GeneratorKind::NotGenerator, FunctionAsyncKind::SyncFunction)); if (!fun) return null(); // Create the top-level field initializer node. - FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Expression, firstTokenPos); + FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Method, firstTokenPos); if (!funNode) return null(); @@ -8117,6 +8116,10 @@ Parser::fieldInitializerOpt(HandleAtom propAtom, size_t& numFieldK handler.setFunctionBody(funNode, initializerBody); + if (pc->superScopeNeedsHomeObject()) { + funbox->setNeedsHomeObject(); + } + if (!finishFunction(false, true)) return null(); From c3b19191f3cf23a1c675be14fc31154d0d89385e Mon Sep 17 00:00:00 2001 From: Martok Date: Tue, 11 Apr 2023 00:00:59 +0200 Subject: [PATCH 19/23] Issue #2142 - Parse and process static class fields Based-on: m-c 1535804/{6,7} --- js/src/frontend/BytecodeEmitter.cpp | 196 +++++++++++++++++++++++---- js/src/frontend/BytecodeEmitter.h | 8 +- js/src/frontend/FullParseHandler.h | 4 +- js/src/frontend/ObjectEmitter.cpp | 132 +++++++++++------- js/src/frontend/ObjectEmitter.h | 134 ++++++++++++------ js/src/frontend/ParseNode.h | 16 +-- js/src/frontend/Parser.cpp | 59 +++++--- js/src/frontend/Parser.h | 20 ++- js/src/frontend/SyntaxParseHandler.h | 2 +- js/src/vm/CommonPropertyNames.h | 2 + 10 files changed, 427 insertions(+), 146 deletions(-) diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 47ea774f86..1524493cb0 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -5704,7 +5704,8 @@ BytecodeEmitter::emitFunction(FunctionNode* funNode, bool needsProto /* = false } if (classContentsIfConstructor) { - fun->lazyScript()->setFieldInitializers(setupFieldInitializers(classContentsIfConstructor)); + fun->lazyScript()->setFieldInitializers(setupFieldInitializers(classContentsIfConstructor, + FieldPlacement::Instance)); } return true; } @@ -5732,7 +5733,7 @@ BytecodeEmitter::emitFunction(FunctionNode* funNode, bool needsProto /* = false FieldInitializers fieldInitializers = FieldInitializers::Invalid(); if (classContentsIfConstructor) { - fieldInitializers = setupFieldInitializers(classContentsIfConstructor); + fieldInitializers = setupFieldInitializers(classContentsIfConstructor, FieldPlacement::Instance); } BytecodeEmitter bce2(this, parser, funbox, script, /* lazyScript = */ nullptr, @@ -7659,6 +7660,7 @@ BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListTy // [stack] CTOR? OBJ size_t curFieldKeyIndex = 0; + size_t curStaticFieldKeyIndex = 0; for (ParseNode* propdef : obj->contents()) { if (propdef->is()) { MOZ_ASSERT(type == ClassBody); @@ -7666,7 +7668,9 @@ BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListTy // is created elsewhere. ClassField* field = &propdef->as(); if (field->name().getKind() == PNK_COMPUTED_NAME) { - if (!emitGetName(cx->names().dotFieldKeys)) { + HandlePropertyName fieldKeys = field->isStatic() ? cx->names().dotStaticFieldKeys + : cx->names().dotFieldKeys; + if (!emitGetName(fieldKeys)) { // [stack] CTOR? OBJ ARRAY return false; } @@ -7683,7 +7687,9 @@ BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListTy return false; } - if (!emitUint32Operand(JSOP_INITELEM_ARRAY, curFieldKeyIndex)) { + size_t fieldKeysIndex = field->isStatic() ? curStaticFieldKeyIndex++ + : curFieldKeyIndex++; + if (!emitUint32Operand(JSOP_INITELEM_ARRAY, fieldKeysIndex)) { // [stack] ARRAY return false; } @@ -7692,8 +7698,6 @@ BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListTy // [stack] CTOR? OBJ return false; } - - curFieldKeyIndex++; } continue; } @@ -7940,9 +7944,13 @@ BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListTy } FieldInitializers -BytecodeEmitter::setupFieldInitializers(ListNode* classMembers) +BytecodeEmitter::setupFieldInitializers(ListNode* classMembers, FieldPlacement placement) { - size_t numFields = classMembers->count_if([](ParseNode* propdef) { return propdef->is(); }); + bool isStatic = placement == FieldPlacement::Static; + size_t numFields = classMembers->count_if([isStatic](ParseNode* propdef) { + return propdef->is()&& + propdef->as().isStatic() == isStatic; + }); return FieldInitializers(numFields); } @@ -7971,18 +7979,21 @@ BytecodeEmitter::setupFieldInitializers(ListNode* classMembers) // BytecodeEmitter::emitPropertyList fills in the elements of the array. // See Parser::fieldInitializer for the `this[.fieldKeys[0]]` part. bool -BytecodeEmitter::emitCreateFieldKeys(ListNode* obj) +BytecodeEmitter::emitCreateFieldKeys(ListNode* obj, FieldPlacement placement) { - size_t numFieldKeys = obj->count_if([](ParseNode* propdef) { + bool isStatic = placement == FieldPlacement::Static; + size_t numFieldKeys = obj->count_if([isStatic](ParseNode* propdef) { return propdef->is() && + propdef->as().isStatic() == isStatic && propdef->as().name().getKind() == PNK_COMPUTED_NAME; }); if (numFieldKeys == 0) return true; - NameOpEmitter noe(this, cx->names().dotFieldKeys, - NameOpEmitter::Kind::Initialize); + HandlePropertyName fieldKeys = isStatic ? cx->names().dotStaticFieldKeys + : cx->names().dotFieldKeys; + NameOpEmitter noe(this, fieldKeys, NameOpEmitter::Kind::Initialize); if (!noe.prepareForRhs()) return false; @@ -8005,45 +8016,64 @@ BytecodeEmitter::emitCreateFieldKeys(ListNode* obj) } bool -BytecodeEmitter::emitCreateFieldInitializers(ClassEmitter& ce, ListNode* obj) +BytecodeEmitter::emitCreateFieldInitializers(ClassEmitter& ce, ListNode* obj, + FieldPlacement placement) { - // [stack] HOMEOBJ HERITAGE? - FieldInitializers fieldInitializers = setupFieldInitializers(obj); + // FieldPlacement::Instance + // [stack] HOMEOBJ HERITAGE? + // + // FieldPlacement::Static + // [stack] CTOR HOMEOBJ + FieldInitializers fieldInitializers = setupFieldInitializers(obj, placement); MOZ_ASSERT(fieldInitializers.valid); size_t numFields = fieldInitializers.numFieldInitializers; if (numFields == 0) return true; - if (!ce.prepareForFieldInitializers(numFields)) { - // [stack] HOMEOBJ HERITAGE? ARRAY + bool isStatic = placement == FieldPlacement::Static; + if (!ce.prepareForFieldInitializers(numFields, isStatic)) { + // [stack] HOMEOBJ HERITAGE? ARRAY + // or: + // [stack] CTOR HOMEOBJ ARRAY return false; } for (ParseNode* propdef : obj->contents()) { - if (!propdef->is()) + if (!propdef->is() || + propdef->as().isStatic() != isStatic) continue; FunctionNode* initializer = propdef->as().initializer(); + if (!ce.prepareForFieldInitializer()) + return false; if (!emitTree(initializer)) { // [stack] HOMEOBJ HERITAGE? ARRAY LAMBDA + // or: + // [stack] CTOR HOMEOBJ ARRAY LAMBDA return false; } if (initializer->funbox()->needsHomeObject()) { MOZ_ASSERT(initializer->funbox()->function()->allowSuperProperty()); - if (!ce.emitFieldInitializerHomeObject()) { + if (!ce.emitFieldInitializerHomeObject(isStatic)) { // [stack] CTOR OBJ ARRAY LAMBDA + // or: + // [stack] CTOR HOMEOBJ ARRAY LAMBDA return false; } } if (!ce.emitStoreFieldInitializer()) { // [stack] HOMEOBJ HERITAGE? ARRAY + // or: + // [stack] CTOR HOMEOBJ ARRAY return false; } } if (!ce.emitFieldInitializersEnd()) { // [stack] HOMEOBJ HERITAGE? + // or: + // [stack] CTOR HOMEOBJ return false; } @@ -8139,6 +8169,108 @@ BytecodeEmitter::emitInitializeInstanceFields() return true; } +bool +BytecodeEmitter::emitInitializeStaticFields(ListNode* classMembers) +{ + size_t numFields = classMembers->count_if([](ParseNode* propdef) { + return propdef->is()&& + propdef->as().isStatic(); + }); + + if (numFields == 0) { + return true; + } + + if (!emitGetName(cx->names().dotStaticInitializers)) { + // [stack] CTOR ARRAY + return false; + } + + for (size_t fieldIndex = 0; fieldIndex < numFields; fieldIndex++) { + bool hasNext = fieldIndex < numFields - 1; + if (fieldIndex < numFields - 1) { + // We DUP to keep the array around (it is consumed in the bytecode below) + // for next iterations of this loop, except for the last iteration, which + // avoids an extra POP at the end of the loop. + if (!emit1(JSOP_DUP)) { + // [stack] CTOR ARRAY ARRAY + return false; + } + } + + if (!emitNumberOp(fieldIndex)) { + // [stack] CTOR ARRAY? ARRAY INDEX + return false; + } + + // Don't use CALLELEM here, because the receiver of the call != the receiver + // of this getelem. (Specifically, the call receiver is `ctor`, and the + // receiver of this getelem is `.staticInitializers`) + if (!emit1(JSOP_GETELEM)) { + // [stack] CTOR ARRAY? FUNC + return false; + } + + if (!emitDupAt(1 + hasNext)) { + // [stack] CTOR ARRAY? FUNC CTOR + return false; + } + + if (!emitCall(JSOP_CALL_IGNORES_RV, 0)) { + // [stack] CTOR ARRAY? RVAL + return false; + } + + if (!emit1(JSOP_POP)) { + // [stack] CTOR ARRAY? + return false; + } + } + + // Overwrite |.staticInitializers| and |.staticFieldKeys| with undefined to + // avoid keeping the arrays alive indefinitely. + auto clearStaticFieldSlot = [&](HandlePropertyName name) { + NameOpEmitter noe(this, name, NameOpEmitter::Kind::SimpleAssignment); + if (!noe.prepareForRhs()) { + // [stack] ENV? VAL? + return false; + } + + if (!emit1(JSOP_UNDEFINED)) { + // [stack] ENV? VAL? UNDEFINED + return false; + } + + if (!noe.emitAssignment()) { + // [stack] VAL + return false; + } + + if (!emit1(JSOP_POP)) { + // [stack] + return false; + } + + return true; + }; + + if (!clearStaticFieldSlot(cx->names().dotStaticInitializers)) + return false; + + auto isStaticFieldWithComputedName = [](ParseNode* propdef) { + return propdef->is() && + propdef->as().isStatic() && + propdef->as().name().getKind() == PNK_COMPUTED_NAME; + }; + + if (classMembers->any_of(isStaticFieldWithComputedName)) { + if (!clearStaticFieldSlot(cx->names().dotStaticFieldKeys)) + return false; + } + + return true; +} + // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See // the comment on emitSwitch. MOZ_NEVER_INLINE bool @@ -8684,15 +8816,17 @@ BytecodeEmitter::emitClass(ClassNode* classNode) // As an optimization omit the |.initializers| binding when no instance // fields are present. bool hasInstanceFields = classMembers->any_of([](ParseNode* propdef) { - return propdef->is(); - }); + return propdef->is() && + !propdef->as().isStatic(); + }); if (hasInstanceFields) { lse.emplace(this); if (!lse->emitScope(ScopeKind::Lexical, constructorScope->scopeBindings())) return false; // Any class with field initializers will have a constructor - if (!emitCreateFieldInitializers(ce, classMembers)) + if (!emitCreateFieldInitializers(ce, classMembers, + FieldPlacement::Instance)) return false; } ctor = &constructorScope->scopeBody()->as().method(); @@ -8726,7 +8860,13 @@ BytecodeEmitter::emitClass(ClassNode* classNode) } } - if (!emitCreateFieldKeys(classMembers)) + if (!emitCreateFieldKeys(classMembers, FieldPlacement::Instance)) + return false; + + if (!emitCreateFieldInitializers(ce, classMembers, FieldPlacement::Static)) + return false; + + if (!emitCreateFieldKeys(classMembers, FieldPlacement::Static)) return false; if (!emitPropertyList(classMembers, ce, ClassBody)) { @@ -8734,6 +8874,16 @@ BytecodeEmitter::emitClass(ClassNode* classNode) return false; } + if (!ce.emitBinding()) { + // [stack] CTOR + return false; + } + + if (!emitInitializeStaticFields(classMembers)) { + // [stack] CTOR + return false; + } + if (!ce.emitEnd(kind)) { // [stack] # class declaration // [stack] diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 15a46ac7d0..7521e236dc 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -530,11 +530,13 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListType type); - FieldInitializers setupFieldInitializers(ListNode* classMembers); - MOZ_MUST_USE bool emitCreateFieldKeys(ListNode* obj); - MOZ_MUST_USE bool emitCreateFieldInitializers(ClassEmitter& ce, ListNode* obj); + enum class FieldPlacement { Instance, Static }; + FieldInitializers setupFieldInitializers(ListNode* classMembers, FieldPlacement placement); + MOZ_MUST_USE bool emitCreateFieldKeys(ListNode* obj, FieldPlacement placement); + MOZ_MUST_USE bool emitCreateFieldInitializers(ClassEmitter& ce, ListNode* obj, FieldPlacement placement); const FieldInitializers& findFieldInitializersForCall(); MOZ_MUST_USE bool emitInitializeInstanceFields(); + MOZ_MUST_USE bool emitInitializeStaticFields(ListNode* classMembers); // To catch accidental misuse, emitUint16Operand/emit3 assert that they are // not used to unconditionally emit JSOP_GETLOCAL. Variable access should diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index cede9a013b..4f3492af4d 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -467,11 +467,11 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) return new_(key, funNode, op, isStatic); } - MOZ_MUST_USE ClassField* newClassFieldDefinition(Node name, FunctionNodeType initializer) + MOZ_MUST_USE ClassField* newClassFieldDefinition(Node name, FunctionNodeType initializer, bool isStatic) { MOZ_ASSERT(isUsableAsObjectPropertyName(name)); - return new_(name, initializer); + return new_(name, initializer, isStatic); } MOZ_MUST_USE bool addClassMemberDefinition(ListNodeType memberList, Node member) diff --git a/js/src/frontend/ObjectEmitter.cpp b/js/src/frontend/ObjectEmitter.cpp index 0cb56c8f3e..bf597d0e95 100644 --- a/js/src/frontend/ObjectEmitter.cpp +++ b/js/src/frontend/ObjectEmitter.cpp @@ -484,51 +484,79 @@ bool ObjectEmitter::emitObject(size_t propertyCount) return true; } -bool ClassEmitter::prepareForFieldInitializers(size_t numFields) +bool ClassEmitter::prepareForFieldInitializers(size_t numFields, bool isStatic) { - MOZ_ASSERT(classState_ == ClassState::Class); + MOZ_ASSERT_IF(!isStatic, classState_ == ClassState::Class); + MOZ_ASSERT_IF(isStatic, classState_ == ClassState::InitConstructor); + MOZ_ASSERT(fieldState_ == FieldState::Start); // .initializers is a variable that stores an array of lambdas containing // code (the initializer) for each field. Upon an object's construction, // these lambdas will be called, defining the values. - initializersAssignment_.emplace(bce_, bce_->cx->names().dotInitializers, + HandlePropertyName initializers = isStatic ? bce_->cx->names().dotStaticInitializers + : bce_->cx->names().dotInitializers; + initializersAssignment_.emplace(bce_, initializers, NameOpEmitter::Kind::Initialize); if (!initializersAssignment_->prepareForRhs()) { return false; } if (!bce_->emitUint32Operand(JSOP_NEWARRAY, numFields)) { - // [stack] HOMEOBJ HERITAGE? ARRAY + // [stack] ARRAY return false; } - MOZ_ASSERT(fieldIndex_ == 0); + fieldIndex_ = 0; #ifdef DEBUG - classState_ = ClassState::FieldInitializers; + if (isStatic) { + classState_ = ClassState::StaticFieldInitializers; + } else { + classState_ = ClassState::InstanceFieldInitializers; + } numFields_ = numFields; #endif return true; } -bool ClassEmitter::emitFieldInitializerHomeObject() +bool ClassEmitter::prepareForFieldInitializer() { - MOZ_ASSERT(classState_ == ClassState::FieldInitializers); - // [stack] OBJ HERITAGE? ARRAY METHOD - if (!bce_->emit2(JSOP_INITHOMEOBJECT, isDerived_ ? 2 : 1)) { - // [stack] OBJ HERITAGE? ARRAY METHOD + MOZ_ASSERT(classState_ == ClassState::InstanceFieldInitializers || + classState_ == ClassState::StaticFieldInitializers); + MOZ_ASSERT(fieldState_ == FieldState::Start); + +#ifdef DEBUG + fieldState_ = FieldState::Initializer; +#endif + return true; +} + +bool ClassEmitter::emitFieldInitializerHomeObject(bool isStatic) +{ + MOZ_ASSERT(fieldState_ == FieldState::Initializer); + // [stack] OBJ HERITAGE? ARRAY METHOD + // or: + // [stack] CTOR HOMEOBJ ARRAY METHOD + uint8_t ofs = isStatic ? 2 + // [stack] CTOR HOMEOBJ ARRAY METHOD CTOR + : isDerived_ ? 2 : 1; + // [stack] OBJ HERITAGE? ARRAY METHOD OBJ + if (!bce_->emit2(JSOP_INITHOMEOBJECT, ofs)) { + // [stack] OBJ HERITAGE? ARRAY METHOD + // or: + // [stack] CTOR HOMEOBJ ARRAY METHOD return false; } #ifdef DEBUG - classState_ = ClassState::FieldInitializerWithHomeObject; + fieldState_ = FieldState::InitializerWithHomeObject; #endif return true; } bool ClassEmitter::emitStoreFieldInitializer() { - MOZ_ASSERT(classState_ == ClassState::FieldInitializers || - classState_ == ClassState::FieldInitializerWithHomeObject); + MOZ_ASSERT(fieldState_ == FieldState::Initializer || + fieldState_ == FieldState::InitializerWithHomeObject); MOZ_ASSERT(fieldIndex_ < numFields_); // [stack] HOMEOBJ HERITAGE? ARRAY METHOD @@ -539,7 +567,7 @@ bool ClassEmitter::emitStoreFieldInitializer() fieldIndex_++; #ifdef DEBUG - classState_ = ClassState::FieldInitializers; + fieldState_ = FieldState::Start; #endif return true; } @@ -548,8 +576,9 @@ bool ClassEmitter::emitFieldInitializersEnd() { MOZ_ASSERT(propertyState_ == PropertyState::Start || propertyState_ == PropertyState::Init); - MOZ_ASSERT(classState_ == ClassState::FieldInitializers || - classState_ == ClassState::FieldInitializerWithHomeObject); + MOZ_ASSERT(classState_ == ClassState::InstanceFieldInitializers || + classState_ == ClassState::StaticFieldInitializers); + MOZ_ASSERT(fieldState_ == FieldState::Start); MOZ_ASSERT(fieldIndex_ == numFields_); if (!initializersAssignment_->emitAssignment()) { @@ -564,7 +593,11 @@ bool ClassEmitter::emitFieldInitializersEnd() } #ifdef DEBUG - classState_ = ClassState::FieldInitializersEnd; + if (classState_ == ClassState::InstanceFieldInitializers) { + classState_ = ClassState::InstanceFieldInitializersEnd; + } else { + classState_ = ClassState::StaticFieldInitializersEnd; + } #endif return true; } @@ -700,7 +733,7 @@ bool ClassEmitter::emitInitConstructor(bool needsHomeObject) { MOZ_ASSERT(propertyState_ == PropertyState::Start); MOZ_ASSERT(classState_ == ClassState::Class || - classState_ == ClassState::FieldInitializersEnd); + classState_ == ClassState::InstanceFieldInitializersEnd); // [stack] HOMEOBJ CTOR @@ -726,8 +759,7 @@ bool ClassEmitter::emitInitDefaultConstructor(const Maybe& classStart, const Maybe& classEnd) { MOZ_ASSERT(propertyState_ == PropertyState::Start); - MOZ_ASSERT(classState_ == ClassState::Class || - classState_ == ClassState::FieldInitializersEnd); + MOZ_ASSERT(classState_ == ClassState::Class); if (classStart && classEnd) { // In the case of default class constructors, emit the start and end @@ -789,12 +821,13 @@ bool ClassEmitter::initProtoAndCtor() return true; } -bool ClassEmitter::emitEnd(Kind kind) +bool ClassEmitter::emitBinding() { MOZ_ASSERT(propertyState_ == PropertyState::Start || propertyState_ == PropertyState::Init); MOZ_ASSERT(classState_ == ClassState::InitConstructor || - classState_ == ClassState::FieldInitializersEnd); + classState_ == ClassState::InstanceFieldInitializersEnd || + classState_ == ClassState::StaticFieldInitializersEnd); // [stack] CTOR HOMEOBJ @@ -804,49 +837,54 @@ bool ClassEmitter::emitEnd(Kind kind) } if (name_ != bce_->cx->names().empty) { - MOZ_ASSERT(tdzCache_.isSome()); MOZ_ASSERT(innerScope_.isSome()); if (!bce_->emitLexicalInitialization(name_)) { // [stack] CTOR return false; } + } - // Pop the inner scope. - if (!innerScope_->leave(bce_)) - return false; - innerScope_.reset(); + // [stack] CTOR - if (kind == Kind::Declaration) { - if (!bce_->emitLexicalInitialization(name_)) { - // [stack] CTOR - return false; - } - // Only class statements make outer bindings, and they do not leave - // themselves on the stack. - if (!bce_->emit1(JSOP_POP)) { - // [stack] - return false; - } - } +#ifdef DEBUG + classState_ = ClassState::BoundName; +#endif + return true; +} - tdzCache_.reset(); - } else if (innerScope_.isSome()) { - // [stack] CTOR - MOZ_ASSERT(kind == Kind::Expression); +bool ClassEmitter::emitEnd(Kind kind) +{ + MOZ_ASSERT(classState_ == ClassState::BoundName); + // [stack] CTOR + + if (innerScope_.isSome()) { MOZ_ASSERT(tdzCache_.isSome()); if (!innerScope_->leave(bce_)) return false; innerScope_.reset(); tdzCache_.reset(); - }else { - // [stack] CTOR - + } else { MOZ_ASSERT(kind == Kind::Expression); MOZ_ASSERT(tdzCache_.isNothing()); } + if (kind == Kind::Declaration) { + MOZ_ASSERT(name_); + + if (!bce_->emitLexicalInitialization(name_)) { + // [stack] CTOR + return false; + } + // Only class statements make outer bindings, and they do not leave + // themselves on the stack. + if (!bce_->emit1(JSOP_POP)) { + // [stack] + return false; + } + } + // [stack] # class declaration // [stack] // [stack] # class expression diff --git a/js/src/frontend/ObjectEmitter.h b/js/src/frontend/ObjectEmitter.h index 38408f7650..96c9b49700 100644 --- a/js/src/frontend/ObjectEmitter.h +++ b/js/src/frontend/ObjectEmitter.h @@ -685,30 +685,20 @@ class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter // | // +-------------------------------+ // | - // | prepareForFieldInitializers - // +-----------------------------+ - // | | - // | | +-------------------+ - // | +--------------------->+--->| FieldInitializers |-+ - // | | +-------------------+ | - // | | | - // | | (emit initializer method) | - // | | +<--------------------------------------------+ - // | | | - // | | | emitFieldInitializerHomeObject +--------------------------------+ - // | | +-------------------------------->| FieldInitializerWithHomeObject |-+ - // | | | +--------------------------------+ | - // | | | | - // | | +------------------------------------------------------------------->+ - // | | | - // | | emitStoreFieldInitializer | - // | +<--+<-----------------------------------------------------------------------+ - // | | - // | | emitFieldInitializersEnd +----------------------+ - // | +-------------------------->| FieldInitializersEnd |-+ - // | +----------------------+ | - // | | - // |<------------------------------------------------------+ + // | prepareForFieldInitializers(isStatic = false) + // +---------------+ + // | | + // | +--------v------------------+ + // | | InstanceFieldInitializers | + // | +---------------------------+ + // | | + // | emitFieldInitializersEnd + // | | + // | +--------v---------------------+ + // | | InstanceFieldInitializersEnd | + // | +------------------------------+ + // | | + // +<--------------+ // | // | // | emitInitConstructor +-----------------+ @@ -717,11 +707,36 @@ class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter // | emitInitDefaultConstructor | | // +----------------------------+ | // | - // +---------------------------------------------------+ - // | - // | (do PropertyEmitter operation) emitEnd +-----+ - // +-------------------------------+--------->| End | - // +-----+ + // +-----------------------------------------------------+ + // | + // | prepareForFieldInitializers(isStatic = true) + // +---------------+ + // | | + // | +--------v----------------+ + // | | StaticFieldInitializers | + // | +-------------------------+ + // | | + // | | emitFieldInitializersEnd + // | | + // | +--------v-------------------+ + // | | StaticFieldInitializersEnd | + // | +----------------------------+ + // | | + // +<--------------+ + // | + // | (do PropertyEmitter operation) + // +--------------------------------+ + // | + // +-------------+ emitBinding | + // | BoundName |<-----------------+ + // +--+----------+ + // | + // | emitEnd + // | + // +--v----+ + // | End | + // +-------+ + // enum class ClassState { // The initial state. Start, @@ -735,20 +750,60 @@ class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter // After calling emitInitConstructor or emitInitDefaultConstructor. InitConstructor, - // After calling prepareForFieldInitializers - // and 0 or more calls to emitStoreFieldInitializer. - FieldInitializers, - - // After calling emitFieldInitializerHomeObject - FieldInitializerWithHomeObject, + // After calling prepareForFieldInitializers(isStatic = false). + InstanceFieldInitializers, // After calling emitFieldInitializersEnd. - FieldInitializersEnd, + InstanceFieldInitializersEnd, + + // After calling prepareForFieldInitializers(isStatic = true). + StaticFieldInitializers, + + // After calling emitFieldInitializersEnd. + StaticFieldInitializersEnd, + + // After calling emitBinding. + BoundName, // After calling emitEnd. End, }; ClassState classState_ = ClassState::Start; + + // The state of the fields emitter. + // + // clang-format off + // + // +-------+ + // | Start +<-----------------------------+ + // +-------+ | + // | | + // | prepareForFieldInitializer | emitStoreFieldInitializer + // v | + // +-------------+ | + // | Initializer +------------------------->+ + // +-------------+ | + // | | + // | emitFieldInitializerHomeObject | + // v | + // +---------------------------+ | + // | InitializerWithHomeObject +------------+ + // +---------------------------+ + // + // clang-format on + enum class FieldState { + // After calling prepareForFieldInitializers + // and 0 or more calls to emitStoreFieldInitializer. + Start, + + // After calling prepareForFieldInitializer + Initializer, + + // After calling emitFieldInitializerHomeObject + InitializerWithHomeObject, + }; + FieldState fieldState_ = FieldState::Start; + size_t numFields_ = 0; #endif @@ -783,11 +838,14 @@ class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter const mozilla::Maybe& classStart, const mozilla::Maybe& classEnd); - MOZ_MUST_USE bool prepareForFieldInitializers(size_t numFields); - MOZ_MUST_USE bool emitFieldInitializerHomeObject(); + MOZ_MUST_USE bool prepareForFieldInitializers(size_t numFields, bool isStatic); + MOZ_MUST_USE bool prepareForFieldInitializer(); + MOZ_MUST_USE bool emitFieldInitializerHomeObject(bool isStatic); MOZ_MUST_USE bool emitStoreFieldInitializer(); MOZ_MUST_USE bool emitFieldInitializersEnd(); + MOZ_MUST_USE bool emitBinding(); + MOZ_MUST_USE bool emitEnd(Kind kind); private: diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 554703da46..57a65a9e9b 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -761,6 +761,7 @@ class ParseNode private: friend class BinaryNode; friend class ForNode; + friend class ClassField; friend class ClassMethod; friend class PropertyAccessBase; friend class SwitchStatement; @@ -768,7 +769,7 @@ class ParseNode ParseNode* right; union { unsigned iflags; /* JSITER_* flags for PNK_{COMPREHENSION,}FOR node */ - bool isStatic; /* only for PNK_CLASSMETHOD */ + bool isStatic; /* only for PNK_CLASSMETHOD and PNK_CLASSFIELD */ bool hasDefault; /* only for PNK_SWITCH */ }; } binary; @@ -785,12 +786,6 @@ class ParseNode ParseNode* initOrStmt; /* var initializer, argument default, * or label statement target */ } name; - struct { - private: - friend class ClassField; - ParseNode* name; - ParseNode* initializer; /* field initializer - optional */ - } field; struct { private: friend class RegExpLiteral; @@ -2141,11 +2136,12 @@ class ClassMethod : public BinaryNode class ClassField : public BinaryNode { public: - ClassField(ParseNode* name, ParseNode* initializer) + ClassField(ParseNode* name, ParseNode* initializer, bool isStatic) : BinaryNode(PNK_CLASSFIELD, JSOP_NOP, TokenPos::box(name->pn_pos, initializer->pn_pos), name, initializer) { + pn_u.binary.isStatic = isStatic; } static bool test(const ParseNode& node) { @@ -2157,6 +2153,10 @@ class ClassField : public BinaryNode ParseNode& name() const { return *left(); } FunctionNode* initializer() const { return &right()->as(); } + + bool isStatic() const { + return pn_u.binary.isStatic; + } }; class SwitchStatement : public BinaryNode diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index b5044c6ca2..c319924fa7 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -7419,7 +7419,7 @@ Parser::classMember(YieldHandling yieldHandling, const ParseContext::ClassStatement& classStmt, HandlePropertyName className, uint32_t classStartOffset, bool hasHeritage, - size_t& numFields, size_t& numFieldKeys, + ClassFields& classFields, ListNodeType& classMembers, bool* done) { *done = false; @@ -7474,8 +7474,10 @@ Parser::classMember(YieldHandling yieldHandling, } if (isStatic) { - errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF); - return false; + if (propAtom == context->names().prototype) { + errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF); + return false; + } } if (propAtom == context->names().constructor) { @@ -7486,9 +7488,13 @@ Parser::classMember(YieldHandling yieldHandling, if (!abortIfSyntaxParser()) return false; - numFields++; + if (isStatic) { + classFields.staticFields++; + } else { + classFields.instanceFields++; + } - FunctionNodeType initializer = fieldInitializerOpt(propAtom, numFieldKeys); + FunctionNodeType initializer = fieldInitializerOpt(propAtom, classFields, isStatic); if (!initializer) return false; @@ -7496,7 +7502,7 @@ Parser::classMember(YieldHandling yieldHandling, return false; } - ClassFieldType field = handler.newClassFieldDefinition(propName, initializer); + ClassFieldType field = handler.newClassFieldDefinition(propName, initializer, isStatic); if (!field) return false; @@ -7613,12 +7619,12 @@ bool Parser::finishClassConstructor(const ParseContext::ClassStatement& classStmt, HandlePropertyName className, bool hasHeritage, uint32_t classStartOffset, uint32_t classEndOffset, - size_t numFields, - ListNodeType& classMembers) + const ClassFields& classFields, ListNodeType& classMembers) { // Fields cannot re-use the constructor obtained via JSOP_CLASSCONSTRUCTOR or // JSOP_DERIVEDCONSTRUCTOR due to needing to emit calls to the field // initializers in the constructor. So, synthesize a new one. + size_t numFields = classFields.instanceFields; if (classStmt.constructorBox == nullptr && numFields > 0) { MOZ_ASSERT(!options().selfHostingMode); // Unconditionally create the scope here, because it's always the @@ -7755,24 +7761,36 @@ Parser::classDefinition(YieldHandling yieldHandling, if (!classMembers) return null(); - size_t numFields = 0; - size_t numFieldKeys = 0; + ClassFields classFields{}; for (;;) { bool done; - if (!classMember(yieldHandling, classStmt, className, classStartOffset, - hasHeritage, numFields, numFieldKeys, classMembers, &done)) + if (!classMember(yieldHandling, classStmt, className, classStartOffset, hasHeritage, + classFields, classMembers, &done)) return null(); if (done) break; } - if (numFieldKeys > 0) { + if (classFields.instanceFieldKeys > 0) { if (!noteDeclaredName(context->names().dotFieldKeys, DeclarationKind::Let, namePos)) return null(); } + + if (classFields.staticFields > 0) { + if (!noteDeclaredName(context->names().dotStaticInitializers, + DeclarationKind::Let, namePos)) + return null(); + } + + if (classFields.staticFieldKeys > 0) { + if (!noteDeclaredName(context->names().dotStaticFieldKeys, + DeclarationKind::Let, namePos)) + return null(); + } + classEndOffset = pos().end; if (!finishClassConstructor(classStmt, className, hasHeritage, - classStartOffset, classEndOffset, numFields, classMembers)) + classStartOffset, classEndOffset, classFields, classMembers)) return null(); if (className) { @@ -7954,7 +7972,7 @@ Parser::synthesizeConstructor(HandleAtom className, uint32_t class template typename ParseHandler::FunctionNodeType -Parser::fieldInitializerOpt(HandleAtom propAtom, size_t& numFieldKeys) +Parser::fieldInitializerOpt(HandleAtom propAtom, ClassFields& classFields, bool isStatic) { bool hasInitializer = false; if (!tokenStream.matchToken(&hasInitializer, TOK_ASSIGN)) @@ -8052,17 +8070,18 @@ Parser::fieldInitializerOpt(HandleAtom propAtom, size_t& numFieldK if (!propAtom) { // See BytecodeEmitter::emitCreateFieldKeys for an explanation of what // .fieldKeys means and its purpose. - Node dotFieldKeys = newInternalDotName(context->names().dotFieldKeys); - if (!dotFieldKeys) + NameNodeType fieldKeysName = newInternalDotName(isStatic ? context->names().dotStaticFieldKeys + : context->names().dotFieldKeys); + if (!fieldKeysName) return null(); - double fieldKeyIndex = numFieldKeys; - numFieldKeys++; + double fieldKeyIndex = isStatic ? classFields.staticFieldKeys++ + : classFields.instanceFieldKeys++; Node fieldKeyIndexNode = handler.newNumber(fieldKeyIndex, DecimalPoint::NoDecimal, wholeInitializerPos); if (!fieldKeyIndexNode) return null(); - Node fieldKeyValue = handler.newPropertyByValue(dotFieldKeys, fieldKeyIndexNode, wholeInitializerPos.end); + Node fieldKeyValue = handler.newPropertyByValue(fieldKeysName, fieldKeyIndexNode, wholeInitializerPos.end); if (!fieldKeyValue) return null(); diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index fd0033ad7d..ec6595b908 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -1488,20 +1488,32 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) enum ClassContext { ClassStatement, ClassExpression }; ClassNodeType classDefinition(YieldHandling yieldHandling, ClassContext classContext, DefaultHandling defaultHandling); + struct ClassFields { + // The number of instance class fields. + size_t instanceFields = 0; + + // The number of instance class fields with computed property names. + size_t instanceFieldKeys = 0; + + // The number of static class fields. + size_t staticFields = 0; + + // The number of static class fields with computed property names. + size_t staticFieldKeys = 0; + }; MOZ_MUST_USE bool classMember(YieldHandling yieldHandling, const ParseContext::ClassStatement& classStmt, HandlePropertyName className, uint32_t classStartOffset, bool hasHeritage, - size_t& numFields, - size_t& numFieldKeys, + ClassFields& classFields, ListNodeType& classMembers, bool* done); MOZ_MUST_USE bool finishClassConstructor( const ParseContext::ClassStatement& classStmt, HandlePropertyName className, bool hasHeritage, uint32_t classStartOffset, uint32_t classEndOffset, - size_t numFieldsWithInitializers, ListNodeType& classMembers); + const ClassFields& classFields, ListNodeType& classMembers); - FunctionNodeType fieldInitializerOpt(HandleAtom atom, size_t& numFieldKeys); + FunctionNodeType fieldInitializerOpt(HandleAtom atom, ClassFields& classFields, bool isStatic); FunctionNodeType synthesizeConstructor(HandleAtom className, uint32_t classNameOffset, bool hasHeritage); diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index c1b6e989ab..b274ac642b 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -332,7 +332,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) MOZ_MUST_USE bool addSpreadProperty(ListNodeType literal, uint32_t begin, Node inner) { return true; } MOZ_MUST_USE bool addObjectMethodDefinition(ListNodeType literal, Node name, FunctionNodeType funNode, JSOp op) { return true; } MOZ_MUST_USE Node newClassMethodDefinition(Node key, FunctionNodeType funNode, JSOp op, bool isStatic) { return NodeGeneric; } - MOZ_MUST_USE Node newClassFieldDefinition(Node name, FunctionNodeType initializer) { return NodeGeneric; } + MOZ_MUST_USE Node newClassFieldDefinition(Node name, FunctionNodeType initializer, bool isStatic) { return NodeGeneric; } MOZ_MUST_USE bool addClassMemberDefinition(ListNodeType memberList, Node member) { return true; } UnaryNodeType newYieldExpression(uint32_t begin, Node value) { return NodeGeneric; } UnaryNodeType newYieldStarExpression(uint32_t begin, Node value) { return NodeGeneric; } diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index f7f324da5e..e1e9f56c31 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -105,6 +105,8 @@ macro(dotThis, dotThis, ".this") \ macro(dotInitializers, dotInitializers, ".initializers") \ macro(dotFieldKeys, dotFieldKeys, ".fieldKeys") \ + macro(dotStaticInitializers, dotStaticInitializers, ".staticInitializers") \ + macro(dotStaticFieldKeys, dotStaticFieldKeys, ".staticFieldKeys") \ macro(each, each, "each") \ macro(elementType, elementType, "elementType") \ macro(else, else_, "else") \ From 22a9d46ef19ae4cc45774fbb2d84ec294863cb81 Mon Sep 17 00:00:00 2001 From: Martok Date: Tue, 11 Apr 2023 03:02:33 +0200 Subject: [PATCH 20/23] Issue #2142 - Track isFieldInitializer on JSScript instead of Scope Introduce a FunctionSyntaxKind for FieldInitializer since special rules (around `arguments`) apply. At the same time we can move the flag from the scope to the JSScript. This is similar to how derived constructors are handled and makes the initWithEnclosingScope code closer to initWithEnclosingContext. This version is a bit more complex than Mozilla's due to different storage of bit flags on JSScript. Based-on: m-c 1636800 --- js/src/frontend/ParseNode.h | 4 ++- js/src/frontend/Parser.cpp | 46 ++++++++++++++++----------------- js/src/frontend/Parser.h | 5 ++-- js/src/frontend/SharedContext.h | 8 ++++-- js/src/jsfun.cpp | 13 ++++++++++ js/src/jsfun.h | 1 + js/src/jsscript.cpp | 7 +++++ js/src/jsscript.h | 19 +++++++++++++- js/src/vm/Scope.cpp | 13 +++------- js/src/vm/Scope.h | 9 +------ 10 files changed, 77 insertions(+), 48 deletions(-) diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 57a65a9e9b..4c9f1cc914 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -625,7 +625,8 @@ enum class FunctionSyntaxKind Expression, // A non-arrow function expression. Statement, // A named function appearing as a Statement. Arrow, - Method, // Method of a class or object. Field initializers also desugar to methods. + Method, // Method of a class or object. + FieldInitializer, // Field initializers desugar to methods. ClassConstructor, DerivedClassConstructor, Getter, @@ -659,6 +660,7 @@ static inline bool IsMethodDefinitionKind(FunctionSyntaxKind kind) { return kind == FunctionSyntaxKind::Method || + kind == FunctionSyntaxKind::FieldInitializer || IsConstructorKind(kind) || IsGetterKind(kind) || IsSetterKind(kind); } diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index c319924fa7..f69e3cf08a 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -221,7 +221,7 @@ SharedContext::computeAllowSyntax(Scope* scope) allowNewTarget_ = true; allowSuperProperty_ = fun->allowSuperProperty(); allowSuperCall_ = fun->isDerivedClassConstructor(); - if (funScope->isFieldInitializer()) { + if (fun->isFieldInitializer()) { allowSuperCall_ = false; allowArguments_ = false; } @@ -526,6 +526,11 @@ FunctionBox::initWithEnclosingParseContext(ParseContext* enclosing, FunctionSynt allowNewTarget_ = true; allowSuperProperty_ = fun->allowSuperProperty(); + if (kind == FunctionSyntaxKind::FieldInitializer) { + setFieldInitializer(); + allowArguments_ = false; + } + if (IsConstructorKind(kind)) { auto stmt = enclosing->findInnermostStatement(); MOZ_ASSERT(stmt); @@ -555,13 +560,6 @@ FunctionBox::initWithEnclosingParseContext(ParseContext* enclosing, FunctionSynt } } -void -FunctionBox::initFieldInitializer(ParseContext* enclosing) -{ - this->initWithEnclosingParseContext(enclosing, FunctionSyntaxKind::Method); - allowArguments_ = false; -} - void FunctionBox::initWithEnclosingScope(Scope* enclosingScope) { @@ -1883,8 +1881,7 @@ Parser::newEvalScopeData(ParseContext::Scope& scope) template <> Maybe -Parser::newFunctionScopeData(ParseContext::Scope& scope, bool hasParameterExprs, - bool isFieldInitializer) +Parser::newFunctionScopeData(ParseContext::Scope& scope, bool hasParameterExprs) { Vector positionalFormals(context); Vector formals(context); @@ -1958,8 +1955,6 @@ Parser::newFunctionScopeData(ParseContext::Scope& scope, bool if (!bindings) return Nothing(); - bindings->isFieldInitializer = isFieldInitializer; - // The ordering here is important. See comments in FunctionScope. BindingName* start = bindings->trailingNames.start(); BindingName* cursor = start; @@ -2408,8 +2403,7 @@ Parser::finishFunctionScopes(bool isStandaloneFunction) template <> bool -Parser::finishFunction(bool isStandaloneFunction /* = false */, - bool isFieldInitializer /* = false */) +Parser::finishFunction(bool isStandaloneFunction /* = false */) { if (!finishFunctionScopes(isStandaloneFunction)) return false; @@ -2426,8 +2420,7 @@ Parser::finishFunction(bool isStandaloneFunction /* = false */ { Maybe bindings = newFunctionScopeData(pc->functionScope(), - hasParameterExprs, - isFieldInitializer); + hasParameterExprs); if (!bindings) return false; funbox->functionScopeBindings().set(*bindings); @@ -2445,8 +2438,7 @@ Parser::finishFunction(bool isStandaloneFunction /* = false */ template <> bool -Parser::finishFunction(bool isStandaloneFunction /* = false */, - bool isFieldInitializer /* = false */) +Parser::finishFunction(bool isStandaloneFunction /* = false */) { // The LazyScript for a lazily parsed function needs to know its set of // free variables and inner functions so that when it is fully parsed, we @@ -2816,6 +2808,7 @@ Parser::newFunction(HandleAtom atom, FunctionSyntaxKind kind, allocKind = gc::AllocKind::FUNCTION_EXTENDED; break; case FunctionSyntaxKind::Method: + case FunctionSyntaxKind::FieldInitializer: MOZ_ASSERT(generatorKind == NotGenerator || generatorKind == StarGenerator); flags = (generatorKind == NotGenerator && asyncKind == SyncFunction ? JSFunction::INTERPRETED_METHOD @@ -3056,6 +3049,7 @@ Parser::functionArguments(YieldHandling yieldHandling, FunctionSyn bool duplicatedParam = false; bool disallowDuplicateParams = kind == FunctionSyntaxKind::Arrow || kind == FunctionSyntaxKind::Method || + kind == FunctionSyntaxKind::FieldInitializer || kind == FunctionSyntaxKind::ClassConstructor; AtomVector& positionalFormals = pc->positionalFormalParameterNames(); @@ -3614,7 +3608,11 @@ Parser::standaloneLazyFunction(HandleFunction fun, bool strict syntaxKind = FunctionSyntaxKind::ClassConstructor; } } else if (fun->isMethod()) { - syntaxKind = FunctionSyntaxKind::Method; + if (fun->isFieldInitializer()) { + syntaxKind = FunctionSyntaxKind::FieldInitializer; + } else { + syntaxKind = FunctionSyntaxKind::Method; + } } else if (fun->isGetter()) { syntaxKind = FunctionSyntaxKind::Getter; } else if (fun->isSetter()) { @@ -7991,15 +7989,16 @@ Parser::fieldInitializerOpt(HandleAtom propAtom, ClassFields& clas } // Create the anonymous function object. + FunctionSyntaxKind syntaxKind = FunctionSyntaxKind::FieldInitializer; RootedFunction fun(context, - newFunction(nullptr, FunctionSyntaxKind::Method, + newFunction(nullptr, syntaxKind, GeneratorKind::NotGenerator, FunctionAsyncKind::SyncFunction)); if (!fun) return null(); // Create the top-level field initializer node. - FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Method, firstTokenPos); + FunctionNodeType funNode = handler.newFunction(syntaxKind, firstTokenPos); if (!funNode) return null(); @@ -8010,7 +8009,8 @@ Parser::fieldInitializerOpt(HandleAtom propAtom, ClassFields& clas FunctionAsyncKind::SyncFunction, false); if (!funbox) return null(); - funbox->initFieldInitializer(pc); + funbox->initWithEnclosingParseContext(pc, syntaxKind); + MOZ_ASSERT(funbox->isFieldInitializer()); funbox->setStart(tokenStream, firstTokenPos); // Push a SourceParseContext on to the stack. @@ -8139,7 +8139,7 @@ Parser::fieldInitializerOpt(HandleAtom propAtom, ClassFields& clas funbox->setNeedsHomeObject(); } - if (!finishFunction(false, true)) + if (!finishFunction()) return null(); if (!leaveInnerFunction(outerpc)) diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index ec6595b908..d0318e405d 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -1582,7 +1582,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) bool tryAnnexB, Directives inheritedDirectives, Directives* newDirectives); bool finishFunctionScopes(bool isStandaloneFunction); - bool finishFunction(bool isStandaloneFunction = false, bool isFieldInitializer = false); + bool finishFunction(bool isStandaloneFunction = false); bool leaveInnerFunction(ParseContext* outerpc); bool matchOrInsertSemicolonHelper(TokenStream::Modifier modifier); @@ -1630,8 +1630,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) mozilla::Maybe newModuleScopeData(ParseContext::Scope& scope); mozilla::Maybe newEvalScopeData(ParseContext::Scope& scope); mozilla::Maybe newFunctionScopeData(ParseContext::Scope& scope, - bool hasParameterExprs, - bool isFieldInitializer); + bool hasParameterExprs); mozilla::Maybe newVarScopeData(ParseContext::Scope& scope); mozilla::Maybe newLexicalScopeData(ParseContext::Scope& scope); LexicalScopeNodeType finishLexicalScope(ParseContext::Scope& scope, Node body); diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h index 8aa068c1fc..4ac2da6fcd 100644 --- a/js/src/frontend/SharedContext.h +++ b/js/src/frontend/SharedContext.h @@ -155,6 +155,7 @@ class FunctionContextFlags bool needsHomeObject:1; bool isDerivedClassConstructor:1; + bool isFieldInitializer:1; // Whether this function has a .this binding. If true, we need to emit // JSOP_FUNCTIONTHIS in the prologue to initialize it. @@ -170,6 +171,7 @@ class FunctionContextFlags definitelyNeedsArgsObj(false), needsHomeObject(false), isDerivedClassConstructor(false), + isFieldInitializer(false), hasThisBinding(false), hasInnerFunctions(false) { } @@ -454,8 +456,7 @@ class FunctionBox : public ObjectBox, public SharedContext void initFromLazyFunction(); void initStandaloneFunction(Scope* enclosingScope); - void initWithEnclosingParseContext(ParseContext* enclosing, FunctionSyntaxKind kind); - void initFieldInitializer(ParseContext* enclosing); + void initWithEnclosingParseContext(ParseContext* enclosing, FunctionSyntaxKind kind); ObjectBox* toObjectBox() override { return this; } JSFunction* function() const { return &object->as(); } @@ -537,6 +538,7 @@ class FunctionBox : public ObjectBox, public SharedContext bool needsHomeObject() const { return funCxFlags.needsHomeObject; } bool isDerivedClassConstructor() const { return funCxFlags.isDerivedClassConstructor; } bool hasInnerFunctions() const { return funCxFlags.hasInnerFunctions; } + bool isFieldInitializer() const { return funCxFlags.isFieldInitializer; } void setHasExtensibleScope() { funCxFlags.hasExtensibleScope = true; } void setHasThisBinding() { funCxFlags.hasThisBinding = true; } @@ -548,6 +550,8 @@ class FunctionBox : public ObjectBox, public SharedContext void setDerivedClassConstructor() { MOZ_ASSERT(function()->isClassConstructor()); funCxFlags.isDerivedClassConstructor = true; } void setHasInnerFunctions() { funCxFlags.hasInnerFunctions = true; } + void setFieldInitializer() { MOZ_ASSERT(function()->isMethod()); + funCxFlags.isFieldInitializer = true; } bool hasSimpleParameterList() const { return !hasRest() && !hasParameterExprs && !hasDestructuringArgs; diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index 2f3bd8ca55..67df78c2f1 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -1286,6 +1286,19 @@ JSFunction::isDerivedClassConstructor() return derived; } +bool +JSFunction::isFieldInitializer() const +{ + bool init; + if (isInterpretedLazy()) { + init = lazyScript()->isFieldInitializer(); + } else { + init = nonLazyScript()->isFieldInitializer(); + } + MOZ_ASSERT_IF(init, isMethod()); + return init; +} + /* static */ bool JSFunction::getLength(JSContext* cx, HandleFunction fun, uint16_t* length) { diff --git a/js/src/jsfun.h b/js/src/jsfun.h index ad6eb07a2c..1833aaeea5 100644 --- a/js/src/jsfun.h +++ b/js/src/jsfun.h @@ -595,6 +595,7 @@ class JSFunction : public js::NativeObject } bool isDerivedClassConstructor(); + bool isFieldInitializer() const; static unsigned offsetOfNativeOrScript() { static_assert(offsetof(U, n.native) == offsetof(U, i.s.script_), diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 1a148578a1..a3f5f07068 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -350,6 +350,7 @@ js::XDRScript(XDRState* xdr, HandleScope scriptEnclosingScope, NeedsHomeObject, IsDerivedClassConstructor, IsDefaultClassConstructor, + IsFieldInitializer, }; uint32_t length, lineno, column, nfixed, nslots; @@ -474,6 +475,8 @@ js::XDRScript(XDRState* xdr, HandleScope scriptEnclosingScope, scriptBits |= (1 << IsDerivedClassConstructor); if (script->isDefaultClassConstructor()) scriptBits |= (1 << IsDefaultClassConstructor); + if (script->isFieldInitializer()) + scriptBits |= (1 << IsFieldInitializer); } if (!xdr->codeUint32(&prologueLength)) @@ -620,6 +623,8 @@ js::XDRScript(XDRState* xdr, HandleScope scriptEnclosingScope, script->isDerivedClassConstructor_ = true; if (scriptBits & (1 << IsDefaultClassConstructor)) script->isDefaultClassConstructor_ = true; + if (scriptBits & (1 << IsFieldInitializer)) + script->isFieldInitializer_ = true; if (scriptBits & (1 << IsLegacyGenerator)) { MOZ_ASSERT(!(scriptBits & (1 << IsStarGenerator))); @@ -2807,6 +2812,7 @@ JSScript::initFromFunctionBox(ExclusiveContext* cx, HandleScript script, script->funHasExtensibleScope_ = funbox->hasExtensibleScope(); script->needsHomeObject_ = funbox->needsHomeObject(); script->isDerivedClassConstructor_ = funbox->isDerivedClassConstructor(); + script->isFieldInitializer_ = funbox->isFieldInitializer(); if (funbox->argumentsHasLocalBinding()) { script->setArgumentsHasVarBinding(); @@ -3485,6 +3491,7 @@ js::detail::CopyScript(JSContext* cx, HandleScript src, HandleScript dst, dst->isGeneratorExp_ = src->isGeneratorExp(); dst->setGeneratorKind(src->generatorKind()); dst->isDerivedClassConstructor_ = src->isDerivedClassConstructor(); + dst->isFieldInitializer_ = src->isFieldInitializer(); dst->needsHomeObject_ = src->needsHomeObject(); dst->isDefaultClassConstructor_ = src->isDefaultClassConstructor(); dst->isAsync_ = src->asyncKind() == AsyncFunction; diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 5534df5806..5d6c09dba6 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -1120,6 +1120,8 @@ class JSScript : public js::gc::TenuredCell bool isDerivedClassConstructor_:1; bool isDefaultClassConstructor_:1; + bool isFieldInitializer_:1; + bool isAsync_:1; bool hasRest_:1; @@ -1129,7 +1131,10 @@ class JSScript : public js::gc::TenuredCell // instead of private to suppress -Wunused-private-field compiler warnings. protected: #if JS_BITS_PER_WORD == 32 - // Currently no padding is needed. +# ifndef DEBUG + // DEBUG is currently 4 bytes larger and doesn't need padding to gc::CellSize + uint32_t padding_; +# endif #endif // @@ -1458,6 +1463,10 @@ class JSScript : public js::gc::TenuredCell return isDerivedClassConstructor_; } + bool isFieldInitializer() const { + return isFieldInitializer_; + } + /* * As an optimization, even when argsHasLocalBinding, the function prologue * may not need to create an arguments object. This is determined by @@ -2096,6 +2105,7 @@ class LazyScript : public gc::TenuredCell uint32_t hasBeenCloned : 1; uint32_t treatAsRunOnce : 1; uint32_t isDerivedClassConstructor : 1; + uint32_t isFieldInitializer : 1; uint32_t needsHomeObject : 1; uint32_t hasRest : 1; uint32_t parseGoal : 1; @@ -2312,6 +2322,13 @@ class LazyScript : public gc::TenuredCell p_.isDerivedClassConstructor = true; } + bool isFieldInitializer() const { + return p_.isFieldInitializer; + } + void setIsFieldInitializer() { + p_.isFieldInitializer = true; + } + bool needsHomeObject() const { return p_.needsHomeObject; } diff --git a/js/src/vm/Scope.cpp b/js/src/vm/Scope.cpp index 870add219f..60d845836e 100644 --- a/js/src/vm/Scope.cpp +++ b/js/src/vm/Scope.cpp @@ -610,14 +610,12 @@ FunctionScope::create(ExclusiveContext* cx, Handle dataArg, if (!data) return nullptr; - return createWithData(cx, &data, hasParameterExprs, dataArg ? dataArg->isFieldInitializer : false, - needsEnvironment, fun, enclosing); + return createWithData(cx, &data, hasParameterExprs, needsEnvironment, fun, enclosing); } /* static */ FunctionScope* FunctionScope::createWithData(ExclusiveContext* cx, MutableHandle> data, - bool hasParameterExprs, bool isFieldInitializer, - bool needsEnvironment, + bool hasParameterExprs, bool needsEnvironment, HandleFunction fun, HandleScope enclosing) { MOZ_ASSERT(data); @@ -638,7 +636,6 @@ FunctionScope::createWithData(ExclusiveContext* cx, MutableHandleisFieldInitializer = isFieldInitializer; data->hasParameterExprs = hasParameterExprs; data->canonicalFunction.init(fun); @@ -740,20 +737,16 @@ FunctionScope::XDR(XDRState* xdr, HandleFunction fun, HandleScope enclosin uint8_t needsEnvironment; uint8_t hasParameterExprs; - uint8_t isFieldInitializer; uint32_t nextFrameSlot; if (mode == XDR_ENCODE) { needsEnvironment = scope->hasEnvironment(); hasParameterExprs = data->hasParameterExprs; - isFieldInitializer = data->isFieldInitializer; nextFrameSlot = data->nextFrameSlot; } if (!xdr->codeUint8(&needsEnvironment)) return false; if (!xdr->codeUint8(&hasParameterExprs)) return false; - if (!xdr->codeUint8(&isFieldInitializer)) - return false; if (!xdr->codeUint16(&data->nonPositionalFormalStart)) return false; if (!xdr->codeUint16(&data->varStart)) @@ -768,7 +761,7 @@ FunctionScope::XDR(XDRState* xdr, HandleFunction fun, HandleScope enclosin MOZ_ASSERT(!data->nextFrameSlot); } - scope.set(createWithData(cx, &uniqueData.ref(), hasParameterExprs, !!isFieldInitializer, + scope.set(createWithData(cx, &uniqueData.ref(), hasParameterExprs, needsEnvironment, fun, enclosing)); if (!scope) return false; diff --git a/js/src/vm/Scope.h b/js/src/vm/Scope.h index a1e52e3800..b4e83dfc9c 100644 --- a/js/src/vm/Scope.h +++ b/js/src/vm/Scope.h @@ -500,9 +500,6 @@ class FunctionScope : public Scope // bindings. bool hasParameterExprs = false; - // Anonymous functions used in field initializers are limited. - bool isFieldInitializer = false; - // Bindings are sorted by kind in both frames and environments. // // Positional formal parameter names are those that are not @@ -551,7 +548,7 @@ class FunctionScope : public Scope private: static FunctionScope* createWithData(ExclusiveContext* cx, MutableHandle> data, - bool hasParameterExprs, bool isFieldInitializer, + bool hasParameterExprs, bool needsEnvironment, HandleFunction fun, HandleScope enclosing); @@ -578,10 +575,6 @@ class FunctionScope : public Scope return data().hasParameterExprs; } - bool isFieldInitializer() const { - return data().isFieldInitializer; - } - uint32_t numPositionalFormalParameters() const { return data().nonPositionalFormalStart; } From 1fac189aeaaacdcba49c71389f608256ba3571c0 Mon Sep 17 00:00:00 2001 From: Martok Date: Tue, 11 Apr 2023 09:48:34 +0200 Subject: [PATCH 21/23] Issue #2142 - Implement class static block Based-on: m-c 1712138/{2,3}, 1713155/2 --- js/src/builtin/ReflectParse.cpp | 38 +++++- js/src/frontend/BytecodeEmitter.cpp | 41 +++++-- js/src/frontend/FoldConstants.cpp | 2 + js/src/frontend/FullParseHandler.h | 6 + js/src/frontend/ParseNode.cpp | 1 + js/src/frontend/ParseNode.h | 30 ++++- js/src/frontend/Parser.cpp | 169 +++++++++++++++++++++++---- js/src/frontend/Parser.h | 19 ++- js/src/frontend/SharedContext.h | 4 + js/src/frontend/SyntaxParseHandler.h | 1 + js/src/js.msg | 1 + js/src/jsast.tbl | 1 + 12 files changed, 278 insertions(+), 35 deletions(-) diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index 6ca0c30e16..b40c5fb408 100644 --- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -544,6 +544,7 @@ class NodeBuilder TokenPos* pos, MutableHandleValue dst); MOZ_MUST_USE bool classField(HandleValue name, HandleValue initializer, TokenPos* pos, MutableHandleValue dst); + MOZ_MUST_USE bool staticClassBlock(HandleValue body, TokenPos* pos, MutableHandleValue dst); /* * expressions @@ -1736,6 +1737,18 @@ NodeBuilder::classField(HandleValue name, HandleValue initializer, dst); } +bool +NodeBuilder::staticClassBlock(HandleValue body, TokenPos* pos, MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_STATIC_CLASS_BLOCK]); + if (!cb.isNull()) + return callback(cb, body, pos, dst); + + return newNode(AST_STATIC_CLASS_BLOCK, pos, + "body", body, + dst); +} + bool NodeBuilder::classMembers(NodeVector& members, MutableHandleValue dst) { @@ -1870,6 +1883,7 @@ class ASTSerializer bool classMethod(ClassMethod* classMethod, MutableHandleValue dst); bool classField(ClassField* classField, MutableHandleValue dst); + bool staticClassBlock(StaticClassBlock* staticClassBlock, MutableHandleValue dst); bool optIdentifier(HandleAtom atom, TokenPos* pos, MutableHandleValue dst) { if (!atom) { @@ -2709,7 +2723,14 @@ ASTSerializer::statement(ParseNode* pn, MutableHandleValue dst) RootedValue prop(cx); if (!classField(field, &prop)) - return false; + return false; + members.infallibleAppend(prop); + } else if (item->is()) { + StaticClassBlock* scb = &item->as(); + MOZ_ASSERT(memberList->pn_pos.encloses(scb->pn_pos)); + RootedValue prop(cx); + if (!staticClassBlock(scb, &prop)) + return false; members.infallibleAppend(prop); } else { ClassMethod* method = &item->as(); @@ -2787,6 +2808,21 @@ ASTSerializer::classField(ClassField* classField, MutableHandleValue dst) builder.classField(key, val, &classField->pn_pos, dst); } +bool +ASTSerializer::staticClassBlock(StaticClassBlock* staticClassBlock, MutableHandleValue dst) +{ + FunctionNode* fun = staticClassBlock->function(); + + NodeVector args(cx); + NodeVector defaults(cx); + + RootedValue body(cx), rest(cx); + rest.setNull(); + return functionArgsAndBody(fun->body(), args, defaults, false, false, + &body, &rest) && + builder.staticClassBlock(body, &staticClassBlock->pn_pos, dst); +} + bool ASTSerializer::leftAssociate(ListNode* node, MutableHandleValue dst) { diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 1524493cb0..eb25903911 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -1529,6 +1529,7 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) case PNK_CLASSMETHOD: // by PNK_CLASS case PNK_CLASSFIELD: // by PNK_CLASS case PNK_CLASSNAMES: // by PNK_CLASS + case PNK_STATICCLASSBLOCK:// by PNK_CLASS case PNK_CLASSMEMBERLIST: // by PNK_CLASS case PNK_IMPORT_SPEC_LIST: // by PNK_IMPORT case PNK_IMPORT_SPEC: // by PNK_IMPORT @@ -7702,6 +7703,12 @@ BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListTy continue; } + if (propdef->is()) { + // Static class blocks are emitted as part of + // emitCreateFieldInitializers. + continue; + } + if (propdef->is()) { // Constructors are sometimes wrapped in LexicalScopeNodes. As we already // handled emitting the constructor, skip it. @@ -7943,13 +7950,32 @@ BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListTy return true; } +static bool +HasInitializer(ParseNode* node, bool isStaticContext) +{ + // For the purposes of bytecode emission, StaticClassBlocks are treated as if + // they were static initializers. + return (node->is() && + node->as().isStatic() == isStaticContext) || + (isStaticContext && node->is()); +} + +static FunctionNode* +GetInitializer(ParseNode* node, bool isStaticContext) +{ + MOZ_ASSERT(HasInitializer(node, isStaticContext)); + MOZ_ASSERT_IF(!node->is(), isStaticContext); + return node->is() ? node->as().initializer() + : node->as().function(); +} + + FieldInitializers BytecodeEmitter::setupFieldInitializers(ListNode* classMembers, FieldPlacement placement) { bool isStatic = placement == FieldPlacement::Static; size_t numFields = classMembers->count_if([isStatic](ParseNode* propdef) { - return propdef->is()&& - propdef->as().isStatic() == isStatic; + return HasInitializer(propdef, isStatic); }); return FieldInitializers(numFields); @@ -8040,11 +8066,10 @@ BytecodeEmitter::emitCreateFieldInitializers(ClassEmitter& ce, ListNode* obj, } for (ParseNode* propdef : obj->contents()) { - if (!propdef->is() || - propdef->as().isStatic() != isStatic) + if (!HasInitializer(propdef, isStatic)) continue; - FunctionNode* initializer = propdef->as().initializer(); + FunctionNode* initializer = GetInitializer(propdef, isStatic); if (!ce.prepareForFieldInitializer()) return false; if (!emitTree(initializer)) { @@ -8173,8 +8198,7 @@ bool BytecodeEmitter::emitInitializeStaticFields(ListNode* classMembers) { size_t numFields = classMembers->count_if([](ParseNode* propdef) { - return propdef->is()&& - propdef->as().isStatic(); + return HasInitializer(propdef, true); }); if (numFields == 0) { @@ -8816,8 +8840,7 @@ BytecodeEmitter::emitClass(ClassNode* classNode) // As an optimization omit the |.initializers| binding when no instance // fields are present. bool hasInstanceFields = classMembers->any_of([](ParseNode* propdef) { - return propdef->is() && - !propdef->as().isStatic(); + return HasInitializer(propdef, false); }); if (hasInstanceFields) { lse.emplace(this); diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index bb8f0d11b6..813900a23f 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -403,6 +403,7 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result) case PNK_FOROF: case PNK_FORHEAD: case PNK_CLASSFIELD: + case PNK_STATICCLASSBLOCK: case PNK_CLASSMEMBERLIST: case PNK_CLASSNAMES: case PNK_NEWTARGET: @@ -1749,6 +1750,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& parser, bo case PNK_ARRAYPUSH: case PNK_MUTATEPROTO: case PNK_COMPUTED_NAME: + case PNK_STATICCLASSBLOCK: case PNK_SPREAD: case PNK_EXPORT: case PNK_VOID: diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index 4f3492af4d..14733d74f2 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -474,12 +474,18 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) return new_(name, initializer, isStatic); } + MOZ_MUST_USE StaticClassBlock* newStaticClassBlock(FunctionNodeType block) + { + return new_(block); + } + MOZ_MUST_USE bool addClassMemberDefinition(ListNodeType memberList, Node member) { MOZ_ASSERT(memberList->isKind(PNK_CLASSMEMBERLIST)); // Constructors can be surrounded by LexicalScopes. MOZ_ASSERT(member->isKind(PNK_CLASSMETHOD) || member->isKind(PNK_CLASSFIELD) || + member->isKind(PNK_STATICCLASSBLOCK) || (member->isKind(PNK_LEXICALSCOPE) && member->as().scopeBody()->isKind(PNK_CLASSMETHOD))); diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index de3ea60931..a529c93d14 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -236,6 +236,7 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) case PNK_PREDECREMENT: case PNK_POSTDECREMENT: case PNK_COMPUTED_NAME: + case PNK_STATICCLASSBLOCK: case PNK_ARRAYPUSH: case PNK_SPREAD: case PNK_MUTATEPROTO: diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 4c9f1cc914..471f42f905 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -117,6 +117,7 @@ class ObjectBox; F(MUTATEPROTO) \ F(CLASS) \ F(CLASSMETHOD) \ + F(STATICCLASSBLOCK) \ F(CLASSFIELD) \ F(CLASSMEMBERLIST) \ F(CLASSNAMES) \ @@ -259,7 +260,7 @@ IsTypeofKind(ParseNodeKind kind) * that doesn't create an outer binding * right: Name node for inner binding * PNK_CLASSMEMBERLIST (ListNode) - * head: list of N PNK_CLASSMETHOD or PNK_CLASSFIELD nodes + * head: list of N PNK_CLASSMETHOD, PNK_CLASSFIELD or PNK_STATICCLASSBLOCK nodes * count: N >= 0 * PNK_CLASSMETHOD (ClassMethod) * name: propertyName @@ -267,6 +268,8 @@ IsTypeofKind(ParseNodeKind kind) * PNK_CLASSFIELD (ClassField) * name: fieldName * initializer: field initializer or null + * PNK_STATICCLASSBLOCK (StaticClassBlock) + * block: block initializer * PNK_MODULE (ModuleNode) * body: statement list of the module * @@ -574,6 +577,7 @@ enum ParseNodeArity macro(CaseClause, CaseClauseType, asCaseClause) \ macro(ClassMethod, ClassMethodType, asClassMethod) \ macro(ClassField, ClassFieldType, asClassField) \ + macro(StaticClassBlock, StaticClassBlockType, asStaticClassBlock) \ macro(ClassNames, ClassNamesType, asClassNames) \ macro(ForNode, ForNodeType, asFor) \ macro(PropertyAccess, PropertyAccessType, asPropertyAccess) \ @@ -627,6 +631,8 @@ enum class FunctionSyntaxKind Arrow, Method, // Method of a class or object. FieldInitializer, // Field initializers desugar to methods. + StaticClassBlock, // Mostly static class blocks act similar to field initializers, however, + // there is some difference in static semantics. ClassConstructor, DerivedClassConstructor, Getter, @@ -2161,6 +2167,28 @@ class ClassField : public BinaryNode } }; +// Hold onto the function generated for a class static block like +// +// class A { +// static { /* this static block */ } +// } +// +class StaticClassBlock : public UnaryNode +{ + public: + explicit StaticClassBlock(FunctionNode* function) + : UnaryNode(PNK_STATICCLASSBLOCK, JSOP_NOP, function->pn_pos, function) { + } + + static bool test(const ParseNode& node) { + bool match = node.isKind(PNK_STATICCLASSBLOCK); + MOZ_ASSERT_IF(match, node.is()); + return match; + } + FunctionNode* function() const { return &kid()->as(); } +}; + + class SwitchStatement : public BinaryNode { public: diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index f69e3cf08a..7c8df7d498 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -463,6 +463,7 @@ FunctionBox::FunctionBox(ExclusiveContext* cx, LifoAlloc& alloc, ObjectBox* trac hasParameterExprs(false), hasDirectEvalInParameterExpr(false), hasDuplicateParameters(false), + allowReturn_(true), useAsm(false), insideUseAsm(false), isAnnexB(false), @@ -526,27 +527,34 @@ FunctionBox::initWithEnclosingParseContext(ParseContext* enclosing, FunctionSynt allowNewTarget_ = true; allowSuperProperty_ = fun->allowSuperProperty(); - if (kind == FunctionSyntaxKind::FieldInitializer) { - setFieldInitializer(); - allowArguments_ = false; - } + if (isGenexpLambda) + thisBinding_ = sc->thisBinding(); + else + thisBinding_ = ThisBinding::Function; if (IsConstructorKind(kind)) { auto stmt = enclosing->findInnermostStatement(); MOZ_ASSERT(stmt); stmt->constructorBox = this; - - if (kind == FunctionSyntaxKind::DerivedClassConstructor) { - setDerivedClassConstructor(); - allowSuperCall_ = true; - needsThisTDZChecks_ = true; - } } - if (isGenexpLambda) - thisBinding_ = sc->thisBinding(); - else - thisBinding_ = ThisBinding::Function; + if (kind == FunctionSyntaxKind::DerivedClassConstructor) { + setDerivedClassConstructor(); + allowSuperCall_ = true; + needsThisTDZChecks_ = true; + } + + if (kind == FunctionSyntaxKind::FieldInitializer || + kind == FunctionSyntaxKind::StaticClassBlock) { + allowArguments_ = false; + if (kind == FunctionSyntaxKind::StaticClassBlock) { + allowSuperCall_ = false; + allowReturn_ = false; + } else { + MOZ_ASSERT(kind == FunctionSyntaxKind::FieldInitializer); + setFieldInitializer(); + } + } } if (sc->inWith()) { @@ -2809,6 +2817,7 @@ Parser::newFunction(HandleAtom atom, FunctionSyntaxKind kind, break; case FunctionSyntaxKind::Method: case FunctionSyntaxKind::FieldInitializer: + case FunctionSyntaxKind::StaticClassBlock: MOZ_ASSERT(generatorKind == NotGenerator || generatorKind == StarGenerator); flags = (generatorKind == NotGenerator && asyncKind == SyncFunction ? JSFunction::INTERPRETED_METHOD @@ -3686,10 +3695,12 @@ Parser::functionFormalParametersAndBody(InHandling inHandling, // See below for an explanation why arrow function parameters and arrow // function bodies are parsed with different yield/await settings. { - AwaitHandling awaitHandling = funbox->isAsync() || - (kind == FunctionSyntaxKind::Arrow && awaitIsKeyword()) - ? AwaitIsKeyword - : AwaitIsName; + AwaitHandling awaitHandling = kind == FunctionSyntaxKind::StaticClassBlock + ? AwaitIsDisallowed + : (funbox->isAsync() || + (kind == FunctionSyntaxKind::Arrow && awaitIsKeyword())) + ? AwaitIsKeyword + : AwaitIsName; AutoAwaitIsKeyword awaitIsKeyword(this, awaitHandling); if (!functionArguments(yieldHandling, kind, funNode)) return false; @@ -7438,12 +7449,26 @@ Parser::classMember(YieldHandling yieldHandling, 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_LC) { + /* Parsing static class block: static { ... } */ + FunctionNodeType staticBlockBody = staticClassBlock(classFields); + if (!staticBlockBody) + return false; + + StaticClassBlockType classBlock = handler.newStaticClassBlock(staticBlockBody); + if (!classBlock) + return false; + + return handler.addClassMemberDefinition(classMembers, classBlock); + } + if (tt != TOK_LP) { isStatic = true; } else { @@ -7968,6 +7993,108 @@ Parser::synthesizeConstructor(HandleAtom className, uint32_t class return funNode; } +template +typename ParseHandler::FunctionNodeType +Parser::staticClassBlock(ClassFields& classFields) +{ + // Both for getting-this-done, and because this will invariably be executed, + // syntax parsing should be aborted. + if (!abortIfSyntaxParser()) + return null(); + + TokenPos firstTokenPos(pos()); + + // Create the anonymous function object. + FunctionSyntaxKind syntaxKind = FunctionSyntaxKind::StaticClassBlock; + AutoAwaitIsKeyword awaitIsKeyword(this, AwaitIsDisallowed); + + RootedFunction fun(context, + newFunction(nullptr, syntaxKind, + GeneratorKind::NotGenerator, + FunctionAsyncKind::SyncFunction)); + if (!fun) + return null(); + + // Create the function node for the static class body. + FunctionNodeType funNode = handler.newFunction(syntaxKind, 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->initWithEnclosingParseContext(pc, syntaxKind); + MOZ_ASSERT(!funbox->allowSuperCall()); + MOZ_ASSERT(!funbox->allowArguments()); + MOZ_ASSERT(!funbox->allowReturn()); + + // Set start at `static` token. + MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_STATIC)); + 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(); + + pc->functionScope().useAsVarScope(pc); + + uint32_t start = firstTokenPos.begin; + + tokenStream.consumeKnownToken(TOK_LC); + + // Static class blocks are code-generated as if they were static field + // initializers, so we bump the staticFields count here, which ensures + // .staticInitializers is noted as used. + classFields.staticFields++; + + LexicalScopeNodeType body = functionBody(InAllowed, YieldIsKeyword, syntaxKind, + StatementListBody); + if (!body) + return null(); + + if (tokenStream.isEOF()) { + error(JSMSG_UNTERMINATED_STATIC_CLASS_BLOCK); + return null(); + } + + tokenStream.consumeKnownToken(TOK_RC, TokenStream::Operand); + + TokenPos wholeBodyPos(start, pos().end); + + handler.setEndPosition(funNode, wholeBodyPos.end); + funbox->setEnd(pos().end); + + // Create a ListNode for the parameters + body (there are no parameters). + ListNodeType argsbody = handler.newList(PNK_PARAMSBODY, wholeBodyPos); + if (!argsbody) + return null(); + + handler.setFunctionFormalParametersAndBody(funNode, argsbody); + funbox->function()->setArgCount(0); + + if (pc->superScopeNeedsHomeObject()) { + funbox->setNeedsHomeObject(); + } + + handler.setEndPosition(body, pos().begin); + handler.setEndPosition(funNode, pos().end); + handler.setFunctionBody(funNode, body); + + if (!finishFunction()) + return null(); + + if (!leaveInnerFunction(outerpc)) + return null(); + + return funNode; +} + template typename ParseHandler::FunctionNodeType Parser::fieldInitializerOpt(HandleAtom propAtom, ClassFields& classFields, bool isStatic) @@ -8349,7 +8476,7 @@ Parser::statement(YieldHandling yieldHandling) // The Return parameter is only used here, and the effect is easily // detected this way, so don't bother passing around an extra parameter // everywhere. - if (!pc->isFunctionBox()) { + if (!pc->allowReturn()) { error(JSMSG_BAD_RETURN_OR_YIELD, js_return_str); return null(); } @@ -8535,7 +8662,7 @@ Parser::statementListItem(YieldHandling yieldHandling, // The Return parameter is only used here, and the effect is easily // detected this way, so don't bother passing around an extra parameter // everywhere. - if (!pc->isFunctionBox()) { + if (!pc->allowReturn()) { error(JSMSG_BAD_RETURN_OR_YIELD, js_return_str); return null(); } @@ -10261,7 +10388,7 @@ Parser::checkLabelOrIdentifierReference(PropertyName* ident, return true; } if (tt == TOK_AWAIT) { - if (awaitIsKeyword()) { + if (awaitIsKeyword() || awaitIsDisallowed()) { errorAt(offset, JSMSG_RESERVED_ID, "await"); return false; } diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index d0318e405d..6976487c50 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -526,6 +526,10 @@ class ParseContext : public Nestable return sc_->isFunctionBox() && sc_->asFunctionBox()->function()->isMethod(); } + bool allowReturn() const { + return sc_->isFunctionBox() && sc_->asFunctionBox()->allowReturn(); + } + uint32_t scriptId() const { return scriptId_; } @@ -588,11 +592,11 @@ enum class PropertyType { }; // Specify a value for an ES6 grammar parametrization. We have no enum for -// [Return] because its behavior is exactly equivalent to checking whether +// [Return] because its behavior is almost exactly equivalent to checking whether // we're in a function box -- easier and simpler than passing an extra // parameter everywhere. enum YieldHandling { YieldIsName, YieldIsKeyword }; -enum AwaitHandling : uint8_t { AwaitIsName, AwaitIsKeyword, AwaitIsModuleKeyword }; +enum AwaitHandling : uint8_t { AwaitIsName, AwaitIsKeyword, AwaitIsModuleKeyword, AwaitIsDisallowed }; enum InHandling { InAllowed, InProhibited }; enum DefaultHandling { NameRequired, AllowDefaultName }; enum TripledotHandling { TripledotAllowed, TripledotProhibited }; @@ -817,7 +821,10 @@ class ParserBase : public StrictModeGetter public: bool awaitIsKeyword() const { - return awaitHandling_ != AwaitIsName; + return awaitHandling_ == AwaitIsKeyword || awaitHandling_ == AwaitIsModuleKeyword; + } + bool awaitIsDisallowed() const { + return awaitHandling_ == AwaitIsDisallowed; } ParseGoal parseGoal() const { @@ -1450,6 +1457,8 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) // Parse a function body. Pass StatementListBody if the body is a list of // statements; pass ExpressionBody if the body is a single expression. + // + // Don't include opening LeftCurly token when invoking. enum FunctionBodyType { StatementListBody, ExpressionBody }; LexicalScopeNodeType functionBody(InHandling inHandling, YieldHandling yieldHandling, FunctionSyntaxKind kind, FunctionBodyType type); @@ -1498,6 +1507,9 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) // The number of static class fields. size_t staticFields = 0; + // The number of static blocks + size_t staticBlocks = 0; + // The number of static class fields with computed property names. size_t staticFieldKeys = 0; }; @@ -1514,6 +1526,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) const ClassFields& classFields, ListNodeType& classMembers); FunctionNodeType fieldInitializerOpt(HandleAtom atom, ClassFields& classFields, bool isStatic); + FunctionNodeType staticClassBlock(ClassFields& classFields); FunctionNodeType synthesizeConstructor(HandleAtom className, uint32_t classNameOffset, bool hasHeritage); diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h index 4ac2da6fcd..29a8cbd18f 100644 --- a/js/src/frontend/SharedContext.h +++ b/js/src/frontend/SharedContext.h @@ -432,6 +432,8 @@ class FunctionBox : public ObjectBox, public SharedContext bool isExprBody_:1; /* arrow function with expression * body or expression closure: * function(x) x*x */ + bool allowReturn_ : 1; /* Used to issue an early error in static class blocks. */ + FunctionContextFlags funCxFlags; @@ -523,6 +525,8 @@ class FunctionBox : public ObjectBox, public SharedContext isExprBody_ = true; } + bool allowReturn() const { return allowReturn_; } + void setGeneratorKind(GeneratorKind kind) { // A generator kind can be set at initialization, or when "yield" is // first seen. In both cases the transition can only happen from diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index b274ac642b..130a5da61d 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -333,6 +333,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) MOZ_MUST_USE bool addObjectMethodDefinition(ListNodeType literal, Node name, FunctionNodeType funNode, JSOp op) { return true; } MOZ_MUST_USE Node newClassMethodDefinition(Node key, FunctionNodeType funNode, JSOp op, bool isStatic) { return NodeGeneric; } MOZ_MUST_USE Node newClassFieldDefinition(Node name, FunctionNodeType initializer, bool isStatic) { return NodeGeneric; } + MOZ_MUST_USE Node newStaticClassBlock(FunctionNodeType block) { return NodeGeneric; } MOZ_MUST_USE bool addClassMemberDefinition(ListNodeType memberList, Node member) { return true; } UnaryNodeType newYieldExpression(uint32_t begin, Node value) { return NodeGeneric; } UnaryNodeType newYieldStarExpression(uint32_t begin, Node value) { return NodeGeneric; } diff --git a/js/src/js.msg b/js/src/js.msg index 2cae3ff125..91637edc6a 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -347,6 +347,7 @@ MSG_DEF(JSMSG_UNNAMED_CLASS_STMT, 0, JSEXN_SYNTAXERR, "class statement requ MSG_DEF(JSMSG_UNNAMED_FUNCTION_STMT, 0, JSEXN_SYNTAXERR, "function statement requires a name") MSG_DEF(JSMSG_UNTERMINATED_COMMENT, 0, JSEXN_SYNTAXERR, "unterminated comment") MSG_DEF(JSMSG_UNTERMINATED_REGEXP, 0, JSEXN_SYNTAXERR, "unterminated regular expression literal") +MSG_DEF(JSMSG_UNTERMINATED_STATIC_CLASS_BLOCK, 0, JSEXN_SYNTAXERR, "unterminated static class block") MSG_DEF(JSMSG_UNTERMINATED_STRING, 0, JSEXN_SYNTAXERR, "unterminated string literal") MSG_DEF(JSMSG_USELESS_EXPR, 0, JSEXN_TYPEERR, "useless expression") MSG_DEF(JSMSG_USE_ASM_DIRECTIVE_FAIL, 0, JSEXN_SYNTAXERR, "\"use asm\" is only meaningful in the Directive Prologue of a function body") diff --git a/js/src/jsast.tbl b/js/src/jsast.tbl index ba6b60b68c..0ef51622b1 100644 --- a/js/src/jsast.tbl +++ b/js/src/jsast.tbl @@ -87,4 +87,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") +ASTDEF(AST_STATIC_CLASS_BLOCK, "StaticClassBlock", "staticClassBlock") /* AST_LIMIT = last + 1 */ From ba9647a12a19f06d5e89f51071acbabfabc6cb6f Mon Sep 17 00:00:00 2001 From: Martok Date: Mon, 1 May 2023 00:57:45 +0200 Subject: [PATCH 22/23] Issue #2142 - Remove the temporary fields option Now that everything is finished, we don't need the feature flag any more. --- js/src/frontend/Parser.cpp | 5 ----- js/src/frontend/TokenStream.cpp | 5 ----- js/src/jsapi.cpp | 1 - js/src/jsapi.h | 2 -- 4 files changed, 13 deletions(-) diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 7c8df7d498..a088949c8b 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -7491,11 +7491,6 @@ Parser::classMember(YieldHandling yieldHandling, return false; if (propType == PropertyType::Field) { - if (!options().fieldsEnabledOption) { - errorAt(propNameOffset, JSMSG_FIELDS_NOT_SUPPORTED); - return false; - } - if (isStatic) { if (propAtom == context->names().prototype) { errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF); diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp index 2c3371b465..f11fdcd793 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -1545,11 +1545,6 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) 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; } diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 6b2a719d7e..c0fa85bc68 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -3873,7 +3873,6 @@ JS::TransitiveCompileOptions::copyPODTransitiveOptions(const TransitiveCompileOp forceAsync = rhs.forceAsync; installedFile = rhs.installedFile; sourceIsLazy = rhs.sourceIsLazy; - fieldsEnabledOption = rhs.fieldsEnabledOption; introductionType = rhs.introductionType; introductionLineno = rhs.introductionLineno; introductionOffset = rhs.introductionOffset; diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 4640ab0b50..a6a5429cf5 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -3820,7 +3820,6 @@ class JS_FRIEND_API(TransitiveCompileOptions) forceAsync(false), installedFile(false), sourceIsLazy(false), - fieldsEnabledOption(true), introductionType(nullptr), introductionLineno(0), introductionOffset(0), @@ -3856,7 +3855,6 @@ class JS_FRIEND_API(TransitiveCompileOptions) bool forceAsync; bool installedFile; // 'true' iff pre-compiling js file in packaged app bool sourceIsLazy; - bool fieldsEnabledOption; // |introductionType| is a statically allocated C string: // one of "eval", "Function", or "GeneratorFunction". From 10951a169ccac1326c2e6111262dcf6d847985ce Mon Sep 17 00:00:00 2001 From: Martok Date: Wed, 26 Apr 2023 15:16:33 +0200 Subject: [PATCH 23/23] Issue #2097 - Implement logical assignment operators Based-on: 1629106/1, 1684020 --- js/src/builtin/ReflectParse.cpp | 14 ++ js/src/frontend/BytecodeEmitter.cpp | 244 ++++++++++++++++++++++++++++ js/src/frontend/BytecodeEmitter.h | 5 + js/src/frontend/FoldConstants.cpp | 6 + js/src/frontend/ParseNode.cpp | 3 + js/src/frontend/ParseNode.h | 9 +- js/src/frontend/Parser.cpp | 14 ++ js/src/frontend/TokenKind.h | 3 + js/src/frontend/TokenStream.cpp | 10 +- 9 files changed, 302 insertions(+), 6 deletions(-) diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index b40c5fb408..0205887ff6 100644 --- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -53,6 +53,8 @@ enum AssignmentOperator { AOP_LSH, AOP_RSH, AOP_URSH, /* binary */ AOP_BITOR, AOP_BITXOR, AOP_BITAND, + /* short-circuit */ + AOP_COALESCE, AOP_OR, AOP_AND, AOP_LIMIT }; @@ -122,6 +124,9 @@ static const char* const aopNames[] = { "|=", /* AOP_BITOR */ "^=", /* AOP_BITXOR */ "&=" /* AOP_BITAND */ + "\?\?=", /* AOP_COALESCE */ + "||=", /* AOP_OR */ + "&&=", /* AOP_AND */ }; static const char* const binopNames[] = { @@ -1974,6 +1979,12 @@ ASTSerializer::aop(JSOp op) return AOP_BITXOR; case JSOP_BITAND: return AOP_BITAND; + case JSOP_COALESCE: + return AOP_COALESCE; + case JSOP_OR: + return AOP_OR; + case JSOP_AND: + return AOP_AND; default: return AOP_ERR; } @@ -3103,6 +3114,9 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst) case PNK_ASSIGN: case PNK_ADDASSIGN: case PNK_SUBASSIGN: + case PNK_COALESCEASSIGN: + case PNK_ORASSIGN: + case PNK_ANDASSIGN: case PNK_BITORASSIGN: case PNK_BITXORASSIGN: case PNK_BITANDASSIGN: diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index eb25903911..1e6390f5f1 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -517,6 +517,17 @@ BytecodeEmitter::emitPopN(unsigned n) return emitUint16Operand(JSOP_POPN, n); } +bool +BytecodeEmitter::emitUnpickN(unsigned n) +{ + MOZ_ASSERT(n != 0); + + if (n == 1) + return emit1(JSOP_SWAP); + + return emit2(JSOP_UNPICK, n); +} + bool BytecodeEmitter::emitCheckIsObj(CheckIsObjectKind kind) { @@ -1221,6 +1232,9 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) case PNK_ASSIGN: case PNK_ADDASSIGN: case PNK_SUBASSIGN: + case PNK_COALESCEASSIGN: + case PNK_ORASSIGN: + case PNK_ANDASSIGN: case PNK_BITORASSIGN: case PNK_BITXORASSIGN: case PNK_BITANDASSIGN: @@ -3977,6 +3991,225 @@ BytecodeEmitter::emitAssignmentOrInit(ParseNodeKind kind, JSOp compoundOp, return true; } +bool +BytecodeEmitter::emitShortCircuitAssignment(ParseNodeKind kind, JSOp op, + ParseNode* lhs, ParseNode* rhs) +{ + TDZCheckCache tdzCache(this); + + // |name| is used within NameOpEmitter, so its lifetime must surpass |noe|. + RootedAtom name(cx); + + // Select the appropriate emitter based on the left-hand side. + Maybe noe; + Maybe poe; + Maybe eoe; + + int32_t startDepth = stackDepth; + + // Number of values pushed onto the stack in addition to the lhs value. + int32_t numPushed; + + // Evaluate the left-hand side expression and compute any stack values needed + // for the assignment. + switch (lhs->getKind()) { + case PNK_NAME: { + name = lhs->as().name(); + noe.emplace(this, name, NameOpEmitter::Kind::CompoundAssignment); + + if (!noe->prepareForRhs()) { + // [stack] ENV? LHS + return false; + } + + numPushed = noe->emittedBindOp(); + break; + } + + case PNK_DOT: { + PropertyAccess* prop = &lhs->as(); + bool isSuper = prop->isSuper(); + + poe.emplace(this, PropOpEmitter::Kind::CompoundAssignment, + isSuper ? PropOpEmitter::ObjKind::Super + : PropOpEmitter::ObjKind::Other); + + if (!poe->prepareForObj()) + return false; + + if (isSuper) { + UnaryNode* base = &prop->expression().as(); + if (!emitGetThisForSuperBase(base)) { + // [stack] THIS SUPERBASE + return false; + } + } else { + if (!emitTree(&prop->expression())) { + // [stack] OBJ + return false; + } + } + + if (!poe->emitGet(prop->key().atom())) { + // [stack] # if Super + // [stack] THIS SUPERBASE LHS + // [stack] # otherwise + // [stack] OBJ LHS + return false; + } + + if (!poe->prepareForRhs()) { + // [stack] # if Super + // [stack] THIS SUPERBASE LHS + // [stack] # otherwise + // [stack] OBJ LHS + return false; + } + + numPushed = 1 + isSuper; + break; + } + + case PNK_ELEM: { + PropertyByValue* elem = &lhs->as(); + bool isSuper = elem->isSuper(); + + eoe.emplace(this, ElemOpEmitter::Kind::CompoundAssignment, + isSuper ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other); + + if (!emitElemObjAndKey(elem, isSuper, *eoe)) { + // [stack] # if Super + // [stack] THIS KEY + // [stack] # otherwise + // [stack] OBJ KEY + return false; + } + + if (!eoe->emitGet()) { + // [stack] # if Super + // [stack] THIS KEY SUPERBASE LHS + // [stack] # otherwise + // [stack] OBJ KEY LHS + return false; + } + + if (!eoe->prepareForRhs()) { + // [stack] # if Super + // [stack] THIS KEY SUPERBASE LHS + // [stack] # otherwise + // [stack] OBJ KEY LHS + return false; + } + + numPushed = 2 + isSuper; + break; + } + + default: + MOZ_CRASH(); + } + + MOZ_ASSERT(stackDepth == startDepth + numPushed + 1); + + // Test for the short-circuit condition. + JumpList jump; + if (!emitJump(op, &jump)) { + // [stack] ... LHS + return false; + } + + // The short-circuit condition wasn't fulfilled, pop the left-hand side value + // which was kept on the stack. + if (!emit1(JSOP_POP)) { + // [stack] ... + return false; + } + + // TODO: Open spec issue about setting inferred function names. + // + if (!emitTree(rhs)) { + // [stack] ... RHS + return false; + } + + // Perform the actual assignment. + switch (lhs->getKind()) { + case PNK_NAME: { + if (!noe->emitAssignment()) { + // [stack] RHS + return false; + } + break; + } + + case PNK_DOT: { + PropertyAccess* prop = &lhs->as(); + + if (!poe->emitAssignment(prop->key().atom())) { + // [stack] RHS + return false; + } + break; + } + + case PNK_ELEM: { + if (!eoe->emitAssignment()) { + // [stack] RHS + return false; + } + break; + } + + default: + MOZ_CRASH(); + } + + MOZ_ASSERT(stackDepth == startDepth + 1); + + // Join with the short-circuit jump and pop anything left on the stack. + if (numPushed > 0) { + JumpList jumpAroundPop; + if (!emitJump(JSOP_GOTO, &jumpAroundPop)) { + // [stack] RHS + return false; + } + + if (!emitJumpTargetAndPatch(jump)) { + // [stack] ... LHS + return false; + } + + // Reconstruct the stack depth after the jump. + stackDepth = startDepth + 1 + numPushed; + + // Move the left-hand side value to the bottom and pop the rest. + if (!emitUnpickN(numPushed)) { + // [stack] LHS ... + return false; + } + + if (!emitPopN(numPushed)) { + // [stack] LHS + return false; + } + + if (!emitJumpTargetAndPatch(jumpAroundPop)) { + // [stack] LHS | RHS + return false; + } + } else { + if (!emitJumpTargetAndPatch(jump)) { + // [stack] LHS | RHS + return false; + } + } + + MOZ_ASSERT(stackDepth == startDepth + 1); + + return true; +} + bool ParseNode::getConstantValue(ExclusiveContext* cx, AllowConstantObjects allowObjects, MutableHandleValue vp, Value* compare, size_t ncompare, @@ -9074,6 +9307,17 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage:: break; } + case PNK_COALESCEASSIGN: + case PNK_ORASSIGN: + case PNK_ANDASSIGN: { + BinaryNode* assignNode = &pn->as(); + if (!emitShortCircuitAssignment(assignNode->getKind(), assignNode->getOp(), + assignNode->left(), assignNode->right())) { + return false; + } + break; + } + case PNK_CONDITIONAL: if (!emitConditionalExpression(pn->as(), valueUsage)) return false; diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 7521e236dc..732a1f24f8 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -457,6 +457,9 @@ struct MOZ_STACK_CLASS BytecodeEmitter // Helper to emit JSOP_POP or JSOP_POPN. MOZ_MUST_USE bool emitPopN(unsigned n); + // Helper to emit JSOP_SWAP or JSOP_UNPICK. + MOZ_MUST_USE bool emitUnpickN(unsigned n); + // Helper to emit JSOP_CHECKISOBJ. MOZ_MUST_USE bool emitCheckIsObj(CheckIsObjectKind kind); @@ -701,6 +704,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitTemplateString(ListNode* templateString); MOZ_MUST_USE bool emitAssignmentOrInit(ParseNodeKind kind, JSOp compoundOp, ParseNode* lhs, ParseNode* rhs); + MOZ_MUST_USE bool emitShortCircuitAssignment(ParseNodeKind kind, JSOp op, + ParseNode* lhs, ParseNode* rhs); MOZ_MUST_USE bool emitReturn(UnaryNode* returnNode); MOZ_MUST_USE bool emitStatement(UnaryNode* exprStmt); diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index 813900a23f..9fb963f63e 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -355,6 +355,9 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result) case PNK_ASSIGN: case PNK_ADDASSIGN: case PNK_SUBASSIGN: + case PNK_COALESCEASSIGN: + case PNK_ORASSIGN: + case PNK_ANDASSIGN: case PNK_BITORASSIGN: case PNK_BITXORASSIGN: case PNK_BITANDASSIGN: @@ -1884,6 +1887,9 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& parser, bo case PNK_ASSIGN: case PNK_ADDASSIGN: case PNK_SUBASSIGN: + case PNK_COALESCEASSIGN: + case PNK_ORASSIGN: + case PNK_ANDASSIGN: case PNK_BITORASSIGN: case PNK_BITANDASSIGN: case PNK_BITXORASSIGN: diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index a529c93d14..7ad470865f 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -262,6 +262,9 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) case PNK_ASSIGN: case PNK_ADDASSIGN: case PNK_SUBASSIGN: + case PNK_COALESCEASSIGN: + case PNK_ORASSIGN: + case PNK_ANDASSIGN: case PNK_BITORASSIGN: case PNK_BITXORASSIGN: case PNK_BITANDASSIGN: diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 471f42f905..46977ee253 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -176,6 +176,9 @@ class ObjectBox; F(ASSIGN) \ F(ADDASSIGN) \ F(SUBASSIGN) \ + F(COALESCEASSIGN) \ + F(ORASSIGN) \ + F(ANDASSIGN) \ F(BITORASSIGN) \ F(BITXORASSIGN) \ F(BITANDASSIGN) \ @@ -395,8 +398,10 @@ IsTypeofKind(ParseNodeKind kind) * PNK_ASSIGN (AssignmentNode) * left: target of assignment * right: value to assign - * PNK_ADDASSIGN, PNK_SUBASSIGN, PNK_BITORASSIGN, PNK_BITXORASSIGN, - * PNK_BITANDASSIGN, PNK_LSHASSIGN, PNK_RSHASSIGN, PNK_URSHASSIGN, + * PNK_ADDASSIGN, PNK_SUBASSIGN, + * PNK_COALESCEASSIGN, PNK_ORASSIGN, PNK_ANDASSIGN, + * PNK_BITORASSIGN, PNK_BITXORASSIGN, PNK_BITANDASSIGN, + * PNK_LSHASSIGN, PNK_RSHASSIGN, PNK_URSHASSIGN, * PNK_MULASSIGN, PNK_DIVASSIGN, PNK_MODASSIGN, PNK_POWASSIGN (AssignmentNode) * left: target of assignment * right: value to assign diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index a088949c8b..a33b3d0d90 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -9150,6 +9150,9 @@ Parser::assignExpr(InHandling inHandling, YieldHandling yieldHandl case TOK_ASSIGN: kind = PNK_ASSIGN; op = JSOP_NOP; break; case TOK_ADDASSIGN: kind = PNK_ADDASSIGN; op = JSOP_ADD; break; case TOK_SUBASSIGN: kind = PNK_SUBASSIGN; op = JSOP_SUB; break; + case TOK_COALESCEASSIGN: kind = PNK_COALESCEASSIGN; op = JSOP_COALESCE; break; + case TOK_ORASSIGN: kind = PNK_ORASSIGN; op = JSOP_OR; break; + case TOK_ANDASSIGN: kind = PNK_ANDASSIGN; op = JSOP_AND; break; case TOK_BITORASSIGN: kind = PNK_BITORASSIGN; op = JSOP_BITOR; break; case TOK_BITXORASSIGN: kind = PNK_BITXORASSIGN; op = JSOP_BITXOR; break; case TOK_BITANDASSIGN: kind = PNK_BITANDASSIGN; op = JSOP_BITAND; break; @@ -9279,6 +9282,17 @@ Parser::assignExpr(InHandling inHandling, YieldHandling yieldHandl } else if (handler.isPropertyAccess(lhs)) { // Permitted: no additional testing/fixup needed. } else if (handler.isFunctionCall(lhs)) { + // We don't have to worry about backward compatibility issues with the new + // compound assignment operators, so we always throw here. Also that way we + // don't have to worry if |f() &&= expr| should always throw an error or + // only if |f()| returns true. + if (kind == PNK_COALESCEASSIGN || + kind == PNK_ORASSIGN || + kind == PNK_ADDASSIGN) { + errorAt(exprPos.begin, JSMSG_BAD_LEFTSIDE_OF_ASS); + return null(); + } + if (!strictModeErrorAt(exprPos.begin, JSMSG_BAD_LEFTSIDE_OF_ASS)) return null(); diff --git a/js/src/frontend/TokenKind.h b/js/src/frontend/TokenKind.h index f6f947f608..232e373dcf 100644 --- a/js/src/frontend/TokenKind.h +++ b/js/src/frontend/TokenKind.h @@ -219,6 +219,9 @@ range(ASSIGNMENT_START, ASSIGN) \ macro(ADDASSIGN, "'+='") \ macro(SUBASSIGN, "'-='") \ + macro(COALESCEASSIGN, "'\?\?='") /* avoid trigraphs warning */ \ + macro(ORASSIGN, "'||='") \ + macro(ANDASSIGN, "'&&='") \ macro(BITORASSIGN, "'|='") \ macro(BITXORASSIGN, "'^='") \ macro(BITANDASSIGN, "'&='") \ diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp index f11fdcd793..c58bc4bd47 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -1876,7 +1876,7 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) case '|': if (matchChar('|')) - tp->type = TOK_OR; + tp->type = matchChar('=') ? TOK_ORASSIGN : TOK_OR; else tp->type = matchChar('=') ? TOK_BITORASSIGN : TOK_BITOR; goto out; @@ -1887,7 +1887,7 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) case '&': if (matchChar('&')) - tp->type = TOK_AND; + tp->type = matchChar('=') ? TOK_ANDASSIGN : TOK_AND; else tp->type = matchChar('=') ? TOK_BITANDASSIGN : TOK_BITAND; goto out; @@ -1906,8 +1906,10 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) ungetCharIgnoreEOL(c); tp->type = TOK_OPTCHAIN; } - } else { - tp->type = matchChar('?') ? TOK_COALESCE : TOK_HOOK; + } else if (matchChar('?')) { + tp->type = matchChar('=') ? TOK_COALESCEASSIGN : TOK_COALESCE; + } else { + tp->type = TOK_HOOK; } goto out;