35
36:- module(swish_trace,
37 [ '$swish wrapper'/2 38 ]). 39:- use_module(library(debug)). 40:- use_module(library(prolog_stack)). 41:- use_module(library(settings)). 42:- use_module(library(pengines)). 43:- use_module(library(apply)). 44:- use_module(library(lists)). 45:- use_module(library(option)). 46:- use_module(library(solution_sequences)). 47:- use_module(library(edinburgh), [debug/0]). 48:- use_module(library(pengines_io), [pengine_io_predicate/1]). 49:- use_module(library(sandbox), []). 50:- use_module(library(prolog_clause)). 51:- use_module(library(prolog_breakpoints)). 52:- use_module(library(http/term_html)). 53:- use_module(library(http/html_write)). 54:- if(exists_source(library(wfs))). 55:- use_module(library(wfs)). 56:- endif. 57
58:- use_module(storage). 59:- use_module(config). 60
61:- if(current_setting(swish:debug_info)). 62:- set_setting(swish:debug_info, true). 63:- endif. 64
65:- set_prolog_flag(generate_debug_info, false). 66
67:- meta_predicate
68 '$swish wrapper'(0, -). 69
74
75:- multifile
76 user:prolog_trace_interception/4,
77 user:message_hook/3. 78
79user:message_hook(trace_mode(_), _, _) :-
80 pengine_self(_), !.
81
90
91:- dynamic
92 trace_pengines/0. 93
94trace_pengines.
95
96user:prolog_trace_interception(Port, Frame, CHP, Action) :-
97 trace_pengines,
98 State = state(0),
99 ( catch(trace_interception(Port, Frame, CHP, Action), E, true),
100 ( var(E)
101 -> nb_setarg(1, State, Action)
102 ; abort 103 ),
104 fail
105 ; arg(1, State, Action)
106 ).
107
108trace_interception(Port, Frame, _CHP, Action) :-
109 pengine_self(Pengine),
110 prolog_frame_attribute(Frame, predicate_indicator, PI),
111 debug(trace, 'HOOK: ~p ~p', [Port, PI]),
112 pengine_property(Pengine, module(Module)),
113 wrapper_frame(Frame, WrapperFrame),
114 debug(trace, 'Me: ~p, wrapper: ~p', [Frame, WrapperFrame]),
115 prolog_frame_attribute(WrapperFrame, level, WrapperDepth),
116 prolog_frame_attribute(Frame, goal, Goal0),
117 prolog_frame_attribute(Frame, level, Depth0),
118 Depth is Depth0 - WrapperDepth - 1,
119 unqualify(Goal0, Module, Goal),
120 debug(trace, '[~d] ~w: Goal ~p', [Depth0, Port, Goal]),
121 term_html(Goal, GoalString),
122 functor(Port, PortName, _),
123 Prompt0 = _{type: trace,
124 port: PortName,
125 depth: Depth,
126 goal: GoalString,
127 pengine: Pengine
128 },
129 add_context(Port, Frame, Prompt0, Prompt1),
130 add_source(Port, Frame, Prompt1, Prompt),
131 pengine_input(Prompt, Reply),
132 trace_action(Reply, Port, Frame, Action), !,
133 debug(trace, 'Action: ~p --> ~p', [Reply, Action]).
134trace_interception(Port, Frame0, _CHP, nodebug) :-
135 pengine_self(_),
136 prolog_frame_attribute(Frame0, goal, Goal),
137 prolog_frame_attribute(Frame0, level, Depth),
138 debug(trace, '[~d] ~w: Goal ~p --> NODEBUG', [Depth, Port, Goal]).
139
140trace_action(continue, _Port, Frame, continue) :-
141 pengine_self(Me),
142 prolog_frame_attribute(Frame, predicate_indicator, Me:Name/Arity),
143 functor(Head, Name, Arity),
144 \+ pengine_io_predicate(Head), !,
145 prolog_skip_level(_, very_deep),
146 debug(trace, '~p', [Me:Name/Arity]).
147trace_action(continue, Port, _, skip) :-
148 box_enter(Port), !.
149trace_action(continue, _, _, continue) :-
150 prolog_skip_level(_, very_deep).
151trace_action(nodebug, _, _, nodebug).
152trace_action(skip, _, _, skip).
153trace_action(retry, _, _, retry).
154trace_action(up , _, _, up).
155trace_action(abort, _, _, abort).
156trace_action(nodebug(Breakpoints), _, _, Action) :-
157 catch(update_breakpoints(Breakpoints), E,
158 print_message(warning, E)),
159 ( Breakpoints == []
160 -> Action = nodebug
161 ; Action = continue,
162 notrace
163 ).
164
165box_enter(call).
166box_enter(redo(_)).
167
168wrapper_frame(Frame0, Frame) :-
169 parent_frame(Frame0, Frame),
170 prolog_frame_attribute(Frame, predicate_indicator, PI),
171 debug(trace, 'Parent: ~p', [PI]),
172 ( PI == swish_call/1
173 -> true
174 ; PI == swish_trace:swish_call/1
175 ), !.
176
177parent_frame(Frame, Frame).
178parent_frame(Frame, Parent) :-
179 prolog_frame_attribute(Frame, parent, Parent0),
180 parent_frame(Parent0, Parent).
181
182unqualify(M:G, M, G) :- !.
183unqualify(system:G, _, G) :- !.
184unqualify(user:G, _, G) :- !.
185unqualify(G, _, G).
186
187term_html(Term, HTMlString) :-
188 pengine_self(Pengine),
189 pengine_property(Pengine, module(Module)),
190 phrase(html(\term(Term,
191 [ module(Module),
192 quoted(true)
193 ])), Tokens),
194 with_output_to(string(HTMlString), print_html(Tokens)).
195
200
201add_context(exception(Exception0), _Frame, Prompt0, Prompt) :-
202 strip_stack(Exception0, Exception),
203 message_to_string(Exception, Msg), !,
204 debug(trace, 'Msg = ~s', [Msg]),
205 ( term_html(Exception, String)
206 -> Ex = json{term_html:String, message:Msg}
207 ; Ex = json{message:Msg}
208 ),
209 Prompt = Prompt0.put(exception, Ex).
210add_context(_, _, Prompt, Prompt).
211
212strip_stack(error(Error, context(prolog_stack(S), Msg)),
213 error(Error, context(_, Msg))) :-
214 nonvar(S).
215strip_stack(Error, Error).
216
232
233:- meta_predicate swish_call(0). 234
235:- if(\+current_predicate(call_delays/2)). 236:- meta_predicate
237 call_delays(0, :),
238 delays_residual_program(:, :). 239
240call_delays(Goal, _:true) :-
241 call(Goal).
242
243delays_residual_program(_, _:[]).
244:- endif. 245
246'$swish wrapper'(Goal, Extra) :-
247 ( nb_current('$variable_names', Bindings)
248 -> true
249 ; Bindings = []
250 ),
251 debug(projection, 'Pre-context-pre ~p, extra=~p', [Bindings, Extra]),
252 maplist(call_pre_context(Goal, Bindings), Extra),
253 debug(projection, 'Pre-context-post ~p, extra=~p', [Bindings, Extra]),
254 call_delays(catch_with_backtrace(swish_call(Goal),
255 E, throw_backtrace(E)), Delays),
256 deterministic(Det),
257 ( tracing,
258 Det == false
259 -> ( notrace,
260 debug(trace, 'Saved tracer', [])
261 ; debug(trace, 'Restoring tracer', []),
262 trace,
263 fail
264 )
265 ; notrace
266 ),
267 call_post_context(_{goal:Goal, bindings:Bindings,
268 delays:Delays, context:Extra}),
269 maplist(call_post_context(Goal, Bindings, Delays), Extra).
270
271throw_backtrace(error(Formal, context(prolog_stack(Stack0), Msg))) :-
272 append(Stack1, [Guard|_], Stack0),
273 is_guard(Guard),
274 !,
275 last(Stack1, Frame),
276 arg(1, Frame, Level),
277 maplist(re_level(Level), Stack1, Stack),
278 throw(error(Formal, context(prolog_stack(Stack), Msg))).
279throw_backtrace(E) :-
280 throw(E).
281
282re_level(Sub,
283 frame(Level0, Clause, Goal),
284 frame(Level, Clause, Goal)) :-
285 Level is 1 + Level0 - Sub.
286
287is_guard(frame(_Level, _Clause, swish_trace:swish_call(_))).
288
289swish_call(Goal) :-
290 Goal,
291 no_lco.
292
293no_lco.
294
295:- '$hide'(swish_call/1). 296:- '$hide'(no_lco/0). 297
305
306:- multifile
307 pre_context/3,
308 post_context/1,
309 post_context/3,
310 post_context/4. 311
312call_pre_context(Goal, Bindings, Var) :-
313 binding(Bindings, Var, Name),
314 pre_context(Name, Goal, Var), !.
315call_pre_context(_, _, _).
316
318
319call_post_context(Dict) :-
320 post_context(Dict), !.
321call_post_context(_).
322
328
329call_post_context(Goal, Bindings, Delays, Var) :-
330 binding(Bindings, Var, Name),
331 post_context(Name, Goal, Delays, Var), !.
332call_post_context(_, _, _, _).
333
334post_context(Name, Goal, _Delays, Extra) :-
335 post_context(Name, Goal, Extra), !.
336post_context(Name, M:_Goal, _, '$residuals'(Residuals)) :-
337 swish_config(residuals_var, Name), !,
338 residuals(M, Residuals).
339post_context(Name, M:_Goal, Delays,
340 '$wfs_residual_program'(TheDelays, Program)) :-
341 Delays \== true,
342 swish_config(wfs_residual_program_var, Name), !,
343 ( current_prolog_flag(toplevel_list_wfs_residual_program, true)
344 -> delays_residual_program(Delays, M:Program),
345 TheDelays = Delays
346 ; TheDelays = undefined,
347 Program = []
348 ).
349
350binding([Name=Var|_], V, Name) :-
351 Var == V, !.
352binding([_|Bindings], V, Name) :-
353 binding(Bindings, V, Name).
354
355
363
364residuals(TypeIn, Goals) :-
365 phrase(prolog:residual_goals, Goals0),
366 maplist(unqualify_residual(TypeIn), Goals0, Goals).
367
368unqualify_residual(M, M:G, G) :- !.
369unqualify_residual(T, M:G, G) :-
370 predicate_property(T:G, imported_from(M)), !.
371unqualify_residual(_, G, G).
372
373
374 377
378add_source(Port, Frame, Prompt0, Prompt) :-
379 debug(trace(line), 'Add source?', []),
380 source_location(Frame, Port, Location), !,
381 Prompt = Prompt0.put(source, Location),
382 debug(trace(line), 'Source ~p ~p: ~p', [Port, Frame, Location]).
383add_source(_, _, Prompt, Prompt).
384
394
395source_location(Frame, Port, Location) :-
396 parent_frame(Frame, Port, _Steps, ShowFrame, PC),
397 ( clause_position(PC)
398 -> true 399 ; prolog_frame_attribute(ShowFrame, parent, Parent),
400 frame_file(Parent, ParentFile),
401 \+ pengine_file(ParentFile)
402 ),
403 ( debugging(trace(file))
404 -> prolog_frame_attribute(ShowFrame, level, Level),
405 prolog_frame_attribute(ShowFrame, predicate_indicator, PI),
406 debug(trace(file), '\t[~d]: ~p', [Level, PI])
407 ; true
408 ),
409 frame_file(ShowFrame, File),
410 pengine_file(File), !,
411 source_position(ShowFrame, PC, Location).
412
418
419parent_frame(Frame0, Port0, Steps, Frame, Port) :-
420 parent_frame(Frame0, Port0, 0, Steps, Frame, Port).
421
422parent_frame(Frame, Port, Steps, Steps, Frame, Port).
423parent_frame(Frame, _Port, Steps0, Steps, Parent, PC) :-
424 direct_parent_frame(Frame, DirectParent, ParentPC),
425 Steps1 is Steps0+1,
426 parent_frame(DirectParent, ParentPC, Steps1, Steps, Parent, PC).
427
428direct_parent_frame(Frame, Parent, PC) :-
429 prolog_frame_attribute(Frame, parent, Parent),
430 prolog_frame_attribute(Frame, pc, PC).
431
432
437
438frame_file(Frame, File) :-
439 prolog_frame_attribute(Frame, clause, ClauseRef), !,
440 ( clause_property(ClauseRef, predicate(system:'<meta-call>'/1))
441 -> prolog_frame_attribute(Frame, parent, Parent),
442 frame_file(Parent, File)
443 ; clause_property(ClauseRef, file(File))
444 ).
445frame_file(Frame, File) :-
446 prolog_frame_attribute(Frame, goal, Goal),
447 qualify(Goal, QGoal),
448 \+ predicate_property(QGoal, foreign),
449 clause(QGoal, _Body, ClauseRef), !,
450 clause_property(ClauseRef, file(File)).
451
456
457pengine_file(File) :-
458 sub_atom(File, 0, _, _, 'pengine://'), !.
459pengine_file(File) :-
460 sub_atom(File, 0, _, _, 'swish://').
461
465
466clause_position(PC) :- integer(PC), !.
467clause_position(exit).
468clause_position(unify).
469clause_position(choice(_)).
470
476
477subgoal_position(ClauseRef, PortOrPC, _, _, _) :-
478 debugging(trace(save_pc)),
479 debug(trace(save_pc), 'Position for ~p at ~p', [ClauseRef, PortOrPC]),
480 asserta(subgoal_position(ClauseRef, PortOrPC)),
481 fail.
482subgoal_position(ClauseRef, unify, File, CharA, CharZ) :- !,
483 clause_info(ClauseRef, File, TPos, _),
484 head_pos(ClauseRef, TPos, PosTerm),
485 nonvar(PosTerm),
486 arg(1, PosTerm, CharA),
487 arg(2, PosTerm, CharZ).
488subgoal_position(ClauseRef, choice(CHP), File, CharA, CharZ) :- !,
489 ( prolog_choice_attribute(CHP, type, jump),
490 prolog_choice_attribute(CHP, pc, To)
491 -> debug(gtrace(position), 'Term-position: choice-jump to ~w', [To]),
492 subgoal_position(ClauseRef, To, File, CharA, CharZ)
493 ; clause_end(ClauseRef, File, CharA, CharZ)
494 ).
495subgoal_position(ClauseRef, Port, File, CharA, CharZ) :-
496 end_port(Port), !,
497 clause_end(ClauseRef, File, CharA, CharZ).
498subgoal_position(ClauseRef, PC, File, CharA, CharZ) :-
499 debug(trace(source), 'In clause ~p at ~p', [ClauseRef, PC]),
500 clause_info(ClauseRef, File, TPos, _),
501 ( '$clause_term_position'(ClauseRef, PC, List)
502 -> debug(trace(source), 'Term-position: for ref=~w at PC=~w: ~w',
503 [ClauseRef, PC, List]),
504 ( find_subgoal(List, TPos, PosTerm)
505 -> true
506 ; PosTerm = TPos,
507 debug(trace(source),
508 'Clause source-info could not be parsed', []),
509 fail
510 ),
511 nonvar(PosTerm),
512 arg(1, PosTerm, CharA),
513 arg(2, PosTerm, CharZ)
514 ; debug(trace(source),
515 'No clause-term-position for ref=~p at PC=~p',
516 [ClauseRef, PC]),
517 fail
518 ).
519
520end_port(exit).
521end_port(fail).
522end_port(exception).
523
524clause_end(ClauseRef, File, CharA, CharZ) :-
525 clause_info(ClauseRef, File, TPos, _),
526 nonvar(TPos),
527 arg(2, TPos, CharA),
528 CharZ is CharA + 1.
529
530head_pos(Ref, Pos, HPos) :-
531 clause_property(Ref, fact), !,
532 HPos = Pos.
533head_pos(_, term_position(_, _, _, _, [HPos,_]), HPos).
534
537
538find_subgoal([A|T], term_position(_, _, _, _, PosL), SPos) :-
539 nth1(A, PosL, Pos), !,
540 find_subgoal(T, Pos, SPos).
541find_subgoal([1|T], brace_term_position(_,_,Pos), SPos) :- !,
542 find_subgoal(T, Pos, SPos).
543find_subgoal(_, Pos, Pos).
544
545
548
553
554source_position(Frame, PC, _{file:File, from:CharA, to:CharZ}) :-
555 debug(trace(pos), '~p', [source_position(Frame, PC, _)]),
556 clause_position(PC),
557 prolog_frame_attribute(Frame, clause, ClauseRef), !,
558 subgoal_position(ClauseRef, PC, File, CharA, CharZ).
559source_position(Frame, _PC, Position) :-
560 prolog_frame_attribute(Frame, goal, Goal),
561 qualify(Goal, QGoal),
562 \+ predicate_property(QGoal, foreign),
563 ( clause(QGoal, _Body, ClauseRef)
564 -> subgoal_position(ClauseRef, unify, File, CharA, CharZ),
565 Position = _{file:File, from:CharA, to:CharZ}
566 ; functor(Goal, Functor, Arity),
567 functor(GoalTemplate, Functor, Arity),
568 qualify(GoalTemplate, QGoalTemplate),
569 clause(QGoalTemplate, _TBody, ClauseRef)
570 -> subgoal_position(ClauseRef, unify, File, CharA, CharZ),
571 Position = _{file:File, from:CharA, to:CharZ}
572 ; find_source(QGoal, File, Line),
573 debug(trace(source), 'At ~w:~d', [File, Line]),
574 Position = _{file:File, line:Line}
575 ).
576
577qualify(Goal, Goal) :-
578 functor(Goal, :, 2), !.
579qualify(Goal, user:Goal).
580
581find_source(Predicate, File, Line) :-
582 predicate_property(Predicate, file(File)),
583 predicate_property(Predicate, line_count(Line)), !.
584
597
598:- multifile pengines:prepare_goal/3. 599
600pengines:prepare_goal(Goal0, Goal, Options) :-
601 forall(set_screen_property(Options), true),
602 option(breakpoints(Breakpoints), Options),
603 Breakpoints \== [],
604 pengine_self(Pengine),
605 pengine_property(Pengine, source(File, Text)),
606 maplist(set_file_breakpoints(Pengine, File, Text), Breakpoints),
607 Goal = (debug, Goal0).
608
614
615set_screen_property(Options) :-
616 pengine_self(Pengine),
617 screen_property_decl(Property),
618 option(Property, Options),
619 assertz(Pengine:screen_property(Property)).
620
621screen_property_decl(height(_)).
622screen_property_decl(width(_)).
623screen_property_decl(rows(_)).
624screen_property_decl(cols(_)).
625screen_property_decl(tabled(_)).
626
632
633swish:tty_size(Rows, Cols) :-
634 pengine_self(Pengine),
635 current_predicate(Pengine:screen_property/1), !,
636 Pengine:screen_property(rows(Rows)),
637 Pengine:screen_property(cols(Cols)).
638swish:tty_size(24, 80).
639
643
644set_file_breakpoints(_Pengine, PFile, Text, Dict) :-
645 debug(trace(break), 'Set breakpoints at ~p', [Dict]),
646 _{file:FileS, breakpoints:List} :< Dict,
647 atom_string(File, FileS),
648 ( PFile == File
649 -> debug(trace(break), 'Pengine main source', []),
650 maplist(set_pengine_breakpoint(File, File, Text), List)
651 ; source_file_property(PFile, includes(File, _Time)),
652 atom_concat('swish://', StoreFile, File)
653 -> debug(trace(break), 'Pengine included source ~p', [StoreFile]),
654 storage_file(StoreFile, IncludedText, _Meta),
655 maplist(set_pengine_breakpoint(PFile, File, IncludedText), List)
656 ; debug(trace(break), 'Not in included source', [])
657 ).
658
662
663set_pengine_breakpoint(Owner, File, Text, Line) :-
664 debug(trace(break), 'Try break at ~q:~d', [File, Line]),
665 line_start(Line, Text, Char),
666 ( set_breakpoint(Owner, File, Line, Char, _0Break)
667 -> !, debug(trace(break), 'Created breakpoint ~p', [_0Break])
668 ; print_message(warning, breakpoint(failed(File, Line, 0)))
669 ).
670
671line_start(1, _, 0) :- !.
672line_start(N, Text, Start) :-
673 N0 is N - 2,
674 offset(N0, sub_string(Text, Start, _, _, '\n')), !.
675
680
681update_breakpoints(Breakpoints) :-
682 breakpoint_by_file(Breakpoints, NewBPS),
683 pengine_self(Pengine),
684 pengine_property(Pengine, source(PFile, Text)),
685 current_pengine_source_breakpoints(PFile, ByFile),
686 forall(( member(File-FBPS, ByFile),
687 member(Id-Line, FBPS),
688 \+ ( member(File-NFBPS, NewBPS),
689 member(Line, NFBPS))),
690 delete_breakpoint(Id)),
691 forall(( member(File-NFBPS, NewBPS),
692 member(Line, NFBPS),
693 \+ ( member(File-FBPS, ByFile),
694 member(_-Line, FBPS))),
695 add_breakpoint(PFile, File, Text, Line)).
696
697breakpoint_by_file(Breakpoints, NewBPS) :-
698 maplist(bp_by_file, Breakpoints, NewBPS).
699
700bp_by_file(Dict, File-Lines) :-
701 _{file:FileS, breakpoints:Lines} :< Dict,
702 atom_string(File, FileS).
703
704add_breakpoint(PFile, PFile, Text, Line) :- !,
705 set_pengine_breakpoint(PFile, PFile, Text, Line).
706add_breakpoint(PFile, File, _Text, Line) :-
707 atom_concat('swish://', Store, File), !,
708 storage_file(Store, Text, _Meta),
709 set_pengine_breakpoint(PFile, File, Text, Line).
710add_breakpoint(_, _, _, _Line). 711
717
718current_pengine_source_breakpoints(PFile, ByFile) :-
719 findall(Pair, current_pengine_breakpoint(PFile, Pair), Pairs0),
720 keysort(Pairs0, Pairs),
721 group_pairs_by_key(Pairs, ByFile).
722
723current_pengine_breakpoint(PFile, PFile-(Id-Line)) :-
724 breakpoint_property(Id, file(PFile)),
725 breakpoint_property(Id, line_count(Line)).
726current_pengine_breakpoint(PFile, File-(Id-Line)) :-
727 source_file_property(PFile, includes(File, _Time)),
728 breakpoint_property(Id, file(File)),
729 breakpoint_property(Id, line_count(Line)).
730
731
735
736:- multifile prolog_clause:open_source/2. 737
738prolog_clause:open_source(File, Stream) :-
739 sub_atom(File, 0, _, _, 'pengine://'), !,
740 ( pengine_self(Pengine)
741 -> true
742 ; debugging(trace(_))
743 ),
744 pengine_property(Pengine, source(File, Source)),
745 open_string(Source, Stream).
746prolog_clause:open_source(File, Stream) :-
747 atom_concat('swish://', GittyFile, File), !,
748 storage_file(GittyFile, Data, _Meta),
749 open_string(Data, Stream).
750
751
752 755
756:- dynamic
757 user:prolog_exception_hook/4,
758 installed/1. 759
760:- volatile
761 installed/1. 762
763exception_hook(Ex, Ex, _Frame, Catcher) :-
764 Catcher \== none,
765 Catcher \== 'C',
766 prolog_frame_attribute(Catcher, predicate_indicator, PI),
767 debug(trace(exception), 'Ex: ~p, catcher: ~p', [Ex, PI]),
768 PI == '$swish wrapper'/1,
769 trace,
770 fail.
771
775
776install_exception_hook :-
777 installed(Ref),
778 ( nth_clause(_, I, Ref)
779 -> I == 1, ! 780 ; retractall(installed(Ref)),
781 erase(Ref), 782 fail
783 ).
784install_exception_hook :-
785 asserta((user:prolog_exception_hook(Ex, Out, Frame, Catcher) :-
786 exception_hook(Ex, Out, Frame, Catcher)), Ref),
787 assert(installed(Ref)).
788
789:- initialization install_exception_hook. 790
791
792 795
796:- multifile
797 sandbox:safe_primitive/1,
798 sandbox:safe_meta_predicate/1. 799
800sandbox:safe_primitive(system:trace).
801sandbox:safe_primitive(system:notrace).
802sandbox:safe_primitive(system:tracing).
803sandbox:safe_primitive(edinburgh:debug).
804sandbox:safe_primitive(system:deterministic(_)).
805sandbox:safe_primitive(swish_trace:residuals(_,_)).
806sandbox:safe_primitive(swish:tty_size(_Rows, _Cols)).
807
808sandbox:safe_meta_predicate(swish_trace:'$swish wrapper'/2).
809
810
811 814
815:- multifile
816 prolog:message/3. 817
818prolog:message(breakpoint(failed(File, Line, _Char))) -->
819 [ 'Failed to set breakpoint at ~w:~d'-[File,Line] ]