Skip to content

Added support for throwing exceptions without call stacks. #663

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
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
8 changes: 8 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
2017-04-03 Jim Hester <james.f.hester@gmail.com>

* inst/include/Rcpp/exceptions.h: Added support for throwing
exceptions without call stacks.
* inst/include/Rcpp/macros/macros.h: Idem
* inst/unitTests/cpp/exceptions.cpp: Idem
* inst/unitTests/runit.exceptions.R: Idem

2017-03-28 James J Balamuta <balamut2@illinois.edu>

* inst/vignettes/Rcpp-FAQ.Rnw: Added "Known Issues" section to FAQ
Expand Down
2 changes: 2 additions & 0 deletions inst/NEWS.Rd
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
\item Added a Known Issues section to the Rcpp FAQ vignette
(James Balamuta in \ghpr{661} addressing \ghit{628}, \ghit{563},
\ghit{552}, \ghit{460}, \ghit{419}, and \ghit{251}).
\item Rcpp::exceptions can now be constructed without a call stack (Jim
Hester in \ghpr{663}).
}
}
}
Expand Down
47 changes: 40 additions & 7 deletions inst/include/Rcpp/exceptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,25 +28,33 @@ namespace Rcpp {

class exception : public std::exception {
public:
explicit exception(const char* message_) : message(message_) { // #nocov start
explicit exception(const char* message_, bool include_call = true) : // #nocov start
message(message_),
include_call_(include_call){
rcpp_set_stack_trace(stack_trace());
}
exception(const char* message_, const char* file, int line) : message(message_) {
exception(const char* message_, const char* file, int line, bool include_call = true) :
message(message_),
include_call_(include_call){
rcpp_set_stack_trace(stack_trace(file,line));
}
bool include_call() const {
return include_call_;
}
virtual ~exception() throw() {}
virtual const char* what() const throw() {
return message.c_str(); // #nocov end
}
private:
std::string message;
bool include_call_;
};

// simple helper
static std::string toString(const int i) { // #nocov start
std::ostringstream ostr;
ostr << i;
return ostr.str(); // #nocov end
return ostr.str(); // #nocov end
}

class no_such_env : public std::exception {
Expand Down Expand Up @@ -127,7 +135,7 @@ namespace Rcpp {
RCPP_EXCEPTION_CLASS(binding_is_locked, std::string("binding is locked: '") + message + "'" )
RCPP_EXCEPTION_CLASS(no_such_namespace, std::string("no such namespace: '") + message + "'" )
RCPP_EXCEPTION_CLASS(function_not_exported, std::string("function not exported: ") + message)
RCPP_EXCEPTION_CLASS(eval_error, message ) // #nocov end
RCPP_EXCEPTION_CLASS(eval_error, message ) // #nocov end

#undef RCPP_EXCEPTION_CLASS
#undef RCPP_SIMPLE_EXCEPTION_CLASS
Expand Down Expand Up @@ -217,6 +225,24 @@ inline SEXP make_condition(const std::string& ex_msg, SEXP call, SEXP cppstack,
return res ;
}

inline SEXP rcpp_exception_to_r_condition(const Rcpp::exception& ex) {
std::string ex_class = demangle( typeid(ex).name() ) ;
std::string ex_msg = ex.what() ;

SEXP call, cppstack;
if (ex.include_call()) {
call = Rcpp::Shield<SEXP>(get_last_call());
cppstack = Rcpp::Shield<SEXP>( rcpp_get_stack_trace());
} else {
call = R_NilValue;
cppstack = R_NilValue;
}
Rcpp::Shield<SEXP> classes( get_exception_classes(ex_class) );
Rcpp::Shield<SEXP> condition( make_condition( ex_msg, call, cppstack, classes) );
rcpp_set_stack_trace( R_NilValue ) ;
return condition ;
}

inline SEXP exception_to_r_condition( const std::exception& ex){
std::string ex_class = demangle( typeid(ex).name() ) ;
std::string ex_msg = ex.what() ;
Expand Down Expand Up @@ -245,7 +271,7 @@ inline SEXP string_to_try_error( const std::string& str){
Rf_setAttrib( tryError, R_ClassSymbol, Rf_mkString("try-error") ) ;
Rf_setAttrib( tryError, Rf_install( "condition") , simpleError ) ;

return tryError; // #nocov end
return tryError; // #nocov end
}

inline SEXP exception_to_try_error( const std::exception& ex){
Expand Down Expand Up @@ -313,7 +339,7 @@ namespace Rcpp{

inline void NORET stop(const std::string& message) { // #nocov start
throw Rcpp::exception(message.c_str());
} // #nocov end
} // #nocov end

template <typename T1>
inline void NORET stop(const char* fmt, const T1& arg1) {
Expand Down Expand Up @@ -366,7 +392,14 @@ namespace Rcpp{
}
}

inline void forward_exception_to_r( const std::exception& ex){
inline void forward_exception_to_r(const std::exception& ex){
SEXP stop_sym = Rf_install( "stop" ) ;
Rcpp::Shield<SEXP> condition( exception_to_r_condition(ex) );
Rcpp::Shield<SEXP> expr( Rf_lang2( stop_sym , condition ) ) ;
Rf_eval( expr, R_GlobalEnv ) ;
}

inline void forward_rcpp_exception_to_r(const Rcpp::exception& ex) {
SEXP stop_sym = Rf_install( "stop" ) ;
Rcpp::Shield<SEXP> condition( exception_to_r_condition(ex) );
Rcpp::Shield<SEXP> expr( Rf_lang2( stop_sym , condition ) ) ;
Expand Down
7 changes: 6 additions & 1 deletion inst/include/Rcpp/macros/macros.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,15 @@
catch( Rcpp::internal::InterruptedException &__ex__) { \
rcpp_output_type = 1 ; \
} \
catch(Rcpp::exception& __ex__) { \
rcpp_output_type = 2 ; \
rcpp_output_condition = PROTECT(rcpp_exception_to_r_condition(__ex__)) ; \
} \
catch( std::exception& __ex__ ){ \
rcpp_output_type = 2 ; \
rcpp_output_condition = PROTECT(exception_to_r_condition(__ex__)) ; \
} catch( ... ){ \
} \
catch( ... ){ \
rcpp_output_type = 2 ; \
rcpp_output_condition = PROTECT(string_to_try_error("c++ exception (unknown reason)")) ; \
} \
Expand Down
5 changes: 5 additions & 0 deletions inst/unitTests/cpp/exceptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,8 @@ double f1(double val) {
double takeLogNested(double val) {
return f1(val);
}

// [[Rcpp::export]]
void noCall() {
throw Rcpp::exception("Testing", false);
}
10 changes: 10 additions & 0 deletions inst/unitTests/runit.exceptions.R
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,14 @@ test.rcppExceptionLocation <- function() {
checkEquals(nested$call, quote(takeLogNested(x)))
}

test.rcppExceptionNoCall <- function() {

# Can throw exceptions that don't include a call stack
e <- tryCatch(noCall(), error = identity)

checkIdentical(e$message, "Testing")
checkIdentical(e$call, NULL)
checkIdentical(e$cppstack, NULL)
checkIdentical(class(e), c("Rcpp::exception", "C++Error", "error", "condition"))
}
}