Contents |
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 ...]
| (fieldtype fieldname);
<fields>
[[!]public|private|protected|static|synchronized|native|abstract|strictfp ...]
argumenttype,...) |
classname(argumenttype,...) |
(returntype methodname(argumenttype,...));
<methods> |
<init>(
[[!]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 |
ExternalClass$InternalClass
, for instance (cfr. The Java Language Specification, Second Edition, Section 13.1). This should not present a problem in practice, since
the rule is mainly intended for transformations at the source code level.
Internal-external class relationships are still represented correctly
inside the binary class files. Decompilers or others tools that rely on
the naming rule may have problems processing obfuscated jars.
Class.forName
and
.class
constructs with constant classes. There is one caveat,
however, when processing libraries that are to be used as such after
obfuscation. The javac compiler compiles .class
to a static
method, class$(String)
. This method calls
Class.forName
and returns a Class
object,
catching some exceptions along the way. ProGuard detects occurrences of
the method. However, if you're using an obfuscated library, the
class$
method may have received a different name, thus
escaping ProGuard's attention. The simple solution is to preserve the name
of this method when obfuscating libraries, as illustrated in this example.
-keep class * implements MyKeepInterface
", and
MyKeepInterface
is not used in your code, the specified
classes are kept, but they are obfuscated. Technically, the interface is
removed in the shrinking phase, making the directive void in the
obfuscation phase. This behavior may be fixed in the future. For now, you
can get around it by explicitly keeping the interface as well:
"-keep class MyKeepInterface
". In any case, creating a proper
configuration file seems a cleaner solution than using such an obfuscation
marker interface.
Examples |
Some typical useful configurations:
-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.
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
.
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.
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.
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.
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.
-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.
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.
-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:
(MyClass)Class.forName(variable).newInstance()
", ProGuard
will list them. Depending on your application, you may need to keep the
mentioned classes with something like "-keep class MyClass
",
or their implementations with something like "-keep class *
implements MyClass
". You can switch off these notes by specifying
the -dontnote
option.
ProGuard may terminate with warnings in a couple of cases:
-ignorewarnings
option, or even the -dontwarn
option.
If ProGuard runs fine, but your processed application doesn't work, there might be several reasons:
ClassNotFoundException
, your code is
probably calling Class.forName
, trying to create the missing
class. ProGuard can only detect constant name arguments, like
Class.forName("mypackage.MyClass")
. For variable name
arguments like Class.forName(someClass)
, you have to keep all
possible classes using the -keep
option, e.g. "-keep
class mypackage.MyClass
" or "-keep class * implements
mypackage.MyInterface
".
NoSuchMethodError
, your code is probably
calling something like myClass.getMethod
, trying to find some
method. Since ProGuard isn't detecting this (yet), you have to keep the
missing method in using the -keep
option, e.g. "-keep
class mypackage.MyClass { void myMethod(); }
".
NullPointerException
or if you don't see
any icons, your processed code may be missing some resource files. Please
remember that ProGuard ignores all non-.class
files in the
input jars. You should add image files, sound files, text files, etc.
manually to the output jars or to some support jars.
AbstractMethodError
, perhaps there's a
bug in ProGuard (gasp!). If so, please contact me, preferably with the
simplest example on which you can find ProGuard to fail.
Should ProGuard crash while processing your application:
-verbose
option).