diff --git a/mutest-driver/src/passes/analysis.rs b/mutest-driver/src/passes/analysis.rs index e7c88fa..7935040 100644 --- a/mutest-driver/src/passes/analysis.rs +++ b/mutest-driver/src/passes/analysis.rs @@ -596,10 +596,11 @@ pub fn run(config: &mut Config) -> CompilerResult> { if call_graph.virtual_calls_count >= 1 || call_graph.dynamic_calls_count >= 1 { let total_calls_count = call_graph.total_calls_count(); - println!("could not resolve {unresolved_pct:.2}% of function calls ({virtual} virtual, {dynamic} dynamic out of {total} function calls)", + println!("could not resolve {unresolved_pct:.2}% of function calls ({virtual} virtual, {dynamic} dynamic, {foreign} foreign out of {total} function calls)", unresolved_pct = (call_graph.virtual_calls_count + call_graph.dynamic_calls_count) as f64 / total_calls_count as f64 * 100_f64, virtual = call_graph.virtual_calls_count, dynamic = call_graph.dynamic_calls_count, + foreign = call_graph.foreign_calls_count, total = total_calls_count, ); } diff --git a/mutest-emit/src/analysis/call_graph.rs b/mutest-emit/src/analysis/call_graph.rs index 7a0911a..b395be0 100644 --- a/mutest-emit/src/analysis/call_graph.rs +++ b/mutest-emit/src/analysis/call_graph.rs @@ -2,6 +2,7 @@ use std::iter; use rustc_hash::{FxHashSet, FxHashMap}; use rustc_middle::mir; +use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; use crate::analysis::ast_lowering; use crate::analysis::hir; @@ -216,6 +217,7 @@ impl<'tcx> Callee<'tcx> { pub struct CallGraph<'tcx> { pub virtual_calls_count: usize, pub dynamic_calls_count: usize, + pub foreign_calls_count: usize, pub root_calls: FxHashSet<(hir::LocalDefId, Callee<'tcx>)>, pub nested_calls: Vec, Callee<'tcx>)>>, } @@ -247,6 +249,7 @@ pub fn reachable_fns<'ast, 'tcx, 'tst>( let mut call_graph = CallGraph { virtual_calls_count: 0, dynamic_calls_count: 0, + foreign_calls_count: 0, root_calls: Default::default(), nested_calls: iter::repeat_with(|| Default::default()).take(depth - 1).collect(), }; @@ -304,6 +307,26 @@ pub fn reachable_fns<'ast, 'tcx, 'tst>( diagnostic.emit(); } + if tcx.is_foreign_item(instance.def_id()) && !tcx.intrinsic(instance.def_id()).is_some() { + let codegen_fn_attrs = tcx.codegen_fn_attrs(instance.def_id()); + let is_allocator_intrinsic = codegen_fn_attrs.flags.intersects( + CodegenFnAttrFlags::ALLOCATOR + | CodegenFnAttrFlags::DEALLOCATOR + | CodegenFnAttrFlags::REALLOCATOR + | CodegenFnAttrFlags::ALLOCATOR_ZEROED + ); + + if !is_allocator_intrinsic { + call_graph.foreign_calls_count += 1; + + let mut diagnostic = tcx.dcx().struct_warn("encountered foreign call during call graph construction"); + diagnostic.span(call.span); + diagnostic.span_label(call.span, format!("call to {}", tcx.def_path_str_with_args(instance.def_id(), instance.args))); + diagnostic.note(format!("in {}", tcx.def_path_str(test.def_id))); + diagnostic.emit(); + } + } + Callee::new(instance.def_id(), instance.args) } @@ -408,6 +431,26 @@ pub fn reachable_fns<'ast, 'tcx, 'tst>( diagnostic.emit(); } + if tcx.is_foreign_item(instance.def_id()) && !tcx.intrinsic(instance.def_id()).is_some() { + let codegen_fn_attrs = tcx.codegen_fn_attrs(instance.def_id()); + let is_allocator_intrinsic = codegen_fn_attrs.flags.intersects( + CodegenFnAttrFlags::ALLOCATOR + | CodegenFnAttrFlags::DEALLOCATOR + | CodegenFnAttrFlags::REALLOCATOR + | CodegenFnAttrFlags::ALLOCATOR_ZEROED + ); + + if !is_allocator_intrinsic { + call_graph.foreign_calls_count += 1; + + let mut diagnostic = tcx.dcx().struct_warn("encountered foreign call during call graph construction"); + diagnostic.span(call.span); + diagnostic.span_label(call.span, format!("call to {}", tcx.def_path_str_with_args(instance.def_id(), instance.args))); + diagnostic.note(format!("in {}", tcx.def_path_str_with_args(caller.def_id, caller.generic_args))); + diagnostic.emit(); + } + } + Callee::new(instance.def_id(), instance.args) } diff --git a/tests/ui/call_graph/ignore_allocator_intrinsics_calls.rs b/tests/ui/call_graph/ignore_allocator_intrinsics_calls.rs new file mode 100644 index 0000000..f729e45 --- /dev/null +++ b/tests/ui/call_graph/ignore_allocator_intrinsics_calls.rs @@ -0,0 +1,13 @@ +//@ print-call-graph +//@ stderr: empty + +use std::alloc::Layout; + +fn make_allocator_intrinsics_call() { + let _ = unsafe { std::alloc::alloc(Layout::from_size_align_unchecked(8, 8)) }; +} + +#[test] +fn test() { + make_allocator_intrinsics_call(); +} diff --git a/tests/ui/call_graph/ignore_intrinsics_calls.rs b/tests/ui/call_graph/ignore_intrinsics_calls.rs new file mode 100644 index 0000000..77abb46 --- /dev/null +++ b/tests/ui/call_graph/ignore_intrinsics_calls.rs @@ -0,0 +1,14 @@ +//@ print-call-graph +//@ stderr: empty + +#![allow(internal_features)] +#![feature(core_intrinsics)] + +fn make_intrinsics_call() { + let _ = core::intrinsics::caller_location(); +} + +#[test] +fn test() { + make_intrinsics_call(); +} diff --git a/tests/ui/call_graph/warn_on_foreign_calls.rs b/tests/ui/call_graph/warn_on_foreign_calls.rs new file mode 100644 index 0000000..ca17c27 --- /dev/null +++ b/tests/ui/call_graph/warn_on_foreign_calls.rs @@ -0,0 +1,20 @@ +//@ print-call-graph +//@ print-targets +//@ stdout +//@ stderr + +extern "C" { + fn foreign(); +} + +unsafe extern "C" fn not_foreign() {} + +fn make_extern_calls() { + unsafe { foreign() }; + unsafe { not_foreign() }; +} + +#[test] +fn test() { + make_extern_calls(); +} diff --git a/tests/ui/call_graph/warn_on_foreign_calls.stderr b/tests/ui/call_graph/warn_on_foreign_calls.stderr new file mode 100644 index 0000000..6ce4e8c --- /dev/null +++ b/tests/ui/call_graph/warn_on_foreign_calls.stderr @@ -0,0 +1,10 @@ +warning: encountered foreign call during call graph construction + --> tests/ui/call_graph/warn_on_foreign_calls.rs:13:14 + | +13 | unsafe { foreign() }; + | ^^^^^^^^^ call to foreign + | + = note: in make_extern_calls + +warning: 1 warning emitted + diff --git a/tests/ui/call_graph/warn_on_foreign_calls.stdout b/tests/ui/call_graph/warn_on_foreign_calls.stdout new file mode 100644 index 0000000..40927ad --- /dev/null +++ b/tests/ui/call_graph/warn_on_foreign_calls.stdout @@ -0,0 +1,26 @@ + +@@@ call graph @@@ + +entry points: + +test test + -> make_extern_calls at tests/ui/call_graph/warn_on_foreign_calls.rs:12:1: 12:23 (#0) + +nested calls at distance 1: + +make_extern_calls at tests/ui/call_graph/warn_on_foreign_calls.rs:12:1: 12:23 (#0) + -> foreign at tests/ui/call_graph/warn_on_foreign_calls.rs:7:5: 7:17 (#0) + -> not_foreign at tests/ui/call_graph/warn_on_foreign_calls.rs:10:1: 10:35 (#0) + +nested calls at distance 2: + + +@@@ targets @@@ + +tests -(1)-> [unsafe] not_foreign at tests/ui/call_graph/warn_on_foreign_calls.rs:10:1: 10:35 (#0) + (1) [tainted] test + +tests -(0)-> [unsafe] make_extern_calls at tests/ui/call_graph/warn_on_foreign_calls.rs:12:1: 12:23 (#0) + (0) test + +targets: 2 total; 0 safe; 2 unsafe (0 tainted)