我正在开发一个中等复杂的 Perl 程序。作为其开发的一部分,它必须经过修改和测试。由于某些环境限制,经常运行这个程序并不是一个容易练习的选项。
我想要的是 Perl 的静态调用图生成器。它不必涵盖所有边缘情况(例如,在 eval 中将变量重新定义为函数,反之亦然)。
(是的,我知道有一个使用 Devel::DprofPP 的运行时调用图生成工具,但运行时不能保证调用每个函数。我需要能够查看每个函数。)
我正在开发一个中等复杂的 Perl 程序。作为其开发的一部分,它必须经过修改和测试。由于某些环境限制,经常运行这个程序并不是一个容易练习的选项。
我想要的是 Perl 的静态调用图生成器。它不必涵盖所有边缘情况(例如,在 eval 中将变量重新定义为函数,反之亦然)。
(是的,我知道有一个使用 Devel::DprofPP 的运行时调用图生成工具,但运行时不能保证调用每个函数。我需要能够查看每个函数。)
一般情况下不能做:
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;
}
我认为 Perl 没有“静态”调用图生成器。
下一个最接近的事情是Devel::NYTProf
.
主要目标是进行分析,但它的输出可以告诉您子例程被调用了多少次,以及从哪里调用。
如果您需要确保调用每个子例程,您还可以使用Devel::Cover
,它会检查以确保您的测试套件涵盖每个子例程。
我最近在尝试解决相同问题的答案时偶然发现了一个脚本。该脚本(链接到下面)使用 GraphViz 创建 Perl 程序或模块的调用图。输出可以是多种图像格式。
http://www.teragridforum.org/mediawiki/index.php?title=Perl_Static_Source_Code_Analysis
我最近解决了一个类似的问题,并想分享我的解决方案。
这个工具是出于绝望而诞生的,它解开了一个 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。