Skip to content

Commit

Permalink
feat: add for expression with 'at' keyword
Browse files Browse the repository at this point in the history
  • Loading branch information
vthib committed Mar 5, 2023
1 parent 508ce60 commit b26fbc3
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 25 deletions.
102 changes: 86 additions & 16 deletions boreal-parser/src/expression/for_expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::ops::Range;
use nom::{
branch::alt,
character::complete::char,
combinator::{cut, map, opt},
combinator::{cut, map, opt, success},
multi::separated_list1,
sequence::{delimited, preceded, terminated},
};
Expand Down Expand Up @@ -58,6 +58,7 @@ pub(super) fn for_expression_non_ambiguous(input: Input) -> ParseResult<Expressi
/// This parses:
/// - `selection 'of' set`
/// - `selection 'of' set 'in' range`
/// - `selection 'of' set 'at' expr`
///
/// But with 'selection' not being an expression.
fn for_expression_abbrev(input: Input) -> ParseResult<Expression> {
Expand Down Expand Up @@ -100,22 +101,28 @@ fn for_expression_with_selection<'a>(
Ok((input, set)) => (input, ExpressionKind::ForRules { selection, set }),
Err(_) => {
let (input, set) = cut(string_set)(input)?;
let (input, range) = opt(preceded(rtrim(ttag("in")), cut(range)))(input)?;

let kind = match range {
None => ExpressionKind::For {
selection,
set,
body: None,
},
Some((from, to)) => ExpressionKind::ForIn {
selection,
set,
from,
to,
let (input, kind) = for_expression_kind(input)?;
(
input,
match kind {
ForExprKind::None => ExpressionKind::For {
selection,
set,
body: None,
},
ForExprKind::In(from, to) => ExpressionKind::ForIn {
selection,
set,
from,
to,
},
ForExprKind::At(offset) => ExpressionKind::ForAt {
selection,
set,
offset,
},
},
};
(input, kind)
)
}
};

Expand All @@ -128,6 +135,25 @@ fn for_expression_with_selection<'a>(
))
}

enum ForExprKind {
None,
In(Box<Expression>, Box<Expression>),
At(Box<Expression>),
}

fn for_expression_kind(input: Input) -> ParseResult<ForExprKind> {
alt((
map(preceded(rtrim(ttag("in")), cut(range)), |(a, b)| {
ForExprKind::In(a, b)
}),
map(
preceded(rtrim(ttag("at")), cut(primary_expression)),
|expr| ForExprKind::At(Box::new(expr)),
),
map(success(()), |_| ForExprKind::None),
))(input)
}

/// Parse a full fledge for expression:
///
/// This parses:
Expand Down Expand Up @@ -605,6 +631,48 @@ mod tests {
span: 0..13,
},
);
parse(
boolean_expression,
"5 of ($a, $b*) at f.b",
"",
Expression {
expr: ExpressionKind::ForAt {
selection: ForSelection::Expr {
expr: Box::new(Expression {
expr: ExpressionKind::Integer(5),
span: 0..1,
}),
as_percent: false,
},
set: VariableSet {
elements: vec![
SetElement {
name: "a".to_owned(),
is_wildcard: false,
span: 6..8,
},
SetElement {
name: "b".to_owned(),
is_wildcard: true,
span: 10..13,
},
],
},
offset: Box::new(Expression {
expr: ExpressionKind::Identifier(Identifier {
name: "f".to_owned(),
name_span: 18..19,
operations: vec![IdentifierOperation {
op: IdentifierOperationType::Subfield("b".to_owned()),
span: 19..21,
}],
}),
span: 18..21,
}),
},
span: 0..21,
},
);

parse_err(boolean_expression, "for true");
parse_err(boolean_expression, "2% /*");
Expand All @@ -616,6 +684,8 @@ mod tests {
parse_err(for_expression_abbrev, "any of thema");
parse_err(for_expression_abbrev, "all of them in");
parse_err(for_expression_abbrev, "all of them in ()");
parse_err(for_expression_abbrev, "all of them at");
parse_err(for_expression_abbrev, "all of them at ()");
}

#[test]
Expand Down
15 changes: 14 additions & 1 deletion boreal-parser/src/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ pub enum ExpressionKind {
body: Option<Box<Expression>>,
},

/// Evaluate multiple variables on a given range.
/// Evaluate the presence of multiple variables in a given range.
///
/// This is equivalent to a [`Self::For`] value, with a body
/// set to `$ in (from..to)`.
Expand All @@ -306,6 +306,19 @@ pub enum ExpressionKind {
to: Box<Expression>,
},

/// Evaluate the presence of multiple variables at a given offset.
///
/// This is equivalent to a [`Self::For`] value, with a body
/// set to `$ at expr`.
ForAt {
/// How many variables must match for this expresion to be true.
selection: ForSelection,
/// Which variables to select.
set: VariableSet,
/// Offset of the variable match.
offset: Box<Expression>,
},

/// Evaluate an identifier with multiple values on a given expression.
///
/// Same as [`Self::For`], but instead of binding a variable,
Expand Down
21 changes: 21 additions & 0 deletions boreal/src/compiler/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,27 @@ pub(super) fn compile_expression(
})
}

parser::ExpressionKind::ForAt {
selection,
set,
offset,
} => {
let offset = compile_expression(compiler, *offset)?;

Ok(Expr {
expr: Expression::For {
selection: compile_for_selection(compiler, selection)?,
set: compile_variable_set(compiler, set, span.clone())?,
body: Box::new(Expression::VariableAt {
variable_index: VariableIndex(None),
offset: offset.unwrap_expr(Type::Integer)?,
}),
},
ty: Type::Boolean,
span,
})
}

parser::ExpressionKind::ForIdentifiers {
selection,
identifiers,
Expand Down
36 changes: 36 additions & 0 deletions boreal/tests/it/for_expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -685,3 +685,39 @@ fn test_for_expression_undefined() {
true,
);
}

#[test]
fn test_for_identifiers_abbrev() {
let build = |cond: &str| {
format!(
r#"rule foo {{
strings:
$a = /aaa/
$b = /bbb/
condition:
{cond}
}}"#,
)
};

let checker = Checker::new(&build("any of ($a, $b) in (0..5)"));
checker.check(b"", false);
checker.check(b"aaa a a a", true);
checker.check(b"a a a aaa", false);
checker.check(b"a bbb aaa", true);

let checker = Checker::new(&build("all of ($a, $b*) in (2..5)"));
checker.check(b"", false);
checker.check(b"aaabbb", false);
checker.check(b" aaabbb", false);
checker.check(b" aaabbb", true);
checker.check(b" aaabbb", false);

let checker = Checker::new(&build("any of them at 2"));
checker.check(b"", false);
checker.check(b"aaabbb", false);
checker.check(b" aaabbb", false);
checker.check(b" aaabbb", true);
checker.check(b" aaabbb", false);
checker.check(b" bbbaaa", true);
}
15 changes: 7 additions & 8 deletions boreal/tests/it/libyara_compat/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1936,14 +1936,13 @@ fn test_count() {

#[test]
fn test_at() {
// FIXME: to enable
// check(
// "rule test {
// strings: $a = \"miss\"
// condition: any of them at 0}",
// b"mississippi",
// true,
// );
check(
"rule test {
strings: $a = \"miss\"
condition: any of them at 0}",
b"mississippi",
true,
);

check(
"rule test {
Expand Down

0 comments on commit b26fbc3

Please sign in to comment.