我正在写一个字节码指导员。现在,我试图找出在物体存在的情况下如何做到这一点。我想对我在JVMS中阅读的两行(第4.9.4节)做一些澄清:
1)“验证者拒绝使用新对象之前的代码 初始化“。
我的问题是,“使用”在这里意味着什么?我猜这意味着:将其作为方法属性传递,在其上调用GETFIELD
和PUTFIELD
,或者在其上调用任何实例方法。他们的其他禁止用途是什么?我相信,我们可以使用DUP
,LOAD
和STORE
等其他说明。
2)“在该方法之前调用另一个实例初始化方法 myClass或其直接超类 这个,唯一的操作方法 可以执行这是分配 在myClass中声明的字段。“
这意味着在<init>
方法中,在调用另一个<init>
之前允许使用GETFIELD和PUTFIELD。但是,在Java中,在调用super()
或this()
之前对实例字段执行任何操作都会导致编译错误。有人可以澄清这个吗?
3)我还有一个问题。对象引用何时初始化,因此可以自由使用?通过阅读JVMS,我想出了一个对象是否被初始化的答案,取决于每个方法。在某个时间点,可以为方法初始化对象,但不能为另一个方法初始化对象。具体而言,当该方法调用的<init>
返回时,对象将被初始化。
例如,考虑main()
方法创建了一个对象并调用<init>
,然后调用超类的<init>
。从super()
返回后,该对象现在被<init>
视为已初始化,但尚未针对main()
进行初始化。这是否意味着,在<init>
super()
之后,我可以将对象作为参数传递给方法,甚至在返回main()之前。
有人可以证实这整个分析是真的吗?感谢您的时间。
ps:我实际上已经向Sun论坛发布了同样的问题但是回复了。我希望我能在这里有更多的运气。谢谢。更新
首先感谢您的回答和时间。虽然我没有得到一个明确的答案(我有很多问题,其中一些有点模糊),但你的答案和例子,以及随后的实验,对于我更深入地了解JVM的工作方式非常有用。
我发现的主要问题是Verifier的行为因不同的实现和版本而不同(这使得字节码操作的工作变得更加复杂)。问题在于不符合JVMS,或者验证者的开发人员缺乏文档,或者JVMS在验证者的区域中有一些微妙的模糊性。
最后一件事,SO Rocks !!!我在官方的Sun JVM规范论坛上发布了同样的问题,直到现在我仍然没有答案。
答案 0 :(得分:4)
答案 1 :(得分:3)
与java语言指定的相反,在字节码级别, 可以在调用超类构造函数之前访问构造函数中的类的字段。以下代码使用asm库来创建这样的类:
package asmconstructortest;
import java.io.FileOutputStream;
import org.objectweb.asm.*;
import org.objectweb.asm.util.CheckClassAdapter;
import static org.objectweb.asm.Opcodes.*;
public class Main {
public static void main(String[] args) throws Exception {
//ASMifierClassVisitor.main(new String[]{"/Temp/Source/asmconstructortest/build/classes/asmconstructortest/Test.class"});
ClassWriter cw = new ClassWriter(0);
CheckClassAdapter ca = new CheckClassAdapter(cw);
ca.visit(V1_5, ACC_PUBLIC + ACC_SUPER, "asmconstructortest/Test2", null, "java/lang/Object", null);
{
FieldVisitor fv = ca.visitField(ACC_PUBLIC, "property", "I", null, null);
fv.visitEnd();
}
{
MethodVisitor mv = ca.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(ICONST_1);
mv.visitFieldInsn(PUTFIELD, "asmconstructortest/Test2", "property", "I");
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
mv.visitInsn(RETURN);
mv.visitMaxs(2, 1);
mv.visitEnd();
}
ca.visitEnd();
FileOutputStream out = new FileOutputStream("/Temp/Source/asmconstructortest/build/classes/asmconstructortest/Test2.class");
out.write(cw.toByteArray());
out.close();
}
}
实例化此类工作正常,没有任何验证错误:
package asmconstructortest;
public class Main2 {
public static void main(String[] args) {
Test2 test2 = new Test2();
System.out.println(test2.property);
}
}
答案 2 :(得分:1)
我建议您下载OpenJDK源代码的副本,并查看验证程序实际检查的内容。如果不出意外,这可能有助于您了解JMV规范所说的内容。
(但是,@ Joachim是对的。依赖于验证器实现的功能,而不是规范所说的是相当危险的。)