[Rd] Am I missing something about debugging?

Mark.Bravington at csiro.au Mark.Bravington at csiro.au
Fri Jan 5 00:16:34 CET 2007

Hi Ross

> Suppose 'b1' 
> > calls 'c1'. If 'c1' exists as "permanent" function defined outside 
> > 'b1' (which I generally prefer, for clarity), then you can call 
> > 'mtrace( c1)' and 'c1' will be invoked whenever it's called-- you 
> > don't have to first 'mtrace' 'b1' and then manually call 
> 'mtrace(c1)' while inside 'b1'.
> Is the effect of mtrace permanent?  For example, if
> b1 <- function() {
>   # stuff
>   c1()
>   # stuff
>   c1()
> }
> And you mtrace(c1), will both calls to c1, as well as any 
> outside of b1, bring up the debugger?

Yes. But there are a couple of tricks you could use to avoid it. First
(less neat), you could 'mtrace( b1)' and then set two fake conditional
breakpoints inside 'b1': one of '{mtrace( c1);F}' before the first call
to 'c1', and one of '{mtrace(c1,F);F}' just after. Second (neater), you
could avoid 'mtrace'ing 'b1' altogether, and instead replace the default
breakpoint at line 1 in 'c1' [SEE ** BELOW] with a conditional
breakpoint. The conditional breakpoint would have to be a bit clever--
it might have to inspect the value of a variable in 'b1' or something
like that. For example, suppose you wrote 'b1' like this:

c1.call.count <- 1
c1( ...)
  c1.call.count <- c1.call.count+1

Then you could 'mtrace( c1)' and do 

'bp( 1, { get( 'c1.call.count', envir=sys.frame( mvb.sys.parent()))==1,

before running 'b1'. Lo & behold, it will only stop in 'c1' on the first
call, not on subsequent ones. [I guess I could add a pass count feature
to the debugger, to make this easier.]

Note that you can call 'bp' without actually being in debug mode, by
using the 'fname' parameter-- the problem is that it is not usually
obvious what line number to set the breakpoint at (though you can find
out by inspecting 'tracees$f'). You can alternatively just 'mtrace( c1)'
and then reset the breakpoint manually the first time it comes up.

One thing I noticed while trying the stuff above, is that you do need to
use 'mvb.sys.parent' instead of 'sys.parent' in the above.
'mvb.sys.parent' and friends are the "do what I mean, not what I say"
equivalents of 'sys.parent' etc for use inside the debugger. The 'debug'
package goes to some trouble to replace calls to 'sys.parent' with calls
to the 'mvb...' equivalents, both in user input and in the debuggee
function, but evidently doesn't do so inside breakpoint expressions.
Consider it a feature...

The frame stack is a thing of great weirdness, by the way; the same
frame can appear several times in the stack, especially when debugging.
I would not recommend trying to really understand it unless you really
have to; the 'debug' package makes efforts to avoid you having to!

> I ask because sometimes the normal "step" semantics in 
> debugging is more useful, i.e., debug into the next call to 
> c1 only.  As I understand it, the debug package can put a 
> one-time only breakpoint (with go), but only in the body of 
> the currently active function.
> Am I correct that both the debug package and the regular 
> debug require explicitly removing debugging from a function 
> to turn off debugging?

True, for the debug package. Not sure about vanilla R debugging
(dissatisfaction with which drove me to write the 'debug' package-- so I
don't use the vanilla stuff these days!).

> > 
> > Even if 'c1' is defined inside the body of 'b1', you can 
> get something 
> > similar by using conditional breakpoints, like this
> > 
> > > mtrace( b1)
> > > # whatever you type to get 'b1' going
> > D(17)> # now look at the code window for 'b1' and find the 
> line just 
> > after the definition of 'c1'
> > D(17)> # ... say that's on line 11
> > D(17)> bp( 11, {mtrace( c1);FALSE})
> > # which will auto-mtrace 'c1' without stopping; of course you could 
> > hardwire this in the code of 'b1' too
> > 
> If you invoke b1 multiple times, will the previous procedure 
> result in wrapping c1 multiple times, e.g., first time 
> through c1 is replaced by mtrace(c1); second time rewrites 
> the already rewritten fucntion?  Is that a problem?

It won't cause a problem; the 'debug' package notices when a function
has already been 'mtrace'd (by inspecting the actual function body) and
doesn't add another "layer".

> > the point is that you can stick all sorts of code inside a 
> conditional 
> > breakpoint to do other things-- if the expression returns 
> FALSE then 
> > the breakpoint won't be triggered, but the side-effects 
> will still happen.
> > You can also use conditional breakpoints and 'skip' command 
> to patch 
> > code on-the-fly, but I generally find it's too much trouble.
> > 
> > 
> > Note also the trick of
> > D(17)> bp(1,F)
> > 
> > which is useful if 'b1' will be called again within the lifetime of 
> > the current top-level expression and you actually don't 
> want to stop.
> Is bp(1, FALSE) equivalent to mtrace(f, false), if one is 
> currently debugging in f?

Not exactly-- but read on. If 'f' is running, you can't un-'mtrace' it.
However, you can prevent the debugger *stopping* when it next enters 'f'
(the difference then is only one of speed). When you 'mtrace' a
function, a breakpoint is set on line 1, to ensure that the debugger
halts for user input when the function is entered. (You'll see a red
asterisk in the code window at line 1, to show that there's some kind of
breakpoint there.) What 'bp(1,F)' does, is disable that breakpoint, so
that the debugger will keep plowing through the code of 'f' without
halting (until another breakpoint or an error). The code window isn't
displayed until/unless the debugger actually halts.

> > 
> > The point about context is subtle because of R's scoping rules-- 
> > should one look at lexical scope, or at things defined in 
> calling functions?
> > The former happens by default in the 'debug' package (ie if 
> you type 
> > the name of something that can be "seen" from the current function, 
> > then the debugger will find it, even if it's not defined in 
> the current frame).
> > For the latter, though, if you are currently "inside" c1, 
> then one way 
> > to do it is to use 'sys.parent()' or 'sys.parent(2)' or whatever to 
> > figure out the frame number of the "context" you want, then 
> you could 
> > do e.g.
> > D(18)> sp <- sys.frame( sys.parent( 2)) D(18)> evalq( ls(), sp)
> > 
> > etc which is not too bad. It's worth experimenting with 
> sys.call etc 
> > while inside my debugger, too-- I have gone to some lengths 
> to try to 
> > ensure that those functions work the way that might be 
> expected (even 
> > though they actually don't... long story).
> > 
> sys.parent and friends  didn't seem to work for me in vanilla 
> R debugging, so this sounds really useful to me.
> > If you are 'mtrace'ing one of the calling functions as 
> well, then you 
> > can also look at the frame numbers in the code windows to work out 
> > where to 'evalq'.
> I thought the frame numbers shown in the debugger are 
> numbered successively for the call stack, and that these are 
> not necessarily the same as the frame numbers in R.  My 
> understanding is that the latter are not guaranteed to be 
> consecutive (relative to the call stack).  From the 
> description of sys.parent:
>      The parent frame of a function evaluation is the environment in
>      which the function was called.  It is not necessarily 
> numbered one
>      less than the frame number of the current evaluation,

The number shown in the 'debug' package (ie the number xxx in the
"D(xxx)>" prompt, and in the separate code windows) is the true frame
number. So I will quite often have two code windows visible, one saying
"outerfunc(4)" and the other [currently active] saying "innerfunc(17)".
Typing 'sys.nframe' will return 17 and 'sys.parent()' will return 4.

again, not sure about vanilla debug.

> > 
> > The current 'debug' package doesn't include a "watch window" (even 
> > though it's something I rely on heavily in Delphi, my main other
> > language) mainly because R can get stuck figuring out what 
> to display 
> > in that window.
> Just out of curiosity, how does that problem arise?  I'd 
> expect showing the variables in a frame to be straightforward.

I think the main issue is ensuring that the watch expressions don't take
overwhelmingly long to display.... imagine if you have some really huge
object resulting from a watch expression which takes 5 minutes to
print-- how do you get R to display only the first 2 lines "quickly"?
What about things that have weird attributes? What if there is a bug in
the print method for the class of object, or a mismatch between the
object's contents and the expectation of the print method? All sorts of
awkwardness can & does happen... Sod's law at work, basically.  I
haven't looked into how far things like 'str' could make it painless
these days.



Mark Bravington
CSIRO Mathematical & Information Sciences
Marine Laboratory
Castray Esplanade
Hobart 7001

ph (+61) 3 6232 5118
fax (+61) 3 6232 5012
mob (+61) 438 315 623

> > It's not that hard to do (I used ot have one in the Splus 
> version of 
> > my debugger) and I might add one in future if demand is 
> high enough. 
> > It would help if there was some way to "time-out" a
> > calculation-- e.g. a 'time.try' function a la
> > 
> >   result <- time.try( { do.some.big.calculation}, 0.05)
> > 
> > which would return an object of class "too-slow" if the calculation 
> > takes more than 0.05s.
> > 
> > I'm certainly willing to consider adding other features to 
> the 'debug'
> > package if they are easy enough and demand is high enough! 
> [And if I 
> > have time, which I mostly don't :( ]
> > 
> > Hope this is of some use
> > 
> > Mark
> Thanks for the info.  It sounds as if the better handling of 
> sys.* in the debug package may get me enough ability to look 
> up the stack to satisfy me.  Or I may discover that the ESS 
> .Last.value problem is behind my inability to use sys.* in 
> the regular browser.
> P.S. Earlier in this thread I mentioned that viewing even 
> basic objects like lists can be awkward in regular debuggers 
> (I was thinking mostly of
> C++).  As a current example, there is thread that begins at
> http://lists.kde.org/?t=116777858300001&r=1&w=2&n=13 on the 
> trials of viewing a single string (this is for KDE, which has 
> a C++ core).

More information about the R-devel mailing list