Skip to main content

rapx/analysis/range/domain/
interproc.rs

1use crate::analysis::range::domain::ConstraintGraph;
2use crate::analysis::range::domain::domain::CallOp;
3use crate::analysis::range::domain::domain::{ConstConvert, IntervalArithmetic, VarNodes};
4use crate::analysis::range::{Range, RangeType};
5use crate::compat::FxHashMap;
6use rustc_hir::def_id::DefId;
7use rustc_middle::mir::Operand;
8use rustc_middle::mir::Place;
9use std::cell::RefCell;
10use std::fmt::Debug;
11use std::rc::Rc;
12
13impl<'tcx, T: IntervalArithmetic + ConstConvert + Debug> CallOp<'tcx, T> {
14    pub fn eval_call(
15        &self,
16        caller_vars: &VarNodes<'tcx, T>,
17        cg_map: &FxHashMap<DefId, Rc<RefCell<ConstraintGraph<'tcx, T>>>>,
18        vars_map: &mut FxHashMap<DefId, Vec<RefCell<VarNodes<'tcx, T>>>>,
19    ) -> Range<T> {
20        match self.fun_path.as_str() {
21            "std::iter::IntoIterator::into_iter" => match self.args.first() {
22                Some(Operand::Copy(place)) | Some(Operand::Move(place)) => {
23                    rap_trace!(
24                        "Iterator detected on place {:?}, returning its range",
25                        place
26                    );
27                    if let Some(var_node) = caller_vars.get(place) {
28                        let range = var_node.get_range().clone();
29                        rap_trace!(
30                            "Iterator detected on place {:?}, returning its range: {:?}",
31                            place,
32                            range
33                        );
34                        return range;
35                    }
36                }
37                _ => {}
38            },
39            "std::iter::Iterator::next" => match self.args.first() {
40                Some(Operand::Copy(place)) | Some(Operand::Move(place)) => {
41                    rap_trace!(
42                        "Iterator next detected on place {:?}, returning its range",
43                        place
44                    );
45                    if let Some(var_node) = caller_vars.get(place) {
46                        let range = var_node.get_range().clone();
47                        rap_trace!(
48                            "Iterator next detected on place {:?}, returning its range: {:?}",
49                            place,
50                            range
51                        );
52                        return range;
53                    }
54                }
55                _ => {}
56            },
57            "core::slice::<impl [T]>::len" => {
58                let mut result = Range::bottom();
59                match self.args.last() {
60                    Some(Operand::Copy(place)) | Some(Operand::Move(place)) => {
61                        let range = caller_vars[place].get_range().clone();
62                        let len = range
63                            .get_upper()
64                            .clone()
65                            .checked_sub(&range.get_lower().clone())
66                            .unwrap_or_else(|| {
67                                rap_trace!(
68                                    "len() subtraction overflow for range {:?}, returning [0, max]",
69                                    range
70                                );
71                                T::max_value()
72                            });
73                        result = Range::exact(len.clone());
74                    }
75                    Some(Operand::Constant(c)) => {}
76                    None => {}
77                    #[cfg(rapx_ge_99)]
78                    _ => {}
79                }
80                rap_trace!(
81                    "len() detected on place {:?}, returning its range: {:?}",
82                    self.sink,
83                    result
84                );
85                return result;
86            }
87            "std::ops::IndexMut::index_mut" => {
88                let mut result = Range::bottom();
89
90                match self.args.last() {
91                    Some(Operand::Copy(place)) | Some(Operand::Move(place)) => {
92                        result = caller_vars[place].get_range().clone();
93                    }
94                    Some(Operand::Constant(c)) => {}
95                    None => {}
96                    #[cfg(rapx_ge_99)]
97                    _ => {}
98                }
99
100                rap_trace!(
101                    "IndexMut detected on place {:?}, returning its range: {:?}",
102                    self.sink,
103                    result
104                );
105                return result;
106            }
107            "std::ops::Index::index" => {
108                let mut result = Range::bottom();
109
110                match self.args.last() {
111                    Some(Operand::Copy(place)) | Some(Operand::Move(place)) => {
112                        result = caller_vars[place].get_range().clone();
113                    }
114                    Some(Operand::Constant(c)) => {}
115                    None => {}
116                    #[cfg(rapx_ge_99)]
117                    _ => {}
118                }
119
120                rap_trace!(
121                    "Index detected on place {:?}, returning its range: {:?}",
122                    self.sink,
123                    result
124                );
125                return result;
126            }
127            "core::panicking::panic" | "std::panicking::panic" => {
128                rap_trace!("Panic call detected, returning bottom range.");
129                return Range::new(T::max_value(), T::min_value(), RangeType::Empty);
130            }
131            _ => {}
132        }
133        // 1. Find the callee's ConstraintGraph in the map.
134        if let Some(rc_callee_cg_cell) = cg_map.get(&self.def_id) {
135            rap_debug!(
136                "Evaluating call to {:?} with args {:?}",
137                self.def_id,
138                self.args
139            );
140            // 2. Try to get a mutable borrow of the callee's graph.
141            //    Using `try_borrow_mut` is safer than `borrow_mut` to avoid panicking on recursive calls.
142            if let Ok(mut callee_cg) = rc_callee_cg_cell.try_borrow_mut() {
143                // 3. Pass arguments from caller to callee.
144                //    This assumes arguments are in order and `_1`, `_2`, ... in the callee MIR.
145                for (i, caller_arg_operand) in self.args.iter().enumerate() {
146                    rap_debug!(
147                        "Processing argument {}: {:?} to callee {:?}",
148                        i,
149                        caller_arg_operand,
150                        self.def_id
151                    );
152                    match caller_arg_operand {
153                        Operand::Copy(caller_arg_place) | Operand::Move(caller_arg_place) => {
154                            // Add the variable node for the caller's argument.
155                            // Callee arguments are typically `_1`, `_2`, ...
156                            let callee_arg_local = rustc_middle::mir::Local::from_usize(i + 1);
157
158                            // Find the corresponding Place and VarNode in the callee.
159                            if let Some(callee_arg_node) = callee_cg.vars.values_mut().find(|v| {
160                                v.v.local == callee_arg_local && v.v.projection.is_empty()
161                            }) {
162                                // Get the range from the caller's variable and set it for the callee's argument.
163                                if let Some(caller_arg_node) = caller_vars.get(&caller_arg_place) {
164                                    let arg_range = caller_arg_node.get_range().clone();
165                                    callee_arg_node.set_range(arg_range);
166                                    rap_debug!(
167                                        "Passing argument from {:?} to callee {:?} : {:?} {:?} -> {:?}",
168                                        caller_arg_place,
169                                        self.def_id,
170                                        callee_arg_node.get_value(),
171                                        caller_arg_node.get_range(),
172                                        callee_arg_node.get_range()
173                                    );
174                                }
175                            }
176                        }
177                        Operand::Constant(const_operand) => {
178                            rap_debug!(
179                                "constant argument {:?} to callee {:?}",
180                                const_operand,
181                                self.def_id
182                            );
183                            let callee_arg_local = rustc_middle::mir::Local::from_usize(i + 1);
184                            if let Some(const_value) = T::from_const(&const_operand.const_) {
185                                if let Some(callee_arg_node) =
186                                    callee_cg.vars.values_mut().find(|v| {
187                                        v.v.local == callee_arg_local && v.v.projection.is_empty()
188                                    })
189                                {
190                                    // Get the range from the caller's variable and set it for the callee's argument.
191
192                                    let arg_range = Range::new(
193                                        const_value.clone(),
194                                        const_value.clone(),
195                                        RangeType::Regular,
196                                    );
197                                    callee_arg_node.set_range(arg_range.clone());
198                                    rap_debug!(
199                                        "Passing argument from {:?} to callee {:?} : {:?} {:?} -> {:?}",
200                                        const_value,
201                                        self.def_id,
202                                        callee_arg_node.get_value(),
203                                        arg_range,
204                                        callee_arg_node.get_range()
205                                    );
206                                }
207                            }
208                            // Find the corresponding Place and VarNode in the callee.
209                        }
210                        #[cfg(rapx_ge_99)]
211                        Operand::RuntimeChecks(_) => {}
212                    }
213                }
214
215                // 4. Run analysis on the callee.
216                //    NOTE: This is a simplification. A full implementation would use memoization
217                //    or a bottom-up analysis order to avoid re-analyzing functions repeatedly.
218                //    For now, we re-run it to ensure argument values are propagated.
219                callee_cg.find_intervals(cg_map, vars_map);
220
221                // 5. Retrieve the return value.
222                //    The return value is stored in `_0` (RETURN_PLACE).
223                let return_place_local = 0 as usize; // `_0` is typically the first local.
224
225                // Find all variables that contribute to the return value.
226                // The `rerurn_places` set in the callee's graph tracks these.
227                if let Some(return_node) = callee_cg.vars.get_mut(&Place::return_place()) {
228                    let return_range = return_node.get_range().clone();
229                    rap_debug!(" final return range {:?} ", return_range);
230                    return return_range;
231                }
232                let Some(callee_varnodes_vec) = vars_map.get_mut(&self.def_id) else {
233                    panic!(
234                        "No variable map entry for this function {:?}, skipping Nuutila\n",
235                        self.def_id
236                    );
237                };
238                callee_cg.reset_vars(callee_varnodes_vec);
239            } else {
240                // Recursive call detected or graph is already borrowed.
241                // Conservatively return a full range.
242                rap_trace!(
243                    "Recursive call or existing borrow for {:?}, returning top.",
244                    self.def_id
245                );
246                return Range::top();
247            }
248        }
249
250        // Callee not found (e.g., external library function, function pointer).
251        // Return a conservative full range.
252        rap_trace!(
253            "Callee ConstraintGraph for {:?} not found, returning top.",
254            self.def_id
255        );
256        Range::top()
257}
258}