写在前面
参考这个项目实现的:南大软件分析作业代码非官方实现
这个学起来就难,概念落到代码上更难…这里就记录一下我的理解(还是要看完前六节课)
顺便,期待下今年的Tai-e项目
Test Framework
package com.CP;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import soot.*;
import soot.options.Options;
import java.util.Arrays;
public class ConstantPropagationTest {
@Before
public void init(){
soot.G.reset();
Options.v().set_allow_phantom_refs(true);//容错
Options.v().set_src_prec(Options.src_prec_class);
Options.v().set_process_dir(Arrays.asList("target/classes/com/TestCP"));
Options.v().set_whole_program(true);
Options.v().set_prepend_classpath(true);
Options.v().set_output_format(Options.output_format_jimple);
Options.v().setPhaseOption("jb", "use-original-names:true");//保留变量原有名字
Options.v().set_keep_line_number(true);//保留原始行号
Scene.v().loadNecessaryClasses();
}
@Test
public void test(){
PackManager.v().getPack("jtp").add(new Transform("jtp.CP", new CPTransformer()));
for (SootClass appClazz : Scene.v().getApplicationClasses()) {
for (SootMethod method : appClazz.getMethods()) {
Body body = method.retrieveActiveBody();
PackManager.v().getPack("jtp").apply(body);
}
}
}
@After
public void write(){
PackManager.v().writeOutput();
}
}
看过之前的soot使用笔记的话,这些代码大部分不成问题,稍微有一些也添加了注释
CPTransformer
package com.CP;
import soot.Body;
import soot.BodyTransformer;
import soot.Unit;
import soot.toolkits.graph.BriefUnitGraph;
import soot.toolkits.graph.UnitGraph;
import java.util.Map;
public class CPTransformer extends BodyTransformer {
@Override
protected void internalTransform(Body body, String s, Map<String, String> map) {
UnitGraph graph = new BriefUnitGraph(body);
CPFlowAnalysis CP = new CPFlowAnalysis(graph);
System.out.println(String.format("=========== Method %s ============\n", body.getMethod().getName()));
for (Unit unit : body.getUnits()) {
System.out.println(String.format("Before %s: %s", unit, CP.getFlowBefore(unit)));
System.out.println(String.format("After %s: %s", unit, CP.getFlowAfter(unit)));
System.out.println("==================================================================\n");
}
}
}
这里CPFlowAnalysis CP = new CPFlowAnalysis(graph);
就是之前提到的soot中flow analysis framework
中我们自己要创建设计的分析类,按照之前的理论框架来建缺少的类就可以了(这时候再看这些代码就没有那么陌生冰冷了)
CPFlowAnalysis
package com.CP;
import soot.Local;
import soot.Unit;
import soot.Value;
import soot.jimple.DefinitionStmt;
import soot.toolkits.graph.DirectedGraph;
import soot.toolkits.scalar.ForwardFlowAnalysis;
public class CPFlowAnalysis extends ForwardFlowAnalysis<Unit, CPFlowSet> {
public CPFlowAnalysis(DirectedGraph<Unit> graph) {
super(graph);
super.doAnalysis();
}
@Override
protected CPFlowSet newInitialFlow() {
return new CPFlowSet();//may,初始化为空
}
@Override
protected void merge(CPFlowSet in1, CPFlowSet in2, CPFlowSet out) {
CPFlowSet meetResult = CPFlowSet.meet(in1, in2);
copy(meetResult, out);
}
@Override
protected void copy(CPFlowSet src, CPFlowSet dest) {
dest.copyFrom(src);
}
@Override
protected void flowThrough(CPFlowSet in, Unit unit, CPFlowSet out) {
copy(in, out);
if(unit instanceof DefinitionStmt){
DefinitionStmt ass = (DefinitionStmt) unit;
Value lVal = ass.getLeftOp();
if(lVal instanceof Local){
Local local = (Local) lVal;
Value rVal = ass.getRightOp();
CPValue rCPVal = in.computeValue(rVal);
out.put(local, rCPVal);
}
}
}
}
简单说一下这个flowThrough
,soot.jimple.DefinitionStmt
这个类代表了所有的定义语句,若左边的是变量,则我们拿到右边的式子(情况会稍微有些复杂),进一步处理后再放入out中
_
:通配符,kill
掉x对应的任意值
值域的抽象
参考课程作业的要求
CPFlowSet
是一对对的pair(x,v):x为变量,v是具体值
package com.CP;
import soot.Local;
import soot.Value;
import soot.jimple.*;
import java.util.*;
public class CPFlowSet {
Map<Local, CPValue> delegateMap;//Local中也会有我们不太需要关注的变量
public CPFlowSet() {
this(new HashMap<>());//can not new a Map
}
public CPFlowSet(Map<Local, CPValue> map){
this.delegateMap = map;
}
public static CPFlowSet meet(CPFlowSet in1, CPFlowSet in2) {//对应gen
CPFlowSet result = new CPFlowSet();
Set<Local> localSet = new HashSet<>();
localSet.addAll(in1.keySet());
localSet.addAll(in2.keySet());
for(Local local: localSet){
CPValue v1 = in1.get(local);
CPValue v2 = in2.get(local);
CPValue meetVal = CPValue.meetValue(v1, v2);
result.put(local, meetVal);
}
return result;
}
public Set<Local> keySet() {
return delegateMap.keySet();
}
public void copyFrom(CPFlowSet src) {
delegateMap.putAll(src.delegateMap);
}
public CPValue computeValue(Value rVal) {
if(rVal instanceof Local){
return get((Local) rVal);//变量对应值
}else if(rVal instanceof IntConstant){
return CPValue.makeConstant(((IntConstant) rVal).value);//直接赋值为常量
}else if(rVal instanceof BinopExpr){
BinopExpr Expr = (BinopExpr) rVal;
Value op1 = Expr.getOp1();//左
CPValue op1Val = computeValue(op1);
Value op2 = Expr.getOp2();//右
CPValue op2Val = computeValue(op2);
// 如果其中有未定义,那么整个计算也是未定义的
if (op1Val == CPValue.getUndef() || op2Val == CPValue.getUndef()) {
return CPValue.getNAC();
}
// 其中有一个不是常数,那么整个计算就不是常数
if (op1Val == CPValue.getNAC() || op2Val == CPValue.getNAC()) {
return CPValue.getNAC();
}
// 两个都是常量
if (Expr instanceof AddExpr) {
return CPValue.makeConstant(op1Val.val() + op2Val.val());
} else if (Expr instanceof SubExpr) {
return CPValue.makeConstant(op1Val.val() - op2Val.val());
} else if (Expr instanceof MulExpr) {
return CPValue.makeConstant(op1Val.val() * op2Val.val());
} else if (Expr instanceof DivExpr) {
return CPValue.makeConstant(op1Val.val() / op2Val.val());
}
}
//对于其他的语句类型就不再考虑,in是什么out是什么,保守估计为NAC
return CPValue.getNAC();
}
public CPValue get(Local local) {
return delegateMap.computeIfAbsent(local, l -> CPValue.getUndef());
}//存在key对应的value则返回,不存在用lamba表达式返回undefined
public CPValue put(Local local, CPValue value) {
return delegateMap.put(local, value);
}
@Override
public String toString() {
return delegateMap.toString();
}//方便查看
}
对于computeValue(Value rVal)
这个函数,代码实现功能对应如下:
这里就参考视频中理解,借鉴的项目中有一个是UNDEF
则为NAC
,觉得不太对???
CPValue
package com.CP;
public class CPValue {
private final static CPValue NAC = new CPValue() {
@Override
public int hashCode() {
return Integer.MIN_VALUE;
}
};
private final static CPValue UNDEF = new CPValue() {
@Override
public int hashCode() {
return Integer.MAX_VALUE;
}
};
private int val;
private CPValue() { }
private CPValue(int val) {
this.val = val;
}
public int val() {
return val;
}
public static CPValue getNAC() {
return NAC;
}
public static CPValue getUndef() {
return UNDEF;
}
public static CPValue makeConstant(int val) {
return new CPValue(val);
}
public static CPValue meetValue(CPValue value1, CPValue value2) {
if (value1 == getUndef()) {
return value2;
}
if (value2 == getUndef()) {
return value1;
}
if (value1 == getNAC() || value2 == getNAC()) {
return getNAC();
}
// 两个具体的值meet
if (value1.val() == value2.val()) {
return makeConstant(value1.val());
}
return getNAC();
}
@Override
public String toString() {
if (this == NAC) {
return "NAC";
}
if (this == UNDEF) {
return "UNDEF";
}
return String.valueOf(val);
}
}
meetValue
对应要实现的:
测试
package com.TestCP;
public class CPTest {
public void nonDistributiveTest() {
int a;
int b;
if (Math.random() > 0.5) {
a = 1;
b = 9;
} else {
a = 9;
b = 1;
}
int c = a + b;
}
public void sameValueMeetTest() {
int a;
int b;
if (Math.random() > 0.5) {
a = 1; // a: same value as false branch
b = 2; // b: not same value as false branch
} else {
a = 1;
b = 1;
}
int c = a + b;
int d = a + a;
int e = b + b;
}
public void testParam(int x) {
int a;
a = x;
int b = a;
}
}
简单看一下结果:
如果之前用的是AssignStmt
而不是DefinitionStmt
,就不会有this
了
$stack4
:random产生的值,对应jimple文件更容易看
$stack5
:if中条件值
对于第二个函数:
最后一个:
这几个例子看样子都是对的