Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proguard removes method invocation, changing behaviour #378

Open
xxDark opened this issue Jan 6, 2024 · 1 comment
Open

Proguard removes method invocation, changing behaviour #378

xxDark opened this issue Jan 6, 2024 · 1 comment

Comments

@xxDark
Copy link

xxDark commented Jan 6, 2024

Greetings.
Before running Proguard, I apply some logic to the jar file to rewrite invokedynamic instructions to their equivalent parts.
The result of the transformation is, essentially:

Object object = method_handle;
if (object == null) {
    object = method_handle = ((MethodHandle) java.lang.invoke.CallSite::makeSite).invoke(LambdaMetafactory::metafactory, "accept", MethodType.fromMethodDescriptorString("...", null), new Object[]{MethodType.fromMethodDescriptorString("....", null), MyClass::method_to_call, MethodType.fromMethodDescriptorString("....", null)}, MethodOwner.class)
    .dynamicInvoker().asType(MethodType.fromMethodDescriptorString("(Ljava/lang/Object;)Ljava/lang/Object;", null));
}

Note that MethodType.fromMethodDescriptorString("....", null) is decompiler sugar and MT is pushed on the stack with LDC instructions instead. One of MTs references class with method in question according to LambdaMetafactory::metafactory bootstrap logic.
Despite not having -assumenosideeffects, the invocation of the method is removed. Bootstrap logic succeeds and lambda is created.
I assume that proguard has some builtin logic to detect lambdas and thus not remove them.
Am I correct that the only way to prevent Proguard from removing a method is to write so in the configuration file using keepclassmembers?
Thanks.

@xxDark
Copy link
Author

xxDark commented Jan 6, 2024

Simple example:

public interface MyConsumer<T> {
    public void accept(T t);
}

public class Test {
	public static void main(String[] args) {
		MyConsumer<String> c = consumer();
		c.accept("Hello, World");
	}
	
	private static MyConsumer<String> consumer() {
		return Test::println;
	}
	
	private static void println(String message) {
		System.out.println(message);
	}
}

After some testing, it is indeed seems like Proguard assumes that invocation of MyConsumer::accept has no side effects if there are no implementations of it in the jar file. If I add at least 1 file that references MyConsumer and use it in the code issue disappears.

Small sample, change .zip to .jar
For the sake of simplicity, use Java 8. Jar doesn't include logic necessary to run on newer versions.
Proguard settings:
Shrinking enabled, Test::main is kept
Obfuscation disabled
Optimizations enabled, 5 passes, all boxes under 'Remove' and 'Remove debugging' are unchecked
dontwarn is set to 'java.lang.invoke.**'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant