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.rb319
1 files changed, 273 insertions, 46 deletions
diff --git a/test/ruby/test_iseq.rb b/test/ruby/test_iseq.rb
index 9b584c540c..b4760dc412 100644
--- a/test/ruby/test_iseq.rb
+++ b/test/ruby/test_iseq.rb
@@ -92,7 +92,17 @@ class TestISeq < Test::Unit::TestCase
42
end
EOF
- assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval)
+ 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
@@ -102,7 +112,7 @@ class TestISeq < Test::Unit::TestCase
end
42
EOF
- assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval)
+ assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval)
end
def test_super_with_block_hash_0
@@ -113,7 +123,7 @@ class TestISeq < Test::Unit::TestCase
end
42
EOF
- assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval)
+ assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval)
end
def test_super_with_block_and_kwrest
@@ -123,17 +133,16 @@ class TestISeq < Test::Unit::TestCase
end
42
EOF
- assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval)
+ 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 = nil.instance_eval{ lambda { x } }
- Ractor.make_shareable(y)
+ y = Ractor.shareable_lambda{x}
y.call
EOF
- assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval)
+ assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval)
end
def test_super_with_anonymous_block
@@ -143,32 +152,36 @@ class TestISeq < Test::Unit::TestCase
end
42
EOF
- assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval)
+ assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval)
end
def test_ractor_unshareable_outer_variable
name = "\u{2603 26a1}"
- y = nil.instance_eval do
- eval("proc {#{name} = nil; proc {|x| #{name} = x}}").call
- end
- assert_raise_with_message(ArgumentError, /\(#{name}\)/) do
- Ractor.make_shareable(y)
+ assert_raise_with_message(Ractor::IsolationError, /\(#{name}\)/) do
+ eval("#{name} = nil; Ractor.shareable_proc{#{name} = nil}")
end
- y = nil.instance_eval do
- eval("proc {#{name} = []; proc {|x| #{name}}}").call
- end
- assert_raise_with_message(Ractor::IsolationError, /'#{name}'/) do
- Ractor.make_shareable(y)
+
+ assert_raise_with_message(Ractor::IsolationError, /\'#{name}\'/) do
+ eval("#{name} = []; Ractor.shareable_proc{#{name}}")
end
+
obj = Object.new
- def obj.foo(*) nil.instance_eval{ ->{super} } end
- assert_raise_with_message(Ractor::IsolationError, /refer unshareable object \[\] from variable '\*'/) do
- Ractor.make_shareable(obj.foo)
+ 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}"
+ src = +"\u{3042} = 1; \u{3042}; \u{3043}"
asm = compile(src).disasm
assert_equal(src.encoding, asm.encoding)
assert_predicate(asm, :valid_encoding?)
@@ -199,6 +212,26 @@ class TestISeq < Test::Unit::TestCase
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
@@ -279,6 +312,56 @@ class TestISeq < Test::Unit::TestCase
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
@@ -292,6 +375,20 @@ class TestISeq < Test::Unit::TestCase
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
@@ -347,11 +444,17 @@ class TestISeq < Test::Unit::TestCase
end
end
assert_equal([m1, e1.message], [m2, e2.message], feature11951)
- 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("")})
+
+ 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
@@ -463,7 +566,7 @@ class TestISeq < Test::Unit::TestCase
["<class:C>@1",
["bar@10", ["block in bar@11",
["block (2 levels) in bar@12"]]],
- ["foo@2", ["ensure in foo@2"],
+ ["foo@2", ["ensure in foo@7"],
["rescue in foo@4"]]],
["<class:D>@17"]]
@@ -496,7 +599,7 @@ class TestISeq < Test::Unit::TestCase
[4, :line],
[7, :line],
[9, :return]]],
- [["ensure in foo@2", [[7, :line]]]],
+ [["ensure in foo@7", [[7, :line]]]],
[["rescue in foo@4", [[5, :line],
[5, :rescue]]]]]],
[["<class:D>@17", [[17, :class],
@@ -542,16 +645,20 @@ class TestISeq < Test::Unit::TestCase
}
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
- rescue RuntimeError => e
- omit e.message if /compile with coverage/ =~ e.message
- raise
+ iseq_to_binary(iseq)
end
10.times do
- bin2 = iseq.to_binary
+ bin2 = iseq_to_binary(iseq)
assert_equal(bin, bin2, message(mesg) {diff hexdump(bin), hexdump(bin2)})
end
iseq2 = RubyVM::InstructionSequence.load_from_binary(bin)
@@ -566,6 +673,23 @@ class TestISeq < Test::Unit::TestCase
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)")
@@ -592,6 +716,17 @@ class TestISeq < Test::Unit::TestCase
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;
@@ -627,7 +762,7 @@ class TestISeq < Test::Unit::TestCase
end
RUBY
- iseq_bin = iseq.to_binary
+ iseq_bin = iseq_to_binary(iseq)
iseq = ISeq.load_from_binary(iseq_bin)
lines = []
TracePoint.new(tracepoint_type){|tp|
@@ -685,18 +820,24 @@ class TestISeq < Test::Unit::TestCase
end
def test_iseq_of
- [proc{},
- method(:test_iseq_of),
- RubyVM::InstructionSequence.compile("p 1", __FILE__)].each{|src|
+ [
+ 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")].each{|src|
+ [
+ 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)
@@ -717,7 +858,7 @@ class TestISeq < Test::Unit::TestCase
def test_iseq_builtin_load
Tempfile.create(["builtin", ".iseq"]) do |f|
f.binmode
- f.write(RubyVM::InstructionSequence.of(1.method(:abs)).to_binary)
+ f.write(iseq_to_binary(RubyVM::InstructionSequence.of(1.method(:abs))))
f.close
assert_separately(["-", f.path], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
@@ -757,7 +898,7 @@ class TestISeq < Test::Unit::TestCase
GC.start
Float(30)
}
- assert_equal :new, r.take
+ assert_equal :new, r.value
RUBY
end
@@ -765,6 +906,10 @@ class TestISeq < Test::Unit::TestCase
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)
@@ -772,6 +917,11 @@ class TestISeq < Test::Unit::TestCase
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:}
@@ -782,9 +932,49 @@ class TestISeq < Test::Unit::TestCase
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
+ a = RubyVM::InstructionSequence.compile("foo(bar: :baz)").to_binary
begin;
1_000_000.times do
RubyVM::InstructionSequence.load_from_binary(a)
@@ -795,13 +985,13 @@ class TestISeq < Test::Unit::TestCase
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).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.puts "_name = 'Prism'; puts 'hello'"
f.close
assert_nothing_raised(TypeError) do
@@ -810,9 +1000,46 @@ class TestISeq < Test::Unit::TestCase
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