Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions inst/NEWS.Rd
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@
with current R versions (Dirk in \ghpr{1469} fixing \ghit{1468})
\item The \code{Nullable::as()} exporter now uses an explicit cast to
the templated type (Dirk in \ghpr{1471} fixing \ghit{1470})
\item A memory leak in the variadic \code{Rcpp::warning()} template
has been fixed by copying the formatted message into a stack buffer
before \code{Rf_warning()} is invoked, so the \code{std::string}
destructor runs even when an R warning handler triggers a
\code{longjmp} (Kevin fixing \ghit{1474})
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We try to keep these to two lines per item, rarely three. Five is a little heavy. Fully context is at the issue and pr (we generally also reference) so brevity is possible.

}
\item Changes in Rcpp Documentation:
\itemize{
Expand Down
13 changes: 12 additions & 1 deletion inst/include/Rcpp/exceptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#define Rcpp__exceptions__h

#include <Rversion.h>
#include <cstdio>

#ifndef RCPP_DEFAULT_INCLUDE_CALL
#define RCPP_DEFAULT_INCLUDE_CALL true
Expand Down Expand Up @@ -186,7 +187,17 @@ struct LongjumpException {

template <typename... Args>
inline void warning(const char* fmt, Args&&... args ) {
Rf_warning("%s", tfm::format(fmt, std::forward<Args>(args)... ).c_str());
// Rf_warning() may longjmp out of this frame (e.g. when the caller
// installs a warning handler via tryCatch(warning=...)). A longjmp
// skips C++ destructors, so the std::string returned by tfm::format()
// would leak its heap buffer. Copy into a stack buffer and let the
// std::string be destroyed before Rf_warning() is invoked.
char buf[8192];
{
const std::string msg = tfm::format(fmt, std::forward<Args>(args)...);
std::snprintf(buf, sizeof(buf), "%s", msg.c_str());
}
Rf_warning("%s", buf);
} // #nocov end

template <typename... Args>
Expand Down
10 changes: 10 additions & 0 deletions inst/tinytest/test_dataframe.R
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,13 @@ expect_warning( DataFrame_PushZeroLength())
## issue #1232: push on empty data.frame
df <- DataFrame_PushOnEmpty()
expect_equal(ncol(df), 3L)

## issue #1474: warning thrown from DataFrame::push_back used to leak the
## formatted std::string when a tryCatch handler longjmp-ed past its dtor.
## Loop a few iterations so valgrind makes any regression obvious.
got_warning <- FALSE
for (i in 1:5) {
tryCatch(DataFrame_PushWrongSize(),
warning = function(w) { got_warning <<- TRUE })
}
expect_true(got_warning)
Loading