Merge pull request #14 from nnethercote/fix-JSON-handling-more

Fix JSON handling more
This commit is contained in:
Nicholas Nethercote 2020-03-04 09:58:08 +11:00 коммит произвёл GitHub
Родитель d5591e4831 cf9b748ffc
Коммит b0abef037e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
2 изменённых файлов: 55 добавлений и 32 удалений

Просмотреть файл

@ -53,7 +53,7 @@ impl Interner {
} }
} }
enum JsonEscaping { enum JsonMode {
No, No,
Yes, Yes,
} }
@ -252,22 +252,23 @@ impl FileInfo {
struct Fixer { struct Fixer {
re: Regex, re: Regex,
file_infos: FxHashMap<String, FileInfo>, file_infos: FxHashMap<String, FileInfo>,
json_escaping: JsonEscaping, json_mode: JsonMode,
} }
/// Records address of functions from a symbol table. /// Records address of functions from a symbol table.
type SymFuncAddrs = FxHashMap<String, u64>; type SymFuncAddrs = FxHashMap<String, u64>;
impl Fixer { impl Fixer {
fn new(json_escaping: JsonEscaping) -> Fixer { fn new(json_mode: JsonMode) -> Fixer {
Fixer { Fixer {
// Matches lines produced by MozFormatCodeAddress(). // Matches lines produced by MozFormatCodeAddress().
re: Regex::new(r"^(.*#\d+: )(.+)\[(.+) \+0x([0-9A-Fa-f]+)\](.*)$").unwrap(), re: Regex::new(r"^(.*#\d+: )(.+)\[(.+) \+0x([0-9A-Fa-f]+)\](.*)$").unwrap(),
file_infos: FxHashMap::default(), file_infos: FxHashMap::default(),
json_escaping, json_mode,
} }
} }
/// Add JSON escapes to a fragment of text.
fn json_escape(string: &str) -> String { fn json_escape(string: &str) -> String {
// Do the escaping. // Do the escaping.
let escaped = serde_json::to_string(string).unwrap(); let escaped = serde_json::to_string(string).unwrap();
@ -276,6 +277,20 @@ impl Fixer {
escaped[1..escaped.len() - 1].to_string() escaped[1..escaped.len() - 1].to_string()
} }
/// Remove JSON escapes from a fragment of text.
fn json_unescape(string: &str) -> String {
// Add quotes.
let quoted = format!("\"{}\"", string);
// Do the unescaping, which also removes the quotes.
let value = serde_json::from_str(&quoted).unwrap();
if let serde_json::Value::String(unescaped) = value {
unescaped
} else {
panic!()
}
}
/// Read the data from `file_name` and construct a `FileInfo` that we can /// Read the data from `file_name` and construct a `FileInfo` that we can
/// subsequently query. Return a description of the failing operation on /// subsequently query. Return a description of the failing operation on
/// error. /// error.
@ -547,11 +562,19 @@ impl Fixer {
let address = u64::from_str_radix(&captures[4], 16).unwrap(); let address = u64::from_str_radix(&captures[4], 16).unwrap();
let after = &captures[5]; let after = &captures[5];
// In JSON mode, unescape the function name before using it for
// lookups, error messages, etc.
let raw_in_file_name = if let JsonMode::Yes = self.json_mode {
Fixer::json_unescape(in_file_name)
} else {
in_file_name.to_string()
};
// If we haven't seen this file yet, parse and record its contents, for // If we haven't seen this file yet, parse and record its contents, for
// this lookup and any future lookups. // this lookup and any future lookups.
let file_info = match self.file_infos.entry(in_file_name.to_string()) { let file_info = match self.file_infos.entry(raw_in_file_name.to_string()) {
Entry::Occupied(o) => o.into_mut(), Entry::Occupied(o) => o.into_mut(),
Entry::Vacant(v) => match Fixer::build_file_info(in_file_name) { Entry::Vacant(v) => match Fixer::build_file_info(&raw_in_file_name) {
Ok(file_info) => v.insert(file_info), Ok(file_info) => v.insert(file_info),
Err(op) => { Err(op) => {
// Print an error message and then set up an empty // Print an error message and then set up an empty
@ -561,32 +584,32 @@ impl Fixer {
// first occurrence. // first occurrence.
// - The line will still receive some transformation, using // - The line will still receive some transformation, using
// the "no symbols or debug info" case below. // the "no symbols or debug info" case below.
eprintln!("fix-stacks error: failed to {} `{}`", op, in_file_name); eprintln!("fix-stacks error: failed to {} `{}`", op, raw_in_file_name);
v.insert(FileInfo::default()) v.insert(FileInfo::default())
} }
}, },
}; };
// If JSON escaping is enabled, we need to escape any new strings we // In JSON mode, we need to escape any new strings we produce. However,
// produce. However, strings that came in from the text (i.e. // strings from the input (i.e. `in_func_name` and `in_file_name`),
// `in_func_name` and `in_file_name`), will already be escaped, so if // will already be escaped, so if they are used in the output they
// they become part of the output they shouldn't be escaped. // shouldn't be re-escaped.
if let Some(func_info) = file_info.func_info(address) { if let Some(func_info) = file_info.func_info(address) {
let raw_func_name = func_info.demangled_name(); let raw_out_func_name = func_info.demangled_name();
let out_func_name = if let JsonEscaping::Yes = self.json_escaping { let out_func_name = if let JsonMode::Yes = self.json_mode {
Fixer::json_escape(&raw_func_name) Fixer::json_escape(&raw_out_func_name)
} else { } else {
raw_func_name raw_out_func_name
}; };
if let Some(line_info) = func_info.line_info(address) { if let Some(line_info) = func_info.line_info(address) {
// We have the function name, filename, and line number from // We have the function name, filename, and line number from
// the debug info. // the debug info.
let raw_file_name = file_info.interner.get(line_info.path); let raw_out_file_name = file_info.interner.get(line_info.path);
let out_file_name = if let JsonEscaping::Yes = self.json_escaping { let out_file_name = if let JsonMode::Yes = self.json_mode {
Fixer::json_escape(&raw_file_name) Fixer::json_escape(&raw_out_file_name)
} else { } else {
raw_file_name.to_string() raw_out_file_name.to_string()
}; };
format!( format!(
@ -622,19 +645,19 @@ r##"usage: fix-stacks [options] < input > output
Post-process the stack frames produced by MozFormatCodeAddress(). Post-process the stack frames produced by MozFormatCodeAddress().
options: options:
-h, --help show this message and exit -h, --help Show this message and exit
-j, --json Use JSON escaping for printed function names and file names -j, --json Treat input and output as JSON fragments
"##; "##;
fn main_inner() -> io::Result<()> { fn main_inner() -> io::Result<()> {
// Process command line arguments. // Process command line arguments.
let mut json_escaping = JsonEscaping::No; let mut json_mode = JsonMode::No;
for arg in env::args().skip(1) { for arg in env::args().skip(1) {
if arg == "-h" || arg == "--help" { if arg == "-h" || arg == "--help" {
println!("{}", USAGE_MSG); println!("{}", USAGE_MSG);
return Ok(()); return Ok(());
} else if arg == "-j" || arg == "--json" { } else if arg == "-j" || arg == "--json" {
json_escaping = JsonEscaping::Yes; json_mode = JsonMode::Yes;
} else { } else {
let msg = format!( let msg = format!(
"bad argument `{}`. Run `fix-stacks -h` for more information.", "bad argument `{}`. Run `fix-stacks -h` for more information.",
@ -646,7 +669,7 @@ fn main_inner() -> io::Result<()> {
let reader = io::BufReader::new(io::stdin()); let reader = io::BufReader::new(io::stdin());
let mut fixer = Fixer::new(json_escaping); let mut fixer = Fixer::new(json_mode);
for line in reader.lines() { for line in reader.lines() {
writeln!(io::stdout(), "{}", fixer.fix(line.unwrap()))?; writeln!(io::stdout(), "{}", fixer.fix(line.unwrap()))?;
} }

Просмотреть файл

@ -31,7 +31,7 @@ fn test_linux() {
// LINE 0x11cd line=13 file=/home/njn/moz/fix-stacks/tests/example.c // LINE 0x11cd line=13 file=/home/njn/moz/fix-stacks/tests/example.c
// LINE 0x11db line=14 file=/home/njn/moz/fix-stacks/tests/example.c // LINE 0x11db line=14 file=/home/njn/moz/fix-stacks/tests/example.c
let mut fixer = Fixer::new(JsonEscaping::No); let mut fixer = Fixer::new(JsonMode::No);
// Test various addresses. // Test various addresses.
let mut func = |name, addr, linenum| { let mut func = |name, addr, linenum| {
@ -62,7 +62,7 @@ fn test_linux() {
func("g", 0x11de, 14); func("g", 0x11de, 14);
// Try a new Fixer. // Try a new Fixer.
fixer = Fixer::new(JsonEscaping::No); fixer = Fixer::new(JsonMode::No);
// Test various addresses outside `main`, `f`, and `g`. // Test various addresses outside `main`, `f`, and `g`.
let mut outside = |addr| { let mut outside = |addr| {
@ -110,7 +110,7 @@ fn test_windows() {
// outputs contains backwards slashes, though, because that is what is used // outputs contains backwards slashes, though, because that is what is used
// within the debug info. // within the debug info.
let mut fixer = Fixer::new(JsonEscaping::Yes); let mut fixer = Fixer::new(JsonMode::Yes);
// Test various addresses using `example-windows`, which redirects to // Test various addresses using `example-windows`, which redirects to
// `example-windows.pdb`. // `example-windows.pdb`.
@ -142,8 +142,8 @@ fn test_windows() {
func("g", 0x6c49, 12); func("g", 0x6c49, 12);
func("g", 0x6c63, 14); func("g", 0x6c63, 14);
// Try a new Fixer, without JSON escaping. // Try a new Fixer, without JSON mode.
fixer = Fixer::new(JsonEscaping::No); fixer = Fixer::new(JsonMode::No);
// Test various addresses outside `main`, `f`, and `g`, using // Test various addresses outside `main`, `f`, and `g`, using
// `example-windows.pdb` directly. // `example-windows.pdb` directly.
@ -229,7 +229,7 @@ fn test_mac() {
// LINE 0xf38 line=10 file=/Users/njn/moz/fix-stacks/tests/mac-lib2.c // LINE 0xf38 line=10 file=/Users/njn/moz/fix-stacks/tests/mac-lib2.c
// LINE 0xf49 line=11 file=/Users/njn/moz/fix-stacks/tests/mac-lib2.c // LINE 0xf49 line=11 file=/Users/njn/moz/fix-stacks/tests/mac-lib2.c
let mut fixer = Fixer::new(JsonEscaping::No); let mut fixer = Fixer::new(JsonMode::No);
// Test addresses from all the object files that `mac-multi` references. // Test addresses from all the object files that `mac-multi` references.
let mut func = |name, addr, full_path, locn| { let mut func = |name, addr, full_path, locn| {
@ -266,7 +266,7 @@ fn test_mac() {
#[test] #[test]
fn test_regex() { fn test_regex() {
let mut fixer = Fixer::new(JsonEscaping::No); let mut fixer = Fixer::new(JsonMode::No);
// Test various different unchanged line forms, that don't match the regex. // Test various different unchanged line forms, that don't match the regex.
let mut unchanged = |line: &str| { let mut unchanged = |line: &str| {
@ -301,7 +301,7 @@ fn test_regex() {
#[test] #[test]
fn test_files() { fn test_files() {
let mut fixer = Fixer::new(JsonEscaping::Yes); let mut fixer = Fixer::new(JsonMode::Yes);
// Test various different file errors. An error message is also printed to // Test various different file errors. An error message is also printed to
// stderr for each one, but we don't test for that. // stderr for each one, but we don't test for that.