Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Crash involving the use of DelegateClass in Ruby 2.7 #1273

Closed
backus opened this issue Nov 7, 2021 · 7 comments
Closed

Crash involving the use of DelegateClass in Ruby 2.7 #1273

backus opened this issue Nov 7, 2021 · 7 comments
Assignees
Labels

Comments

@backus
Copy link
Contributor

backus commented Nov 7, 2021

I'm working on upgrading an app to Ruby 2.7 from Ruby 2.6.8 and ran into a crash that only happens under 2.7.

Consider the following code:

require 'delegate'

module MutantCrash
  class Foo
  end

  class Thing < DelegateClass(Foo)
    def bar; end
  end
end

Running under 2.6 works fine:

$ ruby -v
ruby 2.6.6p146 (2020-03-31 revision 67876) [x86_64-darwin19]

$ cat Gemfile.lock | grep mutant
  remote: https://com:xxx@gem.mutant.dev/
    mutant (0.10.35)
    mutant-license (0.1.1.1.3319570810364303218730943090542303819067.3)
    mutant-rspec (0.10.35)
      mutant (= 0.10.35)
  mutant (~> 0.10.35)
  mutant-license!
  mutant-rspec (~> 0.10.35)

$ bundle exec mutant run --include lib --require example --use rspec 'MutantCrash*' > /dev/null
warning: parser/current is loading parser/ruby26, which recognizes
warning: 2.6.8-compliant syntax, but you are running 2.6.6.
warning: please see https://github.com/whitequark/parser#compatibility-with-ruby-mri.
Uncovered mutations detected, exiting nonzero!

2.7 produces a crash

$ ruby -v
ruby 2.7.4p191 (2021-07-07 revision a21a3b7d23) [x86_64-darwin20]

$ cat Gemfile.lock | grep mutant
  remote: https://com:xxx@gem.mutant.dev/
    mutant (0.10.35)
    mutant-license (0.1.1.1.3319570810364303218730943090542303819067.3)
    mutant-rspec (0.10.35)
      mutant (= 0.10.35)
  mutant (~> 0.10.35)
  mutant-license!
  mutant-rspec (~> 0.10.35)

$ bundle exec mutant run --include lib --require example --use rspec 'MutantCrash*' > /dev/null
Traceback (most recent call last):
	45: from /dev/gems/ruby-2.7.4/bin/ruby_executable_hooks:22:in `<main>'
	44: from /dev/gems/ruby-2.7.4/bin/ruby_executable_hooks:22:in `eval'
	43: from /dev/gems/ruby-2.7.4/bin/mutant:23:in `<main>'
	42: from /dev/gems/ruby-2.7.4/bin/mutant:23:in `load'
	41: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/bin/mutant:47:in `<top (required)>'
	40: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/cli/command.rb:55:in `call'
	39: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/cli/command.rb:81:in `execute'
	38: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/cli/command/environment/run.rb:44:in `action'
	37: from /dev/gems/ruby-2.7.4/gems/unparser-0.6.0/lib/unparser/either.rb:115:in `bind'
	36: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/cli/command/environment/run.rb:44:in `block in action'
	35: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/cli/command/environment.rb:28:in `bootstrap'
	34: from /dev/gems/ruby-2.7.4/gems/unparser-0.6.0/lib/unparser/either.rb:115:in `bind'
	33: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/cli/command/environment.rb:28:in `block in bootstrap'
	32: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/bootstrap.rb:38:in `call'
	31: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/matcher/filter.rb:16:in `call'
	30: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/matcher/chain.rb:15:in `call'
	29: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/matcher/chain.rb:15:in `flat_map'
	28: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/matcher/chain.rb:15:in `each'
	27: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/matcher/chain.rb:16:in `block in call'
	26: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/matcher/namespace.rb:17:in `call'
	25: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/matcher/chain.rb:15:in `call'
	24: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/matcher/chain.rb:15:in `flat_map'
	23: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/matcher/chain.rb:15:in `each'
	22: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/matcher/chain.rb:16:in `block in call'
	21: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/matcher/scope.rb:28:in `call'
	20: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/matcher/chain.rb:15:in `call'
	19: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/matcher/chain.rb:15:in `flat_map'
	18: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/matcher/chain.rb:15:in `each'
	17: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/matcher/chain.rb:16:in `block in call'
	16: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/matcher/methods.rb:24:in `call'
	15: from /dev/gems/ruby-2.7.4/gems/unparser-0.6.0/lib/unparser/adamantium/method_builder.rb:90:in `block (2 levels) in create_memoized_method'
	14: from /dev/gems/ruby-2.7.4/gems/unparser-0.6.0/lib/unparser/adamantium.rb:59:in `fetch'
	13: from /dev/gems/ruby-2.7.4/gems/unparser-0.6.0/lib/unparser/adamantium.rb:59:in `fetch'
	12: from /dev/gems/ruby-2.7.4/gems/unparser-0.6.0/lib/unparser/adamantium.rb:60:in `block in fetch'
	11: from /dev/gems/ruby-2.7.4/gems/unparser-0.6.0/lib/unparser/adamantium.rb:60:in `synchronize'
	10: from /dev/gems/ruby-2.7.4/gems/unparser-0.6.0/lib/unparser/adamantium.rb:61:in `block (2 levels) in fetch'
	 9: from /dev/gems/ruby-2.7.4/gems/unparser-0.6.0/lib/unparser/adamantium.rb:61:in `fetch'
	 8: from /dev/gems/ruby-2.7.4/gems/unparser-0.6.0/lib/unparser/adamantium.rb:62:in `block (3 levels) in fetch'
	 7: from /dev/gems/ruby-2.7.4/gems/unparser-0.6.0/lib/unparser/adamantium/method_builder.rb:91:in `block (3 levels) in create_memoized_method'
	 6: from /dev/gems/ruby-2.7.4/gems/unparser-0.6.0/lib/unparser/adamantium/method_builder.rb:91:in `call'
	 5: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/matcher/methods.rb:35:in `methods'
	 4: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/matcher/methods.rb:35:in `each_with_object'
	 3: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/matcher/methods.rb:35:in `each'
	 2: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/matcher/methods.rb:36:in `block in methods'
	 1: from /dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/matcher/methods.rb:90:in `access'
/dev/gems/ruby-2.7.4/gems/mutant-0.10.35/lib/mutant/matcher/methods.rb:90:in `instance_method': undefined method `!~' for class `MutantCrash::Thing' (NameError)
Did you mean?  !

I believe this is caused by a regression / bug in Ruby 2.7 (or maybe they intentionally did this? No clue). I noticed that the result of {public,protected,private}_instance_methods returns more values in 2.7 than it does in 2.6:

$ ruby -v
ruby 2.6.8p205 (2021-07-07 revision 67951) [x86_64-darwin20]

$ ruby -I lib -r example -e 'puts MutantCrash::Thing.public_instance_methods.size'
59

$ rvm use 2.7.4
$ ruby -I lib -r example -e 'puts MutantCrash::Thing.public_instance_methods.size'
64

Not sure if ruby core just changed DelegateClass a lot in 2.7, but this issue from @pocke 1 year ago seems related: https://bugs.ruby-lang.org/issues/16982

@mbj
Copy link
Owner

mbj commented Nov 7, 2021

Having an interesting time reproducing this within mutants test suite. Outside its fine. Still on it.

@mbj
Copy link
Owner

mbj commented Nov 7, 2021

reproduced within nice.

@mbj
Copy link
Owner

mbj commented Nov 7, 2021

@backus This is a bug in 2.7. 2.6 had an imperfect delegate, 2.7 a buggy and 3.0 fixed the issue.

require 'delegate'

puts("Ruby-Version: #{RUBY_VERSION}")

class Foo < DelegateClass(String)
  def foo
  end
end

if Foo.public_instance_methods.any? { |name| name.equal?(:!~) }
  puts ":!~ does exist!"
  p Foo.instance_method(:!~) # crashes only in 2.7
else
  puts ":!~ does NOT exist!"
end

@mbj
Copy link
Owner

mbj commented Nov 7, 2021

Ruby-Version: 3.0.2
:!~ does exist!
#<UnboundMethod: String(Kernel)#!~(_)>
Ruby-Version: 2.6.6
:!~ does NOT exist!
Ruby-Version: 2.7.4
:!~ does exist!
Traceback (most recent call last):
	1: from x.rb:12:in `<main>'
x.rb:12:in `instance_method': undefined method `!~' for class `Foo' (NameError)
Did you mean?  !

@mbj mbj added the bug label Nov 7, 2021
@mbj mbj self-assigned this Nov 7, 2021
mbj added a commit that referenced this issue Nov 7, 2021
@mbj
Copy link
Owner

mbj commented Nov 7, 2021

@backus Can you check out #1275 (might be merged once you see this). It should make mutant skip over degenerate object interfaces like this case.

@mbj mbj closed this as completed in b642990 Nov 7, 2021
@backus
Copy link
Contributor Author

backus commented Nov 7, 2021

@mbj just tried it out and it seems to work perfectly! Once you cut 0.11.1 I will upgrade our app accordingly. Thanks for getting to this so fast! Helped unblock our upgrade process 😃

@mbj
Copy link
Owner

mbj commented Nov 8, 2021

@backus 0.11.1 was released last night.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants