rapx/check/senryx/
visitor.rs

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