程序静态分析
程序静态分析(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); } }