aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-07-07 21:20:01 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-07-07 21:20:06 +0300
commite4a7095c9339fcf14d286936b7eeb16ad4106cc9 (patch)
tree3d11937cb3c9a37be38122ba3f82242e834e83c0 /src
parent0a684b4130e7e82074831a7af8db5f1af8fed84d (diff)
fix: guard windows process tree cycles
refs #1083
Diffstat (limited to 'src')
-rw-r--r--src/platform/windows.rs58
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(&current).copied() {
+ let mut visited = HashSet::new();
+ while visited.insert(current) {
+ let Some(parent) = parent_by_pid.get(&current).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(