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 get_section(operation_bodies,Machine,Operations),
159 maplist(op_name_clashes(SortedAllIds),Operations),
160 get_section(definitions,Machine,Defs),
161 maplist(def_name_clashes(SortedAllIds),Defs).
162
163 get_all_machine_ids(Machine,SortedAllIds) :-
164 % get all variables and constants that might clash
165 get_section_ids(abstract_variables,Machine,'variable',AbsVars),
166 get_section_ids(concrete_variables,Machine,'variable',ConcVars),
167 get_section_ids(abstract_constants,Machine,'constant',AbsCons),
168 get_section_ids(concrete_constants,Machine,'constant',ConcCons),
169 get_section_ids(deferred_sets,Machine,'set',DefSets),
170 get_section_ids(enumerated_sets,Machine,'set',EnumSets),
171 get_section_ids(enumerated_elements,Machine,'set element',EnumElems),
172 % enumerated b_get_named_machine_set(GlobalSetName,ListOfConstants) + b_get_machine_set(S)
173 append([AbsVars,ConcVars,AbsCons,ConcCons,DefSets,EnumSets,EnumElems],AllIds),
174 keyword_clash(AllIds),
175 sort(AllIds,SortedAllIds).
176
177 get_section_ids(Section,Machine,Class,ResultList) :-
178 get_section(Section,Machine,Vars),
179 findall(machine_id(ID,Class,Pos,Section),
180 (member(TID,Vars),get_texpr_id(TID,ID),get_texpr_info(TID,Pos)),ResultList).
181
182
183 :- use_module(tools_matching,[is_b_keyword/2]).
184
185 % can be useful for Z, TLA+, Event-B machines:
186 % Clashes may lead to strange type or parse errors in VisB, REPL, ...
187 keyword_clash(AllIds) :-
188 ? member(machine_id(Name,Class,Pos,Section),AllIds),
189 is_b_keyword(Name,_),
190 get_descr(Pos,Section,Descr),
191 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),
192 (classical_b_mode
193 -> add_message(bmachine_static_checks,Msg,'',Pos) % user probably uses new backquote syntax already
194 ; add_warning(bmachine_static_checks,Msg,'',Pos)
195 ),
196 fail.
197 keyword_clash(_).
198
199 :- use_module(bsyntaxtree,[map_over_typed_bexpr/2]).
200 :- use_module(external_functions,[is_external_function_name/1]).
201
202 def_name_clashes(AllIds,definition_decl(Name,DefType,_DefPos,Args,_RawExpr,_Deps)) :- !,
203 (is_external_function_name(Name)
204 -> true % do not check external predicates, functions, subst
205 % they are not written by user and possibly not used and definitions are virtual and not used anyway
206 ; findall(b(identifier(ID),any,[nodeid(IdPos)]),
207 member(identifier(IdPos,ID),Args),ArgIds), % args can sometimes not be identifiers; see test 1711
208 debug_println(4,checking_def(Name,DefType,Args,ArgIds)),
209 include(clash_warnings('DEFINITION parameter',AllIds,'DEFINITION',Name),ArgIds,_ArgsCausingWarning)
210 % TO DO: check Body; for this we need a map_over_raw_expression to detect local variables introduced !
211 ).
212 def_name_clashes(_,D) :- print(unknown_def(D)),nl.
213
214 :- use_module(preferences,[get_preference/2]).
215 op_name_clashes(AllIds,Operation) :-
216 get_texpr_expr(Operation,operation(IdFull,Results,Params,Subst)),
217 %get_texpr_id(IdFull,op(Id)),
218 IdFull = b(identifier(op(Id)),Type,Info),
219 (get_preference(clash_strict_checks,true)
220 -> include(clash_warnings('Operation name',AllIds,operation,Id),[b(identifier(Id),Type,Info)],_NameCausingWarning) % fix PROB-60
221 ; true),
222 include(clash_warnings('Operation parameter',AllIds,operation,Id),Params,_ParamsCausingWarning),
223 include(clash_warnings('Operation result variable',AllIds,operation,Id),Results,_ResultsCausingWarning),
224 (map_over_typed_bexpr(bmachine_static_checks:check_operation_body_clashes(AllIds,Id),Subst),fail ; true).
225
226 :- public check_operation_body_clashes/3.
227 check_operation_body_clashes(AllIds,Operation,TSubst) :-
228 get_texpr_expr(TSubst,Subst),
229 (local_variable_clash(Subst,TSubst,AllIds,Operation);
230 illegal_op_call(Subst,Operation)).
231
232 local_variable_clash(Subst,TSubst,AllIds,Operation) :-
233 introduces_local_variable(Subst,ID),
234 clash_local_warnings(AllIds,Operation,ID,TSubst).
235
236 % check if something like zz(1) <-- Op(a) is used; this is not allowed according to Atelier-B
237 illegal_op_call(operation_call(CalledOperation,Results,_Parameters),Operation) :-
238 member(TID,Results), \+ get_texpr_id(TID,_),
239 (get_texpr_id(CalledOperation,op(CalledId)) -> true ; CalledId=CalledOperation),
240 ajoin(['Return value of operation call to ',CalledId,' must be stored in identifier within:'],Msg),
241 add_error(bmachine_static_checks,Msg,Operation,TID).
242
243 % check for constructs which introduced local variables
244 introduces_local_variable(var(Parameters,_),ID) :-
245 % currently B-interpreter cannot correctly deal with this in the context of operation_call
246 member(TID,Parameters), get_texpr_id(TID,ID).
247
248 :- use_module(probsrc(error_manager),[extract_span_description/2]).
249 % ord_member does not work below because of free variable Class
250 my_ord_member(Name,Class,Descr,[machine_id(Name1,_,_,_)|T]) :-
251 Name @> Name1, !,
252 my_ord_member(Name,Class,Descr,T).
253 my_ord_member(Name,Class,Descr,[machine_id(Name,Class,Pos,Section)|_]) :-
254 get_descr(Pos,Section,Descr).
255
256 get_descr(Pos,Section,Descr) :-
257 (extract_span_description(Pos,Descr) -> true
258 ; ajoin(['from section ',Section],Descr) ).
259
260 clash_warnings(Context,AllIds,OpOrDef,OperationId,TName) :-
261 get_texpr_id(TName,Name),
262 my_ord_member(Name,Class,Descr,AllIds), !,
263 ajoin(['The ', Class, ' "', Name, '" (', Descr,') has the same name as a ',
264 Context, ' in ',OpOrDef,' "', OperationId,'".'], Msg),
265 add_warning(bmachine_static_checks,Msg,'',TName).
266
267 clash_local_warnings(AllIds,OperationId,Name,Pos) :-
268 my_ord_member(Name,Class,Descr,AllIds), !,
269 % we could check and see if Name is really visible from this location!
270 % (see public_examples/B/Other/LustreTranslations/UMS_verif/M_UMS_verif.mch)
271 ajoin(['The ', Class, ' "', Name, '" (', Descr,') has the same name as a local variable in operation "', OperationId,'".'], Msg),
272 add_warning(bmachine_static_checks,Msg,'',Pos).
273
274 % TODO: this does not and can not work here:
275 % - Some preconditions are removed (typing only....) during machine simplification
276 % - Needs to be verified during typechecking
277 % ---------------------
278 % Checks if an operations parameter is not typed by a pre condition
279 % ---------------------
280 %parameters_without_pre_condition(Machine) :-
281 % get_section(operation_bodies,Machine,Operations),
282 % maplist(parameters_without_pre_condition_aux,Operations).
283 %
284 %parameters_without_pre_condition_aux(Operation) :-trace,
285 % get_texpr_expr(Operation,operation(IdFull,_Results,Params,Subst)),
286 % get_texpr_id(IdFull,op(Id)),
287 % (Params == []
288 % -> true % no parameters
289 % ; (get_texpr_expr(Subst,precondition(_,_))
290 % -> true % parameters and precondition
291 % ; ajoin(['Operation ', Id, ' has parameters but no pre-condition.'], Msg),
292 % add_warning(bmachine_static_checks,Msg)
293 % )).
294
295
296 % EXTENDED ADDITIONAL CHECKS
297 % these are run (optionally) after machine is loaded and bmachine pre-calculations have run
298
299 :- use_module(b_read_write_info,[check_all_variables_written/0]).
300
301 extended_static_check_machine :-
302 extended_static_check_machine(_).
303 extended_static_check_machine(Check) :-
304 reset_static_check,
305 esc_step(Check).
306
307 :- use_module(probsrc(bmachine), [b_get_main_filename/1, b_machine_name/1, b_get_machine_header_position/2]).
308 :- use_module(tools,[get_modulename_filename/2, get_filename_extension/2]).
309 :- use_module(bsyntaxtree,[map_over_typed_bexpr_with_names/2]).
310 :- use_module(specfile,[get_specification_description/2, animation_minor_mode/1, classical_b_mode/0]).
311 :- use_module(bmachine).
312 :- use_module(b_machine_hierarchy,[machine_type/2, machine_operations/2]).
313
314 esc_step(variables) :-
315 check_all_variables_written,
316 fail.
317 esc_step(machine_name) :-
318 b_machine_name(Name),
319 b_get_main_filename(Filename),
320 get_modulename_filename(Filename,ModuleName),
321 Name \= ModuleName,
322 (atom_concat('MAIN_MACHINE_FOR_',RealName,Name) % see dummy_machine_name in bmachine_construction
323 -> RealName \= ModuleName
324 ; is_dummy_machine_name(Name,Filename)
325 -> fail % dummy Rules DSL or Alloy machine name, do not create warning
326 ; true
327 ),
328 (b_get_machine_header_position(Name,Span) -> true
329 ; Span = src_position_with_filename(1,1,1,Filename)),
330 get_specification_description(machine,MCH),
331 ajoin(['Filename ',ModuleName,' does not match name of ', MCH, ': '],Msg),
332 add_warning(bmachine_static_checks,Msg,Name,Span),
333 fail.
334 esc_step(variables) :- debug_println(19,checking_identifiers_for_clashes),
335 full_b_machine(Machine),
336 local_quantified_variable_clashes(Machine),
337 fail.
338 esc_step(operations) :- machine_type(MachName,abstract_machine),
339 machine_operations(MachName,Ops),
340 member(identifier(Span,OpName),Ops),
341 atom_codes(OpName,Codes), member(46,Codes), % "." element of name
342 % Section 7.23, paragraph 2 of Atelier-B handbook: in abstract machine we cannot use renamed operation names
343 ajoin(['Operation name in abstract machine ',MachName,' is composed: '],Msg),
344 add_warning(bmachine_static_checks,Msg,OpName,Span),
345 fail.
346 esc_step(operations) :- debug_println(19,checking_operation_bodies),
347 b_get_machine_operation(ID,_Res,_TParas,Body,_OType,_Pos),
348 check_operation_body(Body,ID),
349 fail.
350 esc_step(operations) :- portray_constant_expr_summary, fail.
351 esc_step(constants) :-
352 check_concrete_constants, % check concrete_constants(.) states
353 fail.
354 esc_step(unused_ids) :-
355 check_unused_ids,
356 fail.
357 esc_step(_).
358
359 :- use_module(probsrc(tools_strings), [atom_prefix/2]).
360 is_dummy_machine_name(Name,_) :- atom_prefix('__RULES_MACHINE_Main',Name).
361 is_dummy_machine_name(alloytranslation,_) :- animation_minor_mode(alloy).
362 is_dummy_machine_name('DEFINITION_FILE',Filename) :- classical_b_mode,
363 get_filename_extension(Filename,'def').
364 % when opening .def files; WARNING: sometimes def files use other extensions
365
366 % TO DO: optionally do these kinds of checks in the REPL
367 local_quantified_variable_clashes(Machine) :-
368 get_all_machine_ids(Machine,SortedAllIds),
369 (get_typed_section(Sec,SecID,Pred),
370 debug_println(19,checking_local_quantified_variable_clashes(Sec,SecID)),
371 map_over_typed_bexpr_with_names(bmachine_static_checks:check_introduced_ids(Sec,SecID,SortedAllIds),Pred)
372 ;
373 check_definition_clashes(SortedAllIds)
374 ),
375 fail.
376 local_quantified_variable_clashes(_).
377
378 :- use_module(bmachine,[b_get_typed_definition/3]).
379 check_definition_clashes(SortedAllIds) :-
380 Scope=[variables],
381 b_get_typed_definition(Name,Scope,TExpr),
382 % TODO: adapt type_check_definitions to return definitions with paras and add paras to list of Ids
383 %print(check(Name,SortedAllIds)),nl,
384 map_over_typed_bexpr_with_names(bmachine_static_checks:check_introduced_ids('DEFINITION',Name,SortedAllIds),TExpr).
385
386 :- public check_introduced_ids/5. % used in map above
387 check_introduced_ids(Section,SectionID,SortedAllIds,TExpr,TNames) :-
388 \+ ignore_constructor(TExpr),
389 (member(TName,TNames),
390 clash_warnings('local variable',SortedAllIds,Section,SectionID,TName)
391 ; removed_identifier(TExpr,TName),
392 clash_warnings('(removed) local variable',SortedAllIds,Section,SectionID,TName)
393 % TODO: also check duplicate_id_hides info fields ?
394 ).
395
396 % detect some identifiers that were removed
397 removed_identifier(b(_,_,Infos),TId) :-
398 member(was(WAS),Infos),
399 introduced_ids(WAS,IDs),
400 % this happens in EnumSetClash2.mch
401 member(TId,IDs).
402
403 introduced_ids(forall(IDs,_,_),IDs).
404 introduced_ids(exists(IDs,_),IDs). % % are there more relevant cases? TODO: use syntaxtraversion
405
406 % ignore certain constructs, which do not really introduce a new identifier:
407 ignore_constructor(b(recursive_let(_,_),_,_)).
408
409
410 get_typed_section(Kind,Name,SubPred) :-
411 b_get_properties_from_machine(P), get_specification_description(properties,PS),
412 get_sub_predicate(PS,P,Kind,Name,SubPred).
413 get_typed_section(Kind,Name,SubPred) :-
414 b_get_invariant_from_machine(P), get_specification_description(invariants,PS),
415 get_sub_predicate(PS,P,Kind,Name,SubPred).
416 get_typed_section(Kind,Name,SubPred) :-
417 get_specification_description(assertions,APS), ajoin(['(dynamic) ',APS],AS),
418 b_get_dynamic_assertions_from_machine(Ps),
419 l_get_sub_predicate(AS,Ps,Kind,Name,SubPred).
420 get_typed_section(Kind,Name,SubPred) :-
421 get_specification_description(assertions,APS), ajoin(['(static) ',APS],AS),
422 b_get_static_assertions_from_machine(Ps),
423 l_get_sub_predicate(AS,Ps,Kind,Name,SubPred).
424 get_typed_section(operation,OpName,P) :- b_get_machine_operation(OpName,_Results,_Parameters,P).
425 % TO DO: add more sections: constraints, DEFINITION bodies
426
427
428 :- use_module(bsyntaxtree, [conjunction_to_list/2, get_texpr_label/2]).
429 get_sub_predicate(Clause,Pred,Kind,Name,SubPred) :-
430 conjunction_to_list(Pred,Preds),
431 l_get_sub_predicate(Clause,Preds,Kind,Name,SubPred).
432 l_get_sub_predicate(Clause,Preds,Kind,Name,SubPred) :-
433 member(SubPred,Preds),
434 (get_texpr_label(SubPred,Label)
435 -> Kind = predicate, ajoin([Label,'" in clause "',Clause],Name)
436 ; Kind = clause, Name=Clause).
437
438
439 % -------------------------
440
441 % check for reading uninitialised variables
442
443 :- use_module(bmachine, [b_top_level_operation/1, b_top_level_feasible_operation/1, b_is_constant/1]).
444 check_operation_body(Body,OpID) :- b_top_level_operation(OpID),
445 \+ b_top_level_feasible_operation(OpID),
446 add_warning(bmachine_static_checks,'Infeasible body for operation:',OpID,Body),
447 fail.
448 check_operation_body(Body,OpID) :-
449 map_over_typed_bexpr(bmachine_static_checks:check_operation_body_var(OpID),Body),
450 fail.
451 check_operation_body(Body,OpID) :-
452 check_for_constant_expressions(Body,OpID,[],_,_).
453
454 check_operation_body_var(OpID,b(var(Parameters,Body),subst,_Pos)) :-
455 get_texpr_ids(Parameters,Ps), sort(Ps,Uninitialised),
456 get_accessed_vars(Body,[],_Modifies,Reads),
457 ord_intersection(Uninitialised,Reads,URead),
458 URead \= [],
459 member(TID,Parameters), get_texpr_id(TID,ID),
460 member(ID,URead),
461 ajoin(['Possibly reading uninitialised variable in operation ',OpID,' : '],Msg),
462 add_warning(bmachine_static_checks,Msg,ID,TID).
463 % TO DO: we could pinpoint more precisely where the read occurs
464
465 % locate potentially expensive fully constant expressions (depending only on B constants)
466 % which are in a dynamic context (operations) and which may be recomputed many times
467 % 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)
468 :- use_module(error_manager,[get_tk_table_position_info/2]).
469 :- use_module(translate,[translate_bexpression_with_limit/3]).
470 find_constant_expressions_in_operations(List) :-
471 find_constant_expressions_in_operations(create_messages,List).
472
473 find_constant_expressions_in_operations(MsgOrWarn,_) :-
474 reset_static_check,
475 assert(store_expr(MsgOrWarn)),
476 b_get_machine_operation(OpID,_Res,_TParas,Body,_OType,_Pos),
477 check_for_constant_expressions(Body,OpID,[],_,_),
478 fail.
479 find_constant_expressions_in_operations(_,list([Header|List])) :-
480 Header = ['Operation', 'Occurence', 'Expr', 'LocalIds', 'Source'],
481 portray_constant_expr_summary,
482 findall(list([OpID,Count,ES,LocalIds,Src]),
483 (const_expr(OpID,Count,BExpr,LocalIds),
484 translate_bexpression_with_limit(BExpr,100,ES),
485 get_tk_table_position_info(BExpr,Src)), List),
486 reset_static_check.
487
488 :- use_module(bsyntaxtree,[syntaxtraversion/6]).
489 check_for_constant_expressions(BExpr,OpID,LocalIds,IsConstant,IsExpensive) :-
490 syntaxtraversion(BExpr,Expr,Type,_Infos,Subs,TNames),
491 (get_texpr_ids(TNames,QuantifiedNewIds), list_to_ord_set(QuantifiedNewIds,SQuantifiedNewIds)
492 -> ord_union(LocalIds,SQuantifiedNewIds,NewLocalIds)
493 ; write(err(TNames)),nl, NewLocalIds = LocalIds),
494 l_check_for_const(Subs,OpID,NewLocalIds,SQuantifiedNewIds,AreAllConstant,IsExpensive1,ExpensiveList),
495 %translate:print_bexpr_or_subst(BExpr),nl, write(cst(AreAllConstant,IsExpensive1,LocalIds)),nl,nl,
496 (AreAllConstant=is_constant,
497 is_constant_expression(Expr,LocalIds,IsExpensive2)
498 -> IsConstant = is_constant,
499 combine_expensive(IsExpensive1,IsExpensive2,IsExpensive),
500 (pred_or_subst(Type) % we may traverse the boundary of expressions to pred/subst: print expressions
501 -> maplist(add_const_expr_msg(OpID,LocalIds),ExpensiveList)
502 ; true)
503 ; IsConstant = not_constant, IsExpensive=not_expensive,
504 maplist(add_const_expr_msg(OpID,LocalIds),ExpensiveList)
505 ).
506
507
508 :- use_module(probsrc(b_interpreter_check),[norm_check/2]).
509 :- use_module(probsrc(hashing),[my_term_hash/2]).
510 :- use_module(probsrc(tools), [ajoin_with_sep/3]).
511 add_const_expr_msg(OpID,LocalIds,BExpr) :-
512 (LocalIds=[] -> debug:debug_mode(on) ; true), % decide whether to show outer-level constant expressions
513 register_constant_expression(BExpr,OpID,LocalIds,Count,MsgOrWarn),
514 (LocalIds = [] -> LMsg=[': ']
515 ; length(LocalIds,Len),
516 (Len =< 3
517 -> ajoin_with_sep(LocalIds,',',LIDS),
518 LMsg = [' inside quantification (',LIDS,'): ']
519 ; prefix_length(LocalIds,Prefix,2),
520 last(LocalIds,LastId),
521 ajoin_with_sep(Prefix,',',LIDS),
522 LMsg = [' inside quantification (',Len,' ids: ',LIDS,',...,',LastId,'): ']
523 )
524 ),
525 (Count > 1
526 -> ajoin(['Repeated constant expression in operation ',OpID,
527 ' (occurence nr. ',Count,', consider lifting it out)'|LMsg],Msg)
528 ; ajoin(['Non-trivial constant expression in operation ',OpID,' (consider lifting it out)'|LMsg],Msg)
529 ),
530 (MsgOrWarn=create_messages
531 -> add_message(b_machine_static_checks,Msg,BExpr,BExpr)
532 ; add_warning(b_machine_static_checks,Msg,BExpr,BExpr)).
533
534 :- dynamic store_expr/1, const_expr_hash_count/2, const_expr/4.
535 reset_static_check :- retractall(const_expr_hash_count(_,_)),
536 retractall(const_expr(_,_,_,_)), retractall(store_expr(_)).
537 register_constant_expression(BExpr,OpID,LocalIds,Count,MsgOrWarn) :-
538 norm_check(BExpr,Norm), my_term_hash(Norm,Hash),
539 (retract(const_expr_hash_count(Hash,C)) -> Count is C+1 ; Count=1),
540 assert(const_expr_hash_count(Hash,Count)),
541 (store_expr(MsgOrWarn) -> assert(const_expr(OpID,Count,BExpr,LocalIds)) ; MsgOrWarn=message).
542
543 portray_constant_expr_summary :-
544 findall(Count,const_expr_hash_count(_,Count),LC),
545 length(LC,Len),
546 format('Constant expressions found: ~w~n',[Len]),
547 sumlist(LC,Total),
548 format('Total # of occurrences: ~w~n',[Total]).
549
550
551 l_check_for_const([],_OpID,_,_,is_constant,not_expensive,[]).
552 l_check_for_const([H|T],OpID,LocalIds,NewQuantLocalIds,AreAllConstant,IsExpensive,ExpResList) :-
553 check_for_constant_expressions(H,OpID,LocalIds,IsConstant,IsExpensive0),
554 project_is_expensive(IsExpensive0,NewQuantLocalIds,IsExpensive1), % project on NewIds introduced at top-level
555 (IsConstant=is_constant
556 -> (IsExpensive1=is_expensive, is_expression(H) % currently we only look for constant expressions
557 -> ExpResList = [H|TR] % add to list for add_const_expr_msg
558 ; ExpResList=TR),
559 l_check_for_const(T,OpID,LocalIds,NewQuantLocalIds,AreAllConstant,IsExpensive2,TR),
560 combine_expensive(IsExpensive1,IsExpensive2,IsExpensive)
561 ; AreAllConstant=not_constant, IsExpensive=not_expensive,
562 l_check_for_const(T,OpID,LocalIds,NewQuantLocalIds,_,_,ExpResList)).
563
564 is_expression(b(_,T,_)) :- \+ pred_or_subst(T).
565
566 pred_or_subst(pred).
567 pred_or_subst(subst).
568
569 % operators on abstract expensive/local_id domain:
570 combine_expensive(not_expensive,IsExpensive2,IsExpensive) :- !, IsExpensive=IsExpensive2.
571 combine_expensive(is_expensive,depends_on_local_ids(Ids,_),Res) :- !,
572 Res=depends_on_local_ids(Ids,is_expensive).
573 combine_expensive(is_expensive,_,IsExpensive) :- !, IsExpensive=is_expensive.
574 combine_expensive(depends_on_local_ids(Ids1,IE1),depends_on_local_ids(Ids2,IE2),Res) :- !,
575 combine_expensive(IE1,IE2,IE),
576 ord_union(Ids1,Ids2,Ids), Res=depends_on_local_ids(Ids,IE).
577 combine_expensive(depends_on_local_ids(Ids,IE1),IE2,Res) :- !,
578 combine_expensive(IE1,IE2,IE),
579 Res=depends_on_local_ids(Ids,IE).
580 combine_expensive(A,B,C) :- write(combine_expensive_uncovered(A,B,C)),nl,nl,C=A.
581
582 % project is_expensive domain value after leaving SQuantifiedNewIds
583 project_is_expensive(depends_on_local_ids(LocalIds,IE),SQuantifiedNewIds,Res) :- !,
584 ord_subtract(LocalIds,SQuantifiedNewIds,NewLocalIds),
585 (NewLocalIds = [] -> Res = IE % is_expensive/not_expensive
586 ; Res= depends_on_local_ids(NewLocalIds,IE)).
587 project_is_expensive(IE,_,IE).
588
589 :- use_module(probsrc(b_global_sets),[lookup_global_constant/2]).
590 % check if based on top-level the expression is constant, assuming all args are constant
591 is_constant_expression(identifier(ID),LocalIds,Kind) :- !,
592 (ord_member(ID,LocalIds) -> Kind = depends_on_local_ids([ID],not_expensive)
593 ; b_is_constant(ID) -> Kind = not_expensive
594 ; b_get_machine_set(ID) -> Kind = not_expensive
595 ; lookup_global_constant(ID,_) -> Kind = not_expensive). % enumerated set element
596 is_constant_expression(Expr,_,IsExpensive) :- is_constant_expression(Expr,IsExpensive).
597
598 is_constant_expression(value(_),not_expensive).
599 % literals:
600 is_constant_expression(max_int,not_expensive).
601 is_constant_expression(min_int,not_expensive).
602 is_constant_expression(boolean_false,not_expensive).
603 is_constant_expression(boolean_true,not_expensive).
604 is_constant_expression(empty_set,not_expensive).
605 is_constant_expression(empty_sequence,not_expensive).
606 is_constant_expression(integer(_),not_expensive).
607 is_constant_expression(real(_),not_expensive).
608 is_constant_expression(string(_),not_expensive).
609
610 is_constant_expression(bool_set,not_expensive).
611 is_constant_expression(freetype_set(_),not_expensive).
612 is_constant_expression(real_set,not_expensive).
613 is_constant_expression(string_set,not_expensive).
614 is_constant_expression(typeset,not_expensive).
615 is_constant_expression(integer_set(_),not_expensive).
616 % simple arithmetic:
617 is_constant_expression(unary_minus(_),not_expensive).
618 is_constant_expression(add(_,_),not_expensive).
619 is_constant_expression(minus(_,_),not_expensive).
620 is_constant_expression(multiplication(_,_),not_expensive).
621 % just constructing values:
622 is_constant_expression(couple(_,_),not_expensive).
623 is_constant_expression(record_field(_,_),not_expensive).
624 is_constant_expression(rec(_),not_expensive).
625 is_constant_expression(set_extension(_),not_expensive).
626 is_constant_expression(sequence_extension(_),not_expensive).
627 is_constant_expression(interval(_,_),not_expensive).
628 % simple deconstructing
629 is_constant_expression(first_of_pair(_),not_expensive).
630 is_constant_expression(second_of_pair(_),not_expensive).
631 is_constant_expression(record_field(_,_),not_expensive).
632 % just typing
633 % TODO: return typing instead of not_expensive as result and check context used later
634 is_constant_expression(pow_subset(_),not_expensive).
635 is_constant_expression(fin_subset(_),not_expensive).
636 is_constant_expression(pow1_subset(_),not_expensive).
637 is_constant_expression(fin1_subset(_),not_expensive).
638 is_constant_expression(seq(_),not_expensive).
639 is_constant_expression(seq1(_),not_expensive).
640 is_constant_expression(iseq(_),not_expensive).
641 is_constant_expression(iseq1(_),not_expensive).
642 is_constant_expression(cartesian_product(_,_),not_expensive).
643 is_constant_expression(mult_or_cart(_,_),not_expensive).
644 is_constant_expression(relations(_,_),not_expensive).
645 is_constant_expression(relations(_,_),not_expensive).
646 is_constant_expression(struct(_),not_expensive).
647 % TO DO: card : trivial if avl_set; maybe we should do this after constants_analysis
648 % TODO: detect some external function calls as not constant: RANDOM, ...
649
650 %is_constant_expression(domain(_),not_expensive). % is not expensive for symbolic lambas, and reasonable for avl_set
651 is_constant_expression(composition(_,_),is_expensive).
652 is_constant_expression(image(_,_),is_expensive).
653 is_constant_expression(iteration(_,_),is_expensive).
654 is_constant_expression(concat(_,_),is_expensive).
655 is_constant_expression(reflexive_closure(_),is_expensive).
656 is_constant_expression(closure(_),is_expensive).
657 is_constant_expression(union(_,_),is_expensive).
658 is_constant_expression(intersection(_,_),is_expensive).
659
660 is_constant_expression(_,is_expensive).
661
662 % ---------------------------
663
664 % perform some checks on symbolic values; look for obvious WD errors
665
666 :- use_module(probsrc(bsyntaxtree),[map_over_typed_bexpr/2]).
667 :- use_module(probsrc(state_space),[is_concrete_constants_state_id/1, visited_expression/2]).
668 :- use_module(probsrc(specfile),[state_corresponds_to_set_up_constants/2]).
669
670 check_concrete_constants :- is_concrete_constants_state_id(ID),!,
671 check_values_in_state(ID).
672 check_concrete_constants.
673
674 check_values_in_state(ID) :- debug_format(19,'Checking values in state with id = ~w~n',[ID]),
675 visited_expression(ID,State),
676 state_corresponds_to_set_up_constants(State,EState),
677 member(bind(VarID,Value),EState),
678 check_symbolic_values(Value,VarID),
679 fail.
680 check_values_in_state(_).
681
682 :- use_module(debug,[debug_println/2, debug_format/3]).
683 :- use_module(error_manager,[add_error/3]).
684
685 check_symbolic_values(Var,Ctxt) :- var(Var),!,
686 add_error(bmachine_static_checks,'Value is a variable',Ctxt).
687 check_symbolic_values(closure(_,_,Body),Ctxt) :- !,
688 debug_format(19,'Checking symbolic value for ~w~n',[Ctxt]),
689 map_over_typed_bexpr(check_symbolic_value(Ctxt),Body).
690 check_symbolic_values(_,_).
691
692 check_symbolic_value(Ctxt,b(E,T,I)) :- check_aux(E,T,I,Ctxt).
693
694 check_aux(function(b(Func,_,_I1),_Arg),_,I2,Ctxt) :- % _I1 sometimes unknown
695 % TO DO: check if Info contains WD flag
696 check_is_partial_function(Func,I2,Ctxt).
697 % TO DO: check sequence operators, ...
698
699 :- use_module(custom_explicit_sets,[is_not_avl_partial_function/2]).
700 :- use_module(library(avl),[avl_size/2]).
701 :- use_module(probsrc(translate), [translate_bvalue_with_limit/3]).
702 check_is_partial_function(value(Val),Info,Ctxt) :- nonvar(Val), Val=avl_set(AVL),
703 is_not_avl_partial_function(AVL,DuplicateKey),!,
704 avl_size(AVL,Size),
705 translate_bvalue_with_limit(DuplicateKey,80,DKS),
706 ajoin(['Relation of size ', Size, ' is not a function (value for ',Ctxt, '); duplicate key: '],Msg),
707 add_warning(bmachine_static_checks,Msg,DKS,Info).
708 check_is_partial_function(_,_,_).
709
710 % ---------------------
711 % Checks if some identifiers are not used / are useless
712 % ---------------------
713
714 :- use_module(bmachine,[b_is_unused_constant/1, get_constant_span/2]).
715 check_unused_ids :-
716 b_is_unused_constant(ID),
717 get_constant_span(ID,Span),
718 % TO DO: check if the constant is used to define other used constants
719 (get_preference(filter_unused_constants,true)
720 -> 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)
721 ; add_message(bmachine_static_checks,'This constant is not used outside of the PROPERTIES/axioms: ',ID,Span)
722 ),
723 fail.
724 check_unused_ids.