summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--test/ruby/test_backtrace.rb16
-rw-r--r--vm_backtrace.c52
2 files changed, 66 insertions, 2 deletions
diff --git a/test/ruby/test_backtrace.rb b/test/ruby/test_backtrace.rb
index aa04cf0961..742463a04d 100644
--- a/test/ruby/test_backtrace.rb
+++ b/test/ruby/test_backtrace.rb
@@ -154,6 +154,22 @@ class TestBacktrace < Test::Unit::TestCase
assert_equal caller(0), caller(0, nil)
end
+ def test_caller_locations_first_label
+ def self.label
+ caller_locations.first.label
+ end
+
+ def self.label_caller
+ label
+ end
+
+ assert_equal 'label_caller', label_caller
+
+ [1].group_by do
+ assert_equal 'label_caller', label_caller
+ end
+ end
+
def test_caller_locations
cs = caller(0); locs = caller_locations(0).map{|loc|
loc.to_s
diff --git a/vm_backtrace.c b/vm_backtrace.c
index 16a9cfdb33..237b010408 100644
--- a/vm_backtrace.c
+++ b/vm_backtrace.c
@@ -543,6 +543,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) {
@@ -568,9 +573,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);
}