Files
regorus/src/rvm/vm/context.rs
Anand Krishnamoorthi e5ac9a2734 feat(rvm): new instructions and loop semantics for Azure Policy support (#659)
The Rego VM was designed around Rego's semantics, but Azure Policy needs
a few things Rego doesn't: host-supplied context alongside input/data,
undefined-to-null coercion for missing fields, skip-undefined collection
behavior for wildcard aliases, and non-vacuous iteration over non-array
values.

This commit adds five new instructions to bridge those gaps:

  LoadContext / LoadMetadata — give programs access to host-supplied
  evaluation context and cached program metadata at runtime.

  ArrayPushDefined — like ArrayPush but silently drops undefined values,
  so wildcard alias collection (field[*].property) excludes absent
  nested properties instead of leaking undefined entries into the array.

  ReturnUndefinedIfNotTrue — early return with Undefined when a guard
  condition isn't satisfied, without tripping a VM assertion failure.
  This models "condition doesn't match" cleanly.

  CoalesceUndefinedToNull — turns Undefined into Null in-place so that
  downstream builtins see null rather than short-circuiting on undefined.

The loop engine also gains an Azure Policy mode: when the source language
is "azure_policy", an Every loop over a non-array value (scalars, null,
objects) iterates once over a virtual Null element instead of being
vacuously true.  This matches how field[*] behaves on non-array fields
in Azure Policy — the condition body runs once against Null, which
typically evaluates to false.

On the plumbing side: the VM gets a context field with set_context(),
metadata is cached as a Value on program load, and map_limit_error is
inlined into memory_check since it had only one call site.

Four new YAML test suites (~880 lines) cover the new instructions and
context/metadata loading, along with instruction parser, display, and
assembly listing support for everything added here.
2026-04-06 15:40:41 -05:00

110 lines
3.3 KiB
Rust

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::rvm::instructions::{ComprehensionMode, LoopMode};
use crate::value::Value;
use crate::Rc;
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::vec::Vec;
/// Loop execution context for managing iteration state
#[derive(Debug, Clone)]
pub struct LoopContext {
pub mode: LoopMode,
pub iteration_state: IterationState,
pub key_reg: u8,
pub value_reg: u8,
pub result_reg: u8,
pub body_start: u16,
pub loop_end: u16,
pub loop_next_pc: u16, // PC of the LoopNext instruction to avoid searching
pub body_resume_pc: usize,
pub success_count: usize,
pub total_iterations: usize,
pub current_iteration_failed: bool, // Track if current iteration had condition failures
}
/// Iterator state for different collection types
#[derive(Debug, Clone)]
pub enum IterationState {
Array {
items: Rc<Vec<Value>>,
index: usize,
},
Object {
obj: Rc<BTreeMap<Value, Value>>,
current_key: Option<Value>,
first_iteration: bool,
},
Set {
items: Rc<BTreeSet<Value>>,
current_item: Option<Value>,
first_iteration: bool,
},
/// Virtual single-element iteration for non-collection values.
/// Used by Azure Policy's `[*]` on scalar/null fields: presents a single
/// "virtual" element to iterate over, which is always `Null` regardless
/// of the underlying source value.
Single {
consumed: bool,
},
}
impl IterationState {
pub(super) const fn advance(&mut self) {
match *self {
Self::Array { ref mut index, .. } => {
*index = index.saturating_add(1);
}
Self::Object {
ref mut first_iteration,
..
}
| Self::Set {
ref mut first_iteration,
..
} => {
*first_iteration = false;
}
Self::Single {
ref mut consumed, ..
} => {
*consumed = true;
}
}
}
}
#[allow(unused)]
#[derive(Debug, Clone)]
pub struct CallRuleContext {
pub return_pc: usize,
pub dest_reg: u8,
pub result_reg: u8,
pub rule_index: u16,
pub rule_type: crate::rvm::program::RuleType,
pub current_definition_index: usize,
pub current_body_index: usize,
}
/// Context for tracking active comprehensions
#[derive(Debug, Clone)]
pub(super) struct ComprehensionContext {
/// Type of comprehension (Array, Set, Object)
pub(super) mode: ComprehensionMode,
/// Register storing the comprehension result collection
pub(super) result_reg: u8,
/// Register holding the current iteration key
pub(super) key_reg: u8,
/// Register holding the current iteration value
pub(super) value_reg: u8,
/// Jump target for comprehension body start
pub(super) body_start: u16,
/// Jump target for comprehension end
pub(super) comprehension_end: u16,
/// Iteration state when comprehension manages iteration itself (None when driven by LoopStart/LoopNext)
pub(super) iteration_state: Option<IterationState>,
/// Resume location for the parent frame once this comprehension completes
pub(super) resume_pc: usize,
}