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