Skip to content

Commit 6e0b10c

Browse files
authored
Merge pull request #582 from jimhester/cppstack
Fixes exception call stack for C++ exceptions
2 parents 8e90f89 + e366353 commit 6e0b10c

File tree

8 files changed

+236
-25
lines changed

8 files changed

+236
-25
lines changed

ChangeLog

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@
2828

2929
* inst/include/Rcpp/date_datetime/newDatetimeVector.h: Added constructor
3030
to instantiate newDatetimeVector from VectorBase.
31+
2016-11-11 Jim Hester <[email protected]>
32+
* inst/include/rcpp/exceptions.h: Return stack trace even if no file
33+
or line is specified. Fix R calls when using Rcpp_eval.
34+
* inst/include/rcpp/routines.h:allow getting a cppstack without specifying a file and line.
35+
* src/api.cpp: Add cppstack support for clang.
36+
* r/exceptions.r:Add a str method for Rcpp_stack_trace objects.
37+
* NAMESPACE: Idem
3138

3239
2016-11-04 Nathan Russell <[email protected]>
3340

NAMESPACE

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,13 @@ export(Module,
3131
demangle,
3232
sizeof,
3333
cpp_object_initializer,
34-
cpp_object_dummy,
34+
cpp_object_dummy,
3535
Rcpp.plugin.maker
3636
)
3737
S3method(print, bytes)
38+
S3method(format, Rcpp_stack_trace)
39+
S3method(str, Rcpp_stack_trace)
40+
S3method(print, Rcpp_stack_trace)
3841
exportClass(RcppClass)
3942

4043

R/exceptions.R

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,16 @@
3333
warnings
3434
}
3535

36+
print.Rcpp_stack_trace <- function(x, ...) {
37+
cat(format(x, ...))
38+
}
39+
40+
str.Rcpp_stack_trace <- function(object, ...) {
41+
cat(format(object, ...))
42+
}
3643

44+
format.Rcpp_stack_trace <- function(x, ...) {
45+
paste0(
46+
if (nzchar(x$file)) paste0(x$file, ":", x$line),
47+
"\n ", paste(collapse = "\n ", seq_along(x$stack), ":", x$stack), "\n")
48+
}

inst/include/Rcpp/exceptions.h

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ namespace Rcpp{
2828

2929
class exception : public std::exception {
3030
public:
31-
explicit exception(const char* message_) : message(message_){}
31+
explicit exception(const char* message_) : message(message_){ rcpp_set_stack_trace(stack_trace()); }
3232
exception(const char* message_, const char* file, int line ) : message(message_){
3333
rcpp_set_stack_trace( stack_trace(file,line) ) ;
3434
}
@@ -124,20 +124,58 @@ namespace Rcpp{
124124
#undef RCPP_SIMPLE_EXCEPTION_CLASS
125125

126126

127+
namespace internal {
128+
129+
inline SEXP nth(SEXP s, int n) {
130+
return Rf_length(s) > n ? (n == 0 ? CAR(s) : CAR(Rf_nthcdr(s, n))) : R_NilValue;
131+
}
132+
133+
// We want the call just prior to the call from Rcpp_eval
134+
// This conditional matches
135+
// tryCatch(evalq(sys.calls(), .GlobalEnv), error = identity, interrupt = identity)
136+
inline bool is_Rcpp_eval_call(SEXP expr) {
137+
SEXP sys_calls_symbol = Rf_install("sys.calls");
138+
SEXP identity_symbol = Rf_install("identity");
139+
SEXP identity_fun = Rf_findFun(identity_symbol, R_BaseEnv);
140+
SEXP tryCatch_symbol = Rf_install("tryCatch");
141+
SEXP evalq_symbol = Rf_install("evalq");
142+
143+
return TYPEOF(expr) == LANGSXP &&
144+
Rf_length(expr) == 4 &&
145+
nth(expr, 0) == tryCatch_symbol &&
146+
CAR(nth(expr, 1)) == evalq_symbol &&
147+
CAR(nth(nth(expr, 1), 1)) == sys_calls_symbol &&
148+
nth(nth(expr, 1), 2) == R_GlobalEnv &&
149+
nth(expr, 2) == identity_fun &&
150+
nth(expr, 3) == identity_fun;
151+
}
152+
}
153+
127154
} // namespace Rcpp
128155

129156
inline SEXP get_last_call(){
130-
SEXP sys_calls_symbol = Rf_install( "sys.calls" ) ;
131-
Rcpp::Shield<SEXP> sys_calls_expr( Rf_lang1(sys_calls_symbol) );
132-
Rcpp::Shield<SEXP> calls( Rcpp_eval( sys_calls_expr, R_GlobalEnv ) );
133-
SEXP res = calls ;
134-
while( !Rf_isNull(CDR(res)) ) res = CDR(res);
135-
return CAR(res) ;
157+
SEXP sys_calls_symbol = Rf_install("sys.calls");
158+
159+
Rcpp::Shield<SEXP> sys_calls_expr(Rf_lang1(sys_calls_symbol));
160+
Rcpp::Shield<SEXP> calls(Rcpp_eval(sys_calls_expr, R_GlobalEnv));
161+
162+
SEXP cur, prev;
163+
prev = cur = calls;
164+
while(CDR(cur) != R_NilValue) {
165+
SEXP expr = CAR(cur);
166+
167+
if (Rcpp::internal::is_Rcpp_eval_call(expr)) {
168+
break;
169+
}
170+
prev = cur;
171+
cur = CDR(cur);
172+
}
173+
return CAR(prev);
136174
}
137175

138176
inline SEXP get_exception_classes( const std::string& ex_class) {
139177
Rcpp::Shield<SEXP> res( Rf_allocVector( STRSXP, 4 ) );
140-
178+
141179
#ifndef RCPP_USING_UTF8_ERROR_STRING
142180
SET_STRING_ELT( res, 0, Rf_mkChar( ex_class.c_str() ) ) ;
143181
#else
@@ -184,7 +222,7 @@ inline SEXP exception_to_r_condition( const std::exception& ex){
184222

185223
inline SEXP string_to_try_error( const std::string& str){
186224
using namespace Rcpp;
187-
225+
188226
#ifndef RCPP_USING_UTF8_ERROR_STRING
189227
Rcpp::Shield<SEXP> simpleErrorExpr( Rf_lang2(::Rf_install("simpleError"), Rf_mkString(str.c_str())) );
190228
Rcpp::Shield<SEXP> tryError( Rf_mkString( str.c_str() ) );
@@ -193,7 +231,7 @@ inline SEXP string_to_try_error( const std::string& str){
193231
SET_STRING_ELT( tryError, 0, Rf_mkCharLenCE( str.c_str(), str.size(), CE_UTF8 ) );
194232
Rcpp::Shield<SEXP> simpleErrorExpr( Rf_lang2(::Rf_install("simpleError"), tryError ));
195233
#endif
196-
234+
197235
Rcpp::Shield<SEXP> simpleError( Rf_eval(simpleErrorExpr, R_GlobalEnv) );
198236
Rf_setAttrib( tryError, R_ClassSymbol, Rf_mkString("try-error") ) ;
199237
Rf_setAttrib( tryError, Rf_install( "condition") , simpleError ) ;
@@ -267,52 +305,52 @@ namespace Rcpp{
267305
inline void NORET stop(const std::string& message) {
268306
throw Rcpp::exception(message.c_str());
269307
}
270-
308+
271309
template <typename T1>
272310
inline void NORET stop(const char* fmt, const T1& arg1) {
273311
throw Rcpp::exception( tfm::format(fmt, arg1 ).c_str() );
274312
}
275-
313+
276314
template <typename T1, typename T2>
277315
inline void NORET stop(const char* fmt, const T1& arg1, const T2& arg2) {
278316
throw Rcpp::exception( tfm::format(fmt, arg1, arg2 ).c_str() );
279317
}
280-
318+
281319
template <typename T1, typename T2, typename T3>
282320
inline void NORET stop(const char* fmt, const T1& arg1, const T2& arg2, const T3& arg3) {
283321
throw Rcpp::exception( tfm::format(fmt, arg1, arg2, arg3).c_str() );
284322
}
285-
323+
286324
template <typename T1, typename T2, typename T3, typename T4>
287325
inline void NORET stop(const char* fmt, const T1& arg1, const T2& arg2, const T3& arg3, const T4& arg4) {
288326
throw Rcpp::exception( tfm::format(fmt, arg1, arg2, arg3, arg4).c_str() );
289327
}
290-
328+
291329
template <typename T1, typename T2, typename T3, typename T4, typename T5>
292330
inline void NORET stop(const char* fmt, const T1& arg1, const T2& arg2, const T3& arg3, const T4& arg4, const T5& arg5) {
293331
throw Rcpp::exception( tfm::format(fmt, arg1, arg2, arg3, arg4, arg5).c_str() );
294332
}
295-
333+
296334
template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
297335
inline void NORET stop(const char* fmt, const T1& arg1, const T2& arg2, const T3& arg3, const T4& arg4, const T5& arg5, const T6& arg6) {
298336
throw Rcpp::exception( tfm::format(fmt, arg1, arg2, arg3, arg4, arg5, arg6).c_str() );
299337
}
300-
338+
301339
template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7>
302340
inline void NORET stop(const char* fmt, const T1& arg1, const T2& arg2, const T3& arg3, const T4& arg4, const T5& arg5, const T6& arg6, const T7& arg7) {
303341
throw Rcpp::exception( tfm::format(fmt, arg1, arg2, arg3, arg4, arg5, arg6, arg7).c_str() );
304342
}
305-
343+
306344
template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8>
307345
inline void NORET stop(const char* fmt, const T1& arg1, const T2& arg2, const T3& arg3, const T4& arg4, const T5& arg5, const T6& arg6, const T7& arg7, const T8& arg8) {
308346
throw Rcpp::exception( tfm::format(fmt, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8).c_str() );
309347
}
310-
348+
311349
template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8, typename T9>
312350
inline void NORET stop(const char* fmt, const T1& arg1, const T2& arg2, const T3& arg3, const T4& arg4, const T5& arg5, const T6& arg6, const T7& arg7, const T8& arg8, const T9& arg9) {
313351
throw Rcpp::exception( tfm::format(fmt, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9).c_str() );
314352
}
315-
353+
316354
template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8, typename T9, typename T10>
317355
inline void NORET stop(const char* fmt, const T1& arg1, const T2& arg2, const T3& arg3, const T4& arg4, const T5& arg5, const T6& arg6, const T7& arg7, const T8& arg8, const T9& arg9, const T10& arg10) {
318356
throw Rcpp::exception( tfm::format(fmt, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10).c_str() );

inst/include/Rcpp/routines.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ SEXP rcpp_set_stack_trace(SEXP);
4343
std::string demangle(const std::string& name);
4444
const char* short_file_name(const char* );
4545
int* get_cache(int n);
46-
SEXP stack_trace( const char *file, int line);
46+
SEXP stack_trace( const char *file = "", int line = -1);
4747
SEXP get_string_elt(SEXP s, R_xlen_t i);
4848
const char* char_get_string_elt(SEXP s, R_xlen_t i);
4949
void set_string_elt(SEXP s, R_xlen_t i, SEXP v);
@@ -143,7 +143,7 @@ inline attribute_hidden const char* short_file_name(const char* file) {
143143
return fun(file);
144144
}
145145

146-
inline attribute_hidden SEXP stack_trace( const char *file, int line){
146+
inline attribute_hidden SEXP stack_trace( const char *file = "", int line = -1){
147147
typedef SEXP (*Fun)(const char*, int);
148148
static Fun fun = GET_CALLABLE("stack_trace");
149149
return fun(file, line);

inst/unitTests/cpp/exceptions.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// -*- mode: C++; c-indent-level: 4; c-basic-offset: 4; indent-tabs-mode: nil; -*-
2+
//
3+
// dates.cpp: Rcpp R/C++ interface class library -- Date + Datetime tests
4+
//
5+
// Copyright (C) 2010 - 2013 Dirk Eddelbuettel and Romain Francois
6+
//
7+
// This file is part of Rcpp.
8+
//
9+
// Rcpp is free software: you can redistribute it and/or modify it
10+
// under the terms of the GNU General Public License as published by
11+
// the Free Software Foundation, either version 2 of the License, or
12+
// (at your option) any later version.
13+
//
14+
// Rcpp is distributed in the hope that it will be useful, but
15+
// WITHOUT ANY WARRANTY; without even the implied warranty of
16+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
// GNU General Public License for more details.
18+
//
19+
// You should have received a copy of the GNU General Public License
20+
// along with Rcpp. If not, see <http://www.gnu.org/licenses/>.
21+
22+
#include <Rcpp.h>
23+
using namespace Rcpp;
24+
25+
// [[Rcpp::export]]
26+
double takeLog(double val) {
27+
if (val <= 0.0) {
28+
throw std::range_error("Inadmissible value");
29+
}
30+
return log(val);
31+
}
32+
33+
// [[Rcpp::export]]
34+
double takeLogRcpp(double val) {
35+
if (val <= 0.0) {
36+
throw Rcpp::exception("Inadmissible value");
37+
}
38+
return log(val);
39+
}
40+
41+
// [[Rcpp::export]]
42+
double takeLogRcppLocation(double val) {
43+
if (val <= 0.0) {
44+
throw Rcpp::exception("Inadmissible value", "exceptions.cpp", 44);
45+
}
46+
return log(val);
47+
}
48+
49+
double f1(double val) {
50+
return takeLogRcppLocation(val);
51+
}
52+
53+
// [[Rcpp::export]]
54+
double takeLogNested(double val) {
55+
return f1(val);
56+
}

inst/unitTests/runit.exceptions.R

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#!/usr/bin/env r
2+
# -*- mode: R; tab-width: 4; -*-
3+
#
4+
# Copyright (C) 2010 - 2013 Dirk Eddelbuettel and Romain Francois
5+
#
6+
# This file is part of Rcpp.
7+
#
8+
# Rcpp is free software: you can redistribute it and/or modify it
9+
# under the terms of the GNU General Public License as published by
10+
# the Free Software Foundation, either version 2 of the License, or
11+
# (at your option) any later version.
12+
#
13+
# Rcpp is distributed in the hope that it will be useful, but
14+
# WITHOUT ANY WARRANTY; without even the implied warranty of
15+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
# GNU General Public License for more details.
17+
#
18+
# You should have received a copy of the GNU General Public License
19+
# along with Rcpp. If not, see <http://www.gnu.org/licenses/>.
20+
.runThisTest <- Sys.getenv("RunAllRcppTests") == "yes"
21+
22+
if (.runThisTest) {
23+
.setUp <- Rcpp:::unitTestSetup("exceptions.cpp")
24+
25+
test.stdException <- function() {
26+
27+
# Code works normally without an exception
28+
checkIdentical(takeLog(1L), log(1L))
29+
30+
# C++ exceptions are converted to R conditions
31+
condition <- tryCatch(takeLog(-1L), error = identity)
32+
33+
checkIdentical(condition$message, "Inadmissible value")
34+
checkIdentical(class(condition), c("std::range_error", "C++Error", "error", "condition"))
35+
36+
# C++ stack only available for Rcpp::exceptions
37+
checkTrue(is.null(condition$cppstack))
38+
39+
checkIdentical(condition$call, quote(takeLog(-1L)))
40+
}
41+
42+
43+
test.rcppException <- function() {
44+
45+
# Code works normally without an exception
46+
checkIdentical(takeLog(1L), log(1L))
47+
48+
# C++ exceptions are converted to R conditions
49+
condition <- tryCatch(takeLogRcpp(-1L), error = identity)
50+
51+
checkIdentical(condition$message, "Inadmissible value")
52+
checkIdentical(class(condition), c("Rcpp::exception", "C++Error", "error", "condition"))
53+
54+
checkTrue(!is.null(condition$cppstack))
55+
56+
checkIdentical(class(condition$cppstack), "Rcpp_stack_trace")
57+
58+
checkEquals(condition$call, quote(takeLogRcpp(-1L)))
59+
}
60+
61+
test.rcppExceptionLocation <- function() {
62+
63+
# Code works normally without an exception
64+
checkIdentical(takeLog(1L), log(1L))
65+
66+
# C++ exceptions are converted to R conditions
67+
condition <- tryCatch(takeLogRcppLocation(-1L), error = identity)
68+
69+
checkIdentical(condition$message, "Inadmissible value")
70+
checkIdentical(class(condition), c("Rcpp::exception", "C++Error", "error", "condition"))
71+
72+
checkTrue(!is.null(condition$cppstack))
73+
checkIdentical(class(condition$cppstack), "Rcpp_stack_trace")
74+
75+
checkIdentical(condition$cppstack$file, "exceptions.cpp")
76+
checkIdentical(condition$cppstack$line, 44L)
77+
78+
checkEquals(condition$call, quote(takeLogRcppLocation(-1L)))
79+
}
80+
81+
test.rcppExceptionLocation <- function() {
82+
83+
# Nested exceptions work the same way
84+
normal <- tryCatch(takeLogRcppLocation(-1L), error = identity)
85+
f1 <- function(x) takeLogNested(x)
86+
87+
nested <- tryCatch(f1(-1), error = identity)
88+
89+
# Message the same
90+
checkIdentical(normal$message, nested$message)
91+
92+
checkEquals(nested$call, quote(takeLogNested(x)))
93+
}
94+
95+
}

src/api.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ using namespace Rcpp;
3232
#include <cxxabi.h>
3333
#endif
3434

35-
#if defined(__GNUC__)
35+
#if defined(__GNUC__) || defined(__clang__)
3636
#if defined(_WIN32) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__CYGWIN__) || defined(__sun) || defined(_AIX) || defined(__MUSL__)
3737
// do nothing
3838
#else
@@ -262,7 +262,7 @@ SEXP rcpp_can_use_cxx11() {
262262

263263
// [[Rcpp::register]]
264264
SEXP stack_trace(const char* file, int line) {
265-
#if defined(__GNUC__)
265+
#if defined(__GNUC__) || defined(__clang__)
266266
#if defined(_WIN32) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__CYGWIN__) || defined(__sun) || defined(_AIX) || defined(__MUSL__)
267267
// Simpler version for Windows and *BSD
268268
List trace = List::create(_["file"] = file,

0 commit comments

Comments
 (0)