The Specification Logic maxHML¶
Overview¶
maxHML enables us to express properties on the executions of programs. Like sHML, maxHML is a another syntactic subset of the more expressive Hennessy-Milner Logic with recursion. It is used to express safety properties, but by contrast to the sHML fragment of The Specification Logic sHML, maxHML is interpreted over executions (called traces), rather than process execution graphs. maxHML formulae are generated from the following grammar:
φ, ψ ∈ maxHML ::= ff | tt (1)
| <α>φ (2)
| [α]φ (3)
| φ or ψ (4)
| φ and ψ (5)
| X (6)
| max(X. φ) (7)
-
Formulae
ffandttdenote falsity and truth respectively, -
<α>φis the existential modal operator guarding the continuation formulaφ(also called a possibility), -
[α]φ, the dual of<α>φ, is the universal modal operator guarding the continuation formulaφ(also called a necessity), -
φ or ψis a disjunction, -
φ and ψis a conjunction, -
Xis a logical variable, and, -
max(X. φ)is the greatest fixed point construct that specifies recursion via the logical variableXand binds the free occurrences ofXin the sub-formulaφ.
To handle reasoning over program event data, the modal operator is equipped with symbolic actions α of the form P when C, where P is an event pattern and C, a decidable Boolean constraint.
Patterns correspond to events that the program under analysis exhibits.
These patterns contain data variables that are instantiated with values learnt at runtime from matched events.
Pattern variables bind the free variables in constraints C, and this binding scope extends to the continuation formula φ.
Symbolic action patterns follow the pattern-matching syntax of Erlang and Elixir, where atoms are matched directly, and the ‘don’t care’ pattern _ matches any data value.
We say that a trace satisfies the formula <P when C>φ when it exhibits an event that matches pattern P, fulfils the constraint C, and the ensuing program behaviour also satisfies φ.
Dually, a trace satisfies [P when C]φ whenever it exhibits an event that matches pattern P, fulfils the constraint C, and the ensuing program behaviour then satisfies φ.
When the constraint is true, the expression when C may be omitted for readability.
Pattern and constraint expressions¶
detectEr supports five event patterns describing the lifecycle of processes.
A fork action is exhibited by a process when it spawns a new child process; its dual, init, is exhibited by the corresponding child upon initialisation.
Process exit actions signal termination, while send and recv describe process interaction.
| Program event | Event pattern | Pattern variable | Description |
|---|---|---|---|
fork |
P₁ → P₂, Mod:Fun(Args) |
P₁ |
PID of the parent process spawning P₂ |
P₂ |
PID of the child process spawned by P₁ |
||
Mod:Fun(Args) |
Function signature consisting of the module, function and arguments | ||
init |
P₁ ← P₂, Mod:Fun(Args) |
P₁ |
PID of the parent process spawning P₂ |
P₂ |
PID of the child process spawned by P₁ |
||
Mod:Fun(Args) |
Function signature consisting of the module, function and arguments | ||
exit |
P₁ * Reason |
P₁ |
PID of the terminated process |
Reason |
Termination reason | ||
send |
P₁ : P₂ ! Msg |
P₁ |
PID of the process issuing the message |
P₂ |
PID of the recipient process | ||
Msg |
Message payload | ||
recv |
P₂ ? Msg |
P₂ |
PID of the recipient process |
Msg |
Message payload |
The variables P₁ and P₂ in event patterns must be a port ID or PID, whereas Reason and Msg may be any Erlang data type, i.e., one of atom, Boolean, integer, float, string, bit string, reference, fun, port ID, PID, tuple, map, and list.
Mod and Fun must be atoms, and Args, an arbitrary list comprised of the aforementioned data types.
Pattern matching
Our current detectEr syntax does not yet implement full Erlang pattern matching, including $, map, record and bit string expressions; these will be added in future releases of the tool.
Note that these data values can still be used in patterns, so long as the pattern matching expression does not unwrap the individual data components of these values.
For instance, the pattern Var = Map is acceptable whereas #{K := V} = Map is not; similarly, Var = List may be used but not [$d, $a | _] = List.
Constraint definitions on pattern variables used by detectEr correspond to Erlang guard sequences consisting of guard expressions. The set of valid Erlang guards supported by detectEr are the following:
- Variables.
- Values, i.e., atom, Boolean, integer, float, string, bit string, reference, fun, port ID, PID, tuple, map, and list.
-
Expressions constructing atoms, integer, floats, lists, and tuples.
-
Term comparisons.
Operator Description ==Equal to /=Not equal to =<Less than or equal to <Less than >=Greater than or equal to >Greater than =:=Exactly equal to =/=Exactly not equal to -
Arithmetic expressions.
Operator Description Argument Type +Unary addition Number -Unary subtraction Number +Addition Number -Subtraction Number *Multiplication Number /Floating point division Number bnotUnary bitwise NOT Integer divInteger division Integer remInteger remainder of X/Y Integer bandBitwise AND Integer borBitwise OR Integer bxorArithmetic bitwise XOR Integer bslArithmetic bit shift left Integer bsrBit shift right Integer -
Boolean expressions.
Operator Description notUnary logical NOT andLogical AND orLogical OR xorLogical XOR -
Short-circuit expressions
andalso,orelse.
A simple example¶
Let us try to specify a safety requirement on the traces of our token server program.
The maxHML formula with symbolic action Prnt ← Srv, token_server:loop([OwnTok, _]) when OwnTok =/= 1 describes the property requiring that “the server does not start with an identifier token other than 1”.
[Prnt ← Srv, token_server:loop([Tok]) when Tok =/= 1]ff
Recall that the universal modality states that, for any program event satisfying the symbolic action P when C in [P when C]φ, the rest of the trace must then satisfy the continuation formula φ.
However, no trace continuation can satisfy the formula ff!
This means that the [Prnt ← Srv, token_server:loop([OwnTok, _]) when OwnTok =/= 1]ff can only be satisfied when our trace does not exhibit the event described by the symbolic action Prnt ← Srv, token_server:loop([OwnTok, _]) when OwnTok =/= 1.
Suppose our server with PID <0.10.0> exhibits the init event <0.16.0> ← <0.10.0>, token_server:loop([-1, 0]) when launched by a parent process with PID <0.16.0>.
It matches pattern Prnt ← Srv, token_server:loop([OwnTok, _]) when OwnTok =/= 1, instantiating the variables Srv = <0.10.0>, Prnt = <0.16.0>, and OwnTok = -1.
The constraint when OwnTok =/= 1 is also satisfied, leading to a violation, i.e., ff.
For a different init event <0.16.0> ← <0.10.0>, token_server:loop([1, 0]), the symbolic action in the modality [Prnt ← Srv, token_server:loop([OwnTok, _]) when OwnTok =/= 1] is not satisfied, and consequently, [Prnt ← Srv, token_server:loop([OwnTok, _]) when OwnTok =/= 1]ff is satisfied.
Analogously, the init event <0.16.0> ← <0.10.0>, calc_server:loop() satisfies the formula since the pattern Prnt ← Srv, token_server:loop([Tok]) fails to match the event shape.
Cheat sheet
The formula <α>tt means that it is possible for the trace to exhibit the symbolic action α. Dually, [α]ff states that the trace must not exhibit the symbolic action α.
You should try expressing the safety properties P1, P2, and P3 stated informally in the Getting Started section in terms of maxHML. Keep in mind that now, our formal model is the set of traces generated by the program, rather than the program transition graph itself.
We next learn how runtime monitors can be synthesised from maxHML.