Friday, April 20, 2012

Proguard, Maven and obfuscating multiple layers of libraries


I was trying to obfuscate a project and kept getting errors like this:

[proguard] Warning: library class xxx.Xxx extends or implements program class yyy.YYY
 [proguard] Warning: there were 1 instances of library classes
depending on prog
ram classes.
 [proguard]          You must avoid such dependencies, since the
program classes
 will
 [proguard]          be processed, while the library classes will
remain unchang
ed.
 [proguard] Error: Please correct the above warnings first.

This can happen if you have a dependency to a library X, that transitively depends on library Y, and you try to obfuscate your app + Y, but not X.  There are two ways to solve this.

Solution 1:
Change X to obfuscate and embed Y. When your app is then obfuscated, it will have no idea that X references the classes in Y, as it will reference obfuscated versions.

Solution 2:
Change your app to obfuscate and embed X.  Thus X and Y will be obfuscated together, it will all be considered "app" code by Proguard, and the error will go away.

Thursday, April 19, 2012

Spring, JavaConfig and obfuscation

Obfuscation of Spring projects can be made easier by using Spring JavaConfig, as it mostly uses plain Java code, but there are some gotchas. Here are some tips (some might be proguard specific):
  • Don't use @Value to populate fields from system properties. Obfuscation can delete the annotation (even with proguard "-keepattributes *Annotation*"). Use this technique instead:

 static private final String someprop;
 static {
  /* @Value technique gets complicated w/ obfuscation */
  someprop = System.getProperty("somepropname");
 }

  • Don't use initMethod or destroyMethod attributes of @Bean. They use reflection, and reflection and obfuscation don't mix well. Do this instead:
 @Bean
 public SomeBean someBean()
 {
  SomeBean result = new SomeBean();
  /* destroyMethod attibute on @Bean gets messed up by obfuscation */
  Runtime.getRuntime().addShutdownHook(new Thread(new Runnable()
  {
   @Override
   public void run()
   {
    result.shutdown();
   }
  }));
  return result;
 }

  • Don't use -overloadaggressively. JavaConfig doesn't like @Bean methods with the same name.

  • Use -keepattributes *Annotation* to preserve annotations.

  • Preserve your public constructor if you reference your @Configuration class from Spring XML.  Also, keep all fields so they are autowired properly.
-keepclassmembers class com.mycompany.MyAppConfig { 
 <fields>;
 <init>();
}


  • Ensure your @Bean methods are obfuscated, but not shunk
-keepclassmembers,allowobfuscation class com.mycompany.MyAppConfig { 
 public *;
}
  • I had many problems when composing several @Configuration contexts like so:
@Configuration
@Import(Y.class)
public class X {
...
}

For some reason, X couldn't resolve some beans in Y. The only way I could get this to work was to change X to inherit from Y, like so:

@Configuration
public class X extends Y {
...
}

This, of course, imposes a hierarchical structure to your contexts.