前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java字节码修改库ASM#ClassReader实现原理及源码分析

Java字节码修改库ASM#ClassReader实现原理及源码分析

作者头像
JavaEdge
发布2020-05-27 11:26:29
2.1K0
发布2020-05-27 11:26:29
举报
文章被收录于专栏:JavaEdgeJavaEdge

1 概述

ASM是Java中比较流行的用来读写字节码的类库,用来基于字节码层面对代码进行分析和转换。

在读写的过程中可以加入自定义的逻辑以增强或修改原来已编译好的字节码,比如CGLIB用它来实现动态代理。ASM被设计用于在运行时对Java类进行生成和转换,当然也包括离线处理。ASM短小精悍、且速度很快,从而避免在运行时动态生成字节码或转换时对程序速度的影响,又因为它体积小巧,可以在很多内存受限的环境中使用。 ASM的主要优势包括如下几个方面:

  1. 它又一个很小,但设计良好并且模块化的API,且易于使用。
  2. 它具有很好的文档,并且还有eclipse插件。
  3. 它支持最新的Java版本。
  4. 它短小精悍、快速、健壮。
  5. 它又一个很大的用户社区,可以给新用户提供支持。
  6. 它的开源许可允许你几乎以任何方式来使用它。

2 ASM Core设计

主要有以下几个类、接口(在org.objectweb.asm包中):

  • ClassReader类:字节码的读取与分析引擎。它采用类似SAX的事件读取机制,每当有事件发生时,调用注册的ClassVisitor、AnnotationVisitor、FieldVisitor、MethodVisitor做相应的处理
  • ClassVisitor接口:定义在读取Class字节码时会触发的事件,如类头解析完成、注解解析、字段解析、方法解析等 解析器使ClassVisitor访问 JVMS 中定义的Class文件结构。 此类解析ClassFile内容,并为遇到的每个字段,方法和字节码指令调用给定ClassVisitor的适当访问方法。
  • AnnotationVisitor接口:定义在解析注解时会触发的事件,如解析到一个基本值类型的注解、enum值类型的注解、Array值类型的注解、注解值类型的注解等
  • FieldVisitor接口:定义在解析字段时触发的事件,如解析到字段上的注解、解析到字段相关的属性等。 MethodVisitor接口:定义在解析方法时触发的事件,如方法上的注解、属性、代码等。
  • ClassWriter类:它实现了ClassVisitor接口,用于拼接字节码。
  • AnnotationWriter类:它实现了AnnotationVisitor接口,用于拼接注解相关字节码
  • FieldWriter类:它实现了FieldVisitor接口,用于拼接字段相关字节码
  • MethodWriter类:它实现了MethodVisitor接口,用于拼接方法相关字节码。
  • SignatureReader类:对类定义、字段定义、方法定义、本地变量定义的签名的解析。Signature因范型引入,用于存储范型定义时的元数据(因为这些元数据在运行时会被擦除)。
  • SignatureVisitor接口:定义在解析Signature时会触发的事件,如正常的Type参数、类或接口的边界等。 SignatureWriter类:它实现了SignatureVisitor接口,用于拼接范型相关字节码。
  • Attribute类:字节码中属性的类抽象。
  • ByteVector类:字节码二进制存储的容器。
  • Opcodes接口:字节码指令的一些常量定义。
  • Type类:类型相关的常量定义以及一些基于其上的操作。

他们之间的类图关系如下: ClassReader是ASM中最核心的实现,它用于读取并解析Class字节码

在构建ClassReader实例时,它首先保存字节码二进制数组b,然后创建items数组,数组的长度在字节码数组的第8、9个字节指定(最前面4个字节是魔数CAFEBABE,之后2个字节是次版本号,再后2个字节是主版本号),每个item表示常量池项在字节码数组的偏移量加1(常量池中每个项由1个字节的type和紧跟的字节数组表示,常量池项有12种类型,其中CONSTANT_FieldRef_Info、CONSTANT_MethodRef_Info、CONSTANT_InterfaceMethodRef_Info、CONSTANT_NameAndType_Info包括其类型字节占用5个字节,另外4个字节每2个字节为字段、方法等所在的类、其名称、描述符在当前常量池中CONSTANT_Utf8_Info类型的引用;CONSTANT_Integer_Info、CONSTANT_Float_Info包括其类型字节占用5个字节,另外四个字节为其对应的值;CONSTANT_Class_Info、CONSTANT_String_Info包括其类型字节占用3个字节,另外两个字节为在当前常量池CONSTANT_Utf8_Info项的索引;CONSTANT_Utf8_Info类型第1个字节表示类型,第2、3个字节为该项所表示的字符串的长度);CONSTANT_Double_Info、CONSTANT_Long_Info加类型字节为9个字;maxStringLength表示最长的UTF8类型的常量池项的值,用于决定在解析CONSTANT_Utf8_Info类型项时最大需要的字符数组;header表示常量池之后的字节码的第一个字节。

ClassReader的属性

在 jacoco 中忽略 code 属性值

代码语言:javascript
复制
  /**
   * 跳过 Code 属性的标志。 如果设置了此标志,则不会解析也不访问Code属性。
   */
  public static final int SKIP_CODE = 1;

ClassReader#accept

在调用ClassReader的accept方法时,它解析字节码中常量池之后的所有元素

accept中开始 visitor 的各种visit方法的调用.

比如visit(访问类声明)、visitSource、visitOuterClass、visitInnerClass、readMethod(读取方法,让指定visitor访问)、readField、visitAnnotaiton、visitTypeAnnotation、visitAttribute、visitEnd,这些方法按一定顺序被执行。

在这里我们关注readMethod的调用。

代码语言:javascript
复制
  /**
   * 使给定的访问者访问传递给此 ClassReader 构造方法的JVMS Class 文件结构
   *
   * @param classVisitor 必须访问此类的访问者。
   * @param parsingOptions 用于解析此类的选项。 #SKIP_CODE #SKIP_DEBUG #SKIP_FRAMES或#EXPAND_FRAMES中的一个或多个
   */
  public void accept(final ClassVisitor classVisitor, final int parsingOptions) {
    accept(classVisitor, new Attribute[0], parsingOptions);
  }
代码语言:javascript
复制
  /**
   * @param attributePrototypes 在类访问期间必须解析的属性的原型.任何类型不等于原型类型的属性都不会被解析:其字节数组值也将一成不变地传递给 ClassWriter
   * 如果该值包含对常量池的引用,或者与由 reader 和 writer 之间的类适配器转换的类元素具有句法或语义链接,则可能会破坏它
   */
  public void accept(
      final ClassVisitor classVisitor,
      final Attribute[] attributePrototypes,
      final int parsingOptions) {
    Context context = new Context();
    context.attributePrototypes = attributePrototypes;
    context.parsingOptions = parsingOptions;
    context.charBuffer = new char[maxStringLength];

    // Read the access_flags, this_class, super_class, interface_count and interfaces fields.
    char[] charBuffer = context.charBuffer;
    int currentOffset = header;
    int accessFlags = readUnsignedShort(currentOffset);
    // 本类名
    String thisClass = readClass(currentOffset + 2, charBuffer);
    // 父类名
    String superClass = readClass(currentOffset + 4, charBuffer);
    String[] interfaces = new String[readUnsignedShort(currentOffset + 6)];
    currentOffset += 8;
    for (int i = 0; i < interfaces.length; ++i) {
      interfaces[i] = readClass(currentOffset, charBuffer);
      currentOffset += 2;
    }
    
    // 读取 class 属性(变量按照JVMS的4.7节的顺序排列)。
    // 属性偏移量不包括attribute_name_index和attribute_length字段。
    
    // InnerClasses 属性的偏移量,或0。
    int innerClassesOffset = 0;
    // EnclosingMethod 属性的偏移量, or 0.
    int enclosingMethodOffset = 0;
    // - The string corresponding to the Signature attribute, or null.
    String signature = null;
    // - The string corresponding to the SourceFile attribute, or null.
    String sourceFile = null;
    // - The string corresponding to the SourceDebugExtension attribute, or null.
    String sourceDebugExtension = null;
    // - The offset of the RuntimeVisibleAnnotations attribute, or 0.
    int runtimeVisibleAnnotationsOffset = 0;
    // - The offset of the RuntimeInvisibleAnnotations attribute, or 0.
    int runtimeInvisibleAnnotationsOffset = 0;
    // - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0.
    int runtimeVisibleTypeAnnotationsOffset = 0;
    // - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0.
    int runtimeInvisibleTypeAnnotationsOffset = 0;
    // - The offset of the Module attribute, or 0.
    int moduleOffset = 0;
    // - The offset of the ModulePackages attribute, or 0.
    int modulePackagesOffset = 0;
    // - The string corresponding to the ModuleMainClass attribute, or null.
    String moduleMainClass = null;
    // - The string corresponding to the NestHost attribute, or null.
    String nestHostClass = null;
    // - The offset of the NestMembers attribute, or 0.
    int nestMembersOffset = 0;
    // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field).
    //   This list in the <i>reverse order</i> or their order in the ClassFile structure.
    Attribute attributes = null;

    int currentAttributeOffset = getFirstAttributeOffset();
    for (int i = readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) {
      // Read the attribute_info's attribute_name and attribute_length fields.
      String attributeName = readUTF8(currentAttributeOffset, charBuffer);
      int attributeLength = readInt(currentAttributeOffset + 2);
      currentAttributeOffset += 6;
      // The tests are sorted in decreasing frequency order (based on frequencies observed on
      // typical classes).
      if (Constants.SOURCE_FILE.equals(attributeName)) {
        sourceFile = readUTF8(currentAttributeOffset, charBuffer);
      } else if (Constants.INNER_CLASSES.equals(attributeName)) {
        innerClassesOffset = currentAttributeOffset;
      } else if (Constants.ENCLOSING_METHOD.equals(attributeName)) {
        enclosingMethodOffset = currentAttributeOffset;
      } else if (Constants.NEST_HOST.equals(attributeName)) {
        nestHostClass = readClass(currentAttributeOffset, charBuffer);
      } else if (Constants.NEST_MEMBERS.equals(attributeName)) {
        nestMembersOffset = currentAttributeOffset;
      } else if (Constants.SIGNATURE.equals(attributeName)) {
        signature = readUTF8(currentAttributeOffset, charBuffer);
      } else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) {
        runtimeVisibleAnnotationsOffset = currentAttributeOffset;
      } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
        runtimeVisibleTypeAnnotationsOffset = currentAttributeOffset;
      } else if (Constants.DEPRECATED.equals(attributeName)) {
        accessFlags |= Opcodes.ACC_DEPRECATED;
      } else if (Constants.SYNTHETIC.equals(attributeName)) {
        accessFlags |= Opcodes.ACC_SYNTHETIC;
      } else if (Constants.SOURCE_DEBUG_EXTENSION.equals(attributeName)) {
        sourceDebugExtension =
            readUtf(currentAttributeOffset, attributeLength, new char[attributeLength]);
      } else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) {
        runtimeInvisibleAnnotationsOffset = currentAttributeOffset;
      } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
        runtimeInvisibleTypeAnnotationsOffset = currentAttributeOffset;
      } else if (Constants.MODULE.equals(attributeName)) {
        moduleOffset = currentAttributeOffset;
      } else if (Constants.MODULE_MAIN_CLASS.equals(attributeName)) {
        moduleMainClass = readClass(currentAttributeOffset, charBuffer);
      } else if (Constants.MODULE_PACKAGES.equals(attributeName)) {
        modulePackagesOffset = currentAttributeOffset;
      } else if (!Constants.BOOTSTRAP_METHODS.equals(attributeName)) {
        // The BootstrapMethods attribute is read in the constructor.
        Attribute attribute =
            readAttribute(
                attributePrototypes,
                attributeName,
                currentAttributeOffset,
                attributeLength,
                charBuffer,
                -1,
                null);
        attribute.nextAttribute = attributes;
        attributes = attribute;
      }
      currentAttributeOffset += attributeLength;
    }

    // Visit class declaration. The minor_version and major_version fields start 6 bytes before
    // the first constant pool entry, which itself starts at cpInfoOffsets[1] - 1 (by definition).
    classVisitor.visit(
        readInt(cpInfoOffsets[1] - 7), accessFlags, thisClass, signature, superClass, interfaces);

    // Visit the SourceFile and SourceDebugExtenstion attributes.
    if ((parsingOptions & SKIP_DEBUG) == 0
        && (sourceFile != null || sourceDebugExtension != null)) {
      classVisitor.visitSource(sourceFile, sourceDebugExtension);
    }

    // Visit the Module, ModulePackages and ModuleMainClass attributes.
    if (moduleOffset != 0) {
      readModuleAttributes(
          classVisitor, context, moduleOffset, modulePackagesOffset, moduleMainClass);
    }

    // Visit the NestHost attribute.
    if (nestHostClass != null) {
      classVisitor.visitNestHost(nestHostClass);
    }

    // Visit the EnclosingMethod attribute.
    if (enclosingMethodOffset != 0) {
      String className = readClass(enclosingMethodOffset, charBuffer);
      int methodIndex = readUnsignedShort(enclosingMethodOffset + 2);
      String name = methodIndex == 0 ? null : readUTF8(cpInfoOffsets[methodIndex], charBuffer);
      String type = methodIndex == 0 ? null : readUTF8(cpInfoOffsets[methodIndex] + 2, charBuffer);
      classVisitor.visitOuterClass(className, name, type);
    }

    // Visit the RuntimeVisibleAnnotations attribute.
    if (runtimeVisibleAnnotationsOffset != 0) {
      int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset);
      int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2;
      while (numAnnotations-- > 0) {
        // Parse the type_index field.
        String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
        currentAnnotationOffset += 2;
        // Parse num_element_value_pairs and element_value_pairs and visit these values.
        currentAnnotationOffset =
            readElementValues(
                classVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true),
                currentAnnotationOffset,
                /* named = */ true,
                charBuffer);
      }
    }

    // Visit the RuntimeInvisibleAnnotations attribute.
    if (runtimeInvisibleAnnotationsOffset != 0) {
      int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset);
      int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2;
      while (numAnnotations-- > 0) {
        // Parse the type_index field.
        String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
        currentAnnotationOffset += 2;
        // Parse num_element_value_pairs and element_value_pairs and visit these values.
        currentAnnotationOffset =
            readElementValues(
                classVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false),
                currentAnnotationOffset,
                /* named = */ true,
                charBuffer);
      }
    }

    // Visit the RuntimeVisibleTypeAnnotations attribute.
    if (runtimeVisibleTypeAnnotationsOffset != 0) {
      int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset);
      int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2;
      while (numAnnotations-- > 0) {
        // Parse the target_type, target_info and target_path fields.
        currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
        // Parse the type_index field.
        String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
        currentAnnotationOffset += 2;
        // Parse num_element_value_pairs and element_value_pairs and visit these values.
        currentAnnotationOffset =
            readElementValues(
                classVisitor.visitTypeAnnotation(
                    context.currentTypeAnnotationTarget,
                    context.currentTypeAnnotationTargetPath,
                    annotationDescriptor,
                    /* visible = */ true),
                currentAnnotationOffset,
                /* named = */ true,
                charBuffer);
      }
    }

    // Visit the RuntimeInvisibleTypeAnnotations attribute.
    if (runtimeInvisibleTypeAnnotationsOffset != 0) {
      int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset);
      int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2;
      while (numAnnotations-- > 0) {
        // Parse the target_type, target_info and target_path fields.
        currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
        // Parse the type_index field.
        String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
        currentAnnotationOffset += 2;
        // Parse num_element_value_pairs and element_value_pairs and visit these values.
        currentAnnotationOffset =
            readElementValues(
                classVisitor.visitTypeAnnotation(
                    context.currentTypeAnnotationTarget,
                    context.currentTypeAnnotationTargetPath,
                    annotationDescriptor,
                    /* visible = */ false),
                currentAnnotationOffset,
                /* named = */ true,
                charBuffer);
      }
    }

    // Visit the non standard attributes.
    while (attributes != null) {
      // Copy and reset the nextAttribute field so that it can also be used in ClassWriter.
      Attribute nextAttribute = attributes.nextAttribute;
      attributes.nextAttribute = null;
      classVisitor.visitAttribute(attributes);
      attributes = nextAttribute;
    }

    // Visit the NestedMembers attribute.
    if (nestMembersOffset != 0) {
      int numberOfNestMembers = readUnsignedShort(nestMembersOffset);
      int currentNestMemberOffset = nestMembersOffset + 2;
      while (numberOfNestMembers-- > 0) {
        classVisitor.visitNestMember(readClass(currentNestMemberOffset, charBuffer));
        currentNestMemberOffset += 2;
      }
    }

    // Visit the InnerClasses attribute.
    if (innerClassesOffset != 0) {
      int numberOfClasses = readUnsignedShort(innerClassesOffset);
      int currentClassesOffset = innerClassesOffset + 2;
      while (numberOfClasses-- > 0) {
        classVisitor.visitInnerClass(
            readClass(currentClassesOffset, charBuffer),
            readClass(currentClassesOffset + 2, charBuffer),
            readUTF8(currentClassesOffset + 4, charBuffer),
            readUnsignedShort(currentClassesOffset + 6));
        currentClassesOffset += 8;
      }
    }

    // 访问字段和方法
    int fieldsCount = readUnsignedShort(currentOffset);
    currentOffset += 2;
    while (fieldsCount-- > 0) {
      currentOffset = readField(classVisitor, context, currentOffset);
    }
    int methodsCount = readUnsignedShort(currentOffset);
    currentOffset += 2;
    while (methodsCount-- > 0) {
      // 读取方法
      currentOffset = readMethod(classVisitor, context, currentOffset);
    }

    // Visit the end of the class.
    classVisitor.visitEnd();
  }

紧接着常量池的2个字节是该类的access标签:ACC_PUBLIC、ACC_FINAL等 之后2个字节为当前类名在常量池CONSTANT_Utf8_Info类型的索引 之后2个字节为其父类名在常量池CONSTANT_Utf8_Info类型的索引(索引值0表示父类为null,即直接继承自Object类) 再之后为其实现的接口数长度和对应各个接口名在常量池中CONSTANT_Utf8_Info类型的索引值 暂时先跳过Field和Method定义信息,解析类的attribute表,它用两个字节表达attribute数组的长度,每个attribute项中最前面2个字节是attribute名称:

  • SourceFile(读取sourceFile值)
  • InnerClasses(暂时纪录起始索引)
  • EnclosingMethod(记录当前匿名类、本地类包含者类名以及包含者的方法名和描述符)
  • Signature(类的签名信息,用于范型)
  • RuntimeVisibleAnnotations(暂时纪录起始索引)
  • Deprecated(表识属性)
  • Synthetic(标识属性)
  • SourceDebugExtension(为调试器提供的自定义扩展信息,读取成一个字符串)
  • RuntimeInvisibleAnnotations(暂时纪录起始索引)

readMethod

JVMS

代码语言:javascript
复制
method_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}
代码语言:javascript
复制
  /**
   * 读取 JVMS method_info 结构并使得给定的 visitor 访问之.
   *
   * @param classVisitor the visitor that must visit the method.
   * @param context information about the class being parsed.
   * @param methodInfoOffset the start offset of the method_info structure.
   * @return the offset of the first byte following the method_info structure.
   */
  private int readMethod(
      final ClassVisitor classVisitor, final Context context, final int methodInfoOffset) {
    char[] charBuffer = context.charBuffer;

    // Read the access_flags, name_index and descriptor_index fields.
    int currentOffset = methodInfoOffset;
    context.currentMethodAccessFlags = readUnsignedShort(currentOffset);
    context.currentMethodName = readUTF8(currentOffset + 2, charBuffer);
    context.currentMethodDescriptor = readUTF8(currentOffset + 4, charBuffer);
    currentOffset += 6;

    // 读方法属性attributes ( Section 4.7 of the JVMS).
    // Attribute offsets exclude the attribute_name_index and attribute_length fields.
    // - The offset of the Code attribute, or 0.
    int codeOffset = 0;
    // - The offset of the Exceptions attribute, or 0.
    int exceptionsOffset = 0;
    // - The strings corresponding to the Exceptions attribute, or null.
    String[] exceptions = null;
    // - Whether the method has a Synthetic attribute.
    boolean synthetic = false;
    // - The constant pool index contained in the Signature attribute, or 0.
    int signatureIndex = 0;
    // - The offset of the RuntimeVisibleAnnotations attribute, or 0.
    int runtimeVisibleAnnotationsOffset = 0;
    // - The offset of the RuntimeInvisibleAnnotations attribute, or 0.
    int runtimeInvisibleAnnotationsOffset = 0;
    // - The offset of the RuntimeVisibleParameterAnnotations attribute, or 0.
    int runtimeVisibleParameterAnnotationsOffset = 0;
    // - The offset of the RuntimeInvisibleParameterAnnotations attribute, or 0.
    int runtimeInvisibleParameterAnnotationsOffset = 0;
    // - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0.
    int runtimeVisibleTypeAnnotationsOffset = 0;
    // - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0.
    int runtimeInvisibleTypeAnnotationsOffset = 0;
    // - The offset of the AnnotationDefault attribute, or 0.
    int annotationDefaultOffset = 0;
    // - The offset of the MethodParameters attribute, or 0.
    int methodParametersOffset = 0;
    // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field).
    //   This list in the <i>reverse order</i> or their order in the ClassFile structure.
    Attribute attributes = null;

    int attributesCount = readUnsignedShort(currentOffset);
    currentOffset += 2;
    while (attributesCount-- > 0) {
      // Read the attribute_info's attribute_name and attribute_length fields.
      String attributeName = readUTF8(currentOffset, charBuffer);
      int attributeLength = readInt(currentOffset + 2);
      currentOffset += 6;
      // The tests are sorted in decreasing frequency order (based on frequencies observed on
      // typical classes).
      if (Constants.CODE.equals(attributeName)) {
        if ((context.parsingOptions & SKIP_CODE) == 0) {
          codeOffset = currentOffset;
        }
      } else if (Constants.EXCEPTIONS.equals(attributeName)) {
        exceptionsOffset = currentOffset;
        exceptions = new String[readUnsignedShort(exceptionsOffset)];
        int currentExceptionOffset = exceptionsOffset + 2;
        for (int i = 0; i < exceptions.length; ++i) {
          exceptions[i] = readClass(currentExceptionOffset, charBuffer);
          currentExceptionOffset += 2;
        }
      } else if (Constants.SIGNATURE.equals(attributeName)) {
        signatureIndex = readUnsignedShort(currentOffset);
      } else if (Constants.DEPRECATED.equals(attributeName)) {
        context.currentMethodAccessFlags |= Opcodes.ACC_DEPRECATED;
      } else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) {
        runtimeVisibleAnnotationsOffset = currentOffset;
      } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
        runtimeVisibleTypeAnnotationsOffset = currentOffset;
      } else if (Constants.ANNOTATION_DEFAULT.equals(attributeName)) {
        annotationDefaultOffset = currentOffset;
      } else if (Constants.SYNTHETIC.equals(attributeName)) {
        synthetic = true;
        context.currentMethodAccessFlags |= Opcodes.ACC_SYNTHETIC;
      } else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) {
        runtimeInvisibleAnnotationsOffset = currentOffset;
      } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
        runtimeInvisibleTypeAnnotationsOffset = currentOffset;
      } else if (Constants.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS.equals(attributeName)) {
        runtimeVisibleParameterAnnotationsOffset = currentOffset;
      } else if (Constants.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS.equals(attributeName)) {
        runtimeInvisibleParameterAnnotationsOffset = currentOffset;
      } else if (Constants.METHOD_PARAMETERS.equals(attributeName)) {
        methodParametersOffset = currentOffset;
      } else {
        Attribute attribute =
            readAttribute(
                context.attributePrototypes,
                attributeName,
                currentOffset,
                attributeLength,
                charBuffer,
                -1,
                null);
        attribute.nextAttribute = attributes;
        attributes = attribute;
      }
      currentOffset += attributeLength;
    }

    // Visit the method declaration.
    MethodVisitor methodVisitor =
        classVisitor.visitMethod(
            context.currentMethodAccessFlags,
            context.currentMethodName,
            context.currentMethodDescriptor,
            signatureIndex == 0 ? null : readUtf(signatureIndex, charBuffer),
            exceptions);
    if (methodVisitor == null) {
      return currentOffset;
    }

    // If the returned MethodVisitor is in fact a MethodWriter, it means there is no method
    // adapter between the reader and the writer. In this case, it might be possible to copy
    // the method attributes directly into the writer. If so, return early without visiting
    // the content of these attributes.
    if (methodVisitor instanceof MethodWriter) {
      MethodWriter methodWriter = (MethodWriter) methodVisitor;
      if (methodWriter.canCopyMethodAttributes(
          this,
          synthetic,
          (context.currentMethodAccessFlags & Opcodes.ACC_DEPRECATED) != 0,
          readUnsignedShort(methodInfoOffset + 4),
          signatureIndex,
          exceptionsOffset)) {
        methodWriter.setMethodAttributesSource(methodInfoOffset, currentOffset - methodInfoOffset);
        return currentOffset;
      }
    }

    // Visit the MethodParameters attribute.
    if (methodParametersOffset != 0) {
      int parametersCount = readByte(methodParametersOffset);
      int currentParameterOffset = methodParametersOffset + 1;
      while (parametersCount-- > 0) {
        // Read the name_index and access_flags fields and visit them.
        methodVisitor.visitParameter(
            readUTF8(currentParameterOffset, charBuffer),
            readUnsignedShort(currentParameterOffset + 2));
        currentParameterOffset += 4;
      }
    }

    // Visit the AnnotationDefault attribute.
    if (annotationDefaultOffset != 0) {
      AnnotationVisitor annotationVisitor = methodVisitor.visitAnnotationDefault();
      readElementValue(annotationVisitor, annotationDefaultOffset, null, charBuffer);
      if (annotationVisitor != null) {
        annotationVisitor.visitEnd();
      }
    }

    // Visit the RuntimeVisibleAnnotations attribute.
    if (runtimeVisibleAnnotationsOffset != 0) {
      int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset);
      int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2;
      while (numAnnotations-- > 0) {
        // Parse the type_index field.
        String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
        currentAnnotationOffset += 2;
        // Parse num_element_value_pairs and element_value_pairs and visit these values.
        currentAnnotationOffset =
            readElementValues(
                methodVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true),
                currentAnnotationOffset,
                /* named = */ true,
                charBuffer);
      }
    }

    // Visit the RuntimeInvisibleAnnotations attribute.
    if (runtimeInvisibleAnnotationsOffset != 0) {
      int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset);
      int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2;
      while (numAnnotations-- > 0) {
        // Parse the type_index field.
        String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
        currentAnnotationOffset += 2;
        // Parse num_element_value_pairs and element_value_pairs and visit these values.
        currentAnnotationOffset =
            readElementValues(
                methodVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false),
                currentAnnotationOffset,
                /* named = */ true,
                charBuffer);
      }
    }

    // Visit the RuntimeVisibleTypeAnnotations attribute.
    if (runtimeVisibleTypeAnnotationsOffset != 0) {
      int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset);
      int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2;
      while (numAnnotations-- > 0) {
        // Parse the target_type, target_info and target_path fields.
        currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
        // Parse the type_index field.
        String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
        currentAnnotationOffset += 2;
        // Parse num_element_value_pairs and element_value_pairs and visit these values.
        currentAnnotationOffset =
            readElementValues(
                methodVisitor.visitTypeAnnotation(
                    context.currentTypeAnnotationTarget,
                    context.currentTypeAnnotationTargetPath,
                    annotationDescriptor,
                    /* visible = */ true),
                currentAnnotationOffset,
                /* named = */ true,
                charBuffer);
      }
    }

    // Visit the RuntimeInvisibleTypeAnnotations attribute.
    if (runtimeInvisibleTypeAnnotationsOffset != 0) {
      int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset);
      int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2;
      while (numAnnotations-- > 0) {
        // Parse the target_type, target_info and target_path fields.
        currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
        // Parse the type_index field.
        String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
        currentAnnotationOffset += 2;
        // Parse num_element_value_pairs and element_value_pairs and visit these values.
        currentAnnotationOffset =
            readElementValues(
                methodVisitor.visitTypeAnnotation(
                    context.currentTypeAnnotationTarget,
                    context.currentTypeAnnotationTargetPath,
                    annotationDescriptor,
                    /* visible = */ false),
                currentAnnotationOffset,
                /* named = */ true,
                charBuffer);
      }
    }

    // Visit the RuntimeVisibleParameterAnnotations attribute.
    if (runtimeVisibleParameterAnnotationsOffset != 0) {
      readParameterAnnotations(
          methodVisitor, context, runtimeVisibleParameterAnnotationsOffset, /* visible = */ true);
    }

    // Visit the RuntimeInvisibleParameterAnnotations attribute.
    if (runtimeInvisibleParameterAnnotationsOffset != 0) {
      readParameterAnnotations(
          methodVisitor,
          context,
          runtimeInvisibleParameterAnnotationsOffset,
          /* visible = */ false);
    }

    // Visit the non standard attributes.
    while (attributes != null) {
      // Copy and reset the nextAttribute field so that it can also be used in MethodWriter.
      Attribute nextAttribute = attributes.nextAttribute;
      attributes.nextAttribute = null;
      methodVisitor.visitAttribute(attributes);
      attributes = nextAttribute;
    }

    // Visit the Code attribute.
    if (codeOffset != 0) {
      methodVisitor.visitCode();
      readCode(methodVisitor, context, codeOffset);
    }

    // Visit the end of the method.
    methodVisitor.visitEnd();
    return currentOffset;
  }

对其他不识别的属性,纪录成Attribute链,如果attribute名称符合在accept中attribute数组中指定的attribute名,则替换传入的attribute数组对应的项;根据解析出来的信息调用以下visit方法:

代码语言:javascript
复制
void visit(int version, int access, String name, String signature, String superName, String[] interfaces);
// sourceFile, sourceDebug
void visitSource(String source, String debug);
// EnclosingMethod attribute: enclosingOwner, enclosingName, enclosingDesc. 
// Note: only when the class has EnclosingMethod attribute, meaning the class is a local class or an anonymous class
void visitOuterClass(String owner, String name, String desc);

依次解析RuntimeVisibleAnnotations和RuntimeInvisibleAnnotations属性,首先解析定义的Annotation的描述符以及运行时可见flag,返回用户自定义的AnnotationVisitor: AnnotationVisitor visitAnnotation(String desc, boolean visible); 对每个定义的Annotation,解析其键值对,并根据不同的Annotation字段值调用AnnotationVisitor中的方法,在所有解析结束后,调用AnnotationVisitor.visitEnd方法:

代码语言:javascript
复制
public interface AnnotationVisitor {
    // 对基本类型的数组,依然采用该方法,visitArray只是在非基本类型时调用。
    void visit(String name, Object value);
    void visitEnum(String name, String desc, String value);
    AnnotationVisitor visitAnnotation(String name, String desc);
    AnnotationVisitor visitArray(String name);

    void visitEnd();
}

之前解析出的attribute链表(非标准的Attribute定义),对每个Attribute实例,调用ClassVisitor中的visitAttribute方法:

代码语言:javascript
复制
void visitAttribute(Attribute attr);
Attribute类包含type字段和一个字节数组:
public class Attribute {
    public final String type;
    byte[] value;
    Attribute next;
}

对每个InnerClasses属性,解析并调用ClassVisitor的visitInnerClass方法(该属性事实上保存了所有其直接内部类以及它本身到最顶层类的路径): void visitInnerClass(String name, String outerName, String innerName, int access);

解析字段,它紧跟接口数组定义之后,最前面的2个字节为字段数组的长度,对每个字段,前面2个字节为访问flag定义,再后2个字节为Name索引,以及2个字节的描述符索引,然后解析其Attribute信息:ConstantValue、Signature、Deprecated、Synthetic、RuntimeVisibleAnnotations、RuntimeInvisibleAnnotations以及非标准定义的Attribute链,而后调用ClassVisitor的visitField方法,返回FieldVisitor实例: // 其中value为静态字段的初始化值(对非静态字段,它的初始化必须由构造函数实现),如果没有初始化值,该值为null。 FieldVisitor visitField(int access, String name, String desc, String signature, Object value); 对返回的FieldVisitor依次对其Annotation以及非标准Attribute解析,调用其visit方法,并在完成后调用它的visitEnd方法:

代码语言:javascript
复制
public interface FieldVisitor {
    AnnotationVisitor visitAnnotation(String desc, boolean visible);
    void visitAttribute(Attribute attr);
    void visitEnd();
}

解析方法定义,它紧跟字段定义之后,最前面的2个字节为方法数组长度,对每个方法,前面2个字节为访问flag定义,再后2个字节为Name索引,以及2个字节的方法描述符索引,然后解析其Attribute信息:Code、Exceptions、Signature、Deprecated、RuntimeVisibleAnnotations、AnnotationDefault、Synthetic、RuntimeInvisibleAnnotations、RuntimeVisibleParameterAnnotations、RuntimeInvisibleParameterAnnotations以及非标准定义的Attribute链,如果存在Exceptions属性,解析其异常类数组,之后调用

ClassVisitor#visitMethod方法

返回MethodVisitor实例:

代码语言:javascript
复制
  /**
   * 访问类的方法.
   * 每次调用此方法时,都必须返回一个新的 MethodVisitor实例(或null)
   * 即它不应返回以前返回的visitor
   *
   * @param access the method's access flags (see {@link Opcodes}). This parameter also indicates if
   *     the method is synthetic and/or deprecated.
   * @param name the method's name.
   * @param descriptor the method's descriptor (see {@link Type}).
   * @param signature the method's signature. May be {@literal null} if the method parameters,
   *     return type and exceptions do not use generic types.
   * @param exceptions the internal names of the method's exception classes (see {@link
   *     Type#getInternalName()}). May be {@literal null}.
   * @return an object to visit the byte code of the method, or {@literal null} if this class
   *     visitor is not interested in visiting the code of this method.
   */
  public MethodVisitor visitMethod(
      final int access,
      final String name,
      final String descriptor,
      final String signature,
      final String[] exceptions) {
    if (cv != null) {
      return cv.visitMethod(access, name, descriptor, signature, exceptions);
    }
    return null;
  }

AnnotationDefault为对Annotation定义时指定默认值的解析;然后依次解析RuntimeVisibleAnnotations、RuntimeInvisibleAnnotations、RuntimeVisibleParameterAnnotations、RuntimeInvisibleParameterAnnotations等属性,调用相关AnnotationVisitor的visit方法;对非标准定义的Attribute链,依次调用MethodVisitor的visitAttribute方法:

代码语言:javascript
复制
public interface MethodVisitor {
    AnnotationVisitor visitAnnotationDefault();
    AnnotationVisitor visitAnnotation(String desc, boolean visible);
    AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible);
    void visitAttribute(Attribute attr);
}

对Code属性解析,读取2个字节的最深栈大小、最大local变量数、code占用字节数,调用MethodVisitor的visitCode()方法表示开始解析Code属性,对每条指令,创建一个Label实例并构成Label数组,解析Code属性中的异常表,对每个异常项,调用visitTryCatchBlock方法: void visitTryCatchBlock(Label start, Label end, Label handler, String type); Label包含以下信息:

代码语言:javascript
复制
/**
 * A label represents a position in the bytecode of a method. Labels are used
 * for jump, goto, and switch instructions, and for try catch blocks.
 * 
 * @author Eric Bruneton
 */
public class Label {
    public Object info;
    int status;
    int line;
    int position;
    private int referenceCount;
    private int[] srcAndRefPositions;
    int inputStackTop;
    int outputStackMax;
    Frame frame;
    Label successor;
    Edge successors;
    Label next;
}

解析Code属性中的内部属性信息:LocalVariableTable、LocalVariableTypeTable、LineNumberTable、StackMapTable、StackMap以及非标准定义的Attribute链,对每个Label调用其visitLineNumber方法以及对每个Frame调用visitFrame方法,并且对相应的指令调用相应的方法:

代码语言:javascript
复制
void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack);
// Visits a zero operand instruction.
void visitInsn(int opcode);
// Visits an instruction with a single int operand.
void visitIntInsn(int opcode, int operand);
// Visits a local variable instruction. A local variable instruction is an instruction that loads or stores the value of a local variable.
void visitVarInsn(int opcode, int var);
// Visits a type instruction. A type instruction is an instruction that takes the internal name of a class as parameter.
void visitTypeInsn(int opcode, String type);
// Visits a field instruction. A field instruction is an instruction that loads or stores the value of a field of an object.
void visitFieldInsn(int opcode, String owner, String name, String desc);
// Visits a method instruction. A method instruction is an instruction that invokes a method.
void visitMethodInsn(int opcode, String owner, String name, String desc);
// Visits a jump instruction. A jump instruction is an instruction that may jump to another instruction.
void visitJumpInsn(int opcode, Label label);
// Visits a label. A label designates the instruction that will be visited just after it.
void visitLabel(Label label);
// Visits a LDC instruction.
void visitLdcInsn(Object cst);
// Visits an IINC instruction.
void visitIincInsn(int var, int increment);
// Visits a TABLESWITCH instruction.
void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels);
// Visits a LOOKUPSWITCH instruction.
void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels);
// Visits a MULTIANEWARRAY instruction.
void visitMultiANewArrayInsn(String desc, int dims);
// Visits a try catch block.
void visitTryCatchBlock(Label start, Label end, Label handler, String type);
void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index);
// Visits a line number declaration.
void visitLineNumber(int line, Label start);
// Visits the maximum stack size and the maximum number of local variables of the method.
void visitMaxs(int maxStack, int maxLocals);

最后调用ClassVisitor的visitEnd方法: void visitEnd();

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-03-31 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 概述
  • 2 ASM Core设计
  • ClassReader的属性
  • ClassReader#accept
    • readMethod
      • ClassVisitor#visitMethod方法
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档