1use std::collections::HashSet;
9
10use crate::compat::FxHashMap;
11use crate::compat::Spanned;
12use rustc_hir::def_id::DefId;
13use rustc_middle::{
14 mir::{
15 AggregateKind, BasicBlock, BinOp, Body, Local, Operand, Place, Rvalue, Statement,
16 StatementKind, Terminator, TerminatorKind, UnOp,
17 },
18 ty::{Ty, TyCtxt, TyKind},
19};
20
21use super::{
22 call_summary::{self, CallEffect, CallEffectSummary},
23 contract::Property,
24 def_use::{PlaceBaseKey, PlaceKey},
25 helpers::CheckpointLocation,
26 path_extractor::{Path, PathStep},
27 slicer::{BackwardItem, ForgetReason, KeepReason, RelevantMirItems},
28};
29
30pub struct ForwardVerifier<'tcx> {
32 tcx: TyCtxt<'tcx>,
33}
34
35impl<'tcx> ForwardVerifier<'tcx> {
36 pub fn new(tcx: TyCtxt<'tcx>) -> Self {
38 Self { tcx }
39 }
40
41 pub fn visit(&self, items: &RelevantMirItems<'tcx>) -> ForwardVisitResult<'tcx> {
43 let mut result =
44 ForwardVisitResult::new(items.checkpoint, items.property.clone(), items.path.clone());
45 let body = self.tcx.optimized_mir(items.checkpoint.caller);
46
47 for item in &items.items {
48 match item {
49 BackwardItem::Statement {
50 block,
51 statement_index,
52 kind,
53 } => {
54 let statement = &body.basic_blocks[*block].statements[*statement_index];
55 self.visit_statement(
56 *block,
57 *statement_index,
58 *kind,
59 statement,
60 body,
61 &mut result,
62 );
63 }
64 BackwardItem::Terminator { block, kind } => {
65 let terminator = body.basic_blocks[*block].terminator();
66 self.visit_terminator(*block, *kind, terminator, body, &mut result);
67 }
68 BackwardItem::PathStep { step, kind } => {
69 result.steps.push(ForwardStep::PathStep {
70 step: step.clone(),
71 reason: *kind,
72 });
73 }
74 BackwardItem::ContractFact { property } => {
75 result.facts.push(StateFact::Contract(property.clone()));
76 }
77 BackwardItem::Forget { reason } => {
78 result.forgets.push(reason.clone());
79 }
80 }
81 }
82
83 result
84 }
85
86 fn visit_statement(
88 &self,
89 block: BasicBlock,
90 statement_index: usize,
91 reason: KeepReason,
92 statement: &Statement<'tcx>,
93 body: &Body<'tcx>,
94 result: &mut ForwardVisitResult<'tcx>,
95 ) {
96 result.steps.push(ForwardStep::Statement {
97 block,
98 statement_index,
99 reason,
100 });
101
102 match &statement.kind {
103 StatementKind::Assign(assign) => {
104 let (place, rvalue) = &**assign;
105 let value = self.value_from_rvalue(rvalue);
106 result.record_value_definition(block, Some(statement_index), place.local, value);
107 self.record_rvalue_facts(block, statement_index, place, rvalue, body, result);
108 }
109 StatementKind::FakeRead(..)
110 | StatementKind::SetDiscriminant { .. }
111 | StatementKind::StorageLive(..)
112 | StatementKind::AscribeUserType(..)
113 | StatementKind::Coverage(..)
114 | StatementKind::PlaceMention(..)
115 | StatementKind::Intrinsic(..)
116 | StatementKind::ConstEvalCounter
117 | StatementKind::Nop => {}
118 #[cfg(not(rapx_rustc_ge_198))]
119 StatementKind::Retag(..) => {}
120 StatementKind::StorageDead(local) => {
121 result.facts.push(StateFact::LocalDead(*local));
122 }
123 _ => result.notes.push(format!(
124 "unsupported statement at bb{}#{statement_index}",
125 block.as_usize()
126 )),
127 }
128 }
129
130 fn visit_terminator(
132 &self,
133 block: BasicBlock,
134 reason: KeepReason,
135 terminator: &Terminator<'tcx>,
136 body: &Body<'tcx>,
137 result: &mut ForwardVisitResult<'tcx>,
138 ) {
139 result.steps.push(ForwardStep::Terminator { block, reason });
140
141 match &terminator.kind {
142 TerminatorKind::Call {
143 func,
144 args,
145 destination,
146 ..
147 } => {
148 let arg_values = args
149 .iter()
150 .map(|arg| value_from_operand(&arg.node))
151 .collect();
152 let effect_summary = call_summary::effect_summary(
153 self.tcx,
154 result.checkpoint.caller,
155 func,
156 destination.local,
157 );
158 let call = CallSummary {
159 destination: destination.local,
160 func: call_summary::call_name(self.tcx, func),
161 arg_count: args.len(),
162 args: arg_values,
163 effects: effect_summary.effects.clone(),
164 unsupported: effect_summary.unsupported,
165 };
166 result.record_value_definition(
167 block,
168 None,
169 destination.local,
170 AbstractValue::CallResult(call.clone()),
171 );
172 result.facts.push(StateFact::Call(call));
173 self.apply_call_effects(
174 block,
175 &effect_summary,
176 args,
177 reason == KeepReason::Checkpoint,
178 result,
179 );
180 let is_uninit = call_summary::is_maybe_uninit_uninit_call(&call_summary::call_name(self.tcx, func));
181 if is_uninit
182 && let Some((_elem_ty_name, elements)) =
183 self.allocated_element_summary(result.checkpoint.caller, Some(destination.local))
184 {
185 let dest_place = PlaceKey {
186 base: crate::verify::def_use::PlaceBaseKey::Local(destination.local.as_usize()),
187 fields: Vec::new(),
188 };
189 let actual_ty = self.tcx.optimized_mir(result.checkpoint.caller)
190 .local_decls[destination.local].ty;
191 result.facts.push(StateFact::KnownAllocated {
192 place: dest_place.clone(),
193 object: dest_place.clone(),
194 ty_name: format!("{actual_ty:?}"),
195 elements,
196 reason: "live local allocation".to_string(),
197 });
198 }
199 }
200 TerminatorKind::SwitchInt { discr, .. } => {
201 if let Some(equals) = chosen_switch_value(&result.path, block, terminator) {
202 let value = value_from_operand(discr);
203 let (cmp_op, cmp_lhs, cmp_rhs) = find_cmp_source(block, discr, body);
204 result.facts.push(StateFact::BranchEq {
205 value: value.clone(),
206 equals,
207 cmp_op,
208 cmp_lhs,
209 cmp_rhs,
210 });
211 if let Some((place, align)) = align_guard_value(&value, equals, result) {
212 result.facts.push(StateFact::KnownAligned {
213 place,
214 align,
215 ty_name: format!("{align}-aligned"),
216 reason: format!("{align}-byte alignment guard on path"),
217 });
218 }
219 } else {
220 result
221 .facts
222 .push(StateFact::PathCondition(format!("{discr:?}")));
223 }
224 }
225 TerminatorKind::Assert { cond, expected, .. } => {
226 let (cmp_op, cmp_lhs, cmp_rhs) = find_cmp_source(block, cond, body);
227 result.facts.push(StateFact::BranchEq {
228 value: value_from_operand(cond),
229 equals: u128::from(*expected),
230 cmp_op,
231 cmp_lhs,
232 cmp_rhs,
233 });
234 }
235 TerminatorKind::Drop { place, .. } => {
236 result
237 .facts
238 .push(StateFact::Drop(PlaceKey::from_mir_place(place)));
239 }
240 TerminatorKind::Goto { .. }
241 | TerminatorKind::Return
242 | TerminatorKind::Unreachable
243 | TerminatorKind::UnwindResume
244 | TerminatorKind::UnwindTerminate(_)
245 | TerminatorKind::Yield { .. }
246 | TerminatorKind::CoroutineDrop
247 | TerminatorKind::FalseEdge { .. }
248 | TerminatorKind::FalseUnwind { .. }
249 | TerminatorKind::InlineAsm { .. }
250 | TerminatorKind::TailCall { .. } => {}
251 }
252 }
253
254 fn value_from_rvalue(&self, rvalue: &Rvalue<'tcx>) -> AbstractValue<'tcx> {
256 match rvalue {
257 Rvalue::Use(operand, ..) => value_from_operand(operand),
258 Rvalue::Repeat(operand, _) => {
259 AbstractValue::Repeat(Box::new(value_from_operand(operand)))
260 }
261 Rvalue::Ref(_, _, place) => AbstractValue::Ref(PlaceKey::from_mir_place(place)),
262 Rvalue::RawPtr(_, place) => AbstractValue::RawPtr(PlaceKey::from_mir_place(place)),
263 Rvalue::ThreadLocalRef(def_id) => AbstractValue::ThreadLocal(format!("{def_id:?}")),
264 Rvalue::Cast(_, operand, ty) => {
265 AbstractValue::Cast(Box::new(value_from_operand(operand)), *ty)
266 }
267 Rvalue::BinaryOp(op, pair) => {
268 let (lhs, rhs) = &**pair;
269 AbstractValue::Binary(
270 *op,
271 Box::new(value_from_operand(lhs)),
272 Box::new(value_from_operand(rhs)),
273 )
274 }
275 #[cfg(all(rapx_rustc_ge_193, not(rapx_rustc_ge_196)))]
276 Rvalue::NullaryOp(op) => AbstractValue::Nullary(format!("{op:?}")),
277 #[cfg(all(not(rapx_rustc_ge_193), not(rapx_rustc_ge_196)))]
278 Rvalue::NullaryOp(op, _) => AbstractValue::Nullary(format!("{op:?}")),
279 Rvalue::UnaryOp(op, operand) => {
280 AbstractValue::Unary(*op, Box::new(value_from_operand(operand)))
281 }
282 Rvalue::Discriminant(place) => {
283 AbstractValue::Discriminant(PlaceKey::from_mir_place(place))
284 }
285 Rvalue::Aggregate(kind, operands) => {
286 AbstractValue::Aggregate(aggregate_name(kind), operands.len())
287 }
288 #[cfg(not(rapx_rustc_ge_196))]
289 Rvalue::ShallowInitBox(operand, ty) => {
290 AbstractValue::ShallowInitBox(Box::new(value_from_operand(operand)), *ty)
291 }
292 Rvalue::CopyForDeref(place) => AbstractValue::Place(PlaceKey::from_mir_place(place)),
293 _ => AbstractValue::Unknown(format!("{rvalue:?}")),
294 }
295 }
296
297 fn record_rvalue_facts(
299 &self,
300 block: BasicBlock,
301 statement_index: usize,
302 place: &Place<'tcx>,
303 rvalue: &Rvalue<'tcx>,
304 body: &Body<'tcx>,
305 result: &mut ForwardVisitResult<'tcx>,
306 ) {
307 let target = PlaceKey::from_mir_place(place);
308 match rvalue {
309 Rvalue::Ref(_, _, source) => {
310 let source = PlaceKey::from_mir_place(source);
311 let object = allocation_object_for_source(&source, result);
312 result.facts.push(StateFact::PointsTo {
313 pointer: target.clone(),
314 source: source.clone(),
315 });
316 if let Some((ty_name, elements)) =
317 self.allocated_element_summary(result.checkpoint.caller, object.local())
318 {
319 result.facts.push(StateFact::KnownAllocated {
320 place: target,
321 object,
322 ty_name,
323 elements,
324 reason: "derived from live local/reference".to_string(),
325 });
326 }
327 }
328 Rvalue::RawPtr(_, source) => {
329 let source_key = PlaceKey::from_mir_place(source);
330 if let Some(alias) =
331 self.deref_pointer_value_for_place(result.checkpoint.caller, source)
332 {
333 result.record_value_definition(
334 block,
335 Some(statement_index),
336 place.local,
337 AbstractValue::Place(alias.clone()),
338 );
339 let ty = self.tcx.optimized_mir(result.checkpoint.caller).local_decls
340 [place.local]
341 .ty;
342 result.facts.push(StateFact::Cast {
343 target: target.clone(),
344 source: AbstractValue::Place(alias),
345 ty,
346 });
347 }
348 let object = self
349 .referenced_object_for_place(result.checkpoint.caller, source)
350 .unwrap_or_else(|| allocation_object_for_source(&source_key, result));
351 result.facts.push(StateFact::PointsTo {
352 pointer: target.clone(),
353 source: source_key,
354 });
355 result.facts.push(StateFact::KnownNonZero {
356 place: target.clone(),
357 reason: "created from reference".to_string(),
358 });
359 if let Some((ty_name, elements)) =
360 self.allocated_element_summary(result.checkpoint.caller, object.local())
361 {
362 result.facts.push(StateFact::KnownAllocated {
363 place: target,
364 object,
365 ty_name,
366 elements,
367 reason: "derived from live local/reference".to_string(),
368 });
369 }
370 }
371 Rvalue::Aggregate(kind, operands) => {
372 let is_decomposable =
373 matches!(kind.as_ref(), AggregateKind::Adt(..) | AggregateKind::Tuple);
374 if !is_decomposable {
375 return;
376 }
377 for (field_index, operand) in operands.iter().enumerate() {
378 let mut field_place = target.clone();
379 field_place.fields.push(field_index);
380 let source_val = value_from_operand(operand);
381 let op_ty = operand.ty(&body.local_decls, self.tcx);
382 result.facts.push(StateFact::Cast {
383 target: field_place,
384 source: source_val,
385 ty: op_ty,
386 });
387 }
388 }
389 Rvalue::Cast(_, operand, ty) => {
390 let source_val = value_from_operand(operand);
391 result.facts.push(StateFact::Cast {
392 target: target.clone(),
393 source: source_val.clone(),
394 ty: *ty,
395 });
396 if let Some(align) = known_alignment_of(&source_val, result) {
397 result.facts.push(StateFact::KnownAligned {
398 place: target.clone(),
399 align,
400 ty_name: format!("cast-{align}"),
401 reason: format!("cast preserves {align}-byte alignment"),
402 });
403 }
404 if let AbstractValue::Place(source_place) = &source_val
405 && let Some((ty_name, elements)) =
406 self.box_projection_allocation(result.checkpoint.caller, source_place, *ty)
407 {
408 result.facts.push(StateFact::KnownAllocated {
409 place: target,
410 object: source_place.clone(),
411 ty_name,
412 elements,
413 reason: "cast from Box-owned pointer field".to_string(),
414 });
415 }
416 }
417 #[cfg(not(rapx_rustc_ge_198))]
418 Rvalue::Use(operand) => {
419 let source_place = match operand {
420 Operand::Copy(place) | Operand::Move(place) => Some(place),
421 _ => None,
422 };
423 if let Some(source_place) = source_place {
424 let has_projection = source_place
425 .projection
426 .iter()
427 .any(|p| {
428 matches!(p,
429 rustc_middle::mir::ProjectionElem::Deref |
430 rustc_middle::mir::ProjectionElem::Field(..)
431 )
432 });
433 if !has_projection {
434 return;
435 }
436 let source_key = PlaceKey::from_mir_place(source_place);
437 let op_ty = operand.ty(&body.local_decls, self.tcx);
438 result.facts.push(StateFact::Cast {
439 target: target.clone(),
440 source: AbstractValue::Place(source_key),
441 ty: op_ty,
442 });
443 }
444 }
445 #[cfg(rapx_rustc_ge_198)]
446 Rvalue::Use(operand, _retag) => {
447 let source_place = match operand {
448 Operand::Copy(place) | Operand::Move(place) => Some(place),
449 _ => None,
450 };
451 if let Some(source_place) = source_place {
452 let has_projection = source_place
453 .projection
454 .iter()
455 .any(|p| {
456 matches!(p,
457 rustc_middle::mir::ProjectionElem::Deref |
458 rustc_middle::mir::ProjectionElem::Field(..)
459 )
460 });
461 if !has_projection {
462 return;
463 }
464 let source_key = PlaceKey::from_mir_place(source_place);
465 let op_ty = operand.ty(&body.local_decls, self.tcx);
466 result.facts.push(StateFact::Cast {
467 target: target.clone(),
468 source: AbstractValue::Place(source_key),
469 ty: op_ty,
470 });
471 }
472 }
473 Rvalue::CopyForDeref(place) => {
474 let source_place = PlaceKey::from_mir_place(place);
475 result.facts.push(StateFact::Cast {
476 target: target.clone(),
477 source: AbstractValue::Place(source_place),
478 ty: self.tcx.types.usize,
479 });
480 }
481 Rvalue::BinaryOp(op, pair) => {
482 let (lhs, rhs) = &**pair;
483 let lhs_val = value_from_operand(lhs);
484 let rhs_val = value_from_operand(rhs);
485 let target_key = target.clone();
486 result.facts.push(StateFact::Binary {
487 target: target_key.clone(),
488 op: *op,
489 lhs: lhs_val.clone(),
490 rhs: rhs_val.clone(),
491 });
492 if *op == BinOp::Mul || *op == BinOp::MulWithOverflow {
495 let rhs_resolved = resolve_value_chain(&rhs_val, result);
496 if let Some(divisor) = const_int_value(&rhs_resolved) {
497 if divisor > 0 && is_power_of_two(divisor) {
498 result.facts.push(StateFact::KnownAligned {
499 place: target_key.clone(),
500 align: divisor as u64,
501 ty_name: format!("result of mul by {divisor}"),
502 reason: format!("multiply by {divisor} (power of two)"),
503 });
504 }
505 }
506 }
507 if *op == BinOp::Add || *op == BinOp::AddWithOverflow {
508 if let Some(a) = known_alignment_of(&lhs_val, result).and_then(|a| {
509 known_alignment_of(&rhs_val, result)
510 .filter(|&b| b == a)
511 .map(|_| a)
512 }) {
513 result.facts.push(StateFact::KnownAligned {
514 place: target_key.clone(),
515 align: a,
516 ty_name: format!("sum of {a}-aligned"),
517 reason: "sum of two aligned values".into(),
518 });
519 }
520 }
521 if *op == BinOp::Div {
522 if let Some(src_align) = known_alignment_of(&lhs_val, result) {
523 let rhs_resolved = resolve_value_chain(&rhs_val, result);
524 if let Some(divisor) = const_int_value(&rhs_resolved) {
525 if divisor > 0 && src_align % divisor as u64 == 0 {
526 let new_align = src_align / divisor as u64;
527 result.facts.push(StateFact::KnownAligned {
528 place: target_key.clone(),
529 align: new_align,
530 ty_name: format!("result of div by {divisor}"),
531 reason: format!("dividing {src_align}-aligned by {divisor}"),
532 });
533 }
534 }
535 }
536 }
537 }
538 _ => {}
539 }
540 }
541
542 fn apply_call_effects(
544 &self,
545 block: BasicBlock,
546 summary: &CallEffectSummary,
547 args: &[Spanned<Operand<'tcx>>],
548 is_target_checkpoint: bool,
549 result: &mut ForwardVisitResult<'tcx>,
550 ) {
551 result.facts.push(StateFact::CallEffect(summary.clone()));
552 let Some(destination) = summary.destination else {
553 return;
554 };
555 let destination_place = PlaceKey {
556 base: super::def_use::PlaceBaseKey::Local(destination.as_usize()),
557 fields: Vec::new(),
558 };
559
560 for effect in &summary.effects {
561 match effect {
562 CallEffect::ReturnAliasArg { arg } | CallEffect::ReturnPointerFromArg { arg } => {
563 if let Some(source) = args.get(*arg).and_then(|arg| operand_place(&arg.node)) {
564 let object = allocation_object_for_source(&source, result);
565 result.facts.push(StateFact::PointsTo {
566 pointer: destination_place.clone(),
567 source: source.clone(),
568 });
569 if let Some((ty_name, elements)) =
570 self.allocated_element_summary(result.checkpoint.caller, object.local())
571 {
572 result.facts.push(StateFact::KnownAllocated {
573 place: destination_place.clone(),
574 object,
575 ty_name,
576 elements,
577 reason: format!("returned by {}", summary.name),
578 });
579 }
580 result.facts.push(StateFact::KnownNonZero {
581 place: destination_place.clone(),
582 reason: format!("returned by {}", summary.name),
583 });
584 }
585 }
586 CallEffect::ReturnPointerAdd { base_arg, .. }
587 | CallEffect::ReturnPointerSub { base_arg, .. } => {
588 if let Some(source) =
589 args.get(*base_arg).and_then(|arg| operand_place(&arg.node))
590 {
591 result.facts.push(StateFact::PointsTo {
592 pointer: destination_place.clone(),
593 source,
594 });
595 }
596 }
597 CallEffect::ReturnNonZero => result.facts.push(StateFact::KnownNonZero {
598 place: destination_place.clone(),
599 reason: format!("returned by {}", summary.name),
600 }),
601 CallEffect::ReturnAligned { align, ty_name } => {
602 result.facts.push(StateFact::KnownAligned {
603 place: destination_place.clone(),
604 align: *align,
605 ty_name: ty_name.clone(),
606 reason: format!("returned by {}", summary.name),
607 });
608 }
609 CallEffect::ReturnConst { value, label } => {
610 result.record_value_definition(
611 block,
612 None,
613 destination,
614 AbstractValue::ConstInt(u128::from(*value)),
615 );
616 result.facts.push(StateFact::KnownConst {
617 place: destination_place.clone(),
618 value: *value,
619 reason: label.clone(),
620 });
621 }
622 CallEffect::ReadMemory { .. } => {}
623 CallEffect::WriteMemory { pointer_arg } => {
624 if let Some(pointer) = args
625 .get(*pointer_arg)
626 .and_then(|arg| operand_place(&arg.node))
627 {
628 let mut init_places = copy_chain_places(&pointer, result);
629 if init_places.is_empty() {
630 init_places.push(pointer);
631 }
632
633 for place in init_places {
634 let ty_name =
635 self.init_write_ty_name(summary, result.checkpoint.caller, &place);
636 result.facts.push(StateFact::KnownInit {
637 place,
638 ty_name,
639 elements: 1,
640 reason: format!("written by {}", summary.name),
641 });
642 }
643 }
644 }
645 CallEffect::ReturnLengthOfArg { .. } => {}
646 CallEffect::ReturnTupleFieldLength { .. } => {}
647 CallEffect::ForgetArgFacts { reason, .. } => {
648 result.forgets.push(reason.clone());
649 }
650 }
651 }
652
653 if summary.unsupported && !is_target_checkpoint {
654 result.forgets.push(ForgetReason::UnknownCall);
655 result
656 .notes
657 .push(format!("unsupported call effect: {}", summary.name));
658 } else if summary.unsupported {
659 result.notes.push(format!(
660 "unsupported target call effect ignored for precondition proof: {}",
661 summary.name
662 ));
663 }
664 }
665
666 fn pointee_ty_name(&self, caller: DefId, place: &PlaceKey) -> Option<String> {
668 if !place.fields.is_empty() {
669 return None;
670 }
671 let local = place.local()?;
672 let ty = self.tcx.optimized_mir(caller).local_decls[local].ty;
673 match ty.kind() {
674 TyKind::RawPtr(ty, _) | TyKind::Ref(_, ty, _) => Some(format!("{ty:?}")),
675 _ => Some(format!("{ty:?}")),
676 }
677 }
678
679 fn init_write_ty_name(
680 &self,
681 summary: &CallEffectSummary,
682 caller: DefId,
683 place: &PlaceKey,
684 ) -> String {
685 let ty_name = self
686 .pointee_ty_name(caller, place)
687 .unwrap_or_else(|| "?".to_string());
688 if summary.name.contains("MaybeUninit") && summary.name.contains("write") {
689 maybe_uninit_inner_ty_name(&ty_name).unwrap_or(ty_name)
690 } else {
691 ty_name
692 }
693 }
694
695 fn allocated_element_summary(
696 &self,
697 caller: DefId,
698 local: Option<Local>,
699 ) -> Option<(String, u64)> {
700 let local = local?;
701 let ty = self.tcx.optimized_mir(caller).local_decls[local].ty;
702 fixed_allocation_elements(self.tcx, caller, ty)
703 }
704
705 fn referenced_object_for_place(&self, caller: DefId, place: &Place<'tcx>) -> Option<PlaceKey> {
706 if !place
707 .projection
708 .iter()
709 .any(|projection| matches!(projection, rustc_middle::mir::ProjectionElem::Deref))
710 {
711 return None;
712 }
713 let body = self.tcx.optimized_mir(caller);
714 for block in body.basic_blocks.iter() {
715 for statement in &block.statements {
716 let StatementKind::Assign(assign) = &statement.kind else {
717 continue;
718 };
719 let (dest, rvalue) = assign.as_ref();
720 if dest.local != place.local {
721 continue;
722 }
723 match rvalue {
724 Rvalue::Ref(_, _, source) | Rvalue::RawPtr(_, source) => {
725 return Some(PlaceKey::from_mir_place(source));
726 }
727 Rvalue::Use(Operand::Copy(source), ..)
728 | Rvalue::Use(Operand::Move(source), ..) => {
729 return Some(PlaceKey::from_mir_place(source));
730 }
731 _ => {}
732 }
733 }
734 }
735 None
736 }
737
738 fn deref_pointer_value_for_place(
739 &self,
740 caller: DefId,
741 place: &Place<'tcx>,
742 ) -> Option<PlaceKey> {
743 if !place
744 .projection
745 .iter()
746 .any(|projection| matches!(projection, rustc_middle::mir::ProjectionElem::Deref))
747 {
748 return None;
749 }
750
751 let body = self.tcx.optimized_mir(caller);
752 for block in body.basic_blocks.iter() {
753 for statement in &block.statements {
754 let StatementKind::Assign(assign) = &statement.kind else {
755 continue;
756 };
757 let (dest, rvalue) = assign.as_ref();
758 if dest.local != place.local {
759 continue;
760 }
761
762 let source = match rvalue {
763 Rvalue::Ref(_, _, source) | Rvalue::RawPtr(_, source) => source,
764 Rvalue::Use(Operand::Copy(source), ..)
765 | Rvalue::Use(Operand::Move(source), ..) => source,
766 _ => continue,
767 };
768 if source.projection.iter().any(|projection| {
769 matches!(projection, rustc_middle::mir::ProjectionElem::Deref)
770 }) {
771 return Some(PlaceKey::from_mir_place(source));
772 }
773 }
774 }
775
776 None
777 }
778
779 fn box_projection_allocation(
780 &self,
781 caller: DefId,
782 source_place: &PlaceKey,
783 cast_ty: Ty<'tcx>,
784 ) -> Option<(String, u64)> {
785 if source_place.fields.is_empty() {
786 return None;
787 }
788 let base = source_place.local()?;
789 let body = self.tcx.optimized_mir(caller);
790 let base_ty = body.local_decls[base].ty;
791 if !format!("{base_ty:?}").contains("Box<") {
792 return None;
793 }
794 let TyKind::RawPtr(pointee, _) = cast_ty.kind() else {
795 return None;
796 };
797 Some((format!("{pointee:?}"), 1))
798 }
799}
800
801#[derive(Clone, Debug)]
803pub struct ForwardVisitResult<'tcx> {
804 pub checkpoint: CheckpointLocation,
806 pub property: Property<'tcx>,
808 pub path: Path,
810 pub values: FxHashMap<Local, AbstractValue<'tcx>>,
812 pub value_definitions: Vec<ValueDefinition<'tcx>>,
814 pub facts: Vec<StateFact<'tcx>>,
816 pub forgets: Vec<ForgetReason>,
818 pub steps: Vec<ForwardStep>,
820 pub notes: Vec<String>,
822}
823
824impl<'tcx> ForwardVisitResult<'tcx> {
825 pub fn new(checkpoint: CheckpointLocation, property: Property<'tcx>, path: Path) -> Self {
827 Self {
828 checkpoint,
829 property,
830 path,
831 values: FxHashMap::default(),
832 value_definitions: Vec::new(),
833 facts: Vec::new(),
834 forgets: Vec::new(),
835 steps: Vec::new(),
836 notes: Vec::new(),
837 }
838 }
839
840 pub fn describe(&self) -> String {
842 let mut lines = Vec::new();
843 lines.push(" forward visit:".to_string());
844 lines.push(format!(
845 " |_ values: {}, facts: {}, precision loss: {}",
846 self.values.len(),
847 self.facts.len(),
848 self.forgets.len()
849 ));
850 if !self.facts.is_empty() {
851 lines.push(" |_ facts:".to_string());
852 for fact in &self.facts {
853 lines.push(format!(" | |_ {fact:?}"));
854 }
855 }
856 if !self.notes.is_empty() {
857 lines.push(" |_ unsupported:".to_string());
858 for note in &self.notes {
859 lines.push(format!(" | |_ {note}"));
860 }
861 }
862 lines.join("\n")
863 }
864
865 pub fn record_value_definition(
867 &mut self,
868 block: BasicBlock,
869 statement_index: Option<usize>,
870 local: Local,
871 value: AbstractValue<'tcx>,
872 ) {
873 let value = Self::fold_self_ref(value, local, &self.values);
874 let ordinal = self.value_definitions.len();
875 self.values.insert(local, value.clone());
876 self.value_definitions.push(ValueDefinition {
877 ordinal,
878 block,
879 statement_index,
880 local,
881 value,
882 });
883 }
884
885 fn fold_self_ref(
890 value: AbstractValue<'tcx>,
891 local: Local,
892 snapshot: &FxHashMap<Local, AbstractValue<'tcx>>,
893 ) -> AbstractValue<'tcx> {
894 let local_usize = local.as_usize();
895 match value {
896 AbstractValue::Place(ref p)
897 if p.base == PlaceBaseKey::Local(local_usize) =>
898 {
899 snapshot.get(&local).cloned().unwrap_or(value)
900 }
901 AbstractValue::Binary(op, lhs, rhs) => AbstractValue::Binary(
902 op,
903 Box::new(Self::fold_self_ref(*lhs, local, snapshot)),
904 Box::new(Self::fold_self_ref(*rhs, local, snapshot)),
905 ),
906 AbstractValue::Unary(op, inner) => AbstractValue::Unary(
907 op,
908 Box::new(Self::fold_self_ref(*inner, local, snapshot)),
909 ),
910 AbstractValue::Cast(inner, ty) => AbstractValue::Cast(
911 Box::new(Self::fold_self_ref(*inner, local, snapshot)),
912 ty,
913 ),
914 _ => value,
915 }
916 }
917
918 pub fn latest_value_definition_before(
920 &self,
921 local: Local,
922 cursor: usize,
923 ) -> Option<&ValueDefinition<'tcx>> {
924 let end = cursor.min(self.value_definitions.len());
925 self.value_definitions[..end]
926 .iter()
927 .rev()
928 .find(|definition| definition.local == local)
929 }
930}
931
932#[derive(Clone, Debug)]
934pub struct ValueDefinition<'tcx> {
935 pub ordinal: usize,
936 pub block: BasicBlock,
937 pub statement_index: Option<usize>,
938 pub local: Local,
939 pub value: AbstractValue<'tcx>,
940}
941
942#[derive(Clone, Debug)]
944pub enum ForwardStep {
945 Statement {
946 block: BasicBlock,
947 statement_index: usize,
948 reason: KeepReason,
949 },
950 Terminator {
951 block: BasicBlock,
952 reason: KeepReason,
953 },
954 PathStep {
955 step: PathStep,
956 reason: KeepReason,
957 },
958}
959
960#[derive(Clone, Debug)]
962pub enum AbstractValue<'tcx> {
963 Unknown(String),
964 Place(PlaceKey),
965 Ref(PlaceKey),
966 RawPtr(PlaceKey),
967 ThreadLocal(String),
968 ConstInt(u128),
969 Const(String),
970 ConstParam(String),
971 Repeat(Box<AbstractValue<'tcx>>),
972 Cast(Box<AbstractValue<'tcx>>, Ty<'tcx>),
973 Unary(UnOp, Box<AbstractValue<'tcx>>),
974 Binary(BinOp, Box<AbstractValue<'tcx>>, Box<AbstractValue<'tcx>>),
975 Nullary(String),
976 Discriminant(PlaceKey),
977 Aggregate(String, usize),
978 #[cfg(not(rapx_rustc_ge_196))]
979 ShallowInitBox(Box<AbstractValue<'tcx>>, Ty<'tcx>),
980 CallResult(CallSummary<'tcx>),
981}
982
983#[derive(Clone, Debug)]
985pub enum StateFact<'tcx> {
986 Contract(Property<'tcx>),
987 PointsTo {
988 pointer: PlaceKey,
989 source: PlaceKey,
990 },
991 Cast {
992 target: PlaceKey,
993 source: AbstractValue<'tcx>,
994 ty: Ty<'tcx>,
995 },
996 Binary {
997 target: PlaceKey,
998 op: BinOp,
999 lhs: AbstractValue<'tcx>,
1000 rhs: AbstractValue<'tcx>,
1001 },
1002 BranchEq {
1003 value: AbstractValue<'tcx>,
1004 equals: u128,
1005 cmp_op: Option<BinOp>,
1006 cmp_lhs: Option<AbstractValue<'tcx>>,
1007 cmp_rhs: Option<AbstractValue<'tcx>>,
1008 },
1009 PathCondition(String),
1010 Drop(PlaceKey),
1011 LocalDead(Local),
1012 Call(CallSummary<'tcx>),
1013 CallEffect(CallEffectSummary),
1014 KnownNonZero {
1015 place: PlaceKey,
1016 reason: String,
1017 },
1018 KnownAligned {
1019 place: PlaceKey,
1020 align: u64,
1021 ty_name: String,
1022 reason: String,
1023 },
1024 KnownInit {
1025 place: PlaceKey,
1026 ty_name: String,
1027 elements: u64,
1028 reason: String,
1029 },
1030 KnownAllocated {
1031 place: PlaceKey,
1032 object: PlaceKey,
1033 ty_name: String,
1034 elements: u64,
1035 reason: String,
1036 },
1037 KnownConst {
1038 place: PlaceKey,
1039 value: u64,
1040 reason: String,
1041 },
1042}
1043
1044#[derive(Clone, Debug)]
1046pub struct CallSummary<'tcx> {
1047 pub destination: Local,
1048 pub func: String,
1049 pub arg_count: usize,
1050 pub args: Vec<AbstractValue<'tcx>>,
1051 pub effects: Vec<CallEffect>,
1052 pub unsupported: bool,
1053}
1054
1055fn extract_const_param_name(text: &str) -> Option<String> {
1056 if let Some(start) = text.find("kind: Param(") {
1057 let rest = &text[start + "kind: Param(".len()..];
1058 if let Some(end) = rest.find(')') {
1059 let inner = &rest[..end];
1060 if let Some(name_start) = inner.find("name: ") {
1061 let name_part = &inner[name_start + "name: ".len()..];
1062 if let Some(name_end) = name_part.find(',') {
1063 return Some(name_part[..name_end].trim().to_string());
1064 }
1065 return Some(name_part.trim().to_string());
1066 }
1067 }
1068 }
1069 None
1070}
1071
1072fn value_from_operand<'tcx>(operand: &Operand<'tcx>) -> AbstractValue<'tcx> {
1074 match operand {
1075 Operand::Copy(place) | Operand::Move(place) => {
1076 AbstractValue::Place(PlaceKey::from_mir_place(place))
1077 }
1078 Operand::Constant(constant) => {
1079 let text = format!("{:?}", constant.const_);
1080 if let Some(name) = extract_const_param_name(&text) {
1081 return AbstractValue::ConstParam(name);
1082 }
1083 const_int_from_debug(&text)
1084 .map(AbstractValue::ConstInt)
1085 .unwrap_or(AbstractValue::Const(text))
1086 }
1087 #[cfg(rapx_rustc_ge_196)]
1088 Operand::RuntimeChecks(_) => AbstractValue::Unknown("RuntimeChecks".to_string()),
1089 }
1090}
1091
1092fn operand_place(operand: &Operand<'_>) -> Option<PlaceKey> {
1094 match operand {
1095 Operand::Copy(place) | Operand::Move(place) => Some(PlaceKey::from_mir_place(place)),
1096 Operand::Constant(_) => None,
1097 #[cfg(rapx_rustc_ge_196)]
1098 Operand::RuntimeChecks(_) => None,
1099 }
1100}
1101
1102fn const_int_from_debug(text: &str) -> Option<u128> {
1104 let text = text.trim();
1105 if text == "const true" {
1106 return Some(1);
1107 }
1108 if text == "const false" {
1109 return Some(0);
1110 }
1111 if let Some(rest) = text.strip_prefix("const ") {
1112 let digits = rest
1113 .chars()
1114 .take_while(|ch| ch.is_ascii_digit())
1115 .collect::<String>();
1116 if digits.is_empty() {
1117 return None;
1118 }
1119 return digits.parse().ok();
1120 }
1121
1122 let scalar = text.strip_prefix("Val(Scalar(0x")?;
1123 let digits = scalar
1124 .chars()
1125 .take_while(|ch| ch.is_ascii_hexdigit())
1126 .collect::<String>();
1127 if digits.is_empty() {
1128 None
1129 } else {
1130 u128::from_str_radix(&digits, 16).ok()
1131 }
1132}
1133
1134fn chosen_switch_value(
1136 path: &Path,
1137 block: BasicBlock,
1138 terminator: &Terminator<'_>,
1139) -> Option<u128> {
1140 let TerminatorKind::SwitchInt { targets, .. } = &terminator.kind else {
1141 return None;
1142 };
1143 let chosen = chosen_successor(path, block)?;
1144 let mut explicit_values = Vec::new();
1145 for (value, target) in targets.iter() {
1146 explicit_values.push(value);
1147 if target == chosen {
1148 return Some(value);
1149 }
1150 }
1151
1152 if targets.otherwise() == chosen && explicit_values.len() == 1 {
1153 return match explicit_values[0] {
1154 0 => Some(1),
1155 1 => Some(0),
1156 _ => None,
1157 };
1158 }
1159
1160 None
1161}
1162
1163fn chosen_successor(path: &Path, block: BasicBlock) -> Option<BasicBlock> {
1165 let mut previous = None;
1166 for step in path.steps.iter() {
1167 match step {
1168 PathStep::Block(current) => {
1169 if previous == Some(block) {
1170 return Some(*current);
1171 }
1172 previous = Some(*current);
1173 }
1174 PathStep::Checkpoint(_) => return None,
1175 }
1176 }
1177 None
1178}
1179
1180fn aggregate_name<'tcx>(kind: &AggregateKind<'tcx>) -> String {
1182 match kind {
1183 AggregateKind::Array(_) => "array".to_string(),
1184 AggregateKind::Tuple => "tuple".to_string(),
1185 AggregateKind::Adt(def_id, ..) => format!("adt({def_id:?})"),
1186 AggregateKind::Closure(def_id, _) => format!("closure({def_id:?})"),
1187 AggregateKind::Coroutine(def_id, _) => format!("coroutine({def_id:?})"),
1188 AggregateKind::CoroutineClosure(def_id, _) => format!("coroutine_closure({def_id:?})"),
1189 AggregateKind::RawPtr(_, _) => "raw_ptr".to_string(),
1190 }
1191}
1192
1193fn const_int_value(val: &AbstractValue<'_>) -> Option<u128> {
1194 match val {
1195 AbstractValue::ConstInt(v) => Some(*v),
1196 _ => None,
1197 }
1198}
1199
1200fn is_power_of_two(n: u128) -> bool {
1201 n > 0 && (n & (n - 1)) == 0
1202}
1203
1204fn is_const_zero(val: &AbstractValue<'_>) -> bool {
1205 matches!(val, AbstractValue::ConstInt(0))
1206}
1207
1208fn align_guard_value<'tcx>(
1209 value: &AbstractValue<'tcx>,
1210 equals: u128,
1211 result: &ForwardVisitResult<'tcx>,
1212) -> Option<(PlaceKey, u64)> {
1213 let resolved = resolve_value_chain(value, result);
1214 if equals == 0 {
1215 match &resolved {
1217 AbstractValue::Binary(BinOp::Rem, rem_l, rem_r) => {
1218 let d = const_int_value(rem_r)?;
1219 if d > 0 && is_power_of_two(d) {
1220 let place = match resolve_value_chain(rem_l, result) {
1221 AbstractValue::Place(p) => p,
1222 AbstractValue::Cast(inner, _) => match inner.as_ref() {
1223 AbstractValue::Place(p) => p.clone(),
1224 _ => return None,
1225 },
1226 _ => return None,
1227 };
1228 return Some((place, d as u64));
1229 }
1230 }
1231 _ => {}
1232 }
1233 }
1234 if equals == 1 {
1235 let AbstractValue::Binary(BinOp::Eq, eq_l, eq_r) = &resolved else {
1237 return None;
1238 };
1239 if !is_const_zero(eq_r) {
1240 return None;
1241 }
1242 let eq_resolved = resolve_value_chain(eq_l, result);
1243 let AbstractValue::Binary(BinOp::Rem, rem_l, rem_r) = &eq_resolved else {
1244 return None;
1245 };
1246 let d = const_int_value(rem_r)?;
1247 if d == 0 || !is_power_of_two(d) {
1248 return None;
1249 }
1250 let place = match resolve_value_chain(rem_l, result) {
1251 AbstractValue::Place(p) => p,
1252 AbstractValue::Cast(inner, _) => match inner.as_ref() {
1253 AbstractValue::Place(p) => p.clone(),
1254 _ => return None,
1255 },
1256 _ => return None,
1257 };
1258 return Some((place, d as u64));
1259 }
1260 None
1261}
1262
1263fn resolve_value_chain<'tcx>(
1264 value: &AbstractValue<'tcx>,
1265 result: &ForwardVisitResult<'tcx>,
1266) -> AbstractValue<'tcx> {
1267 let mut cur = value.clone();
1268 let mut seen = HashSet::new();
1269 loop {
1270 if !seen.insert(format!("{cur:?}")) {
1271 return cur;
1272 }
1273 cur = match &cur {
1274 AbstractValue::Place(p) => {
1275 if let PlaceBaseKey::Local(ix) = &p.base {
1276 match result.values.get(&Local::from_usize(*ix)) {
1277 Some(v) => v.clone(),
1278 None => return cur,
1279 }
1280 } else {
1281 return cur;
1282 }
1283 }
1284 _ => return cur,
1285 };
1286 }
1287}
1288
1289fn copy_chain_places<'tcx>(place: &PlaceKey, result: &ForwardVisitResult<'tcx>) -> Vec<PlaceKey> {
1290 let mut places = Vec::new();
1291 let mut cur = place.clone();
1292 let mut seen = HashSet::new();
1293 loop {
1294 if !seen.insert(cur.clone()) {
1295 break;
1296 }
1297 if !places.contains(&cur) {
1298 places.push(cur.clone());
1299 }
1300
1301 let Some(local) = cur.local() else {
1302 break;
1303 };
1304 let Some(value) = result.values.get(&local) else {
1305 break;
1306 };
1307 match value {
1308 AbstractValue::Place(next) => cur = next.clone(),
1309 AbstractValue::Cast(inner, _) => match inner.as_ref() {
1310 AbstractValue::Place(next) => cur = next.clone(),
1311 _ => break,
1312 },
1313 _ => break,
1314 }
1315 }
1316 places
1317}
1318
1319fn allocation_object_for_source<'tcx>(
1320 source: &PlaceKey,
1321 result: &ForwardVisitResult<'tcx>,
1322) -> PlaceKey {
1323 let mut cur = source.clone();
1324 let mut seen = HashSet::new();
1325 loop {
1326 if !seen.insert(cur.clone()) {
1327 return cur;
1328 }
1329 if let Some(local) = cur.local()
1330 && let Some(value) = result.values.get(&local)
1331 {
1332 match value {
1333 AbstractValue::Place(next)
1334 | AbstractValue::Ref(next)
1335 | AbstractValue::RawPtr(next) => {
1336 cur = next.clone();
1337 continue;
1338 }
1339 AbstractValue::Cast(inner, _) => {
1340 if let AbstractValue::Place(next)
1341 | AbstractValue::Ref(next)
1342 | AbstractValue::RawPtr(next) = inner.as_ref()
1343 {
1344 cur = next.clone();
1345 continue;
1346 }
1347 }
1348 _ => {}
1349 }
1350 }
1351 let Some(next) = result.facts.iter().find_map(|fact| match fact {
1352 StateFact::PointsTo { pointer, source } if pointer == &cur => Some(source.clone()),
1353 _ => None,
1354 }) else {
1355 return cur;
1356 };
1357 cur = next;
1358 }
1359}
1360
1361fn fixed_allocation_elements<'tcx>(
1362 tcx: TyCtxt<'tcx>,
1363 caller: DefId,
1364 ty: Ty<'tcx>,
1365) -> Option<(String, u64)> {
1366 match ty.kind() {
1367 TyKind::Array(elem, len) => {
1368 let value = len.try_to_target_usize(tcx)?;
1369 Some((format!("{elem:?}"), value))
1370 }
1371 TyKind::Ref(_, inner, _) => fixed_allocation_elements(tcx, caller, *inner),
1372 TyKind::Slice(elem) => Some((format!("{elem:?}"), 0)),
1373 TyKind::RawPtr(_, _) => None,
1374 TyKind::Adt(_, args) => {
1375 let ty_name = format!("{ty:?}");
1376 if ty_name.contains("MaybeUninit") {
1377 if let Some(first) = args.first()
1378 && let Some(inner_ty) = first.as_type()
1379 && let TyKind::Array(elem, len) = inner_ty.kind()
1380 {
1381 let value = len.try_to_target_usize(tcx).unwrap_or(0);
1386 return Some((format!("{elem:?}"), value));
1387 }
1388 return Some((ty_name, 1));
1389 }
1390 Some((ty_name, 1))
1391 }
1392 _ => Some((format!("{ty:?}"), 1)),
1393 }
1394}
1395
1396fn maybe_uninit_inner_ty_name(ty_name: &str) -> Option<String> {
1397 let ty_name = ty_name.trim();
1398 for prefix in [
1399 "std::mem::MaybeUninit<",
1400 "core::mem::MaybeUninit<",
1401 "MaybeUninit<",
1402 ] {
1403 if let Some(inner) = ty_name
1404 .strip_prefix(prefix)
1405 .and_then(|s| s.strip_suffix('>'))
1406 {
1407 return Some(inner.trim().to_string());
1408 }
1409 }
1410 None
1411}
1412
1413fn known_alignment_of<'tcx>(
1414 value: &AbstractValue<'tcx>,
1415 result: &ForwardVisitResult<'tcx>,
1416) -> Option<u64> {
1417 let mut best: Option<u64> = None;
1418 let mut cur = value.clone();
1419 let mut seen = HashSet::new();
1420 loop {
1421 if !seen.insert(format!("{cur:?}")) {
1422 break;
1423 }
1424 if let AbstractValue::Place(ref p) = cur {
1425 for f in &result.facts {
1426 if let StateFact::KnownAligned { place, align, .. } = f {
1427 if place == p {
1428 best = best.map_or(Some(*align), |b| Some(b.max(*align)));
1429 }
1430 if place.fields.is_empty() != p.fields.is_empty() && place.base == p.base {
1431 best = best.map_or(Some(*align), |b| Some(b.max(*align)));
1432 }
1433 }
1434 }
1435 }
1436 cur = match &cur {
1437 AbstractValue::Place(p) => {
1438 if let PlaceBaseKey::Local(ix) = &p.base {
1439 match result.values.get(&Local::from_usize(*ix)) {
1440 Some(v) => v.clone(),
1441 None => break,
1442 }
1443 } else {
1444 break;
1445 }
1446 }
1447 AbstractValue::Cast(inner, _) => (**inner).clone(),
1448 _ => break,
1449 };
1450 }
1451 best
1452}
1453
1454fn find_cmp_source<'tcx>(
1455 block: BasicBlock,
1456 cond: &Operand<'tcx>,
1457 body: &Body<'tcx>,
1458) -> (
1459 Option<BinOp>,
1460 Option<AbstractValue<'tcx>>,
1461 Option<AbstractValue<'tcx>>,
1462) {
1463 let place = match cond {
1464 Operand::Copy(place) | Operand::Move(place) => place,
1465 _ => return (None, None, None),
1466 };
1467 let bb = &body.basic_blocks[block];
1468 for stmt in &bb.statements {
1469 let StatementKind::Assign(assign) = &stmt.kind else { continue };
1470 if assign.0.local != place.local { continue };
1471 let Rvalue::BinaryOp(op, pair) = &assign.1 else { continue };
1472 if !matches!(op, BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge | BinOp::Eq | BinOp::Ne) { continue; }
1473 return (Some(*op), Some(value_from_operand(&pair.0)), Some(value_from_operand(&pair.1)));
1474 }
1475 (None, None, None)
1476}