# regression test for GC marking stubs in invalidated code assert_normal_exit %q{ garbage = Array.new(10_000) { [] } # create garbage to cause iseq movement eval(<<~RUBY) def foo(n, garbage) if n == 2 # 1.times.each to create a cfunc frame to preserve the JIT frame # which will return to a stub housed in an invalidated block return 1.times.each do Object.define_method(:foo) {} garbage.clear GC.verify_compaction_references(toward: :empty, expand_heap: true) end end foo(n + 1, garbage) end RUBY foo(1, garbage) } # regression test for callee block handler overlapping with arguments assert_equal '3', %q{ def foo(_req, *args) = args.last def call_foo = foo(0, 1, 2, 3, &->{}) call_foo } # call leaf builtin with a block argument assert_equal '0', "0.abs(&nil)" # regression test for invokeblock iseq guard assert_equal 'ok', %q{ return :ok unless defined?(GC.compact) def foo = yield 10.times do |i| ret = eval("foo { #{i} }") raise "failed at #{i}" unless ret == i GC.compact end :ok } unless defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? # Not yet working on RJIT # regression test for overly generous guard elision assert_equal '[0, :sum, 0, :sum]', %q{ # In faulty versions, the following happens: # 1. YJIT puts object on the temp stack with type knowledge # (CArray or CString) about RBASIC_CLASS(object). # 2. In iter=0, due to the type knowledge, YJIT generates # a call to sum() without any guard on RBASIC_CLASS(object). # 3. In iter=1, a singleton class is added to the object, # changing RBASIC_CLASS(object), falsifying the type knowledge. # 4. Because the code from (1) has no class guard, it is incorrectly # reused and the wrong method is invoked. # Putting a literal is important for gaining type knowledge. def carray(iter) array = [] array.sum(iter.times { def array.sum(_) = :sum }) end def cstring(iter) string = "" string.sum(iter.times { def string.sum(_) = :sum }) end [carray(0), carray(1), cstring(0), cstring(1)] } # regression test for return type of Integer#/ # It can return a T_BIGNUM when inputs are T_FIXNUM. assert_equal 0x3fffffffffffffff.to_s, %q{ def call(fixnum_min) (fixnum_min / -1) - 1 end call(-(2**62)) } # regression test for return type of String#<< assert_equal 'Sub', %q{ def call(sub) = (sub << sub).itself class Sub < String; end call(Sub.new('o')).class } # test splat filling required and feeding rest assert_equal '[0, 1, 2, [3, 4]]', %q{ public def lead_rest(a, b, *rest) [self, a, b, rest] end def call(args) = 0.lead_rest(*args) call([1, 2, 3, 4]) } # test missing opts are nil initialized assert_equal '[[0, 1, nil, 3], [0, 1, nil, 3], [0, 1, nil, 3, []], [0, 1, nil, 3, []]]', %q{ public def lead_opts(a, b=binding.local_variable_get(:c), c=3) [self, a, b, c] end public def opts_rest(a=raise, b=binding.local_variable_get(:c), c=3, *rest) [self, a, b, c, rest] end def call(args) [ 0.lead_opts(1), 0.lead_opts(*args), 0.opts_rest(1), 0.opts_rest(*args), ] end call([1]) } # test filled optionals with unspecified keyword param assert_equal 'ok', %q{ def opt_rest_opt_kw(_=1, *, k: :ok) = k def call = opt_rest_opt_kw(0) call } # test splat empty array with rest param assert_equal '[0, 1, 2, []]', %q{ public def foo(a=1, b=2, *rest) [self, a, b, rest] end def call(args) = 0.foo(*args) call([]) } # Regression test for yielding with autosplat to block with # optional parameters. https://github.com/Shopify/yjit/issues/313 assert_equal '[:a, :b, :a, :b]', %q{ def yielder(arg) = yield(arg) + yield(arg) yielder([:a, :b]) do |c = :c, d = :d| [c, d] end } # Regression test for GC mishap while doing shape transition assert_equal '[:ok]', %q{ # [Bug #19601] class RegressionTest def initialize @a = @b = @fourth_ivar_does_shape_transition = nil end def extender @first_extended_ivar = [:ok] end end GC.stress = true # Used to crash due to GC run in rb_ensure_iv_list_size() # not marking the newly allocated [:ok]. RegressionTest.new.extender.itself } unless RUBY_DESCRIPTION.include?('+RJIT') # Skip on RJIT since this uncovers a crash assert_equal 'true', %q{ # regression test for tracking type of locals for too long def local_setting_cmp(five) victim = 5 five.define_singleton_method(:respond_to?) do |_, _| victim = nil end # +1 makes YJIT track that victim is a number and # defined? calls respond_to? from above indirectly unless (victim + 1) && defined?(five.something) # Would return wrong result if we still think `five` is a number victim.nil? end end local_setting_cmp(Object.new) local_setting_cmp(Object.new) } assert_equal '18374962167983112447', %q{ # regression test for incorrectly discarding 32 bits of a pointer when it # comes to default values. def large_literal_default(n: 0xff00_fabcafe0_00ff) n end def call_graph_root large_literal_default end call_graph_root call_graph_root } assert_normal_exit %q{ # regression test for a leak caught by an assert on --yjit-call-threshold=2 Foo = 1 eval("def foo = [#{(['Foo,']*256).join}]") foo foo Object.send(:remove_const, :Foo) } assert_normal_exit %q{ # Test to ensure send on overriden c functions # doesn't corrupt the stack class Bar def bar(x) x end end class Foo def bar Bar.new end end foo = Foo.new # before this change, this line would error # because "s" would still be on the stack # String.to_s is the overridden method here p foo.bar.bar("s".__send__(:to_s)) } assert_equal '[nil, nil, nil, nil, nil, nil]', %q{ [NilClass, TrueClass, FalseClass, Integer, Float, Symbol].each do |klass| klass.class_eval("def foo = @foo") end [nil, true, false, 0xFABCAFE, 0.42, :cake].map do |instance| instance.foo instance.foo end } assert_equal '[nil, nil, nil, nil, nil, nil]', %q{ # Tests defined? on non-heap objects [NilClass, TrueClass, FalseClass, Integer, Float, Symbol].each do |klass| klass.class_eval("def foo = defined?(@foo)") end [nil, true, false, 0xFABCAFE, 0.42, :cake].map do |instance| instance.foo instance.foo end } assert_equal '[nil, "instance-variable", nil, "instance-variable"]', %q{ # defined? on object that changes shape between calls class Foo def foo defined?(@foo) end def add @foo = 1 end def remove self.remove_instance_variable(:@foo) end end obj = Foo.new [obj.foo, (obj.add; obj.foo), (obj.remove; obj.foo), (obj.add; obj.foo)] } assert_equal '["instance-variable", 5]', %q{ # defined? on object too complex for shape information class Foo def initialize 100.times { |i| instance_variable_set("@foo#{i}", i) } end def foo [defined?(@foo5), @foo5] end end Foo.new.foo } # getinstancevariable with shape too complex assert_normal_exit %q{ class Foo def initialize @a = 1 end def getter @foobar end end # Initialize ivars in changing order, making the Foo # class have shape too complex 100.times do |x| foo = Foo.new foo.instance_variable_set(:"@a#{x}", 1) foo.instance_variable_set(:"@foobar", 777) # The getter method eventually sees shape too complex r = foo.getter if r != 777 raise "error" end end } assert_equal '0', %q{ # This is a regression test for incomplete invalidation from # opt_setinlinecache. This test might be brittle, so # feel free to remove it in the future if it's too annoying. # This test assumes --yjit-call-threshold=2. module M Foo = 1 def foo Foo end def pin_self_type_then_foo _ = @foo foo end def only_ints 1 + self foo end end class Integer include M end class Sub include M end foo_method = M.instance_method(:foo) dbg = ->(message) do return # comment this out to get printouts $stderr.puts RubyVM::YJIT.disasm(foo_method) $stderr.puts message end 2.times { 42.only_ints } dbg["There should be two versions of getinlineache"] module M remove_const(:Foo) end dbg["There should be no getinlinecaches"] 2.times do 42.only_ints rescue NameError => err _ = "caught name error #{err}" end dbg["There should be one version of getinlineache"] 2.times do Sub.new.pin_self_type_then_foo rescue NameError _ = 'second specialization' end dbg["There should be two versions of getinlineache"] module M Foo = 1 end dbg["There should still be two versions of getinlineache"] 42.only_ints dbg["There should be no getinlinecaches"] # Find name of the first VM instruction in M#foo. insns = RubyVM::InstructionSequence.of(foo_method).to_a if defined?(RubyVM::YJIT.blocks_for) && (insns.last.find { Array === _1 }&.first == :opt_getinlinecache) RubyVM::YJIT.blocks_for(RubyVM::InstructionSequence.of(foo_method)) .filter { _1.iseq_start_index == 0 }.count else 0 # skip the test end } # Check that frozen objects are respected assert_equal 'great', %q{ class Foo attr_accessor :bar def initialize @bar = 1 freeze end end foo = Foo.new 5.times do begin foo.bar = 2 rescue FrozenError end end foo.bar == 1 ? "great" : "NG" } # Check that global variable set works assert_equal 'string', %q{ def foo $foo = "string" end foo } # Check that exceptions work when setting global variables assert_equal 'rescued', %q{ def set_var $var = 100 rescue :rescued end set_var trace_var(:$var) { raise } set_var } # Check that global variables work assert_equal 'string', %q{ $foo = "string" def foo $foo end foo } # Check that exceptions work when getting global variable assert_equal 'rescued', %q{ module Warning def warn(message) raise end end def get_var $= rescue :rescued end $VERBOSE = true get_var get_var } # Check that global tracepoints work assert_equal 'true', %q{ def foo 1 end foo foo foo called = false tp = TracePoint.new(:return) { |event| if event.method_id == :foo called = true end } tp.enable foo tp.disable called } # Check that local tracepoints work assert_equal 'true', %q{ def foo 1 end foo foo foo called = false tp = TracePoint.new(:return) { |_| called = true } tp.enable(target: method(:foo)) foo tp.disable called } # Make sure that optional param methods return the correct value assert_equal '1', %q{ def m(ary = []) yield(ary) end # Warm the JIT with a 0 param call 2.times { m { } } m(1) { |v| v } } # Test for topn assert_equal 'array', %q{ def threequals(a) case a when Array "array" when Hash "hash" else "unknown" end end threequals([]) threequals([]) threequals([]) } # Test for opt_mod assert_equal '2', %q{ def mod(a, b) a % b end mod(7, 5) mod(7, 5) } # Test for opt_mult assert_equal '12', %q{ def mult(a, b) a * b end mult(6, 2) mult(6, 2) } # Test for opt_div assert_equal '3', %q{ def div(a, b) a / b end div(6, 2) div(6, 2) } # BOP redefined methods work when JIT compiled assert_equal 'false', %q{ def less_than x x < 10 end class Integer def < x false end end less_than 2 less_than 2 less_than 2 } # BOP redefinition works on Integer#< assert_equal 'false', %q{ def less_than x x < 10 end less_than 2 less_than 2 class Integer def < x false end end less_than 2 } # BOP redefinition works on Integer#<= assert_equal 'false', %q{ def le(x, y) = x <= y le(2, 2) class Integer def <=(_) = false end le(2, 2) } # BOP redefinition works on Integer#> assert_equal 'false', %q{ def gt(x, y) = x > y gt(3, 2) class Integer def >(_) = false end gt(3, 2) } # BOP redefinition works on Integer#>= assert_equal 'false', %q{ def ge(x, y) = x >= y ge(2, 2) class Integer def >=(_) = false end ge(2, 2) } # Putobject, less-than operator, fixnums assert_equal '2', %q{ def check_index(index) if 0x40000000 < index raise "wat? #{index}" end index end check_index 2 check_index 2 } # foo leaves a temp on the stack before the call assert_equal '6', %q{ def bar return 5 end def foo return 1 + bar end foo() retval = foo() } # Method with one arguments # foo leaves a temp on the stack before the call assert_equal '7', %q{ def bar(a) return a + 1 end def foo return 1 + bar(5) end foo() retval = foo() } # Method with two arguments # foo leaves a temp on the stack before the call assert_equal '0', %q{ def bar(a, b) return a - b end def foo return 1 + bar(1, 2) end foo() retval = foo() } # Passing argument types to callees assert_equal '8.5', %q{ def foo(x, y) x + y end def bar foo(7, 1.5) end bar bar } # Recursive Ruby-to-Ruby calls assert_equal '21', %q{ def fib(n) if n < 2 return n end return fib(n-1) + fib(n-2) end r = fib(8) } # Ruby-to-Ruby call and C call assert_normal_exit %q{ def bar puts('hi!') end def foo bar end foo() foo() } # Method aliasing assert_equal '42', %q{ class Foo def method_a 42 end alias method_b method_a def method_a :somethingelse end end @obj = Foo.new def test @obj.method_b end test test } # Method aliasing with method from parent class assert_equal '777', %q{ class A def method_a 777 end end class B < A alias method_b method_a end @obj = B.new def test @obj.method_b end test test } # The hash method is a C function and uses the self argument assert_equal 'true', %q{ def lehashself hash end a = lehashself b = lehashself a == b } # Method redefinition (code invalidation) test assert_equal '1', %q{ def ret1 return 1 end klass = Class.new do def alias_then_hash(klass, method_to_redefine) # Redefine the method to be ret1 klass.alias_method(method_to_redefine, :ret1) hash end end instance = klass.new i = 0 while i < 12 if i < 11 # Redefine the bar method instance.alias_then_hash(klass, :bar) else # Redefine the hash method to be ret1 retval = instance.alias_then_hash(klass, :hash) end i += 1 end retval } # Code invalidation and opt_getinlinecache assert_normal_exit %q{ class Foo; end # Uses the class constant Foo def use_constant(arg) [Foo.new, arg] end def propagate_type i = Array.new i.itself # make it remember that i is on-heap use_constant(i) end propagate_type propagate_type use_constant(Foo.new) class Jo; end # bump global constant state use_constant(3) } # Method redefinition (code invalidation) and GC assert_equal '7', %q{ def bar() return 5 end def foo() bar() end foo() foo() def bar() return 7 end 4.times { GC.start } foo() foo() } # Method redefinition with two block versions assert_equal '7', %q{ def bar() return 5 end def foo(n) return ((n < 5)? 5:false), bar() end foo(4) foo(4) foo(10) foo(10) def bar() return 7 end 4.times { GC.start } foo(4) foo(4)[1] } # Method redefinition while the method is on the stack assert_equal '[777, 1]', %q{ def foo redef() 777 end def redef # Redefine the global foo eval("def foo; 1; end", TOPLEVEL_BINDING) # Collect dead code GC.stress = true GC.start # But we will return to the original foo, # which remains alive because it's on the stack end # Must produce [777, 1] [foo, foo] } # Test for GC safety. Don't invalidate dead iseqs. assert_normal_exit %q{ Class.new do def foo itself end new.foo new.foo new.foo new.foo end 4.times { GC.start } def itself self end } # test setinstancevariable on extended objects assert_equal '1', %q{ class Extended attr_reader :one def write_many @a = 1 @b = 2 @c = 3 @d = 4 @one = 1 end end foo = Extended.new foo.write_many foo.write_many foo.write_many } # test setinstancevariable on embedded objects assert_equal '1', %q{ class Embedded attr_reader :one def write_one @one = 1 end end foo = Embedded.new foo.write_one foo.write_one foo.write_one } # test setinstancevariable after extension assert_equal '[10, 11, 12, 13, 1]', %q{ class WillExtend attr_reader :one def make_extended @foo1 = 10 @foo2 = 11 @foo3 = 12 @foo4 = 13 end def write_one @one = 1 end def read_all [@foo1, @foo2, @foo3, @foo4, @one] end end foo = WillExtend.new foo.write_one foo.write_one foo.make_extended foo.write_one foo.read_all } # test setinstancevariable on frozen object assert_equal 'object was not modified', %q{ class WillFreeze def write @ivar = 1 end end wf = WillFreeze.new wf.write wf.write wf.freeze begin wf.write rescue FrozenError "object was not modified" end } # Test getinstancevariable and inline caches assert_equal '6', %q{ class Foo def initialize @x1 = 1 @x2 = 1 @x2 = 1 @x3 = 1 @x4 = 3 end def bar x = 1 @x4 + @x4 end end f = Foo.new f.bar f.bar } # Test that getinstancevariable codegen checks for extended table size assert_equal "nil\n", %q{ class A def read @ins1000 end end ins = A.new other = A.new 10.times { other.instance_variable_set(:"@otr#{_1}", 'value') } 1001.times { ins.instance_variable_set(:"@ins#{_1}", 'value') } ins.read ins.read ins.read p other.read } # Test that opt_aref checks the class of the receiver assert_equal 'special', %q{ def foo(array) array[30] end foo([]) foo([]) special = [] def special.[](idx) 'special' end foo(special) } # Test that object references in generated code get marked and moved assert_equal "good", %q{ def bar "good" end def foo bar end foo foo begin GC.verify_compaction_references(expand_heap: true, toward: :empty) rescue NotImplementedError # in case compaction isn't supported end foo } # Test polymorphic getinstancevariable. T_OBJECT -> T_STRING assert_equal 'ok', %q{ @hello = @h1 = @h2 = @h3 = @h4 = 'ok' str = "" str.instance_variable_set(:@hello, 'ok') public def get @hello end get get str.get str.get } # Test polymorphic getinstancevariable, two different classes assert_equal 'ok', %q{ class Embedded def initialize @ivar = 0 end def get @ivar end end class Extended < Embedded def initialize @v1 = @v2 = @v3 = @v4 = @ivar = 'ok' end end embed = Embedded.new extend = Extended.new embed.get embed.get extend.get extend.get } # Test megamorphic getinstancevariable assert_equal 'ok', %q{ parent = Class.new do def initialize @hello = @h1 = @h2 = @h3 = @h4 = 'ok' end def get @hello end end subclasses = 300.times.map { Class.new(parent) } subclasses.each { _1.new.get } parent.new.get } # Test polymorphic opt_aref. array -> hash assert_equal '[42, :key]', %q{ def index(obj, idx) obj[idx] end index([], 0) # get over compilation threshold [ index([42], 0), index({0=>:key}, 0), ] } # Test polymorphic opt_aref. hash -> array -> custom class assert_equal '[nil, nil, :custom]', %q{ def index(obj, idx) obj[idx] end custom = Object.new def custom.[](_idx) :custom end index({}, 0) # get over compilation threshold [ index({}, 0), index([], 0), index(custom, 0) ] } # Test polymorphic opt_aref. array -> custom class assert_equal '[42, :custom]', %q{ def index(obj, idx) obj[idx] end custom = Object.new def custom.[](_idx) :custom end index([], 0) # get over compilation threshold [ index([42], 0), index(custom, 0) ] } # Test custom hash method with opt_aref assert_equal '[nil, :ok]', %q{ def index(obj, idx) obj[idx] end custom = Object.new def custom.hash 42 end h = {custom => :ok} [ index(h, 0), index(h, custom) ] } # Test default value block for Hash with opt_aref assert_equal '[42, :default]', %q{ def index(obj, idx) obj[idx] end h = Hash.new { :default } h[0] = 42 [ index(h, 0), index(h, 1) ] } # Test default value block for Hash with opt_aref_with assert_equal "false", %q{ def index_with_string(h) h["foo"] end h = Hash.new { |h, k| k.frozen? } index_with_string(h) index_with_string(h) } # A regression test for making sure cfp->sp is proper when # hitting stubs. See :stub-sp-flush: assert_equal 'ok', %q{ class D def foo Object.new end end GC.stress = true 10.times do D.new.foo # ^ # This hits a stub with sp_offset > 0 end :ok } # Test polymorphic callsite, cfunc -> iseq assert_equal '[Cfunc, Iseq]', %q{ public def call_itself itself # the polymorphic callsite end class Cfunc; end class Iseq def itself self end end call_itself # cross threshold [Cfunc.call_itself, Iseq.call_itself] } # Test polymorphic callsite, iseq -> cfunc assert_equal '[Iseq, Cfunc]', %q{ public def call_itself itself # the polymorphic callsite end class Cfunc; end class Iseq def itself self end end call_itself # cross threshold [Iseq.call_itself, Cfunc.call_itself] } # attr_reader method assert_equal '[100, 299]', %q{ class A attr_reader :foo def initialize @foo = 100 end # Make it extended def fill! @bar = @jojo = @as = @sdfsdf = @foo = 299 end end def bar(ins) ins.foo end ins = A.new oth = A.new oth.fill! bar(ins) bar(oth) [bar(ins), bar(oth)] } # get ivar on object, then on hash assert_equal '[42, 100]', %q{ class Hash attr_accessor :foo end class A attr_reader :foo def initialize @foo = 42 end end def use(val) val.foo end h = {} h.foo = 100 obj = A.new use(obj) [use(obj), use(h)] } # get ivar on String assert_equal '[nil, nil, 42, 42]', %q{ # @foo to exercise the getinstancevariable instruction public def get_foo @foo end get_foo get_foo # compile it for the top level object class String attr_reader :foo end def run str = String.new getter = str.foo insn = str.get_foo str.instance_variable_set(:@foo, 42) [getter, insn, str.foo, str.get_foo] end run run } # splatting an empty array on a getter assert_equal '42', %q{ @foo = 42 module Kernel attr_reader :foo end def run foo(*[]) end run run } # splatting an empty array on a specialized method assert_equal 'ok', %q{ def run "ok".to_s(*[]) end run run } # splatting an single element array on a specialized method assert_equal '[1]', %q{ def run [].<<(*[1]) end run run } # specialized method with wrong args assert_equal 'ok', %q{ def run(x) "bad".to_s(123) if x rescue :ok end run(false) run(true) } # getinstancevariable on Symbol assert_equal '[nil, nil]', %q{ # @foo to exercise the getinstancevariable instruction public def get_foo @foo end dyn_sym = ("a" + "b").to_sym sym = :static # compile get_foo dyn_sym.get_foo dyn_sym.get_foo [dyn_sym.get_foo, sym.get_foo] } # attr_reader on Symbol assert_equal '[nil, nil]', %q{ class Symbol attr_reader :foo end public def get_foo foo end dyn_sym = ("a" + "b").to_sym sym = :static # compile get_foo dyn_sym.get_foo dyn_sym.get_foo [dyn_sym.get_foo, sym.get_foo] } # passing too few arguments to method with optional parameters assert_equal 'raised', %q{ def opt(a, b = 0) end def use opt end use rescue nil begin use :ng rescue ArgumentError :raised end } # passing too many arguments to method with optional parameters assert_equal 'raised', %q{ def opt(a, b = 0) end def use opt(1, 2, 3, 4) end use rescue nil begin use :ng rescue ArgumentError :raised end } # test calling Ruby method with a block assert_equal '[1, 2, 42]', %q{ def thing(a, b) [a, b, yield] end def use thing(1,2) { 42 } end use use } # test calling C method with a block assert_equal '[42, 42]', %q{ def use(array, initial) array.reduce(initial) { |a, b| a + b } end use([], 0) [use([2, 2], 38), use([14, 14, 14], 0)] } # test calling block param assert_equal '[1, 2, 42]', %q{ def foo(&block) block.call end [foo {1}, foo {2}, foo {42}] } # test calling without block param assert_equal '[1, false, 2, false]', %q{ def bar block_given? && yield end def foo(&block) bar(&block) end [foo { 1 }, foo, foo { 2 }, foo] } # test calling block param failing assert_equal '42', %q{ def foo(&block) block.call end foo {} # warmup begin foo rescue NoMethodError => e 42 if nil == e.receiver end } # test calling method taking block param assert_equal '[Proc, 1, 2, 3, Proc]', %q{ def three(a, b, c, &block) [a, b, c, block.class] end def zero(&block) block.class end def use_three three(1, 2, 3) {} end def use_zero zero {} end use_three use_zero [use_zero] + use_three } # test building empty array assert_equal '[]', %q{ def build_arr [] end build_arr build_arr } # test building array of one element assert_equal '[5]', %q{ def build_arr(val) [val] end build_arr(5) build_arr(5) } # test building array of several element assert_equal '[5, 5, 5, 5, 5]', %q{ def build_arr(val) [val, val, val, val, val] end build_arr(5) build_arr(5) } # test building empty hash assert_equal '{}', %q{ def build_hash {} end build_hash build_hash } # test building hash with values assert_equal '{:foo=>:bar}', %q{ def build_hash(val) { foo: val } end build_hash(:bar) build_hash(:bar) } # test string interpolation with known types assert_equal 'foobar', %q{ def make_str foo = -"foo" bar = -"bar" "#{foo}#{bar}" end make_str make_str } # test string interpolation with unknown types assert_equal 'foobar', %q{ def make_str(foo, bar) "#{foo}#{bar}" end make_str("foo", "bar") make_str("foo", "bar") } # test string interpolation with known non-strings assert_equal 'foo123', %q{ def make_str foo = -"foo" bar = 123 "#{foo}#{bar}" end make_str make_str } # test string interpolation with unknown non-strings assert_equal 'foo123', %q{ def make_str(foo, bar) "#{foo}#{bar}" end make_str("foo", 123) make_str("foo", 123) } # test that invalidation of String#to_s doesn't crash assert_equal 'meh', %q{ def inval_method "".to_s end inval_method class String def to_s "meh" end end inval_method } # test that overriding to_s on a String subclass works consistently assert_equal 'meh', %q{ class MyString < String def to_s "meh" end end def test_to_s(obj) obj.to_s end OBJ = MyString.new # Should return '' both times test_to_s("") test_to_s("") # Can return '' if YJIT optimises String#to_s too aggressively test_to_s(OBJ) test_to_s(OBJ) } # test string interpolation with overridden to_s assert_equal 'foo', %q{ class String def to_s "bad" end end def make_str(foo) "#{foo}" end make_str("foo") make_str("foo") } # Test that String unary plus returns the same object ID for an unfrozen string. assert_equal 'true', %q{ def jittable_method str = "bar" old_obj_id = str.object_id uplus_str = +str uplus_str.object_id == old_obj_id end jittable_method } # Test that String unary plus returns a different unfrozen string when given a frozen string assert_equal 'false', %q{ # Logic needs to be inside an ISEQ, such as a method, for YJIT to compile it def jittable_method frozen_str = "foo".freeze old_obj_id = frozen_str.object_id uplus_str = +frozen_str uplus_str.object_id == old_obj_id || uplus_str.frozen? end jittable_method } # String-subclass objects should behave as expected inside string-interpolation via concatstrings assert_equal 'monkeys / monkeys, yo!', %q{ class MyString < String # This is a terrible idea in production code, but we'd like YJIT to match CRuby def to_s super + ", yo!" end end def jittable_method m = MyString.new('monkeys') "#{m} / #{m.to_s}" end jittable_method } # String-subclass objects should behave as expected for string equality assert_equal 'false', %q{ class MyString < String # This is a terrible idea in production code, but we'd like YJIT to match CRuby def ==(b) "#{self}_" == b end end def jittable_method ma = MyString.new("a") # Check equality with string-subclass receiver ma == "a" || ma != "a_" || # Check equality with string receiver "a_" == ma || "a" != ma || # Check equality between string subclasses ma != MyString.new("a_") || # Make sure "string always equals itself" check isn't used with overridden equality ma == ma end jittable_method } # Test to_s duplicates a string subclass object but not a string assert_equal 'false', %q{ class MyString < String; end def jittable_method a = "a" ma = MyString.new("a") a.object_id != a.to_s.object_id || ma.object_id == ma.to_s.object_id end jittable_method } # Test freeze on string subclass assert_equal 'true', %q{ class MyString < String; end def jittable_method fma = MyString.new("a").freeze # Freezing a string subclass should not duplicate it fma.object_id == fma.freeze.object_id end jittable_method } # Test unary minus on string subclass assert_equal 'true', %q{ class MyString < String; end def jittable_method ma = MyString.new("a") fma = MyString.new("a").freeze # Unary minus on frozen string subclass should not duplicate it fma.object_id == (-fma).object_id && # Unary minus on unfrozen string subclass should duplicate it ma.object_id != (-ma).object_id end jittable_method } # Test unary plus on string subclass assert_equal 'true', %q{ class MyString < String; end def jittable_method fma = MyString.new("a").freeze # Unary plus on frozen string subclass should not duplicate it fma.object_id != (+fma).object_id end jittable_method } # test getbyte on string class assert_equal '[97, :nil, 97, :nil, :raised]', %q{ def getbyte(s, i) byte = begin s.getbyte(i) rescue TypeError :raised end byte || :nil end getbyte("a", 0) getbyte("a", 0) [getbyte("a", 0), getbyte("a", 1), getbyte("a", -1), getbyte("a", -2), getbyte("a", "a")] } unless defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? # Not yet working on RJIT # Test << operator on string subclass assert_equal 'abab', %q{ class MyString < String; end def jittable_method a = -"a" mb = MyString.new("b") buf = String.new mbuf = MyString.new buf << a << mb mbuf << a << mb buf + mbuf end jittable_method } # test invokebuiltin as used in struct assignment assert_equal '123', %q{ def foo(obj) obj.foo = 123 end struct = Struct.new(:foo) obj = struct.new foo(obj) foo(obj) } # test invokebuiltin_delegate as used inside Dir.open assert_equal '.', %q{ def foo(path) Dir.open(path).path end foo(".") foo(".") } # test invokebuiltin_delegate_leave in method called from jit assert_normal_exit %q{ def foo(obj) obj.clone end foo(Object.new) foo(Object.new) } # test invokebuiltin_delegate_leave in method called from cfunc assert_normal_exit %q{ def foo(obj) [obj].map(&:clone) end foo(Object.new) foo(Object.new) } # defining TrueClass#! assert_equal '[false, false, :ok]', %q{ def foo(obj) !obj end x = foo(true) y = foo(true) class TrueClass def ! :ok end end z = foo(true) [x, y, z] } # defining FalseClass#! assert_equal '[true, true, :ok]', %q{ def foo(obj) !obj end x = foo(false) y = foo(false) class FalseClass def ! :ok end end z = foo(false) [x, y, z] } # defining NilClass#! assert_equal '[true, true, :ok]', %q{ def foo(obj) !obj end x = foo(nil) y = foo(nil) class NilClass def ! :ok end end z = foo(nil) [x, y, z] } # polymorphic opt_not assert_equal '[true, true, false, false, false, false, false]', %q{ def foo(obj) !obj end foo(0) [foo(nil), foo(false), foo(true), foo([]), foo(0), foo(4.2), foo(:sym)] } # getlocal with 2 levels assert_equal '7', %q{ def foo(foo, bar) while foo > 0 while bar > 0 return foo + bar end end end foo(5,2) foo(5,2) } # test pattern matching assert_equal '[:ok, :ok]', %q{ class C def destructure_keys {} end end pattern_match = ->(i) do case i in a: 0 :ng else :ok end end [{}, C.new].map(&pattern_match) } # Call to object with singleton assert_equal '123', %q{ obj = Object.new def obj.foo 123 end def foo(obj) obj.foo() end foo(obj) foo(obj) } # Call method on an object that has a non-material # singleton class. # TODO: assert that it takes no side exits? This # test case revealed that we were taking exits unnecessarily. assert_normal_exit %q{ def foo(obj) obj.itself end o = Object.new.singleton_class foo(o) foo(o) } # Call to singleton class assert_equal '123', %q{ class Foo def self.foo 123 end end def foo(obj) obj.foo() end foo(Foo) foo(Foo) } # invokesuper edge case assert_equal '[:A, [:A, :B]]', %q{ class B def foo = :B end class A < B def foo = [:A, super()] end A.new.foo A.new.foo # compile A#foo class C < A define_method(:bar, A.instance_method(:foo)) end C.new.bar } # Same invokesuper bytecode, multiple destinations assert_equal '[:Forward, :SecondTerminus]', %q{ module Terminus def foo = :Terminus end module SecondTerminus def foo = :SecondTerminus end module Forward def foo = [:Forward, super] end class B include SecondTerminus end class A < B include Terminus include Forward end A.new.foo A.new.foo # compile class B include Forward alias bar foo end # A.ancestors.take(5) == [A, Forward, Terminus, B, Forward, SecondTerminus] A.new.bar } # invokesuper calling into itself assert_equal '[:B, [:B, :m]]', %q{ module M def foo = :m end class B include M def foo = [:B, super] end ins = B.new ins.singleton_class # materialize the singleton class ins.foo ins.foo # compile ins.singleton_class.define_method(:bar, B.instance_method(:foo)) ins.bar } # invokesuper changed ancestor assert_equal '[:A, [:M, :B]]', %q{ class B def foo :B end end class A < B def foo [:A, super] end end module M def foo [:M, super] end end ins = A.new ins.foo ins.foo A.include(M) ins.foo } # invokesuper changed ancestor via prepend assert_equal '[:A, [:M, :B]]', %q{ class B def foo :B end end class A < B def foo [:A, super] end end module M def foo [:M, super] end end ins = A.new ins.foo ins.foo B.prepend(M) ins.foo } # invokesuper replaced method assert_equal '[:A, :Btwo]', %q{ class B def foo :B end end class A < B def foo [:A, super] end end ins = A.new ins.foo ins.foo class B def foo :Btwo end end ins.foo } # invokesuper with a block assert_equal 'true', %q{ class A def foo = block_given? end class B < A def foo = super() end B.new.foo { } B.new.foo { } } # invokesuper in a block assert_equal '[0, 2]', %q{ class A def foo(x) = x * 2 end class B < A def foo 2.times.map do |x| super(x) end end end B.new.foo B.new.foo } # Call to fixnum assert_equal '[true, false]', %q{ def is_odd(obj) obj.odd? end is_odd(1) is_odd(1) [is_odd(123), is_odd(456)] } # Call to bignum assert_equal '[true, false]', %q{ def is_odd(obj) obj.odd? end bignum = 99999999999999999999 is_odd(bignum) is_odd(bignum) [is_odd(bignum), is_odd(bignum+1)] } # Call to fixnum and bignum assert_equal '[true, false, true, false]', %q{ def is_odd(obj) obj.odd? end bignum = 99999999999999999999 is_odd(bignum) is_odd(bignum) is_odd(123) is_odd(123) [is_odd(123), is_odd(456), is_odd(bignum), is_odd(bignum+1)] } # Call to static and dynamic symbol assert_equal 'bar', %q{ def to_string(obj) obj.to_s end to_string(:foo) to_string(:foo) to_string((-"bar").to_sym) to_string((-"bar").to_sym) } # Call to flonum and heap float assert_equal '[nil, nil, nil, 1]', %q{ def is_inf(obj) obj.infinite? end is_inf(0.0) is_inf(0.0) is_inf(1e256) is_inf(1e256) [ is_inf(0.0), is_inf(1.0), is_inf(1e256), is_inf(1.0/0.0) ] } assert_equal '[1, 2, 3, 4, 5]', %q{ def splatarray [*(1..5)] end splatarray splatarray } assert_equal '[1, 1, 2, 1, 2, 3]', %q{ def expandarray arr = [1, 2, 3] a, = arr b, c, = arr d, e, f = arr [a, b, c, d, e, f] end expandarray expandarray } assert_equal '[1, 1]', %q{ def expandarray_useless_splat arr = (1..10).to_a a, * = arr b, (*) = arr [a, b] end expandarray_useless_splat expandarray_useless_splat } assert_equal '[:not_heap, nil, nil]', %q{ def expandarray_not_heap a, b, c = :not_heap [a, b, c] end expandarray_not_heap expandarray_not_heap } assert_equal '[:not_array, nil, nil]', %q{ def expandarray_not_array(obj) a, b, c = obj [a, b, c] end obj = Object.new def obj.to_ary [:not_array] end expandarray_not_array(obj) expandarray_not_array(obj) } assert_equal '[1, 2, nil]', %q{ def expandarray_rhs_too_small a, b, c = [1, 2] [a, b, c] end expandarray_rhs_too_small expandarray_rhs_too_small } assert_equal '[nil, 2, nil]', %q{ def foo(arr) a, b, c = arr end a, b, c1 = foo([0, 1]) a, b, c2 = foo([0, 1, 2]) a, b, c3 = foo([0, 1]) [c1, c2, c3] } assert_equal '[1, [2]]', %q{ def expandarray_splat a, *b = [1, 2] [a, b] end expandarray_splat expandarray_splat } assert_equal '2', %q{ def expandarray_postarg *, a = [1, 2] a end expandarray_postarg expandarray_postarg } assert_equal '10', %q{ obj = Object.new val = nil obj.define_singleton_method(:to_ary) { val = 10; [] } def expandarray_always_call_to_ary(object) * = object end expandarray_always_call_to_ary(obj) expandarray_always_call_to_ary(obj) val } # regression test of local type change assert_equal '1.1', %q{ def bar(baz, quux) if baz.integer? baz, quux = quux, nil end baz.to_s end bar(123, 1.1) bar(123, 1.1) } # test enabling a line TracePoint in a C method call assert_equal '[[:line, true]]', %q{ events = [] events.instance_variable_set( :@tp, TracePoint.new(:line) { |tp| events << [tp.event, tp.lineno] if tp.path == __FILE__ } ) def events.to_str @tp.enable; '' end # Stay in generated code while enabling tracing def events.compiled(obj) String(obj) @tp.disable; __LINE__ end line = events.compiled(events) events[0][-1] = (events[0][-1] == line) events } # test enabling a c_return TracePoint in a C method call assert_equal '[[:c_return, :String, :string_alias, "events_to_str"]]', %q{ events = [] events.instance_variable_set(:@tp, TracePoint.new(:c_return) { |tp| events << [tp.event, tp.method_id, tp.callee_id, tp.return_value] }) def events.to_str @tp.enable; 'events_to_str' end # Stay in generated code while enabling tracing alias string_alias String def events.compiled(obj) string_alias(obj) @tp.disable end events.compiled(events) events } unless defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? # RJIT calls extra Ruby methods # test enabling a TracePoint that targets a particular line in a C method call assert_equal '[true]', %q{ events = [] events.instance_variable_set(:@tp, TracePoint.new(:line) { |tp| events << tp.lineno }) def events.to_str @tp.enable(target: method(:compiled)) '' end # Stay in generated code while enabling tracing def events.compiled(obj) String(obj) __LINE__ end line = events.compiled(events) events[0] = (events[0] == line) events } # test enabling tracing in the middle of splatarray assert_equal '[true]', %q{ events = [] obj = Object.new obj.instance_variable_set(:@tp, TracePoint.new(:line) { |tp| events << tp.lineno }) def obj.to_a @tp.enable(target: method(:compiled)) [] end # Enable tracing in the middle of the splatarray instruction def obj.compiled(obj) * = *obj __LINE__ end obj.compiled([]) line = obj.compiled(obj) events[0] = (events[0] == line) events } # test enabling tracing in the middle of opt_aref. Different since the codegen # for it ends in a jump. assert_equal '[true]', %q{ def lookup(hash, tp) hash[42] tp.disable; __LINE__ end lines = [] tp = TracePoint.new(:line) { lines << _1.lineno if _1.path == __FILE__ } lookup(:foo, tp) lookup({}, tp) enable_tracing_on_missing = Hash.new { tp.enable } expected_line = lookup(enable_tracing_on_missing, tp) lines[0] = true if lines[0] == expected_line lines } # test enabling c_call tracing before compiling assert_equal '[[:c_call, :itself]]', %q{ def shouldnt_compile itself end events = [] tp = TracePoint.new(:c_call) { |tp| events << [tp.event, tp.method_id] } # assume first call compiles tp.enable { shouldnt_compile } events } unless defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? # RJIT calls extra Ruby methods # test enabling c_return tracing before compiling assert_equal '[[:c_return, :itself, main]]', %q{ def shouldnt_compile itself end events = [] tp = TracePoint.new(:c_return) { |tp| events << [tp.event, tp.method_id, tp.return_value] } # assume first call compiles tp.enable { shouldnt_compile } events } unless defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? # RJIT calls extra Ruby methods # test c_call invalidation assert_equal '[[:c_call, :itself]]', %q{ # enable the event once to make sure invalidation # happens the second time we enable it TracePoint.new(:c_call) {}.enable{} def compiled itself end # assume first call compiles compiled events = [] tp = TracePoint.new(:c_call) { |tp| events << [tp.event, tp.method_id] } tp.enable { compiled } events } # test enabling tracing for a suspended fiber assert_equal '[[:return, 42]]', %q{ def traced_method Fiber.yield 42 end events = [] tp = TracePoint.new(:return) { events << [_1.event, _1.return_value] } # assume first call compiles fiber = Fiber.new { traced_method } fiber.resume tp.enable(target: method(:traced_method)) fiber.resume events } # test compiling on non-tracing ractor then running on a tracing one assert_equal '[:itself]', %q{ def traced_method itself end tracing_ractor = Ractor.new do # 1: start tracing events = [] tp = TracePoint.new(:c_call) { events << _1.method_id } tp.enable Ractor.yield(nil) # 3: run compiled method on tracing ractor Ractor.yield(nil) traced_method events ensure tp&.disable end tracing_ractor.take # 2: compile on non tracing ractor traced_method tracing_ractor.take tracing_ractor.take } # Try to hit a lazy branch stub while another ractor enables tracing assert_equal '42', %q{ def compiled(arg) if arg arg + 1 else itself itself end end ractor = Ractor.new do compiled(false) Ractor.yield(nil) compiled(41) end tp = TracePoint.new(:line) { itself } ractor.take tp.enable ractor.take } # Test equality with changing types assert_equal '[true, false, false, false]', %q{ def eq(a, b) a == b end [ eq("foo", "foo"), eq("foo", "bar"), eq(:foo, "bar"), eq("foo", :bar) ] } # Redefined String eq assert_equal 'true', %q{ class String def ==(other) true end end def eq(a, b) a == b end eq("foo", "bar") eq("foo", "bar") } # Redefined Integer eq assert_equal 'true', %q{ class Integer def ==(other) true end end def eq(a, b) a == b end eq(1, 2) eq(1, 2) } # aset on array with invalid key assert_normal_exit %q{ def foo(arr) arr[:foo] = 123 end foo([1]) rescue nil foo([1]) rescue nil } # test ractor exception on when setting ivar assert_equal '42', %q{ class A def self.foo _foo = 1 _bar = 2 begin @bar = _foo + _bar rescue Ractor::IsolationError 42 end end end A.foo A.foo Ractor.new { A.foo }.take } assert_equal '["plain", "special", "sub", "plain"]', %q{ def foo(arg) arg.to_s end class Sub < String end special = String.new("special") special.singleton_class [ foo("plain"), foo(special), foo(Sub.new("sub")), foo("plain") ] } assert_equal '["sub", "sub"]', %q{ def foo(arg) arg.to_s end class Sub < String def to_s super end end sub = Sub.new("sub") [foo(sub), foo(sub)] } assert_equal '[1]', %q{ def kwargs(value:) value end 5.times.map { kwargs(value: 1) }.uniq } assert_equal '[:ok]', %q{ def kwargs(value:) value end 5.times.map { kwargs() rescue :ok }.uniq } assert_equal '[:ok]', %q{ def kwargs(a:, b: nil) value end 5.times.map { kwargs(b: 123) rescue :ok }.uniq } assert_equal '[[1, 2]]', %q{ def kwargs(left:, right:) [left, right] end 5.times.flat_map do [ kwargs(left: 1, right: 2), kwargs(right: 2, left: 1) ] end.uniq } assert_equal '[[1, 2]]', %q{ def kwargs(lead, kwarg:) [lead, kwarg] end 5.times.map { kwargs(1, kwarg: 2) }.uniq } # optional and keyword args assert_equal '[[1, 2, 3]]', %q{ def opt_and_kwargs(a, b=2, c: nil) [a,b,c] end 5.times.map { opt_and_kwargs(1, c: 3) }.uniq } assert_equal '[[1, 2, 3]]', %q{ def opt_and_kwargs(a, b=nil, c: nil) [a,b,c] end 5.times.map { opt_and_kwargs(1, 2, c: 3) }.uniq } # Bug #18453 assert_equal '[[1, nil, 2]]', %q{ def opt_and_kwargs(a = {}, b: nil, c: nil) [a, b, c] end 5.times.map { opt_and_kwargs(1, c: 2) }.uniq } assert_equal '[[{}, nil, 1]]', %q{ def opt_and_kwargs(a = {}, b: nil, c: nil) [a, b, c] end 5.times.map { opt_and_kwargs(c: 1) }.uniq } # leading and keyword arguments are swapped into the right order assert_equal '[[1, 2, 3, 4, 5, 6]]', %q{ def kwargs(five, six, a:, b:, c:, d:) [a, b, c, d, five, six] end 5.times.flat_map do [ kwargs(5, 6, a: 1, b: 2, c: 3, d: 4), kwargs(5, 6, a: 1, b: 2, d: 4, c: 3), kwargs(5, 6, a: 1, c: 3, b: 2, d: 4), kwargs(5, 6, a: 1, c: 3, d: 4, b: 2), kwargs(5, 6, a: 1, d: 4, b: 2, c: 3), kwargs(5, 6, a: 1, d: 4, c: 3, b: 2), kwargs(5, 6, b: 2, a: 1, c: 3, d: 4), kwargs(5, 6, b: 2, a: 1, d: 4, c: 3), kwargs(5, 6, b: 2, c: 3, a: 1, d: 4), kwargs(5, 6, b: 2, c: 3, d: 4, a: 1), kwargs(5, 6, b: 2, d: 4, a: 1, c: 3), kwargs(5, 6, b: 2, d: 4, c: 3, a: 1), kwargs(5, 6, c: 3, a: 1, b: 2, d: 4), kwargs(5, 6, c: 3, a: 1, d: 4, b: 2), kwargs(5, 6, c: 3, b: 2, a: 1, d: 4), kwargs(5, 6, c: 3, b: 2, d: 4, a: 1), kwargs(5, 6, c: 3, d: 4, a: 1, b: 2), kwargs(5, 6, c: 3, d: 4, b: 2, a: 1), kwargs(5, 6, d: 4, a: 1, b: 2, c: 3), kwargs(5, 6, d: 4, a: 1, c: 3, b: 2), kwargs(5, 6, d: 4, b: 2, a: 1, c: 3), kwargs(5, 6, d: 4, b: 2, c: 3, a: 1), kwargs(5, 6, d: 4, c: 3, a: 1, b: 2), kwargs(5, 6, d: 4, c: 3, b: 2, a: 1) ] end.uniq } # implicit hashes get skipped and don't break compilation assert_equal '[[:key]]', %q{ def implicit(hash) hash.keys end 5.times.map { implicit(key: :value) }.uniq } # default values on keywords don't mess up argument order assert_equal '[2]', %q{ def default_value 1 end def default_expression(value: default_value) value end 5.times.map { default_expression(value: 2) }.uniq } # constant default values on keywords assert_equal '[3]', %q{ def default_expression(value: 3) value end 5.times.map { default_expression }.uniq } # non-constant default values on keywords assert_equal '[3]', %q{ def default_value 3 end def default_expression(value: default_value) value end 5.times.map { default_expression }.uniq } # reordered optional kwargs assert_equal '[[100, 1]]', %q{ def foo(capacity: 100, max: nil) [capacity, max] end 5.times.map { foo(max: 1) }.uniq } # invalid lead param assert_equal 'ok', %q{ def bar(baz: 2) baz end def foo bar(1, baz: 123) end begin foo foo rescue ArgumentError => e print "ok" end } # reordered required kwargs assert_equal '[[1, 2, 3, 4]]', %q{ def foo(default1: 1, required1:, default2: 3, required2:) [default1, required1, default2, required2] end 5.times.map { foo(required1: 2, required2: 4) }.uniq } # reordered default expression kwargs assert_equal '[[:one, :two, 3]]', %q{ def foo(arg1: (1+0), arg2: (2+0), arg3: (3+0)) [arg1, arg2, arg3] end 5.times.map { foo(arg2: :two, arg1: :one) }.uniq } # complex kwargs assert_equal '[[1, 2, 3, 4]]', %q{ def foo(required:, specified: 999, simple_default: 3, complex_default: "4".to_i) [required, specified, simple_default, complex_default] end 5.times.map { foo(specified: 2, required: 1) }.uniq } # cfunc kwargs assert_equal '{:foo=>123}', %q{ def foo(bar) bar.store(:value, foo: 123) bar[:value] end foo({}) foo({}) } # cfunc kwargs assert_equal '{:foo=>123}', %q{ def foo(bar) bar.replace(foo: 123) end foo({}) foo({}) } # cfunc kwargs assert_equal '{:foo=>123, :bar=>456}', %q{ def foo(bar) bar.replace(foo: 123, bar: 456) end foo({}) foo({}) } # variadic cfunc kwargs assert_equal '{:foo=>123}', %q{ def foo(bar) bar.merge(foo: 123) end foo({}) foo({}) } # optimized cfunc kwargs assert_equal 'false', %q{ def foo :foo.eql?(foo: :foo) end foo foo } # attr_reader on frozen object assert_equal 'false', %q{ class Foo attr_reader :exception def failed? !exception.nil? end end foo = Foo.new.freeze foo.failed? foo.failed? } # regression test for doing kwarg shuffle before checking for interrupts assert_equal 'ok', %q{ def new_media_drop(attributes:, product_drop:, context:, sources:) nil.nomethod rescue nil # force YJIT to bail to side exit [attributes, product_drop, context, sources] end def load_medias(product_drop: nil, raw_medias:, context:) raw_medias.map do |raw_media| case new_media_drop(context: context, attributes: raw_media, product_drop: product_drop, sources: []) in [Hash, ProductDrop, Context, Array] else raise "bad shuffle" end end end class Context; end class ProductDrop attr_reader :title def initialize(title) @title = title end end # Make a thread so we have thread switching interrupts th = Thread.new do while true; end end 1_000.times do |i| load_medias(product_drop: ProductDrop.new("foo"), raw_medias: [{}, {}], context: Context.new) end th.kill.join :ok } # regression test for tracing attr_accessor methods. assert_equal "true", %q{ c = Class.new do attr_accessor :x alias y x alias y= x= end obj = c.new ar_meth = obj.method(:x) aw_meth = obj.method(:x=) aar_meth = obj.method(:y) aaw_meth = obj.method(:y=) events = [] trace = TracePoint.new(:c_call, :c_return){|tp| next if tp.path != __FILE__ next if tp.method_id == :call case tp.event when :c_call events << [tp.event, tp.method_id, tp.callee_id] when :c_return events << [tp.event, tp.method_id, tp.callee_id, tp.return_value] end } test_proc = proc do obj.x = 1 obj.x obj.y = 2 obj.y aw_meth.call(1) ar_meth.call aaw_meth.call(2) aar_meth.call end test_proc.call # populate call caches trace.enable(&test_proc) expected = [ [:c_call, :x=, :x=], [:c_return, :x=, :x=, 1], [:c_call, :x, :x], [:c_return, :x, :x, 1], [:c_call, :x=, :y=], [:c_return, :x=, :y=, 2], [:c_call, :x, :y], [:c_return, :x, :y, 2], ] * 2 expected == events } # duphash assert_equal '{:foo=>123}', %q{ def foo {foo: 123} end foo foo } # newhash assert_equal '{:foo=>2}', %q{ def foo {foo: 1+1} end foo foo } # block invalidation edge case assert_equal 'undef', %q{ class A def foo(arg) arg.times { A.remove_method(:bar) } self end def bar 4 end def use(arg) # two consecutive sends. When bar is removed, the return address # for calling it is already on foo's control frame foo(arg).bar rescue NoMethodError :undef end end A.new.use 0 A.new.use 0 A.new.use 1 } # block invalidation edge case assert_equal 'ok', %q{ class A Good = :ng def foo(arg) arg.times { A.const_set(:Good, :ok) } self end def id(arg) arg end def use(arg) # send followed by an opt_getinlinecache. # The return address remains on the control frame # when opt_getinlinecache is invalidated. foo(arg).id(Good) end end A.new.use 0 A.new.use 0 A.new.use 1 } assert_equal 'ok', %q{ # test hitting a branch stub when out of memory def nimai(jita) if jita :ng else :ok end end nimai(true) nimai(true) RubyVM::YJIT.simulate_oom! if defined?(RubyVM::YJIT) nimai(false) } assert_equal 'new', %q{ # test block invalidation while out of memory def foo :old end def test foo end def bar :bar end test test RubyVM::YJIT.simulate_oom! if defined?(RubyVM::YJIT) # Old simulat_omm! leaves one byte of space and this fills it up bar bar def foo :new end test } assert_equal 'ok', %q{ # Try to compile new method while OOM def foo :ok end RubyVM::YJIT.simulate_oom! if defined?(RubyVM::YJIT) foo foo } # struct aref embedded assert_equal '2', %q{ def foo(s) s.foo end S = Struct.new(:foo) foo(S.new(1)) foo(S.new(2)) } # struct aref non-embedded assert_equal '4', %q{ def foo(s) s.d end S = Struct.new(:a, :b, :c, :d, :e) foo(S.new(1,2,3,4,5)) foo(S.new(1,2,3,4,5)) } # struct aset embedded assert_equal '123', %q{ def foo(s) s.foo = 123 end s = Struct.new(:foo).new foo(s) s = Struct.new(:foo).new foo(s) s.foo } # struct aset non-embedded assert_equal '[1, 2, 3, 4, 5]', %q{ def foo(s) s.a = 1 s.b = 2 s.c = 3 s.d = 4 s.e = 5 end S = Struct.new(:a, :b, :c, :d, :e) s = S.new foo(s) s = S.new foo(s) [s.a, s.b, s.c, s.d, s.e] } # struct aref too many args assert_equal 'ok', %q{ def foo(s) s.foo(:bad) end s = Struct.new(:foo).new foo(s) rescue :ok foo(s) rescue :ok } # struct aset too many args assert_equal 'ok', %q{ def foo(s) s.set_foo(123, :bad) end s = Struct.new(:foo) do alias :set_foo :foo= end foo(s) rescue :ok foo(s) rescue :ok } # File.join is a cfunc accepting variable arguments as a Ruby array (argc = -2) assert_equal 'foo/bar', %q{ def foo File.join("foo", "bar") end foo foo } # File.join is a cfunc accepting variable arguments as a Ruby array (argc = -2) assert_equal '', %q{ def foo File.join() end foo foo } # Make sure we're correctly reading RStruct's as.ary union for embedded RStructs assert_equal '3,12', %q{ pt_struct = Struct.new(:x, :y) p = pt_struct.new(3, 12) def pt_inspect(pt) "#{pt.x},#{pt.y}" end # Make sure pt_inspect is JITted 10.times { pt_inspect(p) } # Make sure it's returning '3,12' instead of e.g. '3,false' pt_inspect(p) } # Regression test for deadlock between branch_stub_hit and ractor_receive_if assert_equal '10', %q{ r = Ractor.new Ractor.current do |main| main << 1 main << 2 main << 3 main << 4 main << 5 main << 6 main << 7 main << 8 main << 9 main << 10 end a = [] a << Ractor.receive_if{|msg| msg == 10} a << Ractor.receive_if{|msg| msg == 9} a << Ractor.receive_if{|msg| msg == 8} a << Ractor.receive_if{|msg| msg == 7} a << Ractor.receive_if{|msg| msg == 6} a << Ractor.receive_if{|msg| msg == 5} a << Ractor.receive_if{|msg| msg == 4} a << Ractor.receive_if{|msg| msg == 3} a << Ractor.receive_if{|msg| msg == 2} a << Ractor.receive_if{|msg| msg == 1} a.length } # checktype assert_equal 'false', %q{ def function() [1, 2] in [Integer, String] end function() } # opt_send_without_block (VM_METHOD_TYPE_ATTRSET) assert_equal 'foo', %q{ class Foo attr_writer :foo def foo() self.foo = "foo" end end foo = Foo.new foo.foo } # anytostring, intern assert_equal 'true', %q{ def foo() :"#{true}" end foo() } # toregexp, objtostring assert_equal '/true/', %q{ def foo() /#{true}/ end foo().inspect } # concatstrings, objtostring assert_equal '9001', %q{ def foo() "#{9001}" end foo() } # opt_send_without_block (VM_METHOD_TYPE_CFUNC) assert_equal 'nil', %q{ def foo nil.inspect # argc: 0 end foo } assert_equal '4', %q{ def foo 2.pow(2) # argc: 1 end foo } assert_equal 'aba', %q{ def foo "abc".tr("c", "a") # argc: 2 end foo } assert_equal 'true', %q{ def foo respond_to?(:inspect) # argc: -1 end foo } assert_equal '["a", "b"]', %q{ def foo "a\nb".lines(chomp: true) # kwargs end foo } # invokebuiltin assert_equal '123', %q{ def foo(obj) obj.foo = 123 end struct = Struct.new(:foo) obj = struct.new foo(obj) } # invokebuiltin_delegate assert_equal '.', %q{ def foo(path) Dir.open(path).path end foo(".") } # opt_invokebuiltin_delegate_leave assert_equal '[0]', %q{"\x00".unpack("c")} # opt_send_without_block (VM_METHOD_TYPE_ISEQ) assert_equal '1', %q{ def foo = 1 def bar = foo bar } assert_equal '[1, 2, 3]', %q{ def foo(a, b) = [1, a, b] def bar = foo(2, 3) bar } assert_equal '[1, 2, 3, 4, 5, 6]', %q{ def foo(a, b, c:, d:, e: 0, f: 6) = [a, b, c, d, e, f] def bar = foo(1, 2, c: 3, d: 4, e: 5) bar } assert_equal '[1, 2, 3, 4]', %q{ def foo(a, b = 2) = [a, b] def bar = foo(1) + foo(3, 4) bar } assert_equal '1', %q{ def foo(a) = a def bar = foo(1) { 2 } bar } assert_equal '[1, 2]', %q{ def foo(a, &block) = [a, block.call] def bar = foo(1) { 2 } bar } # opt_send_without_block (VM_METHOD_TYPE_IVAR) assert_equal 'foo', %q{ class Foo attr_reader :foo def initialize @foo = "foo" end end Foo.new.foo } # opt_send_without_block (VM_METHOD_TYPE_OPTIMIZED) assert_equal 'foo', %q{ Foo = Struct.new(:bar) Foo.new("bar").bar = "foo" } assert_equal 'foo', %q{ Foo = Struct.new(:bar) Foo.new("foo").bar } # getblockparamproxy assert_equal 'foo', %q{ def foo(&block) block.call end foo { "foo" } } # getblockparam assert_equal 'foo', %q{ def foo(&block) block end foo { "foo" }.call } assert_equal '[1, 2]', %q{ def foo x = [2] [1, *x] end foo foo } # respond_to? with changing symbol assert_equal 'false', %q{ def foo(name) :sym.respond_to?(name) end foo(:to_s) foo(:to_s) foo(:not_exist) } # respond_to? with method being defined assert_equal 'true', %q{ def foo :sym.respond_to?(:not_yet_defined) end foo foo module Kernel def not_yet_defined = true end foo } # respond_to? with undef method assert_equal 'false', %q{ module Kernel def to_be_removed = true end def foo :sym.respond_to?(:to_be_removed) end foo foo class Object undef_method :to_be_removed end foo } # respond_to? with respond_to_missing? assert_equal 'true', %q{ class Foo end def foo(x) x.respond_to?(:bar) end foo(Foo.new) foo(Foo.new) class Foo def respond_to_missing?(*) = true end foo(Foo.new) } # bmethod assert_equal '[1, 2, 3]', %q{ one = 1 define_method(:foo) do one end 3.times.map { |i| foo + i } } # return inside bmethod assert_equal 'ok', %q{ define_method(:foo) do 1.tap { return :ok } end foo } # bmethod optional and keywords assert_equal '[[1, nil, 2]]', %q{ define_method(:opt_and_kwargs) do |a = {}, b: nil, c: nil| [a, b, c] end 5.times.map { opt_and_kwargs(1, c: 2) }.uniq } # bmethod with forwarded block assert_equal '2', %q{ define_method(:foo) do |&block| block.call end def bar(&block) foo(&block) end bar { 1 } bar { 2 } } # bmethod with forwarded block and arguments assert_equal '5', %q{ define_method(:foo) do |n, &block| n + block.call end def bar(n, &block) foo(n, &block) end bar(0) { 1 } bar(3) { 2 } } # bmethod with forwarded unwanted block assert_equal '1', %q{ one = 1 define_method(:foo) do one end def bar(&block) foo(&block) end bar { } bar { } } # test for return stub lifetime issue assert_equal '1', %q{ def foo(n) if n == 2 return 1.times { Object.define_method(:foo) {} } end foo(n + 1) end foo(1) } # case-when with redefined === assert_equal 'ok', %q{ class Symbol def ===(a) true end end def cw(arg) case arg when :b :ok when 4 :ng end end cw(4) } assert_equal 'threw', %q{ def foo(args) wrap(*args) rescue ArgumentError 'threw' end def wrap(a) [a] end foo([Hash.ruby2_keywords_hash({})]) } assert_equal 'threw', %q{ # C call def bar(args) Array(*args) rescue ArgumentError 'threw' end bar([Hash.ruby2_keywords_hash({})]) } # Test instance_of? and is_a? assert_equal 'true', %q{ 1.instance_of?(Integer) && 1.is_a?(Integer) } # Test instance_of? and is_a? for singleton classes assert_equal 'true', %q{ a = [] def a.test = :test a.instance_of?(Array) && a.is_a?(Array) } # Test instance_of? for singleton_class # Yes this does really return false assert_equal 'false', %q{ a = [] def a.test = :test a.instance_of?(a.singleton_class) } # Test is_a? for singleton_class assert_equal 'true', %q{ a = [] def a.test = :test a.is_a?(a.singleton_class) } # Test send with splat to a cfunc assert_equal 'true', %q{ 1.send(:==, 1, *[]) } # Test empty splat with cfunc assert_equal '2', %q{ def foo Integer.sqrt(4, *[]) end # call twice to deal with constant exiting foo foo } # Test non-empty splat with cfunc assert_equal 'Hello World', %q{ def bar args = ["Hello "] greeting = "World" greeting.insert(0, *args) greeting end bar } # Regression: this creates a temp stack with > 127 elements assert_normal_exit %q{ def foo(a) [ a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, ] end def entry foo(1) end entry } # Test that splat and rest combined # properly dupe the array assert_equal "[]", %q{ def foo(*rest) rest << 1 end def test(splat) foo(*splat) end EMPTY = [] custom = Object.new def custom.to_a EMPTY end test(custom) test(custom) EMPTY } # Rest with send assert_equal '[1, 2, 3]', %q{ def bar(x, *rest) rest.insert(0, x) end send(:bar, 1, 2, 3) } # Fix splat block arg bad compilation assert_equal "foo", %q{ def literal(*args, &block) s = ''.dup literal_append(s, *args, &block) s end def literal_append(sql, v) sql << v end literal("foo") } # regression test for accidentally having a parameter truncated # due to Rust/C signature mismatch. Used to crash with # > [BUG] rb_vm_insn_addr2insn: invalid insn address ... # or # > ... `Err` value: TryFromIntError(())' assert_normal_exit %q{ n = 16384 eval( "def foo(arg); " + "_=arg;" * n + '_=1;' + "Object; end" ) foo 1 } # Regression test for CantCompile not using starting_ctx assert_normal_exit %q{ class Integer def ===(other) false end end def my_func(x) case x when 1 1 when 2 2 else 3 end end my_func(1) } # Regression test for CantCompile not using starting_ctx assert_equal "ArgumentError", %q{ def literal(*args, &block) s = ''.dup args = [1, 2, 3] literal_append(s, *args, &block) s end def literal_append(sql, v) [sql.inspect, v.inspect] end begin literal("foo") rescue ArgumentError "ArgumentError" end } # Rest with block # Simplified code from railsbench assert_equal '[{"/a"=>"b", :as=>:c, :via=>:post}, [], nil]', %q{ def match(path, *rest, &block) [path, rest, block] end def map_method(method, args, &block) options = args.last args.pop options[:via] = method match(*args, options, &block) end def post(*args, &block) map_method(:post, args, &block) end post "/a" => "b", as: :c } # Test rest and kw_args assert_equal '[true, true, true, true]', %q{ def my_func(*args, base: nil, sort: true) [args, base, sort] end def calling_my_func results = [] results << (my_func("test") == [["test"], nil, true]) results << (my_func("test", base: :base) == [["test"], :base, true]) results << (my_func("test", sort: false) == [["test"], nil, false]) results << (my_func("test", "other", base: :base) == [["test", "other"], :base, true]) results end calling_my_func } # Test Integer#[] with 2 args assert_equal '0', %q{ 3[0, 0] } # unspecified_bits + checkkeyword assert_equal '2', %q{ def callee = 1 # checkkeyword should see unspecified_bits=0 (use bar), not Integer 1 (set bar = foo). def foo(foo, bar: foo) = bar def entry(&block) # write 1 at stack[3]. Calling #callee spills stack[3]. 1 + (1 + (1 + (1 + callee))) # &block is written to a register instead of stack[3]. When &block is popped and # unspecified_bits is pushed, it must be written to stack[3], not to a register. foo(1, bar: 2, &block) end entry # call branch_stub_hit (spill temps) entry # doesn't call branch_stub_hit (not spill temps) } # Test rest and optional_params assert_equal '[true, true, true, true]', %q{ def my_func(stuff, base=nil, sort=true, *args) [stuff, base, sort, args] end def calling_my_func results = [] results << (my_func("test") == ["test", nil, true, []]) results << (my_func("test", :base) == ["test", :base, true, []]) results << (my_func("test", :base, false) == ["test", :base, false, []]) results << (my_func("test", :base, false, "other", "other") == ["test", :base, false, ["other", "other"]]) results end calling_my_func } # Test rest and optional_params and splat assert_equal '[true, true, true, true, true]', %q{ def my_func(stuff, base=nil, sort=true, *args) [stuff, base, sort, args] end def calling_my_func results = [] splat = ["test"] results << (my_func(*splat) == ["test", nil, true, []]) splat = [:base] results << (my_func("test", *splat) == ["test", :base, true, []]) splat = [:base, false] results << (my_func("test", *splat) == ["test", :base, false, []]) splat = [:base, false, "other", "other"] results << (my_func("test", *splat) == ["test", :base, false, ["other", "other"]]) splat = ["test", :base, false, "other", "other"] results << (my_func(*splat) == ["test", :base, false, ["other", "other"]]) results end calling_my_func } # Regresssion test: rest and optional and splat assert_equal 'true', %q{ def my_func(base=nil, *args) [base, args] end def calling_my_func array = [] my_func(:base, :rest1, *array) == [:base, [:rest1]] end calling_my_func } # Fix failed case for large splat assert_equal 'true', %q{ def d(a, b=:b) end def calling_func ary = 1380888.times; d(*ary) end begin calling_func rescue ArgumentError true end } unless defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? # Not yet working on RJIT # Regresssion test: register allocator on expandarray assert_equal '[]', %q{ func = proc { [] } proc do _x, _y = func.call end.call } # Catch TAG_BREAK in a non-FINISH frame with JIT code assert_equal '1', %q{ def entry catch_break end def catch_break while_true do break end 1 end def while_true while true yield end end entry } assert_equal '6', %q{ class Base def number = 1 + yield end class Sub < Base def number = super + 2 end Sub.new.number { 3 } } # Integer multiplication and overflow assert_equal '[6, -6, 9671406556917033397649408, -9671406556917033397649408, 21267647932558653966460912964485513216]', %q{ def foo(a, b) a * b end r1 = foo(2, 3) r2 = foo(2, -3) r3 = foo(2 << 40, 2 << 41) r4 = foo(2 << 40, -2 << 41) r5 = foo(1 << 62, 1 << 62) [r1, r2, r3, r4, r5] } # Integer multiplication and overflow (minimized regression test from test-basic) assert_equal '8515157028618240000', %q{2128789257154560000 * 4} # Inlined method calls assert_equal 'nil', %q{ def putnil = nil def entry = putnil entry.inspect } assert_equal '1', %q{ def putobject_1 = 1 def entry = putobject_1 entry } assert_equal 'false', %q{ def putobject(_unused_arg1) = false def entry = putobject(nil) entry } assert_equal 'true', %q{ def entry = yield entry { true } } assert_normal_exit %q{ ivars = 1024.times.map { |i| "@iv_#{i} = #{i}\n" }.join Foo = Class.new Foo.class_eval "def initialize() #{ivars} end" Foo.new } assert_equal '0', %q{ def spill 1.to_i # not inlined end def inline(_stack1, _stack2, _stack3, _stack4, _stack5) 0 # inlined end def entry # RegTemps is 00111110 prior to the #inline call. # Its return value goes to stack_idx=0, which conflicts with stack_idx=5. inline(spill, 2, 3, 4, 5) end entry }