36
37:- module(prolog_stack,
38 [ get_prolog_backtrace/2, 39 get_prolog_backtrace/3, 40 prolog_stack_frame_property/2, 41 print_prolog_backtrace/2, 42 print_prolog_backtrace/3, 43 backtrace/1, 44 print_last_choicepoint/0,
45 print_last_choicepoint/2 46 ]). 47:- use_module(library(debug),[debug/3]). 48:- autoload(library(error),[must_be/2]). 49:- autoload(library(lists),[nth1/3,append/3]). 50:- autoload(library(option),[option/2,option/3,merge_options/3]). 51:- autoload(library(prolog_clause),
52 [clause_name/2,predicate_name/2,clause_info/4]). 53
54
55:- dynamic stack_guard/1. 56:- multifile stack_guard/1. 57
58:- predicate_options(print_prolog_backtrace/3, 3,
59 [ subgoal_positions(boolean),
60 show_file(oneof([absolute, basename]))
61 ]). 62
92
93:- create_prolog_flag(backtrace, true, [type(boolean), keep(true)]). 94:- create_prolog_flag(backtrace_depth, 20, [type(integer), keep(true)]). 95:- create_prolog_flag(backtrace_goal_depth, 3, [type(integer), keep(true)]). 96:- create_prolog_flag(backtrace_show_lines, true, [type(boolean), keep(true)]). 97
128
129get_prolog_backtrace(MaxDepth, Stack) :-
130 get_prolog_backtrace(MaxDepth, Stack, []).
131
132get_prolog_backtrace(Fr, MaxDepth, Stack) :-
133 integer(Fr), integer(MaxDepth), var(Stack),
134 !,
135 get_prolog_backtrace_lc(MaxDepth, Stack, [frame(Fr)]),
136 nlc.
137get_prolog_backtrace(MaxDepth, Stack, Options) :-
138 get_prolog_backtrace_lc(MaxDepth, Stack, Options),
139 nlc. 140 141 142
143nlc.
144
145get_prolog_backtrace_lc(MaxDepth, Stack, Options) :-
146 ( option(frame(Fr), Options)
147 -> PC = call
148 ; prolog_current_frame(Fr0),
149 prolog_frame_attribute(Fr0, pc, PC),
150 prolog_frame_attribute(Fr0, parent, Fr)
151 ),
152 ( option(goal_term_depth(GoalDepth), Options)
153 -> true
154 ; current_prolog_flag(backtrace_goal_depth, GoalDepth)
155 ),
156 option(guard(Guard), Options, none),
157 ( def_no_clause_refs(Guard)
158 -> DefClauseRefs = false
159 ; DefClauseRefs = true
160 ),
161 option(clause_references(ClauseRefs), Options, DefClauseRefs),
162 must_be(nonneg, GoalDepth),
163 backtrace(MaxDepth, Fr, PC, GoalDepth, Guard, ClauseRefs, Stack, Options).
164
165def_no_clause_refs(system:catch_with_backtrace/3).
166
167backtrace(0, _, _, _, _, _, [], _) :- !.
168backtrace(MaxDepth, Fr, PC, GoalDepth, Guard, ClauseRefs,
169 [frame(Level, Where, Goal)|Stack], Options) :-
170 prolog_frame_attribute(Fr, level, Level),
171 ( PC == foreign
172 -> prolog_frame_attribute(Fr, predicate_indicator, Pred),
173 Where = foreign(Pred)
174 ; PC == call
175 -> prolog_frame_attribute(Fr, predicate_indicator, Pred),
176 Where = call(Pred)
177 ; prolog_frame_attribute(Fr, clause, Clause)
178 -> clause_where(ClauseRefs, Clause, PC, Where, Options)
179 ; Where = meta_call
180 ),
181 ( Where == meta_call
182 -> Goal = 0
183 ; copy_goal(GoalDepth, Fr, Goal)
184 ),
185 ( prolog_frame_attribute(Fr, pc, PC2)
186 -> true
187 ; PC2 = foreign
188 ),
189 ( prolog_frame_attribute(Fr, parent, Parent),
190 prolog_frame_attribute(Parent, predicate_indicator, PI),
191 PI == Guard 192 -> backtrace(1, Parent, PC2, GoalDepth, Guard, ClauseRefs, Stack, Options)
193 ; prolog_frame_attribute(Fr, parent, Parent),
194 more_stack(Parent)
195 -> D2 is MaxDepth - 1,
196 backtrace(D2, Parent, PC2, GoalDepth, Guard, ClauseRefs, Stack, Options)
197 ; Stack = []
198 ).
199
200more_stack(Parent) :-
201 prolog_frame_attribute(Parent, predicate_indicator, PI),
202 \+ ( PI = ('$toplevel':G),
203 G \== (toplevel_call/1)
204 ),
205 !.
206more_stack(_) :-
207 current_prolog_flag(break_level, Break),
208 Break >= 1.
209
220
221clause_where(true, Clause, PC, clause(Clause, PC), _).
222clause_where(false, Clause, PC, pred_line(PredName, File:Line), Options) :-
223 option(subgoal_positions(true), Options, true),
224 subgoal_position(Clause, PC, File, CharA, _CharZ),
225 File \= @(_), 226 lineno(File, CharA, Line),
227 clause_predicate_name(Clause, PredName),
228 !.
229clause_where(false, Clause, _PC, pred_line(PredName, File:Line), _Options) :-
230 clause_property(Clause, file(File)),
231 clause_property(Clause, line_count(Line)),
232 clause_predicate_name(Clause, PredName),
233 !.
234clause_where(false, Clause, _PC, clause_name(ClauseName), _Options) :-
235 clause_name(Clause, ClauseName).
236
246
247copy_goal(0, _, 0) :- !. 248copy_goal(D, Fr, Goal) :-
249 prolog_frame_attribute(Fr, goal, Goal0),
250 ( Goal0 = Module:Goal1
251 -> copy_term_limit(D, Goal1, Goal2),
252 ( hidden_module(Module)
253 -> Goal = Goal2
254 ; Goal = Module:Goal2
255 )
256 ; copy_term_limit(D, Goal0, Goal)
257 ).
258
259hidden_module(system).
260hidden_module(user).
261
262copy_term_limit(0, In, '...') :-
263 compound(In),
264 !.
265copy_term_limit(N, In, Out) :-
266 is_dict(In),
267 !,
268 dict_pairs(In, Tag, PairsIn),
269 N2 is N - 1,
270 MaxArity = 16,
271 copy_pairs(PairsIn, N2, MaxArity, PairsOut),
272 dict_pairs(Out, Tag, PairsOut).
273copy_term_limit(N, In, Out) :-
274 compound(In),
275 !,
276 compound_name_arity(In, Functor, Arity),
277 N2 is N - 1,
278 MaxArity = 16,
279 ( Arity =< MaxArity
280 -> compound_name_arity(Out, Functor, Arity),
281 copy_term_args(0, Arity, N2, In, Out)
282 ; OutArity is MaxArity+2,
283 compound_name_arity(Out, Functor, OutArity),
284 copy_term_args(0, MaxArity, N2, In, Out),
285 SkipArg is MaxArity+1,
286 Skipped is Arity - MaxArity - 1,
287 format(atom(Msg), '<skipped ~D of ~D>', [Skipped, Arity]),
288 arg(SkipArg, Out, Msg),
289 arg(Arity, In, InA),
290 arg(OutArity, Out, OutA),
291 copy_term_limit(N2, InA, OutA)
292 ).
293copy_term_limit(_, In, Out) :-
294 copy_term_nat(In, Out).
295
296copy_term_args(I, Arity, Depth, In, Out) :-
297 I < Arity,
298 !,
299 I2 is I + 1,
300 arg(I2, In, InA),
301 arg(I2, Out, OutA),
302 copy_term_limit(Depth, InA, OutA),
303 copy_term_args(I2, Arity, Depth, In, Out).
304copy_term_args(_, _, _, _, _).
305
306copy_pairs([], _, _, []) :- !.
307copy_pairs(Pairs, _, 0, ['<skipped>'-Skipped]) :-
308 !,
309 length(Pairs, Skipped).
310copy_pairs([K-V0|T0], N, MaxArity, [K-V|T]) :-
311 copy_term_limit(N, V0, V),
312 MaxArity1 is MaxArity - 1,
313 copy_pairs(T0, N, MaxArity1, T).
314
315
329
330prolog_stack_frame_property(frame(Level,_,_), level(Level)).
331prolog_stack_frame_property(frame(_,Where,_), predicate(PI)) :-
332 frame_predicate(Where, PI).
333prolog_stack_frame_property(frame(_,clause(Clause,PC),_), location(File:Line)) :-
334 subgoal_position(Clause, PC, File, CharA, _CharZ),
335 File \= @(_), 336 lineno(File, CharA, Line).
337prolog_stack_frame_property(frame(_,_,_,Goal), goal(Goal)) :-
338 Goal \== 0.
339
340
341frame_predicate(foreign(PI), PI).
342frame_predicate(call(PI), PI).
343frame_predicate(clause(Clause, _PC), PI) :-
344 clause_property(Clause, predicate(PI)).
345
346default_backtrace_options(Options) :-
347 ( current_prolog_flag(backtrace_show_lines, true),
348 current_prolog_flag(iso, false)
349 -> Options = []
350 ; Options = [subgoal_positions(false)]
351 ).
352
367
368print_prolog_backtrace(Stream, Backtrace) :-
369 print_prolog_backtrace(Stream, Backtrace, []).
370
371print_prolog_backtrace(Stream, Backtrace, Options) :-
372 default_backtrace_options(DefOptions),
373 merge_options(Options, DefOptions, FinalOptions),
374 phrase(message(Backtrace, FinalOptions), Lines),
375 print_message_lines(Stream, '', Lines).
376
377:- public 378 message//1. 379
380message(Backtrace) -->
381 {default_backtrace_options(Options)},
382 message(Backtrace, Options).
383
384message(Backtrace, Options) -->
385 message_frames(Backtrace, Options),
386 warn_nodebug(Backtrace).
387
388message_frames([], _) -->
389 [].
390message_frames([H|T], Options) -->
391 message_frames(H, Options),
392 ( {T == []}
393 -> []
394 ; [nl],
395 message_frames(T, Options)
396 ).
397
398message_frames(frame(Level, Where, 0), Options) -->
399 !,
400 level(Level),
401 where_no_goal(Where, Options).
402message_frames(frame(Level, _Where, '$toplevel':toplevel_call(_)), _) -->
403 !,
404 level(Level),
405 [ '<user>'-[] ].
406message_frames(frame(Level, Where, Goal), Options) -->
407 level(Level),
408 [ ansi(code, '~p', [Goal]) ],
409 where_goal(Where, Options).
410
411where_no_goal(foreign(PI), _) -->
412 [ '~w <foreign>'-[PI] ].
413where_no_goal(call(PI), _) -->
414 [ '~w'-[PI] ].
415where_no_goal(pred_line(PredName, File:Line), Options) -->
416 !,
417 [ '~w at '-[PredName] ], file_line(File:Line, Options).
418where_no_goal(clause_name(ClauseName), _) -->
419 !,
420 [ '~w <no source>'-[ClauseName] ].
421where_no_goal(clause(Clause, PC), Options) -->
422 { nonvar(Clause),
423 !,
424 clause_where(false, Clause, PC, Where, Options)
425 },
426 where_no_goal(Where, Options).
427where_no_goal(meta_call, _) -->
428 [ '<meta call>' ].
429
430where_goal(foreign(_), _) -->
431 [ ' <foreign>'-[] ],
432 !.
433where_goal(pred_line(_PredName, File:Line), Options) -->
434 !,
435 [ ' at ' ], file_line(File:Line, Options).
436where_goal(clause_name(ClauseName), _) -->
437 !,
438 [ '~w <no source>'-[ClauseName] ].
439where_goal(clause(Clause, PC), Options) -->
440 { nonvar(Clause),
441 !,
442 clause_where(false, Clause, PC, Where, Options)
443 },
444 where_goal(Where, Options).
445where_goal(clause(Clause, _PC), _) -->
446 { clause_property(Clause, file(File)),
447 clause_property(Clause, line_count(Line))
448 },
449 !,
450 [ ' at ', url(File:Line) ].
451where_goal(clause(Clause, _PC), _) -->
452 { clause_name(Clause, ClauseName)
453 },
454 !,
455 [ ' ~w <no source>'-[ClauseName] ].
456where_goal(_, _) -->
457 [].
458
459level(Level) -->
460 [ ansi(bold, '~|~t[~D]~6+ ', [Level]) ].
461
462file_line(File:Line, Options), option(show_files(basename), Options) ==>
463 { file_base_name(File, Base),
464 format(string(Label), '~w:~d', [Base, Line])
465 },
466 [ url(File:Line, Label) ].
467file_line(File:Line, _Options) ==>
468 [ url(File:Line) ].
469
470warn_nodebug(Backtrace) -->
471 { contiguous(Backtrace) },
472 !.
473warn_nodebug(_Backtrace) -->
474 [ nl,nl,
475 'Note: some frames are missing due to last-call optimization.'-[], nl,
476 'Re-run your program in debug mode (:- debug.) to get more detail.'-[]
477 ].
478
479contiguous([frame(D0,_,_)|Frames]) :-
480 contiguous(Frames, D0).
481
482contiguous([], _).
483contiguous([frame(D1,_,_)|Frames], D0) :-
484 D1 =:= D0-1,
485 contiguous(Frames, D1).
486
487
492
493:- multifile
494 user:prolog_clause_name/2. 495
496clause_predicate_name(Clause, PredName) :-
497 user:prolog_clause_name(Clause, PredName),
498 !.
499clause_predicate_name(Clause, PredName) :-
500 nth_clause(Head, _N, Clause),
501 !,
502 predicate_name(user:Head, PredName).
503
504
508
509backtrace(MaxDepth) :-
510 get_prolog_backtrace_lc(MaxDepth, Stack, []),
511 print_prolog_backtrace(user_error, Stack).
512
513
514subgoal_position(ClauseRef, PC, File, CharA, CharZ) :-
515 debug(backtrace, 'Term-position in ~p at PC=~w:', [ClauseRef, PC]),
516 clause_info(ClauseRef, File, TPos, _),
517 '$clause_term_position'(ClauseRef, PC, List),
518 debug(backtrace, '\t~p~n', [List]),
519 find_subgoal(List, TPos, PosTerm),
520 compound(PosTerm),
521 arg(1, PosTerm, CharA),
522 arg(2, PosTerm, CharZ).
523
527
528find_subgoal(_, Pos, Pos) :-
529 var(Pos),
530 !.
531find_subgoal([A|T], term_position(_, _, _, _, PosL), SPos) :-
532 nth1(A, PosL, Pos),
533 !,
534 find_subgoal(T, Pos, SPos).
535find_subgoal([1|T], brace_term_position(_,_,Pos), SPos) :-
536 !,
537 find_subgoal(T, Pos, SPos).
538find_subgoal(List, parentheses_term_position(_,_,Pos), SPos) :-
539 !,
540 find_subgoal(List, Pos, SPos).
541find_subgoal(_, Pos, Pos).
542
543
549
550lineno(File, Char, Line) :-
551 setup_call_cleanup(
552 ( prolog_clause:try_open_source(File, Fd),
553 set_stream(Fd, newline(detect))
554 ),
555 lineno_(Fd, Char, Line),
556 close(Fd)).
557
558lineno_(Fd, Char, L) :-
559 stream_property(Fd, position(Pos)),
560 stream_position_data(char_count, Pos, C),
561 C > Char,
562 !,
563 stream_position_data(line_count, Pos, L0),
564 L is L0-1.
565lineno_(Fd, Char, L) :-
566 skip(Fd, 0'\n),
567 lineno_(Fd, Char, L).
568
569
570 573
577
578print_last_choicepoint :-
579 prolog_current_choice(ChI0), 580 prolog_choice_attribute(ChI0, parent, ChI1),
581 print_last_choicepoint(ChI1, []).
582print_last_choicepoint.
583
585
586print_last_choicepoint(ChI1, Options) :-
587 real_choice(ChI1, ChI),
588 prolog_choice_attribute(ChI, frame, F),
589 prolog_frame_attribute(F, goal, Goal),
590 Goal \= '$execute_goal2'(_,_,_), 591 !,
592 option(message_level(Level), Options, warning),
593 get_prolog_backtrace(2, [_|Stack], [frame(F)]),
594 ( predicate_property(Goal, foreign)
595 -> print_message(Level, choicepoint(foreign(Goal), Stack))
596 ; prolog_frame_attribute(F, clause, Clause),
597 ( prolog_choice_attribute(ChI, pc, PC)
598 -> Ctx = jump(PC)
599 ; prolog_choice_attribute(ChI, clause, Next)
600 -> Ctx = clause(Next)
601 ),
602 print_message(Level, choicepoint(clause(Goal, Clause, Ctx), Stack))
603 ).
604print_last_choicepoint(_, _).
605
606real_choice(Ch0, Ch) :-
607 prolog_choice_attribute(Ch0, type, Type),
608 dummy_type(Type),
609 !,
610 prolog_choice_attribute(Ch0, parent, Ch1),
611 real_choice(Ch1, Ch).
612real_choice(Ch, Ch).
613
614dummy_type(debug).
615dummy_type(none).
616
617prolog:message(choicepoint(Choice, Stack)) -->
618 choice(Choice),
619 [ nl, 'Called from', nl ],
620 message(Stack).
621
622choice(foreign(Goal)) -->
623 success_goal(Goal, 'a foreign choice point').
624choice(clause(Goal, ClauseRef, clause(Next))) -->
625 success_goal(Goal, 'a choice point in alternate clause'),
626 [ nl ],
627 [ ' ' ], clause_descr(ClauseRef), [': clause succeeded', nl],
628 [ ' ' ], clause_descr(Next), [': next candidate clause' ].
629choice(clause(Goal, ClauseRef, jump(PC))) -->
630 { clause_where(false, ClauseRef, PC, Where,
631 [subgoal_positions(true)])
632 },
633 success_goal(Goal, 'an in-clause choice point'),
634 [ nl, ' ' ],
635 where_no_goal(Where).
636
637success_goal(Goal, Reason) -->
638 [ ansi(code, '~p', [Goal]),
639 ' left ~w (after success)'-[Reason]
640 ].
641
642where_no_goal(pred_line(_PredName, File:Line)) -->
643 !,
644 [ url(File:Line) ].
645where_no_goal(clause_name(ClauseName)) -->
646 !,
647 [ '~w <no source>'-[ClauseName] ].
648
649clause_descr(ClauseRef) -->
650 { clause_property(ClauseRef, file(File)),
651 clause_property(ClauseRef, line_count(Line))
652 },
653 !,
654 [ url(File:Line) ].
655clause_descr(ClauseRef) -->
656 { clause_name(ClauseRef, Name)
657 },
658 [ '~w'-[Name] ].
659
660
661 664
698
699:- multifile prolog:prolog_exception_hook/5. 700:- dynamic prolog:prolog_exception_hook/5. 701
702prolog:prolog_exception_hook(error(E, context(Ctx0,Msg)),
703 error(E, context(prolog_stack(Stack),Msg)),
704 Fr, GuardSpec, Debug) :-
705 current_prolog_flag(backtrace, true),
706 \+ is_stack(Ctx0, _Frames),
707 ( atom(GuardSpec)
708 -> debug(backtrace, 'Got uncaught (guard = ~q) exception ~p (Ctx0=~p)',
709 [GuardSpec, E, Ctx0]),
710 stack_guard(GuardSpec),
711 Guard = GuardSpec
712 ; prolog_frame_attribute(GuardSpec, predicate_indicator, Guard),
713 debug(backtrace, 'Got exception ~p (Ctx0=~p, Catcher=~p)',
714 [E, Ctx0, Guard]),
715 stack_guard(Guard)
716 -> true
717 ; Debug == true,
718 stack_guard(debug),
719 Guard = none
720 ),
721 ( current_prolog_flag(backtrace_depth, Depth)
722 -> Depth > 0
723 ; Depth = 20 724 ),
725 get_prolog_backtrace(Depth, Stack0,
726 [ frame(Fr),
727 guard(Guard)
728 ]),
729 debug(backtrace, 'Stack = ~p', [Stack0]),
730 clean_stack(Stack0, Stack1),
731 join_stacks(Ctx0, Stack1, Stack).
732
733clean_stack(List, List) :-
734 stack_guard(X), var(X),
735 !. 736clean_stack(List, Clean) :-
737 clean_stack2(List, Clean).
738
739clean_stack2([], []).
740clean_stack2([H|_], [H]) :-
741 guard_frame(H),
742 !.
743clean_stack2([H|T0], [H|T]) :-
744 clean_stack2(T0, T).
745
746guard_frame(frame(_,clause(ClauseRef, _, _))) :-
747 nth_clause(M:Head, _, ClauseRef),
748 functor(Head, Name, Arity),
749 stack_guard(M:Name/Arity).
750
751join_stacks(Ctx0, Stack1, Stack) :-
752 nonvar(Ctx0),
753 Ctx0 = prolog_stack(Stack0),
754 is_list(Stack0), !,
755 append(Stack0, Stack1, Stack).
756join_stacks(_, Stack, Stack).
757
758
767
768stack_guard(none).
769stack_guard(system:catch_with_backtrace/3).
770stack_guard(debug).
771
772
773 776
777:- multifile
778 prolog:message//1. 779
780prolog:message(error(Error, context(Stack, Message))) -->
781 { Message \== 'DWIM could not correct goal',
782 is_stack(Stack, Frames)
783 },
784 !,
785 '$messages':translate_message(error(Error, context(_, Message))),
786 [ nl, 'In:', nl ],
787 ( {is_list(Frames)}
788 -> message(Frames)
789 ; ['~w'-[Frames]]
790 ).
791
792is_stack(Stack, Frames) :-
793 nonvar(Stack),
794 Stack = prolog_stack(Frames)