Zelix KlassMaster - Documentation

ZKM Script Exclusions Tutorial

Introduction

The ZKM Script language contains a number of statements that rely on the concept of package, class, field or method exclusion. These statements include All these statements operate by specifying certain objects (ie. packages, classes, fields or methods) that are to be excluded (or unexcluded) from some aspect of the operation of the ZKM Script trim or obfuscate statement. All of the statements listed above use the same syntax for specifying objects for exclusion (or unexclusion). This tutorial focuses on the more complicated aspects of that syntax and gives some examples based on typical scenarios. The documentation specific to each statement does a good job of explaining basic exclusions and this tutorial assumes that you have already looked at these basic explanations and examples. Similarly, if you need a explanation of the effect of any of the listed statements you should see the documentation for that statement.

This tutorial consists of ten parts.

Basic exclusions

Generally speaking, an exclusion parameter specifies either a package, a class, a field or a method.

Package exclusion parameters

The simplest package exclusion parameter simply specifies a package name. For example, the parameter pack1.pack2. explicitly specifies one particular package. Of course, basic package exclusions become more powerful when the "*" wildcard is used.
Parameter Matches
pack1.pack2. just the one package pack1.pack2
*. all packages
pack1*. all packages with names starting with "pack1"
pack1.*. all sub-packages of package pack1
pack1.. pack1 and all sub-packages of package pack1
Note that in ZKM Script the final "." character is part of the package name. The importance of this point will become clearer when we look at containing object exclusions and complex name specifications.

Class exclusion parameters

Informally (with mandatory components in bold), the syntax of a class exclusion parameter is:

<annotations> <classModifiers> "<archiveQualifier>"!<packageQualifiers>.<className> <extendsClause> <implementsClause>

Useful points to note are:
  • the optional <packageQualifiers>. component has the same syntax as a package exclusion parameter.
  • class modifiers can be negated by using the "!" character.
  • classes specified in the <extendsClause> and <implementsClause> components must be fully qualified but they can contain the "*" wildcard.
Parameter Matches
Class1 just the one class Class1 that is in the default package
pack1.Class1 just the one class Class1 that is in the package pack1
* all classes that are in the default package
*.* all classes that are in any package other than the default package
pack1..* all classes that are in the package pack1 or in any sub-package of pack1
"MyJar*.jar"!*.* all classes contained in a JAR file with a name matching "MyJar*.jar"
@pack2.MyAnnotation0 *.* all classes annotated by a class matching pack2.MyAnnotation0 that are in any package other than the default package
public !final *.* extends java.util.Observable all public, non-final classes that are in any package other than the default package and extend (directly or indirectly) java.util.Observable
p*.C* implements java.io.Serializable, java.util.Observer all classes with names starting with "C" that are in any package starting with "p" and that implement (directly or indirectly) both java.io.Serializable and java.util.Observer

Field exclusion parameters

Informally (with mandatory components in bold), the syntax of a field exclusion parameter is:

<annotations> <classModifiers> <packageQualifiers>.<className> <extendsClause> <implementsClause>
   <annotations> <fieldModifiers> <fieldType> <fieldName>

Useful points to note are:
  • the <annotations> <classModifiers> <packageQualifiers>.<className> <extendsClause> <implementsClause> component has the same syntax as a class exclusion parameter. It specifies the containing class.
  • the field modifiers can be negated by using the "!" character.
  • if the <fieldType> is non-primitive then the class name must be fully qualified.
Parameter Matches
Class1 field1 just the one field field1 in the one class Class1 that is in the default package
pack1.Class1 int f* all the int fields starting with "f" in the one class pack1.Class1
*.* implements java.io.Serializable !static !transient * all the non-static and non-transient fields in classes that implement java.io.Serializable
public *.* private java.lang.String[] * all the private String[] fields in public classes that are in any package other than the default package
public *.* @pack2.MyAnnotation0 private int * all the private int fields annotated with a class matching pack2.MyAnnotation0 in public classes that are in any package other than the default package

Method exclusion parameters

Informally (with mandatory components in bold), the syntax of a method exclusion parameter is:

<annotations> <classModifiers> <packageQualifiers>.<className> <extendsClause> <implementsClause>
   <annotations> <methodModifiers> <methodName>(<argumentTypes>) <throwsClause>

Useful points to note are:
  • the <annotations> <classModifiers> <packageQualifiers>.<className> <extendsClause> <implementsClause> component has the same syntax as a class exclusion parameter. It specifies the containing class.
  • the method modifiers can be negated by using the "!" character.
  • the special <methodName> <init> means "method constructor".
  • the special <methodName> <clinit> means "class initializer method".
  • if any of the <argumentTypes> is non-primitive then the class name must be fully qualified.
  • the absence of a <argumentTypes> component means "no arguments".
  • a simple "*" means "no or any arguments".
  • a "?" (i.e. a method parameter placeholder) can be used in a comma delimited <argumentTypes> list to match any single parameter type.
Parameter Matches
Class1 method1() just the one method method1() that takes no parameters in the one class Class1 that is in the default package
pack1.Class1 m*(*) all the methods starting with "m" with any or no parameters in the one class pack1.Class1
*.* @pack2.MyAnnotation0 public *(*) all public methods with any or no parameters that are annotated with the class pack2.MyAnnotation0 and appear in any class and package except the default package
*.* *(int, ?, java.lang.String) all methods which take an int, any second parameter and a String and which appear in any class and package except the default package
*.* *(?, ?) all methods which take exactly two parameters and which appear in any class and package except the default package
*.* *(int, @pack2.MyAnnotation0 *) all methods which take an int parameter followed by a parameter of any type which is annotated with the class pack2.MyAnnotation0 and which appear in any class and package except the default package
package *.* !public final *(*) all the non-public and final methods in classes with package scope
*.* public static main(java.lang.String[]) all the public, static main(String[]) methods in classes that are in any package other than the default package
*.* *(int, java.lang.String) throws java.lang.Exception all the methods that take an int and a String as a argument and throw a java.lang.Exception or one of its subclasses

Containing objects exclusions

As mentioned above, an exclusion parameter generally specifies either a package, a class, a field or a method. However, with the exclude statement there is a way to specify a class name exclusion parameter that also excludes the name of the package that contains the class. Similarly, you can specify field and method exclusion parameters that exclude
  • the name of the class that contains the field or method and/or
  • the name of the package that contains the field or method
To do this you append a "^" character to the end of the package or class name.
Parameter Matches
pack1.^Class1 the one class Class1 in the package pack1. Also "matches" the containing package pack1. Note that the "^" follows the package name and that the package name includes the final "." character.
Class1^ method1() the one method method1() that takes no parameters in the one class Class1 that is in the default package. Also "matches" the containing class Class1
*.^*^ *(*) all methods in any class that is not in the default package. Also "matches" the packages and classes that contain a method.
pack1..^*^ *(*) all methods in any class that is in pack1 or any of its sub-packages. Also "matches" the packages and classes that contain a method.

Contained objects exclusions

As mentioned above, an exclusion parameter generally specifies either a package, a class, a field or a method. However, with the exclude statement there is a way to specify a class name exclusion parameter that also excludes the names of all the fields and methods contained in the class. To do this you follow the class name specification with a "+" character. You can either append the character directly or separate it with a space.
Parameter Matches
pack1.Class1 + the one class Class1 in the package pack1. Also "matches" all the fields and methods of pack1.Class1
pack1.* + implements pack2.Class2 all classes in the package pack1 that implement pack2.Class2. Also "matches" all the fields and methods of any matched classes.

Complex name specifications

Wherever a package, class, field or method name can appear in an exclusion statement, you can use a complex name specification. A complex name specification allows you to use && (ie. "and") and || (ie. "or") conditions and negation to more succinctly express complex specifications. Useful points to note are:
  • the highest level of a complex specification is always enclosed in brackets (ie. "(...)").
  • the negation character "!" must always immediately precede the opening "(" of a bracket pair.
  • a complex name specification is either a package, class, field or method name specification. It cannot be a mixture of these name types.
  • "&&" takes precedence over "||". So (x || y && z) is equivalent to (x || (y && z)).
Parameter Matches
(pack1. || pack2.)Class1 any class Class1 in either of the packages pack1 or pack2. Note that the package names end with the "." character.
(pack1.*. && !(pack1.pack1. || pack1.pack3.))* any class in a sub-package of pack1 but not pack1.pack1 or pack1.pack3.
pack1.!(Class1) * any field in a class in package pack1 except those in a class named Class1.
(pack1. || pack1.*.)(C* || D*) (m* || n*)(*) any method starting with either "m" or "n" that is in a class starting with either "C" or "D" that is in the package pack1 or one of its sub-packages. The final (*) specifies that the any method argument types will match.
(pack1. || pack2.)^Class1 any class Class1 in either of the packages pack1 or pack2. Also matches the containing package because of the "^" character.

Exclusions based on annotations

You can exclude packages, classes, fields and methods based upon the presence or absence of annotations at the class, field, method or method parameter level. You can use the "*" wildcard and the && (i.e. "and"), || (i.e. "or") and ! (i.e. negation) syntax to form complex annotation specifications.

As with complex name specifications, useful points to note are:
  • the highest level of a complex annotation specification is always enclosed in brackets (ie. "(...)").
  • the negation character "!" must always immediately precede the opening "(" of a bracket pair.
  • "&&" takes precedence over "||". So (@x || @y && @z) is equivalent to (@x || (@y && @z)).
Note that the annotation's package qualifiers must be either explicitly specified or implicitly specified via a wildcard. Also, Zelix KlassMaster™ can only match exclusions involving annotations if those annotations are actually in the class file. Annotations that have a RetentionPolicy of SOURCE are discared by the compiler and are not compiled into the class file that is generated. So you can only usefully specify exclusions using annotations if those annotations have a RetentionPolicy of CLASS or RUNTIME.

Parameter Matches
@pack2.MyAnnotation0 *.* all classes annotated with pack2.MyAnnotation0 that are in any package other than the default package.
*.* @pack2.MyAnnotation0 * all fields annotated with pack2.MyAnnotation0 that are in any class in any package other than the default package.
*.* @pack2.MyAnnotation0 *(*) all methods with any or no parameters annotated with pack2.MyAnnotation0 that are in any class in any package other than the default package.
*.* *(@pack2.MyAnnotation0 int) all methods with a single int parameter which is annotated with pack2.MyAnnotation0 that are in any class in any package other than the default package.
(@pack2.MyAnnotation0 && !(@pack2.MyAnnotation1)) *.* all classes annotated with pack2.MyAnnotation0 AND NOT annotated with pack2.MyAnnotation1 that are in any package other than the default package.
!(@*.*) *.* all classes NOT annotated with any class and that are in any package other than the default package.

Exclusion of annotations

If an annotation class name appears totally by itself (without modifiers, field or method specifications, extends or implements clauses etc.) then it is interpreted in a special way. For example, the following exclude statement:

exclude @pack1.Class1;

Is interpreted as:

exclude pack1.Class1 implements java.lang.annotation.Annotation;

However, the main reason for the syntax is that in the trimExclude and trimUnexclude statements the syntax has an addtional special meaning. In the case of the trimExclude it also means that annotations matching the specified class(es) should not be removed where they otherwise would have been if the deleteAnnotationAttributes=true setting is specified on the trim statement. In the case of the trimUnexclude the syntax removes from the set of exclusions created by the corresponding trimExclude.

As mentioned above, the annotation class specification must appear by itself but it can contain wildcards.

Exclusions based on references by annotations

In some unusual circumstances, you may need to exclude a class that is not directly annotated but rather is referenced by an annotation on another class. For example, you may have a class pack1.Class0 that is not directly annotated but another class pack0.Class1 is annotated in the following way.

@pack2.MyAnnotation0(foo=pack1.Class0.class)
class pack1.Class1 {
...
}

So, the annotation of pack1.Class1 refers to pack1.Class0. You can specify the exclusion of the referenced class in the following way.

@pack2.MyAnnotation0(foo=*.*)

The above exclusion parameter matches all classes that
  • have a name matching "*"
  • are in a package matching "*"
  • are referenced by the "foo" attribute of the annotation class pack2.MyAnnotation0 which annotates an opened class or one of its fields, methods or method parameters
Points to note are:
  • This kind of an exclusion is a class level exclusion like any other.
  • It is the referenced class which is being excluded and not the class that actually has the referencing annotation.
  • The default annotation attribute name is "value". So, if a class was annotated as @pack2.MyAnnotation0(pack1.Class0.class) then the exclusion would be @pack2.MyAnnotation0(value=*.*)
Parameter Matches
@pack2.MyAnnotation0(foo=*.*) all classes referenced by the "foo" attribute of the annotation class pack2.MyAnnotation0 in some annotation appearing in an opened class.
@pack2.*(value=*.*^) extends pack2.Class2 public *(*) all public methods in any classes referenced by the default "value" attribute of any annotation class matching pack2.* and which extends pack2.Class2. Also exclude the containing classes.

Exclusions based on contained members

You can exclude classes based upon the presence or absence of specified fields or method within the classes. You can use the "*" wildcard and the && (i.e. "and"), || (i.e. "or") and ! (i.e. negation) syntax to form complex member specifications. Note that the member specifications are enclosed in braces (i.e curly brackets) that follow the "containing".

If you use complex member specifications within a containing clause, useful points to note are:
  • the highest level of a complex member specification is always enclosed in brackets (ie. "(...)").
  • the negation character "!" must always immediately precede the opening "(" of a bracket pair.
Parameter Matches
*.* containing{native *(*)} all classes containing a native method.
*.* containing{java.land.String[] m*} all classes containing a String array field with a name starting with "m".
*.* containing{(x*(*) && y*)} all classes containing a method starting with "x" and a field starting with "y".
*.* containing{!(@*.* *(*))} all classes that do not contain an annotated method.

Method signature class exclusions (+signatureClasses)

If a ZKM Script exclude statement method parameter ends with the keyword +signatureClasses then, for each matching method, the fully qualified class names of the method's return and parameter types are also excluded from obfuscation. Types which are primitive are ignored. So, given an exclude statement

exclude *.* method1(*) +signatureClasses;

then if a method with the signature pack1.Class1 method1(pack2.Class2) matches the exclusion parameter then, in addition to excluding the name of the method, the +signatureClasses flag has the effect of excluding the fully qualified class names:
  • pack1.Class1 (because it is the return type of a matching method) and
  • pack2.Class2 (because it is a parameter type of a matching method)

Method prefix (linked) exclusions

Some features of the Java language create dependencies between method and field names. For example, Java Beans have getter and setter methods which have a correspondence between the method name and the name of the field that is accessed. So you will have the method getTime() returning the value of the field time. At the time of obfuscation, you may not want the prefix get to be changed or the correspondence with the field name to be broken.

The exclude statement provides the special method prefix exclusion parameter to deal with these situations. Using the example of a class that contains a method named getTime() and a field named time then we specify a method prefix exclude parameter get<link>(). The prefix is get. If the field time is renamed to x then the method getTime() will be renamed to getX().

Informally (with mandatory components in bold), the syntax is:
<classAnnotations> <classModifiers> <packageQualifiers>.<className> <extendsClause> <implementsClause>
    <methodAnnotations> <methodModifiers> <prefix><methodName>(<argumentTypes>) <throwsClause>
Useful points to note are:
  • the <annotations> <classModifiers> <packageQualifiers>.<className> <extendsClause> <implementsClause> component has the same syntax as a class exclusion parameter. It specifies the containing class.
  • the method modifiers can be negated by using the "!" character.
  • if any of the <argumentTypes> is non-primitive then the class name must be fully qualified.
  • the absence of a <argumentTypes> component means "no arguments".
  • a "*" <argumentTypes> component means "no or any arguments".
  • a "*" can also be used in a comma delimited <argumentTypes> list to match any single parameter type.

Parameter Matches
*.* get<link>() Exclude the "get" prefix in all method names starting with "get" that take no parameters that are in any class in any package other than the default package.
*.* set<link>(*) Exclude the "set" prefix in all method names starting with "set" that take any or no parameters that are in any class in any package other than the default package.
*.* set<link>(java.lang.String) Exclude the "set" prefix in all method names starting with "set" that take a single String parameter that are in any class in any package other than the default package.

Class suffix (linked) exclusions

Some features of the Java language create dependencies between class names. For example, the RMI "stub" class of a class Class1 must be named Class1_Stub. For RMI/IIOP the "stub" class must be named _Class1_Stub. So we have a situation where for some classes a suffix (eg. "_Stub") and perhaps a prefix (eg. "_") must not be changed during obfuscation. Also, we have a situation where the core part of a class name (ie. non-prefix and non-suffix) must always match the name of some other class.

The exclude statement provides the special class suffix exclusion parameter to deal with these situations. Informally (with mandatory components in bold), the syntax is:
<packageQualifiers>.<prefix><link><suffix> <extendsClause> <implementsClause> <searchClause>
Useful points to note are:
  • the optional <packageQualifiers> component has the same syntax as a package exclusion parameter.
  • classes specified in the <extendsClause> and <implementsClause> components must be fully qualified but they can contain the "*" wildcard.
  • the packages specified in the <searchClause> component can contain wildcards.

Let's take the following classes as our example set.

 pack1.Class1
 pack1.Class1_X
 pack1.Class2
 pack1._Class2_Y
 pack1.pack2.Class3
 pack1.Class3_Z

Parameter Matches
*.<link>_X pack1.Class1_X is matched because it has the "_X" suffix and it isn't in the default package. The non-suffix part of the name is Class1. Zelix KlassMaster™ looks in the same package for a class named Class1 and finds pack1.Class1. If the name of pack1.Class1 is obfuscated to be pack1.c then the name of pack1.Class1_X will be obfuscated to be pack1.c_X
p*._<link>_Y pack1._Class2_Y is matched because it has the "_" prefix and the "_Y" suffix and it is in a package starting with "p". The non-prefix, non-suffix part of the name is Class2. Zelix KlassMaster™ looks in the same package for a class named Class2 and finds pack1.Class2. If the name of pack1.Class2 is obfuscated to be pack1.c then the name of pack1._Class2_Y will be obfuscated to be pack1._c_Y
pack1.<link>_Z search pack1.pack2 pack1.Class3_Z is matched because it has the "_Z" suffix and it is in the pack1 package. The non-suffix part of the name is Class3. Zelix KlassMaster™ looks in the same package for a class named Class3 but cannot find one. It then looks at the package specified in the search clause and finds pack1.pack2.Class3. If the name of pack1.pack2.Class3 is obfuscated to be pack1.pack2.c then the name of pack1.Class3_Z will be obfuscated to be pack1.c_Z

Exclusion Troubleshooting

You may have to do a little experimentation with your exclusions. Some scenarios that can cause problems are
  1. Java Reflection API accesses to classes, fields and methods that Zelix KlassMaster™ was unable to fully resolve. (If Zelix KlassMaster™ was unable to fully resolve a Reflection API call then it will report the fact in the Class Open Warnings dialog or the Zelix KlassMaster™ log.}
  2. Specialized APIs (not catered for by the existing default exclusions) that directly access your classes, fields or methods.
In such cases the best solution is to add explicit exclusions.

If you find yourself needing to debug your exclusions when running a ZKM Script then you may find it helpful to run Zelix KlassMaster™ using the "-v" (ie. verbose) parameter. This will result in a great deal of extra information being written to the log. This extra information will include
  • the effective exclude parameters (including the default exclusions)
  • which classes, fields or methods were directly matched by a particular exclusion parameter.
 
Documentation Table of Contents
Zelix KlassMaster - Java Obfuscator