1 % (c) 2013-2019 Lehrstuhl fuer Softwaretechnik und Programmiersprachen,
2 % Heinrich Heine Universitaet Duesseldorf
3 % This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html)
4
5 :- module(bmachine_static_checks, [static_check_main_machine/1, toplevel_raw_predicate_sanity_check/4]).
6
7 :- use_module(probsrc(module_information)).
8 :- module_info(group,typechecker).
9 :- module_info(description,'Static checking of B machines upon construction. Detection of name clashes and uninitalised variables.').
10
11 :- use_module(bmachine_structure, [get_section/3]).
12 :- use_module(b_read_write_info, [get_accessed_vars/4]).
13 :- use_module(bsyntaxtree, [get_texpr_expr/2,get_texpr_id/2,get_texpr_ids/2]).
14 :- use_module(error_manager, [add_warning/3, add_warning/4, add_error/4]).
15 :- use_module(debug, [debug_println/2]).
16 :- use_module(tools, [ajoin/2]).
17 :- use_module(library(lists)).
18
19 static_check_main_machine(Machine) :-
20 debug_println(19,"Running static machine checks"),
21 initialises_all_variables(Machine),
22 check_name_clashes(Machine).
23
24
25 % ---------------------------------------------
26 % check for unnatural invariants and properties
27 % ---------------------------------------------
28 % the priority of the implication is sometimes surprising
29 % a & (x=1) => (y=2) & c --> top-level symbol is the implication !
30 % the test is done on the raw predicate before ast_cleanup removes potential typing predicates; see tests 106, 107
31 toplevel_raw_predicate_sanity_check(invariant,MachName,RawPredicate,Infos) :-
32 ? member(has_variables,Infos), % otherwise it is ok to have just a single disjunt, implication,...
33 check_top_level(RawPredicate,MachName,'INVARIANT'),
34 fail.
35 toplevel_raw_predicate_sanity_check(properties,MachName,RawPredicate,Infos) :-
36 member(has_constants,Infos), % otherwise it is ok to have just a single disjunt, implication,...
37 check_top_level(RawPredicate,MachName,'PROPERTIES'),
38 fail.
39 toplevel_raw_predicate_sanity_check(_,_,_,_).
40
41 % typical error: forget parentheses; if we write P => Q & R --> this is parsed as P => (Q & R)
42 check_top_level(implication(_,B,C),MachName,SECT) :-
43 (composed_predicate(B) ; composed_predicate(C)),!,
44 add_warning(bmachine_static_checks,'Top-level implication (=>) in clause: ',MachName:SECT).
45 check_top_level(disjunct(_,B,C),MachName,SECT) :-
46 (composed_predicate(B) ; composed_predicate(C)),!,
47 add_warning(bmachine_static_checks,'Top-level disjunction (or) in clause: ',MachName:SECT).
48 check_top_level(equivalence(_,B,C),MachName,SECT) :-
49 (composed_predicate(B) ; composed_predicate(C)),!,
50 add_warning(bmachine_static_checks,'Top-level equivalence (<=>) in clause: ',MachName:SECT).
51
52 % check if a predicate is composed of a binary operator where confusion may arise
53 % with negation and quantification there are explicit parentheses; the user should not be confused
54 composed_predicate(A) :- functor(A,Operator,3),
55 boolean_operator(Operator).
56 boolean_operator(conjunct).
57 boolean_operator(disjunct).
58 boolean_operator(implication).
59 boolean_operator(equivalence).
60
61 % ---------------------
62 % Checks is all variables are initialised by the machine
63 % ---------------------
64 initialises_all_variables(Machine) :-
65 get_section(initialisation,Machine,Initialisation),
66 % check wich variables are read / modified by the initialisation
67 get_accessed_vars(Initialisation,[],Modifies,_Reads),
68 get_machine_variables(Machine,SortedAllVars),
69 % check for each variable, if the initialisation modifies it
70 exclude(is_initialised(Modifies),SortedAllVars,Uninitialised),
71 % generate a warning if unitialised is not empty
72 generate_uninitialised_warning(Uninitialised,Initialisation),
73 % now check order of initialisation sequences
74 check_initialisation_order(Initialisation,SortedAllVars,[],_).
75
76 get_machine_variables(Machine,SortedAllVars) :-
77 % get all variables that should be initialised
78 get_section(abstract_variables,Machine,AbsVars),
79 get_section(concrete_variables,Machine,ConcVars),
80 append(AbsVars,ConcVars,TAllVars),
81 get_texpr_ids(TAllVars,AllVars),
82 sort(AllVars,SortedAllVars).
83
84 is_initialised(Modifies,Id) :-
85 memberchk(Id,Modifies).
86
87 generate_uninitialised_warning([],_) :- !.
88 generate_uninitialised_warning(Vars,Initialisation) :-
89 Msg='Machine may not initialise some of its variables: ',
90 add_warning(bmachine_static_checks,Msg,Vars,Initialisation).
91
92
93 :- use_module(library(ordsets)).
94 % Check if order of sequential compositions in INITIALISATION is ok
95 % TO DO: add support for if(LIST) and while
96 check_initialisation_order(b(Subst,subst,Info),AllVars,AlreadyInit,OutInit) :-
97 check_initialisation_order2(Subst,Info,AllVars,AlreadyInit,OutInit),!.
98 check_initialisation_order(Subst,AllVars,AlreadyInit,OutInit) :-
99 % print('CHCK '), translate:print_subst(Subst),nl,
100 get_accessed_vars(Subst,[],Modifies,Reads),
101 check_subst_or_pred(Subst,Modifies,Reads,AllVars,AlreadyInit,OutInit).
102
103 check_subst_or_pred(Subst,Modifies,Reads,AllVars,AlreadyInit,OutInit) :-
104 ord_union(Modifies,AlreadyInit,OutInit), % after Subst we have initialised OutInit
105 % below is already checked by type checker:
106 %ord_subtract(Modifies,AllVars,IllegalAssignments),
107 %(IllegalAssignments=[] -> true
108 % ; add_warning(bmachine_static_checks,'INITIALISATION writes illegal variables: ',IllegalAssignments,Subst)),
109 ord_intersection(Reads,AllVars,ReadFromSameMachine),
110 (atomic_subst(Subst)
111 -> ord_subtract(ReadFromSameMachine,AlreadyInit,IllegalReads)
112 ; ord_subtract(ReadFromSameMachine,OutInit,IllegalReads) % we use OutInit: there could be an if with sequence inside
113 ),
114 (IllegalReads=[] -> true
115 ; add_warning(bmachine_static_checks,'INITIALISATION reads variables which are not yet initialised: ',IllegalReads,Subst)).
116
117 :- use_module(bsyntaxtree,[find_identifier_uses/3]).
118 check_initialisation_order2(choice([First|T]),Info,AllVars,AlreadyInit,OutInit) :- !,
119 check_initialisation_order(First,AllVars,AlreadyInit,OutInit), % we pick the output of the first choice
120 (T=[] -> true ; check_initialisation_order(b(choice(T),subst,Info),AllVars,AlreadyInit,_)).
121 check_initialisation_order2(parallel([First|T]),Info,AllVars,AlreadyInit,OutInit) :- !,
122 check_initialisation_order(First,AllVars,AlreadyInit,OutInit1), % we pick the output of the first choice
123 (T=[] -> OutInit=OutInit1
124 ; check_initialisation_order2(parallel(T),Info,AllVars,AlreadyInit,OutInitRest),
125 ord_union(OutInit1,OutInitRest,OutInit)
126 ).
127 check_initialisation_order2(init_parallel(S),Info,AllVars,AlreadyInit,OutInit) :- !,
128 check_initialisation_order(b(parallel(S),subst,Info),AllVars,AlreadyInit,OutInit).
129 check_initialisation_order2(sequence([First|T]),Info,AllVars) --> !,
130 check_initialisation_order(First,AllVars),
131 ({T=[]} -> [] ; check_initialisation_order(b(sequence(T),subst,Info),AllVars)).
132 check_initialisation_order2(any(_Ids,Pred,Subst),_Info,AllVars,AlreadyInit,OutInit) :- !,
133 find_identifier_uses(Pred,[],Reads),
134 check_subst_or_pred(Pred,[],Reads,AllVars,AlreadyInit,_), % check predicate reads are ok
135 check_initialisation_order(Subst,AllVars,AlreadyInit,OutInit).
136 % TO DO: also check if-then-else, while, ...
137
138 atomic_subst(b(S,_,_)) :- atomic_subst2(S).
139 atomic_subst2(skip).
140 atomic_subst2(assign(_,_)).
141 atomic_subst2(assign_single_id(_,_)).
142 atomic_subst2(becomes_element_of(_,_)).
143 %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)
144
145 % ---------------------
146 % Checks if an operations parameter or local variable clashes with a variable
147 % ---------------------
148 check_name_clashes(Machine) :-
149 debug_println(10,'Checking for name clashes'),
150 % get all variables and constants that might clash
151 get_section_ids(abstract_variables,Machine,'variable',AbsVars),
152 get_section_ids(concrete_variables,Machine,'variable',ConcVars),
153 get_section_ids(abstract_constants,Machine,'constant',AbsCons),
154 get_section_ids(concrete_constants,Machine,'constant',ConcCons),
155 get_section_ids(deferred_sets,Machine,'set',DefSets),
156 get_section_ids(enumerated_sets,Machine,'set',EnumSets),
157 get_section_ids(enumerated_elements,Machine,'set element',EnumElems),
158 % enumerated b_get_named_machine_set(GlobalSetName,ListOfConstants) + b_get_machine_set(S)
159 append([AbsVars,ConcVars,AbsCons,ConcCons,DefSets,EnumSets,EnumElems],AllIds),
160 sort(AllIds,SortedAllIds),
161 %print(SortedAllIds),nl,nl,
162 % for each operation, compare the parameter names with existing vars/constants
163 get_section(operation_bodies,Machine,Operations),
164 maplist(op_name_clashes(SortedAllIds),Operations),
165 get_section(definitions,Machine,Defs),
166 maplist(def_name_clashes(SortedAllIds),Defs).
167
168 get_section_ids(Section,Machine,Class,ResultList) :-
169 get_section(Section,Machine,Vars),
170 findall(machine_id(ID,Class),(member(TID,Vars),get_texpr_id(TID,ID)),ResultList).
171
172
173 :- use_module(bsyntaxtree,[map_over_typed_bexpr/2]).
174 :- use_module(external_functions,[is_external_function_name/1]).
175
176 def_name_clashes(AllIds,definition_decl(Name,DefType,_DefPos,Args,_RawExpr,_Deps)) :- !,
177 findall(b(identifier(ID),any,[nodeid(IdPos)]),
178 member(identifier(IdPos,ID),Args),ArgIds), % args can sometimes not be identifiers; see test 1711
179 debug_println(4,checking_def(Name,DefType,Args,ArgIds)),
180 % exclude external functions/predicates/...;
181 % EXTERNAL_ declarations do not seem to be included; TO DO: check whether external function actually declared
182 ? (is_external_function_name(Name) -> true
183 ; include(clash_warnings('DEFINITION parameter',AllIds,'DEFINITION',Name),ArgIds,_ArgsCausingWarning)
184 % TO DO: check Body; for this we need a map_over_raw_expression to detect local variables introduced !
185 ).
186 def_name_clashes(_,D) :- print(unknown_def(D)),nl.
187
188 :- use_module(preferences,[get_preference/2]).
189 op_name_clashes(AllIds,Operation) :-
190 get_texpr_expr(Operation,operation(IdFull,Results,Params,Subst)),
191 %get_texpr_id(IdFull,op(Id)),
192 IdFull = b(identifier(op(Id)),Type,Info),
193 (get_preference(clash_strict_checks,true)
194 -> include(clash_warnings('Operation name',AllIds,operation,Id),[b(identifier(Id),Type,Info)],_NameCausingWarning) % fix PROB-60
195 ; true),
196 include(clash_warnings('Operation parameter',AllIds,operation,Id),Params,_ParamsCausingWarning),
197 include(clash_warnings('Operation result variable',AllIds,operation,Id),Results,_ResultsCausingWarning),
198 ? (map_over_typed_bexpr(bmachine_static_checks:check_operation_body(AllIds,Id),Subst),fail ; true).
199
200 :- public check_operation_body/3.
201 check_operation_body(AllIds,Operation,TSubst) :-
202 ? get_texpr_expr(TSubst,Subst),
203 ? (local_variable_clash(Subst,TSubst,AllIds,Operation);
204 illegal_op_call(Subst,Operation)).
205
206 local_variable_clash(Subst,TSubst,AllIds,Operation) :-
207 ? introduces_local_variable(Subst,ID),
208 %print(check(ID,Operation)),nl,
209 clash_local_warnings(AllIds,Operation,ID,TSubst).
210
211 % check if something like zz(1) <-- Op(a) is used; this is not allowed according to Atelier-B
212 illegal_op_call(operation_call(CalledOperation,Results,_Parameters),Operation) :-
213 member(TID,Results), \+ get_texpr_id(TID,_),
214 (get_texpr_id(CalledOperation,op(CalledId)) -> true ; CalledId=CalledOperation),
215 ajoin(['Return value of operation call to ',CalledId,' must be stored in identifier within:'],Msg),
216 add_error(bmachine_static_checks,Msg,Operation,TID).
217
218 % check for constructs which introduced local variables
219 introduces_local_variable(var(Parameters,_),ID) :- % currently B-interpreter cannot correctly deal with this in the context of operation_call
220 ? member(TID,Parameters), get_texpr_id(TID,ID).
221
222 % ord_member does not work below because of free variable Class
223 my_ord_member(Name,Class,[machine_id(Name1,_)|T]) :-
224 Name @> Name1, !,
225 my_ord_member(Name,Class,T).
226 my_ord_member(Name,Class,[machine_id(Name,Class)|_]).
227
228 clash_warnings(Context,AllIds,OpOrDef,OperationId,TName) :-
229 get_texpr_id(TName,Name),
230 my_ord_member(Name,Class,AllIds), !,
231 ajoin([Context, ' "', Name, '" has the same name as a ', Class, ' in ',OpOrDef,' "', OperationId,'".'], Msg),
232 add_warning(bmachine_static_checks,Msg,'',TName).
233
234 clash_local_warnings(AllIds,OperationId,Name,Pos) :-
235 my_ord_member(Name,Class,AllIds), !,
236 ajoin(['Local variable has the same name as a ', Class, ' in operation ', OperationId,': '], Msg),
237 add_warning(bmachine_static_checks,Msg,Name,Pos).
238
239 % TODO: this does not and can not work here:
240 % - Some preconditions are removed (typing only....) during machine simplification
241 % - Needs to be verified during typechecking
242 % ---------------------
243 % Checks if an operations parameter is not typed by a pre condition
244 % ---------------------
245 %parameters_without_pre_condition(Machine) :-
246 % get_section(operation_bodies,Machine,Operations),
247 % maplist(parameters_without_pre_condition_aux,Operations).
248 %
249 %parameters_without_pre_condition_aux(Operation) :-trace,
250 % get_texpr_expr(Operation,operation(IdFull,_Results,Params,Subst)),
251 % get_texpr_id(IdFull,op(Id)),
252 % (Params == []
253 % -> true % no parameters
254 % ; (get_texpr_expr(Subst,precondition(_,_))
255 % -> true % parameters and precondition
256 % ; ajoin(['Operation ', Id, ' has parameters but no pre-condition.'], Msg),
257 % add_warning(bmachine_static_checks,Msg)
258 % )).