soot数据流 -- 基于soot的过程内数据流分析

原文出处

程序静态分析

程序静态分析(program static analysis)是指在不运行代码的方式下,通过词法分析、语法分析、控制流、数据流分析等技术对程序代码进行扫描,验证代码是否满足规范性、安全性、可靠性、可维护性等指标的一种代码分析技术。(百度百科


静态分析中的xxx-sensitive的一些理解:

(此内容来自知乎

xxx-sensitive是指静态分析中用于降低误报(false positive)的技术。存在flow-、path-、context-。

flow-insensitive:是把statements当作一个集合来看的,各个statement之间没有顺序,所以control flow statement(if、while)可以直接删除;flow-sensitive是说要关注statements之间的先后顺序。

path-insensitive:if、while静态分析时不知道动态的执行路线(path),所以一般会把它们不同的分支的数据流集merge起来。而path-sensitive则针对不同的路径的数据集不会merge,而是分别进行分析;

context-insensitve:只关心function之间的数据传递(参数、返回值、side-effect)而忽略了同一函数在不同call side下不同的context,即忽略了call stack。将call site和return当作goto,并添加一些赋值语句,这样造成的情况是,第一个call site处正常,第二个call site时返回值可能出现两个。

 

静态分析时,无论是分析源代码还是目标代码,分析的对象(方法、语句、变量)都只有一份:同一个方法我们只会写一个方法体(方法体里的语句也就只有一份)。同一个变量只会声明一次。然后动态运行程序的时候:

一个方法可能会被调用N次,每次调用的上下文可以不一样,不同上下文中这个方法里的变量的值会不同;一个方法里,一个变量的不同位置的值也会不一样;一个方法里同一个位置的变量的值在程序执行不同路径时也不一样。写的方法、语句、变量在动态运行时仿佛有了“分身”,每个分身都有自己的值。静态分析的时候对于同一个对象只能看到一个实体,如果直接分析,一个变量所有“分身”的相关属性会全部合并,并且一个变量的属性合并了,会影响其他变量的分析结果。

静态分析为得到准确的结果,就得为分析的对象模拟动态运行时的分身。

xxx-sensitive就是在静态分析时,按照xxx给程序里的对象(模拟动态运行)创建“分身”(或者说按照xxx区分分析对象):按照上下文区分叫作context-sensitive;按照位置区分叫作flow-sensitive;按照路径区分叫作path-sensitive。区分之后就可以减少false positive。

数据流分析

(此内容来自龙书)

数据流分析:指的是一组用来获取有关数据如何沿着程序执行路径(control-flow graph)流动的相关信息的技术。

 

在所有的数据流分析应用中,我们都会把每个程序点和一个数据流(data-flow value)关联起来。这个值是在该点可能观察到的所有程序状态的集合的抽象表示。所有可能的数据流值的集合称为这个数据流应用的域(domain)。

 

我们把每个语句s之前和之后的数据流值分别记为IN[s]和OUT[s]。数据流问题(data-flow problem)就是要对一组约束求解。这组约束对所有的语句s限定了IN[s]和OUT[s]之间的关系。约束分为两种:基于语句语义(传递函数)的约束和基于控制流的约束。

 

数据流分析一般分为:intra-procedural analysis和inter-proceduralanalysis,在上篇Phase中提到,jtp pack和wjtp pack可以分别被用来实现自定义的数据流分析

 

实现数据流分析前需要搞清楚的问题

数据流的方向(前向、后向);交汇运算采用交集还是并集(当path-insenstitive时);传递函数;数据流集合的初始化。

采用soot框架实现过程内数据流分析

(此部分内容参考点击打开链接

1.      过程内数据流分析

过程内数据流分析(intra-proceduraldata-flow analysis)指在一个单独方法的控制流图上操作。在soot中的控制流图为UnitGraph。UnitGraph中节点表示statements,如果在控制流上一个表示source node流向target node,那么这两个结点存在边(edge)。

数据流分析都与unitgraph每个节点上的两个元素有关,这两个集合被称为:in-set和out-set。这些集合会被初始化,然后沿着语句节点传播,指导一个定点抵达才停止。

最后,你要做的就是检查每个句子的前后的flow set。通过设计的数据流分析,你的flow sets应该会直接告诉你所需要的信息。

 

2.      Forward、backward orbranched(解决数据流的方向问题)?

在soot中FlowAnalysis存在三种不同类型的方法:

ForwardFlowAnalysis:这个分析以UnitGraph的entrystatement作为开始并开始传播;

BackwardsFlowAnalysis:这个分析以UnitGraph的exit node(s)作为分析并且向后开始传播(当然可以将UnitGraph转换产生inverseGraph,然后再使用ForwardFlowAnalysis进行分析);

ForwardBranchedFlowAnalysis:这个分析本质上也是Forward分析,但是它允许你在不同分支处传递不同的flow sets。例如:如果传播到如if(p!=null)语句处,当“p is not null”时,传播进入“then”分支,当“p is null”时传播进入“else”分支(Forward、backward分析都在分支处会将分析结果merge)。

 

3.      实现过程间数据流分析的关键方法

Constructor

必须实现一个携带DirectedGraph作为参数的构造函数,并且将该参数传递给super constructor。然后,在构造函数结束时调用doAnalysis(),doAnalysis()将真正执行数据流分析。而在调用super constructor和doAnalysis之间,可以自定义数据分析结构。

<span style="font-size:14px;">public MyAnalysis(DirectedGraph graph) { //构造函数
	super(graph);
	// TODO Auto-generated constructor stub
	emptySet = new ArraySparseSet();
	doAnalysis();//执行fixed-point
}</span>


newInitialFlow()和entryInitialFlow()(数据流集合的初始化问题

newInitialFlow()方法返回一个对象,这个对象被赋值给每个语句的in-set和out-set集合,除过UnitGraph的第一个句子的in-set集合(如果你实现的是backwards分析,则是一个exit statement语句)。第一个句子的in-set集合由entryInitialFlow()初始化。

<span style="font-size:14px;">@Override
protected Object newInitialFlow() {
	// TODO Auto-generated method stub
	return emptySet.emptySet();
}

@Override
protected Object entryInitialFlow() {
	// TODO Auto-generated method stub
	return emptySet.emptySet();
}</span>

copy(..)

copy(..)方法携带两个参数,一个source和一个target。它仅仅实现将source中的元素拷贝到target中。

<span style="font-size:14px;">@Override
protected void copy(Object source, Object dest) {
	// TODO Auto-generated method stub
	FlowSet srcSet = (FlowSet)source,
	destSet = (FlowSet)dest;
	srcSet.copy(destSet);
}</span>


merge(..)(数据流的交汇运算问题

merge(..)方法被用来在control-flow的合并点处合并数据流集,例如:在句子(if/then/else)分支的结束点。与copy(..)不同的是,它携带了三个参数,一个参数是来自左边分支的out-set,一个参数是来自右边分支的out-set,另外一个参数是两个参数merge后的集合,这个集合将是合并点的下一个句子的in-set集合。

注:merge(..)本质上指的是控制流的交汇运算,一般根据待分析的具体问题来决定采用并集还是交集。

<span style="font-size:14px;">@Override
protected void merge(Object in1, Object in2, Object out) {
	// TODO Auto-generated method stub
	FlowSet inSet1 = (FlowSet)in1,
	inSet2 = (FlowSet)in2,
	outSet = (FlowSet)out;
	//inSet1.union(inSet2, outSet);
	inSet1.intersection(inSet2, outSet);
}</span>

flowThrough(..)(数据流的传递函数问题

flowThrough(..)方法是真正执行流函数,它有三个参数:in-set、被处理的节点(一般指的就是句子Unit)、out-set。这个方法的实现内容完全取决于你的分析。

注:flowThrough()本质上就是一个传递函数。在一个语句之前和之后的数据流值受该语句的语义的约束。比如,假设我们的数据流分析涉及确定各个程序点上各变量的常量值。如果变量a在执行语句b=a之前的值为v,那么在该语句之后a和b的值都是v。一个赋值语句之前和之后的数据流值的关系被称为传递函数。针对前向分析和后向分析,传递函数有两种风格。

<span style="font-size:14px;">@Override
protected void flowThrough(Object in, Object d, Object out) {
	// TODO Auto-generated method stub
	FlowSet inSet = (FlowSet)in,
	outSet = (FlowSet)out;
	Unit u = (Unit) d;
	kill(inSet,u,outSet);
	gen(outSet,u);
}

private void kill(FlowSet inSet, Unit u, FlowSet outSet) {
	// TODO Auto-generated method stub
	FlowSet kills = (FlowSet)emptySet.clone();//Unit的kills
	Iterator defIt = u.getDefBoxes().iterator();
	while(defIt.hasNext()){
		ValueBox defBox = (ValueBox)defIt.next();
			
		if(defBox.getValue() instanceof Local){
			Iterator inIt = inSet.iterator();
			while(inIt.hasNext()){
				Local inValue = (Local)inIt.next();
				if(inValue.equivTo(defBox.getValue())){
					kills.add(defBox.getValue());
			}
		}
	<span style="white-space:pre">	</span>}
	}
	inSet.difference(kills, outSet);
}

private void gen(FlowSet outSet, Unit u) {
	// TODO Auto-generated method stub
	Iterator useIt = u.getUseBoxes().iterator();
	while(useIt.hasNext()){
		ValueBox e = (ValueBox)useIt.next();
		if(e.getValue() instanceof Local)
			outSet.add(e.getValue());
	}
}</span>


Flow sets

(此部分内容参考点击打开链接

    在soot中,flow sets代表control-flowgraph中与节点相关的数据集合。Flow set存在有界限的(interface BoundedFlowSet)和无界限的(interfaceFlowSet)两种表达。有界限的集合知道可能值的全体集合,而无界限的集合则不知道。

Interface FlowSet<T>提供的关键方法有

<span style="font-size:14px;">FlowSet<T> clone() //克隆当前FlowSet的集合
FlowSet<T> emptySet() //返回一个空集,通常比((FlowSet)clone()).clear()效率更高
void copy(FlowSet<T> dest) //拷贝当前集合到dest集合中
void union(FlowSet<T> other) //FlowSet∪other = FlowSet
void union(FlowSet<T> other,FlowSet<T> dest) // FlowSet∪other = dest,其中other、dest可以与该FlowSet一样
void intersection(FlowSet<T> other) //FlowSet∩other = FlowSet
void intersection(FlowSet<T> other,FlowSet<T> dest) // FlowSet∩other = FlowSet,其中,dest、other可以和该FlowSet一样
void difference(FlowSet<T> other) // FlowSet-other = FlowSet
void difference(FlowSet<T> other,FlowSet<T> dest) // FlowSet-other = dest,其中,dest、other和FlowSet可能相同。
</span>

还有isEmpty()、size()、add(T obj)、remove(T obj)、contains(Tobj)、isSubSet(FlowSet<T> other)、iterator()、toList()等。

上述方法足以使flow sets成为一个有效的lattice元素。


当实现BoundedFlowSet时,它需要提供方法,该方法能够产生set‘s complement和its topped set(一个lattice element包括所有的可能的值的集合)。

Soot提供了四种flow sets的实现:ArraySparseSet,ArrayPackedSet,ToppedSet和DavaFlowSet。

ArraySparseSet:是一个无界限的flowset。该set代表一个数组引用。注意:当比较元素是否相等时,一般使用继承自Object对象的equals。但是在soot中的元素都是代表一些代码结构,不能覆写equals方法。而是实现了interface soot.EquivTo。因此,如果你需要一个包含类似binary operation expressions的集合,你需要使用equivTo方法实现自定义的比较方法去比较是否相等


针对intra-procedural analysis,本人实现了一个活跃变量的代码

import java.util.Iterator;

import soot.Local;
import soot.Unit;
import soot.ValueBox;
import soot.toolkits.graph.DirectedGraph;
import soot.toolkits.scalar.ArraySparseSet;
import soot.toolkits.scalar.BackwardFlowAnalysis;
import soot.toolkits.scalar.FlowSet;

class MyAnalysis extends BackwardFlowAnalysis{
	
	private FlowSet emptySet;

	public MyAnalysis(DirectedGraph graph) { //构造函数
		super(graph);
		// TODO Auto-generated constructor stub
		emptySet = new ArraySparseSet();
		doAnalysis();//执行fixed-point
	}

	@Override
	protected void flowThrough(Object in, Object d, Object out) {
		// TODO Auto-generated method stub
		FlowSet inSet = (FlowSet)in,
				outSet = (FlowSet)out;
		Unit u = (Unit) d;
		kill(inSet,u,outSet);
		gen(outSet,u);
	}

	private void kill(FlowSet inSet, Unit u, FlowSet outSet) {
		// TODO Auto-generated method stub
		FlowSet kills = (FlowSet)emptySet.clone();//Unit的kills
		Iterator defIt = u.getDefBoxes().iterator();
		while(defIt.hasNext()){
			ValueBox defBox = (ValueBox)defIt.next();
			
			if(defBox.getValue() instanceof Local){
				Iterator inIt = inSet.iterator();
				while(inIt.hasNext()){
					Local inValue = (Local)inIt.next();
					if(inValue.equivTo(defBox.getValue())){
						kills.add(defBox.getValue());
					}
				}
			}
		}
		inSet.difference(kills, outSet);
	}

	private void gen(FlowSet outSet, Unit u) {
		// TODO Auto-generated method stub
		Iterator useIt = u.getUseBoxes().iterator();
		while(useIt.hasNext()){
			ValueBox e = (ValueBox)useIt.next();
				if(e.getValue() instanceof Local)
					outSet.add(e.getValue());
			}
		}


	@Override
	protected Object newInitialFlow() {
		// TODO Auto-generated method stub
		return emptySet.emptySet();
	}

	@Override
	protected Object entryInitialFlow() {
		// TODO Auto-generated method stub
		return emptySet.emptySet();
	}

	@Override
	protected void merge(Object in1, Object in2, Object out) {
		// TODO Auto-generated method stub
		FlowSet inSet1 = (FlowSet)in1,
				inSet2 = (FlowSet)in2,
				outSet = (FlowSet)out;
		//inSet1.union(inSet2, outSet);
		inSet1.intersection(inSet2, outSet);
	}

	@Override
	protected void copy(Object source, Object dest) {
		// TODO Auto-generated method stub
		FlowSet srcSet = (FlowSet)source,
				destSet = (FlowSet)dest;
		srcSet.copy(destSet);
	}

}

 

(TheSnowBoy_2) 附:

下面是另一个简单的例子,该例子是在Soot中实现了,直接拿过来放在这里,因为有助于理解。

摘自soot源码:

  • 目的 】 分析局部变量是否进行了初始化
  • 向前分析 】 也就是在不定点分析(分析元素是随机抽取的)的时候,看其结点的后继。(补充:向后分析,看某个结点的前驱)
  • ForwardFlowAnalysis 】该类接受两种类型的参数,这里给其传递 Unit ,FlowSet。
  • flowthough 】 规则十分简单,有定义的变量则加入。
  • 不定点分析 】使得程序考虑部分,而不需要从入口点开始,进行深度优先遍历等等,忽略了复杂的图的结构,使得算法具有 通用性,在 doAnalysis()  中。

/**
 * An analysis to check whether or not local variables have been initialised.
 * 
 * @author Ganesh Sittampalam
 * @author Eric Bodden
 */
public class InitAnalysis extends ForwardFlowAnalysis<Unit, FlowSet<Local>> {
    FlowSet<Local> allLocals;

    public InitAnalysis(UnitGraph g) {
        super(g);
        allLocals = new ArraySparseSet<Local>();        
        for (Local loc : g.getBody().getLocals()) {
            allLocals.add(loc);
        }

        doAnalysis();
    }

    @Override
    protected FlowSet<Local> entryInitialFlow() {
        return new ArraySparseSet<Local>();
    }
    
    @Override
    protected FlowSet<Local> newInitialFlow() {
        FlowSet<Local> ret = new ArraySparseSet<Local>();
        allLocals.copy(ret);
        return ret;
    }

    @Override
    protected void flowThrough(FlowSet<Local> in, Unit unit, FlowSet<Local> out) {
        in.copy(out);

        for (ValueBox defBox : unit.getDefBoxes()) {
            Value lhs = defBox.getValue();
            if (lhs instanceof Local) {
                out.add((Local) lhs);
            }
        }
    }

    @Override
    protected void merge(FlowSet<Local> in1, FlowSet<Local> in2, FlowSet<Local> out) {
        in1.intersection(in2, out);
    }
    
    @Override
    protected void copy(FlowSet<Local> source, FlowSet<Local> dest) {
        source.copy(dest);
    }

}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值