rapx/analysis/core/alias_analysis/mfp/
intraproc.rs

1extern crate rustc_mir_dataflow;
2use rustc_data_structures::fx::FxHashMap;
3use rustc_hir::def_id::DefId;
4use rustc_middle::{
5    mir::{
6        Body, CallReturnPlaces, Location, Operand, Place, Rvalue, Statement, StatementKind,
7        Terminator, TerminatorEdges, TerminatorKind,
8    },
9    ty::{self, Ty, TyCtxt, TypingEnv},
10};
11use rustc_mir_dataflow::{Analysis, JoinSemiLattice, fmt::DebugWithContext};
12use std::cell::RefCell;
13use std::rc::Rc;
14
15use super::super::{FnAliasMap, FnAliasPairs};
16use super::transfer;
17use crate::analysis::core::alias_analysis::default::types::is_not_drop;
18
19/// Apply a function summary to the current state
20fn apply_function_summary<'tcx>(
21    state: &mut AliasDomain,
22    destination: Place<'tcx>,
23    args: &[Operand<'tcx>],
24    summary: &FnAliasPairs,
25    place_info: &PlaceInfo<'tcx>,
26) {
27    // Convert destination to PlaceId
28    let dest_id = transfer::mir_place_to_place_id(destination);
29
30    // Build a mapping from callee's argument indices to caller's PlaceIds
31    // Index 0 is return value, indices 1+ are arguments
32    let mut actual_places = vec![dest_id.clone()];
33    for arg in args {
34        if let Some(arg_id) = transfer::operand_to_place_id(arg) {
35            actual_places.push(arg_id);
36        } else {
37            // If argument is not a place (e.g., constant), use a dummy
38            actual_places.push(PlaceId::Local(usize::MAX));
39        }
40    }
41
42    // Apply each alias pair from the summary
43    for alias_pair in summary.aliases() {
44        let left_idx = alias_pair.left_local();
45        let right_idx = alias_pair.right_local();
46
47        // Check bounds
48        if left_idx >= actual_places.len() || right_idx >= actual_places.len() {
49            continue;
50        }
51
52        // Skip if either place is a dummy (constant argument)
53        // Dummy places use usize::MAX as a sentinel value
54        if actual_places[left_idx] == PlaceId::Local(usize::MAX)
55            || actual_places[right_idx] == PlaceId::Local(usize::MAX)
56        {
57            continue;
58        }
59
60        // Get actual places with field projections
61        let mut left_place = actual_places[left_idx].clone();
62        for &field_idx in alias_pair.lhs_fields() {
63            left_place = left_place.project_field(field_idx);
64        }
65
66        let mut right_place = actual_places[right_idx].clone();
67        for &field_idx in alias_pair.rhs_fields() {
68            right_place = right_place.project_field(field_idx);
69        }
70
71        // Get indices and union
72        if let (Some(left_place_idx), Some(right_place_idx)) = (
73            place_info.get_index(&left_place),
74            place_info.get_index(&right_place),
75        ) {
76            let left_may_drop = place_info.may_drop(left_place_idx);
77            let right_may_drop = place_info.may_drop(right_place_idx);
78            if left_may_drop && right_may_drop {
79                state.union(left_place_idx, right_place_idx);
80            }
81        }
82    }
83}
84
85/// Conservative fallback for library functions without MIR
86/// Assumes return value may alias with any may_drop argument
87fn apply_conservative_alias_for_call<'tcx>(
88    state: &mut AliasDomain,
89    destination: Place<'tcx>,
90    args: &[rustc_span::source_map::Spanned<rustc_middle::mir::Operand<'tcx>>],
91    place_info: &PlaceInfo<'tcx>,
92) {
93    // Get destination place
94    let dest_id = transfer::mir_place_to_place_id(destination);
95    let dest_idx = match place_info.get_index(&dest_id) {
96        Some(idx) => idx,
97        None => {
98            return;
99        }
100    };
101
102    // Only apply if destination may_drop
103    if !place_info.may_drop(dest_idx) {
104        return;
105    }
106
107    // Union with all may_drop arguments
108    for (_i, arg) in args.iter().enumerate() {
109        if let Some(arg_id) = transfer::operand_to_place_id(&arg.node) {
110            if let Some(arg_idx) = place_info.get_index(&arg_id) {
111                if place_info.may_drop(arg_idx) {
112                    // Create conservative alias
113                    state.union(dest_idx, arg_idx);
114
115                    // Sync fields for more precision
116                    transfer::sync_fields(state, &dest_id, &arg_id, place_info);
117                }
118            }
119        }
120    }
121}
122
123/// Place identifier supporting field-sensitive analysis
124#[derive(Debug, Clone, PartialEq, Eq, Hash)]
125pub enum PlaceId {
126    /// A local variable (e.g., _1)
127    Local(usize),
128    /// A field projection (e.g., _1.0)
129    Field {
130        base: Box<PlaceId>,
131        field_idx: usize,
132    },
133}
134
135impl PlaceId {
136    /// Get the root local of this place
137    pub fn root_local(&self) -> usize {
138        match self {
139            PlaceId::Local(idx) => *idx,
140            PlaceId::Field { base, .. } => base.root_local(),
141        }
142    }
143
144    /// Create a field projection
145    pub fn project_field(&self, field_idx: usize) -> PlaceId {
146        PlaceId::Field {
147            base: Box::new(self.clone()),
148            field_idx,
149        }
150    }
151
152    /// Check if this place has the given place as a prefix
153    /// e.g., _1.0.1 has prefix _1, _1.0.1 has prefix _1.0, but not _2
154    pub fn has_prefix(&self, prefix: &PlaceId) -> bool {
155        if self == prefix {
156            return true;
157        }
158
159        match self {
160            PlaceId::Local(_) => false,
161            PlaceId::Field { base, .. } => base.has_prefix(prefix),
162        }
163    }
164}
165
166/// Information about all places in a function
167#[derive(Clone)]
168pub struct PlaceInfo<'tcx> {
169    /// Mapping from PlaceId to index
170    place_to_index: FxHashMap<PlaceId, usize>,
171    /// Mapping from index to PlaceId
172    index_to_place: Vec<PlaceId>,
173    /// Mapping from PlaceId to MIR Place (when available)
174    place_to_mir: FxHashMap<PlaceId, Place<'tcx>>,
175    /// Whether each place may need drop
176    may_drop: Vec<bool>,
177    /// Whether each place needs drop
178    need_drop: Vec<bool>,
179    /// Total number of places
180    num_places: usize,
181}
182
183impl<'tcx> PlaceInfo<'tcx> {
184    /// Create a new PlaceInfo with initial capacity
185    pub fn new() -> Self {
186        PlaceInfo {
187            place_to_index: FxHashMap::default(),
188            index_to_place: Vec::new(),
189            place_to_mir: FxHashMap::default(),
190            may_drop: Vec::new(),
191            need_drop: Vec::new(),
192            num_places: 0,
193        }
194    }
195
196    /// Build PlaceInfo from MIR body
197    pub fn build(tcx: TyCtxt<'tcx>, def_id: DefId, body: &'tcx Body<'tcx>) -> Self {
198        let mut info = Self::new();
199        let ty_env = TypingEnv::post_analysis(tcx, def_id);
200
201        // Register all locals first
202        for (local, local_decl) in body.local_decls.iter_enumerated() {
203            let ty = local_decl.ty;
204            let need_drop = ty.needs_drop(tcx, ty_env);
205            let may_drop = !is_not_drop(tcx, ty);
206
207            let place_id = PlaceId::Local(local.as_usize());
208            info.register_place(place_id.clone(), may_drop, need_drop);
209
210            // Create fields for this type recursively
211            info.create_fields_for_type(tcx, ty, place_id, 0, 0, ty_env);
212        }
213
214        info
215    }
216
217    /// Recursively create field PlaceIds for a type
218    fn create_fields_for_type(
219        &mut self,
220        tcx: TyCtxt<'tcx>,
221        ty: Ty<'tcx>,
222        base_place: PlaceId,
223        field_depth: usize,
224        deref_depth: usize,
225        ty_env: TypingEnv<'tcx>,
226    ) {
227        // Limit recursion depth to avoid infinite loops
228        const MAX_FIELD_DEPTH: usize = 5;
229        const MAX_DEREF_DEPTH: usize = 3;
230        if field_depth >= MAX_FIELD_DEPTH || deref_depth >= MAX_DEREF_DEPTH {
231            return;
232        }
233
234        match ty.kind() {
235            // For references, recursively create fields for the inner type
236            // This allows handling patterns like (*_1).0 where _1 is &T
237            ty::Ref(_, inner_ty, _) => {
238                self.create_fields_for_type(
239                    tcx,
240                    *inner_ty,
241                    base_place,
242                    field_depth,
243                    deref_depth + 1,
244                    ty_env,
245                );
246            }
247            // For raw pointers, also create fields for the inner type
248            ty::RawPtr(inner_ty, _) => {
249                self.create_fields_for_type(
250                    tcx,
251                    *inner_ty,
252                    base_place,
253                    field_depth,
254                    deref_depth + 1,
255                    ty_env,
256                );
257            }
258            // For ADTs (structs/enums), create fields
259            ty::Adt(adt_def, substs) => {
260                for (field_idx, field) in adt_def.all_fields().enumerate() {
261                    let field_ty = field.ty(tcx, substs);
262                    let field_place = base_place.project_field(field_idx);
263
264                    // Check if field may/need drop
265                    // Use the ty_env from the function context to avoid param-env mismatch
266                    let need_drop = field_ty.needs_drop(tcx, ty_env);
267
268                    // Special handling: when deref_depth > 0, we are creating fields for
269                    // a type accessed through a reference/pointer (e.g., (*_1).0 where _1 is &T).
270                    // In this case, even if the field type itself doesn't need drop (e.g., i32),
271                    // we should still track it for alias analysis because it represents memory
272                    // accessed through a reference.
273                    let may_drop = if deref_depth > 0 {
274                        true
275                    } else {
276                        !is_not_drop(tcx, field_ty)
277                    };
278
279                    self.register_place(field_place.clone(), may_drop, need_drop);
280
281                    // Recursively create nested fields
282                    self.create_fields_for_type(
283                        tcx,
284                        field_ty,
285                        field_place,
286                        field_depth + 1,
287                        deref_depth,
288                        ty_env,
289                    );
290                }
291            }
292            // For tuples, create fields
293            ty::Tuple(fields) => {
294                for (field_idx, field_ty) in fields.iter().enumerate() {
295                    let field_place = base_place.project_field(field_idx);
296
297                    // For tuples, we conservatively check drop requirements
298                    // Note: Tuple fields don't have a specific DefId, so we use a simpler check
299
300                    // Special handling: when deref_depth > 0, we are creating fields for
301                    // a type accessed through a reference/pointer. Even if the field type
302                    // doesn't need drop, we should track it for alias analysis.
303                    let may_drop = if deref_depth > 0 {
304                        true
305                    } else {
306                        !is_not_drop(tcx, field_ty)
307                    };
308
309                    // For need_drop, use the ty_env from the function context
310                    let need_drop = field_ty.needs_drop(tcx, ty_env);
311
312                    self.register_place(field_place.clone(), may_drop, need_drop);
313
314                    // Recursively create nested fields
315                    self.create_fields_for_type(
316                        tcx,
317                        field_ty,
318                        field_place,
319                        field_depth + 1,
320                        deref_depth,
321                        ty_env,
322                    );
323                }
324            }
325            _ => {
326                // Other types don't have explicit fields we track
327            }
328        }
329    }
330
331    /// Register a new place and return its index
332    pub fn register_place(&mut self, place_id: PlaceId, may_drop: bool, need_drop: bool) -> usize {
333        if let Some(&idx) = self.place_to_index.get(&place_id) {
334            return idx;
335        }
336
337        let idx = self.num_places;
338        self.place_to_index.insert(place_id.clone(), idx);
339        self.index_to_place.push(place_id);
340        self.may_drop.push(may_drop);
341        self.need_drop.push(need_drop);
342        self.num_places += 1;
343        idx
344    }
345
346    /// Get the index of a place
347    pub fn get_index(&self, place_id: &PlaceId) -> Option<usize> {
348        self.place_to_index.get(place_id).copied()
349    }
350
351    /// Get the PlaceId for an index
352    pub fn get_place(&self, idx: usize) -> Option<&PlaceId> {
353        self.index_to_place.get(idx)
354    }
355
356    /// Check if a place may drop
357    pub fn may_drop(&self, idx: usize) -> bool {
358        self.may_drop.get(idx).copied().unwrap_or(false)
359    }
360
361    /// Check if a place needs drop
362    pub fn need_drop(&self, idx: usize) -> bool {
363        self.need_drop.get(idx).copied().unwrap_or(false)
364    }
365
366    /// Get total number of places
367    pub fn num_places(&self) -> usize {
368        self.num_places
369    }
370
371    /// Associate a MIR place with a PlaceId
372    pub fn associate_mir_place(&mut self, place_id: PlaceId, mir_place: Place<'tcx>) {
373        self.place_to_mir.insert(place_id, mir_place);
374    }
375}
376
377/// Alias domain using Union-Find data structure
378#[derive(Clone, PartialEq, Eq, Debug)]
379pub struct AliasDomain {
380    /// Parent array for Union-Find
381    parent: Vec<usize>,
382    /// Rank for path compression
383    rank: Vec<usize>,
384}
385
386impl AliasDomain {
387    /// Create a new domain with n places
388    pub fn new(num_places: usize) -> Self {
389        AliasDomain {
390            parent: (0..num_places).collect(),
391            rank: vec![0; num_places],
392        }
393    }
394
395    /// Find the representative of a place (with path compression)
396    pub fn find(&mut self, idx: usize) -> usize {
397        if self.parent[idx] != idx {
398            self.parent[idx] = self.find(self.parent[idx]);
399        }
400        self.parent[idx]
401    }
402
403    /// Union two places (returns true if they were not already aliased)
404    pub fn union(&mut self, idx1: usize, idx2: usize) -> bool {
405        let root1 = self.find(idx1);
406        let root2 = self.find(idx2);
407
408        if root1 == root2 {
409            return false;
410        }
411
412        // Union by rank
413        if self.rank[root1] < self.rank[root2] {
414            self.parent[root1] = root2;
415        } else if self.rank[root1] > self.rank[root2] {
416            self.parent[root2] = root1;
417        } else {
418            self.parent[root2] = root1;
419            self.rank[root1] += 1;
420        }
421
422        true
423    }
424
425    /// Check if two places are aliased
426    pub fn are_aliased(&mut self, idx1: usize, idx2: usize) -> bool {
427        self.find(idx1) == self.find(idx2)
428    }
429
430    /// Remove all aliases for a place (used in kill phase)
431    /// This correctly handles the case where idx is the root of a connected component
432    pub fn remove_aliases(&mut self, idx: usize) {
433        // Find the root of the connected component containing idx
434        let root = self.find(idx);
435
436        // Collect all nodes in the same connected component
437        let mut component_nodes = Vec::new();
438        for i in 0..self.parent.len() {
439            if self.find(i) == root {
440                component_nodes.push(i);
441            }
442        }
443
444        // Remove idx from the component
445        component_nodes.retain(|&i| i != idx);
446
447        // Isolate idx
448        self.parent[idx] = idx;
449        self.rank[idx] = 0;
450
451        // Rebuild the remaining component if it's not empty
452        if !component_nodes.is_empty() {
453            // Reset all nodes in the remaining component
454            for &i in &component_nodes {
455                self.parent[i] = i;
456                self.rank[i] = 0;
457            }
458
459            // Re-union them together (excluding idx)
460            let first = component_nodes[0];
461            for &i in &component_nodes[1..] {
462                self.union(first, i);
463            }
464        }
465    }
466
467    /// Remove all aliases for a place and all its field projections
468    /// This ensures that when lv is killed, all lv.* are also killed
469    pub fn remove_aliases_with_prefix(&mut self, place_id: &PlaceId, place_info: &PlaceInfo) {
470        // Collect all place indices that have place_id as a prefix
471        let mut indices_to_remove = Vec::new();
472
473        for idx in 0..self.parent.len() {
474            if let Some(pid) = place_info.get_place(idx) {
475                if pid.has_prefix(place_id) {
476                    indices_to_remove.push(idx);
477                }
478            }
479        }
480
481        // Remove aliases for all collected indices
482        for idx in indices_to_remove {
483            self.remove_aliases(idx);
484        }
485    }
486
487    /// Get all alias pairs (for debugging/summary extraction)
488    pub fn get_all_alias_pairs(&self) -> Vec<(usize, usize)> {
489        let mut pairs = Vec::new();
490        let mut domain_clone = self.clone();
491
492        for i in 0..self.parent.len() {
493            for j in (i + 1)..self.parent.len() {
494                if domain_clone.are_aliased(i, j) {
495                    pairs.push((i, j));
496                }
497            }
498        }
499
500        pairs
501    }
502}
503
504impl JoinSemiLattice for AliasDomain {
505    fn join(&mut self, other: &Self) -> bool {
506        // Safety check: both domains must have the same size
507        // This ensures they represent the same place space
508        assert_eq!(
509            self.parent.len(),
510            other.parent.len(),
511            "AliasDomain::join: size mismatch (self: {}, other: {})",
512            self.parent.len(),
513            other.parent.len()
514        );
515
516        let mut changed = false;
517
518        // Get all alias pairs from other and union them in self
519        let pairs = other.get_all_alias_pairs();
520        for (i, j) in pairs {
521            if self.union(i, j) {
522                changed = true;
523            }
524        }
525
526        changed
527    }
528}
529
530impl DebugWithContext<FnAliasAnalyzer<'_>> for AliasDomain {}
531
532/// Intraprocedural alias analyzer
533pub struct FnAliasAnalyzer<'tcx> {
534    pub tcx: TyCtxt<'tcx>,
535    _body: &'tcx Body<'tcx>,
536    _def_id: DefId,
537    place_info: PlaceInfo<'tcx>,
538    /// Function summaries for interprocedural analysis
539    fn_summaries: Rc<RefCell<FnAliasMap>>,
540    /// (Debug) Number of BBs we have iterated through
541    pub bb_iter_cnt: RefCell<usize>,
542}
543
544impl<'tcx> FnAliasAnalyzer<'tcx> {
545    /// Create a new analyzer for a function
546    pub fn new(
547        tcx: TyCtxt<'tcx>,
548        def_id: DefId,
549        body: &'tcx Body<'tcx>,
550        fn_summaries: Rc<RefCell<FnAliasMap>>,
551    ) -> Self {
552        // Build place info by analyzing the body
553        let place_info = PlaceInfo::build(tcx, def_id, body);
554        FnAliasAnalyzer {
555            tcx,
556            _body: body,
557            _def_id: def_id,
558            place_info,
559            fn_summaries,
560            bb_iter_cnt: RefCell::new(0),
561        }
562    }
563
564    /// Get the place info
565    pub fn place_info(&self) -> &PlaceInfo<'tcx> {
566        &self.place_info
567    }
568}
569
570// Implement Analysis for FnAliasAnalyzer
571impl<'tcx> Analysis<'tcx> for FnAliasAnalyzer<'tcx> {
572    type Domain = AliasDomain;
573
574    const NAME: &'static str = "FnAliasAnalyzer";
575
576    fn bottom_value(&self, _body: &Body<'tcx>) -> Self::Domain {
577        // Bottom is no aliases
578        AliasDomain::new(self.place_info.num_places())
579    }
580
581    fn initialize_start_block(&self, _body: &Body<'tcx>, _state: &mut Self::Domain) {
582        // Entry state: no initial aliases between parameters
583    }
584
585    fn apply_primary_statement_effect(
586        &self,
587        state: &mut Self::Domain,
588        statement: &Statement<'tcx>,
589        _location: Location,
590    ) {
591        match &statement.kind {
592            StatementKind::Assign(box (lv, rvalue)) => {
593                match rvalue {
594                    // Use(operand): lv = operand
595                    Rvalue::Use(operand) => {
596                        transfer::transfer_assign(state, *lv, operand, &self.place_info);
597                    }
598                    // Ref: lv = &rv or lv = &raw rv
599                    Rvalue::Ref(_, _, rv) | Rvalue::RawPtr(_, rv) => {
600                        transfer::transfer_ref(state, *lv, *rv, &self.place_info);
601                    }
602                    // CopyForDeref: similar to ref
603                    Rvalue::CopyForDeref(rv) => {
604                        transfer::transfer_ref(state, *lv, *rv, &self.place_info);
605                    }
606                    // Cast: lv = operand as T
607                    Rvalue::Cast(_, operand, _) => {
608                        transfer::transfer_assign(state, *lv, operand, &self.place_info);
609                    }
610                    // Aggregate: lv = (operands...)
611                    Rvalue::Aggregate(_, operands) => {
612                        let operand_slice: Vec<_> = operands.iter().map(|op| op.clone()).collect();
613                        transfer::transfer_aggregate(state, *lv, &operand_slice, &self.place_info);
614                    }
615                    // ShallowInitBox: lv = ShallowInitBox(operand, T)
616                    Rvalue::ShallowInitBox(operand, _) => {
617                        transfer::transfer_assign(state, *lv, operand, &self.place_info);
618                    }
619                    // Other rvalues don't create aliases
620                    _ => {}
621                }
622            }
623            // Other statement kinds don't affect alias analysis
624            _ => {}
625        }
626    }
627
628    fn apply_primary_terminator_effect<'mir>(
629        &self,
630        state: &mut Self::Domain,
631        terminator: &'mir Terminator<'tcx>,
632        _location: Location,
633    ) -> TerminatorEdges<'mir, 'tcx> {
634        // (Debug)
635        {
636            *self.bb_iter_cnt.borrow_mut() += 1;
637        }
638        match &terminator.kind {
639            // Call: apply both kill and gen effects
640            // Note: Ideally gen effect should be in apply_call_return_effect, but that method
641            // is not being called by rustc's dataflow framework in current version.
642            // Therefore, we handle both effects here, following MOP's approach.
643            TerminatorKind::Call {
644                target,
645                destination,
646                args,
647                func,
648                ..
649            } => {
650                // Step 1: Apply kill effect for the destination
651                let operand_slice: Vec<_> = args
652                    .iter()
653                    .map(|spanned_arg| spanned_arg.node.clone())
654                    .collect();
655                transfer::transfer_call(state, *destination, &operand_slice, &self.place_info);
656
657                // Step 2: Apply gen effect - function summary or fallback
658                if let Operand::Constant(c) = func {
659                    if let ty::FnDef(callee_def_id, _) = c.ty().kind() {
660                        // Try to get the function summary
661                        let fn_summaries = self.fn_summaries.borrow();
662                        if let Some(summary) = fn_summaries.get(callee_def_id) {
663                            // Apply the function summary
664                            apply_function_summary(
665                                state,
666                                *destination,
667                                &operand_slice,
668                                summary,
669                                &self.place_info,
670                            );
671                        } else {
672                            // No summary available (e.g., library function without MIR)
673                            // Drop the borrow before calling the fallback function
674                            drop(fn_summaries);
675
676                            // Apply conservative fallback: assume return value may alias with any may_drop argument
677                            apply_conservative_alias_for_call(
678                                state,
679                                *destination,
680                                args,
681                                &self.place_info,
682                            );
683                        }
684                    } else {
685                        // FnPtr? Closure?
686                        // rap_warn!(
687                        //     "[MFP-alias] Ignoring call to {:?} because it's not a FnDef",
688                        //     c
689                        // );
690                    }
691                }
692
693                // Step 3: Return control flow edges
694                if let Some(target_bb) = target {
695                    TerminatorEdges::Single(*target_bb)
696                } else {
697                    TerminatorEdges::None
698                }
699            }
700
701            // Drop: doesn't affect alias relationships
702            TerminatorKind::Drop { target, .. } => TerminatorEdges::Single(*target),
703
704            // SwitchInt: return all possible edges
705            TerminatorKind::SwitchInt { discr, targets } => {
706                TerminatorEdges::SwitchInt { discr, targets }
707            }
708
709            // Assert: return normal edge
710            TerminatorKind::Assert { target, .. } => TerminatorEdges::Single(*target),
711
712            // Goto: return target
713            TerminatorKind::Goto { target } => TerminatorEdges::Single(*target),
714
715            // Return: no successors
716            TerminatorKind::Return => TerminatorEdges::None,
717
718            // All other terminators: assume no successors for safety
719            _ => TerminatorEdges::None,
720        }
721    }
722
723    fn apply_call_return_effect(
724        &self,
725        _state: &mut Self::Domain,
726        _block: rustc_middle::mir::BasicBlock,
727        _return_places: CallReturnPlaces<'_, 'tcx>,
728    ) {
729        // Note: This method is part of the rustc Analysis trait but is not being called
730        // by the dataflow framework in current rustc version when using iterate_to_fixpoint.
731        // The call return effect (gen effect) is instead handled directly in
732        // apply_primary_terminator_effect to ensure it is actually executed.
733        // This is consistent with how MOP analysis handles function calls.
734    }
735}