From 5cc281d52ac1d87f9b31ff7e28f531512cd02296 Mon Sep 17 00:00:00 2001 From: Yijia Huang Date: Wed, 5 Oct 2022 08:34:56 -0700 Subject: [PATCH] Implement support for class static initialization blocks https://bugs.webkit.org/show_bug.cgi?id=235085 rdar://99056882 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed by Yusuke Suzuki. Class static initialization block is a new feature of a class to perform additional static initialization during class definition evaluation. ``` class C { static { /* … */ } } ``` TC39 Spec: https://tc39.es/proposal-class-static-block/ TC39 Proposal: https://github.com/tc39/proposal-class-static-block MDN Web Doc: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Class_static_initialization_blocks In this patch, static blocks are implemented as functions which are evaluated along with the initialization of static class fields during class definition evaluation. This can be further optimized by inlining static block functions to the field initialization. * JSTests/stress/class-static-block.js: Added. (assert): (A): (assert.C): (assert.B): (assert.D): (assert.A): (assert.A.friendA.prototype.getX): (assert.A.friendA.prototype.setX): (assert.A.prototype.getX): (assert.inner): (catch.C.prototype.async inner): (catch.C): (catch): (async inner.C.prototype.async inner): (async inner.C): (async inner): (C.inner): (C): (await.C.inner): (await.C): (await): (arguments.C.inner): (arguments.C): (arguments): * Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp: (JSC::PropertyListNode::emitBytecode): (JSC::FunctionCallValueNode::emitBytecode): (JSC::FuncExprNode::emitBytecode): * Source/JavaScriptCore/parser/ASTBuilder.h: (JSC::ASTBuilder::createFunctionExpr): (JSC::ASTBuilder::createProperty): (JSC::ASTBuilder::makeFunctionCallNode): * Source/JavaScriptCore/parser/NodeConstructors.h: (JSC::PropertyNode::PropertyNode): (JSC::FunctionCallValueNode::FunctionCallValueNode): (JSC::FuncExprNode::FuncExprNode): * Source/JavaScriptCore/parser/Nodes.h: (JSC::FuncExprNode::isStaticBlockFunction const): * Source/JavaScriptCore/parser/Parser.cpp: (JSC::Parser::isArrowFunctionParameters): (JSC::Parser::parseStatementListItem): (JSC::Parser::parseVariableDeclarationList): (JSC::Parser::parseBreakStatement): (JSC::Parser::parseContinueStatement): (JSC::Parser::parseReturnStatement): (JSC::Parser::parseTryStatement): (JSC::Parser::parseBlockStatement): (JSC::stringArticleForFunctionMode): (JSC::stringForFunctionMode): (JSC::Parser::parseFunctionParameters): (JSC::Parser::parseFunctionInfo): (JSC::Parser::parseClass): (JSC::Parser::parseClassFieldInitializerSourceElements): (JSC::Parser::parseAssignmentExpression): (JSC::Parser::parsePrimaryExpression): (JSC::Parser::parseMemberExpression): (JSC::Parser::parseUnaryExpression): * Source/JavaScriptCore/parser/Parser.h: (JSC::Scope::setSourceParseMode): (JSC::Scope::setIsStaticBlockScope): (JSC::Scope::isStaticBlockScope): (JSC::Parser::canUseIdentifierAwait): (JSC::Parser::disallowedIdentifierAwaitReason): (JSC::Parser::findClosetFunctionScope): (JSC::Parser::findClosetAsyncFunctionScope): (JSC::Parser::findScopeUntilStaticBlock): * Source/JavaScriptCore/parser/ParserModes.h: (JSC::isFunctionParseMode): (JSC::isMethodParseMode): * Source/JavaScriptCore/parser/SyntaxChecker.h: (JSC::SyntaxChecker::makeFunctionCallNode): (JSC::SyntaxChecker::createFunctionExpr): (JSC::SyntaxChecker::createProperty): * Source/JavaScriptCore/runtime/FunctionExecutable.cpp: (JSC::FunctionExecutable::toStringSlow): Canonical link: https://commits.webkit.org/255173@main --- ...obalCatchNewTargetSyntaxError.baseline-jsc | 2 +- .../globalNewTargetSyntaxError.baseline-jsc | 2 +- ...aramCatchNewTargetSyntaxError.baseline-jsc | 2 +- JSTests/stress/class-static-block.js | 649 ++++++++++++++++++ .../stress/code-cache-incorrect-caching.js | 4 +- JSTests/stress/modules-syntax-error.js | 2 +- .../bytecompiler/NodesCodegen.cpp | 23 +- Source/JavaScriptCore/parser/ASTBuilder.h | 10 + .../JavaScriptCore/parser/NodeConstructors.h | 16 + Source/JavaScriptCore/parser/Nodes.h | 35 +- Source/JavaScriptCore/parser/Parser.cpp | 140 +++- Source/JavaScriptCore/parser/Parser.h | 100 ++- Source/JavaScriptCore/parser/ParserModes.h | 7 +- Source/JavaScriptCore/parser/SyntaxChecker.h | 5 + .../runtime/FunctionExecutable.cpp | 1 + 15 files changed, 923 insertions(+), 75 deletions(-) create mode 100644 JSTests/stress/class-static-block.js diff --git a/JSTests/ChakraCore/test/es6/globalCatchNewTargetSyntaxError.baseline-jsc b/JSTests/ChakraCore/test/es6/globalCatchNewTargetSyntaxError.baseline-jsc index 692195c0e925c..e2a9a7ec49e74 100644 --- a/JSTests/ChakraCore/test/es6/globalCatchNewTargetSyntaxError.baseline-jsc +++ b/JSTests/ChakraCore/test/es6/globalCatchNewTargetSyntaxError.baseline-jsc @@ -1,2 +1,2 @@ -Exception: SyntaxError: new.target is only valid inside functions. +Exception: SyntaxError: new.target is only valid inside functions or static blocks. at globalCatchNewTargetSyntaxError.js:6 diff --git a/JSTests/ChakraCore/test/es6/globalNewTargetSyntaxError.baseline-jsc b/JSTests/ChakraCore/test/es6/globalNewTargetSyntaxError.baseline-jsc index 88e6bc8a80278..2345383e4645c 100644 --- a/JSTests/ChakraCore/test/es6/globalNewTargetSyntaxError.baseline-jsc +++ b/JSTests/ChakraCore/test/es6/globalNewTargetSyntaxError.baseline-jsc @@ -1,2 +1,2 @@ -Exception: SyntaxError: new.target is only valid inside functions. +Exception: SyntaxError: new.target is only valid inside functions or static blocks. at globalNewTargetSyntaxError.js:6 diff --git a/JSTests/ChakraCore/test/es6/globalParamCatchNewTargetSyntaxError.baseline-jsc b/JSTests/ChakraCore/test/es6/globalParamCatchNewTargetSyntaxError.baseline-jsc index cb886bacb83f1..985ea22f36972 100644 --- a/JSTests/ChakraCore/test/es6/globalParamCatchNewTargetSyntaxError.baseline-jsc +++ b/JSTests/ChakraCore/test/es6/globalParamCatchNewTargetSyntaxError.baseline-jsc @@ -1,2 +1,2 @@ -Exception: SyntaxError: new.target is only valid inside functions. +Exception: SyntaxError: new.target is only valid inside functions or static blocks. at globalParamCatchNewTargetSyntaxError.js:7 diff --git a/JSTests/stress/class-static-block.js b/JSTests/stress/class-static-block.js new file mode 100644 index 0000000000000..b40634e805d0d --- /dev/null +++ b/JSTests/stress/class-static-block.js @@ -0,0 +1,649 @@ +function assert(b) { + if (!b) { + throw "bad assert!" + } +} + +function shouldThrow(func, errorMessage) { + var errorThrown = false; + var error = null; + try { + func(); + } catch (e) { + errorThrown = true; + error = e; + } + if (!errorThrown) + throw new Error('not thrown'); + if (String(error) !== errorMessage) + throw new Error(` + bad error: ${String(error)} + expected error: ${errorMessage} + `); +} + +// ---------- single static block ---------- +{ + var y = 'Outer y'; + + class A { + static field = 'Inner y'; + static { + var y = this.field; + } + } + + assert(y === 'Outer y'); +} + +// ---------- multiple static blocks ---------- +{ + class C { + static { + assert(this.x === undefined); + } + static x = 10; + static { + assert(this.x === 10); + assert(this.y === undefined); + } + static y = 20; + static { + assert(this.y === 20); + } + } +} + +// ---------- use this ---------- +{ + class A { + static field = 'A static field'; + static { + assert(this.field === 'A static field'); + } + } +} + +// ---------- use super ---------- +{ + class A { + static fieldA = 'A.fieldA'; + } + class B extends A { + static { + assert(super.fieldA === 'A.fieldA'); + } + } +} + +// ---------- access to private fields ---------- +{ + let getDPrivateField; + + class D { + #privateField; + constructor(v) { + this.#privateField = v; + } + static { + getDPrivateField = (d) => d.#privateField; + } + } + + assert(getDPrivateField(new D('private')) === 'private'); +} + +// ---------- "friend" access ---------- +{ + let A, B; + + let friendA; + + A = class A { + #x; + constructor(x) { + this.#x = x; + } + static { + friendA = { + getX(obj) { return obj.#x }, + setX(obj, value) { obj.#x = value } + }; + } + getX() { + return this.#x; + } + }; + + B = class B { + constructor(a) { + const x = friendA.getX(a); + friendA.setX(a, x + 32); + } + }; + + let a = new A(10); + new B(a); + + assert(a.getX() === 42); +} + +// ---------- break ---------- +{ + class C { + static { + while (false) + break; // isStaticBlock = true, isValid = false, isCurrentScopeValid = true + } + } +} + +{ + class C { + static { + while (false) { + break; // isStaticBlock = true, isValid = true, isCurrentScopeValid = false + } + } + } +} + +shouldThrow(() => { + eval(` + while (false) { + class C { + static { + break; // isStaticBlock = true, isValid = false, isCurrentScopeValid = false + } + } + } + `); +}, `SyntaxError: 'break' cannot cross static block boundary.`); + +shouldThrow(() => { + eval(` + class C { + static { + break; // isStaticBlock = true, isValid = false, isCurrentScopeValid = false + } + } + `); +}, `SyntaxError: 'break' cannot cross static block boundary.`); + +shouldThrow(() => { + eval(` + while (false) { + class C { + static { + { + break; // isStaticBlock = true, isValid = false, isCurrentScopeValid = false + } + } + } + } + `); +}, `SyntaxError: 'break' cannot cross static block boundary.`); + +shouldThrow(() => { + eval(` + class C { + static { + { + break; // isStaticBlock = true, isValid = false, isCurrentScopeValid = false + } + } + } + `); +}, `SyntaxError: 'break' cannot cross static block boundary.`); + +// ---------- continue ---------- +{ + class C { + static { + while (false) + continue; // isStaticBlock = true, isValid = false, isCurrentScopeValid = true + } + } +} + +{ + class C { + static { + while (false) { + continue; // isStaticBlock = true, isValid = true, isCurrentScopeValid = false + } + } + } +} + +shouldThrow(() => { + eval(` + while (false) { + class C { + static { + continue; // isStaticBlock = true, isValid = false, isCurrentScopeValid = false + } + } + } + `); +}, `SyntaxError: 'continue' cannot cross static block boundary.`); + +shouldThrow(() => { + eval(` + class C { + static { + continue; // isStaticBlock = true, isValid = false, isCurrentScopeValid = false + } + } + `); +}, `SyntaxError: 'continue' cannot cross static block boundary.`); + +shouldThrow(() => { + eval(` + while (false) { + class C { + static { + { + continue; // isStaticBlock = true, isValid = false, isCurrentScopeValid = false + } + } + } + } + `); +}, `SyntaxError: 'continue' cannot cross static block boundary.`); + +shouldThrow(() => { + eval(` + class C { + static { + { + continue; // isStaticBlock = true, isValid = false, isCurrentScopeValid = false + } + } + } + `); +}, `SyntaxError: 'continue' cannot cross static block boundary.`); + +// ---------- arguments ---------- +{ + class C { + static { + function inner() { + [arguments](); + } + } + } +} + +{ + class C { + static { + class B { + inner() { + [arguments](); + } + } + } + } +} + +{ + class C { + static { + function inner() { + arguments[0]; + } + } + } +} + +{ + class C { + static { + class B { + inner() { + arguments[0]; + } + } + } + } +} + +{ + class C { + static { + (a, b) => { + this.arguments[0]; + }; + } + } +} + +shouldThrow(() => { + eval(` + class C { + static { + arguments; + } + } + `); +}, `SyntaxError: Cannot use 'arguments' as an identifier in static block.`); + +// ---------- yield ---------- + +{ + class A { + static { + function* gen() { + yield 42; + } + } + } +} + +{ + class A { + static { + class B { + *gen() { + yield 42; + } + } + } + + } +} + +shouldThrow(() => { + eval(` + class C { + static { + function inner() { + yield 0; + } + } + } + `); +}, `SyntaxError: Unexpected keyword 'yield'. Cannot use yield expression out of generator.`); + +shouldThrow(() => { + eval(` + class C { + static { + yield 0; + } + } + `); +}, `SyntaxError: Unexpected keyword 'yield'. Cannot use 'yield' within static block.`); + +// ---------- await ---------- +{ + class C { + static { + function inner() { + try { } catch (await) { } + } + } + } +} + +{ + class C { + static { + class B { + inner() { + try { } catch (await) { } + } + } + } + } +} + +{ + class C { + static { + async function inner() { + await 0; + } + } + } +} + +{ + class C { + static { + class B { + async inner() { + await 0; + } + } + } + } +} + +{ + class C { + static { + function inner() { + let await = 10; + } + } + } +} + +{ + class C { + static { + class B { + inner() { + let await = 10; + } + } + } + } +} + +{ + async function outer() { + class C { + static { + async function inner() { + await 0; + } + } + } + } +} + +{ + async function outer() { + class C { + static { + class B { + async inner() { + await 0; + } + } + } + } + } +} + +{ + async function outer() { + class C { + static { + function inner() { + let await = 10; + } + } + } + } +} + +{ + async function outer() { + class C { + static { + class B { + inner() { + let await = 10; + } + } + } + } + } +} + +{ + class C { + static { + function inner() { + [await](); + } + } + } +} + +{ + class C { + static { + class B { + inner() { + [await](); + } + } + } + } +} + +// ---------- others ---------- +{ + function doSomethingWith(x) { + return { + y: x + 1, + z: x + 2 + }; + } + + class C { + static x = 10; + static y; + static z; + static { + try { + const obj = doSomethingWith(this.x); + C.y = obj.y; + C["z"] = obj.z; + } catch { + } + } + } + + assert(C.y === 11); + assert(C.z === 12); +} + +{ + class C { + static y; + static z; + static { + try { + throw "err"; + } catch { + C.y = 13; + C['z'] = 14; + } + } + } + + assert(C.y === 13); + assert(C.z === 14); +} + +{ + var value = null; + class C { + static { + function inner() { + value = new.target; + } + inner(); + } + } + assert(value === undefined); +} + +{ + class C { + static { + value = new.target; + } + } + assert(value === undefined); +} + +{ + class C { + static { + function inner() { + { + return 10; + } + } + } + } +} + +{ + class C { + static { + function inner() { + Promise.resolve().then(makeMasquerader(), makeMasquerader()); + } + { + Promise.resolve().then(makeMasquerader(), makeMasquerader()); + } + } + } +} + +{ + class C { + static { + { + function foo(arg) { + let o; + if (arg) { + o = {}; + } else { + o = function() { } + } + return typeof o; + } + noInline(foo); + + for (let i = 0; i < 10000; i++) { + let bool = !!(i % 2); + let result = foo(bool); + if (bool) + assert(result === "object"); + else + assert(result === "function"); + } + } + } + } +} + +{ + function foo() { + assert(foo.caller === null); + } + class C { + static { + foo(); + } + } +} diff --git a/JSTests/stress/code-cache-incorrect-caching.js b/JSTests/stress/code-cache-incorrect-caching.js index 9e51af78cc9c5..83f5753e668ce 100644 --- a/JSTests/stress/code-cache-incorrect-caching.js +++ b/JSTests/stress/code-cache-incorrect-caching.js @@ -35,7 +35,7 @@ var global = this; eval('new.target'); } catch (e) { thrown = true; - shouldBe(String(e), "SyntaxError: new.target is only valid inside functions."); + shouldBe(String(e), "SyntaxError: new.target is only valid inside functions or static blocks."); } shouldBe(thrown, true); `); @@ -45,7 +45,7 @@ var global = this; globalEval('new.target'); } catch (e) { thrown = true; - shouldBe(String(e), "SyntaxError: new.target is only valid inside functions."); + shouldBe(String(e), "SyntaxError: new.target is only valid inside functions or static blocks."); } shouldBe(thrown, true); } diff --git a/JSTests/stress/modules-syntax-error.js b/JSTests/stress/modules-syntax-error.js index 701650df5d3a8..8810c9fb5ebbc 100644 --- a/JSTests/stress/modules-syntax-error.js +++ b/JSTests/stress/modules-syntax-error.js @@ -356,7 +356,7 @@ export * as "\ud800" from "./ok.js" checkModuleSyntaxError(String.raw` new.target; -`, `SyntaxError: new.target is only valid inside functions.:2`); +`, `SyntaxError: new.target is only valid inside functions or static blocks.:2`); checkModuleSyntaxError(String.raw` super(); diff --git a/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp b/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp index 23fa6639e603e..bc2a2d1d03992 100644 --- a/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp +++ b/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp @@ -649,7 +649,7 @@ RegisterID* PropertyListNode::emitBytecode(BytecodeGenerator& generator, Registe continue; } - if (p->isStaticClassField()) { + if (p->isStaticClassElement()) { ASSERT(staticFieldLocations); staticFieldLocations->append(p->position()); continue; @@ -719,7 +719,7 @@ RegisterID* PropertyListNode::emitBytecode(BytecodeGenerator& generator, Registe continue; } - if (p->isStaticClassField()) { + if (p->isStaticClassElement()) { ASSERT(staticFieldLocations); staticFieldLocations->append(p->position()); continue; @@ -1263,6 +1263,25 @@ RegisterID* FunctionCallValueNode::emitBytecode(BytecodeGenerator& generator, Re return ret; } +// ------------------------------ StaticBlockFunctionCallNode ---------------------------------- + +RegisterID* StaticBlockFunctionCallNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst) +{ + // There are two possible optimizations in this implementation. + // https://bugs.webkit.org/show_bug.cgi?id=245925 + RefPtr homeObject = emitHomeObjectForCallee(generator); + RefPtr function = generator.emitNode(m_expr); + emitPutHomeObject(generator, function.get(), homeObject.get()); + RefPtr returnValue = generator.finalDestination(dst, function.get()); + + CallArguments callArguments(generator, nullptr); + generator.move(callArguments.thisRegister(), generator.thisRegister()); + RefPtr result = generator.emitCallInTailPosition(returnValue.get(), function.get(), NoExpectedFunction, callArguments, divot(), divotStart(), divotEnd(), DebuggableCall::Yes); + + generator.emitProfileType(returnValue.get(), divotStart(), divotEnd()); + return result.get(); +} + // ------------------------------ FunctionCallResolveNode ---------------------------------- RegisterID* FunctionCallResolveNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst) diff --git a/Source/JavaScriptCore/parser/ASTBuilder.h b/Source/JavaScriptCore/parser/ASTBuilder.h index 49d818591a444..c2cb6e23528ca 100644 --- a/Source/JavaScriptCore/parser/ASTBuilder.h +++ b/Source/JavaScriptCore/parser/ASTBuilder.h @@ -130,6 +130,7 @@ class ASTBuilder { static constexpr OptionSet DontBuildStrings = { }; ExpressionNode* makeBinaryNode(const JSTokenLocation&, int token, std::pair, std::pair); + ExpressionNode* makeStaticBlockFunctionCallNode(const JSTokenLocation&, ExpressionNode* func, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd); ExpressionNode* makeFunctionCallNode(const JSTokenLocation&, ExpressionNode* func, bool previousBaseWasSuper, ArgumentsNode* args, const JSTextPosition& divotStart, const JSTextPosition& divot, const JSTextPosition& divotEnd, size_t callOrApplyChildDepth, bool isOptionalCall); JSC::SourceElements* createSourceElements() { return new (m_parserArena) JSC::SourceElements(); } @@ -520,6 +521,10 @@ class ASTBuilder { return new (m_parserArena) PropertyNode(ident, methodDef, type, SuperBinding::Needed, tag); } + PropertyNode* createProperty(const Identifier* propertyName, PropertyNode::Type type, SuperBinding superBinding, ClassElementTag tag) + { + return new (m_parserArena) PropertyNode(*propertyName, type, superBinding, tag); + } PropertyNode* createProperty(const Identifier* propertyName, ExpressionNode* node, PropertyNode::Type type, SuperBinding superBinding, InferName inferName, ClassElementTag tag) { if (inferName == InferName::Allowed) { @@ -1417,6 +1422,11 @@ ExpressionNode* ASTBuilder::makeCoalesceNode(const JSTokenLocation& location, Ex return new (m_parserArena) CoalesceNode(location, expr1, expr2, hasAbsorbedOptionalChain); } +ExpressionNode* ASTBuilder::makeStaticBlockFunctionCallNode(const JSTokenLocation& location, ExpressionNode* func, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd) +{ + return new (m_parserArena) StaticBlockFunctionCallNode(location, func, divot, divotStart, divotEnd); +} + ExpressionNode* ASTBuilder::makeFunctionCallNode(const JSTokenLocation& location, ExpressionNode* func, bool previousBaseWasSuper, ArgumentsNode* args, const JSTextPosition& divotStart, const JSTextPosition& divot, const JSTextPosition& divotEnd, size_t callOrApplyChildDepth, bool isOptionalCall) { ASSERT(divot.offset >= divot.lineStartOffset); diff --git a/Source/JavaScriptCore/parser/NodeConstructors.h b/Source/JavaScriptCore/parser/NodeConstructors.h index b4df24458926a..1d8459202b4f6 100644 --- a/Source/JavaScriptCore/parser/NodeConstructors.h +++ b/Source/JavaScriptCore/parser/NodeConstructors.h @@ -250,6 +250,14 @@ namespace JSC { { } + inline PropertyNode::PropertyNode(const Identifier& name, Type type, SuperBinding superBinding, ClassElementTag tag) + : m_name(&name) + , m_type(type) + , m_needsSuperBinding(superBinding == SuperBinding::Needed) + , m_classElementTag(static_cast(tag)) + { + } + inline PropertyNode::PropertyNode(const Identifier& name, ExpressionNode* assign, Type type, SuperBinding superBinding, ClassElementTag tag) : m_name(&name) , m_expression(nullptr) @@ -407,6 +415,14 @@ namespace JSC { ASSERT(divot.offset >= divotStart.offset); } + inline StaticBlockFunctionCallNode::StaticBlockFunctionCallNode(const JSTokenLocation& location, ExpressionNode* expr, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd) + : ExpressionNode(location) + , ThrowableExpressionData(divot, divotStart, divotEnd) + , m_expr(expr) + { + ASSERT(divot.offset >= divotStart.offset); + } + inline FunctionCallResolveNode::FunctionCallResolveNode(const JSTokenLocation& location, const Identifier& ident, ArgumentsNode* args, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd) : ExpressionNode(location) , ThrowableExpressionData(divot, divotStart, divotEnd) diff --git a/Source/JavaScriptCore/parser/Nodes.h b/Source/JavaScriptCore/parser/Nodes.h index 253e6dade7f2b..ffdee03818a0e 100644 --- a/Source/JavaScriptCore/parser/Nodes.h +++ b/Source/JavaScriptCore/parser/Nodes.h @@ -736,8 +736,9 @@ namespace JSC { enum class ClassElementTag : uint8_t { No, Instance, Static, LastTag }; class PropertyNode final : public ParserArenaFreeable { public: - enum Type : uint16_t { Constant = 1, Getter = 2, Setter = 4, Computed = 8, Shorthand = 16, Spread = 32, PrivateField = 64, PrivateMethod = 128, PrivateSetter = 256, PrivateGetter = 512 }; + enum Type : uint16_t { Constant = 1, Getter = 2, Setter = 4, Computed = 8, Shorthand = 16, Spread = 32, PrivateField = 64, PrivateMethod = 128, PrivateSetter = 256, PrivateGetter = 512, Block = 1024 }; + PropertyNode(const Identifier&, Type, SuperBinding, ClassElementTag); PropertyNode(const Identifier&, ExpressionNode*, Type, SuperBinding, ClassElementTag); PropertyNode(ExpressionNode*, Type, SuperBinding, ClassElementTag); PropertyNode(ExpressionNode* propertyName, ExpressionNode*, Type, SuperBinding, ClassElementTag); @@ -754,6 +755,8 @@ namespace JSC { bool isClassField() const { return isClassProperty() && !needsSuperBinding(); } bool isInstanceClassField() const { return isInstanceClassProperty() && !needsSuperBinding(); } bool isStaticClassField() const { return isStaticClassProperty() && !needsSuperBinding(); } + bool isStaticClassBlock() const { return m_type & Block; } + bool isStaticClassElement() const { return isStaticClassBlock() || isStaticClassField(); } bool isOverriddenByDuplicate() const { return m_isOverriddenByDuplicate; } bool isPrivate() const { return m_type & (PrivateField | PrivateMethod | PrivateGetter | PrivateSetter); } bool hasComputedName() const { return m_expression; } @@ -772,10 +775,10 @@ namespace JSC { private: friend class PropertyListNode; - const Identifier* m_name; - ExpressionNode* m_expression; - ExpressionNode* m_assign; - unsigned m_type : 10; + const Identifier* m_name { nullptr }; + ExpressionNode* m_expression { nullptr }; + ExpressionNode* m_assign { nullptr }; + unsigned m_type : 11; unsigned m_needsSuperBinding : 1; static_assert(1 << 2 > static_cast(ClassElementTag::LastTag), "ClassElementTag shouldn't use more than two bits"); unsigned m_classElementTag : 2; @@ -803,6 +806,16 @@ namespace JSC { return m_node->isStaticClassField(); } + bool isStaticClassBlock() const + { + return m_node->isStaticClassBlock(); + } + + bool isStaticClassElement() const + { + return m_node->isStaticClassElement(); + } + void setHasPrivateAccessors(bool hasPrivateAccessors) { m_hasPrivateAccessors = hasPrivateAccessors; @@ -986,6 +999,18 @@ namespace JSC { ArgumentsNode* m_args; }; + class StaticBlockFunctionCallNode final : public ExpressionNode, public ThrowableExpressionData { + public: + StaticBlockFunctionCallNode(const JSTokenLocation&, ExpressionNode*, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd); + + private: + RegisterID* emitBytecode(BytecodeGenerator&, RegisterID* = nullptr) final; + + bool isFunctionCall() const final { return true; } + + ExpressionNode* m_expr { nullptr }; + }; + class FunctionCallResolveNode final : public ExpressionNode, public ThrowableExpressionData { public: FunctionCallResolveNode(const JSTokenLocation&, const Identifier&, ArgumentsNode*, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd); diff --git a/Source/JavaScriptCore/parser/Parser.cpp b/Source/JavaScriptCore/parser/Parser.cpp index 4f1250664a805..85676c8832c8d 100644 --- a/Source/JavaScriptCore/parser/Parser.cpp +++ b/Source/JavaScriptCore/parser/Parser.cpp @@ -384,7 +384,7 @@ template bool Parser::isArrowFunctionParameters(T } if (matchSpecIdentifier()) { - semanticFailIfTrue(isDisallowedIdentifierAwait(m_token), "Cannot use 'await' as a parameter name in an async function"); + semanticFailIfTrue(isDisallowedIdentifierAwait(m_token), "Cannot use 'await' as a parameter name ", disallowedIdentifierAwaitReason()); SavePoint saveArrowFunctionPoint = createSavePoint(context); next(); bool isArrowFunction = match(ARROWFUNCTION); @@ -776,6 +776,10 @@ template TreeStatement Parser::parseStatementList FALLTHROUGH; case AWAIT: case YIELD: { + if (UNLIKELY(currentScope()->isStaticBlock())) { + failIfTrue(match(YIELD), "Cannot use 'yield' within static block"); + failIfTrue(match(AWAIT), "Cannot use 'await' within static block"); + } // This is a convenient place to notice labeled statements // (even though we also parse them as normal statements) // because we allow the following type of code in sloppy mode: @@ -895,6 +899,7 @@ template TreeExpression Parser::parseVariableDecl failIfTrue(match(PRIVATENAME), "Cannot use a private name to declare a variable"); if (matchSpecIdentifier()) { + semanticFailIfTrue(currentScope()->isStaticBlock() && isArgumentsIdentifier(), "Cannot use 'arguments' as an identifier in static block"); failIfTrue(isPossiblyEscapedLet(m_token) && (declarationType == DeclarationType::LetDeclaration || declarationType == DeclarationType::ConstDeclaration), "Cannot use 'let' as an identifier name for a LexicalDeclaration"); semanticFailIfTrue(isDisallowedIdentifierAwait(m_token), "Cannot use 'await' as a ", declarationTypeToVariableKind(declarationType), " ", disallowedIdentifierAwaitReason()); @@ -1622,9 +1627,15 @@ template TreeStatement Parser::parseBreakStatemen JSTextPosition start = tokenStartPosition(); JSTextPosition end = tokenEndPosition(); next(); - + + std::optional isBreakValid; + if (UNLIKELY(currentScope()->isStaticBlock())) { + isBreakValid = breakIsValid(); + semanticFailIfTrue(!currentScope()->breakIsValid() && !isBreakValid.value(), "'break' cannot cross static block boundary"); + } + if (autoSemiColon()) { - semanticFailIfFalse(breakIsValid(), "'break' is only valid inside a switch or loop statement"); + semanticFailIfFalse(isBreakValid.value_or(breakIsValid()), "'break' is only valid inside a switch or loop statement"); return context.createBreakStatement(location, &m_vm.propertyNames->nullIdentifier, start, end); } failIfFalse(matchSpecIdentifier(), "Expected an identifier as the target for a break statement"); @@ -1644,9 +1655,15 @@ template TreeStatement Parser::parseContinueState JSTextPosition start = tokenStartPosition(); JSTextPosition end = tokenEndPosition(); next(); - + + std::optional isContinueValid; + if (UNLIKELY(currentScope()->isStaticBlock())) { + isContinueValid = continueIsValid(); + semanticFailIfTrue(!currentScope()->continueIsValid() && !isContinueValid.value(), "'continue' cannot cross static block boundary"); + } + if (autoSemiColon()) { - semanticFailIfFalse(continueIsValid(), "'continue' is only valid inside a loop statement"); + semanticFailIfFalse(isContinueValid.value_or(continueIsValid()), "'continue' is only valid inside a loop statement"); return context.createContinueStatement(location, &m_vm.propertyNames->nullIdentifier, start, end); } failIfFalse(matchSpecIdentifier(), "Expected an identifier as the target for a continue statement"); @@ -1665,7 +1682,7 @@ template TreeStatement Parser::parseReturnStateme { ASSERT(match(RETURN)); JSTokenLocation location(tokenLocation()); - semanticFailIfFalse(currentScope()->isFunction(), "Return statements are only valid inside functions"); + semanticFailIfFalse(currentScope()->isFunction() && !currentScope()->isStaticBlock(), "Return statements are only valid inside functions"); JSTextPosition start = tokenStartPosition(); JSTextPosition end = tokenEndPosition(); next(); @@ -1838,6 +1855,7 @@ template TreeStatement Parser::parseTryStatement( } else { handleProductionOrFail(OPENPAREN, "(", "start", "'catch' target"); DepthManager statementDepth(&m_statementDepth); + semanticFailIfTrue(currentScope()->isStaticBlock() && match(AWAIT), "Cannot use 'await' as identifier within static block"); m_statementDepth++; AutoPopScopeRef catchScope(this, pushScope()); catchScope->setIsLexicalScope(); @@ -1855,8 +1873,7 @@ template TreeStatement Parser::parseTryStatement( } handleProductionOrFail(CLOSEPAREN, ")", "end", "'catch' target"); matchOrFail(OPENBRACE, "Expected exception handler to be a block statement"); - constexpr bool isCatchBlock = true; - catchBlock = parseBlockStatement(context, isCatchBlock); + catchBlock = parseBlockStatement(context, BlockType::CatchBlock); failIfFalse(catchBlock, "Unable to parse 'catch' block"); std::tie(catchEnvironment, functionStack) = popScope(catchScope, TreeBuilder::NeedsFreeVariableInfo); ASSERT(functionStack.isEmpty()); @@ -1889,7 +1906,7 @@ template TreeStatement Parser::parseDebuggerState } template -template TreeStatement Parser::parseBlockStatement(TreeBuilder& context, bool isCatchBlock) +template TreeStatement Parser::parseBlockStatement(TreeBuilder& context, BlockType type) { ASSERT(match(OPENBRACE)); @@ -1901,8 +1918,19 @@ template TreeStatement Parser::parseBlockStatemen ScopeRef newScope = pushScope(); newScope->setIsLexicalScope(); newScope->preventVarDeclarations(); - if (isCatchBlock) + switch (type) { + case BlockType::CatchBlock: newScope->setIsCatchBlockScope(); + break; + case BlockType::StaticBlock: + newScope->setIsStaticBlock(); + break; + case BlockType::Normal: + break; + default: + RELEASE_ASSERT_NOT_REACHED(); + break; + } lexicalScope.setIsValid(newScope, this); } JSTokenLocation location(tokenLocation()); @@ -2222,6 +2250,7 @@ static const char* stringArticleForFunctionMode(SourceParseMode mode) case SourceParseMode::ModuleAnalyzeMode: case SourceParseMode::ModuleEvaluateMode: case SourceParseMode::ClassFieldInitializerMode: + case SourceParseMode::ClassStaticBlockMode: RELEASE_ASSERT_NOT_REACHED(); return ""; } @@ -2264,6 +2293,7 @@ static const char* stringForFunctionMode(SourceParseMode mode) case SourceParseMode::ModuleAnalyzeMode: case SourceParseMode::ModuleEvaluateMode: case SourceParseMode::ClassFieldInitializerMode: + case SourceParseMode::ClassStaticBlockMode: RELEASE_ASSERT_NOT_REACHED(); return ""; } @@ -2276,6 +2306,8 @@ template template bool Parser::parseFunctionInfo(TreeBuild context.setEndOffset(functionInfo.body, m_lexer->currentOffset()); if (functionScope->strictMode() && requirements != FunctionNameRequirements::Unnamed) { ASSERT(functionInfo.name); - RELEASE_ASSERT(SourceParseModeSet(SourceParseMode::NormalFunctionMode, SourceParseMode::MethodMode, SourceParseMode::ArrowFunctionMode, SourceParseMode::GeneratorBodyMode, SourceParseMode::GeneratorWrapperFunctionMode).contains(mode) || isAsyncFunctionOrAsyncGeneratorWrapperParseMode(mode)); + RELEASE_ASSERT(SourceParseModeSet(SourceParseMode::NormalFunctionMode, SourceParseMode::MethodMode, SourceParseMode::ArrowFunctionMode, SourceParseMode::GeneratorBodyMode, SourceParseMode::GeneratorWrapperFunctionMode, SourceParseMode::ClassStaticBlockMode).contains(mode) || isAsyncFunctionOrAsyncGeneratorWrapperParseMode(mode)); semanticFailIfTrue(m_vm.propertyNames->arguments == *functionInfo.name, "'", functionInfo.name->impl(), "' is not a valid function name in strict mode"); semanticFailIfTrue(m_vm.propertyNames->eval == *functionInfo.name, "'", functionInfo.name->impl(), "' is not a valid function name in strict mode"); } @@ -2928,6 +2960,7 @@ template TreeClassExpression Parser::parseClass(T classHeadScope->preventVarDeclarations(); classHeadScope->setStrictMode(); next(); + semanticFailIfTrue(currentScope()->isStaticBlock() && match(AWAIT), "Cannot use 'await' as a class name within static block"); ASSERT_WITH_MESSAGE(requirements != FunctionNameRequirements::Unnamed, "Currently, there is no caller that uses FunctionNameRequirements::Unnamed for class syntax."); ASSERT_WITH_MESSAGE(!(requirements == FunctionNameRequirements::None && !info.className), "When specifying FunctionNameRequirements::None, we need to initialize info.className with the default value in the caller side."); @@ -2982,6 +3015,7 @@ template TreeClassExpression Parser::parseClass(T // For backwards compatibility, "static" is a non-reserved keyword in non-strict mode. ClassElementTag tag = ClassElementTag::Instance; + SourceParseMode parseMode = SourceParseMode::MethodMode; auto type = PropertyNode::Constant; if (match(RESERVED_IF_STRICT) && *m_token.m_data.ident == m_vm.propertyNames->staticKeyword) { SavePoint savePoint = createSavePoint(context); @@ -2989,8 +3023,11 @@ template TreeClassExpression Parser::parseClass(T if (match(OPENPAREN) || match(SEMICOLON) || match(EQUAL)) { // Reparse "static()" as a method or "static" as a class field. restoreSavePoint(context, savePoint); - } else + } else { tag = ClassElementTag::Static; + if (match(OPENBRACE)) + parseMode = SourceParseMode::ClassStaticBlockMode; + } } // FIXME: Figure out a way to share more code with parseProperty. @@ -2999,7 +3036,6 @@ template TreeClassExpression Parser::parseClass(T TreeExpression computedPropertyName = 0; bool isGetter = false; bool isSetter = false; - SourceParseMode parseMode = SourceParseMode::MethodMode; if (consume(TIMES)) parseMode = SourceParseMode::GeneratorWrapperMethodMode; @@ -3052,6 +3088,7 @@ template TreeClassExpression Parser::parseClass(T break; case OPENBRACKET: next(); + semanticFailIfTrue(currentScope()->isStaticBlock() && match(IDENT) && isArgumentsIdentifier(), "Cannot use 'arguments' as an identifier in static block"); computedPropertyName = parseAssignmentExpression(context); type = static_cast(type | PropertyNode::Computed); failIfFalse(computedPropertyName, "Cannot parse computed property name"); @@ -3080,6 +3117,10 @@ template TreeClassExpression Parser::parseClass(T type = static_cast(type | PropertyNode::PrivateField); break; } + case OPENBRACE: + failIfFalse(parseMode == SourceParseMode::ClassStaticBlockMode, "Cannot parse static block without 'static'"); + type = static_cast(type | PropertyNode::Block); + break; default: if (m_token.m_type & KeywordTokenFlag) goto namedKeyword; @@ -3148,6 +3189,16 @@ template TreeClassExpression Parser::parseClass(T property = context.createProperty(ident, computedPropertyName, initializer, type, SuperBinding::NotNeeded, tag); else property = context.createProperty(ident, initializer, type, SuperBinding::NotNeeded, inferName, tag); + } else if (parseMode == SourceParseMode::ClassStaticBlockMode) { + matchOrFail(OPENBRACE, "Expected block statement for class static block"); + size_t usedVariablesSize = currentScope()->currentUsedVariablesSize(); + currentScope()->pushUsedVariableSet(); + SetForScope parsingWithClassStaticBlockMode(m_parseMode, parseMode); + DepthManager statementDepth(&m_statementDepth); + m_statementDepth = 1; + failIfFalse(parseBlockStatement(context, BlockType::StaticBlock), "Cannot parse class static block"); + property = context.createProperty(ident, type, SuperBinding::Needed, tag); + classScope->markLastUsedVariablesSetAsCaptured(usedVariablesSize); } else { ParserFunctionInfo methodInfo; bool isConstructor = tag == ClassElementTag::Instance && *ident == propertyNames.constructor; @@ -3280,6 +3331,9 @@ template TreeSourceElements Parser::parseClassFie type = DefineFieldNode::Type::ComputedName; break; } + case OPENBRACE: + RELEASE_ASSERT(isStaticField); + break; default: if (m_token.m_type & KeywordTokenFlag) goto namedKeyword; @@ -3288,17 +3342,35 @@ template TreeSourceElements Parser::parseClassFie } // Only valid class fields are handled in this function. - ASSERT(match(EQUAL) || match(SEMICOLON) || match(CLOSEBRACE) || m_lexer->hasLineTerminatorBeforeToken()); + ASSERT(match(EQUAL) || match(SEMICOLON) || match(CLOSEBRACE) || match(OPENBRACE) || m_lexer->hasLineTerminatorBeforeToken()); + + TreeStatement statement; + if (match(OPENBRACE) && isStaticField) { + JSTextPosition startPosition = tokenStartPosition(); + JSTokenLocation startLocation(tokenLocation()); + unsigned expressionStart = tokenStart(); - TreeExpression initializer = 0; - if (consume(EQUAL)) - initializer = parseAssignmentExpression(context); + ParserFunctionInfo functionInfo; + functionInfo.name = &m_vm.propertyNames->nullIdentifier; + SetForScope setInnerParseMode(m_parseMode, SourceParseMode::ClassStaticBlockMode); + failIfFalse((parseFunctionInfo(context, FunctionNameRequirements::None, false, ConstructorKind::None, SuperBinding::Needed, expressionStart, functionInfo, FunctionDefinitionType::Expression)), "Cannot parse static block function"); + TreeExpression expression = context.createFunctionExpr(startLocation, functionInfo); - if (type == DefineFieldNode::Type::PrivateName) - currentScope()->useVariable(ident, false); + JSTextPosition endPosition = lastTokenEndPosition(); + expression = context.makeStaticBlockFunctionCallNode(startLocation, expression, endPosition, startPosition, lastTokenEndPosition()); - TreeStatement defineField = context.createDefineField(fieldLocation, ident, initializer, type); - context.appendStatement(sourceElements, defineField); + statement = context.createExprStatement(startLocation, expression, startPosition, m_lastTokenEndPosition.line); + } else { + TreeExpression initializer = 0; + if (consume(EQUAL)) + initializer = parseAssignmentExpression(context); + + if (type == DefineFieldNode::Type::PrivateName) + currentScope()->useVariable(ident, false); + + statement = context.createDefineField(fieldLocation, ident, initializer, type); + } + context.appendStatement(sourceElements, statement); } ASSERT(!hasError()); @@ -4112,6 +4184,22 @@ template TreeExpression Parser::parseAssignmen TreeExpression lhs = parseConditionalExpression(context); + // Current implementation of parseAssignmentExpression causes a weird parsing loop + // for this example: + // + // class C { + // static { + // ((x = await) => 0); + // } + // } + // + // which makes the 'await' error caught in parseConditionalExpression escaping from + // parseAssignmentExpression. Therefore, we need to capture the error directly after + // parseConditionalExpression. Besides, the usage of `await` is strictly limited in + // class static block. + if (UNLIKELY(!lhs && currentScope()->isStaticBlock() && match(AWAIT))) + propagateError(); + if (maybeValidArrowFunctionStart && !match(EOFTOK)) { bool isArrowFunctionToken = match(ARROWFUNCTION); if (!lhs || isArrowFunctionToken) { @@ -4939,6 +5027,7 @@ template TreeExpression Parser::parsePrimaryExpre return context.createThisExpr(location); } case AWAIT: + semanticFailIfTrue(currentScope()->isStaticBlock(), "The 'await' keyword is disallowed in the IdentifierReference position within static block"); if (m_parserState.functionParsePhase == FunctionParsePhase::Parameters) semanticFailIfFalse(m_parserState.allowAwait, "Cannot use 'await' within a parameter default expression"); else if (currentFunctionScope()->isAsyncFunctionBoundary() || isModuleParseMode(sourceParseMode())) @@ -4946,6 +5035,7 @@ template TreeExpression Parser::parsePrimaryExpre goto identifierExpression; case IDENT: { + semanticFailIfTrue(currentScope()->isStaticBlock() && isArgumentsIdentifier(), "Cannot use 'arguments' as an identifier in static block"); if (UNLIKELY(*m_token.m_data.ident == m_vm.propertyNames->async && !m_token.m_data.escaped)) { JSTextPosition start = tokenStartPosition(); const Identifier* ident = m_token.m_data.ident; @@ -4962,7 +5052,7 @@ template TreeExpression Parser::parsePrimaryExpre return createResolveAndUseVariable(context, ident, isEval, start, location); } if (UNLIKELY(m_parserState.isParsingClassFieldInitializer)) - failIfTrue(*m_token.m_data.ident == m_vm.propertyNames->arguments, "Cannot reference 'arguments' in class field initializer"); + failIfTrue(isArgumentsIdentifier(), "Cannot reference 'arguments' in class field initializer"); identifierExpression: JSTextPosition start = tokenStartPosition(); const Identifier* ident = m_token.m_data.ident; @@ -5168,7 +5258,7 @@ template TreeExpression Parser::parseMemberExpres ScopeRef closestOrdinaryFunctionScope = closestParentOrdinaryFunctionNonLexicalScope(); bool isClassFieldInitializer = m_parserState.isParsingClassFieldInitializer; bool isFunctionEvalContextType = m_isInsideOrdinaryFunction && (closestOrdinaryFunctionScope->evalContextType() == EvalContextType::FunctionEvalContext || closestOrdinaryFunctionScope->evalContextType() == EvalContextType::InstanceFieldEvalContext); - semanticFailIfFalse(currentScope()->isFunction() || isFunctionEvalContextType || isClassFieldInitializer, "new.target is only valid inside functions"); + semanticFailIfFalse(currentScope()->isFunction() || currentScope()->isStaticBlock() || isFunctionEvalContextType || isClassFieldInitializer, "new.target is only valid inside functions or static blocks"); baseIsNewTarget = true; if (currentScope()->isArrowFunction()) { semanticFailIfFalse(!closestOrdinaryFunctionScope->isGlobalCodeScope() || isFunctionEvalContextType || isClassFieldInitializer, "new.target is not valid inside arrow functions in global code"); @@ -5455,8 +5545,10 @@ template TreeExpression Parser::parseUnaryExpress bool hasPrefixUpdateOp = false; unsigned lastOperator = 0; - if (UNLIKELY(match(AWAIT) && (currentFunctionScope()->isAsyncFunctionBoundary() || isModuleParseMode(sourceParseMode())))) + if (UNLIKELY(match(AWAIT) && (currentFunctionScope()->isAsyncFunctionBoundary() || isModuleParseMode(sourceParseMode())))) { + semanticFailIfTrue(currentScope()->isStaticBlock(), "Cannot use 'await' within static block"); return parseAwaitExpression(context); + } JSTokenLocation location(tokenLocation()); diff --git a/Source/JavaScriptCore/parser/Parser.h b/Source/JavaScriptCore/parser/Parser.h index 0f73cbfe55bb1..678af7c95da7c 100644 --- a/Source/JavaScriptCore/parser/Parser.h +++ b/Source/JavaScriptCore/parser/Parser.h @@ -148,7 +148,7 @@ struct Scope { WTF_MAKE_NONCOPYABLE(Scope); public: - Scope(const VM& vm, ImplementationVisibility implementationVisibility, LexicalScopeFeatures lexicalScopeFeatures, bool isFunction, bool isGenerator, bool isArrowFunction, bool isAsyncFunction) + Scope(const VM& vm, ImplementationVisibility implementationVisibility, LexicalScopeFeatures lexicalScopeFeatures, bool isFunction, bool isGenerator, bool isArrowFunction, bool isAsyncFunction, bool isStaticBlock) : m_vm(vm) , m_implementationVisibility(implementationVisibility) , m_shadowsArguments(false) @@ -182,6 +182,7 @@ struct Scope { , m_loopDepth(0) , m_switchDepth(0) , m_innerArrowFunctionFeatures(0) + , m_isStaticBlock(isStaticBlock) { m_usedVariables.append(UniquedStringImplPtrSet()); } @@ -263,6 +264,11 @@ struct Scope { setIsFunction(); break; + case SourceParseMode::ClassStaticBlockMode: + setIsFunction(); + setIsStaticBlock(); + break; + case SourceParseMode::ArrowFunctionMode: setIsArrowFunction(); break; @@ -303,6 +309,14 @@ struct Scope { void setIsCatchBlockScope() { m_isCatchBlockScope = true; } bool isCatchBlockScope() { return m_isCatchBlockScope; } + void setIsStaticBlock() + { + m_isStaticBlock = true; + m_isStaticBlockBoundary = true; + } + bool isStaticBlock() { return m_isStaticBlock; } + bool isStaticBlockBoundary() { return m_isStaticBlockBoundary; } + void setIsLexicalScope() { m_isLexicalScope = true; @@ -824,6 +838,8 @@ struct Scope { m_isArrowFunction = false; m_isAsyncFunction = false; m_isAsyncFunctionBoundary = false; + m_isStaticBlock = false; + m_isStaticBlockBoundary = false; } void setIsGeneratorFunction() @@ -894,37 +910,39 @@ struct Scope { const VM& m_vm; ImplementationVisibility m_implementationVisibility; - bool m_shadowsArguments; - bool m_usesEval; - bool m_needsFullActivation; - bool m_hasDirectSuper; - bool m_needsSuperBinding; - bool m_allowsVarDeclarations; - bool m_allowsLexicalDeclarations; + bool m_shadowsArguments : 1 { false }; + bool m_usesEval : 1 { false }; + bool m_needsFullActivation : 1 { false }; + bool m_hasDirectSuper : 1 { false }; + bool m_needsSuperBinding : 1 { false }; + bool m_allowsVarDeclarations : 1 { true }; + bool m_allowsLexicalDeclarations : 1 { true }; LexicalScopeFeatures m_lexicalScopeFeatures; - bool m_isFunction; - bool m_isGenerator; - bool m_isGeneratorBoundary; - bool m_isArrowFunction; - bool m_isArrowFunctionBoundary; - bool m_isAsyncFunction; - bool m_isAsyncFunctionBoundary; - bool m_isLexicalScope; - bool m_isGlobalCodeScope; - bool m_isSimpleCatchParameterScope; - bool m_isCatchBlockScope; - bool m_isFunctionBoundary; - bool m_isValidStrictMode; - bool m_hasArguments; - bool m_isEvalContext; - bool m_hasNonSimpleParameterList; - bool m_isClassScope; - EvalContextType m_evalContextType; - unsigned m_constructorKind; - unsigned m_expectedSuperBinding; - int m_loopDepth; - int m_switchDepth; - InnerArrowFunctionCodeFeatures m_innerArrowFunctionFeatures; + bool m_isFunction : 1; + bool m_isGenerator : 1; + bool m_isGeneratorBoundary : 1 { false }; + bool m_isArrowFunction : 1; + bool m_isArrowFunctionBoundary : 1 { false }; + bool m_isAsyncFunction : 1; + bool m_isAsyncFunctionBoundary : 1 { false }; + bool m_isLexicalScope : 1 { false }; + bool m_isGlobalCodeScope : 1 { false }; + bool m_isSimpleCatchParameterScope : 1 { false }; + bool m_isCatchBlockScope : 1 { false }; + bool m_isStaticBlock : 1 { false }; + bool m_isStaticBlockBoundary : 1 { false }; + bool m_isFunctionBoundary : 1 { false }; + bool m_isValidStrictMode : 1 { true }; + bool m_hasArguments : 1 { false }; + bool m_isEvalContext : 1 { false }; + bool m_hasNonSimpleParameterList : 1 { false }; + bool m_isClassScope : 1 { false }; + EvalContextType m_evalContextType { EvalContextType::None }; + unsigned m_constructorKind { static_cast(ConstructorKind::None) }; + unsigned m_expectedSuperBinding { static_cast(SuperBinding::NotNeeded) }; + int m_loopDepth { 0 }; + int m_switchDepth { 0 }; + InnerArrowFunctionCodeFeatures m_innerArrowFunctionFeatures { 0 }; typedef Vector LabelStack; std::unique_ptr m_labels; @@ -1303,6 +1321,7 @@ class Parser { bool isGenerator = false; bool isArrowFunction = false; bool isAsyncFunction = false; + bool isStaticBlock = false; if (!m_scopeStack.isEmpty()) { implementationVisibility = m_scopeStack.last().implementationVisibility(); lexicalScopeFeatures = m_scopeStack.last().lexicalScopeFeatures(); @@ -1310,8 +1329,9 @@ class Parser { isGenerator = m_scopeStack.last().isGenerator(); isArrowFunction = m_scopeStack.last().isArrowFunction(); isAsyncFunction = m_scopeStack.last().isAsyncFunction(); + isStaticBlock = m_scopeStack.last().isStaticBlock(); } - m_scopeStack.constructAndAppend(m_vm, implementationVisibility, lexicalScopeFeatures, isFunction, isGenerator, isArrowFunction, isAsyncFunction); + m_scopeStack.constructAndAppend(m_vm, implementationVisibility, lexicalScopeFeatures, isFunction, isGenerator, isArrowFunction, isAsyncFunction, isStaticBlock); return currentScope(); } @@ -1696,7 +1716,7 @@ class Parser { { ScopeRef current = currentScope(); while (!current->breakIsValid()) { - if (!current.hasContainingScope()) + if (!current.hasContainingScope() || current->isStaticBlockBoundary()) return false; current = current.containingScope(); } @@ -1706,7 +1726,7 @@ class Parser { { ScopeRef current = currentScope(); while (!current->continueIsValid()) { - if (!current.hasContainingScope()) + if (!current.hasContainingScope() || current->isStaticBlockBoundary()) return false; current = current.containingScope(); } @@ -1773,7 +1793,8 @@ class Parser { template TreeStatement parseExpressionStatement(TreeBuilder&); template TreeStatement parseExpressionOrLabelStatement(TreeBuilder&, bool allowFunctionDeclarationAsStatement); template TreeStatement parseIfStatement(TreeBuilder&); - template TreeStatement parseBlockStatement(TreeBuilder&, bool isCatchBlock = false); + enum class BlockType : uint8_t { Normal, CatchBlock, StaticBlock }; + template TreeStatement parseBlockStatement(TreeBuilder&, BlockType = BlockType::Normal); enum class IsOnlyChildOfStatement { Yes, No }; template TreeExpression parseExpression(TreeBuilder&, IsOnlyChildOfStatement = IsOnlyChildOfStatement::No); @@ -1896,7 +1917,7 @@ class Parser { ALWAYS_INLINE bool canUseIdentifierAwait() { - return m_parserState.allowAwait && !currentScope()->isAsyncFunction() && m_scriptMode != JSParserScriptMode::Module; + return m_parserState.allowAwait && !currentScope()->isAsyncFunction() && !currentScope()->isStaticBlock() && m_scriptMode != JSParserScriptMode::Module; } bool isDisallowedIdentifierYield(const JSToken& token) @@ -1954,6 +1975,8 @@ class Parser { { if (!m_parserState.allowAwait || currentScope()->isAsyncFunction()) return "in an async function"; + if (currentScope()->isStaticBlock()) + return "in a static block"; if (m_scriptMode == JSParserScriptMode::Module) return "in a module"; RELEASE_ASSERT_NOT_REACHED(); @@ -1970,6 +1993,11 @@ class Parser { return nullptr; } + ALWAYS_INLINE bool isArgumentsIdentifier() + { + return *m_token.m_data.ident == m_vm.propertyNames->arguments; + } + enum class FunctionParsePhase { Parameters, Body }; struct ParserState { int assignmentCount { 0 }; diff --git a/Source/JavaScriptCore/parser/ParserModes.h b/Source/JavaScriptCore/parser/ParserModes.h index 1ae56ec439a43..6443ce636290e 100644 --- a/Source/JavaScriptCore/parser/ParserModes.h +++ b/Source/JavaScriptCore/parser/ParserModes.h @@ -69,6 +69,7 @@ enum class SourceParseMode : uint8_t { AsyncGeneratorWrapperMethodMode = 17, GeneratorWrapperMethodMode = 18, ClassFieldInitializerMode = 19, + ClassStaticBlockMode = 20, }; class SourceParseModeSet { @@ -118,7 +119,8 @@ ALWAYS_INLINE bool isFunctionParseMode(SourceParseMode parseMode) SourceParseMode::AsyncGeneratorBodyMode, SourceParseMode::AsyncGeneratorWrapperFunctionMode, SourceParseMode::AsyncGeneratorWrapperMethodMode, - SourceParseMode::ClassFieldInitializerMode).contains(parseMode); + SourceParseMode::ClassFieldInitializerMode, + SourceParseMode::ClassStaticBlockMode).contains(parseMode); } ALWAYS_INLINE bool isAsyncFunctionParseMode(SourceParseMode parseMode) @@ -206,7 +208,8 @@ ALWAYS_INLINE bool isMethodParseMode(SourceParseMode parseMode) SourceParseMode::SetterMode, SourceParseMode::MethodMode, SourceParseMode::AsyncMethodMode, - SourceParseMode::AsyncGeneratorWrapperMethodMode).contains(parseMode); + SourceParseMode::AsyncGeneratorWrapperMethodMode, + SourceParseMode::ClassStaticBlockMode).contains(parseMode); } ALWAYS_INLINE bool isGeneratorOrAsyncFunctionBodyParseMode(SourceParseMode parseMode) diff --git a/Source/JavaScriptCore/parser/SyntaxChecker.h b/Source/JavaScriptCore/parser/SyntaxChecker.h index ed0da5fe56666..ae931fbf576fc 100644 --- a/Source/JavaScriptCore/parser/SyntaxChecker.h +++ b/Source/JavaScriptCore/parser/SyntaxChecker.h @@ -148,6 +148,7 @@ class SyntaxChecker { static constexpr OptionSet DontBuildStrings = LexerFlags::DontBuildStrings; int createSourceElements() { return SourceElementsResult; } + ExpressionType makeStaticBlockFunctionCallNode(const JSTokenLocation&, ExpressionType, int, int, int) { return CallExpr; } ExpressionType makeFunctionCallNode(const JSTokenLocation&, ExpressionType, bool, int, int, int, int, size_t, bool) { return CallExpr; } ExpressionType createCommaExpr(const JSTokenLocation&, ExpressionType) { return CommaExpr; } ExpressionType appendToCommaExpr(const JSTokenLocation&, ExpressionType, ExpressionType, ExpressionType) { return CommaExpr; } @@ -239,6 +240,10 @@ class SyntaxChecker { { return Property(type); } + Property createProperty(const Identifier*, PropertyNode::Type type, SuperBinding, ClassElementTag) + { + return Property(type); + } int createPropertyList(const JSTokenLocation&, Property) { return PropertyListResult; } int createPropertyList(const JSTokenLocation&, Property, int) { return PropertyListResult; } int createElementList(int, int) { return ElementsListResult; } diff --git a/Source/JavaScriptCore/runtime/FunctionExecutable.cpp b/Source/JavaScriptCore/runtime/FunctionExecutable.cpp index 3764e8993b104..8f79e466a12b2 100644 --- a/Source/JavaScriptCore/runtime/FunctionExecutable.cpp +++ b/Source/JavaScriptCore/runtime/FunctionExecutable.cpp @@ -206,6 +206,7 @@ JSString* FunctionExecutable::toStringSlow(JSGlobalObject* globalObject) case SourceParseMode::ArrowFunctionMode: case SourceParseMode::ClassFieldInitializerMode: + case SourceParseMode::ClassStaticBlockMode: break; case SourceParseMode::AsyncFunctionMode: