As mentioned earlier, there are two kinds of breakpoints:
spypoints and advice points. The main purpose of
spypoints is to support interactive debugging. In contrast with
this, advice points can help you to perform non-interactive
debugging activities. For example, the following advice point will
check a program invariant: whether the condition
always holds at exit from
| ?- add_breakpoint([pred(foo/2),advice] -[exit,goal(foo(X,Y)),\+true(Y-X<3),trace], _). % Conditional advice point for user:foo/2 added, BID=1 % advice | ?- foo(4, Y). Y = 3 % advice | ?- foo(9, Y). 3 3 Exit: foo(7,13) ? n 2 2 Exit: foo(8,21) ?
The test part of the above breakpoint contains a
advice condition, making it an advice point. (You
can also include the
debugger condition in spypoint specs,
although this is the default interpretation.)
The action part starts with the
condition. Because of this the rest of the action part is evaluated only
at Exit ports. By placing the port condition in the action
part, we ensure the creation of a procedure box at the Call
port, as explained earlier.
Next, we get hold of the goal arguments using the
goal condition, and use the
\+true(Y-X<3) test to check if
the invariant is violated. If this happens, then the last condition sets the
mode action variable to
trace, switching on the
add_breakpoint/2 call the above example shows two
top-level calls to
foo/2. The invariant holds within the first
goal, but is violated within the second. Notice that the advice
mechanism works with the interactive debugger switched off.
You can ask the question, why do we need advice points? The same task could be implemented using a spypoint. For example:
| ?- add_breakpoint(pred(foo/2) -[exit,goal(foo(X,Y)),\+true(Y-X<3),leash], _). % The debugger will first zip -- showing spypoints (zip) % Conditional spypoint for user:foo/2 added, BID=1 % zip | ?- foo(4, X). X = 3 % zip | ?- foo(9, X). * 3 3 Exit: foo(7,13) ? z * 2 2 Exit: foo(8,21) ?
The main reason to have a separate advice mechanism is to be able to perform checks independently of the interactive debugging. With the second solution, if you happen to start some interactive debugging, then you cannot be sure that the invariant is always checked. For example, no spypoints will be activated during a skip. In contrast with this, the advice mechanism is watching the program execution all the time, independently of the debugging mode.
Advice points are handled in very much the same way as spypoints are. When arriving at a port, advice point selection takes place first, followed by spypoint selection. This can be viewed as the debugger making two passes over the current breakpoints, considering advice points only in the first pass, and spypoints only in the second.
In both passes the debugger tries to find a breakpoint that can be activated, checking the test and action parts, as described earlier. However, there are some differences between the two passes:
modeis set to current debugging mode,
command = proceed,
show = silent. Note that this is done independently of the debugging mode (in contrast with the spypoint search initialization).
. This means that if no action part is given, then the only effect of the advice point will be to build a procedure box (because of the
command = proceedinitialization).
commandis set to
Having performed advice processing, the debugger inspects the
command variable. The command values different from
flit are called divertive, as they alter
the normal flow of control (e.g.
involve user interaction (
ask). If the
command value is
divertive, then the prescribed action is performed immediately, without
executing the spypoint selection process. Otherwise, if
command = proceed, then it is noted that the advice part requests the
building of a procedure box. Next, the second, spypoint
processing pass is carried out, and possible user interaction takes
place, as described earlier. A procedure box is built if either
the advice point or the spypoint search requests this.
Let us conclude this section by another example, a generic advice point for collecting branch coverage information:
| ?- add_breakpoint( (advice,call) - ( line(F,L) -> true(assert(line_reached(F,L))), flit ; flit ), _). % Generic advice point added, BID=1 % advice,source_info | ?- foo(4,X). X = 3 ? ; no % advice,source_info | ?- setof(X, line_reached(F,X), S). F = '/home/bob/myprog.pl', S = [31,33,34,35,36]
This advice point will be applicable at every Call port. It
will then assert a fact with the file name and the line number if
source information is available. Finally, it will set the
flit on both branches of execution. This is to
communicate the fact that the advice point does not request the
building of a procedure box.
It is important to note that this recording of the line numbers reached is performed independently of the interactive debugging.
In this example we used the
','/2 operator, rather than
list notation, for describing the conjunction of conditions, as
this seems to better fit the if-then-else expression used in the action
part. We could have still used lists in the tests part, and in the
“then” part of the actions. Note that if we omit the “else” branch, then
the action part will fail if no source information is available for the
given call. This will cause a procedure box to be built, which is
an unnecessary overhead. An alternative solution, using the
line/2 test twice, is the following:
| ?- add_breakpoint([advice,call,line(_,_)]- [line(F,L),true(assert(line_reached(F,L))),flit], _).
Further examples of advice points are available in