AssignAnalyzer案例1

前言

上篇文章介绍了BaseAnalyzer,AbstractAssignAnalyzer,AssignAnalyzer.其中有很多点分享的不够透彻,这里就对于AssignAnalyzer,什么是静态变量和静态初始块来介绍.

案例

静态变量和静态初始块的案例

本案例的代码如下:

public class AssignDemo {

	static int a = 7;
	static final int b ;
	
	static {
		
		a = 10;
		b = 8;
	}
	
	int c = 9;
	
	final int d;
	
	{
		c = 12;
		d = 12;
	}
	
	public void test() {
		
		int c = 10;
	}
}

在AssignAnalyzer中,首先调用了analyzeTree方法,这个是在com.sun.tools.javac.comp.Flow.analyzeTree(Env<AttrContext>, TreeMaker)中调用的.如下:

public void analyzeTree(Env<AttrContext> env, TreeMaker make) {
    new AliveAnalyzer().analyzeTree(env, make);
    new AssignAnalyzer(log, syms, lint, names).analyzeTree(env);
    new FlowAnalyzer().analyzeTree(env, make);
    new CaptureAnalyzer().analyzeTree(env, make);
}

接着在com.sun.tools.javac.comp.Flow.AbstractAssignAnalyzer.analyzeTree(Env<?>, JCTree) 中对字段进行了初始化,然后调用scan方法开始进行遍历,这部分的代码我们在上一篇文章有介绍过.

最终来到了com.sun.tools.javac.comp.Flow.AbstractAssignAnalyzer.visitClassDef(JCClassDecl)方法,一开始是保存现场的代码,如下:

// 如果tree 没有对应的符号,则直接return
if (tree.sym == null) {
    return;
}

JCClassDecl classDefPrev = classDef;
int firstadrPrev = firstadr;
int nextadrPrev = nextadr;
ListBuffer<P> pendingExitsPrev = pendingExits;

接下来,是初始化pendingExits, firstadr, classDef如下:

pendingExits = new ListBuffer<P>();
if (tree.name != names.empty) {// 如果该类不是匿名类
    firstadr = nextadr;
}
classDef = tree;

对于当前,由于AssignDemo所对应的树的名字不为空,因此, firstadr = nextadr = 0

接下来时处理static fields.

for (List<JCTree> l = tree.defs; l.nonEmpty(); l = l.tail) {
    if (l.head.hasTag(VARDEF)) {
        JCVariableDecl def = (JCVariableDecl)l.head;
        if ((def.mods.flags & STATIC) != 0) {
            VarSymbol sym = def.sym;
            if (trackable(sym)) { // 如果该变量是可追踪的(在当前,意味着该变量是final且未赋值的),就保存到vardecls中
                newVar(def);
            }
        }
    }
}

这里先说明一下, AssignDemo语法树的defs中有如下成员:

1

其中,init方法是javac添加的.那么其中,是静态变量的有如下:

  • static int a = 7
  • static final int b

那么是否这两个字段都在这里处理呢? 问题的关键是在trackable中,如下:

protected boolean trackable(VarSymbol sym) {
    return
        sym.pos >= startPos &&
        ((sym.owner.kind == MTH ||
         ((sym.flags() & (FINAL | HASINIT | PARAMETER)) == FINAL &&
          classDef.sym.isEnclosedBy((ClassSymbol)sym.owner))));
}

这里的判断条件是给定符号的位置>= startPos && (sym 属于方法 || (当前的符号是 final && 当前符号是classDef的成员))

对于static int a = 7 来说:

  • sym.pos >= startPos 满足.(a 所对应的pos 为 41,远远大于startPos(2))

  • sym.flags() & (FINAL | HASINIT | PARAMETER)) == FINAL 不满足,这里因为其没有final修饰,同时其初始赋值为7,所以sym.flags() & (FINAL | HASINIT | PARAMETER)) 的结果就是 HASINIT

  • 因此,static int a = 7 不会在这里处理,static int a = 7 不是这里所说的static fields.

对于static final int b 来说:

  • sym.pos >= startPos 满足.(a 所对应的pos 为 66,远远大于startPos(2))

  • sym.flags() & (FINAL | HASINIT | PARAMETER)) == FINAL 满足,这里因为其有final修饰,同时没有初始赋值,所以sym.flags() & (FINAL | HASINIT | PARAMETER)) 的结果就是 FINAL

  • 同时,该符号是被AssignDemo所对应的符号所包围(Enclosed)的.这是很明显的

  • 因此,static final int b 会在这里处理,static final int b 是这里所说的static fields.那么接下来就会调用AbstractAssignAnalyzer.newVar(JCVariableDecl)方法.如下:

void newVar(JCVariableDecl varDecl) {
    VarSymbol sym = varDecl.sym;
    vardecls = ArrayUtils.ensureCapacity(vardecls, nextadr);// 对vardecls 进行扩容
    if ((sym.flags() & FINAL) == 0) {// 如果当前符号不是final的
        sym.flags_field |= EFFECTIVELY_FINAL; // 那么就修改该变量的标识符为有效final 
    }
    sym.adr = nextadr;
    vardecls[nextadr] = varDecl;
    exclVarFromInits(varDecl, nextadr); // 从初始化对应的bitmap中去除
    uninits.incl(nextadr);// 加入到未初始化的bitmap中
    nextadr++;
}

处理逻辑如下:

  1. 首先获得b所对应的符号VarSymbol
  2. 对 vardecls 进行扩容,对于当前是不需要的,vardecls的长度是32,而nextadr是0, vardecls的长度完全满足nextadr.
  3. 如果VarSymbol不是final的,则设置其修饰符为有效final的,对于当前, VarSymbol是final修饰的,因此这步是不会执行的.
  4. 设置VarSymbol的adr为nextadr,也就是 0
  5. 保存VarSymbol到vardecls中,下标是0
  6. 从初始化对应的bitmap中去除. 这样也就意味着下标为0的变量还未初始化.此时的位图为:00000000000000000000000000000000
  7. 加入到未初始化的bitmap中.此时的位图为:
    10000000000000000000000000000000
  8. nextadr 自增

static fields 处理完之后,就是处理static initializers.如下:

for (List<JCTree> l = tree.defs; l.nonEmpty(); l = l.tail) {
    if (!l.head.hasTag(METHODDEF) &&
        (TreeInfo.flags(l.head) & STATIC) != 0) {
        scan(l.head);
    }
}

满足条件的有:

  • static int a = 7
  • static final int b
  • 静态代码块

以上3者都会调用Flow.BaseAnalyzer.scan(JCTree)方法,区别在于:

  1. 对于static int a = 7 来说,是变量声明,因此会调用AbstractAssignAnalyzer.visitVarDef(JCVariableDecl),如下:

    public void visitVarDef(JCVariableDecl tree) {
        boolean track = trackable(tree.sym);
        if (track && tree.sym.owner.kind == MTH) {// 如果当前变量是方法中的
            newVar(tree);
        }
        if (tree.init != null) {
            scanExpr(tree.init);
            if (track) {
                letInit(tree.pos(), tree.sym);
            }
        }
    }
    
    • 首先,对于a来说,是不可追踪的,这点前面已经有介绍
    • 由于是不可追踪的同时该符号的拥有者的类型是TYP,不是MTH,因此不会执行Flow.AbstractAssignAnalyzer.newVar(JCVariableDecl)
    • 同时,由于该a有初始化部分–> 7 ,因此,会调用scanExpr方法.而由于7是一个int类型的字面量,那么最终调用的是TreeScanner.visitLiteral(JCLiteral),该方法是空实现.
    • 还是因为该符号是不可追踪的,因此是不会调用Flow.AbstractAssignAnalyzer.letInit(DiagnosticPosition, VarSymbol)
  2. 对于 static final int b来说,同时是变量声明,因此也会调用AbstractAssignAnalyzer.visitVarDef(JCVariableDecl).与static int a = 7 的区别是:

    • 该符号是可追踪的,这点在前面有说明
    • 其没有初始化部分

    因此对于static final int b来说,就相当于是没有处理.

  3. 对于静态代码块,我们在下篇文章中进行解释.

接下来就是处理实例字段,实例初始化器,这部分的代码和静态处理是一样的,这里就不展开了

总结

  1. 对于AssignAnalyzer来说,有static 修饰,没有初始化值的字段是static 字段, 没有static 修饰,没有初始化值的字段是实例字段
  2. 对于AssignAnalyzer来说,有static 修饰,有初始化值的字段是static 初始化器, 没有static 修饰,有初始化值的字段是实例初始化器

猜你喜欢

转载自blog.csdn.net/qq_26000415/article/details/82769883