3

This article mainly contains the following contents:

  1. Analysis of Lombok's implementation mechanism.
  2. Description and use of the plug-in annotation processor.
  3. Hands-on implementation of lombok's @Getter and @Setter annotations.
  4. Configure IDEA to debug the Java compilation process.

1. Lombok

Official website introduction:

Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.
Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.

Add Lombok dependency in Maven:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.20</version>
    <scope>provided</scope>
</dependency>

A simple example is as follows. Getter/setter methods are automatically generated by adding annotations.

package com.sumkor;

import lombok.Getter;
import lombok.Setter;

/**
 * @author Sumkor
 * @since 2021/12/27
 */
@Setter
@Getter
public class MyTest {

    private String value;

    public static void main(String[] args) {
        MyTest myTest = new MyTest();
        myTest.setValue("hello");
        System.out.println(myTest.getValue());
    }
}

The compiled code is as follows:

package com.sumkor;

public class MyTest {
    private String value;

    public MyTest() {
    }

    public static void main(String[] args) {
        MyTest myTest = new MyTest();
        myTest.setValue("hello");
        System.out.println(myTest.getValue());
    }

    public void setValue(String value) {
        this.value = value;
    }

    public String getValue() {
        return this.value;
    }
}

2. Annotation Processor

2.1 Javac compiler

"In-depth understanding of the Java virtual machine: JVM advanced features and best practices" Chapter 10 "Front-end Compilation and Optimization" introduces the Javac compiler.

The Javac compilation process is as follows:

From the overall structure of Javac code, the compilation process can be roughly divided into 1 preparation process and 3 processing processes, which are as follows.

  1. Preparation process: Initialize the plug-in annotation processor.
  2. The process of parsing and filling the symbol table includes: lexical and grammatical analysis; filling the symbol table.
  3. The annotation processing process of the plug-in annotation processor.
  4. Analysis and bytecode generation process.

Javac的编译过程

Focus on the description of the plug-in annotation processor:

After JDK 5, the Java language provides support for annotations. Annotations are originally designed to be the same as ordinary Java code, and they will only play a role during the running of the program. But in JDK 6, the JSR-269 proposal was proposed and passed. The proposal designed a set of standard APIs called "plug-in annotation processors", which can process specific annotations in the code in advance to compile time, thereby Affect the working process of the front-end compiler. We can think of the plug-in annotation processor as a set of compiler plug-ins. When these plug-ins work, they allow to read, modify, and add any element in the abstract syntax tree. If these plug-ins modify the syntax tree during the processing of annotations, the compiler will return to the process of parsing and filling the symbol table to reprocess it until all the plug-in annotation processors have not modified the syntax tree.

You can see that Lombok is implemented based on a plug-in annotation processor:

With the standard API for compiler annotation processing, the programmer's code may interfere with the behavior of the compiler. Since any element in the syntax tree, even code comments, can be accessed in the plug-in, plug-in annotations are used The plug-in implemented by the processor has a lot of room for function. As long as there is enough creativity, programmers can use the plug-in annotation processor to achieve many things that could only be done manually in coding. For example, Lombok, a well-known coding efficiency tool in Java, can automatically generate getter/setter methods, perform vacancy checks, generate checked exception tables, generate equals() and hashCode() methods, etc., to help developers eliminate Java through annotations The lengthy code of these are implemented by relying on the plug-in annotation processor.

2.2 Java annotations

The annotations in Java are divided into runtime annotations and compile-time annotations. The retention policy of the annotations is specified by setting the RetentionPolicy in the meta-annotation @Retention:

/**
 * Annotation retention policy.  The constants of this enumerated type
 * describe the various policies for retaining annotations.  They are used
 * in conjunction with the {@link Retention} meta-annotation type to specify
 * how long annotations are to be retained.
 *
 * @author  Joshua Bloch
 * @since 1.5
 */
public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

instruction:

  • SOURCE: Indicates that the annotation information will be discarded by the compiler and will not remain in the class file. The annotation information will only remain in the source file.
  • CLASS: Indicates that the annotation information is kept in the class file. When the program is compiled, it will not be read by the virtual machine while it is running.
  • RUNTIME: Indicates that the annotation information is retained in the class file. When the program is compiled, it will be kept at runtime by the virtual machine.

The annotations used in daily development are all of the RUNTIME type, which can be called and read by reflection during runtime.

javax.annotation.Resource

@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource

The annotations in Lombok are of type SOURCE and will only be read by the plug-in annotation processor during compilation.

lombok.Getter

@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Getter

2.3 Plug-in annotation processor

The custom annotation processor needs to inherit the AbstractProcessor class. The basic framework is roughly as follows:

package com.sumkor.processor;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import java.util.Set;

@SupportedAnnotationTypes("com.sumkor.annotation.Getter")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class GetterProcessor extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        return true;
    }
}

in:

  • @SupportedAnnotationTypes represents which annotations the annotation processor is interested in. You can use the asterisk * as a wildcard to represent interest in all annotations.
  • @SupportedSourceVersion indicates which versions of Java code can be processed by this annotation processor.
  • init() used to obtain some environmental information during the compilation phase.
  • process() can write specific logic for processing the syntax tree. If there is no need to change or add the content in the abstract syntax tree, the process() method can return a Boolean value of false to notify the compiler that the code in this round has not changed.

This article will implement a specific custom plug-in annotation processor and perform breakpoint debugging.

2.4 Javac APT

To use the plug-in annotation processor to modify the syntax tree in the compilation phase, you need to use the annotation processing tool APT (Annotation Processing Tool) in Javac, which is a tool provided by Sun to help the annotation processing process. APT is designed to manipulate Java sources File, not the compiled class.

This article uses JDK 8. Javac related source code is stored in tools.jar. If you want to use it in the program, you must put this library on the classpath. Note that by the time of JDK 9, all the Java class libraries of the entire JDK were refactored and divided by modularization, and the Javac compiler was moved to the jdk.compiler module, and access to this module was strictly restricted.

jdk.compiler

2.5 JCTree syntax tree

com.sun.tools.javac.tree.JCTree is the base class of syntax tree elements, including the following important subclasses:

  • JCStatement: Declare the syntax tree node, the common subclasses are as follows

    • JCBlock: Syntax tree node of statement block
    • JCReturn: syntax tree node of the return statement
    • JCClassDecl: class definition syntax tree node
    • JCVariableDecl: field/variable definition syntax tree node
  • JCMethodDecl: Method definition syntax tree node
  • JCModifiers: access to flag syntax tree nodes
  • JCExpression: Expression syntax tree node, common subcategories are as follows

    • JCAssign: Syntax tree node of assignment statement
    • JCIdent: Identifier syntax tree node, which can be variables, types, keywords, etc.

JCTree uses the visitor model to decouple data and data processing. Part of the source code is as follows:

public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {

    public int pos = -1;

    public abstract void accept(JCTree.Visitor visitor);

}

Using the visitor TreeTranslator, you can access the class definition node JCClassDecl on JCTree, and then you can obtain and modify the member variables, methods and other nodes in the class.

During the encoding process, you can use javax.annotation.processing.Messager to print information about the compilation process.
Note that the printMessage method of Messager will automatically filter duplicate log messages when printing the log.

Compared with printing logs, using IDEA tool to debug the compilation process will have a more intuitive understanding of the JCTree syntax tree.
At the end of the article, the configuration of debugging the plug-in annotation processor in IDEA is provided.

3. Hands-on implementation

Create two projects respectively to implement and verify the @Getter and @Setter annotations.

Create project lombok-processor, including custom annotations and plug-in annotation processors.
Create the project lombok-app, which depends on the lombok-processor project, and use the custom annotations in it for testing.

3.1 processor project

The overall structure of the project is as follows:
lombok-processor

Maven configuration

Because the Java syntax tree needs to be modified during the compilation phase and the API related to the syntax tree needs to be called, the tools.jar in the JDK directory is introduced into the current project.

<dependency>
    <groupId>com.sun</groupId>
    <artifactId>tools</artifactId>
    <version>1.8</version>
    <scope>system</scope>
    <systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>

The lombok-processor project uses the Java SPI mechanism to make its custom plug-in annotation processor effective for the lombok-app project. Since the lombok-processor project needs to exclude its own plug-in annotation processor during compilation, configure the maven resource to filter out the SPI files, and then add the SPI files to the jar package of the lombok-processor project when it is packaged.
In addition, in order to facilitate debugging, the source code of the lombok-processor project is also released to the local warehouse.

The complete maven build configuration is as follows:

<build>
    <!-- 配置一下resources标签,过滤掉META-INF文件夹,这样在编译的时候就不会找到services的配置 -->
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <excludes>
                <exclude>META-INF/**/*</exclude>
            </excludes>
        </resource>
    </resources>

    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.1</version>
            <configuration>
                <source>${maven.compiler.target}</source>
                <target>${maven.compiler.target}</target>
            </configuration>
        </plugin>
        <!-- 在打包前(prepare-package生命周期)再把services文件夹重新拷贝过来 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-resources-plugin</artifactId>
            <version>2.6</version>
            <executions>
                <execution>
                    <id>process-META</id>
                    <phase>prepare-package</phase>
                    <goals>
                        <goal>copy-resources</goal>
                    </goals>
                    <configuration>
                        <outputDirectory>target/classes</outputDirectory>
                        <resources>
                            <resource>
                                <directory>${basedir}/src/main/resources/</directory>
                                <includes>
                                    <include>**/*</include>
                                </includes>
                            </resource>
                        </resources>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        <!-- Source attach plugin -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-source-plugin</artifactId>
            <version>3.0.1</version>
            <configuration>
                <attach>true</attach>
            </configuration>
            <executions>
                <execution>
                    <phase>compile</phase>
                    <goals>
                        <goal>jar</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Custom annotation

Custom annotations mainly use two meta annotations:

  • @Target({ElementType.TYPE}) indicates that it is an annotation to the class.
  • @Retention(RetentionPolicy.SOURCE) means that this annotation only works at compile time and will not exist at runtime.

Custom @Getter annotation:

package com.sumkor.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE}) 
@Retention(RetentionPolicy.SOURCE) 
public @interface Getter {
}

Custom @Setter annotation:

package com.sumkor.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Setter {
}

Custom annotation processor

BaseProcessor

Define the abstract base class BaseProcessor, which is used to uniformly obtain the tool classes of the compilation stage, such as JavacTrees, TreeMaker, etc.
Since this project needs to be debugged and run in IDEA, the ProcessingEnvironment of IDEA environment is introduced.

package com.sumkor.processor;

import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Names;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import java.lang.reflect.Method;

/**
 * @author Sumkor
 * @since 2021/12/27
 */
public abstract class BaseProcessor extends AbstractProcessor {

    protected Messager messager;   // 用来在编译期打log用的
    protected JavacTrees trees;    // 提供了待处理的抽象语法树
    protected TreeMaker treeMaker; // 封装了创建AST节点的一些方法
    protected Names names;         // 提供了创建标识符的方法

    /**
     * 获取编译阶段的一些环境信息
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        processingEnv = jbUnwrap(ProcessingEnvironment.class, processingEnv);
        super.init(processingEnv);
        this.messager = processingEnv.getMessager();
        this.trees = JavacTrees.instance(processingEnv);
        Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
        this.treeMaker = TreeMaker.instance(context);
        this.names = Names.instance(context);
    }

    /**
     * 获取 IDEA 环境下的 ProcessingEnvironment
     */
    private static <T> T jbUnwrap(Class<? extends T> iface, T wrapper) {
        T unwrapped = null;
        try {
            final Class<?> apiWrappers = wrapper.getClass().getClassLoader().loadClass("org.jetbrains.jps.javac.APIWrappers");
            final Method unwrapMethod = apiWrappers.getDeclaredMethod("unwrap", Class.class, Object.class);
            unwrapped = iface.cast(unwrapMethod.invoke(null, iface, wrapper));
        }
        catch (Throwable ignored) {}
        return unwrapped != null? unwrapped : wrapper;
    }

}
GetterProcessor

The annotation processor corresponding to the custom annotation @Getter is as follows, the code flow:

  1. Get the class decorated by @Getter annotation.
  2. Find all member variables on the class.
  3. Construct getter methods for member variables.

The difficulty lies in the use of JavacTrees and TreeMaker related APIs. The key codes in the article are commented to facilitate understanding.

package com.sumkor.processor;

import com.sumkor.annotation.Getter;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeTranslator;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Name;

import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.util.Set;

/**
 * @author Sumkor
 * @since 2021/12/24
 */
@SupportedAnnotationTypes("com.sumkor.annotation.Getter")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class GetterProcessor extends BaseProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        messager.printMessage(Diagnostic.Kind.NOTE, "========= GetterProcessor init =========");
    }

    /**
     * 对 AST 进行处理
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // 获取被自定义 Getter 注解修饰的元素
        Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(Getter.class);
        set.forEach(element -> {
            // 根据元素获取对应的语法树 JCTree
            JCTree jcTree = trees.getTree(element);
            jcTree.accept(new TreeTranslator() {
                // 处理语法树的类定义部分 JCClassDecl
                @Override
                public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
                    List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil();
                    for (JCTree tree : jcClassDecl.defs) {
                        // 找到语法树上的成员变量节点,存储到 jcVariableDeclList 集合
                        if (tree.getKind().equals(Tree.Kind.VARIABLE)) {
                            JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) tree;
                            jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl);
                        }
                    }
                    // 为成员变量构造 getter 方法,并添加到 JCClassDecl 之中
                    jcVariableDeclList.forEach(jcVariableDecl -> {
                        messager.printMessage(Diagnostic.Kind.NOTE, jcVariableDecl.getName() + " has been processed");
                        jcClassDecl.defs = jcClassDecl.defs.prepend(makeGetterMethodDecl(jcVariableDecl));
                    });
                    super.visitClassDef(jcClassDecl);
                }

            });
        });
        return true;
    }

    /**
     * 为成员遍历构造 getter 方法
     */
    private JCTree.JCMethodDecl makeGetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
        ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
        // 生成表达式 return this.value;
        statements.append(treeMaker.Return(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName())));
        // 加上大括号 { return this.value; }
        JCTree.JCBlock body = treeMaker.Block(0, statements.toList());
        // 组装方法
        return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), getNewMethodName(jcVariableDecl.getName()), jcVariableDecl.vartype, List.nil(), List.nil(), List.nil(), body, null);
    }

    /**
     * 驼峰命名法
     */
    private Name getNewMethodName(Name name) {
        String s = name.toString();
        return names.fromString("get" + s.substring(0, 1).toUpperCase() + s.substring(1, name.length()));
    }

}
SetterProcessor

The annotation processor SetterProcessor corresponding to the custom annotation @Setter, the overall process is similar to GetterProcessor:

  1. Get the class decorated by @Setter annotation.
  2. Find all member variables on the class.
  3. Construct setter methods for member variables.

Focus on the logic of constructing the setter method:

/**
 * 为成员构造 setter 方法
 */
private JCTree.JCMethodDecl makeSetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
    ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
    // 生成表达式 this.value = value;
    JCTree.JCExpressionStatement aThis = makeAssignment(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName()), treeMaker.Ident(jcVariableDecl.getName()));
    statements.append(aThis);
    // 加上大括号 { this.value = value; }
    JCTree.JCBlock block = treeMaker.Block(0, statements.toList());

    // 生成方法参数之前,指明当前语法节点在语法树中的位置,避免出现异常 java.lang.AssertionError: Value of x -1
    treeMaker.pos = jcVariableDecl.pos;

    // 生成方法参数 String value
    JCTree.JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER), jcVariableDecl.getName(), jcVariableDecl.vartype, null);
    List<JCTree.JCVariableDecl> parameters = List.of(param);
    // 生成返回对象 void
    JCTree.JCExpression methodType = treeMaker.Type(new Type.JCVoidType());

    // 组装方法
    return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), getNewMethodName(jcVariableDecl.getName()), methodType, List.nil(), parameters, List.nil(), block, null);
}

/**
 * 赋值操作 lhs = rhs
 */
private JCTree.JCExpressionStatement makeAssignment(JCTree.JCExpression lhs, JCTree.JCExpression rhs) {
    return treeMaker.Exec(
            treeMaker.Assign(lhs, rhs)
    );
}

/**
 * 驼峰命名法
 */
private Name getNewMethodName(Name name) {
    String s = name.toString();
    return names.fromString("set" + s.substring(0, 1).toUpperCase() + s.substring(1, name.length()));
}

3.2 app project

The overall structure of the project is as follows, where IDEA’s red prompt is caused by the inability to recognize custom annotations and does not affect the operation of the project.
lombok-app
Introduce the lombok-processor project in maven:

<dependency>
    <groupId>com.sumkor</groupId>
    <artifactId>lombok-processor</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

Write a test class and introduce custom annotations @Getter and @Setter. You can see that IDEA can compile and run normally, although it is marked in red to indicate that it cannot find a method.
MyApp

4. Debugging

4.1 IDEA configuration

Use Attach Remote JVM to debug the compilation process of lombok-app project.

  1. Create a remote debugger and specify the port as 5005.
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

run/debug configuration

  1. Select in the menu: Help -> Edit Custom VM Options, add the following content and restart IDEA. The port here is the port that needs to be attached selected in the previous step.
-Dcompiler.process.debug.port=5005
  1. Open IDEA's Debug Build Process. Note that this option will be turned off by default every time IDEA is restarted.

Debug Build Process

4.2 Start debugging

  1. Add a breakpoint in the annotation processor, it is recommended to set the breakpoint in AbstractProcessor#init.
  2. Execute the mvn clean operation to clear the contents of the last build.
  3. Execute ctrl + F9 operation to build lombok-app project.
    build project
    In the status bar, you can see that the build process is waiting for the debugger to connect:
    Build Process Pause
  4. Run the remote debugging just created.
    Start Run Configuration
    You can see that the breakpoint in AbstractProcessor#init has been entered.
    AbstractProcessor#init the custom annotation processor, and observes the modification result of TreeMaker on the syntax tree.
    SetterProcessor

    4.3 Problem solving

When writing the SetterProcessor#process method, if treeMaker.pos = jcVariableDecl.pos; is missing, an error message will be reported during the compilation process: java.lang.AssertionError: Value of x -1 , the specific compilation information is as follows:

Executing pre-compile tasks...
Loading Ant configuration...
Running Ant tasks...
Running 'before' tasks
Checking sources
Copying resources... [lombok-app]
Parsing java... [lombok-app]
java: ========= GetterProcessor init =========
java: value has been processed
java: ========= SetterProcessor init =========
java: 编译器 (1.8.0_91) 中出现异常错误。如果在 Bug Database (http://bugs.java.com) 中没有找到该错误, 请通过 Java Bug 报告页 (http://bugreport.java.com) 建立该 Java 编译器 Bug。请在报告中附上您的程序和以下诊断信息。谢谢。
java: java.lang.AssertionError: Value of x -1
java:     at com.sun.tools.javac.util.Assert.error(Assert.java:133)
java:     at com.sun.tools.javac.util.Assert.check(Assert.java:94)
java:     at com.sun.tools.javac.util.Bits.incl(Bits.java:186)
java:     at com.sun.tools.javac.comp.Flow$AssignAnalyzer.initParam(Flow.java:1858)
java:     at com.sun.tools.javac.comp.Flow$AssignAnalyzer.visitMethodDef(Flow.java:1807)
java:     at com.sun.tools.javac.tree.JCTree$JCMethodDecl.accept(JCTree.java:778)
java:     at com.sun.tools.javac.tree.TreeScanner.scan(TreeScanner.java:49)
java:     at com.sun.tools.javac.comp.Flow$BaseAnalyzer.scan(Flow.java:404)
java:     at com.sun.tools.javac.comp.Flow$AssignAnalyzer.scan(Flow.java:1382)
java:     at com.sun.tools.javac.comp.Flow$AssignAnalyzer.visitClassDef(Flow.java:1749)
java:     at com.sun.tools.javac.tree.JCTree$JCClassDecl.accept(JCTree.java:693)
java:     at com.sun.tools.javac.comp.Flow$AssignAnalyzer.analyzeTree(Flow.java:2446)
java:     at com.sun.tools.javac.comp.Flow$AssignAnalyzer.analyzeTree(Flow.java:2429)
java:     at com.sun.tools.javac.comp.Flow.analyzeTree(Flow.java:211)
java:     at com.sun.tools.javac.main.JavaCompiler.flow(JavaCompiler.java:1327)
java:     at com.sun.tools.javac.main.JavaCompiler.flow(JavaCompiler.java:1296)
java:     at com.sun.tools.javac.main.JavaCompiler.compile2(JavaCompiler.java:901)
java:     at com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:860)
java:     at com.sun.tools.javac.main.Main.compile(Main.java:523)
java:     at com.sun.tools.javac.api.JavacTaskImpl.doCall(JavacTaskImpl.java:129)
java:     at com.sun.tools.javac.api.JavacTaskImpl.call(JavacTaskImpl.java:138)
java:     at org.jetbrains.jps.javac.JavacMain.compile(JavacMain.java:238)
java:     at org.jetbrains.jps.incremental.java.JavaBuilder.lambda$compileJava$2(JavaBuilder.java:514)
java:     at org.jetbrains.jps.incremental.java.JavaBuilder.invokeJavac(JavaBuilder.java:560)
java:     at org.jetbrains.jps.incremental.java.JavaBuilder.compileJava(JavaBuilder.java:512)
java:     at org.jetbrains.jps.incremental.java.JavaBuilder.compile(JavaBuilder.java:355)
java:     at org.jetbrains.jps.incremental.java.JavaBuilder.doBuild(JavaBuilder.java:280)
java:     at org.jetbrains.jps.incremental.java.JavaBuilder.build(JavaBuilder.java:234)
java:     at org.jetbrains.jps.incremental.IncProjectBuilder.runModuleLevelBuilders(IncProjectBuilder.java:1485)
java:     at org.jetbrains.jps.incremental.IncProjectBuilder.runBuildersForChunk(IncProjectBuilder.java:1123)
java:     at org.jetbrains.jps.incremental.IncProjectBuilder.buildTargetsChunk(IncProjectBuilder.java:1268)
java:     at org.jetbrains.jps.incremental.IncProjectBuilder.buildChunkIfAffected(IncProjectBuilder.java:1088)
java:     at org.jetbrains.jps.incremental.IncProjectBuilder.buildChunks(IncProjectBuilder.java:854)
java:     at org.jetbrains.jps.incremental.IncProjectBuilder.runBuild(IncProjectBuilder.java:441)
java:     at org.jetbrains.jps.incremental.IncProjectBuilder.build(IncProjectBuilder.java:190)
java:     at org.jetbrains.jps.cmdline.BuildRunner.runBuild(BuildRunner.java:132)
java:     at org.jetbrains.jps.cmdline.BuildSession.runBuild(BuildSession.java:318)
java:     at org.jetbrains.jps.cmdline.BuildSession.run(BuildSession.java:146)
java:     at org.jetbrains.jps.cmdline.BuildMain$MyMessageHandler.lambda$channelRead0$0(BuildMain.java:218)
java:     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
java:     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
java:     at java.lang.Thread.run(Thread.java:745)
java: Compilation failed: internal java compiler error
Checking dependencies... [lombok-app]
Dependency analysis found 0 affected files
Errors occurred while compiling module 'lombok-app'
javac 1.8.0_91 was used to compile java sources
Finished, saving caches...
Compilation failed: errors: 1; warnings: 0
Executing post-compile tasks...
Loading Ant configuration...
Running Ant tasks...
Synchronizing output directories...

From the exception stack information, it can be seen that com.sun.tools.javac.util.Bits.incl(Bits.java:186) , and the location of the exception was located:

com.sun.tools.javac.util.Bits#incl

public void incl(int var1) {
    Assert.check(this.currentState != Bits.BitsState.UNKNOWN);
    Assert.check(var1 >= 0, "Value of x " + var1); // 这一行报错
    this.sizeTo((var1 >>> 5) + 1);
    this.bits[var1 >>> 5] |= 1 << (var1 & 31);
    this.currentState = Bits.BitsState.NORMAL;
}

Break point analysis of the abnormal link, you can locate the error report caused by the SetterProcessor#process method. The core value that causes the problem is the pos field of JCTree, which is used to indicate the position of the current syntax tree node in the syntax tree, and the pos generated by TreeMaker are all fixed values. You need to set this field to the pos of the parsed element. Yes (see the previous section for the modified SetterProcessor#process method).

5. Reference


Author: Sumkor
Link: https://segmentfault.com/a/1190000041200280


Sumkor
148 声望1.3k 粉丝

会写点代码