Debug

From 太極
Jump to navigation Jump to search

R

How to debug an R code

debug(), debugonce() and isdebugged() functions

trace() and browser(), findLineNum() and setBreakpoint()

  • trace() inserts arbitrary code at any position in an existing function. trace() is occasionally useful when you’re debugging code that you don’t have the source for. More complicated than debug(). To insert a browser() function, we don't need to modify the original function. browser() debug statement R
    myFun <- function() {
        x <- 8:1
        y <- 1:8
        plot(y~x)
        lines(y~x)
        text(x,y, letters[1:8], pos=3)
    }
    as.list(body(myFun)) 
    # [[1]]
    # `{`
    # 
    # [[2]]
    # x <- 8:1
    # 
    # [[3]]
    # y <- 1:8
    # 
    # [[4]]
    # plot(y ~ x)
    # 
    # ... More ... 
    trace(myFun, browser, at = 4)
    myFun()   
    untrace(myFun)
    

    There are several examples in ?trace().

    ##  Very simple use. Not useful
    trace(sum)
    hist(rnorm(100)) # shows about 3-4 calls to sum()
    untrace(sum)
    
    ## Show how pt() is called from inside power.t.test():
    trace(pt) ## would show ~20 calls, but we want to see more:
    trace(pt, tracer = quote(cat(sprintf("tracing pt(*, ncp = %.15g)\n", ncp))),
          print = FALSE) # <- not showing typical extra
    power.t.test(20, 1, power=0.8, sd=NULL)  ##--> showing the ncp root finding:
    untrace(pt)
    
    f <- function(x, y) {
        y <- pmax(y, 0.001)
        if (x > 0) x ^ y else stop("x must be positive")
    }
    ## arrange to call the browser on entering and exiting
    ## function f
    trace("f", quote(browser(skipCalls = 4)),
          exit = quote(browser(skipCalls = 4)))
    trace("f", quote(if(any(y < 0)) yOrig <- y),
          exit = quote(if(exists("yOrig")) browser(skipCalls = 4)),
          print = FALSE)
    
    ## Enter the browser just before stop() is called.  First, find
    ## the step numbers
    untrace(f) # (as it has changed f's body !)
    as.list(body(f))
    as.list(body(f)[[3]]) # -> stop(..) is [[4]]
    trace("f", quote(browser(skipCalls = 4)), at = list(c(3,4)))
    f(-1,2)
    
    ## trace a utility function, with recover so we
    ## can browse in the calling functions as well.
    trace("as.matrix", recover)
    
    ## turn off the tracing (that happened above)
    untrace(c("f", "as.matrix"))
    

How to quit debugging

press Q and then ENTER

list or undebug all debugged functions

https://stackoverflow.com/a/12807637

Using assign() in functions

For example, insert the following line to your function

 assign(envir=globalenv(), "GlobalVar", localvar)

options(error)

options(
  error = rlang::entrace, 
  rlang_backtrace_on_error = "branch")

Debug lapply()/sapply()

Debugging with RStudio

Debug R source code

Build R with debug information

$ ./configure --help
$ ./configure --enable-R-shlib --with-valgrind-instrumentation=2 \
                               --with-system-valgrind-headers \
               CFLAGS='-g -O0 -fPIC' \
               FFLAGS='-g -O0 -fPIC' \
               CXXFLAGS='-g -O0 -fPIC' \
               FCFLAGS='-g -O0 -fPIC' 
$ make -j4
$ sudo make install
# Make sure to create a file <src/Makevars> with something like: CFLAGS=-ggdb -O0
# Or more generally
# CFLAGS=-Wall -Wextra -pedantic -O0 -ggdb
# CXXFLAGS=-Wall -Wextra -pedantic -O0 -ggdb
# FFLAGS=-Wall -Wextra -pedantic -O0 -ggdb

$ tree nidemo
$ R CMD INSTALL nidemo
$ cat bug.R
$ R -f bug.R 
$ R -d gdb
(gdb) r
> library(nidemo)
> Ctrl+C
(gdb) b nid_buggy_freq
(gdb) c  # continue
> buggy_freq("nidemo/DESCRIPTION") # stop at breakpoint 1
(gdb) list
(gdb) n # step through
(gdb) # press RETURN a few times until you see the bug
(gdb) d 1 # delete the first break point
(gdb) b Rf_error # R's C entry point for the error function
(gdb) c
> buggy_freq("nidemo/DESCRIPTION")
(gdb) bt 5 # last 5 stack frames
(gdb) frame 2
(gdb) list
(gdb) p freq_data
(gdb) p ans
(gdb) call Rf_PrintValues(ans)
(gdb) call Rf_PrintValues(fname)
(gdb) q
# Edit buggy.c

$ R CMD INSTALL nidemo # re-install the package
$ R -f bug.R
$ R -d gdb
(gdb) run
> source("bug.R") # error happened
(gdb) bt 5 # show the last 5 frames
(gdb) frame 2
(gdb) list
(gdb) frame 1
(gdb) list
(gdb) p file
(gdb) p fh
(gdb) q
# Edit buggy.c

$ R CMD INSTALL nidemo
$ R -f bug.R

.Call

Registering native routines

https://cran.rstudio.com/doc/manuals/r-release/R-exts.html#Registering-native-routines

Pay attention to the prefix argument .fixes (eg .fixes = "C_") in useDynLib() function in the NAMESPACE file.

Example of debugging cor() function

Note that R's cor() function called a C function cor().

stats::cor
....
 .Call(C_cor, x, y, na.method, method == "kendall")

A step-by-step screenshot of debugging using the GNU debugger gdb can be found on my Github repository https://github.com/arraytools/r-debug.