proguard icon indicating copy to clipboard operation
proguard copied to clipboard

Proguard 7.0.1 inappropriately keeps classes that override default interface methods

Open drosenbauer opened this issue 3 years ago • 2 comments

A demonstration source code is attached.

To execute the demo:

  • Unzip the ZIP file, which contains two .java source files and a config.pro.
  • Compile the two .java source files with java.
  • Zip the two resulting .class files into Plain.jar: zip Plain.jar *.class
  • Run Proguard: bin/proguard.sh @config.pro

Expected behavior:

The class ConsumerWithError is not used by any "keep" entrypoints and should be removed during shrinking.

Actual behavior:

ConsumerWithError
  is invoked by    ConsumerWithError.accept (12:19)
  implements       java.util.function.Consumer.accept
  is a library method.

Source.zip

drosenbauer avatar Mar 22 '21 19:03 drosenbauer

Hi @drosenbauer ! Thanks for the clear report. I successfully executed your demo, and I agree that I would expect this interface to be removed during shrinking. When I modify your ConsumerWithError interface as follows:

  • remove all method invocations of methods that are in ConsumerWithError
  • modify the return type of methods with return type ConsumerWithError (namely andThen)
  • AND remove the anonymous function return (a) -> { ... }, then the interface is removed. My hypothesis of what is happening here is the following:
  • ProGuard marks the Consumer interface (including its methods) as 'used, as it is extended by the ConsumerWithError` interface/class.
  • Then ProGuard finds out that the ConsumerWithError class overrides certain methods (with a default implementation) of the Consumer class.
  • ProGuard notices that the ConsumerWithError class is used in (at least one of) these methods. It thus marks all classes that are referenced from the interface methods ... even the interface itself, without checking whether there even exists a concrete class implementing the interface. It is, however, clear that the ConsumerWithError interface is never implemented by a concrete class, but ProGuard doesn't seem to check this, therefore keeping the interface without actual usage.

I hope this clears things up a bit! I will make a ticket for this, so it can hopefully be fixed in a future release. Have a nice day!

heckej avatar Aug 27 '21 13:08 heckej

a very simple test case from https://stackoverflow.com/questions/11584159/is-there-a-way-to-make-runnables-run-throw-an-exception/53398040#53398040

@FunctionalInterface
public interface CheckedRunnable<E extends Exception> extends Runnable {

    @Override
    default void run() throws RuntimeException {
        try {
            runThrows();
        }
        catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    void runThrows() throws E;
}

Proguard incorrectly keep this class

 [proguard] Shrinking...
 [proguard] Explaining why classes and class members are being kept...
 [proguard] packageCheckedRunnable
 [proguard]   is invoked by    package.CheckedRunnable: void run() (8:23)
 [proguard]   implements       java.lang.Runnable: void run()
 [proguard]   is a library method.

lwr avatar Jan 20 '24 03:01 lwr