WALA/com.ibm.wala.core.tests/src/com/ibm/wala/core/tests/arraybounds/PruneArrayOutOfBoundExcepti...

245 lines
9.7 KiB
Java

package com.ibm.wala.core.tests.arraybounds;
import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.everyItem;
import static org.hamcrest.CoreMatchers.hasItem;
import java.io.IOException;
import java.util.LinkedHashSet;
import org.hamcrest.Matcher;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ErrorCollector;
import com.ibm.wala.analysis.arraybounds.ArrayOutOfBoundsAnalysis;
import com.ibm.wala.analysis.nullpointer.IntraproceduralNullPointerAnalysis;
import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.core.tests.util.TestConstants;
import com.ibm.wala.ipa.callgraph.AnalysisOptions;
import com.ibm.wala.ipa.callgraph.AnalysisScope;
import com.ibm.wala.ipa.callgraph.impl.Everywhere;
import com.ibm.wala.ipa.cfg.PrunedCFG;
import com.ibm.wala.ipa.cfg.exceptionpruning.ExceptionFilter2EdgeFilter;
import com.ibm.wala.ipa.cfg.exceptionpruning.filter.ArrayOutOfBoundFilter;
import com.ibm.wala.ipa.cfg.exceptionpruning.filter.CombinedExceptionFilter;
import com.ibm.wala.ipa.cfg.exceptionpruning.filter.NullPointerExceptionFilter;
import com.ibm.wala.ipa.cha.ClassHierarchy;
import com.ibm.wala.ipa.cha.ClassHierarchyException;
import com.ibm.wala.ipa.cha.ClassHierarchyFactory;
import com.ibm.wala.ssa.AllIntegerDueToBranchePiPolicy;
import com.ibm.wala.ssa.DefaultIRFactory;
import com.ibm.wala.ssa.IR;
import com.ibm.wala.ssa.IRFactory;
import com.ibm.wala.ssa.ISSABasicBlock;
import com.ibm.wala.ssa.SSACFG;
import com.ibm.wala.ssa.SSAInstruction;
import com.ibm.wala.types.TypeReference;
import com.ibm.wala.util.collections.Pair;
import com.ibm.wala.util.config.AnalysisScopeReader;
/**
* This test will check that:
* <ul>
* <li>no normal edge is removed
* <li>in case of an exceptional edge being removed, the instruction causing the
* edge is an instruction that is able to throw a NullpointerException or an
* ArrayIndexOutOfBoundException and no other exception may be thrown by this
* instruction.
* <li>the number of removed edges is as expected
* </ul>
*
* So there is no explicit check for specific lines.
*
* For an example how to use the exception pruning see
* {@link PruneArrayOutOfBoundExceptionEdge#computeCfgAndPrunedCFG(IMethod)}
*
* @author Stephan Gocht {@code <stephan@gobro.de>}
*
*/
public class PruneArrayOutOfBoundExceptionEdge {
private static ClassLoader CLASS_LOADER = PruneArrayOutOfBoundExceptionEdge.class.getClassLoader();
private static final String DETECTABLE_TESTDATA = "Larraybounds/Detectable";
/**
* The number of Basic Blocks, which have an exception edge, that should be
* removed. (#[array access] + #[other])
*/
private static final int DETECTABLE_EXPECTED_COUNT = 34 + 2;
private static final String NOT_DETECTABLE_TESTDATA = "Larraybounds/NotDetectable";
/**
* The number of Basic Blocks, which have an exception edge, that should be
* removed. (#[array access] + #[other])
*/
private static final int NOT_DETECTABLE_EXPECTED_COUNT = 0 + 3;
private static final String NOT_IN_BOUND_TESTDATA = "Larraybounds/NotInBound";
/**
* The number of Basic Blocks, which have an exception edge, that should be
* removed. (#[array access] + #[other])
*/
private static final int NOT_IN_BOUND_EXPECTED_COUNT = 0 + 1;
private static IRFactory<IMethod> irFactory;
private static AnalysisOptions options;
private static AnalysisScope scope;
private static ClassHierarchy cha;
@Rule
public ErrorCollector collector = new ErrorCollector();
@BeforeClass
public static void init() throws IOException, ClassHierarchyException {
scope = AnalysisScopeReader.readJavaScope(TestConstants.WALA_TESTDATA, null, CLASS_LOADER);
cha = ClassHierarchyFactory.make(scope);
irFactory = new DefaultIRFactory();
options = new AnalysisOptions();
options.getSSAOptions().setPiNodePolicy(new AllIntegerDueToBranchePiPolicy());
}
private static IR getIr(IMethod method) {
return irFactory.makeIR(method, Everywhere.EVERYWHERE, options.getSSAOptions());
}
public static Pair<SSACFG, PrunedCFG<SSAInstruction, ISSABasicBlock>> computeCfgAndPrunedCFG(IMethod method) {
IR ir = getIr(method);
SSACFG cfg = ir.getControlFlowGraph();
ArrayOutOfBoundsAnalysis arrayBoundsAnalysis = new ArrayOutOfBoundsAnalysis(ir);
IntraproceduralNullPointerAnalysis nullPointerAnalysis = new IntraproceduralNullPointerAnalysis(ir);
CombinedExceptionFilter<SSAInstruction> filter = new CombinedExceptionFilter<>();
filter.add(new ArrayOutOfBoundFilter(arrayBoundsAnalysis));
filter.add(new NullPointerExceptionFilter(nullPointerAnalysis));
ExceptionFilter2EdgeFilter<ISSABasicBlock> edgeFilter = new ExceptionFilter2EdgeFilter<>(filter, cha, cfg);
PrunedCFG<SSAInstruction, ISSABasicBlock> prunedCfg = PrunedCFG.make(cfg, edgeFilter);
return Pair.make(cfg, prunedCfg);
}
private static IClass getIClass(String name) {
final TypeReference typeRef = TypeReference.findOrCreate(scope.getApplicationLoader(), name);
return cha.lookupClass(typeRef);
}
@Test
public void detectable() {
IClass iClass = getIClass(DETECTABLE_TESTDATA);
checkRemovedEdges(iClass, DETECTABLE_EXPECTED_COUNT);
}
@Test
public void notDetectable() {
IClass iClass = getIClass(NOT_DETECTABLE_TESTDATA);
checkRemovedEdges(iClass, NOT_DETECTABLE_EXPECTED_COUNT);
}
@Test
public void notInBound() {
IClass iClass = getIClass(NOT_IN_BOUND_TESTDATA);
checkRemovedEdges(iClass, NOT_IN_BOUND_EXPECTED_COUNT);
}
private void checkRemovedEdges(IClass iClass, int expectedNumberOfArrayAccesses) {
int numberOfDeletedExceptionEdges = 0;
for (IMethod method : iClass.getAllMethods()) {
if (method.getDeclaringClass().equals(iClass)) {
String identifyer = method.getDeclaringClass().getName().toString() + "#" + method.getName().toString();
Pair<SSACFG, PrunedCFG<SSAInstruction, ISSABasicBlock>> cfgs = computeCfgAndPrunedCFG(method);
SSACFG cfg = cfgs.fst;
PrunedCFG<SSAInstruction, ISSABasicBlock> prunedCfg = cfgs.snd;
for (ISSABasicBlock block : cfg) {
checkNormalSuccessors(cfg, prunedCfg, block);
boolean isEdgeRemoved = checkExceptionalSuccessors(block, cfg, prunedCfg, method, identifyer);
numberOfDeletedExceptionEdges += isEdgeRemoved ? 1 : 0;
}
}
}
/*
* Possible reasons for this to fail are:
*
*
* *_NUMBER_OF_BB_WITHOUT_EXCEPTION is not set to the correct value (maybe
* the test data has changed).
*
* Not all methods of the class analyzed.
*
* There is a bug, so not all edges are deleted.
*/
collector.checkThat("Number of deleted edges is not as expected for " + iClass.getName().toString(),
numberOfDeletedExceptionEdges, equalTo(expectedNumberOfArrayAccesses));
}
/**
* Check in case of an exceptional edge being removed, the instruction causing
* the edge is an instruction that is able to throw a NullpointerException or
* an ArrayIndexOutOfBoundException and no other exception may be thrown by
* this instruction.
*
* @param block
* @param cfg
* @param prunedCfg
* @param method
* @param identifyer
* @return if an edge of block was removed
*/
private boolean checkExceptionalSuccessors(ISSABasicBlock block, SSACFG cfg, PrunedCFG<SSAInstruction, ISSABasicBlock> prunedCfg,
IMethod method, String identifyer) {
boolean isEdgeRemoved = false;
LinkedHashSet<ISSABasicBlock> exceptionalSuccessorCfg = new LinkedHashSet<>(cfg.getExceptionalSuccessors(block));
LinkedHashSet<ISSABasicBlock> exceptionalSuccessorPruned = new LinkedHashSet<>(
prunedCfg.getExceptionalSuccessors(block));
if (!exceptionalSuccessorCfg.equals(exceptionalSuccessorPruned)) {
isEdgeRemoved = true;
if (block.getLastInstructionIndex() >= 0) {
SSAInstruction lastInstruction = block.getLastInstruction();
lastInstruction.getExceptionTypes();
Matcher<Iterable<? super TypeReference>> matcher1 = anyOf(hasItem(equalTo(TypeReference.JavaLangNullPointerException)),
hasItem(equalTo(TypeReference.JavaLangArrayIndexOutOfBoundsException)));
collector.checkThat("Edge deleted but cause instruction can't throw NullPointerException"
+ "nor ArrayIndexOutOfBoundsException: " + identifyer + ":" + method.getLineNumber(lastInstruction.iindex),
lastInstruction.getExceptionTypes(), matcher1);
Matcher<Iterable<TypeReference>> matcher2 = everyItem(anyOf(equalTo(TypeReference.JavaLangNullPointerException),
equalTo(TypeReference.JavaLangArrayIndexOutOfBoundsException)));
collector.checkThat("Edge deleted but cause instruction throws other exceptions as NullPointerException"
+ "and ArrayIndexOutOfBoundsException: " + identifyer + ":" + method.getLineNumber(lastInstruction.iindex),
lastInstruction.getExceptionTypes(), matcher2);
} else {
collector.addError(new Throwable("Exceptional edge deleted, but no instruction as cause. - No last instruction."));
}
}
return isEdgeRemoved;
}
private void checkNormalSuccessors(SSACFG cfg, PrunedCFG<SSAInstruction, ISSABasicBlock> prunedCfg, ISSABasicBlock block) {
LinkedHashSet<ISSABasicBlock> normalSuccessorCfg = new LinkedHashSet<>(cfg.getNormalSuccessors(block));
LinkedHashSet<ISSABasicBlock> normalSuccessorPruned = new LinkedHashSet<>(prunedCfg.getNormalSuccessors(block));
collector.checkThat("", normalSuccessorPruned, equalTo(normalSuccessorCfg));
}
@AfterClass
public static void free() {
scope = null;
cha = null;
irFactory = null;
options = null;
}
}