【南大静态代码分析】作业 5:非上下文敏感指针分析

作业 5:非上下文敏感指针分析

题目链接:https://tai-e.pascal-lab.net/pa5.html

评测链接:https://oj.pascal-lab.net/problem

作业出品:南京大学《软件分析》课程,谭添、李樾

代码讲解

  • addReachable

这个函数的作用是遇到可达方法的时候构造调用图,同时对方法中的 NEWCOPYSTATIC LOAD FIELDSTATIC STORE FIELD,和 STATIC INVOKE 语句进行处理。

这里有一个难理解的点,伪代码中有 \(S \cup= S_m\) ,用来添加新可达方法中的语句,后面对这些语句再遍历,但是可达方法中的语句一定也是可达的,所以标记方法可达和添加方法中的可达语句本质是一样的,可以通过遍历所有可达方法再获得其中的所有语句来实现,method.getIR().getStmts() ,不需要额外的空间来存储。

使用访问者模式来实现,避免不同类型的语句使用多个 if-else 的情况。一些具体的 Stmt 子类中提供了 accept 的具体实现,传递 this ,调用 visitor 中重载的 visit 函数来执行相应函数。

java 复制代码
public <T> T accept(StmtVisitor<T> visitor) {
    return visitor.visit(this);
}

visitor 中重载的 visit 函数需要根据传递进来的 this 的具体类型,提供不同的实现。

StmtVisitor 接口中提供了对所有类型语句重载的默认 visit 函数。

java 复制代码
/**
 * Processes new reachable method.
 */
private void addReachable(JMethod method) {
    // TODO - finish me
    if (!callGraph.contains(method)) {  // 当前调用图中该方法是否可达
        callGraph.addReachableMethod(method);  // 添加可达方法到调用图
        for (Stmt stmt : method.getIR().getStmts()) {  // 遍历方法中所有语句,调用accept
            stmt.accept(stmtProcessor);
        }
    }
}
  • StmtProcessor

这是一个实现了 StmtVisitor 接口的内部私有类,负责针对不同语句实现具体的 visit 函数。

这里特别注意一下 STATIC INVOKE 情况,对于静态方法调用,处理流程和实例方法调用差不多,但是更简单,因为不需要传递 this 参数,获取目标方法的时候也不用 Dispatch ,即函数 resolveCallee 的第一个参数传递 null 即可。

java 复制代码
/**
 * Processes statements in new reachable methods.
 */
private class StmtProcessor implements StmtVisitor<Void> {
    // TODO - if you choose to implement addReachable()
    //  via visitor pattern, then finish me

    @Override
    public Void visit(New stmt) {  // 对于NEW语句,例如 x = new X()
        Obj obj = heapModel.getObj(stmt);  // 从模拟堆中获取当前语句的allocation site
        VarPtr varPtr = pointerFlowGraph.getVarPtr(stmt.getLValue());  // 获取左值变量指针
        workList.addEntry(varPtr, new PointsToSet(obj));  // 添加(x,o)到worklist
        return null;
    }

    @Override
    public Void visit(Copy stmt) {  // 对于COPY语句,例如 x = y,左右值都是变量
        VarPtr lVar = pointerFlowGraph.getVarPtr(stmt.getLValue());  // 获取左值变量指针
        VarPtr rVar = pointerFlowGraph.getVarPtr(stmt.getRValue());  // 获取右值变量指针
        addPFGEdge(rVar, lVar);  // 添加边到pfg
        return null;
    }

    @Override
    public Void visit(LoadArray stmt) {  // 对于LOAD ARRAY语句,不处理
        return StmtVisitor.super.visit(stmt);
    }

    @Override
    public Void visit(StoreArray stmt) { // 对于STORY ARRAY语句,不处理
        return StmtVisitor.super.visit(stmt);
    }

    @Override
    public Void visit(LoadField stmt) {  // 对于LOAD FIELD语句
        JField loadField = stmt.getFieldRef().resolve();  // 获取要被load的字段
        if (loadField.isStatic()) {  // 只处理STATIC LOAD FIELD语句
            VarPtr lVar = pointerFlowGraph.getVarPtr(stmt.getLValue()); // 获取左值变量指针
            StaticField rStaticField = pointerFlowGraph.getStaticField(loadField);  // 获取静态字段指针
            addPFGEdge(rStaticField, lVar);  // 添加边到pfg
        }
        return null;
    }

    @Override
    public Void visit(StoreField stmt) {  // 对于STORE FIELD语句
        JField loadField = stmt.getFieldRef().resolve();  // 获取要被store的字段
        if (loadField.isStatic()) {  // 只处理STATIC STORE FIELD语句
            VarPtr rVar = pointerFlowGraph.getVarPtr(stmt.getRValue());  // 获取右值变量指针
            StaticField staticField = pointerFlowGraph.getStaticField(loadField);  // 获取静态字段指针
            addPFGEdge(rVar, staticField);  // 添加边到pfg
        }
        return null;
    }

    @Override
    public Void visit(Invoke stmt) {  // 对于STATIC INVOKE语句
        if (stmt.isStatic()) {
            Var lVar = stmt.getLValue();  // 获取左值变量
            JMethod callee = resolveCallee(null, stmt);  // 获取调用的函数
            Edge<Invoke, JMethod> edge = new Edge(CallKind.STATIC, stmt, callee);  // 构造静态类型函数调用边
            if (callGraph.addEdge(edge)) {  // 如果调用图中没有该边
                addReachable(callee);  // 添加该边到调用图
                InvokeExp invokeExp = stmt.getInvokeExp();  // 获取调用函数表达式
                for (int i = 0; i < invokeExp.getArgCount(); i++) {  // 按位置在实参和形参之间添加边到pfg
                    Var actual = invokeExp.getArg(i);
                    VarPtr actualPtr = pointerFlowGraph.getVarPtr(actual);
                    Var form = callee.getIR().getParam(i);
                    VarPtr formPtr = pointerFlowGraph.getVarPtr(form);
                    addPFGEdge(actualPtr, formPtr);
                }
                if (lVar != null) {  // 在所有返回值和接收参数之间添加边到pfg
                    for (Var returnVar : callee.getIR().getReturnVars()) {
                        VarPtr returnVarPtr = pointerFlowGraph.getVarPtr(returnVar);
                        VarPtr lVarPtr = pointerFlowGraph.getVarPtr(lVar);
                        addPFGEdge(returnVarPtr, lVarPtr);
                    }
                }
            }
        }
        return null;
    }
}
  • addPFGEdge

这个函数的作用是在 PFG 中添加一条指针传递边。

java 复制代码
/**
 * Adds an edge "source -> target" to the PFG.
 */
private void addPFGEdge(Pointer source, Pointer target) {
    // TODO - finish me
    if (pointerFlowGraph.addEdge(source, target)) {
        if (!source.getPointsToSet().isEmpty()) {
            workList.addEntry(target, source.getPointsToSet());
        }
    }
}
  • analyze

这个函数的作用是实现 worklist 工作流程。

java 复制代码
/**
 * Processes work-list entries until the work-list is empty.
 */
private void analyze() {
    // TODO - finish me
    while (!workList.isEmpty()) {
        WorkList.Entry entry = workList.pollEntry();
        Pointer pointer = entry.pointer();
        PointsToSet pts = entry.pointsToSet();
        PointsToSet difference = propagate(pointer, pts);

        if (pointer instanceof VarPtr varPtr) {  // 如果是变量指针才可能继续更新Allocation Site
            Var var = varPtr.getVar();  // 获取变量
            for (Obj obj : difference) {  // 遍历所有新的Allocation Site
                for (StoreField storeField : var.getStoreFields()) {  // 如果是实例StoreFields
                    JField field = storeField.getFieldRef().resolve();
                    InstanceField instanceField = pointerFlowGraph.getInstanceField(obj, field);
                    Var rVar = storeField.getRValue();
                    VarPtr rVarPtr = pointerFlowGraph.getVarPtr(rVar);
                    addPFGEdge(rVarPtr, instanceField);
                }
                for (LoadField loadField : var.getLoadFields()) {  // 如果是实例LoadFields
                    JField field = loadField.getFieldRef().resolve();
                    InstanceField instanceField = pointerFlowGraph.getInstanceField(obj, field);
                    Var lVar = loadField.getLValue();
                    VarPtr lVarPtr = pointerFlowGraph.getVarPtr(lVar);
                    addPFGEdge(instanceField, lVarPtr);
                }
                for (StoreArray storeArray : var.getStoreArrays()) { // 如果是StoreArrays
                    Var rVar = storeArray.getRValue();
                    VarPtr rVarPtr = pointerFlowGraph.getVarPtr(rVar);
                    ArrayIndex arrayIndex = pointerFlowGraph.getArrayIndex(obj);
                    addPFGEdge(rVarPtr, arrayIndex);
                }
                for (LoadArray loadArray : var.getLoadArrays()) {  // 如果是LoadArrays
                    Var lVar = loadArray.getLValue();
                    VarPtr lVarPtr = pointerFlowGraph.getVarPtr(lVar);
                    ArrayIndex arrayIndex = pointerFlowGraph.getArrayIndex(obj);
                    addPFGEdge(arrayIndex, lVarPtr);
                }
                processCall(var, obj);  // 处理该变量调用的实例方法
            }
        }
    }
}
  • propagate

这个函数有两个作用,一是求 pointsToSetpt(pointer) 的差集,二是传递 pointsToSetpt(pointer)

其中 if (!difference.isEmpty()) 这个判断非常重要,如果差集是空,说明pointer 的指针集中已经包含了 pointsToSetpointer 的指针集并没有被更新,不需要执行后面的传递差集到 pointer 后继指针的操作。否则在循环引用的情况下会出现死循环,worklist 中始终添加新的元素。测试用例 Assign2.java 将无法通过。

java 复制代码
/**
 * Propagates pointsToSet to pt(pointer) and its PFG successors,
 * returns the difference set of pointsToSet and pt(pointer).
 */
private PointsToSet propagate(Pointer pointer, PointsToSet pointsToSet) {
    // TODO - finish me
    PointsToSet difference = new PointsToSet();
    PointsToSet ptn = pointer.getPointsToSet();
    for (Obj obj : pointsToSet) {
        if (!ptn.contains(obj)) {
            ptn.addObject(obj);
            difference.addObject(obj);
        }
    }
    if (!difference.isEmpty()) {
        for (Pointer s : pointerFlowGraph.getSuccsOf(pointer)) {
            workList.addEntry(s, pointsToSet);
        }
    }
    return difference;
}
  • processCall

这个函数的作用是处理实例方法调用,与静态方法调用的差异就是多了 thisreceiver objectAllocation Site 的传递(并没有添加 PFG 边)。

注意构造调用图边的时候要考虑所有非静态方法调用的情况(INTERFACEDYNAMICSPECIALVIRTUAL)。

java 复制代码
/**
 * Processes instance calls when points-to set of the receiver variable changes.
 *
 * @param var  the variable that holds receiver objects
 * @param recv a new discovered object pointed by the variable.
 */
private void processCall(Var var, Obj recv) {   // 仅仅是非静态方法调用(静态方法调用在addReachable中已经调用了)
    // TODO - finish me
    for (Invoke invoke : var.getInvokes()) {
        JMethod callee = resolveCallee(recv, invoke);
        Var thisVar = callee.getIR().getThis();
        VarPtr thisVarPtr = pointerFlowGraph.getVarPtr(thisVar);
        workList.addEntry(thisVarPtr, new PointsToSet(recv));
        Edge edge = null;
        if (invoke.isInterface()) {
            edge = new Edge(CallKind.INTERFACE, invoke, callee);
        } else if (invoke.isDynamic()) {
            edge = new Edge(CallKind.DYNAMIC, invoke, callee);
        } else if (invoke.isSpecial()) {
            edge = new Edge(CallKind.SPECIAL, invoke, callee);
        } else if (invoke.isVirtual()) {
            edge = new Edge(CallKind.VIRTUAL, invoke, callee);
        } else {
            edge = new Edge(CallKind.OTHER, invoke, callee);
        }
        if (callGraph.addEdge(edge)) {
            addReachable(callee);
            InvokeExp invokeExp = invoke.getInvokeExp();
            for (int i = 0; i < invokeExp.getArgCount(); i++) {
                Var actual = invokeExp.getArg(i);
                VarPtr actualPtr = pointerFlowGraph.getVarPtr(actual);
                Var form = callee.getIR().getParam(i);
                VarPtr formPtr = pointerFlowGraph.getVarPtr(form);
                addPFGEdge(actualPtr, formPtr);
            }
            Var lVar = invoke.getLValue();

            if (lVar != null) {
                for (Var returnVar : callee.getIR().getReturnVars()) {
                    VarPtr returnVarPtr = pointerFlowGraph.getVarPtr(returnVar);
                    VarPtr lVarPtr = pointerFlowGraph.getVarPtr(lVar);
                    addPFGEdge(returnVarPtr, lVarPtr);
                }
            }
        }

    }
}

评测结果