Class: Thread::Backtrace::Location
| Relationships & Source Files | |
| Inherits: | Object |
| Defined in: | vm_backtrace.c, ast.rb |
Overview
An object representation of a stack frame, initialized by Kernel.caller_locations.
For example:
# caller_locations.rb
def a(skip)
caller_locations(skip)
end
def b(skip)
a(skip)
end
def c(skip)
b(skip)
end
c(0..2).map do |call|
puts call.to_s
end
Running ruby caller_locations.rb will produce:
caller_locations.rb:2:in `a'
caller_locations.rb:5:in `b'
caller_locations.rb:8:in `c'
Here's another example with a slightly different result:
# foo.rb
class Foo
attr_accessor :locations
def initialize(skip)
@locations = caller_locations(skip)
end
end
Foo.new(0..2).locations.map do |call|
puts call.to_s
end
Now run ruby foo.rb and you should see:
init.rb:4:in `initialize'
init.rb:8:in `new'
init.rb:8:in `<main>'
Instance Method Summary
-
#absolute_path
Returns the full file path of this frame.
-
#base_label
Returns the base label of this frame, which is usually equal to the label, without decoration.
-
#inspect
Returns the same as calling #inspect on the string representation of
#to_str -
#label
Returns the label of this frame.
-
#lineno
Returns the line number of this frame.
-
#path
Returns the file name of this frame.
-
#source_range ⇒ Ruby::SourceRange
Returns the
::Ruby::SourceRangefor the::Rubyexpression associated with this backtrace location. -
#syntax_tree ⇒ Prism::Node | {RubyVM::AbstractSyntaxTree::Node} | nil
Returns the AST node at this location, by re-parsing the source file.
-
#to_s
Returns a Kernel.caller style string representing this frame.
Instance Method Details
#absolute_path
Returns the full file path of this frame.
Same as #path, except that it will return absolute path even if the frame is in the main script.
# File 'vm_backtrace.c', line 706
static VALUE
location_absolute_path_m(VALUE self)
{
return location_realpath(location_ptr(self));
}
#base_label
Returns the base label of this frame, which is usually equal to the label, without decoration.
Consider the following example:
def foo
puts caller_locations(0).first.base_label
1.times do
puts caller_locations(0).first.base_label
1.times do
puts caller_locations(0).first.base_label
end
end
end
The result of calling foo is this:
foo
foo
foo
# File 'vm_backtrace.c', line 404
static VALUE
location_base_label_m(VALUE self)
{
return location_base_label(location_ptr(self));
}
#inspect
Returns the same as calling inspect on the string representation of
#to_str
# File 'vm_backtrace.c', line 772
static VALUE
location_inspect_m(VALUE self)
{
return rb_str_inspect(location_to_str(location_ptr(self)));
}
#label
Returns the label of this frame.
Usually consists of method, class, module, etc names with decoration.
Consider the following example:
def foo
puts caller_locations(0).first.label
1.times do
puts caller_locations(0).first.label
1.times do
puts caller_locations(0).first.label
end
end
end
The result of calling foo is this:
foo
block in foo
block (2 levels) in foo
# File 'vm_backtrace.c', line 364
static VALUE
location_label_m(VALUE self)
{
return location_label(location_ptr(self));
}
#lineno
Returns the line number of this frame.
For example, using caller_locations.rb from Location
loc = c(0..1).first
loc.lineno #=> 2
# File 'vm_backtrace.c', line 198
static VALUE
location_lineno_m(VALUE self)
{
return INT2FIX(location_lineno(location_ptr(self)));
}
#path
Returns the file name of this frame. This will generally be an absolute path, unless the frame is in the main script, in which case it will be the script location passed on the command line.
For example, using caller_locations.rb from Location
loc = c(0..1).first
loc.path #=> caller_locations.rb
# File 'vm_backtrace.c', line 426
static VALUE
location_path_m(VALUE self)
{
const rb_iseq_t *iseq = location_iseq(location_ptr(self));
return iseq ? rb_iseq_path(iseq) : Qnil;
}
#source_range ⇒ Ruby::SourceRange
Returns the ::Ruby::SourceRange for the ::Ruby expression associated with this
backtrace location.
On CRuby, this method re-reads and re-parses the source file to determine
the range. ::File errors encountered while reading the source are propagated.
::RuntimeError is raised if required source location information is
unavailable, or if the source has changed.
RubyVM.keep_script_lines = true can be used to retain source files in memory and avoid re-reading them from the filesystem.
Locations from eval'd code are only available with RubyVM.keep_script_lines = true.
# File 'vm_backtrace.c', line 589
static VALUE
location_source_range_m(VALUE self)
{
#ifdef USE_ISEQ_NODE_ID
rb_backtrace_location_t *backtrace_location = location_ptr(self);
const rb_iseq_t *iseq = location_iseq(backtrace_location);
if (!iseq) {
rb_raise(rb_eRuntimeError, "cannot get source range for location without Ruby bytecode");
}
rb_iseq_check(iseq);
int node_id = location_node_id(backtrace_location);
if (node_id == -1) {
rb_raise(rb_eRuntimeError, "cannot get source range for location without a node ID");
}
if (!ISEQ_BODY(iseq)->has_source_hash) {
rb_raise(rb_eRuntimeError, "cannot get source range because the source hash is unavailable");
}
uint64_t source_hash = ISEQ_BODY(iseq)->source_hash;
VALUE path = rb_iseq_path(iseq);
VALUE absolute_path = rb_iseq_realpath(iseq);
VALUE script_lines = ISEQ_BODY(iseq)->variable.script_lines;
VALUE source;
VALUE parser_path = path;
int first_lineno = 1;
if (!NIL_P(script_lines)) {
source = rb_ary_join(script_lines, Qnil);
first_lineno = location_source_first_lineno(iseq, script_lines);
}
else if (iseq_from_e_script_p(iseq, path, source_hash)) {
source = rb_e_script;
}
else if (!NIL_P(absolute_path)) {
source = location_source_read_file(absolute_path);
parser_path = absolute_path;
}
else {
rb_raise(rb_eArgError, "cannot get source range for location in eval");
}
if (NIL_P(parser_path)) {
parser_path = rb_str_new_cstr("(eval)");
}
if (!location_source_hash_matches(source, source_hash)) {
rb_raise(rb_eRuntimeError, "source has been modified");
}
rb_code_location_t code_location;
bool found;
if (ISEQ_BODY(iseq)->prism) {
found = pm_node_source_location(
source,
parser_path,
first_lineno,
node_id,
&code_location
);
}
else {
found = rb_ast_node_source_location(
source,
parser_path,
first_lineno,
node_id,
ISEQ_BODY(iseq)->type == ISEQ_TYPE_BLOCK,
ISEQ_BODY(iseq)->location.node_id,
&code_location
);
}
if (!found) {
rb_raise(rb_eRuntimeError, "cannot find node ID %d in parsed source", node_id);
}
return rb_source_range_new(path, absolute_path, &code_location);
#else
rb_raise(rb_eRuntimeError, "cannot get source range because node IDs are disabled");
#endif
}
#syntax_tree ⇒ Prism::Node | {RubyVM::AbstractSyntaxTree::Node} | nil
Returns the AST node at this location, by re-parsing the source file. See
RubyVM::InstructionSequence#syntax_tree for when nil is returned.
This method is experimental and might change without notice.
# File 'ast.rb', line 484
def syntax_tree iseq = Primitive.iseq_of_backtrace_location(self) return nil unless iseq node_id = Primitive.node_id_for_backtrace_location(self) return nil unless node_id scope = iseq.syntax_tree return nil unless scope if scope.is_a?(RubyVM::AbstractSyntaxTree::Node) return Primitive.ast_node_find(scope, node_id) end return scope if scope.node_id == node_id queue = [scope] while (node = queue.shift) node.compact_child_nodes.each do |child| return child if child.node_id == node_id queue << child end end nil end
#to_s
Returns a Kernel.caller style string representing this frame.
# File 'vm_backtrace.c', line 762
static VALUE
location_to_str_m(VALUE self)
{
return location_to_str(location_ptr(self));
}