【软件分析】Tai-e实验代码理解与踩坑记录

软件分析实验Tai-e代码理解与踩坑记录

  • A1
  • A2
  • A3
  • A4
    • 实现类层次结构分析(CHA)
    • 实现过程间常量传播
    • 实现过程间 Worklist 求解器

静态分析实验太阿地址
同学优质的课程专属博客

A1

  1. 每个SetFact包括了一个Var集合的类成员和若干操作集合的方法,每个程序点的IN/OUT都拥有一个SetFact. 初始化时因为LiveVariableAnalysis都要初始化为空集,返回new的值即可。
@Override
    public SetFact<Var> newBoundaryFact(CFG<Stmt> cfg) {//IN[exit]=∅
        //空集->集合中没有任何var->新建一个SetFact即可
        return new SetFact<>();
    }

    @Override
    public SetFact<Var> newInitialFact() {//IN[B]=∅ 为了完成meet策略,OUT赋一样的初值∅
        //空集->集合中没有任何var->新建一个SetFact即可
        return new SetFact<>();
    }
  1. 这里逻辑简单(因为analysis写好了),我因为在每轮循环开始忘记恢复change的值耽误了一会儿。
@Override
    protected void doSolveBackward(CFG<Node> cfg, DataflowResult<Node, Fact> result) {
        boolean change = false;//记录这一轮是否有至少一个变化的IN
        do{
            change = false;//这里我忘记恢复初值了,一度导致死循环
            for (Node node : cfg) {
                if(!cfg.isExit(node)){
                    for (Node succ : cfg.getSuccsOf(node)) {
                        //把每个succ的IN与target的OUT取并集
                        analysis.meetInto(result.getInFact(succ), result.getOutFact(node));
                    }
                    //cfg中的node在LiveVariableAnalysis中的类型是Stmt,所以调用了自己实现的代码
                    //利用返回值判断是否有IN值改变
                    if(analysis.transferNode(node, result.getInFact(node), result.getOutFact(node)) && !change){
                        change = true;
                    }
                }
            }
        }while (change);
    }
  1. 因为LValue和RValue不是永远是Var,所以强制类型转换前一定要用instance of判断,否则会报错exp.invokespecial cannot be cast to Var这样的类型转换错误。
    【软件分析】Tai-e实验代码理解与踩坑记录_第1张图片
  2. Optional可以有效提醒程序员里面的值可能为空,为空isPresent()则返回false,不为空调用get()则获得T类型实例。【软件分析】Tai-e实验代码理解与踩坑记录_第2张图片
        if(defB.isPresent()){//java.util.Optional中的isPresent可判断是否为空
            if(defB.get() instanceof Var) {//注意一定要判断,LValue不是永远为Var->不判断时会报cannot be cast to Var
                res.remove((Var) defB.get());//OUT[B]-DEF[B]
            }
        }

        for(RValue rValue: stmt.getUses()){//注意一定要判断,LValue不是永远为Var->不判断时会报cannot be cast to Var
            if(rValue instanceof Var){
                res.add((Var) rValue);
            }
        }

A2

  1. 关注细节 issue

  2. evaluate函数判断常量不是看Var的isTempConst,这是理论(能有和真的存int有区别)要看实际:Value.isConstant判断

  3. 利用好copyFrom函数,然后要用中间值。如果不使用虽然比较了赋值语句对IN的修改,但没有比较旧的IN和旧的OUT。

  4. newBoundaryFact要用cfg.getIR().getParams()获得方法的参数(不是getVars,这个是变量+%this),记得设置为NAC。(因为不分析过程外的方法,这里可能返回任何职,需要做最保守的假设。显然这是sound但是不精确的。第七课介绍的过程间分析会精确很多。)
    在这里插入图片描述
    【软件分析】Tai-e实验代码理解与踩坑记录_第3张图片

  5. IN依然记得赋初值。为了meet策略。

  6. worklist中用有序队列存储,而不是set,因为有forward顺序需求

  7. worklist添加时记得去重(用contains判断)

A3

  1. 用BFS/DFS遍历CFG图,我使用的是BFS,用queue进队出队实现。不能无脑遍历node,这样无法检测控制流不可达代码。
  2. 分支中的可达代码不用立即处理,加入队列之后处理即可。毕竟分支内的语句一般不止一条。
  3. 遍历BFS可处理分支不可达代码和无用赋值,但不能处理控制流不可达代码,所以BFS结束后还要另外进行处理。
  for(Stmt stmt: cfg.getIR().getStmts()){//这里处理控制流不可达
      if(!liveCode.contains(stmt) && !deadCode.contains(stmt)){
           deadCode.add(stmt);
      }
 }
  1. AssignStmt 的 LValue 不一定是 Var,A1也有这个问题,见继承关系即知。issue
  2. transferNode函数在处理backward时自己会处理in/out的反转,传参时不用手动改,我在第一次写worklist.java时出错了。
if(analysis.transferNode(node, result.getInFact(node), result.getOutFact(node))){
    for(Node pred : cfg.getPredsOf(node)){
        if(!nodeQueue.contains(pred)){
             nodeQueue.add(pred);
        }
    }
}
  1. 实现时的思路【软件分析】Tai-e实验代码理解与踩坑记录_第4张图片

A4

实现类层次结构分析(CHA)

  1. Resolve(b.foo())=ACD:这里为什么不是special?b.foo()并不代表直接调用super.foo(),所以是virtual【软件分析】Tai-e实验代码理解与踩坑记录_第5张图片
  2. getDeclaringClass返回class type;getSubsignature:返回被调用方法的子签名(两方法结合就能获得完整子签名了)

一个方法的子签名只包含它的方法名和方法签名的描述符,如 foo 的子签名是:“T foo(P,Q,R)” ,而它的完整签名是:“”。

【软件分析】Tai-e实验代码理解与踩坑记录_第6张图片

  1. 子类包括直接和间接,所以要用bfs/dfs。我使用了bfs。【软件分析】Tai-e实验代码理解与踩坑记录_第7张图片
  2. 注意virtual call包含interface的情况。【软件分析】Tai-e实验代码理解与踩坑记录_第8张图片
    【软件分析】Tai-e实验代码理解与踩坑记录_第9张图片
	if(jClass.isInterface()){
		q.addAll(hierarchy.getDirectImplementorsOf(jClass));
		q.addAll(hierarchy.getDirectSubinterfacesOf(jClass));
	}else {
    	q.addAll(hierarchy.getDirectSubclassesOf(jClass));
	}

实现过程间常量传播

  1. edge transfer逻辑:定义了 transferEdge(edge, fact) 函数来实现 edge transfer【软件分析】Tai-e实验代码理解与踩坑记录_第10张图片
  • normal edge黑实线:transferEdge(edge, fact) = fact
  • call-to-return edge黑虚线:左值LHS kill掉,其他往下传。
    • 若左侧没有变量的调用,比如 m(…):不修改 fact,edge transfer 是一个恒等函数。
  • call-edge:将实参(argument)在调用点中的值传递给被调用函数的形参(parameter)。返回值为被调用函数的形参,如x。
    • 首先从调用点的 OUT fact 中获取实参的值
    • 返回一个新的 fact
    • 这个 fact 把形参映射到它对应的实参的值
    • 易错点:
	for(int i = 0; i < params.size(); i++){//callee中的param与Invoke中的arg值一一对应
    	res.update(params.get(i), callSiteOut.get(args.get(i)));
    }
  • return edge:edge transfer 函数将被调用方法的返回值传递给调用点等号左侧的变量。
    • 从被调用方法的 exit 节点的 OUT fact 中获取返回值(可能有多个,你需要思考一下该怎么处理)

    • 返回一个将调用点等号左侧的变量映射到返回值的 fact。(对应等号左边的变量)

    • 如果该调用点等号左侧没有变量,那么 edge transfer 函数仅会返回一个空 fact。

    • 易错点:多个返回值的处理利用cp.meetValue

	for (Var var : edge.getReturnVars()) {
		val = cp.meetValue(val, returnOut.get(var));
    }
  1. 理解callNode和NonCallNode的区别。都是利用返回值判断OUT值是否改变,但CallNode调用TransferEdge实现对IN的修改,再与没变的OUT(存储OLD_OUT)进行对比,就实现判断OUT值是否改变。这也是为什么“你在实现 transfer*Edge() 方法的时候,不应该修改第二个参数,也就是该边的源节点的 OUT fact。”
/**
 * Dispatches {@code Node} to specific node transfer functions for
 * call nodes and non-call nodes.判断OUT是否有变化
 */
@Override
public boolean transferNode(Node node, Fact in, Fact out) {
    if (icfg.isCallSite(node)) {
        return transferCallNode(node, in, out);
    } else {
        return transferNonCallNode(node, in, out);
    }
}

实现过程间 Worklist 求解器

过程间与过程内求解器仅有两处不同:

  1. 在计算一个节点的 IN fact 时,过程间求解器需要对传入的 edge 和前驱们的 OUT facts 应用 edge transfer 函数(transferEdge)。过程内直接是OUT[B]。在这里插入图片描述
  2. 个人不太懂“仅需要”,感觉逻辑和过程间几乎一样。因为其他方法的 entry 节点和非 entry 节点还是要设置initial fact,否则会报错。可能这是实现meetInto策略的原因,算法本身没有特殊要求?不懂。

在初始化的过程中,过程间求解器需要初始化程序中所有的 IN/OUT fact,也就是 ICFG 的全部节点。但你仅需要对 ICFG 的 entry 方法(比如 main 方法)的 entry 节点设置 boundary fact。这意味着其他方法的 entry 节点和非 entry 节点的初始 fact 是一样的。

你可能感兴趣的:(软件分析,java)