1use rustc_hir::def_id::DefId;
8use rustc_middle::{
9 mir::{BasicBlock, Body, Local, Operand, Place, ProjectionElem},
10 ty::{Ty, TyCtxt},
11};
12use z3::{
13 Context,
14 ast::{Ast, Bool, Int},
15};
16
17use crate::compat::{FxHashMap, FxHashSet};
18use crate::verify::{
19 def_use::PlaceKey,
20 path_extractor::Path,
21};
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
25pub struct AllocId(pub usize);
26
27#[derive(Clone, Debug)]
29pub struct Provenance<'ctx> {
30 pub alloc_id: AllocId,
32 pub offset: Int<'ctx>,
35 pub is_field_offset: bool,
40}
41
42#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
44pub struct ValueInvariants {
45 pub non_null: bool,
46 pub aligned: bool,
47 pub init: bool,
48 pub in_bounds: bool,
49 pub align_n: Option<u64>,
52 pub is_field_offset: bool,
56}
57
58#[derive(Clone, Debug)]
72pub struct VmValue<'ctx, 'tcx> {
73 pub term: Int<'ctx>,
75 pub ty: Ty<'tcx>,
77 pub provenance: Option<Provenance<'ctx>>,
79 pub invariants: ValueInvariants,
81}
82
83impl<'ctx, 'tcx> VmValue<'ctx, 'tcx> {
84 pub fn new(term: Int<'ctx>, ty: Ty<'tcx>) -> Self {
85 VmValue { term, ty, provenance: None, invariants: ValueInvariants::default() }
86 }
87
88 pub fn provenance_alloc_id(&self) -> Option<AllocId> {
90 self.provenance.as_ref().map(|p| p.alloc_id)
91 }
92}
93
94#[derive(Clone, Debug)]
99pub struct Allocation<'ctx, 'tcx> {
100 pub base: Int<'ctx>,
102
103 pub size: Int<'ctx>,
105
106 pub align: u64,
108
109 pub element_ty: Option<Ty<'tcx>>,
111
112 pub is_external: bool,
115
116 pub dead: bool,
118
119 pub initialized: bool,
121
122 pub alive_assumed: bool,
124
125 pub nul_terminated: bool,
128
129 pub parent: Option<AllocId>,
131
132 pub slice_data: Option<AllocId>,
135}
136
137#[derive(Clone, Copy, Debug, Default)]
139pub(crate) struct ContractFlags {
140 pub split_transmute_asserted: bool,
142 pub alias_hazard_accepted: bool,
144 pub has_checked_bounds: bool,
147 pub saw_next_discriminant: bool,
150}
151
152#[derive(Clone, Debug, Default)]
154pub(crate) struct ByteInfo<'ctx> {
155 pub value: Option<Int<'ctx>>,
157 pub init: bool,
159 pub nul: Option<bool>,
161}
162
163pub(crate) struct InlineFrame<'ctx, 'tcx> {
168 pub body: &'ctx Body<'tcx>,
169 pub def_id: DefId,
170 pub saved_locals: FxHashMap<Local, VmValue<'ctx, 'tcx>>,
171}
172
173pub struct VmState<'ctx, 'tcx> {
179 pub(crate) ctx: &'ctx Context,
181
182 pub(crate) tcx: TyCtxt<'tcx>,
184
185 pub(crate) caller_def_id: DefId,
187
188 pub(crate) body: &'ctx Body<'tcx>,
190
191 pub(crate) locals: FxHashMap<Local, VmValue<'ctx, 'tcx>>,
193
194 pub(crate) local_addresses: FxHashMap<Local, Int<'ctx>>,
196
197 pub(crate) local_alloc_ids: FxHashMap<Local, AllocId>,
199
200 pub(crate) allocations: Vec<Allocation<'ctx, 'tcx>>,
202
203 pub(crate) path_conditions: Vec<Bool<'ctx>>,
205
206 pub(crate) definition_count: usize,
208
209 pub(crate) next_alloc_id: usize,
211
212 pub(crate) block_occurrences: FxHashMap<BasicBlock, usize>,
214
215 pub(crate) binary_op_sources: FxHashMap<PlaceKey, (Option<PlaceKey>, Option<PlaceKey>)>,
217
218 pub(crate) comparison_conds: FxHashMap<PlaceKey, Bool<'ctx>>,
221
222 pub(crate) discriminant_terms: FxHashMap<Local, Int<'ctx>>,
227
228 pub(crate) other_op_sources: FxHashMap<PlaceKey, (Option<PlaceKey>, Option<PlaceKey>)>,
232
233 pub(crate) contract_flags: ContractFlags,
235
236 pub(crate) field_values: FxHashMap<(Local, Vec<usize>), VmValue<'ctx, 'tcx>>,
239
240 pub(crate) is_empty_len: FxHashMap<Local, Int<'ctx>>,
245
246 pub(crate) iter_ptr_offset: FxHashMap<Local, Int<'ctx>>,
251
252 pub(crate) bytes: FxHashMap<(AllocId, usize), ByteInfo<'ctx>>,
256
257 pub(crate) notes: Vec<String>,
259
260 pub(crate) path: Option<Path>,
262
263 pub(crate) last_call_name: String,
265
266 pub(crate) inline_depth: usize,
271
272 pub(crate) inline_frames: Vec<InlineFrame<'ctx, 'tcx>>,
277
278 pub(crate) not_mask_terms: FxHashSet<Int<'ctx>>,
282}
283
284impl<'ctx, 'tcx> VmState<'ctx, 'tcx> {
285 pub fn new(
287 ctx: &'ctx Context,
288 tcx: TyCtxt<'tcx>,
289 body: &'ctx Body<'tcx>,
290 caller_def_id: DefId,
291 ) -> Self {
292 Self {
293 ctx,
294 tcx,
295 body,
296 caller_def_id,
297 locals: FxHashMap::default(),
298 local_addresses: FxHashMap::default(),
299 local_alloc_ids: FxHashMap::default(),
300 allocations: Vec::new(),
301 path_conditions: Vec::new(),
302 definition_count: 0,
303 next_alloc_id: 0,
304 block_occurrences: FxHashMap::default(),
305 binary_op_sources: FxHashMap::default(),
306 comparison_conds: FxHashMap::default(),
307 discriminant_terms: FxHashMap::default(),
308 other_op_sources: FxHashMap::default(),
309 contract_flags: ContractFlags::default(),
310 field_values: FxHashMap::default(),
311 is_empty_len: FxHashMap::default(),
312 iter_ptr_offset: FxHashMap::default(),
313 bytes: FxHashMap::default(),
314 notes: Vec::new(),
315 path: None,
316 last_call_name: String::new(),
317 inline_depth: 0,
318 inline_frames: Vec::new(),
319 not_mask_terms: FxHashSet::default(),
320 }
321 }
322
323 pub fn local_value(&self, local: Local) -> Option<&VmValue<'ctx, 'tcx>> {
325 self.locals.get(&local)
326 }
327
328 pub fn set_local(&mut self, local: Local, value: VmValue<'ctx, 'tcx>) {
330 self.locals.insert(local, value);
331 }
332
333 pub fn local_address(&mut self, local: Local) -> Int<'ctx> {
335 if let Some(addr) = self.local_addresses.get(&local) {
336 return addr.clone();
337 }
338 let name = format!("addr__{}", local.as_usize());
339 let addr = Int::new_const(self.ctx, name.as_str());
340 self.local_addresses.insert(local, addr.clone());
341 addr
342 }
343
344 pub fn allocate(
346 &mut self,
347 size: Int<'ctx>,
348 align: u64,
349 element_ty: Option<Ty<'tcx>>,
350 ) -> (AllocId, Int<'ctx>) {
351 let id = AllocId(self.next_alloc_id);
352 self.next_alloc_id += 1;
353 let base = {
354 let name = format!("heap_{}", id.0);
355 Int::new_const(self.ctx, name.as_str())
356 };
357 let alloc = Allocation {
358 base: base.clone(),
359 size,
360 align,
361 element_ty,
362 is_external: false,
363 dead: false,
364 initialized: false,
365 alive_assumed: false,
366 nul_terminated: false,
367 parent: None,
368 slice_data: None,
369 };
370 self.allocations.push(alloc);
371 (id, base)
372 }
373
374 pub fn allocate_external(
377 &mut self,
378 size: Int<'ctx>,
379 align: u64,
380 element_ty: Option<Ty<'tcx>>,
381 ) -> (AllocId, Int<'ctx>) {
382 let id = AllocId(self.next_alloc_id);
383 self.next_alloc_id += 1;
384 let base = {
385 let name = format!("ext_{}", id.0);
386 Int::new_const(self.ctx, name.as_str())
387 };
388 let alloc = Allocation {
389 base: base.clone(),
390 size,
391 align,
392 element_ty,
393 is_external: true,
394 dead: false,
395 initialized: false,
396 alive_assumed: false,
397 nul_terminated: false,
398 parent: None,
399 slice_data: None,
400 };
401 self.allocations.push(alloc);
402 (id, base)
403 }
404
405 pub(crate) fn alloc(&self, id: AllocId) -> &Allocation<'ctx, 'tcx> {
407 &self.allocations[id.0]
408 }
409
410 pub(crate) fn alloc_mut(&mut self, id: AllocId) -> &mut Allocation<'ctx, 'tcx> {
412 &mut self.allocations[id.0]
413 }
414
415 pub fn fresh_int(&self, prefix: &str) -> Int<'ctx> {
417 let name = format!("{}_{}", prefix, self.definition_count);
418 Int::new_const(self.ctx, name.as_str())
419 }
420
421 pub fn record_definition(&mut self) {
423 self.definition_count += 1;
424 }
425
426 pub fn field_value(&self, local: Local, path: &[usize]) -> Option<&VmValue<'ctx, 'tcx>> {
428 self.field_values.get(&(local, path.to_vec()))
429 }
430
431 pub fn set_field_value(&mut self, local: Local, path: Vec<usize>, value: VmValue<'ctx, 'tcx>) {
433 self.field_values.insert((local, path), value);
434 }
435
436 pub fn record_byte_value(&mut self, alloc_id: AllocId, offset: usize, term: Int<'ctx>) {
438 let byte = self.bytes.entry((alloc_id, offset)).or_default();
439 byte.value = Some(term);
440 byte.init = true;
441 }
442
443 pub fn mark_byte_init(&mut self, alloc_id: AllocId, offset: usize) {
445 self.bytes.entry((alloc_id, offset)).or_default().init = true;
446 }
447
448 pub fn mark_byte_nul(&mut self, alloc_id: AllocId, offset: usize) {
450 self.bytes.entry((alloc_id, offset)).or_default().nul = Some(true);
451 }
452
453 pub fn mark_byte_non_nul(&mut self, alloc_id: AllocId, offset: usize) {
455 self.bytes.entry((alloc_id, offset)).or_default().nul = Some(false);
456 }
457
458 pub fn get_byte_value(&self, alloc_id: AllocId, offset: usize) -> Option<&Int<'ctx>> {
460 self.bytes.get(&(alloc_id, offset)).and_then(|b| b.value.as_ref())
461 }
462
463 pub fn is_byte_init(&self, alloc_id: AllocId, offset: usize) -> bool {
465 self.bytes.get(&(alloc_id, offset)).is_some_and(|b| b.init)
466 }
467
468 pub fn is_byte_nul(&self, alloc_id: AllocId, offset: usize) -> bool {
470 self.bytes.get(&(alloc_id, offset)).is_some_and(|b| b.nul == Some(true))
471 }
472
473 pub fn is_byte_non_nul(&self, alloc_id: AllocId, offset: usize) -> bool {
475 self.bytes.get(&(alloc_id, offset)).is_some_and(|b| b.nul == Some(false))
476 }
477
478 pub fn alloc_byte_values(&self, alloc_id: AllocId) -> Vec<(usize, &Int<'ctx>)> {
480 let mut pairs: Vec<_> = self
481 .bytes
482 .iter()
483 .filter_map(|((aid, off), byte)| {
484 if *aid == alloc_id { byte.value.as_ref().map(|term| (*off, term)) } else { None }
485 })
486 .collect();
487 pairs.sort_by_key(|(off, _)| *off);
488 pairs
489 }
490
491 pub fn alloc_nul_offsets(&self, alloc_id: AllocId) -> Vec<usize> {
493 self.bytes.iter()
494 .filter_map(|((aid, off), byte)| {
495 if *aid == alloc_id && byte.nul == Some(true) { Some(*off) } else { None }
496 })
497 .collect()
498 }
499
500 pub fn alloc_non_nul_offsets(&self, alloc_id: AllocId) -> Vec<usize> {
502 self.bytes.iter()
503 .filter_map(|((aid, off), byte)| {
504 if *aid == alloc_id && byte.nul == Some(false) { Some(*off) } else { None }
505 })
506 .collect()
507 }
508
509 pub(crate) fn copy_byte_tracking(&mut self, src: AllocId, dst: AllocId) {
512 let infos: Vec<(usize, ByteInfo<'ctx>)> = self.bytes.iter()
513 .filter(|((aid, _), _)| *aid == src)
514 .map(|((_, off), byte)| (*off, byte.clone()))
515 .collect();
516 for (off, byte) in infos {
517 self.bytes.insert((dst, off), byte);
518 }
519 }
520
521 pub fn assert_all(&self, solver: &z3::Solver<'ctx>) {
523 for cond in &self.path_conditions {
524 solver.assert(cond);
525 }
526 let zero = Int::from_u64(self.ctx, 0);
527 for alloc in &self.allocations {
528 if !alloc.is_external {
529 solver.assert(&alloc.base._eq(&zero).not());
530 }
531 solver.assert(&alloc.size.ge(&zero));
532 if alloc.align > 1 {
533 let align_term = Int::from_u64(self.ctx, alloc.align);
534 solver.assert(&alloc.base.rem(&align_term)._eq(&zero));
535 }
536 }
537
538 for (_local, value) in self.locals.iter() {
539 if value.invariants.non_null {
540 solver.assert(&value.term._eq(&zero).not());
541 }
542 if let Some(ref prov) = value.provenance {
543 let alloc = self.alloc(prov.alloc_id);
544 let expected = Int::add(self.ctx, &[&alloc.base, &prov.offset]);
545 solver.assert(&value.term._eq(&expected));
546 }
547 if matches!(value.ty.kind(),
548 rustc_middle::ty::TyKind::Uint(_)
549 | rustc_middle::ty::TyKind::Bool
550 | rustc_middle::ty::TyKind::Char
551 ) {
552 solver.assert(&value.term.ge(&zero));
553 }
554 if matches!(value.ty.kind(), rustc_middle::ty::TyKind::Bool) {
555 let one = Int::from_u64(self.ctx, 1);
556 solver.assert(&value.term.le(&one));
557 }
558 if matches!(value.ty.kind(), rustc_middle::ty::TyKind::Char) {
559 let max = Int::from_u64(self.ctx, 0x10FFFF);
560 solver.assert(&value.term.le(&max));
561 }
562 }
563 for value in self.field_values.values() {
564 if value.invariants.non_null {
565 solver.assert(&value.term._eq(&zero).not());
566 }
567 if let Some(ref prov) = value.provenance {
568 let alloc = self.alloc(prov.alloc_id);
569 let expected = Int::add(self.ctx, &[&alloc.base, &prov.offset]);
570 solver.assert(&value.term._eq(&expected));
571 }
572 if matches!(value.ty.kind(),
573 rustc_middle::ty::TyKind::Uint(_)
574 | rustc_middle::ty::TyKind::Bool
575 | rustc_middle::ty::TyKind::Char
576 ) {
577 solver.assert(&value.term.ge(&zero));
578 }
579 if matches!(value.ty.kind(), rustc_middle::ty::TyKind::Bool) {
580 let one = Int::from_u64(self.ctx, 1);
581 solver.assert(&value.term.le(&one));
582 }
583 if matches!(value.ty.kind(), rustc_middle::ty::TyKind::Char) {
584 let max = Int::from_u64(self.ctx, 0x10FFFF);
585 solver.assert(&value.term.le(&max));
586 }
587 }
588 }
589}
590
591impl std::fmt::Debug for VmState<'_, '_> {
592 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
593 f.debug_struct("VmState")
594 .field("locals_count", &self.locals.len())
595 .field("allocations_count", &self.allocations.len())
596 .field("path_conditions", &self.path_conditions.len())
597 .field("definitions", &self.definition_count)
598 .field("notes", &self.notes)
599 .finish()
600 }
601}
602
603impl<'ctx, 'tcx> VmState<'ctx, 'tcx> {
606 pub(crate) fn value_of_operand(&self, operand: &Operand<'tcx>) -> VmValue<'ctx, 'tcx> {
608 match operand {
609 Operand::Copy(place) | Operand::Move(place) => {
610 self.value_of_place(place)
611 .unwrap_or_else(|| self.unknown_value_for_place(place))
612 }
613 Operand::Constant(constant) => {
614 let text = format!("{:?}", constant.const_);
615 let int_val = crate::helpers::mir_utils::const_scalar_int(self.tcx, &constant.const_, &text);
616 let is_field_offset = int_val.is_none()
617 && crate::helpers::mir_utils::offset_of_container(self.tcx, &constant.const_)
618 .is_some();
619 let term = if let Some(v) = int_val {
620 if v < 0 {
621 Int::from_i64(self.ctx, v as i64)
622 } else {
623 Int::from_u64(self.ctx, v as u64)
624 }
625 } else {
626 let name = format!("const_{}", text.replace([':', '#', ' '], "_"));
629 Int::new_const(self.ctx, name.as_str())
630 };
631 let ty = constant.const_.ty();
632 VmValue {
633 term,
634 ty,
635 provenance: None,
636 invariants: ValueInvariants {
637 is_field_offset,
638 ..ValueInvariants::default()
639 },
640 }
641 }
642 #[cfg(rapx_ge_99)]
643 Operand::RuntimeChecks(_) => {
644 VmValue::new(self.fresh_int("runtime_checks"), self.body.local_decls[Local::from_usize(0)].ty)
645 }
646 }
647 }
648
649 pub(crate) fn value_of_place(&self, place: &Place<'tcx>) -> Option<VmValue<'ctx, 'tcx>> {
651 if place.projection.is_empty() {
652 return self.locals.get(&place.local).cloned();
653 }
654
655 let field_path: Vec<usize> = place.projection.iter()
657 .filter_map(|proj| match proj.kind() {
658 ProjectionElem::Field(field_idx, _) => Some(field_idx.as_usize()),
659 _ => None,
660 })
661 .collect();
662
663 if !field_path.is_empty() && field_path.len() == place.projection.len() {
666 if let Some(val) = self.field_values.get(&(place.local, field_path)).cloned() {
667 return Some(val);
668 }
669 if let Some(base_val) = self.locals.get(&place.local) {
674 if let Some(ref prov) = base_val.provenance {
675 return Some(VmValue {
676 term: base_val.term.clone(),
677 ty: place.ty(self.body, self.tcx).ty,
678 provenance: Some(prov.clone()),
679 invariants: base_val.invariants,
680 });
681 }
682 }
683 return None;
684 }
685
686 if !field_path.is_empty() && field_path.len() < place.projection.len()
689 && place.projection.iter().any(|p| matches!(p.kind(), ProjectionElem::Deref))
690 {
691 let non_field_deref = place.projection.iter()
693 .all(|p| matches!(p.kind(), ProjectionElem::Field(..) | ProjectionElem::Deref));
694 if non_field_deref {
695 let fp: Vec<usize> = place.projection.iter()
697 .filter_map(|proj| match proj.kind() {
698 ProjectionElem::Field(field_idx, _) => Some(field_idx.as_usize()),
699 _ => None,
700 })
701 .collect();
702 if let Some(val) = self.field_values.get(&(place.local, fp)).cloned() {
703 return Some(val);
704 }
705 }
706 }
707
708 let mut base = self.locals.get(&place.local)?.clone();
712 for proj in place.projection.iter() {
713 match proj.kind() {
714 ProjectionElem::Deref => {
715 let _ = &base.provenance; base.ty = place.ty(self.body, self.tcx).ty;
717 }
718 ProjectionElem::Field(_field_idx, _) => {
719 let field_indices: Vec<usize> = place.projection.iter()
721 .filter_map(|p| match p.kind() {
722 ProjectionElem::Field(fi, _) => Some(fi.as_usize()),
723 _ => None,
724 })
725 .collect();
726 if !field_indices.is_empty() {
727 if let Some(val) = self.field_values.get(&(place.local, field_indices)).cloned() {
728 return Some(val);
729 }
730 }
731 base.ty = place.ty(self.body, self.tcx).ty;
733 }
734 _ => {}
735 }
736 }
737
738 if place.projection.len() == 1 {
740 if let Some(proj) = place.projection.first() {
741 match proj {
742 ProjectionElem::Index(local) => {
743 if let Some(ref prov) = base.provenance {
744 let alloc_id = prov.alloc_id;
745 let byte_vals: Vec<_> = self.alloc_byte_values(alloc_id);
746 if !byte_vals.is_empty() {
747 let inner_ty = match base.ty.kind() {
748 rustc_middle::ty::TyKind::Array(inner, _) => *inner,
749 _ => return Some(base.clone()),
750 };
751 let elem_sz = self.size_of_ty(inner_ty) as usize;
752 let step = elem_sz.max(1);
753 if let Some(index_val) = self.locals.get(local) {
754 if let Some(concrete_idx) = index_val.term.as_u64() {
755 let offset = concrete_idx as usize * step;
756 let term = self
757 .get_byte_value(alloc_id, offset)
758 .cloned()
759 .unwrap_or_else(|| self.fresh_int("arr_elem"));
760 return Some(VmValue {
761 term,
762 ty: place.ty(self.body, self.tcx).ty,
763 provenance: None,
764 invariants: ValueInvariants::default(),
765 });
766 } else {
767 let mut chain = self.fresh_int("arr_elem");
768 for (offset, term) in byte_vals.iter().rev() {
769 let vidx = offset / step;
770 let idx_term = Int::from_u64(self.ctx, vidx as u64);
771 let cond = index_val.term._eq(&idx_term);
772 chain = Bool::ite(&cond, term, &chain);
773 }
774 return Some(VmValue {
775 term: chain,
776 ty: place.ty(self.body, self.tcx).ty,
777 provenance: None,
778 invariants: ValueInvariants::default(),
779 });
780 }
781 }
782 }
783 }
784 return Some(base.clone());
785 }
786 _ => {}
787 }
788 match proj.kind() {
789 ProjectionElem::Deref => {
790 let mut val = base.clone();
791 val.ty = place.ty(self.body, self.tcx).ty;
792 return Some(val);
793 }
794 ProjectionElem::Field(_field_idx, _field_ty) => {
795 let val = base.clone();
796 return Some(val);
797 }
798 _ => {
799 let mut val = base.clone();
802 val.ty = place.ty(self.body, self.tcx).ty;
803 return Some(val);
804 }
805 }
806 }
807 }
808
809 if place.projection.len() > 1
812 && place.projection.iter().any(|p| matches!(
813 p.kind(), ProjectionElem::Deref | ProjectionElem::Downcast(..)
814 ))
815 {
816 let mut val = base;
817 val.ty = place.ty(self.body, self.tcx).ty;
818 return Some(val);
819 }
820
821 None
822 }
823
824 pub(crate) fn unknown_value_for_place(&self, place: &Place<'tcx>) -> VmValue<'ctx, 'tcx> {
826 let ty = place.ty(self.body, self.tcx).ty;
827 let is_raw_ptr = matches!(ty.kind(), rustc_middle::ty::TyKind::RawPtr(..));
828 VmValue {
829 term: self.fresh_int("unknown"),
830 ty,
831 provenance: None,
832 invariants: ValueInvariants {
833 non_null: is_raw_ptr,
834 ..Default::default()
835 },
836 }
837 }
838}
839