The Journal of Systems and Software 84 (2011) 1171–1190
Contents lists available at ScienceDirect
The Journal of Systems and Software journal homepage: www.elsevier.com/locate/jss
An algorithm for capturing variables dependences in test suites Wes Masri ∗ , Hiba Halabi Electrical and Computer Engineering Department, American University of Beirut, Beirut, Lebanon
a r t i c l e
i n f o
Article history: Received 21 June 2010 Received in revised form 24 January 2011 Accepted 6 February 2011 Available online 15 February 2011 Keywords: Dynamic information flow analysis Test suite profiling Memoization
a b s t r a c t The use of dynamic dependence analysis spans several areas of software research including software testing, debugging, fault localization, and security. Many of the techniques devised in these areas require the execution of large test suites in order to generate profiles that capture the dependences that occurred between given types of program elements. When the aim is to capture direct and indirect dependences between finely granular elements, such as statements and variables, this process becomes highly costly due to: (1) the large number of elements, and (2) the transitive nature of the indirect dependence relationship. The focus of this paper is on computing dynamic dependences between variables, i.e., dynamic information flow analysis or DIFA. First, because the problem of tracking dependences between statements, i.e., dynamic slicing, has already been addressed by numerous researchers. Second, because DIFA is a more difficult problem given that the number of variables in a program is unbounded. We present an algorithm that, in the context of test suite execution, leverages the already computed dependences to efficiently compute subsequent dependences within the same or later test runs. To evaluate our proposed algorithm, we conducted an empirical comparative study that contrasted it, with respect to efficiency, to three other algorithms: (1) a naïve basic algorithm, (2) a memoization based algorithm that does not leverage computed dependences from previous test runs, and (3) an algorithm that uses reduced ordered binary decision diagrams (roBDDs) to maintain and manage dependences. The results indicated that our new DIFA algorithm performed considerably better in terms of both runtime and memory consumption. © 2011 Elsevier Inc. All rights reserved.
1. Introduction Program dependences are leveraged by several areas of software engineering including software testing, debugging, fault localization, and security (Harman and Danicic, 1995; Masri et al., 2007; Masri and Podgurski, 2009a, 2008; Masri, 2010; Masri and ElGhali, 2009; El-Ghali and Masri, 2009). The two recognized types of dependence relationships are data dependence and control dependence (Ferrante et al., 1987; Podgurski and Clarke, 1990; Podgurski, 1989). The former occurs when one statement defines a memory location and another statement uses it, and the latter occurs when a conditional statement controls the execution of another statement. In this paper we are concerned with the direct and indirect data and/or control dependences that occur at runtime, which we refer to hereafter as dynamic dependences (Sinha et al., 2001; Masri and Podgurski, 2009b). Dynamic dependences are important because they determine with a high level of precision, which parts of a program are executed, how information flows between statements and variables, and how infected or suspicious states propagate.
∗ Corresponding author. E-mail addresses:
[email protected] (W. Masri),
[email protected] (H. Halabi). 0164-1212/$ – see front matter © 2011 Elsevier Inc. All rights reserved. doi:10.1016/j.jss.2011.02.007
Dynamic information flow analysis (DIFA) (Korel and Yalamanchili, 1994; Masri and Podgurski, 2009a,b) and dynamic slicing (Agrawal and Horgan, 1990; Masri, 2008; Masri and Podgurski, 2009b; Zhang et al., 2003) are closely related techniques that analyze the execution trace of a program to identify the dynamic dependences that occurred. Whereas dynamic slicing is concerned with identifying the set of all statements that influence another statement at a given execution point, DIFA is concerned with identifying the set of all variables that influence another variable of interest. Many techniques designed to improve the reliability and security of software are based on analyzing DIFA or dynamic slicing profiles, thus, requiring the execution of large test suites to generate such profiles (Masri et al., 2007; Masri and Podgurski, 2009a, 2008; Masri, 2010; Masri and El-Ghali, 2009; El-Ghali and Masri, 2009; Elbaum et al., 2002; Harrold et al., 2000; Rothermel et al., 2001). The focus of this paper is on devising a DIFA algorithm to be used in the context of test suite execution and profiling. Our interest stems from the fact that dynamic slicing has already been addressed by numerous researchers, and that DIFA is a more difficult problem given that the number of variables in a program is unbounded. In previous work (Masri, 2008), we devised a dynamic slicing algorithm that leverages the already computed statements dependences to compute subsequent dependences within the same run.
1172
W. Masri, H. Halabi / The Journal of Systems and Software 84 (2011) 1171–1190
In this paper we build on that work and present a DIFA algorithm that, in the context of test suite execution, leverages the already computed variables dependences to efficiently compute subsequent dependences within the same or later test runs. To evaluate our proposed algorithm, we conduct an empirical study that compares its performance to three baseline algorithms: (1) a naïve basic algorithm (Masri and Podgurski, 2009b; Beszedes et al., 2001), (2) a memoization based algorithm that does not leverage computed dependences from previous test runs (similar to the one described in (Masri, 2008)), and (3) an algorithm that uses reduced ordered binary decision diagrams (roBDDs) to maintain and manage dependences (Zhang et al., 2004). Note that all involved algorithms are forward computing, i.e., they operate in tandem with the program’s execution, and thus when a request for a set of variables is made, it is already available (Beszedes et al., 2001; Korel and Yalamanchili, 1994; Masri and Podgurski, 2009b). Forward computing algorithms are advantageous because they can be used for online analysis of a program. The main contributions of this work are as follows: 1) A new DIFA algorithm designed for test suite execution, which, compared to existing algorithms, is theoretically and empirically faster, and empirically more space efficient. 2) Tool support for the algorithm under the Java platform. The remainder of this paper is organized as follows. Section 2 provides background on dynamic dependence analysis and DIFA. Section 3 describes the baseline algorithms. Section 4 describes our proposed DIFA algorithm. Section 5 presents an empirical study that contrasts our algorithm (and variations of it) to the baseline algorithms. Section 6 discusses threats to the validity of our approach. Section 7 presents related work. Finally, in Section 8 we conclude and present future work. Note also that much of the algorithms’ details are provided in Appendices A, B, and C, and Appendix D provides a supportive discussion of our main algorithm. 2. Background This section presents the background that underlies the DIFA forward computing algorithm we devised in previous work (Masri and Podgurski, 2009b), which is also relevant to this work. We first define control dependence and data dependence then present the adopted equation for DIFA. 2.1. Direct dynamic control dependence Informally, in the context of static analysis, a statement t is directly control dependent on a conditional statement s, denoted s → DirC t, if s decides, via the branches it controls, whether t is executed or not (Ferrante et al., 1987; Podgurski and Clarke, 1990; Podgurski, 1989). The dynamic counterpart of the →DirC relation is the direct dynamic control dependence relation denoted →DirDynC , which holds between program actions (statement executions) (Korel and Yalamanchili, 1994) in an execution trace. The kth action in a trace T is denoted by T(k) or by sk , where s is the corresponding statement. A subtrace of T, denoted T(k, m) is the subsequence of actions starting at T(k) and ending at T(m). We now provide a definition of →DirDynC that is based on the →DirC relation: Definition. Let sk and tm be two actions in an execution trace T, where k < m and sk is a predicate action, tm is directly dynamically control dependent on sk , denoted sk → DirDynC tm , iff sk is the most recent predicate action to occur prior to action tm such that s → DirC t.
2.2. Direct dynamic data dependence Modeling dynamic data dependences between actions requires defining the following sets: the set of variables D(sk ) defined at sk , the set of variables U(sk ) used at sk , and the set of variables D(T) defined during the execution of trace T. Definition. Let sk and tm be two actions in an execution trace T, where k < m, tm is directly dynamically data dependent on sk , denoted sk → DirDynD tm , iff. (D(sk ) ∩ U(t m )) − D(T (k + 1, m − 1)) = / ∅ Informally, sk → DirDynD tm iff tm uses a variable that was last defined by sk . The →DirDynD relation models both intra-procedural and inter-procedural data dependences. The latter occur when an execution trace spans different functions and data defined in one function are used in another. 2.3. Dynamic dependence Direct dynamic dependence encompasses the combination of →DirDynC and →DirDynD . Definition. Given two actions sk and tm with k < m, tm is directly dynamically dependent on sk , denoted sk → DirDynDep tm , iff sk → DirDynC tm or sk → DirDynD tm . The set of actions that tm is directly dynamically dependent on (or directly influenced by) is denoted DInfluence(tm ). As stated earlier, in this paper we are concerned with direct and indirect dynamic data and/or control dependence, i.e., dynamic dependence, which is the transitive closure of the →DirDynDep relation. Definition. Let T be an execution trace and let sk and tm be actions in T with k < m, tm is dynamically dependent on sk , denoted sk → DynDep tm , iff there is a sequence a1 , a2 , . . ., an of actions in T(k, m) such that a1 = sk , an = tm , and for i = 1,. . ., n − 1, ai → DirDynDep ai + 1 . The set of actions that tm is dynamically dependent on (or influenced by) is denoted Influence(tm ). It follows that Influence(t m ) = DInfluence(t m ) ∪
Influence(sk )
sk ∈ DInfluence(t m )
2.4. Dynamic information flow analysis Dynamic information flow analysis is closely related to dynamic slicing (a well known concept) in that they both analyze the execution trace to identify dynamic control and data dependence relationships. Whereas dynamic slicing is concerned with identifying the set of all statements that influence sk or that influence a variable at sk , DIFA is concerned with identifying the set of all variables that influence a variable at an action sk . Definition. Let T be an execution trace, let sk and tm be actions in T with k < m, let x and y be variables such that x is used at sk and y is defined at tm , respectively. Then information flows from x at sk to y at tm iff sk → DynDep tm . The set of variables from which information flows to y at tm is: InfoFlow(t m ) = U(t m ) ∪ U(Influence(t m )) where U(tm ) is the set of variables used at tm and U(Influence(tm )) is the set of variables used at the actions that influence tm .
W. Masri, H. Halabi / The Journal of Systems and Software 84 (2011) 1171–1190
1173
required to compute it at prior actions) is O(V2 ); the worst case, involving the union of V sets each with V variables, occurs when information flows from every variable into every other variable and tk is directly influenced by all active variables.2 Concerning memory requirements, at any given time during execution, InfoFlow sets are stored for each active variable. Therefore, the worst-case storage requirement of InfoFlow is O(V2 ); the worst case occurs when information flows from every variable into every other variable in the program. 3.2. Memoized Fig. 1. Basic InfoFlow algorithm applied on every action of the execution trace.
InfoFlow(tm ) could also be defined recursively as follows:
InfoFlow(t m ) = U(t m ) ∪
InfoFlow(sk )
(I)
s ∈ DInfluence(t m )
Note that the InfoFlow equation above is implemented in our DynFlow tool (Masri and Podgurski, 2009b) that targets the Java platform. It supports fine-grained DIFA by tracking flows involving local variables, global variables, array elements, class instances, and instance variables. DynFlow comprises two main components: the Instrumenter and the Profiler. The preliminary step in applying it is to instrument the target byte code classes and/or jar files using the Instrumenter, which was implemented using the Byte Code Engineering Library, BCEL (2003). The Instrumenter inserts a number of method calls to the Profiler at given points of interest. At runtime, the instrumented application invokes the Profiler, passing it the information that enables it to monitor information flows and build dynamic slices. 3. Baseline algorithms In order to evaluate the performance of our proposed DIFA algorithm (described in Section 4), we compare it in Section 5 to three baseline algorithms that are based on existing work. Here we briefly describe these algorithms which we call Basic, Memoized, and roBDD. Section 3.4 also describes the structure of the information flow sets that these algorithms compute. 3.1. Basic The Basic dynamic information flow analysis algorithm is a naïve implementation of equation (I) described in Section 2.4 and in detail in (Masri and Podgurski, 2009b). Fig. 1 shows a high level description of it. The algorithm applies the equation sequentially at every action tm of an executing program and then stores the results for subsequent use. Since the algorithm is forward computing, when the equation is applied at tm , all the values it depends on have already been computed and are available. In this respect, this algorithm is similar to the dynamic slicing algorithm presented in (Beszedes et al., 2001; Faragó and Gergely, 2002). In the Basic algorithm, set union operations have the biggest impact on performance. Let V be the number of active variables in a program, i.e., variables that at some point during execution had an influence over some other variable(s) via data and/or control dependences, and assume that primitive set operations used to implement unions have unit cost.1 Then the worst-case time requirement to compute InfoFlow at an action tk (excluding the time
1
In our implementation, sets are implemented with hash tables, so such operations have O(1) cost on average.
The Basic DIFA algorithm performs the following main steps at each action sk : (1) determines the set of variables sk uses, (2) determines the predicate action on which sk is directly dynamically control dependent, (3) computes InfoFlow(sk ) by unconditionally performing the set union of all the InfoFlow sets associated with the entities identified in steps 1 and 2, and (4) stores the computed InfoFlow set to be potentially used in a subsequent occurrence of step 3. A basic dynamic slicing algorithm would perform parallel steps as described in (Masri and Podgurski, 2009b). In Masri (2008) we presented a dynamic slicing algorithm that optimizes the costly operation at step 3 as follows. The set union operation on a given pair of slices is performed only once, and the resulting set is stored. Consequently, when the same pair of slices must be merged, the pre-computed union slice can be simply fetched from storage instead of being recomputed. This technique is generally called memoization in the literature (Cormen et al., 2001). Hence, the time complexity of merging a pair of slices for the first time is O(S), where S is the number of statements, but the expected cost of subsequent merges of the same slices is O(1). Appendix A describes the memoization based slicing algorithm in more details. We implemented a variation of the above algorithm in the context of DIFA to use as one of our baseline techniques. This DIFA algorithm, which we will refer to as Memoized, has O(V) time complexity for merging a pair of InfoFlow sets for the first time, but its expected cost of subsequent merges of the same InfoFlow sets is O(1). Note that its memory complexity at a given action sk is O(V2 ) due to the following: (a) the worst-case scenario occurs when DInfluence(sk ) is comprised of all V active variables, (b) the algorithm performs successive pair-wise set union operations (see k Appendix A) on the InfoFlow log V sets associated with DInfluence(s ) resulting in a total of V L=12 1/2L = V − 1 operations, and (c) each of these union operations is applied on two sets of V variables. 3.3. roBDD Zhang et al. (2004) conducted an empirical study where they observed the following: same dynamic slices tend to reappear during execution, different slices tend to share statements, and clusters of statements located near each other in the program often appear in a slice. They developed a forward computing algorithm that exploits the identified characteristics. The reappearance of slices is exploited by saving distinct slices only, and the sharing and clustering characteristics are exploited by representing the sets corresponding to dynamic slices using reduced order binary decision diagrams (roBDDs). The space efficiency of their roBDD-based algorithm is expected due to the fact that: (1) only distinct slices are stored, and (2) slices are represented using roBDDs, a technique whose compression efficiency is well established.
2 A variable v directly influences an action tk if v is used by an action that directly influences tk . Note that at tk there can be only O(V) reaching definitions and O(1) direct control dependences.
1174
W. Masri, H. Halabi / The Journal of Systems and Software 84 (2011) 1171–1190
Fig. 2. Designator classes.
We implemented a variation of the roBDD-based slicing algorithm described in (Zhang et al., 2004) to support DIFA, which we will refer to hereafter as roBDD. The main difference of our implementation is that it tracks dependences between variables as opposed to statements. We based our implementation on the javaBDD library (javabdd.sourceforge.net) which is built on top of Buddy (buddy.sourceforge.net), the same C library used in (Zhang et al., 2004). The time complexity of the roBDD algorithm at a given action is O(V3 ), this is because the union operation on two sets represented using roBDDs has a worst-case quadratic cost (Zhang et al., 2004). Its overall space complexity is O(2V ), this follows from the analysis presented by Meinel and Theobald (1998).
its declaring method, its index, and associated thread. Note that an additional attribute capturing the instance of the method call would have made the identification of local variables more accurate, e.g., one that represents the timestamp (or count) of the call within a thread. With our current implementation we opted not to include such attribute as doing so will degrade the performance of our algorithms in case of deep recursion. For example, given a recursive function with n local variables, the cardinality of the InfoFlow set associated with the return value after m recursions is likely to be proportional to n × m (as opposed to just n), which not only increases the memory requirements but also time requirements for this and subsequent computations of InfoFlow sets. Note though that this decision might be reconsidered in future work.
3.4. Information flow sets Our implementations of the DIFA algorithms involved in this work target the Java platform. Therefore, the computed and maintained InfoFlow (information flow) sets consist of array elements, instance fields, local variables, and static fields, which are uniquely identified by instances of the following four classes: ArrayElementDesignator, InstanceFieldDesignator, LocalVariableDesignator, and StaticFieldDesignator, respectively. The UML diagram in Fig. 2 describes these four designator classes. Two InfoFlow sets are equal if the designators of their elements are equal. An array element is identified by its array object reference and its index. An instance field is identified by its underlying object reference and field name. A static field is identified by its class name and field name. Finally, we identify a local variable by
4. DIFA algorithm for test suite profiling Forward computing dynamic slicing was improved drastically through leveraging roBDD’s (Zhang et al., 2004) and memoization (Masri, 2008), but leveraging these two mechanisms alone for DIFA did not have a similarly positive effect. That is, in preliminary experiments, the roBDD and Memoized DIFA algorithms described in Section 3 did not yield promising results. Therefore, we opted to investigate and improve the Memoized DIFA algorithm to devise a new algorithm that is suitable for full test suite execution and profiling. The following are the two main assumptions behind our new algorithm that were drawn from experimental results conducted in (Masri, 2008; Zhang et al., 2004) and from Section 5.2:
W. Masri, H. Halabi / The Journal of Systems and Software 84 (2011) 1171–1190
1175
Fig. 3. Introduced level of indirection.
1) Execution profiles are typically generated for full test suites as opposed to individual test runs. And when computing an InfoFlow set at a given action, there is a high probability that the set union operations that need to be performed have already been performed during InfoFlow computations that previously took place in the same test run or an earlier test run in the test suite. 2) While InfoFlow sets corresponding to different actions may not be identical, they often have many elements in common. And it is likely that such overlapping is total within a given type of designators, e.g., two sets might differ in the local variables they contain whereas their respective array elements, instance fields and static fields are all identical. Sections 4.1 and 4.2 describe the new design decisions influenced by the above assumptions, however, it should be noted that in our new algorithm we adopt several design decisions similar to the ones adopted by the Memoized algorithm (Appendix A) as described next. Every InfoFlow set is stored and associated with a unique identifier (uid). The association between the distinct InfoFlow sets and their uids is maintained in two hash tables. The first is called ifToUidMap where the keys are the distinct InfoFlow sets and the values are the uids, and the second is called uidToIFMap where distinct InfoFlow sets are indexed by their corresponding uids. As in the Memoized algorithm, to enable the reuse of the unions of the previously merged InfoFlow indices, we use a hash table which maps uids of distinct InfoFlow sets to the uid of their union. However, unlike the Memoized strategy, we drop the intermediate pair-wise processing because we observed that it escalates the memory requirements considerably, without enhancing the runtime majorly. Therefore, we choose to optimize the set union operations by considering k-wise processing of uids, where k is the number of direct dynamic dependencies of an action. This is feasible in practice since in (Masri, 2008) we found that an action tm is typically directly dependent on two to five other actions only, i.e., typically 2 ≤ |DInfluence(tm )| ≤ 5. 4.1. Leveraging InfoFlow sets computed in previous tests The aim here is to leverage the already computed dependences to efficiently compute subsequent dependences within the same
or later test runs. First, we opt to execute all tests within the same process so that InfoFlow sets would be available for potential reuse in subsequent tests. Recalling the structure of information flow sets exposed in Section 3.4, array element designators and instance field designators contain references to their respective underlying objects; relying on these references to track objects across tests is obviously not possible due to their redefinition in each test. Second, we opt to use a (best effort) matching strategy where objects of a given test can be matched with objects of previous tests according to some matching criteria, thereby allowing the leverage of InfoFlow sets (containing array elements and/or instance fields) that were computed in previous tests. 4.1.1. Matching objects across tests Our matching criteria should be sound and safe. The former means that no two distinct objects of the same run should be mapped to the same object, and the latter means that truly similar objects should be matched; otherwise, the potential of exploiting previous InfoFlow sets will be diminished. The criteria we use for considering two objects as identical across test runs include: (1) signature of method in which the object was defined, (2) instruction at which the object was defined, specifically, the byte code offset within the method, (3) thread that instantiated the object, and (4) order of instantiation of object. This last criterion is needed when distinct objects are created while the first three criteria are satisfied, which would typically occur in loops. In case of a successful object match, say object oj in run tj matched object oi in a previous run ti , all array element and instance field designators referencing oj must now reference oi instead in order to take advantage of already computed InfoFlow sets. Searching for and updating all such designators is clearly not efficient. We therefore modify the ArrayElementDesignator and InstanceFieldDesignator classes as shown in Fig. 3 such that the level of indirection allowed by the introduction of the ObjectDesignator class makes it possible to substitute the costly search and update process by a simple hash table lookup. An instance of an ObjectDesignator class is hereafter referred to as Object Designator. Such an instance comprises the object reference and the elements of the matching criteria. Object Designators are stored in a hash table called Objects. When a new object is created and a matching object is found according to the aforementioned criteria, the matched
1176
W. Masri, H. Halabi / The Journal of Systems and Software 84 (2011) 1171–1190
Fig. 4. Original InfoFlow set representation.
Object Designator would have its object’s reference updated, thus reflecting that of the new instance. The mapping between an object reference and its Object Designator is captured through another hash table, called ObjMapping. Appendix B encodes simplified steps of our algorithm and Appendix D provides a supportive discussion of our algorithm. 4.1.2. Garbage collection Although our improved algorithm is highly likely to minimize the runtime as we progress in the test suite, yet accumulating all historical information flows from one run to the next will almost certainly cause a long running program to run out of memory. To address this issue, we initially explored garbage collecting InfoFlow sets that have been inactive for a predefined number of test runs. However, this approach exhibited noticeable slowdowns. That led us to adopt a simpler but more efficient garbage collection mechanism which calls for flushing out all previously processed InfoFlow sets whenever the program becomes at risk of going out of memory. 4.2. Leveraging the overlapping characteristics of InfoFlow sets We first present an example to illustrate how overlapping could be total within a given type of designators. Suppose that in a given program we made the assignment statement y = x, where x has been defined at an earlier stage. Once that statement is reached, the InfoFlow set for x would have already been processed which we show in Fig. 4. Based on the Memoized algorithm, the list of direct influences of y would consist of the uid of the InfoFlow set of x, and the uid of x itself. Assuming that this pair has not been processed before, the Memoized algorithm would force an explicit unionization of the InfoFlow set of x and the variable x itself. The resulting InfoFlow set is captured in Fig. 5. Because of a single added element, the cost of unionization and the imposed memory requirements are obviously considerable. We conjecture that splitting the InfoFlow set into four sets each comprising designators of the same type, not only would minimize the memory requirements tremendously by reducing the degree of overlapping between different InfoFlow sets, but would also achieve time efficiency since the sets undergoing unionization would become smaller.
Incorporating our conjecture requires the following changes. The uid of an InfoFlow set would now be associated with four indices each corresponding to one of the designators type. For that purpose the uidToIFMap hash table will now be replaced by uidToQuadMap, and ifToUidMap replaced by quadToUidMap. The four indices are encapsulated within an object of type QuadList, containing AEDuid, IFDuid, LVDuid, and SFDuid. Accordingly, the first index AEDuid would map to an InfoFlow set containing Array Element Designators only. This mapping is established using a hash table called AEDuidToIFMap and the reverse mapping is established using the hash table ifToAEDuidMap, where the keys are InfoFlow sets of Array Element Designators solely and the values are AEDuids. Likewise, we have similar data structures for each of the remaining three designator types. We earlier argued that we should consider k-wise (as opposed to pair-wise) processing of uids, where k is the number of direct dynamic dependencies of an action. Therefore instead of having a uidPairToUid hash table where keys are comprised of uid pairs only, we would have uidKToUidMap where keys are comprised of k-tuple uids. Such a hash table is established for each of the four designators. Finally, another hash table, namely uidKToUidQuadMap is required, which associates the uids of k information flow lists to the UID of their union, represented through a QuadList. Thus, for a given executing statement, the uid of every direct influence is an index to an instance of QuadList, which itself contains uids where each maps to an InfoFlow set of similarly typed designators. This is in contrast to having the uid of every direct influence map directly to its InfoFlow set. Going back to our example, the representation of the InfoFlow set of variable x and y would change from that exemplified in Figs. 4 and 5 to that captured in Fig. 6a and b, respectively. In summary, instead of processing and storing the entirety of every new InfoFlow set, only a portion may be unionized and stored, which would enhance both the runtime and the memory requirements. The importance also lies in that these portions may not necessarily be identical in size, and often one portion of a full set corresponding to a particular designator type may be negligible with respect to another. Finally, Appendix C describes our algorithm that leverages the above observation.
Fig. 5. Processing the InfoFlow set of y using memoization.
W. Masri, H. Halabi / The Journal of Systems and Software 84 (2011) 1171–1190
1177
Fig. 6. a Updated InfoFlow set (after splitting). b Processing the InfoFlow set of y (after splitting).
5. Empirical study We compare our new algorithm against the three baseline algorithms described in Section 3, namely, Basic, Memoized and roBDD. And in order to better assess the impact of each of our design decisions on performance, we conduct our study using three variants of our algorithm, namely, Memoized-I, Memoized-II, and Memoized-III described below: 1. Memoized-I – In this variant, memoization is used to exploit InfoFlow sets from current and previous tests, as described in Section 4.1. Also, k-wise processing is adopted, hence reducing the space complexity at every computational point from quadratic to linear, and resulting in O(V) space cost. Note however that in this case, V denotes the total number of variables induced by all input tests within the test suite. The time complexity combines that of the Memoized algorithm and the object matching process described in Section 4.1.1. Because the object process matching exploits the use of hash tables (see Appendix B), matching objects from one run to the next is instantaneous. Thus, the time complexity of Memoized-I amounts to the time cost required by
the Memoized algorithm with the probability of evading InfoFlow sets unionization increasing as we progress within the test suite. 2. Memoized-II – In this variant, memoization is used to exploit InfoFlow sets from the current test only. Also, InfoFlow sets are decomposed into subsets according to the four types of designators, as described in Section 0. Maintaining all InfoFlow sets of all variables for the entire execution trace results in exponential memory complexity. When decomposing the sets into the four subsets, the worst-case memory complexity becomes equal to 2VA + 2V1 + 2VL + 2VS where VA , VI , VL , VS refer to the number of designators occupied by array elements, instance fields, local variables, and static fields respectively such that V = VA + VI + VL + VS . Mathematically we have 2VA + 2V1 + 2VL + 2VS ≤ 2(VA +V1 +VL +VS ) = 2V . The worst-case behavior occurs when all the variables in the program are of the same designator type, thus requiring the same worst-case memory complexity as that of the Memoized algorithm, which is 2V . The minimum value for 2VA + 2V1 + 2VL + 2VS is 4 × 2V/4 which occurs when the total number of designators is split equally among the four designator types. This results in a global exponential memory reduction by a factor of 2V /(4 × 2V/4 ) = 2(3V/4) − 2 . Although this
1178
W. Masri, H. Halabi / The Journal of Systems and Software 84 (2011) 1171–1190
Table 1 Runtime and memory complexities of the implemented algorithms. Algorithm
Time complexity
Memory complexity (at a particular computational point)
Worst-case memory consumption
Basic Memoized
O(V2 ) O(V) for the first merge O(1) for subsequent merges O(V3 ) O(V) for the first merge O(1) for subsequent merges (matching process is instantaneous) O(V) for the first merge O(1) for subsequent merges O(V) for the first merge O(1) for subsequent merges
O(V) O(V2 )
V2 2V
O(2V ) O(V)
2V 2V
O(V)
2VA + 2V1 + 2VL + 2VS < 2V (exponential reduction) 2VA + 2V1 + 2VL + 2VS < 2V (exponential reduction)
roBDD Memoized-I
Memoized-II Memoized-III
O(V)
algorithm necessitates an additional hash table required by the introduced level of indirection, but its size is linear with respect to the total number of variables, which gets disregarded when appended to the asymptotic complexity. The runtime complexity is theoretically the same as that of Memoized. Practically though, decomposing an InfoFlow set may reduce the number and the sizes of the sets undergoing unionization. 3. Memoized-III – This is basically our new algorithm that incorporates the designs of both Memoized-I and Memoized-II. We build upon the DynFlow (Masri and Podgurski, 2009b) tool to implement our new and baseline algorithms. Since DynFlow previously supported only the Basic algorithm, we extended it to include the implementations of a total of five DIFA algorithms, namely, Memoized, roBDD, Memoized-I, Memoized-II, and Memoized-III. Note that although DynFlow maintains all InfoFlow sets for all variables across the entire execution trace, it only retains the references to the sets associated with last definitions of variables. Finally, the complexities of all involved algorithms are summarized in Table 1. 5.1. Empirical setup To evaluate our improved algorithm, we apply our tool on three open source Java applications, the Xerces XML Parser version 2.1 (52,528 lines of code), the JTidy HTML syntax checker and pretty printer (9153 lines of code), and the Bouncy Castle Crypto API, referred to as Crypto hereafter, which provides an implementation for the Java Cryptography Extension and the Java Cryptography Architecture. These programs are selected because they do not require interactive user input; hence, they can be executed repeatedly from a script or a driver program. Xerces is tested using an XML test suite of 1000 tests contributed by several organizations such as Sun and IBM. JTidy is tested using 365 files downloaded from the Google Groups (groups.google.com) using a web crawler. For Crypto, we perform a series of encryption operations (such as MD5, MAC, etc.) on 100 randomly generated inputs that vary in size from 100 b to 100 kb. To enable a better visualization of our results, we order all input tests within every test suite of the selected applications in increasing order of the input size, since the execution time is almost directly proportional to the input size. All numbers presented are the result of considering the average value of three runs. Finally, note that in our experiments we used a 64-bit platform with 8 GB of RAM.
Fig. 7. Utilization ratios for Xerces.
our subject programs. When an InfoFlow set is being computed and it is found that it has been computed during a previous run, the hit counter gets incremented; otherwise, the miss counter is incremented. Note that we only increment the hit/miss counters once for every distinct InfoFlow set. Accordingly, during a given run, the sum of the hit and miss counters gives the total number of distinct InfoFlow sets computed during that particular run. We record the values of the two counters at every run of the test suite, and we define the Utilization Ratio as the ratio of the hit counter to the total number of distinct InfoFlow sets computed in a given run. Fig. 7 plots the utilization ratios for Xerces. After the 800th test, DIFA becomes more expensive. Along with the accumulation process, this causes the tolerated free memory to go below threshold, causing four calls to the garbage collector (see Section 4.1.2), thus resulting in four instances where the utilization ratio drops to zero. On average, the utilization ratio for Xerces across its test suite is 0.82. Fig. 8 plots the utilization ratios for JTidy. The utilization ratios witness their highest values between the 125th and the 250th tests.
5.2. Empirical evaluation of the utilization ratio across a test suite In order to evaluate our hypothesis that new tests are likely to share a considerable number of pre-computed information flows from previous runs, using Memoized-I, we keep track of two counters during the computation of InfoFlow sets induced by each of
Fig. 8. Utilization ratios for JTidy.
W. Masri, H. Halabi / The Journal of Systems and Software 84 (2011) 1171–1190
Fig. 9. Utilization ratios for Crypto.
During the last 115 tests, the garbage collector is called seven times. This fact, along with the distinct test cases, causes the utilization ratios to drop considerably within the last runs. Overall, the average utilization ratio across the entire test suite is 0.59. Fig. 9 plots the utilization ratios for Crypto. Crypto’s test suite is made of randomly generated input tests that are sorted in increasing order of size. As forecasted based on the nature of the program, the utilization ratios for Crypto are shown to be almost strictly increasing. At the end of the second run, the utilization ratio jumps to almost 0.8 and remains increasing till it reaches approximately 0.98 for the 100th test. 5.3. Execution times We present a comparison of the timing measures obtained when using all six algorithms. We do not discuss the timing measurements required for instrumenting the original program as it is fairly negligible. Following the same strategy as that in (Masri, 2008), we divide the execution time into components, Tcomp and Tbase . Tcomp is the time needed to compute InfoFlow sets and Tbase is the time that incorporates, among other things, the overhead of calling the DynFlow profiler, computing the direct data/control dependences, and building the control flow graphs of the methods when they are first invoked. Because all six algorithms are built on top of the DynFlow tool, Tbase would be the same for all six implementations. For a better visualization of the timing measurements in the included graphs, we factor out Tbase from the upcoming comparative timing measures. Note that the BDD library requires that the initial size of its node table be specified, which when initialized to a relatively small size would result in periodic table expansions and garbage collection processes that would increase the runtime considerably. On the other hand, initializing the node table to a relatively large size would have no impact on runtime, but would increase the memory consumption. To factor out this effect (to the advantage of roBDD), we record the actual BDD node table size required by every input test and initialize the table size accordingly, thus resulting in the optimal – yet generally not viable – performance of the roBDD algorithm both in terms of runtime and memory. Also note that the decision to sort the tests in increasing order of their size might have an impact on the performance Memoized-I and Memoized-III (only). We witnessed that by running Memoized-I on Xerces while having the tests sorted in decreasing (as opposed to increasing) order of their size, the runtime performance improved by 8.8%. For better visualization, we present the runtime measurements corresponding to each 100 tests at a time. Fig. 10a through Fig. 10j plot the runtime measurements for Xerces. Again, we exclude the plot for Basic as it demonstrates the worst runtime measurements. More specifically, the runtime percent increase of Basic over Mem-
1179
oized for the entire test suite is 42.06%. roBDD shows a global performance that is inferior to Memoized, where it demonstrates a 10.6% increase in runtime with respect to the latter for the first 835 tests and it goes out of memory for the remaining. We also omit the plot for roBDD to enable a better comparative visualization of Memoized relative to the new techniques. As observed, Memoized shows on average the worst runtime performance. Memoized-I is shown to have the lowest runtime measures at some instances and relatively high runtime measures at others. This can be attributed to the value of the utilization ratio witnessed at every input test run accompanied with the fact that the hash table operations are likely to become more expensive as its size increases. During the last 200 runs, the garbage collector gets called four times, resulting in lower values for the utilization ratios and possibly an increased cost associated with the operations on the hash tables. Fluctuations of the utilization ratios across the test suite would result in inconsistent behavior of Memoized-I. On the other hand, MemoizedIII is shown to have a more uniform performance as it combines the advantages of Memoized-I and Memoized-II. When compared to Memoized, the runtime percent decrease for the entire test suite is 51.75% for Memoized-I, 46.56% for Memoized-II, and 68.33% for Memoized-III. Fig. 11a through Fig. 11d plot the runtime measurements for JTidy. Again, we exclude the plot of the Basic algorithm as its global runtime is almost 10-folds that of Memoized. Unlike for Xerces, roBDD demonstrates on average a superior performance over Memoized, specifically, it exhibits a 45.28% relative decrease in the total runtime. The performance of Memoized-I goes in harmony with its associated utilization ratios depicted in Fig. 8. During the middle runs where the utilization ratios are considerably high, Memoized-I witnesses on average the lowest runtime measurements. After the 250th run, the utilization ratios become relatively low and the hash tables become large causing the performance of Memoized-I to decay where its runtime measures exceed that of Memoized in few cases. Overall, when compared against Memoized, Memoized-I runtime percent decrease is 34.27%, while Memoized-II demonstrates a 48.44% global decrease in the runtime measurements. Similar to Xerces, the behavior of Memoized-III is more uniform resulting in a 61.52% decrease in the total runtime. Fig. 12 plots the runtime measures for Crypto. Here, the timing measures are nearly increasing, probably because the tests are sorted in increasing order of their size. As expected, Basic is found to consume the most time, which in total is five times that consumed by Memoized. Therefore again, for better visualization, we do not show the time measures of Basic. Fig. 12 shows that even the optimal roBDD performance exhibits the worst-case runtime performance after Basic. For the entire test suite, the runtime percent decrease of Memoized with respect to roBDD is 45.13%. When compared to Memoized, the total runtime decrease for the entire test suite is 92.45% for Memoized-I, 53.49% for Memoized-II, and 94.41% for Memoized-III. The idea of utilizing previously computed InfoFlow sets in subsequent runs proves to be highly beneficial in terms of runtime, where both Memoized-I and Memoized-III result in minimal time costs. To better visualize the difference between both approaches, Fig. 13 isolates their runtime measures. More precisely, Memoized-III witnesses a global percentage decrease of 25.87% in runtime over Memoized-I. 5.4. Memory requirements We now contrast the space efficiency of all six algorithms in terms of the JVM peak heap usage. The JVM heap usage is recorded right after the Java garbage collector is invoked. The memory heap usage is measured by computing the following Java expression at
1180
W. Masri, H. Halabi / The Journal of Systems and Software 84 (2011) 1171–1190
every test run within the test suite: java.lang.Runtime( ).totalMemory( ) − java.lang.Runtime( ).freeMemory( )
Fig. 14 plots the memory usage of every input test for Xerces. The roBDD algorithm consumes the most memory where it goes out of memory at the 836th run. With respect to the four remain-
ing modes, as anticipated, Memoized-I and Memoized-III require the most memory as we progress within the test suite. Once the machine becomes close to running out of memory, all previously stored InfoFlow sets and related data are flushed out. This process has been carried out four times for Memoized-I and once for Memoized-III as shown in the figure. Memoized-III takes advantage of Memoized-II and consumes less memory than that required by Memoized-I with an average percentage decrease of 37.7% in memory usage. Fig. 15 focuses on the relative memory consumption
Fig. 10. (a thru j) Execution times for Xerces: Tcomp vs. input tests. (a) Execution times for the first 100 tests. (b) Execution times for the second 100 tests. (c) Execution times for the third 100 tests. (d) Execution times for the fourth 100 tests. (e) Execution times for the fifth 100 tests. (f) Execution times for the sixth 100 tests. (g) Execution times for the seventh 100 tests. (h) Execution times for the eighth 100 tests. (i) Execution times for the ninth 100 tests. (j) Execution times for the tenth 100 tests.
W. Masri, H. Halabi / The Journal of Systems and Software 84 (2011) 1171–1190
1181
Fig. 10. (Continued. )
between Memoized and Memoized-II. The latter shows an enhancement in its memory consumption that reaches 50% during the last couple of runs, which are the most expensive within the test suite. In fact, the average decrease in memory requirements is 12.47% for the entire test suite, reaching an average decrease of almost 37% for the last 100 runs. Fig. 16 plots the memory usage for JTidy of each of the 365 input tests. Memoized-I consumes the most memory. After the first 250 runs, the garbage collector gets called seven times. Memoized-III
consumes considerably less memory than Memoized-I, witnessing an average percentage decrease in memory usage of 52% with respect to the latter and demonstrating instead three occasions where the machine is close to going out of memory. The remaining three algorithms consume substantially less memory than the prior algorithms. To enable a better visualized comparison, the corresponding plots are isolated in Fig. 17. On average, roBDD consumes more memory than the remaining two approaches, followed by Memoized which exhibits an average percentage decrease of 8.14%
Fig. 11. (a thru d) Execution times for JTidy: Tcomp vs. input tests. (a) Execution times for the first 100 tests. (b) Execution times for the second 100 tests. (c) Execution times for the next 80 tests. (d) Execution times for the last 85 tests.
1182
W. Masri, H. Halabi / The Journal of Systems and Software 84 (2011) 1171–1190
Fig. 12. Execution times for Crypto: Tcomp vs. input tests.
with respect to its former. Memoized-II consumes the least memory with an average percentage decrease of 42.6% with respect to Memoized. Fig. 18, plots the memory usage of every input test for Crypto. Crypto, by nature, consumes much less memory than Xerces or JTidy. Recall that the memory consumption of roBDD reflects its optimal consumption unlike all other remaining modes that initialize the hash tables with a predefined size. roBDD consumes the least memory, followed by Memoized-II, and then Memoized. In comparison to Memoized, roBDD results in an average percentage decrease of 40.95% in terms of memory usage, while Memoized-II results in a 6% decrease. Certainly, Memoized-III and Memoized-I would result in higher memory usage as they accumulate InfoFlow sets from one run to another. Again, Memoized-III consumes less memory than Memoized-I since it integrates the advantages of MemoizedII.
5.5. Discussion and analysis In this section, we summarize our empirical findings. Basic shows the worst performance by far, which is strictly anticipated since it unconditionally merges InfoFlow sets without any optimization. The global runtime performance of Memoized-I is shown to beat that of Memoized, while itself displaying varying performances within the test suite in accordance with the associated values of its utilization ratios. That is, at some instances within the test suite when the utilization ratio is considerably high, MemoizedI exhibits the best runtime performance of all other approaches. At instances where the utilization ratio becomes relatively low, the runtime may become worse than that observed by Memoized. We attribute this degraded performance to the fact that operations performed on hash tables may become more expensive when their sizes become substantially large. Memoized-I’s memory consump-
Fig. 13. Isolating the Memoized-I and Memoized-III plots from Fig. 12.
W. Masri, H. Halabi / The Journal of Systems and Software 84 (2011) 1171–1190
1183
Fig. 14. Memory usage (Mbytes) vs. input tests for Xerces over 1000 tests.
Fig. 15. Isolating the Memoized and Memoized-II plots from Fig. 14.
tion is predictable since it accumulates information flows from one input test to the next within a test suite. Memoized-II is found effective both in terms of runtime and memory. The observation is that for all three subject programs, the
total runtime percent decrease with respect to Memoized is around 50%. With respect to memory consumption, the technique has been shown effective. Considering Crypto, Memoized-II did not show major memory enhancements relative to Memoized because in that
Fig. 16. Memory usage (Mbytes) vs. input tests for JTidy over 365 tests.
1184
W. Masri, H. Halabi / The Journal of Systems and Software 84 (2011) 1171–1190
Fig. 17. Isolating the Memoized, Memoized-II and roBDD plots from Fig. 16.
application more than 97% of the variables are array elements, which almost touches on the worst-case scenario for memory consumption in Memoized-II, as described at the beginning of this section. Unlike Memoized-I that may witness significant fluctuations within a test suite, Memoized-III is shown to be the most uniform and globally the most efficient. The root behind the observed uniformity and efficiency is the combined benefits of Memoized-I and Memoized-II. Compared against Memoized-I, Memoized-III results in major memory reduction, though it certainly remains expensive as it still implements the essence behind Memoized-I. Also, because Memoized-III lasts longer before it goes out of memory, its average utilization ratio across the entire test suite may be higher, thus improving the overall performance. roBDD witnesses the most dramatic changes both in terms of runtime and memory consumption between the different subject programs. For example, for Crypto, it is the most expensive in terms of time, but the most efficient in terms of memory. For Xerces, it is found to be slightly less time efficient than the Memoized, but its memory usage is even worse than that witnessed by Memoized-I. For JTidy, roBDD shows well-improved runtime measures over Memoized while requiring modest memory requirements. This highly nondeterministic behavior of roBDD is directly related to the applicability of binary decision diagrams,
and how well exploited the clustering and overlapping properties are. Though roBDD is generally efficient for dynamic slicing (Zhang et al., 2004), it may not be strictly the case for DIFA. While the number of statements in a program is bounded, the number of variables is not, possibly requiring larger node table sizes associated with the BDDs. This in turn may cause the clustering property to be less observed within DIFA than it is for dynamic slicing. The three different behaviors witnessed by roBDD for the three subject programs make it an unreliable choice for achieving conforming performances for any subject program, bearing in mind that the witnessed performances – both in terms of runtime and memory – are the optimal performances for roBDD which are practically unlikely. 6. Threats to validity The potential threats to internal validity of our empirical evaluation include: 1) Imprecision in the memory usage measurements: Since Java relies heavily on garbage collection, our memory usage measurements are not likely to be accurate. However, this should not be of major concern as such lack of accuracy affects every one of the implementations we considered in our comparisons. To
Fig. 18. Memory usage (Mbytes) vs. input tests for Crypto over 100 tests.
W. Masri, H. Halabi / The Journal of Systems and Software 84 (2011) 1171–1190
2)
3)
4)
5)
alleviate this potential inaccuracy, we explicitly call the garbage collector System.gc() before computing the memory usage. Inaccuracy in the timing measurements: The BDD Java library we use within the implementation of roBDD has many of its components implemented in the C language. This might influence the comparative timing measurements made since the remaining competing algorithms have been fully implemented in Java. Potential incorrectness or faulty implementation of the evaluated algorithms: In our experiments we assume that Basic is correct and consider it as an oracle to evaluate the correctness of the other algorithms. That is, when in verification mode, our tool computes each Inflow set using Basic and one of evaluated algorithms and the results are compared. Running our experiments in verification mode lead to no disparity in our results. Uncertainty in the efficiency of the roBDD algorithm: roBDD necessitates that the initial BDD node table size be defined a priori, and such a choice will have a considerable impact on both timing and memory performance. Limitation of the dependence analysis in the presence of calls to native or uninstrumented methods: our implementation attempts to address this issue as follows. Given a call to an uninstrumented or native function foo() such as “int x = foo(int y);” x would be considered dependent on y regardless of whether it actually is. That is, we conservatively assume that the return value depends on y, which clearly reduces the precision of our analysis. Also, our analysis cannot account for dependences induced in foo() that might involve variables not passed as parameters. In all, the accuracy of our analysis is reduced due to this limitation.
Finally, a major threat to external validity of our results is the limited sample of subject programs and inputs considered. Further empirical studies with a variety of additional subject programs will be required to assess the generality of our approach. It would be desirable that the subject programs be sampled from different application domains and from different program categories within which the programs exhibit similar structural characteristics. Unfortunately, identifying the structural characteristics that might be relevant to the performance of DIFA algorithms requires further extensive studies.
7. Related work A vast body of work exists that relates to dynamic dependence analysis, in this section we limit our coverage to work related to dynamic information flow analysis and dynamic slicing as they are most closely related to this work.
7.1. Dynamic information flow analysis Lampson (1973) motivated the work in the context of security by listing a number of possible information leaks that could potentially be detected by information flow analysis (Korel and Yalamanchili, 1994). Zimmermann et al. (2003a,b) proposed an intrusion detection model based on runtime enforcement of an information flow policy, which specifies which information flows are permissible in a given system. Their model deals with information flows between entire objects, whereas we are interested in fine-grained analysis that tracks flows involving object fields, local variables, global variables and array elements. Haldar et al. extended the Java Virtual Machine (JVM) to add mandatory access control (MAC) that allows information flow policies to be enforced (Haldar et al., 2005), but also their mechanism tracks information flows at the granularity of objects and does not consider flows involving control dependences.
1185
Information flow analysis is related to taint analysis which aims at identifying the program operations that could be affected by untrustworthy input values. Generally, taint analysis involves analysis of data dependences but not control dependences, whereas information flow analysis addresses both types of dependences and so has a wider scope of application. The Perl scripting language provides a special execution mode called taint mode, in which all user-supplied input is treated as suspicious unless the programmer explicitly approves it. Taint mode is very effective at preventing data flows from user-supplied input data to potentially dangerous system calls, but it is unable to detect or prevent information flows involving control dependence. Newsome and Song (2005) propose dynamic data taint analysis for automatic detection of overwrite attacks, and they describe TaintCheck, a tool that performs dynamic taint analysis by performing binary rewriting at run time. Clause et al. (2007) presented Dytan, a tool for dynamic tainting analysis that supports x86 executables. In Masri and Podgurski (2009b), Masri and Podgurski proposed a technique for detecting and debugging insecure information flows using DIFA. Their algorithm was the first precise forward computing algorithm that applies to both structured and unstructured programs. However, it unconditionally merges the flow sets influencing an executing statement, which is a costly operation (see Section 3.1). 7.2. Dynamic slicing Dynamic slicing was first introduced by Korel and Laski (1988), where it has been shown that precise dynamic slices can be considerably smaller than their static counterparts. Agrawal and Horgan (1990) used the notion of a program dynamic dependence graph (DDG) in computing dynamic slices. They proposed using a reduced dynamic dependence graph, whose size is bounded, where a new node is created only if it causes a new dynamic slice to be introduced. Zhang et al. (2003) described three different algorithms based respectively on: (1) backward computation of dynamic slices, (2) pre-computation of dependence graphs, and (3) traversing the execution trace to service a slice request. All the aforementioned algorithms are based on “backward” analysis which requires the execution trace to be available a priori. The focus of this paper is on forward computing algorithms. Korel and Yalamanchili (1994) were the first to propose a forward computing algorithm for dynamic slicing, their algorithm is applicable only to structured programs. In Beszedes et al. (2001) and Faragó and Gergely (2002), another forward computing slicing algorithm was proposed for handling possibly unstructured C programs. However, this algorithm employs imprecise rules for capturing control dependences. In Zhang et al. (2004), Zhang et al. introduced an efficient forward computing algorithm that uses reduced ordered binary decision diagrams (roBDDs). Masri (2008) explored the use of memoization in the forward computation of dynamic slices. The algorithms in (Zhang et al., 2004; Masri, 2008) are further described in Section 3. 8. Conclusions and future work We presented a DIFA algorithm that leverages InfoFlow sets that were computed in previous test runs. One of the challenges that our algorithm needed to address is how to accurately and efficiently match objects across test runs. We conducted an empirical study that contrasted it, with respect to efficiency, to three other algorithms: (1) Basic: a naïve basic algorithm, (2) Memoized: a memoization based algorithm that does not leverage computed dependences from previous test runs, and (3) roBDD: an algorithm that uses reduced ordered binary decision diagrams to maintain and manage dependences. The results showed that our algorithm
1186
W. Masri, H. Halabi / The Journal of Systems and Software 84 (2011) 1171–1190
is globally the most efficient, i.e., it performed best when considering the combined time and space requirements of several subject programs. Whereas roBDD exhibited the most irregular behavior in performance as its effectiveness is considerably affected by the nature of the subject programs and the node table size chosen. For future work, we intend to do the following:
1) Investigate more accurate object matching techniques across runs, especially in relation to thread matching, as described in Steven et al. (2000). 2) Investigate the use of additional attributes to better identify local variables, e.g., the timestamp of the method call within a thread. 3) Try to reduce the impact of Tbase . That is, focus on reducing the cost of calling the DynFlow Profiler and computing the direct data/control dependences. 4) Investigate ways to apply the lessons learned from this work onto improving dynamic slicing and strength-based DIFA Masri and Podgurski (2009a), which is a dependence analysis technique that aims at identifying the dependences that carry measurable information from source to target. 5) Conduct more extensive empirical studies involving subject programs of varying domains.
Acknowledgment This research was supported in part by NSF grant no. 0819987.
Appendix A. Memoization-based dynamic slicing algorithm Algorithm A-1: Pseudo-code of the memoization based dynamic slicing algorithm Compute MemoizedDynSlice() Input: Action tm 1. Compute DInfluence(tm ) 2. uidList.add(tm .UID) 3. for all sk in DInfluence(tm ) do 4. uidList.add(sk .UID) 5. end for 6. while uidList.size > 1 do 7. m = 0 8. max = uidList.size − 1 8. For n = 0 to max by 2 do 9. if n + 1 < = max then 10. uidList[m] = PairUnion(uidList[n], uidList[n + 1]) else 11. uidList[m] = uidList[n] 12. m++ 13. end for 14. trim(uidList, m) end while 15. tm .UID = uidList[0]
Algorithm A-2: Pseudo-code of the PairUnion helper method Compute PairUnion() Input: UID s1 uid Input: UID s2 uid Output: UID s3 uid 1. s3 uid = uidPairToUid.get({s1 uid, s2 uid}) 2. If s3 uid ! = null then 3. done 4. else 5. s3 = uidToSlice[s1 uid] U uidToSlice[s2 uid] 6. s3 uid = sliceToUid.get(s3) 7. if s3 uid = = null then 8. s3 uid = generate unique identifier 9. uidToSlice[s3 uid] = s3
10. sliceToUid.put(s3, s3 uid) 11. end if 12. uidPairToUid.put({s1 uid, s2 uid}, s3 uid) 13. End if
This algorithm (presented in (Masri, 2008)) requires that the first time a slice occurs it is stored and associated with a unique identifier (uid). The association between the distinct slices and their uids is maintained in two data structures; a hash table called sliceToUid where the keys are the distinct slices, and the values are the uids, and a one-dimensional array called uidToSlice where distinct slices are indexed by their corresponding uids. To enable the reuse of the union of previously merged pairs of slices, a hash table is used called uidPairToUid, which associates the uids of two slices to the uid of their union slice. Algorithm A-1 presents a high level description of the algorithm. In short, the algorithm applies pair-wise processing to the uids of the statements directly influencing an action tm , which have been computed and stored within a list of uids, referred to hereafter as uidList, in lines 1–4. Lines 6–14 compute the slice representing the union of all the uids in uidList by repeatedly calling the PairUnion() method on its successive pairs of uids. Line 10 stores the result of the PairUnion() method in the uidList itself, thus allowing the uidList to shrink after every iteration, until it finally contains one uid, that is the uid of the slice resulting from the algorithm. Line 14 trims the size of uidList as the slices associated with its right side entries have been incorporated in its left side entries. Algorithm A-2 presents a high level description of the PairUnion() method. Given two slices S1 and S2 with uids s1 uid and s2 uid respectively; the method first checks whether an entry associating these slices with their union already exists in uidPairToUid. If the entry exists, then the uid of the union of this pair can be obtained in O(1) time, as no union computation is needed. But if the entry does not exist, then the union of S1 and S2 should be explicitly computed, which requires O(S) expected time, where S is the number of statements. Referring to the resulting unionized set as S3, it is first checked whether it is its first occurrence by looking it up in sliceToUid (lines 6 and 7). This operation requires O(S) expected time, because simply computing a hash key based on all the statements in the slice takes linear time, and so does the checking of the equality of two slices. If no entry corresponding to S3 is found in sliceToUid, then a new unique identifier s3 uid is first generated for it by incrementing the index of the last element in uidToSlice. Second, S3 is added to uidToSlice at location s3 uid. Third, (S3, s3 uid) is added to sliceToUid. And finally, an entry is added to uidPairToUid such that the key consists of {s1 uid, s2 uid} and the value is s3 uid. On the other hand, if an entry for S3 is found in sliceToUid, then only that last step of adding an entry to uidPairToUid is required. Appendix B. Memoized-I algorithm Algorithm B-1: High level description of Memoized-I 1. Upon the creation of a new Array Element Designator or Instance Field Designator, call it varDes, do { 2. Check whether the underlying object reference (o) exists as a key in the ObjMapping hash table 3. If found, then 4. Set the designator’s Object Designator (varDes.od) to the image of object (o) in ObjMapping EXIT 5. Collect the object’s information: class name, method signature, thread identifier, and object’s instruction 6. Create an Object Designator’s instance (od) for the current object and determine its sequence number
W. Masri, H. Halabi / The Journal of Systems and Software 84 (2011) 1171–1190
7. If (this run is the program’s first run) a. Insert (od) in the Objects hash table b. Store the association (o, od) within ObjMapping c. Set the designator’s Object Designator (varDes.od) to (od) 8. Else a. Search for a matching Object designator od to od in Objects b. If od is found, then i. Update the matched Object Designator’s object reference (od .instance) to the reference of the new matching object (o) ii. Store the association (o, od ) within ObjMapping iii. Set the designator’s Object Designator to the matched instance (i.e. varDes.od = od ) c. Else i. Insert (od) in the Objects hash table ii. Store the association (o, od) within ObjMapping iii. Set the designator’s Object Designator (varDes.od) to (od)
Tracing the simplified steps of our algorithm above, whenever a new Array Element Designator or Instance Field Designator gets created, we check to see whether its underlying object reference has already been previously processed, that is, if it exists as a key in ObjMapping (lines 1 and 2). If it does exist, then the current designator’s Object Designator is set to reflect the image of that instance, or explicitly, to the Object Designator embracing that instance (lines 3 and 4), and the algorithm terminates. If it does not exist, then we create a new Object Designator containing the new object reference and its related information (lines 5 and 6). Unless the run is the first, we search within the Object Designators in Objects for a matching object according to the previously mentioned matching criteria (line 8.a). When a match is found, the matched Object Designator instance would have its object’s reference updated, thus reflecting that of the new instance, and the association between the matching object reference and its matched Object Designator is stored. Finally, the current designator’s Object Designator is set to that found to match (line 8.b). When a match is not found, then we add the newly created Object Designator to the list of current designators in the Objects hash table, store the association between the newly created object and newly created Object Designator in ObjMapping, and set the current designator’s Object Designator to that newly created one (line 8.c). When the run is the first one, we consider the same actions as that taken when no match is found (line 7). In order to fully optimize the above algorithm, searching for a matching Object Designator, in addition to updating an Object Designator sequence number reflected in lines 8.a and 6 respectively have not been made straightforwardly linear. Instead, efficient hash functions have been utilized so that the algorithm would require O(1) on average. The proposition is to exploit the benefits of a hash table by having the Object Designator’s hash code encapsulate the criteria required for matching so that the search becomes instantaneous. The Object Designator’s equality in this case would be based on comparing the designators’ underlying objects’ information constituting the matching criteria. As for the update sequence operation, the proposition is to have the Object Designator’s hash code be based on all four criteria for object matching except for the sequence number, and create a new hash table, namely ObjToSeq, that would map every Object Designator to the last updated sequence number of its partially similar designators (that is Object Designators having the same matching criteria except the sequence number). Accordingly, the Object Designator’s equality in this case would be based on equalizing all the aforesaid matching criteria, except for the sequence number. Thus, the Object Designator’s hash function and equality methods behave differently depending on the mode of operation (finding an object match vs. updating the sequence number).
Appendix C. Memoized-II algorithm Algorithm C-1: Pseudo-code of the main computational method of Memoized-II Compute MemoizedII() Input: Action tm 1. Compute DInfluence(tm ) 2. uidList.add(tm .UID) 3. For all sk ∈DInfluence(tm ) 4. uidList.add(sk .uid) endfor 5. If(uidList.size() = = 1) then 6. tm .UID = uidList[0]; 7. return; 8. Else k = sortArray(uidList, size) end if 9. t uid = uidKToUidIFMap.get(uidList[0], uidList[1] . . . uidList[k]) 10. If (t uid ! = null) then 11. tm .UID = t uid 12. else 13. {AEDuidList, IFDuidList, LVDuidList, SFDuidList} = classifyIndices(uidList); 14. if (AEDuidList.size() > = 0) AEDuid = handleAEDuidList(AEDuidList); 15. if (IFDuidList.size() > = 0) IFDuid = handleIFDuidList(IFDuidList); 16. if (LVDuidList.size() > = 0) LVDuid = handleLVDuidList(LVDuidList); 17. if (SFDuidList.size() > = 0) SFDuid = handleSFDuidList(SFDuidList); 18. QuadIndices newQuad = new QuadIndices(AEDuid, IFDuid, LVDuid, SFDuid); 19. newUID = quadToUidMap.get(newQuad); 20. if(newUID = = null) 21. newUID = generate a unique identifier 22. uidToQuadMap.put(newUID, newQuad); 23. quadToUidMap.put(newQuad, newUID); end if 24. uidKToUidIFMap.put(uidList[0], uidList[1] . . . uidList[k], newUID); 25. tm .UID = newUID; end if Algorithm C-2: Pseudo-code for the helper method classifyIndices of Memoized-II //pre-condition: size > = 1 Compute classifyIndices(Integer[] uidList) 1. for (int i = 0; i < uidList.size(); i++) 2. cur = uidList[i]; 3. QuadIndices quad = (QuadIndices)uidToQuadMap.get(cur); 4. if (quad.AEDuid! = null) → AEDuidList[+ + AED index] = quad. AEDuid; 5. if(quad.IFDuid! = null) → IFDuidList[+ + IFD index] = quad. IFDuid; 6. if(quad.LVDuid! = null) → LVDuidList[+ + LVD index] = quad. LVDuid; 7. if(quad.SFDuid! = null) → SFDuidList[+ + SFD index] = quad. SFDuid; end for 8. return {AEDuidList, IFDuidList, LVDuidList, SFDuidList} Algorithm C-3: Pseudo-code for the helper method handleAEDuidList of Memoized-II Compute handleAEDuidList(Integer[] AEDuidList) 1. if(AEDuidList.size() = = 1) return AEDuidList[0]; 2. else k = sortArray(AEDuidList); end if 3. AEDuid = AEDuidKToUidIFMap.get(AEDuidList[0], AEDuidList[1] . . . AEDuidList[k]); 4. if(AEDuid! = null) return AEDuid else 5. newIF = AEDuidToIFMap.get(AEDuidList[0]) U AEDuidToIFMap.get(AEDuidList[1]) . . . U AEDuidToIFMap.get(AEDuidList[k]) 6. newAEDuid = ifToAEDuidMap.get(newIF); 7. if (newAEDuid = = null) 8. newAEDuid = create a unique identifier 9. AEDuidToIFMap.put(newAEDuid, newIF); 10. ifToAEDuidMap.put(newIF, newAEDuid); end if 11. AEDuidKToUidIFMap.put({AEDuidList[0], AEDuidList[1] . . . AEDuidList[k]}, newAEDuid); 12. return newAEDuid end if Compute handleIFDuidList (Integer[] IFDuidList) {. . .} Compute handleLVDuidList (Integer[] LVDuidList) {. . .} Compute handleSFDuidList (Integer[] SFDuidList) {. . .}
1187
1188
W. Masri, H. Halabi / The Journal of Systems and Software 84 (2011) 1171–1190
Algorithm C-1 presents a high level description of our second improved dynamic information flow algorithm. Lines 1–4 collect the uids of the objects or variables influencing the current action in uidList. Lines 5–7 set the uid of the underlying executing statement to the uid of the first element in uidList in case the uidList contains a single element. Line 8 sorts the uidList according to increasing order of uids. According to the size of the sorted uidList, represented as k, line 9 checks whether an entry for the k uids {uidList[0] . . . uidList[k]} exists in uidKToUidIFMap. If the entry exists, then we obtain the uid to the QuadList reflecting the union of the k uids in O(1) time. If the entry does not exist, then we first need to classify the indices of every designator type (line 13). The method classifyIndices() explores the QuadList associated with every uid in uidList and groups the uids according to their types within four created type-specific uid lists namely, AEDuidList, IFDuidList, LVDuidList, and SFDuidList. For example, every uid within AEDuidList maps to an IF list of Array Element Designators in AEDuidToIFMap. Lines 14–17 handle every such uidList, and return the uid of the union of all uids in this list. This is done four times for every designator type’s uidList. Now that the uid to every unionized IF list of grouped-type designators has been found for each of the four designators, line 18 puts these four uids in a single container, namely a QuadList referred to in the algorithm as newQuad. Line 19 checks whether newQuad already exists in quadToUidMap. If it does not, then we generate a unique identifier (newUID) by incrementing the index of the last element in uidToQuadMap, and insert the record (newUID, newQuad) and (newQuad, newUID) into uidToQuadMap and quadToUidMap respectively. Finally, line 24 inserts the mapping between the k uids {uidList[0] . . . uidList[k]} and the newQuad’s uid (whether found or created) into uidKToUidIFMap, and line 25 sets newQuad’s uid as the uid of the underlying executing statement. Algorithm C-2 presents a high level description of the classifyIndices () method. It is given a list of uids, referred to as uidList, where every uid maps to an instance of QuadIndices. It does a simple iteration over the list, extracts every uid, fetches its associated QuadIndices instance, and appends every non-null type-specific uid into one of four created type-specific uidLists. After all uids of the input uidList have been processed, it returns the four lists namely AEDuidList, IFDuidList, LVDuidList, and SFDuidList, respectively. Algorithm C-3 presents a high level description of the handleAEDuidList () method. The input is an AEDuidList returned by classifyIndices () method, which is constituted of uids to IF Lists of Array Element Designators whose mapping is established within AEDuidToIFMap. Line 1 checks whether the AEDuidList contains a single element; if so, it returns this element. If AEDuidList contains more than one element, then it gets sorted in line 2 by increasing order of uids. According to the size of the sorted AEDuidList, represented as k, line 3 checks whether an entry for the k uids {AEDuidList[0] . . . AEDuidList[k]} exists in AEDuidKToUidIFMap. If the entry exists, then we can obtain the uid of the union of this ktuple uids, and no computation is required. But if the entry does not exist, then we need to explicitly compute the union of the IF lists of Array Element Designators that are associated with the k-tuple uids in AEDuidList; we refer to the resulting union as newIF. Then, we check whether this is the first occurrence of newIF by looking it up in ifToAEDuidMap. If no entry corresponding to newIF is found in ifToAEDuidMap, then this is the first occurrence of newIF, and the following is done: 1. Generate a unique identifier (newAEDuid) for it by incrementing the index of the last element in AEDuidToIFMap. 2. Add (newAEDuid, newIF) to AEDuidToIFMap. 3. Add (newIF, newAEDuid) to ifToAEDuidMap.
4. Add an entry to AEDuidKToUidIFMap such that the key consists of the k-tuple uids {AEDuidList[0] . . . AEDuidList[k]} and the value consists of newAEDuid. However, if we do find an entry for newIF in ifToAEDuidMap, then we only add an entry to AEDuidKToUidIFMap as in step (4) above. Appendix D. Supportive discussion/examples of Memoized-I As stated in Section 4.1.1 our matching criteria should be sound and safe, i.e., the following two respective conditions must be satisfied: 1. Soundness: No two distinct objects of the same run should be mapped to the same object. 2. Safety (or completeness): Truly similar objects should be matched; otherwise, the potential of exploiting previously computed InfoFlow sets will be diminished. First, we illustrate the importance of soundness. Assume that two different objects of the same run were mapped to the same Object Designator. Recall from Appendix B, step 8.b.i, that upon matching a given object to an Object Designator, the designator is updated to reference that underlying object. Accordingly, if the matched Object Designator already references a live object (of the same run), then, upon updating its reference, the previously computed InfoFlow sets referencing that Object Designator would become inaccurate. For example, considering the snippet of code below: 1. int a[] = new int[10]; 2. a[1] = a[0]; 3. int b[] = new int[10]; At lines 1 and 3, Object Designators o1 and o2 referencing array a and b respectively would be created. At line 2, the InfoFlow set of a[1] would be comprised of a[0], that is, a single Array Element Designator (AED) having 0 as a value for its index, and o1 as an Object Designator. Now suppose that the matching criteria was flawed, such that at line 3, object b was matched to Object Designator o1 (already referencing array a). In this case, the reference in o1 would get updated from a to b. Therefore, the InfoFlow set of a[1] would actually become b[0], which is incorrect. Note how applying our matching criteria to the above example leads to a sound behavior; in this case, the fact that objects a and b were defined by two different instructions guarantees soundness. Second, we illustrate the importance of safety. We argue that similar objects should be matched; otherwise, even if soundness holds, the algorithm would be correct, however, the potential of exploiting previously computed InfoFlow sets will diminish. Considering the same example, suppose that the program is run twice. During the first run, two Object Designators o1 and o2 are created that reference a and b, respectively. Again, the InfoFlow set of a[1] would be comprised of a single AED, which references o1 and whose index value is 0. If the matching criteria is correct, then during the second run, at line 1, array a would map to Object Designator o1 , and the reference in o1 would get updated to a (of the second run). Thus, at line 2, the system would realize that the computed InfoFlow set (comprised of a single AED referencing o1 and whose index is 0) already exists. Suppose however that the matching criteria was flawed, such that during the second run, array a and array b are matched to Object Designators o2 and o1 , respectively (note the reversed order).
W. Masri, H. Halabi / The Journal of Systems and Software 84 (2011) 1171–1190
In this case, upon processing line 2, the computed InfoFlow set is comprised of a single AED, which references o2 and whose index is 0. Such an InfoFlow set does not exist. Thus, the newly computed set gets stored. The undesirable consequence of this mismatch will clearly escalate if other variables in the program are influenced by a[1]. In this case, the memoization technique would fail to exploit previously computed InfoFlow sets, and consequently, all such InfoFlow sets need to be recomputed and stored. To elaborate, consider the slightly more complex example below: 1. 2. 3. 4. 5. 6. 7.
int a[] = new int[10]; a[0] = 1; int b[] = new int[1001]; b[1] = 1; b[2] = a[0]; for (i = 2; i < 1000; i++) b[i + 1] = b[i] + b[i − 1];
Tracing the memoization technique on the updated example, the InfoFlow set of b[2] is comprised of a single AED: {a[0]}. The InfoFlow set of b[3] is comprised of three AEDs and a single Local Variable Designator (LVD): {b[2], b[1], a[0], i}. Successively, the InfoFlow set of b[4] is comprised of four AEDs and a single LVD: {b[3], b[2], b[1], a[0], i}. When the first run of the program terminates, the system would have computed and stored 999 InfoFlow sets, illustrated below. Variable b[2] b[3] b[4] ... B[1000]
InfoFlow set a[0] a[0], b[1], b[2], i a[0], b[1], b[2], b[3], i a[0], b[1], . . ., b[999], i
Running our matching criteria on the program above, during the second run, at lines 1 and 3 respectively, array a and array b would map to Object Designators o1 and o2 respectively whose references would get updated to a and b (of the second run). At line 5, the system would realize that the computed InfoFlow set (comprised of a single AED referencing o1 and whose index is 0) already exists. Similarly, as a result of repetitively executing line 7, all other previously computed InfoFlow sets would get exploited. Suppose however that the matching criteria was flawed, such that during the second run, array a and array b are respectively matched to Object Designators o2 and o1 instead. Then, reflecting the same aforesaid analysis of the above example, at line 5, the algorithm would fail to recognize the AED of a[0], and a new one would get created. Consequently, all remaining InfoFlow sets which reference a[0] (shown in the above table) will get recomputed and stored. This happens because each of them embraces the newly created AED of a[0], which is unidentified. This example clearly demonstrates the importance of safety, since due to a single mismatch, the algorithm failed to exploit many (valid) InfoFlow sets that have been computed at a previous run. Finally, we illustrate the importance of incorporating the sequence number in our matching criteria. Consider the code below, in which 100 instances of Student objects are created and method getAge() returns the value of an instance variable: - int a[] = new int[100]; - for (int i = 0; i < 100; i++) - a[i] = (new Student(i)).getAge(); The InfoFlow set of every array element includes an Instance Field Designator that references a different Student object. If the object sequence number was not incorporated within the matching criteria, then, all instantiated Student objects would map to the
1189
same Object Designator which would reference the object that was created last, and all references to previously created objects would not be accounted for. This would not occur if the sequence number is considered. In such a case, 100 different Object Designators would be created, each associated with a different Student object.
References Agrawal, H., Horgan, J.R., 1990. Dynamic program slicing. In: Proceedings of the ACM SIGPLAN 1990 Conference on Programming Language Design and Implementation , (White Plains, New York, United States). PLDI ’90. ACM, New York, NY, pp. 246–256. The Byte Code Engineering Library (BCEL), 2003. The Apache Jakarta Project, http://jakarta.apache.org/bcel. Apache Software Foundation 2003. Beszedes, Á., Gergely, T., Szabó, Z.M., Csirik, J., Gyimothy, T., 2001. Dynamic slicing method for maintenance of large C programs. In: Proceedings of the Fifth European Conference on Software Maintenance and Reengineering (March 14–16, 2001) , CSMR. IEEE Computer Society, Washington, DC, 105. Clause, J., Li, W., Orso, A., 2007. Dytan: a generic dynamic taint analysis framework. In: Proceedings of the 2007 International Symposium on Software Testing and Analysis , (London, United Kingdom, July 09–12, 2007). ISSTA ’07. ACM, New York, NY, pp. 196–206. Cormen, T., Leiserson, C., Rivest, R., Stein, C., 2001. Introduction to Algorithms, 2 ed. The MIT Press. Elbaum, S., Malishevsky, A.G., Rothermel, G., 2002. Test case prioritization: a family of empirical studies. IEEE Transactions on Software Engineering 28 (2), 159–182. Faragó, C., Gergely, T., 2002. Handling pointers and unstructured statements in the forward computed dynamic slice algorithm. Acta Cybernetica 15, 489–508. Ferrante, J., Ottenstein, K.J., Warren, J.D., 1987. The program dependence graph and its use in optimization. ACM Transactions on Programming Languages and Systems 9 (3), 319–349. Haldar, V., Chandra, D., Franz, M., 2005. Practical, dynamic information-flow for virtual machines. Technical Report No. 05–02. Department of Information and Computer Science, University of California, Irvine. Harman, M., Danicic, S., 1995. Using program slicing to simplify testing. Software Testing Verification and Reliability 3, 143–162. Harrold, M.J., Rothermel, G., Sayre, K., Wu, R., Yi, L., 2000. An empirical investigation of the relationship between spectra differences and regression faults. Journal of Software Testing, Verification, and Reliability 10 (3), 171–194. Korel, B., Laski, J., 1988. Dynamic program slicing. Information Processing Letters 29, 155–163. Korel, B., Yalamanchili, S., 1994. Forward computation of dynamic program slices. In: Ostrand, T. (Ed.), Proceedings of the 1994 ACM SIGSOFT international Symposium on Software Testing and Analysis. Seattle, Washington, United States, August 17–19, 1994. ISSTA ’94. ACM, New York, NY, pp. 66–79. Lampson, B.W., 1973. A note on the confinement problem. Communication of the ACM 16 10, 613–615. Masri, W., Podgurski, A., Leon, D., 2007. An empirical study of test case filtering techniques based on exercising information flows. IEEE Transactions on Software Engineering 33 (7), 454. Masri, W., Podgurski, A., 2009a. Measuring the strength of information flows in programs. ACM Transactions on Software Engineering and Methodology 19 (2), Article 5, October. Masri, W., 2010. Fault localization based on information flow coverage. Software Testing Verification and Reliability 20, 121–147. Masri, W., 2008. Exploiting the empirical characteristics of program dependences for improved forward computation of dynamic slice. Empirical Software Engineering (Springer) 13, 369–399. Masri, W., Podgurski, A., 2009b. Algorithms and tool support for dynamic information flow analysis. Information and Software Technology 51, 385–404. Masri, W., Podgurski, A., 2008. Application-based anomaly intrusion detection with dynamic information flow analysis. Computers & Security 27, 176–187. Masri, W., El-Ghali, M., 2009. Test case filtering and prioritization based on coverage of combinations of program elements. Seventh International Workshop on Dynamic Analysis, WODA, Chicago, IL. El-Ghali, M., Masri, W., 2009. Intrusion detection using signatures extracted from execution profiles. 5th International Workshop on Software Engineering for Secure Systems, SESS, Vancouver, Canada. Meinel, C., Theobald, T., 1998. Algorithms and Data Structures in VLSI Design. Springer–Verlag New York, Inc., Secaucus, NJ, USA. Newsome, J., Song, D., 2005. Dynamic taint analysis for automatic detection, analysis, and signature generation of exploits on commodity software. In: Proceedings of the Network and Distributed System Security Symposium (NDSS 2005). Podgurski, A., Clarke, L., 1990. A formal model of program dependencies and its implications for software testing, debugging, and maintenance. IEEE Transactions on Software Engineering 16 (9), 965–979. Podgurski, A., 1989. Significance of program dependences for software testing, debugging and maintenance. Ph.D. Thesis. Computer Sc. Dept., U. of Mass. (Sept. 1989). Rothermel, G., Untch, R., Chu, C., Harrold, M.J., 2001. Prioritizing test cases for regression testing. IEEE Transactions on Software Engineering 27 (10), 929–948. Sinha, S., Harrold, M.J., Rothermel, G., 2001. Interprocedural control dependence. ACM Transactions on Software Engineering and Methodology 10 (2), 209–254.
1190
W. Masri, H. Halabi / The Journal of Systems and Software 84 (2011) 1171–1190
Steven, J., Chandra, P., Fleck, B., Podgurski, A.J., 2000. Rapture: A capture/replay tool for observation-based testing. In: Proceedings of the 2000 international Symposium on Software Testing and Analysis, ISSTA 2000. Zhang, X., Gupta, R., Zhang, Y., 2004. Efficient forward computation of dynamic slices using reduced ordered binary decision diagrams. In: Intl. Conf. on Software Engineering , Edinburgh, UK, May. Zhang, X., Gupta, R., Zhang, Y., 2003. Precise dynamic slicing algorithms. In: IEEE/ACM International Conference on Software Engineering , Portland, Oregon, May, pp. 319–329. Zimmermann, J., Mé, L., Bidan, C., 2003. An improved reference flow control model for policy-based intrusion detection. 8th European Symposium on Research in Computer Security (Gjøvik, Norway, October 2003), Lecture Notes in Computer Science 2808, Springer–Verlag. Zimmermann, J., Mé, L., Bidan, C., 2003b. Experimenting with a policy-based HIDS based on and information flow control model. In: 19th Computer Security Applications Conference , Las Vegas, November.
Wes Masri is an Associate Professor in the Electrical and Computer Engineering Department at the American University of Beirut. He received his PhD in Computer Engineering from Case Western Reserve University in 2005, his M.S. in Electrical Engineering from Penn State in 1988 and B.S. in Electrical Engineering also from Case Western Reserve University in 1986. His research interest is in devising software analysis techniques that enhance software testing, software security, and fault localization. He also spent over fifteen years in the software industry mainly as a software architect and developer. Some of the industries he was involved in include: medical imaging, middleware, telecom, genomics, semiconductor, document imaging, and financial. Hiba Halabi is a software engineer employed at Murex S.A.S, Lebanon. She obtained her BS and MS degrees in Computer Science from the American University of Beirut, with a minor in Mathematics. Her research interests include software testing and debugging.