上级目录

为什么会存在 Type 类

在 ASM 的代码中,有一个 Type 类(org.objectweb.asm.Type)。为什么会有这样一个 Type 类呢?

大家知道,在 JDK 当中有一个 java.lang.reflect.Type 类。对于 java.lang.reflect.Type 类来说,它是一个接口,它有一个我们经常使用的子类,即 java.lang.Class;相应的,在 ASM 当中有一个 org.objectweb.asm.Type 类。

JDK ASM
类名 java.lang.reflect.Type org.objectweb.asm.Type
位置 rt.jar asm.jar

在编写代码层面,如果我们不能区分出 java.lang.reflect.Type 类和 org.objectweb.asm.Type 类,我们也不能很好的使用它们。

  • Java File:具体表现为 .java 文件,在里面使用 Java 语言编写代码,它是属于 Java Language Specification 的范畴。
  • Class File:具体表现为 .class 文件,它里面的内容遵循 ClassFile 的结构,它是属于 JVM Specification 的范畴。
  • ASM:它是一个类库。我们在编写 ASM 代码的时候,是在 .java 文件中编写,使用的是 Java 语言,而它所操作的对象却是 .class 文件。

换句话说,ASM 实现,从本质上来说,是一只脚踩在 Java Language Specification 的范畴,而另一只脚却踩在 JVM Specification 的范畴。ASM,在这两个范畴中,扮演的一个非常重要的角色,就是将 Java Language Specification 范畴的概念和 JVM Specification 范畴的概念进行转换。

JLS 与 JVM 之间的关系

这两个范畴,是相关的,但是又不是那种密不可分的关系。比如说,Java 语言编写的程序可以运行在 JVM 上,Scala 语言编写的程序也可以运行在 JVM 上,甚至 Python 语言编写的程序也可以编写在 JVM 上;也就是说,某一种编程语言和 JVM 之间,并不是一种非常强的依赖关系。

 各种语言与 JVM 之间的关系

Java Language Specification ASM JVM Specification
int <--- 向左转换 ---Type--- 向右转换 ---> I
float <--- 向左转换 ---Type--- 向右转换 ---> F
java.lang.String <--- 向左转换 ---Type--- 向右转换 ---> java/lang/String

.java 文件中,我们经常使用 java.lang.Class 类;而在 .class 文件中,需要经常用到 internal name、type descriptor 和 method descriptor;而在 ASM 中,org.objectweb.asm.Type 类就是帮助我们进行两者之间的转换。

Type 类

class info

第一个部分,Type 类继承自 Object 类,而且带有 final 标识,所以不会存在子类。

public final class Type {
}

fields

第二个部分,Type 类定义的字段有哪些。这里我们列出了 4 个字段,这 4 个字段可以分成两组。

  • 第一组,只包括 sort 字段,是 int 类型,它标识了 Type 类的类别。
  • 第二组,包括 valueBuffervalueBeginvalueEnd 字段,这 3 个字段组合到一起表示一个 value 值,本质上就是一个字符串。
public final class Type {
    // 标识类型
    private final int sort;

    // 标识内容
    private final String valueBuffer;
    private final int valueBegin;
    private final int valueEnd;
}

constructors

第三个部分,Type 类定义的构造方法有哪些。由于 Type 类的构造方法用 private 修饰,因此“外界”不能使用 new 关键字创建 Type 对象。

public final class Type {
    private Type(final int sort, final String valueBuffer, final int valueBegin, final int valueEnd) {
        this.sort = sort;
        this.valueBuffer = valueBuffer;
        this.valueBegin = valueBegin;
        this.valueEnd = valueEnd;
    }
}

methods

第四个部分,Type 类定义的方法有哪些。在 Type 类里,定义了一些方法,这些方法是与字段有直接关系的。

public final class Type {
    public int getSort() {
        return sort == INTERNAL ? OBJECT : sort;
    }

    public String getClassName() {
        switch (sort) {
            case VOID:
                return "void";
            case BOOLEAN:
                return "boolean";
            case CHAR:
                return "char";
            case BYTE:
                return "byte";
            case SHORT:
                return "short";
            case INT:
                return "int";
            case FLOAT:
                return "float";
            case LONG:
                return "long";
            case DOUBLE:
                return "double";
            case ARRAY:
                StringBuilder stringBuilder = new StringBuilder(getElementType().getClassName());
                for (int i = getDimensions(); i > 0; --i) {
                    stringBuilder.append("[]");
                }
                return stringBuilder.toString();
            case OBJECT:
            case INTERNAL:
                return valueBuffer.substring(valueBegin, valueEnd).replace('/', '.');
            default:
                throw new AssertionError();
        }
    }

    public String getInternalName() {
        return valueBuffer.substring(valueBegin, valueEnd);
    }

    public String getDescriptor() {
        if (sort == OBJECT) {
            return valueBuffer.substring(valueBegin - 1, valueEnd + 1);
        } else if (sort == INTERNAL) {
            return 'L' + valueBuffer.substring(valueBegin, valueEnd) + ';';
        } else {
            return valueBuffer.substring(valueBegin, valueEnd);
        }
    }
}

关于这些方法的使用,示例如下:

import org.objectweb.asm.Type;

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Type t = Type.getType("Ljava/lang/String;");

        int sort = t.getSort();                    // ASM
        String className = t.getClassName();       // Java File
        String internalName = t.getInternalName(); // Class File
        String descriptor = t.getDescriptor();     // Class File

        System.out.println(sort);         // 10,它对应于 Type.OBJECT 字段
        System.out.println(className);    // java.lang.String   注意,分隔符是“.”
        System.out.println(internalName); // java/lang/String   注意,分隔符是“/”
        System.out.println(descriptor);   // Ljava/lang/String; 注意,分隔符是“/”,前有“L”,后有“;”
    }
}

静态成员

静态字段

Type 类里,定义了一些常量字段,有 int 类型,也有 String 类型。

public final class Type {
    public static final int VOID = 0;
    public static final int BOOLEAN = 1;
    public static final int CHAR = 2;
    public static final int BYTE = 3;
    public static final int SHORT = 4;
    public static final int INT = 5;
    public static final int FLOAT = 6;
    public static final int LONG = 7;
    public static final int DOUBLE = 8;
    public static final int ARRAY = 9;
    public static final int OBJECT = 10;
    public static final int METHOD = 11;
    private static final int INTERNAL = 12;

    private static final String PRIMITIVE_DESCRIPTORS = "VZCBSIFJD";
}

Type 类里,也定义了一些 Type 类型的字段,这些字段是由上面的 intString 类型的字段组合得到。

public final class Type {
    public static final Type VOID_TYPE = new Type(VOID, PRIMITIVE_DESCRIPTORS, VOID, VOID + 1);
    public static final Type BOOLEAN_TYPE = new Type(BOOLEAN, PRIMITIVE_DESCRIPTORS, BOOLEAN, BOOLEAN + 1);
    public static final Type CHAR_TYPE = new Type(CHAR, PRIMITIVE_DESCRIPTORS, CHAR, CHAR + 1);
    public static final Type BYTE_TYPE = new Type(BYTE, PRIMITIVE_DESCRIPTORS, BYTE, BYTE + 1);
    public static final Type SHORT_TYPE = new Type(SHORT, PRIMITIVE_DESCRIPTORS, SHORT, SHORT + 1);
    public static final Type INT_TYPE = new Type(INT, PRIMITIVE_DESCRIPTORS, INT, INT + 1);
    public static final Type FLOAT_TYPE = new Type(FLOAT, PRIMITIVE_DESCRIPTORS, FLOAT, FLOAT + 1);
    public static final Type LONG_TYPE = new Type(LONG, PRIMITIVE_DESCRIPTORS, LONG, LONG + 1);
    public static final Type DOUBLE_TYPE = new Type(DOUBLE, PRIMITIVE_DESCRIPTORS, DOUBLE, DOUBLE + 1);
}

静态方法

这里介绍的几个 get*Type() 方法,是静态(static)方法。这几个方法的主要目的就是得到一个 Type 对象。

public final class Type {
    public static Type getType(final Class clazz) {
        if (clazz.isPrimitive()) {
            if (clazz == Integer.TYPE) {
                return INT_TYPE;
            } else if (clazz == Void.TYPE) {
                return VOID_TYPE;
            } else if (clazz == Boolean.TYPE) {
                return BOOLEAN_TYPE;
            } else if (clazz == Byte.TYPE) {
                return BYTE_TYPE;
            } else if (clazz == Character.TYPE) {
                return CHAR_TYPE;
            } else if (clazz == Short.TYPE) {
                return SHORT_TYPE;
            } else if (clazz == Double.TYPE) {
                return DOUBLE_TYPE;
            } else if (clazz == Float.TYPE) {
                return FLOAT_TYPE;
            } else if (clazz == Long.TYPE) {
                return LONG_TYPE;
            } else {
                throw new AssertionError();
            }
        } else {
            return getType(getDescriptor(clazz));
        }
    }

    public static Type getType(final Constructor<?> constructor) {
        return getType(getConstructorDescriptor(constructor));
    }

    public static Type getType(final Method method) {
        return getType(getMethodDescriptor(method));
    }

    public static Type getType(final String typeDescriptor) {
        return getTypeInternal(typeDescriptor, 0, typeDescriptor.length());
    }

    public static Type getMethodType(final String methodDescriptor) {
        return new Type(METHOD, methodDescriptor, 0, methodDescriptor.length());
    }

    public static Type getObjectType(final String internalName) {
        return new Type(internalName.charAt(0) == '[' ? ARRAY : INTERNAL, internalName, 0, internalName.length());
    }
}

获取 Type 对象

Type 类有一个 private 的构造方法,因此 Type 对象实例不能通过 new 关键字来创建。但是,Type 类提供了 static method 和 static field 来获取对象。

方式一:java.lang.Class

从一个 java.lang.Class 对象来获取 Type 对象:

import org.objectweb.asm.Type;

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Type t = Type.getType(String.class);
        System.out.println(t);
    }
}

方式二:descriptor

从一个描述符(descriptor)来获取 Type 对象:

import org.objectweb.asm.Type;

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Type t1 = Type.getType("Ljava/lang/String;");
        System.out.println(t1);

        // 这里是方法的描述符
        Type t2 = Type.getMethodType("(II)I");
        System.out.println(t2);
    }
}

方式三:internal name

从一个 internal name 来获取 Type 对象:

import org.objectweb.asm.Type;

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Type t = Type.getObjectType("java/lang/String");
        System.out.println(t);
    }
}

方式四:static field

从一个 Type 类的静态字段来获取 Type 对象:

import org.objectweb.asm.Type;

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Type t = Type.INT_TYPE;
        System.out.println(t);
    }
}

特殊的方法

这里介绍的两个方法与数组类型相关:

  • getDimensions() 方法,用于获取数组的维度
  • getElementType() 方法,用于获取数组的元素的类型
public final class Type {
    public int getDimensions() {
        int numDimensions = 1;
        while (valueBuffer.charAt(valueBegin + numDimensions) == '[') {
            numDimensions++;
        }
        return numDimensions;
    }

    public Type getElementType() {
        final int numDimensions = getDimensions();
        return getTypeInternal(valueBuffer, valueBegin + numDimensions, valueEnd);
    }
}

示例代码:

import org.objectweb.asm.Type;

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Type t = Type.getType("[[[[[Ljava/lang/String;");

        int dimensions = t.getDimensions();
        Type elementType = t.getElementType();

        System.out.println(dimensions);    // 5
        System.out.println(elementType);   // Ljava/lang/String;
    }
}

这里介绍的两个方法与“方法”相关:

  • getArgumentTypes() 方法,用于获取“方法”接收的参数类型
  • getReturnType() 方法,用于获取“方法”返回值的类型
public final class Type {
    public Type[] getArgumentTypes() {
        return getArgumentTypes(getDescriptor());
    }

    public Type getReturnType() {
        return getReturnType(getDescriptor());
    }
}

示例代码:

import org.objectweb.asm.Type;

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Type methodType = Type.getMethodType("(Ljava/lang/String;I)V");

        String descriptor = methodType.getDescriptor();
        Type[] argumentTypes = methodType.getArgumentTypes();
        Type returnType = methodType.getReturnType();

        System.out.println("Descriptor: " + descriptor);
        System.out.println("Argument Types:");
        for (Type t : argumentTypes) {
            System.out.println("    " + t);
        }
        System.out.println("Return Type: " + returnType);
    }
}

输出结果:

Descriptor: (Ljava/lang/String;I)V
Argument Types:
    Ljava/lang/String;
    I
Return Type: V

这里列举的 3 个方法是与“类型占用 slot 空间的大小”相关:

  • getSize() 方法,用于返回某一个类型所占用的 slot 空间的大小。
  • getArgumentsAndReturnSizes() 方法,用于返回方法所对应的 slot 空间的大……
    • 其中,参数大小 int argumentsSize = argumentsAndReturnSizes >> 2;。注意,在 argumentsSize 当中包含了隐藏的 this 变量。
    • 其中,返回值大小 int returnSize = argumentsAndReturnSizes 0x03)
public final class Type {
    public int getSize() {
        switch (sort) {
            case VOID:
                return 0;
            case BOOLEAN:
            case CHAR:
            case BYTE:
            case SHORT:
            case INT:
            case FLOAT:
            case ARRAY:
            case OBJECT:
            case INTERNAL:
                return 1;
            case LONG:
            case DOUBLE:
                return 2;
            default:
                throw new AssertionError();
        }
    }

    public int getArgumentsAndReturnSizes() {
        return getArgumentsAndReturnSizes(getDescriptor());
    }

    public static int getArgumentsAndReturnSizes(final String methodDescriptor) {
        int argumentsSize = 1;
        // Skip the first character, which is always a '('.
        int currentOffset = 1;
        int currentChar = methodDescriptor.charAt(currentOffset);
        // Parse the argument types and compute their size, one at a each loop iteration.
        while (currentChar != ')') {
            if (currentChar == 'J' || currentChar == 'D') {
                currentOffset++;
                argumentsSize += 2;
            } else {
                while (methodDescriptor.charAt(currentOffset) == '[') {
                    currentOffset++;
                }
                if (methodDescriptor.charAt(currentOffset++) == 'L') {
                    // Skip the argument descriptor content.
                    int semiColumnOffset = methodDescriptor.indexOf(';', currentOffset);
                    currentOffset = Math.max(currentOffset, semiColumnOffset + 1);
                }
                argumentsSize += 1;
            }
            currentChar = methodDescriptor.charAt(currentOffset);
        }
        currentChar = methodDescriptor.charAt(currentOffset + 1);
        if (currentChar == 'V') {
            return argumentsSize << 2;
        } else {
            int returnSize = (currentChar == 'J' || currentChar == 'D') ? 2 : 1;
            return argumentsSize << 2 | returnSize;
        }
    }
}

示例代码:

import org.objectweb.asm.Type;

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Type t = Type.INT_TYPE;
        System.out.println(t.getSize()); // 1
    }
}
import org.objectweb.asm.Type;

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Type t = Type.LONG_TYPE;
        System.out.println(t.getSize()); // 2
    }
}
import org.objectweb.asm.Type;

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Type t = Type.getMethodType("(II)I");
        int value = t.getArgumentsAndReturnSizes();

        int argumentsSize = value >> 2;
        int returnSize = value & 0b11;

        System.out.println(argumentsSize); // 3
        System.out.println(returnSize);    // 1
    }
}

这里介绍的方法与 opcode 相关:

  • getOpcode(int opcode) 方法,会让我们写代码的过程中更加方便。

但是,值得注意的一点:参数 opcode 的取值是一些“指定的操作码(opcode)”,而不能随便乱写。 这些“指定的操作码(opcode)”在方法的注释中写明了,必须是 ILOAD, ISTORE, IALOAD, IASTORE, IADD, ISUB, IMUL, IDIV, IREM, INEG, ISHL, ISHR, IUSHR, IAND, IOR, IXORIRETURN 中的某一个值。

作为初学者,我们一定要注意 getOpcode(opcode) 方法参数值的有效性。 否则,它就会返回一个错误的结果,导致整个程序出现错误。 当我们去检查代码整体思路的时候,会觉得程序的代码逻辑上没有问题,但是生成的代码就是不能正确运行, 百思不得其解,很可能就是给 getOpcode(opcode) 方法传入了一个错误的参数值。

举个具体例子,有一次我给 getOpcode(opcode) 方法传入了 ICONST_0 值(这是一个错误的值), 我期望得到 FCONST_0DCONST_0,最终没有达到预期的效果。 因为 ICONST_0,对于 getOpcode(int opcode) 来说,不是一个合理的值。

public final class Type {
    /**
     * Returns a JVM instruction opcode adapted to this {@link Type}. This method must not be used for
     * method types.
     *
     * @param opcode a JVM instruction opcode. This opcode must be one of ILOAD, ISTORE, IALOAD,
     *     IASTORE, IADD, ISUB, IMUL, IDIV, IREM, INEG, ISHL, ISHR, IUSHR, IAND, IOR, IXOR and
     *     IRETURN.
     * @return an opcode that is similar to the given opcode, but adapted to this {@link Type}. For
     *     example, if this type is {@code float} and {@code opcode} is IRETURN, this method returns
     *     FRETURN.
     */
    public int getOpcode(final int opcode) {
        if (opcode == Opcodes.IALOAD || opcode == Opcodes.IASTORE) {
            switch (sort) {
                case BOOLEAN:
                case BYTE:
                    return opcode + (Opcodes.BALOAD - Opcodes.IALOAD);
                case CHAR:
                    return opcode + (Opcodes.CALOAD - Opcodes.IALOAD);
                case SHORT:
                    return opcode + (Opcodes.SALOAD - Opcodes.IALOAD);
                case INT:
                    return opcode;
                case FLOAT:
                    return opcode + (Opcodes.FALOAD - Opcodes.IALOAD);
                case LONG:
                    return opcode + (Opcodes.LALOAD - Opcodes.IALOAD);
                case DOUBLE:
                    return opcode + (Opcodes.DALOAD - Opcodes.IALOAD);
                case ARRAY:
                case OBJECT:
                case INTERNAL:
                    return opcode + (Opcodes.AALOAD - Opcodes.IALOAD);
                case METHOD:
                case VOID:
                    throw new UnsupportedOperationException();
                default:
                    throw new AssertionError();
            }
        } else {
            switch (sort) {
                case VOID:
                    if (opcode != Opcodes.IRETURN) {
                        throw new UnsupportedOperationException();
                    }
                    return Opcodes.RETURN;
                case BOOLEAN:
                case BYTE:
                case CHAR:
                case SHORT:
                case INT:
                    return opcode;
                case FLOAT:
                    return opcode + (Opcodes.FRETURN - Opcodes.IRETURN);
                case LONG:
                    return opcode + (Opcodes.LRETURN - Opcodes.IRETURN);
                case DOUBLE:
                    return opcode + (Opcodes.DRETURN - Opcodes.IRETURN);
                case ARRAY:
                case OBJECT:
                case INTERNAL:
                    if (opcode != Opcodes.ILOAD && opcode != Opcodes.ISTORE && opcode != Opcodes.IRETURN) {
                        throw new UnsupportedOperationException();
                    }
                    return opcode + (Opcodes.ARETURN - Opcodes.IRETURN);
                case METHOD:
                    throw new UnsupportedOperationException();
                default:
                    throw new AssertionError();
            }
        }
    }
}

示例代码:

import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.util.Printer;

public class HelloWorldRun {
    public static void main(String[] args) throws Exception {
        Type t = Type.FLOAT_TYPE;

        int[] opcodes = new int[]{
                Opcodes.IALOAD,
                Opcodes.IASTORE,
                Opcodes.ILOAD,
                Opcodes.ISTORE,
                Opcodes.IADD,
                Opcodes.ISUB,
                Opcodes.IRETURN,
        };

        for (int oldOpcode : opcodes) {
            int newOpcode = t.getOpcode(oldOpcode);

            String oldName = Printer.OPCODES[oldOpcode];
            String newName = Printer.OPCODES[newOpcode];

            System.out.printf("%-7s --- %-7s%n", oldName, newName);
        }
    }
}

输出结果:

IALOAD  --- FALOAD
IASTORE --- FASTORE
ILOAD   --- FLOAD
ISTORE  --- FSTORE
IADD    --- FADD
ISUB    --- FSUB
IRETURN --- FRETURN

总结

本文主要对 Type 类进行了介绍,内容总结如下:

  • 第一点,Type 类的作用是什么? Type 类是一个工具类,它的一个主要目的是将 Java 语言当中的概念转换成 ClassFile 当中的概念。
  • 第二点,学习 Type 类的方式就是“分而治之”。在 Type 类当中,定义了许多的字段和方法,它们是一个整体,内容也很繁杂;于是,我们将 Type 类分成不同的部分来讲解,就是希望大家能循序渐进的理解这个类的各个部分,方便以后对该类的使用。

当然,也不要求大家一下子把这个类的内容全部掌握,因为这里面的很多方法都是和 ClassFile 的结构密切相关的;如果大家对于 ClassFile 的结构不太了解,那么理解这些方法也会有一定的困难。总的来说,希望大家在以后使用的过程中,对这些方法慢慢熟悉起来。