JAVAC编译流程

发布时间 2023-04-18 12:24:38作者: ylc0x01

Javac编译过程

转变过程

入口

来源:com.sun.tools.javac.main.Main#compile(java.lang.String[])
  public int compile(String[] args) {
        // 创建上下文
        Context context = new Context();
        // 上下文中放入JavacFileManager
        JavacFileManager.preRegister(context); 
  			// 执行编译
        int result = compile(args, context);
        if (fileManager instanceof JavacFileManager) {
            ((JavacFileManager)fileManager).close();
        }
        return result;
    }

java源代码

在符号表填充阶段会对.java文件和.class文件进行查找和加载

com.sun.tools.javac.jvm.ClassReader#fillIn方法会调用

com.sun.tools.javac.file.JavacFileManager#list方法

来源:com.sun.tools.javac.file.JavacFileManager#list
  // loaction 是.java或者.class文件要查找的地址
  // packageName 是要查找文件的包名
  // kinds 指明为是.java文件还是.class文件
    public Iterable<JavaFileObject> list(Location location,
                                         String packageName,
                                         Set<JavaFileObject.Kind> kinds,
                                         boolean recurse)
        throws IOException
    {
  			// 文件迭代器
        Iterable<? extends File> path = getLocation(location);
        if (path == null)
            return List.nil();
        RelativeDirectory subdirectory = RelativeDirectory.forPackage(packageName);
        ListBuffer<JavaFileObject> results = new ListBuffer<JavaFileObject>();

        for (File directory : path)
          	// 如果directory是目录,将目录下满足要求的文件追加到results列表中
          	// 如果directory是压缩包,最终创建ZipFileIndexArchive对象
          	// 压缩包中满足要求的文件并追加到results列表中
            listContainer(directory, subdirectory, kinds, recurse, results);
        return results.toList();  
}

词法分析 -> token流

com.sun.tools.javac.main.JavaCompiler#compile方法会间接调用com.sun.tools.javac.file.RegularFileObject#getCharContent方法,将java源文件转换了字符流。

获取字符流后,间接调用com.sun.tools.javac.main.JavaCompiler#parse方法,在while循环中将字符流转化为Name流映射为token流

com.sun.tools.javac.parser.Scanner

来源:com.sun.tools.javac.parser.Scanner#nextToken
public void nextToken() {
  	...
      while (true) {
                pos = bp;
                // switch语句会根据首个出现的字符来判断可能生成的Token对象
                switch (ch) { 
                    /*
			        1、特殊字符的处理
			        2、标识符的处理
			        3、数字的处理
			        4、分隔符的处理
			        5、斜线作为首字符的处理
			        6、单引号作为首字符的处理
			        7、双引号作为首字符的处理
			        8、默认的处理
                     */
                }
      }
    ...
}

语法分析 -> 抽象语法树

转换为Token流后,调用com.sun.tools.javac.tree.TreeMaker#TopLevel方法new 一个编译单元

来源:com.sun.tools.javac.tree.TreeMaker#TopLevel
    public JCCompilationUnit TopLevel(List<JCAnnotation> packageAnnotations,
    JCExpression pid,
    List<JCTree> defs) {
        ...
        JCCompilationUnit tree = new JCCompilationUnit(packageAnnotations, pid, defs, null, null, null, null);
        ...
        return tree;
    }

调用com.sun.tools.javac.comp.Enter#complete方法完成对符号的输入

来源:com.sun.tools.javac.comp.Enter#complete
    public void complete(List<JCCompilationUnit> trees, ClassSymbol c) {
        ...
        try {
          	// 将当前编译单元下所有的非本地类的类符号输入到对应owner类的符号表中
            classEnter(trees, null);

            if  (memberEnter.completionEnabled) {
                while (uncompleted.nonEmpty()) {
                    ClassSymbol clazz = uncompleted.next();
                    if (c == null || c == clazz || prevUncompleted == null)
                        // 完成类符号对象或包符号对象中的符号输入
                        clazz.complete();
                    ...
                }
                for (JCCompilationUnit tree : trees) {
                    if (tree.starImportScope.elems == null) {
                        ...
                        Env<AttrContext> topEnv = topLevelEnv(tree);
                        // 符号输入的第二阶段,类中的方法和成员变量进行符号输入
                        memberEnter.memberEnter(tree, topEnv);
                      	...
                    }
                }
            }
        } finally {
            ...
        }
    }

完成符号输入后,抽象语法树就构建完成

注意:在构建抽象语法树之后会处理插入式注解器,比如对lombok下相关注解器对java源代码的注解进行处理,生成对应的Java代码。生成代码后会重复符号输入的过程,并且返回新的JavaCompiler对象。

语义分析 -> 标注语法树

在com.sun.tools.javac.main.JavaCompiler#compile2中进行语义分析

来源:com.sun.tools.javac.main.JavaCompiler#compile2
    private void compile2() {
       ...
         generate(desugar(flow(attribute(todo))));
       ...
    }

Attr

com.sun.tools.javac.comp.Attr完成语义分析

  • 引用消除
  • 类型检查
  • 常量折叠

Flow

com.sun.tools.javac.comp.Flow完成语法检查

  • 变量赋值检查
  • 语句活跃性分析
  • 异常检查

Lower

com.sun.tools.javac.comp.Lower完成解语法糖

  • 可变长参数
  • 自动拆装箱
  • 条件编译
  • 增强for循环
  • 选择表达式
  • 枚举类
  • 泛型擦除
  • 内部类

代码生成 -> 字节码

  • 字节码指令生成
  • .class文件生成