luontola / retrolambda

Backport of Java 8's lambda expressions to Java 7, 6 and 5
Apache License 2.0
3.54k stars 227 forks source link

Method references to private methods #17

Closed naixx closed 10 years ago

naixx commented 10 years ago

Hi. The problem is:

{
  someObserable.finallyDo(this::myProblemMethod);
}

private void myProblemMethod(){}

The code is compiled and dexed to run on Davlik VM. But in runtime on an android device I get warnings and the method isn't called, of course.

 DexOpt: illegal method access (ca;.myProblemMethod ()V from (Fragment$$Lambda$1;)
 I/dalvikvm﹕ Could not find method myProblemMethod, referenced from method Fragment$$Lambda$1.call
VFY: unable to resolve virtual method 43525:

The code works as a lambda if myProblemMethod is package private, public, everything except private. The method can be called explicitly. But it doesn't work as a method reference. Warnings appear only once.

So, is it a problem, that retrolambda somehow processes references, so that dex excludes them from the build?

luontola commented 10 years ago

Thanks. I was able to reproduce it. I'll have a look at solving it soon, maybe tomorrow.

I'm guessing that this has something to do with how in Java 8 the lambda classes have some special handling to that they can call private methods of other classes. Retrolamdba should probably detect when a method reference targets a private method and make it package private.

luontola commented 10 years ago

This isn't a Dalvik specific problem, so I renamed the issue.

naixx commented 10 years ago

Great to hear, that the problem is identified!

luontola commented 10 years ago

Still one bug remains (seems to depend on JDK 8 build). Working on it...

luontola commented 10 years ago

The fix is included in Retrolambda 1.2.2. It should show up in Maven Central in a couple of hours.

naixx commented 10 years ago

Looks like magic, thanks!

naixx commented 10 years ago

Hello, @orfjackal !

After updating to the new version, I've catched a crash.

 @Test
    public void call_of_method_with_reference(){
        class InnerClass {
            void foo(){
                Runnable r = LambdaTest.this::privateVoidMethod;
                r.run();
            }
        }
        new InnerClass().foo();
        privateVoidMethod();
        assertThat(privateInstanceMethod(), is("foo"));
    }

The code structure is like this. I haven't checked it in a test.

luontola commented 10 years ago

Please submit a SSCCE. You example doesn't compile and I haven't managed to reproduce the problem.

naixx commented 10 years ago

@orfjackal ok, finally I managed to reproduce the problem.

void thisMethodIsAlsoReferenced() {
    Observable.from("foo").lift((Operator<String, String>) (subscriber) -> {
        Runnable ref = MyOuterClass.this::problemPrivateMethod; //we make a reference in an anonymous class
        ref.run();
        return subscriber;
    }).subscribe(s -> {
        L.e(s);
    });
    problemPrivateMethod(); //java.lang.NoSuchMethodError
}

Unfortunately, I can't reproduce the problem in tests.

luontola commented 10 years ago

Please make that example short and self contained (http://www.sscce.org/) so that I can run it. Or send the me .class files before and after processing that code with Retrolambda.

Does the problem happen with Oracle JVM or only Dalvik VM?

naixx commented 10 years ago

I tried to make a simple junit test in a fork of your repo, but the problem didn't appear in that case. The only way to reproduce is run on Android device.

https://github.com/naixx/PrivateMethodAndroidBug Here is an Android project, that reproduces the problem with Davlik VM. I hope it matches SSCCE a bit :)

The warnings I get:

I/dalvikvm﹕ Could not find method com.example.testbug.app.MainActivity.problemPrivateMethod, referenced from method com.example.testbug.app.MainActivity.methodCall
W/dalvikvm﹕ VFY: unable to resolve direct method 8443: Lcom/example/testbug/app/MainActivity;.problemPrivateMethod ()V

And error on method usage.

I've also added classes before and after retrolambda from a real project. The problem method is cleanup and problemPrivateMethod

luontola commented 10 years ago

How to run that project? I can compile it with gradlew clean build but I haven't done any Android development and I can't figure out the command for starting the application.

naixx commented 10 years ago

If you don't have device, the easiest way is to run it through emulator or Genymotion. After build to run it on device or emulator type adb install ResultedFile.apk. You can also try Android Studio for that propose. It is the simplest way. I hope, this can help.

luontola commented 10 years ago

Thanks. I was able to reproduce it. Let's continue discussion in #18