Open zdary opened 5 years ago
Thanks for the report, I attached the reproducible sample from the sourceforge ticket here. It is clear what is going wrong, ProGuard does not obfuscate the respective interface methods in case of generics correctly as it looks only at the descriptor of methods atm.
This does not look like a quick and easy fix, but we will try to fix it asap.
I checked the sample and inspected the resulting class files. The type erased methods from the interface are still there and are not obfuscated:
public final java.lang.Object getTypedSomething();
public final void setTypedSomething(java.lang.Object);
Also when I test it with the same setup as you with a Main method, it works correctly. Can you possibly update the sample to illustrate the exact problem you are facing?
@netomi the method which need to stay is:
public MyPojo getTypedSomething();
not public Object getTypedSomething();
Object is fine as that really hides the generics. All other types fail
So when modifying the Main class to include something like that:
MyPojo arg = inter.getTypedSomething();
the resulting bytecode looks like that:
22: invokeinterface #7, 1 // InterfaceMethod MyInterface.getTypedSomething:()Ljava/lang/Object;
27: checkcast #4 // class MyPojo
so the compiler will invoke the type erased method and add a checkcast instruction. So the specialized method is not needed in general.
Can you describe in detail in which scenario the specialized method would be called and what is your tooling in this scenario (kotlin, certain java version)?
Hi @netomi
ok, let me take you through the process. We have a class in a library. - that's the jar which will not be obfuscated and needs to stay as-is.
public interface MyInterface<T> {
T getTypedSomething();
Then we have an implementation in the code which will be obfustated.
public class MyImplFail implements MyInterface<MyPojo> {
MyPojo getTypedSomething() {...}
Notice that T changes to MyPojo.
Expected :
public class MyImplFail implements MyInterface<MyPojo> {
MyPojo getTypedSomething();
Actual:
public final class a implements MyInterface<c> {
private c a() ;
So the actual problem is that proguard renames MyPojo getTypedSomething()
to c a()
which breaks the interface which is located in a library and is not obfuscated.
public interface MyInterface<T> {
T getTypedSomething();
I hope I explain myself clearly :) Feel free to reach out if not.
The problem description is clear, I can also see that result in the sample project:
MyImplFail -> a: 14:14:MyPojo getTypedSomething() -> a 1:1:java.lang.Object getTypedSomething() -> getTypedSomething
so the specialized method MyPojo getTypedSomething() gets obfuscated as ProGuard does not recognize it being an implementation method of the MyInterface interface.
Now, what we dont understand is in which cases that actually results in something wrong or an error. We did various tests and they all worked as the type erased method will be called at runtime.
Can you give more details about the environment where this leads to a runtime / compile error?
oh, we develop a plugin in java for enterprise level software. Since our code is just a plugin the "library" code loads the obfuscated implementation classes and uses interface methods to invoke the code
What are the target / source levels for the java plugin?
1.8 / 1.8
ok thanks, can you maybe share the snippet of code that loads / uses the obfuscated plugin which results in the crash I guess?
Also what source / target level is the consumer of the library using? Is the consumer maybe developed in another JVM language, like kotlin?
That would help us to create a reproducible testcase.
Hi, intellij uses a mix of kotlin and java.
The plugin developers declare all the extension points in plugin.xml each extension point has its own xml tag. For example
<projectService serviceImplementation="com.intellij.idea.plugin.hybris.flexibleSearch.mixins.FlexibleSearchElementFactory"/>
<applicationService serviceImplementation="com.intellij.idea.plugin.hybris.notification.NotificationManager"/>
Looking at the source code. They use Class.forName to get the class
Class.forName(myClassName, true, pluginClassLoader));
and then use reflection to check that the class implements the right interface and then get the constructor.
try {
Constructor<T> constructor = aClass.getDeclaredConstructor();
try {
constructor.setAccessible(true);
}
catch (SecurityException e) {
return aClass.newInstance();
}
return constructor.newInstance();
}
For the test case, the main method should be in a library and should use the above to load the class then access it using the interface.
I continued reproducing the issue, this time I tried to instantiate the class in kotlin and access the typed method, the resulting byte code is like that:
47: invokeinterface #39, 1 // InterfaceMethod test/MyInterface.getTypedSomething:()Ljava/lang/Object;
52: checkcast #41 // class java/lang/String
So the type erased method is being called and then a cast is done.
The corresponding kotlin code is like that:
val o:MyInterface<String> = Class.forName("MyImplFail")?.newInstance() as MyInterface<String>
val f = o.getTypedSomething()
Can you share a stacktrace that you get when loading the obfuscated plugin fails?
Hi,
proguard does not recognize interface with gererics in a library. It's implementation method names are then changed.
This interface is in library.jar
Implementation class is in in.jar
Proguard result
The problem is the generics. If your implementation uses
I have attached a sample project demonstrating this bug here https://sourceforge.net/p/proguard/bugs/765/
Thank you, Martin