Debug: Difference between revisions

From 太極
Jump to navigation Jump to search
Line 21: Line 21:


=== trace() and browser(), findLineNum() and setBreakpoint() ===
=== trace() and browser(), findLineNum() and setBreakpoint() ===
<ul>
<li>Temporarily add browser() to a function. [https://youtu.be/-yy_3htRHdU?t=654 R debugging tutorial]. trace(foo, edit = TRUE). Suppose we run classpredict::classPredict() and got an error where the '''traceback()''' or '''options(error = recover)''' shows the error came from classpredict:::GetPredictionResults(). We can do the following. ''' ''This is useful if the bug happened in an un-exported function from some package, not just in our R code.'' '''
<pre>
undebug(classPredict) # not necessary now
trace(classpredict:::GetPredictionResults, edit = TRUE)
# add browser() to a place we suspect the error came from
# click 'Save' to exit
classPredict(...)
# hopefully we found the reason
untrace(classpredict:::GetPredictionResults)
</pre>
</li>
</ul>
* [https://www.rdocumentation.org/packages/base/versions/3.6.2/topics/trace trace(what, tracer, exit, at, print, signature, where = topenv(parent.frame()), edit = FALSE)]
* [https://www.rdocumentation.org/packages/base/versions/3.6.2/topics/trace trace(what, tracer, exit, at, print, signature, where = topenv(parent.frame()), edit = FALSE)]
<ul>
<ul>

Revision as of 15:54, 9 March 2022

R

How to debug an R code

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

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

  • Temporarily add browser() to a function. R debugging tutorial. trace(foo, edit = TRUE). Suppose we run classpredict::classPredict() and got an error where the traceback() or options(error = recover) shows the error came from classpredict:::GetPredictionResults(). We can do the following. This is useful if the bug happened in an un-exported function from some package, not just in our R code.
    undebug(classPredict) # not necessary now
    
    trace(classpredict:::GetPredictionResults, edit = TRUE)
    # add browser() to a place we suspect the error came from
    # click 'Save' to exit
    
    classPredict(...)
    # hopefully we found the reason
    
    untrace(classpredict:::GetPredictionResults)
    
  • Debugging in R – How to Easily Overcome Errors in Your Code? See the trace() example there. It seems the function definition line (e.g. function(x)) means at=1.
    set.seed(5656)
    trace_test <- function(x){
      r <- log(x)
      if(r>=10){
        return(r)
      } else{
        return(2*r)
      }
    }
    val <- rnorm(50)
    trace_test(val)
    
    trace(trace_test,quote( if(any(is.nan(r))){browser()} ), at=3, print=F)
    trace_test(val)
    
    # Note that the following 'at' option will result in an error
    trace(trace_test,quote( if(any(is.nan(r))){browser()} ), at=2)
    trace_test(val)
    # Error in eval(expr, p) : object 'r' not found
    
    # fix of the above trace() example
    trace(trace_test,quote( if(any(x < 0)){browser()} ), at=2)
    trace_test(val)
    
  • Chapter 11 Debugging R code > trace(). It is not necessary to use the at option.
  • 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.