mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
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.
93 lines
3.1 KiB
YAML
93 lines
3.1 KiB
YAML
# ArrayPushDefined instruction test suite
|
|
#
|
|
# Like ArrayPush but silently skips Undefined values. Used by the Azure Policy
|
|
# compiler when collecting `field('alias[*].property')` results: absent nested
|
|
# properties produce Undefined and should be excluded from the collected array.
|
|
#
|
|
# Semantics:
|
|
# - If value register is Undefined → no-op (skip).
|
|
# - If value register is any other value (including Null) → push to array.
|
|
# - If arr register is not an array → error.
|
|
|
|
cases:
|
|
# =========================================================================
|
|
# Undefined values are skipped
|
|
# =========================================================================
|
|
|
|
- note: push_undefined_is_skipped
|
|
description: Pushing an undefined value leaves the array unchanged
|
|
literals: []
|
|
instructions:
|
|
- "ArrayNew { dest: 0 }"
|
|
# Register 1 is implicitly Undefined
|
|
- "ArrayPushDefined { arr: 0, value: 1 }"
|
|
- "Return { value: 0 }"
|
|
want_result: []
|
|
|
|
- note: push_undefined_among_defined
|
|
description: Only defined values are collected; undefined ones are silently dropped
|
|
literals:
|
|
- 10
|
|
- 20
|
|
instructions:
|
|
- "ArrayNew { dest: 0 }"
|
|
- "Load { dest: 1, literal_idx: 0 }"
|
|
- "ArrayPushDefined { arr: 0, value: 1 }"
|
|
# Register 2 is Undefined — should be skipped
|
|
- "ArrayPushDefined { arr: 0, value: 2 }"
|
|
- "Load { dest: 3, literal_idx: 1 }"
|
|
- "ArrayPushDefined { arr: 0, value: 3 }"
|
|
- "Return { value: 0 }"
|
|
want_result: [10, 20]
|
|
|
|
# =========================================================================
|
|
# Null and other values are kept
|
|
# =========================================================================
|
|
|
|
- note: push_null_is_kept
|
|
description: Null is not undefined — it is pushed to the array
|
|
literals: []
|
|
instructions:
|
|
- "ArrayNew { dest: 0 }"
|
|
- "LoadNull { dest: 1 }"
|
|
- "ArrayPushDefined { arr: 0, value: 1 }"
|
|
- "Return { value: 0 }"
|
|
want_result: [null]
|
|
|
|
- note: push_bool_is_kept
|
|
description: Boolean value is pushed normally
|
|
literals:
|
|
- true
|
|
instructions:
|
|
- "ArrayNew { dest: 0 }"
|
|
- "Load { dest: 1, literal_idx: 0 }"
|
|
- "ArrayPushDefined { arr: 0, value: 1 }"
|
|
- "Return { value: 0 }"
|
|
want_result: [true]
|
|
|
|
- note: push_string_is_kept
|
|
description: String value is pushed normally
|
|
literals:
|
|
- "hello"
|
|
instructions:
|
|
- "ArrayNew { dest: 0 }"
|
|
- "Load { dest: 1, literal_idx: 0 }"
|
|
- "ArrayPushDefined { arr: 0, value: 1 }"
|
|
- "Return { value: 0 }"
|
|
want_result: ["hello"]
|
|
|
|
# =========================================================================
|
|
# Non-array target is an error
|
|
# =========================================================================
|
|
|
|
- note: push_to_non_array_errors
|
|
description: ArrayPushDefined on a non-array register produces an error
|
|
literals:
|
|
- 42
|
|
- 1
|
|
instructions:
|
|
- "Load { dest: 0, literal_idx: 0 }"
|
|
- "Load { dest: 1, literal_idx: 1 }"
|
|
- "ArrayPushDefined { arr: 0, value: 1 }"
|
|
want_error: "Register 0 does not contain an array"
|