Given the following Perl program:
package Foo;
use strict;
use warnings;
sub new {
my ($class) = @_;
return bless {}, $class;
}
sub c {
print "IN ORIG C\n";
}
sub DESTROY {
print "IN DESTROY\n";
c();
}
1;
package main;
use strict;
use warnings;
no warnings qw/redefine once/;
local *Foo::c = sub { print "IN MY C\n" };
my $f = Foo->new();
undef $f;
I expect output as:
IN DESTROY
IN MY C
But I actually get output as:
IN DESTROY
IN ORIG C
Q: Why is my localized redefinition of Foo::c
not taking effect?
When perl code is compiled, globs for package variables/symbols are looked up (and created as necessary) and referenced directly from the compiled code.
So when you (temporarily) replace the symbol table entry for *Foo::c
at runtime, all the already compiled code that used *Foo::c
still uses the original glob. But do/require'd code or eval STRING or symbolic references won't.
(Very similar to Access package variable after its name is removed from symbol table in Perl?, see the examples there.)
This is a bug in perl which will be fixed in 5.22 (see Leon's comment below).
This happens because undef $f;
doesn't actually free up and destroy $f
, it just marks it as ready to
be freed by a nextstate
op.
nextstate
ops exist roughly between each statement, and they are there
to clean up the stack, among other things.
In your example, since undef $f
is the last thing in the file, there
is no nextstate after it, so your local destructor goes out of scope
before $f
's destructor is called (or, the global destruction that
happens just isn't aware of your local change.)
When you add a print statement after undef $f
, the nextstate
op
before the print calls your local destructor.
You can see the additional nextstate
in your example at
https://gist.github.com/calid/aeb939147fdd171cffe3#file-04-diff-concise-out.
You can also see this behaviour by checking caller()
in your DESTROY
method:
sub DESTROY {
my ($pkg, $file, $line) = caller;
print "Destroyed at $pkg, $file, $line\n";
c();
}
mhorsfall@tworivers:~$ perl foo.pl
Destroyed at main, foo.pl, 0
IN DESTROY
IN ORIG C
mhorsfall@tworivers:~$ echo 'print "hi\n"' >> foo.pl
mhorsfall@tworivers:~$ perl foo.pl
Destroyed at main, foo.pl, 30
IN DESTROY
IN MY C
hi
(Line 30 being the print "hi\n"
)
Hope that sheds some light on this.
Cheers.
The problem here doesn't have to do with compile time vs runtime but rather with scoping.
The use of local
limits the scope of your modified Foo::c
to the remainder of the current scope (which in your example is the remainder of your script). But DESTROY
doesn't run in that scope, even when you explicitly undef $f
(See http://perldoc.perl.org/perlobj.html#Destructors for more discussion of the behavior of DESTROY
). It runs at an undetermined time later, specifically AFTER $f has "gone out of scope". Therefore, any local
ized changes you have made in the scope of $f will not apply whenever DESTROY
finally runs.
You can see this yourself by simply removing the local
in your example:
With local
IN DESTROY
IN ORIG C
Without local
IN DESTROY
IN MY C
Or by adding a few additional subroutines and calling them in package::main
scope:
package Foo;
...
sub d {
c();
}
sub DESTROY {
print "IN DESTROY\n";
c();
}
1;
package main;
...
sub e {
Foo::c();
}
local *Foo::c = sub { print "IN MY C\n" };
my $f = Foo->new();
Foo::c();
Foo::d();
e();
undef $f;
Which prints
IN MY C
IN MY C
IN MY C
IN DESTROY
IN ORIG C
So only in DESTROY is the original c used, further demonstrating that this is a scoping issue.
Also see https://stackoverflow.com/a/19100461/232706 for a great explanation of Perl scoping rules.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With