Skip to content

Commit

Permalink
Limits the recursion depth of the parser.
Browse files Browse the repository at this point in the history
  • Loading branch information
sunli829 committed Aug 9, 2022
1 parent f0dc854 commit 1f67e3a
Show file tree
Hide file tree
Showing 8 changed files with 2,082 additions and 2,224 deletions.
2 changes: 1 addition & 1 deletion parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ version = "4.0.6"

[dependencies]
async-graphql-value = { path = "../value", version = "4.0.6" }
pest = "2.1.3"
pest = "2.2.1"
serde = { version = "1.0.125", features = ["derive"] }
serde_json = "1.0.64"

Expand Down
2 changes: 1 addition & 1 deletion parser/src/graphql.pest
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ selection_set = { "{" ~ selection+ ~ "}" }
selection = { field | inline_fragment | fragment_spread }
field = { alias? ~ name ~ arguments? ~ directives? ~ selection_set? }
alias = { name ~ ":" }
fragment_spread = { "..." ~ name ~ directives? }
fragment_spread = { "..." ~ !type_condition ~ name ~ directives? }
inline_fragment = { "..." ~ type_condition? ~ directives? ~ selection_set }

fragment_definition = { "fragment" ~ name ~ type_condition ~ directives? ~ selection_set }
Expand Down
4 changes: 4 additions & 0 deletions parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ pub enum Error {
},
/// The document does not contain any operation.
MissingOperation,
/// Recursion limit exceeded.
RecursionLimitExceeded,
}

impl Error {
Expand Down Expand Up @@ -106,6 +108,7 @@ impl Error {
ErrorPositions::new_2(*second, *first)
}
Self::MissingOperation => ErrorPositions::new_0(),
Self::RecursionLimitExceeded => ErrorPositions::new_0(),
}
}
}
Expand All @@ -126,6 +129,7 @@ impl Display for Error {
write!(f, "fragment {} is defined twice", fragment)
}
Self::MissingOperation => f.write_str("document does not contain an operation"),
Self::RecursionLimitExceeded => f.write_str("recursion limit exceeded."),
}
}
}
Expand Down
55 changes: 38 additions & 17 deletions parser/src/parse/executable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@ use async_graphql_value::Name;

use super::*;

const MAX_RECURSION_DEPTH: usize = 64;

macro_rules! recursion_depth {
($remaining_depth:ident) => {{
if $remaining_depth == 0 {
return Err(Error::RecursionLimitExceeded);
}
$remaining_depth - 1
}};
}

/// Parse a GraphQL query document.
///
/// # Errors
Expand All @@ -10,13 +21,10 @@ use super::*;
pub fn parse_query<T: AsRef<str>>(input: T) -> Result<ExecutableDocument> {
let mut pc = PositionCalculator::new(input.as_ref());

let items = parse_definition_items(
exactly_one(GraphQLParser::parse(
Rule::executable_document,
input.as_ref(),
)?),
&mut pc,
)?;
eprintln!("1");
let pairs = GraphQLParser::parse(Rule::executable_document, input.as_ref())?;
eprintln!("2");
let items = parse_definition_items(exactly_one(pairs), &mut pc)?;

let mut operations = None;
let mut fragments: HashMap<_, Positioned<FragmentDefinition>> = HashMap::new();
Expand Down Expand Up @@ -149,7 +157,7 @@ fn parse_operation_definition_item(
ty: OperationType::Query,
variable_definitions: Vec::new(),
directives: Vec::new(),
selection_set: parse_selection_set(pair, pc)?,
selection_set: parse_selection_set(pair, pc, MAX_RECURSION_DEPTH)?,
},
},
_ => unreachable!(),
Expand All @@ -172,7 +180,7 @@ fn parse_named_operation_definition(
parse_variable_definitions(pair, pc)
})?;
let directives = parse_opt_directives(&mut pairs, pc)?;
let selection_set = parse_selection_set(pairs.next().unwrap(), pc)?;
let selection_set = parse_selection_set(pairs.next().unwrap(), pc, MAX_RECURSION_DEPTH)?;

debug_assert_eq!(pairs.next(), None);

Expand Down Expand Up @@ -231,6 +239,7 @@ fn parse_variable_definition(
fn parse_selection_set(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
remaining_depth: usize,
) -> Result<Positioned<SelectionSet>> {
debug_assert_eq!(pair.as_rule(), Rule::selection_set);

Expand All @@ -240,31 +249,41 @@ fn parse_selection_set(
SelectionSet {
items: pair
.into_inner()
.map(|pair| parse_selection(pair, pc))
.map(|pair| parse_selection(pair, pc, remaining_depth))
.collect::<Result<_>>()?,
},
pos,
))
}

fn parse_selection(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<Selection>> {
fn parse_selection(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
remaining_depth: usize,
) -> Result<Positioned<Selection>> {
debug_assert_eq!(pair.as_rule(), Rule::selection);

let pos = pc.step(&pair);
let pair = exactly_one(pair.into_inner());

Ok(Positioned::new(
match pair.as_rule() {
Rule::field => Selection::Field(parse_field(pair, pc)?),
Rule::field => Selection::Field(parse_field(pair, pc, remaining_depth)?),
Rule::fragment_spread => Selection::FragmentSpread(parse_fragment_spread(pair, pc)?),
Rule::inline_fragment => Selection::InlineFragment(parse_inline_fragment(pair, pc)?),
Rule::inline_fragment => {
Selection::InlineFragment(parse_inline_fragment(pair, pc, remaining_depth)?)
}
_ => unreachable!(),
},
pos,
))
}

fn parse_field(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<Field>> {
fn parse_field(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
remaining_depth: usize,
) -> Result<Positioned<Field>> {
debug_assert_eq!(pair.as_rule(), Rule::field);

let pos = pc.step(&pair);
Expand All @@ -277,7 +296,7 @@ fn parse_field(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Position
})?;
let directives = parse_opt_directives(&mut pairs, pc)?;
let selection_set = parse_if_rule(&mut pairs, Rule::selection_set, |pair| {
parse_selection_set(pair, pc)
parse_selection_set(pair, pc, recursion_depth!(remaining_depth))
})?;

debug_assert_eq!(pairs.next(), None);
Expand Down Expand Up @@ -325,6 +344,7 @@ fn parse_fragment_spread(
fn parse_inline_fragment(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
remaining_depth: usize,
) -> Result<Positioned<InlineFragment>> {
debug_assert_eq!(pair.as_rule(), Rule::inline_fragment);

Expand All @@ -335,7 +355,8 @@ fn parse_inline_fragment(
parse_type_condition(pair, pc)
})?;
let directives = parse_opt_directives(&mut pairs, pc)?;
let selection_set = parse_selection_set(pairs.next().unwrap(), pc)?;
let selection_set =
parse_selection_set(pairs.next().unwrap(), pc, recursion_depth!(remaining_depth))?;

debug_assert_eq!(pairs.next(), None);

Expand Down Expand Up @@ -366,7 +387,7 @@ fn parse_fragment_definition_item(
let name = parse_name(pairs.next().unwrap(), pc)?;
let type_condition = parse_type_condition(pairs.next().unwrap(), pc)?;
let directives = parse_opt_directives(&mut pairs, pc)?;
let selection_set = parse_selection_set(pairs.next().unwrap(), pc)?;
let selection_set = parse_selection_set(pairs.next().unwrap(), pc, MAX_RECURSION_DEPTH)?;

debug_assert_eq!(pairs.next(), None);

Expand Down
Loading

0 comments on commit 1f67e3a

Please sign in to comment.