LibJS: Resolve super constructor before arguments
Match ECMA-262's SuperCall evaluation order by resolving the super constructor before evaluating the argument list. This preserves the constructor across argument-side prototype mutations and still lets abrupt argument evaluation happen before the constructor check. Add LibJS coverage and update bytecode baselines for the new saved super-constructor operand. The test262 superCallOrder staging test now passes.
This commit is contained in:
parent
c23b944340
commit
2f99653b33
12 changed files with 110 additions and 47 deletions
|
|
@ -359,6 +359,8 @@ i64 asm_fallback_handler(VM* vm, u32 pc)
|
|||
return execute_nonthrowing<Op::GetImportMeta>(*vm, pc);
|
||||
case Instruction::Type::GetNewTarget:
|
||||
return execute_nonthrowing<Op::GetNewTarget>(*vm, pc);
|
||||
case Instruction::Type::GetSuperConstructor:
|
||||
return execute_nonthrowing<Op::GetSuperConstructor>(*vm, pc);
|
||||
case Instruction::Type::GetTemplateObject:
|
||||
return execute_nonthrowing<Op::GetTemplateObject>(*vm, pc);
|
||||
case Instruction::Type::IsCallable:
|
||||
|
|
|
|||
|
|
@ -535,6 +535,11 @@ op GetNewTarget < Instruction
|
|||
m_dst: Operand
|
||||
endop
|
||||
|
||||
op GetSuperConstructor < Instruction
|
||||
@nothrow
|
||||
m_dst: Operand
|
||||
endop
|
||||
|
||||
op GetObjectPropertyIterator < Instruction
|
||||
m_dst_iterator: Operand
|
||||
m_object: Operand
|
||||
|
|
@ -1096,6 +1101,7 @@ endop
|
|||
|
||||
op SuperCallWithArgumentArray < Instruction
|
||||
m_dst: Operand
|
||||
m_super_constructor: Operand
|
||||
m_arguments: Operand
|
||||
m_is_synthetic: bool
|
||||
endop
|
||||
|
|
|
|||
|
|
@ -704,6 +704,7 @@ void VM::run_bytecode(size_t entry_point)
|
|||
HANDLE_INSTRUCTION(GetLengthWithThis);
|
||||
HANDLE_INSTRUCTION(GetMethod);
|
||||
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(GetNewTarget);
|
||||
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(GetSuperConstructor);
|
||||
HANDLE_INSTRUCTION(GetObjectPropertyIterator);
|
||||
HANDLE_INSTRUCTION(GetPrivateById);
|
||||
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(GetTemplateObject);
|
||||
|
|
@ -2893,6 +2894,13 @@ void GetNewTarget::execute_impl(VM& vm) const
|
|||
vm.set(dst(), vm.get_new_target());
|
||||
}
|
||||
|
||||
// 13.3.7.2 GetSuperConstructor ( ), https://tc39.es/ecma262/#sec-getsuperconstructor
|
||||
void GetSuperConstructor::execute_impl(VM& vm) const
|
||||
{
|
||||
auto* super_constructor = get_super_constructor(vm);
|
||||
vm.set(dst(), super_constructor ? Value(super_constructor) : js_null());
|
||||
}
|
||||
|
||||
void GetImportMeta::execute_impl(VM& vm) const
|
||||
{
|
||||
vm.set(dst(), vm.get_import_meta());
|
||||
|
|
@ -3149,17 +3157,18 @@ ThrowCompletionOr<void> SuperCallWithArgumentArray::execute_impl(VM& vm) const
|
|||
// 2. Assert: Type(newTarget) is Object.
|
||||
VERIFY(new_target.is_object());
|
||||
|
||||
// 3. Let func be GetSuperConstructor().
|
||||
auto* func = get_super_constructor(vm);
|
||||
// 3. Let _superConstructor_ be GetSuperConstructor().
|
||||
auto super_constructor = vm.get(m_super_constructor);
|
||||
|
||||
// NON-STANDARD: We're doing this step earlier to streamline control flow.
|
||||
// 5. If IsConstructor(func) is false, throw a TypeError exception.
|
||||
if (!Value(func).is_constructor()) [[unlikely]]
|
||||
// 4. Let _argList_ be ? ArgumentListEvaluation of |Arguments|.
|
||||
// NOTE: The bytecode generator performs this step before emitting this instruction.
|
||||
|
||||
// 5. If IsConstructor(_superConstructor_) is *false*, throw a *TypeError* exception.
|
||||
if (!super_constructor.is_constructor()) [[unlikely]]
|
||||
return vm.throw_completion<TypeError>(ErrorType::NotAConstructor, "Super constructor");
|
||||
|
||||
auto& function = static_cast<FunctionObject&>(*func);
|
||||
auto& function = super_constructor.as_function();
|
||||
|
||||
// 4. Let argList be ? ArgumentListEvaluation of Arguments.
|
||||
auto& argument_array = vm.get(m_arguments).as_array_exotic_object();
|
||||
size_t argument_array_length = 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -412,6 +412,14 @@ fn generate_expression_inner(
|
|||
|
||||
// === SuperCall ===
|
||||
ExpressionKind::SuperCall(data) => {
|
||||
// https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation
|
||||
// 3. Let _superConstructor_ be GetSuperConstructor().
|
||||
// 4. Let _argList_ be ? ArgumentListEvaluation of |Arguments|.
|
||||
let super_constructor = generator.allocate_register();
|
||||
generator.emit(Instruction::GetSuperConstructor {
|
||||
dst: super_constructor.operand(),
|
||||
});
|
||||
|
||||
let arguments = if data.is_synthetic {
|
||||
// Synthetic constructor: super(...arguments) — single spread argument,
|
||||
// don't call @@iterator on %Array.prototype%.
|
||||
|
|
@ -424,6 +432,7 @@ fn generate_expression_inner(
|
|||
let dst = choose_dst(generator, preferred_dst);
|
||||
generator.emit(Instruction::SuperCallWithArgumentArray {
|
||||
dst: dst.operand(),
|
||||
super_constructor: super_constructor.operand(),
|
||||
arguments: arguments.operand(),
|
||||
is_synthetic: data.is_synthetic,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,15 +17,16 @@ block0:
|
|||
[ a0] End value:reg7
|
||||
|
||||
|
||||
C$c11ea3e3 super-call-spread-register-order.js:3:14
|
||||
Registers: 7
|
||||
C$be5ab2d3 super-call-spread-register-order.js:3:14
|
||||
Registers: 8
|
||||
Blocks: 1
|
||||
Constants:
|
||||
[0] = Undefined
|
||||
|
||||
block0:
|
||||
[ 0] CreateRestParams dst:arg0, rest_index:0
|
||||
[ 10] NewArray dst:reg5
|
||||
[ 20] ArrayAppend dst:reg5, src:arg0, is_spread:true
|
||||
[ 30] SuperCallWithArgumentArray dst:reg6, arguments:reg5, is_synthetic:false
|
||||
[ 40] End value:Undefined
|
||||
[ 10] GetSuperConstructor dst:reg5
|
||||
[ 18] NewArray dst:reg6
|
||||
[ 28] ArrayAppend dst:reg6, src:arg0, is_spread:true
|
||||
[ 38] SuperCallWithArgumentArray dst:reg7, super_constructor:reg5, arguments:reg6, is_synthetic:false
|
||||
[ 50] End value:Undefined
|
||||
|
|
|
|||
|
|
@ -25,16 +25,17 @@ block0:
|
|||
[ 140] End value:reg7
|
||||
|
||||
|
||||
Derived$730ebf72 super-computed-eval-order.js:4:14
|
||||
Registers: 7
|
||||
Derived$704ace62 super-computed-eval-order.js:4:14
|
||||
Registers: 8
|
||||
Blocks: 1
|
||||
Constants:
|
||||
[0] = Undefined
|
||||
|
||||
block0:
|
||||
[ 0] NewArray dst:reg5
|
||||
[ 10] SuperCallWithArgumentArray dst:reg6, arguments:reg5, is_synthetic:false
|
||||
[ 20] End value:Undefined
|
||||
[ 0] GetSuperConstructor dst:reg5
|
||||
[ 8] NewArray dst:reg6
|
||||
[ 18] SuperCallWithArgumentArray dst:reg7, super_constructor:reg5, arguments:reg6, is_synthetic:false
|
||||
[ 30] End value:Undefined
|
||||
|
||||
|
||||
Base$a461b108
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ block0:
|
|||
[ a0] End value:reg7
|
||||
|
||||
|
||||
C$be5ab2d3 super-computed-string-to-id.js:3:14
|
||||
Registers: 7
|
||||
C$bb96c1c3 super-computed-string-to-id.js:3:14
|
||||
Registers: 8
|
||||
Blocks: 1
|
||||
Constants:
|
||||
[0] = String("foo")
|
||||
|
|
@ -26,9 +26,10 @@ C$be5ab2d3 super-computed-string-to-id.js:3:14
|
|||
[2] = Undefined
|
||||
|
||||
block0:
|
||||
[ 0] NewArray dst:reg5
|
||||
[ 10] SuperCallWithArgumentArray dst:reg6, arguments:reg5, is_synthetic:false
|
||||
[ 20] ResolveThisBinding
|
||||
[ 28] ResolveSuperBase dst:reg6
|
||||
[ 30] PutByIdWithThis base:reg6, this_value:this, `foo`, src:Int32(1), kind:Normal
|
||||
[ 50] End value:Undefined
|
||||
[ 0] GetSuperConstructor dst:reg5
|
||||
[ 8] NewArray dst:reg6
|
||||
[ 18] SuperCallWithArgumentArray dst:reg7, super_constructor:reg5, arguments:reg6, is_synthetic:false
|
||||
[ 30] ResolveThisBinding
|
||||
[ 38] ResolveSuperBase dst:reg7
|
||||
[ 40] PutByIdWithThis base:reg7, this_value:this, `foo`, src:Int32(1), kind:Normal
|
||||
[ 60] End value:Undefined
|
||||
|
|
|
|||
|
|
@ -23,17 +23,18 @@ block0:
|
|||
[ 108] End value:reg7
|
||||
|
||||
|
||||
Derived$eee8d5da super-constructor-call.js:4:14
|
||||
Registers: 7
|
||||
Derived$ec24e4ca super-constructor-call.js:4:14
|
||||
Registers: 8
|
||||
Blocks: 1
|
||||
Constants:
|
||||
[0] = Undefined
|
||||
|
||||
block0:
|
||||
[ 0] Mov dst:reg6, src:arg0
|
||||
[ 10] NewArray dst:reg5, elements:[reg6]
|
||||
[ 28] SuperCallWithArgumentArray dst:reg6, arguments:reg5, is_synthetic:false
|
||||
[ 38] End value:Undefined
|
||||
[ 0] GetSuperConstructor dst:reg5
|
||||
[ 8] Mov dst:reg7, src:arg0
|
||||
[ 18] NewArray dst:reg6, elements:[reg7]
|
||||
[ 30] SuperCallWithArgumentArray dst:reg7, super_constructor:reg5, arguments:reg6, is_synthetic:false
|
||||
[ 48] End value:Undefined
|
||||
|
||||
|
||||
Base$a461b108
|
||||
|
|
|
|||
|
|
@ -30,17 +30,18 @@ block0:
|
|||
[ 1a8] End value:reg7
|
||||
|
||||
|
||||
A$3d02c59c super-evaluation-order.js:1:1
|
||||
Registers: 7
|
||||
A$3a3ed48c super-evaluation-order.js:1:1
|
||||
Registers: 8
|
||||
Blocks: 1
|
||||
|
||||
block0:
|
||||
[ 0] CreateVariable `args`, is_immutable:false, is_global:false, is_strict:false
|
||||
[ 10] CreateRestParams dst:arg0, rest_index:0
|
||||
[ 20] InitializeLexicalBinding `args`, src:arg0
|
||||
[ 38] GetBinding dst:reg5, `args`
|
||||
[ 50] SuperCallWithArgumentArray dst:reg6, arguments:reg5, is_synthetic:true
|
||||
[ 60] Return value:reg6
|
||||
[ 38] GetSuperConstructor dst:reg5
|
||||
[ 40] GetBinding dst:reg6, `args`
|
||||
[ 58] SuperCallWithArgumentArray dst:reg7, super_constructor:reg5, arguments:reg6, is_synthetic:true
|
||||
[ 70] Return value:reg7
|
||||
|
||||
|
||||
read$5dc3ca35 super-evaluation-order.js:3:9
|
||||
|
|
|
|||
|
|
@ -26,17 +26,18 @@ block0:
|
|||
[ 148] End value:reg7
|
||||
|
||||
|
||||
B$cbd8a311 super-length-access.js:7:1
|
||||
Registers: 7
|
||||
B$ce9c9421 super-length-access.js:7:1
|
||||
Registers: 8
|
||||
Blocks: 1
|
||||
|
||||
block0:
|
||||
[ 0] CreateVariable `args`, is_immutable:false, is_global:false, is_strict:false
|
||||
[ 10] CreateRestParams dst:arg0, rest_index:0
|
||||
[ 20] InitializeLexicalBinding `args`, src:arg0
|
||||
[ 38] GetBinding dst:reg5, `args`
|
||||
[ 50] SuperCallWithArgumentArray dst:reg6, arguments:reg5, is_synthetic:true
|
||||
[ 60] Return value:reg6
|
||||
[ 38] GetSuperConstructor dst:reg5
|
||||
[ 40] GetBinding dst:reg6, `args`
|
||||
[ 58] SuperCallWithArgumentArray dst:reg7, super_constructor:reg5, arguments:reg6, is_synthetic:true
|
||||
[ 70] Return value:reg7
|
||||
|
||||
|
||||
A$ef0589ec
|
||||
|
|
|
|||
|
|
@ -25,17 +25,18 @@ block0:
|
|||
[ 148] End value:reg7
|
||||
|
||||
|
||||
Foo$f8ebe2ee super-optional-call-this.js:4:1
|
||||
Registers: 7
|
||||
Foo$fbafd3fe super-optional-call-this.js:4:1
|
||||
Registers: 8
|
||||
Blocks: 1
|
||||
|
||||
block0:
|
||||
[ 0] CreateVariable `args`, is_immutable:false, is_global:false, is_strict:false
|
||||
[ 10] CreateRestParams dst:arg0, rest_index:0
|
||||
[ 20] InitializeLexicalBinding `args`, src:arg0
|
||||
[ 38] GetBinding dst:reg5, `args`
|
||||
[ 50] SuperCallWithArgumentArray dst:reg6, arguments:reg5, is_synthetic:true
|
||||
[ 60] Return value:reg6
|
||||
[ 38] GetSuperConstructor dst:reg5
|
||||
[ 40] GetBinding dst:reg6, `args`
|
||||
[ 58] SuperCallWithArgumentArray dst:reg7, super_constructor:reg5, arguments:reg6, is_synthetic:true
|
||||
[ 70] Return value:reg7
|
||||
|
||||
|
||||
Base$a461b108
|
||||
|
|
|
|||
|
|
@ -132,6 +132,36 @@ test("super constructor call from child class with argument", () => {
|
|||
expect(c.x).toBe(10);
|
||||
});
|
||||
|
||||
test("super constructor is resolved before argument evaluation", () => {
|
||||
function Base() {}
|
||||
|
||||
class SwizzlesConstructorPrototype extends Base {
|
||||
constructor() {
|
||||
super(Object.setPrototypeOf(SwizzlesConstructorPrototype, null));
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
new SwizzlesConstructorPrototype();
|
||||
}).not.toThrow();
|
||||
|
||||
class ThrowsWhileSuperConstructorIsInvalid extends Base {
|
||||
constructor() {
|
||||
function thrower() {
|
||||
throw new RangeError("argument evaluation");
|
||||
}
|
||||
|
||||
super(thrower());
|
||||
}
|
||||
}
|
||||
|
||||
Object.setPrototypeOf(ThrowsWhileSuperConstructorIsInvalid, Math.sin);
|
||||
|
||||
expect(() => {
|
||||
new ThrowsWhileSuperConstructorIsInvalid();
|
||||
}).toThrowWithMessage(RangeError, "argument evaluation");
|
||||
});
|
||||
|
||||
test("advanced 'extends' RHS", () => {
|
||||
const foo = {
|
||||
bar() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue