From 7798821f5ba4e574d5049f2182c90d229d7923c1 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Sun, 10 Apr 2022 00:56:04 +0100 Subject: [PATCH] LibJS: Add tests for the new steps added to PerformEval --- .../LibJS/Tests/classes/class-inheritance.js | 250 ++++++++++++++++++ .../Tests/classes/class-private-fields.js | 37 +++ .../Tests/classes/class-public-fields.js | 37 +++ .../Tests/functions/function-new-target.js | 36 +++ Userland/Libraries/LibJS/Tests/return.js | 28 ++ 5 files changed, 388 insertions(+) diff --git a/Userland/Libraries/LibJS/Tests/classes/class-inheritance.js b/Userland/Libraries/LibJS/Tests/classes/class-inheritance.js index 336e2b729b9..3ec85ec3ee1 100644 --- a/Userland/Libraries/LibJS/Tests/classes/class-inheritance.js +++ b/Userland/Libraries/LibJS/Tests/classes/class-inheritance.js @@ -199,3 +199,253 @@ test("Issue #8574, super property access before super() call", () => { }).toThrowWithMessage(ReferenceError, "|this| has not been initialized"); expect(hit).toBeTrue(); }); + +test("can access super via direct eval", () => { + let superCalled = false; + const aObject = { a: 1 }; + const bObject = { b: 2 }; + + class A { + constructor() { + superCalled = true; + } + + foo() { + return aObject; + } + + bar() { + return bObject; + } + } + + class B extends A { + constructor() { + eval("super()"); + } + } + + expect(() => { + new B(); + }).not.toThrow(); + + expect(superCalled).toBeTrue(); + superCalled = false; + + class C extends A { + constructor() { + eval("super()"); + return eval("super.foo()"); + } + } + + expect(() => { + new C(); + }).not.toThrow(); + + expect(superCalled).toBeTrue(); + superCalled = false; + + expect(new C()).toBe(aObject); + + expect(superCalled).toBeTrue(); + superCalled = false; + + class D extends A { + constructor() { + eval("super()"); + return eval("super['bar']()"); + } + } + + expect(() => { + new D(); + }).not.toThrow(); + + expect(superCalled).toBeTrue(); + superCalled = false; + + expect(new D()).toBe(bObject); + + expect(superCalled).toBeTrue(); +}); + +test("cannot access super via indirect eval", () => { + const indirect = eval; + let superCalled = false; + + const aObject = { a: 1 }; + const bObject = { b: 1 }; + + class A { + constructor() { + superCalled = true; + this.a = aObject; + this.c = bObject; + } + } + + class B extends A { + constructor() { + indirect("super()"); + } + } + + expect(() => { + new B(); + }).toThrowWithMessage(SyntaxError, "'super' keyword unexpected here"); + + expect(superCalled).toBeFalse(); + + class C extends A { + constructor() { + super(); + return indirect("super.a"); + } + } + + expect(() => { + new C(); + }).toThrowWithMessage(SyntaxError, "'super' keyword unexpected here"); + + expect(superCalled).toBeTrue(); + superCalled = false; + + class D extends A { + constructor() { + super(); + return indirect("super['b']"); + } + } + + expect(() => { + new D(); + }).toThrowWithMessage(SyntaxError, "'super' keyword unexpected here"); + + expect(superCalled).toBeTrue(); +}); + +test("super outside of derived class fails to parse", () => { + expect("super").not.toEval(); + expect("super()").not.toEval(); + expect("super.a").not.toEval(); + expect("super['b']").not.toEval(); + expect("function a() { super }").not.toEval(); + expect("function a() { super() }").not.toEval(); + expect("function a() { super.a }").not.toEval(); + expect("function a() { super['b'] }").not.toEval(); + expect("() => { super }").not.toEval(); + expect("() => { super() }").not.toEval(); + expect("() => { super.a }").not.toEval(); + expect("() => { super['b'] }").not.toEval(); + expect("class A { constructor() { super } }").not.toEval(); + expect("class A { constructor() { super() } }").not.toEval(); + + expect(() => { + eval("super"); + }).toThrowWithMessage(SyntaxError, "'super' keyword unexpected here"); + + expect(() => { + eval("super()"); + }).toThrowWithMessage(SyntaxError, "'super' keyword unexpected here"); + + expect(() => { + eval("super.a"); + }).toThrowWithMessage(SyntaxError, "'super' keyword unexpected here"); + + expect(() => { + eval("super['b']"); + }).toThrowWithMessage(SyntaxError, "'super' keyword unexpected here"); + + function a() { + eval("super"); + } + + expect(() => { + a(); + }).toThrowWithMessage(SyntaxError, "'super' keyword unexpected here"); + + expect(() => { + new a(); + }).toThrowWithMessage(SyntaxError, "'super' keyword unexpected here"); + + function b() { + eval("super()"); + } + + expect(() => { + b(); + }).toThrowWithMessage(SyntaxError, "'super' keyword unexpected here"); + + expect(() => { + new b(); + }).toThrowWithMessage(SyntaxError, "'super' keyword unexpected here"); + + function c() { + eval("super.a"); + } + + expect(() => { + c(); + }).toThrowWithMessage(SyntaxError, "'super' keyword unexpected here"); + + expect(() => { + new c(); + }).toThrowWithMessage(SyntaxError, "'super' keyword unexpected here"); + + function d() { + eval("super['b']"); + } + + expect(() => { + d(); + }).toThrowWithMessage(SyntaxError, "'super' keyword unexpected here"); + + expect(() => { + new d(); + }).toThrowWithMessage(SyntaxError, "'super' keyword unexpected here"); + + const e = () => eval("super"); + + expect(() => { + e(); + }).toThrowWithMessage(SyntaxError, "'super' keyword unexpected here"); + + const f = () => eval("super()"); + + expect(() => { + f(); + }).toThrowWithMessage(SyntaxError, "'super' keyword unexpected here"); + + const g = () => eval("super.a"); + + expect(() => { + g(); + }).toThrowWithMessage(SyntaxError, "'super' keyword unexpected here"); + + const h = () => eval("super['b']"); + + expect(() => { + h(); + }).toThrowWithMessage(SyntaxError, "'super' keyword unexpected here"); + + class I { + constructor() { + eval("super"); + } + } + + expect(() => { + new I(); + }).toThrowWithMessage(SyntaxError, "'super' keyword unexpected here"); + + class J { + constructor() { + eval("super()"); + } + } + + expect(() => { + new J(); + }).toThrowWithMessage(SyntaxError, "'super' keyword unexpected here"); +}); diff --git a/Userland/Libraries/LibJS/Tests/classes/class-private-fields.js b/Userland/Libraries/LibJS/Tests/classes/class-private-fields.js index 2eebaa62457..a00dd74235c 100644 --- a/Userland/Libraries/LibJS/Tests/classes/class-private-fields.js +++ b/Userland/Libraries/LibJS/Tests/classes/class-private-fields.js @@ -111,3 +111,40 @@ test("private identifier not followed by 'in' throws", () => { test("cannot have static and non static field with the same description", () => { expect("class A { static #simple; #simple; }").not.toEval(); }); + +test("'arguments' is not allowed in class field initializer", () => { + expect("class A { #a = arguments; }").not.toEval(); + expect("class B { static #b = arguments; }").not.toEval(); + + class C { + #c = eval("arguments"); + } + + expect(() => { + new C(); + }).toThrowWithMessage(SyntaxError, "'arguments' is not allowed in class field initializer"); + + expect(() => { + class D { + static #d = eval("arguments"); + } + }).toThrowWithMessage(SyntaxError, "'arguments' is not allowed in class field initializer"); +}); + +test("using 'arguments' via indirect eval throws at runtime instead of parse time", () => { + const indirect = eval; + + class A { + #a = indirect("arguments"); + } + + expect(() => { + new A(); + }).toThrowWithMessage(ReferenceError, "'arguments' is not defined"); + + expect(() => { + class B { + static #b = indirect("arguments"); + } + }).toThrowWithMessage(ReferenceError, "'arguments' is not defined"); +}); diff --git a/Userland/Libraries/LibJS/Tests/classes/class-public-fields.js b/Userland/Libraries/LibJS/Tests/classes/class-public-fields.js index 4bc405e362d..da76f4cf802 100644 --- a/Userland/Libraries/LibJS/Tests/classes/class-public-fields.js +++ b/Userland/Libraries/LibJS/Tests/classes/class-public-fields.js @@ -86,6 +86,43 @@ test("with super class", () => { expect(b.super_field).toBe(4); }); +test("'arguments' is not allowed in class field initializer", () => { + expect("class A { a = arguments; }").not.toEval(); + expect("class B { static b = arguments; }").not.toEval(); + + class C { + c = eval("arguments"); + } + + expect(() => { + new C(); + }).toThrowWithMessage(SyntaxError, "'arguments' is not allowed in class field initializer"); + + expect(() => { + class D { + static d = eval("arguments"); + } + }).toThrowWithMessage(SyntaxError, "'arguments' is not allowed in class field initializer"); +}); + +test("using 'arguments' via indirect eval throws at runtime instead of parse time", () => { + const indirect = eval; + + class A { + a = indirect("arguments"); + } + + expect(() => { + new A(); + }).toThrowWithMessage(ReferenceError, "'arguments' is not defined"); + + expect(() => { + class B { + static b = indirect("arguments"); + } + }).toThrowWithMessage(ReferenceError, "'arguments' is not defined"); +}); + describe("class fields with a 'special' name", () => { test("static", () => { class A { diff --git a/Userland/Libraries/LibJS/Tests/functions/function-new-target.js b/Userland/Libraries/LibJS/Tests/functions/function-new-target.js index 9eb7659391f..bd3369ff210 100644 --- a/Userland/Libraries/LibJS/Tests/functions/function-new-target.js +++ b/Userland/Libraries/LibJS/Tests/functions/function-new-target.js @@ -20,6 +20,42 @@ test("basic functionality", () => { expect(new baz().newTarget).toEqual(baz); }); +test("retrieving new.target from direct eval", () => { + function foo() { + return eval("new.target"); + } + + let result; + + expect(() => { + result = foo(); + }).not.toThrowWithMessage(SyntaxError, "'new.target' not allowed outside of a function"); + + expect(result).toBe(undefined); + + expect(() => { + result = new foo(); + }).not.toThrowWithMessage(SyntaxError, "'new.target' not allowed outside of a function"); + + expect(result).toBe(foo); +}); + +test("cannot retrieve new.target from indirect eval", () => { + const indirect = eval; + + function foo() { + return indirect("new.target"); + } + + expect(() => { + foo(); + }).toThrowWithMessage(SyntaxError, "'new.target' not allowed outside of a function"); + + expect(() => { + new foo(); + }).toThrowWithMessage(SyntaxError, "'new.target' not allowed outside of a function"); +}); + test("syntax error outside of function", () => { expect("new.target").not.toEval(); }); diff --git a/Userland/Libraries/LibJS/Tests/return.js b/Userland/Libraries/LibJS/Tests/return.js index 2c5b8683f63..fdde273be4b 100644 --- a/Userland/Libraries/LibJS/Tests/return.js +++ b/Userland/Libraries/LibJS/Tests/return.js @@ -51,3 +51,31 @@ describe("returning from loops", () => { expect(foo()).toBe(10); }); }); + +test("cannot use return in eval", () => { + const indirect = eval; + + expect(() => { + eval("return 1;"); + }).toThrowWithMessage(SyntaxError, "'return' not allowed outside of a function"); + + expect(() => { + indirect("return 1;"); + }).toThrowWithMessage(SyntaxError, "'return' not allowed outside of a function"); + + function foo() { + eval("return 1;"); + } + + expect(() => { + foo(); + }).toThrowWithMessage(SyntaxError, "'return' not allowed outside of a function"); + + function bar() { + indirect("return 1;"); + } + + expect(() => { + bar(); + }).toThrowWithMessage(SyntaxError, "'return' not allowed outside of a function"); +});