Каков процесс инициализации внутреннего класса java на уровне байтового кода? - PullRequest
1 голос
/ 02 августа 2020

Я написал агент java для инструментария commons-net с открытым исходным кодом. Но у меня были следующие ошибки:

java.lang.VerifyError: Bad type on operand stack
Exception Details:
  Location:
    org/apache/commons/net/util/SubnetUtils$SubnetInfo.<init>(Lorg/apache/commons/net/util/SubnetUtils;)V @9: invokestatic
  Reason:
    Type uninitializedThis (current frame, stack[2]) is not assignable to 'java/lang/Object'
  Current Frame:
    bci: @9
    flags: { flagThisUninit }
    locals: { uninitializedThis, 'org/apache/commons/net/util/SubnetUtils' }
    stack: { 'org/apache/commons/net/util/SubnetUtils', uninitializedThis, uninitializedThis }
  Bytecode:
    0x0000000: 12ca b800 cf2a 2b5f 59b8 00d3 5fb5 0002
    0x0000010: 2ab7 0003 b800 d6b1                    

    at...

и

Exception in thread "Thread-1" java.lang.VerifyError: Bad type on operand stack
Exception Details:
  Location:
    org/apache/commons/net/tftp/TFTPServer$TFTPTransfer.<init>(Lorg/apache/commons/net/tftp/TFTPServer;Lorg/apache/commons/net/tftp/TFTPPacket;)V @10: invokestatic
  Reason:
    Type uninitializedThis (current frame, stack[2]) is not assignable to 'java/lang/Object'
  Current Frame:
    bci: @10
    flags: { flagThisUninit }
    locals: { uninitializedThis, 'org/apache/commons/net/tftp/TFTPServer', 'org/apache/commons/net/tftp/TFTPPacket' }
    stack: { 'org/apache/commons/net/tftp/TFTPServer', uninitializedThis, uninitializedThis }
  Bytecode:
    0x0000000: 1301 84b8 0189 2a2b 5f59 b801 8d5f b500
    0x0000010: 012a b700 022a 035f 59b8 018d 5fb5 0003
    0x0000020: 2a01 5f59 b801 8d5f b500 042a 2c5f 59b8
    0x0000030: 018d 5fb5 0005 b801 90b1   

    at...

Что интересно, обе эти две ошибки возникают во внутреннем классе, и кадры стека очень похожи. Я предполагаю, что это просто происходит, когда внутренний класс инициализируется, потому что я вижу uninitializedThis в стеке.

Мой адаптер MethodVisitor очень длинный и сложный, поэтому я не публиковал его здесь. Я использую его для отслеживания событий method_start, method_end, new_object, object_modify, PUTFIELD, PUTSTATIC и т.д. c. Мне нужно записать измененный объект и новый объект, вызвав INVOKESTATIC одного из моих методов. Я комментирую вставленную инструкцию DUP и INVOKESTATIC после NEW, но она все равно сообщает о той же ошибке. Думаю, если бы я знал, как инициализируется внутренний класс, отладка была бы намного проще.

Исходный код этих двух <init> методов:

public class SubnetUtils {
    ...
    public final class SubnetInfo {
        /* Mask to convert unsigned int to a long (i.e. keep 32 bits) */
        private static final long UNSIGNED_INT_MASK = 0x0FFFFFFFFL;

        private SubnetInfo() {}
        ...
    }
    ...
}
public class TFTPServer implements Runnable
{
    ...
    private class TFTPTransfer implements Runnable
    {
        private final TFTPPacket tftpPacket_;

        private boolean shutdownTransfer = false;

        TFTP transferTftp_ = null;

        public TFTPTransfer(TFTPPacket tftpPacket)
        {
            tftpPacket_ = tftpPacket;
        }
        ...
    }
    ...
}

Я обнаружил ошибочный код в моем классе адаптера MethodVisitor. То есть, когда я комментирую этот код, кажется, что ошибка исчезла. Но я до сих пор не понимаю, почему произошла эта ошибка. У конструктора SubnetInfo, похоже, нет операции по изменению какого-либо поля. Думаю, может быть, это из-за каких-то скрытых функций для инициализации внутреннего класса?

    public void visitFieldInsn(int opc, String owner, String name, String desc){
        // Access Static Fields
        if ((opc == GETSTATIC || opc == PUTSTATIC) && shouldIncludeClass(owner)){
            mv.visitLdcInsn(owner);
            mv.visitMethodInsn(INVOKESTATIC, MyClass, MyMethod,
                    "(Ljava/lang/String;)V", false);
        }

        if (...){
            if (opc == PUTFIELD){
                Type t = Type.getType(desc);
                if (t.getSize() == 2){
                    mv.visitInsn(DUP2_X1);
                    mv.visitInsn(POP2);
                }else {
                    mv.visitInsn(SWAP);
                }
                mv.visitInsn(DUP);
                mv.visitMethodInsn(INVOKESTATIC, MyClass,
                        MyClass.TRACE_OBJ_MODIFY, "(Ljava/lang/Object;)V", false);
                if (t.getSize() == 2){
                    mv.visitInsn(DUP_X2);
                    mv.visitInsn(POP);
                }else {
                    mv.visitInsn(SWAP);
                }
            }else if (opc == PUTSTATIC){
                mv.visitMethodInsn(INVOKESTATIC, MyClass,
                        MyClass.TRACE_PUTSTATIC, "()V", false);
            }
        }
        mv.visitFieldInsn(opc, owner, name, desc);
    }

Я отследил только одну инструкцию PUTFIELD в конструкторе SubnetInfo. В моем журнале:

Transform: org/apache/commons/net/util/SubnetUtils$SubnetInfo
**** PUTFIELD found ****
**** className org/apache/commons/net/util/SubnetUtils$SubnetInfo ****
**** methodName <init> ****
**** opc PUTFIELD ****
**** owner org/apache/commons/net/util/SubnetUtils$SubnetInfo ****
**** name this$0 ****
**** desc Lorg/apache/commons/net/util/SubnetUtils; ****

Что означает эта инструкция PUTFIELD? Помещать объект внешнего класса в поле this$0 внутреннего класса? Есть ли способ определить инициализацию внутреннего класса?

...