1 Use case 1: Happy numbers
In order to show the potential of the tool, we provide an example to compare two versions of an Erlang program that computes happy numbers. Concretely, these two versions are based on completely different algorithms. Therefore, this use case shows that SecEr is able to deal with such scenarios where the correspondence between versions is not direct. Both of them are taken from the Rosetta Code repository:111Consulted in this concrete version: http://rosettacode.org/mw/index.php?title=Happy_numbers&oldid=251560#Erlang
First of all, and before using SecEr, we have to slightly adapt these codes. Our implementation needs a common input function, but in the original happy0 module there is a function main/0, which just runs a concrete test. Therefore, we have replaced this function with main/2 (see Listing 1) to match it with the corresponding one in the happy1 module (Listing 2). Moreover, in the happy1 module, we have added the Happy variable222This variable addition will not be needed when SecEr allows to define expressions as POIs. (line 2), which stores the result of is_happy(X,). In both modules, we have added a type specification (represented with spec in Erlang) to obtain more useful test cases in less time333Note that depending on the ITC, the execution of these programs can loop forever. Although we run each execution of a ITC with a limited timeout, these useless ITCs are spending valuable time that could be used by more useful ITCs..
Listing 3 shows the SecEr’s output when comparing both implementations of the program using a timeout of 15 seconds. The POI for both modules is the Happy variable, which is evaluated many times per execution, returning a trace with more than one element. As we can see, the execution of both implementations behave identically with respect to the Happy variable in the 812 generated test cases.
In order to see the output of the tool when the behaviour of the two compared programs differ, we have introduced two different errors in the happy1 module of Listing 2.
- An error inside the is_happy function
The first error is introduced by replacing the whole line 2 with X < 10 -> false;. With this change, the behaviour of both programs differs. When the user runs SecEr using the previous POI, it produces the report shown in Listing 4. SecEr is reporting that the POI has been executed several times and in some of these executions the values of the POI differed. Concretely, according to the given example, the executions of ITC main(2,6) compute different values at the sixth time both POIs are executed. Given this report, the user can continue testing using other POIs in order to find the source of the discrepancy. Since variable Happy is the result of calling the same function in both codes, there are two possible sources of the discrepancy: either the arguments that can take different values during the execution (i.e. X) or the code executed by the called function (i.e. is_happy). Following this idea, the user could use argument X as the new POI. In this case, if both versions produce different traces for X, then the discrepancy source must be in the parts of the code responsible for the values taken by variable X. Otherwise, the argument X can be discarded as the source of the discrepancy, and the source must be in something executed by the is_happy function. Listing 5 shows the report provided by SecEr when selecting variable X as the POI. The reported discrepancy indicates that both traces are the same until a point in the execution were the version in Listing 2 continues producing values. This behaviour is the expected one because the result of is_happy has an influence in the number of times the call is executed. Therefore, the user can conclude that the arguments do not produce the discrepancy and the error is inside the is_happy function.
- An error outside the is_happy function
The second error consists in the replacement of the whole line 2 with false -> happy(X + 2, Top, XS). The output of SecEr in this case is depicted in Listing 6. The discrepancy reported is exactly the same one than the one reported in the previous error. Therefore, the user could use here variable X as the next POI following the same idea explained above. Listing 7 shows the report of SecEr in this case. Here the error is different from the one obtained above. It indicates that the values computed for X differ between versions. This is a clear indicator that this error is not being produced by function is_happy and it comes from where the variable X obtains its values. There are only two places in the code where this occurs (apart from the initial call that according to the reported error seems to be correctly defined), so now it is easy for the user to spot the error using this information.
As we can observe in both errors, it does not matter where the bug is located as long as the bug affects the values computed at the POI. As we have seen in the examples, another interesting feature of the tool arises when we have a POI that is evaluated several times during the execution of the program. In this case, SecEr allows us to differentiate between two kinds of errors: traces that differ in their number of elements where a trace is a prefix of the other one (Listing 5) and traces with the same values in different order or with different values (Listing 7).
2 Use case 2: An improvement of the string:tokens/2 function
In this case of study, we consider a real commit of the Erlang/OTP distribution that improved the performance of the string:tokens/2 function. Figure 1 shows the code of the original and the improved versions. The differences introduced in this commit can be consulted here:
The improvement performed in this commit consists in two main changes. The first one is a general improvement obtained by reversing the input string (the one that is going to be tokenized) at the beginning of the process. The second one improves the cases where the separator list has only one element. The algorithm uses two auxiliary functions in both cases, so its structure is kept between both versions. However, the optimized code defines two versions of these functions to cover the single-element list of separators and the rest of cases separately.
We can use SecEr to check whether the behaviour of both versions is the same. A good way to start is to check whether the results produced by function tokens/2 are the same. In order to do this, we have to slightly modify the codes by adding variable Res in line 1 of the original version and in line 1 of the improved version444Note that this modification is only needed because of the current limitations of the tool. However, the approach has not this limitation, as it allows to select any expression, not only variables., both in Figure 1. Then, this variable is used as a POI. This allow the user to check that both versions preserve the same behaviour (see Listing 8).
[C | tokens_multiple_2(S, Seps, Toks, [C])]
In this scenario, SecEr reports that some of the traces differ (see Listing 9). The error indicates that the produced values are not the expected ones. As in this particular case the main modification is the specialization of function tokens1/3, analyze the differences between the new versions of this function seems a good way to continue searching discrepancies, i.e. tokens_single_1/3 and tokens_multiple_1/3. For doing this, we need to add new variables Res1 and Res2 in lines 1 and 1 of Figure 1 (optimized version). The report for both POIs is depicted in Listings 10 and 11, respectively. Let us first analyze the report in Listing 10. At first sight, one can think that we have spotted the function responsible of the discrepancy, but this is not true. What SecEr is reporting here is that for some cases, the optimized version has not produced any value. For instance, the call tokens([0,5,2],), that SecEr uses to exemplify the problem, is executed by line 1 of the optimized version, so it is not entering to the case branch that executes tokens_single_1/3. Similar errors arise when the separator list contains more than one element, executing in this case the branch in line 1. So this kind of errors are expected and are not considered to be a discrepancy. On the other hand, the report on Listing 11 shows two types of discrepancies. The first one has the same explanation of the one reported in Listing 10. However, the second one corresponds to a real source of discrepancy, since they are cases where tokens_multiple_1/3 is executed and produced different results than tokens1/3. Note that almost 75% (5301 out of 7134) of the ITCs produced by SecEr correspond to the relevant error. This is due to the priority given to the discrepant traces as it is explained in Section 3.3 of . Listing 11 also reveals one interesting feature of SecEr, which is that it can report different types of discrepancies for a given pair of POIs and provide the user with an example of each of them.
3 Use case 3: Complex numbers
The following use case is extracted555It is, in turn, built using a module from the Rosetta Code repository:
http://rosettacode.org/wiki/Arithmetic/Complex from the use-case database of EDD666https://github.com/tamarit/edd, an algorithmic debugger for Erlang:
This use case is formed by two versions of the same module that are almost identical except that one of them has a bug. Figure 2 shows the code (as it is on GitHub) of the buggy version. The bug is in lines 2 and 2, while the correct code is obtained by replacing these lines with lines 2 and 2. In the rest of the section, we show how SecEr can be used to detect this type of bugs.
We can start our test by choosing as a POI each variable of the resulting tuple of function calculate/4 in line 2, one by one. We named complex0 and complex1, the correct and the buggy versions of the code, respectively. The report given by SecEr for all the variables is similar to the one depicted in Listing 12, which shows that both versions behave in the same way, except for variable Inversion for which SecEr indicates that some discrepancies exist as it is shown in Listing 13
. In fact, this listing is showing two kinds of errors. The first indicates the lack of traces for one of the executions (probably due to runtime errors) and the second references to the discrepancy of the computed values. Since variableInversion comes from the result of the call to function inverse/1 in line 2, there are two possible sources of the discrepancy: the argument (variable A in this case) or the definition of function inverse/1. Therefore, the second step could be to check whether both versions are using the same arguments by placing a POI in variable A. However, this variable is used in all the other calls of function calculate/4, so this means that A most likely is not the source of the discrepancy. Therefore, the user could decide to continue searching for the bug inside function inverse/1 using as a POI the variables that defines the result, i.e. RealPart and ImgPart, both in line 2. Listing 14 shows the report provided by SecEr for variable RealPart. At this point, it is not needed to rerun SecEr using as a POI variable ImgPart, because we have already found an error. In this concrete case, if SecEr is rerun, a similar report will be produced. So, the next step is to place a POI in one of the two variables that define the value of RealPart, i.e. variables C and Mod in line 2. The report for variable C is depicted in Listing 15, whereas for variable Mod is in Listing 16. Thanks to these last reports, the user already can easily find the source of the discrepancy in the assignment of variable Mod.
-  D. Insa, S. Pérez, J. Silva, and S. Tamarit. Erlang code evolution control. Pre-proceedings of the 27th International Symposium on Logic-Based Program Synthesis and Transformation (LOPSTR 2017), abs/1709.05291, 2017.
-  D. Insa, S. Pérez, J. Silva, and S. Tamarit. Erlang code evolution control. Logic-Based Program Synthesis and Transformation (LOPSTR 2017) Lecture Notes in Computer Science (To appear), 2018.