Skip to main content

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;