1use std::collections::HashSet;
13
14use rustc_hir::def_id::DefId;
15use rustc_middle::{
16 mir::{Local, Operand, Rvalue, StatementKind, TerminatorKind},
17 ty::{self, TyCtxt},
18};
19
20use crate::{
21 helpers::name::parse_signature,
22 verify::{
23 call_summary::CallEffect,
24 contract::Property,
25 def_use::PlaceKey,
26 helpers::Checkpoint,
27 report::CheckResult,
28 verifier::{AbstractValue, CallSummary, ForwardVisitResult, StateFact},
29 },
30};
31
32use super::common::{SmtCheckResult, SmtChecker};
33
34#[derive(Clone, Copy, Debug, Eq, PartialEq)]
35enum AliveProducer {
36 SharedSlice,
37 UniqueSlice,
38}
39
40#[derive(Clone, Debug, Eq, PartialEq)]
41enum ReturnLifetime {
42 Elided,
43 Named(String),
44 Static,
45 Unknown,
46}
47
48#[derive(Clone, Debug)]
49struct SignatureInfo {
50 text: String,
51 return_lifetime: ReturnLifetime,
52 has_ref_self: bool,
53 has_mut_self: bool,
54 consumes_self: bool,
55}
56
57pub(crate) fn check<'tcx>(
59 checker: &SmtChecker<'tcx>,
60 checkpoint: &Checkpoint<'tcx>,
61 property: &Property<'tcx>,
62 forward: &ForwardVisitResult<'tcx>,
63) -> SmtCheckResult {
64 let Some(destination) = call_destination(checker.tcx, checkpoint) else {
65 return SmtCheckResult::unknown("Alive producer destination could not be resolved");
66 };
67 let Some(producer) =
68 alive_producer_from_destination(checker.tcx, checkpoint.caller, destination)
69 else {
70 return SmtCheckResult::unknown(
71 "Alive lowering currently supports borrowed view producers",
72 );
73 };
74 if !destination_flows_to_return(checker.tcx, checkpoint.caller, destination) {
75 return SmtCheckResult::proved(
76 "Alive view is local; no escaped returned lifetime must be anchored",
77 );
78 }
79
80 let Some(signature) = SignatureInfo::from_def_id(checker.tcx, checkpoint.caller) else {
81 return SmtCheckResult::unknown("Alive caller signature could not be recovered");
82 };
83 let target = checker
84 .property_target(checkpoint, property)
85 .or_else(|| checker.callsite_arg_place(checkpoint, 0));
86 let pointer_origin_param = target.as_ref().and_then(|place| {
87 pointer_origin_param_local(checker.tcx, checkpoint.caller, place, forward)
88 });
89
90 match &signature.return_lifetime {
91 ReturnLifetime::Elided => check_elided_return(producer, &signature),
92 ReturnLifetime::Static => failed("Alive failed: returned slice is widened to 'static"),
93 ReturnLifetime::Named(lifetime) => check_named_return(
94 checker.tcx,
95 checkpoint.caller,
96 producer,
97 &signature,
98 lifetime,
99 pointer_origin_param,
100 ),
101 ReturnLifetime::Unknown => {
102 SmtCheckResult::unknown("Alive return lifetime shape is not supported yet")
103 }
104 }
105}
106
107fn check_elided_return(producer: AliveProducer, signature: &SignatureInfo) -> SmtCheckResult {
109 if signature.has_mut_self && producer == AliveProducer::UniqueSlice {
110 return SmtCheckResult::proved(
111 "Alive proved: returned mutable slice is tied to the current &mut self borrow",
112 );
113 }
114 if signature.has_ref_self && producer == AliveProducer::SharedSlice {
115 return SmtCheckResult::proved(
116 "Alive proved: returned shared slice is tied to the current &self borrow",
117 );
118 }
119 if signature.has_ref_self && producer == AliveProducer::UniqueSlice {
120 return failed("Alive failed: mutable slice is tied only to a shared self borrow");
121 }
122 SmtCheckResult::unknown("Alive elided lifetime is not tied to a supported receiver")
123}
124
125fn check_named_return<'tcx>(
127 tcx: TyCtxt<'tcx>,
128 caller: DefId,
129 producer: AliveProducer,
130 signature: &SignatureInfo,
131 lifetime: &str,
132 pointer_origin_param: Option<usize>,
133) -> SmtCheckResult {
134 let source_params = params_with_lifetime(tcx, caller, signature, lifetime);
135
136 if let Some(origin) = pointer_origin_param {
137 if source_params.contains(&origin) {
138 return SmtCheckResult::proved(format!(
139 "Alive proved: returned lifetime '{lifetime} is tied to the source parameter that produced the pointer"
140 ));
141 }
142 if !source_params.is_empty() {
143 return failed(format!(
144 "Alive failed: returned lifetime '{lifetime} is tied to a host parameter, but the pointer comes from another source"
145 ));
146 }
147 }
148
149 if producer == AliveProducer::UniqueSlice
150 && signature.has_ref_self
151 && !signature.consumes_self
152 && source_params.is_empty()
153 {
154 return failed(format!(
155 "Alive failed: returned mutable slice uses lifetime '{lifetime}, but that lifetime is not tied to this &mut self borrow"
156 ));
157 }
158
159 if source_params.is_empty() {
160 return failed(format!(
161 "Alive failed: returned lifetime '{lifetime} has no proven source parameter"
162 ));
163 }
164
165 SmtCheckResult::unknown(format!(
166 "Alive could not prove that the pointer is produced from lifetime '{lifetime}"
167 ))
168}
169
170fn failed(note: impl Into<String>) -> SmtCheckResult {
172 SmtCheckResult {
173 result: CheckResult::Failed,
174 query: None,
175 notes: vec![note.into()],
176 }
177}
178
179fn alive_producer_from_destination<'tcx>(
181 tcx: TyCtxt<'tcx>,
182 caller: DefId,
183 destination: Local,
184) -> Option<AliveProducer> {
185 let body = tcx.optimized_mir(caller);
186 let ty = body.local_decls[destination].ty;
187 match ty.kind() {
188 ty::Ref(_, _, ty::Mutability::Mut) => Some(AliveProducer::UniqueSlice),
189 ty::Ref(_, _, ty::Mutability::Not) => Some(AliveProducer::SharedSlice),
190 _ => None,
191 }
192}
193
194impl SignatureInfo {
195 fn from_def_id(tcx: TyCtxt<'_>, def_id: DefId) -> Option<Self> {
197 let text = function_signature_text(tcx, def_id)?;
198 let params = params_segment(&text).unwrap_or_default();
199 Some(Self {
200 return_lifetime: return_lifetime(&text),
201 has_mut_self: params.contains("&mut self")
202 || params.contains("&'") && params.contains("mut self"),
203 has_ref_self: params.contains("&self")
204 || params.contains("&mut self")
205 || params.contains("&'") && params.contains("self"),
206 consumes_self: params
207 .split(',')
208 .next()
209 .is_some_and(|first| first.trim() == "self"),
210 text,
211 })
212 }
213}
214
215fn function_signature_text(tcx: TyCtxt<'_>, def_id: DefId) -> Option<String> {
217 let local = def_id.as_local()?;
218 let hir_id = tcx.local_def_id_to_hir_id(local);
219 let span = tcx.hir_span(hir_id);
220 let snippet = tcx.sess.source_map().span_to_snippet(span).ok()?;
221 let start = snippet.find("fn ")?;
222 let rest = &snippet[start..];
223 let end = rest.find('{').unwrap_or(rest.len());
224 Some(rest[..end].split_whitespace().collect::<Vec<_>>().join(" "))
225}
226
227fn params_segment(signature: &str) -> Option<String> {
229 let start = signature.find('(')?;
230 let end = signature[start + 1..].find(')')? + start + 1;
231 Some(signature[start + 1..end].to_string())
232}
233
234fn return_lifetime(signature: &str) -> ReturnLifetime {
236 let Some((_, ret)) = signature.split_once("->") else {
237 return ReturnLifetime::Unknown;
238 };
239 let ret = ret
240 .split("where")
241 .next()
242 .unwrap_or(ret)
243 .trim()
244 .trim_end_matches(';')
245 .trim()
246 .replace("& '", "&'");
247 if ret.starts_with("&'static") {
248 return ReturnLifetime::Static;
249 }
250 if let Some(rest) = ret.strip_prefix("&'") {
251 let lifetime = rest
252 .chars()
253 .take_while(|ch| ch.is_ascii_alphanumeric() || *ch == '_')
254 .collect::<String>();
255 if !lifetime.is_empty() {
256 return ReturnLifetime::Named(lifetime);
257 }
258 }
259 if ret.starts_with('&') {
260 return ReturnLifetime::Elided;
261 }
262 ReturnLifetime::Unknown
263}
264
265fn params_with_lifetime(
267 tcx: TyCtxt<'_>,
268 caller: DefId,
269 signature: &SignatureInfo,
270 lifetime: &str,
271) -> HashSet<usize> {
272 let (names, _) = parse_signature(tcx, caller);
273 names
274 .iter()
275 .enumerate()
276 .filter_map(|(index, name)| {
277 if name.is_empty() {
278 return None;
279 }
280 let pattern = format!("{name}: &'{lifetime}");
281 let text = signature.text.replace("& '", "&'");
282 text.contains(&pattern).then_some(index + 1)
283 })
284 .collect()
285}
286
287fn call_destination<'tcx>(tcx: TyCtxt<'tcx>, checkpoint: &Checkpoint<'tcx>) -> Option<Local> {
289 let body = tcx.optimized_mir(checkpoint.caller);
290 let terminator = body.basic_blocks[checkpoint.block].terminator();
291 let TerminatorKind::Call { destination, .. } = &terminator.kind else {
292 return None;
293 };
294 Some(destination.local)
295}
296
297fn destination_flows_to_return<'tcx>(tcx: TyCtxt<'tcx>, caller: DefId, destination: Local) -> bool {
299 if destination.as_usize() == 0 {
300 return true;
301 }
302 let body = tcx.optimized_mir(caller);
303 if body.local_decls[Local::from_usize(0)].ty == body.local_decls[destination].ty {
304 return true;
305 }
306 for block in body.basic_blocks.iter() {
307 for statement in &block.statements {
308 let StatementKind::Assign(assign) = &statement.kind else {
309 continue;
310 };
311 let (target, rvalue) = &**assign;
312 if target.local.as_usize() != 0 {
313 continue;
314 }
315 if rvalue_source_local(rvalue) == Some(destination) {
316 return true;
317 }
318 }
319 }
320 false
321}
322
323fn rvalue_source_local<'tcx>(rvalue: &Rvalue<'tcx>) -> Option<Local> {
325 match rvalue {
326 Rvalue::Use(Operand::Copy(place), ..)
327 | Rvalue::Use(Operand::Move(place), ..)
328 | Rvalue::Cast(_, Operand::Copy(place), _)
329 | Rvalue::Cast(_, Operand::Move(place), _)
330 | Rvalue::CopyForDeref(place) => Some(place.local),
331 _ => None,
332 }
333}
334
335fn pointer_origin_param_local<'tcx>(
337 tcx: TyCtxt<'tcx>,
338 caller: DefId,
339 place: &PlaceKey,
340 forward: &ForwardVisitResult<'tcx>,
341) -> Option<usize> {
342 let mut seen = HashSet::new();
343 pointer_origin_param_local_inner(tcx, caller, place, forward, &mut seen)
344}
345
346fn pointer_origin_param_local_inner<'tcx>(
348 tcx: TyCtxt<'tcx>,
349 caller: DefId,
350 place: &PlaceKey,
351 forward: &ForwardVisitResult<'tcx>,
352 seen: &mut HashSet<PlaceKey>,
353) -> Option<usize> {
354 if !seen.insert(place.clone()) {
355 return None;
356 }
357 if let Some(local) = place.local() {
358 let body = tcx.optimized_mir(caller);
359 if (1..=body.arg_count).contains(&local.as_usize()) {
360 return Some(local.as_usize());
361 }
362
363 if let Some(call) = call_result_for_local(forward, local) {
364 return call_pointer_origin_param(tcx, caller, call, forward, seen);
365 }
366
367 if let Some(value) = forward.values.get(&local) {
368 return value_pointer_origin_param(tcx, caller, value, forward, seen);
369 }
370 }
371 None
372}
373
374fn call_result_for_local<'a, 'tcx>(
376 forward: &'a ForwardVisitResult<'tcx>,
377 local: Local,
378) -> Option<&'a CallSummary<'tcx>> {
379 forward.facts.iter().find_map(|fact| {
380 let StateFact::Call(call) = fact else {
381 return None;
382 };
383 (call.destination == local).then_some(call)
384 })
385}
386
387fn call_pointer_origin_param<'tcx>(
389 tcx: TyCtxt<'tcx>,
390 caller: DefId,
391 call: &CallSummary<'tcx>,
392 forward: &ForwardVisitResult<'tcx>,
393 seen: &mut HashSet<PlaceKey>,
394) -> Option<usize> {
395 for effect in &call.effects {
396 match effect {
397 CallEffect::ReturnPointerFromArg { arg } | CallEffect::ReturnAliasArg { arg } => {
398 let value = call.args.get(*arg)?;
399 return value_pointer_origin_param(tcx, caller, value, forward, seen);
400 }
401 CallEffect::ReturnPointerAdd { base_arg, .. }
402 | CallEffect::ReturnPointerSub { base_arg, .. } => {
403 let value = call.args.get(*base_arg)?;
404 return value_pointer_origin_param(tcx, caller, value, forward, seen);
405 }
406 _ => {}
407 }
408 }
409 None
410}
411
412fn value_pointer_origin_param<'tcx>(
414 tcx: TyCtxt<'tcx>,
415 caller: DefId,
416 value: &AbstractValue<'tcx>,
417 forward: &ForwardVisitResult<'tcx>,
418 seen: &mut HashSet<PlaceKey>,
419) -> Option<usize> {
420 match value {
421 AbstractValue::Place(place) | AbstractValue::Ref(place) | AbstractValue::RawPtr(place) => {
422 pointer_origin_param_local_inner(tcx, caller, place, forward, seen)
423 }
424 AbstractValue::Cast(inner, _) => {
425 value_pointer_origin_param(tcx, caller, inner, forward, seen)
426 }
427 AbstractValue::CallResult(call) => {
428 call_pointer_origin_param(tcx, caller, call, forward, seen)
429 }
430 _ => None,
431 }
432}