Winch provides stack traces for call chains that cross between R and C function calls. This is a useful tool for developers of R packages where a substantial portion of the code is C or C++.
Install the released version of winch from CRAN with:
install.packages("winch")
Install the development version from GitHub with:
# install.packages("pak")
# pak::pak("r-prof/winch")
install.packages("winch", repos = c('https://r-prof.r-universe.dev', 'https://cloud.r-project.org'))
Below is an example where an R function calls into C which calls back into R. Note the second-to-last entry in the trace:
library(winch)
foo <- function() {
winch_call(bar)
}
bar <- function() {
winch_trace_back()
}
trace <- foo()
# Keep the render reproducible: the instruction pointers are randomised by
# ASLR, and the shared object is loaded from a temporary directory whose
# name is new on every load -- and the varying path width re-aligns the
# whole table. Show the columns that are stable.
trace$ip <- NULL
trace$pathname <- basename(trace$pathname)
# A frame whose symbol the linker cannot resolve falls back to its address,
# which is randomised too.
trace$func <- sub("^[0-9a-f]{8,}$", "<unresolved>", trace$func)
# Everything below the closure call is the stack of whatever invoked the
# render, so its depth -- and with it the column alignment -- depends on how
# this document was knitted. The frames that show what winch does are the
# first few, and they are the same either way.
head(trace, 12)
#> func pathname is_libr
#> 1 do_dotcall libR.so TRUE
#> 2 Rf_eval libR.so TRUE
#> 3 do_set libR.so TRUE
#> 4 Rf_eval libR.so TRUE
#> 5 do_begin libR.so TRUE
#> 6 Rf_eval libR.so TRUE
#> 7 R_execClosure libR.so TRUE
#> 8 applyClosure_core libR.so TRUE
#> 9 Rf_eval libR.so TRUE
#> 10 do_begin libR.so TRUE
#> 11 Rf_eval libR.so TRUE
#> 12 R_execClosure libR.so TRUE
rlang::entrace() checks if winch is installed, and adds a native backtrace.
As this cannot be easily demonstrated in a knitr document, the output is copied from a GitHub Actions run.
options(
error = rlang::entrace,
rlang_backtrace_on_error = "full",
rlang_trace_use_winch = TRUE
)
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()
winch uses a very simple 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 expends precious run time, so this may be the reason for the 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.
winch_init_library() for details.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.