1 | % (c) 2009-2024 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 | ||
6 | :- module(cbc_path_solver,[ | |
7 | create_testcase_path/5, | |
8 | create_testcase_path_nondet/4, | |
9 | ||
10 | testcase_path_timeout/9, | |
11 | testcase_path_timeout_catch/9, | |
12 | testcase_predicate_timeout/3, | |
13 | ||
14 | testcase_initialise/5, % not a very nice API call; used in sap, refactor | |
15 | testcase_set_up_events/9, % ditto | |
16 | add_constants_to_state_space/5, add_operations_to_state_space/5, % ditto | |
17 | remove_constants_from_state/4, | |
18 | verify_alloy_command/5 | |
19 | ]). | |
20 | ||
21 | :- use_module(probsrc(module_information),[module_info/2]). | |
22 | :- module_info(group,cbc). | |
23 | :- module_info(description,'Create paths for Event-B or Classical B via constraint solving'). | |
24 | ||
25 | ||
26 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
27 | % test case generation for a given list of event by | |
28 | % constraint solving | |
29 | ||
30 | :- use_module(probsrc(b_interpreter)). | |
31 | %:- use_module(b_interpreter_eventb). | |
32 | :- use_module(probsrc(kernel_waitflags)). | |
33 | :- use_module(probsrc(store)). | |
34 | :- use_module(probsrc(debug)). | |
35 | :- use_module(probsrc(bmachine)). | |
36 | :- use_module(probsrc(state_space)). | |
37 | :- use_module(probsrc(solver_interface)). | |
38 | :- use_module(probsrc(debug),[debug_format/3]). | |
39 | :- use_module(probsrc('cdclt_solver/cdclt_solver')). | |
40 | :- use_module(probsrc(bsyntaxtree),[conjunct_predicates/2]). | |
41 | :- use_module(probsrc('smt_solvers_interface/smt_solvers_interface')). | |
42 | :- use_module(probsrc(tools), [start_ms_timer/1,stop_ms_walltimer_with_msg/2]). | |
43 | :- use_module(probsrc(error_manager),[add_internal_error/2, enter_new_error_scope/2, exit_error_scope/3, | |
44 | add_error/3, add_message/3, | |
45 | critical_enumeration_warning_occured_in_error_scope/0]). | |
46 | :- use_module(probsrc(tools), [ajoin/2]). | |
47 | ||
48 | %create_testcase_path(Events,Timeout,Result) :- | |
49 | % create_testcase_path(init,Events,b(truth,pred,[]),Timeout,Result). | |
50 | %create_testcase_path(INIT,Events,Timeout,Result) :- % version with arity 4; not used | |
51 | % create_testcase_path(INIT,Events,b(truth,pred,[]),Timeout,Result). | |
52 | ||
53 | ||
54 | % use the constraint solver to create a path from an initial state (and valuation of the constants) | |
55 | % with the given list of events/operations and the provided target predicate | |
56 | % INIT is either | |
57 | % init: start from an initial state | |
58 | % invariant start from a state satisfying the invariant | |
59 | % typing start from a state without any constraint, apart from typing | |
60 | % typing(Pred) start from a state where Pred holds (and typing) | |
61 | % pred(P) start from invariant and predicate P | |
62 | ||
63 | create_testcase_path(INIT,Events,EndPredicate,Timeout,Result) :- | |
64 | % allow user interrupts during the search, just fail if that happens | |
65 | testcase_path_timeout(INIT,Timeout,Events,EndPredicate,ConstantsState,Operations,TestStates,TransInfos,R), | |
66 | ( R==ok -> | |
67 | add_constants_to_state_space(ConstantsState,TestStates,ConstTestStates,SetupSequence,StartStateId), | |
68 | add_operations_to_state_space(Operations,StartStateId,ConstTestStates,TransInfos,OpSequence), | |
69 | append(SetupSequence,OpSequence,Result) | |
70 | ; Result = R). | |
71 | ||
72 | % same as create_testcase_path: does not react to interrupts, no time-out, but can be backtracked ! | |
73 | create_testcase_path_nondet(INIT,Events,EndPredicate,Result) :- | |
74 | % allow user interrupts during the search, just fail if that happens | |
75 | testcase_path(INIT,Events,EndPredicate,ConstantsState,Operations,TestStates,TransInfos), | |
76 | add_constants_to_state_space(ConstantsState,TestStates,ConstTestStates,SetupSequence,StartStateId), | |
77 | add_operations_to_state_space(Operations,StartStateId,ConstTestStates,TransInfos,OpSequence), | |
78 | append(SetupSequence,OpSequence,Result). | |
79 | ||
80 | % ------------------------------- | |
81 | :- use_module(probsrc(clpfd_interface),[catch_clpfd_overflow_call3/3]). | |
82 | % copy of predicate in static_analysis to avoid meta_predicate error in Spider: | |
83 | :- meta_predicate catch_enumeration_warning_and_overflow(0,0). | |
84 | catch_enumeration_warning_and_overflow(Call,Handler) :- | |
85 | % throw/1 predicate raises instantiation_error | |
86 | catch_clpfd_overflow_call3( | |
87 | catch(Call, enumeration_warning(enumerating(_),_Type,_,_,critical), call(Handler)), | |
88 | message, % could also be silent | |
89 | call(Handler)). | |
90 | ||
91 | testcase_path_timeout_catch(Pred,TIMEOUT,Seq,P2,Csts,Ops,TestS,TI,Res) :- | |
92 | enter_new_error_scope(ScopeID,testcase_path_timeout_catch), | |
93 | (catch_enumeration_warning_and_overflow( | |
94 | cbc_path_solver:testcase_path_timeout(Pred,TIMEOUT,Seq,P2,Csts,Ops,TestS,TI,Res), | |
95 | Res=virtual_time_out) | |
96 | -> exit_error_scope(ScopeID,_ErrOcc,testcase_path_timeout_catch) | |
97 | ; (critical_enumeration_warning_occured_in_error_scope -> | |
98 | debug_format(19,'Enumeration warning occurred and is treated as timeout due to failure to find solution.~n',[]), | |
99 | exit_error_scope(ScopeID,_ErrOcc,testcase_path_timeout_catch), | |
100 | Res=virtual_time_out | |
101 | ; exit_error_scope(ScopeID,_ErrOcc,testcase_path_timeout_catch), | |
102 | fail | |
103 | ) | |
104 | ). | |
105 | ||
106 | ||
107 | :- use_module(probsrc(tools_meta),[safe_time_out/3]). | |
108 | :- use_module(extension('user_signal/user_signal'), [user_interruptable_call_det/2]). | |
109 | ||
110 | testcase_path_timeout(INIT,0,Events,EndPredicate,ConstantsState,Operations,TestStates,TransInfos,R) :- | |
111 | !,testcase_path_interrupt(INIT,Events,EndPredicate,ConstantsState,Operations,TestStates,TransInfos,R). | |
112 | testcase_path_timeout(INIT,TimeoutMs,Events,EndPredicate,ConstantsState,Operations,TestStates,TransInfos,Result) :- | |
113 | safe_time_out(testcase_path_interrupt(INIT,Events,EndPredicate,ConstantsState,Operations,TestStates,TransInfos,R), | |
114 | TimeoutMs, | |
115 | T), | |
116 | (T==success -> Result=R ; Result=timeout). | |
117 | ||
118 | :- use_module(probsrc(clpfd_interface),[catch_clpfd_overflow_call3/3]). | |
119 | testcase_path_interrupt(INIT,Events,EndPredicate,ConstantsState,Operations,TestStates,TransInfos,Result) :- | |
120 | catch_clpfd_overflow_call3( | |
121 | testcase_path_interrupt2(INIT,Events,EndPredicate,ConstantsState,Operations,TestStates,TransInfos,Result), | |
122 | message, % no need to create warning or error; we return clpfd_overflow as result | |
123 | Result=clpfd_overflow). | |
124 | ||
125 | testcase_path_interrupt2(INIT,Events,EndPredicate,ConstantsState,Operations,TestStates,TransInfos,Result) :- | |
126 | user_interruptable_call_det(testcase_path(INIT,Events,EndPredicate,ConstantsState,Operations,TestStates,TransInfos), | |
127 | Interrupt), | |
128 | (Interrupt==ok -> Result=ok ; Result=interrupt). | |
129 | ||
130 | add_constants_to_state_space(ConstantsState,TestStates,TestStates,[],root) :- | |
131 | empty_state(ConstantsState),!. | |
132 | add_constants_to_state_space(ConstantsState,TestStates,ConstTestStates,[Tuple],ConstantsID) :- | |
133 | Tuple = (SetupID,Setup,root,ConstantsID), | |
134 | specfile:create_setup_constants_operation(ConstantsState,complete_properties,Setup), | |
135 | tcltk_interface:tcltk_add_new_transition_transid(root,Setup,ConstantsID,concrete_constants(ConstantsState),[],SetupID), | |
136 | remove_constants_from_state(TestStates,ConstantsState,ConstantsID,ConstTestStates). | |
137 | ||
138 | remove_constants_from_state([],_,_,[]). | |
139 | remove_constants_from_state([InState|Irest],Constants,ConstId,[const_and_vars(ConstId,OutState)|Orest]) :- | |
140 | append(OutState,Constants,InState),!, % assume constants are at hend | |
141 | remove_constants_from_state(Irest,Constants,ConstId,Orest). | |
142 | ||
143 | ||
144 | add_operations_to_state_space([],_,[],[],[]). | |
145 | add_operations_to_state_space([Operation|Orest],PrevId,[State|Srest],[TransInfo|Trest],[Tuple|Irest]) :- | |
146 | Tuple = (OpId,Operation,PrevId,NextId), | |
147 | tcltk_interface:tcltk_add_new_transition_transid(PrevId,Operation,NextId,State,TransInfo,OpId), | |
148 | add_operations_to_state_space(Orest,NextId,Srest,Trest,Irest). | |
149 | ||
150 | :- use_module(probsrc(store),[normalise_state/2, normalise_states/2]). | |
151 | :- use_module(probsrc(kernel_waitflags),[push_wait_flag_call_stack_info/3, opt_push_wait_flag_call_stack_info/3]). | |
152 | ||
153 | testcase_path(INITIALISE,Events,EndPredicate,NormalisedConstantsState,OPS,StateSequence,TINFOS) :- | |
154 | init_wait_flags(WF0,[testcase_path]), | |
155 | opt_push_wait_flag_call_stack_info(WF0,using_state(cbc_path_solver,InitialState),WF), | |
156 | (INITIALISE=init | |
157 | -> OPS = [OpInit|Operations], TINFOS = [ITransInfo|OpTransInfos], | |
158 | testcase_initialise(ConstantsState,InitialState,OpInit,ITransInfo,WF) | |
159 | % TODO: push Init on WF call stack | |
160 | ; OPS = Operations, TINFOS = OpTransInfos, | |
161 | (just_typing_opt(INITIALISE) | |
162 | -> set_up_a_typed_state(ConstantsState,InitialState,WF) % only set up types | |
163 | ; set_up_a_valid_state(ConstantsState,InitialState,WF) % set up invariant | |
164 | ), | |
165 | % TO DO: project away constants and variables not needed for OPS and Predicates | |
166 | (init_opt_has_predicate(INITIALISE,InitialPredicate) | |
167 | -> eval_predicate(InitialPredicate,InitialState,WF) | |
168 | ; true) | |
169 | ), | |
170 | testcase_set_up_events(Events,InitialState,ConstantsState,Operations,States,FinalState,OpTransInfos,WF,WF2), | |
171 | eval_predicate(EndPredicate,FinalState,WF2), | |
172 | ground_wait_flags(WF2), | |
173 | normalise_states([InitialState|States],StateSequence), | |
174 | normalise_state(ConstantsState,NormalisedConstantsState). | |
175 | ||
176 | just_typing_opt(Option) :- get_init_option_paras(Option,Typing,_), !, Typing=typing. | |
177 | init_opt_has_predicate(Option,Pred) :- get_init_option_paras(Option,_,Pred),!, Pred \= none. | |
178 | ||
179 | get_init_option_paras(init,init,none). | |
180 | get_init_option_paras(typing,typing,none). | |
181 | get_init_option_paras(typing(Pred),typing,Pred). % just use typing from invariant and add P | |
182 | get_init_option_paras(invariant,invariant,none). | |
183 | get_init_option_paras(pred(Pred),invariant,Pred). % add P to invariant | |
184 | get_init_option_paras(INITIALISE,_,_) :- | |
185 | add_internal_error('Illegal init option:',get_init_option_paras(INITIALISE)),fail. | |
186 | ||
187 | ||
188 | testcase_predicate_timeout(Pred,Timeout,Result) :- | |
189 | catch(( | |
190 | safe_time_out(testcase_predicate(Pred), Timeout, T), | |
191 | (T==success -> Result=ok ; Result=timeout) | |
192 | ), enumeration_warning(_,_,_,_,_), Result=timeout). | |
193 | ||
194 | testcase_predicate(Predicate) :- | |
195 | init_wait_flags(WF,[testcase_predicate]), | |
196 | (just_typing_opt(Predicate) -> | |
197 | set_up_a_typed_state(_ConstantsState,InitialState,WF) % only set up types | |
198 | ; | |
199 | set_up_a_valid_state(_ConstantsState,InitialState,WF)), | |
200 | (init_opt_has_predicate(Predicate,Pred) -> | |
201 | eval_predicate(Pred,InitialState,WF) | |
202 | ; true), | |
203 | ground_wait_flags(WF). | |
204 | ||
205 | eval_predicate(Predicate,State,WF) :- | |
206 | empty_state(EmptyState), | |
207 | b_test_boolean_expression(Predicate,EmptyState,State,WF). | |
208 | ||
209 | :- use_module(probsrc(b_state_model_check),[set_up_transition/7, set_up_initialisation/5]). | |
210 | ||
211 | ||
212 | testcase_set_up_events([],State,_ConstState,[],[],State,[],WF,WF). | |
213 | testcase_set_up_events([Event|Erest],InState,ConstantState,[Operation|Orest], | |
214 | [InterState|Srest],OutState,[TransInfo|Trest],WF,WF3) :- | |
215 | set_up_transition(Event,Operation,ConstantState,InState,InterState,TransInfo,WF), | |
216 | push_wait_flag_call_stack_info(WF,after_event(Operation),WF2), | |
217 | testcase_set_up_events(Erest,InterState,ConstantState,Orest,Srest,OutState,Trest,WF2,WF3). | |
218 | ||
219 | ||
220 | ||
221 | testcase_initialise(ConstantState,InitialState,Initialisation,TransInfo,WF) :- | |
222 | set_up_constants_state(ConstantState,WF), | |
223 | set_up_initialisation(ConstantState,InitialState,Initialisation,TransInfo,WF). % TO DO: here I would like to be able to set up the invariant instead, if requested by another parameter | |
224 | ||
225 | :- use_module(probsrc(bmachine),[b_get_invariant_from_machine/1, b_get_properties_from_machine/1, | |
226 | b_get_machine_constants/1, b_get_machine_variables/1]). | |
227 | :- use_module(probsrc(b_global_sets),[static_symmetry_reduction_for_global_sets/1]). | |
228 | :- use_module(probsrc(b_enumerate), [b_tighter_enumerate_all_values/2]). | |
229 | :- use_module(probsrc(b_interpreter), [b_test_boolean_expression/4]). | |
230 | ||
231 | set_up_constants_state(ConstantsState,_WF) :- | |
232 | \+ not_all_transitions_added(root), | |
233 | \+ state_space:max_reached_or_timeout_for_node(root), | |
234 | % We should also check enumeration warnings : [DONE with max_reached_or_timeout_for_node] | |
235 | (state_space:transition(root,_,N) -> true), | |
236 | \+ ( state_space:transition(root,_,N2), N2\=N ), | |
237 | state_space:visited_expression(N,concrete_constants(ConstantsState)), | |
238 | !, % there is only one possible valuation of the constants | |
239 | debug_format(19,'Reusing constant values from state id ~w (only solution for PROPERTIES/axioms)~n',[N]). | |
240 | set_up_constants_state(ConstantState,WF) :- | |
241 | b_get_properties_from_machine(Properties), | |
242 | b_get_machine_constants(Constants), | |
243 | empty_state(EmptyState), | |
244 | set_up_typed_localstate(Constants,_FreshVars,TypedVals,EmptyState,ConstantState,positive), | |
245 | static_symmetry_reduction_for_global_sets(ConstantState), % from b_global_sets | |
246 | b_tighter_enumerate_all_values(TypedVals,WF), | |
247 | b_test_boolean_expression(Properties,EmptyState,ConstantState,WF). | |
248 | ||
249 | % set up a valid state satisfying the invariant | |
250 | set_up_a_valid_state(ConstantState,ValidState,WF) :- | |
251 | set_up_constants_state(ConstantState,WF), | |
252 | b_get_machine_variables(Variables), | |
253 | % create_target_state(Variables,Values,ConstantsState,ValidState,WF), | |
254 | set_up_typed_localstate(Variables,_Values,TypedVals,ConstantState,ValidState,positive), | |
255 | b_tighter_enumerate_all_values(TypedVals,WF), | |
256 | b_get_invariant_from_machine(Invariant), | |
257 | empty_state(EmptyState), | |
258 | b_test_boolean_expression(Invariant,EmptyState,ValidState,WF). | |
259 | ||
260 | ||
261 | ||
262 | set_up_constants_typed_state(ConstantState,WF) :- | |
263 | b_get_machine_constants(Constants), | |
264 | empty_state(EmptyState), | |
265 | set_up_typed_localstate(Constants,_FreshVars,TypedVals,EmptyState,ConstantState,positive), | |
266 | b_tighter_enumerate_all_values(TypedVals,WF). | |
267 | ||
268 | % set up a valid state satisfying the typing conditions of the invariant; but not necessarily the invariant | |
269 | set_up_a_typed_state(ConstantState,ValidState,WF) :- | |
270 | set_up_constants_typed_state(ConstantState,WF), | |
271 | b_get_machine_variables(Variables), | |
272 | % create_target_state(Variables,Values,ConstantsState,ValidState,WF), | |
273 | set_up_typed_localstate(Variables,_Values,TypedVals,ConstantState,ValidState,positive), | |
274 | b_tighter_enumerate_all_values(TypedVals,WF). | |
275 | ||
276 | ||
277 | %% verify_alloy_command(+CmdName, +Solver, -CmdIsValid, -IsCheckCmd, -Res). | |
278 | % Verify a command by solving the translated operation's precondition conjoined with the machine properties. | |
279 | % This is a special case of cbc path checking of length 1 but using a monolithic predicate. | |
280 | % Assumes that a corresponding Alloy model is loaded. | |
281 | % Solver is one of prob, probkodkod, probsmt or z3. | |
282 | verify_alloy_command(ProvidedCmdName, Solver, CmdIsValid, IsCheckCmd, Res) :- | |
283 | (ProvidedCmdName = '_' , b_get_machine_operation(CmdName, _, _, _) -> true % _ as a wildcard for first command | |
284 | ; CmdName = ProvidedCmdName), | |
285 | if(b_get_machine_operation(CmdName, _, _, Body), | |
286 | true, | |
287 | (add_error(cbc_path_solver,'Unknown Alloy command name:',CmdName), | |
288 | findall(Other,b_get_machine_operation(Other, _, _, _),OL), | |
289 | add_message(cbc_path_solver,'Available B operations: ',OL),fail)), | |
290 | get_precondition_from_translated_alloy_operation(Body, Precondition), | |
291 | b_get_properties_from_machine(Properties), | |
292 | conjunct_predicates([Properties,Precondition], Conj), | |
293 | start_ms_timer(Timer), | |
294 | ( Solver == prob | |
295 | -> solve_predicate(Conj, _, 1, [use_smt_mode/true,use_clpfd_solver/true,use_chr_solver/false,clean_up_pred,allow_improving_wd_mode/true], Res) | |
296 | ; Solver == probkodkod | |
297 | -> solve_predicate(Conj, _, 1, [use_smt_mode/true,use_clpfd_solver/true,use_chr_solver/false,clean_up_pred,allow_improving_wd_mode/true,use_solver_on_load/kodkod], Res) | |
298 | ; Solver == probsmt | |
299 | -> cdclt_solve_predicate(Conj, _SolvedWDPred, Res) | |
300 | ; Solver == z3, | |
301 | smt_solve_predicate(z3, Conj, _, Res) | |
302 | ), | |
303 | ( atom_concat(check, _, CmdName) | |
304 | -> validate_solver_result_for_command(check, Res, CmdIsValid), | |
305 | IsCheckCmd = true | |
306 | ; validate_solver_result_for_command(run, Res, CmdIsValid), | |
307 | IsCheckCmd = false | |
308 | ), | |
309 | (silent_mode(on) -> true | |
310 | ; (Res=solution(_) -> RS=solution ; RS=Res), | |
311 | ajoin(['Checking Alloy command ',CmdName,' with ',Solver,'; result = ',RS],Msg), | |
312 | stop_ms_walltimer_with_msg(Timer,Msg)). | |
313 | % comment in to perform double-checking of result: | |
314 | %(Solver=prob -> true ; eval_strings:double_check_smt_result(nostate(Solver),[],Conj,[],Res)). | |
315 | verify_alloy_command(ProvidedCmdName, Solver, CmdIsValid, IsCheckCmd, Res) :- | |
316 | add_internal_error('Call failed:',verify_alloy_command(ProvidedCmdName, Solver, CmdIsValid, IsCheckCmd, Res)). | |
317 | ||
318 | %% validate_solver_result_for_command(+RunOrCheck, +Res). | |
319 | % Note that check commands are negated to search for a counterexample. | |
320 | validate_solver_result_for_command(RunOrCheck, Res, CmdIsValid) :- | |
321 | Res == contradiction_found, | |
322 | !, | |
323 | ( RunOrCheck == check | |
324 | -> CmdIsValid = true | |
325 | ; CmdIsValid = false | |
326 | ). | |
327 | validate_solver_result_for_command(RunOrCheck, Res, CmdIsValid) :- | |
328 | Res = solution(_), | |
329 | ground(Res), | |
330 | !, | |
331 | ( RunOrCheck == run | |
332 | -> CmdIsValid = true | |
333 | ; CmdIsValid = false | |
334 | ). | |
335 | validate_solver_result_for_command(_, _, unknown). | |
336 | ||
337 | % We generate precondition/2 in alloy2b. No other guards. | |
338 | get_precondition_from_translated_alloy_operation(b(precondition(TPrecondition,_),subst,_), Precondition) :- | |
339 | !, | |
340 | Precondition = TPrecondition. | |
341 | get_precondition_from_translated_alloy_operation(_, b(truth,pred,[])). |