Modeling Programs with Unstructured Control Flow for ... - CiteSeerX

1 downloads 0 Views 123KB Size Report
[18] Raymond Reiter. A theory of diagnosis from first principles. Artificial Intelligence,. 32(1):57–95, 1987. [19] Ehud Shapiro. Algorithmic Program Debugging.
Modeling Programs with Unstructured Control Flow for Debugging Wolfgang Mayer and Markus Stumptner University of South Australia Advanced Computing Research Centre 5095 Mawson Lakes SA, Adelaide, Australia {mayer,mst}@cs.unisa.edu.au

Abstract. Even with modern software development methodologies, the actual debugging of source code, i.e., location and identification of errors in the program when errant behavior is encountered during testing, remains a crucial part of software development. To apply model-based diagnosis techniques, which have long been state of the art in hardware diagnosis, for automatic debugging, a model of a given program must be automatically created from the source code. This work describes a model that reflects the execution semantics of the Java language, including exceptions and unstructured control flow, thereby providing unprecedented scope in the application of model-based diagnosis to programs. Besides the structural model building process, a behavioral description of some of the model components is given. Finally, impacts of the modeling decisions on the diagnostic process are considered.

1

Introduction

Debugging, i.e., detecting a faulty behavior within a program, locating the cause of the fault, and fixing the fault by means of changing the program, continues to be a crucial and challenging task in software development. Many papers have been published so far in the domain of finding faults in software, e.g., testing or formal verification [1], and locating them, e.g., program slicing [23] and automatic program debugging [19, 11]. More recently model-based diagnosis [18] has been used for locating faults in software by several researchers [2, 10, 15]. [2] shows the relationship between automatic program debugging and the model-based approach. In [24] the author discusses the relationship between slicing and model-based debugging. This paper extends prior research on model-based diagnosis for locating bugs in programs written in mainstream programming languages, with the language Java used as an example. The idea behind the model-based debugging approach is (1) to automatically compile a program to its logical model or to a constraint satisfaction problem, (2) to use the model together with test cases and a model-based diagnosis engine for computing the diagnosis candidates, and (3) to map the candidates back to their corresponding locations within the original program. Formally, given a set of test cases on which the program is run, a (minimal) diagnosis is defined as a (minimal) set of incorrectness assumptions AB(C) on a subset C ∈ ∆ of components COM P in the program (usually statements) such that {AB(C)|C ∈ ∆} ∪ {¬AB(C)|C ∈ COM P \ ∆} ∪ SD

is consistent [18]. Here, SD is a logical theory describing the program’s behavior under the assumption that components work correctly. Since the computation depends on observations in terms of test case output, unlike formal verification approaches, no separate formal specification is necessary - everything but the test cases is computed automatically from the source code. Conversely, where verification model checkers produce counterexamples, the outcome of the diagnosis process are code locations. Model-based debugging thus complements, rather than replaces verification techniques. Diagnosis granularity is primarily determined by the choice of diagnosis components, e.g., statements, expressions, or other program entities. The crucial factor that determines discrimination, i.e., the ability to focus quickly on a particular error, is the selection of the model. In the past two categories of models developed in the Jade project were published which represent two end points of the spectrum: dependencybased [12] and value-based models [13]. The models presented in [14, 17] have successfully been applied to debug Java programs. The tests consist of small to medium sized Java programs together with their faulty variants and given test cases. A comparison of the models and their effectiveness relative to each other as well as compared to a normal interactive debugger was given in [15]. Whereas these methods have been applied to large scale programs in concurrent languages [22], so far, one important factor that has held up experimentation with “off the shelf” example programs has been the omission of certain frequently used language constructs from our models, most notably exception handling. The hierarchical structure of the models renders them unsuitable for diagnosing programs which do not conform to the assumption of a “structured” control flow based purely on loop and if statements, This excludes a wide array of language features which includes structured exceptions handling constructs, recursive method calls, return statements, and jump statements. This paper addresses that issue and presents a different modeling approach, where the restrictions on program structure are relaxed in order to overcome the limitations of previous models. As a result it represents a significant step towards a model of that language that can cover arbitrary Java programs. The new modeling algorithm is based on building a modular, multi-directional version of the classical control flow graph (CFG) [6], which represents an approximation of all possible execution paths. The nodes of this CFG represent basic blocks (i.e. a sequence of operations that always executes sequentially without branching), and the arcs represent the control flow between these blocks. In a following step, the CFG is enhanced by dataflow information. The variables that are used and modified by a basic block are computed and arcs between any two dataflow-dependent blocks are inserted. The basic blocks in each method are then transformed into a constraint network. The constraints are grouped in two categories that represent the semantics of the language elements (e.g. the computational statements and expressions) on one hand and flow control restrictions on the other hand (e.g., turning off alternate execution paths to the one actually taken). The constraint variables are likewise partitioned, one group representing the actual program variables accessed and modified by the code, the other group representing the control flow (i.e., the selection of which statements are executed). As a result, control and dataflow of a program can be reasoned about in a uniform framework. The representation of the program execution as a constraint system provides means for computing values in both directions: forward and backward. This is essential for ap-

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

class Fac { static int f(int x) throws OutOfRangeException { if (x < 0) throw new OutOfRangeException(); else if (x == 0) return 1; else return x * f( x - 1 ); } static boolean exception = false; static int main() { int fac = 0; try { fac = f(3); } catch(OutOfRangeException ex) { exception = true; } return fac; } }

Fig. 1. Example Program

plying model-based debugging approaches, which rely on an effective theorem prover generating small conflict sets. The paper is organized as follows: In Section 2 we describe the construction of the bidirectional CFG, followed by the description of how the diagnosis model is actually constructed in Section 3. Finally, we discuss the capabilities of the model (in particular the types of errors that can be identified).

2

Modeling Unstructured Control Flow

The automatic transformation from the Java source code to the logic model used for diagnosis proceeds in three steps. Build CFG. The program is transformed into its CFG representation. Each method is represented as a directed graph with basic blocks as nodes and all possible transitions between each nodes as arcs. In addition, nodes are included for each method’s (unique) entry and (potentially multiple, since there is a separate one for every type of exception) exit points. Details about the algorithm can be found in [20]. Note that we utilize the intra-procedural version of their algorithm, as method calls are represented hierarchically. Therefore, if a called method has multiple possible exit paths, the call must be the last statement in the basic block. Compute Dataflow Information. In this step the CFG is enhanced by dataflow information. In particular, for each basic block the variables that are used and modified are computed using an abstract interpretation approach [3]. For instance variables, the approach of [15] is followed (i.e. the instance variables are treated similarly to global variables). Transform CFG into Constraint Representation. Each basic block of the CFG is transformed into a constraint representation improved over that of [15] by transforming each basic block separately (instead of building one large constraint network). The transformation of a basic block can be summarized as follows: The program is decomposed into a set of primitive language elements (e.g. variable access, assign-

ments, operators, method calls, etc.), which are ordered according to the execution semantics of the Java language. For each primitive element a constraint is generated that simulates the effects of the element in both forward and backward direction. For every assignment to a variable, a new constraint variable is generated and is subsequently used up to the point in the code where the variable is reassigned. For unnamed results of expressions an auxiliary constraint variable is created. A special placeholder component is inserted for each method call and loop statement that summarizes the effects of the method or loop. Similar to the CFG construction, these components have output connections for each exit node of the called method’s (the loop’s) model. To obtain a model of the whole method, the models of the basic blocks are composed according to the control and dataflow information computed previously. The blocks are processed in the (partial-)order that is specified by the control flow graph. For each block, the block’s model is added to the method’s model and is connected to those constraint variables defined by already processed blocks that are used by some constraint in the block. However, due to the nonlinear control flow, for some blocks more than one constraint variable can represent a particular program variable. Consider the program in Fig. 1. The program dependency graph for method main() is depicted in Fig. 2(a). Here, for Block D, two possible definitions of variable fac are visible (Block A and Block C). Traditionally, this problem is avoided by inserting dataflow φ-functions [4], which select among one of the two possible paths. A similar approach is taken here, where the φ functions are modeled by additional merge constraints, that combine the different constraint variables into a single variable that is subsequently used as input for the block. In case of traditional dataflow analysis, inserting φ functions for each control flow join is sufficient, as reasoning is performed in only one direction. When reasoning in both directions, similar functions have to be introduced for control flow branches. As before, these ’inverse φ functions’ are modeled by additional split constraints. A split constraint uses the current connection as input and distributes the information to its output variables (there is one for each possible path). Note that the behavior of split and merge constraints is equivalent, but operating in the opposite direction. After all blocks have been assembled into a single model, constraint variables for all variables used by a block that are not previously defined in the method are created and are associated with the method’s entry point. Similarly, all modified externally visible variables are collected and associated with each of the method’s exit points. The modeling of the control flow proceeds as follows. For each basic block b a constraint variable ctrl(b) is created that represents the control flow for b given the program run ρ executed under the current test case. The domain of control variables is defined as {>, ⊥, ?}. If ctrl(b) = >, b is definitely executed for ρ. If ctrl(b) = ⊥, b is definitely not executed, and if ctrl(b) =?, we do not know. Together with other constraints ensuring required properties for the control flow, this is equivalent to removing all paths that require the execution of this block from the CFG. Split and merge constraints are not associated with a control variable, as they cannot be disabled. If the last statement s in a basic block b represents a branch statement (i.e. a conditional statement or a method call with multiple possible exit points), the model of s provides an additional output connection for each possible exit of the method. The con-

nections correspond to the possible transitions in the CFG after s is executed. They are connected to the control variables of the following blocks that correspond to the paths taken. If s is not a branch, the value of the control variable of b would be always equal to the control variable of the following block b0 . Therefore, the b0 reuses the control variable associated with b. Similar to the dataflow connections above, for blocks with multiple incoming control edges, additional constraints that merge all the incoming control connections into one variable are inserted. To ensure the control variables accurately model the program’s control flow, a further condition must be ensured: for each block, not more than one transition must be taken upon exit from the block. Further, if a block definitely is executed, the taken exit transition is also definitely executed. These conditions are ensured by adding explicit (liveness) constraints. Finally, to combine the reasoning about data and control flow, each of the dataflow constraint variables v is also associated with a control variable. For variables associated with outgoing connections of a basic block b, ctrl(b) is used for this. Otherwise, if v corresponds to the output of a split constraint, the control variable corresponding to the path the variable represents is selected. Example. To illustrate the modeling process, reconsider the program from Fig. 1. The model of method main is depicted in Fig. 2(b). Control flow at the entry point of the method is represented by constraint variable a. The call of method f() splits the control flow in two separate paths. Therefore, split components are inserted for the variables fac and exception.1 Besides the connections representing the data flow for the two variables, the split constraints are connected to the constraint variables representing the control flow corresponding to the data connections. Note that for both the split components the value of the represented variable is not used in the non-exceptional branch and therefore the variables representing these branches are omitted. Nevertheless, the components may not be removed from the model, as they form a propagation barrier for the backward reasoning algorithm in case it is unknown which path of the branch is followed and the values are different for any two paths. The path represented by variable b represents the transition to the exception handler block. The normal return from the method is represented by c. As exactly one of these paths has to be followed if Block A can be reached, the constraint live enforces this condition. The model of Block D requires variable fac and the control flow as input connections. As there are multiple incoming arcs for both dependencies in the CFG, merge constraints have to be inserted. The constraint for variable fac is connected to the two output ports providing the values of the variable and to the control variables a and c of the corresponding blocks. Further, the output port of the merge constraint is connected to the input port of Block D, which requires the value of fac, and to D’s control variable. The merge constraint for variable exception is connected in a similar way. 1

Here, knowledge about the program’s structure is incorporated into the model building process. For the example it is known that, following the method call, the CFG consists of a single entry/single exit (SESE) region [8] to Block D. Therefore, only the modified variables of this SESE region are considered when generating dataflow split components. See also the Optimize Model section below.

main() exception

main A 3

0

a live f()

fac=

_return

exception

A

fac

B

c true

B

b

C

fac

C exception=

fac= fac

exception

exception

fac

D

d fac

D _return= _return

main_exit

(a) Dependency graph of method main(). Solid lines represent control dependencies, dashed lines data dependencies. Data dependencies are labeled with the variable they represent

exit

(b) Model of Method main(). For clarity, blocks are drawn without embedded constraints. Constraint variables are drawn as circles (control variables are drawn filled). Split and merge constraints are represented by triangles. The set of blocks and variables associated with a control variable are drawn as hyper-edges containing the control variable. Fig. 2.

Optimize Model. In addition to the previous model building steps, the constraint system can be further simplified by transformations similar to those in [8]. Here, merge constraints for control flow edges can be eliminated if they merge all control flow variables contained in a Single Entry Single Exit (SESE) region (i.e. a merge constraint can be replaced by a SESE’s control input connection if the constraint postdominates the region). In the example above, statements in lines 10–14 form a SESE region, which contains exactly the control variables b and c. Therefore, the merge constraint and constraint variable d are removed and all connections to d are redirected to a, which is the control variable for the entire SESE region. Figure 3 depicts the final model of method main(). Similarly, dataflow split and merge constraints may be removed if a SESE region does not modify the corresponding variables.

main

a

e1 live exception

A

f2

c b

B

C

f1

fac

e2

f3

e4

f4

exception

fac

f5 e3

D

r exit

Fig. 3. Optimized Model of Method main().

3

Execution Semantics

This section briefly describes the behavior of the model components. For brevity, only essential components and differences to [15, 14] are presented. First, the bijection between program statements and model components is abandoned. This makes it possible to represent a single statement as multiple components and allows fine grained control over the model’s assumptions, spanning multiple levels in the program structure. The assumptions for each constraint are grouped into a hierarchy, resembling the program structure. This provides means for making assumptions about single statements, blocks of statements, and hierarchically nested statements. In particular, the representation allows to distinguish the calling of a wrong method from a faulty implementation of the called method. Also, this representation is expected to reduce the complexity of the diagnostic process, as program structure can be exploited to speedup diagnosing (the approach is taken from [5]). A description of the most important model components follows (C represents a constraint and ¬AB denotes that the component is assumed to behave correctly): Constants. Component representing constant literals enforce their output value to be equal to the constant k specified in the program. ¬AB(C) ∧ const(C) ⇒ out(C) = k Variable Access and Assignments are modeled by constraints propagate the value of the input variable to the output constraint variable. For assignments, the input connec-

tion represents the result of the right hand side expression. Otherwise, the connection is associated with the current value of the variable. ¬AB(C) ∧ (ident(C) ∨ assign(C)) ⇒ out(C) = in(C) Operators. Constraints modeling operators compute the value for the output variable by applying the represented operator to the values provided by input variables. For binary operators the behavior is specified as follows (opC represents the operator that is modeled by the constraint): ¬AB(C) ∧ binop(C) ⇒ out(C) = opC (in1(C), in2(C)) Method Calls. Method calls are represented as a single component with entry connections representing the union of the input connections used by each of the possibly called method’s models. The output connections are formed in a similar way. In difference to the model from [15], the component modeling the method call provides output connections for the modified variables not only for a normal return from the method, but also for exceptional exits. For each of the possibly thrown exceptions the component provides output ports corresponding to the modified variables, together with a control connection representing the exceptional path. When the method call component is executed, the model of each of the method call candidates is retrieved. The component representing the call is replaced by a switch component, which simulates the selection of the called method according to the receiver’s dynamic type. The component provides output ports for each candidate, which represent the control flow activation for the method. The models of the methods are then inlined into the model of the calling method, using the control connections provided by the switch component. The input and output connections of the models are replaced by the input and output connections attached to the former method call component, eventually with the insertion of split and merge constraints as necessary. ∀t∈Types (¬AB(C) ∧ call (C) ∧ type(C) = t ⇒ Mt (C)) Types denotes the set of possible types of the receiver object, Mt (C) denotes the model of the method that is executed for type t (with inputs and outputs substituted with C’s variables), and type(C) represents the value of the input constraint variable that corresponds to the receiver’s dynamic type. Note that the switch component is implicit in the rules’ antecedence. Although this representation incurs a slightly higher overhead at runtime compared to inlined method models, it is particularly useful for dealing with recursive methods, as the recursion depth need not be known when building the model. Another advantage of the hierarchical approach is the fact that it is easy to define abnormal modes for a method call. For inlined CFGs this is not easily possible in every case. Furthermore, for some programs and test cases the models may remain smaller, as methods that are not needed to derive a conflict need not be expanded. While Loops. Components representing loops are treated similarly to method call components. In contrast to method calls, here the model of the loop (the body and the condition) is known in advance and therefore the switch component is unnecessary. When the loop is expanded (i.e. the condition evaluates to >), the model of the

condition and the body is inserted before the component representing the loop. If the condition evaluates to ⊥, the loop component is removed from the model and its output connections are replaced with its input connections. Otherwise, the component remains in the model, as it is not known how many iterations the loop executes. ¬AB(C) ∧ while(C) ∧ Mcond (C) |= ⊥ ⇒ ∀v∈MVars (outv (C) = inv (C)) cond (C) represents the evaluation of the condition’s model using the constraint’s input values, MVars represents the variables modified by the loop condition or the loop body. Note that the behavior of the component is undefined for the case where the condition does not evaluate to > or ⊥. Dataflow Split & Merge. These components represent the dataflow φ functions, either in forward direction (merge), or in backward direction (split). Each component dealing with n input or output paths has connections datai (each associated with a control variable controli ) (i ∈ [1 . . . n]), which represent the data variables for each of the paths. Further, the variable datau (associated with controlu ) represents the unified connections of datai . The behavior of these components can be formalized as follows (assume each of the data connections holds a set of values): Let Active denote the subset of indices i where controli (C) 6= ⊥. Then dfmerge(C) ⇒ ∀i (datau (C) ⊆ datai (C) ∨ controli (C) = ⊥) \ dfmerge(C ) ⇒ datau (C) ⊇ datai (C) i∈Active

The first equation states that all the values of the unified connection must be supplied by every data connection that can possibly be executed. The second condition covers the case where values are propagated in the other direction: in this case the unified connection must be consistent with the intersection of all other data connections. Control merge components representing joins in control flow are modeled similarly to the previous paragraph, but no data connections are present. Each component has n input variables controli and an output variable controlo . The behavior is given as follows: _ cfmerge(C) ⇒ controlo (C) = controli (C) i

Liveness constraints ensure that if a statement (except the last statement of the program) is reachable then exactly one of its successors is executed. The constraint has an input variable livei for each of the statement’s successor paths and is also connected to the surrounding block’s liveness variable livep .   ^  _ liveness(C) ⇒ livep (C) = livei (C) ∧ ¬livei (C) ∨ ¬livej (C) i

3.1

i6=j

Example

To demonstrate them model’s ability to effectively locate faults, reconsider the program in Fig. 1 together with a test case that specifies the value true for variable exception

and 0 for the result of main(). As a first step, the values provided by the test case are inserted into the model: e3 = true, r = 0, and a = > (the entry and exit points of the method are reachable). Further, at the beginning of the diagnostic process it is assumed that all components are correct. By propagating values through the model, e4 = true and e1 = false are derived. For e2, both values are derived, which results in a conflict. Therefore, the corresponding branch of the model is eliminated by adding c = ⊥ for the associated control variable c. a = > together with ¬AB( 11 ) derive b = ⊥, which violates the liveness constraint (because of c = ⊥). Therefore, the model is found inconsistent and the conflict {¬AB( 7 ), ¬AB( 11 )} 2 is returned. To break the conflict, either {AB( 7 )} or {AB( 11 )} has to be valid. Assuming {AB( 7 )}, true is derived for e2–e4 without causing a conflict. r = 0, a = >, and ¬AB( 15 ) derive 0 for f3–f5. On the other hand, a = > and ¬AB( 11 ) derive c = > and 6 for f2 and f3, resulting in a contradiction for f3 (with the value 0 derived in the previous inference). Hence, 7 cannot be a diagnosis and cannot be responsible for the misbehavior. For {AB( 11 )} values for e1–e3 are derived as in the first computation. However, b = ⊥ cannot be derived and therefore the model is consistent. As a result, {AB( 11 )} is the only single fault diagnosis.

4

Discussion

This section provides an overview over the impact of some modeling decisions on the diagnostic process. Conflicts. To be able to derive conflicts after a model has been found inconsistent3 , the propagation algorithm must keep track of which constraints derive which values. Alternatively, approaches similar to [9] can be applied. For efficiency, the constraint network propagates only deltas of values, so that execution in the next propagation cycle can be focused on the newly added values. Also, method calls and loops are expanded to full models only when reasonable input values are present at their inputs (e.g. only if the receiver is known). Faults. The model proposed herein is able to locate general functional faults, where parts of a program are faulty without assigning to wrong variables. This also includes the subclass of structural faults where a wrong variable is used. Faults can be detected not only in the top level method, but also in hierarchically embedded program elements. However, the model assumes the control flow graph to be fixed and therefore faults causing changes in the CFG cannot be located reliably. For example, changing the type of the exception of a throw statement may cause a changed control flow, as a different catch block may be responsible for the new type. Dealing with structural faults is generally more difficult, as the model has to be modified dynamically during the diagnostic process. The modular modeling approach helps to avoid unnecessary re-computation, as structural changes that are not visible outside a method do not trigger a model update for the calling methods. 2 3

Statements are identified by their line numbers. The model herein derives an inconsistency iff there is no possible and consistent path between the model’s start node and its end node.

Another approach for detecting structural faults we currently incorporate into the model is to compare the dependencies of the program with the dependencies specified by assertions and design information [21, 7]. Contracts. To reduce the amount of user interaction required to isolate a faulty statement, assertions may be inserted into the program (e.g. via the Java 1.4 assert statement). Assertion expressions are translated into the model. However, components representing assertions are always assumed to work correctly, i.e. there is no conventional abnormal mode for these components. Pre- and postconditions are treated similarly to assertions. Preconditions are translated into assertions and are inserted before the first statement of the method. Postconditions are converted to assertions and are inserted after each statement causing a return to the caller.

5

Conclusion

Model-based debugging carries considerable promise based on the concept of automatically deriving a diagnosis model from the program source code and using this, together with input-output test vectors, to identify faults in programs [16]. An effective model representation is a crucial step in building a framework for model-based debugging. This work represents the first time that non-structured control flow is represented in a diagnosis model. We have described the computation of the bidirectional control flow graph that permits reasoning about unstructured control flows on both directions as required for diagnostic reasoning. After optimizing the graph to remove ”dead” path combinations, we have described how the code and graph are mapped into the logicbased diagnosis model. Notably, this approach omits the strict view of a component representing one statement of earlier work [15] and provides a more flexible mapping from code to model. Since real-world programs make frequent use of constructs such as exceptions, this model represents a significant step beyond earlier models that represented Java programs for debugging, but were restricted to linear control flows. As it shares the basic model-based architectures, it can be smoothly incorporated into the implemented JADE intelligent debugging engine [15].

References [1] Edmund M. Clarke, Orna Grumberg, and David E. Long. Model Checking and Abstraction. ACM Transactions on Programming Languages and Systems, 16(5):1512–1542, September 1994. [2] Luca Console, Gerhard Friedrich, and Daniele Theseider Dupr´e. Model-based diagnosis meets error diagnosis in logic programs. In Proceedings 13th International Joint Conf. on Artificial Intelligence, pages 1494–1499, Chambery, August 1993. [3] Patrick Cousot and Radhia Cousot. Abstract interpretation: A unified lattice model for static analysis of programs by construction of approximation of fixpoints. In Proc. Symposium on Principles of Programming Languages, pages 238–252, Los Angeles, California, January 1977. [4] Ron Cytron, Jeanne Ferrante, Barry K. Rosen, Mark N. Wegman, and F. Kenneth Zadeck. Efficiently computing static single assignment form and the control dependence graph. ACM TOPLAS, 13(4):451–490, 1991.

[5] Alexander Felfernig, Gerhard Friedrich, Dietmar Jannach, and Markus Stumptner. Consistency based diagnosis of configuration knowledge-bases. In Proc. 10th Int’l Workshop on Principles of Diagnosis, Loch Awe, June 1999. [6] Jeanne Ferrante, Karl J. Ottenstein, and Joe D. Warren. The program dependence graph and its use in optimization. ACM Transactions on Programming Languages and Systems, 9(3):319–349, 1987. [7] Daniel Jackson. Aspect: Detecting Bugs with Abstract Dependences. ACM Transactions on Software Engineering and Methodology, 4(2):109–145, April 1995. [8] Richard Johnson and Keshav Pingali. Dependence-based program analysis. In Proceedings of the SIGPLAN Conference on Programming Language Design and Implementation, pages 78–89, 1993. [9] Ulrich Junker. QUICKXPLAIN: Conflict detection for arbitrary constraint propagation algorithms. In IJCAI’01 Workshop on Modelling and Solving problems with constraints, Seattle, WA, USA, August 2001. [10] Beat Liver. Modeling software systems for diagnosis. In Proceedings of the Fifth International Workshop on Principles of Diagnosis, pages 179–184, New Paltz, NY, October 1994. [11] J. W. Lloyd. Declarative Error Diagnosis. New Generation Computing, 5:133–154, 1987. [12] Cristinel Mateis, Markus Stumptner, and Franz Wotawa. Debugging of Java programs using a model-based approach. In Proc. 10th Int’l Workshop on Principles of Diagnosis, Loch Awe, Scotland, 1999. [13] Cristinel Mateis, Markus Stumptner, and Franz Wotawa. Modeling Java Programs for Diagnosis. In Proceedings of the European Conference on Artificial Intelligence (ECAI), Berlin, Germany, August 2000. [14] Cristinel Mateis, Markus Stumptner, and Franz Wotawa. A Value-Based Diagnosis Model for Java Programs. In Proc. 11th Int’l Workshop on Principles of Diagnosis, Morelia, Mexico, June 2000. [15] Wolfgang Mayer, Markus Stumptner, Dominik Wieland, and Franz Wotawa. Can AI help to improve debugging substantially? Debugging Experiences with Value-Based Models. In Proceedings of the European Conference on Artificial Intelligence (ECAI), pages 417–421, Lyon, 2002. [16] Wolfgang Mayer, Markus Stumptner, Dominik Wieland, and Franz Wotawa. Towards an Integrated Debugging Environment. In Proceedings of the European Conference on Artificial Intelligence (ECAI), pages 422–426, Lyon, 2002. [17] Wolfgang Mayer, Markus Stumptner, and Franz Wotawa. Model-based Debugging or How to Diagnose Programs Automatically. In Proc. IEA/AIE, Springer LNAI, pages 746–757, Cairns, Australia, June 2002. [18] Raymond Reiter. A theory of diagnosis from first principles. Artificial Intelligence, 32(1):57–95, 1987. [19] Ehud Shapiro. Algorithmic Program Debugging. MIT Press, Cambridge, Massachusetts, 1983. [20] Saurabh Sinha and Mary Jean Harrold. Analysis and Testing of Programs with Exception Handling Constructs. IEEE Transactions on Software Engineering, 26(9):849–871, 2000. [21] Markus Stumptner. Using design information to identify structural software faults. In Proc. 14th Australian Joint Conf. on AI, Springer LNAI 2256, pages 473–486, Adelaide, December 2001. [22] Markus Stumptner and Franz Wotawa. Using Model-Based Reasoning for Locating Faults in VHDL Designs. K¨unstliche Intelligenz, 14(4):62–67, 2000. [23] Mark Weiser. Program slicing. IEEE Transactions on Software Engineering, 10(4):352– 357, July 1984.

[24] Franz Wotawa. On the Relationship between Model-Based Debugging and Program Slicing. Artificial Intelligence, 135(1–2):124–143, 2002.

Suggest Documents