一般我們寫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()方 法。