This repository has been archived by the owner on Nov 1, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 199
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Record coverage using
debuggable-module
(#2701)
- Loading branch information
Showing
20 changed files
with
1,410 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
[workspace] | ||
members = [ | ||
"atexit", | ||
"coverage", | ||
"coverage-legacy", | ||
"debuggable-module", | ||
"debugger", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
[package] | ||
name = "coverage" | ||
version = "0.1.0" | ||
edition = "2021" | ||
license = "MIT" | ||
|
||
[dependencies] | ||
anyhow = "1.0" | ||
debuggable-module = { path = "../debuggable-module" } | ||
iced-x86 = "1.17" | ||
log = "0.4.17" | ||
regex = "1.0" | ||
symbolic = { version = "10.1", features = ["debuginfo", "demangle", "symcache"] } | ||
thiserror = "1.0" | ||
|
||
[target.'cfg(target_os = "windows")'.dependencies] | ||
debugger = { path = "../debugger" } | ||
|
||
[target.'cfg(target_os = "linux")'.dependencies] | ||
pete = "0.9" | ||
# For procfs, opt out of the `chrono` freature; it pulls in an old version | ||
# of `time`. We do not use the methods that the `chrono` feature enables. | ||
procfs = { version = "0.12", default-features = false, features=["flate2"] } | ||
|
||
[dev-dependencies] | ||
clap = { version = "4.0", features = ["derive"] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
use std::process::Command; | ||
use std::time::Duration; | ||
|
||
use anyhow::Result; | ||
use clap::Parser; | ||
use coverage::allowlist::{AllowList, TargetAllowList}; | ||
use coverage::binary::BinaryCoverage; | ||
|
||
#[derive(Parser, Debug)] | ||
struct Args { | ||
#[arg(long)] | ||
module_allowlist: Option<String>, | ||
|
||
#[arg(long)] | ||
source_allowlist: Option<String>, | ||
|
||
#[arg(short, long)] | ||
timeout: Option<u64>, | ||
|
||
command: Vec<String>, | ||
} | ||
|
||
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5); | ||
|
||
fn main() -> Result<()> { | ||
let args = Args::parse(); | ||
|
||
let timeout = args | ||
.timeout | ||
.map(Duration::from_millis) | ||
.unwrap_or(DEFAULT_TIMEOUT); | ||
|
||
let mut cmd = Command::new(&args.command[0]); | ||
if args.command.len() > 1 { | ||
cmd.args(&args.command[1..]); | ||
} | ||
|
||
let mut allowlist = TargetAllowList::default(); | ||
|
||
if let Some(path) = &args.module_allowlist { | ||
allowlist.modules = AllowList::load(path)?; | ||
} | ||
|
||
if let Some(path) = &args.source_allowlist { | ||
allowlist.source_files = AllowList::load(path)?; | ||
} | ||
|
||
let coverage = coverage::record::record(cmd, timeout, allowlist)?; | ||
|
||
dump_modoff(coverage)?; | ||
|
||
Ok(()) | ||
} | ||
|
||
fn dump_modoff(coverage: BinaryCoverage) -> Result<()> { | ||
for (module, coverage) in &coverage.modules { | ||
for (offset, count) in coverage.as_ref() { | ||
if count.reached() { | ||
println!("{}+{offset:x}", module.base_name()); | ||
} | ||
} | ||
} | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
use anyhow::Result; | ||
use regex::{Regex, RegexSet}; | ||
use std::path::Path; | ||
|
||
#[derive(Clone, Debug, Default)] | ||
pub struct TargetAllowList { | ||
pub functions: AllowList, | ||
pub modules: AllowList, | ||
pub source_files: AllowList, | ||
} | ||
|
||
impl TargetAllowList { | ||
pub fn new(modules: AllowList, source_files: AllowList) -> Self { | ||
// Allow all. | ||
let functions = AllowList::default(); | ||
|
||
Self { | ||
functions, | ||
modules, | ||
source_files, | ||
} | ||
} | ||
} | ||
|
||
#[derive(Clone, Debug)] | ||
pub struct AllowList { | ||
allow: RegexSet, | ||
deny: RegexSet, | ||
} | ||
|
||
impl AllowList { | ||
pub fn new(allow: RegexSet, deny: RegexSet) -> Self { | ||
Self { allow, deny } | ||
} | ||
|
||
pub fn load(path: impl AsRef<Path>) -> Result<Self> { | ||
let path = path.as_ref(); | ||
let text = std::fs::read_to_string(path)?; | ||
Self::parse(&text) | ||
} | ||
|
||
pub fn parse(text: &str) -> Result<Self> { | ||
use std::io::{BufRead, BufReader}; | ||
|
||
let reader = BufReader::new(text.as_bytes()); | ||
|
||
let mut allow = vec![]; | ||
let mut deny = vec![]; | ||
|
||
// We could just collect and pass to the `RegexSet` ctor. | ||
// | ||
// Instead, check each rule individually for diagnostic purposes. | ||
for (index, line) in reader.lines().enumerate() { | ||
let line = line?; | ||
|
||
match AllowListLine::parse(&line) { | ||
Ok(valid) => { | ||
use AllowListLine::*; | ||
|
||
match valid { | ||
Blank | Comment => { | ||
// Ignore. | ||
} | ||
Allow(re) => { | ||
allow.push(re); | ||
} | ||
Deny(re) => { | ||
deny.push(re); | ||
} | ||
} | ||
} | ||
Err(err) => { | ||
// Ignore invalid lines, but warn. | ||
let line_number = index + 1; | ||
warn!("error at line {}: {}", line_number, err); | ||
} | ||
} | ||
} | ||
|
||
let allow = RegexSet::new(allow.iter().map(|re| re.as_str()))?; | ||
let deny = RegexSet::new(deny.iter().map(|re| re.as_str()))?; | ||
let allowlist = AllowList::new(allow, deny); | ||
|
||
Ok(allowlist) | ||
} | ||
|
||
pub fn is_allowed(&self, path: impl AsRef<str>) -> bool { | ||
let path = path.as_ref(); | ||
|
||
// Allowed if rule-allowed but not excluded by a negative (deny) rule. | ||
self.allow.is_match(path) && !self.deny.is_match(path) | ||
} | ||
} | ||
|
||
impl Default for AllowList { | ||
fn default() -> Self { | ||
// Unwrap-safe due to valid constant expr. | ||
let allow = RegexSet::new([".*"]).unwrap(); | ||
let deny = RegexSet::empty(); | ||
|
||
AllowList::new(allow, deny) | ||
} | ||
} | ||
|
||
pub enum AllowListLine { | ||
Blank, | ||
Comment, | ||
Allow(Regex), | ||
Deny(Regex), | ||
} | ||
|
||
impl AllowListLine { | ||
pub fn parse(line: &str) -> Result<Self> { | ||
let line = line.trim(); | ||
|
||
// Allow and ignore blank lines. | ||
if line.is_empty() { | ||
return Ok(Self::Blank); | ||
} | ||
|
||
// Support comments of the form `# <comment>`. | ||
if line.starts_with("# ") { | ||
return Ok(Self::Comment); | ||
} | ||
|
||
// Deny rules are of the form `! <rule>`. | ||
if let Some(expr) = line.strip_prefix("! ") { | ||
let re = glob_to_regex(expr)?; | ||
return Ok(Self::Deny(re)); | ||
} | ||
|
||
// Try to interpret as allow rule. | ||
let re = glob_to_regex(line)?; | ||
Ok(Self::Allow(re)) | ||
} | ||
} | ||
|
||
#[allow(clippy::single_char_pattern)] | ||
fn glob_to_regex(expr: &str) -> Result<Regex> { | ||
// Don't make users escape Windows path separators. | ||
let expr = expr.replace(r"\", r"\\"); | ||
|
||
// Translate glob wildcards into quantified regexes. | ||
let expr = expr.replace("*", ".*"); | ||
|
||
// Anchor to line start and end. | ||
let expr = format!("^{expr}$"); | ||
|
||
Ok(Regex::new(&expr)?) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests; |
3 changes: 3 additions & 0 deletions
3
src/agent/coverage/src/allowlist/test-data/allow-all-glob-except-commented.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
a/* | ||
! a/c | ||
# c |
3 changes: 3 additions & 0 deletions
3
src/agent/coverage/src/allowlist/test-data/allow-all-glob-except.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
a/* | ||
! a/c | ||
c |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
a | ||
a/b | ||
b | ||
c |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
a | ||
b |
Empty file.
Oops, something went wrong.