Added Tests and Testdata for array bounds analysis.

This commit is contained in:
Stephan Gocht 2015-10-19 23:20:36 +02:00
parent e85816a986
commit f8cae1b509
5 changed files with 487 additions and 0 deletions

View File

@ -0,0 +1,177 @@
package arraybounds;
/**
*
* All array accesses in the following class are unnecessary and they will be
* detected correctly by the array bounds analysis.
*
* @author Stephan Gocht <stephan@gobro.de>
*
*/
public class Detectable {
private int[] memberArr = new int[5];
/**
* Note: This is correct, even if memberArr is not final!
*
* @param i
* @return memberArr[i]
*/
public int memberLocalGet(int i) {
int[] arr = memberArr;
if (i >= 0 && i < arr.length) {
return arr[i];
} else {
throw new IllegalArgumentException();
}
}
public int[] constantCreation() {
return new int[] { 3, 4, 5 };
}
public int get(int i, int[] arr) {
if (i >= 0 && i < arr.length) {
return arr[i];
} else {
throw new IllegalArgumentException();
}
}
public void loop(int[] arr) {
for (int i = 0; i < arr.length; i++) {
arr[i] = 0;
}
}
public boolean equals(int[] a, int[] b) {
int lenA = a.length;
int lenB = b.length;
boolean result;
if (lenA == lenB) {
result = true;
for (int i = 0; i < lenA; i++) {
if (a[i] != b[i]) {
result = false;
}
}
} else {
result = false;
}
return result;
}
public void copy(int[] src, int[] dst) {
int lenSrc = src.length;
int lenDst = dst.length;
if (lenSrc < lenDst) {
for (int i = 0; i < lenSrc; i++) {
dst[i] = src[i];
}
} else {
throw new IllegalArgumentException();
}
}
/**
* swaps elements of a and b for all i: 0 <= i < min(a.length, b.length)
*
* @param a
* @param b
*/
public void swapWithMin(int[] a, int[] b) {
final int l1 = a.length;
final int l2 = b.length;
int length;
if (l1 < l2) {
length = l1;
} else {
length = l2;
}
for (int i = 0; i < length; i++) {
int tmp = a[i];
a[i] = b[i];
b[i] = tmp;
}
}
/**
* Invert the order of all elements of arr with index i: fromIndex <= i <
* toIndex.
*
* @param arr
* @param fromIndex
* @param toIndex
*/
public void partialInvert(int[] arr, int fromIndex, int toIndex) {
if (fromIndex >= 0 && toIndex <= arr.length && fromIndex < toIndex) {
for (int next = fromIndex; next < toIndex; ++next) {
int tmp = arr[toIndex - 1];
int i;
for (i = toIndex - 1; i > next; --i) {
arr[i] = arr[i - 1];
}
arr[i] = tmp; // i == next
}
}
}
/**
* The constant 3 is stored in a variable. The pi construction for the
* variable allows to detect, that the array access is in bound.
*
* Compare to {@link NotDetectable#dueToConstantPropagation(int[])}
*
* @param arr
* @return arr[3]
*/
public int nonFinalConstant(int[] arr) {
int i = 3;
if (i < arr.length) {
return arr[i];
} else {
throw new IllegalArgumentException();
}
}
/**
* Workaround for {@link NotDetectable#constants(int[])}
*
* Note: It is important, that the variable five, is compared directly to the
* length, and that further computations are performed with this variable.
*
* @param arr
* @return arr[3]
*/
public int constantsWorkaround(int[] arr) {
int five = 5;
if (arr.length > five) {
return arr[five - 2];
} else {
throw new IllegalArgumentException();
}
}
/**
* Actually aliasing is only working, because it is removed during
* construction by wala.
*
* @param i
* @param arr
* @return arr[i]
*/
public int aliasing(int i, int[] arr) {
int[] a = arr;
int[] b = arr;
if (0 <= i && i < a.length) {
return b[i];
} else {
throw new IllegalArgumentException();
}
}
}

View File

@ -0,0 +1,141 @@
package arraybounds;
/**
*
* All array accesses in the following class are unnecessary but they will not
* be detected correctly by the array bounds analysis.
*
* @author Stephan Gocht <stephan@gobro.de>
*
*/
public class NotDetectable {
private final int[] memberArr = new int[5];
/**
* Member calls are not supported. See {@link Detectable#memberLocalGet(int)}
* for workaround.
*
* @param i
* @return memberArr[i]
*/
public int memberGet(int i) {
if (i >= 0 && i < memberArr.length) {
return memberArr[i];
} else {
throw new IllegalArgumentException();
}
}
private int getLength() {
return memberArr.length;
}
/**
* Interprocedural analysis is not supported.
*
* @param i
* @return memberArr[i]
*/
public int interproceduralGet(int i) {
if (i >= 0 && i < getLength()) {
return memberArr[i];
} else {
throw new IllegalArgumentException();
}
}
/**
* This example does not work: We know 5 > 3 and sometimes length > 5 > 3. In
* case of variables this conditional relation is resolved by introducing pi
* nodes. For constants pi nodes can be generated, but the pi variables will
* not be used (maybe due to constant propagation?). Additionally 5 != 3, so
* even if we would use pi-variables for 5, there would be no relation to 3: 0
* -(5)-> 5, 5 -(-5)-> 0, {5,length} -(0)-> 5', 0 -(3)-> 3, 3 -(-3)-> 0 Given
* the inequality graph above, we know that 5,5',3 are larger than 0 and 5
* larger 3 and length is larger than 5', but not 5' larger than 3. Which is
* not always the case in general anyway.
*
* This may be implemented by replacing each use of a constant dominated by a
* definition of a pi of a constant, with a fresh variable, that is connected
* to the inequality graph accordingly.
*
* For a workaround see {@link Detectable#nonFinalConstant(int[])}
*
*
*
* @param arr
* @return arr[3]
*/
public int constants(int[] arr) {
if (arr.length > 5) {
return arr[3];
} else {
throw new IllegalArgumentException();
}
}
/**
* As the variable i is final, constant propagation will prevent the detection
* (See also {@link NotDetectable#constants(int[])}), for a working example
* see {@link Detectable#nonFinalConstant(int[])}.
*
* @param arr
* @return arr[3]
*/
public int dueToConstantPropagation(int[] arr) {
final int i = 3;
if (i < arr.length) {
return arr[i];
} else {
throw new IllegalArgumentException();
}
}
public int indirectComparison(int[] arr) {
int i = 3;
int e = i + 1;
if (e < arr.length) {
return arr[i];
} else {
throw new IllegalArgumentException();
}
}
/**
* Neither modulo, multiplication or division with constants will work.
*
* Note: Any operation can be performed BEFORE comparing a variable to the
* array length.
*
* @param arr
* @param i
* @return arr[i]
*/
public int modulo(int[] arr, int i) {
if (0 <= i && i < arr.length) {
return arr[i % 2];
} else {
throw new IllegalArgumentException();
}
}
/**
* Neither subtraction of variables nor inverting variables will work.
*
* Note: Any operation can be performed BEFORE comparing a variable to the
* array length.
*
* @param arr
* @param i
* @return arr[i]
*/
public int variableSubtraction(int[] arr, int i) {
if (0 <= i && i < arr.length) {
int i2 = 0 - i;
int i3 = -i2;
return arr[i3];
} else {
throw new IllegalArgumentException();
}
}
}

View File

@ -0,0 +1,44 @@
package arraybounds;
/**
*
* All array accesses in the following class are necessary and they will be
* detected correctly by the array bounds analysis.
*
* @author Stephan Gocht <stephan@gobro.de>
*
*/
public class NotInBound {
public int phiOverwrite(int[] arr, boolean condition) {
if (arr.length > 0) {
int l = arr.length;
if (condition) {
l = 5;
}
return arr[l - 1];
} else {
throw new IllegalArgumentException();
}
}
public void offByOne(int[] arr) {
for (int i = 0; i <= arr.length; i++) {
arr[i] = 0;
}
}
public int unknownLength(int[] arr) {
return arr[4];
}
public int ambiguity(int[] arr1, int[] arr2, int i) {
int[] arr = arr2;
if (0 <= i && i < arr.length) {
arr = arr1;
return arr[i];
} else {
throw new IllegalArgumentException();
}
}
}

View File

@ -0,0 +1,124 @@
package com.ibm.wala.core.tests.arraybounds;
import java.io.IOException;
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 static org.hamcrest.CoreMatchers.*;
import com.ibm.wala.analysis.arraybounds.ArrayOutOfBoundsAnalysis;
import com.ibm.wala.analysis.arraybounds.ArrayOutOfBoundsAnalysis.UnnecessaryCheck;
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.cha.ClassHierarchy;
import com.ibm.wala.ipa.cha.ClassHierarchyException;
import com.ibm.wala.ssa.AllDueToBranchePiPolicy;
import com.ibm.wala.ssa.DefaultIRFactory;
import com.ibm.wala.ssa.IR;
import com.ibm.wala.ssa.IRFactory;
import com.ibm.wala.types.TypeReference;
import com.ibm.wala.util.config.AnalysisScopeReader;
public class ArrayboundsAnalysisTest {
private static ClassLoader CLASS_LOADER = ArrayboundsAnalysisTest.class.getClassLoader();
private static final String DETECTABLE_TESTDATA = "Larraybounds/Detectable";
private static final int DETECTABLE_NUMBER_OF_ARRAY_ACCESS = 21;
private static final String NOT_DETECTABLE_TESTDATA = "Larraybounds/NotDetectable";
private static final int NOT_DETECTABLE_NUMBER_OF_ARRAY_ACCESS = 7;
private static final String NOT_IN_BOUND_TESTDATA = "Larraybounds/NotInBound";
private static final int NOT_IN_BOUND_TESTDATA_NUMBER_OF_ARRAY_ACCESS = 4;
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 = ClassHierarchy.make(scope);
irFactory = new DefaultIRFactory();
options = new AnalysisOptions();
options.getSSAOptions().setPiNodePolicy(new AllDueToBranchePiPolicy());
}
public static IR getIr(IMethod method) {
return irFactory.makeIR(method, Everywhere.EVERYWHERE, options.getSSAOptions());
}
public 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);
assertAllSameNecessity(iClass, DETECTABLE_NUMBER_OF_ARRAY_ACCESS, equalTo(UnnecessaryCheck.BOTH));
}
@Test
public void notDetectable() {
IClass iClass = getIClass(NOT_DETECTABLE_TESTDATA);
assertAllSameNecessity(iClass, NOT_DETECTABLE_NUMBER_OF_ARRAY_ACCESS, not(equalTo(UnnecessaryCheck.BOTH)));
}
@Test
public void notInBound() {
IClass iClass = getIClass(NOT_IN_BOUND_TESTDATA);
assertAllSameNecessity(iClass, NOT_IN_BOUND_TESTDATA_NUMBER_OF_ARRAY_ACCESS, not(equalTo(UnnecessaryCheck.BOTH)));
}
public void assertAllSameNecessity(IClass iClass, int expectedNumberOfArrayAccesses, Matcher<UnnecessaryCheck> matcher) {
int numberOfArrayAccesses = 0;
for (IMethod method : iClass.getAllMethods()) {
if (method.getDeclaringClass().equals(iClass)) {
String identifyer = method.getDeclaringClass().getName().toString() + "#" + method.getName().toString();
ArrayOutOfBoundsAnalysis analysis = new ArrayOutOfBoundsAnalysis(getIr(method));
for (UnnecessaryCheck unnecessary : analysis.getBoundsCheckNecessary().values()) {
numberOfArrayAccesses++;
collector.checkThat("Unexpected necessity for bounds check in " + identifyer, unnecessary, matcher);
}
}
}
/*
* Possible reasons for this to fail are:
*
*
* *_NUMBER_OF_ARRAY_ACCESS 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 array accesses are found.
*/
collector.checkThat("Number of found array accesses is not as expected for " + iClass.getName().toString(),
numberOfArrayAccesses, equalTo(expectedNumberOfArrayAccesses));
}
@AfterClass
public static void free() throws IOException, ClassHierarchyException {
scope = null;
cha = null;
irFactory = null;
options = null;
}
}

View File

@ -10,6 +10,7 @@ Require-Bundle: com.ibm.wala.shrike,
com.ibm.wala.util;bundle-version="1.0.0";visibility:=reexport
Bundle-ActivationPolicy: lazy
Export-Package: .,
com.ibm.wala.analysis.arraybounds,
com.ibm.wala.analysis.pointers,
com.ibm.wala.analysis.reflection,
com.ibm.wala.analysis.reflection.java7,