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 | | :- module(b_read_write_info,[get_accessed_vars/4, get_accessed_vars/5, |
6 | | recompute_while_read_write_info/2, |
7 | | get_nondet_modified_vars/3, |
8 | | tcltk_read_write_matrix/1, |
9 | | tcltk_variable_read_write_matrix/2, |
10 | | tcltk_dot_variable_modification_analysis/1, |
11 | | tcltk_get_guards_for_ops/1, |
12 | | tcltk_get_parameter_free_guards_for_ops/1, |
13 | | b_get_read_write/3, |
14 | | b_get_operation_read_guard_vars/4, |
15 | | b_get_read_write_vars/5, |
16 | | b_get_written_variables/1, check_all_variables_written/0, |
17 | | %,b_get_read_may_must_write/7 % currently not used outside |
18 | | |
19 | | get_lhs_assigned_identifier/2 |
20 | | ]). |
21 | | |
22 | | |
23 | | :- use_module(module_information,[module_info/2]). |
24 | | :- module_info(group,typechecker). |
25 | | :- module_info(description,'ProBs type checker and type inference module.'). |
26 | | |
27 | | :- use_module(library(lists)). |
28 | | :- use_module(library(ordsets)). |
29 | | :- use_module(self_check). |
30 | | :- use_module(error_manager). |
31 | | :- use_module(bsyntaxtree). |
32 | | |
33 | | |
34 | | recompute_while_read_write_info(TExpr,NewTExpr) :- |
35 | | (transform_bexpr(b_read_write_info:update_while_info,TExpr,NewTExpr) |
36 | | -> true |
37 | | ; add_internal_error('Call failed: ',transform_bexpr(b_read_write_info:update_while_info,TExpr,NewTExpr)), |
38 | | NewTExpr=TExpr). |
39 | | |
40 | | :- use_module(translate,[print_bexpr/1]). |
41 | | :- public update_while_info/2. |
42 | | update_while_info(b(WHILE,subst,OldInfo),b(WHILE,subst,NewInfo)) :- |
43 | | WHILE = while(_TPred,_TBody,_TInv,TVar), |
44 | | get_accessed_vars(b(WHILE,subst,OldInfo),[],Modifies,Reads), |
45 | | (debug:debug_mode(off) -> true |
46 | | ; nl,print('UPDATING WHILE with VARIANT: '), print_bexpr(TVar),nl, |
47 | | format(' WHILE Reads : ~w~n Modifies : ~w~n',[Reads,Modifies]) |
48 | | ), |
49 | | (delete(OldInfo,reads(OldReads),OI1) -> true ; OldReads=[], OI1=OldInfo), |
50 | | (delete(OI1,modifies(OldModifies),OI2) -> true ; OldModifies=[], OI2=OI1), |
51 | | ((Reads,Modifies)=(OldReads,OldModifies) |
52 | | -> NewInfo=OldInfo |
53 | | ; format('***** different from Reads : ~w~n Modifies : ~w~n',[OldReads,OldModifies]), |
54 | | ord_subtract(Reads,OldReads,NewR), |
55 | | ord_subtract(OldReads,Reads,DelR), |
56 | | format(' added: ~w~n deleted: ~w~n****~n',[NewR,DelR]), |
57 | | NewInfo = [modifies(Modifies),reads(Reads)|OI2] |
58 | | ). |
59 | | |
60 | | % TO DO: also update info for operation call ! |
61 | | |
62 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
63 | | |
64 | | :- use_module(bsyntaxtree,[get_global_identifiers/1]). |
65 | | |
66 | | /* get_accessed_vars/4 computes the variables that are read or written to |
67 | | by a substitution. |
68 | | get_accessed_vars(+Subst,+LocalVars,-Modified,-Reads): |
69 | | Subst is the substitution (as a typed syntax tree) |
70 | | LocalVars is a list of (untyped) ids that are local variables. |
71 | | Local variables are ignored and not included in the list of read or written |
72 | | variables. |
73 | | Modified is the list of (untyped) identifiers that are written by the substitution and |
74 | | Reads is the list of (untyped) identifiers that are read. */ |
75 | | get_accessed_vars(TSubst,LocalVars,Modified,Reads) :- |
76 | | get_accessed_vars(TSubst,LocalVars,Modified,_,Reads). |
77 | | get_accessed_vars(TSubst,LocalVars,Modified,NonDetModified,Reads) :- |
78 | | get_global_identifiers(IS), % TO DO: currently this information is set up only *after* type checking !! |
79 | | % this means the accessed vars will also contain SETS and enumerated set elements ! |
80 | | % either we need to filter them out later, or change the startup / precompilation process |
81 | | list_to_ord_set(LocalVars,SLV), |
82 | | ord_union(IS,SLV,LocalVarsToIgnore), |
83 | | empty_mrinfo(In), |
84 | | get_accessed_vars1(TSubst,LocalVarsToIgnore,In,Out), |
85 | | extract_reads_and_modifies(In,Out,Modified,NonDetModified,Reads). |
86 | | extract_reads_and_modifies(mrinfo(_,_,_),mrinfo(Modified,NonDetModified,Reads), |
87 | | Modified,NonDetModified,Reads). |
88 | | |
89 | | % get variables which are modified non-deterministically; does not traverse operation calls at the moment |
90 | | get_nondet_modified_vars(TSubst,LocalVars,NonDetModified) :- |
91 | | empty_mrinfo(In), |
92 | | list_to_ord_set(LocalVars,SLocalVars), |
93 | | get_accessed_vars1(TSubst,SLocalVars,In,Out), |
94 | | Out=mrinfo(_,NonDetModified,_). |
95 | | |
96 | | is_operation_call(operation_call(Operation,Results,Arguments),Operation,Results,Arguments). |
97 | | is_operation_call(operation_call_in_expr(Operation,Arguments),Operation,[],Arguments). % cannot match, as we are in a substitution context |
98 | | |
99 | | get_accessed_vars1(TSubst,LocalVars,In,Out) :- |
100 | | get_texpr_expr(TSubst,Subst), |
101 | | ( is_operation_call(Subst,Operation,Results,Arguments) -> |
102 | | get_texpr_info(TSubst,Info), |
103 | | def_get_texpr_id(Operation,op(OperationName)), |
104 | | memberchk(modifies(MVars),Info), |
105 | | (memberchk(non_det_modifies(NonDetMVars),Info) -> true ; NonDetMVars=[]), |
106 | | memberchk(reads(RVars),Info), |
107 | | % info fields added in btype2(operation(...) in btypechecker.pl |
108 | | %op_modifies_reads_cache(OperationName,MVarsC,RVarsC) %this is no longer necessary |
109 | | (ground(MVars),ground(RVars) |
110 | | -> true |
111 | | ; add_error(b_read_write_info, 'Cannot obtain modifies/reads info for called operation (probably illegal local call): ',OperationName), |
112 | | % TEST 655 used to reach this branch |
113 | | MVars=[], RVars=[], NonDetMVars=[] |
114 | | % this should only happen when local operation called !?! |
115 | | % or ensure that operations are processed before we reach operation calls |
116 | | % this may be problematic for local operations |
117 | | ), |
118 | | accessed_vars_for_operation_call(MVars,NonDetMVars,RVars,LocalVars,Results,Arguments,In,Out) |
119 | | ; get_accessed_vars2(Subst,LocalVars,In,Out) -> |
120 | | true |
121 | | ; Subst = identifier(ID) -> add_error(b_read_write_info,'Identifier not a valid B Operation: ',ID,TSubst),In=Out |
122 | | ; |
123 | | add_internal_error('Could not extract information about accessed variables for substitution', |
124 | | get_accessed_vars2(Subst,LocalVars,In,Out)), |
125 | | %trace, get_accessed_vars2(Subst,LocalVars,In,Out), |
126 | | fail). |
127 | | accessed_vars_for_operation_call(MVars,NonDetMVars,RVars,LocalVars,Results,Arguments) --> |
128 | | add_non_local_modified_vars(MVars,LocalVars), |
129 | | add_non_local_read_vars(RVars,LocalVars), |
130 | | add_modified_vars(Results,LocalVars), |
131 | | add_non_local_non_det_modified_vars(NonDetMVars,LocalVars), |
132 | | add_read_vars_l(Arguments,LocalVars). |
133 | | |
134 | | get_accessed_vars2(skip,_) --> !. |
135 | | get_accessed_vars2(block(Subst),LocalVars) --> get_accessed_vars1(Subst,LocalVars). |
136 | | get_accessed_vars2(init_statement(Subst),LocalVars) --> get_accessed_vars1(Subst,LocalVars). |
137 | | get_accessed_vars2(assertion(A,Subst),R) --> add_read_vars(A,R),get_accessed_vars1(Subst,R). |
138 | | get_accessed_vars2(witness_then(A,Subst),R) --> add_read_vars(A,R),get_accessed_vars1(Subst,R). |
139 | | get_accessed_vars2(assign(LHS,E),R) --> %Note: LHS can be nested function application f(a)(b) := |
140 | | {maplist(get_lhs_assigned_identifier,LHS,Ids)}, add_modified_vars(Ids,R), |
141 | | {convlist(get_lhs_read_expression,LHS,LHS_R), append(LHS_R,E,RE)}, add_read_vars_l(RE,R). |
142 | | get_accessed_vars2(assign_single_id(Id,E),R) --> |
143 | | add_read_vars_l([E],R), % Note: LHS is a single Identifier: nothing is ever read there |
144 | | add_modified_vars([Id],R). |
145 | | get_accessed_vars2(precondition(P,Subst),R) -->add_read_vars(P,R),get_accessed_vars1(Subst,R). |
146 | | get_accessed_vars2(assertion(Subst),R) --> get_accessed_vars1(Subst,R). |
147 | | get_accessed_vars2(witness_then(Subst),R) --> get_accessed_vars1(Subst,R). |
148 | | get_accessed_vars2(choice(Substs),R) --> get_accessed_vars_for_choice(Substs,R). |
149 | | get_accessed_vars2(if(Substs),R) --> get_accessed_vars_for_if(Substs,R). |
150 | | get_accessed_vars2(select(Whens,Else),R) --> get_accessed_vars_for_select(Whens,Else,R). |
151 | | get_accessed_vars2(select(Whens),R) --> get_accessed_vars_for_select(Whens,none,R). |
152 | | get_accessed_vars2(case(Expr,Substs,Else),R) --> get_accessed_vars_for_case(Expr,Substs,Else,R). |
153 | | get_accessed_vars2(case_or(Exprs,Subst),R) --> add_read_vars_l(Exprs,R),get_accessed_vars1(Subst,R). |
154 | | get_accessed_vars2(any(Ids,G,Subst),R) --> get_mrinfo(ModIn,_,_), |
155 | | add_tvars_to_ord_list(R,Ids,L),add_read_vars(G,L), |
156 | | get_accessed_vars1(Subst,L), |
157 | | add_delta_modified_to_non_det_modified(ModIn). % variables modified within an ANY are supposed to be non-det assigned; e.g. ANY x WHERE x:1..2 THEN y:=x END |
158 | | get_accessed_vars2(let(Ids,G,Subst),R) --> |
159 | | add_tvars_to_ord_list(R,Ids,L),add_read_vars(G,L),get_accessed_vars1(Subst,L). |
160 | | get_accessed_vars2(lazy_let_subst(ID,SharedExpr,Subst),R) --> add_tvars_to_ord_list(R,[ID],L), |
161 | | add_read_vars(SharedExpr,L),get_accessed_vars1(Subst,L). |
162 | | get_accessed_vars2(becomes_element_of(Ids,E),R) --> add_non_det_modified_vars(Ids,R),add_read_vars(E,R). |
163 | | get_accessed_vars2(becomes_such(Ids,E),LocalVars0) --> |
164 | | add_non_det_modified_vars(Ids,LocalVars0), |
165 | | {find_read_vars_for_becomes_such(Ids,E,LocalVars0,ReadVars)}, |
166 | | add_read_vars2(ReadVars). |
167 | | %get_accessed_vars2(evb2_becomes_such(Ids,E),R) --> % now translated to becomes_such |
168 | | % add_non_det_modified_vars(Ids,R), add_read_vars(E,R). |
169 | | get_accessed_vars2(sequence(Substs),R) --> get_accessed_vars_sequence(Substs,R). |
170 | | get_accessed_vars2(parallel(Substs),R) --> get_accessed_vars_l(Substs,R). |
171 | | get_accessed_vars2(while(Cond,Subst,Inv,Variant),R) --> |
172 | | get_accessed_vars_for_while(Cond,Subst,Inv,Variant,R). |
173 | | get_accessed_vars2(var(Ids,Subst),R) --> add_tvars_to_ord_list(R,Ids,L),get_accessed_vars1(Subst,L). |
174 | | % TODO: we need information about which variables are modified by an external function |
175 | | get_accessed_vars2(external_subst_call(_Name,Exprs),R) --> add_read_vars_l(Exprs,R). |
176 | | get_accessed_vars2(rlevent(_Name,_Section,VariantStatus,Params,Guard,Theorems,Actions,VWitnesses,PWitnesses,Unmod,AbstractEvents),R) --> |
177 | | % the code duplicates the logic in collect_read_vars in bmachine_eventb.pl; TO-DO: merge |
178 | | add_tvars_to_ord_list(R,Params,L), |
179 | | add_read_vars(Guard,L), |
180 | | add_read_vars_l(Theorems,L), |
181 | | add_read_vars_l(Unmod,L), % for unmodifiables we check that whether new values correspond to old values and thus need read accces to the old value in check_if_invalid_vars_are_modified |
182 | | add_variant_status_read_vars(VariantStatus,R), % the parameters should not be used in the Variant |
183 | | add_witness_read_vars_l(VWitnesses,L), |
184 | | add_witness_read_vars_l(PWitnesses,L), |
185 | | get_accessed_vars_l(Actions,L), |
186 | | get_accessed_vars_l(AbstractEvents,L). |
187 | | |
188 | | get_accessed_vars_for_while(Cond,Subst,Inv,Variant,Locals) --> |
189 | | get_accessed_vars_for_while_invariant(Inv,Locals), |
190 | | add_read_vars(Cond,Locals), |
191 | | add_read_vars(Variant,Locals), |
192 | | %get_accessed_vars1(Subst,Locals). |
193 | | % The Substitution may not be executed; just like an IF |
194 | | % e.g. "WHILE P DO x:=1 ... END" may keep (and thus virtually read) the old value of x if P is false initially |
195 | | get_accessed_vars_for_if_when(if_with_no_else,[Subst],Locals). |
196 | | get_accessed_vars_for_while_invariant(Inv,Locals,In,Out) :- |
197 | | empty_mrinfo(LI), |
198 | | add_read_vars(Inv,Locals,LI,LO), |
199 | | extract_reads_and_modifies(LI,LO,Modified,_,Reads0), |
200 | | maplist(remove_optional_prime,Reads0,Reads), |
201 | | list_to_ord_set(Reads,SReads), % removing prime can create duplicates |
202 | | add_non_local_read_vars(SReads,Locals,In,Inter), |
203 | | add_non_local_modified_vars(Modified,Locals,Inter,Out). |
204 | | remove_optional_prime(Id,Orig) :- atom_concat(Orig,'$0',Id),!. |
205 | | remove_optional_prime(Id,Id). |
206 | | |
207 | | get_accessed_vars_for_if(ElseIfs,Locals) --> |
208 | | { maplist(split_elseif,ElseIfs,Conds,Subst), |
209 | | (if_has_else_block(Conds) -> HasElse = has_else ; HasElse = if_with_no_else) }, |
210 | | % mark all used variables in the conditions as read |
211 | | add_read_vars_l(Conds,Locals), |
212 | | get_accessed_vars_for_if_when(HasElse,Subst,Locals). |
213 | | split_elseif(Expr,Cond,Subst) :- get_texpr_expr(Expr,if_elsif(Cond,Subst)). |
214 | | |
215 | | get_accessed_vars_for_select(Whens,Else,Locals) --> |
216 | | { maplist(split_when,Whens,Conds,Subst1), |
217 | | ( Else = none -> |
218 | | HasElse=select_no_else, Subst = Subst1 |
219 | | ; |
220 | | HasElse=has_else, Subst = [Else|Subst1]) }, |
221 | | % mark all used variables in the conditions as read |
222 | | add_read_vars_l(Conds,Locals), |
223 | | get_accessed_vars_for_if_when(HasElse,Subst,Locals). |
224 | | split_when(Expr,Cond,Subst) :- get_texpr_expr(Expr,select_when(Cond,Subst)). |
225 | | |
226 | | |
227 | | % treat substitution body of IF/WHEN/WHILE |
228 | | get_accessed_vars_for_if_when(HasElse,Subst,Locals) --> |
229 | | { get_accessed_vars_for_if_when2(HasElse,Subst,Locals,Modified,NonDetModifies,Reads) }, |
230 | | add_read_vars2(Reads), |
231 | | %add_modified_vars2(Modified) |
232 | | ({(HasElse==has_else ; HasElse==if_with_no_else ; Subst = [_,_|_])} |
233 | | -> {my_ord_union(NonDetModifies,Modified,M2)}, |
234 | | add_non_det_modified_vars2(M2) |
235 | | ; add_non_det_modified_vars2(NonDetModifies), |
236 | | add_modified_vars2(Modified) % there is only one branch: Modified variables not also marked as non-det modified |
237 | | ). |
238 | | |
239 | | :- use_module(tools,[maplist5/5]). |
240 | | |
241 | | get_accessed_vars_for_if_when2(has_else,Subst,Locals,ModifiedBySome,NonDetModifiedBySome,ReadBySome) :- |
242 | | maplist5(get_accessed_vars_x(Locals),Subst,Modifies,NonDetModifies,Reads), |
243 | | % We want to calculate those elements that are modified by some (in the union) |
244 | | % but not by all (in the intersection) |
245 | | ord_union(NonDetModifies,NonDetModifiedBySome), |
246 | | ord_union(Modifies,ModifiedBySome), |
247 | | ord_intersection(Modifies,ModifiedByAll), |
248 | | ord_subtract(ModifiedBySome,ModifiedByAll,ModifiedByNotAll), |
249 | | % we add those values that are not modified by all to the set of read variables |
250 | | % because e.g. "IF P THEN x:=1 END" is equivalent to "IF P THEN x:=1 ELSE x:=x END" |
251 | | ord_union([ModifiedByNotAll|Reads],ReadBySome). |
252 | | get_accessed_vars_for_if_when2(select_no_else,Subst,Locals,ModifiedBySome,NonDetModifiedBySome,ReadBySome) :- |
253 | | % Note "SELECT P THEN x:=1 END" is NOT equivalent to "SELECT P THEN x:=1 ELSE x:=x END" ! |
254 | | % there is no implicit skip, which copies all variables; |
255 | | % as such it can be treated like an if with an exhaustive list of cases |
256 | | % the treatment for computing ModifiedBySome,ReadBySome is identical |
257 | | get_accessed_vars_for_if_when2(has_else,Subst,Locals,ModifiedBySome,NonDetModifiedBySome,ReadBySome). |
258 | | get_accessed_vars_for_if_when2(if_with_no_else,Subst,Locals,ModifiedBySome,NonDetModifiedBySome,ReadBySome) :- |
259 | | % no else block present, we must assume that each written variable |
260 | | % can also be treated like a read variable, see example in the other condition: |
261 | | % e.g. "IF P THEN x:=1 END" is equivalent to "IF P THEN x:=1 ELSE x:=x END" and thus reads the old value of x |
262 | | maplist5(get_accessed_vars_x(Locals),Subst,Modifies,NonDetModifies,Reads), |
263 | | ord_union(NonDetModifies,NonDetModifiedBySome), |
264 | | ord_union(Modifies,ModifiedBySome), |
265 | | ord_union([ModifiedBySome|Reads],ReadBySome). |
266 | | |
267 | | get_accessed_vars_for_choice(Substs,Locals,In,mrinfo(ModOut,NDOut2,Read)) :- |
268 | | get_accessed_vars_for_if_when(has_else,Substs,Locals,In,Out), |
269 | | In = mrinfo(ModIn,_,_), |
270 | | Out = mrinfo(ModOut,NDOut,Read), % these three fields will be all variables |
271 | | % mark all assignments as non-deterministically chosen: |
272 | | add_modified_to_non_det_modified(ModIn,ModOut,NDOut,NDOut2). |
273 | | |
274 | | add_modified_to_non_det_modified(ModIn,ModOut,NDOut,NDOut2) :- |
275 | | ord_subtract(ModOut,ModIn,Delta), % these variables were detected as written |
276 | | ord_union(Delta,NDOut,NDOut2). |
277 | | |
278 | | |
279 | | % get the modifies/read info for use in a DCG context: |
280 | | get_mrinfo(Modified,NonDetModified,InRead,I,I) :- |
281 | | (var(I) -> add_internal_error('Unbound mrinfo env:',I) ; true), |
282 | | I = mrinfo(Modified,NonDetModified,InRead), |
283 | | (var(Modified) -> add_internal_error('Illegal mrinfo env:',I) ; true). |
284 | | |
285 | | %portray_mrinfo(Msg,I,I) :- I=mrinfo(Modified,NonDetModified,Read),!, |
286 | | % format('~w : modified=~w (non-det:~w), read=~w~n',[Msg,Modified,NonDetModified,Read]). |
287 | | %portray_mrinfo(Msg,I,I) :- format(user_error,'*** ~w ~w~n',[Msg,I]). |
288 | | |
289 | | % create an initial modifies/reads info environment for the DCGs |
290 | | empty_mrinfo(mrinfo([],[],[])). |
291 | | |
292 | | my_ord_union(Vars,S,Res) :- \+ is_ordset(Vars),!, |
293 | | add_internal_error('Not sorted (first argument): ',my_ord_union(Vars,S,Res)), |
294 | | list_to_ord_set(Vars,SV), ord_union(SV,S,Res). |
295 | | my_ord_union(Vars,S,Res) :- \+ is_ordset(S),!, |
296 | | add_internal_error('Not sorted (second argument): ',my_ord_union(Vars,S,Res)), |
297 | | list_to_ord_set(S,SS), ord_union(Vars,SS,Res). |
298 | | my_ord_union(Vars,S,Res) :- ord_union(Vars,S,Res). |
299 | | |
300 | | |
301 | | |
302 | | add_delta_modified_to_non_det_modified(ModIn,mrinfo(ModOut,NonDet,Read),mrinfo(ModOut,NonDet2,Read)) :- |
303 | | add_modified_to_non_det_modified(ModIn,ModOut,NonDet,NonDet2). |
304 | | |
305 | | get_accessed_vars_for_case(Expr,Eithers,Else,Locals) --> |
306 | | % we can treat a case statement analogously to an IF-THEN-ELSE statement |
307 | | % where an ELSE is present |
308 | | % additionally, we have to add all expressions to the read set |
309 | | add_read_vars(Expr,Locals), |
310 | | { maplist(split_case_or,Eithers,ExprsList,Substs), append(ExprsList,Exprs) }, |
311 | | add_read_vars_l(Exprs,Locals), |
312 | | get_accessed_vars_for_if_when(has_else,[Else|Substs],Locals). |
313 | | split_case_or(CaseOr,Exprs,Subst) :- get_texpr_expr(CaseOr,case_or(Exprs,Subst)). |
314 | | |
315 | | % just the same as get_accessed_vars, but with different order of parameters |
316 | | % to allow call with maplist |
317 | | get_accessed_vars_x(Locals,Subst,Modifies,NonDetModifies,Reads) :- |
318 | | get_accessed_vars(Subst,Locals,Modifies,NonDetModifies,Reads). |
319 | | |
320 | | if_has_else_block(Conds) :- |
321 | | last(Conds,Cond),is_truth(Cond). |
322 | | |
323 | | % add ids of unsorted list of typed identifiers to sorted list of ids: |
324 | | add_tvars_to_ord_list(SortedVars,TIds,NewVars,X,X) :- |
325 | | get_texpr_ids(TIds,Ids), |
326 | | list_to_ord_set(Ids,SIds), |
327 | | my_ord_union(SortedVars,SIds,NewVars). |
328 | | %append(Vars,Ids,NewVars). |
329 | | |
330 | | add_read_vars_l([],_LocalVars) --> !. |
331 | | add_read_vars_l([Expr|Rest],LocalVars) --> !, |
332 | | add_read_vars(Expr,LocalVars), |
333 | | add_read_vars_l(Rest,LocalVars). |
334 | | add_read_vars_l(E,LV) --> !, {add_internal_error('Not a list: ',add_read_vars_l(E,LV,_,_))}. |
335 | | |
336 | | add_variant_status_read_vars(b(Status,status,_Info),LocalVars) --> |
337 | | % TO DO: if member(convergence_proved,Info) should we add the vars? |
338 | | add_variant_status_aux(Status,LocalVars). |
339 | | |
340 | | add_variant_status_aux(convergent(VariantExpr),LocalVars) --> !, |
341 | | add_read_vars(VariantExpr,LocalVars). |
342 | | add_variant_status_aux(anticipated(VariantExpr),LocalVars) --> !, |
343 | | add_read_vars(VariantExpr,LocalVars). |
344 | | add_variant_status_aux(ordinary,_) --> !, []. |
345 | | add_variant_status_aux(Status,_) --> {add_warning(b_read_write_info,'Unrecognised VARIANT status: ',Status)}. |
346 | | |
347 | | add_witness_read_vars_l([],_LocalVars) --> !. |
348 | | add_witness_read_vars_l([Expr|Rest],LocalVars) --> |
349 | | add_witness_read_vars(Expr,LocalVars), !, |
350 | | add_witness_read_vars_l(Rest,LocalVars). |
351 | | add_witness_read_vars_l(E,LV) --> !, {add_internal_error('Not a witness list: ',add_witness_read_vars_l(E,LV,_,_))}. |
352 | | |
353 | | % extract modified identifier from LHS of assign: |
354 | | get_lhs_assigned_identifier(b(function(LHS,_),_,_),Res) :- !, get_lhs_assigned_identifier(LHS,Res). |
355 | | get_lhs_assigned_identifier(b(record_field(LHS,_),_,_),Res) :- !, % something like xx'field := yy (cf test 2032) |
356 | | get_lhs_assigned_identifier(LHS,Res). |
357 | | get_lhs_assigned_identifier(ID,ID) :- |
358 | | (ID= b(identifier(_),_,_) -> true ; add_warning(b_read_write_info,'Unrecognized LHS of assignment: ',ID,ID)). |
359 | | |
360 | | % extract part of LHS of assign which reads information: |
361 | | get_lhs_read_expression(b(function(LHS,Read1),T,I),Res) :- !, |
362 | | Res = b(function(LHS,Read1),T,I). |
363 | | get_lhs_read_expression(b(record_field(LHS,Read1),T,I),Res) :- !, |
364 | | Res = b(record_field(LHS,Read1),T,I). % TO DO: check if LHS has more than one field? but could be nested |
365 | | % old version which for f(a) := b only marked a as read in LHS f(a); now we also mark f |
366 | | % indeed f(a) := b is equivalent to f := f <+ {a|->b} ; so the old value of f is implicitly read (important for ltsmin symbolic model checking) |
367 | | % similarly r'x := b also reads the old value of r (if r has more than one field) |
368 | | %get_lhs_read_expression(b(function(LHS,Read1),_,_),Res) :- !, |
369 | | % (get_lhs_read_expression(LHS,Read2) -> create_couple(Read1,Read2,Res) |
370 | | % ; Res = Read1). |
371 | | |
372 | | add_witness_read_vars(b(witness(ID,Predicate),witness,_),LocalVars) --> !, |
373 | | % a parameter witness talks about a parameter no longer there: it is not really read here, but the predicate is used to set the any local parameter in another context |
374 | | % a variable witness alks about a variable non-deterministically assigned in the abstraction; we also pretend it is not read here (but assigned somewhere else) |
375 | | add_tvars_to_ord_list(LocalVars,[ID],LocalVars2), % we add ID to local variables not read "really" read here |
376 | | %{print(witness_add_read_vars(ID)),nl}, |
377 | | add_read_vars(Predicate,LocalVars2). |
378 | | |
379 | | add_read_vars(Expression,LocalVars) --> |
380 | | {find_identifier_uses(Expression,LocalVars,ReadVars)}, % TOOD: here we can have operation_call_in_expr with non-ground reads info! |
381 | | add_read_vars2(ReadVars). |
382 | | add_read_vars2(SVars,mrinfo(Modified,NonDetModified,InRead), |
383 | | mrinfo(Modified,NonDetModified,OutRead)) :- my_ord_union(SVars,InRead,OutRead). |
384 | | |
385 | | |
386 | | |
387 | | add_non_local_read_vars(ReadVars,LocalVars) --> |
388 | | {ord_subtract(ReadVars,LocalVars,AddVars)}, |
389 | | add_read_vars2(AddVars). |
390 | | add_non_local_modified_vars(WriteVars,LocalVars) --> |
391 | | {ord_subtract(WriteVars,LocalVars,AddVars)}, |
392 | | add_modified_vars2(AddVars). |
393 | | |
394 | | add_modified_vars(Ids,LocalVars) --> |
395 | | {filter_result_vars(Ids,LocalVars,ModifiedVars)}, |
396 | | add_modified_vars2(ModifiedVars). |
397 | | add_modified_vars2(Vars,In,Res) :- |
398 | | (var(Vars) |
399 | | -> add_internal_error('Modified Vars variable: ',add_modified_vars2(Vars,In,Res)) |
400 | | ; true), |
401 | | In = mrinfo(InModified,NonDetModified,Read), |
402 | | Res = mrinfo(OutModified,NonDetModified,Read), |
403 | | list_to_ord_set(Vars,SVars), |
404 | | my_ord_union(SVars,InModified,OutModified). |
405 | | |
406 | | |
407 | | add_non_local_non_det_modified_vars(WriteVars,LocalVars) --> |
408 | | {ord_subtract(WriteVars,LocalVars,AddVars)}, |
409 | | add_non_det_modified_vars2(AddVars). |
410 | | |
411 | | add_non_det_modified_vars(Ids,LocalVars) --> |
412 | | {filter_result_vars(Ids,LocalVars,ModifiedVars)}, |
413 | | {list_to_ord_set(ModifiedVars,SModifiedVars)}, |
414 | | add_non_det_modified_vars2(SModifiedVars). |
415 | | add_non_det_modified_vars2(Vars,In,Res) :- |
416 | | (var(Vars) |
417 | | -> add_internal_error('Modified Vars variable: ',add_non_det_modified_vars2(Vars,In,Res)) |
418 | | ; true), |
419 | | In = mrinfo(InModified,InNonDetModified,Read), |
420 | | Res = mrinfo(OutModified,OutNonDetModified,Read), |
421 | | my_ord_union(Vars,InModified,OutModified), |
422 | | my_ord_union(Vars,InNonDetModified,OutNonDetModified). |
423 | | |
424 | | |
425 | | :- use_module(tools,[remove_all/3]). |
426 | | filter_result_vars(Ids,LocalVars,ModifiedVars) :- |
427 | | % extract_changed_id might fail if the user entered |
428 | | % a wrong LHS of an assignment. This should lead to an |
429 | | % type error, handled by the assign type checking rule. |
430 | | convlist(extract_changed_id,Ids,Names), |
431 | | remove_all(Names,LocalVars,ModifiedVars). |
432 | | extract_changed_id(Id,Name) :- |
433 | | get_texpr_id(Id,Name),!. |
434 | | extract_changed_id(FuncOrRec,Name) :- |
435 | | get_texpr_expr(FuncOrRec,Expr), |
436 | | get_nested_modified_expr(Expr,TId), |
437 | | !, |
438 | | extract_changed_id(TId,Name). % we can have nested function calls f(x)(y) := z or nested records f(x)'field := z |
439 | | % nested records are currently not allowed in the btypechecker though (id_or_function_or_rec) |
440 | | extract_changed_id(X,Name) :- |
441 | | add_internal_error('Could not extract changed id: ',extract_changed_id(X,Name)),fail. |
442 | | |
443 | | get_nested_modified_expr(function(TId,_),TId). |
444 | | get_nested_modified_expr(record_field(TId,_Field),TId). |
445 | | |
446 | | get_accessed_vars_l([],_) --> []. |
447 | | get_accessed_vars_l([S|Rest],LovalVars) --> |
448 | | get_accessed_vars1(S,LovalVars), |
449 | | get_accessed_vars_l(Rest,LovalVars). |
450 | | |
451 | | % get accessed vars for a sequence (sequential composition) |
452 | | get_accessed_vars_sequence([],_) --> []. |
453 | | get_accessed_vars_sequence([S|Rest],LocalVars) --> %portray_mrinfo(seq(S)), |
454 | | % TO DO: we could always compute must_write information in get_accessed_vars, rather than re-traversing here: |
455 | | {Rest \= [], extract_must_write(S,not_nested,WIds),my_ord_union(WIds,LocalVars,LocalVars2)},!, |
456 | | % not_nested: do not look at nested operation calls: they may not be precompiled; and we cannot read them from here anyway? ; relevant tests: 1601, 1782, 1870 |
457 | | % treat case x := 0 ; y := x+1 --> x is read after completely written |
458 | | % careful, we can have: f(1) := 0; y := f(2) ! |
459 | | % we can also have: x:=x+1 ; y := x --> here x is read |
460 | | % Note: we need the must_write information to be sure that x cannot be read after S; may_write is not enough |
461 | | get_accessed_vars1(S,LocalVars), |
462 | | % in rest any read, write to LocalVars2 will not be registered; |
463 | | % only problem is that we could have something like x:=0 ; x::{1,2} and we would not register x as NonDetModifies |
464 | | % NonDetModifies is only used in animation interface to show additional virtual parameters for operations |
465 | | % it is thus not critical |
466 | | get_accessed_vars_sequence(Rest,LocalVars2). |
467 | | get_accessed_vars_sequence([S|Rest],LocalVars) --> |
468 | | get_accessed_vars1(S,LocalVars), |
469 | | get_accessed_vars_sequence(Rest,LocalVars). |
470 | | |
471 | | % now handled in find_identifier_uses |
472 | | %:- use_module(tools,[number_suffix/3]). |
473 | | %:- assert_must_succeed(( b_read_write_info:append_primed_vars([b(identifier(x),integer,[])],[],R), R = ['x$0'])). |
474 | | %append_primed_vars(TIds,R,L) :- |
475 | | % maplist(add_dollar_zero,TIds,Primed), |
476 | | % append(Primed,R,L). |
477 | | %:- assert_must_succeed(( b_read_write_info:add_dollar_zero(b(identifier(x),integer,[]),R), R = 'x$0')). |
478 | | %add_dollar_zero(TId,P) :- |
479 | | % get_texpr_id(TId,Id), |
480 | | % number_suffix(Id,0,P). |
481 | | |
482 | | :- use_module(tools_lists,[ord_member_nonvar_chk/2]). |
483 | | % remap reads of ID$0 to reads of ID |
484 | | remap_primed_ids([],_,_,[]). |
485 | | remap_primed_ids([ID0|T],SRenameList,LocalVars0,[ID|RT]) :- |
486 | | (ord_member_nonvar_chk(rename(ID0,ID),SRenameList) |
487 | | -> ord_nonmember(ID,LocalVars0) % only keep if not a local variable |
488 | | ; ID=ID0 |
489 | | ), |
490 | | !, |
491 | | remap_primed_ids(T,SRenameList,LocalVars0,RT). |
492 | | remap_primed_ids([_|T],SRenameList,LocalVars0,RT) :- |
493 | | remap_primed_ids(T,SRenameList,LocalVars0,RT). |
494 | | |
495 | | % compute read variables for becomes_such; remapping reads of ID$0 to ID |
496 | | find_read_vars_for_becomes_such(TIds,Body,LocalVars0,BMSuchReadVars) :- |
497 | | add_tvars_to_ord_list(LocalVars0,TIds,LocalVars1,_,_), % ignore reading new values; this does not correspond to reading a value in the current state |
498 | | % all Ids are already marked as accessed, any reference to Id$0 is a read of the old value; do not add Id$0 to the accessed variables |
499 | | get_texpr_ids(TIds,Names), |
500 | | maplist(add_dollar_zero,Names,RenameList), |
501 | | list_to_ord_set(RenameList,SRenameList), |
502 | | find_identifier_uses(Body,LocalVars1,ReadVars), |
503 | | remap_primed_ids(ReadVars,SRenameList,LocalVars0,BMSuchReadVars). |
504 | | |
505 | | :- use_module(tools,[number_suffix/3]). % $0 |
506 | | add_dollar_zero(Id,rename(P,Id)) :- |
507 | | number_suffix(Id,0,P). |
508 | | |
509 | | |
510 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
511 | | |
512 | | % READ-WRITE-INFO for OPERATIONS |
513 | | |
514 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
515 | | |
516 | | |
517 | | :- use_module(bmachine,[b_get_operation_normalized_read_write_info/3, b_get_machine_operation/4, |
518 | | b_is_operation_name/1, |
519 | | b_get_machine_variables/1, b_get_machine_constants/1, b_is_variable/1]). |
520 | | |
521 | | b_get_read_write(O,R,W) :- |
522 | | if(b_get_operation_normalized_read_write_info(O,R,W),true, |
523 | | (R,W)=('$all','$all')). % it used to be that in Event-B no read information was stored; all calls to b_get_operation_normalized_read_write_info failed; has been fixed |
524 | | |
525 | | b_get_machine_operation_with_local_paras(OpName,AllLocalParas,Body) :- |
526 | | b_get_machine_operation(OpName,Results,Parameters,Body), |
527 | | append(Parameters,Results,TP), |
528 | | get_texpr_ids(TP,TPIds), |
529 | | list_to_ord_set(TPIds,AllLocalParas). |
530 | | |
531 | | /* We distinguish between read variables in the guard and read variables in the event statement */ |
532 | | b_get_read_write_guard_action(Operation,ReadGuard,ReadAction,Read,Write) :- |
533 | | b_get_read_write(Operation,Read,Write), |
534 | | b_get_operation_read_guard_vars(Operation,false,ReadGuard,_Precise), |
535 | | b_get_machine_operation_with_local_paras(Operation,LocalParas,Body), |
536 | | get_texpr_expr(Body,RLEVENT), |
537 | | (RLEVENT = rlevent(_Name,_Section,_Stat,_Parameters,_Grd,_Thm,_Act,_VWit,_PWit,_Unmod,_AbsEvents) -> |
538 | | % special code to deal with *all* guards/actions (also in abstractions) + not extract guards from actions |
539 | | % TO DO: maybe move this to get_guard/get_action (see below) ; or maybe provide parameter to choose |
540 | | % in some circumstances (Deadlock checking) it is sufficient to look at lowest refinement level |
541 | | % for LTSMin,... : we need info from all refinement levels and from theorems |
542 | | % TO DO: look at _Unmod |
543 | | findall(A1,(get_full_eventb_action(RLEVENT,AA),member(A1,AA)),A) |
544 | | ; get_action(Operation,A) -> true |
545 | | ; add_error(b_get_read_write_guard_action,'Could not get action for operation: ',Operation), fail |
546 | | ), |
547 | | (is_list(A) % in case of Event-B events the action block is a list of assignments |
548 | | -> find_identifier_uses_l(A,LocalParas,RActionVars) % TO DO: use get_accessed_vars ? |
549 | | ; get_accessed_vars(A,LocalParas,_,RActionVars) |
550 | | ), |
551 | | list_to_ord_set(RActionVars,RActionVars1), |
552 | | rw_ord_intersection(RActionVars1,Read,ReadAction). |
553 | | |
554 | | :- dynamic b_get_operation_read_guard_vars_cache/4. |
555 | | reset_b_read_write_info :- retractall(b_get_operation_read_guard_vars_cache(_,_,_,_)). |
556 | | |
557 | | :- use_module(eventhandling,[register_event_listener/3]). |
558 | | :- register_event_listener(reset_specification,reset_b_read_write_info, |
559 | | 'Reset Read-Write Operation Cache.'). |
560 | | :- register_event_listener(reset_prob,reset_b_read_write_info, |
561 | | 'Reset Read-Write Operation Cache.'). |
562 | | |
563 | | |
564 | | % if JustVariables is true we return just the variables |
565 | | b_get_operation_read_guard_vars(Operation,JustVariables,VarsResult,VarsPrecise) :- |
566 | | (var(Operation) -> b_is_operation_name(Operation) ; true), |
567 | | b_get_operation_read_guard_vars2(Operation,JustVariables,VarsResult,VarsPrecise). |
568 | | % this gets the variables/identifiers read in the guard of an operation |
569 | | % Note: this part will include parts of the guard that depend on the parameters |
570 | | b_get_operation_read_guard_vars2(Operation,JustVariables,VarsResult,VarsPrecise) :- |
571 | | b_get_operation_read_guard_vars_cache(Operation,JustVariables,R,P),!, |
572 | | VarsResult=R, VarsPrecise=P. |
573 | | b_get_operation_read_guard_vars2(Operation,JustVariables,VarsResult,VarsPrecise) :- |
574 | | b_get_machine_operation_with_local_paras(Operation,Paras,_), |
575 | | get_operation_guard(Operation,G,Precise), |
576 | | % RGuardVars can comprise also local variables by operations with parameters |
577 | | find_identifier_uses(G,Paras,RGuardVars), |
578 | | list_to_ord_set(RGuardVars,ReadGuard), |
579 | | (JustVariables=false |
580 | | -> Result = ReadGuard |
581 | | ; b_get_machine_variables(TVars),get_texpr_ids(TVars,Vars), |
582 | | list_to_ord_set(Vars,Variables), |
583 | | ord_intersection(ReadGuard,Variables,Result) |
584 | | ), |
585 | | assertz(b_get_operation_read_guard_vars_cache(Operation,JustVariables,Result,Precise)), |
586 | | VarsResult=Result, VarsPrecise=Precise. |
587 | | |
588 | | |
589 | | % show full guards |
590 | | tcltk_get_guards_for_ops(list(Res)) :- |
591 | | findall([Name,' GUARD ',GS, 'PRECISE', Precise], |
592 | | (get_operation_guard(Name,G,Precise), |
593 | | translate:translate_bexpression(G,GS)), Res). |
594 | | |
595 | | % show guards which do not depend on parameters and the rest (the Action) |
596 | | tcltk_get_parameter_free_guards_for_ops(list(Res)) :- |
597 | | findall([Name,' LTSMIN-GUARD ',GS, 'ACTION ',AS], |
598 | | (get_operation_propositional_guards(Name,Guards,A),conjunct_predicates(Guards,G), |
599 | | translate:translate_bexpression(G,GS), |
600 | | translate:translate_substitution(A,AS)), Res). |
601 | | |
602 | | get_operation_guard(Operation,G,Precise) :- |
603 | | b_get_machine_operation(Operation,_RR,_P,TBody), |
604 | | get_texpr_expr(TBody,Body), |
605 | | get_operation_guard_aux(Body,Operation,G,Precise). |
606 | | |
607 | | :- use_module(probsrc(b_state_model_check), [get_guard_and_precision/3]). |
608 | | get_operation_guard_aux(RLEVENT,_,G,Precision) :- |
609 | | RLEVENT = rlevent(_Name,_Section,_Status,_Parameters,_Grd,_Thm,_Act,_VWit,_PWit,_Unmod,_AbsEvents), |
610 | | !, |
611 | | Precision = precise, % computation in Event-B is always precise, no while loops or sequences |
612 | | findall(G1,get_full_eventb_guard(RLEVENT,G1),Gs), |
613 | | conjunct_predicates(Gs,G). |
614 | | get_operation_guard_aux(_,Operation,G,Precise) :- get_guard_and_precision(Operation,G,Precise). |
615 | | |
616 | | |
617 | | % TO DO: we should move this code to b_state_model_check/predicate_debugger:get_operation_enabling_condition2 ? |
618 | | get_full_eventb_guard(rlevent(_Name,_Section,VariantStatus,_Parameters,Grd,Thms,_Act,_VWit,_PWit,_Unmod,AbsEvents),G) :- |
619 | | (G=Grd |
620 | | ; member(G,Thms) % this is relevant for LTS Min; but not for normal enabling analysis; |
621 | | % Note guard theorems are fully checked by interpreter |
622 | | ; b_operation_guards:get_variant_pred(VariantStatus,G) |
623 | | ; member(b(E,_,_),AbsEvents),get_full_eventb_guard(E,G)). |
624 | | get_full_eventb_action(rlevent(_Name,_Section,_Stat,_Parameters,_Grd,_Thm,Act,_VWit,_PWit,_Unmod,AbsEvents),A) :- |
625 | | (A=Act ; member(b(E,_,_),AbsEvents),get_full_eventb_action(E,A)). |
626 | | |
627 | | |
628 | | % Note: this is the LTSMin meaning of action (for LTSMin a guard depending on the parameter is part of the action) |
629 | | % Action will also contain parts of the guard that depend on parameters ! |
630 | | % This is important for independence |
631 | | % Example: CopyYY2XX(p) = PRE v>1 & p = yy THEN xx := p END --> Guard is #p.(p=yy) --> v>1 ; LTS Min Action is PRE p=yy THEN x := yy END; |
632 | | get_action(OpName,Action) :- get_operation_propositional_guards(OpName,_,Action). |
633 | | |
634 | | |
635 | | % we need just the variables that are read by the operation (constants will not be considered) |
636 | | b_get_read_write_vars(Operation,ReadGuardV,ReadActionV,ReadV,Write) :- |
637 | | b_get_read_write_vars_and_consts(false,Operation,ReadGuardV,ReadActionV,ReadV,Write). |
638 | | b_get_read_write_vars_and_consts(IncludeConstants,Operation,ReadGuardV,ReadActionV,ReadV,Write) :- |
639 | | b_get_read_write_guard_action(Operation,ReadGuard,ReadAction,Read,Write), |
640 | | b_get_machine_variables(TVars), |
641 | | (IncludeConstants==true |
642 | | -> b_get_machine_constants(TConsts), append(TConsts,TVars,TCV) % should we include get_global_identifiers ? |
643 | | ; TCV = TVars), |
644 | | get_texpr_ids(TCV,Vars), |
645 | | list_to_ord_set(Vars,Variables), |
646 | | ord_intersection(ReadGuard,Variables,ReadGuardV), |
647 | | ord_intersection(ReadAction,Variables,ReadActionV), |
648 | | ord_intersection(Read,Variables,ReadV). |
649 | | |
650 | | |
651 | | % ----------------------------------------------- |
652 | | % compute MAY/MUST write information (for LTSMin) |
653 | | % ----------------------------------------------- |
654 | | |
655 | | :- use_module(bsyntaxtree, [get_texpr_id/2, def_get_texpr_ids/2, is_truth/1, def_get_texpr_id/2]). |
656 | | |
657 | | % get read/write with may/must write info |
658 | | b_get_read_may_must_write(Operation,MustWriteV,MayWriteV) :- |
659 | | b_get_machine_operation(Operation,ReturnVars,_P,Body), |
660 | | b_get_read_write(Operation,_,WriteV), |
661 | | (is_eventb_event(Body) -> MayWriteV = [], |
662 | | WriteV = MustWriteV % in Event-B there are no conditions on the actions |
663 | | ; extract_must_write(Body,nested,MustWriteV1) -> |
664 | | % remove return values: they are not global variables written to |
665 | | def_get_texpr_ids(ReturnVars,RV),list_to_ord_set(RV,SRV), |
666 | | ord_subtract(MustWriteV1,SRV,MustWriteV), |
667 | | ord_subtract(WriteV,MustWriteV,MayWriteV) |
668 | | ; add_internal_error('Extraction of Must-Write failed: ',extract_must_write(Body,nested,_)), |
669 | | MustWriteV=[], MayWriteV=WriteV). |
670 | | /* |
671 | | b_get_read_may_must_write(Operation,ReadGuardV,ReadActionV,ReadV,MustWriteV,MayWriteV,WriteV) :- |
672 | | b_get_read_write_vars_and_consts(true,Operation,ReadGuardV,ReadActionV,ReadV,WriteV), |
673 | | b_get_machine_operation(Operation,ReturnVars,_P,Body), |
674 | | (is_eventb_event(Body) -> MayWriteV = [], MustWriteV=WriteV % in Event-B there are no conditions on the actions |
675 | | ; extract_must_write(Body,nested,MustWriteV1) -> |
676 | | % remove return values: they are not global variables written to |
677 | | def_get_texpr_ids(ReturnVars,RV),list_to_ord_set(RV,SRV), |
678 | | ord_subtract(MustWriteV1,SRV,MustWriteV), |
679 | | ord_subtract(WriteV,MustWriteV,MayWriteV) |
680 | | ; add_internal_error('Extraction of Must-Write failed: ',extract_must_write(Body,_)), |
681 | | MustWriteV=[], MayWriteV=WriteV). |
682 | | */ |
683 | | is_eventb_event(b(E,_,_)) :- |
684 | | E = rlevent(_Name,_Section,_Status,_Params,_Guard,_Theorems,_Actions,_VWitnesses,_PWitnesses,_Unmod,_AbstractEvents). |
685 | | |
686 | | % compute the variables that must be written by an operation if it is enabled |
687 | ? | extract_must_write(b(Sub,_,_),Nested,MV) :- extract_must_write_aux(Sub,Nested,MV),!. |
688 | | extract_must_write(O,N,MV) :- add_internal_error('Call failed: ',extract_must_write(O,N,MV)),MV=[]. |
689 | | |
690 | | |
691 | | %extract_must_write_aux(P,V) :- functor(P,F,N), print(ex_aux(F,N)),nl,fail. |
692 | | extract_must_write_aux(assign(LHS,_),_,V) :- maplist(get_id,LHS,Vars), list_to_ord_set(Vars,V). |
693 | | extract_must_write_aux(assign_single_id(TID,_),_,[ID]) :- get_id(TID,ID). |
694 | | extract_must_write_aux(becomes_such(LHS,_),_,V) :- maplist(get_id,LHS,Vars), list_to_ord_set(Vars,V). |
695 | | extract_must_write_aux(becomes_element_of(LHS,_),_,V) :- maplist(get_id,LHS,Vars), list_to_ord_set(Vars,V). |
696 | | extract_must_write_aux(precondition(_,TBody),Nested,MV) :- extract_must_write(TBody,Nested,MV). |
697 | | extract_must_write_aux(assertion(_,TBody),Nested,MV) :- extract_must_write(TBody,Nested,MV). |
698 | | extract_must_write_aux(witness_then(_,TBody),Nested,MV) :- extract_must_write(TBody,Nested,MV). |
699 | | extract_must_write_aux(any(_,_,TBody),Nested,MV) :- extract_must_write(TBody,Nested,MV). |
700 | | extract_must_write_aux(let(_,_,TBody),Nested,MV) :- extract_must_write(TBody,Nested,MV). |
701 | | extract_must_write_aux(lazy_let_subst(_,_,TBody),Nested,MV) :- extract_must_write(TBody,Nested,MV). |
702 | | extract_must_write_aux(block(TBody),Nested,MV) :- extract_must_write(TBody,Nested,MV). |
703 | | extract_must_write_aux(choice([A]),Nested,MV) :- !, extract_must_write(A,Nested,MV). |
704 | | extract_must_write_aux(choice([A|T]),Nested,MV) :- |
705 | | extract_must_write(A,Nested,MV1), extract_must_write_aux(choice(T),Nested,MV2), ord_intersection(MV1,MV2,MV). |
706 | | extract_must_write_aux(if([I]),Nested,MV) :- !, get_texpr_expr(I,if_elsif(Cond,TBody)), |
707 | | (is_truth(Cond) -> extract_must_write(TBody,Nested,MV) ; MV=[]). % we have ELSE skip and then write nothing |
708 | | extract_must_write_aux(if([I|T]),Nested,MV) :- get_texpr_expr(I,if_elsif(_,TBody)), |
709 | | extract_must_write(TBody,Nested,MV1), extract_must_write_aux(if(T),Nested,MV2), ord_intersection(MV1,MV2,MV). |
710 | | extract_must_write_aux(select([SW]),Nested,MV) :- !, |
711 | | get_texpr_expr(SW,select_when(_,TBody)),extract_must_write(TBody,Nested,MV). |
712 | | extract_must_write_aux(select([SW|T]),Nested,MV) :- |
713 | | get_texpr_expr(SW,select_when(_,TBody)), extract_must_write(TBody,Nested,MV1), |
714 | | extract_must_write_aux(select(T),Nested,MV2),ord_intersection(MV1,MV2,MV). |
715 | | extract_must_write_aux(select(T,Else),Nested,MV) :- |
716 | | extract_must_write(Else,Nested,MV1), |
717 | | extract_must_write_aux(select(T),Nested,MV2),ord_intersection(MV1,MV2,MV). |
718 | | extract_must_write_aux(case(_,[],Else),Nested,MV) :- extract_must_write(Else,Nested,MV). |
719 | | extract_must_write_aux(case(E,[C|T],Else),Nested,MV) :- |
720 | | get_texpr_expr(C,case_or(_,TBody)), |
721 | | extract_must_write(TBody,Nested,MV1), |
722 | | extract_must_write_aux(case(E,T,Else),Nested,MV2),ord_intersection(MV1,MV2,MV). |
723 | | extract_must_write_aux(parallel([A]),Nested,MV) :- !, extract_must_write(A,Nested,MV). |
724 | | extract_must_write_aux(parallel([A|T]),Nested,MV) :- |
725 | | extract_must_write(A,Nested,MV1), extract_must_write_aux(parallel(T),Nested,MV2), ord_union(MV1,MV2,MV). |
726 | | extract_must_write_aux(sequence(S),Nested,MV) :- !, extract_must_write_aux(parallel(S),Nested,MV). |
727 | | extract_must_write_aux(skip,_,[]). |
728 | | extract_must_write_aux(external_subst_call(_,_),_,[]). % assume there is no guarantee that something is written |
729 | | extract_must_write_aux(while(_,_,_,_),_,[]). % in case the condition is false, the loop is not entered |
730 | | extract_must_write_aux(var(Parameters,TBody),Nested,MV) :- |
731 | | def_get_texpr_ids(Parameters,IDs), |
732 | | list_to_ord_set(IDs,MV2), |
733 | | extract_must_write(TBody,Nested,MV1), |
734 | | ord_subtract(MV1,MV2,MV). |
735 | | extract_must_write_aux(operation_call(TOperation,Results,_),Nested,MustWriteV) :- |
736 | | maplist(get_id,Results,Vars), list_to_ord_set(Vars,WrittenResults), |
737 | | (Nested=not_nested -> MustWriteV=WrittenResults |
738 | | ; def_get_texpr_id(TOperation,op(Operation)), |
739 | | b_get_read_may_must_write(Operation,MustWriteVInner,_), %and ideally cache values |
740 | | ord_union(WrittenResults,MustWriteVInner,MustWriteV) |
741 | | ). |
742 | | extract_must_write_aux(Other,_,[]) :- |
743 | | add_message('Uncovered substitution for must_write computation: ',Other). |
744 | | |
745 | | % get id of LHS assignment |
746 | | get_id(b(E,_,_),ID) :- get_id_aux(E,ID). |
747 | | get_id_aux(function(A,_),ID) :- get_id(A,ID). |
748 | | get_id_aux(record_field(A,_),ID) :- get_id(A,ID). |
749 | | get_id_aux(identifier(ID),ID). |
750 | | |
751 | | % mainly used for LTS Min (uses LTSMin's definition of Action and (parameter-free) Guard): |
752 | | tcltk_read_write_matrix(list([list(['Operation','ReadsGuard','ReadsAction','MustWrite','MayWrite'])|Result])) :- |
753 | | findall(list([Operation,RG,RA,MustW,MayW]),b_get_read_may_must_write_for_ltsmin(Operation,RG,RA,MustW,MayW),Result). |
754 | | |
755 | | :- use_module(bsyntaxtree,[get_global_identifiers/1]). |
756 | | % obtains the read and written variables (constants, SETS and enumerated elements not included) |
757 | | % seperates the operations into parameter-independent guards and the rest, the Action |
758 | | :- use_module(b_operation_guards,[get_operation_propositional_guards/3]). |
759 | | :- use_module(probsrc(bmachine),[b_get_machine_operation_for_animation/4]). |
760 | | :- use_module(bmachine,[b_top_level_operation/1]). |
761 | | |
762 | | |
763 | | get_operation(Operation,Results,Paras) :- |
764 | | b_top_level_operation(Operation), |
765 | | b_get_machine_operation_for_animation(Operation,Results,Paras,_TB). |
766 | | |
767 | | b_get_read_may_must_write_for_ltsmin(Operation,ReadsGuard,ReadsAction,MustW,MayW) :- |
768 | | get_operation(Operation,Results,Paras), |
769 | | % LTSmin needs the constants in the read matrices; |
770 | | % if they are not included, it will transfer the uninitialised constants from the root state |
771 | | %b_get_machine_constants(TConsts), append(Paras,TConsts,TP), |
772 | | append(Results, Paras, ResultsAndParas), |
773 | | get_texpr_ids(ResultsAndParas,TPIds), |
774 | | get_global_identifiers(Global), |
775 | | append(TPIds,Global,PIDs),list_to_ord_set(PIDs,VarsToIgnore), % the VarsToIgnore should not be included in the matrix |
776 | | b_get_read_may_must_write(Operation,MustW,MayW), % we ignore the info about guards and actions: LTS Min has another definition of what a guard is than b_get_read_may_must_write |
777 | | get_operation_propositional_guards(Operation,Guards,RestBody), % obtain LTSMin guards, which are independent of the parameters |
778 | | get_accessed_vars(RestBody,VarsToIgnore,_Modified,ReadsAction), % this computes the ReadsAction the LTSMin way |
779 | | %format('READS ACTION for ~w ~n ~w~n',[Operation,ReadsAction]), |
780 | | conjunct_predicates(Guards,G), |
781 | | find_identifier_uses(G,VarsToIgnore,ReadsGuard). |
782 | | % (op_has_parameters(Operation) |
783 | | % -> ReadsAction = FullReads % guards on parameters may be projected away; leading to implicit reads in actions |
784 | | % % TO DO: convert operations into normal form; until this is done: simply return full reads |
785 | | % ; ReadsAction = RA). |
786 | | |
787 | | %op_has_parameters(OpName) :- |
788 | | % b_get_machine_operation_for_animation(OpName,_Results,Paras,_TB), |
789 | | % Paras = [_|_]. |
790 | | |
791 | | |
792 | | rw_ord_intersection('$all',B,R) :- !,R=B. |
793 | | rw_ord_intersection(A,'$all',R) :- !,R=A. |
794 | | rw_ord_intersection(A,B,R) :- ord_intersection(A,B,R). |
795 | | |
796 | | |
797 | | % compute the variables that are written by at least one operation: |
798 | | b_get_written_variables(WrittenVariables) :- |
799 | | findall(Var, |
800 | | (b_top_level_operation(Operation), |
801 | | %get_operation(Operation,_Results,_Paras), |
802 | | b_get_read_write(Operation,_,WriteV), |
803 | | member(Var,WriteV) |
804 | | ),Vars), |
805 | | sort(Vars,WrittenVariables). |
806 | | |
807 | | written_by_not_top_level_operation(Variable,Operation) :- |
808 | | b_get_read_write(Operation,_,WriteV), |
809 | | \+ b_top_level_operation(Operation), |
810 | | member(Variable,WriteV). |
811 | | |
812 | | % compute the variables that are read by at least one operation: |
813 | | b_get_read_variables(ReadVariables) :- |
814 | | findall(Var, |
815 | | (b_top_level_operation(Operation), |
816 | | b_get_read_write(Operation,ReadV,_), |
817 | | member(Var,ReadV), |
818 | | b_is_variable(Var) |
819 | | ),Vars), |
820 | | sort(Vars,ReadVariables). |
821 | | % compute the variables that are read by at least one operation: |
822 | | b_get_read_guard_variables(ReadVariables) :- |
823 | | findall(Var, |
824 | | (b_top_level_operation(Operation), |
825 | | b_get_operation_read_guard_vars(Operation,true,ReadV,_), |
826 | | member(Var,ReadV), |
827 | | b_is_variable(Var) |
828 | | ),Vars), |
829 | | sort(Vars,ReadVariables). |
830 | | |
831 | | % cannot be run in bmachine_static_checks as operations are not yet precompiled |
832 | | check_all_variables_written :- |
833 | | b_get_written_variables(WrittenVariables), |
834 | | b_get_machine_variables(TVars), |
835 | | member(TVar,TVars), |
836 | | get_texpr_id(TVar,Var), |
837 | | \+ ord_member(Var,WrittenVariables), |
838 | | (written_by_not_top_level_operation(Var,Op2) |
839 | | -> ajoin(['Variable is written by no top-level operation (but is written by ',Op2,'): '],Msg), |
840 | | add_warning(check_all_variables_written,Msg,Var,TVar) |
841 | | ; add_warning(check_all_variables_written,'Variable is written by no operation (consider defining it as a constant): ',Var,TVar) |
842 | | ), |
843 | | fail. |
844 | | check_all_variables_written. |
845 | | |
846 | | % compute a matrix which shows for variables who reads and modifies them: |
847 | | tcltk_variable_read_write_matrix(Warn,list([list(['Variable','Read by','Written by'])|Result])) :- |
848 | | findall(list([Variable,Reads,Writes]), |
849 | | get_read_write_ops_for_variable(Variable,Reads,Writes),Result), |
850 | | (Warn=no_check -> true ; check_all_variables_written). |
851 | | |
852 | | get_read_write_ops_for_variable(Var,ReadOps,WriteOps) :- |
853 | | b_get_machine_variables(TVars), member(TVar,TVars), get_texpr_id(TVar,Var), |
854 | | findall(Op,(b_top_level_operation(Op),b_get_read_write(Op,Reads,_),ord_member(Var,Reads)),ReadOps), |
855 | | findall(Op,(b_top_level_operation(Op),b_get_read_write(Op,_,Writes),ord_member(Var,Writes)),WriteOps). |
856 | | |
857 | | |
858 | | % -------------------- READ/WRITE VARIABLE ANALYSIS GRAPH ------------------------- |
859 | | |
860 | | :- use_module(dotsrc(dot_graph_generator), [gen_dot_graph/3]). |
861 | | tcltk_dot_variable_modification_analysis(File) :- |
862 | | gen_dot_graph(File,var_op_node_predicate,var_mod_pred). |
863 | | |
864 | | |
865 | | :- use_module(tools_strings,[ajoin/2]). |
866 | | :- use_module(preferences,[get_preference/2]). |
867 | | var_op_node_predicate(NodeID,SubGraph,NodeDesc,Shape,Style,Color) :- |
868 | | b_get_written_variables(WrittenVariables), |
869 | | b_get_read_variables(ReadVariables), |
870 | | b_get_read_guard_variables(ReadGrdVariables), |
871 | | SubGraph=none, |
872 | | Color='RoyalBlue4', |
873 | | b_is_variable(NodeID), |
874 | | (ord_member(NodeID,ReadGrdVariables) |
875 | | -> Style=none |
876 | | ; get_preference(dot_var_mod_hide_only_written,false), |
877 | | Style=dotted |
878 | | ), |
879 | | (\+ ord_member(NodeID,WrittenVariables) |
880 | | -> Shape=invtrapezium, ajoin([NodeID,'\\n (NOT WRITTEN)'],NodeDesc) |
881 | | ; \+ ord_member(NodeID,ReadVariables) |
882 | | -> Shape=trapezium, ajoin([NodeID,'\\n (NOT READ)'],NodeDesc) |
883 | | ; Shape=box, NodeDesc=NodeID |
884 | | ). |
885 | | var_op_node_predicate(NodeID,SubGraph,NodeDesc,Shape,Style,Color) :- |
886 | | b_top_level_operation(NodeID), |
887 | | %b_get_read_write(NodeID,_,Writes), dif(Writes,[]), % do not show query operations |
888 | | SubGraph=none, Shape=rect, Style='filled,rounded', |
889 | | NodeDesc=NodeID, Color=gray90. |
890 | | |
891 | | |
892 | | :- use_module(translate,[translate_bexpression_with_limit/3]). |
893 | | var_mod_pred(OpNodeID,Label,SuccID,Color,Style) :- Color=red4, Style=solid, % writes variable |
894 | | b_get_read_guard_variables(ReadVariables), |
895 | | b_get_read_may_must_write(OpNodeID,MustWrite,_), |
896 | | member(SuccID,MustWrite), |
897 | | (get_preference(dot_var_mod_hide_only_written,true) |
898 | | -> ord_member(SuccID,ReadVariables) ; true), |
899 | | (get_assignment_value(OpNodeID,SuccID,Val) |
900 | | -> translate_bexpression_with_limit(Val,30,VS), |
901 | | ajoin([':= \\n',VS],Label) |
902 | | ; Label = writes). |
903 | | var_mod_pred(OpNodeID,Label,SuccID,Color,Style) :- Color=goldenrod, Label='may write',Style=dotted, |
904 | | b_get_read_guard_variables(ReadVariables), |
905 | | b_get_read_may_must_write(OpNodeID,_,MayWrite), |
906 | | member(SuccID,MayWrite), |
907 | | (get_preference(dot_var_mod_hide_only_written,true) |
908 | | -> ord_member(SuccID,ReadVariables) ; true). |
909 | | var_mod_pred(NodeID,Label,SuccOpID,Color,Style) :- Color=azure4, Style=solid, % reads variable |
910 | | %b_get_read_write(SuccOpID,Reads,_), |
911 | | %b_get_read_may_must_write_for_ltsmin(SuccOpID,ReadsGuard,_,_,_), |
912 | | b_get_operation_read_guard_vars(SuccOpID,true,ReadsGuard,_), |
913 | | member(NodeID,ReadsGuard), |
914 | | (get_decisive_predicate_from_guard(SuccOpID,NodeID,_Pred,Op,Val) |
915 | | -> translate_bexpression_with_limit(Val,30,VS), |
916 | | ajoin([Op,'\\n',VS],Label) |
917 | | ; Label = reads). |
918 | | var_mod_pred(NodeID,Label,SuccOpID,Color,Style) :- Color=azure3, Style=solid, Label = reads_body, |
919 | | b_get_read_variables(ReadVariables), |
920 | | b_get_read_write(SuccOpID,ReadV,_), |
921 | | b_get_operation_read_guard_vars(SuccOpID,true,ReadsGuard,_), |
922 | | member(NodeID,ReadV), nonmember(NodeID,ReadsGuard), member(NodeID,ReadVariables). |
923 | | |
924 | | % try to find relevant, decisive checks from guard |
925 | | get_decisive_predicate_from_guard(Operation,Variable,TPred,Op,TVal) :- |
926 | | get_operation_guard(Operation,G,_), |
927 | | member_in_conjunction(TPred,G), |
928 | | get_texpr_expr(TPred,Pred), |
929 | | is_decisive(Pred,Variable,Op,TVal). |
930 | | |
931 | | is_decisive(equal(TID,VAL),Variable,'=',VAL) :- get_texpr_id(TID,Variable), simple_val(VAL). |
932 | | is_decisive(equal(VAL,TID),Variable,'=',VAL) :- get_texpr_id(TID,Variable), simple_val(VAL). |
933 | | % TO DO: other checks ? not_equal, greater, ... |
934 | | simple_val(b(V,_,_)) :- simple_val2(V). |
935 | | simple_val2(empty_set). |
936 | | simple_val2(empty_sequence). |
937 | | simple_val2(boolean_true). |
938 | | simple_val2(boolean_false). |
939 | | simple_val2(integer(_)). |
940 | | simple_val2(string(_)). |
941 | | simple_val2(max_int). |
942 | | simple_val2(min_int). |
943 | | simple_val2(value(_)). |
944 | | |
945 | | get_assignment_value(Operation,Variable,Val) :- |
946 | | get_action(Operation,TAction), |
947 | | get_texpr_expr(TAction,Action), |
948 | | get_assign_val(Action,Variable,Val). |
949 | | |
950 | | get_assign_val(assign_single_id(TID,Val),ID,Val) :- |
951 | | get_texpr_id(TID,ID),!, |
952 | | (simple_val(Val) -> true ; simple_modification(Val,ID)). |
953 | | get_assign_val(any(_,_,TBody),Var,Val) :- get_texpr_expr(TBody,Body), get_assign_val(Body,Var,Val). |
954 | | get_assign_val(select([b(select_when(_,TBody),_,_)]),Var,Val) :- |
955 | | get_texpr_expr(TBody,Body), get_assign_val(Body,Var,Val). |
956 | | get_assign_val(parallel(Bodies),Var,Val) :- member(TBody,Bodies), |
957 | | get_texpr_expr(TBody,Body), get_assign_val(Body,Var,Val). |
958 | | % TO DO: sequence, ... |
959 | | |
960 | | simple_modification(TExpr,Var) :- get_texpr_expr(TExpr,Expr), |
961 | | simple_modification_aux(Expr,Var). |
962 | | simple_modification_aux(add(A,B),Var) :- simple_binop(A,B,Var). |
963 | | simple_modification_aux(minus(A,B),Var) :- simple_binop(A,B,Var). |
964 | | simple_modification_aux(multiplication(A,B),Var) :- simple_binop(A,B,Var). |
965 | | |
966 | | simple_binop(TID,Val,Var) :- get_texpr_id(TID,Var), simple_val(Val). |
967 | | simple_binop(Val,TID,Var) :- get_texpr_id(TID,Var), simple_val(Val). |
968 | | |