Skip to content

jawond/winch

 
 

Repository files navigation

winch

Lifecycle: experimental R build status CRAN status

The goal of winch is to provide stack traces that combine R and C function calls. This is primarily useful for developers of R packages where a substantial portion of the code is C or C++.

Installation

Once on CRAN, you can install the released version of winch from CRAN with:

install.packages("winch")

Install the development version from GitHub with:

devtools::install_github("r-lib/winch")

Example

This is an example where an R function calls into C which calls back into R, see the second-to-last entry in the trace:

library(winch)

foo <- function() {
  winch_call(bar)
}

bar <- function() {
  winch_trace_back()
}

foo()
#>                  func               ip
#> 1  Rf_NewFrameConfirm 00007efcabb59480
#> 2             Rf_eval 00007efcabba34f0
#> 3        R_execMethod 00007efcabba8550
#> 4             Rf_eval 00007efcabba34f0
#> 5        R_execMethod 00007efcabba6e90
#> 6             Rf_eval 00007efcabba34f0
#> 7             Rf_eval 00007efcabba5170
#> 8     Rf_applyClosure 00007efcabba6090
#> 9             Rf_eval 00007efcabba34f0
#> 10       R_execMethod 00007efcabba6e90
#> 11            Rf_eval 00007efcabba34f0
#> 12            Rf_eval 00007efcabba5170
#> 13    Rf_applyClosure 00007efcabba6090
#> 14            Rf_eval 00007efcabba34f0
#> 15         winch_call 00007efc98a2c240
#> 16 Rf_NewFrameConfirm 00007efcabb578a0
#> 17 Rf_NewFrameConfirm 00007efcabb59480
#> 18            Rf_eval 00007efcabba34f0
#> 19       R_execMethod 00007efcabba6e90
#> 20            Rf_eval 00007efcabba34f0
#> 21            Rf_eval 00007efcabba5170
#> 22    Rf_applyClosure 00007efcabba6090
#> 23            Rf_eval 00007efcabba34f0
#> 24       R_execMethod 00007efcabba6e90
#> 25            Rf_eval 00007efcabba34f0
#> 26            Rf_eval 00007efcabba5170
#> 27    Rf_applyClosure 00007efcabba6090
#> 28            Rf_eval 00007efcabba34f0
#> 29     R_forceAndCall 00007efcabba93a0
#> 30           do_Rprof 00007efcabb8db80
#> 31            Rf_eval 00007efcabba34f0
#> 32            Rf_eval 00007efcabba5170
#> 33    Rf_applyClosure 00007efcabba6090
#>                                        pathname
#> 1                        /usr/lib/R/lib/libR.so
#> 2                        /usr/lib/R/lib/libR.so
#> 3                        /usr/lib/R/lib/libR.so
#> 4                        /usr/lib/R/lib/libR.so
#> 5                        /usr/lib/R/lib/libR.so
#> 6                        /usr/lib/R/lib/libR.so
#> 7                        /usr/lib/R/lib/libR.so
#> 8                        /usr/lib/R/lib/libR.so
#> 9                        /usr/lib/R/lib/libR.so
#> 10                       /usr/lib/R/lib/libR.so
#> 11                       /usr/lib/R/lib/libR.so
#> 12                       /usr/lib/R/lib/libR.so
#> 13                       /usr/lib/R/lib/libR.so
#> 14                       /usr/lib/R/lib/libR.so
#> 15 /home/kirill/git/R/r-prof/winch/src/winch.so
#> 16                       /usr/lib/R/lib/libR.so
#> 17                       /usr/lib/R/lib/libR.so
#> 18                       /usr/lib/R/lib/libR.so
#> 19                       /usr/lib/R/lib/libR.so
#> 20                       /usr/lib/R/lib/libR.so
#> 21                       /usr/lib/R/lib/libR.so
#> 22                       /usr/lib/R/lib/libR.so
#> 23                       /usr/lib/R/lib/libR.so
#> 24                       /usr/lib/R/lib/libR.so
#> 25                       /usr/lib/R/lib/libR.so
#> 26                       /usr/lib/R/lib/libR.so
#> 27                       /usr/lib/R/lib/libR.so
#> 28                       /usr/lib/R/lib/libR.so
#> 29                       /usr/lib/R/lib/libR.so
#> 30                       /usr/lib/R/lib/libR.so
#> 31                       /usr/lib/R/lib/libR.so
#> 32                       /usr/lib/R/lib/libR.so
#> 33                       /usr/lib/R/lib/libR.so
#>  [ reached 'max' / getOption("max.print") -- omitted 99 rows ]

rlang::entrace() checks if winch is installed, and adds a native backtrace. This cannot be easily demonstrated in a knitr document, the output is copied from this GitHub Actions run.

options(
  error = rlang::entrace,
  rlang_backtrace_on_error = "full",
  rlang_trace_use_winch = 1L
)

vctrs::vec_as_location(quote, 2)
Error: Must subset elements with a valid subscript vector.
✖ Subscript has the wrong type `function`.
ℹ It must be logical, numeric, or character.
Backtrace:
    █
 1. └─vctrs::vec_as_location(quote, 2)
 2.   └─`/vctrs.so`::vctrs_as_location()
 3.     └─`/vctrs.so`::vec_as_location_opts()

How does it work?

It’s a very crude heuristic. R’s traceback (and also profiling) infrastructure introduces the notion of a “context”. Every call to an R function opens a new context, and closes it when execution of the function ends. Unfortunately, no new context is established for native code called with .Call() or .External(). Establishing contexts requires precious run time, this might be the reason for this omission.

To work around this limitation, the source code of all R functions along the call chain is scanned for instances of .Call and .External. The native call stack (obtained via libunwind or libbacktrace) is scanned for chunks of code outside of libR.so (R’s main library) – these are assumed to correspond to .Call() or .External(). The native traces are embedded as artificial calls into the R stack trace.

Limitations

  • The matching will not be perfect, it still may lead to quicker discovery of the cause of an error.
  • Windows only works on x64, and there the traces can be obtained only for one shared library at a time. See winch_init_library() for details.

Code of Conduct

Please note that the winch project is released with a Contributor Code of Conduct. By contributing to this project, you agree to abide by its terms.

About

High-level combined tracebacks

Resources

Code of conduct

Stars

Watchers

Forks

Packages

No packages published

Languages

  • R 52.1%
  • C 32.8%
  • Shell 15.1%