Printing out the code of an anonymous subroutine

和自甴很熟 提交于 2019-12-03 12:27:31

问题


I'm currently working in a very complex Perl architecture, and I want to create some debugging tools. Since a lot of the behavior involves anonymous subroutines, I'd like to analyze some of the behavior, and all I have to work with is a reference to the subroutine.

In short, is there a way to print the code (since Perl is interpreted it may still be available?) of a subroutine reference?


回答1:


The core module B::Deparse provides this functionality.

use B::Deparse ();

my $deparse = B::Deparse->new;

my $code = sub {print "hello, world!"};

print 'sub ', $deparse->coderef2text($code), "\n";

which prints:

sub {
    print 'hello, world!';
}

When using B::Deparse it is important to remember that what it returns is a decompiled version of the compiled tree of op-codes, not the original source text. This means that constants, arithmetic expressions, and other constructs may be folded and rewritten by the optimizer.

The other part of the puzzle is dealing with closed over lexical variables. If the subroutines you are working with access any external lexicals, they will not be present in the output of deparse, and will cause recompilation to fail. You can solve this with the closed_over and set_closed_over functions from the PadWalker module.

use PadWalker qw/closed_over set_closed_over/;

my $closure = do {
    my $counter = 0;
    sub {$counter++}
};

print $closure->(), ' ' for 1..3; # 0 1 2
print "\n";

my $pad = closed_over $closure; # hash of lexicals

                 # create dummy lexicals for compilation
my $copy = eval 'my ('.join(','=> keys %$pad).');'. 
                'sub '.$deparse->coderef2text($closure);

set_closed_over $copy, $pad;  # replace dummy lexicals with real ones

print $copy->(), ' ' for 1..3; # 3 4 5

Finally, if you want to find out where the subroutine's real source code is, you can use the core B module:

use B ();
my $meta = B::svref_2object($closure);

print "$closure at ".$meta->FILE.' line '.$meta->GV->LINE."\n";

which prints something like:

CODE(0x28dcffc) at filename.pl line 21



回答2:


Yeah, Data::Dumper can be told to bring in B::Deparse, via something like:

#!/usr/bin/perl

use Data::Dumper;
use strict;
use warnings;
$Data::Dumper::Deparse = 1;

my $code = sub { my $a = 42;  print $a ** 2; };

print Dumper $code;

There is an object-oriented interface as well (described in the perldoc for Data::Dumper), if you prefer.

Note: The code that is output won't be identical to what you originally specified, but it will have the same semantics.




回答3:


Also, Devel::Dwarn sets Data::Dumper so it deparses by default. It quickly made it my favourite dumper:

perl -MDevel::Dwarn -e "Dwarn { callback => sub { 1+1 } }"

gives

{
  callback => sub {
      2;
  }
}



回答4:


For this sort of thing, I always refer to Track the filename/line number of an anonymous coderef on PerlMonks. Randal had an idea to tag anonymous subroutines so you could see where you defined them, and I extended it a bit. It uses some of the same stuff that Eric posted, but with a little more.



来源:https://stackoverflow.com/questions/5589397/printing-out-the-code-of-an-anonymous-subroutine

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!