Debug: Difference between revisions

From 太極
Jump to navigation Jump to search
Line 158: Line 158:
   rlang_backtrace_on_error = "branch")
   rlang_backtrace_on_error = "branch")
</pre>
</pre>
=== icecream package ===
[https://www.r-bloggers.com/2023/11/print-debugging-now-with-icecream/ Print Debugging (Now with Icecream!)]


=== An Example: classpredict ===
=== An Example: classpredict ===

Revision as of 16:07, 7 November 2023

R

How to debug an R code

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

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

  • 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")

icecream package

Print Debugging (Now with Icecream!)

An Example: classpredict

Temporarily add browser() to a function. R debugging tutorial. trace(pkg:::foo, edit = TRUE) & untrace(). Suppose we run classpredict::classPredict() and got an error. 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.

  1. Use traceback() to find out the function the error came from (classpredict:::GetPredictionResults() in this case.
  2. Insert browser() and find out the problem
    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
    
  3. untrace(classpredict:::GetPredictionResults)

A quicker method that seems to combine the power of traceback() & browser() is options(error=recover) which will let users to choose which (problematic) function to enter (not the problematic line of code). It does not enter the 1st line of the function so we cannot use the key n to debug line by line. See a screencast.

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.