Forum: Ruby class destruction (evil genius metaprogramming)

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Ce8b03e5750097942c58e12b46724312?d=identicon&s=25 Giles Bowkett (Guest)
on 2007-06-15 03:14
(Received via mailing list)
I want to write a module which, when included in another module,
destroys every method of a class in that module. I realize this isn't
really a very useful thing to do, but I want to do it to see if it can
be done. I want to actually include the module and have it replace all
existing methods of this class with just one method.

In Python you can change an object's class in the middle of your
program by reassigning its magical __class__ variable. That would be a
very simple way to implement this idea. Doesn't seem possible here,
though.

Here's an attempt which failed:

>> class Muppet
>>   def show
>>     "it's the muppet show!"
>>     end
>>   end
=> nil
>> kermit = Muppet.new
=> #<Muppet:0x123129c>
>> kermit.show
=> "it's the muppet show!"
>> class Muppet
>>   remove_method(:show)
>>   end
=> Muppet
>> kermit.show
NoMethodError: undefined method `show' for #<Muppet:0x123129c>
        from (irb):36
        from :0

So far so good, but I want to get rid of *every* method. Doing what I
just did programmatically, iterating over all available methods,
that's the thing which still eludes me.

None of these attempts work:

>> kermit.methods.each{|m| class << Muppet ; (remove_method(m)) ; end}
NameError: undefined local variable or method `m' for #<Class:Muppet>
        from (irb):43
        from (irb):43:in `each'
        from (irb):43
        from :0
>> kermit.methods.each{|m| class Muppet; remove_method(m.to_sym) ; end}
NameError: undefined local variable or method `m' for Muppet:Class
        from (irb):44
        from (irb):44:in `each'
        from (irb):44
        from :0
>> kermit.methods.each{|m| Muppet.class_eval(remove_method(m))}
NoMethodError: undefined method `remove_method' for #<Object:0x349f4>
        from (irb):45
        from (irb):45:in `each'
        from (irb):45
        from :0

I have to admit, it's not necessarily a bad thing if this task proves
impossible, but I feel like it *should* be possible.

--
Giles Bowkett

Blog: http://gilesbowkett.blogspot.com
Portfolio: http://www.gilesgoatboy.org
8f6f95c4bd64d5f10dfddfdcd03c19d6?d=identicon&s=25 Rick Denatale (rdenatale)
on 2007-06-15 03:56
(Received via mailing list)
On 6/14/07, Giles Bowkett <gilesb@gmail.com> wrote:
>
> >> kermit.show
> So far so good, but I want to get rid of *every* method. Doing what I
>         from :0
>         from (irb):45
>         from :0
>
> I have to admit, it's not necessarily a bad thing if this task proves
> impossible, but I feel like it *should* be possible.

kermit.class.instance_methods(false).each{|m|
Muppet.instance_eval("remove_method #{m.inspect}")}

Note 1. kermit.methods will return all methods inherited or not.
Note 2. remove method is a class method, so it's evaluated in the
context of the class object, hence Muppet.instance_eval
Note 3: you could alternatively pass symbols for the method names

kermit.class.instance_methods(false).each{|m|
Muppet.instance_eval("remove_method :#{m}")}



--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/

IPMS/USA Region 12 Coordinator
http://ipmsr12.denhaven2.com/

Visit the Project Mercury Wiki Site
http://www.mercuryspacecraft.com/
Ce8b03e5750097942c58e12b46724312?d=identicon&s=25 Giles Bowkett (Guest)
on 2007-06-15 04:27
(Received via mailing list)
> kermit.class.instance_methods(false).each{|m|
> Muppet.instance_eval("remove_method #{m.inspect}")}
>
> Note 1. kermit.methods will return all methods inherited or not.
> Note 2. remove method is a class method, so it's evaluated in the
> context of the class object, hence Muppet.instance_eval
> Note 3: you could alternatively pass symbols for the method names
>
> kermit.class.instance_methods(false).each{|m|
> Muppet.instance_eval("remove_method :#{m}")}

Muahahaha! That was awesome! Soon the world will quake in fear when I
put this information to use!

But why doesn't this work?

>> kermit.methods(false).each do |m|
?>     Object.instance_eval("remove_method :#{m}")
>>   end
=> []
>> kermit.methods
=> 53

I tried it with Kernel also and no dice.

Tried it with something else, though, and got a great error message:

(eval):1: warning: removing `initialize' may cause serious problem

Muahahaha!

--
Giles Bowkett

Blog: http://gilesbowkett.blogspot.com
Portfolio: http://www.gilesgoatboy.org
E87bf52b19c95ec977aba1c0c777f752?d=identicon&s=25 Stephen Smith (Guest)
on 2007-06-15 06:04
(Received via mailing list)
Why should this inspire fear?
1fba4539b6cafe2e60a2916fa184fc2f?d=identicon&s=25 unknown (Guest)
on 2007-06-15 13:20
(Received via mailing list)
Hi --

On Fri, 15 Jun 2007, Rick DeNatale wrote:

> kermit.class.instance_methods(false).each{|m|
> Muppet.instance_eval("remove_method #{m.inspect}")}
>
> Note 1. kermit.methods will return all methods inherited or not.
> Note 2. remove method is a class method, so it's evaluated in the
> context of the class object, hence Muppet.instance_eval
> Note 3: you could alternatively pass symbols for the method names
>
> kermit.class.instance_methods(false).each{|m|
> Muppet.instance_eval("remove_method :#{m}")}

You can also just use the block form of instance_eval:

   Muppet.instance_eval { remove_method(m) }


David
703fbc991fd63e0e1db54dca9ea31b53?d=identicon&s=25 Robert Dober (Guest)
on 2007-06-15 13:26
(Received via mailing list)
On 6/15/07, dblack@wobblini.net <dblack@wobblini.net> wrote:
> > Note 3: you could alternatively pass symbols for the method names
> >
> > kermit.class.instance_methods(false).each{|m|
> > Muppet.instance_eval("remove_method :#{m}")}
>
> You can also just use the block form of instance_eval:
>
>    Muppet.instance_eval { remove_method(m) }

or Muppet.send :remove_method, :m

Cheers
Robert
1fba4539b6cafe2e60a2916fa184fc2f?d=identicon&s=25 unknown (Guest)
on 2007-06-15 15:51
(Received via mailing list)
Hi --

On Fri, 15 Jun 2007, Robert Dober wrote:

>> > context of the class object, hence Muppet.instance_eval
>> > Note 3: you could alternatively pass symbols for the method names
>> >
>> > kermit.class.instance_methods(false).each{|m|
>> > Muppet.instance_eval("remove_method :#{m}")}
>>
>> You can also just use the block form of instance_eval:
>>
>>    Muppet.instance_eval { remove_method(m) }
>
> or Muppet.send :remove_method, :m

If you have a method called "m" :-)


David
1fba4539b6cafe2e60a2916fa184fc2f?d=identicon&s=25 unknown (Guest)
on 2007-06-15 15:57
(Received via mailing list)
Hi --

On Fri, 15 Jun 2007, Giles Bowkett wrote:

>
> => 53
kermit.methods(false) will give you kermit's singleton methods:

irb(main):019:0> s = ""
=> ""
irb(main):020:0> def s.x; end
=> nil
irb(main):021:0> s.methods(false)
=> ["x"]

Also, you can't remove these methods from Object because they're not
defined there.  You'd have to do:

   class << s
     remove_method(:x)
   end


David
703fbc991fd63e0e1db54dca9ea31b53?d=identicon&s=25 Robert Dober (Guest)
on 2007-06-15 16:05
(Received via mailing list)
On 6/15/07, dblack@wobblini.net <dblack@wobblini.net> wrote:
> >> > Muppet.instance_eval("remove_method #{m.inspect}")}
> >>
> >>    Muppet.instance_eval { remove_method(m) }
> >
> > or Muppet.send :remove_method, :m
>
> If you have a method called "m" :-)
Ah I see :(, Probably 50% of my errors come from spurious ":" ARRRRG

Muppet.send :remove_method, m ### TESTED ;)

Sorry.
Robert
F54bc341bc19c48e5860fda022663795?d=identicon&s=25 Nathan Taylor (shadowcipher)
on 2007-06-15 18:16
(Received via mailing list)
Code injection attack to own a RoR site possibly?  That would be my
guess.

- Nathan
Ce8b03e5750097942c58e12b46724312?d=identicon&s=25 Giles Bowkett (Guest)
on 2007-06-15 22:41
(Received via mailing list)
> > Why should this inspire fear?
>
> Code injection attack to own a RoR site possibly?  That would be my guess.

Sort of. I'm trying to use it to build a Rails plugin called
acts_as_fox, which overrides every method on a model to return "chunky
bacon!" (It's not really that terrifying, I just kind of had a Dr.
Frankenstein moment, drunk on power type thing.)

Unfortunately, applying Rick's code directly to an ActiveRecord model
doesn't quite accomplish this, because it's missing the superclass
methods, but applying it to ActiveRecord::Base doesn't work either. I
did get it to work by doing it twice, both on the actual model and on
ActiveRecord::Base itself, but that's very unsatisfying, because I
solved the problem by cutting and pasting. (I think I understand why
it worked; ActiveRecord::Base attaches a lot of methods to its
subclasses, instead of having them inherited directly.) It also fails
to really accomplish what I want to do, because it means that making
one model acts_as_fox destroys all the other models. (I also need to
attach a method_missing to return "chunky bacon!" but that part's
obviously trivial.)

Really the quickest way to accomplish this would be to simply pop the
model out of its inheritance hierarchy - redefine the model not to
have any superclass except Object - but I don't know if that's
possible. Trying it in the most obvious way (class Foo < Object; end,
where Foo was already defined Foo < ActiveRecord::Base) results in a
TypeError with the message "superclass mismatch."

But there must be a clean way to open up the class, grab all its
methods, including those derived from superclasses, and simply
reassign them. Something like

Foo.instance_methods(true).each{|m| Foo.instance_eval("alias
:chunky_bacon " + m)}

--
Giles Bowkett

Blog: http://gilesbowkett.blogspot.com
Portfolio: http://www.gilesgoatboy.org
1fba4539b6cafe2e60a2916fa184fc2f?d=identicon&s=25 unknown (Guest)
on 2007-06-16 12:33
(Received via mailing list)
Hi --

On Sat, 16 Jun 2007, Giles Bowkett wrote:

> doesn't quite accomplish this, because it's missing the superclass
>
> Really the quickest way to accomplish this would be to simply pop the
> model out of its inheritance hierarchy - redefine the model not to
> have any superclass except Object - but I don't know if that's
> possible. Trying it in the most obvious way (class Foo < Object; end,
> where Foo was already defined Foo < ActiveRecord::Base) results in a
> TypeError with the message "superclass mismatch."

I do sometimes wonder what would happen if the ancestry array were
writeable.  It could be interesting.  I haven't thought through the
possible pitfalls.

> But there must be a clean way to open up the class, grab all its
> methods, including those derived from superclasses, and simply
> reassign them. Something like
>
> Foo.instance_methods(true).each{|m| Foo.instance_eval("alias
> :chunky_bacon " + m)}

Here's a little demo that does pretty much that (sparing the _ methods
like __send__):

class Object
   def mask
     puts "I'm masking a method"
   end
end

class C
   def x
     puts "x"
   end

   def y
     puts "y"
   end
end

class D < C
   def z
     puts "z"
   end
end

class D
   instance_methods.reject {|m| /^_/.match(m) }.each do |m|
     alias_method m, :mask
   end
end

C.new.x   # x
D.new.x   # I'm masking a method
D.new.z   # I'm masking a method


David
918c6daad03c85e51ad1a11f57017947?d=identicon&s=25 Devin Mullins (twifkak)
on 2007-06-16 18:06
(Received via mailing list)
dblack@wobblini.net wrote:
> I do sometimes wonder what would happen if the ancestry array were
> writeable.  It could be interesting.  I haven't thought through the
> possible pitfalls.

You mean, like Object#become? :)

Seriously, how would that play with the core classes, which don't keep
their state in instance variables?

Devin
8f6f95c4bd64d5f10dfddfdcd03c19d6?d=identicon&s=25 Rick Denatale (rdenatale)
on 2007-06-16 18:43
(Received via mailing list)
On 6/16/07, dblack@wobblini.net <dblack@wobblini.net> wrote:

> I do sometimes wonder what would happen if the ancestry array were
> writeable.  It could be interesting.  I haven't thought through the
> possible pitfalls.

Actually the ancestry array only exists as the return value of the
ancestry method, internally it's a chain of class objects, and module
wrapper's.  Some things, like including a module will alter that
chain, although there are restrictions which lead to things like the
double module inclusion problem.

Perhaps evil.rb lets you monkey with the chain, it would seem to be
within its mission.

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/
1fba4539b6cafe2e60a2916fa184fc2f?d=identicon&s=25 unknown (Guest)
on 2007-06-16 20:23
(Received via mailing list)
Hi --

On Sun, 17 Jun 2007, Devin Mullins wrote:

> dblack@wobblini.net wrote:
>> I do sometimes wonder what would happen if the ancestry array were
>> writeable.  It could be interesting.  I haven't thought through the
>> possible pitfalls.
>
> You mean, like Object#become? :)

Not exactly.  As I understand it, #become involves references changing
from one object to another.  I'm thinking of something more like:

   module M
   end

   a = Object.new
   class << a
     ancestors.unshift(M)  # essentially same as a.extend(M)
   end

> Seriously, how would that play with the core classes, which don't keep their
> state in instance variables?

I'm not sure how that would matter specifically.  What problem are you
envisioning?  (Not that the whole thing wouldn't be a train-wreck
anyway :-)


David
1fba4539b6cafe2e60a2916fa184fc2f?d=identicon&s=25 unknown (Guest)
on 2007-06-17 01:19
(Received via mailing list)
Hi --

On Sun, 17 Jun 2007, Rick DeNatale wrote:

> On 6/16/07, dblack@wobblini.net <dblack@wobblini.net> wrote:
>
>> I do sometimes wonder what would happen if the ancestry array were
>> writeable.  It could be interesting.  I haven't thought through the
>> possible pitfalls.
>
> Actually the ancestry array only exists as the return value of the
> ancestry method, internally it's a chain of class objects, and module
> wrapper's.

By making it writeable I meant making it an interface to the chain
itself.  Obviously you can do:

   String.ancestors << "blah"

but that wasn't the point :-)


David
31e038e4e9330f6c75ccfd1fca8010ee?d=identicon&s=25 Gregory Brown (Guest)
on 2007-06-17 16:10
(Received via mailing list)
On 6/16/07, dblack@wobblini.net <dblack@wobblini.net> wrote:
>
> Not exactly.  As I understand it, #become involves references changing
> from one object to another.  I'm thinking of something more like:
>
>    module M
>    end
>
>    a = Object.new
>    class << a
>      ancestors.unshift(M)  # essentially same as a.extend(M)
>    end

Would you want to be able to switch ordering too?  I wonder what kind
of interesting evil could come of that...
Ce8b03e5750097942c58e12b46724312?d=identicon&s=25 Giles Bowkett (Guest)
on 2007-06-18 20:06
(Received via mailing list)
> > >> I do sometimes wonder what would happen if the ancestry array were
> > >> writeable.  It could be interesting.  I haven't thought through the
> > >> possible pitfalls.

...

> Would you want to be able to switch ordering too?  I wonder what kind
> of interesting evil could come of that...

You can play with this in Python. If you modify the __class__ magic
var you can change an object's class in real-time. When I first
discovered this, I posted on a Python list that it seemed like a
sportscar without a seatbelt, and got some very outraged responses
from Python programmers who felt passionately that it was one of the
language's best features. The most memorable response turned my words
around ("sportscar without a speed limit").

On the flipside, I also heard a story from a guy who found this kind
of thing heavily abused in some legacy bioinformatics code in Python,
and he was not happy about it. Objects would wander off into
particular methods and come back home completely transformed. It
sounded very confusing.

--
Giles Bowkett

Blog: http://gilesbowkett.blogspot.com
Portfolio: http://www.gilesgoatboy.org
Ce8b03e5750097942c58e12b46724312?d=identicon&s=25 Giles Bowkett (Guest)
on 2007-06-18 20:08
(Received via mailing list)
> Here's a little demo that does pretty much that

gracias!

> (sparing the _ methods
> like __send__):

no mercy! no survivors!

>
>
> class D
>    instance_methods.reject {|m| /^_/.match(m) }.each do |m|
>      alias_method m, :mask
>    end
> end
>
> C.new.x   # x
> D.new.x   # I'm masking a method
> D.new.z   # I'm masking a method

I tried something very similar and it failed, but I can't recall why.
I'll check this later in the day.

--
Giles Bowkett

Blog: http://gilesbowkett.blogspot.com
Portfolio: http://www.gilesgoatboy.org
This topic is locked and can not be replied to.