一般我們寫Java源碼,用Java編譯器編譯出.class文件,是不會碰到校驗失敗的狀況的,因為正常的 Java編譯器都會小心對待生成的代碼。所以,想要看到校驗失敗的狀況,很容易的一個辦法就是自己生成 不合法的字節碼。
這裡我用了ObjectWeb的ASM來生成字節碼。可以從官網下載asm-3.1.jar,並保證其在編譯和運行下面 這個程序時在classpath上。
(本來是很想順便試試Charles O. Nutter寫的bitescript庫,不過惰性上來了,懶得去下載……下次 吧,下次)
Java代碼
import java.io.FileOutputStream; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; public class TestASM implements Opcodes { public static void main(String[] args) throws Exception { ClassWriter cw = new ClassWriter(0); cw.visit( V1_5, // class format version ACC_PUBLIC, // class modifiers "TestVerification", // class name fully qualified name null, // generic signature "java/lang/Object", // super class fully qualified name new String[] { } // implemented interfaces ); MethodVisitor mv = cw.visitMethod( ACC_PUBLIC, // access modifiers "foo", // method name "()V", // method description null, // generic signature null // exceptions ); mv.visitCode(); mv.visitInsn(FCONST_0); mv.visitVarInsn(FSTORE, 1); mv.visitVarInsn(ILOAD, 1); mv.visitVarInsn(ISTORE, 1); mv.visitInsn(RETURN); mv.visitMaxs(1, 2); mv.visitEnd(); // end method cw.visitEnd(); // end class byte[] clz = cw.toByteArray(); FileOutputStream out = new FileOutputStream("TestVerification.class"); out.write(clz); out.close(); } }
運行該程序後,得到的是TestVerification.class文件,其內容是:
Java bytecode代碼
public class TestVerification extends java.lang.Object minor version: 0 major version: 49 Constant pool: const #1 = Asciz TestVerification; const #2 = class #1; // TestVerification const #3 = Asciz java/lang/Object; const #4 = class #3; // java/lang/Object const #5 = Asciz foo; const #6 = Asciz ()V; const #7 = Asciz Code; { public void foo(); Code: Stack=1, Locals=2, Args_size=1 0: fconst_0 1: fstore_0 2: iload_0 3: istore_0 4: return }
可以看到,foo()裡代碼先把float類型的常量0.0壓到求值棧上(fconst_0),然後將它彈出並保存到 局部存儲區的第一格(fstore_0)。接下來從局部存儲區的第一格取出一個int類型的值壓到求值棧上 (iload_0),再將其彈出並再次保存到局部存儲區的第一格(istore_0)。
要是硬要用Java來表示這個.class文件裡的邏輯,大概類似這樣:
Java代碼
public class TestVerification { public void foo() { float a = 0; ((int) a) = a; } }
很明顯這沒辦法用Java表達出來……
上面代碼中,局部存儲區的第一格就在同一個方法裡前後用於保存了float和int類型的值,破壞了類 型安全。JVM為了保證類型安全,要求局部存儲區裡每格只能保存固定類型的值,load/store與對應的格 的類型必須匹配。
運行這個程序會導致校驗錯誤:
Command prompt代碼
D:\temp_code>java TestVerification Exception in thread "main" java.lang.VerifyError: (class: TestVerification, method: foo signature: ()V) Register 1 contains wrong type Could not find the main class: TestVerification. Program will exit.
於是JVM的類加載器先抱怨校驗失敗,然後拒絕加載這個main()方法,後面就連鎖抱怨找不到main()方 法。