From 00d126ad7d39fd6d117af0e9fba4758c4ecc9752 Mon Sep 17 00:00:00 2001 From: gustav-fff <286169375+gustav-fff@users.noreply.github.com> Date: Sat, 29 Aug 2026 17:22:57 -0700 Subject: [PATCH] fix(deps): bump pyo3 0.24 -> 0.29 for GHSA-36hh-v3qg-5jq4 (#838) pyo3 < 0.29.0 has an out-of-bounds read in nth/nth_back for PyList and PyTuple iterators. Migrate the removed/renamed APIs: PyObject -> Py, Python::allow_threads -> detach, Python::with_gil -> attach, and opt out of the now-deprecated implicit FromPyObject derive on the result pyclasses (nothing extracts them by value). Closes #838 --- Cargo.lock | 50 +++++++-------------------------- crates/fff-python/Cargo.toml | 2 +- crates/fff-python/src/finder.rs | 40 +++++++++++++------------- crates/fff-python/src/types.rs | 28 +++++++++--------- crates/fff-python/src/watch.rs | 2 +- 5 files changed, 46 insertions(+), 76 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index de93390b2..c394df57b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1204,15 +1204,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "indoc" -version = "2.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" -dependencies = [ - "rustversion", -] - [[package]] name = "inotify" version = "0.11.1" @@ -1448,15 +1439,6 @@ dependencies = [ "libc", ] -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - [[package]] name = "mimalloc" version = "0.1.48" @@ -1864,37 +1846,32 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.24.2" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5203598f366b11a02b13aa20cab591229ff0a89fd121a308a5df751d5fc9219" +checksum = "4688ddedf473e32662b9b067670129a8afb8c18e351482c70d62ba4a88171e8b" dependencies = [ - "cfg-if", - "indoc", "libc", - "memoffset", "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", - "unindent", ] [[package]] name = "pyo3-build-config" -version = "0.24.2" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99636d423fa2ca130fa5acde3059308006d46f98caac629418e53f7ebb1e9999" +checksum = "f41027e41b4bd03f6e60f9f417fe24a6341a6bb744edd62b6f709f2a52ea30e9" dependencies = [ - "once_cell", "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.24.2" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78f9cf92ba9c409279bc3305b5409d90db2d2c22392d443a87df3a1adad59e33" +checksum = "e591a95526fead067432c3b3a33fc74770b87b1e04e73671090d9c2055a2b327" dependencies = [ "libc", "pyo3-build-config", @@ -1902,9 +1879,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.24.2" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b999cb1a6ce21f9a6b147dcf1be9ffedf02e0043aec74dc390f3007047cecd9" +checksum = "73225868fc1cd84eef2c3c230ddb91273bf1de46aeb8a4248da76d32a0924a1c" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -1914,13 +1891,12 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.24.2" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "822ece1c7e1012745607d5cf0bcb2874769f0f7cb34c4cde03b9358eb9ef911a" +checksum = "571575aa3749fa6216757dd47d2a3e7ef360f329a40f0666a9fbd14889024952" dependencies = [ "heck", "proc-macro2", - "pyo3-build-config", "quote", "syn", ] @@ -2628,12 +2604,6 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" -[[package]] -name = "unindent" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" - [[package]] name = "url" version = "2.5.8" diff --git a/crates/fff-python/Cargo.toml b/crates/fff-python/Cargo.toml index 964786adf..157eabd9c 100644 --- a/crates/fff-python/Cargo.toml +++ b/crates/fff-python/Cargo.toml @@ -20,4 +20,4 @@ zlob = ["fff/zlob", "fff-query-parser/zlob"] fff = { package = "fff-search", path = "../fff-core", version = "0.10.5", default-features = false } fff-query-parser = { path = "../fff-query-parser", version = "0.10.5", default-features = false } git2 = { workspace = true } -pyo3 = { version = "0.24.0", features = ["extension-module", "abi3-py310"] } +pyo3 = { version = "0.29.0", features = ["extension-module", "abi3-py310"] } diff --git a/crates/fff-python/src/finder.rs b/crates/fff-python/src/finder.rs index 7d7857f19..f4a27246a 100644 --- a/crates/fff-python/src/finder.rs +++ b/crates/fff-python/src/finder.rs @@ -210,7 +210,7 @@ impl FileFinder { let init_frecency = shared_frecency.clone(); let init_query_tracker = query_tracker.clone(); - py.allow_threads(move || -> PyResult<()> { + py.detach(move || -> PyResult<()> { if let Some(path) = frecency_db_path { create_parent_dir(&path)?; let tracker = FrecencyTracker::open(&path).map_err(py_err)?; @@ -274,9 +274,9 @@ impl FileFinder { fn __exit__( &mut self, py: Python<'_>, - _exc_type: PyObject, - _exc_value: PyObject, - _traceback: PyObject, + _exc_type: Py, + _exc_value: Py, + _traceback: Py, ) { let _ = self.close(py); } @@ -284,7 +284,7 @@ impl FileFinder { fn close(&mut self, py: Python<'_>) -> PyResult<()> { // Release the GIL while an in-flight callback finishes. let picker = self.picker.clone(); - py.allow_threads(move || picker.shutdown_watches_and_wait()); + py.detach(move || picker.shutdown_watches_and_wait()); clear_shared_state(&self.picker, &self.frecency, &self.query_tracker); Ok(()) } @@ -355,7 +355,7 @@ impl FileFinder { let query_tracker = self.query_tracker.clone(); let query = query.to_string(); - py.allow_threads(move || -> PyResult<_> { + py.detach(move || -> PyResult<_> { let picker_guard = picker.read().map_err(py_err)?; let picker = picker_guard .as_ref() @@ -410,7 +410,7 @@ impl FileFinder { let picker = self.picker.clone(); let pattern = pattern.to_string(); - py.allow_threads(move || -> PyResult<_> { + py.detach(move || -> PyResult<_> { let picker_guard = picker.read().map_err(py_err)?; let picker = picker_guard .as_ref() @@ -462,7 +462,7 @@ impl FileFinder { let picker = self.picker.clone(); let query = query.to_string(); - py.allow_threads(move || -> PyResult<_> { + py.detach(move || -> PyResult<_> { let picker_guard = picker.read().map_err(py_err)?; let picker = picker_guard .as_ref() @@ -522,7 +522,7 @@ impl FileFinder { let query = query.to_string(); let (items, scores, total_matched, total_files, total_dirs) = - py.allow_threads(move || -> PyResult<_> { + py.detach(move || -> PyResult<_> { let picker_guard = picker.read().map_err(py_err)?; let picker = picker_guard .as_ref() @@ -566,7 +566,7 @@ impl FileFinder { )) })?; - let items: PyResult> = items + let items: PyResult>> = items .into_iter() .map(|item| match item { MixedItem::File(file) => Ok(Py::new(py, file)?.into_any()), @@ -618,7 +618,7 @@ impl FileFinder { let mode = parse_grep_mode(mode)?; let cursor_offset = cursor.map(|c| c.offset as usize).unwrap_or(0); - py.allow_threads(move || -> PyResult<_> { + py.detach(move || -> PyResult<_> { let picker_guard = picker.read().map_err(py_err)?; let picker = picker_guard .as_ref() @@ -682,7 +682,7 @@ impl FileFinder { let mode = parse_grep_mode(mode)?; let cursor_offset = cursor.map(|c| c.offset as usize).unwrap_or(0); - py.allow_threads(move || -> PyResult<_> { + py.detach(move || -> PyResult<_> { let picker_guard = picker.read().map_err(py_err)?; let picker = picker_guard .as_ref() @@ -734,7 +734,7 @@ impl FileFinder { #[pyo3(signature = (timeout_ms=5000))] fn wait_for_scan_blocking(&self, py: Python<'_>, timeout_ms: u64) -> PyResult { let picker = self.picker.clone(); - py.allow_threads(move || Ok(picker.wait_for_scan(Duration::from_millis(timeout_ms)))) + py.detach(move || Ok(picker.wait_for_scan(Duration::from_millis(timeout_ms)))) } /// Subscribe to filesystem changes matching `pattern`. @@ -768,9 +768,9 @@ impl FileFinder { let id = { let picker = self.picker.clone(); - py.allow_threads(move || { + py.detach(move || { picker.watch(&pattern, options, move |_id, events| { - Python::with_gil(|py| { + Python::attach(|py| { if !active_cb.load(Ordering::Acquire) { return; } @@ -800,7 +800,7 @@ impl FileFinder { let cache_budget_max_bytes = self.cache_budget_max_bytes; let cache_budget_max_file_size = self.cache_budget_max_file_size; - py.allow_threads(move || -> PyResult<()> { + py.detach(move || -> PyResult<()> { if !new_path.exists() { return Err(py_err(format!( "Path does not exist: {}", @@ -852,7 +852,7 @@ impl FileFinder { fn refresh_git_status(&self, py: Python<'_>) -> PyResult { let picker = self.picker.clone(); let frecency = self.frecency.clone(); - py.allow_threads(move || { + py.detach(move || { picker .refresh_git_status(&frecency) .map_err(py_err) @@ -871,7 +871,7 @@ impl FileFinder { let query_tracker = self.query_tracker.clone(); let query = query.to_string(); - py.allow_threads(move || -> PyResult { + py.detach(move || -> PyResult { let file_path = fff::path_utils::canonicalize(&selected_file_path).map_err(py_err)?; let project_path = { let guard = picker.read().map_err(py_err)?; @@ -898,7 +898,7 @@ impl FileFinder { let picker = self.picker.clone(); let query_tracker = self.query_tracker.clone(); - py.allow_threads(move || -> PyResult> { + py.detach(move || -> PyResult> { let project_path = { let guard = picker.read().map_err(py_err)?; guard.as_ref().map(|p| p.base_path().to_path_buf()) @@ -936,7 +936,7 @@ impl FileFinder { picker_indexed_files, frecency_initialized, query_tracker_initialized, - ) = py.allow_threads(move || -> PyResult<_> { + ) = py.detach(move || -> PyResult<_> { // Resolve the path to inspect: explicit arg → indexed base path → // process cwd. Report a cwd-resolution failure instead of silently // discovering from an empty path. diff --git a/crates/fff-python/src/types.rs b/crates/fff-python/src/types.rs index ec18b18a4..ee47b6e41 100644 --- a/crates/fff-python/src/types.rs +++ b/crates/fff-python/src/types.rs @@ -1,6 +1,6 @@ use pyo3::prelude::*; -#[pyclass] +#[pyclass(skip_from_py_object)] #[derive(Clone)] pub struct Score { #[pyo3(get)] @@ -37,7 +37,7 @@ impl Score { } } -#[pyclass] +#[pyclass(skip_from_py_object)] #[derive(Clone)] pub struct FileItem { #[pyo3(get)] @@ -70,7 +70,7 @@ impl FileItem { } } -#[pyclass] +#[pyclass(skip_from_py_object)] #[derive(Clone)] pub struct DirItem { #[pyo3(get)] @@ -91,7 +91,7 @@ impl DirItem { } } -#[pyclass] +#[pyclass(skip_from_py_object)] #[derive(Clone)] pub struct MixedFileItem { #[pyo3(get)] @@ -124,7 +124,7 @@ impl MixedFileItem { } } -#[pyclass] +#[pyclass(skip_from_py_object)] #[derive(Clone)] pub struct MixedDirItem { #[pyo3(get)] @@ -145,7 +145,7 @@ impl MixedDirItem { } } -#[pyclass] +#[pyclass(skip_from_py_object)] #[derive(Clone)] pub struct MatchRange { #[pyo3(get)] @@ -161,7 +161,7 @@ impl MatchRange { } } -#[pyclass] +#[pyclass(skip_from_py_object)] #[derive(Clone)] pub struct GrepMatch { #[pyo3(get)] @@ -212,7 +212,7 @@ impl GrepMatch { } } -#[pyclass] +#[pyclass(skip_from_py_object)] #[derive(Clone)] pub struct SearchResult { #[pyo3(get)] @@ -245,7 +245,7 @@ impl SearchResult { } } -#[pyclass] +#[pyclass(skip_from_py_object)] #[derive(Clone)] pub struct DirSearchResult { #[pyo3(get)] @@ -281,7 +281,7 @@ impl DirSearchResult { #[pyclass] pub struct MixedSearchResult { #[pyo3(get)] - pub items: Vec, + pub items: Vec>, #[pyo3(get)] pub scores: Vec, #[pyo3(get)] @@ -313,7 +313,7 @@ impl MixedSearchResult { } } -#[pyclass] +#[pyclass(skip_from_py_object)] #[derive(Clone)] pub struct GrepResult { #[pyo3(get)] @@ -365,7 +365,7 @@ impl GrepResult { } } -#[pyclass] +#[pyclass(skip_from_py_object)] #[derive(Clone)] pub struct ScanProgress { #[pyo3(get)] @@ -388,7 +388,7 @@ impl ScanProgress { } } -#[pyclass] +#[pyclass(skip_from_py_object)] #[derive(Clone)] pub struct WatchEvent { #[pyo3(get)] @@ -404,7 +404,7 @@ impl WatchEvent { } } -#[pyclass] +#[pyclass(skip_from_py_object)] #[derive(Clone)] pub struct GrepCursor { #[pyo3(get)] diff --git a/crates/fff-python/src/watch.rs b/crates/fff-python/src/watch.rs index 05b7f8e79..eac0fc5fd 100644 --- a/crates/fff-python/src/watch.rs +++ b/crates/fff-python/src/watch.rs @@ -47,7 +47,7 @@ impl WatchSubscription { slf } - fn __exit__(&self, _exc_type: PyObject, _exc_value: PyObject, _traceback: PyObject) { + fn __exit__(&self, _exc_type: Py, _exc_value: Py, _traceback: Py) { self.unsubscribe(); }