ClementTsang/bottom · error
stat string is malformed
Error message
stat string is malformed
What it means
After finding the opening parenthesis, the parser uses rsplit_once(") ") to split the comm field from the remaining stat fields; this requires a closing parenthesis followed by a space. If the closing ') ' is absent the stat line is structurally invalid, so the library raises this error.
Solutions
- Re-read the stat file; a transient truncation usually resolves on retry because the process either completed or disappeared.
- Skip the malformed entry during process enumeration instead of failing the whole scan.
- If constructing test fixtures, always emit a full stat line: '(comm) R ppid ...' with a closing ') ' after the comm field.
Example fix
// before
let (comm, rest) = line[start_paren + 1..]
.rsplit_once(") ")
.ok_or_else(|| anyhow!("stat string is malformed"))?;
// after (caller-side retry for transient truncation)
let proc = (0..3).find_map(|_| {
Process::from_file(&stat_path).ok()
}).unwrap_or_else(|| panic!("process {:?} unreadable", stat_path)); Defensive patterns
Strategy: retry
Validate before calling
fn stat_is_complete(line: &str) -> bool {
line.find('(').and_then(|s| line[s..].find(") ")).is_some()
}
if !stat_is_complete(&line) { /* re-read or skip */ } Type guard
fn split_stat(line: &str) -> Option<(&str, &str)> {
let s = line.find('(')? + 1;
line[s..].rsplit_once(") ")
} Try / catch
for _ in 0..2 {
match Process::from_file(path) {
Ok(p) => break Some(p),
Err(e) if e.to_string().contains("malformed") => continue,
Err(e) => return Err(e.into()),
}
} Prevention
- Retry stat reads once on parse failure — truncation from process exit is common
- In tests, always generate full stat lines including the ') ' separator
- Treat per-entry parse failure as 'process gone' during enumeration
When it happens
Trigger: Process::from_file receives a stat line where the comm field is never closed — e.g. truncated file content (read raced with process death), a comm name containing malformed data, or hand-crafted/invalid stat content in tests.
Common situations: Reading /proc/[pid]/stat for a process that is terminating while being read, yielding partial lines; synthetic stat fixtures in tests that only include the opening paren; corrupted or non-standard procfs output.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
AI-assisted analysis of ClementTsang/bottom@b77d317502 (2026-09-07).
Data as JSON: /api/errors/8006b0f626d9898d.
Report an issue: GitHub.
Appendix: source
Thrown at src/collection/processes/linux/process.rs:107
// "last" closing parentheses.
let (comm, rest) = {
let start_paren = line
.find('(')
.ok_or_else(|| anyhow!("start paren missing"))?;
// So, we _could_ try and be smart and only parse a limited slice of
// the string - however, there appears to be no ABI
// guarantees of comm length anymore, so we just take the hit and do
// an rsplit over the full string.
//
// Sources/discussion:
// - https://man.archlinux.org/man/proc_pid_stat.5.en
// - https://stackoverflow.com/questions/23534263/what-is-the-maximum-allowed-limit-on-the-length-of-a-process-name#comment138697304_23534499
// - https://elixir.bootlin.com/linux/v7.1.3/source/fs/proc/array.c#L100
// - https://github.com/ClementTsang/bottom/pull/2163#issuecomment-5017857303
let (comm, rest) = line[start_paren + 1..]
.rsplit_once(") ")
.ok_or_else(|| anyhow!("stat string is malformed"))?;
(comm.to_string(), rest)
};
let mut rest = rest.split(' ');
let state = next_part(&mut rest)?
.chars()
.next()
.ok_or_else(|| anyhow!("missing state"))?;
let ppid: Pid = next_part(&mut rest)?.parse()?;
// Skip 4 fields (pgrp, session, tty_nr, tpgid)
let mut rest = rest.skip(4);
// read flags for kernel thread (PF_KTHREAD from include/linux/sched.h)
let flags: u32 = next_part(&mut rest)?.parse()?;
let is_kernel_thread: bool = flags & 0x00200000 != 0;
View on GitHub (pinned to b77d317502)