summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Evans <[email protected]>2023-10-25 12:49:28 -0700
committerJeremy Evans <[email protected]>2023-12-07 09:30:36 -0800
commit3a88de3ca73052809f5c0bfb4ef8cd435b29ae5f (patch)
treed2b80103106391194d69322940bb43a8ca00e177
parent0c3593b6573b4186c980fb4ea7635bf95261c749 (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.rb48
-rw-r--r--vm_insnhelper.c11
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;