Bug #22176
open[Feature] TracePoint#defined_box: get the Ruby::Box where a method is defined
Description
Summary¶
In TracePoint events such as :call / :c_call / :return / :c_return, I'd like to propose adding an attribute TracePoint#defined_box, which returns the Ruby::Box in which the called method was defined.
Currently, tp.defined_class correctly returns a distinct class/module object per Box, but there is no direct way to know which Box that object belongs to.
Background / Motivation¶
With Ruby::Box, the same class name and the same source file can be loaded independently in multiple Boxes.
In this situation, tools that observe and record method calls via TracePoint (profilers, security auditing tools, test support tools, etc.) run into a practical problem: they cannot distinguish "which Box the called method was defined in."
Reproduction¶
sample1.rb:
b = Ruby::Box.new
b.require_relative 'sample2'
require_relative 'sample2'
tp = TracePoint.new(:call) do |tp|
if tp.method_id == :method1
puts "Calling #{tp.defined_class}##{tp.method_id} from #{tp.path}:#{tp.lineno}"
end
end
tp.enable
ins1 = Sample2.new
ins1.method1
ins2 = b::Sample2.new
ins2.method1
sample2.rb:
Output:
$ RUBY_BOX=1 ./miniruby ./sample1.rb
./miniruby: warning: Ruby::Box is experimental, and the behavior may change in the future!
See https://docs.ruby-lang.org/en/master/Ruby/Box.html for known issues, etc.
Calling Sample2#method1 from /path/to/sample2.rb:2
This is method 1
Calling Sample2#method1 from /path/to/sample2.rb:2
This is method 1
There is no way to distinguish the two calls using tp.defined_class, tp.method_id, tp.path, or tp.lineno.
I have confirmed that the class returned by tp.defined_class does in fact have a different object id in each case, but there is no way to check which Box it is associated with.
Known workarounds and their issues¶
1. tp.binding.eval "Ruby::Box.current.inspect"¶
It's possible to obtain the Box indirectly by evaluating Ruby::Box.current through tp.binding. However, :c_call / :c_return (calls into methods implemented in C) have no corresponding Ruby-level binding, so tp.binding itself is unavailable, and this workaround cannot be used there.
2. tp.self.method(tp.method_id).box.inspect¶
Re-obtaining an Object#method reference does work, and this also works for C methods. However, it has the downside of re-constructing a Method object inside the TracePoint block every time, which adds overhead, and it isn't very intuitive.
Proposed API¶
Add TracePoint#defined_box, paired with tp.defined_class, returning the Ruby::Box instance in which the method/class was defined (or nil when RUBY_BOX is disabled).
tp = TracePoint.new(:call, :c_call) do |tp|
puts "#{tp.defined_class}##{tp.method_id} defined in #{tp.defined_box.inspect}"
end
On naming¶
| Candidate | Verdict | Reason |
|---|---|---|
defined_box |
Proposed | Symmetric with defined_class |
box |
Under consideration | Matches Method#box, but less specific |
Possible implementation¶
I'm considering two directions and would appreciate feedback:
- Go through
rb_trace_arg_t *and userb_callable_method_entry_without_refinements()to look up the method definition from the klass and called_id, then read the box off of it. This may add some overhead. - Add an argument carrying the defining-Box information to
rb_exec_event_hook_orig(). Since this function is used very broadly, this would come with a higher cost of change.
Open questions¶
- For singleton methods,
defined_classreturns the singleton class per spec. Shoulddefined_boxlikewise return the Box that singleton class belongs to? - Is similar information also needed for the
:script_compiledevent, etc.?
Environment¶
$ ./miniruby --version
ruby 4.1.0dev (2026-07-04T01:14:12Z master 870c8d6a50) +PRISM [arm64-darwin25]
Use cases¶
Security auditing / debugging¶
When multiple Boxes coexist and an unexpected side effect occurs, there is likely demand for being able to trace "which Box the code that actually ran was defined in."
In vivarium, an auditing tool I'm developing, I'm also considering per-Box auditing, but currently the only options are going through tp.binding or re-fetching Method#box each time, both of which feel redundant.
Verifying dependency updates¶
One of the use cases for Box is running an old and a new version of a dependency in parallel, each in its own Box, and comparing the resulting responses. For this, a mechanism to visualize which Box's code path was actually executed is needed.
Application to multi-tenant systems, etc.¶
TracePoint would make it possible to tally how much code from which Box (i.e., which plugin or tenant) was invoked. For example, an APM/profiling tool could use this to visualize per-Box usage, which may be a real demand.
Since this seems like a generally useful attribute to trace with respect to Box in the first place, I expect more ideas for use cases to surface as well.
Related¶
- [Feature #21311] Namespace on read (revised)
No data to display