View source with formatted comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2002-2024, University of Amsterdam
    7                              VU University Amsterdam
    8                              CWI, Amsterdam
    9                              SWI-Prolog Solutions b.v.
   10    All rights reserved.
   11
   12    Redistribution and use in source and binary forms, with or without
   13    modification, are permitted provided that the following conditions
   14    are met:
   15
   16    1. Redistributions of source code must retain the above copyright
   17       notice, this list of conditions and the following disclaimer.
   18
   19    2. Redistributions in binary form must reproduce the above copyright
   20       notice, this list of conditions and the following disclaimer in
   21       the documentation and/or other materials provided with the
   22       distribution.
   23
   24    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   25    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   26    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   27    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   28    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   29    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   30    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   31    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   32    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   33    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   34    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   35    POSSIBILITY OF SUCH DAMAGE.
   36*/
   37
   38:- module(prolog_debug,
   39          [ debug/3,                    % +Topic, +Format, :Args
   40            debug/1,                    % +Topic
   41            nodebug/1,                  % +Topic
   42            debugging/1,                % ?Topic
   43            debugging/2,                % ?Topic, ?Bool
   44            list_debug_topics/0,
   45            list_debug_topics/1,        % +Options
   46            debug_message_context/1,    % (+|-)What
   47
   48            assertion/1                 % :Goal
   49          ]).   50:- autoload(library(lists),[append/3,delete/3,selectchk/3,member/2]).   51:- autoload(library(prolog_stack),[backtrace/1]).   52:- autoload(library(option), [option/3, option/2]).   53:- autoload(library(dcg/high_order), [sequence/5]).   54
   55:- set_prolog_flag(generate_debug_info, false).   56:- create_prolog_flag(optimise_debug, default,
   57                      [ keep(true),
   58                        type(oneof([default,false,true]))
   59                      ]).   60
   61:- meta_predicate
   62    assertion(0),
   63    debug(+,+,:).   64
   65:- multifile prolog:assertion_failed/2.   66:- dynamic   prolog:assertion_failed/2.   67
   68/*:- use_module(library(prolog_stack)).*/ % We use the autoloader if needed
   69
   70%:- set_prolog_flag(generate_debug_info, false).
   71
   72:- dynamic
   73    debugging/3.                    % Topic, Enabled, To
   74
   75/** <module> Print debug messages and test assertions
   76
   77This library is a replacement for  format/3 for printing debug messages.
   78Messages are assigned a _topic_. By   dynamically  enabling or disabling
   79topics the user can  select  desired   messages.  Calls  to  debug/3 and
   80assertion/1 are removed when  the  code   is  compiled  for optimization
   81unless the Prolog flag `optimise_debug` is set to `true`.
   82
   83Using the predicate assertion/1 you  can   make  assumptions  about your
   84program explicit, trapping the debugger if the condition does not hold.
   85
   86Output and actions by these predicates   can be configured using _hooks_
   87to fit your environment. With XPCE, you can  use the call below to start
   88a graphical monitoring tool.
   89
   90    ?- prolog_ide(debug_monitor).
   91*/
   92
   93%!  debugging(+Topic) is semidet.
   94%!  debugging(-Topic) is nondet.
   95%!  debugging(?Topic, ?Bool) is nondet.
   96%
   97%   Examine debug topics. The form debugging(+Topic)  may be used to
   98%   perform more complex debugging tasks.   A typical usage skeleton
   99%   is:
  100%
  101%     ```
  102%           (   debugging(mytopic)
  103%           ->  <perform debugging actions>
  104%           ;   true
  105%           ),
  106%           ...
  107%     ```
  108%
  109%   The other two calls are intended to examine existing and enabled
  110%   debugging tokens and are typically not used in user programs.
  111
  112debugging(Topic) :-
  113    debugging(Topic, true, _To).
  114
  115debugging(Topic, Bool) :-
  116    debugging(Topic, Bool, _To).
  117
  118%!  debug(+Topic) is det.
  119%!  nodebug(+Topic) is det.
  120%
  121%   Add/remove a topic  from  being   printed.  nodebug(_)  removes  all
  122%   topics. Gives a warning if the  topic   is  not defined unless it is
  123%   used from a directive. The latter allows placing debug topics at the
  124%   start of a (load-)file without warnings.
  125%
  126%   For debug/1, Topic can be  a  term   `Topic  >  Out`, where `Out` is
  127%   either a stream or  stream-alias  or   a  filename  (an  atom). This
  128%   redirects debug information on this topic   to  the given output. On
  129%   Linux systems redirection can be used   to  make the message appear,
  130%   even if the `user_error` stream is redefined using
  131%
  132%       ?- debug(Topic > '/proc/self/fd/2').
  133%
  134%   A platform independent way to  get   debug  messages  in the current
  135%   console (for example, a `swipl-win` window,  or login using `ssh` to
  136%   Prolog running an SSH server from the `libssh` pack) is to use:
  137%
  138%       ?- stream_property(S, alias(user_error)),
  139%          debug(Topic > S).
  140%
  141%   Do not forget to  disable  the   debugging  using  nodebug/1  before
  142%   quitting the console if Prolog must remain running.
  143
  144debug(Topic) :-
  145    with_mutex(prolog_debug, debug(Topic, true)).
  146nodebug(Topic) :-
  147    with_mutex(prolog_debug, debug(Topic, false)).
  148
  149debug(Spec, Val) :-
  150    debug_target(Spec, Topic, Out),
  151    (   (   retract(debugging(Topic, Enabled0, To0))
  152        *-> update_debug(Enabled0, To0, Val, Out, Enabled, To),
  153            assert(debugging(Topic, Enabled, To)),
  154            fail
  155        ;   (   prolog_load_context(file, _)
  156            ->  true
  157            ;   print_message(warning, debug_no_topic(Topic))
  158            ),
  159            update_debug(false, [], Val, Out, Enabled, To),
  160            assert(debugging(Topic, Enabled, To))
  161        )
  162    ->  true
  163    ;   true
  164    ).
  165
  166debug_target(Spec, Topic, To) :-
  167    nonvar(Spec),
  168    Spec = (Topic > To),
  169    !.
  170debug_target(Topic, Topic, -).
  171
  172update_debug(_, To0, true, -, true, To) :-
  173    !,
  174    ensure_output(To0, To).
  175update_debug(true, To0, true, Out, true, Output) :-
  176    !,
  177    (   memberchk(Out, To0)
  178    ->  Output = To0
  179    ;   append(To0, [Out], Output)
  180    ).
  181update_debug(false, _, true, Out, true, [Out]) :- !.
  182update_debug(_, _, false, -, false, []) :- !.
  183update_debug(true, [Out], false, Out, false, []) :- !.
  184update_debug(true, To0, false, Out, true, Output) :-
  185    !,
  186    delete(To0, Out, Output).
  187
  188ensure_output([], [user_error]) :- !.
  189ensure_output(List, List).
  190
  191%!  debug_topic(+Topic) is det.
  192%
  193%   Declare a topic for debugging.  This can be used to find all
  194%   topics available for debugging.
  195
  196debug_topic(Topic) :-
  197    (   debugging(Registered, _, _),
  198        Registered =@= Topic
  199    ->  true
  200    ;   assert(debugging(Topic, false, []))
  201    ).
  202
  203%!  list_debug_topics is det.
  204%!  list_debug_topics(+Options) is det.
  205%
  206%   List currently known topics for debug/3   and their setting. Options
  207%   is  either  an  atom  or   string,    which   is   a  shorthand  for
  208%   `[search(String)]` or a normal option list. Defined options are:
  209%
  210%     - search(String)
  211%       Only show topics that match String.  Match is case insensitive
  212%       on the printed representation of the term.
  213%     - active(+Boolean)
  214%       Only print topics that are active (`true`) or inactive
  215%       (`false`).
  216%     - output(+To)
  217%       Only print topics whose target location matches To.  This option
  218%       implicitly restricts the output to active topics.
  219
  220list_debug_topics :-
  221    list_debug_topics([]).
  222
  223list_debug_topics(Options) :-
  224    (   atom(Options)
  225    ;   string(Options)
  226    ),
  227    !,
  228    list_debug_topics([search(Options)]).
  229list_debug_topics(Options) :-
  230    option(active(Activated), Options, _),
  231    findall(debug_topic(Topic, String, Activated, To),
  232            matching_topic(Topic, String, Activated, To, Options),
  233            Tuples),
  234    print_message(information, debug_topics(Tuples)).
  235
  236matching_topic(Topic, String, Activated, To, Options) :-
  237    debugging(Topic, Activated, To),
  238    (   option(output(Stream), Options)
  239    ->  memberchk(Stream, To)
  240    ;   true
  241    ),
  242    topic_to_string(Topic, String),
  243    (   option(search(Search), Options)
  244    ->  sub_atom_icasechk(String, _, Search)
  245    ;   true
  246    ).
  247
  248topic_to_string(Topic, String) :-
  249    numbervars(Topic, 0, _, [singletons(true)]),
  250    term_string(Topic, String, [quoted(true), numbervars(true)]).
  251
  252:- multifile
  253    prolog_debug_tools:debugging_hook/0.  254
  255prolog_debug_tools:debugging_hook :-
  256    (   debugging(_, true, _)
  257    ->  list_debug_topics([active(true)])
  258    ).
  259
  260
  261%!  debug_message_context(+What) is det.
  262%
  263%   Specify additional context for debug messages.
  264%
  265%   @deprecated New code should use   the Prolog flag `message_context`.
  266%   This predicates adds or deletes topics from this list.
  267
  268debug_message_context(+Topic) :-
  269    current_prolog_flag(message_context, List),
  270    (   memberchk(Topic, List)
  271    ->  true
  272    ;   append(List, [Topic], List2),
  273        set_prolog_flag(message_context, List2)
  274    ).
  275debug_message_context(-Topic) :-
  276    current_prolog_flag(message_context, List),
  277    (   selectchk(Topic, List, Rest)
  278    ->  set_prolog_flag(message_context, Rest)
  279    ;   true
  280    ).
  281
  282%!  debug(+Topic, +Format, :Args) is det.
  283%
  284%   Format a message if debug topic  is enabled. Similar to format/3
  285%   to =user_error=, but only prints if   Topic is activated through
  286%   debug/1. Args is a  meta-argument  to   deal  with  goal for the
  287%   @-command.   Output   is   first    handed     to    the    hook
  288%   prolog:debug_print_hook/3.  If  this  fails,    Format+Args   is
  289%   translated  to  text   using    the   message-translation   (see
  290%   print_message/2) for the  term  debug(Format,   Args)  and  then
  291%   printed to every matching destination   (controlled  by debug/1)
  292%   using print_message_lines/3.
  293%
  294%   The message is preceded by '% ' and terminated with a newline.
  295%
  296%   @see    format/3.
  297
  298debug(Topic, Format, Args) :-
  299    debugging(Topic, true, To),
  300    !,
  301    print_debug(Topic, To, Format, Args).
  302debug(_, _, _).
  303
  304
  305%!  prolog:debug_print_hook(+Topic, +Format, +Args) is semidet.
  306%
  307%   Hook called by debug/3.  This  hook   is  used  by the graphical
  308%   frontend that can be activated using prolog_ide/1:
  309%
  310%     ==
  311%     ?- prolog_ide(debug_monitor).
  312%     ==
  313
  314:- multifile
  315    prolog:debug_print_hook/3.  316
  317print_debug(_Topic, _To, _Format, _Args) :-
  318    nb_current(prolog_debug_printing, true),
  319    !.
  320print_debug(Topic, To, Format, Args) :-
  321    setup_call_cleanup(
  322        nb_setval(prolog_debug_printing, true),
  323        print_debug_guarded(Topic, To, Format, Args),
  324        nb_delete(prolog_debug_printing)).
  325
  326print_debug_guarded(Topic, _To, Format, Args) :-
  327    prolog:debug_print_hook(Topic, Format, Args),
  328    !.
  329print_debug_guarded(_, [], _, _) :- !.
  330print_debug_guarded(Topic, To, Format, Args) :-
  331    phrase('$messages':translate_message(debug(Format, Args)), Lines),
  332    (   member(T, To),
  333        debug_output(T, Stream),
  334        with_output_to(
  335            Stream,
  336            print_message_lines(current_output, kind(debug(Topic)), Lines)),
  337        fail
  338    ;   true
  339    ).
  340
  341
  342debug_output(user, user_error) :- !.
  343debug_output(Stream, Stream) :-
  344    is_stream(Stream),
  345    !.
  346debug_output(File, Stream) :-
  347    open(File, append, Stream,
  348         [ close_on_abort(false),
  349           alias(File),
  350           buffer(line)
  351         ]).
  352
  353
  354                 /*******************************
  355                 *           ASSERTION          *
  356                 *******************************/
  357
  358%!  assertion(:Goal) is det.
  359%
  360%   Acts similar to C assert()  macro.  It   has  no  effect if Goal
  361%   succeeds. If Goal fails or throws    an exception, the following
  362%   steps are taken:
  363%
  364%     * call prolog:assertion_failed/2.  If prolog:assertion_failed/2
  365%       fails, then:
  366%
  367%       - If this is an interactive toplevel thread, print a
  368%         message, the stack-trace, and finally trap the debugger.
  369%       - Otherwise, throw error(assertion_error(Reason, G),_) where
  370%         Reason is one of =fail= or the exception raised.
  371
  372assertion(G) :-
  373    \+ \+ catch(G,
  374                Error,
  375                assertion_failed(Error, G)),
  376
  377    !.
  378assertion(G) :-
  379    assertion_failed(fail, G),
  380    assertion_failed.               % prevent last call optimization.
  381
  382assertion_failed(Reason, G) :-
  383    prolog:assertion_failed(Reason, G),
  384    !.
  385assertion_failed(unwind(_), _) :-
  386    !.
  387assertion_failed(Reason, _) :-
  388    assertion_rethrow(Reason),
  389    !,
  390    throw(Reason).
  391assertion_failed(Reason, G) :-
  392    print_message(error, assertion_failed(Reason, G)),
  393    backtrace(10),
  394    (   current_prolog_flag(break_level, _) % interactive thread
  395    ->  trace
  396    ;   throw(error(assertion_error(Reason, G), _))
  397    ).
  398
  399assertion_failed.
  400
  401assertion_rethrow(time_limit_exceeded).
  402
  403                 /*******************************
  404                 *           EXPANSION          *
  405                 *******************************/
  406
  407%       The optimise_debug flag  defines whether  Prolog  optimizes
  408%       away assertions and  debug/3 statements.  Values are =true=
  409%       (debug is optimized away),  =false= (debug is retained) and
  410%       =default= (debug optimization is dependent on the optimise
  411%       flag).
  412
  413optimise_debug :-
  414    (   current_prolog_flag(optimise_debug, true)
  415    ->  true
  416    ;   current_prolog_flag(optimise_debug, default),
  417        current_prolog_flag(optimise, true)
  418    ->  true
  419    ).
  420
  421:- multifile
  422    system:goal_expansion/2.  423
  424system:goal_expansion(debug(Topic,_,_), true) :-
  425    (   optimise_debug
  426    ->  true
  427    ;   debug_topic(Topic),
  428        fail
  429    ).
  430system:goal_expansion(debugging(Topic), fail) :-
  431    (   optimise_debug
  432    ->  true
  433    ;   debug_topic(Topic),
  434        fail
  435    ).
  436system:goal_expansion(assertion(_), true) :-
  437    optimise_debug.
  438system:goal_expansion(assume(_), true) :-
  439    print_message(informational,
  440                  compatibility(renamed(assume/1, assertion/1))),
  441    optimise_debug.
  442
  443
  444                 /*******************************
  445                 *            MESSAGES          *
  446                 *******************************/
  447
  448:- multifile
  449    prolog:message/3.  450
  451prolog:message(assertion_failed(_, G)) -->
  452    [ 'Assertion failed: ~q'-[G] ].
  453prolog:message(debug(Fmt, Args)) -->
  454    [ Fmt-Args ].
  455prolog:message(debug_no_topic(Topic)) -->
  456    [ '~q: no matching debug topic (yet)'-[Topic] ].
  457prolog:message(debug_topics(Tuples)) -->
  458    [ ansi(bold, '~w~t ~w~35| ~w~n', ['Debug Topic', 'Activated', 'To']),
  459      '~`\u2015t~48|', nl
  460    ],
  461    sequence(debug_topic, [nl], Tuples).
  462
  463debug_topic(debug_topic(_, TopicString, true, [user_error])) -->
  464    [ ansi(bold, '~s~t \u2714~35|', [TopicString]) ].
  465debug_topic(debug_topic(_, TopicString, true, To)) -->
  466    [ ansi(bold, '~s~t \u2714~35| ~q', [TopicString, To]) ].
  467debug_topic(debug_topic(_, TopicString, false, _To)) -->
  468    [ '~s~t -~35|'-[TopicString] ].
  469
  470
  471                 /*******************************
  472                 *             HOOKS            *
  473                 *******************************/
  474
  475%!  prolog:assertion_failed(+Reason, +Goal) is semidet.
  476%
  477%   This hook is called if the Goal  of assertion/1 fails. Reason is
  478%   unified with either =fail= if Goal simply failed or an exception
  479%   call otherwise. If this hook  fails,   the  default behaviour is
  480%   activated.  If  the  hooks  throws  an   exception  it  will  be
  481%   propagated into the caller of assertion/1.
  482
  483
  484                 /*******************************
  485                 *            SANDBOX           *
  486                 *******************************/
  487
  488:- multifile sandbox:safe_meta/2.  489
  490sandbox:safe_meta(prolog_debug:assertion(X), [X]).
  491
  492		 /*******************************
  493		 *  SUPPRESS AUTOLOAD WARNINGS	*
  494		 *******************************/
  495
  496'$nowarn_autoload'(debug/1, _).
  497'$nowarn_autoload'(nodebug/1, _)