为何int[]与String[]合并为Object[]触发JVM验证错误?
数组类型合并与JVM验证错误的疑问
仔细阅读JVMS §4.10.2.2后,我注意到以下段落:
若对应值均为数组引用类型,则检查其维度。若数组类型维度相同,则合并后的值是二者首个公共超类型数组的实例引用。(若任一或两个数组类型的元素类型为基本类型,则用Object替代元素类型。)
[...]
即使int[]与String[]也可合并,结果为Object[],因为计算首个公共超类型时用Object替代了int。
int[]并非Object[]的子类型,这一点很有意思,于是我做了如下测试:
package test.se17; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import static org.objectweb.asm.Opcodes.*; import java.lang.invoke.MethodHandles; public class CastArrayDump { public static byte[] dump() throws Exception { ClassWriter classWriter = new ClassWriter(0); MethodVisitor methodVisitor; classWriter.visit(V1_5, ACC_PUBLIC | ACC_SUPER, "test/se17/CastArray", null, "java/lang/Object", null); { methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "cast", "(Ljava/lang/Object;)[Ljava/lang/Object;", null, null); methodVisitor.visitCode(); Label end = new Label(); methodVisitor.visitVarInsn(ALOAD, 0); methodVisitor.visitTypeInsn(INSTANCEOF, "[I"); Label notint = new Label(); methodVisitor.visitJumpInsn(IFEQ, notint); methodVisitor.visitVarInsn(ALOAD, 0); methodVisitor.visitTypeInsn(CHECKCAST, "[I"); methodVisitor.visitJumpInsn(GOTO, end); methodVisitor.visitLabel(notint); methodVisitor.visitVarInsn(ALOAD, 0); methodVisitor.visitTypeInsn(INSTANCEOF, "[Ljava/lang/String;"); Label notobj = new Label(); methodVisitor.visitJumpInsn(IFEQ, notobj); methodVisitor.visitVarInsn(ALOAD, 0); methodVisitor.visitTypeInsn(CHECKCAST, "[Ljava/lang/String;"); methodVisitor.visitJumpInsn(GOTO, end); methodVisitor.visitLabel(notobj); methodVisitor.visitMethodInsn(INVOKESTATIC, "test/se17/CastArray", "newIllegalArgumentException", "()Ljava/lang/IllegalArgumentException;", false); methodVisitor.visitInsn(ATHROW); methodVisitor.visitLabel(end); methodVisitor.visitInsn(ARETURN); methodVisitor.visitMaxs(2, 2); methodVisitor.visitEnd(); } { methodVisitor = classWriter.visitMethod(ACC_PRIVATE | ACC_STATIC, "newIllegalArgumentException", "()Ljava/lang/IllegalArgumentException;", null, null); methodVisitor.visitCode(); Label label0 = new Label(); methodVisitor.visitTypeInsn(NEW, "java/lang/IllegalArgumentException"); methodVisitor.visitInsn(DUP); methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalArgumentException", "<init>", "()V", false); methodVisitor.visitInsn(ARETURN); methodVisitor.visitMaxs(2, 0); methodVisitor.visitEnd(); } classWriter.visitEnd(); return classWriter.toByteArray(); } public static void main(String[] args) throws IllegalAccessException, Exception { MethodHandles.lookup().defineClass(dump()); test(); } private static void test() { System.out.println(CastArray.cast(new String[0])); } }
注意:CastArrayDump是针对具有相同签名cast方法的存根CastArray编译的。
运行此代码时,我得到以下异常:
Exception in thread "main" java.lang.VerifyError: (class: test/se17/CastArray, method: cast signature: (Ljava/lang/Object;)[Ljava/lang/Object;) Wrong return type in function at java.base/java.lang.ClassLoader.defineClass0(Native Method) at java.base/java.lang.System$2.defineClass(System.java:2307) at java.base/java.lang.invoke.MethodHandles$Lookup$ClassDefiner.defineClass(MethodHandles.java:2439) at java.base/java.lang.invoke.MethodHandles$Lookup$ClassDefiner.defineClass(MethodHandles.java:2416) at java.base/java.lang.invoke.MethodHandles$Lookup.defineClass(MethodHandles.java:1843) at test.se17/test.se17.CastArrayDump.main(CastArrayDump.java:69)
在cast方法中,控制流在end标签处合并。栈上的内容要么是String[],要么是int[]——按道理应合并为Object[]。
areturn指令类型安全的前提是:封闭方法的声明返回类型ReturnType为引用类型,且可从传入的操作数栈中合法弹出匹配ReturnType的类型。
现在,栈上应该是Object[],因为我们刚完成了合并。但不知为何实际并非如此,导致出现“Wrong return type in function”错误。
因此,我肯定遗漏了JVMS中禁止此操作的相关内容。JVMS中哪部分条款规定此操作不可行?
(明确说明:我从一开始就没指望这个能成功)
内容的提问来源于stack exchange,提问作者Johannes Kuhn
相关产品推荐
相关产品推荐

