Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add operator overloading for building grammars. #37

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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