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
ff
andtt
denote 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, -
X
is a logical variable, and, -
max(X. φ)
is the greatest fixed point construct that specifies recursion via the logical variableX
and binds the free occurrences ofX
in 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 bnot
Unary bitwise NOT Integer div
Integer division Integer rem
Integer remainder of X/Y Integer band
Bitwise AND Integer bor
Bitwise OR Integer bxor
Arithmetic bitwise XOR Integer bsl
Arithmetic bit shift left Integer bsr
Bit shift right Integer -
Boolean expressions.
Operator Description not
Unary logical NOT and
Logical AND or
Logical OR xor
Logical 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.