diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-07 21:20:01 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-07 21:20:06 +0300 |
| commit | e4a7095c9339fcf14d286936b7eeb16ad4106cc9 (patch) | |
| tree | 3d11937cb3c9a37be38122ba3f82242e834e83c0 /src | |
| parent | 0a684b4130e7e82074831a7af8db5f1af8fed84d (diff) | |
fix: guard windows process tree cycles
refs #1083
Diffstat (limited to 'src')
| -rw-r--r-- | src/platform/windows.rs | 58 |
1 files changed, 53 insertions, 5 deletions
diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 6ac05767..49a68111 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -1,5 +1,5 @@ use std::{ - collections::{HashMap, VecDeque}, + collections::{HashMap, HashSet, VecDeque}, ffi::c_void, mem::{size_of, MaybeUninit}, path::PathBuf, @@ -223,11 +223,15 @@ fn process_is_ancestor( parent_by_pid: &HashMap<u32, u32>, ) -> bool { let mut current = descendant_pid; - while let Some(parent) = parent_by_pid.get(¤t).copied() { + let mut visited = HashSet::new(); + while visited.insert(current) { + let Some(parent) = parent_by_pid.get(¤t).copied() else { + return false; + }; if parent == ancestor_pid { return true; } - if parent == 0 || parent == current { + if parent == 0 { return false; } current = parent; @@ -244,13 +248,23 @@ fn descendant_entries(root_pid: u32, entries: &[WindowsProcessEntry]) -> Vec<&Wi let mut output = Vec::new(); let mut queue = VecDeque::new(); + let mut visited = HashSet::new(); + visited.insert(root_pid); if let Some(root_children) = children.get(&root_pid) { - queue.extend(root_children.iter().copied()); + for entry in root_children.iter().copied() { + if visited.insert(entry.pid) { + queue.push_back(entry); + } + } } while let Some(entry) = queue.pop_front() { output.push(entry); if let Some(next) = children.get(&entry.pid) { - queue.extend(next.iter().copied()); + for child in next.iter().copied() { + if visited.insert(child.pid) { + queue.push_back(child); + } + } } } output @@ -897,6 +911,40 @@ mod tests { } #[test] + fn windows_process_tree_ignores_pid_reuse_cycles() { + let entries = vec![ + test_entry(10, 30, "powershell.exe", &["powershell.exe"]), + test_entry(20, 10, "codex.exe", &["codex.exe"]), + test_entry(30, 20, "node.exe", &["node.exe"]), + ]; + + let descendants = super::descendant_entries(10, &entries); + + assert_eq!( + descendants + .iter() + .map(|entry| entry.pid) + .collect::<Vec<_>>(), + vec![20, 30] + ); + } + + #[test] + fn windows_process_tree_returns_shell_when_candidate_parent_chain_cycles() { + let entries = vec![ + test_entry(10, 40, "powershell.exe", &["powershell.exe"]), + test_entry(20, 10, "codex.exe", &["codex.exe"]), + test_entry(30, 10, "codex.exe", &["codex.exe"]), + test_entry(40, 10, "node.exe", &["node.exe"]), + ]; + + let job = super::select_pane_foreground_job(10, &entries).unwrap(); + + assert_eq!(job.process_group_id, 10); + assert_eq!(job.processes[0].name, "powershell.exe"); + } + + #[test] fn scrollback_editor_argv_uses_editor_env_and_appends_path() { let path = std::path::Path::new(r"C:\Users\User\AppData\Local\Temp\herdr scrollback.txt"); let argv = super::scrollback_editor_argv_with_env( |
