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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
use crate::prelude::*;
use std::thread::JoinHandle;
/// A CWE warning message.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord, Default)]
pub struct CweWarning {
pub name: String,
pub version: String,
pub addresses: Vec<String>,
pub tids: Vec<String>,
pub symbols: Vec<String>,
pub other: Vec<Vec<String>>,
pub description: String,
}
impl CweWarning {
/// Creates a new CweWarning by only setting name, version and description
pub fn new(
name: impl ToString,
version: impl ToString,
description: impl ToString,
) -> CweWarning {
CweWarning {
name: name.to_string(),
version: version.to_string(),
addresses: Vec::new(),
tids: Vec::new(),
symbols: Vec::new(),
other: Vec::new(),
description: description.to_string(),
}
}
/// Sets the address field of the CweWarning
pub fn addresses(mut self, addresses: Vec<String>) -> CweWarning {
self.addresses = addresses;
self
}
/// Sets the Tids field of the CweWarning
pub fn tids(mut self, tids: Vec<String>) -> CweWarning {
self.tids = tids;
self
}
/// Sets the symbols field of the CweWarning
pub fn symbols(mut self, symbols: Vec<String>) -> CweWarning {
self.symbols = symbols;
self
}
/// Sets the other field of the CweWarning
pub fn other(mut self, other: Vec<Vec<String>>) -> CweWarning {
self.other = other;
self
}
}
impl std::fmt::Display for CweWarning {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
formatter,
"[{}] ({}) {}",
self.name, self.version, self.description
)
}
}
/// A generic log message.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)]
pub struct LogMessage {
/// The log message.
pub text: String,
/// The severity/type of the log message.
pub level: LogLevel,
/// The location inside the binary that the message is related to.
pub location: Option<Tid>,
/// The analysis where the message originated.
pub source: Option<String>,
}
impl LogMessage {
/// Create a new `Info`-level log message
pub fn new_info(text: impl Into<String>) -> LogMessage {
LogMessage {
text: text.into(),
level: LogLevel::Info,
location: None,
source: None,
}
}
/// Create a new `Debug`-level log message
pub fn new_debug(text: impl Into<String>) -> LogMessage {
LogMessage {
text: text.into(),
level: LogLevel::Debug,
location: None,
source: None,
}
}
/// Create a new `Error`-level log message
pub fn new_error(text: impl Into<String>) -> LogMessage {
LogMessage {
text: text.into(),
level: LogLevel::Error,
location: None,
source: None,
}
}
/// Associate a specific location to the log message.
pub fn location(mut self, location: Tid) -> LogMessage {
self.location = Some(location);
self
}
/// Set the name of the source analysis for the log message.
pub fn source(mut self, source: impl Into<String>) -> LogMessage {
self.source = Some(source.into());
self
}
}
/// The severity/type of a log message.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)]
pub enum LogLevel {
/// Messages intended for debugging.
Debug,
/// Errors encountered during analysis.
Error,
/// Non-error messages intended for the user.
Info,
}
impl std::fmt::Display for LogMessage {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.level {
LogLevel::Debug => write!(formatter, "DEBUG: ")?,
LogLevel::Error => write!(formatter, "ERROR: ")?,
LogLevel::Info => write!(formatter, "INFO: ")?,
};
match (&self.source, &self.location) {
(Some(source), Some(location)) => write!(formatter, "{} @ {}: ", source, location)?,
(Some(source), None) => write!(formatter, "{}: ", source)?,
(None, Some(location)) => write!(formatter, "{}: ", location)?,
(None, None) => (),
};
write!(formatter, "{}", self.text)
}
}
/// Print all provided log- and CWE-messages.
///
/// Log-messages will always be printed to `stdout`.
/// CWE-warnings will either be printed to `stdout` or to the file path provided in `out_path`.
///
/// If `emit_json` is set, the CWE-warnings will be converted to json for the output.
pub fn print_all_messages(
logs: Vec<LogMessage>,
cwes: Vec<CweWarning>,
out_path: Option<&str>,
emit_json: bool,
) {
for log in logs {
println!("{}", log);
}
let output: String = if emit_json {
serde_json::to_string_pretty(&cwes).unwrap()
} else {
cwes.iter()
.map(|cwe| format!("{}", cwe))
.collect::<Vec<String>>()
.join("\n")
+ "\n"
};
if let Some(file_path) = out_path {
std::fs::write(file_path, output).unwrap();
} else {
print!("{}", output);
}
}
/// The message types a logging thread can receive.
/// See the [`LogThread`] type for more information.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)]
pub enum LogThreadMsg {
/// A normal log message.
Log(LogMessage),
/// A CWE warning
Cwe(CweWarning),
/// If the log collector thread receives this signal,
/// it should stop receiving new messages
/// and instead terminate and return the collected messages prior to receiving the termination signal.
Terminate,
}
/// A type for managing threads for collecting log messages.
///
/// With [`LogThread::spawn()`] one can create a new log thread
/// whose handle is contained in the returned `LogThread` struct.
/// By calling the [`collect()`](LogThread::collect()) method
/// one can tell the log thread to shut down
/// and return the logs collected to this point.
/// If the `LogThread` object gets dropped before calling `collect()`,
/// the corresponding logging thread will be stopped
/// and all collected logs will be discarded.
///
/// If one deliberately wants to discard all logging messages,
/// one can simply create a sender to a disconnected channel
/// via [`LogThread::create_disconnected_sender()`].
pub struct LogThread {
msg_sender: crossbeam_channel::Sender<LogThreadMsg>,
thread_handle: Option<JoinHandle<(Vec<LogMessage>, Vec<CweWarning>)>>,
}
impl Drop for LogThread {
/// If the logging thread still exists,
/// send it the `Terminate` signal.
/// Then wait until the logging thread stopped.
fn drop(&mut self) {
// Make sure the logging thread gets terminated when dropping this.
let _ = self.msg_sender.send(LogThreadMsg::Terminate);
if let Some(handle) = self.thread_handle.take() {
let _ = handle.join();
}
}
}
impl LogThread {
/// Create a new `LogThread` object with a handle to a freshly spawned logging collector thread.
///
/// The parameter is the function containing the actual log collection logic.
/// I.e. the function should receive messages through the given receiver until the channel disconnects
/// or until it receives a [`LogThreadMsg::Terminate`] message.
/// After that it should return the logs collected up to that point.
pub fn spawn<F>(collector_func: F) -> LogThread
where
F: FnOnce(crossbeam_channel::Receiver<LogThreadMsg>) -> (Vec<LogMessage>, Vec<CweWarning>)
+ Send
+ 'static,
{
let (sender, receiver) = crossbeam_channel::unbounded();
let thread_handle = std::thread::spawn(move || collector_func(receiver));
LogThread {
msg_sender: sender,
thread_handle: Some(thread_handle),
}
}
/// Just create a disconnected sender to a (non-existing) logging thread.
/// Can be used like a sender to a channel that deliberately discards all messages sent to it.
pub fn create_disconnected_sender() -> crossbeam_channel::Sender<LogThreadMsg> {
let (sender, _) = crossbeam_channel::unbounded();
sender
}
/// Get a sender that can be used to send messages to the logging thread corresponding to this `LogThread` instance.
pub fn get_msg_sender(&self) -> crossbeam_channel::Sender<LogThreadMsg> {
self.msg_sender.clone()
}
/// Stop the logging thread by sending it the `Terminate` signal
/// and then return all logs collected until that point.
pub fn collect(mut self) -> (Vec<LogMessage>, Vec<CweWarning>) {
let _ = self.msg_sender.send(LogThreadMsg::Terminate);
if let Some(handle) = self.thread_handle.take() {
handle.join().unwrap()
} else {
(Vec::new(), Vec::new())
}
}
}