Project

General

Profile

Actions

Bug #22197

closed

Backtraces show methods which do not exist

Bug #22197: Backtraces show methods which do not exist
1

Added by Eregon (Benoit Daloze) about 1 month ago. Updated 8 days ago.

Status:
Closed
Target version:
ruby -v:
ruby 4.0.3 (2026-04-21 revision 85ddef263a) +PRISM [arm64-darwin25]
[ruby-core:126092]

Description

class Parent
  def original
    puts caller_locations(0), nil
  end
end

class Child < Parent
  alias_method :alias, :original
end

Child.new.alias

module Original
  def original
    puts caller_locations(0)
  end
end

class A
  define_method(:a, Original.instance_method(:original))
end

A.new.a

gives:

$ ruby -v backtrace_alias.rb
ruby 4.0.3 (2026-04-21 revision 85ddef263a) +PRISM [arm64-darwin25]
backtrace_alias.rb:3:in 'Child#original'
backtrace_alias.rb:11:in '<main>'

backtrace_alias.rb:15:in 'A#original'
backtrace_alias.rb:23:in '<main>'

But this seems clearly incorrect:

  • there is no Child#original, there is only Parent#original and Child#alias.
  • there is no A#original, there is only Original#original and A#a.

This behavior exists since Ruby 3.4 which added the module name in backtraces (#19117).

So the issue is that backtraces show the "original method name from the definition" with the "method owner of the actually-called method".

It should be consistent to avoid showing methods which don't exist.
And it should be the original method from the definition, because we show the file and line of that, so we should also show the original name (already the case) and the original module (not the case yet, the bug).

FWIW TruffleRuby already behaves like that:

$ truffleruby 34.0.1 (2026-04-26), like ruby 3.4.9, Oracle GraalVM Native [arm64-darwin23]
backtrace_alias.rb:3:in 'Parent#original'
backtrace_alias.rb:11:in '<main>'

backtrace_alias.rb:15:in 'Original#original'
backtrace_alias.rb:23:in '<main>'

In #19117 there was some discussion about this, notably:
https://bugs.ruby-lang.org/issues/19117#note-17
@byroot (Jean Boussier)

My suggestion is for the owner. Simply put because it matches the path and other existing method representations.

But actually the owner isn't that, what is described here is the original module of the method definition, same as the expected from this issue.

https://bugs.ruby-lang.org/issues/19117#note-18
@Eregon (Benoit Daloze)

It must be the owner, anything else would be very confusing.
I believe nobody wants String#then (there is no such method), they want to see Kernel#then.

Where I was wrong about owner being the only thing. I was also expressing we should show where the method is defined, but I overlooked the "originally" part (aliases, define_method, etc).

Also CC @mame (Yusuke Endoh) who implemented the change.

I think this is worth fixing for Ruby 4.1 (not sure if worth backporting).

PR: https://github.com/ruby/ruby/pull/17963

Updated by Eregon (Benoit Daloze) about 1 month ago · Edited 1Actions #1 [ruby-core:126093]

In CRuby implementation terms we'd get the original method module with something like:

const rb_callable_method_entry_t *me = cme;
// (1) unwrap an explicit alias entry, if present
while (me->def->type == VM_METHOD_TYPE_ALIAS) {
  me = me->def->body.alias.original_me;      // cf. original_method_definition(), vm_method.c:2737
}
// (2) defined_class -> real module
VALUE dc = me->defined_class;
VALUE mod = RB_TYPE_P(dc, T_ICLASS) ? RBASIC_CLASS(dc) : dc;   // + prepend-origin guard (4156-4158)

I'll attempt to make a PR for it soon.

EDIT: it's actually more complicated than that.

Updated by sophiathedev (Nguyen Thang) 26 days ago · Edited Actions #2 [ruby-core:126121]

Eregon (Benoit Daloze) wrote in #note-1:

In CRuby implementation terms we'd get the original method module with something like:

const rb_callable_method_entry_t *me = cme;
// (1) unwrap an explicit alias entry, if present
while (me->def->type == VM_METHOD_TYPE_ALIAS) {
  me = me->def->body.alias.original_me;      // cf. original_method_definition(), vm_method.c:2737
}
// (2) defined_class -> real module
VALUE dc = me->defined_class;
VALUE mod = RB_TYPE_P(dc, T_ICLASS) ? RBASIC_CLASS(dc) : dc;   // + prepend-origin guard (4156-4158)

I'll attempt to make a PR for it soon.

This is my first time contributing to Ruby core. I've submitted a PR to fix this issue here:
https://github.com/ruby/ruby/pull/17978

Thank you @Eregon (Benoit Daloze) for the bug report and the helpful notes! 🐐

Any feedback on the PR is highly appreciated.

Updated by Eregon (Benoit Daloze) 21 days ago Actions #3 [ruby-core:126157]

PR is ready: https://github.com/ruby/ruby/pull/17963
Would appreciate some reviews :)


sophiathedev (Nguyen Thang) wrote in #note-2:

This is my first time contributing to Ruby core. I've submitted a PR to fix this issue here:
https://github.com/ruby/ruby/pull/17978

Thank you for your PR, I think this is quite a difficult fix for a first PR though.
I took a look and your PR fixes one case but not the other cases (I commented there).

Updated by Eregon (Benoit Daloze) 21 days ago Actions #4

  • Description updated (diff)

Updated by matz (Yukihiro Matsumoto) 9 days ago Actions #5 [ruby-core:126295]

This is a bug and should be fixed.

The file and line we print are those of the original definition, so the module must come from that same definition. Mixing it with the run-time owner produces a name for a method that does not exist, which is worse than either choice alone.

Please fix it in 4.1. It changes backtrace output, so I lean against not backporting, but I leave that to the branch maintainers.

Matz.

Updated by Eregon (Benoit Daloze) 8 days ago Actions #6

  • Status changed from Open to Closed

Applied in changeset git|a89b3a6fd5cb5f382adfef7d28dfc73e330f614d.


[Bug #22197] Show the original definition module in backtrace labels

An alias or a method installed via define_method(UnboundMethod) shares the
original method definition, but the CME's owner and defined_class point at
the site where the copy was installed. Combined with the method name (taken
from the original definition), backtraces reported a "Class#method" pair
that never existed: an alias in a subclass was shown as Child#original
instead of Parent#original, and define_method(Original.instance_method(:m))
was shown as A#m instead of Original#m.

Recover the defining module only for shared definitions (def->aliased),
leaving plain, singleton and class methods untouched. Apply it to both
Location#label and the backtrace string built by location_to_str.

Distinguish the singleton-class cases via the singleton's attached object:
define_method(SomeModule.instance_method(:m)) installed on a singleton class
must report SomeModule#m, while an aliased class method (def self.m), whose
iseq cref holds the lexical class rather than the singleton it lives on,
must keep its owner. Only keep the owner when it is the singleton class of
the cref's class; otherwise the cref names the genuine definition site, so
use it.

Co-Authored-By: Claude Opus 4.8

Updated by Eregon (Benoit Daloze) 8 days ago Actions #7

  • Assignee set to Eregon (Benoit Daloze)
  • Target version set to 4.1
Actions

Also available in: PDF Atom