2009-08-18 6 views
11

Sto lavorando a un programma Perl moderatamente complesso. Come parte del suo sviluppo, deve passare attraverso modifiche e test. A causa di alcuni vincoli ambientali, l'esecuzione frequente di questo programma non è un'opzione facile da esercitare.Come posso creare un grafico di chiamata di analisi statica per Perl?

Quello che voglio è un generatore di call-graph statico per Perl. Non deve coprire ogni caso limite (e, g., Ridefinire le variabili per essere funzioni o viceversa in una valutazione).

(Sì, so che c'è un impianto di generazione di call-grafico run-time con Devel :: DprofPP, ma in fase di esecuzione non è garantito a chiamare ogni funzione. Ho bisogno di essere in grado di guardare ogni funzione .)

+0

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

+0

Questo è specificamente chiedendo ** Analisi statica **. L'altro non specifica se è statico o meno. –

+0

Dato che non c'è una risposta definitiva, lo cambio in CW. –

risposta

4

Non penso che ci sia un generatore di grafo di chiamata "statico" per Perl.

La prossima cosa più vicina sarebbe Devel::NYTProf.

L'obiettivo principale è la profilazione, ma l'output può dirvi quante volte è stata richiamata una subroutine e da dove.

Se è necessario assicurarsi che tutte le subroutine vengano richiamate, è possibile utilizzare anche Devel::Cover, che verifica che la propria suite di test copra tutte le subroutine.

+1

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

+0

Brad, il problema è che il profilo di runtime nel caso generale non fornirà tutte le funzioni del programma. Ci sarà solo un sottoinsieme limitato del programma eseguito in ogni esecuzione. Ecco perché voglio specificamente un analizzatore statico. –

+2

Ecco perché ho anche citato 'Devel :: Cover', che assicura che tutte le subroutine vengano chiamate. –

7

non può essere fatto nel caso generale:

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

$obj->$method(); 

Tuttavia, dovrebbe essere abbastanza facile da ottenere un gran numero di casi (Esegui questo programma contro se stesso):

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

Nota, questo manca

  • chiamate a funzioni che non utilizzano parentesi (ad esempio print "foo\n";)
  • chiamate a funzioni sottoposte a dereferenziazione (ad es. $coderef->())
  • chiamate a metodi che sono stringhe (ad es $obj->$method())
  • chiama il putt la parentesi aperta su una linea diversa
  • altre cose che non ho pensato di

cattura in modo non corretto

  • commentato funzioni (ad esempio #foo())
  • alcune stringhe (ad esempio "foo()")
  • altre cose che non ho pensato di

Se si desidera una soluzione migliore di quella mod inutile, è il momento di cominciare a cercare in PPI, ma anche che avrò problemi con le cose come $obj->$method().

Proprio perché ero annoiato, ecco una versione che utilizza PPI. Trova solo chiamate di funzione (non chiamate di metodo). Inoltre, non fa alcun tentativo di mantenere univoci i nomi delle subroutine (ad es.se chiami la stessa subroutine più di una volta verrà mostrata più di una volta).

#!/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 - Sarei curioso di vedere il tuo codice, ma proprio come FYI, alcuni tentativi esistono già, ad es. http://lists.netisland.net/archives/phlpm/phlpm-2004/msg00024.html – DVK

4

io non sono sicuro che è fattibile al 100% (dal momento che il codice Perl non può essere analizzato in modo statico, in teoria, a causa di BEGIN blocchi e tali - vedi very recent SO discussion). Inoltre, i riferimenti alle subroutine possono rendere molto difficile anche nei luoghi in cui i blocchi BEGIN non entrano in gioco.

Tuttavia, someone apparently made the attempt - Lo so solo ma non l'ho mai usato, quindi l'acquirente deve stare attento.

+0

Non è lavorabile al 100%. Sto bene con quello. Il 90% è sufficiente secondo me. –