Skip to content

Commit

Permalink
Tests added.
Browse files Browse the repository at this point in the history
  • Loading branch information
BMikh0 committed Feb 13, 2024
1 parent a0d52ab commit 3f2bc99
Show file tree
Hide file tree
Showing 3 changed files with 243 additions and 27 deletions.
10 changes: 10 additions & 0 deletions libcasr/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
242 changes: 221 additions & 21 deletions libcasr/src/csharp.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand All @@ -14,12 +14,12 @@ pub struct CSharpStacktrace;
impl CSharpStacktrace {
fn get_regex_format_for_stacktrace_entry(entry: &str) -> Result<Regex> {
let regexps = [
// Mono
Regex::new(r"^at (?P<service_info>\(.+?\) )?(?P<function>\S+?)(?: ?(?P<params>\(.*?\)))? (?:<(?P<base>\w+) \+ (?P<offset>\w+)>|\[0x[\da-fA-F]+\]) in (?:<[\da-fA-F#]+>|(?P<file>.+)):(?P<line>\w+)$").unwrap(),
// DotNet
Regex::new(r"^at (?P<service_info>\(.+?\) )?(?P<function>\S+?)(?P<params>\(.*?\))?(?: in (?P<file>.+):line (?P<line>\w+))?$").unwrap()
Regex::new(r"^at (?:(?P<service_info>\(.+?\)) )?(?P<function>\S+?)(?P<params>\(.*?\))?(?: in (?P<file>.+):line (?P<line>\w+))?$").unwrap(),
// Mono
Regex::new(r"^at (?:(?P<service_info>\(.+?\)) )?(?P<function>\S+?)(?: ?(?P<params>\(.*?\)))?(?: (?:<(?P<base>\w+)(?: \+ (?P<offset>\w+))?>|\[0x[\da-fA-F]+\]))?(?: in (?:<[\da-fA-F#]+>|(?P<file>.+)):(?P<line>\w+))?$").unwrap()
];

for re in regexps {
if re.is_match(entry) {
return Ok(re);
Expand All @@ -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<StacktraceEntry> {
fn parse_stacktrace_entry(entry: &str, format_regex: &Regex) -> Result<StacktraceEntry> {
let Some(cap) = format_regex.captures(entry) else {
return Err(Error::Casr(format!("Couldn't parse stacktrace line: {entry}")));
};
Expand Down Expand Up @@ -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;
Expand All @@ -87,7 +87,7 @@ impl CSharpStacktrace {

impl ParseStacktrace for CSharpStacktrace {
fn extract_stacktrace(stream: &str) -> Result<Vec<String>> {
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)
Expand Down Expand Up @@ -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::<Result<StacktraceEntry>>().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<ExecutionClass> {
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<service_info>\(.+?\)) )?(?P<function>\S+?)(?P<params>\(.*?\))?(?: in (?P<file>.+):line (?P<line>\w+))?$";
let re_mono = r"^at (?:(?P<service_info>\(.+?\)) )?(?P<function>\S+?)(?: ?(?P<params>\(.*?\)))?(?: (?:<(?P<base>\w+)(?: \+ (?P<offset>\w+))?>|\[0x[\da-fA-F]+\]))?(?: in (?:<[\da-fA-F#]+>|(?P<file>.+)):(?P<line>\w+))?$";

fn get_regex(entry: &str) -> Result<String> {
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 <f6b2b0ea894844dc83a96f9504d8f570>:0") {
Ok(re) => assert_eq!(re, re_mono),
Err(err) => panic!("{err}")
}

match get_regex("at A`1.<set_Q>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.<set_Q>g__g|1_0(Int32[] arr(((((((((( in /home/user/dotnet/2/A.cs:line 19")
}

match get_regex("at Program+<>c.<Main>b__0_2 (System.Int32 i) <0x7f30488306b0 + 0x00035> in <f6b2b0ea894844dc83a96f9504d8f570#610bc057486c618efb3936233b088988>: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.<Main>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.<set_Q>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].<set_Q>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 <f6b2b0ea894844dc83a96f9504d8f570#610bc057486c618efb3936233b088988>: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.<Main>b__0_2 (System.Int32 i) <0x7f1c08e516b0 + 0x00035> in <f6b2b0ea894844dc83a96f9504d8f570#17898f75ae7b737216569c39168a3967>:0
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
at Program.Main () <0x7f1c08e51010 + 0x000ea> in <f6b2b0ea894844dc83a96f9504d8f570#17898f75ae7b737216569c39168a3967>: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.<set_Q>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].<set_Q>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 <f6b2b0ea894844dc83a96f9504d8f570#610bc057486c618efb3936233b088988>: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.<Main>b__0_2 (System.Int32 i) <0x7f1c08e516b0 + 0x00035> in <f6b2b0ea894844dc83a96f9504d8f570#17898f75ae7b737216569c39168a3967>:0",
"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",
"at Program.Main () <0x7f1c08e51010 + 0x000ea> in <f6b2b0ea894844dc83a96f9504d8f570#17898f75ae7b737216569c39168a3967>: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.<set_Q>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].<set_Q>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.<Main>b__0_2(System.Int32 i)".to_string());
assert_eq!(stacktrace[3].address, 139758385041945);
assert_eq!(
stacktrace[3].function,
"Program.<Main>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].<set_Q>g__g|1_0 (System.Int32[] arr) <0x40b745f0 + 0x00122> in /home/user/mono/2/src/2.cs:13");
}
}
18 changes: 12 additions & 6 deletions libcasr/src/stacktrace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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();
Expand Down

0 comments on commit 3f2bc99

Please sign in to comment.