Skip to content

Make Rcpp::exception thread-safe #1043

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 12, 2020
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
7 changes: 7 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
2020-02-09 Joshua Pritikin <jpritikin@pobox.com>

* inst/include/Rcpp.h: Include exceptions_impl.h
* inst/include/Rcpp/exceptions.h: Make thread-safe
* inst/include/Rcpp/exceptions_impl.h: New home for code moved
from src/api.cpp

2020-01-05 Dirk Eddelbuettel <edd@debian.org>

* inst/include/Rcpp/exceptions.h: A few more #nocov tags
Expand Down
5 changes: 4 additions & 1 deletion inst/NEWS.Rd
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
\item Finalizer calls clear external pointer first (Kirill Müller and
Dirk in \ghpr{1038}).
\item Scalar operations with a rhs matrix no longer change the
matrix value (Qiang in \ghpr{1040} fixing (again) \ghit{365}).
matrix value (Qiang in \ghpr{1040} fixing (again) \ghit{365}).
\item \code{Rcpp::exception} and \code{Rcpp::stop} are now
thread-safe. Be cautioned that most of Rcpp continues to be not
thread-safe.
}
\item Changes in Rcpp Attributes:
\itemize{
Expand Down
2 changes: 2 additions & 0 deletions inst/include/Rcpp.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
#include <Rcpp/Formula.h>
#include <Rcpp/DataFrame.h>

#include <Rcpp/exceptions_impl.h>

#if !defined(RCPP_FORCE_OLD_DATE_DATETIME_VECTORS)
#define RCPP_NEW_DATE_DATETIME_VECTORS 1
#endif
Expand Down
17 changes: 11 additions & 6 deletions inst/include/Rcpp/exceptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,22 @@
#define RCPP_DEFAULT_INCLUDE_CALL true
#endif

#define GET_STACKTRACE() stack_trace( __FILE__, __LINE__ )
#define GET_STACKTRACE() R_NilValue

namespace Rcpp {

// Throwing an exception must be thread-safe to avoid surprises w/ OpenMP.
class exception : public std::exception {
public:
explicit exception(const char* message_, bool include_call = RCPP_DEFAULT_INCLUDE_CALL) : // #nocov start
message(message_),
include_call_(include_call){
rcpp_set_stack_trace(Shield<SEXP>(stack_trace()));
include_call_(include_call) {
record_stack_trace();
}
exception(const char* message_, const char*, int, bool include_call = RCPP_DEFAULT_INCLUDE_CALL) :
message(message_),
include_call_(include_call){
rcpp_set_stack_trace(Shield<SEXP>(stack_trace()));
include_call_(include_call) {
record_stack_trace();
}
bool include_call() const {
return include_call_;
Expand All @@ -50,9 +51,12 @@ namespace Rcpp {
virtual const char* what() const throw() {
return message.c_str(); // #nocov end
}
inline void copy_stack_trace_to_r() const;
private:
std::string message;
bool include_call_;
std::vector<std::string> stack;
inline void record_stack_trace();
};

// simple helper
Expand Down Expand Up @@ -340,7 +344,8 @@ inline SEXP exception_to_condition_template( const Exception& ex, bool include_c
}

inline SEXP rcpp_exception_to_r_condition(const Rcpp::exception& ex) {
return exception_to_condition_template(ex, ex.include_call());
ex.copy_stack_trace_to_r();
return exception_to_condition_template(ex, ex.include_call());
}

inline SEXP exception_to_r_condition( const std::exception& ex){
Expand Down
88 changes: 88 additions & 0 deletions inst/include/Rcpp/exceptions_impl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// exceptions_impl.h: Rcpp R/C++ interface class library -- exceptions
//
// Copyright (C) 2020 Dirk Eddelbuettel, Romain Francois, and Joshua N. Pritikin
//
// This file is part of Rcpp.
//
// Rcpp is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// Rcpp is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Rcpp. If not, see <http://www.gnu.org/licenses/>.

namespace Rcpp {
#if defined(__GNUC__) || defined(__clang__)
#if defined(_WIN32) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__CYGWIN__) || defined(__sun) || defined(_AIX) || defined(__MUSL__) || defined(__HAIKU__) || defined(__ANDROID__)
// do nothing
#else
#include <execinfo.h>

// Extract mangled name e.g. ./test(baz+0x14)[0x400962]
static std::string demangler_one(const char* input) {
static std::string buffer;
buffer = input;
size_t last_open = buffer.find_last_of('(');
size_t last_close = buffer.find_last_of(')');
if (last_open == std::string::npos ||
last_close == std::string::npos) {
return input; // #nocov
}
std::string function_name = buffer.substr(last_open + 1, last_close - last_open - 1);
// Strip the +0x14 (if it exists, which it does not in earlier versions of gcc)
size_t function_plus = function_name.find_last_of('+');
if (function_plus != std::string::npos) {
function_name.resize(function_plus);
}
buffer.replace(last_open + 1, function_name.size(), demangle(function_name));
return buffer;
}
#endif
#endif

// thread-safe; invoked prior to throwing the exception
inline void exception::record_stack_trace()
{
#if defined(__GNUC__) || defined(__clang__)
#if defined(_WIN32) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__CYGWIN__) || defined(__sun) || defined(_AIX) || defined(__MUSL__) || defined(__HAIKU__) || defined(__ANDROID__)
// C++ stack not available on this system
#else // ! (defined(_WIN32) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__CYGWIN__) || defined(__sun) || defined(_AIX) || defined(__ANDROID__)

/* inspired from http://tombarta.wordpress.com/2008/08/01/c-stack-traces-with-gcc/ */
const size_t max_depth = 100;
int stack_depth;
void *stack_addrs[max_depth];

stack_depth = backtrace(stack_addrs, max_depth);
char **stack_strings = backtrace_symbols(stack_addrs, stack_depth);

std::transform(stack_strings + 1, stack_strings + stack_depth,
std::back_inserter(stack), demangler_one);
free(stack_strings); // malloc()ed by backtrace_symbols
#endif
#endif
}

// not thread-safe; invoked after catching the exception
inline void exception::copy_stack_trace_to_r() const
{
if (!stack.size()) {
rcpp_set_stack_trace(R_NilValue);
return;
}

CharacterVector res(stack.size());
std::copy(stack.begin(), stack.end(), res.begin());
List trace = List::create(_["file" ] = "",
_["line" ] = -1,
_["stack"] = res);
trace.attr("class") = "Rcpp_stack_trace";
rcpp_set_stack_trace(trace);
}
};
65 changes: 2 additions & 63 deletions src/api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,35 +32,6 @@ using namespace Rcpp;
#include <cxxabi.h>
#endif

#if defined(__GNUC__) || defined(__clang__)
#if defined(_WIN32) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__CYGWIN__) || defined(__sun) || defined(_AIX) || defined(__MUSL__) || defined(__HAIKU__) || defined(__ANDROID__)
// do nothing
#else
#include <execinfo.h>

// Extract mangled name e.g. ./test(baz+0x14)[0x400962]
static std::string demangler_one(const char* input) {
static std::string buffer;
buffer = input;
size_t last_open = buffer.find_last_of('(');
size_t last_close = buffer.find_last_of(')');
if (last_open == std::string::npos ||
last_close == std::string::npos) {
return input; // #nocov
}
std::string function_name = buffer.substr(last_open + 1, last_close - last_open - 1);
// Strip the +0x14 (if it exists, which it does not in earlier versions of gcc)
size_t function_plus = function_name.find_last_of('+');
if (function_plus != std::string::npos) {
function_name.resize(function_plus);
}
buffer.replace(last_open + 1, function_name.size(), demangle(function_name));
return buffer;
}
#endif
#endif


namespace Rcpp {

namespace internal {
Expand Down Expand Up @@ -282,42 +253,10 @@ SEXP rcpp_can_use_cxx11() { // #nocov start

// [[Rcpp::register]]
SEXP stack_trace(const char* file, int line) {
#if defined(__GNUC__) || defined(__clang__)
#if defined(_WIN32) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__CYGWIN__) || defined(__sun) || defined(_AIX) || defined(__MUSL__) || defined(__HAIKU__) || defined(__ANDROID__)
// Simpler version for Windows and *BSD
List trace = List::create(_["file"] = file,
_[ "line" ] = line,
_[ "stack" ] = "C++ stack not available on this system");
trace.attr("class") = "Rcpp_stack_trace";
return trace;
#else // ! (defined(_WIN32) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__CYGWIN__) || defined(__sun) || defined(_AIX) || defined(__ANDROID__)

/* inspired from http://tombarta.wordpress.com/2008/08/01/c-stack-traces-with-gcc/ */
const size_t max_depth = 100;
int stack_depth;
void *stack_addrs[max_depth];
char **stack_strings;

stack_depth = backtrace(stack_addrs, max_depth);
stack_strings = backtrace_symbols(stack_addrs, stack_depth);

std::string current_line;

CharacterVector res(stack_depth - 1);
std::transform(stack_strings + 1, stack_strings + stack_depth, res.begin(), demangler_one);
free(stack_strings); // malloc()ed by backtrace_symbols

List trace = List::create(_["file" ] = file,
_["line" ] = line,
_["stack"] = res);
trace.attr("class") = "Rcpp_stack_trace";
return trace;
#endif
#else /* !defined( __GNUC__ ) */
return R_NilValue;
#endif
return R_NilValue;
}


// // [ [ Rcpp::register ] ]
// void print(SEXP s) {
// Rf_PrintValue(s); // defined in Rinternals.h
Expand Down