Debug
R
How to debug an R code
- How to Easily and Efficiently Conquer Errors in Your Code
- Chapter 11 Debugging R code from the book What They Forgot to Teach You About R
- Debugging in R – How to Easily Overcome Errors in Your Code?. Examples are provided.
- R debugging tutorial (youtube) and accompanying document in github. options(error = recover) is useful! We can return to default error handling with options(error = NULL).
- Note that options(error = recover) only works in an interactive session.
- In an non-interactive mode, see traceback() for interactive and non-interactive R sessions
options(error=function() { traceback(2); if(!interactive()) quit("no", status = 1, runLast = FALSE) })
- browser() and other resources
- Use breakpoints in an IDE.
- RStudio's "Rerun with Debug" or "Show Traceback" features. See Debugging with the RStudio IDE by posit.co.
- debug(), traceback(), browser()
debug(), debugonce() and isdebugged() functions
trace() and browser(), findLineNum() and setBreakpoint()
- browser(text = "", condition = NULL, expr = TRUE, skipCalls = 0L)
# It still runs like for-loop lapply(1:3, function(i){ browser(); rnorm(i)})
- findLineNum() and setBreakpoint() if we use source() to get the functions. Or eval(parse(text=x)).
- as.list(body(myFun)).
- 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.
- Use traceback() to find out the function the error came from (classpredict:::GetPredictionResults() in this case.
- 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
- 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()
- https://stackoverflow.com/questions/1395622/debugging-lapply-sapply-calls
- https://stat.ethz.ch/R-manual/R-devel/library/utils/html/recover.html. Use options(error=NULL) to turn it off.
Debugging with RStudio
- https://www.rstudio.com/resources/videos/debugging-techniques-in-rstudio/
- https://github.com/ajmcoqui/debuggingRStudio/blob/master/RStudio_Debugging_Cheatsheet.pdf
- https://support.rstudio.com/hc/en-us/articles/205612627-Debugging-with-RStudio
Debug R source code
Build R with debug information
- R -> Build R from its source on Windows
- http://www.stats.uwo.ca/faculty/murdoch/software/debuggingR/ (defunct)
- http://www.stats.uwo.ca/faculty/murdoch/software/debuggingR/gdb.shtml (defunct)
- Build R with debug information (see the discussion here). Cf output messages from running ./configure and make using the default options.
$ ./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
- My note of debugging cor() function
- Using gdb to debug R packages with native code (Video) The steps to debug is given below.
# 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
- Compiled code from "R packages" by Hadley Wickham
- Debugging C/C++ code from Bioconductor (case study)
- Same idea for the Rcpp situation. See What are productive ways to debug Rcpp compiled code loaded in R (on OS X Mavericks)?
.Call
- Writing R Extensions manual.
- R’s C interface from Advanced R by Hadley Wickham
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.