Foundations of the Trace Assertion Method of Module ... - CiteSeerX

15 downloads 112 Views 257KB Size Report
The stepwise refinement of trace assertion specifications is considered. .... a part of a single observable event, an invocation of GET returns an integer i, so the full ..... s3 a:1 b:2 a:2 a:2 b:3 b:1 j j. E s1 s3 b:2. ⇔ ̺(b:2)(s1) = {s3}. ⇔ δ(b)(s1) = {s3}.
Foundations of the Trace Assertion Method of Module Interface Specification ∗ Emil Sekerinski McMaster University Hamilton, Ontario, Canada L8S 4K1 [email protected]

Ryszard Janicki McMaster University Hamilton, Ontario, Canada L8S 4K1 [email protected]

June 2000

Abstract The trace assertion method is a formal state machine based method for specifying module interfaces. A module interface specification treats the module as a black-box, identifying all module’s access programs (i.e. programs that can be invoked from outside of the module), and describing their externally visible effects. In the method, both the module states and the behaviors observed are fully described by traces built from access program invocations and their visible effects. A formal model for the trace assertion method is proposed. The concept of step-traces is introduced and applied. The stepwise refinement of trace assertion specifications is considered. The role of non-determinism, normal and exceptional behavior, value functions and multi-object modules are discussed. The relationship with algebraic specifications is analyzed. A tabular notation for writing trace specifications to ensure readability is adapted. Keywords. Module interface specifications, trace assertion method, state machines, Mealy machines, step-sequences, relational model, nondeterminism, module refinement, tabular notation.

1

Introduction

Software modules, viewed as “black boxes” [25, 23], hide some design decisions and provide abstract data types. They can be specified using the trace assertion method. A trace is a complete history of the visible behavior of a module. It includes all events affecting the module, eventually with the outputs produced. Formally a trace is a sequence of events. The fundamental principle is that a trace specification describes only those features of a module (or an object in general) that are externally observable and the central idea of the approach is that traces can be divided into clusters and each cluster is represented by a single canonical trace. The trace assertion method was first formulated by Bartussek and Parnas in [3], as a possible answer for problems with algebraic specifications [7, 34], which will be discussed later. It also can avoid the problem of overspecification in model-oriented specifications, e.g. [1]. A typical example is the use of a sequence for specifying a stack module, where PUSH will append the new element either at the front or the tail of the sequence, the choice being arbitrary. In the trace assertion method, this decision ∗

To appear in IEEE Transactions on Software Engineering. Partially supported by NSERC Research Grant

1

is avoided. Since its introduction the method has undergone many modifications [12, 22, 27, 33]. In recent years, there has been an increased interest in the trace assertion method [14, 16, 15, 24, 28, 32]. However, a satisfactory foundation has not yet been developed. The trace assertion method is based on the following postulates: • Information hiding [25, 23] is a fundamental principle for specification. • Sequences are simple and powerful tool for specifying abstract objects. • Explicit equations are preferable over implicit equations like those of algebraic specifications. • State machines are simple and powerful tool for specifying modules. For many applications state machines are better than algebras, and their use for specification is growing [1, 2, 11]. State machines (not necessary finite) are equivalent to algebras. This relationship differs for different machines and algebras, but the general idea of relationship may be illustrated as follows: ⇔ a(p) = q,

δ(p, a) = q |

{z

|

}

state machine

{z

}

algebra

where δ is a transition function of a state machine with a as a function name, and a(p) is a function named a applied to p. See [8, 4] and Section 15. The term “trace” has at least two different meanings. One it that a trace is just a sequence of events, actions, operations, or systems calls, i.e. it is a sequence of specially interpreted elements. The other meaning is that a trace is an element of a partially commutative monoid, where the monoid operation is concatenation (see [6]). In the second case the name “Mazurkiewicz traces” is often used [6, 18] . Traces in the first sense can be treated as a special case of the second (the independency relation is empty, i.e. no commutativity at all). The “step-traces” used in this paper lie somewhere between the first and the second meaning. The contributions of this paper to the trace assertion method are: • The role of nondeterminism, which caused some problem in the previous models is explained. • The concept of exceptional behavior if formally analyzed. • The role of value functions, in particular for nondeterministic modules, is discussed. • The use of step-traces to overcome difficulties with asymmetry caused by the use of ordinary traces is proposed. • A notion of refinement for trace assertion specifications is introduced. • The use of abstract state constructors for the problem of finding canonical traces is certain situations is suggested. • A formal model for multi-object modules is proposed and discussed.

2

Overview. In the next section we introduce and briefly discuss three simple modules. These modules are used to illustrate the major problems and solutions. In Section 3 the question “What is an atomic observable event?” is discussed. Section 4 reviews the fundamentals of the relational model of programs and of program refinement. The automata model for module specifications is introduced in Section 5, together with a notion of module refinement and a simulation condition. A module access-program may return some values, but is it absolutely necessary to specify this fact by a separate output value function? This problem is discussed in Section 6. Objects described by sequences and the concept of step-sequences are discussed in Section 7, while automata with states specified by the step-sequences are analyzed in Section 8. The formal concept of a trace assertion specification is given in Section 9. The special instances of the trace assertion specification in Mealy form and the controversial use of invisible actions are discussed respectively in Sections 10 and 11. Exceptional behavior is discussed in Section 12. The idea is that misuses can be modeled separately and eventually they may be added to the pure trace specification as an enhancement. Section 13 defines a format for the trace specification technique. All the examples from Section 2 are formally specified in this format. Refinement with these examples is illustrated in Section 14. Section 15 deals with multi-object modules. The uniquely labeled sets of step-traces are introduced and used as a specification tool. The relationship between trace assertions and algebraic specifications is analyzed in Section 16. The last section contains final comments.

2

Introductory Examples

We shall use the following examples of modules: Stack, Unique Integer, Very Drunk Stack and Drunk Stack. Each module is designed to implement a single object. The Stack module provides three access programs, • PUSH(i): enters an integer i on the stack, • POP: takes no arguments and removes the top of the stack, and • TOP: takes no arguments and returns the value which is on the top of the stack. Intuitively, a state of the stack is determined by the finite sequence of integers, the last element of the sequence represents the top of the stack, and the first represents the bottom. Note that every sequence of properly used access programs leads to exactly one state. For instance PUSH(4).PUSH(1).POP. PUSH(7).TOP and PUSH(4).PUSH(7) both lead to the state h4, 7i. They could be seen as equivalent and we can choose for instance the trace PUSH(4).PUSH(7) as a canonical trace representing the state h4, 7i. The Unique Integer module provides only one access program, • GET: does not take any argument and returns an integer value from the set of integers a machine can represent. The only restriction on the return value is that it cannot be any value that was returned by previous GET invocations. Intuitively the state of Unique Integer is determined by the set of all integers that were returned by all previous GET invocations. In this case the sequence, say GET.GET.GET, corresponds to any set {i1 , i2 , i3 }, where i1 , i2 and i3 are distinct integers. However, the invocation of GET is only 3

a part of a single observable event, an invocation of GET returns an integer i, so the full observable event is a pair (GET, i), or, more conveniently, GET:i. A pair GET:i is a call-response event, with the call GET and the response i. Any trace built from GET :i pairs describes one state. For instance both GET :5.GET :1.GET :8, and GET :1.GET :5.GET :8, describe the state {1, 5, 8}. They could be seen as equivalent and we can choose for instance GET:1.GET:5.GET:8 as the canonical trace. However, since the order of GET’s is not important, quite opposite, it may cause some problems when imposed, we will use a canonical step-trace hGET :1.GET :5.GET :8i, as a state descriptor. The operator h·i makes the order irrelevant, i.e. hGET:1.GET:5.GET:8i = hGET:8.GET:1.GET:5i, etc. (see Section 7). Both Stack and Unique Integer can be modeled by state machines (automata) for which every trace describes exactly one state. The difference is that for Unique Integer traces are built from pairs (call, response) while for Stack calls alone are sufficient. The case when traces built from calls alone are sufficient are called output independent. Module Drunk Stack is the same as Stack except that access program POP behaves differently, • POP: if the length of the stack is one removes the top element, if the length is greater than one removes either the top element or the two top elements of the stack. Now the trace PUSH(7).PUSH(4).PUSH(1).PUSH(3).POP may lead to two states: h7, 4, 1i and h7, 4i. Adding outputs to the events does not change the situation since both PUSH and POP produce no output. However, each state is unambiguously described by an appropriate trace built from PUSH calls. For instance PUSH(7).PUSH(4).PUSH(1) describes the state h7, 4, 1i, and only this state, so canonical traces can be built. However, the traces PUSH(7).PUSH(4).PUSH(1) and PUSH(7).PUSH(4).PUSH(1). PUSH(3).POP may no longer be considered as equivalent, they lead to different sets of states. They could be interpreted as similar since the sets of states they represent are not disjoint, and they both belong to the same cluster of traces. The cluster of traces they belong to is the set of all traces that may lead to the state h7, 4, 1i. This cluster is unambiguously represented by the trace PUSH(7).PUSH(4).PUSH(1). The program PUSH is “sober” so it can be used to specify canonical traces. The use of output independent traces is sufficient for Drunk Stack. It can also be modeled by a non-deterministic state machine with states unambiguously described by canonical traces. The Very Drunk Stack has two “drunk” access programs POP and PUSH. Access programs TOP and POP are the same as for Drunk Stack, while the behavior of PUSH is the following, • PUSH(i): enters an integer i either once or twice on the stack. In this case the trace PUSH(7).PUSH(4) leads to h7, 4i, h7, 7, 4i, h7, 4, 4i, or h7, 7, 4, 4i. Moreover, each trace which does not lead to the empty stack, may lead to at least two different states. Thus canonical traces, interpreted as traces that can unambiguously describe states, cannot be defined. We need to proceed differently. One way is to observe that the state h7, 4i is the only state that can be reached by both the trace PUSH(7).PUSH(4) and the trace PUSH(7).PUSH(4).POP.POP. Thus the set of traces {PUSH(7).PUSH(4) , PUSH(7).PUSH(4).POP.POP} could be used as a trace descriptor of the state h7, 4i. One may observe that every state can unambiguously described in this sense by a finite set of traces. Modeling states of modules by sets of canonical traces was proposed in [24]. However, we reject such an approach. The sets of traces that describe states 4

can be large and complex even for relatively simple, non-deterministic modules. We believe such an approach will result in a complex and unreadable specification. We propose the use of abstract constructor programs instead. In the case of Very Drunk Stack, all states can easily be specified by an abstract constructor (invisible) program push1(i) which pushes i exactly once on the stack. The specification obtained is simple and natural (see Section 13, Figures 8 and 9).

3

Alphabet

Since a trace specification describes only those features of a module that are externally observable, the question arises what an atomic observation is. What constitutes an alphabet from which the traces are built? We consider two kinds of observations: • call events like PUSH(5), and • call-response events like GET:5. Let f be the name of an access program and let input(f ) and output(f ) be the sets of possible argument and result values. The signature sig(f ) is the triple: sig(f ) = (f , input(f ), output(f )). We assume that neither input(f ) nor output(f ) are empty by having input(f ) = {nil} and output(f ) = {nil} as default. For example: sig(PUSH) = (PUSH, integer, {nil}), sig(TOP) = (TOP, {nil}, integer), sig(POP) = (POP, {nil}, {nil}). For a finite set E of access program names, the signature sig(E) is the set of all signatures of f ∈ E: sig(E) = {sig(f ) | f ∈ E}. Given E, the call-response alphabet ∆E is the set of all possible triples, written f (x):g of access program names, arguments, and return values: ∆E = {f (x):g | f ∈ E, x ∈ input(f ), y ∈ output(f )}. We adopt the convention of omitting nil in signatures. For example, for the stack modules we have E = {PUSH, TOP, POP} and: ∆E = {PUSH(i) | i ∈ integer} ∪ {TOP:i | i ∈ integer} ∪ {POP}. For a given set E of access program names, we also define the call alphabet ΣE and the response alphabet OE : ΣE = {f (x) | f ∈ E, x ∈ input(f )}, OE = {d | ∃f ∈ E . d ∈ output(f )}. Note that the sequences of call-response event occurrences are what is really observed. However, one may abstract away from the output values, if states can be unambiguously described by sequences of call event occurrences only. 5

4

Relational Model of Programs

We review the fundamentals of the relational model of programs (e.g. [30]). Data refinement is introduced according to [10], except that, rather than taking relations extended by a bottom element, “demonic relational composition” and “demonic refinement” is used. We write S ↔ T for the set of all relations between S and T, formally defined as S ↔ T = 2S×T . For relations Q ∈ S ↔ T and R ∈ T ↔ U, the relational composition Q ◦ R, the relational image Q[s] of a set s ⊆ S, and the relational image Q(x) of an element x ∈ S are defined as follows: Q ◦ R = {(x, z) | ∃y . x Q y ∧ y R z}, Q[s] = {y | ∃x . x Q y ∧ x ∈ s}, Q(x) = {y | x Q y}. Here, x Q y stands for (x, y) ∈ Q. If Q is interpreted as a (possibly nondeterministic) program over initial state space S and final state space T, then the domain of Q, i.e. the set of all initial states which are related to at least one final state, is the precondition for which execution of Q will terminate with a defined outcome. Outside its domain, program Q may not terminate. Using the notation Q(x) for the image of x under R suggests that we may equivalently view Q as a set valued function. In particular, where convenient, we define a relation Q by an equation of the from Q(x) = e for all x. The sequential (demonic) composition Q ; R is Q ◦ R restricted to those initial states for which Q leads to intermediate states in which R is defined. If x Q y and y is not in the domain of R, then x is not in the domain of Q ; R: Q ; R = {(x, z) | x(Q ◦ R)z ∧ (∀y . x Q y ⇒ R(y) 6= ∅)}. Assume Q, Q′ ∈ S ↔ S. Relation Q′ is an (algorithmic) refinement of Q if Q′ is “more deterministic” than Q and the domain of Q′ is not smaller than the domain of Q: Q ⊑ Q′ ⇔ (∀x . Q(x) 6= ∅ ⇒ Q′ (x) ⊆ Q(x) ∧ Q′ (x) 6= ∅). Now assume that Q is as above and Q′ ∈ S′ ↔ S′ . Let R be a relation between the state spaces of Q and Q′ , i.e. R ∈ S ↔ S′ . Then Q′ is a data refinement of Q via R means: Q ⊑R Q′ ⇔ (∀x . Q(x) 6= ∅ ⇒ (R ◦ Q′ )(x) ⊆ (Q ◦ R)(x) ∧ (R ◦ Q′ )(x) 6= ∅). Algorithmic refinement is a special case of data refinement, Q ⊑Id Q′ ⇔ Q ⊑ Q′ where Id is the identity relation. Refinement is reflexive and transitive in the sense that Q ⊑ Q and for Q′′ ∈ S′′ ↔ S′′ and R′ ∈ S′ ↔ S′′ : Q ⊑R Q′ ∧ Q′ ⊑R′ Q′′ ⇒ Q ⊑R◦R′ Q′′ . Sequential composition is monotonic with respect to refinement: P ⊑R P ′ ∧ Q ⊑R Q ′ ⇒ P ; Q ⊑R P ′ ; Q ′ .

6

5

Automata

The standard automata model is used for module specifications by associating signatures with the alphabet, similarly to [20]. Data refinement is used for forward simulation of automata. Simulations of automata are further discussed in in [10, 21]. Let ∆ be an alphabet, ∆∗ be the set of all sequences built from the elements of ∆ including the empty sequence denoted by ε. For every two sequences x, y ∈ ∆∗ , their concatenation is denoted by x.y. A (nondeterministic) automaton A is a quadruple, A = (∆, S, ̺, s0 ), where ∆ is the alphabet, S is the (finite or infinite) set of states, ̺ is the transition relation, ̺ ∈ ∆ → S ↔ S, and s0 ∈ S is the initial state. The extended transition relation ̺∗ ∈ ∆∗ → S ↔ S, is defined for every x ∈ ∆∗ and a ∈ ∆ as: ̺∗ (ε) = Id, ̺∗ (x.a) = ̺∗ (x) ; ̺(a). We use automata for specifying modules: The set ∆ consists of sequences of call-responses, the set S is the state private to the module in the sense that it is only accessed through calls to the module, the state s0 is the initial state of the module, and the function ̺ specifies the change of the module’s state for each possible call. Formally, for a given signature E, a module specification A is an automaton: A = (∆E , S, ̺, s0 ). The set L(A) = {x ∈ ∆∗ | ̺∗ (x)(s0 ) 6= ∅} contains all valid sequences of call-responses of the module, i.e. describes the normal behavior of the module. Module A is transition deterministic if |̺(b)(s)| ≤ 1 for all b ∈ ∆E and s ∈ S. Module A is output deterministic if for all a ∈ ΣE there exists at most one d ∈ OE such that ̺(a:d) 6= ∅, where ∅ is the empty relation. Module A is deterministic if it is both transition deterministic and output deterministic. Abstraction in the module specification is achieved in two ways. First, the automaton may be nondeterministic, thus hiding implementation decisions. Secondly, the automaton can use a more abstract state space than would be required for an (efficient) implementation. Abstraction is formalized by introducing a refinement relation between modules. Module refinement is defined in terms of the observable behavior, which ultimately are the possible values returned after a sequence of calls. We first decompose ̺ into a transition relation δA ∈ Σ → (S ↔ S), or δ for short, and a value relation vA ∈ Σ → (S ↔ O), or v for short: S δ(a) = {̺(a:d) | d ∈ O}, v(a) = {(s, d) | ̺(a:d)(s) 6= ∅}. The extended transition relation δ∗ ∈ Σ∗ → (S ↔ S), is defined by δ∗ (ε) = Id and δ∗ (x.a) = δ∗ (x) ; δ(a). The response relation rA ∈ Σ+ ↔ O, or r for short, defines the set of all possible responses (outputs) after a non-empty sequence of calls, starting from the initial state: r(x.a) = (δ∗ (x) ; v(a))(s0 ). For a given signature E, let A = (∆E , S, ̺, s0 ) and A′ = (∆E , S′ , ̺′ , s′0 ) be module specifications with the same alphabet but possibly different state space, transition function and initial state. Then A′ is a 7

behavioral refinement of A, written A ≤ A′ , if after any sequence of calls for which A returns some defined output, A′ returns also at least one output value and all the possible outputs returned by A′ would also possible for A: A ≤ A′ ⇔ rA ⊑ rA′ . Note that behavioral refinement is expressed without direct reference to the states of A and A′ . If follows immediately that behavioral refinement is reflexive and transitive. For example, given appropriate definitions of the modules, we would have: Very Drunk Stack ≤ Drunk Stack and Drunk Stack ≤ Stack. Let E be a signature, let ̺ ∈ ∆E → S ↔ S and ̺′ ∈ ∆E → S′ ↔ S′ be transition relations with the same alphabet but different state space, and let R be a relation between S and S′ . We note that ∆E ⊆ ΣE × OE . Transition relation ̺ is data-refined by ̺′ , written ̺ ⊑R ̺′ , means that for a given initial state and access program call, the outputs which are possible with ̺′ are also possible with ̺ (the nondeterminism in selecting a response may be reduced) and the final states which are possible for ̺′ are also possible with ̺ (the nondeterminism in selecting a final state may be reduced), where the initial and final states are related via R. Moreover, whenever for a given initial state and access program call at least one response and final state are defined in ̺, there must be also at least one response and final state defined by ̺′ (the domain must not be reduced), where the initial and final stated are related via R. For this, let ̺˜ ∈ ΣE × S ↔ OE × S be a relation which is isomorphic to ̺ but makes ΣE part of the initial state space and OE part of the final state space. Data refinement is defined in terms of ̺˜ and ̺˜′ : ̺ ⊑R ̺′ ⇔ ̺˜ ⊑Id×R ̺˜′ , (a, s) ̺˜ (d, t) ⇔ s (̺(a:d)) t. Module specification A′ simulates A via simulation relation R if the initial values are in relation R and the transition relations are data refined via R: A ⊑R A′ ⇔ s0 R s′0 ∧ ̺ ⊑R ̺′ . If for some relation R module A′ simulates module A, then A′ is a behavioral refinement of A. Hence this gives a practical way of establishing module refinement: Proposition 5.1 For a given signature E, let A = (∆E , S, ̺, s0 ) and A′ = (∆E , S′ , ̺′ , s′0 ) be module specifications. If R ∈ S ↔ S′ then: A ⊑R A ′ ⇒ A ≤ A ′ . For the purpose of the proof we need two lemmas. First, we generalize data refinement to allow different relations for the initial and final state space. Assume Q ∈ S0 ↔ S1 , Q′ ∈ S0′ ↔ S1′ , T ∈ S0 ↔ S0′ , and U ∈ S1 ↔ S1′ . Data refinement of Q by Q′ via T, U is defined by: Q ⊑T,U Q′ ⇔ (∀x . Q(x) 6= ∅ ⇒ (T ◦ Q′ )(x) ⊆ (Q ◦ U)(x) ∧ (T ◦ Q′ )(x) 6= ∅). Ordinary data refinement is a special case since Q ⊑T Q′ ⇔ Q ⊑T,T Q′ . Sequential composition is monotonic with respect to generalized data refinement in the sense that, assuming additionally R ∈ S1 ↔ S2 , R′ ∈ S1′ ↔ S2′ , and V ∈ S2 ↔ S2′ : Q ⊑T,U Q′ ∧ R ⊑U,V R′ ⇒ Q ; R ⊑T,V Q′ ; R′ . 8

For removing a data refinement on the initial state we have that for any Q, R, x, x′ : x R x′ ∧ Q ⊑R,Id Q′ ⇒ (Q(x) 6= ∅ ⇒ Q′ (x′ ) ⊆ Q(x) ∧ Q′ (x′ ) 6= ∅). Proof of Proposition 5.1 First, we observe that by definition, A ⊑R A′ implies ̺˜ ⊑Id×R ̺˜′ . From this, we can show that for any a ∈ Σ: [

{̺(a:d) | d ∈ O} ⊑R

[

{̺′ (a:d) | d ∈ O}.

This is done by expanding the definitions and some subsequent simplifications. By the definition of δA (a), this is equivalent to: ∀a ∈ Σ . δA (a) ⊑R δA′ (a). With the monotonicity of sequential composition and by using induction over the length of x ∈ Σ∗ we conclude: ∀x ∈ Σ∗ . δA∗ (x) ⊑R δA∗ ′ (x). Furthermore, from the definition of vA and ̺˜ ⊑Id×R ̺˜′ we get: ∀a ∈ Σ . vA (a) ⊑R,Id vA′ (a). Again, this is done by expanding the definitions and some subsequent simplifications. From these two, using the monotonicity lemma above, we have: ∀x ∈ Σ∗ , a ∈ Σ . δA∗ (x) ; vA (a) ⊑R,Id δA∗ ′ (x) ; vA′ (a). As s0 R s′0 holds by the assumption that A ⊑R A′ , we can apply above lemma for removing data refinement on the initial state and get: ∀x ∈ Σ∗ , a ∈ Σ . (δA∗ (x) ; vA (a))(s0 ) 6= ∅ ⇒ (δA∗ ′ (x) ; vA′ (a))(s′0 ) ⊆ (δA∗ (x) ; vA (a))(s0 ) ∧ (δA∗ ′ (x) ; vA′ (a))(s′0 ) 6= ∅. By the definition of rA this is equivalent to: ∀x ∈ Σ∗ , a ∈ Σ . rA (x.a) 6= ∅ ⇒ rA′ (x.a) ⊆ rA (x.a) ∧ rA′ (x.a) 6= ∅, which again is equivalent to rA ⊑ rA′ , and hence implies A ≤ A′ .

6

Mealy Machines

In contrast to automata, Mealy machines specify the next state and the output by separate functions. For a given signature E, a Mealy machine M is a tuple, M = (∆E , S, δ, v, s0 ) where ∆E are call-responses of signature E, S is the (finite or infinite) set of states, δ ∈ ΣE → S ↔ S is the state transition relation, v ∈ ΣE → S → OE , s0 ∈ S is the initial state, and all valid call-responses of δ and v are according to ∆E : ∀a ∈ ΣE , s ∈ S . δ(a)(s) 6= ∅ ⇒ a:v(a)(s) ∈ ∆E 9

b:3 a:1 j  @ s2 @ @

- qjs1 @ @ @ @ @b:2 @ @ @ Rj @ b:1 s3@ @

a:2

a:2 δ(b)(s1 ) = {s3 } v(b)(s1 ) = 2

)



s1j

b:2

- s3j ⇔

̺(b:2)(s1 ) = {s3 }

Figure 1: The Mealy machine with Σ = {a, b}, O = {1, 2, 3}, or the deterministic automaton with ∆ = {a:1, a:2, b:1, b:2, b:3}. The state s1 is initial. For every Mealy machine M we can construct an automaton AM , AM = (∆E , S, ̺, s0 ) over the same alphabet ∆E , same set S of states, same initial state s0 and the transition relation ̺ ∈ ∆E → S ↔ S defined by: ( δ(a)(s) if v(a)(s) = d ̺(a:d)(s) = ∅ if v(a)(s) 6= d The automaton AM is equivalent to the Mealy machine M in the sense that the set of valid call-response sequences of M and AM are identical. Figure 1 illustrates the relationship between M and AM . However, not every automaton, even not every deterministic automaton, can be interpreted as a Mealy machine. In Figure 1, if one adds an arrow from s1 to s3 labeled by a:2, the new automaton cannot be interpreted as a Mealy machine 1 . We may use both Mealy machines and standard automata as the backbone of our model. The descriptive power of Mealy machines is at best the same as transition deterministic automata, only notation is different, more complex in our opinion. It might occasionally be convenient to use Mealy machines instead of standard automata. As an example, we define simulation of Mealy machines which implies simulation of the corresponding automata. Assuming M = (∆E , S, δ, v, s0 ) and M ′ = (∆E , S′ , δ′ , v′ , s′0 ) we define: M ⊑R M ′ ⇔ s0 R s′0 ∧ δ ⊑R δ′ ∧ v ⊑R v′ v ⊑R v′ ⇔ ∀s ∈ S, s′ ∈ S′ , a ∈ ΣE . s R s′ ⇒ v(a)(s) = v′ (a)(s′ ) Proposition 6.1 M ⊑R M ′ ⇒ AM ⊑R AM′ . 1

Since v ∈ ΣE → S → OE , then v(s, a) can be equal to 1 or 2 but not both. Extending v to ΣE → (S ↔ O) does not help, since it does not indicate that a:1 leads from s1 to s2 and a:2 from s1 to s3 .

10

(b, 1) v Q  S Q (b, 2) S Q sP Q (b, 3) 1v  (c, 1) PP S  qv -v P  v v 7  (a, 1) PP  1   S w Sv @ q P  (a, 3) P 3  @  (c, 2) @ Rv @  

(a, 1) (b, 1) (a, 2) (c, 1) (b, 2) (d, 1) v

-v

-v

-v

-v

-v

(a, 2)

(a)

(b) Figure 2: (a) Total order defined by the sequence a.b.a.c.b.d and (b) Weak order defined by the sequence a.ha.c.bi.hb.ci.a.b. Proof Suppose M = (∆E , S, δ, v, s0 ) and M ′ = (∆E , S′ , δ′ , v, s′0 ). We have to show that s0 R s′0 and ̺ ⊑R ̺′ where ̺(a:d)(s) = δ(a)(s) if v(a)(s) = d and ∅ otherwise, and similarly ̺′ (a:d)(s) = δ′ (a)(s) if v′ (a)(s) = d and ∅ otherwise. The first part follows immediately from M ⊑R M ′ , the second part can be shown to hold by first unfolding the definitions. The difference is that here the refinements of δ and v are dealt with separately, which may be of practical advantage. In general the standard automata provide a better and simpler model. In particular adding nondeterminism to value functions in Mealy formalism is problematic and, although possible, is seldom done, because the formalism becomes complex. In [27, 33, 15, 24] Mealy machines were used and we believed that resulted in unnecessary complexity and formal problems [33]. After deciding to use automata as a backbone of the specification technique, the next question is how to describe the set of states in an as abstract as possible way, i.e. in a way which does not commit to implementation decisions prematurely.

7

Defining Objects by Sequences

The ingenuity of the trace assertion method ([3]) is to use traces (i.e. some kind of sequences) not only as a medium to describe behavior, but to specify states as well. Sequences are easy to specify and understand and, since we observe only traces of call-responses, they provide anyway the entire visible information. Let ∆ be an alphabet (possibly infinite), and let x ∈ ∆∗ . Not assuming an interpretation of elements of ∆, what kind of structure can x be, what kind of information can x contain? Consider x = a.b.a.c.b.d. The sequence x can be interpreted as a total order tox of the occurrences of elements of ∆, as illustrated in Figure 2(a). By an occurrence of a we mean a pair (a, i), where i is a natural number indicating the occurrence. Consider now the sequence y = a.b.c.d and suppose that we have the additional information that the order between the occurrences of a, b, c, d does not matter. To express this, we introduce a partial operator h·i which takes a plain sequence and removes the order of its elements. Sequences where each element of the alphabet occurs at most once are called plain. The set of all plain sequences over ∆ is

11

denoted by Plain(∆). We can interpret h·i as transforming a plain sequence into the corresponding set, for example hyi” can be interpreted as {a, b, c, d}. By mixing “h·i” with standard concatenation “.”, we obtain sequences like a.ha.c.bi.hb.ci.a.b. Such sequences are used especially in concurrency theory. They are called step-sequences or subset languages ([18, 29]). They represent weak (or stratified) partial orders ([9, 18]). Figure 2(b) illustrates this relationship. Formally step-sequences are constructed as sequences over the alphabet Fin(2∆ ), where for every family of sets X , Fin(X ) = {X | X ∈ X ∧ X is finite}. In this sense our a.ha.c.bi.hb.ci.a.b corresponds to the sequence of sets: {a}.{a, c, b}.{b, c}.{a}.{b}. From Szpirlajn theorem [9] it follows that every partial order corresponds uniquely to the set of all its total extensions. In particular every set X = {a1 , . . . , an } is a partial order with empty ordering relation, and it can be seen as a description of the set of all total order that can be built from the elements of X. Since finite total orders can be specified as sequences, the set {a1 , . . . , an } can be seen as a description of all plain sequences built from a1 ,. . . ,an . For instance {a, b, c} can be seen as a description of the set of sequences {a.b.c, a.c.b, b.a.c, b.c.a, c.a.b, c.b.a}. In general a step-sequence can are be interpreted as a set of all sequences corresponding to all total extensions of the weak orders specified by the step-sequence. For instance the step-sequence a.hb.ai.c.ha.ci defines the set of sequences: {a.b.a.c.a.c, a.a.b.c.a.c, a.b.a.c.c.a, a.a.b.c.c.a}. The set of sequences corresponding to the step-sequence from Figure 2(b) consists of 12 elements, including for instance a.a.c.b.b.c.a.b and a.b.c.a.c.b.a.b Formally, the set of step-sequences over ∆, denoted h∆∗ i, is the smallest set of sequences over ∆ ∪ {h, i} such that: • every x ∈ ∆∗ is a step-sequence, • if x ∈ Plain(∆), then hxi is a step-sequence, • if x and y are step-sequences, then x.y is a step-sequence. In addition to the above concatenation “.” on step-sequences, we define weak concatenation, denoted by “⌣”. Weak concatenation with an empty step-sequence is defined by: x ⌣ ε = x and ε ⌣ y = y. For non-empty step-sequences x and y, the idea of x ⌣ y is to merge the last “step” of x with the first “step” of y. If the result of the concatenation of the last and first “step”, respectively, is plain, then we have for instance (a.b) ⌣ c = (a.c) ⌣ b = a.hb.ci, ha.bi ⌣ hc.d.ei = ha.b.c.d.ei, and (a.ha.bi) ⌣ (hc.di.a) = a.ha.b.c.di.a. For non-plain step-sequences, “⌣” can be illustrated as follows: if x = ha.bi.c.ha.ci and y = ha.bi.a.c then x ⌣ y = ha.bi.c.ha.b.ci.a.c, i.e. the last step of x, ha.ci, is merged with the first step ha.bi of y. We would also like to write expressions like (ha.bi.c.ha.ci) ⌣ (ha.bi.a.c) = ha.bi.c.(ha.ci ⌣ ha.bi).a.c . Formally, weak concatenation can be defined as follows. Since every non-empty step sequences x, y can be expressed as x = x1 .α, y = β.y1 , where α = hti or α = a, β = hsi or β = b, t and s are plain, t 6= ǫ, s 6= ǫ, a, b ∈ ∆, we can define x ⌣ y in this case by: x ⌣ y = x1 .(α ⌣ β).y1 12



The interpretation of step-sequences is given by a mapping sem : h∆∗ i → 2∆ . Let set(x) denote the set of all elements of ∆, from which the string x ∈ ∆∗ is built. For example set(a.b.c.a.c) = {a, b, c}. The mapping sem may be defined as follows: 1. ∀x ∈ ∆∗ . sem(x) = {x}, 2. ∀x ∈ Plain(∆) . sem(hxi) = {y ∈ Plain(∆) | set(x) = set(y)}, 3. ∀x, y ∈ h∆∗ i . sem(x.y) = sem(x).sem(y), where “.” in “sem(x).sem(y)” denotes the standard concatenation of sets of sequences (see [13]). For instance sem(ha.b.ci) = {a.b.c, a.c.b, b.a.c, b.c.a, c.a.b, c.b.a}, sem(a.hb.ai.c.ha.ci) = {a.b.a.c.a.c, a.a.b.c.a.c, a.b.a.c.c.a, a.a.b.c.c.a}. The two views of step sequences, sequences of sets and sets of sequences, are compatible in the sense that two step sequences x, y ∈ h∆∗ i are equal, x = y, if and only if they are equal in their interpretations as sets of sequences, sem(x) = sem(y). For all x, y ∈ h∆∗ i we will say that x is a prefix of y if there is z ∈ h∆∗ i such that y = x.z or y = x ⌣ z. For every t ∈ h∆∗ i and every a ∈ ∆ we shall write a ∈ t if a is contained in t. For instance a ∈ b.ha.bi, and a ∈ / b.b.c. We use step-sequences to specify states of automata.

8

Trace Only Automata

Let A = (∆, S, ̺, s0 ) be an automaton. We shall say that A has the canonical trace property (ct-property) if for every state s ∈ S there is a trace xs ∈ ∆∗ such that ̺∗ (xs )(s0 ) = {s}. Not every automaton has ct-property and every transition-deterministic automaton has ct-property. The automaton from the left hand side of Figure 3 does not have ct-property (the automaton can then “generate” only two traces ǫ and a and it has three states). Frequently there is more than one xs satisfying ̺∗ (xs )(s0 ) = {s}. For example for the automaton from Figure 1 and the state s3 we have (s1 is initial here) ̺∗ (b:2.(a:2)i )(s1 ) = {s3 }, for every i ≥ 0. If A has ct-property we may define a set of canonical traces [27]. A set of traces CanTr ∈ ∆∗ is canonical if for every s ∈ S there is exactly one xs ∈ CanTr, its unique representation, such that ̺∗ (xs )(s0 ) = {s}. Automaton A is isomorphic to Act = (∆, CanTr, ̺ct , xs0 ), where ̺ct (a)(xs ) = {xs1 , . . . , xsk } ⇐⇒ ̺(a)(s) = {s1 , . . . , sk }. Automata like Act are called trace only automata since their states are defined in terms of traces. Mealy machine counterparts of trace only automata are used extensively for the trace assertion method, e.g. [14, 16, 15, 24, 27, 33]. The problem is that using traces may frequently result in a kind of asymmetry which makes the specification less readable than expected. Consider the automaton on the right hand side of Figure 3. It occurs typically as part of a greater automaton. The state s4 is unambiguously defined by two traces a.a.b and a.b.a. Each of them can be chosen as a canonical one. If a.a.b is chosen, then the canonical trace x = a.a is a prefix of a.a.b, hence we have ̺ct (b)(x) = {x.b}. The canonical trace y = a.b is not a prefix of a.a.b, so ̺ct (a)(y) 6= {y.a}. The asymmetry is induced by the choice of a canonical trace, the automaton itself is symmetrical, from the state s1 we reach s4 in two steps, using both a and b in any order, a.b or b.a. Such asymmetry makes some specifications unnecessarily complex. The Unique Integer module is a classical example, but the problem occurs frequently in real modules

13

a.a s3j

> Z  Zb  a ε a a.a.b or a.b.a Z~ Z sj - s1j j q 4 > a.ha.bi Z s0 a  Zb  a ZZ  ~ sj 2

qj  A A  Aa a  A  A  U A  j j

a.b (b)

(a)

Figure 3: (a) Example of an automaton which does not have ct-property and (b) Example of asymmetry when canonical traces are used. as well. The asymmetry disappears when step-traces are used to identify states. When the state s4 is identified by a.ha.bi, then both a.b and a.a are prefixes of a.ha.bi (a.ha.bi = (a.b) ⌣ a = (a.a) ⌣ b). For an automaton A with ct-property, we define the set C ⊆ h∆∗ i of canonical step-traces. Let ̺¯ be an extension of ̺ defined on the Cartesian product of the states and plain traces: ̺¯(t) =

(

̺∗ (x) x ∈ sem(t) ∧ (∀y, z ∈ sem(t) . ̺∗ (y) = ̺∗ (z)) ∅ otherwise

Now, let C be any subset of h∆∗ i satisfying: 1. ∀t ∈ C. ∃st ∈ S. ∀x ∈ sem(t) ̺∗ (x)(s0 ) = {st }, 2. 2∀s ∈ S.∃!ts ∈ C. ̺¯∗ (ts )(s0 ) = {s}. The symbol ∃! denotes “there exists exactly one”. Note that the ct-property implies the existence of (at least one) C. What if an automaton does not have the ct-property? First we must note that such a situation occurs rather seldom in practice. The Drunk Stack has the ct-property, Very Drunk Stack does not, but neither of them is a part of any real system. They were chosen to illustrate potential problems. But if the best and most readable model of a module is an automaton-like structure without ct-property, we can use a concept similar to labeled transition system [2]. In contrast to automata, each arrow in a transition system has a unique name. The elements of ∆ attached to arrows in automata are called labels in transition systems. By “labeled transition system” we mean that each arrow has two attachments, a unique name, and a not necessarily unique label. We do not need each arrow to be unique, we need only the ct-property, so the following construction is proposed. An automaton with the alphabet of state constructors is a tuple A = (∆, Υ, S, ̺, s0 ), where ∆ is the alphabet, Υ is the state constructors alphabet, S is the set of states, ̺ is the transition relation, ̺ ∈ (∆ ∪ Υ) → S ↔ S, and s0 ∈ S is the initial state. The transition relation ̺ must satisfy the following conditions: 14

1. ∀α ∈ Υ, s ∈ S . |̺(α)(s)| ≤ 1, 2. ∀s1 , s2 ∈ S . (∃a ∈ ∆ . s2 ∈ ̺(a)(s1 ) ⇔ ∃α ∈ Υ . ̺(α)(s1 ) = {s2 }). The set L(A) = {x ∈ ∆∗ | ̺∗ (x)(s0 ) 6= ∅} describes the normal behavior of the module. The elements of Υ do not occur in L(A). We do not assume ∆ ∩ Υ = ∅. The first condition says that A restricted to Υ is a transition-deterministic automaton. Hence the ct-property is guaranteed. The second condition guarantees that each arrow is marked by one element of ∆ and one element of Υ. Since automata with state constructors alphabet do always have ct-property, their states can always be specified as canonical step-sequences. Every automaton may be extended to an equivalent automaton with state constructor alphabet by simply defining Υ = {(s, a, s′ ) | s′ ∈ ̺(v)(s)}, and extending ̺ onto Υ by ̺(s, a, s′ )(s) = {s′ }. This construction results in a labeled transition system, and is of a very little use in practice, but is always possible.

9

Trace Assertion Specifications

Trace assertion specifications emerge when using canonical traces for the states of module specifications. More precisely, given a signature E, a trace assertion specification TA is a module specification TA = (∆E , C, ̺, t0 ), where C ⊆ h∆∗E i is a set of step-traces such that every step-trace t describes unambiguously one state, and this is the state the sequence x ∈ sem(t) leads to: ∀t ∈ C, x ∈ sem(t) . ̺∗ (x)(t0 ) = {t}. For the Stack and Drunk Stack modules, C can just be the set of all sequences of type PUSH(i1 ). PUSH(i2 ). . . . .PUSH(ik ), and for instance: ̺(TOP:4)(PUSH(5).PUSH(7).PUSH(4)) = {PUSH(5).PUSH(7).PUSH(4)} ̺(TOP:8)(PUSH(5).PUSH(7).PUSH(4)) =∅ ̺(PUSH(5))(PUSH(5).PUSH(7).PUSH(4)) = {PUSH(5).PUSH(7).PUSH(4).PUSH(5)} The access program called POP behaves differently in Stack than in Drunk Stack, for example: ̺(POP)(PUSH(5).PUSH(7).PUSH(4)) = {PUSH(5).PUSH(7)} for Stack, while for Drunk Stack: ̺(POP)(PUSH(5).PUSH(7).PUSH(4), POP) = {PUSH(5).PUSH(7), PUSH(5)}. For the Unique Integer module, the set C can be defined as the set of all step-sequences hGET:i1 .GET: i2 . . . . .GET:ik i, where ij = ik ⇔ j = k, and for instance: ̺(GET:7)(GET:3.GET:6.GET:9) = {hGET:3.GET:6.GET:9.GET:7i} = {hGET:3.GET:6.GET:7.GET:9i} ̺(GET:3)(GET:3.GET:6.GET:9) = ∅ 15

The Very Drunk Stack cannot be modeled (in a natural way) by TA as defined above. Given a trace assertion specification TA, we define the competence function κ : C × Σ → Bool = {0, 1}, in the following way: κ(t, a) =

(

0 if ∀d ∈ O . ̺(a:d)(t) = ∅ 1 if ∃d ∈ O . ̺(a:d)(t) 6= ∅

The notion of competence function follows from [24]. It defines what is a misuse. If κ(t, a) = 0, then the use of a at the state described by t is a misuse. For both Stack and Drunk Stack we have: κ(ε, POP) = κ(ε, TOP) = 0, and κ(tfull , PUSH(d)) = 0, if tfull represents the full stack. Otherwise κ(t, a) = 1. For the Unique Integer, κ(t, GET) = 0 only if t represents the state where all available integers are used. Let π : h∆∗ i → hΣ∗ i be a projection mapping, for any a:d ∈ ∆ and x, y ∈ h∆∗ i defined by: π(ε) = ε,

π(a:d) = a,

π(x.y) = π(x).π(y),

π(hxi) = hπ(x)i.

For example π(a1:d1 .a2:d2 .a3:d3 .a4:d4 ) = a1 .a2 .a3 .a4 , and π(a:3.ha:2.b:2i.ha:3.a:2i) = a.ha.bi.ha.ai. A trace assertion specification TA is output independent if for every x, y ∈ L(TA), x = y ⇔ π(x) = π(y), otherwise it is output dependent. If TA is output independent then π can be interpreted as a one-to-one function, so π −1 is a function on π(L(TA)). Both Stack and Drunk Stack are output independent while Unique Integer is not. Note that in [27, 33] and others the output independent trace assertion specifications are called deterministic while output dependent are called non-deterministic. In our terminology, both Stack and Unique Integer are transition deterministic, while Drunk Stack is not. Transition determinism does not imply output independence and output independence does not imply transition determinism. Unique Integer is transition deterministic but output dependent, Drunk Stack is not transition deterministic but output independent.

10

Mealy Form of Trace Assertion Specifications

If TA is output independent, it may be represented as a kind of a Mealy machine, with a separate specification of the output function. Most trace assertion models in the literature are based on Mealy machines. We think that, in general, the automata concept is better, but for the output independent TA’s the Mealy model also leads to equally readable specification. It also helps to explain the relationship with algebraic specifications (see Chapter 16). Lemma 10.1 If TA is output independent then, for all t ∈ C, a ∈ Σ, and all d ∈ O, ̺(a:d)(t) 6= ∅ ⇒ (∀d′ 6= d . ̺(a:d′ )(t) = ∅).

16

Proof. Suppose that there are d, d′ ∈ ∆∗ , a ∈ C, such that d 6= d′ , and ̺(a:d)(t) 6= ∅, ̺(a:d′ )(t) 6= ∅. Hence for all x ∈ sem(t), x.a:d 6= x.a:d′ , while π(x.a:d) = π(x).a = π(t.a:d′ ), a contradiction. Lemma 10.1 says that for output independent TA, for every t ∈ C, a ∈ Σ, there exists at most one d ∈ O such that ̺(a:d)(t) is not empty. Given an output independent trace assertion specification, we define the mapping δ : Σ → π(C) ↔ π(C), the calls only transition function, as δ(a)(π(t)) = π(̺(a:d)(t)) and the mapping v : Σ → π(C) → O ∪ {nil}, the output value function as follows: v(π(t), a) =

(

d if ∃d ∈ O . ̺(a:d)(t) = 6 ∅ nil if ∀d ∈ O . ̺(a:d)(t) = ∅

Lemma 10.1 guarantees the well-definedness of δ and v. Proposition 10.2 If TA is output independent and deterministic then for all t, s ∈ C, a:d ∈ ∆: s ∈ ̺(a:d)(t)

⇐⇒

π(s) ∈ δ(a)(π(t)) ∧ v(a)(π(t)) = d.

Proof. (⇒) From the definitions of δ and v. (⇐) Suppose s ∈ / ̺(a:d)(t). We have to consider two cases. Case 1. ̺(a:d)(t) = ∅. From the definition of v, we have v(a)(π(t)) = nil 6= d. Case 2. ̺(a:d)(t) 6= ∅. Then δ(a)(π(t)) = π(̺(a:d)(t)). If π(s) ∈ δ(a)(π(t)) then there exists s′ ∈ C such that π(s) = π(s′ ), a contradiction, since TA is output independent. Proposition 10.2 guarantees that for output independent TA’s, the mapping ̺ is completely defined by δ and v. The Stack is output independent, so instead of ̺(TOP:4)(PUSH(5).PUSH(7).PUSH(4)) = {PUSH(5).PUSH(7).PUSH(4)}, one can write equivalently, as commonly used in the existing literature: δ(TOP)(PUSH(5).PUSH(7).PUSH(4)) = {PUSH(5).PUSH(7).PUSH(4)}, v(TOP)(PUSH(5).PUSH(7).PUSH(4)) = 4. Proposition 10.2 allows to represent any output independent trace assertion specification in an equivalent form, which is called the Mealy form. Formally the Mealy form of TA is defined by: TAMealy = (∆E , π(C), δ, v, t0 ). For every trace assertion specification TA, not necessary output independent, an explicate value function v̺ : Σ → C ↔ O can be defined as v̺ (a)(t) = {d | ̺(a:d)(t) 6= ∅}. But v̺ differs from v. The function v can only be defined for an output independent TA, occurs together with δ and cannot be derived form δ. The mapping v̺ is redundant, it is derived from ̺.

17

For the Unique Integer module which is output dependent one may just write ̺(hGET:3.GET:6.GET:9i, GET:7) = {hGET:3.GET:6.GET:9.GET:7i}, ̺(hGET:3.GET:6.GET:9i, GET:i) = ∅ if i ∈ {3, 6, 9}, or one may write equivalently: ̺(hGET:3.GET:6.GET:9i, GET:7) = {hGET:3.GET:6.GET:7.GET:9i}, ̺(hGET:3.GET:6.GET:9i, GET:i) = ∅ if i ∈ {3, 6, 9}, v̺ (hGET:3.GET:6.GET:9i, GET) = {i | i ∈ / {3, 6, 9}}. The second way is longer and, in our opinion, does not increase readability. It is used in [33] and others. All the concepts introduced so far do not allow to model Very Drunk Stack in a natural way. The reason is that in this case the natural states of the stack, i.e. the sequences of integers, cannot be unambiguously described by the sequences of call-responses. The solution we suggested in Section 2 is to introduce a state constructor push1(i) to describe the stack states.

11

Trace Assertion Specifications with State Constructors

Given a signature E, a trace assertion specification with state constructors is an automaton ΥTA = (∆E , Υ, C, ̺, t0 ), with the alphabet Υ of state constructors where, as for trace assertion specifications, C ⊆ hΥ∗ i is the set of canonical step-traces: ∀t ∈ C, x ∈ sem(t) . ̺∗ (x)(t0 ) = {t}. We do not assume ∆E ∩ Υ = ∅, although it may often happen. The elements of Υ \ ∆E are invisible (abstract). For the Very Drunk Stack module, the set Υ is the set of all abstract invisible calls push1(i), where i is any available integer, and C is the set of all sequences push1(i1 ). . . . .push1(ik ). In this case we have ∆E ∩ Υ = ∅. For instance ̺(TOP:4)(push1(5).push1(7).push1(4)) ̺(TOP:8)(push1(5).push1(7).push1(4)) ̺(PUSH(5))(push1(5).push1(7).push1(4)) ̺(POP)(push1(5).push1(7).push1(4))

= {push1(5).push1(7).push(4)} =∅ = {t1 , t2 } = {push1(5).push1(7), push1(5)},

where t1 = push1(5).push1(7).push1(4).push1(5), t2 = push1(5).push1(7).push1(4).push1(5).push1(5). A trace assertion specification with state constructors ΥTA defines the following normal behavior L(ΥTA) = {x ∈ ∆∗ | ̺∗ (x)(t0 ) 6= ∅}. Note that Υ is not involved in L(ΥTA). The output independent ΥTA, the Mealy form of an output independent ΥTA, and the competence function κ are defined analogously as for TA.

18

Introducing invisible state constructors is clearly against the philosophy of the trace assertion method as formulated in [3]. One of the advantages claimed in [3] was no need for hidden functions to specify modules with delays. The algebraic specifications of those modules have required hidden functions. On the other hand, what we really want is to specify the visible behavior of a module in the most easy and readable yet precise way. The states are auxiliary concepts, and the invisible calls seem to serve well as the state constructors.

12

Enhancements and Exceptional Behavior

Large specifications are best developed and presented in a number of steps of increasing complexity. In particular, we suggest that the first step describes the normal behavior and exceptional behavior is added in the second (or later) step. The second step can be seen as an enhancement of the first step, in the sense that it additional behavior is specified while the original is preserved. Let us take the stack module and a trace t = PUSH(i1 ).PUSH(i2 ). . . . .PUSH(in ). Suppose that the stack has a bound n, i.e. t is a state of the full stack, and consider the trace: t.PUSH(in+1 ) = PUSH(i1 ).PUSH(i2 ). ... .PUSH(in ).PUSH(in+1 ). Since we cannot prevent such an access program call to occur, the question arises what behavior this trace describes. Defining the transition relation to be empty in this case allows nontermination. Alternatively, we can specify that PUSH(in+1 ) should be ignored or that it replaced the previous top element. However, in any case the state structure of the module is independent of its exceptional behavior. All states of the stack are entirely defined by its normal behavior. Let TA = (∆E , C, ̺, t0 ) be a trace assertion specification. If κ(t, a) = 0 then for all d ∈ O, we have ̺(a:d)(t) = ∅, which means that a at t is a misuse and it does not generate any normal behavior. In principle, an enhancement of TA consists in defining new ̺′ such that ̺′ (a:d)(t) 6= ∅ when κ(t, a) = 0. It is a structure complimentary to TA. Formally, an enhancement enh(TA) of TA is a triple enh(TA) = (∆E′ , C ′ , ̺′ ), where: ∆E′ is an enhanced call-response alphabet, C ′ ⊆ h∆∗E′ i is an enhanced set of canonical traces, ̺′ ∈ ∆E′ → C ′ ↔ C ′ is an enhanced transition relation, which is defined only if the transition relation of TA is not defined, ∀t ∈ C . ∀a ∈ ΣE . κ′ (t, a) = 1 ⇒ κ(t, a) = 0, where κ and κ′ are the competence functions of TA and enh(TA), respectively. The enhancement enh(TA) is called plain if ∆E′ ⊆ ∆E , and C ′ ⊆ C. Non-plain enh(TA) means that there are some special error recovery states and some separate error recovery procedure. We shall not consider such examples in this paper. For Stack and Drunk Stack a plain enhancement can be defined by: ̺′ (POP)(ε) = ̺′ (TOP)(ε) = {ε}

and

̺′ (PUSH(i))(tfull ) = {tfull }

where tfull is the canonical step-trace corresponding to the full stack, and ̺′ (a:d)(t) = ∅ for the rest of t, a, and d. For the Unique Integer module the enhancement can be defined by: ̺′ (GET:nil)(tall ) = {tall } 19

where tall is the canonical trace corresponding to the state where all available integers are used up, and ̺′ (a:d)(t) = ∅ for all other t, a, and d. Enhancements describing exceptional behavior are typically output independent, hence can be represented in a Mealy form (∆E′ , π(C ′ ), δ′ , v′ ). The definition is practically identical as for output independent TA’s. The only difference is that the enhancements do not possess initial step-traces. For Stack and Drunk Stack the plain form of an enhancement can be defined by: δ′ (POP)(ε) = δ′ (TOP)(ε) = {ε},

δ′ (PUSH(d))(tfull ) = {tfull },

v′ (TOP)(tfull ) = nil,

and δ′ (a)(t) = ∅ for the rest of t and a. We obtain the enhanced trace assertion specification ETA by taking the composition (union) of TA and the enhancement enh(TA): The full specification is just a union of TA and enh(TA), ETA = TA∪enh(TA), i.e. ETA = (∆E ∪ ∆E′ , C ∪ C ′ , ̺ ∪ ̺′ , t0 ). Hence ̺+ = ̺ ∪ ̺′ satisfies ̺+ ∈ (∆ ∪ ∆E ) → (C ∪ C ′ ) ↔ C ∪ C ′ , and for all t ∈ C ∪ C ′ , a ∈ ∆E ∪ ∆E ′ , d ∈ O ∪ O ′ , +

̺ (a:d)(t) =

(

̺(t, a:d) if t ∈ C ∧ κ(t, a) = 1 ̺′ (t, a:d) otherwise

Proposition 12.1 For any plain enhancement enh(TA): TA ≤ TA ∪ enh(TA) Proof. Since enh(TA) is plain, we have that ETA = TA ∪ enh(TA) = (∆E , C, ̺ ∪ ̺′ , t0 ). We apply Proposition 5.1 with R = Id. Refinement follows immediately from the above observation that ̺+ defines additional behavior only if κ(t, a) = 0. For every output independent ETA we can standardly built its Mealy form ETAMealy . In a very similar way we may define an enhancement for the trace assertion specification with state constructors as a composition of a trace assertion specification with state constructors and its enhancement.

13

Specification Format

To be useful in practice, the trace assertion technique must provide some specification formats. Two such formats are described and later used. Any trace assertion specification in the standard form consists of four sections: Syntax, Canonical Step-trace Definition, Trace Assertions and Dictionary. A trace assertion specification in the Mealy form consists of five sections: Syntax, Canonical Step-trace Definition, Trace Assertions, Output Values and Dictionary. In the Mealy form the Syntax section is just a table which specifies for each module access-program name f ∈ E, the possible inputs input(f ) and by the number of arguments each program takes and the type of each argument, and the possible outputs output(f ) by the type of each return value. In the standard form it also specifies call-response formats for all access programs. 20

In the Canonical Step-trace Definition section, the predicate canonical and the initial canonical step-trace are defined. In general this could be a complex definition with a tabular notation involved (c.f. [27, 33]). However, in the majority of (well thought of) cases this is a relatively simple formula. The convention [e(xi )]ki=j as a shorthand for e(xj ).e(xj+1 ). . . . .e(xk ) and ε if k < j, is often used. In the standard form the Trace Assertions section is a sequence of trace assertions of the form ̺(a:d)(t) = {t1 , . . . , tk } for all calls a defined in the Syntax section. The traces t, t1 , . . . , tk are the canonical step-traces. Since ̺ is a total function it must be defined for every possible a and d. The convention that empty relations or sets, respectively, are specified by omission is used. If for particular values of t, a and d, the value of the function ̺(a:d)(t) does not appear in the Trace Assertions section this means that ̺(a:d)(t) = ∅. In the Mealy form the Trace Assertions section is a sequence of trace assertions of the form δ(a)(t) = {t1 , . . . , tk }. To specify transition deterministic trace assertions the following tabular notation is used2 . Conditions ̺(a:d)(t) = condition1 .....

Trace Patterns pattern1(t) .....

Equivalence this c’ .....

The column Equivalence defines the canonical step-trace t′ such that ̺(a:d)(t) = {t′ }. Since t here is a variable, t′ could be different for different t, the columns Conditions and Trace Patterns are used to specify all different cases. The column Trace Patterns contains appropriate patterns (or their characteristic predicates) for t, while the column Conditions contains predicates on the trace and argument variables. The first row above should be read if condition1 and pattern1(t) then ̺(a:d)(t) = {this c′ }. The columns Conditions and Trace Patterns can be omitted if not needed. The empty cells in those columns denote the predicate true. For trace assertions which are not transition deterministic the tabular notation is slightly different, namely: Conditions condition1 ̺(a:d)(t) = condition2 .....

Trace Patterns pattern1(t) pattern2(t) .....

Clusters t1,1 t1,2 t1,3 t1,4 t2,1 t2,2 .....

In this case the rows should be read as follows: if condition1 and pattern1(t) then ̺(a:d)(t) = {t1,1 , t1,2 , t1,3 , t1,4 }, if condition2 and pattern2(t) then ̺(a:d)(t) = {t2,1 , t2,2 }, etc. Since in this case the canonical step-traces do not represent equivalence classes but some clusters of traces the third column has now the name Clusters. For the Mealy form we have also the Output Values section, which defines the value function v. A similar tabular notation is used, in this case a table consists of the columns Conditions, Trace Patterns and Value. The nil values are specified by omission. 2

See [17, 19, 26] for details on tabular notation.

21

Syntax of Access Programs Name POP PUSH TOP

Argument

Value

integer integer

Call-Response Forms POP:nil PUSH(d):nil TOP:d

Canonical Step-traces canonical(t) ⇔ t = [PUSH(di )]ni=1 ∧ 0 ≤ n ≤ size t0 = ε Trace Assertions Trace Patterns ̺(POP)(t) = t = s.PUSH(d) % t=ε

Equivalence s ε

Condition ̺(PUSH(d))(t) = length(t) < size % length(t) = size Condition ̺(TOP:d)(t) = %

d = nil

Equivalence t.PUSH(d) t

Trace Patterns t = s.PUSH(d) t=ε

Equivalence t ε

Dictionary size : the size of the stack length(t) : the length of the trace t Figure 4: Enhanced Trace Assertion Specification for Stack Module The Dictionary section provides the definitions of the terms, auxiliary functions, types and other structures that are used in the body of the specification. The Dictionary section is rather short for simple examples. The format for enhanced trace assertion specification is basically the same as the described above. The only difference is that new rows that correspond to the enhancement are added. We use the convention that all the rows added by the enhancement are marked by the symbol “%” at the beginning. Figures 4, 5, 6, 7, 8 present various forms of trace assertion specifications of the Stack, Drunk Stack and Very Drunk Stack modules in the specification format described above.

22

Syntax of Access Programs Name POP PUSH TOP

Argument

Value

integer integer

Canonical Step-traces canonical(t) ⇔ t = [PUSH(di )]ni=1 ∧ 0 ≤ n ≤ size t0 = ε Trace Assertions Trace Patterns δ(POP)(t) = t = s.PUSH(d) % t=ε

δ(PUSH(d))(t) =

δ(TOP)(t) =

Equivalence s ε

Condition length(t) < size % length(t) = size

Equivalence t

or

Equivalence t.PUSH(d) t

δ(TOP)(t) =

Trace Patterns t 6= ε % t=ε

Equivalence t t

Values Trace Patterns v(TOP)(t) = t = s.PUSH(d) % t=ε

Value d nil

Dictionary size : the size of the stack length(t) : the length of the trace t Figure 5: Mealy Form of the Enhanced Trace Assertion Specification for Stack Module

23

Syntax of Access Programs Name GET

Value integer

Call-Response Forms GET:d

Canonical Step-traces canonical(t) ⇔ t = h[GET:di ]ni=1 i ∧ 0 ≤ n ≤ size ∧ (di = dj ⇔ i = j) t0 = ε Trace Assertions

̺(GET:d)(t) =

Condition length(t) < limit ∧ GET:d ∈ /t % length(t) = limit ∧ d = nil

Equivalence t ⌣ GET:d t

Dictionary limit : the number of available integers limit = maxinteger − mininteger + 1 maxinteger : the maximum available integer mininteger : the minimum available integer length(t) : the length of the trace t Figure 6: Enhanced Trace Assertion Specification for Unique Integer Module

14

Refining Modules

We illustrate the refinement of trace assertion specifications by showing that Drunk Stack is refined by Stack. From the Trace Assertion section in Figure 4 we get following transition relation ̺S for Stack: ̺S (POP)(t, t′ )

⇔ ∃d, s . (t = s.PUSH(d) ∧ t′ = s) ∨ (t = ε ∧ t′ = ε) ⇔ ∃d . t = t′ .PUSH(d) ∨ (t = ε ∧ t′ = ε), ′ ̺S (PUSH(d))(t, t ) ⇔ (length(t) < size ∧ t′ = t.PUSH(d)) ∨ (length(t) = size ∧ t′ = t), ̺S (TOP:d)(t, t′ ) ⇔ ∃s . (t = s.PUSH(d) ∧ t′ = t) ∨ (d = nil ∧ t = ε ∧ t′ = ε).

We transform ̺ into ̺˜ such that (a, t) ̺˜ (d, t′ ) ⇔ t (̺(a:d)) t′ (see Section 5): ̺˜S (POP, t)(nil, t′ ) ⇔ ̺S (POP)(t, t′ ), ′ ̺˜S (PUSH(d), t)(nil, t ) ⇔ ̺S (PUSH(d))(t, t′ ), ̺˜S (TOP, t)(d, t′ ) ⇔ ̺S (TOP:d)(t, t′ ).

24

Syntax of Access Programs Name POP PUSH TOP

Argument

Value

integer integer

Call-Response Forms POP:nil PUSH(d):nil TOP:d

Canonical Step-traces canonical(t) ⇔ t = [PUSH(di )]ni=1 ∧ 0 ≤ n ≤ size t0 = ε Trace Assertions Trace Patterns t = PUSH(d) ̺(POP)(t) = t = s.PUSH(d1 ).PUSH(d2 ) % t=ε Condition ̺(PUSH(d))(t) = length(t) < size % length(t) = size Condition ̺(TOP:d)(t) = %

d = nil

Clusters ε s.PUSH(d1 ) ε

s

Equivalence t.PUSH(d) t

Trace Patterns t = s.PUSH(d) t=ε

Equivalence t ε

Dictionary size : the size of the stack length(t) : the length of the trace t Figure 7: Enhanced Trace Assertion Specification for Drunk Stack Module

25

Syntax of Access Programs Visible Name POP PUSH TOP

Abstract Name

Argument

Value

integer integer push1

integer

Call-Response Forms POP:nil PUSH(d):nil TOP:d push1(d)

Canonical Step-traces canonical(t) ⇔ t = [push1(di )]ni=1 ∧ 0 ≤ n ≤ size t0 = ε Trace Assertions Trace Patterns t = push1(d) ̺(POP)(t) = t = s.push1(d1 ).push1(d2 ) % t=ε Condition length(t) < size − 1 ̺(PUSH(d))(t) = length(t) = size − 1 % lenght(t) = size Condition ̺(TOP:d)(t) = %

d = nil

Clusters ε s.push1(d1 ) ε

s

Cluster t.push1(d).push1(d) t.push1(d) t.push1(d) t

Trace Patterns t = s.PUSH(d) t=ε

Equivalence t ε

Dictionary size : the size of the stack length(t) : the length of the trace t Figure 8: Enhanced Trace Assertion Specification for Very Drunk Stack Module

26

From the Trace Assertion section in Figure 7 we get the following transition relation ̺DS for Drunk Stack: ̺DS (POP)(t, t′ )

⇔ ∃d, d1 , d2 , s . (t = PUSH(d) ∧ t′ = ε)∨ (t = s.PUSH(d1 ).PUSH(d2 ) ∧ (t′ = s.PUSH(d1 ) ∨ t′ = s)∨ (t = ε ∧ t′ = ε) ⇔ ∃d, d1 , d2 . t = t′ .PUSH(d) ∨ t = t′ .PUSH(d1 ).PUSH(d2 )∨, (t = ε ∧ t′ = ε) ′ ̺DS (PUSH(d))(t, t ) ⇔ (length(t) < size ∧ t′ = t.PUSH(d)) ∨ (length(t) = size ∧ t′ = t), ̺DS (TOP:d)(t, t′ ) ⇔ ∃s . (t = s.PUSH(d) ∧ t′ = t) ∨ (d = nil ∧ t = ε ∧ t′ = ε).

We transform ̺DS into ̺˜DS : ̺˜DS (POP, t)(nil, t′ ) ⇔ ̺DS (POP)(t, t′ ), ̺˜DS (PUSH(d), t)(nil, t′ ) ⇔ ̺DS (PUSH(d))(t, t′ ), ̺˜DS (TOP, t)(d, t′ ) ⇔ ̺DS (TOP:d)(t, t′ ). For showing simulation between Drunk Stack and Stack, we have to find a relation R between the canonical traces of Drunk Stack and those of Stack. Let subseq(x, y) be a relation between sequences x and y which holds if elements of x occur in the same order in y, i.e. subseq is the smallest relation such that for any sequences x, y and element a: subseq(ε, ε)

and

subseq(x, y) ⇒ subseq(x.a, y.a)

and

subseq(x, y) ⇒ subseq(x, y.a).

Intuitively, the canonical traces of Drunk Stack correspond to those of Stack with some PUSH(d) elements interspersed. Hence we define: R(t, t′ ) ⇔ subseq(t′ , t). The first condition for Stack to simulate Drunk Stack using R is that the initial trace t0 = ε of Drunk Stack and t0 = ε of Stack are in relation R, which holds trivially. The second condition is ̺˜DS ⊑Id×R ̺˜S , which is defined as: ̺˜DS (a, t) 6= ∅ ⇒ ((Id × R) ◦ ̺˜S )(a, t) ⊆ (˜ ̺DS ◦ (Id × R))(a, t) ∧ ((Id × R) ◦ ̺˜S )(a, t) 6= ∅, where a are all the calls of Drunk Stack and Stack, and t ranges over all canonical trances of Drunk Stack. We consider the cases a = POP, a = PUSH(d), and a = TOP separately. For a = POP, we have that ̺˜DS (POP, t) 6= ∅ for any canonical trace t and similarly ̺˜s (POP, t) 6= ∅. As R is a total relation, it is easy to see that ((Id × R) ◦ ̺˜S )(POP, t) 6= ∅ for any canonical trace t. Hence above condition simplifies in this case to: ((Id × R) ◦ ̺˜S )(POP, t) ⊆ (˜ ̺DS ◦ (Id × R))(POP, t), which is equivalent to: (∃t′ . subseq(t′ , t) ∧ (∃d . t′ = t′′ .PUSH(d) ∨ (t′ = ε ∧ t′′ = ε))) ⇒ (∃t′ . (∃d, d1 , d2 . t = t′ .PUSH(d) ∨ t = t′ .PUSH(d1 ).PUSH(d2 ) ∨ (t = ε ∧ t′ = ε)) ∧subseq(t′′ , t′ )), 27

for all t′′ . This holds according to the rules of logic and above definition of subseq. The cases a = PUSH(d) and a = TOP follow similarly. In total, this establishes Drunk Stack ⊑R Stack, which according to Proposition 5.1 implies Drunk Stack ≤ Stack. Trace assertion specifications like Stack can be further refined into modules with a “more concrete” state space. For example, a Stack implementation could use an array A and integer N, related to the canonical traces of Stack by R(t, A, N) ⇔ N = length(t) ∧ t = A[1..N], where A[1..N] selects the subsequence of A with the first N elements. Such refinement steps can be carried out in a standard way, e.g. [10, 21]. However, this establishes a link between abstract trace assertion specifications and efficient implementations.

15

Multi-Object Modules

In practical applications, it is not unusual that a module is designed to implement several independent homogeneous objects. For example in some applications, one may need to design a (multi-object) stack module that implements two or, in general, any number of stacks, plus for instance the stack concatenation operation. The module may be self-initializing, i.e. the first use of PUSH(stack name, i) creates a stack stack name or may require object generator like new(stack name). A natural way of modeling such modules is to define the global states as sets of states of individual modules, with the empty set as the initial state. We already know how to specify individual states (by canonical step-traces) and relationships between them (by trace assertions). Note that the sets can be specified by sequences, the sequence “{a, b, c}” specifies the set consisting of the elements a, b, c. This convention is used for years and is easy to understand3 . We need only an apparatus to make the states of individual objects distinct, to transform global states by both global calls (like concatenate, which affects more than one individual state, or new, which create a new local state), and local calls (like PUSH, which affects only one local state). The states of individual objects may be made distinct by adding individual labels to them. For instance {stack1 7→ PUSH(3).PUSH(5), stack2 7→ PUSH(3).PUSH(1).PUSH(8), stack3 7→ ε} may represent a global state consisting of three stacks stack1, stack2, stack3, where the local state of stack1 is PUSH(3).PUSH(4), the local state of stack2 is PUSH(3).PUSH(1).PUSH(8) and stack3 is empty. The stack1, stack2 and stack3 are unique labels attached to appropriate canonical step-traces, creating labeled step-traces. This lead us to the concept of uniquely labeled sets.

15.1 Uniquely Labeled Sets Let X be a set and L be a set of labels. A subset X of L×X is a labeled set. We shall write α 7→ x ∈ L×X instead of (α, x) ∈ L × X. If L = {1, 2, 3}, X = {a, b} then {1 7→ a, 1 7→ b, 2 7→ a} is an example of a labeled set. A set X ⊆ L × X is uniquely labeled if for all α ∈ L and for all x, y ∈ X, (α 7→ x ∈ X ∧ α 7→ y ∈ X ) ⇔ x = y. 3

In a sense {a, b, c} and ha.b.ci describe the same object, only the interpretation is different, see Section 7.

28

X is uniquely labeled if every element of it has an unambiguous label. For example {1 7→ a, 2 7→ a} is uniquely labeled, while {1 7→ a, 1 7→ b, 2 7→ a} is not. The family of all uniquely labeled sets over L × X is denoted by U(L, X). Note that ∅ is uniquely labeled, and for every X ∈ U(L, X), |X | ≤ |L|. In particular we are interested in the family U(L, h∆∗ i), where ∆ is an alphabet. For every uniquely labeled set X ⊆ L × X, let L(X ) be the set of all its labels, L(X ) = {α ∈ L | ∃x ∈ X. α 7→ x ∈ X }. For every α ∈ L and every X ∈ U(L, X), let X |α = x and X k α = α 7→ x if α 7→ x ∈ X for some x, and undefined otherwise. For instance if X = {1 7→ a, 2 7→ b} then X |1 = a and X k 1 = 1 7→ a, while X |3 and X k 3 are undefined. The operator | is called “projection” and k is called “selection”. Note that X k α = α 7→ X |α, if X |α is defined. For every two uniquely labeled sets X , Y, we define the operation ←֓ and ⊕ as follows: X ←֓ Y = X \ {α 7→ x ∈ X | α ∈ L(Y)} ∪ Y, X ⊕ Y = X ∪ Y \ {α 7→ x | x ∈ X ∧ α ∈ L(X ) ∩ L(Y)}. Note that X ←֓ Y, X ⊕ Y are always uniquely labeled and X ⊕ Y = Y ⊕ X , but it may happen that X ←֓ Y 6= Y ←֓ X . The operation ←֓ replaces elements of X by the elements of Y with the same labels. If L(X ) ⊆ L(Y) then X ←֓ Y = Y. If L(X ) ∩ L(Y) = ∅ then X ←֓ Y = X ∪ Y = X ⊕ Y. For instance {1 7→ a, 2 7→ b} ←֓ {1 7→ b} = {1 7→ b, 2 7→ b}, and {1 7→ a, 2 7→ b}⊕{1 7→ b} = {2 7→ b}. The operator ←֓ is called the labeled replacement, the operator ⊕ is an auxiliary operator that is used to define concatenation and weak concatenation for the elements of U(L, h∆∗ i). The elements of U(L, h∆∗ i) are called uniquely labeled sets of step-sequences, and the elements of L × h∆∗ i labeled step-sequences. In particular ∅ and α 7→ ε are labeled step-sequences. For every τ1 , τ2 ∈ U(L, h∆∗ i) we define concatenation “.” and weak concatenation “⌣” by: τ1 .τ2 = {α 7→ t1 .t2 | α 7→ t1 ∈ τ1 ∧ α 7→ t2 ∈ τ2 } ∪ (τ1 ⊕ τ2 ) τ1 ⌣ τ2 = {α 7→ t1 ⌣ t2 | α 7→ t1 ∈ τ1 ∧ α 7→ t2 ∈ τ2 } ∪ (τ1 ⊕ τ2 ) Clearly τ1 .τ2 and τ1 ⌣ τ2 are elements of U(L, h∆∗ i). For instance if τ1 = {1 7→ ε, 2 7→ a.hb.ai, 3 7→ a.a} and τ2 = {1 7→ a.b, 2 7→ hc.di}, then we have τ1 .τ2 = {1 7→ a.b, 2 7→ a.hb.ai.hc.di, 3 7→ a.a}, τ1 ⌣ τ2 = {1 7→ a.b, 2 7→ a.hb.a.c.di, 3 7→ a.a}. We also extend the operator ∈, for any α ∈ L, τ ∈ U(L, h∆∗ i), a ∈ ∆ by: α ∈ τ ⇔ ∃t ∈ h∆∗ i . α 7→ t ∈ τ, a ∈ τ ⇔ ∃α ∈ L. ∃t ∈ h∆∗ i . α 7→ t ∈ τ ∧ a ∈ t. For instance, 2 ∈ {1 7→ a.a, 2 7→ a.hb.ai}, but 3 ∈ / {1 7→ a.a, 2 7→ a.hb.ai}, b ∈ {1 7→ a.a, 2 7→ a.hb.ai}, but c ∈ / {1 7→ a.a, 2 7→ a.hb.ai}.

29

15.2 Multi-Objects Trace Assertion Specifications Let TA = (∆E , C, ̺, t0 ) be a trace assertion specification, and let L be a set of labels. By a free multiobject trace assertion specification generated by TA and L, we mean a tuple: LTA = (L × ∆E , U(L, C), ̺L , ∅) where: L × ∆E is the set of labeled call-responses, U(L, C) is the uniquely labeled set of canonical step-traces, ̺L : L × ∆E → U(L, C) ↔ U(L, C) is the transition relation defined for all τ ∈ U(L, C) and α 7→ a:d ∈ L×∆ by: ̺L (α 7→ a:d)(τ ) =

(

{ τ ←֓ {α 7→ t} | t ∈ ̺(a:d)(τ |α) } if ̺(a:d)(τ |α) = 6 ∅ ∅ if ̺(a:d)(τ |α) = ∅

The above definition assumes self-initialization of modules, i.e. the first call initializes a given object in the module. Without self-initialization, the user must initialize an object before the call relating to this object. The pair (L, TA) describes LTA completely, since LTA is entirely specified by the specification TA and the description of L. For instance L = the set of all available names, and TA from Figure 4 (without enhancement) describe completely the self-initializing multi-stack module. We may easily derive that in such a case (we specify normal behavior only so far): ̺L (st1 7→ PUSH(3))(∅) = {st1 7→ PUSH(3)}, while ̺L (st1 7→ POP)(∅) = ∅. If τ = {st1 7→ PUSH(3).PUSH(1), st2 7→ ε, st3 7→ PUSH(5)}, then we have ̺L (st1 7→ POP)(τ ) = {st1 7→ PUSH(3), st2 7→ ε, st3 7→ PUSH(5)}, while ̺L (st2 7→ POP)(τ ) = ∅. In the sequel, except in the theory part, we shall prefer to write PUSH(st1, 3). PUSH(st1, 1) instead of st1 7→ PUSH(3).PUSH(1). The normal behavior described by LTA is given by: L(LTA) = {x | x ∈ (L×∆)∗ ∧ ̺∗L (x)(∅) 6= ∅}, where ̺∗L is the standard extension of ̺L onto (L×∆)∗ → U(L, C) ↔ U(L, C) (see Section 6.1). The empty trace, ε, always does belong to L(LTA) since, by the definition, ̺∗L (ε)(∅) = {∅} = 6 ∅. For the self-initializing multi-stack trace assertion specification LTA we have x = PUSH(st1, 1). PUSH(st2, 3).POP(st1) ∈ L(LTA) since ̺∗L (x)(∅) = {st1 7→ ε, PUSH(st2, 3)} = 6 ∅, while x.POP(st1) ∈ / ∗ L(LTA) since ̺L (x.POP(st1))(∅) = ∅. For a given LTA, let ̺∅L denote the following transition relation, for all τ ∈ U(L, C) and α 7→ a:d ∈ L×∆: ( δL (α 7→ a:d)(τ ) if α ∈ τ ̺∅L (α 7→ a:d)(τ ) = ∅ if α ∈ /τ A multi-object trace assertion specification is a tuple: MTA = (LTA, ∆Eglob , ̺glob ) where LTA as above, ∆Eglob is the set of global call-response events, δglob the global transition relation, such that for some Σglob : S i input(Eglob ) = ∞ i=0 (Σglob ×L ), ̺glob ∈ ∆glob → U(L, C) ↔ U(L, C). 30

We write a(α1 , . . . , αk ) ∈ ∆glob , and a(α1 , . . . , αk ) : d ∈ ∆glob , rather than (a, α1 , . . . , αk ), and (a, α1 , . . . , αk , d). For instance, we write new(α) instead of (new, α) and concatenate(α1 , α2 , α3 ) instead of (concatenate, α1 , α2 , α3 ). For every MTA we define the transition relation ̺ˆ ∈ ((L × ∆) ∪ ∆glob ) → U(L, C) ↔ U(L, C), where for all τ ∈ U(L, C) and p ∈ (L×∆) ∪ ∆glob :    ̺L (p)(τ )

if p ∈ L×∆ ∧ new ∈ / Σglob ̺ˆ(p)(τ ) = if p ∈ L×∆ ∧ new ∈ Σglob  ̺ (p)(τ ) if p ∈ ∆ glob glob ̺∅ (p)(τ )  L

To create a new instance of an object, we use the access program call new ∈ Σglob × L, defined by: ̺glob (new(α))(τ ) =

(

{ τ ←֓ {α 7→ t0 } } if α ∈ τ ∅ if α ∈ /τ

In other words ̺ˆ = ̺L ∪ ̺glob if new ∈ / Σglob , and ̺ˆ = ̺∅L ∪ ̺glob otherwise. The global program new(α) is usually accompanied by a program delete(α) ∈ Σglob × L, which deletes the instance of TA with label α: ̺glob (delete(α))(τ ) =

(

{ τ \ {τ k α} } if α ∈ τ ∅ if α ∈ /τ

The normal behavior generated by MTA is defined by: L(MTA) = {x | x ∈ ((L×∆) ∪ ∆glob )∗ ∧ ̺ˆ∗ (x)(∅) 6= ∅}. For instance ε always does belong to L(MTA) since ̺ˆ∗ (ε)(∅) = {∅} 6= ∅, if new, delete ∈ Σglob , then new(α1 ).new(α2 ).delete(α2 ) ∈ L(MTA) since ̺ˆ∗ (new(α1 ).new(α2 ).delete(α2 ))(∅) = {α1 7→ t0 } = 6 ∅, and ̺ˆ∗ (new(α1 ).new(α2 ).delete(α2 ).delete(α1 ))(∅) = {∅} = 6 ∅, while new(α1 ).delete(α2 ) ∈ / L(MTA) since ̺ˆ∗ (new(α1 ).delete(α2 ))(∅) = ∅. MTA is self-initialized if new ∈ / Σglob , output independent if TA is output independent, deterministic if TA is deterministic and |δglob (τ, p)| ≤ 1. The concepts of enhancement, full specification, Mealy form and state constructors can easily be introduced for multi-objects modules. The counterparts of Lemma 10.1, Proposition 10.2 and Proposition 12.1 also do hold for multi-objects trace specifications. Figure 9 represents a full self-initialized multi-object trace assertion specification for the Cross module that was introduced and analyzed in [14]. The specification of the Cross module caused some problems when the older convention and techniques were used [14]. The Cross specification is non-deterministic and output independent. The module implements up to two sets, labeled by either a or b, each set may contain 0, 1, both 0 and 1 or is empty. There are two local operations INSERT, which inserts an element into a given set, TEST which tests if an element is in a given set, and one global operation CROSS which takes two sets and divides non-deterministically their union into two disjoint sets. The module is self-initializing, the first INSERT creates a set. Figure 10 represents the full multi-objects trace assertion specification of Multi-Stack with new, concatenate, and delete as global program calls. The specification is deterministic, output-independent and non self-initializing.

31

Labels L = {a, b} Syntax of Access Programs Name Type Argument INSERT local 0 or 1 TEST local 0 or 1 CROSS global

Value Boolean

Call-Response Forms INSERT(∗, i):nil TEST(∗, i):d CROSS:nil

Code i

Local Canonical Step-traces (Coded) canonical(t) ⇔ (t = ε ∨ t = i ∨ t = hi1 .i2 i) ∧ i, i1 , i2 ∈ {0, 1} ∧ i1 6= i2 t0 = ε Local Trace Assertions Equivalence t⌣i Condition ̺(TEST(∗, i):d)(t) = i ∈ t ∧ d = true i∈ / t ∧ d = false ̺(INSERT(∗, i))(t) =

Equivalence t t

Global Canonical Step-traces (Redundant) global canonical(τ ) ⇔ τ = ∅ ∨ ((τ = {a 7→ t} ∨ τ = {b 7→ t}) ∧ canonical(t)) ∨(τ = {a 7→ t1 , b 7→ t2 } ∧ canonical(t1 ) ∧ canonical(t2 )) τ0 = ∅ Global Trace Assertions ̺(CROSS)(τ ˆ )= Condition 0 ∈ τ ∧ 1 ∈ τ ∧ |τ | = 2 0∈τ ∧1∈ / τ ∧ |τ | = 2 1∈τ ∧0∈ / τ ∧ |τ | = 2 τ = {a 7→ ε, b 7→ ε} % |τ | < 2

Clusters {a 7→ h0.1i, b 7→ ε} {a 7→ 0, b 7→ 1} {a 7→ 1, b 7→ 0} {a 7→ ε, b 7→ h0.1i} {a 7→ ε, b 7→ 0} {a 7→ 0, b 7→ ε} {a 7→ ε, b 7→ 1} {a 7→ 1, b 7→ ε} τ τ

Extended Local Trace Assertions (Redundant, except for enhancements) Equivalence ̺(INSERT(α, ˆ i))(τ ) = τ ⌣ {α 7→ i}

̺(TEST(α, ˆ i):d)(τ ) =

Condition α 7→ i ∈ τ ∧ d = true α ∈ τ ∧ α 7→ i ∈ / τ ∧ d = false % α∈ /τ

Equivalence τ τ τ

Figure 9: Enhanced Trace Assertion Specification for (a self-initializing) Cross Module

32

Labels L =available names Syntax of Access Programs Name Type Argument POP local PUSH local integer TOP local new global label concatenate global 3 × label delete global label

Value

integer

Call-Response Forms POP(∗):nil PUSH(∗, i):nil TOP(∗):i new(∗) concatenate(∗, ∗, ∗) delete(∗)

Codes i

Local Canonical Step-traces canonical(t) ⇔ t = [di ]ni=1 ∧ 0 ≤ n ≤ size t0 = ε Local Trace Assertions Trace Patterns Equivalence ̺(POP(∗))(t) = t = s.d s % t=ε ε Condition Equivalence ̺(PUSH(∗, d))(t) = length(t) < size t.d % length(t) = size t Condition Trace Patterns Equivalence ̺(TOP(∗):d)(t) = t = s.d t % d = nil t=ε ε Global Canonical Traces (Redundant) Vi canonical(ti ) ∧ (αj = αl ⇔ j = l) global canonical(τ ) ⇔ τ = {αi 7→ ti }ki=1 ∧ ti=1 τ0 = ∅ Global Trace Assertions Condition ̺ˆ(new(α))(τ ) = α∈ /τ % α∈τ

Equivalence τ ←֓ {α 7→ ε} τ

Condition ̺ˆ(concatenate(α1 , α2 , α3 )(τ ) = α1 ∈ τ ∧ α2 ∈ τ ∧ α3 ∈ τ % α1 ∈ / τ ∨ α2 ∈ / τ ∧ α3 ∈ /τ Condition Equivalence ̺ˆ(delete(α))(τ ) = α∈τ τ \ {τ k α} % α∈ /τ τ

Equivalence τ ←֓ {α3 7→ τ |α1 .τ |α2 } τ

Dictionary size : the size of the stack, length(t) : the length of the trace t Figure 10: Enhanced Trace Assertion Specification for Multiple Stack Module. Extended Local Trace Assertions are omitted as redundant. 33

16

Trace Assertion Method and Algebraic Specification

There are strong similarities between the trace assertion method and the algebraic specification method (see [7, 34]) for specifying abstract data types. Examples of similarities are: 1. Syntax parts of trace assertion specifications correspond to signatures in algebraic specification. 2. For output-independent trace assertion specifications, trace assertions correspond to conditional equations, 3. Canonical traces corresponds canonical terms (see [7]). 4. State constructors (as introduced in the paper to solve the problem that it is not always possible to represent the possible states uniquely by sequences of call-response pairs of visible functions) correspond to auxiliary/hidden functions in algebraic specification. However, there are major differences. The main difference is that 1. Algebraic specification supports implicit equations while trace assertion method uses explicit equations only. The functions PUSH, POP, and TOP operating on non-empty stack may abstractly be implicitly defined as [7]: POP(PUSH(s, a)) = s TOP(PUSH(s, a)) = a Less abstract, with states of the stack represented as sequences and “.” denoting concatenation, explicit definition of the same part of stack is the following (also see [7]): PUSH(s, a) = s.a POP(s.a) = s TOP(s.a) = a df

In the second, explicit, case we may replace “=” by “=”, but in the first, implicit, case we cannot. The trace assertion specification is a straight abstraction of the second case. The implicit definitions might sometimes be shorter, they are usually more abstract. However, typically explicit definitions are considered to be more readable and easier to understand. The stack is well-known and easy to understand module, but even here some students have encountered initial problems to understand that the implicit equations really define the stack, while the explicit equations are practically self-explaining. For more complex modules, as for example parts of protocols [5, 12], parts of software for aircraft control [31], or intra-processor, inter-process communication via mailboxes [32] both defining and understanding implicit equations might be difficult (how simple would equational definitions of the Unique Integer or the Cross module look like?). The second difference is 2. The underlying models for algebraic specification are abstract algebras [4], while the underlying model for trace assertion method are automata. While as we mentioned before there is similarity between automata and algebras, they are different models. The other differences: 34

3. To specify in trace assertion specifications that a function does not change the state, it is necessary to explicitly write trace assertions expressing this, while in algebraic specification it is possible already in the signature to express this so that there is no need for equations. 4. Trace assertion specifications provide syntactic facilities which makes it possible in certain cases to specify a function by a single trace assertion, where the use of auxiliary/hidden functions (e.g. in the definition of a stack with overflow [3]) or recursive definitions (e.g. in the definition of the dequeue function for a queue [7]) are necessary in algebraic specifications. 5. State constructors that correspond to auxiliary/hidden functions are used only to handle heavy non-determinism, while the use of auxiliary/hidden functions is much wider in algebraic specifications. Suppose for instance that PUSH additionally returns the value that is pushed on the top of the stack. The trace assertion specification requires only small adjustments, in Figure 5 we need to replace PUSH(d):nil by PUSH(d) : d in the last column of the Syntax of Access Programs, t = [PUSH(di )]ni=1 by t = [PUSH(di ):di ]ni=1 in the Canonical Step-traces definition (or nothing if codes are used as in Figure 11), and ̺(PUSH(d))(t) =

Condition length(t) < size % length(t) = size

Equivalence t.PUSH(d) t

by: Condition ̺(PUSH(d):d)(t) = length(t) < size % length(t) = size

Equivalence t.PUSH(d):d t

Similar minor adjustments are needed for the Mealy form of Figure 6. The algebraic specification requires the use of an auxiliary/hidden function push, and may look like: POP(push(s, a)) = s TOP(push(s, a)) = a PUSH(s, a) = (push(s, a), a) where PUSH : Stack × integer → Stack × integer. The descriptive power of trace assertion specification and algebraic specification is the same. Every trace assertion specification can be transformed into an equivalent canonical terms algebra ([7, 34]), and for every algebraic specification, a trace assertion specification equivalent to the canonical terms algebra of the given algebraic specification can be constructed. The constructions in the general case are formally complex and tedious, even so the intuitions seem to be clear. We will show how such transformations may look like in some special cases. Those transformations will also emphasize similarities and differences. A trace assertion specification TA = (∆E , C, ̺, t0 ) is total if it defines a transition for all calls, i.e. κ(t, a) = 1 for all t ∈ C and a ∈ ΣE . Let TA = (∆E , C, ̺, t0 ) be a deterministic and total trace assertion specification. We define the many-sorted algebra ATA = (SortC , SortO , Sort1 , . . . , SortkTA ; Op) 35

where SortC = C, SortO = OE , Sort1 , . . . , SortkTA are the domains of the arguments of the procedures (function calls) from NΣ , and Op = {ˆf | f ∈ E} ∪ {˜f | f ∈ E}, where ˆf and ˜f are the functions defined as follows: ̺(f (d1 , ...., dr ) : d)(t) = {s} ⇐⇒

(

ˆf (t, d1 , ..., dr ) = s ˜f (t, d1 , ..., dr ) = d

For TA representing the Stack module (Figure 4) the above transformation result in the following twosorted function algebra ATA = (SortC , SortO , Sort1 ; Op) with SortC = C = {[PUSH(di )]ni=1 | 0 ≤ n ≤ size ∩ di ∈ integer}, SortO = OE = integer ∪ {nil}, d PUSH, g POP, d POP, g TOP, d TOP}, g d : SortC × Sort1 → SortC , Sort1 = integer, Op = {PUSH, PUSH g d g d : SortC → SortC , PUSH : SortC × Sort1 → SortO , POP : SortC → SortC , POP : SortC → SortO , TOP g : SortC → SortO , and for every t ∈ C, and every integer i: TOP d PUSH(t, i) = t.PUSH(i) if lenght(t) < size, and, d PUSH(t, i) =t if lenght(t) = size, g PUSH(t, i) = nil d d POP(t.PUSH(i)) = t and POP(ǫ) = ǫ, g POP(t) = nil d TOP(t) =t g g TOP(t.PUSH(i)) =i and TOP(ǫ) = nil,

We say that the “tilde” function ˜f is trivial if range(˜f ) = {nil}, and that the “hat” function ˆf is trivial if g POP g and TOP d are trivial. ˆf (t, d1 , . . . , dr ) = t for every t. For the Stack example, the functions PUSH, Let Amodified be the algebra derived from ATA by eliminating all trivial functions. We will consider TA modified ATA as an algebraic equivalent of the deterministic trace assertion specification TA.

For non-deterministic trace assertion specifications we proceed in a similar manner, but instead of standard many-sorted algebras we have to use for instance partial algebras (see [34] chapter 3.3.5). Consider a many-sorted algebra A = (S0 , S1 , . . . , Sk ; Op). We say that a sort Si is domestic if for every f ∈ Op, Si is a component of the domain of f , and there exists at least one g ∈ Op such that Si is the range of g. The sort is called foreign if it is not domestic. For instance for the two sorted algebra that defines stack of integers, the sort stack is domestic and the sort integers is foreign. Intuitively, the domestic sort is defined by the algebra, and all foreign sorts are predefined by other means. An element of so ∈ S0 is called a generator of S0 (see [34]), if every element of S0 can be derived from s0 by applying a sequence of operators (functions) from Op. To transform an algebraic specification in a trace assertion specification we have to resolve following main problems: 1. all implicit equations must be replaced by explicit ones, 36

2. only one domestic sort is allowed, 3. there exists a generator s0 of S0 . It appears that many algebraic specifications can be transformed into the form described above, however the result is usually less general. Let A = (S0 , S1 , . . . , Sk ; Op) be a many-sorted algebra with explicit equations, one domestic domain S0 , and suppose s0 is a generator of S0 . Without loss of generality we may assume that for every f ∈ Op, the domain of f is of the form S0 × Si1 × . . . × Sif , i.e. the value of the first argument of f belongs to S0 , and that S0 is the set of canonical terms [7, 34] (so in the case of stack, instead of h1, 2i ∈ S0 , we have PUSH(PUSH(stack, 1), 2) ∈ S0 ). Define E, the set of access program names as E = {f | f ∈ Op}. For all f ∈ E, we defined input(f ) and output(f ) to be the smallest sets such that: (v1 , ..., vk ) ∈ input(f ) ∧ nil ∈ output(f ) ⇐⇒ ∃t, s ∈ S0 . f (t, v1 , ..., vr ) = s (v1 , ..., vk ) ∈ input(f ) ∧ d ∈ output(f ) ⇐⇒ ∃t ∈ S0 . f (t, v1 , ..., vk ) = d. Let Θ be the following mapping that transforms S0 into a set of traces over ∆E : Θ(s0 ) = ǫ and for every f (v0 , v1 , . . . , vk ) ∈ S0 , Θ(f (v0 , v1 , . . . , vk ) = Θ(v0 ).f (v1 , . . . , vk ):nil. Now define TA = (∆E , Θ(S0 ), ̺, ǫ) where:

̺(f (v1 , . . . , vk ):nil)(s) = {t} ⇐⇒ f (s, v1 , . . . , vk ) = t ̺(f (v1 , . . . , vk ):d)(s) = {s} ⇐⇒ f (s, v1 , . . . , vk ) = d.

and ̺(a:d)(s) = ∅ for all other cases. We shall consider TA as a trace assertion specification that is equivalent to the algebra A. We believe the areas of applications for the algebraic specifications are different than for the trace assertion method. The algebraic specification is better suited for defining abstract data types in programming languages (as SML, LARCH, etc., see [34]). The trace assertion method is better suited for specifying complex interface modules as for instance communication protocols [5, 12, 31, 32]. The division follows from the general pattern of applicability of automata based and algebraic models. One may model integers as an automaton (it is usually defined as an algebra), or may define the semantics of SCR specification [11] as an abstract algebra (it is defined as a kind of automaton), however in both cases the advantage as such way of modeling is hardly seen.

17

Final Comment

An automata-based model for the trace assertion method has been presented and its formal consistency has been proven. A modified specification format based on this model has also been proposed. The main points of the model are the following: • the alphabet which represent observable event occurrences is built from call-response events, 37

• the structure of the trace assertion specification is entirely described on the bases of normal behavior only, • the refinement relation captures the externally observable behavior of module specifications, • trace assertion specifications can be refined into “more deterministic” and “more total” trace assertion specifications or into module specifications with some “more concrete” state space, using a simulation relation, • exceptional behavior is specified separately as an enhancement of normal behavior, and such an enhancement may be added to the trace assertion specification, leading a behavioral refinement, • canonical step-traces (instead of canonical traces) are used to specify states for single-object modules, and sets of canonical step-traces are used to specify states for multi-object modules. Sequence notation is used to specify both step-traces and sets of step traces, • Mealy forms are special cases of a more general yet simpler model, • multi-object modules are specified using the concept of uniquely labeled sets of step-traces. Neither the monitored events [11, 27, 33] nor non-sequential modules are considered in this paper. For non-sequential models a possible delay between a call and its response must be modeled, so “trueconcurrency” models should rather be used [18]. We have shown that the output value functions are redundant. The theory does not need them, and we believe they usually make specifications less readable. We have found the standard forms shorter and more readable than the Mealy forms. For the output dependent trace specifications, the explicit output functions seem to be useless at all. The specification of multi-object modules is not much different than single-object modules. The trace assertion method and algebraic specification can be seen as complimentary approaches. They have some things in common, but substantial differences as well. The main difference is the use of implicit equations in algebraic specifications, and explicit equations only in trace assertions. Their areas of applications seem to be different.

Acknowledgment We would like to thank D. Parnas and A. J. van Schouwen for many useful comments and discussions. We gratefully acknowledge three anonymous referees, whose comments significantly contributed to the final version of this paper.

References [1] J.-R. Abrial. The B Book: Assigning Programs to Meaning. Cambridge University Press, 1996. [2] A. Arnold. Finite Transition Systems. Prentice Hall, 1994. [3] W. Bartussek and D.L. Parnas. Using assertions about traces to write abstract specifications for software modules. In G. Bracchi and P. C. Lockemann, editors, 2nd Conf. on European Cooperation in Informatics on Information Systems Methodology, Lecture Notes in Computer Science 65, pages 211–236, Venice, Italy, 1978. Springer-Verlag. 38

[4] P. M. Cohen. Universal Algebra. D. Reidel, 1981. [5] F. Courtois and D. L. Parnas. Formally specifying a communication protocol using the trace assertion method. Technical Report CRL Report No. 269, McMaster University, Hamilton, Ontario, Canada, 1993. [6] V. Diekert and G. Rozenberg, editors. The Book of Traces. World Scientific, Singapore, 1995. [7] H. Ehrig and B. Mahr. Fundamentals of Algebraic Specification 1: Equations and Initial Semantics. EATCS Monographs in Theoretical Computer Science. Springer-Verlag, Berlin ; New York, 1985. [8] S. Eilenberg. Automata, Languages and Machines, volume A. Academic Press, New York, 1974. [9] R. Fr¨aisse. Theory of Relations. North Holland, 1986. [10] Jifeng He, C. A. R. Hoare, and J. W. Sanders. Data refinement refined. In B. Robinet and R. Wilhelm, editors, European Symposium on Programming, Lecture Notes in Computer Science 213. Springer-Verlag, 1986. [11] C. L. Heitmeyer, R. D. Jeffords, and B. G. Labaw. Automated consistency checking of requirements specification. ACM Transactions on Software Engineering and Methodology, 5(3):231–261, 1996. [12] D. M. Hoffman. The trace specification of communication protocols. IEEE Transactions on Computers, 34(12):1102–1113, 1985. [13] J.E. Hopcroft and J.D. Ullman. Introduction to Automata Theory, Languages and Computations. Addison-Wesley, Reading, MA, 1979. [14] M. Iglewski, M. Kubica, and J. Madey. Trace specification of non-deterministic multi-object modules. Technical Report 95-05(205), Institute of Informatics, Warsaw University, Warsaw, Poland, 1995. [15] M. Iglewski, J. Madey, and K. Stencel. On fundamentals of the trace assertion method. Technical Report RR 94/09-6, Universit´e du Qu´ebec a` Hull, Hull, Canada, 1994. [16] M. Iglewski, J. Mincer-Daszkiewicz, and J. Stencel. Some experiences with specification of nondeterministic modules. Technical Report RR 94/09-7, Universit´e du Qu´ebec a` Hull, Hull, Canada, 1994. [17] R. Janicki. Towards a formal semantics of parnas tables. In 17th International Conference on Software Engineering, pages 231–240, Seattle, Washington, USA, 1995. ACM Press. [18] R. Janicki and M. Koutny. Structure of concurrency. Theoretical Computer Science, 112(1):5–52, 1993. [19] R. Janicki, D. L. Parnas, and J. Zucker. Tabular representations in relational documents. In C. Brink and G. Schmidt, editors, Relational Methods in Computer Science. Springer-Verlag, 1997.

39

[20] N. Lynch and M. Tuttle. An introduction to input/output automata. CWI Quarterly, 2(3):219–246, 1989. [21] N. Lynch and F. Vaandrager. Forward and backward simulations: I. untimed systems. Information and Computation, 121(2):214–233, 1995. [22] J. McLean. A formal foundations for the abstract specification of software. Journal of the ACM, 31(3):600–627, 1984. [23] H. D. Mills. Stepwise refinement and verification in box-structure systems. Computer, pages 23–26, 1988. [24] T. Norvell. On trace specifications. CRL Report 305, McMaster University, Hamilton, Canada, 1995. [25] D. Parnas. A technique for software module specification with examples. Communications of the ACM, 15(5):330–336, 1972. [26] D. Parnas, J. Madey, and M. Iglewski. Precise documentation of well-structured programs. IEEE Transactions on Software Engineering, 20(12):948–976, 1994. [27] D. Parnas and Y. Wang. The trace assertion method of module interface specification. Technical Report 89–261, CIS, Queen’s University, 1989. [28] S. J. Prowell. Sequence-Based Software Specification. Ph.D. Thesis, University of Tennessee, 1996. [29] G. Rozenberg and R. Varraedt. Subset languages of Petri nets. Theoretical Computer Science, 26:301–323, 1983. [30] G. Schmidt and T. Str¨ohlein. Relations and Graphs: Discrete Mathematics for Computer Scientists. EATCS Monographs in Theoretical Computer Science. Springer-Verlag, Berlin ; New York, 1993. [31] A. J. van Schouwen. The A-7 requirements model: Re-examination for real time systems and an application to monitoring systems. Technical Report 90–276, Queen’s University, 1990. [32] A. J. van Schouwen. On the road to practical module interface specification. In Lecture presented at McMaster Workshop on Tools for Tabular Notations, McMaster University, Hamilton, Ontario, Canada, 1996. [33] Y. Wang. Specifying and Simulating the Externally Observable Behaviour of Modules. Ph.D. Thesis, McMaster University, 1994. also CRL Report 292, TRIO. [34] M. Wirsing. Algebraic specification. In J. van Leeuwen, editor, Handbook of Theoretical Computer Science, volume 2, pages 675–788. Elsevier Science, 1990.

40

Biographies Ryszard Janicki is a Professor at the Department of Computing and Software, McMaster University, Hamilton, Canada. He received the M.Sc. degree in Applied Mathematics from the Warsaw University of Technology, Poland in 1975, and the Ph.D. and Habilitation in Computer Science from the Polish Academy of Sciences, Warsaw, Poland in 1977 and 1981 respectively. He taught computer science and mathematics at the Warsaw University of Technology, Poland in 1975-1984, Aalborg University, Denmark in 1984-86, before joining McMaster in 1986. He was a Visiting Scholar at University of Newcastle upon Tyne, U.K., in 1982 and a Visiting Professor at Bordeaux University, France, in 199495. He published more than 80 papers and co-authored a monograph. His research interests include concurrency theory, fundamentals of software engineering, ranking theory, and relational methods in computer science. Emil Sekerinski is Assistant Professor at the Department of Computing and Software, McMaster University, Hamilton, Canada. He studied computer science in Stuttgart and Karlsruhe, Germany, and received the diploma in computer science and the doctoral degree from the University of Karlsruhe, in 1989 ˚ and 1994, respectively. He had positions at Abo Akademi, Turku, Finland, 1995-1997 before joining McMaster in 1997. He published more than 20 papers and co-edited a book. His research interests include mathematical specification and development techniques, object orientation, concurrent and reactive systems, programming languages and programming tools.

41