如何为Perl创建静态分析调用图?

时间:2009-08-18 17:19:40

标签: perl static-analysis call-graph

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

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

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

5 个答案:

答案 0 :(得分: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()
  • 将putt称为不同行上的左括号
  • 我没有想过的其他事情

错误地捕获

  • 评论了函数(例如#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;
}

答案 1 :(得分:4)

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

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

主要目标是分析,但它的输出可以告诉你子程序被调用了多少次,以及从哪里开始。

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

答案 2 :(得分:4)

我不确定它是否100%可行(因为Perl代码在理论上无法进行静态分析,因为BEGIN块等等 - 请参阅very recent SO discussion)。此外,即使在BEGIN块不起作用的地方,子程序引用也可能很难做到。

然而,someone apparently made the attempt - 我只知道它,但从未使用它,所以买家要小心。

答案 3 :(得分:2)

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

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

答案 4 :(得分:1)

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

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

一个简单的正则表达式引擎的好处是它可以很容易地扩展到其他语言。
该工具现在还支持 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