use crate::helpers::mir_utils;
use rustc_hir::def_id::DefId;
#[cfg(rapx_const_ext)]
use rustc_middle::ty::consts::ConstExt;
use rustc_middle::{
mir::{Body, ConstValue, Local, Operand, Place, ProjectionElem},
ty::{Region, Ty, TyCtxt},
};
use z3::{
Context, SatResult, Solver,
ast::{Array, Ast, Bool, Dynamic, Int},
};
use crate::compat::{FxHashMap, FxHashSet};
use crate::helpers::def_use::PlaceKey;
pub(crate) const REPEATED_ARRAY_ELEMENT: usize = usize::MAX;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) struct AllocId(pub usize);
#[derive(Clone, Debug)]
pub(crate) enum OffsetKind<'z3> {
Field,
Element(Int<'z3>),
Byte,
}
#[derive(Clone, Debug)]
pub(crate) struct Provenance<'z3> {
pub alloc_id: AllocId,
pub offset: Int<'z3>,
pub offset_kind: Option<OffsetKind<'z3>>,
}
#[derive(Clone, Debug, Default)]
pub(crate) struct ValueFacts<'z3> {
pub non_null: bool,
pub init: bool,
pub in_bounds: bool,
pub align_n: Option<Int<'z3>>,
}
#[derive(Clone, Debug)]
pub(crate) struct VmValue<'z3, 'tcx> {
pub z3_term: Int<'z3>,
pub ty: Ty<'tcx>,
pub provenance: Option<Provenance<'z3>>,
pub facts: ValueFacts<'z3>,
pub source: ValueSource<'z3>,
}
impl<'z3, 'tcx> VmValue<'z3, 'tcx> {
pub(crate) fn new(term: Int<'z3>, ty: Ty<'tcx>) -> Self {
VmValue {
z3_term: term,
ty,
provenance: None,
facts: ValueFacts::default(),
source: ValueSource::None,
}
}
pub(crate) fn provenance_alloc_id(&self) -> Option<AllocId> {
self.provenance.as_ref().map(|p| p.alloc_id)
}
pub(crate) fn discriminant(&self) -> Option<&Int<'z3>> {
match &self.source {
ValueSource::Discriminant(d) => Some(d),
_ => None,
}
}
pub(crate) fn bool_cond(&self) -> Option<&Bool<'z3>> {
match &self.source {
ValueSource::Comparison { cond, .. } => Some(cond),
_ => None,
}
}
pub(crate) fn is_field_offset(&self) -> bool {
matches!(self.source, ValueSource::FieldOffset)
}
pub(crate) fn is_pointer(&self) -> bool {
self.provenance.is_some()
}
}
#[derive(Clone, Debug)]
pub(crate) enum AllocKind<'z3> {
Object,
Slice { len: Int<'z3> },
External,
}
#[derive(Clone, Debug)]
pub(crate) enum ElementTy<'tcx> {
Typed(Ty<'tcx>),
Generic,
}
impl<'tcx> ElementTy<'tcx> {
pub(crate) fn as_ty(&self) -> Option<Ty<'tcx>> {
match self {
ElementTy::Typed(t) => Some(*t),
ElementTy::Generic => None,
}
}
pub(crate) fn is_generic(&self) -> bool {
matches!(self, ElementTy::Generic)
}
}
impl<'tcx> From<Option<Ty<'tcx>>> for ElementTy<'tcx> {
fn from(o: Option<Ty<'tcx>>) -> Self {
match o {
Some(t) => ElementTy::Typed(t),
None => ElementTy::Generic,
}
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct ForEachFacts<'z3, 'tcx> {
pub target_ty: Option<Ty<'tcx>>,
pub aligned_ty: Option<Ty<'tcx>>,
pub allocated: Option<(Ty<'tcx>, Int<'z3>)>,
pub owning: bool,
}
#[derive(Clone, Debug, Default)]
pub(crate) struct AllocFacts<'z3, 'tcx> {
pub dead: bool,
pub owning: Option<Int<'z3>>,
pub liveness: Option<Region<'tcx>>,
pub for_each: ForEachFacts<'z3, 'tcx>,
}
#[derive(Clone, Debug, Default)]
pub(crate) struct ContentFacts<'tcx> {
pub initialized: bool,
pub cstr_trusted: bool,
pub utf8_trusted: bool,
pub split_transmute_to: Option<Ty<'tcx>>,
}
#[derive(Clone, Debug)]
pub(crate) struct Allocation<'z3, 'tcx> {
pub base: Int<'z3>,
pub size: Int<'z3>,
pub align: Int<'z3>,
pub element_ty: ElementTy<'tcx>,
pub kind: AllocKind<'z3>,
pub facts: AllocFacts<'z3, 'tcx>,
pub parent: Option<AllocId>,
}
impl<'z3, 'tcx> Allocation<'z3, 'tcx> {
pub(crate) fn new(
base: Int<'z3>,
size: Int<'z3>,
align: Int<'z3>,
element_ty: Option<Ty<'tcx>>,
kind: AllocKind<'z3>,
) -> Self {
Allocation {
base,
size,
align,
element_ty: element_ty.into(),
kind,
facts: AllocFacts::default(),
parent: None,
}
}
pub(crate) fn is_external(&self) -> bool {
matches!(self.kind, AllocKind::External)
}
pub(crate) fn slice_len(&self) -> Option<&Int<'z3>> {
match &self.kind {
AllocKind::Slice { len } => Some(len),
_ => None,
}
}
pub(crate) fn set_slice_len(&mut self, len: Int<'z3>) {
self.kind = AllocKind::Slice { len };
}
}
#[derive(Default, Clone)]
pub(crate) struct InlineCtx<'z3, 'tcx> {
pub inline_depth: usize,
pub inline_truncated_terms: FxHashSet<Int<'z3>>,
pub inline_branch_terms: FxHashSet<Int<'z3>>,
pub opaque_intrinsic_terms: FxHashSet<Int<'z3>>,
pub arg_referents: Vec<Option<Local>>,
pub path_saved_arg_referents: Vec<Vec<Option<Local>>>,
pub path_saved_deferred_writes: Vec<Vec<(Local, Vec<usize>, VmValue<'z3, 'tcx>)>>,
pub deferred_field_writes: Vec<(Local, Vec<usize>, VmValue<'z3, 'tcx>)>,
}
#[derive(Clone, Debug)]
pub(crate) enum ValueSource<'z3> {
None,
CallsiteConst,
FieldOffset,
Discriminant(Int<'z3>),
Comparison {
lhs: Option<PlaceKey>,
rhs: Option<PlaceKey>,
op: rustc_middle::mir::BinOp,
cond: Bool<'z3>,
},
BinaryOp {
lhs: Option<PlaceKey>,
rhs: Option<PlaceKey>,
op: rustc_middle::mir::BinOp,
},
}
impl<'z3> ValueSource<'z3> {
pub(crate) fn operands(
&self,
) -> Option<(
&Option<PlaceKey>,
&Option<PlaceKey>,
rustc_middle::mir::BinOp,
)> {
match self {
ValueSource::Comparison { lhs, rhs, op, .. } => Some((lhs, rhs, *op)),
ValueSource::BinaryOp { lhs, rhs, op } => Some((lhs, rhs, *op)),
_ => None,
}
}
pub(crate) fn field_offset_only(&self) -> ValueSource<'z3> {
match self {
ValueSource::FieldOffset => ValueSource::FieldOffset,
_ => ValueSource::None,
}
}
}
#[derive(Clone)]
pub(crate) struct MemoryUnit<'z3, 'tcx> {
pub(crate) allocation: Allocation<'z3, 'tcx>,
pub(crate) content: MemoryContent<'z3, 'tcx>,
}
#[derive(Default, Clone)]
pub(crate) struct MemoryContent<'z3, 'tcx> {
pub(crate) byte_array: Option<Array<'z3>>,
pub(crate) byte_written: FxHashSet<usize>,
pub(crate) values: FxHashMap<(Ty<'tcx>, Vec<usize>), VmValue<'z3, 'tcx>>,
pub(crate) indexed_elements: Vec<(Int<'z3>, VmValue<'z3, 'tcx>)>,
pub(crate) indexed_elements_modified: bool,
pub(crate) facts: ContentFacts<'tcx>,
}
#[derive(Default, Clone)]
pub(crate) struct Constraints<'z3, 'tcx> {
pub(crate) assertions: Vec<Bool<'z3>>,
pub(crate) term_caches: TermCaches<'z3, 'tcx>,
}
#[derive(Default, Clone)]
pub(crate) struct TermCaches<'z3, 'tcx> {
pub(crate) sizes: FxHashMap<Ty<'tcx>, Int<'z3>>,
pub(crate) aligns: FxHashMap<Ty<'tcx>, Int<'z3>>,
pub(crate) not_mask_terms: FxHashSet<Int<'z3>>,
pub(crate) exact_div_roots: FxHashMap<Int<'z3>, Int<'z3>>,
pub(crate) div_roots: FxHashMap<Int<'z3>, (Int<'z3>, Int<'z3>)>,
pub(crate) iter_ptr_offset:
FxHashMap<(AllocId, Int<'z3>), (AllocId, Int<'z3>, Option<Int<'z3>>)>,
pub(crate) uninit_byte: Option<Int<'z3>>,
}
#[derive(Clone)]
pub(crate) struct FrameState {
pub(crate) current_def_id: DefId,
pub(crate) local_alloc: FxHashMap<Local, AllocId>,
}
#[derive(Clone)]
pub(crate) struct VmState<'z3, 'tcx> {
pub(crate) z3_ctx: &'z3 Context,
pub(crate) tcx: TyCtxt<'tcx>,
pub(crate) current_frame: FrameState,
pub(crate) caller_frames: Vec<FrameState>,
pub(crate) active_method_calls: Vec<Option<super::method_cache::MethodSnapshot<'z3, 'tcx>>>,
pub(crate) method_returns: Vec<super::method_cache::MethodReturn<'z3, 'tcx>>,
pub(crate) units: Vec<MemoryUnit<'z3, 'tcx>>,
pub(crate) constraints: Constraints<'z3, 'tcx>,
pub(crate) inline: InlineCtx<'z3, 'tcx>,
pub(crate) unsupported_mir: bool,
pub(crate) path_infeasible: bool,
pub(crate) incomplete_inline_branch: bool,
pub(crate) possible_uninit_allocs: FxHashSet<AllocId>,
pub(crate) alias_hazard_allocs: FxHashSet<AllocId>,
}
impl<'z3, 'tcx> VmState<'z3, 'tcx> {
pub(crate) fn new(z3_ctx: &'z3 Context, tcx: TyCtxt<'tcx>, caller_def_id: DefId) -> Self {
let mut constraints = Constraints::default();
let uninit = Int::fresh_const(z3_ctx, "uninit_byte");
constraints.term_caches.uninit_byte = Some(uninit.clone());
constraints
.assertions
.push(uninit.ge(&Int::from_u64(z3_ctx, 256)));
Self {
z3_ctx,
tcx,
current_frame: FrameState {
current_def_id: caller_def_id,
local_alloc: FxHashMap::default(),
},
caller_frames: Vec::default(),
active_method_calls: Vec::default(),
method_returns: Vec::default(),
units: Vec::default(),
inline: InlineCtx::default(),
unsupported_mir: false,
path_infeasible: false,
incomplete_inline_branch: false,
possible_uninit_allocs: FxHashSet::default(),
constraints,
alias_hazard_allocs: FxHashSet::default(),
}
}
pub(crate) fn body(&self) -> &'tcx Body<'tcx> {
self.tcx.optimized_mir(self.current_frame.current_def_id)
}
pub(crate) fn save_frame(&mut self) -> FrameState {
FrameState {
current_def_id: self.current_frame.current_def_id,
local_alloc: std::mem::take(&mut self.current_frame.local_alloc),
}
}
pub(crate) fn restore_frame(&mut self, frame: FrameState) {
self.current_frame = frame;
}
pub(crate) fn local_value(&self, local: Local) -> Option<&VmValue<'z3, 'tcx>> {
let alloc_id = *self.current_frame.local_alloc.get(&local)?;
let view_ty = self.body().local_decls[local].ty;
self.load_value(alloc_id, view_ty, &[])
}
pub(crate) fn term_involves(terms: &FxHashSet<Int<'z3>>, term: &Int<'z3>) -> bool {
let mut pending = vec![Dynamic::from_ast(term)];
let mut seen = FxHashSet::default();
while let Some(ast) = pending.pop() {
if !seen.insert(ast.clone()) {
continue;
}
if ast.as_int().is_some_and(|value| terms.contains(&value)) {
return true;
}
pending.extend(ast.children());
}
false
}
pub(crate) fn set_local(&mut self, local: Local, value: VmValue<'z3, 'tcx>) {
self.ensure_local_allocation(local);
let alloc_id = self.current_frame.local_alloc[&local];
self.possible_uninit_allocs.remove(&alloc_id);
let view_ty = self.body().local_decls[local].ty;
self.store_value(alloc_id, view_ty, vec![], value);
}
pub(crate) fn invalidate_indexed_array(&mut self, local: Local) {
let view_ty = self.body().local_decls[local].ty;
let mut element_ty = view_ty;
let mut is_array = false;
while let rustc_middle::ty::TyKind::Array(inner, _) = element_ty.kind() {
element_ty = *inner;
is_array = true;
}
if is_array
&& let Some(&alloc_id) = self.current_frame.local_alloc.get(&local) {
let content = self.content_mut(alloc_id);
let was_tracked = content.indexed_elements_modified
|| !content.indexed_elements.is_empty();
content.indexed_elements.clear();
content.indexed_elements_modified = was_tracked;
content
.values
.retain(|(ty, path), _| *ty != view_ty || path.is_empty());
}
}
pub(crate) fn mark_initialized(&mut self, value: &mut VmValue<'z3, 'tcx>) {
value.facts.init = true;
if let Some(prov) = &value.provenance {
self.content_mut(prov.alloc_id).facts.initialized = true;
}
}
pub(crate) fn local_address(&mut self, local: Local) -> Int<'z3> {
self.ensure_local_allocation(local);
let id = self.current_frame.local_alloc[&local];
self.units[id.0].allocation.base.clone()
}
pub(crate) fn allocate(
&mut self,
size: Int<'z3>,
align: Int<'z3>,
element_ty: Option<Ty<'tcx>>,
) -> (AllocId, Int<'z3>) {
self.allocate_internal(size, align, element_ty, AllocKind::Object)
}
pub(crate) fn allocate_external(
&mut self,
size: Int<'z3>,
align: Int<'z3>,
element_ty: Option<Ty<'tcx>>,
) -> (AllocId, Int<'z3>) {
self.allocate_internal(size, align, element_ty, AllocKind::External)
}
pub(crate) fn allocate_slice(
&mut self,
len: Int<'z3>,
elem_size: Int<'z3>,
align: Int<'z3>,
element_ty: Option<Ty<'tcx>>,
) -> (AllocId, Int<'z3>) {
let size = Int::mul(self.z3_ctx, &[&len, &elem_size]);
let (id, base) = self.allocate(size, align, element_ty);
self.alloc_mut(id).set_slice_len(len);
(id, base)
}
fn allocate_internal(
&mut self,
size: Int<'z3>,
align: Int<'z3>,
element_ty: Option<Ty<'tcx>>,
kind: AllocKind<'z3>,
) -> (AllocId, Int<'z3>) {
let id = AllocId(self.units.len());
let base = {
let name = format!(
"{}_{}",
if matches!(kind, AllocKind::External) {
"ext"
} else {
"heap"
},
id.0
);
Int::new_const(self.z3_ctx, name.as_str())
};
let alloc = Allocation::new(base.clone(), size, align, element_ty, kind);
self.units.push(MemoryUnit {
allocation: alloc,
content: MemoryContent::default(),
});
(id, base)
}
pub(crate) fn alloc(&self, id: AllocId) -> &Allocation<'z3, 'tcx> {
&self.units[id.0].allocation
}
pub(crate) fn alloc_mut(&mut self, id: AllocId) -> &mut Allocation<'z3, 'tcx> {
&mut self.units[id.0].allocation
}
pub(crate) fn content(&self, id: AllocId) -> &MemoryContent<'z3, 'tcx> {
&self.units[id.0].content
}
pub(crate) fn content_mut(&mut self, id: AllocId) -> &mut MemoryContent<'z3, 'tcx> {
&mut self.units[id.0].content
}
pub(crate) fn is_cstr_trusted(&self, id: AllocId) -> bool {
self.content(id).facts.cstr_trusted
}
pub(crate) fn is_utf8_trusted(&self, id: AllocId) -> bool {
self.content(id).facts.utf8_trusted
}
pub(crate) fn root_alloc(&self, id: AllocId) -> AllocId {
let mut cur = id;
let mut guard = 0;
while let Some(parent) = self.alloc(cur).parent {
cur = parent;
guard += 1;
if guard >= self.units.len() {
break;
}
}
cur
}
pub(crate) fn fresh_int(&self, prefix: &str) -> Int<'z3> {
Int::fresh_const(self.z3_ctx, prefix)
}
pub(crate) fn field_value(&self, local: Local, path: &[usize]) -> Option<&VmValue<'z3, 'tcx>> {
let alloc_id = *self.current_frame.local_alloc.get(&local)?;
let view_ty = self.body().local_decls[local].ty;
self.load_value(alloc_id, view_ty, path).or_else(|| {
let mut candidates = vec![path.to_vec()];
for position in (0..path.len()).rev() {
if path[position] == REPEATED_ARRAY_ELEMENT {
continue;
}
let alternatives = candidates.clone();
for mut candidate in alternatives {
candidate[position] = REPEATED_ARRAY_ELEMENT;
if let Some(value) = self.load_value(alloc_id, view_ty, &candidate) {
return Some(value);
}
candidates.push(candidate);
}
}
None
})
}
pub(crate) fn field_paths(&self, local: Local) -> Vec<Vec<usize>> {
let Some(&alloc_id) = self.current_frame.local_alloc.get(&local) else {
return Vec::new();
};
let view_ty = self.body().local_decls[local].ty;
self.units[alloc_id.0]
.content
.values
.keys()
.filter(|(t, p)| *t == view_ty && !p.is_empty())
.map(|(_, p)| p.clone())
.collect()
}
fn frame_local_ty(&self, frame: &FrameState, local: Local) -> Ty<'tcx> {
self.tcx.optimized_mir(frame.current_def_id).local_decls[local].ty
}
pub(crate) fn frame_field_paths(&self, frame: &FrameState, local: Local) -> Vec<Vec<usize>> {
let Some(&alloc_id) = frame.local_alloc.get(&local) else {
return Vec::new();
};
let view_ty = self.frame_local_ty(frame, local);
self.units[alloc_id.0]
.content
.values
.keys()
.filter(|(t, p)| *t == view_ty && !p.is_empty())
.map(|(_, p)| p.clone())
.collect()
}
pub(crate) fn frame_field_value(
&self,
frame: &FrameState,
local: Local,
path: &[usize],
) -> Option<&VmValue<'z3, 'tcx>> {
let alloc_id = *frame.local_alloc.get(&local)?;
let view_ty = self.frame_local_ty(frame, local);
self.load_value(alloc_id, view_ty, path)
}
pub(crate) fn frame_local_value(
&self,
frame: &FrameState,
local: Local,
) -> Option<&VmValue<'z3, 'tcx>> {
let alloc_id = *frame.local_alloc.get(&local)?;
let view_ty = self.frame_local_ty(frame, local);
self.load_value(alloc_id, view_ty, &[])
}
pub(crate) fn all_local_values(&self) -> Vec<(Local, &VmValue<'z3, 'tcx>)> {
self.current_frame
.local_alloc
.keys()
.copied()
.filter_map(|local| self.local_value(local).map(|value| (local, value)))
.collect()
}
pub(crate) fn iter_buffer(&self, local: Local) -> Option<AllocId> {
self.field_value(local, &[1])
.and_then(|end| end.provenance.as_ref())
.map(|ep| ep.alloc_id)
}
pub(crate) fn iter_utf8_buffer(&self, local: Local) -> Option<(AllocId, Int<'z3>)> {
let mut best: Option<(usize, AllocId, Int<'z3>)> = None;
for path in self.field_paths(local) {
if path.last() != Some(&1) {
continue;
}
let Some(v) = self.field_value(local, &path) else {
continue;
};
let Some(prov) = v.provenance.as_ref() else {
continue;
};
if best
.as_ref()
.is_none_or(|(depth, _, _)| path.len() < *depth)
{
best = Some((path.len(), prov.alloc_id, prov.offset.clone()));
}
}
best.map(|(_, id, off)| (id, off))
}
pub(crate) fn owner_ptr_field(&self, local: Local) -> Option<&VmValue<'z3, 'tcx>> {
let ty = self.body().local_decls[local].ty;
if let Some(path) = self.container_ptr_field(ty)
&& let Some(v) = self.field_value(local, &path)
&& v.provenance_alloc_id().is_some()
{
return Some(v);
}
self.field_paths(local).iter().find_map(|path| {
self.field_value(local, path)
.filter(|v| v.provenance_alloc_id().is_some())
})
}
pub(crate) fn invalidate_owner_field(&mut self, local: Local) {
let ty = self.body().local_decls[local].ty;
let Some(path) = self.container_ptr_field(ty) else {
return;
};
let Some(mut fv) = self.field_value(local, &path).cloned() else {
return;
};
if fv.provenance_alloc_id().is_some() {
fv.provenance = None;
self.set_field_value(local, path, fv);
}
}
pub(crate) fn set_field_value(
&mut self,
local: Local,
path: Vec<usize>,
value: VmValue<'z3, 'tcx>,
) {
let view_ty = self.body().local_decls[local].ty;
self.ensure_local_allocation(local);
let alloc_id = self.current_frame.local_alloc[&local];
self.store_value(alloc_id, view_ty, path, value);
}
pub(crate) fn write_local_path(
&mut self,
local: Local,
path: Vec<usize>,
value: VmValue<'z3, 'tcx>,
) {
if path.is_empty() {
self.invalidate_indexed_array(local);
self.invalidate_field_descendants(local, &[]);
self.set_local(local, value);
} else {
self.invalidate_field_descendants(local, &path);
self.set_field_value(local, path, value);
}
}
pub(crate) fn invalidate_field_descendants(&mut self, local: Local, path: &[usize]) {
if let Some(&alloc_id) = self.current_frame.local_alloc.get(&local) {
let view_ty = self.body().local_decls[local].ty;
self.content_mut(alloc_id).values.retain(|(ty, stored_path), _| {
*ty != view_ty
|| stored_path.len() <= path.len()
|| !stored_path.starts_with(path)
});
}
}
pub(crate) fn assert_all(&self, solver: &z3::Solver<'z3>) {
for cond in &self.constraints.assertions {
solver.assert(cond);
}
let zero = Int::from_u64(self.z3_ctx, 0);
for unit in &self.units {
let alloc = &unit.allocation;
if !alloc.is_external() {
solver.assert(&alloc.base._eq(&zero).not());
}
solver.assert(&alloc.size.ge(&zero));
if alloc.align.simplify().as_u64() != Some(1) {
solver.assert(&alloc.base.rem(&alloc.align)._eq(&zero));
}
}
let local_ids: Vec<Local> = self.current_frame.local_alloc.keys().copied().collect();
for local in local_ids {
if let Some(value) = self.local_value(local) {
self.assert_value_constraints(solver, value);
}
for path in self.field_paths(local) {
if let Some(value) = self.field_value(local, &path) {
self.assert_value_constraints(solver, value);
}
}
}
}
fn assert_value_constraints(&self, solver: &z3::Solver<'z3>, value: &VmValue<'z3, 'tcx>) {
let zero = Int::from_u64(self.z3_ctx, 0);
if value.facts.non_null {
solver.assert(&value.z3_term._eq(&zero).not());
}
if let Some(ref prov) = value.provenance {
let is_scalar = matches!(
value.ty.kind(),
rustc_middle::ty::TyKind::Uint(_)
| rustc_middle::ty::TyKind::Int(_)
| rustc_middle::ty::TyKind::Bool
| rustc_middle::ty::TyKind::Char
);
if !is_scalar {
let alloc = self.alloc(prov.alloc_id);
let expected = Int::add(self.z3_ctx, &[&alloc.base, &prov.offset]);
solver.assert(&value.z3_term._eq(&expected));
}
}
if matches!(
value.ty.kind(),
rustc_middle::ty::TyKind::Uint(_)
| rustc_middle::ty::TyKind::Bool
| rustc_middle::ty::TyKind::Char
) {
solver.assert(&value.z3_term.ge(&zero));
}
if matches!(value.ty.kind(), rustc_middle::ty::TyKind::Bool) {
let one = Int::from_u64(self.z3_ctx, 1);
solver.assert(&value.z3_term.le(&one));
}
if matches!(value.ty.kind(), rustc_middle::ty::TyKind::Char) {
let max = Int::from_u64(self.z3_ctx, 0x10FFFF);
solver.assert(&value.z3_term.le(&max));
}
}
}
impl std::fmt::Debug for VmState<'_, '_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("VmState")
.field("locals_count", &self.current_frame.local_alloc.len())
.field("allocations_count", &self.units.len())
.field("assertions", &self.constraints.assertions.len())
.finish()
}
}
impl<'z3, 'tcx> VmState<'z3, 'tcx> {
pub(crate) fn indexed_element_target(
&self,
place: &Place<'tcx>,
) -> Option<(AllocId, Int<'z3>)> {
let element_ty = place.ty(self.body(), self.tcx).ty;
if !matches!(
element_ty.kind(),
rustc_middle::ty::TyKind::RawPtr(..)
| rustc_middle::ty::TyKind::Int(..)
| rustc_middle::ty::TyKind::Uint(..)
| rustc_middle::ty::TyKind::Bool
| rustc_middle::ty::TyKind::Char
| rustc_middle::ty::TyKind::Float(..)
) {
return None;
}
if !matches!(
place.projection.last()?.kind(),
ProjectionElem::Index(_) | ProjectionElem::ConstantIndex { .. }
) {
return None;
}
let mut current_ty = self.body().local_decls[place.local].ty;
let mut fields = Vec::new();
let mut index = Int::from_u64(self.z3_ctx, 0);
let element_size = self.size_of_ty(element_ty);
if element_size == 0 {
return None;
}
let mut indexed = false;
for projection in place.projection.iter() {
match projection {
ProjectionElem::Field(field, field_ty) if !indexed => {
fields.push(field.as_usize());
current_ty = field_ty;
}
ProjectionElem::Deref => {
current_ty = match current_ty.kind() {
rustc_middle::ty::TyKind::Ref(_, inner, _)
| rustc_middle::ty::TyKind::RawPtr(inner, _) => *inner,
_ => return None,
};
}
ProjectionElem::Downcast(..) => {}
ProjectionElem::Index(local) => {
let inner = match current_ty.kind() {
rustc_middle::ty::TyKind::Array(inner, _)
| rustc_middle::ty::TyKind::Slice(inner) => *inner,
_ => return None,
};
let inner_size = self.size_of_ty(inner);
if inner_size == 0 || inner_size % element_size != 0 {
return None;
}
let term = self.local_value(local)?.z3_term.clone();
let stride = Int::from_u64(self.z3_ctx, inner_size / element_size);
let step = Int::mul(self.z3_ctx, &[&term, &stride]);
index = Int::add(self.z3_ctx, &[&index, &step]);
current_ty = inner;
indexed = true;
}
ProjectionElem::ConstantIndex { offset, from_end, .. } => {
let (inner, length) = match current_ty.kind() {
rustc_middle::ty::TyKind::Array(inner, length) => (*inner, Some(length)),
rustc_middle::ty::TyKind::Slice(inner) => (*inner, None),
_ => return None,
};
let inner_size = self.size_of_ty(inner);
if inner_size == 0 || inner_size % element_size != 0 {
return None;
}
let position = if from_end {
length?.try_to_target_usize(self.tcx)?.checked_sub(offset)?
} else {
offset
};
let term = Int::from_u64(self.z3_ctx, position);
let stride = Int::from_u64(self.z3_ctx, inner_size / element_size);
let step = Int::mul(self.z3_ctx, &[&term, &stride]);
index = Int::add(self.z3_ctx, &[&index, &step]);
current_ty = inner;
indexed = true;
}
_ => return None,
}
}
if current_ty != element_ty {
return None;
}
let alloc_id = if fields.is_empty() {
let local_ty = self.body().local_decls[place.local].ty;
if matches!(local_ty.kind(), rustc_middle::ty::TyKind::Array(..)) {
self.current_frame.local_alloc.get(&place.local).copied()?
} else {
self.local_value(place.local)
.and_then(|value| value.provenance_alloc_id())
.or_else(|| self.current_frame.local_alloc.get(&place.local).copied())?
}
} else {
self.field_value(place.local, &fields)?
.provenance_alloc_id()?
};
let stored_ty = self.alloc(alloc_id).element_ty.as_ty()?;
let mut stored_leaf_ty = stored_ty;
while let rustc_middle::ty::TyKind::Array(inner, _) = stored_leaf_ty.kind() {
stored_leaf_ty = *inner;
}
(stored_leaf_ty == element_ty).then_some((alloc_id, index))
}
pub(crate) fn store_indexed_element(
&mut self,
alloc_id: AllocId,
index: Int<'z3>,
value: VmValue<'z3, 'tcx>,
) {
let retained = self.content(alloc_id)
.indexed_elements
.iter()
.filter(|(stored_index, _)| self.path_implies(&stored_index._eq(&index).not()))
.cloned()
.collect();
let content = self.content_mut(alloc_id);
content.indexed_elements_modified = true;
content.indexed_elements = retained;
content.indexed_elements.push((index, value));
}
pub(crate) fn path_implies(&self, condition: &Bool<'z3>) -> bool {
if condition.simplify().as_bool() == Some(true) {
return true;
}
let solver = Solver::new(self.z3_ctx);
for assertion in &self.constraints.assertions {
solver.assert(assertion);
}
solver.assert(&condition.not());
solver.check() == SatResult::Unsat
}
fn stored_indexed_element(&self, place: &Place<'tcx>) -> Option<VmValue<'z3, 'tcx>> {
let (alloc_id, index) = self.indexed_element_target(place)?;
let elements = &self.content(alloc_id).indexed_elements;
if let Some((_, value)) = elements.iter().rev().find(|(stored_index, _)| index == *stored_index) {
return Some(value.clone());
}
for (stored_index, value) in elements.iter().rev() {
if self.path_implies(&index._eq(stored_index)) {
return Some(value.clone());
}
}
None
}
pub(crate) fn symbolic_const_term(
&self,
constant: &rustc_middle::mir::Const<'tcx>,
) -> Int<'z3> {
self.symbolic_const_term_text(&format!("{constant:?}"))
}
pub(crate) fn symbolic_const_term_text(&self, text: &str) -> Int<'z3> {
let name = format!("const_{}", text.replace([':', '#', ' '], "_"));
Int::new_const(self.z3_ctx, name.as_str())
}
pub(crate) fn const_param_term(&self, index: u32) -> Option<Int<'z3>> {
let generics = self.tcx.generics_of(self.current_frame.current_def_id);
if index as usize >= generics.count() {
return None;
}
let param = generics.param_at(index as usize, self.tcx);
if !matches!(param.kind, rustc_middle::ty::GenericParamDefKind::Const { .. }) {
return None;
}
let ty = self.tcx.type_of(param.def_id).instantiate_identity();
#[cfg(rapx_ge_99)]
let ty = ty.skip_norm_wip();
let value = rustc_middle::ty::Const::new_param(
self.tcx,
rustc_middle::ty::ParamConst::new(index, param.name),
);
Some(self.symbolic_const_term(&rustc_middle::mir::Const::Ty(ty, value)))
}
pub(crate) fn value_of_operand(&self, operand: &Operand<'tcx>) -> VmValue<'z3, 'tcx> {
match operand {
Operand::Copy(place) | Operand::Move(place) => self
.value_of_place(place)
.unwrap_or_else(|| self.unknown_value_for_place(place)),
Operand::Constant(constant) => {
let text = format!("{:?}", constant.const_);
if let rustc_middle::mir::Const::Unevaluated(uneval, _) = constant.const_ {
if mir_utils::is_sized_type_property(self.tcx, uneval.def, "IS_ZST")
&& !uneval.args.is_empty()
{
let ty = uneval.args.type_at(0);
let size = if mir_utils::ty_has_type_param(ty) {
self.size_sym_read(ty)
} else {
Int::from_u64(self.z3_ctx, self.size_of_ty(ty))
};
let zero = Int::from_u64(self.z3_ctx, 0);
let one = Int::from_u64(self.z3_ctx, 1);
return VmValue::new(size._eq(&zero).ite(&one, &zero), constant.const_.ty());
}
let is_size = mir_utils::is_sized_type_property(self.tcx, uneval.def, "SIZE");
let is_align = mir_utils::is_sized_type_property(self.tcx, uneval.def, "ALIGN");
if (is_size || is_align) && !uneval.args.is_empty() {
let ty = uneval.args.type_at(0);
let term = if is_size {
self.size_sym_read(ty)
} else {
self.align_sym_read(ty)
};
return VmValue::new(term, constant.const_.ty());
}
}
let int_val = mir_utils::eval_const_scalar_int(self.tcx, &constant.const_, &text);
let field_offset = int_val.is_none()
&& mir_utils::offset_of_container(self.tcx, &constant.const_).is_some();
let enum_variant = mir_utils::const_destructure(self.tcx, &constant.const_)
.and_then(|(variant, _)| variant);
let term = if let Some(v) = int_val {
if v < 0 {
Int::from_i64(self.z3_ctx, v as i64)
} else {
Int::from_u64(self.z3_ctx, v as u64)
}
} else {
self.symbolic_const_term(&constant.const_)
};
let ty = constant.const_.ty();
VmValue {
z3_term: term,
ty,
provenance: None,
facts: ValueFacts::default(),
source: if let Some(v) = enum_variant {
ValueSource::Discriminant(Int::from_u64(self.z3_ctx, v))
} else if field_offset {
ValueSource::FieldOffset
} else {
ValueSource::None
},
}
}
#[cfg(rapx_ge_95)]
Operand::RuntimeChecks(_) => VmValue::new(
self.fresh_int("runtime_checks"),
self.body().local_decls[Local::from_usize(0)].ty,
),
}
}
pub(crate) fn materialize_const_adt(
&mut self,
local: Local,
constant: &rustc_middle::mir::Const<'tcx>,
) {
let Some((_variant, fields)) = mir_utils::const_destructure(self.tcx, constant) else {
return;
};
for (idx, (field_val, field_ty)) in fields.iter().enumerate() {
let term = self.const_field_term(field_val, *field_ty);
self.set_field_value(local, vec![idx], VmValue::new(term, *field_ty));
}
}
fn const_field_term(&self, val: &ConstValue, ty: Ty<'tcx>) -> Int<'z3> {
if let Some(scalar) = (*val).try_to_scalar_int() {
let v = mir_utils::scalar_int_value(scalar, ty);
if v < 0 {
return Int::from_i64(self.z3_ctx, v as i64);
}
return Int::from_u64(self.z3_ctx, v as u64);
}
let name = format!(
"const_field_{}",
format!("{:?}", val).replace([':', '#', ' '], "_")
);
Int::new_const(self.z3_ctx, name.as_str())
}
fn byte_from_field(&self, alloc_id: AllocId, ty: Ty<'tcx>, offset: usize) -> Option<Int<'z3>> {
let rustc_middle::ty::TyKind::Adt(adt_def, substs) = ty.kind() else {
return None;
};
let variant = adt_def.non_enum_variant();
for (idx, field_def) in variant.fields.iter().enumerate() {
let field_ty = mir_utils::field_ty(self.tcx, field_def, substs);
let field_off = self.field_offset_in_bytes(ty, idx) as usize;
let field_size = self.size_of_ty(field_ty) as usize;
if offset >= field_off && offset < field_off + field_size {
let fv = self.load_value(alloc_id, ty, &[idx])?;
let byte_idx = offset - field_off;
if byte_idx >= 8 {
return None;
}
let divisor = Int::from_u64(self.z3_ctx, 1u64 << (byte_idx * 8));
let modulus = Int::from_u64(self.z3_ctx, 256);
return Some(fv.z3_term.div(&divisor).rem(&modulus));
}
}
None
}
pub(crate) fn value_of_place(&self, place: &Place<'tcx>) -> Option<VmValue<'z3, 'tcx>> {
if place.projection.is_empty() {
return self.local_value(place.local).cloned();
}
let place_ty = place.ty(self.body(), self.tcx).ty;
let last_is_index = place.projection.last().is_some_and(|projection| {
matches!(
projection.kind(),
ProjectionElem::Index(_) | ProjectionElem::ConstantIndex { .. }
)
});
let indexed_target_modified = last_is_index
&& self
.indexed_element_target(place)
.is_some_and(|(alloc_id, _)| self.content(alloc_id).indexed_elements_modified);
let index_count = place
.projection
.iter()
.filter(|projection| {
matches!(
projection.kind(),
ProjectionElem::Index(_) | ProjectionElem::ConstantIndex { .. }
)
})
.count();
let indexed_value = last_is_index
&& (matches!(place_ty.kind(), rustc_middle::ty::TyKind::RawPtr(..))
|| index_count > 1
|| indexed_target_modified);
if indexed_value {
if let Some(value) = self.stored_indexed_element(place) {
return Some(value);
}
if indexed_target_modified {
return Some(self.unknown_value_for_place(place));
}
if let Some(&alloc_id) = self.current_frame.local_alloc.get(&place.local)
&& !self.content(alloc_id).indexed_elements_modified
{
let mut path = Vec::new();
let mut supported = true;
for projection in place.projection.iter() {
match projection {
ProjectionElem::Field(field, _) => path.push(field.as_usize()),
ProjectionElem::Index(local) => {
let index = self.local_value(local)
.and_then(|value| value.z3_term.simplify().as_u64())
.and_then(|index| usize::try_from(index).ok())
.unwrap_or(REPEATED_ARRAY_ELEMENT);
path.push(index);
}
ProjectionElem::ConstantIndex { offset, from_end: false, .. } => {
path.push(offset as usize);
}
_ => supported = false,
}
}
if supported
&& let Some(value) = self.field_value(place.local, &path)
{
return Some(value.clone());
}
}
}
if let Some(ProjectionElem::Index(index_local)) = place.projection.last()
&& place.projection[..place.projection.len() - 1]
.iter()
.all(|projection| matches!(projection.kind(), ProjectionElem::Field(..)))
&& let Some(index) = self
.local_value(*index_local)
.and_then(|value| value.z3_term.simplify().as_u64())
{
let mut path = place.projection[..place.projection.len() - 1]
.iter()
.filter_map(|projection| match projection.kind() {
ProjectionElem::Field(index, _) => Some(index.as_usize()),
_ => None,
})
.collect::<Vec<_>>();
path.push(index as usize);
if let Some(value) = self.field_value(place.local, &path) {
return Some(value.clone());
}
}
if indexed_value {
return Some(self.unknown_value_for_place(place));
}
if place.projection.len() == 1
&& matches!(place.projection[0], ProjectionElem::Deref)
&& let Some(pointer) = self.local_value(place.local)
&& let Some(provenance) = &pointer.provenance
&& self.alloc(provenance.alloc_id).element_ty.as_ty() == Some(place_ty)
&& let Some(offset) = provenance.offset.simplify().as_u64()
&& let size = self.size_of_ty(place_ty)
&& size != 0
&& offset % size == 0
{
let value = self.units[provenance.alloc_id.0]
.content
.values
.iter()
.find_map(|((ty, path), value)| {
(matches!(ty.kind(), rustc_middle::ty::TyKind::Array(elem, _) if *elem == place_ty)
&& path.as_slice() == [offset as usize / size as usize])
.then_some(value.clone())
});
if let Some(value) = value {
return Some(value);
}
}
if place.projection.len() == 1
&& matches!(place.projection[0], ProjectionElem::Deref)
&& matches!(
place_ty.kind(),
rustc_middle::ty::TyKind::Int(_)
| rustc_middle::ty::TyKind::Uint(_)
| rustc_middle::ty::TyKind::Bool
| rustc_middle::ty::TyKind::Char
| rustc_middle::ty::TyKind::Float(_)
)
{
let pointer = self.local_value(place.local)?;
if let Some(provenance) = &pointer.provenance
&& provenance.offset.simplify().as_u64() == Some(0)
&& let Some(value) = self.load_value(provenance.alloc_id, place_ty, &[])
{
return Some(value.clone());
}
return Some(self.unknown_value_for_place(place));
}
let field_path: Vec<usize> = place
.projection
.iter()
.filter_map(|proj| match proj.kind() {
ProjectionElem::Field(field_idx, _) => Some(field_idx.as_usize()),
_ => None,
})
.collect();
let is_pure_field = place.projection.iter().all(|p| {
matches!(
p.kind(),
ProjectionElem::Field(..) | ProjectionElem::Downcast(..)
)
});
let has_downcast = place
.projection
.iter()
.any(|p| matches!(p.kind(), ProjectionElem::Downcast(..)));
if !field_path.is_empty() && is_pure_field {
if let Some(val) = self.field_value(place.local, &field_path).cloned() {
return Some(val);
}
if let Some(base_val) = self.local_value(place.local)
&& self.is_len_field_read(base_val.ty, &field_path)
&& let Some(len) = self.len_from_value(base_val)
{
return Some(VmValue::new(len, place_ty));
}
if !has_downcast {
if let Some(base_val) = self.local_value(place.local)
&& let Some(ref prov) = base_val.provenance
{
return Some(VmValue {
z3_term: base_val.z3_term.clone(),
ty: place_ty,
provenance: Some(prov.clone()),
facts: base_val.facts.clone(),
source: ValueSource::None,
});
}
return None;
}
}
if !field_path.is_empty()
&& field_path.len() < place.projection.len()
&& place
.projection
.iter()
.any(|p| matches!(p.kind(), ProjectionElem::Deref))
{
let non_field_deref = place.projection.iter().all(|p| {
matches!(
p.kind(),
ProjectionElem::Field(..)
| ProjectionElem::Deref
| ProjectionElem::Subslice { .. }
)
});
if non_field_deref {
if let Some(val) = self.field_value(place.local, &field_path).cloned() {
return Some(val);
}
if let Some(base_val) = self.local_value(place.local)
&& let Some(alloc_id) = base_val.provenance_alloc_id()
{
let view_ty = mir_utils::pointee_ty(base_val.ty).unwrap_or(base_val.ty);
if let Some(val) = self.load_value(alloc_id, view_ty, &field_path).cloned() {
return Some(val);
}
}
if let Some(base_val) = self.local_value(place.local)
&& self.is_len_field_read(base_val.ty, &field_path)
&& let Some(len) = self.len_from_value(base_val)
{
return Some(VmValue::new(len, place_ty));
}
}
}
let mut base = self.local_value(place.local)?.clone();
for proj in place.projection.iter() {
match proj.kind() {
ProjectionElem::Deref => {
base.ty = place_ty;
}
ProjectionElem::Field(_field_idx, _) => {
if !field_path.is_empty()
&& let Some(val) = self.field_value(place.local, &field_path).cloned()
{
return Some(val);
}
base.ty = place_ty;
}
_ => {}
}
}
let proj = place.projection.last().expect("non-empty projection");
let prefix_is_deref = place.projection[..place.projection.len() - 1]
.iter()
.all(|p| matches!(p.kind(), ProjectionElem::Deref));
if prefix_is_deref {
let index = match proj {
ProjectionElem::Index(local) => self
.local_value(*local)
.and_then(|value| value.z3_term.simplify().as_u64()),
ProjectionElem::ConstantIndex {
offset,
from_end: false,
..
} => Some(*offset),
_ => None,
};
if let Some(index) = index
&& let Some(value) = self.field_value(place.local, &[index as usize])
{
return Some(value.clone());
}
if let ProjectionElem::Index(local) = proj
&& let Some(index_value) = self.local_value(*local)
&& matches!(
place_ty.kind(),
rustc_middle::ty::TyKind::Int(_)
| rustc_middle::ty::TyKind::Uint(_)
| rustc_middle::ty::TyKind::Bool
| rustc_middle::ty::TyKind::Char
)
&& let rustc_middle::ty::TyKind::Array(_, length) =
self.body().local_decls[place.local].ty.kind()
&& let Some(length) = length.try_to_target_usize(self.tcx)
{
let repeated = self
.field_value(place.local, &[REPEATED_ARRAY_ELEMENT])
.cloned();
let elements = if repeated.is_some() {
self.field_paths(place.local)
.into_iter()
.filter_map(|path| {
(path.len() == 1
&& path[0] < length as usize
&& path[0] != REPEATED_ARRAY_ELEMENT)
.then(|| {
self.field_value(place.local, &path)
.cloned()
.map(|value| (path[0], value))
})
.flatten()
})
.collect::<Vec<_>>()
} else {
(0..length)
.filter_map(|index| {
self.field_value(place.local, &[index as usize])
.cloned()
.map(|value| (index as usize, value))
})
.collect::<Vec<_>>()
};
if repeated.is_some() || elements.len() == length as usize {
let mut term = repeated
.map(|value| value.z3_term)
.unwrap_or_else(|| self.fresh_int("array_index"));
for (index, element) in elements {
let selected = index_value
.z3_term
._eq(&Int::from_u64(self.z3_ctx, index as u64));
term = selected.ite(&element.z3_term, &term);
}
return Some(VmValue::new(term, place_ty));
}
}
}
if let ProjectionElem::Index(local) = proj {
if prefix_is_deref && let Some(ref prov) = base.provenance {
let alloc_id = prov.alloc_id;
let decl_ty = self.body().local_decls[place.local].ty;
let inner_ty = match decl_ty.kind() {
rustc_middle::ty::TyKind::Array(inner, _) => *inner,
rustc_middle::ty::TyKind::Ref(_, inner, _) => match inner.kind() {
rustc_middle::ty::TyKind::Slice(e) => *e,
_ => return Some(base.clone()),
},
rustc_middle::ty::TyKind::Slice(e) => *e,
_ => return Some(base.clone()),
};
let elem_sz = self.size_of_ty(inner_ty) as usize;
let step = elem_sz.max(1);
if let Some(index_val) = self.local_value(*local) {
let offset = Int::mul(
self.z3_ctx,
&[&index_val.z3_term, &Int::from_u64(self.z3_ctx, step as u64)],
);
if self.units[alloc_id.0].content.byte_array.is_some() {
let term = self.byte_read(alloc_id, &offset);
let is_uninit = offset
.simplify()
.as_u64()
.map(|off| !self.is_byte_init(alloc_id, off as usize))
.unwrap_or(false);
if !is_uninit {
return Some(VmValue {
z3_term: term,
ty: place_ty,
provenance: None,
facts: ValueFacts::default(),
source: ValueSource::None,
});
}
}
if let Some(off) = offset.simplify().as_u64()
&& let Some(ty) = self.alloc(alloc_id).element_ty.as_ty()
&& let Some(b) = self.byte_from_field(alloc_id, ty, off as usize)
{
return Some(VmValue {
z3_term: b,
ty: place_ty,
provenance: None,
facts: ValueFacts::default(),
source: ValueSource::None,
});
}
}
}
return Some(base.clone());
}
let mut val = base;
val.ty = place_ty;
Some(val)
}
fn is_len_field_read(&self, ty: Ty<'tcx>, field_path: &[usize]) -> bool {
if field_path.len() != 1 {
return false;
}
let field = field_path[0];
let adt = match ty.kind() {
rustc_middle::ty::TyKind::Ref(_, inner, _)
| rustc_middle::ty::TyKind::RawPtr(inner, _) => match inner.kind() {
rustc_middle::ty::TyKind::Slice(_) | rustc_middle::ty::TyKind::Str => {
return field == 1;
}
rustc_middle::ty::TyKind::Adt(adt_def, _) => *adt_def,
_ => return false,
},
rustc_middle::ty::TyKind::Adt(adt_def, _) => *adt_def,
_ => return false,
};
adt.is_struct()
&& adt
.non_enum_variant()
.fields
.get(rustc_abi::FieldIdx::from_usize(field))
.is_some_and(|f| f.name.as_str() == "len")
}
pub(crate) fn unknown_value_for_place(&self, place: &Place<'tcx>) -> VmValue<'z3, 'tcx> {
let ty = place.ty(self.body(), self.tcx).ty;
VmValue::new(self.fresh_int("unknown"), ty)
}
}