fix(query): prevent infinite loop with + and ? quantifiers
**Problem:** A query with a `?` quantifier followed by a `+` quantifier would hang at 100% CPU usage while iterating through a tree, regardless of the source content. **Solution:** Collect all quantifiers in one step, and then add the required repeat/optional step logic *after* we have determined the composite quantifier we need to use for the current step.
This commit is contained in:
parent
d64b863030
commit
829733a35e
2 changed files with 57 additions and 27 deletions
|
|
@ -5032,6 +5032,26 @@ fn test_query_quantified_captures() {
|
|||
("comment.documentation", "// quuz"),
|
||||
],
|
||||
},
|
||||
Row {
|
||||
description: "multiple quantifiers should not hang query parsing",
|
||||
language: get_language("c"),
|
||||
code: indoc! {"
|
||||
// foo
|
||||
// bar
|
||||
// baz
|
||||
"},
|
||||
pattern: r"
|
||||
((comment) ?+ @comment)
|
||||
",
|
||||
// This should be identical to the `*` quantifier.
|
||||
captures: &[
|
||||
("comment", "// foo"),
|
||||
("comment", "// foo"),
|
||||
("comment", "// foo"),
|
||||
("comment", "// bar"),
|
||||
("comment", "// baz"),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
allocations::record(|| {
|
||||
|
|
|
|||
|
|
@ -2736,12 +2736,6 @@ static TSQueryError ts_query__parse_pattern(
|
|||
|
||||
stream_advance(stream);
|
||||
stream_skip_whitespace(stream);
|
||||
|
||||
QueryStep repeat_step = query_step__new(WILDCARD_SYMBOL, depth, false);
|
||||
repeat_step.alternative_index = starting_step_index;
|
||||
repeat_step.is_pass_through = true;
|
||||
repeat_step.alternative_is_immediate = true;
|
||||
array_push(&self->steps, repeat_step);
|
||||
}
|
||||
|
||||
// Parse the zero-or-more repetition operator.
|
||||
|
|
@ -2750,21 +2744,6 @@ static TSQueryError ts_query__parse_pattern(
|
|||
|
||||
stream_advance(stream);
|
||||
stream_skip_whitespace(stream);
|
||||
|
||||
QueryStep repeat_step = query_step__new(WILDCARD_SYMBOL, depth, false);
|
||||
repeat_step.alternative_index = starting_step_index;
|
||||
repeat_step.is_pass_through = true;
|
||||
repeat_step.alternative_is_immediate = true;
|
||||
array_push(&self->steps, repeat_step);
|
||||
|
||||
// Stop when `step->alternative_index` is `NONE` or it points to
|
||||
// `repeat_step` or beyond. Note that having just been pushed,
|
||||
// `repeat_step` occupies slot `self->steps.size - 1`.
|
||||
QueryStep *step = array_get(&self->steps, starting_step_index);
|
||||
while (step->alternative_index != NONE && step->alternative_index < self->steps.size - 1) {
|
||||
step = array_get(&self->steps, step->alternative_index);
|
||||
}
|
||||
step->alternative_index = self->steps.size;
|
||||
}
|
||||
|
||||
// Parse the optional operator.
|
||||
|
|
@ -2773,12 +2752,6 @@ static TSQueryError ts_query__parse_pattern(
|
|||
|
||||
stream_advance(stream);
|
||||
stream_skip_whitespace(stream);
|
||||
|
||||
QueryStep *step = array_get(&self->steps, starting_step_index);
|
||||
while (step->alternative_index != NONE && step->alternative_index < self->steps.size) {
|
||||
step = array_get(&self->steps, step->alternative_index);
|
||||
}
|
||||
step->alternative_index = self->steps.size;
|
||||
}
|
||||
|
||||
// Parse an '@'-prefixed capture pattern
|
||||
|
|
@ -2822,6 +2795,43 @@ static TSQueryError ts_query__parse_pattern(
|
|||
}
|
||||
}
|
||||
|
||||
QueryStep repeat_step;
|
||||
QueryStep *step;
|
||||
switch (quantifier) {
|
||||
case TSQuantifierOneOrMore:
|
||||
repeat_step = query_step__new(WILDCARD_SYMBOL, depth, false);
|
||||
repeat_step.alternative_index = starting_step_index;
|
||||
repeat_step.is_pass_through = true;
|
||||
repeat_step.alternative_is_immediate = true;
|
||||
array_push(&self->steps, repeat_step);
|
||||
break;
|
||||
case TSQuantifierZeroOrMore:
|
||||
repeat_step = query_step__new(WILDCARD_SYMBOL, depth, false);
|
||||
repeat_step.alternative_index = starting_step_index;
|
||||
repeat_step.is_pass_through = true;
|
||||
repeat_step.alternative_is_immediate = true;
|
||||
array_push(&self->steps, repeat_step);
|
||||
|
||||
// Stop when `step->alternative_index` is `NONE` or it points to
|
||||
// `repeat_step` or beyond. Note that having just been pushed,
|
||||
// `repeat_step` occupies slot `self->steps.size - 1`.
|
||||
step = array_get(&self->steps, starting_step_index);
|
||||
while (step->alternative_index != NONE && step->alternative_index < self->steps.size - 1) {
|
||||
step = array_get(&self->steps, step->alternative_index);
|
||||
}
|
||||
step->alternative_index = self->steps.size;
|
||||
break;
|
||||
case TSQuantifierZeroOrOne:
|
||||
step = array_get(&self->steps, starting_step_index);
|
||||
while (step->alternative_index != NONE && step->alternative_index < self->steps.size) {
|
||||
step = array_get(&self->steps, step->alternative_index);
|
||||
}
|
||||
step->alternative_index = self->steps.size;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
capture_quantifiers_mul(capture_quantifiers, quantifier);
|
||||
|
||||
return 0;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue