Devious or what? : hacking super : should this work?

J

Jeremy Henty

I've discovered that an unadorned call to "super" need *not* call the
parent method with the same arguments!!! If you bind the entire
argument list to a variable you can modify the list before calling
"super". Check it out:

class Show
def initialize(*args)
puts args.inspect
end
end

Show.new(1,34,"asd")

class Show2 < Show
def initialize(*args)
puts args.shift
super
end
end

Show2.new(1,34,"asd")

===>

[1, 34, "asd"]
1
[34, "asd"]

So "args" is bound to the *actual* array of arguments, not just a
copy. Is this a bug or a feature? Might this behaviour change in
future?

Regards,

Jeremy Henty
 
D

dblack

Hi --

I've discovered that an unadorned call to "super" need *not* call the
parent method with the same arguments!!! If you bind the entire
argument list to a variable you can modify the list before calling
"super". Check it out:

class Show
def initialize(*args)
puts args.inspect
end
end

Show.new(1,34,"asd")

class Show2 < Show
def initialize(*args)
puts args.shift
super
end
end

Show2.new(1,34,"asd")

===>

[1, 34, "asd"]
1
[34, "asd"]

So "args" is bound to the *actual* array of arguments, not just a
copy. Is this a bug or a feature? Might this behaviour change in
future?

I assume it's a feature. super should be pretty transparent; for
example, if an argument is for some reason going to be altered in
place by the original method, you want that to still happen when you
call the method that calls super.


David

--
http://www.rubypowerandlight.com => Ruby/Rails training & consultancy
http://www.manning.com/black => RUBY FOR RAILS (reviewed on
Slashdot, 7/12/2006!)
http://dablog.rubypal.com => D[avid ]A[. ]B[lack's][ Web]log
(e-mail address removed) => me
 
J

Jeremy Henty

... super should be pretty transparent; for example, if an argument
is for some reason going to be altered in place by the original
method, you want that to still happen when you call the method that
calls super.

True, but that's not what surprised me. Given "def foo(*args) ..." I
always expected the *members* of args to be the arguments of the
method call (and not copies of them). I did not expect args *itself*
to be part of the Ruby call stack. I assumed it was just a temporary
object created on the fly to give me access to the method arguments.
I thought that nothing else in Ruby had a reference to it, so I
expected that modifying args itself (rather than its members) would
have no effect outside of the current scope.

But I was wrong. It looks like args is the actual array of method
arguments in the Ruby call stack. Modifying args changes the call
stack, causing super to supply a different list of objects to the
parent method.

Regards,

Jeremy Henty
 
D

Dominik Bathon

True, but that's not what surprised me. Given "def foo(*args) ..." I
always expected the *members* of args to be the arguments of the
method call (and not copies of them). I did not expect args *itself*
to be part of the Ruby call stack. I assumed it was just a temporary
object created on the fly to give me access to the method arguments.

This definitely is the case for Ruby 1.8 and I can't reproduce your
example with 1.8.4, only with 1.9. I am not sure why this was changed.

In 1.8 the arguments are stored in a C array in the FRAME struct and when
splatting is used a new Ruby array is created every time:

$ cat super_splat.rb
class A
def foo(*a); p a, a.object_id; end
end

class B < A
def foo(*a); p a, a.object_id; a.shift; super; end
end

B.new.foo(1,2,3)

$ ruby super_splat.rb
[1, 2, 3]
-604430366
[1, 2, 3]
-604430956


And even in 1.9 it actually is a different Ruby array:

$ ruby19 super_splat.rb
[1, 2, 3]
-604528228
[2, 3]
-604528338


Dominik
 
J

Jeremy Henty

This definitely is the case for Ruby 1.8 and I can't reproduce your
example with 1.8.4, only with 1.9.

I am running 1.8.5-preview1 . If I downgrade to 1.8.4 I get the
behaviour I first expected. So my expectations were not that
ridiculous, which is comforting. And I guess the answer to my
question "might this behaviour change" is clearly "yes"! (Which means
I have some code to rewrite, /me sighs.)
In 1.8 the arguments are stored in a C array in the FRAME struct and
when splatting is used a new Ruby array is created every time:

$ cat super_splat.rb
class A
def foo(*a); p a, a.object_id; end
end

class B < A
def foo(*a); p a, a.object_id; a.shift; super; end
end

B.new.foo(1,2,3)

Under 1.8.4 :
[1, 2, 3]
-605563672
[1, 2, 3]
-605563762

Under 1.8.5 :
[1, 2, 3]
-605295548
[2, 3]
-605295638

Is this an intentional change or a side-effect of something else?
And even in 1.9 it actually is a different Ruby array:

Yes, the array is always a new VALUE (with a different
(RARRAY(self))->ptr). What's changed is the relation between the C
array of method arguments that is copied when we call super, and the C
array that is wrapped by args. It seems that in 1.8.4 the latter is a
copy of the former, so the call to super gets a copy of the array that
was originally passed to the current method, even if the contents of
args have subsequently changed. In 1.8.5 they look to be the same, so
the call to super gets a copy of the current contents of args.

Regards,

Jeremy Henty
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,534
Members
45,008
Latest member
Rahul737

Latest Threads

Top