rapx/analysis/senryx/
visitor.rs

1use crate::{
2    analysis::{
3        Analysis,
4        core::{
5            alias_analysis::AAResult,
6            ownedheap_analysis::OHAResultMap,
7            range_analysis::{RangeAnalysis, default::RangeAnalyzer},
8        },
9        graphs::scc::Scc,
10        safedrop::graph::SafeDropGraph,
11        senryx::{
12            contracts::{
13                abstract_state::AlignState,
14                property::{CisRangeItem, PropertyContract},
15            },
16            dominated_graph::FunctionSummary,
17            symbolic_analysis::{AnaOperand, SymbolicDef, ValueDomain},
18        },
19        utils::{draw_dot::render_dot_string, fn_info::*, show_mir::display_mir},
20    },
21    rap_debug, rap_warn,
22};
23use rustc_middle::ty::GenericParamDefKind;
24use serde::de;
25use std::{
26    collections::{HashMap, HashSet},
27    fmt::Debug,
28    hash::Hash,
29};
30use syn::Constraint;
31
32use super::dominated_graph::DominatedGraph;
33use super::dominated_graph::InterResultNode;
34use super::generic_check::GenericChecker;
35use super::matcher::UnsafeApi;
36use super::matcher::{get_arg_place, parse_unsafe_api};
37use rustc_data_structures::fx::FxHashMap;
38use rustc_hir::def_id::DefId;
39use rustc_middle::{
40    mir::{
41        self, AggregateKind, BasicBlock, BasicBlockData, BinOp, CastKind, Local, Operand, Place,
42        ProjectionElem, Rvalue, Statement, StatementKind, Terminator, TerminatorKind,
43    },
44    ty::{self, GenericArgKind, PseudoCanonicalInput, Ty, TyCtxt, TyKind},
45};
46use rustc_span::{Span, source_map::Spanned};
47
48#[derive(Debug, PartialEq, Eq, Clone)]
49pub struct CheckResult {
50    pub func_name: String,
51    pub func_span: Span,
52    pub failed_contracts: HashMap<usize, HashSet<String>>,
53    pub passed_contracts: HashMap<usize, HashSet<String>>,
54}
55
56impl CheckResult {
57    /// Create a new CheckResult for a function.
58    /// func_name: cleaned function path, func_span: source span of the function.
59    pub fn new(func_name: &str, func_span: Span) -> Self {
60        Self {
61            func_name: func_name.to_string(),
62            func_span,
63            failed_contracts: HashMap::new(),
64            passed_contracts: HashMap::new(),
65        }
66    }
67}
68
69#[derive(Debug, PartialEq, Eq, Clone)]
70pub enum PlaceTy<'tcx> {
71    Ty(usize, usize), // layout(align,size) of one specific type
72    GenericTy(String, HashSet<Ty<'tcx>>, HashSet<(usize, usize)>), // get specific type in generic map
73    Unknown,
74}
75
76impl<'tcx> PlaceTy<'tcx> {
77    /// Return the set of possible ABI alignments for this place/type.
78    /// - For concrete types returns a single-element set with the ABI alignment.
79    /// - For generic type parameters returns all candidate alignments collected from
80    ///   the generic instantiation set.
81    pub fn possible_aligns(&self) -> HashSet<usize> {
82        match self {
83            PlaceTy::Ty(align, _size) => {
84                let mut set = HashSet::new();
85                set.insert(*align);
86                set
87            }
88            PlaceTy::GenericTy(_, _, tys) => tys.iter().map(|ty| ty.0).collect(),
89            _ => HashSet::new(),
90        }
91    }
92}
93
94impl<'tcx> Hash for PlaceTy<'tcx> {
95    /// Custom hash implementation placeholder for `PlaceTy`.
96    /// Currently a no-op because `PlaceTy` is used primarily as a key in internal
97    /// analysis maps where exact hashing is not required. This keeps behavior stable.
98    fn hash<H: std::hash::Hasher>(&self, _state: &mut H) {}
99}
100
101/// Visitor that traverses MIR body and builds symbolic and pointer chains.
102/// Holds analysis state such as type mappings, value domains and constraints.
103pub struct BodyVisitor<'tcx> {
104    pub tcx: TyCtxt<'tcx>,
105    pub def_id: DefId,
106    pub safedrop_graph: SafeDropGraph<'tcx>,
107    pub local_ty: HashMap<usize, PlaceTy<'tcx>>,
108    pub visit_time: usize,
109    pub check_results: Vec<CheckResult>,
110    pub generic_map: HashMap<String, HashSet<Ty<'tcx>>>,
111    pub proj_ty: HashMap<usize, Ty<'tcx>>,
112    pub chains: DominatedGraph<'tcx>,
113    pub value_domains: HashMap<usize, ValueDomain<'tcx>>,
114    pub path_constraints: Vec<SymbolicDef<'tcx>>,
115}
116
117// === Partition: Initialization & state ===
118// Initialization and visitor state: constructors, field maps and helpers used globally
119// by subsequent analysis phases.
120impl<'tcx> BodyVisitor<'tcx> {
121    /// Construct a new BodyVisitor for `def_id`.
122    /// Initializes helper structures and generic type map.
123    pub fn new(tcx: TyCtxt<'tcx>, def_id: DefId, visit_time: usize) -> Self {
124        let body = tcx.optimized_mir(def_id);
125        let param_env = tcx.param_env(def_id);
126        let satisfied_ty_map_for_generic =
127            GenericChecker::new(tcx, param_env).get_satisfied_ty_map();
128        let mut chains = DominatedGraph::new(tcx, def_id);
129        chains.init_arg();
130        Self {
131            tcx,
132            def_id,
133            safedrop_graph: SafeDropGraph::new(tcx, def_id, OHAResultMap::default()),
134            local_ty: HashMap::new(),
135            visit_time,
136            check_results: Vec::new(),
137            generic_map: satisfied_ty_map_for_generic,
138            proj_ty: HashMap::new(),
139            chains,
140            value_domains: HashMap::new(),
141            path_constraints: Vec::new(),
142        }
143    }
144}
145
146/// === Partition: Path-sensitive analysis driver ===
147/// Compute and iterate control-flow paths and merge results.
148impl<'tcx> BodyVisitor<'tcx> {
149    /// Perform path-sensitive forward analysis.
150    /// Uses Tarjan-produced paths and performs per-path symbolic and pointer analysis.
151    /// Returns an InterResultNode merging all path results.
152    pub fn path_forward_check(
153        &mut self,
154        fn_map: &FxHashMap<DefId, AAResult>,
155    ) -> InterResultNode<'tcx> {
156        let mut inter_return_value =
157            InterResultNode::construct_from_var_node(self.chains.clone(), 0);
158        if self.visit_time >= 1000 {
159            return inter_return_value;
160        }
161        // get path and body
162        let paths = self.get_all_paths();
163        let body = self.tcx.optimized_mir(self.def_id);
164        let target_name = get_cleaned_def_path_name(self.tcx, self.def_id);
165        // initialize local vars' types
166        let locals = body.local_decls.clone();
167        for (idx, local) in locals.iter().enumerate() {
168            let local_ty = local.ty;
169            let layout = self.visit_ty_and_get_layout(local_ty);
170            self.local_ty.insert(idx, layout);
171        }
172
173        // Iterate all the paths. Paths have been handled by tarjan.
174        let tmp_chain = self.chains.clone();
175        for (index, (path, constraint)) in paths.iter().enumerate() {
176            // Init three data structures in every path
177            self.value_domains.clear();
178            for (arg_index, _) in body.args_iter().enumerate() {
179                let local = arg_index + 1;
180                self.record_value_def(local, SymbolicDef::Param(local));
181            }
182            self.path_constraints = Vec::new();
183            self.chains = tmp_chain.clone();
184            self.set_constraint(constraint);
185            for (i, block_index) in path.iter().enumerate() {
186                if block_index >= &body.basic_blocks.len() {
187                    continue;
188                }
189                let next_block = path.get(i + 1).cloned();
190                self.path_analyze_block(
191                    &body.basic_blocks[BasicBlock::from_usize(*block_index)].clone(),
192                    index,
193                    *block_index,
194                    next_block,
195                    fn_map,
196                );
197                let tem_basic_blocks = &self.safedrop_graph.mop_graph.blocks[*block_index]
198                    .scc
199                    .nodes
200                    .clone();
201                // also analyze basic blocks that belong to dominated SCCs
202                if tem_basic_blocks.len() > 0 {
203                    for sub_block in tem_basic_blocks {
204                        self.path_analyze_block(
205                            &body.basic_blocks[BasicBlock::from_usize(*sub_block)].clone(),
206                            index,
207                            *block_index,
208                            next_block,
209                            fn_map,
210                        );
211                    }
212                }
213            }
214
215            // Used for debug
216            // If running detailed (visit_time == 0), show debug reports.
217            if self.visit_time == 0 {
218                // self.display_combined_debug_info();
219            }
220
221            // merge path analysis results
222            let curr_path_inter_return_value =
223                InterResultNode::construct_from_var_node(self.chains.clone(), 0);
224            inter_return_value.merge(curr_path_inter_return_value);
225        }
226
227        inter_return_value
228    }
229
230    /// Analyze one basic block: process statements then its terminator for the current path.
231    pub fn path_analyze_block(
232        &mut self,
233        block: &BasicBlockData<'tcx>,
234        path_index: usize,
235        bb_index: usize,
236        next_block: Option<usize>,
237        fn_map: &FxHashMap<DefId, AAResult>,
238    ) {
239        for statement in block.statements.iter() {
240            self.path_analyze_statement(statement, path_index);
241        }
242        self.path_analyze_terminator(
243            &block.terminator(),
244            path_index,
245            bb_index,
246            next_block,
247            fn_map,
248        );
249    }
250
251    /// Retrieve all paths and optional range-based constraints for this function.
252    /// Falls back to safedrop graph paths if range analysis did not produce constraints.
253    pub fn get_all_paths(&mut self) -> HashMap<Vec<usize>, Vec<(Place<'tcx>, Place<'tcx>, BinOp)>> {
254        let mut range_analyzer = RangeAnalyzer::<i64>::new(self.tcx, false);
255        let path_constraints_option =
256            range_analyzer.start_path_constraints_analysis_for_defid(self.def_id); // if def_id does not exist, this will break down
257        let mut path_constraints: HashMap<Vec<usize>, Vec<(_, _, _)>> =
258            if path_constraints_option.is_none() {
259                let mut results = HashMap::new();
260                let paths: Vec<Vec<usize>> = self.safedrop_graph.mop_graph.get_paths();
261                for path in paths {
262                    results.insert(path, Vec::new());
263                }
264                results
265            } else {
266                path_constraints_option.unwrap()
267            };
268        self.safedrop_graph.mop_graph.find_scc();
269        // If this is the top-level analysis, keep only paths that contain unsafe calls.
270        if self.visit_time == 0 {
271            let contains_unsafe_blocks = get_all_std_unsafe_callees_block_id(self.tcx, self.def_id);
272            path_constraints.retain(|path, cons| {
273                path.iter()
274                    .any(|block_id| contains_unsafe_blocks.contains(block_id))
275            });
276        }
277        path_constraints
278    }
279}
280
281// === Partition: Statement and Rvalue handlers ===
282// Assignment and rvalue processing, symbolic defs and chains.
283impl<'tcx> BodyVisitor<'tcx> {
284    /// Dispatch analysis for a single MIR statement (assignments, intrinsics, etc.).
285    pub fn path_analyze_statement(&mut self, statement: &Statement<'tcx>, _path_index: usize) {
286        // Examine MIR statements and dispatch to specific handlers.
287        match statement.kind {
288            StatementKind::Assign(box (ref lplace, ref rvalue)) => {
289                self.path_analyze_assign(lplace, rvalue, _path_index);
290            }
291            StatementKind::Intrinsic(box ref intrinsic) => match intrinsic {
292                mir::NonDivergingIntrinsic::CopyNonOverlapping(cno) => {
293                    if cno.src.place().is_some() && cno.dst.place().is_some() {
294                        let _src_pjc_local =
295                            self.handle_proj(true, cno.src.place().unwrap().clone());
296                        let _dst_pjc_local =
297                            self.handle_proj(true, cno.dst.place().unwrap().clone());
298                    }
299                }
300                _ => {}
301            },
302            StatementKind::StorageDead(local) => {}
303            _ => {}
304        }
305    }
306
307    /// Handle assignment rvalues: record symbolic defs and update pointer/alias chains.
308    pub fn path_analyze_assign(
309        &mut self,
310        lplace: &Place<'tcx>,
311        rvalue: &Rvalue<'tcx>,
312        path_index: usize,
313    ) {
314        let lpjc_local = self.handle_proj(false, lplace.clone());
315        match rvalue {
316            Rvalue::Use(op) => {
317                if let Some(ana_op) = self.lift_operand(op) {
318                    let def = match ana_op {
319                        AnaOperand::Local(src) => SymbolicDef::Use(src),
320                        AnaOperand::Const(val) => SymbolicDef::Constant(val),
321                    };
322                    self.record_value_def(lpjc_local, def);
323                }
324                match op {
325                    Operand::Move(rplace) => {
326                        let rpjc_local = self.handle_proj(true, rplace.clone());
327                        self.chains.merge(lpjc_local, rpjc_local);
328                    }
329                    Operand::Copy(rplace) => {
330                        let rpjc_local = self.handle_proj(true, rplace.clone());
331                        self.chains.copy_node(lpjc_local, rpjc_local);
332                    }
333                    _ => {}
334                }
335            }
336            Rvalue::Repeat(op, _const) => match op {
337                Operand::Move(rplace) | Operand::Copy(rplace) => {
338                    let _rpjc_local = self.handle_proj(true, rplace.clone());
339                }
340                _ => {}
341            },
342            Rvalue::Ref(_, _, rplace) | Rvalue::RawPtr(_, rplace) => {
343                // Recording that the left-hand side is a reference to right-hand side.
344                let rpjc_local = self.handle_proj(true, rplace.clone());
345                self.record_value_def(lpjc_local, SymbolicDef::Ref(rpjc_local));
346                self.chains.point(lpjc_local, rpjc_local);
347            }
348            // ThreadLocalRef: x = &thread_local_static
349            Rvalue::ThreadLocalRef(_def_id) => {
350                // todo
351            }
352            // Cast: x = y as T
353            Rvalue::Cast(cast_kind, op, ty) => {
354                if let Some(AnaOperand::Local(src_idx)) = self.lift_operand(op) {
355                    self.record_value_def(
356                        lpjc_local,
357                        SymbolicDef::Cast(src_idx, format!("{:?}", cast_kind)),
358                    );
359                }
360                match op {
361                    Operand::Move(rplace) | Operand::Copy(rplace) => {
362                        let rpjc_local = self.handle_proj(true, rplace.clone());
363                        let r_point_to = self.chains.get_point_to_id(rpjc_local);
364                        if r_point_to == rpjc_local {
365                            self.chains.merge(lpjc_local, rpjc_local);
366                        } else {
367                            self.chains.point(lpjc_local, r_point_to);
368                        }
369                    }
370                    _ => {}
371                }
372            }
373            Rvalue::BinaryOp(bin_op, box (op1, op2)) => {
374                // Binary operator: try to form a symbolic binary definition when possible.
375                if let (Some(ana_op1), Some(ana_op2)) =
376                    (self.lift_operand(op1), self.lift_operand(op2))
377                {
378                    // Handle pointer offset operations specially
379                    if *bin_op == BinOp::Offset {
380                        if let AnaOperand::Local(base) = ana_op1 {
381                            let base_ty = self.get_ptr_pointee_layout(base);
382                            self.record_value_def(
383                                lpjc_local,
384                                SymbolicDef::PtrOffset(*bin_op, base, ana_op2, base_ty),
385                            );
386                            return;
387                        }
388                    }
389                    // Handle other binary operations
390                    let def = match (ana_op1.clone(), ana_op2) {
391                        (AnaOperand::Local(l), rhs) => Some(SymbolicDef::Binary(*bin_op, l, rhs)),
392                        (AnaOperand::Const(_), AnaOperand::Local(l)) => match bin_op {
393                            BinOp::Add
394                            | BinOp::Mul
395                            | BinOp::BitAnd
396                            | BinOp::BitOr
397                            | BinOp::BitXor
398                            | BinOp::Eq
399                            | BinOp::Ne => Some(SymbolicDef::Binary(*bin_op, l, ana_op1)),
400                            _ => None,
401                        },
402                        _ => None,
403                    };
404
405                    if let Some(d) = def {
406                        self.record_value_def(lpjc_local, d);
407                    } else if let (AnaOperand::Const(c), AnaOperand::Local(l)) = (
408                        self.lift_operand(op1).unwrap(),
409                        self.lift_operand(op2).unwrap(),
410                    ) {
411                        if matches!(bin_op, BinOp::Add | BinOp::Mul | BinOp::Eq) {
412                            self.record_value_def(
413                                lpjc_local,
414                                SymbolicDef::Binary(*bin_op, l, AnaOperand::Const(c)),
415                            );
416                        }
417                    }
418                }
419            }
420            // NullaryOp: x = SizeOf(T); This is runtime checks
421            Rvalue::NullaryOp(_null_op) => {
422                // todo
423            }
424            // UnaryOp: x = !y / x = -y
425            Rvalue::UnaryOp(un_op, op) => {
426                // Unary op: record unary operation on LHS (operand value not stored here).
427                self.record_value_def(lpjc_local, SymbolicDef::UnOp(*un_op));
428            }
429            // Discriminant: x = discriminant(y); read enum tag
430            Rvalue::Discriminant(_place) => {
431                // todo
432            }
433            // Aggregate: x = (y, z) / x = [y, z] / x = S { f: y }
434            Rvalue::Aggregate(box agg_kind, op_vec) => match agg_kind {
435                AggregateKind::Array(_ty) => {}
436                AggregateKind::Adt(_adt_def_id, _, _, _, _) => {
437                    for (idx, op) in op_vec.into_iter().enumerate() {
438                        let (is_const, val) = get_arg_place(op);
439                        if is_const {
440                            self.chains.insert_field_node(
441                                lpjc_local,
442                                idx,
443                                Some(Ty::new_uint(self.tcx, rustc_middle::ty::UintTy::Usize)),
444                            );
445                        } else {
446                            let node = self.chains.get_var_node_mut(lpjc_local).unwrap();
447                            node.field.insert(idx, val);
448                        }
449                    }
450                }
451                _ => {}
452            },
453            Rvalue::ShallowInitBox(op, _ty) => match op {
454                Operand::Move(rplace) | Operand::Copy(rplace) => {
455                    let _rpjc_local = self.handle_proj(true, rplace.clone());
456                }
457                _ => {}
458            },
459            Rvalue::CopyForDeref(p) => {
460                let op = Operand::Copy(p.clone());
461                if let Some(ana_op) = self.lift_operand(&op) {
462                    let def = match ana_op {
463                        AnaOperand::Local(src) => SymbolicDef::Use(src),
464                        AnaOperand::Const(val) => SymbolicDef::Constant(val),
465                    };
466                    self.record_value_def(lpjc_local, def);
467                }
468            }
469            _ => {}
470        }
471    }
472
473    /// Get the layout of the pointee type of a pointer or reference.
474    pub fn get_ptr_pointee_layout(&self, ptr_local: usize) -> PlaceTy<'tcx> {
475        if let Some(node) = self.chains.get_var_node(ptr_local) {
476            if let Some(ty) = node.ty {
477                if is_ptr(ty) || is_ref(ty) {
478                    let pointee = get_pointee(ty);
479                    return self.visit_ty_and_get_layout(pointee);
480                }
481            }
482        }
483        PlaceTy::Unknown
484    }
485}
486
487/// === Partition: Terminator handling ===
488/// Terminator handling: calls, drops and branch-switch translation into constraints.
489impl<'tcx> BodyVisitor<'tcx> {
490    /// Analyze a MIR terminator (calls, drops, switches, etc.) for a path.
491    /// Updates chains/value domains based on call, drop and switch semantics.
492    pub fn path_analyze_terminator(
493        &mut self,
494        terminator: &Terminator<'tcx>,
495        path_index: usize,
496        bb_index: usize,
497        next_block: Option<usize>,
498        fn_map: &FxHashMap<DefId, AAResult>,
499    ) {
500        match &terminator.kind {
501            TerminatorKind::Call {
502                func,
503                args,
504                destination: dst_place,
505                target: _,
506                unwind: _,
507                call_source: _,
508                fn_span,
509            } => {
510                if let Operand::Constant(func_constant) = func {
511                    if let ty::FnDef(callee_def_id, raw_list) = func_constant.const_.ty().kind() {
512                        let mut mapping = FxHashMap::default();
513                        self.get_generic_mapping(raw_list.as_slice(), callee_def_id, &mut mapping);
514                        rap_debug!(
515                            "func {:?}, generic type mapping {:?}",
516                            callee_def_id,
517                            mapping
518                        );
519                        self.handle_call(
520                            dst_place,
521                            callee_def_id,
522                            args,
523                            path_index,
524                            fn_map,
525                            *fn_span,
526                            mapping,
527                        );
528                    }
529                }
530            }
531            TerminatorKind::Drop {
532                place,
533                target: _,
534                unwind: _,
535                replace: _,
536                drop: _,
537                async_fut: _,
538            } => {
539                // Handle explicit drop: mark variable as dropped in chain graph.
540                let drop_local = self.handle_proj(false, *place);
541                if !self.chains.set_drop(drop_local) {
542                    // double-drop detected; optionally warn for debugging.
543                    // rap_warn!(
544                    //     "In path {:?}, double drop {drop_local} in block {bb_index}",
545                    //     self.paths[path_index]
546                    // );
547                }
548            }
549            TerminatorKind::SwitchInt { discr, targets } => {
550                if let Some(next_bb) = next_block {
551                    self.handle_switch_int(discr, targets, next_bb);
552                }
553            }
554            _ => {}
555        }
556    }
557
558    /// Return the MIR type of a local given its numeric `p` index.
559    pub fn get_ty_by_place(&self, p: usize) -> Ty<'tcx> {
560        let body = self.tcx.optimized_mir(self.def_id);
561        let locals = body.local_decls.clone();
562        return locals[Local::from(p)].ty;
563    }
564
565    /// Update field state graph from an inter-procedural result node.
566    pub fn update_fields_states(&mut self, inter_result: InterResultNode<'tcx>) {
567        self.chains.init_self_with_inter(inter_result);
568    }
569
570    /// Get the generic name to an actual type mapping when used for a def_id.
571    /// If current def_id doesn't have generic, then search its parent.
572    /// The generic set include type and allocator.
573    /// Example: generic_mapping (T -> u32, A -> std::alloc::Global)
574    fn get_generic_mapping(
575        &self,
576        raw_list: &[rustc_middle::ty::GenericArg<'tcx>],
577        def_id: &DefId,
578        generic_mapping: &mut FxHashMap<String, Ty<'tcx>>,
579    ) {
580        let generics = self.tcx.generics_of(def_id);
581        for param in &generics.own_params {
582            if let GenericParamDefKind::Type {
583                has_default: _,
584                synthetic: _,
585            } = param.kind
586            {
587                if let Some(ty) = raw_list.get(param.index as usize) {
588                    if let GenericArgKind::Type(actual_ty) = (*ty).kind() {
589                        let param_name = param.name.to_string();
590                        generic_mapping.insert(param_name, actual_ty);
591                    }
592                }
593            }
594        }
595        if generics.own_params.len() == 0 && generics.parent.is_some() {
596            let parent_def_id = generics.parent.unwrap();
597            self.get_generic_mapping(raw_list, &parent_def_id, generic_mapping);
598        }
599    }
600
601    /// Handle a function call: record symbolic Call, check unsafe contracts, and merge aliases.
602    pub fn handle_call(
603        &mut self,
604        dst_place: &Place<'tcx>,
605        def_id: &DefId,
606        args: &Box<[Spanned<Operand<'tcx>>]>,
607        path_index: usize,
608        fn_map: &FxHashMap<DefId, AAResult>,
609        fn_span: Span,
610        generic_mapping: FxHashMap<String, Ty<'tcx>>,
611    ) {
612        // Record call as a symbolic definition for the destination local.
613        let dst_local = self.handle_proj(false, *dst_place);
614        let func_name = get_cleaned_def_path_name(self.tcx, *def_id);
615        let mut call_arg_indices = Vec::new();
616        for arg in args.iter() {
617            if let Some(ana_op) = self.lift_operand(&arg.node) {
618                call_arg_indices.push(ana_op);
619            }
620        }
621        self.record_value_def(dst_local, SymbolicDef::Call(func_name, call_arg_indices));
622
623        if !self.tcx.is_mir_available(def_id) {
624            return;
625        }
626
627        // Find std unsafe API call, then check the contracts.
628        if let Some(fn_result) =
629            parse_unsafe_api(get_cleaned_def_path_name(self.tcx, *def_id).as_str())
630        {
631            self.handle_std_unsafe_call(
632                dst_place,
633                def_id,
634                args,
635                path_index,
636                fn_map,
637                fn_span,
638                fn_result,
639                generic_mapping,
640            );
641        }
642
643        self.handle_offset_call(dst_place, def_id, args);
644
645        self.set_bound(def_id, dst_place, args);
646
647        // merge alias results
648        self.handle_ret_alias(dst_place, def_id, fn_map, args);
649    }
650
651    /// For certain library calls (e.g. `slice::len`), bind computed values into object contracts.
652    fn set_bound(
653        &mut self,
654        def_id: &DefId,
655        dst_place: &Place<'tcx>,
656        args: &Box<[Spanned<Operand>]>,
657    ) {
658        if args.len() == 0 || !get_cleaned_def_path_name(self.tcx, *def_id).contains("slice::len") {
659            return;
660        }
661        let d_local = self.handle_proj(false, dst_place.clone());
662        let ptr_local = get_arg_place(&args[0].node).1;
663        let mem_local = self.chains.get_point_to_id(ptr_local);
664        let mem_var = self.chains.get_var_node_mut(mem_local).unwrap();
665        for cis in &mut mem_var.cis.contracts {
666            if let PropertyContract::InBound(cis_ty, len) = cis {
667                *len = CisRangeItem::new_var(d_local);
668            }
669        }
670    }
671
672    /// Merge function-level alias results into internal chains and value domains.
673    /// Uses cached alias analysis (AAResult) to connect return/arg relationships.
674    pub fn handle_ret_alias(
675        &mut self,
676        dst_place: &Place<'tcx>,
677        def_id: &DefId,
678        fn_map: &FxHashMap<DefId, AAResult>,
679        args: &Box<[Spanned<Operand>]>,
680    ) {
681        let d_local = self.handle_proj(false, dst_place.clone());
682        if let Some(retalias) = fn_map.get(def_id) {
683            for alias_set in retalias.aliases() {
684                let (l, r) = (alias_set.lhs_no, alias_set.rhs_no);
685                let (l_fields, r_fields) =
686                    (alias_set.lhs_fields.clone(), alias_set.rhs_fields.clone());
687                let (l_place, r_place) = (
688                    if l != 0 {
689                        get_arg_place(&args[l - 1].node)
690                    } else {
691                        (false, d_local)
692                    },
693                    if r != 0 {
694                        get_arg_place(&args[r - 1].node)
695                    } else {
696                        (false, d_local)
697                    },
698                );
699                // if left value is a constant, then update right variable's value
700                if l_place.0 {
701                    let snd_var = self
702                        .chains
703                        .find_var_id_with_fields_seq(r_place.1, &r_fields);
704                    self.chains
705                        .update_value(self.chains.get_point_to_id(snd_var), l_place.1);
706                    continue;
707                }
708                // if right value is a constant, then update left variable's value
709                if r_place.0 {
710                    let fst_var = self
711                        .chains
712                        .find_var_id_with_fields_seq(l_place.1, &l_fields);
713                    self.chains
714                        .update_value(self.chains.get_point_to_id(fst_var), r_place.1);
715                    continue;
716                }
717                let (fst_var, snd_var) = (
718                    self.chains
719                        .find_var_id_with_fields_seq(l_place.1, &l_fields),
720                    self.chains
721                        .find_var_id_with_fields_seq(r_place.1, &r_fields),
722                );
723                // If this var is a pointer/ref, prefer the pointed node when merging.
724                let fst_to = self.chains.get_point_to_id(fst_var);
725                let snd_to = self.chains.get_point_to_id(snd_var);
726                let is_fst_point = fst_to != fst_var;
727                let is_snd_point = snd_to != snd_var;
728                let fst_node = self.chains.get_var_node(fst_var).unwrap();
729                let snd_node = self.chains.get_var_node(snd_var).unwrap();
730                let is_fst_ptr = is_ptr(fst_node.ty.unwrap()) || is_ref(fst_node.ty.unwrap());
731                let is_snd_ptr = is_ptr(snd_node.ty.unwrap()) || is_ref(snd_node.ty.unwrap());
732                rap_debug!(
733                    "{:?}: {fst_var},{fst_to},{is_fst_ptr} -- {snd_var},{snd_to},{is_snd_ptr}",
734                    def_id
735                );
736                match (is_fst_ptr, is_snd_ptr) {
737                    (false, true) => {
738                        // left is value, right is pointer: point right to left or merge appropriately.
739                        if is_snd_point {
740                            self.chains.point(snd_var, fst_var);
741                        } else {
742                            self.chains.merge(fst_var, snd_to);
743                        }
744                    }
745                    (false, false) => {
746                        // both are values: merge value nodes.
747                        self.chains.merge(fst_var, snd_var);
748                    }
749                    (true, true) => {
750                        // both are pointers: prefer merging what they point-to if available.
751                        if is_fst_point && is_snd_point {
752                            self.chains.merge(fst_to, snd_to);
753                        } else if !is_fst_point && is_snd_point {
754                            self.chains.point(fst_var, snd_to);
755                        } else if is_fst_point && !is_snd_point {
756                            self.chains.point(snd_var, fst_to);
757                        } else {
758                            self.chains.merge(fst_var, snd_var);
759                        }
760                    }
761                    (true, false) => {
762                        // left is pointer, right is value: point left to right or merge.
763                        if is_fst_point {
764                            self.chains.point(fst_var, snd_var);
765                        } else {
766                            self.chains.merge(snd_var, fst_to);
767                        }
768                    }
769                }
770            }
771        }
772        // If no alias cache is found and dst is a ptr, then initialize dst's states.
773        else {
774            let d_ty = self.chains.get_local_ty_by_place(d_local);
775            if d_ty.is_some() && (is_ptr(d_ty.unwrap()) || is_ref(d_ty.unwrap())) {
776                self.chains
777                    .generate_ptr_with_obj_node(d_ty.unwrap(), d_local);
778            }
779        }
780    }
781
782    /// Compute a compact FunctionSummary for this function based on the return local (_0).
783    /// If the return resolves to a param or const expression, include it in the summary.
784    pub fn compute_function_summary(&self) -> FunctionSummary<'tcx> {
785        if let Some(domain) = self.value_domains.get(&0) {
786            if let Some(def) = &domain.def {
787                let resolved_def = self.resolve_symbolic_def(def, 0); // 0 is the initial recursion deepth
788                return FunctionSummary::new(resolved_def);
789            }
790        }
791        FunctionSummary::new(None)
792    }
793
794    /// Resolve a SymbolicDef into a simplified form referencing params or constants.
795    /// For example, given `_1 = add(_2, _3)`, attempt to express the result in terms of params.
796    fn resolve_symbolic_def(
797        &self,
798        def: &SymbolicDef<'tcx>,
799        depth: usize,
800    ) -> Option<SymbolicDef<'tcx>> {
801        // Limit recursion depth to avoid infinite loops in symbolic resolution.
802        if depth > 10 {
803            return None;
804        }
805
806        match def {
807            // Base cases: parameters and constants are already resolved.
808            SymbolicDef::Param(_) | SymbolicDef::Constant(_) => Some(def.clone()),
809            // If this is a use/ref/cast, follow to the referenced local and resolve it.
810            SymbolicDef::Use(local_idx) | SymbolicDef::Ref(local_idx) => {
811                self.resolve_local(*local_idx, depth + 1)
812            }
813            SymbolicDef::Cast(src_idx, _ty) => self.resolve_local(*src_idx, depth + 1),
814            // For binary ops, resolve LHS and RHS then rebuild a param-based result when possible.
815            SymbolicDef::Binary(op, lhs_idx, rhs_op) => {
816                let lhs_resolved = self.resolve_local(*lhs_idx, depth + 1)?;
817                let rhs_resolved_op = match rhs_op {
818                    AnaOperand::Const(c) => AnaOperand::Const(*c),
819                    AnaOperand::Local(l) => match self.resolve_local(*l, depth + 1) {
820                        Some(SymbolicDef::Constant(c)) => AnaOperand::Const(c),
821                        Some(SymbolicDef::Param(p)) => AnaOperand::Local(p),
822                        _ => return None,
823                    },
824                };
825                match lhs_resolved {
826                    // Only return a symbolic binary if LHS resolves to a parameter.
827                    SymbolicDef::Param(p_idx) => {
828                        Some(SymbolicDef::Binary(*op, p_idx, rhs_resolved_op))
829                    }
830                    _ => None,
831                }
832            }
833            _ => None,
834        }
835    }
836
837    /// Resolve a local's symbolic definition by consulting the value_domains map.
838    fn resolve_local(&self, local_idx: usize, depth: usize) -> Option<SymbolicDef<'tcx>> {
839        if let Some(domain) = self.value_domains.get(&local_idx) {
840            if let Some(def) = &domain.def {
841                return self.resolve_symbolic_def(def, depth);
842            }
843        }
844        None
845    }
846
847    /// Handle calls to pointer offset functions (e.g., `ptr::add`, `ptr::sub`).
848    pub fn handle_offset_call(
849        &mut self,
850        dst_place: &Place<'tcx>,
851        def_id: &DefId,
852        args: &Box<[Spanned<Operand<'tcx>>]>,
853    ) {
854        let func_name = get_cleaned_def_path_name(self.tcx, *def_id);
855
856        let is_ptr_op = func_name.contains("ptr") || func_name.contains("slice");
857        if !is_ptr_op {
858            return;
859        }
860
861        let is_byte_sub =
862            func_name.ends_with("::byte_sub") || func_name.ends_with("::wrapping_byte_sub"); // specific check for byte ops
863
864        let is_sub =
865            is_byte_sub || func_name.ends_with("::sub") || func_name.ends_with("::wrapping_sub");
866
867        let is_byte_add =
868            func_name.ends_with("::byte_add") || func_name.ends_with("::wrapping_byte_add");
869
870        let is_add =
871            is_byte_add || func_name.ends_with("::add") || func_name.ends_with("::wrapping_add");
872
873        let is_byte_offset =
874            func_name.ends_with("::byte_offset") || func_name.ends_with("::wrapping_byte_offset");
875
876        let is_offset = is_byte_offset
877            || func_name.ends_with("::offset")
878            || func_name.ends_with("::wrapping_offset");
879
880        if !is_sub && !is_add && !is_offset {
881            return;
882        }
883        if args.len() < 2 {
884            return;
885        }
886
887        let dst_local = self.handle_proj(false, *dst_place);
888
889        let bin_op = if is_sub {
890            BinOp::Sub
891        } else if is_add {
892            BinOp::Add
893        } else {
894            BinOp::Offset
895        };
896
897        let mut arg_indices = Vec::new();
898        for arg in args.iter() {
899            if let Some(ana_op) = self.lift_operand(&arg.node) {
900                match ana_op {
901                    AnaOperand::Local(l) => arg_indices.push(l),
902                    AnaOperand::Const(_) => arg_indices.push(0),
903                }
904            } else {
905                arg_indices.push(0);
906            }
907        }
908        let base_op = &args[0].node;
909        let base_local = if let Some(AnaOperand::Local(l)) = self.lift_operand(base_op) {
910            l
911        } else {
912            return;
913        };
914
915        // judge whether it's a byte-level operation
916        let is_byte_op = is_byte_sub || is_byte_add || is_byte_offset;
917
918        let place_ty = if is_byte_op {
919            // if it's a byte operation, force stride to 1 (Align 1, Size 1)
920            PlaceTy::Ty(1, 1)
921        } else {
922            // otherwise, use the pointer's pointee type's Layout (stride = sizeof(T))
923            self.get_ptr_pointee_layout(base_local)
924        };
925
926        // Create a symbolic definition for the pointer offset operation.
927        let summary_def = SymbolicDef::PtrOffset(bin_op, 1, AnaOperand::Local(2), place_ty);
928        let summary = FunctionSummary::new(Some(summary_def));
929
930        // Apply the function summary to the destination local.
931        self.apply_function_summary(dst_local, &summary, &arg_indices);
932    }
933
934    /// Handle SwitchInt: Convert branch selections into constraints AND refine abstract states.
935    /// Convert a SwitchInt terminator into path constraints and refine state when possible.
936    ///
937    /// The function maps the branch target taken into a concrete equality/inequality
938    /// constraint on the discriminator local and attempts to refine abstract states
939    /// (e.g. alignment) when the condition corresponds to recognized helper calls.
940    fn handle_switch_int(
941        &mut self,
942        discr: &Operand<'tcx>,
943        targets: &mir::SwitchTargets,
944        next_bb: usize,
945    ) {
946        let discr_op = match self.lift_operand(discr) {
947            Some(op) => op,
948            None => return,
949        };
950
951        let discr_local_idx = match discr_op {
952            AnaOperand::Local(idx) => idx,
953            _ => return,
954        };
955
956        let mut matched_val = None;
957        for (val, target_bb) in targets.iter() {
958            if target_bb.as_usize() == next_bb {
959                matched_val = Some(val);
960                break;
961            }
962        }
963
964        if let Some(val) = matched_val {
965            // Explicit match found. Try to refine abstract state according to the boolean value.
966            // Example: if discr corresponds to `is_aligned()`, matched_val==1 means true.
967            self.refine_state_by_condition(discr_local_idx, val);
968
969            // Record equality constraint for the taken branch (discr == val).
970            let constraint =
971                SymbolicDef::Binary(BinOp::Eq, discr_local_idx, AnaOperand::Const(val));
972            self.path_constraints.push(constraint);
973        } else if targets.otherwise().as_usize() == next_bb {
974            // "Otherwise" branch taken.
975            // Check what values were explicitly skipped to infer the current value.
976            let mut explicit_has_zero = false;
977            let mut explicit_has_one = false;
978
979            for (val, _) in targets.iter() {
980                if val == 0 {
981                    explicit_has_zero = true;
982                }
983                if val == 1 {
984                    explicit_has_one = true;
985                }
986
987                // Add Ne constraints for skipped targets
988                let constraint =
989                    SymbolicDef::Binary(BinOp::Ne, discr_local_idx, AnaOperand::Const(val));
990                self.path_constraints.push(constraint);
991            }
992
993            // Inference logic for Boolean checks:
994            // If only one boolean value was enumerated in explicit targets, infer the other
995            // value for the otherwise branch and attempt refinement accordingly.
996            if explicit_has_zero && !explicit_has_one {
997                // Only 0 was explicit and skipped -> infer true
998                self.refine_state_by_condition(discr_local_idx, 1);
999            } else if explicit_has_one && !explicit_has_zero {
1000                // Only 1 was explicit and skipped -> infer false
1001                // This lets us mark the else-branch as unaligned when appropriate.
1002                self.refine_state_by_condition(discr_local_idx, 0);
1003            }
1004        }
1005    }
1006
1007    /// Entry point for refining states based on a condition variable's value.
1008    fn refine_state_by_condition(&mut self, cond_local: usize, matched_val: u128) {
1009        // Clone the value domain entry to avoid holding a borrow while we mutate state.
1010        let domain = match self.value_domains.get(&cond_local).cloned() {
1011            Some(d) => d,
1012            None => return,
1013        };
1014
1015        // If this discriminant corresponds to a call, dispatch to the specific refinement logic.
1016        if let Some(SymbolicDef::Call(func_name, args)) = &domain.def {
1017            self.apply_condition_refinement(func_name, args, matched_val);
1018        }
1019    }
1020
1021    /// Dispatcher: Applies specific state updates based on function name and return value.
1022    fn apply_condition_refinement(
1023        &mut self,
1024        func_name: &str,
1025        args: &Vec<AnaOperand>,
1026        matched_val: u128,
1027    ) {
1028        // Handle is_aligned check
1029        if func_name.ends_with("is_aligned") || func_name.contains("is_aligned") {
1030            if let Some(AnaOperand::Local(ptr_local)) = args.get(0) {
1031                // Determine target state: 1 -> Aligned, 0 -> Unaligned
1032                let is_aligned_state = if matched_val == 1 {
1033                    Some(true)
1034                } else if matched_val == 0 {
1035                    Some(false)
1036                } else {
1037                    None
1038                };
1039
1040                if let Some(aligned) = is_aligned_state {
1041                    // 1. Update the variable directly involved in the check (Current Node)
1042                    // This covers cases where the checked variable is used immediately in the block.
1043                    self.update_align_state(*ptr_local, aligned);
1044
1045                    // 2. Trace back to the source (Root Node) and update it
1046                    // This covers cases where new copies are created from the source (e.g. _5 = copy _1).
1047                    let root_local = self.find_source_var(*ptr_local);
1048                    if root_local != *ptr_local {
1049                        self.update_align_state(root_local, aligned);
1050                    }
1051                }
1052            }
1053        }
1054    }
1055
1056    /// Helper: Trace back through Use/Cast/Copy to find the definitive source local.
1057    pub fn find_source_var(&self, start_local: usize) -> usize {
1058        let mut curr = start_local;
1059        let mut depth = 0;
1060        while depth < 20 {
1061            if let Some(domain) = self.value_domains.get(&curr) {
1062                match &domain.def {
1063                    Some(SymbolicDef::Use(src)) | Some(SymbolicDef::Cast(src, _)) => {
1064                        curr = *src;
1065                    }
1066                    _ => return curr,
1067                }
1068            } else {
1069                return curr;
1070            }
1071            depth += 1;
1072        }
1073        curr
1074    }
1075
1076    /// Apply function summary to update Visitor state and Graph
1077    pub fn apply_function_summary(
1078        &mut self,
1079        dst_local: usize,
1080        summary: &FunctionSummary<'tcx>,
1081        args: &Vec<usize>, // Caller's local indices for arguments
1082    ) {
1083        if let Some(def) = &summary.return_def {
1084            // 1. Resolve the definition to current context
1085            if let Some(resolved_def) = self.resolve_summary_def(def, args) {
1086                // 2. Record value definition in Visitor (essential for Z3 checks)
1087                // This ensures self.value_domains has the PtrOffset info
1088                self.record_value_def(dst_local, resolved_def.clone());
1089
1090                // 3. Update DominatedGraph based on the resolved definition type
1091                match resolved_def {
1092                    SymbolicDef::PtrOffset(_, base_local, _, _) => {
1093                        // Update graph topology and node info
1094                        self.chains
1095                            .update_from_offset_def(dst_local, base_local, resolved_def);
1096                    }
1097                    _ => {}
1098                }
1099            }
1100        }
1101    }
1102
1103    /// Resolve a definition from Function Summary (using Param indices)
1104    /// into a concrete SymbolicDef (using Caller's Locals).
1105    fn resolve_summary_def(
1106        &self,
1107        def: &SymbolicDef<'tcx>,
1108        args: &Vec<usize>,
1109    ) -> Option<SymbolicDef<'tcx>> {
1110        match def {
1111            // Param(i) -> Use(Arg_i)
1112            SymbolicDef::Param(idx) => {
1113                // Assuming 1-based index in Summary Params
1114                if *idx > 0 && idx - 1 < args.len() {
1115                    Some(SymbolicDef::Use(args[idx - 1]))
1116                } else {
1117                    None
1118                }
1119            }
1120            // PtrOffset(Param_Base, Param_Offset) -> PtrOffset(Local_Base, Local_Offset)
1121            SymbolicDef::PtrOffset(op, base_param_idx, offset_op, ty) => {
1122                if *base_param_idx > 0 && base_param_idx - 1 < args.len() {
1123                    let base_local = args[base_param_idx - 1];
1124
1125                    // Resolve offset operand
1126                    let real_offset = match offset_op {
1127                        AnaOperand::Local(idx) => {
1128                            if *idx > 0 && idx - 1 < args.len() {
1129                                AnaOperand::Local(args[idx - 1])
1130                            } else {
1131                                AnaOperand::Const(0) // Fallback or Error handling
1132                            }
1133                        }
1134                        AnaOperand::Const(c) => AnaOperand::Const(*c),
1135                    };
1136
1137                    Some(SymbolicDef::PtrOffset(
1138                        *op,
1139                        base_local,
1140                        real_offset,
1141                        ty.clone(),
1142                    ))
1143                } else {
1144                    None
1145                }
1146            }
1147            // Handle other variants if necessary (e.g., Constant)
1148            SymbolicDef::Constant(c) => Some(SymbolicDef::Constant(*c)),
1149            _ => None,
1150        }
1151    }
1152
1153    // ------------------------------------------------
1154
1155    /// Apply path constraints into chains and propagate simple constant equalities
1156    /// into value_domains for later symbolic checks.
1157    pub fn set_constraint(&mut self, constraint: &Vec<(Place<'tcx>, Place<'tcx>, BinOp)>) {
1158        for (p1, p2, op) in constraint {
1159            let p1_num = self.handle_proj(false, p1.clone());
1160            let p2_num = self.handle_proj(false, p2.clone());
1161            self.chains.insert_patial_op(p1_num, p2_num, op);
1162
1163            if let BinOp::Eq = op {
1164                // If RHS is a known constant, record it as p1's value_constraint.
1165                let maybe_const = self.value_domains.get(&p2_num).and_then(|d| {
1166                    if let Some(SymbolicDef::Constant(c)) = d.def {
1167                        Some(c)
1168                    } else {
1169                        None
1170                    }
1171                });
1172
1173                if let Some(c) = maybe_const {
1174                    self.value_domains
1175                        .entry(p1_num)
1176                        .and_modify(|d| d.value_constraint = Some(c))
1177                        .or_insert(ValueDomain {
1178                            def: None,
1179                            value_constraint: Some(c),
1180                        });
1181                }
1182
1183                // Also propagate constant from p1 to p2 when available.
1184                let maybe_const_p1 = self.value_domains.get(&p1_num).and_then(|d| {
1185                    if let Some(SymbolicDef::Constant(c)) = d.def {
1186                        Some(c)
1187                    } else {
1188                        None
1189                    }
1190                });
1191
1192                if let Some(c) = maybe_const_p1 {
1193                    self.value_domains
1194                        .entry(p2_num)
1195                        .and_modify(|d| d.value_constraint = Some(c))
1196                        .or_insert(ValueDomain {
1197                            def: None,
1198                            value_constraint: Some(c),
1199                        });
1200                }
1201            }
1202        }
1203    }
1204
1205    /// Return layout information (align, size) or Unknown for a place via chain-derived type.
1206    pub fn get_layout_by_place_usize(&self, place: usize) -> PlaceTy<'tcx> {
1207        if let Some(ty) = self.chains.get_obj_ty_through_chain(place) {
1208            return self.visit_ty_and_get_layout(ty);
1209        } else {
1210            return PlaceTy::Unknown;
1211        }
1212    }
1213
1214    /// Determine layout info (align,size) for a type. For generics, collect possible concrete layouts.
1215    pub fn visit_ty_and_get_layout(&self, ty: Ty<'tcx>) -> PlaceTy<'tcx> {
1216        match ty.kind() {
1217            TyKind::RawPtr(ty, _)
1218            | TyKind::Ref(_, ty, _)
1219            | TyKind::Slice(ty)
1220            | TyKind::Array(ty, _) => self.visit_ty_and_get_layout(*ty),
1221            TyKind::Param(param_ty) => {
1222                let generic_name = param_ty.name.to_string();
1223                let mut layout_set: HashSet<(usize, usize)> = HashSet::new();
1224                let ty_set = self.generic_map.get(&generic_name.clone());
1225                if ty_set.is_none() {
1226                    if self.visit_time == 0 {
1227                        rap_warn!(
1228                            "Can not get generic type set: {:?}, def_id:{:?}",
1229                            generic_name,
1230                            self.def_id
1231                        );
1232                    }
1233                    return PlaceTy::GenericTy(generic_name, HashSet::new(), layout_set);
1234                }
1235                for ty in ty_set.unwrap().clone() {
1236                    if let PlaceTy::Ty(align, size) = self.visit_ty_and_get_layout(ty) {
1237                        layout_set.insert((align, size));
1238                    }
1239                }
1240                return PlaceTy::GenericTy(generic_name, ty_set.unwrap().clone(), layout_set);
1241            }
1242            TyKind::Adt(def, _list) => {
1243                if def.is_enum() {
1244                    return PlaceTy::Unknown;
1245                } else {
1246                    PlaceTy::Unknown
1247                }
1248            }
1249            TyKind::Closure(_, _) => PlaceTy::Unknown,
1250            TyKind::Alias(_, ty) => {
1251                // rap_warn!("self ty {:?}",ty.self_ty());
1252                return self.visit_ty_and_get_layout(ty.self_ty());
1253            }
1254            _ => {
1255                let param_env = self.tcx.param_env(self.def_id);
1256                let ty_env = ty::TypingEnv::post_analysis(self.tcx, self.def_id);
1257                let input = PseudoCanonicalInput {
1258                    typing_env: ty_env,
1259                    value: ty,
1260                };
1261                if let Ok(layout) = self.tcx.layout_of(input) {
1262                    // let layout = self.tcx.layout_of(param_env.and(ty)).unwrap();
1263                    let align = layout.align.abi.bytes_usize();
1264                    let size = layout.size.bytes() as usize;
1265                    return PlaceTy::Ty(align, size);
1266                } else {
1267                    // rap_warn!("Find type {:?} that can't get layout!", ty);
1268                    PlaceTy::Unknown
1269                }
1270            }
1271        }
1272    }
1273
1274    /// Handle special binary operations (e.g., pointer offset) and extract operand places.
1275    pub fn handle_binary_op(
1276        &mut self,
1277        first_op: &Operand,
1278        bin_op: &BinOp,
1279        second_op: &Operand,
1280        path_index: usize,
1281    ) {
1282        // Currently collects arg places for Offset.
1283        match bin_op {
1284            BinOp::Offset => {
1285                let _first_place = get_arg_place(first_op);
1286                let _second_place = get_arg_place(second_op);
1287            }
1288            _ => {}
1289        }
1290    }
1291
1292    /// Convert a MIR Place (with projections) into an internal numeric node id.
1293    /// Handles deref and field projections by consulting/creating chain nodes.
1294    pub fn handle_proj(&mut self, is_right: bool, place: Place<'tcx>) -> usize {
1295        let mut proj_id = place.local.as_usize();
1296        for proj in place.projection {
1297            match proj {
1298                ProjectionElem::Deref => {
1299                    let point_to = self.chains.get_point_to_id(place.local.as_usize());
1300                    if point_to == proj_id {
1301                        proj_id = self.chains.check_ptr(proj_id);
1302                    } else {
1303                        proj_id = point_to;
1304                    }
1305                }
1306                ProjectionElem::Field(field, ty) => {
1307                    proj_id = self
1308                        .chains
1309                        .get_field_node_id(proj_id, field.as_usize(), Some(ty));
1310                }
1311                _ => {}
1312            }
1313        }
1314        proj_id
1315    }
1316
1317    /// Record or overwrite the symbolic definition for a local in value_domains.
1318    fn record_value_def(&mut self, local_idx: usize, def: SymbolicDef<'tcx>) {
1319        self.value_domains
1320            .entry(local_idx)
1321            .and_modify(|d| d.def = Some(def.clone()))
1322            .or_insert(ValueDomain {
1323                def: Some(def),
1324                value_constraint: None,
1325            });
1326    }
1327
1328    /// Convert a MIR Operand into an AnaOperand (local node id or constant) when possible.
1329    fn lift_operand(&mut self, op: &Operand<'tcx>) -> Option<AnaOperand> {
1330        match op {
1331            Operand::Copy(place) | Operand::Move(place) => {
1332                Some(AnaOperand::Local(self.handle_proj(true, place.clone())))
1333            }
1334            Operand::Constant(box c) => match c.const_ {
1335                rustc_middle::mir::Const::Ty(_ty, const_value) => {
1336                    if let Some(val) = const_value.try_to_target_usize(self.tcx) {
1337                        Some(AnaOperand::Const(val as u128))
1338                    } else {
1339                        None
1340                    }
1341                }
1342                rustc_middle::mir::Const::Unevaluated(_unevaluated, _ty) => None,
1343                rustc_middle::mir::Const::Val(const_value, _ty) => {
1344                    if let Some(scalar) = const_value.try_to_scalar_int() {
1345                        let val = scalar.to_uint(scalar.size());
1346                        Some(AnaOperand::Const(val))
1347                    } else {
1348                        None
1349                    }
1350                }
1351            },
1352        }
1353    }
1354
1355    /// Trace a pointer value back to its base local and accumulate byte offsets.
1356    /// Returns (base_local, total_offset) when traceable.
1357    ///
1358    /// Example: p3 = p2.byte_offset(v2), p2 = p1.byte_offset(v1)
1359    /// returns (p1, v1 + v2) for trace_base_ptr(p3).
1360    pub fn trace_base_ptr(&self, local: usize) -> Option<(usize, u64)> {
1361        // Walk symbolic definitions backwards to find the base pointer and accumulated offset.
1362        let mut curr = local;
1363        let mut total_offset = 0;
1364        let mut depth = 0;
1365
1366        loop {
1367            if depth > 10 {
1368                return None;
1369            }
1370            depth += 1;
1371
1372            if let Some(domain) = self.value_domains.get(&curr) {
1373                match &domain.def {
1374                    Some(SymbolicDef::Binary(BinOp::Offset, base, offset_op)) => {
1375                        if let AnaOperand::Const(off) = offset_op {
1376                            total_offset += *off as u64;
1377                            curr = *base;
1378                        } else {
1379                            return None;
1380                        }
1381                    }
1382                    Some(SymbolicDef::Use(src)) | Some(SymbolicDef::Cast(src, _)) => {
1383                        curr = *src;
1384                    }
1385                    Some(SymbolicDef::Ref(src)) => {
1386                        return Some((*src, total_offset));
1387                    }
1388                    Some(SymbolicDef::Param(_)) => {
1389                        return Some((curr, total_offset));
1390                    }
1391                    _ => return None,
1392                }
1393            } else {
1394                return None;
1395            }
1396        }
1397    }
1398}
1399
1400// === Partition: Debugging & display helpers ===
1401// Debugging and display helpers: pretty-printers and formatting utilities for analysis state.
1402impl<'tcx> BodyVisitor<'tcx> {
1403    /// Display a combined debug table merging DominatedGraph and ValueDomain info.
1404    /// Handles different lengths of variables in graph (includes heap nodes) vs domains.
1405    pub fn display_combined_debug_info(&self) {
1406        const TABLE_WIDTH: usize = 200; // Expanded width for all columns
1407        println!(
1408            "\n{:=^width$}",
1409            " Combined Analysis State Report ",
1410            width = TABLE_WIDTH
1411        );
1412
1413        // 1. Collect and Sort All Unique Variable IDs
1414        let mut all_ids: HashSet<usize> = self.value_domains.keys().cloned().collect();
1415        all_ids.extend(self.chains.variables.keys().cloned());
1416        let mut sorted_ids: Vec<usize> = all_ids.into_iter().collect();
1417        sorted_ids.sort();
1418
1419        if sorted_ids.is_empty() {
1420            println!("  [Empty Analysis State]");
1421            println!("{:=^width$}\n", "", width = TABLE_WIDTH);
1422            return;
1423        }
1424
1425        // 2. Define Table Header
1426        let sep = format!(
1427            "+{:-^6}+{:-^25}+{:-^8}+{:-^15}+{:-^30}+{:-^25}+{:-^40}+{:-^15}+",
1428            "", "", "", "", "", "", "", ""
1429        );
1430        println!("{}", sep);
1431        println!(
1432            "| {:^6} | {:^25} | {:^8} | {:^15} | {:^30} | {:^25} | {:^40} | {:^15} |",
1433            "ID", "Type", "Pt-To", "Fields", "States", "Graph Offset", "Sym Def", "Sym Val"
1434        );
1435        println!("{}", sep);
1436
1437        // 3. Iterate and Print Rows
1438        for id in sorted_ids {
1439            // -- Extract Graph Info (if exists) --
1440            let (ty_str, pt_str, fields_str, states_str, g_offset_str) =
1441                if let Some(node) = self.chains.variables.get(&id) {
1442                    // Type
1443                    let t = node
1444                        .ty
1445                        .map(|t| format!("{:?}", t))
1446                        .unwrap_or_else(|| "None".to_string());
1447
1448                    // Points-To
1449                    let p = node
1450                        .points_to
1451                        .map(|p| format!("_{}", p))
1452                        .unwrap_or_else(|| "-".to_string());
1453
1454                    // Fields
1455                    let f = if node.field.is_empty() {
1456                        "-".to_string()
1457                    } else {
1458                        let mut fs: Vec<String> = node
1459                            .field
1460                            .iter()
1461                            .map(|(k, v)| format!(".{}->_{}", k, v))
1462                            .collect();
1463                        fs.sort();
1464                        fs.join(", ")
1465                    };
1466
1467                    // States (Align, etc.)
1468                    let mut s_vec = Vec::new();
1469                    match &node.ots.align {
1470                        AlignState::Aligned(ty) => {
1471                            if let Some(node_ty) = node.ty {
1472                                if is_ptr(node_ty) || is_ref(node_ty) {
1473                                    s_vec.push(format!("Align({:?})", ty));
1474                                }
1475                            }
1476                        }
1477                        AlignState::Unaligned(ty) => s_vec.push(format!("Unalign({:?})", ty)),
1478                        AlignState::Unknown => {} // Skip unknown to keep clean, or use "Unknown"
1479                    }
1480                    let s = if s_vec.is_empty() {
1481                        "-".to_string()
1482                    } else {
1483                        s_vec.join(", ")
1484                    };
1485
1486                    // Graph Offset (Simplified Formatting Logic)
1487                    let off = if let Some(def) = &node.offset_from {
1488                        match def {
1489                            SymbolicDef::PtrOffset(op, base, idx, _) => {
1490                                let op_str = self.binop_to_symbol(op);
1491                                let idx_str = match idx {
1492                                    AnaOperand::Local(l) => format!("_{}", l),
1493                                    AnaOperand::Const(c) => format!("{}", c),
1494                                };
1495                                format!("_{} {} {}", base, op_str, idx_str)
1496                            }
1497                            SymbolicDef::Binary(BinOp::Offset, base, idx) => {
1498                                let idx_str = match idx {
1499                                    AnaOperand::Local(l) => format!("_{}", l),
1500                                    AnaOperand::Const(c) => format!("{}", c),
1501                                };
1502                                format!("_{} + {}", base, idx_str)
1503                            }
1504                            _ => format!("{:?}", def),
1505                        }
1506                    } else {
1507                        "-".to_string()
1508                    };
1509
1510                    (t, p, f, s, off)
1511                } else {
1512                    (
1513                        "-".to_string(),
1514                        "-".to_string(),
1515                        "-".to_string(),
1516                        "-".to_string(),
1517                        "-".to_string(),
1518                    )
1519                };
1520
1521            // -- Extract Value Domain Info (if exists) --
1522            let (sym_def_str, sym_val_str) = if let Some(domain) = self.value_domains.get(&id) {
1523                let d = self
1524                    .format_symbolic_def(domain.def.as_ref())
1525                    .replace('\n', " ");
1526                let v = match domain.value_constraint {
1527                    Some(val) => format!("== {}", val),
1528                    None => "-".to_string(),
1529                };
1530                (d, v)
1531            } else {
1532                ("-".to_string(), "-".to_string())
1533            };
1534
1535            // -- Print Combined Row --
1536            println!(
1537                "| {:<6} | {:<25} | {:<8} | {:<15} | {:<30} | {:<25} | {:<40} | {:<15} |",
1538                id,
1539                self.safe_truncate(&ty_str, 25),
1540                pt_str,
1541                self.safe_truncate(&fields_str, 15),
1542                self.safe_truncate(&states_str, 30),
1543                self.safe_truncate(&g_offset_str, 25),
1544                self.safe_truncate(&sym_def_str, 40),
1545                self.safe_truncate(&sym_val_str, 15)
1546            );
1547        }
1548
1549        println!("{}", sep);
1550        println!("{:=^width$}\n", " End Report ", width = TABLE_WIDTH);
1551    }
1552
1553    /// Pretty-print the collected path constraints for debugging.
1554    /// Display the true conditions in all branches.
1555    pub fn display_path_constraints(&self) {
1556        const TABLE_WIDTH: usize = 86;
1557        println!(
1558            "\n{:=^width$}",
1559            " Path Constraints Report ",
1560            width = TABLE_WIDTH
1561        );
1562
1563        if self.path_constraints.is_empty() {
1564            println!("  [Empty Path Constraints]");
1565            println!("{:=^width$}\n", "", width = TABLE_WIDTH);
1566            return;
1567        }
1568
1569        println!("| {:^6} | {:^73} |", "Index", "Constraint Expression");
1570        let sep = format!("+{:-^6}+{:-^73}+", "", "");
1571        println!("{}", sep);
1572
1573        for (i, constraint) in self.path_constraints.iter().enumerate() {
1574            let def_raw = self.format_symbolic_def(Some(constraint));
1575            let def_str = def_raw.replace('\n', " ").replace('\t', " ");
1576
1577            println!("| {:<6} | {:<73} |", i, self.safe_truncate(&def_str, 73));
1578        }
1579
1580        println!("{}", sep);
1581        println!("{:=^width$}\n", " End Report ", width = TABLE_WIDTH);
1582    }
1583
1584    /// Display all variables' symbolic definitions and value constraints.
1585    /// Pretty-print value domains (symbolic definitions and constraints) for debug.
1586    pub fn display_value_domains(&self) {
1587        const TABLE_WIDTH: usize = 86;
1588        println!(
1589            "\n{:=^width$}",
1590            " Value Domain Analysis Report ",
1591            width = TABLE_WIDTH
1592        );
1593
1594        let mut locals: Vec<&usize> = self.value_domains.keys().collect();
1595        locals.sort();
1596
1597        if locals.is_empty() {
1598            println!("  [Empty Value Domains]");
1599            println!("{:=^width$}\n", "", width = TABLE_WIDTH);
1600            return;
1601        }
1602
1603        let print_row = |c1: &str, c2: &str, c3: &str, is_header: bool| {
1604            if is_header {
1605                println!("| {:^6} | {:^40} | {:^15} |", c1, c2, c3);
1606            } else {
1607                println!(
1608                    "| {:<6} | {:<40} | {:<15} |",
1609                    c1,
1610                    self.safe_truncate(c2, 40),
1611                    c3,
1612                );
1613            }
1614        };
1615
1616        let sep = format!("+{:-^6}+{:-^40}+{:-^15}+", "", "", "");
1617        println!("{}", sep);
1618        print_row("Local", "Symbolic Definition", "Constraint", true);
1619        println!("{}", sep);
1620
1621        for local_idx in locals {
1622            let domain = &self.value_domains[local_idx];
1623
1624            let local_str = format!("_{}", local_idx);
1625
1626            let def_raw = self.format_symbolic_def(domain.def.as_ref());
1627            let def_str = def_raw.replace('\n', " ").replace('\t', " ");
1628
1629            let constraint_str = match domain.value_constraint {
1630                Some(v) => format!("== {}", v),
1631                None => String::from("-"),
1632            };
1633
1634            print_row(&local_str, &def_str, &constraint_str, false);
1635        }
1636
1637        println!("{}", sep);
1638        println!("{:=^width$}\n", " End Report ", width = TABLE_WIDTH);
1639    }
1640
1641    /// Truncate a string to max_width preserving character boundaries.
1642    /// Returns a shortened string with ".." appended when truncation occurs.
1643    fn safe_truncate(&self, s: &str, max_width: usize) -> String {
1644        let char_count = s.chars().count();
1645        if char_count <= max_width {
1646            return s.to_string();
1647        }
1648        let truncated: String = s.chars().take(max_width - 2).collect();
1649        format!("{}..", truncated)
1650    }
1651
1652    /// Convert a SymbolicDef into a short human-readable string for display.
1653    fn format_symbolic_def(&self, def: Option<&SymbolicDef<'tcx>>) -> String {
1654        match def {
1655            None => String::from("Top (Unknown)"),
1656            Some(d) => match d {
1657                SymbolicDef::Param(idx) => format!("Param(arg_{})", idx),
1658                SymbolicDef::Constant(val) => format!("Const({})", val),
1659                SymbolicDef::Use(idx) => format!("Copy(_{})", idx),
1660                SymbolicDef::Ref(idx) => format!("&_{}", idx),
1661                SymbolicDef::Cast(idx, ty_str) => format!("_{} as {}", idx, ty_str),
1662                SymbolicDef::UnOp(op) => format!("{:?}(op)", op), // unary op placeholder
1663                SymbolicDef::Binary(op, lhs, rhs) => {
1664                    let op_str = self.binop_to_symbol(op);
1665                    let rhs_str = match rhs {
1666                        AnaOperand::Local(i) => format!("_{}", i),
1667                        AnaOperand::Const(c) => format!("{}", c),
1668                    };
1669                    format!("_{} {} {}", lhs, op_str, rhs_str)
1670                }
1671                SymbolicDef::Call(func_name, args) => {
1672                    let args_str: Vec<String> = args.iter().map(|a| format!("_{:?}", a)).collect();
1673                    format!("{}({})", func_name, args_str.join(", "))
1674                }
1675                SymbolicDef::PtrOffset(binop, ptr, offset, size) => {
1676                    let op_str = self.binop_to_symbol(&binop);
1677                    format!("ptr_offset({}, _{}, {:?}, {:?})", op_str, ptr, offset, size)
1678                }
1679            },
1680        }
1681    }
1682
1683    /// Map MIR BinOp to a human-friendly operator string.
1684    fn binop_to_symbol(&self, op: &BinOp) -> &'static str {
1685        match op {
1686            BinOp::Add => "+",
1687            BinOp::Sub => "-",
1688            BinOp::Mul => "*",
1689            BinOp::Div => "/",
1690            BinOp::Rem => "%",
1691            BinOp::BitXor => "^",
1692            BinOp::BitAnd => "&",
1693            BinOp::BitOr => "|",
1694            BinOp::Shl => "<<",
1695            BinOp::Shr => ">>",
1696            BinOp::Eq => "==",
1697            BinOp::Ne => "!=",
1698            BinOp::Lt => "<",
1699            BinOp::Le => "<=",
1700            BinOp::Gt => ">",
1701            BinOp::Ge => ">=",
1702            BinOp::Offset => "ptr_offset",
1703            _ => "",
1704        }
1705    }
1706
1707    /// Display the path in DOT format.
1708    pub fn display_path_dot(&self, path: &[usize]) {
1709        let base_name = get_cleaned_def_path_name(self.tcx, self.def_id);
1710        let path_suffix = path
1711            .iter()
1712            .map(|b| b.to_string())
1713            .collect::<Vec<String>>()
1714            .join("_");
1715
1716        let name = format!("{}_path_{}", base_name, path_suffix);
1717        let dot_string = self.chains.to_dot_graph();
1718        render_dot_string(name, dot_string);
1719    }
1720}