Skip to content

Commit

Permalink
[parser] bail on deeply nested expressions (#718)
Browse files Browse the repository at this point in the history
  • Loading branch information
zsol authored Jul 4, 2022
1 parent c894160 commit 343f56f
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 9 deletions.
16 changes: 16 additions & 0 deletions libcst/_nodes/tests/test_binary_op.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import libcst as cst
from libcst import parse_expression
from libcst._nodes.tests.base import CSTNodeTest
from libcst._parser.entrypoints import is_native
from libcst.metadata import CodeRange
from libcst.testing.utils import data_provider

Expand Down Expand Up @@ -174,3 +175,18 @@ def test_valid(self, **kwargs: Any) -> None:
)
def test_invalid(self, **kwargs: Any) -> None:
self.assert_invalid(**kwargs)

@data_provider(
(
{
"code": '"a"' * 6000,
"parser": parse_expression,
},
{
"code": "[_" + " for _ in _" * 6000 + "]",
"parser": parse_expression,
},
)
)
def test_parse_error(self, **kwargs: Any) -> None:
self.assert_parses(**kwargs, expect_success=not is_native())
2 changes: 1 addition & 1 deletion native/libcst/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ trace = ["peg/trace"]

[dependencies]
paste = "1.0.4"
pyo3 = { version = "0.16.5", optional = true }
pyo3 = { version = "0.16", optional = true }
thiserror = "1.0.23"
peg = "0.8.0"
chic = "1.2.2"
Expand Down
26 changes: 18 additions & 8 deletions native/libcst/src/parser/grammar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ impl<'input, 'a: 'input> ParseElem<'input> for TokVec<'a> {
}
}

const MAX_RECURSION_DEPTH: usize = 3000;

parser! {
pub grammar python<'a>(input: &'a str) for TokVec<'a> {

Expand Down Expand Up @@ -1117,7 +1119,7 @@ parser! {

rule strings() -> String<'input, 'a>
= s:(str:tok(STRING, "STRING") t:&_ {(make_string(str), t)}
/ str:fstring() t:&_ {(String::Formatted(str), t)})+ {
/ str:fstring() t:&_ {(String::Formatted(str), t)})+ {?
make_strings(s)
}

Expand Down Expand Up @@ -1171,7 +1173,7 @@ parser! {
// Comprehensions & generators

rule for_if_clauses() -> CompFor<'input, 'a>
= c:for_if_clause()+ { merge_comp_fors(c) }
= c:for_if_clause()+ {? merge_comp_fors(c) }

rule for_if_clause() -> CompFor<'input, 'a>
= asy:_async() f:lit("for") tgt:star_targets() i:lit("in")
Expand Down Expand Up @@ -2240,14 +2242,19 @@ fn make_bare_genexp<'input, 'a>(
}
}

fn merge_comp_fors<'input, 'a>(comp_fors: Vec<CompFor<'input, 'a>>) -> CompFor<'input, 'a> {
fn merge_comp_fors<'input, 'a>(
comp_fors: Vec<CompFor<'input, 'a>>,
) -> GrammarResult<CompFor<'input, 'a>> {
if comp_fors.len() > MAX_RECURSION_DEPTH {
return Err("shallower comprehension");
}
let mut it = comp_fors.into_iter().rev();
let first = it.next().expect("cant merge empty comp_fors");

it.fold(first, |acc, curr| CompFor {
Ok(it.fold(first, |acc, curr| CompFor {
inner_for_in: Some(Box::new(acc)),
..curr
})
}))
}

fn make_left_bracket<'input, 'a>(tok: TokenRef<'input, 'a>) -> LeftSquareBracket<'input, 'a> {
Expand Down Expand Up @@ -2816,10 +2823,13 @@ fn make_string<'input, 'a>(tok: TokenRef<'input, 'a>) -> String<'input, 'a> {

fn make_strings<'input, 'a>(
s: Vec<(String<'input, 'a>, TokenRef<'input, 'a>)>,
) -> String<'input, 'a> {
) -> GrammarResult<String<'input, 'a>> {
if s.len() > MAX_RECURSION_DEPTH {
return Err("shorter concatenated string");
}
let mut strings = s.into_iter().rev();
let (first, _) = strings.next().expect("no strings to make a string of");
strings.fold(first, |acc, (str, tok)| {
Ok(strings.fold(first, |acc, (str, tok)| {
let ret: String<'input, 'a> = String::Concatenated(ConcatenatedString {
left: Box::new(str),
right: Box::new(acc),
Expand All @@ -2828,7 +2838,7 @@ fn make_strings<'input, 'a>(
right_tok: tok,
});
ret
})
}))
}

fn make_fstring_expression<'input, 'a>(
Expand Down

0 comments on commit 343f56f

Please sign in to comment.