Skip to content

Commit d545a6a

Browse files
fix(parser): ensure all loops advance parsing, fuzz with arbitrary bytes (#828)
* fix(parser): ensure all loops advance parsing * fix(parser): write root operation parsing with a loop This also fixes a case where bogus input was accepted, and did not raise a parse error: ```graphql schema { query: Query { mutation: Mutation { subscription: Subscription } ``` * chore(parser): add peek_while_kind variant for simple loops * fix(parser): add parse_separated_list helper; remove recursion from directive locations parser * add test that fails on main and is fixed here * Add fuzz target parsing arbitrary strings with token limit * fix(parser): always consume token in operation_type() parser * add failing test: stray StringValue at token limit * fix(parser): remove unwrap that may trigger with token limits * fix(parser): fix panic if token limit is reached mid-type * fix(parser): remove unwrap from enum_value parser * Use peek_while_kind * Tweak fuzz comment
1 parent f797730 commit d545a6a

24 files changed

+356
-189
lines changed

crates/apollo-parser/src/lexer/token.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::fmt;
33
use crate::TokenKind;
44

55
/// A token generated by the lexer.
6-
#[derive(Clone)]
6+
#[derive(Clone, PartialEq, Eq)]
77
pub struct Token<'a> {
88
pub(crate) kind: TokenKind,
99
pub(crate) data: &'a str,

crates/apollo-parser/src/parser/grammar/argument.rs

+10-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::parser::grammar::value::Constness;
22
use crate::parser::grammar::{input, name, value};
33
use crate::{Parser, SyntaxKind, TokenKind, S, T};
4+
use std::ops::ControlFlow;
45

56
/// See: https://spec.graphql.org/October2021/#Argument
67
///
@@ -27,9 +28,9 @@ pub(crate) fn arguments(p: &mut Parser, constness: Constness) {
2728
} else {
2829
p.err("expected an Argument");
2930
}
30-
while let Some(TokenKind::Name) = p.peek() {
31+
p.peek_while_kind(TokenKind::Name, |p| {
3132
argument(p, constness);
32-
}
33+
});
3334
p.expect(T![')'], S![')']);
3435
}
3536

@@ -45,9 +46,13 @@ pub(crate) fn arguments_definition(p: &mut Parser) {
4546
} else {
4647
p.err("expected an Argument Definition");
4748
}
48-
while let Some(TokenKind::Name | TokenKind::StringValue) = p.peek() {
49-
input::input_value_definition(p);
50-
}
49+
p.peek_while(|p, kind| match kind {
50+
TokenKind::Name | TokenKind::StringValue => {
51+
input::input_value_definition(p);
52+
ControlFlow::Continue(())
53+
}
54+
_ => ControlFlow::Break(()),
55+
});
5156
p.expect(T![')'], S![')']);
5257
}
5358

crates/apollo-parser/src/parser/grammar/directive.rs

+36-31
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::parser::grammar::value::Constness;
22
use crate::parser::grammar::{argument, description, input, name};
33
use crate::{Parser, SyntaxKind, TokenKind, S, T};
4+
use std::ops::ControlFlow;
45

56
/// See: https://spec.graphql.org/October2021/#DirectiveDefinition
67
///
@@ -31,9 +32,13 @@ pub(crate) fn directive_definition(p: &mut Parser) {
3132
} else {
3233
p.err("expected an Argument Definition");
3334
}
34-
while let Some(TokenKind::Name | TokenKind::StringValue) = p.peek() {
35-
input::input_value_definition(p);
36-
}
35+
p.peek_while(|p, kind| match kind {
36+
TokenKind::Name | TokenKind::StringValue => {
37+
input::input_value_definition(p);
38+
ControlFlow::Continue(())
39+
}
40+
_ => ControlFlow::Break(()),
41+
});
3742
p.expect(T![')'], S![')']);
3843
}
3944

@@ -52,29 +57,27 @@ pub(crate) fn directive_definition(p: &mut Parser) {
5257

5358
if let Some(TokenKind::Name | T![|]) = p.peek() {
5459
let _g = p.start_node(SyntaxKind::DIRECTIVE_LOCATIONS);
55-
if let Some(T![|]) = p.peek() {
56-
p.bump(S![|]);
57-
}
58-
directive_locations(p, false);
60+
directive_locations(p);
5961
} else {
6062
p.err("expected valid Directive Location");
6163
}
6264
}
6365

64-
/// See: https://spec.graphql.org/October2021/#DirectiveLocations
66+
/// https://spec.graphql.org/October2021/#DirectiveLocation
6567
///
66-
/// *DirectiveLocations*:
67-
/// DirectiveLocations **|** DirectiveLocation
68-
/// **|**? DirectiveLocation
69-
pub(crate) fn directive_locations(p: &mut Parser, is_location: bool) {
70-
if let Some(T![|]) = p.peek() {
71-
p.bump(S![|]);
72-
directive_locations(p, false);
73-
}
68+
/// *DirectiveLocation*:
69+
/// *ExecutableDirectiveLocation*
70+
/// *TypeSystemDirectiveLocation*
71+
///
72+
/// (This function does not distinguish between the two groups of
73+
/// locations.)
74+
fn directive_location(p: &mut Parser) {
75+
let Some(token) = p.peek_token() else {
76+
return;
77+
};
7478

75-
if let Some(TokenKind::Name) = p.peek() {
76-
let loc = p.peek_data().unwrap();
77-
match loc {
79+
if token.kind == TokenKind::Name {
80+
match token.data {
7881
"QUERY" => {
7982
let _g = p.start_node(SyntaxKind::DIRECTIVE_LOCATION);
8083
p.bump(SyntaxKind::QUERY_KW);
@@ -152,21 +155,23 @@ pub(crate) fn directive_locations(p: &mut Parser, is_location: bool) {
152155
p.bump(SyntaxKind::INPUT_FIELD_DEFINITION_KW);
153156
}
154157
_ => {
155-
if !is_location {
156-
p.err("expected valid Directive Location");
157-
}
158-
return;
158+
p.err("expected valid Directive Location");
159159
}
160160
}
161-
if p.peek_data().is_some() {
162-
return directive_locations(p, true);
163-
}
164-
}
165-
if !is_location {
166-
p.err("expected Directive Locations");
161+
} else {
162+
p.err("expected Directive Location");
167163
}
168164
}
169165

166+
/// See: https://spec.graphql.org/October2021/#DirectiveLocations
167+
///
168+
/// *DirectiveLocations*:
169+
/// DirectiveLocations **|** DirectiveLocation
170+
/// **|**? DirectiveLocation
171+
pub(crate) fn directive_locations(p: &mut Parser) {
172+
p.parse_separated_list(T![|], S![|], directive_location);
173+
}
174+
170175
/// See: https://spec.graphql.org/October2021/#Directive
171176
///
172177
/// *Directive[Const]*:
@@ -188,9 +193,9 @@ pub(crate) fn directive(p: &mut Parser, constness: Constness) {
188193
/// Directive[?Const]*
189194
pub(crate) fn directives(p: &mut Parser, constness: Constness) {
190195
let _g = p.start_node(SyntaxKind::DIRECTIVES);
191-
while let Some(T![@]) = p.peek() {
196+
p.peek_while_kind(T![@], |p| {
192197
directive(p, constness);
193-
}
198+
});
194199
}
195200

196201
#[cfg(test)]

crates/apollo-parser/src/parser/grammar/document.rs

+29-6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::{
55
},
66
Parser, SyntaxKind, TokenKind,
77
};
8+
use std::ops::ControlFlow;
89

910
/// See: https://spec.graphql.org/October2021/#Document
1011
///
@@ -13,16 +14,19 @@ use crate::{
1314
pub(crate) fn document(p: &mut Parser) {
1415
let doc = p.start_node(SyntaxKind::DOCUMENT);
1516

16-
while let Some(node) = p.peek() {
17+
p.peek_while(|p, kind| {
1718
assert_eq!(
1819
p.recursion_limit.current, 0,
1920
"unbalanced limit increment / decrement"
2021
);
2122

22-
match node {
23+
match kind {
2324
TokenKind::StringValue => {
24-
let def = p.peek_data_n(2).unwrap();
25-
select_definition(def, p);
25+
if let Some(def) = p.peek_data_n(2) {
26+
select_definition(def, p);
27+
} else {
28+
p.err_and_pop("expected a definition after this StringValue");
29+
}
2630
}
2731
TokenKind::Name => {
2832
let def = p.peek_data().unwrap();
@@ -32,10 +36,12 @@ pub(crate) fn document(p: &mut Parser) {
3236
let def = p.peek_data().unwrap();
3337
select_definition(def, p);
3438
}
35-
TokenKind::Eof => break,
39+
TokenKind::Eof => return ControlFlow::Break(()),
3640
_ => p.err_and_pop("expected a StringValue, Name or OperationDefinition"),
3741
}
38-
}
42+
43+
ControlFlow::Continue(())
44+
});
3945

4046
p.push_ignored();
4147

@@ -274,4 +280,21 @@ enum join__Graph {
274280
}
275281
}
276282
}
283+
284+
#[test]
285+
fn trailing_description_at_limit() {
286+
let parser = crate::Parser::new(
287+
r#"
288+
"All our queries"
289+
type Query {
290+
a: Int
291+
}
292+
293+
"Imagine another type below!"
294+
"#,
295+
)
296+
.token_limit(18);
297+
// Must not panic
298+
let _cst = parser.parse();
299+
}
277300
}

crates/apollo-parser/src/parser/grammar/enum_.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use crate::parser::grammar::value::Constness;
44
use crate::parser::grammar::{description, directive, name, value};
55
use crate::{Parser, SyntaxKind, TokenKind, S, T};
6+
use std::ops::ControlFlow;
67

78
/// See: https://spec.graphql.org/October2021/#EnumTypeDefinition
89
///
@@ -78,9 +79,13 @@ pub(crate) fn enum_values_definition(p: &mut Parser) {
7879
_ => p.err("expected Enum Value Definition"),
7980
}
8081

81-
while let Some(TokenKind::Name | TokenKind::StringValue) = p.peek() {
82-
enum_value_definition(p);
83-
}
82+
p.peek_while(|p, kind| match kind {
83+
TokenKind::Name | TokenKind::StringValue => {
84+
enum_value_definition(p);
85+
ControlFlow::Continue(())
86+
}
87+
_ => ControlFlow::Break(()),
88+
});
8489

8590
p.expect(T!['}'], S!['}']);
8691
}

crates/apollo-parser/src/parser/grammar/field.rs

+8-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use crate::parser::grammar::value::Constness;
44
use crate::parser::grammar::{argument, description, directive, name, selection, ty};
55
use crate::{Parser, SyntaxKind, TokenKind, S, T};
6+
use std::ops::ControlFlow;
67

78
/// See: https://spec.graphql.org/October2021/#Field
89
///
@@ -40,10 +41,13 @@ pub(crate) fn field(p: &mut Parser) {
4041
pub(crate) fn fields_definition(p: &mut Parser) {
4142
let _g = p.start_node(SyntaxKind::FIELDS_DEFINITION);
4243
p.bump(S!['{']);
43-
while let Some(TokenKind::Name | TokenKind::StringValue) = p.peek() {
44-
// Guaranteed to eat at least one token if the next token is a Name or StringValue
45-
field_definition(p);
46-
}
44+
p.peek_while(|p, kind| match kind {
45+
TokenKind::Name | TokenKind::StringValue => {
46+
field_definition(p);
47+
ControlFlow::Continue(())
48+
}
49+
_ => ControlFlow::Break(()),
50+
});
4751
p.expect(T!['}'], S!['}']);
4852
}
4953

crates/apollo-parser/src/parser/grammar/input.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::parser::grammar::value::Constness;
22
use crate::parser::grammar::{description, directive, name, ty, value};
33
use crate::{Parser, SyntaxKind, TokenKind, S, T};
4+
use std::ops::ControlFlow;
45

56
/// See: https://spec.graphql.org/October2021/#InputObjectTypeDefinition
67
///
@@ -76,9 +77,14 @@ pub(crate) fn input_fields_definition(p: &mut Parser) {
7677
} else {
7778
p.err("expected an Input Value Definition");
7879
}
79-
while let Some(TokenKind::Name | TokenKind::StringValue) = p.peek() {
80-
input_value_definition(p);
81-
}
80+
p.peek_while(|p, kind| {
81+
if matches!(kind, TokenKind::Name | TokenKind::StringValue) {
82+
input_value_definition(p);
83+
ControlFlow::Continue(())
84+
} else {
85+
ControlFlow::Break(())
86+
}
87+
});
8288

8389
p.expect(T!['}'], S!['}']);
8490
}

crates/apollo-parser/src/parser/grammar/object.rs

+2-13
Original file line numberDiff line numberDiff line change
@@ -90,24 +90,13 @@ pub(crate) fn implements_interfaces(p: &mut Parser) {
9090
let _g = p.start_node(SyntaxKind::IMPLEMENTS_INTERFACES);
9191
p.bump(SyntaxKind::implements_KW);
9292

93-
if let Some(T![&]) = p.peek() {
94-
p.bump(S![&]);
95-
}
96-
97-
if let Some(TokenKind::Name) = p.peek() {
98-
ty::named_type(p);
99-
} else {
100-
p.err("expected an Interface name");
101-
}
102-
103-
while let Some(T![&]) = p.peek() {
104-
p.bump(S![&]);
93+
p.parse_separated_list(T![&], S![&], |p| {
10594
if let Some(TokenKind::Name) = p.peek() {
10695
ty::named_type(p);
10796
} else {
10897
p.err("expected an Interface name");
10998
}
110-
}
99+
});
111100
}
112101

113102
#[cfg(test)]

crates/apollo-parser/src/parser/grammar/operation.rs

+3-43
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,6 @@
11
use crate::parser::grammar::value::Constness;
2-
use crate::parser::grammar::{directive, name, selection, ty, variable};
3-
use crate::{Parser, SyntaxKind, TokenKind, S, T};
4-
5-
/// RootOperationTypeDefinition is used in a SchemaDefinition. Not to be confused
6-
/// with OperationDefinition.
7-
///
8-
/// See: https://spec.graphql.org/October2021/#RootOperationTypeDefinition
9-
///
10-
/// *RootOperationTypeDefinition*:
11-
/// OperationType **:** NamedType
12-
pub(crate) fn root_operation_type_definition(p: &mut Parser, is_operation_type: bool) {
13-
if let Some(T!['{']) = p.peek() {
14-
p.bump(S!['{']);
15-
}
16-
17-
if let Some(TokenKind::Name) = p.peek() {
18-
let guard = p.start_node(SyntaxKind::ROOT_OPERATION_TYPE_DEFINITION);
19-
operation_type(p);
20-
if let Some(T![:]) = p.peek() {
21-
p.bump(S![:]);
22-
ty::named_type(p);
23-
if p.peek().is_some() {
24-
guard.finish_node();
25-
26-
// TODO use a loop instead of recursion
27-
if p.recursion_limit.check_and_increment() {
28-
p.limit_err("parser recursion limit reached");
29-
return;
30-
}
31-
root_operation_type_definition(p, true);
32-
p.recursion_limit.decrement();
33-
return;
34-
}
35-
} else {
36-
p.err("expected a Name Type");
37-
}
38-
}
39-
40-
if !is_operation_type {
41-
p.err("expected an Operation Type");
42-
}
43-
}
2+
use crate::parser::grammar::{directive, name, selection, variable};
3+
use crate::{Parser, SyntaxKind, TokenKind, T};
444

455
/// See: https://spec.graphql.org/October2021/#OperationDefinition
466
///
@@ -92,7 +52,7 @@ pub(crate) fn operation_type(p: &mut Parser) {
9252
"query" => p.bump(SyntaxKind::query_KW),
9353
"subscription" => p.bump(SyntaxKind::subscription_KW),
9454
"mutation" => p.bump(SyntaxKind::mutation_KW),
95-
_ => p.err("expected either a 'mutation', a 'query', or a 'subscription'"),
55+
_ => p.err_and_pop("expected either a 'mutation', a 'query', or a 'subscription'"),
9656
}
9757
}
9858
}

0 commit comments

Comments
 (0)