mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
- emit AssertCondition for equality-only assignment plans (outside soft-assert mode) so rules like `0 = 1` fail under the VM just like the interpreter - let comprehension bodies consume assertion failures by advancing or exiting their iteration context, both in run-to-completion and suspendable execution Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
386 lines
14 KiB
Rust
386 lines
14 KiB
Rust
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
use super::Compiler;
|
|
use super::Register;
|
|
use crate::compiler::destructuring_planner::plans::{
|
|
AssignmentPlan, BindingPlan, DestructuringPlan, WildcardSide,
|
|
};
|
|
use crate::lexer::Span;
|
|
use crate::rvm::instructions::Instruction;
|
|
use crate::value::Value;
|
|
use anyhow::{bail, Result};
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub enum PlanContext {
|
|
ColonAssignment,
|
|
Assignment,
|
|
FunctionParameter,
|
|
LoopIndex,
|
|
SomeIn,
|
|
}
|
|
|
|
impl PlanContext {
|
|
fn require_defined_values(self) -> bool {
|
|
matches!(
|
|
self,
|
|
PlanContext::Assignment
|
|
| PlanContext::FunctionParameter
|
|
| PlanContext::LoopIndex
|
|
| PlanContext::SomeIn
|
|
)
|
|
}
|
|
}
|
|
|
|
impl<'a> Compiler<'a> {
|
|
pub fn compile_assignment_plan_using_hoisted_destructuring(
|
|
&mut self,
|
|
plan: &AssignmentPlan,
|
|
span: &Span,
|
|
) -> Result<Register> {
|
|
match plan {
|
|
AssignmentPlan::ColonEquals {
|
|
rhs_expr, lhs_plan, ..
|
|
} => {
|
|
let rhs_reg = self.compile_rego_expr_with_span(rhs_expr, rhs_expr.span(), false)?;
|
|
let _ = self.apply_destructuring_plan(
|
|
lhs_plan,
|
|
rhs_reg,
|
|
span,
|
|
PlanContext::ColonAssignment,
|
|
)?;
|
|
Ok(rhs_reg)
|
|
}
|
|
AssignmentPlan::EqualsBindLeft {
|
|
rhs_expr, lhs_plan, ..
|
|
} => {
|
|
let rhs_reg = self.compile_rego_expr_with_span(rhs_expr, rhs_expr.span(), false)?;
|
|
let _ = self.apply_destructuring_plan(
|
|
lhs_plan,
|
|
rhs_reg,
|
|
span,
|
|
PlanContext::Assignment,
|
|
)?;
|
|
Ok(self.load_bool_literal(true, span))
|
|
}
|
|
AssignmentPlan::EqualsBindRight {
|
|
lhs_expr, rhs_plan, ..
|
|
} => {
|
|
let lhs_reg = self.compile_rego_expr_with_span(lhs_expr, lhs_expr.span(), false)?;
|
|
let _ = self.apply_destructuring_plan(
|
|
rhs_plan,
|
|
lhs_reg,
|
|
span,
|
|
PlanContext::Assignment,
|
|
)?;
|
|
Ok(self.load_bool_literal(true, span))
|
|
}
|
|
AssignmentPlan::EqualsBothSides { element_pairs, .. } => {
|
|
for (value_expr, value_plan) in element_pairs {
|
|
let value_reg =
|
|
self.compile_rego_expr_with_span(value_expr, value_expr.span(), false)?;
|
|
let _ = self.apply_destructuring_plan(
|
|
value_plan,
|
|
value_reg,
|
|
span,
|
|
PlanContext::Assignment,
|
|
)?;
|
|
}
|
|
Ok(self.load_bool_literal(true, span))
|
|
}
|
|
AssignmentPlan::EqualityCheck { lhs_expr, rhs_expr } => {
|
|
let lhs_reg = self.compile_rego_expr_with_span(lhs_expr, lhs_expr.span(), false)?;
|
|
let rhs_reg = self.compile_rego_expr_with_span(rhs_expr, rhs_expr.span(), false)?;
|
|
let dest = self.alloc_register();
|
|
self.emit_instruction(
|
|
Instruction::Eq {
|
|
dest,
|
|
left: lhs_reg,
|
|
right: rhs_reg,
|
|
},
|
|
span,
|
|
);
|
|
if !self.soft_assert_mode {
|
|
self.emit_instruction(Instruction::AssertCondition { condition: dest }, span);
|
|
}
|
|
Ok(dest)
|
|
}
|
|
AssignmentPlan::WildcardMatch {
|
|
lhs_expr,
|
|
rhs_expr,
|
|
wildcard_side,
|
|
} => match wildcard_side {
|
|
WildcardSide::Both => Ok(self.load_bool_literal(true, span)),
|
|
WildcardSide::Lhs => {
|
|
let rhs_reg =
|
|
self.compile_rego_expr_with_span(rhs_expr, rhs_expr.span(), false)?;
|
|
self.emit_instruction(
|
|
Instruction::AssertNotUndefined { register: rhs_reg },
|
|
span,
|
|
);
|
|
Ok(self.load_bool_literal(true, span))
|
|
}
|
|
WildcardSide::Rhs => {
|
|
let lhs_reg =
|
|
self.compile_rego_expr_with_span(lhs_expr, lhs_expr.span(), false)?;
|
|
self.emit_instruction(
|
|
Instruction::AssertNotUndefined { register: lhs_reg },
|
|
span,
|
|
);
|
|
Ok(self.load_bool_literal(true, span))
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn apply_binding_plan(
|
|
&mut self,
|
|
plan: &BindingPlan,
|
|
value_register: Register,
|
|
span: &Span,
|
|
) -> Result<Option<Register>> {
|
|
match plan {
|
|
BindingPlan::Assignment { .. } => {
|
|
bail!("assignment binding plans should be handled via compile_assignment_plan")
|
|
}
|
|
BindingPlan::LoopIndex {
|
|
destructuring_plan, ..
|
|
} => self.apply_destructuring_plan(
|
|
destructuring_plan,
|
|
value_register,
|
|
span,
|
|
PlanContext::LoopIndex,
|
|
),
|
|
BindingPlan::Parameter {
|
|
destructuring_plan, ..
|
|
} => self.apply_destructuring_plan(
|
|
destructuring_plan,
|
|
value_register,
|
|
span,
|
|
PlanContext::FunctionParameter,
|
|
),
|
|
BindingPlan::SomeIn { .. } => {
|
|
bail!("use apply_some_in_binding_plan for SomeIn bindings")
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn apply_some_in_binding_plan(
|
|
&mut self,
|
|
key_plan: Option<&DestructuringPlan>,
|
|
key_register: Option<Register>,
|
|
value_plan: &DestructuringPlan,
|
|
value_register: Register,
|
|
span: &Span,
|
|
) -> Result<()> {
|
|
if let (Some(plan), Some(register)) = (key_plan, key_register) {
|
|
let _ = self.apply_destructuring_plan(plan, register, span, PlanContext::SomeIn)?;
|
|
}
|
|
let _ =
|
|
self.apply_destructuring_plan(value_plan, value_register, span, PlanContext::SomeIn)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn apply_destructuring_plan(
|
|
&mut self,
|
|
plan: &DestructuringPlan,
|
|
value_register: Register,
|
|
span: &Span,
|
|
context: PlanContext,
|
|
) -> Result<Option<Register>> {
|
|
match plan {
|
|
DestructuringPlan::Var(name_span) => {
|
|
self.bind_variable(name_span, value_register, span, context)?;
|
|
}
|
|
DestructuringPlan::Ignore => {}
|
|
DestructuringPlan::EqualityExpr(expected_expr) => {
|
|
let expected_reg =
|
|
self.compile_rego_expr_with_span(expected_expr, expected_expr.span(), false)?;
|
|
let cmp_reg = self.alloc_register();
|
|
self.emit_instruction(
|
|
Instruction::Eq {
|
|
dest: cmp_reg,
|
|
left: value_register,
|
|
right: expected_reg,
|
|
},
|
|
span,
|
|
);
|
|
if self.soft_assert_mode {
|
|
return Ok(Some(cmp_reg));
|
|
}
|
|
self.emit_instruction(Instruction::AssertCondition { condition: cmp_reg }, span);
|
|
}
|
|
DestructuringPlan::EqualityValue(expected_value) => {
|
|
let expected_reg = self.load_literal_value(expected_value, span);
|
|
let cmp_reg = self.alloc_register();
|
|
self.emit_instruction(
|
|
Instruction::Eq {
|
|
dest: cmp_reg,
|
|
left: value_register,
|
|
right: expected_reg,
|
|
},
|
|
span,
|
|
);
|
|
if self.soft_assert_mode {
|
|
return Ok(Some(cmp_reg));
|
|
}
|
|
self.emit_instruction(Instruction::AssertCondition { condition: cmp_reg }, span);
|
|
}
|
|
DestructuringPlan::Array { element_plans } => {
|
|
self.assert_array_length(value_register, element_plans.len(), span)?;
|
|
for (index, element_plan) in element_plans.iter().enumerate() {
|
|
let literal_idx = self.add_literal(Value::from(index));
|
|
let element_reg = self.alloc_register();
|
|
self.emit_instruction(
|
|
Instruction::IndexLiteral {
|
|
dest: element_reg,
|
|
container: value_register,
|
|
literal_idx,
|
|
},
|
|
span,
|
|
);
|
|
if context.require_defined_values() {
|
|
self.emit_instruction(
|
|
Instruction::AssertNotUndefined {
|
|
register: element_reg,
|
|
},
|
|
span,
|
|
);
|
|
}
|
|
let _ =
|
|
self.apply_destructuring_plan(element_plan, element_reg, span, context)?;
|
|
}
|
|
}
|
|
DestructuringPlan::Object {
|
|
field_plans,
|
|
dynamic_fields,
|
|
} => {
|
|
for (key, field_plan) in field_plans {
|
|
let literal_idx = self.add_literal(key.clone());
|
|
let field_reg = self.alloc_register();
|
|
self.emit_instruction(
|
|
Instruction::IndexLiteral {
|
|
dest: field_reg,
|
|
container: value_register,
|
|
literal_idx,
|
|
},
|
|
span,
|
|
);
|
|
self.emit_instruction(
|
|
Instruction::AssertNotUndefined {
|
|
register: field_reg,
|
|
},
|
|
span,
|
|
);
|
|
let _ = self.apply_destructuring_plan(field_plan, field_reg, span, context)?;
|
|
}
|
|
|
|
for (key_expr, field_plan) in dynamic_fields {
|
|
let key_reg =
|
|
self.compile_rego_expr_with_span(key_expr, key_expr.span(), false)?;
|
|
let field_reg = self.alloc_register();
|
|
self.emit_instruction(
|
|
Instruction::Index {
|
|
dest: field_reg,
|
|
container: value_register,
|
|
key: key_reg,
|
|
},
|
|
span,
|
|
);
|
|
self.emit_instruction(
|
|
Instruction::AssertNotUndefined {
|
|
register: field_reg,
|
|
},
|
|
span,
|
|
);
|
|
let _ = self.apply_destructuring_plan(field_plan, field_reg, span, context)?;
|
|
}
|
|
}
|
|
}
|
|
Ok(None)
|
|
}
|
|
|
|
fn bind_variable(
|
|
&mut self,
|
|
name_span: &Span,
|
|
value_register: Register,
|
|
span: &Span,
|
|
context: PlanContext,
|
|
) -> Result<()> {
|
|
let var_name = name_span.text();
|
|
if var_name == "_" {
|
|
return Ok(());
|
|
}
|
|
|
|
if self.is_var_bound_in_current_scope(var_name) {
|
|
bail!("Variable '{var_name}' already defined in current scope");
|
|
}
|
|
|
|
let dest = self.alloc_register();
|
|
self.emit_instruction(
|
|
Instruction::Move {
|
|
dest,
|
|
src: value_register,
|
|
},
|
|
span,
|
|
);
|
|
self.add_variable(var_name, dest);
|
|
|
|
if context.require_defined_values() {
|
|
self.emit_instruction(Instruction::AssertNotUndefined { register: dest }, span);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn load_bool_literal(&mut self, value: bool, span: &Span) -> Register {
|
|
let dest = self.alloc_register();
|
|
self.emit_instruction(Instruction::LoadBool { dest, value }, span);
|
|
dest
|
|
}
|
|
|
|
fn load_literal_value(&mut self, value: &Value, span: &Span) -> Register {
|
|
let literal_idx = self.add_literal(value.clone());
|
|
let dest = self.alloc_register();
|
|
self.emit_instruction(Instruction::Load { dest, literal_idx }, span);
|
|
dest
|
|
}
|
|
|
|
fn assert_array_length(
|
|
&mut self,
|
|
array_register: Register,
|
|
expected_length: usize,
|
|
span: &Span,
|
|
) -> Result<()> {
|
|
let expected_literal = self.add_literal(Value::from(expected_length));
|
|
let actual_len_reg = self.alloc_register();
|
|
self.emit_instruction(
|
|
Instruction::Count {
|
|
dest: actual_len_reg,
|
|
collection: array_register,
|
|
},
|
|
span,
|
|
);
|
|
|
|
let expected_len_reg = self.alloc_register();
|
|
self.emit_instruction(
|
|
Instruction::Load {
|
|
dest: expected_len_reg,
|
|
literal_idx: expected_literal,
|
|
},
|
|
span,
|
|
);
|
|
|
|
let cmp_reg = self.alloc_register();
|
|
self.emit_instruction(
|
|
Instruction::Eq {
|
|
dest: cmp_reg,
|
|
left: actual_len_reg,
|
|
right: expected_len_reg,
|
|
},
|
|
span,
|
|
);
|
|
self.emit_instruction(Instruction::AssertCondition { condition: cmp_reg }, span);
|
|
Ok(())
|
|
}
|
|
}
|