Why didn't my localized package override take effect?

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 the output to be as follows:

 IN DESTROY IN MY C 

But I really get output like:

 IN DESTROY IN ORIG C 

Q: Why does my localized override of Foo::c not take effect?

+7
perl
source share
3 answers

This is a perl bug that will be fixed in 5.22 (see Leon's comment below).

This is because undef $f; doesn't actually release or destroy $f , it just marks it as ready to be freed with nextstate op.

nextstate ops exist between just about every statement, and they are there to clear the stack, by the way.

In your example, since undef $f is the last thing in the file, there is no next state after it, so your local destructor goes beyond $f destructor is called (or, global destruction, which is simply not known about your local changes.)

When you add a print statement after undef $f , nextstate op before printing calls your local destructor.

You can see an additional nextstate in your example at https://gist.github.com/calid/aeb939147fdd171cffe3#file-04-diff-concise-out .

You can also see this behavior by checking caller() in the 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 is print "hi\n" )

Hope this sheds light on this.

Greetings.

+2
source share

When perl code is compiled, globs for package variables / symbols are scanned (and generated as needed) and refer directly to the compiled code.

So, when you (temporarily) replace the character 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 links will not.

(very similar to accessing a package variable after removing it from a symbol table in Perl?, see examples there.)

+6
source share

The problem here is not related to compilation time and runtime, but to the scope.

Using local limits the scope of your modified Foo::c to the rest of the current scope (which in your example is the rest of your script). But DESTROY does not work in this area, even if you are clearly undef $f (see http://perldoc.perl.org/perlobj.html#Destructors for a more detailed discussion of the behavior of DESTROY ). It starts after an indefinite time later, namely AFTER $ f "goes beyond." Therefore, any local changes you make to the $ f area will not be applied when DESTROY finally launched.

You can see this for yourself by simply removing local in your example:

With local

 IN DESTROY IN ORIG C 

Without local

 IN DESTROY IN MY C 

Or by adding a few additional routines and calling them in the package::main :

 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; 

What seal

 IN MY C IN MY C IN MY C IN DESTROY IN ORIG C 

Thus, only DESTROY uses the original c, once again demonstrating that this is a problem.

Also see fooobar.com/questions/490572 / ... for a detailed explanation of Perl definition rules.

+2
source share

All Articles