Skip to content

Commit 65b431c

Browse files
Account for use statements when resolving paths in kani::stub attributes (rust-lang#2003)
Co-authored-by: Aaron Bembenek <[email protected]>
1 parent afe238d commit 65b431c

File tree

36 files changed

+788
-39
lines changed

36 files changed

+788
-39
lines changed

kani-compiler/src/kani_middle/resolve.rs

Lines changed: 149 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
use std::collections::VecDeque;
1111

1212
use rustc_hir::def::{DefKind, Res};
13-
use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_INDEX};
14-
use rustc_hir::ItemKind;
13+
use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_INDEX, LOCAL_CRATE};
14+
use rustc_hir::{ItemKind, UseKind};
1515
use rustc_middle::ty::TyCtxt;
1616

1717
/// Attempts to resolve a simple path (in the form of a string) to a `DefId`.
@@ -196,12 +196,9 @@ fn resolve_in_foreign_module(
196196
return resolve_in_foreign_module(tcx, inner_mod_id, segments);
197197
}
198198
}
199-
Res::Def(DefKind::Struct, type_id) | Res::Def(DefKind::Enum, type_id) => {
199+
Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Union, type_id) => {
200200
if first == child.ident.as_str() && segments.len() == 2 {
201-
let maybe_resolved = resolve_in_inherent_impls(tcx, type_id, &segments[1]);
202-
if maybe_resolved.is_some() {
203-
return maybe_resolved;
204-
}
201+
return resolve_in_type(tcx, type_id, &segments[1]);
205202
}
206203
}
207204
_ => {}
@@ -215,30 +212,34 @@ fn resolve_in_foreign_module(
215212
None
216213
}
217214

215+
/// Generates a more friendly string representation of a local module's name
216+
/// (the default representation for the crate root is the empty string).
217+
fn module_to_string(tcx: TyCtxt, current_module: LocalDefId) -> String {
218+
let def_id = current_module.to_def_id();
219+
if def_id.is_crate_root() {
220+
format!("module `{}`", tcx.crate_name(LOCAL_CRATE))
221+
} else {
222+
format!("module `{}`", tcx.def_path_str(def_id))
223+
}
224+
}
225+
218226
/// Resolves a path relative to a local module.
219227
fn resolve_relative(
220228
tcx: TyCtxt,
221229
current_module: LocalDefId,
222230
mut segments: Segments,
223231
) -> Option<DefId> {
224-
let current_module_string = || -> String {
225-
let def_id = current_module.to_def_id();
226-
if def_id.is_crate_root() {
227-
"crate root".to_string()
228-
} else {
229-
format!("module `{}`", tcx.def_path_str(def_id))
230-
}
231-
};
232232
tracing::debug!(
233233
"Resolving `{}` in local {}",
234234
segments_to_string(&segments),
235-
current_module_string()
235+
module_to_string(tcx, current_module)
236236
);
237237

238238
let first = segments.front().or_else(|| {
239239
tracing::debug!("Unable to resolve the empty path");
240240
None
241241
})?;
242+
let mut glob_imports = Vec::new();
242243
for item_id in tcx.hir().module_items(current_module) {
243244
let item = tcx.hir().item(item_id);
244245
let def_id = item.owner_id.def_id.to_def_id();
@@ -247,7 +248,7 @@ fn resolve_relative(
247248
if first == item.ident.as_str() && segments.len() == 1 {
248249
tracing::debug!(
249250
"Resolved `{first}` as a function in local {}",
250-
current_module_string()
251+
module_to_string(tcx, current_module)
251252
);
252253
return Some(def_id);
253254
}
@@ -258,41 +259,139 @@ fn resolve_relative(
258259
return resolve_relative(tcx, def_id.expect_local(), segments);
259260
}
260261
}
261-
ItemKind::Enum(..) | ItemKind::Struct(..) => {
262+
ItemKind::Enum(..) | ItemKind::Struct(..) | ItemKind::Union(..) => {
262263
if first == item.ident.as_str() && segments.len() == 2 {
263-
let maybe_resolved = resolve_in_inherent_impls(tcx, def_id, &segments[1]);
264-
if maybe_resolved.is_some() {
265-
return maybe_resolved;
264+
return resolve_in_type(tcx, def_id, &segments[1]);
265+
}
266+
}
267+
ItemKind::Use(use_path, UseKind::Single) => {
268+
if first == item.ident.as_str() {
269+
segments.pop_front();
270+
return resolve_in_use(tcx, use_path, segments);
271+
}
272+
}
273+
ItemKind::Use(use_path, UseKind::Glob) => {
274+
// Do not immediately try to resolve the path using this glob,
275+
// since paths resolved via non-globs take precedence.
276+
glob_imports.push(use_path);
277+
}
278+
ItemKind::ExternCrate(orig_name_opt) => {
279+
if first == item.ident.as_str() {
280+
if let Some(orig_name) = orig_name_opt {
281+
segments[0] = orig_name.to_string();
266282
}
283+
return resolve_external(tcx, segments);
267284
}
268285
}
269286
_ => (),
270287
}
271288
}
272289

273-
tracing::debug!("Unable to resolve `{first}` as an item in local {}", current_module_string());
290+
resolve_in_glob_uses(tcx, current_module, glob_imports, &segments).or_else(|| {
291+
tracing::debug!(
292+
"Unable to resolve `{first}` as an item in local {}",
293+
module_to_string(tcx, current_module)
294+
);
295+
None
296+
})
297+
}
298+
299+
/// Resolves a path relative to a local or foreign module.
300+
fn resolve_in_module(tcx: TyCtxt, current_module: DefId, segments: Segments) -> Option<DefId> {
301+
match current_module.as_local() {
302+
None => resolve_in_foreign_module(tcx, current_module, segments),
303+
Some(local_id) => resolve_relative(tcx, local_id, segments),
304+
}
305+
}
306+
307+
/// Resolves a path by exploring a non-glob use statement.
308+
fn resolve_in_use(tcx: TyCtxt, use_path: &rustc_hir::Path, segments: Segments) -> Option<DefId> {
309+
if let Res::Def(def_kind, def_id) = use_path.res {
310+
tracing::debug!(
311+
"Resolving `{}` via `use` import of `{}`",
312+
segments_to_string(&segments),
313+
tcx.def_path_str(def_id)
314+
);
315+
match def_kind {
316+
DefKind::Fn => {
317+
if segments.is_empty() {
318+
tracing::debug!(
319+
"Resolved `{}` to a function via `use` import of `{}`",
320+
segments_to_string(&segments),
321+
tcx.def_path_str(def_id)
322+
);
323+
return Some(def_id);
324+
}
325+
}
326+
DefKind::Mod => return resolve_in_module(tcx, def_id, segments),
327+
DefKind::Struct | DefKind::Enum | DefKind::Union => {
328+
if segments.len() == 1 {
329+
return resolve_in_type(tcx, def_id, &segments[0]);
330+
}
331+
}
332+
_ => (),
333+
}
334+
}
335+
tracing::debug!("Unable to resolve `{}` via `use` import", segments_to_string(&segments));
274336
None
275337
}
276338

277-
/// Resolves a name in an `impl` block.
278-
fn resolve_in_impl(tcx: TyCtxt, impl_id: DefId, name: &str) -> Option<DefId> {
279-
tracing::debug!("Resolving `{name}` in impl block `{}`", tcx.def_path_str(impl_id));
280-
for assoc_item in tcx.associated_item_def_ids(impl_id) {
281-
let item_path = tcx.def_path_str(*assoc_item);
282-
let last = item_path.split("::").last().unwrap();
283-
if last == name {
284-
tracing::debug!("Resolved `{name}` in impl block `{}`", tcx.def_path_str(impl_id));
285-
return Some(*assoc_item);
339+
/// Resolves a path by exploring glob use statements.
340+
fn resolve_in_glob_uses(
341+
tcx: TyCtxt,
342+
current_module: LocalDefId,
343+
glob_imports: Vec<&rustc_hir::Path>,
344+
segments: &Segments,
345+
) -> Option<DefId> {
346+
let glob_resolves = glob_imports
347+
.iter()
348+
.filter_map(|use_path| {
349+
let span = tracing::span!(tracing::Level::DEBUG, "glob_resolution");
350+
let _enter = span.enter();
351+
resolve_in_glob_use(tcx, use_path, segments.clone())
352+
})
353+
.collect::<Vec<_>>();
354+
if glob_resolves.len() == 1 {
355+
return glob_resolves.first().copied();
356+
}
357+
if glob_resolves.len() > 1 {
358+
// Raise an error if it's ambiguous which glob import a function comes
359+
// from. rustc will also raise an error in this case if the ambiguous
360+
// function is present in code (and not just as an attribute argument).
361+
// TODO: We should make this consistent with error handling for other
362+
// cases (see <https://github.com/model-checking/kani/issues/2013>).
363+
let location = module_to_string(tcx, current_module);
364+
let mut msg = format!(
365+
"glob imports in local {location} make it impossible to \
366+
unambiguously resolve path; the possibilities are:"
367+
);
368+
for def_id in glob_resolves {
369+
msg.push_str("\n\t");
370+
msg.push_str(&tcx.def_path_str(def_id));
286371
}
372+
tcx.sess.err(msg);
287373
}
288-
tracing::debug!("Unable to resolve `{name}` in impl block `{}`", tcx.def_path_str(impl_id));
289374
None
290375
}
291376

292-
/// Resolves a name in the inherent `impl` blocks of a type (i.e., non-trait
293-
/// `impl`s).
294-
fn resolve_in_inherent_impls(tcx: TyCtxt, type_id: DefId, name: &str) -> Option<DefId> {
377+
/// Resolves a path by exploring a glob use statement.
378+
fn resolve_in_glob_use(
379+
tcx: TyCtxt,
380+
use_path: &rustc_hir::Path,
381+
segments: Segments,
382+
) -> Option<DefId> {
383+
if let Res::Def(DefKind::Mod, def_id) = use_path.res {
384+
resolve_in_module(tcx, def_id, segments)
385+
} else {
386+
None
387+
}
388+
}
389+
390+
/// Resolves a method in a type. It currently does not resolve trait methods
391+
/// (see <https://github.com/model-checking/kani/issues/1997>).
392+
fn resolve_in_type(tcx: TyCtxt, type_id: DefId, name: &str) -> Option<DefId> {
295393
tracing::debug!("Resolving `{name}` in type `{}`", tcx.def_path_str(type_id));
394+
// Try the inherent `impl` blocks (i.e., non-trait `impl`s).
296395
for impl_ in tcx.inherent_impls(type_id) {
297396
let maybe_resolved = resolve_in_impl(tcx, *impl_, name);
298397
if maybe_resolved.is_some() {
@@ -303,6 +402,21 @@ fn resolve_in_inherent_impls(tcx: TyCtxt, type_id: DefId, name: &str) -> Option<
303402
None
304403
}
305404

405+
/// Resolves a name in an `impl` block.
406+
fn resolve_in_impl(tcx: TyCtxt, impl_id: DefId, name: &str) -> Option<DefId> {
407+
tracing::debug!("Resolving `{name}` in impl block `{}`", tcx.def_path_str(impl_id));
408+
for assoc_item in tcx.associated_item_def_ids(impl_id) {
409+
let item_path = tcx.def_path_str(*assoc_item);
410+
let last = item_path.split("::").last().unwrap();
411+
if last == name {
412+
tracing::debug!("Resolved `{name}` in impl block `{}`", tcx.def_path_str(impl_id));
413+
return Some(*assoc_item);
414+
}
415+
}
416+
tracing::debug!("Unable to resolve `{name}` in impl block `{}`", tcx.def_path_str(impl_id));
417+
None
418+
}
419+
306420
/// Does the current module have a (direct) submodule with the given name?
307421
fn has_submodule_with_name(tcx: TyCtxt, current_module: LocalDefId, name: &str) -> bool {
308422
for item_id in tcx.hir().module_items(current_module) {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
// Copyright Kani Contributors
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
33

4-
fn mock() {}
4+
pub fn mock() {}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
// Copyright Kani Contributors
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
33

4-
fn mock() {}
4+
pub fn mock() {}

tests/cargo-kani/stubbing-do-not-resolve/src/lib.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66
//! matches the name of a local module -- to external functions that match that
77
//! path.
88
9-
use other_crate1;
10-
use other_crate2;
9+
// Pull these crates into the compiler.
10+
mod ignore_me {
11+
use other_crate1;
12+
use other_crate2;
13+
}
1114

1215
mod my_mod {
1316

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Copyright Kani Contributors
2+
# SPDX-License-Identifier: Apache-2.0 OR MIT
3+
[package]
4+
name = "stubbing-do-not-resolve"
5+
version = "0.1.0"
6+
edition = "2021"
7+
8+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
9+
10+
[dependencies]
11+
other_crate = { path = "other_crate" }
12+
13+
[package.metadata.kani]
14+
flags = { enable-unstable=true, enable-stubbing=true }
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
VERIFICATION:- SUCCESSFUL
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Copyright Kani Contributors
2+
# SPDX-License-Identifier: Apache-2.0 OR MIT
3+
[package]
4+
name = "other_crate"
5+
version = "0.1.0"
6+
edition = "2021"
7+
8+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
9+
10+
[dependencies]
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
pub fn magic_number13() -> u32 {
5+
13
6+
}
7+
8+
pub mod inner_mod {
9+
pub fn magic_number42() -> u32 {
10+
42
11+
}
12+
}
13+
14+
pub struct MyType {}
15+
16+
impl MyType {
17+
pub fn magic_number101() -> u32 {
18+
101
19+
}
20+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
//! This tests whether we take into account `extern crate XXX as YYY;`
5+
//! statements when resolving paths in `kani::stub` attributes.
6+
7+
extern crate other_crate as foo;
8+
9+
#[kani::proof]
10+
#[kani::stub(zero, foo::magic_number13)]
11+
#[kani::stub(one, foo::inner_mod::magic_number42)]
12+
#[kani::stub(two, foo::MyType::magic_number101)]
13+
fn harness() {
14+
assert_eq!(zero(), foo::magic_number13());
15+
assert_eq!(one(), foo::inner_mod::magic_number42());
16+
assert_eq!(two(), foo::MyType::magic_number101());
17+
}
18+
19+
fn zero() -> u32 {
20+
0
21+
}
22+
23+
fn one() -> u32 {
24+
1
25+
}
26+
27+
fn two() -> u32 {
28+
2
29+
}
Lines changed: 12 additions & 0 deletions
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-use-as-foreign"
5+
version = "0.1.0"
6+
edition = "2021"
7+
8+
[dependencies]
9+
other_crate = { path = "other_crate" }
10+
11+
[package.metadata.kani]
12+
flags = { enable-unstable=true, enable-stubbing=true }

0 commit comments

Comments
 (0)