Locked down supported values in default rule.

Only scalars, composites and comprehensions containing scalars are supported.
This will be changed as OPA fixes bug with default values

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2023-02-10 06:09:39 -08:00
parent c92707433e
commit 3a3bbcdb7d
4 changed files with 409 additions and 30 deletions

View File

@@ -474,6 +474,7 @@ impl<'source> Interpreter<'source> {
panic!();
}
}
Literal::NotExpr { expr, .. } => matches!(self.eval_expr(expr)?, Value::Bool(false)),
_ => unimplemented!(),
});
@@ -1347,6 +1348,108 @@ impl<'source> Interpreter<'source> {
}
}
fn check_default_value(&self, expr: &'source Expr<'source>) -> Result<()> {
use Expr::*;
let (kind, span) = match expr {
// Scalars are supported
String(_) | RawString(_) | Number(_) | True(_) | False(_) | Null(_) => return Ok(()),
// Uminus of number is treated as a single expression,
UnaryExpr { expr, .. } if matches!(expr.as_ref(), Number(_)) => return Ok(()),
Var(span) => ("var", span),
// Check each item in array/set.
Array { items, .. } | Set { items, .. } => {
for item in items {
self.check_default_value(item)?;
}
return Ok(());
}
// Check each field in object
Object { fields, .. } => {
for (_, key, value) in fields {
self.check_default_value(key)?;
self.check_default_value(value)?;
}
return Ok(());
}
// Check each statement in comprehensions
ArrayCompr { term, query, .. } | SetCompr { term, query, .. } => {
self.check_default_value(term)?;
for stmt in &query.stmts {
self.check_default_value_in_stmt(stmt)?;
}
return Ok(());
}
ObjectCompr {
key, value, query, ..
} => {
self.check_default_value(key)?;
self.check_default_value(value)?;
for stmt in &query.stmts {
self.check_default_value_in_stmt(stmt)?;
}
return Ok(());
}
Call { span, .. } => ("call", span),
UnaryExpr { span, .. } => ("unaryexpr", span),
RefDot { span, .. } => ("ref", span),
RefBrack { span, .. } => ("ref", span),
BinExpr { span, .. } => ("binexpr", span),
BoolExpr { span, .. } => ("boolexpr", span),
ArithExpr { span, .. } => ("arithexpr", span),
AssignExpr { span, .. } => ("assignexpr", span),
Membership { span, .. } => ("membership", span),
};
Err(span.error(format!("invalid `{kind}` in default value").as_str()))
}
fn check_default_value_in_stmt(&self, stmt: &'source LiteralStmt<'source>) -> Result<()> {
for m in &stmt.with_mods {
self.check_default_value(&m.refr)?;
self.check_default_value(&m.r#as)?;
}
match &stmt.literal {
Literal::SomeVars { span, .. } => {
Err(span.error("invalid `some vars` in default value"))
}
Literal::SomeIn {
key,
value,
collection,
..
} => {
self.check_default_value(key)?;
if let Some(value) = &value {
self.check_default_value(value)?;
}
self.check_default_value(collection)
}
Literal::Expr { expr, .. } | Literal::NotExpr { expr, .. } => {
self.check_default_value(expr)
}
Literal::Every { span, .. } => Err(span.error("invalid `every` in default value")),
}
}
fn check_default_rules(&self) -> Result<()> {
for module in &self.modules {
for rule in &module.policy {
if let Rule::Default { value, .. } = rule {
self.check_default_value(value)?;
}
}
}
Ok(())
}
fn eval_default_rule(&mut self, rule: &'source Rule<'source>) -> Result<()> {
// Skip reprocessing rule.
if self.processed.contains(rule) {
@@ -1368,12 +1471,7 @@ impl<'source> Interpreter<'source> {
Parser::get_path_ref_components_into(refr, &mut path)?;
let paths: Vec<&str> = path.iter().map(|s| s.text()).collect();
if matches!(
value,
Expr::Var(_) | Expr::RefBrack { .. } | Expr::RefDot { .. }
) {
bail!("illegal default rule (value contains a variable or reference)");
}
self.check_default_value(value)?;
let value = self.eval_expr(value)?;
// Assume at this point that all the non-default rules have been evaluated.
@@ -1495,6 +1593,7 @@ impl<'source> Interpreter<'source> {
self.data = data.clone();
}
self.check_default_rules()?;
self.update_function_table()?;
self.gather_rules()?;

View File

@@ -61,6 +61,14 @@ impl<'source> Span<'source> {
pub fn text(&self) -> &'source str {
&self.source.contents[self.start as usize..self.end as usize]
}
pub fn message(&self, kind: &str, msg: &str) -> String {
self.source.message(self.line, self.col, kind, msg)
}
pub fn error(&self, msg: &str) -> anyhow::Error {
self.source.error(self.line, self.col, msg)
}
}
impl<'source> Debug for Span<'source> {

View File

@@ -15,7 +15,6 @@ pub struct Parser<'source> {
line: u16,
end: u16,
future_keywords: BTreeMap<&'source str, Span<'source>>,
in_default_value: bool,
}
const FUTURE_KEYWORDS: [&str; 4] = ["contains", "every", "if", "in"];
@@ -31,7 +30,6 @@ impl<'source> Parser<'source> {
line: 0,
end: 0,
future_keywords: BTreeMap::new(),
in_default_value: false,
})
}
@@ -550,22 +548,6 @@ impl<'source> Parser<'source> {
}
}
if self.in_default_value {
if let Some((kind, span)) = match &term {
Expr::Var(v) => Some(("var", v)),
Expr::RefDot { span, .. } => Some(("ref", span)),
Expr::Call { span, .. } => Some(("call", span)),
Expr::RefBrack { span, .. } => Some(("ref", span)),
_ => None,
} {
return Err(self.source.error(
span.line,
span.col,
format!("invalid {kind} in default value").as_str(),
));
}
}
Ok(term)
}
@@ -1418,9 +1400,7 @@ impl<'source> Parser<'source> {
// todo: Rego errors for binary expressions here, but they are
// somehow valid in a comprehension
self.in_default_value = true;
let value = self.parse_term()?;
self.in_default_value = false;
span.end = self.end;
Ok(Rule::Default {
span,