I'm running into a problem with the dalvik dex converter and the opcode it is using to invoke methods. Basically I have a private final
method defined in my class, and when calling it, instead of generating the invoke-direct
opcode, dx is generating invoke-super
. Because it's a private method, the method doesn't exist on the super class, so I get a VFY violation on the device. I was able to track down the exact scenario that triggers this, and it appears to happen when:
--target 1.6
If those two conditions are met, the resulting dex class has invoke-super
instead of invoke-direct
. If I disable JaCoCo OR if I compile with --target 1.5
, it uses the correct invoke-direct
opcode.
In looking at the javap
disassembled class code, I can see what causes dx
to assume super instead of direct:
Not instrumented, compiled for 1.6:
$ javap -d com.example.ClassName | grep waitForConnectivity
159: invokespecial #115; //Method waitForConnectivity:()V
$ dexdump -d classes.dex | grep waitForConnectivity
147ad8: 7010 6042 0200 |001e: invoke-direct {v2}, Lcom/example/ClassName;.waitForConnectivity:()V // method@4260
Instrumented, compiled for 1.5 (--target 1.5
):
$ javap -d com.example.ClassName | grep waitForConnectivity
235: invokespecial #115; //Method waitForConnectivity:()V
$ dexdump -d classes.dex | grep waitForConnectivity
149d4c: 7010 9242 0400 |0018: invoke-direct {v4}, Lcom/example/ClassName;.waitForConnectivity:()V // method@4292
Instrumented, compiled for 1.6:
$ javap -d com.example.ClassName | grep waitForConnectivity
235: invokespecial #115; //Method com/example/ClassName.waitForConnectivity:()V
$ dexdump -d classes.dex | grep waitForConnectivity
149d4c: 6f10 9242 0400 |0018: invoke-super {v4}, Lcom/example/ClassName;.waitForConnectivity:()V // method@4292
So the difference is that the compiled .class file has compiled java bytecode that references the fully qualified class name of the this
class (notice "//Method waitForConnectivity:()V
" vs "//Method com/example/ClassName.waitForConnectivity:()V
"). It appears that dx
automatically assumes that if the method name is fully qualified, it must use invoke-super
, but if it's not qualified, it uses invoke-direct
.
My questions are:
dx
, or a bug in JaCoCo? My current workaround is to have a Maven "jacoco" profile, and in there I override the ${java.version}
property to change it from the default "1.6" to "1.5". Is there any better solution?
One of the rules that dx
uses to determine whether to emit invoke-super
or invoke-direct
is whether it believes the method call is being made on the same class as the one doing the calling. See RopperMachine.java
in the source, around line 912, included here for reference:
case ByteOps.INVOKESPECIAL: {
/*
* Determine whether the opcode should be
* INVOKE_DIRECT or INVOKE_SUPER. See vmspec-2 section 6
* on "invokespecial" as well as section 4.8.2 (7th
* bullet point) for the gory details.
*/
CstMethodRef ref = (CstMethodRef) cst;
if (ref.isInstanceInit() ||
(ref.getDefiningClass() == method.getDefiningClass()) ||
!method.getAccSuper()) {
return RegOps.INVOKE_DIRECT;
}
return RegOps.INVOKE_SUPER;
It would be interesting to see a more complete dump of the class that's getting misconverted. I think it's probably the case that what you're seeing from javap
isn't quite a complete picture of reality. Note that dx
itself has a .class file dumper built into it which provides a lot more detail than javap
. Invoke it as dx --dump --bytes path/to/Name.class
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With