Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this Perl produce "Not a CODE reference?"

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.

like image 811
KingPong Avatar asked Jan 12 '11 22:01

KingPong


2 Answers

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.

like image 124
Ether Avatar answered Nov 05 '22 13:11

Ether


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.

like image 21
DVK Avatar answered Nov 05 '22 14:11

DVK