Skip to content

Commit

Permalink
Parsing refinement
Browse files Browse the repository at this point in the history
  • Loading branch information
PaDarochek committed Oct 10, 2023
1 parent 7545dea commit aac6e34
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 100 deletions.
16 changes: 16 additions & 0 deletions libcasr/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ pub const STACK_FRAME_FUNCTION_IGNORE_REGEXES_JAVA: &[&str] = &[
r"^javax\.",
];

/// Regular expressions for JS functions to be ignored.
pub const STACK_FRAME_FUNCTION_IGNORE_REGEXES_JS: &[&str] = &[
// TODO
r"^<anonymous>$",
];

/// Regular expressions for python functions to be ignored.
pub const STACK_FRAME_FUNCTION_IGNORE_REGEXES_PYTHON: &[&str] = &[
// TODO
Expand Down Expand Up @@ -228,6 +234,16 @@ pub const STACK_FRAME_FILEPATH_IGNORE_REGEXES_JAVA: &[&str] = &[
r"^[^.]$",
];

/// Regular expressions for paths to JS files that should be ignored.
pub const STACK_FRAME_FILEPATH_IGNORE_REGEXES_JS: &[&str] = &[
// TODO
r"^<anonymous>$",
r"^native$",
// JS internal modules
r"^(|node:)internal/?",
r"^(|node:)events/?",
];

/// Regular expressions for paths to python files that should be ignored.
pub const STACK_FRAME_FILEPATH_IGNORE_REGEXES_PYTHON: &[&str] = &[
// TODO
Expand Down
242 changes: 142 additions & 100 deletions libcasr/src/js.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
//! JS module implements `ParseStacktrace` and `Exception` traits for JS reports.
use crate::stacktrace::ParseStacktrace;
use crate::error::{Error, Result};
use crate::stacktrace::{ParseStacktrace, Stacktrace, StacktraceEntry};

use crate::error::*;
use crate::stacktrace::*;
use regex::Regex;

/// Structure provides an interface for processing the stack trace.
Expand All @@ -24,7 +23,7 @@ impl ParseStacktrace for JSStacktrace {
.as_str()
.split('\n')
.filter(|l| !l.is_empty())
.map(|l| l.to_string())
.map(|l| l.trim().to_string())
.collect::<Vec<String>>();

Ok(stacktrace)
Expand All @@ -40,46 +39,45 @@ impl ParseStacktrace for JSStacktrace {
Regex::new(r"^(?:\s|\t)*at(?:\s|\t)+(.+?)(?:(?:(?:\s|\t)+(\[as.*?\]))?(?:\s|\t)*)$")
.unwrap();

fn parse_location(
entry: &str,
stentry: &mut StacktraceEntry,
loc: &str,
can_be_func: bool,
) -> Result<()> {
fn parse_location(entry: &str, stentry: &mut StacktraceEntry, loc: &str) -> Result<()> {
if loc.is_empty() {
return Err(Error::Casr(format!(
"Couldn't parse location. Entry: {entry}"
)));
}

let mut debug: Vec<String> = loc.split(':').map(|s| s.to_string()).collect();
if debug.len() > 1 {
if debug.len() > 3 {
let tail = debug[debug.len() - 2..].to_vec();
debug = [debug[..debug.len() - 2].join(":")].to_vec();
debug.append(&mut tail.to_vec());
}
if debug.len() == 1 {
// Location contains filename only
stentry.debug.file = debug[0].to_string();
stentry.debug.line = if let Ok(line) = debug[1].parse::<u64>() {
line
return Ok(());
}
if debug.len() > 3 {
// Filename contains ':' so all the elements except
// the last 2 belong to filename
let tail = debug[debug.len() - 2..].to_vec();
debug = [debug[..debug.len() - 2].join(":")].to_vec();
debug.append(&mut tail.to_vec());
}

stentry.debug.file = debug[0].to_string();
stentry.debug.line = if let Ok(line) = debug[1].parse::<u64>() {
line
} else {
return Err(Error::Casr(format!(
"Couldn't parse line number {}. Entry: {entry}",
debug[1]
)));
};
if debug.len() == 3 {
stentry.debug.column = if let Ok(column) = debug[2].parse::<u64>() {
column
} else {
return Err(Error::Casr(format!(
"Couldn't parse line number {}. Entry: {entry}",
debug[1]
"Couldn't parse column number {}. Entry: {entry}",
debug[2]
)));
};
if debug.len() == 3 {
stentry.debug.column = if let Ok(column) = debug[2].parse::<u64>() {
column
} else {
return Err(Error::Casr(format!(
"Couldn't parse column number {}. Entry: {entry}",
debug[2]
)));
}
}
} else if can_be_func {
if debug[0].ends_with(".js") || debug[0] == "native" {
stentry.debug.file = debug[0].to_string();
} else {
stentry.function = debug[0].to_string();
}
} else {
stentry.debug.file = debug[0].to_string();
}
Ok(())
}
Expand All @@ -97,28 +95,33 @@ impl ParseStacktrace for JSStacktrace {

let debug = cap.get(1).unwrap().as_str().to_string();
if debug == "<anonymous>" {
// Eval is located in anonymous function
stentry.function = "eval".to_string();
} else {
// Can append function name where eval is located
stentry.function = "eval at ".to_string() + debug.as_str();
}
if let Some(method_name) = cap.get(2) {
// at eval (eval at func [as method] (file:line[:column]))
stentry.function += (" ".to_string() + method_name.as_str()).as_str();
}

let debug = cap.get(3).unwrap().as_str().to_string();
if debug.contains('(') || debug.contains(')') {
// Irrelevant entry that contains nested evals
// Fill <anonymous> to filter this ectry from stacktrace
// Entry contains nested evals
// Fill <anonymous> to filter this entry from stacktrace
// after parsing
stentry.function = "<anonymous>".to_string();
return Ok(());
}
parse_location(entry, stentry, &debug, false)?;
parse_location(entry, stentry, &debug)?;

// Recalculate location adding offset inside eval function
let debug = cap.get(4).unwrap().as_str().to_string();
let mut eval_stentry = StacktraceEntry::default();
parse_location(entry, &mut eval_stentry, &debug, false)?;
if eval_stentry.debug.line != 0 {
parse_location(entry, &mut eval_stentry, &debug)?;
if eval_stentry.debug.line >= 3 {
// Line number inside eval function starts with 3
stentry.debug.line += eval_stentry.debug.line - 3;
if eval_stentry.debug.column != 0 {
stentry.debug.column = eval_stentry.debug.column;
Expand All @@ -129,21 +132,25 @@ impl ParseStacktrace for JSStacktrace {

if let Some(cap) = re_full.captures(entry) {
if entry.starts_with("at eval") && entry.contains("eval at") {
// at eval (eval at func (file:line[:column]), <anonymous>:line[:column])
// Parse eval
parse_eval(entry, &mut stentry)?;
} else {
// at function ([file][:line[:column]])
// Not eval
// at func (file:line[:column])
// Parse function with location
stentry.function = cap.get(1).unwrap().as_str().to_string();
if let Some(method_name) = cap.get(2) {
// at func [as method] (file:line[:column])
stentry.function += (" ".to_string() + method_name.as_str()).as_str();
}
let debug = cap.get(3).unwrap().as_str().to_string();
parse_location(entry, &mut stentry, &debug, false)?;
parse_location(entry, &mut stentry, &debug)?;
}
} else if let Some(cap) = re_without_pars.captures(entry) {
// at file:line[:column]
// Parse location only
let debug = cap.get(1).unwrap().as_str().to_string();
parse_location(entry, &mut stentry, &debug, true)?;
parse_location(entry, &mut stentry, &debug)?;
} else {
return Err(Error::Casr(format!(
"Couldn't parse stacktrace line: {entry}"
Expand All @@ -159,51 +166,97 @@ impl ParseStacktrace for JSStacktrace {
.map(String::as_str)
.filter(|entry| !entry.contains("unknown location"))
.map(Self::parse_stacktrace_entry)
.filter(|entry| {
entry.as_ref().is_ok_and(|x| {
x.debug.file != "native"
&& x.debug.file != "<anonymous>"
&& x.function != "<anonymous>"
})
})
.collect()
}
}

#[cfg(test)]
mod tests {
use super::*;
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,
},
init_ignored_frames,
stacktrace::{
Filter, STACK_FRAME_FILEPATH_IGNORE_REGEXES, STACK_FRAME_FUNCTION_IGNORE_REGEXES,
},
};

#[test]
fn test_js_stacktrace() {
let stream = r#"Uncaught ReferenceError: var is not defined
at new Uint8Array (<anonymous>)
at Object.decode (/fuzz/node_modules/jpeg-js/lib/decoder.js:1110:13)
at fuzz (/fuzz/FuzzTarget.js:6:14)
at result (/fuzz/node_modules/@jazzer.js/core/core.ts:335:15)
at Worker.fuzz [as fn] (/home/user/test_js_stacktrace/main.js:1:2017)
at process.<anonymous> (/home/user/.nvm/versions/node/v16.15.1/lib/node_modules/jsfuzz/build/src/worker.js:55:30)
at process.emit (node:events:527:28)
at <anonymous>
at bootstrap_node.js:609:3
at file:///home/user/node/offset.js:3:37
at async Loader.import (internal/modules/esm/loader.js:178:24)
at eval (eval at <anonymous> (eval at g (/fuzz/FuzzTarget.js:7:7)), <anonymous>:4:23)
at eval (eval at <anonymous> (file:///home/user/node/offset.js:3:3), <anonymous>:3:7)
at eval (eval at g (/fuzz/FuzzTarget.js:7:7), <anonymous>:8:13)
at eval (/.svelte-kit/runtime/components/layout.svelte:8:41)
Uncaught ReferenceError: var is not defined
at Object.decode (/fuzz/node_modules/jpeg-js/lib/decoder.js:1110:13)
at fuzz (/fuzz/FuzzTarget.js:6:14)
at result (/fuzz/node_modules/@jazzer.js/core/core.ts:335:15)
at Worker.fuzz [as fn] (/home/user/test_js_stacktrace/main.js:1:2017)
at process.<anonymous> (/home/user/.nvm/versions/node/v16.15.1/lib/node_modules/jsfuzz/build/src/worker.js:55:30)
at process.emit (node:events:527:28)
at <anonymous>
at bootstrap_node.js:609:3
at file:///home/user/node/offset.js:3:37
at async Loader.import (internal/modules/esm/loader.js:178:24)
at eval (eval at <anonymous> (eval at g (/fuzz/FuzzTarget.js:7:7)), <anonymous>:4:23)
at eval (eval at <anonymous> (file:///home/user/node/offset.js:3:3), <anonymous>:3:7)
at eval (eval at g (/fuzz/FuzzTarget.js:7:7), <anonymous>:8:13)
at eval (/.svelte-kit/runtime/components/layout.svelte:8:41)"#;

let raw_stacktrace = &[
"at new Uint8Array (<anonymous>)",
"at Object.decode (/fuzz/node_modules/jpeg-js/lib/decoder.js:1110:13)",
"at fuzz (/fuzz/FuzzTarget.js:6:14)",
"at result (/fuzz/node_modules/@jazzer.js/core/core.ts:335:15)",
"at Worker.fuzz [as fn] (/home/pa_darochek/Documents/SandBox/test_js_stacktrace/main.js:1:2017)",
"at process.<anonymous> (/home/pa_darochek/.nvm/versions/node/v16.15.1/lib/node_modules/jsfuzz/build/src/worker.js:55:30)",
"at Worker.fuzz [as fn] (/home/user/test_js_stacktrace/main.js:1:2017)",
"at process.<anonymous> (/home/user/.nvm/versions/node/v16.15.1/lib/node_modules/jsfuzz/build/src/worker.js:55:30)",
"at process.emit (node:events:527:28)",
"at func (:1:2)",
"at <anonymous>",
"at bootstrap_node.js:609:3",
"at file://<path-to-parent-folder>/offset.js:3:37",
"at file:///home/user/node/offset.js:3:37",
"at async Loader.import (internal/modules/esm/loader.js:178:24)",
"at eval (eval at <anonymous> (eval at g (/fuzz/FuzzTarget.js:7:7)), <anonymous>:4:23)",
"at eval (eval at <anonymous> (file://<path-to-parent-folder>/offset.js:3:3), <anonymous>:3:7)",
"at eval (eval at <anonymous> (file:///home/user/node/offset.js:3:3), <anonymous>:3:7)",
"at eval (eval at g (/fuzz/FuzzTarget.js:7:7), <anonymous>:8:13)",
"at eval (/.svelte-kit/runtime/components/layout.svelte:8:41)",
];

let trace = raw_stacktrace
.iter()
.map(|e| e.to_string())
.collect::<Vec<String>>();
let sttr = JSStacktrace::parse_stacktrace(&trace);
let bt = JSStacktrace::extract_stacktrace(stream).unwrap();
assert_eq!(bt, trace);

let sttr = JSStacktrace::parse_stacktrace(&bt);
if sttr.is_err() {
panic!("{}", sttr.err().unwrap());
}
let mut stacktrace = sttr.unwrap();

init_ignored_frames!("js");
stacktrace.filter();

let stacktrace = sttr.unwrap();
assert_eq!(
stacktrace[0].debug.file,
"/fuzz/node_modules/jpeg-js/lib/decoder.js".to_string()
Expand All @@ -224,58 +277,47 @@ mod tests {
assert_eq!(stacktrace[2].function, "result".to_string());
assert_eq!(
stacktrace[3].debug.file,
"/home/pa_darochek/Documents/SandBox/test_js_stacktrace/main.js".to_string()
"/home/user/test_js_stacktrace/main.js".to_string()
);
assert_eq!(stacktrace[3].debug.line, 1);
assert_eq!(stacktrace[3].debug.column, 2017);
assert_eq!(stacktrace[3].function, "Worker.fuzz [as fn]".to_string());
assert_eq!(stacktrace[4].debug.file, "/home/pa_darochek/.nvm/versions/node/v16.15.1/lib/node_modules/jsfuzz/build/src/worker.js".to_string());
assert_eq!(
stacktrace[4].debug.file,
"/home/user/.nvm/versions/node/v16.15.1/lib/node_modules/jsfuzz/build/src/worker.js"
.to_string()
);
assert_eq!(stacktrace[4].debug.line, 55);
assert_eq!(stacktrace[4].debug.column, 30);
assert_eq!(stacktrace[4].function, "process.<anonymous>".to_string());
assert_eq!(stacktrace[5].debug.file, "node:events".to_string());
assert_eq!(stacktrace[5].debug.line, 527);
assert_eq!(stacktrace[5].debug.column, 28);
assert_eq!(stacktrace[5].function, "process.emit".to_string());
assert_eq!(stacktrace[6].debug.file, "".to_string());
assert_eq!(stacktrace[6].debug.line, 1);
assert_eq!(stacktrace[6].debug.column, 2);
assert_eq!(stacktrace[6].function, "func".to_string());
assert_eq!(stacktrace[7].debug.file, "bootstrap_node.js".to_string());
assert_eq!(stacktrace[7].debug.line, 609);
assert_eq!(stacktrace[7].debug.column, 3);
assert_eq!(stacktrace[7].function, "".to_string());
assert_eq!(stacktrace[5].debug.file, "bootstrap_node.js".to_string());
assert_eq!(stacktrace[5].debug.line, 609);
assert_eq!(stacktrace[5].debug.column, 3);
assert_eq!(stacktrace[5].function, "".to_string());
assert_eq!(
stacktrace[8].debug.file,
"file://<path-to-parent-folder>/offset.js".to_string()
stacktrace[6].debug.file,
"file:///home/user/node/offset.js".to_string()
);
assert_eq!(stacktrace[8].debug.line, 3);
assert_eq!(stacktrace[8].debug.column, 37);
assert_eq!(stacktrace[8].function, "".to_string());
assert_eq!(stacktrace[6].debug.line, 3);
assert_eq!(stacktrace[6].debug.column, 37);
assert_eq!(stacktrace[6].function, "".to_string());
assert_eq!(
stacktrace[9].debug.file,
"internal/modules/esm/loader.js".to_string()
stacktrace[7].debug.file,
"file:///home/user/node/offset.js".to_string()
);
assert_eq!(stacktrace[9].debug.line, 178);
assert_eq!(stacktrace[9].debug.column, 24);
assert_eq!(stacktrace[9].function, "async Loader.import".to_string());
assert_eq!(stacktrace[7].debug.line, 3);
assert_eq!(stacktrace[7].debug.column, 7);
assert_eq!(stacktrace[7].function, "eval".to_string());
assert_eq!(stacktrace[8].debug.file, "/fuzz/FuzzTarget.js".to_string());
assert_eq!(stacktrace[8].debug.line, 12);
assert_eq!(stacktrace[8].debug.column, 13);
assert_eq!(stacktrace[8].function, "eval at g".to_string());
assert_eq!(
stacktrace[10].debug.file,
"file://<path-to-parent-folder>/offset.js".to_string()
);
assert_eq!(stacktrace[10].debug.line, 3);
assert_eq!(stacktrace[10].debug.column, 7);
assert_eq!(stacktrace[10].function, "eval".to_string());
assert_eq!(stacktrace[11].debug.file, "/fuzz/FuzzTarget.js".to_string());
assert_eq!(stacktrace[11].debug.line, 12);
assert_eq!(stacktrace[11].debug.column, 13);
assert_eq!(stacktrace[11].function, "eval at g".to_string());
assert_eq!(
stacktrace[12].debug.file,
stacktrace[9].debug.file,
"/.svelte-kit/runtime/components/layout.svelte".to_string()
);
assert_eq!(stacktrace[12].debug.line, 8);
assert_eq!(stacktrace[12].debug.column, 41);
assert_eq!(stacktrace[12].function, "eval".to_string());
assert_eq!(stacktrace[9].debug.line, 8);
assert_eq!(stacktrace[9].debug.column, 41);
assert_eq!(stacktrace[9].function, "eval".to_string());
}
}
Loading

0 comments on commit aac6e34

Please sign in to comment.