ProGuard User Manual

Contents

  1. Introduction
  2. Usage
  3. Limitations
  4. Examples
  5. Troubleshooting
 

Introduction

ProGuard is a Java class file shrinker and obfuscator. It detects and removes unused classes, fields, methods, and attributes, and it renames the remaining classes, fields, and methods using short meaningless names. The resulting jars are more compact and more difficult to reverse-engineer.

ProGuard can also be used to list unused fields and methods in an application, and to print out the internal structure of class files.

ProGuard typically reads the input jars, shrinks their class files, obfuscates the shrunk class files, and writes them to an output jar. In the shrinking phase, one or more seed methods or classes are required. These seeds are typically main methods and applets. The shrinker can then recursively determine which classes and class members are used, and discard the rest. In the obfuscation phase, all classes and class members get new names, except for the seeds, so they can still be called by their original names.

ProGuard automatically handles Class.forName("SomeClass") and SomeClass.class constructs. The referenced classes are preserved in the shrinking phase, and the string arguments are properly replaced in the obfuscation phase. With variable string arguments, it's generally not possible to determine their possible values (they might be read from a configuration file, for instance). However, ProGuard will note constructs like "(SomeClass)Class.forName(variable).newInstance()". These might be an indication that the class or interface SomeClass and/or its implementations may need to be preserved. The user can adapt his configuration accordingly.

 

Usage

To run ProGuard, just type:

java -jar proguard.jar <options> ...

Or, assuming the ProGuard jar is in your class path:

java proguard.ProGuard <options> ...

Options can also be put in one or more configuration files. Typically, you'll put most options in a configuration file, and just call:

java proguard.ProGuard @myconfig.pro

where myconfig.pro contains the actual options.

You can simply combine command line options and options from configuration files, e.g.:

java proguard.ProGuard @myconfig.pro -printusage -printmapping

In a configuration file, a # sign and all remaining characters on that line are ignored, allowing you to add comments.

Extra whitespace between words and delimiters is ignored. To specify file names with spaces or special characters, or more exotically, to specify empty class names, words can be quoted with single or double quotes. Note that the quotes may need to be escaped when used on the command line of Unix systems, to avoid gobbling by the shell.

Options can be grouped arbitrarily in arguments on the command line and in lines in configuration files. This means that you can quote any arbitrary section of command line options, to avoid shell expansion of special characters, for instance.

The order of the options is irrelevant. They can be abbreviated to their first unique characters.

The following table lists all supported options.

@filename
Short for '-include filename'.
-include filename Recursively reads configuration commands from the given file filename.
-libraryjars jarname[:...] Specifies the library jars of the application to be processed. The class files in these jars will not be included in the output jar. The specified library jars should at least contain the class files that are extended by application class files. Library class files that are only called needn't be present. The jars can be specified with one or more -libraryjars directives. Multiple jars can also be specified with a single directive using the path separator (e.g. ':' on Unix, or ';' on Windows platforms).
-injars jarname[:...] Specifies the program jars of the application to be processed. The class files in these jars will be processed and merged in the output jar. The jars can be specified with one or more -injars directives. Multiple jars can also be specified with a single directive using the path separator (e.g. ':' on Unix, or ';' on Windows platforms).
-outjar jarname Specifies the name of the output jar. Without this directive, no jars will be written. With multiple directives, several identical copies of the output jar will be written.
-keep class_specification Specifies the classes and their class members to be preserved. They will be preserved with their original names. This is typically the required seed for recursively determining which other classes and class members need to be preserved. For example, in order to keep an application, you can specify the main class along with its main method. In order to process a library, you should specify all publicly accessible items.
-keepclassmembers class_specification Specifies any class members to be preserved, if their classes are preserved as well. For example, you may want to keep all serialization fields and methods of classes that implement the Serializable interface.
-keepclasseswithmembers class_specification Specifies the classes and their class members to be preserved, on the condition that all of the specified class members are present. For example, you may want to keep all applications that have a main method, without having to list them explicitly.
-keepnames class_specification Specifies the classes and their class members whose names are to be preserved, if they aren't removed in the shrinking phase. For example, you may want to keep all class names of classes that implement the Serializable interface, so that the processed code remains compatible with any originally serialized classes. Classes that aren't used at all can still be removed.
-keepclassmembernames class_specification Specifies the class members whose the names are to be preserved, if they aren't removed in the shrinking phase. For example, you may want to keep all method names of native methods, so that the processed code can still link with the native library code. Native methods that aren't used at all can still be removed.
-keepattributes attribute_name[,...] Specifies any optional attributes to be preserved. A * wildcard can be used to preserve all attributes. Typical optional attributes are LineNumberTable, LocalVariableTable, SourceFile, Deprecated, and Synthetic. The InnerClasses attribute name can be specified as well, referring to the source name part of this attribute.
-printseeds Specifies to exhaustively list classes and class members matched by the various -keep commands. This list can be useful to verify if the intended class members are really found, especially when wildcarded. For example, you may want to list all the applications or all the applets that you are keeping.
-printusage Specifies to list dead code of the input class files. For example, you can list the unused code of an application. Only applicable when shrinking.
-printmapping Specifies to print the mapping from old names to new names for classes and class members that have been renamed. Only applicable when obfuscating.
-verbose Specifies to write out some more information during processing. If the program terminates with an exception, this option will print out the entire stack trace, instead of just the exception message.
-dump Specifies to write out the internal structure of the class files, after any processing. For example, you may want to write out the contents of a given jar file, without shrinking or obfuscating it first.
-ignorewarnings Specifies to print any warnings about unresolved references to superclasses, interfaces, or class members, but to continue processing in any case. If the classes or class members are indeed required for processing, the output jar will not function properly. Only use this option if you know what you're doing!
-dontwarn Specifies not to warn about unresolved references at all. Again, if the unresolved classes or class members are indeed required for processing, the output jar will not function properly. Only use this option if you know what you're doing!
-dontnote Specifies not to print notes about class casts of variable dynamically created objects. These notes provide hints about classes that may have to be kept.
-dontshrink Specifies not to shrink the input class files. By default, shrinking is applied: all classes and class members are removed, except for the ones listed by the various -keep commands, and the ones they depend on, recursively.
-dontobfuscate Specifies not to obfuscate the input class files. By default, obfuscation is applied: classes and class members receive new short random names, except for the ones listed by the various -keep commands. Internal attributes that are useful for debugging, such as source files names, variable names, and line numbers are removed.
-overloadaggressively Specifies to apply aggressive overloading while obfuscating. Multiple fields and methods can then get the same names, as long as their arguments and return types are different (not just their arguments, in the case of methods). This option can make the output jar even smaller (and less comprehensible). Only applicable when obfuscating.

Note that the resulting class files fall within the Java bytecode specification (cfr. The Java Virtual Machine Specification, Second Edition, first paragraphs of Section 4.5 and Section 4.6), even though this kind of overloading is not allowed in the Java language (cfr. The Java Language Specification, Second Edition, Section 8.3 and Section 8.4.7). Still, some tools have problems with it. Most notably, Sun's JDK 1.2.2 javac compiler produces an exception when compiling with such a library (cfr. Bug #4216736). You therefore probably shouldn't use this option for processing libraries.

-defaultpackage package_name Specifies to repackage all class files that are renamed into the single given package. This option can make the output jar even smaller (and less comprehensible). Only applicable when obfuscating.

Note that this kind of repackaging may cause access permissions to become inconsistent, e.g. a class with default access may move to a different package from some other class that is using it. Even though unusual, this is formally allowed by Java's binary compatibility specifications (cfr. The Java Language Specification, Second Edition, Section 13.4.6).

In the above table, filename and jarname can contain system properties delimited by '<' and '>', e.g. "<java.home>". The system property is automatically replaced by its value. File names and jar names with special characters like spaces and parentheses should be quoted with single or double quotes.

Furthermore, class_specification refers to a template of classes and class members. The corresponding options are only applied to classes and class members with matching templates. It was designed to look very Java-like, with some extensions for wildcards:

[[!]public|final|abstract ...] interface|class * | classname
     [extends|implements classname]
[{
     [[!]public|private|protected|static|volatile|transient ...]
         <fields>
| (fieldtype fieldname);
     [[!]public|private|protected|static|synchronized|native|abstract|strictfp ...]
         <methods> | <init>(
argumenttype,...) | classname(argumenttype,...) | (returntype methodname(argumenttype,...));
     [[!]public|private|protected|static ... ] *;
    ...
}]

The above specification may look cluttered if your browser window isn't wide enough. Square brackets "[]" mean that something is optional. Ellipsis dots "..." mean that any number of items may be specified. A vertical bar "|" indicates a choice between alternatives. Non-bold parentheses "()" just group parts of the specification that belong together.

Every classname must be specified in full, e.g. java.lang.String. The wildcard class name * refers to any class.

The extends and implements specifications are typically used to restrict wildcard classes. They are currently equivalent, specifying that only classes extending or implementing the given class qualify. Note that the given class itself is not included in this set. If required, it should be specified in a separate option.

Fields and methods are specified much like in Java, except that method argument list don't contain argument names. The wildcard field name <fields> refers to any field. The wildcard method name <methods> refers to any method. The wildcard class member name * refers to any field or method. Note that all of the above wildcards don't have return types.

Constructors can be specified using their short class name (without package), using their full class name, or using the <init> wildcard. As in the Java language, the constructor specification has an argument list, but no return type.

The class access modifiers and class member access modifiers are typically used to restrict wildcard classes and class members. They specify that the corresponding access flags have to be set for the member to match. A preceding ! specifies that the corresponding access flag should be unset.

Combining multiple flags is allowed (e.g. public static). It means that both access flags have to be set (e.g. public and static), except when they are conflicting, in which case at least one of them has to be set (e.g. at least public or protected).

 

Limitations

 

Examples

Some typical useful configurations:

  1. A typical application
  2. A typical applet
  3. All possible applications in the input jars
  4. All possible applets in the input jars
  5. An application with serializable classes
  6. An application with native methods
  7. Processing a library
  8. Finding dead code
  9. Printing out the internal structure of class files
 

A typical application

Shrink and obfuscate the ProGuard application itself:
-libraryjars <java.home>/lib/rt.jar
-injars      proguard.jar
-outjar      proguard_out.jar
-overloadaggressively
-defaultpackage pro

-keep public class proguard.ProGuard {
    public static void main(java.lang.String[]);
}

Note the use of the <java.home> system property; it is replaced automatically.

Also note that the type names are fully specified: proguard.ProGuard and java.lang.String[].

The access modifiers public and static are not really required in this case, since we know a priori that the specified class and method have the proper access flags. It just looks more familiar this way.

We're using the -overloadaggressively and -defaultpackage options to shave off some extra bytes, but we could leave them out as well. The -defaultpackage directive moves all classes to the given package. Only proguard.ProGuard keeps its original name.  

A typical applet

Shrink and obfuscate the applet mypackage.MyApplet:
-libraryjars <java.home>/lib/rt.jar
-injars      in.jar
-outjar      out.jar

-keep public class mypackage.MyApplet

The typical applet methods will be preserved automatically, since mypackage.MyApplet is an extension of the Applet class in the library rt.jar.  

All possible applications in the input jars

Shrink and obfuscate all public applications in in.jar:
-libraryjars <java.home>/lib/rt.jar
-injars      in.jar
-outjar      out.jar
-printseeds

-keepclasseswithmembers public class * {
    public static void main(java.lang.String[]);
}

Note the use of -keepclasseswithmembers. We don't want to preserve all classes, just all classes that have main methods, and those methods.

The -printseeds option prints out which classes exactly will be preserved, so we know for sure we're getting what we want.  

All possible applets in the input jars

Shrink and obfuscate all public applets in in.jar:
-libraryjars <java.home>/lib/rt.jar
-injars      in.jar
-outjar      out.jar
-printseeds

-keep public class * extends java.applet.Applet

We're simply keeping all classes that extend the Applet class.

Again, the -printseeds option prints out which applets exactly will be preserved.  

An application with serializable classes

Shrink and obfuscate the application mypackage.MyApplication, properly preserving all serializable classes:
-libraryjars <java.home>/lib/rt.jar
-injars      in.jar
-outjar      out.jar

-keep public class mypackage.MyApplication {
    public static void main(java.lang.String[]);
}

-keepnames class * implements java.io.Serializable

-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    Object writeReplace();
    Object readResolve();
}

The -keepnames option makes sure that any serializable classes that are preserved also keep their names. This way, the processed code remains backward compatible with the original code.

The -keepclassmembers option makes sure that their serialization fields and methods are kept. By using this directive instead of the basic -keep directive, we're not forcing to preserve all serializable classes.  

An application with native methods

Shrink and obfuscate the application mypackage.MyApplication, preserving all native method names in all preserved classes:
-libraryjars <java.home>/lib/rt.jar
-injars      in.jar
-outjar      out.jar

-keep public class mypackage.MyApplication {
    public static void main(java.lang.String[]);
}

-keepclassmembernames class * {
    native <methods>;
}

Note the use of -keepclassmembernames. We don't want to preserve all classes or all native methods; we just want to keep all native method names from being obfuscated.  

Processing a library

Shrink and obfuscate an entire library, keeping all public and protected classes and class members, native method names, and serialization code:
-libraryjars <java.home>/lib/rt.jar
-injars      in.jar
-outjar      out.jar

-keep public class * {
    public protected *;
}

-keepclassmembernames class * {
    native <methods>;
    static Class class$(java.lang.String);
}

-keepnames class * implements java.io.Serializable

-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    Object writeReplace();
    Object readResolve();
}

This configuration should preserve everything we'll ever want to access in the library. Only if there are any other non-public classes or methods that are invoked dynamically, they should be specified using additional -keep directives.

Note the use of -keepclassmembernames for the static class$ method. This method is inserted by the javac compiler to implement the .class construct. ProGuard will automatically detect it and deal with it; no problem at this point. However, ProGuard may be used again to shrink an application that uses this obfuscated library. Without having preserved the method's name, the automatic detection would fail this second time around.  

Finding dead code

List unused fields and methods in the application mypackage.MyApplication:
-libraryjars <java.home>/lib/rt.jar
-injars      in.jar
-dontobfuscate
-printusage

-keep public class mypackage.MyApplication {
    public static void main(java.lang.String[]);
}

We're not specifying an output jar, just printing out some results.

We're saving a little bit of time by not passing thru the obfuscation phase.  

Printing out the internal structure of class files

Print out the internal structure of all class files in the input jar:
-injars in.jar
-dontwarn
-dontnote
-dontshrink
-dontobfuscate
-dump

Note how we don't need to specify the Java run-time jar, because we're not processing the input jar at all. In this case, we can safely ignore any warnings about unresolved references, so we use the -dontwarn option. We're also not interested in any advice about dynamic invocations, so we use the -dontnote option.

 

Troubleshooting

ProGuard may print out some notes:

ProGuard may terminate with warnings in a couple of cases:

If ProGuard runs fine, but your processed application doesn't work, there might be several reasons:

Should ProGuard crash while processing your application:


Copyright © 2002 Eric Lafortune.