blob: 73dce7323b6a04e46dc5150ce20d4d82f3ffe125 [file] [log] [blame]
/*
* ProGuard -- shrinking, optimization, obfuscation, and preverification
* of Java bytecode.
*
* Copyright (c) 2002-2017 Eric Lafortune @ GuardSquare
*
* 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.optimize.peephole.LineNumberLinearizer;
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.3.3";
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.optimize)
{
linearizeLineNumbers();
}
if (configuration.obfuscate)
{
obfuscate();
}
if (configuration.optimize)
{
trimLineNumbers();
}
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);
}
/**
* Disambiguates the line numbers of all program classes, after
* optimizations like method inlining and class merging.
*/
private void linearizeLineNumbers()
{
programClassPool.classesAccept(new LineNumberLinearizer());
}
/**
* Trims the line number table attributes of all program classes.
*/
private void trimLineNumbers()
{
programClassPool.classesAccept(new AllAttributeVisitor(true,
new LineNumberTableAttributeTrimmer()));
}
/**
* 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);
}
}