1use crate::{
26 analysis::utils::fn_info::{get_pointee, is_ptr, is_ref},
27 check::senryx::{
28 contract::{AlignState, PropertyContract},
29 symbolic_analysis::{AnaOperand, SymbolicDef, verify_with_z3},
30 visitor::{BodyVisitor, PlaceTy},
31 },
32 rap_debug,
33};
34use rustc_middle::{mir::BinOp, ty::Ty};
35use z3::ast::{Ast, BV};
36
37impl<'tcx> BodyVisitor<'tcx> {
38 pub fn check_align(&self, arg: usize, contract_required_ty: Ty<'tcx>) -> bool {
45 let required_ty_layout = self.visit_ty_and_get_layout(contract_required_ty);
46 if self.check_align_from_facts(arg, &required_ty_layout) {
47 return true;
48 }
49
50 if let Some((op, base_local, offset_op, stride_layout)) = self.get_ptr_offset_info(arg) {
51 return self.check_offset_align_with_z3(
52 op,
53 base_local,
54 offset_op,
55 stride_layout,
56 contract_required_ty,
57 );
58 }
59
60 self.check_align_directly(arg, required_ty_layout)
61 }
62
63 fn check_align_from_facts(&self, arg: usize, required_layout: &PlaceTy<'tcx>) -> bool {
65 if let Some(var) = self.chains.get_var_node(arg) {
66 for fact in &var.facts.contracts {
67 if let PropertyContract::Align(fact_ty) = fact {
68 let fact_layout = self.visit_ty_and_get_layout(*fact_ty);
69 if Self::two_types_cast_check(fact_layout, required_layout.clone()) {
70 return true;
71 }
72 }
73 }
74 }
75 false
76 }
77
78 fn check_align_directly(&self, pointer_id: usize, required_ty: PlaceTy<'tcx>) -> bool {
80 if let Some(pointee) = self.chains.get_obj_ty_through_chain(pointer_id) {
81 let pointee_ty = self.visit_ty_and_get_layout(pointee);
82 let pointer = self.chains.get_var_node(pointer_id).unwrap();
83
84 if let AlignState::Aligned(_) = pointer.ots.align {
85 return Self::two_types_cast_check(pointee_ty, required_ty);
86 }
87 }
88 false
89 }
90
91 fn check_offset_align_with_z3(
97 &self,
98 op: BinOp,
99 base_local: usize,
100 offset_op: AnaOperand,
101 stride_layout: PlaceTy<'tcx>,
102 contract_required_ty: Ty<'tcx>,
103 ) -> bool {
104 let req_layout = self.visit_ty_and_get_layout(contract_required_ty);
105 let mut req_aligns = req_layout.possible_aligns();
106
107 if let PlaceTy::GenericTy(..) = req_layout {
108 if req_aligns.is_empty() {
109 req_aligns.extend([1, 2, 4, 8, 16, 32, 64]);
110 }
111 }
112
113 if req_aligns.len() == 1 && req_aligns.contains(&1) {
114 return true;
115 }
116
117 let base_node = if let Some(node) = self.chains.get_var_node(base_local) {
118 node
119 } else {
120 return false;
121 };
122
123 let base_pointee_ty = if let Some(ty) = base_node.ty {
124 get_pointee(ty)
125 } else {
126 return false;
127 };
128
129 let base_layout = self.visit_ty_and_get_layout(base_pointee_ty);
130 let mut base_aligns = base_layout.possible_aligns();
131
132 if let PlaceTy::GenericTy(..) = base_layout {
133 if base_aligns.is_empty() {
134 base_aligns.extend([1, 2, 4, 8, 16, 32, 64]);
135 }
136 }
137
138 rap_debug!(
139 "Z3 Align Check: base_{} {:?} (aligns {:?}) {:?} offset (stride {:?}) => req_aligns {:?}",
140 base_local,
141 op,
142 base_aligns,
143 op,
144 stride_layout,
145 req_aligns
146 );
147
148 verify_with_z3(
149 self.value_domains.clone(),
150 self.path_constraints.clone(),
151 |ctx, vars| {
152 let bv_zero = BV::from_u64(ctx, 0, 64);
153
154 let bv_base = if let Some(b) = vars.get(&base_local) {
155 b.clone()
156 } else {
157 return z3::ast::Bool::from_bool(ctx, false);
158 };
159
160 let bv_index = match &offset_op {
161 AnaOperand::Local(idx) => {
162 if let Some(v) = vars.get(idx) {
163 v.clone()
164 } else {
165 BV::from_u64(ctx, 0, 64)
166 }
167 }
168 AnaOperand::Const(val) => BV::from_u64(ctx, *val as u64, 64),
169 };
170
171 let possible_strides: Vec<u64> = match &stride_layout {
172 PlaceTy::Ty(_, size) => vec![*size as u64],
173 PlaceTy::GenericTy(_, _, layout_set) => {
174 if layout_set.is_empty() {
175 vec![1, 2, 4, 8, 16, 32, 64]
176 } else {
177 layout_set.iter().map(|(_, size)| *size as u64).collect()
178 }
179 }
180 PlaceTy::Unknown => vec![1],
181 };
182
183 let mut constraints = Vec::new();
184 let is_same_generic = match (&req_layout, &base_layout) {
185 (PlaceTy::GenericTy(n1, _, _), PlaceTy::GenericTy(n2, _, _)) => n1 == n2,
186 _ => false,
187 };
188
189 for stride in possible_strides {
190 let bv_stride = BV::from_u64(ctx, stride, 64);
191 let bv_byte_offset = bv_index.bvmul(&bv_stride);
192 let result_ptr = match op {
193 BinOp::Add => bv_base.bvadd(&bv_byte_offset),
194 BinOp::Sub => bv_base.bvsub(&bv_byte_offset),
195 _ => bv_base.bvadd(&bv_byte_offset),
196 };
197
198 if is_same_generic {
199 for align in &req_aligns {
200 let bv_align = BV::from_u64(ctx, *align as u64, 64);
201 let base_is_aligned = bv_base.bvurem(&bv_align)._eq(&bv_zero);
202 let result_aligned = result_ptr.bvurem(&bv_align)._eq(&bv_zero);
203 constraints.push(base_is_aligned.implies(&result_aligned));
204 }
205 } else {
206 for b_align in &base_aligns {
207 for r_align in &req_aligns {
208 let bv_base_align = BV::from_u64(ctx, *b_align as u64, 64);
209 let bv_req_align = BV::from_u64(ctx, *r_align as u64, 64);
210 let base_is_aligned = bv_base.bvurem(&bv_base_align)._eq(&bv_zero);
211 let result_aligned = result_ptr.bvurem(&bv_req_align)._eq(&bv_zero);
212 constraints.push(base_is_aligned.implies(&result_aligned));
213 }
214 }
215 }
216 }
217
218 if constraints.is_empty() {
219 return z3::ast::Bool::from_bool(ctx, false);
220 }
221
222 let constraints_refs: Vec<&z3::ast::Bool> = constraints.iter().collect();
223 z3::ast::Bool::and(ctx, &constraints_refs)
224 },
225 )
226 }
227
228 fn get_ptr_offset_info(&self, arg: usize) -> Option<(BinOp, usize, AnaOperand, PlaceTy<'tcx>)> {
230 if let Some(domain) = self.chains.get_var_node(arg) {
231 if let Some(SymbolicDef::PtrOffset(op, base, off, place_ty)) = &domain.offset_from {
232 return Some((*op, *base, off.clone(), place_ty.clone()));
233 }
234 }
235 None
236 }
237
238 pub fn update_align_state(&mut self, ptr_local: usize, is_aligned: bool) {
244 let ptr_ty_opt = self.chains.get_var_node(ptr_local).and_then(|n| n.ty);
245
246 if let Some(ptr_ty) = ptr_ty_opt {
247 if is_ptr(ptr_ty) || is_ref(ptr_ty) {
248 let pointee_ty = get_pointee(ptr_ty);
249
250 if let Some(ptr_node) = self.chains.get_var_node_mut(ptr_local) {
251 if is_aligned {
252 ptr_node.ots.align = AlignState::Aligned(pointee_ty);
253 rap_debug!(
254 "Refine State: _{} (source) marked as Aligned({:?}) via condition (True).",
255 ptr_local,
256 pointee_ty
257 );
258 } else {
259 ptr_node.ots.align = AlignState::Unaligned(pointee_ty);
260 rap_debug!(
261 "Refine State: _{} (source) marked as Unaligned({:?}) via condition (False).",
262 ptr_local,
263 pointee_ty
264 );
265 }
266 }
267 }
268 }
269 }
270
271 pub fn check_align_by_pre_computed_state(
273 &self,
274 arg: usize,
275 contract_required_ty: Ty<'tcx>,
276 ) -> bool {
277 if let Some(var) = self.chains.get_var_node(arg) {
278 if let AlignState::Aligned(state_ty) = var.ots.align {
279 let state_layout = self.visit_ty_and_get_layout(state_ty);
280 let req_layout = self.visit_ty_and_get_layout(contract_required_ty);
281
282 rap_debug!(
283 "Check Align: arg_{} StateTy: {:?} vs ReqTy: {:?}",
284 arg,
285 state_layout,
286 req_layout
287 );
288
289 return Self::two_types_cast_check(state_layout, req_layout);
290 } else {
291 rap_debug!("Check Align: arg_{} is Unaligned or Unknown", arg);
292 }
293 } else {
294 rap_debug!("Check Align: arg_{} node not found", arg);
295 }
296 false
297 }
298
299 fn two_types_cast_check(src: PlaceTy<'tcx>, dest: PlaceTy<'tcx>) -> bool {
301 let src_aligns = src.possible_aligns();
302 let dest_aligns = dest.possible_aligns();
303 if dest_aligns.len() == 0 && src != dest {
304 return false;
305 }
306
307 for &d_align in &dest_aligns {
308 if d_align != 1 && src_aligns.len() == 0 {
309 return false;
310 }
311 for &s_align in &src_aligns {
312 if s_align < d_align {
313 return false;
314 }
315 }
316 }
317 true
318 }
319
320 fn _try_refine_alignment(&self, base_local: usize, current_align: u64) -> u64 {
322 for candidate in [64, 32, 16, 8, 4] {
323 if candidate <= current_align {
324 break;
325 }
326
327 let is_proven = verify_with_z3(
328 self.value_domains.clone(),
329 self.path_constraints.clone(),
330 |ctx, vars| {
331 if let Some(bv_base) = vars.get(&base_local) {
332 let bv_cand = BV::from_u64(ctx, candidate, 64);
333 let bv_zero = BV::from_u64(ctx, 0, 64);
334 bv_base.bvurem(&bv_cand)._eq(&bv_zero)
335 } else {
336 z3::ast::Bool::from_bool(ctx, false)
337 }
338 },
339 );
340
341 if is_proven {
342 rap_debug!(
343 "Refine Align: Successfully refined base_{} to align {}",
344 base_local,
345 candidate
346 );
347 return candidate;
348 }
349 }
350 current_align
351 }
352
353 fn _check_offset_is_aligned(&self, offset: &AnaOperand, align: u64) -> bool {
355 verify_with_z3(
356 self.value_domains.clone(),
357 self.path_constraints.clone(),
358 |ctx, vars| {
359 let bv_align = BV::from_u64(ctx, align, 64);
360 let bv_zero = BV::from_u64(ctx, 0, 64);
361
362 let bv_offset = match offset {
363 AnaOperand::Local(idx) => {
364 if let Some(v) = vars.get(idx) {
365 v.clone()
366 } else {
367 BV::from_u64(ctx, 0, 64)
368 }
369 }
370 AnaOperand::Const(val) => BV::from_u64(ctx, *val as u64, 64),
371 };
372
373 bv_offset.bvurem(&bv_align)._eq(&bv_zero)
374 },
375 )
376 }
377
378 fn _check_cumulative_offset_aligned(
380 &self,
381 acc_def: &SymbolicDef<'tcx>,
382 curr_op: &AnaOperand,
383 align: u64,
384 ) -> bool {
385 verify_with_z3(
386 self.value_domains.clone(),
387 self.path_constraints.clone(),
388 |ctx, vars| {
389 let bv_align = BV::from_u64(ctx, align, 64);
390 let bv_zero = BV::from_u64(ctx, 0, 64);
391 let bv_acc = self.symbolic_def_to_bv(ctx, vars, acc_def);
392 let bv_curr = match curr_op {
393 AnaOperand::Local(idx) => {
394 if let Some(v) = vars.get(idx) {
395 v.clone()
396 } else {
397 BV::from_u64(ctx, 0, 64)
398 }
399 }
400 AnaOperand::Const(val) => BV::from_u64(ctx, *val as u64, 64),
401 };
402 let total = bv_acc.bvadd(&bv_curr);
403 total.bvurem(&bv_align)._eq(&bv_zero)
404 },
405 )
406 }
407
408 fn symbolic_def_to_bv<'a>(
410 &self,
411 ctx: &'a z3::Context,
412 vars: &std::collections::HashMap<usize, BV<'a>>,
413 def: &SymbolicDef<'tcx>,
414 ) -> BV<'a> {
415 match def {
416 SymbolicDef::Constant(c) => BV::from_u64(ctx, *c as u64, 64),
417 SymbolicDef::Use(l) => vars.get(l).cloned().unwrap_or(BV::from_u64(ctx, 0, 64)),
418 SymbolicDef::Binary(op, lhs, rhs) => {
419 let lhs_bv = vars.get(lhs).cloned().unwrap_or(BV::from_u64(ctx, 0, 64));
420 let rhs_bv = match rhs {
421 AnaOperand::Local(l) => {
422 vars.get(l).cloned().unwrap_or(BV::from_u64(ctx, 0, 64))
423 }
424 AnaOperand::Const(c) => BV::from_u64(ctx, *c as u64, 64),
425 };
426 match op {
427 BinOp::Add => lhs_bv.bvadd(&rhs_bv),
428 _ => BV::from_u64(ctx, 0, 64),
429 }
430 }
431 _ => BV::from_u64(ctx, 0, 64),
432 }
433 }
434}