Support eval "return" at toplevel

Since Ruby 2.4, `return` is supported at toplevel.  This makes `eval "return"`
also supported at toplevel.

This mostly uses the same tests as direct `return` at toplevel, with a couple
differences:

`END {return if false}` is a SyntaxError, but `END {eval "return" if false}`
is not an error since the eval is never executed. `END {return}` is a
SyntaxError, but `END {eval "return"}` is a LocalJumpError.

The following is a SyntaxError:

```ruby
class X
nil&defined?0--begin e=no_method_error(); return; 0;end
end
```

However, the following is not, because the eval is never executed:

```ruby
class X
nil&defined?0--begin e=no_method_error(); eval "return"; 0;end
end
```

Fixes [Bug #19779]
This commit is contained in:
Jeremy Evans 2023-10-25 12:49:28 -07:00
parent 0c3593b657
commit 3a88de3ca7
2 changed files with 58 additions and 1 deletions

View File

@ -1405,6 +1405,54 @@ eom
end
end
def test_eval_return_toplevel
feature4840 = '[ruby-core:36785] [Feature #4840]'
line = __LINE__+2
code = "#{<<~"begin;"}#{<<~'end;'}"
begin;
eval "return"; raise
begin eval "return"; rescue SystemExit; exit false; end
begin eval "return"; ensure puts "ensured"; end #=> ensured
begin ensure eval "return"; end
begin raise; ensure; eval "return"; end
begin raise; rescue; eval "return"; end
eval "return false"; raise
eval "return 1"; raise
"#{eval "return"}"
raise((eval "return"; "should not raise"))
begin raise; ensure eval "return"; end; self
begin raise; ensure eval "return"; end and self
eval "return puts('ignored')" #=> ignored
BEGIN {eval "return"}
end;
.split(/\n/).map {|s|[(line+=1), *s.split(/#=> /, 2)]}
failed = proc do |n, s|
RubyVM::InstructionSequence.compile(s, __FILE__, nil, n).disasm
end
Tempfile.create(%w"test_return_ .rb") do |lib|
lib.close
args = %W[-W0 -r#{lib.path}]
all_assertions_foreach(feature4840, *[:main, :lib].product([:class, :top], code)) do |main, klass, (n, s, *ex)|
if klass == :class
s = "class X; #{s}; end"
if main == :main
assert_in_out_err(%[-W0], s, ex, /return/, proc {failed[n, s]}, success: false)
else
File.write(lib, s)
assert_in_out_err(args, "", ex, /return/, proc {failed[n, s]}, success: false)
end
else
if main == :main
assert_in_out_err(%[-W0], s, ex, [], proc {failed[n, s]}, success: true)
else
File.write(lib, s)
assert_in_out_err(args, "", ex, [], proc {failed[n, s]}, success: true)
end
end
end
end
end
def test_return_toplevel_with_argument
assert_warn(/argument of top-level return is ignored/) {eval("return 1")}
end

View File

@ -1809,7 +1809,16 @@ vm_throw_start(const rb_execution_context_t *ec, rb_control_frame_t *const reg_c
}
}
break;
case ISEQ_TYPE_EVAL:
case ISEQ_TYPE_EVAL: {
const rb_iseq_t *is = escape_cfp->iseq;
enum rb_iseq_type t = ISEQ_BODY(is)->type;
while (t == ISEQ_TYPE_RESCUE || t == ISEQ_TYPE_ENSURE || t == ISEQ_TYPE_EVAL) {
if (!(is = ISEQ_BODY(is)->parent_iseq)) break;
t = ISEQ_BODY(is)->type;
}
toplevel = t == ISEQ_TYPE_TOP || t == ISEQ_TYPE_MAIN;
break;
}
case ISEQ_TYPE_CLASS:
toplevel = 0;
break;