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

简介: Java字节码修改库ASM#ClassReader实现原理及源码分析(上)

ClassReader的属性

在 jacoco 中忽略 code 属性值

  /**
   * 跳过 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的调用。

  /**
   * 使给定的访问者访问传递给此 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);
  }

34.png

  /**
   * @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(暂时纪录起始索引)


目录
相关文章
|
7月前
|
自然语言处理 前端开发 算法
Java编译器优化秘籍:字节码背后的IR魔法与常见技巧
编译器将源代码转换为机器码的过程中,会经历多个中间表达形式(IR)的转换与优化。前端生成高级IR(HIR),后端将其转为低级IR(LIR)并进行机器相关优化。Java编译流程包括源码到字节码、再由即时编译器转换为内部HIR(如SSA图)、优化后生成LIR,最终编译为机器码。常见优化技术包括常量折叠、值编号、死代码消除、公共子表达式消除等,旨在提升程序性能与执行效率。
303 0
|
存储 缓存 安全
Java HashMap详解及实现原理
Java HashMap是Java集合框架中常用的Map接口实现,基于哈希表结构,允许null键和值,提供高效的存取操作。它通过哈希函数将键映射到数组索引,并使用链表或红黑树解决哈希冲突。HashMap非线程安全,多线程环境下需注意并发问题,常用解决方案包括ConcurrentHashMap和Collections.synchronizedMap()。此外,合理设置初始化容量和加载因子、重写hashCode()和equals()方法有助于提高性能和避免哈希冲突。
786 17
Java HashMap详解及实现原理
|
监控 Java 开发者
深入理解Java中的线程池实现原理及其性能优化####
本文旨在揭示Java中线程池的核心工作机制,通过剖析其背后的设计思想与实现细节,为读者提供一份详尽的线程池性能优化指南。不同于传统的技术教程,本文将采用一种互动式探索的方式,带领大家从理论到实践,逐步揭开线程池高效管理线程资源的奥秘。无论你是Java并发编程的初学者,还是寻求性能调优技巧的资深开发者,都能在本文中找到有价值的内容。 ####
|
Java
轻松上手Java字节码编辑:IDEA插件VisualClassBytes全方位解析
本插件VisualClassBytes可修改class字节码,包括class信息、字段信息、内部类,常量池和方法等。
896 6
|
Java 数据格式 索引
使用 Java 字节码工具检查类文件完整性的原理是什么
Java字节码工具通过解析和分析类文件的字节码,检查其结构和内容是否符合Java虚拟机规范,确保类文件的完整性和合法性,防止恶意代码或损坏的类文件影响程序运行。
262 5
|
Java API Maven
如何使用 Java 字节码工具检查类文件的完整性
本文介绍如何利用Java字节码工具来检测类文件的完整性和有效性,确保类文件未被篡改或损坏,适用于开发和维护阶段的代码质量控制。
497 5
|
Java
Java基础之 JDK8 HashMap 源码分析(中间写出与JDK7的区别)
这篇文章详细分析了Java中HashMap的源码,包括JDK8与JDK7的区别、构造函数、put和get方法的实现,以及位运算法的应用,并讨论了JDK8中的优化,如链表转红黑树的阈值和扩容机制。
298 1
如何从Java字节码角度分析问题|8月更文挑战
如何从Java字节码角度分析问题|8月更文挑战
124 1
|
存储 缓存 Java
java线程内存模型底层实现原理
java线程内存模型底层实现原理
java线程内存模型底层实现原理
|
Java
JAVA-ASM学习笔记之java字节码简介
前言 由于之前接触了集团的故障演练平台monkeyking,对JavaAgent与ASM产生了兴趣。前段时间稍微总结了下JavaAgent的简单内容《JavaAgent学习笔记》。这次则想学习一下ASM(Java字节码操纵框架)。但是在学习的过程中,发现一直对类似如下的代码持续懵逼中,看来想要了解ASM,还是得学下JAVA字节码。 static ClassWriter createCl
15381 0