1#![forbid(unsafe_code)]
8
9use serde::{Deserialize, Serialize};
10use std::fmt;
11
12pub mod dedup;
13pub mod log_parser;
14pub mod report;
15
16pub use dedup::{dedup_hash, DEDUP_DEFAULT_DEPTH};
17pub use log_parser::{parse as parse_asan_log, parse_one as parse_asan_log_one};
18pub use report::{Backtrace, CrashReport, Frame, Verdict};
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
25#[serde(tag = "kind", rename_all = "snake_case")]
26pub enum CrashKind {
27 HeapBufferOverflow { side: Side },
28 StackBufferOverflow,
29 GlobalBufferOverflow,
30 UseAfterFree { quarantine_residence_ms: u64 },
31 DoubleFree,
32 InvalidFree,
33 StackUseAfterReturn,
34 StackUseAfterScope,
35 MemoryLeak { bytes: u64 },
36 Unknown,
38}
39
40#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
41#[serde(rename_all = "snake_case")]
42pub enum Side {
43 Left,
44 Right,
45}
46
47#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
49#[serde(rename_all = "snake_case")]
50#[repr(u8)]
51pub enum Severity {
52 Info = 0,
53 Low = 1,
54 Medium = 2,
55 High = 3,
56 Critical = 4,
57}
58
59impl fmt::Display for Severity {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 f.write_str(match self {
62 Severity::Info => "info",
63 Severity::Low => "low",
64 Severity::Medium => "medium",
65 Severity::High => "high",
66 Severity::Critical => "critical",
67 })
68 }
69}
70
71impl CrashKind {
72 #[allow(clippy::match_same_arms)]
83 pub const fn severity(&self) -> Severity {
84 match self {
85 CrashKind::HeapBufferOverflow { side: Side::Right } => Severity::Critical,
87 CrashKind::UseAfterFree { .. } => Severity::Critical,
88 CrashKind::DoubleFree => Severity::High,
89 CrashKind::HeapBufferOverflow { side: Side::Left } => Severity::High,
90 CrashKind::StackBufferOverflow => Severity::High,
91 CrashKind::StackUseAfterReturn => Severity::High,
92 CrashKind::GlobalBufferOverflow => Severity::Medium,
93 CrashKind::StackUseAfterScope => Severity::Medium,
94 CrashKind::InvalidFree => Severity::Medium,
95 CrashKind::MemoryLeak { .. } => Severity::Low,
96 CrashKind::Unknown => Severity::Info,
97 }
98 }
99
100 pub const fn short_name(&self) -> &'static str {
101 match self {
102 CrashKind::HeapBufferOverflow { .. } => "heap-buffer-overflow",
103 CrashKind::StackBufferOverflow => "stack-buffer-overflow",
104 CrashKind::GlobalBufferOverflow => "global-buffer-overflow",
105 CrashKind::UseAfterFree { .. } => "use-after-free",
106 CrashKind::DoubleFree => "double-free",
107 CrashKind::InvalidFree => "invalid-free",
108 CrashKind::StackUseAfterReturn => "stack-use-after-return",
109 CrashKind::StackUseAfterScope => "stack-use-after-scope",
110 CrashKind::MemoryLeak { .. } => "memory-leak",
111 CrashKind::Unknown => "unknown",
112 }
113 }
114}
115
116#[derive(Debug, thiserror::Error)]
117pub enum OracleError {
118 #[error("serialization failed: {0}")]
119 Serialize(#[from] serde_json::Error),
120 #[error("i/o failure: {0}")]
121 Io(#[from] std::io::Error),
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn severity_ordering_matches_spec_12_2() {
130 assert!(
132 CrashKind::HeapBufferOverflow { side: Side::Right }.severity()
133 >= CrashKind::UseAfterFree { quarantine_residence_ms: 0 }.severity()
134 );
135 assert!(
136 CrashKind::UseAfterFree { quarantine_residence_ms: 0 }.severity()
137 >= CrashKind::DoubleFree.severity()
138 );
139 assert!(CrashKind::DoubleFree.severity() >= CrashKind::InvalidFree.severity());
140 assert!(CrashKind::InvalidFree.severity() >= CrashKind::MemoryLeak { bytes: 0 }.severity());
141 }
142
143 #[test]
144 fn short_name_is_stable() {
145 assert_eq!(
147 CrashKind::HeapBufferOverflow { side: Side::Right }.short_name(),
148 "heap-buffer-overflow"
149 );
150 }
151}