Mark Dominus on 16 Jan 2004 22:16:37 -0000


[Date Prev] [Date Next] [Thread Prev] [Thread Next] [Date Index] [Thread Index]

Re: Perl call tree utility


Mark Dominus:
> I'm looking for a utility which will take a program and produce a
> table of which functions can call which other functions.  It should do
> this by static analysis of the source code.  I don't want just a table
> of which functions *have* called which functions in the course of some
> particular run.
> 
> I wasn't able to find anything on CPAN, and I suspect I just haven't
> thought of the right keyword.

I gave up on CPAN and wrote one.  It requires the B::Utils module.

To use, do

        perl -MCalltree yourprogram.pl

It then prints out a report like this:


        __MAIN__: 
          Getopt::Long::config
          HTML::Parse::parse_html
          LWP::Debug::level
          LWP::Version
          main::GetOptions
          main::printResponseChain
          main::str2time
          main::time2str
          main::uf_uri
          main::usage

        main::GetOptions: 
          Getopt::Long::FindOption
          Getopt::Long::OptCtl
          Getopt::Long::ParseOptionSpec

        main::printResponseChain: 
          main::printResponseChain

        main::status_message: 

        main::str2time: 
          HTTP::Date::parse_date
          Time::Local::timegm
          Time::Local::timelocal
          Time::Zone::tz_offset

        main::time2str: 

        main::uf_uri: 
          URI::Heuristic::uf_uristr

        main::usage: 


Actually the default output includes the report for all the other
modules too.  There's an option to suppress this, but since there's no
documentation you'll have to read the code to find it.  If there are
any wizards-in-training on the list, I think a good exercise for them
would be to read and understand this code.


Share and enjoy!

----------------------------------------------------------------
package Calltree;
use B::Utils qw(all_roots walkoptree_simple);

my %legal_options 
  = (INCLUDE_PACKAGES => undef,
     EXCLUDE_PACKAGES => [__PACKAGE__],
     CALLBACK =>  \&print_report,
     CALLBACK_DATA => undef,
    );

our %OPT;

sub import {
  my ($class, %opts) = @_;
  my @BAD;
  for my $k (keys %opts) {
    if (exists $legal_option{uc $k}) {
      $OPT{uc $k} = $opts{$k};
    } else {
      push @BAD, $k ;
    }
  }
  if (@BAD) {
    my $options = @BAD == 1 ? 'option' : 'options';
    require Carp;
    Carp::croak "$class: unrecognized $options @BAD";
  }
  for my $k (keys %legal_options) {
    $OPT{$k} = $legal_options{$k} unless defined $OPT{$k};
  }
}

sub array_to_hash {
  my %h;
  for (@_) { $h{$_} = 1 }
  \%h;
}

sub adjust_options {
  my $opt = shift;
  if (! defined $opt->{INCLUDE_PACKAGES}) {
    $opt->{INCLUDE_PACKAGES} = array_to_hash(walk_stashes(), 'main');
  }

  for my $k (qw(INCLUDE_PACKAGES EXCLUDE_PACKAGES)) {
    if (! ref $opt->{$k}) {
      $opt->{$k} = array_to_hash(split /,\s*/, $opt->{$k});
    } elsif (ref $opt->{$k} eq 'ARRAY') {
      $opt->{$k} = array_to_hash(@{$opt->{$k}});
    }
  }
}

sub walk_stashes {
  my $top = shift || '';
  return if $top eq '::main';
#  print "* $top\n";
  my @packages = $top;
  while (my $name = each %{"$top\::"}) {
    next unless $name =~ s/::$//;
    push @packages, walk_stashes("$top\::$name");
  }
#  print "=> @packages\n";
  map /^(?:::)?(.*)/, @packages;
}

sub trim_stashname {
  my $sn = shift;
  $sn =~ s/::$//;
  return $sn;
}

sub INIT {
  adjust_options(\%OPT);

  my %root = all_roots();
  my %CALLS;
  while (my ($name, $root) = each %root) {
    my ($pkg) = $name =~ /(.*)::/; 
    next unless $OPT{INCLUDE_PACKAGES}{$pkg};
    next if $OPT{EXCLUDE_PACKAGES}{$pkg};
    my @CALLS;
    $CALLS{$name} = {};
    walkoptree_simple($root, \&find_subcall, \@CALLS);
    for my $call (@CALLS) {
      $CALLS{$name}{$call} = 1;
    }
  } 
  $OPT{CALLBACK}->(\%CALLS, $OPT{CALLBACK_DATA});
  exit;
}

sub find_subcall {
  my ($op, $dest) = @_;
  if ($op->name eq 'gv' && $op->next && $op->next->name eq 'entersub') {
    my $cur_gv = $op->gv;
    push @$dest, join '::', $cur_gv->STASH->NAME, $cur_gv->NAME; 
  }
}

sub print_report {
  my $C = shift;
  for my $caller (sort keys %$C) {
    print "\n$caller: \n";
    for my $callee (sort keys %{$C->{$caller}}) {
      print "  $callee\n";
    }
  }
}

"Cogito, ergo sum";
-
**Majordomo list services provided by PANIX <URL:http://www.panix.com>**
**To Unsubscribe, send "unsubscribe phl" to majordomo@lists.pm.org**