Advances in Computers Atif Memon Eds Download
Advances in Computers Atif Memon Eds Download
https://ebookbell.com/product/advances-in-computers-atif-memon-
eds-4427006
https://ebookbell.com/product/advances-in-computers-volume-131-1st-
edition-ali-hurson-51984522
https://ebookbell.com/product/advances-in-computers-82-1st-edition-
marvin-v-zelkowitz-editor-2253216
https://ebookbell.com/product/advances-in-computers-
volume-78-improving-the-web-1st-edition-marvin-zelkowitz-phd-ms-
bs-4542968
https://ebookbell.com/product/advances-in-computers-85-1st-edition-
atif-memon-eds-4547710
Advances In Computers 86 1st Edition Ali Hurson And Atif Memon Eds
https://ebookbell.com/product/advances-in-computers-86-1st-edition-
ali-hurson-and-atif-memon-eds-4547712
Advances In Computers 87 1st Edition Ali Hurson And Atif Memon Eds
https://ebookbell.com/product/advances-in-computers-87-1st-edition-
ali-hurson-and-atif-memon-eds-4547714
https://ebookbell.com/product/advances-in-computers-89-1st-edition-
atif-memon-eds-4547718
https://ebookbell.com/product/advances-in-computers-volume-92-1st-
edition-ali-hurson-eds-4633924
https://ebookbell.com/product/advances-in-computers-volume-98-1st-
edition-ali-hurson-5427158
VOLUME NINETY ONE
Advances in
COMPUTERS
Edited by
ATIF MEMON
University of Maryland
4115 A.V. Williams Building
College Park, MD 20742,USA
Email: [email protected]
Notices
No responsibility is assumed by the publisher for any injury and/or damage to persons
or property as a matter of products liability, negligence or otherwise, or from any use or
operation of any methods, products, instructions or ideas contained in the material herein.
Library of Congress Cataloging-in-Publication Data
A catalog record for this book is available from the Library of Congress
British Library Cataloguing-in-Publication Data
A catalogue record for this book is available from the British Library
ISBN: 978-0-12-408089-8
ISSN: 0065-2458
vii
viii Preface
Reverse-Engineering Software
Behavior
Neil Walkinshaw
Department of Computer Science,The University of Leicester, Leicester, UK
Contents
1. Introduction 2
2. Background 4
2.1 Modeling Software Behavior 6
2.1.1 Modeling Data 6
2.1.2 Modeling Sequential Behavior 7
2.1.3 Combining Data and Control 8
2.2 The Reverse-Engineering Process 11
2.2.1 Summary 12
3. Static Analysis 13
3.1 Why do we Need Behavioral Models if we have Source Code? 13
3.2 Intermediate Representations—Source Code as a Graph 15
3.2.1 The Control Flow Graph 15
3.3 Answering Questions about Behavior with Static Analysis 19
3.3.1 Dominance Analysis, Dependence Analysis, and Slicing 20
3.4 Building behavioral Models by Static Analysis 25
3.4.1 Collecting “Static Traces”—Identifying Potential Program Executions 25
3.4.2 Deriving Models from Static Traces 29
3.5 Limitations of Static Analysis 30
4. Dynamic Analysis 31
4.1 Tracing and the Challenge of Inductive Inference 31
4.1.1 Tracing 32
4.1.2 The Essential Challenge of Dynamic Analysis 34
4.2 Practical Trace Collection Approaches 35
4.2.1 Selecting Inputs by the Category Partition Method 35
4.2.2 Selecting Inputs by Exercising the Surrounding System 37
4.2.3 Random Input Selection 38
4.3 Data Function Inference 39
4.3.1 Using General-Purpose Data Model Inference Algorithms 39
4.3.2 Inferring Pre-Conditions, Post-Conditions, and Invariants 42
4.4 State Machine Inference 43
4.5 Limitations of Dynamic Analysis 44
Abstract
Software systems are large and intricate, often constituting hundreds of components,
where the source code may or may not be available. Fully understanding the runtime
behavior of such a system is a daunting task. Over the past four decades, a range of semi-
automated reverse-engineering techniques have been devised to fulfill (or assist with the
fulfillment) of this goal. This chapter provides a broad overview of these techniques,
incorporating elements of source code analysis, trace analysis, and model inference.
1. INTRODUCTION
This chapter presents a broad introduction to the challenge of reverse-
engineering software behavior. In broad terms, the challenge is to be able
to derive useful information about the runtime dynamics of a system by
analyzing it,either in terms of its source code,or by observing it as it executes.
The nature of the reverse-engineered information can be broad. Examples
include constraints over the values of variables, the possible sequences in
which events occur or statements are executed, or the order in which input
is supplied via a GUI.
As a research topic, the challenge of reverse-engineering behavioral mod-
els is one of the longest-established in Software Engineering. The first paper
(to the best of the authors’knowledge) on reverse-engineering state machines
(by dynamic analysis) was Moore’s 1956 paper on Gedanken Experiments
[39]. The paper that has perhaps had the biggest impact in the area and
remains among the most cited is Biermann’s 1972 paper on the k-Tails state
machine inference algorithm [3].
The topic is popular because it represents a fundamental, scientifically
interesting challenge. The topic of predicting software behavior is beset by
negative results and seemingly fatal limitations. If we want to analyze the
Reverse-Engineering Software Behavior 3
source code, even determining the most trivial properties can be generally
undecidable [45]. If we want to analyze software executions, there are an
infinite number of inputs, and we are confronted by the circular problem
that we need to know about the program behavior before we can select a
suitable sample of executions to analyze.
However, the topic is not only popular because it is scientifically inter-
esting. It also addresses a fundamentally important practical problem at the
heart of software-engineering. In practice, software systems are often devel-
oped and maintained on an ad hoc basis. Although there might exist an
initial model or statement of requirements, these rapidly become outdated
as requirements change and the system evolves. Ultimately,a system can reach
a point in its development where there is no point of reference about how
the system behaves or is supposed to behave, which has obvious implications
for crucial tasks such as testing and quality assurance.
This chapter aims to provide an overview of some of the key topics that
constitute this broad field. It discusses the basic goals (models of software
behavior), static and dynamic analysis techniques that aim to (at least in part)
achieve them, methods to evaluate the resulting models, and some of the
open challenges. It assumes no prior knowledge of source-code analysis,
dynamic analysis, and only a limited knowledge about the possible modeling
formalisms used to capture program behavior.
Clearly, given the breadth of the field, it is impossible to provide an
exhaustive analysis and set of references for each of these subtopics within a
single chapter. Instead this chapter aims to serve as an introduction. It aims
to provide a useful overview that, while avoiding too much detail, manages
to convey (at least intuitively) the key problems and research contributions
for the main topics involved. Although the chapter seeks to avoid technical
detail, where possible it uses applied examples and illustrations to provide
the reader with a high-level picture of how the various techniques work.
The rest of the chapter is structured as follows:
2. BACKGROUND
The term “software behavior” can assume a broad range of different
meanings. In this chapter, it is defined as broadly as possible:The rules that
capture constraints or changes to the state of a program as it executes. There are
numerous different ways in which such rules can be represented and cap-
tured.This subsection presents an overview of the two main facets of software
behavior: data and control. It presents an informal introduction to the key
modeling formalisms that can capture behavior in these terms, and provides
a high-level overview of the generic reverse-engineering process.
To illustrate the various types of modeling languages, we adopt a simple
running example of a bounded stack. As can be seen from Fig. 1, it has a con-
ventional interface (the push, pop, and peek functions), with the additional
feature that the bound on its size can be specified via its constructor.
Throughout this section, several notions and modeling formalisms will
be introduced. Since the purpose is merely to provide an intuition of the
Reverse-Engineering Software Behavior 5
to track the data-state of the system throughout these events. To address this
problem, a range of extensions have been developed to existing modeling
techniques, that enable the two facets of data and control to be combined.
Such combined notations are generallyTuring-complete. In other words, these
notations can be used to capture any sequential program that can be encoded
as a Turing Machine.
2.1.3.1 EFSMs
Several such notations have been developed, which extend the conventional
FSM in such a way that states and transitions can be decorated with guards
and data operations [19, 6, 24]. Extended Finite State Machines (EFSMs) [6]
represent one particularly popular variant. These extend conventional state
machines in three ways: (1) by adding a memory to store data variables, (2) by
enabling transitions to have data-guards (expressed in terms of conditions on
the memory contents), and (3) by associating transitions with functions that
can transform the memory contents.
An example of an EFSM model of the BoundedStack example is shown
in Fig. 4. For the memory we use the two variables that correspond to the
10 Neil Walkinshaw
data members in the source code: s and lim.The labels consist of three parts
(separated by a “|”). The first part is the input (in our case the method call),
the second part is the guard condition on the memory (a boolean predicate
on s and lim), and the third part is any operation on the memory that is
executed along with the state transition. For example, if we look to state
a, there are two outgoing transitions for the function push. If lim==0,
nothing can be pushed onto the stack, and a push has no effect on the data
state (thus looping to the same state). If lim>0 however, a push leads from
state a to b, and results in an actual push of o onto the stack.
As a whole,the three states distinguish behavior from what happens when
the stack is empty (state a), the stack is not empty, but also not full (state b),
and the stack is full (state c). It is important to note that there can be lots of
valid potential EFSMs for the same system, which may differ in the number
of states and the nature of the guards.
Models such as EFSMs are better able to capture software behavior
because they are “richer” in terms of their syntax and semantics. However,
an important downside is that this adds complexity, which makes them less
appealing to developers. For example,simple assertions in the source code are
much more widely used than EFSMs, because they can be readily produced
during development. Generating richer models such as EFSMs incurs an
overhead; the developer has to invest at least as much effort into generating
the EFSM as they might have to invest into the source code of the system
itself. This of course is a key reason for wanting to use reverse-engineering
Reverse-Engineering Software Behavior 11
techniques—to generate such specifications when the developer has not had
the time to, or when existing specifications have become outdated.
used to infer or deduce a behavioral model. This step is often automated, but
may involve human intervention (e.g., to incorporate domain knowledge).
All reverse-engineering procedures that produce behavioral models can
be characterized in terms of these steps. Some solely rely on static analysis,
others on dynamic analysis, and some combine the two. Some incorporate
input from developers, and some are iterative—gradually refining the target
representation by soliciting additional traces, or additional inputs from the
developer at each iteration.
This schematic view of the reverse-engineering process provides a useful
reference point for the rest of this chapter. It shows where static and dynamic
analysis fit in, and how the developer can play a role in each case. It also
shows how techniques can be iterative. The final model can be fed back
to the reverse-engineer, who might seek to refine the result by considering
additional sets of traces, or by integrating other forms of source code analysis.
2.2.1 Summary
This section has presented shown some of the core languages with which
software behavior can be modeled. It has also presented an overview of the
general reverse-engineering process: the task of analyzing a software system
Reverse-Engineering Software Behavior 13
3. STATIC ANALYSIS
Static analysis is concerned with the analysis of the source code syntax
(i.e., without executing the program). In principle, source code can be seen
as the ultimate behavioral model of a software system. It encodes exactly
those instructions that will be executed at runtime; it imposes an order on
when events can occur, and governs the possible data values at different points
during the execution.
This section seeks to provide a relatively self-contained introduction to
the role of static analysis in reverse-engineering models of software behavior.
It starts by covering the basics—showing how source code can be interpreted
as a graph that captures the flow of control between individual expressions
and the associated flow of data values. These are presented in relative detail
here, not only because they form the basis for all more complex source code
analysis techniques, but also because they are of considerable value in their
own right. These representations alone provide a complete (albeit low level)
overview of the order in which statements can be executed, and how they
can pass data among each other.
This is followed by an overview of the analysis techniques that build upon
these notions of control and data flow to extract information about behavior
(the ability to reason about variable values at given points during the software
execution). Finally, the section concludes with an overview of the various
factors that limit the accuracy of static analysis. The descriptions of static
analysis techniques attempt to present an intuitive and informal introduction.
For more formal definitions, along with algorithms to compute the various
relations within the code, there are several comprehensive overviews [2].
Firstly, there is the problem of feature location. Source code that pertains
to a single facet of functionality can, depending on the language paradigm,
be spread across the system. Whereas the specific element of behavior that
we seek to reverse-engineer might only cover a small fraction of the source
code base (e.g., the code in a large drawing package that is responsible for
loading a drawing from a file), the code in question may not be localized
to a particular module or component in the system. High-level software
behavior often arises from extensive interaction between different modules
or packages [34], especially where Object-Oriented systems are concerned
[56]. So, although source code contains all of the relevant information, this
can be difficult to locate because it contains an overwhelming amount of
irrelevant information too.
Secondly, there is the related problem of abstraction. Once the relevant
source code has been localized, there is the challenge of interpreting its
functional behavior from its implementation. The way in which a particular
unit of functionality is encoded in source code depends on several factors,
such as the choice of programming language, paradigm, architecture, exter-
nal libraries, etc. Thus, a program that achieves a particular functionality in
one language or paradigm can differ substantially from another (the reader is
referred to the Rosetta Code web repository1 for some illustrative examples
of this).
Thirdly, source code is a static representation, whereas its behavior is
intrinsically dynamic.The challenge of discerning program behavior therefore
necessarily requires the developer to “mentally execute” the relevant source
code, to keep track of the values of various relevant variables, and to predict
the output. For any non-trivial source code and non-trivial functionality,
this can incur an intractable mental overhead [5].
In summary, attempting to develop a complete understanding of software
behavior from source code alone is rarely practical. Leaving aside the basic
problem that the source code might not be available in its entirety, the essen-
tial challenges listed above can be summarized as follows: There is (1) the
“horizontal” complexity of trying to discern the source code that is relevant
to the behavioral feature in question, there is (2) the “vertical” complexity of
interpreting the relevant behavior at a suitable level of abstraction, and finally
there is (3) the problem of mentally deducing the dynamic behavior of the
system, and how its state changes from one execution point to the next.
1 http://www.rosettacode.org.
Reverse-Engineering Software Behavior 15
Fig. 6. Bounce.bas BASIC routine to plot the bounce of a ball, and the corresponding
CFG.
in Fig. 6, plots out the trajectory of the bounce of a ball (shown in Fig. 7),
given a set of inputs denoting time-increment, velocity, and a coefficient
representing the elasticity of the ball.
The CFG is shown on the right of the figure. It plots the possible paths
through the function (the possible sequences in which statements can be
executed). It starts and ends with the entry and exit nodes, which do not
correspond to actual statements, but merely serve to indicate the entry and
exit points for the function. Predicate-statements (e.g., if or while state-
ments) are represented by branches in the CFG, where the outgoing edges
denote the true or false evaluation of the predicate. Statements at the end of
a loop include an edge back to the predicate.
Reverse-Engineering Software Behavior 17
are rarely used as visual aids, but form the basis for more advanced analyses,
as will be shown below.
Fig. 10. Dominator and post-dominator trees for bounce (shown on the left and the
right respectively).
Reverse-Engineering Software Behavior 23
that are responsible for computing the variables in the slicing criterion.
When computed in terms of the PDG, a slice simply consists of all of those
statements in the PDG that can reach the node in the graph representing
the criterion.
Slices are clearly useful in their own right. As mentioned at the beginning
of this chapter, one of the core problems of understanding software behavior
is the information overload. There are lots of irrelevant source code, and the
code that is relevant to the feature in question might be spread across the
code base. Slices can (at least in theory) locate the relevant source code.
To get an intuition of how useful slicing can be, let us suppose that we are
interested in finding out which statements affect the behavior of variables
Reverse-Engineering Software Behavior 25
S1 and l at line 35. Extracting a slice from the dependence graph is simply
a matter of tracing back along every incoming path to node 35, marking the
statements that are traversed in the process. The resulting slice is shown in
Fig. 13—both in terms of the reduced PDG, as well as the corresponding
source code.
The “headline” value of slicing is clear. In reference to the challenges
discussed in Section 3.1, it limits the amount of source code that has to
be navigated with respect to a particular comprehension task, reducing the
mental overhead on the developer. Importantly, these benefits also transfer to
other non-manual tasks, including reverse-engineering. Even though these
source code analysis techniques do not directly reverse-engineer a model,
they can reduce the amount of source code that needs to be considered
for a re-engineering task. Slicing can be used to reduce the code base to
something more manageable. Slicing is used for the same purpose in other
domains, such as source-code verification [13], and (its originally intended
purpose) debugging [58].
Fig. 13. Computing the slice in bounce.bas, where the slicing criterion is line 35, vari-
ables S1 and l. This shows that 18 of the 37 lines are relevant to the computation.
Reverse-Engineering Software Behavior 27
Fig. 14. An example of infeasible paths; the only feasible paths through this CFG are
those that take both true branches, or both false branches. Paths that mix true and false
branches are infeasible.
feasible executions from source code is (as will be elaborated in Section 3.5)
generally undecidable. However, there are numerous techniques that have
emerged from the field of software verification that can at least provide an
approximation of feasible executions.
The challenge of identifying a feasible path through a program is best con-
templated in terms of the CFG. In a CFG, a program execution corresponds
to a path from the entry to the exit node. The problem is that not every
walk through the CFG corresponds to a feasible execution. One branch in
the graph might give rise to a configuration of data values that prohibit the
execution of certain subsequent branches. This is illustrated in Fig. 14. Only
two of the four paths through the graph are actually feasible.
The task of collecting “static traces” involves processing the CFG to elicit
a collection of paths that are feasible. This is generally accomplished by
28 Neil Walkinshaw
Fig. 15. Small portion of a symbolic execution tree, for a portion of code taken from the
“bounce” code in Fig. 6. For the sake of readability the tree only reaches a depth of six
execution steps, which suffices to give an intuition of how it is constructed.
and variable assignments that have occurred so far. The feasibility of a path
is reflected in the satisfiability of its path condition.
Even from this example,it becomes apparent that the tree expands rapidly.
The tree is limited to a depth of 6, but does not even contain the full bodies
of the nested for-loops. Each iteration of the outer for-loop would entail ten
nodes (three of which are branch-points). As the depth of the tree increases,
the number of nodes in the tree increases exponentially.
One way of limiting the expansion of the tree is to use SAT-solvers.
These can prevent the expansion of any nodes with path conditions that are
unsatisfiable (and so correspond to infeasible program executions). However,
this is often of limited use. If the program in question couples loops with non-
trivial predicate conditions (e.g., involving floating-point arithmetic) it can
become impossible to determine whether particular paths are feasible or not.
If loop-termination depends on the values of unknown input parameters,
there remains no option but to expand the loop indefinitely (or, in practice,
up to some arbitrary limit).
possible for execution to reach state B from state A. In other words, there is
a state transition A → B.
4. DYNAMIC ANALYSIS
Whereas static analysis is concerned with deriving information from
the source code syntax, dynamic analysis is broadly concerned with analyz-
ing the program while it executes. This can involve keeping track of data
and control events—either as externally observable input/output, or inter-
nal information such as the execution sequence of individual statements or
values that are assigned to particular variables.
Whereas static analysis often consumes an overwhelming amount of
effort to increase precision by eliminating infeasible predictions, this is not
a problem with dynamic analysis. If anything, the challenge with dynamic
analysis is the converse [14]. We begin with a set of observed executions,
and from this have to make generalizations about program behavior, based
on inferences from the given set of executions.
As with static analysis, dynamic analysis encompasses a very broad range
of techniques with different purposes. For example, profilers can provide a
high-level view of the performance of a programming, debuggers can be
used to make explicit the internal state of a program at particular points, and
testing can check that a program is returning the correct outputs.This section
focusses on a selection of dynamic analysis techniques that are particularly
useful from a reverse-engineering perspective.
The chapter is structured as follows. It begins by presenting the process
of tracing—the fundamental step of recording the information about pro-
gram executions, which forms the basis for the subsequent analysis. It then
continues to discuss in broad terms the general relationship between the
inference of software models, and the general Machine Learning problem
of inferring models from given examples. This is followed by the presen-
tation of the more specific challenges of inferring state machines and data
models from traces. The section is, as was the case with the static analysis
section, finished with a discussion of the intrinsic limitations of dynamic
analysis.
4.1.1 Tracing
Software tracing is, in broad terms, the task of recording software executions
and storing them for subsequent analysis. Intuitively, the task is simple. To
monitor program execution, and to feed the name of every significant event
(such as the name of a method call), along with the values of relevant data
parameters and state variables,to a text file. Many modern programming lan-
guages are accompanied by relatively sophisticated trace generators (c.f., the
Eclipse TPTP tracing plugin for Java3 , or the Erlang OTP built-in tracing
functions4 ).
A trace is, put simply, a sequence of observations of the system. Each
observation might contain an element of control (e.g., a method-entry point,
or some other location in the code), and an associated data state (variable
values, object-types, etc.). In simple terms, for the purpose of discussion here,
we envisage a trace of n observations in the following simple format:
T = (p0 , D0 ), . . . , (pn , Dn )
Each observation takes the form p, D, where p is some point in the code
(this same point may feature in several observations within the same trace),
and D represents the state of the various relevant data variables at that point.
To produce a program trace, it is first necessary to determine what needs
to be traced. Tracing is an expensive process; recording information is time
consuming, trace data can consume large volumes of storage space, and traces
can be time consuming to analyze.The question of what to trace depends on
the nature of the analysis to be carried out. The question is critical because
recording too much information can lead to traces that become unman-
ageable, and can lead to unintended side-effects that affect the subsequent
behavior of the program (both of these problems are elaborated below).
The default option of recording everything at every point becomes rapidly
intractable for all but the simplest programs. To provide an intuition, we can
draw upon an example of a trace in JHotDraw (a Java drawing problem
that is popular for case-studies in the Software Engineering community—
see Fig. 16). The system (version 5.2) is relatively simple, consisting of 142
3 http://www.eclipse.org/tptp/.
4 http://www.erlang.org/doc/.
Reverse-Engineering Software Behavior 33
classes. However, the trace that captures the sequence of method signatures
involved in the (roughly four-second long) process of starting the program,
drawing the rectangle shown in the figure, and closing the window, contains
a total of 2426 method invocations. And this is still relatively lightweight; the
trace only traces method-entry points (omitting individual statements, data
values, and object-identities), which would lead to an order-of-magnitude
increase in the size of the trace.
This illustrates an important trade-off between the utility of the traces
and their scale. On the one hand it is important to collect all of the infor-
mation that is necessary for the analysis in question. If we are constructing
a behavioral model, this should include all of the information at each point
during the execution that is relevant to the behavior that is of interest. On
the other hand,it is important to avoid an information overload,which could
reduce the accuracy of the model, and increase the expense of the analysis.
There is also the question of which traces to collect. Programs tend to accept
inputs from an infinite domain. Given that dynamic analysis techniques are
restricted to a finite sample of these, it is vital that they are in some sense
representative of “general” or “routine” program usage. Otherwise the results
of the dynamic analysis risk become biased.
This tension between scale and the need for a representative set of traces
represents the core of the dynamic analysis challenge. This is exacerbated
when the analyst lacks an in-depth familiarity with the program (which
34 Neil Walkinshaw
Fig. 17. Illustration of the problem of inferring behavior from limited data points.
Input Categories
lim o s.size()
One way to attenuate this problem is to prioritize the test cases. Certain
category combinations will be more critical than others—some may elicit a
greater variance of program behavior than others. Therefore, if it is going to
be impossible to exhaustively execute the test cases,it makes sense to concen-
trate on the most important ones or, if possible, to order the combinations
according to some order of importance, so that the key combinations are
guaranteed to be executed.
vulnerable to the first problem mentioned above). This will, for a given
Java class, generate extensive numbers of inputs (in the form of JUnit tests),
regardless of the complexity of the input parameter objects.
Fig. 18. Sample trace for the push method, containing 27 trace elements.
Fig. 19. Output from the NNge Algorithm—the numbers in parentheses after each rule
indicate the number of trace observations that support a given rule.
some computational process. The systems for which they were developed
might produce noisy meteorological data, or arbitrary heights and genders of
shoppers in a supermarket,or share-prices in a recession.They do not tend to
look for explicit,discrete relationships between variables,but instead focus on
statistical, probabilistically justified correlations and clusterings, which tend
to produce models that seem counterintuitive when the system in question
is a discrete logical software system.
===========================================================================
source.BoundedStack.push(java.lang.Object):::ENTER
o != null
o.getClass() == java.lang.String.class
===========================================================================
source.BoundedStack.push(java.lang.Object):::EXIT17
return == true
===========================================================================
source.BoundedStack.push(java.lang.Object):::EXIT17;condition="return == true"
===========================================================================
source.BoundedStack.push(java.lang.Object):::EXIT21
this.lim one of { 2, 5 }
return == false
===========================================================================
source.BoundedStack.push(java.lang.Object):::EXIT21;condition="not(return == true)"
===========================================================================
source.BoundedStack.push(java.lang.Object):::EXIT
this.lim == orig(this.lim)
this.s == orig(this.s)
(return == false) ==> (this.lim one of { 2, 5 })
(return == true) ==> (this.lim one of { 2, 5, 100 })
===========================================================================
source.BoundedStack.push(java.lang.Object):::EXIT;condition="return == true"
return == true
===========================================================================
source.BoundedStack.push(java.lang.Object):::EXIT;condition="not(return == true)"
this.lim one of { 2, 5 }
return == false
===========================================================================
Fig. 20. Daikon output for the push method, using the trace from Fig. 18. The points
EXIT17 and EXIT21 represent the two individual return statements. The EXIT point covers
both of these return points.
Fig. 21. Set of traces arranged into a prefix-tree at the top, and the final “merged” state
machine on the bottom.
ebookbell.com