From 56b2ff061945998ea0ede555bc3b7edd85bcd697 Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Sat, 25 Feb 2023 16:58:20 +0100 Subject: [PATCH] Add the possibility to dump a gcno and an optional gcda to json A gcno file contains some interesting information about the structure of the functions making the source file. So this patch aims to provide a json dump of a gcno in order to make it consumable by other tools. --- src/main.rs | 20 +++++- src/reader.rs | 115 ++++++++++++++++++++++++++++++-- test/llvm/reader.gcno.gcda.json | 1 + test/llvm/reader.gcno.json | 1 + 4 files changed, 131 insertions(+), 6 deletions(-) create mode 100644 test/llvm/reader.gcno.gcda.json create mode 100644 test/llvm/reader.gcno.json diff --git a/src/main.rs b/src/main.rs index 3562df7a8..26686fd34 100644 --- a/src/main.rs +++ b/src/main.rs @@ -96,7 +96,7 @@ impl FromStr for Filter { )] struct Opt { /// Sets the input paths to use. - #[structopt(required = true)] + #[structopt()] paths: Vec, /// Sets the path to the compiled binary to be used. #[structopt(short, long, value_name = "PATH")] @@ -259,6 +259,12 @@ struct Opt { /// No symbol demangling. #[structopt(long)] no_demangle: bool, + #[structopt(long, value_name = "PATH")] + gcno: Option, + #[structopt(long, value_name = "PATH")] + gcda: Option, + #[structopt(long, value_name = "PATH")] + json_output: Option, } fn main() { @@ -318,6 +324,18 @@ fn main() { ); } + if let Some(gcno) = opt.gcno { + if let Err(err) = Gcno::to_json(gcno, opt.gcda, opt.json_output) { + error!("Cannot export gcno/gcda to json: {}", err); + } + return; + } + + if opt.paths.is_empty() { + error!("paths is a required option."); + return; + } + let file_filter = FileFilter::new( opt.excl_line, opt.excl_start, diff --git a/src/reader.rs b/src/reader.rs index f17fcd593..6e22fabe5 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -1,5 +1,8 @@ use rustc_hash::{FxHashMap, FxHashSet}; -use smallvec::SmallVec; +use serde::ser::{SerializeSeq, Serializer}; +use serde::{self, Serialize}; +use serde_json::{self}; +use smallvec::{Array, SmallVec}; use std::cmp; use std::collections::{btree_map, hash_map, BTreeMap}; use std::convert::From; @@ -24,6 +27,19 @@ const GCOV_TAG_COUNTER_ARCS: u32 = 0x01a1_0000; const GCOV_TAG_OBJECT_SUMMARY: u32 = 0xa100_0000; const GCOV_TAG_PROGRAM_SUMMARY: u32 = 0xa300_0000; +fn serialize_smallvec(vec: &SmallVec, s: S) -> Result +where + A: Array, + A::Item: Serialize, + S: Serializer, +{ + let mut seq = s.serialize_seq(Some(A::size()))?; + for element in vec { + seq.serialize_element(element)?; + } + seq.end() +} + #[derive(Debug)] pub enum GcovError { Io(std::io::Error), @@ -74,7 +90,7 @@ enum FileType { Gcda, } -#[derive(Default)] +#[derive(Default, Serialize)] pub struct Gcno { version: u32, checksum: u32, @@ -83,10 +99,11 @@ pub struct Gcno { programcounts: u32, runcounts: u32, functions: Vec, + #[serde(skip_serializing)] ident_to_fun: FxHashMap, } -#[derive(Debug)] +#[derive(Debug, Serialize)] struct GcovFunction { identifier: u32, start_line: u32, @@ -101,29 +118,38 @@ struct GcovFunction { cfg_checksum: u32, file_name: String, name: String, + #[serde(serialize_with = "serialize_smallvec")] blocks: SmallVec<[GcovBlock; 16]>, + #[serde(serialize_with = "serialize_smallvec")] edges: SmallVec<[GcovEdge; 16]>, real_edge_count: usize, lines: FxHashMap, executed: bool, } -#[derive(Debug)] +#[derive(Debug, Serialize)] struct GcovBlock { no: usize, + #[serde(serialize_with = "serialize_smallvec")] source: SmallVec<[usize; 2]>, + #[serde(serialize_with = "serialize_smallvec")] destination: SmallVec<[usize; 2]>, + #[serde(serialize_with = "serialize_smallvec")] lines: SmallVec<[u32; 16]>, + #[serde(skip_serializing)] line_max: u32, + #[serde(skip_serializing)] counter: u64, } -#[derive(Debug)] +#[derive(Debug, Serialize)] struct GcovEdge { source: usize, destination: usize, flags: u32, + #[serde(skip_serializing)] counter: u64, + #[serde(skip_serializing)] cycles: u64, } @@ -379,6 +405,57 @@ impl Gcno { } } + pub fn to_json( + gcno_path: PathBuf, + gcda_path: Option, + output: Option, + ) -> Result<(), GcovError> { + let output_path = output.unwrap_or("-".to_owned()); + let writer: Box = if output_path.eq("-") { + Box::new(std::io::stdout()) + } else { + let output = std::fs::File::create(&output_path) + .unwrap_or_else(|_| panic!("Cannot open file {} for writing", &output_path)); + Box::new(output) + }; + let writer = std::io::BufWriter::new(writer); + Self::write_to_json(gcno_path, gcda_path, writer) + } + + pub fn write_to_json( + gcno_path: PathBuf, + gcda_path: Option, + mut writer: W, + ) -> Result<(), GcovError> { + let mut gcno = Self::new(); + let file = File::open(&gcno_path)?; + let stem = gcno_path.file_stem().unwrap().to_str().unwrap(); + let mut reader = BufReader::new(file); + let mut buf = vec![]; + reader.read_to_end(&mut buf)?; + gcno.read(FileType::Gcno, buf, stem)?; + + if let Some(gcda_path) = gcda_path { + let file = File::open(&gcda_path)?; + let stem = gcda_path.file_stem().unwrap().to_str().unwrap(); + let mut reader = BufReader::new(file); + let mut buf = vec![]; + reader.read_to_end(&mut buf)?; + gcno.read(FileType::Gcda, buf, stem)?; + gcno.stop(); + gcno.finalize(true); + } else { + gcno.stop(); + } + + let json = serde_json::to_string(&gcno).unwrap(); + if writeln!(writer, "{json}").is_err() { + Err(GcovError::Str("Cannot write json".to_string())) + } else { + Ok(()) + } + } + fn guess_endianness(mut typ: [u8; 4], buffer: &[u8], stem: &str) -> Result { if 4 <= buffer.len() { let bytes = &buffer[..4]; @@ -1218,6 +1295,7 @@ mod tests { use super::*; use crate::defs::FunctionMap; + use std::io::BufWriter; fn from_path(gcno: &mut Gcno, typ: FileType, path: &str) { let path = PathBuf::from(path); @@ -1253,6 +1331,17 @@ mod tests { assert_eq!(output, input); } + #[test] + fn test_dump_gcno_to_json() { + let mut bytes = Vec::new(); + let buf = BufWriter::new(&mut bytes); + Gcno::write_to_json(PathBuf::from("test/llvm/reader.gcno"), None, buf).unwrap(); + let json = String::from_utf8(bytes).unwrap(); + let input = get_input_string("test/llvm/reader.gcno.json"); + + assert_eq!(json, input); + } + #[test] fn test_reader_gcno_gcda() { let mut gcno = Gcno::new(); @@ -1265,6 +1354,22 @@ mod tests { assert_eq!(output, input); } + #[test] + fn test_dump_gcno_gcda_to_json() { + let mut bytes = Vec::new(); + let buf = BufWriter::new(&mut bytes); + Gcno::write_to_json( + PathBuf::from("test/llvm/reader.gcno"), + Some(PathBuf::from("test/llvm/reader.gcda")), + buf, + ) + .unwrap(); + let json = String::from_utf8(bytes).unwrap(); + let input = get_input_string("test/llvm/reader.gcno.gcda.json"); + + assert_eq!(json, input); + } + #[test] fn test_reader_gcno_gcda_gcc6() { let mut gcno = Gcno::new(); diff --git a/test/llvm/reader.gcno.gcda.json b/test/llvm/reader.gcno.gcda.json new file mode 100644 index 000000000..b0e35b47e --- /dev/null +++ b/test/llvm/reader.gcno.gcda.json @@ -0,0 +1 @@ +{"version":42,"checksum":3712873117,"cwd":null,"programcounts":1,"runcounts":1,"functions":[{"identifier":0,"start_line":1,"start_column":0,"end_line":0,"end_column":0,"artificial":0,"line_checksum":160103273,"cfg_checksum":0,"file_name":"reader.c","name":"foo","blocks":[{"no":0,"source":[16],"destination":[0],"lines":[]},{"no":1,"source":[0],"destination":[1],"lines":[4]},{"no":2,"source":[1,14],"destination":[2,3],"lines":[4]},{"no":3,"source":[2],"destination":[4,5],"lines":[5]},{"no":4,"source":[4],"destination":[6],"lines":[7,8]},{"no":5,"source":[5],"destination":[7],"lines":[12]},{"no":6,"source":[7,11],"destination":[8,9],"lines":[12]},{"no":7,"source":[8],"destination":[10],"lines":[13,14]},{"no":8,"source":[10],"destination":[11],"lines":[12]},{"no":9,"source":[9],"destination":[12],"lines":[]},{"no":10,"source":[6,12],"destination":[13],"lines":[16]},{"no":11,"source":[13],"destination":[14],"lines":[4]},{"no":12,"source":[3],"destination":[15],"lines":[17]},{"no":13,"source":[15],"destination":[16],"lines":[]}],"edges":[{"source":0,"destination":1,"flags":0},{"source":1,"destination":2,"flags":0},{"source":2,"destination":3,"flags":0},{"source":2,"destination":12,"flags":0},{"source":3,"destination":4,"flags":0},{"source":3,"destination":5,"flags":0},{"source":4,"destination":10,"flags":0},{"source":5,"destination":6,"flags":0},{"source":6,"destination":7,"flags":0},{"source":6,"destination":9,"flags":0},{"source":7,"destination":8,"flags":0},{"source":8,"destination":6,"flags":0},{"source":9,"destination":10,"flags":0},{"source":10,"destination":11,"flags":0},{"source":11,"destination":2,"flags":0},{"source":12,"destination":13,"flags":0},{"source":13,"destination":0,"flags":1}],"real_edge_count":16,"lines":{"16":540,"13":3,"7":537,"4":594,"17":54,"14":3,"8":537,"5":540,"12":6},"executed":true},{"identifier":1,"start_line":20,"start_column":0,"end_line":0,"end_column":0,"artificial":0,"line_checksum":957985011,"cfg_checksum":0,"file_name":"reader.c","name":"main","blocks":[{"no":0,"source":[7],"destination":[0],"lines":[]},{"no":1,"source":[0],"destination":[1],"lines":[22,23,25]},{"no":2,"source":[1,5],"destination":[2,3],"lines":[25]},{"no":3,"source":[2],"destination":[4],"lines":[27,28]},{"no":4,"source":[4],"destination":[5],"lines":[25]},{"no":5,"source":[3],"destination":[6],"lines":[29]},{"no":6,"source":[6],"destination":[7],"lines":[]}],"edges":[{"source":0,"destination":1,"flags":0},{"source":1,"destination":2,"flags":0},{"source":2,"destination":3,"flags":0},{"source":2,"destination":5,"flags":0},{"source":3,"destination":4,"flags":0},{"source":4,"destination":2,"flags":0},{"source":5,"destination":6,"flags":0},{"source":6,"destination":0,"flags":1}],"real_edge_count":7,"lines":{"29":1,"23":1,"28":53,"25":54,"22":1,"27":53},"executed":true}]} diff --git a/test/llvm/reader.gcno.json b/test/llvm/reader.gcno.json new file mode 100644 index 000000000..399c50c17 --- /dev/null +++ b/test/llvm/reader.gcno.json @@ -0,0 +1 @@ +{"version":42,"checksum":3712873117,"cwd":null,"programcounts":0,"runcounts":0,"functions":[{"identifier":0,"start_line":1,"start_column":0,"end_line":0,"end_column":0,"artificial":0,"line_checksum":160103273,"cfg_checksum":0,"file_name":"reader.c","name":"foo","blocks":[{"no":0,"source":[16],"destination":[0],"lines":[]},{"no":1,"source":[0],"destination":[1],"lines":[4]},{"no":2,"source":[1,14],"destination":[2,3],"lines":[4]},{"no":3,"source":[2],"destination":[4,5],"lines":[5]},{"no":4,"source":[4],"destination":[6],"lines":[7,8]},{"no":5,"source":[5],"destination":[7],"lines":[12]},{"no":6,"source":[7,11],"destination":[8,9],"lines":[12]},{"no":7,"source":[8],"destination":[10],"lines":[13,14]},{"no":8,"source":[10],"destination":[11],"lines":[12]},{"no":9,"source":[9],"destination":[12],"lines":[]},{"no":10,"source":[6,12],"destination":[13],"lines":[16]},{"no":11,"source":[13],"destination":[14],"lines":[4]},{"no":12,"source":[3],"destination":[15],"lines":[17]},{"no":13,"source":[15],"destination":[16],"lines":[]}],"edges":[{"source":0,"destination":1,"flags":0},{"source":1,"destination":2,"flags":0},{"source":2,"destination":3,"flags":0},{"source":2,"destination":12,"flags":0},{"source":3,"destination":4,"flags":0},{"source":3,"destination":5,"flags":0},{"source":4,"destination":10,"flags":0},{"source":5,"destination":6,"flags":0},{"source":6,"destination":7,"flags":0},{"source":6,"destination":9,"flags":0},{"source":7,"destination":8,"flags":0},{"source":8,"destination":6,"flags":0},{"source":9,"destination":10,"flags":0},{"source":10,"destination":11,"flags":0},{"source":11,"destination":2,"flags":0},{"source":12,"destination":13,"flags":0},{"source":13,"destination":0,"flags":1}],"real_edge_count":16,"lines":{},"executed":false},{"identifier":1,"start_line":20,"start_column":0,"end_line":0,"end_column":0,"artificial":0,"line_checksum":957985011,"cfg_checksum":0,"file_name":"reader.c","name":"main","blocks":[{"no":0,"source":[7],"destination":[0],"lines":[]},{"no":1,"source":[0],"destination":[1],"lines":[22,23,25]},{"no":2,"source":[1,5],"destination":[2,3],"lines":[25]},{"no":3,"source":[2],"destination":[4],"lines":[27,28]},{"no":4,"source":[4],"destination":[5],"lines":[25]},{"no":5,"source":[3],"destination":[6],"lines":[29]},{"no":6,"source":[6],"destination":[7],"lines":[]}],"edges":[{"source":0,"destination":1,"flags":0},{"source":1,"destination":2,"flags":0},{"source":2,"destination":3,"flags":0},{"source":2,"destination":5,"flags":0},{"source":3,"destination":4,"flags":0},{"source":4,"destination":2,"flags":0},{"source":5,"destination":6,"flags":0},{"source":6,"destination":0,"flags":1}],"real_edge_count":7,"lines":{},"executed":false}]}