All-Values Symbolic Execution

0 downloads 0 Views 208KB Size Report
at the depended assignments, that is, the logic-or of the assumptions of the if-else-if .... [6] K. L. McMillan, “Lazy annotation for program testing and verification,” in ...
All-Values Symbolic Execution Giovanni Denaro University of Milano-Bicocca Department of Informatics, Systems and Communication Viale Sarca, 336 20126 Milano, Italy [email protected] performance of the analysis. Throughout this paper, we refer to this (traditional) style of symbolic execution as all-paths symbolic execution (APaSE). The number of paths in a program is exponential in the maximum number of the control flow decisions taken by the program along a path. Even a medium size program of few hundreds of lines of code may already contain a massive number (e.g., millions) of program paths, enough to question the viability of APaSE for that program. This problem is well known to people that work with symbolic execution. Often, it leads to approaches that bound the depth (and then the number) of the paths covered by the analysis. Possible bounds can be set on the maximum number of times that an instruction can be revisited, the maximum number of symbolic conditionals that can be tolerated, or the maximum depth of function calls that will be explored from the top-level methods [4]. These embodiments of symbolic execution are referred to as bounded symbolic execution. In fact, bounded symbolic execution trades completeness and/or precision for performance. This paper proposes and elaborates on a novel strategy to accomplish the symbolic execution of a program, which we refer to as all-values symbolic execution (AVaSE). AVaSE tackles the solution of the symbolic execution problem from a radically different perspective than APaSE. While APaSE focuses on unfolding the symbolic states reachable through unrolling the program paths, AVaSE focuses on enumerating the values that derive from the symbolic execution of the program statements. AVaSE tracks the symbolic expressions produced while symbolically executing each statement, collapsing together equal expressions produced in different traversals of a statement; It works with awareness of the (control- and data-) dependencies between the statements, and does not recompute a statement until new values are available at the statements that it depends on. The key insights of AVaSE are that many statements may produce limited sets of values along the possible program paths. The all-values style for symbolic execution entails appealing characteristics in comparison to the all-paths counterpart. AVaSE can straightforwardly optimize the symbolic executions of those statements that produce the same values along multiple paths. The line of reasoning is that the symbolic

Abstract—This paper discusses and exemplifies our ideas on all-values symbolic execution, an alternative strategy to the traditional all-paths style of symbolic execution. All-values symbolic execution focuses on enumerating the (symbolic) values that may derive from the symbolic execution of program statements. It exploits program dependencies to optimize the symbolic execution of those statements that can be executed with the same symbolic inputs on multiple (up to infinite) paths. Although a fully working implementation and a thorough evaluation are yet to come, this paper illustrates with simple, but representative examples that the proposed technique can boost the efficiency of symbolic execution, and suite interesting new applications.

I. I NTRODUCTION Symbolic execution is at the core of many modern approaches to automatic software checking and test generation [1], [2], [3], [4], [5], [6]. In a nutshell, symbolic execution is the process of executing of a program by using symbols instead of numbers as input values [7]. While an ordinary program execution computes program states over concrete values (e.g. integers), symbolic execution summarizes infinite sets of such states as expressions over symbolic input values. The main attraction of symbolic execution lies on the ability to foster precise, though suitably abstracted, explorations of a program’s state space, accounting for flow sensitiveness and pointer aliasing to full extent, while compactly capturing (possibly infinite) sets of program states with the symbolic representation. The strong achievements in the field of automatic solver technologies (e.g. [8]), make it often possible to extract concrete representatives of a symbolic state, and use these to confirm the results of a symbolic execution against the working software with no margin of error. Despite the recent boost of research, modern symbolic executors still experience difficulties to effectively analyze practical software. As a major hinder, symbolic execution induces the exploration of the state space by traversing the executable program paths one by one. A typical instance of this paradigm is visiting all possible (feasible) program paths in depth first order (e.g., [3], [5]). As a consequence, the huge amounts of executable paths that characterize most practical programs strongly affects the convergence and the

c 2012 IEEE 978-1-4673-1822-8/12/$31.00

138

AST 2012, Zurich, Switzerland

execution does not need to be recomputed as long as a statement receives as input the same operands as in previous executions. AVaSE can directly exploit effective program traversals for specific analysis goals, diverting from the execution flows of numeric executions. For example, if we aim to generate test cases that execute specific branches, we will unlikely care about executing any possible combinations of intermediate statements, but the ones that the target branches depend directly or indirectly on. AVaSE may converge even for programs with infinite paths, if symbolic execution results to a bounded set of values at any statement (that is, the same sets of values keep repeating through all executions). This paper exemplifies the ideas of AVaSE on selected, simple but representative programs. It describes the design of an engine for AVaSE implemented as a proof-of-concept prototype at the current the status of our research. It discusses other approaches and emerging results that relate to AVaSE.

even for small programs, the combinations of the possible decision outcomes along the program control flow may cause the exponential growth of the number of paths, which likely challenges the viability and the effectiveness of symbolic execution-based software analyzers. Figure 1.b indicates result of AVaSE on simple. The result consists of a list a values for each statement of simple. Each value is reported as a pair [symb expr, path cond], meaning that the computation of a statement produces the symbolic expression symb expr under the assumptions indicated in the path condition path cond. For instance, for the statement at line 2, AVaSE produces the value [T, T ], which denotes that this statement assigns the symbolic expression true (a constant in this case) to the variable allneg in all cases (path condition equals to true). The path conditions suffixed with a ∗ mark have been cleaned for readability, omitting the assumptions through the if-else-if blocks, which are however straightforward for this example. For instance, at line 11, the value [F, α1 ≥ 0∗ ] represents the program behavior of assigning variable allneg to f alse when α1 ≥ 0 (where α1 is the fresh symbol assigned to variable a1 at line 5), which further depends on entering none of the if-else-if branches at preceding lines. Note that the concrete meaning of producing a value may differ for different types of statements: In our framework, assignment statements produce the values that result from executing the assignments, while if-statements produce the evaluations of the corresponding conditionals. The result of AVaSE in Figure 1 is very concise. It is straightforward and easy to solve the path conditions of all values and generate test cases accordingly (note that all nonshown pieces of path conditions contain clauses over unconstrained symbols only), including the test case that executes the abort statement. AVaSE solves the symbolic execution of simple more efficiently than a traditional symbolic executor, scaling to the generalized versions simplen with increase of complexity linear, rather than exponential in n. In fact, in simple, any code block yields a small set of values independently from the values produced by the other code blocks. The path condition of the abort statement then results from the combination of the values at the last line of each code block. The AVaSE of simple can be completed in one single pass of the code, exploiting the knowledge of the dependencies between the statements in the program as hinted in Section III. The fragment coinbox in Figure 2.a (∗ indicates again non-deterministic values) exemplifies a case in which AVaSE converges for a program with infinite paths. This fragment constructs an object of class CoinBox (adapted from [9]) and then executes an arbitrary sequence of calls to the methods add and vend on this object. The values that result from the symbolic execution of coinbox are showed in Figure 2.b; α, ι, ιι and ιιι are fresh symbols that represent the value passed to the constructor and the evaluations of the

II. T HE C ASE FOR A LL -VALUES S YMBOLIC E XECUTION Whoever has found themselves playing with symbolic execution/executors is surely familiar with the frustration of not being able to complete the execution of programs that may seem easy at a first glance. Let us for example consider the fragment simple in Figure 1.a, where ∗ indicates a non-deterministic value. This program may lead to execute the abort statement at line 23, if variables a1 and a2 are assigned negative values and none of the if-else-if branches is entered. An all-paths symbolic executor can determine the executability of the abort statement by going through the exploration of the paths of the program, until finding the (sole) path where no if-else-if branch is entered and both the conditionals a1 ≥ 0 and a2 ≥ 0 evaluate to false at lines 10 and 19, respectively. Depending on the path selection strategy, this requires exploring up to 36 program paths in the worst case. (Notice that in simple each code block yields 6 possible decision outcomes.) Generalizing the fragment simple for n variables (ai )i=1..n , the number of paths to be explored in the worst case to execute the abort statement is 6n . For instance, with n = 10 the worst case requires executing more than 60 million paths, even though the program amounts to less than 100 lines of code. We ran the analysis of (generalized-)simple using the symbol executor JPF-SE [3]. JPF-SE explores the program paths in depth-first order, and must explore all paths of simple to execute the abort statement. In our environment1 JPF-SE succeeds for simplen=7 in about 10 minutes, while it goes out of memory for simplen>7 . Although its simplicity, this experiment confirms the intuition that, 1 We ran JPF-SE on a MacBook-Pro notebook equipped with Intel Core 2 Duo and 4GB of memory reserved to the Java Virtual Machine where JPF-SE executes.

139

1 2

simple : boolean allneg = t r u e ;

[T , T]

3 4 5 6 7 8 9 10 11

/ / code b l o c k 1 i n t a1 = ∗ ; i f ( ∗ ) a1 = 1 ; e l s e i f ( ∗ ) a1 = 2 ; e l s e i f ( ∗ ) a1 = 3 ; e l s e i f ( ∗ ) a1 = 4 ; i f ( a1 ≥ 0 ) ) allneg = false ;

12 13 14 15 16 17 18 19 20

/ / code b l o c k 2 i n t a2 = ∗ ; i f ( ∗ ) a2 = 1 ; e l s e i f ( ∗ ) a2 = 2 ; e l s e i f ( ∗ ) a2 = 3 ; e l s e i f ( ∗ ) a2 = 4 ; i f ( a2 ≥ 0 ) ) allneg = false ;

21 22 23

if ( allneg ) abort ( ) ;

[ α1 , T ] [ 1 , T∗ ] [ 2 , T∗ ] [ 3 , T∗ ] [ 4 , T∗ ] [ α1 ≥ 0 , T∗ ] [ T , T∗ ] [ F , α1 ≥ 0∗ ] [ α2 , T ] [ 1 , T∗ ] [ 2 , T∗ ] [ 3 , T∗ ] [ 4 , T∗ ] [ α2 ≥ 0 , T∗ ] [ T , T∗ ] [ F , α2 ≥ 0∗ ] [ F , α1 ≥ 0 ∨ α2 ≥ 0∗ ] [ T , ¬(α1 ≥ 0) ∧ ¬(α2 ≥ 0)∗ ] [ − , ¬(α1 ≥ 0) ∧ ¬(α2 ≥ 0)∗ ]

(a)

(b)

T and F stand for the boolean literals true and f alse, respectively. ∗ marks path conditions that have been cleaned for readability. Figure 1.

All-values symbolic execution of fragment simple:

conditional of the if-statement in the first 3 iterations of the loop, respectively; we omit all but one path conditions for readability. All statements of fragment coinbox produce a finite set of values on all possible execution paths, the reason being that method add may increment attribute coin (of type unsigned int) in a limited number of cases, while all other assignments in the program use constant values. AVaSE discovers that iterating the loop more than three time does not produce additional values.

us consider, for example, the dataflow association between line 24 that defines variable count and line 17 that uses that variable. Executing this association amounts to finding a test case that calls method vend in a state that triggers the then-branch of the if statement at line 23, followed by an invocation of method add. When considering the datadependency of the assignment at line 17 from the one at line 24, AVaSE computes the third of the values annotated at the right of line 17, whose (partial) path condition is shown. Indeed solving this path condition yields a test case that executes the association. The values in Figure 2.b cover all feasible dataflow associations of class CoinBox. The infeasible static associations, as for example the association between line 24 (definition) and line 20 (use) that would be computed by a static dataflow analyzer, are not represented in the results of AVaSE since there is no satisfiable pathcondition for these associations.

Next we envision the application of AVaSE for the automatic generation of test cases according to dataflow testing. Dataflow testing amounts to finding test cases to exercise the relations between the program locations that define given values and the program locations that use those values [10]. Dataflow testing is believed to have high potential for software validation of object-oriented software [11]. Dataflow testing entails sophisticated testing requirements that aim to exercise relations between code locations, rather than focusing single locations separately.

Buy et al. describe a technique that automates the generation of test cases to exercise the sequences of method calls needed to satisfy dataflow associations [9]. Their technique works by executing all methods separately with traditional symbolic execution, and then using a constraint solver to match the pre- and post-conditions of different methods that

While symbolically processing the data-dependencies between the program statements, AVaSE computes the execution conditions of the dataflow associations in the code. We sketch this idea based on the code fragment in Figure 2. Let

140

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

coinbox : CoinBox cbox = new CoinBox ( ∗ ) ; while (∗){ if (∗) cbox . add ( ) ; e l s e cbox . vend ( ) ; }

[ ι , T ] [ ιι , T ] [ ιιι , T ]

c l a s s CoinBox { uint coins ; CoinBox ( u i n t c o i n s ) { this . coins = coins ; } i n t add ( ) { i f ( coins < 2){ c o i n s ++; return 0; } e l s e return coi ns +1; } b o o l e a n vend ( ) { i f ( coins > 1){ coins = 0; return true ; } e l s e return f a l s e ; } }

[α , T] [α < 2 , [α + 1 , [0 , ∗] [ α +1 ,







[α > 1 , [0 , ∗] [T, ∗ ] [F ,



] [α < 1 , ] [α + 2 ,

] [ α +2 , ∗



] [α > 0 ,

] [F , ∗ ] [T, ∗ ] ] [ 1 , α > 1 ∧ ¬ι ∧ ιι∗ ] [ 2 ,





] [ α +3 ,



] [3 ,



]

] [T,



] [F ,



]





]

]

(a)

(b)

T and F stand for the boolean literals true and f alse, respectively. Figure 2.

All-values symbolic execution of fragment coinbox

can be involved in chains of dataflow associations. In their paper, they use the class CoinBox in Figure 2 to exemplify the technique. The reader might want to compare that the procedure proposed by Buy et al. is much more laborious than AVaSE, and less resistant to infeasible dataflow associations computed by static analysis either.

a decision point and the decision taken at S0 determines whether or not S1 is necessarily executed. In the fragment if(a1≥0){abort;} the abort statement is controldependent on the preceding if statement. Our engine for AVaSE exploits the knowledge of program dependencies to optimize the symbolic computation of the statements of the program under analysis. Here, we discuss the idea that underlies our method by example, with reference to fragment simple and the expected outcome of AVaSE presented in Figure 1.

III. T OWARDS AN I MPLEMENTATION We are currently designing an engine for AVaSE conceptualized on top of the general framework of program dependency analysis [12]. A program dependency is a relation between a pair of program statements. We classify dependencies as data- or control-dependencies. A statement S1 is data-dependent on a statement S0 , if S1 uses (refers) a memory location defined (assigned) by S0 . For example, in the fragment a1=a0;...;if(a1≥0){...} the if statement is datadependent on the preceding assignment statement. A statement S1 is control-dependent on a statement S0 , if S0 is

Symbolic expression according to data dependencies: To evaluate a statement, say s, AVaSE considers the set S of the statements that s is data-dependent on, and computes the results by symbolically applying the semantic of s according to all and only the values in S. Example: To compute the values that may result from the evaluation of the conditionals of the if statements at the end of each code block (e.g., the one at line 19), the

141

engine can exploit the knowledge that those conditionals are data-dependent on solely the assignments made in the same block. For the i-th code block, those assignments yield either symbol αi or a positive value between 1 and 4, which make the considered conditional evaluate to αi ≥ 0 or true, respectively.

The result is a disjunction between both values from line 19, each conjuncted to its own path condition. Note that computing the path condition of a value requires a combination of the above methods in the general case. All values of figures 1 and 2 and their respective path conditions (including the parts omitted in the figure) can be computed by instantiating the above exemplified methods. We prevent the propagation of infeasible values by assessing the path conditions for satisfiability and discarding values with unsatisfiable path conditions. We rely on state-of-theart SMT solvers for this purpose. Prototype: We are in the process of developing a prototype tool that operates AVaSE of Java programs. It works on top of the dataflow analysis available in the WALA library2 . It performs the symbolic execution in line with the work-list based algorithm of the dataflow analysis. (Dataflow analysis visits the program code in reverse post order, that is, each statement is visited before all its successor statements have been visited, except when the successor is reached by a back edge.) In this way, our prototype can solve the whole set of paths determined by the forward edges of the program control-flow in a single pass of the code. For example, a single pass suffices to complete allvalues symbolic execution for fragment simple showed in Figure 1 (in 6 seconds in the case of simplen=10 ). The prototype has been successfully used to compute AVaSE on simple programs in proof-of-concept fashion. We are currently extending the prototype to handle reference aliasing, inter-procedural flows and loops to full extent, in view of performing extensive experiments with AVaSE.

To determine the path conditions of the values computed at a statement, the engine relies on three different types of information: the path conditions of the depended data, the knowledge of how the assignment statements kill (override) each other, and the control-dependencies of the statement. Path condition according to data dependencies: The path condition of any computed value (logically) specializes the path conditions of the values correspondingly used from data-depended statements. Example: Evaluating the conditional at line 19 to true depends on the data set by the assignments in the ifelse-if branches in the code block. The path conditions of these assignments assume that a specific branch is entered. Computing value true at line 19 assumes that one of those assignments is done. Thus, the corresponding path condition results to the disjunction of the path conditions of the values at the depended assignments, that is, the logic-or of the assumptions of the if-else-if branches. Path condition according to killing relationships: The path condition of any computed value (logically) specializes the negated path conditions at the assignments that may kill the values used from data-depended statements. Example: Evaluating the conditional at line 19 to α2 ≥ 0 depends on the assignment at the beginning of the code block, which has a free path condition (in fact this assignment happens on all execution paths). However, the value set by this assignment is killed if any branch of the subsequent if-else-if block is entered. Therefore, while computing the path condition of value α2 ≥ 0, we need to consider the conjunction of the negated path conditions of the if-elseif branches. Note that it is based on this procedure that the values true and α2 ≥ 0 at line 19 result to having complementary path conditions.

IV. R ELATED W ORK Other recent approaches share with AVaSE the goal of optimizing symbolic execution for handling the exponential amount of paths in the checked programs. Compositional symbolic execution is a method to perform inter-procedural symbolic execution of a program by composing the results from analyzing the single program functions at the intra-procedural level [13], [14]. When symbolically executing a program function along some path, the method incrementally computes a function summary, that is, a record of the already explored paths in the form of the preconditions and the corresponding post-conditions entailed by those paths. Then, upon next calls to that function, it queries an SMT solver on whether the recorded pre-conditions are met by the symbolic states at the call sites, in which cases it exploits the function summary instead of engaging in symbolically re-executing the function. Similarly to compositional symbolic execution, AVaSE leverages the idea of summarizing already explored symbolic states. Though, it works at the granularity of individual program statements, by enumerating the values that result

Path condition according to control dependencies: The path condition of any computed value (logically) specializes the path conditions available from the corresponding controldepended statements. Example: Let us consider the value computed at line 20 (and similarly at the last line of each code block). Such value derives from an assignment to the boolean literal f alse, which neither has associated data dependencies, nor is per-se constrained by any assumption, nor can be killed anyway. Executing line 20 is control-dependent on entering the if statement at line 19. Therefore, the path condition of value f alse at line 20 is set according to the values that make the conditional of the depended if statement evaluate to true.

2 wala.sourceforge.net

142

from the execution of each statement. As an important consequence of this finer granularity, AVaSE can check the subsumption between new and already summarized symbolic states by simply comparing symbolic values for equality, with no need to involve (expensive) SMT solving for this check. (Note that SMT solving can still be required at later stages, e.g., to solve symbolic states to concrete inputs and generate test cases.) AVaSE guards the execution of the program statements according to the control- and data-dependencies. Because of the transitivity of these program-dependencies, AVaSE can foster summarization of state space regions of variable (and lazily determined) size, ranging from small regions (e.g., related to single statements) that will be evaluated several times, to large regions (e.g., related to entire functions or modules) that can be quickly summarized and not analyzed any further. Due to the preliminary status of our implementation of AVaSE, we aim to investigate the concrete weight of the relative strengths and weaknesses between AVaSE and compositional symbolic execution as future work. Overall, we believe that the two methods are amenable to work in complementary and synergic fashion. A number of recent papers present approaches that exploit program dependencies to optimize symbolic execution. These approaches can be generalized in the conceptual framework of AVaSE. Santelices and Harrold present an approach that exploits the transitive closure of the program dependencies with respect to a program location, to compute (on-demand) the symbolic state at specified locations [15]. Their approach partitions the relevant set of program paths into equivalence classes (path families) that require a single symbolic execution per class. Qi et al. achieve similar goals by combining concolic path exploration with the computation of the yet-to-execute relevant slices of the program with respect to the output variables [16]. Boonstoppel et al. presented the method RWset that identifies whether continuing symbolic execution along a suffix of a program path will produce effects already seen in an another (previously explored) path, and dismisses the exploration of such path suffixes [17]. To this end, RWset embodies a (dynamic) analysis of the live variables, based on tracking memory locations defined and used by the checked code. These approaches are all similar in that they partition the program paths based on the dependencies entailed by the outputs of interest. Sinha describes a symbolic execution engine that performs program traversals using a work-list based approach and then joins the symbolic states that reach a same location from different program flows [18]. This approach handles the propagation of the symbolic information at the granularity of whole symbolic states rather than of the values produced at statements. In general, work-list based algorithms can be generalized as abstract interpretation processes converging

on some fix-point [19]. As a final note, there can definitely be cases in which APaSE is a better choice than AVaSE, because of the specific analysis goals that are being pursued. All-paths testing, when viable, is the exemplary case in which traditional symbolic execution can be expected to work better than AVaSE. V. R ESEARCH AGENDA We are currently in the process of extending our prototype engine for AVaSE with the aim of enabling us to thoroughly evaluate the technique on large programs and comparing it with related approaches. We envision great potential for AVaSE to suite interesting applications. Automating dataflow testing is one possible application, as exemplified in this paper. The existing techniques to automatically generate test cases rely on control-flow based abstractions (statements, branches, paths) to exploit the structure of the code, and thus experience difficulties to meet the requirements of dataflow testing. Conversely, AVaSE natively derives the execution conditions of the feasible data dependencies in the code. As another possible application, we aim to investigate whether and to which extent AVaSE may promote subsumption checking between symbolic states that is usually challenging for symbolic execution [20]. Subsumption checking amounts to determining symbolic states that occur multiple times during the execution, such that the traces that originate from those states do not need to be recomputed. AVaSE can facilitate subsumption checking along the lines of the example from Figure 2, by identifying values that recur during the analysis. ACKNOWLEDGMENT This work is partially supported by the European Community under the call FP7-ICT-2009-5 - project PINCETTE 257647. R EFERENCES [1] C. S. Pasareanu and W. Visser, “A survey of new trends in symbolic execution for software testing and analysis,” Journal on Software Tools for Technology Transfer, vol. 11, pp. 339– 353, 2009. [2] N. E. Beckman, A. V. Nori, S. K. Rajamani, R. J. Simmons, S. D. Tetali, and A. V. Thakur, “Proofs from tests,” IEEE Transactions on Software Engineering, vol. 36, no. 4, pp. 495–508, 2010. [3] S. Anand, C. S. Pasareanu, and W. Visser, “JPF-SE: A symbolic execution extension to Java PathFinder,” in International Conference on Tools and Algorithms for the Construction and Analysis of Systems, vol. LNCS 4424, 2007, pp. 134–138. [4] A. Tomb, G. P. Brat, and W. Visser, “Variably interprocedural program analysis for runtime error detection,” in International Symposium on Software Testing and Analysis. ACM Press, 2007.

143

[14] S. Anand, P. Godefroid, and N. Tillmann, “Demand-driven compositional symbolic execution,” in Proceedings of the 14th International Conference on Tools and Algorithms for the Construction and Analysis of Systems (TACAS’08. Springer, 2008.

[5] P. Godefroid, N. Klarlund, and K. Sen, “DART: directed automated random testing,” in nternational Conference on Programming Language Design and Implementation. ACM Press, 2005, pp. 213–223. [6] K. L. McMillan, “Lazy annotation for program testing and verification,” in International Conference on Computer Aided Verification, 2010, pp. 104–118.

[15] R. A. Santelices and M. J. Harrold, “Exploiting program dependencies for scalable multiple-path symbolic execution,” in International Symposium on Software Testing and Analysis, 2010, pp. 195–206.

[7] J. C. King, “Symbolic execution and program testing,” Communications of the ACM, vol. 19, no. 7, pp. 385–394, 1976.

[16] D. Qi, H. D. Nguyen, and A. Roychoudhury, “Path exploration based on symbolic output,” in ACM SIGSOFT Symposium on Foundations of Software Engineering, ser. ESEC/FSE ’11. ACM, 2011, pp. 278–288.

[8] L. D. Moura and N. Bjørner, “Z3: an efficient smt solver,” in Proceedings of the Theory and practice of software, 14th international conference on Tools and algorithms for the construction and analysis of systems, ser. TACAS’08/ETAPS’08. Berlin, Heidelberg: Springer-Verlag, 2008, pp. 337–340.

[17] P. Boonstoppel, C. Cadar, and D. Engler, “Rwset: Attacking path explosion in constraint-based test generation,” in Proceedings of the International Conference on Tools and Algorithms for the Construction and Analysis of Systems (TACAS’08), ser. LNCS, vol. 4963. Springer, 2008, pp. 351– 366.

[9] U. Buy, A. Orso, and M. Pezz´e, “Automated testing of classes,” in International Symposium on Software Testing and Analysis, 2000, pp. 39–48. [10] P. G. Frankl and E. J. Weyuker, “An applicable family of data flow testing criteria,” IEEE Transactions on Software Engineering, vol. 14, pp. 1483–1498, October 1988.

[18] N. Sinha, “Symbolic program analysis using term rewriting and generalization,” in International Conference on Formal Methods in Computer-Aided Design. IEEE Press, 2008, pp. 19:1–19:9.

[11] M. J. Harrold and G. Rothermel, “Performing data flow testing on classes,” in Proceedings of the 2nd ACM SIGSOFT Symposium on Foundations of Software Engineering, ser. FSE’94. ACM, 1994, pp. 154–163.

[19] D. A. Schmidt, “Data flow analysis is model checking of abstract interpretations,” in Proceedings of the 25th ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages, ser. POPL ’98. ACM, 1998, pp. 38–48.

[12] J. Ferrante, K. J. Ottenstein, and J. D. Warren, “The program dependence graph and its use in optimization,” ACM Transactions on Programming Languages and Systems, vol. 9, 1987.

[20] W. Visser, C. S. Pˇasˇareanu, and R. Pel´anek, “Test input generation for java containers using state matching,” in International Symposium on Software Testing and Analysis. ACM Press, 2006, pp. 37–48.

[13] P. Godefroid, “Compositional dynamic test generation,” in Proceedings of the 34th annual ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages, ser. POPL ’07. ACM, 2007, pp. 47–54.

144