Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

RSpec makes define_method public? #873

Merged
merged 3 commits into from

4 participants

@moll

Beats me, but I just stumbled upon a passing test that failed IRL because it used the private Module.define_method. Seems that after requiring RSpec, it's not private any more.

On Ruby v1.9.3 with RSpec v2.13:

>> Object.define_method
NoMethodError: private method `define_method' called for Object:Class
    from (irb):1
    from /usr/local/bin/irb:12:in `<main>'
>> require "rspec"
=> true
>> Object.define_method
ArgumentError: wrong number of arguments (0 for 1)
    from (irb):3:in `define_method'
    from (irb):3
    from /usr/local/bin/irb:12:in `<main>'
@thomas-holmes

Looks like it is intentionally being made to be this way in memoization_helpers.rb:475

@myronmarston

Looks like it is intentionally being made to be this way in memoization_helpers.rb:475

That's only making it public in an anonymous module, though. I don't think that affect Object, although I could be wrong...

Regardless, we should fix this. Thanks for reporting it!

@thomas-holmes

I will try to look into it further, I'd like to help :+1:

@thomas-holmes

I tried defining another method in the same place that was private (method_undefined) and it became public on Object. I think that definitively makes this the culprit. Any suggestions on how to proceed containing this?

@moll

Other than ditching Ruby? :) I'd say removing line 475 as the following holds true:

>> Object.define_method
NoMethodError: private method `define_method' called for Object:Class
        from (irb):1
        from /usr/local/bin/irb:12:in `<main>'
>> module Foo
>>   public_class_method :define_method
>> end
=> Foo
>> Object.define_method
ArgumentError: wrong number of arguments (0 for 1)
        from (irb):3:in `define_method'
        from (irb):3
        from /usr/local/bin/irb:12:in `<main>'
@myronmarston

To me, it feels like a bug in ruby. Regardless, we should fix the code so it doesn't do this. The reason I had put public_class_method :define_method was to make it easy to define new methods on those modules, as that's basically the role they serve. We can remove that line and use send instead, though. Alternately, if you can find another way to make define_method public w/o it leaking....then have at it :).

@thomas-holmes thomas-holmes referenced this pull request from a commit in thomas-holmes/rspec-core
@thomas-holmes thomas-holmes No longer make define_method public, use send instead.
MemoizedHelpers::ClassMethods module was making define_method
public for ease of use within the module. No longer do this and
use send instead. This fixes rspec/rspec-core#873.
5280e95
@JonRowe JonRowe was assigned
@JonRowe
Owner

I'm guessing public_class_method works on the original class it's defined on rather than the one we're creating, changing how we define the method as public (by redefining the method) works on the current class.

I've also added a test to ensure we don't leak it.

Review? /cc @myronmarston @samphippen @soulcutter

@moll

Hehe, after reporting this I investigated how visibility works in Ruby and found out the public method applies only to instance methods of the current class and thereby does not apply to methods set on the singleton class. It's a nuts system because the public you, @JonRowe, added on line 475 is useless.

Which, on a side note, is probably a SNAFU everywhere where people expect methods defined via def self.foo following private to be private.

@myronmarston

Thanks to both of you for working on this!

The spec added by @JonRowe is nice (we want specs for this kind of stuff to prevent regressions), but I think I prefer the simplicity of the @thomas-holmes's fix. I had originally made the method public as a convenience...but the solution of redefining define_method to make it public seems more complicated then just using send.

JonRowe and others added some commits
@JonRowe JonRowe test that we dont leak define_method into public methods fbc33d6
@thomas-holmes thomas-holmes No longer make define_method public, use send instead.
MemoizedHelpers::ClassMethods module was making define_method
public for ease of use within the module. No longer do this and
use send instead. This fixes rspec/rspec-core#873.
4d3a64d
@JonRowe
Owner

Ok I've picked across @thomas-holmes' fix, just rerunning the specs then I'll merge it.

@myronmarston

@JonRowe -- thanks! Please add a changelog entry, too.

@thomas-holmes

Thanks guys!

@JonRowe JonRowe merged commit 5fd60b5 into from
@myronmarston

So this is interesting:

https://bugs.ruby-lang.org/issues/8284

I started a discussion on the ruby rogues mailing list about this, someone reporte the bug to the ruby issue tracker and it got fixed. Turns out it's been a bug in MRI since ruby 1.0.

@JonRowe
Owner

I liked this bit

I don't know if it will be, but in any case there is always
`singleton_class.send :public, :define_method`
instead or equivalent.

It's a bit more clear, IMO, as using "public_class_method" on a Module is a bit strange...
Modules don't really have "class methods".

FWIW, there are no specs in RubySpec about "public_class_method" on a Module.
@kalabiyau kalabiyau referenced this pull request in b4mboo/git-review
Closed

Add ruby 2.0 to travis test list #53

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Apr 18, 2013
  1. @JonRowe
  2. @thomas-holmes @JonRowe

    No longer make define_method public, use send instead.

    thomas-holmes authored JonRowe committed
    MemoizedHelpers::ClassMethods module was making define_method
    public for ease of use within the module. No longer do this and
    use send instead. This fixes rspec/rspec-core#873.
  3. @JonRowe

    update changelog

    JonRowe authored
This page is out of date. Refresh to see the latest.
View
2  Changelog.md
@@ -44,6 +44,8 @@ Bug fixes
* Fix `Example#full_description` so that it gets filled in by the last
matcher description (as `Example#description` already did) when no
doc string has been provided (David Chelimsky).
+* Fix the memoized methods (`let` and `subject`) leaking `define_method`
+ as a `public` method. (Thomas Holmes and Jon Rowe) (#873)
Deprecations
View
9 lib/rspec/core/memoized_helpers.rb
@@ -192,7 +192,7 @@ def let(name, &block)
# We have to pass the block directly to `define_method` to
# allow it to use method constructs like `super` and `return`.
raise "#let or #subject called without a block" if block.nil?
- MemoizedHelpers.module_for(self).define_method(name, &block)
+ MemoizedHelpers.module_for(self).send(:define_method, name, &block)
# Apply the memoization. The method has been defined in an ancestor
# module so we can use `super` here to get the value.
@@ -293,7 +293,7 @@ def subject(name=nil, &block)
let(name, &block)
alias_method :subject, name
- self::NamedSubjectPreventSuper.define_method(name) do
+ self::NamedSubjectPreventSuper.send(:define_method, name) do
raise NotImplementedError, "`super` in named subjects is not supported"
end
else
@@ -466,13 +466,8 @@ def self.module_for(example_group)
get_constant_or_yield(example_group, :LetDefinitions) do
mod = Module.new do
include Module.new {
- public_class_method :define_method
example_group.const_set(:NamedSubjectPreventSuper, self)
}
-
- # Expose `define_method` as a public method, so we can
- # easily use it below.
- public_class_method :define_method
end
example_group.__send__(:include, mod)
View
7 spec/rspec/core/memoized_helpers_spec.rb
@@ -659,5 +659,12 @@ def define_and_run_group
end
end
end
+
+ describe 'Module#define_method' do
+ it 'is still a private method' do
+ a_module = Module.new
+ expect { a_module.define_method(:name) { "implementation" } }.to raise_error NoMethodError
+ end
+ end
end
Something went wrong with that request. Please try again.