summaryrefslogtreecommitdiff
path: root/vm_backtrace.c
diff options
context:
space:
mode:
authorNARUSE, Yui <naruse@airemix.jp>2021-04-02 16:08:30 +0900
committerNARUSE, Yui <naruse@airemix.jp>2021-04-02 16:08:30 +0900
commit0315e1e5ca0722f9dffeae70b860c19de303e339 (patch)
tree95135bd3767dc9a3c6fa1a83373f64cf98ce1d93 /vm_backtrace.c
parent4e2738f477b5343a0d48a400c975220fed123c9b (diff)
merge revision(s) f9f13a4f6d8be706b17efc089c28f7bc617ef549: [Backport #17746]
Ensure that caller respects the start argument Previously, if there were ignored frames (iseq without pc), we could go beyond the requested start frame. This has two changes: 1) Ensure that we don't look beyond the start frame by using last_cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(last_cfp) until the desired start frame is reached. 2) To fix the failures caused by change 1), which occur when a limited number of frames is requested, scan the VM stack before allocating backtrace frames, looking for ignored frames. This is complicated if there are ignored frames before and after the start, in which case we need to scan until the start frame, and then scan backwards, decrementing the start value until we get to the point where start will result in the number of requested frames. This fixes a Rails test failure. Jean Boussier was able to to produce a failing test case outside of Rails. Co-authored-by: Jean Boussier <jean.boussier@gmail.com> --- test/ruby/test_backtrace.rb | 16 ++++++++++++++ vm_backtrace.c | 52 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 66 insertions(+), 2 deletions(-)
Diffstat (limited to 'vm_backtrace.c')
-rw-r--r--vm_backtrace.c52
1 files changed, 50 insertions, 2 deletions
diff --git a/vm_backtrace.c b/vm_backtrace.c
index fae65b5c2a..93871c9d2a 100644
--- a/vm_backtrace.c
+++ b/vm_backtrace.c
@@ -542,6 +542,11 @@ backtrace_each(const rb_execution_context_t *ec,
real_size = size = last = 0;
}
else {
+ /* Ensure we don't look at frames beyond the ones requested */
+ for(; from_last > 0 && start_cfp >= last_cfp; from_last--) {
+ last_cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(last_cfp);
+ }
+
real_size = size = start_cfp - last_cfp + 1;
if (from_last > size) {
@@ -567,9 +572,52 @@ backtrace_each(const rb_execution_context_t *ec,
init(arg, size);
- /* SDR(); */
+ /* If a limited number of frames is requested, scan the VM stack for
+ * ignored frames (iseq without pc). Then adjust the start for the
+ * backtrace to account for skipped frames.
+ */
+ if (start > 0 && num_frames >= 0 && num_frames < real_size) {
+ ptrdiff_t ignored_frames;
+ bool ignored_frames_before_start = false;
+ for (i=0, j=0, cfp = start_cfp; i<last && j<real_size; i++, j++, cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp)) {
+ if (cfp->iseq && !cfp->pc) {
+ if (j < start)
+ ignored_frames_before_start = true;
+ else
+ i--;
+ }
+ }
+ ignored_frames = j - i;
+
+ if (ignored_frames) {
+ if (ignored_frames_before_start) {
+ /* There were ignored frames before start. So just decrementing
+ * start for ignored frames could still result in not all desired
+ * frames being captured.
+ *
+ * First, scan to the CFP of the desired start frame.
+ *
+ * Then scan backwards to previous frames, skipping the number of
+ * frames ignored after start and any additional ones before start,
+ * so the number of desired frames will be correctly captured.
+ */
+ for (i=0, j=0, cfp = start_cfp; i<last && j<real_size && j < start; i++, j++, cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp)) {
+ /* nothing */
+ }
+ for (; start > 0 && ignored_frames > 0 && j > 0; j--, ignored_frames--, start--, cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp)) {
+ if (cfp->iseq && !cfp->pc) {
+ ignored_frames++;
+ }
+ }
+ } else {
+ /* No ignored frames before start frame, just decrement start */
+ start -= ignored_frames;
+ }
+ }
+ }
+
for (i=0, j=0, cfp = start_cfp; i<last && j<real_size; i++, j++, cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp)) {
- if (i < start) {
+ if (j < start) {
if (iter_skip) {
iter_skip(arg, cfp);
}