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.\n",
71 found.file(),
72 found.line(),
73 found.column()
74 );
75 }
76 None => {
77 return String::from(
78 "cli-git: internal error; its message is not shown because it may contain \
79 repository content.\n",
80 );
81 }
82 }
83}
84
85/// What:
86/// The hook itself:
87/// write the notice to standard error and nothing else.
88/// `&PanicHookInfo<'_>` borrows what the panic carries;
89/// its message is never read.
90/// Why:
91/// A hook cannot report a failure to write,
92/// so a closed stream is ignored.
93///
94/// In TS you'd write (pseudocode):
95/// ```ts
96/// function payloadFreePanicHook(info: PanicInfo): void;
97/// ```
98pub fn payload_free_panic_hook(information: &PanicHookInfo<'_>) {
99 // What: `let _ = ...` deliberately discards the write's `Result`.
100 // Why: There is nowhere left to report a failure to write a diagnostic.
101 //
102 // In TS you'd write (pseudocode):
103 // ```ts
104 // try { process.stderr.write(notice); } catch { /* stream closed */ }
105 // ```
106 let _ = std::io::stderr().write_all(panic_notice(information.location()).as_bytes());
107}
108
109/// What:
110/// Make `payload_free_panic_hook` the process's panic hook.
111/// `Box::new(..)` puts the named function where the standard library keeps its hook.
112/// Why:
113/// The executable calls this before anything can scan,
114/// as the scanner's embedding
115/// contract requires.
116///
117/// In TS you'd write (pseudocode):
118/// ```ts
119/// function installPayloadFreePanicHook(): void;
120/// ```
121pub fn install_payload_free_panic_hook() {
122 std::panic::set_hook(Box::new(payload_free_panic_hook));
123}
124
125/// Hook controls,
126/// each in its own process,
127/// stay out of the release executable.
128#[cfg(test)]
129#[path = "panic_notice_tests.rs"]
130mod tests;