feat!: implement StreamingIterator instead of Iterator for QueryMatches and QueryCaptures

This fixes UB when either `QueryMatches` or `QueryCaptures` had collect called on it.

Co-authored-by: Amaan Qureshi <amaanq12@gmail.com>
This commit is contained in:
Lukas Seidel 2024-09-29 23:34:48 +02:00 committed by GitHub
parent 12007d3ebe
commit 6b1ebd3d29
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 271 additions and 105 deletions

View file

@ -27,6 +27,7 @@ use std::os::fd::AsRawFd;
#[cfg(all(windows, feature = "std"))]
use std::os::windows::io::AsRawHandle;
use streaming_iterator::{StreamingIterator, StreamingIteratorMut};
use tree_sitter_language::LanguageFn;
#[cfg(feature = "wasm")]
@ -201,23 +202,25 @@ pub struct QueryMatch<'cursor, 'tree> {
}
/// A sequence of [`QueryMatch`]es associated with a given [`QueryCursor`].
pub struct QueryMatches<'query, 'cursor, T: TextProvider<I>, I: AsRef<[u8]>> {
pub struct QueryMatches<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> {
ptr: *mut ffi::TSQueryCursor,
query: &'query Query,
text_provider: T,
buffer1: Vec<u8>,
buffer2: Vec<u8>,
_phantom: PhantomData<(&'cursor (), I)>,
current_match: Option<QueryMatch<'query, 'tree>>,
_phantom: PhantomData<(&'tree (), I)>,
}
/// A sequence of [`QueryCapture`]s associated with a given [`QueryCursor`].
pub struct QueryCaptures<'query, 'cursor, T: TextProvider<I>, I: AsRef<[u8]>> {
pub struct QueryCaptures<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> {
ptr: *mut ffi::TSQueryCursor,
query: &'query Query,
text_provider: T,
buffer1: Vec<u8>,
buffer2: Vec<u8>,
_phantom: PhantomData<(&'cursor (), I)>,
current_match: Option<(QueryMatch<'query, 'tree>, usize)>,
_phantom: PhantomData<(&'tree (), I)>,
}
pub trait TextProvider<I>
@ -2433,6 +2436,7 @@ impl QueryCursor {
text_provider,
buffer1: Vec::default(),
buffer2: Vec::default(),
current_match: None,
_phantom: PhantomData,
}
}
@ -2457,6 +2461,7 @@ impl QueryCursor {
text_provider,
buffer1: Vec::default(),
buffer2: Vec::default(),
current_match: None,
_phantom: PhantomData,
}
}
@ -2522,7 +2527,7 @@ impl<'tree> QueryMatch<'_, 'tree> {
}
#[doc(alias = "ts_query_cursor_remove_match")]
pub fn remove(self) {
pub fn remove(&self) {
unsafe { ffi::ts_query_cursor_remove_match(self.cursor, self.id) }
}
@ -2551,7 +2556,7 @@ impl<'tree> QueryMatch<'_, 'tree> {
}
}
fn satisfies_text_predicates<I: AsRef<[u8]>>(
pub fn satisfies_text_predicates<I: AsRef<[u8]>>(
&self,
query: &Query,
buffer1: &mut Vec<u8>,
@ -2669,13 +2674,16 @@ impl QueryProperty {
}
}
impl<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> Iterator
/// Provide StreamingIterator instead of traditional one as the underlying object in the C library
/// gets updated on each iteration. Created copies would have their internal state overwritten,
/// leading to Undefined Behavior
impl<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> StreamingIterator
for QueryMatches<'query, 'tree, T, I>
{
type Item = QueryMatch<'query, 'tree>;
fn next(&mut self) -> Option<Self::Item> {
unsafe {
fn advance(&mut self) {
self.current_match = unsafe {
loop {
let mut m = MaybeUninit::<ffi::TSQueryMatch>::uninit();
if ffi::ts_query_cursor_next_match(self.ptr, m.as_mut_ptr()) {
@ -2686,23 +2694,35 @@ impl<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> Iterator
&mut self.buffer2,
&mut self.text_provider,
) {
return Some(result);
break Some(result);
}
} else {
return None;
break None;
}
}
}
};
}
fn get(&self) -> Option<&Self::Item> {
self.current_match.as_ref()
}
}
impl<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> Iterator
impl<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> StreamingIteratorMut
for QueryMatches<'query, 'tree, T, I>
{
fn get_mut(&mut self) -> Option<&mut Self::Item> {
self.current_match.as_mut()
}
}
impl<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> StreamingIterator
for QueryCaptures<'query, 'tree, T, I>
{
type Item = (QueryMatch<'query, 'tree>, usize);
fn next(&mut self) -> Option<Self::Item> {
unsafe {
fn advance(&mut self) {
self.current_match = unsafe {
loop {
let mut capture_index = 0u32;
let mut m = MaybeUninit::<ffi::TSQueryMatch>::uninit();
@ -2718,15 +2738,27 @@ impl<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> Iterator
&mut self.buffer2,
&mut self.text_provider,
) {
return Some((result, capture_index as usize));
break Some((result, capture_index as usize));
}
result.remove();
} else {
return None;
break None;
}
}
}
}
fn get(&self) -> Option<&Self::Item> {
self.current_match.as_ref()
}
}
impl<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> StreamingIteratorMut
for QueryCaptures<'query, 'tree, T, I>
{
fn get_mut(&mut self) -> Option<&mut Self::Item> {
self.current_match.as_mut()
}
}
impl<T: TextProvider<I>, I: AsRef<[u8]>> QueryMatches<'_, '_, T, I> {