1use crate::error::*;
3use crate::exception::Exception;
4use crate::execution_class::ExecutionClass;
5use crate::stacktrace::{ParseStacktrace, Stacktrace, StacktraceEntry};
6use regex::Regex;
7
8pub struct CSharpStacktrace;
10
11impl ParseStacktrace for CSharpStacktrace {
12    fn extract_stacktrace(stream: &str) -> Result<Vec<String>> {
13        let re = Regex::new(r"(?m)^Unhandled [Ee]xception(?::\n|\. )(?:.|\n)*?((?:[ \n\t]*(?:at [\S ]+|--- End of inner exception stack trace ---))+)$").unwrap();
14
15        let Some(cap) = re.captures(stream) else {
16            return Err(Error::Casr(
17                "The stacktrace format is not recognized".to_string(),
18            ));
19        };
20
21        Ok(cap
22            .get(1)
23            .unwrap()
24            .as_str()
25            .split('\n')
26            .map(|s| s.trim().to_string())
27            .filter(|s| !s.is_empty() && s != "--- End of inner exception stack trace ---")
28            .collect::<Vec<String>>())
29    }
30
31    fn parse_stacktrace_entry(entry: &str) -> Result<StacktraceEntry> {
32        let re = Regex::new(r"^at (?P<service_info>\([\S ]+?\) )?(?P<function>\S+?)(?: ?(?P<params>\([\S ]*?\)))?(?: <(?P<base>\w+)(?: \+ (?P<offset>\w+))?>| \[(?P<il_offset>\w+)\])?(?: in (?:<(?P<mvid>[[:xdigit:]]+)(?:#[[:xdigit:]]+)?>|(?P<file>[\S ]+)):(?:line )?(?P<line>\w+))?$").unwrap();
33
34        let Some(cap) = re.captures(entry) else {
35            return Err(Error::Casr(format!(
36                "Couldn't parse stacktrace line: {entry}"
37            )));
38        };
39
40        let group_as_str = |name| cap.name(name).map(|m| m.as_str());
41
42        let mut stentry = StacktraceEntry::default();
43
44        if let Some(function) = group_as_str("function") {
45            let mut function = function.to_string();
46
47            if let Some(params) = group_as_str("params") {
48                function.push_str(params)
49            }
50
51            if let Some(service_info) = group_as_str("service_info") {
52                function.insert_str(0, service_info);
53            }
54
55            stentry.function = function;
56        }
57
58        let re_hex = Regex::new(r"^0x([[:xdigit:]]+)$").unwrap();
59        let parse_hex = |s| {
60            re_hex
61                .captures(s)
62                .and_then(|c| u64::from_str_radix(c.get(1).unwrap().as_str(), 16).ok())
63        };
64
65        if let Some(base) = group_as_str("base") {
66            let Some(parsed_base) = parse_hex(base) else {
67                return Err(Error::Casr(format!("Couldn't parse address: {base}")));
68            };
69
70            if let Some(offset) = group_as_str("offset") {
71                let Some(address) = parse_hex(offset)
72                    .and_then(|parsed_offset| parsed_base.checked_add(parsed_offset))
73                else {
74                    return Err(Error::Casr(format!(
75                        "Couldn't parse address: {base} + {offset}"
76                    )));
77                };
78
79                stentry.address = address;
80            } else {
81                stentry.address = parsed_base;
82            }
83        } else if let Some(il_offset) = group_as_str("il_offset") {
84            let Some(parsed_il_offset) = parse_hex(il_offset) else {
85                return Err(Error::Casr(format!(
86                    "Couldn't parse IL offset: {il_offset}"
87                )));
88            };
89
90            stentry.address = parsed_il_offset;
91        }
92
93        if let Some(file) = group_as_str("file").or_else(|| group_as_str("mvid")) {
94            stentry.debug.file = file.to_string();
95        }
96
97        if let Some(line) = group_as_str("line") {
98            let Ok(parsed_line) = line.parse::<u64>() else {
99                return Err(Error::Casr(format!(
100                    "Couldn't parse stacktrace line num: {line}"
101                )));
102            };
103
104            stentry.debug.line = parsed_line;
105        }
106
107        Ok(stentry)
108    }
109
110    fn parse_stacktrace(entries: &[String]) -> Result<Stacktrace> {
111        entries
112            .iter()
113            .map(|s| Self::parse_stacktrace_entry(s))
114            .collect()
115    }
116}
117
118pub struct CSharpException;
120
121impl Exception for CSharpException {
122    fn parse_exception(stream: &str) -> Option<ExecutionClass> {
123        let re = Regex::new(r"(?m)^Unhandled [Ee]xception(:\n|\. )((?:.|\n)*?)\n[ \t]*(?:at [\S ]+|--- End of inner exception stack trace ---)$").unwrap();
124
125        let cap = re.captures(stream)?;
126
127        let delimiter = if cap.get(1).unwrap().as_str() == ":\n" {
128            " ---> "
129        } else {
130            "\n ---> "
131        };
132        let description = cap.get(2).unwrap().as_str();
133
134        let (exception, message) = description
135            .rsplit_once(delimiter)
136            .map_or(description, |(_, s)| s)
137            .split_once(": ")?;
138
139        Some(ExecutionClass {
140            severity: "NOT_EXPLOITABLE".to_string(),
141            short_description: exception.to_string(),
142            description: message.to_string(),
143            explanation: "".to_string(),
144        })
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151    use crate::stacktrace::{Filter, tests::safe_init_ignore_stack_frames};
152
153    #[test]
154    fn test_csharp_parse_exception() {
155        let streams = [
156            "
157Unhandled Exception:
158System.ArithmeticException: 123
159
160321
161
1627
163 ---> System.ArgumentException: 1111 ---> System.IO.IOException: cccc
164
165   --- End of inner exception stack trace ---",
166            "
167Unhandled Exception:
168System.ArgumentException: 1111
169 ---> System.IO.IOException: cccc
170   --- End of inner exception stack trace ---",
171            "
172Unhandled exception. System.ArgumentException: 1111
173 ---> System.IO.IOException: cccc
174
175
176   at Program.<Main>g__f|0_0(Func`2 d, Int32& l, Int32 m) in /home/user/dotnet/2/Program.cs:line 9",
177            "
178Unhandled exception. System.ArgumentException: 1111 ---> System.IO.IOException: cccc
179
180
181
182   at Program.<Main>g__f|0_0(Func`2 d, Int32& l, Int32 m) in /home/user/dotnet/2/Program.cs:line 9",
183        ];
184
185        let exceptions = streams.map(|s| {
186            let Some(e) = CSharpException::parse_exception(s) else {
187                panic!("Couldn't get C# exception from stream: {s}");
188            };
189            e
190        });
191
192        assert_eq!(exceptions[0].short_description, "System.IO.IOException");
193        assert_eq!(exceptions[0].description, "cccc\n");
194        assert_eq!(exceptions[1].short_description, "System.IO.IOException");
195        assert_eq!(exceptions[1].description, "cccc");
196        assert_eq!(exceptions[2].short_description, "System.IO.IOException");
197        assert_eq!(exceptions[2].description, "cccc\n\n");
198        assert_eq!(exceptions[3].short_description, "System.ArgumentException");
199        assert_eq!(
200            exceptions[3].description,
201            "1111 ---> System.IO.IOException: cccc\n\n\n"
202        );
203    }
204
205    #[test]
206    fn test_csharp_stacktrace() {
207        let stream = "Unhandled exception. System.ArithmeticException: 123
208
209321
210
2117
212
213 ---> System.ArgumentException: 1111
214 ---> System.IO.IOException: cccc
215 ---> System.IO.IOException: bbbb
216 ---> System.IO.IOException: aaaa
217 ---> System.IO.IOException: I/O error occurred.
218   --- End of inner exception stack trace ---
219   --- End of inner exception stack trace ---
220   --- End of inner exception stack trace ---
221
222
223   at C.qwe()
224   at B..ctor() in /home/user/dotnet/2/A.cs:line 37
225   at A`1.<>c.<set_Q>b__1_1() in /home/user/dotnet/2/A.cs:line 15
226
227   at A`1.h[Z](Func`1 a)
228   --- End of inner exception stack trace ---
229   --- End of inner exception stack trace ---
230  at A`1[T].<set_Q>g__g|1_0 (System.Int32[] arr) <0x40b745f0 + 0x00122> in /home/user/mono/2/src/2.cs:13
231  at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback () [0x00000] in <c79446e93efd45a0b7bc2f9631593aff>:0
232
233  at A`1[T].set_Q (System.Int32 value) <0x40275140 + 0x00082> in <f6b2b0ea894844dc83a96f9504d8f570#610bc057486c618efb3936233b088988>:0
234  at (wrapper runtime-invoke) staticperformanceoptimization.runtime_invoke_void (object,intptr,intptr,intptr) <0xffffffff>
235
236
237  at (wrapper managed-to-native) System.Drawing.GDIPlus:GdiplusStartup (ulong&,System.Drawing.GdiplusStartupInput&,System.Drawing.GdiplusStartupOutput&)
238  at Program+<>c.<Main>b__0_2 (System.Int32 i) <0x7f1c08e516b0 + 0x00035> in <f6b2b0ea894844dc83a96f9504d8f570#17898f75ae7b737216569c39168a3967>:0
239  at Program.<Main>g__f|0_0 (System.Func`2[T,TResult] d, System.Int32& l, System.Int32 m) <0x7f1c08e51140 + 0x000d9> in <f6b2b0ea894844dc83a96f9504d8f570#17898f75ae7b737216569c39168a3967>:0
240  at Program.Main () <0x7f1c08e51010 + 0x000ea> in <f6b2b0ea894844dc83a96f9504d8f570#17898f75ae7b737216569c39168a3967>:0
241";
242
243        let trace = [
244            "at C.qwe()",
245            "at B..ctor() in /home/user/dotnet/2/A.cs:line 37",
246            "at A`1.<>c.<set_Q>b__1_1() in /home/user/dotnet/2/A.cs:line 15",
247            "at A`1.h[Z](Func`1 a)",
248            "at A`1[T].<set_Q>g__g|1_0 (System.Int32[] arr) <0x40b745f0 + 0x00122> in /home/user/mono/2/src/2.cs:13",
249            "at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback () [0x00000] in <c79446e93efd45a0b7bc2f9631593aff>:0",
250            "at A`1[T].set_Q (System.Int32 value) <0x40275140 + 0x00082> in <f6b2b0ea894844dc83a96f9504d8f570#610bc057486c618efb3936233b088988>:0",
251            "at (wrapper runtime-invoke) staticperformanceoptimization.runtime_invoke_void (object,intptr,intptr,intptr) <0xffffffff>",
252            "at (wrapper managed-to-native) System.Drawing.GDIPlus:GdiplusStartup (ulong&,System.Drawing.GdiplusStartupInput&,System.Drawing.GdiplusStartupOutput&)",
253            "at Program+<>c.<Main>b__0_2 (System.Int32 i) <0x7f1c08e516b0 + 0x00035> in <f6b2b0ea894844dc83a96f9504d8f570#17898f75ae7b737216569c39168a3967>:0",
254            "at Program.<Main>g__f|0_0 (System.Func`2[T,TResult] d, System.Int32& l, System.Int32 m) <0x7f1c08e51140 + 0x000d9> in <f6b2b0ea894844dc83a96f9504d8f570#17898f75ae7b737216569c39168a3967>:0",
255            "at Program.Main () <0x7f1c08e51010 + 0x000ea> in <f6b2b0ea894844dc83a96f9504d8f570#17898f75ae7b737216569c39168a3967>:0"
256        ].map(String::from).to_vec();
257
258        let bt = CSharpStacktrace::extract_stacktrace(stream).unwrap();
259
260        assert_eq!(bt, trace);
261
262        let mut stacktrace = match CSharpStacktrace::parse_stacktrace(&trace) {
263            Ok(s) => s,
264            Err(e) => panic!("{e}"),
265        };
266
267        assert_eq!(stacktrace.len(), 12);
268
269        assert_eq!(stacktrace[0].function, "C.qwe()".to_string());
270        assert_eq!(stacktrace[1].function, "B..ctor()".to_string());
271        assert_eq!(
272            stacktrace[1].debug.file,
273            "/home/user/dotnet/2/A.cs".to_string()
274        );
275        assert_eq!(stacktrace[1].debug.line, 37);
276        assert_eq!(
277            stacktrace[2].function,
278            "A`1.<>c.<set_Q>b__1_1()".to_string()
279        );
280        assert_eq!(
281            stacktrace[2].debug.file,
282            "/home/user/dotnet/2/A.cs".to_string()
283        );
284        assert_eq!(stacktrace[2].debug.line, 15);
285        assert_eq!(stacktrace[6].address, 1076318658);
286        assert_eq!(
287            stacktrace[6].function,
288            "A`1[T].set_Q(System.Int32 value)".to_string()
289        );
290        assert_eq!(
291            stacktrace[6].debug.file,
292            "f6b2b0ea894844dc83a96f9504d8f570".to_string()
293        );
294        assert_eq!(stacktrace[6].debug.line, 0);
295        assert_eq!(stacktrace[7].address, 0xffffffff);
296        assert_eq!(
297            stacktrace[7].function,
298            "(wrapper runtime-invoke) staticperformanceoptimization.runtime_invoke_void(object,intptr,intptr,intptr)".to_string()
299        );
300        assert_eq!(
301            stacktrace[8].function,
302            "(wrapper managed-to-native) System.Drawing.GDIPlus:GdiplusStartup(ulong&,System.Drawing.GdiplusStartupInput&,System.Drawing.GdiplusStartupOutput&)".to_string()
303        );
304        assert_eq!(stacktrace[9].address, 139758385043173);
305        assert_eq!(
306            stacktrace[9].function,
307            "Program+<>c.<Main>b__0_2(System.Int32 i)".to_string()
308        );
309        assert_eq!(
310            stacktrace[9].debug.file,
311            "f6b2b0ea894844dc83a96f9504d8f570".to_string()
312        );
313        assert_eq!(stacktrace[9].debug.line, 0);
314        assert_eq!(
315            stacktrace[10].function,
316            "Program.<Main>g__f|0_0(System.Func`2[T,TResult] d, System.Int32& l, System.Int32 m)"
317                .to_string()
318        );
319        assert_eq!(stacktrace[10].address, 139758385041945);
320        assert_eq!(
321            stacktrace[10].debug.file,
322            "f6b2b0ea894844dc83a96f9504d8f570".to_string()
323        );
324        assert_eq!(stacktrace[10].debug.line, 0);
325
326        safe_init_ignore_stack_frames();
327        stacktrace.filter();
328
329        assert_eq!(stacktrace.len(), 4);
330
331        assert_eq!(stacktrace[0].function, "A`1.h[Z](Func`1 a)".to_string());
332        assert_eq!(stacktrace[1].address, 1085753106);
333        assert_eq!(
334            stacktrace[1].function,
335            "A`1[T].<set_Q>g__g|1_0(System.Int32[] arr)".to_string()
336        );
337        assert_eq!(
338            stacktrace[1].debug.file,
339            "/home/user/mono/2/src/2.cs".to_string()
340        );
341        assert_eq!(stacktrace[1].debug.line, 13);
342        assert_eq!(stacktrace[2].address, 0);
343        assert_eq!(
344            stacktrace[2].function,
345            "System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()".to_string()
346        );
347        assert_eq!(
348            stacktrace[2].debug.file,
349            "c79446e93efd45a0b7bc2f9631593aff".to_string()
350        );
351        assert_eq!(stacktrace[2].debug.line, 0);
352        assert_eq!(stacktrace[3].address, 139758385041658);
353        assert_eq!(stacktrace[3].function, "Program.Main()".to_string());
354        assert_eq!(
355            stacktrace[3].debug.file,
356            "f6b2b0ea894844dc83a96f9504d8f570".to_string()
357        );
358        assert_eq!(stacktrace[3].debug.line, 0);
359    }
360}