Runtime Verification. Grigore Roşu. University of Illinois at Urbana-Champaign

Size: px
Start display at page:

Download "Runtime Verification. Grigore Roşu. University of Illinois at Urbana-Champaign"

Transcription

1 Runtime Verification Grigore Roşu University of Illinois at Urbana-Champaign

2 2

3 Contents 1 Introduction A Taxonomy of Runtime Analysis Techniques Trace Storing versus Non-Storing Algorithms Synchronous versus Asynchronous Monitoring Predictive versus Exact Analysis Background, Preliminaries, Notations Preliminaries Membership Equational Logic Maude Safety Properties Finite Traces Infinite Traces Finite and Infinite Traces Always Past Characterization Monitoring Specifying Safety Properties as Monitors Complexity of Monitoring a Safety Property Monitoring Safety Properties is Arbitrarily Hard Canonical Monitors Event/Trace Observation 77 6 Monitor Synthesis: Finite State Machines (FSM) Binary Transition Trees (BTT) Multi-Transition and Binary Transition Tree Finite State Machines

4 6.1.2 From MT-FSMs to BTT-FSMs Multi-Transitions and Binary Transition Trees Binary Transition Tree Finite State Machines Monitor Synthesis: Extended Regular Expressions (ERE) Monitoring ERE Safety Needs Non-Elementary Space Discussion and Relevance of the Lower-Bound Result The Lower-Bound Result Generating Optimal Monitors for ERE Introduction Extended Regular Expressions and Derivatives Hidden Logic and Coinduction Behavioral Equivalence, Satisfaction and Specification Generating Minimal DFA Monitors by Coinduction Implementation and Evaluation Monitor Synthesis: Linear Temporal Logic (LTL) Finite Trace Future Time Linear Temporal Logic Finite Trace Semantics Finite Trace Semantics in Maude A Backwards, Asynchronous, but Efficient Algorithm An Example Generating Dynamic Programming Algorithms A Forwards and Often Synchronous Algorithm An Event Consuming Algorithm Correctness and Completeness Further Optimization by Memoization Generating Forwards, Synchronous and Efficient Monitors From LTL Formulae to BTT-FSMs Examples Conclusions Efficient Monitoring of ω-languages Introduction Preliminaries: Büchi Automata Generating a BTT-FSM from a Büchi automaton Monitor Generation and MOP Evaluation Conclusions

5 10 Efficient Monitoring of Always Past Temporal Safety Introduction The PathExplorer Architecture The Observer Code Instrumentation Finite Trace Past Time LTL Syntax Formal Semantics Recursive Semantics Equivalent Logics Monitoring Safety by Rewriting Maude Formulae and Data Structures Propositional Calculus Past Time Linear Temporal Logic Monitoring with Maude Synthesizing Monitors for Safety Properties The Algorithm Illustrated by an Example The Algorithm Formalized Optimizing the Generated Code Implementation of Offline and Inline Monitoring Conclusion Optimal Monitoring of Always Past Temporal Safety The Monitor Synthesis Algorithm A Maude Implementation of the Monitor Synthesizer Monitoring Always-Past Temporal Safety with Call-Return Introduction ptltl and ptcaret ptcaret Derived Operators and Examples A Monitor Synthesis Algorithm for ptcaret The Target Language The Monitor Synthesis Algorithm Implementation as Logic Plugin, Optimizations, Example Conclusion and Future Work Auxiliary Material - not included in original paper The Algorithm Formalized

6 12 Efficient Monitoring of Parametric Context-Free Patterns Introduction Example Contributions Paper Outline Related Work Runtime Monitoring Context-free Grammars in Testing and Verification MOP Revisited MOP in a Nutshell Suffix Matching in JavaMOP Context-Free Patterns in JavaMOP Preliminaries CFG Monitoring Algorithms CFG-plug-in Implementation Proofs of Correctness Evaluation Experimental Settings Properties Results Conclusions and Future Work Efficient Monitoring with Deterministic String Rewrite Systems Introduction Examples Contributions Paper Outline Related Work and MOP Monitoring SRS Specifications Preliminaries String Rewriting Algorithm Overview Pattern Match Automata Rewriting using Pattern Match Automata Evaluation Conclusion

7 14 Allen Linear (Interval) Temporal Logic - Translation to LTL and Monitor Synthesis Introduction Allen (Linear) Temporal Logic - ATL (ALTL) Linear Translation of ALTL into LT L Monitoring ALTL Experiment Conclusion Parametric Property Monitoring Introduction and Motivation Motivating examples and highlights Related Work Contributions Paper Structure Examples of Parametric Properties Releasing acquired resources Authenticate before use Safe iterators Correct locking Safe resource use by safe client Success ratio Mathematical Background: Partial Functions, Least Upper Bounds (lubs) and lub Closures Partial Functions Least Upper Bounds of Families of Sets of Partial Maps Least Upper Bound Closures Parametric Traces and Properties The Non-Parametric Case The Parametric Case Slicing With Less Algorithm for Online Parametric Trace Slicing Monitors and Parametric Monitors The Non-Parametric Case The Parametric Case Algorithms for Parametric Trace Monitoring Unoptimized but Simpler Algorithm Optimized Algorithm Implementation in JavaMOP and RV

8 Implementation Optimizations Experiments and Evaluation Concluding Remarks, Future Work and Acknowledgments Efficient Formalism-Independent Monitoring of Parametric Properties Introduction Approach Overview Background: Parametric Monitoring Events, Traces and Properties Parametric Monitors Monitoring with Creation Events : C + X Limitations of C + X and Enable Sets Enable Sets Computing Enable Sets Monitoring with Enable Sets: D X Timestamping Monitors: Algorithm D X Proofs of Correctness Implementation and Evaluation Conclusion Predictive Runtime Analysis Static Analysis to Improve Runtime Verification Semantics-Based Runtime Verification Defining a Formal Semantics Semantics-Based Symbolic Execution Program Verification as Exhaustive Runtime Verification Conclusion and Future Work Safety Properties and Monitoring

9 Topics to cover: Safety properties and their monitoring. How many safety properties are there? Can they all be monitored? Complexity of monitoring in different formalism: LTL, RE, ERE, CFG, SRS. Both dynamic properties, like above, and static properties (e.g., complex heap patterns). Event/Trace observation. How to observe the execution of a program? Instrumentation vs. runtime environment. Monitor synthesis. Generating optimal monitors for several formalisms: LTL, FT/PT-LTL, RE, ERE, CFG, SRS, PT-CaRet, Allen TL. Parametric property monitoring. How to deal with multiple instances of monitors? Predictive runtime analysis. Vector clock vs SMT-based techniques. Static analysis to improve runtime verification. Improve runtime overhead by not instrumenting what is unnecessary. Improve prediction capability by looking beyond the trace. Semantics-based Runtime Verification. Defining a formal language semantics. Using a semantics to do symbolic execution and runtime verify properties. Ultimate goal: verify programs by exhaustive runtime verification. Papers which have been completely absorbed: * [209]: Section 7.2; Chapter 2 * [198]: Chapter 2; Chapter 3; Chapter 4; Section 7.1; * [194]: Section 6.1; Chapter 8; * [70]: Chapter 6; Chapter 9; * [119, 118]: Chapter 10; * [200]: Chapter 11 9

10 10

11 Chapter 1 Introduction Begin of intro stuff for chapter on safety From SACS: Abstract sacs: This paper addresses the problem of runtime verification from a foundational perspective, answering questions like Is there a consensus among the various definitions of a safety property? (Answer: Yes), How many safety properties exist? (Answer: As many as real numbers), How difficult is the problem of monitoring a safety property? (Answer: Arbitrarily complex), Is there any formalism that can express all safety properties? (Answer: No), etc. Various definitions of safety properties as sets of execution traces have been proposed in the literature, some over finite traces, others over infinite traces, yet others over both finite and infinite traces. By employing cardinality arguments and a novel notion of persistence, this paper first establishes the existence of bijective correspondences between the various notions of safety property. It then shows that safety properties can be characterized as always past properties. Finally, it proposes a general notion of monitor, which allows to show that safety properties correspond precisely to the monitorable properties, and then to establish that monitoring a safety property is arbitrarily hard. 11

12 From safety: Abstract safety: Various definitions of safety properties as sets of execution traces have been introduced in the literature, some over finite traces, others over infinite traces, yet others over both finite and infinite traces. By employing cardinality arguments, this paper first shows that these notions of safety are ultimately equivalent, by showing each of them to have the cardinal of the continuum. It is then shown that all safety properties can be characterized as always past properties, and then that the problem of monitoring a safety property can be arbitrarily hard. Finally, two decidable specification formalisms for safety properties are discussed, namely extended regular expressions and past time LTL. It is shown that monitoring the former requires non-elementary space. An optimal monitor synthesis algorithm is given for the latter; the generated monitors run in space linear with the number of temporal operators and in time linear with the size of the formula. A safety property is a behavioral property which, once violated, cannot be satisfied anymore. For example, a property always x > 0 is violated when x 0 is observed for the first time; this safety property remains violated even though eventually x > 0 might hold. That means that one can identify each safety property with a set of bad finite execution traces, with the intuition that once one of those is reached the safety property is violated. There are several apparently different ways to formalize safety. Perhaps the most immediate one is to complement the bad traces above and thus to define a safety property as a prefix-closed property over finite traces (containing the good traces ) by property in this paper we mean a set of finite or infinite traces. Inspired by Lamport [154], Alpern and Schneider [9] define safety properties over infinite traces as ones with the property that if an infinite trace is unacceptable then there must be some finite prefix of it which is already unacceptable, in the sense that there is no acceptable infinite completion of it. Is there any relationship between these two definitions of safety? We show rather indirectly that there is, by showing that their corresponding sets of safety properties have the cardinal c of the continuum (i.e., the cardinal of R, the set of real numbers), so there exists some bijective mapping between the two. Unfortunately, the existence of such a bijection is 12

13 as little informative as the existence of a bijection between the real numbers and the irrational numbers. To capture the relationship between finite- and infinite-trace safety properties in a meaningful way, we introduce a subset of finite-trace safety properties, called persistent, and then construct an explicit bijection between that subset and the infinite-trace safety properties. Interestingly, over finite traces there are as many safety properties as unrestricted properties (finite-traces are enumerable and P(N) is in bijection with R), while over infinite traces there are c safety properties versus 2 c unrestricted properties (infinite traces are in bijection with R). It is also common to define safety properties as properties over both finite and infinite traces, the intuition for the finite traces being that of unfinished computations. For example, Lamport [155] extends the notion of infinite-trace safety properties to properties over both finite and infinite traces, while Schneider et al. [204, 103] give an alternative definition of safety over finite and infinite traces, called execution monitoring. One immediate technical advantage of allowing both finite and infinite traces is that one can define prefix-closed properties. We indirectly show that prefix-closeness is not a sufficient condition to define safety properties when infinite traces are also allowed, by showing that there are 2 c prefix-closed properties versus, as expected, only c safety properties. Another common way to specify safety properties is as always past properties, that is, as properties containing only words whose finite prefixes satisfy a given property. If P is a property on finite prefixes, then we write P for the always P safety property containing the words with prefixes in P. We show that specifying safety properties as always past properties is fully justified by showing that, for each of the three types of traces (finite, infinite, and both), the always past properties are precisely the safety properties as defined above. It is common to specify P using some logical formalism, for example past time linear temporal logic (past LTL) [161]; for example, one can specify a before b in past LTL as the formula b a. The problem of monitoring safety properties is also investigated in this paper. Since there are as many safety properties as real numbers, it is not unexpected that some of them can be very hard to monitor. We show that the problem of monitoring a safety property is arbitrarily hard, by showing that it reduces to deciding membership of natural 13

14 numbers to a set of natural numbers. In particular, we can associate a safety property to any degree in the arithmetic hierarchy as well as to any complexity class in the decidable universe, whose monitoring is as hard as that degree or complexity class. From SACS: This paper makes three novel contributions, two technical and another pedagogical. On the technical side, it first introduces the notion of a persistent safety property, which appears to be the right finite-trace correspondent of an infinite-trace safety property, and uses it to show the cardinal equivalence of the various notions of safety property encountered in the literature. Also on the technical side, it rigorously defines the problem of monitoring a safety property, and it shows that it can be arbitrarily hard. On the pedagogical side, this paper offers the first comprehensive study and uniform presentation of safety properties and of their monitoring. 14

15 From safety: In practice not all (c = R ) safety properties are meaningful, but only those (ℵ 0 = N ) which are specifiable using formal specification languages or logics of interest. We also investigate the problem of monitoring safety properties expressed using two common formalisms, namely regular expressions extended with complement, also called extended regular expressions (ERE), and LTL. It is known that both formalisms allow polynomial finitetrace membership checking algorithms [126, 194] if one has random access to the trace, but that both require exponential space if the trace can only be analyzed online [190, 147]. It is also known that LTL can indeed be monitored in exponential space [71] and so is claimed 1 for EREs in [190]. We show that the claim in [190] is, unfortunately, wrong, by showing that ERE monitoring requires non-elementary space. To do so, we propose for any n N a safety property P n whose monitoring requires space non-elementary in n, as well as an ERE of size O(n 3 ). Since the known monitoring algorithms for LTL in its full generality are asymptotically optimal, what is left to do is to consider important fragments of LTL. We focus on the always past fragment and give a monitor synthesis algorithm that takes formulae ϕ and generate monitors for them that need O(k) total space and O( ϕ ) time to process each event, where k is the number of past operators in ϕ. This improves over the best known algorithm that needs space O( ϕ (and same time). End of intro stuff for chapter on safety 15

16 from J.ASE 2005: 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 rewriting-based 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. next is from J.ASE 2005 Future time Linear Temporal Logic, abbreviated LTL, was introduced 16

17 by Pnueli in 1977 [183] (see also [160, 162]) 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 systems written in Java, C and C++ [21, 72, 124, 65, 182, 93, 220, 114, 227]. 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 [59], 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 [115, 116] 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 [66]. The instrumented code emits relevant events to an observer during execution (see Figure 1.1). The observer can be running a Maude [59] 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. Using temporal logic in testing is an idea of broad practical and theoretical 17

18 Figure 1.1: Overview of JPaX. interest. One example is the commercial Temporal Rover and DBRover tools [73, 75], 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 [158, 140] 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 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 [144] 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 [102, 101, 100], uses a future time temporal logic formula to guide the execution of a program for debugging purposes. Java MultiPathExplorer [208] is a tool which checks a past time LTL safety formula against a partial order extracted online from an execution trace. POTA [205] is another partial order trace analyzer system. Java-MoP [46] 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 [165]. Algorithms using alternating automata to monitor LTL properties are proposed in [83], and a specialized LTL collecting statistics along the execution trace is described in [84]. Various algorithms to generate testing automata from temporal logic formulae are discussed in [185, 180], and [92] 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 18

19 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 [190, 206] 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 [15] 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 [227]. 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 a 1 and then a sub-action a 2, and that the failure of any of the sub-actions should not propagate: action a should eventually succeed, regardless of whether a 1 or a 2 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 (<>) 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 19

20 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 1.1, 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 2.1 together with Maude [59], a high-performance system supporting both membership equational logic [174] and rewriting logic [173]. 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 second 2, so it can quite well be used as an implementation language. Section 8.1 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. 2 Personal communication by José Meseguer. 20

21 In previous work we described a technique which synthesizes efficient dynamic programming algorithms for checking LTL formulae on finite execution traces [193]. 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 [109, 46]), for the sake of completeness we present it in some detail in Section 8.2. 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 trace-storing. In [110, 111, 109] 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. Section 8.3 presents our first practical rewriting-based algorithm, which can directly monitor an LTL formula. This algorithm originates in [105, 193] and it was partially presented at the Automated Software Engineering conference [107]. 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 8.4.1, after processing each event. The practical result of Section 8.3 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 8.4, where a rewriting-based algorithm is used to generate an optimal special 21

22 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) [39], 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. 1.1 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 the paper 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 22

23 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 [126] for a O(n 3 m) dynamic programming algorithm (n is the length of the execution trace and m is the size of the ERE), and to [229, 148] for O(n 2 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 nonstoring 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 8.2), 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. 23

24 1.1.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. The dynamic programming algorithm presented in Section 8.2 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 8.3 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 8.3 is synchronous in many practical situations. The algorithm in Section 8.4 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 (Figure 8.2), 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 PSPACE-complete, like for standard infinite trace LTL [214]). 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 8.3. 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 24

25 (!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 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 waitfor-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 [202], 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 time temporal logic, and a prototype system called Java MultiPathExplorer is being implemented [208]. The main idea here is to instrument Java classes to emit events timestamped by vector clocks [82], 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 25

26 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. 26

27 Chapter 2 Background, Preliminaries, Notations Add some structure to this chapter from RV03 and RTA03 We let N denote the set of natural numbers including 0 but excluding the infinity symbol and let N denote the set N { }. We also let Q denote the set of rational numbers and R the set of real numbers; as for natural numbers, the subscript can also be added to Q and R for the corresponding extensions of these sets. Q + and R + denote the sets of strictly positive (0 not included) rational and real numbers, respectively. We fix a set Σ of elements called events or states. We call words in Σ finite traces and those in Σ ω infinite traces. If u Σ Σ ω then u i is the i-th state or event that appears in u. We call finite-trace properties sets P Σ of finite traces, infinite-trace properties sets P Σ ω of infinite traces, and just properties sets P Σ Σ ω of finite or infinite traces. If the finite or infinite aspect of traces is understood from context, then we may call any of the types or properties above just properties. We may write P (w) for a property P and a (finite or infinite) trace w whenever w P. Traces and properties are more commonly called words and languages, respectively, in the literature; we prefer to call them traces and properties to better reflect the intuition that our target application is monitoring and system observance, not formal languages. We take, however, the liberty to also call them words and languages whenever that terminology seems more appropriate. 27

28 In some cases states can be simply identified with their names, or labels, and specifications of properties on traces may just refer to those labels. For example, the regular expression (s 1 s 2 ) specifies all those finite traces starting with state s 1 and in which states s 1 and s 2 alternate. In other cases, one can think of states as sets of atomic predicates, that is, predicates that hold in those states: if s is a state and a is an atomic predicate, then we say that a(s) is true iff a holds in s; thus, if all it matters with respect to states is which predicates hold and which do not hold in each state, then states can be faithfully identified with sets of predicates. We prefer to stay loose with respect to what holds means, because, depending on the context, it can mean anything. In conventional software situations, atomic predicates can be: boolean expressions over variables of the program, their satisfaction being decided by evaluating them in the current state of the program; or whether a function is being called or returned from; or whether a particular variable is being written to; or whether a particular lock is being held by a particular thread; and so on. In the presence of atomic predicates, specifications of properties on traces typically only refer to the atomic predicates. For example, the property always a before b, that is, those traces containing no state in which b holds that is not preceded by some state in which a holds (for example, a can stand for authentication and b for resource access ), can be expressed in LTL as the formula (b a). Let us recall some basic notions and notations from formal languages, temporarily using the consecrated terminology of words and languages instead of traces and properties. For an alphabet Σ, let L Σ be the set of languages over Σ, i.e., the powerset P(Σ ). By abuse of language and notation, let be the empty language {} and ɛ the language containing only the empty word, {ɛ}. If L 1, L 2 L Σ then L 1 L 2 is the language {α 1 α 2 α 1 L 1 and α 2 L 2 }. Note that L = L = and L ɛ = ɛ L = L. If L L Σ then L is {α 1 α 2 α n n 0 and α 1, α 2,..., α n L} and L is Σ L. We next recall some notions related to cardinality. If A is any set, we let A denote the cardinal of A, which expresses the size of A. When A is finite, A is precisely the number of elements of A and we call it a finite cardinal. Infinite sets can have different cardinals, called transfinite or even infinite. For example, natural numbers N have the cardinal ℵ 0 (pronounced aleph zero ) and real numbers R have the cardinal c, also called the cardinal of the continuum. Two sets A and B are said to have the same cardinal, written A = B, iff there is some bijective mapping between the two. We 28

29 write A B iff there is some injective mapping from A to B. The famous Cantor-Bernstein-Schroeder theorem states that if A B and B A then A = B. In other words, to show that there is some bijection between sets A and B, it suffices to find an injection from A to B and an injection from B to A. The two injections need not be bijections. For example, the inclusion of the interval (0, 1) in R + is obviously an injection, so (0, 1) R +. On the other hand, the function x x/(2x + 1) from R + to (0, 1) (in fact its codomain is the interval (0, 1/2)) is also injective, so R + (0, 1). Neither of the two injective functions is bijective, yet by the Cantor-Bernstein-Schroeder theorem there is some bijection between (0, 1) and R +, that is, (0, 1) = R +. We will use this theorem to relate the various types of safety properties; for example, we will show that there is an injective function from safety properties over finite traces to safety properties over infinite traces and another injective function in the opposite direction. Unfortunately, the Cantor-Bernstein-Schroeder theorem is existential: it only says that some bijection exists between the two sets, but it does not give us an explicit bijection. Since the visualization of a concrete bijection between different sets of safety properties can be very meaningful, we will avoid using the Cantor-Bernstein-Schroeder theorem when we can find an explicit bijection between two sets of safety properties. If A is a set of cardinal α, then 2 α is the cardinal of P(A), the power set of A (the set of subsets of A). It is known that 2 ℵ 0 = c, that is, there are as many sets of natural numbers as real numbers. The famous, still unanswered continuum hypothesis, states that there is no set whose size is strictly between ℵ 0 and c; more generally, it states that, for any transfinite cardinal α, there is no proper cardinal between α and 2 α. If A and B are infinite sets, then A + B and A B are the cardinals of the sets A B and A B, respectively. An important property of transfinite cardinals is that of absorption the larger cardinal absorbs the smaller one: if α and β are transfinite cardinals such that α β, then α + β = α β = β; in particular, c 2 c = 2 c. Besides sets of natural numbers, there are several other important sets that have cardinal c: streams (i.e., infinite sequences) of Booleans, streams of reals, non-empty closed or open intervals of reals, as well as the sets of all open or closed sets of reals, respectively (Exercise 1). For our purposes, if Σ is an enumerable set of states, then Σ is also enumerable, so it has cardinal ℵ 0. Also, if Σ c, in particular if it is finite, then Σ ω has the cardinal c, because it is equivalent to streams of states. We can then immediately infer that the set of finite-trace properties over Σ has 29

30 cardinal 2 ℵ 0 = c, while the set of infinite-trace properties has cardinal 2 c. end from RV03 and RTA03 Exercises Exercise 1 Show that each of the following sets have cardinal c: streams (i.e., infinite sequences) of Booleans; streams of natural numbers; streams of real numbers; closed intervals of real numbers; open intervals of real numbers; closed sets of real numbers; open sets of real numbers. from J.ASE 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 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 [174, 38] for a comprehensive exposition of MEL. Basic Definition A many-kinded algebraic signature (K, Σ) consists of a set K and a (K K)- indexed set Σ = {Σ k1 k 2...k n,k k 1, k 2,..., k n, k K} of operations, where an operation σ Σ k1 k 2...k n,k is written σ : k 1 k 2...k n 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. 30

31 For a many-kinded signature (K, Σ), a Σ-algebra A consists of a K- indexed set {A k k K} together with interpretations of operations σ : k 1 k 2...k n k into functions A σ : A k1 A k2 A kn A k. For any given signature Ω = (K, Σ, π), an Ω-membership algebra A is a Σ-algebra together with a set A s 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 the algebra of Σ-terms over variables in X extending X iteratively as follows: if σ : k 1 k 2...k n k and t 1 T Σ,k1 (X), t 2 T Σ,k2 (X),..., t n T Σ,kn (X), then σ(t 1, t 2,..., t n ) 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 a 1 a n, where a, a 1,..., a n are atomic (Ω, X)-equations or (Ω, X)-memberships, and {a 1,..., a n } 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) A s. A satisfies ( X) a if a 1... a n, written A = Ω ( X) a if a 1... a n, iff for each θ : X A, if A, θ = Ω a 1 and... and A, θ = Ω a n, 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. Inference Rules MEL admits complete deduction (see [174], where the rule of congruence is stated in a somewhat different but equivalent way). In the congruence rule below, σ Σ k1...k i,k, W is a set of variables w 1 : k 1,..., w i 1 : k i 1, w i+1 : k i+1,..., w n : k n, and σ(w, t) is a shorthand for the term 31

32 σ(w 1,..., w i 1, t, w i+1,..., w n )): (1) Reflexivity : E Ω ( X) t = t (2) Symmetry : (3) Transitivity : (4) Congruence : (5) Membership : (6) Modus Ponens : E Ω ( X) t = t E Ω ( X) t = t E Ω ( X) t = t, E Ω ( X) t = t E Ω ( X) t = t E Ω ( X) t = t E Ω ( X, W ) σ(w, t) = σ(w, t ), for each σ Σ E Ω ( X) t = t, E Ω ( X) t : s E Ω ( X) t : s Given a sentence in E ( Y ) t = t if t 1 = t 1... t n = t n w 1 : s 1... w m : s m (resp. ( Y ) t : s if t 1 = t 1... t n = t n w 1 : s 1... w m : s m ) and θ : Y T Σ (X) s.t. for all i {1,.., n} and j {1,.., m} E Ω ( X) θ(t i ) = θ(t i ), E Ω ( X) θ(w j ) : s j 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 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 : (8) Implication Elimination : E Ω X ( ) a if a 1... a n E Ω ( X) a if a 1... a n E {a 1,..., a n } Ω ( ) a E Ω ( ) a if a 1... a n 32

33 Theorem 1 (from [174]) With the notation above, E = Ω ( X) a if a 1... a n if and only if E Ω ( X) a if a 1... a n. 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 8.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 [97] 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. 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...k n,k and s 1,..., s n, s S with π(s 1 ) = k 1,..., π(s n ) = k n, π(s) = k, then the declaration σ : s 1 s n s is syntactic sugar for ( x 1 : k 1,..., x n : k n ) σ(x 1,..., x n ) : s if x 1 : s 1 x n : s n. 33

34 Variables. ( x : s, X) a if a 1 a n is syntactic sugar for the Ω- sentence ( x : π(s), X) a if a 1 a n x : s. With this, the operation declaration σ : s 1 s n s above is equivalent to ( x 1 : s 1,..., x n : s n ) σ(x 1,..., x n ) : s Maude Maude [59] is a freely distributed high-performance system in the OBJ [98] algebraic specification family, supporting both rewriting logic [173] and membership equational logic [174]. 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 [59] 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. 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. 34

35 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. 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-fix 1 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 1 Underscores are places for arguments. 35

36 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 operator 2, 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 A : Atom. var T : Trace. var E : Event. var E* : Event*. op _{_} : Formula Event* -> Formula [prec 10]. eq true{e*} = true. eq false{e*} = false. eq A{A E} = true. eq A{E} = false [owise]. eq A{E *} = A{E}. 2 The lower the precedence number, the tighter the binding. 36

37 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. Defining Propositional Calculus A rewriting decision procedure for propositional calculus due to Hsiang [130] is adapted and presented. It provides the usual connectives _/\_ 37

38 (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 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 [98]. Below we give its obvious translation to Maude together with its finite trace semantics, noticing that Hsiang [130] 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 38

39 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 loaded 3 in Maude, reductions can be done as follows: reduce a -> b /\ c <-> (a -> b) /\ (a -> c). ***> should be true reduce a <->! b. ***> 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 16. end from J.ASE 05 3 Either by typing it or using the command in <filename>. 39

40 40

41 Chapter 3 Safety Properties safety property = every violation occurs after a finite execution. later, when talking about LTL, discuss the classification of safety properties in [151] In the literature, what we call prefixes are also called good prefixes, while the rest of the prefixes are called bad prefixes. Intuitively, a safety property of a system is one stating that the system cannot go wrong, or, as Lamport [154] put it, that the bad thing never happens. In other words, in order for a system to violate a safety property, it should eventually go wrong or the bad thing should eventually happen. There is a very strong relationship between safety properties and runtime monitoring: if a safety property is violated by a running system, then the violation should happen during the execution of the system, in a finite amount of time, so a monitor for that property observing the running system should be able to detect the violation; an additional point in the favor of monitoring is that, if a system violates a safety property at some moment during its execution, then there is no way for the system to continue its execution to eventually satisfy the property, so a monitor needs not wait for a better future once it detects a bad present/past. State properties or assertions that need only the current state of the running system to check whether they are violated or not, such as no division by 0, or x positive, or no deadlock, are common safety properties; 41

42 once violated, one can stop the computation or take corrective measures. However, there are also interesting safety properties that involve more than one state of the system, such as if one uses resource x then one must have authenticated at some moment in the past, or any start of a process must be followed by a stop within 10 units of time, or take command from user only if the user has logged in at some moment in the past and has not logged out since then, etc. Needless to say that the atomic events, or states, which form execution traces on which safety properties are defined, can be quite abstract: not all the details of a system execution are relevant for the particular safety property of interest. In the context of monitoring, these relevant events or states can be extracted by means of appropriate instrumentation of the system. For example, runtime monitoring systems such as Tracematches [6] and MOP [49] use aspect-oriented technology to hook relevant observation points and appropriate event filters in a system. It is customary to define safety properties as properties over infinite traces, to capture the intuition that they are defined for systems that can potentially run forever, such as reactive systems. A point in favor of infinite traces is that finite traces can be regarded as special cases of infinite traces, namely ones that stutter indefinitely in their last state (see, for example, Abadi and Lamport [2, 3]). Infinite traces are particularly desirable when one specifies safety properties using formalisms that have infinite-trace semantics, such as linear temporal logics or corresponding automata. While infinity is a convenient abstraction that is relatively broadlyaccepted nowadays in mathematics and in theoretical foundations of computer science, there is no evidence so far that a system can have an infinitetrace behavior (we have not seen any). A disclaimer is in place here: we do not advocate finite-traces as a foundation for safety properties; all we try to do is to argue that, just because they can be seen as a special case of infinite traces, finite traces are not entirely uninteresting. For example, a safety property associated to a one-time-access key issued to a client can be activate, then use at most once, then close. Using regular patterns over the alphabet of relevant events Σ = {activate, use, close}, this safety property can be expressed as activate (ɛ + use) close ; any trace that is not a prefix of the language of this regular expression violates the property, including any other activation or use of the key after it was closed. While these finite-trace safety properties can easily be expressed as infinite-trace safety properties, we believe that that would be more artificial than simply accepting that in practice we deal with many finite-trace safety properties. 42

43 In this section we discuss various approaches to formalize safety properties and show that they are ultimately directly or indirectly equivalent. We categorize them into finite-trace safety properties, infinite-trace safety properties, and finite- and infinite-trace safety properties: 1. Section 3.1 defines safety properties over finite traces as prefix closed properties. A subset of finite-trace safety properties, that we call persistent, contain only traces that have a future within the property, that is, finite traces that can be continued into other finite traces that are also in the safety property. Persistent safety properties appear to be the right finite-trace variant that corresponds faithfully to the more conventional infinite-trace safety properties. Even though persistent safety properties form a proper subset of finite-trace safety properties and each finite-trace safety property has a largest persistent safety property included in it, we show that there is in fact a bijection between safety properties and persistent safety properties by showing them both to have the cardinal of the continuum c. 2. In Section 3.2, we consider two standard infinite-trace definitions of a safety property, one based on the intuition that violating behaviors must manifest so after a finite number of events and the other based on the intuition of a safety property as a closed set in an appropriate topology over infinite-traces. We show them both equivalent to persistent safety properties over finite traces, by constructing an explicit bijection (as opposed to using cardinality arguments and infer the existence of a bijection); consequently, infinite-trace safety properties also have the cardinal of the continuum c. Since closed sets of real numbers are in a bijective correspondence with the real numbers, we indirectly rediscover Alpern and Schneider s result [9] stating that infinite-trace safety properties correspond to closed sets in infinite-trace topology. 3. Section 3.3 considers safety properties defined over both finite and infinite traces. We discuss two definitions of such safety properties encountered in the literature, and, using cardinality arguments, we show their equivalence with safety properties over only finite traces. In particular, safety properties over finite and infinite traces also have the cardinality of the continuum c. We also show that prefix-closeness is not a sufficient condition to characterize (not even bijectively) such safety properties, by showing that there are significantly more (2 c ) prefixclosed properties over finite and infinite traces than safety properties. 43

44 Therefore, each of the classes of safety properties is in bijection with the real numbers. Since there are so many safety properties, we can also insightfully conclude that there is no enumerable mechanism to define all the safety properties, because ℵ 0 c. Therefore, particular logical or syntactic recursive formalisms can only define some of the safety properties, but not all of them. 3.1 Finite Traces One of the most common intuitions for a safety property is as a prefix-closed set of finite traces. This captures best the intuition that once something bad happened, there is no way to recover: if w P then there is no u such that P (wu), which is equivalent to saying that if P (wu) then P (w), which is equivalent to saying that P is prefix closed. From a monitoring perspective, a prefix closed property can be regarded as one containing all the good (complete or partial) behaviors of the observed system: once a state is encountered that does not form a good behavior together with the previously observed states, then a violation can be reported. Definition 1 Let prefixes : Σ P(Σ ) be the prefix function returning for any finite trace all its prefixes, and let prefixes : P(Σ ) P(Σ ) be its corresponding closure operator that takes sets of finite traces and closes them under prefixes. Note that prefixes : P(Σ ) P(Σ ) is indeed a closure operator (Exercise 2): it is extensive (P prefixes(p )), monotone (P P implies prefixes(p ) prefixes(p )), and idempotent (prefixes(prefixes(p )) = prefixes(p )). Definition 2 Let Safety be the set of finite-trace prefix-closed properties, that is, the set {P P(Σ ) P = prefixes(p )}. In other words, Safety is the set of fixed points of the prefix operator prefixes : P(Σ ) P(Σ ). The star superscript in Safety reflects that its traces are finite; in the next section we will define a set Safety ω of infinite-trace safety properties. Since prefixes(p ) Safety for any P P(Σ ), we can assume from here on that prefixes : P(Σ ) P(Σ ) is actually a function P(Σ ) Safety. Example 1 Consider the one-time-access key safety property discussed above, saying that a client can activate, then use at most once, and then 44

45 close the key. If Σ = {activate, use, close}, then this safety property can be expressed as the finite set of finite words {ɛ, activate, activate close, activate use, activate use close} No other behavior is allowed. Now suppose that the safety policy is extended to allow multiple uses of the key once activated, but still no further events once it is closed. The extended safety property has now infinitely many finite-traces: {ɛ} {activate} {use n n N} {ɛ, close}. Note that this property is indeed prefix-closed. A monitor in charge of online checking this safety property would report a violation if the first event is not activate, or if it encounters any second activate event, or if it encounters any event after a close event is observed, including another close event. It is interesting to note that this finite-trace safety property encompasses both finite and infinite aspects. For example, it does not preclude behaviors in which one sees an activate event and then an arbitrary number of use events; use events can persist indefinitely after an activate event without violating the property. On the other hand, once a close event is encountered, no other event can be further seen. We will shortly see that the safety property above properly includes the persistent safety property {ɛ} {activate use n n N}, which corresponds to the infinite-trace safety property {activate use ω }. While prefix closeness seems to be the right requirement for a safety property, one can argue that it is not sufficient. For example, in the context of reactive systems that supposedly run forever, one may think of a safety property as one containing safe finite traces, that is, ones for which the reactive system can always find a way to continue its execution safely. The definition of safety properties above includes, among other safety properties, the empty set of traces as well as all prefix-closed finite sets of finite traces; any reactive system will eventually violate such safety properties, so one can say that the definition of safety property above is too generous. We next define persistent safety properties as ones that always allow a future; intuitively, an observed reactive system that is in a safe state can always (if persistent enough) find a way to continue its execution to a next safe state. This notion is reminiscent of feasibility, a semantic characterization of fairness in [13], and of machine closeness [2, 203], also used in the context of fairness. 45

46 Definition 3 Let PersistentSafety be the set of finite-trace persistent safety properties, that is, safety properties P Safety such that if P (w) for some w Σ then there is some a Σ such that P (wa). If a persistent safety property is non-empty, then note that it must contain an infinite number of words. The persistency aspect of a finite-trace safety property can be regarded, in some sense, as a liveness argument. Indeed, assuming that it is a good thing for a trace to be indefinitely continued, then a persistent safety property is one in which the good thing always eventually happens. If one takes the liberty to regard stuck computations as unfair, then the persistency aspect above can also be regarded as a fairness argument. Another way to think of persistent safety properties is as a means to refer to infinite behaviors by means of finite traces. This view is, in some sense, dual to the more common approach to regard finite behaviors as infinite behaviors that stutter infinitely in a last state (see, for example, Abadi and Lamport [2, 3] for a formalization of such last-state infinite stuttering). Note that if Σ is a degenerate set of events containing only one element, that is, if Σ = 1, then Safety = ℵ 0 and PersistentSafety = 2; indeed, if Σ = {a} then Safety contains precisely the finite properties a n = {a i 0 i n} for each n N plus the infinite property {a n n N}, so a total of ℵ = ℵ 0 properties, while PersistentSafety contains only two properties, namely and {a n n N}. The case when there is only one event or state in Σ is neither interesting nor practical. Therefore, from here on in this paper we take the liberty to assume that Σ 2. Since in practice Σ contains states or events generated by a computer, for simplicity in stating some of the subsequent results, we also take the liberty to assume that Σ ℵ 0 ; therefore, Σ can be any finite or recursively enumerable set, including N, N, Q, etc., but cannot be R or any set larger than R. With these assumptions, it follows that Σ = ℵ 0 (finite words are recursively enumerable) and Σ ω = c (infinite streams have the cardinality of the continuum). Proposition 1 Safety and PersistentSafety are closed under union; Safety is also closed under intersection. Proof: The union and the intersection of prefix-closed properties is also prefix-closed. Also, the union of persistent prefix-closed properties is also persistent. The intersection of persistent safety properties may not be persistent: 46

47 Example 2 Let Σ be the set {0, 1}. Let P = {1 m m N} and P = {ɛ} {10 m m N} be two persistent safety properties, where ɛ is the empty word (the word containing no letters). Then P P is the finite safety property {ɛ, 1}, which is not persistent. If one thinks that this happened because P P does not contain any proper (i.e., non-empty) persistent property, then one can take instead the persistent safety properties P = {0 n n N} {1 m m N} and P = {0 n n N} ({ɛ} {10 m m N}, whose intersection is the safety property {0 n n N} {0 n 1 n N}. This safety property is not persistent because its words ending in 1 cannot persist, but it contains the proper persistent safety property {0 n n N}. Therefore, we can associate to any safety property in Safety a largest persistent safety property in PersistentSafety, by simply taking the union of all persistent safety properties that are included in the original safety property (the empty property is one of them, the smallest): Definition 4 For a safety property P Safety, let P PersistentSafety be the largest persistent safety property with P P. The following example shows that one may need to eliminate infinitely many words from a safety property in order to obtain a persistent safety property: Example 3 Let Σ = {0, 1} and let P be the safety property {0 n n N} {0 n 1 n N}. Then P can contain no word ending with a 1 and can contain all the words of 0 s. Therefore, P = {0 n n N}. Finite safety properties obviously cannot contain any non-empty persistent safety property, that is, P = if P is finite. But what if P is infinite? Is it always the case that it contains a non-empty persistent safety property? Interestingly, it turns out that this is true if and only if Σ is finite: Proposition 2 If Σ is finite and P is a safety property containing infinitely many words, then P. Proof: For each letter a Σ, let us define the derivative of P wrt a, written δ a (P ), as the language {w Σ aw P }. Since P = {ɛ} a Σ{a} δ a (P ) 47

48 since Σ is finite, and since P is infinite, it follows that there is some a 1 Σ such that δ a1 (P ) is infinite; note that a 1 P since P is prefix closed. Similarly, since δ a1 (P ) is infinite, there is some a 2 Σ such that δ a2 (δ a1 (P )) is infinite and a 1 a 2 P. Iterating this reasoning, we can find some a n Σ for each n N, such that a 1 a 2... a n P and δ an ( (δ a2 (δ a1 (P ))) ) is infinite, that is, the set {w Σ a 1 a 2... a n w P } is infinite. It is now easy to see that the set {a 1 a 2... a n n N} P is persistent. Therefore, P. The following example shows that Σ must indeed be finite in order for the result above to hold: Example 4 Consider some infinite set of events or states Σ. Then we can label distinct elements in Σ with distinct labels in N { }. We only need these elements from Σ; therefore, without loss of generality, we can assume that Σ = N { }. Let P be the safety property {ɛ} { n (n 1)... (m + 1) m 0 m n + 1}, where ɛ is the empty word (the word containing no letters) and n... (n + 1) is also the empty word for any n N. Then P is the empty property. Indeed, note that any persistent safety property P included in P cannot have traces ending in 0, because those cannot be continued into other traces in P ; since P cannot contain traces ending in 0, it cannot contain traces ending in 1 either, because such traces can only be continued with a 0 letter into traces in P, but those traces have already been decided that cannot be part of P ; inductively, one can show that P can contain no words ending in letters that are natural numbers in N. Since the only trace in P ending in is itself and since can only be continued with a natural number letter into a trace in P but such trace cannot belong to P, we deduce that P can contain no word with letters in Σ. In particular, P must be empty. Even though we know that the largest persistent safety property P included into a safety property P always exists because PersistentSafety is closed under union, we would like to have a more constructive way to obtain it. A first and obvious thing to do is to eliminate from P all the stuck computations, that is, those which cannot be added any new state to obtain a trace that is also in P. This removal step does not destroy the prefix-closeness of P, but it may reveal new computations which are stuck. By iteratively eliminating all the computations that get stuck in a finite number of steps, one would expect to obtain a persistent safety property, 48

49 namely precisely P. It turns out that this is indeed true only if Σ is finite. If that is the case, then the following can also be used as an alternative definition of P : Proposition 3 Given safety property P Safety, then let P be the property {w P ( a Σ) wa P }. Also, let {P i i N} be properties defined as P 0 = P and P i+1 = Pi for all i 0. Then P = i 0 P i whenever Σ is finite. Proof: It is easy to see that if P is prefix-closed then P P is also prefix-closed, so P is also a property in Safety. Therefore, the properties P i form a sequence P = P 0 P 1 P 2 of increasingly smaller safety properties. Let us first prove that i 0 P i is a persistent safety property. Assume by contradiction that for some w i 0 P n there is no a Σ such that wa i 0 P i. In other words, we can find for each a Σ some i a 0 such that wa P ia. Since Σ is finite, we can let i be the largest among the natural numbers i a N for all a Σ. Since P i P ia for all a Σ, it should be clear that there is no a Σ such that wa P i, which means that w P i+1. This contradicts the fact that w i 0 P i. Therefore, i 0 P i PersistentSafety. Let us now prove that i 0 P i is the largest persistent safety property included in P. Let P be any persistent safety property included in P. We show by induction on i that P P i for all i N. The base case, P P 0, is obvious. Suppose that P P i for some i N and let w P. Since P is persistent, there is some a Σ such that wa P P i, which means that w P i+1. Since w was chosen arbitrarily, it follows that P P i+1. Therefore, P i 0 P i. We next show that the finiteness of Σ was a necessary requirement in order for the result above to hold. In other words, we show that if Σ is allowed to be infinite then we can find a safety property P Safety over Σ such that P PersistentSafety and i 0 P i Safety are distinct. Since we showed in the proof of Proposition 3 that any persistent safety property P is included in i 0 P i, it follows that P i 0 P i. Since P is the largest persistent safety property included in P, one can easily show that P = ( i 0 P i). Therefore, it suffices to find a safety property P such that i 0 P i is not persistent, which is what we do in the next example: Example 5 Consider the safety property P over infinite Σ = N { } discussed in Example 4, namely {ɛ} { n (n 1)... (m + 1) m 0 m 49

50 n + 1}. Then one can easily show by induction on i N that the properties P i defined in Proposition 3 are the sets {ɛ} { n (n 1)... (m + 1) m i m n + 1}; in other words, each P i excludes from P all the words whose last letters are smaller than i when regarded as natural numbers. Then the intersection i 0 P i contains no trace ending in a natural number; the only possibility left is then i 0 P i = {ɛ, }, which is different from P = (see Example 4). One may argue that P i 0 P i above happened precisely because P was empty. One can instead pick the safety property Q = {0 n n N} P. Then one can show following the same idea as in Example 4 that Q = {0 n n N}. Further, one can show that Q i = {0 n n N} P i, so i 0 Q i = {0 n n N} {0 n n N}, which is different from Q. Persistency is reminiscent of feasibility introduced by Apt et al. [13] in the context of fairness, and of machine closeness introduced by Abadi and Lamport [2, 3] (see also Schneider [203]) in the context of refinement. Let us use the terminology machine closeness : a property L (typically a liveness or a fairness property) is machine closed for a property M (typically given as the language of some state machine) iff L does not prohibit any of the observable runtime behaviors of M, that is, iff prefixes(m) = prefixes(m L); for example, if M is the total property (i.e., every event is possible at any moment, i.e., M = Σ ) and L is the property stating that always eventually event a, then any prefix of M can be continued to obtain a property satisfying L. Persistency is related to machine closeness in that a safety property P is persistent if and only if P is machine closed for P. In other words, there is nothing P can do in a finite amount of time that P cannot do. However, there is a caveat here: since liveness and fairness are inherently infinite-trace notions, machine closeness (or feasibility) have been introduced in the context of infinite-traces. On the other hand, persistency makes sense only in the context of finite traces. It is clear that PersistentSafety is properly included in Safety. Yet, we next show that, surprisingly, there is a bijective correspondence between Safety and PersistentSafety, both having the cardinal of the continuum: Theorem 2 PersistentSafety = Safety = c. Proof: Since Σ is recursively enumerable and since 2 ℵ 0 = c, we can readily infer that PersistentSafety Safety P(Σ ) = c. Let us now define an injective function ϕ from the open interval of real numbers (0, 1) to PersistentSafety. Since Σ 2, let us distinguish 50

51 two different elements in Σ and let us label them 0 and 1. For a real r (0, 1), let ϕ(r) be the set {α α {0, 1} and 0.α < r}, where 0.α is the (rational) number in (0, 1) whose decimals in binary representation are α, and where α is the word in Σ corresponding to α. Note that the set ϕ(r) P(Σ ) is prefix-closed for any r (0, 1), and that if w ϕ(r) then also w0 ϕ(r) (the latter holds since, by real numbers conventions, 0.α = 0.α0), so ϕ(r) PersistentSafety. Since the set of rationals with finite number of decimals in binary representation is dense in R (i.e., it intersects any open interval in R) and in particular in the interval (0, 1), it follows that the function ϕ : (0, 1) PersistentSafety is injective: indeed, if r 1 r 2 (0, 1), say r 1 < r 2, then there is some α {0, 1} such that r 1 < 0.α < r 2, so ϕ(r 1 ) ϕ(r 2 ). Since the interval (0, 1) has the cardinal of the continuum c, the existence of the injective function ϕ implies that c PersistentSafety. By the Cantor-Bernstein-Schroeder theorem it follows that PersistentSafety = Safety = c. From safety: The proof above could have been rearranged to avoid the need to use the set PersistentSafety. However, we prefer to keep it for two reasons: 1. For finite-traces, persistent safety properties appear to be more natural in the context of reactive systems than just prefix closed properties; 2. Persistent safety properties play a technical bridge role in the next section to show that the infinite-trace safety properties also have the cardinal c. With regards to finite-traces, persistent safety properties appear to be more natural in the context of reactive systems than just prefix-closed properties. Also, persistent safety properties play a technical bridge role in the next section to show that the infinite-trace safety properties also have the cardinal c. 3.2 Infinite Traces The finite-trace safety properties defined above, persistent or not, rely on the intuition of a correct prefix: a safety property is identified with the set of all its finite prefixes. In the case of a persistent safety property, each informal 51

52 infinite acceptable behavior is captured by its infinite set of finite prefixes. Even though persistent safety properties appear to capture well in a finitetrace setting the intuition of safety in the context of (infinite-trace) reactive systems, one could argue that it does not say anything about unacceptable infinite traces. Indeed, one may think that persistent safety properties do not capture the intuition that if an infinite trace is unacceptable then there must be some finite prefix of it which is already unacceptable. In this section we show that there is in fact a bijection between safety properties over infinite traces and persistent safety properties over finite traces as we defined them in the previous section. We start by extending the prefixes function to infinite traces: Definition 5 Let prefixes : Σ ω P(Σ ) be the function returning for any infinite trace u all its finite prefixes prefixes(u), and let prefixes : P(Σ ω ) P(Σ ) be its corresponding extension to sets of infinite traces. Note that prefixes(s) PersistentSafety for any S P(Σ ω ), so prefixes is in fact a function P(Σ ω ) PersistentSafety. The definition of safety properties over infinite traces below appears to be the most used definition of a safety property in the literature; at our knowledge, it was formally introduced by Alpern and Schneider [9], but they credit the insights of their definition to Lamport [154]. Definition 6 Let Safety ω be the set of infinite-trace properties Q P(Σ ω ) s.t.: if u Q then there is a finite trace w prefixes(u) s.t. wv Q for any v Σ ω. In other words, if an infinite behavior violates the safety property then there is some finite-trace violation threshold ; once the violation threshold is reached, there is no chance to recover. The following proposition can serve as an alternative and more compact definition of Safety ω : Proposition 4 Safety ω = {Q P(Σ ω ) u Q iff prefixes(u) prefixes(q)}. Proof: Since u Q implies prefixes(u) prefixes(q), the only thing left to show is that Q Safety ω iff prefixes(u) prefixes(q) implies u Q ; the latter is equivalent to u Q implies prefixes(u) prefixes(q), which is further equivalent to u Q implies there is some w prefixes(u) s.t. w prefixes(q), which is indeed equivalent to Q Safety ω. 52

53 Another common intuition for safety properties over infinite traces is as closed sets in the topology corresponding to Σ ω. Alpern and Schneider captured formally this intuition for the first time in [9]; then it was used as a convenient definition of safety by Abadi and Lamport [2, 3] among others: Definition 7 An infinite sequence u (1), u (2),..., of infinite traces in Σ ω converges to u Σ ω, or u is a limit of u (1), u (2),...,, written u = lim i u (i), iff for all m 0 there is an n 0 such that u (i) 1 u(i) 2... u(i) m = u 1 u 2... u m for all i n. If Q P(Σ ω ) then Q, the closure of Q, is the set {lim i u (i) u (i) Q for all i N}. It can be easily shown that the overline closure above is indeed a closure operator on Σ ω, that is, it is extensive (Q Q), monotone (Q Q implies Q Q ), and idempotent (Q = Q); see Exercise 6. Definition 8 Let Safety ω lim be the set of properties {Q P(Σω ) Q = Q}. As expected, the two infinite-trace safety property definitions are equivalent; we have not found any formal proof in the literature, so for the sake of completeness we give a simple proof here: Proposition 5 Safety ω lim = Safety ω. Proof: All we need to prove is that for any Q P(Σ ω ) and any u Σ ω, prefixes(u) prefixes(q) iff u = lim i u (i) for some infinite sequence of infinite traces u (1), u (2),... in Q. If prefixes(u) prefixes(q) then one can find for each i 0 some u (i) Q such that u 1 u 2... u i = u (i) 1 u(i) 2... u(i) i, so for each m 0 one can pick n = m such that u 1 u 2... u m = u (i) 1 u(i) 2... u(i) m for all i n, so u = lim i u (i). Conversely, if u = lim i u (i) for some infinite sequence of infinite traces u (1), u (2),... in Q, then for any m 0 there is some n 0 such that u 1 u 2... u m = u (n) 1 u(n) 2... u (n) m, that is, for any prefix of u there is some u Q having the same prefix, that is, prefixes(u) prefixes(q). The next result establishes the relationship between infinite-trace safety properties and finite-trace persistent safety properties, by proposing a concrete bijective mapping relating the two (as opposed to using cardinality arguments to indirectly show only the existence of such a mapping). Therefore, there is also a bijective correspondence between safety properties over infinite traces and the real numbers: 53

54 Note there are 2 c properties over Σ ω Theorem 3 Safety ω = PersistentSafety = c. Proof: We show that there is a bijective function between the two sets of safety properties. Recall that prefixes(s) PersistentSafety for any S P(Σ ω ), that is, that prefixes is a function P(Σ ω ) PersistentSafety. Let prefixes : Safety ω PersistentSafety be the restriction of this prefix function to Safety ω. Let us also define a function ω : PersistentSafety Safety ω as follows: ω(p ) = {u Σ ω prefixes(u) P }. This function is well-defined: if u ω(p ) then by the definition of ω(p ) there is some w prefixes(u) such that w P ; since w prefixes(wv) for any v Σ ω, it follows that wv ω(p ) for any v Σ ω. We next show that prefixes and ω are inverse to each other. Let us first show that prefixes(ω(p )) = P for any P PersistentSafety. The inclusion prefixes(ω(p )) P follows by the definition of ω(p ): prefixes(u) P for any u ω(p ). The inclusion P prefixes(ω(p )) follows from the fact that P is a persistent safety property: for any w P one can iteratively build an infinite sequence v 1, v 2,..., such that wv 1, wv 1 v 2,... P, so wv 1 v 2... ω(p ). Let us now show that ω(prefixes(q)) = Q for any Q Safety ω. The inclusion Q ω(prefixes(q)) is immediate. For the other inclusion, let u ω(prefixes(q)), that is, prefixes(u) prefixes(q). Suppose by contradiction that u Q. Then there is some w prefixes(u) such that wv Q for any v Σ ω. Since w prefixes(u) and prefixes(u) prefixes(q), it follows that w prefixes(q), that is, that there is some u Q such that u = wv for some v Σ ω. This contradicts the fact that wv Q for any v Σ ω. Consequently, u Q. The second part follows by Theorem Finite and Infinite Traces It is also common to define safety properties as properties over both finite and infinite traces, the intuition for the finite traces being that of unfinished computations. For example, Lamport [155] extends the notion of safety in Definition 6 to properties over both finite and infinite traces, while Schneider et al [204, 103] give an alternative definition of safety over finite and infinite traces. We define both approaches shortly and then show their equivalence and their bijective correspondence with real numbers. Before that, we argue that the mix of finite and infinite traces is less trivial than it may appear, 54

55 by showing that there are significantly more prefix closed properties than in the case when only finite traces were considered. Definition 9 Let PrefixClosed,ω be the set of prefix-closed sets of finite and infinite traces: for Q Σ Σ ω, Q PrefixClosed,ω iff prefixes(q) Q. Also, let PersistentPrefixClosed,ω be the set of persistent prefix-closed sets of finite and infinite traces: for Q PrefixClosed,ω, it is the case that Q PersistentPrefixClosed,ω if Q(w) for some w Σ then that there is some a Σ such that Q(wa). The next result says that there is a bijective correspondence between prefix-closed and persistent prefix-closed properties also in the case of finite and infinite traces, but that there are exponentially more such properties than in the case of just finite traces: Proposition 6 PersistentPrefixClosed,ω = PrefixClosed,ω = 2 c. Proof: We show 2 c PersistentPrefixClosed,ω PrefixClosed,ω 2 c, where the middle inequality is immediate. For 2 c PersistentPrefixClosed,ω, let us define ϕ : P((0, 1)) PersistentPrefixClosed,ω as ϕ(r) = {α} prefixes(α) 0.α R where we assume for any real number in the interval (0, 1) its decimal binary representation 0.α with α {0, 1} ω (if the number is rational then α may contain infinitely many ending 0 s), and α is the infinite trace in Σ ω replacing each 0 and 1 in α by 0 and 1, respectively, where 0 and 1 are two arbitrary but fixed distinct elements in Σ (recall that Σ 2. Note that ϕ(r) is well-defined: it is clearly prefix-closed and it is also persistent because its finite traces are exactly prefixes of infinite traces, so they admit continuations in ϕ(r). It is easy to see that ϕ is injective. Since (0, 1) = c, we conclude that 2 c PersistentPrefixClosed,ω. To show PrefixClosed,ω 2 c, note that any property in PrefixClosed,ω is a union of a subset in Σ and a subset in Σ ω, so PrefixClosed,ω 2 Σ 2 Σω. Since Σ = ℵ 0, Σ ω = c, 2 ℵ 0 = c, and c 2 c = 2 c (by absorption of transfinite cardinals), we get that PrefixClosed,ω 2 c. The fact that properties in PersistentPrefixClosed,ω contain also infinite traces was crucial in showing the injectivity of ϕ in the proof above. A similar construction for the finite trace setting does not work. Indeed, if 55

56 one tries to define a function ϕ : P((0, 1)) PersistentSafety as ϕ(r) = 0.α R prefixes(α), then one can show it well-defined but cannot show it injective: e.g., ϕ((0, 0.5)) = ϕ((0, 0.5]). Since safety properties over finite and infinite traces are governed by the same intuitions as safety properties over only finite or over only infinite traces, the result above tells us that prefix closeness is not a sufficient condition to properly capture the safety properties. Schneider [204] proposes an additional condition in the context of his EM (execution monitoring) framework, namely that if an infinite trace is not in the property, then there is a finite prefix of it which is not in the property either. It is easy to see that this additional condition is equivalent to saying that an infinite trace is in the property whenever all its finite prefixes are in the property, which allows us to compactly define safety properties over finite and infinite traces in the EM style as follows: Definition 10 Safety,ω EM = {Q Σ Σ ω u Q iff prefixes(u) Q}. Note that Safety,ω EM PrefixClosed,ω. We will shortly show that Safety,ω EM is in fact much smaller than PrefixClosed,ω, by showing that Safety,ω EM = c. The consecrated definition of a safety property in the context of both finite and infinite traces is perhaps the one proposed by Lamport in [155], which relaxes the one in Definition 6 by allowing u to range over both finite and infinite traces: Definition 11 Let Safety,ω be the set of finite- and infinite-trace properties {Q Σ Σ ω u Q ( w prefixes(u)) ( v Σ Σ ω ) wv Q} Schneider informally stated in [204] that the two definitions of safety above are equivalent. It is not hard to show it formally: Proposition 7 Safety,ω EM = Safety,ω. Proof: First note that Safety,ω PrefixClosed,ω : if wu Q Safety,ω and w Q then there is some w prefixes(w), say w = w w, such that w v Q for any v, in particular w w u Q, which contradicts wu Q. Safety,ω Safety,ω EM : let Q Safety,ω and u Σ Σ ω s.t. prefixes(u) Q; if u Q then there is some w prefixes(u) s.t. wv Q for any v, in particular for v the empty word, that is, w Q, which contradicts prefixes(u) Q. Safety,ω EM Safety,ω : let u Q Safety,ω EM ; then prefixes(u) Q, that is, there is some w prefixes(u) s.t. w Q; since Q is prefix-closed, it follows that wv Q for any v Σ Σ ω. 56

57 We next show that there is a bijective correspondence between the safety properties over finite or infinite traces above and the finite trace safety properties in Section 3.1: Theorem 4 Safety,ω = Safety,ω EM = Safety = c. Proof: Safety Safety,ω,ω EM since the properties in SafetyEM are prefixclosed, so Safety Safety,ω EM. Since the functions prefixes : P(Σ ) P(Σ ) and prefixes : P(Σ ω ) P(Σ ) have actual co-domains Safety and PersistentSafety, respectively, they can be organized as a function prefixes : Safety,ω EM Safety. Let us show that this function is injective. Let us assume Q Q Safety,ω EM, say u Q and u Q, s.t. prefixes(q) = prefixes(q ). Since u Q Safety,ω EM it follows that prefixes(u) prefixes(q) Q, which implies that prefixes(u) prefixes(q ) Q ; since Q Safety,ω EM, it follows that u Q, contradiction. Therefore, prefixes : Safety,ω EM Safety is injective, which proves that Safety,ω EM Safety. The rest follows by Proposition 7 and Theorem Always Past Characterization Another common way to specify safety properties is by giving an arbitrary property on finite traces, not necessarily prefix closed, and then to require that any acceptable behavior must have all its finite prefixes in the given property. A particularly frequent case is when one specifies the property of the finite-prefixes using the past-time fragment of linear temporal logics (LTL). For example, Manna and Pnueli [161] call the resulting always (past LTL) properties safety formulae; many other authors, including ourselves, adopted the terminology safety formula from Manna and Pnueli, although some qualify it as LTL safety formula. An example of an LTL safety formula is always (b implies eventually in the past a), written using LTL notation as (b a) ; here the past time formula b a compactly specifies all the finite-traces {wsw s w, w Σ, s, s Σ, a(s) and b(s ) hold} {ws w Σ, s Σ, b(s) does not hold}. From safety: We will investigate the case when safety properties are expressed as LTL safety formulae, as well as optimal monitoring techniques for such safety properties, in Section

58 In the remainder of this section we assume that the past time prefix properties are given as ordinary sets of finite-traces (so we make abstraction of how these properties are expressed) and show not only that the resulting always past properties are safety properties, but also that any safety properties can be expressed as an always past property. This holds for all the variants of safety properties (i.e., over finite traces, over infinite traces, or over both finite and infinite traces). Definition 12 Let P Σ be any property over finite traces. Then we define the always past property P as follows: (finite traces) {w Σ prefixes(w) P }; and (infinite traces) {u Σ ω prefixes(u) P }; and (finite and infinite traces) {u Σ Σ ω prefixes(u) P }. Let Safety, Safety ω and Safety,ω be the corresponding sets of properties. From safety: In Section 10.7 we show that the language L( ϕ), that corresponds to the LTL safety formula ϕ for ϕ some pasttime LTL formula, is a property in Safety. ω If one was interested in a finite-trace or in a both finite and infinite trace semantics of LTL, then one could have shown that L( ϕ) Safety or that L( ϕ) Safety,ω. Intuitively, one can regard the square as a closure operator. Technically, it is not precisely a closure operator because it does not operate on the same set: it takes finite-trace properties to any of the three types of properties considered. Since prefixes takes properties back to finite-trace properties, we can show the following result saying that the square is a closure operator via prefixes, and that safety properties are precisely the sets of words which are closed this way: Proposition 8 The following hold for all three types of safety properties: (prefixes( P )) = P for any P Σ ; Q is a safety property iff (prefixes(q)) = Q. 58

59 Proof: Left as an exercise to the reader. See Exercise 5. We next show that the always past properties are all safety properties and, moreover, that any safety property can be expressed as an always past property: Theorem 5 The following hold: Safety = Safety, Safety ω = Safety ω, and Safety,ω = Safety,ω. Therefore, each of the always past safety properties have the cardinal c. Proof: We prove each of the equalities by double inclusion. Safety Safety. It is true because any property P in Safety is prefixclosed. Safety Safety. If P Safety then we claim that P = P, so P Safety. Indeed, since P is prefix-closed, prefixes(w) P for any w P, so w P ; also, since w prefixes(w), it follows that for any w P, w P. Safety ω Safety ω. Let P be an always past property in Safety ω, and let u be an infinite trace in Σ ω such that u P. Then it follows that prefixes(u) P, that is, there is some w prefixes(u) such that w P. Since w prefixes(wv) for any v Σ ω, it means that there is no v P such that prefixes(wv) P, that is, there is no v Σ ω such that wv P. Therefore, P Safety ω. Safety ω Safety ω. If Q Safety ω then we claim that Q = prefixes(q). The inclusion Q prefixes(q) is clear, because u Q implies prefixes(u) prefixes(q). For the other inclusion, note that if prefixes(u) prefixes(q) for some u Σ ω, then u must be in Q: if u Q then by the definition of Q Safety ω, there is some w prefixes(u) which cannot be completed into an infinite trace in Q, which contradicts prefixes(u) prefixes(q). Safety,ω Safety,ω. By Proposition 7, it suffices to show that Safety,ω Safety,ω,ω EM. Let P be an always past property in Safety, and let u Σ Σ ω such that prefixes(u) prefixes( P ). Since prefixes( P ) P, it follows that u P ; therefore, P Safety,ω EM. 59

60 Safety,ω Safety,ω,ω. It is straightforward to see that Q SafetyEM implies Q = prefixes(q). The cardinality part follows by Theorems 2, 3, and 4. Proposition 8 and Theorem 5 give yet another characterization for safety properties over any of the three combinations of traces, namely one in the style of the equivalent formulation of safety over infinite traces in Proposition 4: Q is a safety property iff it contains precisely the words whose prefixes are in prefixes(q). Exercises Exercise 2 The prefixes : P(Σ ) P(Σ ) in Definition 1 is a closure operator: it is extensive (P prefixes(p )), monotone (P P implies prefixes(p ) prefixes(p )), and idempotent (prefixes(prefixes(p )) = prefixes(p )). Exercise 3 (Counter-)Example 4 showed that the finiteness of Σ was necessary in order for Proposition 2 to hold, by defining a property P over Σ = N { } in which all non-empty words start with. Can we remove from Σ and from all the words in P? Why, or why not? Exercise 4 Same like Exercise 3, but for Example 5 instead of Example 4. Exercise 5 Prove Proposition 8. Exercise 6 The closure under limits operation in Definition 7 is indeed a closure operator on Σ ω : it is extensive (Q Q), monotone (Q Q implies Q Q ), and idempotent (Q = Q). 60

61 Chapter 4 Monitoring In this section we give yet another characterization of safety properties, namely as monitorable properties. Specifically, we formally define a monitor as a (possibly infinite) state machine without final states but with a partial transition function, and then we show that safety properties are precisely the properties that can be monitored with such monitors. We then elaborate on the problem of defining the complexity of monitoring a safety property, discussing some pitfalls and guiding principles, and show that monitoring a safety property can be an arbitrarily hard problem. Finally, we give a more compact and mathematical equivalent definition of a monitor, which may be useful in further foundational efforts in this area. Relate our definition of a monitor with Schneider s security automata 4.1 Specifying Safety Properties as Monitors Safety properties are difficult to work with as flat sets of finite or infinite words, not only because they can contain infinitely many words, but also because such a flat representation is inconvenient for further analysis. It is important therefore to specify safety properties using formalisms that are easier to represent and reason about. 61

62 From safety: The next sections in this paper investigate several dedicated formalisms that proved to be convenient in specifying safety, such as finite state machines, regular expressions and temporal logics, together with corresponding limitations and efficient monitor synthesis techniques. Formalisms known to be useful for specifying safety properties include regular expressions and temporal logics, which can be efficiently translated into finite-state machines which can then be used as monitors. In this section we formalize the intuitive notion of a monitor as a special state machine and give yet another characterization of safety properties, namely as monitorable properties. Since monitorable properties are completely defined by their monitors, it follows that all safety properties can be specified by their corresponding monitors. Recall that we work under the assumption that Σ is a set of events or program states such that Σ ℵ 0. Definition 13 A Σ-monitor, or just a monitor (when Σ is understood), is a triple M = (S, s 0, M : S Σ S), where S is a set of states, s 0 S is the initial state, and M is a deterministic partial transition function. Therefore, a monitor as defined above is nothing but a deterministic state machine without final states. Moreover, the set of states is allowed to be infinite, and the transition function has no complexity requirements (it can even be undecidable). We could have defined monitors to be standard state machines, but the subsequent technical developments would have been slightly more involved. From safety: In fact, we aim at shortly giving an even more compact definition of a monitor, that we will call canonical monitor, which appears to be sufficient to capture any safety property. The intuition for a monitor is the expected one: the monitor is driven by events generated by the observed program (the letters in Σ) each newly received event drives the monitor from its current state to some other state, as indicated by the transition function M; if the monitor ever gets stuck, that is, if the transition function M is undefined on the current state and the current event, then the monitored property is declared violated at that point by the monitor. 62

63 For any partial function M : S Σ S, we obey the following common notational convention. If s S and w = w 1 w 2... w k Σ, we write M(s, w) whenever M(s, w) is defined, that is, whenever M(s, w 1 ) and M(M(s, w 1 ), w 2 ) and... and M(...(M(s, w 1 ), w 2 )..., w k ) are all defined, which is nothing but only saying that M(...(M(s, w 1 ), w 2 )..., w k ) is defined. If we write M(s, w) = s for some s S, then, as expected, we mean that M(...(M(s, w 1 ), w 2 )..., w k ) is defined and equal to s. A monitor specifies a finite-trace property, an infinite-trace property, as well as a finite- and infinite-trace property: Definition 14 Given a monitor M = (S, s 0, M : S Σ S), we define the following properties: L (M) = {w Σ M(s 0, w) }, L ω (M) = {u Σ ω M(s 0, w) for all w prefixes(u)}, and L,ω (M) = L (M) L ω (M). We call L (M) the finite-trace property specified by M, call L ω (M) the infinite-trace property specified by M, and call L,ω (M) the finite- and infinite-trace property specified by M. Also, we let S M = {s S ( w Σ ) M(s 0, w) = s} denote the set of reachable states of M. A monitorable property is a property which can be specified by a monitor. We next capture this intuitive notion formally: Definition 15 For a property P Σ Σ ω, we let Monitors(P ) be the set of monitors {M L,ω (M) = P }. If Monitors(P ) then P is called monitorable and the elements of Monitors(P ) are called monitors of P. We define the following classes of properties: Monitorable = {P Σ P monitorable}, Monitorable ω = {P Σ ω P monitorable}, and Monitorable,ω = {P Σ Σ ω P monitorable}. The notion of persistence can also be adapted to monitors: 63

64 Definition 16 A monitor M = (S, s 0, M : S Σ S) is persistent iff for any reachable state s S M, there is an a Σ such that M(s, a). Let PersistentMonitorable = {L (M) M persistent} be the set of finite-trace properties monitorable by persistent monitors. Our next goal is to show that each monitor admits a largest persistent submonitor. To formalize it, we lift the conventional partial order relation on partial functions to monitors: Definition 17 If M 1 = (S, s 0, M 1 : S Σ S) and M 2 = (S, s 0, M 2 : S Σ S) are two monitors sharing the same states and initial state, then let M 1 M 2, read M 1 a submonitor of M 2, iff for any s S and any a Σ, if M 1 (s, a) is defined then M 2 (s, a) is also defined and M 2 (s, a) = M 1 (s, a). The above can be easily generalized to allow M 1 to only have a subset of the states of M 2, but we found that generalization unnecessary so far. The above partial-order on monitors allows us to use conventional mathematics to obtain the largest persistent sub-monitor of a monitor: Proposition 9 ({K K M and K persistent}, ) is a complete (join) semilattice for any monitor M. Proof: If {K i = (S, s 0, K i : S Σ S) M} i I is a set of persistent monitors, then their supremum (or join) is the monitor K = (S, s 0, K : S Σ S) where K(s, a) = s iff there is some i I such that K i (s, a) = s. It is easy to see that K is a well-defined monitor and that it is persistent. Since complete semilattices have maximum elements, the following definition is fully justified: Definition 18 For any monitor M = (S, s 0, M : S Σ S), we let M = (S, s 0, M : S Σ S) be the -maximal element of the complete lattice ({K K M and K persistent}, ). We next show that, as expected, there is a tight relationship between persistent safety properties (Definition 3) and persistent canonical monitors. Proposition 10 Let M = (S, s 0, M : S Σ S). Then the following hold: L ω (M) = L ω (M ), 64

65 L (M ) = L (M), and M persistent iff L (M) persistent. Proof: The first property can be shown by the following sequence of equivalences: u L ω (M) iff M(s 0, w) for all w prefixes(u), iff there is some persistent monitor K M such as K(s 0, w) for all w prefixes(u), iff M (s 0, w) for all w prefixes(u), iff u L ω (M ). The second property can be shown as follows: w L (M ) iff M (s 0, w), iff there is some u L ω (M ) such that w prefixes(u) (because M is persistent), iff there is some u L ω (M) such that w prefixes(u) (by the first property), iff there is some u Σ ω such that w prefixes(u) L (M), iff there is some u Σ ω such that w prefixes(u) L (M) (because prefixes(u) is a persistent safety property), iff w L (M). Finally, the third property is an immediate consequence of the second, noticing that M is persistent iff it is equal to M, and that L (M) is persistent iff it is equal to L (M). Theorem 6 The following hold: Monitorable = Safety, Monitorable ω = Safety ω, Monitorable,ω = Safety,ω, and PersistentMonitorable = PersistentSafety. Proof: First, note that the following hold for any monitor M: L (M) Safety, L ω (M) Safety ω, and L,ω (M) Safety,ω. These all follow by Theorem 5: taking P in Definition 12 to be the property {w Σ M(s 0, w) }, then P over finite traces is precisely L (M), over infinite traces is precisely L ω (M), and over finite and infinite traces is precisely L,ω (M), so the three languages are in Safety, Safety, ω and Safety,ω, respectively. Therefore, Monitorable Safety, Monitorable ω Safety ω, and Monitorable,ω Safety,ω. 65

66 Second, note that we can associate a default monitor M P to any finitetrace property P Σ, namely (S P, ɛ, M P : S P Σ S P ), where S P = prefixes(p ), ɛ is the empty word, and M P (w, a) is defined iff wa prefixes(p ), and in that case M P (w, a) = wa. Moreover, it is easy to check that L (M P ) = {w Σ prefixes(w) P } = P (over finite traces), L ω (M P ) = {u Σ ω prefixes(u) P } = P (over infinite traces), L,ω (M P ) = {u Σ Σ ω prefixes(w) P } = P (over both finite and infinite traces). Since P was chosen arbitrarily, it follows then by Theorem 5 that Safety Monitorable, Safety ω Monitorable ω, and Safety,ω Monitorable,ω. Finally, the equality PersistentMonitorable = PersistentSafety follows by the first fact and by Proposition Complexity of Monitoring a Safety Property We here address the problem of defining the complexity of monitoring. Before we give our definition, let us first discuss some pitfalls in defining this notion. Our definition for the complexity of monitoring resulted as a consequence of trying to avoid these pitfalls. Let P be a safety property. Pitfall 1. The complexity of monitoring P is nothing but the complexity of checking, for an input word w Σ, whether w prefixes(p ). This would be an easy to formulate decision problem, but, unfortunately, does not capture well the intuition of monitoring, because it does not require that the word w be processed incrementally, as its letters become available from the observed system. Incremental processing of letters can make a huge difference in both how complex monitoring is and how monitoring complexity can be defined. For example, it is well-known that the membership problem of a finite word to the language of an extended regular expression (ERE), i.e., a regular expression extended with complement operators, is a polynomial problem (the classic algorithm by Hopcroft and Ullman [126] runs in space O(m 2 n) and time O(m 3 n), where m is the size of the word and n that of the expression). However, From safety: as shown in Section 7 66

67 there are EREs defining safety properties whose monitoring requires nonelementary space and time. Of course, this non-elementary lower-bound is expressed only as a function of the size of the ERE representing the safety property; it does not take into account the size of the monitored trace. This leads us to our first guiding principle: Principle 1. The complexity of monitoring a safety property P should depend only upon P, not upon the trace being monitored. Indeed, since monitoring is a process that involves potentially unbounded traces, if the complexity of monitoring a property P were expressed as a function of the execution trace as well, then that complexity measure would be close to meaningless in practice, because monitoring reactive systems would have unbounded complexity. For example, consider an operating system monitoring some safety property on how its resources are being used by the various running processes; what one would like to know here is what is the runtime overhead of monitoring that safety property at each relevant event, and not the obvious fact that the more the operating system runs the larger the total runtime overhead is. Nevertheless, one can admittedly argue that it would still be useful to know how complex the monitoring of P against a given finite trace w is, in terms of both the size of (some representation of) P and the size of w; however, this is nothing but a conventional membership test decision problem, that has nothing to do with monitoring. If one picks some arbitrary off-the-shelf efficient algorithm for membership testing and uses that at each newly received event on the existing finite execution trace, then one may obtain a monitoring algorithm whose complexity to process each event grows in time, as events are processed. In the context of monitoring a reactive system, that means that eventually the monitoring process may become unfeasible, regardless of how many resources are initially available and regardless of how efficient the membership testing algorithm is. What one needs in order for the monitoring process to stay feasible regardless of how many events are observed, is a special membership algorithm that processes each event as received and whose state or processing time does not increase potentially unbounded as events are received. Therefore, one needs an algorithm which, if it takes resources R to check w, then it takes at most R + to check a one-event continuation wa of w, where does not depend on w. In other words, one needs a monitor for P of complexity. 67

68 Pitfall 2. P is typically infinite, so the complexity of monitoring P should be a function of the size of some finite specification, or representation, of P. Indeed, since Principle 1 tells us that the complexity of monitoring P is a function of P only and not of the monitored trace, one may be tempted to conclude that it is a function of the size of some convenient encoding of P. There are at least two problems with this approach, that we discuss below. One problem is that the same property P can be specified in many different ways as a structure of finite size; for example, it can be specified as a regular expression, as an extended regular expression, as a temporal logic formula, as an ordinary automaton, as a push-down automaton, etc. These formalisms may represent P as specifications of quite different sizes. Which is the most appropriate? It is, nevertheless, interesting and important to study the complexity of monitoring safety properties expressed using different specification formalisms, as a function of the property representation size, because that can give us an idea of the amount of resources needed to monitor a particular specification. However, one should be aware that such a complexity measure is an attribute of the corresponding specification formalisms, not of the specified property itself. Indeed, the higher this complexity measure for a particular formalism, the higher the encoding strength of safety properties in that formalism: for example, the complexity of monitoring safety properties expressed as EREs is non-elementary in the size of the original ERE, while the complexity of monitoring the same property expressed as an ordinary regular expression is linear in the size of the regular expression. Does that mean that one can monitor safety properties expressed as regular expressions non-elementarily more efficiently than one can monitor safety properties expressed as EREs? Of course not, because EREs and regular expressions have the same expressiveness, so they specify exactly the same safety properties. All it means is that EREs can express safety-properties non-elementarily more compactly than ordinary regular expressions. Another problem with this approach is that apparently appropriate representations of P may be significantly larger than it takes to monitor P. One may say, for example, that, whenever possible, a natural way to specify a particular safety property is as a finite-state machine, e.g., 68

69 as a monitor like in Definition 78. To be more concrete, consider that the safety property P n saying every 2 n -th event is a is specified as a monitor of 2 n states that transits with any event from each state to the next one, except for the 2 n -th state, which has only one transition, with event a, back to state 1. Therefore, the size of this representation of P n is Ω(2 n ). Assuming that each state takes n bits of storage (for example, assume that states are exactly the binary encodings of the numbers 1, 2, 3,..., 2 n ) and that the next state can be calculated from the current state in linear complexity with the size of the state (which is true in our scenario), then it is clear that the actual complexity of monitoring P n is O(n). If the complexity of monitoring P n were a function of the size of the specification of P n, then one could wrongly conclude that the complexity of monitoring every 2 n -th event is a is O(2 n ). Therefore, a safety property P has an inherent complexity w.r.t. monitoring, complexity which has nothing to do with how P is represented, or encoded, or specified. It is that inherent complexity attribute of safety properties that we are after here. From the discussion above, we draw our second guiding principle: Principle 2. The monitoring complexity of a safety property P is an attribute of P alone, not a function of the size of some adhoc representation of P. By Theorem 6, safety properties are precisely those properties that are monitorable, that is, those properties P for which there are (finite-state or not) monitors M = (S, s 0, M : S Σ S) whose (finite-trace, infinite-trace, or finite- and infinite-trace this depends upon the type of P ) language is precisely P. Any algorithm, program or system that one may come up with to be used as a monitor for P, can be organized as a monitor of the form M = (S, s 0, M : S Σ S) for P. Consequently, the complexity of monitoring P cannot be smaller than the functional complexity of the partial function M : S Σ S) corresponding to some best monitor M for P ; if there are no additional restrictions, then by best monitor we mean the one whose functional complexity of M is smallest. In particular, if there is no monitor for P whose transition partial function M is decidable, then we can safety say that the problem of monitoring P is undecidable. This discussion leads to the following: 69

70 Pitfall 3. The complexity of monitoring P is the functional complexity of function M, where M = (S, s 0, M : S Σ S) is the best monitor for P. Since safety properties are precisely the monitorable properties, this appears to be a very natural definition for the complexity of monitoring. While the functional complexity of the monitor function is indeed important because it directly influences the efficiency of monitoring, it is not a sufficient measure for the complexity of monitoring. That is because the functional complexity of M only says how complex M is in terms of the size of its input; it does not say anything about how large the state of the monitor can grow in time. For example, the rewriting-based monitoring algorithm for EREs from [190], whose states are EREs and whose transition is a derivative operation of functional complexity O(n 2 ) taking an ERE of size n into an ERE of size O(n 2 ). It would be very misleading to say that the complexity of monitoring EREs is O(n 2 ), because it may sound much better than it actually is: the n 2 factor accumulates as events are processed. Any monitor for EREs, including the one based on derivatives, eventually requires non-elementary resources (in the size of the ERE) to process a new event. Therefore, while the complexity of the function M being executed at each newly received event by a monitor M is definitely a necessary and important factor to be considered when defining the complexity of monitoring using M, it is not sufficient. One also needs to take into account the size of the input that is being passed to the monitoring function, that is, the size of the monitor state together with the size of the received event. In particular, a monitor storing all the observed trace has unbounded complexity, say, even though its monitoring function has trivial complexity (e.g., the event storing function has linear complexity). More generally, if a property admits no finite-state monitor, than we d like to say that its monitoring complexity is : indeed, for any monitor for such a property and for any amount of resources R, there is some sequence of events that would lead the monitor to a state that needs more than R resources to be stored or computed. These observations lead us to the following: Principle 3. The complexity of monitoring P is a function of both the functional complexity of M and of the size of the states in S, where M = (S, s 0, M : S Σ S) is an appropriately chosen ( best ) monitor for P. 70

71 We next follow the three principles above and derive our definition for the complexity of monitoring a safety property P. Before that, let us first define the complexity of monitoring a safety property using a particular monitor for that property, or in other words, let us first define the complexity of a monitor. During a monitoring session using a monitor, at any moment in time one needs to store at least one state, namely the state that the monitor is currently in. When receiving a new event, the monitor launches its transition function on the current state and the received input. Therefore, the (worstcase) complexity of monitoring with M = (S, s 0, M : S Σ S) could be defined as max{fc(m(s, a)) s S, a Σ}, where FC(M(s, a)) is the functional complexity of evaluating M on state s and event a, as a function of the sizes of s and a. In other words, the worst-case monitoring complexity of a particular monitor is the maximal functional complexity that its transition function has on any state and any input; this functional complexity is expressed as a function of the size of the pair (state,event). In order for such a definition to make sense formally, one would need to define or axiomatize the size of monitor states and the size of events. Since in order to distinguish N elements one needs log(n) space, we deduce that one needs at least log( S ) space to store the state of the monitor in its worst-case monitoring scenario (each state in S is reachable). Definition 19 Given a monitor M = (S, s 0, M : S Σ S), we define the complexity of monitoring M, written C Mon (M), as the function FC(M)(log S ) : N N, which is the uncurried version applied on log S of the worst-case functional complexity FC(M) : N N N of the partial function M as a function of the size of the pair (state,event) being passed to it. We assume that the complexity of monitoring a safety property P is the worst-case complexity of monitoring it using some appropriate, best monitor for P : min{max{fc(m(s, a)) s S, a Σ} M = (S, s 0, M) Monitors(P )}, 71

72 From safety: where FC(M(s, a)) is the functional complexity of evaluating M on state s and event a, as a function of the sizes of s and a. In other words, we assume that the complexity of monitoring a safety property P is the worst-case complexity of monitoring it using some appropriate, best monitor for P. The worst-case monitoring complexity of a particular monitor is the maximal functional complexity that its transition function has on any state and any input; this functional complexity is expressed as a function of the size of the pair (state,event). Therefore, in order for such a definition to make sense formally, one would need to define or axiomatize the size of monitor states and the size of events. This gives us the following: Definition 20 We let C Mon (P ) = min{fc(m) log( S ), 1 Σ M = (S, s 0, M) Monitors(P )} be the complexity of monitoring a safety property P. 4.3 Monitoring Safety Properties is Arbitrarily Hard We show that the problem of monitoring a safety property can be arbitrarily complex. The previous section tells us that there are as many safety properties as real numbers. Therefore, it is not surprising that some of them can be very hard or impossible to monitor. In this section we formalize this intuitive argument. Our approach is to show that we can associate a safety property P S to any set of natural numbers S, such that monitoring that safety property is as hard as checking membership of arbitrary natural numbers to S. The result then follows from the fact that checking memberships of natural numbers to sets of natural numbers is a problem that can be arbitrarily complex. Theorem 2 indirectly says that we can associate a persistent safety property to any set of natural numbers (sets of natural numbers are in a bijective correspondence with the real numbers). However, it is not clear how that safety property looks and neither how to monitor it. We next give a more concrete mapping from sets of natural numbers to (persistent) safety properties and show that monitoring the property is equivalent to 72

73 testing membership to the set. It suffices to assume that Σ contains only two elements, say Σ = {0, 1}. Definition 21 Let P : P(N) PersistentSafety be the mapping defined as follows: for any S N, let P S be the set 1 {1 k 0 k S} {0, 1}. It is easy to see that P S is a persistent safety property over finite traces. Also, it is easy to see that the bijection in the proof of Theorem 3 associates to P S the safety property over infinite traces 1 ω {1 k 0 k S} {0, 1} ω. Let us now investigate the problem of monitoring P S. Proposition 11 For any S N, monitoring P S is equivalent to deciding membership of natural numbers to S. Proof: If M S is an oracle deciding membership of natural numbers to S, that is, if M S (n) is true iff n S, then one can build a monitor for P S as follows: for a given trace, incrementally read and count the number of prefix 1 s; if no 0 is ever observed then monitor indefinitely without reporting any violation; when a first 0 is observed, if any, ask if M(k), where k is the number of 1 s observed; if M(k) is false, then report violation; if M(k) is true, then continue monitoring indefinitely and never report violation. It is clear that this is indeed a monitor for P S. Conversely, if we had any monitor for P S then we could build a decision procedure for membership to S as follows: given k N, send to the monitor a sequence of k ones followed by a 0; if the monitor reports violation then deduce that k S; if the monitor does not report violation, then deduce that k S. It is clear that this is a decision procedure for membership to S. The proof works for both persistent safety properties over finite traces and for safety properties over infinite traces. The claim in the title of this section follows now from the fact that the set S of natural numbers can be chosen so that its membership problem is arbitrarily complex. For example, since there are as many subsets of natural numbers as real numbers while there are only as many Turing machines as natural numbers, it follows that there are many (exponentially) more sets of natural numbers that are not recognized by Turing machines than those that are. In particular, there are sets of natural numbers corresponding to any degree in the arithmetic hierarchy, i.e., to predicates A(k) of the form (Q 1 k 1 )(Q 2 k 2 ) (Q n k n ) R(k, k 1, k 2,, k n ), where Q 1, Q 2,..., Q n are alternating (universal or existential) quantifiers and R is a recursive/decidable 73

74 relation: for A such a predicate, let S A be the set of natural numbers {k A(k)}. Recall that if Q 1 is then A is called a Π n property, while if Q 1 is then A is called a Σ n property. In particular, Σ 0 = Π 0 and they contain precisely the recursive/decidable properties, Σ 1 contains precisely the class of recursively enumerable problems, Π 1 contains precisely the co-recursively enumerable problems, etc.; a standard Π 2 problem is TOTALITY: given k N, is it true that Turing machine with Gödel number k terminates on all inputs? Since each level in the arithmetic hierarchy contains problems strictly harder than problems on the previous layer (because Σ n Π n Σ n+1 Π n+1 ), the arithmetic hierarchy gives us a universe of safety properties whose monitoring can be arbitrarily hard. Within the decidable fragment, as expected, monitoring safety properties can also have any complexity. Indeed, pick for example any NP-complete problem and let S be the set of inputs (coded as natural numbers) for which the problem has a positive answer; then, as explained in the proof of Proposition 11, monitoring P S against input 1 k 0 is equivalent to deciding membership of k to S, which is further equivalent to answering the NP-complete problem on input k. Of course, in practice a particular (implementation of a) monitor can be more complex than the corresponding membership problem; for example, monitors corresponding to NP-complete problems are most likely exponential. Also, note that a monitor for P S needs not necessarily do its complex computation on an input 1 k 0 when it encounters the 0. It can perform intermediate computations as it reads the prefix 1 s and thus pay a lesser computational price when the 0 is encountered. What Proposition 11 says is that the total complexity to process the input 1 k 0 can be no lower than the complexity of checking whether k S. 4.4 Canonical Monitors We conclude this section with an alternative definition of a monitor, called canonical monitor, which is more compact than our previous definition and which appears to be sufficient to capture any safety property. We do not make any use of this alternative definition in this paper, but it may serve as a basis for further foundational endeavors in this area. The set of states S of a monitor (S, s 0, M : S Σ S) are typically enumerable, so they can be very well replaced with natural numbers. Moreover, the initial state s 0 can be encoded, by convention, as the first natural number, 0. A monitor then becomes nothing but a partial function N Σ N. We 74

75 therefore rightfully call these particular monitors canonical: Definition 22 A canonical Σ-monitor is a partial function N : N Σ N. Let S N = {n ( w) N (0, w) = n} be the states of N. As before, let L (N ) = {w Σ N (0, w) }, L ω (N ) = {u Σ ω N (0, w) for all w prefixes(u)}, and L,ω (N ) = L (N ) L ω (N ). Although the set of states S in a monitor (S, s 0, M : S Σ S) is allowed to have any cardinal while the states in canonical monitors are restricted to natural numbers, it turns out that canonical monitors can in fact express all monitorable properties: Proposition 12 A property P Σ (resp. P Σ ω, resp. P Σ Σ ω ) is monitorable iff there is some canonical monitor N such that P = L (N ) (resp. P = L ω (N ), resp. P = L,ω (N )). Proof: Since any canonical monitor is a monitor, it follows that any property specifiable by a canonical monitor is indeed monitrable. For the converse, let P be a property monitorable by some monitor M = (S, s 0, M : S Σ S). Since Σ ℵ 0, we can enumerate all the states of M that can be reached from s 0 with its transition function M. There are many different ways to do this (e.g., in breadth-first order, in depth-first order, etc.), but these are all ultimately irrelevant. If we let S r = {s 0, s 1, s 2,...} denote the resulting set of reachable states, then it is easy to first note that the monitor M r = (S r, s 0, M : S r Σ S r ) specifies the same property P as M, and second note that M r specifies the same property as the canonical monitor N : N Σ N defined by N (i, a) = j iff M(s i, a) = s j. Exercises Exercise 7 Define a canonical monitor for the property A file can only be accessed if it is open. That is, the file can only be accessed if it was opened at some moment in the past and it was not closed since then. Suppose Σ consists of the events/actions {o, a, c}, where o stands for file open, a for file access, and c for file close. 75

76 76

77 Chapter 5 Event/Trace Observation 77

78 78

79 Chapter 6 Monitor Synthesis: Finite State Machines (FSM) Currently, this chapter has a lot of duplication. The material has been collected from 2 papers, but not rationally merged yet. discuss also DFA, NFA, using NFA as monitor, REs, RE2NFA, derivatives 6.1 Binary Transition Trees (BTT) 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 s 1, s 2,..., s n, under the knowledge that a transition to each such state is enabled deterministically by some Boolean formula, p 1, p 2,..., p n, respectively, on atomic state predicates. Definition 23 Let S be a set whose elements are called states, and let A be another set, whose elements are called atomic predicates. Let {s 1, s 2,..., s n } S and let p 1, p 2,..., p n be propositions over atoms in A (using the usual 79

80 Boolean combinators presented in Subsection 2.1.2), with the property that exactly one of them is true at any moment, that is, p 1 p 2... p n holds and for any distinct p i, p j, it is the case that p i p j holds. Then we call the n-tuple [p 1?s 1, p 2?s 2,..., p n?s n ] 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 The intuition underlying multitransitions is straightforward: depending on which of the propositions p 1, p 2,..., p n is true at a given moment, a corresponding transition to exactly one of the states s 1, s 2,..., s n will take place. Formally, Definition 24 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 ([p 1?s 1, p 2?s 2,..., p n?s n ]) = s i, where θ(p i ) = true; notice that θ(p j ) = false for any 1 j i n. Definition 25 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 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 2.1.2, 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. Definition 26 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 BTT ::= S (A? 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 80

81 θ BTT : BTT (S, A) S inductively as follows: θ BTT (s) = s for any s S, θ BTT (a? b 1 : b 2 ) = θ BTT (b 1 ) if θ(a) = true, and θ BTT (a? b 1 : b 2 ) = θ BTT (b 2 ) if θ(a) = false. A binary transition tree b in BTT (S, A) is said to implement a multitransition t in MT (S, A) if and only if θ BTT (b) = θ MT (t) for any map θ : A {true, false}. BTTs generalize BDDs [39], 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 a 1 ; if a 1 then (eval a 2 ; if a 2 then go to state s1 else (eval a 3 ; if a 3 then go to state false else go to state s2)) else (eval a 3 ; if a 3 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 6.1. Figure 6.1: Graphical representation for the BTT a1? a2? s1 : a3? false : s2 : a3? s2 : true. Definition 27 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 81

82 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 s 1 θ 1 s2 θ 2 sn θ n sn+1 and call it a sequence of transitions whenever s 1 θ 1 s2,..., s n θ n sn+1 are transitions. 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. Definition 28 If true S then a sequence of transitions s 1 θ 1 s2 θ 2 s n θ n true is called a validating sequence (for s1 ); if false S then s 1 θ 1 s2 θ 2 sn θ n 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 MT-FSMs. 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 8.4.1, one proves that states s and s are logically equivalent (for now, equivalence can be interpreted intuitively: they have the same behavior), 82

83 and that s and s have the multi-transitions [p 1?s 1, p 2?s 2,..., p n?s n ] 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([p 1?s 1, p 2?s 2,..., p n?s n ], [p 1?s 1, p 2?s 2,..., p n?s n ]), defined as follows: Merge([p 1?s 1, p 2?s 2,..., p n?s n ], [p 1?s 1, p 2?s 2,..., p n?s n ]) contains all choices p?s, where s is a state in {s 1, s 2,..., s n } {s 1, s 2,..., s n } and p is p i when s = s i for some 1 i n and s s i for all 1 i n, or p is p i when s = s i for some 1 i n and s s i for all 1 i n, or p is p i p i when s = s i 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 13 Merge([p 1?s 1, p 2?s 2,..., p n?s n ], [p 1?s 1, p 2?s 2,..., p n?s n ]) is a well-formed multi-transition whenever both [p 1?s 1, p 2?s 2,..., p n?s n ] and [p 1?s 1, p 2?s 2,..., p n?s n ] are well-formed multi-transitions. Therefore, Merge can be seen as a function P f (MT (S, A)) MT (S, A), where P f 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 8.1, 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. Discussion about variants of finite-trace LTL needed. For such situations, we enrich our definitions of MT-FSM and BTT-FSM with support for terminal events: Definition 29 A terminating multi-transition finite state machine (abbreviated MT-FSM*) is a tuple (S, A, µ, µ ), where (S, A, µ) is an MT-FSM and 83

84 µ 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)) = true (or false), respectively. We call s θ true (or false) a terminating transition. A sequence θ s 1 θ 1 s2 2 θ n sn true is called an accepting sequence (for s 1 ) and a θ sequence s 1 θ 1 s2 2 θ n sn false is called a rejecting sequence (for s 1 ). 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 30 Given a state s S in an MT-FSM* or in a BTT-FSM* M, we let L M (s) denote the set of finite traces e θ1... e θn with the property that s θ 1 θ n true is an accepting sequence in M. If a state s 0 S is desired to be initial, then we write it at the end of the tuple, such as (S, A, µ, µ, s 0 ) θ or (S, A, β, β, s 0 ), and let L M denote L M (s 0 ). If s 1 0 s1 θn s n 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 L M for any (empty or not) trace t, and it is called an invalid prefix if and only if e θ1... e θn t L M for any trace t. The following is immediate. Proposition 14 If true S then L M (true) = E, and if false S then L M (false) =. If s θ s in M then L M (s ) = {t e θ t L M (s)}; more generally, if s θ 1 s 1 θn s n is a sequence of transitions in M then L M (s n ) = {t e θ1... e θn t L M (s)}. In particular, if s = s 0 then e θ1... e θn is a valid prefix if and only if L M (s n ) = E, and it is an invalid prefix if and only if L M (s n ) = 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 84

85 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 a 1,..., a k that hold with probabilities π 1,..., π k and have costs c 1,..., c k, respectively, and a multi-transition p 1?s 1,..., p n?s n where p 1,..., p n are Boolean formulae over a 1,..., a k. 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 7 If π 1 =... = π k and c 1 =... = c k 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 6 Consider again the traffic light controller safety property stating that after green comes yellow, which was written as [](green -> (!red U 85

86 State MT for non-terminal events MT for terminal events 1 [ yellow \/!green? 1,!yellow /\ green /\!red? 2,!yellow /\ green /\ red? false ] [ yellow \/!green? true,!yellow /\ green? false ] 2 [ yellow? 1,!yellow /\!red? 2,!yellow /\ red? false ] [ yellow? true,!yellow? false ] Figure 6.2: MT-FSM for the formula [](green ->!red U yellow). 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 6.2. 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 BTT-FSM further reduces the amount of predicates to be evaluated, by enforcing an evaluate-by-need policy. Figure 6.3 shows a BTT-FSM implementing the MT-FSM in Figure

87 State BTT for non-terminal events BTT for terminal events 1 2 Figure 6.3: A BTT-FSM for the formula [](green ->!red U yellow). 6.2 Multi-Transitions and Binary Transition Trees Büchi automata cannot be used unchanged as monitors. For the rest of the paper we explore structures suitable for monitoring as well as techniques to transform Büchi automata into such structures. Deterministic multitransitions (MT) and binary-transition trees (BTTs) were introduced in [110, 195]. In this section we extend their original definitions with nondeterminism. Definition 31 Let S and A be sets of states and atomic predicates, respectively, and let P A denote the set of propositions over atoms in A, using the usual boolean operators. If {s 1, s 2,..., s n } S and {p 1, p 2,..., p n } P A, we call the n-tuple [p 1 : s 1, p 2 : s 2,..., p n : s n ] a (nondeterministic) multi-transition (MT) over P A and S. Let MT (P A, S) denote the set of MTs over P A and S. Intuitively, if a monitor is in a state associated to an MT [p 1 : s 1, p 2 : s 2,..., p n : s n ] then p 1, p 2,..., p n can be regarded as guards allowing the monitor to nondeterministically transit to one of the states s 1, s 2,..., s n. Definition 32 Maps θ : A {true, false} are called A-events, or simply events. Given an A-event θ, we define its multi-transition extension as the map θ MT : MT (P A, S) 2 S, where θ MT ([p 1 : s 1, p 2 : s 2,..., p n : s n ]) = {s i θ = p i }. 87

88 The role of A-events is to transmit the monitor information regarding the running program. In any program state, the map θ assigns atomic propositions to true iff they hold in that state, otherwise to false. Therefore, A-events can be regarded as abstractions of the program states. Moreover, technically speaking, A-events are in a bijective map to P A. For an MT µ, the set of states θ MT (µ) is often called the set of possible continuations of µ under θ. Example 7 If µ = [a b: s 1, a b: s 2, c: s 3 ], and θ(a)=true, θ(b)=false, and θ(c)=true, then the set of possible continuations of µ under θ, θ MT (µ), is {s 1, s 3 }. Definition 33 A (nondeterministic) binary transition tree (BTT) over A and S is inductively defined as either a set in 2 S or a structure of the form a? β 1 : β 2, for some atom a and for some binary transition trees β 1 and β 2. Let BT T (A, S) denote the set of BT T s over the set of states S and atoms A. Definition 34 Given an event θ, we define its binary transition tree extension as the map θ BT T : BT T (A, S) 2 S, where: θ BT T (Q) = Q for any set of states Q S, θ BT T (a? β 1 : β 2 ) = θ BT T (β 1 ) if θ(a) = true, and θ BT T (a? β 1 : β 2 ) = θ BT T (β 2 ) if θ(a) = false. Definition 35 A BT T β implements an MT µ, written β = µ, iff for any event θ, it is the case that θ BT T (β) = θ MT (µ). Example 8 The BTT b? (a? (c? s 1 s 3 : s 1 ) : (c? s 2 : )) : (c? s 1 s 3 : s 3 ) implements the multi-transition shown in Example 7. Fig. 6.4 represents this BTT graphically. The right branch of the node labeled with b corresponds to the BTT expression (c? s 1 s 3 : s 3 ), and similarly for the left branch and every other node. Atomic predicates can be any host programming language boolean expressions. For example, one may be interested if a variable x is positive or if a vector v[ ] is sorted. Some atomic predicates typically are more expensive to evaluate than others. Since our purpose is to generate efficient monitors, we need to take the evaluation costs of atomic predicates into consideration. Moreover, some predicates can hold with higher probability than others; for example, some predicates may be simple sanity checks, such as checking whether the output of a 88

89 b a c c c s 1 s 3 s 3 s 1 s 3 s 1 s Ø 2 Figure 6.4: BTT corresponding to the MT in Example 1 sorting procedure is indeed sorted. We next assume that atomic predicates are given evaluation costs and probabilities to hold. These may be estimated apriori, either statically or dynamically. Definition 36 If ς : A R + and π : A [0, 1] are cost and probability functions for events in A, respectively, then let γ ς,π : BT T (A, S) R + defined as: γ ς,π (Q) = 0 for any Q S, and γ ς,π (a? β 1 : β 2 ) = ς(a) + π(a) γ ς,π (β 1 ) + (1 π(a)) γ ς,π (β 2 ), be the expected (evaluation) cost function on BT T s in BT T (A, S). Example 9 Given ς = {(a, 10), (b, 5), (c, 20)} and π = {(a, 0.2), (b, 0.5), (c, 0.5)}, the expected evaluation cost of the BTT defined in Example 2 is 30. With the terminology and motivations above, the following problem develops as an interesting and important problem in monitor synthesis: Problem: Optimal BT T (A, S). Input: A multi-transition µ = [p 1 : s 1, p 2 : s 2,..., p n : s n ] with associated cost ς : A R + and probability π : A [0, 1]. Output: A minimal cost BTT β with β = µ. Binary decision trees (BDTs) and diagrams (BDDs) have been studied as models and data-structures for several problems in artificial intelligence [176] and program verification [55]. Appendix B discusses BDTs and how they relate to BTTs. Moret [176] shows that a simpler version of this problem, using BDTs, is NP-hard. In spite of this result, in general the number of atoms in formulae is relatively small, so it is not impractical to exhaustively search for the optimal 89

90 BTT. We next informally describe a backtracking algorithm that we are currently using in our implementation to compute the minimal cost BTT by exhaustive search. Start with the sequence of all atoms in A. Pick one atom, say a, and make two recursive calls to this procedure, each assuming one boolean assignment to a. In each call, pass the remaining sequence of atoms to test, and simplify the set of propositions in the multi-transition according to the value of a. The product of the BTTs is taken when the recursive calls return in order to compute all BTTs starting with a. This procedure repeats until no atom is left in the sequence. We select the minimal cost BTT amongst all computed. 6.3 Binary Transition Tree Finite State Machines We next define an automata-like structure, formalizing the desired concept of an effective runtime monitor. The transitions of each state are all-together encoded by a BTT, in practice the statistically optimal one, in order for the monitor to efficiently transit as events take place in the monitored program. Violations occur when one cannot further transit to any state for any event. A special state, called neverviolate, will denote a configuration in which one can no longer detect a violation, so one can stop the monitoring session if this state is reached. Definition 37 A binary transition tree finite state machine (BTT- FSM) is a tuple A, S, btt, S 0, where A is a set of atoms, S is a set of states potentially including a special state called neverviolate, btt is a map associating a BTT in BTT(A,S) to each state in S where btt(neverviolate) = {neverviolate} when neverviolate S, and S 0 S is a subset of initial states. Definition 38 Let A, S, btt, S 0 be a BTT-FSM. For an event θ and Q, Q S, we write Q θ Q and call it a transition between sets of states, whenever Q = s Q θ BT T (btt(s)). A trace of events θ 1 θ 2...θ j generates θ a sequence of transitions Q 1 θ 0 Q1 2 θ j... Q j in the BTT-FSM, where θ i+1 Q 0 = S 0 and Q i Q i+1, for all 0 i<j. The trace is rejecting iff Q j ={}. Note that no finite extension of a trace θ 1 θ 2...θ j will be rejected if neverviolate Q j. The state neverviolate denotes a configuration in which violations can no longer be detected for any finite trace extension. This means that the set Q k will not be empty, for any k > j, when neverviolate 90

91 Q j. Therefore, the monitoring session can stop at event j if neverviolate Q j, because we are only interested in violations of requirements. Exercises Exercise 8 Give pseudo-code for the problem of generating a minimal BTT for a given MT (described in Section and right after Definition 36). Exercise 9 Prove or disprove Conjecture 7. 91

92 92

93 Chapter 7 Monitor Synthesis: Extended Regular Expressions (ERE) bad vs. good prefixes cite Vardi 7.1 Monitoring ERE Safety Needs Non-Elementary Space Extended regular expressions (EREs) add complementation ( ) to regular expressions (REs). Complementation can be handy when defining safety properties, because it allows us to say both what should never happen as well as what could happen. In particular, in the context of monitoring EREs, one can switch between the expression of bad prefixes of a safety property and that of good prefixes by just applying a complement operator. In this section we show that any monitor for safety properties expressed as EREs requires non-elementary space. More precisely, for a given n N, we build an ERE of size O(n 3 ) such that its language is prefix closed and any monitor for its language requires space ] 2 2 n 2 n nested power of 2 opeations discuss that this also implies non-elementary time complexity 93

94 7.1.1 Discussion and Relevance of the Lower-Bound Result move some of the discussion below to Chapter 6 Since regular expressions (REs) and deterministic (DFA) and nondeterministic (NFA) finite-state automata are enumerable structures while the set of safety properties is in bijection with the set of real numbers, there are many safety properties that cannot be expressed using REs or automata (or any other formalism whose objects are enumerable). Nevertheless, there are many safety properties of interest that can be expressed as REs or automata. Safety properties over finite or infinite traces can be expressed as REs in at least two different ways: 1. Use an RE to express the language of its bad prefixes; or 2. Use an RE to express the language of its (good) prefixes. In the first case, the RE captures the finite-trace behaviors that should never happen, while in the second the RE captures the ones that could possibly happen. Obviously, not all REs correspond to safety properties in one or the other of the two cases above. For example, the RE (0 1) + cannot express the bad prefixes of a safety property, because 01 is a bad prefix while 010 is not. In order for an RE to express the bad prefixes of a safety property, it should have the property that once w is in its language, then all ww for any w should also be in its language. The RE (0 1) + cannot express the good prefixes of a safety property either: 0101 is a good prefix while 010 is not. The language of an RE must be prefix closed in order to express the good prefixes of a safety property. In both cases above, monitoring the safety property can be done very efficiently (linearly in the size of the RE, both space-wise and time-wise) by first translating its corresponding RE into an NFA, for example using a technique such as Thompson s [225], and then doing one of the following: 1. In the first case, simulate the NFA-to-DFA construction on the fly as events are received. The state of the monitor is therefore a set of states of the NFA. At start, that set contains only the initial state of the NFA. For each new event, construct the next set by collecting all the NFA states that can be reached via the received event from any of the existing states in the set. If a final state is reached then report 94

95 violation of the property: bad prefix found. Since the final states in the NFA symbolize the reach of a bad prefix and since bad prefixes have no future in a safety property, the NFA associated to the original RA can be optimized (in case it is not already optimal directly from its construction) by removing any edges out of its final states. 2. In the second case, the monitor works the same way as in the first case, but checking at each time that the monitor state (also a set of NFA states) contains at least one final state of the NFA; if that is not the case, then report violation: prefix found which is not good. If one is willing to pay the exponential price and determinize the NFA of good prefixes, then one can further optimize the resulting DFA (in case it is not already optimized by construction) by collapsing all its non-final states into a dead-end state: indeed, the reachability of a non-final state signifies the reachability of a bad prefix, which has no future. It is not clear whether or how to optimize the NFA of good prefixes using the additional info that it is a safety property. almost all the discussion above can go to Chapter 6. Extended regular expressions (EREs) add complementation ( ) to REs. Meyer and Stockmeyer [218, 219] showed that EREs can express languages non-elementarily more compactly than REs. In other words, for any constant k 1, one can find EREs of large enough size n N for which there is no RE having the same language of size less than , with k nested power operations. Meyer and Stockmeyer [218, 219] showed that several other problems concerning EREs are also non-elementary, including: the equivalence of EREs, the emptiness of the language of an ERE (and implicitly the emptiness of the complement of the language of an ERE), the automata generation (NFA or DFA) from an ERE, etc. Note that it is straightforward to generate potentially non-elementarily large automata from EREs. All one needs to do is to iteratively apply NFA-to-DFA transformations for EREs under complement operators and then complement the resulting DFAs (by complementing their final states). Since each NFA-to-DFA transformation may lead to an exponential explosion on the number of states and since the ERE can have arbitrarily many nested complement operators, the resulting NFA or DFA can be non-elementarily larger than the original ERE. As already mentioned above, if we allow complementation then we can easily switch from an expression defining the bad prefixes of a safety property 95 n

96 to one defining its good prefixes, and backwards, by applying a complement operator. Therefore, from here on, when we say that an ERE expresses a safety property, without any loss of generality we assume that it defines the good prefixes of the safety property; in particular, we assume that its language is prefix closed. Clearly, if one can afford to generate an automaton from the ERE expressing a safety property, then one can and probably should use that automaton as a monitor for the safety property. However, since such an automaton can be enormous compared to the the size of the original ERE, a natural question to ask is whether one can generate monitors for safety properties expressed as EREs that need less than non-elementary space in the size of the original ERE. We next give a negative answer to this question. Let us first discuss our subsequent lower bound result from a more conceptual perspective. Notice that synchronous monitoring, that is, the monitoring process where an error is detected as soon as it appears, is harder than checking for satisfiability (or non-emptiness); indeed, if a formula in a particular formalism is not satisfiable (or it has an empty language), then a synchronous monitor should detect that before the first event is observed. We refer the interested reader to [194] for a discussion on various types of monitoring, including synchronous versus asynchronous monitoring. Synchronous monitors need to either directly (e.g., by accumulating logical constraints while verifying their consistency) or indirectly (e.g., by generating statically an automaton or a structure containing all possible future behaviors) check for satisfiability (or non-emptiness) of the remaining requirements as events are observed online. Unfortunately, this is an expensive process that may be desired to be avoided, at the expense of delaying the detection of violations. For example, the rewriting based monitoring approach for LTL in [194] delays the detection of violations of LTL formulae of the form (next ϕ) and (next ϕ) for one more event, to avoid invoking an expensive satisfiability checker for LTL but to instead invoke a propositional satisfiability checker which is less expensive in practice; this is closely related to the notion of informative prefixes in [150] that tell all the story. Our subsequent lower-bound result states that any monitor for safety properties expressed using EREs, synchronous or asynchronous, requires non-elementary space. Let us now clarify that our lower bound result is not a consequence of the lower-bound result by Meyer and Stockmeyer in [218] (see [219] for a proof of that result). A first reason is that neither the ERE constructed in [219, 218] nor its complement is prefix closed. Indeed, we here focus on a subset of 96

97 EREs, rather than arbitrary EREs, namely those whose languages are prefix closed, so they express good prefixes of safety properties. Supposing that one could modify the hard ERE in [219, 218] whose complement non-emptiness requires non-elementary space into one whose language is prefix-closed and whose size is linear in the size of the original one, the fact that synchronous monitoring of EREs is harder than checking for emptiness does not necessarily imply that monitoring that hard ERE requires non-elementary space. In fact, monitoring that particular ERE requires constant time to process each event, because it is equivalent with an automaton of one state it takes, however, non-elementary space to compute that one state automaton. What it says is therefore that the initialization step of ERE-safety synchronous monitoring requires, in the worst case, non-elementary space. Interestingly, if one could modify the results in [219, 218] to hold for prefix-closed EREs, including especially the result stating that finding an RE equivalent to an ERE is a non-elementary problem, then one could show that synchronous monitoring of safety properties expressed as EREs requires non-elementary space! Indeed, supposing that one had for any ERE a monitor that takes only elementary space in its worst-case monitoring scenario, then one could use that monitor to generate a DFA for the ERE as follows: start with the initial state of the monitor and then discover and store new states of the monitor by feeding arbitrary (but finite in number) events to each state of the monitor until no new state is discovered. This closure operation takes as much time and space as the number of states the monitor reach; since by assumption the monitor needs only elementary space to store its state in any scenario, we deduce that the obtained automaton has size elementary in the size of the ERE (and it also takes elementary time and space to generate it). In other words, we could find an elementary algorithm to associate to any (prefix closed) ERE an equivalent RE, contradicting the non-elementary lower bound in [219, 218] (again, supposing that the lower-bound results in [219, 218] could be modified for prefix-closed EREs). Unfortunately, it is not that clear how to reduce the non-elementary problems in [219, 218] to asynchronous monitoring, and thus to conclude that asynchronous monitoring also requires non-elementary space. That is because a smart asynchronous monitor may in principle collapse states (when regarded as an automaton as in the construction above) in rather unexpected ways, just because it knows that eventually an error may be reported anyway if observation continues indefinitely from that state on; in other words, states with the property that eventually violation detected 97

98 in the future may be collapsed as equivalent by an asynchronous monitor. This way, the DFA extracted from a monitor for a safety property expressed as an ERE may be significantly smaller than the DFA corresponding to the ERE (and obviously, it may have a different language). Our next result shows that asynchronous monitoring of ERE-safety also requires non-elementary space, which is a more general lower-bound result than the space non-elementarity of synchronous monitoring. Moreover, it gives an alternative proof of the non-elementary lower-bounds by Stockmeyer and Meyer [219, 218], because automata corresponding to safety-defining EREs are just special cases of monitors, so they must also take non-elementary space. Moreover, we improve the results in [219, 218] by showing that their lower-bounds also hold for a subset of EREs, namely those corresponding to safety properties. Summarizing the discussion above, we believe that the main contributions of our subsequent lower-bound result are the following: 1. We show that asynchronous monitoring already requires non-elementary space, same as synchronous monitoring. For example, an ERE monitoring algorithm was presented by Roşu and Viswanathan in [190], which rewrites or derives the ERE by each letter in the input word; the derivation process consists of some straightforward rewrite rules, some for expanding the ERE others for contracting it via simplifications. No comprehensive and expensive check for emptiness on the resulting ERE is performed, except what is done by the simplification rules (for example R and ɛ R R, etc.). A check for emptiness can and should be eventually performed (for example, a check for emptiness can be done periodically, say every 10 x events for some convenient x). This gives us an asynchronous ERE monitoring algorithm, which, unlike the simplistic NFA/DFA generation algorithm, does not pay upfront the potentially non-elementary worst-case penalty! However, our subsequent lower bound result tells that there is a worst-case scenario in which one cannot avoid the non-elementary space required to store the continuously changing ERE if one wants to correctly eventually detect violations of the original ERE. And that is the case for any synchronous or asynchronous monitoring algorithm for safety properties expressed as EREs. 2. We propose a different technique to prove non-elementary lower-bounds, fundamentally different from the one in [219]. The technique in [219] is 98

99 based on diagonalization arguments and encodings of accepting Turing machine computations as finite trace words. Our technique is inspired from an idea by Chandra, Kozen and Stockmeyer [43] introduced to show the power of alternation and then used by several authors to prove exponential lower bounds [146, 147, 190, 194]. At our knowledge, the use of such a technique to show non-elementary lower bounds is novel. The idea of the technique in [43] is to define expressions having as languages L n = {w (1) #w (2) # #w (k) $w w (1), w (2),..., w (k), w {0, 1} n, ( 1 i k) w (i) = w}. Our idea is to define, using EREs, words of the form X n $X n, where X n and X n are n-deep nested sets starting with elements in {0, 1} n (i.e., sets of sets of... of sets of elements in {0, 1} n, with n power set operations), such that X n is n-nested included in X n, where i-nested inclusion is standard inclusion when i = 1 and, if i > 1, then it is defined inductively as: X i is i-nested included in X i iff for each X i 1 X i, there is some X i 1 X i such that X i 1 is (i 1)-nested included in X i 1. One more observation is in place before we move on to the technical details. It is known that the membership problem for EREs, testing whether a word w of size m is in the language of an ERE of size n, is polynomial in m and n. For example, the classic algorithm by Hopcroft and Ullman [126] runs in space O(m 2 n) and time O(m 3 n); slightly improved algorithms have been proposed by several authors [121, 229, 230, 231, 149, 133], reducing space requirements to O(m 2 k + m n) and time to O(m 3 k + m 2 n) or worse, where k is the number of complement operators in the ERE; a recent ERE membership algorithm was proposed by the author in [197], which runs in space O(m log m 2 n ) and time O(m 2 log m 2 n ) when m > 2 n. These algorithms appear to be efficient, because they are polynomial or simply exponential in the ERE, so one may think that one may device an elementary ERE-safety monitoring algorithm as follows: store the trace of events and at each newly received event invoke one of these efficient ERE membership algorithms. While this algorithm will indeed be elementary in the size of the ERE and the size of the trace, our lower bound result says that it will, in fact, be non-elementary in the size of only the ERE! In other words, for a carefully chosen hard ERE of size n, there are finite traces of large enough size m so that checking their membership is a problem which is non-elementary in n; this will indeed happen when m is non-elementarily larger than n. 99

100 7.1.2 The Lower-Bound Result EREs define languages by inductively applying union (+), concatenation ( ), Kleene Closure ( ), intersection ( ), and complementation ( ). The language of an ERE R, say L(R), is defined inductively as follows, where s Σ: L( ) =, L(ɛ) = {ɛ}, L(s) = {s}, L(R 1 + R 2 ) = L(R 1 ) L(R 2 ), L(R 1 R 2 ) = L(R 1 ) L(R 2 ), L(R ) = (L(R)), L(R 1 R 2 ) = L(R 1 ) L(R 2 ), L( R) = L(R). If R does not contain and then it is a regular expression (RE). By applying De Morgan s law R 1 R 2 ( R 1 + R 2 ), EREs can be linearly (in both time and size) translated into equivalent EREs without intersection; therefore, intersection of EREs is just syntactic sugar. The size of an ERE is the total number of occurrences of letters and composition operators (+,,, and ) that it contains. In what follows we assume that Σ is finite. For notational simplicity, in what follows we let Σ also denote the RE s 1 + s s n where Σ = {s 1, s 2,..., s n } and let Σ denote both the language {s 1, s 2,..., s n } and the RE (s 1 + s s n ). For n N, let us define inductively the following alphabets and languages: Σ 0 = {0, 1} and Ψ 0 = {0, 1} n, and Σ i = Σ i 1 {# i } and Ψ i = {# i # i } ({# i } Ψ i 1 ) + {# i }, for all 1 i n. In the above, # i are n fresh letters. The intuition for the languages Ψ i above is to encode nested sets of depth i that contain sets of words of n bits at their deepest level. The symbols # i play the role of markers separating the elements of such sets. For example, the word # 2 # 1 # 1 # 2 # 1 01# 1 10# 1 # 2 # 1 # 1 # 2 100

101 encodes {{}, {01, 10}, {}}, that is, the set {{}, {01, 10}}; since the multiplicity and order of elements in sets are irrelevant, the same set can have (infinitely) many different encodings. Formally, let us define the following set functions associating to encodings in Ψ i corresponding nested sets: set 0 : Ψ 0 {0, 1} n is the identity function on Ψ 0, i.e., set 0 (w) = w; set i : Ψ i P i ({0, 1} n ), where P i is the power set operator applied i times, set i (# i # i ) = {}, set i (# i X i 1 # i ) = {set i 1 (X i 1 )}, and set i (# i X i 1 X i ) = {set i 1 (X i 1 )} set i (X i ), for all 1 i n, X i 1 Ψ i 1, and X i Ψ i. Note that set 0 (Ψ 0 ) = 2 n and set i (Ψ i ) = P(set i 1 (Ψ i 1 )) for all 1 i n; 2 2 n therefore, set i (Ψ i ) = 2 for all 1 i n, with i + 1 nested power operations. Let us define nested-inclusion relations F i : P i ({0, 1} n ) P i ({0, 1} n ) for 0 i n and nested-membership relations A i : P i 1 ({0, 1} n ) P i ({0, 1} n ) for 1 i n as follows: F 0 is the identity on {0, 1} n and F 1 is : P({0, 1} n ) P({0, 1} n ), A 1 is : {0, 1} n P({0, 1} n ), and for 1 < i, S i 1 A S i iff there is some S i 1 S i such that S i 1 F S i 1, and S i F S i iff S i 1 A S i for each S i 1 S i. For example, if n = 2 then {{00, 01}, {01, 10}, {11}} F 2 {{00, 01, 10}, {00, 11}} because {00, 01}, {01, 10} F 1 {00, 01, 10} and {11} F 1 {00, 11}. We can now define Σ as Σ n {$} and the infinite trace property P ω n over Σ: (ɛ (Σ n {X n $ X n X n, X n Ψ n and set n (X n) F n set n (X n )}) {$}) Σ ω n. An infinite trace in P ω n therefore contains at most two $ letters and infinitely many letters in Σ n. There are no restrictions on the appearance of the letters in Σ n when there is no $ letter or when there is precisely one $ letter. However, if the infinite trace contains precisely two $ letters, that is, if it has the form w$w $u for some w, w Σ n and some u Σ ω n, then w and w must be in Ψ n and the nested set corresponding to w must nested-include the nested set corresponding to w ; there are no restrictions on u. 101

102 Proposition 15 P ω n Safety ω. Proof: There are two cases to analyze for an infinite trace that is not in Pn ω : when it contains more than two $ letter, or when it has the form w$w $u with w, w Σ n and u Σ ω n, but it is not the case that w, w Ψ n and set n (w ) F n set n (w). In the first case, we can pick the first prefix of the infinite trace containing three $ letters in total; clearly, this finite trace prefix cannot be continued into any acceptable infinite trace. In the second case, since there are no restrictions on the letters in u, we can easily see that the prefix w$w $ is already a violation threshold: there is no u Σ ω n such that w$w $u Pn ω. The bijection in the proof of Theorem 3 associates to each infinitetrace safety property a persistent finite-trace safety property by taking its prefixes. Let P n be the persistent finite-trace safety property prefixes(pn ω ) corresponding to Pn ω. It is easy to see that P n is the property Σ n Σ n {$} Σ n {X n $ X n X n, X n Ψ n and set n (X n) F n set n (X n )} {$} Σ n. Note that monitoring P ω n is the same as monitoring P n : in both cases, besides the capability to checking whether there are more than two $ letters, which is trivial, the monitor needs to store sufficient information about the nested set corresponding to X n, so that, when the first $ is seen, to be able to check whether it nested-includes the set corresponding to the upcoming, yet unknown X n. Theorem 8 Any synchronous or asynchronous monitor for P n or Pn ω needs 2 2 n space non-elementary in n, namely Ω(2 ), with n nested power operations. Proof: Suppose that M is a monitor for P n or P ω n and suppose that, during a monitoring session, it reads the prefix X n Ψ n. Regardless of how M is defined or implemented, in particular regardless of whether it reports violations synchronously or asynchronously, when the first $ event is encountered, the state of M must contain enough information to sooner or later be able to decide whether the set set n (X n) corresponding to any upcoming (unknown at the time the $ is observed) sequence X n is nestedincluded in set n (X n ). Since set(x n) can in particular be equal to set n (X n ), and since once the second $ event is observed there is no further event that may bring new knowledge to the monitor, we deduce that M must be able 102

103 to distinguish any two different sets in set n (Ψ n ) when the first $ event is encountered, that is, M s states must be different after reading words in Ψ n whose corresponding nested sets are different. Therefore, M must be able to distinguish set n (Ψ n ) different possibilities. Since one needs Ω(log N) space to distinguish among N different situations (one label for each), we 2 2 n conclude that M needs space Ω(log( set n (Ψ n ) )), that is, Ω(2 ) with n nested power operations. Hence, any monitor for P n needs non-elementarily large space in n. We next show how to construct an ERE polynomial in size with n whose language is precisely P n. Theorem 9 There is an ERE of size O(n 3 ) whose language is P n. Proof: Note that P n is a union of three languages, the first two being trivial to express as languages of corresponding REs. As expected, the difficult part is to associate an ERE to the language {X n $ X n X n, X n Ψ n and set n (X n) F n set n (X n )}. Note that so far we did not need complementation. The property above can, however, be expressed as an ERE of size O(n 3 ) using O(n) nested complement operators. The idea is to define iteratively a sequence of EREs K i for 0 1 n whose languages contain words X i w$w X i with set i (X i ) F i set(x i ), which are contiguous fragments of desired words X n $X n. Then K n would be the language that we are looking for. To define K i, we observe that the nested-inclusion S i F i S i is equivalent to: there is no S i 1 S i such that it is not the case that we can find some S i 1 S i such that S i 1 F i 1 S i 1. This crucial observation will allow us to define K i in terms of K i 1. We next develop the technical details. Let us first define regular patterns corresponding to each of the languages Ψ i for 0 i n; to avoid introducing new names, we ambiguously let the corresponding regular expressions have the same names as their languages: Let Ψ 0 = (0 + 1) n, where for an RE, r n is r r r (n times); and Let Ψ i = # i # i + (# i Ψ i 1 ) + # i for all 1 i n. Iteratively eliminating the Ψ i 1 regular expressions from the right-hand-sides, we eventually obtain n + 1 regular patters, each of size O(n) (the size of Ψ 0 as a regular expression is O(n) and each Ψ i adds a constant size to that of Ψ i 1 ). 103

104 Next we define REs for the languages prefixes(ψ i ) and suffixes(ψ i ) of prefixes and respectively suffixes of words in Ψ i, for all 0 i n. We only discuss the prefix closure languages; the suffix closures are dual. The prefix closures can be defined relatively easily inductively as follows: prefixes(ψ 0 ) = n k=0 {0, 1}k, and prefixes(ψ i ) = {ɛ, # i # i } {# i } prefixes(ψ i 1 ) ({# i } Ψ i 1 ) + ({# i } Ψ i 1 ) + {# i } prefixes(ψ i 1 ) = {# i # i } ({# i } Ψ i 1 ) ({ɛ} {# i } prefixes(ψ i 1 )). These languages can be expressed with the following REs; as before, we ambiguously use the same names for the corresponding REs: Let prefixes(ψ 0 ) = ɛ + (0 + 1) + (0 + 1) (0 + 1) n = (ɛ ) n ; and Let prefixes(ψ i ) = # i # i + (# i Ψ i 1 ) (ɛ + # i prefixes(ψ i 1 )). Iteratively eliminating the REs prefixes(ψ i 1 ) from the right-hand-sides, we eventually obtain n+1 REs, each of size O(i 2 +n) (the size of prefixes(ψ 0 ) as an RE is O(n) and each prefixes(ψ i ) adds size O(i) to that of prefixes(ψ i 1 )). Dually, Let suffixes(ψ 0 ) = ɛ + (0 + 1) + (0 + 1) (0 + 1) n = (ɛ ) n ; and Let suffixes(ψ i ) = # i # i + (ɛ + suffixes(ψ i 1 ) # i ) (Ψ i 1 # i ). We next define REs L i and R i for 0 i n whose languages contain the contiguous fragments of words in Ψ n that are allowed to appear to the left and to the right of $, respectively, so that words in L(L i ) start with # i and words in L(R i ) end with # i. Let us also assume by convention that L n+1 = R n+1 = ɛ (the RE whose language contains only the empty word). It is easy to see that L i and R i can be defined as follows: Let L i = # i Σ n suffixes(ψ n ), and Let R i = Σ n # i prefixes(ψ n ). Note that words in L i and R i may not necessarily start or end with a word in Ψ i : indeed, the # i that may start or end L i or R i could very well be followed or preceded, respectively, by a # i+1 or, if i = n, by $. 104

105 Let us also define the EREs L i and R i whose languages are included in those of L i and R i, respectively, and whose words start or end with words in Ψ i : Let L i = Ψ i Σ n suffixes(ψ n ), and Let R i = Σ n Ψ i prefixes(ψ n ). It is not difficult to see that L i = Ψ i L i+1 and R i = R i+1 Ψ i. Note that the sizes of L i, R i, L i and R i are O(n 2 ). Let us now define the EREs K i for 0 i n as follows: K 0 = L 0 $ R 0 n 1 k=0 (Σk 0 0 Σ 0 Σ n k Σ k 0 1 Σ 1 Σ n k 1 0 ), and K i = L i $ R i (( ((# i Ψ i 1 ) # i K i 1 ) L i $ R i 1 ) (# i Ψ i 1 ) # i ). We next show by induction on i that L(K i ) is the language {X i wx i X i, X i Ψ i, w L(L i+1 $ R i+1 ), set i (X i) F i set i (X i )}. It is easy to see that L(K 0 ) = {X 0 wx 0 X 0 Ψ 0, w L(L 1 $ R 1 ), because the large conjunct in K 0 states that the words formed with the first n letters and with the last ones, respectively, are equal and in Ψ 0, and because L 0 $ R 0 = Ψ 0 L 1 $ R 1 Ψ 0 and F 0 is the identity on {0, 1} n. For the inductive step, let us now assume that for some arbitrary 1 i < n, L(K i 1 ) is the language {X i 1 wx i 1 X i 1, X i 1 Ψ i 1, w L(L i $ R i ), set i 1 (X i 1) F i 1 set i 1 (X i 1 )}. Then we can easily show that L((# i Ψ i 1 ) # i K i 1 ) is the language {X i wx i 1 X i Ψ i, X i 1 Ψ i 1, w L(L i+1 $ R i ), set i 1 (X i 1) A i 1 set i (X i )}. Then we can show that L( ((# i Ψ i 1 ) # i K i 1 ) L i $ R i 1 ) is {X i wx i 1 X i Ψ i, X i 1 Ψ i 1, w L(L i+1 $ R i ), set i 1 (X i 1) A i 1 set i (X i )}. Now we can show that L(( ((# i Ψ i 1 ) # i K i 1 ) L i $ R i 1 ) (# i Ψ i 1 ) # i ) is the language {X i wx i X i, X i Ψ i, w L(L i+1 $ R i+1 ), set i (X i) F i 1 set i (X i )}. 105

106 Finally, we are now able to show that L(K i ), that is, L(L i $ R i (( ((# i Ψ i 1 ) # i K i 1 ) L i $ R i 1 ) (# i Ψ i 1 ) # i )) is the language {X i wx i X i, X i Ψ i, w L(L i+1 $ R i+1 ), set i (X i) F i set i (X i )}. Since L n+1 = R n+1 = ɛ, it follows that L(K n ) = {X n $X n X n, X n Ψ n, set i (X i) F i set i (X i )}. The size of K n is O(n 3 ). We can now show that the language of the ERE of size O(n 3 ) (ɛ + (Σ n + K n ) $) Σ n is indeed P n. We can now formulate our space lower-bound result for monitoring ERE-safety as a corollary of the two results above. Corollary 1 For any n N, there is some safety property whose good prefixes are precisely the words in the language of an ERE of size O(n) and 2 2 whose monitoring (synchronous or asynchronous) requires space Ω(2 with 3 n nested power operations. 3 n ) 7.2 Generating Optimal Monitors for ERE incorporate RTA 03 paper with Mahesh 106

107 Abstract: Software engineers and programmers can easily understand regular patterns, as shown by the immense interest in and the success of scripting languages like Perl, based essentially on regular expression pattern matching. We believe that regular expressions provide an elegant and powerful specification language also for monitoring requirements, because an execution trace of a program is in fact a string of states. Extended regular expressions (EREs) add complementation to regular expressions, which brings additional benefits by allowing one to specify patterns that must not occur during an execution. Complementation gives one the power to express patterns on strings more compactly. In this paper we present a technique to generate optimal monitors from EREs. Our monitors are deterministic finite automata (DFA) and our novel contribution is to generate them using a modern coalgebraic technique called coinduction. Based on experiments with our implementation, which can be publicly tested and used over the web, we believe that our technique is more efficient than the simplistic method based on complementation of automata which can quickly lead to a highly-exponential state explosion Introduction Regular expressions can express patterns in strings in a compact way. They proved very useful in practice; many programming/scripting languages like Perl, Python, Tcl/Tk support regular expressions as core features. Because of their power to express a rich class of patterns, regular expressions, are used not only in computer science but also in various other fields, such as molecular biology [142]. All these applications boast of very efficient implementation of regular expression pattern matching and/or membership algorithms. Moreover, it has been found that compactness of regular expressions can be increased non-elementarily by adding complementation ( R) to the usual union (R 1 + R 2 ), concatenation (R 1 R 2 ), and repetition (R ) operators of regular expressions. These are known as extended regular expressions (EREs) and they proved very intuitive and succinct in expressing regular patterns. Recent trends have shown that the software analysis community is inclining towards scalable techniques for software verification. Works in [108] merged temporal logics with testing, hereby getting the benefits of 107

108 both worlds. The Temporal Rover tool (TR) and its follower DB Rover [73] are already commercial. In these tools the Java code is instrumented automatically so that it can check the satisfaction of temporal logic properties at runtime. The MaC tool [140, 158] has been developed to monitor safety properties in interval past time temporal logics. In [180, 185], various algorithms to generate testing automata from temporal logic formulae, are described. Java PathExplorer [105] is a runtime verification environment currently under development at NASA Ames. The Java MultiPathExplorer tool [207] proposes a technique to monitor all equivalent traces that can be extracted from a given execution, thus increasing the coverage of monitoring. [92, 107] present efficient algorithms for monitoring future time temporal logic formulae, while [110] gives a technique to synthesize efficient monitors from past time temporal formulae. [190] uses rewriting to perform runtime monitoring of EREs. An interesting aspect of EREs is that they can express safety properties compactly, like those encountered in testing and monitoring. By generating automata from logical formulae, several of the works above show that the safety properties expressed by different variants of temporal logics are subclasses of regular languages. The converse is not true, because there are regular patterns which cannot be expressed using temporal logics, most notoriously those related to counting; e.g., the regular expression (0 (0+1)) saying that every other letter is 0 does not admit an equivalent temporal logic formula. Additionally, EREs tend to be often very natural and intuitive in expressing requirements. For example, let us try to capture the safety property it should not be the case that in any trace of a traffic light we see green and then immediately red at any point. The natural and intuitive way to express it in ERE is (( ) green red ( )), where is the empty ERE (no words), so means anything. Previous approaches to ERE membership testing [121, 178, 229, 149, 134] have focussed on developing techniques that are polynomial in both the size of the word and the size of the formulae. The best known result in these approaches is described in [149] where they can check if a word satisfies an ERE in time O(m n 2 ) and space O(m m + k n 2 ), where m is the size of the ERE, n is the length of the word, and k is the number of negation/intersection operators. These algorithms, unfortunately, cannot be used for the purpose of monitoring. This is because they are not incremental. They assume the entire word is available before their execution. Additionally, their running time and space requirements are quadratic in the size of the trace. This 108

109 is unacceptable when one has a long trace of events and wants to monitor a small ERE, as it is typically the case. This problem is removed in [190] where traces are checked against EREs through incremental rewriting. At present, we do not know if the technique in [190] is optimal or not. A simple, straightforward, and practical approach is to generate optimal deterministic finite automata (DFA) from EREs [125]. This process involves the conversion of each negative sub-component of the ERE to a non-deterministic finite automaton (NFA), determinization of the NFA into a DFA, complementation of the DFA, and then its minimization. The algorithm runs in a bottom-up fashion starting from the innermost negative ERE sub components. This method, although generates the minimal automata, is too complex and cumbersome in practice. Its space requirements can be non-elementarily larger than the initial regular ERE, because negation involves an NFA-to-DFA translation, which implies an exponential blow-up; since negations can be nested, the size of such NFAs or DFAs could be highly exponential. Our approach is to generate the minimal DFA from an ERE using coinductive techniques. In this paper, the DFA thus generated is called the optimal monitor for the given ERE. Currently, we are not aware of any other algorithm that does this conversion in a straightforward way. The complexity of our algorithm seems to be hard to evaluate, because it depends on the size of the minimal DFA associated to an ERE and we are not aware of any lower bound results in this direction. However, experiments are very encouraging. Our implementation, which is available for evaluation on the internet via a CGI server reachable from rarely took longer than one second to generate a DFA, and it took only 18 minutes to generate the minimal 107 state DFA for the ERE in Example 16 which was used to show the exponential space lower bound of ERE monitoring in [190]. In a nutshell, in our approach we use the concept of derivatives of an ERE, as described in Subsection For a given ERE one generates all possible derivatives of the ERE for all possible sequences of events. The size of this set of derivatives depends upon the size of the initial ERE. However, several of these derivative EREs can be equivalent to each other. One can check the equivalence of EREs using coinductive technique as described in Section 7.2.3, that generates a set of equivalent EREs, called circularities. In Section 7.2.5, we show how circularities can be used to construct an efficient algorithm that generates optimal DFAs from EREs. In Section 16.7, we describe an implementation of this algorithm and give performance analysis 109

110 results. We also made available on the internet a CGI interface to this algorithm Extended Regular Expressions and Derivatives In this section we recall extended regular expressions and their derivatives. Extended Regular Expressions Extended regular expressions (ERE) define languages by inductively applying union (+), concatenation ( ), Kleene Closure ( ), intersection ( ), and complementation ( ). More precisely, for an alphabet E, whose elements are called events in this paper, an ERE over E is defined as follows, where a E: R ::= ɛ a R + R R R R R R R. The language defined by an expression R, denoted by L(R), is defined inductively as L( ) =, L(ɛ) = {ɛ}, L(A) = {A}, L(R 1 + R 2 ) = L(R 1 ) L(R 2 ), L(R 1 R 2 ) = {w 1 w 2 w 1 L(R 1 ) and w 2 L(R 2 )}, L(R ) = (L(R)), L(R 1 R 2 ) = L(R 1 ) L(R 2 ), L( R) = Σ \ L(R). Given an ERE, as defined above using union, concatenation, Kleene Closure, intersection and complementation, one can translate it into an equivalent expression that does not have any intersection operation, by applying De Morgan s Law: R 1 R 2 = ( R 1 + R 2 ). The translation only results in a linear blowup in size. Therefore, in the rest of the paper we do not consider expressions containing intersection. More precisely, we only consider EREs of the form Derivatives R ::= R + R R R R R a ɛ. 110

111 In this subsection we recall the notion of derivative, or residual (see [12, 11], where several interesting properties of derivatives are also presented). It is based on the idea of event consumption, in the sense that an extended regular expression R and an event a produce another extended regular expression, denoted R{a}, with the property that for any trace w, aw R if and only if w R{a}. In the rest of the paper assume defined the typical operators on EREs and consider that the operator + is associative and commutative and that the operator is associative. In other words, reasoning is performed modulo the equations: (R 1 + R 2 ) + R 3 = R 1 + (R 2 + R 3 ), R 1 + R 2 = R 2 + R 1, (R 1 R 2 ) R 3 = R 1 (R 2 R 3 ). We next consider an operation { } which takes an ERE and an event, and give several equations which define its operational semantics recursively, on the structure of regular expressions: (R 1 + R 2 ){a} = R 1 {a} + R 2 {a} (1) (R 1 R 2 ){a} = (R 1 {a}) R 2 + if (ɛ R 1 ) then R 2 {a} else fi (2) (R ){a} = (R{a}) R (3) ( R){a} = (R{a}) (4) b{a} = if (b == a) then ɛ else fi (5) ɛ{a} = (6) {a} = (7) The right-hand sides of these equations use operations which we describe next. if ( ) then else fi takes a boolean term and two EREs as arguments and has the expected meaning defined by two equations: if (true) then R 1 else R 2 fi = R 1 (8) if (false) then R 1 else R 2 fi = R 2 (9) We assume a set of equations that properly define boolean expressions and reasoning. Boolean expressions include the constants true and false, as well as the usual connectors,, and not. Testing for empty trace membership (which is used by (2)) can be defined via the following equations: 111

112 ɛ (R 1 + R 2 ) = (ɛ R 1 ) (ɛ R 2 ) ɛ (R 1 R 2 ) = (ɛ R 1 ) (ɛ R 2 ) ɛ (R ) = true ɛ ( R) = not(ɛ R) ɛ b = false ɛ ɛ = true ɛ = false (10) (11) (12) (13) (14) (15) (16) The 16 equations above are natural and intuitive. [190] shows that these equations, when regarded as rewriting rules are terminating and ground Church-Rosser (modulo associativity and commutativity of + and modulo associativity of ), so they can be used as a functional procedure to calculate derivatives. Due to the fact that the 16 equations defining the derivatives can generate useless terms, in order to keep EREs compact we also propose defining several simplifying equations, including at least the following: + R = R, R =, ɛ R = R, R + R = R. The following result (see, e.g., [190] for a proof) gives a simple procedure, based on derivatives, to test whether a word belongs to the language of an ERE: Theorem 10 For any ERE R and any events a, a 1, a 2,..., a n in A, the following hold: 1. a 1 a 2...a n L(R{a}) if and only if aa 1 a 2...a n L(R); and 2. a 1 a 2...a n L(R) if and only if ɛ R{a 1 }{a 2 }...{a n } Hidden Logic and Coinduction We use circular coinduction, defined rigorously in the context of hidden logics and implemented in the BOBJ system [186, 94, 95], to test whether two EREs are equivalent, that is, if they have the same language. Since the goal of this paper is to translate an ERE into a minimal DFA, standard techniques for checking equivalence, such as translating the two expressions into DFAs and then comparing those, do not make sense in this framework. A particularly appealing aspect of circular coinduction in the framework of 112

113 EREs is that it does not only show that two EREs are equivalent, but also generates a larger set of equivalent EREs which will all be used in order to generate the target DFA. Hidden logic is a natural extension of algebraic specification which benefits of a series of generalizations in order to capture various natural notions of behavioral equivalence found in the literature. It distinguishes visible sorts for data from hidden sorts for states, with states behaviorally equivalent if and only if they are indistinguishable under a formally given set of experiments. To keep the presentation simple and self contained, in this section we define an oversimplified version of hidden logic together with its associated circular coinduction proof rule, still general enough to support defining and proving EREs behaviorally equivalent. Algebraic Preliminaries The reader is assumed familiar with basic equational logic and algebra in this section. We recall a few notions in order to just make our notational conventions precise. An S-sorted signature Σ is a set of sorts/types S together with operational symbols on those, and a Σ-algebra A is a collection of sets {A s s S} and a collection of functions appropriately defined on those sets, one for each operational symbol. Given an S-sorted signature Σ and an S-indexed set of variables Z, let T Σ (Z) denote the Σ-term algebra over variables in Z. If V S then Σ V is a V -sorted signature consisting of all those operations in Σ with sorts entirely in V. We may let σ(x) denote the term σ(x 1,..., x n ) when the number of arguments of σ and their order and sorts are not important. If only one argument is important, then to simplify writing we place it at the beginning; for example, σ(t, X) is a term having σ as root with only variables as arguments except one, and we do not care which one, which is t. If t is a Σ-term of sort s over a special variable of sort s and A is a Σ-algebra, then A t : A s A s is the usual interpretation of t in A Behavioral Equivalence, Satisfaction and Specification Given disjoint sets V, H called visible and hidden sorts, a hidden (V, H)- signature, say Σ, is a many sorted (V H)-signature. A hidden subsignature of Σ is a hidden (V, H)-signature Γ with Γ Σ and Γ V = Σ V. The data signature is Σ V. An operation of visible result not in Σ V is called an attribute, and a hidden sorted operation is called a method. Unless otherwise stated, the rest of this section assumes fixed a hidden signature Σ with a fixed subsignature Γ. Informally, Σ-algebras are universes 113

114 of possible states of a system, i.e., black boxes, where one is only concerned with behavior under experiments with operations in Γ, where an experiment is an observation of a system attribute after perturbation; this is formalized below. A Γ-context for sort s V H is a term in T Γ ({ : s}) with one occurrence of. A Γ-context of visible result sort is called a Γ-experiment. If c is a context for sort h and t T Σ,h then c[t] denotes the term obtained from c by substituting t for ; we may also write c[ ] for the context itself. Given a hidden Σ-algebra A with a hidden subsignature Γ, for sorts s (V H), we define Γ-behavioral equivalence of a, a A s by a Γ Σ a iff A c (a) = A c (a ) for all Γ-experiments c; we may write instead of Γ Σ when Σ and Γ can be inferred from context. We require that all operations in Σ are compatible with Γ Σ. Note that behavioral equivalence is the identity on visible sorts, since the trivial contexts : v are experiments for all v V. A major result in hidden logics, underlying the foundations of coinduction, is that Γ-behavioral equivalence is the largest equivalence which is identity on visible sorts and which is compatible with the operations in Γ. Behavioral satisfaction of equations can now be naturally defined in terms of behavioral equivalence. A hidden Σ-algebra A Γ-behaviorally satisfies a Σ-equation ( X) t = t, say e, iff for each θ : X A, θ(t) Γ Σ θ(t ); in this case we write A Γ Σ e. If E is a set of Σ-equations we then write A Γ Σ E when A Γ-behaviorally satisfies each Σ-equation in E. We may omit Σ and/or Γ from Γ Σ when they are clear. A behavioral Σ-specification is a triple (Σ, Γ, E) where Σ is a hidden signature, Γ is a hidden subsignature of Σ, and E is a set of Σ-sentences equations. Non-data Γ-operations (i.e., in Γ Σ V ) are called behavioral. A Σ-algebra A behaviorally satisfies a behavioral specification B = (Σ, Γ, E) iff A Γ Σ E, in which case we write A B; also B e iff A B implies A Γ Σ e. EREs can be very naturally defined as a behavioral specification. The enormous benefit of doing so is that the behavioral inference, including most importantly coinduction, provide a decision procedure for equivalence of EREs. [94] shows how standard regular expressions (without negation) can be defined as a behavioral specification, a BOBJ implementation, and also how BOBJ with its circular coinductive rewriting algorithm can prove automatically several equivalences of regular expressions. Related interesting work can also be found in [201]. In this paper we extend that to general EREs, generate minimal observer monitors, and also give several other 114

115 examples. Example 10 A behavioral specification of EREs defines a set of two visible sorts V = {Bool, Event}, one hidden sort H = {Ere}, one behavioral attribute ɛ : Ere Bool and one behavioral method, the derivative, { } : Ere Event Ere, together with all the other operations in Subsection defining EREs, including the events in E which are defined as visible constants of sort Event, and all the equations in Subsection We call it the ERE behavioral specification and let B ERE denote it. Since the only behavioral operators are the test for ɛ membership and the derivative, it follows that the experiments have exactly the form ɛ {a 1 }{a 2 }...{a n }, for any events a 1, a 2,..., a n. In other words, an experiment consists of a series of derivations followed by an ɛ membership test, and therefore two regular expressions are behavioral equivalent if and only if they cannot be distinguished by such experiments. Notice that the above reasoning applies within any algebra satisfying the presented behavioral specification. The one we are interested in is, of course, the free one, whose set carriers contain exactly the extended regular expressions as presented in Subsection 7.2.2, and the operations have the obvious interpretations. We informally call it the ERE algebra. Letting denote the behavioral equivalence relation generated on the ERE algebra, then Theorem 10 immediately yields the following important result. Theorem 11 If R 1 and R 2 are two EREs then R 1 R 2 if and only if L(R 1 ) = L(R 2 ). This theorem allows us to prove equivalence of EREs by making use of behavioral inference in the ERE behavioral specification, from now on simply referred to by B, including (especially) circular coinduction. The next section shows how circular coinduction works and how it can be used to show EREs equivalent. Circular Coinduction as an Inference Rule In the simplified version of hidden logics defined above, the usual equational inference rules, i.e., reflexivity, symmetry, transitivity, substitution and congruence [186] are all sound for behavioral satisfaction. However, equational reasoning can derive only a very limited amount of interesting behavioral equalities. For that reason, circular coinduction has been developed 115

116 as a very powerful automated technique to show behavioral equivalence. We let denote the relation being defined by the equational rules plus circular coinduction, for deduction from a specification to an equation. Before we present circular coinduction formally, we give the reader some intuitions by duality to structural induction. The reader who is only interested in using the presented procedure or who is not familiar with structural induction, can skip this paragraph. Inductive proofs show equality of terms t(x), t (x) over a given variable x (seen as a constant) by showing t(σ(x)) equals t (σ(x)) for all σ in a basis, while circular coinduction shows terms t, t behaviorally equivalent by showing equivalence of δ(t) and δ(t ) for all behavioral operations δ. Coinduction applies behavioral operations at the top, while structural induction applies generator/constructor operations at the bottom. Both induction and circular coinduction assume some frozen instances of t, t equal when checking the inductive/coinductive step: for induction, the terms are frozen at the bottom by replacing the induction variable by a constant, so that no other terms can be placed beneath the induction variable, while for coinduction, the terms are frozen at the top, so that they cannot be used as subterms of other terms (with some important but subtle exceptions which are not needed here; see [95]). Freezing terms at the top is elegantly handled by a simple trick. Suppose every specification has a special visible sort b, and for each (hidden or visible) sort s in the specification, a special operation [ ] : s b. No equations are assumed for these operations and no user defined sentence can refer to them; they are there for technical reasons. Thus, with just the equational inference rules, for any behavioral specification B and any equation ( X) t = t, it is necessarily the case that B ( X) t = t iff B ( X) [t] = [t ]. The rule below preserves this property. Let the sort of t, t be hidden; then Circular Coinduction: B {( X) [t] = [t ]} ( X, W ) [δ(t, W )] = [δ(t, W )], for all appropriate δ Γ B ( X) t = t We call the equation ( X) [t] = [t ] added to B a circularity; it could just as well have been called a coinduction hypothesis or a co-hypothesis, but we find the first name more intuitive because from a coalgebraic point of view, coinduction is all about finding circularities. 116

117 Theorem 12 The usual equational inference rules together with Circular Coinduction are sound. That means that if B ( X) t = t and sort(t, t ) b, or if B ( X) [t] = [t ], then B ( X) t = t. Example 11 Suppose that we want to show that the EREs (a + b) and (a b ) admit the same language. By Theorem 11, we can instead show that B ERE ( ) (a + b) = (a b ). Notice that a and b are treated as constant events here; one can also prove the result when a and b are variables, but one would need to first make use of the theorem of hidden constants [186]. To simplify writing, we omit the empty quantifier of equations. By the Circular Coinduction rule, one generates the following three proof obligations B ERE {[(a + b) ] = [(a b ) ]} [ɛ (a + b) ] = [ɛ (a b ) ], B ERE {[(a + b) ] = [(a b ) ]} [(a + b) {a}] = [(a b ) {a}], B ERE {[(a + b) ] = [(a b ) ]} [(a + b) {b}] = [(a b ) {b}]. The first proof task follows immediately by using the equations in B as rewriting rules, while the other two tasks reduce to B ERE {[(a + b) ] = [(a b ) ]} [(a + b) ] = [a (a b ) ], B ERE {[(a + b) ] = [(a b ) ]} [(a + b) ] = [b (a b ) ]. By applying Circular Coinduction twice, after simplifying the two obvious proof tasks stating the ɛ membership, one gets the following four proof obligations B ERE {[(a + b) ] = [(a b ) ], [(a + b) ] = [a (a b ) ]} [(a + b) ]{a} = [a (a b ) {a}], B ERE {[(a + b) ] = [(a b ) ], [(a + b) ] = [a (a b ) ]} [(a + b) ]{b} = [a (a b ) {b}], B ERE {[(a + b) ] = [(a b ) ], [(a + b) ] = [b (a b ) ]} [(a + b) ]{a} = [b (a b ) {a}], B ERE {[(a + b) ] = [(a b ) ], [(a + b) ] = [b (a b ) ]} [(a + b) ]{b} = [b (a b ) {b}], which, after simplification translate into B ERE {[(a + b) ] = [(a b ) ], [(a + b) ] = [a (a b ) ]} [(a + b) ] = [a (a b ) ], B ERE {[(a + b) ] = [(a b ) ], [(a + b) ] = [a (a b ) ]} [(a + b) ] = [b (a b ) ], B ERE {[(a + b) ] = [(a b ) ], [(a + b) ] = [b (a b ) ]} [(a + b) ] = [a (a b ) ], B ERE {[(a + b) ] = [(a b ) ], [(a + b) ] = [b (a b ) ]} [(a + b) ] = [b (a b ) ], Again by applying circular coinduction we get 117

118 B ERE {[(a + b) ] = [(a b ) ], [(a + b) ] = [a (a b ) ], B ERE {[(a + b) ] = [(a b ) ], [(a + b) ] = [a (a b ) ], B ERE {[(a + b) ] = [(a b ) ], [(a + b) ] = [b (a b ) ], B ERE {[(a + b) ] = [(a b ) ], [(a + b) ] = [b (a b ) ], [(a + b) ] = [b (a b ) ]} [(a + b) ] = [a (a b ) ], [(a + b) ] = [b (a b ) ]} [(a + b) ] = [b (a b ) ], [(a + b) ] = [a (a b ) ]} [(a + b) ] = [a (a b ) ], [(a + b) ] = [a (a b ) ]} [(a + b) ] = [b (a b ) ], which now follow all immediately. Notice that BOBJ uses the newly added (to B ERE ) equations as rewriting rules when it applies its circular coinductive rewriting algorithm, so the proof above is done slightly differently, but entirely automatically. Example 12 Suppose now that one wants to show that (a b) ɛ + a + (a + b) b(a + b)(a + b). One can also do it entirely automatically by circular coinduction as above, generating the following list of circularities: [ (a b)] = [ɛ + a + (a + b) b(a + b)(a + b) ], [ (ɛ)] = [(a + b) b(a + b)(a + b) + (a + b)(a + b) ], [ ( )] = [(a + b) b(a + b)(a + b) + (a + b) ], [ ( )] = [(a + b) b(a + b)(a + b) + (a + b)(a + b) + (a + b) ]. Example 13 One can also show by circular coinduction that concrete EREs satisfy systems of guarded equations. This is an interesting but unrelated subject, so we do not discuss it in depth here. However, we show how easily one can prove by coinduction that a b is the solution of the equation R = a R + b. This equation can be given by adding a new ERE constant r to B ERE, together with the equations ɛ r = false, r{a} = r, and r{b} = ɛ. Circular Coinduction applied on the goal r = a b generates the proof tasks: which all follow immediately. B ERE {[r] = [a b]} [ɛ r] = [ɛ a b], B ERE {[r] = [a b]} [r{a}] = [a b{a}], B ERE {[r] = [a b]} [r{b}] = [a b{b}], The following says that circular coinduction provides a decision procedure for equivalence of EREs. 118

119 Theorem 13 If R 1 and R 2 are two EREs, then L(R 1 ) = L(R 2 ) if and only if B ERE R 1 = R 2. Moreover, since the rules in B ERE are ground Church-Rosser and terminating, circular coinductive rewriting[94, 95], which iteratively rewrites proof tasks to their normal forms followed by a one step coinduction if needed, gives a decision procedure for ERE equivalence Generating Minimal DFA Monitors by Coinduction In this section we show how one can use the set of circularities generated by applying the circular coinduction rules in order to generate a minimal DFA from any ERE. This DFA can then be used as an optimal monitor for that ERE. The main idea here is to associate states in DFA to EREs obtained by deriving the initial ERE; when a new ERE is generated, it is tested for equivalence with all the other already generated EREs by using the coinductive procedure presented in the previous section. A crucial observation which significantly reduces the complexity of our procedure is that, once an equivalence is proved by circular coinductive rewriting, the entire set of circularities accumulated represent equivalent EREs. These can be used to later quickly infer the other equivalences, without having to generate the same circularities over and over again. Since BOBJ does not (yet) provide any mechanism to return the set of circularities accumulated after proving a given behavioral equivalence, we were unable to use BOBJ to implement our optimal monitor generator. Instead, we have implemented our own version of coinductive rewriting engine for EREs, which is described below. We are given an initial ERE R 0 over alphabet A and from that we want to generate the equivalent minimal DFA D = (S, A, δ, s 0, F ), where S is the set of states, δ : S A S is the transition function, s 0 is the initial state, and F S is the set of final states. The coinductive rewriting engine explicitly accumulates the proven circularities in a set. The set is initialized to an empty set at the beginning of the algorithm. It is updated with the accumulated circularities whenever we prove equivalence of two regular expressions in the algorithm. The algorithm maintains the set of states S in the form of non-equivalent EREs. At the beginning of the algorithm S is initialized with a single element, which is the given ERE R 0. Next, we generate all the derivatives of the initial ERE one by one in a depth first manner. A derivative R x = R{x} is added to the set S, if the set does not contain any ERE equivalent to the derivative R x. We then extend the transition function by setting δ(r, x) = R x. If an ERE R equivalent to the 119

120 derivative already exists in the set S, we extend the transition function by setting δ(r, x) = R. To check if an ERE equivalent to the derivative R x already exists in the set S, we sequentially go through all the elements of the set S and try to prove its equivalence with R x. In testing the equivalence we first add the set of circularities to the initial B. Then we invoke the coinductive procedure. If for some ERE R S, we are able to prove that R R x i.e B Eq all Eq new R = R x, then we add the new equivalences Eq new, created by the coinductive procedure, to the set of circularities. Thus we reuse the already proven equivalences in future proofs. The derivatives of the initial ERE R 0 with respect to all events in the alphabet A are generated in a depth first fashion. The pseudo code for the whole algorithm is given in Figure 1. dfs(r) begin foreach x A do R x R{x}; if R S such that B Eq all Eq new R = R x then δ(r, x) = R ; Eq all Eq all Eq new endfor end else S S {R x }; δ(r, x) = R x ; dfs(r x ); fi Figure 7.1: ERE to minimal DFA generation algorithm In the procedure dfs the set of final states F consists of the EREs from S which contain ɛ. This can be tested efficiently using the equations (10-16) in Subsection The DFA generated by the procedure dfs may now contain some states which are non-final and from which the DFA can never reach a final state. We remove these redundant states by doing a breadth first search in backward direction from the final states. This can be done in time linear in the size of the DFA. Theorem 14 If D is the DFA generated for a given ERE R by the above algorithm then 1. L(D) = L(R), 2. D is the minimal DFA accepting L(R). 120

121 Proof: Suppose a 1 a 2... a n L(R). Then ɛ R{a 1 }{a 2 }... {a n }. If R i = R{a 1 }{a 2 }... {a i } then R i+1 = R i {a i+1 }. To prove that a 1 a 2... a n L(D), we use induction to show that for each 1 i n, R i δ(r, a 1 a 2... a i ). For the base case if R 1 R{a 1 } then dfs extends the transition function by setting δ(r, a 1 ) = R. Therefore, R 1 R = δ(r, a 1 ). If R 1 R then dfs extends δ by setting δ(r, a 1 ) = R 1. So R 1 δ(r, a 1 ) holds in this case also. For the induction step let us assume that R i R = δ(r, a 1 a 2... a i ). If δ(r, a i+1 ) = R then from the dfs procedure we can see that R R {a i+1 }. However, R i {a i+1 } R {a i+1 }. So R i+1 R = δ(r, a i+1 ) = δ(r, a 1 a 2... a i+1 ). Also notice ɛinr n δ(r, a 1 a 2... a n ); this implies that δ(r, a 1 a 2... a n ) is a final state and hence a 1 a 2... a n L(D). Now suppose a 1 a 2... a n L(D). The proof that a 1 a 2... a n L(R) goes in a similar way by showing that R i δ(r, a 1, a 2... a i ) Implementation and Evaluation We have implemented the coinductive rewriting engine in the rewriting specification language Maude 2.0 [57]. The interested readers can download the implementation from the website The operations on extended regular languages that are supported by our implementation are ~ for negation, * for Kleene Closure, for concatenation, & for intersection, and + for union in increasing order of precedence. Here, the intersection operator & is a syntactic sugar and is translated to an ERE containing union and negation using De Morgan s Law: eq R1 & R2 = ~ (~ R1 + ~ R2). To evaluate the performance of the algorithm we have generated the minimal DFA for all possible EREs of size up to 9. Surprisingly, the size of any DFA for EREs of size up to 9 did not exceed 9. Here the number of states gives the size of a DFA. The following table shows the performance of our procedure for the worst EREs of a given size. The code is executed on a 121

122 Pentium 4 2.4GHz, 4 GB RAM linux machine. Size ERE no. of states in DFA Time (ms) Rewrites 4 (a b) 4 < (a b) * 4 < ((a b) *) (a a a) ((a b) * b) (a a b) b Example 14 In particular, for the ERE (a a b) b the generated minimal DFA is given in Figure 7.2. Figure 7.2: (a a b) b Example 15 The ERE (( empty) (green red) ( empty) ) states the safety property that it should not be the case that in any trace of a traffic light we see green and red consecutively at any point. The set of events are assumed to be { green, red, yellow}. We think that this is the most intuitive and natural expression for this safety property. The implementation took 1ms and 1663 rewrites to generate the minimal DFA with 2 states. The DFA is given in Figure 7.3. However for large EREs the algorithm may take a long time to generate a minimal DFA. The size of the generated DFA may grow non-elementarily 122

123 Figure 7.3: (( empty) (green red) ( empty)) in the worst case. We generated DFAs for some complex EREs of larger sizes and got relatively promising results. One such sample result is as follows. Example 16 Let us consider the following ERE of size 110 ( $) $( $) ( #) #( ((0 + 1)0#( #) $(0 + 1)0 + (0 + 1)1#( #) $(0 + 1)1) (0(0 + 1)#( #) $0(0 + 1) + 1(0 + 1)#( #) $1(0 + 1))). This ERE accepts the language L 2, where L k = {σ#w#σ $w w {0, 1} k and σ, σ {0, 1, #} } The language L k was first introduced in [42] to show the power of alternation, used in [190] to show an exponential lower bound on ERE monitoring, and in [146, 147] to show the lower bounds for model checking. Our implementation took almost 18 minutes to generate the minimal DFA of size 107 and in the process it performed 1,374,089,220 rewrites. The above example shows that the procedure can take a large amount of time and space to generate DFAs for large EREs. To avoid the computation associated with the generation of minimal DFA we plan to maintain a database of EREs and their corresponding minimal DFAs on the internet. Whenever someone wants to generate the minimal DFA for a given ERE he/she can look up the internet database for the minimal DFA. If the ERE and the corresponding DFA exists in the database he/she can retrieve the corresponding DFA and use it as a monitor. Otherwise, he/she can generate the minimal DFA for the ERE and submit it to the internet database to create a new entry. The database will check the equivalence of the submitted ERE and the corresponding minimal DFA and insert it in the database. In this way one can avoid the computation of generating minimal DFA if it is already done by someone else. To further reduce the computation, circularities could also be stored in the database. 123

124 Online Monitor Generation and Visualization We have extended our implementation to create an internet server for optimal monitor generation that can be accessed from the the url Given an ERE the server generates the optimal DFA monitor for a user. The user submits the ERE through a web based form. A CGI script handling the web form takes the submitted ERE as an input, invokes the Maude implementation to generate the minimal DFA, and presents it to the user either as a graphical or a textual representation. To generate the graphical representation of the DFA we are currently using the GraphViz tool [86]. We presented a new technique to generate optimal monitors for extended regular expressions, which avoids the traditional technique based on complementation of automata, that we think is quite complex and not necessary. Instead, we have considered the (co)algebraic definition of EREs and applied coinductive inferencing techniques in an innovative way to generate the minimal DFA. Our approach to store already proven equivalences has resulted into a very efficient and straightforward algorithm to generate minimal DFA. We have evaluated our implementation on several hundreds EREs and have got promising results in terms of running time. Finally we have installed a server on the internet which can generate the optimal DFA for a given ERE. At least two major contributions have been made. Firstly, we have shown that coinduction is a viable and quite practical method to prove equivalence of extended regular expressions. Previously this was done only for regular expressions without complementation. Secondly, building on the coinductive technique, we have devised an algorithm to generate minimal DFAs from EREs. At present we have no bound for the size of the optimal DFA, but we know for sure that the DFAs we generate are indeed optimal. However we know that the size of an optimal DFA is bounded by some exponential in the size of the ERE. As future work, it seems interesting to investigate the size of minimal DFAs generated from EREs, and also to apply our coinductive techniques to generate monitors for other logics, such as temporal logics. 124

125 Exercises Exercise 10 Does the lower bound result in Section 7.1 hold for semiextended regular expressions, that is, for regular expressions extended only with intersection ( ) but not negation ( )? If yes, then sketch a proof. If no, then explain why not and try to find another lower bound. Exercise 11 Implement the derivative-based ERE membership checking algorithm described in Section (see also Theorem 10). Use your favorite programming language. Exercise 12 Implement the coinductive monitor synthesis for ERE described in Section

126 126

127 Chapter 8 Monitor Synthesis: Linear Temporal Logic (LTL) add co-inductive algorithm 8.1 Finite Trace Future Time Linear Temporal Logic Classical (infinite trace) linear temporal logic (LTL) [183, 160, 162] 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 + 2 P for some set of atomic propositions P, i.e., an infinite trace over the alphabet 2 P, 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 = 127

128 !<>!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 Finite Trace Semantics As mentioned in Subsection 2.1.2, 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 t i denotes the suffix trace that starts at position i, with positions starting at 1. The satisfaction relation = Trace Formula defines 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: 128

129 t = true iff true, t = false iff false, t = A iff A head(t), t = X /\ Y iff t = X and t = Y, t = X ++ Y iff t = X xor t = Y, t = o X iff (if tail(t) is defined then tail(t) = X else t = X), t = <>X iff ( i length(t)) t i = X, t = []X iff ( i length(t)) t i = X, t = X U Y iff ( i length(t)) (t i = Y and ( j < i) t j = 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 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. 129

130 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 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 takes 1 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 1 On a 1.7GHz, 1Gb memory PC. 130

131 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. Since ( n m) = ( m n 1 ) + ( n 1 m 1 ), it follows that T (n, m) > ( m n ), that is, T (n, m) = Ω(n m ), which is of course unacceptable. 8.2 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 [193], 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 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 [110, 109] 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 131

132 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 breadth-first order: ϕ 1 = []((p U q) -> <>(q -> or)), ϕ 2 = (p U q) -> <>(q -> or), ϕ 3 = p U q, ϕ 4 = <>(q -> or), ϕ 5 = p, ϕ 6 = q, ϕ 7 = q -> or, ϕ 8 = q, ϕ 9 = or, ϕ 10 = r. Given any finite trace t = e 1 e 2...e n 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 t i = ϕ j as follows: s[i, 10] = (r e i ) s[i, 9] = s[i + 1, 10] s[i, 8] = (q e i ) s[i, 7] = s[i, 8] implies s[i, 9] s[i, 6] = (q e i ) s[i, 5] = (p e i ) s[i, 4] = s[i, 7] or s[i + 1, 4] s[i, 3] = s[i, 6] or (s[i, 5] and s[i + 1, 3]) s[i, 2] = s[i, 3] implies s[i, 4] s[i, 1] = 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: 132

133 s[n, 10] = (r e n ) s[n, 9] = s[n, 10] s[n, 8] = (q e n ) s[n, 7] = s[n, 8] implies s[n, 9] s[n, 6] = (q e n ) s[n, 5] = (p e n ) s[n, 4] = s[n, 7] s[n, 3] = s[n, 6] s[n, 2] = s[n, 3] implies s[n, 4] s[n, 1] = 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 = e 1 e 2...e n next[10] (r e n ); next[9] next[10]; next[8] (q e n ); next[7] next[8] implies next[9]; next[6] (q e n ); next[5] (p e n ); 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 e i ); now[9] next[10]; now[8] (q e i ); now[7] now[8] implies now[9]; now[6] (q e i ); now[5] (p e i ); 133

134 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 [110, 109]. 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 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 breadthfirst 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 t i = ϕ j can be completely determined from the truth values of t i = ϕ j for all j < j m and the truth values of t i+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 e n ). 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); 134

135 If ϕ j is ϕ j1 Op ϕ j2 for some j < j 1, j 2 m, then next[j] = next[j 1 ] op next[j 2 ], 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 < j 1, j 2 m, then next[j] = next[j 2 ] 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 e i ). If ϕ j is!ϕ j for some j < j m, then now[j] = not now[j ]; If ϕ j is ϕ j1 Op ϕ j2 for j < j 1, j 2 m, then now[j] = now[j 1 ] op now[j 2 ], 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 < j 1, j 2 m, then now[j] = now[j 2 ] or (now[j 1 ] and next[j]). After each iteration, next[1] says whether the initial LTL formula is validated by the trace e i e i+1...e n. 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, 135

136 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 = e 1 e 2...e n ); 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, e n ); ); if ϕ j =!ϕ j then output( not next[,j, ]; ); if ϕ j = ϕ j1 Op ϕ j2 then output( next[,j 1, ] op next[, j 2, ]; ); 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[,j 2, ]; ); } 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, e i ); ); if ϕ j =!ϕ j then output( not now[,j, ]; ); if ϕ j = ϕ j1 Op ϕ j2 then output( now[,j 1, ] op now[, j 2, ]; ); 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[, j 2, ] or (now[,j 1, ] 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 136

137 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 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. 8.3 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 rewritingbased algorithm is linear in the size of the execution trace and worst-case exponential in the size of the monitored LTL formula 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. 137

138 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 by 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) =*=> 138

139 ((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 8.3.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 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 in its loop mode [63], 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 139

140 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 session 2. 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 [190] for a related result for extended regular expressions). Theorem 15 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(2 m ) space to be stored. Moreover, the exponential space cannot be avoided: any synchronous or asynchronous forwards monitoring algorithm for LTL requires space Ω(2 c m ) space, where c is some fixed constant. 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 2 m possibilities to combine them in a conjunction. Therefore, one needs space O(2 m ) 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, 2 In fact, JPaX reports a similar message also when the current monitoring requirement becomes true or false at any time during the monitoring process. 140

141 # and $. Consider also some natural number k and the language L k = {σ#w#σ $w w {0, 1} k and σ, σ {0, 1, #} }. This language was previously used in several works [146, 147, 190] to prove lower bounds. The language can be shown to contain exactly those finite traces satisfying the following LTL formula [147] of size Θ(k 2 ): φ k = [(!$) U ($ /\ (!$))] /\ [# /\ n+1 # /\ n (( i 0 /\ ($-> i 0))\/ ( i 1 /\ ($-> i 1)))]. Let us define an equivalence relation on finite traces in ( #). For a σ ( #), 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 ). Now observe that the number of equivalence classes of k is 2 2k ; this is because for any S (0 + 1) k, there is a σ such that S(σ) = S. Since φ k = Θ(k 2 ), it follows that there is some constant c such that φ k c k 2 for all large enough k. Let c be the constant 1/ c. We will prove this lower bound result by contradiction. Suppose A is an LTL forwards monitoring algorithm that uses less that 2 c m space for any LTL 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 2 k space. Since the number of equivalence classes of k is 2 2k, by the pigeon hole principle there must be two strings σ 1 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 k σ 2, it follows that (S(σ 1 ) \ S(σ 2 )) (S(σ 2 ) \ S(σ 1 )). Take w (S(σ 1 ) \ S(σ 2 )) (S(σ 2 ) \ S(σ 1 )). Then clearly, exactly one out of σ 1 $w and σ 2 $w is in L k, 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 8.1.2, and then checked 141 i=1

142 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 we will see how this algorithm can be made even more efficient, using memoization 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 8.1. 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 [96], PVS [211], or Maude-ITP [56]. We have already done it in PVS, but we prefer to use only plain Maude in this paper. Theorem 16 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 induction on the formula X. Constants e and x are needed in order to prove the first lemma via the theorem of constants. However, since we prove these lemmas by structural induction on X, we not only have to add two constants e and t for the universally quantified variables E and T, but also two other constants y and z standing for formulas which 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. 142

143 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 (e = a == e - a) and (e = true == e - true) and (e = false == e - false) and (e = y /\ z == e - y /\ z) and (e = y ++ z == e - y ++ z) and (e = [] y == e - [] y) and (e = <> y == e - <> y) and (e = y U z == e - y U z) and (e = o y == e - o y) and (e,t = true == t = true{e}) and (e,t = false == t = false{e}) and (e,t = b == t = b{e}) and (e,t = c == t = c{e}) and (e,t = y /\ z == t = (y /\ z){e}) and (e,t = y ++ z == t = (y ++ z){e}) and (e,t = [] y == t = ([] y){e}) and (e,t = <> y == t = (<> y){e}) and (e,t = y U z == t = (y U z){e}) and (e,t = o y == t = (o y){e}). It took Maude 129 reductions to prove these lemmas. Therefore, one can 143

144 safely add now these lemmas as follows: 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. 144

145 8.3.3 Further Optimization by Memoization Even though the formula transforming algorithm in Subsection 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] 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 8.3.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 145

146 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. 8.4 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) [39], 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). 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 146

147 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 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 8.3 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 8.1 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 8.3. 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 8.2. If there is no formula in S equivalent to ϕ θ, then the new formula ϕ θ is added to S, multitransition µ(ϕ) 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 15, this recursion will eventually terminate, leading to an MT-FSM*. The procedure Valid used in LTL2MT-FSM above is defined in Figure 147

148 1. let S be ϕ 2. procedure LTL2MT-FSM(ϕ) 3. let µ (ϕ) be 4. let µ(ϕ) be 5. foreach θ : A {true, false} do let e θ be the list of atoms a with θ(a) = true 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 8.1: Algorithm to generate a minimal MT-FSM* (S, A, µ, µ, ϕ) from an LTL formula ϕ 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 8.3, 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, 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 8.3 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 148

149 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 ϕ θ S 8. then let S be S {ϕ θ } 9. if Valid(ϕ θ ) = false then return false 10. endfor 11. return true 12. end function Figure 8.2: Validity checker for an LTL formula ϕ. Theorem 15, this procedure will also eventually terminate. Theorem 17 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 8.1, by any finite trace; 2. The space and time complexity of Valid(ϕ) is 2 O(2m) ; 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,..., θ n, θ, it is the case that ϕ θ 1 ϕ 1 θn θ ϕ n true if and only if the finite trace e θ1... e θn e θ satisfies ϕ, and ϕ θ 1 ϕ 1 θn ϕ n θ false if and only if e θ1... e θn e θ does not satisfy ϕ; 4. M is synchronous; more precisely, for any events θ 1,..., θ n, it is the case that ϕ θ 1 ϕ 1 θn e ϕ θ n true if and only if eθ1... e θn e θ is a valid prefix of ϕ, and ϕ θ 1 ϕ 1 θn e ϕ θ n false if and only if eθ1... e θn e θ is a bad prefix of ϕ; 5. The space and time complexity of LTL2MT-FSM(ϕ) is 2 O(2m) ; 149

150 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 6.1.2, needs O(2 m ) 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 8.3 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 15, each formula ϕ into which ϕ can evolve needs O(2 m ) space to be stored. That means that there can be at most 2 O(2m) such formulae. So the total space needed to store S in the worst case is 2 O(2m). 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 2 m and since reducing the derivative of a formula by an event takes time proportional with the size of the formula, we deduce that the total running time of the Valid procedure is 2 O(2m). 3. By Theorem 16 and the event consuming procedure described in Subsection 8.3.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 LTL semantics. That means the MT-FSM* generated by LTL2MT- FSM(ϕ) contains a formula-state ϕ 1 which is equivalent to ϕ{e θ1 }; moreover, ϕ θ 1 ϕ 1 in this MT-FSM*. Inductively, one can show that there is a series of formulae-states ϕ 1,..., ϕ n such that ϕ θ 1 ϕ 1 θn ϕ 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 150

151 θ ϕ 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, ϕ such that ϕ θ 1 ϕ 1 θn θ ϕ n ϕ is a sequence of transitions in the generated MT-FSM and ϕ{e θ1 }{ }{e θn }{e θ } is equivalent to ϕ. By Proposition 14, 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 8.1, 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 analysis of Valid in 2 above, by Theorem 15 we get that there can be at most 2 O(2m) formulae generated in S, so the total space needed to store S in the worst case is also 2 O(2m). 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 the original formula ϕ, so its space and time complexity will also be 2 O(2m) each time it is called by LTL2MT-FSM. One can easily see now that the space and time requirements of LTL2MT-FSM(ϕ) are also 2 O(2m) (the constant in the exponent O(2 m ) can be appropriately enlarged). 6. For any MT-FSM* machine M = (S, A, µ, µ, q 0 ), one can associate a formula, more precisely a state in M, to each state in S as follows. ϕ is θ associated to the initial state q 0. Then for each transition q 1 q2 in M such that q 1 has a formula ϕ 1 associated and q 2 has no formula associated, then θ one associates that ϕ 2 to q 2 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 L M (q) = L M (ϕ 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 of states. Let ϕ be a formula in S and let ϕ θ 1 ϕ 1 θn θ ϕ n ϕ be a sequence of transitions in M leading to ϕ ; note that all formulae in S 151

152 are reachable via transitions in M from ϕ. Let q be the state in S such that θ q 1 0 q1 θn θ q n q. Then L M (q ) = L M (ϕ q ). Since by Proposition 14, L M (q ) = {t e θ1... e θn e θ t L M (q 0 )} and L M (ϕ ) = {t e θ1... e θn e θ t L M (ϕ)}, it follows that L M (ϕ q ) = L M (ϕ ), 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(2 m ) bits to encode a state of M, which is the main memory needed by the monitoring algorithm. Like in Theorem 15, 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 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(2 m ). 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(2 m ), and which also takes time O(2 m ). 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 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 8.3, also shown in Figure 6.3 in flowchart notation; Figure 6.2 shows its optimal MT-FSM*. For simplicity, the states true and false do not appear in Figure 8.3. Notice that the atomic predicate red 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 152

153 State Non-terminal event Terminal event 1 yellow? 1 : green? red? false : 2 : 1 yellow? true : green? false : true 2 yellow? 1 : red? false : 2 yellow? true : false Figure 8.3: An optimal BTT-FSM* for the formula [](green ->!red U yellow). 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.4 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.4, 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 8.3 is satisfactory in practice if one is willing to use a rewriting engine for monitoring. 8.5 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 153

154 Formula State Monitoring BTT Terminating BTT a 1 1 a? t : f ( a a) (a -> b) 1 a? (b? 1 : 2) : 1 a? (b? t : f) : t 2 b? 1 : 2 b? t : f a U (b U c) 1 c? t : (a? 1 : (b? 2 : f)) c? t : f 2 c? t : (b? 2 : f) c? t : f a U (b U (c U d)) 1 d? t : a? 1 : b? 2 : c? 3 : f d? t : f 2 d? t : b? 2 : c? 3 : f d? t : f 3 d? t : c? 3 : f d? t : f ((a U b) U c) U d 1 d? t : c? 1 : b? 4 : a? 5 : f d? t : f 2 b? c? t : 7 : a? c? 6 : 2 : f c? b? t : f : f 3 b? d? t : c? 1 : 4 : a? d? 6 : c? 3 : 5 : f d? b? t : f : f 4 c? d? t : 1 : b? d? 7 : 4 : a? d? 2 : 5 : f d? c? t : f : f 5 b? d? c? t : 7 : c? 1 : 4 : a? d? c? 6 : 2 : c? 3 : 5 : f d? c? b? t : f : f : f 6 b? t : a? 6 : f b? t : f 7 c? t : b? 7 : a? 2 : f c? t : f Figure 8.4: Six BTT-FSM*s generated in less than 15 seconds. 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 154

155 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 real-time 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 is left as future work. Exercises Exercise 13 As seen in Chapters 8 and 9, there are different semantics for LTL. This can be quite confusing in practice, because one can write a safety property using LTL thinking of one semantics, say finite-trace semantics, but then use the monitor generation algorithm for the other semantics, say infinite-trace. (1) Give an LTL formula which is valid under one semantics but not under the other. (2) Give an LTL formula whose languages of bad-prefixes are different under the two semantics. (3) Describe an implementation that automatically checks whether a given formula admits different bad prefixes under finite-trace vs. infinite-trace semantics. 155

156 156

157 Chapter 9 Efficient Monitoring of ω-languages currently from CAV 05 paper [70]; this material will eventually be dissolved in other parts of the book. Abstract: We present a technique for generating efficient monitors for ω-regular-languages. We show how Büchi automata can be reduced in size and transformed into special, statistically optimal nondeterministic finite state machines, called binary transition tree finite state machines (BTT- FSMs), which recognize precisely the minimal bad prefixes of the original ω-regular-language. The presented technique is implemented as part of a larger monitoring framework and is available for download. 9.1 Introduction There is increasing recent interest in the area of runtime verification [117, 216], which is an area which aims at bridging testing and formal verification. In runtime verification, monitors are generated from system requirements. These monitors observe online executions of programs and check them against requirements. The checks can be either precise, with the purpose of detecting existing errors in the observed execution trace, or predictive, with the purpose of detecting errors that have not occurred in the observed execution but were close to happening and could possibly occur in other executions of the (typically concurrent) system. Runtime verification can be used either during 157

158 testing, to catch errors, or during operation, to detect and recover from errors. Since monitoring unavoidably adds runtime overhead to a monitored program, an important technical challenge in runtime verification is that of synthesizing efficient monitors from specifications. Requirements of systems can be expressed in a variety of formalisms, not all of them necessarily easily monitorable. As perhaps best shown by the immense success of programming languages like Perl and Python, regular patterns can be easily devised and understood by ordinary software developers. ω-regular-languages [40, 228] add infinite repetitions to regular languages, thus allowing one to specify properties of reactive systems [162]. The usual acceptance condition in finite state machines (FSM) needs to be modified in order to recognize infinite words, thus leading to Büchi automata [55]. Logics like linear temporal logics (LTL) [162] often provide a more intuitive and compact means to specify system requirements than ω-regular patterns. It is therefore not surprising that a large amount of work has been dedicated to generating (small) Büchi automata from, and verifying programs against, LTL formulae [90, 228, 80, 88]. Based on the belief that ω-languages represent a powerful and convenient formalism to express requirements of systems, we address the problem of generating efficient monitors from ω-languages expressed as Büchi automata. More precisely, we generate monitors that recognize the minimal bad prefixes [150] of such languages. A bad prefix is a finite sequence of events which cannot be the prefix of any accepting trace. A bad prefix is minimal if it does not contain any other bad prefix. Therefore, our goal is to develop efficient techniques that read events of the monitored program incrementally, and precisely detect when a bad prefix has occurred. Dual to the notion of bad prefix is that of a good prefix, meaning that the trace will be accepted for any infinite extension of the prefix. We present a technique that transforms a Büchi automaton into a special (nondeterministic) finite state machine, called a binary transition tree finite state machine (BTT-FSM), that can be used as a monitor: by maintaining a set of possible states which is updated as events are available. A sequence of events is a bad prefix iff the set of states in the monitor becomes empty. One interesting aspect of the generated monitors is that they may contain a special state, called neverviolate, which, once reached, indicates that the specification is not monitorable from that moment on. That can mean either that the specification has been fulfilled (e.g., a specification (x > 0) becomes fulfilled when x is first seen larger than 0), or that from that moment 158

159 on, there will always be some possible continuation of the execution trace. For example, the monitor generated for (a b) will have exactly one state, neverviolate, reflecting the intuition that liveness properties cannot be monitored. As usual, a program state is abstracted as a set of relevant atomic predicates that hold in that state. However, in the context of monitoring, the evaluation of these atomic predicates can be the most expensive part of the entire monitoring process. One predicate, for example, can say whether the vector v[ ] is sorted. Assuming that each atomic predicate has a given evaluation cost and a given probability to hold, which can be estimated apriori either by static or by dynamic analysis, the BTT-FSM generated from a Büchi automaton executes a conditional program, called a binary transition tree (BTT), evaluating atomic predicates by need in each state in order to statistically optimize the decision to which states to transit. One such BTT is shown in Fig The work presented in this paper is part of a larger project focusing on monitoring-oriented programming (MOP) [47, 46] which is a tool-supported software development framework in which monitoring plays a foundational role. MOP aims at reducing the gap between specification and implementation by integrating the two through monitoring: specifications are checked against implementations at runtime, and recovery code is provided to be executed when specifications are violated. MOP is specification-formalismindependent: one can add one s favorite or domain-specific requirements formalism via a generic notion of logic plug-in, which encapsulates a formal logical syntax plus a corresponding monitor synthesis algorithm. The work presented in this paper is implemented and provided as part of the LTL logic plugin in our MOP framework. It is also available for online evaluation and download on the MOP website [1]. Some Background and Related Work. Automata theoretic modelchecking is a major application of Büchi automata. Many model-checkers, including most notably SPIN [122], use this technique. So a significant effort has been put into the construction of small Büchi automata from LTL formulae. Gerth et al. [90] show a tableaux procedure to generate on-the-fly Büchi automata of size 2 O( ϕ ) from LTL formulae ϕ. Kesten et al. [138] describe a backtracking algorithm, also based on tableaux, to generate Büchi automata from formulae involving both past and future modalities (PTL), but no complexity results are shown. It is known that LTL model-checking is PSPACE-complete [215] and PTL is as expressive and as hard as LTL [164], 159

160 though exponentially more succinct [164]. Recently, Gastin and Oddoux [88] showed a procedure to generate standard Büchi automata of size 2 O( ϕ ) from PTL via alternating automata. Several works [80, 90] describe simplifications to reduce the size of Büchi automata. Algebraic simplifications can also be applied apriori on the LTL formula. For instance, a U b c U b (a c) U b is a valid LTL congruence that will reduce the size of the generated Büchi automaton. All these techniques producing small automata are very useful in our monitoring context because the smaller the original Büchi automaton for the ω-language, the smaller the BTT-FSM. Simplifications of the automaton with respect to monitoring are the central subject of this paper. Kupferman et al. [150] classify safety according to the notion of informativeness. Informative prefixes are those that tell the whole story : they witness the violation (or validation) of a specification. Unfortunately, not all bad prefixes are informative; e.g., the language denoted by (q ( (p))) (r ( ( p))) does not include any word whose prefix is {q, r}, {q}, {!p}. This is a (minimal) bad but not informative prefix, since it does not witness the violation taking place in the next state. One can use the construction described in [150] to build an automaton of size O(2 2 ϕ ) which recognizes all bad prefixes but, unfortunately, this automaton may be too large to be stored. Our fundamental construction is similar in spirit to theirs but we do not need to apply a subset construction on the input Büchi since we already maintain the set of possible states that the running program can be in. Geilen [89] shows how Büchi automata can be turned into monitors. The construction builds a tableaux similar to [90] in order to produce an FSM of size O(2 ϕ ) for recognizing informative good prefixes. Here we detect all the minimal bad prefixes, rather than just the informative ones. Unlike model-checking where a user hopes to see a counter-example that witnesses the violation, when monitoring critical applications one might want to observe a problem as soon as it occurs. The technique illustrated here is implemented as a plug-in in the MOP runtime verification (RV) framework [47, 46]. Other RV tools include Java- MaC [140], JPaX [106], JMPaX [210], and Eagle [22]. Java-MaC uses a special interval temporal logic as the specification language, while JPaX and JMPaX support variants of LTL. These systems instrument the Java bytecode to emit events to an external monitor observer. JPaX was used to analyze NASA s K9 Mars Rover code [16]. JMPaX extends JPaX with predictive capabilities. Eagle is a finite-trace temporal logic and tool for runtime verification, defining a logic similar to the µ-calculus with data- 160

161 Figure 9.1: Büchi automaton recognizing the ω-regular expression (a + b) b ω parameterization 9.2 Preliminaries: Büchi Automata Büchi automata and their ω-languages have been studied extensively during the past decades. They are well suited to program verification because one can check satisfaction of properties represented as Büchi automata statically against transition systems [228, 55]. LTL is an important but proper subset of ω-languages. Definition 39 A (nondeterministic) standard Büchi automaton is a tuple Σ, S, δ, S 0, F, where Σ is an alphabet, S is a set of states, δ : S Σ 2 S is a transition function, S 0 S is the set of initial states, and F S is a set of accepting states. In practice, Σ typically refers to events or actions in a system to be analyzed. Definition 40 A Büchi automaton A = Σ, S, δ, S 0, F is said to accept an infinite word τ Σ ω iff there is some accepting run in the automaton, that is, a map ρ: Nat S such that ρ 0 S 0, ρ i+1 δ(ρ i, τ i ) for all i 0, and inf(ρ) F, where inf(ρ) contains the states occurring infinitely often in ρ. The language of A, L(A), consists of all words it accepts. Therefore, ρ can be regarded as an infinite path in the automaton that starts with an initial state and contains at least one accepting state appearing infinitely often in the trace. Fig. 9.1 shows a nondeterministic Büchi automaton for the ω-regular expression (a + b) b ω that contains all the infinite words over a and b with finitely many as. Definition 41 Let L(A) be the language of a Büchi automaton A= Σ, S, δ, S 0, F. A finite word x Σ is a bad prefix of A iff for any y Σ ω the concatenation xy L(A). A bad prefix is minimal if no other bad prefix is a prefix of it. 161

162 Therefore, no bad prefix of the language of a Büchi automaton can be extended to an accepted word. Similarly to [55], from now on we may tacitly assume that Σ is defined in terms of propositions over atoms. For instance, the self-transitions of s 1 in Fig. 9.1 can be represented as one self-transition, a b. 9.3 Generating a BTT-FSM from a Büchi automaton Not any property can be monitored. For example, in order to check a liveness property one needs to ensure that certain propositions hold infinitely often, which cannot be verified at runtime. This section describes how to transform a Büchi automaton into an efficient BTT-FSM that rejects precisely the minimal bad prefixes of the denoted ω-language. Definition 42 A monitor FSM (MFSM) is a tuple Σ, S, δ, S 0, where Σ = P A is an alphabet, S is a set of states potentially including a special state neverviolate, δ : S Σ 2 S is a transition function with δ(neverviolate, true) = {neverviolate} when neverviolate S, and S 0 S are initial states. Note that we take Σ to be P A, the set of propositions over atoms in A. Like BTT-FSMs, MFSMs may also have a special neverviolate state. θ Definition 43 Let Q 1 θ 0 Q1 2 θ j... Qj be a sequence of transitions in the MFSM Σ, S, δ, S 0, generated from t = θ 1 θ 2...θ j, where Q 0 = S 0 and Q i+1 = s Q i {δ(s, σ) θ i+1 = σ}, for all 0 i < j. We say that the MFSM rejects t iff Q j = {}. No finite extension of t will be rejected if neverviolate Q j. From Büchi to MFSM. We next describe two simplification procedures on a Büchi automaton that are sound w.r.t. monitoring, followed by the construction of an MFSM. The first procedure identifies segments of the automaton which cannot lead to acceptance and can therefore be safely removed. As we will show shortly, this step is necessary in order to guarantee the soundness of the monitoring procedure. The second simplification identifies states with the property that if they are reached then the corresponding 162

163 requirement cannot be violated by any finite extension of the trace, so monitoring is ineffective from there on. Note that reaching such a state does not necessarily mean that a good prefix has been recognized, but only that the property is not monitorable from there on. Definition 44 Let Σ, S, δ, S 0, F be a Büchi automaton, C a connected component of its associated graph, and nodes(c) the states associated to C. We say that C is isolated iff for any s nodes(c) and σ Σ, it is the case that δ(s, σ) nodes(c). We say that C is total iff for any s nodes(c) and event θ, there are transitions σ such that θ = σ and δ(s, σ) nodes(c). Therefore, there is no way to escape from an isolated connected component, and regardless of the upcoming event, it is always possible to transit from any node of a total connected component to another node in that component. Removing Bad States. The next procedure removes states of the Büchi automaton which cannot be part of any accepting run (see Definition 2). Note that any state appearing in such an accepting run must eventually reach an accepting state. This procedure is fundamentally inspired by stronglyconnected-component-analysis [138, 228], used to check emptiness of the language denoted by a Büchi automaton. Given a Büchi automaton A = Σ, S, δ, S 0, F, let U S be the largest set of states such that the language of Σ, S, δ, U, F is empty. The states in U are unnecessary in A, because they cannot change its language. Fortunately, U can be calculated effectively as the set of states that cannot reach any cycle in the graph associated to A which contains at least one accepting state in F. Fig. 9.2 shows an algorithm to do this. INPUT : A Büchi automaton A OUTPUT : A smaller Büchi automaton A such that L(A ) = L(A). REMOVE BAD STATES : for each maximal connected component C of A if (C is isolated and nodes(c) F= ) then mark all states in C bad DFS MARK BAD ; REMOVE BAD Figure 9.2: Removing bad states The loop identifies maximal isolated connected components which do not contain any accepting states. The nodes in these components are marked 163

164 as bad. The procedure DFS MARK BAD performs a depth-first-search in the graph and marks nodes as bad when all outgoing edges lead to a bad node. Finally, the procedure REMOVE BAD removes all the bad states. The runtime complexity of this algorithm is dominated by the computation of maximal connected components. In our implementation, we used Tarjan s O(V + E) double DFS [55]. The proof of correctness is simple and it appears in Appendix A. The Büchi automaton A produced by the algorithm in Fig. 9.2 has the property that there is some proper path from any of its states to some accepting state. One can readily generate an MFSM from a Büchi automaton A by first applying the procedure REMOVE BAD STATES in Fig. 9.2, and then ignoring the acceptance conditions. Theorem 18 The MFSM generated from a Büchi automaton A as above rejects precisely the minimal bad prefixes of L(A). Proof: Let A= Σ, S, δ, S 0, F be the original Büchi automaton, let A = Σ, S, δ, S 0, F be the Büchi automaton obtained from A by applying the algorithm in Fig. 9.2, and let Σ, S, δ, S 0 be the corresponding MFSM of A. For any finite trace t = θ 1...θ j, let us consider its corresponding sequence θ of transitions in the MFSM Q 1... θ j 0 Q j, where Q 0 is S 0. Note that the trace t can also be regarded as a sequence of letters in the alphabet Σ of A, because we assumed Σ is P A and because there is a bijection between propositions in P A and A-events. All we need to show is that t is a bad prefix of A if and only if Q j =. Recall that A has the property that there is some non-empty path from any of its states to some accepting state. Thus, one can build an infinite path in A starting with any of its nodes, with the property that some accepting state occurs infinitely often. In other words, Q j is not empty iff the finite trace t is the prefix of some infinite trace in L(A ). This is equivalent to saying that Q j is empty iff the trace t is a bad prefix in A. Since Q j empty implies Q j empty for any j>j, it follows that the MFSM rejects precisely the minimal bad prefixes of A. Theorem 1 says that the MFSM obtained from a Büchi automaton as above can be used as a monitor for the corresponding ω-language. Indeed, one only needs to maintain a current set of states Q, initially S 0, and transform it accordingly as new events θ are generated by the observed program: if Q Q θ then set Q to Q ; if Q ever becomes empty then report violation. Theorem 1 tells us that a violation will be reported as soon as a bad prefix is encountered. Collapsing Never-Violate States. Reducing runtime overhead is crucial 164

165 a s 1 a b true s 2 s 3 true Figure 9.3: Non-monitorable automaton in runtime verification. There are many situations when the monitoring process can be safely stopped, because the observed finite trace cannot be finitely extended to any bad prefix. The following procedure identifies states in a Büchi automaton which cannot lead to the violation of any finite computation. For instance, the Büchi automaton in Fig. 9.3 can only reject infinite words in which the state s 2 occurs finitely many times; moreover, at least one transition is possible at any moment. Therefore, the associated MFSM will never report a violation, even though there are infinite words that are not accepted. We call such an automaton non-monitorable. This example makes it clear that if a state like s 1 is ever reached by the monitor, it does not mean that we found a good prefix, but that we could stop looking for bad prefixes. Let A= Σ, S, δ, S 0, F be a Büchi automaton simplified with REMOVE BAD STATES. The procedure in Fig. 9.4 finds states which, if reached by a monitor, then the monitor can no longer detect violations regardless of what events will be observed in the future. The procedure first identifies the total connected components. According to the definition of totality, once a monitor reaches a state of a total connected component, the monitor will have the possibility to always transit within that connected component, thus never getting a chance to report violation. All states of a total component can therefore be marked as never violate. Other states can also be marked as such if, for any events, it is possible to transit from them to states already marked never violate ; that is the reason for the disjunction in the second conditional. The procedure finds such nodes in a depth-first-search. Finally, COLLAPSE-NEVER VIOLATE collapses all components marked never violate, if any, to a distinguished node, neverviolate, having just a true transition to itself. If any collapsed node was in the initial set of states, then the entire automaton is collapsed to neverviolate. The procedure GENERATE MFSM produces an MFSM by ignoring accepting conditions. 165

166 INPUT : A Büchi automaton A, cost function ς, and probability function π. OUTPUT : An effective BTT-FSM monitor rejecting the bad prefixes of L(A). COLLAPSE NEVER VIOLATE : for each maximal connected component C of A if ( C is total ) then mark all states in C as never violate for each s in depth-first-search visit if ( {σ δ(s, σ) contains some state marked never violate } ) then mark s as never violate COLLAPSE-NEVER VIOLATE ; GENERATE MFSM ; GENERATE BTT-FSM Figure 9.4: Collapsing non-monitorable states Taking as input this MFSM, say Σ, S, δ, S 0, cost function ς, and probability function π, GENERATE BTT-FSM constructs a BTT-FSM A, S, btt, S 0, where A corresponds to the set of atoms from which the alphabet Σ is built, and the map btt, here represented by a set of pairs, is defined as follows: btt = {(neverviolate, {neverviolate}) neverviolate S } {(s, β s ) s S -{neverviolate} β s = µ s }, where β s optimally implements µ s w.r.t. ς and π, with µ s = ( {[σ : s ] s δ (s, σ)}) The symbol denotes concatenation on a set of multi-transitions. Optimal BTTs β s are generated like in Section 6.2. Proof of correctness appears in Appendix A. 9.4 Monitor Generation and MOP We have shown that one can generate from a Büchi automaton a BTT- FSM recognizing precisely its bad prefixes. However, it is still necessary to integrate the BTT-FSM monitor within the program to be observed. Monitoring-oriented programming (MOP) [46] aims at merging specification and implementation through generation of runtime monitors from specifications and integration of those within implementation. In MOP, the task of generating monitors is divided into defining a logic engine and a language shell. The logic engine is concerned with the translation of specifications given as logical formulae into monitoring (pseudo-)code. The shell is responsible for the integration of the monitor within the application. Fig. 9.5 captures the essence of the synthesis process of LTL monitors in MOP using the technique described in this paper. The user defines specifications either as annotations in the code or in a separate file. The 166

167 φ A φ,a φ spec LTL logic engine Java shell Def. of events, predicates, and handlers Program Instrumentation Figure 9.5: Generation of monitors in MOP specification contains definitions of events and state predicates, as well as LTL formulae expressing trace requirements. These formulae treat events and predicates as atomic propositions. Handlers are defined to track violation or validation of requirements. For instance, assume the events a and b denote the login and the logoff of the same user, respectively. Then the formula (a ( a U b)) states that the user cannot be logged in more than once. A violation handler could be declared to track the user who logged in twice. The logic engine is responsible for the translation of the formulae ϕ and ϕ into two BTT-FSM monitors. One detects violation and the other validation of ϕ. Note that if the user is just interested in validation (no violation handler), then only the automaton for negation is generated. Finally, the language shell reads the definition of events and instruments the code so that the monitor will receive the expected notifications. We used LTL2BA [179] to generate standard Büchi automata from LTL formulae. The described procedures are implemented in Java. This software and a WWW demo are available from the MOP website [1] Evaluation Table 1 shows BTT-FSM monitors for some LTL formulae. The BTT definition corresponding to a state follows the arrow ( ). Initial states appear in brackets. For producing this table, we used the same cost and probabilities for all events and selected the smallest BTT. The first formula cannot be validated by monitoring and presents the permanent possibility to be violated; that is why its BTT-FSM does not have a neverviolate state. The second formula can never be violated since event a followed by event b can always occur in the future, so its BTT-FSM consists of just one state neverviolate. The last formula shows that our procedure does not aim at distinguishing validating from non-violating prefixes. Table 2 shows that our technique can not only identify non-monitorable formulae, but also reduce the cost of monitoring by collapsing large parts of the Büchi automaton. We use the symbols,, and to denote, respectively, the effectiveness of REMOVE BAD STATES, the first, and the second loop of COLLAPSE NEVER VIOLATE. The first group contains non-monitorable formulae. The next contains formulae where monitor size could not be 167

168 Temporal Formula BTT-FSM (a b U c) [s 0 ] c? (b? s 0 s 1 : s 0 ) : (a? (b? s 1 : ) : (b? s 0 s 1 : s 0 )) s 1 b? (c? s 0 s 1 : s 1 ) : (c? s 0 : ) (a b) [neverviolate] {neverviolate} a U b U c [s 0 ] c? neverviolate : (a? (b? s 0 s 1 : s 0 ) : (b? s 1 : )) s 1 c? neverviolate : (b? s 1 : ) neverviolate {neverviolate} Table 9.1: BTT-FSMs generated from temporal formulae reduced by our procedures. The third group shows formulae where our simplifications could significantly reduce the monitor size. The last group shows examples of accidentally safe and pathologically safe formulae from [150]. A formula ϕ is accidentally safe iff not all bad prefixes are informative [150] (i.e., can serve as a witness for violation) but all computations that violate ϕ have an informative bad prefix. A formula ϕ is pathologically safe if there is a computation that violates ϕ and has no informative bad prefix. Since we detect all minimal bad prefixes, informativeness does not play any role in our approach. Both formulae are monitorable. For the last formula, in particular, a minimal bad prefix will be detected as soon as the monitor observes a a, having previously observed a b. One can generate and visualize the BTT-FSMsof all these formulae, and many others, online at [1]. 9.5 Conclusions Not all properties a Büchi automaton can express are monitorable. This paper describes transformations that can be applied to extract the monitorable components of Büchi automata, reducing their size and the cost of runtime verification. The resulting automata are called monitor finite state machines (MFSMs). The presented algorithms have polynomial running time in the size of the original Büchi automata and have already been implemented. Another contribution of this paper is the definition and use of binary transition trees (BTTs) and corresponding finite state machines (BTT-FSMs), as well as a translation from MFSMs to BTT-FSMs. These special-purpose state machines encode optimal evaluation paths of boolean propositions in transitions. 168

169 Temporal Formula # states # transitions symplif. a 2, 1 3, 1 a U ( b) 3, 1 5, 1 (a b c) 2, 1 4, 1 a U (b U (c U ( d))) 2, 1 3, 1 a U (b U (c U (d e))) 5, 1 15, 1 a U (b U (c U (d e))) 12, 1 51, 1 a 1, 1 1, 1 (a b U c) 2, 2 4, 4 a U (b U (c U d)) 4, 4 10, 10 a ( b) ( (e)) 5, 4 11, 6 a ( b) ( c) ( (e)) 9, 6 29, 12 a ( b) ( c) ( d) ( (e)) 17, 10 83, 30 a ( ( (b c U d))) ( (e)) 7, 5 20, 10 (a ( (c))) (b ( ( c))) 3, 3 5, 5 ( (a ( (c))) (b ( ( c)))) (a) (b) 12, 6 43, 22 Table 9.2: Number of states and transitions before and after monitoring simplifications 169

170 We used LTL2BA [179] to generate Büchi automata from LTL, and Java to implement the presented algorithms. Our algorithms, as well as a graphical HTML interface, are available at [1]. This work is motivated by, and is part of, a larger project aiming at promoting monitoring as a foundational principle in software development, called monitoring-oriented programming (MOP). In MOP, the user specifies formulae, atoms, cost and probabilities associated to atoms, as well as violation and validation handlers. Then all these are used to automatically generate monitors and integrate them within the application. This work is concerned with monitoring violations of requirements. In the particular case of LTL, validations of formulae can also be checked using the same technique by monitoring the negation of the input formula. Further work includes implementing the algorithm defined in [88] for generating Büchi automata of size 2 O( ϕ ) from PTL, combining multiple formulae in a single automaton as showed by Ezick [81] so as to reduce redundancy of proposition evaluations, and applying further (standard) NFA simplifications to MFSM. 170

171 appendixes from submission Appendix A. Proof of Correctness (Correctness of REMOVE BAD STATES) Let A = Σ, S, δ, S 0, F be the input and A = Σ, S, δ, S 0, F the output Büchi automata of the procedure. We want to show that their denoted languages are the same. Let ρ be an accepting run of A. Then ρ can be fragmented in ρ ρ where ρ is the prefix whose states appear only finitely many times in ρ. Each state in ρ is therefore reachable from any other and therefore must be in a connected component where some states are in F. The converse is also true, i.e., any connected component reachable from the set of initial states having at least one state in F generates accepting runs (from [55] pp. 129). It is then immediate to notice that no accepting run ρ contains states that can never reach a state in F. It turns out that any state belonging to an isolated component with no accepting states can be trivially removed as well as any state that can only make transitions to others which never reach an accepting state. This reachability analysis is performed in a depth-first-search as usual. Since only states that will never appear in accepting runs of A are removed, it follows that L(A)=L(A ). (Correctness of COLLAPSE NEVER VIOLATE) First, we need to show that the MFSM produced by GENERATE MFSM still detects bad prefixes of L(A), where A is the input Büchi automaton having the property that all states can reach accepting states. From the observation thal all states that have been collapsed to neverviolate can reach an accepting state, the proof of the first part is similar to that of Theorem 1 and is omitted. So, the MFSM is still a monitor for the bad prefixes of L(A). In other words, this simplification is sound w.r.t. monitoring minimal bad prefixes. In addition to this, we need to show that the state neverviolate is reached only if violations of the requirement can no longer occur. We prove this second correctness criteria by a case analysis on the shape of the MFSM associated to the input Büchi. Each case corresponds to a loop in COLLAPSE NEVER VIOLATE. θ (case 1) Let Q 1 θ 0 Q1 2 θ j... Qj be a sequence of transitions on the trace θ 1 θ 2...θ j produced by the MFSM corresponding to the input Büchi automaton A. Recall that we assume REMOVE BAD STATES is called before COLLAPSE NEVER VIOLATE. That is, all states in the input automaton reach some state in F. If some node in a total connected component C of the 171

172 graph associated to A belongs to Q j then it is not possible for any finite trace with prefix θ 1 θ 2...θ j to be rejected by the corresponding MFSM since it is always possible to make a transition between nodes in C from the definition of totality. Those states in C can be marked as never violate meaning that they denote the special neverviolate state. θ (case 2) Let Q 1 Q1 θ 2... θ j 0 Qj be a sequence of transitions produced on the trace θ 1 θ 2...θ j by the MFSM defined as before. If any state belongs to Q j with the property that for further events θ j+1 there exists a transition to a state marked never violate, then there must exist some state marked never violate in Q j+1. Such states can be found by checking the validity of the disjunction of propositions labeling the edges of transitions to states marked never violate. Since the monitor will definitely reach a state marked never violate in the trace θ 1 θ 2...θ j θ j+1, it is therefore safe to reach never violate in Q j as well, since violations are impossible from event j on. The states marked with the never violate label can therefore be collapsed, since a violation cannot occur if any of these states is reached. The BTT-FSM is generated from the MFSM according to the defined construction. Appendix B. Complexity Results We use the definition of Binary Decision Trees (BDTs) due to Garey [87]. As we will show next, Garey s BDTs serve as a procedure to identify one among a set of possible objects characterizing some aspect of interest. Hyafil and Rivest [132] proved the Optimal BDT problem NP-complete. Moret [176] shows that these BDTs are a restricted form of decision trees similar to binary transition trees as defined here. Let X = {x 1,..., x n } be a finite set of objects and T = {T 1,..., T t } a finite set of tests. For each test T i, 1 i t, and object x j, 1 j n, we either have T i (x j ) = true or T i (x j ) = false. A binary decision tree associates tests in the root and all other internal nodes, and associates objects in X to terminal nodes. The optimal decision tree problem is to determine whether there exists a decision tree with cost less than or equal to w which completely identifies each element in X, given T and X. The cost of this tree is defined as Σ xi X p(x i ), where p(x i ) is the length of the path from the root to the terminal identifying x i. One might think of the objects in X as possible answers to a question. The tests in T serve to prune the data set of answers. A decision tree defines 172

173 x 1 x 2 x 3 x 4 x 5 x 6 x 7 T 1 T F T F T F F T T 2 F T F F F T 4 T T 6 T 3 T F F F F x 5 F F T 3 x 7 T 5 T 4 F F F F T F F T 5 F F F F F T x 1 F x 3 x 6 T 2 T 6 F F F F F F T x 2 x 4 Figure 9.6: Table of tests and a possible BDT for this table possible sequences of questions to ask in order to give a unique answer among those in X. The table of Fig. 9.6 appears in [87] and denotes the set of tests available to use in some binary decision tree. The definition above [87] and the NP-completeness proof for the Optimal BDT problem [132] assume the table provided as input is unambiguous and completely identifies the objects in X. That is, any object in X can be distinguished by applying some sequence of tests. The decision tree in Fig. 9.6 appears in [87] as an identification procedure for the tests and objects defined in the adjacent table. The subtree to the left of a node denotes objects whose answers to the test labeling that node are true and the result of all other tests applied in the path to the root are consistent. The right subtree is similar. This is similar to binary transition trees. However, it is worth noting that in order to identify x j one does not need to test all T i having T i (x j ) = true. Observe this in particular for x 6 and x 7. It means that decision trees prune the data set in each test they make. A decision can be made whenever it is possible to identify an object. For instance, no object but x 7 assigns false to T 1 and true to T 6. So this is a sufficient condition to identify x 7. Moret showed that the construction of optimal binary decision trees, similar to BTTs (where tests take the form of boolean proposition over a set of atoms) is an NP-hard [176] problem. That implies that our BTT problem is also NP-hard. Exercises Exercise 14 Consider the following three (infinite-trace) LTL formulae from Table 9.2: (a b c), (a b U c), and, respectively, a ( b) ( (e)). For each of them do the following: (1) give a Buchi automaton that has the same language; (2) give an MFSM corresponding to (1) that rejects precisely the minimal bad prefixes (see Theorem 18); (3) optimize the MFSM in (2) by collapsing the non-monitorable states (algorithm in Figure 9.4); (4) give the optimal BTT-FSM corresponding to (3). T 1 173

174 174

175 Chapter 10 Efficient Monitoring of Always Past Temporal Safety This chapter contains material from [119]. See also [118]. Abstract: The problem of testing whether a finite execution trace of events generated by an executing program violates a linear temporal logic (LTL) formula occurs naturally in runtime analysis of software. Two efficient algorithms for this problem are presented in this paper, both for checking safety formulae of the form always P, where P is a past time LTL formula. The first algorithm is implemented by rewriting and the second synthesizes efficient code from formulae. Further optimizations of the second algorithm are suggested, reducing space and time consumption. Special operators suitable for writing succinct specifications are discussed and shown equivalent to the standard past time operators. This work is part of NASA s PathExplorer project, the objective of which is to construct a flexible framework for efficient monitoring and analysis of program executions. 175

176 Like in future time LTL, but dually, we can have various ways to interpret ϕ. The stationary interpretation appears to be different from the strong / weak interpretation. That is, strong reduces to weak and viceversa, but it is not clear how stationary reduces to any of these or to anything else Introduction The work presented in this paper is part of a project at NASA Ames Research Center, called PathExplorer [107, 106, 112, 105, 189], that aims at developing a practical testing environment for NASA software developers. The basic idea of the project is to analyze the execution trace of a running program to detect errors. The errors that we are considering at this stage are multithreading errors such as deadlocks and data races, and non-conformance with linear temporal logic specifications, which is the main focus of this paper. Linear Temporal Logic (LTL) [183, 160, 162] is a logic for specifying properties of reactive and concurrent systems. The models of LTL are infinite execution traces, reflecting the behavior of such systems as ideally always being ready to respond to requests, operating systems being a typical example. LTL has been mainly used to specify properties of concurrent and interactive down-scaled models of real systems, so that fully formal correctness proofs could subsequently be carried out, for example using theorem provers or model checkers (see for example [123, 120, 113]). However, formal proof techniques are usually not scalable to real sized systems without a substantial effort to abstract the system more or less manually to a model which can be analyzed. Model checking of programs has received an increased attention from the formal methods community within the last couple of years, and several systems have emerged that can directly model check source code, such as Java and C [114, 227, 72, 124, 65, 21, 182]. Stateless model checkers [93, 220] try to avoid the abstraction process by not storing states. Although these systems provide high confidence, they scale less well because most of their internal algorithms are exponential or worse. Testing scales well, and is by far the most used technique in practice to validate software systems. The merge of testing and temporal logic specification is an attempt to achieve the benefits of both approaches, while avoiding some of the pitfalls of ad hoc testing and the complexity of fullblown theorem proving and model checking. Of course, there is a price to pay 176

177 in order to obtain a scalable technique: the loss of coverage. The suggested framework can only be used to examine single execution traces, and can therefore not be used to prove a system correct. Our work is based on the belief that software engineers are willing to trade coverage for scalability, so our goal is to provide tools that are completely automatic, implement very efficient algorithms and find many errors in programs. A longer term goal is to explore the use of conformance with a formal specification to achieve fault tolerance. The idea is that the failure may trigger a recovery action in the monitored program. The idea of using LTL in program testing is not new. It has already been pursued in commercial tools such as Temporal Rover (TR) [73], which stimulated our work us in a major way. In TR, future and past time LTL properties are stated as formal comments within the program at chosen program points, like assertions. These formal comments are then, by a pre-processor, translated into code, which is inserted at the position of the comments, and executed whenever reached during program execution 1. The MaC tool [158] is another example of a runtime monitoring tool that has inspired this work. Here Java bytecode is automatically instrumented to generate events of interest during the execution. Of special interest is the temporal logic used in MaC, which can be classified as a past time interval logic convenient for expressing monitoring properties in a succinct way. A theoretical contribution in this paper is Theorem 19, which shows that the MaC temporal logic, together with 10 others, is equivalent to the standard past time temporal logic. The path exploration tool described in [100] uses a future time temporal logic formula to guide the execution of a program for debugging purposes. Hence, the role of a temporal logic formula is turned around from monitoring a trace to generation of a trace. Past time LTL has been shown to have the same expressiveness as future time LTL [85]. However, past time LTL is exponentially more succinct than future time LTL [164]. For example, a property like every response should be preceded by a request can be easily stated in past time logic (reflecting directly the previous sentence), but the corresponding future time representation becomes it s not the case that (there is no request until (there is a response and no request)). Hence, past time LTL is more convenient for specifying certain properties, and is the focus of this paper. We present two efficient monitoring algorithms for checking safety formulae of the form always P, where P is a past time LTL formula, one based 1 The implementation details of TR are not public. 177

178 on formula rewriting and one based on synthesizing efficient monitoring code from a formula. The rewriting-based algorithm illustrates how rewriting can be used to easily and elegantly define new logics for monitoring. This may be of interest when experimenting with logics, or if logics are domain specific and change with the application, or if one simply wants a small and elegant implementation. The synthesis-based algorithm, on the other hand, generates a very effective monitor for the particular past time logic, and focuses on efficiency. It is also better suited for generating code that can be inserted in the monitored program, in contrast to the rewriting approach, where a rewriting engine must be called by an external call. The first algorithm is implemented by rewriting using Maude [59, 61, 62], an executable specification language whose main operational engine is based on term rewriting. Since flexibility with respect to defining/modifying monitoring logics is a very important factor at this stage in the development of PathExplorer, we have actually developed a general framework using Maude which allows one to easily and effectively define new logics for runtime analysis and to monitor execution traces against formulae in these logics. The rewriting algorithm presented in this paper instantiate that framework to our logic of interest, past time LTL. The second algorithm presented in this paper is designed to be as efficient and specialized as possible, thus adding the minimum possible amount of runtime overhead. It essentially synthesizes a special purpose, efficient monitoring code from formulae, which is further compiled into an executable monitor. Further optimizations of the second algorithm are suggested, making each monitoring step typically run in time lower than the size of the monitored formula. Both algorithms are based on the fact that the semantics of past time LTL can be defined recursively in such as way that one only needs to look one step, or event, backwards in order to compute the new truth value of a formula and of its subformulae, thus allowing one to process and then discard the events as they are received from the instrumented program. Several special operators suitable for writing succinct monitoring safety specifications are introduced and shown semantically equivalent to the standard past time operators. Section 10.2 gives a short description of the PathExplorer architecture, putting the presented work in context. Section 10.3 recalls past time LTL and introduces several monitoring operators together with their semantics, then discusses several past time logics and finally shows their equivalences. Section 10.4 first presents our rewriting-based framework for defining and executing new monitoring logics, and then shows how past time LTL fits into 178

179 Figure 10.1: Overview of the PaX observer. this framework. Section 10.5 finally explains our monitor-synthesis algorithm, together with optimizations and two ways to implement it. Section 10.6 concludes the paper The PathExplorer Architecture PathExplorer, PaX, is a flexible environment for monitoring and analyzing program executions. A program (or a set of programs) to be monitored, is supposed to be instrumented to emit execution events to an observer, which then examines the events and checks whether they satisfy certain user-given constraints. We first give an overview of the observer that monitors the event stream. Then we discuss how a program is instrumented for monitoring of temporal logic properties. The instrumentation presented is specialized to Java, but the principles carry over to any programming language The Observer The constraints to be monitored can be of different kinds and defined in different languages. Each kind of constraint is represented by a module. Such a constraint module in principle implements a particular logic or program analysis algorithm. Currently there are modules for checking deadlock potentials, data race potentials, and for checking temporal logic formulae in different logics. Amongst the latter, several modules have been implemented for checking future time temporal logic, and the work presented in this paper is the basis for a module for checking past time logic formulae. In general, the user can program new constraint modules and in this manner extend PaX in an easy way. The system is defined in a component-based way, based on a dataflow view, where components are put together using a pipeline operator, see Figure The dataflow between any two components is a stream of events in simple text format, without any a-priori assumptions about the format of the events; the receiving component just ignores events it cannot recognize. 179

180 This simplifies composition and allows for components to be written in different languages and in particular to define observers of arbitrary systems, programmed in a variety of programming languages. This latter fact is important at NASA since several systems are written in a mixture of C, C++ and Java. The central component of the PaX system is a so-called dispatcher. The dispatcher receives events from the executing program or system and then retransmits the event stream to each of the constraint modules. Each module is running in its own process with one input pipe, only dealing with events that are relevant to the module. For this purpose each module is equipped with an event parser. The dispatcher takes as input a configuration script, which specifies a list of commands - a command for each module that starts the module in a process. The dispatcher may read its input event stream from a file, or alternatively from a socket, to which the instrumented running program must write the event stream. In the latter case, monitoring can happen on-the-fly as the event stream is produced, and potentially on a different computer than the observed system Code Instrumentation The program or system to be observed must be instrumented to emit execution events to the dispatcher (writing them to a file or to a socket as discussed above). We have currently implemented an automated instrumentation package for Java bytecode using the Java bytecode engineering tool JTrek [137]. The instrumentation package together with PathExplorer is called Java PathExplorer (JPaX). Given information about what kind of events to be emitted, the instrumentation package instruments the bytecode by inserting extra code for emitting events. For deadlock analysis, for example, events are generated that inform about lock acquisitions and releases. For temporal logic monitoring, one specifies the variables to be observed, and what predicates over these variables one wants to refer to in the temporal properties to be monitored. Imagine for example that the observer monitors the formula: always p, involving the predicate p, and that p is intended to be defined as p x > y, where x and y are static variables defined in a class C. In this case all updates to these variables must be instrumented, such that an update to any of them causes the predicate to be evaluated, and a toggle p to be emitted to the observer in case it has changed. The instrumentation script is written in Java (using reflection), but in essence can be represented as follows: 180

181 Figure 10.2: Events corresponding to observing predicate p x > y. monitor C.x, C.y; proposition p is C.x > C.y; The code will then be instrumented to emit changes in the predicate p. More specifically, first the initial value of the predicate is transmitted to the observer. Subsequently, whenever one of the two variables is updated, the predicate is evaluated, and in case its value has changed since last evaluation, the predicate name p is transmitted to the observer as a toggle. The observer keeps track of the value of the predicate, based on its initial value, and the subsequent predicate toggles. Figure 10.2 shows an execution trace where x and y initially are 0, and then subsequently updated. The corresponding values of p are shown. Also shown are the events that are sent to the observer. That is, the initial value of p and the subsequent p toggles Finite Trace Past Time LTL In this section we remind some basic notions of finite trace linear past time temporal logic [160, 162], establish some conventions and introduce some operators that we found particularly useful for runtime monitoring. We emphasize that the semantics of past time LTL can be elegantly defined recursively, thus allowing us to implement monitoring algorithms that only need to look one step backwards. We also show that past time LTL can be entirely defined using just the special operators, that were introduced essentially because of practical needs, thus strengthening our belief that past time LTL is an appropriate candidate logic for expressing monitoring safety requirements. 181

182 Syntax We allow the following constructors for formulae, where A is a finite set of atomic propositions : F ::= true false A F F op F (propositional operators) F F F F S s F F S w F (standard past time operators) F F [F, F ) s [F, F ) w (monitoring operators) The propositional binary operators, op, are the standard ones, that is, disjunction, conjunction, implication, equivalence, and exclusive disjunction. The standard past time and the monitoring operators are often called temporal operators, because they refer to other (past) moments in time. The operator F should be read previously F ; its intuition is that F held at the immediately previous moment in time. F should be read eventually in the past F, with the intuition that there is some past moment in time when F was true. F should be read always in the past F, with the obvious meaning. The operator F 1 S s F 2, which should be read F 1 strong since F 2, reflects the intuition that F 2 held at some moment in the past and, since then, F 1 held all the time. F 1 S w F 2 is a weak version of since, read F 1 weak since F 2, saying that either F 1 was true all the time or otherwise F 1 S s F 2. The monitoring operators,, [, ) s, and [, ) w were inspired by work in runtime verification in [158]. We found these operators often more intuitive and compact than the usual past time operators in specifying runtime requirements, despite the fact that they have the same expressive power as the standard ones, as we discovered later. The operator F should be read start F ; it says that the formula F just started to be true, that is, it was false previously but it is true now. Dually, the operator F which is read end F, carries the intuition that F ends to be true, that is, it was previously true but it is false now. The operators [F 1, F 2 ) s and [F 1, F 2 ) w are read strong/weak interval F 1, F 2 and they carry the intuition that F 1 was true at some point in the past but F 2 has not been seen to be true since then, including that moment. For example, if Start and Down are predicates on the state of a web server to be monitored, then [Start, Down) s is a property stating that the server was rebooted recently and since then it was not down, 182

183 t = true is always true, t = false is always false, t = a iff a(s n ) holds, t = F iff t = F, t = F 1 op F 2 iff t = F 1 and/or/etc. t = F 2, when op is / /etc., t = F iff t = F, where t = t n 1 if n > 1 and t = t if n = 1, t = F iff t i = F for some 1 i n, t = F iff t i = F for all 1 i n, t = F 1 S s F 2 iff t j = F 2 for some 1 j n and t i = F 1 for all j < i n, t = F 1 S w F 2 iff t = F 1 S s F 2 or t = F 1, t = F iff t = F and t n 1 = F, t = F iff t n 1 = F and t = F, t = [F 1, F 2 ) s iff t j = F 1 for some 1 j n and t i = F 2 for all j i n, t = [F 1, F 2 ) w iff t = [F 1, F 2 ) s or t = F 2. Figure 10.3: Semantics of finite trace past time LTL. while [Start, Down) w says that the server was not down recently, meaning that it was either not down at all recently or it was rebooted and since then it was not down Formal Semantics We next present formally the intuitive semantics described above. We regard a trace as a finite sequence of abstract states. In practice, these states are generated by events emitted by the program or system that we want to observe. Such events could indicate when variables values are changed or when locks are acquired or released by threads or processes, or even when a physical action takes place, such as opening or closing a valve, a gate, or a door. If s is a state and a is an atomic proposition then a(s) is true if and only if a holds in the state s. Notice that we are loose with respect to what holds means, because, depending on the context, it can mean anything. However, in the case of JPaX the atomic predicates are just any Java boolean expressions and their satisfaction is decided by evaluating them in the current state of the Java program. If t = s 1 s 2... s n (n 1) is a trace then we let t i denote the trace s 1 s 2... s i for each 1 i n. The formal semantics of the operators defined in the previous subsection is given in Figure

184 t = F iff t = F or (n > 1 and t n 1 = F ), t = F iff t = F and (n > 1 implies t n 1 = F ), t = F 1 S s F 2 iff t = F 2 or (n > 1 and t = F 1 and t n 1 = F 1 S s F 2 ), t = F 1 S w F 2 iff t = F 2 or (t = F 1 and (n > 1 implies t n 1 = F 1 S w F 2 )), t = [F 1, F 2 ) s iff t = F 2 and (t = F 1 or (n > 1 and t n 1 = [F 1, F 2 ) s )), t = [F 1, F 2 ) w iff t = F 2 and (t = F 1 or (n > 1 implies t n 1 = [F 1, F 2 ) w )). Figure 10.4: Recursive semantics of finite trace past time LTL. Notice the special semantics of the operator previously on a trace of one state: s = F iff s = F. This is consistent with the view that a trace consisting of exactly one state s is considered like a stationary infinite trace containing only the state s. We adopted this view because of intuitions related to monitoring. One can start monitoring a process potentially at any moment, so the first state in the trace might be different from the initial state of the monitored process. We think that the best guess one can have w.r.t. the past of the monitored program is that it was stationary. Alternatively, one could consider that F is false on a trace of one state for any atomic proposition F, but we find this semantics inconvenient because some atomic propositions may be related, such as, for example, a proposition gate-up and a proposition gate-down Recursive Semantics An observation of crucial importance in the design of the subsequent algorithms is that the semantics above can be defined recursively, in such a way that the satisfaction relation for a formula and a trace can be calculated along the execution trace looking only one step backwards, as shown in Figure For example, according to the formal, nonrecursive, semantics, a trace t = s 1 s 2...s n satisfies the formula [F 1, F 2 ) w if and only if either F 2 was false all the time in the past or otherwise F 1 was true at some point and since then F 2 was always false, including that moment. Therefore, in the case of a trace of size 1, i.e., when n = 1, it follows immediately that t = [F 1, F 2 ) w if and only if t = F 2. Otherwise, if the trace has more than one event then first of all t = F 2, and then either t = F 1 or else the prefix trace satisfies the interval formula, that is, t n 1 = [F 1, F 2 ) w. Similar reasoning applies to the other recurrences. 184

185 Equivalent Logics We call the past time temporal logic presented above ptltl. There is a tendency among logicians to minimize the number of operators in a given logic. For example, it is known that two operators are sufficient in propositional calculus, and two more ( next and until ) are needed for future time temporal logics. There are also various ways to minimize ptltl. Let ptltl Ops be the restriction of ptltl to the propositional operators plus the operations in Ops. Then Theorem 19 The following 12 logics are all equivalent to ptltl: 1. ptltl {,Ss}, 2. ptltl {,Sw}, 3. ptltl {,[)s}, 4. ptltl {,[)w}, 5. ptltl {,Ss}, 6. ptltl {,Sw}, 7. ptltl {,[)s}, 8. ptltl {,[)w}, 9. ptltl {,Ss}, 10. ptltl {,Sw}, 11. ptltl {,[)s}, 12. ptltl {,[)w}. The first two are known in the literature [160]. 185

186 Proof: We first show the following properties: 1. F = true S s F 2. F = F 3. F 1 S w F 2 = ( F 1 ) (F 1 S s F 2 ) 4. F = F S w false 5. F = F 6. F 1 S s F 2 = ( F 2 ) (F 1 S w F 2 ) 7. F = F F 8. F = F F 9. [F 1, F 2 ) s = F 2 (( F 2 ) S s F 1 ) 10. [F 1, F 2 ) w = F 2 (( F 2 ) S w F 1 ) 11. F = F 12. F = F 13. [F 1, F 2 ) w = ( F 2 ) [F 1, F 2 ) s 14. [F 1, F 2 ) s = ( F 1 ) [F 1, F 2 ) w 15. F = (F F ) ( F F ) 16. F 1 S s F 2 = F 2 [ F 2, F 1 ) s These properties are intuitive and relatively easy to prove. For example, property 15., the definition of F in terms of F and F, says that in order to find out the value of a formula F in the previous state it suffices to look at the value of the formula in the current state and then, if it is true then look if the formula just started to be true or else look if the formula just ended to be true. We next only prove property 10., the proofs of the others are similar and straightforward. In order to prove 10., one needs to show that for any trace t, it is the case that t = [F 1, F 2 ) w if and only if t = w F 2 (( F 2 ) S w F 1 ). We show this by induction on the size of the trace t. If the size of t is 1, that is, if t = s 1, then t = [F 1, F 2 ) w iff iff t = F 2 iff t = F 2 iff (by absorption in boolean reasoning) t = F 2 and (t = F 1 or t = F 2 ) iff t = F 2 and (t = F 1 or t = F 2 ) iff t = F 2 (( F 2 ) S w F 1 ). 186

187 If the size of the trace t is n > 1 then t = [F 1, F 2 ) w iff iff (by the recursive semantics) t = F 2 and (t = F 1 or t n 1 = [F 1, F 2 ) w ) iff (by the induction hypothesis) t = F 2 and (t = F 1 or t n 1 = F 2 (( F 2 ) S w F 1 )) iff t = F 2 and (t = F 1 or t n 1 = F 2 and t n 1 = ( F 2 ) S w F 1 ) iff t = F 2 and (t = F 1 or t = F 2 and t n 1 = ( F 2 ) S w F 1 ) iff (by the recursive semantics) t = F 2 and t = ( F 2 ) S w F 1 iff t = F 2 (( F 2 ) S w F 1 ). Therefore, [F 1, F 2 ) w = F 2 (( F 2 ) S w F 1 ). The equivalences of the 12 logics with ptltl follow now immediately. For example, in order to show the 8th logic, ptltl {,[)s}, equivalent to ptltl, one needs to show how the operators and [, ) s can define all the other past time temporal operators. This is straightforward because, 11. shows how can be defined in terms of, 15. shows how F can be defined using just and, 16. defines S s, 1. defines, 2., 3. S w, and 13. defines the weak interval. The interested reader can check the other 11 equivalences of logics. Unlike in theoretical research, in practical monitoring of programs we want to have as many temporal operators available as possible and not to automatically translate them into a reduced kernel set. The reason is twofold. On the one hand, the more operators are available, the more succinct and natural the task of writing requirement specifications. On the other hand, as seen later in the paper, additional memory is needed for each temporal operator, so we want to keep the formulae as concise as possible Monitoring Safety by Rewriting The architecture of JPaX is such that events extracted from a running program are sent to an observer which decides whether requirements are violated or not. An important concern that we had and are still having at this 187

188 relatively incipient stage of JPaX, is whether the chosen monitoring logics are expressive enough to specify powerful, practical and interesting requirements. Since flexibility with respect to defining/modifying monitoring logics is a very important factor at this stage, we have developed a rewriting-based framework which allows one to easily and effectively define new logics for runtime analysis and to monitor execution traces against formulae in these logics. We use the rewriting system Maude as a basis for this framework. In the following we first present Maude, and how formulae and important data structures are represented in Maude. Then we describe how basic propositional calculus is defined in Maude, and then how ptltl is defined. Finally, it is described how this Maude definition of ptltl is used for monitoring requirements stated by the user on execution traces Maude We have implemented our logic-defining framework by rewriting in Maude [59, 61, 62]. Maude is a modularized membership equational [174] and rewriting logic [173] specification and verification system, whose operational engine is mainly based on a very efficient implementation of rewriting. A Maude module consists of sort and operator declarations, as well as equations relating terms over the operators and universally quantified variables. Modules can be composed in a hierarchical manner, building new theories from old theories. A particular attractive aspect of Maude is its mix-fix notation for syntax, which, together with precedence attributes of operators gives us an elegant way to compactly define syntax of logics. For example, the operation declarations 2 : op _/\_ : Expression Expression -> Expression [prec 33]. op _\/_ : Expression Expression -> Expression [prec 40]. op [_,_} : Expression Expression -> Expression. op if_then_else_ : Bool Expression Expression -> Expression. define a simple syntax over the sort Expression, where conjunction and disjunction are infix operators (the underscores stand for arguments, whose 2 These declarations are artificial and intended to explain some of Maude s features; they will not be needed later in the paper. 188

189 sorts are listed after the colon), while the interval and the conditional are mixfix: operator and arguments can be mixed. Conjunction binds tighter than disjunction because it has a lower precedence (the lower the precedence the tighter the binding), so one is relieved from having to add useless parentheses to one s formulae. It is often the case that equational and/or rewriting logics act like foundational logics, in the sense that other logics, or more precisely their syntax and operational semantics, can be expressed and efficiently executed by rewriting, so we regard Maude as a good choice to develop and prototype with various monitoring logics. The Maude implementations of the current logics supported by JPaX are quite compact. They are based on a simple, general architecture to define new logics which we only describe informally in the next subsection. Maude s notation will be introduced on the fly as needed Formulae and Data Structures We have defined a generic module, called FORMULA, which defines the infrastructure for all the user-defined logics. Its Maude code is rather technical and so will not be given here. The module FORMULA includes some designated basic sorts, such as Formula for syntactic formulae, FormulaDS for formula data structures needed when more information than the formula itself should be stored for the next transition as in the case of past time LTL, Atom for atomic propositions (or state variables), AtomState for assignments of boolean values to atoms, also called states, and AtomState* for such assignments together with final assignments, i.e., those that are followed by the end of a trace, sometimes requiring a special evaluation procedure. A state As is made terminal by applying it a unary operator, * : AtomState -> AtomState*. Formula is a subsort of FormulaDS, because there are logics in which no extra information but a modified formula needs to be carried over for the next iteration (such as future time LTL which is also provided by JPaX). There are two constants of sort Formula provided, namely true and false, with the obvious meaning. The propositions that hold in a certain program state are generated by the executing instrumented program. One of the most important operators in FORMULA is { }:FormulaDS AtomState* -> FormulaDS, which updates the formula data structure when an (abstract) state change occurs during the execution of the program. Notice the use of mix-fix notation for operator declaration, in which underscores represent places of arguments, their order being the one in the arity of the operator. 189

190 On atomic propositions, say A, the module FORMULA defines the update operator as follows: A{As*} is true or false, depending on whether As* assigns true or false to the atom A, where As* is an atom state (i.e., an assignment from atoms to boolean values), which is either a terminal state (the last in a trace) or not. In the case of propositional calculus, this update operation basically evaluates propositions in the new state. For other logics it can be more complicated, depending on their trace semantics Propositional Calculus Propositional calculus should be included in any monitoring logic. Therefore, we begin with the following module which is heavily used in JPaX. It implements an efficient rewriting procedure due to Hsiang [129] to decide validity of propositions, reducing any boolean expression to an exclusive disjunction (formally written _++_) of conjunctions (_/\_): fmod PROP-CALC is extending FORMULA. *** Constructors *** op _/\_ : Formula Formula -> Formula [assoc comm]. op _++_ : Formula Formula -> Formula [assoc comm]. vars X Y Z : Formula. var As* : AtomState*. eq true /\ X = X. eq false /\ X = false. eq false ++ X = X. eq X ++ X = false. eq X /\ X = X. eq X /\ (Y ++ Z) = (X /\ Y) ++ (X /\ Z). *** Derived operators *** op _\/_ : Formula Formula -> Formula. op _->_ : Formula Formula -> Formula. op _<->_ : Formula Formula -> Formula. op!_ : Formula -> Formula. eq X \/ Y = (X /\ Y) ++ X ++ Y. eq! X = true ++ X. eq X -> Y = true ++ X ++ (X /\ Y). eq X <-> Y = true ++ X ++ Y. *** Operational Semantics 190

191 eq (X /\ Y){As*} = X{As*} /\ Y{As*}. eq (X ++ Y){As*} = X{As*} ++ Y{As*} endfm In Maude, operators are introduced after the op and ops (when more than one operator is introduced) symbols. Operators can be given attributes in square brackets, such as associativity and commutativity. Universally quantified variables used in equations are introduced after the var and vars symbols. Finally, equations are introduced after the eq symbol. The specification of the simple propositional calculus above shows the flexibility of the mix-fix notation of Maude, which allows us to define the syntax of a logic in the most natural way. The equations above are interpreted as rewriting rules by Maude, so they will be applied from left to right only. However, due to the associativity and commutativity attributes, rewrites as well as matchings are applied modulo associativity and commutativity (AC), making therefore the procedure implied by the rewrite rules for propositional calculus above highly non-trivial. As proved by Hsiang [129], the AC rewriting system above has the property that any proposition is reduced to true or false if it is semantically true or false, or otherwise to a canonical form modulo AC; thus two formulae are equivalent if and only if their canonical forms are equal modulo AC. We found this procedure quite convenient so far, being able to efficiently reduce formulae of hundreds of symbols that occurred in practical examples. However, one should of course not expect this procedure to work efficiently on any proposition, because the propositional validity problem is NP-complete Past Time Linear Temporal Logic Past time LTL can now be implemented on top of the provided logic-defining framework. Our rewriting based implementation below follows the recursive semantics of past time LTL defined in Subsection , and, it appears similar to the Java implementation used in [158]. We next explain the PT-LTL module in detail. We start by defining the syntax of past time LTL. Since it extends the module PROP-CALC of propositional calculus, we only have to define syntax for the temporal operators: fmod PT-LTL is extending PROP-CALC. op (*)_ : Formula -> Formula. 191

192 *** previously op <*>_ : Formula -> Formula. *** eventually in the past op [*]_ : Formula -> Formula. *** always in the past op _Ss_ : Formula Formula -> Formula. *** strong since op _Sw_ : Formula Formula -> Formula. *** weak since op start : Formula -> Formula. *** start op end : Formula -> Formula. *** end op [_,_}s : Formula Formula -> Formula. *** strong interval op [_,_}w : Formula Formula -> Formula. *** weak interval We have used a curly bracket to close the intervals because, for some technical parsing related reasons, Maude does not allow unbalanced parentheses in its terms. The syntax above can now be used by users to write monitoring requirements as formulae. These formulae are loaded by JPaX at initialization and then sent to Maude for parsing and processing. When the first event from the instrumented program is received by JPaX, it sends this event to Maude in order to initialize its monitoring data structures associated to its formulae (remember that the recursive definition of past time LTL in Subsection treats the first event of the trace differently). This is done by launching the reduction mkds(f, As) in Maude, where F is the formula to monitor and As is the atom state abstracting the first event generated by the monitored program; mkds is an abbreviation for make data structure and is defined below. Before we define the operation mkds, we first discuss the formula data structures storing not only the formulae but also their current satisfaction status. It is worth noticing that the strong and weak temporal operators have exactly the same recursive semantics starting with the second event. That suggests that we do not need nodes of different type (strong and weak) in the formula data structure once the monitoring process is initialized: the difference between strong and weak versions of an operator are rather represented by the initial values passed as arguments to a single common version of the operator. The following operation declarations therefore define the constructors for these data structures: 192

193 op atom : Atom Bool -> FormulaDS. op and : FormulaDS FormulaDS Bool -> FormulaDS. op xor : FormulaDS FormulaDS Bool -> FormulaDS. op previously : FormulaDS Bool -> FormulaDS. op eventuallypast : FormulaDS Bool -> FormulaDS. op alwayspast : FormulaDS Bool -> FormulaDS. op since : FormulaDS FormulaDS Bool -> FormulaDS. op start : FormulaDS Bool -> FormulaDS. op end : FormulaDS Bool -> FormulaDS. op interval: FormulaDS FormulaDS Bool -> FormulaDS. The first operation defines a cell storing an atomic proposition together with its observed boolean value, while the next two store conjunction and exclusive disjunction nodes. According to the propositional calculus procedure defined in module PROP-CALC in Subsection , these are the only propositional operators that can occur in reduced formulae. The remaining operators are the seven past time temporal operators introduced so far. An operator that extracts the boolean value associated to a temporal formula is needed in the sequel, so we define it next. The syntax of this operator is [ ] : FormulaDS -> Bool and it is defined in the module FORMULA, together with its obvious equations [true] = true and [false] = false. Its definition on temporal and propositional and temporal operators follows: var A : Atom. var B : Bool. vars D Dx Dy : FormulaDS. eq [and(dx,dy,b)] = B. eq [xor(dx,dy,b)] = B. eq [atom(a,b)] = B. eq [previously(d,b)] = B. eq [eventuallypast(d,b)] = B. eq [alwayspast(d,b)] = B. eq [since(dx,dy,b)] = B. eq [interval(dx,dy,b)] = B. eq [start(dx,b)] = B. eq [end(dx,b)] = B. The operation mkds can be defined now. It basically follows the recursive semantics in Subsection , when the length of the trace is 1: vars X Y : Formula. op mkds : Formula AtomState -> FormulaDS. eq mkds(true, As) = true. 193

194 eq mkds(false, As) = false. eq mkds(a, As) = atom(a, (A{As} == true)). eq mkds(x /\ Y, As) = and(mkds(x,as), mkds(y,as), [mkds(x,as)] and [mkds(x,as)]). eq mkds(x ++ Y, As) = xor(mkds(x,as), mkds(y,as), [mkds(x,as)] xor [mkds(x,as)]). eq mkds( (*)X, As) = previously(mkds(x, As), [mkds(x, As)]). eq mkds( <*>X, As) = eventuallypast(mkds(x, As), [mkds(x, As)]). eq mkds( [*]X, As) = alwayspast(mkds(x, As), [mkds(x, As)]). eq mkds(x Ss Y, As) = since(mkds(x,as), mkds(y,as), [mkds(y,as)]). eq mkds(x Sw Y, As) = since(mkds(x,as), mkds(y,as), [mkds(x,as)] or [mkds(y,as)]). eq mkds(start(x), As) = start(mkds(x,as),false). eq mkds(end(x), As) = end(mkds(x,as), false). eq mkds([x,y}s, As) = interval(mkds(x,as), mkds(y,as), [mkds(y,as)] and not [mkds(y,as)]). eq mkds([x,y}w, As) = interval(mkds(x,as), mkds(y,as), not [mkds(y,as)]). The data structure associated to a past time formula is essentially its syntax tree augmented with a boolean bit for each node. Each boolean bit will store the result of the satisfaction relation between the current execution trace and the corresponding subformula. The only thing left is to define how the formula data structures, or more precisely their bits, modify when a new event is received. This is defined below, using the operator { } : FormulaDS AtomState -> FormulaDS provided by the module Formula: eq atom(a, B){As} = atom(a, (A{As} == true)). eq and(dx, Dy, B){As} = and(dx{as}, Dy{As},[Dx{As}] and [Dy{As}]). eq xor(dx, Dy, B){As} = xor(dx{as}, Dy{As},[Dx{As}] xor [Dy{As}]). eq previously(d,b){as} = previously(d{as},[d]). eq eventuallypast(d, B){As} = 194

195 eventuallypast(d{as}, [D{As}] or B). eq alwayspast(d, B){As} = alwayspast(d{as}, [D{As}] and B). eq since(dx, Dy, B){As} = since(dx{as}, Dy{As}, [Dy{As}] or [Dx{As}] and B). eq start(dx,b){as} = start(dx{as}, [Dx{As}] and not B). eq end(dx,b){as} = end(dx{as}, not [Dx{As}] and B). eq interval(dx, Dy, B){As} = interval(dx{as}, Dy{As}, not [Dy{As}] and ([Dx{As}] or B)). endfm The operator _==_ is built-in and takes two terms of same sort, reduces them to their normal forms, and then returns true if they are equal and false otherwise Monitoring with Maude In this subsection we give more details on how the actual rewriting based monitoring process works. When the JPaX system is started, the user is supposed to have already specified several formulae in a file containing monitoring requirements. The first thing JPaX does is to start a Maude process, load the past time LTL semantics described above, and then set Maude to run in its loop mode, which is an execution mode in which Maude maintains a state term which the user (potentially another process, such as JPaX) can modify interactively. Then JPaX sends Maude all the requirement formulae that the user wants to monitor. Maude stores them in its loop state and waits for JPaX to send events. Notice that the above is general and applies to any logic. When JPaX receives the first event from the instrumented program that is relevant for the past time LTL analysis module, it just sends it to Maude. On receiving the first event, say As, Maude needs to generate the formula data structures for all the formulae to be monitored. It does so by replacing each formula F in the loop state by the normal form of the term mkds(f, As). Then it waits for JPaX to submit further events. Each time a new relevant event As is received by JPaX from the instrumented program, it just forwards it to Maude. Then Maude replaces each formula data structure D in its loop state by D{As} and then waits for further events. 195

196 If at any moment [D] is false for the data structure D associated to a formula F, then Maude sends an error message to JPaX, which further warns the user appropriately. It should be obvious that the runtime complexity of the rewriting monitoring algorithm is O(m) to process an event, where m is the size of the past time LTL formula to monitor. That is, the algorithm only needs to traverse the data structure representing the formula bottom-up for each new event, and update one bit in each node. So the overall runtime complexity is O(n m), where n is the number of events to be monitored. This is the best one can asymptotically hope from a runtime monitoring algorithm, but of course, there is room for even faster algorithms in practical situations, as the one presented in the next section. The main benefit of the rewriting algorithm presented in this section is that it falls under the general framework by which one can easily add or experiment with new monitoring logics within the JPaX system. The Maude code performing the above steps is relatively straightforward but rather ugly, so we prefer not to present it here. Additionally, Maude s support for inter-process communication is planned to be changed soon, so this code would become soon obsolete Synthesizing Monitors for Safety Properties The rewriting algorithm above is a very good choice in the context of the current version of JPaX, because it gives us flexibility and is efficient enough to process events at a faster rate than they can actually be sent by JPaX. However, there might be situations in which a full scale AC rewriting engine like Maude is not available, such as within an embedded system, or in which as little runtime overhead as possible is allowed, such as in real time applications. In this section we present a dynamic programming based algorithm, also based on the recursive semantics of past time LTL in Subsection , which takes as input a formula and generates source code which can further be compiled into an efficient executable monitor for that formula. This algorithm can be used in two different ways. On the one hand, it can be used as an efficient external monitor to take an action when a formula is violated, such as to report an error to a user, to reboot the system, to send a message, or even to generate a correcting task. On the other hand, it can be used in a context in which one allows past time LTL annotations in the source code of a program, where the logical annotations 196

197 can be expanded into source code which is further compiled together with the original program. These two use modes, offline versus inline, are further explained in Subsection The Algorithm Illustrated by an Example In this section we show via an example how to generate dynamic programming code for a concrete ptltl-formula. We think that this example would practically be sufficient for the reader to foresee our general algorithm presented in the next subsection. Let p [q, (r s)) s be the ptltlformula that we want to generate code for. The formula states: whenever p becomes true, then q has been true in the past, and since then we have not yet seen the end of r or s. The code translation depends on an enumeration of the subformulae of the formula that satisfies the enumeration invariant: any formula has an enumeration number smaller than the numbers of all its subformulae. Let ϕ 0, ϕ 1,..., ϕ 8 be such an enumeration: ϕ 0 = p [q, (r s)) s, ϕ 1 = p, ϕ 2 = p, ϕ 3 = [q, (r s)) s, ϕ 4 = q, ϕ 5 = (r s), ϕ 6 = r s, ϕ 7 = r, ϕ 8 = s. Note that the formulae have here been enumerated in a post-order fashion. One could have chosen a breadth-first order, or any other enumeration, as long as the enumeration invariant is true. The input to the generated program will be a finite trace t = s 1 s 2...s n of n events. The generated program will maintain a state via a function update : State Event State, which updates the state with a given event. In order to illustrate the dynamic programming aspect of the solution, one can imagine recursively defining a matrix s[1..n, 0..8] of boolean values {0, 1}, with the meaning that s[i, j] = 1 iff t i = ϕ j. Then one can fill the table according to the recursive semantics of past time LTL as described in Subsection This would be the standard way of regarding the above satisfaction problem as a dynamic programming problem. An important 197

198 observation is, however, that, like in many other dynamic programming algorithms, one doesn t have to store the entire table s[1..n, 0..8], which would be quite large in practice; in this case, one needs only s[i, 0..8] and s[i 1, 0..8], which we ll write now[0..8] and pre[0..8] from now on, respectively. It is now only a relatively simple exercise to write up the following algorithm for checking the above formula on a finite trace: State state {}; bit pre[0..8]; bit now[0..8]; Input: trace t = s 1 s 2...s n ; /* Initialization of state and pre */ state update(state, s 1 ); pre[8] s(state); pre[7] r(state); pre[6] pre[7] or pre[8]; pre[5] false; pre[4] q(state); pre[3] pre[4] and not pre[5]; pre[2] p(state); pre[1] false; pre[0] not pre[1] or pre[3]; /* Event interpretation loop */ for i = 2 to n do { state update(state, s i ); now[8] s(state); now[7] r(state); 198

199 now[6] now[7] or now[8]; now[5] not now[6] and pre[6]; now[4] q(state); now[3] (pre[3] or now[4]) and not now[5]; now[2] p(state); now[1] now[2] and not pre[2]; now[0] not now[1] or now[3]; if now[0] = 0 then output( property violated ); pre now; }; In the following we explain the generated program. Declarations Initially a state is declared. This will be updated as the input event list is processed. Next, the two arrays pre and now are declared. The pre array will contain values of all subformulae in the previous state, while now will contain the value of all subformulae in the current state. Initialization The initialization phase consists of initializing the state variable and the pre array. The first event s 1 of the event list is used to initialize the state variable. The pre array is initialized by evaluating all subformulae bottom up, starting with highest formula numbers, and assigning these values to the corresponding elements of the pre array; hence, for any i {0... 8} pre[i] is assigned the initial value of formula ϕ i. The pre array is initialized in such a way as to maintain the view that the initial state is supposed stationary before monitoring is started. This in particular means that p is false, as well as is (r s), since there is no change in state (indices 1 and 5). The interval operator has the obvious initial interpretation: the first argument must be true and the second false for the formula to be true (index 3). Propositions are true if they hold in the initial state (indices 2, 4, 7 and 8), and boolean operators are interpreted the standard way (indices 0, 6). 199

200 Event Loop The main evaluation loop goes through the event trace, starting from the second event. For each such event, the state is updated, followed by assignments to the now array in a bottom-up fashion similar to the initialization of the pre array: the array elements are assigned values from higher index values to lower index values, corresponding to the values of the corresponding subformulae. Propositional boolean operators are interpreted the standard way (indices 0 and 6). The formula p is true if p is true now and not true in the previous state (index 1). Similarly with the formula (r s) (index 5). The formula [q, (r s)) s is true if either the formula was true in the previous state, or q is true in the current state, and in addition (r s) is not true in the current state (index 3). At the end of the loop an error message is issued if now[0], the value of the whole formula, has the value 0 in the current state. Finally, the entire now array is copied into pre. Given a fixed ptltl formula, the analysis of this algorithm is straightforward. Its time complexity is Θ(n) where n is the length of the input trace, the constant being given by the size of the ptltl formula. The memory required is constant, since the length of the two arrays is the size of the ptltl formula. However, one may want to also include the size of the formula, say m, into the analysis; then the time complexity is obviously Θ(n m) while memory required is 2 (m + 1) bits. The authors conjecture that it s hard to find an algorithm running faster than the above in practical situations, though some slight optimizations are possible (see Section ) The Algorithm Formalized We now formally describe our algorithm that synthesizes a dynamic programming algorithm from a ptltl-formula. It takes as input a formula and generates a program as the one above, containing a for loop which traverses the trace of events, while validating or invalidating the formula. The generated program is printed using the function output, which takes one or more string or integer parameters which are concatenated in the output. This algorithm is designed to generate pseudocode, but it can easily be adapted to generate code in any imperative programming language: Input: past time LTL formula ϕ let ϕ 0, ϕ 1,..., ϕ m be the subformulae of ϕ; 200

201 output( State state {}; ); output( bit pre[0..m]; ); output( bit now[0..m]; ); output( Input: trace t = s 1 s 2...s n ; ); output( /* Initialization of state and pre */ ); output( state update(state, s 1 ); ); for j = m downto 0 do { output( pre[, j, ] ); if ϕ j is a variable then output(ϕ j, (state); ); if ϕ j is true then output( true; ); if ϕ j is false then output( false; ); if ϕ j = ϕ j then output( not pre[,j, ]; ); if ϕ j = ϕ j1 op ϕ j2 then output( pre[,j 1, ] op pre[,j 2, ]; ); if ϕ j = ϕ j1 then output( pre[, j 1, ]; ); if ϕ j = ϕ j1 then output( pre[, j 1, ]; ); if ϕ j = ϕ j1 then output( pre[, j 1, ]; ); if ϕ j = ϕ j1 S s ϕ j2 then output( pre[, j 2, ]; ); 201

202 if ϕ j = ϕ j1 S w ϕ j2 then output( pre[,j 1, ] or pre[, j 2, ]; ); if ϕ j = [ϕ j1, ϕ j2 ) s then output( pre[,j 1, ] and not pre[, j 2, ]; ); if ϕ j = [ϕ j1, ϕ j2 ) w then output( not pre[, j 2, ]; ); if ϕ j = ϕ j if ϕ j = ϕ j then output( false; ); then output( false; ); }; output( /* Event interpretation loop */ ); output( for i = 2 to n do { ); for j = m downto 0 do { output( now[, j, ] ); if ϕ j is a variable then output(ϕ j, (state); ); if ϕ j is true then output( true; ); if ϕ j is false then output( false; ); if ϕ j = ϕ j then output( not now[,j, ]; ); if ϕ j = ϕ j1 op ϕ j2 then output( now[,j 1, ] op now[, j 2, ]; ); if ϕ j = ϕ j1 then output( pre[, j 1, ]; ); if ϕ j = ϕ j1 then output( pre[, j, ] or now[,j 1, ] ); if ϕ j = ϕ j1 then output( pre[, j, ] and now[,j 1, ] ); 202

203 if ϕ j = ϕ j1 S s ϕ j2 then output( (pre[, j, ] and now[,j 1, ]) or now[, j 2, ]; ); if ϕ j = ϕ j1 S w ϕ j2 then output( (pre[, j, ] and now[,j 1, ]) or now[, j 2, ]; ); if ϕ j = [ϕ j1, ϕ j2 ) s then output( (pre[, j, ] or now[,j 1, ]) and not now[, j 2, ]; ); if ϕ j = [ϕ j1, ϕ j2 ) w then output( (pre[, j, ] or now[,j 1, ]) and not now[, j 2, ]; ); if ϕ j = ϕ j then output( now[, j, ] and not pre[, j, ]; ); if ϕ j = ϕ j then output( not now[, j, ] and pre[, j, ]; ); }; output( if now[0] = 0 then output( property violated ); ); output( pre now; ); output( } ); op is any binary propositional connective. Since we have already given a detailed explanation of the example in the previous section, we shall only give a very brief description of this algorithm. The formula should be first visited top down to assign increasing numbers to subformulae as they are visited. Let ϕ 0, ϕ 1,..., ϕ m be the list of all 203

204 subformulae. Because of the recursive nature of ptltl, this step ensures us that the truth value of t i = ϕ j can be completely determined from the truth values of t i = ϕ j for all j < j m and the truth values of t i 1 = ϕ j for all j j m. Before we generate the main loop, we should first generate code for initializing the array pre[0..m], basically giving it the truth values of the subformulae on the initial state, conceptually being an infinite trace with repeated occurrences of the initial state. After that, the generated main event loop will process the events. The loop body will update/calculate the array now and in the end will move it into the array pre to serve as basis for the next iteration. After each iteration i, now[0] tells whether the formula is validated by the trace s 1 s 2...s i. Since the formula enumeration procedure is linear, the algorithm synthesizes a dynamic programming algorithm from an ptltl formula in linear time with the size of the formula. The boolean operations used above are usually very efficiently implemented on any microprocessor and the arrays of bits pre 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. We shall next illustrate how such optimizations can be part of the translation algorithm Optimizing the Generated Code The generated code presented in Subsection is not optimal. Even though a smart compiler can in principle generate good machine code from it, it is still worth exploring ways to synthesize directly optimized code especially because there are some attributes that are specific to the runtime observer which a compiler cannot take into consideration. A first observation is that not all the bits in pre are needed, but only those which are used at the next iteration, namely 2, 3, and 6. Therefore, only a bit per temporal operator is needed, thereby reducing significantly the memory required by the generated algorithm. Then the body of the generated for loop becomes after (blind) substitution (we don t consider the initialization code here): state update(state, s i ) now[3] r(state) or s(state) 204

205 now[2] (pre[2] or q(state)) and now[1] p(state) not (not now[3] and pre[3]) if ((not (now[1] and not pre[1]) or now[2]) = 0) then output( property violated ); that can be further optimized by boolean simplifications: state update(state, s i ) now[3] r(state) or s(state) now[2] (pre[2] or q(state)) and now[1] p(state) (now[3] or not pre[3]) if (now[1] and not pre[1] and not now[2]) then output( property violated ); The most expensive part of the code above is the function calls, namely p(state), q(state), r(state), and s(state). Depending upon the runtime requirements, the execution time of these functions may vary significantly. However, since one of the major concerns of monitoring is to affect the normal execution of the monitored program as little as possible, especially in the inline monitoring approach, one would of course want to evaluate the atomic predicates on states only if really needed, or rather to evaluate only those that, probabilistically, add a minimum cost. Since we don t want to count on an optimizing compiler, we prefer to store the boolean formula as some kind of binary decision diagram, more precisely, as a term over the conditional operation? :, where e 1?e 2 : e 3 means: if e 1 then e 2 else e 3. For example, pre[3]? pre[2]? now[3] : q(state) : pre[2]? 1 : q(state) (see [106] for a formal definition). Therefore, one is faced with the following optimization problem: Given a boolean formula ϕ using propositions a 1, a 2,..., a n of costs c 1, c 2,..., c n, respectively, find a (? : )-expression that optimally implements ϕ. 205

206 We have implemented a procedure in Maude, on top of propositional calculus, which generates all correct (? : )-expressions for ϕ, admittedly a potentially exponential number in the number of distinct atomic propositions in ϕ, and then chooses the shortest in size, ignoring the costs. Applied on the code above, it yields: state update(state, s i ) now[3] r(state)? 1 : s(state) now[2] pre[3]? pre[2]? now[3] : now[1] p(state) q(state) : pre[2]? 1 : q(state) if (pre[1]? 0 : now[2]? 0 : now[1 ]) then output( property violated ); We would like to extend our procedure to take the evaluation costs of predicates into consideration. These costs can either be provided by the user of the system or be calculated automatically by a static analysis of predicates code, or even be estimated by executing the predicates on a sample of states. However, based on our examples so far, we conjecture at this incipient stage that, given a boolean formula ϕ in which all the atomic propositions have the same cost, the probabilistically runtime optimal (? : )-expression implementing ϕ is exactly the one which is smallest in size. A further optimization would be to generate directly machine code instead of using a compiler. Then the arrays of bits now and pre can be stored in two registers, which would be all the memory needed. Since all the operations executed are bit operations, the generated code is expected to be very fast. One could even imagine hardware implementations of past time monitors, using the same ideas, in order to enforce safety requirements on physical devices Implementation of Offline and Inline Monitoring In this section we briefly describe our efforts to implement the above described algorithm to create monitors for observing the execution of Java programs in PathExplorer. We present two approaches that we have pursued. In the off-line approach we create a monitor that runs in parallel with the 206

207 executing program, potentially on a different computer, receiving events from the running program, and checking on-the-fly that the formulae are satisfied. In this approach the formulae to be checked are given in a separate specification. In the inline approach, formulae are written as comments in the program text, and are then expanded into Java code that is inserted after the comments. Offline Monitoring The code generator for off-line monitoring has been written in Java, using JavaCC [135], an environment for writing parsers and for generating and manipulating abstract syntax trees. The input to the code generator is a specification given in a file separate from the program. The specification for our example looks as follows (the default interpretation of intervals is strong ): specification Example is P = start(p) -> [q,end(r s)); end Several named formulae can be listed; here we have only included one, named P. The translator reads this specification and generates a single Java class, called Formulae, which contains all the machinery for evaluating all the formulae (in this case one) in the specification. This class must then be compiled and instantiated as part of the monitor. The class contains an evaluate() method which is applied after each state change and which will evaluate all the formulae. The class constructor takes as parameter a reference to the object that represents the state, such that any updates to the state by the monitor, based on received events, can be seen by the evaluate() method. The generated Formulae class for the above specification looks as follows: class Formulae{ abstract class Formula{ protected String name; protected State state; protected boolean[] pre; protected boolean[] now; public Formula(String name,state state){ this.name = name; this.state = state; } public String getname(){return name;} public abstract boolean evaluate(); } private List formulae = new ArrayList(); public void evaluate(){ 207

208 Iterator it = formulae.iterator(); while(it.hasnext()){ Formula formula = (Formula)it.next(); if(!formula.evaluate()){ System.out.println("Property " + formula.getname() + " violated"); }}} class Formula_P extends Formula{ public boolean evaluate(){ now[8] = state.holds("s"); now[7] = state.holds("r"); now[6] = now[7] now[8]; now[5] =!now[6] && pre[6]; now[4] = state.holds("q"); now[3] = (pre[3] now[4]) &&!now[5]; now[2] = state.holds("p"); now[1] = now[2] &&!pre[2]; now[0] =!now[1] now[3]; System.arraycopy(now,0,pre,0,9); return now[0]; } public Formula_P(State state){ super("p",state); pre = new boolean[9]; now = new boolean[9]; pre[8] = state.holds("s"); pre[7] = state.holds("r"); pre[6] = pre[7] pre[8]; pre[5] = false; pre[4] = state.holds("q"); pre[3] = pre[4] &&!pre[5]; pre[2] = state.holds("p"); pre[1] = false; pre[0] =!pre[1] pre[3]; } } public Formulae(State state){ formulae.add(new Formula_P(state)); } } The class contains an inner abstract 3 class Formula and, in the general case, an inner class Formula X extending the Formula class for each formula in the specification, where X is the formula s name. In our case there is one such Formula P class. The abstract Formula class declares the pre and now arrays, without giving them any size, since this is formula specific. An abstract evaluate method is also declared. The class Formula P contains the real definition of this evaluate() method. The constructor for this class in addition initializes the sizes of pre and now depending on the size of the 3 An abstract class is a class where some methods are abstract, by having no body. Implementations for these methods will be provided in extending subclasses. 208

209 formula, and also initializes the pre array. In order to handle the general case where several formulae occur in the specification, and hence many Formula X classes are defined, we need to create instances for all these classes and store them in some data structure where they can be accessed by the outermost evaluate() method. The formulae list variable is initialized to contain all these instances when the constructor of the Formulae class is called. The outermost evaluate() method, on each invocation, goes through this list and calls evaluate() on each single formula object. Inline Monitoring The general architecture of PaX was mainly designed for offline monitoring in order to accommodate applications where the source code is not available or where the monitored process is not even a program, but some kind of physical device. However, it is often the case that the source code of an application is available and that one is willing to accept extra code for testing purposes. Inline monitoring has actually higher precision because one knows exactly where an event was emitted in the execution of the program. Moreover, one can even throw exceptions when a safety property is violated, like in Temporal Rover [73], so the running program has the possibility to recover from an erroneous execution or to guide its execution in order to avoid undesired behaviors. In order to provide support for inline monitoring, we developed some simple scripts that replace temporal annotations in Java source code by actual monitoring code, which throws an exception when the formula is violated. In [112] we show an example of expanded code for future time LTL. The for loop and the update of the state in the generic algorithm in Section are not needed anymore because the atomic predicates use directly the current state of the program when the expanded code is reached during the execution. In [46] the tool Java-MoP is described, which implements the presented algorithm as a logic plug-in for inline monitoring (as well as for off-line monitoring). The following code snippets illustrate the inline approach. Assume a class A, that defines four integer variables and a method m, which contains the past time temporal logic formula from above. Now the propositions p, q, r and s are defined to refer to the four variables. The intention is that whenever the program point of the comment is reached, the formula will be evaluated. 209

210 class A{ int a,b,c,d; void m(){... proposition p = a>0; proposition q = b>0; proposition r = c>0; proposition s = d>0; property P = start(p) -> [q,end(r s)); */... } } This class is now automatically translated into the following, where code representing the semantics of the formula has been inserted at the position of the formula comment, and in the constructor: class A{ int a,b,c,d; boolean[] pre = new boolean[9]; boolean[] now = new boolean[9]; public A(){ pre[8] = d>0; pre[7] = c>0; pre[6] = pre[7] pre[8]; pre[5] = false; pre[4] = b>0; pre[3] = pre[4] &&!pre[5]; pre[2] = a>0; pre[1] = false; pre[0] =!pre[1] pre[3]; } void m(){... now[8] = d>0; now[7] = c>0; now[6] = now[7] now[8]; now[5] =!now[6] && pre[6]; now[4] = b>0; now[3] = (pre[3] now[4]) &&!now[5]; now[2] = a>0; now[1] = now[2] &&!pre[2]; now[0] =!now[1] now[3]; System.arraycopy(now,0,pre,0,9); if(!now[0])throw Violated("P");... } } 210

211 It is essentially the same code as in the offline case, except that the looping constructs have been removed. It is inline monitoring that motivated us to optimize the generated code as much as possible as in Subsection Since the running program and the monitor are a single process now, the time needed to execute the monitoring code can significantly influence the otherwise normal execution of the monitored program Conclusion Two efficient algorithms for monitoring safety requirements expressed using past time linear temporal logic were presented, one based on rewriting and implemented in Maude, and the other based on dynamic programming, synthesizing specialized monitors from formulae. They both check that a finite sequence of events emitted by a running program satisfies a formula. Operators convenient for monitoring were considered and shown equivalent to standard past time temporal operators. These algorithms have been implemented in PathExplorer, a runtime verification tool currently under development. The synthesis algorithm has also been implemented (as a plug-in) in the Java-MoP tool [46], which is a general framework for supporting program monitoring for user provided logics; and in the JMPaX tool [208], which extends part of this work to partial order models instead of simple traces. It is our intention to investigate how the presented algorithms can be refined to work for a logic that combines past and future time temporal logic and that can refer to real-time and data values. Other kinds of runtime verification are also investigated, such as, for example, techniques for detecting error potentials in multi-threaded programs. Recent work on detecting high-level data races is described in [14]. A number of experiments have been carried out with PathExplorer on a planetary rover application written in 35,000 lines of C++. The experiments range from concurrency analysis (deadlock and data race analysis) to monitoring of temporal logic formulae combined with test case generation, as described in [15]. A model checker is used to generate test cases, where a test case consists of input to the application plus a set of temporal formulae that the execution of the application on that input must satisfy. When running this testing environment, hundreds of test cases are generated and the execution of these are monitored against the generated formulae. Initial 211

212 experiments have been made with a logic that combines past time and future time temporal logic and supports real-time and data reasoning. A bug was detected in the rover application in the very first such experiment we made. A thread did not detect a premature termination of a certain task in a timely manner. The programmer had forgotten to insert this termination check and was reminded by a single run of the testing environment. This testing environment is planned to become part of the rover application programmer s testing toolbox. new stuff below 10.7 Optimal Monitoring of Always Past Temporal Safety A monitor synthesis algorithm from linear temporal logic (LTL) safety formulae of the form ϕ where ϕ is a past time LTL formula was presented in [109]. The generated monitors implemented the recursive semantics of past-time LTL using a dynamic programming technique, and needed O( ϕ ) time to process each new event and O( ϕ ) total space. Some compiler-like optimizations of the generated monitors were also proposed in [109], which would further reduce the required space. It is not clear how much the required space could be reduced by applying those optimizations. We here show how to generate using a divide-and-conquer technique directly monitors that need O(k) space and still O( ϕ ) time, where k is the number of temporal operators in ϕ The Monitor Synthesis Algorithm For simplicity, we assume only two past operators, namely (previously) and S (since). Let us first note that one cannot asymptotically reduce the space requirements below Ω(k), where k is the number of temporal operators appearing in the formula to monitor ϕ. Indeed, one can take ϕ = (# 1 t 1 ) (# k t k ), where for each 1 i k, # i is some event and t i is some temporal formula containing precisely one past temporal operator, i.e., a or a S. Any monitor for ϕ must directly or indirectly store the status of each t i at every event, to be able to react accordingly in case the next event is some # i. Assuming that the events # i are distinct 212

213 and that the formulae t i are unrelated, then the monitor needs to distinguish among 2 k possible states, so it needs Ω(k) space. In what follows, we assume the usual recursive semantics of LTL, also presented below, restricted to safety formulae of the form ϕ, where ϕ is a past-time LTL. We adopt the simplifying assumption that the empty trace invalidates any atomic proposition and any past temporal operator; as argued in [109], this may not always be the best choice, but other semantic variations regarding the empty trace present no difficulties for monitoring. Definition 45 (adapted from [161]) LTL formulae of the form ϕ (read always ϕ ), where ϕ is a past-time LTL formula, are called LTL safety formulae; we may call them just safety formulae when LTL is understood from the context. An infinite trace u Σ ω satisfies ϕ, written u = ϕ, iff each w prefixes(u) satisfies the past-time LTL formula ϕ, written also w = ϕ and defined inductively as follows: w = true is always true, ws = a iff a(s) holds, w = ϕ iff w = ϕ, w = ϕ 1 ϕ 2 iff w = ϕ 1 and w = ϕ 2, ws = ϕ iff w = ϕ, ws = ϕ 1 S ϕ 2 iff ws = ϕ 2 or ws = F ϕ and w = ϕ 1 S ϕ 2 ɛ = ϕ is false otherwise Given safety formula ϕ, we let L( ϕ) Σ ω be the set {u Σ ω u = ϕ}. Proposition 16 L( ϕ) Safety ω for any past-time LTL formula ϕ. Proof: By the definition of L( ϕ) in Definition 45 and the definition of P in Definition 12, one can easily note that L( ϕ) = L(ϕ), where L(ϕ) = {w Σ w = ϕ}. Therefore, L( ϕ) Safety. ω The rest follows by Theorem 5. Let us next investigate the problem of monitoring safety properties P Safety ω expressed as languages of safety formulae, that is, P = L( ϕ) for some past-time LTL formula ϕ. Because of the recursive nature of the satisfaction relation, a first important observation is that the generated monitor only needs to store information regarding the status of temporal operators from the previous state. More precisely, the monitor needs one bit per temporal operator, keeping the satisfaction status of the subformula corresponding to that temporal operator; when a new state is received, 213

214 the satisfaction status of the subformula is recalculated according to the recursive semantics above and then the bit is updated. The order in which the temporal operators are processed when a new state is received is important: the nested operators must be processed first. We next present the actual monitor synthesis algorithm at a high-level. We refrain from giving detailed pseudocode as we did in [109], because different applications may choose different implementation paradigms. For example, we are currently using rewriting techniques to implement the monitor synthesis algorithms in MOP [49]; Section shows our complete Maude rewriting implementation of the subsequent monitor synthesis algorithm. Step 1 Let ϕ 1,..., ϕ k be the k subformulae of ϕ corresponding to temporal operators, such that, if ϕ i is a subformula of ϕ j, then i < j; this can be easily achieved by a DFS traversal of ϕ. Step 2 Let bit[1..k] be a vector of k bits initialized to 0 (or false); bit[i] will store information related to ϕ i from the previous state: if ϕ i = ψ then bit[i] says if ψ was satisfied at the previous state; if ϕ i = ψ S ψ then bit[i] says if ϕ i was satisfied at the previous step. Step 3 Let bit [1..k] be another vector of k bits; this will be used to store temporary results, which will be moved eventually into the vector bit[1..k]. Step 4 Generate a loop that executes whenever a new state s is available; the body of the loop executes the following code: Step 4.1 For each i from 1 to k execute a bit assignment as follows, where for a subformula ψ of ϕ, ψ is the boolean expression replacing in ψ each non-nested temporal subformula ϕ j by bit[j] if ϕ j is a previously formula or by bit [j] if ϕ j is a since formula, and each remaining atomic proposition a by its satisfaction in the current state, a(s): if ϕ i = ψ then generate the assignment bit [i] := ψ if ϕ i = ψ S ψ then generate the assignment bit [i] := ψ ψ bit[i] 214

215 Step 4.2 Generate the conditional: if ϕ is false then error (formula violated) Step 4.3 Generate code to move the contents of bit [1..k] into bit[1..k]. Note that the generated monitors are well-defined, because each time a ψ boolean expression is generated, all the bits in bit [1..k] that are needed are already calculated. One can also perform boolean simplifications when calculating ψ to reduce runtime overhead even further. For example, in our implementation that also generated the code below (see Section ), we used the simplification ψ = ψ. To illustrate the monitor generation algorithm above, let us consider the past time LTL formula: ϕ = (a ( b (c S (d ( e S f))))). Step 1 produces the following enumeration of ϕ s subformulae: ϕ 1 = b, ϕ 2 = e S f, and ϕ 3 = c S (d ( e S f)). The other steps eventually generate the code: bit[1..3] := false; // three global bits foreach new state s do { // first update the bits in a consistent order bit [1] := b(s); bit [2] := f(s) ( e(s) bit[2]); bit [3] := d(s) bit [2]) (c(s) bit[3]); // then check whether the formula is violated if a(s) (bit[1] bit [3]) then Error; // finally, update the state of the monitor bit[1..3] := bit [1..3] } It is easy to see that for any past LTL formula ϕ of k temporal operators, the state of the generated monitor is encoded on k bits, namely the vector bit[1..k]. The runtime of the generated monitor is still O( ϕ ), because each temporal operator in ϕ results in an assignment and a read operation in the monitor, while each boolean operator in ϕ is executed by the monitor A Maude Implementation of the Monitor Synthesizer We here show a term rewriting implementation of the algorithm above, using the Maude system [60]. Implementations in other languages are obviously also possible; however, rewriting proved to be an elegant means to generate monitors from logical formulae in several other contexts, and so seems to be here. In what follows we show the complete Maude code that takes as input 215

216 a formula, parses it, generates the monitor, and then pretty prints it. We use the K technique here [196], which is a rewriting-based language and/or logic definitional technique; to use K, one needs to first upload the generic, i.e., application-independent, module discussed at the end of this section. Atomic Predicates We start by defining the atomic state predicates that one can use in formulae. These can be either identifiers (of the form a, abc, a123, etc.; these are provided by the Maude builtin module QID): fmod PREDICATE is --- atomic predicates can be quoted identifiers protecting QID. sort Predicate. subsort Qid < Predicate. endfm Syntax of Formulae Let us next define the syntax of formulae. We here use Maude s mixfix notation for defining syntax as algebraic operators, where underscores stay for arguments. Also, note that operators are assigned precedences (declared as operator attributes), to relive the user from writing parentheses (the lower the precedence the tighter the binding): fmod SYNTAX is protecting PREDICATE. sort Formula. subsort Predicate < Formula. op!_ : Formula -> Formula [prec 20]. op _/\_ : Formula Formula -> Formula [prec 23]. op O_ : Formula -> Formula [prec 21]. op _S_ : Formula Formula -> Formula [prec 22]. endfm Target Language We are done with the input language. Let us now define the output language. We need a very simple language for implementing the generated monitors, namely one with limited assignment, conditional and looping. The generated code, as well as the target language, play no role in this paper; one is expected to change the language below to one s desired target language (Java, C, 216

217 C#, assembler, etc.). Our chosen language below has bits, expressions, statements and code. Bits are also expressions; code is a list of statements composed sequentially using ; or just concatenation. The syntax below is also making use of precedence attributes. The format attributes are useful solely for pretty-printing reasons (see Maude s manual [60] for details on formatting): fmod CODE is --- syntax for the generated code protecting PREDICATE + INT + STRING. sorts Bit Exp Statement Code. subsorts Bit < Exp. subsort Statement < Code. ops (bit[_]) (bit [_]) : Nat -> Bit. ops (bit[1.. _]) (bit [1.. _]) : Int -> Bit. op _(s) : Predicate -> Exp [prec 0]. ops true false : -> Exp. op!_ : Exp -> Exp [prec 20]. op _/\_ : Exp Exp -> Exp [prec 23]. op _\/_ : Exp Exp -> Exp [prec 24]. op _:=_ : Exp Exp -> Statement [prec 27 format(ni d d d)]. op if_then_ : Exp Statement -> Statement [format(ni d d ++ --) prec 30]. op foreach new state s do _ : Code -> Statement [format(n d d d d s++ --n)]. op Error : -> Statement [format(ni d)]. op //_ : String -> Statement [format(ni d d)]. op nil : -> Code. op _;_ : Code Code -> Code [assoc id: nil prec 40]. op : Code Code -> Code [assoc id: nil prec 40]. op {_} : Code -> Statement [format(d d --ni ++)]. --- code simplification rules var B : Exp. eq!! B = B. endfm The following module defines the actual monitor synthesis algorithm. We use the K definitional technique here, because it yields a very compact implementation. K is centered on the basic intuition of computation; computations are encoded as first-order data-structures that evolve, via rewriting, to results. Computations are sequentialized using the list constructor -> ; thus, if K and K are computations, then K -> K is the computation consisting of K followed by K. Computations may eventually yield results; for example, K -> K may rewrite (in context) to R -> K, meaning that R is the result that K reduces to. An important feature of K is that one can 217

218 schedule lists of tasks for reduction; for example, [K1,K2,K3] -> K may eventually reduce to [R1,R2,R3] -> K, where R1, R2, and R3 are the results that K1, K2, and K3 reduce to, in this order. To use K, one needs to import the module K discussed at the and of this section. The equations of the module K (three in total) are all about reducing a list of computations to a list of results, supposing that one knows how to reduce one computation to one result. K is a definitional framework that is generic in computations and results. More precisely, it provides sorts KComputation and KResult, and expects its user to define the desired computations and results, as well as rules to reduce a computation to a result. Computations typically can be reduced to results only in context; to facilitate this, K provides a sort KConfiguration, which is also supposed to be populated accordingly. The sort KConfiguration is a multi-set sort over a sort KConfigurationItem, where the multi-set constructor is just concatenation; also, the sort KComputation is a list sort over KComputationItem, where the list constructor is ->. To make use of K, one needs to first define constructors for the sorts KConfigurationItem, KComputationItem and KResult, and then to define how each computation item reduces to a result. In our case, the computations are the formulae or subformulae that still need to be processed, and the results are the corresponding boolean expressions that need to be checked in the current (generated code) context to see whether the formula has been violated or not. We define the following additional constructors: we add four constructors for configurations, namely k that wraps the current computation, code that wraps the current generated code, and nextbit that wraps the next available bit; we add one main constructor for computations, form, that wraps a formula, and one constant computation item per operator in the input language (the later is needed to know how to combine back the results of the corresponding subexpressions; finally, we add one constructor for results, exp, that wraps a boolean expression. The formula is processed in a depth-first-order, following a divide-andconquer philosophy. Each subformula is decomposed into a list of computation subtasks consisting of its subformulae, then the corresponding results are composed back into a result corresponding to the original subformula. Recall that equations/rules apply wherever they match, not only at the top. Let us only discuss the two equations defining the since ( S ), the last two in the module below. The first one is straightforward: it decomposes 218

219 the task of processing F1 S F2 to the subtasks of processing F1 and F2; the computation item S is placed in the computation structure to prepare the terrain for the next equation. The next equation applies after F1 and F2 have been processed, say to expressions B1 and B2, respectively; if C is the code generated so far and if I+1 is the next bit available, then the boolean expression corresponding to the current since formula is indeed bit (I+1), provided that one adds the corresponding code capturing the recursive semantics of since to the generated code. fmod MONITOR-GENERATION is protecting K + SYNTAX + CODE. op k : KComputation -> KConfigurationItem. op code : Code -> KConfigurationItem. op nextbit : Nat -> KConfigurationItem. op process : Formula -> KConfiguration. op form : Formula -> KComputationItem. op exp : Exp -> KResult. ops! /\ O S : -> KComputationItem. var P : Predicate. vars F F1 F2 : Formula. var C : Code. var I : Nat. vars B B1 B2 : Exp. var K : KComputation. eq process(f) = k(form(f)) code(nil) nextbit(0). eq k(form(p) -> K) = k(exp(p(s)) -> K). eq form(! F) = form(f) ->!. eq exp(b) ->! = exp(! B). eq form(f1 /\ F2) = [form(f1),form(f2)] -> /\. eq [exp(b1),exp(b2)] -> /\ = exp(b1 /\ B2). eq form(o F) = form(f) -> O. eq k(exp(b) -> O -> K) code(c) nextbit(i) = k(exp(bit[i + 1]) -> K) code(c ; bit [I + 1] := B) nextbit(i + 1). eq form(f1 S F2) = [form(f1), form(f2)] -> S. eq k([exp(b1),exp(b2)] -> S -> K) code(c) nextbit(i) = k(exp(bit [I + 1]) -> K) code(c ; bit [I + 1] := B2 \/ B1 /\ bit[i + 1]) nextbit(i + 1). endfm Putting It All together The following module plugs the code generated above into the general pattern: fmod PRETTY-PRINT is protecting MONITOR-GENERATION. sort Monitor. op genmonitor : Formula -> Code. 219

220 op makemonitor : KConfiguration -> Code. var F : Formula. var B : Exp. var C : Code. vars N M : Nat. eq genmonitor(f) = makemonitor(process(f)). eq makemonitor(k(exp(b)) code(c) nextbit(n)) = bit[1.. N] := false ; foreach new state s do { // "first update the bits in a consistent order" C ; // "then check whether the formula is violated" if!(b) then Error ; // "finally, update the state of the monitor" bit[1.. N] := bit [1.. N] }. endfm Our implementation of the monitor synthesizer is now complete. To use it, one can ask Maude reduce terms of the form genmonitor(f), where F is the formula that one wants to generate into a monitor. For example: reduce genmonitor(!( a /\!(O b /\ c S ( d /\ (! e S f)))) ). For the formula above, Maude will give the expected answer, pretty printed as follows: \ / --- Welcome to Maude --- / \ Maude 2.2 built: Mar :37:22 Copyright SRI International Sat Jan 27 12:01: Maude> in p ========================================== fmod K ========================================== fmod PREDICATE ========================================== fmod SYNTAX ========================================== fmod CODE ========================================== fmod MONITOR-GENERATION ========================================== fmod PRETTY-PRINT 220

221 ========================================== reduce in PRETTY-PRINT : genmonitor(! ( a /\! (O b /\ c S ( d /\! e S f)))). rewrites: 46 in ms cpu (1ms real) (~ rewrites/second) result Code: bit[1.. 3] := false ; foreach new state s do { // "first update the bits in a consistent order" bit [1] := b(s) ; bit [2] := f(s) \/! e(s) /\ bit[2] ; bit [3] := d(s) /\ bit [2] \/ c(s) /\ bit[3] ; // "then check whether the formula is violated" if a(s) /\! (bit[1] /\ bit [3]) then Error ; // "finally, update the state of the monitor" bit[1.. 3] := bit [1.. 3] } Maude> The K Module One should upload the next module whenever one wants to use the K technique to define a language, logic or tool. Note that the module below has nothing to do with our particular logic under consideration in this paper; that is the reason for which we exiled it here. fmod K is sorts KConfigurationItem KConfiguration. subsort KConfigurationItem < KConfiguration. op empty : -> KConfiguration. op : KConfiguration KConfiguration -> KConfiguration [assoc comm id: empty]. sorts KComputationItem KNeComputation KComputation. subsort KComputationItem < KNeComputation < KComputation. op nil : -> KComputation. op _->_ : KComputation KComputation -> KComputation [assoc id: nil]. op _->_ : KNeComputation KNeComputation -> KNeComputation [ditto]. sort KComputationList. subsort KComputation < KComputationList. op nil : -> KComputationList. op _,_ : KComputationList KComputationList -> KComputationList [assoc id: nil]. sort KResult KResultList. subsorts KResult < KResultList < KComputation. 221

222 op nil : -> KResultList. op _,_ : KResultList KResultList -> KResultList [assoc id: nil]. op [_] : KComputationList -> KComputationItem. op [_] : KResultList -> KComputationItem. op [_ _] : KComputationList KResultList -> KComputationItem. var K : KNeComputation. var Kl : KComputationList. var R : KResult. var Rl : KResultList. eq [K,Kl] = K -> [Kl nil]. eq R -> [K,Kl Rl] = K -> [Kl Rl,R]. eq R -> [nil Rl] = [Rl,R]. endfm To use K, after importing the module above, one should define one s own constructors for configuration items (sort KConfigurationItem), for computation items (sort KComputationItem), and for results (sort KResult). For our example, we defined all these at the beginning of the module MONITOR-GENERATION. Exercises Exercise 15 Theorem 19 showed that only two temporal operators are actually needed. All the others can be regarded as derived operators. This is similar to how only two logical connectives are actually needed in propositional logic, say and. Therefore, it is tempting to pick only two temporal operators and then desugar all the others to them. Explain the drawbacks of doing so in terms of the time/space complexity of the resulting monitors, addressing each of the discussed monitoring approaches separately: rewriting (Section 10.4), monitor generation (Section 10.5) including post-generation optimizations (Section ), as well as directly-optimal monitor generation (Section 10.7). Exercise 16 The monitor synthesis algorithms discussed in this chapter generate monitors whose states are vectors of bits, and which execute some simple code each time a new event is observed. Explain how you can generate conventional finite-state automata from such monitors. Explain the advantages and disadvantages of using automata generated this way as monitors. 222

223 Chapter 11 Monitoring Always-Past Temporal Safety with Call-Return Material from [200] Abstract: We present an extension of past time LTL with call/return atoms, called ptcaret, together with a monitor synthesis algorithm for it. ptcaret includes abstract variants of past temporal operators, which can express properties over traces in which terminated function or procedure executions are abstracted away into a call and a corresponding return. This way, ptcaret can express safety properties about procedural programs which cannot be expressed using conventional linear temporal logics. The generated monitors contain both a local state and a stack. The local state is encoded on as many bits as concrete temporal operators the original formula has. The stack pushes/pops bit vectors of size the number of abstract temporal operators the original formula has: push on begins, pop on ends of procedure executions. An optimized implementation is also discussed and is available to download. 223

224 11.1 Introduction Theoretically speaking, it appears to be straightforward to monitor properties expressed as past time linear temporal logic (ptltl) formulae, since the fix-point semantics of the temporal operators gives a direct deterministic automaton. The practical challenge in monitoring ptltl formulae stays in how to do it efficiently, both time-wise and memory-wise, so that the added runtime overhead to the observed system is minimal. Since in a real-life runtime verification application there could be millions of monitor instances living at the same time, each observing tens of millions of events (see, e.g., [7, 20] and [51, 52] for numbers and evaluations of runtime verification systems on large benchmarks), every bit of memory or monitor processing time may translate into significantly higher runtime overhead, to an extent that the overall use of runtime verification in a particular application may become unfeasible. For example, in many cases it may not be a good idea to generate an actual deterministic automaton as a monitor, because that may have an exponential or worse size; instead, a non-deterministic automaton performing an NFA-to-DFA construction on the fly saving space exponentially may be more appropriate, or even a monitor that does not store any automaton at all, but has an efficient way to generate the next state on-the-fly. Havelund and Roşu proposed a monitor synthesis algorithm for past-time LTL (ptltl) formulae ϕ [109]. The generated monitors implement the recursive semantics of ptltl using a dynamic programming technique, and need O( ϕ ) time to process each new event and O( ϕ ) total space. Roşu proposed an improved monitor synthesis algorithm for ptltl in [191] (un unpublished technical report) which, using a divide-and-conquer strategy, generates monitors that need O(k) space and still O( ϕ ) time, where k is the number of temporal operators in ϕ. Alur et al. gave an extension of linear temporal logic (LTL) with calls and returns [10], called CaRet. Unlike LTL, CaRet allows for matching call/return states in linear traces, allowing to express program trace properties not expressible using plain LTL. In particular, one can express properties on the execution stack of a program, such as function g is always called from within function f, or structured-programming safety policies such as each method must release before it terminates all the locks that it acquired during its execution, or even properties that are allowed to be temporarily violated, such as user u never directly accesses the passwords file (but may access it through system procedures). Because of allowing 224

225 such important and desirable safety properties to be formally stated at the same time faithfully including LTL, CaRet can be a more attractive temporal logic then LTL, provided of course that the complexity of checking programs against CaRet formulae does not make it unfeasible. We define a past time variant of CaRet, called ptcaret, show by examples its usefulness in expressing a series of safety properties involving calls of functions/procedures, and then propose a monitor synthesis algorithm for properties expressed as ptcaret formulae. Motivated by practical reasons, ptcaret distinguishes call/return states from begin/end states: the former take place in the caller s context, while the latter take place in the callee s. This simple and standard distinction allows more flexibility and elegance in expressing properties, but requires an additional (but reasonable) constraint on traces: calls always immediately precede begins, and ends always immediately precede returns. ptcaret conservatively extends ptltl by adding abstract variants of temporal operators, namely abstract previously and abstract since. The semantics of these operators is that of their corresponding core ptltl operators previously and since, but on the abstract trace obtained by collapsing executed functions or procedures into only two states, namely the caller s state at the call of the invoked function or procedure and the caller s state at its corresponding return. In other words, from the point of view of the abstract temporal operators, the intermediate states generated during function executions are invisible. Of course, the standard temporal operators continue to see the whole trace. The monitors generated from ptcaret formulae using the proposed algorithm have both a monitor state and a monitor stack, so they can be regarded as push-down automata; however, both the monitor states and the data pushed onto stacks are calculated online, on a by-need basis. The monitor state is encoded on as many bits as standard past time operators in the original formula, while the monitor stack pushes/pops as many bits of data as abstract temporal operators in the original formula. If no abstract temporal operators are used in a ptcaret formula, that is, if the ptcaret formula is a ptltl formula, then its generated monitor is identical to that obtained using the technique in [191]. In other words, not only is ptcaret a conservative extension of ptltl, but the proposed monitor synthesis algorithm conservatively extends the best known, provably optimal monitor synthesis algorithm for ptltl. The proposed ptcaret monitor synthesis algorithm has been imple- 225

226 mented and is available to download and experiment with via a web interface at [17]. The rest of the paper is structured as follows: Section 11.2 discusses ptcaret as an extension of ptltl; Section 15.2 introduces useful derived operators and shows some examples of ptcaret specifications. Section discusses our monitor synthesis algorithm, including its implementation. Section 11.5 concludes the paper ptltl and ptcaret We here recall past time linear temporal logic (ptltl) and define its extension ptcaret. For simplicity, we assume only two types of past operators, namely previously and since. Other common or less common temporal operators can be added as derived operators. ptltl contains only the usual, standard variants of temporal operators, while ptcaret contains both standard and abstract variants. We follow the usual recursive semantics of past time LTL and adopt the simplifying assumption that the empty trace invalidates any atomic proposition and any past temporal operator; as argued in [109], this may not always be the best choice, but other semantic variations regarding the empty trace present no difficulties for monitoring and can easily be accommodated. Definition 46 Syntactically, ptltl consists of formulae over the grammar ϕ ::= true a ϕ ϕ ϕ ϕ ϕ S ϕ, where a ranges over a set A of state predicates. Other common syntactic constructs can be defined as derived operators in a standard way: false is true, ϕ ( eventually in the past ) is true S ϕ, ϕ ( always in the past ) is ( ϕ)), etc. LTL s models, even for its safety fragment, traditionally are infinite traces (see, e.g., [161]), where a trace is a sequence of states, where a state is commonly abstracted as a set of atomic predicates in A. refer to Chapter 3 below; or merge some of the text earlier in the book; or even remove it altogether. According to Lamport [154], a safety property is a set of such infinite traces (properties are commonly identified with the sets of traces satisfying them) 226

227 such that once an execution violates it then it can never satisfy it again later. Formally, a set of infinite traces Q is a safety property if and only if for any infinite trace u, if u Q then there is some finite prefix w of u such that wv Q for all infinite traces v. It can be shown that there are as many safety properties as real numbers [191]. Unfortunately, any logical formalism can define syntactically only as many formulae as natural numbers. Thus, any logical formalism can only express a small portion of safety properties. In LTL, a common way to specify safety properties is as always past formulae, that is, as formulae of the form ϕ ( is always in the future ), where ϕ is a formula in ptltl. There are two problems with identifying the problem of monitoring a ptltl specification ϕ with checking the running system against the LTL safety formula ϕ: on the one hand, LTL has an infinite trace semantics, while during monitoring we only have a finite number of past states available, and, on the other hand, once the LTL formula ϕ is violated then it can never be satisfied in the future. However, a major use of monitoring is in the context of recoverable systems, in the sense that the monitor can trigger recovery code when ϕ is violated, in the hope that ϕ will be satisfied from here on. For these reasons, we adopt a slightly modified semantics of past time LTL, namely the one on finite traces borrowed from [109]: Definition 47 A (program) state is a set of atomic predicates in A; let s, s, etc., denote states, and let ProgState denote the set of all states. A trace is a finite sequence of states in ProgState ; let w, w, etc., denote traces, and ɛ denote the empty trace. If w ɛ, that is, if w = w s for some trace w and some state s, then we let prefix(w) denote the trace w and call it the (concrete) prefix of w, and let last(w) denote the state s. The satisfaction relation w = ϕ between a trace w and a ptltl formula ϕ is defined recursively as follows: w = true is always true, w = a iff w ɛ and a last(w), w = ψ iff w = ψ, w = ψ ψ iff w = ψ and w = ψ, w = ψ iff w ɛ and prefix(w) = ψ, w = ψ S ψ iff w ɛ and (w = ψ or w = ψ and prefix(w) = ψ S ψ ). Unlike other places in the book, previously is defined with the strong meaning above. 227

228 We next introduce ptcaret as an extension of ptltl. Syntactically, it only adds abstract versions of the two temporal operators previously and since to ptltl; semantically, some special atomic predicates corresponding to calls, returns, begins and ends of functions/procedures need to be assumed, as well as some natural and practically reasonable restrictions on traces. Definition 48 ptcaret syntactically extends ptltl with: ϕ ::=... ϕ ϕ S ϕ The former is called abstract previously and the latter abstract since. The semantics of abstract previously and since are defined exactly as the semantics of their concrete counterparts, but on an abstract version of the trace from which all the intermediate states of the terminated function or procedure executions are erased. In order for this erasure, or abstraction, process to work, we need to impose some constraints on traces that are always satisfied in practice. Definition 49 In ptcaret, the set of atomic predicates A contains four special predicates: call, begin, end, and return. A state contains at most one of these and is called call, begin, end, or return state if it contains the corresponding predicate. ptcaret traces are constrained to the following restrictions: (1) any call state, except when the last one, must be immediately followed by a begin state, and any begin state must be immediately preceded by a call state; (2) any end state, except when the last one, must be immediately followed by a return state, and any return state must be immediately preceded by an end. For a trace w as above, we let w denote its abstraction, which is obtained by iteratively erasing contiguous subtraces s b w s e of w in which s b is a begin state, s e is an end state which is not the last one in w, and w contains no begin or end states. One more restriction is imposed on ptcaret traces: (3) the abstractions of ptcaret traces contain no return states which are not immediately preceded by call states. 228

229 call return begin end (A) (B) Figure 11.1: ptcaret trace (A) and abstraction (B). : end of w, : w state, : w state. Call and return states occur in the caller s context. Thus, call/return states can contain other predicates which may not be possible to evaluate in the callee s context during runtime monitoring. The begin/end states are generated in the callee s context, at the beginning and at the end of the execution of the invoked function, respectively. Similarly, for some common programming languages, begin/end states may contain other predicates that cannot be evaluated in the caller s context. The original CaRet logic [10] did not distinguish between call and begin states or between end and return states. We included all four of them in ptcaret for the reasons above and also because most trace monitoring systems (e.g., Tracematches [7, 20] and MOP [51, 52]) make a clear distinction between these four types of states. Fig (A) shows a ptcaret trace. To better reflect the call-return structure of the ptcaret trace, states are placed on different levels: states on the higher level are generated in the caller s context while those on the lower level are generated in the callee s. The vertical dotted lines connect the corresponding call-begin and end-return pairs. Fig (B) shows the abstraction of that trace: if w ends with the state pointed by, w contains only the circled states. Restrictions (1) and (2) on ptcaret traces are very natural. One source of doubt though can be the sub-requirements that any return state must be preceded by an end state, and that any begin state must be preceded by a call state. While a return or a begin can indeed happen in any programming language only after a corresponding end or call state, respectively, one may argue that monitoring of a property should be allowed to start at any moment, in particular in between call and begin, or in between end and return states. While our synthesized monitors from ptcaret formulae (see Section ) can be easily adapted to start monitoring at any moment in the trace, for the sake of a smoother and simpler development of the theoretical foundations of ptcaret, we assume that any ptcaret trace 229

230 starts from the beginning of the program execution and thus satisfies the above-mentioned restrictions. Restriction (3) ensures that a trace does not contain return states that do not have corresponding matching call states, also a natural restriction on complete traces. Our definition of trace abstraction above is admittedly operational, but we think that it captures the desired end/begin matching concept both compactly and intuitively. Alternatively, we could have followed the CaRet style in [10] and define the matching begin state of an end state as the latest begin state containing a balanced number of begin/end states in between. uncomment the next text? Definition 50 For a non-empty ptcaret trace w, let prefix(w), called the abstract prefix of w (not to be confused with the abstraction of the prefix of w, prefix(w)), be either prefix(w) if last(w) is not a return state, or otherwise the prefix of w up to and including the corresponding matching call state of last(w) if it is a return state; formally, if last(w) is a return state then prefix(w) is the trace w s c, where w = w w for some w with w = s c s r, where s c and s r are call and return states, respectively. Fig illustrates prefix(w) on two traces, with the down arrow pointing to the ends of the traces. In Fig (A) we assume that w ends with a state that is not a return (the arrow points to a call state) and in Fig (B) w ends with a return state (the states of the corresponding prefix(w) are marked with diamonds). Definition 51 The satisfaction relation between a ptcaret trace w and a ptcaret formula ϕ is defined recursively exactly like in ptltl for the ptltl operators, and as follows for the two abstract temporal operators: w = ψ iff w ɛ and prefix(w) = ψ, w = ψ S ψ iff w ɛ and (w = ψ or w = ψ and prefix(w) = ψ S ψ ). Therefore, a formula ψ is satisfied in a return state iff ψ was satisfied at the corresponding matching call state. It is satisfied in a non-return state, including an end state, iff ψ is satisfied in that state (that is, if and only if ψ was satisfied in the concrete (non-abstract) previous state). Fig compares the and operators. The arrows point, for each state, where the formula ψ in ψ (A) and in ψ (B) holds. For most states, 230

231 (A) (B) Figure 11.2: prefix(w) on two traces, (A) and (B). : the end of w, : state in prefix(w). (A) (B) Figure 11.3: Concrete (A) and abstract (B) previous states for and. their abstract previous state is the concrete previous one; the only difference is on return states, because the abstract previous state of a return state is its call state. (A) (B) Figure 11.4: ψ S ψ (A) versus ψ S ψ (B). : where ψ holds, : where ψ holds. Figure 11.4 compares ψ S ψ and ψ S ψ. Notice that the various call/return levels play no role in the satisfaction of ψ S ψ, but that they play a crucial role in the satisfaction of ψ S ψ : for the latter, ψ must hold on the same level or a higher level as the level of the current state. One can show the following expected property of abstract since: Proposition 17 ϕ 1 S ϕ 2 is semantically equivalent to ϕ 2 ϕ 1 (ϕ 1 S ϕ 2 ). One should not get tricked and assume that w = ϕ if and only if w = ϕ, or that w = ϕ 1 S ϕ 2 if and only if w = ϕ 1 S ϕ 2! The reason is that subformulae ϕ, ϕ 1 or ϕ 2 may contain concrete temporal operators whose 231

232 semantics still involve the entire execution trace, not only the abstract one. Some examples in this category are shown in Section Nevertheless, the following holds: Proposition 18 For a ptcaret trace w and formula ϕ containing no concrete temporal operators and S, w = ϕ iff w = ˆϕ, where ˆϕ is the ptltl formula replacing each abstract temporal operator in ϕ by its concrete variant ptcaret Derived Operators and Examples Besides the usual derived Boolean operators and past time temporal operators eventually in the past, always in the past, as well as start, stop, and interval operators like in [109], which can all be also defined abstract variants, we can define several other interesting, ptcaret-specific derived operators. In the rest of the paper we use the standard notation for the derived Boolean operators, e.g.,,, etc., with their usual precedences, and assume that binds as tight as while S binds tighter than the binary Boolean operators. At beginning. Suppose that one would like a particular property, say ψ, to hold at the beginning of the execution of the current function. We can define the derived temporal b, say at beginning, as b ψ def = (begin ψ) ( begin (begin ψ) S begin). Note that the concrete previously operator is used inside the argument of the abstract since operator. The above is correct because the last begin state seen by the abstract since is indeed the beginning of the current function or procedure. One should not get tricked and try to define the above b ψ def = (begin ψ) ( begin (begin ψ) S call). That is because the current function may have called and returned from several other functions, and the abstract since can still see all the call/return states. The above would vacuously hold in such a case. At call. Suppose now that one wants ψ to hold at the state when the current function was called. For the same reason as above, one cannot simply replace begin by call in the definition b above. However, one 232

233 (A) (B) Figure 11.5: Derived operators. : current state, : states b, S b ; : states c, S c can define the derived temporal c, say at call, in terms of at beginning simply as c ψ def b ψ. In Fig (A), supposing that the current state is the one pointed to by the arrow, ψ should hold in the diamond state b ψ and in the circle state c ψ. Stack since on beginnings. The abstract since can be used to write properties in which the terminated function executions are irrelevant. There may be cases in which one wants to write properties referring exclusively to the execution stack of a program, ignoring any other states. For example, one may want to say that ψ held on the stack since property ψ held. As usual, one may be interested in properties ψ and ψ to hold either at call time, or at execution beginning time. Let us first define a stack since on beginnings derived operator: ψ S b ψ def = (begin ψ) S (begin ψ ). Stack since on calls. To define a stack since on calls one cannot simply replace begin by call in the above. Instead, one can define it as follows: ϕ 1 S c ϕ 2 def = (call ϕ 1 ) S (begin ϕ 2 ). In Fig (B), if the current state is the one pointed by the arrow, the begin stack consists of the diamonds and the call stack consists of the circles. With the stack since derived temporal operators above, one can further define other derived operators, such as stack eventually in the past on calls (say c), stack always in the past on beginnings (say b), etc. 233

234 Let us next further illustrate the strength of ptcaret by specifying some concrete properties that would be hard or impossible to specify in ptltl. Suppose that in a particular context, function f must be called only directly by function g. Assuming call f and call g are predicates that hold when f and g are called, respectively, we can specify this property in ptcaret as follows: call c call g. Suppose now that f can be called only directly or indirectly by g: a call to g must be on the stack whenever f is called. We can specify that as follows: call f ccall g. A common safety property in many systems is that resources acquired during a function execution must be released before the function ends. Assuming that acquire and release are predicates that hold when the resource of interest is acquired or released, respectively, we can specify this property as follows: end ( acquire S begin ( release S acquire)). A more complex example is discussed in Section A Monitor Synthesis Algorithm for ptcaret As discussed in [191] for ptltl, thanks to the recursive nature of the satisfaction relation on the standard ptltl temporal operators (see Definition 47), the monitor generated from a ptcaret formula needs only one global bit per standard (non-abstract) temporal operator. This bit maintains the satisfaction status of the subformula corresponding to that standard temporal operator; when a new state is observed, the satisfaction status of that subformula is recalculated according to the recursive semantics in Definition 47 and the bit is updated. In order for this to work, one needs to have already updated or have an easy way to calculate the status of the subformulae. The situation is more complex for the abstract temporal operators, as one needs to store enough information about the past so that one is able to 234

235 update the status of abstract operators satisfaction regardless of how the future evolves. The main complication comes from the fact that one needs to freeze the satisfaction status of the subformulae corresponding to abstract temporal operators whenever a begin state is observed, and then unfreeze it when the corresponding end state is observed, thus recovering the information that was available right when the function call took place. Fortunately, that can be obtained by using a stack to push/pop the satisfaction status of the abstract temporal subformulae. More precisely, a stack bit is needed per abstract temporal operator in the ptcaret formula, maintaining the satisfaction status of the subformula corresponding to that abstract operator. When a new state is observed, the satisfaction status of that subformula is recalculated according to the recursive semantics in Definition 50 and the stack bit updated; if the newly observed state is a begin, then the status of the stack bits is pushed on the stack before the actual monitor state update; if the newly observed state is an end, then the status of the stack bits is popped from the stack after the monitor state update The Target Language To state and prove the correctness of any program generation algorithm, one needs to have a formal semantics of the target language. This section gives a formal syntax and semantics to the simple and generic language in which we synthesize monitors. One can very easily translate this language into standard languages, such as C, C++, C#, Java, or even into native machine code. For each ptcaret formula ϕ, we are going to generate (in Section ) a monitor M ϕ as a statement in a language L ϕ. The only difference between the languages L ϕ is the set of variables that one can assign values to; the rest of the language constructs are the same for all ϕ. The language L ϕ has the following simple syntax (note that L ϕ1 L ϕ2 whenever ϕ 1 is a subformula of ϕ 2 ): Var ::= α φ (one for each subformula φ of ϕ rooted in or S ) β φ (one for each subformula φ of ϕ rooted in or S ) Exp ::= true A Var Exp Exp Exp Stm ::= Var := Exp if begin then push if end then pop output(exp) Stm Stm 235

236 Therefore, programs in L ϕ can use predicates in A (the atomic predicate set of ptcaret) as ordinary (Boolean) expressions, together with Boolean variables α φ and β φ, one per standard and abstract temporal operator in ϕ, respectively, and together with Boolean constructs such as complement and conjunction. Statements can be composed using juxtaposition, and can be: α φ or β φ variable assignment, output of a Boolean expression, or conditional push/pop, the latter pushing or popping, by convention, precisely the bit vector β. We assume a (rather conventional) denotational semantics for L ϕ as follows: Definition 52 If ϕ has k 1 standard temporal operators and k 2 abstract temporal operators, then let MonState ϕ (we think of L ϕ programs as monitors) be the state space of L ϕ, that is, the domain Bool k 1 Bool k 2 Stack Output, where Bool is the set {true, false}, Stack is the domain (Bool k 2 ) of stacks, or lists, over bit vectors of size k 2, and Output is the domain Bool of bit lists. Let the functions be defined as follows: J K : Exp MonState ϕ ProgState Bool J K : Stm MonState ϕ ProgState MonState ϕ JtrueK( α, β, σ, ω)(s) = true, JaK( α, β, σ, ω)(s) = s(a), Jα φ K( α, β, σ, ω)(s) = α(i), where i k 1 is the α-index corresponding to φ, Jβ φ K( α, β, σ, ω)(s) = β(j), where j k 2 is the β-index corresponding to φ, Jb 1 b 2 K( α, β, σ, ω)(s) = Jb 1 K( α, β, σ, ω)(s) and Jb 2 K( α, β, σ, ω)(s), Jα φ := bk( α, β, σ, ω)(s) = ( α[ α(i) JbK( α, β, σ, ω)(s)], β, σ, ω), Jβ φ := bk( α, β, σ, ω)(s) = ( α, β[ β(j) { JbK( α, β, σ, ω)(s)], σ, ω), Jif begin then pushk( α, β, ( α, β, σ, ω)(s) = β σ, ω) if s(begin), ( α, β, σ, ω) otherwise, { Jif end then popk( α, β, ( α, β σ, ω)(s) =, σ, ω) if s(end) and σ = β σ, ( α, β, σ, ω) otherwise, Joutput(b)K( α, β, σ, ω)(s) = ( α, β, σ, ω JbK( α, β, σ, ω)), Jstm stm K( α, β, σ, ω)(s) = Jstm K(JstmK( α, β, σ, ω)(s)). We can now associate a function JM ϕ K : MonState ϕ ProgState MonState ϕ to each program M ϕ in L ϕ. For a monitor state ( α, β, σ, ω) MonState ϕ and a program state s ProgState, JM ϕ K( α, β, σ, ω)(s) = ( α, β, σ, ω ) 236

237 if and only if the monitor M ϕ executed in state ( α, β, σ, ω) when program state s is observed, produces monitor state ( α, β, σ, ω ). Definition 53 By abuse of notation, we also let JM ϕ K : ProgState MonState ϕ be the function (false k 1 is the vector of k 1 false bits, and ɛ is the empty list): { JM ϕ K(ɛ) = (false k 1, false k 2, ɛ, ɛ) the initial monitor state JM ϕ K(ws) = JM ϕ K(JM ϕ K(w))(s) The Monitor Synthesis Algorithm We next present the actual monitor synthesis algorithm at a high-level. We refrain from giving detailed pseudocode as in [109], because different applications may choose different implementation paradigms. For example, our implementation of the ptcaret logic plugin in the context of the MOP system [51, 52], discussed in Section 16.7, uses term rewriting techniques. The monitoring code for a ptcaret formula ϕ can be split into three pieces: code to be executed before the monitor outputs the satisfaction status of the formula, the outputting code, and code to be executed after the output. Let Code ϕ before denote the former and let Codeϕ after denote the latter. Code ϕ before is concerned with updating the status of the since operators in a bottom-up fashion, while Code ϕ after with updating the status of the previously operators. Indeed, in order to output the satisfaction status of ϕ, one needs to know the status of all the since operators, which may depend upon values in the current state as well as upon values of nested since operators, so the inner since operators need to be processed before the outer ones. On the other hand, one need not know the particular details (values of atomic predicates) of the current state in order to know the status of the previously operators; all one needs to make sure of is that the status of the previously operators has been updated at the appropriate previous state (or states in the case of abstract previously ), after the monitor output. Interestingly, note that, unlike the since operators, the previously operators need to be processed in a top-down fashion, that is, the outer ones need to be processed before the inner ones. Note that the monitors M ϕ generated in Figure 11.6 are well-defined, in the sense that each time a generated Boolean expression ψ is executed, all the α and β bits that are needed have been calculated. That is because 237

238 Input: A ptcaret formula ϕ Output: Code that monitors ϕ Step 1 Allocate a bit α φ, initially false, for each subformula φ of ϕ rooted in a standard temporal operator. Intuition for this bit is: if φ = ψ then α φ says if ψ was satisfied at the previous state; if φ = ψ S ψ then α φ says if φ was satisfied at previous state. Step 2 Allocate a bit β φ, initially false, for each subformula φ of ϕ rooted in an abstract temporal operator. Intuition for this bit is: if φ = ψ then β φ says if ψ was satisfied at abstract previous state; if φ = ψ S ψ, β φ says if φ was satisfied at abstract previous state. Step 3 Initialize Code ϕ before and Codeϕ after as follows: Code ϕ before to the code if begin then push, and Code ϕ after to the code if end then pop. Notation: For subformulae φ of ϕ, let φ be the Boolean expression replacing in φ each temporal-operator-rooted subformula ψ which is not a subformula of another temporal-operator-rooted subformula of φ, by either α ψ when ψ is rooted in a standard temporal operator, or by β ψ when ψ is rooted in an abstract operator. E.g., a b S c (d S e) is a β b S c α (d S e). Step 4 Following a depth-first-search (DFS) traversal of ϕ, for each subformula φ of ϕ rooted in a temporal operator do: if φ = ψ then Code ϕ after (α φ := ψ) Code ϕ after if φ = ψ then Code ϕ after (β φ := ψ) Code ϕ after if φ = ψ S ψ then Code ϕ before Codeϕ before (α φ := ψ ψ α φ ) if φ = ψ S ψ then Code ϕ before Codeϕ before (β φ := ψ ψ β φ ) Step 5 Output monitor M ϕ as the code Code ϕ before output(ϕ) Codeϕ after Figure 11.6: The monitor synthesis algorithm for ptcaret 238

239 the code is generated following a DFS traversal of the original ptcaret formula. M ϕ is run at each newly generated event, or program state, and outputs either true or false. Note that each M ϕ has the form (if begin then push) C ϕ 1 output(oϕ ) C ϕ 2 (if end then pop), for some potential statements C ϕ 1 and Cϕ 2, and for some Boolean expression Oϕ. To simplify notation, we introduce the following: Definition 54 Let C ϕ 1, Oϕ, C ϕ 2 be a shorthand for: We use for C ϕ 1 or Cϕ 2 if begin then push C ϕ 1 output(o ϕ ) C ϕ 2 if end then pop when they do not exist. The following result structurally relates monitors generated for formulae ϕ to monitors generated for its subformulae. One can use this proposition as an equivalent, recursive way to synthesize monitors for ptcaret: Proposition 19 If M ψ = C ψ 1, Oψ, C ψ 2 and M ψ = Cψ 1, Oψ, C ψ 2 then: M true =, true, M a =, a, M ψ = C ψ 1, Oψ, C ψ 2 M ψ ψ = C ψ 1 Cψ 1, Oψ O ψ, C ψ 2 C ψ 2 M ψ = C ψ 1, α ψ, (α ψ := ψ) C ψ 2 M ψ S ψ = C ψ 1 Cψ 1 (α ψ S ψ := ψ ψ α ψ S ψ ), α ψ S ψ, C ψ 2 C ψ 2 M ψ = C ψ 1, β ψ, (β ψ := ψ) Cψ 2 M ψ S ψ = C ψ 1 Cψ 1 (β ψ S ψ := ψ ψ β ψ S ψ ), β ψ S ψ, C ψ 2 C ψ 2 To prove the correctness of our monitor synthesis algorithm, we need to show that after observing any sequence of program states w, a synthesized monitor M ϕ outputs the same result as the satisfaction status of w = ϕ. Therefore, we need to define the output of the monitor M ϕ after observing w : 239

240 Definition 55 Let LM ϕ M : ProgState + Bool be defined for each (nonempty) w ProgState + as LM ϕ M(w) = b iff JM ϕ K(w) = ( α, β, σ, ω b). For uniformity, let us extend LM ϕ M to a function ProgState Bool (as in Definitions 47 and 50): LM ϕ M(ɛ) = false when ϕ = a, ψ, ψ S ψ, ψ, ψ S ψ ; LM ψ M(ɛ) = LM ψ M(ɛ); LM ψ ψ M(ɛ) = LM ψ M(ɛ) LM ψ M(ɛ). Proposition 20 The following hold for any w ProgState : LM true M(w) is always true, LM a M(w) iff w ɛ and a last(w), LM ψ M(w) iff not LM ψ M(w), LM ψ ψ M(w) iff LM ψ M(w) and LM ψ M(w), LM ψm(w) iff w ɛ and LM ψ M(prefix(w)), LM ψ S ψ M(w) iff w ɛ and (LM ψ M(w) or LM ψ M(w) and LM ψ S ψ M(prefix(w))), LM ψ M(w) iff w ɛ and LM ψ M(prefix(w)), LM ψ S ψ M(w) iff w ɛ and (LM ψ M(w) or LM ψ M(w) and LM ψ S ψ M(prefix(w))). Proof: The non-trivial ones are those for temporal operators. We only discuss S, because the others follow the same idea and are simpler. The monitors for ψ S ψ, ψ, and ψ, respectively, following the notations in Proposition 19 are: M ψ S ψ M ψ M ψ 1. if begin then push if begin then push if begin then push 2. C ψ 1 Cψ 1 3. β ψ S ψ := ψ ψ β ψ S ψ C ψ 1 C ψ 2 4. output(β ψ S ψ ) output(ψ) output(ψ ) 5. C ψ 2 C ψ 2 C ψ 2 C ψ 2 6. if end then pop if end then pop if end then pop Note that the property holds vacuously if w = ɛ. Assume now that w = w s, for some s ProgState. An interesting and useful property of the generated 240

241 monitors is that their semantics is very modular, and that pushing or popping β does not affect the modular semantics. For example, note that C ψ 1 in M ψ S ψ uses no variables defined in C ψ 1 or in C ψ 2, and the bit β ψ S ψ is only defined in line 3. and used in lines 3. and 4. This modularity guarantees that, if we were to output ψ or ψ at line 3. or 4. in M ψ S ψ, then its output after processing w would be nothing but LM ψ M(w) or LM ψ M(w), respectively. That means that the ψ and ψ in the expression assigned to β ψ S ψ at line 4. when processing the last state in w are LM ψ M(w) and LM ψ M(w), respectively. We claim that β ψ S ψ in the assigned expression at line 4. is LM ψ S ψ M(prefix(w)). There are two cases to analyze. (1) if s is not a return state, then β ψ S ψ was assigned at line 3. in the previous execution of the monitor, when processing the last state in w, so it is nothing but LM ψ S ψ M(prefix(w)); and (2) if s is a return state, then it means that the last state in w was an end state, so the vector β was popped from the stack at the end of the previous step. The only thing left to note is that our push on begins and pop or ends correctly match begin and end states; this follows from the fact that we assume traces complete and well-formed (Definition 49). Theorem 20 The monitor synthesis algorithm in Figure 11.6 is correct, that is, for any ptcaret formula ϕ and for any w ProgState, LM ϕ M(w) iff w = ϕ. Proof: Straightforward, by induction on both the structure of ϕ and the length of w, noticing that there is a one-to-one correspondence between the definition of satisfaction in Definitions 47 and 50, and the properties in Proposition Implementation as Logic Plugin, Optimizations, Example MOP [51, 52] is a configurable runtime verification framework, in which specification requirements formalisms can be added modularly, by means of logic plugins. A logic plugin essentially encapsulates a monitor synthesis algorithm for a formalism that one can then use to specify properties of programs. The current JavaMOP tool has logic plugins for future time LTL, past time LTL, Allen algebra, extended regular expressions, JML, JASS. JavaMOP takes a Java application to be monitored and specifications using any of the included formalisms together with validation and/or violation 241

242 handlers (saying what to do if property validated or violated, in particular nothing), and then waves them together in a runtime verified application by first generating monitors for all the properties using their corresponding logic plugins, and then generating and compiling an AspectJ extension of the original program (runtime monitors are aspects ). To maintain a reduced runtime overhead (shown on large benchmarks to be, on average, below 10%), MOP piggybacks monitor states onto object states. The ptcaret MOP logic plugin. We implemented the ptcaret monitor synthesis algorithm in Section as an MOP logic plugin. Our implementation can be found and experimented with online at [17]. Largescale experiments are still to be performed; we are currently engineering the MOP system to allow monitor states to piggyback not only object states, but also the program stack. In short, our implementation uses term rewriting and the Maude system [60], and follows the monitor synthesis algorithm in Figure 11.6 and its equivalent, recursive formulation in Proposition 19. Implementations in other languages are obviously also possible; however, term rewriting proved to be an elegant means to synthesize monitors from logical formulae in several other contexts (the other MOP plugins, as well as in JPaX [194]), and so seems to be here. Our implementation starts by defining the Boolean expressions as an algebraic specification using Maude s mixfix notation (equivalent to contextfree grammars); derived Boolean operators are also defined, together with several simplification rules ( true = f alse, etc.). Boolean expressions are imported both in the target language module and in the ptcaret module. Both the target language and the ptcaret modules are defined as algebraic signatures, enriched with structural equalities which turn into simplification rules when executed; this way, for example, each ptcaret derived operator is defined with one equation capturing its definition. Several other derived operators are defined in addition to those discussed in Section The monitor generation module imports both the target language and the ptcaret modules, and adds two equations per temporal logic operator; e.g., the equations below process the abstract since : eq form(f1 Sa F2) = [form(f1), form(f2)] -> Sa. eq k([exp(b1),exp(b2)] -> Sa -> K) code(i,c1,c2) nextbeta(n) = k(exp(beta[n]) -> K) code(i beta[n] := false, C1 beta[n] := B2 or B1 and beta[n], C2) nextbeta(n + 1). First equation says that subformulae should be processed first (DFS traversal). The second equation combines the codes generated from the subformulae as 242

243 shown in Proposition 19, appending the assignment for the corresponding bit to C1. Note that C1 here accumulates the code before of both subformulae; in terms of Proposition 19, it is C ψ 1 Cψ 1. I accumulates the monitor initialization code. Finally the optimizations below are implemented also as rewrite rules. Optimizations. Term-rewriting-based code-generation algorithms can be easily extended with optimizations, because these can be captured as rewrite rules. We discuss some of the optimizations enabled in our implementation. First, we perform Boolean simplifications when calculating ψ to reduce runtime overhead ( ψ = ψ, true ψ = true, etc.). Another immediate optimization is the following. The generated code originally has the form (see Fig. 11.6) (if begin then push) C (if end then pop), for some code C. However, since a program state can only contain at most one of the special predicates, this can be optimized into (syntax of target language needs to be slightly extended): if begin then { push C[begin true, end false, call false, return false] exit } if end then { C[begin false, end true, call false, return false] pop exit } C[begin false, end false] After the substitutions above, further Boolean simplifications may be triggered. Also, some assignments may become redundant, such as, for example, beta[3] := beta[3] ; rules to eliminate such assignments are also given. A further optimization on the generated code is possible, but we have not implemented it yet: some subformulae can repeat in different parts of the original formula; the current implementation generates monitoring code for each repeating instance, which is redundant and can be reduced using a smarter optimization algorithm. Example. We here show the monitor generated by our implementation for a more complex ptcaret specification. Suppose that a program carries 243

244 out a critical multi-phase task and the following safety properties must hold when execution enters the second phase: 1. Execution entered the first phase within the same procedure; 2. Resource acquired within same procedure since first phase must be released; 3. Caller of current procedure must have had approval for the second phase; 4. Task is executed directly or indirectly by the procedure safe exec. These can be captured as the following ptcaret formula: enter phase 2 ( ( enter phase 1 S begin) ( acquire S enter phase 1 ( release S c (has phase 2 pass) b(safe exec) Our implementation generates the following monitor for this specification: if begin then { push(beta); beta[0] := safe_exec or beta[0]; beta[1] := enter_ph1 or not acquire and beta[1]; beta[2] := acquire or not release and beta[2]; beta[3] := true; beta[4] := true; output(not enter_ph2 or not beta[4] and alpha[0] and beta[0] and (not beta[2] or beta[1])); alpha[3] := true; alpha[2] := alpha[1]; alpha[1] := has_ph2_pass; alpha[0] := has_ph2_pass; exit } if end then { beta[1] := enter_ph1 or not acquire and beta[1]; beta[2] := acquire or not release and beta[2]; beta[3] := beta[3] and (not alpha[3] or alpha[2]); beta[4] := not enter_ph1 and beta[4]; output(not enter_ph2 or not beta[4] and beta[0] and beta[3] and (not beta[2] or beta[1])); alpha[3] := false; alpha[2] := alpha[1]; alpha[1] := has_ph2_pass; alpha[0] := has_ph2_pass; pop(beta); exit } beta[1] := enter_ph1 or not acquire and beta[1]; 244

245 beta[2] := acquire or not release and beta[2]; beta[3] := beta[3] and (not alpha[3] or alpha[2]); beta[4] := not enter_ph1 and beta[4]; output(not enter_ph2 or not beta[4] and beta[0] and beta[3] and (not beta[2] or beta[1])); alpha[3] := false; alpha[2] := alpha[1]; alpha[1] := has_ph2_pass; alpha[0] := has_ph2_pass The formula contains derived operators, c, which are first expanded. The monitoring code uses four α bits and five β bits (the expanded formula contains four concrete temporal operators and five abstract ones). For example, b(safe exec) is expanded into (begin true) S (begin safe exec), which is then simplified to true S (begin safe exec), equivalent to (begin safe exec). beta[0] in the generated code is used to check this operation; it only needs to be updated at the begin state, where it becomes true if safe exec holds Conclusion and Future Work We presented the logic ptcaret and a monitor synthesis algorithm for it. ptcaret includes abstract variants of past temporal operators. It can express safety properties about procedural programs which cannot be expressed using conventional ptltl. The generated monitors contain both a local state and a stack. The local state is encoded on as many bits as concrete temporal operators the original formula had, while the stack pushes/pops bit vectors of size the number of abstract temporal operators the original formula had. An optimized implementation of the monitor synthesis algorithm has been organized as an MOP logic plugin, and is available to download from [17]. There is room for further optimizations of the generated code. An extensive evaluation of the effectiveness of ptcaret runtime verification on large programs needs to be conducted. On the theoretical side, it would be interesting to explore the relationship between our monitors generated for ptcaret and the nested word automata in [44]; [44] gives an operational monitoring language for nested words based on BLAST s specification language. In contrast, our language is declarative and an operational encoding synthesized automatically. uncommend the next text 245

246 11.6 Auxiliary Material - not included in original paper In this section we show via an example how to generate dynamic programming code for a concrete ptltl-formula. We think that this example would practically be sufficient for the reader to foresee our general algorithm presented in the next subsection. Let p [q, (r s)) s be the ptltlformula that we want to generate code for. The formula states: whenever p becomes true, then q has been true in the past, and since then we have not yet seen the end of r or s. The code translation depends on an enumeration of the subformulae of the formula that satisfies the enumeration invariant: any formula has an enumeration number smaller than the numbers of all its subformulae. Let ϕ 0, ϕ 1,..., ϕ 8 be such an enumeration: ϕ 0 = p [q, (r s)) s, ϕ 1 = p, ϕ 2 = p, ϕ 3 = [q, (r s)) s, ϕ 4 = q, ϕ 5 = (r s), ϕ 6 = r s, ϕ 7 = r, ϕ 8 = s. Note that the formulae have here been enumerated in a post-order fashion. One could have chosen a breadth-first order, or any other enumeration, as long as the enumeration invariant is true. The input to the generated program will be a finite trace t = s 1 s 2...s n of n events. The generated program will maintain a state via a function update : State Event State, which updates the state with a given event. In order to illustrate the dynamic programming aspect of the solution, one can imagine recursively defining a matrix s[1..n, 0..8] of boolean values {0, 1}, with the meaning that s[i, j] = 1 iff t i = ϕ j. Then one can fill the table according to the recursive semantics of past time LTL as described in Subsection This would be the standard way of regarding the above satisfaction problem as a dynamic programming problem. An important observation is, however, that, like in many other dynamic programming algorithms, one doesn t have to store the entire table s[1..n, 0..8], which would be quite large in practice; in this case, one needs only s[i, 0..8] and 246

247 s[i 1, 0..8], which we ll write now[0..8] and pre[0..8] from now on, respectively. It is now only a relatively simple exercise to write up the following algorithm for checking the above formula on a finite trace: State state {}; bit pre[0..8]; bit now[0..8]; Input: trace t = s 1 s 2...s n ; /* Initialization of state and pre */ state update(state, s 1 ); pre[8] s(state); pre[7] r(state); pre[6] pre[7] or pre[8]; pre[5] false; pre[4] q(state); pre[3] pre[4] and not pre[5]; pre[2] p(state); pre[1] false; pre[0] not pre[1] or pre[3]; /* Event interpretation loop */ for i = 2 to n do { state update(state, s i ); now[8] s(state); now[7] r(state); now[6] now[7] or now[8]; 247

248 now[5] not now[6] and pre[6]; now[4] q(state); now[3] (pre[3] or now[4]) and not now[5]; now[2] p(state); now[1] now[2] and not pre[2]; now[0] not now[1] or now[3]; if now[0] = 0 then output( property violated ); pre now; }; In the following we explain the generated program. Declarations Initially a state is declared. This will be updated as the input event list is processed. Next, the two arrays pre and now are declared. The pre array will contain values of all subformulae in the previous state, while now will contain the value of all subformulae in the current state. Initialization The initialization phase consists of initializing the state variable and the pre array. The first event s 1 of the event list is used to initialize the state variable. The pre array is initialized by evaluating all subformulae bottom up, starting with highest formula numbers, and assigning these values to the corresponding elements of the pre array; hence, for any i {0... 8} pre[i] is assigned the initial value of formula ϕ i. The pre array is initialized in such a way as to maintain the view that the initial state is supposed stationary before monitoring is started. This in particular means that p is false, as well as is (r s), since there is no change in state (indexes 1 and 5). The interval operator has the obvious initial interpretation: the first argument must be true and the second false for the formula to be true (index 3). Propositions are true if they hold in the initial state (indices 2, 4, 7 and 8), and boolean operators are interpreted the standard way (indices 0, 6). 248

249 Event Loop The main evaluation loop goes through the event trace, starting from the second event. For each such event, the state is updated, followed by assignments to the now array in a bottom-up fashion similar to the initialization of the pre array: the array elements are assigned values from higher index values to lower index values, corresponding to the values of the corresponding subformulae. Propositional boolean operators are interpreted the standard way (indices 0 and 6). The formula p is true if p is true now and not true in the previous state (index 1). Similarly with the formula (r s) (index 5). The formula [q, (r s)) s is true if either the formula was true in the previous state, or q is true in the current state, and in addition (r s) is not true in the current state (index 3). At the end of the loop an error message is issued if now[0], the value of the whole formula, has the value 0 in the current state. Finally, the entire now array is copied into pre. Given a fixed ptltl formula, the analysis of this algorithm is straightforward. Its time complexity is Θ(n) where n is the length of the input trace, the constant being given by the size of the ptltl formula. The memory required is constant, since the length of the two arrays is the size of the ptltl formula. However, one may want to also include the size of the formula, say m, into the analysis; then the time complexity is obviously Θ(n m) while memory required is 2 (m + 1) bits. The authors conjecture that it s hard to find an algorithm running faster than the above in practical situations, though some slight optimizations are possible (see Section ) The Algorithm Formalized We now formally describe our algorithm that synthesizes a dynamic programming algorithm from a ptltl-formula. It takes as input a formula and generates a program as the one above, containing a for loop which traverses the trace of events, while validating or invalidating the formula. The generated program is printed using the function output, which takes one or more string or integer parameters which are concatenated in the output. This algorithm is designed to generate pseudocode, but it can easily be adapted to generate code in any imperative programming language: Input: past time LTL formula ϕ let ϕ 0, ϕ 1,..., ϕ m be the subformulae of ϕ; 249

250 output( State state {}; ); output( bit pre[0..m]; ); output( bit now[0..m]; ); output( Input: trace t = s 1 s 2...s n ; ); output( /* Initialization of state and pre */ ); output( state update(state, s 1 ); ); for j = m downto 0 do { output( pre[, j, ] ); if ϕ j is a variable then output(ϕ j, (state); ); if ϕ j is true then output( true; ); if ϕ j is false then output( false; ); if ϕ j = ϕ j then output( not pre[,j, ]; ); if ϕ j = ϕ j1 op ϕ j2 then output( pre[,j 1, ] op pre[,j 2, ]; ); if ϕ j = ϕ j1 then output( pre[, j 1, ]; ); if ϕ j = ϕ j1 then output( pre[, j 1, ]; ); if ϕ j = ϕ j1 then output( pre[, j 1, ]; ); if ϕ j = ϕ j1 S s ϕ j2 then output( pre[, j 2, ]; ); 250

251 if ϕ j = ϕ j1 S w ϕ j2 then output( pre[,j 1, ] or pre[, j 2, ]; ); if ϕ j = [ϕ j1, ϕ j2 ) s then output( pre[,j 1, ] and not pre[, j 2, ]; ); if ϕ j = [ϕ j1, ϕ j2 ) w then output( not pre[, j 2, ]; ); if ϕ j = ϕ j if ϕ j = ϕ j then output( false; ); then output( false; ); }; output( /* Event interpretation loop */ ); output( for i = 2 to n do { ); for j = m downto 0 do { output( now[, j, ] ); if ϕ j is a variable then output(ϕ j, (state); ); if ϕ j is true then output( true; ); if ϕ j is false then output( false; ); if ϕ j = ϕ j then output( not now[,j, ]; ); if ϕ j = ϕ j1 op ϕ j2 then output( now[,j 1, ] op now[, j 2, ]; ); if ϕ j = ϕ j1 then output( pre[, j 1, ]; ); if ϕ j = ϕ j1 then output( pre[, j, ] or now[,j 1, ] ); if ϕ j = ϕ j1 then output( pre[, j, ] and now[,j 1, ] ); 251

252 if ϕ j = ϕ j1 S s ϕ j2 then output( (pre[, j, ] and now[,j 1, ]) or now[, j 2, ]; ); if ϕ j = ϕ j1 S w ϕ j2 then output( (pre[, j, ] and now[,j 1, ]) or now[, j 2, ]; ); if ϕ j = [ϕ j1, ϕ j2 ) s then output( (pre[, j, ] or now[,j 1, ]) and not now[, j 2, ]; ); if ϕ j = [ϕ j1, ϕ j2 ) w then output( (pre[, j, ] or now[,j 1, ]) and not now[, j 2, ]; ); if ϕ j = ϕ j then output( now[, j, ] and not pre[, j, ]; ); if ϕ j = ϕ j then output( not now[, j, ] and pre[, j, ]; ); }; output( if now[0] = 0 then output( property violated ); ); output( pre now; ); output( } ); op is any binary propositional connective. Since we have already given a detailed explanation of the example in the previous section, we shall only give a very brief description of this algorithm. The formula should be first visited top down to assign increasing numbers to subformulae as they are visited. Let ϕ 0, ϕ 1,..., ϕ m be the list of all 252

253 subformulae. Because of the recursive nature of ptltl, this step ensures us that the truth value of t i = ϕ j can be completely determined from the truth values of t i = ϕ j for all j < j m and the truth values of t i 1 = ϕ j for all j j m. Before we generate the main loop, we should first generate code for initializing the array pre[0..m], basically giving it the truth values of the subformulae on the initial state, conceptually being an infinite trace with repeated occurrences of the initial state. After that, the generated main event loop will process the events. The loop body will update/calculate the array now and in the end will move it into the array pre to serve as basis for the next iteration. After each iteration i, now[0] tells whether the formula is validated by the trace s 1 s 2...s i. Since the formula enumeration procedure is linear, the algorithm synthesizes a dynamic programming algorithm from an ptltl formula in linear time with the size of the formula. The boolean operations used above are usually very efficiently implemented on any microprocessor and the arrays of bits pre 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. We shall next illustrate how such optimizations can be part of the translation algorithm. Exercises Exercise 17 Suppose that the set A of state predicates in Definition 49 contains only the four special predicates call, begin, end and return. Give an example of a safety property over ptcaret traces that is not expressible using the ptcaretlogic formalism. Exercise 18 Specify using ptcaretthe following property: function f can only be called, directly or indirectly, via function g, and at most once. In other words, no invocation of g can result in invoking f twice, directly or indirectly, and an invocation of f can only happen from within an invocation of g, directly or indirectly. Exercise 19 Consider the four derived operators in Section Modify the ptcaret monitor synthesis algorithm in Section , as well as the related results that yield its correctness, to generate monitoring code directly 253

254 for these derived operators, without first desugaring them to the standard operators. Exercise 20 Following a similar idea as in Exercise 16, explain how to generate a push-down automaton from a ptcaret monitor generated using the technique discussed in this chapter. Explain also the advantages and disadvantages of using such a push-down automata as monitors. 254

255 Chapter 12 Efficient Monitoring of Parametric Context-Free Patterns Material from [169], which extends [50] Abstract: Recent developments in runtime verification and monitoring show that parametric regular and temporal logic specifications can be efficiently monitored against large programs. However, these logics reduce to ordinary finite automata, limiting their expressivity. For example, neither can specify structured properties that refer to the call stack of the program. While context-free grammars (CFGs) are expressive and well-understood, existing techniques for monitoring CFGs generate large runtime overhead in real-life applications. This paper demonstrates that monitoring parametric CFGs is practical (with overhead on the order of 12% or lower in most cases). We present a monitor synthesis algorithm for CFGs based on an LR(1) parsing algorithm, modified to account for good prefix matching. In addition, a logic-independent mechanism is introduced to support matching against the suffixes of execution traces Introduction Runtime verification (RV) is a relatively new formal analysis approach in which specifications of requirements are given together with the code to 255

256 check, as in traditional formal verification, but the code is checked against its requirements at runtime, as in testing. A large number of runtime verification approaches and systems, including TemporalRover [74], JPaX [106], JavaMaC [141], Hawk/Eagle [69], Tracematches [7, 20], J-Lo [32], PQL [166], PTQL [99], MOP [52, 51], Pal [45], RuleR [25], etc., have been developed recently. In a runtime verification system, monitoring code is generated from the specified properties and integrated with the system one wishes to monitor. Therefore, a runtime verification approach consists of at least three interrelated aspects: (1) a specification formalism, used to state properties to monitor, (2) a monitor synthesis algorithm, and (3) a program instrumentor. The chosen specification formalism determines the expressivity of the runtime verification approach and/or system. Monitoring safety properties is arbitrarily complex [204]. Recent developments in runtime verification, however, show that regular and temporallogic-based formal specifications can be efficiently monitored against large programs. As shown by a series of experiments in the context of Tracematches [20] and JavaMOP [52], parametric regular and temporal logic specifications can be monitored against large programs with little runtime overhead, on the order of 12% or lower. However, both regular expressions and temporal logics reduce to ordinary finite automata when monitored, so they have inherently limited expressivity. More specifically, most runtime verification approaches and systems consider only flat execution traces, or execution traces without any structure. Consequently, users of such runtime verification systems are prevented from specifying and checking structured properties, those properties referring to the program structure such as properties with requirements on the contents of the program call stack. Examples of such structured safety properties include a resource should be released in the same method which acquired it or a resource cannot be accessed if the unsafe method foo is in the current call stack Example An important and desirable category of properties that cannot be expressed using regular patterns is one in which pairs of events need to match each other, potentially in a nested way. For example, suppose that one prefers to use one s own locking mechanism for thread synchronization. As usual, for multiple reasons including the allowance of re-entrant synchronized methods (in particular to support recursion), locks are allowed to be acquired and released multiple times by any given thread. However, the lock is effectively 256

257 Relevant trace starts here + = acquire = release Second violation: Unmatched release First violation: No release before end + + Figure 12.1: Example trace for structured acquire and release of locks. released, so that other threads can acquire it, only when the lock releases match the lock acquires. One may want to impose an even stronger locking safety policy: the lock releases should match the lock acquires within the boundaries of each method call. This property is vacuously satisfied when locks are acquired and released in a structured manner using synchronized blocks or methods, like in Java 4+, but it may be easily violated when one implements one s own locking mechanism or uses the semaphores available in Java 5. For example, Figure 12.1 shows an execution violating this basic safety policy twice (each deeper level symbolizes a nested method invocation). First, the policy is violated when one returns from the last (nested) method invocation because one does not release the acquired lock. Second, the policy is also violated immediately after the return from the last method invocation because the lock is released twice by its caller, but acquired only once. Supposing that the system is instrumented to emit events begin and end when methods of interest are started and terminated, and that the events acquire and release are triggered when the lock of interest is acquired and released, respectively, then here is an initial, straightforward way to express this safety policy as a context-free grammar: S ɛ S begin S end S acquire S release Because of the production S ɛ, the pattern is able to terminate (ɛ is the empty trace). This pattern will match any trace with begin events in balance with end events because these two events occur only in the production 257

258 perthread SafeLock(Lock l) { event acquire before(lock l) : call(* Lock.acquire()) && target(l) {} event release after(lock l) : call(* Lock.release()) && target(l) {} event begin before() : execution(* *.*(..)) &&!within(lock) {} event end after() : execution(* *.*(..)) &&!within(lock) {} lr_lazy : S -> epsilon S acquire M release A, M -> epsilon M begin M end M acquire M release, A -> epsilon A begin A { System.out.println("Unsafe lock operation found!"); } } Figure 12.2: JavaMOP specification for the safe lock safety property using the CFG plug-in. S S begin S end 1, where they are matched. The S at the beginning of the production allows an unbounded number of these balanced groupings in a row, e.g., begin begin end end begin end is a valid trace with two balanced groupings in a row. The production S S acquire S release is similar to that with begin and end events, allowing balanced groupings of acquire and release events. Because all the productions have the same recursive symbol, S, it is possible for begin/end pairs to nest within acquire/release pairs, and vice versa. Thus a valid trace would be begin acquire begin end release end begin acquire acquire release release end. This pattern is simple and works, however, it has a deficiency in that it must monitor every begin of every method in a given program, even those which do not perform any thread synchronization. Next, we present a more efficient grammar that ignores begin events that happen before the first acquire event in a trace 2. The next grammar looks complicated, but we must stress that it is only complicated to improve monitoring efficiency. S ɛ S acquire M release A M ɛ M begin M end M acquire M release A ɛ A begin A end Again, the productions begin with recursive references to S to allow for repetition of balanced groupings. This time, however, the S productions only allow for acquire and release, not begin and end. This ensures that 1 Note that S a b is shorthand for S a, S b. 2 Events that occur before the first event in a valid trace are ignored by the monitoring algorithm. Events which begin a valid trace are known as creation events [52]. 258

259 only begins and ends occurring after the first acquire are monitored. The non-terminal M stands for matched sub-traces, i.e., traces in which all the pairs begin/end and acquire/release are properly matched, and A stands for sequences of (not necessarily matched) begin and end events. The A productions are necessary because failures would be reported for end events at the end of a trace due to the lack of a matching begin that occurred before the acquire creation event. The A productions also allow for more begin events after the last release because we do not want method calls after the last release to cause the invocation of the failure handler. Any (finished or unfinished) execution trace that is not a prefix of a word in the language of S in the context-free grammar (CFG) above is an execution that violates the safety policy. The CFG runtime verification technique presented in this paper and implemented as a logic-plug-in in JavaMOP is able to monitor safety properties expressed as CFGs like above 3. Monitoring- Oriented Programming (MOP) and JavaMOP (the Java implementation of MOP) are discussed in Section Figure 12.2 shows this SafeLock property expressed as a JavaMOP specification, using the CFG logic plug-in. The modifier perthread tells JavaMOP to consider events from separate threads as separate traces. This is particularly important as we do not wish begins and ends of separate threads to cause the pattern to fail. SafeLock denotes the name of the specification, while the list after SafeLock is the list of parameters to the specification (see below). SafeLock is parametric in the Lock because we do not wish the releases and acquires of separate Locks to interfere. The keyword event introduces an event; the event is first given a name, and then its trigger is defined using an AspectJ [139] advice (before the colon) and a pointcut (after the colon). Of particular note, however, is!within(lock), used so that we do not monitor the begins and ends of the acquire and release methods, which would cause the pattern to fail. JavaMOP s generic approach to parametric specifications is described in [54] and [50]. Because of this generic approach, the logical formalisms in which properties are expressed need not be aware of the parameters; parameters are added automatically and generically by the JavaMOP framework. The keyword lr lazy introduces the CFG pattern. Three other possible keywords can be used : lr, lalr, and lalr lazy. The two lazy keywords mean 3 Our CFG plug-in actually supports only the LR(1) and LALR(1) languages; when we use the term context-free we actually mean LR(1)/LALR(1), unless explicitly mentioned otherwise. 259

260 that when an event is encountered that causes a pattern match failure, the failure handler is invoked, but the event itself is not kept in the monitor state so that more failures can be found. If the error causing event were kept, as in the non-lazy keywords, each following event would cause an error, regardless of whether it should. The lazy method is how most programming language parsers work, allowing multiple syntax errors to be caught in one parse. lr and lalr determine which table generation algorithm is used (see Section 12.5 for more information on the two table generation algorithms). The first nonterminal in the pattern is assumed to be the start symbol of the grammar (see Section ). introduces a pattern failure handler. The code within the braces runs whenever the pattern fails to match because an invalid event for a given point in a trace is seen. As an alternative, JavaMOP handlers. This gives extra power to our CFG plug-in because context-free languages are not closed under complementation. The code generated automatically from the JavaMOP specification in Figure 12.2, following the technique described in the rest of the paper, has more than 700 lines of (human unreadable) AspectJ code. We ran this property against a hand-crafted program, which generated the sequence of events seen in Figure Both pattern failures were successfully caught in a single run because the CFG plug-in does not add failure inducing events to the monitor state when lr lazy is used. If the keyword lr is used instead of lr lazy, only the first failure is caught by the generated monitor Contributions Several approaches have been proposed to monitor context-free properties. For example, Program Query Language (PQL) [166] is based on a description language that encompasses the intersection of context-free languages. Hawk/Eagle [69] uses a fix-point logic and RuleR [25] uses a rule based logic that can specify context-free properties. These approaches propose what we feel are rather complex solutions for monitoring parametric context-free patterns. They generate inefficient monitoring code in many cases, thus preventing practical parametric context-free property monitoring with these systems. The inefficiency of PQL in comparison to JavaMOP with contextfree patterns is discussed in Section This paper shows that monitoring (the LR(1) and LALR(1) subsets of) parametric context-free patterns is practical. We generate non-parametric monitors instead of parsers for the defined context-free pattern. Parameters are handled separately, using the 260

261 algorithm in [54, 50]. This way, we provide an efficient system for monitoring parametric context-free properties. Our algorithm is totally different from the monitoring algorithm used by the PQL system [166], which mixes the handling of parameters and monitoring of context-free patterns. When monitoring pattern languages, such as extended regular expressions (ERE) or CFG, we wish to report a match anytime a trace at a given point in program execution matches the pattern. For example, if we have a pattern that is looking for writes to a closed file, we might use the ERE close write write*. We wish to report a match on every write, so that we can locate all of the trouble spots in the program. We call this matching every good prefix of the trace because close write is a prefix of close write write which is a prefix of close write write write, and we wish for a match to be reported on each of these prefix traces. We provide two methods to deal with the problem of monitoring good prefixes. One is to modify the LR(1) parsing algorithm with a stack copying process. The second method, called guaranteed acceptance, was discovered after our work in [168]. Additionally, we extended JavaMOP with suffix matching. Suffix matching is, informally, matching against every suffix of a given trace, and is the mode of matching used in Tracematches [20]. A definition of suffix matching can be found in Section We also describe optimizations particular to the JavaMOP suffix matching algorithm that improve the efficiency of suffix matching in JavaMOP. We also performed an extensive evaluation of the CFG monitoring algorithm using the DaCapo [30] benchmark suite and properties used previously to evaluate runtime verification systems [52, 35]. The properties are expressed as CFGs in this evaluation rather than regular expressions for use in JavaMOP. Even when monitored using the CFG plug-in, however, these regular pattern based specifications still use constant space. We thus performed an evaluation of three strictly context-free properties which use theoretically unbounded space to show that, even with such properties, the overhead is reasonable, and to show the usefulness of context-free properties. The results of this analysis compare favorably with PQL and Tracematches, two state-of-the-art runtime monitoring systems. One of these properties (ImprovedLeakingSync) is expressible in neither PQL nor Tracematches, for reasons explained in Section Another of the properties (SafeFileWriter), while expressible in PQL, is not expressible in Tracematches because Tracematches has limited ability to express structured properties, rather than the full generality of the LR(1) languages. 261

262 Over both the adapted regular properties and the new strictly contextfree properties, the overhead of JavaMOP with CFGs is, on average, over 8 times less than Tracematches on properties that Tracematches is able to express, and over 12 times less than PQL on properties that can be expressed in PQL. On all but 9 of the 45 benchmark/property pairs that generated events 4, the overhead is less than 5% in JavaMOP with CFGs. Beyond the work of [168], we have implemented three more versions of the original LR(1)-based cfg plug-in. We now support LALR(1) and a version of both LR(1) and LALR(1) that stay in an error state when an error token is encountered (using la/lalr instead of lr/lalr lazy). We discovered and proved the concept of guaranteed acceptance (see Section ). We also provide more a comprehensive analysis of the experiments as presented in [168] Paper Outline The remainder of the paper is as follows: Section 13.2 illustrates related work; Section 12.3 gives a brief overview of MOP and JavaMOP; Section 12.4 describes suffix matching together with its novel, optimized implementation in JavaMOP; Section 12.5 explains our CFG monitor synthesis technique in JavaMOP, including considerations for suffix matching; Section 12.6 explains our experimental setup and the results of our experiments; Section concludes the paper and describes some future work Related Work Runtime Monitoring Many approaches have been proposed to monitor program execution against formally specified properties. Interested readers can refer to [52] for an extensive discussion on existing runtime monitoring approaches. Briefly, all runtime monitoring approaches except MOP [51, 48, 52] have their specification formalisms hardwired and only two of them share the same logic (LTL). MOP will be discussed in Section This observation strengthens our belief underlying MOP there probably is no silver-bullet specification formalism for all purposes. Also, most approaches focus on detecting either violations (pattern failures in CFG) or validations (pattern matches in CFG) 4 Overall there are 66 benchmark/property pairs, but 21 of them generate no events, and are removed to more fairly represent the overhead of runtime monitoring. 262

263 Approach Logic Scope Mode Handler JPaX [106] LTL class offline violation TemporalRover [74] MiTL class inline violation JavaMaC [141] PastLTL class outline violation Hawk [69] Eagle global inline violation RuleR [25] RuleR global inline violation Tracematches [20] Reg. Exp. global inline validation J-Lo [32] LTL global inline violation Pal [45] modified Blast global inline validation PQL [166] PQL global inline validation PTQL [99] SQL global outline validation Table 12.1: Runtime Verification Breakdown. of the desired property and support only fixed types of monitors, e.g., online monitors that run together with the monitored program or offline monitors that check the logged execution trace after program termination. Specifically, there are four orthogonal attributes of a runtime monitoring system: logic, scope, running mode, and handlers. The logic specifies which formalism is used to specify the property. The scope determines where to check the property; it can be class invariant, global, interface, etc. The running mode denotes where the monitoring code runs; it can be inline (weaved into the code), online (operating at the same time as the program), outline (receiving events from the program remotely, e.g., over a socket), or offline (checking logged event traces) 5. The handlers specify what actions to perform under exceptional conditions; there can be violation and validation handlers. It is worth noting that for many logics, violation and validation are not complementary to each other, i.e., the violation of a formula does not always imply the validation of the negation of the formula. Most runtime monitoring approaches can be framed in terms of these attributes, as illustrated in Table For example, JPaX can be regarded as an approach that uses linear temporal logic (LTL) to specify class-scoped properties, whose monitors work in offline mode and only detect violation. In general, JavaMOP has proven to be the most efficient of the runtime monitoring systems despite being generic in logical formalism. Of the systems mentioned in Table 12.1, only PQL [166], Hawk/Eagle [69], 5 Offline implies outline, and inline implies online. 263

264 and RuleR [25] can handle arbitrary context-free properties. Hawk/Eagle adopts a fix-point logic and uses term rewriting during the monitoring, making it rather inefficient. It also has problems with large programs because it does not garbage collect the objects used in monitoring. In addition, Hawk/Eagle is not publicly available 6. Because of this and the fact that Hawk/Eagle has not been run on DaCapo [30] with the same properties, we cannot compare our CFG plug-in with Hawk/Eagle. RulerR is a simplification of Eagle that is rule based rather than µ-calculus based, but it still has the ability to specify context-free properties. The current implementation is not built for efficiency or ease of expression with regards to context-free properties. In addition to PQL, we decided to perform comparisons with Tracematches [20], as it is able to monitor a very limited set of context-free properties using compiler-specific support provided by their special AspectJ compiler, ABC [18], and because it is a very efficient system. Pal [45] is able to monitor properties that take calls and returns into account, giving a limited context-free ability for this one case. Pal is implemented for C, rather than Java, and the implementation is not publicly available Context-free Grammars in Testing and Verification Context-free grammars have seen use in several areas of testing and verification not immediately related to runtime monitoring. Attributed context-free grammars were used as a means to generated test input and output pairs by [77]. The generated test inputs and outputs could be used both the test the specification from which the test grammar was designed, as well as the final implementation of a specification, using automatic test drivers. Their test case generator was capable of generating test cases from the grammar both randomly and systematically. The attributes of the context-free test generation grammars allow a user to attach context sensitive information to parts of the grammar, and allow for refinement of test case generation in order to avoid redundant test cases. Earlier attempts of test case generation via grammars [184, 104, 128] were employed to generate test input only for compilers and parsers rather than programs and their specifications (though, [76] used grammars to generate test cases in much 6 [20] makes an argument for the inefficiency of Hawk/Eagle. Since Hawk/Eagle is not publicly available (only its rewrite based algorithm is public [69]), the authors of Hawk/Eagle kindly agreed to monitor some of the simple properties from [35]. We have confirmed the inefficiency claims of [20] with the authors of Hawk/Eagle. 264

265 the same way, it could not generate outputs, and it generated far too many similar test cases). In [167] context-free grammars were used to generate test data for VLSI (very large scale integration) circuits. [212] applied the concept of test case generation using context-free grammars to Java virtual machine implementations. All of these approaches differ quite a bit from runtime monitoring. The overhead of these approaches is not nearly so important because they are used to generate test cases in an offline manner, rather than running at the same time as a program that is under testing or a production system, situations for which runtime monitoring is intended. Additionally, runtime monitoring attempts to monitor behavior of a system rather than to generate test cases. [131] used context-free grammars for an entirely different purpose that is more related to runtime monitoring than is test case generation. They created an interface specification language that uses context-free grammars to provide stub code for model checking. The grammar specifies the sequence of method invocations allowed by the component. The stubs are called by the code under model checking, providing a means of modular model checking. T The grammar generated stubs execute during the model checking process to ensure that the non-stub portions of code always follow the specification of method calls given by the grammar. In this way it is similar to runtime monitoring with context-free grammars, as the grammar is used to specify intended behavior, and flag errors when the behavior is not followed at runtime. Our work differs primarily in that it is designed to enforce behavior in a running system rather than to abstract a coponent, and in that it is parametric, whereas the interface grammars are not MOP Revisited MOP is an extensible runtime verification framework that provides efficient, logic-independent support for parametric specifications. JavaMOP is an implementation of MOP for the Java programming language. By encapsulating our monitor synthesis algorithm for non-parametric CFG patterns in a JavaMOP logic plug-in, we have achieved an efficient monitoring tool for universally quantified parametric CFG specifications. Additionally, we have implemented a novel extension of MOP, in Java- MOP, to support suffix matching independent of the language used for pattern specification. We define suffix matching as matching against ev- 265

266 ery suffix of a given event trace, while total matching, also supported by JavaMOP, attempts to match the entire trace seen at a particular point 7. Because Tracematches supports only suffix matching, and PQL supports a skip semantics more akin to suffix matching than total, we use suffix matching in our experiments MOP in a Nutshell MOP [51, 48, 52] is a formal framework for software development and analysis, in which the developer specifies desired properties using formal specification languages, along with code to execute when properties are matched or fail to match. Monitoring code is then automatically generated from the specified properties and integrated together with the user-provided code into the original system. MOP is a highly extensible and configurable runtime verification framework. The user is allowed to extend the MOP framework with his/her own logics via logic plug-ins which encapsulate the monitor synthesis algorithms. This extensibility of MOP is supported by an especially designed layered architecture [51], which separates monitor generation and monitor integration. By standardizing the protocols between layers, modules can be added and reused easily and independently. In addition to choosing the formalism for a specification, one can also configure the behaviors of the generated monitor through different attributes [48]. Depending upon configuration, the monitors can be separate programs reading events from a log file, from a socket, or from a buffer, or can be inlined within the program at the event observation points; monitors can verify the observed execution trace as a whole or check fragments of the trace. All these configurations are independent of the formalism used to specify the property. MOP also provides efficient and logic-independent support for universally quantified parameters [50], which is useful for specifying properties related to more than one object. This extension allows associating parameters with MOP specifications and generating efficient monitoring code from parametric specifications with monitor synthesis algorithms for nonparametric specifications. MOP s generic support for universally quantified patterns simplified our CFG plug-in s implementation. The JavaMOP implementation provides several interfaces, including a web-based interface, a command-line interface, and an Eclipse-based GUI, 7 At a given point in program execution the trace of events seen at that point is evaluated as a complete trace in both total and suffix matching. This means if one is monitoring the pattern e, a match must be reported every time the e event occurs. 266

267 providing the developer with different means to manage and process MOP specifications. JavaMOP follows a client-server architecture to flexibly support these various interfaces, as well as for portability reasons [49]. AspectJ [139] is employed for monitor integration: JavaMOP translates outputs of logic-plug-ins into AspectJ code, which is then merged within the original program by the AspectJ compiler. Five logic-plug-ins are currently provided with JavaMOP: Java Modeling Language (JML) [156], Extended Regular Expressions (ERE), Past-Time and Future-time Linear Temporal Logics (LTL) (see [49] for more details), and Context-Free Grammar (CFG) that is introduced in this paper. Note that these plug-ins can be supported by any implementation of MOP. One might expect some loss of efficiency for MOP s genericity of logics. However, the JavaMOP-generated monitors yield very reasonable runtime overhead in practice, even for properties requiring intensive runtime checking (on the order of 12% or lower). In most cases it is as efficient as hand optimized monitoring code [52] Suffix Matching in JavaMOP These foundations should be moved in Chapter 4 and/or Chapter 1. According to application requirements, one may want to check a property against either the whole execution trace or every suffix of a trace. Total matching has been adopted by many runtime verification approaches to detect pattern failures of properties, e.g., JPaX [106] and JavaMaC [141]. Suffix matching has been used mainly by monitoring approaches that aim to find pattern matches of properties, e.g., Tracematches [20]. PQL has a skip semantics, wherein a specification is matched against the trace, but events may be skipped. A precise explanation of PQL s semantics is available in [166]. To define suffix and total matching, we first must define traces and properties: Definition 1 Let E be a set of events. An E-trace, or simply a trace when E is understood from context, is any finite sequence of events in E, that is, an element in E. In the context of monitoring, an execution trace is a sequence of events observed up to the current moment, thus execution traces are always finite. 267

268 Definition 2 An E-property P, or simply a property, is a pair of disjoint sets (P +, P ) where P + E and P E ; P + is the set of pattern matching traces and P is its set of pattern failing traces 8. Therefore, our notation of property is quite general; for each particular specification formalism, one needs to associate an appropriate property to each formula or pattern in that formalism. For example, for a CFG G, we let P G = (P +, P ) be defined as expected: P + is L(G) (the language of G, see Section ) and w P iff w is not the prefix of any w L(G) Definition 3 The total matching semantics of P is a function JP K total : E {match, fail,?} defined as follows for each w E : match if w P + JP K total (w) = fail if w P? otherwise The suffix matching semantics of P is a function JP K suffix : E {match,?} defined as follows for each w E : match if there are w 1, w 2 such that w = w 1 w 2 and JP K JP K suffix (w) = total (w 2 ) = match? otherwise As an example of where suffix matching is useful, consider the HasNext property. This property specifies that the Java API method hasnext must be called before every call of next for an Iterator. If we use total matching and a match handler, we must define the pattern as (using a regular expression) (hasnext + (hasnext next))* next next, to allow for all of the hasnext events, or correct uses of next that may occur before the two temporally adjacent calls to next. If we use suffix matching, because all suffixes of the trace are 8 More recently we have generalized this concept to generic categories beyond just match and fail [54, 50]. However, match and fail are sufficient for context-free patterns. 268

269 tried, the pattern next next is sufficient, so long as the hasnext event is still defined. The previous design of JavaMOP supported only total matching. We have implemented a logic-independent extension of JavaMOP to also support suffix matching. This extension is based on the observation that, although total matching and suffix matching have inherently different semantics, it is not difficult to support suffix matching in a total matching setting, if one maintains a set of monitor states during monitoring and creates a new monitor instance at each event (this amounts to checking the property on each suffix incrementally). However, the situation becomes more complicated when one wants to develop a logic-independent solution, since different logical formalisms can have different state representations. For example, the monitor state can be an integer when the monitor is based on a state machine, a vector like the past-time LTL monitor, or a stack such as the CFG monitor discussed below. Hence, our solution is to treat every monitor as a blackbox without assumptions on its internal state. Also, instead of maintaining a set of monitor states in the monitor, we use a wrapper monitor that keeps a set of total matching monitors as its state for suffix matching. For simplicity, from now on, when we say monitor without specific constraints, we mean the monitor generated for total matching. When an event is received, the wrapper monitor for suffix matching operates as follows: 1. create a new monitor and add it to the suffix matching monitor set; 2. invoke every monitor in the monitor set to handle the received event; 3. if a monitor enters its pattern fail state, remove it from the monitor set; 4. if a monitor enters its pattern match state, report the pattern match. The third step is used to keep the suffix matching monitor set small by removing unnecessary monitors. Indeed, this implements suffix matching semantics because each total monitor is monitoring a suffix of the current trace, and pattern match is only reported if one of the suffixes is valid. Using our current implementation of suffix matching in JavaMOP, one may further improve the monitoring efficiency if the monitor provides an equals method that compares two monitors with regard to their internal states, and a hashcode method used to reduce the amount of calls to equals. This interface is used to populate a Java HashSet: the combination of 269

270 the definition of hashcode and equals ensures the monitors in the HashSet are declared duplicates, and removed, based on monitor state rather than memory location. This interface can be easily generated by each JavaMOP logic plug-in because it has full knowledge of the monitor semantics. It is important to note that our approach does not depend on the underlying specification formalism. JavaMOP requires that the logic plug-in designate creation events that are the starting events of a validating trace, in order avoid creating unnecessary monitors. A new monitor instance needs to be created only when creation events occur. This feature is especially useful when combined with suffix matching, which otherwise requires creating a new monitor at every event Context-Free Patterns in JavaMOP We support the LR(1) subset of context-free grammars (CFGs), as well as LALR(1) which is a subset of LR(1). LR(1) is so named because it parses input Left to right and produces a Right-most derivation. The 1 denotes that one token of look-ahead is used. The LA in LALR stands for look-ahead, because, under certain conditions,states in the LR(1) table with different look-aheads may be merged in the LALR(1) table (see Section ). LR(1) can only recognize a subset of the deterministic context-free languages, which are themselves a strict subset of the context-free languages (CFLs) [5, 127]. LR(1), however, is an expressive subset, able to define the syntaxes of most modern programming languages. We chose LR rather than LL because LR recognizes a larger number of grammars without translation. We base our implementation on the Knuth algorithm [143] for LR(1) parser table generation as presented in [5]. While the action and goto tables generated are normal LR(1) action and goto tables, the algorithm used to parse has been modified to work in the context of monitoring, explained in detail below. We added a plug-in, which generates LALR(1) using the algorithm presented in [5], because LALR(1) generates smaller tables in some cases. The LALR(1) tables are, at worst, identical to the LR(1) tables; they are never larger. The downside of LALR(1), however, is that it is a strict subset of LR(1). Comparisons between the table size of LR(1) and LALR(1) for the properties we tested can be found in Section 12.6, and an explanation of the LALR(1) optimization can be found in Section Sections cover standard issues related to LR parsing from a monitoring context, while the remainder of Section 12.5 covers new issues 270

271 specific to adapting LR parsing to monitoring Preliminaries Some of these should go in Chapter 1 A CFG G is defined as a tuple of the form, G = (NT, Σ, P, S). Σ, the alphabet of the CFG, is often referred to as the set of terminals. A very special terminal, $, represents the end of the input. NT is the set of nonterminals. P is the set of productions, which define what strings nonterminals derive. NT Σ is often called the set of symbols. Productions have the form A γ, where A NT and γ is a string that either consists of symbols, or is the empty string, ɛ, i.e. γ (Σ NT ). We use the conventional alternation operator, : a production of the form A γ 0 γ 1 can be equivalently represented as two productions A γ 0 and A γ 1. S is the start symbol that non-terminal from which all strings in the language are derived. For example, G = ({A}, {a, b}, P 0, A) where P 0 = {A aab ɛ} is a simple CFG for the language {a n b n n N}. The non-terminal A can derive aab an indeterminate amount of times before deriving ɛ, allowing a n b n for any n N. Two important sets are defined for every non-terminal in a grammar: the first and follow sets. These are used in action table construction. The first set will be used to decide which terminals in the given grammar define monitor creation events (we shall be more specific about this below). The follow set will be useful in illustrating the fundamental challenge of monitoring CFGs. The first set of a non-terminal A, denoted first(a), is the set of all terminals t such that the sub-strings which reduce to A may possibly begin with t. The follow set, denoted follow(a), is the set of terminals which follow the strings which reduce to A. These terminals signify a reduction by A. A reduction is the step whereby a right hand side of a production, γ, is replaced by the left hand side non-terminal in the production. For example, if we have the string aaabbb, and we are using our example grammar, G, we can perform a reduction with γ = ɛ resulting in aaaabbb. We can then perform another reduction with γ = aab resulting in aaabb, eventually we reach aab, which reduces to A (and because A is the start symbol, aaabbb must be in L(G), where L(G) represents the language derived by G). 271

272 CFG Monitoring Algorithms We developed the CFG monitoring algorithms based on existing parsing algorithms. Action and goto tables are generated from the given CFG pattern and used to advance the monitor at runtime according to the observed events. Moreover, the CFG monitor is unaware of relevant parameters since they are handled by the underlying MOP framework. This greatly simplifies the monitoring algorithm. We next introduce the monitor algorithms in more detail. CFG Simplification The CFG plug-in first applies some standard simplifications to the given grammar [127]. The first step of simplification is the removal of non-generating nonterminals (A is non-generating if s Σ, s cannot be reduced to A in one or more reductions). The next step in the simplification process is the removal of nonterminals which are unreachable from the start symbol (A is unreachable from the start symbol if there is no string γ that contains A and reduces to the start symbol in any number of steps). The last step removes ɛ-productions from the grammar. After ɛ-productions have been removed from G, resulting in G, L(G ) = L(G) ɛ. 9 A monitor matching the empty event trace has little utility, so we feel this is a fair compromise. Tables and Monitoring After simplification, the tables are generated for a deterministic push-down automaton (DPDA), that is, a deterministic finite automaton with a stack, which is to be used as a monitor. The algorithm first adds a production to introduce $. If the start symbol of the original grammar was S, it adds a production S S$. The next step is generating the canonical LR(1) (or LALR(1)) collection. The collection consists of collection items; each item represents a state in the automaton. A collection item is a set of productions with a marker, *, for the current position in the right hand side, augmented with a look-ahead. For example, a possible collection item for the simple HasNext pattern S next next is [{S S $, $}, {S next next, $}], another is [{S next next, $}]. In both of these collection items, the look-ahead is $. The collection item is first created for the start production, 9 The remaining productions are restructured to account for the removal of ɛ-productions without changing the recognized language, other than as mentioned. 272

273 1 globals action table, goto table 2 initialize stack.push(initial state) 3 procedure monitor(event, stack) 4 locals state, state, stack, A 5 state stack.top() 6 while (true) { 7 switch (action table[state,event].action type) { 8 case shift : 9 state action table[state, event].next state 10 if (state = error) { 11 pattern failure 12 break while 13 } 14 stack.push(state ) 15 if (action table[state, $].action type = reduce) { 16 stack stack.copy() 17 monitor($, stack ) 18 } 19 break while 20 case reduce : 21 stack.pop(action table[state, event].pop) 22 A action table[state, event].non terminal 23 state stack.top() 24 stack.push(goto table[state, A]) 25 break switch 26 case accept : 27 pattern match 28 break while 29 } 30 } Figure 12.3: CFG Monitoring Algorithm with Stack Copying. 273

274 Table 12.2: LALR(1) Tables for SafeLock S S$. All productions for S are added to the state with * at the beginning of the right hand side, as can be seen in our example collection item. If there is a production for S such that the first symbol is a nonterminal, A, all the productions of A will also be added, with * at the beginning. This process is transitively closed. The next collection items are generated by advancing * in the production, and then taking the transitive closure for any nonterminals immediately following *. Once the collection is created, the tables are generated by treating each item as a state. The productions in the item are considered for each alphabet symbol (including $). If the marker appears in front of said terminal, a shift action is generated. The algorithm decides which collection item to shift to by looking for the collection item where there is a production with the same right hand side as the production that caused the shift action, but with * advanced one position. For example, the next collection from [{S next next, $}] would be [{S next next, $}]. Shift actions can never be generated for $; the algorithm disallows it. The handling of shift actions by the parsing algorithm can be seen below. If, however, there is a production in the item such as [{S next next, $}], where the marker appears at the end, and the terminal in question is the look- 274

Runtime Verification. Grigore Roşu. University of Illinois at Urbana-Champaign

Runtime Verification. Grigore Roşu. University of Illinois at Urbana-Champaign Runtime Verification Grigore Roşu University of Illinois at Urbana-Champaign 2 Contents 1 Introduction 7 2 Background, Preliminaries, Notations 13 3 Safety Properties 17 3.1 Finite Traces...........................

More information

On Safety Properties and Their Monitoring

On Safety Properties and Their Monitoring Scientific Annals of Computer Science vol.??, 201?, pp. 1 39 On Safety Properties and Their Monitoring Grigore Roşu 1 Abstract This paper addresses the problem of runtime verification from a foundational

More information

The State Explosion Problem

The State Explosion Problem The State Explosion Problem Martin Kot August 16, 2003 1 Introduction One from main approaches to checking correctness of a concurrent system are state space methods. They are suitable for automatic analysis

More information

Chapter 4: Computation tree logic

Chapter 4: Computation tree logic INFOF412 Formal verification of computer systems Chapter 4: Computation tree logic Mickael Randour Formal Methods and Verification group Computer Science Department, ULB March 2017 1 CTL: a specification

More information

Abstractions and Decision Procedures for Effective Software Model Checking

Abstractions and Decision Procedures for Effective Software Model Checking Abstractions and Decision Procedures for Effective Software Model Checking Prof. Natasha Sharygina The University of Lugano, Carnegie Mellon University Microsoft Summer School, Moscow, July 2011 Lecture

More information

Alan Bundy. Automated Reasoning LTL Model Checking

Alan Bundy. Automated Reasoning LTL Model Checking Automated Reasoning LTL Model Checking Alan Bundy Lecture 9, page 1 Introduction So far we have looked at theorem proving Powerful, especially where good sets of rewrite rules or decision procedures have

More information

Parametric Trace Slicing and Monitoring

Parametric Trace Slicing and Monitoring Parametric Trace Slicing and Monitoring Grigore Roşu and Feng Chen Department of Computer Science, University of Illinois at Urbana-Champaign {grosu,fengchen}@cs.uiuc.edu Abstract Trace analysis plays

More information

Course Runtime Verification

Course Runtime Verification Course Martin Leucker (ISP) Volker Stolz (Høgskolen i Bergen, NO) INF5140 / V17 Chapters of the Course Chapter 1 Recall in More Depth Chapter 2 Specification Languages on Words Chapter 3 LTL on Finite

More information

Temporal logics and explicit-state model checking. Pierre Wolper Université de Liège

Temporal logics and explicit-state model checking. Pierre Wolper Université de Liège Temporal logics and explicit-state model checking Pierre Wolper Université de Liège 1 Topics to be covered Introducing explicit-state model checking Finite automata on infinite words Temporal Logics and

More information

Model Checking: An Introduction

Model Checking: An Introduction Model Checking: An Introduction Meeting 3, CSCI 5535, Spring 2013 Announcements Homework 0 ( Preliminaries ) out, due Friday Saturday This Week Dive into research motivating CSCI 5535 Next Week Begin foundations

More information

SEMANTICS AND ALGORITHMS FOR PARAMETRIC MONITORING

SEMANTICS AND ALGORITHMS FOR PARAMETRIC MONITORING Logical Methods in Computer Science Vol. 8 (1:09) 2012, pp. 1 47 www.lmcs-online.org Submitted Dec. 1, 2009 Published Feb. 23, 2012 SEMANTICS AND ALGORITHMS FOR PARAMETRIC MONITORING GRIGORE ROŞU a AND

More information

Chapter 3: Linear temporal logic

Chapter 3: Linear temporal logic INFOF412 Formal verification of computer systems Chapter 3: Linear temporal logic Mickael Randour Formal Methods and Verification group Computer Science Department, ULB March 2017 1 LTL: a specification

More information

Lecture Notes on Inductive Definitions

Lecture Notes on Inductive Definitions Lecture Notes on Inductive Definitions 15-312: Foundations of Programming Languages Frank Pfenning Lecture 2 September 2, 2004 These supplementary notes review the notion of an inductive definition and

More information

Automatic Synthesis of Distributed Protocols

Automatic Synthesis of Distributed Protocols Automatic Synthesis of Distributed Protocols Rajeev Alur Stavros Tripakis 1 Introduction Protocols for coordination among concurrent processes are an essential component of modern multiprocessor and distributed

More information

Formal Verification Techniques. Riccardo Sisto, Politecnico di Torino

Formal Verification Techniques. Riccardo Sisto, Politecnico di Torino Formal Verification Techniques Riccardo Sisto, Politecnico di Torino State exploration State Exploration and Theorem Proving Exhaustive exploration => result is certain (correctness or noncorrectness proof)

More information

Timo Latvala. March 7, 2004

Timo Latvala. March 7, 2004 Reactive Systems: Safety, Liveness, and Fairness Timo Latvala March 7, 2004 Reactive Systems: Safety, Liveness, and Fairness 14-1 Safety Safety properties are a very useful subclass of specifications.

More information

Finite-State Model Checking

Finite-State Model Checking EECS 219C: Computer-Aided Verification Intro. to Model Checking: Models and Properties Sanjit A. Seshia EECS, UC Berkeley Finite-State Model Checking G(p X q) Temporal logic q p FSM Model Checker Yes,

More information

Lecture Notes on Inductive Definitions

Lecture Notes on Inductive Definitions Lecture Notes on Inductive Definitions 15-312: Foundations of Programming Languages Frank Pfenning Lecture 2 August 28, 2003 These supplementary notes review the notion of an inductive definition and give

More information

Introduction to Model Checking. Debdeep Mukhopadhyay IIT Madras

Introduction to Model Checking. Debdeep Mukhopadhyay IIT Madras Introduction to Model Checking Debdeep Mukhopadhyay IIT Madras How good can you fight bugs? Comprising of three parts Formal Verification techniques consist of three parts: 1. A framework for modeling

More information

The efficiency of identifying timed automata and the power of clocks

The efficiency of identifying timed automata and the power of clocks The efficiency of identifying timed automata and the power of clocks Sicco Verwer a,b,1,, Mathijs de Weerdt b, Cees Witteveen b a Eindhoven University of Technology, Department of Mathematics and Computer

More information

LTL Model Checking. Wishnu Prasetya.

LTL Model Checking. Wishnu Prasetya. LTL Model Checking Wishnu Prasetya wishnu@cs.uu.nl www.cs.uu.nl/docs/vakken/pv Overview This pack : Abstract model of programs Temporal properties Verification (via model checking) algorithm Concurrency

More information

Requirements Validation. Content. What the standards say (*) ?? Validation, Verification, Accreditation!! Correctness and completeness

Requirements Validation. Content. What the standards say (*) ?? Validation, Verification, Accreditation!! Correctness and completeness Requirements Validation Requirements Management Requirements Validation?? Validation, Verification, Accreditation!! Check if evrything is OK With respect to what? Mesurement associated with requirements

More information

What You Must Remember When Processing Data Words

What You Must Remember When Processing Data Words What You Must Remember When Processing Data Words Michael Benedikt, Clemens Ley, and Gabriele Puppis Oxford University Computing Laboratory, Park Rd, Oxford OX13QD UK Abstract. We provide a Myhill-Nerode-like

More information

Automata-Theoretic LTL Model-Checking

Automata-Theoretic LTL Model-Checking Automata-Theoretic LTL Model-Checking Arie Gurfinkel arie@cmu.edu SEI/CMU Automata-Theoretic LTL Model-Checking p.1 LTL - Linear Time Logic (Pn 77) Determines Patterns on Infinite Traces Atomic Propositions

More information

Supplementary Notes on Inductive Definitions

Supplementary Notes on Inductive Definitions Supplementary Notes on Inductive Definitions 15-312: Foundations of Programming Languages Frank Pfenning Lecture 2 August 29, 2002 These supplementary notes review the notion of an inductive definition

More information

Impartial Anticipation in Runtime-Verification

Impartial Anticipation in Runtime-Verification Impartial Anticipation in Runtime-Verification Wei Dong 1, Martin Leucker 2, and Christian Schallhart 2 1 School of Computer, National University of Defense Technology, P.R.China 2 Institut für Informatik,

More information

Computer-Aided Program Design

Computer-Aided Program Design Computer-Aided Program Design Spring 2015, Rice University Unit 3 Swarat Chaudhuri February 5, 2015 Temporal logic Propositional logic is a good language for describing properties of program states. However,

More information

Testing Extended Regular Language Membership Incrementally by Rewriting

Testing Extended Regular Language Membership Incrementally by Rewriting Testing Extended Regular Language Membership Incrementally by Rewriting Grigore Roşu and Mahesh Viswanathan Department of Computer Science University of Illinois at Urbana-Champaign, USA Abstract. In this

More information

CS422 - Programming Language Design

CS422 - Programming Language Design 1 CS422 - Programming Language Design Denotational Semantics Grigore Roşu Department of Computer Science University of Illinois at Urbana-Champaign 2 Denotational semantics, also known as fix-point semantics,

More information

Linear Temporal Logic and Büchi Automata

Linear Temporal Logic and Büchi Automata Linear Temporal Logic and Büchi Automata Yih-Kuen Tsay Department of Information Management National Taiwan University FLOLAC 2009 Yih-Kuen Tsay (SVVRL @ IM.NTU) Linear Temporal Logic and Büchi Automata

More information

Failure Diagnosis of Discrete Event Systems With Linear-Time Temporal Logic Specifications

Failure Diagnosis of Discrete Event Systems With Linear-Time Temporal Logic Specifications Failure Diagnosis of Discrete Event Systems With Linear-Time Temporal Logic Specifications Shengbing Jiang and Ratnesh Kumar Abstract The paper studies failure diagnosis of discrete event systems with

More information

Lecture 14 - P v.s. NP 1

Lecture 14 - P v.s. NP 1 CME 305: Discrete Mathematics and Algorithms Instructor: Professor Aaron Sidford (sidford@stanford.edu) February 27, 2018 Lecture 14 - P v.s. NP 1 In this lecture we start Unit 3 on NP-hardness and approximation

More information

Chapter 5: Linear Temporal Logic

Chapter 5: Linear Temporal Logic Chapter 5: Linear Temporal Logic Prof. Ali Movaghar Verification of Reactive Systems Spring 94 Outline We introduce linear temporal logic (LTL), a logical formalism that is suited for specifying LT properties.

More information

Temporal Logic Model Checking

Temporal Logic Model Checking 18 Feb, 2009 Thomas Wahl, Oxford University Temporal Logic Model Checking 1 Temporal Logic Model Checking Thomas Wahl Computing Laboratory, Oxford University 18 Feb, 2009 Thomas Wahl, Oxford University

More information

Helsinki University of Technology Laboratory for Theoretical Computer Science Research Reports 66

Helsinki University of Technology Laboratory for Theoretical Computer Science Research Reports 66 Helsinki University of Technology Laboratory for Theoretical Computer Science Research Reports 66 Teknillisen korkeakoulun tietojenkäsittelyteorian laboratorion tutkimusraportti 66 Espoo 2000 HUT-TCS-A66

More information

Linear-time Temporal Logic

Linear-time Temporal Logic Linear-time Temporal Logic Pedro Cabalar Department of Computer Science University of Corunna, SPAIN cabalar@udc.es 2015/2016 P. Cabalar ( Department Linear oftemporal Computer Logic Science University

More information

Runtime Verification of Safety/Progress Properties

Runtime Verification of Safety/Progress Properties untime Verification of Safety/Progress Properties VEIMAG Lab & Université Joseph Fourier Grenoble, France Yliès Falcone and Jean-Claude Fernandez and Laurent Mounier November 24 th 2009, Prague, Czesh

More information

Synthesis of Designs from Property Specifications

Synthesis of Designs from Property Specifications Synthesis of Designs from Property Specifications Amir Pnueli New York University and Weizmann Institute of Sciences FMCAD 06 San Jose, November, 2006 Joint work with Nir Piterman, Yaniv Sa ar, Research

More information

Consistent Global States of Distributed Systems: Fundamental Concepts and Mechanisms. CS 249 Project Fall 2005 Wing Wong

Consistent Global States of Distributed Systems: Fundamental Concepts and Mechanisms. CS 249 Project Fall 2005 Wing Wong Consistent Global States of Distributed Systems: Fundamental Concepts and Mechanisms CS 249 Project Fall 2005 Wing Wong Outline Introduction Asynchronous distributed systems, distributed computations,

More information

Matching Logic: Syntax and Semantics

Matching Logic: Syntax and Semantics Matching Logic: Syntax and Semantics Grigore Roșu 1 and Traian Florin Șerbănuță 2 1 University of Illinois at Urbana-Champaign, USA grosu@illinois.edu 2 University of Bucharest, Romania traian.serbanuta@unibuc.ro

More information

DRAFT. Diagonalization. Chapter 4

DRAFT. Diagonalization. Chapter 4 Chapter 4 Diagonalization..the relativized P =?NP question has a positive answer for some oracles and a negative answer for other oracles. We feel that this is further evidence of the difficulty of the

More information

Automata-based Verification - III

Automata-based Verification - III COMP30172: Advanced Algorithms Automata-based Verification - III Howard Barringer Room KB2.20: email: howard.barringer@manchester.ac.uk March 2009 Third Topic Infinite Word Automata Motivation Büchi Automata

More information

A Enforceable Security Policies Revisited

A Enforceable Security Policies Revisited A Enforceable Security Policies Revisited DAVID BASIN, ETH Zurich VINCENT JUGÉ, MINES ParisTech FELIX KLAEDTKE, ETH Zurich EUGEN ZĂLINESCU, ETH Zurich We revisit Schneider s work on policy enforcement

More information

Equivalence of Regular Expressions and FSMs

Equivalence of Regular Expressions and FSMs Equivalence of Regular Expressions and FSMs Greg Plaxton Theory in Programming Practice, Spring 2005 Department of Computer Science University of Texas at Austin Regular Language Recall that a language

More information

From Liveness to Promptness

From Liveness to Promptness From Liveness to Promptness Orna Kupferman Hebrew University Nir Piterman EPFL Moshe Y. Vardi Rice University Abstract Liveness temporal properties state that something good eventually happens, e.g., every

More information

Automata Theory and Formal Grammars: Lecture 1

Automata Theory and Formal Grammars: Lecture 1 Automata Theory and Formal Grammars: Lecture 1 Sets, Languages, Logic Automata Theory and Formal Grammars: Lecture 1 p.1/72 Sets, Languages, Logic Today Course Overview Administrivia Sets Theory (Review?)

More information

T Reactive Systems: Temporal Logic LTL

T Reactive Systems: Temporal Logic LTL Tik-79.186 Reactive Systems 1 T-79.186 Reactive Systems: Temporal Logic LTL Spring 2005, Lecture 4 January 31, 2005 Tik-79.186 Reactive Systems 2 Temporal Logics Temporal logics are currently the most

More information

Turing machines and linear bounded automata

Turing machines and linear bounded automata and linear bounded automata Informatics 2A: Lecture 29 John Longley School of Informatics University of Edinburgh jrl@inf.ed.ac.uk 27 November 2015 1 / 15 The Chomsky hierarchy: summary Level Language

More information

Decentralized Control of Discrete Event Systems with Bounded or Unbounded Delay Communication

Decentralized Control of Discrete Event Systems with Bounded or Unbounded Delay Communication Decentralized Control of Discrete Event Systems with Bounded or Unbounded Delay Communication Stavros Tripakis Abstract We introduce problems of decentralized control with communication, where we explicitly

More information

Turing machines and linear bounded automata

Turing machines and linear bounded automata and linear bounded automata Informatics 2A: Lecture 30 John Longley School of Informatics University of Edinburgh jrl@inf.ed.ac.uk 25 November 2016 1 / 17 The Chomsky hierarchy: summary Level Language

More information

Logic Model Checking

Logic Model Checking Logic Model Checking Lecture Notes 10:18 Caltech 101b.2 January-March 2004 Course Text: The Spin Model Checker: Primer and Reference Manual Addison-Wesley 2003, ISBN 0-321-22862-6, 608 pgs. the assignment

More information

Computation Tree Logic (CTL) & Basic Model Checking Algorithms

Computation Tree Logic (CTL) & Basic Model Checking Algorithms Computation Tree Logic (CTL) & Basic Model Checking Algorithms Martin Fränzle Carl von Ossietzky Universität Dpt. of Computing Science Res. Grp. Hybride Systeme Oldenburg, Germany 02917: CTL & Model Checking

More information

Automata-based Verification - III

Automata-based Verification - III CS3172: Advanced Algorithms Automata-based Verification - III Howard Barringer Room KB2.20/22: email: howard.barringer@manchester.ac.uk March 2005 Third Topic Infinite Word Automata Motivation Büchi Automata

More information

CS256/Spring 2008 Lecture #11 Zohar Manna. Beyond Temporal Logics

CS256/Spring 2008 Lecture #11 Zohar Manna. Beyond Temporal Logics CS256/Spring 2008 Lecture #11 Zohar Manna Beyond Temporal Logics Temporal logic expresses properties of infinite sequences of states, but there are interesting properties that cannot be expressed, e.g.,

More information

Overview. Discrete Event Systems Verification of Finite Automata. What can finite automata be used for? What can finite automata be used for?

Overview. Discrete Event Systems Verification of Finite Automata. What can finite automata be used for? What can finite automata be used for? Computer Engineering and Networks Overview Discrete Event Systems Verification of Finite Automata Lothar Thiele Introduction Binary Decision Diagrams Representation of Boolean Functions Comparing two circuits

More information

Automata Theory (2A) Young Won Lim 5/31/18

Automata Theory (2A) Young Won Lim 5/31/18 Automata Theory (2A) Copyright (c) 2018 Young W. Lim. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later

More information

Temporal Logic. Stavros Tripakis University of California, Berkeley. We have designed a system. We want to check that it is correct.

Temporal Logic. Stavros Tripakis University of California, Berkeley. We have designed a system. We want to check that it is correct. EE 244: Fundamental Algorithms for System Modeling, Analysis, and Optimization Fall 2016 Temporal logic Stavros Tripakis University of California, Berkeley Stavros Tripakis (UC Berkeley) EE 244, Fall 2016

More information

Finite Automata. Mahesh Viswanathan

Finite Automata. Mahesh Viswanathan Finite Automata Mahesh Viswanathan In this lecture, we will consider different models of finite state machines and study their relative power. These notes assume that the reader is familiar with DFAs,

More information

IC3 and Beyond: Incremental, Inductive Verification

IC3 and Beyond: Incremental, Inductive Verification IC3 and Beyond: Incremental, Inductive Verification Aaron R. Bradley ECEE, CU Boulder & Summit Middle School IC3 and Beyond: Incremental, Inductive Verification 1/62 Induction Foundation of verification

More information

2. Elements of the Theory of Computation, Lewis and Papadimitrou,

2. Elements of the Theory of Computation, Lewis and Papadimitrou, Introduction Finite Automata DFA, regular languages Nondeterminism, NFA, subset construction Regular Epressions Synta, Semantics Relationship to regular languages Properties of regular languages Pumping

More information

Efficient Monitoring of ω-languages

Efficient Monitoring of ω-languages Efficient Monitoring of ω-languages Marcelo d Amorim and Grigore Roşu Department of Computer Science University of Illinois at Urbana-Champaign 201 N. Goodwin, Urbana, IL, 61801, USA {damorim, grosu}@uiuc.edu

More information

Computational Tasks and Models

Computational Tasks and Models 1 Computational Tasks and Models Overview: We assume that the reader is familiar with computing devices but may associate the notion of computation with specific incarnations of it. Our first goal is to

More information

Lecture 2 Automata Theory

Lecture 2 Automata Theory Lecture 2 Automata Theory Ufuk Topcu Nok Wongpiromsarn Richard M. Murray Outline: Transition systems Linear-time properties Regular propereties EECI, 14 May 2012 This short-course is on this picture applied

More information

CDS 270 (Fall 09) - Lecture Notes for Assignment 8.

CDS 270 (Fall 09) - Lecture Notes for Assignment 8. CDS 270 (Fall 09) - Lecture Notes for Assignment 8. ecause this part of the course has no slides or textbook, we will provide lecture supplements that include, hopefully, enough discussion to complete

More information

MOST OF the published research on control of discreteevent

MOST OF the published research on control of discreteevent IEEE TRANSACTIONS ON AUTOMATIC CONTROL, VOL. 43, NO. 1, JANUARY 1998 3 Discrete-Event Control of Nondeterministic Systems Michael Heymann and Feng Lin, Member, IEEE Abstract Nondeterminism in discrete-event

More information

Timo Latvala. February 4, 2004

Timo Latvala. February 4, 2004 Reactive Systems: Temporal Logic LT L Timo Latvala February 4, 2004 Reactive Systems: Temporal Logic LT L 8-1 Temporal Logics Temporal logics are currently the most widely used specification formalism

More information

Static Program Analysis

Static Program Analysis Static Program Analysis Xiangyu Zhang The slides are compiled from Alex Aiken s Michael D. Ernst s Sorin Lerner s A Scary Outline Type-based analysis Data-flow analysis Abstract interpretation Theorem

More information

Lecture Notes on Certifying Theorem Provers

Lecture Notes on Certifying Theorem Provers Lecture Notes on Certifying Theorem Provers 15-317: Constructive Logic Frank Pfenning Lecture 13 October 17, 2017 1 Introduction How do we trust a theorem prover or decision procedure for a logic? Ideally,

More information

CIS 842: Specification and Verification of Reactive Systems. Lecture Specifications: Specification Patterns

CIS 842: Specification and Verification of Reactive Systems. Lecture Specifications: Specification Patterns CIS 842: Specification and Verification of Reactive Systems Lecture Specifications: Specification Patterns Copyright 2001-2002, Matt Dwyer, John Hatcliff, Robby. The syllabus and all lectures for this

More information

UNIT-VIII COMPUTABILITY THEORY

UNIT-VIII COMPUTABILITY THEORY CONTEXT SENSITIVE LANGUAGE UNIT-VIII COMPUTABILITY THEORY A Context Sensitive Grammar is a 4-tuple, G = (N, Σ P, S) where: N Set of non terminal symbols Σ Set of terminal symbols S Start symbol of the

More information

Variable Automata over Infinite Alphabets

Variable Automata over Infinite Alphabets Variable Automata over Infinite Alphabets Orna Grumberg a, Orna Kupferman b, Sarai Sheinvald b a Department of Computer Science, The Technion, Haifa 32000, Israel b School of Computer Science and Engineering,

More information

Lecture 11: Measuring the Complexity of Proofs

Lecture 11: Measuring the Complexity of Proofs IAS/PCMI Summer Session 2000 Clay Mathematics Undergraduate Program Advanced Course on Computational Complexity Lecture 11: Measuring the Complexity of Proofs David Mix Barrington and Alexis Maciel July

More information

Revising UNITY Programs: Possibilities and Limitations 1

Revising UNITY Programs: Possibilities and Limitations 1 Revising UNITY Programs: Possibilities and Limitations 1 Ali Ebnenasir, Sandeep S. Kulkarni, and Borzoo Bonakdarpour Software Engineering and Network Systems Laboratory Department of Computer Science and

More information

Automata, Logic and Games: Theory and Application

Automata, Logic and Games: Theory and Application Automata, Logic and Games: Theory and Application 1. Büchi Automata and S1S Luke Ong University of Oxford TACL Summer School University of Salerno, 14-19 June 2015 Luke Ong Büchi Automata & S1S 14-19 June

More information

Binary Decision Diagrams and Symbolic Model Checking

Binary Decision Diagrams and Symbolic Model Checking Binary Decision Diagrams and Symbolic Model Checking Randy Bryant Ed Clarke Ken McMillan Allen Emerson CMU CMU Cadence U Texas http://www.cs.cmu.edu/~bryant Binary Decision Diagrams Restricted Form of

More information

Tree sets. Reinhard Diestel

Tree sets. Reinhard Diestel 1 Tree sets Reinhard Diestel Abstract We study an abstract notion of tree structure which generalizes treedecompositions of graphs and matroids. Unlike tree-decompositions, which are too closely linked

More information

Semantic Equivalences and the. Verification of Infinite-State Systems 1 c 2004 Richard Mayr

Semantic Equivalences and the. Verification of Infinite-State Systems 1 c 2004 Richard Mayr Semantic Equivalences and the Verification of Infinite-State Systems Richard Mayr Department of Computer Science Albert-Ludwigs-University Freiburg Germany Verification of Infinite-State Systems 1 c 2004

More information

Runtime Verification of Safety-Progress Properties

Runtime Verification of Safety-Progress Properties Unité Mixte de Recherche 5104 CNRS - INPG - UJF Centre Equation 2, avenue de VIGNATE F-38610 GIERES tel : +33 456 52 03 40 fax : +33 456 52 03 50 http://www-verimag.imag.fr Runtime Verification of Safety-Progress

More information

Linear-Time Logic. Hao Zheng

Linear-Time Logic. Hao Zheng Linear-Time Logic Hao Zheng Department of Computer Science and Engineering University of South Florida Tampa, FL 33620 Email: zheng@cse.usf.edu Phone: (813)974-4757 Fax: (813)974-5456 Hao Zheng (CSE, USF)

More information

Software Verification using Predicate Abstraction and Iterative Refinement: Part 1

Software Verification using Predicate Abstraction and Iterative Refinement: Part 1 using Predicate Abstraction and Iterative Refinement: Part 1 15-414 Bug Catching: Automated Program Verification and Testing Sagar Chaki November 28, 2011 Outline Overview of Model Checking Creating Models

More information

Turing machines and linear bounded automata

Turing machines and linear bounded automata and linear bounded automata Informatics 2A: Lecture 29 John Longley School of Informatics University of Edinburgh jrl@inf.ed.ac.uk 25 November, 2011 1 / 13 1 The Chomsky hierarchy: summary 2 3 4 2 / 13

More information

CISC 4090: Theory of Computation Chapter 1 Regular Languages. Section 1.1: Finite Automata. What is a computer? Finite automata

CISC 4090: Theory of Computation Chapter 1 Regular Languages. Section 1.1: Finite Automata. What is a computer? Finite automata CISC 4090: Theory of Computation Chapter Regular Languages Xiaolan Zhang, adapted from slides by Prof. Werschulz Section.: Finite Automata Fordham University Department of Computer and Information Sciences

More information

Chapter 2. Reductions and NP. 2.1 Reductions Continued The Satisfiability Problem (SAT) SAT 3SAT. CS 573: Algorithms, Fall 2013 August 29, 2013

Chapter 2. Reductions and NP. 2.1 Reductions Continued The Satisfiability Problem (SAT) SAT 3SAT. CS 573: Algorithms, Fall 2013 August 29, 2013 Chapter 2 Reductions and NP CS 573: Algorithms, Fall 2013 August 29, 2013 2.1 Reductions Continued 2.1.1 The Satisfiability Problem SAT 2.1.1.1 Propositional Formulas Definition 2.1.1. Consider a set of

More information

Theory of computation: initial remarks (Chapter 11)

Theory of computation: initial remarks (Chapter 11) Theory of computation: initial remarks (Chapter 11) For many purposes, computation is elegantly modeled with simple mathematical objects: Turing machines, finite automata, pushdown automata, and such.

More information

Automata-Theoretic Model Checking of Reactive Systems

Automata-Theoretic Model Checking of Reactive Systems Automata-Theoretic Model Checking of Reactive Systems Radu Iosif Verimag/CNRS (Grenoble, France) Thanks to Tom Henzinger (IST, Austria), Barbara Jobstmann (CNRS, Grenoble) and Doron Peled (Bar-Ilan University,

More information

A General Testability Theory: Classes, properties, complexity, and testing reductions

A General Testability Theory: Classes, properties, complexity, and testing reductions A General Testability Theory: Classes, properties, complexity, and testing reductions presenting joint work with Luis Llana and Pablo Rabanal Universidad Complutense de Madrid PROMETIDOS-CM WINTER SCHOOL

More information

Compilers. Lexical analysis. Yannis Smaragdakis, U. Athens (original slides by Sam

Compilers. Lexical analysis. Yannis Smaragdakis, U. Athens (original slides by Sam Compilers Lecture 3 Lexical analysis Yannis Smaragdakis, U. Athens (original slides by Sam Guyer@Tufts) Big picture Source code Front End IR Back End Machine code Errors Front end responsibilities Check

More information

Deterministic Finite Automata

Deterministic Finite Automata Deterministic Finite Automata COMP2600 Formal Methods for Software Engineering Ranald Clouston Australian National University Semester 2, 2013 COMP 2600 Deterministic Finite Automata 1 Pop quiz What is

More information

Intelligent Agents. Formal Characteristics of Planning. Ute Schmid. Cognitive Systems, Applied Computer Science, Bamberg University

Intelligent Agents. Formal Characteristics of Planning. Ute Schmid. Cognitive Systems, Applied Computer Science, Bamberg University Intelligent Agents Formal Characteristics of Planning Ute Schmid Cognitive Systems, Applied Computer Science, Bamberg University Extensions to the slides for chapter 3 of Dana Nau with contributions by

More information

Theoretical Foundations of the UML

Theoretical Foundations of the UML Theoretical Foundations of the UML Lecture 17+18: A Logic for MSCs Joost-Pieter Katoen Lehrstuhl für Informatik 2 Software Modeling and Verification Group moves.rwth-aachen.de/teaching/ws-1718/fuml/ 5.

More information

On Real-time Monitoring with Imprecise Timestamps

On Real-time Monitoring with Imprecise Timestamps On Real-time Monitoring with Imprecise Timestamps David Basin 1, Felix Klaedtke 2, Srdjan Marinovic 1, and Eugen Zălinescu 1 1 Institute of Information Security, ETH Zurich, Switzerland 2 NEC Europe Ltd.,

More information

Parametric and Sliced Causality

Parametric and Sliced Causality Parametric and Sliced Causality Feng Chen and Grigore Roşu Department of Computer Science University of Illinois at Urbana - Champaign, USA {fengchen,grosu}@uiuc.edu Abstract. Happen-before causal partial

More information

Lecture 2 Automata Theory

Lecture 2 Automata Theory Lecture 2 Automata Theory Ufuk Topcu Nok Wongpiromsarn Richard M. Murray EECI, 18 March 2013 Outline Modeling (discrete) concurrent systems: transition systems, concurrency and interleaving Linear-time

More information

Outline F eria AADL behavior 1/ 78

Outline F eria AADL behavior 1/ 78 Outline AADL behavior Annex Jean-Paul Bodeveix 2 Pierre Dissaux 3 Mamoun Filali 2 Pierre Gaufillet 1 François Vernadat 2 1 AIRBUS-FRANCE 2 FéRIA 3 ELLIDIS SAE AS2C Detroit Michigan April 2006 FéRIA AADL

More information

Double Header. Model Checking. Model Checking. Overarching Plan. Take-Home Message. Spoiler Space. Topic: (Generic) Model Checking

Double Header. Model Checking. Model Checking. Overarching Plan. Take-Home Message. Spoiler Space. Topic: (Generic) Model Checking Double Header Model Checking #1 Two Lectures Model Checking SoftwareModel Checking SLAM and BLAST Flying Boxes It is traditional to describe this stuff (especially SLAM and BLAST) with high-gloss animation

More information

Distribution of reactive systems

Distribution of reactive systems UNIVERSITÉ LIBRE DE BRUXELLES Faculté des Sciences Département d Informatique Distribution of reactive systems MEUTER Cédric Mémoire présenté vue de l obtention du diplome d étude approfondie en informatique

More information

Revisiting Synthesis of GR(1) Specifications

Revisiting Synthesis of GR(1) Specifications Revisiting Synthesis of GR(1) Specifications Uri Klein & Amir Pnueli Courant Institute of Mathematical Sciences, NYU Haifa Verification Conference, October 2010 What Is Synthesis? Rather than implement

More information

CSC173 Workshop: 13 Sept. Notes

CSC173 Workshop: 13 Sept. Notes CSC173 Workshop: 13 Sept. Notes Frank Ferraro Department of Computer Science University of Rochester September 14, 2010 1 Regular Languages and Equivalent Forms A language can be thought of a set L of

More information

CS522 - Programming Language Semantics

CS522 - Programming Language Semantics 1 CS522 - Programming Language Semantics Simply Typed Lambda Calculus Grigore Roşu Department of Computer Science University of Illinois at Urbana-Champaign 2 We now discuss a non-trivial extension of

More information

Guest lecturer: Prof. Mark Reynolds, The University of Western Australia

Guest lecturer: Prof. Mark Reynolds, The University of Western Australia Università degli studi di Udine Corso per il dottorato di ricerca: Temporal Logics: Satisfiability Checking, Model Checking, and Synthesis January 2017 Lecture 01, Part 02: Temporal Logics Guest lecturer:

More information