Skip to content

Avoid calling >= on unknown receivers in ls command#1193

Closed
dejmedus wants to merge 1 commit intoruby:masterfrom
Shopify:call-methods-on-owned-obj
Closed

Avoid calling >= on unknown receivers in ls command#1193
dejmedus wants to merge 1 commit intoruby:masterfrom
Shopify:call-methods-on-owned-obj

Conversation

@dejmedus
Copy link
Copy Markdown

@dejmedus dejmedus commented Mar 27, 2026

If Ls#dump_methods is called with an obj whose ancestors alter >=, the comparison (c >= Object/c >= Class) may raise an error. For example:

class Foo; class << self; undef >=; end; end
foo = Foo.new
ls foo

Foo >= Object # NoMethodError     

To prevent this, we could defensively flip the comparison so that <= is called on known receivers

@dejmedus dejmedus marked this pull request as ready for review March 27, 2026 23:19
ancestors = ancestors.reject { |c| c >= Object } if klass < Object
singleton_ancestors = (singleton_class&.ancestors || []).reject { |c| c >= Class }
ancestors = ancestors.reject { |c| Object <= c } if klass < Object
singleton_ancestors = (singleton_class&.ancestors || []).reject { |c| Class <= c }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To solve the problem, the change looks good. However, ls foo ls Foo doesn't work when some other methods are missing:

<
constants
class_variables
to_s
ancestors
public_instance_methods

Unlike Object instances that we need to consider BasicObject, I think we can assume Classes doesn't undef/change most of these methods: constants, class_variables, to_s, ancestors, public_instance_methods.
But for <, I guess it might be removed and need to change if klass < Object to if Object > klass in the situation you are assuming.

Can you share what kind of situation :<= is removed? Will :> be removed too?
Do you know an existing gem that creates a class without :<=?
The correct fix depends on the situation what you are assuming, and needs to be commented in test code.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👋 Yes, sorry, I should have added more details! The situation came up in Rubydex where an undefined method '>=' NoMethodError occurred on c >= Class due to a shadowed singleton_class returning an instance rather than the real class (better details in this issue). I believe the singleton_class is okay due to #1177 and is being fixed in Rubydex, but this raised the idea that flipping the comparison might prevent situations where a foreign object behaved oddly when compared

need to change if klass < Object to if Object > klass in the situation

I've updated my commit with this change:)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for providing the background 👍
I've confirmed the bug that described in Shopify/rubydex#685 with IRB 1.17.0

irb(main):001* module Rubydex
irb(main):002*   class SingletonClass
irb(main):003*     def ancestors; [self] end
irb(main):004*   end
irb(main):005*   class Namespace
irb(main):006*     def singleton_class
irb(main):007*       SingletonClass.new
irb(main):008*     end
irb(main):009*   end
irb(main):010> end
irb(main):011> 
irb(main):012> 
=> :singleton_class
irb(main):013> ns = Rubydex::Namespace.new
=> #<Rubydex::Namespace:0x000000016221e2c0>
irb(main):014> ls ns
/path/to/.rbenv/versions/4.0.0/lib/ruby/gems/4.0.0/gems/irb-1.17.0/lib/irb/command/ls.rb:74:in 'block in IRB::Command::Ls#dump_methods': undefined method '>=' for an instance of Rubydex::SingletonClass (NoMethodError)

        singleton_ancestors = (singleton_class&.ancestors || []).reject { |c| c >= Class }

So the error is caused by:

Rubydex::SingletonClass.new >= Class
(irb):13:in '<main>': undefined method '>=' for an instance of Rubydex::SingletonClass (NoMethodError)

However, flipping the operator doesn't solve the problem. It still raises error because the receiver was not a Class that doesn't have :>=, but was a normal Object.

Class <= Rubydex::SingletonClass.new
(irb):14:in 'Module#<=': compared with non class/module (TypeError)

Previously, when calling comparison methods on
receivers we don't control (`c >= Object`), there
was a chance it could lack or have overridden the
method. This commit flips them to prevent this
`Object <= c`
@dejmedus dejmedus force-pushed the call-methods-on-owned-obj branch from d6cca2f to b1e82a0 Compare March 29, 2026 04:43
@tompng
Copy link
Copy Markdown
Member

tompng commented Mar 29, 2026

Closing because the original issue was not about Class that removed :>=, and already fixed by #1177

@tompng tompng closed this Mar 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants