Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the possibility to dump a gcno and an optional gcda to json #967

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ impl FromStr for Filter {
)]
struct Opt {
/// Sets the input paths to use.
#[structopt(required = true)]
#[structopt()]
paths: Vec<String>,
/// Sets the path to the compiled binary to be used.
#[structopt(short, long, value_name = "PATH")]
Expand Down Expand Up @@ -259,6 +259,12 @@ struct Opt {
/// No symbol demangling.
#[structopt(long)]
no_demangle: bool,
#[structopt(long, value_name = "PATH")]
gcno: Option<PathBuf>,
#[structopt(long, value_name = "PATH")]
gcda: Option<PathBuf>,
#[structopt(long, value_name = "PATH")]
json_output: Option<String>,
}

fn main() {
Expand Down Expand Up @@ -318,6 +324,18 @@ fn main() {
);
}

if let Some(gcno) = opt.gcno {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make this option conflict with others, given that we return early when it is set?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you want to do that ?

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,
Expand Down
115 changes: 110 additions & 5 deletions src/reader.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<A, S>(vec: &SmallVec<A>, s: S) -> Result<S::Ok, S::Error>
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),
Expand Down Expand Up @@ -74,7 +90,7 @@ enum FileType {
Gcda,
}

#[derive(Default)]
#[derive(Default, Serialize)]
pub struct Gcno {
version: u32,
checksum: u32,
Expand All @@ -83,10 +99,11 @@ pub struct Gcno {
programcounts: u32,
runcounts: u32,
functions: Vec<GcovFunction>,
#[serde(skip_serializing)]
ident_to_fun: FxHashMap<u32, usize>,
}

#[derive(Debug)]
#[derive(Debug, Serialize)]
struct GcovFunction {
identifier: u32,
start_line: u32,
Expand All @@ -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<u32, u64>,
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,
}

Expand Down Expand Up @@ -379,6 +405,57 @@ impl Gcno {
}
}

pub fn to_json(
gcno_path: PathBuf,
gcda_path: Option<PathBuf>,
output: Option<String>,
) -> Result<(), GcovError> {
let output_path = output.unwrap_or("-".to_owned());
let writer: Box<dyn std::io::Write> = 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<W: Write>(
gcno_path: PathBuf,
gcda_path: Option<PathBuf>,
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<bool, GcovError> {
if 4 <= buffer.len() {
let bytes = &buffer[..4];
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Expand All @@ -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();
Expand Down
1 change: 1 addition & 0 deletions test/llvm/reader.gcno.gcda.json
Original file line number Diff line number Diff line change
@@ -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}]}
1 change: 1 addition & 0 deletions test/llvm/reader.gcno.json
Original file line number Diff line number Diff line change
@@ -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}]}