Debug: Difference between revisions

From 太極
Jump to navigation Jump to search
Line 9: Line 9:
=== trace() and browser(), findLineNum() and setBreakpoint() ===
=== trace() and browser(), findLineNum() and setBreakpoint() ===
* [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)]
* [https://www.rdocumentation.org/packages/base/versions/3.6.2/topics/browser browser(text = "", condition = NULL, expr = TRUE, skipCalls = 0L)]
<ul>
<li>[https://www.rdocumentation.org/packages/base/versions/3.6.2/topics/browser browser(text = "", condition = NULL, expr = TRUE, skipCalls = 0L)]
<pre>
# It still runs like for-loop
lapply(1:3, function(i){ browser(); rnorm(i)})
</pre>
</li>
</ul>
* [https://www.rdocumentation.org/packages/utils/versions/3.6.2/topics/findLineNum findLineNum() and setBreakpoint()] if we use '''source()''' to get the functions. Or [https://zhanxw.com/blog/2011/05/r代码除错-how-to-debug-r-code/ eval(parse(text=x))].
* as.list(body(myFun)).  
* as.list(body(myFun)).  
* [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].
<ul>
* [https://www.rdocumentation.org/packages/utils/versions/3.6.2/topics/findLineNum findLineNum() and setBreakpoint()] if we use '''source()''' to get the functions. Or [https://zhanxw.com/blog/2011/05/r代码除错-how-to-debug-r-code/ eval(parse(text=x))].
<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].


More complicated than '''debug()'''. To insert a browser() function, we don't need to modify the original function.
More complicated than '''debug()'''. To insert a browser() function, we don't need to modify the original function.
Line 85: Line 93:
untrace(c("f", "as.matrix"))
untrace(c("f", "as.matrix"))
</pre>
</pre>
</li>
</ul>


=== How to quit debugging ===
=== How to quit debugging ===

Revision as of 15:49, 18 September 2020

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.