From 356f68293a4633375db25f6433bc17ef00118894 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 26 Jun 2023 17:18:05 -0700 Subject: [PATCH] Fix false positive query match bug, introduced in #2085 --- cli/src/tests/helpers/query_helpers.rs | 4 +- cli/src/tests/query_test.rs | 38 +++++++++++- lib/src/query.c | 84 +++++++++++++------------- 3 files changed, 81 insertions(+), 45 deletions(-) diff --git a/cli/src/tests/helpers/query_helpers.rs b/cli/src/tests/helpers/query_helpers.rs index 0638701c..a21320b1 100644 --- a/cli/src/tests/helpers/query_helpers.rs +++ b/cli/src/tests/helpers/query_helpers.rs @@ -318,8 +318,8 @@ pub fn assert_query_matches( let tree = parser.parse(source, None).unwrap(); let mut cursor = QueryCursor::new(); let matches = cursor.matches(&query, tree.root_node(), source.as_bytes()); - assert_eq!(collect_matches(matches, &query, source), expected); - assert_eq!(cursor.did_exceed_match_limit(), false); + pretty_assertions::assert_eq!(collect_matches(matches, &query, source), expected); + pretty_assertions::assert_eq!(cursor.did_exceed_match_limit(), false); } pub fn collect_matches<'a>( diff --git a/cli/src/tests/query_test.rs b/cli/src/tests/query_test.rs index f0f37788..e244405a 100644 --- a/cli/src/tests/query_test.rs +++ b/cli/src/tests/query_test.rs @@ -853,6 +853,33 @@ fn test_query_matches_with_wildcard_at_the_root() { }); } +#[test] +fn test_query_matches_with_wildcard_within_wildcard() { + allocations::record(|| { + let language = get_language("javascript"); + let query = Query::new( + language, + " + (_ (_) @child) @parent + ", + ) + .unwrap(); + + assert_query_matches( + language, + &query, + "/* a */ b; c;", + &[ + (0, vec![("parent", "/* a */ b; c;"), ("child", "/* a */")]), + (0, vec![("parent", "/* a */ b; c;"), ("child", "b;")]), + (0, vec![("parent", "b;"), ("child", "b")]), + (0, vec![("parent", "/* a */ b; c;"), ("child", "c;")]), + (0, vec![("parent", "c;"), ("child", "c")]), + ], + ); + }); +} + #[test] fn test_query_matches_with_immediate_siblings() { allocations::record(|| { @@ -1166,11 +1193,20 @@ fn test_query_matches_with_non_terminal_repetitions_within_root() { language, &query, r#" + function f() { + d; + e; + f; + g; + } a; b; c; "#, - &[(0, vec![("id", "a"), ("id", "b"), ("id", "c")])], + &[ + (0, vec![("id", "d"), ("id", "e"), ("id", "f"), ("id", "g")]), + (0, vec![("id", "a"), ("id", "b"), ("id", "c")]), + ], ); }); } diff --git a/lib/src/query.c b/lib/src/query.c index bdc3b0e4..64ab57e2 100644 --- a/lib/src/query.c +++ b/lib/src/query.c @@ -3435,6 +3435,48 @@ static inline bool ts_query_cursor__advance( self->depth, ts_node_type(ts_tree_cursor_current_node(&self->cursor)) ); + + // After leaving a node, remove any states that cannot make further progress. + uint32_t deleted_count = 0; + for (unsigned i = 0, n = self->states.size; i < n; i++) { + QueryState *state = &self->states.contents[i]; + QueryStep *step = &self->query->steps.contents[state->step_index]; + + // If a state completed its pattern inside of this node, but was deferred from finishing + // in order to search for longer matches, mark it as finished. + if ( + step->depth == PATTERN_DONE_MARKER && + (state->start_depth > self->depth || self->depth == 0) + ) { + LOG(" finish pattern %u\n", state->pattern_index); + array_push(&self->finished_states, *state); + did_match = true; + deleted_count++; + } + + // If a state needed to match something within this node, then remove that state + // as it has failed to match. + else if ( + step->depth != PATTERN_DONE_MARKER && + (uint32_t)state->start_depth + (uint32_t)step->depth > self->depth + ) { + LOG( + " failed to match. pattern:%u, step:%u\n", + state->pattern_index, + state->step_index + ); + capture_list_pool_release( + &self->capture_list_pool, + state->capture_list_id + ); + deleted_count++; + } + + else if (deleted_count > 0) { + self->states.contents[i - deleted_count] = *state; + } + } + self->states.size -= deleted_count; } // Leave this node by stepping to its next sibling or to its parent. @@ -3461,48 +3503,6 @@ static inline bool ts_query_cursor__advance( self->halted = true; } } - - if (self->on_visible_node) { - // After leaving a node, remove any states that cannot make further progress. - uint32_t deleted_count = 0; - for (unsigned i = 0, n = self->states.size; i < n; i++) { - QueryState *state = &self->states.contents[i]; - QueryStep *step = &self->query->steps.contents[state->step_index]; - - // If a state completed its pattern inside of this node, but was deferred from finishing - // in order to search for longer matches, mark it as finished. - if (step->depth == PATTERN_DONE_MARKER) { - if (state->start_depth > self->depth || self->halted) { - LOG(" finish pattern %u\n", state->pattern_index); - array_push(&self->finished_states, *state); - did_match = true; - deleted_count++; - continue; - } - } - - // If a state needed to match something within this node, then remove that state - // as it has failed to match. - else if ((uint32_t)state->start_depth + (uint32_t)step->depth > self->depth) { - LOG( - " failed to match. pattern:%u, step:%u\n", - state->pattern_index, - state->step_index - ); - capture_list_pool_release( - &self->capture_list_pool, - state->capture_list_id - ); - deleted_count++; - continue; - } - - if (deleted_count > 0) { - self->states.contents[i - deleted_count] = *state; - } - } - self->states.size -= deleted_count; - } } // Enter a new node.