我遇到了dalvik dex转换器及其用于调用方法的操作码的问题。基本上我在我的类中定义了private final
方法,并且在调用它时,不是生成invoke-direct
操作码,而是生成invoke-super
。因为它是私有方法,所以超类上不存在该方法,因此我在设备上遇到VFY违规。我能够找到触发这个的确切场景,它似乎发生在:
--target 1.6
如果满足这两个条件,则生成的dex类具有invoke-super
而不是invoke-direct
。如果我禁用JaCoCo OR,如果我使用--target 1.5
编译,则使用正确的invoke-direct
操作码。
在查看javap
反汇编的类代码时,我可以看到导致dx
假设超级而非直接的原因:
没有检测,编译为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
经过检验,编译为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
经过检测,编译为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
不同之处在于,编译的.class文件已经编译了java字节码,该字节码引用了this
类的完全限定类名(注意“//Method waitForConnectivity:()V
”vs“ //Method com/example/ClassName.waitForConnectivity:()V
“)。 dx
似乎自动假定如果方法名称是完全限定的,则必须使用invoke-super
,但如果它不合格,则使用invoke-direct
。
我的问题是:
dx
中的错误,还是JaCoCo中的错误? 我目前的解决方法是拥有一个Maven“jacoco”配置文件,并在那里覆盖${java.version}
属性,将其从默认的“1.6”更改为“1.5”。有没有更好的解决方案?
答案 0 :(得分:2)
dx
用于确定是否发出invoke-super
或invoke-direct
的规则之一是它是否认为方法调用是在与执行调用的类相同的类上进行的。请参阅此处包含的第912行中的RopperMachine.java
以供参考:
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;
看到一个被错误转换的类更完整的转储会很有趣。我认为你可能从javap
看到的并不是现实的全貌。请注意,dx
本身内置了一个.class文件转储器,它提供了比javap
更多的细节。将其调用为dx --dump --bytes path/to/Name.class
。