mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
fix(rvm): assert every-quantifier results so failing cases don't pass (#765)
The RVM was silently succeeding on `every` quantifiers (and loops nested inside an `every` body) that should have failed. In each case the loop computed a pass/fail into a register that the surrounding query then ignored, so the RVM disagreed with the interpreter. Four related fixes: - compile_every_quantifier: guard the loop result so a failing `every` body makes the rule undefined instead of always succeeding. - resolve_iteration_state: `every` over a non-iterable scalar (number, string, bool, null, undefined) is now undefined, not vacuously true. Only genuinely empty collections stay true; any/forEach are untouched. - a `some ... in` inside an `every` body now guards its loop result, so a `some` that matches nothing fails the current iteration. Top-level rule bodies still rely on context yields and are unaffected. - a hoisted index iteration (`some i` / `arr[i]`) inside an `every` body gets the same guard. Also drop `every` from OPA_TODO_FOLDERS so the interpreter-vs-RVM differential suite covers it, add an OPA_UNSKIP_FOLDERS env override for auditing other still-skipped folders, and add regression cases for every variant above.
This commit is contained in:
committed by
GitHub
parent
9a486c79bf
commit
6ef5e74eb2
@@ -13,7 +13,7 @@ use crate::ast::{self, ExprRef, LiteralStmt, Query};
|
||||
use crate::compiler::destructuring_planner::plans::BindingPlan;
|
||||
use crate::compiler::hoist::{HoistedLoop, LoopType};
|
||||
use crate::lexer::Span;
|
||||
use crate::rvm::instructions::{LoopMode, LoopStartParams};
|
||||
use crate::rvm::instructions::{GuardMode, LoopMode, LoopStartParams};
|
||||
use crate::rvm::Instruction;
|
||||
use crate::Value;
|
||||
use alloc::format;
|
||||
@@ -197,6 +197,19 @@ impl<'a> Compiler<'a> {
|
||||
*end = loop_end;
|
||||
}
|
||||
|
||||
// The loop writes its overall pass/fail into `result_reg`
|
||||
// (`success_count == total_iterations` for `Every`). The enclosing query
|
||||
// must fail (evaluate to undefined) when the quantifier does not hold, so
|
||||
// guard on `result_reg` here. Without this the `every` result is computed
|
||||
// but discarded, leaving the surrounding rule to always succeed.
|
||||
self.emit_instruction(
|
||||
Instruction::Guard {
|
||||
register: result_reg,
|
||||
mode: GuardMode::Condition,
|
||||
},
|
||||
span,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -312,6 +325,25 @@ impl<'a> Compiler<'a> {
|
||||
*end = loop_end;
|
||||
}
|
||||
|
||||
// A hoisted index-iteration loop inside an `every` body acts as a
|
||||
// condition on the current iteration: if the indexed reference matches
|
||||
// nothing the iteration must fail. The `every` body emits no context
|
||||
// yield, so the loop result register is otherwise discarded (same
|
||||
// situation as `some ... in`). Guard on it so a non-matching indexed
|
||||
// reference fails the enclosing `every` iteration.
|
||||
if matches!(
|
||||
self.context_stack.last().map(|c| &c.context_type),
|
||||
Some(ContextType::Every)
|
||||
) {
|
||||
self.emit_instruction(
|
||||
Instruction::Guard {
|
||||
register: result_reg,
|
||||
mode: GuardMode::Condition,
|
||||
},
|
||||
collection.span(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -70,12 +70,31 @@ impl<'a> Compiler<'a> {
|
||||
..
|
||||
} = &stmt.literal
|
||||
{
|
||||
self.compile_some_in_loop_with_remaining_statements(
|
||||
let some_result_reg = self.compile_some_in_loop_with_remaining_statements(
|
||||
key,
|
||||
value,
|
||||
collection,
|
||||
&stmts[idx..],
|
||||
)?;
|
||||
// Inside an `every` body a `some ... in` acts as a condition
|
||||
// on the current iteration: if it matches nothing the
|
||||
// iteration must fail. Unlike a top-level rule body (where
|
||||
// per-iteration context yields produce the results), the
|
||||
// `every` body has no yield, so the loop result register is
|
||||
// otherwise discarded. Guard on it so a `some` that matches
|
||||
// nothing fails the enclosing `every` iteration.
|
||||
if matches!(
|
||||
self.context_stack.last().map(|c| &c.context_type),
|
||||
Some(ContextType::Every)
|
||||
) {
|
||||
self.emit_instruction(
|
||||
Instruction::Guard {
|
||||
register: some_result_reg,
|
||||
mode: GuardMode::Condition,
|
||||
},
|
||||
&stmt.span,
|
||||
);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user