mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
Lock down ACI tests and more OPA test folders (#54)
Borrowed from rego-cpp Significantly (> 10 times) faster execution. $ cargo test -r --test aci aci/mount_device passed 9.597958ms aci/mount_overlay passed 10.159208ms aci/scratch_mount passed 8.598875ms aci/create_container passed 10.237292ms aci/shutdown_container passed 6.904084ms aci/scratch_unmount passed 6.530875ms aci/unmount_overlay passed 5.958875ms aci/unmount_device passed 5.657834ms aci/load_fragment passed 6.049917ms Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
committed by
GitHub
parent
639ba72c90
commit
bb0ca29753
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::ast::{Expr, Ref};
|
||||
use crate::builtins::regex::regex_match;
|
||||
use crate::builtins::utils::{ensure_args_count, ensure_set};
|
||||
use crate::builtins::BuiltinFcn;
|
||||
use crate::lexer::Span;
|
||||
@@ -20,6 +21,7 @@ lazy_static! {
|
||||
m.insert("all", (all, 1));
|
||||
m.insert("any", (any, 1));
|
||||
m.insert("set_diff", (set_diff, 2));
|
||||
m.insert("re_match", (regex_match, 2));
|
||||
m
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ pub mod deprecated;
|
||||
mod encoding;
|
||||
pub mod numbers;
|
||||
mod objects;
|
||||
mod regex;
|
||||
mod semver;
|
||||
pub mod sets;
|
||||
mod strings;
|
||||
@@ -45,7 +46,7 @@ lazy_static! {
|
||||
sets::register(&mut m);
|
||||
objects::register(&mut m);
|
||||
strings::register(&mut m);
|
||||
//regex::register(&mut m);
|
||||
regex::register(&mut m);
|
||||
//glob::register(&mut m);
|
||||
bitwise::register(&mut m);
|
||||
conversions::register(&mut m);
|
||||
|
||||
51
src/builtins/regex.rs
Normal file
51
src/builtins/regex.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::ast::{Expr, Ref};
|
||||
use crate::builtins;
|
||||
use crate::builtins::utils::{ensure_args_count, ensure_string};
|
||||
use crate::lexer::Span;
|
||||
use crate::value::Value;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use regex::Regex;
|
||||
|
||||
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
|
||||
m.insert("regex.is_valid", (is_valid, 1));
|
||||
m.insert("regex.match", (regex_match, 2));
|
||||
m.insert("regex.split", (regex_split, 2));
|
||||
}
|
||||
|
||||
fn is_valid(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
|
||||
let name = "regex.is_valid";
|
||||
ensure_args_count(span, name, params, args, 1)?;
|
||||
Ok(ensure_string(name, ¶ms[0], &args[0])
|
||||
.map_or(Value::Bool(false), |p| Value::Bool(Regex::new(&p).is_ok())))
|
||||
}
|
||||
|
||||
pub fn regex_match(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
|
||||
let name = "regex.match";
|
||||
ensure_args_count(span, name, params, args, 2)?;
|
||||
let pattern = ensure_string(name, ¶ms[0], &args[0])?;
|
||||
let value = ensure_string(name, ¶ms[1], &args[1])?;
|
||||
|
||||
let pattern = Regex::new(&pattern).or_else(|_| bail!(span.error("invalid regex")))?;
|
||||
Ok(Value::Bool(pattern.is_match(&value)))
|
||||
}
|
||||
|
||||
pub fn regex_split(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
|
||||
let name = "regex.split";
|
||||
ensure_args_count(span, name, params, args, 2)?;
|
||||
let pattern = ensure_string(name, ¶ms[0], &args[0])?;
|
||||
let value = ensure_string(name, ¶ms[1], &args[1])?;
|
||||
|
||||
let pattern = Regex::new(&pattern).or_else(|_| bail!(span.error("invalid regex")))?;
|
||||
Ok(Value::from_array(
|
||||
pattern
|
||||
.split(&value)
|
||||
.map(|s| Value::String(s.into()))
|
||||
.collect::<Vec<Value>>(),
|
||||
))
|
||||
}
|
||||
@@ -242,7 +242,7 @@ impl Interpreter {
|
||||
// Stop path collection upon encountering the leading variable.
|
||||
Expr::Var(v) => {
|
||||
path.reverse();
|
||||
return self.lookup_var(v, &path[..]);
|
||||
return self.lookup_var(v, &path[..], false);
|
||||
}
|
||||
// Accumulate chained . field accesses.
|
||||
Expr::RefDot { refr, field, .. } => {
|
||||
@@ -259,8 +259,23 @@ impl Interpreter {
|
||||
// Note, we have the choice to evaluate a non-string index
|
||||
_ => {
|
||||
path.reverse();
|
||||
let obj = self.eval_expr(refr)?;
|
||||
|
||||
let index = self.eval_expr(index)?;
|
||||
|
||||
// Handle indexing into data.
|
||||
if let Ok(ref_path) = get_path_string(refr, None) {
|
||||
if get_root_var(refr)?.text() == "data" && index != Value::Undefined {
|
||||
let index = match &index {
|
||||
Value::String(s) => s.to_string(),
|
||||
_ => index.to_string(),
|
||||
};
|
||||
let ref_path = ref_path + "." + &index + "." + &path.join("");
|
||||
self.ensure_rule_evaluated(ref_path)?;
|
||||
}
|
||||
}
|
||||
|
||||
let obj = self.eval_expr(refr)?;
|
||||
|
||||
let mut v = obj[&index].clone();
|
||||
// Qualified references starting with data (e.g data.p.q) can
|
||||
// be indexed using numbers. The number will be converted to string
|
||||
@@ -451,55 +466,87 @@ impl Interpreter {
|
||||
let (name, value) = match op {
|
||||
AssignOp::Eq => {
|
||||
match (lhs.as_ref(), rhs.as_ref()) {
|
||||
(Expr::Var(lhs_span), Expr::Var(rhs_span)) => {
|
||||
let (lhs_name, lhs_var) = (lhs_span.source_str(), self.eval_expr(lhs)?);
|
||||
let (rhs_name, rhs_var) = (rhs_span.source_str(), self.eval_expr(rhs)?);
|
||||
|
||||
match (&lhs_var, &rhs_var) {
|
||||
(Value::Undefined, Value::Undefined) => {
|
||||
bail!(lhs.span().error("both operands are unsafe"))
|
||||
(_, Expr::Var(var)) if self.lookup_var(var, &[], true)? == Value::Undefined => {
|
||||
(var.source_str(), self.eval_expr(lhs)?)
|
||||
}
|
||||
(Expr::Var(var), _) if self.lookup_var(var, &[], true)? == Value::Undefined => {
|
||||
(var.source_str(), self.eval_expr(rhs)?)
|
||||
}
|
||||
(
|
||||
Expr::Array {
|
||||
items: lhs_items, ..
|
||||
},
|
||||
Expr::Array {
|
||||
items: rhs_items,
|
||||
span: rhs_span,
|
||||
},
|
||||
) => {
|
||||
if lhs_items.len() != rhs_items.len() {
|
||||
bail!(rhs_span
|
||||
.error("mismatch in number of array elements in lhs and rhs"));
|
||||
}
|
||||
for (lhs, rhs) in std::iter::zip(lhs_items.iter(), rhs_items.iter()) {
|
||||
if self.eval_assign_expr(&AssignOp::Eq, lhs, rhs)? != Value::Bool(true)
|
||||
{
|
||||
return Ok(Value::Bool(false));
|
||||
}
|
||||
(Value::Undefined, _) => (lhs_name, rhs_var),
|
||||
(_, Value::Undefined) => (rhs_name, lhs_var),
|
||||
// TODO: avoid reeval
|
||||
_ => return self.eval_bool_expr(&BoolOp::Eq, lhs, rhs),
|
||||
}
|
||||
return Ok(Value::Bool(true));
|
||||
}
|
||||
(Expr::Var(lhs_span), _) => {
|
||||
let (name, var) = (lhs_span.source_str(), self.eval_expr(lhs)?);
|
||||
|
||||
// TODO: Check this
|
||||
// Allow variable overwritten inside a loop
|
||||
if !matches!(var, Value::Undefined)
|
||||
&& self.loop_var_values.get(rhs).is_none()
|
||||
{
|
||||
return self.eval_bool_expr(&BoolOp::Eq, lhs, rhs);
|
||||
}
|
||||
|
||||
(name, self.eval_expr(rhs)?)
|
||||
(Expr::Object { .. }, Expr::Object { .. }) => {
|
||||
// TODO: destructure
|
||||
return self.eval_bool_expr(&BoolOp::Eq, lhs, rhs);
|
||||
}
|
||||
(_, Expr::Var(rhs_span)) => {
|
||||
let (name, var) = (rhs_span.source_str(), self.eval_expr(rhs)?);
|
||||
|
||||
// TODO: Check this
|
||||
// Allow variable overwritten inside a loop
|
||||
if !matches!(var, Value::Undefined)
|
||||
&& self.loop_var_values.get(lhs).is_none()
|
||||
{
|
||||
return self.eval_bool_expr(&BoolOp::Eq, lhs, rhs);
|
||||
}
|
||||
|
||||
(name, self.eval_expr(lhs)?)
|
||||
(Expr::Array { .. }, _) => {
|
||||
let value = self.eval_expr(rhs)?;
|
||||
let mut cache = BTreeMap::new();
|
||||
let mut type_match = BTreeSet::new();
|
||||
return self
|
||||
.make_bindings(false, &mut type_match, &mut cache, lhs, &value)
|
||||
.map(Value::Bool);
|
||||
}
|
||||
(_, Expr::Array { .. }) => {
|
||||
let value = self.eval_expr(lhs)?;
|
||||
let mut cache = BTreeMap::new();
|
||||
let mut type_match = BTreeSet::new();
|
||||
return self
|
||||
.make_bindings(false, &mut type_match, &mut cache, rhs, &value)
|
||||
.map(Value::Bool);
|
||||
}
|
||||
(Expr::Object { .. }, _) => {
|
||||
let value = self.eval_expr(rhs)?;
|
||||
let mut cache = BTreeMap::new();
|
||||
let mut type_match = BTreeSet::new();
|
||||
return self
|
||||
.make_bindings(false, &mut type_match, &mut cache, lhs, &value)
|
||||
.map(Value::Bool);
|
||||
}
|
||||
(_, Expr::Object { .. }) => {
|
||||
let value = self.eval_expr(lhs)?;
|
||||
let mut cache = BTreeMap::new();
|
||||
let mut type_match = BTreeSet::new();
|
||||
return self
|
||||
.make_bindings(false, &mut type_match, &mut cache, rhs, &value)
|
||||
.map(Value::Bool);
|
||||
}
|
||||
// Treat the assignment as comparison if neither lhs nor rhs is a variable
|
||||
_ => return self.eval_bool_expr(&BoolOp::Eq, lhs, rhs),
|
||||
}
|
||||
}
|
||||
AssignOp::ColEq => {
|
||||
let rhs_value = self.eval_expr(rhs)?;
|
||||
if rhs_value == Value::Undefined {
|
||||
return Ok(rhs_value);
|
||||
}
|
||||
|
||||
let name = if let Expr::Var(span) = lhs.as_ref() {
|
||||
span.source_str()
|
||||
} else {
|
||||
bail!("internal error: unexpected");
|
||||
let mut cache = BTreeMap::new();
|
||||
let mut type_match = BTreeSet::new();
|
||||
return self
|
||||
.make_bindings(false, &mut type_match, &mut cache, lhs, &rhs_value)
|
||||
.map(Value::Bool);
|
||||
};
|
||||
|
||||
// TODO: Check this
|
||||
@@ -513,7 +560,7 @@ impl Interpreter {
|
||||
.error(&format!("redefinition for variable {}", name)));
|
||||
}
|
||||
|
||||
(name, self.eval_expr(rhs)?)
|
||||
(name, rhs_value)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -632,6 +679,7 @@ impl Interpreter {
|
||||
let raise_error = is_last && type_match.get(expr).is_none();
|
||||
|
||||
match (expr.as_ref(), value) {
|
||||
(Expr::Var(ident), _) if ident.text().as_ref() == &"_" => Ok(true),
|
||||
(Expr::Var(ident), _) => {
|
||||
self.add_variable(&ident.source_str(), value.clone())?;
|
||||
Ok(true)
|
||||
@@ -1649,29 +1697,25 @@ impl Interpreter {
|
||||
));
|
||||
}
|
||||
|
||||
let mut args_scope = Scope::new();
|
||||
for (idx, a) in args.iter().enumerate() {
|
||||
let a = match a.as_ref() {
|
||||
Expr::Var(s) => s.source_str(),
|
||||
_ => {
|
||||
match self.eval_expr(a) {
|
||||
Ok(a) => {
|
||||
if a != param_values[idx] {
|
||||
// Skip this rule definition.
|
||||
continue 'outer;
|
||||
}
|
||||
// Back up local variables of current function and empty
|
||||
// the local variables of callee function.
|
||||
let scopes = std::mem::take(&mut self.scopes);
|
||||
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
// TODO: destructuring function arguments.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
//TODO: check call in params
|
||||
args_scope.insert(a, param_values[idx].clone());
|
||||
// Set the arguments scope.
|
||||
let args_scope = Scope::new();
|
||||
self.scopes.push(args_scope);
|
||||
|
||||
let mut cache = BTreeMap::new();
|
||||
let mut type_match = BTreeSet::new();
|
||||
|
||||
for (idx, a) in args.iter().enumerate() {
|
||||
if self
|
||||
.make_bindings(false, &mut type_match, &mut cache, a, ¶m_values[idx])
|
||||
.is_err()
|
||||
{
|
||||
self.scopes = scopes;
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
|
||||
let ctx = Context {
|
||||
@@ -1683,12 +1727,6 @@ impl Interpreter {
|
||||
is_compr: false,
|
||||
};
|
||||
|
||||
// Back up local variables of current function and empty
|
||||
// the local variables of callee function.
|
||||
let scopes = std::mem::take(&mut self.scopes);
|
||||
|
||||
// Set the arguments scope.
|
||||
self.scopes.push(args_scope);
|
||||
let value = match self.eval_rule_bodies(ctx, span, bodies) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
@@ -1768,10 +1806,17 @@ impl Interpreter {
|
||||
}
|
||||
Ok(Value::Bool(true))
|
||||
}
|
||||
_ => {
|
||||
_ if allow_return_arg => {
|
||||
let ret_value = self.eval_call_impl(span, fcn, ¶ms[..params.len() - 1])?;
|
||||
let value = self.eval_expr(&ea)?;
|
||||
Ok(Value::Bool(ret_value == value))
|
||||
let mut cache = BTreeMap::new();
|
||||
let mut type_match = BTreeSet::new();
|
||||
self.make_bindings(false, &mut type_match, &mut cache, &ea, &ret_value)
|
||||
.map(Value::Bool)
|
||||
}
|
||||
_ => {
|
||||
let expected = self.eval_expr(¶ms[params.len() - 1])?;
|
||||
let ret_value = self.eval_call_impl(span, fcn, ¶ms[..params.len() - 1])?;
|
||||
Ok(Value::Bool(ret_value == expected))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1813,7 +1858,7 @@ impl Interpreter {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn lookup_var(&mut self, span: &Span, fields: &[&str]) -> Result<Value> {
|
||||
fn lookup_var(&mut self, span: &Span, fields: &[&str], no_error: bool) -> Result<Value> {
|
||||
let name = span.source_str();
|
||||
|
||||
// Return local variable/argument.
|
||||
@@ -1828,6 +1873,9 @@ impl Interpreter {
|
||||
|
||||
// TODO: should we return before checking for input?
|
||||
if self.no_rules_lookup {
|
||||
if no_error {
|
||||
return Ok(Value::Undefined);
|
||||
}
|
||||
return Err(span.error("undefined var"));
|
||||
}
|
||||
|
||||
@@ -1869,6 +1917,12 @@ impl Interpreter {
|
||||
Self::get_path_string(&self.current_module()?.package.refr, Some("data"))?;
|
||||
let rule_path = module_path + "." + name.text();
|
||||
|
||||
if !no_error
|
||||
&& self.rules.get(&rule_path).is_none()
|
||||
&& self.default_rules.get(&rule_path).is_none()
|
||||
{
|
||||
bail!(span.error("var is unsafe"));
|
||||
}
|
||||
self.ensure_rule_evaluated(rule_path)?;
|
||||
|
||||
let value = Self::get_value_chained(self.data.clone(), &path[..]);
|
||||
|
||||
@@ -147,7 +147,7 @@ impl Source {
|
||||
let col_spaces = col as usize - 1;
|
||||
|
||||
format!(
|
||||
"\n-->{}:{}:{}\n{:<line_num_width$}|\n\
|
||||
"\n--> {}:{}:{}\n{:<line_num_width$}|\n\
|
||||
{:<line_num_width$}| {}\n\
|
||||
{:<line_num_width$}| {:<col_spaces$}^\n\
|
||||
{}: {}",
|
||||
|
||||
@@ -570,6 +570,15 @@ impl Analyzer {
|
||||
} else {
|
||||
gather_input_vars(expr, &self.scopes, scope)?;
|
||||
gather_loop_vars(expr, &self.scopes, scope)?;
|
||||
|
||||
let extra_arg = get_extra_arg(
|
||||
expr,
|
||||
Some(self.current_module_path.as_str()),
|
||||
&self.functions,
|
||||
);
|
||||
if let Some(ea) = extra_arg {
|
||||
gather_vars(&ea, false, &self.scopes, scope)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Literal::Every { domain, .. } => {
|
||||
@@ -600,19 +609,26 @@ impl Analyzer {
|
||||
scope: &mut Scope,
|
||||
first_use: &mut BTreeMap<SourceStr, Span>,
|
||||
definitions: &mut Vec<Definition<SourceStr>>,
|
||||
return_arg: &Option<Ref<Expr>>,
|
||||
assigned_vars: &Option<&BTreeSet<SourceStr>>,
|
||||
) -> Result<(Vec<SourceStr>, Vec<Ref<Expr>>)> {
|
||||
let mut used_vars = vec![];
|
||||
let mut comprs = vec![];
|
||||
traverse(expr, &mut |e| match e.as_ref() {
|
||||
Var(v) if !matches!(*v.text(), "_" | "input" | "data") => {
|
||||
let name = v.source_str();
|
||||
let is_extra_arg = match assigned_vars {
|
||||
Some(vars) => vars.contains(&v.source_str()),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if scope.locals.contains(&name)
|
||||
/*|| scope.inputs.contains(name) */
|
||||
{
|
||||
used_vars.push(name.clone());
|
||||
first_use.entry(name).or_insert(v.clone());
|
||||
} else if !scope.inputs.contains(&name) && Some(e.clone()) != *return_arg {
|
||||
if !is_extra_arg {
|
||||
used_vars.push(name.clone());
|
||||
first_use.entry(name).or_insert(v.clone());
|
||||
}
|
||||
} else if !scope.inputs.contains(&name) {
|
||||
bail!(v.error(format!("use of undefined variable `{name}` is unsafe").as_str()));
|
||||
}
|
||||
Ok(false)
|
||||
@@ -627,7 +643,7 @@ impl Analyzer {
|
||||
scope,
|
||||
first_use,
|
||||
definitions,
|
||||
return_arg,
|
||||
assigned_vars,
|
||||
)?;
|
||||
definitions.push(Definition {
|
||||
var: var.clone(),
|
||||
@@ -992,30 +1008,42 @@ impl Analyzer {
|
||||
&self.functions,
|
||||
);
|
||||
if let Some(ref ea) = extra_arg {
|
||||
if let Expr::Var(return_arg) = ea.as_ref() {
|
||||
let (mut used_vars, comprs) = Self::gather_used_vars_comprs_index_vars(
|
||||
expr,
|
||||
&mut scope,
|
||||
&mut first_use,
|
||||
&mut definitions,
|
||||
&extra_arg,
|
||||
)?;
|
||||
let var = if *return_arg.text() != "_" {
|
||||
// The var in the return argument slot would have been processed as
|
||||
// an used var. Remove it from used vars and add it as the variable being
|
||||
// defined.
|
||||
used_vars.pop();
|
||||
return_arg.source_str()
|
||||
} else {
|
||||
empty_str.clone()
|
||||
};
|
||||
self.process_comprs(
|
||||
&comprs[..],
|
||||
&mut scope,
|
||||
&mut first_use,
|
||||
&mut used_vars,
|
||||
)?;
|
||||
definitions.push(Definition { var, used_vars });
|
||||
// Gather vars that are being bound
|
||||
let mut extras_scope = Scope::default();
|
||||
gather_assigned_vars(ea, false, &self.scopes, &mut extras_scope)?;
|
||||
|
||||
for var in &extras_scope.locals {
|
||||
scope.locals.insert(var.clone());
|
||||
}
|
||||
|
||||
// Gather vars being used.
|
||||
let (mut used_vars, comprs) = Self::gather_used_vars_comprs_index_vars(
|
||||
expr,
|
||||
&mut scope,
|
||||
&mut first_use,
|
||||
&mut definitions,
|
||||
&Some(&extras_scope.locals),
|
||||
)?;
|
||||
|
||||
self.process_comprs(
|
||||
&comprs[..],
|
||||
&mut scope,
|
||||
&mut first_use,
|
||||
&mut used_vars,
|
||||
)?;
|
||||
|
||||
if !extras_scope.locals.is_empty() {
|
||||
for var in extras_scope.locals {
|
||||
definitions.push(Definition {
|
||||
var,
|
||||
used_vars: used_vars.clone(),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
definitions.push(Definition {
|
||||
var: empty_str.clone(),
|
||||
used_vars,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
self.process_expr(expr, &mut scope, &mut first_use, &mut definitions)?;
|
||||
|
||||
Reference in New Issue
Block a user