views:

89

answers:

3

Hi all,

When debugging a function I usually use

library(debug)
mtrace(FunctionName)
FunctionName(...)

And that works quite well for me.

However, sometimes I am trying to debug a complex function that I don't know. In which case, I can find that inside that function there is another function that I would like to "go into" ("debug") - so to better understand how the entire process works.

So one way of doing it would be to do:

library(debug)
mtrace(FunctionName)
FunctionName(...)
# when finding a function I want to debug inside the function, run again:
mtrace(FunctionName.SubFunction)

The question is - is there a better/smarter way to do interactive debugging (as I have described) that I might be missing?

p.s: I am aware that there where various questions asked on the subject on SO (see here). Yet I wasn't able to come across a similar question/solution to what I asked here.

+4  A: 

Not entirely sure about the use case, but when you encounter a problem, you can call the function traceback(). That will show the path of your function call through the stack until it hit its problem. You could, if you were inclined to work your way down from the top, call debug on each of the functions given in the list before making your function call. Then you would be walking through the entire process from the beginning.

Here's an example of how you could do this in a more systematic way, by creating a function to step through it:

walk.through <- function() {
  tb <- unlist(.Traceback)
  if(is.null(tb)) stop("no traceback to use for debugging")
  assign("debug.fun.list", matrix(unlist(strsplit(tb, "\\(")), nrow=2)[1,], envir=.GlobalEnv)
  lapply(debug.fun.list, function(x) debug(get(x)))
  print(paste("Now debugging functions:", paste(debug.fun.list, collapse=",")))
}

unwalk.through <- function() {
  lapply(debug.fun.list, function(x) undebug(get(as.character(x))))
  print(paste("Now undebugging functions:", paste(debug.fun.list, collapse=",")))
  rm(list="debug.fun.list", envir=.GlobalEnv)
}

Here's a dummy example of using it:

foo <- function(x) { print(1); bar(2) }
bar <- function(x) { x + a.variable.which.does.not.exist }
foo(2)

# now step through the functions
walk.through() 
foo(2)

# undebug those functions again...
unwalk.through()
foo(2)

IMO, that doesn't seem like the most sensible thing to do. It makes more sense to simply go into the function where the problem occurs (i.e. at the lowest level) and work your way backwards.

I've already outlined the logic behind this basic routine in "favorite debugging trick".

Shane
Thanks Shane,I could use your code with mtrace, which might be nice at some cases. But in general I take your point regarding the bottom up debugging.
Tal Galili
Hi Shane, on second thought. Can we extract the list of functions from traceback so that we could run your function only on them ?
Tal Galili
That's exactly what my walk.through function does.
Shane
-1 for me reading your reply first thing after getting up and not reading it through :(Great answer and code - I can imagine cases I will find uses for it! Thanks!
Tal Galili
+4  A: 

I like options(error=recover) as detailed previously on SO. Things then stop at the point of error and one can inspect.

Dirk Eddelbuettel
Thank you Dirk. My question stemmed from cases where the bug in my code came from previous steps with weird end cases. I agree that the first thing should be your option. And if we are on the subject, do you think it could be possible to ask the function to recover using something similar to mtrace (from {debug}) instead of the base browser() ?
Tal Galili
Have you tried `options(error=mtrace)`?
Shane
Now I did, it errors with: [1] 1Error in bar(2) : object 'a.variable.which.does.not.exist' not foundError during wrapup: invalid first argument
Tal Galili
I don't know how mtrace works, but you can create your own error condition to use it properly: `options(error=function(x) mtrace(x, ...))`.
Shane
@Dirk: While I agree with that approach generally, I don't think that it really addresses the question: he wants to walk through functions to see what happens before the error occurs...
Shane
Hi Shane, I tried again, here is the error: > options(error=function(x) mtrace(x))> foo(1)[1] 1Error in bar(2) : object 'a.variable.which.does.not.exist' not foundError during wrapup: Can't find x
Tal Galili
@Tal: I don't know how to work with mtrace: you will have to figure that out yourself. But you can create your own custom error functions is my general point.
Shane
I will play with it, thank both of you for the help.
Tal Galili
+1  A: 

(I'm the author of the 'debug' package where 'mtrace' lives)

If the definition of 'SubFunction' lives outside 'MyFunction', then you can just mtrace 'SubFunction' and don't need to mtrace 'MyFunction'. And functions run faster if they're not 'mtrace'd, so it's good to mtrace only as little as you need to. (But you probably know those things already!)

If 'MyFunction' is only defined inside 'SubFunction', one trick that might help is to use a conditional breakpoint in 'MyFunction'. You'll need to 'mtrace( MyFunction)', then run it, and when the debugging window appears, find out what line 'MyFunction' is defined in. Say it's line 17. Then the following should work:

D(n)> bp( 1, F) # don't bother showing the window for MyFunction again D(n)> bp( 18, { mtrace( SubFunction); FALSE}) D(n)> go()

It should be clear what this does (or it will be if you try it).

The only downsides are: the need to do it again whenever you change the code of 'MyFunction', and; the slowing-down that might occur through 'MyFunction' itself being mtraced.

You could also experiment with adding a 'debug.sub' argument to 'MyFunction', that defaults to FALSE. In the code of 'MyFunction', then add this line immediately after the definition of 'SubFunction':

if( debug.sub) mtrace( SubFunction)

That avoids any need to mtrace 'MyFunction' itself, but does require you to be able to change its code.

Mark Bravington
Hello Mark, I am very glad you joined the discussing. I strongly support your package, and enjoy using it (almost on a daily basis). The tips you gave are interesting. From the discussion above there is one ability that I am trying to figure out how to do with mtrace - and that is, automatic "mtrace"-ing of ALL (or of selected) functions that are involved in a bug. The function that Shane gave in his answer well demonstrates it. The need comes when the bug originates around the breaking point. Thanks again for all your code and time!
Tal Galili