/*
|
* 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);
|
}
|
}
|