proguard.ProGuard Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of proguard-base Show documentation
Show all versions of proguard-base Show documentation
ProGuard is a free shrinker, optimizer, obfuscator, and preverifier for Java bytecode
/*
* ProGuard -- shrinking, optimization, obfuscation, and preverification
* of Java bytecode.
*
* Copyright (c) 2002-2021 Guardsquare NV
*
* 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 org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import proguard.backport.Backporter;
import proguard.classfile.*;
import proguard.classfile.attribute.Attribute;
import proguard.classfile.attribute.visitor.AllAttributeVisitor;
import proguard.classfile.editor.*;
import proguard.classfile.io.kotlin.KotlinMetadataWriter;
import proguard.classfile.kotlin.visitor.ReferencedKotlinMetadataVisitor;
import proguard.classfile.util.*;
import proguard.classfile.visitor.*;
import proguard.configuration.ConfigurationLoggingAdder;
import proguard.evaluation.IncompleteClassHierarchyException;
import proguard.configuration.InitialStateInfo;
import proguard.io.ExtraDataEntryNameMap;
import proguard.logging.Logging;
import proguard.mark.Marker;
import proguard.obfuscate.Obfuscator;
import proguard.optimize.Optimizer;
import proguard.optimize.gson.GsonOptimizer;
import proguard.optimize.peephole.LineNumberLinearizer;
import proguard.preverify.*;
import proguard.resources.file.ResourceFilePool;
import proguard.shrink.Shrinker;
import proguard.strip.KotlinAnnotationStripper;
import proguard.util.*;
import java.io.*;
/**
* Tool for shrinking, optimizing, obfuscating, and preverifying Java classes.
*
* @author Eric Lafortune
*/
public class ProGuard
{
private static final Logger logger = LogManager.getLogger(ProGuard.class);
public static final String VERSION = "ProGuard, version " + getVersion();
private final Configuration configuration;
private ClassPool programClassPool = new ClassPool();
private final ClassPool libraryClassPool = new ClassPool();
private final ResourceFilePool resourceFilePool = new ResourceFilePool();
// Stores information about the original state of the program class pool
// used for configuration debugging.
private InitialStateInfo initialStateInfo;
// All injected data entries.
private final ExtraDataEntryNameMap extraDataEntryNameMap = new ExtraDataEntryNameMap();
/**
* 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
{
Logging.configureVerbosity(configuration.verbose);
logger.always().log(VERSION);
GPL.check();
try
{
if (configuration.printConfiguration != null)
{
printConfiguration();
}
new ConfigurationChecker(configuration).check();
if (configuration.programJars != null &&
configuration.programJars.hasOutput() &&
new UpToDateChecker(configuration).check())
{
return;
}
if (configuration.targetClassVersion != 0)
{
configuration.backport = true;
}
readInput();
if (configuration.shrink ||
configuration.optimize ||
configuration.obfuscate ||
configuration.preverify)
{
clearPreverification();
}
if (configuration.printSeeds != null ||
configuration.backport ||
configuration.shrink ||
configuration.optimize ||
configuration.obfuscate ||
configuration.preverify ||
configuration.addConfigurationDebugging ||
configuration.keepKotlinMetadata)
{
initialize();
mark();
}
if (configuration.addConfigurationDebugging)
{
// Remember the initial state of the program classpool and resource filepool
// before shrinking / obfuscation / optimization.
initialStateInfo = new InitialStateInfo(programClassPool);
}
if (configuration.keepKotlinMetadata)
{
stripKotlinMetadataAnnotations();
}
if (configuration.optimize ||
configuration.obfuscate)
{
introducePrimitiveArrayConstants();
}
if (configuration.backport)
{
backport();
}
if (configuration.addConfigurationDebugging)
{
addConfigurationLogging();
}
if (configuration.printSeeds != null)
{
printSeeds();
}
if (configuration.preverify ||
configuration.android)
{
inlineSubroutines();
}
if (configuration.shrink)
{
shrink();
}
if (configuration.optimize)
{
optimizeGson();
}
if (configuration.optimize)
{
for (int optimizationPass = 0;
optimizationPass < configuration.optimizationPasses;
optimizationPass++)
{
if (!optimize(optimizationPass+1, configuration.optimizationPasses))
{
// 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();
}
}
linearizeLineNumbers();
}
if (configuration.obfuscate)
{
obfuscate();
}
if (configuration.keepKotlinMetadata)
{
keepKotlinMetadata();
}
if (configuration.optimize ||
configuration.obfuscate)
{
expandPrimitiveArrayConstants();
}
if (configuration.targetClassVersion != 0)
{
target();
}
if (configuration.preverify)
{
preverify();
}
// Trim line numbers after preverification as this might
// also remove some instructions.
if (configuration.optimize ||
configuration.preverify)
{
trimLineNumbers();
}
if (configuration.shrink ||
configuration.optimize ||
configuration.obfuscate ||
configuration.preverify)
{
sortClassElements();
}
if (configuration.programJars.hasOutput())
{
writeOutput();
}
if (configuration.dump != null)
{
dump();
}
}
catch (IncompleteClassHierarchyException e)
{
throw new RuntimeException(
System.lineSeparator() + System.lineSeparator() +
"It appears you are missing some classes resulting in an incomplete class hierarchy, " + System.lineSeparator() +
"please refer to the troubleshooting page in the manual: " + System.lineSeparator() +
"https://www.guardsquare.com/en/products/proguard/manual/troubleshooting#superclass" + System.lineSeparator()
);
}
}
/**
* Prints out the configuration that ProGuard is using.
*/
private void printConfiguration() throws IOException
{
logger.info("Printing configuration to [{}]...",
PrintWriterUtil.fileName(configuration.printConfiguration)
);
PrintWriter pw = PrintWriterUtil.createPrintWriterOut(configuration.printConfiguration);
try
{
new ConfigurationWriter(pw).write(configuration);
}
finally
{
PrintWriterUtil.closePrintWriter(configuration.printConfiguration, pw);
}
}
/**
* Reads the input class files.
*/
private void readInput() throws IOException
{
logger.info("Reading input...");
// Fill the program class pool and the library class pool.
new InputReader(configuration).execute(programClassPool,
libraryClassPool,
resourceFilePool);
}
/**
* Initializes the cross-references between all classes, performs some
* basic checks, and shrinks the library class pool.
*/
private void initialize() throws IOException
{
logger.info("Initializing...");
new Initializer(configuration).execute(programClassPool,
libraryClassPool,
resourceFilePool);
}
/**
* Marks the classes, class members and attributes to be kept or encrypted,
* by setting the appropriate access flags.
*/
private void mark()
{
logger.info("Marking classes and class members to be kept...");
new Marker(configuration).mark(programClassPool,
libraryClassPool);
}
/**
* Strips the Kotlin metadata annotation where possible.
*/
private void stripKotlinMetadataAnnotations()
{
new KotlinAnnotationStripper().execute(configuration,
programClassPool,
libraryClassPool);
}
/**
* Replaces primitive array initialization code by primitive array constants.
*/
private void introducePrimitiveArrayConstants()
{
programClassPool.classesAccept(new ArrayInitializationReplacer());
}
/**
* Expands primitive array constants back to traditional primitive array
* initialization code.
*/
private void expandPrimitiveArrayConstants()
{
programClassPool.classesAccept(new PrimitiveArrayConstantReplacer());
}
/**
* Backports java language features to the specified target version.
*/
private void backport()
{
new Backporter(configuration).execute(programClassPool,
libraryClassPool,
extraDataEntryNameMap);
}
/**
* Adds configuration logging code, providing suggestions on improving
* the ProGuard configuration.
*/
private void addConfigurationLogging() throws IOException
{
new ConfigurationLoggingAdder().execute(programClassPool,
libraryClassPool,
extraDataEntryNameMap);
}
/**
* Adapts Kotlin Metadata annotations.
*/
private void keepKotlinMetadata()
{
logger.info("Adapting Kotlin metadata...");
WarningPrinter warningPrinter = new WarningLogger(logger, configuration.warn);
ClassCounter counter = new ClassCounter();
programClassPool.classesAccept(
new ReferencedKotlinMetadataVisitor(
new KotlinMetadataWriter(warningPrinter, counter)));
logger.info(" Number of Kotlin classes adapted: {}", counter.getCount());
}
/**
* Sets that target versions of the program classes.
*/
private void target() throws IOException
{
logger.info("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
{
logger.info("Printing kept classes, fields, and methods...");
PrintWriter pw = PrintWriterUtil.createPrintWriterOut(configuration.printSeeds);
try
{
new SeedPrinter(pw).write(configuration,
programClassPool,
libraryClassPool);
}
finally
{
PrintWriterUtil.closePrintWriter(configuration.printSeeds, pw);
}
}
/**
* Performs the shrinking step.
*/
private void shrink() throws IOException
{
logger.info("Shrinking...");
// We'll print out some explanation, if requested.
if (configuration.whyAreYouKeeping != null)
{
logger.info("Explaining why classes and class members are being kept...");
}
// We'll print out the usage, if requested.
if (configuration.printUsage != null)
{
logger.info("Printing usage to [{}]...", PrintWriterUtil.fileName(configuration.printUsage));
}
// Perform the actual shrinking.
programClassPool =
new Shrinker(configuration).execute(programClassPool,
libraryClassPool,
resourceFilePool);
}
/**
* Performs the subroutine inlining step.
*/
private void inlineSubroutines()
{
logger.info("Inlining subroutines...");
// Perform the actual inlining.
new SubroutineInliner(configuration).execute(programClassPool);
}
/**
* Optimizes usages of the Gson library.
*/
private void optimizeGson() throws IOException
{
// Do we have Gson code?
// Is Gson optimization enabled?
if (programClassPool.getClass("com/google/gson/Gson") != null &&
(configuration.optimizations == null ||
new ListParser(new NameParser()).parse(configuration.optimizations)
.matches(Optimizer.LIBRARY_GSON)))
{
logger.info("Optimizing usages of Gson library...");
// Perform the Gson optimization.
new GsonOptimizer().execute(programClassPool,
libraryClassPool,
extraDataEntryNameMap,
configuration);
}
}
/**
* Performs the optimization step.
*/
private boolean optimize(int currentPass,
int maxPasses) throws IOException
{
logger.info("Optimizing (pass {}/{})...", currentPass, maxPasses);
// Perform the actual optimization.
return new Optimizer(configuration).execute(programClassPool,
libraryClassPool,
extraDataEntryNameMap);
}
/**
* Performs the obfuscation step.
*/
private void obfuscate() throws IOException
{
logger.info("Obfuscating...");
// Perform the actual obfuscation.
new Obfuscator(configuration).execute(programClassPool,
libraryClassPool,
resourceFilePool);
}
/**
* 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(VersionConstants.CLASS_VERSION_1_6,
new AllMethodVisitor(
new AllAttributeVisitor(
new NamedAttributeDeleter(Attribute.STACK_MAP_TABLE)))));
}
/**
* Performs the preverification step.
*/
private void preverify()
{
logger.info("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
{
logger.info("Writing output...");
// Write out the program class pool.
new OutputWriter(configuration).execute(programClassPool,
initialStateInfo,
resourceFilePool,
extraDataEntryNameMap);
}
/**
* Prints out the contents of the program classes.
*/
private void dump() throws IOException
{
logger.info("Printing classes to [{}]...", PrintWriterUtil.fileName(configuration.dump));
PrintWriter pw = PrintWriterUtil.createPrintWriterOut(configuration.dump);
try
{
programClassPool.classesAccept(new ClassPrinter(pw));
}
finally
{
PrintWriterUtil.closePrintWriter(configuration.dump, pw);
}
}
/**
* Returns the implementation version from the manifest.
*/
public static String getVersion()
{
Package pack = ProGuard.class.getPackage();
if (pack != null)
{
String version = pack.getImplementationVersion();
if (version != null)
{
return version;
}
}
return "undefined";
}
/**
* The main method for ProGuard.
*/
public static void main(String[] args)
{
if (args.length == 0)
{
logger.warn(VERSION);
logger.warn("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)
{
logger.error("Unexpected error", ex);
System.exit(1);
}
System.exit(0);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy