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.
Related
I'm running into an issue at runtime when using Proguard with Kotlin reflection. Basically, when I am calling KClass.declaredMemberProperties, I get:
Incomplete hierarchy for class FieldBoundObject, unresolved classes [com.example.test.classes.DisposableObject]
kotlin.reflect.jvm.internal.components.RuntimeErrorReporter.reportIncompleteHierarchy
Here is the relevant stacktrace:
Caused by java.lang.IllegalStateException: Incomplete hierarchy for class FieldBoundObject, unresolved classes [com.example.test.classes.DisposableObject]
at kotlin.reflect.jvm.internal.components.RuntimeErrorReporter.reportIncompleteHierarchy(RuntimeErrorReporter.kt:26)
at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor$DeserializedClassTypeConstructor.computeSupertypes(DeserializedClassDescriptor.kt:181)
at kotlin.reflect.jvm.internal.impl.types.AbstractTypeConstructor$supertypes$1.invoke(AbstractTypeConstructor.kt:34)
at kotlin.reflect.jvm.internal.impl.types.AbstractTypeConstructor$supertypes$1.invoke(AbstractTypeConstructor.kt:22)
at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedLazyValue.invoke(LockBasedStorageManager.java:354)
at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedNotNullLazyValue.invoke(LockBasedStorageManager.java:410)
at kotlin.reflect.jvm.internal.impl.types.AbstractTypeConstructor.getSupertypes(AbstractTypeConstructor.kt:23)
at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor$DeserializedClassMemberScope.getNonDeclaredFunctionNames(DeserializedClassDescriptor.kt:278)
at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedMemberScope$functionNamesLazy$2.invoke(DeserializedMemberScope.kt:73)
at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedMemberScope$functionNamesLazy$2.invoke(DeserializedMemberScope.kt:40)
at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedLazyValue.invoke(LockBasedStorageManager.java:354)
at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedNotNullLazyValue.invoke(LockBasedStorageManager.java:410)
at kotlin.reflect.jvm.internal.impl.storage.StorageKt.getValue(storage.kt:42)
at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedMemberScope.getFunctionNamesLazy(DeserializedMemberScope.kt)
at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedMemberScope.getFunctionNames(DeserializedMemberScope.kt:84)
at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaClassMemberScope.computeFunctionNames(LazyJavaClassMemberScope.kt:75)
at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaClassMemberScope.computeFunctionNames(LazyJavaClassMemberScope.kt:65)
at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaScope.computeNonDeclaredFunctions(LazyJavaScope.kt:342)
at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaScope$allDescriptors$1.invoke(LazyJavaScope.kt:61)
at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaScope$allDescriptors$1.invoke(LazyJavaScope.kt:55)
at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedLazyValue.invoke(LockBasedStorageManager.java:354)
at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedNotNullLazyValue.invoke(LockBasedStorageManager.java:410)
at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaScope.getContributedDescriptors(LazyJavaScope.kt:323)
at kotlin.reflect.jvm.internal.impl.resolve.scopes.ResolutionScope$DefaultImpls.getContributedDescriptors$default(ResolutionScope.kt:52)
at kotlin.reflect.jvm.internal.KDeclarationContainerImpl.getProperties(KDeclarationContainerImpl.kt:64)
at kotlin.reflect.jvm.internal.KClassImpl$Data$declaredNonStaticMembers$2.invoke(KClassImpl.kt:151)
at kotlin.reflect.jvm.internal.KClassImpl$Data$declaredNonStaticMembers$2.invoke(KClassImpl.kt:44)
at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:92)
at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:31)
at kotlin.reflect.jvm.internal.KClassImpl$Data.getDeclaredNonStaticMembers(KClassImpl.kt)
at kotlin.reflect.full.KClasses.getDeclaredMemberProperties(KClasses.kt:163)
at com.example.test.classes.FieldBinder.bindFields(FieldBinder.kt:51)
at com.example.test.classes.FieldBinder.(FieldBinder.kt:28)
at com.example.test.classes.FieldBoundObject.(FieldBoundObject.kt:15)
at com.example.test.models.TestClass.(TestClass.java:52)
at com.example.test.models.TestClass.(TestClass.java:58)
at com.example.test.ReactLandingActivity.startClass(ReactLandingActivity.java:93)
Here is the relevant call in Fieldbinder.kt:
class FieldBinder(receiver: Any) {
private fun bindFields(receiver: Any, classToCheck: Class<*>) {
for (property in classToCheck.kotlin.declaredMemberProperties) {
//...
}
}
// ...
}
Here is the signature for the "missing" class DisposableObject
package com.example.test.classes
open class DisposableObject : ScopeProvider, Dispose {
//...
}
I think it has something to do with name obfuscation. I've tried using #Keep on the actual class itself, as well as using -keep class on the class itself in my Proguard file. However, what DOES solve the problem is keeping names in Proguard:
-dontobfuscate
or
-keepnames class com.example.test.classes.DisposableObject
Is there an issue with name obfuscation and Kotlin reflection? I'm not having the same issues with Java reflection. Or is there a better way to solve this problem? Thanks!
I defined an annotation named #KeepAll.
I have an interface like
#KeepAll
public interface MainEntity {
//some methods
}
I want to keep all classes which implement this interface from obfuscation. Is this possible on ProGuard?
NOTE I know I can define it as
-keep public class * implements **.MainEntity
But I don't want to specify interface name but annotation name.
After a long trial and error process I get what I want. Here is the solution
Keep class names with annotation KeepAll;
-keep #com.package.name.KeepAll public class **
Keep class members of classes and interface with annotation KeepAll;
-keepclassmembers #com.package.name.KeepAll class ** { public <methods>; <fields>;}
Keep class members of a class which implemets a class that has KeepAll annotation. (This was what I want)
-keepclassmembers public class * implements #com.package.name.KeepAll ** { public <methods>; <fields>;}
You can tell ProGuard to keep everything with an annotation like this:
-keep #com.google.inject.Singleton public interface *
The above would keep the interface itself from obfuscation.
To get the implementations of the interface you can do something like this:
-keep public class * implements **.MainEntity
So Right now I am confused what you want to achieve. If you only annotate the interface it wont be a help for ProGuard. The classes would need this annotation.
In other words: what -keep commands should I use to tell Proguard to avoid obfuscating my classes that represent native libraries? (since JNA requires that the names match the equivalent native function, struct, etc.)
This is the rule I'm using for now:
-keepclassmembers class * extends com.sun.jna.** {
<fields>;
<methods>;
}
I still think there might be a better way to do it though.
For me worked as well
-keep class com.sun.jna.** { *; }
-keep class * implements com.sun.jna.** { *; }
I think I solved it using these rules instead, because it seems they need everything of the package to be de-obfuscated:
-keep class com.sun.jna.** { *; }
-keep class * implements com.sun.jna.** { *; }
JNA by default uses Library interface method names to look up native function names. Anything other than those should be able to withstand obfuscation.
If your tests include coverage of all JNA calls then you should be able to test this almost as fast as asking the question here.
EDIT
Consider this a comment, since I'm not prepared to offer "-keep" commands :)
You certainly must avoid elimination or reordering of any Structure fields.
I have distributed an application on the Android Marketplace. I am getting error reports back in from a small handful of users (maybe 2%) where they are getting NullPointerExceptions where it doesn't make logical sense.
I have never been able to replicate this myself. The code is relatively straightforward and is a common code path that EVERY user has to follow. I've actually taken every separate line of code that could possibly be creating the NPE and wrapped it in a try-catch block and throw a custom runtime exception, but I'm still getting NullPointerException errors not caught.
At this point, the only thing I can imagine it would be is something related to my Proguard obfuscation. I have seen some other article talking about taking out the -overloadaggressively option if you notice odd behavior, but as far as I can tell, I'm not using that option.
Has anybody else experienced mysterious NPEs using android and proguard. Are there any other settings people can recommend to dial down the optimizations that might be causing this issue?
Any other ideas?
For reference, here is the unobfuscated function that is getting the NPE:
public MainMenuScreen(final HauntedCarnival game) {
super(game);
game.startMusic("data/music/intro.mp3");
stage = new Stage(Screen.SCREEN_WIDTH, Screen.SCREEN_HEIGHT,true);
stage.addActor(new Image("background", Assets.mainMenuBackground));
Image title = new Image("title", Assets.mainMenuTitle);
title.x = 0;
title.y = 340;
resetEyeBlink();
stage.addActor(title);
dispatcher.registerInputProcessor(stage);
settings = game.getSettings();
eyeBlinkImage = new Image("eyeBlink", Assets.eyeBlink);
if (settings.getPlayers().isEmpty()) {
settings.addPlayer("Player One");
settings.save(game);
}
setupContinue();
}
So the only possibilities I can see are game, dispatcher and settings.
game gets set via this code in another class. game is a final variable in that other class:
game.setScreen(new MainMenuScreen(game));
dispatcher gets set within in the call to super above.
getSettings() returns a settings object that gets set at the very start of the application, is private and never gets unset. Its also used before this method several times.
There are not auto-boxing primitives.
here is the proguard config:
-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontpreverify
-verbose
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
-keepattributes Signature
-keep public class com.alkilabs.hauntedcarnival.settings.Settings
-keep public class com.alkilabs.hauntedcarnival.settings.Settings {
*;
}
-keep public class com.alkilabs.hauntedcarnival.settings.Player
-keep public class com.alkilabs.hauntedcarnival.settings.Player {
*;
}
-keepnames public class com.alkilabs.hauntedcarnival.world.World
-keepnames public class * extends com.alkilabs.hauntedcarnival.world.upgrades.Upgrade
-keepnames public class * extends com.alkilabs.hauntedcarnival.world.achievments.Achievement
-keepnames public class com.alkilabs.hauntedcarnival.world.monsters.MonsterType
-keepclassmembers class * extends com.alkilabs.hauntedcarnival.world.monsters.Monster {
public <init>(com.alkilabs.hauntedcarnival.world.monsters.MonsterType, java.lang.Integer, com.alkilabs.hauntedcarnival.world.World);
}
-keepnames public class com.alkilabs.hauntedcarnival.world.items.ItemType
-keepclassmembers class * extends com.alkilabs.hauntedcarnival.world.items.Item {
public <init>(com.alkilabs.hauntedcarnival.world.World, java.lang.Integer, java.lang.Integer);
}
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-dontwarn com.badlogic.gdx.scenes.scene2d.ui.utils.DesktopClipboard
-dontwarn com.badlogic.gdx.utils.JsonWriter
-dontwarn com.badlogic.gdx.utils.XmlWriter
-keepclasseswithmembernames class * {
native <methods>;
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
OK, I think I got to the root of the issue/confusion.
One of the things proguard does is in-line some methods. Because of this, the entire contents of my setupContinue() function at the bottom of my constructor was added directly into the contents of my constructor. So now I have a bunch more code to review, and I do see some more possibilities for NPEs. I'm pretty sure I'll get to the bottom of my issue.
I figured this out by taking the obfuscated.jar that proguard produces, and running it through a decompiler. Its an interesting exercise as you get get little more insight into the inner workings of proguard. I highly recommend it for people wanting to better understand the impacts that proguard has on their code.
Your best bet would be to use the mapping.txt file and the retrace tool to find the exact location of the error.
From there it would be easier to understand if it's indeed Proguard or some other end-case you didn't think of.
To do this, you need to copy the stack trace from the developer's console to another file, let's assume it's called
c:\trace.txt
Now, in your project, you'll find a Proguard folder with 4 files.
Let's assume your project is in
c:\project
What you'll need to do is run the retrace tool (using the batch file for easier use) located at (change to the location of your Android Sdk folder) :
c:\android-sdk-windows\tools\proguard\bin\retrace.bat
Go to that folder and run:
retrace c:\project\proguard\mapping.txt c:\trace.txt
From there on, it would be much easier to figure our the exact line of the exception and to probably find the error.
From my experience, the only things that could get messed up are third party libraries. Normal Android code, for all my projects, was never harmed from obfuscation.
Sorry I can't post comments yet (I'm new to SO).
This could be another problem I've encountered myself. On some phones there was a problem at some point with missing Android libraries like a JSon library.
I'd recommend you take a closer look at what phones that actually get the NPE - there might be some similarities.
In my case it was the HTC Desire Z, that was missing the JSon library and so the app force closed every time the JSon part was called. The problem was fixed by HTC later on with a hotfix to the Desire Z's rom.
I use in my project a piece of code as described here
http://lexandera.com/2009/01/extracting-html-from-a-webview/
I create the .apk file, install it on my device and it correctly works. If I try to use the obfuscation with proguard the project fails, the method showHTML(String html) of MyJavaScriptInterface is not reached.
My proguard configuration regarding that
-keep public class com.mypackage.MyClass.MyJavaScriptInterface
-keep public class * implements com.mypackage.MyClass.MyJavaScriptInterface
-keepclassmembers class * implements com.mypackage.MyClass.MyJavaScriptInterface {
<methods>;
}
according to this this answer Android proguard Javascript Interface problem.
SOLVED.
As Eric suggested, I changed the Proguard configuration file like this:
-keep public class com.mypackage.MyClass$MyJavaScriptInterface
-keep public class * implements com.mypackage.MyClass$MyJavaScriptInterface
-keepclassmembers class com.mypackage.MyClass$MyJavaScriptInterface {
<methods>;
}
Now my project works perfectly.
For API 17+ you also need to preserve the #JavascriptInterface annotations:
-keepattributes JavascriptInterface
http://developer.android.com/reference/android/webkit/JavascriptInterface.html
If MyJavaScriptInterface is an inner class of MyClass, ProGuard expects a fully qualified name com.mypackage.MyClass$MyJavaScriptInterface. The naming convention with $ is used in the compiled class files on which ProGuard operates. Note that ProGuard mentions class names in the configuration that it can't find in the input jar, suggesting that these names may have been misspelled.
-keepclassmembers class com.mypackage.MyClass$JavaScriptInterface {
public *;
}
Use only this. It works for me.
Those Who are laze to provide the entire package path.
-keepclassmembers class **.*$PaymentJavaScriptInterface{
public *;
}
As suggested by edit in question,
out of those suggestions,
only using
-keepclassmembers class com.mypackage.MyClass$MyJavaScriptInterface {
public *;
}
with Important -
For API 17+ to preserve #JavascriptInterface annotations:
-keepattributes JavascriptInterface
(Which was stopping my app to work on Marshmallow)