解析和填充符号表这个过程主要由com.sun.tools.javac.comp.Enter及com.sun.tools.javac.comp.MemberEnter两个类来实现的。这两个类会将遇到的所有的定义填充到符号表中,并且把这写符号和相应的范围(scope)联系在一起。实现方法是就是通过前面讲解的Visitor(访问者)类来实现的,由上而下的遍历AST(抽象语法树),访问所有的类,包括类里面的成员类。
1、符号的查找及输入
对于Javac来说,首先会涉及到依赖符号的查找,例如某个类对依赖类List中的方法调用,就需要通过符号来进行查找。对于包下的类符号及类中成员的符号的加载涉及到一个非常重要的接口:SymbolCompleter,这个接口的定义如下:
/** Symbol completer interface.
*/
public interface SymbolCompleter {
void complete(Symbol sym) throws SymbolCompletionFailure;
}
两个重要的实现类为ClassReader与MemberEnter,关于MemberEnter的实现及complete()方法的实现逻辑将在后面进行详细的说明。这里重点说明ClassReader中一些重要的方法。
/** Completion for classes to be loaded(完成要加载的类). Before a class is loaded
* we treeMaker sure its enclosing class (if any) is loaded.
*/
public void complete(Symbol sym) throws SymbolCompletionFailure {
if (sym.kind == TYP02) {
ClassSymbol c = (ClassSymbol)sym;
c.members_field = new ErrorScope(c); // treeMaker sure it's always defined
// suppressFlush值可以保证下面的annotate在内部类加载完成时只调用一次
// 不会由于外部类的加载而重复调用
boolean saveSuppressFlush = suppressFlush;
suppressFlush = true;
try {
completeOwners(c.owner);
completeEnclosing(c);
} finally {
suppressFlush = saveSuppressFlush;
}
fillInClassSymbol(c);
} else if (sym.kind == PCK01) {
PackageSymbol p = (PackageSymbol)sym;
try {
fillInPackageSymbol(p);
} catch (IOException ex) {
throw new SymbolCompletionFailure(sym, ex.getLocalizedMessage()).initCause(ex);
}
}
if (!filling && !suppressFlush) { // 当没有在读一个文件并且不需要抑制刷新时
annotate.flush(); // finish attaching annotations
}
}
可以看到有两个重要的处理逻辑,一个是对符号类型为TYP02的处理,也就是对类或接口中定义的符号的填充,另外就是对包的处理。
可以这样想一下,我们在引入依赖类或者继承了父类时,最可能会用到这些类中定义的成员类、变量及方法,那么就需要将这些符号填充到符号表中便于后面进行语义分析。
主要是调用fillInClassSymbol(c)来将类内定义的符号填充到members_field属性中的。在fillInClassSymbol()方法中最主要调用了readClassFile(c)来完成对.class文件的读取,由于读取时涉及到了.class文件的内容,这些将在后面的章节进行详细的讲解。
看一下包符号的填充,这个类似于类符号的填充,主要读取了这个包下的所有类符号并填充到members_field属性中。调用的fillInPackageSymbol()方法正是在第二章-文件的读取与写入时详细讲解过,这里不做过多介绍。
2、符号输入第一阶段
/** Bootstrap method: enter one class from a list of toplevel trees and
* place the rest on uncompleted for later processing.
* @param trees The list of trees to be processed.
* @param c The class symbol to be processed.
*/
public void complete(List trees, ClassSymbol c) {
annotate.enterStart();
ListBuffer prevUncompleted = uncompleted;
if (memberEnter.completionEnabled) {
uncompleted = new ListBuffer();
}
try {
// enter all classes, and construct uncompleted list
// 输入所有类,创建未完成列表
classEnter(trees, null);
// complete all uncompleted classes in memberEnter
if (memberEnter.completionEnabled) {
while (uncompleted.nonEmpty()) {
ClassSymbol clazz = uncompleted.next();
if (c == null || c == clazz || prevUncompleted == null) {
clazz.complete();
} else {
prevUncompleted.append(clazz); // defer
}
}
// if there remain any unimported toplevels (these must have
// no classes at all), process their import statements as well.
for (JCCompilationUnit tree : trees) {
if (tree.starImportScope.elems == null) {
JavaFileObject prev = log.useSource(tree.sourcefile);
Env topEnv = topLevelEnv(tree);
memberEnter.memberEnter(tree, topEnv);
log.useSource(prev);
}
}
}
} finally {
uncompleted = prevUncompleted;
annotate.enterDone(); // 处理放到队列中元素(方法与变量)上的注解
}
}
Enter类继承了JCTreeVisitor类,这样就可以通过选择性的覆写访问者方法visitXXX()来实现自己想处理的逻辑了,在Enter中主要覆写了如下2个访问者方法:
(1)visitTopLevel
填充package属性,类型为PackageSymbol。如果当前有包名,则调用classReader.enterPackage(fn)赋值,否则值为syms.unnamedPackage。enterPackage()方法的源代码如下:
/** Make a package, given its fully qualified name.
*/
public PackageSymbol enterPackage(Name fullname) {
PackageSymbol p = packages.get(fullname);
if (p == null) {
Assert.check(!fullname.isEmpty(), "rootPackage missing!");
Name n = Convert.shortName(fullname);
// 如果fullname=java.lang时,temp=java
Name temp = Convert.packagePart(fullname);
PackageSymbol parentPS = enterPackage(temp);
p = new PackageSymbol(n,parentPS);
p.completer = this;
packages.put(fullname, p);
}
return p;
}
为各个包建立PackageSymbol,并将建立好的符号存储到packages中。这里将对每个PackageSymbol的completer属性赋值为ClassReader对象。然后在visitTopLevel()中就可以直接调用package属性的complete()方法完成包下类符号的填充了,如下代码:
tree.packge.complete(); // Find all classes in package.
接着完成环境的创建:
Env topEnv = topLevelEnv(tree);
输入当前这个编译单元中定义的类及类成员:
classEnter(tree.defs, topEnv);
(2)visitClassDef
对于visitClassDef()方法的处理逻辑相对多一些,不过主要的逻辑就是创建类的符号并赋值到JCClassDef节点的sym属性上。对于顶层类的符号来说,除了要输入到包的members_filed中,还要输入到当前包下作用域的的符号表中。
将符号的completer属性设置为MemberEnter实例,因为这些符号中定义的成员符号还没有填充到相应的作用域下的符号表中,而这个工作将交由MemberEnter类来完成。
除此之外将类符号压入队列中,等待后续进一步处理其中定义的成员。
不过这些符号在通过import关键字引入时,在处理具体的引用时就已经被引入到了全局的符号表中了。通过JCCompliationUnit中的两个重要属性:
public ImportScope namedImportScope;
public StarImportScope starImportScope;
来查找依赖符号。
3、符号输入第二阶段
MemberEnter类同样继承了JCTreeVisitor类,并对其实的一些visitXXX()方法进行了覆写,被覆写的方法主要有:
(1)visitTopLevel
(2)visitImport
(3)visitVarDef
(4)visitMethodDef
由名字不难看出访问的各个抽象语法树的节点,不过这里没有visitClassDef,因为每个代表类的树节点的符号都会调用complete()方法,间接调用到MemberEnter()方法,如下:
在Enter的complete()方法中在处理完classEnter()后有如下调用逻辑:
while (uncompleted.nonEmpty()) {
ClassSymbol clazz = uncompleted.next();
if (c == null || c == clazz || prevUncompleted == null) {
clazz.complete();
} else {
prevUncompleted.append(clazz); // defer
}
}
也就是取出uncompleted队列中所有示完成的类符号并依次调用complete()方法,由于这些符号在符号输入第一阶段时都被赋值为MemberEnter实例,所以当然会调用MemberEnter的complete()方法。
MemberEnter类中的complete()方法的逻辑有点复杂,不过
成员包括类成员(静态与非静态成员)、方法及变量,不过这里并不包括方法形式参数及局部变量的处理,关于方法参数及局部变量的处理主要是在Attr标记阶段完成的。
Enter给每一个类的符号都添加了一个MemberEnter对象,这个对象是由第二个阶段来调用的。
这些类被MemberEnter对象所完成(completed,即完成类的成员变量的Enter)。首先,MemberEnter决定一个类的参数,父类和接口。然后这些符号被添加进了类的范围中。不像前一个步骤,这个步骤是懒惰执行的。类的成员只有在被访问时,才加入类的定义中的。这里的实现,是通过安装一个完成对象(member object)到类的符号中。这些对象可以在需要时调用memberEnter。