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.
195 lines
6.2 KiB
YAML
195 lines
6.2 KiB
YAML
# CoalesceUndefinedToNull instruction test suite
|
|
#
|
|
# Used by the Azure Policy compiler to convert undefined (missing) field values
|
|
# to null before passing them to comparison operators. This prevents undefined
|
|
# propagation from short-circuiting subsequent builtins.
|
|
#
|
|
# Semantics:
|
|
# - If register == Undefined → replaces it with Null (in-place).
|
|
# - For any other value (null, bool, number, string, array, object) → no-op.
|
|
|
|
cases:
|
|
# =========================================================================
|
|
# Basic coalescing behavior
|
|
# =========================================================================
|
|
|
|
- note: undefined_becomes_null
|
|
description: Undefined register value is replaced with null
|
|
literals: []
|
|
instructions:
|
|
- "CoalesceUndefinedToNull { register: 0 }"
|
|
- "Return { value: 0 }"
|
|
want_result: null
|
|
|
|
- note: null_unchanged
|
|
description: Null value is not modified
|
|
literals: []
|
|
instructions:
|
|
- "LoadNull { dest: 0 }"
|
|
- "CoalesceUndefinedToNull { register: 0 }"
|
|
- "Return { value: 0 }"
|
|
want_result: null
|
|
|
|
- note: true_unchanged
|
|
description: Boolean true is not modified
|
|
literals: []
|
|
instructions:
|
|
- "LoadTrue { dest: 0 }"
|
|
- "CoalesceUndefinedToNull { register: 0 }"
|
|
- "Return { value: 0 }"
|
|
want_result: true
|
|
|
|
- note: false_unchanged
|
|
description: Boolean false is not modified
|
|
literals: []
|
|
instructions:
|
|
- "LoadFalse { dest: 0 }"
|
|
- "CoalesceUndefinedToNull { register: 0 }"
|
|
- "Return { value: 0 }"
|
|
want_result: false
|
|
|
|
- note: number_unchanged
|
|
description: Number value is not modified
|
|
literals:
|
|
- 42
|
|
instructions:
|
|
- "Load { dest: 0, literal_idx: 0 }"
|
|
- "CoalesceUndefinedToNull { register: 0 }"
|
|
- "Return { value: 0 }"
|
|
want_result: 42
|
|
|
|
- note: string_unchanged
|
|
description: String value is not modified
|
|
literals:
|
|
- "hello"
|
|
instructions:
|
|
- "Load { dest: 0, literal_idx: 0 }"
|
|
- "CoalesceUndefinedToNull { register: 0 }"
|
|
- "Return { value: 0 }"
|
|
want_result: "hello"
|
|
|
|
- note: empty_string_unchanged
|
|
description: Empty string is not modified (not confused with undefined)
|
|
literals:
|
|
- ""
|
|
instructions:
|
|
- "Load { dest: 0, literal_idx: 0 }"
|
|
- "CoalesceUndefinedToNull { register: 0 }"
|
|
- "Return { value: 0 }"
|
|
want_result: ""
|
|
|
|
- note: array_unchanged
|
|
description: Array value is not modified
|
|
literals: []
|
|
instructions:
|
|
- "ArrayNew { dest: 0 }"
|
|
- "CoalesceUndefinedToNull { register: 0 }"
|
|
- "Return { value: 0 }"
|
|
want_result: []
|
|
|
|
# =========================================================================
|
|
# Multiple coalesces — only undefined registers are affected
|
|
# =========================================================================
|
|
|
|
- note: selective_coalescing
|
|
description: Only undefined registers are coalesced; others remain unchanged
|
|
literals:
|
|
- 99
|
|
instructions:
|
|
- "Load { dest: 0, literal_idx: 0 }"
|
|
- "CoalesceUndefinedToNull { register: 0 }"
|
|
- "CoalesceUndefinedToNull { register: 1 }"
|
|
- "ArrayNew { dest: 2 }"
|
|
- "ArrayPush { arr: 2, value: 0 }"
|
|
- "ArrayPush { arr: 2, value: 1 }"
|
|
- "Return { value: 2 }"
|
|
want_result: [99, null]
|
|
|
|
# =========================================================================
|
|
# Interaction with ReturnUndefinedIfNotTrue
|
|
# =========================================================================
|
|
|
|
- note: coalesce_then_guard_null_returns_undefined
|
|
description: >
|
|
Coalescing undefined to null, then passing to ReturnUndefinedIfNotTrue
|
|
returns undefined (null is not true)
|
|
literals:
|
|
- "should not reach"
|
|
instructions:
|
|
- "CoalesceUndefinedToNull { register: 0 }"
|
|
- "ReturnUndefinedIfNotTrue { condition: 0 }"
|
|
- "Load { dest: 1, literal_idx: 0 }"
|
|
- "Return { value: 1 }"
|
|
want_result: "#undefined"
|
|
|
|
- note: coalesce_preserves_true_for_guard
|
|
description: >
|
|
Coalescing a true value is a no-op, so ReturnUndefinedIfNotTrue
|
|
continues execution
|
|
literals:
|
|
- "reached"
|
|
instructions:
|
|
- "LoadTrue { dest: 0 }"
|
|
- "CoalesceUndefinedToNull { register: 0 }"
|
|
- "ReturnUndefinedIfNotTrue { condition: 0 }"
|
|
- "Load { dest: 1, literal_idx: 0 }"
|
|
- "Return { value: 1 }"
|
|
want_result: "reached"
|
|
|
|
# =========================================================================
|
|
# Interaction with HostAwait (suspendable mode)
|
|
# =========================================================================
|
|
|
|
- note: coalesce_after_host_await_undefined_response
|
|
description: >
|
|
HostAwait returns undefined, CoalesceUndefinedToNull converts it to null
|
|
literals:
|
|
- "await-id"
|
|
- "arg"
|
|
instructions:
|
|
- "Load { dest: 1, literal_idx: 1 }"
|
|
- "Load { dest: 2, literal_idx: 0 }"
|
|
- "HostAwait { dest: 0, arg: 1, id: 2 }"
|
|
- "CoalesceUndefinedToNull { register: 0 }"
|
|
- "Return { value: 0 }"
|
|
host_await_responses:
|
|
- id: "await-id"
|
|
value: "#undefined"
|
|
want_result: null
|
|
|
|
- note: coalesce_after_host_await_value_response
|
|
description: >
|
|
HostAwait returns a real value, CoalesceUndefinedToNull is a no-op
|
|
literals:
|
|
- "await-id"
|
|
- "arg"
|
|
instructions:
|
|
- "Load { dest: 1, literal_idx: 1 }"
|
|
- "Load { dest: 2, literal_idx: 0 }"
|
|
- "HostAwait { dest: 0, arg: 1, id: 2 }"
|
|
- "CoalesceUndefinedToNull { register: 0 }"
|
|
- "Return { value: 0 }"
|
|
host_await_responses:
|
|
- id: "await-id"
|
|
value: "real-value"
|
|
want_result: "real-value"
|
|
|
|
- note: suspendable_coalesce_after_host_await
|
|
description: >
|
|
In suspendable mode: HostAwait suspends and resumes with value,
|
|
CoalesceUndefinedToNull is a no-op
|
|
literals:
|
|
- "await-id"
|
|
- "arg"
|
|
instructions:
|
|
- "Load { dest: 1, literal_idx: 1 }"
|
|
- "Load { dest: 2, literal_idx: 0 }"
|
|
- "HostAwait { dest: 0, arg: 1, id: 2 }"
|
|
- "CoalesceUndefinedToNull { register: 0 }"
|
|
- "Return { value: 0 }"
|
|
host_await_responses_suspendable:
|
|
- id: "await-id"
|
|
value: 42
|
|
ignore_run_to_completion_hostawait_failure: true
|
|
want_result: 42
|