Merge branch 'master' of https://github.com/d2an3/WALA
This commit is contained in:
commit
f5c36d7411
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package com.ibm.wala.examples.analysis.dataflow;
|
||||
|
||||
public enum NullSet {
|
||||
MayNULL, MayNotNULL
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue