| /* |
| * ProGuard -- shrinking, optimization, obfuscation, and preverification |
| * of Java bytecode. |
| * |
| * Copyright (c) 2002-2014 Eric Lafortune (eric@graphics.cornell.edu) |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License as published by the Free |
| * Software Foundation; either version 2 of the License, or (at your option) |
| * any later version. |
| * |
| * This program is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
| * more details. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with this program; if not, write to the Free Software Foundation, Inc., |
| * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| package proguard; |
| |
| import proguard.classfile.*; |
| import proguard.classfile.attribute.visitor.AllAttributeVisitor; |
| import proguard.classfile.editor.*; |
| import proguard.classfile.visitor.*; |
| import proguard.obfuscate.Obfuscator; |
| import proguard.optimize.Optimizer; |
| import proguard.preverify.*; |
| import proguard.shrink.Shrinker; |
| |
| import java.io.*; |
| |
| /** |
| * Tool for shrinking, optimizing, obfuscating, and preverifying Java classes. |
| * |
| * @author Eric Lafortune |
| */ |
| public class ProGuard |
| { |
| public static final String VERSION = "ProGuard, version 5.1"; |
| |
| private final Configuration configuration; |
| private ClassPool programClassPool = new ClassPool(); |
| private final ClassPool libraryClassPool = new ClassPool(); |
| |
| |
| /** |
| * Creates a new ProGuard object to process jars as specified by the given |
| * configuration. |
| */ |
| public ProGuard(Configuration configuration) |
| { |
| this.configuration = configuration; |
| } |
| |
| |
| /** |
| * Performs all subsequent ProGuard operations. |
| */ |
| public void execute() throws IOException |
| { |
| System.out.println(VERSION); |
| |
| GPL.check(); |
| |
| if (configuration.printConfiguration != null) |
| { |
| printConfiguration(); |
| } |
| |
| new ConfigurationChecker(configuration).check(); |
| |
| if (configuration.programJars != null && |
| configuration.programJars.hasOutput() && |
| new UpToDateChecker(configuration).check()) |
| { |
| return; |
| } |
| |
| readInput(); |
| |
| if (configuration.shrink || |
| configuration.optimize || |
| configuration.obfuscate || |
| configuration.preverify) |
| { |
| clearPreverification(); |
| } |
| |
| if (configuration.printSeeds != null || |
| configuration.shrink || |
| configuration.optimize || |
| configuration.obfuscate || |
| configuration.preverify) |
| { |
| initialize(); |
| } |
| |
| if (configuration.targetClassVersion != 0) |
| { |
| target(); |
| } |
| |
| if (configuration.printSeeds != null) |
| { |
| printSeeds(); |
| } |
| |
| if (configuration.shrink) |
| { |
| shrink(); |
| } |
| |
| if (configuration.preverify) |
| { |
| inlineSubroutines(); |
| } |
| |
| if (configuration.optimize) |
| { |
| for (int optimizationPass = 0; |
| optimizationPass < configuration.optimizationPasses; |
| optimizationPass++) |
| { |
| if (!optimize()) |
| { |
| // Stop optimizing if the code doesn't improve any further. |
| break; |
| } |
| |
| // Shrink again, if we may. |
| if (configuration.shrink) |
| { |
| // Don't print any usage this time around. |
| configuration.printUsage = null; |
| configuration.whyAreYouKeeping = null; |
| |
| shrink(); |
| } |
| } |
| } |
| |
| if (configuration.obfuscate) |
| { |
| obfuscate(); |
| } |
| |
| if (configuration.preverify) |
| { |
| preverify(); |
| } |
| |
| if (configuration.shrink || |
| configuration.optimize || |
| configuration.obfuscate || |
| configuration.preverify) |
| { |
| sortClassElements(); |
| } |
| |
| if (configuration.programJars.hasOutput()) |
| { |
| writeOutput(); |
| } |
| |
| if (configuration.dump != null) |
| { |
| dump(); |
| } |
| } |
| |
| |
| /** |
| * Prints out the configuration that ProGuard is using. |
| */ |
| private void printConfiguration() throws IOException |
| { |
| if (configuration.verbose) |
| { |
| System.out.println("Printing configuration to [" + fileName(configuration.printConfiguration) + "]..."); |
| } |
| |
| PrintStream ps = createPrintStream(configuration.printConfiguration); |
| try |
| { |
| new ConfigurationWriter(ps).write(configuration); |
| } |
| finally |
| { |
| closePrintStream(ps); |
| } |
| } |
| |
| |
| /** |
| * Reads the input class files. |
| */ |
| private void readInput() throws IOException |
| { |
| if (configuration.verbose) |
| { |
| System.out.println("Reading input..."); |
| } |
| |
| // Fill the program class pool and the library class pool. |
| new InputReader(configuration).execute(programClassPool, libraryClassPool); |
| } |
| |
| |
| /** |
| * Initializes the cross-references between all classes, performs some |
| * basic checks, and shrinks the library class pool. |
| */ |
| private void initialize() throws IOException |
| { |
| if (configuration.verbose) |
| { |
| System.out.println("Initializing..."); |
| } |
| |
| new Initializer(configuration).execute(programClassPool, libraryClassPool); |
| } |
| |
| |
| /** |
| * Sets that target versions of the program classes. |
| */ |
| private void target() throws IOException |
| { |
| if (configuration.verbose) |
| { |
| System.out.println("Setting target versions..."); |
| } |
| |
| new Targeter(configuration).execute(programClassPool); |
| } |
| |
| |
| /** |
| * Prints out classes and class members that are used as seeds in the |
| * shrinking and obfuscation steps. |
| */ |
| private void printSeeds() throws IOException |
| { |
| if (configuration.verbose) |
| { |
| System.out.println("Printing kept classes, fields, and methods..."); |
| } |
| |
| PrintStream ps = createPrintStream(configuration.printSeeds); |
| try |
| { |
| new SeedPrinter(ps).write(configuration, programClassPool, libraryClassPool); |
| } |
| finally |
| { |
| closePrintStream(ps); |
| } |
| } |
| |
| |
| /** |
| * Performs the shrinking step. |
| */ |
| private void shrink() throws IOException |
| { |
| if (configuration.verbose) |
| { |
| System.out.println("Shrinking..."); |
| |
| // We'll print out some explanation, if requested. |
| if (configuration.whyAreYouKeeping != null) |
| { |
| System.out.println("Explaining why classes and class members are being kept..."); |
| } |
| |
| // We'll print out the usage, if requested. |
| if (configuration.printUsage != null) |
| { |
| System.out.println("Printing usage to [" + fileName(configuration.printUsage) + "]..."); |
| } |
| } |
| |
| // Perform the actual shrinking. |
| programClassPool = |
| new Shrinker(configuration).execute(programClassPool, libraryClassPool); |
| } |
| |
| |
| /** |
| * Performs the subroutine inlining step. |
| */ |
| private void inlineSubroutines() |
| { |
| if (configuration.verbose) |
| { |
| System.out.println("Inlining subroutines..."); |
| } |
| |
| // Perform the actual inlining. |
| new SubroutineInliner(configuration).execute(programClassPool); |
| } |
| |
| |
| /** |
| * Performs the optimization step. |
| */ |
| private boolean optimize() throws IOException |
| { |
| if (configuration.verbose) |
| { |
| System.out.println("Optimizing..."); |
| } |
| |
| // Perform the actual optimization. |
| return new Optimizer(configuration).execute(programClassPool, libraryClassPool); |
| } |
| |
| |
| /** |
| * Performs the obfuscation step. |
| */ |
| private void obfuscate() throws IOException |
| { |
| if (configuration.verbose) |
| { |
| System.out.println("Obfuscating..."); |
| |
| // We'll apply a mapping, if requested. |
| if (configuration.applyMapping != null) |
| { |
| System.out.println("Applying mapping [" + fileName(configuration.applyMapping) + "]"); |
| } |
| |
| // We'll print out the mapping, if requested. |
| if (configuration.printMapping != null) |
| { |
| System.out.println("Printing mapping to [" + fileName(configuration.printMapping) + "]..."); |
| } |
| } |
| |
| // Perform the actual obfuscation. |
| new Obfuscator(configuration).execute(programClassPool, libraryClassPool); |
| } |
| |
| |
| /** |
| * Clears any JSE preverification information from the program classes. |
| */ |
| private void clearPreverification() |
| { |
| programClassPool.classesAccept( |
| new ClassVersionFilter(ClassConstants.CLASS_VERSION_1_6, |
| new AllMethodVisitor( |
| new AllAttributeVisitor( |
| new NamedAttributeDeleter(ClassConstants.ATTR_StackMapTable))))); |
| } |
| |
| |
| /** |
| * Performs the preverification step. |
| */ |
| private void preverify() |
| { |
| if (configuration.verbose) |
| { |
| System.out.println("Preverifying..."); |
| } |
| |
| // Perform the actual preverification. |
| new Preverifier(configuration).execute(programClassPool); |
| } |
| |
| |
| /** |
| * Sorts the elements of all program classes. |
| */ |
| private void sortClassElements() |
| { |
| programClassPool.classesAccept(new ClassElementSorter()); |
| } |
| |
| |
| /** |
| * Writes the output class files. |
| */ |
| private void writeOutput() throws IOException |
| { |
| if (configuration.verbose) |
| { |
| System.out.println("Writing output..."); |
| } |
| |
| // Write out the program class pool. |
| new OutputWriter(configuration).execute(programClassPool); |
| } |
| |
| |
| /** |
| * Prints out the contents of the program classes. |
| */ |
| private void dump() throws IOException |
| { |
| if (configuration.verbose) |
| { |
| System.out.println("Printing classes to [" + fileName(configuration.dump) + "]..."); |
| } |
| |
| PrintStream ps = createPrintStream(configuration.dump); |
| try |
| { |
| programClassPool.classesAccept(new ClassPrinter(ps)); |
| } |
| finally |
| { |
| closePrintStream(ps); |
| } |
| } |
| |
| |
| /** |
| * Returns a print stream for the given file, or the standard output if |
| * the file name is empty. |
| */ |
| private PrintStream createPrintStream(File file) |
| throws FileNotFoundException |
| { |
| return file == Configuration.STD_OUT ? System.out : |
| new PrintStream( |
| new BufferedOutputStream( |
| new FileOutputStream(file))); |
| } |
| |
| |
| /** |
| * Closes the given print stream, or closes it if is the standard output. |
| * @param printStream |
| */ |
| private void closePrintStream(PrintStream printStream) |
| { |
| if (printStream == System.out) |
| { |
| printStream.flush(); |
| } |
| else |
| { |
| printStream.close(); |
| } |
| } |
| |
| |
| /** |
| * Returns the canonical file name for the given file, or "standard output" |
| * if the file name is empty. |
| */ |
| private String fileName(File file) |
| { |
| if (file == Configuration.STD_OUT) |
| { |
| return "standard output"; |
| } |
| else |
| { |
| try |
| { |
| return file.getCanonicalPath(); |
| } |
| catch (IOException ex) |
| { |
| return file.getPath(); |
| } |
| } |
| } |
| |
| |
| /** |
| * The main method for ProGuard. |
| */ |
| public static void main(String[] args) |
| { |
| if (args.length == 0) |
| { |
| System.out.println(VERSION); |
| System.out.println("Usage: java proguard.ProGuard [options ...]"); |
| System.exit(1); |
| } |
| |
| // Create the default options. |
| Configuration configuration = new Configuration(); |
| |
| try |
| { |
| // Parse the options specified in the command line arguments. |
| ConfigurationParser parser = new ConfigurationParser(args, |
| System.getProperties()); |
| try |
| { |
| parser.parse(configuration); |
| } |
| finally |
| { |
| parser.close(); |
| } |
| |
| // Execute ProGuard with these options. |
| new ProGuard(configuration).execute(); |
| } |
| catch (Exception ex) |
| { |
| if (configuration.verbose) |
| { |
| // Print a verbose stack trace. |
| ex.printStackTrace(); |
| } |
| else |
| { |
| // Print just the stack trace message. |
| System.err.println("Error: "+ex.getMessage()); |
| } |
| |
| System.exit(1); |
| } |
| |
| System.exit(0); |
| } |
| } |