Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl Monk, Perl Meditation
 
PerlMonks  

How to instrument a "fake" subroutine?

by samtregar (Abbot)
on May 22, 2002 at 18:44 UTC ( [id://168546]=perlquestion: print w/replies, xml ) Need Help??

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

While working on Devel::Profiler I've stumbled into a dark pit. Basically, Devel::Profiler works by instrumenting every sub it can find by walking symbol tables. When it finds a sub it wraps it in a profiling routine and installs it in the sub's slot in the symbol table. This is generally working fine. The problem occurs when that subroutine isn't a real subroutine, but a fake produced by exporting a symbol that doesn't exist. This exported sub will then call AUTOLOAD when it's called. Fcntl, for example, uses this pattern.

Here's a bit of code that shows a simplified version of what I'm doing. CAUTION: this code loops infinitely printing "LOCK_EX called" - it might lock up your machine if you're not quick with Ctrl-C.

#!/usr/bin/perl -w use Fcntl qw(LOCK_EX); # instrument LOCK_EX my $lock_ex_coderef = \&Fcntl::LOCK_EX; *Fcntl::LOCK_EX = sub { print "LOCK_EX called\n"; return &$lock_ex_coderef; }; # call LOCK_EX $_ = LOCK_EX;

Now, what I'd expected was that this code would print "LOCK_EX called" just once and then call Fcntl::AUTOLOAD as usual. Can anyone think of a way to get that behavior? Or perhaps I just need to understand why this method isn't working. Can anyone explain why a calling code-ref to a "fake" sub causes this recursion?

Alternately, can anyone propose a way to detect "fake" subroutines so I could skip them for now? B:: solutions are acceptable.

Thanks!
-sam

Replies are listed 'Best First'.
Re: How to instrument a "fake" subroutine?
by educated_foo (Vicar) on May 22, 2002 at 20:22 UTC
    Interesting... Fcntl::AUTOLOAD never even gets called, and for some reason whatever's in the saved coderef just calls us again. My wild guess is that Perl puts some sort of magic reference to the autoloading code in that symbol table entry when it gets exported. When this code gets called, it looks and sees that the sub has been defined in the meantime (by you), and happily calls that, rather than looking for an autoload.

    In any case, temporarily putting the old glob back in place fixes it, and AUTOLOAD is only called the first time, as things should be.

    #!/usr/bin/perl -w use Fcntl qw(LOCK_EX); # instrument LOCK_EX my $lock_ex_coderef = \&Fcntl::LOCK_EX; my $newglob = *Fcntl::LOCK_EX = sub { print "LOCK_EX called\n"; no warnings 'redefine'; *Fcntl::LOCK_EX = $lock_ex_coderef; if (wantarray) { my @ret = &$lock_ex_coderef; *Fcntl::LOCK_EX = $newglob; @ret; } else { my $ret = &$lock_ex_coderef; *Fcntl::LOCK_EX = $newglob; $ret; } }; # call LOCK_EX print $_ = LOCK_EX; print "\n";
    /s
      Interesting solution. I have some vague worries about recursive calls to instrumented subs... I think maybe only the first call would get caught since by the time the second call comes the old sub is restored. But I'll have to work up a test case to be sure.

      Thanks,
      -sam

Re: How to instrument a "fake" subroutine?
by broquaint (Abbot) on May 22, 2002 at 20:40 UTC
    Can anyone explain why a calling code-ref to a "fake" sub causes this recursion?
    Because it's just like a normal sub calling itself since the coderef is pointing to the sub that is currently being called.

    A way around calling the fake sub would be to see if the coderef was pointing to an actual sub living in a symbol table somewhere

    #!/usr/bin/perl -w use Fcntl qw(LOCK_EX); my $lock_ex_coderef= \&Fcntl::LOCK_EX;; # instrument LOCK_EX *Fcntl::LOCK_EX = sub { print "LOCK_EX called\n"; return &$lock_ex_coderef if defined &$lock_ex_coderef; }; # call LOCK_EX $_ = LOCK_EX;
    This works because LOCK_EX (which is what $lock_ex_coderef points to) isn't defined at that point.

    Another (albeit more hackish) way to see if a dummy sub is being called is to see if the coderef and the symbol table entry have the same reference value

    use Fcntl qw(LOCK_EX); # define here so the &Fcntl::LOCK_EX can see it my $lock_ex_coderef; # instrument LOCK_EX *Fcntl::LOCK_EX = sub { print "LOCK_EX called\n"; return &$lock_ex_coderef unless $lock_ex_coderef eq *{$Fcntl::{LOCK_EX}}{CODE}; }; $lock_ex_coderef = \&Fcntl::LOCK_EX; # call LOCK_EX $_ = LOCK_EX;
    This is very icky (it won't pass string any time soon ;-), but somewhat effective as it halts recursion by seeing that it would be calling itself. Also having written the code I learned that $lock_ex_coderef is merely pointing to an entry in the symbol table whereas &Fcntl::LOCK_EX is pointing to a coderef (which is why the $lock_ex_coderef assignment had to go after the &Fcntl::LOCK_EX assignment).
    HTH

    _________
    broquaint

      Because it's just like a normal sub calling itself since the coderef is pointing to the sub that is currently being called.

      I don't think I follow. How does it work on non-fake subs then? By your reasoning wouldn't they also recurse? (They don't.)

      Unfortunately my actual situation in Devel::Profiler is complicated enough that neither of your solutions will apply directly. But they have given me some good ideas.

      Thanks,
      -sam

        I don't think I follow. How does it work on non-fake subs then?
        Hmm, I think we may have our wires crossed (that'll teach me for posting after 10pm ;-) My reasoning is that if your return is the call from a coderef that is pointing to the current coderef then of course it will recurse e.g
        my $f = \&foo; *foo = sub { print "in foo()\n"; return &$f; } foo(); __output__ in foo() in foo() ... and so on
        $f is just pointing to *main::foo{CODE} and since &foo is defined by the time it is called $f will then be pointing to a fully fledged sub and it recurses. Am I missing something?
        HTH

        _________
        broquaint

        update: changed sub foo to *foo = sub {}

Re: How to instrument a "fake" subroutine?
by ton (Friar) on May 22, 2002 at 19:08 UTC
    To check if the subroutine really exists:

    if (defined(&Fcntl::LOCK_EX))

    To get desired behavior: just call the subroutine once before attempting to redefine it.

    -Ton
    -----
    Be bloody, bold, and resolute; laugh to scorn
    The power of man...

      Thanks for the check code! Unfortunately, I can't just call the sub before instrumenting it. What if it has side-effects or produces output? Devel::Profiler can't make the assumption that calling subroutines before they're used is harmless.

      -sam

Re: How to instrument a "fake" subroutine?
by pdcawley (Hermit) on May 23, 2002 at 08:42 UTC
    LOCK_EX is a constant function, so every use of it gets inlined at compile time. Which makes life somewhat tricky because your instrumented LOCK_EX will never get called except in an eval EXPR type call.
      Wrong. Please look at the source for Fcntl.pm to confirm. Devel::Profiler already has code to handle constants, but this is a beast of a different stripe entirely.

      -sam

        Note to self: Always read the fscking source first. I stand corrected, sorry 'bout that.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://168546]
Approved by samtregar
Front-paged by gryphon
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others examining the Monastery: (4)
As of 2024-04-19 21:05 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found