2009-08-18 30 views
11

Je travaille sur un programme Perl modérément complexe. Dans le cadre de son développement, il doit subir des modifications et des tests. En raison de certaines contraintes d'environnement, l'exécution fréquente de ce programme n'est pas une option facile à exercer.Comment puis-je créer un graphe d'appel d'analyse statique pour Perl?

Ce que je veux, c'est un générateur de graphe d'appel statique pour Perl. Il n'a pas à couvrir tous les cas de bords (par exemple, redéfinir les variables pour les fonctions ou vice versa dans une évaluation).

(Oui, je sais qu'il ya une centrale d'appel graphique d'exécution avec Devel :: dprofpp, mais la gestion du temps n'est pas garanti d'appeler toutes les fonctions. Je besoin pouvoir regarder chaque fonction

+0

http://stackoverflow.com/questions/1270477/how-can-i-generate-call-graphs-for-perl-modules-and-scripts –

+0

Celui-ci demande spécifiquement pour ** Analyse statique **. L'autre ne spécifie pas si c'est statique ou non. –

+0

Puisqu'il n'y a pas de réponse définitive, je permets de passer à CW. –

Répondre

4

Je ne pense pas qu'il existe un générateur de graphe d'appel "statique" pour Perl. La prochaine chose la plus proche serait Devel::NYTProf.

L'objectif principal est le profilage, mais sa sortie peut vous dire combien de fois un sous-programme a été appelé, et d'où.

Si vous devez vous assurer que tous les sous-programmes sont appelés, vous pouvez également utiliser Devel::Cover, qui vérifie que votre suite de tests couvre chaque sous-routine.

+1

http://www.slideshare.net/Tim.Bunce/develnytprof-200907 –

+0

Brad, le problème est que le profilage à l'exécution dans le cas général ne vous donnera pas toutes les fonctions du programme. Il y aura seulement un sous-ensemble limité du programme exécuté dans chaque exécution. C'est pourquoi je veux spécifiquement un analyseur statique. –

+2

C'est pourquoi j'ai aussi mentionné 'Devel :: Cover', il s'assure que tous vos sous-programmes sont appelés. –

7

ne peut se faire dans le cas général:

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

$obj->$method(); 

Cependant, il devrait être assez facile d'obtenir un grand nombre de cas (exécuter ce programme contre lui-même):

#!/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; 

Notez ce manque

  • appels à des fonctions qui n'utilisent pas entre parenthèses (par exemple print "foo\n";)
  • appels à des fonctions qui sont déréférencées (par ex. $coderef->())
  • appels à des méthodes qui sont des chaînes (par exemple $obj->$method())
  • appelle le putt la parenthèse ouverte sur une autre ligne
  • autres choses que je ne l'ai pas pensé

Il capture de façon incorrecte

  • fonctions (par exemple #foo() a commenté)
  • certaines chaînes (par exemple "foo()")
  • autres choses que je ne l'ai pas pensé

Si vous voulez une meilleure solution que bidouille sans valeur, il est temps de commencer à chercher dans PPI, mais même cela aura des problèmes avec des choses comme $obj->$method().

Juste parce que je m'ennuyais, voici une version qui utilise PPI. Il ne trouve que les appels de fonction (pas les appels de méthode). Il ne fait également aucune tentative pour conserver les noms des sous-programmes uniques (c.-à-d.si vous appelez le même sous-programme plus d'une fois, il apparaîtra plus d'une fois).

#!/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

Chas - Je serais curieux de voir votre code, mais juste comme FYI, certaines tentatives existent déjà, par ex. http://lists.netisland.net/archives/phlpm/phlpm-2004/msg00024.html – DVK

4

Je ne suis pas sûr qu'il est 100% possible (puisque le code Perl ne peut pas être analysé de façon statique en théorie, en raison de BEGIN blocs et tels - voir very recent SO discussion). En outre, les références de sous-programmes peuvent rendre très difficile à faire même dans des endroits où blocs n'entrent pas en jeu.

Cependant, someone apparently made the attempt - Je le sais seulement mais ne l'ai jamais employé ainsi l'acheteur méfiez-vous.

+0

Ce n'est pas 100% réalisable. Je suis d'accord avec ça. 90% est suffisant à mon avis. –