Bug hunting toolbox

Note: This is a work in progress. When it is complete these notes will be removed.

Note: Do not reply to this topic, questions, concerns, comments, etc. are to handled in
Wiki Discussion: Bug hunting toolbox

Note: This is just to get the topic started and hopefully others to jump in and make this useful. Even if you are brand new to Prolog, this is such a basic and fundamental concept that you can and should join in to help improve the value of this Wiki. Join the discussion at Wiki Discussion: Bug hunting toolbox

Note: This post is a wiki page and if you have Trust level: Basic you can edit this by clicking on the pencil icon in the lower right. Capture
These topics also have history so they can be rolled-back if needed.

Note: For now this is just a random collection of items as a list. They still need to be properly formatted and have a more detailed explanation with examples, but for now having them collected in one place is better than not having them at all. As noted, anyone can edit this and even add more details and example code to this. :smiley:


Prolog Unit Tests

Individual test can be run from the command line, e.g.

:- begin_tests(test_group).

test(01) :- 
   format('done.~w',[]).

:- end_tests(test_group).
?- run_tests(test_group:1).

This can also be run using gtrace/0, e.g.

?- gtrace.
true.

[trace]  ?- run_tests(test_group:1).

Typically, cross-module calls are considered OK for testing and debugging. You can dynamically import any predicate from a module.

:- export(m:p/1).
:- import(m:p/1).

(ref)


Debugging and Tracing Programs

catch/3

catch_with_backtrace/3

call_with_time_limit/2
call_with_depth_limit/3
call_with_inference_limit/3

Control Predicates

Failure slice

protocol/1

print_term/2

write_canonical/1

writeq/1,2

write_term(X,[dotlists(true)]).

List the program, predicates or clauses

Verify Type of a Term

format/2,3


library(debug) - Manage debug messages and check assertions. Example of using debug/1

Also see Debug infrastructure makes a distinction between topics as “string” and as “atom”


Obtaining Runtime Statistics

Execution profiling


error.pl – Error generating support

jan
Just for the record, error:has_type/2 should not throw exceptions. That is the task of the more general must_be/2 that uses has_type/2.


  • is_of_type(+Type, @Term) is semidet
    True if Term satisfies Type.

Runtime determinacy checker (rdet) - can be used to trap predicates that should never fail.

library(error): Error generating support

  • must_be (+Type, @Term)
    True if Term satisfies the type constraints for Type. Defined types are atom , atomic , between , boolean , callable , chars , codes , text , compound , constant , float , integer , nonneg , positive_integer , negative_integer , nonvar , number , oneof , list , list_or_partial_list , symbol , var , rational , encoding , dict and string .

library(check): Consistency checking - This library provides some consistency checks for the loaded Prolog program.

Cross-referencer - A cross-referencer is a tool that examines the caller-callee relation between predicates, and, using this information to explicate dependency relations between source files, finds calls to non-existing predicates and predicates for which no callers can be found.

  • gxref - Run cross-referencer on all currently loaded files and present a graphical overview of the result.

Exception handling

Draw diagrams - Graph drawing software (Wikipedia) or Pen and paper.
GraphViz is a popular console application which has several domain specific languages for describing graphs on which DOT seems to be the most popular. Example here.

Generate diagrams for your Prolog module applications

Consider Cognitive dimensions of notations

If the data has a sequential pattern to it check Online Encyclopedia of Integer Sequences(OEIS) for references and ideas.Catalan numbers and Hamilton Paths are common

When writing formatted text to a string:
format/3 with with_output_to/2

Environment Control (Prolog flags)

  • generate_debug_info(bool, changeable)
    If true (default) generate code that can be debugged using trace/0, spy/1, etc. Can be set to false using the -nodebug . This flag is scoped within a source file. Many of the libraries have :- set_prolog_flag(generate_debug_info, false) to hide their details from a normal trace.
    See: Append/3 isn’t deterministic if first arg is var?

listing/2 The option source(true) is quite helpful to find the actual definition of multifile predicates and where the clauses come from.


Add this to your code at the place where you want a conditional break and run make/0 .

    (some_condition -> gtrace ; true),

(ref)


Using the Logtalk linter to check Prolog modules code: See: Linter

Descriptions of the icons/glyphs in the graphical debugger?

For a Finite State Automata of the states using gtrace/0 see: Gtrace - Example finite-state automaton
The FSA is a PDF which needs to be downloaded: SWI-Prolog Debugger FSA.pdf (682.7 KB)

prolog_clause.pl – Get detailed source-information about a clause. This module started life as part of the GUI tracer. As it is generally useful for debugging purposes it has moved to the general Prolog library.

Examining the program - Noted in topic: Line number (and file name) of fact read, specifically this post.

Edit Exceptions in the prolog editor. Who knew that existed and had error defined to trace “if not caught”? - Trace_on_error

SWI-Prolog web-enabled monitor (GitHub) (post)
Usage: ?- prolog_ide(debug_monitor).

Related: Matching patterns without unification in Prolog - Stack Overflow

?- debug(concurrent). See: Some notes on writing a concurrent programing Howto. Comments and corrections welcome

Note: The coverage analysis tool as of 11/28/2019 is just a proof of concept. See: PLunit How-To / teaching example

Related to debugging (trying to understand working code).
When the code is working, there are lots of good test cases and you don’t know what a predicate or line is doing then comment it out, run the test and see what breaks.

Sometimes when using gtrace/0 and single stepping, the code will appear to skip ahead. To see the predicates that were skipped over look in the Call Stack dialog and click on the previous predicate call in the stack. You can continue clicking on the predicate calls in the stack all the way back to the start of the thread.

Note: When using any form of assert with any form of retract, and you are getting unrepeatable results, e.g. unit tests are now failing that use to pass and no change was made to the test or predicates used by the test, then shut down the top level with halt/0 and start again as the Prolog database is probably corrupted.

Advise: If you are not sure how a predicate works before using it, take the time to use it by itself to understand how it works so that you are not compounding your problem by introducing more unknowns. See: Assignment question about lists using findall and between

Advise: For brain storming ideas read " How to solve it : a new aspect of mathematical method" by George Pólya (WorldCat) (partial pdf)

Concolic Testing in Logic Programming

Interacting with a server

It is often useful to be able to interact with a running Prolog process, notably if it concerns an embedded Prolog engine or server process. This allows for inspecting the database, threads, etc., activate debug/3 statements or reload some code using make/0 without service interrupt. This can be achieved using library(prolog_server), providing a telnet connection to the server. Instead of telnet, one can also use nc (net cat). Be aware of the seurity implications! One day, we should provide this functionality on top of libssh and using a pseudo tty such that we can deal with command line editing and ^C.


This idea has one foot in debugging and one foot out so it is included.

Part of debugging is trying to understand which of different strategies may be faster. To make this easier to determine is first_solution/3.

See: Goal Evaluation with Timeouts


Use fuzzing.


SWI-Prolog is open source so look at the source code if you need.

Most predicates implemented in Prolog can have the source code easily found by

  1. Locating the predicate in the documentation. e.g. append/3
  2. Click on image (link)

or

GitHub SWI-Prolog

Libraries are in the SWI-Prolog libraries folder

Packages are in other GitHub repositories


Use numbervars/3

(ref)


Rubber duck debugging

Need a duck, try an interactive version. Duckie


show_coverage/1

Run Goal and write a report on which percentage of the clauses in each file are used by the program and which percentage of the clauses always fail.


Using double negation.

From James Cash’s blog (See blog for details).

In particular, I’ve found it useful when building up a difference list of character codes & wanting to print out what the values are part-way through.


Use color coding in your result messages. See: Adding some color to your text messages


If using the GUI tracer (gtrace), check if the term is cyclic. A cyclic term will show the word cycle in the value display window.

TODO: Create example code that causes the annotation for the variable name.


If others can’t reproduce the problem run check_installation/0

See: SWI-Prolog has check_installation/0

Also consider/do a full uninstall and then reinstall of the application(s).


To aid in debugging term_expansion/2 use debug/3 with listing/1.

See: Simple term_expansion debugging


Debugging Foreign Predicate

Just compile the extension for debugging, run gdb swipl and load the program. Now break using ^C and set a gdb breakpoint on the function. Continue from gdb, call the predicate from Prolog and gdb should trap the breakpoint.

Once there, Prolog objects are typically either integers or anonymous pointers. There is a large number of utility functions that you can call from gdb to know what these objects are. In this context the most practical is

(gdb) call pl_writeln(a)

where a is a variable of type term_t to print the Prolog term. Another useful function is PL_backtrace(depth, flags) to print a Prolog backtrace.

Ref:
Foreign Language Interface
pl_writeln
PL_backtrace()


Checking for tail call optimization.

Use prolog_current_frame/1 (ref)


For Constraint Logic Programming consider using call_residue_vars/2. (ref)


Use gspy/1. (ref)


Using debug/3 with a value that requires calling a predicate to create the value should only occur when debug/3 is active. To achieve this use the format/2 format specification ~@.

For an example see: Debugging with difference list of character codes


Nice to know when bug huting.

predicates, modules and functors (name/arity pairs) are not garbage collected. (ref)


Checking for choice points. prolog_choice_attribute/3 and deterministic/1


Use tspy/1 (ref)


load library(http/http_error) (ref)


Use a meta-interpreter (ref)


Use wrap_predicate/4 (ref)


Use prolog_walk_code/1 (ref)


Use quasiquotations to validate structured text/stream input.


If using an out of cycle release, e.g. head on git, then the web site documentation may be out of sync with the code. To get the updated documentation use help/1 at the command line, e.g. ?- help(redis). (ref)


When using gtrace/0 (ref)

We have the conventional stack and a chain of choice points. Each of these have a stack as well, each eventually links up with the current active environment stack.

We have two types of choice that can be distinguished: those on my parent frame(s) and those that are not. The ones that are not (and thus stand to the right) are subgoals that succeeded with a choice point. If you seek for deterministic last-call optimized code, these are bad.


Note that the source view is an embedded PceEmacs editor in read-only mode. You can used the ‘e’ command to switch to write mode, edit and save using ^x^s, compile using ^c^b (compile buffer).

This allows for fixing things are while running:

  • See the output is incorrect (failure, wrong answer, success while failure was expected, etc).
  • Use ‘e’, edit, ^x^s, ^c^b, hit ‘r’ to retry and test again.

Another useful command is ^x2, Emacs’ split-window . This is mapped by PceEmacs to create a new editor window showing the same file (initially at the same cursor location). You can use the new window with all usual menu functionality for editing. Multiple editor windows on the same file are kept in sync at any time (they are all visualizations of the same editor buffer ).

This strategy for hot-fixing is something unique to Prolog as retry provides us with a time machine. It takes a little while to get used to, but after that it is a life saver for debugging and fixing issues in a program state that takes long to reproduce. Think of long runtime to reach the critical point or a lot of user interaction for interactive programs.

(ref)

The main difference is the time machine provided by retry , which backtracks to the start of a goal before re-running it. If the program has no interfering side effects this means you are really back at the old state. This has two enormous advantages. While walking around in languages without such a time machine you have to be very careful not to jump over the buggy function because if you did, you often have to restart from the beginning. When it is no problem to skip over the buggy code, one can use hierarchical debugging: first step over the whole goal. If you do not like the result, retry and step over each of the sub goals until you find one that misbehaves, etc.

(ref)


By default SWI-Prolog uses two threads: main and gc . The first is your application, the second does atom and clause garbage collection. If you run the development tools such as gtrace/0 or PceEmacs, a pce thread is added that does the GUI so you can edit, examine resource usage, etc. while your application is running.

Using swipl --no-threads it runs completely single threaded. There are very few use cases for that. You can also build a single threaded version from the sources. That makes the core a little smaller and marginally faster.

(ref)


See: Viewing source / setting breakpoints on term_expansion/2 generated rules


Use style_check(-singleton)

Ref: 5 lines of code that hang in Windows, but do not hang in MacOS or Linux


Trace single predicates from top level.

?- trace(plus/4, exit).
%         plus/4: [exit]
?- scanl(plus, [1,2,3,4,5], [10,20,30,40,50], 0, Sums).
 T Exit: plus(1, 10, 0, 11)
 T Exit: plus(2, 20, 11, 33)
 T Exit: plus(3, 30, 33, 66)
 T Exit: plus(4, 40, 66, 110)
 T Exit: plus(5, 50, 110, 165)
Sums = [0, 11, 33, 66, 110, 165].

(ref)


That is why, when a stack trace is dumped, … levels typically have missing levels.

(ref)


Some modules contain :- set_prolog_flag(generate_debug_info, false). which does not allow the trace and debugging tools that use prolog_trace_interception/4 to step into the module.

Just flip the flag with set_prolog_flag(generate_debug_info, true). (ref)


Capture the screen output to a file.

Two options:

  1. Protocol/1 (Used to capture screen to file)
  2. Use tell/1 and told/0. e.g. tell('my_file.txt'), goal(Arguments), told.

Search the GitHub SWI-Prolog repositories. (link)

This will search the code as well as the issues etc.


Make sure all of your software is fully upgraded.


From Jan W. (ref)

some sort of deadlock.

First step would be to use Control-C and then ‘b’ (break) to get a new toplevel and then threads/0 to see the state of all threads. If that doesn’t reveal anything one may need the C level debugger to see what is happening.


For debugging errors related for a version of SWI-Prolog built using cmake this topic is of interest.

Difficult to reproduce problems while running tests


Trap/1 for trapping errors


For more detailed testing of SSL see this post.

The way to get more detail is a little complicated for ssl. Starting from the build directory, run:

cd packages/ssl
../../src/swipl ../../../packages/ssl/test_ssl.pl
<banner>
?- test_ssl.
% PL-Unit: ssl_options ....... done
% PL-Unit: ssl_server . done
% PL-Unit: ssl_keys ..... done
% PL-Unit: https_open . done
% PL-Unit: ssl_certificates ................................ done
% PL-Unit: crypto_data_encrypt . done
% All 47 tests passed
true.

For Stack limit (???Gb) exceeded

See this StackOverflow question How to implement list item deletion for all argument modes? and answer

The fix
To help Prolog find answers for above “problematic” queries, swap the two recursive rules:

deleted(_,[],[]).
deleted(U,[V|W],[V|X]) :-  % this clause was last
   dif(U,V),
   deleted(U,W,X).
deleted(X,[X|Y],Z) :-
   deleted(X,Y,Z).

Since the SWI-Prolog source code is directly accessible from the Prolog top level you can use it to inspect predicates implemented in Prolog, e.g.

?- edit(append/3).
true.

image

but to also see details on control structures, e.g.

?- edit(;).
true.


When converting code to use SSU, if a problem is encountered consider using

?- gtrap(_).

(ref)


For a long running process an example of which is a web server and a patch is needed, consider

Hotloading (AKA Dynamic software updating ( DSU ))

(ref)


Identifying unexpected choice points.

See: Print last choice point


Using explain/1 to produce listing with hyperlinks to lines of code.

See: Explain/1 with hyperlinks


Note that applications can use prolog_listen/3 to keep overall track of created and terminated threads.

(ref)


prove it is sound and safe using an intermediate predicate that we can generate on the fly or using goal_expansion/2?

(ref)


One is that if you load a s(CASP) file using Prolog consult/1, etc. you must be aware that if the file starts with # , the first line is taken as comment. This allows for #!/usr/bin/env swipl to make a Prolog file executable on POSIX systems. So, leave the first line blank.

(ref)


The all solutions predicates (or anything that copies terms) do not play too well with constraints.

(ref)


ODBC drivers may have a tracing option.

For example see: Setting Tracing Options


Since Prolog is about searching and saving state during a search, the state information is saved to a temporary file. For various reasons the location of the temp file may need to be changed such as needing more space or needing a faster drive, etc. (think SDD, HDD, NAS, Ram drive).

The location of the temporary file can be changed using the Prolog flag tmp_dir(atom, changeable) or on the command line by using the goal option -g with the goal using the Prolog flag. (ref)

10 Likes

There is a 32000 character limit for post that is inherited from the DB size of a field. It can be changed but we don’t. So another reply is added to continue the collection of knowledge.


From Jan W. (ref)

Without really answering your question, did you discover the SSU rules (Head => Body) and det/1? Since I use those, debugging time has been reduced a lot. Other recent goodies are trap/1 and gtrap/1 to quickly get the debugger at an exception. And of course, there is check/0 to do some static analysis, be it a lot less than mercury. And, last but not least the new “sweeprolog” mode for GNU emacs or the built-in Emacs clone save you a lot of work.


For creating a test case that mocks an external resourceby Jan W. (ref)

There is nothing out of the box. There are lots of options though. For example, implement a mock version of the library that calls the external service and load it instead of the real thing by changing the (library) search path. You can also use wrap_predicate/4 to create a wrapper around any predicate. That is intended to call the original, but of course you don’t need to. You can even abolish/1 the old predicate and assert a new definition.

None of that is portable. Wrapping predicates is really SWI-Prolog specific. There is no portability in search path handling (but many systems have it) and ISO turned abolish/1 into something useless because you can only use it on dynamic code where, in addition to retractall/1, it removes the predicate properties (dynamic, etc.) SWI-Prolog kept the pre-ISO semantics where abolish/1 applies to any predicate.

Than you need an alternative implementation for the external server. I don’t know how much you can generalize that. I guess it can be anything.