Why is *main::my_func{CODE}* a coderef when function my_func() is exported but not defined?
Asked Answered
T

1

2

I have project where I renamed a Perl module because it had a too generic name and wrote a small compatibility module. I even wrote a test for the compatibility module but it was false positive. Why does this feel so hard to test? How to actually test the existence of the exported function if I can't call it because of side effects?

In my compatibility module I inherited from the new module but didn't import its functions. That wasn't enough, to my surprise. Here I built a minimal example with only 1 package involved:

use strict;
use warnings;
use feature qw(say);

{
    package MyPackage;

    use parent 'Exporter';
    our @EXPORT = qw(foo);
}

MyPackage->import();

say *main::foo{CODE};
say *MyPackage::foo{CODE};
say main->can('foo');
say MyPackage->can('foo');
foo();

(I replaced use MyPackage with MyPackage->import() to make this example work in 1 file.)

The output:

CODE(0x55fa5cf9e750)
CODE(0x55fa5cf9e750)
CODE(0x55fa5cf9e750)
CODE(0x55fa5cf9e750)
Undefined subroutine &MyPackage::foo called at inherit.pl line 16.

This all looks like the function exists but it fails when I try to call it.

This is Perl 5.34.

Update to answer zdim’s qestion: What is the actual issue?

I moved the actual code from OldModule.pm to NewModule.pm (not their real names, of course) and created a new OldModule.pm for compatibility with existing software using that. The actual code provided both package functions via import() as well as class and object methods. So I used use parent 'NewModule' to inherit all of them. I believed this would also inherit package functions. My Test with Test2’s can_ok incorrectly showed that it worked.

Thanks to a bug report from a coworker I learned that this was not enough and found I needed to import the package functions as well.

I can’t simply call the functions in question to test the compatibility package because this is part of a CGI app and the functions actually do database operation or print out stuff to STDOUT and would pollute my TAP output. (Yes, part of my mission is to get rid of CGI, of course.)

package OldPackage;

# ABSTRACT: transitional package for new name NewPackage

use strict;
use warnings;

use NewPackage;  # this is the line I added to fix the issue

use Exporter qw(import);
our @EXPORT = @NewPackage::EXPORT;   # this line only declared undefined functions

1;
Thrombo answered 24/1, 2022 at 13:11 Comment(8)
"Why is *main::foo{CODE} a coderef when function foo() is exported but not defined?" It seems like Exporter.pm does not care if the sub is defined or not in MyPackage, it just assigns \&{"MyPackage::foo"} to *{"main::foo"}, see line 66 in Exporter.pmNephro
But if both are not defined, why does can() show a coderef of the same address? What is that actually? I tried perl -MCarp::Always and thought I might see a call to Exporter which then calls MyPackage::foo() but it seems to fail directly. I find this weird.Heinrick
Re "But if both are not defined, why does can() show a coderef of the same address?", For the same reason that *x{SCALAR} would given $x = undef;.Exordium
Can we see a bit about your actual problem -- important parts of the bad-name-module and the good-name ("compatibility") one? It appears that the bad-name dude is a class? Me, I don't at all get the description of the problem ("false postitive" -- what is? "can't call it because of side effects" -- what side effects?). The shown code isn't much of a surprise I'd say -- Exporter copies foo to the caller's symbol table and that's that; now it's there in the sub's slot and reported as such. Even if it's not defined -- OK, a quirk perhaps. (But one shouldn't do that so it's kinda OK)Oby
I think this is a bug (imho) or at least highly undesirable so I filed it in p5 github.com/Perl/perl5/issues/19366Jeneejenei
@zdim: I added an update with the story and my compatibility code. Does that answer your question?Heinrick
@DanielBöhmer Thank you, it sure helps (I think, for people to help you out) quite a bit. I asked because it feels (to me) that there shouldn't be a problem that you are asking about so I'd expect that it comes from earlier/higher. One note: I'd either inherit, if the original package is a class (it bless-es), or import if it isn't; not both. (I'm not saying that doing both causes the problem but it's messy, isn't it.)Oby
Now, if a name is exported that doesn't exist that's: (1) bad -- why is that happening? (2) Exporter's problem? Or, you can import via @EXPORT_OK so you have an exact list of names and then write a test for everything that's imported, to defend yourself against that. But if you only inherit then no non-existing "subs" should creep in, right? (All this is shooting in the dark a little since I still don't fully get the problem but perhaps that's just me...)Oby
E
2

Exporter, in effect, does something similar to this:

*main::foo = \&MyPackage::foo;

Just like with scalars, arrays and hashes, referencing a sub vivifies the symbol. Specifically, it creates an undefined sub as if you had done sub foo;.

$ perl -M5.010 -e'
   \&foo;    say *foo{CODE};
   sub bar;  say *bar{CODE};
'
CODE(0x55da28044470)
CODE(0x55da28073750)

exists(&f) checks if it exists, and defined(&f) checks if it's defined.

$ perl -M5.010 -e'
   sub foo { }
   sub bar;
   
   for (qw( foo bar baz )) {
      say exists(  &$_ ) ? "$_ exists"     : "$_ doesn\x27t exist";
      say defined( &$_ ) ? "$_ is defined" : "$_ isn\x27t defined";
   }
'
foo exists
foo is defined
bar exists
bar isn't defined
baz doesn't exist
baz isn't defined
Exordium answered 24/1, 2022 at 15:42 Comment(4)
That's a pretty garbage api. Exporter.pm should at least warn if it's declaring a slot that it doesn't have a sub to initialize it to. And can should not return true if the slot is declared but uninitialized. The error "Undefined subroutine" makes 0 sense in that context. I can't see any reason for anyone to want can to return a reference (true) on the basis of the slot initialized to an undef value.Jeneejenei
I don't have a problem with what can does. It returns the sub that would be called if you did $o->foo, just like it should. The problem is Exporter creating it in the first place. It shouldn't do that.Exordium
@Exordium If the same module uses AutoLoader, Exporter can't rely on the symbol being present at the time import() is called. Or am I missing something?Hoagy
@clamp, It could impose a requirement for the subs to be declared, but it this way it doesn't.Exordium

© 2022 - 2024 — McMap. All rights reserved.