Skip to content

Commit

Permalink
Use the caller location as default filename for eval family of methods
Browse files Browse the repository at this point in the history
[Feature #19755]

Before (in /tmp/test.rb):

```ruby
Object.class_eval("p __FILE__") # => "(eval)"
```

After:

```ruby
Object.class_eval("p __FILE__") # => "(eval at /tmp/test.rb:1)"
```

This makes it much easier to track down generated code in case
the author forgot to provide a filename argument.
  • Loading branch information
byroot committed Jul 24, 2023
1 parent 14d16bd commit 43a5c19
Show file tree
Hide file tree
Showing 13 changed files with 147 additions and 60 deletions.
7 changes: 7 additions & 0 deletions spec/ruby/core/basicobject/instance_eval_spec.rb
Expand Up @@ -84,6 +84,13 @@ def foo

end

ruby_version_is "3.3" do
it "uses the caller location as default location" do
f = Object.new
f.instance_eval("[__FILE__, __LINE__]").should == ["(eval at #{__FILE__}:#{__LINE__})", 1]
end
end

it "has access to receiver's instance variables" do
BasicObjectSpecs::IVars.new.instance_eval { @secret }.should == 99
BasicObjectSpecs::IVars.new.instance_eval("@secret").should == 99
Expand Down
16 changes: 12 additions & 4 deletions spec/ruby/core/binding/eval_spec.rb
Expand Up @@ -60,10 +60,12 @@
bind.eval("#foo\n__LINE__", "(test)", 88).should == 89
end

it "uses (eval) as __FILE__ if single argument given" do
obj = BindingSpecs::Demo.new(1)
bind = obj.get_binding
bind.eval("__FILE__").should == '(eval)'
ruby_version_is ""..."3.3" do
it "uses (eval) as __FILE__ if single argument given" do
obj = BindingSpecs::Demo.new(1)
bind = obj.get_binding
bind.eval("__FILE__").should == '(eval)'
end
end

it "uses 1 as __LINE__" do
Expand Down Expand Up @@ -104,4 +106,10 @@

bind.eval("'bar'.foo").should == "foo"
end

ruby_version_is "3.3" do
it "uses the caller location as default filename" do
binding.eval("[__FILE__, __LINE__]").should == ["(eval at #{__FILE__}:#{__LINE__})", 1]
end
end
end
39 changes: 28 additions & 11 deletions spec/ruby/core/kernel/eval_spec.rb
Expand Up @@ -159,20 +159,37 @@ class Object
end
end

it "uses (eval) filename if none is provided" do
eval("__FILE__").should == "(eval)"
eval("__FILE__", binding).should == "(eval)"
eval("__FILE__", binding, "success").should == "success"
eval("eval '__FILE__', binding").should == "(eval)"
eval("eval '__FILE__', binding", binding).should == "(eval)"
eval("eval '__FILE__', binding", binding, 'success').should == '(eval)'
eval("eval '__FILE__', binding, 'success'", binding).should == 'success'
end
ruby_version_is ""..."3.3" do
it "uses (eval) filename if none is provided" do
eval("__FILE__").should == "(eval)"
eval("__FILE__", binding).should == "(eval)"
eval("__FILE__", binding, "success").should == "success"
eval("eval '__FILE__', binding").should == "(eval)"
eval("eval '__FILE__', binding", binding).should == "(eval)"
eval("eval '__FILE__', binding", binding, 'success').should == '(eval)'
eval("eval '__FILE__', binding, 'success'", binding).should == 'success'
end

it 'uses (eval) for __FILE__ and 1 for __LINE__ with a binding argument' do
eval("[__FILE__, __LINE__]", binding).should == ["(eval)", 1]
it 'uses (eval) for __FILE__ and 1 for __LINE__ with a binding argument' do
eval("[__FILE__, __LINE__]", binding).should == ["(eval)", 1]
end
end

ruby_version_is "3.3" do
it "uses (eval at __FILE__:__LINE__) if none is provided" do
eval("__FILE__").should == "(eval at #{__FILE__}:#{__LINE__})"
eval("__FILE__", binding).should == "(eval at #{__FILE__}:#{__LINE__})"
eval("__FILE__", binding, "success").should == "success"
eval("eval '__FILE__', binding").should == "(eval at (eval at #{__FILE__}:#{__LINE__}):1)"
eval("eval '__FILE__', binding", binding).should == "(eval at (eval at #{__FILE__}:#{__LINE__}):1)"
eval("eval '__FILE__', binding", binding, 'success').should == "(eval at success:1)"
eval("eval '__FILE__', binding, 'success'", binding).should == 'success'
end

it 'uses (eval at __FILE__:__LINE__) for __FILE__ and 1 for __LINE__ with a binding argument' do
eval("[__FILE__, __LINE__]", binding).should == ["(eval at #{__FILE__}:#{__LINE__})", 1]
end
end
# Found via Rubinius bug github:#149
it "does not alter the value of __FILE__ in the binding" do
first_time = EvalSpecs.call_eval
Expand Down
6 changes: 6 additions & 0 deletions spec/ruby/core/module/shared/class_eval.rb
Expand Up @@ -52,6 +52,12 @@ def foo
ModuleSpecs.send(@method, "[__FILE__, __LINE__]", "test", 102).should == ["test", 102]
end

ruby_version_is "3.3" do
it "uses the caller location as default filename" do
ModuleSpecs.send(@method, "[__FILE__, __LINE__]").should == ["(eval at #{__FILE__}:#{__LINE__})", 1]
end
end

it "converts a non-string filename to a string using to_str" do
(file = mock(__FILE__)).should_receive(:to_str).and_return(__FILE__)
ModuleSpecs.send(@method, "1+1", file)
Expand Down
31 changes: 23 additions & 8 deletions spec/ruby/core/tracepoint/path_spec.rb
Expand Up @@ -13,14 +13,29 @@
path.should == "#{__FILE__}"
end

it 'equals (eval) inside an eval for :end event' do
path = nil
TracePoint.new(:end) { |tp|
next unless TracePointSpec.target_thread?
path = tp.path
}.enable do
eval("module TracePointSpec; end")
ruby_version_is ""..."3.3" do
it 'equals (eval) inside an eval for :end event' do
path = nil
TracePoint.new(:end) { |tp|
next unless TracePointSpec.target_thread?
path = tp.path
}.enable do
eval("module TracePointSpec; end")
end
path.should == '(eval)'
end
end

ruby_version_is "3.3" do
it 'equals "(eval at __FILE__:__LINE__)" inside an eval for :end event' do
path = nil
TracePoint.new(:end) { |tp|
next unless TracePointSpec.target_thread?
path = tp.path
}.enable do
eval("module TracePointSpec; end")
end
path.should == "(eval at #{__FILE__}:#{__LINE__ - 2})"
end
path.should == '(eval)'
end
end
2 changes: 1 addition & 1 deletion spec/ruby/language/defined_spec.rb
Expand Up @@ -203,7 +203,7 @@
it "warns about the void context when parsing it" do
-> {
eval "defined?(DefinedSpecs.side_effects / 2); 42"
}.should complain("(eval):1: warning: possibly useless use of defined? in void context\n", verbose: true)
}.should complain(/warning: possibly useless use of defined\? in void context/, verbose: true)
end
end
end
Expand Down
12 changes: 10 additions & 2 deletions spec/ruby/language/file_spec.rb
Expand Up @@ -7,8 +7,16 @@
-> { eval("__FILE__ = 1") }.should raise_error(SyntaxError)
end

it "equals (eval) inside an eval" do
eval("__FILE__").should == "(eval)"
ruby_version_is ""..."3.3" do
it "equals (eval) inside an eval" do
eval("__FILE__").should == "(eval)"
end
end

ruby_version_is "3.3" do
it "equals (eval at __FILE__:__LINE__) inside an eval" do
eval("__FILE__").should == "(eval at #{__FILE__}:#{__LINE__})"
end
end
end

Expand Down
4 changes: 3 additions & 1 deletion test/-ext-/debug/test_profile_frames.rb
Expand Up @@ -14,6 +14,8 @@ def self.corge(block)
end

class Sample2
EVAL_LINE = __LINE__ + 3

def baz(block)
instance_eval "def zab(block) block.call end"
[self, zab(block)]
Expand Down Expand Up @@ -112,7 +114,7 @@ def test_profile_frames
"SampleClassForTestProfileFrames#foo",
"TestProfileFrames#test_profile_frames",
]
paths = [ nil, file=__FILE__, "(eval)", file, file, file, file, file, file, nil ]
paths = [ nil, file=__FILE__, "(eval at #{__FILE__}:#{SampleClassForTestProfileFrames::Sample2::EVAL_LINE})", file, file, file, file, file, file, nil ]
absolute_paths = [ "<cfunc>", file, nil, file, file, file, file, file, file, nil ]

assert_equal(labels.size, frames.size)
Expand Down
4 changes: 2 additions & 2 deletions test/ruby/test_beginendblock.rb
Expand Up @@ -45,9 +45,9 @@ def end1
end

def test_endblockwarn_in_eval
assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", [], ['(eval):2: warning: END in method; use at_exit'])
assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", [], ['test.rb:1: warning: END in method; use at_exit'])
begin;
eval <<-EOE
eval <<-EOE, nil, "test.rb", 0
def end2
END {}
end
Expand Down
4 changes: 2 additions & 2 deletions test/ruby/test_eval.rb
Expand Up @@ -547,8 +547,8 @@ def test_eval_location_fstring
end

def test_eval_location_binding
assert_equal(['(eval)', 1], eval("[__FILE__, __LINE__]", nil))
assert_equal(['(eval)', 1], eval("[__FILE__, __LINE__]", binding))
assert_equal(["(eval at #{__FILE__}:#{__LINE__})", 1], eval("[__FILE__, __LINE__]", nil))
assert_equal(["(eval at #{__FILE__}:#{__LINE__})", 1], eval("[__FILE__, __LINE__]", binding))
assert_equal(['foo', 1], eval("[__FILE__, __LINE__]", nil, 'foo'))
assert_equal(['foo', 1], eval("[__FILE__, __LINE__]", binding, 'foo'))
assert_equal(['foo', 2], eval("[__FILE__, __LINE__]", nil, 'foo', 2))
Expand Down
2 changes: 1 addition & 1 deletion test/ruby/test_exception.rb
Expand Up @@ -1310,7 +1310,7 @@ def backtrace; :backtrace; end

def test_backtrace_in_eval
bug = '[ruby-core:84434] [Bug #14229]'
assert_in_out_err(['-e', 'eval("raise")'], "", [], /^\(eval\):1:/, bug)
assert_in_out_err(['-e', 'eval("raise")'], "", [], /^\(eval at .*\):1:/, bug)
end

def test_full_message
Expand Down
38 changes: 19 additions & 19 deletions test/ruby/test_method.rb
Expand Up @@ -1431,25 +1431,25 @@ def test_define_method_with_symbol
end

def test_argument_error_location
body = <<-'END_OF_BODY'
eval <<-'EOS'
$line_lambda = __LINE__; $f = lambda do
_x = 1
end
$line_method = __LINE__; def foo
_x = 1
end
begin
$f.call(1)
rescue ArgumentError => e
assert_equal "(eval):#{$line_lambda.to_s}:in `block in <main>'", e.backtrace.first
end
begin
foo(1)
rescue ArgumentError => e
assert_equal "(eval):#{$line_method}:in `foo'", e.backtrace.first
end
EOS
body = <<~'END_OF_BODY'
eval <<~'EOS', nil, "main.rb"
$line_lambda = __LINE__; $f = lambda do
_x = 1
end
$line_method = __LINE__; def foo
_x = 1
end
begin
$f.call(1)
rescue ArgumentError => e
assert_equal "main.rb:#{$line_lambda}:in `block in <main>'", e.backtrace.first
end
begin
foo(1)
rescue ArgumentError => e
assert_equal "main.rb:#{$line_method}:in `foo'", e.backtrace.first
end
EOS
END_OF_BODY

assert_separately [], body
Expand Down
42 changes: 33 additions & 9 deletions vm_eval.c
Expand Up @@ -1613,7 +1613,23 @@ rb_each(VALUE obj)
return rb_call(obj, idEach, 0, 0, CALL_FCALL);
}

static VALUE eval_default_path;
static VALUE eval_default_path = Qfalse;

static VALUE
get_eval_default_path(void)
{
int location_lineno;
VALUE location_path = rb_source_location(&location_lineno);
if (!NIL_P(location_path)) {
return rb_fstring(rb_sprintf("(eval at %"PRIsVALUE":%d)", location_path, location_lineno));
}

if (!eval_default_path) {
eval_default_path = rb_fstring_lit("(eval)");
rb_gc_register_mark_object(eval_default_path);
}
return eval_default_path;
}

static const rb_iseq_t *
eval_make_iseq(VALUE src, VALUE fname, int line, const rb_binding_t *bind,
Expand Down Expand Up @@ -1653,11 +1669,7 @@ eval_make_iseq(VALUE src, VALUE fname, int line, const rb_binding_t *bind,
if (!NIL_P(fname)) fname = rb_fstring(fname);
}
else {
if (!eval_default_path) {
eval_default_path = rb_fstring_lit("(eval)");
rb_gc_register_mark_object(eval_default_path);
}
fname = eval_default_path;
fname = get_eval_default_path();
coverage_enabled = FALSE;
}

Expand Down Expand Up @@ -1969,7 +1981,7 @@ specific_eval(int argc, const VALUE *argv, VALUE self, int singleton, int kw_spl
return yield_under(self, singleton, 1, &self, kw_splat);
}
else {
VALUE file = Qundef;
VALUE file = Qnil;
int line = 1;
VALUE code;

Expand All @@ -1982,6 +1994,11 @@ specific_eval(int argc, const VALUE *argv, VALUE self, int singleton, int kw_spl
file = argv[1];
if (!NIL_P(file)) StringValue(file);
}

if (NIL_P(file)) {
file = get_eval_default_path();
}

return eval_under(self, singleton, code, file, line);
}
}
Expand Down Expand Up @@ -2508,9 +2525,16 @@ rb_current_realfilepath(void)
if (path == eval_default_path) {
return Qnil;
}
else {
return path;

// [Feature #19755] implicit eval location is "(eval at #{__FILE__}:#{__LINE__})"
if (RSTRING_LEN(path) > 9) {
if (RSTRING_PTR(path)[RSTRING_LEN(path) - 1] == ')' &&
memcmp(RSTRING_PTR(path), "(eval at ", 9) == 0) {
return Qnil;
}
}

return path;
}
return Qnil;
}
Expand Down

0 comments on commit 43a5c19

Please sign in to comment.