1use std::collections::HashSet;
10
11use rustc_abi::FieldIdx;
12use rustc_hir::def_id::DefId;
13use rustc_middle::ty::{Ty, TyCtxt, TyKind};
14
15use super::common::{SmtCheckResult, SmtChecker};
16use crate::verify::{
17 call_summary::CallEffect,
18 contract::Property,
19 def_use::PlaceKey,
20 helpers::Checkpoint,
21 verifier::{AbstractValue, ForwardVisitResult, StateFact},
22};
23
24const MAX_TYPED_TRACE_DEPTH: usize = 32;
25
26pub(crate) fn check<'tcx>(
28 checker: &SmtChecker<'tcx>,
29 checkpoint: &Checkpoint<'tcx>,
30 property: &Property<'tcx>,
31 forward: &ForwardVisitResult<'tcx>,
32) -> SmtCheckResult {
33 let Some(target) = checker.property_target(checkpoint, property) else {
34 return SmtCheckResult::unknown("Typed target could not be resolved");
35 };
36 let Some(required_ty) = checker.property_required_ty(checkpoint, property) else {
37 return SmtCheckResult::unknown("Typed type could not be resolved");
38 };
39
40 let mut context = TypedContext {
41 checker,
42 caller: checkpoint.caller,
43 forward,
44 required_ty,
45 };
46
47 let mut seen = HashSet::new();
48 if context.place_is_typed(&target, &mut seen, 0) {
49 SmtCheckResult::proved(format!(
50 "typed provenance proved for {} as {:?}",
51 place_debug(&target),
52 required_ty
53 ))
54 } else {
55 SmtCheckResult::unknown(format!(
56 "current path facts do not prove {} is typed as {:?}",
57 place_debug(&target),
58 required_ty
59 ))
60 }
61}
62
63struct TypedContext<'a, 'tcx> {
64 checker: &'a SmtChecker<'tcx>,
65 caller: DefId,
66 forward: &'a ForwardVisitResult<'tcx>,
67 required_ty: Ty<'tcx>,
68}
69
70impl<'a, 'tcx> TypedContext<'a, 'tcx> {
71 fn place_is_typed(
73 &mut self,
74 place: &PlaceKey,
75 seen: &mut HashSet<PlaceKey>,
76 depth: usize,
77 ) -> bool {
78 if depth > MAX_TYPED_TRACE_DEPTH || !seen.insert(place.clone()) {
79 return false;
80 }
81
82 if self.known_init_matches(place) {
83 return true;
84 }
85
86 if let Some(value) = self.latest_value_for_place(place)
87 && self.value_is_typed(&value, seen, depth + 1)
88 {
89 return true;
90 }
91
92 if let Some(value) = self.latest_cast_source_for_place(place)
93 && self.value_is_typed(&value, seen, depth + 1)
94 {
95 return true;
96 }
97
98 if let Some(source) = self.latest_points_to_source(place) {
99 if self.storage_place_is_typed(&source) {
100 return true;
101 }
102 if self.place_is_typed(&source, seen, depth + 1) {
103 return true;
104 }
105 }
106
107 self.storage_place_is_typed(place)
108 }
109
110 fn value_is_typed(
112 &mut self,
113 value: &AbstractValue<'tcx>,
114 seen: &mut HashSet<PlaceKey>,
115 depth: usize,
116 ) -> bool {
117 if depth > MAX_TYPED_TRACE_DEPTH {
118 return false;
119 }
120
121 match value {
122 AbstractValue::Place(place) => self.place_is_typed(place, seen, depth + 1),
123 AbstractValue::Ref(place) | AbstractValue::RawPtr(place) => {
124 self.storage_place_is_typed(place) || self.place_is_typed(place, seen, depth + 1)
125 }
126 AbstractValue::Cast(inner, ty) => {
127 self.ty_has_required_payload(*ty) && self.value_is_typed(inner, seen, depth + 1)
128 }
129 AbstractValue::CallResult(call) => call.effects.iter().any(|effect| match effect {
130 CallEffect::ReturnPointerFromArg { arg }
131 | CallEffect::ReturnAliasArg { arg }
132 | CallEffect::ReturnPointerAdd { base_arg: arg, .. }
133 | CallEffect::ReturnPointerSub { base_arg: arg, .. } => call
134 .args
135 .get(*arg)
136 .is_some_and(|arg_value| self.value_is_typed(arg_value, seen, depth + 1)),
137 _ => false,
138 }),
139 _ => false,
140 }
141 }
142
143 fn latest_value_for_place(&self, place: &PlaceKey) -> Option<AbstractValue<'tcx>> {
145 if !place.fields.is_empty() {
146 return None;
147 }
148 let local = place.local()?;
149 self.forward
150 .latest_value_definition_before(local, self.forward.value_definitions.len())
151 .map(|definition| definition.value.clone())
152 }
153
154 fn latest_cast_source_for_place(&self, place: &PlaceKey) -> Option<AbstractValue<'tcx>> {
156 self.forward.facts.iter().rev().find_map(|fact| {
157 let StateFact::Cast { target, source, ty } = fact else {
158 return None;
159 };
160 if target == place && self.ty_has_required_payload(*ty) {
161 Some(source.clone())
162 } else {
163 None
164 }
165 })
166 }
167
168 fn latest_points_to_source(&self, place: &PlaceKey) -> Option<PlaceKey> {
170 self.forward.facts.iter().rev().find_map(|fact| {
171 let StateFact::PointsTo { pointer, source } = fact else {
172 return None;
173 };
174 (pointer == place).then(|| source.clone())
175 })
176 }
177
178 fn known_init_matches(&self, place: &PlaceKey) -> bool {
180 if self.forward.facts.iter().rev().any(|fact| {
181 let StateFact::KnownInit { place: init_place, ty_name, elements, .. } = fact else { return false };
182 init_place == place && *elements > 0 && self.ty_name_matches(ty_name)
183 }) {
184 return true;
185 }
186 if !place.fields.is_empty() {
187 let mut ancestor = place.clone();
188 while !ancestor.fields.is_empty() {
189 ancestor.fields.pop();
190 if self.forward.facts.iter().rev().any(|fact| {
191 let StateFact::KnownInit { place: init_place, ty_name, elements, .. } = fact else { return false };
192 init_place == &ancestor && *elements > 0 && self.ty_name_matches(ty_name)
193 }) {
194 return true;
195 }
196 }
197 }
198 false
199 }
200
201 fn storage_place_is_typed(&self, place: &PlaceKey) -> bool {
203 let Some(ty) = self.place_ty(place) else {
204 return false;
205 };
206
207 match *ty.kind() {
208 TyKind::RawPtr(_, _) => false,
209 TyKind::Ref(_, inner, _) => self.ty_matches(payload_ty(inner)),
210 TyKind::Slice(element) | TyKind::Array(element, _) => self.ty_matches(element),
211 _ => self.ty_matches(ty),
212 }
213 }
214
215 fn place_ty(&self, place: &PlaceKey) -> Option<Ty<'tcx>> {
217 let local = place.local()?;
218 let body = self.checker.tcx.optimized_mir(self.caller);
219 let mut ty = body.local_decls.get(local)?.ty;
220
221 for field in &place.fields {
222 ty = field_ty(self.checker.tcx, ty, *field)?;
223 }
224
225 Some(ty)
226 }
227
228 fn ty_has_required_payload(&self, ty: Ty<'tcx>) -> bool {
230 self.ty_matches(payload_ty(ty))
231 }
232
233 fn ty_matches(&self, ty: Ty<'tcx>) -> bool {
235 ty == self.required_ty || format!("{ty:?}") == format!("{:?}", self.required_ty)
236 }
237
238 fn ty_name_matches(&self, ty_name: &str) -> bool {
240 ty_name == format!("{:?}", self.required_ty)
241 }
242}
243
244fn payload_ty<'tcx>(ty: Ty<'tcx>) -> Ty<'tcx> {
246 match *ty.kind() {
247 TyKind::RawPtr(inner, _) | TyKind::Ref(_, inner, _) => payload_ty(inner),
248 TyKind::Slice(element) | TyKind::Array(element, _) => element,
249 _ => ty,
250 }
251}
252
253fn field_ty<'tcx>(tcx: TyCtxt<'tcx>, base_ty: Ty<'tcx>, field: usize) -> Option<Ty<'tcx>> {
255 let peeled_ty = base_ty.peel_refs();
256 match *peeled_ty.kind() {
257 TyKind::Adt(adt_def, args) => {
258 if !adt_def.is_struct() && !adt_def.is_union() {
259 return None;
260 }
261 let variant = adt_def.non_enum_variant();
262 if field >= variant.fields.len() {
263 return None;
264 }
265 #[cfg(not(rapx_rustc_ge_198))]
266 let ty = variant.fields[FieldIdx::from_usize(field)].ty(tcx, args);
267 #[cfg(rapx_rustc_ge_198)]
268 let ty = variant.fields[FieldIdx::from_usize(field)]
269 .ty(tcx, args)
270 .skip_norm_wip();
271 Some(ty)
272 }
273 TyKind::Tuple(fields) => fields.get(field).copied(),
274 _ => None,
275 }
276}
277
278fn place_debug(place: &PlaceKey) -> String {
280 format!("{place:?}")
281}