作业 5:非上下文敏感指针分析
题目链接:https://tai-e.pascal-lab.net/pa5.html
评测链接:https://oj.pascal-lab.net/problem
作业出品:南京大学《软件分析》课程,谭添、李樾
代码讲解
addReachable
这个函数的作用是遇到可达方法的时候构造调用图,同时对方法中的 NEW
,COPY
,STATIC LOAD FIELD
,STATIC 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
这个函数有两个作用,一是求 pointsToSet
和 pt(pointer)
的差集,二是传递 pointsToSet
到 pt(pointer)
。
其中 if (!difference.isEmpty())
这个判断非常重要,如果差集是空,说明pointer
的指针集中已经包含了 pointsToSet
,pointer
的指针集并没有被更新,不需要执行后面的传递差集到 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
这个函数的作用是处理实例方法调用,与静态方法调用的差异就是多了 this
到 receiver object
的 Allocation Site
的传递(并没有添加 PFG
边)。
注意构造调用图边的时候要考虑所有非静态方法调用的情况(INTERFACE
,DYNAMIC
,SPECIAL
,VIRTUAL
)。
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);
}
}
}
}
}