#![allow(clippy::print_stderr, clippy::pattern_type_mismatch)]
use crate::ast::*;
use crate::builtins::{self, BuiltinFcn};
use crate::compiled_policy::CompiledPolicyData;
#[cfg(feature = "azure_policy")]
use crate::compiled_policy::TargetInfo;
use crate::compiler::destructuring_planner::{
AssignmentPlan, BindingPlan, DestructuringPlan, WildcardSide,
};
use crate::compiler::hoist::{HoistedLoop, LoopType};
use crate::lexer::*;
use crate::lookup::Lookup;
use crate::parser::Parser;
use crate::scheduler::*;
use crate::utils::limits::{monotonic_now, ExecutionTimer, ExecutionTimerConfig};
#[cfg(feature = "std")]
use crate::utils::*;
#[cfg(not(feature = "std"))]
use crate::utils::{get_extra_arg, get_path_string, get_root_var, FunctionTable};
use crate::value::*;
use crate::*;
use crate::{Expression, Extension, Location, QueryResult, QueryResults};
#[cfg(feature = "coverage")]
use crate::query::traversal::traverse;
use crate::Rc;
use alloc::collections::btree_map::Entry as BTreeMapEntry;
use alloc::collections::{BTreeMap, BTreeSet};
use anyhow::{anyhow, bail, Result};
use core::ops::Bound::*;
type Scope = BTreeMap<SourceStr, Value>;
type ExprLookup = Lookup<Value>;
#[cfg(feature = "azure_policy")]
pub mod error;
#[cfg(feature = "azure_policy")]
pub mod target {
pub mod infer;
pub mod resolve;
}
type ContextExprs = (Option<Ref<Expr>>, Option<Ref<Expr>>);
type State = (
Value,
Value,
Value,
BTreeSet<Ref<Rule>>,
Value,
BTreeMap<String, FunctionModifier>,
RuleValues,
);
#[derive(Debug, Clone)]
enum FunctionModifier {
Function(String),
Value(Value),
}
type RuleValues = BTreeMap<Vec<Value>, (Value, Ref<Expr>)>;
#[derive(Debug)]
pub struct Interpreter {
compiled_policy: Rc<CompiledPolicyData>,
data: Value,
#[cfg(feature = "coverage")]
coverage: Map<Source, Vec<bool>>,
#[cfg(feature = "coverage")]
enable_coverage: bool,
traces: Option<Vec<Rc<str>>>,
gather_prints: bool,
prints: Vec<String>,
extensions: Map<String, (u8, Rc<Box<dyn Extension>>)>,
module: Option<Ref<Module>>,
current_module_path: String,
current_module_index: u32,
query_schedule: Option<Schedule>,
query_module: Option<NodeRef<Module>>,
input: Value,
init_data: Value,
with_document: Value,
with_functions: BTreeMap<String, FunctionModifier>,
scopes: Vec<Scope>,
loop_var_values: ExprLookup,
contexts: Vec<Context>,
processed: BTreeSet<Ref<Rule>>,
processed_paths: Value,
rule_values: RuleValues,
active_rules: Vec<Ref<Rule>>,
builtins_cache: BTreeMap<(&'static str, Vec<Value>), Value>,
no_rules_lookup: bool,
execution_timer: ExecutionTimer,
}
impl Default for Interpreter {
fn default() -> Self {
Self::new()
}
}
impl Clone for Interpreter {
fn clone(&self) -> Self {
Self {
compiled_policy: self.compiled_policy.clone(),
data: self.data.clone(),
init_data: self.init_data.clone(),
input: self.input.clone(),
with_document: self.with_document.clone(),
with_functions: self.with_functions.clone(),
gather_prints: self.gather_prints,
prints: self.prints.clone(),
traces: self.traces.clone(),
extensions: self.extensions.clone(),
#[cfg(feature = "coverage")]
coverage: self.coverage.clone(),
#[cfg(feature = "coverage")]
enable_coverage: self.enable_coverage,
processed: BTreeSet::default(),
processed_paths: Value::new_object(),
loop_var_values: ExprLookup::new(),
scopes: Vec::default(),
rule_values: BTreeMap::default(),
builtins_cache: BTreeMap::default(),
active_rules: Vec::default(),
contexts: Vec::default(),
current_module_path: String::default(),
current_module_index: 0,
query_schedule: None,
query_module: None,
module: None,
no_rules_lookup: false,
execution_timer: ExecutionTimer::new(self.execution_timer.config()),
}
}
}
#[derive(Debug, Clone)]
struct Context {
key_expr: Option<ExprRef>,
output_expr: Option<ExprRef>,
value: Value,
result: Option<QueryResult>,
results: QueryResults,
is_compr: bool,
rule_ref: Option<ExprRef>,
rule_value: Value,
is_set: bool,
is_old_style_set: bool,
output_constness_determined: bool,
early_return: bool,
}
impl Default for Context {
fn default() -> Self {
Self {
key_expr: None,
output_expr: None,
value: Value::Undefined,
result: None,
results: QueryResults::default(),
is_compr: false,
rule_ref: None,
rule_value: Value::new_object(),
is_set: false,
is_old_style_set: false,
output_constness_determined: false,
early_return: false,
}
}
}
impl Interpreter {
pub fn new() -> Interpreter {
let compiled_policy = compiled_policy::CompiledPolicyData {
strict_builtin_errors: true, ..Default::default()
};
Interpreter {
compiled_policy: Rc::new(compiled_policy),
data: Value::new_object(),
module: None,
current_module_path: String::default(),
current_module_index: 0,
query_schedule: None,
query_module: None,
input: Value::Undefined,
init_data: Value::new_object(),
with_document: Value::new_object(),
with_functions: BTreeMap::default(),
scopes: Vec::default(),
contexts: Vec::default(),
loop_var_values: Lookup::default(),
processed: BTreeSet::default(),
processed_paths: Value::new_object(),
rule_values: BTreeMap::default(),
active_rules: Vec::default(),
builtins_cache: BTreeMap::default(),
no_rules_lookup: false,
traces: None,
extensions: Map::default(),
#[cfg(feature = "coverage")]
coverage: Map::default(),
#[cfg(feature = "coverage")]
enable_coverage: false,
gather_prints: false,
prints: Vec::default(),
execution_timer: ExecutionTimer::new(None),
}
}
pub fn new_from_compiled_policy(compiled_policy: Rc<CompiledPolicyData>) -> Self {
Self {
data: Value::new_object(),
module: None,
current_module_path: String::default(),
current_module_index: 0,
query_module: None,
query_schedule: None,
input: Value::Undefined,
with_document: Value::new_object(),
with_functions: BTreeMap::default(),
scopes: Vec::default(),
contexts: Vec::default(),
loop_var_values: Lookup::default(),
processed: BTreeSet::default(),
processed_paths: Value::new_object(),
rule_values: BTreeMap::default(),
active_rules: Vec::default(),
builtins_cache: BTreeMap::default(),
no_rules_lookup: false,
traces: None,
#[cfg(feature = "coverage")]
coverage: Map::default(),
#[cfg(feature = "coverage")]
enable_coverage: false,
gather_prints: false,
prints: Vec::default(),
extensions: compiled_policy.extensions.clone(),
compiled_policy: compiled_policy.clone(),
init_data: compiled_policy
.data
.clone()
.unwrap_or_else(Value::new_object),
execution_timer: ExecutionTimer::new(None),
}
}
fn reset_execution_timer_state(&mut self) {
self.execution_timer.reset();
if self.execution_timer.limit().is_none() {
return;
}
if let Some(now) = monotonic_now() {
self.execution_timer.start(now);
}
}
fn execution_timer_tick(&mut self, work_units: u32) -> Result<()> {
if self.execution_timer.limit().is_none() {
return Ok(());
}
let Some(now) = monotonic_now() else {
return Ok(());
};
self.execution_timer.tick(work_units, now)?;
Ok(())
}
#[inline]
fn check_execution_time(&mut self) -> Result<()> {
self.execution_timer_tick(1)
}
fn compiled_policy_mut(&mut self) -> &mut CompiledPolicyData {
Rc::make_mut(&mut self.compiled_policy)
}
pub fn set_schedule(&mut self, schedule: Option<Rc<Schedule>>) {
self.compiled_policy_mut().schedule = schedule;
}
pub fn set_functions(&mut self, functions: FunctionTable) {
self.compiled_policy_mut().functions = functions;
}
pub fn set_modules(&mut self, modules: Rc<Vec<Ref<Module>>>) {
self.compiled_policy_mut().modules = modules;
}
pub fn set_loop_hoisting_table(&mut self, table: crate::compiler::hoist::HoistedLoopsLookup) {
self.compiled_policy_mut().loop_hoisting_table = table;
}
pub fn take_loop_hoisting_table(&mut self) -> crate::compiler::hoist::HoistedLoopsLookup {
core::mem::replace(
&mut self.compiled_policy_mut().loop_hoisting_table,
crate::compiler::hoist::HoistedLoopsLookup::new(),
)
}
pub const fn get_data_mut(&mut self) -> &mut Value {
&mut self.data
}
pub fn set_init_data(&mut self, data: Value) {
self.init_data = data;
}
pub const fn get_init_data(&self) -> &Value {
&self.init_data
}
pub const fn get_init_data_mut(&mut self) -> &mut Value {
&mut self.init_data
}
#[allow(dead_code)]
pub const fn get_compiled_policy(&self) -> &Rc<CompiledPolicyData> {
&self.compiled_policy
}
pub fn set_traces(&mut self, enable_tracing: bool) {
self.traces = match enable_tracing {
true => Some(vec![]),
false => None,
};
}
pub fn set_strict_builtin_errors(&mut self, b: bool) {
self.compiled_policy_mut().strict_builtin_errors = b;
}
pub fn set_execution_timer_config(&mut self, config: Option<ExecutionTimerConfig>) {
self.execution_timer = ExecutionTimer::new(config);
self.reset_execution_timer_state();
}
pub fn set_input(&mut self, input: Value) {
self.input = input.clone();
if let Ok(with_input) = Self::make_or_get_value_mut(&mut self.with_document, &["input"]) {
*with_input = input;
}
}
pub fn init_with_document(&mut self) -> Result<()> {
*Self::make_or_get_value_mut(&mut self.with_document, &["data"])? = self.init_data.clone();
*Self::make_or_get_value_mut(&mut self.with_document, &["input"])? = self.input.clone();
Ok(())
}
pub fn clear_builtins_cache(&mut self) {
self.builtins_cache.clear();
}
pub fn clean_internal_evaluation_state(&mut self) {
self.data = self.init_data.clone();
self.processed.clear();
self.processed_paths = Value::new_object();
self.ensure_loop_var_values_capacity();
self.scopes = vec![Scope::new()];
self.contexts = vec![];
self.rule_values.clear();
self.builtins_cache.clear();
self.reset_execution_timer_state();
}
#[cfg(feature = "allocator-memory-limits")]
fn memory_check(&mut self) -> Result<()> {
let _ = self; crate::utils::limits::check_memory_limit_if_needed().map_err(|err| anyhow!(err))
}
#[cfg(not(feature = "allocator-memory-limits"))]
const fn memory_check(&mut self) -> Result<()> {
let _ = self; Ok(())
}
fn set_loop_var_value(&mut self, expr: &ExprRef, value: Value) -> Result<()> {
let module_idx = self.current_module_index;
let expr_idx = expr.eidx();
self.loop_var_values
.set_checked(module_idx, expr_idx, value)
.map_err(|err| anyhow!("internal error: loop var indices out of bounds: {err}"))
}
fn get_loop_var_value(&self, expr: &ExprRef) -> Result<Option<&Value>> {
let module_idx = self.current_module_index;
let expr_idx = expr.eidx();
self.loop_var_values
.get_checked(module_idx, expr_idx)
.map_err(|err| anyhow!("internal error: loop var indices out of bounds: {err}"))
}
fn remove_loop_var_value(&mut self, expr: &ExprRef) {
let module_idx = self.current_module_index;
let expr_idx = expr.eidx();
self.loop_var_values.clear(module_idx, expr_idx);
}
#[inline]
fn loop_assignment_expr(loop_info: &HoistedLoop) -> &ExprRef {
loop_info.loop_expr.as_ref().unwrap_or(&loop_info.value)
}
#[inline]
fn loop_index_expr(loop_info: &HoistedLoop) -> Option<&ExprRef> {
loop_info.key.as_ref().or(None)
}
#[inline]
const fn loop_collection_expr(loop_info: &HoistedLoop) -> &ExprRef {
&loop_info.collection
}
#[inline]
fn loop_span(loop_info: &HoistedLoop) -> Span {
loop_info.value.span().clone()
}
fn get_walk_binding_plan(&self, loop_info: &HoistedLoop) -> Result<Option<DestructuringPlan>> {
if loop_info.loop_type != LoopType::Walk {
return Ok(None);
}
if let Expr::Call { params, .. } = Self::loop_assignment_expr(loop_info).as_ref() {
if let Some(last_param) = params.last() {
let module_idx = self.current_module_index;
let expr_idx = last_param.as_ref().eidx();
let binding_plan = self
.compiled_policy
.loop_hoisting_table
.get_expr_binding_plan(module_idx, expr_idx)
.map_err(|err| anyhow!("loop hoisting table out of bounds: {err}"))?;
return match binding_plan.cloned() {
Some(BindingPlan::Parameter {
destructuring_plan, ..
}) => Ok(Some(destructuring_plan)),
Some(other_plan) => bail!(
"internal error: expected Parameter for walk output parameter, got {:?}",
other_plan
),
None => bail!("internal error: missing binding plan for walk output parameter"),
};
}
}
bail!("internal error: walk loop missing output parameter expression")
}
fn ensure_loop_var_values_capacity(&mut self) {
for (module_idx, module) in self.compiled_policy.modules.iter().enumerate() {
if let Ok(idx_u32) = u32::try_from(module_idx) {
self.loop_var_values
.ensure_capacity(idx_u32, module.num_expressions);
}
}
if let Some(query_module) = &self.query_module {
let query_module_idx = self.compiled_policy.modules.len();
let eidx = query_module.num_expressions;
if let Ok(idx_u32) = u32::try_from(query_module_idx) {
self.loop_var_values.ensure_capacity(idx_u32, eidx);
}
}
}
fn current_module(&self) -> Result<Ref<Module>> {
self.module
.clone()
.ok_or_else(|| anyhow!("internal error: current module not set"))
}
fn current_scope(&mut self) -> Result<&Scope> {
self.scopes
.last()
.ok_or_else(|| anyhow!("internal error: no active scope"))
}
fn current_scope_mut(&mut self) -> Result<&mut Scope> {
self.scopes
.last_mut()
.ok_or_else(|| anyhow!("internal error: no active scope"))
}
#[inline(always)]
fn add_variable(&mut self, name: &SourceStr, value: Value) -> Result<()> {
if name.text() != "_" {
self.current_scope_mut()?.insert(name.clone(), value);
}
Ok(())
}
fn add_variable_or(&mut self, name: &SourceStr) -> Result<Value> {
for scope in self.scopes.iter().rev() {
if let Some(variable) = scope.get(name) {
return Ok(variable.clone());
}
}
self.add_variable(name, Value::Undefined)?;
Ok(Value::Undefined)
}
fn eval_chained_ref_dot_or_brack(&mut self, mut expr: &ExprRef) -> Result<Value> {
let mut path = vec![];
loop {
if let Some(v) = self.get_loop_var_value(expr)? {
path.reverse();
return Ok(Self::get_value_chained(v.clone(), &path[..]));
}
match expr.as_ref() {
Expr::Var { span, .. } => {
path.reverse();
return self.lookup_var(span, &path[..], false);
}
Expr::RefDot { refr, field, .. } => {
expr = refr;
path.push(field.0.text());
}
Expr::RefBrack { refr, index, .. } => match index.as_ref() {
Expr::String { span, .. } => {
expr = refr;
path.push(span.text());
}
_ => {
path.reverse();
let index = self.eval_expr(index)?;
if let Ok(ref_path) = get_path_string(refr, None) {
if get_root_var(refr)?.text() == "data" && index != Value::Undefined {
let index = match &index {
Value::String(s) => s.to_string(),
_ => index.to_string(),
};
let ref_path = if path.is_empty() {
format!("{ref_path}.{index}")
} else {
format!("{ref_path}.{index}.{}", path.join("."))
};
self.ensure_rule_evaluated(ref_path)?;
}
}
let obj = self.eval_expr(refr)?;
let mut v = obj[&index].clone();
if v == Value::Undefined
&& matches!(index, Value::Number(_))
&& get_root_var(refr)?.text() == "data"
{
let index = index.to_string();
v = obj[index].clone();
}
return Ok(Self::get_value_chained(v, &path[..]));
}
},
_ => {
path.reverse();
return Ok(Self::get_value_chained(self.eval_expr(expr)?, &path[..]));
}
}
}
}
fn eval_bool_expr(
&mut self,
op: &BoolOp,
lhs_expr: &ExprRef,
rhs_expr: &ExprRef,
) -> Result<Value> {
let lhs = self.eval_expr(lhs_expr)?;
let rhs = self.eval_expr(rhs_expr)?;
if lhs == Value::Undefined || rhs == Value::Undefined {
return Ok(Value::Undefined);
}
builtins::comparison::compare(op, &lhs, &rhs)
}
fn eval_bin_expr(&mut self, op: &BinOp, lhs: &ExprRef, rhs: &ExprRef) -> Result<Value> {
let lhs_value = self.eval_expr(lhs)?;
let rhs_value = self.eval_expr(rhs)?;
if lhs_value == Value::Undefined || rhs_value == Value::Undefined {
return Ok(Value::Undefined);
}
match op {
BinOp::Union => builtins::sets::union(lhs, rhs, lhs_value, rhs_value),
BinOp::Intersection => builtins::sets::intersection(lhs, rhs, lhs_value, rhs_value),
}
}
fn eval_arith_expr(
&mut self,
span: &Span,
op: &ArithOp,
lhs: &ExprRef,
rhs: &ExprRef,
) -> Result<Value> {
let lhs_value = self.eval_expr(lhs)?;
let rhs_value = self.eval_expr(rhs)?;
if lhs_value == Value::Undefined || rhs_value == Value::Undefined {
return Ok(Value::Undefined);
}
match (op, &lhs_value, &rhs_value) {
(ArithOp::Sub, Value::Set(_), _) | (ArithOp::Sub, _, Value::Set(_)) => {
builtins::sets::difference(lhs, rhs, lhs_value, rhs_value)
}
_ => builtins::numbers::arithmetic_operation(
span,
op,
lhs,
rhs,
lhs_value,
rhs_value,
self.compiled_policy.strict_builtin_errors,
),
}
}
fn eval_every(
&mut self,
_span: &Span,
key: &Option<Span>,
value: &Span,
domain: &ExprRef,
query: &Ref<Query>,
) -> Result<bool> {
self.check_execution_time()?;
let domain = self.eval_expr(domain)?;
self.scopes.push(Scope::new());
self.contexts.push(Context {
value: Value::new_set(),
..Context::default()
});
let mut r = true;
match domain {
Value::Array(a) => {
for (idx, v) in a.iter().enumerate() {
self.add_variable(&value.source_str(), v.clone())?;
if let Some(key) = key {
self.add_variable(&key.source_str(), Value::from(idx))?;
}
if !self.eval_query(query)? {
r = false;
break;
}
}
}
Value::Set(s) => {
for v in s.iter() {
self.add_variable(&value.source_str(), v.clone())?;
if let Some(key) = key {
self.add_variable(&key.source_str(), v.clone())?;
}
if !self.eval_query(query)? {
r = false;
break;
}
}
}
Value::Object(o) => {
for (k, v) in o.iter() {
self.add_variable(&value.source_str(), v.clone())?;
if let Some(key) = key {
self.add_variable(&key.source_str(), k.clone())?;
}
if !self.eval_query(query)? {
r = false;
break;
}
}
}
_ => {
r = false;
}
};
self.contexts.pop();
self.scopes.pop();
Ok(r)
}
pub fn execute_destructuring_plan(
&mut self,
plan: &DestructuringPlan,
value: &Value,
) -> Result<Value> {
self.check_execution_time()?;
if value == &Value::Undefined {
return Ok(Value::Undefined);
}
fn compare(v1: &Value, v2: &Value) -> Result<Value> {
if v1 != v2 || v1 == &Value::Undefined {
Ok(Value::Undefined)
} else {
Ok(Value::from(true))
}
}
match plan {
DestructuringPlan::Var(var_name) => {
self.add_variable(&var_name.source_str(), value.clone())?;
Ok(Value::Bool(true))
}
DestructuringPlan::Ignore => Ok(Value::Bool(true)),
DestructuringPlan::EqualityExpr(expected_expr) => {
let expected = self.eval_expr(expected_expr)?;
compare(value, &expected)
}
DestructuringPlan::EqualityValue(expected) => Ok(Value::from(value == expected)),
DestructuringPlan::Array { element_plans } => {
if let Value::Array(arr) = value {
if arr.len() != element_plans.len() {
return Ok(Value::Undefined);
}
for (i, element_plan) in element_plans.iter().enumerate() {
let Some(element) = arr.get(i) else {
return Ok(Value::Undefined);
};
if self.execute_destructuring_plan(element_plan, element)?
!= Value::from(true)
{
return Ok(Value::Undefined);
}
}
Ok(Value::from(true))
} else {
Ok(Value::Undefined) }
}
DestructuringPlan::Object {
field_plans,
dynamic_fields,
} => {
if let Value::Object(obj) = value {
for (key, field_plan) in field_plans {
if let Some(field_value) = obj.get(key) {
if self.execute_destructuring_plan(field_plan, field_value)?
!= Value::from(true)
{
return Ok(Value::Undefined);
}
} else {
return Ok(Value::Undefined); }
}
if !dynamic_fields.is_empty() {
for (key_expr, field_plan) in dynamic_fields {
let key_value = self.eval_expr(key_expr)?;
if key_value == Value::Undefined {
return Ok(Value::Undefined);
}
if let Some(field_value) = obj.get(&key_value) {
if self.execute_destructuring_plan(field_plan, field_value)?
!= Value::from(true)
{
return Ok(Value::Undefined);
}
} else {
return Ok(Value::Undefined);
}
}
}
Ok(Value::from(true))
} else {
Ok(Value::Undefined) }
}
}
}
fn execute_assignment_plan(&mut self, plan: &AssignmentPlan) -> Result<Value> {
self.check_execution_time()?;
match plan {
AssignmentPlan::ColonEquals {
lhs_expr: _,
rhs_expr,
lhs_plan,
} => {
let rhs_value = self.eval_expr(rhs_expr)?;
self.execute_destructuring_plan(lhs_plan, &rhs_value)
}
AssignmentPlan::EqualsBindLeft {
lhs_expr: _,
rhs_expr,
lhs_plan,
} => {
let rhs_value = self.eval_expr(rhs_expr)?;
self.execute_destructuring_plan(lhs_plan, &rhs_value)
}
AssignmentPlan::EqualsBindRight {
lhs_expr,
rhs_expr: _,
rhs_plan,
} => {
let lhs_value = self.eval_expr(lhs_expr)?;
self.execute_destructuring_plan(rhs_plan, &lhs_value)
}
AssignmentPlan::EqualsBothSides {
lhs_expr: _,
rhs_expr: _,
element_pairs,
} => {
for (value_expr, pattern_plan) in element_pairs {
let value = self.eval_expr(value_expr)?;
if self.execute_destructuring_plan(pattern_plan, &value)? != Value::from(true) {
return Ok(Value::Undefined);
}
}
Ok(Value::from(true))
}
AssignmentPlan::WildcardMatch {
lhs_expr,
rhs_expr,
wildcard_side,
} => match wildcard_side {
WildcardSide::Both => Ok(Value::Bool(true)),
WildcardSide::Lhs => {
let rhs_value = self.eval_expr(rhs_expr)?;
if rhs_value == Value::Undefined {
Ok(Value::Undefined)
} else {
Ok(Value::Bool(true))
}
}
WildcardSide::Rhs => {
let lhs_value = self.eval_expr(lhs_expr)?;
if lhs_value == Value::Undefined {
Ok(Value::Undefined)
} else {
Ok(Value::Bool(true))
}
}
},
AssignmentPlan::EqualityCheck { lhs_expr, rhs_expr } => {
let lhs_value = self.eval_expr(lhs_expr)?;
let rhs_value = self.eval_expr(rhs_expr)?;
if lhs_value == Value::Undefined || rhs_value == Value::Undefined {
return Ok(Value::Undefined);
}
if lhs_value == rhs_value {
Ok(Value::Bool(true))
} else {
Ok(Value::Undefined)
}
}
}
}
fn eval_some_in(
&mut self,
_span: &Span,
_key_expr: &Option<ExprRef>,
_value_expr: &ExprRef,
collection: &ExprRef,
stmts: &[&LiteralStmt],
) -> Result<bool> {
self.check_execution_time()?;
let scope_saved = self.current_scope()?.clone();
let mut count: usize = 0;
let module_idx = self.current_module_index;
let expr_idx = collection.as_ref().eidx();
let binding_plan = self
.compiled_policy
.loop_hoisting_table
.get_expr_binding_plan(module_idx, expr_idx)
.map_err(|err| anyhow!("loop hoisting table out of bounds: {err}"))?;
let Some(BindingPlan::SomeIn {
key_plan,
value_plan,
..
}) = binding_plan.cloned()
else {
bail!("internal error: missing binding plan for some..in expression");
};
match self.eval_expr(collection)? {
Value::Array(a) => {
for (idx, value) in a.iter().enumerate() {
*self.current_scope_mut()? = scope_saved.clone();
let mut success = if let Some(key_plan) = &key_plan {
self.execute_destructuring_plan(key_plan, &Value::from(idx))?
== Value::from(true)
} else {
true
};
success = success
&& self.execute_destructuring_plan(&value_plan, value)?
== Value::from(true);
if !success {
*self.current_scope_mut()? = scope_saved.clone();
continue;
}
let mut should_break = false;
if self.eval_stmts(stmts)? {
count = count.saturating_add(1);
if let Some(ctx) = self.contexts.last() {
if ctx.early_return {
should_break = true;
}
}
}
*self.current_scope_mut()? = scope_saved.clone();
if should_break {
break;
}
}
}
Value::Set(s) => {
for value in s.iter() {
*self.current_scope_mut()? = scope_saved.clone();
let mut success = if let Some(key_plan) = &key_plan {
self.execute_destructuring_plan(key_plan, value)? == Value::from(true)
} else {
true
};
success = success
&& self.execute_destructuring_plan(&value_plan, value)?
== Value::from(true);
if !success {
*self.current_scope_mut()? = scope_saved.clone();
continue;
}
let mut should_break = false;
if self.eval_stmts(stmts)? {
count = count.saturating_add(1);
if let Some(ctx) = self.contexts.last() {
if ctx.early_return {
should_break = true;
}
}
}
*self.current_scope_mut()? = scope_saved.clone();
if should_break {
break;
}
}
}
Value::Object(o) => {
for (key, value) in o.iter() {
*self.current_scope_mut()? = scope_saved.clone();
let mut success = if let Some(key_plan) = &key_plan {
self.execute_destructuring_plan(key_plan, key)? == Value::from(true)
} else {
true
};
success = success
&& self.execute_destructuring_plan(&value_plan, value)?
== Value::from(true);
if !success {
*self.current_scope_mut()? = scope_saved.clone();
continue;
}
let mut should_break = false;
if self.eval_stmts(stmts)? {
count = count.saturating_add(1);
if let Some(ctx) = self.contexts.last() {
if ctx.early_return {
should_break = true;
}
}
}
*self.current_scope_mut()? = scope_saved.clone();
if should_break {
break;
}
}
}
Value::Undefined => (),
v => {
bail!(collection.span().error(
format!("`some .. in collection` expects array/set/object. Got `{v}`").as_str()
))
}
}
Ok(count > 0)
}
fn make_expression_result(span: &Span, v: &Value) -> Expression {
Expression {
value: v.clone(),
text: span.text().to_string().into(),
location: Location {
row: span.line,
col: span.col,
},
}
}
fn eval_stmt_impl(&mut self, stmt: &LiteralStmt, stmts: &[&LiteralStmt]) -> Result<bool> {
self.memory_check()?;
self.check_execution_time()?;
Ok(match &stmt.literal {
Literal::Expr { span, expr, .. } => {
let value = match expr.as_ref() {
Expr::Call {
span: call_span,
fcn,
params,
..
} => self.eval_call(
call_span,
expr,
fcn,
params,
get_extra_arg(
expr,
Some(self.current_module_path.as_str()),
&self.compiled_policy.functions,
),
true,
)?,
_ => self.eval_expr(expr)?,
};
if let Some(ctx) = self.contexts.last_mut() {
if let Some(result) = &mut ctx.result {
if value != Value::Undefined {
result
.expressions
.push(Self::make_expression_result(span, &value));
} else {
result.bindings = Value::new_object();
result.expressions.clear();
}
}
}
if let Value::Bool(bool) = value {
bool
} else {
value != Value::Undefined
}
}
Literal::NotExpr { span, expr, .. } => {
let value = match expr.as_ref() {
Expr::Call {
span: call_span,
fcn,
params,
..
} => self.eval_call(
call_span,
expr,
fcn,
params,
get_extra_arg(
expr,
Some(self.current_module_path.as_str()),
&self.compiled_policy.functions,
),
false,
)?,
_ => self.eval_expr(expr)?,
};
if let Some(ctx) = self.contexts.last_mut() {
if let Some(result) = &mut ctx.result {
result
.expressions
.push(Self::make_expression_result(span, &Value::Bool(true)));
}
}
matches!(value, Value::Bool(false) | Value::Undefined)
}
Literal::SomeVars { span, vars, .. } => {
for var in vars {
let name = var.source_str();
if self.current_scope()?.get(&name).is_some() {
bail!("duplicated definition of local variable {}", name);
}
self.add_variable_or(&name)?;
}
if let Some(ctx) = self.contexts.last_mut() {
if let Some(result) = &mut ctx.result {
result
.expressions
.push(Self::make_expression_result(span, &Value::Bool(true)));
}
}
true
}
Literal::SomeIn {
span,
key,
value,
collection,
} => {
if let Some(ctx) = self.contexts.last_mut() {
if let Some(result) = &mut ctx.result {
result
.expressions
.push(Self::make_expression_result(span, &Value::Bool(true)));
}
}
self.eval_some_in(span, key, value, collection, stmts)?
}
Literal::Every {
span,
key,
value,
domain,
query,
} => {
if let Some(ctx) = self.contexts.last_mut() {
if let Some(result) = &mut ctx.result {
result
.expressions
.push(Self::make_expression_result(span, &Value::Bool(true)));
}
}
self.eval_every(span, key, value, domain, query)?
}
})
}
fn apply_with_modifiers(&mut self, stmt: &LiteralStmt) -> Result<(Option<State>, bool)> {
if !stmt.with_mods.is_empty() {
let with_document = self.with_document.clone();
let input = self.input.clone();
let data = self.data.clone();
let processed = self.processed.clone();
let with_functions = self.with_functions.clone();
let rule_values = self.rule_values.clone();
self.processed.clear();
let processed_paths =
core::mem::replace(&mut self.processed_paths, Value::new_object());
self.rule_values.clear();
let mut skip_exec = false;
for wm in &stmt.with_mods {
let path = Parser::get_path_ref_components(&wm.refr)?;
let path: Vec<&str> = path.iter().map(|s| s.text()).collect();
let mut target = path.join(".");
let mut target_is_function = self.lookup_function_by_name(&target).is_some()
|| Self::is_builtin(wm.refr.span(), &target);
if !target_is_function
&& !target.starts_with("data.")
&& !target.starts_with("input.")
&& target != "input"
{
if self.lookup_function_by_name(&target).is_none()
&& !Self::is_builtin(wm.refr.span(), &target)
{
target = format!("{}.{}", self.current_module_path, target);
if self.lookup_function_by_name(&target).is_none() {
bail!(wm.refr.span().error("undefined rule"));
}
target_is_function = true;
}
}
if target_is_function {
match self.eval_expr(&wm.r#as) {
Ok(v) if v != Value::Undefined => {
self.with_functions
.insert(target, FunctionModifier::Value(v));
}
_ => {
let mut function_path =
get_path_string(&wm.r#as, Some(&self.current_module_path))?;
if self.lookup_function_by_name(&function_path).is_none() {
function_path = get_path_string(&wm.r#as, None)?;
if self.lookup_function_by_name(&function_path).is_none()
&& !Self::is_builtin(wm.r#as.span(), &function_path)
{
skip_exec = true;
}
}
self.with_functions
.insert(target, FunctionModifier::Function(function_path));
}
}
} else {
let value = self.eval_expr(&wm.r#as)?;
skip_exec = value == Value::Undefined;
let Some(first) = path.first() else {
bail!(wm.refr.span().error("empty path in with modifier"));
};
if *first == "input" || *first == "data" {
let mut obj = &mut self.with_document;
for p in &path {
if !matches!(obj, Value::Object(_)) {
*obj = Value::new_object();
}
obj = obj
.as_object_mut()?
.entry(Value::String(p.to_string().into()))
.or_insert(Value::new_object());
}
*obj = value;
if let Some(rules) = self.compiled_policy.rules.get(&target) {
for r in rules {
self.processed.insert(r.clone());
}
}
} else {
bail!(wm.refr.span().error("not a valid target for with modifier"));
}
}
}
self.data = self.with_document["data"].clone();
self.input = self.with_document["input"].clone();
Ok((
Some((
with_document,
input,
data,
processed,
processed_paths,
with_functions,
rule_values,
)),
skip_exec,
))
} else {
Ok((None, false))
}
}
fn restore_state(&mut self, saved_state: Option<State>) -> Result<()> {
if let Some(s) = saved_state {
(
self.with_document,
self.input,
self.data,
self.processed,
self.processed_paths,
self.with_functions,
self.rule_values,
) = s;
}
Ok(())
}
fn eval_stmt(&mut self, stmt: &LiteralStmt, stmts: &[&LiteralStmt]) -> Result<bool> {
let (saved_state, skip_exec) = self.apply_with_modifiers(stmt)?;
let r = if !skip_exec {
self.eval_stmt_impl(stmt, stmts)
} else {
Ok(false)
};
self.restore_state(saved_state)?;
r
}
fn clear_scope(scope: &mut Scope) {
for (_, v) in scope.iter_mut() {
*v = Value::Undefined;
}
}
fn eval_stmts_in_loop(
&mut self,
stmts: &[&LiteralStmt],
loops: &[HoistedLoop],
) -> Result<bool> {
self.memory_check()?;
self.check_execution_time()?;
if loops.is_empty() {
if let Some((first_stmt, tail_stmts)) = stmts.split_first() {
if self.eval_stmt(first_stmt, tail_stmts)? {
if !matches!(&first_stmt.literal, Literal::SomeIn { .. }) {
self.eval_stmts(tail_stmts)
} else {
Ok(true)
}
} else {
Ok(false)
}
} else {
self.eval_stmts(stmts)
}
} else {
let (loop_info, loop_tail) = loops
.split_first()
.ok_or_else(|| anyhow!("internal error: expected loop info"))?;
let (first_stmt, _tail_stmts) = stmts
.split_first()
.ok_or_else(|| anyhow!("internal error: expected statements for loop"))?;
let mut result = false;
let (saved_state, _) = self.apply_with_modifiers(first_stmt)?;
let collection_expr = Self::loop_collection_expr(loop_info).clone();
let loop_value = if let Expr::Call {
span, fcn, params, ..
} = collection_expr.as_ref()
{
let extra_arg = get_extra_arg(
&collection_expr,
Some(self.current_module_path.as_str()),
&self.compiled_policy.functions,
);
let params_slice: &[ExprRef] = if extra_arg.is_some() {
let (_, prefix) = params
.split_last()
.ok_or_else(|| anyhow!("internal error: expected params"))?;
prefix
} else {
params.as_slice()
};
self.eval_call_impl(span, &collection_expr, fcn, params_slice)?
} else {
self.eval_expr(&collection_expr)?
};
self.restore_state(saved_state)?;
if let Some(walk_plan) = self.get_walk_binding_plan(loop_info)? {
let loop_target_expr = Self::loop_assignment_expr(loop_info);
self.scopes.push(Scope::default());
let query_result = self.get_current_context()?.result.clone();
let mut walk_result = false;
match loop_value {
Value::Array(items) => {
for item in items.iter() {
self.memory_check()?;
self.set_loop_var_value(loop_target_expr, item.clone())?;
if self.execute_destructuring_plan(&walk_plan, item)?
== Value::from(true)
{
walk_result =
self.eval_stmts_in_loop(stmts, loop_tail)? || walk_result;
}
Self::clear_scope(self.current_scope_mut()?);
if let Some(ctx) = self.contexts.last_mut() {
ctx.result.clone_from(&query_result);
if ctx.early_return {
break;
}
}
}
}
Value::Undefined => (),
other => {
let span = Self::loop_span(loop_info);
bail!(span
.error(format!("walk expected array result, got `{other}`").as_str()));
}
}
self.scopes.pop();
self.remove_loop_var_value(loop_target_expr);
return Ok(walk_result);
}
let index_expr = Self::loop_index_expr(loop_info);
if let Some(Expr::Var {
span: index_var, ..
}) = index_expr.map(|r| r.as_ref())
{
if let Some(idx) = self.lookup_local_var(&index_var.source_str()) {
if loop_value[&idx] != Value::Undefined {
result = self.eval_stmts_in_loop(stmts, loop_tail)? || result;
return Ok(result);
} else if idx != Value::Undefined {
return Ok(false);
}
}
}
let index_plan = if let Some(index) = index_expr {
let module_idx = self.current_module_index;
let expr_idx = index.as_ref().eidx();
let plan = self
.compiled_policy
.loop_hoisting_table
.get_expr_binding_plan(module_idx, expr_idx)
.map_err(|err| anyhow!("loop hoisting table out of bounds: {err}"))?;
match plan.cloned() {
Some(BindingPlan::LoopIndex {
destructuring_plan, ..
}) => destructuring_plan,
Some(other_plan) => {
bail!("internal error: expected LoopIndex for loop index expression, got {:?}", other_plan);
}
None => {
bail!("internal error: no binding plan found for loop index expression");
}
}
} else {
bail!("internal error: no binding plan found for loop index expression");
};
self.scopes.push(Scope::default());
let query_result = self.get_current_context()?.result.clone();
let loop_target_expr = Self::loop_assignment_expr(loop_info);
match loop_value {
Value::Array(items) => {
for (idx, v) in items.iter().enumerate() {
self.memory_check()?;
self.set_loop_var_value(loop_target_expr, v.clone())?;
if self.execute_destructuring_plan(&index_plan, &Value::from(idx))?
== Value::from(true)
{
result = self.eval_stmts_in_loop(stmts, loop_tail)? || result;
}
Self::clear_scope(self.current_scope_mut()?);
if let Some(ctx) = self.contexts.last_mut() {
ctx.result.clone_from(&query_result);
if ctx.early_return {
break;
}
}
}
self.remove_loop_var_value(loop_target_expr);
}
Value::Set(items) => {
for v in items.iter() {
self.memory_check()?;
self.set_loop_var_value(loop_target_expr, v.clone())?;
if self.execute_destructuring_plan(&index_plan, v)? == Value::from(true) {
result = self.eval_stmts_in_loop(stmts, loop_tail)? || result;
}
Self::clear_scope(self.current_scope_mut()?);
if let Some(ctx) = self.contexts.last_mut() {
ctx.result.clone_from(&query_result);
if ctx.early_return {
break;
}
}
}
self.remove_loop_var_value(loop_target_expr);
}
Value::Object(obj) => {
for (k, v) in obj.iter() {
self.memory_check()?;
self.set_loop_var_value(loop_target_expr, v.clone())?;
if self.execute_destructuring_plan(&index_plan, k)? == Value::from(true) {
result = self.eval_stmts_in_loop(stmts, loop_tail)? || result;
}
Self::clear_scope(self.current_scope_mut()?);
if let Some(ctx) = self.contexts.last_mut() {
ctx.result.clone_from(&query_result);
if ctx.early_return {
break;
}
}
}
self.remove_loop_var_value(loop_target_expr);
}
Value::Undefined => {
result = false;
}
_ => {
result = false;
}
}
self.scopes.pop();
Ok(result)
}
}
fn eval_rule_ref(&mut self, rule_refr: &ExprRef) -> Result<Vec<Value>> {
self.check_execution_time()?;
let mut comps = vec![];
let mut expr = rule_refr;
loop {
match expr.as_ref() {
Expr::Var { span: v, .. } => {
comps.push(Value::String(v.text().into()));
break;
}
Expr::RefBrack {
refr: nested_refr,
index,
..
} => {
comps.push(self.eval_expr(index)?);
expr = nested_refr;
}
Expr::RefDot {
refr: nested_refr,
field,
..
} => {
comps.push(Value::String(field.0.text().into()));
expr = nested_refr;
}
_ => {
bail!(expr.span().error("not a valid rule ref"));
}
}
}
comps.reverse();
Ok(comps)
}
fn update_rule_value(
&mut self,
span: &Span,
path: Vec<Value>,
mut value: Value,
is_set: bool,
) -> Result<()> {
{
let mut init_obj = &self.init_data;
for p in path.iter() {
init_obj = &init_obj[p];
}
if init_obj != &Value::Undefined {
return Ok(());
}
}
let mut obj = &mut self.data;
let len = path.len();
for (idx, p) in path.into_iter().enumerate() {
if p == Value::Undefined {
break;
}
if idx == len.saturating_sub(1) {
if is_set {
let set = obj
.as_object_mut()
.map_err(|_| anyhow!(span.error("previous value is not an object")))?
.entry(p)
.or_insert(Value::new_set())
.as_set_mut()
.map_err(|_| anyhow!(span.error("previous value is not a set")))?;
set.append(value.as_set_mut()?);
} else {
let obj = obj
.as_object_mut()
.map_err(|_| anyhow!(span.error("previous value is not an object")))?;
match obj.entry(p) {
BTreeMapEntry::Vacant(v) => {
if value != Value::Undefined {
v.insert(value);
} else {
v.insert(Value::new_object());
}
}
BTreeMapEntry::Occupied(o) => {
if o.get() != &value && value != Value::Undefined {
bail!(span
.error("complete rules should not produce multiple outputs"))
}
}
}
}
break;
} else {
obj = obj
.as_object_mut()
.map_err(|_| anyhow!(span.error("previous value is not an object")))?
.entry(p)
.or_insert(Value::new_object());
}
}
Ok(())
}
fn is_constant_ref(mut expr: &Ref<Expr>) -> Result<bool> {
loop {
match expr.as_ref() {
Expr::Var { .. } => break,
Expr::RefDot { refr, .. } => expr = refr,
Expr::RefBrack { refr, index, .. } if Self::is_simple_literal(index)? => {
expr = refr;
}
_ => return Ok(false),
}
}
Ok(true)
}
fn is_simple_literal(expr: &Ref<Expr>) -> Result<bool> {
Ok(matches!(
expr.as_ref(),
Expr::String { .. }
| Expr::RawString { .. }
| Expr::Bool { .. }
| Expr::Null { .. }
| Expr::Number { .. }
))
}
fn is_constant_output(key_expr: &Option<Ref<Expr>>, output_expr: &Ref<Expr>) -> Result<bool> {
let is_const = if let Some(key_expr) = key_expr {
Self::is_simple_literal(key_expr)?
} else {
true
};
Ok(is_const && Self::is_simple_literal(output_expr)?)
}
fn eval_output_expr_in_loop(&mut self, loops: &[HoistedLoop]) -> Result<bool> {
self.check_execution_time()?;
if loops.is_empty() {
let (key_expr, output_expr) = self.get_exprs_from_context()?;
let ctx = self.get_current_context()?;
let (is_set, is_old_style_set, is_rule, constness_determined) = (
ctx.is_set,
ctx.is_old_style_set,
!ctx.is_compr,
ctx.output_constness_determined,
);
if let Some(rule_ref) = ctx.rule_ref.clone() {
let mut is_const_rule = if is_rule && !constness_determined {
Self::is_constant_ref(&rule_ref)?
} else {
false
};
let mut comps = self.eval_rule_ref(&rule_ref)?;
if let Some(ke) = &key_expr {
comps.push(self.eval_expr(ke)?);
}
let output = if let Some(oe) = &output_expr {
is_const_rule = is_const_rule && Self::is_constant_output(&key_expr, oe)?;
self.eval_expr(oe)?
} else if is_old_style_set && !comps.is_empty() {
let output = comps
.last()
.cloned()
.ok_or_else(|| anyhow!("internal error: missing comps output"))?;
comps.pop();
output
} else {
Value::Bool(true)
};
let comps_defined = comps.iter().all(|v| v != &Value::Undefined);
let ctx_mut = self.get_current_context_mut()?;
if is_const_rule {
ctx_mut.early_return = true;
}
if is_rule {
ctx_mut.output_constness_determined = true;
}
if output == Value::Undefined || !comps_defined {
ctx_mut.rule_value = Value::Undefined;
return Ok(false);
}
if is_set {
let set = ctx_mut
.rule_value
.as_object_mut()?
.entry(Value::from_array(comps))
.or_insert(Value::new_set());
if output != Value::Undefined {
set.as_set_mut()?.insert(output);
return Ok(true);
}
return Ok(false);
}
match ctx_mut
.rule_value
.as_object_mut()?
.entry(Value::from_array(comps))
{
BTreeMapEntry::Vacant(v) => {
v.insert(output);
}
BTreeMapEntry::Occupied(o) if o.get() != &output => bail!(rule_ref
.span()
.error("rules must not produce multiple outputs")),
_ => {
}
}
return Ok(true);
}
match (key_expr, output_expr) {
(Some(ke), Some(oe)) => {
let key = self.eval_expr(&ke)?;
let value = self.eval_expr(&oe)?;
let ctx_mut = self.get_current_context_mut()?;
if key != Value::Undefined && value != Value::Undefined {
let map = ctx_mut.value.as_object_mut()?;
match map.get(&key) {
Some(pv) if *pv != value => {
let span = ke.span();
return Err(span.source.error(
span.line,
span.col,
format!(
"value for key `{}` generated multiple times: `{}` and `{}`",
serde_json::to_string_pretty(&key).map_err(anyhow::Error::msg)?,
serde_json::to_string_pretty(&pv).map_err(anyhow::Error::msg)?,
serde_json::to_string_pretty(&value).map_err(anyhow::Error::msg)?,
)
.as_str(),
));
}
_ => map.insert(key, value),
};
} else {
match &ctx_mut.value {
Value::Object(_) => (),
_ => ctx_mut.value = Value::Undefined,
}
};
}
(None, Some(oe)) => {
let output = self.eval_expr(&oe)?;
let ctx_mut = self.get_current_context_mut()?;
if output != Value::Undefined {
match &mut ctx_mut.value {
Value::Array(a) => {
Rc::make_mut(a).push(output);
}
Value::Set(ref mut s) => {
Rc::make_mut(s).insert(output);
}
a => bail!("internal error: invalid context value {a}"),
}
} else if !ctx_mut.is_compr {
match &ctx_mut.value {
Value::Set(_) => (),
_ => ctx_mut.value = Value::Undefined,
}
}
}
_ => (),
}
let result_opt = {
let current_ctx = self.get_current_context_mut()?;
current_ctx.result.clone()
};
if let Some(mut current_result) = result_opt {
if let Some(scope) = self.scopes.last() {
for (name, value) in scope.iter() {
current_result
.bindings
.as_object_mut()?
.insert(Value::String(name.to_string().into()), value.clone());
}
}
if current_result.expressions.len() == 1 || current_result .expressions
.iter()
.all(|v| v.value != Value::Undefined && v.value != Value::Bool(false))
&& !current_result.expressions.is_empty()
{
let ctx_mut = self.get_current_context_mut()?;
ctx_mut.results.result.push(current_result);
}
}
return Ok(true);
}
let (loop_info, loop_tail) = loops
.split_first()
.ok_or_else(|| anyhow!("internal error: expected loop info"))?;
let mut result = false;
let loop_target_expr = Self::loop_assignment_expr(loop_info);
match self.eval_expr(Self::loop_collection_expr(loop_info))? {
Value::Array(items) => {
for v in items.iter() {
self.set_loop_var_value(loop_target_expr, v.clone())?;
result = self.eval_output_expr_in_loop(loop_tail)? || result;
}
}
Value::Set(items) => {
for v in items.iter() {
self.set_loop_var_value(loop_target_expr, v.clone())?;
result = self.eval_output_expr_in_loop(loop_tail)? || result;
}
}
Value::Object(obj) => {
for (_, v) in obj.iter() {
self.set_loop_var_value(loop_target_expr, v.clone())?;
result = self.eval_output_expr_in_loop(loop_tail)? || result;
}
}
_ => {
let span = Self::loop_span(loop_info);
return Err(span
.source
.error(span.line, span.col, "item cannot be indexed"));
}
}
self.remove_loop_var_value(loop_target_expr);
Ok(result)
}
fn get_current_context(&self) -> Result<&Context> {
match self.contexts.last() {
Some(ctx) => Ok(ctx),
_ => bail!("internal error: no active context found"),
}
}
fn get_current_context_mut(&mut self) -> Result<&mut Context> {
match self.contexts.last_mut() {
Some(ctx) => Ok(ctx),
_ => bail!("internal error: no active context found"),
}
}
fn get_exprs_from_context(&self) -> Result<ContextExprs> {
let ctx = self.get_current_context()?;
Ok((ctx.key_expr.clone(), ctx.output_expr.clone()))
}
fn eval_output_expr(&mut self) -> Result<bool> {
self.check_execution_time()?;
let (key_expr, output_expr) = self.get_exprs_from_context()?;
let mut loops: Vec<HoistedLoop> = Vec::new();
if let Some(ke) = &key_expr {
match self
.compiled_policy
.loop_hoisting_table
.get_expr_loops(self.current_module_index, ke.as_ref().eidx())
.map_err(|err| anyhow!("loop hoisting table out of bounds: {err}"))?
{
Some(hoisted_loops) => {
loops.extend(hoisted_loops.clone());
}
None => {
bail!(ke.span().error("Loop hoisting information not found for key expression. This is likely a bug in the compilation phase."));
}
}
}
if let Some(oe) = &output_expr {
match self
.compiled_policy
.loop_hoisting_table
.get_expr_loops(self.current_module_index, oe.as_ref().eidx())
.map_err(|err| anyhow!("loop hoisting table out of bounds: {err}"))?
{
Some(hoisted_loops) => {
loops.extend(hoisted_loops.clone());
}
None => {
bail!(oe.span().error("Loop hoisting information not found for output expression. This is likely a bug in the compilation phase."));
}
}
}
let r = self.eval_output_expr_in_loop(&loops[..])?;
let ctx = self.get_current_context()?;
ctx.output_expr
.as_ref()
.map_or_else(|| Ok(r), |_oe| Ok(ctx.rule_value != Value::Undefined))
}
fn eval_stmts(&mut self, stmts: &[&LiteralStmt]) -> Result<bool> {
let mut eval_success = true;
for (idx, stmt) in stmts.iter().enumerate() {
self.memory_check()?;
if !eval_success {
break;
}
let loop_exprs: Vec<HoistedLoop> = match self
.compiled_policy
.loop_hoisting_table
.get_statement_loops(self.current_module_index, stmt.sidx)
.map_err(|err| anyhow!("loop hoisting table out of bounds: {err}"))?
{
Some(hoisted_loops) => {
hoisted_loops.clone()
}
None => {
bail!(stmt.span.error("Loop hoisting information not found for statement. This is likely a bug in the compilation phase."));
}
};
if !loop_exprs.is_empty() {
let remaining = stmts.get(idx..).unwrap_or(&[]);
return self.eval_stmts_in_loop(remaining, &loop_exprs[..]);
}
let tail = idx
.checked_add(1)
.and_then(|n| stmts.get(n..))
.unwrap_or(&[]);
eval_success = self.eval_stmt(stmt, tail)?;
if matches!(&stmt.literal, Literal::SomeIn { .. }) {
return Ok(eval_success);
}
}
if eval_success {
eval_success = self.eval_output_expr()?;
} else {
let result_opt = {
let ctx = self.get_current_context_mut()?;
ctx.result.clone()
};
if let Some(mut gathered_result) = result_opt {
if let Some(scope) = self.scopes.last() {
for (name, value) in scope.iter() {
gathered_result
.bindings
.as_object_mut()?
.insert(Value::String(name.to_string().into()), value.clone());
}
}
if gathered_result.expressions.len() == 1 || gathered_result .expressions
.iter()
.all(|v| v.value != Value::Undefined && v.value != Value::Bool(false))
&& !gathered_result.expressions.is_empty()
{
let ctx = self.get_current_context_mut()?;
ctx.results.result.push(gathered_result);
}
}
}
Ok(eval_success)
}
fn eval_query(&mut self, query: &Ref<Query>) -> Result<bool> {
self.check_execution_time()?;
self.scopes.push(Scope::new());
let order_indices = {
let query_module_index = u32::try_from(self.compiled_policy.modules.len())
.map_err(|err| anyhow!("query module index overflow: {err}"))?;
if self.current_module_index == query_module_index {
let schedule = match self.query_schedule.as_ref() {
Some(s) => s
.queries
.get_checked(query_module_index, query.qidx)
.map_err(|err| anyhow!("schedule out of bounds: {err}"))?,
None => None,
};
match schedule {
Some(schedule) => Some(&schedule.order),
None => {
if self.query_schedule.is_some() {
bail!(query
.span
.error("statements not scheduled in query {query:?}"));
}
None
}
}
} else {
let schedule = match self.compiled_policy.schedule.as_ref() {
Some(s) => s
.queries
.get_checked(self.current_module_index, query.qidx)
.map_err(|err| anyhow!("schedule out of bounds: {err}"))?,
None => None,
};
match schedule {
Some(schedule) => Some(&schedule.order),
None => {
if self.compiled_policy.schedule.is_some() {
bail!(query
.span
.error("statements not scheduled in query {query:?}"));
}
None
}
}
}
};
let ordered_stmts: Vec<&LiteralStmt> = match order_indices {
Some(order) => {
let stmts_len = query.stmts.len();
if order.len() != stmts_len {
let msg = format!(
"invalid schedule: expected {stmts_len} statement indices, found {}",
order.len()
);
bail!(query.span.error(msg.as_str()));
}
let mut ordered = Vec::with_capacity(stmts_len);
for idx in order {
let stmt_idx = usize::from(*idx);
let stmt = query.stmts.get(stmt_idx).ok_or_else(|| {
let msg = format!(
"invalid schedule index {stmt_idx} for {} statements",
stmts_len
);
query.span.error(msg.as_str())
})?;
ordered.push(stmt);
}
ordered
}
None => query.stmts.iter().collect(),
};
let r = self.eval_stmts(&ordered_stmts);
self.scopes.pop();
r
}
fn eval_array(&mut self, items: &Vec<ExprRef>) -> Result<Value> {
self.check_execution_time()?;
let mut array = Vec::new();
for item in items {
let term = self.eval_expr(item)?;
if term == Value::Undefined {
return Ok(Value::Undefined);
}
array.push(term);
}
Ok(Value::from_array(array))
}
fn eval_object(&mut self, fields: &Vec<(Span, ExprRef, ExprRef)>) -> Result<Value> {
self.check_execution_time()?;
let mut object = BTreeMap::new();
for (_, key, value) in fields {
let key = self.eval_expr(key)?;
if key == Value::Undefined {
return Ok(Value::Undefined);
}
let value = self.eval_expr(value)?;
if value == Value::Undefined {
return Ok(Value::Undefined);
}
object.insert(key, value);
}
Ok(Value::from_map(object))
}
fn eval_set(&mut self, items: &Vec<ExprRef>) -> Result<Value> {
self.check_execution_time()?;
let mut set = BTreeSet::new();
for item in items {
let term = self.eval_expr(item)?;
if term == Value::Undefined {
return Ok(Value::Undefined);
}
set.insert(term);
}
Ok(Value::from_set(set))
}
fn eval_membership(
&mut self,
key: &Option<ExprRef>,
value: &ExprRef,
collection: &ExprRef,
) -> Result<Value> {
self.check_execution_time()?;
let value = self.eval_expr(value)?;
let collection = self.eval_expr(collection)?;
let result = match &collection {
Value::Array(array) => {
if let Some(key) = key {
let key = self.eval_expr(key)?;
collection[&key] == value
} else {
array.contains(&value)
}
}
Value::Object(object) => {
if let Some(key) = key {
let key = self.eval_expr(key)?;
collection[&key] == value
} else {
object.values().any(|item| *item == value)
}
}
Value::Set(set) => {
if key.is_some() {
false
} else {
set.contains(&value)
}
}
_ => {
false
}
};
Ok(Value::Bool(result))
}
fn eval_array_compr(&mut self, term: &ExprRef, query: &Ref<Query>) -> Result<Value> {
self.check_execution_time()?;
self.contexts.push(Context {
output_expr: Some(term.clone()),
value: Value::new_array(),
is_compr: true,
..Context::default()
});
self.eval_query(query)?;
match self.contexts.pop() {
Some(ctx) => Ok(ctx.value),
None => bail!("internal error: context already popped"),
}
}
fn eval_set_compr(&mut self, term: &ExprRef, query: &Ref<Query>) -> Result<Value> {
self.check_execution_time()?;
self.contexts.push(Context {
output_expr: Some(term.clone()),
value: Value::new_set(),
is_compr: true,
..Context::default()
});
self.eval_query(query)?;
match self.contexts.pop() {
Some(ctx) => Ok(ctx.value),
None => bail!("internal error: context already popped"),
}
}
fn eval_object_compr(
&mut self,
key: &ExprRef,
value: &ExprRef,
query: &Ref<Query>,
) -> Result<Value> {
self.check_execution_time()?;
self.contexts.push(Context {
key_expr: Some(key.clone()),
output_expr: Some(value.clone()),
value: Value::new_object(),
is_compr: true,
..Context::default()
});
self.eval_query(query)?;
match self.contexts.pop() {
Some(ctx) => Ok(ctx.value),
None => bail!("internal error: context already popped"),
}
}
fn lookup_function_by_name(&self, path: &str) -> Option<(&Vec<Ref<Rule>>, &Ref<Module>)> {
let mut path = path.to_owned();
if !path.starts_with("data.") {
path = format!("{}.{}", self.current_module_path, path);
}
match self.compiled_policy.functions.get(&path) {
Some((f, _, m)) => Some((f, m)),
_ => None,
}
}
fn eval_builtin_call(
&mut self,
span: &Span,
name: &str,
builtin: builtins::BuiltinFcn,
params: &[ExprRef],
args: Vec<Value>,
) -> Result<Value> {
self.check_execution_time()?;
if args.iter().any(|a| a == &Value::Undefined) {
return Ok(Value::Undefined);
}
let cache = builtins::must_cache(name);
if let Some(cached_key) = &cache {
if let Some(v) = self.builtins_cache.get(&(cached_key, args.clone())) {
return Ok(v.clone());
}
}
let v = match builtin.0(
span,
params,
&args[..],
self.compiled_policy.strict_builtin_errors,
) {
Ok(v) => v,
Err(_) if !self.compiled_policy.strict_builtin_errors => return Ok(Value::Undefined),
Err(e) => Err(e)?,
};
if name == "trace" {
if let (Some(traces), Value::String(msg)) = (&mut self.traces, &v) {
traces.push(msg.clone());
self.memory_check()?;
return Ok(Value::Bool(true));
}
}
if let Some(cached_key) = cache {
self.builtins_cache.insert((cached_key, args), v.clone());
}
self.memory_check()?;
Ok(v)
}
#[allow(unused_variables)]
fn lookup_builtin(span: &Span, path: &str) -> Result<Option<&'static BuiltinFcn>> {
if let Some(builtin) = builtins::BUILTINS.get(path) {
return Ok(Some(builtin));
}
Ok(None)
}
fn is_builtin(span: &Span, path: &str) -> bool {
path == "print" || matches!(Self::lookup_builtin(span, path), Ok(Some(_)))
}
fn to_printable(value: &Value, s: &mut String) {
match value {
Value::Array(array) => {
s.push('[');
for (idx, e) in array.iter().enumerate() {
if idx > 0 {
s.push_str(", ");
}
Self::to_printable(e, s);
}
s.push(']');
}
Value::Set(set) => {
s.push('{');
for (idx, e) in set.iter().enumerate() {
if idx > 0 {
s.push_str(", ");
}
Self::to_printable(e, s);
}
s.push('}');
}
Value::Object(map) => {
s.push('{');
for (idx, (k, entry_value)) in map.iter().enumerate() {
if idx > 0 {
s.push_str(", ");
}
Self::to_printable(k, s);
s.push_str(": ");
Self::to_printable(entry_value, s);
}
s.push('}');
}
other => s.push_str(&format!("{other}")),
}
}
fn eval_print(&mut self, span: &Span, params: &[ExprRef], args: Vec<Value>) -> Result<Value> {
const MAX_ARGS: u8 = 100;
if args.len() > usize::from(MAX_ARGS) {
bail!(span.error(&format!("print supports upto {MAX_ARGS} arguments")));
}
#[cfg(not(feature = "std"))]
if !self.gather_prints {
return Ok(Value::Bool(true));
}
let mut msg = String::default();
for (i, p) in params.iter().enumerate() {
if i > 0 {
msg.push(' ');
}
match self.eval_expr(p)? {
Value::Undefined => msg.push_str("<undefined>"),
Value::String(s) => msg.push_str(&format!("{s}")),
a => Self::to_printable(&a, &mut msg),
}
}
if self.gather_prints {
self.prints
.push(format!("{}:{}: {msg}", span.source.file(), span.line));
}
#[cfg(feature = "std")]
if !self.gather_prints {
std::eprintln!("{msg}");
}
Ok(Value::Bool(true))
}
fn eval_call_impl(
&mut self,
span: &Span,
expr: &ExprRef,
fcn: &ExprRef,
params: &[ExprRef],
) -> Result<Value> {
self.check_execution_time()?;
if let Some(v) = self.get_loop_var_value(expr)? {
return Ok(v.clone());
}
let fcn_path = match get_path_string(fcn, None) {
Ok(p) => p,
_ => bail!(span.error("invalid function expression")),
};
let mut param_values = Vec::with_capacity(params.len());
for p in params {
param_values.push(self.eval_expr(p)?);
}
let orig_fcn_path = fcn_path.clone();
let mut with_functions_saved = None;
let selected_fcn_path = match self.with_functions.get(&orig_fcn_path) {
Some(FunctionModifier::Function(p)) => {
let p = p.clone();
with_functions_saved = Some(self.with_functions.clone());
self.with_functions.clear();
p
}
Some(FunctionModifier::Value(value_override)) => {
if param_values
.iter()
.any(|evaluated_value| evaluated_value == &Value::Undefined)
{
return Ok(Value::Undefined);
}
return Ok(value_override.clone());
}
_ => orig_fcn_path.clone(),
};
let mut extension = None;
let empty: Vec<Ref<Rule>> = vec![];
let (fcns_rules, fcn_module) = match self.lookup_function_by_name(&selected_fcn_path) {
Some((fcns, m)) => (fcns, Some(m.clone())),
_ => {
if self
.compiled_policy
.default_rules
.contains_key(&selected_fcn_path)
|| self
.compiled_policy
.default_rules
.contains_key(&get_path_string(fcn, Some(&self.current_module_path))?)
{
(&empty, self.module.clone())
}
else if let Some(ext) = self.extensions.get_mut(&selected_fcn_path) {
extension = Some(ext);
(&empty, None)
} else if selected_fcn_path == "print" {
return self.eval_print(span, params, param_values);
}
else if let Some(builtin) = Self::lookup_builtin(span, &selected_fcn_path)? {
let r = self.eval_builtin_call(
span,
&selected_fcn_path.clone(),
*builtin,
params,
param_values,
);
if let Some(with_functions) = with_functions_saved {
self.with_functions = with_functions;
}
return r;
} else {
bail!(
span.error(format!("could not find function {selected_fcn_path}").as_str())
);
}
}
};
if param_values.iter().any(|v| v == &Value::Undefined) {
if let Some(with_functions) = with_functions_saved {
self.with_functions = with_functions;
}
return Ok(Value::Undefined);
}
if let Some((nargs, ext)) = extension {
if param_values.len() != usize::from(*nargs) {
bail!(span.error("incorrect number of parameters supplied to extension"));
}
let r = Rc::make_mut(ext)(param_values);
if let Some(with_functions) = with_functions_saved {
self.with_functions = with_functions;
}
match r {
Ok(v) => return Ok(v),
Err(e) => bail!(span.error(&format!("{e}"))),
}
}
let fcns = fcns_rules.clone();
let mut results: Vec<Value> = Vec::new();
let mut errors: Vec<anyhow::Error> = Vec::new();
'outer: for fcn_rule in fcns {
let (args, output_expr, bodies) = match fcn_rule.as_ref() {
Rule::Spec {
head: RuleHead::Func { args, assign, .. },
bodies,
..
} => (args, assign.as_ref().map(|a| a.value.clone()), bodies),
_ => bail!("internal error not a function"),
};
if args.len() != params.len() {
return Err(span.source.error(
span.line,
span.col,
format!(
"mismatch in number of arguments. supplied {}, expected {}",
params.len(),
args.len()
)
.as_str(),
));
}
let scopes = core::mem::take(&mut self.scopes);
let args_scope = Scope::new();
self.scopes.push(args_scope);
let callee_module_idx = fcn_module
.as_ref()
.map(|module| self.find_module_index(module))
.unwrap_or(self.current_module_index);
for (idx, a) in args.iter().enumerate() {
let module_idx = callee_module_idx;
let expr_idx = a.as_ref().eidx();
let param_value = param_values
.get(idx)
.ok_or_else(|| anyhow!("internal error: missing param value"))?;
let binding_success = if let Some(BindingPlan::Parameter {
destructuring_plan,
..
}) = self
.compiled_policy
.loop_hoisting_table
.get_expr_binding_plan(module_idx, expr_idx)
.map_err(|err| anyhow!("loop hoisting table out of bounds: {err}"))?
.cloned()
{
self.execute_destructuring_plan(&destructuring_plan, param_value)?
== Value::from(true)
} else {
return Err(span.error(&format!(
"binding plan not found for parameter {}",
a.span().text()
)));
};
if !binding_success {
self.scopes = scopes;
continue 'outer;
}
}
let ctx = Context {
output_expr: output_expr.clone(),
value: Value::new_set(),
..Context::default()
};
let prev_module = self.set_current_module(fcn_module.clone())?;
let value = match self.eval_rule_bodies(ctx, span, bodies) {
Ok(v) => v,
Err(e) => {
errors.push(e);
self.scopes = scopes;
continue;
}
};
self.set_current_module(prev_module)?;
let result = match &value {
Value::Set(s) if s.len() == 1 => {
let Some(v) = s.iter().next() else {
return Err(anyhow!("internal error: expected single value in set"));
};
v.clone()
}
Value::Set(s) if !s.is_empty() => {
return Err(span.source.error(
span.line,
span.col,
format!("function produced multiple outputs {value:?}").as_str(),
))
}
Value::Set(s) if s.is_empty() && output_expr.is_none() => Value::Bool(true),
Value::Set(s) if s.is_empty() => Value::Undefined,
Value::Undefined => Value::Undefined,
v => v.clone(),
};
self.scopes = scopes;
if result != Value::Undefined {
results.push(result);
}
}
if self.compiled_policy.strict_builtin_errors && !errors.is_empty() {
return Err(errors
.first()
.map(|e| anyhow!(e.to_string()))
.unwrap_or_else(|| anyhow!("internal error: missing error entry")));
}
if results.is_empty() {
let scopes = core::mem::take(&mut self.scopes);
if errors.is_empty() {
let rules = match self
.compiled_policy
.default_rules
.get(&selected_fcn_path)
.cloned()
{
Some(rules) => Some(rules),
None => {
let alt_fcn_path = get_path_string(fcn, Some(&self.current_module_path))?;
self.compiled_policy
.default_rules
.get(&alt_fcn_path)
.cloned()
}
};
if let Some(rules) = rules {
for (rule, _) in rules.iter() {
if let Rule::Default { value, .. } = rule.as_ref() {
match self.eval_expr(value) {
Ok(v) => results.push(v),
Err(e) => errors.push(e),
}
}
}
}
}
self.scopes = scopes;
}
if let Some(with_functions) = with_functions_saved {
self.with_functions = with_functions;
}
if results.is_empty() {
if errors.is_empty() {
return Ok(Value::Undefined);
} else {
return Err(errors
.first()
.map(|e| anyhow!(e.to_string()))
.unwrap_or_else(|| anyhow!("internal error: missing error entry")));
}
}
if results.windows(2).any(|w| matches!(w, [a, b] if a != b)) {
return Err(span.source.error(
span.line,
span.col,
"functions must not produce multiple outputs for same inputs",
));
}
results
.first()
.cloned()
.ok_or_else(|| anyhow!("internal error: expected function result"))
}
fn eval_call(
&mut self,
span: &Span,
expr: &ExprRef,
fcn: &ExprRef,
params: &[ExprRef],
extra_arg: Option<ExprRef>,
allow_return_arg: bool,
) -> Result<Value> {
self.check_execution_time()?;
if extra_arg.is_some() {
let (last_param, arg_prefix) = params
.split_last()
.ok_or_else(|| anyhow!("internal error: expected at least one param"))?;
let value = self.eval_call_impl(span, expr, fcn, arg_prefix)?;
if allow_return_arg {
let module_idx = self.current_module_index;
let expr_idx = last_param.as_ref().eidx();
if let Some(BindingPlan::Parameter {
destructuring_plan, ..
}) = self
.compiled_policy
.loop_hoisting_table
.get_expr_binding_plan(module_idx, expr_idx)
.map_err(|err| anyhow!("loop hoisting table out of bounds: {err}"))?
.cloned()
{
let result = self.execute_destructuring_plan(&destructuring_plan, &value)?;
Ok(result)
} else {
Err(span.error(&format!(
"binding plan not found for parameter {}",
last_param.span().text()
)))
}
} else {
let expected = self.eval_expr(last_param)?;
Ok(Value::Bool(value == expected))
}
} else {
self.eval_call_impl(span, expr, fcn, params)
}
}
fn lookup_local_var(&self, name: &SourceStr) -> Option<Value> {
for scope in self.scopes.iter().rev() {
if let Some(v) = scope.get(name) {
return Some(v.clone());
}
}
None
}
fn ensure_module_evaluated(&mut self, path: String) -> Result<()> {
self.check_execution_time()?;
for module in self.compiled_policy.modules.clone().iter().cloned() {
if Some(&module) == self.module.as_ref() {
continue;
}
let module_path_str = get_path_string(&module.package.refr, Some("data"))?;
let has_dot_after_prefix = module_path_str
.get(path.len()..)
.is_some_and(|suffix| suffix.starts_with('.'));
if module_path_str.starts_with(&path)
&& (module_path_str.len() == path.len() || has_dot_after_prefix)
{
let module_path_components = Parser::get_path_ref_components(&module.package.refr)?;
let module_path_components: Vec<&str> =
module_path_components.iter().map(|s| s.text()).collect();
let vref = Self::make_or_get_value_mut(&mut self.data, &module_path_components)?;
if *vref == Value::Undefined {
*vref = Value::new_object();
}
for rule in &module.policy {
if !self.processed.contains(rule) {
self.eval_rule(&module, rule)?;
}
}
let prev_module = self.set_current_module(Some(module.clone()))?;
for rule in &module.policy {
if !self.processed.contains(rule) {
self.eval_default_rule(rule)?;
}
}
self.set_current_module(prev_module)?;
self.mark_processed(&module_path_components)?;
}
}
Ok(())
}
fn ensure_rule_evaluated(&mut self, path: String) -> Result<()> {
self.check_execution_time()?;
let mut matched = false;
if let Some(rules) = self.compiled_policy.rules.get(&path) {
matched = true;
for r in rules.clone() {
if !self.processed.contains(&r) {
let module = self.get_rule_module(&r)?;
self.eval_rule(&module, &r)?;
}
}
}
if let Some(rules) = self.compiled_policy.default_rules.get(&path) {
matched = true;
for (r, _) in rules.clone() {
if !self.processed.contains(&r) {
let module = self.get_rule_module(&r)?;
let prev_module = self.set_current_module(Some(module))?;
self.eval_default_rule(&r)?;
self.set_current_module(prev_module)?;
}
}
}
if matched {
let comps: Vec<&str> = path.split('.').collect();
if let Some((_, tail)) = comps.split_first() {
self.mark_processed(tail)?;
}
}
Ok(())
}
fn is_processed(&self, path: &[&str]) -> Result<bool> {
let mut obj = &self.processed_paths;
for p in path {
if obj[&Value::Undefined] == Value::Null {
return Ok(true);
}
match &obj[*p] {
Value::Undefined => return Ok(false),
v => obj = v,
}
}
Ok(obj[&Value::Undefined] == Value::Null)
}
fn mark_processed(&mut self, path: &[&str]) -> Result<()> {
let obj = self.processed_paths.make_or_get_value_mut(path)?;
if obj == &Value::Undefined {
*obj = Value::new_object();
}
obj.as_object_mut()?.insert(Value::Undefined, Value::Null);
Ok(())
}
fn lookup_var(&mut self, span: &Span, fields: &[&str], no_error: bool) -> Result<Value> {
let name = span.source_str();
if let Some(v) = self.lookup_local_var(&name) {
return Ok(Self::get_value_chained(v, fields));
}
if name.text() == "input" {
return Ok(Self::get_value_chained(self.input.clone(), fields));
}
if self.no_rules_lookup {
if no_error {
return Ok(Value::Undefined);
}
return Err(span.error("undefined var"));
}
if name.text() == "data" {
if self.is_processed(fields)? {
return Ok(Self::get_value_chained(self.data.clone(), fields));
}
if fields.is_empty() && self.active_rules.is_empty() {
for module in self.compiled_policy.modules.clone().iter() {
for rule in &module.policy {
self.eval_rule(module, rule)?;
}
}
}
let requested_path = format!("data.{}", fields.join("."));
self.ensure_module_evaluated(requested_path.clone())?;
for i in (1..=fields.len()).rev() {
let prefix = fields.iter().take(i).copied().collect::<Vec<_>>();
let prefix_path = format!("data.{}", prefix.join("."));
if self.compiled_policy.rules.contains_key(&prefix_path)
|| self
.compiled_policy
.default_rules
.contains_key(&prefix_path)
{
self.ensure_rule_evaluated(prefix_path)?;
break;
}
}
Ok(Self::get_value_chained(self.data.clone(), fields))
} else if !self.compiled_policy.modules.is_empty() {
let module = self.current_module()?;
let parsed_path = Parser::get_path_ref_components(&module.package.refr)?;
let mut module_var_path: Vec<&str> = parsed_path.iter().map(|s| s.text()).collect();
module_var_path.push(name.text());
if self.is_processed(&module_var_path)? {
let value = Self::get_value_chained(self.data.clone(), &module_var_path);
return Ok(Self::get_value_chained(value, fields));
}
let rule_path = format!("data.{}", module_var_path.join("."));
if !no_error
&& !self.compiled_policy.rules.contains_key(&rule_path)
&& !self.compiled_policy.default_rules.contains_key(&rule_path)
&& !self.compiled_policy.imports.contains_key(&rule_path)
{
bail!(span.error(&format!(
"var {} is unsafe (path {:?}, scopes {:?})",
name.text(),
module_var_path,
self.scopes
)));
}
let mut found = false;
for i in (0..=fields.len()).rev() {
let comps = fields.iter().take(i).copied().collect::<Vec<_>>();
let path = if comps.is_empty() {
rule_path.clone()
} else {
format!("{}.{}", rule_path, comps.join("."))
};
if self.compiled_policy.rules.contains_key(&path)
|| self.compiled_policy.default_rules.contains_key(&path)
{
self.ensure_rule_evaluated(path)?;
found = true;
break;
}
}
if !found {
if let Some(imported_var) = self.compiled_policy.imports.get(&rule_path).cloned() {
return Ok(Self::get_value_chained(
self.eval_expr(&imported_var)?,
fields,
));
}
}
let value = Self::get_value_chained(self.data.clone(), &module_var_path[..]);
Ok(Self::get_value_chained(value, fields))
} else {
Ok(Value::Undefined)
}
}
fn eval_expr(&mut self, expr: &ExprRef) -> Result<Value> {
self.check_execution_time()?;
#[cfg(feature = "coverage")]
if self.enable_coverage {
let span = expr.span();
let source = &span.source;
let line = usize::try_from(span.line).unwrap_or(usize::MAX);
if line > 0 {
match self.coverage.get_mut(source) {
Some(c) => {
let needed = line.saturating_add(1);
if c.len() < needed {
c.resize(needed, false);
}
if let Some(slot) = c.get_mut(line) {
*slot = true;
}
}
_ => {
let size = line.saturating_add(1);
let mut c = vec![false; size];
if let Some(slot) = c.get_mut(line) {
*slot = true;
}
self.coverage.insert(source.clone(), c);
}
}
}
}
match expr.as_ref() {
Expr::Null { value: v, .. }
| Expr::Bool { value: v, .. }
| Expr::Number { value: v, .. } => Ok(v.clone()),
Expr::String { value: v, .. } => Ok(v.clone()),
Expr::RawString { value: v, .. } => Ok(v.clone()),
Expr::Var { .. } => self.eval_chained_ref_dot_or_brack(expr),
Expr::RefDot { .. } => self.eval_chained_ref_dot_or_brack(expr),
Expr::RefBrack { .. } => self.eval_chained_ref_dot_or_brack(expr),
Expr::ArithExpr { op, lhs, rhs, .. } => self.eval_arith_expr(expr.span(), op, lhs, rhs),
Expr::AssignExpr { .. } => {
let module_idx = self.current_module_index;
let expr_idx = expr.as_ref().eidx();
let expr_text = expr.span().text().to_string();
let binding_plan = self
.compiled_policy
.loop_hoisting_table
.get_expr_binding_plan(module_idx, expr_idx)
.map_err(|err| anyhow!("loop hoisting table out of bounds: {err}"))?
.cloned()
.ok_or_else(|| {
expr.span().error(
format!(
"binding plan missing for assignment expression (module_idx={module_idx}, expr_idx={expr_idx}, expr='{expr_text}')"
)
.as_str(),
)
})?;
let BindingPlan::Assignment { plan } = binding_plan else {
bail!(expr.span().error("internal error: not an assignment plan"));
};
self.execute_assignment_plan(&plan)
}
Expr::BinExpr { op, lhs, rhs, .. } => self.eval_bin_expr(op, lhs, rhs),
Expr::BoolExpr { op, lhs, rhs, .. } => self.eval_bool_expr(op, lhs, rhs),
Expr::Membership {
key,
value,
collection,
..
} => self.eval_membership(key, value, collection),
#[cfg(feature = "rego-extensions")]
Expr::OrExpr { lhs, rhs, .. } => {
let lhs = self.eval_expr(lhs)?;
match lhs {
Value::Bool(false) | Value::Null | Value::Undefined => self.eval_expr(rhs),
_ => Ok(lhs),
}
}
Expr::Array { items, .. } => self.eval_array(items),
Expr::Object { fields, .. } => self.eval_object(fields),
Expr::Set { items, .. } => self.eval_set(items),
Expr::ArrayCompr { term, query, .. } => self.eval_array_compr(term, query),
Expr::ObjectCompr {
key, value, query, ..
} => self.eval_object_compr(key, value, query),
Expr::SetCompr { term, query, .. } => self.eval_set_compr(term, query),
Expr::UnaryExpr {
span, expr: uexpr, ..
} => match uexpr.as_ref() {
Expr::Number { .. } if !uexpr.span().text().starts_with('-') => {
builtins::numbers::arithmetic_operation(
span,
&ArithOp::Sub,
expr,
uexpr,
Value::from(0),
self.eval_expr(uexpr)?,
self.compiled_policy.strict_builtin_errors,
)
}
_ => bail!(expr
.span()
.error("unary - can only be used with numeric literals")),
},
Expr::Call {
span, fcn, params, ..
} => self.eval_call(span, expr, fcn, params, None, false),
}
}
fn make_rule_context(&self, head: &RuleHead) -> Result<(Context, Vec<Span>)> {
let module = self.current_module()?;
let mut path = Parser::get_path_ref_components(&module.package.refr)?;
match head {
RuleHead::Compr { refr, assign, .. } => {
let output_expr = assign.as_ref().map(|assign| assign.value.clone());
let (refr, key_expr, value) = match refr.as_ref() {
Expr::RefBrack { refr, index, .. } => {
(refr, Some(index.clone()), Value::new_object())
}
_ => (refr, None, Value::new_array()),
};
Parser::get_path_ref_components_into(refr, &mut path)?;
Ok((
Context {
key_expr,
output_expr,
value,
rule_ref: Some(refr.clone()),
..Context::default()
},
path,
))
}
RuleHead::Set { refr, key, .. } => {
Parser::get_path_ref_components_into(refr, &mut path)?;
let is_old_style_set = key.is_none();
Ok((
Context {
output_expr: key.clone(),
value: Value::new_set(),
rule_ref: Some(refr.clone()),
is_set: true,
is_old_style_set,
..Context::default()
},
path,
))
}
other => bail!("internal error: unhandled rule ref type: {other:?}"),
}
}
fn get_rule_module(&self, rule: &Ref<Rule>) -> Result<Ref<Module>> {
for m in self.compiled_policy.modules.iter() {
if m.policy.iter().any(|r| r == rule) {
return Ok(m.clone());
}
}
bail!("internal error: could not find module for rule");
}
fn eval_rule_bodies(
&mut self,
ctx: Context,
span: &Span,
bodies: &[RuleBody],
) -> Result<Value> {
self.check_execution_time()?;
let n_scopes = self.scopes.len();
let result = if bodies.is_empty() {
self.contexts.push(ctx.clone());
self.eval_output_expr()
} else {
let mut result = Ok(true);
for (idx, body) in bodies.iter().enumerate() {
if idx == 0 {
self.contexts.push(ctx.clone());
} else {
self.contexts.pop();
let output_expr = body.assign.as_ref().map(|e| e.value.clone());
self.contexts.push(Context {
output_expr,
..ctx.clone()
});
}
result = self.eval_query(&body.query);
if matches!(&result, Ok(true) | Err(_)) {
break;
}
}
result
};
let popped_ctx = match self.contexts.pop() {
Some(current_ctx) => current_ctx,
_ => bail!("internal error: rule's context already popped"),
};
let result = result?;
if self.scopes.len() != n_scopes {
return Err(anyhow!("internal error: scope leak after eval_rule_bodies"));
}
if popped_ctx.rule_ref.is_some() {
if result {
return Ok(popped_ctx.rule_value);
} else {
return Ok(Value::Undefined);
}
}
Ok(match result {
true => match &popped_ctx.value {
Value::Object(_) => popped_ctx.value,
Value::Array(a) if a.len() == 1 => a
.first()
.cloned()
.ok_or_else(|| anyhow!("internal error: expected array element"))?,
Value::Array(a) if a.is_empty() => Value::Bool(true),
Value::Array(_) => {
return Err(span.source.error(
span.line,
span.col,
"complete rules should not produce multiple outputs",
))
}
Value::Set(_) => popped_ctx.value,
_ => {
return Err(anyhow!(
"internal error: unexpected ctx.value for rule evaluation: {:?}",
popped_ctx.value
));
}
},
false => Value::Undefined,
})
}
fn get_value_chained(mut obj: Value, path: &[&str]) -> Value {
for p in path {
obj = obj[&Value::String(p.to_string().into())].clone();
}
obj
}
#[inline]
pub fn make_or_get_value_mut<'a>(obj: &'a mut Value, paths: &[&str]) -> Result<&'a mut Value> {
let (first, tail) = match paths.split_first() {
Some(v) => v,
None => return Ok(obj),
};
let key = Value::String((*first).into());
if obj == &Value::Undefined {
*obj = Value::new_object();
}
if let Value::Object(map) = obj {
if map.get(&key).is_none() {
Rc::make_mut(map).insert(key.clone(), Value::Undefined);
}
}
match obj {
Value::Object(map) => match Rc::make_mut(map).get_mut(&key) {
Some(v) if tail.is_empty() => Ok(v),
Some(v) => Self::make_or_get_value_mut(v, tail),
_ => bail!("internal error: unexpected"),
},
Value::Undefined if !tail.is_empty() => {
*obj = Value::new_object();
Self::make_or_get_value_mut(obj, paths)
}
Value::Undefined => Ok(obj),
_ => bail!("internal error: make: not an object {obj:?}"),
}
}
pub fn merge_rule_value(span: &Span, value: &mut Value, new: Value) -> Result<()> {
match value.merge(new) {
Ok(()) => Ok(()),
Err(_) => Err(span.error("rules should not produce multiple outputs.")),
}
}
pub fn get_path_string(refr: &Expr, document: Option<&str>) -> Result<String> {
let mut comps = vec![];
let mut expr_opt = Some(refr);
while let Some(expr) = expr_opt {
match expr {
Expr::RefDot {
refr: nested_refr,
field,
..
} => {
comps.push(field.0.text());
expr_opt = Some(nested_refr);
}
Expr::RefBrack {
refr: nested_refr,
index,
..
} if matches!(index.as_ref(), Expr::String { .. }) => {
if let Expr::String { span: s, .. } = index.as_ref() {
comps.push(s.text());
expr_opt = Some(nested_refr);
}
}
Expr::Var { span: v, .. } => {
comps.push(v.text());
expr_opt = None;
}
_ => bail!(expr.span().error("invalid ref expression")),
}
}
if let Some(doc_component) = document {
comps.push(doc_component);
};
comps.reverse();
Ok(comps.join("."))
}
pub fn set_current_module(
&mut self,
module: Option<Ref<Module>>,
) -> Result<Option<Ref<Module>>> {
let previous_module = self.module.clone();
if let Some(new_module) = &module {
self.current_module_path =
Self::get_path_string(&new_module.package.refr, Some("data"))?;
self.current_module_index = self.find_module_index(new_module);
}
self.module = module;
Ok(previous_module)
}
fn find_module_index(&self, module: &Ref<Module>) -> u32 {
self.compiled_policy
.modules
.iter()
.position(|m| core::ptr::eq(m.as_ref(), module.as_ref()))
.map_or_else(
|| {
self.query_module.as_ref().map_or(0, |query_module| {
if core::ptr::eq(query_module.as_ref(), module.as_ref()) {
u32::try_from(self.compiled_policy.modules.len()).unwrap_or(u32::MAX)
} else {
0
}
})
},
|idx| u32::try_from(idx).unwrap_or(u32::MAX),
)
}
const fn get_rule_refr(rule: &Rule) -> &ExprRef {
match rule {
Rule::Spec { head, .. } => match &head {
RuleHead::Compr { refr, .. }
| RuleHead::Set { refr, .. }
| RuleHead::Func { refr, .. } => refr,
},
Rule::Default { refr, .. } => refr,
}
}
fn check_default_value(expr: &ExprRef) -> Result<()> {
use Expr::*;
let (kind, span) = match expr.as_ref() {
String { .. } | RawString { .. } | Number { .. } | Bool { .. } | Null { .. } => {
return Ok(())
}
UnaryExpr { expr, .. } if matches!(expr.as_ref(), Number { .. }) => return Ok(()),
ArrayCompr { .. } | SetCompr { .. } | ObjectCompr { .. } => return Ok(()),
Array { items, .. } | Set { items, .. } => {
for item in items {
Self::check_default_value(item)?;
}
return Ok(());
}
Object { fields, .. } => {
for (_, key, value) in fields {
Self::check_default_value(key)?;
Self::check_default_value(value)?;
}
return Ok(());
}
Var { span, .. } => ("var", span),
Call { span, .. } => ("call", span),
UnaryExpr { span, .. } => ("unaryexpr", span),
RefDot { span, .. } => ("ref", span),
RefBrack { span, .. } => ("ref", span),
BinExpr { span, .. } => ("binexpr", span),
BoolExpr { span, .. } => ("boolexpr", span),
ArithExpr { span, .. } => ("arithexpr", span),
AssignExpr { span, .. } => ("assignexpr", span),
Membership { span, .. } => ("membership", span),
#[cfg(feature = "rego-extensions")]
OrExpr { span, .. } => ("orexpr", span),
};
Err(span.error(format!("invalid `{kind}` in default value").as_str()))
}
pub fn check_default_rules(&self) -> Result<()> {
for module in self.compiled_policy.modules.iter() {
for rule in &module.policy {
if let Rule::Default { value, .. } = rule.as_ref() {
Self::check_default_value(value)?;
}
}
}
Ok(())
}
pub fn eval_default_rule(&mut self, rule: &Ref<Rule>) -> Result<()> {
self.check_execution_time()?;
if self.processed.contains(rule) {
return Ok(());
}
if let Rule::Default {
span,
refr,
value,
args,
..
} = rule.as_ref()
{
if !args.is_empty() {
return Ok(());
}
let scopes = core::mem::take(&mut self.scopes);
let module = self.current_module()?;
let mut path = Parser::get_path_ref_components(&module.package.refr)?;
let (refr, index) = match refr.as_ref() {
Expr::RefBrack { refr, index, .. } => (refr, Some(index.clone())),
Expr::RefDot { .. } => (refr, None),
Expr::Var { .. } => (refr, None),
_ => bail!(refr
.span()
.error(&format!("invalid token {refr:?} with the default keyword"))),
};
Parser::get_path_ref_components_into(refr, &mut path)?;
let paths: Vec<&str> = path.iter().map(|s| s.text()).collect();
Self::check_default_value(value)?;
let value = self.eval_expr(value)?;
if let Some(index) = index {
let index = self.eval_expr(&index)?;
let mut object = Value::new_object();
object.as_object_mut()?.insert(index.clone(), value);
let vref = Self::make_or_get_value_mut(&mut self.data, &paths)?;
if let Value::Object(btree) = &vref {
if !btree.contains_key(&index) {
Self::merge_rule_value(span, vref, object)?;
}
} else if let Value::Undefined = vref {
Self::merge_rule_value(span, vref, object)?;
}
} else {
let vref = Self::make_or_get_value_mut(&mut self.data, &paths)?;
if let Value::Undefined = &vref {
Self::merge_rule_value(span, vref, value)?;
}
};
self.scopes = scopes;
self.processed.insert(rule.clone());
}
Ok(())
}
#[cfg(feature = "rvm")]
pub fn eval_default_rule_for_compiler(&mut self, rule_path: &str) -> Result<Value> {
self.check_execution_time()?;
self.input = Value::Undefined;
self.data = Value::Undefined;
self.ensure_loop_var_values_capacity();
let default_rules = self.compiled_policy.default_rules.get(rule_path).cloned();
if let Some(rules) = default_rules {
for (rule, _) in rules {
for module in self.compiled_policy.modules.iter() {
if module.policy.contains(&rule) {
let prev_module = self.set_current_module(Some(module.clone()))?;
let result = self.eval_default_rule(&rule);
self.set_current_module(prev_module)?;
if result.is_ok() {
let components: Vec<&str> = rule_path.split('.').skip(1).collect();
let value = Self::get_value_chained(self.data.clone(), &components);
if value != Value::Undefined {
return Ok(value);
}
}
return result.map(|_| Value::Undefined);
}
}
}
}
bail!("Could not find default rule for path: {}", rule_path);
}
fn update_data(
&mut self,
span: &Span,
_refr: &Expr,
path: &[&str],
value: Value,
) -> Result<()> {
if value == Value::Undefined {
return Ok(());
}
let vref = Self::make_or_get_value_mut(&mut self.data, path)?;
if Self::get_value_chained(self.init_data.clone(), path) == Value::Undefined {
Self::merge_rule_value(span, vref, value)
} else {
Ok(())
}
}
fn check_rule_path(
&mut self,
refr: &ExprRef,
path: &[Value],
value: &Value,
is_set: bool,
) -> Result<()> {
let range = self.rule_values.range((Unbounded, Included(path.to_vec())));
let mut conflict = None;
for (k, (v, r)) in range.rev() {
if path.starts_with(k) {
if k == path && (is_set || v == value) {
} else {
conflict = Some((v, r));
}
} else {
break;
}
}
if let Some((_, r)) = conflict {
bail!(refr.span().error(&format!(
"rule conflicts with the following rule:\n{}",
r.span().message("", "defined here")
)));
}
self.rule_values
.insert(path.to_vec(), (value.clone(), refr.clone()));
Ok(())
}
fn eval_rule_impl(&mut self, module: &Ref<Module>, rule: &Ref<Rule>) -> Result<()> {
self.check_execution_time()?;
match rule.as_ref() {
Rule::Spec {
span,
head: rule_head,
bodies: rule_body,
} => {
match rule_head {
RuleHead::Compr { refr, .. } | RuleHead::Set { refr, .. } => {
let (ctx, _) = self.make_rule_context(rule_head)?;
let is_set = ctx.is_set;
let is_object = ctx.key_expr.is_some() && !is_set;
let value = self.eval_rule_bodies(ctx, span, rule_body)?;
let package_components = self.eval_rule_ref(&module.package.refr)?;
if value != Value::Undefined {
for (path, value_in_map) in value.as_object()? {
let mut full_path = package_components.clone();
full_path.append(&mut path.as_array()?.clone());
self.check_rule_path(refr, &full_path, value_in_map, is_set)?;
self.update_rule_value(
span,
full_path,
value_in_map.clone(),
is_set,
)?;
}
} else if is_set {
if let Ok(mut comps) = self.eval_rule_ref(refr) {
let mut full_path = package_components;
full_path.append(&mut comps);
self.update_rule_value(span, full_path, Value::new_set(), true)?;
}
} else if is_object {
if let Expr::RefBrack { refr, .. } = refr.as_ref() {
if let Ok(mut comps) = self.eval_rule_ref(refr) {
let mut full_path = package_components;
full_path.append(&mut comps);
self.update_rule_value(
span,
full_path,
Value::Undefined,
false,
)?;
}
}
}
self.processed.insert(rule.clone());
}
RuleHead::Func {
refr, args, assign, ..
} => {
let mut path =
Parser::get_path_ref_components(&self.current_module()?.package.refr)?;
Parser::get_path_ref_components_into(refr, &mut path)?;
let path: Vec<&str> = path.iter().map(|s| s.text()).collect();
if let Some((_, prefix)) = path.split_last() {
if !prefix.is_empty() {
self.update_data(span, refr, prefix, Value::new_object())?;
}
}
if args.is_empty() {
let ctx = Context {
output_expr: assign.as_ref().map(|a| a.value.clone()),
value: Value::new_array(),
..Context::default()
};
let value = self.eval_rule_bodies(ctx, span, rule_body)?;
self.update_data(refr.span(), refr, &path[..], value)?;
}
}
}
}
_ => bail!("internal error: unexpected"),
}
Ok(())
}
pub fn eval_rule(&mut self, module: &Ref<Module>, rule: &Ref<Rule>) -> Result<()> {
self.check_execution_time()?;
self.current_module_index = self.find_module_index(module);
if self.processed.contains(rule) {
return Ok(());
}
if let Rule::Default { .. } = rule.as_ref() {
return Ok(());
}
self.active_rules.push(rule.clone());
if self.active_rules.iter().filter(|&r| r == rule).count() == 2 {
let mut msg = String::default();
for r in &self.active_rules {
let refr = Self::get_rule_refr(r);
let span = refr.span();
msg.push_str(
span.source
.message(span.line, span.col, "depends on", "")
.as_str(),
);
}
msg.push_str("cyclic evaluation");
self.active_rules.pop();
let refr = Self::get_rule_refr(rule);
let span = refr.span();
return Err(span.source.error(
span.line,
span.col,
format!("recursion detected when evaluating rule:{msg}").as_str(),
));
}
let scopes = core::mem::take(&mut self.scopes);
let prev_module = self.set_current_module(Some(module.clone()))?;
let res = self.eval_rule_impl(module, rule);
self.set_current_module(prev_module)?;
self.scopes = scopes;
match self.active_rules.pop() {
Some(ref r) if r == rule => res,
_ => bail!("internal error: current rule not active"),
}
}
pub fn eval_user_query(
&mut self,
module: &Ref<Module>,
query: &Ref<Query>,
query_schedule: Schedule,
enable_tracing: bool,
) -> Result<QueryResults> {
self.check_execution_time()?;
self.traces = match enable_tracing {
true => Some(vec![]),
false => None,
};
self.memory_check()?;
self.query_schedule = Some(query_schedule);
self.contexts.push(Context {
value: Value::new_set(),
result: Some(QueryResult::default()),
..Context::default()
});
self.query_module = Some(module.clone());
let prev_module = self.set_current_module(Some(module.clone()))?;
let prev_module_index = self.current_module_index;
let compiled_modules_len =
u32::try_from(self.compiled_policy.modules.len()).unwrap_or(u32::MAX);
self.current_module_index = compiled_modules_len;
self.ensure_loop_var_values_capacity();
let query_r = self.eval_query(query);
self.query_module = None;
let mut results = match self.contexts.pop() {
Some(ctx) => ctx.results,
_ => bail!("internal error: no context"),
};
let current_module_idx = compiled_modules_len;
let current_query_idx = query.qidx;
if let Some(ref self_schedule) = &self.query_schedule {
if let Some(active_schedule) = self_schedule
.queries
.get_checked(current_module_idx, current_query_idx)
.map_err(|err| anyhow!("schedule out of bounds: {err}"))?
{
for result in results.result.iter_mut() {
let exprs_len = result.expressions.len();
if active_schedule.order.len() != exprs_len {
continue;
}
let placeholder = Expression {
value: Value::Undefined,
text: "".into(),
location: Location { row: 0, col: 0 },
};
let mut ordered_expressions = vec![placeholder; exprs_len];
let mut invalid = false;
for (expr_idx, value) in result.expressions.iter().enumerate() {
let Some(&order_idx) = active_schedule.order.get(expr_idx) else {
invalid = true;
break;
};
let orig_idx = usize::from(order_idx);
if let Some(slot) = ordered_expressions.get_mut(orig_idx) {
*slot = value.clone();
} else {
invalid = true;
break;
}
}
if !invalid
&& !ordered_expressions
.iter()
.any(|v| v.value == Value::Undefined)
{
result.expressions = ordered_expressions;
}
}
}
}
self.query_schedule = None;
self.set_current_module(prev_module)?;
self.current_module_index = prev_module_index;
if let Some(r) = results.result.last() {
if matches!(&r.bindings, Value::Object(obj) if obj.is_empty())
&& (r.expressions.len() > 1
&& r.expressions.iter().any(|e| e.value == Value::Bool(false)))
{
results = QueryResults::default();
}
}
match query_r {
Ok(_) => Ok(results),
Err(e) => Err(e),
}
}
fn get_rule_path_components(mut refr: &Ref<Expr>) -> Result<Vec<Rc<str>>> {
let mut components: Vec<Rc<str>> = vec![];
loop {
refr = match refr.as_ref() {
Expr::Var { span: v, .. } => {
components.push(v.text().into());
break;
}
Expr::RefBrack { refr, index, .. } => {
if let Expr::String { span: s, .. } = index.as_ref() {
components.push(s.text().into());
} else {
components.clear();
}
refr
}
Expr::RefDot { refr, field, .. } => {
components.push(field.0.text().into());
refr
}
_ => break,
}
}
components.reverse();
Ok(components)
}
pub fn create_rule_prefixes(&mut self) -> Result<()> {
for module in self.compiled_policy.modules.clone().iter() {
let module_path = Self::get_rule_path_components(&module.package.refr)?;
for rule in &module.policy {
let rule_refr = Self::get_rule_refr(rule);
let mut prefix_path = module_path.clone();
let mut components = Self::get_rule_path_components(rule_refr)?;
let is_old_set = matches!(
rule.as_ref(),
Rule::Spec {
head: RuleHead::Set { key: None, .. },
..
}
);
if components.len() >= 2 && is_old_set {
components.pop();
}
if components.len() <= 1 {
continue;
}
components.pop();
prefix_path.append(&mut components);
let prefix_path: Vec<&str> = prefix_path.iter().map(|s| s.as_ref()).collect();
if Self::get_value_chained(self.data.clone(), &prefix_path) == Value::Undefined {
self.update_data(
rule_refr.span(),
rule_refr,
&prefix_path,
Value::new_object(),
)?;
}
}
}
Ok(())
}
fn record_rule(&mut self, refr: &Ref<Expr>, rule: Ref<Rule>) -> Result<()> {
let comps = Parser::get_path_ref_components(refr)?;
let comps: Vec<&str> = comps.iter().map(|s| s.text()).collect();
for (c, _) in comps.iter().enumerate() {
let path = format!(
"{}.{}",
self.current_module_path,
comps
.iter()
.take(c.saturating_add(1))
.copied()
.collect::<Vec<_>>()
.join(".")
);
if c == comps.len().saturating_sub(1) {
self.compiled_policy_mut().rule_paths.insert(path.clone());
}
match self.compiled_policy_mut().rules.entry(path) {
MapEntry::Occupied(o) => {
o.into_mut().push(rule.clone());
}
MapEntry::Vacant(v) => {
v.insert(vec![rule.clone()]);
}
}
}
Ok(())
}
fn record_default_rule(
&mut self,
refr: &Ref<Expr>,
rule: &Ref<Rule>,
index: Option<String>,
) -> Result<()> {
let comps = Parser::get_path_ref_components(refr)?;
let comps: Vec<&str> = comps.iter().map(|s| s.text()).collect();
for (idx, comp_idx) in (0..comps.len()).enumerate() {
let path = format!(
"{}.{}",
self.current_module_path,
comps
.iter()
.take(comp_idx.saturating_add(1))
.copied()
.collect::<Vec<_>>()
.join(".")
);
if comp_idx == comps.len().saturating_sub(1) {
self.compiled_policy_mut().rule_paths.insert(path.clone());
}
match self.compiled_policy_mut().default_rules.entry(path) {
MapEntry::Occupied(o) => {
if idx == comps.len().saturating_sub(1) {
for (_, i) in o.get() {
if let (Some(old), Some(new)) = (i, &index) {
if old == new {
bail!(refr.span().error("multiple default rules for the variable with the same index"));
}
} else if index.is_some() || i.is_some() {
bail!(refr.span().error("conflict type with the default rules"));
}
}
}
o.into_mut().push((rule.clone(), index.clone()));
}
MapEntry::Vacant(v) => {
v.insert(vec![(rule.clone(), index.clone())]);
}
}
}
Ok(())
}
pub fn process_imports(&mut self) -> Result<()> {
for module in self.compiled_policy.modules.clone().iter() {
let module_path = get_path_string(&module.package.refr, Some("data"))?;
for import in &module.imports {
let target = match &import.r#as {
Some(s) => s.text(),
_ => match import.refr.as_ref() {
Expr::RefDot { field, .. } => field.0.text(),
Expr::RefBrack { index, .. } => match index.as_ref() {
Expr::String { span: s, .. } => s.text(),
_ => "",
},
Expr::Var { span: v, .. } if v.text() == "input" => {
#[cfg(feature = "std")]
std::eprintln!(
"{}",
import
.refr
.span()
.message("warning", "redundant import of `input`")
);
continue;
}
_ => "",
},
};
if target.is_empty() {
bail!(import
.refr
.span()
.message("warning", "invalid ref in import"));
}
self.compiled_policy_mut()
.imports
.insert(format!("{}.{}", module_path, target), import.refr.clone());
}
}
Ok(())
}
pub fn gather_rules(&mut self) -> Result<()> {
for module in self.compiled_policy.modules.clone().iter() {
let prev_module = self.set_current_module(Some(module.clone()))?;
for rule in &module.policy {
let refr = Self::get_rule_refr(rule);
if let Rule::Spec { .. } = rule.as_ref() {
let refr = match refr.as_ref() {
Expr::RefBrack { index, .. }
if matches!(index.as_ref(), Expr::String { .. }) =>
{
refr
}
Expr::RefBrack { refr, .. } => refr,
_ => refr,
};
self.record_rule(refr, rule.clone())?;
} else if let Rule::Default { .. } = rule.as_ref() {
let (refr, index) = match refr.as_ref() {
Expr::RefBrack { refr, index, .. } => {
if !matches!(
index.as_ref(),
Expr::Bool { .. } | Expr::Number { .. } | Expr::String { .. }
) {
bail!(index.span().error("index is not a scalar value"));
}
let index = self.eval_expr(index)?;
(refr, Some(index.to_string()))
}
_ => (refr, None),
};
self.record_default_rule(refr, rule, index)?;
}
}
self.set_current_module(prev_module)?;
}
Ok(())
}
pub fn add_extension(
&mut self,
path: String,
nargs: u8,
extension: Box<dyn Extension>,
) -> Result<()> {
if let MapEntry::Vacant(v) = self.extensions.entry(path) {
v.insert((nargs, Rc::new(extension)));
Ok(())
} else {
bail!("extension already added");
}
}
#[cfg(feature = "coverage")]
fn gather_coverage_in_query(
&self,
query: &Ref<Query>,
covered: &Vec<bool>,
file: &mut crate::coverage::File,
) -> Result<()> {
for stmt in &query.stmts {
match &stmt.literal {
Literal::SomeVars { .. } => (),
Literal::SomeIn {
value, collection, ..
} => {
self.gather_coverage_in_expr(value, covered, file)?;
self.gather_coverage_in_expr(collection, covered, file)?;
}
Literal::Expr { expr, .. } | Literal::NotExpr { expr, .. } => {
self.gather_coverage_in_expr(expr, covered, file)?;
}
Literal::Every {
domain,
query: nested_query,
..
} => {
self.gather_coverage_in_expr(domain, covered, file)?;
self.gather_coverage_in_query(nested_query, covered, file)?;
}
}
}
Ok(())
}
#[cfg(feature = "coverage")]
fn gather_coverage_in_expr(
&self,
expr: &Ref<Expr>,
covered: &Vec<bool>,
file: &mut crate::coverage::File,
) -> Result<()> {
use Expr::*;
traverse(expr, &mut |e| {
Ok(match e.as_ref() {
ArrayCompr { query, .. } | SetCompr { query, .. } | ObjectCompr { query, .. } => {
self.gather_coverage_in_query(query, covered, file)?;
false
}
_ => {
let line_usize = usize::try_from(e.span().line).unwrap_or(usize::MAX);
match covered.get(line_usize) {
Some(true) => {
let line_u32 = u32::try_from(line_usize).unwrap_or(u32::MAX);
file.covered.insert(line_u32);
}
Some(false) | None => {
let line_u32 = u32::try_from(line_usize).unwrap_or(u32::MAX);
file.not_covered.insert(line_u32);
}
}
true
}
})
})?;
Ok(())
}
#[cfg(feature = "coverage")]
pub fn get_coverage_report(&self) -> Result<crate::coverage::Report> {
let mut report = crate::coverage::Report::default();
for module in self.compiled_policy.modules.iter() {
let span = module.package.refr.span();
let Some(covered) = self.coverage.get(&span.source) else {
continue;
};
let mut file = crate::coverage::File {
path: span.source.file().clone(),
code: span.source.contents().clone(),
covered: BTreeSet::new(),
not_covered: BTreeSet::new(),
};
for rule in &module.policy {
match rule.as_ref() {
Rule::Spec { head, bodies, .. } => {
match head {
RuleHead::Compr { assign, .. } | RuleHead::Func { assign, .. } => {
if let Some(a) = assign {
self.gather_coverage_in_expr(&a.value, covered, &mut file)?;
}
}
RuleHead::Set { key, .. } => {
if let Some(k) = key {
self.gather_coverage_in_expr(k, covered, &mut file)?;
}
}
}
for b in bodies {
self.gather_coverage_in_query(&b.query, covered, &mut file)?;
}
}
Rule::Default { value, .. } => {
self.gather_coverage_in_expr(value, covered, &mut file)?;
}
}
}
report.files.push(file);
}
Ok(report)
}
#[cfg(feature = "coverage")]
pub fn set_enable_coverage(&mut self, enable: bool) {
if self.enable_coverage != enable {
self.enable_coverage = enable;
self.clear_coverage_data();
}
}
#[cfg(feature = "coverage")]
pub fn clear_coverage_data(&mut self) {
self.coverage = Map::new();
}
pub fn set_gather_prints(&mut self, b: bool) {
if b != self.gather_prints {
core::mem::take(&mut self.prints);
}
self.gather_prints = b;
}
pub fn take_prints(&mut self) -> Result<Vec<String>> {
Ok(core::mem::take(&mut self.prints))
}
pub fn eval_rule_in_path(&mut self, path: String) -> Result<Value> {
self.check_execution_time()?;
if !self.compiled_policy.rule_paths.contains(&path) {
bail!("not a valid rule path");
}
self.ensure_rule_evaluated(path.clone())?;
let parts: Vec<&str> = path.split('.').collect();
let (_, tail) = parts
.split_first()
.ok_or_else(|| anyhow!("internal error: expected rule path"))?;
let value = Self::get_value_chained(self.data.clone(), tail);
#[cfg(feature = "azure_policy")]
{
if let Some(target_info) = &self.compiled_policy.target_info {
if value != Value::Undefined {
target_info.effect_schema.validate(&value)?;
}
}
}
Ok(value)
}
pub fn compile(&mut self, rule: Option<Rc<str>>) -> Result<Rc<CompiledPolicyData>> {
let data = Some(self.init_data.clone());
let extensions = self.extensions.clone();
let compiled_policy = self.compiled_policy_mut();
compiled_policy.data = data;
compiled_policy.extensions = extensions;
if let Some(rule) = rule {
if !compiled_policy.rule_paths.contains(rule.as_ref()) {
bail!("not a valid rule path");
}
compiled_policy.rule_to_evaluate = rule;
} else {
compiled_policy.rule_to_evaluate = "".into();
}
use crate::compiler::hoist::LoopHoister;
let hoister = compiled_policy
.schedule
.clone()
.map_or_else(LoopHoister::new, LoopHoister::new_with_schedule);
let loop_lookup = hoister.populate(compiled_policy.modules.as_ref())?;
compiled_policy.loop_hoisting_table = loop_lookup;
Ok(self.compiled_policy.clone())
}
}