Skip to content

Commit 0680dc6

Browse files
authored
Merge pull request #6 from os-checker/reachability
feat: reuse kani's reachability analysis
2 parents 21f2e80 + 511453a commit 0680dc6

File tree

7 files changed

+3452
-112
lines changed

7 files changed

+3452
-112
lines changed

src/functions/kani/coercion.rs

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
//! This module contains methods that help us process coercions.
5+
//! There are many types of coercions in rust, they are described in the
6+
//! [RFC 401 Coercions](https://rust-lang.github.io/rfcs/0401-coercions.html).
7+
//!
8+
//! The more complicated coercions are DST Coercions (aka Unsized Coercions). These coercions
9+
//! allow rust to create references to dynamically sized types (such as Traits and Slices) by
10+
//! casting concrete sized types. Unsized coercions can also be used to cast unsized to unsized
11+
//! types. These casts work not only on the top of references, but it also handle
12+
//! references inside structures, allowing the unsized coercions of smart pointers. The
13+
//! definition of custom coercions for smart pointers can be found in the
14+
//! [RFC 982 DST Coercion](https://rust-lang.github.io/rfcs/0982-dst-coercion.html).
15+
16+
use rustc_hir::lang_items::LangItem;
17+
use rustc_middle::traits::{ImplSource, ImplSourceUserDefinedData};
18+
use rustc_middle::ty::TraitRef;
19+
use rustc_middle::ty::adjustment::CustomCoerceUnsized;
20+
use rustc_middle::ty::{PseudoCanonicalInput, Ty, TyCtxt, TypingEnv};
21+
use rustc_smir::rustc_internal;
22+
use stable_mir::Symbol;
23+
use stable_mir::ty::{RigidTy, Ty as TyStable, TyKind};
24+
25+
/// Given an unsized coercion (e.g. from `&u8` to `&dyn Debug`), extract the pair of
26+
/// corresponding base types `T`, `U` (e.g. `u8`, `dyn Debug`), where the source base type `T` must
27+
/// implement `Unsize<U>` and `U` is either a trait or slice.
28+
///
29+
/// For more details, please refer to:
30+
/// <https://doc.rust-lang.org/reference/type-coercions.html#unsized-coercions>
31+
///
32+
/// This is used to determine the vtable implementation that must be tracked by the fat pointer.
33+
///
34+
/// For example, if `&u8` is being converted to `&dyn Debug`, this method would return:
35+
/// `(u8, dyn Debug)`.
36+
///
37+
/// There are a few interesting cases (references / pointers are handled the same way):
38+
/// 1. Coercion between `&T` to `&U`.
39+
/// - This is the base case.
40+
/// - In this case, we extract the types that are pointed to.
41+
/// 2. Coercion between smart pointers like `NonNull<T>` to `NonNull<U>`.
42+
/// - Smart pointers implement the `CoerceUnsize` trait.
43+
/// - Use CustomCoerceUnsized information to traverse the smart pointer structure and find the
44+
/// underlying pointer.
45+
/// - Use base case to extract `T` and `U`.
46+
/// 3. Coercion between a pointer to a structure whose tail is being coerced.
47+
///
48+
/// E.g.: A user may want to define a type like:
49+
/// ```
50+
/// struct Message<T> {
51+
/// header: &str,
52+
/// content: T,
53+
/// }
54+
/// ```
55+
/// They may want to abstract only the content of a message. So one could coerce a
56+
/// `&Message<String>` into a `&Message<dyn Display>`. In this case, this would:
57+
/// - Apply base case to extract the pair `(Message<T>, Message<U>)`.
58+
/// - Extract the tail element of the struct which are of type `T` and `U`, respectively.
59+
/// 4. Coercion between smart pointers of wrapper structs.
60+
/// - Apply the logic from item 2 then item 3.
61+
#[allow(dead_code)]
62+
pub fn extract_unsize_casting_stable(
63+
tcx: TyCtxt,
64+
src_ty: TyStable,
65+
dst_ty: TyStable,
66+
) -> CoercionBaseStable {
67+
let CoercionBase { src_ty, dst_ty } = extract_unsize_casting(
68+
tcx,
69+
rustc_internal::internal(tcx, src_ty),
70+
rustc_internal::internal(tcx, dst_ty),
71+
);
72+
CoercionBaseStable {
73+
src_ty: rustc_internal::stable(src_ty),
74+
dst_ty: rustc_internal::stable(dst_ty),
75+
}
76+
}
77+
78+
pub fn extract_unsize_casting<'tcx>(
79+
tcx: TyCtxt<'tcx>,
80+
src_ty: Ty<'tcx>,
81+
dst_ty: Ty<'tcx>,
82+
) -> CoercionBase<'tcx> {
83+
trace!(?src_ty, ?dst_ty, "extract_unsize_casting");
84+
// Iterate over the pointer structure to find the builtin pointer that will store the metadata.
85+
let coerce_info = CoerceUnsizedIterator::new(
86+
tcx,
87+
rustc_internal::stable(src_ty),
88+
rustc_internal::stable(dst_ty),
89+
)
90+
.last()
91+
.unwrap();
92+
// Extract the pointee type that is being coerced.
93+
let src_pointee_ty = extract_pointee(tcx, coerce_info.src_ty).unwrap_or_else(|| {
94+
panic!("Expected source to be a pointer. Found {:?} instead", coerce_info.src_ty)
95+
});
96+
let dst_pointee_ty = extract_pointee(tcx, coerce_info.dst_ty).unwrap_or_else(|| {
97+
panic!("Expected destination to be a pointer. Found {:?} instead", coerce_info.dst_ty)
98+
});
99+
// Find the tail of the coercion that determines the type of metadata to be stored.
100+
let (src_base_ty, dst_base_ty) = tcx.struct_lockstep_tails_for_codegen(
101+
src_pointee_ty,
102+
dst_pointee_ty,
103+
TypingEnv::fully_monomorphized(),
104+
);
105+
trace!(?src_base_ty, ?dst_base_ty, "extract_unsize_casting result");
106+
assert!(
107+
dst_base_ty.is_trait() || dst_base_ty.is_slice(),
108+
"Expected trait or slice as destination of unsized cast, but found {dst_base_ty:?}"
109+
);
110+
CoercionBase { src_ty: src_base_ty, dst_ty: dst_base_ty }
111+
}
112+
113+
/// This structure represents the base of a coercion.
114+
///
115+
/// This base is used to determine the information that will be stored in the metadata.
116+
/// E.g.: In order to convert an `Rc<String>` into an `Rc<dyn Debug>`, we need to generate a
117+
/// vtable that represents the `impl Debug for String`. So this type will carry the `String` type
118+
/// as the `src_ty` and the `dyn Debug` trait as `dst_ty`.
119+
#[derive(Debug)]
120+
pub struct CoercionBase<'tcx> {
121+
pub src_ty: Ty<'tcx>,
122+
pub dst_ty: Ty<'tcx>,
123+
}
124+
125+
#[derive(Debug)]
126+
#[allow(dead_code)]
127+
pub struct CoercionBaseStable {
128+
pub src_ty: TyStable,
129+
pub dst_ty: TyStable,
130+
}
131+
/// Iterates over the coercion path of a structure that implements `CoerceUnsized<T>` trait.
132+
/// The `CoerceUnsized<T>` trait indicates that this is a pointer or a wrapper for one, where
133+
/// unsizing can be performed on the pointee. More details:
134+
/// <https://doc.rust-lang.org/std/ops/trait.CoerceUnsized.html>
135+
///
136+
/// Given an unsized coercion between `impl CoerceUnsized<T>` to `impl CoerceUnsized<U>` where
137+
/// `T` is sized and `U` is unsized, this iterator will walk over the fields that lead to a
138+
/// pointer to `T`, which shall be converted from a thin pointer to a fat pointer.
139+
///
140+
/// Each iteration will also include an optional name of the field that differs from the current
141+
/// pair of types.
142+
///
143+
/// The first element of the iteration will always be the starting types.
144+
/// The last element of the iteration will always be pointers to `T` and `U`.
145+
/// After unsized element has been found, the iterator will return `None`.
146+
pub struct CoerceUnsizedIterator<'tcx> {
147+
tcx: TyCtxt<'tcx>,
148+
src_ty: Option<TyStable>,
149+
dst_ty: Option<TyStable>,
150+
}
151+
152+
/// Represent the information about a coercion.
153+
#[derive(Debug, Clone, PartialEq)]
154+
pub struct CoerceUnsizedInfo {
155+
/// The name of the field from the current types that differs between each other.
156+
pub field: Option<Symbol>,
157+
/// The type being coerced.
158+
pub src_ty: TyStable,
159+
/// The type that is the result of the coercion.
160+
pub dst_ty: TyStable,
161+
}
162+
163+
impl<'tcx> CoerceUnsizedIterator<'tcx> {
164+
pub fn new(
165+
tcx: TyCtxt<'tcx>,
166+
src_ty: TyStable,
167+
dst_ty: TyStable,
168+
) -> CoerceUnsizedIterator<'tcx> {
169+
CoerceUnsizedIterator { tcx, src_ty: Some(src_ty), dst_ty: Some(dst_ty) }
170+
}
171+
}
172+
173+
/// Iterate over the coercion path. At each iteration, it returns the name of the field that must
174+
/// be coerced, as well as the current source and the destination.
175+
/// E.g.: The first iteration of casting `NonNull<String>` -> `NonNull<&dyn Debug>` will return
176+
/// ```rust,ignore
177+
/// CoerceUnsizedInfo {
178+
/// field: Some("ptr"),
179+
/// src_ty, // NonNull<String>
180+
/// dst_ty // NonNull<&dyn Debug>
181+
/// }
182+
/// ```
183+
/// while the last iteration will return:
184+
/// ```rust,ignore
185+
/// CoerceUnsizedInfo {
186+
/// field: None,
187+
/// src_ty: Ty, // *const String
188+
/// dst_ty: Ty, // *const &dyn Debug
189+
/// }
190+
/// ```
191+
impl Iterator for CoerceUnsizedIterator<'_> {
192+
type Item = CoerceUnsizedInfo;
193+
194+
fn next(&mut self) -> Option<Self::Item> {
195+
if self.src_ty.is_none() {
196+
assert_eq!(self.dst_ty, None, "Expected no dst type.");
197+
return None;
198+
}
199+
200+
// Extract the pointee types from pointers (including smart pointers) that form the base of
201+
// the conversion.
202+
let src_ty = self.src_ty.take().unwrap();
203+
let dst_ty = self.dst_ty.take().unwrap();
204+
let field = match (src_ty.kind(), dst_ty.kind()) {
205+
(
206+
TyKind::RigidTy(RigidTy::Adt(src_def, ref src_args)),
207+
TyKind::RigidTy(RigidTy::Adt(dst_def, ref dst_args)),
208+
) => {
209+
// Handle smart pointers by using CustomCoerceUnsized to find the field being
210+
// coerced.
211+
assert_eq!(src_def, dst_def);
212+
let src_fields = &src_def.variants_iter().next().unwrap().fields();
213+
let dst_fields = &dst_def.variants_iter().next().unwrap().fields();
214+
assert_eq!(src_fields.len(), dst_fields.len());
215+
216+
let CustomCoerceUnsized::Struct(coerce_index) = custom_coerce_unsize_info(
217+
self.tcx,
218+
rustc_internal::internal(self.tcx, src_ty),
219+
rustc_internal::internal(self.tcx, dst_ty),
220+
);
221+
let coerce_index = coerce_index.as_usize();
222+
assert!(coerce_index < src_fields.len());
223+
224+
self.src_ty = Some(src_fields[coerce_index].ty_with_args(src_args));
225+
self.dst_ty = Some(dst_fields[coerce_index].ty_with_args(dst_args));
226+
Some(src_fields[coerce_index].name.clone())
227+
}
228+
_ => {
229+
// Base case is always a pointer (Box, raw_pointer or reference).
230+
assert!(
231+
extract_pointee(self.tcx, src_ty).is_some(),
232+
"Expected a pointer, but found {src_ty:?}"
233+
);
234+
None
235+
}
236+
};
237+
Some(CoerceUnsizedInfo { field, src_ty, dst_ty })
238+
}
239+
}
240+
241+
/// Get information about an unsized coercion.
242+
/// This code was extracted from `rustc_monomorphize` crate.
243+
/// <https://github.com/rust-lang/rust/blob/4891d57f7aab37b5d6a84f2901c0bb8903111d53/compiler/rustc_monomorphize/src/lib.rs#L25-L46>
244+
fn custom_coerce_unsize_info<'tcx>(
245+
tcx: TyCtxt<'tcx>,
246+
source_ty: Ty<'tcx>,
247+
target_ty: Ty<'tcx>,
248+
) -> CustomCoerceUnsized {
249+
let def_id = tcx.require_lang_item(LangItem::CoerceUnsized, None);
250+
251+
let trait_ref = TraitRef::new(tcx, def_id, tcx.mk_args_trait(source_ty, [target_ty.into()]));
252+
253+
match tcx.codegen_select_candidate(PseudoCanonicalInput {
254+
typing_env: TypingEnv::fully_monomorphized(),
255+
value: trait_ref,
256+
}) {
257+
Ok(ImplSource::UserDefined(ImplSourceUserDefinedData { impl_def_id, .. })) => {
258+
tcx.coerce_unsized_info(impl_def_id).unwrap().custom_kind.unwrap()
259+
}
260+
impl_source => {
261+
unreachable!("invalid `CoerceUnsized` impl_source: {:?}", impl_source);
262+
}
263+
}
264+
}
265+
266+
/// Extract pointee type from builtin pointer types.
267+
fn extract_pointee(tcx: TyCtxt<'_>, typ: TyStable) -> Option<Ty<'_>> {
268+
rustc_internal::internal(tcx, typ).builtin_deref(true)
269+
}

src/functions/kani/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
mod coercion;
2+
mod reachability;
3+
4+
pub use reachability::{CallGraph, collect_reachable_items};

0 commit comments

Comments
 (0)