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. 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;