revert: reject marginal numeric VM slot reuse

This commit is contained in:
boris
2026-09-06 05:37:20 +08:00
parent 5122c73aa8
commit 52c7831bf6
2 changed files with 100 additions and 65 deletions
+26 -65
View File
@@ -168,40 +168,36 @@ impl Program {
Instruction::Push(value) => scratch.stack.push(value),
Instruction::LoadVariable(index) => {
let index = usize::from(index);
let value = if scratch.variable_generations[index] == scratch.generation {
scratch.variables[index]
} else {
let expected_type = self.variable_types[index];
let value = resolve(index, &self.variables[index], expected_type)?;
if value.value_type() != expected_type {
return Err(EvalError::new(format!(
"variable {} expected {:?}, got {:?}",
self.variables[index],
expected_type,
value.value_type()
)));
let cached = scratch.variables[index];
let value = match cached {
Some(value) => value,
None => {
let expected_type = self.variable_types[index];
let value = resolve(index, &self.variables[index], expected_type)?;
if value.value_type() != expected_type {
return Err(EvalError::new(format!(
"variable {} expected {:?}, got {:?}",
self.variables[index],
expected_type,
value.value_type()
)));
}
scratch.variables[index] = Some(value);
value
}
scratch.variables[index] = value;
scratch.variable_generations[index] = scratch.generation;
value
};
scratch.stack.push(value);
}
Instruction::LoadLocal(index) => {
let index = usize::from(index);
if scratch.local_generations[index] != scratch.generation {
return Err(EvalError::new(format!(
"local slot {index} was not initialized"
)));
}
let value = scratch.locals[index];
let value = scratch.locals[index].ok_or_else(|| {
EvalError::new(format!("local slot {index} was not initialized"))
})?;
scratch.stack.push(value);
}
Instruction::StoreLocal(index) => {
let index = usize::from(index);
let value = pop(&mut scratch.stack)?;
scratch.locals[index] = value;
scratch.local_generations[index] = scratch.generation;
scratch.locals[usize::from(index)] = Some(value);
}
Instruction::Unary(operator) => {
let value = pop(&mut scratch.stack)?;
@@ -264,11 +260,8 @@ impl Program {
#[derive(Debug, Default)]
pub(crate) struct Scratch {
stack: Vec<Value>,
variables: Vec<Value>,
variable_generations: Vec<u32>,
locals: Vec<Value>,
local_generations: Vec<u32>,
generation: u32,
variables: Vec<Option<Value>>,
locals: Vec<Option<Value>>,
}
impl Scratch {
@@ -280,21 +273,10 @@ impl Scratch {
.len()
.saturating_sub(self.stack.capacity()),
);
self.generation = self.generation.wrapping_add(1);
if self.generation == 0 {
self.variable_generations.fill(0);
self.local_generations.fill(0);
self.generation = 1;
}
if self.variables.len() < program.variables.len() {
self.variables
.resize(program.variables.len(), Value::Number(0.0));
self.variable_generations.resize(program.variables.len(), 0);
}
if self.locals.len() < program.local_count {
self.locals.resize(program.local_count, Value::Number(0.0));
self.local_generations.resize(program.local_count, 0);
}
self.variables.clear();
self.variables.resize(program.variables.len(), None);
self.locals.clear();
self.locals.resize(program.local_count, None);
}
}
@@ -1380,27 +1362,6 @@ mod tests {
}
}
#[test]
fn scratch_generation_rollover_invalidates_cached_slots() {
let program = compile("left + right", |_| Some(ValueType::Number)).expect("compile");
let mut scratch = Scratch::default();
let first = program
.evaluate(&mut scratch, |_index, name, _expected| {
Ok(Value::Number(if name == "left" { 1.0 } else { 2.0 }))
})
.expect("first evaluation");
assert_eq!(first, Value::Number(3.0));
scratch.generation = u32::MAX;
let second = program
.evaluate(&mut scratch, |_index, name, _expected| {
Ok(Value::Number(if name == "left" { 10.0 } else { 20.0 }))
})
.expect("evaluation after generation rollover");
assert_eq!(second, Value::Number(30.0));
assert_eq!(scratch.generation, 1);
}
#[test]
fn matches_rhai_for_representative_numeric_boolean_corpus() {
let source = r#"