Skip to content
Permalink
Browse files
Improve error message for uninitialized |this| in derived constructor
https://bugs.webkit.org/show_bug.cgi?id=220221

Reviewed by Yusuke Suzuki.

JSTests:

* stress/async-arrow-functions-lexical-binding-in-class.js:
* stress/async-arrow-functions-lexical-super-binding.js:
* stress/class-derived-from-null.js:
* stress/generator-eval-this.js:
* stress/super-property-access-tdz.js:

LayoutTests/imported/w3c:

* web-platform-tests/custom-elements/parser/parser-fallsback-to-unknown-element-expected.txt:

Source/JavaScriptCore:

Since class constructors perform `return this;` by default, and derived
constructors require `super()` to be called before |this| access, regular
TDZ error message is quite confusing, given the following code:

    `new (class extends Object { constructor() { } });`

Considering that currently op_check_tdz is called on thisRegister() only
in derived constructors, this patch modifies its slow path to throw a
helpful error message that covers |this| access and non-object returns.

V8 and SpiderMonkey have similar error messages, mentioning `super()`.

slow_path_throw_tdz_error is merged into slow_path_check_tdz, which is
invoked from baseline JIT, so we can reliably acquire the bytecode and
avoid code duplication.

* llint/LowLevelInterpreter32_64.asm:
* llint/LowLevelInterpreter64.asm:
* runtime/CommonSlowPaths.cpp:
(JSC::JSC_DEFINE_COMMON_SLOW_PATH):
* runtime/CommonSlowPaths.h:

LayoutTests:

* js/arrowfunction-supercall-expected.txt:
* js/arrowfunction-superproperty-expected.txt:
* js/class-syntax-extends-expected.txt:
* js/class-syntax-super-expected.txt:
* js/script-tests/arrowfunction-supercall.js:
* js/script-tests/arrowfunction-superproperty.js:
* js/script-tests/class-syntax-super.js:


Canonical link: https://commits.webkit.org/232717@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@271120 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
shvaikalesh committed Jan 2, 2021
1 parent fd08aaf commit 9a400c097f5c28d8b04901607a851db80a7b76e7
Showing 21 changed files with 116 additions and 53 deletions.
@@ -1,3 +1,16 @@
2021-01-02 Alexey Shvayka <shvaikalesh@gmail.com>

Improve error message for uninitialized |this| in derived constructor
https://bugs.webkit.org/show_bug.cgi?id=220221

Reviewed by Yusuke Suzuki.

* stress/async-arrow-functions-lexical-binding-in-class.js:
* stress/async-arrow-functions-lexical-super-binding.js:
* stress/class-derived-from-null.js:
* stress/generator-eval-this.js:
* stress/super-property-access-tdz.js:

2021-01-02 Alexey Shvayka <shvaikalesh@gmail.com>

Don't throw if `function.caller` is a non-strict / generator / async function
@@ -254,7 +254,7 @@ function checkTDZDuringCreate(klass) {
// We do not care about this error
}
drainMicrotasks();
const error = asyncError.error instanceof ReferenceError && asyncError.error.toString() === 'ReferenceError: Cannot access uninitialized variable.';
const error = asyncError.error.toString() === `ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object.`;
if (!error) throw new Error('TDZ error is expected, but appeared:' + asyncError.error);
}

@@ -123,7 +123,7 @@ drainMicrotasks();
shouldBe(childA4, undefined);
shouldBe(value, 'abc');
shouldBe(error, undefined);
shouldBe(catchError.toString(), 'ReferenceError: Cannot access uninitialized variable.');
shouldBe(catchError.toString(), `ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object.`);

catchError = undefined;
error = undefined;
@@ -41,7 +41,7 @@ function test1() {
return this;
}
}
assertThrow(()=>(new E), 'ReferenceError: Cannot access uninitialized variable.');
assertThrow(()=>(new E), `ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object.`);
assert(Reflect.getPrototypeOf(E.prototype) === null);
}
test(test1);
@@ -116,7 +116,7 @@ function test6() {
class E extends jsNull() { constructor() { let ret = this; return ret; } }
class F extends jsNull() { constructor() { return 25; } }
class G extends jsNull() { constructor() { super(); } }
assertThrow(() => Reflect.construct(E, [], D), 'ReferenceError: Cannot access uninitialized variable.');
assertThrow(() => Reflect.construct(E, [], D), `ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object.`);
assertThrow(() => Reflect.construct(F, [], D), 'TypeError: Cannot return a non-object type in the constructor of a derived class.');

let threw = false;
@@ -43,7 +43,7 @@ class A extends B {

shouldThrow(() => {
new A();
}, `ReferenceError: Cannot access uninitialized variable.`);
}, `ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object.`);

class C {
*generator()
@@ -15,7 +15,7 @@ function shouldThrowTDZ(f) {
f();
} catch(e) {
assert(e instanceof ReferenceError);
assert(e.toString() === "ReferenceError: Cannot access uninitialized variable.");
assert(e.toString() === `ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object.`);
threw = true;
}
assert(threw);
@@ -1,3 +1,18 @@
2021-01-02 Alexey Shvayka <shvaikalesh@gmail.com>

Improve error message for uninitialized |this| in derived constructor
https://bugs.webkit.org/show_bug.cgi?id=220221

Reviewed by Yusuke Suzuki.

* js/arrowfunction-supercall-expected.txt:
* js/arrowfunction-superproperty-expected.txt:
* js/class-syntax-extends-expected.txt:
* js/class-syntax-super-expected.txt:
* js/script-tests/arrowfunction-supercall.js:
* js/script-tests/arrowfunction-superproperty.js:
* js/script-tests/class-syntax-super.js:

2021-01-02 Alexey Shvayka <shvaikalesh@gmail.com>

Don't throw if `function.caller` is a non-strict / generator / async function
@@ -1,3 +1,12 @@
2021-01-02 Alexey Shvayka <shvaikalesh@gmail.com>

Improve error message for uninitialized |this| in derived constructor
https://bugs.webkit.org/show_bug.cgi?id=220221

Reviewed by Yusuke Suzuki.

* web-platform-tests/custom-elements/parser/parser-fallsback-to-unknown-element-expected.txt:

2020-12-30 Yusuke Suzuki <ysuzuki@apple.com>

[JSC] WebAssembly Table/Memory/Global should allow inheritance
@@ -1,6 +1,6 @@
CONSOLE MESSAGE: TypeError: The result of constructing a custom element must be a HTMLElement
CONSOLE MESSAGE: TypeError: The result of constructing a custom element must be a HTMLElement
CONSOLE MESSAGE: ReferenceError: Cannot access uninitialized variable.
CONSOLE MESSAGE: ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object.
CONSOLE MESSAGE: Bad

PASS HTML parser must create a fallback HTMLUnknownElement when a custom element constructor returns a Text node
@@ -22,8 +22,8 @@ PASS indexOfnestedArrowInStackError > -1 && errorStack.indexOf('nestedArrow', in
PASS indexOfarrowInChildConstructorInStackError > -1 && errorStack.indexOf('arrowInChildConstructor', indexOfarrowInChildConstructorInStackError + 1) === -1 is true
PASS indexOfChildClassInStackError > -1 && errorStack.indexOf('ChildClass', indexOfChildClassInStackError + 1) === -1 is true
PASS (new class extends A { constructor() { ((a = super())=>{})() } }).id is value
PASS (new class extends A { constructor() { ((a = this)=>{ return a; })() } }) threw exception ReferenceError: Cannot access uninitialized variable..
PASS (new class extends A { constructor() { ((a = this, b=super())=>{ return a; })() } }) threw exception ReferenceError: Cannot access uninitialized variable..
PASS (new class extends A { constructor() { ((a = this)=>{ return a; })() } }) threw exception ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object..
PASS (new class extends A { constructor() { ((a = this, b=super())=>{ return a; })() } }) threw exception ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object..
PASS (new class extends A { constructor() { ((a = new.target)=>{ return a; })(); super(); } }) did not throw exception.
PASS (new class extends A { constructor() { ((a = new.target, b=super())=>{ return a; })() } }) did not throw exception.
PASS successfullyParsed is true
@@ -5,7 +5,7 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE

PASS (new B()).getValueParentFunction() is expectedValue
PASS (new C(false)).value is expectedValue
PASS (new C(true)) threw exception ReferenceError: Cannot access uninitialized variable..
PASS (new C(true)) threw exception ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object..
PASS E.getParentStaticValue() is expectedValue
PASS f.prop is expectedValue + "-" + expectedValue
PASS f.prop is expectedValue + "-" + "new-value"
@@ -14,14 +14,14 @@ PASS (new F()).genGetParentValue().next().value is expectedValue
PASS (new F()).genGetParentValueDeepArrow().next().value is expectedValue
PASS (new class extends A { constructor() { ((a = super(), b = super.getValue())=>{ this.id = b; })() } }).id is expectedValue
PASS (new class extends A { constructor() { ((a = super(), b = new.target)=>{ this.newTarget = b; })(); expectedNewTarget = new.target;} }).newTarget is expectedNewTarget
PASS (new class extends A { constructor() { ((a = super.getValue())=>{ this.id = a; })() } }) threw exception ReferenceError: Cannot access uninitialized variable..
PASS (new class extends A { constructor() { ((a = super.getValue(), b=super())=>{ this.id = a; })() } }) threw exception ReferenceError: Cannot access uninitialized variable..
PASS (new class extends F { constructor() { ((a = super.prop)=>{ return a; })() } }) threw exception ReferenceError: Cannot access uninitialized variable..
PASS (new class extends F { constructor() { ((a = super.prop, b=super())=>{ return a; })() } }) threw exception ReferenceError: Cannot access uninitialized variable..
PASS (new class extends F { constructor() { ((a = (super.prop = "value"))=>{ this.id = a; })() } }) threw exception ReferenceError: Cannot access uninitialized variable..
PASS (new class extends F { constructor() { ((a = (super.prop = "value"), b=super())=>{ this.id = a; })() } }) threw exception ReferenceError: Cannot access uninitialized variable..
PASS (new class extends F { constructor() { ((a = super.genGetParentValue().next().value)=>{ this.id = a; })() } }) threw exception ReferenceError: Cannot access uninitialized variable..
PASS (new class extends F { constructor() { ((a = super.genGetParentValue().next().value, b=super())=>{ this.id = a; })() } }) threw exception ReferenceError: Cannot access uninitialized variable..
PASS (new class extends A { constructor() { ((a = super.getValue())=>{ this.id = a; })() } }) threw exception ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object..
PASS (new class extends A { constructor() { ((a = super.getValue(), b=super())=>{ this.id = a; })() } }) threw exception ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object..
PASS (new class extends F { constructor() { ((a = super.prop)=>{ return a; })() } }) threw exception ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object..
PASS (new class extends F { constructor() { ((a = super.prop, b=super())=>{ return a; })() } }) threw exception ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object..
PASS (new class extends F { constructor() { ((a = (super.prop = "value"))=>{ this.id = a; })() } }) threw exception ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object..
PASS (new class extends F { constructor() { ((a = (super.prop = "value"), b=super())=>{ this.id = a; })() } }) threw exception ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object..
PASS (new class extends F { constructor() { ((a = super.genGetParentValue().next().value)=>{ this.id = a; })() } }) threw exception ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object..
PASS (new class extends F { constructor() { ((a = super.genGetParentValue().next().value, b=super())=>{ this.id = a; })() } }) threw exception ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object..
PASS successfullyParsed is true

TEST COMPLETE
@@ -57,7 +57,7 @@ PASS Object.getPrototypeOf((class extends null { constructor () { super(); } }).
PASS new (class extends undefined { constructor () { this } }):::TypeError: The superclass is not a constructor.
PASS x = undefined; new (class extends x { constructor () { super(); } }):::TypeError: The superclass is not a constructor.
PASS class x {}; new (class extends null { constructor () { return new x; } }) instanceof x
PASS new (class extends null { constructor () { this; } }):::ReferenceError: Cannot access uninitialized variable.
PASS new (class extends null { constructor () { this; } }):::ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object.
PASS new (class extends null { constructor () { super(); } }):::TypeError: function is not a constructor (evaluating 'super()')
PASS x = {}; new (class extends null { constructor () { return x } }):::x
PASS y = 12; class C extends null { constructor () { return y; } }; new C;:::TypeError: Cannot return a non-object type in the constructor of a derived class.
@@ -6,7 +6,7 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE
PASS (new Base) instanceof Base
PASS (new Derived) instanceof Derived
PASS (new DerivedWithEval) instanceof DerivedWithEval
PASS (new DerivedWithEval(true)):::ReferenceError: Cannot access uninitialized variable.
PASS (new DerivedWithEval(true)):::ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object.
PASS (new Derived).callBaseMethod():::baseMethodValue
PASS x = (new Derived).callBaseMethod; x():::baseMethodValue
PASS (new Derived).callBaseMethodInGetter:::baseMethodValue
@@ -30,17 +30,17 @@ PASS (new (class { constructor() { eval('super.property = "ABC"'); } })).propert
PASS (new (class { constructor() { var arr = () => eval('super.property = "ABC"'); arr(); } })).property === "ABC"
PASS new (class { constructor() { return undefined; } }) instanceof Object
PASS new (class { constructor() { return 1; } }) instanceof Object
PASS new (class extends Base { constructor() { return undefined } }):::ReferenceError: Cannot access uninitialized variable.
PASS new (class extends Base { constructor() { return undefined } }):::ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object.
PASS new (class extends Base { constructor() { super(); return undefined } }) instanceof Object
PASS x = { }; new (class extends Base { constructor() { return x } });:::x
PASS x instanceof Base
PASS new (class extends Base { constructor() { } }):::ReferenceError: Cannot access uninitialized variable.
PASS new (class extends Base { constructor() { } }):::ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object.
PASS new (class extends Base { constructor() { return 1; } }):::TypeError: Cannot return a non-object type in the constructor of a derived class.
PASS new (class extends null { constructor() { return undefined } }):::ReferenceError: Cannot access uninitialized variable.
PASS new (class extends null { constructor() { return undefined } }):::ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object.
PASS new (class extends null { constructor() { super(); return undefined } }):::TypeError: function is not a constructor (evaluating 'super()')
PASS x = { }; new (class extends null { constructor() { return x } });:::x
PASS x instanceof Object
PASS new (class extends null { constructor() { } }):::ReferenceError: Cannot access uninitialized variable.
PASS new (class extends null { constructor() { } }):::ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object.
PASS new (class extends null { constructor() { return 1; } }):::TypeError: Cannot return a non-object type in the constructor of a derived class.
PASS new (class extends null { constructor() { super() } }):::TypeError: function is not a constructor (evaluating 'super()')
PASS new (class { constructor() { super() } }):::SyntaxError: super is not valid in this context.
@@ -56,9 +56,9 @@ PASS (function () { eval("super()");})():::SyntaxError: super is not valid in th
PASS new (class { constructor() { (function () { eval("super()");})(); } }):::SyntaxError: super is not valid in this context.
PASS (new (class { method() { (function () { eval("super.method()");})(); }})).method():::SyntaxError: super is not valid in this context.
PASS new (class extends Base { constructor() { super(); super();}}):::ReferenceError: 'super()' can't be called more than once in a constructor.
PASS (new class D extends class { m() {}} { constructor() { eval('super["m"]()') } }):::ReferenceError: Cannot access uninitialized variable.
PASS new class extends class { m() {}} { constructor() { super["m"](super()) } }:::ReferenceError: Cannot access uninitialized variable.
PASS (new class D extends class { m() {}} { constructor(f) { super[f()]() } }(()=>"m")):::ReferenceError: Cannot access uninitialized variable.
PASS (new class D extends class { m() {}} { constructor() { eval('super["m"]()') } }):::ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object.
PASS new class extends class { m() {}} { constructor() { super["m"](super()) } }:::ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object.
PASS (new class D extends class { m() {}} { constructor(f) { super[f()]() } }(()=>"m")):::ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object.
PASS (new class D extends class { m() {}} { constructor() { super(); eval('super["m"]()') } })
PASS new class extends class { m() {}} { constructor() { super(); super["m"](super()) } }:::ReferenceError: 'super()' can't be called more than once in a constructor.
PASS (new class D extends class { m() {}} { constructor(f) { super(); super[f()]() } }(()=>"m"))
@@ -149,8 +149,8 @@ shouldBeTrue("indexOfarrowInChildConstructorInStackError > -1 && errorStack.inde
shouldBeTrue("indexOfChildClassInStackError > -1 && errorStack.indexOf('ChildClass', indexOfChildClassInStackError + 1) === -1");

shouldBe("(new class extends A { constructor() { ((a = super())=>{})() } }).id", "value");
shouldThrow('(new class extends A { constructor() { ((a = this)=>{ return a; })() } })', '"ReferenceError: Cannot access uninitialized variable."');
shouldThrow('(new class extends A { constructor() { ((a = this, b=super())=>{ return a; })() } })', '"ReferenceError: Cannot access uninitialized variable."');
shouldThrow('(new class extends A { constructor() { ((a = this)=>{ return a; })() } })', `"ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object."`);
shouldThrow('(new class extends A { constructor() { ((a = this, b=super())=>{ return a; })() } })', `"ReferenceError: 'super()' must be called in derived constructor before accessing |this| or returning non-object."`);
shouldNotThrow('(new class extends A { constructor() { ((a = new.target)=>{ return a; })(); super(); } })');
shouldNotThrow('(new class extends A { constructor() { ((a = new.target, b=super())=>{ return a; })() } })');

0 comments on commit 9a400c0

Please sign in to comment.