summaryrefslogtreecommitdiff
path: root/test/ruby/test_iseq.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/ruby/test_iseq.rb')
-rw-r--r--test/ruby/test_iseq.rb1045
1 files changed, 1045 insertions, 0 deletions
diff --git a/test/ruby/test_iseq.rb b/test/ruby/test_iseq.rb
new file mode 100644
index 0000000000..b4760dc412
--- /dev/null
+++ b/test/ruby/test_iseq.rb
@@ -0,0 +1,1045 @@
+require 'test/unit'
+require 'tempfile'
+
+class TestISeq < Test::Unit::TestCase
+ ISeq = RubyVM::InstructionSequence
+
+ def test_no_linenum
+ bug5894 = '[ruby-dev:45130]'
+ assert_normal_exit('p RubyVM::InstructionSequence.compile("1", "mac", "", 0).to_a', bug5894)
+ end
+
+ def compile(src, line = nil, opt = nil)
+ unless line
+ line = caller_locations(1).first.lineno
+ end
+ EnvUtil.suppress_warning do
+ ISeq.new(src, __FILE__, __FILE__, line, opt)
+ end
+ end
+
+ def lines src, lines = nil
+ body = compile(src, lines).to_a[13]
+ body.find_all{|e| e.kind_of? Integer}
+ end
+
+ def test_allocate
+ assert_raise(TypeError) {ISeq.allocate}
+ end
+
+ def test_to_a_lines
+ assert_equal [__LINE__+1, __LINE__+2, __LINE__+4], lines(<<-EOS, __LINE__+1)
+ p __LINE__ # 1
+ p __LINE__ # 2
+ # 3
+ p __LINE__ # 4
+ EOS
+
+ assert_equal [__LINE__+2, __LINE__+4], lines(<<-EOS, __LINE__+1)
+ # 1
+ p __LINE__ # 2
+ # 3
+ p __LINE__ # 4
+ # 5
+ EOS
+
+ assert_equal [__LINE__+3, __LINE__+4, __LINE__+7, __LINE__+9], lines(<<~EOS, __LINE__+1)
+ 1 # should be optimized out
+ 2 # should be optimized out
+ p __LINE__ # 3
+ p __LINE__ # 4
+ 5 # should be optimized out
+ 6 # should be optimized out
+ p __LINE__ # 7
+ 8 # should be optimized out
+ 9
+ EOS
+ end
+
+ def test_unsupported_type
+ ary = compile("p").to_a
+ ary[9] = :foobar
+ assert_raise_with_message(TypeError, /:foobar/) {ISeq.load(ary)}
+ end if defined?(RubyVM::InstructionSequence.load)
+
+ def test_loaded_cdhash_mark
+ iseq = compile(<<-'end;', __LINE__+1)
+ def bug(kw)
+ case kw
+ when "false" then false
+ when "true" then true
+ when "nil" then nil
+ else raise("unhandled argument: #{kw.inspect}")
+ end
+ end
+ end;
+ assert_separately([], <<-"end;")
+ iseq = #{iseq.to_a.inspect}
+ RubyVM::InstructionSequence.load(iseq).eval
+ assert_equal(false, bug("false"))
+ GC.start
+ assert_equal(false, bug("false"))
+ end;
+ end if defined?(RubyVM::InstructionSequence.load)
+
+ def test_cdhash_after_roundtrip
+ # CDHASH was not built properly when loading from binary and
+ # was causing opt_case_dispatch to clobber its stack canary
+ # for its "leaf" instruction attribute.
+ iseq = compile(<<~EOF, __LINE__+1)
+ case Class.new(String).new("foo")
+ when "foo"
+ 42
+ end
+ EOF
+ assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval)
+ end
+
+ def test_forwardable
+ iseq = compile(<<~EOF, __LINE__+1)
+ Class.new {
+ def bar(a, b); a + b; end
+ def foo(...); bar(...); end
+ }
+ EOF
+ assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval.new.foo(40, 2))
+ end
+
+ def test_super_with_block
+ iseq = compile(<<~EOF, __LINE__+1)
+ def (Object.new).touch(*) # :nodoc:
+ foo { super }
+ end
+ 42
+ EOF
+ assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval)
+ end
+
+ def test_super_with_block_hash_0
+ iseq = compile(<<~EOF, __LINE__+1)
+ # [Bug #18250] `req` specifically cause `Assertion failed: (key != 0), function hash_table_raw_insert`
+ def (Object.new).touch(req, *)
+ foo { super }
+ end
+ 42
+ EOF
+ assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval)
+ end
+
+ def test_super_with_block_and_kwrest
+ iseq = compile(<<~EOF, __LINE__+1)
+ def (Object.new).touch(**) # :nodoc:
+ foo { super }
+ end
+ 42
+ EOF
+ assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval)
+ end
+
+ def test_lambda_with_ractor_roundtrip
+ iseq = compile(<<~EOF, __LINE__+1)
+ x = 42
+ y = Ractor.shareable_lambda{x}
+ y.call
+ EOF
+ assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval)
+ end
+
+ def test_super_with_anonymous_block
+ iseq = compile(<<~EOF, __LINE__+1)
+ def (Object.new).touch(&) # :nodoc:
+ foo { super }
+ end
+ 42
+ EOF
+ assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval)
+ end
+
+ def test_ractor_unshareable_outer_variable
+ name = "\u{2603 26a1}"
+ assert_raise_with_message(Ractor::IsolationError, /\(#{name}\)/) do
+ eval("#{name} = nil; Ractor.shareable_proc{#{name} = nil}")
+ end
+
+ assert_raise_with_message(Ractor::IsolationError, /\'#{name}\'/) do
+ eval("#{name} = []; Ractor.shareable_proc{#{name}}")
+ end
+
+ obj = Object.new
+ def obj.foo(*) Ractor.shareable_proc{super} end
+ assert_raise_with_message(Ractor::IsolationError, /cannot make a shareable Proc because it can refer unshareable object \[\]/) do
+ obj.foo(*[])
+ end
+ end
+
+ def test_ractor_shareable_value_frozen_core
+ iseq = RubyVM::InstructionSequence.compile(<<~'RUBY')
+ # shareable_constant_value: literal
+ REGEX = /#{}/ # [Bug #20569]
+ RUBY
+ assert_includes iseq_to_binary(iseq), "REGEX".b
+ end
+
+ def test_disasm_encoding
+ src = +"\u{3042} = 1; \u{3042}; \u{3043}"
+ asm = compile(src).disasm
+ assert_equal(src.encoding, asm.encoding)
+ assert_predicate(asm, :valid_encoding?)
+ src.encode!(Encoding::Shift_JIS)
+ asm = compile(src).disasm
+ assert_equal(src.encoding, asm.encoding)
+ assert_predicate(asm, :valid_encoding?)
+
+ obj = Object.new
+ name = "\u{2603 26a1}"
+ obj.instance_eval("def #{name}; tap {}; end")
+ assert_include(RubyVM::InstructionSequence.of(obj.method(name)).disasm, name)
+ end
+
+ def test_compile_file_encoding
+ Tempfile.create(%w"test_iseq .rb") do |f|
+ f.puts "{ '\u00de' => 'Th', '\u00df' => 'ss', '\u00e0' => 'a' }"
+ f.close
+
+ EnvUtil.with_default_external(Encoding::US_ASCII) do
+ assert_warn('') {
+ load f.path
+ }
+ assert_nothing_raised(SyntaxError) {
+ RubyVM::InstructionSequence.compile_file(f.path)
+ }
+ end
+ end
+ end
+
+ def test_compile_file_options
+ Tempfile.create(%w"test_iseq .rb") do |f|
+ f.puts('_ = "test"')
+ f.close
+ iseq = RubyVM::InstructionSequence.compile_file(f.path, { frozen_string_literal: false })
+ refute_predicate iseq.eval, :frozen?
+
+ iseq = RubyVM::InstructionSequence.compile_file(f.path, { frozen_string_literal: true })
+ assert_predicate iseq.eval, :frozen?
+ end
+ end
+
+ def test_compile_options
+ iseq = RubyVM::InstructionSequence.compile("'test'", nil, nil, nil, { frozen_string_literal: false })
+ refute_predicate iseq.eval, :frozen?
+
+ iseq = RubyVM::InstructionSequence.compile("'test'", nil, nil, nil, { frozen_string_literal: true })
+ assert_predicate iseq.eval, :frozen?
+ end
+
+ LINE_BEFORE_METHOD = __LINE__
+ def method_test_line_trace
+
+ _a = 1
+
+ _b = 2
+
+ end
+
+ def test_line_trace
+ iseq = compile(<<~EOF, __LINE__+1)
+ a = 1
+ b = 2
+ c = 3
+ # d = 4
+ e = 5
+ # f = 6
+ g = 7
+ EOF
+
+ assert_equal([1, 2, 3, 5, 7], iseq.line_trace_all)
+ iseq.line_trace_specify(1, true) # line 2
+ iseq.line_trace_specify(3, true) # line 5
+
+ result = []
+ TracePoint.new(:specified_line){|tp|
+ result << tp.lineno
+ }.enable{
+ iseq.eval
+ }
+ assert_equal([2, 5], result)
+
+ iseq = ISeq.of(self.class.instance_method(:method_test_line_trace))
+ assert_equal([LINE_BEFORE_METHOD + 3, LINE_BEFORE_METHOD + 5], iseq.line_trace_all)
+ end if false # TODO: now, it is only for C APIs.
+
+ LINE_OF_HERE = __LINE__
+ def test_location
+ iseq = ISeq.of(method(:test_location))
+
+ assert_equal(__FILE__, iseq.path)
+ assert_match(/#{__FILE__}/, iseq.absolute_path)
+ assert_equal("test_location", iseq.label)
+ assert_equal("test_location", iseq.base_label)
+ assert_equal(LINE_OF_HERE+1, iseq.first_lineno)
+
+ line = __LINE__
+ iseq = ISeq.of(Proc.new{})
+ assert_equal(__FILE__, iseq.path)
+ assert_match(/#{__FILE__}/, iseq.absolute_path)
+ assert_equal("test_location", iseq.base_label)
+ assert_equal("block in test_location", iseq.label)
+ assert_equal(line+1, iseq.first_lineno)
+ end
+
+ def test_label_fstring
+ c = Class.new{ def foobar() end }
+
+ a, b = eval("# encoding: us-ascii\n'foobar'.freeze"),
+ ISeq.of(c.instance_method(:foobar)).label
+ assert_same a, b
+ end
+
+ def test_disable_opt
+ src = "a['foo'] = a['bar']; 'a'.freeze"
+ body= compile(src, __LINE__, false).to_a[13]
+ body.each{|insn|
+ next unless Array === insn
+ op = insn.first
+ assert(!op.to_s.match(/^opt_/), "#{op}")
+ }
+ end
+
+ def test_invalid_source
+ bug11159 = '[ruby-core:69219] [Bug #11159]'
+ assert_raise(TypeError, bug11159) {compile(nil)}
+ assert_raise(TypeError, bug11159) {compile(:foo)}
+ assert_raise(TypeError, bug11159) {compile(1)}
+ end
+
+ def test_invalid_source_no_memory_leak
+ # [Bug #21394]
+ assert_no_memory_leak(["-rtempfile"], "#{<<-"begin;"}", "#{<<-'end;'}", rss: true)
+ code = proc do |t|
+ RubyVM::InstructionSequence.new(nil)
+ rescue TypeError
+ else
+ raise "TypeError was not raised during RubyVM::InstructionSequence.new"
+ end
+
+ 10.times(&code)
+ begin;
+ 1_000_000.times(&code)
+ end;
+
+ # [Bug #21394]
+ # RubyVM::InstructionSequence.new calls rb_io_path, which dups the string
+ # and can leak memory if the dup raises
+ assert_no_memory_leak(["-rtempfile"], "#{<<-"begin;"}", "#{<<-'end;'}", rss: true)
+ MyError = Class.new(StandardError)
+ String.prepend(Module.new do
+ def initialize_dup(_)
+ if $raise_on_dup
+ raise MyError
+ else
+ super
+ end
+ end
+ end)
+
+ code = proc do |t|
+ Tempfile.create do |f|
+ $raise_on_dup = true
+ t.times do
+ RubyVM::InstructionSequence.new(f)
+ rescue MyError
+ else
+ raise "MyError was not raised during RubyVM::InstructionSequence.new"
+ end
+ ensure
+ $raise_on_dup = false
+ end
+ end
+
+ code.call(100)
+ begin;
+ code.call(1_000_000)
+ end;
+ end
+
+ def test_frozen_string_literal_compile_option
+ $f = 'f'
+ line = __LINE__ + 2
+ code = <<-'EOS'
+ ['foo', 'foo', "#{$f}foo", "#{'foo'}"]
+ EOS
+ s1, s2, s3, s4 = compile(code, line, {frozen_string_literal: true}).eval
+ assert_predicate(s1, :frozen?)
+ assert_predicate(s2, :frozen?)
+ assert_not_predicate(s3, :frozen?)
+ assert_not_predicate(s4, :frozen?)
+ end
+
+ def test_frozen_string_literal_compile_option_file
+ Tempfile.create(%w[fsl .rb]) do |f|
+ f.write("['foo', 'foo', \"\#{$f}foo\", \"\#{'foo'}\"]\n")
+ f.flush
+ $f = 'f'
+ s1, s2, s3, s4 = RubyVM::InstructionSequence
+ .compile_file(f.path, frozen_string_literal: true).eval
+ assert_predicate(s1, :frozen?)
+ assert_predicate(s2, :frozen?)
+ assert_not_predicate(s3, :frozen?)
+ assert_not_predicate(s4, :frozen?)
+ end
+ end
+
+ # Safe call chain is not optimized when Coverage is running.
+ # So we can test it only when Coverage is not running.
+ def test_safe_call_chain
+ src = "a&.a&.a&.a&.a&.a"
+ body = compile(src, __LINE__, {peephole_optimization: true}).to_a[13]
+ labels = body.select {|op, arg| op == :branchnil}.map {|op, arg| arg}
+ assert_equal(1, labels.uniq.size)
+ end if (!defined?(Coverage) || !Coverage.running?)
+
+ def test_parent_iseq_mark
+ assert_separately([], <<-'end;', timeout: 20)
+ ->{
+ ->{
+ ->{
+ eval <<-EOS
+ class Segfault
+ define_method :segfault do
+ x = nil
+ GC.disable
+ 1000.times do |n|
+ n.times do
+ x = (foo rescue $!).local_variables
+ end
+ GC.start
+ end
+ x
+ end
+ end
+ EOS
+ }.call
+ }.call
+ }.call
+ at_exit { assert_equal([:n, :x], Segfault.new.segfault.sort) }
+ end;
+ end
+
+ def test_syntax_error_message
+ feature11951 = '[Feature #11951]'
+
+ src, line = <<-'end;', __LINE__+1
+ def x@;end
+ def y@;end
+ end;
+ e1 = e2 = nil
+ m1 = EnvUtil.verbose_warning do
+ e1 = assert_raise(SyntaxError) do
+ eval(src, nil, __FILE__, line)
+ end
+ end
+ m2 = EnvUtil.verbose_warning do
+ e2 = assert_raise(SyntaxError) do
+ ISeq.new(src, __FILE__, __FILE__, line)
+ end
+ end
+ assert_equal([m1, e1.message], [m2, e2.message], feature11951)
+
+ if e1.message.lines[0] == "#{__FILE__}:#{line}: syntax errors found\n"
+ # Prism lays out the error messages in line with the source, so the
+ # following assertions do not make sense in that context.
+ else
+ message = e1.message.each_line
+ message.with_index(1) do |line, i|
+ next if /^ / =~ line
+ assert_send([line, :start_with?, __FILE__],
+ proc {message.map {|l, j| (i == j ? ">" : " ") + l}.join("")})
+ end
+ end
+ end
+
+ # [Bug #19173]
+ def test_compile_error
+ assert_raise SyntaxError do
+ RubyVM::InstructionSequence.compile 'using Module.new; yield'
+ end
+ end
+
+ def test_compile_file_error
+ Tempfile.create(%w"test_iseq .rb") do |f|
+ f.puts "end"
+ f.close
+ path = f.path
+ assert_in_out_err(%W[- #{path}], "#{<<-"begin;"}\n#{<<-"end;"}", /unexpected 'end'/, [], success: true)
+ begin;
+ path = ARGV[0]
+ begin
+ RubyVM::InstructionSequence.compile_file(path)
+ rescue SyntaxError => e
+ puts e.message
+ end
+ end;
+ end
+ end
+
+ def test_translate_by_object
+ assert_separately([], <<-"end;")
+ class Object
+ def translate
+ end
+ end
+ assert_equal(0, eval("0"))
+ end;
+ end
+
+ def test_inspect
+ %W[foo \u{30d1 30b9}].each do |name|
+ assert_match(/@#{name}/, ISeq.compile("", name).inspect, name)
+ m = ISeq.compile("class TestISeq::Inspect; def #{name}; end; instance_method(:#{name}); end").eval
+ assert_match(/:#{name}@/, ISeq.of(m).inspect, name)
+ end
+ end
+
+ def anon_star(*); end
+
+ def test_anon_rest_param_in_disasm
+ iseq = RubyVM::InstructionSequence.of(method(:anon_star))
+ param_names = iseq.to_a[iseq.to_a.index(:method) + 1]
+ assert_equal [:*], param_names
+ end
+
+ def anon_keyrest(**); end
+
+ def test_anon_keyrest_param_in_disasm
+ iseq = RubyVM::InstructionSequence.of(method(:anon_keyrest))
+ param_names = iseq.to_a[iseq.to_a.index(:method) + 1]
+ assert_equal [:**], param_names
+ end
+
+ def anon_block(&); end
+
+ def test_anon_block_param_in_disasm
+ iseq = RubyVM::InstructionSequence.of(method(:anon_block))
+ param_names = iseq.to_a[iseq.to_a.index(:method) + 1]
+ assert_equal [:&], param_names
+ end
+
+ def strip_lineno(source)
+ source.gsub(/^.*?: /, "")
+ end
+
+ def sample_iseq
+ ISeq.compile(strip_lineno(<<-EOS))
+ 1: class C
+ 2: def foo
+ 3: begin
+ 4: rescue
+ 5: p :rescue
+ 6: ensure
+ 7: p :ensure
+ 8: end
+ 9: end
+ 10: def bar
+ 11: 1.times{
+ 12: 2.times{
+ 13: }
+ 14: }
+ 15: end
+ 16: end
+ 17: class D < C
+ 18: end
+ EOS
+ end
+
+ def test_each_child
+ iseq = sample_iseq
+
+ collect_iseq = lambda{|iseq|
+ iseqs = []
+ iseq.each_child{|child_iseq|
+ iseqs << collect_iseq.call(child_iseq)
+ }
+ ["#{iseq.label}@#{iseq.first_lineno}", *iseqs.sort_by{|k, *| k}]
+ }
+
+ expected = ["<compiled>@1",
+ ["<class:C>@1",
+ ["bar@10", ["block in bar@11",
+ ["block (2 levels) in bar@12"]]],
+ ["foo@2", ["ensure in foo@7"],
+ ["rescue in foo@4"]]],
+ ["<class:D>@17"]]
+
+ assert_equal expected, collect_iseq.call(iseq)
+ end
+
+ def test_trace_points
+ collect_iseq = lambda{|iseq|
+ iseqs = []
+ iseq.each_child{|child_iseq|
+ iseqs << collect_iseq.call(child_iseq)
+ }
+ [["#{iseq.label}@#{iseq.first_lineno}", iseq.trace_points], *iseqs.sort_by{|k, *| k}]
+ }
+ assert_equal [["<compiled>@1", [[1, :line],
+ [17, :line]]],
+ [["<class:C>@1", [[1, :class],
+ [2, :line],
+ [10, :line],
+ [16, :end]]],
+ [["bar@10", [[10, :call],
+ [11, :line],
+ [15, :return]]],
+ [["block in bar@11", [[11, :b_call],
+ [12, :line],
+ [14, :b_return]]],
+ [["block (2 levels) in bar@12", [[12, :b_call],
+ [13, :b_return]]]]]],
+ [["foo@2", [[2, :call],
+ [4, :line],
+ [7, :line],
+ [9, :return]]],
+ [["ensure in foo@7", [[7, :line]]]],
+ [["rescue in foo@4", [[5, :line],
+ [5, :rescue]]]]]],
+ [["<class:D>@17", [[17, :class],
+ [18, :end]]]]], collect_iseq.call(sample_iseq)
+ end
+
+ def test_empty_iseq_lineno
+ iseq = ISeq.compile(<<-EOS)
+ # 1
+ # 2
+ def foo # line 3 empty method
+ end # line 4
+ 1.time do # line 5 empty block
+ end # line 6
+ class C # line 7 empty class
+ end
+ EOS
+
+ iseq.each_child{|ci|
+ ary = ci.to_a
+ type = ary[9]
+ name = ary[5]
+ line = ary[13].first
+ case type
+ when :method
+ assert_equal "foo", name
+ assert_equal 3, line
+ when :class
+ assert_equal '<class:C>', name
+ assert_equal 7, line
+ when :block
+ assert_equal 'block in <compiled>', name
+ assert_equal 5, line
+ else
+ raise "unknown ary: " + ary.inspect
+ end
+ }
+ end
+
+ def hexdump(bin)
+ bin.unpack1("H*").gsub(/.{1,32}/) {|s|
+ "#{'%04x:' % $~.begin(0)}#{s.gsub(/../, " \\&").tap{|_|_[24]&&="-"}}\n"
+ }
+ end
+
+ def iseq_to_binary(iseq)
+ iseq.to_binary
+ rescue RuntimeError => e
+ omit e.message if /compile with coverage/ =~ e.message
+ raise
+ end
+
+ def assert_iseq_to_binary(code, mesg = nil)
+ iseq = RubyVM::InstructionSequence.compile(code)
+ bin = assert_nothing_raised(mesg) do
+ iseq_to_binary(iseq)
+ end
+ 10.times do
+ bin2 = iseq_to_binary(iseq)
+ assert_equal(bin, bin2, message(mesg) {diff hexdump(bin), hexdump(bin2)})
+ end
+ iseq2 = RubyVM::InstructionSequence.load_from_binary(bin)
+ a1 = iseq.to_a
+ a2 = iseq2.to_a
+ assert_equal(a1, a2, message(mesg) {diff iseq.disassemble, iseq2.disassemble})
+ if iseq2.script_lines
+ assert_kind_of(Array, iseq2.script_lines)
+ else
+ assert_nil(iseq2.script_lines)
+ end
+ iseq2
+ end
+
+ def test_to_binary_with_hidden_local_variables
+ assert_iseq_to_binary("for _foo in bar; end")
+
+ bin = iseq_to_binary(RubyVM::InstructionSequence.compile(<<-RUBY))
+ Object.new.instance_eval do
+ a = []
+ def self.bar; [1] end
+ for foo in bar
+ a << (foo * 2)
+ end
+ a
+ end
+ RUBY
+ v = RubyVM::InstructionSequence.load_from_binary(bin).eval
+ assert_equal([2], v)
+ end
+
+ def test_to_binary_with_objects
+ assert_iseq_to_binary("[]"+100.times.map{|i|"<</#{i}/"}.join)
+ assert_iseq_to_binary("@x ||= (1..2)")
+ end
+
+ def test_to_binary_pattern_matching
+ code = "case foo; in []; end"
+ iseq = compile(code)
+ assert_include(iseq.disasm, "TypeError")
+ assert_include(iseq.disasm, "NoMatchingPatternError")
+ EnvUtil.suppress_warning do
+ assert_iseq_to_binary(code, "[Feature #14912]")
+ end
+ end
+
+ def test_to_binary_dumps_nokey
+ iseq = assert_iseq_to_binary(<<-RUBY)
+ o = Object.new
+ class << o
+ def foo(**nil); end
+ end
+ o
+ RUBY
+ assert_equal([[:nokey]], iseq.eval.singleton_method(:foo).parameters)
+ end
+
+ def test_to_binary_dumps_noblock
+ iseq = assert_iseq_to_binary(<<-RUBY)
+ o = Object.new
+ class << o
+ def foo(&nil); end
+ end
+ o
+ RUBY
+ assert_equal([[:noblock]], iseq.eval.singleton_method(:foo).parameters)
+ end
+
+ def test_to_binary_line_info
+ assert_iseq_to_binary("#{<<~"begin;"}\n#{<<~'end;'}", '[Bug #14660]').eval
+ begin;
+ class P
+ def p; end
+ def q; end
+ E = ""
+ N = "#{E}"
+ attr_reader :i
+ end
+ end;
+
+ # cleanup
+ ::Object.class_eval do
+ remove_const :P
+ end
+ end
+
+ def collect_from_binary_tracepoint_lines(tracepoint_type, filename)
+ iseq = RubyVM::InstructionSequence.compile(strip_lineno(<<-RUBY), filename)
+ class A
+ class B
+ 2.times {
+ def self.foo
+ _a = 'good day'
+ raise
+ rescue
+ 'dear reader'
+ end
+ }
+ end
+ B.foo
+ end
+ RUBY
+
+ iseq_bin = iseq_to_binary(iseq)
+ iseq = ISeq.load_from_binary(iseq_bin)
+ lines = []
+ TracePoint.new(tracepoint_type){|tp|
+ next unless tp.path == filename
+ lines << tp.lineno
+ }.enable{
+ EnvUtil.suppress_warning {iseq.eval}
+ }
+
+ lines
+ ensure
+ Object.send(:remove_const, :A) rescue nil
+ end
+
+ def test_to_binary_line_tracepoint
+ filename = "#{File.basename(__FILE__)}_#{__LINE__}"
+ lines = collect_from_binary_tracepoint_lines(:line, filename)
+
+ assert_equal [1, 2, 3, 4, 4, 12, 5, 6, 8], lines, '[Bug #14702]'
+ end
+
+ def test_to_binary_class_tracepoint
+ filename = "#{File.basename(__FILE__)}_#{__LINE__}"
+ lines = collect_from_binary_tracepoint_lines(:class, filename)
+
+ assert_equal [1, 2], lines, '[Bug #14702]'
+ end
+
+ def test_to_binary_end_tracepoint
+ filename = "#{File.basename(__FILE__)}_#{__LINE__}"
+ lines = collect_from_binary_tracepoint_lines(:end, filename)
+
+ assert_equal [11, 13], lines, '[Bug #14702]'
+ end
+
+ def test_to_binary_return_tracepoint
+ filename = "#{File.basename(__FILE__)}_#{__LINE__}"
+ lines = collect_from_binary_tracepoint_lines(:return, filename)
+
+ assert_equal [9], lines, '[Bug #14702]'
+ end
+
+ def test_to_binary_b_call_tracepoint
+ filename = "#{File.basename(__FILE__)}_#{__LINE__}"
+ lines = collect_from_binary_tracepoint_lines(:b_call, filename)
+
+ assert_equal [3, 3], lines, '[Bug #14702]'
+ end
+
+ def test_to_binary_b_return_tracepoint
+ filename = "#{File.basename(__FILE__)}_#{__LINE__}"
+ lines = collect_from_binary_tracepoint_lines(:b_return, filename)
+
+ assert_equal [10, 10], lines, '[Bug #14702]'
+ end
+
+ def test_iseq_of
+ [
+ proc{},
+ method(:test_iseq_of),
+ RubyVM::InstructionSequence.compile("p 1", __FILE__),
+ begin; raise "error"; rescue => error; error.backtrace_locations[0]; end
+ ].each{|src|
+ iseq = RubyVM::InstructionSequence.of(src)
+ assert_equal __FILE__, iseq.path
+ }
+ end
+
+ def test_iseq_of_twice_for_same_code
+ [
+ proc{},
+ method(:test_iseq_of_twice_for_same_code),
+ RubyVM::InstructionSequence.compile("p 1"),
+ begin; raise "error"; rescue => error; error.backtrace_locations[0]; end
+ ].each{|src|
+ iseq1 = RubyVM::InstructionSequence.of(src)
+ iseq2 = RubyVM::InstructionSequence.of(src)
+
+ # ISeq objects should be same for same src
+ assert_equal iseq1.object_id, iseq2.object_id
+ }
+ end
+
+ def test_iseq_builtin_to_a
+ invokebuiltin = eval(EnvUtil.invoke_ruby(['-e', <<~EOS], '', true).first)
+ insns = RubyVM::InstructionSequence.of([].method(:pack)).to_a.last
+ p insns.find { |insn| insn.is_a?(Array) && insn[0] == :opt_invokebuiltin_delegate_leave }
+ EOS
+ assert_not_nil(invokebuiltin)
+ assert_equal([:func_ptr, :argc, :index, :name], invokebuiltin[1].keys)
+ end
+
+ def test_iseq_builtin_load
+ Tempfile.create(["builtin", ".iseq"]) do |f|
+ f.binmode
+ f.write(iseq_to_binary(RubyVM::InstructionSequence.of(1.method(:abs))))
+ f.close
+ assert_separately(["-", f.path], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ bin = File.binread(ARGV[0])
+ assert_raise(ArgumentError) do
+ RubyVM::InstructionSequence.load_from_binary(bin)
+ end
+ end;
+ end
+ end
+
+ def test_iseq_option_debug_level
+ assert_raise(TypeError) {ISeq.compile("", debug_level: "")}
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ RubyVM::InstructionSequence.compile("", debug_level: 5)
+ end;
+ end
+
+ def test_mandatory_only
+ assert_separately [], <<~RUBY
+ at0 = Time.at(0)
+ assert_equal at0, Time.public_send(:at, 0, 0)
+ RUBY
+ end
+
+ def test_mandatory_only_redef
+ assert_separately ['-W0'], <<~RUBY
+ r = Ractor.new{
+ Float(10)
+ module Kernel
+ undef Float
+ def Float(n)
+ :new
+ end
+ end
+ GC.start
+ Float(30)
+ }
+ assert_equal :new, r.value
+ RUBY
+ end
+
+ def test_ever_condition_loop
+ assert_ruby_status([], "BEGIN {exit}; while true && true; end")
+ end
+
+ def test_short_circuited_loop_condition
+ assert_ruby_status([], "while true || true; exit; end; abort")
+ end
+
+ def test_unreachable_syntax_error
+ mesg = /Invalid break/
+ assert_syntax_error("false and break", mesg)
+ assert_syntax_error("if false and break; end", mesg)
+ end
+
+ def test_unreachable_pattern_matching
+ assert_in_out_err([], "true or 1 in 1")
+ assert_in_out_err([], "true or (case 1; in 1; 1; in 2; 2; end)")
+ end
+
+ def test_unreachable_pattern_matching_in_if_condition
+ assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", %w[1])
+ begin;
+ if true or {a: 0} in {a:}
+ p 1
+ else
+ p a
+ end
+ end;
+ end
+
+ def test_unreachable_next_in_block
+ bug20344 = '[ruby-core:117210] [Bug #20344]'
+ assert_nothing_raised(SyntaxError, bug20344) do
+ compile(<<~RUBY)
+ proc do
+ next
+
+ case nil
+ when "a"
+ next
+ when "b"
+ when "c"
+ proc {}
+ end
+
+ next
+ end
+ RUBY
+ end
+ end
+
+ def test_serialize_anonymous_outer_variables
+ iseq = RubyVM::InstructionSequence.compile(<<~'RUBY')
+ obj = Object.new
+ def obj.test
+ [1].each do
+ raise "Oops"
+ rescue
+ return it
+ end
+ end
+ obj
+ RUBY
+
+ binary = iseq.to_binary # [Bug # 21370]
+ roundtripped_iseq = RubyVM::InstructionSequence.load_from_binary(binary)
+ object = roundtripped_iseq.eval
+ assert_equal 1, object.test
+ end
+
+ def test_loading_kwargs_memory_leak
+ assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~'end;'}", rss: true)
+ a = RubyVM::InstructionSequence.compile("foo(bar: :baz)").to_binary
+ begin;
+ 1_000_000.times do
+ RubyVM::InstructionSequence.load_from_binary(a)
+ end
+ end;
+ end
+
+ def test_ibf_bignum
+ iseq = RubyVM::InstructionSequence.compile("0x0"+"_0123_4567_89ab_cdef"*5)
+ expected = iseq.eval
+ result = RubyVM::InstructionSequence.load_from_binary(iseq_to_binary(iseq)).eval
+ assert_equal expected, result, proc {sprintf("expected: %x, result: %x", expected, result)}
+ end
+
+ def test_compile_prism_with_file
+ Tempfile.create(%w"test_iseq .rb") do |f|
+ f.puts "_name = 'Prism'; puts 'hello'"
+ f.close
+
+ assert_nothing_raised(TypeError) do
+ RubyVM::InstructionSequence.compile_prism(f)
+ end
+ end
+ end
+
+ def block_using_method
+ yield
+ end
+
+ def block_unused_method
+ end
+
+ def test_unused_param
+ a = RubyVM::InstructionSequence.of(method(:block_using_method)).to_a
+
+ assert_equal true, a.dig(11, :use_block)
+
+ b = RubyVM::InstructionSequence.of(method(:block_unused_method)).to_a
+ assert_equal nil, b.dig(11, :use_block)
+ end
+
+ def test_compile_prism_with_invalid_object_type
+ assert_raise(TypeError) do
+ RubyVM::InstructionSequence.compile_prism(Object.new)
+ end
+ end
+
+ def test_load_from_binary_only_accepts_string_param
+ assert_raise(TypeError) do
+ var_0 = 0
+ RubyVM::InstructionSequence.load_from_binary(var_0)
+ end
+ end
+
+ def test_while_in_until_condition
+ assert_in_out_err(["--dump=i", "-e", "until while 1; end; end"]) do |stdout, stderr, status|
+ assert_include(stdout.shift, "== disasm:")
+ assert_include(stdout.pop, "leave")
+ assert_predicate(status, :success?)
+ end
+ end
+
+ def test_compile_empty_under_gc_stress
+ EnvUtil.under_gc_stress do
+ RubyVM::InstructionSequence.compile_file(File::NULL)
+ end
+ end
+end