rapx/check/senryx/
dominated_graph.rs

1//! Dominated graph state model for Senryx MIR analysis.
2//!
3//! The graph stores MIR locals, synthetic memory objects, field-sensitive nodes,
4//! points-to relationships, contract fact sets, and operational
5//! trace states (OTS). Checkers query this graph to decide whether an unsafe
6//! call's safety contracts are satisfied on the current path.
7
8use crate::{
9    analysis::utils::fn_info::{display_hashmap, get_pointee, is_ptr, is_ref, is_slice},
10    check::senryx::{
11        contract::{AlignState, ContractExpr, ContractFactSet, PropertyContract},
12        symbolic_analysis::{AnaOperand, SymbolicDef},
13    },
14};
15use rustc_hir::def_id::DefId;
16use rustc_middle::mir::BinOp;
17use rustc_middle::mir::Local;
18use rustc_middle::ty::TyKind;
19use rustc_middle::ty::{Ty, TyCtxt};
20use std::collections::{HashMap, HashSet};
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 = 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 facts: ContractFactSet<'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            facts: ContractFactSet::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            facts: ContractFactSet::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            facts: ContractFactSet::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    /// Construct a graph for `def_id` and initialize one node for each MIR local.
250    ///
251    /// Pointer and reference arguments are completed later by `init_arg`, where
252    /// they are connected to synthetic object nodes and caller contracts.
253    pub fn new(tcx: TyCtxt<'tcx>, def_id: DefId) -> Self {
254        let body = tcx.optimized_mir(def_id);
255        let locals = body.local_decls.clone();
256        let mut var_map: HashMap<usize, VariableNode<'_>> = HashMap::new();
257        for (idx, local) in locals.iter().enumerate() {
258            let local_ty = local.ty;
259            let mut node = VariableNode::new_default(idx, Some(local_ty));
260            if local_ty.to_string().contains("MaybeUninit") {
261                node.ots.init = false;
262            }
263            var_map.insert(idx, node);
264        }
265        Self {
266            tcx,
267            def_id,
268            local_len: locals.len(),
269            variables: var_map,
270        }
271    }
272
273    /// Initialize the self node with an intermediate result.
274    pub fn init_self_with_inter(&mut self, inter_result: InterResultNode<'tcx>) {
275        let self_node = self.get_var_node(1).unwrap().clone();
276        if self_node.ty.unwrap().is_ref() {
277            let obj_node = self.get_var_node(self.get_point_to_id(1)).unwrap();
278            self.dfs_insert_inter_results(inter_result, obj_node.id);
279        } else {
280            self.dfs_insert_inter_results(inter_result, self_node.id);
281        }
282    }
283
284    /// Insert intermediate results into the graph.
285    pub fn dfs_insert_inter_results(&mut self, inter_result: InterResultNode<'tcx>, local: usize) {
286        let new_id = self.generate_node_id();
287        let node = self.get_var_node_mut(local).unwrap();
288        // node.ty = inter_result.ty;
289        node.ots = inter_result.states;
290        node.const_value = inter_result.const_value;
291        if inter_result.point_to.is_some() {
292            let new_node = inter_result.point_to.unwrap();
293            node.points_to = Some(new_id);
294            self.insert_node(
295                new_id,
296                new_node.ty.clone(),
297                local,
298                None,
299                new_node.states.clone(),
300            );
301            self.dfs_insert_inter_results(*new_node, new_id);
302        }
303        for (field_idx, field_inter) in inter_result.fields {
304            let field_node_id = self.insert_field_node(local, field_idx, field_inter.ty.clone());
305            self.dfs_insert_inter_results(field_inter, field_node_id);
306        }
307    }
308
309    /// Initialize the arguments of the function.
310    pub fn init_arg(&mut self) {
311        // init arg nodes' point to nodes.
312        let body = self.tcx.optimized_mir(self.def_id);
313        let locals = body.local_decls.clone();
314        let fn_sig = self.tcx.fn_sig(self.def_id).skip_binder();
315        let param_len = fn_sig.inputs().skip_binder().len();
316        for idx in 1..param_len + 1 {
317            let local_ty = locals[Local::from(idx)].ty;
318            self.generate_ptr_with_obj_node(local_ty, idx);
319        }
320        // init args' contract facts
321        let fact_results = crate::analysis::utils::fn_info::generate_contract_from_annotation(
322            self.tcx,
323            self.def_id,
324        );
325        for (base, fields, contract) in fact_results {
326            if fields.len() == 0 {
327                self.insert_fact_for_arg(base, contract);
328            } else {
329                let mut cur_base = base;
330                let mut field_node = base;
331                for field in fields {
332                    field_node = self.insert_field_node(cur_base, field.0, Some(field.1));
333                    // check if field's type is ptr or ref: yes -> create shadow var
334                    self.generate_ptr_with_obj_node(field.1, field_node);
335                    cur_base = field_node;
336                }
337                self.insert_fact_for_arg(field_node, contract);
338            }
339        }
340    }
341
342    /// Insert a contract fact into an argument node.
343    fn insert_fact_for_arg(&mut self, local: usize, contract: PropertyContract<'tcx>) {
344        let node = self.get_var_node_mut(local).unwrap();
345        node.facts.add_contract(contract);
346    }
347
348    /// When generate obj node, this function will add InBound Sp automatically.
349    pub fn generate_ptr_with_obj_node(&mut self, local_ty: Ty<'tcx>, idx: usize) -> usize {
350        let new_id = self.generate_node_id();
351        if is_ptr(local_ty) {
352            // modify ptr node pointed
353            self.get_var_node_mut(idx).unwrap().points_to = Some(new_id);
354            self.get_var_node_mut(idx).unwrap().ots = States::new_unknown();
355            // insert pointed object node
356            self.insert_node(
357                new_id,
358                Some(get_pointee(local_ty)),
359                idx,
360                None,
361                States::new_unknown(),
362            );
363            self.add_bound_for_obj(new_id, local_ty);
364        } else if is_ref(local_ty) {
365            // modify ptr node pointed
366            self.get_var_node_mut(idx).unwrap().points_to = Some(new_id);
367            // insert ref object node
368            self.insert_node(
369                new_id,
370                Some(get_pointee(local_ty)),
371                idx,
372                None,
373                States::new(get_pointee(local_ty)),
374            );
375            self.add_bound_for_obj(new_id, local_ty);
376        }
377        new_id
378    }
379
380    /// Add a bound for an object node.
381    fn add_bound_for_obj(&mut self, new_id: usize, local_ty: Ty<'tcx>) {
382        let new_node = self.get_var_node_mut(new_id).unwrap();
383        let new_node_ty = get_pointee(local_ty);
384        let contract = if is_slice(new_node_ty).is_some() {
385            let inner_ty = is_slice(new_node_ty).unwrap();
386            PropertyContract::new_obj_boundary(inner_ty, ContractExpr::new_unknown())
387        } else {
388            PropertyContract::new_obj_boundary(new_node_ty, ContractExpr::new_value(1))
389        };
390        new_node.facts.add_contract(contract);
391    }
392
393    /// if current node is ptr or ref, then return the new node pointed by it.
394    pub fn check_ptr(&mut self, arg: usize) -> usize {
395        if self.get_var_node_mut(arg).unwrap().ty.is_none() {
396            display_hashmap(&self.variables, 1);
397        };
398        let node_ty = self.get_var_node_mut(arg).unwrap().ty.unwrap();
399        if is_ptr(node_ty) || is_ref(node_ty) {
400            return self.generate_ptr_with_obj_node(node_ty, arg);
401        }
402        arg
403    }
404
405    /// Get the type of a local variable by its place.
406    pub fn get_local_ty_by_place(&self, arg: usize) -> Option<Ty<'tcx>> {
407        let body = self.tcx.optimized_mir(self.def_id);
408        let locals = body.local_decls.clone();
409        if arg < locals.len() {
410            return Some(locals[Local::from(arg)].ty);
411        } else {
412            // If the arg is a field of some place, we search the whole map for it.
413            return self.get_var_node(arg).unwrap().ty;
414        }
415    }
416
417    /// Get the type of an object through a chain of pointers.
418    pub fn get_obj_ty_through_chain(&self, arg: usize) -> Option<Ty<'tcx>> {
419        let var = self.get_var_node(arg).unwrap();
420        // If the var is ptr or ref, then find its pointed obj.
421        if let Some(pointed_idx) = var.points_to {
422            self.get_obj_ty_through_chain(pointed_idx)
423        } else {
424            var.ty
425        }
426    }
427
428    /// Get the ID of the node pointed to by a pointer or reference.
429    pub fn get_point_to_id(&self, arg: usize) -> usize {
430        let var = self.get_var_node(arg).unwrap();
431        if let Some(pointed_idx) = var.points_to {
432            pointed_idx
433        } else {
434            arg
435        }
436    }
437
438    /// Check if a node ID corresponds to a local variable.
439    pub fn is_local(&self, node_id: usize) -> bool {
440        self.local_len > node_id
441    }
442}
443
444// This implementation has the auxiliary functions of DominatedGraph,
445// including c/r/u/d nodes and printing chains' structure.
446impl<'tcx> DominatedGraph<'tcx> {
447    /// Return the next synthetic node id for field or pointee object nodes.
448    pub fn generate_node_id(&self) -> usize {
449        if self.variables.len() == 0 || *self.variables.keys().max().unwrap() < self.local_len {
450            return self.local_len;
451        }
452        *self.variables.keys().max().unwrap() + 1
453    }
454
455    /// Get or create the synthetic node for `local.field_idx`.
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 a field node under `local` and return the generated 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(_, _, _) = 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 nodes for move assignments (`lv = move rv`).
615    ///
616    /// After the merge, `lv` becomes an alias of `rv` and incoming pointers that
617    /// used to target `lv` are redirected to `rv`.
618    pub fn merge(&mut self, lv: usize, rv: usize) {
619        let lv_node = self.get_var_node_mut(lv).unwrap().clone();
620        if lv_node.alias_set.contains(&rv) {
621            return;
622        }
623        for lv_pointed_by in lv_node.pointed_by.clone() {
624            self.point(lv_pointed_by, rv);
625        }
626        let lv_node = self.get_var_node_mut(lv).unwrap();
627        lv_node.alias_set.remove(&lv);
628        let lv_ty = lv_node.ty;
629        let rv_node = self.get_var_node_mut(rv).unwrap();
630        rv_node.alias_set.insert(lv);
631        if rv_node.ty.is_none() {
632            rv_node.ty = lv_ty;
633        }
634    }
635
636    /// Copy node state for copy assignments (`lv = copy rv`).
637    ///
638    /// This preserves value/state facts from `rv` on `lv` and points `lv` at
639    /// the same modeled object when `rv` is a pointer/reference.
640    pub fn copy_node(&mut self, lv: usize, rv: usize) {
641        let rv_node = self.get_var_node_mut(rv).unwrap().clone();
642        let lv_node = self.get_var_node_mut(lv).unwrap();
643        lv_node.ots = rv_node.ots;
644        lv_node.facts = rv_node.facts;
645        lv_node.is_dropped = rv_node.is_dropped;
646        lv_node.offset_from = rv_node.offset_from;
647        let lv_id = lv_node.id;
648        if rv_node.points_to.is_some() {
649            self.point(lv_id, rv_node.points_to.unwrap());
650        }
651    }
652
653    /// Insert intermediate results into the graph.
654    fn insert_node(
655        &mut self,
656        dv: usize,
657        ty: Option<Ty<'tcx>>,
658        parent_id: usize,
659        child_id: Option<usize>,
660        state: States<'tcx>,
661    ) {
662        self.variables.insert(
663            dv,
664            VariableNode::new(dv, child_id, HashSet::from([parent_id]), ty, state),
665        );
666    }
667
668    /// Set the drop flag for a node.
669    pub fn set_drop(&mut self, idx: usize) -> bool {
670        if let Some(ori_node) = self.get_var_node_mut(idx) {
671            if ori_node.is_dropped == true {
672                // rap_warn!("Double free detected!"); // todo: update reports
673                return false;
674            }
675            ori_node.is_dropped = true;
676        }
677        true
678    }
679
680    /// Update the constant value of a node.
681    pub fn update_value(&mut self, arg: usize, value: usize) {
682        let node = self.get_var_node_mut(arg).unwrap();
683        node.const_value = value;
684        node.ots.init = true;
685    }
686
687    /// Insert a partial-order predicate between two nodes.
688    pub fn insert_partial_order(&mut self, p1: usize, p2: usize, op: &BinOp) {
689        let contract = PropertyContract::new_partial_order(p1, p2, *op);
690        let p1_node = self.get_var_node_mut(p1).unwrap();
691        p1_node.facts.add_contract(contract.clone());
692        let p2_node = self.get_var_node_mut(p2).unwrap();
693        p2_node.facts.add_contract(contract);
694    }
695}
696
697/// Debug implementation for DominatedGraph.
698impl<'tcx> DominatedGraph<'tcx> {
699    /// Generate a DOT graph representation of the dominated graph.
700    pub fn to_dot_graph(&self) -> String {
701        let mut dot = String::new();
702        writeln!(dot, "digraph DominatedGraph {{").unwrap();
703
704        writeln!(
705            dot,
706            "    graph [compound=true, splines=polyline, nodesep=0.5, ranksep=0.5];"
707        )
708        .unwrap();
709        writeln!(dot, "    node [shape=plain, fontname=\"Courier\"];").unwrap();
710        writeln!(dot, "    edge [fontname=\"Courier\"];").unwrap();
711
712        let mut isolated_nodes = Vec::new();
713        let mut connected_nodes = Vec::new();
714
715        let mut keys: Vec<&usize> = self.variables.keys().collect();
716        keys.sort();
717
718        for id in keys {
719            if let Some(node) = self.variables.get(id) {
720                let is_isolated =
721                    node.points_to.is_none() && node.field.is_empty() && node.pointed_by.is_empty();
722
723                if is_isolated {
724                    isolated_nodes.push(*id);
725                } else {
726                    connected_nodes.push(*id);
727                }
728            }
729        }
730
731        if !isolated_nodes.is_empty() {
732            writeln!(dot, "    subgraph cluster_isolated {{").unwrap();
733            writeln!(dot, "        label = \"Isolated Variables (Grid Layout)\";").unwrap();
734            writeln!(dot, "        style = dashed;").unwrap();
735            writeln!(dot, "        color = grey;").unwrap();
736
737            let total_iso = isolated_nodes.len();
738            let cols = (total_iso as f64).sqrt().ceil() as usize;
739
740            for id in &isolated_nodes {
741                if let Some(node) = self.variables.get(id) {
742                    let node_label = self.generate_node_label(node);
743                    writeln!(dot, "        {} [label=<{}>];", id, node_label).unwrap();
744                }
745            }
746
747            for chunk in isolated_nodes.chunks(cols) {
748                write!(dot, "        {{ rank=same;").unwrap();
749                for id in chunk {
750                    write!(dot, " {};", id).unwrap();
751                }
752                writeln!(dot, " }}").unwrap();
753            }
754
755            for i in 0..isolated_nodes.chunks(cols).len() {
756                let current_row_idx = i * cols;
757                let next_row_idx = (i + 1) * cols;
758
759                if next_row_idx < total_iso {
760                    let curr_id = isolated_nodes[current_row_idx];
761                    let next_id = isolated_nodes[next_row_idx];
762                    writeln!(
763                        dot,
764                        "        {} -> {} [style=invis, weight=100];",
765                        curr_id, next_id
766                    )
767                    .unwrap();
768                }
769            }
770
771            writeln!(dot, "    }}").unwrap();
772        }
773
774        if !connected_nodes.is_empty() {
775            writeln!(dot, "    subgraph cluster_connected {{").unwrap();
776            writeln!(dot, "        label = \"Reference Graph\";").unwrap();
777            writeln!(dot, "        color = black;").unwrap();
778
779            for id in &connected_nodes {
780                if let Some(node) = self.variables.get(id) {
781                    let node_label = self.generate_node_label(node);
782                    writeln!(dot, "        {} [label=<{}>];", id, node_label).unwrap();
783                }
784            }
785            writeln!(dot, "    }}").unwrap();
786        }
787
788        // draw edges
789        writeln!(dot, "").unwrap();
790        for id in &connected_nodes {
791            if let Some(node) = self.variables.get(id) {
792                if let Some(target) = node.points_to {
793                    writeln!(
794                        dot,
795                        "    {} -> {} [label=\"ptr\", color=\"blue\", fontcolor=\"blue\"];",
796                        id, target
797                    )
798                    .unwrap();
799                }
800
801                let mut field_indices: Vec<&usize> = node.field.keys().collect();
802                field_indices.sort();
803                for field_idx in field_indices {
804                    let target_id = node.field.get(field_idx).unwrap();
805                    writeln!(
806                        dot,
807                        "    {} -> {} [label=\".{}\", style=\"dashed\"];",
808                        id, target_id, field_idx
809                    )
810                    .unwrap();
811                }
812            }
813        }
814
815        writeln!(dot, "}}").unwrap();
816        dot
817    }
818
819    /// Generate a label for a node in the DOT graph.
820    fn generate_node_label(&self, node: &VariableNode<'tcx>) -> String {
821        let ty_str = match node.ty {
822            Some(t) => format!("{:?}", t),
823            None => "None".to_string(),
824        };
825        let safe_ty = html_escape(&ty_str);
826
827        format!(
828            r#"<table border="0" cellborder="1" cellspacing="0" cellpadding="4">
829                <tr><td colspan="2"><b>ID: {}</b></td></tr>
830                <tr><td align="left">Type:</td><td align="left">{}</td></tr>
831                <tr><td align="left">Const:</td><td align="left">{}</td></tr>
832               </table>"#,
833            node.id, safe_ty, node.const_value,
834        )
835    }
836
837    /// Debug helper: Visualize the graph structure and states in a table format
838    pub fn display_dominated_graph(&self) {
839        const TABLE_WIDTH: usize = 145; // Keep enough room for the Offset column.
840        println!(
841            "\n{:=^width$}",
842            " Dominated Graph Report ",
843            width = TABLE_WIDTH
844        );
845
846        let mut sorted_ids: Vec<&usize> = self.variables.keys().collect();
847        sorted_ids.sort();
848
849        if sorted_ids.is_empty() {
850            println!("  [Empty Graph]");
851            println!("{:=^width$}\n", "", width = TABLE_WIDTH);
852            return;
853        }
854
855        // Define table headers and separator
856        // ID: 6, Type: 25, Pt-To: 8, Fields: 15, Offset: 25, States: 40
857        let sep = format!(
858            "+{:-^6}+{:-^25}+{:-^8}+{:-^15}+{:-^25}+{:-^40}+",
859            "", "", "", "", "", ""
860        );
861        println!("{}", sep);
862        println!(
863            "| {:^6} | {:^25} | {:^8} | {:^15} | {:^25} | {:^40} |",
864            "ID", "Type", "Pt-To", "Fields", "Offset", "States"
865        );
866        println!("{}", sep);
867
868        for id in sorted_ids {
869            let node = &self.variables[id];
870
871            // 1. Format Type: Convert Ty to string and handle None
872            let ty_str = node
873                .ty
874                .map(|t| format!("{:?}", t))
875                .unwrap_or_else(|| "None".to_string());
876
877            // 2. Format Points-To: Show target node ID if exists
878            let pt_str = node
879                .points_to
880                .map(|p| format!("_{}", p))
881                .unwrap_or_else(|| "-".to_string());
882
883            // 3. Format Fields: Show "field_idx -> node_id" mapping
884            let fields_str = if node.field.is_empty() {
885                "-".to_string()
886            } else {
887                let mut fs: Vec<String> = node
888                    .field
889                    .iter()
890                    .map(|(k, v)| format!(".{}->_{}", k, v))
891                    .collect();
892                fs.sort(); // Keep deterministic order
893                fs.join(", ")
894            };
895
896            // 4. Format Offset: Show offset source info nicely
897            let offset_str = if let Some(def) = &node.offset_from {
898                match def {
899                    // PtrOffset: "_base +/- index"
900                    SymbolicDef::PtrOffset(op, base, idx, _) => {
901                        let op_str = match op {
902                            BinOp::Add => "+",
903                            BinOp::Sub => "-",
904                            _ => "?",
905                        };
906                        let idx_str = match idx {
907                            AnaOperand::Local(l) => format!("_{}", l),
908                            AnaOperand::Const(c) => format!("{}", c),
909                        };
910                        format!("_{} {} {}", base, op_str, idx_str)
911                    }
912                    SymbolicDef::Binary(BinOp::Offset, base, idx) => {
913                        let idx_str = match idx {
914                            AnaOperand::Local(l) => format!("_{}", l),
915                            AnaOperand::Const(c) => format!("{}", c),
916                        };
917                        format!("_{} + {}", base, idx_str)
918                    }
919                    _ => format!("{:?}", def),
920                }
921            } else {
922                "-".to_string()
923            };
924
925            // 5. Format States: concise flags for Init, NonNull, Align, etc.
926            let mut states_vec = Vec::new();
927            // 5.1 Extract alignment info
928            match &node.ots.align {
929                AlignState::Aligned(ty) => {
930                    let node_ty = node.ty.unwrap();
931                    if is_ptr(node_ty) || is_ref(node_ty) {
932                        states_vec.push(format!("Align({:?})", ty));
933                    }
934                }
935                AlignState::Unaligned(ty) => states_vec.push(format!("Unalign({:?})", ty)),
936                AlignState::Unknown => states_vec.push("Unknown".to_string()),
937            }
938            let states_str = if states_vec.is_empty() {
939                "-".to_string()
940            } else {
941                states_vec.join(", ")
942            };
943
944            // Print the row with truncation to keep table alignment
945            println!(
946                "| {:<6} | {:<25} | {:<8} | {:<15} | {:<25} | {:<40} |",
947                id,
948                self.safe_truncate_str(&ty_str, 25),
949                pt_str,
950                self.safe_truncate_str(&fields_str, 15),
951                self.safe_truncate_str(&offset_str, 25),
952                self.safe_truncate_str(&states_str, 40)
953            );
954        }
955
956        println!("{}", sep);
957        println!("{:=^width$}\n", " End Graph ", width = TABLE_WIDTH);
958    }
959
960    // Helper: Truncate strings to maintain table layout
961    fn safe_truncate_str(&self, s: &str, max_width: usize) -> String {
962        if s.chars().count() <= max_width {
963            return s.to_string();
964        }
965        let truncated: String = s.chars().take(max_width - 2).collect();
966        format!("{}..", truncated)
967    }
968}
969
970/// Escape HTML characters in a string.
971fn html_escape(input: &str) -> String {
972    input
973        .replace("&", "&amp;")
974        .replace("<", "&lt;")
975        .replace(">", "&gt;")
976        .replace("\"", "&quot;")
977}
978
979impl<'tcx> DominatedGraph<'tcx> {
980    /// Public method called by BodyVisitor to update graph topology
981    /// when a PtrOffset definition is applied.
982    pub fn update_from_offset_def(
983        &mut self,
984        // Parameters for the offset definition
985        target_local: usize,
986        // Base local variable being offset
987        base_local: usize,
988        // The symbolic definition of the offset
989        offset_def: SymbolicDef<'tcx>,
990    ) {
991        // 1. Update Pointing: target points to whatever base points to
992        // Because offset pointer usually stays within the same object allocation
993        let base_point_to = self.get_point_to_id(base_local);
994        self.point(target_local, base_point_to);
995
996        // 2. Record the offset relationship on the node
997        // This is crucial for backtracking the base pointer during checks
998        if let Some(node) = self.get_var_node_mut(target_local) {
999            node.offset_from = Some(offset_def);
1000
1001            rap_debug!(
1002                "Graph Update: _{} is offset of _{} (via update_from_offset_def)",
1003                target_local,
1004                base_local
1005            );
1006        }
1007    }
1008}