Skip to content

Commit 50a66f0

Browse files
committed
feat(call_graph): Add option for an explicit call graph trace length limit
1 parent bc9f7d2 commit 50a66f0

10 files changed

Lines changed: 115 additions & 12 deletions

mutest-driver-cli/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ pub fn command() -> clap::Command {
133133
.group(clap::ArgGroup::new("unsafe-targeting").args(&["safe", "cautious", "risky", "unsafe"]).multiple(false))
134134
.arg(clap::arg!(--"mutation-operators" [MUTATION_OPERATORS] "Mutation operators to apply to the code, separated by commas.").value_delimiter(',').value_parser(mutation_operators::possible_values()).default_value("all").display_order(115))
135135
.arg(clap::arg!(--"call-graph-depth-limit" [CALL_GRAPH_DEPTH_LIMIT] "Limit depth of call graph analysis, which is complete by default.").value_parser(clap::value_parser!(usize)).display_order(150))
136+
.arg(clap::arg!(--"call-graph-trace-length-limit" [CALL_GRAPH_TRACE_LENGTH_LIMIT] "Limit maximum length of analyzed call traces during call graph analysis, which is complete by default.").value_parser(clap::value_parser!(usize)).display_order(150))
136137
.arg(clap::arg!(-d --depth [DEPTH] "Callees of each test function are mutated up to the specified depth.").default_value("3").value_parser(clap::value_parser!(usize)).display_order(150))
137138
.arg(clap::arg!(--"mutant-batch-algorithm" [MUTANT_BATCH_ALGORITHM] "Algorithm to use to batch mutations into mutants.").value_parser(mutant_batch_algorithm::possible_values()).default_value(mutant_batch_algorithm::NONE).display_order(199))
138139
.arg(clap::arg!(--"mutant-batch-size" [MUTANT_BATCH_SIZE] "Maximum number of mutations to batch into a single mutant.").default_value("1").value_parser(clap::value_parser!(usize)).display_order(199))

mutest-driver/src/config.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ pub struct Options<'op, 'm> {
9696
pub unsafe_targeting: UnsafeTargeting,
9797
pub operators: Operators<'op, 'm>,
9898
pub call_graph_depth_limit: Option<usize>,
99+
pub call_graph_trace_length_limit: Option<usize>,
99100
pub mutation_depth: usize,
100101
pub mutation_batching_algorithm: MutationBatchingAlgorithm,
101102
pub mutation_batching_randomness: MutationBatchingRandomness,

mutest-driver/src/main.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ pub fn main() {
237237
};
238238

239239
let mut call_graph_depth_limit = mutest_arg_matches.get_one::<usize>("call-graph-depth-limit").copied();
240+
let mut call_graph_trace_length_limit = mutest_arg_matches.get_one::<usize>("call-graph-trace-length-limit").copied();
240241
let mutation_depth = *mutest_arg_matches.get_one::<usize>("depth").unwrap();
241242

242243
if let Some(call_graph_depth_limit_value) = call_graph_depth_limit && call_graph_depth_limit_value < mutation_depth {
@@ -248,6 +249,15 @@ pub fn main() {
248249
call_graph_depth_limit = None;
249250
}
250251

252+
if let Some(call_graph_trace_length_limit_value) = call_graph_trace_length_limit && call_graph_trace_length_limit_value < mutation_depth {
253+
let mut diagnostic = early_dcx.early_struct_warn("explicit call graph trace length limit argument ignored as mutation depth exceeds it");
254+
diagnostic.note(format!("mutation depth is set to {mutation_depth}"));
255+
diagnostic.note(format!("call graph trace length limit was explicitly set to {call_graph_trace_length_limit_value}, but will be ignored"));
256+
diagnostic.emit();
257+
258+
call_graph_trace_length_limit = None;
259+
}
260+
251261
let mutation_batching_algorithm = {
252262
use mutest_driver_cli::mutant_batch_algorithm as opts;
253263

@@ -333,6 +343,7 @@ pub fn main() {
333343
unsafe_targeting,
334344
operators: &mutation_operators,
335345
call_graph_depth_limit,
346+
call_graph_trace_length_limit,
336347
mutation_depth,
337348
mutation_batching_algorithm,
338349
mutation_batching_randomness,

mutest-driver/src/passes/analysis.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -122,19 +122,19 @@ pub fn run(config: &mut Config) -> CompilerResult<Option<AnalysisPassResult>> {
122122

123123
let all_mutable_fns_count = mutest_emit::analysis::call_graph::all_mutable_fns(tcx, &tests).count();
124124

125-
let call_graph_depth_limit = match opts.call_graph_depth_limit {
126-
Some(call_graph_depth_limit) => {
127-
if call_graph_depth_limit < opts.mutation_depth {
128-
tcx.dcx().fatal("mutation depth exceeds explicit call graph depth limit argument");
129-
}
130-
Some(call_graph_depth_limit)
131-
}
132-
None => None,
133-
};
125+
let call_graph_depth_limit = opts.call_graph_depth_limit;
126+
if let Some(v) = call_graph_depth_limit && v < opts.mutation_depth {
127+
tcx.dcx().fatal("mutation depth exceeds explicit call graph depth limit argument");
128+
}
129+
130+
let call_graph_trace_length_limit = opts.call_graph_trace_length_limit;
131+
if let Some(v) = call_graph_trace_length_limit && v < opts.mutation_depth {
132+
tcx.dcx().fatal("mutation depth exceeds explicit call graph trace length limit argument");
133+
}
134134

135135
let t_target_analysis_start = Instant::now();
136136

137-
let (call_graph, mut reachable_fns) = mutest_emit::analysis::call_graph::reachable_fns(tcx, &def_res, &generated_crate_ast, &tests, call_graph_depth_limit);
137+
let (call_graph, mut reachable_fns) = mutest_emit::analysis::call_graph::reachable_fns(tcx, &def_res, &generated_crate_ast, &tests, call_graph_depth_limit, call_graph_trace_length_limit);
138138
let mut json_definitions = Default::default();
139139
if let Some(write_opts) = &opts.write_opts {
140140
let t_write_start = Instant::now();

mutest-emit/src/analysis/call_graph.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@ pub fn reachable_fns<'ast, 'tcx, 'tst>(
347347
krate: &'ast ast::Crate,
348348
tests: &'tst [Test],
349349
depth_limit: Option<usize>,
350+
trace_length_limit: Option<usize>,
350351
) -> (CallGraph<'tcx>, Vec<Target<'tst>>) {
351352
let mut call_graph = CallGraph {
352353
virtual_calls_count: 0,
@@ -613,6 +614,7 @@ pub fn reachable_fns<'ast, 'tcx, 'tst>(
613614
unsafety: Option<UnsafeSource>,
614615
call_trace: &mut CallTrace<'tcx>,
615616
targets: &mut FxHashMap<hir::LocalDefId, Target<'tst>>,
617+
trace_length_limit: Option<usize>,
616618
) {
617619
let &[.., caller] = &call_trace.nested_calls[..] else { return; };
618620

@@ -670,6 +672,11 @@ pub fn reachable_fns<'ast, 'tcx, 'tst>(
670672
entry_point.unsafe_call_path = Ord::max(unsafety, entry_point.unsafe_call_path);
671673
}
672674

675+
if let Some(trace_length_limit) = trace_length_limit && call_trace.nested_calls.len() >= trace_length_limit {
676+
tcx.dcx().warn("exceeded explicit call graph trace length limit");
677+
return;
678+
}
679+
673680
for &call in callee_lookup_cache.callees_of_nested_caller(caller) {
674681
// We have encontered a recursion point along this call trace; end the search along this trace.
675682
if call_trace.nested_calls.iter().any(|callee_in_trace| call.callee == *callee_in_trace) { continue; }
@@ -681,7 +688,7 @@ pub fn reachable_fns<'ast, 'tcx, 'tst>(
681688
hir::Safety::Unsafe => Some(UnsafeSource::Unsafe),
682689
};
683690

684-
record_targets(tcx, def_res, krate, test_def_ids, callee_lookup_cache, test, unsafety, call_trace, targets);
691+
record_targets(tcx, def_res, krate, test_def_ids, callee_lookup_cache, test, unsafety, call_trace, targets, trace_length_limit);
685692

686693
call_trace.nested_calls.pop();
687694
}
@@ -694,7 +701,7 @@ pub fn reachable_fns<'ast, 'tcx, 'tst>(
694701
let Some(test) = tests.iter().find(|test| test.def_id == entry_point) else { unreachable!() };
695702
let unsafety = None;
696703
let mut call_trace = CallTrace { root: entry_point, nested_calls: smallvec![call.callee] };
697-
record_targets(tcx, def_res, krate, &test_def_ids, &callee_lookup_cache, test, unsafety, &mut call_trace, &mut targets);
704+
record_targets(tcx, def_res, krate, &test_def_ids, &callee_lookup_cache, test, unsafety, &mut call_trace, &mut targets, trace_length_limit);
698705
}
699706
}
700707

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
//@ build
2+
//@ stderr
3+
//@ mutest-flags: --call-graph-trace-length-limit=3 --depth=5
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
warning: explicit call graph trace length limit argument ignored as mutation depth exceeds it
2+
|
3+
= note: mutation depth is set to 5
4+
= note: call graph trace length limit was explicitly set to 3, but will be ignored
5+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//@ print-call-graph
2+
//@ print-targets
3+
//@ stdout
4+
//@ stderr
5+
//@ mutest-flags: -v --call-graph-trace-length-limit=3
6+
7+
fn depth_5() {}
8+
9+
fn depth_4() {
10+
depth_5();
11+
}
12+
13+
fn depth_3() {
14+
depth_4();
15+
}
16+
17+
fn depth_2() {
18+
depth_3();
19+
}
20+
21+
fn depth_1() {
22+
depth_2();
23+
}
24+
25+
#[test]
26+
fn test() {
27+
depth_1();
28+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
warning: exceeded explicit call graph trace length limit
2+
3+
warning: 1 warning emitted
4+
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
built call graph with depth of 5
2+
reached 60.00% of functions from tests (3 out of 5 functions)
3+
4+
@@@ call graph @@@
5+
6+
entry points:
7+
8+
test test
9+
-> depth_1 at tests/ui/call_graph/warn_on_incomplete_call_graph_due_to_explicit_trace_length_limit.rs:21:1: 21:13 (#0)
10+
11+
nested calls at distance 1:
12+
13+
depth_1 at tests/ui/call_graph/warn_on_incomplete_call_graph_due_to_explicit_trace_length_limit.rs:21:1: 21:13 (#0)
14+
-> depth_2 at tests/ui/call_graph/warn_on_incomplete_call_graph_due_to_explicit_trace_length_limit.rs:17:1: 17:13 (#0)
15+
16+
nested calls at distance 2:
17+
18+
depth_2 at tests/ui/call_graph/warn_on_incomplete_call_graph_due_to_explicit_trace_length_limit.rs:17:1: 17:13 (#0)
19+
-> depth_3 at tests/ui/call_graph/warn_on_incomplete_call_graph_due_to_explicit_trace_length_limit.rs:13:1: 13:13 (#0)
20+
21+
nested calls at distance 3:
22+
23+
depth_3 at tests/ui/call_graph/warn_on_incomplete_call_graph_due_to_explicit_trace_length_limit.rs:13:1: 13:13 (#0)
24+
-> depth_4 at tests/ui/call_graph/warn_on_incomplete_call_graph_due_to_explicit_trace_length_limit.rs:9:1: 9:13 (#0)
25+
26+
nested calls at distance 4:
27+
28+
depth_4 at tests/ui/call_graph/warn_on_incomplete_call_graph_due_to_explicit_trace_length_limit.rs:9:1: 9:13 (#0)
29+
-> depth_5 at tests/ui/call_graph/warn_on_incomplete_call_graph_due_to_explicit_trace_length_limit.rs:7:1: 7:13 (#0)
30+
31+
32+
@@@ targets @@@
33+
34+
tests -(2)-> depth_3 at tests/ui/call_graph/warn_on_incomplete_call_graph_due_to_explicit_trace_length_limit.rs:13:1: 13:13 (#0)
35+
(2) test
36+
37+
tests -(1)-> depth_2 at tests/ui/call_graph/warn_on_incomplete_call_graph_due_to_explicit_trace_length_limit.rs:17:1: 17:13 (#0)
38+
(1) test
39+
40+
tests -(0)-> depth_1 at tests/ui/call_graph/warn_on_incomplete_call_graph_due_to_explicit_trace_length_limit.rs:21:1: 21:13 (#0)
41+
(0) test
42+
43+
targets: 3 total; 3 safe; 0 unsafe (0 tainted)

0 commit comments

Comments
 (0)