-
-
Notifications
You must be signed in to change notification settings - Fork 219
Fixes exception call stack for C++ exceptions #582
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
Conversation
Looks very promising. I had been pondering releasing this weekend but may as well push back a week. |
@@ -33,4 +33,6 @@ | |||
warnings | |||
} | |||
|
|||
|
|||
`str.Rcpp_stack_trace` <- function(x, ...) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a fan of the ` style for functions, personally. Adds little here but oh well -- we have a mixed bag of styles in the code already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is actually a holdover from an earlier iteration when the method was for Rcpp::exception
, which made the backticks (or quotes) necessary. I can remove them now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost not worth a commit; we can clean up another time. We still have inconsistent white-space and whatnot.
Oh, and there he did :) Thanks.
SEXP sys_calls_symbol = Rf_install( "sys.call" ) ; | ||
|
||
// -9 Skips the wrapped tryCatch from Rcpp_eval | ||
Rcpp::Shield<SEXP> sys_calls_expr( Rf_lang2(sys_calls_symbol, Rf_ScalarInteger(-9)) ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we can do something a bit more robust here? (Does this work in the presence of eager byte-compilation?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any thoughts on how?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the last R Summit Luke warned against hard coding the number of calls on the stack so I think this might indeed fail in the presence of said eager byte-compilation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess you could loop up the stack looking for the name of the target enclosing function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes the other way to do it would be to go back to using sys.calls()
and return the call just prior to tryCatch(sys.calls(), error = identity, interrupt = identity)
, which is what we call from the Rcpp_eval at https://github.com/RcppCore/Rcpp/pull/582/files#diff-5b6f3a71a6058dccfe3c3426c64110d2L132.
For example making a small change [1] to this PR, running the tryCatch(takeLog-1), error = identity)
example from above, setting a breakpoint at exception.h:135 and running Rf_PrintValue(calls)
gets you this callstack [2].
Writing the code to properly and robustly match [[6]]
below is not something I have quite worked out how to do yet, which is why I went for the simpler solution to this PR initially, suggestions welcome.
[1]
diff --git a/inst/include/Rcpp/exceptions.h b/inst/include/Rcpp/exceptions.h
index b35d4bb..cd8e46f 100644
--- a/inst/include/Rcpp/exceptions.h
+++ b/inst/include/Rcpp/exceptions.h
@@ -127,10 +127,10 @@ namespace Rcpp{
} // namespace Rcpp
inline SEXP get_last_call(){
- SEXP sys_calls_symbol = Rf_install( "sys.call" ) ;
+ SEXP sys_calls_symbol = Rf_install( "sys.calls" ) ;
// -9 Skips the wrapped tryCatch from Rcpp_eval
- Rcpp::Shield<SEXP> sys_calls_expr( Rf_lang2(sys_calls_symbol, Rf_ScalarInteger(-9)) );
+ Rcpp::Shield<SEXP> sys_calls_expr( Rf_lang1(sys_calls_symbol) );
Rcpp::Shield<SEXP> calls( Rcpp_eval( sys_calls_expr, R_GlobalEnv ) );
return calls;
}
[2]
[[1]]
tryCatch(takeLog(-1), error = identity)
[[2]]
tryCatchList(expr, classes, parentenv, handlers)
[[3]]
tryCatchOne(expr, names, parentenv, handlers[[1L]])
[[4]]
doTryCatch(return(expr), name, parentenv, handler)
[[5]]
takeLog(-1)
[[6]]
tryCatch(evalq(sys.calls(), <environment>), error = function (x)
x, interrupt = function (x)
x)
[[7]]
tryCatchList(expr, classes, parentenv, handlers)
[[8]]
tryCatchOne(tryCatchList(expr, names[-nh], parentenv, handlers[-nh]),
names[nh], parentenv, handlers[[nh]])
[[9]]
doTryCatch(return(expr), name, parentenv, handler)
[[10]]
tryCatchList(expr, names[-nh], parentenv, handlers[-nh])
[[11]]
tryCatchOne(expr, names, parentenv, handlers[[1L]])
[[12]]
doTryCatch(return(expr), name, parentenv, handler)
[[13]]
evalq(sys.calls(), <environment>)
[[14]]
eval(substitute(expr), envir, enclos)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ccing @lionel- since he's been working on related (R-level) code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bb82bd9 now matches the expression exactly rather than relying on the position in the call stack.
Is it not as efficient as it could be, a couple intermediate expressions used in multiple conditions could be stored, I wrote it this way for readabilities sake.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tricky part of looking at sys.calls()
and related functions is to ignore intervening frames. rlang:::trail_make()
is one way of getting the vector of relevant parent frames.
I think if you run rlang::call_stack()
instead of sys.calls()
here, you should get only a handful of frames instead of 14.
Also adds support for populating the cppstack for Rcpp::exceptions when using clang and a str method to make printing a little nicer.
We can't take a dependency on rlang (we don't depend on any packages since 2/3 of CRAN recursively depends on us) so if we wanted to use that approach we'd need to copy the implementation into Rcpp (but it sounds like we may already have a solution that works well enough?) |
@jjallaire Yes there's definitely no need for an additional dependency here. |
yup I was only suggesting to copy |
// We want the call just prior to the call from Rcpp_eval | ||
// This conditional matches | ||
// tryCatch(evalq(sys.calls(), .GlobalEnv), error = identity, interrupt = identity) | ||
if (TYPEOF(expr) == LANGSXP && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we refactor this into a separate function (e.g. in the internal
namespace just above here)? E.g. internal::is_Rcpp_eval_call
or something like that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 9412f1f
Rf_length(expr) == 4 && | ||
nth(expr, 0) == tryCatch_symbol && | ||
CAR(nth(expr, 1)) == evalq_symbol && | ||
CAR(nth(nth(expr, 1), 1)) == sys_calls_symbol && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to validate that the evalq
call has a size of 3 here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need to, see #582 (comment), but we can do so if you think it is better to be explicit about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I wasn't aware that nth
validated the length already; thanks!
LGTM barring some nitpicks -- it's probably an issue we'll never encounter in practice but it seems like the current iteration of the code could fail if it found a stack frame like
Ie, a somewhat more 'bare' |
This is robust to the list size throughout, The function actually works perfectly fine if you remove the first two conditions entirely. TYPEOF(expr) == LANGSXP &&
Rf_length(expr) == 4 I had them in there more for explicitness than anything else. |
I wasn't aware that |
Also I am happy to add some tests for this functionality to prevent future regressions, is there an existing test file that would be appropriate to put them in or should I make new ones? |
Not sure. It is a little all over the place in |
Apparently we already had @jimhester Can you take another look? |
Silly me. Standard case of multiple compilation units sharing code from a header. I guess making the function |
// We want the call just prior to the call from Rcpp_eval | ||
// This conditional matches | ||
// tryCatch(evalq(sys.calls(), .GlobalEnv), error = identity, interrupt = identity) | ||
bool is_Rcpp_eval_call(SEXP expr) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please try inline bool
@eddelbuettel Yep, just did so in 65e502d, will write some tests in a few. |
@@ -126,40 +126,49 @@ namespace Rcpp{ | |||
|
|||
} // namespace Rcpp | |||
|
|||
inline SEXP nth(SEXP s, int n) { | |||
return Rf_length(s) > n ? (n == 0 ? CAR(s) : CAR(Rf_nthcdr(s, n))) : R_NilValue; | |||
namespace internal { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this accidentally was left outside of the outer namespace Rcpp
bombing compilation:
https://travis-ci.org/RcppCore/Rcpp#L2618
na.cpp:27:12: error: reference to ‘internal’ is ambiguous
return internal::Rcpp_IsNA(x);
^
In file included from /home/travis/build/RcppCore/Rcpp/Rcpp.Rcheck/Rcpp/include/RcppCommon.h:122:0,
from /home/travis/build/RcppCore/Rcpp/Rcpp.Rcheck/Rcpp/include/Rcpp.h:27,
from na.cpp:22:
/home/travis/build/RcppCore/Rcpp/Rcpp.Rcheck/Rcpp/include/Rcpp/exceptions.h:129:20: note: candidates are: namespace internal { }
namespace internal {
^
In file included from /home/travis/build/RcppCore/Rcpp/Rcpp.Rcheck/Rcpp/include/Rcpp.h:27:0,
from na.cpp:22:
/home/travis/build/RcppCore/Rcpp/Rcpp.Rcheck/Rcpp/include/RcppCommon.h:45:24: note: namespace Rcpp::internal { }
namespace internal {
^
Ok now have some simple tests for this as well. |
This appears ready. I ran full reverse-depends checks overnight, after having run another set yesterday against master. No new issues. I plan to merge later today. All ok? |
Also adds support for populating the cppstack for Rcpp::exceptions when
using clang and a str method to make printing a little nicer.
Fixes #579