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