Bug #22197
closedBacktraces show methods which do not exist
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 onlyParent#originalandChild#alias. - there is no
A#original, there is onlyOriginal#originalandA#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).
Updated by Eregon (Benoit Daloze) about 1 month ago
· Edited
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
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
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
- Description updated (diff)
Updated by matz (Yukihiro Matsumoto) 9 days ago
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
- 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 noreply@anthropic.com
Updated by Eregon (Benoit Daloze) 8 days ago
- Assignee set to Eregon (Benoit Daloze)
- Target version set to 4.1