Home | History | Annotate | Download | only in proguard
      1 /*
      2  * ProGuard -- shrinking, optimization, obfuscation, and preverification
      3  *             of Java bytecode.
      4  *
      5  * Copyright (c) 2002-2009 Eric Lafortune (eric (at) graphics.cornell.edu)
      6  *
      7  * This program is free software; you can redistribute it and/or modify it
      8  * under the terms of the GNU General Public License as published by the Free
      9  * Software Foundation; either version 2 of the License, or (at your option)
     10  * any later version.
     11  *
     12  * This program is distributed in the hope that it will be useful, but WITHOUT
     13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     14  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
     15  * more details.
     16  *
     17  * You should have received a copy of the GNU General Public License along
     18  * with this program; if not, write to the Free Software Foundation, Inc.,
     19  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
     20  */
     21 package proguard;
     22 
     23 import proguard.classfile.ClassPool;
     24 import proguard.classfile.editor.ClassElementSorter;
     25 import proguard.classfile.visitor.*;
     26 import proguard.obfuscate.Obfuscator;
     27 import proguard.optimize.Optimizer;
     28 import proguard.preverify.*;
     29 import proguard.shrink.Shrinker;
     30 
     31 import java.io.*;
     32 
     33 /**
     34  * Tool for shrinking, optimizing, obfuscating, and preverifying Java classes.
     35  *
     36  * @author Eric Lafortune
     37  */
     38 public class ProGuard
     39 {
     40     public static final String VERSION = "ProGuard, version 4.4";
     41 
     42     private final Configuration configuration;
     43     private       ClassPool     programClassPool = new ClassPool();
     44     private final ClassPool     libraryClassPool = new ClassPool();
     45 
     46 
     47     /**
     48      * Creates a new ProGuard object to process jars as specified by the given
     49      * configuration.
     50      */
     51     public ProGuard(Configuration configuration)
     52     {
     53         this.configuration = configuration;
     54     }
     55 
     56 
     57     /**
     58      * Performs all subsequent ProGuard operations.
     59      */
     60     public void execute() throws IOException
     61     {
     62         System.out.println(VERSION);
     63 
     64         GPL.check();
     65 
     66         if (configuration.printConfiguration != null)
     67         {
     68             printConfiguration();
     69         }
     70 
     71         if (configuration.programJars != null     &&
     72             configuration.programJars.hasOutput() &&
     73             new UpToDateChecker(configuration).check())
     74         {
     75             return;
     76         }
     77 
     78         readInput();
     79 
     80         if (configuration.shrink    ||
     81             configuration.optimize  ||
     82             configuration.obfuscate ||
     83             configuration.preverify)
     84         {
     85             initialize();
     86         }
     87 
     88         if (configuration.targetClassVersion != 0)
     89         {
     90             target();
     91         }
     92 
     93         if (configuration.printSeeds != null)
     94         {
     95             printSeeds();
     96         }
     97 
     98         if (configuration.shrink)
     99         {
    100             shrink();
    101         }
    102 
    103         if (configuration.preverify)
    104         {
    105             inlineSubroutines();
    106         }
    107 
    108         if (configuration.optimize)
    109         {
    110             for (int optimizationPass = 0;
    111                  optimizationPass < configuration.optimizationPasses;
    112                  optimizationPass++)
    113             {
    114                 if (!optimize())
    115                 {
    116                     // Stop optimizing if the code doesn't improve any further.
    117                     break;
    118                 }
    119 
    120                 // Shrink again, if we may.
    121                 if (configuration.shrink)
    122                 {
    123                     // Don't print any usage this time around.
    124                     configuration.printUsage       = null;
    125                     configuration.whyAreYouKeeping = null;
    126 
    127                     shrink();
    128                 }
    129             }
    130         }
    131 
    132         if (configuration.obfuscate)
    133         {
    134             obfuscate();
    135         }
    136 
    137         if (configuration.preverify)
    138         {
    139             preverify();
    140         }
    141 
    142         if (configuration.shrink    ||
    143             configuration.optimize  ||
    144             configuration.obfuscate ||
    145             configuration.preverify)
    146         {
    147             sortClassElements();
    148         }
    149 
    150         if (configuration.programJars.hasOutput())
    151         {
    152             writeOutput();
    153         }
    154 
    155         if (configuration.dump != null)
    156         {
    157             dump();
    158         }
    159     }
    160 
    161 
    162     /**
    163      * Prints out the configuration that ProGuard is using.
    164      */
    165     private void printConfiguration() throws IOException
    166     {
    167         if (configuration.verbose)
    168         {
    169             System.out.println("Printing configuration to [" + fileName(configuration.printConfiguration) + "]...");
    170         }
    171 
    172         PrintStream ps = createPrintStream(configuration.printConfiguration);
    173         try
    174         {
    175             new ConfigurationWriter(ps).write(configuration);
    176         }
    177         finally
    178         {
    179             closePrintStream(ps);
    180         }
    181     }
    182 
    183 
    184     /**
    185      * Reads the input class files.
    186      */
    187     private void readInput() throws IOException
    188     {
    189         if (configuration.verbose)
    190         {
    191             System.out.println("Reading input...");
    192         }
    193 
    194         // Fill the program class pool and the library class pool.
    195         new InputReader(configuration).execute(programClassPool, libraryClassPool);
    196     }
    197 
    198 
    199     /**
    200      * Initializes the cross-references between all classes, performs some
    201      * basic checks, and shrinks the library class pool.
    202      */
    203     private void initialize() throws IOException
    204     {
    205         if (configuration.verbose)
    206         {
    207             System.out.println("Initializing...");
    208         }
    209 
    210         new Initializer(configuration).execute(programClassPool, libraryClassPool);
    211     }
    212 
    213 
    214     /**
    215      * Sets that target versions of the program classes.
    216      */
    217     private void target() throws IOException
    218     {
    219         if (configuration.verbose)
    220         {
    221             System.out.println("Setting target versions...");
    222         }
    223 
    224         new Targeter(configuration).execute(programClassPool);
    225     }
    226 
    227 
    228     /**
    229      * Prints out classes and class members that are used as seeds in the
    230      * shrinking and obfuscation steps.
    231      */
    232     private void printSeeds() throws IOException
    233     {
    234         if (configuration.verbose)
    235         {
    236             System.out.println("Printing kept classes, fields, and methods...");
    237         }
    238 
    239         // Check if we have at least some keep commands.
    240         if (configuration.keep == null)
    241         {
    242             throw new IOException("You have to specify '-keep' options for the shrinking step.");
    243         }
    244 
    245         PrintStream ps = createPrintStream(configuration.printSeeds);
    246         try
    247         {
    248             // Create a visitor for printing out the seeds. We're  printing out
    249             // the program elements that are preserved against shrinking,
    250             // optimization, or obfuscation.
    251             SimpleClassPrinter printer = new SimpleClassPrinter(false, ps);
    252             ClassPoolVisitor classPoolvisitor =
    253                 ClassSpecificationVisitorFactory.createClassPoolVisitor(configuration.keep,
    254                                                                         new ProgramClassFilter(printer),
    255                                                                         new ProgramMemberFilter(printer),
    256                                                                         true,
    257                                                                         true,
    258                                                                         true);
    259 
    260             // Print out the seeds.
    261             programClassPool.accept(classPoolvisitor);
    262             libraryClassPool.accept(classPoolvisitor);
    263         }
    264         finally
    265         {
    266             closePrintStream(ps);
    267         }
    268     }
    269 
    270 
    271     /**
    272      * Performs the shrinking step.
    273      */
    274     private void shrink() throws IOException
    275     {
    276         if (configuration.verbose)
    277         {
    278             System.out.println("Shrinking...");
    279 
    280             // We'll print out some explanation, if requested.
    281             if (configuration.whyAreYouKeeping != null)
    282             {
    283                 System.out.println("Explaining why classes and class members are being kept...");
    284             }
    285 
    286             // We'll print out the usage, if requested.
    287             if (configuration.printUsage != null)
    288             {
    289                 System.out.println("Printing usage to [" + fileName(configuration.printUsage) + "]...");
    290             }
    291         }
    292 
    293         // Perform the actual shrinking.
    294         programClassPool =
    295             new Shrinker(configuration).execute(programClassPool, libraryClassPool);
    296     }
    297 
    298 
    299     /**
    300      * Performs the subroutine inlining step.
    301      */
    302     private void inlineSubroutines()
    303     {
    304         if (configuration.verbose)
    305         {
    306             System.out.println("Inlining subroutines...");
    307         }
    308 
    309         // Perform the actual inlining.
    310         new SubroutineInliner(configuration).execute(programClassPool);
    311     }
    312 
    313 
    314     /**
    315      * Performs the optimization step.
    316      */
    317     private boolean optimize() throws IOException
    318     {
    319         if (configuration.verbose)
    320         {
    321             System.out.println("Optimizing...");
    322         }
    323 
    324         // Perform the actual optimization.
    325         return new Optimizer(configuration).execute(programClassPool, libraryClassPool);
    326     }
    327 
    328 
    329     /**
    330      * Performs the obfuscation step.
    331      */
    332     private void obfuscate() throws IOException
    333     {
    334         if (configuration.verbose)
    335         {
    336             System.out.println("Obfuscating...");
    337 
    338             // We'll apply a mapping, if requested.
    339             if (configuration.applyMapping != null)
    340             {
    341                 System.out.println("Applying mapping [" + fileName(configuration.applyMapping) + "]");
    342             }
    343 
    344             // We'll print out the mapping, if requested.
    345             if (configuration.printMapping != null)
    346             {
    347                 System.out.println("Printing mapping to [" + fileName(configuration.printMapping) + "]...");
    348             }
    349         }
    350 
    351         // Perform the actual obfuscation.
    352         new Obfuscator(configuration).execute(programClassPool, libraryClassPool);
    353     }
    354 
    355 
    356     /**
    357      * Performs the preverification step.
    358      */
    359     private void preverify()
    360     {
    361         if (configuration.verbose)
    362         {
    363             System.out.println("Preverifying...");
    364         }
    365 
    366         // Perform the actual preverification.
    367         new Preverifier(configuration).execute(programClassPool);
    368     }
    369 
    370 
    371     /**
    372      * Sorts the elements of all program classes.
    373      */
    374     private void sortClassElements()
    375     {
    376         programClassPool.classesAccept(new ClassElementSorter());
    377     }
    378 
    379 
    380     /**
    381      * Writes the output class files.
    382      */
    383     private void writeOutput() throws IOException
    384     {
    385         if (configuration.verbose)
    386         {
    387             System.out.println("Writing output...");
    388         }
    389 
    390         // Write out the program class pool.
    391         new OutputWriter(configuration).execute(programClassPool);
    392     }
    393 
    394 
    395     /**
    396      * Prints out the contents of the program classes.
    397      */
    398     private void dump() throws IOException
    399     {
    400         if (configuration.verbose)
    401         {
    402             System.out.println("Printing classes to [" + fileName(configuration.dump) + "]...");
    403         }
    404 
    405         PrintStream ps = createPrintStream(configuration.dump);
    406         try
    407         {
    408             programClassPool.classesAccept(new ClassPrinter(ps));
    409         }
    410         finally
    411         {
    412             closePrintStream(ps);
    413         }
    414     }
    415 
    416 
    417     /**
    418      * Returns a print stream for the given file, or the standard output if
    419      * the file name is empty.
    420      */
    421     private PrintStream createPrintStream(File file)
    422     throws FileNotFoundException
    423     {
    424         return isFile(file) ?
    425             new PrintStream(new BufferedOutputStream(new FileOutputStream(file))) :
    426             System.out;
    427     }
    428 
    429 
    430     /**
    431      * Closes the given print stream, or closes it if is the standard output.
    432      * @param printStream
    433      */
    434     private void closePrintStream(PrintStream printStream)
    435     {
    436         if (printStream == System.out)
    437         {
    438             printStream.flush();
    439         }
    440         else
    441         {
    442             printStream.close();
    443         }
    444     }
    445 
    446 
    447     /**
    448      * Returns the absolute file name for the given file, or the standard output
    449      * if the file name is empty.
    450      */
    451     private String fileName(File file)
    452     {
    453         return isFile(file) ?
    454             file.getAbsolutePath() :
    455             "standard output";
    456     }
    457 
    458 
    459     /**
    460      * Returns whether the given file is actually a file, or just a placeholder
    461      * for the standard output.
    462      */
    463     private boolean isFile(File file)
    464     {
    465         return file.getPath().length() > 0;
    466     }
    467 
    468 
    469     /**
    470      * The main method for ProGuard.
    471      */
    472     public static void main(String[] args)
    473     {
    474         if (args.length == 0)
    475         {
    476             System.out.println(VERSION);
    477             System.out.println("Usage: java proguard.ProGuard [options ...]");
    478             System.exit(1);
    479         }
    480 
    481         // Create the default options.
    482         Configuration configuration = new Configuration();
    483 
    484         try
    485         {
    486             // Parse the options specified in the command line arguments.
    487             ConfigurationParser parser = new ConfigurationParser(args);
    488 
    489             try
    490             {
    491                 parser.parse(configuration);
    492             }
    493             finally
    494             {
    495                 parser.close();
    496             }
    497 
    498             // Execute ProGuard with these options.
    499             new ProGuard(configuration).execute();
    500         }
    501         catch (Exception ex)
    502         {
    503             if (configuration.verbose)
    504             {
    505                 // Print a verbose stack trace.
    506                 ex.printStackTrace();
    507             }
    508             else
    509             {
    510                 // Print just the stack trace message.
    511                 System.err.println("Error: "+ex.getMessage());
    512             }
    513 
    514             System.exit(1);
    515         }
    516 
    517         System.exit(0);
    518     }
    519 }
    520