class destruction (evil genius metaprogramming)

Discussion in 'Ruby' started by Giles Bowkett, Jun 15, 2007.

  1. 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
     
    Giles Bowkett, Jun 15, 2007
    #1
    1. Advertising

  2. On 6/14/07, Giles Bowkett <> wrote:
    > 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.


    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/
     
    Rick DeNatale, Jun 15, 2007
    #2
    1. Advertising

  3. > 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
     
    Giles Bowkett, Jun 15, 2007
    #3
  4. Giles Bowkett

    Guest

    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

    --
    * Books:
    RAILS ROUTING (new! http://safari.awprofessional.com/9780321509246)
    RUBY FOR RAILS (http://www.manning.com/black)
    * Ruby/Rails training
    & consulting: Ruby Power and Light, LLC (http://www.rubypal.com)
     
    , Jun 15, 2007
    #4
  5. Giles Bowkett

    Robert Dober Guest

    On 6/15/07, <> wrote:
    > 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) }


    or Muppet.send :remove_method, :m

    Cheers
    Robert


    --
    You see things; and you say Why?
    But I dream things that never were; and I say Why not?
    -- George Bernard Shaw
     
    Robert Dober, Jun 15, 2007
    #5
  6. Giles Bowkett

    Guest

    Hi --

    On Fri, 15 Jun 2007, Robert Dober wrote:

    > On 6/15/07, <> wrote:
    >> 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) }

    >
    > or Muppet.send :remove_method, :m


    If you have a method called "m" :)


    David

    --
    * Books:
    RAILS ROUTING (new! http://safari.awprofessional.com/9780321509246)
    RUBY FOR RAILS (http://www.manning.com/black)
    * Ruby/Rails training
    & consulting: Ruby Power and Light, LLC (http://www.rubypal.com)
     
    , Jun 15, 2007
    #6
  7. Giles Bowkett

    Guest

    Hi --

    On Fri, 15 Jun 2007, Giles Bowkett 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}")}

    >
    > 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


    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

    --
    * Books:
    RAILS ROUTING (new! http://safari.awprofessional.com/9780321509246)
    RUBY FOR RAILS (http://www.manning.com/black)
    * Ruby/Rails training
    & consulting: Ruby Power and Light, LLC (http://www.rubypal.com)
     
    , Jun 15, 2007
    #7
  8. Giles Bowkett

    Robert Dober Guest

    On 6/15/07, <> wrote:
    > Hi --
    >
    > On Fri, 15 Jun 2007, Robert Dober wrote:
    >
    > > On 6/15/07, <> wrote:
    > >> 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) }

    > >
    > > 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


    --
    You see things; and you say Why?
    But I dream things that never were; and I say Why not?
    -- George Bernard Shaw
     
    Robert Dober, Jun 15, 2007
    #8
  9. > > 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
     
    Giles Bowkett, Jun 15, 2007
    #9
  10. Giles Bowkett

    Guest

    Hi --

    On Sat, 16 Jun 2007, Giles Bowkett wrote:

    >> > 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."


    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

    --
    * Books:
    RAILS ROUTING (new! http://safari.awprofessional.com/9780321509246)
    RUBY FOR RAILS (http://www.manning.com/black)
    * Ruby/Rails training
    & consulting: Ruby Power and Light, LLC (http://www.rubypal.com)
     
    , Jun 16, 2007
    #10
  11. 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
     
    Devin Mullins, Jun 16, 2007
    #11
  12. On 6/16/07, <> 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/
     
    Rick DeNatale, Jun 16, 2007
    #12
  13. Giles Bowkett

    Guest

    Hi --

    On Sun, 17 Jun 2007, Devin Mullins wrote:

    > 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

    --
    * Books:
    RAILS ROUTING (new! http://safari.awprofessional.com/9780321509246)
    RUBY FOR RAILS (http://www.manning.com/black)
    * Ruby/Rails training
    & consulting: Ruby Power and Light, LLC (http://www.rubypal.com)
     
    , Jun 16, 2007
    #13
  14. Giles Bowkett

    Guest

    Hi --

    On Sun, 17 Jun 2007, Rick DeNatale wrote:

    > On 6/16/07, <> 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

    --
    * Books:
    RAILS ROUTING (new! http://safari.awprofessional.com/9780321509246)
    RUBY FOR RAILS (http://www.manning.com/black)
    * Ruby/Rails training
    & consulting: Ruby Power and Light, LLC (http://www.rubypal.com)
     
    , Jun 17, 2007
    #14
  15. On 6/16/07, <> wrote:
    > Hi --
    >
    > On Sun, 17 Jun 2007, Devin Mullins wrote:
    >
    > > 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


    Would you want to be able to switch ordering too? I wonder what kind
    of interesting evil could come of that...
     
    Gregory Brown, Jun 17, 2007
    #15
  16. > > >> 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
     
    Giles Bowkett, Jun 18, 2007
    #16
  17. > Here's a little demo that does pretty much that

    gracias!

    > (sparing the _ methods
    > like __send__):


    no mercy! no survivors!

    > 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


    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
     
    Giles Bowkett, Jun 18, 2007
    #17
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Toby A Inkster
    Replies:
    0
    Views:
    403
    Toby A Inkster
    Dec 11, 2003
  2. Replies:
    12
    Views:
    1,083
    Ville Vainio
    Feb 17, 2006
  3. GHUM
    Replies:
    6
    Views:
    341
  4. Replies:
    0
    Views:
    101
  5. Todd S.
    Replies:
    3
    Views:
    158
    Matthew Moss
    Jan 26, 2006
Loading...

Share This Page