1
Using Model Checking to Validate Style-Specific Architectural
Refactoring Patterns
Zoë StephensonJohn McDermid
2
Overview
• Motivation• Description of HADES• Underlying semantics• Refactoring• Translation for model checking• Analysis of refactoring patterns• Conclusions / further work
3
Motivation
• Researching into families of dependable control systems– Families are related applications that exhibit small, well-defined
variations
• Control systems traditionally defined through control diagrams– Simple hierarchical structure, based on physical concepts– Not specifically tailored for software
• “How do we get control diagrams that are suitable for software families?”– Use software architecture techniques
4
Software Architecture
• To help with product families– Interface defined by data items communicated
• Same paradigm as control law diagrams• Component boundary is individual data areas
– Composition by matching interface data areas
• To help with dependability– Static hierarchy of packages and components
• Also provides containment of elements that vary
– Low-level component can communicate outside of high-level interfaces
• e.g. to communicate fault information appropriately
• Need to be able to guarantee that communication is possible when components are combined
5
Software Architecture
• Looked at several architectural styles– Wright, Darwin, HRT-HOOD, Mascot/RTN...
• Looked at several other ideas– UML, Simulink
• No existing style matched well enough to have the type of composition needed and also show whether the architecture works when composed
• Would need to make modifications– No confidence that the type of composition does allow for a
guarantee that the architecture works• Invented new, simple ADL to experiment with ideas
– Results provide confidence needed to apply ideas to existing, mature ADL
– Everything must be understandable by the control engineers too
6
HADES
• Hierarchical Architecture for Dependable Embedded Systems
• Based on ideas from HRT-HOOD and MASCOT/RTN– Network of activities and data areas– Cyclic behaviour
• Collect inputs, process, produce outputs, loop
• Adapted to include new interface concept– Interfaces defined in terms of communication with data areas– Low-level interfaces can operate outside of communications
defined at higher level
7
HADESP
I: Signal
O: Signal
D: Pool
A
B
Integer
Integer
Integer
Activity
Data Area
Communication
Initiation
8
Signal Protocol
– Single value– May initiate up to all but one of its connections
• Outgoing connections triggered by incoming connections
– Data value is used immediately rather than stored– Represents procedure call or function call
communication
D: SignalA B D: SignalA B
9
Pool Protocol
– Single value– May not initiate connections
• Separate incoming connections for reading and writing
– Most recently written data value is retained and transmitted when requested
– Represents single shared variable communication
D: PoolA B
10
Semantics
• Communications define:– Data flow direction– Initiation responsibility (source or sink)– Termination (immediate or blocking)
• To model arbitrary networks of activities and data areas, we use transition-constrained finite state machines– One machine per activity and data area– Constraints to represent the communication structure
• Constraints also represent parent-child relationships
– View of semantics that is familiar to engineers
11
State Machine Semantics
I: Signal
A
Integer
W
WIn
Rx
Str
Ret
Tx
WRel
W
WIn
CIn
Proc
Susp
Cout
WRel
Q;N:requested
I:connected_in
S:supplied
C:cached
O:connected_out
D:delivered
R;M:releasedQ;N:requested
I:connected_in
S:supplied
C:computed
O:connected_out
D:delivered
R;M:released
13
D1: Signal
B
Integer
W
WIn
Rx
Str
Ret
Tx
WRel
W
WIn
CIn
Proc
Susp
Cout
WRel
Q;N:requested
I:connected_in
S:supplied
C:cached
O:connected_out
D:delivered
Q;N:requested
I:connected_in
S:supplied
C:computed
O:connected_out
D:delivered
A
W
WIn
CIn
Proc
Susp
Cout
WRel
D2: Signal
Integer
W
WIn
Rx
Str
Ret
Tx
WRel
P
W
WIn
CIn
Proc
Susp
Cout
WRel
W
Q;N:requested
I:connected_in
S:supplied
C:cached
O:connected_out
D:delivered
R; M: released
W
R; M: released
W
R; M: released
Q;N:requested
I:connected_in
S:supplied
C:computed
O:connected_out
D:delivered
W
R; M: released
W
R; M: released
Q;N:requested
I:connected_in
S:supplied
C:cached
O:connected_out
D:delivered
D1: Signal
B
Integer
W
WIn
Rx
Str
Ret
Tx
WRel
W
WIn
CIn
Proc
Susp
Cout
WRel
Q;N:requested
I:connected_in
S:supplied
C:cached
O:connected_out
D:delivered
Q;N:requested
I:connected_in
S:supplied
C:computed
O:connected_out
D:delivered
A
W
WIn
CIn
Proc
Susp
Cout
WRel
D2: Signal
Integer
W
WIn
Rx
Str
Ret
Tx
WRel
P
W
WIn
CIn
Proc
Susp
Cout
WRel
W
Q;N:requested
I:connected_in
S:supplied
C:cached
O:connected_out
D:delivered
R; M: released
W
R; M: released
W
R; M: released
Q;N:requested
I:connected_in
S:supplied
C:computed
O:connected_out
D:delivered
W
R; M: released
W
R; M: released
Q;N:requested
I:connected_in
S:supplied
C:cached
O:connected_out
D:delivered
State Machine View
Constraint 1
Constraint 2
14
Solutions
• More complex semantics– Allow outputs at different times– Reduces claim to ‘simplicity’
• Fewer constraints– Reduces usefulness of synchronisation model
• Change the architecture, preserve intent– What change needs to be made?
• Analysis of the whole model will only show whether a problem exists, not necessarily where or how to fix it
• We choose to change the architecture
16
D1: Signal
B
Integer
W
WIn
Rx
Str
Ret
Tx
WRel
W
WIn
CIn
Proc
Susp
Cout
WRel
Q;N:requested
I:connected_in
S:supplied
C:cached
O:connected_out
D:delivered
Q;N:requested
I:connected_in
S:supplied
C:computed
O:connected_out
D:delivered
A2
W
WIn
CIn
Proc
Susp
Cout
WRel
D2: Signal
Integer
W
WIn
Rx
Str
Ret
Tx
WRel
P
W
WIn
CIn
Proc
Susp
Cout
WRel
W
Q;N:requested
I:connected_in
S:supplied
C:cached
O:connected_out
D:delivered
R; M: released
W
R; M: released
W
R; M: released
Q;N:requested
I:connected_in
S:supplied
C:computed
O:connected_out
D:delivered
W
R; M: released
W
R; M: released
Q;N:requested
I:connected_in
S:supplied
C:cached
O:connected_out
D:delivered
A1
W
WIn
CIn
Proc
Susp
Cout
WRel
Q;N:requested
I:connected_in
S:supplied
C:computed
O:connected_out
D:delivered
DInt: Signal
Integer
W
WIn
Rx
Str
Ret
Tx
WRel
W
R; M: released
Q;N:requested
I:connected_in
S:supplied
C:computed
O:connected_out
D:delivered
W
R; M: released
D1: Signal
B
Integer
W
WIn
Rx
Str
Ret
Tx
WRel
W
WIn
CIn
Proc
Susp
Cout
WRel
Q;N:requested
I:connected_in
S:supplied
C:cached
O:connected_out
D:delivered
Q;N:requested
I:connected_in
S:supplied
C:computed
O:connected_out
D:delivered
A2
W
WIn
CIn
Proc
Susp
Cout
WRel
D2: Signal
Integer
W
WIn
Rx
Str
Ret
Tx
WRel
P
W
WIn
CIn
Proc
Susp
Cout
WRel
W
Q;N:requested
I:connected_in
S:supplied
C:cached
O:connected_out
D:delivered
R; M: released
W
R; M: released
W
R; M: released
Q;N:requested
I:connected_in
S:supplied
C:computed
O:connected_out
D:delivered
W
R; M: released
W
R; M: released
Q;N:requested
I:connected_in
S:supplied
C:cached
O:connected_out
D:delivered
A1
W
WIn
CIn
Proc
Susp
Cout
WRel
Q;N:requested
I:connected_in
S:supplied
C:computed
O:connected_out
D:delivered
DInt: Signal
Integer
W
WIn
Rx
Str
Ret
Tx
WRel
W
R; M: released
Q;N:requested
I:connected_in
S:supplied
C:computed
O:connected_out
D:delivered
W
R; M: released
Altered State Machine View
17
Refactoring Patterns
• Refactoring:– Preserve intended behaviour– Eliminate unintended consequences
• Four refactoring patterns identified– Function call / return– Submerge bidirectional interaction– Feedback loop push pool– Feedback loop pull pool
20
Validation
• Validation criteria:– Before refactoring, a problem exists– After refactoring, the problem is removed– After refactoring, the intended behaviour of the pre-
refactoring structure is preserved
• Initial validation performed by visualising structures in a domain-specific simulator– Especially useful for demonstrating intended
behaviour
• We used model checking to ensure coverage– Reused framework from the simulator tool
21
Model Checking StrategyDraw Tool Model
Translate
XML Model
Visualise
Validation
Translate
SPIN/NuSMV Model
Check
ValidationExisting tools
New tools
24
Example State Machine View
A1
W
WIn
CIn
Proc
Susp
Cout
WRel
W
InputData: Pool
W
WIn
Rx
Str
Ret
Tx
WRel
Q:requested
I:connected_in
S:supplied
N:requested
O:connected_out
D:delivered
W
M:releasedC:cachedR:released
Q;N:requested
I:connected_in
S:supplied
C:computed
O:connected_out
D:delivered
R; M: released
OutputData: Signal
W
WIn
Cx
Str
Ret
Tx
WRel
W
Q;N:requested
I:connected_in
S:supplied
C:cached
O:connected_out
D:delivered
R; M: released
25
Translation
• Assumption– Model is correct if each component’s “W” state is always
reachable via the other states
• Three steps– Represent state of each component (data area and activity)– Represent the rules for updating the state of each component– Compose the rules into a complete model
• Different underlying models in SPIN and NuSMV– Interleaved vs. synchronous– Impacts on the representation of rules for updating state
26
NuSMV Strategy
sharedstateVAR state for each componentSPEC at least one state change
component(shared)TRANS case constraint & state = X : next(state) = Y; 1 : next(state)=state;SPEC initial ‘W’ state always reachable
mainVAR instantiates shared stateand components
• Rules localised to components
27
NuSMV ExampleMODULE sharedstate
VAR
odsstate : { W, WIn, Rx, Str, Ret, Tx, WRel};
astate : { W, WIn, CIn, Proc, Susp, COut, WRel};
idpstate : { W, WIn, Rx, Str, Ret, Tx, WRel};
SPEC
AG (odsstate = W & astate = W & idpstate = W) -> AX ! (odsstate = W & astate = W & idpstate = W)
MODULE odscomponent(shared)
ASSIGN
init(shared.odsstate) := W;
TRANS
case
shared.odsstate = Ret : next(shared.odsstate) = Tx;
shared.astate = Proc & shared.odsstate = W & shared.odsstate = W : next(shared.odsstate) = WIn;
shared.odsstate = WRel & shared.odsstate = WRel : next(shared.odsstate) = W;
shared.odsstate = Str : next(shared.odsstate) = Ret;
shared.odsstate = WIn & shared.astate = Susp : next(shared.odsstate) = Rx;
shared.odsstate = WRel & shared.odsstate = WRel : next(shared.odsstate) = W;
shared.odsstate = Tx : next(shared.odsstate) = WRel;
shared.odsstate = Rx : next(shared.odsstate) = Str;
shared.astate = Proc & shared.odsstate = W & shared.odsstate = W : next(shared.odsstate) = WIn;
1 : next(shared.odsstate) = shared.odsstate;
esac;
SPEC
AG AF shared.odsstate = W
MODULE acomponent(shared)
ASSIGN
init(shared.astate) := W;
TRANS
case
shared.odsstate = WIn & shared.astate = Susp : next(shared.astate) = COut;
shared.astate = W & shared.idpstate = W & shared.astate = W : next(shared.astate) = WIn;
shared.astate = WRel & shared.astate = WRel : next(shared.astate) = W;
shared.astate = Proc & shared.odsstate = W & shared.odsstate = W : next(shared.astate) = Susp;
shared.astate = WIn & shared.idpstate = Ret : next(shared.astate) = CIn;
shared.astate = WRel & shared.astate = WRel : next(shared.astate) = W;
shared.astate = COut : next(shared.astate) = WRel;
shared.astate = CIn : next(shared.astate) = Proc;
shared.astate = W & shared.idpstate = W & shared.astate = W : next(shared.astate) = WIn;
1 : next(shared.astate) = shared.astate;
esac;
SPEC
AG AF shared.astate = W
MODULE idpcomponent(shared)
ASSIGN
init(shared.idpstate) := W;
TRANS
case
shared.astate = WIn & shared.idpstate = Ret : next(shared.idpstate) = Tx;
shared.astate = W & shared.idpstate = W & shared.astate = W : next(shared.idpstate) = Ret;
shared.idpstate = WRel : next(shared.idpstate) = W;
shared.idpstate = Str & shared.idpstate = Str : next(shared.idpstate) = W;
shared.idpstate = WIn : next(shared.idpstate) = Rx;
shared.idpstate = Str & shared.idpstate = Str : next(shared.idpstate) = W;
shared.idpstate = Tx : next(shared.idpstate) = WRel;
shared.idpstate = Rx : next(shared.idpstate) = Str;
shared.idpstate = W : next(shared.idpstate) = WIn;
1 : next(shared.idpstate) = shared.idpstate;
esac;
SPEC
AG AF shared.idpstate = W
MODULE main
VAR
shared: sharedstate;
theods: odscomponent(shared);
thea: acomponent(shared);
theidp: idpcomponent(shared);
MODULE sharedstate
VAR
odsstate : { W, WIn, Rx, Str, Ret, Tx, WRel};
astate : { W, WIn, CIn, Proc, Susp, COut, WRel};
idpstate : { W, WIn, Rx, Str, Ret, Tx, WRel};
SPEC
AG (odsstate = W & astate = W & idpstate = W) -> AX ! (odsstate = W & astate = W & idpstate = W)
MODULE odscomponent(shared)ASSIGN init(shared.odsstate) := W;TRANS case shared.odsstate = Ret : next(shared.odsstate) = Tx; shared.astate = Proc & shared.odsstate = W & shared.odsstate = W : next(shared.odsstate) = WIn; shared.odsstate = WRel & shared.odsstate = WRel : next(shared.odsstate) = W; shared.odsstate = Str : next(shared.odsstate) = Ret; shared.odsstate = WIn & shared.astate = Susp : next(shared.odsstate) = Rx; shared.odsstate = WRel & shared.odsstate = WRel : next(shared.odsstate) = W; shared.odsstate = Tx : next(shared.odsstate) = WRel; shared.odsstate = Rx : next(shared.odsstate) = Str; shared.astate = Proc & shared.odsstate = W & shared.odsstate = W : next(shared.odsstate) = WIn; 1 : next(shared.odsstate) = shared.odsstate; esac;SPEC AG AF shared.odsstate = W
MODULE mainVAR shared: sharedstate; theods: odscomponent(shared); thea: acomponent(shared); theidp: idpcomponent(shared);
28
NuSMV State Machine View
A1
Proc
OutputData: Signal
W
WIn
Q;N:requested
shared.astate = Proc & shared.odsstate = W & shared.odsstate = W :next(shared.odsstate) = WIn;
29
SPIN Strategy
global datamtype state for each component
constraintproctype constraint() { endconstraint: do :: atomic { if :: constraints -> update :: else -> skip fi } od}
initInstantiate constraints with run
• Rules localised to transition constraints
30
SPIN Example
mtype { W, WIn, CIn, Proc, Susp, COut, WRel, Rx, Str, Ret, Tx };
mtype odsstate = W;
mtype astate = W;
mtype idpstate = W;
proctype odsods_1() {
endodsods_1: do
:: atomic {
if
:: (odsstate == WRel) ->
odsstate = W;
printf("odsstate = W\n");
:: else -> skip
fi
}
od
}
proctype aodsods_1() {
endaodsods_1: do
:: atomic {
if
:: (odsstate == W && astate == Proc) ->
odsstate = WIn;
printf("odsstate = WIn\n");
astate = Susp;
printf("astate = Susp\n");
:: else -> skip
fi
}
od
}
proctype aidpa_1() {
endaidpa_1: do
:: atomic {
if
:: (astate == W && idpstate == W) ->
astate = WIn;
printf("astate = WIn\n");
idpstate = Ret;
printf("idpstate = Ret\n");
:: else -> skip
fi
}
od
}
proctype odsa_1() {
endodsa_1: do
:: atomic {
if
:: (odsstate == WIn && astate == Susp) ->
odsstate = Rx;
printf("odsstate = Rx\n");
astate = COut;
printf("astate = COut\n");
:: else -> skip
fi
}
od
}
proctype idp_1() {
endidp_1: do
:: atomic {
if
:: (idpstate == Rx) ->
idpstate = Str;
printf("idpstate = Str\n");
:: else -> skip
fi
}
od
}
proctype idpidp_1() {
endidpidp_1: do
:: atomic {
if
:: (idpstate == Str) ->
idpstate = W;
printf("idpstate = W\n");
:: else -> skip
fi
}
od
}
proctype a_1() {
enda_1: do
:: atomic {
if
:: (astate == COut) ->
astate = WRel;
printf("astate = WRel\n");
:: else -> skip
fi
}
od
}
proctype ods_1() {
endods_1: do
:: atomic {
if
:: (odsstate == Tx) ->
odsstate = WRel;
printf("odsstate = WRel\n");
:: else -> skip
fi
}
od
}
proctype a_2() {
enda_2: do
:: atomic {
if
:: (astate == CIn) ->
astate = Proc;
printf("astate = Proc\n");
:: else -> skip
fi
}
od
}
proctype idp_2() {
endidp_2: do
:: atomic {
if
:: (idpstate == W) ->
idpstate = WIn;
printf("idpstate = WIn\n");
:: else -> skip
fi
}
od
}
proctype ods_2() {
endods_2: do
:: atomic {
if
:: (odsstate == Str) ->
odsstate = Ret;
printf("odsstate = Ret\n");
:: else -> skip
fi
}
od
}
proctype aidp_1() {
endaidp_1: do
:: atomic {
if
:: (astate == WIn && idpstate == Ret) ->
astate = CIn;
printf("astate = CIn\n");
idpstate = Tx;
printf("idpstate = Tx\n");
:: else -> skip
fi
}
od
}
proctype idp_3() {
endidp_3: do
:: atomic {
if
:: (idpstate == Tx) ->
idpstate = WRel;
printf("idpstate = WRel\n");
:: else -> skip
fi
}
od
}
proctype ods_3() {
endods_3: do
:: atomic {
if
:: (odsstate == Ret) ->
odsstate = Tx;
printf("odsstate = Tx\n");
:: else -> skip
fi
}
od
}
proctype aa_1() {
endaa_1: do
:: atomic {
if
:: (astate == WRel) ->
astate = W;
printf("astate = W\n");
:: else -> skip
fi
}
od
}
proctype idp_4() {
endidp_4: do
:: atomic {
if
:: (idpstate == WIn) ->
idpstate = Rx;
printf("idpstate = Rx\n");
:: else -> skip
fi
}
od
}
proctype idp_5() {
endidp_5: do
:: atomic {
if
:: (idpstate == WRel) ->
idpstate = W;
printf("idpstate = W\n");
:: else -> skip
fi
}
od
}
proctype ods_4() {
endods_4: do
:: atomic {
if
:: (odsstate == Rx) ->
odsstate = Str;
printf("odsstate = Str\n");
:: else -> skip
fi
}
od
}
init {
printf("odsstate = W\n");
printf("astate = W\n");
printf("idpstate = W\n");
run odsods_1();
run aodsods_1();
run aidpa_1();
run odsa_1();
run idp_1();
run idpidp_1();
run a_1();
run ods_1();
run a_2();
run idp_2();
run ods_2();
run aidp_1();
run idp_3();
run ods_3();
run aa_1();
run idp_4();
run idp_5();
run ods_4();
}
mtype { W, WIn, CIn, Proc, Susp, COut, WRel, Rx, Str, Ret, Tx };mtype odsstate = W;mtype astate = W;mtype idpstate = W;proctype aodsods_1() { endaodsods_1: do :: atomic { if :: (odsstate == W && astate == Proc) -> odsstate = WIn; printf("odsstate = WIn\n"); astate = Susp; printf("astate = Susp\n"); :: else -> skip fi } od}
init { printf("odsstate = W\n"); printf("astate = W\n"); printf("idpstate = W\n"); run odsods_1(); run aodsods_1(); run aidpa_1(); run odsa_1(); run idp_1(); run idpidp_1(); run a_1(); run ods_1(); run a_2(); run idp_2(); run ods_2(); run aidp_1(); run idp_3(); run ods_3(); run aa_1(); run idp_4(); run idp_5(); run ods_4();}
31
SPIN State Machine ViewA1
Proc
OutputData: Signal
W
WIn
Q;N:requested
if:: (odsstate == W && astate == Proc) -> odsstate = WIn; printf("odsstate = WIn\n"); astate = Susp; printf("astate = Susp\n");:: else -> skipfi
Susp
C: Computed
32
Results
Model Expect Simulator NuSMV SPIN
Deadlock Livelock Deadlock Livelock Deadlock Livelock Deadlock Livelock
f13 Yes No Yes No Yes No Yes No
f14 No No No No No No No No
f18 Yes No Yes No Yes No Yes No
f19 No No No No No No No No
fpush Yes No Yes No Yes No Yes No
f17a No No Yes No Yes No Yes No
fpull Yes No Yes No Yes No Yes No
f17b No No Yes No Yes No Yes No
x1 Yes No No No No No No No
x2 No No No No No No No No
lraam No No No Yes No Yes - -
f28 No No Yes No Yes No Yes No
lraam-mod No No - - No No No No
Function call pattern came out entirely as expectedSubmerge pattern also came out as expectedFeedback push pattern needed asynchronous terminationFeedback pull pattern had the same problemWe created some further examples to increase confidence in the simulatorThis translation of an RTN structure for a missile launch controller could not complete when analysed in SPIN
The problem was with unconstrained pools on the edge of an architectural model, we introduced ‘source’ and ‘sink’ protocols to solve the problem
This is figure 28 from the original HADES manual, a thrust reverser controller – it also has the synchronous termination problem like the feedback loops
33
Unconstrained Pool Problem
• If only one side of a pool is attached, the machine can just loop round the unattached side
• Invented ‘source’ and ‘sink’ component types that are just one half of a pool
• Possible to convert one sink and one source into a pool when composing
A1
W
WIn
CIn
Proc
Susp
Cout
WRel
W
InputData: Pool
W
WIn
Rx
Str
Ret
Tx
WRel
Q:requested
I:connected_in
S:supplied
N:requested
O:connected_out
D:delivered
W
M:releasedC:cachedR:released
Q;N:requested
I:connected_in
S:supplied
C:computed
O:connected_out
D:delivered
R; M: released
34
Evaluation
• Simulator and model checker results consistent– Model checker identifies livelock and deadlock– Model checker known to exhaustively search for
problems– Simulator gives more immediate feedback to the user
in identifying what to change to fix the problem
• Simulator, SPIN and NuSMV are equally usable for fully-constrained models
• Demonstrates ability to reason about whether components operate correctly when composed
35
Conclusions
• Simple ADL just to check that ideas are sound– Refactoring patterns to match user intent to the
simple synchronisation model– Patterns needed validation
• Partial validation using a visualisation technique• Not necessarily exhaustive
• Validation completed with model checkers– Obtained consistent results– Choice of tool does not affect ability to obtain results
• Other criteria e.g. user experience, tool availability, tool integration would come into play instead
36
Further Work
• Apply ideas to existing ADL– AADL, UML, Simulink…
• Integrate model checkers into visualisation tool– Automatic invocation– Visualisation of results in terms of the architecture
• Examine real time / data storage issues– Bottlenecks– Blocking on queues
• Examine model-based design issues– Integration of previous work
• Activity specifications• Translation to programming language structures (packages)