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

Using obfuscated dependency results in "java finished with non-zero exit value 1" #124

Closed
kustra opened this issue Feb 25, 2017 · 8 comments

Comments

@kustra
Copy link

kustra commented Feb 25, 2017

Short version:

I created a minimal self-contained Gradle project that reproduces the issue: RetrolambdaTest.zip

Please run ./gradlew clean :app:assembleRelease in the project directory. It will fail with:

...
:app:transformClassesWithRetrolambdaForRelease FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:transformClassesWithRetrolambdaForRelease'.
> Process 'command '/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/bin/java'' finished with non-zero exit value 1

Slightly longer version:

I'm developing a closed source Android library that I'm obfuscating using proguard before uploading it to Maven. The library is using Java 6 syntax, so no retrolambda.

When I try to use this library in my app (using retrolambda, gradle-retrolambda and Java 8 syntax), I get the aforementioned error. The app itself is not proguarded, only the library.

If I include a non-obfuscated version of my library in the app, it works fine.
If I include -noverify in my JVM args as mentioned in #25, it also works fine, but I'm not exactly comfortable with this solution.

I tried to narrow down the exact cause of the error. I've found that those few lines of code in the test project are necessary to reproduce it. In particular, please take a look at the MyLibService and TestService classes. It turned out to not matter whether the obfuscated library comes from Maven or just another gradle module, so in the test project, it's in the lib module.

@kustra
Copy link
Author

kustra commented Feb 27, 2017

I experimented a bit more with this. Seems like the issue can be worked around if I extract the code triggering the error into a private class. So, instead of

public class MyLibService extends Service {

    @Override
    public IBinder onBind(Intent intent) {
        return Math.cos(1) < 0 ? new Binder() : null;
    }
}

I can write:

public class MyLibService extends Service {

    private final RetrolambdaBugfix retrolambdaBugfix = new RetrolambdaBugfix();

    @Override
    public IBinder onBind(Intent intent) {
        return retrolambdaBugfix.onBind(intent);
    }

    class RetrolambdaBugfix {
        IBinder onBind(Intent intent) {
            return Math.cos(1) < 0 ? new Binder() : null;
        }

    }
}

and it starts working.

@kustra
Copy link
Author

kustra commented Feb 27, 2017

I think the error is caused by any conditional evaluation in MyLibService. E.g. this variant also reproduces it:

public class MyLibService extends Service {

    private Object o;

    @Override
    public void onCreate() {
        super.onCreate();

        if (o != null) {

        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

If I remove the if block from the code, it works.

@luontola
Copy link
Owner

Here is the debug log from running gradle.

15:54:01.589 [INFO] [system.out] Retrolambda 2.5.0
15:54:01.650 [INFO] [system.out] 00:00  INFO: Bytecode version: 50 (Java 6)
15:54:01.650 [INFO] [system.out] 00:00  INFO: Default methods:  false
15:54:01.650 [INFO] [system.out] 00:00  INFO: Input directory:  /Users/esko/devel/retrolambda/tmp/RetrolambdaTest/app/build/intermediates/classes/release
15:54:01.651 [INFO] [system.out] 00:00  INFO: Output directory: /Users/esko/devel/retrolambda/tmp/RetrolambdaTest/app/build/intermediates/transforms/retrolambda/release/folders/1/1/retrolambda
15:54:01.651 [INFO] [system.out] 00:00  INFO: Classpath:        [/Users/esko/devel/retrolambda/tmp/RetrolambdaTest/app/build/intermediates/exploded-aar/RetrolambdaTest/lib/unspecified/jars/classes.jar, /Library/Java/JavaVirtualMachines/jdk1.8.0_74.jdk/Contents/Home/jre/lib/rt.jar, /Users/esko/devel/Android/platforms/android-25/android.jar, /Users/esko/devel/retrolambda/tmp/RetrolambdaTest/app/build/intermediates/classes/release]
15:54:01.651 [INFO] [system.out] 00:00  INFO: Included files:   all
15:54:01.654 [INFO] [system.out] 00:00  INFO: Agent enabled:    false
15:54:01.720 [INFO] [system.out] 00:00 ERROR: Failed to run Retrolambda
15:54:01.720 [INFO] [system.out] java.lang.RuntimeException: Failed to backport class: hu/kustra/retrolambdatest/TestService
15:54:01.720 [INFO] [system.out]        at net.orfjackal.retrolambda.Transformers.transform(Transformers.java:128)
15:54:01.720 [INFO] [system.out]        at net.orfjackal.retrolambda.Transformers.transform(Transformers.java:107)
15:54:01.720 [INFO] [system.out]        at net.orfjackal.retrolambda.Transformers.backportClass(Transformers.java:47)
15:54:01.720 [INFO] [system.out]        at net.orfjackal.retrolambda.Retrolambda.run(Retrolambda.java:83)
15:54:01.720 [INFO] [system.out]        at net.orfjackal.retrolambda.Main.main(Main.java:28)
15:54:01.720 [INFO] [system.out] Caused by: java.lang.RuntimeException: Failed to backport lambda or method reference: hu/kustra/retrolambdatest/TestService.lambda$onCreate$0()V (6)
15:54:01.720 [INFO] [system.out]        at net.orfjackal.retrolambda.lambdas.LambdaReifier.reifyLambdaClass(LambdaReifier.java:42)
15:54:01.720 [INFO] [system.out]        at net.orfjackal.retrolambda.lambdas.BackportLambdaInvocations$InvokeDynamicInsnConverter.backportLambda(BackportLambdaInvocations.java:186)
15:54:01.720 [INFO] [system.out]        at net.orfjackal.retrolambda.lambdas.BackportLambdaInvocations$InvokeDynamicInsnConverter.visitInvokeDynamicInsn(BackportLambdaInvocations.java:175)
15:54:01.720 [INFO] [system.out]        at net.orfjackal.retrolambda.asm.ClassReader.readCode(ClassReader.java:1519)
15:54:01.720 [INFO] [system.out]        at net.orfjackal.retrolambda.asm.ClassReader.readMethod(ClassReader.java:1032)
15:54:01.720 [INFO] [system.out]        at net.orfjackal.retrolambda.asm.ClassReader.accept(ClassReader.java:708)
15:54:01.720 [INFO] [system.out]        at net.orfjackal.retrolambda.asm.ClassReader.accept(ClassReader.java:521)
15:54:01.720 [INFO] [system.out]        at net.orfjackal.retrolambda.Transformers.lambda$transform$5(Transformers.java:107)
15:54:01.720 [INFO] [system.out]        at net.orfjackal.retrolambda.Transformers.transform(Transformers.java:124)
15:54:01.720 [INFO] [system.out]        ... 4 more
15:54:01.720 [INFO] [system.out] Caused by: java.lang.IllegalAccessException: no such method: hu.kustra.retrolambdatest.TestService.lambda$onCreate$0()void/invokeStatic
15:54:01.720 [INFO] [system.out]        at java.lang.invoke.MemberName.makeAccessException(MemberName.java:869)
15:54:01.720 [INFO] [system.out]        at java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:990)
15:54:01.720 [INFO] [system.out]        at java.lang.invoke.MethodHandles$Lookup.resolveOrFail(MethodHandles.java:1382)
15:54:01.720 [INFO] [system.out]        at java.lang.invoke.MethodHandles$Lookup.findStatic(MethodHandles.java:777)
15:54:01.720 [INFO] [system.out]        at net.orfjackal.retrolambda.lambdas.Types.toMethodHandle(Types.java:46)
15:54:01.720 [INFO] [system.out]        at net.orfjackal.retrolambda.lambdas.Types.asmToJdkType(Types.java:26)
15:54:01.720 [INFO] [system.out]        at net.orfjackal.retrolambda.lambdas.LambdaReifier.callBootstrapMethod(LambdaReifier.java:106)
15:54:01.720 [INFO] [system.out]        at net.orfjackal.retrolambda.lambdas.LambdaReifier.reifyLambdaClass(LambdaReifier.java:37)
15:54:01.720 [INFO] [system.out]        ... 12 more
15:54:01.720 [INFO] [system.out] Caused by: java.lang.VerifyError: Expecting a stackmap frame at branch target 19
15:54:01.720 [INFO] [system.out] Exception Details:
15:54:01.720 [INFO] [system.out]   Location:
15:54:01.720 [INFO] [system.out]     com/mylib/service/MyLibService.onBind(Landroid/content/Intent;)Landroid/os/IBinder; @6: ifge
15:54:01.721 [INFO] [system.out]   Reason:
15:54:01.721 [INFO] [system.out]     Expected stackmap frame at this location.
15:54:01.721 [INFO] [system.out]   Bytecode:
15:54:01.721 [INFO] [system.out]     0x0000000: 0fb8 0007 0e98 9c00 0dbb 0002 59b7 0006
15:54:01.721 [INFO] [system.out]     0x0000010: a700 0401 b0                           
15:54:01.721 [INFO] [system.out] 
15:54:01.721 [INFO] [system.out]        at java.lang.invoke.MethodHandleNatives.resolve(Native Method)
15:54:01.721 [INFO] [system.out]        at java.lang.invoke.MemberName$Factory.resolve(MemberName.java:962)
15:54:01.721 [INFO] [system.out]        at java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:987)
15:54:01.721 [INFO] [system.out]        ... 18 more
15:54:01.726 [DEBUG] [org.gradle.process.internal.DefaultExecHandle] Changing state to: FAILED
15:54:01.726 [DEBUG] [org.gradle.process.internal.DefaultExecHandle] Process 'command '/Library/Java/JavaVirtualMachines/jdk1.8.0_74.jdk/Contents/Home/bin/java'' finished with exit value 1 (state: FAILED)
15:54:01.727 [DEBUG] [org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter] Finished executing task ':app:transformClassesWithRetrolambdaForRelease'
15:54:01.727 [LIFECYCLE] [class org.gradle.TaskExecutionLogger] :app:transformClassesWithRetrolambdaForRelease FAILED

Relevant parts of the output of javap -v -p ./lib/build/intermediates/classes/release/com/mylib/service/MyLibService.class

public class com.mylib.service.MyLibService extends android.app.Service
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER

  public android.os.IBinder onBind(android.content.Intent);
    descriptor: (Landroid/content/Intent;)Landroid/os/IBinder;
    flags: ACC_PUBLIC
    Code:
      stack=4, locals=2, args_size=2
         0: dconst_1
         1: invokestatic  #2                  // Method java/lang/Math.cos:(D)D
         4: dconst_0
         5: dcmpg
         6: ifge          19
         9: new           #3                  // class android/os/Binder
        12: dup
        13: invokespecial #4                  // Method android/os/Binder."<init>":()V
        16: goto          20
        19: aconst_null
        20: areturn
      LineNumberTable:
        line 12: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      21     0  this   Lcom/mylib/service/MyLibService;
            0      21     1 intent   Landroid/content/Intent;
      StackMapTable: number_of_entries = 2
        frame_type = 19 /* same */
        frame_type = 64 /* same_locals_1_stack_item */
          stack = [ class android/os/Binder ]

There is a stack map table in the method that the exception mentions, so I'm confused. Otherwise it smells like issue #25

@luontola
Copy link
Owner

Ah, found it. The file lib/unspecified/jars/classes.jar contains a MyLibService.class file which is Java 7 bytecode (major version 51) but which doesn't contain a stack map table. In other words it's invalid Java bytecode (stack map table was optional in Java 6 but mandatory in Java 7).

$ javap -v -p com.mylib.service.MyLibService
Classfile /Users/esko/devel/retrolambda/tmp/RetrolambdaTest/foo/com/mylib/service/MyLibService.class
  Last modified Jan 1, 1970; size 387 bytes
  MD5 checksum 9a700a93de143cf12e7f15a2a5da1083
  Compiled from "SourceFile"
public class com.mylib.service.MyLibService extends android.app.Service
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Class              #17            // android/app/Service
   #2 = Class              #18            // android/os/Binder
   #3 = Class              #19            // com/mylib/service/MyLibService
   #4 = Class              #21            // java/lang/Math
   #5 = Methodref          #1.#8          // android/app/Service."<init>":()V
   #6 = Methodref          #2.#8          // android/os/Binder."<init>":()V
   #7 = Methodref          #4.#9          // java/lang/Math.cos:(D)D
   #8 = NameAndType        #13:#10        // "<init>":()V
   #9 = NameAndType        #20:#11        // cos:(D)D
  #10 = Utf8               ()V
  #11 = Utf8               (D)D
  #12 = Utf8               (Landroid/content/Intent;)Landroid/os/IBinder;
  #13 = Utf8               <init>
  #14 = Utf8               Code
  #15 = Utf8               LineNumberTable
  #16 = Utf8               SourceFile
  #17 = Utf8               android/app/Service
  #18 = Utf8               android/os/Binder
  #19 = Utf8               com/mylib/service/MyLibService
  #20 = Utf8               cos
  #21 = Utf8               java/lang/Math
  #22 = Utf8               onBind
{
  public com.mylib.service.MyLibService();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #5                  // Method android/app/Service."<init>":()V
         4: return
      LineNumberTable:
        line 8: 0

  public android.os.IBinder onBind(android.content.Intent);
    descriptor: (Landroid/content/Intent;)Landroid/os/IBinder;
    flags: ACC_PUBLIC
    Code:
      stack=4, locals=2, args_size=2
         0: dconst_1
         1: invokestatic  #7                  // Method java/lang/Math.cos:(D)D
         4: dconst_0
         5: dcmpg
         6: ifge          19
         9: new           #2                  // class android/os/Binder
        12: dup
        13: invokespecial #6                  // Method android/os/Binder."<init>":()V
        16: goto          20
        19: aconst_null
        20: areturn
      LineNumberTable:
        line 12: 0
}
SourceFile: "SourceFile"

@luontola
Copy link
Owner

In summary, this is the same issue as #25. The workaround is to add -noverify, or to not use proguard, or to fix proguard so that it will produce valid Java 7 bytecode, or to publish the library as Java 6 bytecode.

@luontola
Copy link
Owner

Please try configuring Retrolambda to produce Java 6 bytecode. I added the following lines to lib/build.gradle and it made the build pass.

apply plugin: 'me.tatarka.retrolambda'

retrolambda {
  javaVersion JavaVersion.VERSION_1_6
}

@luontola
Copy link
Owner

luontola commented Feb 27, 2017

Now that I read your message better, I see you don't need Retrolambda in the published library. So just configuring gradle to compile it as Java 6 should fix it. I managed to compile the sample project by configuring sourceCompatibility and targetCompatibility in lib/build.gradle

android {
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_6
    targetCompatibility JavaVersion.VERSION_1_6
  }
}

@kustra
Copy link
Author

kustra commented Feb 27, 2017

The library uses some Java 7 language features, so I ended up adding retrolambda to it, with sourceCompatibility and targetCompatibility set to Java 7. Thank you for helping.

By the way, Proguard seems to have a StackMapTable attribute that I can tell it to keep. It doesn't seem to affect the error though. Do you think it's a proguard bug?

@kustra kustra closed this as completed Feb 27, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants