Rewriting-based Techniques for Runtime Verification
Grigore Roşu∗
Klaus Havelund
[email protected]
[email protected]
Department of Computer Science
University of Illinois at Urbana-Champaign
Kestrel Technology
NASA Ames Research Center
Abstract
Techniques for efficiently evaluating future time Linear Temporal Logic (abbreviated LTL)
formulae on finite execution traces are presented. While the standard models of LTL are infinite
traces, finite traces appear naturally when testing and/or monitoring real applications that only
run for limited time periods. A finite trace variant of LTL is formally defined, together with
an immediate executable semantics which turns out to be quite inefficient if used directly, via
rewriting, as a monitoring procedure. Then three algorithms are investigated. First, a simple
synthesis algorithm for monitors based on dynamic programming is presented; despite the efficiency of the generated monitors, they unfortunately need to analyze the trace backwards, thus
making them unusable in most practical situations. To circumvent this problem, two rewritingbased practical algorithms are further investigated, one using rewriting directly as a means for
online monitoring, and the other using rewriting to generate automata-like monitors, called
binary transition tree finite state machines (and abbreviated BTT-FSMs). Both rewriting algorithms are implemented in Maude, an executable specification language based on a very efficient
implementation of term rewriting. The first rewriting algorithm essentially consists of a set of
equations establishing an executable semantics of LTL, using a simple formula transforming
approach. This algorithm is further improved to build automata on-the-fly via caching and
reuse of rewrites (called memoization), resulting in a very efficient and small Maude program
that can be used to monitor program executions. The second rewriting algorithm builds on the
first one and synthesizes provably minimal BTT-FSMs from LTL formulae, which can then be
used to analyze execution traces online without the need for a rewriting system. The presented
work is part of an ambitious runtime verification and monitoring project at NASA Ames, called
PathExplorer, and demonstrates that rewriting can be a tractable and attractive means for
experimenting and implementing logics for program monitoring.
1
Introduction and Motivation
Future time Linear Temporal Logic, abbreviated LTL, was introduced by Pnueli in 1977 [51] (see
also [44, 45]) for stating properties about reactive and concurrent systems. LTL provides temporal
operators that refer to the future/remaining part of an execution trace relative to a current point of
reference. The standard models of LTL are infinite execution traces, reflecting the behavior of such
systems as ideally always being ready to respond to requests. Methods, such as model checking,
have been developed for proving programs correct with respect to requirements specified as LTL
formulae. Several systems are currently being developed that apply model checking to software
∗
Supported in part by joint NSF/NASA grant CCR-0234524.
1
systems written in Java, C and C++ [2, 11, 35, 9, 50, 18, 61, 27, 62]. However, for very large
systems, there is little hope that one can actually prove correctness, and one must in those cases
rely on debugging and testing. In the context of highly reliable and/or safety critical systems, one
would actually want to monitor a program execution during operation and to determine whether
it conforms to its specification. Any violation of the specification can then be used to guide the
execution of the program into a safe state, either manually or automatically. In this paper we
describe a collection of algorithms for monitoring program executions against LTL formulae. It is
demonstrated how term rewriting, and in particular the Maude rewriting system [7], can be used
to implement some of these algorithms very efficiently and conveniently.
The work presented in this paper has been started as part of, and stimulated by, the PathExplorer project at NASA Ames, and in particular the Java PathExplorer (JPaX) tool [28, 29]
for monitoring Java programs. JPaX facilitates automated instrumentation of Java byte-code,
currently using Compaq’s Jtrek which is not public anymore, but soon using BCEL [10]. The instrumented code emits relevant events to an observer during execution (see Figure 1). The observer
can be running a Maude [7] process as a special case, so Maude’s rewriting engine can be used to
drive a temporal logic operational semantics with program execution events. The observer may
run on a different computer, in which case the events are transmitted over a socket. The system
is driven by a specification, stating what properties to be proved and what parts of the code to be
instrumented. When the observer receives the events it dispatches these to a set of observer modules, each module performing a particular analysis that has been requested. In addition to checking
temporal logic requirements, modules have also been programmed to perform error pattern analysis
of multi-threaded programs, predicting deadlock and datarace potentials.
Specifications
Instrumentation
Verification
Java
Program
Observer
Deadlock
Bytecode
Dispatcher
Event Stream
Compile
Datarace
LTL
Maude
Instrument
...
Instrumented
Bytecode
Execute
(JVM)
Figure 1: Overview of JPaX .
Using temporal logic in testing is an idea of broad practical and theoretical interest. One
example is the commercial Temporal Rover and DBRover tools [12, 13], in which LTL properties
are translated into code, which is then inserted at chosen positions in the program and executed
whenever reached during program execution. The MaC tool [43, 38] is another example of a
runtime monitoring tool. Here, Java byte-code is automatically instrumented to generate events
of interest during the execution. Of special interest is the temporal logic used in MaC, which
2
can be classified as a past time interval logic convenient for expressing monitoring properties in
a succinct way. All the systems above try to discharge the program execution events as soon as
possible, in order to minimize the space requirements. In contrast, a technique is proposed in
[39] where the execution events are stored in an SQL database at runtime and then analyzed by
means of queries after the program terminates. The PET tool, described in [24, 23, 22], uses a
future time temporal logic formula to guide the execution of a program for debugging purposes. Java
MultiPathExplorer [58] is a tool which checks a past time LTL safety formula against a partial order
extracted online from an execution trace. POTA [56] is another partial order trace analyzer system.
Java-MoP [5] is a generic logic monitoring tool encouraging “monitoring-oriented programming” as
a paradigm merging specification and implementation. Complexity results for testing a finite trace
against temporal formulae expressed in different temporal logics are investigated in [46]. Algorithms
using alternating automata to monitor LTL properties are proposed in [16], and a specialized LTL
collecting statistics along the execution trace is described in [15]. Various algorithms to generate
testing automata from temporal logic formulae are discussed in [52, 49], and [17] presents a Büchi
automata inspired algorithm adapted to finite trace LTL.
The major goal of this paper is to present rewriting-based algorithms for effectively and efficiently evaluating LTL formulae on finite execution traces online, that is, by processing each event
as it arrives. An important contribution of this paper is to show how a rewriting system, such
as Maude, makes it possible to experiment with monitoring logics very efficiently and elegantly,
and furthermore can be used as a practical program monitoring engine. This approach allows one
to formalize ideas in a framework close to standard mathematics. The presented algorithms are
considered in the context of JPaX, but they can be easily adapted and used within other monitoring frameworks. We claim that the techniques presented in this paper, even though applied to
LTL, are in fact generic and can be easily applied to other logics for monitoring. For example,
in [54, 57] we applied the same generic, “formula transforming”, techniques to obtain rewriting
based algorithms for situations in which the logic for monitoring was replaced by extended regular
expressions (regular expressions with complement).
A non-trivial application of the rewriting based techniques presented in this paper is X9, a
test-case generation and monitoring environment for a software system that controls the planetary
NASA rover K9. This collaborative effort is described in more detail in [1] and it will be presented
in full detail elsewhere soon. The rover controller, programmed in 35,000 lines of C++, essentially
executes plans, where a plan is a tree-like structure consisting of actions and sub-actions. The leaf
actions control various hardware on the rover, such as for example the camera and the wheels.
The execution of a plan must cause the actions to be executed in the right order and must satisfy
various time constraints, also part of the plan. Actions can start and eventually either terminate
successfully or fail. Plans can specify how failed sub-actions can propagate upwards.
Testing the rover controller consists of generating plans and then monitoring that the plan
actions are executed in the right order and that failures are propagated correctly. X9 automatically
generates plans from a “grammar” of the structure of plans, using the Java PathFinder model
checker [62]. For each plan, a set of temporal formulae that an execution of the plan must satisfy
is also generated. For example, a plan may state that an action a should be executed by first
executing a sub-action a1 and then a sub-action a2 , and that the failure of any of the sub-actions
should not propagate: action a should eventually succeed, regardless of whether a1 or a2 fails. The
generated temporal formulae will state these requirements, such as for example [](start(a) ->
<>succeed(a)) saying that “it is always the case ([]) that when action a starts, then eventually
3
(<>) it terminates successfully”, and execution traces are monitored against them.
X9 is currently being turned into a mature system to be used by the developer. It is completely
automated, generating a web-page containing all the warnings found. The top-level web-page
identifies all the test-cases that have failed (by violating some of the temporal properties), each
linked to a web-page containing specifics such as the plan, the execution trace, and the properties
that are violated. X9 has itself been tested by seeding errors into the rover controller code. The
automated monitoring relieves the programmer from manually analyzing printed execution traces.
Extending the logic with real-time, as is planned in future work, is crucial for this application since
plans are heavily annotated with time constraints.
In Section 2, based on our experience, we give a rough classification of monitoring and runtime analysis algorithms by considering three important criteria. A first criterion is whether the
execution trace of the monitored or analyzed program needs to be stored or not. Storing a trace
might be very useful for specific types of analysis because one could have random access to events,
but storing an execution trace is an expensive operation in practice, so sometimes trace-storing
algorithms may not be desirable. A second criterion regards the synchronicity of the monitor,
more precisely whether the monitor is able to react as soon as the specification or the requirement
has been violated. Synchronicity may often trigger running a validity checker for the logic under
consideration, which is typically a very expensive task. Finally, monitoring and analysis algorithms
can also be classified as “predictive” versus “exact”, where the “exact” ones monitor the observed
execution trace as a flat list of events, while the predictive algorithms try to guess potential erroneous behaviors of programs that can occur under different executions. All the algorithms in this
paper are exact.
This paper requires a certain amount of mathematical notions and notations, which we introduce
in Section 3 together with Maude [7], a high-performance system supporting both membership
equational logic [48] and rewriting logic [47]. The current version of Maude can do more than 3
million rewritings per second on standard PCs, and its compiled version is intended to support
more than 15 million rewritings per second1 , so it can quite well be used as an implementation
language.
Section 4 defines the finite trace variant of linear temporal logic that we use in the rest of the
paper. We found, by carefully analyzing several practical examples, that the most appropriate
assumption to make at the end of the trace is that it is stationary in the last state. Then we define
the semantics of the temporal operators using their usual meaning in infinite trace LTL, where the
finite trace is infinitely extended by repeating the last state. Another option would be to consider
that all atomic predicates are false or true in the state following the last one, but this would be
problematic when inter-dependent predicates are involved, such as “gate-up” and “gate-down”.
In previous work we described a technique which synthesizes efficient dynamic programming
algorithms for checking LTL formulae on finite execution traces [53]. Even though this algorithm
is not dependent on rewriting (but it could be easily implemented in Maude by rewriting as we
did with its dual variant for past time LTL [32, 5]), for the sake of completeness we present it
in some detail in Section 5. This algorithm evaluates a formula bottom-up for each point in the
trace, going backwards from the final state towards the initial state. Unfortunately, despite its
linear complexity, this algorithm cannot be used online because it is both asynchronous and tracestoring. In [33, 25, 32] we dualize this technique and apply it to past time LTL, in which case the
trace more naturally can be examined in a forwards direction synchronously.
1
Personal communication by José Meseguer.
4
Section 6 presents our first practical rewriting-based algorithm, which can directly monitor an
LTL formula. This algorithm originates in [31, 53] and it was partially presented at the Automated
Software Engineering conference [30]. The algorithm is expressed as a set of equations establishing
an executable semantics of LTL using a simple formula transforming approach. The idea is to
rewrite or transform an LTL monitoring requirement formula ϕ when an event e is received, to
a formula ϕ{e}, which represents the new requirement that the monitored system should fulfill
for the remaining part of the trace. This way, the LTL formula to monitor “evolves” into other
LTL formulae by subsequent transformations. We show, however, that the size of the evolving
formula is in the worst-case exponentially bounded by the size of the original LTL formula, and
also that an exponential space explosion cannot be avoided in certain unfortunate cases. The
efficiency of this rewriting algorithm can be improved by almost an order of magnitude by caching
and reusing rewrites (a.k.a. “memoization”), which is supported by Maude. This algorithm is
often synchronous, though there are situations in which it misses reporting a violation at the exact
event when it occurs. The violation is, however, detected at a subsequent event. This algorithm
can be relatively easily transformed into a synchronous one if one is willing to pay the price of
running a validity checker, like the one presented in Subsection 7.3, after processing each event.
The practical result of Section 6 is a very efficient and small Maude program that can be used to
monitor program executions. The decision to use Maude has made it very easy to experiment with
logics and algorithms in monitoring.
We finally present an alternative solution to monitoring LTL in Section 7, where a rewritingbased algorithm is used to generate an optimal special observer from an LTL formula. By optimality
is meant everything one may expect, such as minimal number of states, forwards traversal of execution traces, synchronicity, efficiency, but also less standard optimality features, such as transiting
from one state to another with a minimum amount of computation. In order to effectively do
this we introduce the notion of binary transition tree (BTT), as a generalization of binary decision
diagrams (BDD) [4], whose purpose is to provide an optimal order in which state predicates need
to be evaluated to decide the next state. The motivation for this is that in practical applications
evaluating a state predicate is a time consuming task, such as for example to check whether a vector
is sorted. The associated finite state machines are called binary transition tree finite state machines
(BTT-FSM). BTT-FSMs can be used to analyze execution traces without the need for a rewriting
system, and can hence be used by observers written in traditional programming languages. The
BTT-FSM generator, which includes a validity checker, is also implemented in Maude and has
about 200 lines of code in total.
2
A Taxonomy of Runtime Analysis Techniques
A runtime analysis technique is regarded in a broad sense in this section; it can be a method or a
concrete algorithm that analyzes the execution trace of a running program and concludes a certain
property about that program. Runtime analysis algorithms can be arbitrarily complex, depending
upon the kind of properties to be monitored or analyzed. Based on our experience with current
procedures implemented in JPaX, in this section we make an attempt to classify runtime analysis
techniques. The three criteria below are intended to be neither exhaustive nor always applicable,
but we found them quite useful in practice. They are not specific to any particular logic or approach,
so we present them before we introduce our logic and algorithms. In fact, this taxonomy will allow
us to appropriately discuss the benefits and drawbacks of our algorithms presented in the rest of
5
the paper.
2.1
Trace Storing versus Non-Storing Algorithms
As events are received from the monitored system, a runtime analysis algorithm typically maintains
a state which allows it to reason about the monitored execution trace. Ideally, the amount of
information needed to be stored by the monitor in its state depends only upon the property to
be monitored and not upon the number of already processed events. This is desired because, due
to the huge amount of events that can be generated during a monitoring session, one would want
one’s monitoring algorithms to work in linear time with the number of events processed.
There are, however, situations where it is not possible or practically feasible to use storage
whose size is a function of only the monitoring requirement. One example is that of monitoring
extended regular expressions (ERE), i.e., regular expressions enriched with a complement operator.
As shown by the success of scripting languages like PERL or PYTHON, software developers tend
to understand and like regular expressions and feel comfortable to describe patterns using those,
so ERE is a good candidate formalism to specify monitoring requirements (we limit ourselves to
only patterns described via temporal logics in this paper though).
It is however known that ERE to automata translation algorithms suffer from a non-elementary
state explosion problem, because a complement operation requires nondeterministic-to-deterministic
automata conversions, which yield exponential blowups in the number of states. Since complement
operations can be nested, generating automata from EREs may often not be feasible in practice.
Fortunately, there are algorithms which avoid this state explosion problem, at the expense of having to store the execution trace and then, at the end of the monitoring session, to analyze it by
traversing it forwards and backwards many times. The interested reader is referred to [36] for a
O(n3 m) dynamic programming algorithm (n is the length of the execution trace and m is the size
of the ERE), and to [63, 42] for O(n2 m) non-trivial algorithms.
Based on these observations, we propose a first criterion to classify monitoring algorithms,
namely on whether they store or do not store the execution trace. In the case of EREs, trace
storing algorithms are polynomial in the size of the trace and linear in the ERE requirement, while
the non-storing ones are linear in the size of the trace and highly exponential in the size of the
requirement. In this paper we show that trace storing algorithms for linear temporal logic can be
linear in both the trace and the requirement (see Section 5), while trace non-storing ones are linear
in the size of the trace but simply exponential in the size of the requirement.
Trace non-storing algorithms are apparently preferred, but, however, their size can be so big
that it could make their use unamenable in certain important situations. One should carefully
analyze the trade-offs in order to make the best choice in a particular situation.
2.2
Synchronous versus Asynchronous Monitoring
There are many safety critical applications in which one would want to report a violation of a
requirement as soon as possible, and to not allow the monitored program to take any further
action once a requirement is violated. We call this desired functionality synchronous monitoring.
Otherwise, if a violation can only be detected after the monitored program executes several more
steps or after it is stopped and its entire execution trace is needed to perform the analysis, then we
call it asynchronous monitoring.
6
The dynamic programming algorithm presented in Section 5 is not synchronous, because one
can detect a violation only after the program is stopped and its execution trace is available for
backwards traversal. The algorithm presented in Section 6 is also asynchronous in general because
there are universally false formulae which are detected so only at the end of an execution trace or
only after several other execution steps. Consider, for example, that one monitors the finite trace
LTL formula !<>([]A \/ []!A), which is false because at the end of any execution trace A either
holds or not, or the formula ooA /\ oo!A, which is also false but will be detected so only after
two more events. However, the rewriting algorithm in Section 6 is synchronous in many practical
situations. The algorithm in Section 7 is always synchronous, though one should be aware of the
fact that its size may become a problem on large formulae.
In order for an LTL monitor to be synchronous, it needs to implement a validity checker for
finite trace LTL, such as the one in Subsection 7.3 (Figure 6), and call it on the current formula after
each event is processed. Checking validity of a finite trace LTL formula is very expensive (we are
not aware of any theoretical result stating its exact complexity, but we believe that it is PSPACEcomplete, like for standard infinite trace LTL [60]). We are currently considering providing a fully
synchronous LTL monitoring module within JPaX, at the expense of calling a validity checker after
each event, and let the user of the system choose either synchronous or asynchronous monitoring.
There are, however, many practical LTL formulae for which violation can be detected synchronously by the formula transforming rewriting-based algorithm presented in Section 6. Consider
for example the sample formula of this paper, [](green -> !red U yellow), which is violated if and
only if a red event is observed after a green one. The monitoring requirement of our algorithm,
which initially is the formula itself, will not be changed unless a green event is received, in which
case it will change to (!red U yellow) /\ [](green -> !red U yellow). A yellow event will turn it
back into the initial formula, a green event will keep it unchanged, but a red event will turn it into
false. If this is the case, then the monitor declares the formula violated and appropriate actions
can be taken. Notice that the violation was detected exactly when it occurred. A very interesting,
practical and challenging problem is to find criteria that say when a formula can be synchronously
monitored without the use of a validity checker.
2.3
Predictive versus Exact Analysis
An increasingly important class of runtime analysis algorithms are concerned with predicting anomalies in programs from successful observed executions. One such algorithm can be easily obtained
by slightly modifying the wait-for-graph algorithm, which is typically used to detect when a system
is in a deadlock state, to make it predict deadlocks. One way to do this is to not remove synchronization objects from the wait-for-graph when threads/processes release them. Then even though a
system is not deadlock, a warning can be reported to users if a cycle is found in the wait-for-graph,
because that represents a potential of a deadlock.
Another algorithm falling into the same category is Eraser [55], a datarace prediction procedure.
For each shared memory region, Eraser maintains a set of active locks which protect it, which is
intersected with the set of locks held by any accessing thread. If the set of active locks ever becomes
empty then a warning is issued to the user, with the meaning that a potential unprotected access
can take place. Both the deadlock and the datarace predictive algorithms are very successful in
practice because they scale well and find many of the errors they are designed for. We have also
implemented improved versions of these algorithms in Java PathExplorer.
We are currently also investigating predictive analysis of safety properties expressed using past
7
time temporal logic, and a prototype system called Java MultiPathExplorer is being implemented
[58]. The main idea here is to instrument Java classes to emit events timestamped by vector clocks
[14], thus enabling the observer to extract a partial order reflecting the causal dependency on the
memory accesses of the multithreaded program. If any linearization of that inferred partial order
leads to a violation of the safety property then a warning is generated to the user, with the meaning
that there can be executions of the multithreaded program, including the current one, which violate
the requirements.
In this paper we restrict ourselves to only exact analysis of execution traces. That means that
the events in the trace are supposed to have occurred exactly in the received order (this can be easily
enforced by maintaining a logical clock, then timestamping each event with the current clock, and
then delivering the messages in increasing order of timestamps), and that we only check whether
that particular order violates the monitoring requirements or not. Techniques for predicting future
time LTL violations will be investigated elsewhere soon.
Although the taxonomy discussed in this section is intended to only be applied to tools, the
problem domain may also admit a similar taxonomy. While such a taxonomy seems to be hard to
accomplish in general, it would certainly be very useful because it would allow one to choose the
proper runtime analysis technique for a given system and set of properties. However, like this paper
shows, it is often the case that one can choose among several types of runtime analysis techniques
for a given problem domain.
3
Preliminaries
In this section we recall notions and notations that will be used in the paper, including membership
equational logic, term rewriting, Maude notation, and (infinite trace) linear temporal logics.
3.1
Membership Equational Logic
Membership equational logic (MEL) extends many- and order-sorted equational logic by allowing
memberships of terms to sorts in addition to the usual equational sentences. We only recall those
MEL notions which are necessary for understanding this paper; the interested reader is referred to
[48, 3] for a comprehensive exposition of MEL.
3.1.1
Basic Definition
A many-kinded algebraic signature (K, Σ) consists of a set K and a (K ⋆ × K)-indexed set Σ =
{Σk1 k2 ...kn ,k | k1 , k2 , ..., kn , k ∈ K} of operations, where an operation σ ∈ Σk1 k2 ...kn ,k is written
σ : k1 k2 ...kn → k. A membership signature Ω is a triple (K, Σ, π) where K is a set of kinds, Σ
is a K-kinded algebraic signature, and π: S → K is a function that assigns to each element in its
domain, called a sort, a kind. Therefore, sorts are grouped according to kinds and operations are
defined on kinds. For simplicity, we will call a “membership signature” just a “signature” whenever
there is no confusion.
For a many-kinded signature (K, Σ), a Σ-algebra A consists of a K-indexed set {Ak | k ∈ K}
together with interpretations of operations σ : k1 k2 ...kn → k into functions Aσ : Ak1 × Ak2 × · · · ×
Akn → Ak . For any given signature Ω = (K, Σ, π), an Ω-membership algebra A is a Σ-algebra
together with a set As ⊆ Aπ(s) for each sort s ∈ S. A particular algebra, called term algebra, is of
special interest. Given a K-kinded signature Σ and a K-indexed set of variables X, let TΣ (X) be
8
the algebra of Σ-terms over variables in X extending X iteratively as follows: if σ : k1 k2 ...kn → k
and t1 ∈ TΣ,k1 (X), t2 ∈ TΣ,k2 (X), ..., tn ∈ TΣ,kn (X), then σ(t1 , t2 , ..., tn ) ∈ TΣ,k (X).
Given a signature Ω and a K-indexed set of variables X, an atomic (Ω, X)-equation has the
form t = t′ , where t, t′ ∈ TΣ,k (X), and an atomic (Ω, X)-membership has the form t : s, where s
is a sort and t ∈ TΣ,π(s) (X). An Ω-sentence in MEL has the form (∀X) a if a1 ∧ . . . ∧ an , where
a, a1 , . . . , an are atomic (Ω, X)-equations or (Ω, X)-memberships, and {a1 , . . . , an } is a set (no
duplications). If n = 0, then the Ω-sentence is called unconditional and written (∀X) a. Equations
are called rewriting rules when they are used only from left to right, as it will happen in this paper.
Given an Ω-algebra A and a K-kinded map θ: X → A, then A, θ |=Ω t = t′ iff θ(t) = θ(t′ ), and
A, θ |=Ω t : s iff θ(t) ∈ As . A satisfies (∀X) a if a1 ∧...∧an , written A |=Ω (∀X) a if a1 ∧ ... ∧ an ,
iff for each θ: X → A, if A, θ |=Ω a1 and ... and A, θ |=Ω an , then A, θ |=Ω a.
An Ω-specification (or Ω-theory) T = (Ω, E) in MEL consists of a signature Ω and a set E of
Ω-sentences. An Ω-algebra A satisfies (or is a model of) T = (Ω, E), written A |= T , iff it satisfies
each sentence in E.
3.1.2
Inference Rules
MEL admits complete deduction (see [48], where the rule of congruence is stated in a somewhat
different but equivalent way). In the congruence rule below, σ ∈ Σk1 ...ki ,k , W is a set of variables
w1 : k1 , . . . , wi−1 : ki−1 , wi+1 : ki+1 , . . . , wn : kn , and σ(W, t) is a shorthand for the term
σ(w1 , . . . , wi−1 , t, wi+1 , . . . , wn )):
(1) Reflexivity :
E ⊢Ω (∀X) t = t
(2) Symmetry :
E ⊢Ω (∀X) t = t′
E ⊢Ω (∀X) t′ = t
(3) Transitivity :
E ⊢Ω (∀X) t = t′ , E ⊢Ω (∀X) t′ = t′′
E ⊢Ω (∀X) t = t′′
(4) Congruence :
E ⊢Ω (∀X) t = t′
E ⊢Ω (∀X, W ) σ(W, t) = σ(W, t′ ), for each σ ∈ Σ
(5) Membership :
E ⊢Ω (∀X) t = t′ , E ⊢Ω (∀X) t : s
E ⊢Ω (∀X) t′ : s
(6) Modus Ponens :
Given a sentence in E
(∀Y ) t = t′ if t1 = t′1 ∧...∧ tn = t′n ∧ w1 : s1 ∧...∧ wm : sm
(resp. (∀Y ) t : s if t1 = t′1 ∧...∧ tn = t′n ∧ w1 : s1 ∧...∧ wm : sm )
and θ: Y → TΣ (X) s.t. for all i ∈ {1, .., n} and j ∈ {1, .., m}
E ⊢Ω (∀X) θ(ti ) = θ(t′i ), E ⊢Ω (∀X) θ(wj ) : sj
E ⊢Ω (∀X) θ(t) = θ(t′ )
(resp. E ⊢Ω (∀X) θ(t) : s)
The rules above can therefore prove any unconditional equation or membership that is true in
all membership algebras satisfying E. In order to derive conditional statements, we will therefore
consider the standard technique adapting the “deduction theorem” to equational logics, namely
9
deriving the conclusion of the sentence after adding the condition as an axiom; in order for this
procedure to be correct, the variables used in the conclusion need to be first transformed into
constants. All variables can be transformed into constants, so we only consider the following
simplified rules:
(7) Theorem of Constants :
E ⊢Ω∪X (∀∅) a if a1 ∧ ... ∧ an
E ⊢Ω (∀X) a if a1 ∧ ... ∧ an
(8) Implication Elimination :
E ∪ {a1 , . . . , an } ⊢Ω (∀∅) a
E ⊢Ω (∀∅) a if a1 ∧ ... ∧ an
Theorem 1 (from [48]) With the notation above, E |=Ω (∀X) a if a1 ∧ ... ∧ an if and only if
E ⊢Ω (∀X) a if a1 ∧ ... ∧ an . Moreover, any statement can be proved by first applying rule (7),
then (8), and then a series of rules (1) to (6).
This theorem is used within the correctness proof of the monitoring algorithm in Section 6.
3.1.3
Initial Semantics and Induction
MEL specifications are often intended to allow only a restricted class of models (or algebras). For
example, a specification of natural numbers defined using the Peano equational axioms would have
many “undesired” models, such as models in which the addition operation is not commutative, or
models in which, for example, 10=0. We restrict the class of models of a MEL specification only
to those which are initial, that is, those which obey the no junk no confusion principle; therefore,
our specifications have initial semantics [20] in this paper. Intuitively, that means that only those
models are allowed in which all elements can be “constructed” from smaller elements and in which
no terms which cannot be proved equal are interpreted to the same elements.
By reducing the class of models, one can enlarge the class of sound inference rules. A major
benefit one gets under initial semantics is that proofs by induction become valid. Since the proof
of correctness for the main algorithm in this paper is done by induction on the structure of the
temporal formula to monitor, it is important for the reader to be aware that the specifications
presented from now on have initial semantics.
3.1.4
Syntactic Sugar Conventions
To make specifications easier to read, the following syntactic sugar conventions are widely accepted:
Subsorts. Given sorts s, s′ with π(s) = π(s′ ) = k, the declaration s < s′ is syntactic sugar for the
conditional membership (∀x : k) x : s′ if x : s.
Operations. If σ ∈ Ωk1 ...kn ,k and s1 , . . . , sn , s ∈ S with π(s1 ) = k1 , . . . , π(sn ) = kn , π(s) = k, then
the declaration σ : s1 · · · sn → s is syntactic sugar for (∀x1 : k1 , . . . , xn : kn ) σ(x1 , . . . , xn ) :
s if x1 : s1 ∧ . . . ∧ xn : sn .
Variables. (∀x : s, X) a if a1 ∧. . .∧an is syntactic sugar for the Ω-sentence (∀x : π(s), X) a if a1 ∧
. . . ∧ an ∧ x : s. With this, the operation declaration σ : s1 · · · sn → s above is equivalent to
(∀x1 : s1 , . . . , xn : sn ) σ(x1 , . . . , xn ) : s.
10
3.2
Maude
Maude [7] is a freely distributed high-performance system in the OBJ [21] algebraic specification
family, supporting both rewriting logic [47] and membership equational logic [48]. Because of its
efficient rewriting engine, able to execute 3 million rewriting steps per second on standard PCs, and
because of its metalanguage features, Maude turns out to be an excellent tool to create executable
environments for various logics, models of computation, theorem provers, and even programming
languages. We were delighted to notice how easily we could implement and efficiently validate our
algorithms for testing LTL formulae on finite event traces in Maude, admittedly a tedious task in
C++ or Java, and hence decided to use Maude at least for the prototyping stage of our runtime
check algorithms.
We informally describe some of Maude’s features via examples in this section, referring the
interested reader to its manual [7] for more details. The examples discussed in this subsection are
not random. On the one hand they show all the major features of Maude that we need, while on
the other hand they are part of our current JPaX implementation; several references to them will
be made later in the paper. Maude supports modularization in the OBJ style. There are various
kinds of modules, but we use only functional modules which follow the pattern “fmod <name> is
<body> endfm”, and which have initial semantics. The body of a functional module consists of a
collection of declarations, of which we are using importation, sorts, subsorts, operations, variables
and equations, usually in this order.
3.2.1
Defining Logics for Monitoring
In the following we introduce some modules that we think are general enough to be used within
any logical environment for program monitoring that one would want to implement by rewriting.
The first one simply defines atomic propositions as an abstract data type having one sort, Atom,
and no operations or constraints:
fmod ATOM is
sort Atom .
endfm
The actual names of atomic propositions will be automatically generated in another module that
extends ATOM, as constants of sort Atom. These will be generated by the observer at the initialization
of monitoring, from the actual properties that one wants to monitor.
An important concept in program monitoring is that of an (abstract) execution trace, which
consists of a finite list of events. We abstract a single event by a list of atoms, those that hold after
the action that generated the event took place. The values of the atomic propositions are updated
by the observer according to the actual state of the executing program and then sent to Maude as
a term of sort Event (more details regarding the communication between the running program and
Maude will be given later):
fmod TRACE is
protecting ATOM .
sorts Event Event* Trace .
subsorts Atom < Event < Event* < Trace .
op empty : -> Event .
op __ : Event Event -> Event [assoc comm id: empty prec 23] .
var A : Atom .
11
eq A A = A .
op _* : Event -> Event* .
op _,_ : Event Trace -> Trace [prec 25] .
endfm
The statement protecting ATOM imports the module ATOM without changing its initial semantics.
The above is a compact way to use mix-fix2 and order-sorted notation to define an abstract data
type of traces: a trace is a comma separated list of events, where an event is itself a set of atoms.
The subsorts declaration declares Atom to be a subsort of Event, which in turn is a subsort of
Event* which is a subsort of Trace. Since elements of a subsort can occur as elements of a supersort
without explicit lifting, we have as a consequence that a single event is also a trace, consisting
of one event. Likewise, an atomic proposition can occur as an event, containing only this atomic
proposition.
Operations can have attributes, such as associativity (A), commutativity (C), identity (I) as
well as precedences, which are written between square brackets. When a binary operation is
declared using the attributes A, C, and/or I, Maude uses built-in efficient specialized algorithms
for matching and rewriting. However, semantically speaking, the A, C, and/or I attributes can be
replaced by there corresponding equations. The attribute prec gives a precedence to an operator3 ,
thus eliminating the need for most parentheses. Notice the special sort Event* which stays for
terminal events, i.e., events that occur at the end of traces. Any event can potentially occur at
the end of a trace. It is often the case that ending events are treated differently, like in the case of
finite trace linear temporal logic; for this reason, we have introduced the operation _* which marks
an event as terminal.
An event is defined as a set of atoms which should in fact be thought of as the set of all
those atoms which “hold” in the new state of the event emitting program. Note the idempotency
equation “eq A A = A”, which ensures that an event is indeed a set. On the other hand, a trace is
a an ordered list of events which can potentially have repetitions of events. For example, the event
“x = 5” can occur several times during the execution of a program. Note that there is no need and
consequently no definition of an empty trace.
Syntax and semantics are basic requirements to any logic. The following module introduces what
we believe are the basic ingredients of monitoring logics, i.e., logics used for specifying monitoring
requirements:
fmod LOGICS-BASIC is
protecting TRACE .
sort Formula .
subsort Atom < Formula .
ops true false : -> Formula .
op [_] : Formula -> Bool .
eq [true] = true .
eq [false] = false .
var
var
var
var
2
3
A : Atom .
T : Trace .
E : Event .
E* : Event* .
Underscores are places for arguments.
The lower the precedence number, the tighter the binding.
12
op
eq
eq
eq
eq
eq
_{_} : Formula Event* -> Formula [prec 10] .
true{E*} = true .
false{E*} = false .
A{A E} = true .
A{E} = false [owise] .
A{E *} = A{E} .
op _|=_ : Trace Formula -> Bool [prec 30] .
eq T |= true = true .
eq T |= false = false .
eq E
|= A = [A{E}] .
eq E,T |= A = E |= A .
endfm
The first block of declarations introduces the sort Formula which can be thought of as a generic
sort for any well-formed formula in any logic. There are two designated formulae, namely true
and false, with the obvious meaning in any monitoring logic. The sort Bool is built-in in Maude
together with two constants true and false, which are different from those of sort Formula, and a
generic operator if_then_else_fi. The “interpretation” operator [_] maps a formula to a Boolean
value. Each logic implemented on top of LOGICS-BASIC is free to define it appropriately; here we
only give the obvious mappings of true and false of Formula into true and false of Bool.
The second block defines the operation _{_} which takes a formula and an event and yields
another formula. The intuition for this operation is that it “evaluates” the formula in the new
state and produces a proof obligation as another formula for the subsequent events. If the returned
formula is true or false then it means that the formula was satisfied or violated, regardless of the
rest of the execution trace; in this case, a message can be returned by the observer. Each logic
will further complete the definition of this operator. Note that the equation “eq A{A E} = true”
speculates Maude’s capability of performing matching modulo associativity, commutativity and
identity (the attributes of the set concatenation on events); it basically says that A{E} is true if E
contains the atom A. The next equation contains the attribute [owise], stating that it should be
applied only if any other equation fails to apply at a particular position.
Finally, the satisfaction relation is defined. Two obvious equations deal with the formulae true
and false. The last two equations state that a trace satisfies an atomic proposition A if evaluating
that atomic proposition A on the first event in the trace yields true. The remaining elements in the
trace do not matter because A is a simple atom, so it refers to only the current state.
3.2.2
Defining Propositional Calculus
A rewriting decision procedure for propositional calculus due to Hsiang [37] is adapted and presented. It provides the usual connectives _/\_ (and), _++_ (exclusive or), _\/_ (or), !_ (negation),
_->_ (implication), and _<->_(equivalence). The procedure reduces tautological formulae to the
constant true and all the others to some canonical form modulo associativity and commutativity.
An unusual aspect of this procedure is that a canonical form consists of an exclusive or of conjunctions. In fact, this choice of basic operators corresponds to regarding propositional calculus as a
Boolean ring rather than as a Boolean algebra. A major advantage of this choice is that normal
forms are unique modulo associativity and commutativity. Even if propositional calculus is very
basic to almost any logical environment, we decided to keep it as a separate logic instead of being
13
part of the logic infrastructure of JPaX. One reason for this decision is that its operational semantics could be in conflict with other logics, for example ones in which conjunctive normal forms are
desired.
An OBJ3 code for this procedure appeared in [21]. Below we give its obvious translation to
Maude together with its finite trace semantics, noticing that Hsiang [37] showed that this rewriting
system modulo associativity and commutativity is Church-Rosser and terminates. The Maude
team was probably also inspired by this procedure, since the builtin BOOL module is very similar.
fmod PROP-CALC is
extending LOGICS-BASIC .
*** Constructors ***
op _/\_ : Formula Formula -> Formula [assoc comm prec 15] .
op _++_ : Formula Formula -> Formula [assoc comm prec 17] .
vars X Y Z : Formula .
eq true /\ X = X .
eq false /\ X = false .
eq X /\ X = X .
eq false ++ X = X .
eq X ++ X = false .
eq X /\ (Y ++ Z) = X /\ Y ++ X /\ Z .
*** Derived operators ***
op _\/_ : Formula Formula -> Formula [assoc prec 19] .
op !_
: Formula -> Formula [prec 13] .
op _->_ : Formula Formula -> Formula [prec 21] .
op _<->_ : Formula Formula -> Formula [prec 23] .
eq X \/ Y = X /\ Y ++ X ++ Y .
eq ! X = true ++ X .
eq X -> Y = true ++ X ++ X /\ Y .
eq X <-> Y = true ++ X ++ Y .
*** Finite trace semantics
var T : Trace .
var E* : Event* .
eq T |= X /\ Y = T |= X and T |= Y .
eq T |= X ++ Y = T |= X xor T |= Y .
eq (X /\ Y){E*} = X{E*} /\ Y{E*} .
eq (X ++ Y){E*} = X{E*} ++ Y{E*} .
eq [X /\ Y] = [X] and [Y] .
eq [X ++ Y] = [X] xor [Y] .
endfm
The statement “extending LOGICS-BASIC” imports the module LOGICS-BASIC with the reserve that
its initial semantics can be extended. The operators “and” and “xor” come from the Maude’s
built-in module BOOL which is automatically imported by any other module.
Operators are declared with special attributes, such as assoc and comm, which enable Maude
to use its specialized efficient internal rewriting algorithms. Once the module above is loaded4 in
Maude, reductions can be done as follows:
reduce a -> b /\ c
reduce a <-> ! b .
4
<->
(a -> b) /\ (a -> c) .
Either by typing it or using the command “in <filename>”.
14
***> should be true
***> should be a ++ b
Notice that one should first declare the constants a, b and c. The last six equations in the module
PROP-CALC are related to the semantics of propositional calculus. The default evaluation strategy for
[_] is eager, so [X] will first evaluate X using propositional calculus reasoning and then will apply
one of the last two equations if needed; these equations will not be applied normally in practical
reductions, they are useful only in the correctness proof stated by Theorem 3.
4
Finite Trace Future Time Linear Temporal Logic
Classical (infinite trace) linear temporal logic (LTL) [51, 44, 45] provides in addition to the propositional logic operators the temporal operators []_ (always), <>_ (eventually), _U_ (until), and o_
(next). An LTL standard model is a function t : N + → 2P for some set of atomic propositions P,
i.e., an infinite trace over the alphabet 2P , which maps each time point (a natural number) into
the set of propositions that hold at that point. The operators have the following interpretation on
such an infinite trace. Assume formulae X and Y. The formula []X holds if X holds in all time points,
while <>X holds if X holds in some future time point. The formula X U Y (X until Y) holds if Y holds
in some future time point, and until then X holds (so we consider strict until). Finally, o X holds for
a trace if X holds in the suffix trace starting in the next (the second) time point. The propositional
operators have their obvious meaning. For example, the formula [](X -> <>Y) is true if for any
time point ([]) it holds that if X is true then eventually (<>) Y is true. It is standard to define a
core LTL using only atomic propositions, the propositional operators !_ (not) and _/\_ (and), and
the temporal operators o_ and _U_, and then define all other propositional and temporal operators
as derived constructs. Standard equations are <>X = true U X and []X = !<>!X.
Our goal is to develop a framework for testing software systems using temporal logic. Tests are
performed on finite execution traces and we therefore need to formalize what it means for a finite
trace to satisfy an LTL formula. We first present a semantics of finite trace LTL using standard
mathematical notation. Then we present a specification in Maude of a finite trace semantics.
Whereas the former semantics uses universal and existential quantification, the second Maude
specification is defined using recursive definitions that have a straightforward operational rewriting
interpretation and which therefore can be executed.
4.1
Finite Trace Semantics
As mentioned in Subsection 3.2.1, a trace is viewed as a non-empty finite sequence of program
states, each state denoting the set of propositions that hold at that state. We shall first outline the
finite trace LTL semantics using standard mathematical notation rather than Maude notation. The
debatable issue here is what happens at the end of the trace. The choice to validate or invalidate
all the atomic propositions does not work in practice, because there might be propositions whose
values are always opposite to each other, such as, for example, “gate up” and “gate down”. Driven
by experiments, we found that a more reasonable assumption is to regard a finite trace as an infinite
stationary trace in which the last event is repeated infinitely.
Assume two total functions on traces, head : Trace → Event returning the head event of a trace
and length returning the length of a finite trace, and a partial function tail : Trace → Trace for
taking the tail of a trace. That is, head(e, t) = head(e) = e, tail(e, t) = t, and length(e) = 1 and
length(e, t) = 1+length(t). Assume further for any trace t, that ti denotes the suffix trace that starts
at position i, with positions starting at 1. The satisfaction relation |= ⊆ Trace × Formula defines
15
when a trace t satisfies a formula f , written t |= f , and is defined inductively over the structure of
the formulae as follows, where A is any atomic proposition and X and Y are any formulae:
t |=
t |=
t |=
t |=
t |=
t |=
t |=
t |=
t |=
true
false
A
X /\ Y
X ++ Y
o X
<>X
[]X
X U Y
iff
iff
iff
iff
iff
iff
iff
iff
iff
true,
false,
A ∈ head(t),
t |= X and t |= Y,
t |= X xor t |= Y,
(if tail(t) is defined then tail (t) |= X else t |= X),
(∃ i ≤ length(t)) ti |= X,
(∀ i ≤ length(t)) ti |= X,
(∃ i ≤ length(t)) (ti |= Y and (∀ j < i) tj |= X).
The semantics of the “next” operator reflects perhaps best the stationarity assumption of last
events in finite traces.
Notice that finite trace LTL can behave quite differently from standard infinite trace LTL. For
example, there are formulae which are not valid in infinite trace LTL but valid in finite trace LTL,
such as <>([]A \/ []!A) for any atomic proposition A, and there are formulae which are satisfiable
in infinite trace LTL and not satisfiable in finite trace LTL, such as the negation of the above.
The formula above is satisfied by any finite trace because the last event/state in the trace either
contains A or it does not.
4.2
Finite Trace Semantics in Maude
Now it can be relatively easily seen that the following Maude specification correctly “defines” the
finite trace semantics of LTL described above. The only important deviation from the rigorous
mathematical formulation described above is that the quantifiers over finite sets of indexes are
expressed recursively.
fmod LTL is
extending PROP-CALC .
*** syntax
op []_ : Formula -> Formula [prec 11] .
op <>_ : Formula -> Formula [prec 11].
op _U_ : Formula Formula -> Formula [prec 14] .
op o_ : Formula -> Formula [prec 11] .
*** semantics
vars X Y : Formula .
var E : Event .
var T : Trace .
eq E
|= o X
= E |= X .
eq E,T |= o X
= T |= X .
eq E
|= <> X = E |= X .
eq E,T |= <> X = E,T |= X or T |= <> X .
eq E
|= [] X = E |= X .
eq E,T |= [] X = E,T |= X and T |= [] X .
eq E
|= X U Y = E |= Y .
eq E,T |= X U Y = E,T |= Y or E,T |= X and T |= X U Y .
endfm
16
Notice that only the temporal operators needed declarations and semantics, the others being already
defined in PROP-CALC and LOGICS-BASIC, and that the definitions that involved the functions head
and tail were replaced by two alternative equations.
One can now directly verify LTL properties on finite traces using Maude’s rewriting engine.
Consider as an example a traffic light that switches between the colors green, yellow, and red. The
LTL property that after green comes yellow, and its negation, can now be verified on a finite trace
using Maude’s rewriting engine, by typing commands to Maude such as:
reduce green, yellow, red, green, yellow, red, green, yellow, red, red
|= [](green -> !red U yellow) .
reduce green, yellow, red, green, yellow, red, green, yellow, red, red
|= !([](green -> !red U yellow)) .
which should return the expected answers, i.e., true and false, respectively. The algorithm above
does nothing but blindly follows the mathematical definition of satisfaction and even runs reasonably fast for relatively small traces. For example, it takes5 about 30ms (74k rewrite steps) to
reduce the first formula above and less than 1s (254k rewrite steps) to reduce the second on traces
of 100 events (10 times larger than the above). Unfortunately, this algorithm does not seem to be
tractable for large event traces, even if run on very fast platforms. As a concrete practical example,
it took Maude 7.3 million rewriting steps (3 seconds) to reduce the first formula above and 2.4
billion steps (1000 seconds) for the second on traces of 1,000 events; it could not finish in one night
(more than 10 hours) the reduction of the second formula on a trace of 10,000 events. Since the
event traces generated by an executing program can easily be larger than 10,000 events, the trivial
algorithm above cannot be used in practice.
A rigorous complexity analysis of the algorithm above is hard (because it has to take into
consideration the evaluation strategy used by Maude for terms of sort Bool) and not worth the
effort. However, a simplified worse-case analysis can be easily made if one only counts the maximum
number of atoms of the form event |= atom that can occur during the rewriting of a satisfaction
term, as if all the Boolean reductions were applied after all the other reductions: let us consider
a formula X = []([](...([]A)...)) where the always operator is nested m times, and a trace T
of size n, and let T (n, m) be the total number of basic satisfactions event |= atom that occur in
the normal form of the term T |= X if no Boolean reductions were applied. Then, the recurrence
formula T (n, m) = T (n − 1, m) + T (n, m − 1) follows immediately from the specification above.
n−1 ) + (n−1 ), it follows that T (n, m) > (m ), that is, T (n, m) = Ω(nm ), which is of
Since (nm ) = (m
n
m−1
course unacceptable.
5
A Backwards, Asynchronous, but Efficient Algorithm
The satisfaction relation above for finite trace LTL can hence be defined recursively, both on the
structure of the formulae and on the size of the execution trace. As is often the case for functions
defined this way, an efficient dynamic programming algorithm can be generated from any LTL
formula. We first show how such an algorithm looks for a particular formula, and then present the
main algorithm generator. The work in this section appeared as a technical report [53], but for a
slightly different finite trace LTL, namely one in which all the atomic propositions were considered
false at the and of the trace. As explained previously in the paper, we are now in the favor of
5
On a 1.7GHz, 1Gb memory PC.
17
a semantics where traces are considered stationary in their last event. The generated dynamic
programming algorithms are as efficient as they can be and one can hope: linear in both the trace
and the LTL formula. Unfortunately, they need to traverse the execution trace backwards, so they
are trace storing and asynchronous. However, a similar but dual technique applies to past time
LTL, producing very efficient forwards and synchronous algorithms [33, 32].
5.1
An Example
The formula we choose below is artificial (and will not be used later in the paper), but contains all four temporal operators. We believe that this example would practically be sufficient
for the reader to foresee the general algorithm presented in the remaining of the section. Let
[]((p U q) -> <>(q -> or)) be an LTL formula and let ϕ1 , ϕ2 , ..., ϕ10 be its subformulae, in breadthfirst order:
ϕ1
ϕ2
ϕ3
ϕ4
ϕ5
ϕ6
ϕ7
ϕ8
ϕ9
ϕ10
=
=
=
=
=
=
=
=
=
=
[]((p U q) -> <>(q -> or)),
(p U q) -> <>(q -> or),
p U q,
<>(q -> or),
p,
q,
q -> or,
q,
or,
r.
Given any finite trace t = e1 e2 ...en of n events, one can recursively define a matrix s[1..n, 1..10] of
Boolean values {0, 1}, with the meaning that s[i, j] = 1 iff ti |= ϕj as follows:
s[i, 10]
s[i, 9]
s[i, 8]
s[i, 7]
s[i, 6]
s[i, 5]
s[i, 4]
s[i, 3]
s[i, 2]
s[i, 1]
=
=
=
=
=
=
=
=
=
=
(r ∈ ei )
s[i + 1, 10]
(q ∈ ei )
s[i, 8] implies s[i, 9]
(q ∈ ei )
(p ∈ ei )
s[i, 7] or s[i + 1, 4]
s[i, 6] or (s[i, 5] and s[i + 1, 3])
s[i, 3] implies s[i, 4]
s[i, 2] and s[i + 1, 1],
for all i < n, where and, or, implies are ordinary Boolean operations and == is the equality
predicate, where s[n, 1..10] are defined as below:
18
s[n, 10]
s[n, 9]
s[n, 8]
s[n, 7]
s[n, 6]
s[n, 5]
s[n, 4]
s[n, 3]
s[n, 2]
s[n, 1]
=
=
=
=
=
=
=
=
=
=
(r ∈ en )
s[n, 10]
(q ∈ en )
s[n, 8] implies s[n, 9]
(q ∈ en )
(p ∈ en )
s[n, 7]
s[n, 6]
s[n, 3] implies s[n, 4]
s[n, 2].
Note again that the trace needs to be traversed backwards, and that the row n of s is filled according
to the stationary view of finite traces in their last event. An important observation is that, like in
many other dynamic programming algorithms, one does not have to store all the table s[1..n, 1..10],
which would be quite large in practice; in this case, one needs only two rows, s[i, 1..10] and s[i +
1, 1..10], which we shall write now and next from now on, respectively. It is now only a simple
exercise to write up the following algorithm:
Input: trace t = e1 e2 ...en
next[10] ← (r ∈ en );
next[9] ← n
⁀ ext[10];
next[8] ← (q ∈ en );
next[7] ← next[8] implies next[9];
next[6] ← (q ∈ en );
next[5] ← (p ∈ en );
next[4] ← next[7];
next[3] ← next[6];
next[2] ← next[3] implies next[4];
next[1] ← next[2];
for i = n − 1 downto 1 do {
now[10] ← (r ∈ ei );
now[9] ← next[10];
now[8] ← (q ∈ ei );
now[7] ← now[8] implies now[9];
now[6] ← (q ∈ ei );
now[5] ← (p ∈ ei );
now[4] ← now[7] or next[4];
now[3] ← now[6] or (now[5] and next[3]);
now[2] ← now[3] implies now[4];
now[1] ← now[2] and next[1];
next ← now }
output(next[1]);
The algorithm above can be further optimized, noticing that only the bits 10, 4, 3 and 1 are
needed in the vectors now and next, as we did for past time LTL in [33, 32]. The analysis of this
algorithm is straightforward. Its time complexity is Θ(n · m) while the memory required is 2 · m
bits, where n is the length of the trace and m is the size of the LTL formula.
19
5.2
Generating Dynamic Programming Algorithms
We now formally describe our algorithm that synthesizes dynamic programming algorithms like
the one above from LTL formulae. Our synthesizer is generic, the potential user being expected to
adapt it to his/her desired target language. The algorithm consists of three main steps:
Breadth First Search. The LTL formula should be first visited in breadth-first search (BFS)
order to assign increasing numbers to subformulae as they are visited. Let ϕ1 , ϕ2 , ..., ϕm be
the list of all subformulae in BFS order. Because of the semantics of finite trace LTL, this
step ensures us that the truth value of ti |= ϕj can be completely determined from the truth
values of ti |= ϕj ′ for all j < j ′ ≤ m and the truth values of ti+1 |= ϕj ′ for all j ≤ j ′ ≤ m.
This recurrence gives the order in which one should generate the code.
Loop Initialization. Before we generate the “for” loop, we should first initialize the vector
next[1..m], which basically gives the truth values of the subformulae on the empty trace.
According to the semantics of LTL, one should fill the vector next backwards. For a given
m ≥ j ≥ 1, next[j] is calculated as follows:
• If ϕj is a variable then next[j] = (ϕj ∈ en ). In a more complex setting, where ϕj was a
state predicate, one would have to evaluate ϕj in the final state in the execution trace;
• If ϕj is !ϕj ′ for some j < j ′ ≤ m, then next[j] = not next[j ′ ], where not is the negation
operation on Booleans (bits);
• If ϕj is ϕj1 Op ϕj2 for some j < j1 , j2 ≤ m, then next[j] = next[j1 ] op next[j2 ], where Op
is any propositional operation and op is its corresponding Boolean operation;
• If ϕj is oϕj ′ , []ϕj ′ , or <>ϕj ′ , then clearly next[j] = next[j ′ ] according to the stationary
semantics of our finite trace LTL;
• If ϕj is ϕj1 U ϕj2 for some j < j1 , j2 ≤ m, then next[j] = next[j2 ] for the same reason as
above.
Loop Generation. Because of the dependences in the recursive definition of finite trace LTL
satisfaction relation, one is expected to visit the remaining of the trace backwards, so the
loop index will vary from n − 1 down to 1. The loop body will update/calculate the vector
now and in the end will move it into the vector next to serve as basis for the next iteration.
At a certain iteration i, the vector now is updated also backwards as follows:
• If ϕj is a variable then now[j] = (ϕj ∈ ei ).
• If ϕj is !ϕj ′ for some j < j ′ ≤ m, then now[j] = not now[j ′ ];
• If ϕj is ϕj1 Op ϕj2 for j < j1 , j2 ≤ m, then now[j] = now[j1 ] op now[j2 ], where Op is
any propositional operation and op is its corresponding Boolean operation;
• If ϕj is oϕj ′ then now[j] = next[j ′ ] since ϕj holds now if and only if ϕj ′ held at the
previous step (which processed the next event, the i + 1-th);
• If ϕj is []ϕj ′ then now[j] = now[j ′ ] and next[j] because ϕj holds now if and only if ϕj ′
holds now and ϕj held at the previous iteration;
• If ϕj is <>ϕj ′ then now[j] = now[j ′ ] or next[j] because of similar reasons as above;
• If ϕj is ϕj1 U ϕj2 for some j < j1 , j2 ≤ m, then now[j] = now[j2 ] or (now[j1 ] and next[j]).
20
After each iteration, next[1] says whether the initial LTL formula is validated by the trace
ei ei+1 ...en . Therefore, the desired output is next[1] after the last iteration. Putting all the above
together, one can now write up the generic pseudocode presented below which can be implemented
very efficiently on any current platform. Since the BFS procedure is linear, the algorithm synthesizes
a dynamic programming algorithm from an LTL formula in linear time and of linear size with the
size of the formula.
The following generic program implements the discussed technique. It takes as input an LTL
formula and generates a “for” loop which traverses the trace of events backwards, thus validating
or invalidating the formula.
Input: LTL formula ϕ
output(“Input: trace t = e1 e2 ...en ”);
let ϕ1 , ϕ2 , ..., ϕm be all the subformulae of ϕ is BFS order
for j = m downto 1 do {
output(“next[”, j, “] ← ”);
if ϕj is a variable then output((ϕj ,“∈ en );”);
if ϕj = !ϕj ′ then output(“not next[”,j ′ , “];”);
if ϕj = ϕj1 Op ϕj2 then output(“next[”,j1 , “] op next[”, j2 , “];”);
if ϕj = oϕj ′ then output(“next[”,j ′ , “];”);
if ϕj = []ϕj ′ then output(“next[”,j ′ , “];”);
if ϕj = <>ϕj ′ then output(“next[”,j ′ , “];”);
if ϕj = ϕj1 U ϕj2 then output(“next[”,j2 , “];”); }
output(“for i = n − 1 downto 1 do {”);
for j = m downto 1 do {
output(“
now[”, j, “] ← ”);
if ϕj is a variable then output((ϕj ,“∈ ei );”);
if ϕj = !ϕj ′ then output(“not now[”,j ′ , “];”);
if ϕj = ϕj1 Op ϕj2 then output(“now[”,j1 , “] op now[”, j2 , “];”);
if ϕj = oϕj ′ then output(“next[”, j ′ , “];”);
if ϕj = []ϕj ′ then output(“now[”, j ′ , “] and next[”, j, “];” );
if ϕj = <>ϕj ′ then output(“now[”, j ′ , “] or next[”, j, “];” );
if ϕj = ϕj1 U ϕj2 then output(“now[”, j2 , “] or (now[”,j1 , “] and next[”, j, “]);”); }
output(“
next ← now; }”);
output(“output next[1];”);
where Op is any propositional connective and op is its corresponding Boolean operator.
The Boolean operations used above are usually very efficiently implemented on any microprocessor and the vectors of bits next and now are small enough to be kept in cache. Moreover, the
dependencies between instructions in the generated “for” loop are simple to analyze, so a reasonable compiler can easily unfold or/and parallelize it to take advantage of machine’s resources.
Consequently, the generated code is expected to run very fast.
The dynamic programming technique presented in this section is as efficient as one can hope,
but, unfortunately, has a major drawback: it needs to traverse the execution trace backwards.
From a practical perspective, that means that the instrumented program is run for some period of
21
time while its execution trace is saved, and then, after the program was stopped, its execution trace
is traversed backwards and (efficiently) analyzed. Besides the obvious inconvenience due to storing
potentially huge execution traces, this method cannot be used to monitor programs synchronously.
6
A Forwards and Often Synchronous Algorithm
In this section we shall present a more efficient rewriting semantics for LTL, based on the idea
of consuming the events in the trace, one by one, and updating a data structure (which is also
a formula) corresponding to the effect of the event on the value of the formula. An important
advantage of this algorithm is that it often detects when a formula is violated or validated before
the end of the execution trace, so, unlike the algorithms above, it is suitable for online monitoring.
Our decision to write an operational semantics this way was motivated by an attempt to program
such an algorithm in Java, where such a solution would be natural. The presented rewriting-based
algorithm is linear in the size of the execution trace and worst-case exponential in the size of the
monitored LTL formula.
6.1
An Event Consuming Algorithm
We will implement this rewriting based algorithm by extending the definition of the event consuming
operation _{_} : Formula Event* -> Formula to temporal operators, with the following intuition.
Assuming a trace E,T consisting of event E followed by trace T, a formula X holds on this trace if
and only if X{E} holds on the remaining trace T. If the event E is terminal then X{E *} holds if and
only if X holds under standard LTL semantics on the infinite trace containing only the event E.
fmod LTL-REVISED is
protecting LTL .
vars X Y : Formula .
var E : Event .
var T : Trace .
eq (o X){E} = X .
eq (o X){E *} = X{E *} .
eq (<> X){E} = X{E} \/ <> X .
eq (<> X){E *} = X{E *} .
eq ([] X){E}
= X{E} /\ [] X .
eq ([] X){E *} = X{E *} .
eq (X U Y){E} = Y{E} \/ X{E} /\ X U Y .
eq (X U Y){E *} = Y{E *} .
op _|-_ : Trace Formula -> Bool .
eq E
|- X = [X{E *}] .
eq E,T |- X = T |- X{E} .
endfm
The rule for the temporal operator []X should be read as follows: the formula X must hold now
(X{E}) and also in the future ([]X). The sub-expression X{E} represents the formula that must hold
on the rest of the trace in order for X to hold now.
As an example, consider again the traffic light controller safety formula [](green -> !red U
yellow), which is first rewritten to [](true ++ green ++ green /\ (true ++ red) U yellow) by
22
the equations in module PROP-CALC. This formula modified by an event green yellow (notice that
two lights can be lit at the same time) yields the rewriting sequence
([](true ++ green ++ green /\ (true ++ red) U yellow)){green yellow} =*=>
(true ++ green{green yellow}
++ green{green yellow} /\ ((true ++ red) U yellow){green yellow})
/\ [](true ++ green ++ green /\ (true ++ red) U yellow)
=*=>
((true ++ red) U yellow){green yellow})
/\ [](true ++ green ++ green /\ (true ++ red) U yellow)
=*=>
(yellow{green yellow} \/ ((true ++ red{green yellow}) /\ (true ++ red) U yellow)
/\ [](true ++ green ++ green /\ (true ++ red) U yellow)
=*=>
[](true ++ green ++ green /\ (true ++ red) U yellow)
which is exactly the original formula, while the same formula transformed by just the event green
yields
([](true ++ green ++ green /\ (true ++ red) U yellow)){green}
=*=>
(true ++ green{green}
++ green{green} /\ ((true ++ red) U yellow){green})
/\ [](true ++ green ++ green /\ (true ++ red) U yellow)
=*=>
((true ++ red) U yellow){green})
/\ [](true ++ green ++ green /\ (true ++ red) U yellow)
=*=>
(yellow{green} \/ ((true ++ red{green}) /\ (true ++ red) U yellow)
/\ [](true ++ green ++ green /\ (true ++ red) U yellow)
=*=>
(true ++ red) U yellow /\ [](true ++ green ++ green /\ (true ++ red) U yellow)
which further modified by an event red yields
(yellow{red} \/ (true ++ red{red}) /\ (true ++ red) U yellow)
/\ ([](true ++ green ++ green /\ (true ++ red) U yellow)){red}
false /\ ([](true ++ green ++ green /\ (true ++ red) U yellow)){red}
false
=*=>
=*=>
When the current formula becomes false, as it happened above, we say that the original formula
has been violated. Indeed, the current formula will remain false for any subsequent trace of events,
so the result of the monitoring session will be false.
Note that the rewriting system described so far obviously terminates, because what it does is
to propagate the current event to the atomic subformulae, replace those by either true or false, and
eventually canonize the newly obtained formula.
Some operators could be defined in terms of others, as is typically the case in the standard
semantics for LTL. For example, we could introduce an equation of the form: <>X = true U X, and
then eliminate the rewriting rule for <>X in the above module. This turns out to be less efficient
in practice though, because more rewrites are needed. This happens regardless of whether one
enables memoization (explained in detail in Subsection 6.3) or not, because memoization brings a
real benefit only when previously processed terms are to be reduced again.
This module eventually defines a new satisfaction relation _|-_ between traces and formulae.
The term T |- X is evaluated now by an iterative traversal over the trace, where each event transforms the formula. Note that the new formula that is generated at each step is always kept small
by being reduced to normal form via the equations in the PROP-CALC module in Subsection 3.2.2.
Our current JPaX implementation of the rewriting algorithm above executes the last two rules
of the module LTL-REVISED outside the main rewriting engine. More precisely, Maude is started
23
in its loop mode [8], which provides the capability of enabling rewriting rules in a reactive system
style: a “state” term is stored, which can then be modified via rewriting rules that are activated
by ASCII text events that are provided via the standard I/O. JPaX starts a Maude process and
assigns the formula to be monitored as its loop mode state. Then, as events are received from
the monitored program, they are filtered and forwarded to the Maude module, which then enables
rewriting on the term X{E}, where X is the current formula and E is the newly received event; the
normal form of this reduction, a formula, is stored as the new loop mode state term. The process
continues until the last event is received. JPaX tags the last event, asking Maude to reduce a
term X{E *}; the result will be either true or false, which is reported to the user at the end of the
monitoring session6 .
A natural question here is how big the stored formula can grow during a monitoring session.
Such a formula will consist of Boolean combinations of sub-formulae of the initial formula, kept
in a minimal canonical form. This can grow exponentially in the size of the initial formula in the
worst-case (see [54] for a related result for extended regular expressions).
Theorem 2 For any formula X of size m and any sequence of events to be monitored E1, E2,
..., En, the formula X{E1}{E2}...{En} needs O(2m ) space to be stored. Moreover, the exponential
space cannot be avoided:
any synchronous or asynchronous forwards monitoring algorithm for LTL
√
m
c
) space, where c is some fixed constant.
requires space Ω(2
Proof: Due to the Boolean ring simplification rules in PROP-CALC, any LTL formula is kept in a
canonical form, which is an exclusive disjunction of conjunctions, where conjuncts have temporal
operators at top. Moreover, after processing any number of events, in our case E1, E2, ..., En,
the conjuncts in the normal form of X{E1}{E2}...{En} are subterms of the initial formula X, each
having a temporal operator at its top. Since there are at most m such subformulae of X, it follows
that there are at most 2m possibilities to combine them in a conjunction. Therefore, one needs
space O(2m ) to store any exclusive disjunction of such conjunctions. This reasoning only applies
on “idealistic” rewriting engines, which carefully optimize space needs during rewriting. It is not
clear to us whether Maude is able to attain this space upper bound in all situations.
For the space lower bound of any finite trace LTL monitoring algorithm, consider a simplified
framework with only two atomic predicate and therefore only four possible states. For simplicity, we
encode these four states by 0, 1, # and $. Consider also some natural number k and the language
Lk = {σ#w#σ ′ $w | w ∈ {0, 1}k and σ, σ ′ ∈ {0, 1, #}∗ }.
This language was previously used in several works [40, 41, 54] to prove lower bounds. The language
can be shown to contain exactly those finite traces satisfying the following LTL formula [41] of size
Θ(k 2 ):
φk = [(!$) U ($ /\ o[](!$))] /\ <>[# /\ on+1 # /\
n
^
((oi 0 /\ []($->oi 0))\/ (oi 1 /\ []($->oi 1)))].
i=1
Let us define an equivalence relation on finite traces in (0 + 1 + #)⋆ . For a σ ∈ (0 + 1 + #)⋆ , define
S(σ) = {w ∈ (0 + 1)k | ∃λ1 , λ2 . λ1 #w#λ2 = σ}. We say that σ1 ≡k σ2 if and only if S(σ1 ) = S(σ2 ).
6
In fact, JPaX reports a similar message also when the current monitoring requirement becomes true or false
at any time during the monitoring process.
24
k
Now observe that the number of equivalence classes of ≡k is 22 ; this is because for any S ⊆ (0+1)k ,
there is a σ such that S(σ) = S.
′
′ 2
Since |φk | = Θ(k 2 ), it follows that
√ there is some constant c such that |φk | ≤ c k for all large
enough k. Let c be the constant 1/ c′ . We will prove this lower bound result
by contradiction.
√
m
c
space for any LTL
Suppose A is an LTL forwards monitoring algorithm that uses less that 2
formulae of large enough size m. We will look at the behavior of the algorithm A on inputs of the
form φk . So m = |φk | ≤ c′ k 2 , and A uses less than 2k space. Since the number of equivalence classes
k
of ≡k is 22 , by the pigeon hole principle there must be two strings σ1 6≡k σ2 such that the memory
of A on φk after reading σ1 $ is the same as the memory after reading σ2 $. In other words, A running
on φk will give the same answer on all traces of the form σ1 $w and σ2 $w. Now since σ1 6≡k σ2 ,
it follows that (S(σ1 ) \ S(σ2 )) ∪ (S(σ2 ) \ S(σ1 )) 6= ∅. Take w ∈ (S(σ1 ) \ S(σ2 )) ∪ (S(σ2 ) \ S(σ1 )).
Then clearly, exactly one out of σ1 $w and σ2 $w is in Lk , and so A running on φk gives the wrong
answer on one of these inputs. Therefore, A is not a correct.
It seems, however, that this worst-case exponential complexity in the size of the LTL formula
is more of theoretical importance than practical, since in general the size of the formula rarely
grew more than twice in our experiments. Verification results are very encouraging and show that
this optimized semantics is orders of magnitude faster than the first semantics. Traces of less than
10,000 events are verified in milliseconds, while traces of 100,000 events never needed more than
3 seconds. This technique scales quite well; we were able to monitor even traces of hundreds of
millions events. As a concrete example, we created an artificial trace by repeating 10 million times
the 10 event trace in Subsection 4.2, and then checked it against the formula [](green -> !red
U yellow). There were needed 4.9 billion rewriting steps for a total of about 1,500 seconds. In
Subsection 6.3 we will see how this algorithm can be made even more efficient, using memoization.
6.2
Correctness and Completeness
In this subsection we prove that the algorithm presented above is correct and complete with respect
to the semantics of finite trace LTL presented in Section 4. The proof is done completely in Maude,
but since Maude is not intended to be a theorem prover, we actually have to generate the proof
obligations by hand. In other words, the proof that follows was not generated automatically.
However, it could have been mechanized by using proof assistants and/or theorem provers like
Kumo [19], PVS [59], or Maude-ITP [6]. We have already done it in PVS, but we prefer to use
only plain Maude in this paper.
Theorem 3 For any trace T and any formula X, T |= X if and only if T |- X.
Proof: By induction, both on traces and formulae. We first need to prove two lemmas, namely
that the following two equations hold in the context of both LTL and LTL-REVISED:
(∀ E : Event, X : Formula) E |= X = E |- X,
(∀ E : Event, T : Trace, X : Formula) E,T |= X = T |= X{E}.
We prove them by structural
to prove the first lemma via
by structural induction on X,
quantified variables E and T,
induction on the formula X. Constants e and x are needed in order
the theorem of constants. However, since we prove these lemmas
we not only have to add two constants e and t for the universally
but also two other constants y and z standing for formulas which
25
can be combined via operators to give other formulas. The induction hypotheses are added to the
following specification via equations. Notice that we merged the two proofs to save space. A proof
assistant like Kumo, PVS or Maude-ITP would prove them independently, generating only the
needed constants for each of them.
fmod PROOF-OF-LEMMAS is
extending LTL .
extending LTL-REVISED .
op e : -> Event .
op t : -> Trace .
ops a b c : -> Atom .
ops y z : -> Formula .
eq e |= y = e |- y .
eq e |= z = e |- z .
eq e,t |= y = t |= y{e} .
eq e,t |= z = t |= z{e} .
eq b{e} = true .
eq c{e} = false .
endfm
It is worth reminding the reader at this stage that the functional modules in Maude have initial
semantics, so proofs by induction are valid. Before proceeding further, the reader should be aware
of the operational semantics of the operation _==_, namely that the two argument terms are first
reduced to their normal forms which are then compared syntactically (but modulo associativity
and commutativity); it returns true if and only if the two normal forms are equal. Therefore, the
answer true means that the two terms are indeed semantically equal, while false only means that
they could not be proved equal; they can still be equal.
reduce
and
and
and
and
and
and
and
and
and
and
and
and
and
and
and
and
and
and
(e
(e
(e
(e
(e
(e
(e
(e
(e
|=
|=
|=
|=
|=
|=
|=
|=
|=
(e,t
(e,t
(e,t
(e,t
(e,t
(e,t
(e,t
(e,t
(e,t
(e,t
a
true
false
y /\ z
y ++ z
[] y
<> y
y U z
o y
|=
|=
|=
|=
|=
|=
|=
|=
|=
|=
==
==
==
==
==
==
==
==
==
true
false
b
c
y /\ z
y ++ z
[] y
<> y
y U z
o y
e
e
e
e
e
e
e
e
e
|||||||||-
==
==
==
==
==
==
==
==
==
==
t
t
t
t
t
t
t
t
t
t
a)
true)
false)
y /\ z)
y ++ z)
[] y)
<> y)
y U z)
o y)
|=
|=
|=
|=
|=
|=
|=
|=
|=
|=
true{e})
false{e})
b{e})
c{e})
(y /\ z){e})
(y ++ z){e})
([] y){e})
(<> y){e})
(y U z){e})
(o y){e}) .
It took Maude 129 reductions to prove these lemmas. Therefore, one can safely add now these
lemmas as follows:
26
fmod LEMMAS is
protecting LTL .
protecting LTL-REVISED .
var E : Event .
var T : Trace .
var X : Formula .
eq E |= X = E |- X .
eq E,T |= X = T |= X{E} .
endfm
We can now prove the theorem, by induction on traces. More precisely, we show:
P(E), and
P(T) implies P(E,T), for all events E and traces T,
where P(T) is the predicate “for all formulas X, T |= X iff T |- X”. This induction schema can be
easily formalized in Maude as follows:
fmod PROOF-OF-THEOREM is
protecting LEMMAS .
op e : -> Event .
op t : -> Trace .
op x : -> Formula .
var X : Formula .
eq t |= X = t |- X .
endfm
reduce e
|= x == e
|- x .
reduce e,t |= x == e,t |- x .
Notice the difference in role between the constant x and the variable X. The first reduction proves
the base case of the induction, using the theorem of constants for the universally quantified variable
X. In order to prove the induction step, we first applied the theorem of constants for the universally
quantified variables E and T, then added P(t) to the hypothesis (the equation “eq t |= X = t |X .”), and then reduced P(e t) using again the theorem of constants for the universally quantified
variable X. Like in the proofs of the lemmas, we merged the two proofs to save space.
6.3
Further Optimization by Memoization
Even though the formula transforming algorithm in Subsection 6.1 can process 100 million events
in about 25 minutes, which is relatively reasonable for practical purposes, it can be significantly
improved by adding only 5 more characters to the existing Maude code presented so far. More
precisely, one can replace the operation declaration
op _{_} : Formula Event* -> Formula [prec 10]
in module LOGICS-BASIC by the operation declaration
op _{_} : Formula Event* -> Formula [memo prec 10]
27
The attribute memo added to an operation declaration instructs Maude to memorize, or cache,
the normal forms of terms rooted in that operation, i.e., those terms will be rewritten only once.
Memoization is implemented by hashing, where the entry in the hash table is given by the term to be
reduced and the value in the hash is its normal form. In our concrete example, memoization has the
effect that any LTL formula will be transformed by a given event exactly once during the monitoring
sequence; if the same formula and the same event occur in the future, the resulting modified formula
is extracted from the hash table without applying any rewriting step. If one thinks of LTL in terms
of automata, then our new algorithm corresponds to building the monitoring automaton on the fly.
The obvious benefit of this technique is that only the needed part of the automaton is built, namely
that part that is reachable during monitoring a particular sequence of events, which is practically
very useful because the entire automaton associated to an LTL formula can be exponential in size,
so storing it might become a problem.
The use of memoization brings a significant improvement in the case of LTL. For example, the
same sequence of 100 million events, which took 1500 seconds using the algorithm presented in
Subsection 6.1, takes only 185 seconds when one uses memoization, for a total of 2.2 rewritings per
processed event and 540,000 events processed per second! We find these numbers amazingly good for
any practical purpose we can think of and believe that, taking into account the simplicity, obvious
correctness and elegance of the rewriting based algorithm (implemented basically by 8 rewriting
rules in LTL-REVISED), it would be hard to argue for any other implementation of LTL monitoring.
One should, however, be careful when one uses memoization because hashing slows down the
rewriting engine. LTL is a happy case where memoization brings a significant improvement, because
the operational semantics of all the operators can be defined recursively, so formulae repeat often
during the monitoring process. However, there might be monitoring logics where memoization
could be less efficient. Such a logic would probably be an extension of LTL with time, allowing
formulae of the form “<5> X” with the meaning “eventually in 5 units of time X”, because of a
potentially very large number of terms to be memoized: <5> X, <4> X, etc. Experimentation is
certainly needed if one designs a new logic for monitoring and wants to use memoization.
7
Generating Forwards, Synchronous and Efficient Monitors
Even though the rewriting based monitoring algorithm presented in the previous section performs
quite well in practice, there can be situations in which one wants to minimize the monitoring
overhead as much as possible. Additionally, despite its simplicity and elegance, the procedure
above requires an efficient rewriting engine modulo associativity and commutativity, which may
not be available or may not be desirable on some monitoring platforms, such as, for example,
within an embedded system.
In this section we give a technique, based on ideas presented previously in the paper, to generate
automata-based optimal monitors for future time LTL formulae. By optimality we here mean
everything one may expect, such as minimal number of states, forwards traversal of execution
traces, synchronicity, efficiency, but also less standard optimality features, such as transiting from
one state to another with a minimum amount of computation. In order to effectively do this
we introduce the notion of binary transition tree (BTT), as a generalization of binary decision
diagrams (BDD) [4], whose purpose is to provide an optimal order in which state predicates need
to be evaluated to decide the next state. The motivation for this is that in practical applications
evaluating a state predicate is a time consuming task, such as for example to check whether a
28
vector is sorted. The associated finite state machines are called binary transition tree finite state
machines (BTT-FSM).
The drawback of generating an optimal BTT-FSM statically, i.e., before monitoring, is the
worst-case double exponential time/space required at startup. Therefore, the algorithm presented
in this section is recommended for situations where the LTL formulae to monitor are relatively small
in size but the runtime overhead is desired to be minimal. It is worth noting that the BTT-FSM
generation process can potentially take place on a machine different from the one performing the
monitoring. In particular, one can think of a WWW fast server offering LTL-to-BTT-FSM services
via the Internet, which can also maintain a database of already generated BTT-FSMs to avoid
regenerating the same monitors.
7.1
Multi-Transition and Binary Transition Tree Finite State Machines
To keep the runtime overhead of monitors low, it is crucial to do as little computation as possible
in order to proceed to the next state. In the sequel we assume that finite state monitors are desired
to efficiently change their state (when a new event is received) to one of possible states s1 , s2 , ...,
sn , under the knowledge that a transition to each such state is enabled deterministically by some
Boolean formula, p1 , p2 , ..., pn , respectively, on atomic state predicates.
Definition 4 Let S be a set whose elements are called states, and let A be another set, whose
elements are called atomic predicates. Let {s1 , s2 , ..., sn } ⊆ S and let p1 , p2 , ..., pn be propositions
over atoms in A (using the usual Boolean combinators presented in Subsection 3.2.2), with the
property that exactly one of them is true at any moment, that is, p1 ∨ p2 ∨ ... ∨ pn holds and for any
distinct pi , pj , it is the case that pi → ¬pj holds. Then we call the n-tuple [p1 ?s1 , p2 ?s2 , ..., pn ?sn ]
a multi-transition (MT) over states S and atomic predicates (or simply atoms) A. Let MT (S, E)
be the set of multi-transitions over states S and atoms A.
Since the rest of the paper is concerned with rather theoretical results, from now on we use
mathematical symbols for the Boolean operators instead of ASCII symbols like in Subsection 3.2.2.
The intuition underlying multi-transitions is straightforward: depending on which of the propositions p1 , p2 , ..., pn is true at a given moment, a corresponding transition to exactly one of the
states s1 , s2 , ..., sn will take place. Formally,
Definition 5 Maps θ : A → {true, false} are called events from now on. With the notation above,
given an event θ, we define a map θMT : MT (S, A) → S as θMT ([p1 ?s1 , p2 ?s2 , ..., pn ?sn ]) = si ,
where θ(pi ) = true; notice that θ(pj ) = false for any 1 ≤ j 6= i ≤ n.
Definition 6 Given an event θ : A → {true, false}, let eθ denote the list of atomic predicates a
with θ(a) = true. There is an obvious correspondence between events as maps A → {true, false}
and events as lists of atomic predicates, which justifies our implementation of events in Subsection
3.2.1. We take the liberty to use either the map or the list notation for events from now on in the
paper. We let E denote the set of all events and call lists, or words, eθ1 . . . eθn ∈ E ⋆ (finite) traces;
this is also consistent with our mechanical Maude ASCII notation in Subsection 3.2.1, except that
we prefer not to separate events by commas in traces from now on, to avoid mathematical notational
conflicts.
We next define binary transition trees.
29
Definition 7 Under the same notations as in the previous definition, a binary transition tree
(BTT) over states S and atoms A is a term over syntax
::= S | (A ? BTT : BTT ).
BTT
We let BTT (S, A) denote the set of binary transition trees over states S and atoms A. Given an
event θ : A → {true, false}, we define a map θBTT : BTT (S, A) → S inductively as follows:
θBTT (s) = s for any s ∈ S,
θBTT (a ? b1 : b2 ) = θBTT (b1 ) if θ(a) = true, and
θBTT (a ? b1 : b2 ) = θBTT (b2 ) if θ(a) = false.
A binary transition tree b in BTT (S, A) is said to implement a multi-transition t in MT (S, A) if
and only if θBTT (b) = θMT (t) for any map θ : A → {true, false}.
BTTs generalize BDDs [4], which can be obtained by taking S = {true, false}. As an example
of BTT, a1 ? a2 ? s1 : a3 ? false : s2 : a3 ? s2 : true says “eval a1 ; if a1 then (eval a2 ; if a2 then go
to state s1 else (eval a3 ; if a3 then go to state false else go to state s2)) else (eval a3 ; if a3 then go
to state s2 else go to state true)”. Note that true and false are just some special states. Depending
on the application they can have different meanings, but in our applications true typically means
that the monitoring requirement has been fulfilled, while false means that it has been violated. It
is often convenient to represent BTTs graphically, such as the one in Figure 2.
y
n
a1
y
n
y
n
a2
s1
a3
y
n
a3
false
true
s2
s2
Figure 2: Graphical representation for the BTT a1 ? a2 ? s1 : a3 ? false : s2 : a3 ? s2 : true.
Definition 8 A multi-transition finite state machine (MT-FSM) is a triple (S, A, µ), where S is a
set of states, A is a set of atomic predicates, and µ is a map from S − {true, false} to MT (S, A). If
S contains true and/or false, then µ(true) = [true?true] and/or µ(false) = [true?false], respectively.
A binary transition tree finite state machine (BTT-FSM) is a triple (S, A, β), where S and A are
like before and β is a map from S − {true, false} to BTT (S, A). If S contains true and/or false,
then it is the case that β(true) = [true] and/or β(false) = [false], respectively. For an event
θ
θ : A → {true, false} in any of these machines, we let s → s′ denote the fact that θMT (µ(s)) = s′ or
θ
θBTT (β(s)) = s′ , respectively. We take the liberty to call s → s′ a transition. Also, we may write
θ
θ
θ
θ
θ
1
2
1
n
n
s2 , . . . , sn →
sn+1
s1 →
s2 →
· · · sn →
sn+1 and call it a sequence of transitions whenever s1 →
are transitions.
30
Note that S is not required to contain the special states true and false, but if it contains them,
these states transit only to themselves. The finite state machine notions above are intended to
abstract the intuitive concept of a monitor. The states in S are monitor states, and some of
them can trigger side effects in practice, such as messages sent or reported to users, actions taken,
feedback sent to the monitored program for guidance purposes, etc.
θ
θ
θ
1
2
n
Definition 9 If true ∈ S then a sequence of transitions s1 →
s2 →
· · · sn →
true is called a
θ1
θ2
θn
validating sequence (for s1 ); if false ∈ S then s1 → s2 → · · · sn → false is called an invalidating
sequence (for s1 ). These notions naturally extend to corresponding finite traces of events eθ1 . . . eθn .
BTT-FSMs can and should be thought of as efficient “implementations” of MT-FSMs, in the
sense that they implement multi-transitions as binary transition trees. These allow one to reduce
the amount of computation in a monitor implementing a BTT-FSM to only evaluate at most all
the atomic predicates in a given state in order to decide to which state to go next; however, it will
only very rarely be the case that all the atomic predicates need to be evaluated.
A natural question is why one should bother at all then with defining and investigating MTFSMs. Indeed, what one really looks for in the context of FSM monitoring is efficient BTT-FSMs.
However, MT-FSMs are nevertheless an important intermediate concept as it will become clearer
later in the paper. This is because they have the nice property of state mergeability, which allows
one to elegantly generate MT-FSMs from logical formulae. By state mergeability we mean the
following. Suppose that during a monitor generation process, such as that for LTL that will be
presented in Subsection 7.3, one proves that states s and s′ are “logically equivalent” (for now,
equivalence can be interpreted intuitively: they have the same behavior), and that s and s′ have
the multi-transitions [p1 ?s1 , p2 ?s2 , ..., pn ?sn ] and [p′1 ?s′1 , p′2 ?s′2 , ..., p′n ?s′n′ ]. Then we can merge s and
s′ into one state whose multi-transition is Merge([p1 ?s1 , p2 ?s2 , ..., pn ?sn ], [p′1 ?s′1 , p′2 ?s′2 , ..., p′n ?s′n′ ]),
defined as follows:
Merge([p1 ?s1 , p2 ?s2 , ..., pn ?sn ], [p′1 ?s′1 , p′2 ?s′2 , ..., p′n ?s′n′ ])
contains all choices p?s′′ , where s′′ is a state in {s1 , s2 , ..., sn } ∪ {s′1 , s′2 , ..., s′n′ } and
• p is pi when s′′ = si for some 1 ≤ i ≤ n and s′′ 6= s′i′ for all 1 ≤ i′ ≤ n′ , or
• p is p′i′ when s′′ = s′i′ for some 1 ≤ i′ ≤ n′ and s′′ 6= si for all 1 ≤ i ≤ n, or
• p is pi ∨ p′i′ when s′′ = si for some 1 ≤ i ≤ n and s′′ = s′i′ for some 1 ≤ i′ ≤ n′ .
It is easy to see that this multi-transition merging operation is well defined, that is,
Proposition 10 Merge([p1 ?s1 , p2 ?s2 , ..., pn ?sn ], [p′1 ?s′1 , p′2 ?s′2 , ..., p′n ?s′n′ ]) is a well-formed multitransition whenever both [p1 ?s1 , p2 ?s2 , ..., pn ?sn ] and [p′1 ?s′1 , p′2 ?s′2 , ..., p′n ?s′n′ ] are well-formed multitransitions. Therefore, Merge can be seen as a function Pf (MT (S, A)) → MT (S, A), where Pf is
the finite powerset operator.
There are situations when a definite answer, true or false is desired at the end of the monitoring
session, as it will be in the case of LTL. As explained in Section 4, the intuition for the last event
in an execution trace is that the trace is infinite and stationary in that last event. This seems to
be the best and simplest assumption about future when a monitoring session is ended. For such
situations, we enrich our definitions of MT-FSM and BTT-FSM with support for terminal events:
31
Definition 11 A terminating multi-transition finite state machine (abbreviated MT-FSM*) is
a tuple (S, A, µ, µ⋆ ), where (S, A, µ) is an MT-FSM and µ⋆ is a map from S − {true, false} to
MT ({true, false}, A). A terminating binary transition tree finite state machine (BTT-FSM*) is
a tuple (S, A, β, β ⋆ ), where (S, A, β) is a BTT-FSM and β ⋆ is a map from S − {true, false} to
BTT ({true, false}, A). For a given event θ : A → {true, false} in any of these finite state machines,
θ⋆
we let s → true (or false) denote the fact that θMT (µ⋆ (s)) = true (or false) or θBTT (β ⋆ (s)) =
θ⋆
θ
1
true (or false), respectively. We call s → true (or false) a terminating transition. A sequence s1 →
θ
⋆
θn
θ
θ
⋆
θn
1
2
2
s2 →
· · · sn → false
s2 →
· · · sn → true is called an accepting sequence (for s1 ) and a sequence s1 →
is called a rejecting sequence (for s1 ). These notions also naturally extend to corresponding finite
traces of events eθ1 . . . eθn .
Languages can be associated to states in MT-FSM*s or BTT-FSM*s as finite words of events.
Definition 12 Given a state s ∈ S in an MT-FSM* or in a BTT-FSM* M , we let LM (s) denote
θ
θ⋆
n
1
true is an accepting sequence
the set of finite traces eθ1 . . . eθn with the property that s →
··· →
in M . If a state s0 ∈ S is desired to be initial, then we write it at the end of the tuple, such as
θ1
θn
(S, A, µ, µ∗, s0 ) or (S, A, β, β∗, s0 ), and let LM denote LM (s0 ). If s0 →
s1 · · · →
sn is a sequence of
transitions from the initial state, then eθ1 . . . eθn is called a valid prefix if and only if eθ1 . . . eθn t ∈ LM
for any (empty or not) trace t, and it is called an invalid prefix if and only if eθ1 . . . eθn t 6∈ LM for
any trace t.
The following is immediate.
θ
Proposition 13 If true ∈ S then LM (true) = E ⋆ , and if false ∈ S then LM (false) = ∅. If s → s′ in
θ
θ
1
n
M then LM (s′ ) = {t | eθ t ∈ LM (s)}; more generally, if s →
s1 · · · →
sn is a sequence of transitions
in M then LM (sn ) = {t | eθ1 . . . eθn t ∈ LM (s)}. In particular, if s = s0 then eθ1 . . . eθn is a valid
prefix if and only if LM (sn ) = E ⋆ , and it is an invalid prefix if and only if LM (sn ) = ∅.
7.2
From MT-FSMs to BTT-FSMs
Supposing that one has encoded a logical requirement into an MT-FSM (we shall see how to do
it for LTL in the next subsection), the next important step is to generate an efficient equivalent
BTT-FSM. In the worst possible case one just has to evaluate all the atomic predicates in order
to proceed to the next state of a BTT-FSM, so they are preferred to MT-FSMs. What one needs
to do is to develop a procedure that takes a multi-transition and returns a BTT. More BTTs can
encode the same multi-transition, so one needs to develop some criteria to select the better ones. A
natural selection criterion would be to minimize the average amount of computation. For example,
if all atomic predicates are equally probable to hold and if an atomic predicate is very expensive to
evaluate, then one would select that BTT that places the expensive predicate as deeply as possible,
so its evaluation is delayed as much as possible. Based on the above, we believe that the following
is an important theoretical problem in runtime monitoring:
Problem: Optimal BTT
Input: A set of atomic predicates a1 , ..., ak that hold with probabilities π1 , ..., πk and
have costs c1 , ..., ck , respectively, and a multi-transition p1 ?s1 , ..., pn ?sn where p1 , ..., pn
are Boolean formulae over a1 , ..., ak .
32
State
MT for non-terminal events
[
1
yellow \/ !green
!yellow /\ green /\ !red
!yellow /\ green /\ red
[
2
yellow
!yellow /\ !red
!yellow /\ red
?
?
?
?
?
?
1,
2,
false
MT for terminal events
1,
2,
false
[
]
yellow \/ !green
!yellow /\ green
[
]
yellow
!yellow
?
?
?
?
true,
false
true,
false
]
]
Figure 3: MT-FSM for the formula [](green -> !red U yellow).
Output: A BTT implementing the multi-transition that probabilistically minimizes
the amount of computation to decide the next state.
The probabilities and the costs in the problem above can be estimated either by static analysis of
the source code of the program, or dynamically by first running and measuring the program several
times, or by combinations of those. We do not know how to solve this interesting problem yet, but
we conjecture the following result that we currently use in our implementation:
Conjecture 14 If π1 = ... = πk and c1 = ... = ck then the solution to the problem above is the
BTT of minimal size.
Our current multi-transition to BTT algorithm is exponential in the number of atomic predicates; it simply enumerates all possible BTTs recursively and then selects the one of minimal size.
Generating the BTTs takes significantly less time than generating the MT-FSM, so we do not
regard it as a practical problem yet. However, it seems to be a challenging theoretical problem.
Example 15 Consider again the traffic light controller safety property stating that “after green
comes yellow”, which was written as [](green -> (!red U yellow)) using LTL notation. Since
more than one light can be lit at any moment, one should be very careful when expressing this
safety property as an MT-FSM or a BTT-FSM.
Let us first express it as an MT-FSM. We need two states, one for the case in which green has not
triggered yet the !red U yellow part and another for the case when it has. The condition to stay in
state 1 is then yellow \/ !green and the condition to move to state 2 is !yellow /\ green /\ !red.
If both a green and a red are seen then the machine should move to state false. The condition to
move from state 2 back to state 1 is yellow, while the condition to stay in state 2 is !yellow /\ !red;
!yellow /\ red should also move the machine in its false state. If the event is terminal then a yellow
would make the reported answer true, i.e., the observed trace is an accepting sequence; if yellow
is not true, then in state 1 the answer should be the opposite of green, while in state 2 the answer
should be simply false. This MT-FSM is shown in Figure 3.
The interesting aspect of our FSMs is that not all the atomic predicates need to always be
evaluated. For example, in state 2 of this MT-FSM the predicate green is not needed. A BTTFSM further reduces the amount of predicates to be evaluated, by enforcing an “evaluate-by-need”
policy. Figure 4 shows a BTT-FSM implementing the MT-FSM in Figure 3.
33
State
BTT for non-terminal events
y
BTT for terminal events
n
yellow
y
1
n
green
y
n
yellow
y
n
1
red
1
false
true
y
2
y
n
green
false
true
n
yellow
1
y
n
red
y
n
yellow
2
false
2
true
false
Figure 4: A BTT-FSM for the formula [](green -> !red U yellow).
7.3
From LTL Formulae to BTT-FSMs
Informally, our algorithm to generate optimal BTT-FSMs from LTL formulae consists of two steps.
First, it generates an MT-FSM with a minimum number of states. Then, using the technique
presented in the previous subsection, it generates an optimal BTT from each MT.
To generate a minimal MT-FSM, our algorithm uses the rewriting based procedure presented
in Section 6 on all possible events, until the set of formulae to which the original LTL formula can
“evolve” stabilizes. The procedure LTL2MT-FSM shown in Figure 5 builds an MT-FSM whose
states are formulae, with the help of a validity checker. Initially, the set S of states contains only the
original LTL formula. LTL2MT-FSM is called on the original LTL formula, and then recursively
in a depth-first manner on all the formulae to which the initial formula can ever evolve via the
event-consuming operator { } introduced in Section 6.
For each LTL state formula ϕ in S, multi-transitions µ(ϕ) and µ⋆ (ϕ) are maintained. For each
possible event θ, one first updates µ⋆ (ϕ) by considering the case in which θ is the last event in a
trace (step 8), and then the current formula ϕ evolves into the corresponding formula ϕθ (step 9).
If some equivalent formula ϕ′ to ϕθ has already been discovered then one only needs to modify
the multi-transition set of ϕ accordingly in order to point to ϕ′ (step 11). Notice that equivalence
of LTL formulae is checked by using a validity procedure (step 10), which is given in Figure 6. If
there is no formula in S equivalent to ϕθ , then the new formula ϕθ is added to S, multi-transition
µ(ϕ) is updated accordingly, and then the MT-FSM generation procedure is called recursively on
ϕθ . This way, one eventually generates all possible LTL formulae into which the initial formula can
ever evolve during a monitoring session; this happens, of course, modulo finite trace LTL semantic
equivalence, implemented elegantly using the validity checker described below. By Theorem 2, this
recursion will eventually terminate, leading to an MT-FSM*.
The procedure Valid used in LTL2MT-FSM above is defined in Figure 6. It essentially
follows the same idea of generating all possible formulae to which the original LTL formula tested
for validity can evolve via the event consumption operator defined by rewriting in Section 6, but
for each newly obtained formula ϕ and for each event θ, it also checks whether an execution trace
stopping at that event would be a rejecting sequence. The intuition for this check is that a formula
is valid under finite trace LTL semantics if and only if any (finite) sequence of events satisfies that
formula; since any generated formula corresponds to one into which the initial formula can evolve,
34
1. let S be ϕ
2. procedure LTL2MT-FSM(ϕ)
3.
let µ⋆ (ϕ) be ∅
4.
let µ(ϕ) be ∅
5.
foreach θ : A → {true, false} do
6.
let eθ be the list of atomsVa with θ(a) = true V
7.
let pθ be the proposition {a | θ(a) = true} ∧ {¬a | θ(a) = false}
8.
let µ⋆ (ϕ) be Merge([pθ ? ϕ{e⋆θ }], µ⋆ (ϕ))
9.
let ϕθ be ϕ{eθ }
10.
if there is ϕ′ ∈ S with Valid(ϕθ ↔ ϕ′ )
11.
then let µ(ϕ) be Merge([pθ ? ϕ′ ], µ(ϕ))
12.
else let S be S ∪ {ϕθ }
13.
let µ(ϕ) be Merge([pθ ? ϕθ ], µ(ϕ))
14.
LTL2MT-FSM(ϕθ )
15.
endfor
16.
if µ(ϕ) = [true ? ϕ] and µ⋆ (ϕ) = [true ? b] then replace ϕ by b everywhere
17. endprocedure
Figure 5: Algorithm to generate a minimal MT-FSM* (S, A, µ, µ⋆ , ϕ) from an LTL formula ϕ.
we need to make sure that each of these formulae becomes true under any possible last monitored
event. This is done by rewriting the term ϕ{e⋆θ } with the rules in Section 6 to its normal form
(step 5.), i.e., true or false. The formula is valid if and only if there is no rejecting sequence; the
entire space of evolving formulae is again explored by depth-first search. Notice that Valid does
not test for equivalence of formulae, so it can potentially generate a larger number of formulae than
LTL2MT-FSM. However, by Theorem 2, this procedure will also eventually terminate.
Theorem 16 Given an LTL formula ϕ of size m, the following hold:
1. The procedure Valid is correct, that is, Valid(ϕ) returns true if and only if ϕ is satisfied, as
defined in Section 4, by any finite trace;
m
2. The space and time complexity of Valid(ϕ) is 2O(2 ) ;
Additionally, letting M denote the MT-FSM* (S, A, µ, µ⋆ , ϕ) generated by LTL2MT-FSM(ϕ), the
following hold:
3. LTL2MT-FSM(ϕ) is correct; more precisely, M has the property that for any events θ1 , ...,
θ
θ
θ⋆
1
n
θn , θ, it is the case that ϕ →
ϕ1 · · · →
ϕn → true if and only if the finite trace eθ1 . . . eθn eθ
θ
θ
θ⋆
1
n
satisfies ϕ, and ϕ →
ϕ1 · · · →
ϕn → false if and only if eθ1 . . . eθn eθ does not satisfy ϕ;
θ
θ
1
n
4. M is synchronous; more precisely, for any events θ1 , ..., θn , it is the case that ϕ →
ϕ1 · · · →
eθ
eθ
θ1
θn
true if and only if eθ1 . . . eθn eθ is a valid prefix of ϕ, and ϕ →
false if
ϕn →
ϕ1 · · · →
ϕn →
and only if eθ1 . . . eθn eθ is a bad prefix of ϕ;
35
1. let S be ϕ
2. function Valid(ϕ)
3.
foreach θ : A → {true, false} do
4.
let eθ be the list of atoms a with θ(a) = true
5.
if ϕ{e⋆θ } = false then return false
6.
let ϕθ be ϕ{eθ }
7.
if ϕθ ∈
6 S
8.
then let S be S ∪ {ϕθ }
9.
if Valid(ϕθ ) = false then return false
10.
endfor
11.
return true
12. end function
Figure 6: Validity checker for an LTL formula ϕ.
m
5. The space and time complexity of LTL2MT-FSM(ϕ) is 2O(2 ) ;
6. M is the smallest MT-FSM* which is correct and synchronous (as defined in 3 and 4 above);
7. Monitoring against a BTT-FSM* corresponding to M , as shown in Subsection 7.2, needs
O(2m ) space and time.
Proof: 1. Using a depth-first search strategy, the procedure Valid visits all possible formulae
into which the original formula ϕ can evolve via any possible sequence of events. These formulae
are different modulo the rewriting rules in Section 6 which are used to simplify LTL formulae and
to remove the derivatives, but those rules were shown to be sound, so Valid indeed explores all
possible LTL formulae into which ϕ can semantically evolve. Moreover, for each such formula, say
ϕ′ , and each event, Valid checks at Step 5 whether a trace terminating with that event in ϕ′ would
lead to rejection. Valid(ϕ) returns true if and only if there is no such rejection, so it is indeed
correct: if it had been some finite trace that did not satisfy ϕ then Valid would have found it
during its exhaustive search.
2. The space required by Valid(ϕ) is clearly dominated by the size of S. By Theorem 2, each
formula ϕ′ into which ϕ can evolve needs O(2m ) space to be stored. That means that there can be
m
m
at most 2O(2 ) such formulae. So the total space needed to store S in the worst case is 2O(2 ) . An
amortized analysis of its running time, tells us that Valid runs its “for each event” loop one per
formula in S. Since the number of events is much less than 2m and since reducing the derivative
of a formula by an event takes time proportional with the size of the formula, we deduce that the
m
total running time of the Valid procedure is 2O(2 ) .
3. By Theorem 3 and the event consuming procedure described in Subsection 6.1, it follows that
eθ1 . . . eθn eθ satisfies ϕ if and only if ϕ{eθ1 }{· · ·}{eθn }{e⋆θ } reduces to true, and that eθ1 . . . eθn eθ
does not satisfy ϕ if and only if ϕ{eθ1 }{· · ·}{eθn }{e⋆θ } reduces to false. It is easy to see that
Valid(ψ ↔ ψ ′ ) returns true if and only if ψ and ψ ′ are formulae equivalent under the finite trace
36
LTL semantics. That means the MT-FSM* generated by LTL2MT-FSM(ϕ) contains a formulaθ
1
state ϕ1 which is equivalent to ϕ{eθ1 }; moreover, ϕ →
ϕ1 in this MT-FSM*. Inductively, one can
θ
θ
1
n
show that there is a series of formulae-states ϕ1 , ..., ϕn such that ϕ →
ϕ1 · · · →
ϕn is a sequence of
transitions in the generated MT-FSM and ϕ{eθ1 }{· · ·}{eθn } is equivalent to ϕn . The rest follows
θ⋆
by noting that ϕn {e⋆θ } reduces to true if and only if ϕn → true in the generated MT-FSM, and that
θ⋆
ϕn {e⋆θ } reduces to false if and only if ϕn → false in the MT-FSM.
4. As in 3 above, one can inductively show that there is a series of formulae ϕ1 , ..., ϕn , ϕ′
θ1
θn
θ
such that ϕ →
ϕ1 · · · →
ϕn → ϕ′ is a sequence of transitions in the generated MT-FSM and
ϕ{eθ1 }{· · ·}{eθn }{eθ } is equivalent to ϕ′ . By Proposition 13, due to the use of the validity checker
in step 10 of LTL2MT-FSM it follows that eθ1 . . . eθn eθ is a valid prefix of ϕ if and only if ϕ′ is
equivalent to true, and that eθ1 . . . eθn eθ is a bad prefix of ϕ if and only if ϕ′ is equivalent to false.
The rest follows now by Step 16 in the algorithm in Figure 5, which, due to the validity checker,
provides a necessary and sufficient condition for a processed formula to be semantically equivalent
to true or false, respectively.
5. The space required by LTL2MT-FSM(ϕ) is again dominated by the size of S. Like in the
m
analysis of Valid in 2 above, by Theorem 2 we get that there can be at most 2O(2 ) formulae
m
generated in S, so the total space needed to store S in the worst case is also 2O(2 ) . For each
newly added formula in S and for each event, Step 10 calls the procedure Valid potentially once
for each already existing formula in S. It is important to notice that the formulae on which Valid
is called are exclusive disjunctions of conjunctions of sub-formulae rooted in temporal operators of
m
the original formula ϕ, so its space and time complexity will also be 2O(2 ) each time it is called
by LTL2MT-FSM. One can easily see now that the space and time requirements of LTL2MTm
FSM(ϕ) are also 2O(2 ) (the constant in the exponent O(2m ) can be appropriately enlarged).
6. For any MT-FSM* machine M ′ = (S ′ , A, µ′ , µ′⋆ , q0 ), one can associate a formula, more precisely
a state in M , to each state in S ′ as follows. ϕ is associated to the initial state q0 . Then for each
θ
transition q1 → q2 in M ′ such that q1 has a formula ϕ1 associated and q2 has no formula associated,
θ
then one associates that ϕ2 to q2 with the property that ϕ1 → ϕ2 is a transition in M . For a state
q in S ′ let ϕq be the formula in S associated to it. Since M ′ is correct and synchronous for ϕ, it
follows that LM ′ (q) = LM (ϕq ) for any state q in S ′ . We can now show that the map associating a
formula in S to any state in S ′ is surjective, which shows that M has therefore a minimal number
θ1
θn
θ
of states. Let ϕ′ be a formula in S and let ϕ →
ϕ1 · · · →
ϕn → ϕ′ be a sequence of transitions in M
leading to ϕ′ ; note that all formulae in S are reachable via transitions in M from ϕ. Let q ′ be the
θ1
θn
θ
state in S ′ such that q0 →
q1 · · · →
qn → q ′ . Then LM ′ (q ′ ) = LM (ϕq′ ). Since by Proposition 13,
′
LM ′ (q ) = {t | eθ1 . . . eθn eθ t ∈ LM ′ (q0 )} and LM (ϕ′ ) = {t | eθ1 . . . eθn eθ t ∈ LM (ϕ)}, it follows that
LM (ϕq′ ) = LM (ϕ′ ), that is, that ϕq′ and ϕ′ are equivalent. Since Step 10 of the LTL2MT-FSM
eventually uses the validity checker on any pairs of formulae in S, it follows that ϕq′ = ϕ′ .
7. In order to distinguish N pieces of data, log(N ) bits are needed to encode each datum. Therefore,
one needs O(2m ) bits to encode a state of M , which is the main memory needed by the monitoring
algorithm. Like in Theorem 2, we assume “idealistic” rewriting engines able to optimize the space
requirements; we are not aware whether Maude is able to attain this performance or not. To
make a transition from one state to another, a BTT-FSM* associated to the MT-FSM* generated
37
by LTL2MT-FSM(ϕ) needs to only evaluate at most all the atomic predicates occurring in the
formula, which, assuming that evaluating atom predicates takes unit time, is clearly O(2m ). When
the entire BTT is evaluated, it finally has to store the newly obtained state of the BTT-FSM*,
which can reuse the space of the previous one, O(2m ), and which also takes time O(2m ).
Once the procedure LTL2MT-FSM terminates, the formulae ϕ, ϕ′ , etc., are not needed anymore, so one can and should replace them by unique labels in order to reduce the amount of storage
needed to encode the MT-FSM* and/or the corresponding BTT-FSM*. This algorithm can be relatively easily implemented in any programming language. We have, however, found Maude again
a very elegant system for this task, implementing the entire LTL formula to BTT-FSM algorithm
in about 200 lines of code.
7.4
Examples
The BTT-FSM generation algorithm presented in this section, despite its overall worst-case high
startup time, can be very useful when formulae are relatively short, as it is most often the case
in practice. For the traffic light controller requirement formula discussed previously in the paper,
[](green -> (!red) U yellow), this algorithm generates in about 0.2 seconds the optimal BTT-FSM*
in Figure 7, also shown in Figure 4 in flowchart notation; Figure 3 shows its optimal MT-FSM*.
For simplicity, the states true and false do not appear in Figure 7. Notice that the atomic predicate
State
1
2
Non-terminal event
yellow ? 1 : green ? red ? false : 2 : 1
yellow ? 1 : red ? false : 2
Terminal event
yellow ? true : green ? false : true
yellow ? true : false
Figure 7: An optimal BTT-FSM* for the formula [](green -> !red U yellow).
does not need to be evaluated on terminal events and that green does not need to be evaluated
in state 2. In this example, the colors are not supposed to exclude each other, that is, the traffic
controller can potentially be both green and red.
The LTL formulae on which our algorithm has the worst performance are those containing many
nested temporal operators (which are not frequently used in specifications anyway, because of the
high risk of getting them wrong). For example, it takes our Maude implementation of this algorithm
1.3 seconds to generate the minimal 3-state (true and false states are not counted) BTT-FSM* for
the formula a U (b U (c U d)) and 13.2 seconds to generate the 7-state minimal BTT-FSM* for the
formula ((a U b) U c) U d. It never took our current implementation more than a few seconds to
generate the BTT-FSM* of any LTL formula of interest for our applications, i.e., non-artificial.
Figure 8 shows the generated BTT-FSM of some artificial LTL formulae, taking together less than
15 seconds to be generated. To keep the figure small, the states true and false together with their
self-transitions are not shown in Figure 8, and they are replaced by t and f in BTTs.
The generated BTT-FSM*s are monitored most efficiently on RAM machines, due to the fact
that conditional statements are implemented via jumps in memory. Monitoring BTT-FSM*s using
rewriting does not seem appropriate because it would require linear time, as a function of number
of states, to extract the BTT associated to a state in a BTT-FSM*. However, we believe that the
algorithm presented in Section 6 is satisfactory in practice if one is willing to use a rewriting engine
for monitoring.
red
38
Formula
[]<> a
<>([]a ∨ []¬a)
[](a -> <>b)
a U (b U c)
a U (b U (c U d))
((a U b) U c) U d
State
1
−
1
2
1
2
1
2
3
1
2
3
4
5
6
7
Monitoring BTT
1
−
a ? (b ? 1 : 2) : 1
b?1:2
c ? t : (a ? 1 : (b ? 2 : f))
c ? t : (b ? 2 : f)
d?t:a?1:b?2:c?3:f
d?t:b?2:c?3:f
d?t:c?3:f
d?t:c?1:b?4:a?5:f
b?c?t:7:a?c?6:2:f
b?d?t:c?1:4:a?d?6:c?3:5:f
c?d?t:1:b?d?7:4:a?d?2:5:f
b?d?c?t:7:c?1:4:a?d?c?6:2:c?3:5:f
b?t:a?6:f
c?t:b?7:a?2:f
Terminating BTT
a?t:f
−
a ? (b ? t : f) : t
b?t:f
c?t:f
c?t:f
d?t:f
d?t:f
d?t:f
d?t:f
c?b?t:f:f
d?b?t:f:f
d?c?t:f:f
d?c?b?t:f:f:f
b?t:f
c?t:f
Figure 8: Six BTT-FSM*s generated in less than 15 seconds.
8
Conclusions
This paper presented a foundational study in using rewriting in runtime verification and monitoring
of systems. After a short discussion on types of monitoring and mathematical and technological
preliminaries, a finite trace linear temporal logic was defined, together with an immediate but inefficient implementation of a monitor following directly its semantics. Then an efficient but ineffective
implementation based on dynamic programming was presented, which traverses the execution trace
backwards. The first effective and relatively efficient rewriting algorithm was further introduced,
based on the idea of transforming the monitoring requirements as events are received from the monitored program. A non-trivial improvement of this algorithm based on hashing rewriting results,
thereby reducing the number of rewritings performed during trace analysis, was also proposed. The
hashing corresponds to building an observer automaton on-the-fly, having the advantage that only
the part of the automaton that is needed for analyzing a given trace is generated. The resulting
algorithm is very efficient. Since in many cases one would want to generate an observer finite state
machine (or automaton) a priori, for example when a rewriting system cannot be used for monitoring, or when minimal runtime overhead is needed, a specialized data-structure called a binary
transition tree (BTT) and corresponding finite state machines were introduced, and an algorithm
for generating minimal such monitors from temporal formulae was discussed.
All algorithms were implemented in surprisingly few lines of Maude code, illustrating the
strength of rewriting for this particular domain. In spite of the reduced size of the code, the
implementations seem to be efficient for practical purposes. As a consequence, we have demonstrated how rewriting can be used not only to experiment with runtime monitoring logics, but also
as an implementation language. As an example of future work is the extension of LTL with realtime constraints. Since Maude by itself provides a high-level specification language, one can argue
that Maude in its entirety can be used for writing requirements. Further work will show whether
this avenue is fruitful. Some of the discussed results and algorithms have been already used in two
NASA applications, JPaX and X9, but an extensive experimental assesment of these techniques
39
is left as future work.
Acknowledgement. The authors warmly thank the anonymous reviewers for their very detailed and useful comments and suggestions on how to improve this paper.
References
[1] C. Artho, D. Drusinsky, A. Goldberg, K. Havelund, M. Lowry, C. Pasareanu, G. Roşu, and
W. Visser. Experiments with Test Case Generation and Runtime Analysis. In Proc. of ASM’03:
Abstract State Machines, volume 2589 of Lecture Notes in Computer Science, pages 87–107,
Taormina, Italy, March 2003. Springer.
[2] T. Ball, A. Podelski, and S. Rajamani. Boolean and Cartesian Abstractions for Model Checking
C Programs. In Proc. of TACAS’01: Tools and Algorithms for the Construction and Analysis
of Systems, Lecture Notes in Computer Science, Genova, Italy, April 2001.
[3] A. Bouhoula, J.-P. Jouannaud, and J. Meseguer. Specification and Proof in Membership
Equational Logic. Theoretical Computer Science, 236:35–132, 2000.
[4] R. E. Bryant. Graph-Based Algorithms for Boolean Function Manipulation. IEEE Transactions on Computers, C-35(8):677–691, August 1986.
[5] F. Chen and G. Roşu. Towards Monitoring-Oriented Programming: A Paradigm Combining
Specification and Implementation. In Proc. of RV’03: the Third International Workshop on
Runtime Verification, volume 89 of Electronic Notes in Theoretical Computer Science, pages
106–125, Boulder, Colorado, USA, 2003. Elsevier Science.
[6] M. Clavel. The ITP Tool. In Logic, Language and Information. Proc. of the First Workshop
on Logic and Language, pages 55–62. Kronos, 2001.
[7] M. Clavel, F. J. Durán, S. Eker, P. Lincoln, N. Martı́-Oliet, J. Meseguer, and J. F. Quesada.
Maude: Specification and Programming in Rewriting Logic, March 1999. Maude System
documentation at http://maude.csl.sri.com/papers.
[8] M. Clavel, F. J. Durán, S. Eker, P. Lincoln, N. Martı́-Oliet, J. Meseguer, and J. F. Quesada.
Maude: Specification and Programming in Rewriting Logic. Theoretical Computer Science,
285:187–243, 2002.
[9] J. Corbett, M. B. Dwyer, J. Hatcliff, C. S. Pasareanu, Robby, S. Laubach, and H. Zheng.
Bandera : Extracting Finite-state Models from Java Source Code. In Proc. of ICSE’00:
International Conference on Software Engineering, Limerich, Ireland, June 2000. ACM Press.
[10] M. Dahm. BCEL. http://jakarta.apache.org/bcel.
[11] C. Demartini, R. Iosif, and R. Sisto. A Deadlock Detection Tool for Concurrent Java Programs.
Software Practice and Experience, 29(7):577–603, July 1999.
[12] D. Drusinsky. The Temporal Rover and the ATG Rover. In Proc. of SPIN’00: SPIN Model
Checking and Software Verification, volume 1885 of Lecture Notes in Computer Science, pages
323–330, Stanford, California, USA, 2000. Springer.
40
[13] D. Drusinsky. Monitoring Temporal Rules Combined with Time Series. In Proc. of CAV’03:
Computer Aided Verification, volume 2725 of Lecture Notes in Computer Science, pages 114–
118, Boulder, Colorado, USA, 2003. Springer-Verlag.
[14] C. J. Fidge. Partial Orders for Parallel Debugging. In Proc. of the 1988 ACM SIGPLAN and
SIGOPS workshop on Parallel and Distributed Debugging, pages 183–194. ACM, 1988.
[15] B. Finkbeiner, S. Sankaranarayanan, and H. Sipma. Collecting Statistics over Runtime Executions. In Proc. of RV’02: The Second International Workshop on Runtime Verification,
volume 70 of Electronic Notes in Theoretical Computer Science, Paris, France, 2002. Elsevier.
[16] B. Finkbeiner and H. Sipma. Checking Finite Traces using Alternating Automata. In Proc. of
RV’01: The First International Workshop on Runtime Verification, volume 55(2) of Electronic
Notes in Theoretical Computer Science, Paris, France, 2001. Elsevier Science.
[17] D. Giannakopoulou and K. Havelund. Automata-Based Verification of Temporal Properties
on Running Programs. In Proc. of ASE’01: International Conference on Automated Software
Engineering, pages 412–416. Institute of Electrical and Electronics Engineers, Coronado Island,
California, 2001.
[18] P. Godefroid. Model Checking for Programming Languages using VeriSoft. In Proc. of
POPL’97: the 24th ACM Symposium on Principles of Programming Languages, pages 174–186,
Paris, France, January 1997.
[19] J. Goguen, K. Lin, G. Roşu, A. Mori, and B. Warinschi. An overview of the Tatami project.
In Cafe: An Industrial-Strength Algebraic Formal Method, pages 61–78. Elsevier, 2000.
[20] J. Goguen, J. Thatcher, E. Wagner, and J. Wright. Initial Algebra Semantics and Continuous
Algebras. Journal of the Association for Computing Machinery, 24(1):68–95, January 1977.
[21] J. Goguen, T. Winkler, J. Meseguer, K. Futatsugi, and J.-P. Jouannaud. Introducing OBJ. In
Software Engineering with OBJ: Algebraic Specification in Action. Kluwer, 2000.
[22] E. Gunter and D. Peled. Tracing the Executions of Concurrent Programs. In Proc. of RV’02:
Second International Workshop on Runtime Verification, volume 70 of Electronic Notes in
Theoretical Computer Science, Copenhagen, Denmark, 2002. Elsevier.
[23] E. L. Gunter, R. P. Kurshan, and D. Peled. PET: An Interactive Software Testing Tool. In
Proc. of CAV’00: Computer Aided Verification, volume 1885 of Lecture Notes in Computer
Science, pages 552–556, Chicago, Illinois, USA, 2003. Springer-Verlag.
[24] E. L. Gunter and D. Peled. Using Functional Languages in Formal Methods: The PET System.
In Parallel and Distributed Processing Techniques and Applications, pages 2981–2986. CSREA,
2000.
[25] K. Havelund, S. Johnson, and G. Roşu. Specification and Error Pattern Based Program Monitoring. In Proc. of the European Space Agency workshop on On-Board Autonomy, Noordwijk,
The Netherlands, October 2001.
41
[26] K. Havelund, M. R. Lowry, and J. Penix. Formal Analysis of a Space Craft Controller using
SPIN. IEEE Transactions on Software Engineering, 27(8):749–765, August 2001. An earlier
version occurred in the Proc. of SPIN’98: the fourth SPIN workshop, Paris, France, 1998.
[27] K. Havelund and T. Pressburger. Model Checking Java Programs using Java PathFinder.
International Journal on Software Tools for Technology Transfer, 2(4):366–381, April 2000.
Special issue containing selected submissions to SPIN’98: the fourth SPIN workshop, Paris,
France, 1998.
[28] K. Havelund and G. Roşu. Java PathExplorer – A Runtime Verification Tool. In Proc.
of i-SAIRAS’01: the 6th International Symposium on Artificial Intelligence, Robotics and
Automation in Space, Montreal, Canada, June 2001.
[29] K. Havelund and G. Roşu. Monitoring Java Programs with Java PathExplorer. In Proc. of
RV’01: the First International Workshop on Runtime Verification, volume 55 of Electronic
Notes in Theoretical Computer Science, pages 97–114, Paris, France, July 2001. Elsevier Science.
[30] K. Havelund and G. Roşu. Monitoring Programs using Rewriting. In Proc. of ASE’01: International Conference on Automated Software Engineering, pages 135–143. Institute of Electrical
and Electronics Engineers, Coronado Island, California, USA, 2001.
[31] K. Havelund and G. Roşu. Testing Linear Temporal Logic Formulae on Finite Execution
Traces. Technical Report TR 01-08, RIACS, May 2001. Written 20 December 2000.
[32] K. Havelund and G. Roşu. Efficient Monitoring of Safety Properties. Software Tools and
Technology Transfer, to appear.
[33] K. Havelund and G. Roşu. Synthesizing Monitors for Safety Properties. In Proc. of TACAS’02:
Tools and Algorithms for Construction and Analysis of Systems, volume 2280 of Lecture Notes
in Computer Science, pages 342–356, Grenoble, France, 2002. Springer.
[34] K. Havelund and N. Shankar. Experiments in Theorem Proving and Model Checking for
Protocol Verification. In Proc. of FME’96: Industrial Benefit and Advances in Formal Methods,
volume 1051 of Lecture Notes in Computer Science, pages 662–681, Oxford, England, 1996.
Springer.
[35] G. J. Holzmann and M. H. Smith. A Practical Method for Verifying Event-Driven Software. In
Proc. of ICSE’99: International Conference on Software Engineering, Los Angeles, California,
USA, May 1999. IEEE/ACM.
[36] J. Hopcroft and J. Ullman. Introduction to Automata Theory, Languages, and Computation.
Addison-Wesley, Reading, Massachusetts, 1979.
[37] J. Hsiang. Refutational Theorem Proving using Term Rewriting Systems. Artificial Intelligence, 25:255–300, 1985.
[38] M. Kim, S. Kannan, I. Lee, and O. Sokolsky. Java-MaC: a Run-time Assurance Tool for
Java. In Proc. of RV’01: First International Workshop on Runtime Verification, volume 55 of
Electronic Notes in Theoretical Computer Science, Paris, France, 2001. Elsevier Science.
42
[39] D. Kortenkamp, T. Milam, R. Simmons, and J. Fernandez. Collecting and Analyzing Data
from Distributed Control Programs. In Proc. of RV’01: First International Workshop on
Runtime Verification, volume 55 of Electronic Notes in Theoretical Computer Science, Paris,
France, 2001. Elsevier Science.
[40] O. Kupferman and M. Y. Vardi. Freedom, Weakness, and Determinism: From Linear-Time
to Branching-Time. In Proc. of the IEEE Symposium on Logic in Computer Science, pages
81–92, 1998.
[41] O. Kupferman and M. Y. Vardi. Model Checking of Safety Properties. In Proc. of CAV’99:
Conference on Computer-Aided Verification, Trento, Italy, 1999.
[42] O. Kupferman and S. Zuhovitzky. An Improved Algorithm for the Membership Problem for
Extended Regular Expressions. In Proc. of the International Symposium on Mathematical
Foundations of Computer Science, volume 2420 of Lecture Notes in Computer Science, 2002.
[43] I. Lee, S. Kannan, M. Kim, O. Sokolsky, and M. Viswanathan. Runtime Assurance Based
on Formal Specifications. In Proc. of PDPTA’99: International Conference on Parallel and
Distributed Processing Techniques and Applications, Las Vegas, Nevada, USA, 1999.
[44] Z. Manna and A. Pnueli. The Temporal Logic of Reactive and Concurrent Systems. Springer,
New York, 1992.
[45] Z. Manna and A. Pnueli. Temporal Verification of Reactive Systems: Safety. Springer, New
York, 1995.
[46] N. Markey and P. Schnoebelen. Model Checking a Path (Preliminary Report). In Proc. of
CONCUR’03: International Conference on Concurrency Theory, volume 2761 of Lecture Notes
in Computer Science, pages 251–265, Marseille, France, August 2003. Springer.
[47] J. Meseguer. Conditional Rewriting Logic as a Unified Model of Concurrency. Theoretical
Computer Science, pages 73–155, 1992.
[48] J. Meseguer. Membership Algebra as a Logical Framework for Equational Specification. In
Proc. of WADT’97: Workshop on Algebraic Development Techniques, volume 1376 of Lecture
Notes in Computer Science, pages 18–61, Tarquinia, Italy, June 1998. Springer.
[49] T. O’Malley, D. Richardson, and L. Dillon. Efficient Specification-Based Oracles for Critical
Systems. In Proc. of the California Software Symposium, 1996.
[50] D. Y. Park, U. Stern, and D. L. Dill. Java Model Checking. In Proc. of the First International
Workshop on Automated Program Analysis, Testing and Verification, Limerick, Ireland, June
2000.
[51] A. Pnueli. The Temporal Logic of Programs. In Proc. of the 18th IEEE Symposium on
Foundations of Computer Science, pages 46–77, 1977.
[52] D. J. Richardson, S. L. Aha, and T. O. O’Malley. Specification-Based Test Oracles for Reactive
Systems. In Proc. of ICSE’92: International Conference on Software Engineering, pages 105–
118, Melbourne, Australia, 1992.
43
[53] G. Roşu and K. Havelund. Synthesizing Dynamic Programming Algorithms from Linear Temporal Logic Formulae. RIACS Technical report TR 01-08, January 2001.
[54] G. Roşu and M. Viswanathan. Testing Extended Regular Language Membership Incrementally
by Rewriting. In Proc. of RTA’03: Rewriting Techniques and Applications, volume 2706 of
Lecture Notes in Computer Science, pages 499–514, Valencia, Spain, June 2003. SpringerVerlag.
[55] S. Savage, M. Burrows, G. Nelson, P. Sobalvarro, and T. Anderson. Eraser: A Dynamic
Data Race Detector for Multithreaded Programs. ACM Transactions on Computer Systems,
15(4):391–411, November 1997.
[56] A. Sen and V. K. Garg. Partial Order Trace Analyzer (POTA) for Distrubted Programs.
In Proc. of RV’03: the Third International Workshop on Runtime Verification, volume 89 of
Electronic Notes in Theoretical Computer Science, Boulder, Colorado, USA, 2003. Elsevier
Science.
[57] K. Sen and G. Roşu. Generating Optimal Monitors for Extended Regular Expressions. In
Proc. of RV’03: the Third International Workshop on Runtime Verification, volume 89 of
Electronic Notes in Theoretical Computer Science, pages 162–181, Boulder, Colorado, USA,
2003. Elsevier Science.
[58] K. Sen, G. Roşu, and G. Agha. Runtime Safety Analysis of Multithreaded Programs. In Proc. of
ESEC/FSE’03: European Software Engineering Conference and ACM SIGSOFT International
Symposium on the Foundations of Software Engineering. ACM, Helsinki, Finland, September
2003.
[59] N. Shankar, S. Owre, and J. M. Rushby. PVS Tutorial. Computer Science Laboratory, SRI
International, Menlo Park, CA, February 1993. Also appears in Tutorial Notes, Formal Methods Europe ’93: Industrial-Strength Formal Methods, pages 357–406, Odense, Denmark, April
1993.
[60] A. P. Sistla and E. M. Clarke. The Complexity of Propositional Linear Temporal Logics.
Journal of the ACM (JACM), 32(3):733–749, 1985.
[61] S. D. Stoller. Model-Checking Multi-Threaded Distributed Java Programs. In Proc. of
SPIN’00: SPIN Model Checking and Software Verification, volume 1885 of Lecture Notes in
Computer Science, pages 224–244, Stanford, California, USA, 2000. Springer.
[62] W. Visser, K. Havelund, G. Brat, and S. Park. Model Checking Programs. In Proc. of ASE’00:
International Conference on Automated Software Engineering, Grenoble, France, September
2000. IEEE CS Press.
[63] H. Yamamoto. An Automata-based Recognition Algorithm for Semi-Extended Regular Expressions. In Proc. of the International Symposium on Mathematical Foundations of Computer
Science, volume 1893 of Lecture Notes in Computer Science, pages 699–708, 2000.
44