| 1 | % (c) 2014-2019 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, | |
| 2 | % Heinrich Heine Universitaet Duesseldorf | |
| 3 | % This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) | |
| 4 | ||
| 5 | % A model checker for safety properties. | |
| 6 | ||
| 7 | % At this point the property intended to be checked has been recognized as a safety property. | |
| 8 | % As a consequence the negation of the property is represented by a finite automaton which | |
| 9 | % accepts all bad prefixes violating the safety property. The algorithm implemented in this module | |
| 10 | % traverses on-the-fly the state space and simulates at the same time movement through the automaton | |
| 11 | % in regard to the evaluation of the atomic propositions. The algorithm halts when either a finite | |
| 12 | % state of the automaton is visited (bad prefix for the property has been found and will be reported as a counter-example) | |
| 13 | % or when all nodes of the state space has been visited. | |
| 14 | ||
| 15 | :- module(safety_mc,[start_mc_safety_property/3]). | |
| 16 | ||
| 17 | %:- use_module(probsrc(bmachine), [b_parse_machine_predicate/2]). | |
| 18 | %:- use_module(probsrc(eclipse_interface),[test_boolean_expression_in_node/2]). | |
| 19 | :- use_module(probsrc(error_manager), [add_error_fail/3, add_warning/2, add_error_and_fail/2]). | |
| 20 | %:- use_module(probsrc(preferences),[set_preference/2, get_preference/2]). | |
| 21 | :- use_module(probsrc(specfile),[animation_mode/1]). | |
| 22 | :- use_module(probsrc(debug),[debug_println/2]). | |
| 23 | :- use_module(probsrc(tools),[cputime/1]). | |
| 24 | :- use_module(probsrc(bsyntaxtree),[is_truth/1]). | |
| 25 | ||
| 26 | %%% state space exploration modules | |
| 27 | :- use_module(probsrc(state_space),[visited_expression_id/1, | |
| 28 | transition/4, visited_expression/2, not_all_transitions_added/1, retract_open_node/1, | |
| 29 | set_context_state/1, clear_context_state/0]). | |
| 30 | %% :- use_module(probsrc(state_space_open_nodes), [retract_open_node_direct/1]). | |
| 31 | ||
| 32 | %%% ltl modules | |
| 33 | %:- use_module(probltlsrc(ltl),[init_states/1]). | |
| 34 | :- use_module(probltlsrc(ltl_safety),[aut_transition/3, aut_finite_state/1, aut_initial_state/1]). | |
| 35 | :- use_module(probltlsrc(state_space_explorer),[compute_transitions_opt/3,get_b_optimisation_options/2]). | |
| 36 | ||
| 37 | %%% library modules | |
| 38 | :- use_module(library(file_systems)). | |
| 39 | :- use_module(library(ordsets)). | |
| 40 | :- use_module(library(random)). | |
| 41 | :- use_module(library(lists)). | |
| 42 | ||
| 43 | :- use_module(probsrc(module_information),[module_info/2]). | |
| 44 | :- module_info(group,ltl). | |
| 45 | :- module_info(description,'This module provides predicates for automata-based model checking of safety properties.'). | |
| 46 | ||
| 47 | :- dynamic visited_pair/4, visited_pair_df/2, accepting_state/3. | |
| 48 | ||
| 49 | %% profiler modules | |
| 50 | %% :- use_module('../../extensions/profiler/profiler.pl'). | |
| 51 | %% :- use_module('../../extensions/profiler/profiler_te.pl'). | |
| 52 | ||
| 53 | %% :- enable_profiling(add_state_at_end/3). | |
| 54 | %% :- enable_profiling(pop_state_from_end/1). | |
| 55 | %% :- enable_profiling(check_for_aut_transition/4). | |
| 56 | %% :- enable_profiling(compute_all_product_transitions/4). | |
| 57 | ||
| 58 | % the automata-based safety model checker | |
| 59 | start_mc_safety_property(SearchMode,StartNodes,Result) :- | |
| 60 | prepare_mc_safety_property, | |
| 61 | animation_mode(MODE), | |
| 62 | get_b_optimisation_options(MODE,Optimizations), | |
| 63 | %% perform_static_analyses(MODE,Optimizations), | |
| 64 | initial_states_mc_safety_property(StartNodes,Optimizations,ProductInitStates), | |
| 65 | (ProductInitStates == [], non_empty_automaton | |
| 66 | -> % add_warning(run_mc_safety_property,'No initial states of the product transition system have been generated.'), | |
| 67 | Result = none, | |
| 68 | print('All open nodes visited! Nodes analysed: '), print(0), nl | |
| 69 | ; otherwise -> | |
| 70 | (\+ aut_finite_state('accept_all') | |
| 71 | -> add_warning(run_mc_safety_property,'No accept_all state has been found. The property is possibly not a safety property.') | |
| 72 | ; true), | |
| 73 | run_mc_safety_property(SearchMode,ProductInitStates,Optimizations,Result) | |
| 74 | ). | |
| 75 | ||
| 76 | run_mc_safety_property(breadth_first,ProductInitStates,Optimizations,Result) :- | |
| 77 | cputime(T1), | |
| 78 | run_mc_safety_property_bf(ProductInitStates,Optimizations,IntermediateResult), | |
| 79 | cputime(T2), | |
| 80 | print('% Overall Checking Time: '), D is T2-T1, print(D), print(' ms'),nl, | |
| 81 | cputime(T1_CE), | |
| 82 | get_bf_search_result(IntermediateResult,Result), | |
| 83 | cputime(T2_CE), | |
| 84 | print('% Time to get the result from model checking: '), D_CE is T2_CE-T1_CE, print(D_CE), print(' ms'),nl. | |
| 85 | run_mc_safety_property(random,ProductInitStates,Optimizations,Result) :- | |
| 86 | cputime(T1), | |
| 87 | (run_mc_safety_property_df(ProductInitStates,Optimizations,Result) -> | |
| 88 | cputime(T2), | |
| 89 | print('Counter-example has been found'),nl, | |
| 90 | print(counter_example(Result)),nl | |
| 91 | ; otherwise -> | |
| 92 | cputime(T2), | |
| 93 | get_state_space_statistics(TS,Product), | |
| 94 | print('All nodes visited. No counter-example has been found.'), nl, | |
| 95 | print('Number of states checked: '), print(Product), print(', where '), | |
| 96 | print('number of states of the transition system is: '), print(TS), nl, | |
| 97 | Result = none | |
| 98 | ), | |
| 99 | print('% Overall Checking Time: '), D is T2-T1, print(D), print(' ms'),nl. | |
| 100 | run_mc_safety_property(Otherwise,_ProductInitStates,_,_Result) :- | |
| 101 | add_error_fail(run_mc_safety_property,'Unknown search mode: ', Otherwise). | |
| 102 | ||
| 103 | /* The algorithm for performing breadth-first search LTL safety model checking */ | |
| 104 | run_mc_safety_property_bf(ProductInitStates,Optimizations,Result) :- | |
| 105 | maplist(create_and_add_init_state,ProductInitStates), | |
| 106 | Nr = 0, | |
| 107 | open_search(Nr,Optimizations,Result). | |
| 108 | ||
| 109 | open_search(Nr,Optimizations,Result) :- | |
| 110 | get_next_node(CurId,AutState,ActionId),!, | |
| 111 | (aut_finite_state_reached(AutState) -> % bad prefix has been found | |
| 112 | Result = counter_example_found, | |
| 113 | print(found_error(counter_example_found,CurId,AutState)),nl | |
| 114 | ; otherwise -> % continue the search | |
| 115 | Nr1 is Nr +1, | |
| 116 | compute_state_space_transitions_if_necessary(CurId,Optimizations,ActionsAndIDs), | |
| 117 | compute_all_product_transitions(CurId,AutState,ActionId,ActionsAndIDs), | |
| 118 | open_search(Nr1,Optimizations,Result) | |
| 119 | ). | |
| 120 | open_search(Nr,_Optimizations,Res) :- | |
| 121 | Res = none, | |
| 122 | print('All open nodes visited! Nodes analysed: '), print(Nr), nl. | |
| 123 | ||
| 124 | compute_state_space_transitions_if_necessary(CurId,Optimizations,ActionsAndIDs) :- | |
| 125 | compute_state_space_transitions_if_necessary_aux(CurId,Optimizations), | |
| 126 | findall((ActId,ActionAsTerm,DestId), | |
| 127 | transition(CurId,ActionAsTerm,ActId,DestId), | |
| 128 | ActionsAndIDs). | |
| 129 | ||
| 130 | compute_state_space_transitions_if_necessary_aux(CurId,Optimizations) :- | |
| 131 | (not_all_transitions_added(CurId),retract_open_node(CurId) | |
| 132 | -> % if node is still not retracted from queue | |
| 133 | set_context_state(CurId), | |
| 134 | visited_expression(CurId,CurState), | |
| 135 | compute_transitions_opt(Optimizations,CurId,CurState), | |
| 136 | clear_context_state | |
| 137 | ; otherwise -> | |
| 138 | true % nothing to do | |
| 139 | ). | |
| 140 | ||
| 141 | ||
| 142 | compute_all_product_transitions(CurId,AutState,ActionId,ActionsAndIDs) :- | |
| 143 | member((ActionId,_Term,DestId),ActionsAndIDs), | |
| 144 | check_for_aut_transition(AutState,DestId,NextAutState,NextActionId), | |
| 145 | (aut_finite_state_reached(NextAutState) -> | |
| 146 | create_and_add_state_to_queue(DestId,NextAutState,NextActionId,CurId,ActionId), | |
| 147 | assert(accepting_state(DestId,NextAutState,NextActionId)) % save accepting state (for building later the counter-example) | |
| 148 | ; otherwise -> | |
| 149 | \+ visited_pair(DestId,NextAutState,_,_), | |
| 150 | create_and_add_state_to_queue(DestId,NextAutState,NextActionId,CurId,ActionId), | |
| 151 | fail | |
| 152 | ). | |
| 153 | compute_all_product_transitions(_CurId,_AutState,_ActionId,_ActionsAndIDs). | |
| 154 | ||
| 155 | %%% getting the initial states with respect to the the safety property | |
| 156 | initial_states_mc_safety_property(StartNodes,Optimizations,ProductInitStatesSet) :- | |
| 157 | findall(Init,aut_initial_state(Init),InitStates), | |
| 158 | get_product_init_states(StartNodes,InitStates,Optimizations,ProductInitStates), | |
| 159 | list_to_ord_set(ProductInitStates,ProductInitStatesSet), | |
| 160 | debug_println(9,init_states(ProductInitStatesSet)). | |
| 161 | ||
| 162 | get_product_init_states([],_InitStates,_Optimizations,[]). | |
| 163 | get_product_init_states([StartNode|Nodes],InitStates,Optimizations,Res) :- | |
| 164 | (ltl_safety:tps_occurring -> compute_state_space_transitions_if_necessary(StartNode,Optimizations,_ActionsAndIDs);true), | |
| 165 | get_init_product(StartNode,InitStates,IStates), | |
| 166 | append(IStates,Res1,Res), | |
| 167 | get_product_init_states(Nodes,InitStates,Optimizations,Res1). | |
| 168 | ||
| 169 | get_init_product(_StartNode,[],[]). | |
| 170 | get_init_product(StartNode,[Init|Inits],Res) :- | |
| 171 | findall(Succ,get_successor_state(StartNode,Init,Succ),L), | |
| 172 | append(L,Res1,Res), | |
| 173 | get_init_product(StartNode,Inits,Res1). | |
| 174 | ||
| 175 | get_successor_state(StartNode,Init,(StartNode,Next,ActionId)) :- | |
| 176 | aut_transition(Init,Label,Next), | |
| 177 | check_transition(Label,StartNode,ActionId). | |
| 178 | %--------------------------------------------------------------------- | |
| 179 | ||
| 180 | %%% utility predicates | |
| 181 | prepare_mc_safety_property :- | |
| 182 | retractall(product(_,_,_)), | |
| 183 | retractall(visited_pair(_,_,_,_)), | |
| 184 | retractall(visited_pair_df(_,_)), | |
| 185 | retractall(accepting_state(_,_,_)). | |
| 186 | ||
| 187 | % automata relevant functions | |
| 188 | aut_finite_state_reached(AutState) :- | |
| 189 | aut_finite_state(AutState), | |
| 190 | AutState = 'accept_all',!. | |
| 191 | ||
| 192 | % Checking whether the automaton expresses the trivial property 'true'. | |
| 193 | % If so, a counter-example [inital_state] is returned | |
| 194 | trivial_automaton :- | |
| 195 | aut_transition(P,L,Q), | |
| 196 | (P=='accept_init',(L = ap(bpred(Pred))->is_truth(Pred);fail),Q =='accept_init'). | |
| 197 | ||
| 198 | non_empty_automaton :- | |
| 199 | aut_transition(_P,_L,_Q). | |
| 200 | ||
| 201 | % state space statistics | |
| 202 | get_state_space_statistics(TS,Product) :- | |
| 203 | findall((X,Y),visited_pair_df(X,Y),L), | |
| 204 | debug_println(9,product_states(L)), | |
| 205 | length(L,Product), | |
| 206 | findall(ID,visited_expression_id(ID),IDL), | |
| 207 | length(IDL,TS). | |
| 208 | %------------------------------------------- | |
| 209 | ||
| 210 | %%% Queue operations | |
| 211 | :- dynamic product/3. | |
| 212 | create_and_add_state_to_queue(CurId,AutState,NextActionId,PrevState,ActionId) :- | |
| 213 | assert(visited_pair(CurId,AutState,PrevState,ActionId)), | |
| 214 | add_state_at_end(CurId,AutState,NextActionId), | |
| 215 | debug_println(9,add_node_to_queue(product(CurId,AutState,NextActionId))). | |
| 216 | ||
| 217 | create_and_add_init_state((CurId,AutState,ActionId)) :- | |
| 218 | assert(visited_pair(CurId,AutState,root,none)), | |
| 219 | add_state_at_end(CurId,AutState,ActionId), | |
| 220 | (aut_finite_state_reached(AutState) -> assert(accepting_state(CurId,AutState,ActionId)); true), | |
| 221 | debug_println(9,add_node_to_queue(product(CurId,AutState,ActionId))). | |
| 222 | ||
| 223 | get_next_node(CurId,AutState,ActionId) :- | |
| 224 | pop_state_from_end(Node), | |
| 225 | Node = product(CurId,AutState,ActionId), | |
| 226 | debug_println(9,node_to_be_explored(Node)). | |
| 227 | ||
| 228 | add_state_at_end(State,AutState,ActionId) :- | |
| 229 | assertz(product(State,AutState,ActionId)). | |
| 230 | %add_state_at_front(State,AutState,ActionId) :- | |
| 231 | % asserta(product(State,AutState,ActionId)). | |
| 232 | pop_state_from_end(Node) :- | |
| 233 | retract(product(State,AutState,ActionId)),!, | |
| 234 | Node = product(State,AutState,ActionId). | |
| 235 | %------------------- | |
| 236 | ||
| 237 | /* An algorithm for checking safety LTL properties in depth-first manner. | |
| 238 | First tryouts showed that the depth-first search is much more effective than the breadth-first mode. */ | |
| 239 | ||
| 240 | run_mc_safety_property_df(InitStates,Optimizations,Result) :- | |
| 241 | member((CurId,AutState,ActionId),InitStates), | |
| 242 | (trivial_automaton -> % the automaton is trivial , i.e. we have checked the formula 'false' and need to return one of the initial states as a counter-example | |
| 243 | %% CEPath = [atom(CurId,CurId,none)], | |
| 244 | Result=model([atom(CurId,CurId,none)],no_loop) | |
| 245 | ; otherwise -> | |
| 246 | mc_safety_property_init((CurId,AutState,ActionId),Optimizations,CEPath,CEPath,Result) | |
| 247 | ). | |
| 248 | ||
| 249 | % at the init state we already checked for next automaton transitions | |
| 250 | mc_safety_property_init((CurId,AutState,ActionId),Optimizations,History,HTail,Result) :- | |
| 251 | (transition(CurId,_Op,ActionId,CurId) -> true ; assert(visited_pair_df(CurId,AutState))), | |
| 252 | mc_safety_property_next_state((CurId,AutState,ActionId),Optimizations,History,HTail,Result). | |
| 253 | ||
| 254 | % model_checker: get_open_node_to_check(DFMODE,NodeID) | |
| 255 | mc_safety_property((CurId,AutState,ActionId),Optimizations,History,HTail,Result) :- | |
| 256 | assert(visited_pair_df(CurId,AutState)), | |
| 257 | debug_println(9,current_history(CurId,History)), | |
| 258 | check_for_aut_transition(AutState,CurId,NextAutState,ActionId), | |
| 259 | mc_safety_property_next_state((CurId,NextAutState,ActionId),Optimizations,History,HTail,Result). | |
| 260 | ||
| 261 | mc_safety_property_next_state((CurId,AutState,ActionId),_Optimizations,History,[atom(CurId,CurId,ActionId1)],Result) :- | |
| 262 | aut_finite_state(AutState), | |
| 263 | AutState = 'accept_all',!, | |
| 264 | ( nonvar(ActionId) -> ActionId1=ActionId; ActionId1 = none), | |
| 265 | print(found_error(counter_example_found,CurId,History)),nl, | |
| 266 | Result = model(History,no_loop). | |
| 267 | mc_safety_property_next_state((CurId,AutState,ActionId),Optimizations,History,HTail,Result) :- | |
| 268 | compute_state_space_transitions_if_necessary(CurId,Optimizations,ActionsAndIDs), | |
| 269 | random_permutation(ActionsAndIDs,ActionsAndIDs1), | |
| 270 | HTail = [atom(CurId,CurId,ActionId)|HTail2], | |
| 271 | get_next_state(ActionsAndIDs1,ActionId,DestId), | |
| 272 | \+visited_pair_df(DestId,AutState), | |
| 273 | (ltl_safety:tps_occurring -> compute_state_space_transitions_if_necessary(DestId,Optimizations,_DstActionsAndIDs);true), | |
| 274 | mc_safety_property((DestId,AutState,_NewActionId),Optimizations,History,HTail2,Result). | |
| 275 | ||
| 276 | check_for_aut_transition(AutState,CurId,NextAutState,ActionId) :- | |
| 277 | get_randomised_next_aut_states(AutState,Trans), | |
| 278 | member((Label,NextAutState),Trans), | |
| 279 | check_transition(Label,CurId,ActionId). | |
| 280 | ||
| 281 | get_randomised_next_aut_states(AutState,Res) :- | |
| 282 | % here we want to test first the non-truth automata transitions in order to terminate earlier in case of a counter-example | |
| 283 | % this is a type of selective randomising (the idea is to execute as soon as possible transitions that do not have the label 'true') | |
| 284 | findall((Labl,Next),(aut_transition(AutState,Labl,Next),\+is_truth_label(Labl)),L), | |
| 285 | random_permutation(L,L1), | |
| 286 | findall((Labl,Next),(aut_transition(AutState,Labl,Next),is_truth_label(Labl)),LTruth), | |
| 287 | append(L1,LTruth,Res). | |
| 288 | ||
| 289 | get_next_state(ActionsAndIDs,ActionId,DstId) :- | |
| 290 | member((ActionId,_Term,DstId),ActionsAndIDs). | |
| 291 | ||
| 292 | %%% for getting the result from LTL Safety Model Check | |
| 293 | get_bf_search_result(none,Result) :- !, Result = none. | |
| 294 | get_bf_search_result(counter_example_found,Result) :- !, | |
| 295 | get_counter_example_from_product(Result), | |
| 296 | debug_println(9,counter_example(Result)). | |
| 297 | get_bf_search_result(Arg,_Result) :- | |
| 298 | add_error_fail(run_mc_safety_property_bf, 'Unknow result: ', Arg). | |
| 299 | ||
| 300 | get_counter_example_from_product(Result) :- | |
| 301 | accepting_state(State,AutState,ActionId),!, | |
| 302 | (nonvar(ActionId) -> Trans = ActionId; Trans = none), | |
| 303 | get_counter_example_list(State,AutState,[atom(State,State,Trans)],ResList), | |
| 304 | Result = model(ResList,no_loop). | |
| 305 | get_counter_example_from_product(_Result) :- | |
| 306 | add_error_and_fail(safety_mc,'No accepting state was found (this is an internal error in the model checker).'). | |
| 307 | ||
| 308 | get_counter_example_list(State,AutState,CurRes,Result) :- | |
| 309 | visited_pair(State,AutState,root,none),!, | |
| 310 | Result = CurRes. | |
| 311 | get_counter_example_list(State,AutState,CurRes,Result) :- | |
| 312 | visited_pair(State,AutState,PrevState,ActionId),!, | |
| 313 | transition(PrevState,_ActionAsTerm,ActionId,State), | |
| 314 | aut_transition(PrevAutState,_,AutState), | |
| 315 | get_counter_example_list(PrevState,PrevAutState,[atom(PrevState,PrevState,ActionId)|CurRes], Result). | |
| 316 | %------------------------------------------------------------------------------------------------------------------ | |
| 317 | ||
| 318 | is_truth_label(true). | |
| 319 | ||
| 320 | :- use_module(ltl_propositions,[check_enabled/2]). | |
| 321 | :- use_module(probltlsrc(ltl_propositions), [check_transition/4, check_ap/2]). | |
| 322 | %%% synchronising the automaton transitions with the successor states of the model | |
| 323 | check_transition(true,_StateId,_ActionId) :- !. | |
| 324 | check_transition(false,_StateId,_ActionId) :- !, fail. | |
| 325 | check_transition(ap(AP),StateId,_ActionId) :- !, | |
| 326 | check_ap_at_state(AP,StateId). | |
| 327 | check_transition(tp(TP),StateId,ActionId) :- !, | |
| 328 | ltl_propositions: check_enabled(TP,StateId), | |
| 329 | transition(StateId,Op,ActionId,DestId), | |
| 330 | ltl_propositions:check_transition(TP,Op,StateId,DestId). | |
| 331 | check_transition(not(AF),StateId,ActionId) :- !, | |
| 332 | check_transition_fail(AF,StateId,ActionId). | |
| 333 | check_transition(and(AF,AG),StateId,ActionId) :- !, | |
| 334 | check_transition(AF,StateId,ActionId), | |
| 335 | check_transition(AG,StateId,ActionId). | |
| 336 | check_transition(or(AF,AG),StateId,ActionId) :- !, | |
| 337 | (check_transition(AF,StateId,ActionId) ; | |
| 338 | check_transition(AG,StateId,ActionId)). | |
| 339 | check_transition(Label,_StateId,_ActionId) :- | |
| 340 | add_error_fail(check_for_aut_transition,'Unknown Buchi automaton transition label: ', Label). | |
| 341 | ||
| 342 | check_transition_fail(true,_StateId,_ActionId) :- !, fail. | |
| 343 | check_transition_fail(false,_StateId,_ActionId) :- !. | |
| 344 | check_transition_fail(ap(AP),StateId,_ActionId) :- !, | |
| 345 | (check_ap_at_state(AP,StateId)-> fail; true). | |
| 346 | check_transition_fail(tp(TP),StateId,ActionId) :- !, | |
| 347 | transition(StateId,Op,ActionId,DestId), | |
| 348 | \+ltl_propositions:check_transition(TP,Op,StateId,DestId). | |
| 349 | check_transition_fail(not(AF),StateId,ActionId) :- !, | |
| 350 | check_transition(AF,StateId,ActionId). | |
| 351 | check_transition_fail(and(AF,AG),StateId,ActionId) :- !, | |
| 352 | (check_transition_fail(AF,StateId,ActionId) ; | |
| 353 | check_transition_fail(AG,StateId,ActionId) ). | |
| 354 | check_transition_fail(or(AF,AG),StateId,ActionId) :- !, | |
| 355 | check_transition_fail(AF,StateId,ActionId), | |
| 356 | check_transition_fail(AG,StateId,ActionId). | |
| 357 | check_transition_fail(Label,_StateId,_ActionId) :- | |
| 358 | add_error_fail(check_for_aut_transition,'Unknown Buchi automaton transition label: ', Label). | |
| 359 | ||
| 360 | check_ap_at_state(deadlock,StateId) :- !, | |
| 361 | animation_mode(MODE), | |
| 362 | get_b_optimisation_options(MODE,Optimizations), | |
| 363 | compute_state_space_transitions_if_necessary_aux(StateId,Optimizations), | |
| 364 | \+transition(StateId,_ActionId,_ActionAsTerm,_DestId). | |
| 365 | check_ap_at_state(AP,StateId) :- | |
| 366 | ltl_propositions: check_ap(AP,StateId). | |
| 367 |