Skip to content

Commit 1d94b6b

Browse files
Resolve paths in kani::stub attributes (ignoring use statements) (rust-lang#1999)
1 parent 8b367c6 commit 1d94b6b

File tree

34 files changed

+767
-82
lines changed

34 files changed

+767
-82
lines changed

kani-compiler/src/kani_middle/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ pub mod attributes;
66
pub mod coercion;
77
pub mod provide;
88
pub mod reachability;
9+
pub mod resolve;
910
pub mod stubbing;

kani-compiler/src/kani_middle/reachability.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ use rustc_middle::ty::{
3333
};
3434

3535
use crate::kani_middle::coercion;
36-
use crate::kani_middle::stubbing::get_stub_name;
36+
use crate::kani_middle::stubbing::get_stub;
3737

3838
/// Collect all reachable items starting from the given starting points.
3939
pub fn collect_reachable_items<'tcx>(
@@ -410,7 +410,7 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MonoItemsFnCollector<'a, 'tcx> {
410410
let caller = tcx.def_path_str(self.instance.def_id());
411411
let callee = tcx.def_path_str(def_id);
412412
// Check if the current function has been stubbed.
413-
if let Some(stub) = get_stub_name(tcx, self.instance.def_id()) {
413+
if let Some(stub) = get_stub(tcx, self.instance.def_id()) {
414414
// During the MIR stubbing transformation, we do not
415415
// force type variables in the stub's signature to
416416
// implement the same traits as those in the
@@ -429,8 +429,9 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MonoItemsFnCollector<'a, 'tcx> {
429429
format!(
430430
"`{receiver_ty}` doesn't implement \
431431
`{trait_}`. The function `{caller}` \
432-
cannot be stubbed by `{stub}` due to \
433-
generic bounds not being met."
432+
cannot be stubbed by `{}` due to \
433+
generic bounds not being met.",
434+
tcx.def_path_str(stub)
434435
),
435436
);
436437
} else {
Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
//! This module contains code for resolving strings representing simple paths to
4+
//! `DefId`s for functions and methods. For the definition of a simple path, see
5+
//! <https://doc.rust-lang.org/reference/paths.html#simple-paths>.
6+
//!
7+
//! TODO: Extend this logic to support resolving qualified paths.
8+
//! <https://github.com/model-checking/kani/issues/1997>
9+
10+
use std::collections::VecDeque;
11+
12+
use rustc_hir::def::{DefKind, Res};
13+
use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_INDEX};
14+
use rustc_hir::ItemKind;
15+
use rustc_middle::ty::TyCtxt;
16+
17+
/// Attempts to resolve a simple path (in the form of a string) to a `DefId`.
18+
/// The current module is provided as an argument in order to resolve relative
19+
/// paths.
20+
///
21+
/// TODO: Extend this implementation to handle qualified paths and simple paths
22+
/// corresponding to trait methods.
23+
/// <https://github.com/model-checking/kani/issues/1997>
24+
pub fn resolve_path(tcx: TyCtxt, current_module: LocalDefId, path_str: &str) -> Option<DefId> {
25+
let span = tracing::span!(tracing::Level::DEBUG, "path_resolution");
26+
let _enter = span.enter();
27+
28+
let path = to_path(tcx, current_module, path_str)?;
29+
match &path.base {
30+
Base::ExternPrelude => resolve_external(tcx, path.segments),
31+
Base::LocalModule { id, may_be_external_path } => {
32+
// Try to resolve it as a relative path first; if this fails and the
33+
// path might be external (it wasn't qualified with `self`, etc.)
34+
// and the current module does not have a submodule with the same
35+
// first segment, try resolving it as an external path.
36+
resolve_relative(tcx, *id, path.segments.clone()).or_else(|| {
37+
if *may_be_external_path
38+
&& !has_submodule_with_name(tcx, current_module, path.segments.front()?)
39+
{
40+
resolve_external(tcx, path.segments)
41+
} else {
42+
None
43+
}
44+
})
45+
}
46+
}
47+
}
48+
49+
/// The segments of a path.
50+
type Segments = VecDeque<String>;
51+
52+
/// Generates the string representation of the path composed of these segments
53+
/// (it is inefficient, but we use it only in debugging output).
54+
fn segments_to_string(segments: &Segments) -> String {
55+
segments.iter().cloned().collect::<Vec<_>>().join("::")
56+
}
57+
58+
/// The "starting point" for a path.
59+
#[derive(Debug)]
60+
enum Base {
61+
/// Indicates an external path.
62+
ExternPrelude,
63+
/// Indicates a path that may be local (and must be local if
64+
/// `may_be_external_path` is false) and should be resolved relative to the
65+
/// module identified by `id`.
66+
LocalModule { id: LocalDefId, may_be_external_path: bool },
67+
}
68+
69+
/// A path consisting of a starting point and a bunch of segments. If `base`
70+
/// matches `Base::LocalModule { id: _, may_be_external_path : true }`, then
71+
/// `segments` cannot be empty.
72+
#[derive(Debug)]
73+
struct Path {
74+
base: Base,
75+
segments: Segments,
76+
}
77+
78+
impl Path {
79+
fn new(base: Base, segments: Segments) -> Self {
80+
Path { base, segments }
81+
}
82+
}
83+
84+
/// Takes a string representation of a path and turns it into a `Path` data
85+
/// structure, resolving qualifiers (like `crate`, etc.) along the way.
86+
fn to_path(tcx: TyCtxt, current_module: LocalDefId, name: &str) -> Option<Path> {
87+
tracing::debug!("Normalizing path `{name}`");
88+
89+
const CRATE: &str = "crate";
90+
// rustc represents initial `::` as `{{root}}`.
91+
const ROOT: &str = "{{root}}";
92+
const SELF: &str = "self";
93+
const SUPER: &str = "super";
94+
95+
// Split the string into segments separated by `::`.
96+
let mut segments: Segments = name.split("::").map(str::to_string).collect();
97+
if segments.is_empty() {
98+
return Some(Path::new(
99+
Base::LocalModule { id: current_module, may_be_external_path: false },
100+
segments,
101+
));
102+
}
103+
104+
// Resolve qualifiers `crate`, initial `::`, and `self`. The qualifier
105+
// `self` may be followed be `super` (handled below).
106+
let first = segments[0].as_str();
107+
let may_be_external_path = !matches!(first, CRATE | SELF | SUPER);
108+
match first {
109+
ROOT => {
110+
segments.pop_front();
111+
return Some(Path::new(Base::ExternPrelude, segments));
112+
}
113+
CRATE => {
114+
segments.pop_front();
115+
// Find the module at the root of the crate.
116+
let current_module_hir_id = tcx.hir().local_def_id_to_hir_id(current_module);
117+
let crate_root = match tcx.hir().parent_iter(current_module_hir_id).last() {
118+
None => current_module,
119+
Some((hir_id, _)) => tcx.hir().local_def_id(hir_id),
120+
};
121+
return Some(Path::new(
122+
Base::LocalModule { id: crate_root, may_be_external_path },
123+
segments,
124+
));
125+
}
126+
SELF => {
127+
segments.pop_front();
128+
}
129+
_ => (),
130+
}
131+
132+
// Pop up the module stack until we account for all the `super` prefixes.
133+
let current_module_hir_id = tcx.hir().local_def_id_to_hir_id(current_module);
134+
let mut parents = tcx.hir().parent_iter(current_module_hir_id);
135+
let mut base_module = current_module;
136+
while segments.front().map(String::as_str) == Some(SUPER) {
137+
segments.pop_front();
138+
let parent = parents.next().map(|p| p.0).or_else(|| {
139+
tracing::debug!("Unable to normalize path `{name}`: too many `super` qualifiers");
140+
None
141+
})?;
142+
base_module = tcx.hir().local_def_id(parent);
143+
}
144+
145+
Some(Path::new(Base::LocalModule { id: base_module, may_be_external_path }, segments))
146+
}
147+
148+
/// Resolves an external path.
149+
fn resolve_external(tcx: TyCtxt, mut segments: Segments) -> Option<DefId> {
150+
tracing::debug!("Resolving `{}` in the external prelude", segments_to_string(&segments));
151+
let first = segments.pop_front().or_else(|| {
152+
tracing::debug!("Unable to resolve the empty path");
153+
None
154+
})?;
155+
for crate_num in tcx.crates(()) {
156+
let crate_name = tcx.crate_name(*crate_num);
157+
if crate_name.as_str() == first {
158+
let crate_def_id = DefId { index: CRATE_DEF_INDEX, krate: *crate_num };
159+
return resolve_in_foreign_module(tcx, crate_def_id, segments);
160+
}
161+
}
162+
tracing::debug!("Unable to resolve `{first}` as an external crate");
163+
None
164+
}
165+
166+
/// Resolves a path relative to a foreign module.
167+
fn resolve_in_foreign_module(
168+
tcx: TyCtxt,
169+
foreign_mod: DefId,
170+
mut segments: Segments,
171+
) -> Option<DefId> {
172+
tracing::debug!(
173+
"Resolving `{}` in foreign module `{}`",
174+
segments_to_string(&segments),
175+
tcx.def_path_str(foreign_mod)
176+
);
177+
178+
let first = segments.front().or_else(|| {
179+
tracing::debug!("Unable to resolve the empty path");
180+
None
181+
})?;
182+
for child in tcx.module_children(foreign_mod) {
183+
match child.res {
184+
Res::Def(DefKind::Fn, def_id) => {
185+
if first == child.ident.as_str() && segments.len() == 1 {
186+
tracing::debug!(
187+
"Resolved `{first}` as a function in foreign module `{}`",
188+
tcx.def_path_str(foreign_mod)
189+
);
190+
return Some(def_id);
191+
}
192+
}
193+
Res::Def(DefKind::Mod, inner_mod_id) => {
194+
if first == child.ident.as_str() {
195+
segments.pop_front();
196+
return resolve_in_foreign_module(tcx, inner_mod_id, segments);
197+
}
198+
}
199+
Res::Def(DefKind::Struct, type_id) | Res::Def(DefKind::Enum, type_id) => {
200+
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+
}
205+
}
206+
}
207+
_ => {}
208+
}
209+
}
210+
211+
tracing::debug!(
212+
"Unable to resolve `{first}` as an item in foreign module `{}`",
213+
tcx.def_path_str(foreign_mod)
214+
);
215+
None
216+
}
217+
218+
/// Resolves a path relative to a local module.
219+
fn resolve_relative(
220+
tcx: TyCtxt,
221+
current_module: LocalDefId,
222+
mut segments: Segments,
223+
) -> 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+
};
232+
tracing::debug!(
233+
"Resolving `{}` in local {}",
234+
segments_to_string(&segments),
235+
current_module_string()
236+
);
237+
238+
let first = segments.front().or_else(|| {
239+
tracing::debug!("Unable to resolve the empty path");
240+
None
241+
})?;
242+
for item_id in tcx.hir().module_items(current_module) {
243+
let item = tcx.hir().item(item_id);
244+
let def_id = item.owner_id.def_id.to_def_id();
245+
match item.kind {
246+
ItemKind::Fn(..) => {
247+
if first == item.ident.as_str() && segments.len() == 1 {
248+
tracing::debug!(
249+
"Resolved `{first}` as a function in local {}",
250+
current_module_string()
251+
);
252+
return Some(def_id);
253+
}
254+
}
255+
ItemKind::Mod(..) => {
256+
if first == item.ident.as_str() {
257+
segments.pop_front();
258+
return resolve_relative(tcx, def_id.expect_local(), segments);
259+
}
260+
}
261+
ItemKind::Enum(..) | ItemKind::Struct(..) => {
262+
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;
266+
}
267+
}
268+
}
269+
_ => (),
270+
}
271+
}
272+
273+
tracing::debug!("Unable to resolve `{first}` as an item in local {}", current_module_string());
274+
None
275+
}
276+
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);
286+
}
287+
}
288+
tracing::debug!("Unable to resolve `{name}` in impl block `{}`", tcx.def_path_str(impl_id));
289+
None
290+
}
291+
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> {
295+
tracing::debug!("Resolving `{name}` in type `{}`", tcx.def_path_str(type_id));
296+
for impl_ in tcx.inherent_impls(type_id) {
297+
let maybe_resolved = resolve_in_impl(tcx, *impl_, name);
298+
if maybe_resolved.is_some() {
299+
return maybe_resolved;
300+
}
301+
}
302+
tracing::debug!("Unable to resolve `{name}` in type `{}`", tcx.def_path_str(type_id));
303+
None
304+
}
305+
306+
/// Does the current module have a (direct) submodule with the given name?
307+
fn has_submodule_with_name(tcx: TyCtxt, current_module: LocalDefId, name: &str) -> bool {
308+
for item_id in tcx.hir().module_items(current_module) {
309+
let item = tcx.hir().item(item_id);
310+
if let ItemKind::Mod(..) = item.kind {
311+
if name == item.ident.as_str() {
312+
return true;
313+
}
314+
}
315+
}
316+
false
317+
}

0 commit comments

Comments
 (0)