Skip to content

Commit

Permalink
Add operator overloading for building grammars.
Browse files Browse the repository at this point in the history
  • Loading branch information
teymour-aldridge committed Aug 27, 2022
1 parent 54399b0 commit 7baa33e
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 108 deletions.
138 changes: 105 additions & 33 deletions fuzzcheck/src/mutators/grammar/grammar.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::ops::{Range, RangeBounds, RangeInclusive};
use std::ops::{Add, BitOr, Range, RangeBounds, RangeInclusive};
use std::rc::{Rc, Weak};

#[cfg(feature = "regex_grammar")]
Expand All @@ -8,46 +8,90 @@ use crate::mutators::grammar::regex::grammar_from_regex;
/// A grammar which can be used for fuzzing.
///
/// See [the module documentation](crate::mutators::grammar) for advice on how to create a grammar.
pub enum Grammar {
pub enum GrammarInner {
Literal(Vec<RangeInclusive<char>>),
Alternation(Vec<Rc<Grammar>>),
Concatenation(Vec<Rc<Grammar>>),
Repetition(Rc<Grammar>, Range<usize>),
Recurse(Weak<Grammar>),
Recursive(Rc<Grammar>),
Alternation(Vec<Grammar>),
Concatenation(Vec<Grammar>),
Repetition(Grammar, Range<usize>),
Recurse(Weak<GrammarInner>),
Recursive(Grammar),
}

#[derive(Debug, Clone)]
/// A [`Grammar`] can be transformed into an [`ASTMutator`] (which generates
/// [`String`]s corresponding to the grammar in question) or combined with other
/// grammars to produce a more complicated grammar.
///
/// For examples on how to use this struct, see the crate documentation
/// ([`super`]).
///
/// [`ASTMutator`]: crate::mutators::grammar::ASTMutator
pub struct Grammar(pub(crate) Rc<GrammarInner>);

impl From<Rc<GrammarInner>> for Grammar {
fn from(inner: Rc<GrammarInner>) -> Self {
Grammar(inner)
}
}

impl AsRef<GrammarInner> for Grammar {
fn as_ref(&self) -> &GrammarInner {
&self.0
}
}

impl Add for Grammar {
type Output = Grammar;

/// Calls [`concatenation`] on the two provided grammars.
fn add(self, rhs: Self) -> Self::Output {
concatenation([self, rhs])
}
}

impl BitOr for Grammar {
type Output = Grammar;

/// Calls [`alternation`] on the two provided grammars.
fn bitor(self, rhs: Self) -> Self::Output {
alternation([self, rhs])
}
}

#[cfg(feature = "regex_grammar")]
#[doc(cfg(feature = "regex_grammar"))]
#[no_coverage]
pub fn regex(s: &str) -> Rc<Grammar> {
pub fn regex(s: &str) -> Grammar {
grammar_from_regex(s)
}

#[no_coverage]
/// Creates an [`Rc<Grammar>`] which outputs characters in the given range.
/// Creates a [`Grammar`] which outputs characters in the given range.
///
/// For example, to generate characters in the range 'a' to 'z' (inclusive), one
/// could use this code
///
/// ```
/// let a_to_z = literal_ranges('a'..='z');
/// # use fuzzcheck::mutators::grammar::literal_ranges;
/// let a_to_z = literal_ranges(vec!['a'..='b', 'q'..='s', 't'..='w']);
/// ```
pub fn literal_ranges(ranges: Vec<RangeInclusive<char>>) -> Rc<Grammar> {
Rc::new(Grammar::Literal(ranges))
pub fn literal_ranges(ranges: Vec<RangeInclusive<char>>) -> Grammar {
Rc::new(GrammarInner::Literal(ranges)).into()
}

#[no_coverage]
/// Creates an [`Rc<Grammar>`] which matches a single character literal.
/// Creates a [`Grammar`] which matches a single character literal.
///
/// ```
/// # use fuzzcheck::mutators::grammar::literal;
/// let l = literal('l');
/// ```
pub fn literal(l: char) -> Rc<Grammar> {
Rc::new(Grammar::Literal(vec![l..=l]))
pub fn literal(l: char) -> Grammar {
Rc::new(GrammarInner::Literal(vec![l..=l])).into()
}

#[no_coverage]
pub fn literal_range<R>(range: R) -> Rc<Grammar>
pub fn literal_range<R>(range: R) -> Grammar
where
R: RangeBounds<char>,
{
Expand All @@ -61,34 +105,61 @@ where
std::ops::Bound::Excluded(x) => unsafe { char::from_u32_unchecked(*x as u32 - 1) },
std::ops::Bound::Unbounded => panic!("The range must have an upper bound"),
};
Rc::new(Grammar::Literal(vec![start..=end]))
Rc::new(GrammarInner::Literal(vec![start..=end])).into()
}

/// Produces a grammar which will choose between the provided grammars.
///
/// For example, this grammar
/// ```
/// # use fuzzcheck::mutators::grammar::{Grammar, alternation, regex};
/// let fuzz_or_check: Grammar = alternation([
/// regex("fuzz"),
/// regex("check")
/// ]);
/// ```
/// would output either "fuzz" or "check".
///
/// It is also possible to use the `|` operator to write alternation grammars.
/// For example, the [`Grammar`] above could be equivalently written as
///
/// ```
/// # use fuzzcheck::mutators::grammar::{Grammar, regex};
/// let fuzz_or_check: Grammar = regex("fuzz") | regex("check");
/// ```
#[no_coverage]
pub fn alternation(gs: impl IntoIterator<Item = Rc<Grammar>>) -> Rc<Grammar> {
Rc::new(Grammar::Alternation(gs.into_iter().collect()))
pub fn alternation(gs: impl IntoIterator<Item = Grammar>) -> Grammar {
Rc::new(GrammarInner::Alternation(gs.into_iter().collect())).into()
}

/// Produces a grammar which will concatenate the output of all the provided
/// grammars together, in order.
///
/// For example, the grammar
/// ```
/// concatenation([
/// # use fuzzcheck::mutators::grammar::{concatenation, Grammar, regex};
/// let fuzzcheck: Grammar = concatenation([
/// regex("fuzz"),
/// regex("check")
/// ])
/// ]);
/// ```
/// would output "fuzzcheck".
///
/// It is also possible to use the `+` operator to concatenate separate grammars
/// together. For example, the grammar above could be equivalently written as
///
/// ```
/// # use fuzzcheck::mutators::grammar::{Grammar, regex};
/// let fuzzcheck: Grammar = regex("fuzz") + regex("check");
/// ```
#[no_coverage]
pub fn concatenation(gs: impl IntoIterator<Item = Rc<Grammar>>) -> Rc<Grammar> {
Rc::new(Grammar::Concatenation(gs.into_iter().collect()))
pub fn concatenation(gs: impl IntoIterator<Item = Grammar>) -> Grammar {
Rc::new(GrammarInner::Concatenation(gs.into_iter().collect())).into()
}

#[no_coverage]
/// Repeats the provided grammar some number of times in the given range.
pub fn repetition<R>(gs: Rc<Grammar>, range: R) -> Rc<Grammar>
/// Repeats the provided [`Grammar`] some number of times in the given range.
pub fn repetition<R>(gs: Grammar, range: R) -> Grammar
where
R: RangeBounds<usize>,
{
Expand All @@ -102,25 +173,26 @@ where
std::ops::Bound::Excluded(x) => *x,
std::ops::Bound::Unbounded => usize::MAX,
};
Rc::new(Grammar::Repetition(gs, start..end))
Rc::new(GrammarInner::Repetition(gs, start..end)).into()
}

#[no_coverage]
/// Used to indicate a point of recursion to Fuzzcheck. Should be combined with
/// [`recursive`].
///
/// See the module documentation ([`super`]) for an example on how to use it.
pub fn recurse(g: &Weak<Grammar>) -> Rc<Grammar> {
Rc::new(Grammar::Recurse(g.clone()))
pub fn recurse(g: &Weak<GrammarInner>) -> Grammar {
Rc::new(GrammarInner::Recurse(g.clone())).into()
}

#[no_coverage]
/// Creates a recursive grammar. This function should be combined with
/// Creates a recursive [`Grammar`]. This function should be combined with
/// [`recurse`] to make recursive calls.
///
/// See the module documentation ([`super`]) for an example on how to use it.
pub fn recursive(data_fn: impl Fn(&Weak<Grammar>) -> Rc<Grammar>) -> Rc<Grammar> {
Rc::new(Grammar::Recursive(Rc::new_cyclic(|g| {
Rc::try_unwrap(data_fn(g)).unwrap()
})))
pub fn recursive(data_fn: impl Fn(&Weak<GrammarInner>) -> Grammar) -> Grammar {
Rc::new(GrammarInner::Recursive(
Rc::new_cyclic(|g| Rc::try_unwrap(data_fn(g).0).unwrap()).into(),
))
.into()
}
4 changes: 2 additions & 2 deletions fuzzcheck/src/mutators/grammar/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,10 @@ pub use ast::AST;
#[doc(cfg(feature = "regex_grammar"))]
pub use grammar::regex;
#[doc(inline)]
pub use grammar::Grammar;
#[doc(inline)]
pub use grammar::{alternation, concatenation, literal, literal_range, literal_ranges, recurse, recursive, repetition};
#[doc(inline)]
pub use grammar::{Grammar, GrammarInner};
#[doc(inline)]
pub use mutators::grammar_based_ast_mutator;
#[doc(inline)]
pub use mutators::ASTMutator;
24 changes: 12 additions & 12 deletions fuzzcheck/src/mutators/grammar/mutators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::rc::{Rc, Weak};

use fuzzcheck_mutators_derive::make_single_variant_mutator;

use super::grammar::Grammar;
use super::grammar::{Grammar, GrammarInner};
use crate::mutators::alternation::AlternationMutator;
use crate::mutators::character_classes::CharacterMutator;
use crate::mutators::either::Either3;
Expand Down Expand Up @@ -233,7 +233,7 @@ impl Mutator<AST> for ASTMutator {
}

#[no_coverage]
pub fn grammar_based_ast_mutator(grammar: Rc<Grammar>) -> ASTMutator {
pub fn grammar_based_ast_mutator(grammar: Grammar) -> ASTMutator {
ASTMutator::from_grammar(grammar)
}

Expand Down Expand Up @@ -282,19 +282,19 @@ impl ASTMutator {
}

#[no_coverage]
pub(crate) fn from_grammar(grammar: Rc<Grammar>) -> Self {
pub(crate) fn from_grammar(grammar: Grammar) -> Self {
let mut others = HashMap::new();
Self::from_grammar_rec(grammar, &mut others)
}

#[no_coverage]
pub(crate) fn from_grammar_rec(
grammar: Rc<Grammar>,
others: &mut HashMap<*const Grammar, Weak<ASTMutator>>,
grammar: Grammar,
others: &mut HashMap<*const GrammarInner, Weak<ASTMutator>>,
) -> Self {
match grammar.as_ref() {
Grammar::Literal(l) => Self::token(CharacterMutator::new(l.clone())),
Grammar::Alternation(gs) => Self::alternation(AlternationMutator::new(
GrammarInner::Literal(l) => Self::token(CharacterMutator::new(l.clone())),
GrammarInner::Alternation(gs) => Self::alternation(AlternationMutator::new(
gs.iter()
.map(
#[no_coverage]
Expand All @@ -303,29 +303,29 @@ impl ASTMutator {
.collect(),
0.0,
)),
Grammar::Concatenation(gs) => {
GrammarInner::Concatenation(gs) => {
let mut ms = Vec::<ASTMutator>::new();
for g in gs {
let m = Self::from_grammar_rec(g.clone(), others);
ms.push(m);
}
Self::concatenation(FixedLenVecMutator::new_without_inherent_complexity(ms))
}
Grammar::Repetition(g, range) => Self::repetition(VecMutator::new_without_inherent_complexity(
GrammarInner::Repetition(g, range) => Self::repetition(VecMutator::new_without_inherent_complexity(
Self::from_grammar_rec(g.clone(), others),
range.start..=range.end - 1,
)),
Grammar::Recurse(g) => {
GrammarInner::Recurse(g) => {
if let Some(m) = others.get(&g.as_ptr()) {
Self::recur(RecurToMutator::from(m))
} else {
panic!()
}
}
Grammar::Recursive(g) => Self::recursive(
GrammarInner::Recursive(g) => Self::recursive(
#[no_coverage]
|m| {
let weak_g = Rc::downgrade(g);
let weak_g = Rc::downgrade(&g.0);
others.insert(weak_g.as_ptr(), m.clone());
Self::from_grammar_rec(g.clone(), others)
},
Expand Down
9 changes: 4 additions & 5 deletions fuzzcheck/src/mutators/grammar/regex.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
use std::rc::Rc;

use regex_syntax::hir::{Class, HirKind, Literal, RepetitionKind, RepetitionRange};

use crate::mutators::grammar::{alternation, concatenation, literal, literal_ranges, repetition, Grammar};
use super::grammar::Grammar;
use crate::mutators::grammar::{alternation, concatenation, literal, literal_ranges, repetition};

#[no_coverage]
pub(crate) fn grammar_from_regex(regex: &str) -> Rc<Grammar> {
pub(crate) fn grammar_from_regex(regex: &str) -> Grammar {
let mut parser = regex_syntax::Parser::new();
let hir = parser.parse(regex).unwrap();
grammar_from_regex_hir_kind(hir.kind())
}
#[no_coverage]
pub fn grammar_from_regex_hir_kind(hir: &HirKind) -> Rc<Grammar> {
pub fn grammar_from_regex_hir_kind(hir: &HirKind) -> Grammar {
match hir {
HirKind::Empty => panic!("empty regexes are not supported"),
HirKind::Literal(l) => match l {
Expand Down
Loading

0 comments on commit 7baa33e

Please sign in to comment.