对字节码和对象的澄清

时间:2010-07-19 06:27:29

标签: java jvm object bytecode verification

我正在写一个字节码指导员。现在,我试图找出在物体存在的情况下如何做到这一点。我想对我在JVMS中阅读的两行(第4.9.4节)做一些澄清:

  

1)“验证者拒绝使用新对象之前的代码   初始化“。

我的问题是,“使用”在这里意味着什么?我猜这意味着:将其作为方法属性传递,在其上调用GETFIELDPUTFIELD,或者在其上调用任何实例方法。他们的其他禁止用途是什么?我相信,我们可以使用DUPLOADSTORE等其他说明。

  

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规范论坛上发布了同样的问题,直到现在我仍然没有答案。

3 个答案:

答案 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是对的。依赖于验证器实现的功能,而不是规范所说的是相当危险的。)