20

For Python, there is a script called importchecker which tells you if you have unnecessary import statements.

Is there a similar utility for Perl use (and require) statements?

divibisan
  • 11,659
  • 11
  • 40
  • 58
Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • perlcritic might have that functionality. – ikegami Sep 05 '12 at 17:44
  • 1
    Note that no solution would be 100% accurate. `use Module;` could be a false negative. `use strict qw( refs );` could be a false positive. `use Module qw( :ALL );` could be a false negative and a false positive. – ikegami Sep 05 '12 at 17:51
  • 1
    What exactly need be checked? (1) Simple `use Mod;` may import symbols -- check all those? (2) Or only for the ones listed in `use Mod qw(...);`? (3) How about `use Module qw();` (and no `Mod` subs are used in code)? Is this about a `use` statement that can be removed altogether as it stands, or about specific (unused) symbols related to a particular `use` statement? (I know only of a perlcritic policy for (2).) – zdim Jan 24 '21 at 05:42
  • Why -- is there an actual problem (and what is it?) other than aesthetics? (Like symbol pollution etc) – zdim Jan 24 '21 at 05:47
  • (I just realized that @zwol posted a bounty, so please see my questions above...) – zdim Jan 24 '21 at 09:18
  • @zdim I'm personally looking for as much of all three as I can get, but (2) is the most important for what I'm doing right now. – zwol Jan 24 '21 at 20:12
  • 1
    @zwol I've only found a perlcritic "policy" for some of case (2), [TooMuchCode::ProhibitUnusedImport](https://metacpan.org/pod/Perl::Critic::Policy::TooMuchCode::ProhibitUnusedImport). Have a look. As for the rest, it'd be messy (from what I've seen) but there are tools to help. (I can post an answer with some elaboration and examples if it helps, but I don't have a comprehensive solution.) – zdim Jan 26 '21 at 07:04
  • @zdim That does enough of what I want to be useful, and it's definitely an improvement on the existing answers, and nobody else's offered anything, so please do make that an answer. – zwol Jan 27 '21 at 19:50
  • @zwol Right, that's a good point. I posted an answer (which I'll edit further to clean up, and in particular if more substance turns up) – zdim Jan 28 '21 at 09:21
  • @zwol Edited for additional notes on the policy use and added notes on how to use `B::Xref`. – zdim Jan 29 '21 at 08:05

3 Answers3

5

Take a look at Devel::TraceUse it might give you a chunk of what you're looking for.

Len Jaffe
  • 3,442
  • 1
  • 21
  • 28
  • 1
    That appears to show me what's being loaded. I want to know what's being loaded but not used. – Dennis Williamson Sep 05 '12 at 19:56
  • I don't knwo how you're going to get static analysis without some pretty gnarly parsing, but for dynamic analysis, you could combine TraceUse with NYTProf and write some code that determines all possible methods and subs available in all the used modules, and then subtracts the methods and subs that you call as listed by NYTProf. – Len Jaffe Sep 06 '12 at 04:45
4

Here is a script I wrote to attempt this. It is very simplistic and will not automate anything for you but it will give you something to start with.

#!/usr/bin/perl

use strict;
use v5.14;

use PPI::Document;
use PPI::Dumper;
use PPI::Find;
use Data::Dumper;

my %import;
my $doc = PPI::Document->new($ARGV[0]);

my $use = $doc->find( sub { $_[1]->isa('PPI::Statement::Include') } );
foreach my $u (@$use) {
    my $node = $u->find_first('PPI::Token::QuoteLike::Words');
    next unless $node;
    $import{$u->module} //= [];
    push $import{$u->module}, $node->literal;
}

my $words = $doc->find( sub { $_[1]->isa('PPI::Token::Word') } );


my @words = map { $_->content } @$words;

my %words;
@words{ @words } = 1;

foreach my $u (keys %import) {
    say $u;
    foreach my $w (@{$import{$u}}) {
        if (exists $words{$w}) {
            say "\t- Found $w";
        }
        else {
            say "\t- Can't find $w";
        }
    }
}
stu42j
  • 485
  • 3
  • 10
4

There is a number of ways to load packages and import symbols (or not). I am not aware of a tool which single-handedly and directly checks whether those symbols are used or not.

But for cases where an explicit import list is given,

use Module qw(func1 func2 ...);

there is a Perl::Critic policy TooMuchCode::ProhibitUnusedImport that helps with much of that.

One runs on the command line

perlcritic --single-policy TooMuchCode::ProhibitUnusedImport program.pl

and the program is checked. Or run without --single-policy flag for a complete check and seek Severity 1 violations in the output, which this is.

For an example, consider a program

use warnings;
use strict;
use feature 'say';

use Path::Tiny;                      # a class; but it imports 'path'    
use Data::Dumper;                    # imports 'Dumper' 
use Data::Dump qw(dd pp);            # imports 'dd' and 'pp'
use Cwd qw(cwd);                     # imports only 'cwd'
use Carp qw(carp verbose);           # imports 'carp'; 'verbose' isn't a symbol
use Term::ANSIColor qw(:constants);  # imports a lot of symbols

sub a_func {
    say "\tSome data: ", pp [ 2..5 ];
    carp "\tA warning";
}

say "Current working directory: ", cwd;

a_func();

Running the above perlcritic command prints

Unused import: dd at line 7, column 5.  A token is imported but not used in the same code.  (Severity: 1)
Unused import: verbose at line 9, column 5.  A token is imported but not used in the same code.  (Severity: 1)

We got dd caught, while pp from the same package isn't flagged since it's used (in the sub), and neither are carp and cwd which are also used; as it should be, out of what the policy aims for.

But note

  • whatever comes with :constants tag isn't found

  • word verbose, which isn't a function (and is used implicitly), is reported as unused

  • if a_func() isn't called then those pp and carp in it are still not reported even though they are then unused. This may be OK, since they are present in code, but it is worth noting

(This glitch-list is likely not exhaustive.)

Recall that the import list is passed to an import sub, which may expect and make use of whatever the module's design deemed worthy; these need not be only function names. It is apparently beyond this policy to follow up on all that. Still, loading modules with the explicit import list with function names is good practice and what this policy does cover is an important use case.

Also, per the clearly stated policy's usage, the Dumper (imported by Data::Dumper) isn't found, nor is path from Path::Tiny. The policy does deal with some curious Moose tricks.

How does one do more? One useful tool is Devel::Symdump, which harvests the symbol tables. It catches all symbols in the above program that have been imported (no Path::Tiny methods can be seen if used, of course). The non-existing "symbol" verbose is included as well though. Add

use Devel::Symdump;

my $syms = Devel::Symdump->new;
say for $syms->functions;

to the above example. To also deal with (runtime) require-ed libraries we have to do this at a place in code after they have been loaded, what can be anywhere in the program. Then best do it in an END block, like

END {
    my $ds = Devel::Symdump->new;
    say for $ds->functions;
};

Then we need to check which of these are unused. At this time the best tool I'm aware of for that job is PPI; see a complete example. Another option is to use a profiler, like Devel::NYTProf.


Another option, which requires some legwork, is the compiler's backend B::Xref, which gets practically everything that is used in the program. It is used as

perl -MO=Xref,-oreport_Xref.txt find_unused.pl

and the (voluminous) output is in the file report_Xref.txt.

The output has sections for each involved file, which have subsections for subroutines and their packages. The last section of the output is directly useful for the present purpose.

For the example program used above I get the output file like

File /.../perl5/lib/perl5//Data/Dump.pm
  ... 
  (some 3,000 lines)
  ...
File find_unused.pl          --> there we go, this program's file
  Subroutine (definitions)
    ... dozens of lines ...  
  Subroutine (main)
    Package main
      &a_func           &43
      &cwd              &27
  Subroutine a_func
    Package ?
      @??               14
    Package main
      &carp             &15
      &pp               &14

So we see that cwd gets called (on line 27) and that carp and pp are also called in the sub a_func. Thus dd and path are unused (out of all imported symbols found otherwise, by Devel::Symdump for example). This is easy to parse.

However, while path is reported when used, if one uses new instead (also in Path::Tiny as a traditional constructor) then that isn't reported in this last section, nor are other methods.

So in principle this is one way to find which of the symbols (for functions) reported to exist by Devel::Symdump have been used in the program.


The example here is simple and easy to process but I have no idea how complete, or hard to parse, this is when all kinds of weird ways for using imported subs are taken into account.

zdim
  • 64,580
  • 5
  • 52
  • 81