diff --git a/libcasr/src/constants.rs b/libcasr/src/constants.rs index 7be1f1c9..64e195a8 100644 --- a/libcasr/src/constants.rs +++ b/libcasr/src/constants.rs @@ -228,6 +228,11 @@ pub const STACK_FRAME_FUNCTION_IGNORE_REGEXES_CPP: &[&str] = &[ r"^atheris::", ]; +/// Regular expressions for с# functions to be ignored. +pub const STACK_FRAME_FUNCTION_IGNORE_REGEXES_CSHARP: &[&str] = &[ + r"^\(wrapper", +]; + /// Regular expressions for paths to java files that should be ignored. pub const STACK_FRAME_FILEPATH_IGNORE_REGEXES_JAVA: &[&str] = &[ // TODO @@ -309,6 +314,11 @@ pub const STACK_FRAME_FILEPATH_IGNORE_REGEXES_CPP: &[&str] = &[ r".*asan_with_fuzzer\.so", ]; +/// Regular expressions for paths to c# files that should be ignored. +pub const STACK_FRAME_FILEPATH_IGNORE_REGEXES_CSHARP: &[&str] = &[ + r"^[^.]$" +]; + // Signal numbers pub const SIGINFO_SIGILL: u32 = 4; pub const SIGINFO_SIGTRAP: u32 = 5; diff --git a/libcasr/src/csharp.rs b/libcasr/src/csharp.rs index 43c45bf4..1c8e549d 100644 --- a/libcasr/src/csharp.rs +++ b/libcasr/src/csharp.rs @@ -1,4 +1,4 @@ -//! C Sharp module implements `ParseStacktrace` and `Exception` traits for C Sharp reports. +//! C# module implements `ParseStacktrace` and `Exception` traits for C# reports. use crate::exception::Exception; use crate::stacktrace::{ParseStacktrace, Stacktrace}; @@ -14,12 +14,12 @@ pub struct CSharpStacktrace; impl CSharpStacktrace { fn get_regex_format_for_stacktrace_entry(entry: &str) -> Result { let regexps = [ - // Mono - Regex::new(r"^at (?P\(.+?\) )?(?P\S+?)(?: ?(?P\(.*?\)))? (?:<(?P\w+) \+ (?P\w+)>|\[0x[\da-fA-F]+\]) in (?:<[\da-fA-F#]+>|(?P.+)):(?P\w+)$").unwrap(), // DotNet - Regex::new(r"^at (?P\(.+?\) )?(?P\S+?)(?P\(.*?\))?(?: in (?P.+):line (?P\w+))?$").unwrap() + Regex::new(r"^at (?:(?P\(.+?\)) )?(?P\S+?)(?P\(.*?\))?(?: in (?P.+):line (?P\w+))?$").unwrap(), + // Mono + Regex::new(r"^at (?:(?P\(.+?\)) )?(?P\S+?)(?: ?(?P\(.*?\)))?(?: (?:<(?P\w+)(?: \+ (?P\w+))?>|\[0x[\da-fA-F]+\]))?(?: in (?:<[\da-fA-F#]+>|(?P.+)):(?P\w+))?$").unwrap() ]; - + for re in regexps { if re.is_match(entry) { return Ok(re); @@ -29,7 +29,7 @@ impl CSharpStacktrace { Err(Error::Casr(format!("Couldn't parse stacktrace line: {entry}"))) } - pub fn parse_stacktrace_entry(entry: &str, format_regex: &Regex) -> Result { + fn parse_stacktrace_entry(entry: &str, format_regex: &Regex) -> Result { let Some(cap) = format_regex.captures(entry) else { return Err(Error::Casr(format!("Couldn't parse stacktrace line: {entry}"))); }; @@ -75,7 +75,7 @@ impl CSharpStacktrace { } if let Some(service_info) = get_group_by_name_as_str("service_info") { - function.insert_str(0, service_info); + function = format!("{service_info} {function}"); } stentry.function = function; @@ -87,7 +87,7 @@ impl CSharpStacktrace { impl ParseStacktrace for CSharpStacktrace { fn extract_stacktrace(stream: &str) -> Result> { - let re = Regex::new(r"(?m)^Unhandled ([Ee])xception(:\n|\. )(?:.|\n)*?((?:\n\s*(?:at [\S ]+|--- End of inner exception stack trace ---))+)$").unwrap(); + let re = Regex::new(r"(?m)^Unhandled (e|E)xception(\. |:\n)(?:.|\n)*?((?:\s*(?:at [\S ]+|--- End of inner exception stack trace ---))+)$").unwrap(); let Some(cap) = re.captures(stream).and_then(|cap| ((cap.get(1).unwrap().as_str() == "E") == (cap.get(2).unwrap().as_str() == ":\n")).then_some(cap) @@ -133,34 +133,234 @@ impl ParseStacktrace for CSharpStacktrace { if let Some(s) = iter.peek() { let re = Self::get_regex_format_for_stacktrace_entry(s)?; - - return iter.map(|s| Self::parse_stacktrace_entry(s, &re)).collect() + return iter.map(|s| Self::parse_stacktrace_entry(s, &re)).collect(); } return std::iter::empty::>().collect(); } } -/// Structure provides an interface for parsing c sharp exception message. +/// Structure provides an interface for parsing c# exception message. pub struct CSharpException; impl Exception for CSharpException { fn parse_exception(stream: &str) -> Option { - let re_mono = Regex::new(r"(?m)^Unhandled Exception:\n((?:.|\n)+?(?: ---> (?:.|\n)+?)*?)\n\s*(?:at .+|--- End of inner exception stack trace ---)$").unwrap(); - let re_dotnet = Regex::new(r"(?m)^Unhandled exception\. ((?:.|\n)+?(?:\n ---> (?:.|\n)+?)*?)\n\s*(?:at .+|--- End of inner exception stack trace ---)$").unwrap(); + let re = Regex::new(r"(?m)^Unhandled (e|E)xception(\. |:\n)((?:.|\n)*?)\s*(?:at [\S ]+|--- End of inner exception stack trace ---)$").unwrap(); + + let cap = re.captures(stream)?; + let get_group_as_str = |i| cap.get(i).unwrap().as_str(); - let description = re_mono.captures(stream) - .or(re_dotnet.captures(stream))? - .get(1)? - .as_str(); + let is_mono = get_group_as_str(1) == "E"; + + if is_mono != (get_group_as_str(2) == ":\n") { return None; } + + let description = get_group_as_str(3); + let delimiter = if is_mono { " ---> " } else { "\n ---> " }; let (exception, message) = description - .trim_start() - .rsplit_once(" ---> ") + .rsplit_once(delimiter) .map_or(description, |(_, s)| s) - .split_once(": ") - .unwrap(); + .split_once(": ")?; Some(ExecutionClass { short_description: exception.to_string(), description: message.to_string(), ..ExecutionClass::default()}) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{init_ignored_frames, stacktrace::Filter}; + + #[test] + fn test_csharp_get_regex_format_for_stacktrace_entry() { + let re_dotnet = r"^at (?:(?P\(.+?\)) )?(?P\S+?)(?P\(.*?\))?(?: in (?P.+):line (?P\w+))?$"; + let re_mono = r"^at (?:(?P\(.+?\)) )?(?P\S+?)(?: ?(?P\(.*?\)))?(?: (?:<(?P\w+)(?: \+ (?P\w+))?>|\[0x[\da-fA-F]+\]))?(?: in (?:<[\da-fA-F#]+>|(?P.+)):(?P\w+))?$"; + + fn get_regex(entry: &str) -> Result { + CSharpStacktrace::get_regex_format_for_stacktrace_entry(entry).map(|r| r.to_string()) + } + + match get_regex("at A`1[T].h[Z] (System.Func`1[TResult] a) [0x00001] in :0") { + Ok(re) => assert_eq!(re, re_mono), + Err(err) => panic!("{err}") + } + + match get_regex("at A`1.g__g|1_0(Int32[] arr(((((((((( in /home/user/dotnet/2/A.cs:line 19") { + Ok(_) => assert!(false), + Err(err) => assert_eq!(err.to_string(), "Casr: Couldn't parse stacktrace line: at A`1.g__g|1_0(Int32[] arr(((((((((( in /home/user/dotnet/2/A.cs:line 19") + } + + match get_regex("at Program+<>c.
b__0_2 (System.Int32 i) <0x7f30488306b0 + 0x00035> in :0") { + Ok(re) => assert_eq!(re, re_mono), + Err(err) => panic!("{err}") + } + + match get_regex("at (wrapper managed-to-native) System.Drawing.GDIPlus:GdiplusStartup (ulong&,System.Drawing.GdiplusStartupInput&,System.Drawing.GdiplusStartupOutput&)") { + Ok(re) => assert_eq!(re, re_mono), + Err(err) => panic!("{err}") + } + + match get_regex("at Program.
g__f|0_0(Func`2 d, Int32& l, Int32 m) in /home/user/dotnet/2/Program.cs:line 9") { + Ok(re) => assert_eq!(re, re_dotnet), + Err(err) => panic!("{err}") + } + + match get_regex("at Program.Main()") { + Ok(re) => assert_eq!(re, re_dotnet), + Err(err) => panic!("{err}") + } + } + + #[test] + fn test_csharp_parse_exception() { + let streams = [ + " +Unhandled Exception: +System.ArithmeticException: 123 + +321 + +7 + ---> System.ArgumentException: 1111 ---> System.IO.IOException: cccc + --- End of inner exception stack trace ---", + " +Unhandled Exception: +System.ArgumentException: 1111 + ---> System.IO.IOException: cccc + --- End of inner exception stack trace ---", + " +Unhandled exception. System.ArgumentException: 1111 + ---> System.IO.IOException: cccc + --- End of inner exception stack trace ---", + " +Unhandled exception. System.ArgumentException: 1111 ---> System.IO.IOException: cccc + --- End of inner exception stack trace ---" + ]; + + let exceptions = streams.map(|s| { + let Some(e) = CSharpException::parse_exception(s) else { + panic!("Couldn't get C# exception from stream: {s}"); + }; + e + }); + + assert_eq!(exceptions[0].short_description, "System.IO.IOException"); + assert_eq!(exceptions[0].description, "cccc"); + assert_eq!(exceptions[1].short_description, "System.IO.IOException"); + assert_eq!(exceptions[1].description, "cccc"); + assert_eq!(exceptions[2].short_description, "System.IO.IOException"); + assert_eq!(exceptions[2].description, "cccc"); + assert_eq!(exceptions[3].short_description, "System.ArgumentException"); + assert_eq!(exceptions[3].description, "1111 ---> System.IO.IOException: cccc"); + } + + #[test] + fn test_csharp_stacktrace() { + let stream = "Unhandled exception. System.ArithmeticException: 123 + +321 + +7 + + ---> System.ArgumentException: 1111 + ---> System.IO.IOException: cccc + ---> System.IO.IOException: bbbb + ---> System.IO.IOException: aaaa + ---> System.IO.IOException: I/O error occurred. + --- End of inner exception stack trace --- + --- End of inner exception stack trace --- + --- End of inner exception stack trace --- + at B..ctor() in /home/user/dotnet/2/A.cs:line 37 + at A`1.<>c.b__1_1() in /home/user/dotnet/2/A.cs:line 15 + at A`1.h[Z](Func`1 a) + --- End of inner exception stack trace --- + --- End of inner exception stack trace --- + at A`1.h[Z](Func`1 a) + at A`1[T].g__g|1_0 (System.Int32[] arr) <0x40b745f0 + 0x00122> in /home/user/mono/2/src/2.cs:13 + at A`1[T].set_Q (System.Int32 value) <0x40275140 + 0x00082> in :0 + at (wrapper runtime-invoke) staticperformanceoptimization.runtime_invoke_void (object,intptr,intptr,intptr) <0xffffffff> + at (wrapper managed-to-native) System.Drawing.GDIPlus:GdiplusStartup (ulong&,System.Drawing.GdiplusStartupInput&,System.Drawing.GdiplusStartupOutput&) + at Program+<>c.
b__0_2 (System.Int32 i) <0x7f1c08e516b0 + 0x00035> in :0 + at Program.
g__f|0_0 (System.Func`2[T,TResult] d, System.Int32& l, System.Int32 m) <0x7f1c08e51140 + 0x000d9> in :0 + at Program.Main () <0x7f1c08e51010 + 0x000ea> in :0 +"; + + let trace = [ + "--- End of inner exception stack trace ---", + "--- End of inner exception stack trace ---", + "--- End of inner exception stack trace ---", + "at B..ctor() in /home/user/dotnet/2/A.cs:line 37", + "at A`1.<>c.b__1_1() in /home/user/dotnet/2/A.cs:line 15", + "at A`1.h[Z](Func`1 a)", + "--- End of inner exception stack trace ---", + "--- End of inner exception stack trace ---", + "at A`1.h[Z](Func`1 a)", + "at A`1[T].g__g|1_0 (System.Int32[] arr) <0x40b745f0 + 0x00122> in /home/user/mono/2/src/2.cs:13", + "at A`1[T].set_Q (System.Int32 value) <0x40275140 + 0x00082> in :0", + "at (wrapper runtime-invoke) staticperformanceoptimization.runtime_invoke_void (object,intptr,intptr,intptr) <0xffffffff>", + "at (wrapper managed-to-native) System.Drawing.GDIPlus:GdiplusStartup (ulong&,System.Drawing.GdiplusStartupInput&,System.Drawing.GdiplusStartupOutput&)", + "at Program+<>c.
b__0_2 (System.Int32 i) <0x7f1c08e516b0 + 0x00035> in :0", + "at Program.
g__f|0_0 (System.Func`2[T,TResult] d, System.Int32& l, System.Int32 m) <0x7f1c08e51140 + 0x000d9> in :0", + "at Program.Main () <0x7f1c08e51010 + 0x000ea> in :0" + ].map(String::from).to_vec(); + + let bt = CSharpStacktrace::extract_stacktrace(stream).unwrap(); + + assert_eq!(bt, trace); + + let mut stacktrace = match CSharpStacktrace::parse_stacktrace(&trace[0..8]) { + Ok(s) => s, + Err(e) => panic!("{e}") + }; + + assert_eq!(stacktrace.len(), 3); + assert_eq!(stacktrace[0].function, "B..ctor()".to_string()); + assert_eq!(stacktrace[0].debug.file, "/home/user/dotnet/2/A.cs".to_string()); + assert_eq!(stacktrace[0].debug.line, 37); + assert_eq!(stacktrace[1].function, "A`1.<>c.b__1_1()".to_string()); + assert_eq!(stacktrace[1].debug.file, "/home/user/dotnet/2/A.cs".to_string()); + assert_eq!(stacktrace[1].debug.line, 15); + assert_eq!(stacktrace[2].function, "A`1.h[Z](Func`1 a)".to_string()); + + stacktrace = match CSharpStacktrace::parse_stacktrace(&trace[9..16]) { + Ok(s) => s, + Err(e) => panic!("{e}") + }; + + assert_eq!(stacktrace.len(), 7); + assert_eq!( + stacktrace[2].function, + "(wrapper runtime-invoke) staticperformanceoptimization.runtime_invoke_void(object,intptr,intptr,intptr)".to_string() + ); + assert_eq!( + stacktrace[3].function, + "(wrapper managed-to-native) System.Drawing.GDIPlus:GdiplusStartup(ulong&,System.Drawing.GdiplusStartupInput&,System.Drawing.GdiplusStartupOutput&)".to_string() + ); + + init_ignored_frames!("csharp"); + stacktrace.filter(); + + assert_eq!(stacktrace.len(), 5); + assert_eq!(stacktrace[0].address, 1085753106); + assert_eq!(stacktrace[0].function, "A`1[T].g__g|1_0(System.Int32[] arr)".to_string()); + assert_eq!(stacktrace[0].debug.file, "/home/user/mono/2/src/2.cs".to_string()); + assert_eq!(stacktrace[0].debug.line, 13); + assert_eq!(stacktrace[1].address, 1076318658); + assert_eq!(stacktrace[1].function, "A`1[T].set_Q(System.Int32 value)".to_string()); + + assert_eq!(stacktrace[2].address, 139758385043173); + assert_eq!(stacktrace[2].function, "Program+<>c.
b__0_2(System.Int32 i)".to_string()); + assert_eq!(stacktrace[3].address, 139758385041945); + assert_eq!( + stacktrace[3].function, + "Program.
g__f|0_0(System.Func`2[T,TResult] d, System.Int32& l, System.Int32 m)".to_string() + ); + assert_eq!(stacktrace[4].address, 139758385041658); + assert_eq!(stacktrace[4].function, "Program.Main()".to_string()); + + let sttr = CSharpStacktrace::parse_stacktrace(&trace); + + assert!(sttr.is_err()); + assert_eq!(sttr.err().unwrap().to_string(), "Casr: Couldn't parse stacktrace line: at A`1[T].g__g|1_0 (System.Int32[] arr) <0x40b745f0 + 0x00122> in /home/user/mono/2/src/2.cs:13"); + } +} diff --git a/libcasr/src/stacktrace.rs b/libcasr/src/stacktrace.rs index 97e4083f..b5dceaf4 100644 --- a/libcasr/src/stacktrace.rs +++ b/libcasr/src/stacktrace.rs @@ -3,12 +3,14 @@ extern crate kodama; extern crate lazy_static; use crate::constants::{ - STACK_FRAME_FILEPATH_IGNORE_REGEXES_CPP, STACK_FRAME_FILEPATH_IGNORE_REGEXES_GO, - STACK_FRAME_FILEPATH_IGNORE_REGEXES_JAVA, STACK_FRAME_FILEPATH_IGNORE_REGEXES_JS, - STACK_FRAME_FILEPATH_IGNORE_REGEXES_PYTHON, STACK_FRAME_FILEPATH_IGNORE_REGEXES_RUST, - STACK_FRAME_FUNCTION_IGNORE_REGEXES_CPP, STACK_FRAME_FUNCTION_IGNORE_REGEXES_GO, - STACK_FRAME_FUNCTION_IGNORE_REGEXES_JAVA, STACK_FRAME_FUNCTION_IGNORE_REGEXES_JS, - STACK_FRAME_FUNCTION_IGNORE_REGEXES_PYTHON, STACK_FRAME_FUNCTION_IGNORE_REGEXES_RUST, + STACK_FRAME_FILEPATH_IGNORE_REGEXES_CPP, STACK_FRAME_FILEPATH_IGNORE_REGEXES_CSHARP, + STACK_FRAME_FILEPATH_IGNORE_REGEXES_GO, STACK_FRAME_FILEPATH_IGNORE_REGEXES_JAVA, + STACK_FRAME_FILEPATH_IGNORE_REGEXES_JS, STACK_FRAME_FILEPATH_IGNORE_REGEXES_PYTHON, + STACK_FRAME_FILEPATH_IGNORE_REGEXES_RUST, + STACK_FRAME_FUNCTION_IGNORE_REGEXES_CPP, STACK_FRAME_FUNCTION_IGNORE_REGEXES_CSHARP, + STACK_FRAME_FUNCTION_IGNORE_REGEXES_GO, STACK_FRAME_FUNCTION_IGNORE_REGEXES_JAVA, + STACK_FRAME_FUNCTION_IGNORE_REGEXES_JS, STACK_FRAME_FUNCTION_IGNORE_REGEXES_PYTHON, + STACK_FRAME_FUNCTION_IGNORE_REGEXES_RUST, }; use crate::error::*; use kodama::{linkage, Method}; @@ -330,6 +332,10 @@ pub trait Filter { STACK_FRAME_FUNCTION_IGNORE_REGEXES_JS, STACK_FRAME_FILEPATH_IGNORE_REGEXES_JS, ), + "csharp" => ( + STACK_FRAME_FUNCTION_IGNORE_REGEXES_CSHARP, + STACK_FRAME_FILEPATH_IGNORE_REGEXES_CSHARP, + ), &_ => (["^[^.]$"].as_slice(), ["^[^.]$"].as_slice()), }) .unzip();