Procházet zdrojové kódy

LibJS: Allow CallExpression as left hand side of for-of/for-in loops

Although this will fail with a ReferenceError it should pass the parser
and only fail if actually assigned to.
davidot před 3 roky
rodič
revize
65bebb5241

+ 1 - 1
Userland/Libraries/LibJS/AST.cpp

@@ -865,7 +865,7 @@ struct ForInOfHeadState {
                     VERIFY(declaration.declarations().first().target().has<NonnullRefPtr<Identifier>>());
                     lhs_reference = TRY(declaration.declarations().first().target().get<NonnullRefPtr<Identifier>>()->to_reference(interpreter, global_object));
                 } else {
-                    VERIFY(is<Identifier>(*expression_lhs) || is<MemberExpression>(*expression_lhs));
+                    VERIFY(is<Identifier>(*expression_lhs) || is<MemberExpression>(*expression_lhs) || is<CallExpression>(*expression_lhs));
                     auto& expression = static_cast<Expression const&>(*expression_lhs);
                     lhs_reference = TRY(expression.to_reference(interpreter, global_object));
                 }

+ 1 - 1
Userland/Libraries/LibJS/Parser.cpp

@@ -3492,7 +3492,7 @@ NonnullRefPtr<Statement> Parser::parse_for_in_of_statement(NonnullRefPtr<ASTNode
                     has_annexB_for_in_init_extension = true;
             }
         }
-    } else if (!lhs->is_identifier() && !is<MemberExpression>(*lhs)) {
+    } else if (!lhs->is_identifier() && !is<MemberExpression>(*lhs) && !is<CallExpression>(*lhs)) {
         bool valid = false;
         if (is<ObjectExpression>(*lhs) || is<ArrayExpression>(*lhs)) {
             auto synthesized_binding_pattern = synthesize_binding_pattern(static_cast<Expression const&>(*lhs));

+ 31 - 4
Userland/Libraries/LibJS/Tests/loops/for-in-basic.js

@@ -67,8 +67,35 @@ test("allow binding patterns", () => {
     expect(counter).toBe(3);
 });
 
-test("allow member expression as variable", () => {
-    const f = {};
-    for (f.a in "abc");
-    expect(f.a).toBe("2");
+describe("special left hand sides", () => {
+    test("allow member expression as variable", () => {
+        const f = {};
+        for (f.a in "abc");
+        expect(f.a).toBe("2");
+    });
+
+    test("allow member expression of function call", () => {
+        const b = {};
+        function f() {
+            return b;
+        }
+
+        for (f().a in "abc");
+
+        expect(f().a).toBe("2");
+        expect(b.a).toBe("2");
+    });
+
+    test("call function is allowed in parsing but fails in runtime", () => {
+        function f() {
+            expect().fail();
+        }
+
+        // Does not fail since it does not iterate
+        expect("for (f() in []);").toEvalTo(undefined);
+
+        expect(() => {
+            eval("for (f() in [0]) { expect().fail() }");
+        }).toThrowWithMessage(ReferenceError, "Invalid left-hand side in assignment");
+    });
 });

+ 30 - 4
Userland/Libraries/LibJS/Tests/loops/for-of-basic.js

@@ -112,8 +112,34 @@ test("allow binding patterns", () => {
     expect(counter).toBe(3);
 });
 
-test("allow member expression as variable", () => {
-    const f = {};
-    for (f.a of "abc");
-    expect(f.a).toBe("c");
+describe("special left hand sides", () => {
+    test("allow member expression as variable", () => {
+        const f = {};
+        for (f.a of "abc");
+        expect(f.a).toBe("c");
+    });
+
+    test("allow member expression of function call", () => {
+        const b = {};
+        function f() {
+            return b;
+        }
+
+        for (f().a of "abc");
+
+        expect(f().a).toBe("c");
+    });
+
+    test("call function is allowed in parsing but fails in runtime", () => {
+        function f() {
+            expect().fail();
+        }
+
+        // Does not fail since it does not iterate but prettier does not like it so we use eval.
+        expect("for (f() of []);").toEvalTo(undefined);
+
+        expect(() => {
+            eval("for (f() of [0]) { expect().fail() }");
+        }).toThrowWithMessage(ReferenceError, "Invalid left-hand side in assignment");
+    });
 });