Why does this Perl produce "Not a CODE reference?"
Asked Answered
I

2

5

I need to remove a method from the Perl symbol table at runtime. I attempted to do this using undef &Square::area, which does delete the function but leaves some traces behind. Specifically, when $square->area() is called, Perl complains that it is "Not a CODE reference" instead of "Undefined subroutine &Square::area called" which is what I expect.

You might ask, "Why does it matter? You deleted the function, why would you call it?" The answer is that I'm not calling it, Perl is. Square inherits from Rectangle, and I want the inheritance chain to pass $square->area through to &Rectangle::area, but instead of skipping Square where the method doesn't exist and then falling through to Rectangle's area(), the method call dies with "Not a CODE reference."

Oddly, this appears to only happen when &Square::area was defined by typeglob assignment (e.g. *area = sub {...}). If the function is defined using the standard sub area {} approach, the code works as expected.

Also interesting, undefining the whole glob works as expected. Just not undefining the subroutine itself.

Here's a short example that illustrates the symptom, and contrasts with correct behavior:

#!/usr/bin/env perl
use strict;
use warnings;

# This generates "Not a CODE reference". Why?
sub howdy; *howdy = sub { "Howdy!\n" };
undef &howdy;
eval { howdy };
print $@;

# Undefined subroutine &main::hi called (as expected)
sub hi { "Hi!\n" }
undef &hi;
eval { hi };
print $@;

# Undefined subroutine &main::hello called (as expected)
sub hello; *hello = sub { "Hello!\n" };
undef *hello;
eval { hello };
print $@;

Update: I have since solved this problem using Package::Stash (thanks @Ether), but I'm still confused by why it's happening in the first place. perldoc perlmod says:

package main;

sub Some_package::foo { ... } # &foo defined in Some_package

This is just a shorthand for a typeglob assignment at compile time:

BEGIN { *Some_package::foo = sub { ... } }

But it appears that it isn't just shorthand, because the two cause different behavior after undefining the function. I'd appreciate if someone could tell me whether this is a case of (1) incorrect docs, (2) bug in perl, or (3) PEBCAK.

Inexistent answered 12/1, 2011 at 22:8 Comment(4)
Where did you get the idea that undef &Square::area would delete a subroutine? Not an attack, just curious...Supersaturated
From perldoc -f undef. The example they give is undef &mysub;, but it works with qualified names, too.Inexistent
Alternate "you might ask" questions. Why did you define &Square::area in the first place? Why don't you [re]define &Square::area to delegate to its superclass?Broker
@JB: this is for a testing apparatus, not live code.Inexistent
S
7

Manipulating symbol table references yourself is bound to get you into trouble, as there are lots of little fiddly things that are hard to get right. Fortunately there is a module that does all the heavy lifting for you, Package::Stash -- so just call its methods add_package_symbol and remove_package_symbol as needed.

Another good method installer that you may want to check out is Sub::Install -- especially nice if you want to generate lots of similar functions.

As to why your approach is not correct, let's take a look at the symbol table after deleting the code reference:

sub foo { "foo!\n"}
sub howdy; *howdy = sub { "Howdy!\n" };

undef &howdy;
eval { howdy };
print $@;

use Data::Dumper;
no strict 'refs';
print Dumper(\%{"main::"});

prints (abridged):

    $VAR1 = {
              'howdy' => *::howdy,
              'foo' => *::foo,
    };

As you can see, the 'howdy' slot is still present - undefining &howdy doesn't actually do anything enough. You need to explicitly remove the glob slot, *howdy.

Supersaturated answered 12/1, 2011 at 22:17 Comment(3)
Thanks for the pointer to Package::Stash. I somehow missed remove_package_symbol the last time I looked. This is what I need, as opposed to remove_package_glob, which would also clobber @area and $area, etc. But while this is useful information, it doesn't answer the question, but instead says "it's a hard question, don't bother asking."Inexistent
@KingPong: reload your page; the explanation just took a little longer to write up. :)Supersaturated
@Esther: If you undef &foo, you'll see that it is still in the symbol table just like howdy(), even though you undef'ed it. The difference is that Perl knows that foo() is gone, presumably because you declared it with "sub foo {...}", which, according to perldoc perlmod is equivalent to BEGIN { *foo = sub {... } }.Inexistent
E
2

The reason it happens is precisely because you assigned a typeglob.

When you delete the CODE symbol, the rest of typeglob is still lingering, so when you try to execute howdy it will point to the non-CODE piece of typeglob.

Euchromatin answered 12/1, 2011 at 22:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.