Skip to content

Commit 2fc5c41

Browse files
Validate function/method stubs (rust-lang#1920)
1 parent 3e7903d commit 2fc5c41

File tree

23 files changed

+428
-74
lines changed

23 files changed

+428
-74
lines changed

kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
//! This file contains the code necessary to interface with the compiler backend
55
66
use crate::codegen_cprover_gotoc::GotocCtx;
7-
use crate::kani_middle::mir_transform;
7+
use crate::kani_middle::provide;
88
use crate::kani_middle::reachability::{
99
collect_reachable_items, filter_closures_in_const_crate_items, filter_crate_items,
1010
};
@@ -58,11 +58,11 @@ impl CodegenBackend for GotocCodegenBackend {
5858
}
5959

6060
fn provide(&self, providers: &mut Providers) {
61-
mir_transform::provide(providers);
61+
provide::provide(providers, &self.queries);
6262
}
6363

6464
fn provide_extern(&self, providers: &mut ty::query::ExternProviders) {
65-
mir_transform::provide_extern(providers);
65+
provide::provide_extern(providers);
6666
}
6767

6868
fn codegen_crate(

kani-compiler/src/kani_middle/mir_transform.rs

-47
This file was deleted.

kani-compiler/src/kani_middle/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
//! and transformations.
55
pub mod attributes;
66
pub mod coercion;
7-
pub mod mir_transform;
7+
pub mod provide;
88
pub mod reachability;
99
pub mod stubbing;
+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
//! This module contains an interface for setting custom query implementations
4+
//! to run during code generation. For example, this can be used to hook up
5+
//! custom MIR transformations.
6+
7+
use crate::kani_middle::reachability::{collect_reachable_items, filter_crate_items};
8+
use crate::kani_middle::stubbing;
9+
use kani_queries::{QueryDb, UserInput};
10+
use rustc_hir::def_id::DefId;
11+
use rustc_interface;
12+
use rustc_middle::ty::query::query_stored::collect_and_partition_mono_items;
13+
use rustc_middle::{
14+
mir::Body,
15+
ty::{query::ExternProviders, query::Providers, TyCtxt},
16+
};
17+
18+
/// Sets up rustc's query mechanism to apply Kani's custom queries to code from
19+
/// the present crate.
20+
pub fn provide(providers: &mut Providers, queries: &QueryDb) {
21+
providers.optimized_mir = run_mir_passes::<false>;
22+
if queries.get_stubbing_enabled() {
23+
providers.collect_and_partition_mono_items = collect_and_partition_mono_items;
24+
}
25+
}
26+
27+
/// Sets up rustc's query mechanism to apply Kani's custom queries to code from
28+
/// external crates.
29+
pub fn provide_extern(providers: &mut ExternProviders) {
30+
providers.optimized_mir = run_mir_passes::<true>;
31+
}
32+
33+
/// Returns the optimized code for the function associated with `def_id` by
34+
/// running rustc's optimization passes followed by Kani-specific passes.
35+
fn run_mir_passes<'tcx, const EXTERN: bool>(tcx: TyCtxt<'tcx>, def_id: DefId) -> &Body<'tcx> {
36+
tracing::debug!(?def_id, "Run rustc transformation passes");
37+
let optimized_mir = if EXTERN {
38+
rustc_interface::DEFAULT_EXTERN_QUERY_PROVIDERS.optimized_mir
39+
} else {
40+
rustc_interface::DEFAULT_QUERY_PROVIDERS.optimized_mir
41+
};
42+
let body = optimized_mir(tcx, def_id);
43+
44+
run_kani_mir_passes(tcx, def_id, body)
45+
}
46+
47+
/// Returns the optimized code for the function associated with `def_id` by
48+
/// running Kani-specific passes. The argument `body` should be the optimized
49+
/// code rustc generates for this function.
50+
fn run_kani_mir_passes<'tcx>(
51+
tcx: TyCtxt<'tcx>,
52+
def_id: DefId,
53+
body: &'tcx Body<'tcx>,
54+
) -> &'tcx Body<'tcx> {
55+
tracing::debug!(?def_id, "Run Kani transformation passes");
56+
stubbing::transform(tcx, def_id, body)
57+
}
58+
59+
/// Runs a reachability analysis before running the default
60+
/// `collect_and_partition_mono_items` query. The reachability analysis finds
61+
/// trait mismatches introduced by stubbing and performs a graceful exit in
62+
/// these cases. Left to its own devices, the default query panics.
63+
/// This is an issue when compiling a library, since the crate metadata is
64+
/// generated (using this query) before code generation begins (which is
65+
/// when we normally run the reachability analysis).
66+
fn collect_and_partition_mono_items(tcx: TyCtxt, key: ()) -> collect_and_partition_mono_items {
67+
let entry_fn = tcx.entry_fn(()).map(|(id, _)| id);
68+
let local_reachable = filter_crate_items(tcx, |_, def_id| {
69+
tcx.is_reachable_non_generic(def_id) || entry_fn == Some(def_id)
70+
});
71+
// We do not actually need the value returned here.
72+
collect_reachable_items(tcx, &local_reachable);
73+
(rustc_interface::DEFAULT_QUERY_PROVIDERS.collect_and_partition_mono_items)(tcx, key)
74+
}

kani-compiler/src/kani_middle/reachability.rs

+38-4
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ use rustc_middle::ty::{
3333
};
3434

3535
use crate::kani_middle::coercion;
36+
use crate::kani_middle::stubbing::get_stub_name;
3637

3738
/// Collect all reachable items starting from the given starting points.
3839
pub fn collect_reachable_items<'tcx>(
@@ -45,6 +46,7 @@ pub fn collect_reachable_items<'tcx>(
4546
for item in starting_points {
4647
collector.collect(*item);
4748
}
49+
tcx.sess.abort_if_errors();
4850

4951
// Sort the result so code generation follows deterministic order.
5052
// This helps us to debug the code, but it also provides the user a good experience since the
@@ -396,15 +398,47 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MonoItemsFnCollector<'a, 'tcx> {
396398

397399
let tcx = self.tcx;
398400
match terminator.kind {
399-
TerminatorKind::Call { ref func, .. } => {
401+
TerminatorKind::Call { ref func, ref args, .. } => {
400402
let callee_ty = func.ty(self.body, tcx);
401403
let fn_ty = self.monomorphize(callee_ty);
402404
if let TyKind::FnDef(def_id, substs) = *fn_ty.kind() {
403-
let instance =
405+
let instance_opt =
404406
Instance::resolve(self.tcx, ParamEnv::reveal_all(), def_id, substs)
405-
.unwrap()
406407
.unwrap();
407-
self.collect_instance(instance, true);
408+
match instance_opt {
409+
None => {
410+
let caller = tcx.def_path_str(self.instance.def_id());
411+
let callee = tcx.def_path_str(def_id);
412+
// Check if the current function has been stubbed.
413+
if let Some(stub) = get_stub_name(tcx, self.instance.def_id()) {
414+
// During the MIR stubbing transformation, we do not
415+
// force type variables in the stub's signature to
416+
// implement the same traits as those in the
417+
// original function/method. A trait mismatch shows
418+
// up here, when we try to resolve a trait method
419+
let generic_ty = args[0].ty(self.body, tcx).peel_refs();
420+
let receiver_ty = tcx.subst_and_normalize_erasing_regions(
421+
substs,
422+
ParamEnv::reveal_all(),
423+
generic_ty,
424+
);
425+
let sep = callee.rfind("::").unwrap();
426+
let trait_ = &callee[..sep];
427+
tcx.sess.span_err(
428+
terminator.source_info.span,
429+
format!(
430+
"`{receiver_ty}` doesn't implement \
431+
`{trait_}`. The function `{caller}` \
432+
cannot be stubbed by `{stub}` due to \
433+
generic bounds not being met."
434+
),
435+
);
436+
} else {
437+
panic!("unable to resolve call to `{callee}` in `{caller}`")
438+
}
439+
}
440+
Some(instance) => self.collect_instance(instance, true),
441+
};
408442
} else {
409443
assert!(
410444
matches!(fn_ty.kind(), TyKind::FnPtr(..)),

kani-compiler/src/kani_middle/stubbing/transform.rs

+96-14
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,107 @@ use rustc_data_structures::fx::FxHashMap;
1212
use rustc_hir::def_id::{DefId, LocalDefId};
1313
use rustc_middle::{mir::Body, ty::TyCtxt};
1414

15+
/// Returns the name of the stub for the function/method identified by `def_id`,
16+
/// and `None` if the function/method is not stubbed.
17+
pub fn get_stub_name(tcx: TyCtxt, def_id: DefId) -> Option<String> {
18+
let mapping = get_stub_mapping(tcx)?;
19+
let name = tcx.def_path_str(def_id);
20+
mapping.get(&name).map(String::clone)
21+
}
22+
1523
/// Returns the new body of a function/method if it has been stubbed out;
16-
/// otherwise, returns `None`.
17-
pub fn transform(tcx: TyCtxt, def_id: DefId) -> Option<&Body> {
18-
if let Some(mapping) = get_stub_mapping(tcx) {
19-
let name = tcx.def_path_str(def_id);
20-
if let Some(replacement) = mapping.get(&name) {
21-
if let Some(replacement_id) = get_def_id(tcx, replacement) {
22-
// TODO: We need to perform validation here (e.g., check that
23-
// the replacement is compatible with the original function).
24-
// <https://github.com/model-checking/kani/issues/1892>
25-
let new_body = tcx.optimized_mir(replacement_id).clone();
26-
return Some(tcx.arena.alloc(new_body));
24+
/// otherwise, returns the old body.
25+
pub fn transform<'tcx>(
26+
tcx: TyCtxt<'tcx>,
27+
def_id: DefId,
28+
old_body: &'tcx Body<'tcx>,
29+
) -> &'tcx Body<'tcx> {
30+
if let Some(replacement) = get_stub_name(tcx, def_id) {
31+
if let Some(replacement_id) = get_def_id(tcx, &replacement) {
32+
let new_body = tcx.optimized_mir(replacement_id).clone();
33+
if check_compatibility(tcx, def_id, old_body, replacement_id, &new_body) {
34+
return tcx.arena.alloc(new_body);
35+
}
36+
} else {
37+
tcx.sess
38+
.span_err(tcx.def_span(def_id), format!("unable to find stub: `{}`", replacement));
39+
};
40+
}
41+
old_body
42+
}
43+
44+
/// Checks whether the stub is compatible with the original function/method: do
45+
/// the arities and types (of the parameters and return values) match up? This
46+
/// does **NOT** check whether the type variables are constrained to implement
47+
/// the same traits; trait mismatches are checked during monomorphization.
48+
fn check_compatibility<'a, 'tcx>(
49+
tcx: TyCtxt,
50+
old_def_id: DefId,
51+
old_body: &'a Body<'tcx>,
52+
stub_def_id: DefId,
53+
stub_body: &'a Body<'tcx>,
54+
) -> bool {
55+
// Check whether the arities match.
56+
if old_body.arg_count != stub_body.arg_count {
57+
tcx.sess.span_err(
58+
tcx.def_span(stub_def_id),
59+
format!(
60+
"arity mismatch: original function/method `{}` takes {} argument(s), stub `{}` takes {}",
61+
tcx.def_path_str(old_def_id),
62+
old_body.arg_count,
63+
tcx.def_path_str(stub_def_id),
64+
stub_body.arg_count
65+
),
66+
);
67+
return false;
68+
}
69+
// Check whether the numbers of generic parameters match.
70+
let old_num_generics = tcx.generics_of(old_def_id).count();
71+
let stub_num_generics = tcx.generics_of(stub_def_id).count();
72+
if old_num_generics != stub_num_generics {
73+
tcx.sess.span_err(
74+
tcx.def_span(stub_def_id),
75+
format!(
76+
"mismatch in the number of generic parameters: original function/method `{}` takes {} generic parameters(s), stub `{}` takes {}",
77+
tcx.def_path_str(old_def_id),
78+
old_num_generics,
79+
tcx.def_path_str(stub_def_id),
80+
stub_num_generics
81+
),
82+
);
83+
return false;
84+
}
85+
// Check whether the types match. Index 0 refers to the returned value,
86+
// indices [1, `arg_count`] refer to the parameters.
87+
// TODO: We currently force generic parameters in the stub to have exactly
88+
// the same names as their counterparts in the original function/method;
89+
// instead, we should be checking for the equivalence of types up to the
90+
// renaming of generic parameters.
91+
// <https://github.com/model-checking/kani/issues/1953>
92+
let mut matches = true;
93+
for i in 0..=old_body.arg_count {
94+
let old_arg = old_body.local_decls.get(i.into()).unwrap();
95+
let new_arg = stub_body.local_decls.get(i.into()).unwrap();
96+
if old_arg.ty != new_arg.ty {
97+
let prefix = if i == 0 {
98+
"return type differs".to_string()
2799
} else {
28-
tcx.sess
29-
.span_err(tcx.def_span(def_id), format!("Unable to find stub: {replacement}"));
100+
format!("type of parameter {} differs", i - 1)
30101
};
102+
tcx.sess.span_err(
103+
new_arg.source_info.span,
104+
format!(
105+
"{prefix}: stub `{}` has type `{}` where original function/method `{}` has type `{}`",
106+
tcx.def_path_str(stub_def_id),
107+
new_arg.ty,
108+
tcx.def_path_str(old_def_id),
109+
old_arg.ty
110+
),
111+
);
112+
matches = false;
31113
}
32114
}
33-
None
115+
matches
34116
}
35117

36118
/// The prefix we will use when serializing the stub mapping as a rustc argument.
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: Unable to find stub: other_crate::PubType::the_answer
2-
error: Unable to find stub: other_crate::PubType::the_answer
3-
error: Unable to find stub: other_crate::PrivType::the_answer
1+
error: unable to find stub: `other_crate::PubType::the_answer`
2+
error: unable to find stub: `other_crate::PubType::the_answer`
3+
error: unable to find stub: `other_crate::PrivType::the_answer`
44
error: could not compile `foreign-function-stubbing` due to 3 previous errors
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
error: Unable to find stub: other_crate::pub_mod::priv_mod::the_answer
2-
error: Unable to find stub: other_crate::the_answer
1+
error: unable to find stub: `other_crate::pub_mod::priv_mod::the_answer`
2+
error: unable to find stub: `other_crate::the_answer`
33
error: could not compile `foreign-function-stubbing` due to 2 previous errors
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Copyright Kani Contributors
2+
# SPDX-License-Identifier: Apache-2.0 OR MIT
3+
[package]
4+
name = "stubbing-validate-random"
5+
version = "0.1.0"
6+
edition = "2021"
7+
8+
[dependencies]
9+
rand = "0.8.5"
10+
11+
[package.metadata.kani]
12+
flags = { enable-unstable=true, enable-stubbing=true }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
VERIFICATION:- SUCCESSFUL

0 commit comments

Comments
 (0)