Compare commits

..

1 Commits

Author SHA1 Message Date
github-actions[bot] 083907ced8 chore: release 2026-05-06 16:08:34 +00:00
14 changed files with 30 additions and 703 deletions
Generated
+1 -1
View File
@@ -1430,7 +1430,7 @@ dependencies = [
[[package]]
name = "regorus-mimalloc"
version = "2.2.6"
version = "2.2.7"
dependencies = [
"regorus-mimalloc-sys",
]
+1 -1
View File
@@ -128,7 +128,7 @@ rand = { version = "0.10.0", default-features = false, features = ["thread_rng"]
msvc_spectre_libs = { version = "0.1", features = ["error"], optional = true }
dashmap = { version = "6.1", default-features = false, optional = true }
lru = { version = "0.16", default-features = false, optional = true }
mimalloc = { package = "regorus-mimalloc", path = "mimalloc", version = "2.2.6", optional = true }
mimalloc = { package = "regorus-mimalloc", path = "mimalloc", version = "2.2.7", optional = true }
# rvm related deps
indexmap = { version = "2.13.1", default-features = false, features = ["serde"], optional = true }
+1 -1
View File
@@ -2,7 +2,7 @@
name = "regorus-mimalloc"
description = "Vendored mimalloc allocator for regorus"
edition = "2021"
version = "2.2.6"
version = "2.2.7"
license = "MIT"
repository = "https://github.com/microsoft/regorus"
+11 -75
View File
@@ -4,9 +4,9 @@
use crate::ast::*;
use crate::builtins::{self, BuiltinFcn};
use crate::compiled_policy::CompiledPolicyData;
#[cfg(feature = "azure_policy")]
use crate::compiled_policy::TargetInfo;
use crate::compiled_policy::{CompiledPolicyData, DefaultRuleInfo};
use crate::compiler::destructuring_planner::{
AssignmentPlan, BindingPlan, DestructuringPlan, WildcardSide,
};
@@ -1724,9 +1724,6 @@ impl Interpreter {
// For now, we restrict constant refs to those that contain only simple literals.
fn is_constant_ref(mut expr: &Ref<Expr>) -> Result<bool> {
loop {
if Self::is_simple_literal(expr)? {
return Ok(true);
}
match expr.as_ref() {
Expr::Var { .. } => break,
Expr::RefDot { refr, .. } => expr = refr,
@@ -1750,30 +1747,6 @@ impl Interpreter {
))
}
fn is_constant_key_expr(&self, expr: &Ref<Expr>) -> Result<bool> {
if Self::is_simple_literal(expr)? {
return Ok(true);
}
match expr.as_ref() {
Expr::Var { span, .. } => {
// A variable that is not currently bound in any active local scope behaves like
// a stable global/package reference for this evaluation.
let is_bound = self
.scopes
.iter()
.rev()
.any(|scope| scope.contains_key(&span.source_str()));
Ok(!is_bound)
}
Expr::RefDot { refr, .. } => self.is_constant_key_expr(refr),
Expr::RefBrack { refr, index, .. } => {
Ok(self.is_constant_key_expr(refr)? && self.is_constant_key_expr(index)?)
}
_ => Ok(false),
}
}
// A rule's output expression is constant if it does not contain local variables.
// For now, we restrict output expressions to those that contain only simple literals.
fn is_constant_output(key_expr: &Option<Ref<Expr>>, output_expr: &Ref<Expr>) -> Result<bool> {
@@ -1824,12 +1797,7 @@ impl Interpreter {
comps.pop();
output
} else {
// Implicit-true partial object rules can vary with each successful key binding.
if let Some(ke) = &key_expr {
if !is_old_style_set && !self.is_constant_key_expr(ke)? {
is_const_rule = false;
}
}
// Rule's constness is determined only by its ref.
Value::Bool(true)
};
@@ -2974,41 +2942,6 @@ impl Interpreter {
Ok(())
}
fn default_rules_for_path(&self, path: &str) -> Option<Vec<DefaultRuleInfo>> {
if let Some(rules) = self.compiled_policy.default_rules.get(path) {
return Some(rules.clone());
}
let (parent_path, index) = path.rsplit_once('.')?;
let rules = self.compiled_policy.default_rules.get(parent_path)?;
let matches = rules
.iter()
.filter(|(_, rule_index)| Self::default_rule_index_matches(rule_index, index))
.cloned()
.collect::<Vec<_>>();
if matches.is_empty() {
None
} else {
Some(matches)
}
}
fn has_default_rules_for_path(&self, path: &str) -> bool {
self.default_rules_for_path(path).is_some()
}
fn default_rule_index_matches(index: &Option<String>, path_component: &str) -> bool {
match index.as_deref() {
Some(index) if index == path_component => true,
Some(index) => index
.strip_prefix('"')
.and_then(|index| index.strip_suffix('"'))
.is_some_and(|index| index == path_component),
None => false,
}
}
fn ensure_rule_evaluated(&mut self, path: String) -> Result<()> {
self.check_execution_time()?;
let mut matched = false;
@@ -3023,9 +2956,9 @@ impl Interpreter {
}
// Evaluate the associated default rules after non-default rules
if let Some(rules) = self.default_rules_for_path(&path) {
if let Some(rules) = self.compiled_policy.default_rules.get(&path) {
matched = true;
for (r, _) in rules {
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))?;
@@ -3116,7 +3049,10 @@ impl Interpreter {
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.has_default_rules_for_path(&prefix_path)
|| self
.compiled_policy
.default_rules
.contains_key(&prefix_path)
{
self.ensure_rule_evaluated(prefix_path)?;
break;
@@ -3140,7 +3076,7 @@ impl Interpreter {
if !no_error
&& !self.compiled_policy.rules.contains_key(&rule_path)
&& !self.has_default_rules_for_path(&rule_path)
&& !self.compiled_policy.default_rules.contains_key(&rule_path)
&& !self.compiled_policy.imports.contains_key(&rule_path)
{
bail!(span.error(&format!(
@@ -3163,7 +3099,7 @@ impl Interpreter {
};
if self.compiled_policy.rules.contains_key(&path)
|| self.has_default_rules_for_path(&path)
|| self.compiled_policy.default_rules.contains_key(&path)
{
self.ensure_rule_evaluated(path)?;
found = true;
@@ -3710,7 +3646,7 @@ impl Interpreter {
self.data = Value::Undefined;
self.ensure_loop_var_values_capacity();
let default_rules = self.default_rules_for_path(rule_path);
let default_rules = self.compiled_policy.default_rules.get(rule_path).cloned();
if let Some(rules) = default_rules {
for (rule, _) in rules {
+1 -28
View File
@@ -181,7 +181,7 @@ impl<'a> Compiler<'a> {
}
fn evaluate_default_rule(&mut self, rule_path: &str) -> Option<u16> {
if !self.may_have_default_rule(rule_path) {
if !self.policy.inner.default_rules.contains_key(rule_path) {
return None;
}
@@ -200,33 +200,6 @@ impl<'a> Compiler<'a> {
None
}
fn may_have_default_rule(&self, rule_path: &str) -> bool {
if self.policy.inner.default_rules.contains_key(rule_path) {
return true;
}
let Some((parent_path, index)) = rule_path.rsplit_once('.') else {
return false;
};
self.policy
.inner
.default_rules
.get(parent_path)
.is_some_and(|rules| {
rules
.iter()
.any(|(_, rule_index)| match rule_index.as_deref() {
Some(rule_index) if rule_index == index => true,
Some(rule_index) => rule_index
.strip_prefix('"')
.and_then(|rule_index| rule_index.strip_suffix('"'))
.is_some_and(|rule_index| rule_index == index),
None => false,
})
})
}
fn extract_destructuring_blocks(&self, rule_index: u16) -> Vec<Option<u32>> {
self.rule_definition_destructuring_patterns[rule_index as usize].clone()
}
+4 -19
View File
@@ -11,7 +11,7 @@
)]
use super::{CompilationContext, Compiler, CompilerError, ContextType, Result, WorklistEntry};
use crate::ast::{AssignOp, Expr, ExprRef, Rule, RuleHead};
use crate::ast::{Expr, ExprRef, Rule, RuleHead};
use crate::compiler::destructuring_planner::plans::BindingPlan;
use crate::lexer::Span;
use crate::rvm::program::{Program, RuleType};
@@ -52,29 +52,14 @@ impl<'a> Compiler<'a> {
let rule_types: BTreeSet<RuleType> = definitions
.iter()
.map(|def| {
if let Rule::Spec { head, bodies, .. } = def.as_ref() {
if let Rule::Spec { head, .. } = def.as_ref() {
match head {
RuleHead::Set { .. } => RuleType::PartialSet,
RuleHead::Compr { refr, assign, .. } => match refr.as_ref() {
// Variable-key bracket heads emit one object entry per successful
// binding, so they must compile as partial objects.
crate::ast::Expr::RefBrack { index, .. }
if super::expressions::try_eval_const(index.as_ref()).is_none() =>
{
crate::ast::Expr::RefBrack { .. } if assign.is_some() => {
RuleType::PartialObject
}
crate::ast::Expr::RefBrack { .. }
if matches!(
assign.as_ref().map(|assign| &assign.op),
Some(AssignOp::Eq)
) =>
{
RuleType::PartialObject
}
crate::ast::Expr::RefBrack { .. } if bodies.is_empty() => {
RuleType::PartialObject
}
crate::ast::Expr::RefBrack { .. } => RuleType::Complete,
crate::ast::Expr::RefBrack { .. } => RuleType::PartialSet,
_ => RuleType::Complete,
},
_ => RuleType::Complete,
+1 -13
View File
@@ -454,19 +454,7 @@ impl RegoVM {
let mut obj_value = self.take_register(obj)?;
if let Ok(obj_mut) = obj_value.as_object_mut() {
match obj_mut.get(&key_value) {
Some(existing_value) if existing_value != &value_value => {
self.set_register(obj, obj_value)?;
return Err(VmError::RuleMultipleOutputs { pc: self.pc });
}
Some(_) => {
self.set_register(obj, obj_value)?;
return Ok(InstructionOutcome::Continue);
}
None => {
obj_mut.insert(key_value, value_value);
}
}
obj_mut.insert(key_value, value_value);
self.set_register(obj, obj_value)?;
} else {
let offending = obj_value.clone();
-3
View File
@@ -209,9 +209,6 @@ pub enum VmError {
#[error("Rule-data conflict: {message} (pc={pc})")]
RuleDataConflict { message: String, pc: usize },
#[error("rules must not produce multiple outputs (pc={pc})")]
RuleMultipleOutputs { pc: usize },
#[error("Arithmetic error: {message} (pc={pc})")]
ArithmeticError { message: String, pc: usize },
+2 -4
View File
@@ -17,9 +17,8 @@ use super::execution_model::{
use super::machine::RegoVM;
impl RegoVM {
/// Returns true if the error must never be silently absorbed by rule
/// evaluation backtracking, including resource-limit failures and semantic
/// rule consistency errors.
/// Returns true if the error represents a resource-limit violation that
/// must never be silently absorbed by rule evaluation.
pub(super) const fn is_fatal_vm_error(err: &VmError) -> bool {
matches!(
err,
@@ -27,7 +26,6 @@ impl RegoVM {
| VmError::MemoryLimitExceeded { .. }
| VmError::RegexSizeLimitExceeded { .. }
| VmError::InstructionLimitExceeded { .. }
| VmError::RuleMultipleOutputs { .. }
)
}
@@ -341,35 +341,6 @@ cases:
result: false
reasons: []
- note: default_rule_with_object_key
data: {}
input: {}
modules:
- |
package test
import rego.v1
default config["timeout"] := 30
config["timeout"] := val if {
val := input.val
}
query: data.test.config.timeout
want_result: 30
- note: default_rule_with_object_key_override
data: {}
input:
val: 60
modules:
- |
package test
import rego.v1
default config["timeout"] := 30
config["timeout"] := val if {
val := input.val
}
query: data.test.config.timeout
want_result: 60
- note: default_only_rule_with_package_query
data: {}
modules:
@@ -1,242 +0,0 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
cases:
- note: partial_object_iteration_some_in_object_v1
data: {}
input:
x:
FOO: 1
BAR: 2
BAZ: 3
modules:
- |
package test
import rego.v1
violations[k] if {
some k, _ in input.x
}
query: data.test
want_result:
violations:
BAR: true
BAZ: true
FOO: true
- note: partial_object_iteration_some_in_array_v1
data: {}
input:
arr: ["FOO", "BAR", "BAZ"]
modules:
- |
package test
import rego.v1
violations[v] if {
some _, v in input.arr
}
query: data.test
want_result:
violations:
BAR: true
BAZ: true
FOO: true
- note: partial_object_iteration_with_filter_v1
data: {}
input:
x:
FOO: 1
BAR: 2
BAZ: 3
modules:
- |
package test
import rego.v1
violations[k] if {
some k, _ in input.x
k != "BAR"
}
query: data.test
want_result:
violations:
BAZ: true
FOO: true
- note: partial_object_iteration_input_lookup_future_keywords
data: {}
input:
x:
FOO: 1
BAR: 2
BAZ: 3
modules:
- |
package test
import future.keywords.if
violations[k] if {
input.x[k]
}
query: data.test
want_result:
violations:
BAR: true
BAZ: true
FOO: true
- note: partial_object_multiple_bodies_collect_all_keys_v1
data: {}
input:
primary:
FOO: 1
BAR: 2
secondary:
BAZ: 3
modules:
- |
package test
import rego.v1
violations[k] if {
some k, _ in input.primary
}
violations[k] if {
some k, _ in input.secondary
}
query: data.test
want_result:
violations:
BAR: true
BAZ: true
FOO: true
- note: constant_key_implicit_true_rule_is_complete_v1
data: {}
input:
enabled: true
other: false
modules:
- |
package test
import rego.v1
p["x"] if {
input.enabled
}
p["x"] if {
input.other
}
query: data.test.p.x
want_result: true
- note: partial_object_duplicate_keys_same_value_are_ok_v1
data: {}
input:
arr: ["FOO", "FOO", "BAR"]
modules:
- |
package test
import rego.v1
violations[v] if {
some _, v in input.arr
}
query: data.test
want_result:
violations:
BAR: true
FOO: true
- note: partial_object_duplicate_keys_different_values_error_v1
data: {}
input:
entries:
- k: "FOO"
v: 1
- k: "FOO"
v: 2
modules:
- |
package test
import rego.v1
violations[k] := v if {
some entry in input.entries
k := entry.k
v := entry.v
}
query: data.test.violations
error: "rules must not produce multiple outputs"
- note: partial_object_and_partial_set_iteration_coexist_v1
data: {}
input:
x:
FOO: 1
BAR: 2
BAZ: 3
modules:
- |
package test
import rego.v1
violations[k] if {
some k, _ in input.x
}
seen contains k if {
some k, _ in input.x
}
query: data.test
want_result:
seen:
set!: ["BAR", "BAZ", "FOO"]
violations:
BAR: true
BAZ: true
FOO: true
- note: partial_object_key_bound_in_outer_scope_v1
data: {}
input:
outer:
FOO: [1, 2]
BAR: [3]
BAZ: []
modules:
- |
package test
import rego.v1
violations[k] if {
some k, arr in input.outer
some _ in arr
}
query: data.test.violations
want_result:
BAR: true
FOO: true
- note: complete_rule_same_value_definitions_still_work_v1
data: {}
input:
role: "superuser"
modules:
- |
package test
import rego.v1
allowed if {
input.role == "admin"
}
allowed if {
input.role == "superuser"
}
query: data.test.allowed
want_result: true
+3 -19
View File
@@ -48,34 +48,18 @@ cases:
want_result: true
- note: default_rule_with_object_key
skip: true # TODO: Fix rule type classification for config["timeout"] - should be Complete, not PartialObject
data: {}
input: {}
modules:
- |
package test
import rego.v1
default config["timeout"] := 30
config["timeout"] := val if {
val := input.val
config["timeout"] := 60 if {
false # This will fail
}
query: data.test.config.timeout
want_result: 30
- note: default_rule_with_object_key_override
data: {}
input:
val: 60
modules:
- |
package test
import rego.v1
default config["timeout"] := 30
config["timeout"] := val if {
val := input.val
}
query: data.test.config.timeout
want_result: 60
- note: default_rule_complex_value
data: {}
modules:
@@ -1,241 +0,0 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
cases:
- note: partial_object_iteration_some_in_object_v1
data: {}
input:
x:
FOO: 1
BAR: 2
BAZ: 3
modules:
- |
package test
import rego.v1
violations[k] if {
some k, _ in input.x
}
query: data.test.violations
want_result:
BAR: true
BAZ: true
FOO: true
- note: partial_object_iteration_some_in_array_v1
data: {}
input:
arr: ["FOO", "BAR", "BAZ"]
modules:
- |
package test
import rego.v1
violations[v] if {
some _, v in input.arr
}
query: data.test.violations
want_result:
BAR: true
BAZ: true
FOO: true
- note: partial_object_iteration_with_filter_v1
data: {}
input:
x:
FOO: 1
BAR: 2
BAZ: 3
modules:
- |
package test
import rego.v1
violations[k] if {
some k, _ in input.x
k != "BAR"
}
query: data.test.violations
want_result:
BAZ: true
FOO: true
- note: partial_object_iteration_input_lookup_future_keywords
data: {}
input:
x:
FOO: 1
BAR: 2
BAZ: 3
modules:
- |
package test
import future.keywords.if
violations[k] if {
input.x[k]
}
query: data.test.violations
want_result:
BAR: true
BAZ: true
FOO: true
- note: partial_object_multiple_bodies_collect_all_keys_v1
data: {}
input:
primary:
FOO: 1
BAR: 2
secondary:
BAZ: 3
modules:
- |
package test
import rego.v1
violations[k] if {
some k, _ in input.primary
}
violations[k] if {
some k, _ in input.secondary
}
query: data.test.violations
want_result:
BAR: true
BAZ: true
FOO: true
- note: constant_key_implicit_true_rule_is_complete_v1
data: {}
input:
enabled: true
other: false
modules:
- |
package test
import rego.v1
p["x"] if {
input.enabled
}
p["x"] if {
input.other
}
query: data.test.p.x
want_result: true
- note: partial_object_duplicate_keys_same_value_are_ok_v1
data: {}
input:
arr: ["FOO", "FOO", "BAR"]
modules:
- |
package test
import rego.v1
violations[v] if {
some _, v in input.arr
}
query: data.test.violations
want_result:
BAR: true
FOO: true
- note: partial_object_duplicate_keys_different_values_error_v1
data: {}
input:
entries:
- k: "FOO"
v: 1
- k: "FOO"
v: 2
modules:
- |
package test
import rego.v1
violations[k] := v if {
some entry in input.entries
k := entry.k
v := entry.v
}
query: data.test.violations
want_error: "multiple outputs"
- note: partial_object_and_partial_set_iteration_coexist_v1
data: {}
input:
x:
FOO: 1
BAR: 2
BAZ: 3
modules:
- |
package test
import rego.v1
violations[k] if {
some k, _ in input.x
}
seen contains k if {
some k, _ in input.x
}
main := {
"seen": seen,
"violations": violations,
}
query: data.test.main
want_result:
seen:
set!: ["BAR", "BAZ", "FOO"]
violations:
BAR: true
BAZ: true
FOO: true
- note: partial_object_key_bound_in_outer_scope_v1
data: {}
input:
outer:
FOO: [1, 2]
BAR: [3]
BAZ: []
modules:
- |
package test
import rego.v1
violations[k] if {
some k, arr in input.outer
some _ in arr
}
query: data.test.violations
want_result:
BAR: true
FOO: true
- note: complete_rule_same_value_definitions_still_work_v1
data: {}
input:
role: "superuser"
modules:
- |
package test
import rego.v1
allowed if {
input.role == "admin"
}
allowed if {
input.role == "superuser"
}
query: data.test.allowed
want_result: true
+5 -27
View File
@@ -6,9 +6,9 @@
# Covers dynamic keys, collisions, non-string keys, and template validation
cases:
- note: object_key_collision_conflict
description: Setting same key twice with different values should raise a rule output conflict
example_rego: "p[\"key\"] = value { value := [1, 2][_] }"
- note: object_key_collision_overwrite
description: Setting same key twice should overwrite the value
example_rego: "{\"key\": 1, \"key\": 2}"
literals:
- {}
- "key"
@@ -26,31 +26,9 @@ cases:
- "Load { dest: 2, literal_idx: 2 }" # value 1
- "ObjectSet { obj: 0, key: 1, value: 2 }"
- "Load { dest: 3, literal_idx: 3 }" # value 2
- "ObjectSet { obj: 0, key: 1, value: 3 }" # Conflict
- "ObjectSet { obj: 0, key: 1, value: 3 }" # Overwrite
- "Return { value: 0 }"
want_error: "multiple outputs"
- note: object_key_duplicate_same_value
description: Setting same key twice with the same value should succeed
example_rego: "p[\"key\"] := 1 if { some _ in [0, 1] }"
literals:
- {}
- "key"
- 1
instruction_params:
object_create_params:
- dest: 0
template_literal_idx: 0
literal_key_fields: []
fields: []
instructions:
- "ObjectCreate { params_index: 0 }"
- "Load { dest: 1, literal_idx: 1 }" # key
- "Load { dest: 2, literal_idx: 2 }" # value
- "ObjectSet { obj: 0, key: 1, value: 2 }"
- "ObjectSet { obj: 0, key: 1, value: 2 }"
- "Return { value: 0 }"
want_result: {"key": 1}
want_result: {"key": 2}
- note: object_dynamic_key_generation
description: Generate object keys dynamically from loop iteration