1 % (c) 2009-2024 Lehrstuhl fuer Softwaretechnik und Programmiersprachen,
2 % Heinrich Heine Universitaet Duesseldorf
3 % This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html)
4
5 /*
6 * This module allows to register predicates as "debugging predicates".
7 * When such a predicate is marked by the prefix operator ~~,
8 * during term expansion, the term is replaced by:
9 * - if remove_debug_calls is true:
10 * one of the term's arguments, namely the one that is marked
11 * by a star (*) during registration
12 * - if remove_debug_calls is false:
13 * only the prefix operator ~~ is removed, leaving the rest
14 * of the term unchanged.
15 *
16 * Example:
17 *
18 * :- register_debugging_call(log(-,*)).
19 * log(Info,X) :-
20 * print(Info),print(' enter: '),print(X),nl,
21 * call(X),
22 * print(Info),print(' leave: '),print(X),nl.
23 *
24 * :- enable_debugging_calls.
25 * p1 :- ~~log(p1, member(X,[a])).
26 * :- disable_debugging_calls.
27 * p2 :- ~~log(p2, member(X,[a])).
28 *
29 * a call of p1 prints:
30 * p1 enter: member(_123,[a])
31 * p1 leave: member(a,[a])
32 * while a call to p2 prints nothing
33 *
34 */
35
36 :- module(debugging_calls,[register_debugging_call/1,
37 register_debugging_calls/1,
38 enable_debugging_calls/0,
39 disable_debugging_calls/0,
40 remove_debugging_calls/4 % called by term expander
41 ]).
42
43 :- use_module(library(lists)).
44 :- use_module(library(codesio)).
45
46 :- use_module(module_information,[module_info/2]).
47
48 :- module_info(group,infrastructure).
49 :- module_info(description,'This module provides functionality to specify meta-calls that (like a must-not-fail call) that are short-circuited when compiling production code.').
50
51 :- set_prolog_flag(double_quotes, codes).
52
53 :- op(300, fy, ~~).
54
55 :- dynamic remove_debug_calls/0.
56 % remove_debug_calls.
57
58 :- dynamic is_debug_call/3.
59
60 safe_atom_codes(A,C) :-
61 catch(atom_codes(A,C),
62 error(representation_error(max_atom_length),_),
63 (print(exception(max_atom_length)),nl,A='???')).
64
65 %avl_term(X) :- var(X),!,fail.
66 %avl_term(avl_set(_)).
67 %avl_term((A,B)):- (avl_term(A) -> true ; avl_term(B)).
68 %avl_term([H|_]) :- avl_term(H).
69
70 get_current_position(Line,Col,Module,File) :-
71 prolog_load_context(module, Module),
72 prolog_load_context(file, File),
73 prolog_load_context(term_position,TPos), % gives something like $stream_position(18031,18031,340,21)
74 !,
75 (TPos= '$stream_position'(_,_,Line,Col) -> true ; Line='?', Col='?').
76 get_current_position('?','?',Module,'?') :-
77 prolog_load_context(module, Module),!.
78 get_current_position('?','?','?','?').
79
80 safe_functor(Var,F,N) :- var(Var),!, F='$VAR', N=0.
81 safe_functor(X,F,N) :- functor(X,F,N).
82
83 % built-ins to observe before their execution where we observe one argument
84 observe_built_in(assert(P),assert,P).
85 observe_built_in(asserta(P),asserta,P).
86 observe_built_in(assertz(P),assertz,P).
87 observe_built_in(ground(P),ground,P).
88 observe_built_in(copy_term(P,_),copy_term,P).
89 observe_built_in(copy_term(P,_,_),copy_term,P).
90 observe_built_in(term_variables(P,_),term_variables,P).
91 observe_built_in(term_hash(P,_),term_hash,P).
92 observe_built_in(term_hash(P,_,_),term_hash,P).
93 observe_built_in(number_vars(P,_,_),term_variables,P).
94 observe_built_in(acyclic_term(P),acyclic_term,P).
95
96 % operators with two arguments which must both be large and which compare the arguments
97 observe_built_in2('/=='(P,Q),'/==',P,Q) :- \+ atom(Q), \+ atom(P).
98 observe_built_in2('/='(P,Q),'/=',P,Q) :- \+ atom(Q), \+ atom(P), Q \== term(undefined).
99 observe_built_in2('=='(P,Q),'==',P,Q) :- \+ atom(Q), \+ atom(P), Q \== term(undefined).
100 observe_built_in2('@<'(P,Q),'@<',P,Q) :- \+ atom(Q), \+ atom(P), Q \== term(undefined).
101 observe_built_in2('@>'(P,Q),'@>',P,Q) :- \+ atom(Q), \+ atom(P), Q \== term(undefined).
102 observe_built_in2(dif(P,Q),dif,P,Q) :- \+ atom(Q), \+ atom(P).
103 observe_built_in2(unify_with_occurs_check(P,Q),unify_with_occurs_check,P,Q) :- \+ atom(Q), \+ atom(P).
104 observe_built_in2(subsumes_term(P,Q),subsumes_term,P,Q) :- \+ atom(Q), \+ atom(P).
105
106
107 % built-ins to observe after their execution
108 observe_built_in_post(frozen(_,P),frozen,P).
109 observe_built_in_post(retract(P),retract,P).
110
111
112 ignore_module(aggregate).
113 ignore_module(assoc).
114 ignore_module(avl).
115 ignore_module(a_star).
116 ignore_module(binomialheap).
117 ignore_module(builtins).
118 ignore_module(chr). % prevent inserting observation code into some library modules
119 ignore_module(chr_compiler_utility).
120 ignore_module(chr_hashtable_store).
121 ignore_module(chr_runtime).
122 ignore_module(chr_translate).
123 ignore_module(clpfd).
124 ignore_module(fastrw).
125 ignore_module(guard_entailment).
126 ignore_module(heaps).
127 ignore_module(hpattvars).
128 ignore_module(hprolog).
129 ignore_module(listmap).
130 ignore_module(lists).
131 ignore_module(mutdict).
132 ignore_module(pairlist).
133 ignore_module(plunit).
134 ignore_module(random).
135 ignore_module(samsort).
136 ignore_module(system).
137 ignore_module(terms).
138 ignore_module(xml).
139
140 ignore_module(chr_integer_inequality).
141
142
143
144 :- load_files(library(system), [when(compile_time), imports([environ/2])]).
145
146 remove_debugging_calls(Layout,Term,Layout,Term) :- var(Term),!.
147 % comment in to see nl (newline term_expansion) info with :- set_prolog_flag(source_info,on).
148 % and be sure to enable user:term_expansion in debugging_calls_te.pl
149 %remove_debugging_calls(Layout,nl,Layout,format(' line:~w col:~w in ~w (~w)~n',[L,C,CM,CF])) :- !, get_current_position(L,C,CM,CF).
150 :- if(environ(prob_debug_flag,true)).
151
152
153 remove_debugging_calls(_,_Call,_,_) :-
154 prolog_load_context(module, CurModule),
155 ignore_module(CurModule),!,
156 %get_current_position(L,C,CM,CF),format(' ignoring >> ~w at line:~w col:~w in ~w (~w)~n',[CurModule,L,C,CM,CF]),
157 fail.
158 remove_debugging_calls(Layout,Call,Layout,
159 (debugging_calls:safe_functor(P,F,N),terms:term_size(P,Sze),
160 (Sze>100 -> (Sze>10000 -> LRG=' ***LARGE***' ; LRG=''),
161 format(' ~w(~w/~w - sze:~w~w) line:~w col:~w in ~w (~w)~n',[BI,F,N,Sze,LRG,L,C,CM,CF]) ; true),
162 Call) ) :-
163 observe_built_in(Call,BI,P), !, get_current_position(L,C,CM,CF).
164 remove_debugging_calls(Layout,Call,Layout,
165 (debugging_calls:safe_functor(P,F,N),terms:term_size(P,Sze),
166 debugging_calls:safe_functor(Q,F2,N2),terms:term_size(Q,Sze2),
167 (Sze>100,Sze2>100,
168 (F,N)=(F2,N2) % otherwise the terms are obviously different and the calls cannot be expensive
169 -> format(' ~w(~w/~w - ~w <-> ~w/~w - ~w) line:~w col:~w in ~w (~w)~n',[BI,F,N,Sze,F2,N2,Sze2,L,C,CM,CF])
170 ; true), Call) ) :-
171 observe_built_in2(Call,BI,P,Q), !, get_current_position(L,C,CM,CF).
172 remove_debugging_calls(Layout,Call,Layout,
173 (Call,debugging_calls:safe_functor(P,F,N),terms:term_size(P,Sze),
174 (Sze>100 -> format(' ~w(~w/~w - ~w) line:~w col:~w in ~w (~w)~n',[BI,F,N,Sze,L,C,CM,CF]) ; true)) ) :-
175 observe_built_in_post(Call,BI,P), !, get_current_position(L,C,CM,CF).
176 remove_debugging_calls(Layout,time_out(P,TO,Res),Layout,
177 (print(time_out(P)),nl,functor(P,F,N),
178 format(' time_out(~w/~w,~w) line:~w col:~w in ~w (~w)~n', [F,N,TO,L,C,CM,CF]),
179 time_out(P,TO,Res))) :- !,
180 get_current_position(L,C,CM,CF).
181 % comment in lines below to see retracts; be sure to enable user:term_expansion in debugging_calls_te.pl
182 %remove_debugging_calls(Layout,retractall(P),Layout,
183 % (retractall(P),functor(P,F,N),format(' retractall(~w/~w) line:~w col:~w in ~w (~w)~n',[F,N,L,C,CM,CF]))) :- !, get_current_position(L,C,CM,CF). % for some reason retractall is sometimes expensive
184 % a few other calls to observe
185 %remove_debugging_calls(Layout,arg(Nr,T,A),Layout,(format(' line:~w col:~w in ~w (~w)~n',[L,C,CM,CF]),arg(Nr,T,A))) :- !, get_current_position(L,C,CM,CF).
186 %remove_debugging_calls(Layout,call_cleanup(P,P2),Layout,(functor(P,F,N),format(' call_cleanup(~w/~w) line:~w col:~w in ~w (~w)~n',[F,N,L,C,CM,CF]),call_cleanup(P,P2))) :- !, get_current_position(L,C,CM,CF).
187 %remove_debugging_calls(Layout,on_exception(Exc,P,P2),Layout,(functor(P,F,N),format(' on_exception(~w/~w) line:~w col:~w in ~w (~w)~n',[F,N,L,C,CM,CF]),on_exception(Exc,P,P2))) :- !, get_current_position(L,C,CM,CF).
188 % comment in next clause to see nesting of call_cleanup calls:
189 %remove_debugging_calls(Layout,call_cleanup(P,P2),Layout,
190 % (debugging_calls:get_functor(P,F,N),debugging_calls:inc_indent,write(enter),FCall,
191 % call_cleanup(P,(P2,debugging_calls:dec_indent,write(exit),FCall)))) :- !,
192 % FCall = format('_call_cleanup(~w/~w) line:~w col:~w in ~w (~w)~n',[F,N,L,C,CM,CF]), get_current_position(L,C,CM,CF).
193 :- endif.
194 remove_debugging_calls(LayoutIn,when(Cond,Body),LayoutOut,ResTerm) :- !,
195 ResTerm = when(Cond,NewBody), % do not expand inside condition
196 (LayoutIn = []
197 -> LayoutBody = [],
198 LayoutOut = []
199 ; LayoutIn = [LayoutWhen,LayoutCond,LayoutBody],
200 LayoutOut = [LayoutWhen,LayoutCond,LayoutBodyOut]),
201 remove_debugging_calls(LayoutBody,Body,LayoutBodyOut,NewBody).
202 %remove_debugging_calls(Layout,ground(Term),Layout,Term2) :- !, Term2 = (nonvar(Term),(debugging_calls:avl_term(Term) -> print(ground_check(Term)),nl, trace ; true),ground(Term)). %% comment in to observe ground checks
203 remove_debugging_calls(LayoutIn,Term,Layout2,Term2) :-
204 functor(Term, ~~, 1),
205 arg(1,Term,Term1),
206 nonvar(Term1),!,functor(Term1,Functor,Arity),
207 ( LayoutIn = [] -> Layout1 = []
208 ; LayoutIn = [_,Layout1]),
209 ( is_debug_call(Functor,Arity,CallPos) ->
210 ( remove_debug_calls ->
211 safe_arg(CallPos,Term1,TermI),
212 ( Layout1 = [] -> LayoutI = []
213 ; Layout1 = [_|ArgLayout] ->
214 nth1(CallPos,ArgLayout,LayoutI)),
215 remove_debugging_calls(LayoutI,TermI,Layout2,Term2)
216 ;
217 remove_debugging_calls(Layout1,Term1,Layout2,Term2))
218 ;
219 write_to_codes(Functor/Arity,M1),
220 append(["encountered unregistered ",M1,
221 " tagged for debugging removal"],M3),
222 safe_atom_codes(Msg1,M3),
223 print_message(error,Msg1),
224 Term2 = Term1
225 ).
226 %remove_debugging_calls(LayoutIn,on_exception(E,Call,Code),LayoutOut,Term2) :- !,
227 % LayoutOut = LayoutIn,
228 % print(on_exception(E,Call,Code)),nl,
229 % Term2 = on_exception(E,Call,(format('~n**EXCEPTION = ~w~n~n',[E]),Code)).
230 %remove_debugging_calls(LayoutIn,time_out(Call,TO,Res),LayoutOut,Term2) :- !,
231 % LayoutOut = LayoutIn,
232 % print(time_out(Call,TO,Res)),nl,
233 % Term2 = ((Call = _:C -> true ; Call=C),
234 % functor(C,F,N),format('**TIMEOUT CALL ~w/~w = ~w ms~n',[F,N,TO]),
235 % time_out(Call,TO,Res),
236 % (Res=time_out -> format('**TIMEOUT OCCURED ~w/~w after ~w ms~n',[F,N,TO]) ; true)).
237 remove_debugging_calls(LayoutIn,Term1,LayoutOut,Term2) :-
238 !,functor(Term1,Functor,Arity),
239 functor(Term2,Functor,Arity),
240 ( LayoutIn = [] ->
241 LayoutOut = [],
242 remove_debugging_calls_args_nl(1,Arity,Term1,Term2)
243 ; LayoutIn = [Pos|Layout1] ->
244 LayoutOut = [Pos|Layout2],
245 remove_debugging_calls_args(Layout1,Term1,Layout2,Term2,1)
246 ;
247 LayoutIn = LayoutOut,
248 Term1 = Term2).
249
250
251 remove_debugging_calls_args([],_,[],_,_).
252 remove_debugging_calls_args([L1|Layout1],Term1,[L2|Layout2],Term2,N) :-
253 safe_arg(N,Term1,In), safe_arg(N,Term2,Out),
254 remove_debugging_calls(L1,In,L2,Out),
255 N2 is N+1,
256 remove_debugging_calls_args(Layout1,Term1,Layout2,Term2,N2).
257
258 safe_arg(N,Term,Arg) :- compound(Term), arg(N,Term,Arg).
259
260 remove_debugging_calls_args_nl(N,Max,_,_) :- N > Max,!.
261 remove_debugging_calls_args_nl(N,Max,Term1,Term2) :-
262 N =< Max,
263 safe_arg(N,Term1,In), safe_arg(N,Term2,Out),
264 remove_debugging_calls([],In,_,Out),
265 N2 is N+1,
266 remove_debugging_calls_args_nl(N2,Max,Term1,Term2).
267
268 register_debugging_calls([]).
269 register_debugging_calls([Call|Calls]) :-
270 register_debugging_call(Call),
271 register_debugging_calls(Calls).
272
273 register_debugging_call(Call) :-
274 functor(Call,Functor,Arity),
275 Call =.. [_|Args],
276 ? ( nth1(Pos,Args,*) ->
277 ? ( nth1(NPos,Args,*), NPos \= Pos ->
278 register_error_msg(Call)
279 ;
280 retractall(is_debug_call(Functor,Arity,_)),
281 assertz(is_debug_call(Functor,Arity,Pos)))
282 ;
283 register_error_msg(Call)).
284 register_error_msg(Call) :-
285 write_to_codes(Call,M),
286 append("register_debug_call has wrong argument: ", M, Codes),
287 safe_atom_codes(Msg,Codes),
288 print_message(error,Msg).
289
290
291 enable_debugging_calls :-
292 retractall(remove_debug_calls).
293 disable_debugging_calls :-
294 (remove_debug_calls -> true; assertz(remove_debug_calls)).
295
296 :- public inc_indent/0.
297 inc_indent :- (bb_get(debugging_indent_level,L) -> true ; L=0),
298 L1 is L+1,
299 bb_put(debugging_indent_level,L1),
300 indent(L).
301
302 indent(X) :- X<1,!.
303 indent(X) :- write('>'), X1 is X-1, indent(X1).
304
305 :- public dec_indent/0.
306 dec_indent :-
307 bb_get(debugging_indent_level,L), L>0,!,
308 L1 is L-1,
309 bb_put(debugging_indent_level,L1),
310 indent(L1).
311 dec_indent :- format('~n**** UNMATCHED decrease_indent~n',[]).
312
313 :- public get_functor/3.
314 get_functor(Var,F,N) :- var(Var), !, F='$VAR',N=0.
315 get_functor(Mod:E,Mod:F,N) :- !, get_functor(E,F,N).
316 get_functor(catch(E,_,_),catch:F,N) :- !, get_functor(E,F,N).
317 get_functor(if(E,_,_),if:F,N) :- !, get_functor(E,F,N).
318 get_functor(call(E),catch:F,N) :- !, get_functor(E,F,N).
319 get_functor(E,F,N) :- (E=true -> trace ; true), functor(E,F,N).
320
321 :- if(current_prolog_flag(dialect, sicstus)).
322 /*
323 * This code uses the term_expansion/6 hook, wich
324 * is a multifile-predicate. Due to a bug in SICStus Prolog,
325 * it is not possible to mix uncompiled and compiled code
326 * for such a predicate.
327 * So we always compile the term_expansion hook
328 */
329 :- current_prolog_flag(compiling,Pre),
330 set_prolog_flag(compiling,compactcode),
331 compile(debugging_calls_te),
332 set_prolog_flag(compiling,Pre).
333 :- else.
334 :- load_files(debugging_calls_te).
335 :- endif.