12

我正在开发一个中等复杂的 Perl 程序。作为其开发的一部分,它必须经过修改和测试。由于某些环境限制,经常运行这个程序并不是一个容易练习的选项。

我想要的是 Perl 的静态调用图生成器。它不必涵盖所有边缘情况(例如,在 eval 中将变量重新定义为函数,反之亦然)。

(是的,我知道有一个使用 Devel::DprofPP 的运行时调用图生成工具,但运行时不能保证调用每个函数。我需要能够查看每个函数。)

4

5 回答 5

8

一般情况下不能做:

my $obj    = Obj->new;
my $method = some_external_source();

$obj->$method();

但是,获得大量案例应该相当容易(针对自身运行此程序):

#!/usr/bin/perl

use strict;
use warnings;

sub foo {
    bar();
    baz(quux());
}

sub bar {
    baz();
}

sub baz {
    print "foo\n";
}

sub quux {
    return 5;
}

my %calls;

while (<>) {
    next unless my ($name) = /^sub (\S+)/;
    while (<>) {
        last if /^}/;
        next unless my @funcs = /(\w+)\(/g;
        push @{$calls{$name}}, @funcs;
    }
}

use Data::Dumper;
print Dumper \%calls;

请注意,这错过了

  • 调用不使用括号的函数(例如print "foo\n";
  • 调用被取消引用的函数(例如$coderef->()
  • 对字符串方法的调用(例如$obj->$method()
  • 将推杆称为另一行的左括号
  • 其他我没有想到的事情

它错误地捕捉到

  • 注释函数(例如#foo()
  • 一些字符串(例如"foo()"
  • 其他我没有想到的事情

如果你想要一个比那些毫无价值的黑客更好的解决方案,是时候开始研究了PPI,但即使它也会遇到诸如$obj->$method().

只是因为我很无聊,这里有一个使用PPI. 它只查找函数调用(而不是方法调用)。它也没有尝试保持子程序的名称唯一(即,如果您多次调用同一个子程序,它将多次显示)。

#!/usr/bin/perl

use strict;
use warnings;

use PPI;
use Data::Dumper;
use Scalar::Util qw/blessed/;

sub is {
    my ($obj, $class) = @_;
    return blessed $obj and $obj->isa($class);
}

my $program = PPI::Document->new(shift);

my $subs = $program->find(
    sub { $_[1]->isa('PPI::Statement::Sub') and $_[1]->name }
);

die "no subroutines declared?" unless $subs;

for my $sub (@$subs) {
    print $sub->name, "\n";
    next unless my $function_calls = $sub->find(
        sub { 
            $_[1]->isa('PPI::Statement')             and
            $_[1]->child(0)->isa("PPI::Token::Word") and
            not (
                $_[1]->isa("PPI::Statement::Scheduled") or
                $_[1]->isa("PPI::Statement::Package")   or
                $_[1]->isa("PPI::Statement::Include")   or
                $_[1]->isa("PPI::Statement::Sub")       or
                $_[1]->isa("PPI::Statement::Variable")  or
                $_[1]->isa("PPI::Statement::Compound")  or
                $_[1]->isa("PPI::Statement::Break")     or
                $_[1]->isa("PPI::Statement::Given")     or
                $_[1]->isa("PPI::Statement::When")
            )
        }
    );
    print map { "\t" . $_->child(0)->content . "\n" } @$function_calls;
}
于 2009-08-18T18:29:38.250 回答
4

我认为 Perl 没有“静态”调用图生成器。

下一个最接近的事情是Devel::NYTProf.

主要目标是进行分析,但它的输出可以告诉您子例程被调用了多少次,以及从哪里调用。

如果您需要确保调用每个子例程,您还可以使用Devel::Cover,它会检查以确保您的测试套件涵盖每个子例程。

于 2009-08-18T17:57:21.153 回答
4

我不确定它是否 100% 可行(因为 Perl 代码在理论上无法静态分析,由于BEGIN块等 - 请参阅最近的 SO 讨论)。BEGIN此外,即使在块不发挥作用的地方,子例程引用也可能使其非常困​​难。

然而,显然有人尝试过——我只知道但从未使用过,所以买家要小心。

于 2009-08-18T18:29:55.317 回答
2

我最近在尝试解决相同问题的答案时偶然发现了一个脚本。该脚本(链接到下面)使用 GraphViz 创建 Perl 程序或模块的调用图。输出可以是多种图像格式。

http://www.teragridforum.org/mediawiki/index.php?title=Perl_Static_Source_Code_Analysis

于 2012-10-19T21:13:11.237 回答
1

我最近解决了一个类似的问题,并想分享我的解决方案。
这个工具是出于绝望而诞生的,它解开了一个 30,000 行遗留脚本中未记录的部分,以实施紧急的错误修复。

它读取源代码,使用 GraphViz 生成 png,然后在屏幕上显示图像。
由于它使用简单的逐行正则表达式,因此格式必须是“合理的”,以便可以确定嵌套。
如果目标代码格式错误,请先通过 linter 运行它。
另外,不要指望解析动态函数调用等奇迹。

一个简单的正则表达式引擎的一线希望是它可以很容易地扩展到其他语言。
该工具现在还支持 awk、bash、basic、dart、fortran、go、lua、javascript、kotlin、matlab、pascal、perl、php、python、r、raku、ruby、rust、scala、swift 和 tcl。

https://github.com/koknat/callGraph

于 2021-05-25T17:34:02.850 回答