2012-01-14 07:56:46 -05:00
|
|
|
require 'test/unit'
|
2016-09-27 04:35:31 -04:00
|
|
|
require 'tempfile'
|
2012-01-14 07:56:46 -05:00
|
|
|
|
|
|
|
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
|
2012-06-09 04:21:52 -04:00
|
|
|
|
2015-10-22 22:58:22 -04:00
|
|
|
def compile(src, line = nil, opt = nil)
|
2021-11-24 01:42:00 -05:00
|
|
|
unless line
|
|
|
|
line = caller_locations(1).first.lineno
|
|
|
|
end
|
2016-02-19 02:58:09 -05:00
|
|
|
EnvUtil.suppress_warning do
|
2016-03-18 22:49:21 -04:00
|
|
|
ISeq.new(src, __FILE__, __FILE__, line, opt)
|
2016-02-19 02:58:09 -05:00
|
|
|
end
|
2015-10-22 22:58:22 -04:00
|
|
|
end
|
|
|
|
|
2021-11-24 01:42:00 -05:00
|
|
|
def lines src, lines = nil
|
|
|
|
body = compile(src, lines).to_a[13]
|
Use Integer instead of Fixnum and Bignum.
* object.c, numeric.c, enum.c, ext/-test-/bignum/mul.c,
lib/rexml/quickpath.rb, lib/rexml/text.rb, lib/rexml/xpath_parser.rb,
lib/rubygems/specification.rb, lib/uri/generic.rb,
bootstraptest/test_eval.rb, basictest/test.rb,
test/-ext-/bignum/test_big2str.rb, test/-ext-/bignum/test_div.rb,
test/-ext-/bignum/test_mul.rb, test/-ext-/bignum/test_str2big.rb,
test/csv/test_data_converters.rb, test/date/test_date.rb,
test/json/test_json_generate.rb, test/minitest/test_minitest_mock.rb,
test/openssl/test_cipher.rb, test/rexml/test_jaxen.rb,
test/ruby/test_array.rb, test/ruby/test_basicinstructions.rb,
test/ruby/test_bignum.rb, test/ruby/test_case.rb,
test/ruby/test_class.rb, test/ruby/test_complex.rb,
test/ruby/test_enum.rb, test/ruby/test_eval.rb,
test/ruby/test_iseq.rb, test/ruby/test_literal.rb,
test/ruby/test_math.rb, test/ruby/test_module.rb,
test/ruby/test_numeric.rb, test/ruby/test_range.rb,
test/ruby/test_rational.rb, test/ruby/test_refinement.rb,
test/ruby/test_rubyvm.rb, test/ruby/test_struct.rb,
test/ruby/test_variable.rb, test/rubygems/test_gem_specification.rb,
test/thread/test_queue.rb: Use Integer instead of Fixnum and Bignum.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@55029 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2016-05-17 09:15:57 -04:00
|
|
|
body.find_all{|e| e.kind_of? Integer}
|
2013-02-28 09:04:53 -05:00
|
|
|
end
|
|
|
|
|
2016-08-21 20:02:59 -04:00
|
|
|
def test_allocate
|
|
|
|
assert_raise(TypeError) {ISeq.allocate}
|
|
|
|
end
|
|
|
|
|
2013-02-28 06:26:23 -05:00
|
|
|
def test_to_a_lines
|
2021-11-24 01:42:00 -05:00
|
|
|
assert_equal [__LINE__+1, __LINE__+2, __LINE__+4], lines(<<-EOS, __LINE__+1)
|
2013-02-28 06:26:23 -05:00
|
|
|
p __LINE__ # 1
|
|
|
|
p __LINE__ # 2
|
|
|
|
# 3
|
|
|
|
p __LINE__ # 4
|
|
|
|
EOS
|
2013-02-28 09:04:53 -05:00
|
|
|
|
2021-11-24 01:42:00 -05:00
|
|
|
assert_equal [__LINE__+2, __LINE__+4], lines(<<-EOS, __LINE__+1)
|
2013-02-28 09:04:53 -05:00
|
|
|
# 1
|
|
|
|
p __LINE__ # 2
|
|
|
|
# 3
|
|
|
|
p __LINE__ # 4
|
|
|
|
# 5
|
|
|
|
EOS
|
|
|
|
|
2021-11-24 01:42:00 -05:00
|
|
|
assert_equal [__LINE__+3, __LINE__+4, __LINE__+7, __LINE__+9], lines(<<~EOS, __LINE__+1)
|
2013-02-28 09:04:53 -05:00
|
|
|
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
|
2013-02-28 06:26:23 -05:00
|
|
|
end
|
|
|
|
|
2021-11-02 05:29:53 -04:00
|
|
|
def test_unsupported_type
|
2016-03-18 22:49:21 -04:00
|
|
|
ary = compile("p").to_a
|
2012-06-09 04:21:52 -04:00
|
|
|
ary[9] = :foobar
|
2016-03-18 22:49:21 -04:00
|
|
|
assert_raise_with_message(TypeError, /:foobar/) {ISeq.load(ary)}
|
2012-06-09 04:21:52 -04:00
|
|
|
end if defined?(RubyVM::InstructionSequence.load)
|
2012-06-09 10:36:56 -04:00
|
|
|
|
2015-09-09 23:17:30 -04:00
|
|
|
def test_loaded_cdhash_mark
|
2015-10-22 22:58:22 -04:00
|
|
|
iseq = compile(<<-'end;', __LINE__+1)
|
2015-09-09 23:17:30 -04:00
|
|
|
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)
|
|
|
|
|
2021-05-21 12:13:55 -04:00
|
|
|
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.
|
2021-11-24 01:42:00 -05:00
|
|
|
iseq = compile(<<~EOF, __LINE__+1)
|
2021-05-21 12:13:55 -04:00
|
|
|
case Class.new(String).new("foo")
|
|
|
|
when "foo"
|
|
|
|
42
|
|
|
|
end
|
|
|
|
EOF
|
|
|
|
assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval)
|
|
|
|
end
|
|
|
|
|
2021-10-13 01:25:34 -04:00
|
|
|
def test_super_with_block
|
2021-11-24 01:42:00 -05:00
|
|
|
iseq = compile(<<~EOF, __LINE__+1)
|
2021-11-24 21:00:28 -05:00
|
|
|
def (Object.new).touch(*) # :nodoc:
|
2021-10-13 01:25:34 -04:00
|
|
|
foo { super }
|
|
|
|
end
|
|
|
|
42
|
|
|
|
EOF
|
|
|
|
assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval)
|
|
|
|
end
|
|
|
|
|
2021-11-22 06:20:38 -05:00
|
|
|
def test_super_with_block_hash_0
|
2021-11-24 01:42:00 -05:00
|
|
|
iseq = compile(<<~EOF, __LINE__+1)
|
2021-11-22 06:20:38 -05:00
|
|
|
# [Bug #18250] `req` specifically cause `Assertion failed: (key != 0), function hash_table_raw_insert`
|
2021-11-24 21:00:28 -05:00
|
|
|
def (Object.new).touch(req, *)
|
2021-11-22 06:20:38 -05:00
|
|
|
foo { super }
|
|
|
|
end
|
|
|
|
42
|
|
|
|
EOF
|
|
|
|
assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval)
|
|
|
|
end
|
|
|
|
|
2021-10-13 01:25:34 -04:00
|
|
|
def test_super_with_block_and_kwrest
|
2021-11-24 01:42:00 -05:00
|
|
|
iseq = compile(<<~EOF, __LINE__+1)
|
2021-11-24 21:00:28 -05:00
|
|
|
def (Object.new).touch(**) # :nodoc:
|
2021-10-13 01:25:34 -04:00
|
|
|
foo { super }
|
|
|
|
end
|
|
|
|
42
|
|
|
|
EOF
|
|
|
|
assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval)
|
|
|
|
end
|
|
|
|
|
2021-10-06 16:55:02 -04:00
|
|
|
def test_lambda_with_ractor_roundtrip
|
2021-11-24 01:42:00 -05:00
|
|
|
iseq = compile(<<~EOF, __LINE__+1)
|
2021-10-06 16:55:02 -04:00
|
|
|
x = 42
|
2021-12-08 13:58:44 -05:00
|
|
|
y = nil.instance_eval{ lambda { x } }
|
2021-10-06 16:55:02 -04:00
|
|
|
Ractor.make_shareable(y)
|
|
|
|
y.call
|
|
|
|
EOF
|
|
|
|
assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval)
|
|
|
|
end
|
|
|
|
|
2021-11-18 15:44:19 -05:00
|
|
|
def test_super_with_anonymous_block
|
2021-11-24 01:42:00 -05:00
|
|
|
iseq = compile(<<~EOF, __LINE__+1)
|
2021-11-24 21:00:28 -05:00
|
|
|
def (Object.new).touch(&) # :nodoc:
|
2021-11-18 15:44:19 -05:00
|
|
|
foo { super }
|
|
|
|
end
|
|
|
|
42
|
|
|
|
EOF
|
|
|
|
assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval)
|
|
|
|
end
|
|
|
|
|
2021-10-29 04:34:09 -04:00
|
|
|
def test_ractor_unshareable_outer_variable
|
|
|
|
name = "\u{2603 26a1}"
|
2021-12-08 13:58:44 -05:00
|
|
|
y = nil.instance_eval do
|
|
|
|
eval("proc {#{name} = nil; proc {|x| #{name} = x}}").call
|
|
|
|
end
|
2021-10-29 04:34:09 -04:00
|
|
|
assert_raise_with_message(ArgumentError, /\(#{name}\)/) do
|
|
|
|
Ractor.make_shareable(y)
|
|
|
|
end
|
2021-12-08 13:58:44 -05:00
|
|
|
y = nil.instance_eval do
|
|
|
|
eval("proc {#{name} = []; proc {|x| #{name}}}").call
|
|
|
|
end
|
2021-10-29 08:40:11 -04:00
|
|
|
assert_raise_with_message(Ractor::IsolationError, /`#{name}'/) do
|
|
|
|
Ractor.make_shareable(y)
|
|
|
|
end
|
2021-10-13 03:48:35 -04:00
|
|
|
obj = Object.new
|
2021-12-08 13:58:44 -05:00
|
|
|
def obj.foo(*) nil.instance_eval{ ->{super} } end
|
Add support for anonymous rest and keyword rest argument forwarding
This allows for the following syntax:
```ruby
def foo(*)
bar(*)
end
def baz(**)
quux(**)
end
```
This is a natural addition after the introduction of anonymous
block forwarding. Anonymous rest and keyword rest arguments were
already supported in method parameters, this just allows them to
be used as arguments to other methods. The same advantages of
anonymous block forwarding apply to rest and keyword rest argument
forwarding.
This has some minor changes to #parameters output. Now, instead
of `[:rest], [:keyrest]`, you get `[:rest, :*], [:keyrest, :**]`.
These were already used for `...` forwarding, so I think it makes
it more consistent to include them in other cases. If we want to
use `[:rest], [:keyrest]` in both cases, that is also possible.
I don't think the previous behavior of `[:rest], [:keyrest]` in
the non-... case and `[:rest, :*], [:keyrest, :**]` in the ...
case makes sense, but if we did want that behavior, we'll have to
make more substantial changes, such as using a different ID in the
... forwarding case.
Implements [Feature #18351]
2021-11-19 12:38:22 -05:00
|
|
|
assert_raise_with_message(Ractor::IsolationError, /refer unshareable object \[\] from variable `\*'/) do
|
2021-10-13 03:48:35 -04:00
|
|
|
Ractor.make_shareable(obj.foo)
|
|
|
|
end
|
2021-10-29 04:34:09 -04:00
|
|
|
end
|
|
|
|
|
2012-06-09 10:36:56 -04:00
|
|
|
def test_disasm_encoding
|
2014-11-23 12:03:32 -05:00
|
|
|
src = "\u{3042} = 1; \u{3042}; \u{3043}"
|
2015-10-22 22:58:22 -04:00
|
|
|
asm = compile(src).disasm
|
2014-11-23 12:03:32 -05:00
|
|
|
assert_equal(src.encoding, asm.encoding)
|
|
|
|
assert_predicate(asm, :valid_encoding?)
|
2012-06-10 02:49:16 -04:00
|
|
|
src.encode!(Encoding::Shift_JIS)
|
2015-10-22 22:58:22 -04:00
|
|
|
asm = compile(src).disasm
|
2014-11-23 12:03:32 -05:00
|
|
|
assert_equal(src.encoding, asm.encoding)
|
|
|
|
assert_predicate(asm, :valid_encoding?)
|
2018-01-11 03:34:55 -05:00
|
|
|
|
|
|
|
obj = Object.new
|
|
|
|
name = "\u{2603 26a1}"
|
|
|
|
obj.instance_eval("def #{name}; tap {}; end")
|
|
|
|
assert_include(RubyVM::InstructionSequence.of(obj.method(name)).disasm, name)
|
2012-06-09 10:36:56 -04:00
|
|
|
end
|
2012-11-30 12:00:30 -05:00
|
|
|
|
2020-11-18 14:39:59 -05:00
|
|
|
def test_compile_file_encoding
|
|
|
|
Tempfile.create(%w"test_iseq .rb") do |f|
|
|
|
|
f.puts "{ '\u00de' => 'Th', '\u00df' => 'ss', '\u00e0' => 'a' }"
|
|
|
|
f.close
|
|
|
|
|
2020-11-19 01:41:38 -05:00
|
|
|
EnvUtil.with_default_external(Encoding::US_ASCII) do
|
|
|
|
assert_warn('') {
|
|
|
|
load f.path
|
|
|
|
}
|
|
|
|
assert_nothing_raised(SyntaxError) {
|
|
|
|
RubyVM::InstructionSequence.compile_file(f.path)
|
|
|
|
}
|
2020-11-18 14:39:59 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-12-01 15:07:58 -05:00
|
|
|
LINE_BEFORE_METHOD = __LINE__
|
|
|
|
def method_test_line_trace
|
|
|
|
|
2016-02-19 02:48:02 -05:00
|
|
|
_a = 1
|
2012-12-01 15:07:58 -05:00
|
|
|
|
2016-02-19 02:48:02 -05:00
|
|
|
_b = 2
|
2012-12-01 15:07:58 -05:00
|
|
|
|
|
|
|
end
|
|
|
|
|
2012-11-30 12:00:30 -05:00
|
|
|
def test_line_trace
|
2021-11-24 01:42:00 -05:00
|
|
|
iseq = compile(<<~EOF, __LINE__+1)
|
|
|
|
a = 1
|
2012-11-30 12:00:30 -05:00
|
|
|
b = 2
|
|
|
|
c = 3
|
|
|
|
# d = 4
|
|
|
|
e = 5
|
|
|
|
# f = 6
|
|
|
|
g = 7
|
2021-11-24 01:42:00 -05:00
|
|
|
EOF
|
2012-11-30 12:00:30 -05:00
|
|
|
|
|
|
|
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)
|
2012-12-01 15:07:58 -05:00
|
|
|
|
|
|
|
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)
|
2012-12-21 08:36:17 -05:00
|
|
|
end if false # TODO: now, it is only for C APIs.
|
2012-11-30 13:02:43 -05:00
|
|
|
|
|
|
|
LINE_OF_HERE = __LINE__
|
|
|
|
def test_location
|
|
|
|
iseq = ISeq.of(method(:test_location))
|
|
|
|
|
|
|
|
assert_equal(__FILE__, iseq.path)
|
2013-12-13 04:18:05 -05:00
|
|
|
assert_match(/#{__FILE__}/, iseq.absolute_path)
|
2012-11-30 13:02:43 -05:00
|
|
|
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)
|
2013-12-13 04:18:05 -05:00
|
|
|
assert_match(/#{__FILE__}/, iseq.absolute_path)
|
2012-11-30 13:02:43 -05:00
|
|
|
assert_equal("test_location", iseq.base_label)
|
|
|
|
assert_equal("block in test_location", iseq.label)
|
|
|
|
assert_equal(line+1, iseq.first_lineno)
|
|
|
|
end
|
2013-11-27 00:39:38 -05:00
|
|
|
|
|
|
|
def test_label_fstring
|
|
|
|
c = Class.new{ def foobar() end }
|
|
|
|
|
|
|
|
a, b = eval("# encoding: us-ascii\n'foobar'.freeze"),
|
2013-11-27 01:03:37 -05:00
|
|
|
ISeq.of(c.instance_method(:foobar)).label
|
|
|
|
assert_same a, b
|
2013-11-27 00:39:38 -05:00
|
|
|
end
|
2014-12-16 20:20:22 -05:00
|
|
|
|
2014-12-17 19:03:30 -05:00
|
|
|
def test_disable_opt
|
2014-12-16 20:20:22 -05:00
|
|
|
src = "a['foo'] = a['bar']; 'a'.freeze"
|
2015-10-22 22:58:22 -04:00
|
|
|
body= compile(src, __LINE__, false).to_a[13]
|
2014-12-16 20:20:22 -05:00
|
|
|
body.each{|insn|
|
2015-10-22 21:49:38 -04:00
|
|
|
next unless Array === insn
|
2014-12-16 20:20:22 -05:00
|
|
|
op = insn.first
|
|
|
|
assert(!op.to_s.match(/^opt_/), "#{op}")
|
|
|
|
}
|
|
|
|
end
|
2015-05-19 05:54:44 -04:00
|
|
|
|
|
|
|
def test_invalid_source
|
|
|
|
bug11159 = '[ruby-core:69219] [Bug #11159]'
|
2016-03-18 22:49:21 -04:00
|
|
|
assert_raise(TypeError, bug11159) {compile(nil)}
|
|
|
|
assert_raise(TypeError, bug11159) {compile(:foo)}
|
|
|
|
assert_raise(TypeError, bug11159) {compile(1)}
|
2015-05-19 05:54:44 -04:00
|
|
|
end
|
2015-08-21 16:47:53 -04:00
|
|
|
|
|
|
|
def test_frozen_string_literal_compile_option
|
|
|
|
$f = 'f'
|
|
|
|
line = __LINE__ + 2
|
|
|
|
code = <<-'EOS'
|
2015-10-08 23:58:34 -04:00
|
|
|
['foo', 'foo', "#{$f}foo", "#{'foo'}"]
|
2015-08-21 16:47:53 -04:00
|
|
|
EOS
|
2015-10-22 22:58:22 -04:00
|
|
|
s1, s2, s3, s4 = compile(code, line, {frozen_string_literal: true}).eval
|
2015-10-09 00:15:53 -04:00
|
|
|
assert_predicate(s1, :frozen?)
|
|
|
|
assert_predicate(s2, :frozen?)
|
2020-08-31 15:24:36 -04:00
|
|
|
assert_not_predicate(s3, :frozen?)
|
2020-09-02 10:12:22 -04:00
|
|
|
assert_not_predicate(s4, :frozen?)
|
2015-08-21 16:47:53 -04:00
|
|
|
end
|
2015-10-22 22:58:22 -04:00
|
|
|
|
2017-09-24 20:09:44 -04:00
|
|
|
# Safe call chain is not optimized when Coverage is running.
|
|
|
|
# So we can test it only when Coverage is not running.
|
2015-10-22 22:58:22 -04:00
|
|
|
def test_safe_call_chain
|
2015-11-05 22:39:23 -05:00
|
|
|
src = "a&.a&.a&.a&.a&.a"
|
2015-10-22 22:58:22 -04:00
|
|
|
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)
|
2017-09-24 20:09:44 -04:00
|
|
|
end if (!defined?(Coverage) || !Coverage.running?)
|
2016-01-13 02:56:51 -05:00
|
|
|
|
|
|
|
def test_parent_iseq_mark
|
2016-01-18 21:52:37 -05:00
|
|
|
assert_separately([], <<-'end;', timeout: 20)
|
2016-01-13 02:56:51 -05:00
|
|
|
->{
|
|
|
|
->{
|
|
|
|
->{
|
|
|
|
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
|
2016-03-19 01:46:20 -04:00
|
|
|
|
|
|
|
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)
|
2019-05-25 22:33:40 -04:00
|
|
|
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
|
2016-03-19 01:46:20 -04:00
|
|
|
end
|
2016-07-13 01:26:00 -04:00
|
|
|
|
2016-09-27 04:35:31 -04:00
|
|
|
def test_compile_file_error
|
|
|
|
Tempfile.create(%w"test_iseq .rb") do |f|
|
|
|
|
f.puts "end"
|
|
|
|
f.close
|
|
|
|
path = f.path
|
2019-01-30 23:35:14 -05:00
|
|
|
assert_in_out_err(%W[- #{path}], "#{<<-"begin;"}\n#{<<-"end;"}", /unexpected `end'/, [], success: true)
|
2016-09-27 04:35:31 -04:00
|
|
|
begin;
|
|
|
|
path = ARGV[0]
|
|
|
|
begin
|
|
|
|
RubyVM::InstructionSequence.compile_file(path)
|
|
|
|
rescue SyntaxError => e
|
|
|
|
puts e.message
|
|
|
|
end
|
|
|
|
end;
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-07-13 01:26:00 -04:00
|
|
|
def test_translate_by_object
|
|
|
|
assert_separately([], <<-"end;")
|
|
|
|
class Object
|
|
|
|
def translate
|
|
|
|
end
|
|
|
|
end
|
|
|
|
assert_equal(0, eval("0"))
|
|
|
|
end;
|
|
|
|
end
|
2017-06-12 00:35:53 -04:00
|
|
|
|
|
|
|
def test_inspect
|
|
|
|
%W[foo \u{30d1 30b9}].each do |name|
|
2019-06-01 07:44:24 -04:00
|
|
|
assert_match(/@#{name}/, ISeq.compile("", name).inspect, name)
|
2017-06-12 00:35:53 -04:00
|
|
|
m = ISeq.compile("class TestISeq::Inspect; def #{name}; end; instance_method(:#{name}); end").eval
|
2019-06-01 07:44:24 -04:00
|
|
|
assert_match(/:#{name}@/, ISeq.of(m).inspect, name)
|
2017-06-12 00:35:53 -04:00
|
|
|
end
|
|
|
|
end
|
2017-12-23 07:48:24 -05:00
|
|
|
|
2021-10-13 01:25:34 -04:00
|
|
|
def anon_star(*); end
|
|
|
|
|
Add support for anonymous rest and keyword rest argument forwarding
This allows for the following syntax:
```ruby
def foo(*)
bar(*)
end
def baz(**)
quux(**)
end
```
This is a natural addition after the introduction of anonymous
block forwarding. Anonymous rest and keyword rest arguments were
already supported in method parameters, this just allows them to
be used as arguments to other methods. The same advantages of
anonymous block forwarding apply to rest and keyword rest argument
forwarding.
This has some minor changes to #parameters output. Now, instead
of `[:rest], [:keyrest]`, you get `[:rest, :*], [:keyrest, :**]`.
These were already used for `...` forwarding, so I think it makes
it more consistent to include them in other cases. If we want to
use `[:rest], [:keyrest]` in both cases, that is also possible.
I don't think the previous behavior of `[:rest], [:keyrest]` in
the non-... case and `[:rest, :*], [:keyrest, :**]` in the ...
case makes sense, but if we did want that behavior, we'll have to
make more substantial changes, such as using a different ID in the
... forwarding case.
Implements [Feature #18351]
2021-11-19 12:38:22 -05:00
|
|
|
def test_anon_rest_param_in_disasm
|
2021-10-13 01:25:34 -04:00
|
|
|
iseq = RubyVM::InstructionSequence.of(method(:anon_star))
|
|
|
|
param_names = iseq.to_a[iseq.to_a.index(:method) + 1]
|
Add support for anonymous rest and keyword rest argument forwarding
This allows for the following syntax:
```ruby
def foo(*)
bar(*)
end
def baz(**)
quux(**)
end
```
This is a natural addition after the introduction of anonymous
block forwarding. Anonymous rest and keyword rest arguments were
already supported in method parameters, this just allows them to
be used as arguments to other methods. The same advantages of
anonymous block forwarding apply to rest and keyword rest argument
forwarding.
This has some minor changes to #parameters output. Now, instead
of `[:rest], [:keyrest]`, you get `[:rest, :*], [:keyrest, :**]`.
These were already used for `...` forwarding, so I think it makes
it more consistent to include them in other cases. If we want to
use `[:rest], [:keyrest]` in both cases, that is also possible.
I don't think the previous behavior of `[:rest], [:keyrest]` in
the non-... case and `[:rest, :*], [:keyrest, :**]` in the ...
case makes sense, but if we did want that behavior, we'll have to
make more substantial changes, such as using a different ID in the
... forwarding case.
Implements [Feature #18351]
2021-11-19 12:38:22 -05:00
|
|
|
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
|
2021-10-13 01:25:34 -04:00
|
|
|
end
|
|
|
|
|
2021-11-18 15:44:19 -05:00
|
|
|
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
|
|
|
|
|
2018-11-06 01:36:51 -05:00
|
|
|
def strip_lineno(source)
|
|
|
|
source.gsub(/^.*?: /, "")
|
|
|
|
end
|
|
|
|
|
2017-12-23 09:46:59 -05:00
|
|
|
def sample_iseq
|
2018-11-06 01:36:51 -05:00
|
|
|
ISeq.compile(strip_lineno(<<-EOS))
|
2017-12-23 09:46:59 -05:00
|
|
|
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
|
2017-12-23 07:48:24 -05:00
|
|
|
EOS
|
2017-12-23 09:46:59 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def test_each_child
|
|
|
|
iseq = sample_iseq
|
2017-12-23 07:48:24 -05:00
|
|
|
|
|
|
|
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@2"],
|
|
|
|
["rescue in foo@4"]]],
|
|
|
|
["<class:D>@17"]]
|
|
|
|
|
|
|
|
assert_equal expected, collect_iseq.call(iseq)
|
|
|
|
end
|
2017-12-23 09:46:59 -05:00
|
|
|
|
|
|
|
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@2", [[7, :line]]]],
|
|
|
|
[["rescue in foo@4", [[5, :line]]]]]],
|
|
|
|
[["<class:D>@17", [[17, :class],
|
|
|
|
[18, :end]]]]], collect_iseq.call(sample_iseq)
|
|
|
|
end
|
2017-12-24 19:27:17 -05:00
|
|
|
|
|
|
|
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
|
2019-06-28 02:04:25 -04:00
|
|
|
case type
|
2017-12-24 19:27:17 -05:00
|
|
|
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
|
2018-02-28 18:31:42 -05:00
|
|
|
|
2018-04-06 23:48:45 -04:00
|
|
|
def hexdump(bin)
|
|
|
|
bin.unpack1("H*").gsub(/.{1,32}/) {|s|
|
|
|
|
"#{'%04x:' % $~.begin(0)}#{s.gsub(/../, " \\&").tap{|_|_[24]&&="-"}}\n"
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2018-04-05 09:13:00 -04:00
|
|
|
def assert_iseq_to_binary(code, mesg = nil)
|
2018-03-15 22:27:50 -04:00
|
|
|
iseq = RubyVM::InstructionSequence.compile(code)
|
2018-04-05 09:13:00 -04:00
|
|
|
bin = assert_nothing_raised(mesg) do
|
2018-03-16 08:06:55 -04:00
|
|
|
iseq.to_binary
|
|
|
|
rescue RuntimeError => e
|
2022-01-04 03:25:30 -05:00
|
|
|
omit e.message if /compile with coverage/ =~ e.message
|
2018-03-16 08:06:55 -04:00
|
|
|
raise
|
|
|
|
end
|
2018-04-05 09:13:00 -04:00
|
|
|
10.times do
|
2018-04-06 23:48:45 -04:00
|
|
|
bin2 = iseq.to_binary
|
|
|
|
assert_equal(bin, bin2, message(mesg) {diff hexdump(bin), hexdump(bin2)})
|
2018-04-05 09:13:00 -04:00
|
|
|
end
|
2018-03-21 06:06:23 -04:00
|
|
|
iseq2 = RubyVM::InstructionSequence.load_from_binary(bin)
|
2018-04-06 23:48:45 -04:00
|
|
|
a1 = iseq.to_a
|
|
|
|
a2 = iseq2.to_a
|
|
|
|
assert_equal(a1, a2, message(mesg) {diff iseq.disassemble, iseq2.disassemble})
|
2021-10-28 12:03:29 -04:00
|
|
|
if iseq2.script_lines
|
|
|
|
assert_kind_of(Array, iseq2.script_lines)
|
|
|
|
else
|
|
|
|
assert_nil(iseq2.script_lines)
|
|
|
|
end
|
2018-04-05 09:13:00 -04:00
|
|
|
iseq2
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_to_binary_with_objects
|
|
|
|
assert_iseq_to_binary("[]"+100.times.map{|i|"<</#{i}/"}.join)
|
2018-04-16 08:46:08 -04:00
|
|
|
assert_iseq_to_binary("@x ||= (1..2)")
|
2018-04-05 09:13:00 -04:00
|
|
|
end
|
|
|
|
|
2019-08-07 22:15:45 -04:00
|
|
|
def test_to_binary_pattern_matching
|
2019-05-20 21:29:34 -04:00
|
|
|
code = "case foo; in []; end"
|
2019-08-07 22:15:45 -04:00
|
|
|
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
|
|
|
|
|
2019-10-02 03:05:40 -04:00
|
|
|
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
|
|
|
|
|
2018-04-05 09:13:00 -04:00
|
|
|
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;
|
2020-01-27 23:43:30 -05:00
|
|
|
|
|
|
|
# cleanup
|
|
|
|
::Object.class_eval do
|
|
|
|
remove_const :P
|
|
|
|
end
|
2018-02-28 18:31:42 -05:00
|
|
|
end
|
2018-08-23 00:12:14 -04:00
|
|
|
|
2018-11-06 01:36:51 -05:00
|
|
|
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
|
2019-06-29 20:34:41 -04:00
|
|
|
_a = 'good day'
|
2018-11-06 01:36:51 -05:00
|
|
|
raise
|
|
|
|
rescue
|
|
|
|
'dear reader'
|
|
|
|
end
|
|
|
|
}
|
|
|
|
end
|
|
|
|
B.foo
|
|
|
|
end
|
|
|
|
RUBY
|
|
|
|
|
2018-08-23 00:12:14 -04:00
|
|
|
iseq_bin = iseq.to_binary
|
2019-06-29 20:55:50 -04:00
|
|
|
iseq = ISeq.load_from_binary(iseq_bin)
|
2018-11-06 01:36:51 -05:00
|
|
|
lines = []
|
|
|
|
TracePoint.new(tracepoint_type){|tp|
|
2018-08-23 00:12:14 -04:00
|
|
|
next unless tp.path == filename
|
2018-11-06 01:36:51 -05:00
|
|
|
lines << tp.lineno
|
2018-08-23 00:12:14 -04:00
|
|
|
}.enable{
|
2019-06-29 20:55:50 -04:00
|
|
|
EnvUtil.suppress_warning {iseq.eval}
|
2018-08-23 00:12:14 -04:00
|
|
|
}
|
2018-11-06 01:36:51 -05:00
|
|
|
|
|
|
|
lines
|
|
|
|
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]'
|
2018-08-23 00:12:14 -04:00
|
|
|
end
|
2018-12-06 05:52:27 -05:00
|
|
|
|
|
|
|
def test_iseq_of
|
|
|
|
[proc{},
|
|
|
|
method(:test_iseq_of),
|
|
|
|
RubyVM::InstructionSequence.compile("p 1", __FILE__)].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|
|
|
|
|
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
|
2019-11-10 00:40:38 -05:00
|
|
|
|
|
|
|
def test_iseq_builtin_to_a
|
2020-06-24 03:29:53 -04:00
|
|
|
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
|
2019-11-10 00:40:38 -05:00
|
|
|
assert_not_nil(invokebuiltin)
|
|
|
|
assert_equal([:func_ptr, :argc, :index, :name], invokebuiltin[1].keys)
|
|
|
|
end
|
2020-04-15 01:27:38 -04:00
|
|
|
|
2020-11-30 01:19:49 -05:00
|
|
|
def test_iseq_builtin_load
|
|
|
|
Tempfile.create(["builtin", ".iseq"]) do |f|
|
|
|
|
f.binmode
|
|
|
|
f.write(RubyVM::InstructionSequence.of(1.method(:abs)).to_binary)
|
|
|
|
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
|
|
|
|
|
2020-04-15 01:27:38 -04:00
|
|
|
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
|
2021-11-25 01:57:36 -05:00
|
|
|
|
|
|
|
def test_mandatory_only
|
|
|
|
assert_separately [], <<~RUBY
|
|
|
|
at0 = Time.at(0)
|
|
|
|
assert_equal at0, Time.public_send(:at, 0, 0)
|
|
|
|
RUBY
|
|
|
|
end
|
2021-12-20 16:03:51 -05:00
|
|
|
|
|
|
|
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.take
|
|
|
|
RUBY
|
|
|
|
end
|
2012-01-14 07:56:46 -05:00
|
|
|
end
|