annotations having no effect in proguard - java

I'm having troubles getting proguard to keep things based on their annotations (under Android).
I have an annotation, com.example.Keep:
#Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR })
public #interface Keep {
}
I have a class, which is only ever constructed through reflection:
public class Bar extends Foo {
#com.example.Keep
Bar(String a, String b) { super(a, b); }
//...
}
It works as expected (class keeps its constructor albeit with an obfuscated class name) When my proguard config includes the following:
-keepattributes Signature,*Annotation*,SourceFile,LineNumberTable
-keepclassmembers class ** extends com.example.Bar {
<init>(...);
}
It drops the constructor if I try change that to:
-keepattributes Signature,*Annotation*,SourceFile,LineNumberTable
-keepclassmembers class ** extends com.example.Bar {
#com.example.Keep <init>(...);
}
I see the same behaviour with both my own annotations and proguard's annotation.jar annotations. I've copied and pasted the annotation name from Foo's import of Keep, so am confident it's not a typo or incorrect import.
Can anyone think of what I might be missing?

If your annotations are being processed as program code, you should explicitly preserve them:
-keep,allowobfuscation #interface *
The reason is somewhat technical: ProGuard may remove them in the shrinking step, so they are no longer effective in the optimization step and the obfuscation step.
Cfr. ProGuard manual > Troubleshooting > Classes or class members not being kept.

Related

Create a custom annotation for springboot beans [duplicate]

Can anyone explain in a clear way the practical differences between the java.lang.annotation.RetentionPolicy constants SOURCE, CLASS, and RUNTIME?
I'm also not exactly sure what the phrase "retaining annotation" means.
RetentionPolicy.SOURCE: Discard during
the compile. These annotations don't
make any sense after the compile has
completed, so they aren't written to
the bytecode.
Example: #Override, #SuppressWarnings
RetentionPolicy.CLASS: Discard during
class load. Useful when doing
bytecode-level post-processing.
Somewhat surprisingly, this is the
default.
RetentionPolicy.RUNTIME: Do not
discard. The annotation should be
available for reflection at runtime.
Example: #Deprecated
Source:
The old URL is dead now
hunter_meta and replaced with hunter-meta-2-098036. In case even this goes down, I am uploading the image of the page.
Image (Right Click and Select 'Open Image in New Tab/Window')
According to your comments about class decompilation, here is how I think it should work:
RetentionPolicy.SOURCE: Won't appear in the decompiled class
RetentionPolicy.CLASS: Appear in the decompiled class, but can't be inspected at run-time with reflection with getAnnotations()
RetentionPolicy.RUNTIME: Appear in the decompiled class, and can be inspected at run-time with reflection with getAnnotations()
Minimal runnable example
Language level:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.SOURCE)
#interface RetentionSource {}
#Retention(RetentionPolicy.CLASS)
#interface RetentionClass {}
#Retention(RetentionPolicy.RUNTIME)
#interface RetentionRuntime {}
public static void main(String[] args) {
#RetentionSource
class B {}
assert B.class.getAnnotations().length == 0;
#RetentionClass
class C {}
assert C.class.getAnnotations().length == 0;
#RetentionRuntime
class D {}
assert D.class.getAnnotations().length == 1;
}
Bytecode level: using javap we observe that the Retention.CLASS annotated class gets a RuntimeInvisible class attribute:
#14 = Utf8 LRetentionClass;
[...]
RuntimeInvisibleAnnotations:
0: #14()
while Retention.RUNTIME annotation gets a RuntimeVisible class attribute:
#14 = Utf8 LRetentionRuntime;
[...]
RuntimeVisibleAnnotations:
0: #14()
and the Runtime.SOURCE annotated .class does not get any annotation.
Examples on GitHub for you to play with.
Retention Policy: A retention policy determines at what point an annotation is discarded. It is s specified using Java's built-in annotations: #Retention[About]
1.SOURCE: annotation retained only in the source file and is discarded
during compilation.
2.CLASS: annotation stored in the .class file during compilation,
not available in the run time.
3.RUNTIME: annotation stored in the .class file and available in the run time.
CLASS
:Annotations are to be recorded in the class file by the compiler but need not be retained by the VM at run time.
RUNTIME
:Annotations are to be recorded in the class file by the compiler and retained by the VM at run time, so they may be read reflectively.
SOURCE
:Annotations are to be discarded by the compiler.
Oracle Doc

How to stop Proguard shrink from removing annotation parameters even though it keeps the annotation itself?

I'm only using Proguard (version 5.2.1) to shrink, not to obfuscate or optimize. Here's my config:
-forceprocessing
-dontobfuscate
-dontoptimize
-dontwarn
-dontnote
-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers
-libraryjars <java.home>/lib
-keep class ** extends **.MyRequestHandler
-keep #org.springframework.stereotype.Component class **
-keep #org.aspectj.lang.annotation.Aspect class **
-keep #interface *
I have a class which is an AspectJ aspect that looks like this:
#Aspect
public class MyAspect {
#Pointcut("execution(* com.amazonaws.services.dynamodb*.*AmazonDynamoDB.*(..))")
public void myPointCut() {
}
#Around(value = "myPointCut()")
public Object logExecution(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println(joinPoint);
return joinPoint.proceed();
}
}
When I feed my application through Proguard, and then decompile the resulting class file, this is what it looks like:
#Aspect
public class MyAspect {
#Pointcut("execution(* com.amazonaws.services.dynamodb*.*AmazonDynamoDB.*(..))")
public void myPointCut() {
}
#Around
public Object logExecution(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println(joinPoint);
return joinPoint.proceed();
}
}
Notice that the "value" parameter from the #Around annotation is missing! This is very weird behavior... I almost think it's a bug in Proguard. It's keeping the annotation itself, just not the parameter... though weirdly enough it is keeping the parameter from the #Pointcut annotation. Does anyone know how to fix this?
As a reminder, I'm only shrinking, and the -keepattributes config is only for obfuscation, so please no one respond that -keepattributes *Annotation* will fix it. I've tried it and it has no effect.
I found this similar question (annotations having no effect in proguard) which is where I got the -keep #interface * config. This setting is supposed to keep all annotations, which it seems to be doing, but for some reason it's not keeping all of the parameters. I tried many variations of this, such as:
-keep #interface **
-keep #interface* *
-keep #interface.On *
-keep #interface.* *
-keepclassmembers class ** { #org.aspectj.lang.annotation.Around ; }
-keepclassmembers class ** { #org.aspectj.lang.annotation.Around.On ; }
-keepclassmembers class ** { #org.aspectj.lang.annotation.Around.* ; }
Some of these approaches just cause Proguard to throw an error, and the ones that don't aren't having any effect. Please help!
The answer was posted on this ProGuard bug report: https://sourceforge.net/p/proguard/bugs/700/
You just need to change this config value:
-keep #interface *
To this:
-keep #interface * {*;}
This still seems like a bug in ProGuard because I'm clearly using those annotation parameters, and I'm telling ProGuard to keep the classes and the annotations, so it shouldn't be removing them... but this is the workaround.

Is it possible to let ProGuard keep elements with RetentionPolicy.SOURCE?

I'm using the #VisibleForTesting annotation located in the Android Support Annotations library, and it looks like this:
#Retention(SOURCE)
public #interface VisibleForTesting {
}
As I understand it, ProGuard operates on the .class files and since this annotation isn't available at compile time due to its retention policy, all the annotated methods are stripped away. I'd like to run automated tests on my app and use the methods exposed for testing to verify that the ProGuard configuration doesn't break any use cases.
Is it possible to configure ProGuard to keep these elements somehow? So far I've tried:
-keep #android.support.annotation.VisibleForTesting class *
-keep class android.support.annotation.** {
#**.VisibleForTesting *;
}
-keep interface android.support.annotation.** {
#**.VisibleForTesting *;
}
And:
-keep interface android.support.annotation.VisibleForTesting
-keepclasseswithmembers class * {
#android.support.annotation.VisibleForTesting *;
}
-keepclassmembers class ** {
#android.support.annotation.VisibleForTesting *;
}
These two configurations do not work. If I annotate the methods with #Keep as well, and configure ProGuard to keep those methods, the methods are kept and the tests pass. However, by doing that I have to annotate all methods with two annotations.
Is it possible to hook into the annotation processor and override the retention policy for #VisibleForTesting? Or is that already too late in the build process?
Guava's #VisibleForTesting uses RetentionPolicy.CLASS, while Android Support Annotations Library uses RetentionPolicy.SOURCE. I'm considering posting a request to change the policy, but I suppose it's set to SOURCE for a reason, possibly due to performance and a very slightly increased file size?
Are there any options other than using two annotations (#VisibleForTesting and #Keep)?
Annotations with a RetentionPolicy == SOURCE are not present in .class files on which ProGuard is operating. Thus there is no way to use them in rules as they will never match.
Annotations with RetentionPolicy == CLASS should work fine. If needed, they can even be removed in release builds using ProGuard.

Tell Proguard to keep annotation on methods

I'm using my own annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.TYPE})
public #interface Loggable { }
and obfuscate using Proguard. I use the -keepattributes *Annotation*
in the Proguard configuration to keep the annotations.
At runtime, when I retrieve the annotation from an annotated class using someClass.getAnnotation(Loggable.class) everything works - I retrieve a non-null instance of my annotation.
However, when I want to apply the same to an annotated method of some class, I retrieve null from someMethod.getAnnotation(Loggable.class).
Is Proguard removing the annotations from methods? How do I tell it not to do so?
I'm using Proguard 4.7.
You need to use keepclassmembers parameter:
-keepclassmembers class ** {
#com.yourpackage.Loggable public *;
}

How to stop ProGuard from stripping the Serializable interface from a class

Is there an explicit way to stop ProGuard from changing a class from implementing an interface?
I have a class that implements java.io.Serializable, let's call it com.my.package.name.Foo. I've found that after running through ProGuard, it no longer implements Serializable. I get null after I cast from Serializable to Foo and false if I check an instance with instanceof Serializable. I've made sure to set ProGuard to ignore this class:
-keep class com.my.package.name.Foo
I've also tried:
-keep class com.my.package.name.Foo { *; }
and I've also tried the whole package by doing this:
-keep class com.my.package.name.** { *; }
or:
-keep class com.my.package.** { *; }
and also just to keep all Serializable classes:
-keep class * implements java.io.Serializable { *; }
but to no avail. I have another class in a sibling package (roughly: com.my.package.name2.Bar) that also implements Serializable and is used similarly but has no issues.
I'm not sure it's relevant, but I'm packing this in a jar for use with Android. The code that uses these classes include putting them in Bundles which is why I need Serializable. I considered that perhaps somehow ProGuard thinks that Foo is never used as a Serializable but that seems unlikely given that I pass it as a parameter to Bundle.putSerializable(String, Serializable) and also I do an implicit cast: Serializable serializable = foo;. In fact, when I debug, I can see the Foo get put into the Bundle and I can examine the Bundle and see the instance of Foo there, but when retrieving it the cast fails.
I had the same issue fixed using below config.
-keepnames class * implements java.io.Serializable
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient <fields>;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
Official Documentation
http://proguard.sourceforge.net/manual/examples.html#serializable
ProGuard doesn't ever strip interfaces that are defined in libraries (like Serializable) from classes in code that is processed (like Foo). Library code may be casting to those interfaces, so they can't be removed.
I get null after I cast from Serializable to Foo
This means that the instance must be null to start with. Your analysis would be correct if you'd get a ClassCastException. You can check that Foo still implements Serializable with javap. The problem probably lies elsewhere. For tips on serialization, you can look at the ProGuard manual > Examples > Processing serializable classes.
Update:
In this case, it turns out to be a configuration problem. ProGuard can only process class files if it knows everything about their hierarchy (just like a compiler). You really have to specify the runtime classes:
-libraryjars <java.home>/lib/rt.jar
or for Android:
-libraryjars /usr/local/android-sdk/platforms/android-17/android.jar
The Android Ant/Eclipse builds automatically specify all necessary -injars/-outjars/-libraryjars options for you, but in a custom build process, you have to specify them yourself. Cfr. ProGuard manual > Examples > A complete Android application.
Note that the option -dontwarn makes the warnings go away, but not the problems. Only use it if really necessary.

Categories

Resources