Debug: Difference between revisions

From 太極
Jump to navigation Jump to search
 
(10 intermediate revisions by the same user not shown)
Line 5: Line 5:
* [https://techvidvan.com/tutorials/r-debug/ Debugging in R – How to Easily Overcome Errors in Your Code?]. Examples are provided.
* [https://techvidvan.com/tutorials/r-debug/ Debugging in R – How to Easily Overcome Errors in Your Code?]. Examples are provided.
<ul>
<ul>
<li>[https://youtu.be/-yy_3htRHdU R debugging tutorial] (youtube) and [https://github.com/berkeley-scf/tutorial-R-debugging accompanying document] in github. '''options(error = recover) ''' is useful! Before we use that, we can do '''backup_options <- options()''' and then do '''options(backup_options)''' at the end.  
<li>[https://youtu.be/-yy_3htRHdU R debugging tutorial] (youtube) and [https://github.com/berkeley-scf/tutorial-R-debugging accompanying document] in github. <span style="color: red">options(error = recover)</span> is useful! We can return to default error handling with '''options(error = NULL)'''.  
<ul>
<ul>
<li>Note that ''options(error = recover) only works in an interactive session.''  </li>
<li>Note that ''options(error = recover) only works in an interactive session.''  </li>
Line 17: Line 17:
</ul>
</ul>
* [https://masalmon.eu/2021/07/13/code-detective/ browser() and other resources]
* [https://masalmon.eu/2021/07/13/code-detective/ browser() and other resources]
* Use breakpoints in an IDE.
* RStudio's "Rerun with Debug" or "Show Traceback" features. See [https://support.posit.co/hc/en-us/articles/205612627-Debugging-with-the-RStudio-IDE Debugging with the RStudio IDE] by posit.co.
* debug(), traceback(), browser()


=== debug(), debugonce() and isdebugged() functions ===
=== debug(), debugonce() and isdebugged() functions ===
Line 33: Line 36:
* as.list(body(myFun)).  
* as.list(body(myFun)).  
<ul>
<ul>
<li>[https://techvidvan.com/tutorials/r-debug/ 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.
<pre>
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)
</pre>
</li>
<li>
[https://rstats.wtf/debugging-r-code.html#trace Chapter 11 Debugging R code > trace()]. It is not necessary to use the '''at''' option.
</li>
<li>[http://adv-r.had.co.nz/Exceptions-Debugging.html 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].
<li>[http://adv-r.had.co.nz/Exceptions-Debugging.html 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].


Line 128: Line 161:
   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 ===
Temporarily add browser() to a function. [https://youtu.be/-yy_3htRHdU?t=654 R debugging tutorial]. <span style="color: red">trace(pkg:::foo, edit = TRUE)</span> & <span style="color: red">untrace()</span>. 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.'' '''
<ol>
<li>Use '''traceback()''' to find out the function the error came from (classpredict:::GetPredictionResults() in this case. </li>
<li>Insert '''browser()''' and find out the problem
<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
</pre>
</li>
<li>'''untrace(classpredict:::GetPredictionResults)''' </li>
</ul>
</ol>
A quicker method that seems to combine the power of ''traceback()'' & ''browser()'' is <span style="color: red">options(error=recover)</span> 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 [https://1drv.ms/v/s!Am92ICmSSQXukSCNvDzPFbIhB2r1?e=aYPmbd screencast].


== Debug lapply()/sapply() ==
== Debug lapply()/sapply() ==

Latest revision as of 12:48, 19 April 2024

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.