LibJS: Treat class definition parts as strict

Record the generator's strictness for each emitted bytecode
instruction, instead of applying the executable's final strictness to
every instruction during assembly. This lets class heritage and
computed element names run with strict assignment semantics inside
sloppy scripts.

Also create the class self-binding as a strict immutable binding,
matching ClassDefinitionEvaluation, and cover strict class heritage and
computed names in the runtime tests.
This commit is contained in:
Andreas Kling 2026-05-21 14:26:23 +02:00 committed by Andreas Kling
parent 6d36517042
commit 4046f65ae7
24 changed files with 89 additions and 44 deletions

View file

@ -22,7 +22,7 @@ pub struct SourceMapEntry {
/// serialized into the final byte stream.
pub struct BasicBlock {
pub index: u32,
pub instructions: Vec<(Instruction, SourceMapEntry)>,
pub instructions: Vec<(Instruction, SourceMapEntry, bool)>,
pub handler: Option<Label>,
pub terminated: bool,
pub resolved_this: bool,
@ -39,9 +39,9 @@ impl BasicBlock {
}
}
pub fn append(&mut self, instruction: Instruction, source_map: SourceMapEntry) {
pub fn append(&mut self, instruction: Instruction, source_map: SourceMapEntry, strict: bool) {
let is_terminator = instruction.is_terminator();
self.instructions.push((instruction, source_map));
self.instructions.push((instruction, source_map, strict));
if is_terminator {
self.terminated = true;
}

View file

@ -73,6 +73,20 @@ pub fn generate_expression(
result
}
fn generate_class_part_expression(
expression: &Expression,
generator: &mut Generator,
preferred_dst: Option<&ScopedOperand>,
) -> Option<ScopedOperand> {
// https://tc39.es/ecma262/#sec-strict-mode-code
// All parts of a |ClassDeclaration| or a |ClassExpression| are strict mode code.
let saved_strict = generator.strict;
generator.strict = true;
let result = generate_expression(expression, generator, preferred_dst);
generator.strict = saved_strict;
result
}
fn emit_get_binding(
generator: &mut Generator,
dst: Operand,
@ -5750,7 +5764,9 @@ fn generate_class_expression(
});
generator.push_static_lexical_environment(class_env.clone());
// Step 3.a: Create binding for the class name in the class environment.
// https://tc39.es/ecma262/#sec-runtime-semantics-classdefinitionevaluation
// If _classBinding_ is not *undefined*, then
// Perform ! _classEnv_.CreateImmutableBinding(_classBinding_, *true*).
// Only emit when the class has a name, or when there's no lhs_name
// (skip this for anonymous classes with lhs_name).
if data.name.is_some() || lhs_name.is_none() {
@ -5765,13 +5781,13 @@ fn generate_class_expression(
mode: EnvironmentMode::Lexical as u32,
is_immutable: true,
is_global: false,
is_strict: false,
is_strict: true,
});
}
// Evaluate super class if present
let super_class = if let Some(super_expression) = &data.super_class {
generate_expression(super_expression, generator, None)
generate_class_part_expression(super_expression, generator, None)
} else {
None
};
@ -5807,7 +5823,7 @@ fn generate_class_expression(
match &element_node.inner {
ClassElement::Method { key, .. } => {
if !is_private_key(key) {
let key_val = generate_expression(key, generator, None);
let key_val = generate_class_part_expression(key, generator, None);
element_keys.push(key_val);
} else {
element_keys.push(None);
@ -5815,7 +5831,7 @@ fn generate_class_expression(
}
ClassElement::Field { key, .. } => {
if !is_private_key(key) {
let key_val = generate_expression(key, generator, None);
let key_val = generate_class_part_expression(key, generator, None);
element_keys.push(key_val);
} else {
element_keys.push(None);

View file

@ -847,7 +847,7 @@ impl Generator {
column: self.current_source_start.column,
};
let block = &mut self.basic_blocks[self.current_block_index.basic_block_index()];
block.append(instruction, source_map);
block.append(instruction, source_map, self.strict);
}
/// Emit a Mov instruction (optimized away if src == dst).
@ -882,7 +882,7 @@ impl Generator {
// instruction is a comparison whose dst matches condition, fuse into a JumpXxx.
if condition.operand().is_register() && std::rc::Rc::strong_count(&condition.inner) == 1 {
let block = &mut self.basic_blocks[self.current_block_index.basic_block_index()];
if let Some((last_instruction, _)) = block.instructions.last() {
if let Some((last_instruction, _, _)) = block.instructions.last() {
let fused = match last_instruction {
Instruction::LessThan { dst, lhs, rhs } if *dst == condition.operand() => {
Some(Instruction::JumpLessThan {
@ -1563,6 +1563,7 @@ impl Generator {
Instruction::GetLexicalEnvironment {
dst,
},
_,
_
)) if *dst == saved_environment
)
@ -1574,7 +1575,7 @@ impl Generator {
.instructions
.iter()
.enumerate()
.any(|(instruction_index, (instruction, _))| {
.any(|(instruction_index, (instruction, _, _))| {
if block_index == load_block_index && instruction_index == 0 {
return false;
}
@ -1612,7 +1613,7 @@ impl Generator {
// Phase 1: Operand rewriting
let mut max_argument_index: Option<u32> = None;
for block in &mut self.basic_blocks {
for (instruction, _) in &mut block.instructions {
for (instruction, _, _) in &mut block.instructions {
instruction.visit_operands(&mut |op: &mut Operand| {
match op.operand_type() {
OperandType::Register => {} // stays as-is
@ -1733,7 +1734,7 @@ impl Generator {
block_offsets.push(offset);
let block = &self.basic_blocks[block_index];
let mut block_actions = Vec::with_capacity(block.instructions.len());
for (instruction, _) in &block.instructions {
for (instruction, _, _) in &block.instructions {
match instruction {
Instruction::Jump { target } => {
let target_block = target.0 as usize;
@ -1824,7 +1825,7 @@ impl Generator {
// Phase 3: Patch labels (block index → byte offset)
for block in &mut self.basic_blocks {
for (instruction, _) in &mut block.instructions {
for (instruction, _, _) in &mut block.instructions {
instruction.visit_labels(&mut |label: &mut Label| {
let block_index = label.0 as usize;
label.0 = u32_from_usize(block_offsets[block_index]);
@ -1854,7 +1855,7 @@ impl Generator {
let handler = block.handler;
let block_actions = &actions[block_index];
for (instruction_index, (instruction, sm)) in block.instructions.iter().enumerate() {
for (instruction_index, (instruction, sm, strict)) in block.instructions.iter().enumerate() {
let action = block_actions[instruction_index];
match action {
InstAction::Skip => {
@ -1874,7 +1875,7 @@ impl Generator {
column: sm.column,
},
);
instruction.encode(self.strict, &mut bytecode);
instruction.encode(*strict, &mut bytecode);
}
InstAction::JumpToReturn(value) => {
let instruction_offset = bytecode.len();
@ -1887,7 +1888,7 @@ impl Generator {
},
);
let replacement = Instruction::Return { value };
replacement.encode(self.strict, &mut bytecode);
replacement.encode(*strict, &mut bytecode);
}
InstAction::JumpToEnd(value) => {
let instruction_offset = bytecode.len();
@ -1900,7 +1901,7 @@ impl Generator {
},
);
let replacement = Instruction::End { value };
replacement.encode(self.strict, &mut bytecode);
replacement.encode(*strict, &mut bytecode);
}
InstAction::EmitJumpFalse { condition, mut target } => {
// Patch label for the target
@ -1916,7 +1917,7 @@ impl Generator {
},
);
let replacement = Instruction::JumpFalse { condition, target };
replacement.encode(self.strict, &mut bytecode);
replacement.encode(*strict, &mut bytecode);
}
InstAction::EmitJumpTrue { condition, mut target } => {
let target_block = target.0 as usize;
@ -1931,7 +1932,7 @@ impl Generator {
},
);
let replacement = Instruction::JumpTrue { condition, target };
replacement.encode(self.strict, &mut bytecode);
replacement.encode(*strict, &mut bytecode);
}
}
}

View file

@ -17,7 +17,7 @@ f$0a149d9a class-computed-key.js:4:5
block0:
[ 0] GetLexicalEnvironment dst:reg4
[ 8] CreateLexicalEnvironment dst:reg5, parent:reg4, capacity:0, is_catch_environment:false
[ 20] CreateVariable ``, is_immutable:true, is_global:false, is_strict:false
[ 20] CreateVariable ``, is_immutable:true, is_global:false, is_strict:true
[ 30] GetGlobal dst:reg6, `Symbol`
[ 40] GetById dst:reg7, base:reg6, `iterator` (Symbol.iterator)
[ 58] SetLexicalEnvironment environment:reg4

View file

@ -24,7 +24,7 @@ classWithName$155d62f1 class-environment.js:2:13
block0:
[ 0] GetLexicalEnvironment dst:reg4
[ 8] CreateLexicalEnvironment dst:reg5, parent:reg4, capacity:0, is_catch_environment:false
[ 20] CreateVariable `Foo`, is_immutable:true, is_global:false, is_strict:false
[ 20] CreateVariable `Foo`, is_immutable:true, is_global:false, is_strict:true
[ 30] SetLexicalEnvironment environment:reg4
[ 38] NewClass dst:C~0, class_environment:reg5, class_blueprint_index:0, element_keys:[element_keys:String("method")]
[ 60] CallConstruct dst:reg6, callee:C~0, C

View file

@ -42,7 +42,7 @@ test$fa7458d4 class-literal-fields.js:2:5
block0:
[ 0] GetLexicalEnvironment dst:reg4
[ 8] CreateLexicalEnvironment dst:reg5, parent:reg4, capacity:0, is_catch_environment:false
[ 20] CreateVariable `A`, is_immutable:true, is_global:false, is_strict:false
[ 20] CreateVariable `A`, is_immutable:true, is_global:false, is_strict:true
[ 30] SetLexicalEnvironment environment:reg4
[ 38] NewClass dst:reg6, class_environment:reg5, class_blueprint_index:0, element_keys:[element_keys:String("x"), element_keys:String("y"), element_keys:String("z"), element_keys:String("w"), element_keys:String("s"), element_keys:String("computed")]
[ 70] Mov dst:A~0, src:reg6

View file

@ -9,7 +9,7 @@ $6a8031db delete-super-eval-order.js:4:1
block0:
[ 0] GetLexicalEnvironment dst:reg4
[ 8] CreateLexicalEnvironment dst:reg5, parent:reg4, capacity:0, is_catch_environment:false
[ 20] CreateVariable `A`, is_immutable:true, is_global:false, is_strict:false
[ 20] CreateVariable `A`, is_immutable:true, is_global:false, is_strict:true
[ 30] SetLexicalEnvironment environment:reg4
[ 38] NewClass dst:reg6, class_environment:reg5, class_blueprint_index:0, element_keys:[element_keys:String("m")]
[ 60] DynamicInitializeLexicalBinding `A`, src:reg6

View file

@ -10,7 +10,7 @@ $6fa5e397 delete-super-property.js:1:1
block0:
[ 0] GetLexicalEnvironment dst:reg4
[ 8] CreateLexicalEnvironment dst:reg5, parent:reg4, capacity:0, is_catch_environment:false
[ 20] CreateVariable `A`, is_immutable:true, is_global:false, is_strict:false
[ 20] CreateVariable `A`, is_immutable:true, is_global:false, is_strict:true
[ 30] SetLexicalEnvironment environment:reg4
[ 38] NewClass dst:reg6, class_environment:reg5, class_blueprint_index:0, element_keys:[element_keys:String("foo"), element_keys:String("baz")]
[ 60] DynamicInitializeLexicalBinding `A`, src:reg6

View file

@ -57,7 +57,7 @@ block6:
block7:
[ f0] CreateLexicalEnvironment dst:reg13, parent:reg4, capacity:0, is_catch_environment:false
[ 108] CreateVariable `C`, is_immutable:true, is_global:false, is_strict:false
[ 108] CreateVariable `C`, is_immutable:true, is_global:false, is_strict:true
[ 118] SetLexicalEnvironment environment:reg4
[ 120] NewClass dst:reg14, class_environment:reg13, class_blueprint_index:0
[ 140] Mov dst:reg12, src:reg14

View file

@ -39,7 +39,7 @@ block6:
block7:
[ d8] CreateLexicalEnvironment dst:reg13, parent:reg4, capacity:0, is_catch_environment:false
[ f0] CreateVariable `Foo`, is_immutable:true, is_global:false, is_strict:false
[ f0] CreateVariable `Foo`, is_immutable:true, is_global:false, is_strict:true
[ 100] SetLexicalEnvironment environment:reg4
[ 108] NewClass dst:reg14, class_environment:reg13, class_blueprint_index:0
[ 128] Mov dst:reg12, src:reg14

View file

@ -8,7 +8,7 @@ $745d24d5 numeric-class-field-name.js:1:1
block0:
[ 0] GetLexicalEnvironment dst:reg4
[ 8] CreateLexicalEnvironment dst:reg5, parent:reg4, capacity:0, is_catch_environment:false
[ 20] CreateVariable `C`, is_immutable:true, is_global:false, is_strict:false
[ 20] CreateVariable `C`, is_immutable:true, is_global:false, is_strict:true
[ 30] SetLexicalEnvironment environment:reg4
[ 38] NewClass dst:reg6, class_environment:reg5, class_blueprint_index:0, element_keys:[element_keys:Int32(42)]
[ 60] DynamicInitializeLexicalBinding `C`, src:reg6

View file

@ -8,7 +8,7 @@ $79e506f5 postfix-increment-private-member.js:1:1
block0:
[ 0] GetLexicalEnvironment dst:reg4
[ 8] CreateLexicalEnvironment dst:reg5, parent:reg4, capacity:0, is_catch_environment:false
[ 20] CreateVariable `C`, is_immutable:true, is_global:false, is_strict:false
[ 20] CreateVariable `C`, is_immutable:true, is_global:false, is_strict:true
[ 30] CreatePrivateEnvironment
[ 38] AddPrivateName `#c`
[ 40] SetLexicalEnvironment environment:reg4

View file

@ -8,7 +8,7 @@ $7f6ce915 private-identifier-access-patterns.js:1:1
block0:
[ 0] GetLexicalEnvironment dst:reg4
[ 8] CreateLexicalEnvironment dst:reg5, parent:reg4, capacity:0, is_catch_environment:false
[ 20] CreateVariable `C`, is_immutable:true, is_global:false, is_strict:false
[ 20] CreateVariable `C`, is_immutable:true, is_global:false, is_strict:true
[ 30] CreatePrivateEnvironment
[ 38] AddPrivateName `#x`
[ 40] SetLexicalEnvironment environment:reg4

View file

@ -8,7 +8,7 @@ $79e506f5 private-logical-assignment-register-order.js:1:1
block0:
[ 0] GetLexicalEnvironment dst:reg4
[ 8] CreateLexicalEnvironment dst:reg5, parent:reg4, capacity:0, is_catch_environment:false
[ 20] CreateVariable `C`, is_immutable:true, is_global:false, is_strict:false
[ 20] CreateVariable `C`, is_immutable:true, is_global:false, is_strict:true
[ 30] CreatePrivateEnvironment
[ 38] AddPrivateName `#x`
[ 40] SetLexicalEnvironment environment:reg4

View file

@ -8,7 +8,7 @@ $79e506f5 private-method-call-description.js:1:1
block0:
[ 0] GetLexicalEnvironment dst:reg4
[ 8] CreateLexicalEnvironment dst:reg5, parent:reg4, capacity:0, is_catch_environment:false
[ 20] CreateVariable `C`, is_immutable:true, is_global:false, is_strict:false
[ 20] CreateVariable `C`, is_immutable:true, is_global:false, is_strict:true
[ 30] CreatePrivateEnvironment
[ 38] AddPrivateName `#m`
[ 40] SetLexicalEnvironment environment:reg4

View file

@ -7,7 +7,7 @@ $091ab4cd super-call-spread-register-order.js:1:1
block0:
[ 0] GetLexicalEnvironment dst:reg4
[ 8] CreateLexicalEnvironment dst:reg5, parent:reg4, capacity:0, is_catch_environment:false
[ 20] CreateVariable `C`, is_immutable:true, is_global:false, is_strict:false
[ 20] CreateVariable `C`, is_immutable:true, is_global:false, is_strict:true
[ 30] GetGlobal dst:reg6, `Object`
[ 40] SetLexicalEnvironment environment:reg4
[ 48] NewClass dst:reg7, super_class:reg6, class_environment:reg5, class_blueprint_index:0

View file

@ -8,12 +8,12 @@ $a37cd2a2 super-computed-eval-order.js:1:1
block0:
[ 0] GetLexicalEnvironment dst:reg4
[ 8] CreateLexicalEnvironment dst:reg5, parent:reg4, capacity:0, is_catch_environment:false
[ 20] CreateVariable `Base`, is_immutable:true, is_global:false, is_strict:false
[ 20] CreateVariable `Base`, is_immutable:true, is_global:false, is_strict:true
[ 30] SetLexicalEnvironment environment:reg4
[ 38] NewClass dst:reg6, class_environment:reg5, class_blueprint_index:0
[ 58] DynamicInitializeLexicalBinding `Base`, src:reg6
[ 68] CreateLexicalEnvironment dst:reg6, parent:reg4, capacity:0, is_catch_environment:false
[ 80] CreateVariable `Derived`, is_immutable:true, is_global:false, is_strict:false
[ 80] CreateVariable `Derived`, is_immutable:true, is_global:false, is_strict:true
[ 90] GetGlobal dst:reg5, `Base`
[ a0] SetLexicalEnvironment environment:reg4
[ a8] NewClass dst:reg7, super_class:reg5, class_environment:reg6, class_blueprint_index:1, element_keys:[element_keys:String("test")]

View file

@ -7,7 +7,7 @@ $091ab4cd super-computed-string-to-id.js:1:1
block0:
[ 0] GetLexicalEnvironment dst:reg4
[ 8] CreateLexicalEnvironment dst:reg5, parent:reg4, capacity:0, is_catch_environment:false
[ 20] CreateVariable `C`, is_immutable:true, is_global:false, is_strict:false
[ 20] CreateVariable `C`, is_immutable:true, is_global:false, is_strict:true
[ 30] GetGlobal dst:reg6, `Object`
[ 40] SetLexicalEnvironment environment:reg4
[ 48] NewClass dst:reg7, super_class:reg6, class_environment:reg5, class_blueprint_index:0

View file

@ -8,12 +8,12 @@ $2a66ad4a super-constructor-call.js:1:1
block0:
[ 0] GetLexicalEnvironment dst:reg4
[ 8] CreateLexicalEnvironment dst:reg5, parent:reg4, capacity:0, is_catch_environment:false
[ 20] CreateVariable `Base`, is_immutable:true, is_global:false, is_strict:false
[ 20] CreateVariable `Base`, is_immutable:true, is_global:false, is_strict:true
[ 30] SetLexicalEnvironment environment:reg4
[ 38] NewClass dst:reg6, class_environment:reg5, class_blueprint_index:0
[ 58] DynamicInitializeLexicalBinding `Base`, src:reg6
[ 68] CreateLexicalEnvironment dst:reg6, parent:reg4, capacity:0, is_catch_environment:false
[ 80] CreateVariable `Derived`, is_immutable:true, is_global:false, is_strict:false
[ 80] CreateVariable `Derived`, is_immutable:true, is_global:false, is_strict:true
[ 90] GetGlobal dst:reg5, `Base`
[ a0] SetLexicalEnvironment environment:reg4
[ a8] NewClass dst:reg7, super_class:reg5, class_environment:reg6, class_blueprint_index:1

View file

@ -10,7 +10,7 @@ $460e17ea super-evaluation-order.js:1:1
block0:
[ 0] GetLexicalEnvironment dst:reg4
[ 8] CreateLexicalEnvironment dst:reg5, parent:reg4, capacity:0, is_catch_environment:false
[ 20] CreateVariable `A`, is_immutable:true, is_global:false, is_strict:false
[ 20] CreateVariable `A`, is_immutable:true, is_global:false, is_strict:true
[ 30] GetGlobal dst:reg6, `Object`
[ 40] SetLexicalEnvironment environment:reg4
[ 48] NewClass dst:reg7, super_class:reg6, class_environment:reg5, class_blueprint_index:0, element_keys:[element_keys:String("read"), element_keys:String("write"), element_keys:String("update")]

View file

@ -7,7 +7,7 @@ $00cee19d super-for-of-resolve-order.js:1:1
block0:
[ 0] GetLexicalEnvironment dst:reg4
[ 8] CreateLexicalEnvironment dst:reg5, parent:reg4, capacity:0, is_catch_environment:false
[ 20] CreateVariable `C`, is_immutable:true, is_global:false, is_strict:false
[ 20] CreateVariable `C`, is_immutable:true, is_global:false, is_strict:true
[ 30] SetLexicalEnvironment environment:reg4
[ 38] NewClass dst:reg6, class_environment:reg5, class_blueprint_index:0
[ 58] DynamicInitializeLexicalBinding `C`, src:reg6

View file

@ -9,12 +9,12 @@ $dc8f51ec super-length-access.js:3:1
block0:
[ 0] GetLexicalEnvironment dst:reg4
[ 8] CreateLexicalEnvironment dst:reg5, parent:reg4, capacity:0, is_catch_environment:false
[ 20] CreateVariable `A`, is_immutable:true, is_global:false, is_strict:false
[ 20] CreateVariable `A`, is_immutable:true, is_global:false, is_strict:true
[ 30] SetLexicalEnvironment environment:reg4
[ 38] NewClass dst:reg6, class_environment:reg5, class_blueprint_index:0, element_keys:[element_keys:String("length")]
[ 60] DynamicInitializeLexicalBinding `A`, src:reg6
[ 70] CreateLexicalEnvironment dst:reg6, parent:reg4, capacity:0, is_catch_environment:false
[ 88] CreateVariable `B`, is_immutable:true, is_global:false, is_strict:false
[ 88] CreateVariable `B`, is_immutable:true, is_global:false, is_strict:true
[ 98] GetGlobal dst:reg5, `A`
[ a8] SetLexicalEnvironment environment:reg4
[ b0] NewClass dst:reg7, super_class:reg5, class_environment:reg6, class_blueprint_index:1, element_keys:[element_keys:String("m")]

View file

@ -8,12 +8,12 @@ $1f56e90a super-optional-call-this.js:1:1
block0:
[ 0] GetLexicalEnvironment dst:reg4
[ 8] CreateLexicalEnvironment dst:reg5, parent:reg4, capacity:0, is_catch_environment:false
[ 20] CreateVariable `Base`, is_immutable:true, is_global:false, is_strict:false
[ 20] CreateVariable `Base`, is_immutable:true, is_global:false, is_strict:true
[ 30] SetLexicalEnvironment environment:reg4
[ 38] NewClass dst:reg6, class_environment:reg5, class_blueprint_index:0, element_keys:[element_keys:String("method")]
[ 60] DynamicInitializeLexicalBinding `Base`, src:reg6
[ 70] CreateLexicalEnvironment dst:reg6, parent:reg4, capacity:0, is_catch_environment:false
[ 88] CreateVariable `Foo`, is_immutable:true, is_global:false, is_strict:false
[ 88] CreateVariable `Foo`, is_immutable:true, is_global:false, is_strict:true
[ 98] GetGlobal dst:reg5, `Base`
[ a8] SetLexicalEnvironment environment:reg4
[ b0] NewClass dst:reg7, super_class:reg5, class_environment:reg6, class_blueprint_index:1, element_keys:[element_keys:String("method")]

View file

@ -19,3 +19,31 @@ test("class constructor prototype property descriptor", () => {
expect(constructorDescriptor.enumerable).toBeFalse();
expect(constructorDescriptor.configurable).toBeTrue();
});
test("class heritage and computed property names are strict mode code", () => {
expect(() => {
class A {
[(Object.preventExtensions({}).prop = 1)]() {}
}
}).toThrow(TypeError);
expect(() => {
const A = class {
[(Object.preventExtensions({}).prop = 1)]() {}
};
}).toThrow(TypeError);
expect(() => {
class A {
[(Object.preventExtensions({}).prop = 1)];
}
}).toThrow(TypeError);
expect(() => {
class A extends (Object.preventExtensions({}).prop = 1) {}
}).toThrow(TypeError);
expect(() => {
const A = class extends (Object.preventExtensions({}).prop = 1) {};
}).toThrow(TypeError);
});