1 % (c) 2013-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 :- module(bmachine_static_checks, [static_check_main_machine/1,
6 extended_static_check_machine/0, extended_static_check_machine/1,
7 find_constant_expressions_in_operations/1, find_constant_expressions_in_operations/2,
8 toplevel_raw_predicate_sanity_check/4]).
9
10 :- use_module(probsrc(module_information)).
11 :- module_info(group,typechecker).
12 :- module_info(description,'Static checking of B machines upon construction. Detection of name clashes and uninitalised variables.').
13
14 :- use_module(bmachine_structure, [get_section/3]).
15 :- use_module(b_read_write_info, [get_accessed_vars/4]).
16 :- use_module(bsyntaxtree, [get_texpr_expr/2,get_texpr_id/2,get_texpr_ids/2, get_texpr_info/2]).
17 :- use_module(error_manager, [add_warning/3, add_warning/4, add_error/4, add_message/4]).
18 :- use_module(debug, [debug_println/2]).
19 :- use_module(tools, [ajoin/2]).
20 :- use_module(library(lists)).
21
22 static_check_main_machine(Machine) :-
23 debug_println(19,'Running static machine checks'),
24 initialises_all_variables(Machine),
25 check_name_clashes(Machine).
26
27
28 % ---------------------------------------------
29 % check for unnatural invariants and properties
30 % ---------------------------------------------
31 % the priority of the implication is sometimes surprising
32 % a & (x=1) => (y=2) & c --> top-level symbol is the implication !
33 % the test is done on the raw predicate before ast_cleanup removes potential typing predicates; see tests 106, 107
34 toplevel_raw_predicate_sanity_check(invariant,MachName,RawPredicate,Infos) :-
35 member(has_variables,Infos), % otherwise it is ok to have just a single disjunt, implication,...
36 check_top_level(RawPredicate,MachName,'INVARIANT'),
37 fail.
38 toplevel_raw_predicate_sanity_check(properties,MachName,RawPredicate,Infos) :-
39 member(has_constants,Infos), % otherwise it is ok to have just a single disjunt, implication,...
40 check_top_level(RawPredicate,MachName,'PROPERTIES'),
41 fail.
42 toplevel_raw_predicate_sanity_check(_,_,_,_).
43
44 % typical error: forget parentheses; if we write P => Q & R --> this is parsed as P => (Q & R)
45 check_top_level(implication(_,B,C),MachName,SECT) :-
46 (composed_predicate(B) ; composed_predicate(C)),!,
47 add_warning(bmachine_static_checks,'Top-level implication (=>) in clause: ',MachName:SECT).
48 check_top_level(disjunct(_,B,C),MachName,SECT) :-
49 (composed_predicate(B) ; composed_predicate(C)),!,
50 add_warning(bmachine_static_checks,'Top-level disjunction (or) in clause: ',MachName:SECT).
51 check_top_level(equivalence(_,B,C),MachName,SECT) :-
52 (composed_predicate(B) ; composed_predicate(C)),!,
53 add_warning(bmachine_static_checks,'Top-level equivalence (<=>) in clause: ',MachName:SECT).
54
55 % check if a predicate is composed of a binary operator where confusion may arise
56 % with negation and quantification there are explicit parentheses; the user should not be confused
57 composed_predicate(A) :- functor(A,Operator,N),
58 boolean_operator(Operator),
59 % Some binary operators (conjunct, disjunct) can have a varying number of arguments.
60 % See predicates associative_functor/1 and unflatten_assoc/4 in btypechecker.
61 N >= 2.
62 boolean_operator(conjunct).
63 boolean_operator(disjunct).
64 boolean_operator(implication).
65 boolean_operator(equivalence).
66
67 % ---------------------
68 % Checks is all variables are initialised by the machine
69 % ---------------------
70 initialises_all_variables(Machine) :-
71 get_section(initialisation,Machine,Initialisation),
72 % check wich variables are read / modified by the initialisation
73 get_accessed_vars(Initialisation,[],Modifies,_Reads),
74 get_machine_variables(Machine,SortedAllVars),
75 % check for each variable, if the initialisation modifies it
76 exclude(is_initialised(Modifies),SortedAllVars,Uninitialised),
77 % generate a warning if unitialised is not empty
78 generate_uninitialised_warning(Uninitialised,Initialisation),
79 % now check order of initialisation sequences
80 check_initialisation_order(Initialisation,SortedAllVars,[],_).
81
82 get_machine_variables(Machine,SortedAllVars) :-
83 % get all variables that should be initialised
84 get_section(abstract_variables,Machine,AbsVars),
85 get_section(concrete_variables,Machine,ConcVars),
86 append(AbsVars,ConcVars,TAllVars),
87 get_texpr_ids(TAllVars,AllVars),
88 sort(AllVars,SortedAllVars).
89
90 is_initialised(Modifies,Id) :-
91 memberchk(Id,Modifies).
92
93 generate_uninitialised_warning([],_) :- !.
94 generate_uninitialised_warning(Vars,Initialisation) :-
95 Msg='Machine may not initialise some of its variables: ',
96 add_warning(bmachine_static_checks,Msg,Vars,Initialisation).
97
98
99 :- use_module(library(ordsets)).
100 % Check if order of sequential compositions in INITIALISATION is ok
101 % TO DO: add support for if(LIST) and while
102 check_initialisation_order(b(Subst,subst,Info),AllVars,AlreadyInit,OutInit) :-
103 check_initialisation_order2(Subst,Info,AllVars,AlreadyInit,OutInit),!.
104 check_initialisation_order(Subst,AllVars,AlreadyInit,OutInit) :-
105 % print('CHCK '), translate:print_subst(Subst),nl,
106 get_accessed_vars(Subst,[],Modifies,Reads),
107 check_subst_or_pred(Subst,Modifies,Reads,AllVars,AlreadyInit,OutInit).
108
109 check_subst_or_pred(Subst,Modifies,Reads,AllVars,AlreadyInit,OutInit) :-
110 ord_union(Modifies,AlreadyInit,OutInit), % after Subst we have initialised OutInit
111 % below is already checked by type checker:
112 %ord_subtract(Modifies,AllVars,IllegalAssignments),
113 %(IllegalAssignments=[] -> true
114 % ; add_warning(bmachine_static_checks,'INITIALISATION writes illegal variables: ',IllegalAssignments,Subst)),
115 ord_intersection(Reads,AllVars,ReadFromSameMachine),
116 (atomic_subst(Subst)
117 -> ord_subtract(ReadFromSameMachine,AlreadyInit,IllegalReads)
118 ; ord_subtract(ReadFromSameMachine,OutInit,IllegalReads) % we use OutInit: there could be an if with sequence inside
119 ),
120 (IllegalReads=[] -> true
121 ; add_warning(bmachine_static_checks,'INITIALISATION reads variables which are not yet initialised: ',IllegalReads,Subst)).
122
123 :- use_module(bsyntaxtree,[find_identifier_uses/3]).
124 check_initialisation_order2(choice([First|T]),Info,AllVars,AlreadyInit,OutInit) :- !,
125 check_initialisation_order(First,AllVars,AlreadyInit,OutInit), % we pick the output of the first choice
126 (T=[] -> true ; check_initialisation_order(b(choice(T),subst,Info),AllVars,AlreadyInit,_)).
127 check_initialisation_order2(parallel([First|T]),Info,AllVars,AlreadyInit,OutInit) :- !,
128 check_initialisation_order(First,AllVars,AlreadyInit,OutInit1), % we pick the output of the first choice
129 (T=[] -> OutInit=OutInit1
130 ; check_initialisation_order2(parallel(T),Info,AllVars,AlreadyInit,OutInitRest),
131 ord_union(OutInit1,OutInitRest,OutInit)
132 ).
133 check_initialisation_order2(init_parallel(S),Info,AllVars,AlreadyInit,OutInit) :- !,
134 check_initialisation_order(b(parallel(S),subst,Info),AllVars,AlreadyInit,OutInit).
135 check_initialisation_order2(sequence([First|T]),Info,AllVars) --> !,
136 check_initialisation_order(First,AllVars),
137 ({T=[]} -> [] ; check_initialisation_order(b(sequence(T),subst,Info),AllVars)).
138 check_initialisation_order2(any(_Ids,Pred,Subst),_Info,AllVars,AlreadyInit,OutInit) :- !,
139 find_identifier_uses(Pred,[],Reads),
140 check_subst_or_pred(Pred,[],Reads,AllVars,AlreadyInit,_), % check predicate reads are ok
141 check_initialisation_order(Subst,AllVars,AlreadyInit,OutInit).
142 % TO DO: also check if-then-else, while, ...
143
144 atomic_subst(b(S,_,_)) :- atomic_subst2(S).
145 atomic_subst2(skip).
146 atomic_subst2(assign(_,_)).
147 atomic_subst2(assign_single_id(_,_)).
148 atomic_subst2(becomes_element_of(_,_)).
149 %atomic_subst2(becomes_such(_,_)). % this needs to be dealt with separately, e.g., test 583 p,solved : (p = %i.(i : 1 .. 9|0) & solved = 0)
150
151 % ---------------------
152 % Checks if an operations parameter or local variable clashes with a variable
153 % ---------------------
154 check_name_clashes(Machine) :-
155 debug_println(10,'Checking for name clashes'),
156 get_all_machine_ids(Machine,SortedAllIds),
157 % for each operation, compare the parameter names with existing vars/constants
158 check_duplicate_machine_ids(SortedAllIds),
159 get_section(operation_bodies,Machine,Operations),
160 maplist(op_name_clashes(SortedAllIds),Operations),
161 get_section(definitions,Machine,Defs),
162 maplist(def_name_clashes(SortedAllIds),Defs).
163
164 get_all_machine_ids(Machine,SortedAllIds) :-
165 % get all variables and constants that might clash
166 get_section_ids(abstract_variables,Machine,'variable',AbsVars),
167 get_section_ids(concrete_variables,Machine,'variable',ConcVars),
168 get_section_ids(abstract_constants,Machine,'constant',AbsCons),
169 get_section_ids(concrete_constants,Machine,'constant',ConcCons),
170 get_section_ids(deferred_sets,Machine,'set',DefSets),
171 get_section_ids(enumerated_sets,Machine,'set',EnumSets),
172 get_section_ids(enumerated_elements,Machine,'set element',EnumElems),
173 % enumerated b_get_named_machine_set(GlobalSetName,ListOfConstants) + b_get_machine_set(S)
174 append([AbsVars,ConcVars,AbsCons,ConcCons,DefSets,EnumSets,EnumElems],AllIds),
175 keyword_clash(AllIds),
176 sort(AllIds,SortedAllIds).
177
178 % check for duplicates within the machine ids
179 % this should normally not happen, if bmachine_construction works properly
180 check_duplicate_machine_ids([]).
181 check_duplicate_machine_ids([machine_id(ID,Class,Pos,Section)|T]) :-
182 check_dup2(T,ID,Class,Pos,Section).
183
184 check_dup2([],_,_,_,_).
185 check_dup2([machine_id(ID,Class,Pos,Section)|T],ID0,Class0,Pos0,Section0) :-
186 (ID0=ID
187 -> get_descr(Pos0,Section0,Descr),
188 (Pos0=Pos, Section0=Section,Class=Class0
189 -> ajoin(['Something is wrong: ', Class0, ' appears twice: '], Msg)
190 ; ajoin(['Something is wrong: the ', Class0, ' "', ID, '" (', Descr,') clashes with ',Class,': '], Msg)
191 ),
192 add_message(bmachine_static_checks,Msg,ID,Pos) % handle_collision will generate an error later ?
193 ; true
194 ),
195 check_dup2(T,ID,Class,Pos,Section).
196
197 get_section_ids(Section,Machine,Class,ResultList) :-
198 get_section(Section,Machine,Vars),
199 findall(machine_id(ID,Class,Pos,Section),
200 (member(TID,Vars),get_texpr_id(TID,ID),get_texpr_info(TID,Pos)),ResultList).
201
202
203 :- use_module(tools_matching,[is_b_keyword/2]).
204
205 % can be useful for Z, TLA+, Event-B machines:
206 % Clashes may lead to strange type or parse errors in VisB, REPL, ...
207 keyword_clash(AllIds) :-
208 ? member(machine_id(Name,Class,Pos,Section),AllIds),
209 is_b_keyword(Name,_),
210 get_descr(Pos,Section,Descr),
211 ajoin(['The ', Class, ' "', Name, '" (', Descr,') has the same name as a classical B keyword (may lead to unexpected parse or type errors when entering formulas unless you surround it by backquotes: `',Name,'`).'], Msg),
212 (classical_b_mode
213 -> add_message(bmachine_static_checks,Msg,'',Pos) % user probably uses new backquote syntax already
214 ; add_warning(bmachine_static_checks,Msg,'',Pos)
215 ),
216 fail.
217 keyword_clash(_).
218
219 :- use_module(bsyntaxtree,[map_over_typed_bexpr/2]).
220 :- use_module(external_functions,[is_external_function_name/1]).
221
222 def_name_clashes(AllIds,definition_decl(Name,DefType,_DefPos,Args,_RawExpr,_Deps)) :- !,
223 (is_external_function_name(Name)
224 -> true % do not check external predicates, functions, subst
225 % they are not written by user and possibly not used and definitions are virtual and not used anyway
226 ; findall(b(identifier(ID),any,[nodeid(IdPos)]),
227 member(identifier(IdPos,ID),Args),ArgIds), % args can sometimes not be identifiers; see test 1711
228 debug_println(4,checking_def(Name,DefType,Args,ArgIds)),
229 include(clash_warnings('DEFINITION parameter',AllIds,'DEFINITION',Name),ArgIds,_ArgsCausingWarning)
230 % TO DO: check Body; for this we need a map_over_raw_expression to detect local variables introduced !
231 ).
232 def_name_clashes(_,D) :- print(unknown_def(D)),nl.
233
234 :- use_module(preferences,[get_preference/2]).
235 op_name_clashes(AllIds,Operation) :-
236 get_texpr_expr(Operation,operation(IdFull,Results,Params,Subst)),
237 %get_texpr_id(IdFull,op(Id)),
238 IdFull = b(identifier(op(Id)),Type,Info),
239 (get_preference(clash_strict_checks,true)
240 -> include(clash_warnings('Operation name',AllIds,operation,Id),[b(identifier(Id),Type,Info)],_NameCausingWarning) % fix PROB-60
241 ; true),
242 include(clash_warnings('Operation parameter',AllIds,operation,Id),Params,_ParamsCausingWarning),
243 include(clash_warnings('Operation result variable',AllIds,operation,Id),Results,_ResultsCausingWarning),
244 (map_over_typed_bexpr(bmachine_static_checks:check_operation_body_clashes(AllIds,Id),Subst),fail ; true).
245
246 :- public check_operation_body_clashes/3.
247 check_operation_body_clashes(AllIds,Operation,TSubst) :-
248 get_texpr_expr(TSubst,Subst),
249 (local_variable_clash(Subst,TSubst,AllIds,Operation);
250 illegal_op_call(Subst,Operation)).
251
252 local_variable_clash(Subst,TSubst,AllIds,Operation) :-
253 introduces_local_variable(Subst,ID),
254 clash_local_warnings(AllIds,Operation,ID,TSubst).
255
256 % check if something like zz(1) <-- Op(a) is used; this is not allowed according to Atelier-B
257 illegal_op_call(operation_call(CalledOperation,Results,_Parameters),Operation) :-
258 member(TID,Results), \+ get_texpr_id(TID,_),
259 (get_texpr_id(CalledOperation,op(CalledId)) -> true ; CalledId=CalledOperation),
260 ajoin(['Return value of operation call to ',CalledId,' must be stored in identifier within:'],Msg),
261 add_error(bmachine_static_checks,Msg,Operation,TID).
262
263 % check for constructs which introduced local variables
264 introduces_local_variable(var(Parameters,_),ID) :-
265 % currently B-interpreter cannot correctly deal with this in the context of operation_call
266 member(TID,Parameters), get_texpr_id(TID,ID).
267
268 :- use_module(probsrc(error_manager),[extract_span_description/2]).
269 % ord_member does not work below because of free variable Class
270 my_ord_member(Name,Class,Descr,[machine_id(Name1,_,_,_)|T]) :-
271 Name @> Name1, !,
272 my_ord_member(Name,Class,Descr,T).
273 my_ord_member(Name,Class,Descr,[machine_id(Name,Class,Pos,Section)|_]) :-
274 get_descr(Pos,Section,Descr).
275
276 get_descr(Pos,Section,Descr) :-
277 (extract_span_description(Pos,Descr) -> true
278 ; ajoin(['from section ',Section],Descr) ).
279
280 clash_warnings(Context,AllIds,OpOrDef,OperationId,TName) :-
281 get_texpr_id(TName,Name),
282 my_ord_member(Name,Class,Descr,AllIds), !,
283 ajoin(['The ', Class, ' "', Name, '" (', Descr,') has the same name as a ',
284 Context, ' in ',OpOrDef,' "', OperationId,'".'], Msg),
285 add_warning(bmachine_static_checks,Msg,'',TName).
286
287 clash_local_warnings(AllIds,OperationId,Name,Pos) :-
288 my_ord_member(Name,Class,Descr,AllIds), !,
289 % we could check and see if Name is really visible from this location!
290 % (see public_examples/B/Other/LustreTranslations/UMS_verif/M_UMS_verif.mch)
291 ajoin(['The ', Class, ' "', Name, '" (', Descr,') has the same name as a local variable in operation "', OperationId,'".'], Msg),
292 add_warning(bmachine_static_checks,Msg,'',Pos).
293
294 % TODO: this does not and can not work here:
295 % - Some preconditions are removed (typing only....) during machine simplification
296 % - Needs to be verified during typechecking
297 % ---------------------
298 % Checks if an operations parameter is not typed by a pre condition
299 % ---------------------
300 %parameters_without_pre_condition(Machine) :-
301 % get_section(operation_bodies,Machine,Operations),
302 % maplist(parameters_without_pre_condition_aux,Operations).
303 %
304 %parameters_without_pre_condition_aux(Operation) :-trace,
305 % get_texpr_expr(Operation,operation(IdFull,_Results,Params,Subst)),
306 % get_texpr_id(IdFull,op(Id)),
307 % (Params == []
308 % -> true % no parameters
309 % ; (get_texpr_expr(Subst,precondition(_,_))
310 % -> true % parameters and precondition
311 % ; ajoin(['Operation ', Id, ' has parameters but no pre-condition.'], Msg),
312 % add_warning(bmachine_static_checks,Msg)
313 % )).
314
315
316 % EXTENDED ADDITIONAL CHECKS
317 % these are run (optionally) after machine is loaded and bmachine pre-calculations have run
318
319 :- use_module(b_read_write_info,[check_all_variables_written/0]).
320
321 extended_static_check_machine :-
322 extended_static_check_machine(_).
323 extended_static_check_machine(Check) :-
324 reset_static_check,
325 esc_step(Check).
326
327 :- use_module(probsrc(bmachine), [b_get_main_filename/1, b_machine_name/1, b_get_machine_header_position/2]).
328 :- use_module(tools,[get_modulename_filename/2, get_filename_extension/2]).
329 :- use_module(bsyntaxtree,[map_over_typed_bexpr_with_names/2]).
330 :- use_module(specfile,[get_specification_description/2, animation_minor_mode/1, classical_b_mode/0]).
331 :- use_module(bmachine).
332 :- use_module(b_machine_hierarchy,[machine_type/2, machine_operations/2]).
333
334 esc_step(variables) :-
335 check_all_variables_written,
336 fail.
337 esc_step(machine_name) :-
338 b_machine_name(Name),
339 b_get_main_filename(Filename),
340 get_modulename_filename(Filename,ModuleName),
341 Name \= ModuleName,
342 (atom_concat('MAIN_MACHINE_FOR_',RealName,Name) % see dummy_machine_name in bmachine_construction
343 -> RealName \= ModuleName
344 ; is_dummy_machine_name(Name,Filename)
345 -> fail % dummy Rules DSL or Alloy machine name, do not create warning
346 ; true
347 ),
348 (b_get_machine_header_position(Name,Span) -> true
349 ; Span = src_position_with_filename(1,1,1,Filename)),
350 get_specification_description(machine,MCH),
351 ajoin(['Filename ',ModuleName,' does not match name of ', MCH, ': '],Msg),
352 add_warning(bmachine_static_checks,Msg,Name,Span),
353 fail.
354 esc_step(variables) :- debug_println(19,checking_identifiers_for_clashes),
355 full_b_machine(Machine),
356 local_quantified_variable_clashes(Machine),
357 fail.
358 esc_step(operations) :- machine_type(MachName,abstract_machine),
359 machine_operations(MachName,Ops),
360 member(identifier(Span,OpName),Ops),
361 atom_codes(OpName,Codes), member(46,Codes), % "." element of name
362 % Section 7.23, paragraph 2 of Atelier-B handbook: in abstract machine we cannot use renamed operation names
363 ajoin(['Operation name in abstract machine ',MachName,' is composed: '],Msg),
364 add_warning(bmachine_static_checks,Msg,OpName,Span),
365 fail.
366 esc_step(operations) :- debug_println(19,checking_operation_bodies),
367 b_get_machine_operation(ID,_Res,_TParas,Body,_OType,_Pos),
368 check_operation_body(Body,ID),
369 fail.
370 esc_step(operations) :- portray_constant_expr_summary, fail.
371 esc_step(constants) :-
372 check_concrete_constants, % check concrete_constants(.) states
373 fail.
374 esc_step(unused_ids) :-
375 check_unused_ids,
376 fail.
377 esc_step(_).
378
379 :- use_module(probsrc(tools_strings), [atom_prefix/2]).
380 is_dummy_machine_name(Name,_) :- atom_prefix('__RULES_MACHINE_Main',Name).
381 is_dummy_machine_name(alloytranslation,_) :- animation_minor_mode(alloy).
382 is_dummy_machine_name('DEFINITION_FILE',Filename) :- classical_b_mode,
383 get_filename_extension(Filename,'def').
384 % when opening .def files; WARNING: sometimes def files use other extensions
385
386 % TO DO: optionally do these kinds of checks in the REPL
387 local_quantified_variable_clashes(Machine) :-
388 get_all_machine_ids(Machine,SortedAllIds),
389 (get_typed_section(Sec,SecID,Pred),
390 debug_println(19,checking_local_quantified_variable_clashes(Sec,SecID)),
391 map_over_typed_bexpr_with_names(bmachine_static_checks:check_introduced_ids(Sec,SecID,SortedAllIds),Pred)
392 ;
393 check_definition_clashes(SortedAllIds)
394 ),
395 fail.
396 local_quantified_variable_clashes(_).
397
398 :- use_module(bmachine,[b_get_typed_definition/3]).
399 check_definition_clashes(SortedAllIds) :-
400 Scope=[variables],
401 b_get_typed_definition(Name,Scope,TExpr),
402 % TODO: adapt type_check_definitions to return definitions with paras and add paras to list of Ids
403 %print(check(Name,SortedAllIds)),nl,
404 map_over_typed_bexpr_with_names(bmachine_static_checks:check_introduced_ids('DEFINITION',Name,SortedAllIds),TExpr).
405
406 :- public check_introduced_ids/5. % used in map above
407 check_introduced_ids(Section,SectionID,SortedAllIds,TExpr,TNames) :-
408 \+ ignore_constructor(TExpr),
409 (member(TName,TNames),
410 clash_warnings('local variable',SortedAllIds,Section,SectionID,TName)
411 ; removed_identifier(TExpr,TName),
412 clash_warnings('(removed) local variable',SortedAllIds,Section,SectionID,TName)
413 % TODO: also check duplicate_id_hides info fields ?
414 ).
415
416 % detect some identifiers that were removed
417 removed_identifier(b(_,_,Infos),TId) :-
418 member(was(WAS),Infos),
419 introduced_ids(WAS,IDs),
420 % this happens in EnumSetClash2.mch
421 member(TId,IDs).
422
423 introduced_ids(forall(IDs,_,_),IDs).
424 introduced_ids(exists(IDs,_),IDs). % % are there more relevant cases? TODO: use syntaxtraversion
425
426 % ignore certain constructs, which do not really introduce a new identifier:
427 ignore_constructor(b(recursive_let(_,_),_,_)).
428
429
430 get_typed_section(Kind,Name,SubPred) :-
431 b_get_properties_from_machine(P), get_specification_description(properties,PS),
432 get_sub_predicate(PS,P,Kind,Name,SubPred).
433 get_typed_section(Kind,Name,SubPred) :-
434 b_get_invariant_from_machine(P), get_specification_description(invariants,PS),
435 get_sub_predicate(PS,P,Kind,Name,SubPred).
436 get_typed_section(Kind,Name,SubPred) :-
437 get_specification_description(assertions,APS), ajoin(['(dynamic) ',APS],AS),
438 b_get_dynamic_assertions_from_machine(Ps),
439 l_get_sub_predicate(AS,Ps,Kind,Name,SubPred).
440 get_typed_section(Kind,Name,SubPred) :-
441 get_specification_description(assertions,APS), ajoin(['(static) ',APS],AS),
442 b_get_static_assertions_from_machine(Ps),
443 l_get_sub_predicate(AS,Ps,Kind,Name,SubPred).
444 get_typed_section(operation,OpName,P) :- b_get_machine_operation(OpName,_Results,_Parameters,P).
445 % TO DO: add more sections: constraints, DEFINITION bodies
446
447
448 :- use_module(bsyntaxtree, [conjunction_to_list/2, get_texpr_label/2]).
449 get_sub_predicate(Clause,Pred,Kind,Name,SubPred) :-
450 conjunction_to_list(Pred,Preds),
451 l_get_sub_predicate(Clause,Preds,Kind,Name,SubPred).
452 l_get_sub_predicate(Clause,Preds,Kind,Name,SubPred) :-
453 member(SubPred,Preds),
454 (get_texpr_label(SubPred,Label)
455 -> Kind = predicate, ajoin([Label,'" in clause "',Clause],Name)
456 ; Kind = clause, Name=Clause).
457
458
459 % -------------------------
460
461 % check for reading uninitialised variables
462
463 :- use_module(bmachine, [b_top_level_operation/1, b_top_level_feasible_operation/1, b_is_constant/1]).
464 check_operation_body(Body,OpID) :- b_top_level_operation(OpID),
465 \+ b_top_level_feasible_operation(OpID),
466 add_warning(bmachine_static_checks,'Infeasible body for operation:',OpID,Body),
467 fail.
468 check_operation_body(Body,OpID) :-
469 map_over_typed_bexpr(bmachine_static_checks:check_operation_body_var(OpID),Body),
470 fail.
471 check_operation_body(Body,OpID) :-
472 check_for_constant_expressions(Body,OpID,[],_,_).
473
474 check_operation_body_var(OpID,b(var(Parameters,Body),subst,_Pos)) :-
475 get_texpr_ids(Parameters,Ps), sort(Ps,Uninitialised),
476 get_accessed_vars(Body,[],_Modifies,Reads),
477 ord_intersection(Uninitialised,Reads,URead),
478 URead \= [],
479 member(TID,Parameters), get_texpr_id(TID,ID),
480 member(ID,URead),
481 ajoin(['Possibly reading uninitialised variable in operation ',OpID,' : '],Msg),
482 add_warning(bmachine_static_checks,Msg,ID,TID).
483 % TO DO: we could pinpoint more precisely where the read occurs
484
485 % locate potentially expensive fully constant expressions (depending only on B constants)
486 % which are in a dynamic context (operations) and which may be recomputed many times
487 % the b_compiler will quite often pre-compile those, but not always (e.g., for relational composition involving infinite functions or when we have WD conditions)
488 :- use_module(error_manager,[get_tk_table_position_info/2]).
489 :- use_module(translate,[translate_bexpression_with_limit/3]).
490 find_constant_expressions_in_operations(List) :-
491 find_constant_expressions_in_operations(create_messages,List).
492
493 find_constant_expressions_in_operations(MsgOrWarn,_) :-
494 reset_static_check,
495 assert(store_expr(MsgOrWarn)),
496 b_get_machine_operation(OpID,_Res,_TParas,Body,_OType,_Pos),
497 check_for_constant_expressions(Body,OpID,[],_,_),
498 fail.
499 find_constant_expressions_in_operations(_,list([Header|List])) :-
500 Header = ['Operation', 'Occurence', 'Expr', 'LocalIds', 'Source'],
501 portray_constant_expr_summary,
502 findall(list([OpID,Count,ES,LocalIds,Src]),
503 (const_expr(OpID,Count,BExpr,LocalIds),
504 translate_bexpression_with_limit(BExpr,100,ES),
505 get_tk_table_position_info(BExpr,Src)), List),
506 reset_static_check.
507
508 :- use_module(bsyntaxtree,[syntaxtraversion/6]).
509 check_for_constant_expressions(BExpr,OpID,LocalIds,IsConstant,IsExpensive) :-
510 syntaxtraversion(BExpr,Expr,Type,_Infos,Subs,TNames),
511 (get_texpr_ids(TNames,QuantifiedNewIds), list_to_ord_set(QuantifiedNewIds,SQuantifiedNewIds)
512 -> ord_union(LocalIds,SQuantifiedNewIds,NewLocalIds)
513 ; write(err(TNames)),nl, NewLocalIds = LocalIds),
514 l_check_for_const(Subs,OpID,NewLocalIds,SQuantifiedNewIds,AreAllConstant,IsExpensive1,ExpensiveList),
515 %translate:print_bexpr_or_subst(BExpr),nl, write(cst(AreAllConstant,IsExpensive1,LocalIds)),nl,nl,
516 (AreAllConstant=is_constant,
517 is_constant_expression(Expr,LocalIds,IsExpensive2)
518 -> IsConstant = is_constant,
519 combine_expensive(IsExpensive1,IsExpensive2,IsExpensive),
520 (pred_or_subst(Type) % we may traverse the boundary of expressions to pred/subst: print expressions
521 -> maplist(add_const_expr_msg(OpID,LocalIds),ExpensiveList)
522 ; true)
523 ; IsConstant = not_constant, IsExpensive=not_expensive,
524 maplist(add_const_expr_msg(OpID,LocalIds),ExpensiveList)
525 ).
526
527
528 :- use_module(probsrc(b_interpreter_check),[norm_check/2]).
529 :- use_module(probsrc(hashing),[my_term_hash/2]).
530 :- use_module(probsrc(tools), [ajoin_with_sep/3]).
531 add_const_expr_msg(OpID,LocalIds,BExpr) :-
532 (LocalIds=[] -> debug:debug_mode(on) ; true), % decide whether to show outer-level constant expressions
533 register_constant_expression(BExpr,OpID,LocalIds,Count,MsgOrWarn),
534 (LocalIds = [] -> LMsg=[': ']
535 ; length(LocalIds,Len),
536 (Len =< 3
537 -> ajoin_with_sep(LocalIds,',',LIDS),
538 LMsg = [' inside quantification (',LIDS,'): ']
539 ; prefix_length(LocalIds,Prefix,2),
540 last(LocalIds,LastId),
541 ajoin_with_sep(Prefix,',',LIDS),
542 LMsg = [' inside quantification (',Len,' ids: ',LIDS,',...,',LastId,'): ']
543 )
544 ),
545 (Count > 1
546 -> ajoin(['Repeated constant expression in operation ',OpID,
547 ' (occurence nr. ',Count,', consider lifting it out)'|LMsg],Msg)
548 ; ajoin(['Non-trivial constant expression in operation ',OpID,' (consider lifting it out)'|LMsg],Msg)
549 ),
550 (MsgOrWarn=create_messages
551 -> add_message(b_machine_static_checks,Msg,BExpr,BExpr)
552 ; add_warning(b_machine_static_checks,Msg,BExpr,BExpr)).
553
554 :- dynamic store_expr/1, const_expr_hash_count/2, const_expr/4.
555 reset_static_check :- retractall(const_expr_hash_count(_,_)),
556 retractall(const_expr(_,_,_,_)), retractall(store_expr(_)).
557 register_constant_expression(BExpr,OpID,LocalIds,Count,MsgOrWarn) :-
558 norm_check(BExpr,Norm), my_term_hash(Norm,Hash),
559 (retract(const_expr_hash_count(Hash,C)) -> Count is C+1 ; Count=1),
560 assert(const_expr_hash_count(Hash,Count)),
561 (store_expr(MsgOrWarn) -> assert(const_expr(OpID,Count,BExpr,LocalIds)) ; MsgOrWarn=message).
562
563 portray_constant_expr_summary :-
564 findall(Count,const_expr_hash_count(_,Count),LC),
565 length(LC,Len),
566 format('Constant expressions found: ~w~n',[Len]),
567 sumlist(LC,Total),
568 format('Total # of occurrences: ~w~n',[Total]).
569
570
571 l_check_for_const([],_OpID,_,_,is_constant,not_expensive,[]).
572 l_check_for_const([H|T],OpID,LocalIds,NewQuantLocalIds,AreAllConstant,IsExpensive,ExpResList) :-
573 check_for_constant_expressions(H,OpID,LocalIds,IsConstant,IsExpensive0),
574 project_is_expensive(IsExpensive0,NewQuantLocalIds,IsExpensive1), % project on NewIds introduced at top-level
575 (IsConstant=is_constant
576 -> (IsExpensive1=is_expensive, is_expression(H) % currently we only look for constant expressions
577 -> ExpResList = [H|TR] % add to list for add_const_expr_msg
578 ; ExpResList=TR),
579 l_check_for_const(T,OpID,LocalIds,NewQuantLocalIds,AreAllConstant,IsExpensive2,TR),
580 combine_expensive(IsExpensive1,IsExpensive2,IsExpensive)
581 ; AreAllConstant=not_constant, IsExpensive=not_expensive,
582 l_check_for_const(T,OpID,LocalIds,NewQuantLocalIds,_,_,ExpResList)).
583
584 is_expression(b(_,T,_)) :- \+ pred_or_subst(T).
585
586 pred_or_subst(pred).
587 pred_or_subst(subst).
588
589 % operators on abstract expensive/local_id domain:
590 combine_expensive(not_expensive,IsExpensive2,IsExpensive) :- !, IsExpensive=IsExpensive2.
591 combine_expensive(is_expensive,depends_on_local_ids(Ids,_),Res) :- !,
592 Res=depends_on_local_ids(Ids,is_expensive).
593 combine_expensive(is_expensive,_,IsExpensive) :- !, IsExpensive=is_expensive.
594 combine_expensive(depends_on_local_ids(Ids1,IE1),depends_on_local_ids(Ids2,IE2),Res) :- !,
595 combine_expensive(IE1,IE2,IE),
596 ord_union(Ids1,Ids2,Ids), Res=depends_on_local_ids(Ids,IE).
597 combine_expensive(depends_on_local_ids(Ids,IE1),IE2,Res) :- !,
598 combine_expensive(IE1,IE2,IE),
599 Res=depends_on_local_ids(Ids,IE).
600 combine_expensive(A,B,C) :- write(combine_expensive_uncovered(A,B,C)),nl,nl,C=A.
601
602 % project is_expensive domain value after leaving SQuantifiedNewIds
603 project_is_expensive(depends_on_local_ids(LocalIds,IE),SQuantifiedNewIds,Res) :- !,
604 ord_subtract(LocalIds,SQuantifiedNewIds,NewLocalIds),
605 (NewLocalIds = [] -> Res = IE % is_expensive/not_expensive
606 ; Res= depends_on_local_ids(NewLocalIds,IE)).
607 project_is_expensive(IE,_,IE).
608
609 :- use_module(probsrc(b_global_sets),[lookup_global_constant/2]).
610 % check if based on top-level the expression is constant, assuming all args are constant
611 is_constant_expression(identifier(ID),LocalIds,Kind) :- !,
612 (ord_member(ID,LocalIds) -> Kind = depends_on_local_ids([ID],not_expensive)
613 ; b_is_constant(ID) -> Kind = not_expensive
614 ; b_get_machine_set(ID) -> Kind = not_expensive
615 ; lookup_global_constant(ID,_) -> Kind = not_expensive). % enumerated set element
616 is_constant_expression(Expr,_,IsExpensive) :- is_constant_expression(Expr,IsExpensive).
617
618 is_constant_expression(value(_),not_expensive).
619 % literals:
620 is_constant_expression(max_int,not_expensive).
621 is_constant_expression(min_int,not_expensive).
622 is_constant_expression(boolean_false,not_expensive).
623 is_constant_expression(boolean_true,not_expensive).
624 is_constant_expression(empty_set,not_expensive).
625 is_constant_expression(empty_sequence,not_expensive).
626 is_constant_expression(integer(_),not_expensive).
627 is_constant_expression(real(_),not_expensive).
628 is_constant_expression(string(_),not_expensive).
629
630 is_constant_expression(bool_set,not_expensive).
631 is_constant_expression(freetype_set(_),not_expensive).
632 is_constant_expression(real_set,not_expensive).
633 is_constant_expression(string_set,not_expensive).
634 is_constant_expression(typeset,not_expensive).
635 is_constant_expression(integer_set(_),not_expensive).
636 % simple arithmetic:
637 is_constant_expression(unary_minus(_),not_expensive).
638 is_constant_expression(add(_,_),not_expensive).
639 is_constant_expression(minus(_,_),not_expensive).
640 is_constant_expression(multiplication(_,_),not_expensive).
641 % just constructing values:
642 is_constant_expression(couple(_,_),not_expensive).
643 is_constant_expression(rec(_),not_expensive).
644 is_constant_expression(set_extension(_),not_expensive).
645 is_constant_expression(sequence_extension(_),not_expensive).
646 is_constant_expression(interval(_,_),not_expensive).
647 % simple deconstructing
648 is_constant_expression(first_of_pair(_),not_expensive).
649 is_constant_expression(second_of_pair(_),not_expensive).
650 is_constant_expression(record_field(_,_),not_expensive).
651 % just typing
652 % TODO: return typing instead of not_expensive as result and check context used later
653 is_constant_expression(pow_subset(_),not_expensive).
654 is_constant_expression(fin_subset(_),not_expensive).
655 is_constant_expression(pow1_subset(_),not_expensive).
656 is_constant_expression(fin1_subset(_),not_expensive).
657 is_constant_expression(seq(_),not_expensive).
658 is_constant_expression(seq1(_),not_expensive).
659 is_constant_expression(iseq(_),not_expensive).
660 is_constant_expression(iseq1(_),not_expensive).
661 is_constant_expression(cartesian_product(_,_),not_expensive).
662 is_constant_expression(mult_or_cart(_,_),not_expensive).
663 is_constant_expression(relations(_,_),not_expensive).
664 is_constant_expression(struct(_),not_expensive).
665 % TO DO: card : trivial if avl_set; maybe we should do this after constants_analysis
666 % TODO: detect some external function calls as not constant: RANDOM, ...
667
668 %is_constant_expression(domain(_),not_expensive). % is not expensive for symbolic lambas, and reasonable for avl_set
669 is_constant_expression(composition(_,_),is_expensive).
670 is_constant_expression(image(_,_),is_expensive).
671 is_constant_expression(iteration(_,_),is_expensive).
672 is_constant_expression(concat(_,_),is_expensive).
673 is_constant_expression(reflexive_closure(_),is_expensive).
674 is_constant_expression(closure(_),is_expensive).
675 is_constant_expression(union(_,_),is_expensive).
676 is_constant_expression(intersection(_,_),is_expensive).
677
678 is_constant_expression(_,is_expensive).
679
680 % ---------------------------
681
682 % perform some checks on symbolic values; look for obvious WD errors
683
684 :- use_module(probsrc(bsyntaxtree),[map_over_typed_bexpr/2]).
685 :- use_module(probsrc(state_space),[is_concrete_constants_state_id/1, visited_expression/2]).
686 :- use_module(probsrc(specfile),[state_corresponds_to_set_up_constants/2]).
687
688 check_concrete_constants :- is_concrete_constants_state_id(ID),!,
689 check_values_in_state(ID).
690 check_concrete_constants.
691
692 check_values_in_state(ID) :- debug_format(19,'Checking values in state with id = ~w~n',[ID]),
693 visited_expression(ID,State),
694 state_corresponds_to_set_up_constants(State,EState),
695 member(bind(VarID,Value),EState),
696 check_symbolic_values(Value,VarID),
697 fail.
698 check_values_in_state(_).
699
700 :- use_module(debug,[debug_println/2, debug_format/3]).
701 :- use_module(error_manager,[add_error/3]).
702
703 check_symbolic_values(Var,Ctxt) :- var(Var),!,
704 add_error(bmachine_static_checks,'Value is a variable',Ctxt).
705 check_symbolic_values(closure(_,_,Body),Ctxt) :- !,
706 debug_format(19,'Checking symbolic value for ~w~n',[Ctxt]),
707 map_over_typed_bexpr(check_symbolic_value(Ctxt),Body).
708 check_symbolic_values(_,_).
709
710 check_symbolic_value(Ctxt,b(E,T,I)) :- check_aux(E,T,I,Ctxt).
711
712 check_aux(function(b(Func,_,_I1),_Arg),_,I2,Ctxt) :- % _I1 sometimes unknown
713 % TO DO: check if Info contains WD flag
714 check_is_partial_function(Func,I2,Ctxt).
715 % TO DO: check sequence operators, ...
716
717 :- use_module(custom_explicit_sets,[is_not_avl_partial_function/2]).
718 :- use_module(library(avl),[avl_size/2]).
719 :- use_module(probsrc(translate), [translate_bvalue_with_limit/3]).
720 check_is_partial_function(value(Val),Info,Ctxt) :- nonvar(Val), Val=avl_set(AVL),
721 is_not_avl_partial_function(AVL,DuplicateKey),!,
722 avl_size(AVL,Size),
723 translate_bvalue_with_limit(DuplicateKey,80,DKS),
724 ajoin(['Relation of size ', Size, ' is not a function (value for ',Ctxt, '); duplicate key: '],Msg),
725 add_warning(bmachine_static_checks,Msg,DKS,Info).
726 check_is_partial_function(_,_,_).
727
728 % ---------------------
729 % Checks if some identifiers are not used / are useless
730 % ---------------------
731
732 :- use_module(bmachine,[b_is_unused_constant/1, get_constant_span/2]).
733 check_unused_ids :-
734 b_is_unused_constant(ID),
735 get_constant_span(ID,Span),
736 % TO DO: check if the constant is used to define other used constants
737 (get_preference(filter_unused_constants,true)
738 -> add_message(bmachine_static_checks,'This constant is not used outside of the PROPERTIES/axioms (and is filtered away because FILTER_UNUSED preference is TRUE): ',ID,Span)
739 ; add_message(bmachine_static_checks,'This constant is not used outside of the PROPERTIES/axioms: ',ID,Span)
740 ),
741 fail.
742 check_unused_ids.