In this article I’m going to take you on a tour of the process I use to build a large-scale RCP application. Obfuscating an RCP application can seem like a big challenge, but it’s really not so bad.
Selecting an obfuscator
The first challenge is selecting the right tool for the job. The most important feature of an RCP application obfuscator (assuming it does a good job at obfuscating!) is that it can obfuscate multiple jars at the same time. Some tools expect you to obfuscate one jar at a time, and unless your RCP application contains only one jar, these tools will not work.
In my case, I chose a tool called Zelix Klassmaster. This obfuscator handles multiple jars, and also integrates nicely into a build process managed by Ant scripts. The process I describe is based on Klassmaster. I’ve also heard that the open-source obfuscator ProGuard can handle multiple jars at the same time, but I haven’t tried this tool myself. If anyone manages to get a similar process working with ProGuard, I’d appreciate it if you’d let me know.
The big picture
The big picture for an obfuscation process includes all of the jars that are distributed with your application. These jars can then be divided into two categories: those that will be obfuscated and those that will not.
- Obfuscated – Jars that make up your own proprietary plugins
- Non-Obfuscated – All third-party plugins and libraries, including the RCP plugins. This category may also include your own plugins that cannot be obfuscated for one reason or another.
Zelix Klassmaster operates on these two sets of jars, loading them all into memory at the same time and then obfuscating only those in the first category.
The build process
An obfuscation process begins after an RCP product has been exported either by the Product Export Wizard or by an automated build process. The exported application is then obfuscated using an Ant script and a related Zelix script. The Ant script controls the process, and it has three functions:
- Create lists of jars and other parameters which are then substituted into the Zelix script.
- Execute the obfuscate Ant task provided by the Zelix obfuscator.
- Replace the original plugin jars with the new obfuscated jars.
In the obfuscate Ant task, Zelix loads all of the plugin jars into memory and then obfuscates those in the list to be obfuscated. These jars are placed into a new folder (in my case a folder called “obfuscated”). These jars are then merged back into the plugin directory of the application.
Of course, if your plugins are not exported as jars, then this process will be a little more involved. Another good argument for migrating to the new packaging structure.
Excluding package names – choosing a naming convention
Exclusions are one of the trickiest part of obfuscating any application, and in particular an RCP application. A good general rule of thumb is that any class referenced in an extension or extension point should be excluded. This doesn’t mean that the code in the class won’t be obfuscated, but that both the package and class names associated with the class need to be excluded. I’ll deal with class names in a bit, but first I’d like to address the issue of package exclusion.
The important thing about package exclusion is that when you exclude a package name that is deep in your package hierarchy, you are required to exclude all other packages above it. This can make it difficult to exclude packages deep in your hierarchy without abandoning package name obfuscation entirely.
The solution I’ve come up with is to make a clear distinction between obfuscated and non-obfuscated packages in the package naming conventions I use. It turns out that this convention fits nicely with another naming convention used in the Eclipse code itself that separates code making up the public API from internal code. Basically, package names that can be obfuscated are always considered to be “internal” packages and will never contain classes referenced by extensions or extension points. Public packages are excluded from package name obfuscation and may contain excluded classes.
To make this convention as useful as possible (to allow as many packages to be obfuscated as possible), this distinction between public and internal needs to be made very high in the package structure. My convention differs from the Eclipse convention in that I make this branch even before the plugin name portion of the package structure. Here is an example of the convention I use.
- Public package – com.marketcontours.plugin-name.subpackage
- Obfuscated package – com.marketcontours.internal.plugin-name.subpackage
Of course, only public packages can be included in the “Exported Packages” list for a plugin. If you specify a package that will be obfuscated, the plugin will not be able resolve the package name at runtime. This is just one more reason to align the concepts of public/internal packages and non-obfuscated/obfuscated packages. And finally, by maximizing the amount of classes in internal packages (which is a good thing to do for many reasons), we can keep the amount of unobfuscated package names to a minimum.
Excluding class names – a new properties file
The second challenge is to exclude the names of classes and interfaces referenced by extensions and extension points. I’ve found that the easiest way to do this is to track the exclusions at the plugin level with a new properties file called obfuscation.properties
. Whenever I need to add a new exclusion, I put it into this property file, and then at build time these exclusions are combined by Zelix into a master exclusion list. The exclusion format I use is particular to Zelix, and is meant to exclude both the package and class names of the classes in question. To see the details of what the exclusions look like, check out the sample properties file included in the download below.
Remember that you need to include any class referenced in either your plugin.xml
or manifest.mf
files. This includes the Plugin (or Activator) class, if you use one.
Also, for now, each obfuscation.properties
file must be added by hand to the the Zelix script. So every time you create a new plugin, you’ll need to edit the script. One area for improvement would be to have the Ant script create the list of properties files at build time and substitute them into the Zelix script.
Obfuscation and localization
There are a few extra gotchas that need to be handled when obfuscating an RCP application. The main one is that your localization classes (if you are using the new NLS class) need to be excluded from obfuscation. Fortunately, this can be done with one exclusion in the Zelix script:
exclude *.* + extends org.eclipse.osgi.util.NLS;
This line causes any class that extends the NLS class to be excluded from obfuscation.
A second issue also relates to NLS classes. When Zelix does flow-control obfuscation (for more information on this feature, see the Zelix site), it adds a static variable to various classes. This is usually not a problem, but with NLS classes it is. The NLS static initializer attempts to resolve the these static variables which it thinks are localized strings, and this initialization fails for these new variables added by Zelix. This doesn’t cause visible runtime problems, but it does pollute the logs. But again, this problem can be solved by adding a single exclude to the Zelix properties file:
obfuscateFlowExclude *.* extends org.eclipse.osgi.util.NLS *;
Try it for yourself
That ends this brief summary of my obfuscation process. If you would like to try this out for yourself, you can download the scripts that I use. Applying them to your own build process should be pretty straightforward, but if you have any questions, feel free to ask me questions.