Taming and Dissecting Recursions Through Interprocedural Weak Topological Ordering
Abstract
Abstract interpretation provides a foundational framework for approximating program semantics by interpreting code through abstract domains using semantic functions over ordered sets along a program’s control flow graph (CFG). To facilitate fixpoint computation in abstract interpretation, weak topological ordering (WTO) is an effective strategy for handling loops, as it identifies strategic control points in the CFG where widening and narrowing operations should be applied. However, existing abstract interpreters still face challenges when extending WTO computation in the presence of recursive programs. Computing a precise whole-program WTO requires full context-sensitive analysis which is not scalable for large programs, while context-insensitive analysis introduces spurious cycles that compromise precision. Current approaches either ignore recursion (resulting in unsoundness) or rely on conservative approximations, sacrificing precision by adopting the greatest elements of abstract domains and applying widening at function boundaries without subsequent narrowing refinements. These can lead to undesired results for downstream tasks, such as bug detection.
To address the above limitations, we present RecTopo, a new technique to boost the efficiency of precise abstract interpretation in the presence of recursive programs through interprocedural weak topological ordering (IWTO). Rather than pursuing an expensive whole-program WTO analysis, RecTopo employs an on-demand approach that strategically decomposes programs at recursion boundaries and constructs targeted IWTOs for each recursive component. RecTopo dissects and analyzes (nested) recursions through interleaved widening and narrowing operations. This approach enables precise control over interpretation ordering within recursive structures while eliminating spurious recursions through systematic correlation of control flow and call graphs.
We implemented RecTopo and evaluated its effectiveness using an assertion-based checking client focused on buffer overflow detection, comparing it against three popular open-source abstract interpreters (IKOS, Clam, CSA). The experiments on 8312 programs from the NIST dataset demonstrate that, on average, RecTopo is 31.99% more precise and achieves a 17.49% higher recall rate compared to three other tools. Moreover, RecTopo exhibits an average precision improvement of 46.51% and a higher recall rate of 32.98% compared to our baselines across ten large open-source projects. Further ablation studies reveal that IWTO reduces spurious widening operations compared to whole-program WTO, resulting in a 12.83% reduction in analysis time.
Keywords and phrases:
Abstract interpretation, recursion, weak topological orderingCopyright and License:
![[Uncaptioned image]](x1.png)
2012 ACM Subject Classification:
Theory of computation Program analysisFunding:
This research is supported by Australian Research Council Grants DP250101396 and FT220100391.Editors:
Jonathan Aldrich and Alexandra SilvaSeries and Publisher:

†Bor-Yuh Evan Chang holds concurrent appointments at the University of Colorado Boulder and as an Amazon Scholar. This paper describes work performed at the University of Colorado Boulder and is not associated with Amazon.
1 Introduction
Abstract interpretation [24, 22, 25] offers a framework for approximating program semantics by analyzing code through abstract domains and applying semantic functions over ordered sets along the program’s control flow graph (CFG). The analysis converges to a conservative solution at a post-fixpoint, which is an over-approximation of the least fixpoint of the program’s semantics. This least fixpoint is defined by a system of equations , where each denotes the abstract states at control point , and represents the corresponding semantic function of a program.
For programs with acyclic control flows, fixpoint computation typically converges after a few iterations over the CFG. However, the presence of cycles introduces cyclic dependencies between program states, complicating the analysis when working with abstract domains characterized by unbounded or very deep lattice heights [27, 62]. In such domains, such as the standard interval domain [17], the iterative computation may require a prohibitively large number of steps to reach a post-fixpoint. Even worse, termination is not guaranteed, as a post-fixpoint may not be reached within a finite number of steps. To facilitate fixpoint computation in these domains, widening techniques [22, 24, 13] compute a safe (upper) approximation of the least fixpoint (a post-fixpoint, hereafter referred to as a fixpoint for simplicity). However, widening operations must be judiciously applied only at cycle points, as spurious widening at acyclic locations introduces precision loss due to over-approximation, which cannot be recovered through subsequent narrowing [11]. Bourdoncle’s algorithm [13] provides an effective solution through weak topological ordering (WTO) for intraprocedural analysis, which efficiently identifies program loops and their nested structures while requiring only one widening point per loop. The WTO framework further enables sophisticated interleaved widening and narrowing strategies [10], where narrowing compensates precision that may have been lost during widening by incorporating invariants such as loop bounds and branching constraints. These advantages have made WTO a standard method for handling intraprocedural loops in many current abstract interpreters [15, 26, 19, 18].
While state-of-the-art abstract interpreters effectively leverage Bourdoncle’s algorithm for loop analysis, they face difficulties when determining WTO for recursive function calls. Several approaches have been proposed for taming recursions and interprocedural abstract interpretation. For non-recursive functions, function inlining enables precise interprocedural analysis. However, this approach becomes infeasible for recursive functions due to potentially infinite inlining sequences in unbounded recursion. To address this, some tools adopt strong assumptions, employing either under- or over-approximation approaches. For example, Astrée [37] completely excludes the analysis of unbounded recursion, while IKOS [15, 40] conservatively assigns top values () to variables in recursive contexts, effectively bypassing the analysis of recursive functions. To improve precision, tools such as Clam [34, 19] and CSA [18] analyze recursive function bodies but introduce many spurious widening operations at function boundaries. Their effectiveness remains limited, as their WTO computations are confined to individual function scopes, preventing the creation of an effective interprocedural order for efficient fixpoint computation and subsequent narrowing to enhance precision. Consequently, both under-approximation and over-approximation approaches can produce undesirable outcomes for downstream tasks, such as bug detection.
Addressing these limitations necessitates an approach that incorporates both the analysis of recursive function bodies and guarantees termination properties. This requires an interprocedural weak topological ordering (IWTO) that establishes an efficient computation order across function boundaries, enabling minimal widening points and allowing narrowing strategies for precise handling of recursions. The primary challenge lies in accurately identifying the IWTO within the control flow graph, where multiple call sites of the same function can generate spurious recursive paths. These paths arise when call sites connect to prior invocations of the same function, potentially introducing unnecessary widening points and thereby compromising the precision of the analysis.
Figure 1 illustrates this challenge in the context of the McCarthy 91 function [43]. The function recur () is a recursive function that takes an integer p as input and recursively invokes itself at to increase p’s value until p is greater than 100. The main function invokes recur twice at and respectively. In the control flow graph, each function invocation at location is decomposed into two distinct nodes: a call site and a corresponding return site (e.g., the call at in the source program manifests as and in the control flow graph). The control flow edges are established such that each call site connects to its callee’s entry point (e.g., , , and ), while return edges link the callee’s exit point to the corresponding return sites (e.g., , , and ). Note that, a spurious recursive path highlighted in red in the figure ( ) arises due to the consecutive call to the function recur at and , which may lead to a redundant widening operation at the spurious recursion and compromises analysis precision. While a fully context-sensitive analysis could theoretically eliminate such spurious paths through precise call-return matching, the exponential complexity of such an approach renders it impractical for real-world program analysis.
We present RecTopo, a new interprocedural fixpoint computation technique that computes interprocedural weak topological ordering (IWTO) in the presence of recursive programs, enabling recursions to be handled as efficiently and precisely as loops. Our approach systematically dissects programs into non-recursive and recursive components, avoiding the complexity of whole-program WTO construction. The framework first leverages Tarjan’s strongly connected component algorithm [61] on the call graph to identify recursive function calling chains. Subsequently, on the CFG, it coalesces call sites of functions within the same recursive chain and assigns each recursion a unified IWTO. This principled decomposition enables the seamless integration of an efficient worklist algorithm with interleaved widening and narrowing operations for precise recursion analysis. Our approach also eliminates spurious interprocedural cycle, thus preventing spurious widening operations that would otherwise arise from distinct call sites of the same function.
Figure 2 illustrates the three-phase architecture of RecTopo, demonstrating how our framework systematically addresses the challenges of interprocedural analysis in the presence of recursions:
-
(a)
Preprocessing: The framework begins by transforming the input program into LLVM’s intermediate representation (LLVM-IR) [41]. This standardized representation provides a unified foundation for constructing both the interprocedural control-flow graph (ICFG) and the corresponding call graph.
-
(b)
IWTO Construction: At the core of our approach lies a novel two-step IWTO construction process. First, we employ Tarjan’s algorithm [61] on the call graph to identify and partition functions based on their recursive relationships. This partitioning is crucial as it enables us to isolate and group functions within the same recursive calling chain. Then, guided by these function partitions, we construct their corresponding IWTOs on the ICFG through iterative applications of Tarjan’s algorithm. This strategic decomposition yields a precise IWTO for each recursion, providing an effective foundation for subsequent analysis phases while avoiding the complexity of whole-program WTO construction.
-
(c)
IWTO-Guided Abstract Interpretation: The framework leverages the constructed IWTOs to perform precise abstract interpretation. By following the computed order, our analysis ensures effective convergence through strategic application of widening and narrowing operations at carefully selected points. This principled approach yields highly precise abstract states that serve as a foundation for various client analyses, such as buffer overflow detection, while maintaining analysis efficiency.
In summary, this paper makes the following major contributions:
-
We introduce RecTopo, a new fixpoint computation approach for precise interprocedural abstract interpretation with enhanced efficiency. Our approach manages recursions precisely by leveraging interprocedural weak topological ordering (IWTO) and an interleaved strategy of widening and narrowing operations.
-
We introduce a scalable algorithm for constructing an IWTO for each recursion, designed to circumvent the high-cost construction of a whole-program weak topological ordering. This is accomplished by applying Tarjan’s strongly connected component algorithm [61] on the call graph, which in turn guides the construction of IWTOs on the control flow graph.
-
We comprehensively evaluate RecTopo’s performance using 8312 programs from the NIST dataset and 10 real-world open-source projects. Experimental results show that RecTopo outperforms our baselines by achieving 31.99% higher precision and a 17.49% increase in recall rate on average. Furthermore, RecTopo shows an average precision improvement of 46.51% and a higher recall rate of 32.98% compared to baselines tools on ten open-source projects.
2 Background
Statements | ||
Constants | ||
Stack virtual registers | ||
Global variables | ||
Program functions | ||
Top-level variables/pointers | ||
Abstract objects | ||
Program variables |
Const/Addr | |||
Gep | |||
Load/Store | |||
Copy | |||
Phi | |||
Binary | |||
Unary | |||
Call | |||
Return | |||
{+, -, , /, %, , , , , &, &&, ,, , , , } |
This section provides the foundational knowledge of the language on which our abstract interpretation is based (see §2.1), as well as discussions on abstract interpretation (see §2.2) and weak topological ordering (see §2.3).
2.1 Language
Table 1 presents the analysis domains and LLVM-like statement set used in abstract interpretation. The set of program variables is split into two disjoint subsets: , comprising address-taken variables, and , containing all top-level variables. Top-level variables adhere to static single assignment (SSA) form and are subject to exactly one definition. Address-taken objects can only be accessed through pointer dereferencing operations at Load/Store instructions. Constant values are initially bound to top-level variables through Cons instructions () that assign constants c. Addr statements, denoted as , allocate address-taken objects o, which may represent stack variables, global variables, or abstract heap objects. Gep enables structured access to composite objects: struct fields are accessed via constant offsets fld, while array elements use variable offsets idx. For field-sensitive analysis, RecTopo employs a field-index-based strategy [12, 52], distinguishing struct fields through unique indices. Copy denotes a simple assignment of top-level variables/pointers. The Phi instruction, fundamental to SSA form, consolidates variable values at control flow merge points. Binary and Unary instructions represent arithmetic and bitwise operations, respectively. Call and Return represents parameter passing and return value handling in function invocations.
2.2 Abstract Interpretation
Abstract interpretation offers a formalized framework for computing program semantics at each control point, represented as an abstract state specific to that point. The collection of all abstract states constitutes an abstract trace, denoted as (see Definition 2). The analysis iteratively updates by applying a semantic function – assumed to be monotonic to simplify the exposition – until reaching a fixpoint. This fixpoint yields a sound over-approximation of the program’s behaviors.
Definition 1 (Concrete and Abstract Domains).
Let be the concrete domain, where represents all possible runtime values. The abstract domain forms a lattice with partial order . A Galois connection formalizes their relationship through abstraction function and concretization function .
Definition 2 (Abstract Trace ).
An abstract trace maps control points to abstract states, where denotes the abstract state at control point . Function yields variable ’s abstract value at . For a program statement , and represent the pre- and post-abstract states, respectively.
Widening.
The widening technique [24, 22] is introduced to ensure fixpoint termination and accelerate convergence by approximating infinite loop behavior. It involves choosing a subset of control points as widening points. For each widening point , we use equation instead of for fixpoint computation. In this equation, is a widening operator, which is formally defined on a poset that satisfies the following properties: (1) and (2) for an increasing chain , the increasing chain where and eventually stabilizes. For example, the widening function on the integer interval domain is defined as: .
Narrowing.
After the widening iteration converges to a fixpoint, a subsequent narrowing iteration, as described in [15, 24, 11, 39], aims to enhance the post solution through a downward fixpoint iteration. The narrowing operator satisfies the following properties: (1) and (2) for a decreasing chain , the decreasing chain where and eventually stabilizes. For example, the narrowing operator on the integer interval domain is defined as: .
2.3 Weak Topological Ordering (WTO)
WTO provides an arbitrary update of abstract states rather than strictly following the order of control points. This arbitrary update (also called chaotic iteration [20]) is proven not to affect the precision of fixpoint computation while having the potential to accelerate the convergence to the fixpoint [20, 21]. We first give some basic concepts of WTO and then delve into how WTO can be leveraged to enhance the precision of widening and accelerate the chaotic iteration algorithm.
Definition 3 (Hierarchical Ordering).
A hierarchical ordering of a set of elements is a well-parenthesized permutation of that does not contain consecutive “”s.
A hierarchical ordering of induces a total order , a binary relation over the elements of , where for any , either or holds, establishing a linear sequence. A hierarchical component is formed by a matching pair of “” and “”, along with the sub-components or elements in between them in the specified order. The hierarchical order could also be viewed as a tree, with leaf nodes representing the elements of V, and sub-trees (with non-leaf roots) representing hierarchical components. The first element of a hierarchical component is denoted as component head, which is highlighted using double underlining as . The set of heads of the components containing the element is denoted by . The depth of an element is defined as , which represents the depth of the leaf node corresponding to in the tree.
Example 4.
“” is a valid hierarchical ordering of elements . Both “” and “” are hierarchical components with and being their respective component heads. The orders like “” or “” are invalid hierarchical orders. The former is not well-parenthesized, and the latter contains consecutive “”s. Components have a hierarchical (nested) structure, meaning that one component can be a sub-component of another. For instance, “” is a sub-component of “”, thus and .
Definition 5 (Weak Topological Ordering).
A weak topological ordering (WTO) of a directed graph is a hierarchical ordering of control points such that for each edge , or . If an edge satisfies , is called a back edge.
Example 6.
One possible WTO of the intraprocedural control flow graph in Figure 3 is “”. and are the WTO heads and and are back edges. It is clear that WTO precisely captures the nested loop structures within the graph. The inside loop is captured by “” while the outside loop is “”.
WTO-Guided Chaotic Iteration.
Given the explicit WTO of the control flow points, the chaotic iteration recursively computes a fixed point of the sub-components of each component in WTO every time the component reaches a fixed point [13]. WTO also provides an admissible set of widening points (the heads of WTO components) such that every loop in the program is cut by at least one widening point.
WTO Construction.
WTO is constructed based on Bourdoncle’s algorithm [32], which utilizes Tarjan’s algorithm [61] to identify all strongly connected components (SCCs) in a directed graph. The algorithm repeatedly applies Tarjan’s algorithm on the graph to break each found SCC into smaller SCCs until no further subdivision is possible. Each time an SCC is divided, smaller SCCs are ordered within the larger one in the WTO. The hierarchical structure of these components reflects the partial ordering of the WTO. Current abstract interpretation frameworks [15, 19, 18] compute WTO at the function level, applying the analysis intraprocedurally. However, the interprocedural fixpoint computation order remains implicit, as it is not guided by an interprocedural WTO structure.
3 Motivating Example
Figure 4 illustrates how RecTopo precisely determines the interprocedural fixpoint computation order and widening points by revisiting the example in Figure 1. We use the integer interval domain [17] to reason about its behaviors. The state-of-the-art analyzers, IKOS [15], Clam [19, 34] and CSA [18], all yield an imprecise top value for recur’s return variable res1 at and res2 at . In comparison, RecTopo derives a more precise result of for res1 and for res2, demonstrating its precise interprocedural fixpoint computation order and widening strategies.
-
(a)
Preprocessing. As shown in Figure 4(a), we first generate the call graph and interprocedural control flow graph (ICFG) of the analyzed program. In the call graph, we identify the function recur as a recursive function, and main as the caller. In the recursive function recur, we observe two distinct recursive calling paths: one from to and another from to . Note that, the cyclic path highlighted in red in the figure ( ) is a spurious recursive path arising due to the consecutive call to the function recur at and .
-
(b)
IWTO Construction. Initially, we apply Tarjan’s algorithm to the call graph, identifying two distinct function partitions: main and recur. Leveraging these partitions, we divide the original ICFG into two sub-ICFGs: one for main and another for recur. This division eliminates interprocedural edges between main and recur, as they are not part of the same recursive calling chain, according to the call graph’s structure. Consequently, this approach removes the spurious recursion previously depicted in Figure 4(a) and prevents the redundant widening of this spurious recursion, thereby maintaining the precision as inlining-based methods for non-recursive functions. Furthermore, the true recursion within recur is comprehensively analyzed, resulting in an interprocedural weak topological ordering (IWTO) of “” (where and are WTO heads). This analysis contrasts with approaches like IKOS [15], which typically omit the function body in recursive analyses, or Clam [19, 34] and CSA [18] which only perform widening on and without narrowing.
-
(c)
IWTO-Guided Abstract Interpretation. The abstract interpretation illustrated in Figure 4(c) follows the order outlined in the IWTO constructed for the program. The execution begins at the main function and progresses through the call sites and . An interleaved widening and narrowing approach is employed to precisely approximate the behaviors of the two IWTO cycles within recur. Specifically, at the call site , the formal parameter p is initialized with the interval . As execution enters the first cycle , a fixpoint is quickly reached with p remaining at , given that the path condition for the branch from to is infeasible. Subsequently, at , the return value is calculated as . This value is then carried over to the second IWTO cycle , where a fixpoint is again promptly achieved, resulting in a final return value of for res1, much more precise compared to for existing state-of-the-art tools [15, 19, 18]. Following this, the second invocation at uses the value of res1, , as the input for p, and re-engages with the IWTO of recur. In the first cycle, the initial widening operation expands p to , followed by narrowing that refines the value to , which is subsequently propagated to as . Consequently, upon returning to , the value of res2 is determined to be , demonstrating consistently greater precision than IKOS, Clam and CSA.
4 RecTopo Approach
In this section, we introduce our approach for constructing interprocedural weak topological ordering in §4.1, followed by the algorithm for IWTO-guided abstract interpretation in §4.2. Additionally, we provide the proof of termination for our algorithm in §4.3.
4.1 IWTO Construction
The construction of IWTO consists of two stages. In the first stage (see §4.1.1), we perform program partitioning to divide the program into several function partitions (Definition 7). The second stage (see §4.1.2) involves constructing the IWTO on the ICFG for each resulting function partition from the first stage.
4.1.1 Program Partitioning
This stage aims to decompose the input whole program into several function partitions (see Definition 7) and identify all function partition entries (see Definition 8). The process of function partitioning segments the whole program analysis into manageable parts while ensuring that the functions within each recursive call are analyzed collectively and precisely. The identification of function partition entries is critical to guarantee that each IWTO is accurately constructed starting from the entry point as dictated by the program’s call sites.
Definition 7 (Function Partition).
A function partition, denoted as , consists of a set of functions that are directly or indirectly reachable from each other on the call graph.
We apply Tarjan’s algorithm [61] to the pre-built call graph of the analyzed program, identifying all strongly connected components (SCCs). Each SCC on the call graph corresponds to a distinct function partition. Non-recursive functions, being single-node SCCs, each forms an individual partition. For recursive functions, those within the same calling chain are aggregated into a single partition. Note that if a recursive function calls a non-recursive function, they are placed in separate partitions because the non-recursive function does not call back, meaning that they do not meet the criteria of mutual reachability required for placement in the same partition (Definition 7).
Definition 8 (Function Partition Entry).
For a function partition , a function is considered a function partition entry if and only if there exists a function such that calls . In other words, is an entry point to the partition if it is invoked by at least one function outside of the partition.
Function Partition Entries Identification.
For each function within a function partition , we examine the predecessors of on the call graph. If any predecessor is found not to belong to , then is designated as a partition entry for that partition.
Example 9.
We illustrate the process of program partitioning with an example and its call graph in Figure 5. This example includes a mutual recursion between the functions and , invoked by at locations and , respectively. Additionally, the function is called by at location . Applying Tarjan’s algorithm to this call graph results in three SCCs: , , and . Each SCC corresponds to a distinct function partition. For the function partition , both functions are identified as partition entries, as they are separately called by at different locations.
4.1.2 IWTO Construction
For each function partition entry, we establish an interprocedural weak topological ordering (IWTO). As shown in Algorithm 1, the main function (Lines 1-5) processes all function partitions of the analyzed program, alongside , which maps each function partition to its function partition entries and aims to produce , which maps each function partition entry to its respective IWTO. The subroutine defined at Lines 6-13 aims to construct IWTO for a specific function partition and its associated entries . first extracts the subgraph corresponding to on the ICFG, and then completes the extracted subgraph by connecting non-recursive calls and returns (Definition 10). Finally, based on the completed subgraph, it constructs an IWTO for each function partition entry and stores the results in . The detailed steps of are outlined as follows:
Step 1: ICFG Partitioning.
Given a function partition , this step (Line 7) aims to extract the induced sub-ICFG corresponding to from the ICFG of the entire input program. The resulting sub-ICFG is composed exclusively of ICFG nodes that are contained within a specific function from .
Definition 10 (Recursive and Non-Recursive Call/Return Site).
A call site in the ICFG is defined as a recursive call site iff. and are in the same function partion. Else, is classified as a non-recursive call site. Each is associated with a corresponding return site , which facilitates the return from the callee function.
Example 11.
Figure 6 shows the sub-ICFG of function partition for the code example in Figure 5. The call sites and are recursive call sites because their callee functions are recursive, and these calls occur within recursive functions ( and ). By comparison, the call sites and are considered non-recursive as they are located in the function, which is not part of the recursion involving or . is also a non-recursive call site because its callee, , is not a recursive function.
Step 2: Sub-ICFG Completion.
This step (Lines 8-10) ensures that every node within the sub-ICFG extracted in Step 1 remains reachable from the entry of its corresponding function. During the ICFG partitioning in Step 1, all non-recursive return sites (Definition 10) become unreachable. This occurs because each non-recursive return site is initially linked to its corresponding call site via the callee function body; however, the callee function is excluded from the sub-ICFG because it does not belong to the current function partition. Therefore, we introduce an additional edge from each non-recursive call site directly to its respective return site.
Step 3: IWTO Construction.
In this step (Lines 11-13), we employ Bourdoncle’s algorithm [32] to build the IWTO for the completed sub-ICFG derived from Step 2. For each partition entry, denoted as , Bourdoncle’s algorithm begins at the function entry node associated with the function partition entry, referred to as in Algorithm 1. It then constructs an IWTO across the entire sub-ICFG, incorporating its interprocedural edges.
Example 12.
Let us revisit the example in Figure 5 and illustrate the three steps in Algorithm 1 used to construct the IWTO of the function partition . In Step 1, we obtain an induced sub-ICFG shown in Figure 6, where call and return sites are delineated with bold outlines. Non-recursive call/return sites are denoted by , whereas recursive call/return sites are indicated by . In Step 2, since the ICFG nodes of are excluded from the sub-ICFG in Step 1, the return site becomes unreachable from its call site. Consequently, we add an edge from the call site to its return site, ensuring reachability from the function entry within the sub-ICFG. Finally, in Step 3, we apply Bourdoncle’s algorithm to the partition entries and , starting from their respective entry ICFG nodes, and . Specifically, initiating from , we establish the IWTO as: “”. The IWTO starting from is outlined as: “”.
4.2 IWTO-Guided Abstract Interpretation
This section first presents the overall algorithm for fixpoint computation in §4.2.1, followed by an example set of feasible node-level interpretation rules that can be applied during the fixpoint computation in §4.2.2.
4.2.1 Abstract Interpretation Algorithm
Algorithm 2 presents the pseudo-code for IWTO-guided abstract interpretation, initialized by the function (Line 1). This algorithm takes two inputs: a map linking each partition entry to its corresponding IWTO (), as described in §4.1.2, and the program’s entry function, (e.g., the function). Starting from , which is a function partition entry (Definition 8), the function iterates through the elements in its IWTO (Lines 5-9), processing each as either a single node (Line 7) or a cycle (Line 9), where cycles in the IWTO may represent either intraprocedural loops or recursive calls. For handling cycles, we apply the interleaved widening and narrowing strategy [10] to compute a fixpoint that considers both efficiency, achieved through widening, and precision, refined by narrowing.
The subroutine , detailed from Line 10 to Line 13 in Algorithm 2, processes individual nodes. At Line 11, the procedure begins by updating the abstract state node-wise, based on the semantics of the ICFG node. If this node is identified as a non-recursive call site (as defined in Definition 10), is recursively invoked to process the corresponding callee function. Recursive call and return sites are treated similarly to normal intraprocedural nodes, as their analysis is integrated within the IWTO.
The algorithm for computing a fixpoint for a cycle is shown in the subroutine, delineated from Line 14 to Line 32 in Algorithm 2. It employs a while loop to continuously process the IWTO elements constituting the cycle. As mentioned in §2.3, each iteration of the loop begins by processing the component head of the IWTO using either widening or narrowing operations, outlined in Lines 18-27, followed by the process other IWTO components (Lines 28-32). The cycle commences with iterations where the widening operation is applied first (Lines 19-23) to the head node, aiming to accelerate the stabilization process. Once the widening has reached a fixpoint, where no changes in the abstract state are observed from one iteration to the next, the algorithm transitions to the narrowing phase (Lines 24-27). This phase refines the analysis’s precision, following the methodologies proposed in [10]. The loop concludes when the narrowing phase also achieves a fixpoint, determined at Line 26, where a consistent abstract state is maintained post-narrowing from one loop iteration to the next.
[CallFlow] |
[RetFlow] |
[Sequence] |
[Cons] |
[Copy] |
[Phi] |
[Unary] |
[Binary] |
[Addr] |
[Gep] |
[Load] |
[Store] |
4.2.2 Node-Level Interpretation Rules
Figure 7 demonstrates an example set of rules for handling each IWTO node, which can be applied at Line 11 in Algorithm 2. Note that these rules can be customized to perform other types of abstract interpretations, such as using different abstract domains or object sensitivity.
The interpretation of each node encompasses two fundamental steps: first, aggregating post-abstract states from predecessors to derive its pre-abstract state; second, transforming the pre-abstract state according to program semantics to obtain the post-abstract state. For interprocedural analysis, abstract states are aggregated via CallFlow and RetFlow rules, while intraprocedural states are gathered through the Sequence rule. Notably, CallFlow employs selective aggregation of abstract states from call sites: it blocks propagation from mismatching non-recursive call sites while allowing propagation from recursive call sites.
Example 13.
Figure 8 illustrates this selective aggregation using the example in Figure 6, demonstrating the application of the CallFlow rule in deriving pre-abstract states for and . Consider the scenario where abstract interpretation progresses to invoking to analyze the function partition from partition entry . When deriving the pre-abstract state of , as shown in Figure 8(a), both and are permitted to propagate. This is because is a recursive call site, whereas , while non-recursive, matches the current partition entry’s call site, denoted as pEntryCallsite in Algorithm 2 at Line 10. Conversely, as shown in Figure 8(b), when deriving the pre-abstract state of , is blocked because does not match pEntryCallsite, , whereas propagates due to the recursive nature of .
The RetFlow rule facilitates the propagation of abstract states from a callee function’s exit node to the corresponding return site. The Sequence rule refines abstract values based on control flow edge conditions. Figure 9 demonstrates this refinement process for deriving the pre-abstract state of . Since the incoming edge from requires condition i > 10 to hold, we refine the abstract value of in by computing its meet with .
The post-abstract state of node is derived from its pre-abstract state and the relevant statement. For pointer-free statements (Cons, Copy, Phi, Unary, Binary, Call and Return), we calculate the abstract value of the left-hand-side variable based on the right-hand-side values and relevant operators. For instance, given a Binary statement and , we derive . Particularly, since Call and Return statements involve parameter and return value passing – essentially top-level variable assignments – we handle each assignment using the Copy rule for translation. For pointer-related statements (Gep, Load and Store), we utilize abstract values across interval and address domains to derive the post-abstract state. To elaborate, for Gep, we join all potential address values formed by adding a base address with an offset to derive the conservative address value for the left-hand-side variable. As for Load, we join the abstract values of all possible addresses to form the conservative abstract value of the left-hand-side variable. Lastly, for Store, we update the abstract value of every possible address . Particularly, when there are multiple addresses , we join the abstract value with each existing abstract value to form ’s new abstract value conservatively. In contrast, when there is only one , it is safe to directly use as ’s new abstract value, because the store must occur at .
Example 14.
We revisit the example in Figure 5 to illustrate the IWTO-guided abstract interpretation. It starts with . The IWTO of main function is outlined as “”. Both and are non-recursive call sites, which require invoking to handle their callee functions. Take as an example. follows the IWTO of function partition {recur1, recur2}, which is outlined as “”, to perform abstract interpretation. The first element of the IWTO, “”, is a cycle, with being the head. To handle the cycle, the function stabilizeCycle is invoked, which iteratively updates the abstract states of the nodes within the cycle. The iteration involves two stages, the widening stage and the narrowing stage. We elaborate on the widening and narrowing stages using the updating process of , as is the only widening point of the cycle. During the widening stage, it is initialized as in the first iteration. In the second iteration, it is first updated to , and then widened to (refer back to §2.2). The iteration then proceeds to the narrowing stage, during which is updated to , refining the value obtained in the widening stage. This refinement benefits from the adjustments made in the Sequence rule while deriving the pre-abstract state of . With the conclusion of the narrowing stage, stabilizeCycle finalizes, and the abstract interpreter continues to address the remainder of the IWTO. After finalizing its handling of the entire IWTO, the abstract interpreter proceeds to addressing and concludes that the return value of this recursion, entering from , is exactly . This exact value also benefits from the refinement conducted by the Sequence rule while deriving the pre-abstract state of .
4.3 Termination Analysis
This section aims to prove that Algorithm 2 is guaranteed to terminate, even in the presence of nested recursions and loops in the input program. Algorithm 2 begins with chaoticIteration, which calls handlePartition to handle the dummy call site to root, the program entry function. It then enters a mutual recursion among three functions: handlePartition, handleNode and stabilizeCycle. Due to the simplicity of handleNode, we inline it at the invoking sites in Lines 7 and 30 for the convenience of the proof. In order to model the recursion described in Algorithm 2, we introduce the concept of recursion tree.
Definition 15 (Recursion Tree).
The recursion tree, denoted as a triple , models the invocation process of a running algorithm. The set is non-empty and consists of nodes, where each represents an instance of a function invocation with specific parameters during the recursive process. The set comprises edges, where an edge indicates that function calls during the recursion. The element denotes the root node of , representing the initial function that starts the recursive calls.
Example 16.
Figure 10 demonstrates the recursion tree of Algorithm 2 as it executes on the program presented in Figure 5. The root node indicates that the recursion of Algorithm 2 begins with , where denotes a dummy call to the program entry, . Additionally, the two edges of the root node indicates that it invokes each child subroutine once during execution.
Since all operations in the functions handlePartition and stabilizeCycle are trivial and guaranteed to terminate, the overall termination of the algorithm can be reduced to the termination of the recursive process. This is equivalent to stating that the recursion tree of Algorithm 2 contains a finite number of nodes for any input.
Lemma 17.
The recursion tree of Algorithm 2 on any input has finite height.
Proof.
Consider a path (sequence of nodes) starting from the root node in the recursion tree. We will show that the number of nodes along this path is bounded by analyzing the invocations of two specific functions: handlePartition (referred as nodes) and stabilizeCycle (reffered as nodes). These are the only types of nodes present on the path, as we have inlined all handleNode invokations for simplicity.
First, we examine the subsequence of handlePartition calls, denoted as:
We use to denote the unique function partition to which function belongs. It follows that for all , . Consequently, for all , is reachable from on the call graph. We aim to prove the following sub-conclusions. Sub-conclusion 1: For all , . Suppose this were not the case. Then, there would exists such that , implying that is reachable from . Furthermore, it follows that would also be reachable from , which contradicts the definition of as a non-recursive call site (according to Definition 10). Sub-conclusion 2: For all , . This follows directly from Sub-conclusion 1 and . If for some , it would contradict Sub-conclusion 1. Sub-conclusion 3: , where denotes the number of function partitions in the program. If this were false, then there would exist at least one pair such that , which would contradict Sub-conclusion 2.
Second, we examine a continuous subsequence of stabilizeCycle invocations,
For this sequence, the inequality is established, where denotes the depth of the . This holds because is a sub-IWTO component of . Furthermore, the number of continuous subsequences is limited to length of the longest subsequence on the path, which, as previously concluded, is . Consequently, the total number of nodes on the path is limited to , where maxCycleDepth is the maximum depth of all IWTO cycles.
Finally, since the path consists solely of and nodes, the length of the path is less than .
Lemma 18.
The recursion tree of Algorithm 2 for any input has finite degree.
Proof.
To establish this, we consider two cases. Case 1: For a node, its degree is limited by the number of elements in ’s corresponding IWTO. Case 2: For a node, its child nodes represent the invoked subroutines within the double-nested loop outlined in Algorithm 2 from Lines 17 to 32. The outer loop is guaranteed to terminate within rounds [13], where represents the height of the lattice when applying the widening operation. The inner loop is bounded by the number of elements in . Thus, the degree of an node is also finite.
In both cases, the degree of the nodes are limited, confirming that the recursion tree has finite degree.
Theorem 19 (Termination).
Algorithm 2 is guaranteed to terminate.
Proof.
5 Evaluation
In this section, we evaluate the performance of RecTopo, focusing on its precision improvement in abstract interpretation and its scalability when applied to large-scale programs. We conduct a comparative analysis between RecTopo and three state-of-the-art abstract interpreters, IKOS [15], Clam [19, 34] and CSA [18], in an assertion-checking client, buffer overflow detection.
Additionally, we perform an ablation study to assess the influence of the IWTO implementation on the overall effectiveness of RecTopo.
Our empirical evaluation addresses the following research questions:
-
RQ1
Analysis Effectiveness: How effective is RecTopo in detecting buffer overflow vulnerabilities compared to state-of-the-art tools when evaluated on standard NIST benchmarks?
-
RQ2
Real-world Applicability: To what extent can RecTopo scale to analyze real-world applications while maintaining its precision in bug detection?
-
RQ3
Impact of IWTO: What is the contribution of the IWTO technique to RecTopo’s overall performance, particularly regarding precision improvements and computational overhead?
5.1 Datasets and Implementation
Project | #LOI | #Method | #Call | #Obj | ||
---|---|---|---|---|---|---|
paste | 8,416 | 53 | 758 | 510 | 9,395 | 9,922 |
md5sum | 11,483 | 63 | 881 | 606 | 12,494 | 13,064 |
YAJL | 20,592 | 151 | 561 | 208 | 9,253 | 9,922 |
8cc | 30,609 | 480 | 2,349 | 3,267 | 15,984 | 25,294 |
libplist | 35,324 | 342 | 1,743 | 1,635 | 13,697 | 20,978 |
MP4v2 | 39,178 | 601 | 610 | 1,991 | 15,595 | 16,733 |
RIOT | 54,597 | 579 | 1,614 | 951 | 20,176 | 20,843 |
darknet | 210,117 | 1,004 | 9,861 | 2,614 | 110,424 | 140,213 |
tmux | 446,626 | 1,967 | 22,369 | 3,879 | 162,879 | 178,924 |
Teeworlds | 529,737 | 2,306 | 20,701 | 5,754 | 251,356 | 246,029 |
Total | 1,386,679 | 7,546 | 61,447 | 21,415 | 621,253 | 681,922 |
Datasets.
We evaluate RecTopo using (1) a benchmark composed of 8312 programs from NIST [46], which includes comprehensive test cases of buffer overflow vulnerabilities, with 800 cases containing recursions, and (2) 10 popular open-source C/C++ projects across various application domains, with their statistics detailed in Table 2. These projects include paste [16] (file merger), md5sum [16] (file verifier), YAJL [4] (JSON parsing library), 8cc [7] (C compiler), libplist [8] (Property List file handler), MP4v2 [2] (MP4 file library), RIOT [3] (IoT operating system), darknet[1] (neural network framework), tmux [5] (terminal multiplexer) and Teeworlds [9] (online multiplayer game).
Implementation.
The experiments are conducted on an Ubuntu 22.04 server with an eight-core 2.60GHz Intel Xeon CPU and 128 GB memory. The interprocedural control and value flow graphs are built upon LLVM-IR (with LLVM version 14.0.0) using the SVF library (version 2.9). The LLVM-IR represents program functions as global variables, further modeled as address-taken variables. The call graph is built by considering the points-to information to resolve indirect calls. Program loops/recursions are captured by IWTO, which is constructed using the algorithm described in Section 4.1 (Algorithm 1). The abstract value representation is implemented using Z3 expressions [45]. We utilize the interleaved widening and narrowing strategy as described in [10] to precisely handle recursions and intraprocedural loops. We detect buffer overflows at each gep instruction by comparing the offset size and the memory size, both of which are calculated based on interval and points-to information from abstract interpretation. For the baselines IKOS, Clam and CSA, we directly use their open-source implementations and their default settings (e.g., interval analysis) for detecting buffer overflows.
5.2 RQ1: NIST Benchmark
Table 3 presents the true positive rates and precision of RecTopo along with three baseline abstract interpreters in detecting buffer overflows [28, 29, 30] using the pre-labeled NIST benchmark, NIST (complete), and its subset containing test cases that includes recursive functions, NIST (recursion).
Dataset | Metric | IKOS | Clam | CSA | RecTopo | ||
---|---|---|---|---|---|---|---|
|
Recall (%) | 53.90 | 55.29 | 80.83 | 80.83 | ||
Precision (%) | 46.38 | 37.18 | 78.38 | 85.97 | |||
|
Recall (%) | 100.00 | 100.00 | 100.00 | 100.00 | ||
Precision (%) | 50.00 | 50.00 | 55.56 | 95.24 |
Comparison Results.
For the complete NIST dataset, RecTopo consistently surpasses the baseline tools. It identifies, on average, 26.24% more buffer overflows than IKOS and Clam, while also achieving an average precision improvement of 44.19% over these tools. Its precision is also 7.59% higher than CSA. This performance underscores the robustness of RecTopo in interprocedural abstract interpretation. Moreover, the advantages of RecTopo are particularly notable in scenarios involving recursive functions, where it achieves a precision rate of 95.24% in detecting buffer overflows. In contrast, the average precision of IKOS, Clam and CSA in these test cases is only approximately 51.85%, 43.39% lower than RecTopo.
Result Analysis.
The improved recall can be attributed to RecTopo’s adoption of CSA’s robust handling of core program features, such as loop handling, and precise external API modeling [18]. Here, we focus on analyzing the precision improvements in handling recursion through two representative code scenarios shown in Figure 11. A key contributing factor is RecTopo’s construction of IWTO for recursive functions, which effectively guides the narrowing process by leveraging program path conditions. As demonstrated in Figure 11(a), the program accesses the buf buffer at with an offset idx, which is obtained from the return value of the mc91(100) function call at . RecTopo precisely determines mc91’s return value in the range . Consequently, it concludes that the buffer access at is safe. In contrast, all our baseline analyses report an overflow alarm here, incorrectly indicating that the value of mc91(100) could be anywhere within .
Project | IKOS | Clam | CSA | RecTopo | ||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
Report | Time | Report | Time | Report | Time | Report | Time | |||||
#TP | #FP | (secs) | #TP | #FP | (secs) | #TP | #FP | (secs) | #TP | #FP | (secs) | |
paste | 3 | 21 | 512 | 2 | 15 | 589 | 3 | 0 | 9 | 3 | 0 | 12 |
md5sum | 2 | 35 | 986 | 1 | 28 | 1146 | 4 | 1 | 8 | 4 | 2 | 10 |
YAJL | 1 | 1625 | 2895 | 1 | 1483 | 3061 | 3 | 0 | 5 | 3 | 0 | 9 |
8cc | 2 | 1893 | 3244 | 1 | 683 | 3824 | 3 | 7 | 36 | 3 | 5 | 38 |
libplist | 1 | 1206 | 3312 | 1 | 835 | 4178 | 5 | 14 | 45 | 5 | 8 | 49 |
MP4v2 | 1 | 965 | 3684 | 2 | 681 | 4375 | 1 | 0 | 13 | 1 | 0 | 16 |
RIOT | 2 | 1325 | 5216 | 2 | 1206 | 6387 | 8 | 6 | 27 | 8 | 4 | 31 |
darknet | 14 | 1265 | 9531 | 9 | 1324 | 10936 | 21 | 10 | 3507 | 21 | 7 | 3725 |
tmux | 4 | 1632 | 11325 | 2 | 1359 | 12894 | 12 | 10 | 824 | 12 | 5 | 983 |
Teeworlds | 2 | 529 | 13569 | 4 | 1436 | 14734 | 15 | 8 | 2886 | 15 | 8 | 3032 |
Total | 32 | 10496 | 54274 | 25 | 9050 | 62124 | 75 | 56 | 7360 | 75 | 39 | 7905 |
Another reason is that the IWTO construction approach effectively eliminates spurious recursions (widening points) arising from multiple call sites of the same function, such as the double call to adder in Figure 11(b). RecTopo accurately differentiates between these call sites, preventing misclassification of program points, , as part of a spurious recursion. This precision prevents RecTopo from widening the value of sum at the adder entry point () to . Consequently, RecTopo derives the precise value of sum at as , thereby eliminating a false overflow alarm.
False Positives of RecTopo.
We further analyze RecTopo’s false positives and identify two primary causes, as demonstrated in the examples in Figure 12. First, RecTopo compromises precision for efficiency in scenarios involving complex path conditions. A case in point is its failure to accurately interpret the path condition at in Figure 12(a), resulting in an imprecise narrowing of the range of n after widening. This imprecision triggers the overflow alarm at . This issue primarily stems from the limitations of the abstract interpretation solver, which, although not a key contribution of RecTopo, affects its precision. Enhancing the solver’s capability for precise path condition interpretation could rectify this. Another source of precision loss is RecTopo’s inability to track relationships between variables. This limitation is illustrated in Figure 12(b), where the parameters m and n have a constraint that their sum at each call site remains constant. However, RecTopo’s interval domain representation only allows for the independent storage of their abstract states, thus failing to capture their interrelation. As a result, when the parameter m is widened to , RecTopo over-approximates idx’s range as . Adopting a relational domain to articulate the constraints between program variables could overcome this limitation.
5.3 RQ2: Real-World Projects
Table 4 presents the experimental results for true positives, false positives and running time of RecTopo compared to the three existing tools (IKOS, Clam and CSA) on ten real-world projects.
Comparison Results and Analysis.
RecTopo demonstrates superior performance across all metrics, successfully identifying 75 real bugs with a precision rate of 65.79%, representing 79.78% of all identified vulnerabilities. In contrast, IKOS and Clam achieve an average recall rate of only 30.32% among discovered bugs. The precision rates of the baseline tools average 19.28%, less than one-third of RecTopo’s precision. Notably, RecTopo achieves these significant improvements while maintaining computational efficiency comparable to CSA. The markedly lower false positive rate of RecTopo can be attributed to its sophisticated abstract interpretation framework, guided by the IWTO approach. This methodology enables precise derivation of abstract states within recursive contexts through carefully orchestrated interleaved widening and narrowing operations on recursions.
Case Study.
Figure 13 illustrates the patch [6] and call graph for a real-world buffer overflow identified by RecTopo in libplist [8], a portable C library to handle Property List files [53]. RecTopo and CSA initially find this bug, which is missed by Clam and IKOS. However, after the bug is patched, CSA erroneously continue to report it as a buffer overflow – resulting in false positives. By comparison, RecTopo accurately eliminates the false alarm after the bug is fixed.
The bug resides in the parse_array_node function, within a recursive calling chain as depicted in Figure 13. It is triggered at line , where the function UINT_TO_HOST accesses the address pointed by index1_ptr and reads an extra bplistref_size bytes. This bug arises because bplistref_size, the buffer access variable in the recursion, is not properly bounded, potentially allowing reads past the allocated buffer of index1_ptr, leading to a buffer overflow. When the issue is addressed by introducing a boundary check before , RecTopo stops reporting the problem, while CSA still signal a bug. This discrepancy is attributed to our utilization of widening and narrowing techniques within the recursive function partition, which precisely leverage path conditions to refine the abstract value of bplistref_size, thereby providing more accurate results and effectively eliminating the false positive.
Statistics.
We present detailed statistics of IWTO across the ten real-world projects. Figure 14(a) illustrates the proportion of nodes residing in IWTO cycles for each program. The data reveals a substantial presence of nodes within IWTO cycles, ranging from 8.4% to 55.9%, with an average of 33.5%. Figure 14(b) depicts the aggregate depth of nodes (as defined in §2.3) across all IWTO cycles in each program. The data exhibits a near-linear growth pattern relative to program size. Given that the time complexity for cycle stabilization is linear with respect to the sum of node depths [13], this suggests that the overall time complexity for stabilizing IWTO cycles scales approximately linearly with program size. Figure 14(c) presents the number of nodes within IWTO cycles per program, while Figure 14(d) shows the maximum node depth in each program’s IWTO. The data demonstrates that while the number of nodes in cycles increases linearly with program scale, the maximum depth remains relatively stable. This relationship explains the linear growth pattern observed in the aggregate node depth measurements.
Project | RecTopo-NR | RecTopo-GL | RecTopo | ||||||
---|---|---|---|---|---|---|---|---|---|
Report | Time | Report | Time | Report | Time | ||||
#TP | #FP | (secs) | #TP | #FP | (secs) | #TP | #FP | (secs) | |
paste | 3 | 13 | 8 | 3 | 32 | 15 | 3 | 0 | 12 |
md5sum | 4 | 9 | 6 | 4 | 36 | 13 | 4 | 2 | 10 |
YAJL | 3 | 62 | 3 | 3 | 97 | 12 | 3 | 0 | 9 |
8cc | 3 | 85 | 30 | 3 | 108 | 44 | 3 | 5 | 38 |
libplist | 5 | 119 | 39 | 5 | 230 | 52 | 5 | 8 | 49 |
MP4v2 | 1 | 131 | 11 | 1 | 193 | 23 | 1 | 0 | 16 |
RIOT | 8 | 126 | 22 | 8 | 285 | 35 | 8 | 4 | 31 |
darknet | 21 | 217 | 3168 | 21 | 914 | 4211 | 21 | 7 | 3725 |
tmux | 12 | 253 | 727 | 12 | 1322 | 1247 | 12 | 5 | 983 |
Teeworlds | 15 | 304 | 2491 | 15 | 1641 | 3416 | 15 | 8 | 3032 |
Total | 75 | 1319 | 6505 | 75 | 4858 | 9068 | 75 | 39 | 7905 |
5.4 RQ3: Ablation Analysis
This section presents a comprehensive ablation analysis of RecTopo, with a focus on evaluating its precision enhancements and scalability.
We compare RecTopo with two distinct variants: RecTopo-NR, which omits recursive calls by assigning a default top value; and RecTopo-GL, which integrates a whole-program global IWTO. This comparative analysis underscores the critical contributions of IWTO in enhancing both precision and scalability.
Table 5 presents the true positives, false positives and running time of RecTopo compared to its two variants (RecTopo-NR and RecTopo-GL) on real-world popular applications. The results demonstrate that the IWTO employed in RecTopo significantly enhances precision while incurring only a moderate increase in computational overhead.
Comparison with RecTopo-NR.
RecTopo exhibits 60.41% higher precision than RecTopo-NR. This superior precision stems from RecTopo’s ability to more precisely calculate the values of variables within recursive functions. In contrast, RecTopo-NR simplifies the process by assigning a generic top value to these variables.
This overly conservative approaches of RecTopo-NR results in broader intervals that frequently trigger false overflow alarms during buffer access. In terms of performance efficiency, RecTopo-NR leads in execution speed as it bypasses the body of recursive functions entirely.
However, the slight time advantage of RecTopo-NR does not compensate for the substantial precision gains achieved by RecTopo, reinforcing its effectiveness in balancing precision with performance.
Comparison with RecTopo-GL.
RecTopo-GL implements a global whole-program IWTO that establishes connections between all call sites and their corresponding callee functions. The abstract interpretation within RecTopo-GL is managed by this singular IWTO, with each cycle processed with precision comparable to that of RecTopo. Despite this, RecTopo-GL demonstrates a reduction in precision by 64.27%, and it also operates 1.15 longer compared to RecTopo.
The reduced precision in RecTopo-GL can be primarily attributed to its redundant widening operations. As depicted in Figure 15(a), RecTopo achieves a significant decrease in the number of widening points, with an average reduction of 58.99% and a peak reduction of up to 81.56%. This substantial decrease in unnecessary widening throughout various program procedures directly contributes to the enhanced precision of RecTopo. Regarding scalability, the comparison of the time required to construct IWTO in RecTopo and RecTopo-GL highlights significant differences. Figure 15(b) clearly shows that constructing a whole-program IWTO in RecTopo-GL demands considerably more time – nearly 36 times longer – than the IWTO approach used in RecTopo. This comparison underscores the efficiency and practicality of the partitioned approach in handling scalability.
6 Related Work
Our discussion centers on areas closely related to RecTopo, specifically focusing on abstract interpretation, fixpoint computation, and recursion handling in program analysis.
Abstract Interpretation.
Abstract interpretation [24, 27] serves as the theoretical foundation for numerous static analysis techniques, encompassing program verification [31, 15, 42, 66, 67], data-flow analysis [50, 51], and static bug detection [59, 60, 18, 55, 56, 63, 48]. These techniques are fundamentally designed to ensure both state over-approximation and analysis termination. Some approaches [48, 36] utilize sparse abstract interpretation framework that propagates abstract states through def-use chains instead of control flows. In contrast, our work operates within the context of control-point-based analysis. Note that this assumption is a design choice made to align with classical abstract interpretation formulations [13, 18, 20, 40, 47], though our approach is not fundamentally restricted to this scope. Weiss et al. [65] proposes database-backed analysis with graph algorithms to enhance both scalability and precision. More recently, Cheng et al. [18] developed cross-domain abstract interpretation to track correlations across different abstract domains. While significant, these approaches are orthogonal to our work.
Fixpoint Computation.
Fixpoint computation, originally conceptualized by Cousot et al. [24], forms the theoretical cornerstone of abstract interpretation by providing conservative approximations of program runtime behaviors. For acyclic control flows, fixpoint computation typically terminates after a few iterations over the CFG. However, in the presence of cyclic control flows, the fixpoint computation may require an impractically large number of iterations to converge, or in the worst case, may never converge, especially when using abstract domains characterized by infinite or very large lattice heights. To address this issue, widening operators [23] are introduced to accelerate the fixpoint computation and ensure its termination. Since its introduction, Bourdoncle’s algorithm [13] has remained the predominant approach for efficient fixpoint computation in abstract interpretation. Subsequent research has largely built upon this foundation, exploring various refinements such as interleaved widening and narrowing strategies to enhance precision [10, 11, 35, 33] and parallel computation techniques [40, 64, 44]. These advances, while valuable, are orthogonal to our focus on interprocedural fixpoint computation. Current interprocedural methods typically either inline callee functions [15], overlooking opportunities for interprocedural topological ordering, or disregard varying calling contexts [48, 47], potentially introducing superfluous widening operations.
Recursion Handling.
In interprocedural static analysis, recursive program structures present significant challenges by potentially generating unbounded call stacks that can affect analyzer termination. Existing approaches address this challenge through various strategies: some employ fixed-depth recursive call unrolling [59, 55, 56], while others explicitly bound the analysis of recursive calls [26, 37]. These approaches offer practical trade-offs between precision and analysis feasibility. More conservative methods focus on computing sound approximations of values within recursive structures [15, 18], prioritizing soundness while potentially sacrificing precision when analyzing recursions. Oh et al. [49] proposed techniques for eliminating spurious interprocedural cycles, though not specifically targeting recursion handling. Rival et al. [54] developed specialized methods for handling recursion in shape analysis, focusing primarily on precise approximation of recursive heap structures – an approach complementary to our work. Keidel et al. [38, 14] introduces a terminable big-step abstract interpretation approach even in the presence of loops and recursions. It achieves this by identifying recurrent call traces of the abstract interpreter itself during runtime. However, while this dynamic approach allows the application of widening on recursions, it currently does not support further narrowing to improve precision. Späth et al. [57] proposes a synchronized pushdown system that improves time complexity for recursive data structures. It operates within a pushdown framework, which differs from our abstract interpretation methodology. Stein [58] introduces a versatile framework for incremental and demand-driven tabulation-based abstract interpretation, utilizing dynamic summarization graphs to support sound analysis of recursion. While effective, it does not incorporate Bourdoncle’s strategy, which could enhance efficiency in handling cycles.
7 Conclusion
This paper introduces RecTopo, a novel approach to fixpoint computation for precise interprocedural abstract interpretation. RecTopo utilizes interprocedural weak topological ordering to steer chaotic iterations across program procedures effectively. This strategy not only incorporates an interleaved widening and narrowing approach for accurately addressing recursions but also eliminates redundant widening caused by interprocedural dependencies. RecTopo demonstrates an average precision improvement of 46.51% and an increased recall rate of 32.98% when compared to baselines tools on ten open-source projects.
References
- [1] Darknet: Open source neural networks in C. https://github.com/pjreddie/darknet, 2023.
- [2] MP4v2: A C/C++ library to create, modify and read MP4 files. https://github.com/enzo1982/mp4v2/, 2023.
- [3] RIOT: The friendly OS for IoT. https://github.com/RIOT-OS/RIOT, 2023.
- [4] YAJL: A fast streaming JSON parsing library in C. https://github.com/lloyd/yajl, 2023.
- [5] Tmux: Tmux source code. https://github.com/tmux/tmux, 2023.
- [6] A buffer overflow patch in libplist. https://github.com/libimobiledevice/libplist/commit/4765d9a60ca4248a8f89289271ac69cbffcc29bc, 2024.
- [7] 8cc: C compiler. https://github.com/rui314/8cc, 2024.
- [8] libplist: A small portable C library to handle Apple Property List files in binary, XML, JSON, or OpenStep format. https://github.com/libimobiledevice/libplist, 2024.
- [9] Teeworlds: A retro multiplayer shooter. https://teeworlds.com/, 2024.
- [10] Gianluca Amato and Francesca Scozzari. Localizing widening and narrowing. In International Static Analysis Symposium, pages 25–42. Springer, 2013. doi:10.1007/978-3-642-38856-9_4.
- [11] Gianluca Amato, Francesca Scozzari, Helmut Seidl, Kalmer Apinis, and Vesal Vojdani. Efficiently intertwining widening and narrowing. Science of Computer Programming, 120:1–24, 2016. doi:10.1016/j.scico.2015.12.005.
- [12] George Balatsouras and Yannis Smaragdakis. Structure-sensitive points-to analysis for C and C++. In SAS ’16, 2016. doi:10.1007/978-3-662-53413-7_5.
- [13] François Bourdoncle. Efficient chaotic iteration strategies with widenings. In Formal Methods in Programming and Their Applications: International Conference Academgorodok, Novosibirsk, Russia June 28–July 2, 1993 Proceedings, pages 128–141. Springer, 2005. doi:10.1007/BFb0039704.
- [14] Katharina Brandl, Sebastian Erdweg, Sven Keidel, and Nils Hansen. Modular abstract definitional interpreters for webassembly. In 37th European Conference on Object-Oriented Programming (ECOOP 2023), pages 5–1. Schloss Dagstuhl – Leibniz-Zentrum für Informatik, 2023.
- [15] Guillaume Brat, Jorge A Navas, Nija Shi, and Arnaud Venet. IKOS: A framework for static analysis based on abstract interpretation. In Software Engineering and Formal Methods: 12th International Conference, SEFM 2014, Grenoble, France, September 1-5, 2014. Proceedings 12, pages 271–277. Springer, 2014. doi:10.1007/978-3-319-10431-7_20.
- [16] Cristian Cadar, Daniel Dunbar, and Dawson Engler. KLEE: Unassisted and automatic generation of high-coverage tests for complex systems programs. In Proceedings of the 8th USENIX Conference on Operating Systems Design and Implementation, OSDI’08, pages 209–224. USENIX Association, 2008. URL: http://www.usenix.org/events/osdi08/tech/full_papers/cadar/cadar.pdf.
- [17] Liqian Chen, Antoine Miné, Ji Wang, and Patrick Cousot. Interval polyhedra: An abstract domain to infer interval linear relationships. In International Static Analysis Symposium, pages 309–325. Springer, 2009. doi:10.1007/978-3-642-03237-0_21.
- [18] Xiao Cheng, Jiawei Wang, and Yulei Sui. Precise sparse abstract execution via cross-domain interaction. In 46th International Conference on Software Engineering, ICSE ’24. ACM/IEEE, 2024. doi:10.1145/3597503.3639220.
- [19] Clam. Clam: LLVM front-end for Crab, 2024. URL: https://github.com/seahorn/clam.
- [20] Patrick Cousot. Asynchronous iterative methods for solving a fixpoint system of monotone equations. Technical report, Research Report IMAG-RR-88, Université Scientifique et Médicale de Grenoble, 1977.
- [21] Patrick Cousot. Méthodes itératives de construction et d’approximation de points fixes d’opérateurs monotones sur un treillis, analyse sémantique des programmes. PhD thesis, Institut National Polytechnique de Grenoble-INPG; Université Joseph-Fourier …, 1978. URL: https://tel.archives-ouvertes.fr/tel-00288657.
- [22] Patrick Cousot. Semantic foundations of program analysis. In Program flow analysis: theory and applications, pages 303–342. Prentice Hall, 1981.
- [23] Patrick Cousot. Abstracting induction by extrapolation and interpolation. In Deepak D’Souza, Akash Lal, and Kim Guldstrand Larsen, editors, Verification, Model Checking, and Abstract Interpretation, pages 19–42, Berlin, Heidelberg, 2015. Springer Berlin Heidelberg. doi:10.1007/978-3-662-46081-8_2.
- [24] Patrick Cousot and Radhia Cousot. Abstract interpretation: a unified lattice model for static analysis of programs by construction or approximation of fixpoints. In Proceedings of the 4th ACM SIGACT-SIGPLAN symposium on Principles of programming languages, pages 238–252, 1977. doi:10.1145/512950.512973.
- [25] Patrick Cousot and Radhia Cousot. Abstract interpretation frameworks. Journal of logic and computation, 2(4):511–547, 1992. doi:10.1093/logcom/2.4.511.
- [26] Patrick Cousot, Radhia Cousot, Jérôme Feret, Laurent Mauborgne, Antoine Miné, David Monniaux, and Xavier Rival. The Astrée analyzer. In Programming Languages and Systems: 14th European Symposium on Programming, ESOP 2005, Held as Part of the Joint European Conferences on Theory and Practice of Software, ETAPS 2005, Edinburgh, UK, April 4-8, 2005. Proceedings 14, pages 21–30. Springer, 2005. doi:10.1007/978-3-540-31987-0_3.
- [27] Patrick Cousot, Roberto Giacobazzi, and Francesco Ranzato. A²I: Abstract² Interpretation. Proc. ACM Program. Lang., 3(POPL), January 2019. doi:10.1145/3290355.
- [28] CWE-121: Stack-based buffer overflow. https://cwe.mitre.org/data/definitions/121.html, 2024.
- [29] CWE-122: Heap-based buffer overflow. https://cwe.mitre.org/data/definitions/122.html, 2024.
- [30] CWE-126: buffer over-read. https://cwe.mitre.org/data/definitions/126.html, 2024.
- [31] Manuvir Das, Sorin Lerner, and Mark Seigle. ESP: Path-sensitive program verification in polynomial time. In Proceedings of the 2002 ACM SIGPLAN Conference on Programming Language Design and Implementation (PLDI), Berlin, Germany, June 17-19, 2002, PLDI ’02, pages 57–68, New York, NY, USA, 2002. Association for Computing Machinery. doi:10.1145/512529.512538.
- [32] Matt Elder. Bourdoncle components, 2010.
- [33] Denis Gopan and Thomas Reps. Lookahead widening. In Thomas Ball and Robert B. Jones, editors, Computer Aided Verification, pages 452–466, Berlin, Heidelberg, 2006. Springer Berlin Heidelberg. doi:10.1007/11817963_41.
- [34] Arie Gurfinkel and Jorge A. Navas. Abstract interpretation of LLVM with a region-based memory model. In Software Verification: 13th International Conference, VSTTE 2021, New Haven, CT, USA, October 18–19, 2021, and 14th International Workshop, NSV 2021, Los Angeles, CA, USA, July 18–19, 2021, Revised Selected Papers, pages 122–144, Berlin, Heidelberg, 2021. Springer-Verlag. doi:10.1007/978-3-030-95561-8_8.
- [35] Nicolas Halbwachs and Julien Henry. When the decreasing sequence fails. In Antoine Miné and David Schmidt, editors, Static Analysis, pages 198–213, Berlin, Heidelberg, 2012. Springer Berlin Heidelberg. doi:10.1007/978-3-642-33125-1_15.
- [36] Ben Hardekopf and Calvin Lin. Flow-sensitive pointer analysis for millions of lines of code. In International Symposium on Code Generation and Optimization (CGO 2011), pages 289–298. IEEE, 2011. doi:10.1109/CGO.2011.5764696.
- [37] Daniel Kästner, Stephan Wilhelm, Stefana Nenova, Patrick Cousot, Radhia Cousot, Jérôme Feret, Laurent Mauborgne, Antoine Miné, Xavier Rival, et al. Astrée: Proving the absence of runtime errors. Proc. of Embedded Real Time Software and Systems (ERTS2 2010), 9, 2010.
- [38] Sven Keidel, Sebastian Erdweg, and Tobias Hombücher. Combinator-based fixpoint algorithms for big-step abstract interpreters. Proceedings of the ACM on Programming Languages, 7(ICFP):955–981, 2023. doi:10.1145/3607863.
- [39] Sol Kim, Kihong Heo, Hakjoo Oh, and Kwangkeun Yi. Widening with thresholds via binary search. Software: Practice and Experience, 46(10):1317–1328, 2016. doi:10.1002/spe.2381.
- [40] Sung Kook Kim, Arnaud J. Venet, and Aditya V. Thakur. Deterministic parallel fixpoint computation. Proc. ACM Program. Lang., 4(POPL), December 2019. doi:10.1145/3371082.
- [41] Chris Lattner and Vikram Adve. LLVM: A compilation framework for lifelong program analysis & transformation. In International symposium on code generation and optimization, 2004. CGO 2004., pages 75–86. IEEE, 2004. doi:10.1109/CGO.2004.1281665.
- [42] Jacob Laurel, Rem Yang, Gagandeep Singh, and Sasa Misailovic. A dual number abstraction for static analysis of Clarke Jacobians. Proc. ACM Program. Lang., 6(POPL), January 2022. doi:10.1145/3498718.
- [43] Zohar Manna. Mathematical theory of computation. Dover Publications, Inc., 2003.
- [44] David Monniaux. The parallel implementation of the Astrée static analyzer. In Kwangkeun Yi, editor, Programming Languages and Systems, pages 86–96, Berlin, Heidelberg, 2005. Springer Berlin Heidelberg. doi:10.1007/11575467_7.
- [45] Leonardo de Moura and Nikolaj Bjørner. Z3: An efficient SMT solver. In International Conference on Tools and Algorithms for the Construction and Analysis of Systems, pages 337–340. Springer, 2008. doi:10.1007/978-3-540-78800-3_24.
- [46] NIST datasets. https://samate.nist.gov/SARD/test-suites/116, 2023.
- [47] Hakjoo Oh, Kihong Heo, Wonchan Lee, Woosuk Lee, and Kwangkeun Yi. Design and implementation of sparse global analyses for C-like languages. In Proceedings of the 33rd ACM SIGPLAN Conference on Programming Language Design and Implementation, PLDI ’12, pages 229–238, New York, NY, USA, 2012. Association for Computing Machinery. doi:10.1145/2254064.2254092.
- [48] Hakjoo Oh, Kihong Heo, Wonchan Lee, Woosuk Lee, and Kwangkeun Yi. The Sparrow static analyzer, 2012. URL: https://opam.ocaml.org/packages/sparrow/.
- [49] Hakjoo Oh and Kwangkeun Yi. An algorithmic mitigation of large spurious interprocedural cycles in static analysis. Software: Practice and Experience, 40(8):585–603, 2010. doi:10.1002/spe.969.
- [50] Komal Pathade and Uday P. Khedker. Computing partially path-sensitive MFP solutions in data flow analyses. In Proceedings of the 27th International Conference on Compiler Construction, CC 2018, pages 37–47, New York, NY, USA, 2018. Association for Computing Machinery. doi:10.1145/3178372.3179497.
- [51] Komal Pathade and Uday P. Khedker. Path sensitive MFP solutions in presence of intersecting infeasible control flow path segments. In Proceedings of the 28th International Conference on Compiler Construction, CC 2019, pages 159–169, New York, NY, USA, 2019. Association for Computing Machinery. doi:10.1145/3302516.3307349.
- [52] D.J. Pearce, P.H.J. Kelly, and C. Hankin. Efficient field-sensitive pointer analysis of C. ACM TOPLAS, 30(1):4–es, 2007. doi:10.1145/1290520.1290524.
- [53] Property list. https://en.wikipedia.org/wiki/Property_list, 2024.
- [54] Xavier Rival and Bor-Yuh Evan Chang. Calling context abstraction with shapes. SIGPLAN Not., 46(1):173–186, January 2011. doi:10.1145/1925844.1926406.
- [55] Qingkai Shi, Xiao Xiao, Rongxin Wu, Jinguo Zhou, Gang Fan, and Charles Zhang. Pinpoint: Fast and precise sparse value flow analysis for million lines of code. In Proceedings of the 39th ACM SIGPLAN Conference on Programming Language Design and Implementation, pages 693–706, 2018. doi:10.1145/3192366.3192418.
- [56] Qingkai Shi, Peisen Yao, Rongxin Wu, and Charles Zhang. Path-sensitive sparse analysis without path conditions. In Proceedings of the 42nd ACM SIGPLAN International Conference on Programming Language Design and Implementation, PLDI 2021, pages 930–943, New York, NY, USA, 2021. Association for Computing Machinery. doi:10.1145/3453483.3454086.
- [57] Johannes Späth, Karim Ali, and Eric Bodden. Context-, flow-, and field-sensitive data-flow analysis using synchronized pushdown systems. Proceedings of the ACM on Programming Languages, 3(POPL):1–29, 2019. doi:10.1145/3290361.
- [58] Benno Stein, Bor-Yuh Evan Chang, and Manu Sridharan. Interactive abstract interpretation with demanded summarization. ACM Transactions on Programming Languages and Systems, 46(1):1–40, 2024. doi:10.1145/3648441.
- [59] Yulei Sui, Ding Ye, and Jingling Xue. Static memory leak detection using full-sparse value-flow analysis. In Proceedings of the 2012 International Symposium on Software Testing and Analysis, ISSTA ’12, pages 254–264. ACM, 2012. doi:10.1145/2338965.2336784.
- [60] Yulei Sui, Ding Ye, and Jingling Xue. Detecting memory leaks statically with full-sparse value-flow analysis. IEEE Trans. Software Eng (TSE ’14)., 40(2):107–122, 2014. doi:10.1109/TSE.2014.2302311.
- [61] Robert Tarjan. Depth-first search and linear graph algorithms. SIAM journal on computing, 1(2):146–160, 1972. doi:10.1137/0201010.
- [62] Alfred Tarski. A lattice-theoretical fixpoint theorem and its applications. Pacific Journal of Mathematics, 5(2):285–309, 1955.
- [63] Oskar Haarklou Veileborg, Georgian-Vlad Saioc, and Anders Møller. Detecting blocking errors in go programs using localized abstract interpretation. In Proceedings of the 37th IEEE/ACM International Conference on Automated Software Engineering, ASE ’22, New York, NY, USA, 2023. Association for Computing Machinery. doi:10.1145/3551349.3561154.
- [64] Arnaud Venet and Guillaume Brat. Precise and efficient static array bound checking for large embedded C programs. In Proceedings of the ACM SIGPLAN 2004 Conference on Programming Language Design and Implementation, PLDI ’04, pages 231–242, New York, NY, USA, 2004. Association for Computing Machinery. doi:10.1145/996841.996869.
- [65] Cathrin Weiss, Cindy Rubio-González, and Ben Liblit. Database-backed program analysis for scalable error propagation. In 2015 IEEE/ACM 37th IEEE International Conference on Software Engineering, volume 1, pages 586–597, 2015. doi:10.1109/ICSE.2015.75.
- [66] Peisen Yao, Qingkai Shi, Heqing Huang, and Charles Zhang. Program analysis via efficient symbolic abstraction. Proc. ACM Program. Lang., 5(OOPSLA), October 2021. doi:10.1145/3485495.
- [67] Zhiqiang Zuo, John Thorpe, Yifei Wang, Qiuhong Pan, Shenming Lu, Kai Wang, Guoqing Harry Xu, Linzhang Wang, and Xuandong Li. Grapple: A graph system for static finite-state property checking of large-scale systems code. In Proceedings of the Fourteenth EuroSys Conference 2019, Dresden, Germany, March 25-28, 2019, EuroSys ’19. ACM, 2019. doi:10.1145/3302424.3303972.