diff options
author | Jeremy Evans <[email protected]> | 2023-10-25 12:49:28 -0700 |
---|---|---|
committer | Jeremy Evans <[email protected]> | 2023-12-07 09:30:36 -0800 |
commit | 3a88de3ca73052809f5c0bfb4ef8cd435b29ae5f (patch) | |
tree | d2b80103106391194d69322940bb43a8ca00e177 | |
parent | 0c3593b6573b4186c980fb4ea7635bf95261c749 (diff) |
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]
-rw-r--r-- | test/ruby/test_syntax.rb | 48 | ||||
-rw-r--r-- | vm_insnhelper.c | 11 |
2 files changed, 58 insertions, 1 deletions
diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index fe0785a754..c274e333b1 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -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 diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 8e1ac27c51..50ca9902fd 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -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; |