This commit is contained in:
Julian Dolby 2017-03-09 08:53:30 -05:00
commit f5c36d7411
4 changed files with 1205 additions and 0 deletions

View File

@ -0,0 +1,937 @@
package com.ibm.wala.examples.analysis.dataflow;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import com.ibm.wala.cfg.Util;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.dataflow.IFDS.ICFGSupergraph;
import com.ibm.wala.dataflow.IFDS.IFlowFunction;
import com.ibm.wala.dataflow.IFDS.IFlowFunctionMap;
import com.ibm.wala.dataflow.IFDS.IMergeFunction;
import com.ibm.wala.dataflow.IFDS.ISupergraph;
import com.ibm.wala.dataflow.IFDS.IUnaryFlowFunction;
import com.ibm.wala.dataflow.IFDS.PathEdge;
import com.ibm.wala.dataflow.IFDS.TabulationDomain;
import com.ibm.wala.dataflow.IFDS.TabulationProblem;
import com.ibm.wala.dataflow.IFDS.TabulationResult;
import com.ibm.wala.dataflow.IFDS.TabulationSolver;
import com.ibm.wala.ipa.callgraph.AnalysisCache;
import com.ibm.wala.ipa.callgraph.CGNode;
import com.ibm.wala.ipa.callgraph.CallGraph;
import com.ibm.wala.ipa.cfg.BasicBlockInContext;
import com.ibm.wala.shrikeBT.IConditionalBranchInstruction.Operator;
import com.ibm.wala.ssa.SSACheckCastInstruction;
import com.ibm.wala.ssa.SSAConditionalBranchInstruction;
import com.ibm.wala.ssa.SSAGetInstruction;
import com.ibm.wala.ssa.SSAInstruction;
import com.ibm.wala.ssa.SSAInvokeInstruction;
import com.ibm.wala.ssa.SSANewInstruction;
import com.ibm.wala.ssa.SSAPhiInstruction;
import com.ibm.wala.ssa.SSAPiInstruction;
import com.ibm.wala.ssa.SSAPutInstruction;
import com.ibm.wala.ssa.SSAReturnInstruction;
import com.ibm.wala.ssa.SymbolTable;
import com.ibm.wala.ssa.analysis.IExplodedBasicBlock;
import com.ibm.wala.types.ClassLoaderReference;
import com.ibm.wala.util.CancelException;
import com.ibm.wala.util.collections.HashSetFactory;
import com.ibm.wala.util.intset.IntIterator;
import com.ibm.wala.util.intset.IntSet;
import com.ibm.wala.util.intset.MutableIntSet;
import com.ibm.wala.util.intset.MutableMapping;
import com.ibm.wala.util.intset.MutableSparseIntSet;
/**
* IFDS-based analysis for detecting null-pointer dereferences. The analysis runs in
* the forward direction, tracking access paths that either may be null (if
* some statement may have written null to the location) or may-not be null
* (some statement wrote a non-null value into the location; if the may-not be
* null fact is unreachable, then the variable must be null). A null-pointer
* error is reported when a variable that may (or must) be null is
* de-referenced.
*
* @author Andrei Dan
*
*/
public class NullAnalysis {
/**
* Maximum length of the Access Paths
*/
protected final static int AP_LENGTH_BOUND = 2;
/**
* The id of the ZERO fact
*/
public static int ZERO;
/**
* Propagate the analysis to library calls. If false, then the analysis will
* assume that library calls return non null values.
*/
protected final static boolean ANALYZE_LIBRARY = false;
/**
* Value number for the auxiliary variable used to collect facts about the
* returned value of a function
*/
public static final int AUX_RETURN_VAL_NUM = Integer.MAX_VALUE;
/**
* The super-graph over which tabulation is performed
*/
protected final ICFGSupergraph supergraph;
private TabulationSolver<BasicBlockInContext<IExplodedBasicBlock>, CGNode, NullAnalysisFact> solver;
/**
* The tabulation domain
*/
protected final NullAnalysisDomain domain;
/**
* Returns the domain of this analysis
*
* @return domain
*/
public NullAnalysisDomain getDomain() {
return domain;
}
/**
* Returns the supergraph of the analyzed program
*
* @return supergraph
*/
public ISupergraph<BasicBlockInContext<IExplodedBasicBlock>, CGNode> getSupergraph() {
return supergraph;
}
/**
* Constructor of the MayNULL analysis
*
* @param cg
* @param cache
*/
public NullAnalysis(CallGraph cg, AnalysisCache cache) {
// we use an ICFGSupergraph, which basically adapts
// ExplodedInterproceduralCFG to the ISupergraph interface
this.supergraph = ICFGSupergraph.make(cg, cache);
domain = new NullAnalysisDomain();
// the ZERO fact
ZERO = domain.add(new NullAnalysisFact(null, null, null));
}
/**
* Returns the symbol table associated with the given node
*
* @param n
* @return symbol table
*/
public SymbolTable getSymbolTable(BasicBlockInContext<IExplodedBasicBlock> n) {
return n.getNode().getIR().getSymbolTable();
}
/**
* Returns the SSA instruction which generated the given node
*
* @param n
* @return SSA instruction
*/
public SSAInstruction getSSAInstr(BasicBlockInContext<IExplodedBasicBlock> n) {
return n.getDelegate().getInstruction();
}
/**
* Controls numbering of MayNULL and MayNotNULL facts for use in tabulation
*/
@SuppressWarnings("serial")
public class NullAnalysisDomain extends MutableMapping<NullAnalysisFact>
implements TabulationDomain<NullAnalysisFact, BasicBlockInContext<IExplodedBasicBlock>> {
@Override
public boolean hasPriorityOver(PathEdge<BasicBlockInContext<IExplodedBasicBlock>> p1,
PathEdge<BasicBlockInContext<IExplodedBasicBlock>> p2) {
// don't worry about work-list priorities
return false;
}
}
/**
* Returns a flow function that is a subset of the given function. The missing
* (key, value) pairs are filtered, depending of the source and destination
* nodes, according to the filteredEdges rules
*
* @param func
* @param src
* @param dest
* @return
*/
protected IUnaryFlowFunction filter(IUnaryFlowFunction func, BasicBlockInContext<IExplodedBasicBlock> src,
BasicBlockInContext<IExplodedBasicBlock> dest) {
return func;
}
protected class NullAnalysisFlowFunctions implements IFlowFunctionMap<BasicBlockInContext<IExplodedBasicBlock>> {
/**
* The domain used for the MayNULL analysis
*/
private final NullAnalysisDomain domain;
protected NullAnalysisFlowFunctions(NullAnalysisDomain domain) {
this.domain = domain;
}
/**
* Adds to the entry Facts set mayNULL or mayNotNULL facts about constant
* value numbers from the symbol table of the given entry block
*
* @param entryBlk
* @param entryFacts
*/
private void getEntryBlkFacts(BasicBlockInContext<IExplodedBasicBlock> entryBlk, MutableIntSet entryFacts) {
SymbolTable st = getSymbolTable(entryBlk);
int maxSt = st.getMaxValueNumber();
// for each entry of the symbol table
for (int i = 1; i <= maxSt; i++) {
if (st.isConstant(i) && !st.isNullConstant(i)) {
// if we have a constant different than NULL, add MayNotNULL
// fact about this value number
NullAnalysisFact mayNotNULLFact = new NullAnalysisFact(entryBlk.getNode(), i, NullSet.MayNotNULL);
int factIdx = domain.add(mayNotNULLFact);
entryFacts.add(factIdx);
}
if (st.isNullConstant(i)) {
// if we have a NULL constant, add a MayNULL fact about this
// value number
NullAnalysisFact mayNULLFact = new NullAnalysisFact(entryBlk.getNode(), i, NullSet.MayNULL);
int factIdx = domain.add(mayNULLFact);
entryFacts.add(factIdx);
}
}
}
/**
* Compose two flow functions.
*
* @param f1
* @param f2
* @return result(x) = f2(f1(x))
*/
private IUnaryFlowFunction compose(final IUnaryFlowFunction f1, final IUnaryFlowFunction f2) {
return new IUnaryFlowFunction() {
@Override
public IntSet getTargets(int d1) {
MutableSparseIntSet result = MutableSparseIntSet.makeEmpty();
IntSet imF1 = f1.getTargets(d1);
IntIterator iterator = imF1.intIterator();
while (iterator.hasNext()) {
int f1 = iterator.next();
result.addAll(f2.getTargets(f1));
}
return result;
}
};
}
/**
* Returns true if we can propagate the given fact after the given PI
* instruction. Used to stop propagating facts that contradict the condition
* associated with the PI instruction.
*
* @param piInstr
* @param src
* @param dest
* @param factType
* @return
*/
private boolean canPropagate(SSAPiInstruction piInstr, BasicBlockInContext<IExplodedBasicBlock> src,
BasicBlockInContext<IExplodedBasicBlock> dest, NullSet factType) {
SSAConditionalBranchInstruction branchInstr = (SSAConditionalBranchInstruction) piInstr.getCause();
boolean isUnaryCond = true;
boolean isEq = branchInstr.getOperator().equals(Operator.EQ);
boolean isNe = branchInstr.getOperator().equals(Operator.NE);
boolean isTrueBranch = dest.getNumber() == Util.getTakenSuccessor(supergraph.getCFG(src), src.getDelegate()).getNumber();
boolean isFalseBranch = dest.getNumber() == Util.getNotTakenSuccessor(supergraph.getCFG(src), src.getDelegate()).getNumber();
if (!isTrueBranch && !isFalseBranch) {
throw new RuntimeException("not true nor false branch");
}
int lhs = branchInstr.getUse(0);
int rhs = branchInstr.getUse(1);
SymbolTable st = getSymbolTable(src);
if (st.isNullConstant(lhs) && st.isNullConstant(rhs)) {
isUnaryCond = false;
}
if (piInstr.getSuccessor() != dest.getDelegate().getOriginalNumber()) {
return false;
}
if ((isEq && isTrueBranch && isUnaryCond) || (isNe && isFalseBranch && isUnaryCond)) {
// variable is equal to null => don't propagate may not be null
if (factType.equals(NullSet.MayNotNULL)) {
return false;
} else {
return true;
}
}
if ((isNe && isTrueBranch && isUnaryCond) || (isEq && isFalseBranch && isUnaryCond)) {
// variable is not equal to null => don't propagate may be null
if (factType.equals(NullSet.MayNULL)) {
return false;
} else {
return true;
}
}
// return true if we do not have to filter the given fact
return true;
}
/**
* Returns the PI flow function between the given source and destination
* nodes. Checks which facts to propagate, such that the condition is not
* contradicted on the true/false branch
*
* @param src
* @param dest
* @return
*/
private IUnaryFlowFunction buildPiFunction(final BasicBlockInContext<IExplodedBasicBlock> src,
final BasicBlockInContext<IExplodedBasicBlock> dest) {
return new IUnaryFlowFunction() {
@Override
public IntSet getTargets(int d1) {
MutableSparseIntSet result = MutableSparseIntSet.makeEmpty();
if (d1 == ZERO) {
result.add(ZERO);
return result;
}
NullAnalysisFact fact = domain.getMappedObject(d1);
Iterator<SSAPiInstruction> piIter = src.iteratePis();
result.add(d1);
while (piIter.hasNext()) {
SSAPiInstruction piInstr = piIter.next();
// if it is a fact about piUseValueNum
if (fact.node.equals(src.getNode()) && fact.ssaVarId == piInstr.getUse(0)) {
if (canPropagate(piInstr, src, dest, fact.type)) {
// propagate facts from piUseValueNum to
// piDefValueNum
int newFactId = domain.add(new NullAnalysisFact(src.getNode(), piInstr.getDef(), fact.accessPath, fact.type));
result.add(newFactId);
}
}
}
return result;
}
};
}
/**
* Returns the PHI flow function for the given destination node. It copies
* the facts that hold for the PHI use value numbers to facts that hold for
* the PHI defined value number.
*
* @param dest
* @return
*/
private IUnaryFlowFunction buildPhiFunction(final BasicBlockInContext<IExplodedBasicBlock> dest) {
return new IUnaryFlowFunction() {
@Override
public IntSet getTargets(int d1) {
MutableSparseIntSet result = MutableSparseIntSet.makeEmpty();
if (d1 == ZERO) {
result.add(ZERO);
return result;
}
result.add(d1);
NullAnalysisFact fact = domain.getMappedObject(d1);
if (fact.node.equals(dest.getNode())) {
Iterator<SSAPhiInstruction> phis = dest.iteratePhis();
while (phis.hasNext()) {
SSAPhiInstruction phi = phis.next();
int phiDef = phi.getDef();
for (int i = 0; i < phi.getNumberOfUses(); i++) {
if (fact.ssaVarId == phi.getUse(i)) {
// propagate fact about PHI use to PHI
// defined
int newFactId = domain.add(new NullAnalysisFact(fact.node, phiDef, fact.accessPath, fact.type));
result.add(newFactId);
}
}
}
}
return result;
}
};
}
/**
* Propagate facts that hold for the returned value number to the
* AUX_RETURN_VAL_NUM auxiliary value number
*
* @param src
* @param dest
* @param entryFacts
* @return
*/
private IUnaryFlowFunction buildReturnFunction(final BasicBlockInContext<IExplodedBasicBlock> src,
BasicBlockInContext<IExplodedBasicBlock> dest, final MutableIntSet entryFacts) {
SSAInstruction instr = getSSAInstr(src);
final SSAReturnInstruction retInstr = (SSAReturnInstruction) instr;
return new IUnaryFlowFunction() {
@Override
public IntSet getTargets(int d1) {
if (d1 == ZERO) {
entryFacts.add(ZERO);
return entryFacts;
}
MutableSparseIntSet result = MutableSparseIntSet.makeEmpty();
result.add(d1);
NullAnalysisFact fact = domain.getMappedObject(d1);
for (int i = 0; i < retInstr.getNumberOfUses(); i++) {
// if we have a fact about the returned value number
if (fact.node.equals(src.getNode()) && fact.ssaVarId == retInstr.getUse(i)) {
// the same fact holds for the auxiliary return
// value number
NullAnalysisFact auxReturnFact = new NullAnalysisFact(src.getNode(), AUX_RETURN_VAL_NUM, fact.accessPath, fact.type);
int auxReturnFactId = domain.add(auxReturnFact);
// propagate the fact for the auxiliary return
// value
result.add(auxReturnFactId);
}
}
return result;
}
};
}
/**
* Generate MayNotNULL facts about the defined value of NEW or GET (the
* heap) instructions. This models the assumption that any value read from
* the heap is not NULL.
*
* @param src
* @param dest
* @param entryFacts
* @return
*/
private IUnaryFlowFunction buildNewFunction(final BasicBlockInContext<IExplodedBasicBlock> src,
BasicBlockInContext<IExplodedBasicBlock> dest, final MutableIntSet entryFacts) {
final SSAInstruction instr = getSSAInstr(src);
NullAnalysisFact newMayNonNull = new NullAnalysisFact(src.getNode(), instr.getDef(), NullSet.MayNotNULL);
final int newMayNotNullId = domain.add(newMayNonNull);
return new IUnaryFlowFunction() {
@Override
public IntSet getTargets(int d1) {
if (d1 == ZERO) {
// add ZERO and the MayNotNull set
entryFacts.add(ZERO);
entryFacts.add(newMayNotNullId);
return entryFacts;
}
MutableSparseIntSet result = MutableSparseIntSet.makeEmpty();
// remove the MayNull facts about the value number
NullAnalysisFact elem = domain.getMappedObject(d1);
if (!elem.node.equals(src.getNode()) || !(elem.ssaVarId == instr.getDef()) || !elem.type.equals(NullSet.MayNULL)) {
// identity function
result.add(d1);
}
return result;
}
};
}
private IUnaryFlowFunction buildGetFieldFunction(final BasicBlockInContext<IExplodedBasicBlock> src,
BasicBlockInContext<IExplodedBasicBlock> dest, final MutableIntSet entryFacts) {
SSAInstruction instr = getSSAInstr(src);
SSAGetInstruction getInstr = (SSAGetInstruction) instr;
final int lhs = getInstr.getDef();
final int rhs = getInstr.getUse(0);
final String field = getInstr.getDeclaredField().getName().toString();
return new IUnaryFlowFunction() {
@Override
public IntSet getTargets(int d1) {
if (d1 == ZERO) {
// add ZERO and the MayNotNull set
entryFacts.add(ZERO);
return entryFacts;
}
MutableSparseIntSet result = MutableSparseIntSet.makeEmpty();
result.add(d1);
NullAnalysisFact elem = domain.getMappedObject(d1);
if (elem.node.equals(src.getNode())) {
if (elem.ssaVarId == rhs) {
if (!elem.accessPath.isEmpty()) {
if (elem.accessPath.get(0).equals(field)) {
// remove first element of the access path
List<String> newAccessPath = NullAnalysisFact.getEmptyAccessPath();
for (int i = 1; i < elem.accessPath.size(); i++) {
newAccessPath.add(elem.accessPath.get(i));
}
NullAnalysisFact newFact = new NullAnalysisFact(src.getNode(), lhs, newAccessPath, elem.type);
int newFactId = domain.add(newFact);
result.add(newFactId);
}
}
}
}
return result;
}
};
}
private IUnaryFlowFunction buildPutFieldFunction(final BasicBlockInContext<IExplodedBasicBlock> src,
BasicBlockInContext<IExplodedBasicBlock> dest, final MutableIntSet entryFacts) {
SSAInstruction instr = getSSAInstr(src);
SSAPutInstruction putInstr = (SSAPutInstruction) instr;
final int lhs = putInstr.getUse(0);
final int rhs = putInstr.getUse(1);
final String field = putInstr.getDeclaredField().getName().toString();
return new IUnaryFlowFunction() {
@Override
public IntSet getTargets(int d1) {
if (d1 == ZERO) {
// add ZERO and the MayNotNull set
entryFacts.add(ZERO);
return entryFacts;
}
MutableSparseIntSet result = MutableSparseIntSet.makeEmpty();
result.add(d1);
NullAnalysisFact elem = domain.getMappedObject(d1);
if (elem.node.equals(src.getNode())) {
if (elem.ssaVarId == rhs) {
if (elem.accessPath.size() < AP_LENGTH_BOUND) {
// add one element to the access path
List<String> newAccessPath = NullAnalysisFact.getEmptyAccessPath();
newAccessPath.add(field);
for (String ap : elem.accessPath) {
newAccessPath.add(ap);
}
NullAnalysisFact newFact = new NullAnalysisFact(src.getNode(), lhs, newAccessPath, elem.type);
int newFactId = domain.add(newFact);
result.add(newFactId);
}
}
}
return result;
}
};
}
private IUnaryFlowFunction buildCheckCastFunction(final BasicBlockInContext<IExplodedBasicBlock> src,
BasicBlockInContext<IExplodedBasicBlock> dest, final MutableIntSet entryFacts) {
SSAInstruction instr = getSSAInstr(src);
SSACheckCastInstruction checkCastInstr = (SSACheckCastInstruction) instr;
final int lhs = checkCastInstr.getDef();
final int rhs = checkCastInstr.getUse(0);
return new IUnaryFlowFunction() {
@Override
public IntSet getTargets(int d1) {
if (d1 == ZERO) {
// add ZERO and the MayNotNull set
entryFacts.add(ZERO);
return entryFacts;
}
MutableSparseIntSet result = MutableSparseIntSet.makeEmpty();
result.add(d1);
NullAnalysisFact elem = domain.getMappedObject(d1);
if (elem.node.equals(src.getNode())) {
if (elem.ssaVarId == rhs) {
NullAnalysisFact newFact = new NullAnalysisFact(src.getNode(), lhs, elem.accessPath, elem.type);
int newFactId = domain.add(newFact);
result.add(newFactId);
}
}
return result;
}
};
}
@Override
public IUnaryFlowFunction getNormalFlowFunction(BasicBlockInContext<IExplodedBasicBlock> src,
BasicBlockInContext<IExplodedBasicBlock> dest) {
final MutableIntSet entryFacts = MutableSparseIntSet.makeEmpty();
IUnaryFlowFunction blockFunc;
if (src.isEntryBlock()) {
getEntryBlkFacts(src, entryFacts);
}
SSAInstruction instr = getSSAInstr(src);
IUnaryFlowFunction piFunc = buildPiFunction(src, dest);
IUnaryFlowFunction phiFunc = buildPhiFunction(dest);
if (instr instanceof SSACheckCastInstruction) {
// e.g. v1 = (Type)v2
blockFunc = buildCheckCastFunction(src, dest, entryFacts);
} else if (instr instanceof SSAGetInstruction && !((SSAGetInstruction) instr).isStatic()) {
// e.g. v1 = v2.next
blockFunc = buildGetFieldFunction(src, dest, entryFacts);
} else if (instr instanceof SSAPutInstruction && !((SSAPutInstruction) instr).isStatic()) {
// e.g. v1.next = v2
blockFunc = buildPutFieldFunction(src, dest, entryFacts);
} else if (instr instanceof SSAReturnInstruction) {
// e.g. return v1
blockFunc = buildReturnFunction(src, dest, entryFacts);
} else if (instr instanceof SSANewInstruction
|| (instr instanceof SSAGetInstruction && ((SSAGetInstruction) instr).isStatic())) {
// the defined value of NEW or GET (the heap) instructions
// number may be not null
// e.g. v1 = new ...
// e.g. v1 = Class.field
blockFunc = buildNewFunction(src, dest, entryFacts);
} else {
// default normal flow function
blockFunc = new IUnaryFlowFunction() {
@Override
public IntSet getTargets(int d1) {
if (d1 == ZERO) {
entryFacts.add(ZERO);
return entryFacts;
}
MutableSparseIntSet result = MutableSparseIntSet.makeEmpty();
result.add(d1);
return result;
}
};
}
// return blockFunction;
IUnaryFlowFunction result = compose(compose(piFunc, blockFunc), phiFunc);
return filter(result, src, dest);
}
@Override
public IUnaryFlowFunction getCallFlowFunction(BasicBlockInContext<IExplodedBasicBlock> src,
final BasicBlockInContext<IExplodedBasicBlock> dest, BasicBlockInContext<IExplodedBasicBlock> ret) {
SSAInstruction ssaInstr = getSSAInstr(src);
final SSAInvokeInstruction invokeInstr = (SSAInvokeInstruction) ssaInstr;
IUnaryFlowFunction callFunc = new IUnaryFlowFunction() {
@Override
public IntSet getTargets(int d1) {
MutableSparseIntSet result = MutableSparseIntSet.makeEmpty();
boolean isInLibrary = !isInAppScope(dest.getNode());
if (isInLibrary && !ANALYZE_LIBRARY) {
// do not analyse the library code
return result;
}
if (d1 == ZERO) {
result.add(ZERO);
if (invokeInstr.getCallSite().isVirtual()) {
// it it's a virtual call, then propagate that the
// first parameter (this) is not null
int thisMayNotNullId = domain.add(new NullAnalysisFact(dest.getNode(), 1, NullSet.MayNotNULL));
result.add(thisMayNotNullId);
}
return result;
}
// incoming fact
NullAnalysisFact fact = domain.getMappedObject(d1);
int numParams = invokeInstr.getNumberOfParameters();
for (int i = 0; i < numParams; i++) {
// if variable in incoming fact used as argument
if (invokeInstr.getUse(i) == fact.ssaVarId) {
// it it's a virtual call, then don't propagate that
// the first parameter (this) may be null
if (!invokeInstr.getCallSite().isStatic() && i == 0 && fact.type.equals(NullSet.MayNULL)
&& fact.accessPath.isEmpty()) {
} else {
// propagate fact of same type to callee formal
// parameter
NullAnalysisFact newFact = new NullAnalysisFact(dest.getNode(), (i + 1), fact.accessPath, fact.type);
int formalParamFactId = domain.add(newFact);
result.add(formalParamFactId);
}
}
}
return result;
}
};
return filter(callFunc, src, dest);
}
@Override
public IFlowFunction getReturnFlowFunction(BasicBlockInContext<IExplodedBasicBlock> call,
final BasicBlockInContext<IExplodedBasicBlock> src, final BasicBlockInContext<IExplodedBasicBlock> dest) {
SSAInstruction ssaInstr = getSSAInstr(call);
final SSAInvokeInstruction ssaInvoke = (SSAInvokeInstruction) ssaInstr;
final int calleeValueNum = ssaInvoke.getDef(0);
IUnaryFlowFunction retFunc = new IUnaryFlowFunction() {
// if there is a return value
@Override
public IntSet getTargets(int d1) {
MutableSparseIntSet result = MutableSparseIntSet.makeEmpty();
if (d1 == ZERO) {
result.add(ZERO);
return result;
}
NullAnalysisFact fact = domain.getMappedObject(d1);
// if the fact corresponds to an auxiliary returned value
// number
if (fact.node.equals(src.getNode()) && fact.ssaVarId == AUX_RETURN_VAL_NUM && ssaInvoke.getNumberOfDefs() > 1) {
// propagate this type of fact to the value numbers in
// the callee
NullAnalysisFact newFact = new NullAnalysisFact(dest.getNode(), calleeValueNum, fact.accessPath, fact.type);
int newFactID = domain.add(newFact);
result.add(newFactID);
}
// if the fact corresponds to "this"
if (fact.node.equals(src.getNode()) && fact.ssaVarId == 1) {
// propagate the fact to variable in caller that
// corresponds to "this" in callee
NullAnalysisFact newFact = new NullAnalysisFact(dest.getNode(), ssaInvoke.getUse(0), fact.accessPath, fact.type);
int newFactID = domain.add(newFact);
result.add(newFactID);
}
return result;
}
};
return filter(retFunc, src, dest);
}
@Override
public IUnaryFlowFunction getCallToReturnFlowFunction(final BasicBlockInContext<IExplodedBasicBlock> src,
BasicBlockInContext<IExplodedBasicBlock> dest) {
SSAInstruction instr = getSSAInstr(src);
final SSAInvokeInstruction invokeInstr = (SSAInvokeInstruction) instr;
Iterator<? extends BasicBlockInContext<IExplodedBasicBlock>> calledNodes = getSupergraph().getCalledNodes(src);
boolean isLibraryCall = true;
while (calledNodes.hasNext()) {
// check if all possible calledNodes are library calls
BasicBlockInContext<IExplodedBasicBlock> calledNode = calledNodes.next();
boolean aux = !isInAppScope(getSupergraph().getProcOf(calledNode));
isLibraryCall = isLibraryCall && aux;
}
// set of facts that def variables that may not be null because they
// are assigned the return value of a library call
final MutableIntSet defMayNotNull = MutableSparseIntSet.makeEmpty();
if (isLibraryCall && !ANALYZE_LIBRARY) {
if (invokeInstr.getNumberOfDefs() > 0) {
// if it is a library call and we do not analyze libraries,
// then result of the call is MayNotNULL
int defMayNotNullFactId = domain.add(new NullAnalysisFact(src.getNode(), invokeInstr.getDef(0), NullSet.MayNotNULL));
defMayNotNull.add(defMayNotNullFactId);
}
}
if (invokeInstr.getCallSite().isVirtual()) {
// if the call is to a virtual method
IUnaryFlowFunction callToRetFunc = new IUnaryFlowFunction() {
@Override
public IntSet getTargets(int d1) {
int derefValueNum = invokeInstr.getUse(0);
if (d1 == ZERO) {
// the object on which the method is called is
// MayNotNULL
int mayNotNullFactId = domain.add(new NullAnalysisFact(src.getNode(), derefValueNum, NullSet.MayNotNULL));
defMayNotNull.add(mayNotNullFactId);
defMayNotNull.add(ZERO);
return defMayNotNull;
}
MutableSparseIntSet result = MutableSparseIntSet.makeEmpty();
NullAnalysisFact fact = domain.getMappedObject(d1);
// the object on which the method is called is not
// MayNULL
if (!fact.node.equals(src.getNode()) || !(fact.ssaVarId == derefValueNum) || !fact.type.equals(NullSet.MayNULL)) {
result.add(d1);
}
return result;
}
};
return filter(callToRetFunc, src, dest);
}
IUnaryFlowFunction callToRetFunc = new IUnaryFlowFunction() {
@Override
public IntSet getTargets(int d1) {
if (d1 == ZERO) {
defMayNotNull.add(ZERO);
return defMayNotNull;
}
MutableSparseIntSet result = MutableSparseIntSet.makeEmpty();
result.add(d1);
return result;
}
};
return filter(callToRetFunc, src, dest);
}
@Override
public IUnaryFlowFunction getCallNoneToReturnFlowFunction(BasicBlockInContext<IExplodedBasicBlock> src,
BasicBlockInContext<IExplodedBasicBlock> dest) {
// I think this is the case of unreachable calls
return getCallToReturnFlowFunction(src, dest);
}
}
protected class NullAnalysisProblem
implements TabulationProblem<BasicBlockInContext<IExplodedBasicBlock>, CGNode, NullAnalysisFact> {
private NullAnalysisFlowFunctions flowFunctions = new NullAnalysisFlowFunctions(domain);
@Override
public ISupergraph<BasicBlockInContext<IExplodedBasicBlock>, CGNode> getSupergraph() {
return supergraph;
}
@Override
public Collection<PathEdge<BasicBlockInContext<IExplodedBasicBlock>>> initialSeeds() {
Collection<PathEdge<BasicBlockInContext<IExplodedBasicBlock>>> result = HashSetFactory.make();
for (BasicBlockInContext<IExplodedBasicBlock> bb : supergraph) {
result.add(PathEdge.createPathEdge(bb, 0, bb, 0));
break;
}
return result;
}
@Override
public IMergeFunction getMergeFunction() {
return null;
}
@Override
public IFlowFunctionMap<BasicBlockInContext<IExplodedBasicBlock>> getFunctionMap() {
return flowFunctions;
}
@Override
public TabulationDomain<NullAnalysisFact, BasicBlockInContext<IExplodedBasicBlock>> getDomain() {
return domain;
}
}
public TabulationResult<BasicBlockInContext<IExplodedBasicBlock>, CGNode, NullAnalysisFact> analyze() {
solver = TabulationSolver.make(new NullAnalysisProblem());
TabulationResult<BasicBlockInContext<IExplodedBasicBlock>, CGNode, NullAnalysisFact> analysisResult = null;
try {
analysisResult = solver.solve();
} catch (CancelException e) {
// this shouldn't happen
assert false;
}
return analysisResult;
}
/**
* Returns true if the given node is in the application, and not in the
* library
*
* @param node
* @return
*/
protected boolean isInAppScope(CGNode node) {
return isInAppScope(node.getMethod());
}
protected boolean isInAppScope(IMethod method) {
return !method.getDeclaringClass().getClassLoader().getReference().equals(ClassLoaderReference.Primordial);
}
protected boolean isInAppScope(BasicBlockInContext<IExplodedBasicBlock> node) {
return isInAppScope(node.getMethod());
}
}

View File

@ -0,0 +1,61 @@
package com.ibm.wala.examples.analysis.dataflow;
import java.util.LinkedList;
import java.util.List;
import com.ibm.wala.ipa.callgraph.CGNode;
public class NullAnalysisFact {
public CGNode node;
public Integer ssaVarId;
public List<String> accessPath;
public NullSet type;
public NullAnalysisFact(CGNode node, Integer ssaVarId, List<String> accessPath, NullSet type) {
this.node = node;
this.ssaVarId = ssaVarId;
this.accessPath = accessPath;
this.type = type;
}
public NullAnalysisFact(CGNode node, Integer ssaVarId, NullSet type) {
this.node = node;
this.ssaVarId = ssaVarId;
this.accessPath = getEmptyAccessPath();
this.type = type;
}
public static List<String> getEmptyAccessPath() {
return new LinkedList<String>();
}
@Override
public String toString() {
return this.node + ", " + this.ssaVarId + ", " + this.accessPath + ", " + this.type;
}
private boolean check(Object x, Object y) {
return (x == null) ? (y == null) : x.equals(y);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof NullAnalysisFact)) {
return false;
}
NullAnalysisFact oFact = (NullAnalysisFact) o;
return check(this.node, oFact.node) && check(this.ssaVarId, oFact.ssaVarId)
&& check(this.accessPath, oFact.accessPath) && check(this.type, oFact.type);
}
private int hc(Object o) {
return (o == null) ? 0 : o.hashCode();
}
@Override
public int hashCode() {
return 7*hc(node) + 11*hc(ssaVarId) + 3*hc(accessPath) + 13*hc(type);
}
}

View File

@ -0,0 +1,5 @@
package com.ibm.wala.examples.analysis.dataflow;
public enum NullSet {
MayNULL, MayNotNULL
}

View File

@ -0,0 +1,202 @@
package com.ibm.wala.examples.drivers;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.dataflow.IFDS.TabulationResult;
import com.ibm.wala.examples.analysis.dataflow.NullAnalysis;
import com.ibm.wala.examples.analysis.dataflow.NullAnalysis.NullAnalysisDomain;
import com.ibm.wala.examples.analysis.dataflow.NullAnalysisFact;
import com.ibm.wala.examples.analysis.dataflow.NullSet;
import com.ibm.wala.ipa.callgraph.AnalysisCache;
import com.ibm.wala.ipa.callgraph.AnalysisCacheImpl;
import com.ibm.wala.ipa.callgraph.AnalysisOptions;
import com.ibm.wala.ipa.callgraph.AnalysisScope;
import com.ibm.wala.ipa.callgraph.CGNode;
import com.ibm.wala.ipa.callgraph.CallGraph;
import com.ibm.wala.ipa.callgraph.CallGraphBuilder;
import com.ibm.wala.ipa.callgraph.CallGraphBuilderCancelException;
import com.ibm.wala.ipa.callgraph.CallGraphStats;
import com.ibm.wala.ipa.callgraph.Entrypoint;
import com.ibm.wala.ipa.callgraph.AnalysisOptions.ReflectionOptions;
import com.ibm.wala.ipa.callgraph.impl.Util;
import com.ibm.wala.ipa.cfg.BasicBlockInContext;
import com.ibm.wala.ipa.cha.ClassHierarchyException;
import com.ibm.wala.ipa.cha.ClassHierarchyFactory;
import com.ibm.wala.ipa.cha.IClassHierarchy;
import com.ibm.wala.ssa.NullTestPiPolicy;
import com.ibm.wala.ssa.SSAInstruction;
import com.ibm.wala.ssa.SSAInvokeInstruction;
import com.ibm.wala.ssa.analysis.IExplodedBasicBlock;
import com.ibm.wala.types.ClassLoaderReference;
import com.ibm.wala.util.collections.Pair;
import com.ibm.wala.util.config.AnalysisScopeReader;
import com.ibm.wala.util.config.FileOfClasses;
import com.ibm.wala.util.intset.IntSet;
import com.ibm.wala.util.io.CommandLine;
import com.ibm.wala.util.warnings.Warnings;
public class NullAnalysisDriver {
// more aggressive exclusions to avoid library blowup
// in inter-procedural tests
private static final String EXCLUSIONS = "java\\/awt\\/.*\n" + "javax\\/swing\\/.*\n" + "sun\\/awt\\/.*\n"
+ "sun\\/swing\\/.*\n" + "com\\/sun\\/.*\n" + "sun\\/.*\n" + "org\\/netbeans\\/.*\n"
+ "org\\/openide\\/.*\n" + "com\\/ibm\\/crypto\\/.*\n" + "com\\/ibm\\/security\\/.*\n"
+ "org\\/apache\\/xerces\\/.*\n" + "java\\/security\\/.*\n" + "";
public static void main(String[] args)
throws ClassHierarchyException, IllegalArgumentException, CallGraphBuilderCancelException, IOException {
long start = System.currentTimeMillis();
Properties p = CommandLine.parse(args);
String scopeFile = p.getProperty("scopeFile");
if (scopeFile == null) {
throw new IllegalArgumentException("must specify scope file");
}
String mainClass = p.getProperty("mainClass");
if (mainClass == null) {
throw new IllegalArgumentException("must specify main class");
}
AnalysisScope scope = AnalysisScopeReader.readJavaScope(scopeFile, null,
NullAnalysisDriver.class.getClassLoader());
scope.setExclusions(new FileOfClasses(new ByteArrayInputStream(EXCLUSIONS.getBytes("UTF-8"))));
IClassHierarchy cha = ClassHierarchyFactory.make(scope);
System.out.println(cha.getNumberOfClasses() + " classes");
// System.out.println(Warnings.asString());
Warnings.clear();
AnalysisOptions options = new AnalysisOptions();
Iterable<Entrypoint> entrypoints = Util.makeMainEntrypoints(scope, cha, mainClass);
options.setEntrypoints(entrypoints);
options.getSSAOptions().setPiNodePolicy(NullTestPiPolicy.createNullTestPiPolicy());
// you can dial down reflection handling if you like
options.setReflectionOptions(ReflectionOptions.NONE);
AnalysisCache cache = new AnalysisCacheImpl();
// other builders can be constructed with different Util methods
CallGraphBuilder builder = Util.makeZeroOneContainerCFABuilder(options, cache, cha, scope);
// CallGraphBuilder builder = Util.makeNCFABuilder(2, options, cache,
// cha, scope);
// CallGraphBuilder builder = Util.makeVanillaNCFABuilder(2, options,
// cache, cha, scope);
System.out.println("building call graph...");
CallGraph cg = builder.makeCallGraph(options, null);
long end = System.currentTimeMillis();
System.out.println("done");
System.out.println("took " + (end - start) + "ms");
System.out.println(CallGraphStats.getStats(cg));
NullAnalysis nullAnalysis = new NullAnalysis(cg, cache);
start = System.currentTimeMillis();
TabulationResult<BasicBlockInContext<IExplodedBasicBlock>, CGNode, NullAnalysisFact> result = nullAnalysis.analyze();;
end = System.currentTimeMillis();
System.out.println("done Null analysis");
System.out.println("NULL analysis took " + (end - start) + "ms");
Set<Pair<BasicBlockInContext<IExplodedBasicBlock>, Integer>> warnings = new HashSet<Pair<BasicBlockInContext<IExplodedBasicBlock>, Integer>>(),
errors = new HashSet<Pair<BasicBlockInContext<IExplodedBasicBlock>, Integer>>();
Iterator<BasicBlockInContext<IExplodedBasicBlock>> nodes = nullAnalysis.getSupergraph().iterator();
NullAnalysisDomain domain = nullAnalysis.getDomain();
while (nodes.hasNext()) {
// for each node of the supergraph
BasicBlockInContext<IExplodedBasicBlock> node = nodes.next();
SSAInstruction instr = node.getDelegate().getInstruction();
if (instr != null && instr instanceof SSAInvokeInstruction) {
// if the node corresponds to an invoke instruction
SSAInvokeInstruction invokeInstr = (SSAInvokeInstruction) instr;
if (invokeInstr.getCallSite().isVirtual()) {
// if it is a virtual invoke (obj.f())
assert invokeInstr.getNumberOfUses() > 0;
int derefValueNum = invokeInstr.getUse(0);
int mayNullFactIdx = domain
.getMappedIndex(new NullAnalysisFact(node.getNode(), derefValueNum, NullSet.MayNULL));
int mayNotNullFactIdx = domain
.getMappedIndex(new NullAnalysisFact(node.getNode(), derefValueNum, NullSet.MayNotNULL));
IntSet reachableFacts = result.getResult(node);
if (reachableFacts.contains(mayNullFactIdx)) {
// if the dereferenced value number may be NULL
if (!reachableFacts.contains(mayNotNullFactIdx)) {
// if the dereferenced value number is certainly
// NULL
if (isInAppScope(node)) {
// record an error, if the node is in app scope
errors.add(Pair.make(node, mayNullFactIdx));
printInvokeStatus(node, invokeInstr, reachableFacts, isInAppScope(node),
"ERROR: NULL dereference");
}
} else {
// if the dereferenced value number may be not NULL
if (isInAppScope(node)) {
// record a warning, if the node is in app scope
warnings.add(Pair.make(node, mayNullFactIdx));
printInvokeStatus(node, invokeInstr, reachableFacts, isInAppScope(node),
"WARNING: possible NULL dereference");
}
}
}
}
}
}
}
/**
* Used for printing analysis information about the given invoke statement
*
* @param node
* @param invokeInstr
* @param reachableFacts
* @param inAppScope
* @param msg
*/
private static void printInvokeStatus(BasicBlockInContext<IExplodedBasicBlock> node,
SSAInvokeInstruction invokeInstr, IntSet reachableFacts, boolean inAppScope, String msg) {
System.out.println("-----");
System.out.println("in: " + node.getMethod());
System.out.println(" " + invokeInstr + " : " + reachableFacts);
System.out.println(" deref: " + invokeInstr.getCallSite().getDeclaredTarget().getName());
System.out.println(" " + msg);
}
/**
* Returns true if the given method is not in the Primordial class loader
*
* @param method
* @return
*/
private static boolean isInAppScope(IMethod method) {
return !method.getDeclaringClass().getClassLoader().getReference().equals(ClassLoaderReference.Primordial);
}
private static boolean isInAppScope(BasicBlockInContext<IExplodedBasicBlock> node) {
boolean isInApp = isInAppScope(node.getMethod());
return isInApp;
}
}