rapx/analysis/senryx/
dominated_graph.rs

1use crate::{
2    analysis::{
3        senryx::{
4            contracts::{
5                abstract_state::AlignState,
6                property::{CisRangeItem, ContractualInvariantState, PropertyContract},
7            },
8            symbolic_analysis::{AnaOperand, SymbolicDef},
9        },
10        utils::fn_info::{display_hashmap, get_pointee, is_ptr, is_ref, is_slice, reverse_op},
11    },
12    rap_debug, rap_warn,
13};
14use rustc_hir::def_id::DefId;
15use rustc_middle::mir::BinOp;
16use rustc_middle::mir::Local;
17use rustc_middle::ty::TyKind;
18use rustc_middle::ty::{Ty, TyCtxt};
19use serde::de;
20use std::collections::{HashMap, HashSet, VecDeque};
21use std::fmt::Write;
22
23/// A collection of state properties for a memory location.
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct States<'tcx> {
26    pub nonnull: bool,
27    pub allocator_consistency: bool,
28    pub init: bool,
29    pub align: AlignState<'tcx>,
30    pub valid_string: bool,
31    pub valid_cstr: bool,
32}
33
34impl<'tcx> States<'tcx> {
35    /// Create a new States instance with default values.
36    pub fn new(ty: Ty<'tcx>) -> Self {
37        Self {
38            nonnull: true,
39            allocator_consistency: true,
40            init: true,
41            align: AlignState::Aligned(ty),
42            valid_string: true,
43            valid_cstr: true,
44        }
45    }
46
47    /// Create a new States instance with all fields set to false/unknown.
48    pub fn new_unknown() -> Self {
49        Self {
50            nonnull: false,
51            allocator_consistency: false,
52            init: false,
53            align: AlignState::Unknown,
54            valid_string: false,
55            valid_cstr: false,
56        }
57    }
58
59    /// Merge the states of this instance with another.
60    pub fn merge_states(&mut self, other: &States<'tcx>) {
61        self.nonnull &= other.nonnull;
62        self.allocator_consistency &= other.allocator_consistency;
63        self.init &= other.init;
64        self.align.merge(&other.align);
65        self.valid_string &= other.valid_string;
66        self.valid_cstr &= other.valid_cstr;
67    }
68}
69
70/// A node in the intermediate result graph.
71#[derive(Debug, Clone)]
72pub struct InterResultNode<'tcx> {
73    pub point_to: Option<Box<InterResultNode<'tcx>>>,
74    pub fields: HashMap<usize, InterResultNode<'tcx>>,
75    pub ty: Option<Ty<'tcx>>,
76    pub states: States<'tcx>,
77    pub const_value: usize,
78}
79
80impl<'tcx> InterResultNode<'tcx> {
81    /// Create a new InterResultNode with default values.
82    pub fn new_default(ty: Option<Ty<'tcx>>) -> Self {
83        Self {
84            point_to: None,
85            fields: HashMap::new(),
86            ty,
87            states: States::new(ty.unwrap()),
88            const_value: 0, // To be modified
89        }
90    }
91
92    /// Construct an InterResultNode from a VariableNode.
93    pub fn construct_from_var_node(chain: DominatedGraph<'tcx>, var_id: usize) -> Self {
94        let var_node = chain.get_var_node(var_id).unwrap();
95        let point_node = if var_node.points_to.is_none() {
96            None
97        } else {
98            Some(Box::new(Self::construct_from_var_node(
99                chain.clone(),
100                var_node.points_to.unwrap(),
101            )))
102        };
103        let fields = var_node
104            .field
105            .iter()
106            .map(|(k, v)| (*k, Self::construct_from_var_node(chain.clone(), *v)))
107            .collect();
108        Self {
109            point_to: point_node,
110            fields,
111            ty: var_node.ty.clone(),
112            states: var_node.ots.clone(),
113            const_value: var_node.const_value,
114        }
115    }
116
117    /// Merge the current node with another node.
118    pub fn merge(&mut self, other: InterResultNode<'tcx>) {
119        if self.ty != other.ty {
120            return;
121        }
122        // merge current node's states
123        self.states.merge_states(&other.states);
124
125        // merge node it points to
126        match (&mut self.point_to, other.point_to) {
127            (Some(self_ptr), Some(other_ptr)) => self_ptr.merge(*other_ptr),
128            (None, Some(other_ptr)) => {
129                self.point_to = Some(other_ptr.clone());
130            }
131            _ => {}
132        }
133        // merge the fields nodess
134        for (field_id, other_node) in &other.fields {
135            match self.fields.get_mut(field_id) {
136                Some(self_node) => self_node.merge(other_node.clone()),
137                None => {
138                    self.fields.insert(*field_id, other_node.clone());
139                }
140            }
141        }
142        // TODO: merge into a range
143        self.const_value = std::cmp::max(self.const_value, other.const_value);
144    }
145}
146
147/// A summary of a function's behavior.
148#[derive(Clone, Debug)]
149pub struct FunctionSummary<'tcx> {
150    pub return_def: Option<SymbolicDef<'tcx>>,
151}
152
153impl<'tcx> FunctionSummary<'tcx> {
154    /// Create a new FunctionSummary.
155    pub fn new(def: Option<SymbolicDef<'tcx>>) -> Self {
156        Self { return_def: def }
157    }
158}
159
160/// A node in the dominated graph.
161#[derive(Debug, Clone)]
162pub struct VariableNode<'tcx> {
163    pub id: usize,
164    pub alias_set: HashSet<usize>,
165    pub points_to: Option<usize>,
166    pub pointed_by: HashSet<usize>,
167    pub field: HashMap<usize, usize>,
168    pub ty: Option<Ty<'tcx>>,
169    pub is_dropped: bool,
170    pub ots: States<'tcx>,
171    pub const_value: usize,
172    pub cis: ContractualInvariantState<'tcx>,
173    pub offset_from: Option<SymbolicDef<'tcx>>,
174}
175
176impl<'tcx> VariableNode<'tcx> {
177    /// Create a new VariableNode.
178    pub fn new(
179        id: usize,
180        points_to: Option<usize>,
181        pointed_by: HashSet<usize>,
182        ty: Option<Ty<'tcx>>,
183        ots: States<'tcx>,
184    ) -> Self {
185        VariableNode {
186            id,
187            alias_set: HashSet::from([id]),
188            points_to,
189            pointed_by,
190            field: HashMap::new(),
191            ty,
192            is_dropped: false,
193            ots,
194            const_value: 0,
195            cis: ContractualInvariantState::new_default(),
196            offset_from: None,
197        }
198    }
199
200    /// Create a new VariableNode with default values.
201    pub fn new_default(id: usize, ty: Option<Ty<'tcx>>) -> Self {
202        VariableNode {
203            id,
204            alias_set: HashSet::from([id]),
205            points_to: None,
206            pointed_by: HashSet::new(),
207            field: HashMap::new(),
208            ty,
209            is_dropped: false,
210            ots: States::new(ty.unwrap()),
211            const_value: 0,
212            cis: ContractualInvariantState::new_default(),
213            offset_from: None,
214        }
215    }
216
217    /// Create a new VariableNode with specific states.
218    pub fn new_with_states(id: usize, ty: Option<Ty<'tcx>>, ots: States<'tcx>) -> Self {
219        VariableNode {
220            id,
221            alias_set: HashSet::from([id]),
222            points_to: None,
223            pointed_by: HashSet::new(),
224            field: HashMap::new(),
225            ty,
226            is_dropped: false,
227            ots,
228            const_value: 0,
229            cis: ContractualInvariantState::new_default(),
230            offset_from: None,
231        }
232    }
233}
234
235/// A dominated graph.
236#[derive(Clone)]
237pub struct DominatedGraph<'tcx> {
238    /// The type context.
239    pub tcx: TyCtxt<'tcx>,
240    /// The definition ID of the function.
241    pub def_id: DefId,
242    /// The number of local variables.
243    pub local_len: usize,
244    /// The variables in the graph. Map from local variable index to VariableNode.
245    pub variables: HashMap<usize, VariableNode<'tcx>>,
246}
247
248impl<'tcx> DominatedGraph<'tcx> {
249    // This constructor will init all the local arguments' node states.
250    // If input argument is ptr or ref, it will point to a corresponding obj node.
251    pub fn new(tcx: TyCtxt<'tcx>, def_id: DefId) -> Self {
252        let body = tcx.optimized_mir(def_id);
253        let locals = body.local_decls.clone();
254        let fn_sig = tcx.fn_sig(def_id).skip_binder();
255        let param_len = fn_sig.inputs().skip_binder().len();
256        let mut var_map: HashMap<usize, VariableNode<'_>> = HashMap::new();
257        let mut obj_cnt = 0;
258        for (idx, local) in locals.iter().enumerate() {
259            let local_ty = local.ty;
260            let mut node = VariableNode::new_default(idx, Some(local_ty));
261            if local_ty.to_string().contains("MaybeUninit") {
262                node.ots.init = false;
263            }
264            var_map.insert(idx, node);
265        }
266        Self {
267            tcx,
268            def_id,
269            local_len: locals.len(),
270            variables: var_map,
271        }
272    }
273
274    /// Initialize the self node with an intermediate result.
275    pub fn init_self_with_inter(&mut self, inter_result: InterResultNode<'tcx>) {
276        let self_node = self.get_var_node(1).unwrap().clone();
277        if self_node.ty.unwrap().is_ref() {
278            let obj_node = self.get_var_node(self.get_point_to_id(1)).unwrap();
279            self.dfs_insert_inter_results(inter_result, obj_node.id);
280        } else {
281            self.dfs_insert_inter_results(inter_result, self_node.id);
282        }
283    }
284
285    /// Insert intermediate results into the graph.
286    pub fn dfs_insert_inter_results(&mut self, inter_result: InterResultNode<'tcx>, local: usize) {
287        let new_id = self.generate_node_id();
288        let node = self.get_var_node_mut(local).unwrap();
289        // node.ty = inter_result.ty;
290        node.ots = inter_result.states;
291        node.const_value = inter_result.const_value;
292        if inter_result.point_to.is_some() {
293            let new_node = inter_result.point_to.unwrap();
294            node.points_to = Some(new_id);
295            self.insert_node(
296                new_id,
297                new_node.ty.clone(),
298                local,
299                None,
300                new_node.states.clone(),
301            );
302            self.dfs_insert_inter_results(*new_node, new_id);
303        }
304        for (field_idx, field_inter) in inter_result.fields {
305            let field_node_id = self.insert_field_node(local, field_idx, field_inter.ty.clone());
306            self.dfs_insert_inter_results(field_inter, field_node_id);
307        }
308    }
309
310    /// Initialize the arguments of the function.
311    pub fn init_arg(&mut self) {
312        // init arg nodes' point to nodes.
313        let body = self.tcx.optimized_mir(self.def_id);
314        let locals = body.local_decls.clone();
315        let fn_sig = self.tcx.fn_sig(self.def_id).skip_binder();
316        let param_len = fn_sig.inputs().skip_binder().len();
317        for idx in 1..param_len + 1 {
318            let local_ty = locals[Local::from(idx)].ty;
319            self.generate_ptr_with_obj_node(local_ty, idx);
320        }
321        // init args' cis
322        let cis_results = crate::analysis::utils::fn_info::generate_contract_from_annotation(
323            self.tcx,
324            self.def_id,
325        );
326        for (base, fields, contract) in cis_results {
327            if fields.len() == 0 {
328                self.insert_cis_for_arg(base, contract);
329            } else {
330                let mut cur_base = base;
331                let mut field_node = base;
332                for field in fields {
333                    field_node = self.insert_field_node(cur_base, field.0, Some(field.1));
334                    // check if field's type is ptr or ref: yes -> create shadow var
335                    self.generate_ptr_with_obj_node(field.1, field_node);
336                    cur_base = field_node;
337                }
338                self.insert_cis_for_arg(field_node, contract);
339            }
340        }
341    }
342
343    /// Insert a contract into the CIS of an argument.
344    fn insert_cis_for_arg(&mut self, local: usize, contract: PropertyContract<'tcx>) {
345        let node = self.get_var_node_mut(local).unwrap();
346        node.cis.add_contract(contract);
347    }
348
349    /// When generate obj node, this function will add InBound Sp automatically.
350    pub fn generate_ptr_with_obj_node(&mut self, local_ty: Ty<'tcx>, idx: usize) -> usize {
351        let new_id = self.generate_node_id();
352        if is_ptr(local_ty) {
353            // modify ptr node pointed
354            self.get_var_node_mut(idx).unwrap().points_to = Some(new_id);
355            self.get_var_node_mut(idx).unwrap().ots = States::new_unknown();
356            // insert pointed object node
357            self.insert_node(
358                new_id,
359                Some(get_pointee(local_ty)),
360                idx,
361                None,
362                States::new_unknown(),
363            );
364            self.add_bound_for_obj(new_id, local_ty);
365        } else if is_ref(local_ty) {
366            // modify ptr node pointed
367            self.get_var_node_mut(idx).unwrap().points_to = Some(new_id);
368            // insert ref object node
369            self.insert_node(
370                new_id,
371                Some(get_pointee(local_ty)),
372                idx,
373                None,
374                States::new(get_pointee(local_ty)),
375            );
376            self.add_bound_for_obj(new_id, local_ty);
377        }
378        new_id
379    }
380
381    /// Add a bound for an object node.
382    fn add_bound_for_obj(&mut self, new_id: usize, local_ty: Ty<'tcx>) {
383        let new_node = self.get_var_node_mut(new_id).unwrap();
384        let new_node_ty = get_pointee(local_ty);
385        let contract = if is_slice(new_node_ty).is_some() {
386            let inner_ty = is_slice(new_node_ty).unwrap();
387            PropertyContract::new_obj_boundary(inner_ty, CisRangeItem::new_unknown())
388        } else {
389            PropertyContract::new_obj_boundary(new_node_ty, CisRangeItem::new_value(1))
390        };
391        new_node.cis.add_contract(contract);
392    }
393
394    /// if current node is ptr or ref, then return the new node pointed by it.
395    pub fn check_ptr(&mut self, arg: usize) -> usize {
396        if self.get_var_node_mut(arg).unwrap().ty.is_none() {
397            display_hashmap(&self.variables, 1);
398        };
399        let node_ty = self.get_var_node_mut(arg).unwrap().ty.unwrap();
400        if is_ptr(node_ty) || is_ref(node_ty) {
401            return self.generate_ptr_with_obj_node(node_ty, arg);
402        }
403        arg
404    }
405
406    /// Get the type of a local variable by its place.
407    pub fn get_local_ty_by_place(&self, arg: usize) -> Option<Ty<'tcx>> {
408        let body = self.tcx.optimized_mir(self.def_id);
409        let locals = body.local_decls.clone();
410        if arg < locals.len() {
411            return Some(locals[Local::from(arg)].ty);
412        } else {
413            // If the arg is a field of some place, we search the whole map for it.
414            return self.get_var_node(arg).unwrap().ty;
415        }
416    }
417
418    /// Get the type of an object through a chain of pointers.
419    pub fn get_obj_ty_through_chain(&self, arg: usize) -> Option<Ty<'tcx>> {
420        let var = self.get_var_node(arg).unwrap();
421        // If the var is ptr or ref, then find its pointed obj.
422        if let Some(pointed_idx) = var.points_to {
423            self.get_obj_ty_through_chain(pointed_idx)
424        } else {
425            var.ty
426        }
427    }
428
429    /// Get the ID of the node pointed to by a pointer or reference.
430    pub fn get_point_to_id(&self, arg: usize) -> usize {
431        let var = self.get_var_node(arg).unwrap();
432        if let Some(pointed_idx) = var.points_to {
433            pointed_idx
434        } else {
435            arg
436        }
437    }
438
439    /// Check if a node ID corresponds to a local variable.
440    pub fn is_local(&self, node_id: usize) -> bool {
441        self.local_len > node_id
442    }
443}
444
445// This implementation has the auxiliary functions of DominatedGraph,
446// including c/r/u/d nodes and printing chains' structure.
447impl<'tcx> DominatedGraph<'tcx> {
448    // Only for inserting field obj node or pointed obj node.
449    pub fn generate_node_id(&self) -> usize {
450        if self.variables.len() == 0 || *self.variables.keys().max().unwrap() < self.local_len {
451            return self.local_len;
452        }
453        *self.variables.keys().max().unwrap() + 1
454    }
455
456    pub fn get_field_node_id(
457        &mut self,
458        local: usize,
459        field_idx: usize,
460        ty: Option<Ty<'tcx>>,
461    ) -> usize {
462        let node = self.get_var_node(local).unwrap();
463        if let Some(alias_local) = node.field.get(&field_idx) {
464            *alias_local
465        } else {
466            self.insert_field_node(local, field_idx, ty)
467        }
468    }
469
470    // Insert the responding field node of one local, then return its genrated node_id.
471    pub fn insert_field_node(
472        &mut self,
473        local: usize,
474        field_idx: usize,
475        ty: Option<Ty<'tcx>>,
476    ) -> usize {
477        let new_id = self.generate_node_id();
478        self.variables
479            .insert(new_id, VariableNode::new_default(new_id, ty));
480        let mut_node = self.get_var_node_mut(local).unwrap();
481        mut_node.field.insert(field_idx, new_id);
482        return new_id;
483    }
484
485    /// Find the variable ID in DG corresponding to a sequence of fields.
486    pub fn find_var_id_with_fields_seq(&mut self, local: usize, fields: &Vec<usize>) -> usize {
487        let mut cur = local;
488        for field in fields.clone() {
489            let mut cur_node = self.get_var_node(cur).unwrap();
490            if let TyKind::Ref(_, ty, _) = cur_node.ty.unwrap().kind() {
491                let point_to = self.get_point_to_id(cur);
492                cur_node = self.get_var_node(point_to).unwrap();
493            }
494            // If there exist a field node, then get it as cur node
495            if cur_node.field.get(&field).is_some() {
496                cur = *cur_node.field.get(&field).unwrap();
497                continue;
498            }
499            // Otherwise, insert a new field node.
500            match cur_node.ty.unwrap().kind() {
501                TyKind::Adt(adt_def, substs) => {
502                    if adt_def.is_struct() {
503                        for (idx, field_def) in adt_def.all_fields().enumerate() {
504                            if idx == field {
505                                cur = self.get_field_node_id(
506                                    cur,
507                                    field,
508                                    Some(field_def.ty(self.tcx, substs)),
509                                );
510                            }
511                        }
512                    }
513                }
514                // TODO: maybe unsafe here for setting ty as None!
515                _ => {
516                    rap_warn!("ty {:?}, field: {:?}", cur_node.ty.unwrap(), field);
517                    rap_warn!(
518                        "set field type as None! --- src: Dominated Graph / find_var_id_with_fields_seq"
519                    );
520                    cur = self.get_field_node_id(cur, field, None);
521                }
522            }
523        }
524        return cur;
525    }
526
527    /// Establishes a points-to relationship: lv -> rv.
528    /// - Updates graph topology.
529    /// - If `lv` is a Reference type, marks it as Aligned (Trusted Source).
530    pub fn point(&mut self, lv: usize, rv: usize) {
531        // rap_debug!("Graph Point: _{} -> _{}", lv, rv);
532
533        // 1. Update Topology: rv.pointed_by.insert(lv)
534        if let Some(rv_node) = self.get_var_node_mut(rv) {
535            rv_node.pointed_by.insert(lv);
536        } else {
537            rap_debug!("Graph Point Error: Target node _{} not found", rv);
538            return;
539        }
540
541        // 2. Update Topology & State: lv.points_to = rv
542        // We need to retrieve 'old_points_to' to clean up later
543        let old_points_to = if let Some(lv_node) = self.get_var_node_mut(lv) {
544            let old = lv_node.points_to;
545            lv_node.points_to = Some(rv);
546
547            // --- Update AlignState based on Type ---
548            // Logic: If lv is a Reference (&T), it implies the pointer is constructed
549            // from a valid, aligned Rust reference. We mark it as Aligned(T, abi_align).
550            if let Some(lv_ty) = lv_node.ty
551                && is_ref(lv_ty)
552            {
553                let pointee_ty = get_pointee(lv_ty);
554                lv_node.ots.align = AlignState::Aligned(pointee_ty);
555
556                rap_debug!(
557                    "Graph Point: Refined Ref _{} ({:?}) to Aligned via point()",
558                    lv,
559                    pointee_ty
560                );
561            }
562
563            old
564        } else {
565            None
566        };
567
568        // 3. Clean up: Remove lv from old_points_to's pointed_by set
569        if let Some(to) = old_points_to {
570            // Only remove if we are changing pointing target (and not pointing to the same thing)
571            if to != rv {
572                if let Some(ori_to_node) = self.get_var_node_mut(to) {
573                    ori_to_node.pointed_by.remove(&lv);
574                }
575            }
576        }
577    }
578
579    /// Get the ID of a variable node.
580    pub fn get_var_nod_id(&self, local_id: usize) -> usize {
581        self.get_var_node(local_id).unwrap().id
582    }
583
584    /// Get a variable node by its local ID.
585    pub fn get_map_idx_node(&self, local_id: usize) -> &VariableNode<'tcx> {
586        self.variables.get(&local_id).unwrap()
587    }
588
589    /// Get a variable node by its local ID.
590    pub fn get_var_node(&self, local_id: usize) -> Option<&VariableNode<'tcx>> {
591        for (_idx, var_node) in &self.variables {
592            if var_node.alias_set.contains(&local_id) {
593                return Some(var_node);
594            }
595        }
596        rap_warn!("def id:{:?}, local_id: {local_id}", self.def_id);
597        display_hashmap(&self.variables, 1);
598        None
599    }
600
601    /// Get a mutable reference to a variable node by its local ID.
602    pub fn get_var_node_mut(&mut self, local_id: usize) -> Option<&mut VariableNode<'tcx>> {
603        let va = self.variables.clone();
604        for (_idx, var_node) in &mut self.variables {
605            if var_node.alias_set.contains(&local_id) {
606                return Some(var_node);
607            }
608        }
609        rap_warn!("def id:{:?}, local_id: {local_id}", self.def_id);
610        display_hashmap(&va, 1);
611        None
612    }
613
614    // Merge node when (lv = move rv);
615    // In this case, lv will be the same with rv.
616    // And the nodes pointing to lv originally will re-point to rv.
617    pub fn merge(&mut self, lv: usize, rv: usize) {
618        let lv_node = self.get_var_node_mut(lv).unwrap().clone();
619        if lv_node.alias_set.contains(&rv) {
620            return;
621        }
622        for lv_pointed_by in lv_node.pointed_by.clone() {
623            self.point(lv_pointed_by, rv);
624        }
625        let lv_node = self.get_var_node_mut(lv).unwrap();
626        lv_node.alias_set.remove(&lv);
627        let lv_ty = lv_node.ty;
628        let lv_states = lv_node.ots.clone();
629        let rv_node = self.get_var_node_mut(rv).unwrap();
630        rv_node.alias_set.insert(lv);
631        // rv_node.states.merge_states(&lv_states);
632        if rv_node.ty.is_none() {
633            rv_node.ty = lv_ty;
634        }
635    }
636
637    // Called when (lv = copy rv);
638    pub fn copy_node(&mut self, lv: usize, rv: usize) {
639        let rv_node = self.get_var_node_mut(rv).unwrap().clone();
640        let lv_node = self.get_var_node_mut(lv).unwrap();
641        let lv_ty = lv_node.ty.unwrap();
642        lv_node.ots = rv_node.ots;
643        lv_node.cis = rv_node.cis;
644        lv_node.is_dropped = rv_node.is_dropped;
645        lv_node.offset_from = rv_node.offset_from;
646        let lv_id = lv_node.id;
647        if rv_node.points_to.is_some() {
648            self.point(lv_id, rv_node.points_to.unwrap());
649        }
650    }
651
652    /// Break the connection between two nodes.
653    fn break_node_connection(&mut self, lv: usize, rv: usize) {
654        let rv_node = self.get_var_node_mut(rv).unwrap();
655        rv_node.pointed_by.remove(&lv);
656        let lv_node = self.get_var_node_mut(lv).unwrap();
657        lv_node.points_to = None;
658    }
659
660    /// Initialize the self node with an intermediate result.
661    fn init_self_node(&mut self, self_id: usize, ty: Option<Ty<'tcx>>, state: States<'tcx>) {
662        let node = self.get_var_node_mut(self_id).unwrap();
663        node.ty = ty;
664        node.ots = state;
665    }
666
667    /// Insert intermediate results into the graph.
668    fn insert_node(
669        &mut self,
670        dv: usize,
671        ty: Option<Ty<'tcx>>,
672        parent_id: usize,
673        child_id: Option<usize>,
674        state: States<'tcx>,
675    ) {
676        self.variables.insert(
677            dv,
678            VariableNode::new(dv, child_id, HashSet::from([parent_id]), ty, state),
679        );
680    }
681
682    /// Delete a node from the graph.
683    fn delete_node(&mut self, idx: usize) {
684        let node = self.get_var_node(idx).unwrap().clone();
685        for pre_idx in &node.pointed_by.clone() {
686            let pre_node = self.get_var_node_mut(*pre_idx).unwrap();
687            pre_node.points_to = None;
688        }
689        if let Some(to) = &node.points_to.clone() {
690            let next_node = self.get_var_node_mut(*to).unwrap();
691            next_node.pointed_by.remove(&idx);
692        }
693        self.variables.remove(&idx);
694    }
695
696    /// Set the drop flag for a node.
697    pub fn set_drop(&mut self, idx: usize) -> bool {
698        if let Some(ori_node) = self.get_var_node_mut(idx) {
699            if ori_node.is_dropped == true {
700                // rap_warn!("Double free detected!"); // todo: update reports
701                return false;
702            }
703            ori_node.is_dropped = true;
704        }
705        true
706    }
707
708    /// Update the constant value of a node.
709    pub fn update_value(&mut self, arg: usize, value: usize) {
710        let node = self.get_var_node_mut(arg).unwrap();
711        node.const_value = value;
712        node.ots.init = true;
713    }
714
715    /// Insert a partial order constraint between two nodes.
716    pub fn insert_patial_op(&mut self, p1: usize, p2: usize, op: &BinOp) {
717        let p1_node = self.get_var_node_mut(p1).unwrap();
718        p1_node
719            .cis
720            .add_contract(PropertyContract::new_patial_order(p2, *op));
721        let p2_node = self.get_var_node_mut(p2).unwrap();
722        p2_node
723            .cis
724            .add_contract(PropertyContract::new_patial_order(p1, reverse_op(*op)));
725    }
726}
727
728/// Debug implementation for DominatedGraph.
729impl<'tcx> DominatedGraph<'tcx> {
730    /// Generate a DOT graph representation of the dominated graph.
731    pub fn to_dot_graph(&self) -> String {
732        let mut dot = String::new();
733        writeln!(dot, "digraph DominatedGraph {{").unwrap();
734
735        writeln!(
736            dot,
737            "    graph [compound=true, splines=polyline, nodesep=0.5, ranksep=0.5];"
738        )
739        .unwrap();
740        writeln!(dot, "    node [shape=plain, fontname=\"Courier\"];").unwrap();
741        writeln!(dot, "    edge [fontname=\"Courier\"];").unwrap();
742
743        let mut isolated_nodes = Vec::new();
744        let mut connected_nodes = Vec::new();
745
746        let mut keys: Vec<&usize> = self.variables.keys().collect();
747        keys.sort();
748
749        for id in keys {
750            if let Some(node) = self.variables.get(id) {
751                let is_isolated =
752                    node.points_to.is_none() && node.field.is_empty() && node.pointed_by.is_empty();
753
754                if is_isolated {
755                    isolated_nodes.push(*id);
756                } else {
757                    connected_nodes.push(*id);
758                }
759            }
760        }
761
762        if !isolated_nodes.is_empty() {
763            writeln!(dot, "    subgraph cluster_isolated {{").unwrap();
764            writeln!(dot, "        label = \"Isolated Variables (Grid Layout)\";").unwrap();
765            writeln!(dot, "        style = dashed;").unwrap();
766            writeln!(dot, "        color = grey;").unwrap();
767
768            let total_iso = isolated_nodes.len();
769            let cols = (total_iso as f64).sqrt().ceil() as usize;
770
771            for id in &isolated_nodes {
772                if let Some(node) = self.variables.get(id) {
773                    let node_label = self.generate_node_label(node);
774                    writeln!(dot, "        {} [label=<{}>];", id, node_label).unwrap();
775                }
776            }
777
778            for chunk in isolated_nodes.chunks(cols) {
779                write!(dot, "        {{ rank=same;").unwrap();
780                for id in chunk {
781                    write!(dot, " {};", id).unwrap();
782                }
783                writeln!(dot, " }}").unwrap();
784            }
785
786            for i in 0..isolated_nodes.chunks(cols).len() {
787                let current_row_idx = i * cols;
788                let next_row_idx = (i + 1) * cols;
789
790                if next_row_idx < total_iso {
791                    let curr_id = isolated_nodes[current_row_idx];
792                    let next_id = isolated_nodes[next_row_idx];
793                    writeln!(
794                        dot,
795                        "        {} -> {} [style=invis, weight=100];",
796                        curr_id, next_id
797                    )
798                    .unwrap();
799                }
800            }
801
802            writeln!(dot, "    }}").unwrap();
803        }
804
805        if !connected_nodes.is_empty() {
806            writeln!(dot, "    subgraph cluster_connected {{").unwrap();
807            writeln!(dot, "        label = \"Reference Graph\";").unwrap();
808            writeln!(dot, "        color = black;").unwrap();
809
810            for id in &connected_nodes {
811                if let Some(node) = self.variables.get(id) {
812                    let node_label = self.generate_node_label(node);
813                    writeln!(dot, "        {} [label=<{}>];", id, node_label).unwrap();
814                }
815            }
816            writeln!(dot, "    }}").unwrap();
817        }
818
819        // draw edges
820        writeln!(dot, "").unwrap();
821        for id in &connected_nodes {
822            if let Some(node) = self.variables.get(id) {
823                if let Some(target) = node.points_to {
824                    writeln!(
825                        dot,
826                        "    {} -> {} [label=\"ptr\", color=\"blue\", fontcolor=\"blue\"];",
827                        id, target
828                    )
829                    .unwrap();
830                }
831
832                let mut field_indices: Vec<&usize> = node.field.keys().collect();
833                field_indices.sort();
834                for field_idx in field_indices {
835                    let target_id = node.field.get(field_idx).unwrap();
836                    writeln!(
837                        dot,
838                        "    {} -> {} [label=\".{}\", style=\"dashed\"];",
839                        id, target_id, field_idx
840                    )
841                    .unwrap();
842                }
843            }
844        }
845
846        writeln!(dot, "}}").unwrap();
847        dot
848    }
849
850    /// Generate a label for a node in the DOT graph.
851    fn generate_node_label(&self, node: &VariableNode<'tcx>) -> String {
852        let ty_str = match node.ty {
853            Some(t) => format!("{:?}", t),
854            None => "None".to_string(),
855        };
856        let safe_ty = html_escape(&ty_str);
857
858        format!(
859            r#"<table border="0" cellborder="1" cellspacing="0" cellpadding="4">
860                <tr><td colspan="2"><b>ID: {}</b></td></tr>
861                <tr><td align="left">Type:</td><td align="left">{}</td></tr>
862                <tr><td align="left">Const:</td><td align="left">{}</td></tr>
863               </table>"#,
864            node.id, safe_ty, node.const_value,
865        )
866    }
867
868    /// Debug helper: Visualize the graph structure and states in a table format
869    pub fn display_dominated_graph(&self) {
870        const TABLE_WIDTH: usize = 145; // 增加宽度以容纳 Offset 列
871        println!(
872            "\n{:=^width$}",
873            " Dominated Graph Report ",
874            width = TABLE_WIDTH
875        );
876
877        let mut sorted_ids: Vec<&usize> = self.variables.keys().collect();
878        sorted_ids.sort();
879
880        if sorted_ids.is_empty() {
881            println!("  [Empty Graph]");
882            println!("{:=^width$}\n", "", width = TABLE_WIDTH);
883            return;
884        }
885
886        // Define table headers and separator
887        // ID: 6, Type: 25, Pt-To: 8, Fields: 15, Offset: 25, States: 40
888        let sep = format!(
889            "+{:-^6}+{:-^25}+{:-^8}+{:-^15}+{:-^25}+{:-^40}+",
890            "", "", "", "", "", ""
891        );
892        println!("{}", sep);
893        println!(
894            "| {:^6} | {:^25} | {:^8} | {:^15} | {:^25} | {:^40} |",
895            "ID", "Type", "Pt-To", "Fields", "Offset", "States"
896        );
897        println!("{}", sep);
898
899        for id in sorted_ids {
900            let node = &self.variables[id];
901
902            // 1. Format Type: Convert Ty to string and handle None
903            let ty_str = node
904                .ty
905                .map(|t| format!("{:?}", t))
906                .unwrap_or_else(|| "None".to_string());
907
908            // 2. Format Points-To: Show target node ID if exists
909            let pt_str = node
910                .points_to
911                .map(|p| format!("_{}", p))
912                .unwrap_or_else(|| "-".to_string());
913
914            // 3. Format Fields: Show "field_idx -> node_id" mapping
915            let fields_str = if node.field.is_empty() {
916                "-".to_string()
917            } else {
918                let mut fs: Vec<String> = node
919                    .field
920                    .iter()
921                    .map(|(k, v)| format!(".{}->_{}", k, v))
922                    .collect();
923                fs.sort(); // Keep deterministic order
924                fs.join(", ")
925            };
926
927            // 4. Format Offset: Show offset source info nicely
928            let offset_str = if let Some(def) = &node.offset_from {
929                match def {
930                    // PtrOffset: "_base +/- index"
931                    SymbolicDef::PtrOffset(op, base, idx, _) => {
932                        let op_str = match op {
933                            BinOp::Add => "+",
934                            BinOp::Sub => "-",
935                            _ => "?",
936                        };
937                        let idx_str = match idx {
938                            AnaOperand::Local(l) => format!("_{}", l),
939                            AnaOperand::Const(c) => format!("{}", c),
940                        };
941                        format!("_{} {} {}", base, op_str, idx_str)
942                    }
943                    SymbolicDef::Binary(BinOp::Offset, base, idx) => {
944                        let idx_str = match idx {
945                            AnaOperand::Local(l) => format!("_{}", l),
946                            AnaOperand::Const(c) => format!("{}", c),
947                        };
948                        format!("_{} + {}", base, idx_str)
949                    }
950                    _ => format!("{:?}", def),
951                }
952            } else {
953                "-".to_string()
954            };
955
956            // 5. Format States: concise flags for Init, NonNull, Align, etc.
957            let mut states_vec = Vec::new();
958            // 5.1 Extract alignment info
959            match &node.ots.align {
960                AlignState::Aligned(ty) => {
961                    let node_ty = node.ty.unwrap();
962                    if is_ptr(node_ty) || is_ref(node_ty) {
963                        states_vec.push(format!("Align({:?})", ty));
964                    }
965                }
966                AlignState::Unaligned(ty) => states_vec.push(format!("Unalign({:?})", ty)),
967                AlignState::Unknown => states_vec.push("Unknown".to_string()),
968            }
969            let states_str = if states_vec.is_empty() {
970                "-".to_string()
971            } else {
972                states_vec.join(", ")
973            };
974
975            // Print the row with truncation to keep table alignment
976            println!(
977                "| {:<6} | {:<25} | {:<8} | {:<15} | {:<25} | {:<40} |",
978                id,
979                self.safe_truncate_str(&ty_str, 25),
980                pt_str,
981                self.safe_truncate_str(&fields_str, 15),
982                self.safe_truncate_str(&offset_str, 25),
983                self.safe_truncate_str(&states_str, 40)
984            );
985        }
986
987        println!("{}", sep);
988        println!("{:=^width$}\n", " End Graph ", width = TABLE_WIDTH);
989    }
990
991    // Helper: Truncate strings to maintain table layout
992    fn safe_truncate_str(&self, s: &str, max_width: usize) -> String {
993        if s.chars().count() <= max_width {
994            return s.to_string();
995        }
996        let truncated: String = s.chars().take(max_width - 2).collect();
997        format!("{}..", truncated)
998    }
999}
1000
1001/// Escape HTML characters in a string.
1002fn html_escape(input: &str) -> String {
1003    input
1004        .replace("&", "&amp;")
1005        .replace("<", "&lt;")
1006        .replace(">", "&gt;")
1007        .replace("\"", "&quot;")
1008}
1009
1010impl<'tcx> DominatedGraph<'tcx> {
1011    /// Public method called by BodyVisitor to update graph topology
1012    /// when a PtrOffset definition is applied.
1013    pub fn update_from_offset_def(
1014        &mut self,
1015        // Parameters for the offset definition
1016        target_local: usize,
1017        // Base local variable being offset
1018        base_local: usize,
1019        // The symbolic definition of the offset
1020        offset_def: SymbolicDef<'tcx>,
1021    ) {
1022        // 1. Update Pointing: target points to whatever base points to
1023        // Because offset pointer usually stays within the same object allocation
1024        let base_point_to = self.get_point_to_id(base_local);
1025        self.point(target_local, base_point_to);
1026
1027        // 2. Record the offset relationship on the node
1028        // This is crucial for backtracking the base pointer during checks
1029        if let Some(node) = self.get_var_node_mut(target_local) {
1030            node.offset_from = Some(offset_def);
1031
1032            rap_debug!(
1033                "Graph Update: _{} is offset of _{} (via update_from_offset_def)",
1034                target_local,
1035                base_local
1036            );
1037        }
1038    }
1039}