git_policy_cli/panic_notice.rs
1//! What:
2//! The panic hook of the executable:
3//! one line naming where an internal error
4//! happened,
5//! never its message.
6//! Why:
7//! The linked scanner catches a matcher panic and reports it as a failed scan,
8//! but
9//! Rust runs the process's panic hook before the panic can be caught,
10//! and the default
11//! hook prints the panic's message,
12//! which can hold bytes of the file being scanned.
13//! The scanner's own executable installs a hook that prints nothing;
14//! this one keeps
15//! the source location,
16//! which holds no repository content,
17//! so a wrapper defect is
18//! still visible.
19//!
20//! In TS you'd write (pseudocode):
21//! ```ts
22//! // process.on('uncaughtException', () => process.stderr.write('cli-git: internal error ...\n'));
23//! ```
24
25/// What:
26/// `Write` adds `write_all` to the standard error stream.
27/// Why:
28/// The notice is written whole,
29/// as bytes.
30///
31/// In TS you'd write (pseudocode):
32/// ```ts
33/// // process.stderr.write(text)
34/// ```
35use std::io::Write;
36/// What:
37/// `Location` is a source file,
38/// line and column;
39/// `PanicHookInfo` is what a panic
40/// hook is given,
41/// its message and its location.
42/// Why:
43/// Only the location is read.
44///
45/// In TS you'd write (pseudocode):
46/// ```ts
47/// type Location = { file: string; line: number; column: number };
48/// ```
49use std::panic::{Location, PanicHookInfo};
50
51/// What:
52/// The line the hook writes,
53/// with the location when the panic has one.
54/// `Option<&Location<'_>>` is "a borrowed location or nothing";
55/// `'_` lets the
56/// compiler choose how long the borrow lasts.
57/// Why:
58/// A pure function of the location can be checked exactly.
59///
60/// In TS you'd write (pseudocode):
61/// ```ts
62/// function panicNotice(location?: Location): string;
63/// ```
64pub fn panic_notice(location: Option<&Location<'_>>) -> String {
65 // `match` unpacks "a location or nothing".
66 match location {
67 Some(found) => {
68 return format!(
69 "cli-git: internal error at {}:{}:{}; its message is not shown because it may \
70 contain repository content. Hint: if you were running a Git command, run it again; your files are \
71 not changed by this notice, so `git status` shows where things stand. If it repeats, report this \
72 location.\n",
73 found.file(),
74 found.line(),
75 found.column()
76 );
77 }
78 None => {
79 return String::from(
80 "cli-git: internal error; its message is not shown because it may contain \
81 repository content. Hint: if you were running a Git command, run it again; `git status` shows where \
82 things stand. If it repeats, report the command you ran.\n",
83 );
84 }
85 }
86}
87
88/// What:
89/// The hook itself:
90/// write the notice to standard error and nothing else.
91/// `&PanicHookInfo<'_>` borrows what the panic carries;
92/// its message is never read.
93/// Why:
94/// A hook cannot report a failure to write,
95/// so a closed stream is ignored.
96///
97/// In TS you'd write (pseudocode):
98/// ```ts
99/// function payloadFreePanicHook(info: PanicInfo): void;
100/// ```
101pub fn payload_free_panic_hook(information: &PanicHookInfo<'_>) {
102 // What: `let _ = ...` deliberately discards the write's `Result`.
103 // Why: There is nowhere left to report a failure to write a diagnostic.
104 //
105 // In TS you'd write (pseudocode):
106 // ```ts
107 // try { process.stderr.write(notice); } catch { /* stream closed */ }
108 // ```
109 let _ = std::io::stderr().write_all(panic_notice(information.location()).as_bytes());
110}
111
112/// What:
113/// Make `payload_free_panic_hook` the process's panic hook.
114/// `Box::new(..)` puts the named function where the standard library keeps its hook.
115/// Why:
116/// The executable calls this before anything can scan,
117/// as the scanner's embedding
118/// contract requires.
119///
120/// In TS you'd write (pseudocode):
121/// ```ts
122/// function installPayloadFreePanicHook(): void;
123/// ```
124pub fn install_payload_free_panic_hook() {
125 std::panic::set_hook(Box::new(payload_free_panic_hook));
126}
127
128/// Hook controls,
129/// each in its own process,
130/// stay out of the release executable.
131#[cfg(test)]
132#[path = "panic_notice_tests.rs"]
133mod tests;