robnagler has asked for the wisdom of the Perl Monks concerning the following question:

When we reference a typeglob as follows:
sub code_ref_for_subroutine { my($proto, $name) = @_; do { no strict; local(*p) = *{$proto->package_name . '::'}; if (exists($p{$name})) { local(*n) = $p{$name}; return *n{CODE} if defined(*n{CODE}); } }; return; }

Global lexicals disappear sometimes. This has been an ongoing problem, which I believe is caused by the semi-autovivification in the defined(*n{CODE}).

I sent a perlbug a while ago, but got no answer. We work around the problem by not using code_ref_for_subroutine, but it's very useful for the way we code.

We've got a variety of unit tests for code_ref_for_subroutine, but none which reproduces the problem. It only seems to happen when running under mod_perl.

Rob

Replies are listed 'Best First'.
Re: typeglob reference deletes global lexical
by dragonchild (Archbishop) on Nov 07, 2008 at 22:13 UTC
    It looks like you're trying to get a subref for a method. Hence, the use of $proto->package_name. A much simpler solution is my $subref = $proto->can( $name );

    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
Re: typeglob reference deletes global lexical
by jeffa (Bishop) on Nov 07, 2008 at 21:44 UTC

    My gut knee-jerk reaction is to command "don't do that" while simultaneously inquiring "Is that the Perl 4 way of making a closure?" What the heck is that?!? =]

    If you are trying to make a closure, there are cleaner ways of doing so. Just do a search here for closure and you will find lots of examples. I also suspect that if you did not receive a response for your perlbug, there is a good chance that either it was not understood or there is a better way to do what you want.

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)
    
Re: typeglob reference deletes global lexical
by JavaFan (Canon) on Nov 07, 2008 at 23:01 UTC
    Considering that lexicals don't live in the stash, I find it hard to believe autovivification in the stash (I've no idea what semi-autovivification is supposed to be) makes lexicals disappears.

    Could you show us some code in which "global lexicals" disappear? (BTW, what do you consider to be a "global lexical"? What's a "non-global lexical"?)

Re: typeglob reference deletes global lexical
by robnagler (Novice) on Nov 08, 2008 at 00:23 UTC
    It seems I need to explain more about the "why" of what I'm doing. Here's an example:
    sub initialize { return shift->call_super_before(\@_, sub { my($self) = @_; $self->die($self->get('values'), undef, 'number of elements mu +st be even') unless @{$self->get('values')} % 2 == 0; return; }); }

    call_super_before uses code_ref_for_subroutine to figure out what the SUPER is from a $proto/$self and caller. I want SUPER to do something (put "values" on $self) and then I'm going to check the result. With call_super_before I don't need to know what SUPER returns or what it takes, it's all transparent to the subclass.

    One suggestion was to use ->can, but this doesn't work, because it will return the subclass's method, not SUPER's method.

    As to "what global lexicals" disappear, it's all of them as near as I can tell. A global lexical is one which is declared in the package scope so properly "package lexicals disappear". The difficulty is that the lexicals are undefined after they are initialized, that is,

    my($_BAR) = 1; sub foo { return shift->call_super_before(\@_, sub { die('gone') unless $_BAR; return; }); }

    During the first call to foo, $_BAR is false, even though it was initialized to 1.

    Here's another tidbit, which is probably related:

    package T1; sub s1 {print("T1:s1\n")} package T2; use base T1; package T3; use base T2; sub s1 {shift->SUPER::s1} package main; no strict 'refs'; exists($T2::{s1}) && die('not here'); T3->s1; exists($T2::{s1}) && die('here');

    During the search for SUPER methods, $T2::{s1} is created with an empty value for *{$T2::{s1}}{CODE}. It's there, but not there. This is what I mean by semi-autovivification: the typeglob is created, but it doesn't contain anything.

    Rob

      During the first call to foo, $_BAR is false, even though it was initialized to 1.

      Assuming the value didn't change, that usually means that foo was called before the assignment occurred.

      use strict; use warnings; my $x = 1; sub foo { print("[$x]\n"); } BEGIN { foo(); } # Prints "[]" and gives an undefined warning.

      Or since you're doing symtab manipulations, maybe it's getting clobbered by something. But that would mean the variable isn't a lexical (my) variable as you claim it is.

      Seeing you didn't provide code that reproduces the problem, there's not much we can do.

        The code is here http://www.bivio.biz/hm/download-bOP I can't seem to distill it to the point of reproducing the problem in a short script.

        Needless to say a simple implementation does not produce the bug. I've stripped all the non-essential parts out, and started mod_perl, but the failure does show up. It is reliable for a particular package and code configuration. However, changing code in other packages can cause the failure to disappear.

      the typeglob is created, but it doesn't contain anything.

      It's not empty. It caches the SUPER(T2::s1)T1::s1 relation.

      { package T1; sub s1 { } } { package T2; use base T1; } { package T3; use base T2; sub s1 { shift->SUPER::s1 } } use Devel::Peek qw( Dump ); { warn("T1::s1: ", \&T1::s1, "\n"); warn("T2::s1 (pre):\n"); Dump(*T2::s1); T3->s1; warn("T2::s1 (post):\n"); Dump(*T2::s1); }
      >perl script.pl 2>&1 | findstr ": CODE CV" T1::s1: CODE(0x183180c) T2::s1 (pre): CV = 0x0 CVGEN = 0x0 T2::s1 (post): CV = 0x183180c CVGEN = 0x91

      SUPER doesn't need to be explicitly mentioned.

      { package T1; sub s1 { } } { package T2; use base T1; } use Devel::Peek qw( Dump ); { warn("T1::s1: ", \&T1::s1, "\n"); warn("T2::s1 (pre):\n"); Dump(*T2::s1); T2->s1; warn("T2::s1 (post):\n"); Dump(*T2::s1); }
      >perl script.pl 2>&1 | findstr ": CODE CV" T1::s1: CODE(0x183180c) T2::s1 (pre): CV = 0x0 CVGEN = 0x0 T2::s1 (post): CV = 0x183180c CVGEN = 0x8e

        I've been trying to narrow this down, and it does get pretty weird. I've got it to the point of calling a closure causes the failure, and if the file lexical is read before the closure call, it doesn't disappear.

        Since I can't reproduce it outside mod_perl (1.x, btw), I suspect this has little to do with typeglobs so sorry for the confusing subject.

        I've got my workaround so I'm going to call it quits on reducing the code to a reproducible failure.

        Rob
      A global lexical is one which is declared in the package scope so properly "package lexicals disappear".
      You got your terminology mixed up. A lexical is by definition lexically scoped. It exists inside a block, and not outside that block (there's a block at the file level as well).

      And package variable, that is, a variable with a package wide scope, is not a lexical variable.

      As for your last tidbit, you don't say what it does for you, and what you expect it to do, but when I run it, it prints "T1:s1", which is what I expect.

Re: typeglob reference deletes global lexical
by ikegami (Patriarch) on Nov 08, 2008 at 00:11 UTC
    What's the ticket number? I can't find your bug report.
      I don't know. I sent it Feb 13, 2008. -- Rob

        The ticket isn't in the system.

        Perl5 tickets for Feb 10..16, 2008

        That probably indicates it wasn't received. I'd normally suggest you try submitting it again, but you need to come up with code that demonstrates the bug first.

Re: typeglob reference deletes global lexical (shared?)
by tye (Sage) on Nov 08, 2008 at 04:05 UTC

    Is this as simple as it not being that the lexical disappears, but that you aren't actually making a closure so access to the file-scoped lexical goes away? Perhaps similar to the standard warning: Variable "$x" will not stay shared.

    - tye