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
325
326prolog_stack_frame_property(frame(Level,_,_), level(Level)).
327prolog_stack_frame_property(frame(_,Where,_), predicate(PI)) :-
328 frame_predicate(Where, PI).
329prolog_stack_frame_property(frame(_,clause(Clause,PC),_), location(File:Line)) :-
330 subgoal_position(Clause, PC, File, CharA, _CharZ),
331 File \= @(_), 332 lineno(File, CharA, Line).
333prolog_stack_frame_property(frame(_,_,_,Goal), goal(Goal)) :-
334 Goal \== 0.
335
336
337frame_predicate(foreign(PI), PI).
338frame_predicate(call(PI), PI).
339frame_predicate(clause(Clause, _PC), PI) :-
340 clause_property(Clause, predicate(PI)).
341
342default_backtrace_options(Options) :-
343 ( current_prolog_flag(backtrace_show_lines, true),
344 current_prolog_flag(iso, false)
345 -> Options = []
346 ; Options = [subgoal_positions(false)]
347 ).
348
363
364print_prolog_backtrace(Stream, Backtrace) :-
365 print_prolog_backtrace(Stream, Backtrace, []).
366
367print_prolog_backtrace(Stream, Backtrace, Options) :-
368 default_backtrace_options(DefOptions),
369 merge_options(Options, DefOptions, FinalOptions),
370 phrase(message(Backtrace, FinalOptions), Lines),
371 print_message_lines(Stream, '', Lines).
372
373:- public 374 message//1. 375
376message(Backtrace) -->
377 {default_backtrace_options(Options)},
378 message(Backtrace, Options).
379
380message(Backtrace, Options) -->
381 message_frames(Backtrace, Options),
382 warn_nodebug(Backtrace).
383
384message_frames([], _) -->
385 [].
386message_frames([H|T], Options) -->
387 message_frames(H, Options),
388 ( {T == []}
389 -> []
390 ; [nl],
391 message_frames(T, Options)
392 ).
393
394message_frames(frame(Level, Where, 0), Options) -->
395 !,
396 level(Level),
397 where_no_goal(Where, Options).
398message_frames(frame(Level, _Where, '$toplevel':toplevel_call(_)), _) -->
399 !,
400 level(Level),
401 [ '<user>'-[] ].
402message_frames(frame(Level, Where, Goal), Options) -->
403 level(Level),
404 [ ansi(code, '~p', [Goal]) ],
405 where_goal(Where, Options).
406
407where_no_goal(foreign(PI), _) -->
408 [ '~w <foreign>'-[PI] ].
409where_no_goal(call(PI), _) -->
410 [ '~w'-[PI] ].
411where_no_goal(pred_line(PredName, File:Line), Options) -->
412 !,
413 [ '~w at '-[PredName] ], file_line(File:Line, Options).
414where_no_goal(clause_name(ClauseName), _) -->
415 !,
416 [ '~w <no source>'-[ClauseName] ].
417where_no_goal(clause(Clause, PC), Options) -->
418 { nonvar(Clause),
419 !,
420 clause_where(false, Clause, PC, Where, Options)
421 },
422 where_no_goal(Where, Options).
423where_no_goal(meta_call, _) -->
424 [ '<meta call>' ].
425
426where_goal(foreign(_), _) -->
427 [ ' <foreign>'-[] ],
428 !.
429where_goal(pred_line(_PredName, File:Line), Options) -->
430 !,
431 [ ' at ' ], file_line(File:Line, Options).
432where_goal(clause_name(ClauseName), _) -->
433 !,
434 [ '~w <no source>'-[ClauseName] ].
435where_goal(clause(Clause, PC), Options) -->
436 { nonvar(Clause),
437 !,
438 clause_where(false, Clause, PC, Where, Options)
439 },
440 where_goal(Where, Options).
441where_goal(clause(Clause, _PC), _) -->
442 { clause_property(Clause, file(File)),
443 clause_property(Clause, line_count(Line))
444 },
445 !,
446 [ ' at ', url(File:Line) ].
447where_goal(clause(Clause, _PC), _) -->
448 { clause_name(Clause, ClauseName)
449 },
450 !,
451 [ ' ~w <no source>'-[ClauseName] ].
452where_goal(_, _) -->
453 [].
454
455level(Level) -->
456 [ ansi(bold, '~|~t[~D]~6+ ', [Level]) ].
457
458file_line(File:Line, Options), option(show_files(basename), Options) ==>
459 { file_base_name(File, Base),
460 format(string(Label), '~w:~d', [Base, Line])
461 },
462 [ url(File:Line, Label) ].
463file_line(File:Line, _Options) ==>
464 [ url(File:Line) ].
465
466warn_nodebug(Backtrace) -->
467 { contiguous(Backtrace) },
468 !.
469warn_nodebug(_Backtrace) -->
470 [ nl,nl,
471 'Note: some frames are missing due to last-call optimization.'-[], nl,
472 'Re-run your program in debug mode (:- debug.) to get more detail.'-[]
473 ].
474
475contiguous([frame(D0,_,_)|Frames]) :-
476 contiguous(Frames, D0).
477
478contiguous([], _).
479contiguous([frame(D1,_,_)|Frames], D0) :-
480 D1 =:= D0-1,
481 contiguous(Frames, D1).
482
483
488
489:- multifile
490 user:prolog_clause_name/2. 491
492clause_predicate_name(Clause, PredName) :-
493 user:prolog_clause_name(Clause, PredName),
494 !.
495clause_predicate_name(Clause, PredName) :-
496 nth_clause(Head, _N, Clause),
497 !,
498 predicate_name(user:Head, PredName).
499
500
504
505backtrace(MaxDepth) :-
506 get_prolog_backtrace_lc(MaxDepth, Stack, []),
507 print_prolog_backtrace(user_error, Stack).
508
509
510subgoal_position(ClauseRef, PC, File, CharA, CharZ) :-
511 debug(backtrace, 'Term-position in ~p at PC=~w:', [ClauseRef, PC]),
512 clause_info(ClauseRef, File, TPos, _),
513 '$clause_term_position'(ClauseRef, PC, List),
514 debug(backtrace, '\t~p~n', [List]),
515 find_subgoal(List, TPos, PosTerm),
516 compound(PosTerm),
517 arg(1, PosTerm, CharA),
518 arg(2, PosTerm, CharZ).
519
523
524find_subgoal(_, Pos, Pos) :-
525 var(Pos),
526 !.
527find_subgoal([A|T], term_position(_, _, _, _, PosL), SPos) :-
528 nth1(A, PosL, Pos),
529 !,
530 find_subgoal(T, Pos, SPos).
531find_subgoal([1|T], brace_term_position(_,_,Pos), SPos) :-
532 !,
533 find_subgoal(T, Pos, SPos).
534find_subgoal(List, parentheses_term_position(_,_,Pos), SPos) :-
535 !,
536 find_subgoal(List, Pos, SPos).
537find_subgoal(_, Pos, Pos).
538
539
545
546lineno(File, Char, Line) :-
547 setup_call_cleanup(
548 ( prolog_clause:try_open_source(File, Fd),
549 set_stream(Fd, newline(detect))
550 ),
551 lineno_(Fd, Char, Line),
552 close(Fd)).
553
554lineno_(Fd, Char, L) :-
555 stream_property(Fd, position(Pos)),
556 stream_position_data(char_count, Pos, C),
557 C > Char,
558 !,
559 stream_position_data(line_count, Pos, L0),
560 L is L0-1.
561lineno_(Fd, Char, L) :-
562 skip(Fd, 0'\n),
563 lineno_(Fd, Char, L).
564
565
566 569
573
574print_last_choicepoint :-
575 prolog_current_choice(ChI0), 576 prolog_choice_attribute(ChI0, parent, ChI1),
577 print_last_choicepoint(ChI1, []).
578print_last_choicepoint.
579
581
582print_last_choicepoint(ChI1, Options) :-
583 real_choice(ChI1, ChI),
584 prolog_choice_attribute(ChI, frame, F),
585 prolog_frame_attribute(F, goal, Goal),
586 Goal \= '$execute_goal2'(_,_,_), 587 !,
588 option(message_level(Level), Options, warning),
589 get_prolog_backtrace(2, [_|Stack], [frame(F)]),
590 ( predicate_property(Goal, foreign)
591 -> print_message(Level, choicepoint(foreign(Goal), Stack))
592 ; prolog_frame_attribute(F, clause, Clause),
593 ( prolog_choice_attribute(ChI, pc, PC)
594 -> Ctx = jump(PC)
595 ; prolog_choice_attribute(ChI, clause, Next)
596 -> Ctx = clause(Next)
597 ),
598 print_message(Level, choicepoint(clause(Goal, Clause, Ctx), Stack))
599 ).
600print_last_choicepoint(_, _).
601
602real_choice(Ch0, Ch) :-
603 prolog_choice_attribute(Ch0, type, Type),
604 dummy_type(Type),
605 !,
606 prolog_choice_attribute(Ch0, parent, Ch1),
607 real_choice(Ch1, Ch).
608real_choice(Ch, Ch).
609
610dummy_type(debug).
611dummy_type(none).
612
613prolog:message(choicepoint(Choice, Stack)) -->
614 choice(Choice),
615 [ nl, 'Called from', nl ],
616 message(Stack).
617
618choice(foreign(Goal)) -->
619 success_goal(Goal, 'a foreign choice point').
620choice(clause(Goal, ClauseRef, clause(Next))) -->
621 success_goal(Goal, 'a choice point in alternate clause'),
622 [ nl ],
623 [ ' ' ], clause_descr(ClauseRef), [': clause succeeded', nl],
624 [ ' ' ], clause_descr(Next), [': next candidate clause' ].
625choice(clause(Goal, ClauseRef, jump(PC))) -->
626 { clause_where(false, ClauseRef, PC, Where,
627 [subgoal_positions(true)])
628 },
629 success_goal(Goal, 'an in-clause choice point'),
630 [ nl, ' ' ],
631 where_no_goal(Where).
632
633success_goal(Goal, Reason) -->
634 [ ansi(code, '~p', [Goal]),
635 ' left ~w (after success)'-[Reason]
636 ].
637
638where_no_goal(pred_line(_PredName, File:Line)) -->
639 !,
640 [ url(File:Line) ].
641where_no_goal(clause_name(ClauseName)) -->
642 !,
643 [ '~w <no source>'-[ClauseName] ].
644
645clause_descr(ClauseRef) -->
646 { clause_property(ClauseRef, file(File)),
647 clause_property(ClauseRef, line_count(Line))
648 },
649 !,
650 [ url(File:Line) ].
651clause_descr(ClauseRef) -->
652 { clause_name(ClauseRef, Name)
653 },
654 [ '~w'-[Name] ].
655
656
657 660
694
695:- multifile prolog:prolog_exception_hook/5. 696:- dynamic prolog:prolog_exception_hook/5. 697
698prolog:prolog_exception_hook(error(E, context(Ctx0,Msg)),
699 error(E, context(prolog_stack(Stack),Msg)),
700 Fr, GuardSpec, Debug) :-
701 current_prolog_flag(backtrace, true),
702 \+ is_stack(Ctx0, _Frames),
703 ( atom(GuardSpec)
704 -> debug(backtrace, 'Got uncaught (guard = ~q) exception ~p (Ctx0=~p)',
705 [GuardSpec, E, Ctx0]),
706 stack_guard(GuardSpec),
707 Guard = GuardSpec
708 ; prolog_frame_attribute(GuardSpec, predicate_indicator, Guard),
709 debug(backtrace, 'Got exception ~p (Ctx0=~p, Catcher=~p)',
710 [E, Ctx0, Guard]),
711 stack_guard(Guard)
712 -> true
713 ; Debug == true,
714 stack_guard(debug),
715 Guard = none
716 ),
717 ( current_prolog_flag(backtrace_depth, Depth)
718 -> Depth > 0
719 ; Depth = 20 720 ),
721 get_prolog_backtrace(Depth, Stack0,
722 [ frame(Fr),
723 guard(Guard)
724 ]),
725 debug(backtrace, 'Stack = ~p', [Stack0]),
726 clean_stack(Stack0, Stack1),
727 join_stacks(Ctx0, Stack1, Stack).
728
729clean_stack(List, List) :-
730 stack_guard(X), var(X),
731 !. 732clean_stack(List, Clean) :-
733 clean_stack2(List, Clean).
734
735clean_stack2([], []).
736clean_stack2([H|_], [H]) :-
737 guard_frame(H),
738 !.
739clean_stack2([H|T0], [H|T]) :-
740 clean_stack2(T0, T).
741
742guard_frame(frame(_,clause(ClauseRef, _, _))) :-
743 nth_clause(M:Head, _, ClauseRef),
744 functor(Head, Name, Arity),
745 stack_guard(M:Name/Arity).
746
747join_stacks(Ctx0, Stack1, Stack) :-
748 nonvar(Ctx0),
749 Ctx0 = prolog_stack(Stack0),
750 is_list(Stack0), !,
751 append(Stack0, Stack1, Stack).
752join_stacks(_, Stack, Stack).
753
754
763
764stack_guard(none).
765stack_guard(system:catch_with_backtrace/3).
766stack_guard(debug).
767
768
769 772
773:- multifile
774 prolog:message//1. 775
776prolog:message(error(Error, context(Stack, Message))) -->
777 { Message \== 'DWIM could not correct goal',
778 is_stack(Stack, Frames)
779 },
780 !,
781 '$messages':translate_message(error(Error, context(_, Message))),
782 [ nl, 'In:', nl ],
783 ( {is_list(Frames)}
784 -> message(Frames)
785 ; ['~w'-[Frames]]
786 ).
787
788is_stack(Stack, Frames) :-
789 nonvar(Stack),
790 Stack = prolog_stack(Frames)