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:
Andreas Kling 2026-05-21 17:03:08 +02:00 committed by Andreas Kling
parent c23b944340
commit 2f99653b33
12 changed files with 110 additions and 47 deletions

View file

@ -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:

View file

@ -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

View file

@ -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;

View file

@ -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,
});

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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() {