• No results found

Fault locating by predicate switching in Rascal

N/A
N/A
Protected

Academic year: 2021

Share "Fault locating by predicate switching in Rascal"

Copied!
26
0
0

Bezig met laden.... (Bekijk nu de volledige tekst)

Hele tekst

(1)

                  Amir Cohen  10686835  10686835@student.uva.nl  June 13, 2016              Internal supervisor: Paul Griffioen, CWI  External supervisor: Femke van Raamsdonk, VU                         

(2)

Contents 

  Abstract  1. Introduction  1.1. Background  1.2. The predicate switching mechanism  2. Research question  3. Method  4. The Original Studies  4.1. Contribution of the Original Study  4.2. Implementation  4.3 Limitations  4.4 Experimentation and Evaluation of the Results  5. The Replication Study  5.1 The Predicate Switching tool  5.1.1. The Role of the Rascal Language  5.1.2 Design of the Predicate Switching Tool  5.1.3. Implementation  5.2. Comparison between our Rascal tool and the tool built by Zhang et al.  6. Experimentation  6.1 The testing suite  6.2. Faults injection  7. Results  8. Related Approaches  9. Threats to validity  10. Future Work  11. Conclusions  12. Bibliography               

(3)

 

Fault locating by predicate switching in Rascal 

   

 

Abstract 

   

The predicate switching method is a heuristic way for locating faults, which was presented by        Zhang et. al. In this method the outcome of executed branching predicate instances is        automatically being switched during re­executions of a failing program, looking for a single        critical predicate instance whose switching results in a successful execution. By examining this        predicate instance, the cause of the bug can then be identified. In this work we analyse the        effectiveness of predicate switching. To this end we build a predicate switching tool in Rascal,        usings Rascal’s M3 meta model for source­code analysis. We then run our tool on a testing        suite of 18 Java programs (in comparison to Zhang et. al who used C programs) with injected        faults that we have composed, and we present an analysis of the observations. We found that        predicate switching is unable to locate faults if multiple instances of executed predicates are        infected by them. We provide an analysis of the effect of predicate switching in these cases in        order to explain why it fails to locate them. Our work serves also as a demonstration of using        Rascal to implement dynamic analysis tools. As Rascal’s M3 model is language independent,        the implementation could be adjusted to support other languages than Java or other language        elements. 

   

Keywords: fault localisation, program slice        branching predicate, predicate trace,        predicate instance, critical predicate, predicate switching, Rascal 

 

(4)

1. Introduction 

 

1.1. Background 

 

During the process of software development, faults are likely to occur. Some faults can directly        be identified with static analysis by, for example, the parser or the compiler. Other faults that        can not be found with static analysis, might only be identified with dynamic analysis during the        program’s execution.  

 

Such faults that cannot be identified in a static way, can only become visible at a certain point        of the execution. The cause of the observed fault however might arise at a point in the        execution earlier to the point at which it becomes observable. The propagation of the fault to        the rest of the program is referred to as the infection chain [4]. Once a fault is observed at some        point, it is the programmer’s mission to trace back in the execution to find the origin of the        infection chain. This process is often done by manual interventions during the program´s        execution, and examining different hypotheses in order to explore and isolate the possible        cause of a fault. This process can be very time consuming. 

 

Different search based approaches have been proposed during the years [4­10] that aim at        automating and accelerating this process. These approaches, however, are often based on        exhaustive exploration of enormous search spaces of all combinations of possible states. This        makes it often impossible to compute both sound and reasonably precise (i.e., nearly complete)        analysis for such problems. 

 

Considering these limitations of the two approaches to debugging ­ manual versus automated,        the option of combining the search power of the computer with the acquaintance of the        programmer with the system could be interesting to investigate. Zhang et. al [1] propose in        their work a heuristical approach for locating faults which is based on automated predicate        switching. They argue that this approach is effective at efficiently locating the predicate in a        program which is related to the fault and thus may save time in finding the origin of the fault.        They present results that support this claim, and also report some cases in which the predicate        switching mechanism fails. However, they do not provide an analysis to explain what makes it        fail in such cases. 

     

(5)

1.2. The predicate switching mechanism 

 

A branching predicate, (or shortly predicate), is a boolean expression whose evaluation is the        condition for execution of a block of instructions. The basic idea of the predicate switching        method is that switching a single predicate might in specific cases remove the influence of        some earlier emerging fault and thus result in a successful execution. The executed branching        predicates are systematically switched separately during re­executions of a given failing        program. Once a successful execution is generated, the programmer can directly examine the        predicate whose switch caused the successful execution. This can spare debugging time by        helping to locate the origin of the fault faster while benefitting from the programme’s        contextual knowledge. 

 

The predicate switching mechanism starts by extracting a predicate trace of a failing program        execution. The predicate trace of an execution is the list of all the predicates that have been        visited during a specific run of a program, in the order of execution. 

 

In the sequel, for each predicate instance (a specific iteration of a predicate in a specific        location in the execution trace of a program) a corresponding execution instance of the        program is generated with the only difference that this particular predicate instance is switched.        The rest of the program is executed just like the original failing execution instance.  

 

The order of the predicate instances to be switched is from the last predicate instance visited to        the first. This process is being iterated until, ideally, the initially observed faulty behavior        disappears.  

 

The predicate instance whose switching prevents the manifestation of the fault and thus results        in a successful execution of the program is called the critical predicate instance. The critical        predicate instance is seen as an indication for the location related to the fault and, once        identified, it is reported to the user and the search terminates. 

 

Example 1 illustrates the dynamics of the predicate switching mechanism. Consider the        hypothetical function from this example which we use to illustrate a fault that can be located by        predicate switching. This example is characterised by the following features: 

 

­ Its control flow has multiple branching predicates (lines 4, 5 and 9).   ­ A computation that influences the control flow (lines 2­3). 

­ An assertion (line 12) that checks if the specifications are satisfied (in this example ­        the returned integer x is expected to have a non­negative value). 

­ A fault seeded (line 2) that makes the assertion fail (namely, we changed the        assignment statement from z = y*x to z = y­x) 

(6)

Assume that the specification that this program has to meet is that, given any integer input, the        value it returns has to be a non­negative integer value. When this specification is met the        program is considered to be correct. The assertion at line 12 tests that this specification is met        by controlling that right before the exit point of the program the value of the variable x that is        about to be returned is indeed not negative. 

 

In the correct version of this program (namely when at line 2 the integer variable z gets the        value of x*y assigned), given any integer input, the assertion is evaluated to true, which means        that the program always returns a non­negative value. Take for instance the input x=(­5) and        y=10. Without the fault at line 2, the branching predicates at lines 9 and 10 should have been        evaluated to true (z<=0, u>0), which would have led to a true evaluation of the assertion (x =        50). The fault in line 2 however (z = y­x) makes the function execute lines 5 and 6 instead of 9        and 10 (z>0, u>0). As a result, the assertion fails (x=(­10). 

 

Traditionally, a programmer will review          and debug the code manually to determine        the first place where a suspicious        statement related to the assertion does        something undesired. In this example          manually reviewing the code to detect the        location of the fault is not necessarily an        extremely tedious task. But in real life        settings where programs are bigger this        could be very time consuming. This is the       

place  where  an  automated  or 

semi­automated search approach could        come to hand as a useful way to reduce        debugging time. The predicate switching          approach suggests automatically switching        the  outcome   of  each   predicate   that   is     1 public int returnNonNegative(int x, int y){  2       int z = y­x; // ←BUG! should be z=y*x    3       int u = (­x);  4       if (z>0) {  5  if (u>0) {x = (­y);}  6  else       {x = x+1;}  7       }  8       else {  9         if (u>0) {x = x*(­10);}  10      else       {x = y;}  11      }  12      assert(x>=0);  13      return x;  14 }  Figure 1 ­ an example of a faulty program to  be located by predicate switching 

visited in a given run, one predicate at a time, and in this manner forcibly making the execution        take a different path. 

 

Back to the example in figure 1, the predicate instance trace of the faulty execution of the code        given the input x = (­5) and y = 10 is {4,5}. The numbers 4 and 5 stand for the lines of the        visited branching predicate instances (z>0 at line 4 and u>0 at line 5). These predicate        instances are being switched, starting from the last predicate instance visited. Specifically in        this case, switching the outcome of the predicate at line 5 (z>0) as if it was (!(z>0)) makes the        program continue at that point from line 4 to line 8, instead of to line 5. As u is assigned the        value 5, the program execution continues to line 9, which assigns to the variable x the value 50,        which is a non­negative number. 

(7)

Note that switching the predicate at line 4 returns the execution to the path it should have taken        if the fault at line 2 had not existed. This results in a correct program with respect to the        specification that the returned value should be non­negative. The correctness of the program is        then controlled by the assertion at line 12, which becomes correct, as x indeed has a positive        value at that point. 

 

In this case, the predicate switching method helped the execution in this case to “recover” ­ or        avoid the fault ­ and this way makes it possible to identify the origin of the fault as one that is        related to the predicate at line 5 (z>0), and thus specifically to the variable z in this case. 

 

2. Research question 

 

In this work we implement a tool for locating faults through automated predicate switching.        For our implementation we used Rascal, which is a meta programming language for analysis        and transformation of programs. Our work serves as a demonstration of using Rascal for        building dynamic analysis tools and automated debugging techniques. 

 

Zhang et al. report that in some situations faults may not be found by the predicate switching        tool. However, an analysis of the reasons why in some situations faults can be located and in        others not, was missing.  

 

As a following step, we had initially explored different factors that can determine the        effectiveness of predicate switching. We restricted our attention to faults that only influence the        condition of if­statements (the branching predicates).       This restriction makes it possible to        completely compensate the fault’s infection on the program by switching a predicate outcome.        On the other hand, faults whose influence exceeds to parts of the program beyond only        branching predicates can have effect on the entire program, which would be irreversible by        predicate switching. However, Zhang et al. show that the code that influences branching        predicates is usually only a small part of entire programs’ code. That means that predicate        switching can only be effective in such restricted settings. 

 

After further investigating the related literature [2,3] and analysing the predicate switching        method, it appeared that a main factor that determines the effectiveness of the predicate        switching tool is whether a fault influences merely a single predicate instance or multiple        predicate instances. If a fault influences more than a single predicate instance it seems that it        cannot located by predicate switching. We are interested in examining the influence of        predicate switching in such cases by addressing the research question:     

Why does the

 

 

 

predicate switching technique fail at locating faults whose infection chain infects

 

 

 

   

 

 

 

 

 

 

multiple predicate instances?

 

(8)

3. Method 

 

In order to explain why predicate switching fails at locating faults whose infection chain        diverges to multiple predicate instances we were interested in investigating the infection chain        of the faults in such programs in comparison with programs in which faults are found by        predicate switching. 

 

To this end, we replicated the implementation of the predicate switching tool as described by        Zhang et al (see section 5). Once we implemented the tool, we composed a testing suite that we        used to experiment with the predicate switching tool on. The testing suite consists of 18 Java        programs that we seeded with faults (see details in section 6.2). To fit the scope of our research        the faults were seeded such that their influence is restricted merely to branching predicates.        Besides, we were looking merely at predicates of if­statements and left while­loops outside the        scope of this work. 

 

In our experiment we run the predicate switching tool on each program in our testing suite and        then report the results. The next step in our research is analysing the effect of the predicate        switching tool on these programs. We track the effect of the tool on each of these programs and        use these observations to explain why it fails at locating faults whose infection­chain diverges        to multiple predicates. 

 

4. The Original Studies 

 

In this section we describe the work that Zhang et. al performed. We briefly describe their        contribution, then we present their implementation or their predicate switching tool. We then        present a summary of their experiments and their results, followed by their conclusions. 

 

4.1. Contribution of the Original Study 

 

In their work, Zhang et. al. show how automated predicate switching helps finding the location        of faults efficiently. They distinguish between what they refer to as “data part” as opposed to        “selection part”. In short ­ data part is the code slice that takes part in producing the output.        Selection part, on the other hand, is the code slice that influences the choice of control flow        (through branching predicates). They show that the size of the search space is being        considerably decreased when limited to merely predicate instances. 

(9)

4.2. Implementation 

 

In order to implement their predicate switching tool, Zhang et. al used Valgrind, an        instrumentation framework that works at binary level and then maps back to source code [14].        Valgrind’s kernel is a dynamic instrumenter which dynamically calls the instrumentation        function provided by the user for a given basic block and executes the instrumented basic block        instead of original one. In their implementation, a program is being instrumented in such a way        that it surrenders the control to the tool at the moment that the execution reaches the predicate        instance at hand, and the outcome of the predicate instance is being reversed by switching the        two branch targets. 

 

4.3 Limitations 

 

Zhang et. al mention also some limitations of predicate switching. For instance, the report that        it cannot locate a fault which is quite complex. They explain that overcoming the problems        created by faulty code may not be possible by switching one predicate instance at a time and        may require switching multiple predicate instances. They report also that predicate switching is        highly unlikely to locate very significant faults such as, for instance, when some functionality        is missing. However, they do not present any analysis of the reasons that cause faults to be        overcome or not in these cases. They also did not describe examples that manifest this        situation. 

4.4 Experimentation and Evaluation of the Results 

 

In their experiment Zhang et. al tested their implementation on 20 cases, out of which 15        pointed at a predicate instance that, once switched, made the program produce correct output or        eliminated the cause of the program crash. In 10 out of these 15 cases in which a fault was        eliminated by switching a predicate, the most recent instance of a predicate (namely, the first        predicate checked which appeared last in the predicate trace) was the critical instance. In 4        other cases up to 10 predicates were examined until the critical one was found, and in one        single case 143 predicates were switched before finding the critical predicate, and only the        second predicate switch that produced correct output was actually related to the fault. 

   

4.5 Conclusions 

 

In their work Zhang et. al presented the idea of critical predicates and an efficient automated        algorithm for locating critical predicates. They show that critical predicates very often are        located in many real reported faulty programs, and provide valuable clues to the cause of the       

(10)

failure and hence assist in fault location. They demonstrated that the mechanism of automated        predicate switching can be effective for reducing the effort required for fault location. They        show how dynamic program slicing can use the results of the predicate switching technique in        order to point to the exact origin of a fault. 

   

5. The Replication Study 

 

In this part we describe the research we have conducted to identify factors that influence the        effectiveness of the predicate switching approach to locate faults. To this end we have built a        predicate switching tool using the Rascal framework [15]. Rascal is suitable for this goal as it        enables doing both structural analysis and transformation of a program (see section 6.2 for a        comparison of our implementation and the implementation of Zhang et al.). 

 

We created a testing suite consisting of 18 Java programs. In each program we seeded one        single fault. As testing all possible errors and combinations is not possible, we were limited to        conducting qualitative experiments. We do that by running the predicate switching tool we        built on all the programs in the testing suite.     

5.1 The Predicate Switching tool 

 

5.1.1. The Role of the Rascal Language

   

One of the features of Rascal that we used is the M3 model, which is a language independent        meta model for source code analysis. That model includes a symbolic representation for        abstract syntax trees of programming languages. Using it made it possible to automatically        identify the locations of all if­statements in the programs with which we experimented. These        locations were then used to perform cross­cutting transformations on the programs in our        testing suite in order to implement the program tracing process and the predicate switching        process. 

5.1.2 Design of the Predicate Switching Tool 

 

We first define the main steps of our implementation as shown in Figure 2. The input of the        system is a program in a specific failing setting. A tracing step then follows in which a trace of        the branching predicates visited during the specific failing execution is being created by       

(11)

running an instrumented version of the failing program. In the next step, the program is        transformed to facilitate the function of dynamic instrumentation of the program. The last step        is then iterative execution of the input program in which ­ using dynamic instrumentation for        each branching predicate from the tracing list ­ a corresponding variation of the execution is        being carried out. The function of the tool is divided into the following steps:          Figure 2 ­ The general function   of the predicate switching tool   

5.1.3. Implementation

  Our implementation of the tool with Rascal has the following functions:   

Extracting location of predicates with Rascal ­             The tool starts with the location of the        original faulty program as an argument (step 1 in Figure 3). This location is then used by        Rascal’s M3 model to extract the exact locations (file location plus the exact begin and end        point in the file) of the branching predicates of all if­statements from all the methods of the        program. We limited the tool to if­statements, while the original work done by Zhang et. al also        considered while­loops. 

 

Create a self­tracing program ­          This part of the tool uses the if­statements’ locations       

extracted in the previous step and wraps the predicate with a call to another boolean function        (step 2 in Figure 3). That function returns the value of that predicate and registers the        predicate’s location in memory. The result of this step is a program which is functionally        identical to the original program, but also creates a trace of the predicate instances which are        visited during the execution of the program. 

 

Create predicate trace ­         In this step (step 3 in Figure 3) the self­tracing program is being run,        which results in a list of all the predicate instances in the sequence of their execution for the        program in the given fault­inducing context. 

 

Instrument the program to facilitate predicate switching ­                 In this step (step 4 in Figure 3)        each predicate that appears in the predicate trace is again being wrapped in the original        program in another boolean function which increments a counter and returns the value of the        predicate’s evaluation. Once the program is run at a later stage, when the counter reaches the        serial number  which  corresponds  to  a  specific  predicate  instance,  the   predicate  switching  

(12)

function  returns  the  negation  of  the  predicate’s evaluation, and in fact causes the        desired switching of the predicate (Figure 4).      Figure 4 ­ The method added to a program by the  tool to facilitate predicate switching   

Iteratively execute the program while          switching predicates ­       In this step (step 5 in          Figure 3) the program is executed once for        each predicate instance from the predicates        trace list. The result is a list that maps each        execution instance (corresponding to a          specific switched predicate instance) of the        program to a boolean value representing        success or failure of the execution (resulting        from the evaluation of the assertion that is        used to check correct behavior). The first        encountered predicate instances that are          reported as the critical predicate instances are        being examined until an actual critical        predicate instance is found, if at all.      Figure 3 ­ The different intermediate phases in the  function of the predicate switching tool  

 

Reflection on the implementation process

   

In order to control the implementation, we made sure that the tool identifies correctly all the        possible variations of if­statements (including if­statements and if­then­else statements, nested        if­statements, if­statements with with curly brackets or without). Additionally, the tool is robust        against non­functional text (such as comments). We saw that the tool properly creates        instrumented versions (both for tracing and for predicate switching) of the programs at the right        location, without exceptional cases. However, at the point of adding the calls to the tracing        function we could not distinguish between branching predicates that were visited in the        program execution and those who were not. This resulted in adding calls to tracing functions        that were not used. 

   

(13)

 

We also controlled that the predicate instance trace was registered in the correct sequence of        execution. Similarly, the predicate instances are being switched in the correct order. The        number of the variations of each program corresponded, as expected, to the size of the        predicate instances trace. We then controlled that every true result coming from the tool indeed        corresponded to the location in the source code of a predicate instance that influences the        program’s output. We did this for each program on which we tested our tool and observed that        the tool works seamlessly. 

 

5.2. Comparison between our Rascal tool and the tool built by Zhang et al. 

 

The implementation of the predicate switching tool works similarly to the tool that Zhang et. al        present in their paper in the following way: 

 

Both tools create a predicate instance trace of a failing execution of a program. Once the        predicate trace is created, both tools start at the end of the predicate trace, according to the Last        Executed First Switched (LEFS) ordering principle, and generate for each predicate instance in        the trace an instrumented variant of the original failing program with that predicate instance        switched. In the sequel the result of each execution is controlled, and the first critical predicate        instance, that is, the predicate instance whose switching leads the program to a successful        execution ends the search process. 

 

The main difference in the way we built the tool and the way Zhang et. al built their tool is the        instrumentation method. While Zhang et. al perform program instrumentation on the binary        level during run­time, we instrument the programs on the source­code level. While Zhang et. al        use Valgrind [14] for their implementation, with which they forcibly take over the execution of        predicates and manipulate the program’s control flow, our implementation executes the        program fed with a reference to the specific predicate instance we want to switch, and the        program switches that predicate instance itself.  

 

We chose for this method of instrumentation as it is suitable for program analysis and        transformation of source­code which served in building our tool. We used the M3 model        provided in Rascal, which is a language independent meta­model for facts about source code.        Considering this, the implementation can easily be adapted to support any language we want to        target the tool at. Besides that, the M3 model is also extensible to include new        language­specific modeling elements. 

 

Another difference between this work and the work presented by Zhang et al. is that in the        implementation of Zhang et. al they refine the ordering of the predicate instances trace by        prioritizing branching predicate instances based on their dependence on faulty code and on the       

(14)

dynamic dependence distance from the erroneous output value. They show that doing this can        further decrease the search space. We did not implement this refinement as it is merely an        optimization feature rather than a crucial functional feature that influences the results. 

   

6. Experimentation 

 

In order to be able to analyse why predicate switching fails at locating faults whose infection        chain diverges to multiple predicate instances, we experimented with the predicate switching        tool we built on a testing suite that we composed especially for this study.       In each program in        our testing suite we injected a fault and added an assertion that is expected to fail when the        fault manifests during execution.  

 

We used the predicate switching tool to repeatedly execute each of these programs.        Correspondingly to the length of the predicate instance trace, the number of iterations on each        program ranges from a single iteration (as in the programs “Three Card Poker” and “Tic        Solver”) to 889 iterations (as in the program “Morse Tree”). 

 

For each program we looked at whether the fault was located by the predicate switching tool.        We then analysed each of these programs and checked whether we indeed observed a relation        between the infection of multiple predicate instances and the success of the tool in locating the        fault.  

 

We then investigated the effect of the predicate switching tool on each of these programs, and        looked for different patterns of the faults’ infection chains through multiple predicate instances        in different programs. Our insights are then presented using an example.  

 

Note that the experiments cannot reflect all possible errors in real life, but they still help us        gain valuable insights for specific debugging scenarios. 

 

6.1 The testing suite 

  

In order to compose the testing suite, we searched the internet for Java programs that meet        different conditions which would make them suitable for our research. Finding such suitable        programs appeared to be quite tedious and took three weeks.  

 

We were looking for programs in which the control flow contains branching predicates.        Besides, we were interested in programs in which some of the branching predicates depend on        some computation, rather than  only on a single  boolean  object. The reason for this  is  that we  

(15)

   

wanted to examine programs with faults that can cause some form of infection chain.        Eventually we selected 18 programs, with sizes varying from 38 to 446 lines of code [15].   

The number of files or classes involved in the manifestation of the faults was not investigated        as a factor that was expected to influence the results. Hence, in order to make the experiments        more overseeable, we limited the research to programs that span over a single file. For the        same reason we limited the search to stand­alone programs that do not depend on external        libraries. 

 

The testing suite we used is different than the one used by Zhang et al. and by other studies in        this field, namely the Siemens testing suite which consists of C programs, containing        documented faults [13]. We could not use this testing suite as Rascal, at the moment that this        study is being conducted, does not support C programs. 

   

6.2. Faults injection 

 

In order to answer our research question we have seeded a single fault in each program from        the testing suite. Each of these faults then leads to some visible undesired behavior. 

 

The injected faults include switching an operator in an equality or in a comparison expression,        changing a value at the left hand side or at the right hand side of an equality expression,        negation of a logical expression or of a boolean object, and switching a literal in an equality        expression. 

 

For the sake of reproducibility, we present the exact faults we have seeded next to the        corresponding correct code in table 1. 

 

Detecting when the undesired behavior is being manifested is done by an assertion added at a        later point that checks that specific conditions are met, which are expected in a successful        execution of the program. It could happen, however, that instead of resulting in undesired        output, a program crashes at a certain point as a result of the fault seeded in it. 

 

Note that the type of faults we examined was not taken as a factor in our experimentation, as        our focus is more on the influence of the infection­chain caused by a fault on the effectiveness        of predicate switching to locate it rather than the influence of the type of fault. 

   

(16)

     

Program  Code fragment with fault  Code fragment without fault 

SudokuGenerator  sudoku[y][x] ​!=​ toCheck  sudoku[y][x] ​==​ toCheck 

Maze  if (grid [row][col] == 0)  if (grid [row][col] == 1

Sudoku1  || ! this.row[row][number]        || ! this.column[column][number]        || ! this.box[getBoxNumber(row, column)][number]);  &&​ ! this.row[row][number]         && ! this.column[column][number]        ​&&​ ! this.box[getBoxNumber(row,  column)][number]); 

AVLTree  (findClosestNode(rootNode, val).value ​!=​ val)  (findClosestNode(rootNode, val).value ​==​ val) 

Date  year < b.year || ((year == b.year) && ((mm > b.mm) || ((mm == 

b.mm) && (dd <= b.dd)))) 

year < b.year || ((year == b.year) && ((mm < b.mm) || ((mm 

== b.mm) && (dd <= b.dd)))) 

Agenda  if (timetable[day].equals("freee") == true)  if (timetable[day].equals("free") == true) 

NQueens  boolean sameDiagonal = (​(c1 ­ r1)​ == (r2 ­ c2));  boolean sameDiagonal = (​(r1 ­ c1)​ == (r2 ­ c2));  

TheoryTimer  return !(elapsed > duration);  return !(elapsed < duration); 

CyclicWords  if (a.charAt((i+j)%len) == b.charAt(j)) {// fault !  if (a.charAt((i+j)%len) != b.charAt(j)) { 

MineSweeper  if (r >= mines.length || r > 0 && c < 0 || c >= mines[r].length)  if (r >= mines.length || r > 0 || c < 0 || c >= mines[r].length) 

Sudoku2  int boxRowOffset = (i / 3); 

int boxColOffset = (j / 3); 

int boxRowOffset = (i / 3)*3;  int boxColOffset = (j / 3)*3; 

LinkedList  if ((current==header || current==ender)) {return false;}//fault  if ((current==header || current==ender)) {return false;}//fault 

ThreeCardPoker  return (getRank(hand[0]) == 0 && getRank(hand[1]) == 1 

&& getRank(hand[2]) == 12) ​&&​ //ace low 

       (getRank(hand[1]) == getRank(hand[0]) + 1 &&          getRank(hand[2]) == getRank(hand[1]) + 1);    return (getRank(hand[0]) == 0 && getRank(hand[1]) == 1  && getRank(hand[2]) == 12) ​||​ //ace low  (getRank(hand[1]) == getRank(hand[0]) + 1 &&  getRank(hand[2]) == getRank(hand[1]) + 1);   

TicSolver  (!won)  (won) 

MorseTree   return (leftChild != null);   return (rightChild != null); 

BinarySearchTree  if(​t2!=null​) {  if(​t2==null​) { 

BMI­Calculator1  (checkLength < 2 && checkLength > 6)  (checkLength > 2 && checkLength < 6) 

BMI­Calculator2  (checkLength < 2 && checkLength > 6)  (checkLength > 2 && checkLength < 6) 

  Table 1 ­ The exact faults   seeded in the testing suite   

7. Results 

 

We want to understand why predicate switching fails at locating faults whose infection chain        spreads to multiple branching predicates. To this end we analyse the behavior of the method by        running it on programs from the testing suite that we composed. 

 

In Table 2 the results of the experiments are presented. The table shows for each program we        used whether it succeeded or failed at finding the critical predicate that helps locating the fault.        For each of the programs the total lines of code is presented, which stands for the maximum        amount of code that has to be reviewed in order to locate the fault. Another column presents       

(17)

the amount of lines of code that is guarded by that critical predicate that was found. The ration        between these two columns can be used to estimate the contribution of predicate switching to        reducing debugging time. The last column shows in which program false positives were found        before successfully locating the actual critical predicate instance. The table also presents the        size of the predicate trace which is the number of predicate instances that were visited during        the faulty execution.          Program    Critical  predicate  instance  found    Trace  Size    LOC to check  without the  tool   (total LOC)    LOC to check  after the tool   (% of Total  LOC)    Number of  false positives  SudokuGenerator  No  25  118  ­  ­  Maze  No  196  61  ­  ­  Sudoku1  No  875  109  ­  ­  AVLTree  Yes  624  364  9 (2,47%)  ­  Date  Yes  25  219  3 (1,37%)  ­  Agenda  Yes  28  82  7 (8,54%)  ­  NQueens  No  433  65  ­  ­  TheoryTimer  Yes  17  98  4 (4,1%)  ­  CyclicWords  Yes  13  38  15 (39,5%)  MineSweeper  No  759  255  ­  ­  Sudoku2  No  488  68  ­  ­  LinkedList  Yes  20  131  4 (3,1%)  ­  ThreeCardPoker  Yes 86  7 (8,14%)  ­  TicSolver  Yes  99  92  31 (33,7%)  MorseTree  No  889  215  ­  ­  BinarySearchTree  Yes  98  446  1 (0.2%)  BMI­Calculator1  Yes 86  13  ­  BMI­Calculator2  No 85  ­  ­    Table 2 ­ Results     

As can be seen in table 2, in 10 programs the seeded fault was located and in the other 8 it was        not. 

   

(18)

underlying patterns and show why predicate switching is limited to locating faults whose        infection chain does not diverge to multiple predicate instances. 

 

The programs in which the faults were located 

 

From the results it seems that in 10 of the programs from        the testing suite, the fault was located. This means that        switching a certain predicate instance results in a program        execution with a correct behavior. From analysing the        programs in which the faults were located, it appeared that        in all these programs the infection chain of the fault was        indeed limited to a single branching predicate instance.        Figure 4 illustrates such pattern, of an if­statement, where        the black node stands for a single infected predicate. 

 

The programs in which the faults were not located 

 

In the other 8 programs, the predicate switching was not        able  to  locate  the  fault. In  these  cases  the fault  is  being  

      

 

Figure 4 ­ A general example for  program control flow in which only  a single branching predicate is  infected 

manifested, but is not located by predicate switching. We analyzed these programs in order to        understand why. From the analysis of these programs we saw that multiple predicate instances        were indeed infected by the faults. We observed two patterns in which infection of multiple        branching predicate instances happens: when multiple predicates depend on faulty code, and        when a single predicate depends on faulty code but is part of an iterative procedure. We will        explain these two situations in more detail in the next sections. 

 

In order to explain these observations, we tracked the behavior of the predicate switching tool        and analyzed its effect by controlling manually the switching of each predicate instance that we        know is related to the fault. The next sections present the findings of these observations. 

 

Multiple predicates depend on faulty code 

 

In such cases where a fault has diverging influence on multiple predicates, it is impossible to        repair the fault by merely switching a single predicate instance. Figure 5 gives a simple        example for such a pattern. We will explain this pattern by illustrating the effect of the        predicate switching tool when multiple predicates are infected. We take as an example the        BMI­Calculator program from our testing suite. This program happens to be written in a way        that it contains two functionally identical code fragments, each influencing a different        branching predicate (see Figures 6.a). 

(19)

 

The code in lines 24 to 37 is responsible for checking        whether the input value that is given for the height is        a number within a specific range. The predicate at        line 38 is influenced by the result of the procedure we        just described. The code in lines 52 to 65 does the        same checking procedure for the weight, which        influences the control flow of the program at another        branching predicate, namely at line 66. The fault in        this program is in the comparison expression that        controls that number of characters in the user input        (line 26 in Figure 6.a). Instead of controlling that the        input length is within a certain range, the code now        controls that it is outside that range. 

 

For comparison we create a variant of the        BMI­Calculator program by simply replacing the        duplicated code (lines 24­37 and 52­58 in Figure 6.a)        in a single boolean function CheckValidity (13­28 in        Figure 6.b) which is similar to these two procedures        and  is  being called at  different  moments. The same 

 

Figure 5 ­ the infection of   different multiple predicates 

fault is also kept in this variant of the program, and is now part of the CheckValidity function.        (line 16 in Figure 6.b). 

 

When we run the predicate switching tool on each of these two variants of the program, we see        that it succeeds at locating the fault in the program depicted in Figure 6.a, whereas when it is        run on the program depicted in Figure 6.b, it fails.  

 

A simplified representation of the control flow graphs corresponding to the code in Figures 6.a        and 6.b is given in Figures 7.a and 7.b. These figures depict more clearly the difference in the        infection chain of the control flow between the two programs (the black nodes represent        infected branching predicates, whereas the white nodes represent uninfected ones). It can be        clearly seen that changing branching predicate 1 (Figure 7.a) results in a fault­free execution of        the program, while in the other case (Figure 7.b), changing either branching predicate 1 or        branching predicate 2 does not completely repair the fault’s effect on the execution. 

 

From the analysis of the effect of predicate switching on each of these programs, it seems the        explanation for the difference in results between these two functionally­identical (when        correct) versions of this BMI Calculation program is related to the difference in the            

(20)

              24.  int checkLength = checkInput.length();  25.  boolean valid = true;  26.  if  (checkLength < 2 && checkLength > 6) {         …   36.  } else { valid = false; }         …   38. if (valid) {  39.  int inputValue = Integer.parseInt(checkInput);  40.  if (inputValue == 0) valid = false;  41.  else height = inputValue;  42.      }  43.  else {  44.  System.out.println("!ERROR");  45.  System.exit(0);  46.      }         …  52. int checkWeight = checkInput.weight();  53. boolean valid = true;  54. if (...) {           …   64. } else { valid = false; }         …  66. if (valid) {  67.   int inputValue = Integer.parseInt(checkInput);  68.   if (inputValue == 0) valid = false;  69.   else weight = inputValue;  70.   }  71. else {  72.   System.out.println("!ERROR");  73.   System.exit(0);  74.   }      Figure 6.a ­ A version of the BMI­Calculator   in which the fault is located by predicate   switching  13. public static boolean checkValidity(String input){  14. int checkInput = input.length();  15. boolean valid = true;  16.  if  (checkLength < 2 && checkLength > 6) {  ...  26. } else { valid = false; }  27. return valid;  28. }  …  50. if (checkValidity(input)) {  51.    int inputValue = Integer.parseInt(checkInput);  52.    if (inputValue == 0) valid = false;  53.    else height = inputValue;  54.       }  55.    else {  56.    System.out.println("!ERROR");  57.    System.exit(0);  58.        }         ...  59. if (checkValidity(input)) {  60.    int inputValue = Integer.parseInt(checkInput);  61.    if (inputValue == 0) valid = false;  62.    else weight = inputValue;  63. }  64.    else {  65.    System.out.println("!ERROR");  66.    System.exit(0);  67.       }              Figure 6.b ­ A version of the BMI­Calculator   in which the fault is not located by predicate   switching       

(21)

infection chains of the fault in each of        these programs. In the program depicted        in Figure 6.a, the infection chain of the        fault includes only a single branching        predicate (the one from line 38). In the        other variant however, in which the fault        is not being located, the infection chain of        the fault diverges to multiple predicates        (namely those at lines 50 and 59). 

 

Single predicate depends on faulty code,            but is part of an iterative or recursive                procedure 

 

In this section we describe another        infection chain pattern that makes our        predicate switching tool fail at locating        faults. Namely, such in which faults have        influence on a single predicate that is a        part of an iterative procedure such as a        for­loop, a while­loop or a recursive        procedure.                 (a)      (b) 

 

Figure 7 ­ The difference in the   control flow between single­ and  multiple infected predicate(s)   

In these cases (for instance, in the Sudoku Generator, Maze and Mine Sweeper programs in our        testing suite), the infected branching predicate is being executed multiple times, which results        in an effect similar to the case in which different multiple branching predicates are infected by        the fault. As the predicate switching tool causes a single switch of a specific branching        predicate instance, it does not manage to repair the effect of the fault completely, and hence it        is not able to generate a successful execution instance. 

 

An illustration of the effect of predicate switching on faults that cause infection chains of that        sort can be seen in Figure 8. This figure illustrates clearly that switching a branching predicate        instance   n (presented as white node at the branching point) fails at repairing other branching        predicate instances n­1 and n+1 (the black nodes at the corresponding branching points).   

There are some exceptions however. A fault whose infection chain diverges to multiple        branching predicates might still be found by predicate switching in cases where all these        branching predicates are themselves controlled by another branching predicate whose        switching still compensates the diverging infection caused in the iteration or recursion. Another        exception is when a fault is in an iterative procedure, but a certain evaluation of the predicate        exits the iteration. In such a case, exiting the iterative procedure might limit the infection to one        predicate instance which can be repaired when switched. 

(22)

 

      predicate instance n­1      predicate instance n        predicate instance n+1    Figure 8 ­ The effect of predicate switching   on the infection of a branching predicate in a for­loop   

8. Related Approaches 

 

There are multiple publications about methods that use predicate switching . The characteristic        that they all share is that they reduce the search space compared to approaches that are based        on arbitrary state changes. While the predicate switching technique presented by Zhang et al. is        not able to locate a fault if it infects multiple predicate instances, some works suggest ways that        could locate faults also in these situations. Liu et al. [3] suggest performing multiple predicate        switches at the same time. Whereas Wang et al. [2] propose automatically generating multiple        successful executions of a faulty program and selecting one that has a predicate trace most        similar to the predicate trace generated from the original failing execution. The sequence of        branch instances evaluated differently in these runs is then returned as a bug report. 

 

Although these methods may be able to locate faults that infect multiple predicate instances,        they both suffer from another problem, which is a sharp increase in complexity, as the number        of possible combinations of predicate instances to switch increases very quickly. Liu et. al        propose to fix this problem by bounding the number of iterations of each loop, while Wang et.        al have simply excluded them from their analysis for feasibility reasons. 

 

Other examples for automated debugging techniques that could be an alternative for using        automated predicate switching are the following: Delta debugging, proposed by Zeller et. al       

(23)

[7], is a technique where one examines the differences in executions that result from gradually        removing parts of the input. This approach, however, is only suitable for a limited range of        program types using specific types and structures of input. Another related approach is        program slicing [11], and especially dynamic slicing [12]. In this approach, debugging is        facilitated by ruling out statements that do not affect the observable error via control and/or        data flow. 

 

Predicate switching, and automated debugging in general (including the approaches mentioned        in this section) differ from automated testing in the sense that while automated testing aims at        detecting faulty program behavior, automated debugging aims at locating the faults themselves.        While automated testing is done by generating and testing large amounts of test cases,        predicate switching is done by generating and testing large amount of program variations.   

9. Threats to validity 

 

One threat to validity in our study is that it uses a testing suite of limited size.       This is rather a         qualitative exploratory study, and not a quantitative study.       Even though the programs we did        investigate can provide valuable insights for our exploratory study, we still cannot perform an        exhaustive statistical examination of all possible errors and scenarios.       Another remarkable    threat to validity is that the scope of this work is limited to faults whose influence is restricted        on branching predicates. That means that we are unable to generalize the conclusions to faults        that violate these restrictions.       We were not able to compare our findings with those of Zhang et        al, as they did not provide analysis of the negative results they achieved, and due to our use of        a different testing suite in a different programming language, as at the time we conduct this        study Rascal supports Java and not C. 

 

10. Future Work 

 

The implementation presented here is a demonstration of how Rascal can be used for building        dynamic analysis tools. This work could be an inspiration for developing additional dynamic        analysis or debugging tools using Rascal. Such tools could be designed to automatically detect        erroneous events by tracing and analyzing specific aspects of programs execution which are        related to specific language structures (for instance iterations and recursions that could cause        excessive use of working memory). Another interesting follow­up direction could be using        predicate switching combined with automated test­case generation. This could be used for        locating faults whose occurrence spans over multiple executions, given ranges of inputs.   

(24)

11. Conclusions 

 

In this replication study we experiment with the predicate switching method presented in        Zhang et al. in order to get insights into the reasons why predicate switching fails at locating        faults when their infection chain diverges to multiple predicate instances.  

 

To this end we build a predicate switching tool using Rascal, which is a framework that is built        for static analysis and program transformation. Our implementation is based on Rascal’s M3        model, which is a language­independent extensible meta­model for source code analysis      which 

makes it suitable to support new languages and language specific modeling elements.

  

 

We show that in different patterns of infection chain that diverge to multiple predicate        instances, switching a single predicate cannot completely remove the effect of the fault. Such        patterns can occur when a fault infects a single predicate which is inside an iterative procedure,        or when the fault infects multiple different predicates. In these cases, the infected predicate        instances that are not repaired by the predicate switching can still cause undesired behavior of        the program. 

   

(25)

 

12. Bibliography 

 

 

1. X. Zhang, N. Gupta and R. Gupta.       Locating Faults Through Automated Predicate          Switching. Proceedings of the 28t International Conference on Software Engineering,        Pages 272­281, 2006. 

 

2. T. Wang and A. Roychoudhury.      Automated Path Generation for Software Fault        Localization. Proceedings of the 20th IEEE International Conference on Automated        Software Engineering, Pages 347­351, 2005. 

 

3. Y. Liu and B. Li.         Automated Program Debugging via Multiple Predicate Switching       .  Proceedings of the Twenty­Fourth AAAI Conference on Artificial Intelligence, Pages        327­332, 2010. 

 

4. H. Cleve and A. Zeller. Locating Causes of Program Failures. proceedings of the 27th        IEEE International Conference on Software Engineering (ICSE), Pages 342­351, 2005.   

5. A. Zeller. Isolating Cause­effect Chains from Computer Programs. SIGSOFT        Symposium on Foundations of Software Engineering, Charleston, Pages 1­10, 2002.   

6. A. Zeller. “Yesterday, My Program Worked. Today, It Does Not. Why?,” 7th European        Software Engineering Conference/ 7th SIGSOFT Symposium on Foundations of        Software Engineering, pages 253­267, Sept. 1999. 

 

7. A. Zeller and R. Hildebrandt. “Simplifying and Isolating Failure       Inducing Input”, IEEE      Transactions on Software Engineering, 183­200, 2002

 

8. M. Renieris and S. Reiss. Fault Localization with Nearest Neighbor Queries.        Proceedings of the 18th International Conference on Automated Software Engineering,        30­39, 2003. 

 

9. Groce, A. Error Explanation with Distance Metrics. In Proceedings of the 10th        International Conference on Tools and Algorithms for the Construction and Analysis of        Systems, 108–122, 2004. 

 

10. S. Chaki, A. Groce, and O. Strichman. Explaining Abstract Counterexamples.        SIGSOFT Symposium on the Foundations of Software Engineering, 73­82, 2004.   

(26)

11. M. Weiser. Program Slicing. IEEE Transactions on Software Engineering, 352­357,        1984.  

 

12. H. Agrawal and J. Horgan. Dynamic Program Slicing. SIGPLAN Conference on        Programming Language Design and Implementation, 246­256, 1990.    13. The Siemens Testing Suite ­ http://sir.unl.edu/portal/bios/tcas.php, [05­02­2016].    14. The Valgrind Instrumentation Framework ­ http://valgrind.org/, [05­10­2015].   

15. The Code Repository that Contains the Source Code of the Tool and the Testing Suite ­        https://zenodo.org/badge/latestdoi/22542/amirico/Predicate_Switching, [07­06­2016],  DOI: 10.5281/zenodo.55059                              

Referenties

GERELATEERDE DOCUMENTEN

Hybrid process algebra, provides us with the opportunity to model dependence between the continuous variables of parallel processes, which is used in the analysis of Fischer’s

Publisher’s PDF, also known as Version of Record (includes final page, issue and volume numbers) Please check the document version of this publication:.. • A submitted manuscript is

Deze factoren hebben te maken met het specifiek kijken naar het individu met aandacht voor de thema’s uit het aangepaste AAIDD model, kenmerken van de cliënten, het meervoudig

Wij zouden het begrip attitude hier willen vertalen in potentiele rol, (of systeem van potentiele rollen) ingebouwd in het self (geheusen). Ret self als systeem

In the case where d k is a linear mixture of the latent source signals as they impinge on the reference sensor of node k, the idea of (3) is to perform a denoising of the

In the following narrative the authors present four cases in which causality is demonstrated between the patients’ sudden clinical deterioration and subsequent death, intracardiac

This suggests that Frisian–Dutch bilingual speakers use an open control mode when they speak Frisian and a coupled control mode when they speak Dutch, leading to the prediction

Use a membership predicate to denote that points belong to lines or circles.. Use the same predicate to denote that lines or circles belong to certain classes of lines