Keywords

1 Introduction

Synthesis is the task of generating a program that meets a required specification. Verification is the task of validating program correctness with respect to a given specification. Both are long-standing problems in computer science, although there has been extensive work on program verification and comparatively less on program synthesis until recently. Over the past several years, certain verification techniques have been adopted to create programs, e.g., applying symbolic execution to synthesize program repairs [25, 26, 29, 32], suggesting the possibility that these two problems may be “two sides of the same coin”. Finding and formalizing this equivalence is valuable in both theory and practice: it allows comparisons between the complexities and underlying structures of the two problems, and it raises the possibility of additional cross-fertilization between two fields that are usually treated separately (e.g., it might enable approximations designed to solve one problem to be applied directly to the other).

This paper establishes a formal connection between certain formulations of program synthesis and verification. We focus on the template-based synthesis problem, which generates missing code for partially completed programs, and we view verification as a reachability problem, which checks if a program can reach an undesirable state. We then constructively prove that template-based synthesis and reachability are equivalent. We reduce a template-based synthesis problem, which consists of a program with parameterized templates to be synthesized and a test suite specification, to a program consisting of a specific location that is reachable only when those templates can be instantiated such that the program meets the given specification. To reduce reachability to synthesis, we transform a reachability instance consisting of a program and a given location into a synthesis instance that can be solved only when the location in the original problem is reachable. Thus, reachability solvers can be applied to synthesize code, and conversely, synthesis tools can be used to determine reachability.

To demonstrate the equivalence, we use the reduction to develop a new automatic program repair technique using an existing test-input generation tool. We view program repair as a special case of template-based synthesis in which “patch” code is generated so that it behaves correctly. We present a prototype tool called CETI that automatically repairs C programs that violate test-suite specifications. Given a test suite and a program failing at least one test in that suite, CETI first applies fault localization to obtain a list of ranked suspicious statements from the buggy program. For each suspicious statement, CETI transforms the buggy program and the information from its test suite into a program reachability instance. The reachability instance is a new program containing a special if branch, whose then branch is reachable only when the original program can be repaired by modifying the considered statement. By construction, any input value that allows the special location to be reached can map directly to a repair template instantiation that fixes the bug. To find a repair, CETI invokes an off-the-shelf automatic test-input generation tool on the transformed code to find test values that can reach the special branch location. These values correspond to changes that, when applied to the original program, cause it to pass the given test suite. This procedure is guaranteed to be sound, but it is not necessarily complete. That is, there may be bugs that the procedure cannot find repairs for, but all proposed repairs are guaranteed to be correct with respect to the given test suite. We evaluated CETI on the Tcas program [13], which has 41 seeded defects, and found that it repaired over 60%, which compares favorably with other state-of-the-art automated bug repair approaches.

To summarize, the main contributions of the paper include:

  • Equivalence Theorem: We constructively prove that the problems of template-based program synthesis and reachability in program verification are equivalent. Even though these two problems are shown to be undecidable in general, the constructions allow heuristics solving one problem to be applied to the other.

  • Automatic Program Repair: We present a new automatic program repair technique, which leverages the construction. The technique reduces the task of synthesizing program repairs to a reachability problem, where the results produced by a test-input generation tool correspond to a patch that repairs the original program.

  • Implementation and Evaluation: We implement the repair algorithm in a prototype tool that automatically repairs C programs, and we evaluate it on a benchmark that has been targeted by multiple program repair algorithms.

2 Motivating Example

We give a concrete example of how the reduction from template-based synthesis to reachability can be used to repair a buggy program. Consider the buggy code shown in Fig. 1, a function excerpted from a traffic collision avoidance system [13]. The intended behavior of the function can be precisely described as: is_upward(in,up,down) = in*100 + up  > down. The table in Fig. 1 gives a test suite describing the intended behavior. The buggy program fails two of the tests, which we propose to repair by synthesizing a patch.

Fig. 1.
figure 1

Example buggy program and test suite. CETI suggests replacing line 4 with the statement bias = up + 100; to repair the bug.

We solve this synthesis problem by restricting ourselves to generating patches under predefined templates, e.g., synthesizing expressions involving program variables and unknown parameters, and then transforming this template-based synthesis problem into a reachability problem instance. In this approach, a template such as

is a linear combinationFootnote 1 of program variables \(v_i\) and unknown template parameters . For clarity, we often denote template parameters with a box to distinguish them from normal program elements. This template can be instantiated to yield concrete expressions such as \(200 + 3v_1 + 4v_2\) via \(c_0=200, c_1=3, c_2=4\). To repair Line 4 of Fig. 1, (bias = down;) with a linear template, we would replace Line 4 with:

where bias, in, up, and down are the variables in scope at Line 4 and the value of each \(c_i\) must be found. We propose to find them by constructing a special program reachability instance and then solving that instance.

The construction transforms the program, its test suite (Fig. 1), and the template statement into a reachability instance consisting of a program and target location. The first key idea is to derive a new program containing the template code with the template parameters represented explicitly as program variables \(c_i\). This program defines the reachability instance, which must assign values to each \(c_i\). The second key idea is that each test case is explicitly represented as a conditional expression. Recall that we seek a single synthesis solution (one set of values for \(c_i\)) that respects all tests. Each test is encoded as a conditional expression (a reachability constraint), and we take their conjunction, being careful to refer to the same \(c_i\) variables in each expression. In the example, we must find one repair that satisfies all six tests, not six separate repairs that each satisfy only one test.

Fig. 2.
figure 2

The reachability problem instance derived from the buggy program and test suite in Fig. 1. Location L is reachable with values such as \(c_0=100,c_1=0,c_2=0,c_3=1,c_4=0\). These values suggest using the statement bias = 100 + up; at Line 4 in the buggy program.

The new program, shown in Fig. 2, contains a function is_upward \(_P\) that resembles the function is_upward in the original code but with Line 4 replaced by the template statement with each reference to a template parameter replaced by a reference to the corresponding new externally-defined program variable. The program also contains a starting function main, which encodes the inputs and expected outputs from the given test suite as the guards to the conditional statement leading to the target location L. Intuitively, the reachability problem instance asks if we can find values for each \(c_i\) that allow control flow to reach location L, which is only reachable iff all tests are satisfied.

This reachability instance can be given as input to any off-the-self test-input generation tool. Here, we use KLEE [8] to find value for each \(c_i\). KLEE determines that the values \(c_0 = 100, c_1 =0, c_2 =0, c_3=1, c_4=0\) allow control flow to reach location L. Finally, we map this solution back to the original program repair problem by applying the \(c_i\) values to the template

generating the statement:

which reduces to bias = 100 + up. Replacing the statement bias = down in the original program with the new statement bias = 100 + up produces a program that passes all of the test cases.

To summarize, a specific question (i.e., can the bug be repaired by applying template X to line Y of program P while satisfying test suite T?) is reduced to a single reachability instance, solvable using a reachability tool such as a test-input generator. This reduction is formally established in the next section.

3 Connecting Program Synthesis and Reachability

We establish the connection between the template-based formulation of program synthesis and the reachability problem in program verification. We first review these problems and then show their equivalence.

3.1 Preliminaries

We consider standard imperative programs in a language like C. The base language includes usual program constructs such as assignments, conditionals, loops, and functions. A function takes as input a (potentially empty) tuple of values and returns an output value. A function can call other functions, including itself. For simplicity, we equate a program P with its finite set of functions, including a special starting function \(\text {main}_P\). For brevity, we write \(P(c_i,\dots ,c_n)=y\) to denote that evaluating the function \(\text {main}_P \in P\) on the input tuple \((c_i,\dots ,c_n)\) results in the value y. Program or function semantics are specified by a test suite consisting of a finite set of input/output pairs. When possible, we use \(c_i\) for concrete input values and \(v_i\) for formal parameters or variable names.

To simplify the presentation, we assume that the language also supports exceptions, admitting non-local control flow by raising and catching exceptions as in modern programming languages such as C++ and Java. We discuss how to remove this assumption in Sect. 3.3.

Template-Based Program Synthesis. Program synthesis aims to automatically generate program code to meet a required specification. The problem of synthesizing a complete program is generally undecidable [42], so many practical synthesis techniques operate on partially-complete programs, filling in well-structured gaps [1, 36, 39, 41, 43, 44]. These techniques synthesize programs from specific grammars, forms, or templates and do not generate arbitrary code. A synthesis template expresses the shape of program constructs, but includes holes (sometimes called template parameters), as illustrated in the previous section. We refer to a program containing such templates as a template program and extend the base language to include a finite, fixed set of template parameters as shown earlier. Using the notation of contextual operational semantics, we write \(P[c_0, \dots , c_n]\) to denote the result of instantiating the template program P with template parameter values \(c_0 \dots c_n\). To find values for the parameters in a template program, many techniques (e.g., [1, 41, 43, 44]) encode the program and its specification as a logical formula (e.g., using axiomatic semantics) and use a constraint solver such as SAT or SMT to find values for the parameters \(c_i\) that satisfy the formula. Instantiating the templates with those values produces a complete program that adheres to the required specification.

Definition 1

Template-based Program Synthesis Problem. Given a template program Q with a finite set of template parameters and a finite test suite of input/output pairs \(T=\{ (i_1,o_1), \dots , (i_m,o_m) \}\), do there exist parameter values \(c_i\) such that \(\forall (i,o) \in T ~.~ (Q[c_1, \dots , c_n])(i) = o\)?

For example, the program in Fig. 1 with Line 4 replaced by is an instance of template-based synthesis. This program passes its test suite given in Fig. 1 using the solution \(\{c_0=100,c_1=1,c_2=0,c_3=1,c_4=0\}\). The decision formulation of the problem asks if satisfying values \(c_1 \dots c_n\) exist; in this presentation we require that witnesses be produced.

Program Reachability. Program reachability is a classic problem which asks if a particular program state or location can be observed at run-time. It is not decidable in general, because it can encode the halting problem (cf. Rice’s Theorem [35]). However, reachability remains a popular and well-studied verification problem in practice. In model checking [10], for example, reachability is used to determine whether program states representing undesirable behaviors could occur in practice. Another application area is test-input generation [9], which aims to produce test values to explore all reachable program locations.

Definition 2

Program Reachability Problem. Given a program P, set of program variables \(\{ x_1,\dots ,x_n \}\) and target location L, do there exist input values \(c_i\) such that the execution of P with \(x_i\) initialized to \(c_i\) reaches L in a finite number of steps?

Fig. 3.
figure 3

An instance of program reachability. Program P reaches location L using the solution \(\{x=-20,y=-40\}\).

Fig. 4.
figure 4

Reducing the reachability example in Fig. 3 to a template-based synthesis program (i.e., synthesize assignments to \(c_x\) and \(c_y\)). The test suite of the reduced synthesis program is \(Q() = 1\).

For example, the program in Fig. 3 has a reachable location L using the solution \(\{x=-20,y=-40\}\). Similar to the synthesis problem, the decision problem formulation of reachability merely asks if the input values \(c_1,\dots ,c_n\) exist; in this presentation we require witnesses be produced.

3.2 Reducing Synthesis to Reachability

We present the constructive reduction from synthesis to reachability. The key to the reduction is a particular “gadget”, which constructs a reachability instance that can be satisfied iff the synthesis problem can be solved.

Reduction: Let Q be a template program with a set of template parameters and a set of finite tests \(T=\{ (i_1,o_1), \dots \}\). We construct \(\mathsf {GadgetS2R}(Q,S,T)\), which returns a new program P (the constructed reachability instance) with a special location L, as follows:

  1. 1.

    For every template parameter , add a fresh global variable \(v_i\). A solution to this reachability instance is an assignment of concrete values \(c_i\) to the variables \(v_i\).

  2. 2.

    For every function \(q\in Q\), define a similar function \(q_P \in P\). The body of \(q_P\) is the same as q, but with every reference to a template parameter replaced with a reference to the corresponding new variable \(v_i\).

  3. 3.

    P also contains a starting function main\(_P\) that encodes the specification information from the test suite T as a conjunctive expression e:

    $$\begin{aligned} e = \bigwedge _{(i,o) \, \in \, T} \text {main}_Q{_P}(i) = o \end{aligned}$$

    where main\(_Q{_P}\) is a function in P corresponding to the starting function main\(_Q\) in Q. In addition, the body of main\(_P\) is one conditional statement leading to a fresh target location L if and only if e is true. Thus, main\(_P\) has the form

    figure a
  4. 4.

    The derived program P consists of the declaration of the new variables (Step 1), the functions \(q_P\)’s (Step 2), and the starting function main\(_P\) (Step 3).

Example:

Figure 2 illustrates the reduction using the example from Fig. 1. The resulting reachability program can arrive at location L using the input \(\{c_0=100,c_1=0,c_2=0,c_3=1,c_4=0\}\), which corresponds to a solution.

Reduction Correctness and Complexity: The correctness of \(\mathsf {GadgetS2R}\), which transforms synthesis to reachability, relies on two key invariantsFootnote 2. First, function calls in the derived program P have the same behavior as template functions in the original program Q. Second, location L is reachable if and only if values \(c_i\) can be assigned to variables \(v_i\) such that Q passes all of the tests.

The complexity of \(\mathsf {GadgetS2R}\) is linear in both the program size and number of test cases of the input instance QST. The constructed program P consists of all functions in Q (with |S| extra variables) and a starting function main\(_P\) with an expression encoding the test suite T.

This reduction directly leads to the main result for this direction of the equivalence:

Theorem 1

The template-based synthesis problem in Definition 1 is reducible to the reachability problem in Definition 2.

3.3 Reducing Reachability to Synthesis

Here, we present the reduction from reachability to synthesis. The reduction also uses a particular gadget to construct a synthesis instance that can be solved iff the reachability instance can be determined.

Reduction: Let P be a program, L be a location in P, and \(V = \{v_1, \dots , v_n\}\) be global variables never directly assigned in P. We construct \(\mathsf {GadgetR2S}(P,L,V)\), which returns a template program Q with template parameters S and a test suite T, as follows:

  1. 1.

    For every variable \(v_i\), define a fresh template variable . Let the set of template parameters S be the set containing each .

  2. 2.

    For every function \(p \in P\), define a derived function \(p_Q \in Q\). Replace each function call to p with the corresponding call to \(p_Q\). Replace each use of a variable \(v_i\) with a read from the corresponding template parameter ; remove all declarations of variables \(v_i\).

  3. 3.

    Raise a unique exception REACHED, at the location in Q corresponding to the location L in P. As usual, when an exception is raised, control immediately jumps to the most recently-executed try-catch block matching that exception. The exception REACHED will be caught iff the location in Q corresponding to \(L \in P\) would be reached.

  4. 4.

    Define a starting function main\(_Q\) that has no inputs and returns an integer value. Let main\(_P{_Q}\) be the function in Q corresponding to the starting function main\(_P\) in P.

    • Insert try-catch construct that calls \(p_Q\) and returns the value 1 if the exception REACHED is caught.

    • At the end of main\(_Q\), return the value 0.

    • Thus, main\(_Q\) has the form

      figure b
  5. 5.

    The derived program Q consists of the finite set of template parameters (Step 1), functions \(p_Q\)’s (Step 2), and the starting function main\(_Q\) (Step 4).

  6. 6.

    The test suite T for Q consists of exactly one test case \(Q() = 1\), indicating the case when the exception REACHED is raised and caught.

Example:

Figure 4 illustrates the reduction using the example from Fig. 3. The synthesized program can be satisfied by \(c_0=-20,c_1=-40\), corresponding to the input (\(x=-20,y=-40\)) which reaches L in Fig. 3.

The exception REACHED represents a unique signal to main\(_Q\) that the location L has been reached. Many modern languages support exceptions for handling special events, but they are not strictly necessary for the reduction to succeed. Other (potentially language-dependent) implementation techniques could also be employed. Or, we could use a tuple to represent the signal, e.g., returning \((v, \mathsf {false})\) from a function that normally returns v if the location corresponding L has not been reached and \((1, \mathsf {true})\) as soon as it has. BLAST [6], a model checker for C programs (which do not support exceptions), uses goto and labels to indicate when a desired location has been reached.

Reduction Correctness and Complexity: The correctness of the \(\mathsf {GadgetS2R}\), which transforms reachability to synthesis, depends on two key invariantsFootnote 3. First, for any \(c_i\), execution in the derived template program Q with mirrors execution in P with \(v_i \mapsto c_i\) up to the point when L is reached (if ever). Second, the exception REACHED is raised in Q iff location L is reachable in P.

The complexity of \(\mathsf {GadgetR2S}\) is linear in the input instance \(P,L,v_i\). The constructed program Q consists of all functions in P and a starting function mainQ having n template variables, where \(n = |\{v_i\}|\).

This reduction directly leads to the main result for this direction of the equivalence:

Theorem 2

The reachability problem in Definition 2 is reducible to the template-based synthesis problem in Definition 1.

3.4 Synthesis \(\equiv \) Reachability

Together, the above two theorems establish the equivalence between the reachability problem in program verification and the template-based program synthesis.

Corollary 1

The reachability problem in Definition 2 and the template-based synthesis problem in Definition 1 are linear-time reducible to each other.

This equivalence is perhaps unsurprising as researchers have long assumed certain relations between program synthesis and verification (e.g., see Sect. 5). However, we believe that a proof of the equivalence is valuable. First, our proof, although straightforward, formally shows that both problems inhabit the same complexity class (e.g., the restricted formulation of synthesis in Definition 1 is as hard as the reachability problem in Definition 2). Second, although both problems are undecidable in the general case, the linear-time transformations allow existing approximations and ideas developed for one problem to apply to the other one. Third, in term of practicality, the equivalence allows for direct application of off-the-shelf reachability and verification tools to synthesize and repair programs. Our approach is not so different from verification works that transform the interested problems into SAT/SMT formulas to be solved by existing efficient solvers. Finally, this work can be extended to more complex classes of synthesis and repair problems. While we demonstrate the approach using linear templates, more general templates can be handled. For example, combinations of nonlinear polynomials can be considered using a priority subset of terms (e.g., \(t_1=x^2, t_2=xy\), as demonstrated in nonlinear invariant generation [33]).

We hope that these results help raise fruitful cross-fertilization among program verification and synthesis fields that are usually treated separately. Because our reductions produce reachability problem instances that are rarely encountered by current verification techniques (e.g., with large guards), they may help refine existing tools or motivate optimizations in new directions. As an example, our bug repair prototype CETI (discussed in the next Section) has produced reachability instances that hit a crashing bug in KLEE that was confirmed to be important by the developersFootnote 4. These hard instances might be used to evaluate and improve verification and synthesis tools (similar to benchmarks used in annual SATFootnote 5 and SMTFootnote 6 competitions).

4 Program Repair Using Test-Input Generation

We use the equivalence to develop CETI (Correcting Errors using Test Inputs), a tool for automated program repair (a synthesis problem) using test-input generation techniques (which solves reachability problems). We define problem of program repair in terms of template-based program synthesis:

Definition 3

Program Repair Problem. Given a program Q that fails at least one test in a finite test suite T and a finite set of parameterized templates S, does there exist a set of statements \(\{s_i\} \subseteq Q\) and parameter values \(c_1, \dots , c_n\) for the templates in S such that \(s_i\) can be replaced with \(S[c_1, \dots , c_n]\) and the resulting program passes all tests in T?

This repair problem thus allows edits to multiple program statements (e.g., we can replace both lines 4 and 10 in Fig. 1 with parameterized templates). The single-edit repair problem restricts the edits to one statement.

CETI implements the key ideas from Theorem 1 in Sect. 3.2 to transform this repair problem into a reachability task solvable by existing verification tools. Given a test suite and a buggy program that fails some test in the suite, CETI employs the statistical fault localization technique Tarantula [23] to identify particular code regions for synthesis, i.e., program statements likely related to the defect. Next, for each suspicious statement and synthesis template, CETI transforms the buggy program, the test suite, the statement and the template into a new program containing a location reachable only when the original program can be repaired. Thus, by default CETI considers single-edit repairs, but it can be modified to repair multiple lines by using k top-ranked suspicious statements (cf. Angelix [29]). Such an approach increases the search space and thus the computational burden placed on the reachability solver.

Our current implementation employs CIL [31] to parse and modify C programs using repair templates similar to those given in [25, 32]. These templates allow modifying constants, expressions (such as the linear template shown in Sect. 2), and logical, comparisons, and arithmetic operators (such as changing || to &&, \(\le \) to <, or + to −). Finally, we send the transformed program to the test-input generation tool KLEE, which produces test values that can reach the designated location. Such test input values, when combined with the synthesis template and the suspicious statement, correspond exactly to a patch that repairs the bug. CETI synthesizes correct-by-construction repairs, i.e., the repair, if found, is guaranteed to pass the test suite.

4.1 Evaluation

To evaluate CETI, we use the Tcas program from the SIR benchmark [13]. The program, which implements an aircraft traffic collision avoidance system, has 180 lines of code and 12 integer inputs. The program comes with a test suite of about 1608 tests and 41 faulty functions, consisting of seeded defects such as changed operators, incorrect constant values, missing code, and incorrect control flow. Among the programs in SIR, Tcas has the most introduced defects (41), and it has been used to benchmark modern bug repair techniques [12, 26, 32].

We manually modify Tcas, which normally prints its result on the screen, to instead return its output to its caller, e.g., becomes return v. For efficiency, many repair techniques initially consider a smaller number of tests in the suite and then verify candidate repairs on the entire suite [32]. In contrast, we use all available tests at all times to guarantee that any repair found by CETI is correct with respect to the test suite. We find that modern tools such as KLEE can handle the complex conditionals that encode such information efficiently and generate the desired solutions within seconds.

The behavior of CETI is controlled by customizable parameters. For the experiments described here, we consider the top \(n=80\) from the ranked list of suspicious statements and, then apply the predefined templates to these statements. For efficiency, we restrict synthesis parameters to be within certain value ranges: constant coefficients are confined to the integral range \([-100000, 100000]\) while the variable coefficients are drawn from the set \(\{-1,0,1\}\).

Results. Table 1 shows the results with 41 buggy Tcas versions. These experiments were performed on a 32-core 2.60 GHz Intel Linux system with 128 GB of RAM. Column Bug Type describes the type of defect. Incorrect Const denotes a defect involving the use of the wrong constant, e.g., 700 instead of 600. Incorrect Op denotes a defect that uses the wrong operator for arithmetic, comparison, or logical calculations, e.g., \(\ge \) instead of >. Missing code denotes defects that entirely lack an expression or statement, e.g., \( a \& \& b\) instead of \( a \& \& b||c\) or return a instead of return a+b. Multiple denotes defects caused by several actions such as missing code at a location and using an incorrect operator at another location. Column T(s) shows the time taken in seconds. Column R-Prog lists the number of reachability program instances that were generated and processed by KLEE. Column Repair? indicates whether a repair was found.

Table 1. Repair results for 41 Tcas defects

We were able to correct 26 of 41 defects, including multiple defects of different types. On average, CETI takes 22 seconds for each successful repair. The tool found 100% of repairs for which the required changes are single edits according to one of our predefined templates (e.g., generating arbitrary integer constants or changing operators at one location). In several cases, defects could be repaired in several ways. For example, defect \(v_{28}\) can be repaired by swapping the results of both branches of a conditional statement or by inverting the conditional guard. CETI also obtained unexpected repairs. For example, the bug in \(v_{13}\) is a comparison against an incorrect constant; the buggy code reads while the human-written patch reads . Our generated repair of also passes all tests.

We were not able to repair 15 of 41 defects, each of which requires edits at multiple locations or the addition of code that is beyond the scope of the current set of templates. As expected, CETI takes longer for these programs because it tries all generated template programs before giving up. One common pattern among these programs is that the bug occurs in a macro definition, e.g., #define C = 100 instead of #define C = 200. Since the CIL front end automatically expands such macros, CETI would need to individually fix each use of the macro in order to succeed. This is an artifact of CIL, rather than a weakness inherent in our algorithm.

CETI, which repairs 26 of 41 Tcas defects, performs well compared to other reported results from repair tools on this benchmark program. GenProg, which finds edits by recombining existing code, can repair 11 of these defects [32, Table 5]. The technique of Debroy and Wong, which uses random mutation, can repair 9 defects [12, Table 2]. FoREnSiC, which uses the concolic execution in CREST, repairs 23 defects [26, Table 1]. SemFix out-performs CETI, repairing 34 defects [32, Table 5], but also uses fifty test cases instead of the entire suite of thousandsFootnote 7. Other repair techniques, including equivalence checking [26] and counterexample guided refinement [26], repair 15 and 16 defects, respectively.

Although CETI uses similar repair templates as both SemFix and FoREnSiC, the repair processes are different. SemFix directly uses and customizes the KLEE symbolic execution engine, and FoRenSiC integrates concolic execution to analyze programs and SMT solving to generate repairs. In contrast, CETI eschews heavyweight analyses, and it simply generates a reachability instance. Indeed, our work is inspired by, and generalizes, these works, observing that the whole synthesis task can be offloaded with strong success in practice.

However, there is a trade-off: customizing a reachability solver to the task of program repair may increase the performance or the number of repairs found, but may also reduce the generality or ease-of-adoption of the overall technique. We note that our unoptimized tool CETI already outperforms published results for GenProg, Debroy and Wong, and FoREnSiC on this benchmark, and is competitive with SemFix.

Limitations. We require that the program behaves deterministically on the test cases and that the defect be reproducible. This limitation can be mitigated by running the test cases multiple times, but ultimately our technique is not applicable if the program is non-deterministic. We assume that the test cases encode all relevant program requirements. If adequate test cases are not available then the repair may not retain required functionality. Our formulation also encodes the test cases as inputs to a starting function (e.g., main) with a single expected output. This might not be feasible for certain types of specifications, such as liveness properties (“eventually” and “always”) in temporal logic. The efficiency of CETI depends on fault localization to reduce the search space. The reachability or test-input generation tool used affects both the efficiency and the efficacy of CETI. For example, if the reachability tool uses a constraint solver that does not support data types such as string or arrays then we will not be able to repair program defects involving those types. Finally, we assume that the repair can be constructed from the provided repair templates.

The reduction in Sect. 3.2 can transform a finite space (buggy) program into an infinite space reachability problem (e.g., we hypothesize that a bounded loop guard \(i \le 10\) is buggy and try to synthesize a new guard using an unknown parameter ). However, this does not invalidate the theoretical or empirical results and the reduction is efficient in the program size and the number of tests. The reduction also might not be optimal if we use complex repair templates (e.g., involving many unknown parameters). In practice we do not need to synthesize many complex values for most defects and thus modern verification tools such as KLEE can solve these problems efficiently, as shown in our evaluation.

This paper concretely demonstrates the applicability of program reachability (test-input generation) to program synthesis (defect repair) but not the reverse direction of using program synthesis to solve reachability. Applying advances in automatic program repair to find test-inputs to reach nontrivial program locations remains future work.

5 Related Work

Program Synthesis and Verification. Researchers have long hypothesized about the relation between program synthesis and verification and proposed synthesis approaches using techniques or tools often used to verify programs such as constraint solving or model checking [1, 43]. For example, Bodik and Solar-Lezama et al.’s work [39, 40] on sketching defines the synthesis task as: \(\exists c~.~\forall (i,o)~.~ \in T ~.~ (P[c])(i) = o\) (similar to our template-based synthesis formulation in Definition 1) and solves the problem using a SAT solver. Other synthesis and program repair researches, e.g., [4, 29, 32, 43, 44], also use similar formulation to integrate verification tools, e.g., test-input generation, to synthesize desired programs. In general, such integrations are common in many ongoing synthesis works including the multi-disciplinary ExCAPE project [14] and the SyGuS competition [45], and have produced many practical and useful tools such as Sketch that generates low-level bit-stream programs [39], Autograder that provides feedback on programming homework [38], and FlashFill that constructs Excel macros [19, 20].

The work presented in this paper is inspired by these works, and generalizes them by establishing a formal connection between synthesis and verification using the template-based synthesis and reachability formulations. We show that it is not just a coincident that the aforementioned synthesis works can exploit verification techniques, but that every template-based synthesis problem can be reduced to the reachability formulation in verification. Dually, we show the other direction that reduces reachability to template-based synthesis, so that every reachability problem can be solved using synthesis. Furthermore, our constructive proofs describe efficient algorithms to do such reductions.

Program Repair and Test-Input Generation. Due to the pressing demand for reliable software, automatic program repair has steadily gained research interests and produced many novel repair techniques. Constraint-based repair approaches, e.g., AFix [21], Angelix [29], SemFix [32], FoRenSiC [7], Gopinath et al. [18], Jobstmann et al. [22], generate constraints and solve them for patches that are correct by construction (i.e., guaranteed to adhere to a specification or pass a test suite). In contrast, generate-and-validate repair approaches, e.g., GenProg [46], Pachika [11], PAR [24], Debroy and Wong [12], Prophet [28], find multiple repair candidates (e.g., using stochastic search or invariant inferences) and verify them against given specifications.

The field of test-input generation has produced many practical techniques and tools to generate high coverage test data for complex software, e.g., fuzz testing [15, 30], symbolic execution [8, 9], concolic (combination of static and dynamic analyses) execution [16, 37], and software model checking [5, 6]. Companies and industrial research labs such as Microsoft, NASA, IBM, and Fujitsu have also developed test-input generation tools to test their own products [2, 3, 17, 27]. Our work allows program repair and synthesis approaches directly apply these techniques and tools.

6 Conclusion

We constructively prove that the template-based program synthesis problem and the reachability problem in program verification are equivalent. This equivalence connects the two problems and enables the application of ideas, optimizations, and tools developed for one problem to the other. To demonstrate this, we develop CETI, a tool for automated program repair using test-input generation techniques that solve reachability problems. CETI transforms the task of synthesizing program repairs to a reachability problem, where the results produced by a test-input generation tool correspond to a patch that repairs the original program. Experimental case studies suggest that CETI has higher success rates than many other standard repair approaches.