mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
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.
This commit is contained in:
committed by
GitHub
parent
8f740e2f6f
commit
e5ac9a2734
@@ -83,6 +83,12 @@ mod tests {
|
||||
data: Option<crate::Value>,
|
||||
#[serde(default)]
|
||||
input: Option<crate::Value>,
|
||||
#[serde(default)]
|
||||
context: Option<crate::Value>,
|
||||
#[serde(default)]
|
||||
metadata_language: Option<String>,
|
||||
#[serde(default)]
|
||||
metadata_annotations: Option<BTreeMap<String, crate::Value>>,
|
||||
literals: Vec<crate::Value>,
|
||||
#[serde(default)]
|
||||
rule_infos: Vec<RuleInfoSpec>,
|
||||
@@ -266,6 +272,9 @@ mod tests {
|
||||
instruction_params: Option<InstructionParamsSpec>,
|
||||
data: Option<Value>,
|
||||
input: Option<Value>,
|
||||
context: Option<Value>,
|
||||
metadata_language: Option<String>,
|
||||
metadata_annotations: Option<BTreeMap<String, Value>>,
|
||||
max_instructions: Option<usize>,
|
||||
host_await_responses: Option<Vec<HostAwaitResponseSpec>>,
|
||||
host_await_responses_run_to_completion: Option<Vec<HostAwaitResponseSpec>>,
|
||||
@@ -285,6 +294,12 @@ mod tests {
|
||||
None
|
||||
};
|
||||
|
||||
let processed_context = if let Some(ref context_value) = context {
|
||||
Some(process_value(context_value)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let processed_rule_tree = if let Some(ref tree_value) = rule_tree {
|
||||
Some(process_value(tree_value)?)
|
||||
} else {
|
||||
@@ -631,6 +646,21 @@ mod tests {
|
||||
program.max_rule_window_size = 255;
|
||||
program.dispatch_window_size = 50;
|
||||
|
||||
// Recompute derived flags since instructions were assigned directly
|
||||
// (bypassing add_instruction which normally tracks has_host_await)
|
||||
program.recompute_host_await_presence();
|
||||
|
||||
// Set metadata if provided
|
||||
if let Some(lang) = metadata_language {
|
||||
program.metadata.language = lang;
|
||||
}
|
||||
if let Some(annotations) = metadata_annotations {
|
||||
program.metadata.annotations = annotations
|
||||
.into_iter()
|
||||
.map(|(key, value)| process_value(&value).map(|processed| (key, processed)))
|
||||
.collect::<anyhow::Result<_>>()?;
|
||||
}
|
||||
|
||||
// Initialize resolved builtins if we have builtin info
|
||||
if !program.builtin_info_table.is_empty() {
|
||||
if let Err(e) = program.initialize_resolved_builtins() {
|
||||
@@ -664,6 +694,10 @@ mod tests {
|
||||
vm.set_input(input_value);
|
||||
}
|
||||
|
||||
if let Some(context_value) = processed_context.clone() {
|
||||
vm.set_context(context_value);
|
||||
}
|
||||
|
||||
if let Some(limit) = max_instructions {
|
||||
vm.set_max_instructions(limit);
|
||||
}
|
||||
@@ -931,6 +965,9 @@ mod tests {
|
||||
test_case.instruction_params.clone(),
|
||||
test_case.data.clone(),
|
||||
test_case.input.clone(),
|
||||
test_case.context.clone(),
|
||||
test_case.metadata_language.clone(),
|
||||
test_case.metadata_annotations.clone(),
|
||||
test_case.max_instructions,
|
||||
test_case.host_await_responses.clone(),
|
||||
test_case.host_await_responses_run_to_completion.clone(),
|
||||
|
||||
Reference in New Issue
Block a user