Skip to main content

rapx/verify/vm/
memory.rs

1//! Symbolic memory model for the VM.
2
3use rustc_middle::{
4    mir::{Local, Place, ProjectionElem},
5    ty::{Ty, TyKind},
6};
7use z3::ast::{Ast, Int};
8
9use super::state::{AllocId, Allocation, Provenance, VmState, VmValue, ValueInvariants};
10
11impl<'ctx, 'tcx> VmState<'ctx, 'tcx> {
12    pub fn address_of_place(&mut self, place: &Place<'tcx>) -> Option<VmValue<'ctx, 'tcx>> {
13        self.ensure_local_allocation(place.local);
14
15        let zero = Int::from_u64(self.ctx, 0);
16
17        if place.projection.is_empty() {
18            let base_addr = self.local_address(place.local);
19            let ty = self.body.local_decls[place.local].ty;
20            // Prefer the local's value provenance over the stack-allocation
21            // provenance. For Box/Vec parameters, the value tracks the heap
22            // allocation while local_alloc_ids tracks the stack location.
23            let provenance = self.locals.get(&place.local)
24                .and_then(|v| v.provenance.clone())
25                .or_else(|| self.local_alloc_ids.get(&place.local).copied()
26                    .map(|alloc_id| Provenance { alloc_id, offset: zero, is_field_offset: false }));
27            return Some(VmValue {
28                term: base_addr,
29                ty,
30                provenance,
31                invariants: ValueInvariants::default(),
32            });
33        }
34
35        let mut term = self.local_address(place.local);
36        let mut provenance: Option<Provenance<'ctx>> = self
37            .local_alloc_ids
38            .get(&place.local)
39            .copied()
40            .map(|alloc_id| Provenance {
41                alloc_id,
42                offset: zero.clone(),
43                is_field_offset: false,
44            });
45        let mut current_ty = self.body.local_decls[place.local].ty;
46
47        for proj in place.projection.iter() {
48            let mut handled = false;
49            if let ProjectionElem::Index(local) = proj {
50                if let Some(val) = self.locals.get(&local) {
51                    if let Some(idx) = val.term.simplify().as_u64() {
52                        let elem_sz = Int::from_u64(self.ctx, self.size_of_ty(current_ty));
53                        let scaled = Int::mul(self.ctx, &[&Int::from_u64(self.ctx, idx), &elem_sz]);
54                        term = Int::add(self.ctx, &[&term, &scaled]);
55                        if let Some(ref mut prov) = provenance {
56                            prov.offset = Int::add(self.ctx, &[&prov.offset, &scaled]);
57                        }
58                        handled = true;
59                    }
60                }
61                if !handled {
62                    let idx = self.fresh_int("idx");
63                    let elem_size = self.size_of_ty(current_ty);
64                    let elem_sz = Int::from_u64(self.ctx, elem_size);
65                    let scaled = Int::mul(self.ctx, &[&idx, &elem_sz]);
66                    term = Int::add(self.ctx, &[&term, &scaled]);
67                    if let Some(ref mut prov) = provenance {
68                        prov.offset = Int::add(self.ctx, &[&prov.offset, &scaled]);
69                    }
70                }
71                continue;
72            }
73            match proj.kind() {
74                ProjectionElem::Field(field_idx, _) => {
75                    let field_offset = self.field_offset_in_bytes(current_ty, field_idx.as_usize());
76                    let field_off = Int::from_u64(self.ctx, field_offset);
77                    term = Int::add(self.ctx, &[&term, &field_off]);
78                    if let Some(ref mut prov) = provenance {
79                        prov.offset = Int::add(self.ctx, &[&prov.offset, &field_off]);
80                    }
81                }
82                ProjectionElem::Deref => {
83                    let pointed = self.locals.get(&place.local)?;
84                    term = pointed.term.clone();
85                    provenance = pointed.provenance.clone();
86                    // For fat pointers (aggregates without provenance),
87                    // use the first field's provenance (the data pointer).
88                    if provenance.is_none()
89                        && matches!(pointed.ty.kind(), TyKind::RawPtr(..))
90                    {
91                        if let Some(field0) = self.field_value(place.local, &[0]) {
92                            provenance = field0.provenance.clone();
93                        }
94                    }
95                    if let TyKind::Ref(_, deref_ty, _) = current_ty.kind() {
96                        current_ty = *deref_ty;
97                    }
98                }
99                _ => {
100                    self.notes.push(format!("unsupported projection: {:?}", proj.kind()));
101                    return None;
102                }
103            }
104        }
105
106        let ty = place.ty(self.body, self.tcx).ty;
107        Some(VmValue {
108            term,
109            ty,
110            provenance,
111            invariants: ValueInvariants::default(),
112        })
113    }
114
115    /// Lazily create a stack allocation for a MIR local if one doesn't exist.
116    pub(crate) fn ensure_local_allocation(&mut self, local: Local) {
117        if self.local_alloc_ids.contains_key(&local) {
118            return;
119        }
120        let ty = self.body.local_decls[local].ty;
121        let align = self.align_of_ty(ty);
122        let base = self.local_address(local);
123        let id = AllocId(self.next_alloc_id);
124        self.next_alloc_id += 1;
125        // For arrays, track the element type (not the array type) so that
126        // len() computes `size / elem_size` correctly.  When the element size
127        // is unknown (a generic `T`), `size_of::<[T; N]>()` collapses to 0, so
128        // instead record the element count `N` as a symbolic term — this keeps
129        // `len() = size / elem_size` equal to `N`, letting downstream
130        // InBound checks (e.g. `get_unchecked_mut(idx)` where `idx < N`) be
131        // discharged against the loop's `idx < N` path condition.
132        let (size_term, element_ty, is_external) = match ty.kind() {
133            TyKind::Array(elem, const_len) => {
134                let elem_size = self.size_of_ty(*elem).max(1) as u64;
135                // Mirror the const-generic symbolic name used by
136                // `value_of_operand` (which formats `mir::Const::Ty`), so the
137                // element-count term is *identical* to the `const N` term
138                // appearing in path conditions (`idx < N`).  This lets the
139                // later InBound SMT query discharge `idx + 1 <= len`.
140                let const_text = format!("Ty({:?}, {:?})", self.tcx.types.usize, const_len);
141                let n_term = match const_len.try_to_target_usize(self.tcx) {
142                    Some(v) => Int::from_u64(self.ctx, v),
143                    None => {
144                        let name = format!("const_{}", const_text.replace([':', '#', ' '], "_"));
145                        Int::new_const(self.ctx, name.as_str())
146                    }
147                };
148                let size = match n_term.as_u64() {
149                    Some(n) => Int::from_u64(self.ctx, n.saturating_mul(elem_size)),
150                    None => Int::mul(self.ctx, &[&n_term, &Int::from_u64(self.ctx, elem_size)]),
151                };
152                (size, Some(*elem), false)
153            }
154            _ => {
155                let size = self.size_of_ty(ty).max(1) as u64;
156                (Int::from_u64(self.ctx, size), Some(ty), false)
157            }
158        };
159        let alloc = Allocation {
160            base,
161            size: size_term,
162            align,
163            element_ty,
164            is_external,
165            dead: false,
166            initialized: false,
167            alive_assumed: false,
168            nul_terminated: false,
169            parent: None,
170            slice_data: None,
171        };
172        self.allocations.push(alloc);
173        self.local_alloc_ids.insert(local, id);
174    }
175
176    pub(crate) fn field_offset_in_bytes(&self, ty: Ty<'tcx>, field_idx: usize) -> u64 {
177        crate::helpers::mir_utils::field_offset_in_bytes(self.tcx, self.caller_def_id, ty, field_idx)
178    }
179
180    pub fn size_of_ty(&self, ty: Ty<'tcx>) -> u64 {
181        crate::helpers::mir_utils::layout_of_ty(self.tcx, self.caller_def_id, ty)
182            .map(|l| l.size.bytes())
183            .unwrap_or(0)
184    }
185
186    pub fn align_of_ty(&self, ty: Ty<'tcx>) -> u64 {
187        crate::helpers::mir_utils::layout_of_ty(self.tcx, self.caller_def_id, ty)
188            .map(|l| l.align.abi.bytes())
189            .unwrap_or(1)
190    }
191
192    pub fn alloc_for_local(&self, local: Local) -> Option<AllocId> {
193        self.local_alloc_ids.get(&local).copied()
194    }
195
196    pub fn allocation_size(&self, alloc_id: AllocId) -> Option<&Int<'ctx>> {
197        Some(&self.alloc(alloc_id).size)
198    }
199
200    pub fn allocation_base(&self, alloc_id: AllocId) -> Option<&Int<'ctx>> {
201        Some(&self.alloc(alloc_id).base)
202    }
203
204    /// Get the element size (in bytes) for a pointer type, peeling
205    /// through `*const T`, `*mut T`, `&T`, and `&[T]` to find `size_of(T)`.
206    pub fn pointee_elem_size(&self, ty: Ty<'tcx>) -> u64 {
207        let inner = match ty.kind() {
208            TyKind::RawPtr(inner_ty, _) | TyKind::Ref(_, inner_ty, _) => *inner_ty,
209            _ => ty,
210        };
211        match inner.kind() {
212            TyKind::Slice(elem) => self.size_of_ty(*elem),
213            _ => self.size_of_ty(inner),
214        }
215    }
216}