diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp/BranchingConstantsExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp/BranchingConstantsExample.java new file mode 100644 index 0000000000..e27810d0be --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp/BranchingConstantsExample.java @@ -0,0 +1,40 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.linear_constant_propagation.lcp; + +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.ConstantValue; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.VariableValue; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.VariableValues; + +/** + * An example to test simple variable values in presence of if-then-else constructs. + * + * @author Robin Körkemeier + */ +public class BranchingConstantsExample { + @ConstantValue(tacIndex = 13, value = 8) + @VariableValues({ + @VariableValue(tacIndex = 2), + @VariableValue(tacIndex = 11), + @VariableValue(tacIndex = 15) + }) + public static void main(String[] args) { + int a = 23; + int b = 7; + + int c; + if (args.length == 0) { + a = 42; + b = 6; + b++; + c = 1; + } else { + c = 2; + } + + int d = 1 + a; + int e = b + 1; + int f = 1 - c; + + System.out.println("a: " + a + ", b: " + b + ", c: " + c + ", d: " + d + ", e: " + e + ", f: " + f); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp/BranchingLinearCombinationExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp/BranchingLinearCombinationExample.java new file mode 100644 index 0000000000..d168d8fe1c --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp/BranchingLinearCombinationExample.java @@ -0,0 +1,66 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.linear_constant_propagation.lcp; + +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.ConstantValue; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.ConstantValues; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.VariableValue; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.VariableValues; + +/** + * An example to test linear combination values in the presence of if-then-else constructs. + * + * @author Robin Körkemeier + */ +public class BranchingLinearCombinationExample { + private static int linearCalculation1(int y, int x) { + int z; + if (y > 0) { + z = 2 * x + 4; + } else { + z = 3 * x - 3; + } + return z; + } + + private static int linearCalculation2(int y, int x) { + if (y < 0) { + return 4 * x - 13; + } else { + return x + 2; + } + } + + @ConstantValues({ + @ConstantValue(tacIndex = 18, value = 19), + @ConstantValue(tacIndex = 20, value = 18), + @ConstantValue(tacIndex = 27, value = 7) + }) + @VariableValues({ + @VariableValue(tacIndex = 23), + @VariableValue(tacIndex = 30) + }) + public static void main(String[] args) { + int a = 7; + + if (args.length == 0) { + a = 6; + a++; + } + + int b; + if (args.length == 1) { + b = 2 * a + 6; + } else { + b = 3 * a - 1; + } + + int c = b - 1; + + int d = linearCalculation1(args.length, a); + int e = linearCalculation1(args.length, 4); + int f = linearCalculation2(args.length, a - 2); + int g = linearCalculation2(args.length, 2); + + System.out.println("a: " + a + ", b: " + b + ", c: " + c + ", d: " + d + ", e: " + e + ", f: " + f + ", g: " + g); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp/ConstantsWithinMethodExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp/ConstantsWithinMethodExample.java new file mode 100644 index 0000000000..81f62995c2 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp/ConstantsWithinMethodExample.java @@ -0,0 +1,27 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.linear_constant_propagation.lcp; + +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.ConstantValue; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.ConstantValues; + +/** + * An example to test constants (simple and linear) in a single method. + * + * @author Robin Körkemeier + */ +public class ConstantsWithinMethodExample { + @ConstantValues({ + @ConstantValue(tacIndex = 0, value = 4), + @ConstantValue(tacIndex = 1, value = 3), + @ConstantValue(tacIndex = 2, value = 12), + @ConstantValue(tacIndex = 3, value = 4), + @ConstantValue(tacIndex = 4, value = 16) + }) + public static void main(String[] args) { + int a = 4; + int b = a; + int c = 3 * b + 4; + + System.out.println("a: " + a + ", b: " + b + ", c: " + c); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp/FieldAccessExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp/FieldAccessExample.java new file mode 100644 index 0000000000..d108bd4146 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp/FieldAccessExample.java @@ -0,0 +1,45 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.linear_constant_propagation.lcp; + +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.VariableValue; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.VariableValues; + +/** + * An example to test field accesses are detected by classical linear constant propagation. + * + * @author Robin Körkemeier + */ +public class FieldAccessExample { + private final int a; + int b; + static int c = 42; + int[] d = new int[]{23}; + + public FieldAccessExample(int a, int b) { + this.a = a; + this.b = b; + } + + public int getA() { + return a; + } + + @VariableValues({ + @VariableValue(tacIndex = 4), + @VariableValue(tacIndex = 5), + @VariableValue(tacIndex = 6), + @VariableValue(tacIndex = 8), + @VariableValue(tacIndex = 11) + }) + public static void main(String[] args) { + FieldAccessExample example = new FieldAccessExample(11, 22); + + int i = example.getA(); + int j = example.b; + int k = c; + int l = example.d.length; + int m = example.d[0]; + + System.out.println("i: " + i + ", j: " + j + ", k: " + k + ", l: " + l + ", m: " + m); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp/LoopExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp/LoopExample.java new file mode 100644 index 0000000000..fae59322db --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp/LoopExample.java @@ -0,0 +1,45 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.linear_constant_propagation.lcp; + +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.ConstantValue; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.VariableValue; + +/** + * An example to test behavior of IDE solver on loop constructs. + * + * @author Robin Körkemeier + */ +public class LoopExample { + public static int loop1(int a) { + int res = 0; + + while (a > 0) { + a--; + res++; + } + + return res; + } + + public static int loop2(int a) { + int res = a - 1; + + while (a > 0) { + a--; + res += 2; + System.out.println(res); + res -= 2; + } + + return res; + } + + @ConstantValue(tacIndex = 3, value = 22) + @VariableValue(tacIndex = 1) + public static void main(String[] args) { + int i = loop1(42); + int j = loop2(23); + + System.out.println("i: " + i + ", j: " + j); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp/PropagationAcrossMethodsExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp/PropagationAcrossMethodsExample.java new file mode 100644 index 0000000000..bcf14df246 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp/PropagationAcrossMethodsExample.java @@ -0,0 +1,63 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.linear_constant_propagation.lcp; + +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.*; + +/** + * An example to test fact and value propagation across methods. + * + * @author Robin Körkemeier + */ +public class PropagationAcrossMethodsExample { + @VariableValues({ + @VariableValue(tacIndex = -3), + @VariableValue(tacIndex = -4) + }) + public int linearCalculation1(String msg, int a, int b) { + System.out.println(msg + ": " + a); + return 42 - 5 * b; + } + + public static int linearCalculation2(String msg, int a) { + System.out.println(msg); + return 12 - 4 * 4 + a; + } + + @ConstantValue(tacIndex = 3, value = 139) + public static int linearCalculation3(String msg, int a) { + System.out.println(msg); + return a + 11; + } + + @UnknownValue(tacIndex = 3) + public static int linearCalculation4(String msg, int a) { + System.out.println(msg); + return 3 * a; + } + + @ConstantValues({ + @ConstantValue(tacIndex = 5, value = -18), + @ConstantValue(tacIndex = 8, value = 132), + @ConstantValue(tacIndex = 10, value = 128), + @ConstantValue(tacIndex = 18, value = 139) + }) + @VariableValues({ + @VariableValue(tacIndex = 13), + @VariableValue(tacIndex = 16) + }) + public static void main(String[] args) { + PropagationAcrossMethodsExample example = new PropagationAcrossMethodsExample(); + + int i = example.linearCalculation1("First call", 23, 12); + int j = example.linearCalculation1("Second call", 2, i); + + int k = linearCalculation2("Third call", j); + int l = linearCalculation2("Fourth call", args.length); + + int m = example.linearCalculation1("Fifth call", 12, l); + + int n = linearCalculation3("Sixth call", k); + + System.out.println("i: " + i + ", j: " + j + ", k:" + k + ", l: " + l + ", m: " + m + ", n: " + n); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp/RecursionExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp/RecursionExample.java new file mode 100644 index 0000000000..d9287aa4a1 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp/RecursionExample.java @@ -0,0 +1,28 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.linear_constant_propagation.lcp; + +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.ConstantValue; + +/** + * An example to test behavior of IDE solver when encountering recursion. + * + * @author Robin Körkemeier + */ +public class RecursionExample { + public static int recursive1(int a) { + if (a > 0) { + a -= 2; + System.out.println(recursive1(a)); + a += 2; + } + + return a + 3; + } + + @ConstantValue(tacIndex = 1, value = 14) + public static void main(String[] args) { + int i = recursive1(11); + + System.out.println("i: " + i); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp/VariablesWithinMethodExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp/VariablesWithinMethodExample.java new file mode 100644 index 0000000000..9ff0ff6c91 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp/VariablesWithinMethodExample.java @@ -0,0 +1,25 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.linear_constant_propagation.lcp; + +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.VariableValue; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.VariableValues; + +/** + * An example to test detection of variable values within a method. + * + * @author Robin Körkemeier + */ +public class VariablesWithinMethodExample { + @VariableValues({ + @VariableValue(tacIndex = 0), + @VariableValue(tacIndex = 3), + @VariableValue(tacIndex = 6) + }) + public static void main(String[] args) { + int a = args.length; + int b = args[0].length(); + int c = Integer.valueOf(42).hashCode(); + + System.out.println("a: " + a + ", b: " + b + ", c: " + c); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/ArrayNativeMethodExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/ArrayNativeMethodExample.java new file mode 100644 index 0000000000..d17418c14d --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/ArrayNativeMethodExample.java @@ -0,0 +1,45 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.linear_constant_propagation.lcp_on_fields; + +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ArrayValue; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ArrayValues; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ConstantArrayElement; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.VariableArrayElement; + +/** + * An example to test conservative handling of array elements in native method calls. + * + * @author Robin Körkemeier + */ +public class ArrayNativeMethodExample { + @ArrayValues({ + @ArrayValue(tacIndex = 1, variableElements = { + @VariableArrayElement(index = 0), + @VariableArrayElement(index = 1), + @VariableArrayElement(index = 2), + @VariableArrayElement(index = 3) + }), + @ArrayValue(tacIndex = 15, constantElements = { + @ConstantArrayElement(index = 0, value = 42), + @ConstantArrayElement(index = 1, value = 23) + }), + @ArrayValue(tacIndex = 23, variableElements = { + @VariableArrayElement(index = 0), + @VariableArrayElement(index = 1), + @VariableArrayElement(index = 2), + @VariableArrayElement(index = 3), + @VariableArrayElement(index = 4), + @VariableArrayElement(index = 5) + }) + }) + public static void main(String[] args) { + int[] arr1 = new int[]{4, 5, 6, 7}; + int[] arr2 = new int[]{42, 23}; + int[] arr3 = new int[6]; + System.arraycopy(arr1, 1, arr3, 2, 3); + + System.out.println("arr1: {" + arr1[0] + ", " + arr1[1] + ", " + arr1[2] + ", " + arr1[3] + "}; arr2: {" + + arr2[0] + ", " + arr2[1] + "}; arr3: {" + arr3[0] + ", " + arr3[1] + ", " + arr3[2] + ", " + arr3[3] + + ", " + arr3[4] + ", " + arr3[5] + "}"); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/ArrayReadWriteAcrossMethodsExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/ArrayReadWriteAcrossMethodsExample.java new file mode 100644 index 0000000000..6fa151f77c --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/ArrayReadWriteAcrossMethodsExample.java @@ -0,0 +1,43 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.linear_constant_propagation.lcp_on_fields; + +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ArrayValue; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ArrayValues; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ConstantArrayElement; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.UnknownArrayElement; + +/** + * An example to test reading and writing array elements across methods. + * + * @author Robin Körkemeier + */ +public class ArrayReadWriteAcrossMethodsExample { + public void setIndexTo23(int[] arr, int index) { + arr[index] = 23; + } + + public void set11To42(int[] arr) { + arr[11] = 42; + } + + @ArrayValues({ + @ArrayValue(tacIndex = 3, unknownElements = { + @UnknownArrayElement(index = 0), + @UnknownArrayElement(index = 1), + @UnknownArrayElement(index = 2), + @UnknownArrayElement(index = 3) + }), + @ArrayValue(tacIndex = 5, constantElements = { + @ConstantArrayElement(index = 11, value = 42) + }) + }) + public static void main(String[] args) { + ArrayReadWriteAcrossMethodsExample example = new ArrayReadWriteAcrossMethodsExample(); + + int[] arr1 = new int[100]; + int[] arr2 = new int[100]; + + example.setIndexTo23(arr1, 2); + example.set11To42(arr2); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/ArrayReadWriteConstantExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/ArrayReadWriteConstantExample.java new file mode 100644 index 0000000000..965206cf3c --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/ArrayReadWriteConstantExample.java @@ -0,0 +1,47 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.linear_constant_propagation.lcp_on_fields; + +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ArrayValue; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ArrayValues; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ConstantArrayElement; + +/** + * An example to test reading and writing array elements in one method. + * + * @author Robin Körkemeier + */ +public class ArrayReadWriteConstantExample { + @ArrayValues({ + @ArrayValue(tacIndex = 1, constantElements = { + @ConstantArrayElement(index = 0, value = 0), + @ConstantArrayElement(index = 1, value = 0), + @ConstantArrayElement(index = 2, value = 42), + @ConstantArrayElement(index = 3, value = 4), + @ConstantArrayElement(index = 4, value = 0) + }), + @ArrayValue(tacIndex = 3, constantElements = { + @ConstantArrayElement(index = 0, value = 0), + @ConstantArrayElement(index = 1, value = 2), + @ConstantArrayElement(index = 2, value = 3), + @ConstantArrayElement(index = 3, value = 4) + }), + @ArrayValue(tacIndex = 17, constantElements = { + @ConstantArrayElement(index = 0, value = 11), + @ConstantArrayElement(index = 1, value = 12), + @ConstantArrayElement(index = 2, value = 13) + }) + }) + public static void main(String[] args) { + int[] arr1 = new int[5]; + int[] arr2 = new int[]{1, 2, 3, 4}; + int[] arr3 = new int[]{11, 12, 13}; + + arr1[2] = 42; + arr1[3] = arr2[3]; + arr2[0] = arr1[4]; + + System.out.println("arr1: {" + arr1[0] + ", " + arr1[1] + ", " + arr1[2] + ", " + arr1[3] + ", " + arr1[4] + + "}; arr2: {" + arr2[0] + ", " + arr2[1] + ", " + arr2[2] + ", " + arr2[3] + "}; arr3: {" + arr3[0] + + ", " + arr3[1] + ", " + arr3[2] + "}"); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/ArrayUnknownIndicesExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/ArrayUnknownIndicesExample.java new file mode 100644 index 0000000000..ba0b3e0fbe --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/ArrayUnknownIndicesExample.java @@ -0,0 +1,52 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.linear_constant_propagation.lcp_on_fields; + +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.ConstantValue; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.VariableValue; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ArrayValue; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ConstantArrayElement; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.VariableArrayElement; + +/** + * An example to test reading and writing of arrays at an unknown index. + * + * @author Robin Körkemeier + */ +public class ArrayUnknownIndicesExample { + @ArrayValue(tacIndex = 1, variableElements = { + @VariableArrayElement(index = 0), + @VariableArrayElement(index = 1), + @VariableArrayElement(index = 10), + @VariableArrayElement(index = 11), + @VariableArrayElement(index = 12), + @VariableArrayElement(index = 13), + @VariableArrayElement(index = 98), + @VariableArrayElement(index = 99), + }, constantElements = { + @ConstantArrayElement(index = 50, value = 99) + }) + @ConstantValue(tacIndex = 9, value = 0) + @VariableValue(tacIndex = 15) + public static void main(String[] args) { + int[] arr = new int[100]; + + int i; + int j; + if (args.length == 0) { + i = 42; + j = 11; + } else { + i = 23; + j = 12; + } + + int a1 = arr[i]; + + arr[j] = 13; + arr[50] = 99; + + int a2 = arr[i]; + + System.out.println("a1: " + a1 + ", a2: " + a2); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/CreateObjectInMethodExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/CreateObjectInMethodExample.java new file mode 100644 index 0000000000..3190da56f1 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/CreateObjectInMethodExample.java @@ -0,0 +1,54 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.linear_constant_propagation.lcp_on_fields; + +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ConstantField; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ObjectValue; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ObjectValues; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.VariableField; + +/** + * An example to test objects created across methods. + * + * @author Robin Körkemeier + */ +public class CreateObjectInMethodExample { + private int a = 42; + + private CreateObjectInMethodExample createNew1() { + CreateObjectInMethodExample example = new CreateObjectInMethodExample(); + example.a -= 11; + return example; + } + + @ObjectValue(tacIndex = 0, constantValues = {@ConstantField(field = "a", value = 33)}) + private CreateObjectInMethodExample createNew2() { + CreateObjectInMethodExample example = new CreateObjectInMethodExample(); + example.a = a + 2; + return example; + } + + @ObjectValue(tacIndex = 0, variableValues = {@VariableField(field = "a")}) + private CreateObjectInMethodExample createNew3() { + CreateObjectInMethodExample example = new CreateObjectInMethodExample(); + example.a = a + 2; + return example; + } + + @ObjectValues({ + @ObjectValue(tacIndex = 0, constantValues = {@ConstantField(field = "a", value = 42)}), + @ObjectValue(tacIndex = 2, constantValues = {@ConstantField(field = "a", value = 31)}), + @ObjectValue(tacIndex = 3, constantValues = {@ConstantField(field = "a", value = 33)}), + @ObjectValue(tacIndex = 4, variableValues = {@VariableField(field = "a")}), + @ObjectValue(tacIndex = 5, variableValues = {@VariableField(field = "a")}) + }) + public static void main(String[] args) { + CreateObjectInMethodExample example1 = new CreateObjectInMethodExample(); + CreateObjectInMethodExample example2 = example1.createNew1(); + CreateObjectInMethodExample example3 = example2.createNew2(); + CreateObjectInMethodExample example4 = example3.createNew3(); + CreateObjectInMethodExample example5 = example4.createNew3(); + + System.out.println("e1: " + example1.a + ", e2: " + example2.a + ", e3: " + example3.a + ", e4: " + + example4.a + ", e5: " + example5.a); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/FieldReadWriteAcrossMethodsExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/FieldReadWriteAcrossMethodsExample.java new file mode 100644 index 0000000000..f373c0e1eb --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/FieldReadWriteAcrossMethodsExample.java @@ -0,0 +1,49 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.linear_constant_propagation.lcp_on_fields; + +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ConstantField; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ObjectValue; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ObjectValues; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.UnknownField; + +/** + * An example to test reading and writing fields of objects across methods. + * + * @author Robin Körkemeier + */ +public class FieldReadWriteAcrossMethodsExample { + private int a = -2; + + private void setA(int a) { + this.a = a; + } + + private void setATo42() { + this.a = 42; + } + + private int getA() { + return a; + } + + @ObjectValues({ + @ObjectValue(tacIndex = 0, unknownValues = {@UnknownField(field = "a")}), + @ObjectValue(tacIndex = 2, constantValues = {@ConstantField(field = "a", value = 42)}), + @ObjectValue(tacIndex = 4, constantValues = {@ConstantField(field = "a", value = -2)}) + }) + public static void main(String[] args) { + FieldReadWriteAcrossMethodsExample example1 = new FieldReadWriteAcrossMethodsExample(); + FieldReadWriteAcrossMethodsExample example2 = new FieldReadWriteAcrossMethodsExample(); + FieldReadWriteAcrossMethodsExample example3 = new FieldReadWriteAcrossMethodsExample(); + + example1.setA(23); + int e1a = example1.getA(); + + example2.setATo42(); + int e2a = example2.getA(); + + int e3a = example3.getA(); + + System.out.println("e1a: " + e1a + ", e2a: " + e2a + ", e3a: " + e3a); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/FieldReadWriteConstantExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/FieldReadWriteConstantExample.java new file mode 100644 index 0000000000..f91461578e --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/FieldReadWriteConstantExample.java @@ -0,0 +1,34 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.linear_constant_propagation.lcp_on_fields; + +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ConstantField; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ObjectValue; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ObjectValues; + +/** + * An example to test reading and writing of object fields with constants. + * + * @author Robin Körkemeier + */ +public class FieldReadWriteConstantExample { + private int a = -1; + + @ObjectValues({ + @ObjectValue(tacIndex = 0, constantValues = {@ConstantField(field = "a", value = -1)}), + @ObjectValue(tacIndex = 2, constantValues = {@ConstantField(field = "a", value = 42)}), + @ObjectValue(tacIndex = 4, constantValues = {@ConstantField(field = "a", value = 41)}) + }) + public static void main(String[] args) { + FieldReadWriteConstantExample example1 = new FieldReadWriteConstantExample(); + FieldReadWriteConstantExample example2 = new FieldReadWriteConstantExample(); + FieldReadWriteConstantExample example3 = new FieldReadWriteConstantExample(); + + example2.a = 23; + example2.a = 42; + + example3.a = example2.a; + example3.a--; + + System.out.println("e1: " + example1.a + ", e2: " + example2.a + ", e3: " + example3.a); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/FieldReadWriteWithBranchingExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/FieldReadWriteWithBranchingExample.java new file mode 100644 index 0000000000..49abee261d --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/FieldReadWriteWithBranchingExample.java @@ -0,0 +1,51 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.linear_constant_propagation.lcp_on_fields; + +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ConstantField; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ObjectValue; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ObjectValues; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.VariableField; + +/** + * An example to test reading and writing object fields in presence of if-then-else constructs. + * + * @author Robin Körkemeier + */ +public class FieldReadWriteWithBranchingExample { + private int a = -1; + + @ObjectValue(tacIndex = 0, variableValues = {@VariableField(field = "a")}) + public static FieldReadWriteWithBranchingExample multipleReturns(int y) { + FieldReadWriteWithBranchingExample e = new FieldReadWriteWithBranchingExample(); + if (y > 0) { + e.a = 42; + return e; + } else { + e.a = 23; + return e; + } + } + + @ObjectValues({ + @ObjectValue(tacIndex = 0, constantValues = {@ConstantField(field = "a", value = 42)}), + @ObjectValue(tacIndex = 2, variableValues = {@VariableField(field = "a")}), + @ObjectValue(tacIndex = 4, variableValues = {@VariableField(field = "a")}) + }) + public static void main(String[] args) { + FieldReadWriteWithBranchingExample example1 = new FieldReadWriteWithBranchingExample(); + FieldReadWriteWithBranchingExample example2 = new FieldReadWriteWithBranchingExample(); + FieldReadWriteWithBranchingExample example3 = new FieldReadWriteWithBranchingExample(); + + if (args.length == 0) { + example1.a = 42; + example2.a = 23; + example3.a = example2.a; + } else { + example1.a = 40; + example1.a += 2; + example3.a = example1.a; + } + + System.out.println("e1: " + example1.a + ", e2: " + example2.a + ", e3: " + example3.a); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/ObjectNativeMethodExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/ObjectNativeMethodExample.java new file mode 100644 index 0000000000..0d51fe7646 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/ObjectNativeMethodExample.java @@ -0,0 +1,31 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.linear_constant_propagation.lcp_on_fields; + +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ConstantField; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ObjectValue; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ObjectValues; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.VariableValue; + +/** + * An example to test conservative handling of objects when encountering native methods. + * + * @author Robin Körkemeier + */ +public class ObjectNativeMethodExample { + int a = 2; + + @VariableValue(tacIndex = 0) + @ObjectValues({ + @ObjectValue(tacIndex = 2, constantValues = { + @ConstantField(field = "a", value = 2) + }) + }) + public static void main(String[] args) { + ObjectNativeMethodExample example1 = new ObjectNativeMethodExample(); + ObjectNativeMethodExample example2 = new ObjectNativeMethodExample(); + + Class clazz = example1.getClass(); + + System.out.println("example1.a: " + example1.a + ", example2.a: " + example2.a + ", clazz: " + clazz.getName()); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/StaticFieldImmutableExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/StaticFieldImmutableExample.java new file mode 100644 index 0000000000..1f40581cde --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/StaticFieldImmutableExample.java @@ -0,0 +1,57 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.linear_constant_propagation.lcp_on_fields; + +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.ConstantValue; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.ConstantValues; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.VariableValue; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ConstantField; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.StaticValues; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.VariableField; + +/** + * An example to test detection of static immutable fields. + * + * @author Robin Körkemeier + */ +public final class StaticFieldImmutableExample { + protected static int a = 42; + static int b; + private static int c; + private static int d; + static final int e = 2; + + static { + b = 23; + + if (System.out != null) { + c = 11; + } else { + c = 12; + } + } + + @StaticValues(constantValues = { + @ConstantField(field = "a", value = 42), + @ConstantField(field = "b", value = 23), + @ConstantField(field = "d", value = 0) + }, variableValues = { + @VariableField(field = "c"), + @VariableField(field = "e") + }) + @ConstantValues({ + @ConstantValue(tacIndex = 0, value = 42), + @ConstantValue(tacIndex = 1, value = 23), + @ConstantValue(tacIndex = 3, value = 0), + @ConstantValue(tacIndex = 4, value = 2) + }) + @VariableValue(tacIndex = 2) + public static void main(String[] args) { + int a = StaticFieldImmutableExample.a; + int b = StaticFieldImmutableExample.b; + int c = StaticFieldImmutableExample.c; + int d = StaticFieldImmutableExample.d; + int e = StaticFieldImmutableExample.e; + + System.out.println("a: " + a + ", b: " + b + ", c: " + c + ", d: " + d + ", e: " + e); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/StaticFieldNonImmutableExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/StaticFieldNonImmutableExample.java new file mode 100644 index 0000000000..07bab14c37 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/StaticFieldNonImmutableExample.java @@ -0,0 +1,38 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.linear_constant_propagation.lcp_on_fields; + +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.VariableValue; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.VariableValues; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.StaticValues; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.VariableField; + +/** + * An example to test detection of static but non-immutable fields. + * + * @author Robin Körkemeier + */ +public class StaticFieldNonImmutableExample { + static int a = 42; + protected static int b = 23; + + @StaticValues(variableValues = { + @VariableField(field = "a"), + @VariableField(field = "b") + }) + @VariableValues({ + @VariableValue(tacIndex = 0), + @VariableValue(tacIndex = 1) + }) + public static void main(String[] args) { + int a = StaticFieldNonImmutableExample.a; + int b = StaticFieldNonImmutableExample.b; + + System.out.println("a: " + a + ", b: " + b); + } +} + +class StaticFieldNonImmutable2Example { + void foobar() { + StaticFieldNonImmutableExample.a = 23; + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/StaticFieldReadWriteAcrossMethodsExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/StaticFieldReadWriteAcrossMethodsExample.java new file mode 100644 index 0000000000..c297594fb3 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/StaticFieldReadWriteAcrossMethodsExample.java @@ -0,0 +1,45 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.linear_constant_propagation.lcp_on_fields; + +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.ConstantValue; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.ConstantValues; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ConstantField; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.StaticValues; + +/** + * An example to test reading and writing private static fields across methods. + * + * @author Robin Körkemeier + */ +public class StaticFieldReadWriteAcrossMethodsExample { + private static int a; + + public void setATo11() { + a = 11; + } + + private void setATo42() { + a = 42; + } + + @StaticValues(constantValues = { + @ConstantField(field = "a", value = 42) + }) + @ConstantValues({ + @ConstantValue(tacIndex = 3, value = 11), + @ConstantValue(tacIndex = 5, value = 42) + }) + public static void main(String[] args) { + StaticFieldReadWriteAcrossMethodsExample example = new StaticFieldReadWriteAcrossMethodsExample(); + + example.setATo11(); + + int a1 = a; + + example.setATo42(); + + int a2 = a; + + System.out.println("a1: " + a1 + ", a2: " + a2); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/StaticFieldReadWriteExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/StaticFieldReadWriteExample.java new file mode 100644 index 0000000000..d6162112a6 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields/StaticFieldReadWriteExample.java @@ -0,0 +1,43 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.linear_constant_propagation.lcp_on_fields; + +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.ConstantValue; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.ConstantValues; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.VariableValue; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ConstantField; +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.StaticValues; + +/** + * An example to test reading and writing private static fields in one method. + * + * @author Robin Körkemeier + */ +public class StaticFieldReadWriteExample { + private static int a; + + @StaticValues(constantValues = { + @ConstantField(field = "a", value = 11) + }) + @ConstantValues({ + @ConstantValue(tacIndex = 5, value = 23), + @ConstantValue(tacIndex = 10, value = 11) + }) + @VariableValue(tacIndex = 2) + public static void main(String[] args) { + StaticFieldReadWriteExample example1 = new StaticFieldReadWriteExample(); + + int a1 = a; + + a = 23; + + int a2 = example1.a; + + example1.a = 11; + + StaticFieldReadWriteExample example2 = new StaticFieldReadWriteExample(); + + int a3 = example2.a; + + System.out.println("a1: " + a1 + ", a2: " + a2 + ", a3: " + a3); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp/ConstantValue.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp/ConstantValue.java new file mode 100644 index 0000000000..5cd136e253 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp/ConstantValue.java @@ -0,0 +1,29 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.linear_constant_propagation.lcp; + +import org.opalj.fpcf.properties.PropertyValidator; +import org.opalj.fpcf.properties.linear_constant_propagation.ConstantValueMatcher; + +import java.lang.annotation.*; + +/** + * Annotation to state that a variable has a constant value. + * + * @author Robin Körkemeier + */ +@PropertyValidator(key = LinearConstantPropagationProperty.KEY, validator = ConstantValueMatcher.class) +@Repeatable(ConstantValues.class) +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface ConstantValue { + /** + * The index of this variable in the TAC + */ + int tacIndex(); + + /** + * The constant value of the variable + */ + int value(); +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp/ConstantValues.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp/ConstantValues.java new file mode 100644 index 0000000000..bdc1a327c8 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp/ConstantValues.java @@ -0,0 +1,21 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.linear_constant_propagation.lcp; + +import org.opalj.fpcf.properties.PropertyValidator; +import org.opalj.fpcf.properties.linear_constant_propagation.ConstantValueMatcher; + +import java.lang.annotation.*; + +/** + * Container annotation for {@link ConstantValue} annotations. + * + * @author Robin Körkemeier + */ +@PropertyValidator(key = LinearConstantPropagationProperty.KEY, validator = ConstantValueMatcher.class) +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface ConstantValues { + ConstantValue[] value(); +} + diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp/LinearConstantPropagationProperty.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp/LinearConstantPropagationProperty.java new file mode 100644 index 0000000000..5a0c295aad --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp/LinearConstantPropagationProperty.java @@ -0,0 +1,11 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.linear_constant_propagation.lcp; + +/** + * Centralized property validator key for linear constant propagation. + * + * @author Robin Körkemeier + */ +public class LinearConstantPropagationProperty { + public static final String KEY = "LinearConstantPropagation"; +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp/UnknownValue.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp/UnknownValue.java new file mode 100644 index 0000000000..f487b4f976 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp/UnknownValue.java @@ -0,0 +1,24 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.linear_constant_propagation.lcp; + +import org.opalj.fpcf.properties.PropertyValidator; +import org.opalj.fpcf.properties.linear_constant_propagation.UnknownValueMatcher; + +import java.lang.annotation.*; + +/** + * Annotation to state that a variables value is unknown. + * + * @author Robin Körkemeier + */ +@PropertyValidator(key = LinearConstantPropagationProperty.KEY, validator = UnknownValueMatcher.class) +@Repeatable(UnknownValues.class) +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface UnknownValue { + /** + * The index of this variable in the TAC + */ + int tacIndex(); +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp/UnknownValues.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp/UnknownValues.java new file mode 100644 index 0000000000..6d19adfe87 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp/UnknownValues.java @@ -0,0 +1,20 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.linear_constant_propagation.lcp; + +import org.opalj.fpcf.properties.PropertyValidator; +import org.opalj.fpcf.properties.linear_constant_propagation.UnknownValueMatcher; + +import java.lang.annotation.*; + +/** + * Container annotation for {@link UnknownValue} annotations. + * + * @author Robin Körkemeier + */ +@PropertyValidator(key = LinearConstantPropagationProperty.KEY, validator = UnknownValueMatcher.class) +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface UnknownValues { + UnknownValue[] value(); +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp/VariableValue.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp/VariableValue.java new file mode 100644 index 0000000000..f07fda1c1a --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp/VariableValue.java @@ -0,0 +1,24 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.linear_constant_propagation.lcp; + +import org.opalj.fpcf.properties.PropertyValidator; +import org.opalj.fpcf.properties.linear_constant_propagation.VariableValueMatcher; + +import java.lang.annotation.*; + +/** + * Annotation to state that a variable has a non-constant value. + * + * @author Robin Körkemeier + */ +@PropertyValidator(key = LinearConstantPropagationProperty.KEY, validator = VariableValueMatcher.class) +@Repeatable(VariableValues.class) +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface VariableValue { + /** + * The index of this variable in the TAC + */ + int tacIndex(); +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp/VariableValues.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp/VariableValues.java new file mode 100644 index 0000000000..78c0a232b9 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp/VariableValues.java @@ -0,0 +1,21 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.linear_constant_propagation.lcp; + +import org.opalj.fpcf.properties.PropertyValidator; +import org.opalj.fpcf.properties.linear_constant_propagation.VariableValueMatcher; + +import java.lang.annotation.*; + +/** + * Container annotation for {@link VariableValue} annotations. + * + * @author Robin Körkemeier + */ +@PropertyValidator(key = LinearConstantPropagationProperty.KEY, validator = VariableValueMatcher.class) +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface VariableValues { + VariableValue[] value(); +} + diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ArrayValue.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ArrayValue.java new file mode 100644 index 0000000000..050f810928 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ArrayValue.java @@ -0,0 +1,39 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields; + +import org.opalj.fpcf.properties.PropertyValidator; +import org.opalj.fpcf.properties.linear_constant_propagation.ArrayValueMatcher; + +import java.lang.annotation.*; + +/** + * Annotation to state that an array has been identified and has certain constant and non-constant elements. + * + * @author Robin Körkemeier + */ +@PropertyValidator(key = LCPOnFieldsProperty.KEY, validator = ArrayValueMatcher.class) +@Repeatable(ArrayValues.class) +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface ArrayValue { + /** + * The index in the TAC of the variable the array is stored in + */ + int tacIndex(); + + /** + * The constant elements of the array + */ + ConstantArrayElement[] constantElements() default {}; + + /** + * The non-constant elements of the array + */ + VariableArrayElement[] variableElements() default {}; + + /** + * The elements of the array with unknown value + */ + UnknownArrayElement[] unknownElements() default {}; +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ArrayValues.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ArrayValues.java new file mode 100644 index 0000000000..d709c75e67 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ArrayValues.java @@ -0,0 +1,20 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields; + +import org.opalj.fpcf.properties.PropertyValidator; +import org.opalj.fpcf.properties.linear_constant_propagation.ArrayValueMatcher; + +import java.lang.annotation.*; + +/** + * Container annotation for {@link ArrayValue} annotations. + * + * @author Robin Körkemeier + */ +@PropertyValidator(key = LCPOnFieldsProperty.KEY, validator = ArrayValueMatcher.class) +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface ArrayValues { + ArrayValue[] value(); +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ConstantArrayElement.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ConstantArrayElement.java new file mode 100644 index 0000000000..774905dcad --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ConstantArrayElement.java @@ -0,0 +1,23 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields; + +import java.lang.annotation.*; + +/** + * Annotation to state that an array element has a constant value. + * + * @author Robin Körkemeier + */ +@Documented +@Target(ElementType.ANNOTATION_TYPE) +public @interface ConstantArrayElement { + /** + * The index of the element + */ + int index(); + + /** + * The constant value of the element + */ + int value(); +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ConstantField.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ConstantField.java new file mode 100644 index 0000000000..eb8bc6fadb --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ConstantField.java @@ -0,0 +1,23 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields; + +import java.lang.annotation.*; + +/** + * Annotation to state that a field has a constant value. + * + * @author Robin Körkemeier + */ +@Documented +@Target(ElementType.ANNOTATION_TYPE) +public @interface ConstantField { + /** + * The name of the field + */ + String field(); + + /** + * The constant value of the field + */ + int value(); +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/LCPOnFieldsProperty.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/LCPOnFieldsProperty.java new file mode 100644 index 0000000000..a3485c244c --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/LCPOnFieldsProperty.java @@ -0,0 +1,11 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields; + +/** + * Centralized property validator key for linear constant propagation on fields. + * + * @author Robin Körkemeier + */ +public class LCPOnFieldsProperty { + public static final String KEY = "LCPOnFields"; +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ObjectValue.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ObjectValue.java new file mode 100644 index 0000000000..ee4ab5df00 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ObjectValue.java @@ -0,0 +1,39 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields; + +import org.opalj.fpcf.properties.PropertyValidator; +import org.opalj.fpcf.properties.linear_constant_propagation.ObjectValueMatcher; + +import java.lang.annotation.*; + +/** + * Annotation to state that an object has been identified and has certain constant and non-constant values. + * + * @author Robin Körkemeier + */ +@PropertyValidator(key = LCPOnFieldsProperty.KEY, validator = ObjectValueMatcher.class) +@Repeatable(ObjectValues.class) +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface ObjectValue { + /** + * The index in the TAC of the variable the object is stored in + */ + int tacIndex(); + + /** + * The constant fields of the object + */ + ConstantField[] constantValues() default {}; + + /** + * The non-constant fields of the object + */ + VariableField[] variableValues() default {}; + + /** + * The fields of the object with unknown value + */ + UnknownField[] unknownValues() default {}; +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ObjectValues.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ObjectValues.java new file mode 100644 index 0000000000..81fb432e1d --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ObjectValues.java @@ -0,0 +1,20 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields; + +import org.opalj.fpcf.properties.PropertyValidator; +import org.opalj.fpcf.properties.linear_constant_propagation.ObjectValueMatcher; + +import java.lang.annotation.*; + +/** + * Container annotation for {@link ObjectValue} annotations. + * + * @author Robin Körkemeier + */ +@PropertyValidator(key = LCPOnFieldsProperty.KEY, validator = ObjectValueMatcher.class) +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface ObjectValues { + ObjectValue[] value(); +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/StaticValues.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/StaticValues.java new file mode 100644 index 0000000000..f219e96d57 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/StaticValues.java @@ -0,0 +1,33 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields; + +import org.opalj.fpcf.properties.PropertyValidator; +import org.opalj.fpcf.properties.linear_constant_propagation.StaticValuesMatcher; + +import java.lang.annotation.*; + +/** + * Annotation to state that an object has certain constant and non-constant static values. + * + * @author Robin Körkemeier + */ +@PropertyValidator(key = LCPOnFieldsProperty.KEY, validator = StaticValuesMatcher.class) +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface StaticValues { + /** + * The constant static fields + */ + ConstantField[] constantValues() default {}; + + /** + * The non-constant static fields + */ + VariableField[] variableValues() default {}; + + /** + * The static fields with unknown value + */ + UnknownField[] unknownValues() default {}; +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/UnknownArrayElement.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/UnknownArrayElement.java new file mode 100644 index 0000000000..356834021b --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/UnknownArrayElement.java @@ -0,0 +1,20 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/** + * Annotation to state that an array elements value is unknown. + * + * @author Robin Körkemeier + */ +@Documented +@Target(ElementType.ANNOTATION_TYPE) +public @interface UnknownArrayElement { + /** + * The index of the element + */ + int index(); +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/UnknownField.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/UnknownField.java new file mode 100644 index 0000000000..7a861140d9 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/UnknownField.java @@ -0,0 +1,20 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/** + * Annotation to state that a field value is unknown. + * + * @author Robin Körkemeier + */ +@Documented +@Target(ElementType.ANNOTATION_TYPE) +public @interface UnknownField { + /** + * The name of the field + */ + String field(); +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/UnknownValue.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/UnknownValue.java new file mode 100644 index 0000000000..0fed859dd5 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/UnknownValue.java @@ -0,0 +1,24 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields; + +import org.opalj.fpcf.properties.PropertyValidator; +import org.opalj.fpcf.properties.linear_constant_propagation.UnknownValueMatcherLCP; + +import java.lang.annotation.*; + +/** + * Annotation to state that a variables value is unknown. + * + * @author Robin Körkemeier + */ +@PropertyValidator(key = LCPOnFieldsProperty.KEY, validator = UnknownValueMatcherLCP.class) +@Repeatable(UnknownValues.class) +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface UnknownValue { + /** + * The index of this variable in the TAC + */ + int tacIndex(); +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/UnknownValues.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/UnknownValues.java new file mode 100644 index 0000000000..b177a37940 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/UnknownValues.java @@ -0,0 +1,20 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields; + +import org.opalj.fpcf.properties.PropertyValidator; +import org.opalj.fpcf.properties.linear_constant_propagation.UnknownValueMatcherLCP; + +import java.lang.annotation.*; + +/** + * Container annotation for {@link UnknownValue} annotations. + * + * @author Robin Körkemeier + */ +@PropertyValidator(key = LCPOnFieldsProperty.KEY, validator = UnknownValueMatcherLCP.class) +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface UnknownValues { + UnknownValue[] value(); +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/VariableArrayElement.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/VariableArrayElement.java new file mode 100644 index 0000000000..50806edd0b --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/VariableArrayElement.java @@ -0,0 +1,20 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/** + * Annotation to state that an array element has a non-constant value. + * + * @author Robin Körkemeier + */ +@Documented +@Target(ElementType.ANNOTATION_TYPE) +public @interface VariableArrayElement { + /** + * The index of the element + */ + int index(); +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/VariableField.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/VariableField.java new file mode 100644 index 0000000000..c3d5717bfb --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/VariableField.java @@ -0,0 +1,20 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/** + * Annotation to state that a field has a non-constant value. + * + * @author Robin Körkemeier + */ +@Documented +@Target(ElementType.ANNOTATION_TYPE) +public @interface VariableField { + /** + * The name of the field + */ + String field(); +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/VariableValue.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/VariableValue.java new file mode 100644 index 0000000000..3878acc786 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/VariableValue.java @@ -0,0 +1,24 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields; + +import org.opalj.fpcf.properties.PropertyValidator; +import org.opalj.fpcf.properties.linear_constant_propagation.VariableValueMatcherLCP; + +import java.lang.annotation.*; + +/** + * Annotation to state that a variable has a non-constant value. + * + * @author Robin Körkemeier + */ +@PropertyValidator(key = LCPOnFieldsProperty.KEY, validator = VariableValueMatcherLCP.class) +@Repeatable(VariableValues.class) +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface VariableValue { + /** + * The index of this variable in the TAC + */ + int tacIndex(); +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/VariableValues.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/VariableValues.java new file mode 100644 index 0000000000..9332818b95 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/VariableValues.java @@ -0,0 +1,20 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields; + +import org.opalj.fpcf.properties.PropertyValidator; +import org.opalj.fpcf.properties.linear_constant_propagation.VariableValueMatcherLCP; + +import java.lang.annotation.*; + +/** + * Container annotation for {@link VariableValue} annotations. + * + * @author Robin Körkemeier + */ +@PropertyValidator(key = LCPOnFieldsProperty.KEY, validator = VariableValueMatcherLCP.class) +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface VariableValues { + VariableValue[] value(); +} diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ide/IDEPropertiesTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ide/IDEPropertiesTest.scala new file mode 100644 index 0000000000..0760c98c92 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ide/IDEPropertiesTest.scala @@ -0,0 +1,39 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package fpcf +package ide + +import java.net.URL + +import com.typesafe.config.Config +import com.typesafe.config.ConfigValueFactory + +import org.opalj.ai.domain.l2 +import org.opalj.ai.fpcf.properties.AIDomainFactoryKey +import org.opalj.br.analyses.Project +import org.opalj.br.analyses.cg.InitialInstantiatedTypesKey +import org.opalj.tac.cg.RTACallGraphKey + +/** + * Specialized test for IDE analyses preparing the configuration. + * + * @author Robin Körkemeier + */ +abstract class IDEPropertiesTest extends PropertiesTest { + override def withRT: Boolean = false + + override def createConfig(): Config = { + super.createConfig() + .withValue( + InitialInstantiatedTypesKey.ConfigKeyPrefix + "AllInstantiatedTypesFinder.projectClassesOnly", + ConfigValueFactory.fromAnyRef(false) + ) + } + + override def init(p: Project[URL]): Unit = { + p.updateProjectInformationKeyInitializationData(AIDomainFactoryKey)(_ => + Set[Class[? <: AnyRef]](classOf[l2.DefaultPerformInvocationsDomainWithCFGAndDefUse[URL]]) + ) + p.get(RTACallGraphKey) + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ide/linear_constant_propagation/LCPOnFieldsTests.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ide/linear_constant_propagation/LCPOnFieldsTests.scala new file mode 100644 index 0000000000..acbd96ad05 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ide/linear_constant_propagation/LCPOnFieldsTests.scala @@ -0,0 +1,60 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package fpcf +package ide +package linear_constant_propagation + +import org.opalj.br.analyses.SomeProject +import org.opalj.fpcf.FPCFAnalysis +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.LinearConstantPropagationProperty +import org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.LCPOnFieldsProperty +import org.opalj.ide.integration.LazyIDEAnalysisProxyScheduler +import org.opalj.tac.fpcf.analyses.fieldaccess.EagerFieldAccessInformationAnalysis +import org.opalj.tac.fpcf.analyses.fieldassignability.LazyL2FieldAssignabilityAnalysis +import org.opalj.tac.fpcf.analyses.ide.instances.lcp_on_fields.LCPOnFieldsAnalysisScheduler +import org.opalj.tac.fpcf.analyses.ide.instances.lcp_on_fields.LinearConstantPropagationAnalysisSchedulerExtended + +/** + * Test runner for linear constant propagation on fields. + * + * @author Robin Körkemeier + */ +class LCPOnFieldsTests extends IDEPropertiesTest { + override def fixtureProjectPackage: List[String] = { + List("org/opalj/fpcf/fixtures/linear_constant_propagation/lcp_on_fields") + } + + describe("Execute the LCPOnFieldsAnalysis") { + val linearConstantPropagationAnalysisSchedulerExtended = + new LinearConstantPropagationAnalysisSchedulerExtended() + val lcpOnFieldsAnalysisScheduler = new LCPOnFieldsAnalysisScheduler() + + val testContext = executeAnalyses(Set( + linearConstantPropagationAnalysisSchedulerExtended, + lcpOnFieldsAnalysisScheduler, + new LazyIDEAnalysisProxyScheduler(linearConstantPropagationAnalysisSchedulerExtended), + new LazyIDEAnalysisProxyScheduler(lcpOnFieldsAnalysisScheduler) { + override def afterPhaseScheduling(propertyStore: PropertyStore, analysis: FPCFAnalysis): Unit = { + val entryPoints = methodsWithAnnotations(analysis.project.asInstanceOf[SomeProject]) + entryPoints.foreach { case (method, _, _) => + propertyStore.force(method, lcpOnFieldsAnalysisScheduler.propertyMetaInformation.key) + propertyStore.force( + method, + linearConstantPropagationAnalysisSchedulerExtended.propertyMetaInformation.key + ) + } + } + }, + LazyL2FieldAssignabilityAnalysis, + EagerFieldAccessInformationAnalysis + )) + + testContext.propertyStore.shutdown() + + validateProperties( + testContext, + methodsWithAnnotations(testContext.project), + Set(LCPOnFieldsProperty.KEY, LinearConstantPropagationProperty.KEY) + ) + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ide/linear_constant_propagation/LinearConstantPropagationTests.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ide/linear_constant_propagation/LinearConstantPropagationTests.scala new file mode 100644 index 0000000000..3f428d2adc --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ide/linear_constant_propagation/LinearConstantPropagationTests.scala @@ -0,0 +1,41 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package fpcf +package ide +package linear_constant_propagation + +import org.opalj.br.analyses.SomeProject +import org.opalj.fpcf.properties.linear_constant_propagation.lcp.LinearConstantPropagationProperty +import org.opalj.ide.integration.EagerIDEAnalysisProxyScheduler +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.LinearConstantPropagationAnalysisScheduler + +/** + * Test runner for linear constant propagation. + * + * @author Robin Körkemeier + */ +class LinearConstantPropagationTests extends IDEPropertiesTest { + override def fixtureProjectPackage: List[String] = { + List("org/opalj/fpcf/fixtures/linear_constant_propagation/lcp") + } + + describe("Execute the LinearConstantPropagationAnalysis") { + val linearConstantPropagationAnalysisScheduler = new LinearConstantPropagationAnalysisScheduler() + + val testContext = executeAnalyses(Set( + linearConstantPropagationAnalysisScheduler, + new EagerIDEAnalysisProxyScheduler( + linearConstantPropagationAnalysisScheduler, + { (project: SomeProject) => methodsWithAnnotations(project).map(_._1) } + ) + )) + + testContext.propertyStore.shutdown() + + validateProperties( + testContext, + methodsWithAnnotations(testContext.project), + Set(LinearConstantPropagationProperty.KEY) + ) + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/ide/IDEPropertyMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/ide/IDEPropertyMatcher.scala new file mode 100644 index 0000000000..fdd66d9709 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/ide/IDEPropertyMatcher.scala @@ -0,0 +1,162 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package fpcf +package properties +package ide + +import scala.collection.immutable.ArraySeq + +import org.opalj.br.AnnotationLike +import org.opalj.br.ClassType +import org.opalj.br.analyses.Project +import org.opalj.fpcf.Property +import org.opalj.fpcf.properties.AbstractPropertyMatcher +import org.opalj.ide.integration.BasicIDEProperty + +/** + * An [[AbstractPropertyMatcher]] with additional functions useful to write property matchers for IDE analyses. + * + * @author Robin Körkemeier + */ +trait IDEPropertyMatcher extends AbstractPropertyMatcher { + /** + * The annotation type that is processed + */ + val singleAnnotationType: ClassType + + /** + * Extract an element from an annotation, treat it as an array and apply a map operator to it. + * + * @param elementName the name of the array element + */ + def mapArrayValue[V]( + p: Project[?], + a: AnnotationLike, + elementName: String, + f: AnnotationLike => V + ): ArraySeq[V] = { + getValue(p, singleAnnotationType, a.elementValuePairs, elementName).asArrayValue.values + .map { a => f(a.asAnnotationValue.annotation) } + } + + /** + * Extract a string value from the objects of an array of an annotation. + * + * @param elementName the name of the array element + * @param aInner the type of the objects inside the array + * @param innerElementName the element to extract from the objects of the array + */ + def mapArrayValueExtractString( + p: Project[?], + a: AnnotationLike, + elementName: String, + aInner: ClassType, + innerElementName: String + ): ArraySeq[String] = { + mapArrayValue( + p, + a, + elementName, + { annotation => getValue(p, aInner, annotation.elementValuePairs, innerElementName).asStringValue.value } + ) + } + + /** + * Extract an int value from the objects of an array of an annotation. + * + * @param elementName the name of the array element + * @param aInner the type of the objects inside the array + * @param innerElementName the element to extract from the objects of the array + */ + def mapArrayValueExtractInt( + p: Project[?], + a: AnnotationLike, + elementName: String, + aInner: ClassType, + innerElementName: String + ): ArraySeq[Int] = { + mapArrayValue( + p, + a, + elementName, + { annotation => getValue(p, aInner, annotation.elementValuePairs, innerElementName).asIntValue.value } + ) + } + + /** + * Extract a string and an int value from the objects of an array of an annotation. + * + * @param elementName the name of the array element + * @param aInner the type of the objects inside the array + * @param innerElementName1 the string element to extract from the objects of the array + * @param innerElementName2 the int element to extract from the objects of the array + */ + def mapArrayValueExtractStringAndInt( + p: Project[?], + a: AnnotationLike, + elementName: String, + aInner: ClassType, + innerElementName1: String, + innerElementName2: String + ): ArraySeq[(String, Int)] = { + mapArrayValue( + p, + a, + elementName, + { annotation => + ( + getValue(p, aInner, annotation.elementValuePairs, innerElementName1).asStringValue.value, + getValue(p, aInner, annotation.elementValuePairs, innerElementName2).asIntValue.value + ) + } + ) + } + + /** + * Extract two int values from the objects of an array of an annotation. + * + * @param elementName the name of the array element + * @param aInner the type of the objects inside the array + * @param innerElementName1 the first element to extract from the objects of the array + * @param innerElementName2 the second element to extract from the objects of the array + */ + + def mapArrayValueExtractIntAndInt( + p: Project[?], + a: AnnotationLike, + elementName: String, + aInner: ClassType, + innerElementName1: String, + innerElementName2: String + ): ArraySeq[(Int, Int)] = { + mapArrayValue( + p, + a, + elementName, + { annotation => + ( + getValue(p, aInner, annotation.elementValuePairs, innerElementName1).asIntValue.value, + getValue(p, aInner, annotation.elementValuePairs, innerElementName2).asIntValue.value + ) + } + ) + } + + /** + * Check whether a collection of properties contains an [[BasicIDEProperty]] that satisfies a given predicate. + */ + def existsBasicIDEProperty(properties: Iterable[Property], p: BasicIDEProperty[?, ?] => Boolean): Boolean = { + properties.exists { + case property: BasicIDEProperty[?, ?] => p(property) + case _ => false + } + } + + /** + * Check whether a collection of properties contains an [[BasicIDEProperty]] that has a result entry that satisfies + * a given predicate. + */ + def existsBasicIDEPropertyResult(properties: Iterable[Property], p: ((?, ?)) => Boolean): Boolean = { + existsBasicIDEProperty(properties, { property => property.results.exists(p(_)) }) + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/linear_constant_propagation/LCPOnFieldsMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/linear_constant_propagation/LCPOnFieldsMatcher.scala new file mode 100644 index 0000000000..f2cfd2e23e --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/linear_constant_propagation/LCPOnFieldsMatcher.scala @@ -0,0 +1,379 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package fpcf +package properties +package linear_constant_propagation + +import org.opalj.br.AnnotationLike +import org.opalj.br.ClassType +import org.opalj.br.Method +import org.opalj.br.analyses.Project +import org.opalj.fpcf.properties.ide.IDEPropertyMatcher +import org.opalj.tac.fpcf.analyses.ide.instances.lcp_on_fields +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation + +/** + * Matcher for [[org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ObjectValue]] and + * [[org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ObjectValues]] annotations. + * + * @author Robin Körkemeier + */ +class ObjectValueMatcher extends AbstractRepeatablePropertyMatcher with IDEPropertyMatcher { + override val singleAnnotationType: ClassType = + ClassType("org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ObjectValue") + override val containerAnnotationType: ClassType = + ClassType("org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ObjectValues") + + private val constantFieldType = + ClassType("org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ConstantField") + private val variableValueType = ClassType("org/opalj/fpcf/properties/linear_constant_propagation/lcp/VariableField") + private val unknownValueType = ClassType("org/opalj/fpcf/properties/linear_constant_propagation/lcp/UnknownField") + + override def validateSingleProperty( + p: Project[?], + as: Set[ClassType], + entity: Any, + a: AnnotationLike, + properties: Iterable[Property] + ): Option[String] = { + val expectedVariableTacIndex = + getValue(p, singleAnnotationType, a.elementValuePairs, "tacIndex").asIntValue.value + + val expectedConstantValues = + mapArrayValueExtractStringAndInt(p, a, "constantValues", constantFieldType, "field", "value") + + val expectedVariableValues = mapArrayValueExtractString(p, a, "variableValues", variableValueType, "field") + + val expectedUnknownValues = mapArrayValueExtractString(p, a, "unknownValues", unknownValueType, "field") + + if (existsBasicIDEPropertyResult( + properties, + { + case (f: lcp_on_fields.problem.AbstractObjectFact, lcp_on_fields.problem.ObjectValue(values)) => + expectedVariableTacIndex == f.definedAtIndex && + expectedConstantValues.forall { + case (fieldName, value) => + values.get(fieldName) match { + case Some(linear_constant_propagation.problem.ConstantValue(c)) => + value == c + case _ => false + } + } && + expectedVariableValues.forall { fieldName => + values.get(fieldName) match { + case Some(linear_constant_propagation.problem.VariableValue) => true + case _ => false + } + } && + expectedUnknownValues.forall { fieldName => + values.get(fieldName) match { + case Some(linear_constant_propagation.problem.UnknownValue) => true + case _ => false + } + } + + case _ => false + } + ) + ) { + None + } else { + val expectedValues = + expectedConstantValues + .map { case (fieldName, c) => fieldName -> linear_constant_propagation.problem.ConstantValue(c) } + .concat(expectedVariableValues.map { fieldName => + fieldName -> linear_constant_propagation.problem.VariableValue + }) + .concat(expectedUnknownValues.map { fieldName => + fieldName -> linear_constant_propagation.problem.UnknownValue + }) + .toMap + Some( + s"Result should contain (${lcp_on_fields.problem.ObjectFact("?", expectedVariableTacIndex)}, ${lcp_on_fields.problem.ObjectValue(expectedValues)})" + ) + } + } +} + +/** + * Matcher for [[org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ArrayValue]] and + * [[org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.ArrayValues]] annotations. + * + * @author Robin Körkemeier + */ +class ArrayValueMatcher extends AbstractRepeatablePropertyMatcher with IDEPropertyMatcher { + override val singleAnnotationType: ClassType = + ClassType("org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ArrayValue") + override val containerAnnotationType: ClassType = + ClassType("org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ArrayValues") + + private val constantArrayElementType = + ClassType("org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ConstantArrayElement") + private val variableArrayElementType = + ClassType("org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/VariableArrayElement") + private val unknownArrayElementType = + ClassType("org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/UnknownArrayElement") + + override def validateSingleProperty( + p: Project[?], + as: Set[ClassType], + entity: Any, + a: AnnotationLike, + properties: Iterable[Property] + ): Option[String] = { + val expectedVariableTacIndex = + getValue(p, singleAnnotationType, a.elementValuePairs, "tacIndex").asIntValue.value + + val expectedConstantElements = + mapArrayValueExtractIntAndInt(p, a, "constantElements", constantArrayElementType, "index", "value") + + val expectedVariableElements = + mapArrayValueExtractInt(p, a, "variableElements", variableArrayElementType, "index") + + val expectedUnknownElements = mapArrayValueExtractInt(p, a, "unknownElements", unknownArrayElementType, "index") + + if (existsBasicIDEPropertyResult( + properties, + { + case ( + f: lcp_on_fields.problem.AbstractArrayFact, + lcp_on_fields.problem.ArrayValue(initValue, elements) + ) => + expectedVariableTacIndex == f.definedAtIndex && + expectedConstantElements.forall { + case (index, value) => + elements.get(index) match { + case Some(linear_constant_propagation.problem.ConstantValue(c)) => + value == c + case None => + initValue == linear_constant_propagation.problem.ConstantValue(value) + case _ => false + } + } && + expectedVariableElements.forall { index => + elements.get(index) match { + case Some(linear_constant_propagation.problem.VariableValue) => true + case None => + initValue == linear_constant_propagation.problem.VariableValue + case _ => false + } + } && + expectedUnknownElements.forall { index => + elements.get(index) match { + case Some(linear_constant_propagation.problem.UnknownValue) => true + case None => + initValue == linear_constant_propagation.problem.UnknownValue + case _ => false + } + } + + case _ => false + } + ) + ) { + None + } else { + val expectedElements = + expectedConstantElements + .map { case (index, c) => index -> linear_constant_propagation.problem.ConstantValue(c) } + .concat(expectedVariableElements.map { index => + index -> linear_constant_propagation.problem.VariableValue + }) + .concat(expectedUnknownElements.map { index => + index -> linear_constant_propagation.problem.UnknownValue + }) + .toMap + Some( + s"Result should contain (${lcp_on_fields.problem.ArrayFact("?", expectedVariableTacIndex)}, ArrayValue(?, $expectedElements)" + ) + } + } +} + +/** + * Matcher for [[org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.StaticValues]] annotations. + * + * @author Robin Körkemeier + */ +class StaticValuesMatcher extends AbstractPropertyMatcher with IDEPropertyMatcher { + override val singleAnnotationType: ClassType = + ClassType("org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/StaticValues") + + private val constantFieldType = + ClassType("org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ConstantField") + private val variableValueType = ClassType("org/opalj/fpcf/properties/linear_constant_propagation/lcp/VariableField") + private val unknownValueType = ClassType("org/opalj/fpcf/properties/linear_constant_propagation/lcp/UnknownField") + + override def validateProperty( + p: Project[?], + as: Set[ClassType], + entity: Any, + a: AnnotationLike, + properties: Iterable[Property] + ): Option[String] = { + val entityClassType = entity.asInstanceOf[Method].classFile.thisType + + val expectedConstantValues = + mapArrayValueExtractStringAndInt(p, a, "constantValues", constantFieldType, "field", "value") + + val expectedVariableValues = mapArrayValueExtractString(p, a, "variableValues", variableValueType, "field") + + val expectedUnknownValues = mapArrayValueExtractString(p, a, "unknownValues", unknownValueType, "field") + + if (expectedConstantValues.forall { + case (fieldName, value) => existsBasicIDEPropertyResult( + properties, + { + case ( + f: lcp_on_fields.problem.AbstractStaticFieldFact, + lcp_on_fields.problem.StaticFieldValue(v) + ) => + f.classType == entityClassType && f.fieldName == fieldName && + (v match { + case linear_constant_propagation.problem.ConstantValue(c) => value == c + case _ => false + }) + + case _ => false + } + ) + } && + expectedVariableValues.forall { fieldName => + existsBasicIDEPropertyResult( + properties, + { + case ( + f: lcp_on_fields.problem.AbstractStaticFieldFact, + lcp_on_fields.problem.StaticFieldValue(v) + ) => + f.classType == entityClassType && f.fieldName == fieldName && + v == linear_constant_propagation.problem.VariableValue + + case _ => false + } + ) + } && + expectedUnknownValues.forall { fieldName => + existsBasicIDEPropertyResult( + properties, + { + case ( + f: lcp_on_fields.problem.AbstractStaticFieldFact, + lcp_on_fields.problem.StaticFieldValue(v) + ) => + f.classType == entityClassType && f.fieldName == fieldName && + v == linear_constant_propagation.problem.UnknownValue + + case _ => false + } + ) + } + ) { + None + } else { + val expectedValues = + expectedConstantValues + .map { case (fieldName, c) => fieldName -> linear_constant_propagation.problem.ConstantValue(c) } + .concat(expectedVariableValues.map { fieldName => + fieldName -> linear_constant_propagation.problem.VariableValue + }) + .concat(expectedUnknownValues.map { fieldName => + fieldName -> linear_constant_propagation.problem.UnknownValue + }) + .toMap + Some( + s"Result should contain ${expectedValues.map { + case (fieldName, value) => + s"(${lcp_on_fields.problem.StaticFieldFact(entityClassType, fieldName)}, ${lcp_on_fields.problem.StaticFieldValue(value)})" + + }}" + ) + } + } +} + +/** + * Matcher for [[org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.VariableValue]] and + * [[org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.VariableValues]] annotations. + * + * @author Robin Körkemeier + */ +class VariableValueMatcherLCP extends AbstractRepeatablePropertyMatcher with IDEPropertyMatcher { + override val singleAnnotationType: ClassType = + ClassType("org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/VariableValue") + override val containerAnnotationType: ClassType = + ClassType("org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/VariableValues") + + override def validateSingleProperty( + p: Project[?], + as: Set[ClassType], + entity: Any, + a: AnnotationLike, + properties: Iterable[Property] + ): Option[String] = { + val expectedVariableTacIndex = + getValue(p, singleAnnotationType, a.elementValuePairs, "tacIndex").asIntValue.value + + if (existsBasicIDEPropertyResult( + properties, + { + case (f: lcp_on_fields.problem.AbstractObjectFact, lcp_on_fields.problem.VariableValue) => + expectedVariableTacIndex == f.definedAtIndex + case (f: lcp_on_fields.problem.AbstractArrayFact, lcp_on_fields.problem.VariableValue) => + expectedVariableTacIndex == f.definedAtIndex + + case _ => false + } + ) + ) { + None + } else { + Some( + s"Result should contain (${lcp_on_fields.problem.ObjectFact("?", expectedVariableTacIndex)}, ${lcp_on_fields.problem.VariableValue})!" + ) + } + } +} + +/** + * Matcher for [[org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.UnknownValue]] and + * [[org.opalj.fpcf.properties.linear_constant_propagation.lcp_on_fields.UnknownValues]] annotations. + * + * @author Robin Körkemeier + */ +class UnknownValueMatcherLCP extends AbstractRepeatablePropertyMatcher with IDEPropertyMatcher { + override val singleAnnotationType: ClassType = + ClassType("org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/UnknownValue") + override val containerAnnotationType: ClassType = + ClassType("org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/UnknownValues") + + override def validateSingleProperty( + p: Project[?], + as: Set[ClassType], + entity: Any, + a: AnnotationLike, + properties: Iterable[Property] + ): Option[String] = { + val expectedVariableTacIndex = + getValue(p, singleAnnotationType, a.elementValuePairs, "tacIndex").asIntValue.value + + if (existsBasicIDEPropertyResult( + properties, + { + case (f: lcp_on_fields.problem.AbstractObjectFact, lcp_on_fields.problem.UnknownValue) => + expectedVariableTacIndex == f.definedAtIndex + case (f: lcp_on_fields.problem.AbstractArrayFact, lcp_on_fields.problem.UnknownValue) => + expectedVariableTacIndex == f.definedAtIndex + + case _ => false + } + ) + ) { + None + } else { + Some( + s"Result should contain (${lcp_on_fields.problem.ObjectFact("?", expectedVariableTacIndex)}, ${lcp_on_fields.problem.UnknownValue})!" + ) + } + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/linear_constant_propagation/LinearConstantPropagationMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/linear_constant_propagation/LinearConstantPropagationMatcher.scala new file mode 100644 index 0000000000..0f19f95058 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/linear_constant_propagation/LinearConstantPropagationMatcher.scala @@ -0,0 +1,145 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package fpcf +package properties +package linear_constant_propagation + +import org.opalj.br.AnnotationLike +import org.opalj.br.ClassType +import org.opalj.br.analyses.Project +import org.opalj.fpcf.properties.ide.IDEPropertyMatcher +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation + +/** + * Matcher for [[org.opalj.fpcf.properties.linear_constant_propagation.lcp.ConstantValue]] and + * [[org.opalj.fpcf.properties.linear_constant_propagation.lcp.ConstantValues]] annotations. + * + * @author Robin Körkemeier + */ +class ConstantValueMatcher extends AbstractRepeatablePropertyMatcher with IDEPropertyMatcher { + override val singleAnnotationType: ClassType = + ClassType("org/opalj/fpcf/properties/linear_constant_propagation/lcp/ConstantValue") + override val containerAnnotationType: ClassType = + ClassType("org/opalj/fpcf/properties/linear_constant_propagation/lcp/ConstantValues") + + override def validateSingleProperty( + p: Project[?], + as: Set[ClassType], + entity: Any, + a: AnnotationLike, + properties: Iterable[Property] + ): Option[String] = { + val expectedVariableTacIndex = + getValue(p, singleAnnotationType, a.elementValuePairs, "tacIndex").asIntValue.value + val expectedVariableValue = + getValue(p, singleAnnotationType, a.elementValuePairs, "value").asIntValue.value + + if (existsBasicIDEPropertyResult( + properties, + { + case ( + linear_constant_propagation.problem.VariableFact(_, definedAtIndex), + linear_constant_propagation.problem.ConstantValue(value) + ) => + expectedVariableTacIndex == definedAtIndex && expectedVariableValue == value + + case _ => false + } + ) + ) { + None + } else { + Some( + s"Result should contain (${linear_constant_propagation.problem.VariableFact("?", expectedVariableTacIndex)}, ${linear_constant_propagation.problem.ConstantValue(expectedVariableValue)})!" + ) + } + } +} + +/** + * Matcher for [[org.opalj.fpcf.properties.linear_constant_propagation.lcp.VariableValue]] and + * [[org.opalj.fpcf.properties.linear_constant_propagation.lcp.VariableValues]] annotations. + * + * @author Robin Körkemeier + */ +class VariableValueMatcher extends AbstractRepeatablePropertyMatcher with IDEPropertyMatcher { + override val singleAnnotationType: ClassType = + ClassType("org/opalj/fpcf/properties/linear_constant_propagation/lcp/VariableValue") + override val containerAnnotationType: ClassType = + ClassType("org/opalj/fpcf/properties/linear_constant_propagation/lcp/VariableValues") + + override def validateSingleProperty( + p: Project[?], + as: Set[ClassType], + entity: Any, + a: AnnotationLike, + properties: Iterable[Property] + ): Option[String] = { + val expectedVariableTacIndex = + getValue(p, singleAnnotationType, a.elementValuePairs, "tacIndex").asIntValue.value + + if (existsBasicIDEPropertyResult( + properties, + { + case ( + linear_constant_propagation.problem.VariableFact(_, definedAtIndex), + linear_constant_propagation.problem.VariableValue + ) => + expectedVariableTacIndex == definedAtIndex + + case _ => false + } + ) + ) { + None + } else { + Some( + s"Result should contain (${linear_constant_propagation.problem.VariableFact("?", expectedVariableTacIndex)}, ${linear_constant_propagation.problem.VariableValue})!" + ) + } + } +} + +/** + * Matcher for [[org.opalj.fpcf.properties.linear_constant_propagation.lcp.UnknownValue]] and + * [[org.opalj.fpcf.properties.linear_constant_propagation.lcp.UnknownValues]] annotations. + * + * @author Robin Körkemeier + */ +class UnknownValueMatcher extends AbstractRepeatablePropertyMatcher with IDEPropertyMatcher { + override val singleAnnotationType: ClassType = + ClassType("org/opalj/fpcf/properties/linear_constant_propagation/lcp/UnknownValue") + override val containerAnnotationType: ClassType = + ClassType("org/opalj/fpcf/properties/linear_constant_propagation/lcp/UnknownValues") + + override def validateSingleProperty( + p: Project[?], + as: Set[ClassType], + entity: Any, + a: AnnotationLike, + properties: Iterable[Property] + ): Option[String] = { + val expectedVariableTacIndex = + getValue(p, singleAnnotationType, a.elementValuePairs, "tacIndex").asIntValue.value + + if (existsBasicIDEPropertyResult( + properties, + { + case ( + linear_constant_propagation.problem.VariableFact(_, definedAtIndex), + linear_constant_propagation.problem.UnknownValue + ) => + expectedVariableTacIndex == definedAtIndex + + case _ => false + } + ) + ) { + None + } else { + Some( + s"Result should contain (${linear_constant_propagation.problem.VariableFact("?", expectedVariableTacIndex)}, ${linear_constant_propagation.problem.UnknownValue})!" + ) + } + } +} diff --git a/OPAL/ProjectDependencies.mmd b/OPAL/ProjectDependencies.mmd index 0de1ad484a..c2b4b3211e 100644 --- a/OPAL/ProjectDependencies.mmd +++ b/OPAL/ProjectDependencies.mmd @@ -8,6 +8,7 @@ flowchart BT br[Bytecode Representation
br] da[Bytecode Disassembler
da] + ide[IDE
ide] ifds[IFDS
ifds] ai[Abstract Interpretation Framework
ai] bc[Bytecode Creator
bc] @@ -38,6 +39,9 @@ flowchart BT br --> bi da --> bi + ide --> si + ide --> br + ifds --> si ifds --> br @@ -48,6 +52,7 @@ flowchart BT de --> ai tac --> ifds + tac --> ide tac --> ai apk --> tac @@ -63,4 +68,4 @@ flowchart BT demos --> framework bp --> framework - hermes --> framework \ No newline at end of file + hermes --> framework diff --git a/OPAL/ProjectDependencies.pdf b/OPAL/ProjectDependencies.pdf index 5357e010af..014dda30bd 100644 Binary files a/OPAL/ProjectDependencies.pdf and b/OPAL/ProjectDependencies.pdf differ diff --git a/OPAL/ProjectDependencies.svg b/OPAL/ProjectDependencies.svg index 3214fad43f..3baade485a 100644 --- a/OPAL/ProjectDependencies.svg +++ b/OPAL/ProjectDependencies.svg @@ -1 +1 @@ -

Common
common

Static Analysis Infrastructure
si

Bytecode Infrastructure
bi

Bytecode Representation
br

Bytecode Disassembler
da

IFDS
ifds

Abstract Interpretation Framework
ai

Bytecode Creator
bc

Three Address Code
tac

Dependency Extraction
de

Bytecode Assembler
ba

APK
apk

Architecture Validation
av

Framework

Demos

BugPicker
bp

Hermes
hermes

\ No newline at end of file +

Common
common

Static Analysis Infrastructure
si

Bytecode Infrastructure
bi

Bytecode Representation
br

Bytecode Disassembler
da

IDE
ide

IFDS
ifds

Abstract Interpretation Framework
ai

Bytecode Creator
bc

Three Address Code
tac

Dependency Extraction
de

Bytecode Assembler
ba

APK
apk

Architecture Validation
av

Framework

Demos

BugPicker
bp

Hermes
hermes

\ No newline at end of file diff --git a/OPAL/ide/Readme.md b/OPAL/ide/Readme.md new file mode 100644 index 0000000000..3a3cec1ce1 --- /dev/null +++ b/OPAL/ide/Readme.md @@ -0,0 +1,2 @@ +# Overview +The ***IDE*** (ide) module provides a generic implementation for IDE analyses. diff --git a/OPAL/ide/build.sbt b/OPAL/ide/build.sbt new file mode 100644 index 0000000000..b511e98651 --- /dev/null +++ b/OPAL/ide/build.sbt @@ -0,0 +1 @@ +// build settings reside in the opal root build.sbt file diff --git a/OPAL/ide/src/main/scala/org/opalj/ide/ifds/integration/IFDSPropertyMetaInformation.scala b/OPAL/ide/src/main/scala/org/opalj/ide/ifds/integration/IFDSPropertyMetaInformation.scala new file mode 100644 index 0000000000..849e48db9c --- /dev/null +++ b/OPAL/ide/src/main/scala/org/opalj/ide/ifds/integration/IFDSPropertyMetaInformation.scala @@ -0,0 +1,18 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package ide +package ifds +package integration + +import org.opalj.fpcf.Entity +import org.opalj.ide.ifds.problem.IFDSValue +import org.opalj.ide.integration.IDEPropertyMetaInformation +import org.opalj.ide.problem.IDEFact + +/** + * Interface for property meta information for IFDS problems based on an IDE problem. + * + * @author Robin Körkemeier + */ +trait IFDSPropertyMetaInformation[Fact <: IDEFact, Statement, Callable <: Entity] + extends IDEPropertyMetaInformation[Fact, IFDSValue, Statement, Callable] diff --git a/OPAL/ide/src/main/scala/org/opalj/ide/ifds/problem/IFDSEdgeFunctions.scala b/OPAL/ide/src/main/scala/org/opalj/ide/ifds/problem/IFDSEdgeFunctions.scala new file mode 100644 index 0000000000..cd0f3760bb --- /dev/null +++ b/OPAL/ide/src/main/scala/org/opalj/ide/ifds/problem/IFDSEdgeFunctions.scala @@ -0,0 +1,21 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package ide +package ifds +package problem + +import org.opalj.ide.problem.EdgeFunction +import org.opalj.ide.problem.IDEValue + +/** + * Edge function evaluating all source values to the bottom value. + * + * @author Robin Körkemeier + */ +object AllBottomEdgeFunction extends org.opalj.ide.problem.AllBottomEdgeFunction[IFDSValue](Bottom) { + override def composeWith[V >: IFDSValue <: IDEValue](secondEdgeFunction: EdgeFunction[V]): EdgeFunction[V] = { + this + } + + override def toString: String = "AllBottomEdgeFunction()" +} diff --git a/OPAL/ide/src/main/scala/org/opalj/ide/ifds/problem/IFDSLattice.scala b/OPAL/ide/src/main/scala/org/opalj/ide/ifds/problem/IFDSLattice.scala new file mode 100644 index 0000000000..124547ee0f --- /dev/null +++ b/OPAL/ide/src/main/scala/org/opalj/ide/ifds/problem/IFDSLattice.scala @@ -0,0 +1,23 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package ide +package ifds +package problem + +import org.opalj.ide.problem.MeetLattice + +/** + * Lattice to use for IFDS problems that are solved with an IDE solver. + * + * @author Robin Körkemeier + */ +object IFDSLattice extends MeetLattice[IFDSValue] { + override def top: IFDSValue = Top + + override def bottom: IFDSValue = Bottom + + override def meet(x: IFDSValue, y: IFDSValue): IFDSValue = (x, y) match { + case (Top, Top) => Top + case _ => Bottom + } +} diff --git a/OPAL/ide/src/main/scala/org/opalj/ide/ifds/problem/IFDSProblem.scala b/OPAL/ide/src/main/scala/org/opalj/ide/ifds/problem/IFDSProblem.scala new file mode 100644 index 0000000000..bd6f6b6d24 --- /dev/null +++ b/OPAL/ide/src/main/scala/org/opalj/ide/ifds/problem/IFDSProblem.scala @@ -0,0 +1,129 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package ide +package ifds +package problem + +import org.opalj.fpcf.Entity +import org.opalj.fpcf.PropertyStore +import org.opalj.ide.problem.EdgeFunctionResult +import org.opalj.ide.problem.IDEFact +import org.opalj.ide.problem.IdentityEdgeFunction +import org.opalj.ide.problem.IDEProblem +import org.opalj.ide.problem.MeetLattice + +/** + * Interface for modeling IFDS problems based on an IDE problem. + * + * @author Robin Körkemeier + */ +abstract class IFDSProblem[Fact <: IDEFact, Statement, Callable <: Entity] + extends IDEProblem[Fact, IFDSValue, Statement, Callable] { + override final val lattice: MeetLattice[IFDSValue] = IFDSLattice + + override final def getNormalEdgeFunction( + source: Statement, + sourceFact: Fact, + target: Statement, + targetFact: Fact + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[IFDSValue] = { + if (sourceFact == nullFact) { + AllBottomEdgeFunction + } else { + IdentityEdgeFunction + } + } + + override final def getCallEdgeFunction( + callSite: Statement, + callSiteFact: Fact, + calleeEntry: Statement, + calleeEntryFact: Fact, + callee: Callable + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[IFDSValue] = { + if (callSiteFact == nullFact) { + AllBottomEdgeFunction + } else { + IdentityEdgeFunction + } + } + + override final def getReturnEdgeFunction( + calleeExit: Statement, + calleeExitFact: Fact, + callee: Callable, + returnSite: Statement, + returnSiteFact: Fact, + callSite: Statement, + callSiteFact: Fact + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[IFDSValue] = { + if (calleeExitFact == nullFact) { + AllBottomEdgeFunction + } else { + IdentityEdgeFunction + } + } + + override final def getCallToReturnEdgeFunction( + callSite: Statement, + callSiteFact: Fact, + callee: Callable, + returnSite: Statement, + returnSiteFact: Fact + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[IFDSValue] = { + if (callSiteFact == nullFact) { + AllBottomEdgeFunction + } else { + IdentityEdgeFunction + } + } + + /** + * Whether precomputed flow functions for a `(callSite, callSiteFact, callee)` combination exist (resp. can be + * generated). + * + * @param callSite where the flow starts + * @param callSiteFact the fact the flow starts with + * @param callee the callable this flow is about + */ + def hasPrecomputedFlowFunction(callSite: Statement, callSiteFact: Fact, callee: Callable)( + implicit propertyStore: PropertyStore + ): Boolean = { + false + } + + override final def hasPrecomputedFlowAndSummaryFunction( + callSite: Statement, + callSiteFact: Fact, + callee: Callable + )(implicit propertyStore: PropertyStore): Boolean = { + hasPrecomputedFlowFunction(callSite, callSiteFact, callee) + } + + override final def getPrecomputedSummaryFunction( + callSite: Statement, + callSiteFact: Fact, + callee: Callable, + returnSite: Statement, + returnSiteFact: Fact + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[IFDSValue] = { + if (callSiteFact == nullFact) { + AllBottomEdgeFunction + } else { + IdentityEdgeFunction + } + } + + override final def getPrecomputedSummaryFunction( + callSite: Statement, + callSiteFact: Fact, + returnSite: Statement, + returnSiteFact: Fact + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[IFDSValue] = { + if (callSiteFact == nullFact) { + AllBottomEdgeFunction + } else { + IdentityEdgeFunction + } + } +} diff --git a/OPAL/ide/src/main/scala/org/opalj/ide/ifds/problem/IFDSValue.scala b/OPAL/ide/src/main/scala/org/opalj/ide/ifds/problem/IFDSValue.scala new file mode 100644 index 0000000000..550a1766b1 --- /dev/null +++ b/OPAL/ide/src/main/scala/org/opalj/ide/ifds/problem/IFDSValue.scala @@ -0,0 +1,28 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package ide +package ifds +package problem + +import org.opalj.ide.problem.IDEValue + +/** + * Type for modeling values for IFDS problems that are solved with an IDE solver. + * + * @author Robin Körkemeier + */ +trait IFDSValue extends IDEValue + +/** + * Top value + * + * @author Robin Körkemeier + */ +case object Top extends IFDSValue + +/** + * Bottom value (all result facts have the bottom value) + * + * @author Robin Körkemeier + */ +case object Bottom extends IFDSValue diff --git a/OPAL/ide/src/main/scala/org/opalj/ide/integration/BaseIDEAnalysisProxyScheduler.scala b/OPAL/ide/src/main/scala/org/opalj/ide/integration/BaseIDEAnalysisProxyScheduler.scala new file mode 100644 index 0000000000..730249ebce --- /dev/null +++ b/OPAL/ide/src/main/scala/org/opalj/ide/integration/BaseIDEAnalysisProxyScheduler.scala @@ -0,0 +1,65 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package ide +package integration + +import scala.collection.immutable.Set + +import org.opalj.br.analyses.ProjectInformationKeys +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.FPCFAnalysisScheduler +import org.opalj.fpcf.Entity +import org.opalj.fpcf.FPCFAnalysis +import org.opalj.fpcf.PartialResult +import org.opalj.fpcf.PropertyBounds +import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.PropertyStoreKey +import org.opalj.fpcf.SomeEOptionP +import org.opalj.ide.problem.IDEFact +import org.opalj.ide.problem.IDEValue +import org.opalj.ide.solver.IDEAnalysisProxy + +/** + * Base scheduler to schedule the proxy analysis that is used to access the IDE analysis results. + * + * @author Robin Körkemeier + */ +trait BaseIDEAnalysisProxyScheduler[Fact <: IDEFact, Value <: IDEValue, Statement, Callable <: Entity] + extends FPCFAnalysisScheduler { + val propertyMetaInformation: IDEPropertyMetaInformation[Fact, Value, Statement, Callable] + + override type InitializationData = IDEAnalysisProxy[Fact, Value, Statement, Callable] + + override def derivesCollaboratively: Set[PropertyBounds] = Set.empty + + override def requiredProjectInformation: ProjectInformationKeys = Seq(PropertyStoreKey) + + override def init( + project: SomeProject, + propertyStore: PropertyStore + ): IDEAnalysisProxy[Fact, Value, Statement, Callable] = { + new IDEAnalysisProxy[Fact, Value, Statement, Callable](project, propertyMetaInformation) + } + + override def uses: Set[PropertyBounds] = + Set(PropertyBounds.ub(propertyMetaInformation.backingPropertyMetaInformation)) + + override def beforeSchedule(project: SomeProject, propertyStore: PropertyStore): Unit = { + /* Add initial result for target callables */ + propertyStore.handleResult( + PartialResult( + propertyMetaInformation, + propertyMetaInformation.targetCallablesPropertyMetaInformation.key, + { (_: SomeEOptionP) => None } + ) + ) + } + + override def afterPhaseScheduling(propertyStore: PropertyStore, analysis: FPCFAnalysis): Unit = {} + + override def afterPhaseCompletion( + project: SomeProject, + propertyStore: PropertyStore, + analysis: FPCFAnalysis + ): Unit = {} +} diff --git a/OPAL/ide/src/main/scala/org/opalj/ide/integration/EagerIDEAnalysisProxyScheduler.scala b/OPAL/ide/src/main/scala/org/opalj/ide/integration/EagerIDEAnalysisProxyScheduler.scala new file mode 100644 index 0000000000..9685bf4e18 --- /dev/null +++ b/OPAL/ide/src/main/scala/org/opalj/ide/integration/EagerIDEAnalysisProxyScheduler.scala @@ -0,0 +1,49 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package ide +package integration + +import org.opalj.br.Method +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.FPCFAnalysis +import org.opalj.br.fpcf.FPCFEagerAnalysisScheduler +import org.opalj.fpcf.Entity +import org.opalj.fpcf.PropertyBounds +import org.opalj.fpcf.PropertyStore +import org.opalj.ide.problem.IDEFact +import org.opalj.ide.problem.IDEValue +import org.opalj.ide.solver.IDEAnalysisProxy + +/** + * A scheduler to (eagerly) schedule the proxy analysis that is used to access the IDE analysis results. + * + * @param methodProvider for which methods the results should be computed eagerly + * + * @author Robin Körkemeier + */ +class EagerIDEAnalysisProxyScheduler[Fact <: IDEFact, Value <: IDEValue, Statement, Callable <: Entity]( + val propertyMetaInformation: IDEPropertyMetaInformation[Fact, Value, Statement, Callable], + methodProvider: SomeProject => Iterable[Method] = { project => project.allMethodsWithBody } +) extends BaseIDEAnalysisProxyScheduler[Fact, Value, Statement, Callable] with FPCFEagerAnalysisScheduler { + def this(ideAnalysisScheduler: IDEAnalysisScheduler[Fact, Value, Statement, Callable, ?]) = { + this(ideAnalysisScheduler.propertyMetaInformation) + } + + def this( + ideAnalysisScheduler: IDEAnalysisScheduler[Fact, Value, Statement, Callable, ?], + methodProvider: SomeProject => Iterable[Method] + ) = { + this(ideAnalysisScheduler.propertyMetaInformation, methodProvider) + } + + override def derivesEagerly: Set[PropertyBounds] = Set(PropertyBounds.ub(propertyMetaInformation)) + + override def start( + project: SomeProject, + propertyStore: PropertyStore, + analysis: IDEAnalysisProxy[Fact, Value, Statement, Callable] + ): FPCFAnalysis = { + propertyStore.scheduleEagerComputationsForEntities(methodProvider(project))(analysis.proxyAnalysis) + analysis + } +} diff --git a/OPAL/ide/src/main/scala/org/opalj/ide/integration/FlowRecordingAnalysisScheduler.scala b/OPAL/ide/src/main/scala/org/opalj/ide/integration/FlowRecordingAnalysisScheduler.scala new file mode 100644 index 0000000000..bd8e64ce5e --- /dev/null +++ b/OPAL/ide/src/main/scala/org/opalj/ide/integration/FlowRecordingAnalysisScheduler.scala @@ -0,0 +1,183 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package ide +package integration + +import scala.annotation.tailrec + +import java.io.File +import java.io.FileNotFoundException +import java.io.FileWriter +import java.io.Writer +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import scala.collection.mutable.{Map => MutableMap} + +import org.opalj.br.analyses.ProjectInformationKeys +import org.opalj.br.analyses.SomeProject +import org.opalj.fpcf.Entity +import org.opalj.fpcf.FPCFAnalysis +import org.opalj.fpcf.PropertyBounds +import org.opalj.fpcf.PropertyStore +import org.opalj.ide.problem.FlowRecorderModes +import org.opalj.ide.problem.FlowRecorderModes.FlowRecorderMode +import org.opalj.ide.problem.FlowRecordingIDEProblem +import org.opalj.ide.problem.IDEFact +import org.opalj.ide.problem.IDEProblem +import org.opalj.ide.problem.IDEValue +import org.opalj.ide.solver.ICFG +import org.opalj.ide.solver.IDEAnalysis +import org.opalj.log.GlobalLogContext +import org.opalj.log.LogContext +import org.opalj.log.OPALLogger + +/** + * Wrapper class for a normal IDE analysis scheduler for debugging purposes. Records the flow paths the IDE solver takes + * for a given base problem as graph and writes it to a file in DOT format. + * + * @param path the location to write the resulting DOT file (either a file ending with `.dot` or a directory) + * @param uniqueFlowsOnly whether to drop or to keep duplicated flows + * @param recordEdgeFunctions whether to record edge functions too or just stick with the flow + * + * @author Robin Körkemeier + */ +class FlowRecordingAnalysisScheduler[ + Fact <: IDEFact, + Value <: IDEValue, + Statement, + Callable <: Entity, + _ICFG <: ICFG[Statement, Callable] +]( + ideAnalysisScheduler: IDEAnalysisScheduler[Fact, Value, Statement, Callable, _ICFG], + path: Option[Path] = Some(Paths.get("target/flow-recordings")), + recorderMode: FlowRecorderMode = FlowRecorderModes.NODE_AS_STMT_AND_FACT, + uniqueFlowsOnly: Boolean = true, + recordEdgeFunctions: Boolean = true +) extends IDEAnalysisScheduler[Fact, Value, Statement, Callable, _ICFG] { + private implicit val logContext: LogContext = GlobalLogContext + + override def propertyMetaInformation: IDEPropertyMetaInformation[Fact, Value, Statement, Callable] = { + ideAnalysisScheduler.propertyMetaInformation + } + + override def createProblem(project: SomeProject, icfg: _ICFG): IDEProblem[Fact, Value, Statement, Callable] = { + val flowRecordingProblem = new FlowRecordingIDEProblem( + ideAnalysisScheduler.createProblem(project, icfg), + icfg, + recorderMode, + uniqueFlowsOnly, + recordEdgeFunctions + ) + startRecording(project, flowRecordingProblem) + flowRecordingProblem + } + + override def createICFG(project: SomeProject): _ICFG = { + ideAnalysisScheduler.createICFG(project) + } + + override def requiredProjectInformation: ProjectInformationKeys = { + ideAnalysisScheduler.requiredProjectInformation + } + + override def uses: Set[PropertyBounds] = { + ideAnalysisScheduler.uses + } + + override def beforeSchedule(project: SomeProject, propertyStore: PropertyStore): Unit = { + ideAnalysisScheduler.beforeSchedule(project, propertyStore) + } + + override def afterPhaseScheduling(propertyStore: PropertyStore, analysis: FPCFAnalysis): Unit = { + ideAnalysisScheduler.afterPhaseScheduling(propertyStore, analysis) + } + + override def afterPhaseCompletion( + project: SomeProject, + propertyStore: PropertyStore, + analysis: FPCFAnalysis + ): Unit = { + ideAnalysisScheduler.afterPhaseCompletion(project, propertyStore, analysis) + stopRecording( + analysis.asInstanceOf[IDEAnalysis[Fact, Value, Statement, Callable]] + .problem.asInstanceOf[FlowRecordingIDEProblem[Fact, Value, Statement, Callable]] + ) + } + + /** + * Associate used writers with the file they write to + */ + private val fileByWriter = MutableMap.empty[Writer, File] + + /** + * Get the file to write the graph to. If [[path]] references a file then this method will return [[path]]. If + * [[path]] references a directory, a new filename is created (s.t. no file gets overwritten). + */ + private def getFile(project: SomeProject): File = { + lazy val className = { + val classFQN = project.projectClassFilesWithSources.head._1.thisType.fqn + classFQN.substring(classFQN.lastIndexOf('/') + 1) + } + + @tailrec + def getNextFreeWithBasePath(basePath: Path, index: Int = 1): Path = { + val pathToCheck = + if (index == 0) { + basePath.resolve(s"$className-flow-recording.dot") + } else { + basePath.resolve(s"$className-flow-recording-$index.dot") + } + + if (Files.exists(pathToCheck)) { + getNextFreeWithBasePath(basePath, index + 1) + } else { + pathToCheck + } + } + + val completePath = path match { + case Some(p) => + if (p.toUri.getPath.endsWith(".dot")) { + p + } else { + getNextFreeWithBasePath(p) + } + case None => getNextFreeWithBasePath(Paths.get(".")) + } + + completePath.toFile + } + + private def startRecording( + project: SomeProject, + flowRecordingProblem: FlowRecordingIDEProblem[Fact, Value, Statement, Callable] + ): Unit = { + val file = getFile(project) + val directoryAsFile = file.getParentFile + val directoryAsPath = directoryAsFile.toPath.toAbsolutePath.normalize() + if (!directoryAsFile.exists()) { + if (directoryAsPath.startsWith(Paths.get(".").toAbsolutePath.normalize())) { + OPALLogger.warn(FrameworkName, s"creating directory '$directoryAsPath' as it didn't exist!") + directoryAsFile.mkdirs() + } else { + throw new FileNotFoundException( + s"Directory '$directoryAsPath' does not exist! Directories outside of the project directory are not created automatically!" + ) + } + } + + val writer = new FileWriter(file) + fileByWriter.put(writer, file) + + flowRecordingProblem.startRecording(writer) + } + + private def stopRecording(flowRecordingProblem: FlowRecordingIDEProblem[Fact, Value, Statement, Callable]): Unit = { + val writer = flowRecordingProblem.stopRecording() + + writer.close() + + OPALLogger.info(FrameworkName, s"wrote flow recording to '${fileByWriter(writer)}'") + } +} diff --git a/OPAL/ide/src/main/scala/org/opalj/ide/integration/IDEAnalysisScheduler.scala b/OPAL/ide/src/main/scala/org/opalj/ide/integration/IDEAnalysisScheduler.scala new file mode 100644 index 0000000000..772fda4629 --- /dev/null +++ b/OPAL/ide/src/main/scala/org/opalj/ide/integration/IDEAnalysisScheduler.scala @@ -0,0 +1,81 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package ide +package integration + +import scala.collection.immutable.Set + +import org.opalj.br.analyses.ProjectInformationKeys +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler +import org.opalj.fpcf.Entity +import org.opalj.fpcf.FPCFAnalysis +import org.opalj.fpcf.PropertyBounds +import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.PropertyStoreKey +import org.opalj.ide.problem.IDEFact +import org.opalj.ide.problem.IDEProblem +import org.opalj.ide.problem.IDEValue +import org.opalj.ide.solver.ICFG +import org.opalj.ide.solver.IDEAnalysis + +/** + * A base scheduler for IDE analyses adding common default behavior. + * + * @author Robin Körkemeier + */ +abstract class IDEAnalysisScheduler[ + Fact <: IDEFact, + Value <: IDEValue, + Statement, + Callable <: Entity, + _ICFG <: ICFG[Statement, Callable] +] extends FPCFLazyAnalysisScheduler { + override final type InitializationData = IDEAnalysis[Fact, Value, Statement, Callable] + + def propertyMetaInformation: IDEPropertyMetaInformation[Fact, Value, Statement, Callable] + + def createProblem(project: SomeProject, icfg: _ICFG): IDEProblem[Fact, Value, Statement, Callable] + + def createICFG(project: SomeProject): _ICFG + + override final def derivesLazily: Some[PropertyBounds] = + Some(PropertyBounds.ub(propertyMetaInformation.backingPropertyMetaInformation)) + + override def requiredProjectInformation: ProjectInformationKeys = + Seq(PropertyStoreKey) + + override def uses: Set[PropertyBounds] = + Set.empty + + override def beforeSchedule(project: SomeProject, propertyStore: PropertyStore): Unit = {} + + override final def init( + project: SomeProject, + propertyStore: PropertyStore + ): IDEAnalysis[Fact, Value, Statement, Callable] = { + val icfg = createICFG(project) + val problem = createProblem(project, icfg) + new IDEAnalysis(project, problem, icfg, propertyMetaInformation) + } + + override final def register( + project: SomeProject, + propertyStore: PropertyStore, + analysis: IDEAnalysis[Fact, Value, Statement, Callable] + ): FPCFAnalysis = { + propertyStore.registerLazyPropertyComputation( + propertyMetaInformation.backingPropertyMetaInformation.key, + analysis.performAnalysis + ) + analysis + } + + override def afterPhaseScheduling(propertyStore: PropertyStore, analysis: FPCFAnalysis): Unit = {} + + override def afterPhaseCompletion( + project: SomeProject, + propertyStore: PropertyStore, + analysis: FPCFAnalysis + ): Unit = {} +} diff --git a/OPAL/ide/src/main/scala/org/opalj/ide/integration/IDEProperty.scala b/OPAL/ide/src/main/scala/org/opalj/ide/integration/IDEProperty.scala new file mode 100644 index 0000000000..be4c173af1 --- /dev/null +++ b/OPAL/ide/src/main/scala/org/opalj/ide/integration/IDEProperty.scala @@ -0,0 +1,51 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package ide +package integration + +import scala.collection + +import org.opalj.fpcf.Property +import org.opalj.fpcf.PropertyKey +import org.opalj.ide.problem.IDEFact +import org.opalj.ide.problem.IDEValue + +/** + * Base interface of properties that are produced by an IDE analysis. + * + * @author Robin Körkemeier + */ +trait IDEProperty[Fact <: IDEFact, Value <: IDEValue] extends Property + +/** + * Basic implementation of [[IDEProperty]] that simply wraps the fact-value results of an IDE analysis. + * + * @param key the property key + * @param results the results produced by the analysis + * + * @author Robin Körkemeier + */ +class BasicIDEProperty[Fact <: IDEFact, Value <: IDEValue]( + val key: PropertyKey[BasicIDEProperty[Fact, Value]], + val results: collection.Set[(Fact, Value)] +) extends IDEProperty[Fact, Value] { + override type Self = BasicIDEProperty[Fact, Value] + + override def toString: String = { + s"BasicIDEProperty(${PropertyKey.name(key)}, {\n${ + results.map { case (fact, value) => s"\t($fact,$value)" }.toList.sorted.mkString("\n") + }\n})" + } + + override def equals(other: Any): Boolean = { + other match { + case basicIDEProperty: BasicIDEProperty[?, ?] => + key == basicIDEProperty.key && results == basicIDEProperty.results + case _ => false + } + } + + override def hashCode(): Int = { + key.hashCode() * 31 + results.hashCode() + } +} diff --git a/OPAL/ide/src/main/scala/org/opalj/ide/integration/IDEPropertyMetaInformation.scala b/OPAL/ide/src/main/scala/org/opalj/ide/integration/IDEPropertyMetaInformation.scala new file mode 100644 index 0000000000..ab0deba8c3 --- /dev/null +++ b/OPAL/ide/src/main/scala/org/opalj/ide/integration/IDEPropertyMetaInformation.scala @@ -0,0 +1,44 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package ide +package integration + +import scala.collection + +import org.opalj.fpcf.Entity +import org.opalj.fpcf.PropertyMetaInformation +import org.opalj.ide.problem.IDEFact +import org.opalj.ide.problem.IDEValue + +/** + * Base interface of property meta information of IDE analyses. Creates [[BasicIDEProperty]] by default. + * + * @author Robin Körkemeier + */ +trait IDEPropertyMetaInformation[Fact <: IDEFact, Value <: IDEValue, Statement, Callable <: Entity] + extends PropertyMetaInformation { + override type Self = BasicIDEProperty[Fact, Value] + + /** + * A property meta information corresponding to this one but used for the actual/underlying IDE analysis + */ + private[ide] val backingPropertyMetaInformation: IDERawPropertyMetaInformation[Fact, Value, Statement] = + new IDERawPropertyMetaInformation[Fact, Value, Statement](this) + + /** + * A property meta information corresponding to this one but used for target callables + */ + private[ide] val targetCallablesPropertyMetaInformation: IDETargetCallablesPropertyMetaInformation[Callable] = + new IDETargetCallablesPropertyMetaInformation[Callable](this) + + /** + * Create a property + * + * @param results the results the property should represent + */ + def createProperty( + results: collection.Set[(Fact, Value)] + ): IDEProperty[Fact, Value] = { + new BasicIDEProperty(key, results) + } +} diff --git a/OPAL/ide/src/main/scala/org/opalj/ide/integration/IDERawProperty.scala b/OPAL/ide/src/main/scala/org/opalj/ide/integration/IDERawProperty.scala new file mode 100644 index 0000000000..f5f4c37096 --- /dev/null +++ b/OPAL/ide/src/main/scala/org/opalj/ide/integration/IDERawProperty.scala @@ -0,0 +1,53 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package ide +package integration + +import scala.collection + +import org.opalj.fpcf.Property +import org.opalj.fpcf.PropertyKey +import org.opalj.ide.problem.IDEFact +import org.opalj.ide.problem.IDEValue + +/** + * Class representing a property that is directly created by an IDE analysis. + * + * @param key the property key (for example from an [[IDERawPropertyMetaInformation]] instance) + * @param stmtResults the raw statement results produced by the analysis + * @param callableResults the raw callable results produced by the analysis + * + * @author Robin Körkemeier + */ +class IDERawProperty[Fact <: IDEFact, Value <: IDEValue, Statement]( + val key: PropertyKey[IDERawProperty[Fact, Value, Statement]], + val stmtResults: collection.Map[Statement, collection.Set[(Fact, Value)]], + val callableResults: collection.Set[(Fact, Value)] +) extends Property { + override type Self = IDERawProperty[Fact, Value, Statement] + + override def toString: String = { + s"IDERawProperty(${PropertyKey.name(key)}, {\n${ + stmtResults.map { case (stmt, results) => + s"\t$stmt\n${ + results.map { case (fact, value) => s"\t\t($fact,$value)" }.toList.sorted.mkString("\n") + }" + }.mkString("\n") + }\n}, {\n${ + callableResults.map { case (fact, value) => s"\t($fact,$value)" }.toList.sorted.mkString("\n") + }\n})" + } + + override def equals(other: Any): Boolean = { + other match { + case ideRawProperty: IDERawProperty[?, ?, ?] => + key == ideRawProperty.key && stmtResults == ideRawProperty.stmtResults && + callableResults == ideRawProperty.callableResults + case _ => false + } + } + + override def hashCode(): Int = { + (key.hashCode() * 31 + stmtResults.hashCode()) * 31 + callableResults.hashCode() + } +} diff --git a/OPAL/ide/src/main/scala/org/opalj/ide/integration/IDERawPropertyMetaInformation.scala b/OPAL/ide/src/main/scala/org/opalj/ide/integration/IDERawPropertyMetaInformation.scala new file mode 100644 index 0000000000..565a1fe5ce --- /dev/null +++ b/OPAL/ide/src/main/scala/org/opalj/ide/integration/IDERawPropertyMetaInformation.scala @@ -0,0 +1,32 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package ide +package integration + +import org.opalj.fpcf.PropertyKey +import org.opalj.fpcf.PropertyMetaInformation +import org.opalj.ide.problem.IDEFact +import org.opalj.ide.problem.IDEValue + +/** + * Class for property meta information for properties that are created by IDE analyses directly (also called 'raw'). + * The property type is fixed to [[IDERawProperty]]. + * + * @param propertyMetaInformation the property meta information this object should be backing + * + * @author Robin Körkemeier + */ +final class IDERawPropertyMetaInformation[Fact <: IDEFact, Value <: IDEValue, Statement]( + propertyMetaInformation: IDEPropertyMetaInformation[Fact, Value, Statement, ?] +) extends PropertyMetaInformation { + override type Self = IDERawProperty[Fact, Value, Statement] + + /** + * The used property key, based on [[propertyMetaInformation]] + */ + private lazy val propertyKey: PropertyKey[IDERawProperty[Fact, Value, Statement]] = { + PropertyKey.create(s"${PropertyKey.name(propertyMetaInformation.key)}_Raw") + } + + override def key: PropertyKey[IDERawProperty[Fact, Value, Statement]] = propertyKey +} diff --git a/OPAL/ide/src/main/scala/org/opalj/ide/integration/IDETargetCallablesProperty.scala b/OPAL/ide/src/main/scala/org/opalj/ide/integration/IDETargetCallablesProperty.scala new file mode 100644 index 0000000000..82b0318b81 --- /dev/null +++ b/OPAL/ide/src/main/scala/org/opalj/ide/integration/IDETargetCallablesProperty.scala @@ -0,0 +1,37 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package ide +package integration + +import scala.collection + +import org.opalj.fpcf.Entity +import org.opalj.fpcf.Property +import org.opalj.fpcf.PropertyKey + +/** + * Property for the target callables that should be analysed by an IDE analysis. + * + * @author Robin Körkemeier + */ +class IDETargetCallablesProperty[Callable <: Entity]( + val key: PropertyKey[IDETargetCallablesProperty[Callable]], + val targetCallables: collection.Set[Callable] +) extends Property { + override type Self = IDETargetCallablesProperty[Callable] + + override def toString: String = + s"IDETargetCallablesProperty(\n${targetCallables.map { callable => s"\t$callable" }.mkString("\n")}\n)" + + override def equals(other: Any): Boolean = { + other match { + case ideTargetCallablesProperty: IDETargetCallablesProperty[?] => + key == ideTargetCallablesProperty.key && targetCallables == ideTargetCallablesProperty.targetCallables + case _ => false + } + } + + override def hashCode(): Int = { + key.hashCode() * 31 + targetCallables.hashCode() + } +} diff --git a/OPAL/ide/src/main/scala/org/opalj/ide/integration/IDETargetCallablesPropertyMetaInformation.scala b/OPAL/ide/src/main/scala/org/opalj/ide/integration/IDETargetCallablesPropertyMetaInformation.scala new file mode 100644 index 0000000000..e0613d4ec8 --- /dev/null +++ b/OPAL/ide/src/main/scala/org/opalj/ide/integration/IDETargetCallablesPropertyMetaInformation.scala @@ -0,0 +1,28 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package ide +package integration + +import org.opalj.fpcf.Entity +import org.opalj.fpcf.PropertyKey +import org.opalj.fpcf.PropertyMetaInformation + +/** + * Class for property meta information for properties carrying the target callables. + * + * @author Robin Körkemeier + */ +final class IDETargetCallablesPropertyMetaInformation[Callable <: Entity]( + propertyMetaInformation: IDEPropertyMetaInformation[?, ?, ?, Callable] +) extends PropertyMetaInformation { + override type Self = IDETargetCallablesProperty[Callable] + + /** + * The used property key, based on [[propertyMetaInformation]] + */ + private lazy val propertyKey: PropertyKey[IDETargetCallablesProperty[Callable]] = { + PropertyKey.create(s"${PropertyKey.name(propertyMetaInformation.key)}_TargetCallables") + } + + override def key: PropertyKey[IDETargetCallablesProperty[Callable]] = propertyKey +} diff --git a/OPAL/ide/src/main/scala/org/opalj/ide/integration/LazyIDEAnalysisProxyScheduler.scala b/OPAL/ide/src/main/scala/org/opalj/ide/integration/LazyIDEAnalysisProxyScheduler.scala new file mode 100644 index 0000000000..f16950546c --- /dev/null +++ b/OPAL/ide/src/main/scala/org/opalj/ide/integration/LazyIDEAnalysisProxyScheduler.scala @@ -0,0 +1,38 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package ide +package integration + +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.FPCFAnalysis +import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler +import org.opalj.fpcf.Entity +import org.opalj.fpcf.PropertyBounds +import org.opalj.fpcf.PropertyStore +import org.opalj.ide.problem.IDEFact +import org.opalj.ide.problem.IDEValue +import org.opalj.ide.solver.IDEAnalysisProxy + +/** + * A scheduler to (lazily) schedule the proxy analysis that is used to access the IDE analysis results. + * + * @author Robin Körkemeier + */ +class LazyIDEAnalysisProxyScheduler[Fact <: IDEFact, Value <: IDEValue, Statement, Callable <: Entity]( + val propertyMetaInformation: IDEPropertyMetaInformation[Fact, Value, Statement, Callable] +) extends BaseIDEAnalysisProxyScheduler[Fact, Value, Statement, Callable] with FPCFLazyAnalysisScheduler { + def this(ideAnalysisScheduler: IDEAnalysisScheduler[Fact, Value, Statement, Callable, ?]) = { + this(ideAnalysisScheduler.propertyMetaInformation) + } + + override def derivesLazily: Some[PropertyBounds] = Some(PropertyBounds.ub(propertyMetaInformation)) + + override def register( + project: SomeProject, + propertyStore: PropertyStore, + analysis: IDEAnalysisProxy[Fact, Value, Statement, Callable] + ): FPCFAnalysis = { + propertyStore.registerLazyPropertyComputation(propertyMetaInformation.key, analysis.proxyAnalysis) + analysis + } +} diff --git a/OPAL/ide/src/main/scala/org/opalj/ide/package.scala b/OPAL/ide/src/main/scala/org/opalj/ide/package.scala new file mode 100644 index 0000000000..9e72f9a436 --- /dev/null +++ b/OPAL/ide/src/main/scala/org/opalj/ide/package.scala @@ -0,0 +1,35 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj + +import com.typesafe.config.Config +import com.typesafe.config.ConfigFactory + +import org.opalj.log.GlobalLogContext +import org.opalj.log.LogContext +import org.opalj.log.OPALLogger.info + +/** + * @author Robin Körkemeier + */ +package object ide { + + final val FrameworkName = "OPAL IDE" + + { + implicit val logContext: LogContext = GlobalLogContext + try { + assert(false) // <= test whether assertions are turned on or off... + info(FrameworkName, "Production Build") + } catch { + case _: AssertionError => info(FrameworkName, "Development Build with Assertions") + } + } + + // We want to make sure that the class loader is used which potentially can + // find the config files; the libraries (e.g., Typesafe Config) may have + // been loaded using the parent class loader and, hence, may not be able to + // find the config files at all. + val BaseConfig: Config = ConfigFactory.load(this.getClass.getClassLoader) + + final val ConfigKeyPrefix = "org.opalj.ide." +} diff --git a/OPAL/ide/src/main/scala/org/opalj/ide/problem/EdgeFunction.scala b/OPAL/ide/src/main/scala/org/opalj/ide/problem/EdgeFunction.scala new file mode 100644 index 0000000000..377ec76fb3 --- /dev/null +++ b/OPAL/ide/src/main/scala/org/opalj/ide/problem/EdgeFunction.scala @@ -0,0 +1,107 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package ide +package problem + +/** + * Interface representing IDE edge functions. + * + * @author Robin Körkemeier + */ +trait EdgeFunction[+Value <: IDEValue] { + /** + * Compute the value of the edge function + * + * @param sourceValue the incoming parameter value + */ + def compute[V >: Value](sourceValue: V): V + + /** + * Compose two edge functions + * + * @param secondEdgeFunction the edge function that is applied after this one + * @return an edge function computing the same values as first applying this edge function and then applying the + * result to the second edge function + */ + def composeWith[V >: Value <: IDEValue](secondEdgeFunction: EdgeFunction[V]): EdgeFunction[V] + + /** + * Combine two edge functions via meet semantics + */ + def meet[V >: Value <: IDEValue](otherEdgeFunction: EdgeFunction[V]): EdgeFunction[V] + + /** + * Check whether two edge functions are equal (s.t. they produce the same result for same source values) + */ + def equals[V >: Value <: IDEValue](otherEdgeFunction: EdgeFunction[V]): Boolean +} + +/** + * Special edge function representing an identity edge function. + * + * @author Robin Körkemeier + */ +case object IdentityEdgeFunction extends EdgeFunction[Nothing] { + override def compute[V >: Nothing](sourceValue: V): V = + sourceValue + + override def composeWith[V >: Nothing <: IDEValue](secondEdgeFunction: EdgeFunction[V]): EdgeFunction[V] = + secondEdgeFunction + + override def meet[V >: Nothing <: IDEValue](otherEdgeFunction: EdgeFunction[V]): EdgeFunction[V] = { + if (otherEdgeFunction.equals(this)) { + this + } else { + otherEdgeFunction.meet(this) + } + } + + override def equals[V >: Nothing <: IDEValue](otherEdgeFunction: EdgeFunction[V]): Boolean = + otherEdgeFunction eq this +} + +/** + * Special edge function representing an edge function where all source values evaluate to the top element. Implementing + * [[composeWith]] is left to the user, as it requires knowledge of the other possible edge functions. + * + * @author Robin Körkemeier + */ +abstract case class AllTopEdgeFunction[Value <: IDEValue](private val top: Value) extends EdgeFunction[Value] { + override def compute[V >: Value](sourceValue: V): V = + top + + override def meet[V >: Value <: IDEValue](otherEdgeFunction: EdgeFunction[V]): EdgeFunction[V] = { + otherEdgeFunction match { + case _: AllTopEdgeFunction[V] => this + case _ => otherEdgeFunction + } + } + + override def equals[V >: Value <: IDEValue](otherEdgeFunction: EdgeFunction[V]): Boolean = + (otherEdgeFunction eq this) || + (otherEdgeFunction match { + case AllTopEdgeFunction(top2) => top == top2 + case _ => false + }) +} + +/** + * Special edge function representing an edge function where all source values evaluate to the bottom element. + * Implementing [[composeWith]] is left to the user, as it requires knowledge of the other possible edge functions. + * + * @author Robin Körkemeier + */ +abstract case class AllBottomEdgeFunction[Value <: IDEValue](private val bottom: Value) extends EdgeFunction[Value] { + override def compute[V >: Value](sourceValue: V): V = + bottom + + override def meet[V >: Value <: IDEValue](otherEdgeFunction: EdgeFunction[V]): EdgeFunction[V] = + this + + override def equals[V >: Value <: IDEValue](otherEdgeFunction: EdgeFunction[V]): Boolean = + (otherEdgeFunction eq this) || + (otherEdgeFunction match { + case AllBottomEdgeFunction(bottom2) => bottom == bottom2 + case _ => false + }) +} diff --git a/OPAL/ide/src/main/scala/org/opalj/ide/problem/EdgeFunctionResult.scala b/OPAL/ide/src/main/scala/org/opalj/ide/problem/EdgeFunctionResult.scala new file mode 100644 index 0000000000..5587f87768 --- /dev/null +++ b/OPAL/ide/src/main/scala/org/opalj/ide/problem/EdgeFunctionResult.scala @@ -0,0 +1,35 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package ide +package problem + +import scala.collection + +import org.opalj.fpcf.SomeEOptionP + +/** + * Interface for encapsulating different states of edge functions. + * + * @author Robin Körkemeier + */ +trait EdgeFunctionResult[Value <: IDEValue] + +/** + * Represent an edge function that is final. + * + * @author Robin Körkemeier + */ +case class FinalEdgeFunction[Value <: IDEValue](edgeFunction: EdgeFunction[Value]) extends EdgeFunctionResult[Value] + +/** + * Represent an interim edge function that may change when the result of one of the dependees changes. + * + * @param interimEdgeFunction an interim edge function to use until new results are present (has to be an upper bound of + * the final edge function) + * + * @author Robin Körkemeier + */ +case class InterimEdgeFunction[Value <: IDEValue]( + interimEdgeFunction: EdgeFunction[Value], + dependees: collection.Set[SomeEOptionP] +) extends EdgeFunctionResult[Value] diff --git a/OPAL/ide/src/main/scala/org/opalj/ide/problem/FlowFunction.scala b/OPAL/ide/src/main/scala/org/opalj/ide/problem/FlowFunction.scala new file mode 100644 index 0000000000..cd27a5486b --- /dev/null +++ b/OPAL/ide/src/main/scala/org/opalj/ide/problem/FlowFunction.scala @@ -0,0 +1,56 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package ide +package problem + +import scala.language.implicitConversions + +import scala.collection.immutable.Set + +import org.opalj.fpcf.SomeEOptionP + +/** + * Interface representing IDE flow functions. + * + * @author Robin Körkemeier + */ +trait FlowFunction[Fact <: IDEFact] { + type FactsAndDependees = FlowFunction.FactsAndDependees[Fact] + + implicit def setOfFactsToFactsAndDependees(facts: scala.collection.Set[? <: Fact]): FactsAndDependees = { + (facts.toSet, Set.empty) + } + + /** + * Compute the facts that are generated by this flow function and the dependees that can cause new facts to be + * generated + * + * @return a set of facts and a set of dependees (a fact that is returned once must also be returned with every + * subsequent call) + */ + def compute(): FactsAndDependees +} + +object FlowFunction { + type FactsAndDependees[Fact] = (scala.collection.Set[Fact], scala.collection.Set[SomeEOptionP]) +} + +/** + * Special flow function that always returns the input fact. + * + * @author Robin Körkemeier + */ +case class IdentityFlowFunction[Fact <: IDEFact](sourceFact: Fact) extends FlowFunction[Fact] { + override def compute(): FactsAndDependees = + Set(sourceFact) +} + +/** + * Special flow function that always returns an empty set. + * + * @author Robin Körkemeier + */ +case class EmptyFlowFunction[Fact <: IDEFact]() extends FlowFunction[Fact] { + override def compute(): FactsAndDependees = + Set.empty[Fact] +} diff --git a/OPAL/ide/src/main/scala/org/opalj/ide/problem/FlowRecordingIDEProblem.scala b/OPAL/ide/src/main/scala/org/opalj/ide/problem/FlowRecordingIDEProblem.scala new file mode 100644 index 0000000000..682d5fe703 --- /dev/null +++ b/OPAL/ide/src/main/scala/org/opalj/ide/problem/FlowRecordingIDEProblem.scala @@ -0,0 +1,385 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package ide +package problem + +import java.io.Writer +import scala.collection.mutable.{ListBuffer => MutableListBuffer} +import scala.collection.mutable.{Map => MutableMap} +import scala.collection.mutable.{Set => MutableSet} + +import org.opalj.fpcf.Entity +import org.opalj.fpcf.PropertyStore +import org.opalj.ide.solver.ICFG + +/** + * Different modes to record flow. + * + * @author Robin Körkemeier + */ +object FlowRecorderModes extends Enumeration { + type FlowRecorderMode = Value + + /** + * A node in the graph is only made up of a statement. The edges are annotated with the propagated facts. + */ + val NODE_AS_STMT: FlowRecorderModes.Value = Value + /** + * A node in the graph is the combination of a statement and a fact. + */ + val NODE_AS_STMT_AND_FACT: FlowRecorderModes.Value = Value +} + +/** + * Wrapper class for a normal IDE problem for debugging purposes. Records the flow paths the IDE solver takes for a + * given base problem as graph and writes it to a file in DOT format. + * + * @param baseProblem the base problem that defines the flows and edge functions that should be analyzed + * @param uniqueFlowsOnly whether to drop or to keep duplicated flows + * @param recordEdgeFunctions whether to record edge functions too or just stick with the flow + * + * @author Robin Körkemeier + */ +class FlowRecordingIDEProblem[Fact <: IDEFact, Value <: IDEValue, Statement, Callable <: Entity]( + val baseProblem: IDEProblem[Fact, Value, Statement, Callable], + val icfg: ICFG[Statement, Callable], + val recorderMode: FlowRecorderModes.FlowRecorderMode = FlowRecorderModes.NODE_AS_STMT, + val uniqueFlowsOnly: Boolean = true, + val recordEdgeFunctions: Boolean = false +) extends IDEProblem[Fact, Value, Statement, Callable] { + /** + * Wrapper class for flow functions doing the actual recording + */ + private class RecordingFlowFunction( + baseFlowFunction: FlowFunction[Fact], + val source: Statement, + val sourceFact: Fact, + val target: Statement, + val flowType: String + ) extends FlowFunction[Fact] { + override def compute(): FactsAndDependees = { + val (facts, dependees) = baseFlowFunction.compute() + facts.foreach { fact => collectedFlows.addOne(createDotEdge(source, sourceFact, target, fact, flowType)) } + (facts, dependees) + } + } + + private type DotEdge = (Statement, Fact, Statement, Fact, String) + + private val collectedFlows = MutableListBuffer.empty[DotEdge] + + private val collectedEdgeFunctions = MutableMap.empty[DotEdge, EdgeFunction[Value]] + + private var writer: Writer = _ + + override val nullFact: Fact = baseProblem.nullFact + + override val lattice: MeetLattice[Value] = baseProblem.lattice + + override def getAdditionalSeeds(stmt: Statement, callee: Callable)( + implicit propertyStore: PropertyStore + ): scala.collection.Set[Fact] = { + baseProblem.getAdditionalSeeds(stmt, callee) + } + + override def getAdditionalSeedsEdgeFunction(stmt: Statement, fact: Fact, callee: Callable)( + implicit propertyStore: PropertyStore + ): EdgeFunctionResult[Value] = { + baseProblem.getAdditionalSeedsEdgeFunction(stmt, fact, callee) + } + + override def getNormalFlowFunction( + source: Statement, + sourceFact: Fact, + target: Statement + )(implicit propertyStore: PropertyStore): FlowFunction[Fact] = { + new RecordingFlowFunction( + baseProblem.getNormalFlowFunction(source, sourceFact, target), + source, + sourceFact, + target, + "normal flow" + ) + } + + override def getCallFlowFunction( + callSite: Statement, + callSiteFact: Fact, + calleeEntry: Statement, + callee: Callable + )(implicit propertyStore: PropertyStore): FlowFunction[Fact] = { + new RecordingFlowFunction( + baseProblem.getCallFlowFunction(callSite, callSiteFact, calleeEntry, callee), + callSite, + callSiteFact, + calleeEntry, + "call flow" + ) + } + + override def getReturnFlowFunction( + calleeExit: Statement, + calleeExitFact: Fact, + callee: Callable, + returnSite: Statement, + callSite: Statement, + callSiteFact: Fact + )(implicit propertyStore: PropertyStore): FlowFunction[Fact] = { + new RecordingFlowFunction( + baseProblem.getReturnFlowFunction(calleeExit, calleeExitFact, callee, returnSite, callSite, callSiteFact), + calleeExit, + calleeExitFact, + returnSite, + "return flow" + ) + } + + override def getCallToReturnFlowFunction( + callSite: Statement, + callSiteFact: Fact, + callee: Callable, + returnSite: Statement + )(implicit propertyStore: PropertyStore): FlowFunction[Fact] = { + new RecordingFlowFunction( + baseProblem.getCallToReturnFlowFunction(callSite, callSiteFact, callee, returnSite), + callSite, + callSiteFact, + returnSite, + "call-to-return flow" + ) + } + + override def getNormalEdgeFunction( + source: Statement, + sourceFact: Fact, + target: Statement, + targetFact: Fact + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[Value] = { + val edgeFunctionResult = baseProblem.getNormalEdgeFunction(source, sourceFact, target, targetFact) + collectedEdgeFunctions.put( + createDotEdge(source, sourceFact, target, targetFact, "normal flow"), + getEdgeFunctionFromEdgeFunctionResult(edgeFunctionResult) + ) + edgeFunctionResult + } + + override def getCallEdgeFunction( + callSite: Statement, + callSiteFact: Fact, + calleeEntry: Statement, + calleeEntryFact: Fact, + callee: Callable + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[Value] = { + val edgeFunctionResult = + baseProblem.getCallEdgeFunction(callSite, callSiteFact, calleeEntry, calleeEntryFact, callee) + collectedEdgeFunctions.put( + createDotEdge(callSite, callSiteFact, calleeEntry, calleeEntryFact, "call flow"), + getEdgeFunctionFromEdgeFunctionResult(edgeFunctionResult) + ) + edgeFunctionResult + } + + override def getReturnEdgeFunction( + calleeExit: Statement, + calleeExitFact: Fact, + callee: Callable, + returnSite: Statement, + returnSiteFact: Fact, + callSite: Statement, + callSiteFact: Fact + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[Value] = { + val edgeFunctionResult = + baseProblem.getReturnEdgeFunction( + calleeExit, + calleeExitFact, + callee, + returnSite, + returnSiteFact, + callSite, + callSiteFact + ) + collectedEdgeFunctions.put( + createDotEdge(calleeExit, calleeExitFact, returnSite, returnSiteFact, "return flow"), + getEdgeFunctionFromEdgeFunctionResult(edgeFunctionResult) + ) + edgeFunctionResult + } + + override def getCallToReturnEdgeFunction( + callSite: Statement, + callSiteFact: Fact, + callee: Callable, + returnSite: Statement, + returnSiteFact: Fact + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[Value] = { + val edgeFunctionResult = + baseProblem.getCallToReturnEdgeFunction(callSite, callSiteFact, callee, returnSite, returnSiteFact) + collectedEdgeFunctions.put( + createDotEdge(callSite, callSiteFact, returnSite, returnSiteFact, "call-to-return flow"), + getEdgeFunctionFromEdgeFunctionResult(edgeFunctionResult) + ) + edgeFunctionResult + } + + override def hasPrecomputedFlowAndSummaryFunction( + callSite: Statement, + callSiteFact: Fact, + callee: Callable + )(implicit propertyStore: PropertyStore): Boolean = { + baseProblem.hasPrecomputedFlowAndSummaryFunction(callSite, callSiteFact, callee) + } + + override def getPrecomputedFlowFunction( + callSite: Statement, + callSiteFact: Fact, + callee: Callable, + returnSite: Statement + )(implicit propertyStore: PropertyStore): FlowFunction[Fact] = { + new RecordingFlowFunction( + baseProblem.getPrecomputedFlowFunction(callSite, callSiteFact, callee, returnSite), + callSite, + callSiteFact, + returnSite, + "precomputed flow" + ) + } + + override def getPrecomputedSummaryFunction( + callSite: Statement, + callSiteFact: Fact, + callee: Callable, + returnSite: Statement, + returnSiteFact: Fact + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[Value] = { + val edgeFunctionResult = + baseProblem.getPrecomputedSummaryFunction(callSite, callSiteFact, callee, returnSite, returnSiteFact) + collectedEdgeFunctions.put( + createDotEdge(callSite, callSiteFact, returnSite, returnSiteFact, "precomputed flow"), + getEdgeFunctionFromEdgeFunctionResult(edgeFunctionResult) + ) + edgeFunctionResult + } + + override def getPrecomputedFlowFunction(callSite: Statement, callSiteFact: Fact, returnSite: Statement)( + implicit propertyStore: PropertyStore + ): FlowFunction[Fact] = { + new RecordingFlowFunction( + baseProblem.getPrecomputedFlowFunction(callSite, callSiteFact, returnSite), + callSite, + callSiteFact, + returnSite, + "precomputed flow" + ) + } + + override def getPrecomputedSummaryFunction( + callSite: Statement, + callSiteFact: Fact, + returnSite: Statement, + returnSiteFact: Fact + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[Value] = { + val edgeFunctionResult = + baseProblem.getPrecomputedSummaryFunction(callSite, callSiteFact, returnSite, returnSiteFact) + collectedEdgeFunctions.put( + createDotEdge(callSite, callSiteFact, returnSite, returnSiteFact, "precomputed flow"), + getEdgeFunctionFromEdgeFunctionResult(edgeFunctionResult) + ) + edgeFunctionResult + } + + private def createDotEdge( + source: Statement, + sourceFact: Fact, + target: Statement, + targetFact: Fact, + flowType: String + ): DotEdge = { + (source, sourceFact, target, targetFact, flowType) + } + + private def getEdgeFunctionFromEdgeFunctionResult( + edgeFunctionResult: EdgeFunctionResult[Value] + ): EdgeFunction[Value] = { + edgeFunctionResult match { + case FinalEdgeFunction(edgeFunction) => edgeFunction + case InterimEdgeFunction(interimEdgeFunction, _) => interimEdgeFunction + } + } + + /** + * Start recording + * + * @param writer to write the graph to + */ + def startRecording(writer: Writer): Unit = { + this.writer = writer + collectedFlows.clear() + collectedEdgeFunctions.clear() + + writer.write("digraph G {\n\tnodesep=\"2.0\";\n\tranksep=\"1.5\";\n") + } + + private def stringifyDotEdge(dotEdge: DotEdge): String = { + val (source, sourceFact, target, targetFact, flowType) = dotEdge + + var fromNode: String = null + var toNode: String = null + var label: String = null + recorderMode match { + case FlowRecorderModes.NODE_AS_STMT => + fromNode = s"${stringifyStatement(source)}" + toNode = s"${stringifyStatement(target)}" + if (recordEdgeFunctions) { + label = + s"$targetFact\\n($flowType),\\n${collectedEdgeFunctions.get(dotEdge).map(_.toString).getOrElse("edge function missing")}" + } else { + label = s"$targetFact\\n($flowType)" + } + case FlowRecorderModes.NODE_AS_STMT_AND_FACT => + fromNode = s"(${stringifyStatement(source)}, $sourceFact)" + toNode = s"(${stringifyStatement(target)}, $targetFact)" + if (recordEdgeFunctions) { + label = + s"$flowType,\\n${collectedEdgeFunctions.get(dotEdge).map(_.toString).getOrElse("edge function missing")}" + } else { + label = flowType + } + } + + s"\t\"$fromNode\" -> \"$toNode\" [label=\"$label\"]\n" + } + + private def stringifyStatement(stmt: Statement): String = { + val stringifiedStatement = stmt.toString + stringifiedStatement.substring(0, stringifiedStatement.indexOf("{")) + } + + /** + * Stop recording and finish writing + */ + def stopRecording(): Writer = { + if (uniqueFlowsOnly) { + val seenFlows = MutableSet.empty[String] + collectedFlows.foreach { dotEdge => + val stringDotEdge = stringifyDotEdge(dotEdge) + if (!seenFlows.contains(stringDotEdge)) { + seenFlows.add(stringDotEdge) + writer.write(stringDotEdge) + } + } + } else { + collectedFlows.foreach { dotEdge => writer.write(stringifyDotEdge(dotEdge)) } + } + + writer.write("}\n") + writer.flush() + + val writerTmp = writer + this.writer = null + + collectedFlows.clear() + collectedEdgeFunctions.clear() + + writerTmp + } +} diff --git a/OPAL/ide/src/main/scala/org/opalj/ide/problem/IDEFact.scala b/OPAL/ide/src/main/scala/org/opalj/ide/problem/IDEFact.scala new file mode 100644 index 0000000000..c7dae838df --- /dev/null +++ b/OPAL/ide/src/main/scala/org/opalj/ide/problem/IDEFact.scala @@ -0,0 +1,11 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package ide +package problem + +/** + * Interface representing IDE facts. + * + * @author Robin Körkemeier + */ +trait IDEFact diff --git a/OPAL/ide/src/main/scala/org/opalj/ide/problem/IDEProblem.scala b/OPAL/ide/src/main/scala/org/opalj/ide/problem/IDEProblem.scala new file mode 100644 index 0000000000..6dc9720853 --- /dev/null +++ b/OPAL/ide/src/main/scala/org/opalj/ide/problem/IDEProblem.scala @@ -0,0 +1,290 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package ide +package problem + +import scala.annotation.unused +import scala.language.implicitConversions + +import scala.collection.immutable.Set + +import org.opalj.fpcf.Entity +import org.opalj.fpcf.PropertyStore + +/** + * Interface for modeling IDE problems. + * + * @author Robin Körkemeier + */ +abstract class IDEProblem[Fact <: IDEFact, Value <: IDEValue, Statement, Callable <: Entity] { + implicit def edgeFunctionToFinalEdgeFunction(edgeFunction: EdgeFunction[Value]): EdgeFunctionResult[Value] = { + FinalEdgeFunction(edgeFunction) + } + + /** + * Empty flow function that can be used when implementing problems + */ + protected val emptyFlowFunction = new EmptyFlowFunction[Fact] + + /** + * The null fact to use. Also used to bootstrap the analysis at the entry points. + */ + val nullFact: Fact + + /** + * The lattice that orders the used values + */ + val lattice: MeetLattice[Value] + + /** + * Add additional facts that the analysis should be seeded with. Traditionally, IDE starts with the null fact at the + * start statements of the callable. E.g. additional seeds can be used for adding facts about the parameters of the + * analyzed callable. + * + * @param stmt the start statement + * @param callee the analyzed callable + */ + def getAdditionalSeeds(stmt: Statement, callee: Callable)( + implicit @unused propertyStore: PropertyStore + ): scala.collection.Set[Fact] = Set.empty + + /** + * Generate an edge function for a flow starting with an additional seeds. + * + * @param stmt the start statement + * @param fact the start fact + * @param callee the analyzed callable + */ + def getAdditionalSeedsEdgeFunction(stmt: Statement, fact: Fact, callee: Callable)( + implicit @unused propertyStore: PropertyStore + ): EdgeFunctionResult[Value] = IdentityEdgeFunction + + /** + * Generate a flow function for a normal flow. + * + * @param source where the normal flow starts + * @param sourceFact the fact the flow starts with + * @param target where the normal flow ends + */ + def getNormalFlowFunction(source: Statement, sourceFact: Fact, target: Statement)( + implicit propertyStore: PropertyStore + ): FlowFunction[Fact] + + /** + * Generate a flow function for a call flow. + * + * @param callSite where the call flow starts (always a call statement) + * @param callSiteFact the fact the flow starts with + * @param calleeEntry where the callable starts (the statement which the callable is started with) + * @param callee the callable that is called + */ + def getCallFlowFunction(callSite: Statement, callSiteFact: Fact, calleeEntry: Statement, callee: Callable)( + implicit propertyStore: PropertyStore + ): FlowFunction[Fact] + + /** + * Generate a flow function for a return flow. + * + * @param calleeExit where the return flow starts (the statement the callable is exited with) + * @param calleeExitFact the fact the flow starts with + * @param callee the callable that is returned from + * @param returnSite where the return flow ends (e.g. the next statement after the call in the callers code) + * @param callSite corresponding to the return flow + * @param callSiteFact corresponding to the return flow + */ + def getReturnFlowFunction( + calleeExit: Statement, + calleeExitFact: Fact, + callee: Callable, + returnSite: Statement, + callSite: Statement, + callSiteFact: Fact + )( + implicit propertyStore: PropertyStore + ): FlowFunction[Fact] + + /** + * Generate a flow function for a call-to-return flow. + * + * @param callSite where the call-to-return flow starts (always a call statement) + * @param callSiteFact the fact the flow starts with + * @param callee the callable this flow is about + * @param returnSite where the call-to-return flow ends (e.g. the next statement after the call) + */ + def getCallToReturnFlowFunction(callSite: Statement, callSiteFact: Fact, callee: Callable, returnSite: Statement)( + implicit propertyStore: PropertyStore + ): FlowFunction[Fact] + + /** + * Generate an edge function for a normal flow. + * + * @param source where the normal flow starts + * @param sourceFact the fact the flow starts with + * @param target where the normal flow ends + * @param targetFact the fact the flow ends with + */ + def getNormalEdgeFunction( + source: Statement, + sourceFact: Fact, + target: Statement, + targetFact: Fact + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[Value] + + /** + * Generate an edge function for a call flow. + * + * @param callSite where the call flow starts (always a call statement) + * @param callSiteFact the fact the flow starts with + * @param calleeEntry where the callable starts (the statement which the callable is started with) + * @param calleeEntryFact the fact the flow ends with + * @param callee the callable that is called + */ + def getCallEdgeFunction( + callSite: Statement, + callSiteFact: Fact, + calleeEntry: Statement, + calleeEntryFact: Fact, + callee: Callable + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[Value] + + /** + * Generate an edge function for a return flow. + * + * @param calleeExit where the return flow starts (the statement the callable is exited with) + * @param calleeExitFact the fact the flow starts with + * @param callee the callable that is returned from + * @param returnSite where the return flow ends (e.g. the next statement after the call in the callers code) + * @param returnSiteFact the fact the flow ends with + * @param callSite corresponding to the return flow + * @param callSiteFact corresponding to the return flow + */ + def getReturnEdgeFunction( + calleeExit: Statement, + calleeExitFact: Fact, + callee: Callable, + returnSite: Statement, + returnSiteFact: Fact, + callSite: Statement, + callSiteFact: Fact + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[Value] + + /** + * Generate an edge function for a call-to-return flow. + * + * @param callSite where the call-to-return flow starts (always a call statement) + * @param callSiteFact the fact the flow starts with + * @param callee the callable this flow is about + * @param returnSite where the call-to-return flow ends (e.g. the next statement after the call) + * @param returnSiteFact the fact the flow ends with + */ + def getCallToReturnEdgeFunction( + callSite: Statement, + callSiteFact: Fact, + callee: Callable, + returnSite: Statement, + returnSiteFact: Fact + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[Value] + + /** + * Whether precomputed flow and summary functions for a `(callSite, callSiteFact, callee)` combination exist + * (resp. can be generated). + * + * @param callSite where the flow starts + * @param callSiteFact the fact the flow starts with + * @param callee the callable this flow is about + */ + def hasPrecomputedFlowAndSummaryFunction(callSite: Statement, callSiteFact: Fact, callee: Callable)( + implicit propertyStore: PropertyStore + ): Boolean = { + false + } + + /** + * Generate a flow function that yields the facts that are valid when going through the callable and reaching the + * return site. Similar to a call-to-return flow (cfg. [[getCallToReturnFlowFunction]]) but capturing the effects + * that flow through the callable. + * + * @param callSite where the flow starts (always a call statement) + * @param callSiteFact the fact the flow starts with + * @param callee the callable this flow is about + * @param returnSite where the flow ends (e.g. the next statement after the call) + * @note In this type of precomputed flow the callable is known. Thus, the call-to-return flow can be applied + * normally and does not need to be integrated in this flow. + */ + def getPrecomputedFlowFunction(callSite: Statement, callSiteFact: Fact, callee: Callable, returnSite: Statement)( + implicit propertyStore: PropertyStore + ): FlowFunction[Fact] = { + throw new IllegalArgumentException( + s"No precomputed flow function for callSite=$callSite, callSiteFact=$callSiteFact, callee=$callee and " + + s"returnSite=$returnSite exists!" + ) + } + + /** + * Generate a summary function from a call-site node up to a return-site node (just what summary functions are in + * the foundation paper, but in one step). + * + * @param callSite where the flow starts (always a call statement) + * @param callSiteFact the fact the flow starts with + * @param callee the callable the flow is about + * @param returnSite where the flow ends (e.g. the next statement after the call) + * @param returnSiteFact the fact the flow ends with + * @note In this type of precomputed flow the callable is known. Thus, the call-to-return flow can be applied + * normally and does not need to be integrated in this flow. + */ + def getPrecomputedSummaryFunction( + callSite: Statement, + callSiteFact: Fact, + callee: Callable, + returnSite: Statement, + returnSiteFact: Fact + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[Value] = { + throw new IllegalArgumentException( + s"No precomputed summary function for callSite=$callSite, callSiteFact=$callSiteFact, " + + s"callee=$callee, returnSite=$returnSite and returnSiteFact=$returnSiteFact exists!" + ) + } + + /** + * Generate a flow function that yields the facts that are valid when going through the unknown callable and + * reaching the return site. Similar to a call-to-return flow (cfg. [[getCallToReturnFlowFunction]]) but capturing + * the effects that flow through the possible callables. + * + * @param callSite where the flow starts (always a call statement) + * @param callSiteFact the fact the flow starts with + * @param returnSite where the flow ends (e.g. the next statement after the call) + * @note In this type of precomputed flow the callable is unknown. Thus, the call-to-return flow is not applied and + * needs to be integrated into this flow. + */ + def getPrecomputedFlowFunction(callSite: Statement, callSiteFact: Fact, returnSite: Statement)( + implicit propertyStore: PropertyStore + ): FlowFunction[Fact] = { + throw new IllegalArgumentException( + s"No precomputed flow function for callSite=$callSite, callSiteFact=$callSiteFact and " + + s"returnSite=$returnSite exists!" + ) + } + + /** + * Generate a summary function from a call-site node up to a return-site node (just what summary functions are in + * the foundation paper, but in one step and for all callables that are possible call targets). + * + * @param callSite where the flow starts (always a call statement) + * @param callSiteFact the fact the flow starts with + * @param returnSite where the flow ends (e.g. the next statement after the call) + * @param returnSiteFact the fact the flow ends with + * @note In this type of precomputed flow the callable is unknown. Thus, the call-to-return flow is not applied and + * needs to be integrated into this flow. + */ + def getPrecomputedSummaryFunction( + callSite: Statement, + callSiteFact: Fact, + returnSite: Statement, + returnSiteFact: Fact + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[Value] = { + throw new IllegalArgumentException( + s"No precomputed summary function for callSite=$callSite, callSiteFact=$callSiteFact, " + + s"returnSite=$returnSite and returnSiteFact=$returnSiteFact exists!" + ) + } +} diff --git a/OPAL/ide/src/main/scala/org/opalj/ide/problem/IDEValue.scala b/OPAL/ide/src/main/scala/org/opalj/ide/problem/IDEValue.scala new file mode 100644 index 0000000000..c5122906cd --- /dev/null +++ b/OPAL/ide/src/main/scala/org/opalj/ide/problem/IDEValue.scala @@ -0,0 +1,11 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package ide +package problem + +/** + * Interface representing IDE values. + * + * @author Robin Körkemeier + */ +trait IDEValue diff --git a/OPAL/ide/src/main/scala/org/opalj/ide/problem/MeetLattice.scala b/OPAL/ide/src/main/scala/org/opalj/ide/problem/MeetLattice.scala new file mode 100644 index 0000000000..631f6df394 --- /dev/null +++ b/OPAL/ide/src/main/scala/org/opalj/ide/problem/MeetLattice.scala @@ -0,0 +1,26 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package ide +package problem + +/** + * Interface representing the lattice that orders the IDE values. + * + * @author Robin Körkemeier + */ +trait MeetLattice[Value <: IDEValue] { + /** + * The top value of the lattice + */ + def top: Value + + /** + * The bottom value of the lattice + */ + def bottom: Value + + /** + * Compute the result of meeting two values + */ + def meet(x: Value, y: Value): Value +} diff --git a/OPAL/ide/src/main/scala/org/opalj/ide/solver/ICFG.scala b/OPAL/ide/src/main/scala/org/opalj/ide/solver/ICFG.scala new file mode 100644 index 0000000000..96a961c9e4 --- /dev/null +++ b/OPAL/ide/src/main/scala/org/opalj/ide/solver/ICFG.scala @@ -0,0 +1,50 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package ide +package solver + +import scala.collection + +import org.opalj.fpcf.Entity + +/** + * Interface representing the interprocedural control flow graph. + * + * @author Robin Körkemeier + */ +trait ICFG[Statement, Callable <: Entity] { + /** + * Get all statements a callable can be entered at + */ + def getStartStatements(callable: Callable): collection.Set[Statement] + + /** + * Get all statements that can directly follow the given one + */ + def getNextStatements(stmt: Statement): collection.Set[Statement] + + /** + * Check whether a statement exits a callable in a normal way (e.g. with a return) + */ + def isNormalExitStatement(stmt: Statement): Boolean + + /** + * Check whether a statement exits a callable in an abnormal way (e.g. by throwing an exception) + */ + def isAbnormalExitStatement(stmt: Statement): Boolean + + /** + * Check whether a statement is a call statement + */ + def isCallStatement(stmt: Statement): Boolean + + /** + * Get all possible callees a call statement could call + */ + def getCallees(stmt: Statement): collection.Set[Callable] + + /** + * Get the callable a statement belongs to + */ + def getCallable(stmt: Statement): Callable +} diff --git a/OPAL/ide/src/main/scala/org/opalj/ide/solver/IDEAnalysis.scala b/OPAL/ide/src/main/scala/org/opalj/ide/solver/IDEAnalysis.scala new file mode 100644 index 0000000000..71bb62aabe --- /dev/null +++ b/OPAL/ide/src/main/scala/org/opalj/ide/solver/IDEAnalysis.scala @@ -0,0 +1,1224 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package ide +package solver + +import scala.collection.mutable.{Map => MutableMap} +import scala.collection.mutable.{Queue => MutableQueue} +import scala.collection.mutable.{Set => MutableSet} + +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.FPCFAnalysis +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.EPS +import org.opalj.fpcf.InterimEUBP +import org.opalj.fpcf.InterimPartialResult +import org.opalj.fpcf.PartialResult +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Results +import org.opalj.fpcf.SomeEOptionP +import org.opalj.fpcf.SomeEPK +import org.opalj.fpcf.SomeEPS +import org.opalj.ide.integration.IDEPropertyMetaInformation +import org.opalj.ide.integration.IDERawProperty +import org.opalj.ide.integration.IDETargetCallablesProperty +import org.opalj.ide.problem.AllTopEdgeFunction +import org.opalj.ide.problem.EdgeFunction +import org.opalj.ide.problem.EdgeFunctionResult +import org.opalj.ide.problem.FinalEdgeFunction +import org.opalj.ide.problem.FlowFunction +import org.opalj.ide.problem.IDEFact +import org.opalj.ide.problem.IdentityEdgeFunction +import org.opalj.ide.problem.IDEProblem +import org.opalj.ide.problem.IDEValue +import org.opalj.ide.problem.InterimEdgeFunction + +/** + * This is a solver for IDE problems. It is based on the exhaustive algorithm that was presented in the original IDE + * paper from 1996 as base. The paper can be found [[https://doi.org/10.1016/0304-3975(96)00072-2 here]]. Naming of + * methods and variables follows the naming used in the original paper as far as possible. + * The original solver is enhanced with several extensions/features as part of the master thesis of Robin Körkemeier. + * The most important enhancements are: + * - The possibility to specify additional analysis seeds (allowing for more precise analysis results). + * - The possibility to provide custom summaries for arbitrary call statements (allowing to retain precision in + * presence of unavailable code as well as to improve performance). + * - On-demand solver execution, to fully integrate into OPAL as a lazy analysis (improves performance especially in + * interacting analysis scenarios; does not affect how IDE problems are specified). + * - The possibility to define interacting IDE analysis (resp. IDE problems that make use of analysis interaction) + * using the blackboard architecture provided by OPAL. + * + * For a simple example IDE problem definition have a look at `LinearConstantPropagationProblem` in the TAC module of + * this project. It implements a basic linear constant propagation as described in the original IDE paper. + * For an example of interacting IDE problems have a look at `LCPOnFieldsProblem` and + * `LinearConstantPropagationProblemExtended`. These are an extension of the basic linear constant propagation and + * capable of detecting and tracking constants in fields. They also are an example for cyclic analysis interaction. + * + * @author Robin Körkemeier + */ +class IDEAnalysis[Fact <: IDEFact, Value <: IDEValue, Statement, Callable <: Entity]( + val project: SomeProject, + val problem: IDEProblem[Fact, Value, Statement, Callable], + val icfg: ICFG[Statement, Callable], + val propertyMetaInformation: IDEPropertyMetaInformation[Fact, Value, Statement, Callable] +) extends FPCFAnalysis { + /** + * Type of a node in the exploded supergraph, denoted by the corresponding statement and IDE fact + */ + private type Node = (Statement, Fact) + /** + * Type of a path in the exploded supergraph, denoted by its start and end node + */ + private type Path = (Node, Node) + + /** + * Type of the path worklist used in the first phase of the algorithm + */ + private type PathWorkList = MutableQueue[Path] + + /** + * Type of a jump function + */ + private type JumpFunction = EdgeFunction[Value] + private type JumpFunctions = MutableMap[(Statement, Statement), MutableMap[(Fact, Fact), JumpFunction]] + /** + * Type of a summary function + */ + private type SummaryFunction = EdgeFunction[Value] + private type SummaryFunctions = MutableMap[Path, SummaryFunction] + + /** + * Type of the node worklist used in the second phase of the algorithm + */ + private type NodeWorkList = MutableQueue[Node] + + private type Values = MutableMap[Node, Value] + + /** + * An instance of a [[AllTopEdgeFunction]] to use in the algorithm (so it doesn't need to be provided as a + * parameter) + */ + private val allTopEdgeFunction: AllTopEdgeFunction[Value] = new AllTopEdgeFunction[Value](problem.lattice.top) { + override def composeWith[V >: Value <: IDEValue](secondEdgeFunction: EdgeFunction[V]): EdgeFunction[V] = { + /* This method cannot be implemented correctly without knowledge about the other possible edge functions. + * It will never be called on this instance anyway. However, we throw an exception here to be safe. */ + throw new UnsupportedOperationException(s"Composing $this with $secondEdgeFunction is not implemented!") + } + + override def meet[V >: Value <: IDEValue](otherEdgeFunction: EdgeFunction[V]): EdgeFunction[V] = + otherEdgeFunction + + override def equals[V >: Value <: IDEValue](otherEdgeFunction: EdgeFunction[V]): Boolean = + otherEdgeFunction eq this + } + + /** + * Container class for simpler interaction and passing of the shared data + */ + private class State( + initialTargetCallablesEOptionP: EOptionP[ + IDEPropertyMetaInformation[Fact, Value, Statement, Callable], + IDETargetCallablesProperty[Callable] + ] + ) { + /** + * Collection of callables to compute results for. Used to optimize solver computation. + */ + private val targetCallables = MutableSet.empty[Callable] + + private var targetCallablesEOptionP: EOptionP[ + IDEPropertyMetaInformation[Fact, Value, Statement, Callable], + IDETargetCallablesProperty[Callable] + ] = initialTargetCallablesEOptionP + + processTargetCallablesEOptionP(initialTargetCallablesEOptionP) + + /** + * Collection of callables that received changes (compared to the last update) which may affect their final + * value, e.g. a changed jump function. Especially used to reduce computation overhead in phase 2 and to reduce + * amount of created results. + */ + private val callablesWithChanges = MutableSet.empty[Callable] + + /** + * The work list for paths used in the first phase + */ + private val pathWorkList: PathWorkList = MutableQueue.empty + + /** + * The jump functions (incrementally calculated) in the first phase + */ + private val jumpFunctions: JumpFunctions = MutableMap.empty + + /** + * The summary functions (incrementally calculated) in the first phase + */ + private val summaryFunctions: SummaryFunctions = MutableMap.empty + + /** + * A map of visited end nodes with corresponding jump function for a given start node (needed for endSummaries + * extension) + */ + private val endSummaries = MutableMap.empty[Node, MutableSet[(Node, JumpFunction)]] + + /** + * A map of call targets to visited call sources (basically a reverse call graph/caller graph; needed for + * endSummaries extension) + */ + private val callTargetsToSources = MutableMap.empty[Node, MutableSet[Node]] + + /** + * The work list for nodes used in the second phase + */ + private val nodeWorkList: NodeWorkList = MutableQueue.empty + + /** + * A data structure storing all calculated (intermediate) values. Additionally grouped by callable for more + * performant access. + */ + private val values: MutableMap[Callable, Values] = MutableMap.empty + + /** + * A data structure mapping outstanding EPKs to the last processed property result as well as the continuations + * to be executed when a new result is available (needed to integrate IDE into OPAL) + */ + private val dependees = MutableMap.empty[SomeEPK, (SomeEOptionP, MutableSet[() => Unit])] + + /** + * Get the callables the IDE analysis should compute results for + */ + def getTargetCallables: scala.collection.Set[Callable] = { + targetCallables + } + + /** + * Get the last processed result of the target callables property + */ + def getTargetCallablesEOptionP: EOptionP[ + IDEPropertyMetaInformation[Fact, Value, Statement, Callable], + IDETargetCallablesProperty[Callable] + ] = { + targetCallablesEOptionP + } + + /** + * Process a new target callables property result. This adds all new callables to [[targetCallables]] and + * remembers them in [[callablesWithChanges]]. + */ + def processTargetCallablesEOptionP(newTargetCallablesEOptionP: EOptionP[ + IDEPropertyMetaInformation[Fact, Value, Statement, Callable], + IDETargetCallablesProperty[Callable] + ]): Unit = { + targetCallablesEOptionP = newTargetCallablesEOptionP + if (targetCallablesEOptionP.hasUBP) { + val addedTargetCallables = targetCallablesEOptionP.ub.targetCallables.diff(targetCallables) + targetCallables.addAll(addedTargetCallables) + // Mark new target callables as changed to trigger result creation + callablesWithChanges.addAll(addedTargetCallables) + } + } + + /** + * Remove all callables from the [[callablesWithChanges]] collection + */ + def clearCallablesWithChanges(): Unit = { + callablesWithChanges.clear() + } + + /** + * Add a callable to the [[callablesWithChanges]] collection + */ + def rememberCallableWithChanges(callable: Callable): Unit = { + callablesWithChanges.add(callable) + } + + /** + * Get the callables that are remembered as having received changes during analysis execution + */ + def getCallablesWithChanges: scala.collection.Set[Callable] = { + callablesWithChanges + } + + /** + * Enqueue a path in the path work list + */ + def enqueuePath(path: Path): Unit = { + pathWorkList.enqueue(path) + } + + /** + * Dequeue a path from the path work list + */ + def dequeuePath(): Path = { + pathWorkList.dequeue() + } + + /** + * Check whether the path work list is empty + */ + def isPathWorkListEmpty: Boolean = { + pathWorkList.isEmpty + } + + /** + * Store a jump function for a path. Existing jump functions for the path are overwritten (meeting, ... needs to + * be done before calling this method). + */ + def setJumpFunction(path: Path, jumpFunction: JumpFunction): Unit = { + val ((source, sourceFact), (target, targetFact)) = path + jumpFunctions + .getOrElseUpdate((source, target), { MutableMap.empty }) + .put((sourceFact, targetFact), jumpFunction) + } + + /** + * Get the jump function for a path. Returns the [[allTopEdgeFunction]] if no jump function can be found for the + * path. + */ + def getJumpFunction(path: Path): JumpFunction = { + val ((source, sourceFact), (target, targetFact)) = path + jumpFunctions + .getOrElse((source, target), Map.empty[(Fact, Fact), JumpFunction]) + .getOrElse((sourceFact, targetFact), allTopEdgeFunction) // else part handles IDE lines 1 - 2 + } + + /** + * Get the jump functions for an incompletely specified path. Does not add the [[allTopEdgeFunction]] in + * contrast to [[getJumpFunction]]. + * + * @param sourceFactOption Fact to filter the source fact of a path against. Matches all facts if empty. + * @param targetFactOption Fact to filter the target fact of a path against. Matches all facts if empty. + */ + def lookupJumpFunctions( + source: Statement, + sourceFactOption: Option[Fact] = None, + target: Statement, + targetFactOption: Option[Fact] = None + ): scala.collection.Map[(Fact, Fact), JumpFunction] = { + val subMap = jumpFunctions.getOrElse((source, target), Map.empty[(Fact, Fact), JumpFunction]) + + // Case distinction on optional parameters for more efficient filtering + (sourceFactOption, targetFactOption) match { + case (Some(sourceFact), Some(targetFact)) => + subMap.filter { case ((sF, tF), _) => sF == sourceFact && tF == targetFact } + case (Some(sourceFact), None) => + subMap.filter { case ((sF, _), _) => sF == sourceFact } + case (None, Some(targetFact)) => + subMap.filter { case ((_, tF), _) => tF == targetFact } + case _ => + subMap + } + } + + /** + * Store a summary function for a path. Existing summary functions for the path are overwritten (meeting, ... + * needs to be done before calling this method). + */ + def setSummaryFunction(path: Path, summaryFunction: SummaryFunction): Unit = { + summaryFunctions.put(path, summaryFunction) + } + + /** + * Get the summary function for a path. Returns the [[allTopEdgeFunction]] if no summary function can be found + * for the path. + */ + def getSummaryFunction(path: Path): SummaryFunction = { + summaryFunctions.getOrElse(path, allTopEdgeFunction) // else part handels IDE lines 3 - 4 + } + + /** + * Add a jump function as end summary for a given path (starting and ending in the same callable) + */ + def addEndSummary(path: Path, jumpFunction: JumpFunction): Unit = { + val (start, end) = path + val set = endSummaries.getOrElseUpdate(start, MutableSet.empty) + set.add((end, jumpFunction)) + } + + /** + * Get all end summaries for a given start node (of a callable) + */ + def getEndSummaries(start: Node): scala.collection.Set[(Node, JumpFunction)] = { + endSummaries.getOrElse(start, Set.empty) + } + + /** + * Remember a visited call edge (which is an edge from a call-site node to a start node). This adds the + * call-site node as possible caller of the start node. + */ + def rememberCallEdge(path: Path): Unit = { + val (source, target) = path + + val set = callTargetsToSources.getOrElseUpdate(target, MutableSet.empty) + set.add(source) + } + + /** + * Get all call-site nodes (that have been visited so far) that target the given start node + */ + def lookupCallSourcesForTarget(target: Statement, targetFact: Fact): scala.collection.Set[Node] = { + callTargetsToSources.getOrElse((target, targetFact), Set.empty) + } + + /** + * Enqueue a node in the node work list + */ + def enqueueNode(node: Node): Unit = { + nodeWorkList.enqueue(node) + } + + /** + * Dequeue a node from the node work list + */ + def dequeueNode(): Node = { + nodeWorkList.dequeue() + } + + /** + * Check whether the node work list is empty + */ + def isNodeWorkListEmpty: Boolean = { + nodeWorkList.isEmpty + } + + /** + * Get the value for a node. Returns the lattice's top element if no value can be found for the node. + * + * @param callable the callable belonging to the node + */ + def getValue(node: Node, callable: Callable): Value = { + values.getOrElse(callable, MutableMap.empty).getOrElse(node, problem.lattice.top) // else part handles IDE line 1 + } + + /** + * Get the value for a node. Returns the lattice's top element if no value can be found for the node. + */ + def getValue(node: Node): Value = { + getValue(node, icfg.getCallable(node._1)) + } + + /** + * Set the value for a node. Existing values are overwritten (meeting needs to be done before calling this + * method). + */ + def setValue(node: Node, newValue: Value, callable: Callable): Unit = { + values.getOrElseUpdate(callable, { MutableMap.empty }).put(node, newValue) + } + + /** + * Remove all stored values + */ + def clearValues(): Unit = { + values.clear() + } + + /** + * Collect the analysis results for a set of callables + * @return a map from statements to results and the results for the callable in total (both per requested + * callable) + */ + def collectResults(callables: scala.collection.Set[Callable]): Map[ + Callable, + (scala.collection.Map[Statement, scala.collection.Set[(Fact, Value)]], scala.collection.Set[(Fact, Value)]) + ] = { + val valuesByCallable = callables + .map { callable => callable -> values.getOrElse(callable, Map.empty[Node, Value]) } + .toMap + + valuesByCallable.map { case (callable, values) => + val resultsByStatement = values + .view + .filterKeys { case (_, d) => d != problem.nullFact } + .groupMap(_._1._1) { case ((_, d), value) => (d, value) } + .map { case (n, dValuePairs) => + ( + n, + dValuePairs.groupMapReduce(_._1)(_._2) { (value1, value2) => + problem.lattice.meet(value1, value2) + }.toSet + ) + } + + val resultsForExit = resultsByStatement + .collect { case (n, values) if icfg.isNormalExitStatement(n) => values.toList } + .flatten + .groupMapReduce(_._1)(_._2) { + (value1, value2) => problem.lattice.meet(value1, value2) + } + .toSet + + callable -> (resultsByStatement, resultsForExit) + } + } + + /** + * Add a dependency + */ + def addDependee(eOptionP: SomeEOptionP, c: () => Unit): Unit = { + // The eOptionP is only inserted the first time the corresponding EPK occurs. Consequently, it is the most + // precise property result that is seen by all dependents. + val (_, set) = dependees.getOrElseUpdate(eOptionP.toEPK, (eOptionP, MutableSet.empty)) + set.add(c) + } + + /** + * Check whether there are outstanding dependencies + * @return + */ + def hasDependees: Boolean = { + dependees.nonEmpty + } + + /** + * Get all dependencies + * @return + */ + def getDependees: scala.collection.Set[SomeEOptionP] = { + dependees.values.map(_._1).toSet + } + + /** + * Get the stored continuations for a dependency and remove them + */ + def getAndRemoveDependeeContinuations(eOptionP: SomeEOptionP): scala.collection.Set[() => Unit] = { + dependees.remove(eOptionP.toEPK).map(_._2).getOrElse(Set.empty).toSet + } + } + + /** + * Run the IDE solver and calculate (and return) the result. This method should only be triggered in combination + * with the IDE proxy! + * + * @param entity Expected to be `None`. Other values do not cause errors but will only return empty (temporary) + * results. + * @return a result for each statement of the target callables plus one result for each target callable itself + * (combining the results of all exit statements) + */ + def performAnalysis(entity: Entity): ProperPropertyComputationResult = { + /* If an actual entity reaches here, there was a concrete request to the property store that was faster than + * this analysis could answer when being called with `entity == None`. Returning an 'empty' result in this case, + * which of course will be updated if the right analysis request completes. */ + if (entity != None) { + return PartialResult( + entity, + propertyMetaInformation.backingPropertyMetaInformation.key, + { (_: SomeEOptionP) => None } + ) + } + + val targetCallablesEOptionP = + propertyStore(propertyMetaInformation, propertyMetaInformation.targetCallablesPropertyMetaInformation.key) + implicit val state: State = new State(targetCallablesEOptionP) + + performPhase1() + performPhase2() + + createResult() + } + + /** + * Perform the first phase of the algorithm. This phase computes the jump and summary functions. + * @return whether the phase is finished or has to be continued once the dependees are resolved + */ + private def performPhase1()(implicit s: State): Boolean = { + seedPhase1() + processPathWorkList() + + !s.hasDependees + } + + /** + * Continue the first phase of the algorithm. Like [[performPhase1]] but without the seeding part. + * @return whether the phase is finished or has to be continued once the dependees are resolved + */ + private def continuePhase1()(implicit s: State): Boolean = { + processPathWorkList() + + !s.hasDependees + } + + /** + * Perform the second phase of the algorithm. This phase computes the result values. + */ + private def performPhase2()(implicit s: State): Unit = { + s.clearValues() + + seedPhase2() + computeValues() + } + + /** + * Continue the second phase of the algorithm. Like [[performPhase2]] but based on the previously computed result + * values. + */ + private def continuePhase2()(implicit s: State): Unit = { + seedPhase2() + computeValues() + } + + /** + * Creates an OPAL result after the algorithm did terminate. The result contains values for the callables from + * [[State.getTargetCallables]]. For subsequent calls this method omits values that did not change compared to the + * previous execution. + */ + private def createResult()( + implicit s: State + ): ProperPropertyComputationResult = { + // Only create results for target callables whose values could have changed + val callables = s.getCallablesWithChanges.intersect(s.getTargetCallables) + val collectedResults = s.collectResults(callables) + // Results for all callables of interest + val callableResults = callables.map { callable => + val (resultsByStatement, resultsForExit) = + collectedResults.getOrElse( + callable, + { (Map.empty[Statement, scala.collection.Set[(Fact, Value)]], Set.empty[(Fact, Value)]) } + ) + val ideRawProperty = new IDERawProperty( + propertyMetaInformation.backingPropertyMetaInformation.key, + resultsByStatement, + resultsForExit + ) + + PartialResult( + callable, + propertyMetaInformation.backingPropertyMetaInformation.key, + { (eOptionP: SomeEOptionP) => + if (eOptionP.hasUBP && eOptionP.ub == ideRawProperty) { + None + } else { + Some(InterimEUBP(callable, ideRawProperty)) + } + } + ) + } + + Results( + callableResults ++ Seq( + // Result for continuing execution on dependee changes + InterimPartialResult( + None, + s.getDependees.toSet ++ Set(s.getTargetCallablesEOptionP), + { (eps: SomeEPS) => + if (eps.toEPK == s.getTargetCallablesEOptionP.toEPK) { + onTargetCallablesUpdateContinuation(eps.asInstanceOf[EPS[ + IDEPropertyMetaInformation[Fact, Value, Statement, Callable], + IDETargetCallablesProperty[Callable] + ]]) + } else { + onDependeeUpdateContinuation(eps) + } + } + ) + ) + ) + } + + /** + * Continuation to run when the target callables property changes + */ + private def onTargetCallablesUpdateContinuation(eps: EPS[ + IDEPropertyMetaInformation[Fact, Value, Statement, Callable], + IDETargetCallablesProperty[Callable] + ])(implicit s: State): ProperPropertyComputationResult = { + // New iteration, so no changes at the beginning + s.clearCallablesWithChanges() + + // Process the new result + s.processTargetCallablesEOptionP(eps) + + // Re-seed the first phase + seedPhase1() + + // Re-seeding can have enqueued paths to the path worklist + continuePhase1() + continuePhase2() + + createResult() + } + + /** + * Continuation to run when a dependee changes + */ + private def onDependeeUpdateContinuation(eps: SomeEPS)( + implicit s: State + ): ProperPropertyComputationResult = { + // New iteration, so no changes at the beginning + s.clearCallablesWithChanges() + + // Get and remove all continuations that are remembered for the EPS + val cs = s.getAndRemoveDependeeContinuations(eps) + + // Call continuations + cs.foreach(c => c()) + + // The continuations can have enqueued paths to the path work list + continuePhase1() + continuePhase2() + + createResult() + } + + /** + * The seeding algorithm for the first phase of the algorithm. Only enqueues the seed paths if the corresponding + * jump function is different from the stored one returned by [[State.getJumpFunction]]. + */ + private def seedPhase1()(implicit s: State): Unit = { + def propagateSeed(e: Path, callable: Callable, f: EdgeFunction[Value]): Unit = { + val oldJumpFunction = s.getJumpFunction(e) + val fPrime = f.meet(oldJumpFunction) + + if (!fPrime.equals(oldJumpFunction)) { + s.setJumpFunction(e, fPrime) + s.enqueuePath(e) + s.rememberCallableWithChanges(callable) + } + } + + val callables = s.getTargetCallables + callables.foreach { callable => + // IDE P1 lines 5 - 6 + icfg.getStartStatements(callable).foreach { stmt => + // Process null fact as in the original algorithm + val path = ((stmt, problem.nullFact), (stmt, problem.nullFact)) + propagateSeed(path, callable, IdentityEdgeFunction) + + // Deal with additional seeds + problem.getAdditionalSeeds(stmt, callable).foreach { fact => + val path = ((stmt, problem.nullFact), (stmt, fact)) + propagateSeed(path, callable, IdentityEdgeFunction) + + def processAdditionalSeed(): Unit = { + val edgeFunction = + handleEdgeFunctionResult( + problem.getAdditionalSeedsEdgeFunction(stmt, fact, callable), + processAdditionalSeed _ + ) + propagateSeed(path, callable, edgeFunction) + } + + processAdditionalSeed() + } + } + } + } + + /** + * Process the path work list. This is the main loop of the first phase of the algorithm. + */ + private def processPathWorkList()(implicit s: State): Unit = { + while (!s.isPathWorkListEmpty) { // IDE P1 line 7 + val path = s.dequeuePath() // IDE P1 line 8 + val ((_, _), (n, _)) = path + val f = s.getJumpFunction(path) // IDE P1 line 9 + + if (icfg.isCallStatement(n)) { // IDE P1 line 11 + processCallFlow(path, f, icfg.getCallees(n)) + } else if (icfg.isNormalExitStatement(n)) { // IDE P1 line 19 + processExitFlow(path, f) + } else { // IDE P1 line 30 + processNormalFlow(path, f) + } + } + } + + /** + * Process a call flow found in the first phase of the algorithm + * + * @param path the path + * @param f the current jump function + * @param qs the callees of the call-site node + */ + private def processCallFlow(path: Path, f: JumpFunction, qs: scala.collection.Set[Callable])( + implicit s: State + ): Unit = { + val ((sp, d1), (n, d2)) = path + + // Possible statements for the return-site node + val rs = icfg.getNextStatements(n) // IDE P1 line 14 + + if (qs.isEmpty) { + // If there are no callees, precomputed summaries are required + rs.foreach { r => + val d5s = handleFlowFunctionResult(problem.getPrecomputedFlowFunction(n, d2, r).compute(), path) + + d5s.foreach { d5 => + val summaryFunction = + handleEdgeFunctionResult(problem.getPrecomputedSummaryFunction(n, d2, r, d5), path) + val callToReturnPath = ((n, d2), (r, d5)) + val oldSummaryFunction = s.getSummaryFunction(callToReturnPath) + val fPrime = summaryFunction.meet(oldSummaryFunction) + + if (!fPrime.equals(oldSummaryFunction)) { + s.setSummaryFunction(callToReturnPath, fPrime) + } + + propagate(((sp, d1), (r, d5)), f.composeWith(fPrime)) + } + } + } else { + qs.foreach { q => + if (problem.hasPrecomputedFlowAndSummaryFunction(n, d2, q)) { + /* Precomputed summaries are provided by the problem definition for this scenario */ + rs.foreach { r => + val d5s = + handleFlowFunctionResult(problem.getPrecomputedFlowFunction(n, d2, q, r).compute(), path) + + d5s.foreach { d5 => + val summaryFunction = + handleEdgeFunctionResult(problem.getPrecomputedSummaryFunction(n, d2, q, r, d5), path) + val callToReturnPath = ((n, d2), (r, d5)) + val oldSummaryFunction = s.getSummaryFunction(callToReturnPath) + val fPrime = summaryFunction.meet(oldSummaryFunction) + + if (!fPrime.equals(oldSummaryFunction)) { + s.setSummaryFunction(callToReturnPath, fPrime) + } + + propagate(((sp, d1), (r, d5)), f.composeWith(fPrime)) + } + } + } else { + val sqs = icfg.getStartStatements(q) + sqs.foreach { sq => + // IDE P1 lines 12 - 13 + val d3s = handleFlowFunctionResult(problem.getCallFlowFunction(n, d2, sq, q).compute(), path) + + d3s.foreach { d3 => + s.rememberCallEdge(((n, d2), (sq, d3))) + + val endSummaries = s.getEndSummaries((sq, d3)) + // Handling for end summaries extension + if (endSummaries.nonEmpty) { + endSummaries.foreach { case ((eq, d4), fEndSummary) => + // End summaries are from start to end nodes, so we need to apply call and exit flow + // accordingly + val f4 = + handleEdgeFunctionResult(problem.getCallEdgeFunction(n, d2, sq, d3, q), path) + rs.foreach { r => + val d5s = handleFlowFunctionResult( + problem.getReturnFlowFunction(eq, d4, q, r, n, d2).compute(), + path + ) + d5s.foreach { d5 => + val f5 = handleEdgeFunctionResult( + problem.getReturnEdgeFunction(eq, d4, q, r, d5, n, d2), + path + ) + val callToReturnPath = ((n, d2), (r, d5)) + val oldSummaryFunction = s.getSummaryFunction(callToReturnPath) + val fPrime = + f4.composeWith(fEndSummary).composeWith(f5).meet(oldSummaryFunction) + + if (!fPrime.equals(oldSummaryFunction)) { + s.setSummaryFunction(callToReturnPath, fPrime) + } + + propagate(((sp, d1), (r, d5)), f.composeWith(fPrime)) + } + } + } + } else { + // Default algorithm behavior + propagate(((sq, d3), (sq, d3)), IdentityEdgeFunction) + } + } + } + } + + // Default algorithm behavior + rs.foreach { r => + val d3s = handleFlowFunctionResult(problem.getCallToReturnFlowFunction(n, d2, q, r).compute(), path) + + // IDE P1 lines 15 - 16 + d3s.foreach { d3 => + propagate( + ((sp, d1), (r, d3)), + f.composeWith(handleEdgeFunctionResult( + problem.getCallToReturnEdgeFunction(n, d2, q, r, d3), + path + )) + ) + } + + // IDE P1 lines 17 - 18 + d3s.foreach { d3 => + val f3 = s.getSummaryFunction(((n, d2), (r, d3))) + if (!f3.equals(allTopEdgeFunction)) { + propagate(((sp, d1), (r, d3)), f.composeWith(f3)) + } + } + } + } + } + } + + /** + * Process an exit flow found in the first phase of the algorithm + * + * @param path the path + * @param f the current jump function + */ + private def processExitFlow(path: Path, f: JumpFunction)(implicit s: State): Unit = { + val ((sp, d1), (n, d2)) = path + val p = icfg.getCallable(n) + + // Handling for end summaries extension + s.addEndSummary(path, f) + + // IDE P1 line 20 + val callSources = s.lookupCallSourcesForTarget(sp, d1) + callSources.foreach { + case (c, d4) => + val rs = icfg.getNextStatements(c) + rs.foreach { r => + // IDE P1 line 21 + val d5s = handleFlowFunctionResult(problem.getReturnFlowFunction(n, d2, p, r, c, d4).compute(), path) + + d5s.foreach { d5 => + // IDE P1 lines 22 - 23 + val f4 = handleEdgeFunctionResult(problem.getCallEdgeFunction(c, d4, sp, d1, p), path) + val f5 = handleEdgeFunctionResult(problem.getReturnEdgeFunction(n, d2, p, r, d5, c, d4), path) + + // IDE P1 line 24 + val callToReturnPath = ((c, d4), (r, d5)) + val oldSummaryFunction = s.getSummaryFunction(callToReturnPath) + val fPrime = f4.composeWith(f).composeWith(f5).meet(oldSummaryFunction) + + // IDE P1 lines 25 - 29 + if (!fPrime.equals(oldSummaryFunction)) { + s.setSummaryFunction(callToReturnPath, fPrime) + + val sqs = icfg.getStartStatements(icfg.getCallable(c)) + sqs.foreach { sq => + val jumpFunctionsMatchingTarget = + s.lookupJumpFunctions(source = sq, target = c, targetFactOption = Some(d4)) + jumpFunctionsMatchingTarget.foreach { + case ((d3, _), f3) if !f3.equals(allTopEdgeFunction) => + propagate(((sq, d3), (r, d5)), f3.composeWith(fPrime)) + case _ => + } + } + } + } + } + } + } + + /** + * Process a normal flow (neither call nor exit) found in the first phase of the algorithm + * + * @param path the path + * @param f the current jump function + */ + private def processNormalFlow(path: Path, f: JumpFunction)(implicit s: State): Unit = { + val ((sp, d1), (n, d2)) = path + + // IDE P1 lines 31 - 32 + icfg.getNextStatements(n).foreach { m => + val d3s = handleFlowFunctionResult(problem.getNormalFlowFunction(n, d2, m).compute(), path) + + d3s.foreach { d3 => + propagate( + ((sp, d1), (m, d3)), + f.composeWith(handleEdgeFunctionResult(problem.getNormalEdgeFunction(n, d2, m, d3), path)) + ) + } + } + } + + /** + * Examine given path and jump function and decide whether this change needs to be propagated. This basically is + * done by comparing the jump function to the old jump function for the path. + * + * @param e the new path + * @param f the new jump function + */ + private def propagate(e: Path, f: EdgeFunction[Value])(implicit s: State): Unit = { + // IDE P1 lines 34 - 37 + val oldJumpFunction = s.getJumpFunction(e) + val fPrime = f.meet(oldJumpFunction) + + if (!fPrime.equals(oldJumpFunction)) { + s.setJumpFunction(e, fPrime) + s.enqueuePath(e) + s.rememberCallableWithChanges(icfg.getCallable(e._2._1)) + } + } + + /** + * Function for simpler integration of the interaction extensions into the original algorithm. Accepts the result of + * computing a flow function (which is a set of facts and a set of dependees), adjusts the internal state to + * correctly handle the dependees, and returns only the facts (which is what flow functions do in the original + * algorithm). + * + * @param factsAndDependees the result of computing a flow function + * @param path the path to re-enqueue when encountering an interim flow function + * @return the (interim) generated flow facts + */ + private def handleFlowFunctionResult( + factsAndDependees: FlowFunction.FactsAndDependees[Fact], + path: Path + )(implicit s: State): scala.collection.Set[Fact] = { + val (facts, dependees) = factsAndDependees + if (dependees.nonEmpty) { + dependees.foreach { dependee => + s.addDependee( + dependee, + () => s.enqueuePath(path) + ) + } + } + facts + } + + /** + * Function for simpler integration of the interaction extensions into the original algorithm. Accepts an edge + * function result, adjusts the internal state to correctly handle the dependees, and returns the contained + * (interim) edge function. + * + * @param path the path to re-enqueue when encountering an interim edge function + * @return the (interim) edge function from the result + */ + private def handleEdgeFunctionResult( + edgeFunctionResult: EdgeFunctionResult[Value], + path: Path + )(implicit s: State): EdgeFunction[Value] = { + handleEdgeFunctionResult(edgeFunctionResult, () => s.enqueuePath(path)) + } + + /** + * Function for simpler integration of the interaction extensions into the original algorithm. Accepts an edge + * function result, adjusts the internal state to correctly handle the dependees, and returns the contained + * (interim) edge function. + * + * @param continuation the continuation to execute when the dependee changes (in case of an interim edge function) + * @return the (interim) edge function from the result + */ + private def handleEdgeFunctionResult( + edgeFunctionResult: EdgeFunctionResult[Value], + continuation: () => Unit + )(implicit s: State): EdgeFunction[Value] = { + edgeFunctionResult match { + case FinalEdgeFunction(edgeFunction) => + edgeFunction + case InterimEdgeFunction(intermediateEdgeFunction, dependees) => + dependees.foreach { dependee => + s.addDependee( + dependee, + continuation + ) + } + intermediateEdgeFunction + } + } + + /** + * The seeding algorithm for the second phase of the algorithm. Only considers callables that are remembered as + * being changed during the first phase so far ([[State.getCallablesWithChanges]]). + */ + private def seedPhase2()(implicit s: State): Unit = { + val callables = s.getCallablesWithChanges + callables.foreach { callable => + // IDE P2 lines 2 - 3 + icfg.getStartStatements(callable).foreach { stmt => + val node = (stmt, problem.nullFact) + s.enqueueNode(node) + s.setValue(node, problem.lattice.bottom, callable) + } + } + } + + /** + * Computes the analysis values. Like the original algorithm, this is made up of two sub-phases. The first one + * computing and propagating values from call-site nodes to the reachable start nodes. This is done by processing + * the node work list. The second sub-phase then simply computes the values for every other statement. + */ + private def computeValues()(implicit s: State): Unit = { + // IDE P2 part (i) + while (!s.isNodeWorkListEmpty) { // IDE P2 line 4 + val node = s.dequeueNode() // IDE P2 line 5 + + val (n, _) = node + + if (icfg.isCallStatement(n)) { // IDE P2 line 11 + processCallNode(node, icfg.getCallees(n)) + } else { // IDE P2 line 7 + processStartNode(node) + } + } + + // IDE P2 part (ii) + // IDE P2 lines 15 - 17 + // Reduced to the callables whose values could have changed + val ps = s.getCallablesWithChanges.intersect(s.getTargetCallables) + ps.foreach { p => + val sps = icfg.getStartStatements(p) + val ns = collectReachableStmts(sps, stmt => !icfg.isCallStatement(stmt)) + + // IDE P2 line 16 - 17 + ns.foreach { n => + sps.foreach { sp => + val jumpFunctionsMatchingTarget = s.lookupJumpFunctions(source = sp, target = n) + jumpFunctionsMatchingTarget.foreach { + case ((dPrime, d), fPrime) if !fPrime.equals(allTopEdgeFunction) => + val nSharp = (n, d) + val vPrime = problem.lattice.meet( + s.getValue(nSharp, p), + fPrime.compute(s.getValue((sp, dPrime), p)) + ) + + s.setValue(nSharp, vPrime, p) + + case _ => + } + } + } + } + } + + /** + * Collect all statements that are reachable from a certain start set of statements. + * + * @param originStmts the statements to start searching from + * @param filterPredicate an additional predicate the collected statements have to fulfill + */ + private def collectReachableStmts( + originStmts: scala.collection.Set[Statement], + filterPredicate: Statement => Boolean + ): Iterator[Statement] = { + new Iterator[Statement]() { + private val collectedStmts = MutableSet.empty[Statement] + private val seenStmts = MutableSet.empty[Statement] + + collectedStmts.addAll(originStmts.filter(filterPredicate)) + seenStmts.addAll(originStmts) + originStmts.filterNot(filterPredicate).foreach { stmt => processStatement(stmt) } + + private def processStatement(stmt: Statement): Unit = { + val workingStmts = MutableQueue(stmt) + + while (workingStmts.nonEmpty) { + icfg.getNextStatements(workingStmts.dequeue()) + .foreach { followingStmt => + if (!seenStmts.contains(followingStmt)) { + seenStmts.add(followingStmt) + + if (filterPredicate(followingStmt)) { + collectedStmts.add(followingStmt) + } else { + workingStmts.enqueue(followingStmt) + } + } + } + } + } + + override def hasNext: Boolean = { + collectedStmts.nonEmpty + } + + override def next(): Statement = { + val stmt = collectedStmts.head + collectedStmts.remove(stmt) + + processStatement(stmt) + + stmt + } + } + } + + /** + * Process a start node found in the first sub-phase of the second phase of the algorithm + * + * @param node the node + */ + private def processStartNode(node: Node)(implicit s: State): Unit = { + val (n, d) = node + + // IDE P2 line 8 + val cs = collectReachableStmts(Set(n), stmt => icfg.isCallStatement(stmt)) + + // IDE P2 lines 9 - 10 + cs.foreach { c => + val jumpFunctionsMatchingTarget = + s.lookupJumpFunctions(source = n, sourceFactOption = Some(d), target = c) + jumpFunctionsMatchingTarget.foreach { + case ((_, dPrime), fPrime) if !fPrime.equals(allTopEdgeFunction) => + propagateValue((c, dPrime), fPrime.compute(s.getValue((n, d)))) + case _ => + } + } + } + + /** + * Process a call-site node found in the first sub-phase of the second phase of the algorithm + * + * @param node the node + * @param qs the callees of the call-site node + */ + private def processCallNode(node: Node, qs: scala.collection.Set[Callable])(implicit s: State): Unit = { + val (n, d) = node + + // IDE P2 lines 12 - 13 + qs.foreach { q => + if (!problem.hasPrecomputedFlowAndSummaryFunction(n, d, q)) { + val sqs = icfg.getStartStatements(q) + sqs.foreach { sq => + val dPrimes = extractFlowFunctionFromResult(problem.getCallFlowFunction(n, d, sq, q).compute()) + dPrimes.foreach { dPrime => + propagateValue( + (sq, dPrime), + extractEdgeFunctionFromResult(problem.getCallEdgeFunction(n, d, sq, dPrime, q)) + .compute(s.getValue(node)) + ) + } + } + } + } + } + + /** + * Examine a given node and value and decide whether this change needs to be propagated. This is basically done by + * comparing the given value to the old value for the node. + * + * @param nSharp the new node + * @param v the new value + */ + private def propagateValue(nSharp: Node, v: Value)(implicit s: State): Unit = { + val callable = icfg.getCallable(nSharp._1) + + // IDE P2 lines 18 - 21 + val oldValue = s.getValue(nSharp, callable) + val vPrime = problem.lattice.meet(v, oldValue) + + if (vPrime != oldValue) { + s.setValue(nSharp, vPrime, callable) + s.enqueueNode(nSharp) + s.rememberCallableWithChanges(callable) + } + } + + /** + * Extract facts from flow function result while ignoring the dependees + */ + private def extractFlowFunctionFromResult( + factsAndDependees: FlowFunction.FactsAndDependees[Fact] + ): scala.collection.Set[Fact] = { + val (facts, _) = factsAndDependees + facts + } + + /** + * Extract edge function from result ignoring the dependees + */ + private def extractEdgeFunctionFromResult(edgeFunctionResult: EdgeFunctionResult[Value]): EdgeFunction[Value] = { + edgeFunctionResult match { + case FinalEdgeFunction(edgeFunction) => + edgeFunction + case InterimEdgeFunction(interimEdgeFunction, _) => + interimEdgeFunction + } + } +} diff --git a/OPAL/ide/src/main/scala/org/opalj/ide/solver/IDEAnalysisProxy.scala b/OPAL/ide/src/main/scala/org/opalj/ide/solver/IDEAnalysisProxy.scala new file mode 100644 index 0000000000..fd3923363e --- /dev/null +++ b/OPAL/ide/src/main/scala/org/opalj/ide/solver/IDEAnalysisProxy.scala @@ -0,0 +1,135 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package ide +package solver + +import scala.collection.immutable.Set + +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.FPCFAnalysis +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.InterimEUBP +import org.opalj.fpcf.InterimPartialResult +import org.opalj.fpcf.InterimResult +import org.opalj.fpcf.PartialResult +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result +import org.opalj.fpcf.Results +import org.opalj.fpcf.SomeEPS +import org.opalj.ide.integration.IDEPropertyMetaInformation +import org.opalj.ide.integration.IDETargetCallablesProperty +import org.opalj.ide.problem.IDEFact +import org.opalj.ide.problem.IDEValue + +/** + * A proxy for IDE analyses that accepts analysis requests for callables as well as statement-callable combinations. + * The [[IDEAnalysis]] solver runs on callables only and additionally produces results for each statement of that + * callable. This proxy analysis reduces all analysis requests to the callable and then forwards them to the actual IDE + * solver. + * + * @author Robin Körkemeier + */ +class IDEAnalysisProxy[Fact <: IDEFact, Value <: IDEValue, Statement, Callable <: Entity]( + val project: SomeProject, + val propertyMetaInformation: IDEPropertyMetaInformation[Fact, Value, Statement, Callable] +) extends FPCFAnalysis { + /** + * @param entity either only a callable or a pair of callable and statement that should be analyzed (if no statement + * is given, the result for all exit statements is calculated) + */ + def proxyAnalysis(entity: Entity): ProperPropertyComputationResult = { + val (callable, stmtOption) = entity match { + case (c: Entity, s: Entity) => (c.asInstanceOf[Callable], Some(s.asInstanceOf[Statement])) + case c => (c.asInstanceOf[Callable], None) + } + + /* Request to trigger IDE solver such that it listens for changes to set of target callables */ + propertyStore(None, propertyMetaInformation.backingPropertyMetaInformation.key) + + createResult(callable, stmtOption) + } + + private def createResult(callable: Callable, stmtOption: Option[Statement]): ProperPropertyComputationResult = { + val backingEOptionP = propertyStore( + callable, + propertyMetaInformation.backingPropertyMetaInformation.key + ) + + val entity = stmtOption match { + case Some(statement) => (callable, statement) + case None => callable + } + + if (backingEOptionP.isEPK) { + /* In this case, the analysis has not been called yet */ + Results( + PartialResult( + propertyMetaInformation, + propertyMetaInformation.targetCallablesPropertyMetaInformation.key, + { + (eOptionP: EOptionP[ + IDEPropertyMetaInformation[Fact, Value, Statement, Callable], + IDETargetCallablesProperty[Callable] + ]) => + /* Add target callable if not yet part of the set */ + if (!eOptionP.hasUBP) { + Some(InterimEUBP( + propertyMetaInformation, + new IDETargetCallablesProperty( + propertyMetaInformation.targetCallablesPropertyMetaInformation.key, + Set(callable) + ) + )) + } else if (eOptionP.ub.targetCallables.contains(callable)) { + None + } else { + Some(InterimEUBP( + propertyMetaInformation, + new IDETargetCallablesProperty( + propertyMetaInformation.targetCallablesPropertyMetaInformation.key, + eOptionP.ub.targetCallables ++ Set(callable) + ) + )) + } + } + ), + InterimPartialResult( + Set(backingEOptionP), + onDependeeUpdateContinuation(callable, stmtOption) + ) + ) + } else if (backingEOptionP.isFinal) { + Result( + entity, + propertyMetaInformation.createProperty( + stmtOption match { + case Some(statement) => backingEOptionP.ub.stmtResults.getOrElse(statement, Set.empty) + case None => backingEOptionP.ub.callableResults + } + ) + ) + } else if (backingEOptionP.hasUBP) { + InterimResult.forUB( + entity, + propertyMetaInformation.createProperty( + stmtOption match { + case Some(statement) => backingEOptionP.ub.stmtResults.getOrElse(statement, Set.empty) + case None => backingEOptionP.ub.callableResults + } + ), + Set(backingEOptionP), + onDependeeUpdateContinuation(callable, stmtOption) + ) + } else { + throw new IllegalStateException(s"Expected a final or interim EPS but got $backingEOptionP!") + } + } + + private def onDependeeUpdateContinuation( + callable: Callable, + stmtOption: Option[Statement] + )(eps: SomeEPS): ProperPropertyComputationResult = { + createResult(callable, stmtOption) + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/ifds/integration/JavaIFDSAnalysisScheduler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/ifds/integration/JavaIFDSAnalysisScheduler.scala new file mode 100644 index 0000000000..b02177c792 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/ifds/integration/JavaIFDSAnalysisScheduler.scala @@ -0,0 +1,26 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package ide +package ifds +package integration + +import org.opalj.br.analyses.SomeProject +import org.opalj.ide.ifds.problem.IFDSValue +import org.opalj.ide.problem.IDEFact +import org.opalj.tac.fpcf.analyses.ide.ifds.problem.JavaIFDSProblem +import org.opalj.tac.fpcf.analyses.ide.integration.JavaIDEAnalysisSchedulerBase +import org.opalj.tac.fpcf.analyses.ide.solver.JavaICFG + +/** + * Specialized IDE analysis scheduler for IFDS problems with Java programs. + * + * @author Robin Körkemeier + */ +abstract class JavaIFDSAnalysisScheduler[Fact <: IDEFact] extends JavaIDEAnalysisSchedulerBase[Fact, IFDSValue] { + override def propertyMetaInformation: JavaIFDSPropertyMetaInformation[Fact] + + override def createProblem(project: SomeProject, icfg: JavaICFG): JavaIFDSProblem[Fact] +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/ifds/integration/JavaIFDSPropertyMetaInformation.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/ifds/integration/JavaIFDSPropertyMetaInformation.scala new file mode 100644 index 0000000000..8342d1dc4e --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/ifds/integration/JavaIFDSPropertyMetaInformation.scala @@ -0,0 +1,20 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package ide +package ifds +package integration + +import org.opalj.br.Method +import org.opalj.ide.ifds.integration.IFDSPropertyMetaInformation +import org.opalj.ide.problem.IDEFact +import org.opalj.tac.fpcf.analyses.ide.solver.JavaStatement + +/** + * Specialized property meta information for IFDS problems with Java programs. + * + * @author Robin Körkemeier + */ +trait JavaIFDSPropertyMetaInformation[Fact <: IDEFact] extends IFDSPropertyMetaInformation[Fact, JavaStatement, Method] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/ifds/problem/JavaIFDSProblem.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/ifds/problem/JavaIFDSProblem.scala new file mode 100644 index 0000000000..45312a848e --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/ifds/problem/JavaIFDSProblem.scala @@ -0,0 +1,20 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package ide +package ifds +package problem + +import org.opalj.br.Method +import org.opalj.ide.ifds.problem.IFDSProblem +import org.opalj.ide.problem.IDEFact +import org.opalj.tac.fpcf.analyses.ide.solver.JavaStatement + +/** + * Specialized IFDS problem for Java programs based on an IDE problem. + * + * @author Robin Körkemeier + */ +abstract class JavaIFDSProblem[Fact <: IDEFact] extends IFDSProblem[Fact, JavaStatement, Method] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/LCPOnFieldsAnalysisScheduler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/LCPOnFieldsAnalysisScheduler.scala new file mode 100644 index 0000000000..a2db7cd028 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/LCPOnFieldsAnalysisScheduler.scala @@ -0,0 +1,53 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package ide +package instances +package lcp_on_fields + +import scala.collection.immutable.Set + +import org.opalj.br.analyses.DeclaredFieldsKey +import org.opalj.br.analyses.ProjectInformationKeys +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.properties.immutability.FieldAssignability +import org.opalj.fpcf.PropertyBounds +import org.opalj.tac.fpcf.analyses.ide.instances.lcp_on_fields.problem.LCPOnFieldsFact +import org.opalj.tac.fpcf.analyses.ide.instances.lcp_on_fields.problem.LCPOnFieldsProblem +import org.opalj.tac.fpcf.analyses.ide.instances.lcp_on_fields.problem.LCPOnFieldsValue +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.LinearConstantPropagationPropertyMetaInformation +import org.opalj.tac.fpcf.analyses.ide.integration.JavaIDEAnalysisScheduler +import org.opalj.tac.fpcf.analyses.ide.integration.JavaIDEAnalysisSchedulerBase +import org.opalj.tac.fpcf.analyses.ide.integration.JavaIDEPropertyMetaInformation +import org.opalj.tac.fpcf.analyses.ide.problem.JavaIDEProblem +import org.opalj.tac.fpcf.analyses.ide.solver.JavaICFG + +/** + * Linear constant propagation on fields as IDE analysis. This implementation is mainly intended to be an example of a + * cyclic IDE analysis (see [[org.opalj.tac.fpcf.analyses.ide.instances.lcp_on_fields.problem.LCPOnFieldsProblem]]). + * + * @author Robin Körkemeier + */ +class LCPOnFieldsAnalysisScheduler extends JavaIDEAnalysisScheduler[LCPOnFieldsFact, LCPOnFieldsValue] + with JavaIDEAnalysisSchedulerBase.ForwardICFG { + override def propertyMetaInformation: JavaIDEPropertyMetaInformation[LCPOnFieldsFact, LCPOnFieldsValue] = + LCPOnFieldsPropertyMetaInformation + + override def createProblem(project: SomeProject, icfg: JavaICFG): JavaIDEProblem[ + LCPOnFieldsFact, + LCPOnFieldsValue + ] = { + new LCPOnFieldsProblem(project, icfg) + } + + override def requiredProjectInformation: ProjectInformationKeys = + super.requiredProjectInformation :+ DeclaredFieldsKey + + override def uses: Set[PropertyBounds] = + super.uses ++ Set( + PropertyBounds.ub(FieldAssignability), + PropertyBounds.ub(LinearConstantPropagationPropertyMetaInformation) + ) +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/LCPOnFieldsPropertyMetaInformation.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/LCPOnFieldsPropertyMetaInformation.scala new file mode 100644 index 0000000000..1558b27d6b --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/LCPOnFieldsPropertyMetaInformation.scala @@ -0,0 +1,23 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package ide +package instances +package lcp_on_fields + +import org.opalj.fpcf.PropertyKey +import org.opalj.tac.fpcf.analyses.ide.instances.lcp_on_fields.problem.LCPOnFieldsFact +import org.opalj.tac.fpcf.analyses.ide.instances.lcp_on_fields.problem.LCPOnFieldsValue +import org.opalj.tac.fpcf.analyses.ide.integration.JavaIDEPropertyMetaInformation + +/** + * Meta information for linear constant propagation on fields. + * + * @author Robin Körkemeier + */ +object LCPOnFieldsPropertyMetaInformation + extends JavaIDEPropertyMetaInformation[LCPOnFieldsFact, LCPOnFieldsValue] { + final val key: PropertyKey[Self] = PropertyKey.create("opalj.ide.LinearConstantPropagationOnFields") +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/LinearConstantPropagationAnalysisSchedulerExtended.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/LinearConstantPropagationAnalysisSchedulerExtended.scala new file mode 100644 index 0000000000..ef9215d0b8 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/LinearConstantPropagationAnalysisSchedulerExtended.scala @@ -0,0 +1,45 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package ide +package instances +package lcp_on_fields + +import org.opalj.br.analyses.SomeProject +import org.opalj.fpcf.PropertyBounds +import org.opalj.tac.fpcf.analyses.ide.instances.lcp_on_fields.problem.LinearConstantPropagationProblemExtended +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.LinearConstantPropagationPropertyMetaInformation +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.problem.LinearConstantPropagationFact +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.problem.LinearConstantPropagationValue +import org.opalj.tac.fpcf.analyses.ide.integration.JavaIDEAnalysisScheduler +import org.opalj.tac.fpcf.analyses.ide.integration.JavaIDEAnalysisSchedulerBase +import org.opalj.tac.fpcf.analyses.ide.integration.JavaIDEPropertyMetaInformation +import org.opalj.tac.fpcf.analyses.ide.problem.JavaIDEProblem +import org.opalj.tac.fpcf.analyses.ide.solver.JavaICFG + +/** + * Extended linear constant propagation as IDE analysis, trying to resolve field accesses with the LCP on fields + * analysis (see [[LCPOnFieldsAnalysisScheduler]]). + * + * @author Robin Körkemeier + */ +class LinearConstantPropagationAnalysisSchedulerExtended + extends JavaIDEAnalysisScheduler[LinearConstantPropagationFact, LinearConstantPropagationValue] + with JavaIDEAnalysisSchedulerBase.ForwardICFG { + override def propertyMetaInformation: JavaIDEPropertyMetaInformation[ + LinearConstantPropagationFact, + LinearConstantPropagationValue + ] = LinearConstantPropagationPropertyMetaInformation + + override def createProblem(project: SomeProject, icfg: JavaICFG): JavaIDEProblem[ + LinearConstantPropagationFact, + LinearConstantPropagationValue + ] = { + new LinearConstantPropagationProblemExtended + } + + override def uses: Set[PropertyBounds] = + super.uses + PropertyBounds.ub(LCPOnFieldsPropertyMetaInformation) +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/problem/LCPOnFieldsEdgeFunctions.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/problem/LCPOnFieldsEdgeFunctions.scala new file mode 100644 index 0000000000..91aaf8fd85 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/problem/LCPOnFieldsEdgeFunctions.scala @@ -0,0 +1,546 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package ide +package instances +package lcp_on_fields +package problem + +import scala.collection.immutable.Map + +import org.opalj.ide.problem.AllBottomEdgeFunction +import org.opalj.ide.problem.AllTopEdgeFunction +import org.opalj.ide.problem.EdgeFunction +import org.opalj.ide.problem.IdentityEdgeFunction +import org.opalj.ide.problem.IDEValue +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.problem.LinearConstantPropagationLattice +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.problem.LinearConstantPropagationValue + +/** + * Edge function holding the current object state (in form of its field-value mapping). + * + * @author Robin Körkemeier + */ +class ObjectEdgeFunction( + val values: Map[String, LinearConstantPropagationValue] +) extends EdgeFunction[LCPOnFieldsValue] { + override def compute[V >: LCPOnFieldsValue](sourceValue: V): V = { + sourceValue match { + case UnknownValue => UnknownValue + case ObjectValue(values2) => ObjectValue((values -- values2.keys) ++ values2) + case VariableValue => ObjectValue(values) + + case _ => + throw new UnsupportedOperationException(s"Computing $this for $sourceValue is not implemented!") + } + } + + override def composeWith[V >: LCPOnFieldsValue <: IDEValue]( + secondEdgeFunction: EdgeFunction[V] + ): EdgeFunction[V] = { + secondEdgeFunction match { + case ObjectEdgeFunction(values2) => + ObjectEdgeFunction((values -- values2.keys) ++ values2) + + case PutFieldEdgeFunction(fieldName, value) => + ObjectEdgeFunction((values - fieldName) + (fieldName -> value)) + + case IdentityEdgeFunction => this + case _: AllTopEdgeFunction[V] => secondEdgeFunction + case _: AllBottomEdgeFunction[V] => secondEdgeFunction + + case _ => + throw new UnsupportedOperationException(s"Composing $this with $secondEdgeFunction is not implemented!") + } + } + + override def meet[V >: LCPOnFieldsValue <: IDEValue](otherEdgeFunction: EdgeFunction[V]): EdgeFunction[V] = { + otherEdgeFunction match { + case ObjectEdgeFunction(values2) => + ObjectEdgeFunction( + values.keySet + .union(values2.keySet) + .map { fieldName => + fieldName -> LinearConstantPropagationLattice.meet( + values.getOrElse(fieldName, linear_constant_propagation.problem.VariableValue), + values2.getOrElse(fieldName, linear_constant_propagation.problem.VariableValue) + ) + } + .toMap + ) + + case PutFieldEdgeFunction(fieldName, value) => + ObjectEdgeFunction( + (values - fieldName).map { case (fieldName2, _) => + fieldName2 -> linear_constant_propagation.problem.VariableValue + } + + (fieldName -> LinearConstantPropagationLattice.meet( + value, + values.getOrElse(fieldName, linear_constant_propagation.problem.VariableValue) + )) + ) + + case IdentityEdgeFunction => this + case _: AllTopEdgeFunction[V] => this + case _: AllBottomEdgeFunction[V] => otherEdgeFunction + + case _ => + throw new UnsupportedOperationException(s"Meeting $this with $otherEdgeFunction is not implemented!") + } + } + + override def equals[V >: LCPOnFieldsValue <: IDEValue](otherEdgeFunction: EdgeFunction[V]): Boolean = { + (otherEdgeFunction eq this) || + (otherEdgeFunction match { + case ObjectEdgeFunction(values2) => values == values2 + case _ => false + }) + } + + override def toString: String = + s"ObjectEdgeFunction(${values.toSeq.sortBy(_._1).map { case (fieldName, value) => s"$fieldName -> $value" }.mkString(", ")})" +} + +object ObjectEdgeFunction { + def apply(values: Map[String, LinearConstantPropagationValue]): ObjectEdgeFunction = { + new ObjectEdgeFunction(values) + } + + def unapply(objectEdgeFunction: ObjectEdgeFunction): Some[Map[String, LinearConstantPropagationValue]] = { + Some(objectEdgeFunction.values) + } +} + +/** + * Edge function for initializing an object. + * + * @author Robin Körkemeier + */ +case object NewObjectEdgeFunction extends ObjectEdgeFunction(Map.empty) { + override def toString: String = "NewObjectEdgeFunction()" +} + +/** + * Edge function modeling the effect of writing the field of an object. + * + * @author Robin Körkemeier + */ +case class PutFieldEdgeFunction( + fieldName: String, + value: LinearConstantPropagationValue +) extends EdgeFunction[LCPOnFieldsValue] { + override def compute[V >: LCPOnFieldsValue](sourceValue: V): V = { + sourceValue match { + case UnknownValue => UnknownValue + case ObjectValue(values) => ObjectValue((values - fieldName) + (fieldName -> value)) + case VariableValue => VariableValue + } + } + + override def composeWith[V >: LCPOnFieldsValue <: IDEValue]( + secondEdgeFunction: EdgeFunction[V] + ): EdgeFunction[V] = { + secondEdgeFunction match { + case ObjectEdgeFunction(values) if values.contains(fieldName) => + ObjectEdgeFunction((values - fieldName) + (fieldName -> value)) + case ObjectEdgeFunction(values) => + ObjectEdgeFunction(values + (fieldName -> value)) + + case PutFieldEdgeFunction(fieldName2, _) if fieldName == fieldName2 => secondEdgeFunction + case PutFieldEdgeFunction(fieldName2, value2) => + ObjectEdgeFunction(Map(fieldName -> value, fieldName2 -> value2)) + + case IdentityEdgeFunction => this + case _: AllTopEdgeFunction[V] => secondEdgeFunction + case _: AllBottomEdgeFunction[V] => secondEdgeFunction + + case _ => + throw new UnsupportedOperationException(s"Composing $this with $secondEdgeFunction is not implemented!") + } + } + + override def meet[V >: LCPOnFieldsValue <: IDEValue](otherEdgeFunction: EdgeFunction[V]): EdgeFunction[V] = { + otherEdgeFunction match { + case ObjectEdgeFunction(values2) => + ObjectEdgeFunction( + (values2 - fieldName).map { case (fieldName2, _) => + fieldName2 -> linear_constant_propagation.problem.VariableValue + } + + (fieldName -> LinearConstantPropagationLattice.meet( + value, + values2.getOrElse(fieldName, linear_constant_propagation.problem.VariableValue) + )) + ) + + case PutFieldEdgeFunction(fieldName2, value2) if fieldName == fieldName2 => + PutFieldEdgeFunction(fieldName, LinearConstantPropagationLattice.meet(value, value2)) + case PutFieldEdgeFunction(fieldName2, _) => + ObjectEdgeFunction(Map( + fieldName -> linear_constant_propagation.problem.VariableValue, + fieldName2 -> linear_constant_propagation.problem.VariableValue + )) + + case IdentityEdgeFunction => this + case _: AllTopEdgeFunction[V] => this + case _: AllBottomEdgeFunction[V] => otherEdgeFunction + + case _ => + throw new UnsupportedOperationException(s"Meeting $this with $otherEdgeFunction is not implemented!") + } + } + + override def equals[V >: LCPOnFieldsValue <: IDEValue](otherEdgeFunction: EdgeFunction[V]): Boolean = { + (otherEdgeFunction eq this) || + (otherEdgeFunction match { + case PutFieldEdgeFunction(fieldName2, value2) => fieldName == fieldName2 && value == value2 + case _ => false + }) + } +} + +/** + * Edge function holding the current array state. An array is represented as an initial value and a collection of + * elements. The array length is not tracked in this problem definition, thus arbitrary indices can be read and written. + * The initial value is used as a fallback/default value for elements that are not in the collection of elements yet + * (will likely be one of `ConstantValue(0)` and `VariableValue`). + * + * @author Robin Körkemeier + */ +class ArrayEdgeFunction( + val initValue: LinearConstantPropagationValue, + val elements: Map[Int, LinearConstantPropagationValue] +) extends EdgeFunction[LCPOnFieldsValue] { + override def compute[V >: LCPOnFieldsValue](sourceValue: V): V = { + sourceValue match { + case UnknownValue => UnknownValue + case ArrayValue(initValue2, elements2) => ArrayValue(initValue2, (elements -- elements2.keys) ++ elements2) + case VariableValue => ArrayValue(initValue, elements) + + case _ => + throw new UnsupportedOperationException(s"Computing $this for $sourceValue is not implemented!") + } + } + + override def composeWith[V >: LCPOnFieldsValue <: IDEValue]( + secondEdgeFunction: EdgeFunction[V] + ): EdgeFunction[V] = { + secondEdgeFunction match { + case NewArrayEdgeFunction(_) => secondEdgeFunction + + case ArrayEdgeFunction(initValue2, elements2) => + ArrayEdgeFunction(initValue2, (elements -- elements2.keys) ++ elements2) + + case PutElementEdgeFunction(index, value) => + index match { + case linear_constant_propagation.problem.UnknownValue => + /* In this case it is unknown which indices will be affected */ + ArrayEdgeFunction(linear_constant_propagation.problem.UnknownValue) + case linear_constant_propagation.problem.ConstantValue(i) => + ArrayEdgeFunction(initValue, (elements - i) + (i -> value)) + case linear_constant_propagation.problem.VariableValue => + /* In this case any index could be affected */ + ArrayEdgeFunction(linear_constant_propagation.problem.VariableValue) + } + + case IdentityEdgeFunction => this + case _: AllTopEdgeFunction[V] => secondEdgeFunction + case _: AllBottomEdgeFunction[V] => secondEdgeFunction + + case _ => + throw new UnsupportedOperationException(s"Composing $this with $secondEdgeFunction is not implemented!") + } + } + + override def meet[V >: LCPOnFieldsValue <: IDEValue](otherEdgeFunction: EdgeFunction[V]): EdgeFunction[V] = { + otherEdgeFunction match { + case ArrayEdgeFunction(initValue2, elements2) => + ArrayEdgeFunction( + LinearConstantPropagationLattice.meet(initValue, initValue2), + elements.keySet.union(elements2.keySet) + .map { index => + index -> LinearConstantPropagationLattice.meet( + elements.getOrElse(index, initValue), + elements2.getOrElse(index, initValue2) + ) + } + .toMap + ) + + case PutElementEdgeFunction(index, value) => + index match { + case linear_constant_propagation.problem.UnknownValue => + ArrayEdgeFunction(linear_constant_propagation.problem.UnknownValue) + case linear_constant_propagation.problem.ConstantValue(i) => + ArrayEdgeFunction( + linear_constant_propagation.problem.VariableValue, + Map(i -> LinearConstantPropagationLattice.meet(value, elements.getOrElse(i, initValue))) + ) + case linear_constant_propagation.problem.VariableValue => + ArrayEdgeFunction(linear_constant_propagation.problem.VariableValue) + } + + case IdentityEdgeFunction => this + case _: AllTopEdgeFunction[V] => this + case _: AllBottomEdgeFunction[V] => otherEdgeFunction + + case _ => + throw new UnsupportedOperationException(s"Meeting $this with $otherEdgeFunction is not implemented!") + } + } + + override def equals[V >: LCPOnFieldsValue <: IDEValue](otherEdgeFunction: EdgeFunction[V]): Boolean = { + (otherEdgeFunction eq this) || + (otherEdgeFunction match { + case ArrayEdgeFunction(initValue2, elements2) => initValue == initValue2 && elements == elements2 + case _ => false + }) + } + + override def toString: String = + s"ArrayEdgeFunction($initValue, ${elements.toSeq.sortBy(_._1).map { + case (index, value) => s"$index -> $value" + }.mkString(", ")})" +} + +object ArrayEdgeFunction { + def apply( + initValue: LinearConstantPropagationValue, + elements: Map[Int, LinearConstantPropagationValue] = Map.empty + ): ArrayEdgeFunction = { + new ArrayEdgeFunction(initValue, elements) + } + + def unapply(arrayEdgeFunction: ArrayEdgeFunction): Some[( + LinearConstantPropagationValue, + Map[Int, LinearConstantPropagationValue] + )] = { + Some((arrayEdgeFunction.initValue, arrayEdgeFunction.elements)) + } +} + +/** + * Edge function for initializing an array. + * + * @author Robin Körkemeier + */ +case class NewArrayEdgeFunction( + override val initValue: LinearConstantPropagationValue = linear_constant_propagation.problem.ConstantValue(0) +) extends ArrayEdgeFunction(initValue, Map.empty) + +/** + * Edge function modeling the effect of writing an element of an array. + * + * @author Robin Körkemeier + */ +case class PutElementEdgeFunction( + index: LinearConstantPropagationValue, + value: LinearConstantPropagationValue +) extends EdgeFunction[LCPOnFieldsValue] { + override def compute[V >: LCPOnFieldsValue](sourceValue: V): V = { + sourceValue match { + case UnknownValue => UnknownValue + case ArrayValue(initValue, elements) => + index match { + case linear_constant_propagation.problem.UnknownValue => + ArrayValue(linear_constant_propagation.problem.UnknownValue, Map.empty) + case linear_constant_propagation.problem.ConstantValue(i) => + ArrayValue(initValue, (elements - i) + (i -> value)) + case linear_constant_propagation.problem.VariableValue => + ArrayValue(linear_constant_propagation.problem.VariableValue, Map.empty) + } + case VariableValue => VariableValue + + case _ => + throw new UnsupportedOperationException(s"Computing $this for $sourceValue is not implemented!") + } + } + + override def composeWith[V >: LCPOnFieldsValue <: IDEValue]( + secondEdgeFunction: EdgeFunction[V] + ): EdgeFunction[V] = { + secondEdgeFunction match { + case NewArrayEdgeFunction(_) => secondEdgeFunction + + case ArrayEdgeFunction(initValue, elements) => + index match { + case linear_constant_propagation.problem.UnknownValue => + ArrayEdgeFunction(linear_constant_propagation.problem.UnknownValue) + case linear_constant_propagation.problem.ConstantValue(i) => + ArrayEdgeFunction( + initValue, + (elements - i) + (i -> LinearConstantPropagationLattice.meet( + value, + elements.getOrElse(i, initValue) + )) + ) + case linear_constant_propagation.problem.VariableValue => + ArrayEdgeFunction(linear_constant_propagation.problem.VariableValue) + } + + case PutElementEdgeFunction(index2, _) if index == index2 => secondEdgeFunction + case PutElementEdgeFunction(_, _) => + ArrayEdgeFunction(linear_constant_propagation.problem.UnknownValue) + .composeWith(this).composeWith(secondEdgeFunction) + + case IdentityEdgeFunction => this + case _: AllTopEdgeFunction[V] => secondEdgeFunction + case _: AllBottomEdgeFunction[V] => secondEdgeFunction + + case _ => + throw new UnsupportedOperationException(s"Composing $this with $secondEdgeFunction is not implemented!") + } + } + + override def meet[V >: LCPOnFieldsValue <: IDEValue](otherEdgeFunction: EdgeFunction[V]): EdgeFunction[V] = { + otherEdgeFunction match { + case ArrayEdgeFunction(initValue, elements) => + index match { + case linear_constant_propagation.problem.UnknownValue => + ArrayEdgeFunction(linear_constant_propagation.problem.UnknownValue) + case linear_constant_propagation.problem.ConstantValue(i) => + ArrayEdgeFunction( + linear_constant_propagation.problem.VariableValue, + Map(i -> LinearConstantPropagationLattice.meet(value, elements.getOrElse(i, initValue))) + ) + case linear_constant_propagation.problem.VariableValue => + ArrayEdgeFunction(linear_constant_propagation.problem.VariableValue) + } + + case PutElementEdgeFunction(index2, value2) => + PutElementEdgeFunction( + LinearConstantPropagationLattice.meet(index, index2), + LinearConstantPropagationLattice.meet(value, value2) + ) + + case IdentityEdgeFunction => this + case _: AllTopEdgeFunction[V] => this + case _: AllBottomEdgeFunction[V] => otherEdgeFunction + + case _ => + throw new UnsupportedOperationException(s"Meeting $this with $otherEdgeFunction is not implemented!") + } + } + + override def equals[V >: LCPOnFieldsValue <: IDEValue](otherEdgeFunction: EdgeFunction[V]): Boolean = { + (otherEdgeFunction eq this) || + (otherEdgeFunction match { + case PutElementEdgeFunction(index2, value2) => index == index2 && value == value2 + case _ => false + }) + } +} + +/** + * Edge function modeling the effect of when a static field gets written. + * + * @author Robin Körkemeier + */ +case class PutStaticFieldEdgeFunction( + value: LinearConstantPropagationValue +) extends EdgeFunction[LCPOnFieldsValue] { + override def compute[V >: LCPOnFieldsValue](sourceValue: V): V = { + StaticFieldValue(value) + } + + override def composeWith[V >: LCPOnFieldsValue <: IDEValue]( + secondEdgeFunction: EdgeFunction[V] + ): EdgeFunction[V] = { + secondEdgeFunction match { + case PutStaticFieldEdgeFunction(_) => secondEdgeFunction + + case IdentityEdgeFunction => this + case _: AllTopEdgeFunction[V] => secondEdgeFunction + case _: AllBottomEdgeFunction[V] => secondEdgeFunction + + case _ => + throw new UnsupportedOperationException(s"Composing $this with $secondEdgeFunction is not implemented!") + } + } + + override def meet[V >: LCPOnFieldsValue <: IDEValue](otherEdgeFunction: EdgeFunction[V]): EdgeFunction[V] = { + otherEdgeFunction match { + case PutStaticFieldEdgeFunction(value2) => + PutStaticFieldEdgeFunction( + LinearConstantPropagationLattice.meet(value, value2) + ) + + case IdentityEdgeFunction => this + case _: AllTopEdgeFunction[V] => this + case _: AllBottomEdgeFunction[V] => otherEdgeFunction + + case _ => + throw new UnsupportedOperationException(s"Meeting $this with $otherEdgeFunction is not implemented!") + } + } + + override def equals[V >: LCPOnFieldsValue <: IDEValue](otherEdgeFunction: EdgeFunction[V]): Boolean = { + (otherEdgeFunction eq this) || + (otherEdgeFunction match { + case PutStaticFieldEdgeFunction(value2) => value == value2 + case _ => false + }) + } +} + +/** + * Edge function for cases where a value is unknown. + * + * @author Robin Körkemeier + */ +object UnknownValueEdgeFunction extends AllTopEdgeFunction[LCPOnFieldsValue](UnknownValue) { + override def composeWith[V >: LCPOnFieldsValue <: IDEValue]( + secondEdgeFunction: EdgeFunction[V] + ): EdgeFunction[V] = { + secondEdgeFunction match { + case ObjectEdgeFunction(_) => secondEdgeFunction + case PutFieldEdgeFunction(fieldName, value) => ObjectEdgeFunction(Map(fieldName -> value)) + case ArrayEdgeFunction(_, _) => secondEdgeFunction + case PutElementEdgeFunction(_, _) => ArrayEdgeFunction(linear_constant_propagation.problem.UnknownValue) + case PutStaticFieldEdgeFunction(_) => secondEdgeFunction + + case VariableValueEdgeFunction => secondEdgeFunction + case UnknownValueEdgeFunction => secondEdgeFunction + + case IdentityEdgeFunction => this + case _: AllTopEdgeFunction[V] => this + + case _ => + throw new UnsupportedOperationException(s"Composing $this with $secondEdgeFunction is not implemented!") + } + } + + override def meet[V >: LCPOnFieldsValue <: IDEValue](otherEdgeFunction: EdgeFunction[V]): EdgeFunction[V] = { + otherEdgeFunction match { + case _: AllTopEdgeFunction[V] => this + case IdentityEdgeFunction => this + case _ => otherEdgeFunction + } + } + + override def equals[V >: LCPOnFieldsValue <: IDEValue](otherEdgeFunction: EdgeFunction[V]): Boolean = { + otherEdgeFunction eq this + } + + override def toString: String = "UnknownValueEdgeFunction()" +} + +/** + * Edge function for cases where a value is variable. + * + * @author Robin Körkemeier + */ +object VariableValueEdgeFunction extends AllBottomEdgeFunction[LCPOnFieldsValue](VariableValue) { + override def composeWith[V >: LCPOnFieldsValue <: IDEValue]( + secondEdgeFunction: EdgeFunction[V] + ): EdgeFunction[V] = { + secondEdgeFunction match { + case NewObjectEdgeFunction => secondEdgeFunction + case _ => this + } + } + + override def toString: String = "VariableValueEdgeFunction()" +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/problem/LCPOnFieldsFact.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/problem/LCPOnFieldsFact.scala new file mode 100644 index 0000000000..b7021f53ac --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/problem/LCPOnFieldsFact.scala @@ -0,0 +1,153 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package ide +package instances +package lcp_on_fields +package problem + +import org.opalj.br.ClassType +import org.opalj.ide.problem.IDEFact + +/** + * Type for modeling facts for linear constant propagation on fields. + * + * @author Robin Körkemeier + */ +trait LCPOnFieldsFact extends IDEFact + +/** + * Fact to use as null fact. + * + * @author Robin Körkemeier + */ +case object NullFact extends LCPOnFieldsFact + +/** + * Common type for different types of entities. + * + * @author Robin Körkemeier + */ +trait AbstractEntityFact extends LCPOnFieldsFact { + /** + * The name of the variable (e.g. `lv0`) + */ + val name: String + /** + * Where the variable is defined (used to uniquely identify a variable/variable fact) + */ + val definedAtIndex: Int + + def toObjectOrArrayFact: AbstractEntityFact +} + +/** + * Type for object facts. + * + * @author Robin Körkemeier + */ +trait AbstractObjectFact extends AbstractEntityFact { + def toObjectFact: ObjectFact = ObjectFact(name, definedAtIndex) + + override def toObjectOrArrayFact: AbstractEntityFact = toObjectFact +} + +/** + * Fact representing a seen object variable. + * + * @author Robin Körkemeier + */ +case class ObjectFact(name: String, definedAtIndex: Int) extends AbstractObjectFact { + override def toObjectFact: ObjectFact = this +} + +/** + * Fact representing a seen object variable and modeling that it gets initialized. + * + * @author Robin Körkemeier + */ +case class NewObjectFact(name: String, definedAtIndex: Int) extends AbstractObjectFact + +/** + * Fact representing a seen object variable and modeling that one of its fields gets written. + * + * @param fieldName the name of the field that gets written + * + * @author Robin Körkemeier + */ +case class PutFieldFact(name: String, definedAtIndex: Int, fieldName: String) extends AbstractObjectFact + +/** + * Type for array facts. + * + * @author Robin Körkemeier + */ +trait AbstractArrayFact extends AbstractEntityFact { + def toArrayFact: ArrayFact = ArrayFact(name, definedAtIndex) + + override def toObjectOrArrayFact: AbstractEntityFact = toArrayFact +} + +/** + * Fact representing a seen array variable. + * + * @author Robin Körkemeier + */ +case class ArrayFact(name: String, definedAtIndex: Int) extends AbstractArrayFact { + override def toArrayFact: ArrayFact = this +} + +/** + * Fact representing a seen array variable and modeling that it gets initialized. + * + * @author Robin Körkemeier + */ +case class NewArrayFact(name: String, definedAtIndex: Int) extends AbstractArrayFact + +/** + * Fact representing a seen array variable and modeling that one of its elements gets written. + * + * @author Robin Körkemeier + */ +case class PutElementFact(name: String, definedAtIndex: Int) extends AbstractArrayFact + +/** + * Type for facts for static fields. + * + * @author Robin Körkemeier + */ +trait AbstractStaticFieldFact extends LCPOnFieldsFact { + /** + * The class type the field belongs to + */ + val classType: ClassType + + /** + * The name of the field + */ + val fieldName: String + + def toStaticFieldFact: AbstractStaticFieldFact = StaticFieldFact(classType, fieldName) +} + +/** + * Fact representing a seen static field. + * + * @author Robin Körkemeier + */ +case class StaticFieldFact(classType: ClassType, fieldName: String) extends AbstractStaticFieldFact { + override def toStaticFieldFact: StaticFieldFact = this + + override def toString: String = s"StaticFieldFact(${classType.simpleName}, $fieldName)" +} + +/** + * Fact representing a seen static field and modeling that it gets written. + * + * @author Robin Körkemeier + */ +case class PutStaticFieldFact(classType: ClassType, fieldName: String) extends AbstractStaticFieldFact { + override def toString: String = s"PutStaticFieldFact(${classType.simpleName}, $fieldName)" +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/problem/LCPOnFieldsLattice.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/problem/LCPOnFieldsLattice.scala new file mode 100644 index 0000000000..5415a67643 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/problem/LCPOnFieldsLattice.scala @@ -0,0 +1,59 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package ide +package instances +package lcp_on_fields +package problem + +import org.opalj.ide.problem.MeetLattice +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.problem.LinearConstantPropagationLattice + +/** + * Lattice used for linear constant propagation on fields. + * + * @author Robin Körkemeier + */ +object LCPOnFieldsLattice extends MeetLattice[LCPOnFieldsValue] { + override def top: LCPOnFieldsValue = UnknownValue + + override def bottom: LCPOnFieldsValue = VariableValue + + override def meet(value1: LCPOnFieldsValue, value2: LCPOnFieldsValue): LCPOnFieldsValue = (value1, value2) match { + case (UnknownValue, _) => value2 + case (_, UnknownValue) => value1 + case (VariableValue, _) => VariableValue + case (_, VariableValue) => VariableValue + + case (ObjectValue(values1), ObjectValue(values2)) => + val values = values1.keySet + .intersect(values2.keySet) + .map { fieldName => + fieldName -> LinearConstantPropagationLattice.meet(values1(fieldName), values2(fieldName)) + } + .toMap + ObjectValue(values) + + case (ArrayValue(initValue1, elements1), ArrayValue(initValue2, elements2)) => + val elements = elements1.keySet + .union(elements2.keySet) + .map { index => + index -> LinearConstantPropagationLattice.meet( + elements1.getOrElse(index, initValue1), + elements2.getOrElse(index, initValue2) + ) + } + .toMap + ArrayValue( + LinearConstantPropagationLattice.meet(initValue1, initValue2), + elements + ) + + case (StaticFieldValue(staticValue1), StaticFieldValue(staticValue2)) => + StaticFieldValue(LinearConstantPropagationLattice.meet(staticValue1, staticValue2)) + + case _ => VariableValue + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/problem/LCPOnFieldsProblem.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/problem/LCPOnFieldsProblem.scala new file mode 100644 index 0000000000..0996df027c --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/problem/LCPOnFieldsProblem.scala @@ -0,0 +1,814 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package ide +package instances +package lcp_on_fields +package problem + +import scala.collection.immutable.Set +import scala.collection.mutable.{Set => MutableSet} + +import org.opalj.ai.isImplicitOrExternalException +import org.opalj.br.Field +import org.opalj.br.IntegerType +import org.opalj.br.Method +import org.opalj.br.analyses.DeclaredFieldsKey +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.properties.immutability.EffectivelyNonAssignable +import org.opalj.br.fpcf.properties.immutability.FieldAssignability +import org.opalj.br.fpcf.properties.immutability.LazilyInitialized +import org.opalj.br.fpcf.properties.immutability.NonAssignable +import org.opalj.fpcf.FinalP +import org.opalj.fpcf.InterimUBP +import org.opalj.fpcf.PropertyStore +import org.opalj.ide.problem.EdgeFunctionResult +import org.opalj.ide.problem.FinalEdgeFunction +import org.opalj.ide.problem.FlowFunction +import org.opalj.ide.problem.IdentityEdgeFunction +import org.opalj.ide.problem.InterimEdgeFunction +import org.opalj.ide.problem.MeetLattice +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.LinearConstantPropagationPropertyMetaInformation +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.problem.LinearConstantPropagationLattice +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.problem.LinearConstantPropagationValue +import org.opalj.tac.fpcf.analyses.ide.problem.JavaIDEProblem +import org.opalj.tac.fpcf.analyses.ide.solver.JavaICFG +import org.opalj.tac.fpcf.analyses.ide.solver.JavaStatement +import org.opalj.tac.fpcf.analyses.ide.solver.JavaStatement.StmtAsCall +import org.opalj.value.IsIntegerValue + +/** + * Definition of linear constant propagation on fields problem. This implementation detects basic cases of linear + * constant propagation involving fields. It detects direct field assignments but fails to detect assignments done in a + * method where the value is passed as an argument (e.g. a classical setter). Similar, array read accesses can only be + * resolved if the index is a constant literal. There also is just minimal support for static fields. + * This implementation is mainly intended to be an example of a cyclic IDE analysis. + * + * @author Robin Körkemeier + */ +class LCPOnFieldsProblem( + project: SomeProject, + icfg: JavaICFG +) extends JavaIDEProblem[LCPOnFieldsFact, LCPOnFieldsValue] { + private val declaredFields = project.get(DeclaredFieldsKey) + + override val nullFact: LCPOnFieldsFact = + NullFact + + override val lattice: MeetLattice[LCPOnFieldsValue] = + LCPOnFieldsLattice + + override def getAdditionalSeeds(stmt: JavaStatement, callee: Method)( + implicit propertyStore: PropertyStore + ): scala.collection.Set[LCPOnFieldsFact] = { + (if (callee.isStatic) { + Set.empty + } else { + /* Add fact for `this` */ + Set(ObjectFact("param0", -1)) + }) ++ + /* Add facts for other parameters */ + callee.parameterTypes + .iterator + .zipWithIndex + .collect { + case (paramType, index) if paramType.isClassType => ObjectFact(s"param${index + 1}", -(index + 2)) + case (paramType, index) if paramType.isClassType => ArrayFact(s"param${index + 1}", -(index + 2)) + } ++ + /* Add facts for static fields of class */ + callee.classFile + .fields + .collect { case field if field.isStatic => StaticFieldFact(field.classFile.thisType, field.name) } + } + + override def getAdditionalSeedsEdgeFunction(stmt: JavaStatement, fact: LCPOnFieldsFact, callee: Method)( + implicit propertyStore: PropertyStore + ): EdgeFunctionResult[LCPOnFieldsValue] = { + fact match { + case ObjectFact(_, _) => UnknownValueEdgeFunction + case ArrayFact(_, _) => ArrayEdgeFunction(linear_constant_propagation.problem.UnknownValue) + case f @ StaticFieldFact(_, _) => getEdgeFunctionForStaticFieldFactByImmutability(f) + case _ => super.getAdditionalSeedsEdgeFunction(stmt, fact, callee) + } + } + + private def getEdgeFunctionForStaticFieldFactByImmutability(staticFieldFact: AbstractStaticFieldFact)( + implicit propertyStore: PropertyStore + ): EdgeFunctionResult[LCPOnFieldsValue] = { + val declaredField = declaredFields(staticFieldFact.classType, staticFieldFact.fieldName, IntegerType) + if (!declaredField.isDefinedField) { + return PutStaticFieldEdgeFunction(linear_constant_propagation.problem.VariableValue) + } + val field = declaredField.definedField + + /* We enhance the analysis with immutability information. When a static field is immutable and we have knowledge + * of an assignment site, then this will always be the value of the field. This way we can make this analysis + * more precise without the need to add precise handling of static initializers. */ + val fieldAssignabilityEOptionP = propertyStore(field, FieldAssignability.key) + + fieldAssignabilityEOptionP match { + case FinalP(fieldAssignability) => + fieldAssignability match { + case NonAssignable | EffectivelyNonAssignable | LazilyInitialized => + val value = getValueForGetStaticExprByStaticInitializer(field) + FinalEdgeFunction(PutStaticFieldEdgeFunction(value)) + case _ => + FinalEdgeFunction(PutStaticFieldEdgeFunction(linear_constant_propagation.problem.VariableValue)) + } + + case InterimUBP(fieldAssignability) => + fieldAssignability match { + case NonAssignable | EffectivelyNonAssignable | LazilyInitialized => + val value = getValueForGetStaticExprByStaticInitializer(field) + InterimEdgeFunction(PutStaticFieldEdgeFunction(value), Set(fieldAssignabilityEOptionP)) + case _ => + FinalEdgeFunction(PutStaticFieldEdgeFunction(linear_constant_propagation.problem.VariableValue)) + } + + case _ => + InterimEdgeFunction( + PutStaticFieldEdgeFunction(linear_constant_propagation.problem.UnknownValue), + Set(fieldAssignabilityEOptionP) + ) + } + } + + private def getValueForGetStaticExprByStaticInitializer(field: Field): LinearConstantPropagationValue = { + var value: LinearConstantPropagationValue = linear_constant_propagation.problem.UnknownValue + + /* Search for statements that write the field in static initializer of the class belonging to the field. */ + field.classFile.staticInitializer match { + case Some(method) => + var workingStmts: Set[JavaStatement] = Set.from(icfg.getStartStatements(method)) + val seenStmts = MutableSet.empty[JavaStatement] + + while (workingStmts.nonEmpty) { + workingStmts.foreach { stmt => + stmt.stmt.astID match { + case PutStatic.ASTID => + val putStatic = stmt.stmt.asPutStatic + if (field.classFile.thisType == putStatic.declaringClass && + field.name == putStatic.name + ) { + stmt.stmt.asPutStatic.value.asVar.value match { + case v: IsIntegerValue => + v.constantValue match { + case Some(constantValue) => + value = LinearConstantPropagationLattice.meet( + value, + linear_constant_propagation.problem.ConstantValue(constantValue) + ) + case _ => return linear_constant_propagation.problem.VariableValue + } + + case _ => + return linear_constant_propagation.problem.VariableValue + } + } + + case _ => + } + } + + seenStmts.addAll(workingStmts) + workingStmts = workingStmts + .iterator + .map(icfg.getNextStatements) + .fold(Set.empty[JavaStatement]) { (nextStmts, stmts) => nextStmts ++ stmts } + .diff(seenStmts) + .toSet + } + + case _ => + } + + value match { + case linear_constant_propagation.problem.UnknownValue => + if (field.isFinal) { + linear_constant_propagation.problem.VariableValue + } else { + linear_constant_propagation.problem.ConstantValue(0) + } + case _ => value + } + } + + override def getNormalFlowFunction( + source: JavaStatement, + sourceFact: LCPOnFieldsFact, + target: JavaStatement + )(implicit propertyStore: PropertyStore): FlowFunction[LCPOnFieldsFact] = { + new FlowFunction[LCPOnFieldsFact] { + override def compute(): FactsAndDependees = { + (source.stmt.astID, sourceFact) match { + case (Assignment.ASTID, NullFact) => + val assignment = source.stmt.asAssignment + assignment.expr.astID match { + case New.ASTID => + /* Generate new object fact from null fact if assignment is a 'new' expression */ + Set(sourceFact, NewObjectFact(assignment.targetVar.name, source.pc)) + + case NewArray.ASTID => + /* Generate new array fact from null fact if assignment is a 'new array' expression for + * an integer array */ + if (assignment.expr.asNewArray.tpe.componentType.isIntegerType) { + Set(sourceFact, NewArrayFact(assignment.targetVar.name, source.pc)) + } else { + Set(sourceFact) + } + + case _ => Set(sourceFact) + } + + case (PutField.ASTID, f: AbstractObjectFact) => + val putField = source.stmt.asPutField + /* Only consider field assignments for integers */ + if (putField.declaredFieldType.isIntegerType) { + val targetObject = putField.objRef.asVar + if (targetObject.definedBy.contains(f.definedAtIndex)) { + /* Generate new (short-lived) fact to handle field assignment */ + Set(PutFieldFact(f.name, f.definedAtIndex, putField.name)) + } else { + Set(f.toObjectFact) + } + } else { + Set(f.toObjectFact) + } + + case (ArrayStore.ASTID, f: AbstractArrayFact) => + val arrayStore = source.stmt.asArrayStore + val arrayVar = arrayStore.arrayRef.asVar + if (arrayVar.definedBy.contains(f.definedAtIndex)) { + Set(PutElementFact(f.name, f.definedAtIndex)) + } else { + Set(f.toArrayFact) + } + + case (_, f: AbstractEntityFact) => + /* Specialized facts only live for one step and are turned back into basic ones afterwards */ + Set(f.toObjectOrArrayFact) + + /* Static fields are modeled such that statements that change their value can always originate from + * the null fact */ + case (PutStatic.ASTID, NullFact) => + val putStatic = source.stmt.asPutStatic + + /* Only fields of type integer */ + if (putStatic.declaredFieldType.isIntegerType) { + val declaredField = declaredFields(putStatic.declaringClass, putStatic.name, IntegerType) + if (!declaredField.isDefinedField) { + return Set(sourceFact) + } + val field = declaredField.definedField + + /* Only private fields (as they cannot be influenced by other static initializers) */ + if (field.isPrivate) { + Set(sourceFact, PutStaticFieldFact(putStatic.declaringClass, putStatic.name)) + } else { + Set(sourceFact) + } + } else { + Set(sourceFact) + } + + case (PutStatic.ASTID, f: AbstractStaticFieldFact) => + val putStatic = source.stmt.asPutStatic + + /* Drop existing fact if for the same static field */ + if (f.classType == putStatic.declaringClass && f.fieldName == putStatic.name) { + Set.empty + } else { + Set(f.toStaticFieldFact) + } + + case (_, f: AbstractStaticFieldFact) => + /* Specialized facts only live for one step and are turned back into basic ones afterwards */ + Set(f.toStaticFieldFact) + + case _ => Set(sourceFact) + } + } + } + } + + override def getCallFlowFunction( + callSite: JavaStatement, + callSiteFact: LCPOnFieldsFact, + calleeEntry: JavaStatement, + callee: Method + )(implicit propertyStore: PropertyStore): FlowFunction[LCPOnFieldsFact] = { + new FlowFunction[LCPOnFieldsFact] { + override def compute(): FactsAndDependees = { + callSiteFact match { + case NullFact => + /* Only propagate null fact if function returns an object or an array of integers */ + if (callee.returnType.isClassType) { + Set(callSiteFact) + } else if (callee.returnType.isArrayType && + callee.returnType.asArrayType.componentType.isIntegerType + ) { + Set(callSiteFact) + } else if (callee.classFile.fields.exists { field => field.isStatic } && + !callee.classFile.fqn.startsWith("java/") && + !callee.classFile.fqn.startsWith("sun/") + ) { + /* The null fact is needed for writing static fields */ + Set(callSiteFact) + } else { + Set.empty + } + + case f: AbstractEntityFact => + val callStmt = callSite.stmt.asCall() + + /* All parameters (including the implicit 'this' reference) */ + val allParams = callStmt.allParams + val staticCallIndexOffset = + if (callStmt.receiverOption.isEmpty) { 1 } + else { 0 } + + allParams + .iterator + .zipWithIndex + .collect { + /* Only parameters where the variable represented by the source fact is one possible + * initializer */ + case (param, index) if param.asVar.definedBy.contains(f.definedAtIndex) => + val adjustedIndex = index + staticCallIndexOffset + f match { + case _: AbstractObjectFact => + ObjectFact(s"param$adjustedIndex", -(adjustedIndex + 1)) + case _: AbstractArrayFact => + ArrayFact(s"param$adjustedIndex", -(adjustedIndex + 1)) + } + } + .toSet + + case f: AbstractStaticFieldFact => + /* Only propagate fact if the callee can access the corresponding static field */ + if (callee.classFile.thisType == f.classType) { + Set(f.toStaticFieldFact) + } else { + Set.empty + } + } + } + } + } + + override def getReturnFlowFunction( + calleeExit: JavaStatement, + calleeExitFact: LCPOnFieldsFact, + callee: Method, + returnSite: JavaStatement, + callSite: JavaStatement, + callSiteFact: LCPOnFieldsFact + )(implicit propertyStore: PropertyStore): FlowFunction[LCPOnFieldsFact] = { + new FlowFunction[LCPOnFieldsFact] { + override def compute(): FactsAndDependees = { + calleeExitFact match { + case NullFact => + /* Always propagate null fact */ + Set(calleeExitFact) + + case f: AbstractEntityFact => + val definedAtIndex = f.definedAtIndex + + if (isImplicitOrExternalException(definedAtIndex)) { + return Set.empty + } + + val callStmt = returnSite.stmt.asCall() + + val allParams = callStmt.allParams + val staticCallIndexOffset = + if (callStmt.receiverOption.isEmpty) { 1 } + else { 0 } + + /* Distinguish parameters and local variables */ + if (definedAtIndex < 0) { + val paramIndex = -definedAtIndex - 1 - staticCallIndexOffset + val param = allParams(paramIndex) + val paramName = param.asVar.name.substring(1, param.asVar.name.length - 1) + param.asVar.definedBy.map { dAI => + f match { + case _: AbstractObjectFact => ObjectFact(paramName, dAI) + case _: AbstractArrayFact => ArrayFact(paramName, dAI) + } + }.toSet + } else { + returnSite.stmt.astID match { + case Assignment.ASTID => + val assignment = returnSite.stmt.asAssignment + + val returnExpr = calleeExit.stmt.asReturnValue.expr + /* Only propagate if the variable represented by the source fact is one possible + * initializer of the variable at the return site */ + if (returnExpr.asVar.definedBy.contains(f.definedAtIndex)) { + Set(f match { + case _: AbstractObjectFact => + ObjectFact(assignment.targetVar.name, returnSite.pc) + case _: AbstractArrayFact => + ArrayFact(assignment.targetVar.name, returnSite.pc) + }) + } else { + Set.empty + } + + case _ => Set.empty + } + } + + case f: AbstractStaticFieldFact => + /* Only propagate fact if the caller can access the corresponding static field */ + if (returnSite.method.classFile.thisType == f.classType) { + Set(f.toStaticFieldFact) + } else { + Set.empty + } + } + } + } + } + + override def getCallToReturnFlowFunction( + callSite: JavaStatement, + callSiteFact: LCPOnFieldsFact, + callee: Method, + returnSite: JavaStatement + )(implicit propertyStore: PropertyStore): FlowFunction[LCPOnFieldsFact] = { + new FlowFunction[LCPOnFieldsFact] { + override def compute(): FactsAndDependees = { + val callStmt = returnSite.stmt.asCall() + + callSiteFact match { + case NullFact => + /* Always propagate null fact */ + Set(callSiteFact) + + case f: AbstractEntityFact => + /* Only propagate if the variable represented by the source fact is no initializer of one of the + * parameters */ + if (callStmt.allParams.exists { param => param.asVar.definedBy.contains(f.definedAtIndex) }) { + Set.empty + } else { + Set(f.toObjectOrArrayFact) + } + + case f: AbstractStaticFieldFact => + /* Propagate facts that are not propagated via the call flow */ + if (callee.classFile.thisType == f.classType) { + Set.empty + } else { + Set(f.toStaticFieldFact) + } + } + } + } + } + + override def getNormalEdgeFunction( + source: JavaStatement, + sourceFact: LCPOnFieldsFact, + target: JavaStatement, + targetFact: LCPOnFieldsFact + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[LCPOnFieldsValue] = { + if (sourceFact == targetFact) { + return FinalEdgeFunction(IdentityEdgeFunction) + } + + targetFact match { + case NewObjectFact(_, _) => + FinalEdgeFunction(NewObjectEdgeFunction) + + case PutFieldFact(_, _, fieldName) => + val valueVar = source.stmt.asPutField.value.asVar + + val lcpEOptionP = + propertyStore((source.method, source), LinearConstantPropagationPropertyMetaInformation.key) + + /* Decide based on the current result of the linear constant propagation analysis */ + lcpEOptionP match { + case FinalP(property) => + val value = getVariableFromProperty(valueVar)(property) + FinalEdgeFunction(PutFieldEdgeFunction(fieldName, value)) + + case InterimUBP(property) => + val value = getVariableFromProperty(valueVar)(property) + value match { + case linear_constant_propagation.problem.UnknownValue => + InterimEdgeFunction(PutFieldEdgeFunction(fieldName, value), Set(lcpEOptionP)) + case linear_constant_propagation.problem.ConstantValue(_) => + InterimEdgeFunction(PutFieldEdgeFunction(fieldName, value), Set(lcpEOptionP)) + case linear_constant_propagation.problem.VariableValue => + FinalEdgeFunction(PutFieldEdgeFunction(fieldName, value)) + } + + case _ => + InterimEdgeFunction( + PutFieldEdgeFunction(fieldName, linear_constant_propagation.problem.UnknownValue), + Set(lcpEOptionP) + ) + } + + case NewArrayFact(_, _) => + FinalEdgeFunction(NewArrayEdgeFunction()) + + case PutElementFact(_, _) => + val arrayStore = source.stmt.asArrayStore + val indexVar = arrayStore.index.asVar + val valueVar = arrayStore.value.asVar + + val lcpEOptionP = + propertyStore((source.method, source), LinearConstantPropagationPropertyMetaInformation.key) + + /* Decide based on the current result of the linear constant propagation analysis */ + lcpEOptionP match { + case FinalP(property) => + val index = getVariableFromProperty(indexVar)(property) + val value = getVariableFromProperty(valueVar)(property) + FinalEdgeFunction(PutElementEdgeFunction(index, value)) + + case InterimUBP(property) => + val index = getVariableFromProperty(indexVar)(property) + val value = getVariableFromProperty(valueVar)(property) + InterimEdgeFunction(PutElementEdgeFunction(index, value), Set(lcpEOptionP)) + + case _ => + InterimEdgeFunction( + PutElementEdgeFunction( + linear_constant_propagation.problem.UnknownValue, + linear_constant_propagation.problem.UnknownValue + ), + Set(lcpEOptionP) + ) + } + + case PutStaticFieldFact(_, _) => + val valueVar = source.stmt.asPutStatic.value.asVar + + val lcpEOptionP = + propertyStore((source.method, source), LinearConstantPropagationPropertyMetaInformation.key) + + /* Decide based on the current result of the linear constant propagation analysis */ + lcpEOptionP match { + case FinalP(property) => + val value = getVariableFromProperty(valueVar)(property) + FinalEdgeFunction(PutStaticFieldEdgeFunction(value)) + + case InterimUBP(property) => + val value = getVariableFromProperty(valueVar)(property) + value match { + case linear_constant_propagation.problem.UnknownValue => + InterimEdgeFunction(PutStaticFieldEdgeFunction(value), Set(lcpEOptionP)) + case linear_constant_propagation.problem.ConstantValue(_) => + InterimEdgeFunction(PutStaticFieldEdgeFunction(value), Set(lcpEOptionP)) + case linear_constant_propagation.problem.VariableValue => + FinalEdgeFunction(PutStaticFieldEdgeFunction(value)) + } + + case _ => + InterimEdgeFunction( + PutStaticFieldEdgeFunction(linear_constant_propagation.problem.UnknownValue), + Set(lcpEOptionP) + ) + } + + case _ => FinalEdgeFunction(IdentityEdgeFunction) + } + } + + private def getVariableFromProperty(var0: JavaStatement.V)( + property: LinearConstantPropagationPropertyMetaInformation.Self + ): LinearConstantPropagationValue = { + property + .results + .iterator + .collect { + case (linear_constant_propagation.problem.VariableFact(_, definedAtIndex), value) + if var0.definedBy.contains(definedAtIndex) => value + } + .fold(linear_constant_propagation.problem.UnknownValue: LinearConstantPropagationValue)( + LinearConstantPropagationLattice.meet + ) + } + + override def getCallEdgeFunction( + callSite: JavaStatement, + callSiteFact: LCPOnFieldsFact, + calleeEntry: JavaStatement, + calleeEntryFact: LCPOnFieldsFact, + callee: Method + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[LCPOnFieldsValue] = { + callSiteFact match { + case NullFact => UnknownValueEdgeFunction + case _ => IdentityEdgeFunction + } + } + + override def getReturnEdgeFunction( + calleeExit: JavaStatement, + calleeExitFact: LCPOnFieldsFact, + callee: Method, + returnSite: JavaStatement, + returnSiteFact: LCPOnFieldsFact, + callSite: JavaStatement, + callSiteFact: LCPOnFieldsFact + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[LCPOnFieldsValue] = IdentityEdgeFunction + + override def getCallToReturnEdgeFunction( + callSite: JavaStatement, + callSiteFact: LCPOnFieldsFact, + callee: Method, + returnSite: JavaStatement, + returnSiteFact: LCPOnFieldsFact + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[LCPOnFieldsValue] = IdentityEdgeFunction + + override def hasPrecomputedFlowAndSummaryFunction( + callSite: JavaStatement, + callSiteFact: LCPOnFieldsFact, + callee: Method + )(implicit propertyStore: PropertyStore): Boolean = { + if (callee.isNative || callee.body.isEmpty) { + return true + } + + super.hasPrecomputedFlowAndSummaryFunction(callSite, callSiteFact, callee) + } + + override def getPrecomputedFlowFunction( + callSite: JavaStatement, + callSiteFact: LCPOnFieldsFact, + callee: Method, + returnSite: JavaStatement + )(implicit propertyStore: PropertyStore): FlowFunction[LCPOnFieldsFact] = { + if (callee.isNative || callee.body.isEmpty) { + return new FlowFunction[LCPOnFieldsFact] { + override def compute(): FactsAndDependees = { + callSiteFact match { + case NullFact => + returnSite.stmt.astID match { + case Assignment.ASTID => + val assignment = returnSite.stmt.asAssignment + if (callee.returnType.isClassType) { + Set(NewObjectFact(assignment.targetVar.name, returnSite.pc)) + } else if (callee.returnType.isArrayType && + callee.returnType.asArrayType.componentType.isIntegerType + ) { + Set(NewArrayFact(assignment.targetVar.name, returnSite.pc)) + } else { + Set.empty + } + + case _ => Set.empty + } + + case f: AbstractEntityFact => + val callStmt = callSite.stmt.asCall() + + /* Check whether fact corresponds to one of the parameters */ + if (callStmt.allParams.exists { param => param.asVar.definedBy.contains(f.definedAtIndex) }) { + Set(f.toObjectOrArrayFact) + } else { + Set.empty + } + + case f: AbstractStaticFieldFact => + if (callee.classFile.thisType == f.classType) { + Set(f.toStaticFieldFact) + } else { + Set.empty + } + } + } + } + } + + super.getPrecomputedFlowFunction(callSite, callSiteFact, callee, returnSite) + } + + override def getPrecomputedSummaryFunction( + callSite: JavaStatement, + callSiteFact: LCPOnFieldsFact, + callee: Method, + returnSite: JavaStatement, + returnSiteFact: LCPOnFieldsFact + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[LCPOnFieldsValue] = { + if (callee.isNative || callee.body.isEmpty) { + return returnSiteFact match { + case NullFact => + IdentityEdgeFunction + + case _: AbstractObjectFact => + val callStmt = callSite.stmt.asCall() + + if (callStmt.declaringClass.isClassType && + callStmt.declaringClass.asClassType.fqn == "java/lang/Object" && callStmt.name == "" + ) { + IdentityEdgeFunction + } else { + /* It is unknown what the callee does with the object */ + VariableValueEdgeFunction + } + + case _: AbstractArrayFact => + NewArrayEdgeFunction(linear_constant_propagation.problem.VariableValue) + + case _: AbstractStaticFieldFact => + PutStaticFieldEdgeFunction(linear_constant_propagation.problem.VariableValue) + } + } + + super.getPrecomputedSummaryFunction(callSite, callSiteFact, callee, returnSite, returnSiteFact) + } + + override def getPrecomputedFlowFunction( + callSite: JavaStatement, + callSiteFact: LCPOnFieldsFact, + returnSite: JavaStatement + )(implicit propertyStore: PropertyStore): FlowFunction[LCPOnFieldsFact] = { + new FlowFunction[LCPOnFieldsFact] { + override def compute(): FactsAndDependees = { + callSiteFact match { + case NullFact => + returnSite.stmt.astID match { + case Assignment.ASTID => + val callStmt = callSite.stmt.asCall() + val assignment = returnSite.stmt.asAssignment + + if (callStmt.descriptor.returnType.isClassType) { + Set(callSiteFact, NewObjectFact(assignment.targetVar.name, returnSite.pc)) + } else if (callStmt.descriptor.returnType.isArrayType && + callStmt.descriptor.returnType.asArrayType.componentType.isIntegerType + ) { + Set(callSiteFact, NewArrayFact(assignment.targetVar.name, returnSite.pc)) + } else { + Set(callSiteFact) + } + + case _ => Set(callSiteFact) + } + + case f: AbstractEntityFact => + Set(f.toObjectOrArrayFact) + + case f: AbstractStaticFieldFact => + Set(f.toStaticFieldFact) + } + } + } + } + + override def getPrecomputedSummaryFunction( + callSite: JavaStatement, + callSiteFact: LCPOnFieldsFact, + returnSite: JavaStatement, + returnSiteFact: LCPOnFieldsFact + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[LCPOnFieldsValue] = { + val callStmt = callSite.stmt.asCall() + + (callSiteFact, returnSiteFact) match { + case (NullFact, _: AbstractObjectFact) => + VariableValueEdgeFunction + + case (NullFact, _: AbstractArrayFact) => + NewArrayEdgeFunction(linear_constant_propagation.problem.VariableValue) + + case (_: AbstractEntityFact, f: AbstractEntityFact) => + /* Constructor of object class doesn't modify the object */ + if (callStmt.declaringClass.isClassType && + callStmt.declaringClass.asClassType.fqn == "java/lang/Object" && callStmt.name == "" + ) { + IdentityEdgeFunction + } + /* Check whether fact corresponds to one of the parameters */ + else if (callStmt.allParams.exists { param => param.asVar.definedBy.contains(f.definedAtIndex) }) { + f match { + case _: AbstractObjectFact => VariableValueEdgeFunction + case _: AbstractEntityFact => + NewArrayEdgeFunction(linear_constant_propagation.problem.VariableValue) + } + } else { + IdentityEdgeFunction + } + + case (_, f: AbstractStaticFieldFact) => + val declaredField = declaredFields(f.classType, f.fieldName, IntegerType) + if (!declaredField.isDefinedField) { + return PutStaticFieldEdgeFunction(linear_constant_propagation.problem.VariableValue) + } + val field = declaredField.definedField + + if (callStmt.declaringClass != f.classType && field.isPrivate) { + IdentityEdgeFunction + } else { + getEdgeFunctionForStaticFieldFactByImmutability(f) + } + + case _ => IdentityEdgeFunction + } + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/problem/LCPOnFieldsValue.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/problem/LCPOnFieldsValue.scala new file mode 100644 index 0000000000..034a2ad791 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/problem/LCPOnFieldsValue.scala @@ -0,0 +1,69 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package ide +package instances +package lcp_on_fields +package problem + +import scala.collection.immutable.Map + +import org.opalj.ide.problem.IDEValue +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.problem.LinearConstantPropagationValue + +/** + * Type for modeling values for linear constant propagation on fields. + * + * @author Robin Körkemeier + */ +trait LCPOnFieldsValue extends IDEValue + +/** + * Value not known (yet). + * + * @author Robin Körkemeier + */ +case object UnknownValue extends LCPOnFieldsValue + +/** + * Value representing the state of an object. + * + * @author Robin Körkemeier + */ +case class ObjectValue( + values: Map[String, LinearConstantPropagationValue] +) extends LCPOnFieldsValue { + override def toString: String = + s"ObjectValue(${values.toSeq.sortBy(_._1).map { case (fieldName, value) => s"$fieldName -> $value" }.mkString(", ")})" +} + +/** + * Value representing the state of an array. + * + * @author Robin Körkemeier + */ +case class ArrayValue( + initValue: LinearConstantPropagationValue, + elements: Map[Int, LinearConstantPropagationValue] +) extends LCPOnFieldsValue { + override def toString: String = + s"ArrayValue($initValue, ${elements.toSeq.sortBy(_._1).map { case (index, value) => s"$index -> $value" }.mkString(", ")})" +} + +/** + * Value representing the value of a static field. + * + * @author Robin Körkemeier + */ +case class StaticFieldValue( + value: LinearConstantPropagationValue +) extends LCPOnFieldsValue + +/** + * Value is variable (not really used currently, mainly for completeness). + * + * @author Robin Körkemeier + */ +case object VariableValue extends LCPOnFieldsValue diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/problem/LinearConstantPropagationProblemExtended.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/problem/LinearConstantPropagationProblemExtended.scala new file mode 100644 index 0000000000..54a034840c --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/problem/LinearConstantPropagationProblemExtended.scala @@ -0,0 +1,246 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package ide +package instances +package lcp_on_fields +package problem + +import scala.collection.immutable.Set + +import org.opalj.br.ClassType +import org.opalj.fpcf.FinalP +import org.opalj.fpcf.InterimUBP +import org.opalj.fpcf.PropertyStore +import org.opalj.ide.problem.EdgeFunctionResult +import org.opalj.ide.problem.FinalEdgeFunction +import org.opalj.ide.problem.InterimEdgeFunction +import org.opalj.tac.fpcf.analyses.ide.instances.lcp_on_fields.LCPOnFieldsPropertyMetaInformation +import org.opalj.tac.fpcf.analyses.ide.instances.lcp_on_fields.problem.{VariableValue => LCPVariableValue} +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.problem.ConstantValue +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.problem.LinearCombinationEdgeFunction +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.problem.LinearConstantPropagationFact +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.problem.LinearConstantPropagationProblem +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.problem.LinearConstantPropagationValue +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.problem.NullFact +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.problem.UnknownValue +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.problem.UnknownValueEdgeFunction +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.problem.VariableFact +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.problem.VariableValue +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.problem.VariableValueEdgeFunction +import org.opalj.tac.fpcf.analyses.ide.solver.JavaStatement +import org.opalj.tac.fpcf.analyses.ide.solver.JavaStatement.V +import org.opalj.value.IsIntegerValue + +/** + * Extended definition of the linear constant propagation problem, trying to resolve field accesses with the LCP on + * fields analysis. + * + * @author Robin Körkemeier + */ +class LinearConstantPropagationProblemExtended extends LinearConstantPropagationProblem { + override def isArrayLoadExpressionGeneratedByFact( + arrayLoadExpr: ArrayLoad[V] + )( + source: JavaStatement, + sourceFact: LinearConstantPropagationFact, + target: JavaStatement + ): Boolean = { + val arrayVar = arrayLoadExpr.arrayRef.asVar + + /* Generate fact only if the variable represented by the source fact is one possible initializer of the index + * variable */ + sourceFact match { + case NullFact => + arrayVar.value.asReferenceValue.asReferenceType.asArrayType.componentType.isIntegerType + + case VariableFact(_, definedAtIndex) => + val indexVar = arrayLoadExpr.index.asVar + indexVar.definedBy.contains(definedAtIndex) && + arrayVar.value.asReferenceValue.asReferenceType.asArrayType.componentType.isIntegerType + } + } + + override def getNormalEdgeFunctionForArrayLoad( + arrayLoadExpr: ArrayLoad[JavaStatement.V] + )( + source: JavaStatement, + sourceFact: LinearConstantPropagationFact, + target: JavaStatement, + targetFact: LinearConstantPropagationFact + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[LinearConstantPropagationValue] = { + if (sourceFact == nullFact) { + return UnknownValueEdgeFunction + } + + val arrayVar = arrayLoadExpr.arrayRef.asVar + + val index = arrayLoadExpr.index.asVar.value match { + case v: IsIntegerValue => v.constantValue + case _ => None + } + + val lcpOnFieldsEOptionP = + propertyStore((source.method, source), LCPOnFieldsPropertyMetaInformation.key) + + lcpOnFieldsEOptionP match { + case FinalP(property) => + val value = getArrayElementFromProperty(arrayVar, index)(property) + FinalEdgeFunction(value match { + case UnknownValue => UnknownValueEdgeFunction + case ConstantValue(c) => LinearCombinationEdgeFunction(0, c, lattice.top) + case VariableValue => VariableValueEdgeFunction + }) + + case InterimUBP(property) => + val value = getArrayElementFromProperty(arrayVar, index)(property) + value match { + case UnknownValue => + InterimEdgeFunction(UnknownValueEdgeFunction, Set(lcpOnFieldsEOptionP)) + case ConstantValue(c) => + InterimEdgeFunction(LinearCombinationEdgeFunction(0, c, lattice.top), Set(lcpOnFieldsEOptionP)) + case VariableValue => + FinalEdgeFunction(VariableValueEdgeFunction) + } + + case _ => + InterimEdgeFunction(UnknownValueEdgeFunction, Set(lcpOnFieldsEOptionP)) + } + } + + private def getArrayElementFromProperty( + arrayVar: JavaStatement.V, + index: Option[Int] + )(property: LCPOnFieldsPropertyMetaInformation.Self): LinearConstantPropagationValue = { + property + .results + .iterator + .collect { + case (f: AbstractArrayFact, ArrayValue(initValue, values)) + if arrayVar.definedBy.contains(f.definedAtIndex) => + index match { + case Some(i) => values.getOrElse(i, initValue) + case None => + if (values.values.forall { v => v == initValue }) { + initValue + } else { + VariableValue + } + } + case (f: AbstractArrayFact, LCPVariableValue) if arrayVar.definedBy.contains(f.definedAtIndex) => + VariableValue + } + .fold(UnknownValue: LinearConstantPropagationValue)(lattice.meet) + } + + override def getNormalEdgeFunctionForGetField( + getFieldExpr: GetField[JavaStatement.V] + )( + source: JavaStatement, + sourceFact: LinearConstantPropagationFact, + target: JavaStatement, + targetFact: LinearConstantPropagationFact + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[LinearConstantPropagationValue] = { + val objectVar = getFieldExpr.objRef.asVar + val fieldName = getFieldExpr.name + + val lcpOnFieldsEOptionP = + propertyStore((source.method, source), LCPOnFieldsPropertyMetaInformation.key) + + /* Decide based on the current result of the LCP on fields analysis */ + lcpOnFieldsEOptionP match { + case FinalP(property) => + val value = getObjectFieldFromProperty(objectVar, fieldName)(property) + FinalEdgeFunction(value match { + case UnknownValue => UnknownValueEdgeFunction + case ConstantValue(c) => LinearCombinationEdgeFunction(0, c, lattice.top) + case VariableValue => VariableValueEdgeFunction + }) + + case InterimUBP(property) => + val value = getObjectFieldFromProperty(objectVar, fieldName)(property) + value match { + case UnknownValue => + InterimEdgeFunction(UnknownValueEdgeFunction, Set(lcpOnFieldsEOptionP)) + case ConstantValue(c) => + InterimEdgeFunction(LinearCombinationEdgeFunction(0, c, lattice.top), Set(lcpOnFieldsEOptionP)) + case VariableValue => + FinalEdgeFunction(VariableValueEdgeFunction) + } + + case _ => + InterimEdgeFunction(UnknownValueEdgeFunction, Set(lcpOnFieldsEOptionP)) + } + } + + private def getObjectFieldFromProperty( + objectVar: JavaStatement.V, + fieldName: String + )(property: LCPOnFieldsPropertyMetaInformation.Self): LinearConstantPropagationValue = { + property + .results + .iterator + .collect { + case (f: AbstractObjectFact, ObjectValue(values)) + if objectVar.definedBy.contains(f.definedAtIndex) && values.contains(fieldName) => values(fieldName) + case (f: AbstractObjectFact, LCPVariableValue) if objectVar.definedBy.contains(f.definedAtIndex) => + VariableValue + } + .fold(UnknownValue: LinearConstantPropagationValue)(lattice.meet) + } + + override def getNormalEdgeFunctionForGetStatic( + getStaticExpr: GetStatic + )( + source: JavaStatement, + sourceFact: LinearConstantPropagationFact, + target: JavaStatement, + targetFact: LinearConstantPropagationFact + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[LinearConstantPropagationValue] = { + val classType = getStaticExpr.declaringClass + val fieldName = getStaticExpr.name + + val lcpOnFieldsEOptionP = propertyStore((source.method, source), LCPOnFieldsPropertyMetaInformation.key) + + /* Decide based on the current result of the LCP on fields analysis */ + lcpOnFieldsEOptionP match { + case FinalP(property) => + FinalEdgeFunction(getStaticFieldFromProperty(classType, fieldName)(property) match { + case UnknownValue => UnknownValueEdgeFunction + case ConstantValue(c) => LinearCombinationEdgeFunction(0, c, lattice.top) + case VariableValue => VariableValueEdgeFunction + }) + + case InterimUBP(property) => + getStaticFieldFromProperty(classType, fieldName)(property) match { + case UnknownValue => + InterimEdgeFunction(UnknownValueEdgeFunction, Set(lcpOnFieldsEOptionP)) + case ConstantValue(c) => + InterimEdgeFunction(LinearCombinationEdgeFunction(0, c, lattice.top), Set(lcpOnFieldsEOptionP)) + case VariableValue => + FinalEdgeFunction(VariableValueEdgeFunction) + } + + case _ => + InterimEdgeFunction(UnknownValueEdgeFunction, Set(lcpOnFieldsEOptionP)) + } + } + + private def getStaticFieldFromProperty( + classType: ClassType, + fieldName: String + )(property: LCPOnFieldsPropertyMetaInformation.Self): LinearConstantPropagationValue = { + property + .results + .iterator + .collect { + case (f: AbstractStaticFieldFact, StaticFieldValue(value)) + if f.classType == classType && f.fieldName == fieldName => value + case (f: AbstractStaticFieldFact, LCPVariableValue) + if f.classType == classType && f.fieldName == fieldName => VariableValue + } + .fold(UnknownValue: LinearConstantPropagationValue)(lattice.meet) + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/LinearConstantPropagationAnalysisScheduler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/LinearConstantPropagationAnalysisScheduler.scala new file mode 100644 index 0000000000..482ea44024 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/LinearConstantPropagationAnalysisScheduler.scala @@ -0,0 +1,39 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package ide +package instances +package linear_constant_propagation + +import org.opalj.br.analyses.SomeProject +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.problem.LinearConstantPropagationFact +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.problem.LinearConstantPropagationProblem +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.problem.LinearConstantPropagationValue +import org.opalj.tac.fpcf.analyses.ide.integration.JavaIDEAnalysisScheduler +import org.opalj.tac.fpcf.analyses.ide.integration.JavaIDEAnalysisSchedulerBase +import org.opalj.tac.fpcf.analyses.ide.integration.JavaIDEPropertyMetaInformation +import org.opalj.tac.fpcf.analyses.ide.problem.JavaIDEProblem +import org.opalj.tac.fpcf.analyses.ide.solver.JavaICFG + +/** + * Linear constant propagation as IDE analysis. + * + * @author Robin Körkemeier + */ +class LinearConstantPropagationAnalysisScheduler + extends JavaIDEAnalysisScheduler[LinearConstantPropagationFact, LinearConstantPropagationValue] + with JavaIDEAnalysisSchedulerBase.ForwardICFG { + override def propertyMetaInformation: JavaIDEPropertyMetaInformation[ + LinearConstantPropagationFact, + LinearConstantPropagationValue + ] = LinearConstantPropagationPropertyMetaInformation + + override def createProblem(project: SomeProject, icfg: JavaICFG): JavaIDEProblem[ + LinearConstantPropagationFact, + LinearConstantPropagationValue + ] = { + new LinearConstantPropagationProblem + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/LinearConstantPropagationPropertyMetaInformation.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/LinearConstantPropagationPropertyMetaInformation.scala new file mode 100644 index 0000000000..ab3f7e4912 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/LinearConstantPropagationPropertyMetaInformation.scala @@ -0,0 +1,23 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package ide +package instances +package linear_constant_propagation + +import org.opalj.fpcf.PropertyKey +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.problem.LinearConstantPropagationFact +import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.problem.LinearConstantPropagationValue +import org.opalj.tac.fpcf.analyses.ide.integration.JavaIDEPropertyMetaInformation + +/** + * Meta information for linear constant propagation. + * + * @author Robin Körkemeier + */ +object LinearConstantPropagationPropertyMetaInformation + extends JavaIDEPropertyMetaInformation[LinearConstantPropagationFact, LinearConstantPropagationValue] { + final val key: PropertyKey[Self] = PropertyKey.create("opalj.ide.LinearConstantPropagation") +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/problem/LinearConstantPropagationEdgeFunctions.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/problem/LinearConstantPropagationEdgeFunctions.scala new file mode 100644 index 0000000000..f149c85033 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/problem/LinearConstantPropagationEdgeFunctions.scala @@ -0,0 +1,169 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package ide +package instances +package linear_constant_propagation +package problem + +import org.opalj.ide.problem.AllBottomEdgeFunction +import org.opalj.ide.problem.AllTopEdgeFunction +import org.opalj.ide.problem.EdgeFunction +import org.opalj.ide.problem.IdentityEdgeFunction +import org.opalj.ide.problem.IDEValue + +/** + * Edge function to calculate the value of a variable `i` for a statement `val i = a * x + b`. + * + * @author Robin Körkemeier + */ +case class LinearCombinationEdgeFunction( + a: Int, + b: Int, + c: LinearConstantPropagationValue = LinearConstantPropagationLattice.top +) extends EdgeFunction[LinearConstantPropagationValue] { + override def compute[V >: LinearConstantPropagationValue](sourceValue: V): V = { + LinearConstantPropagationLattice.meet( + sourceValue match { + case ConstantValue(x) => ConstantValue(a * x + b) + case VariableValue if a == 0 => ConstantValue(b) + case VariableValue => VariableValue + case UnknownValue => UnknownValue + }, + c + ) + } + + override def composeWith[V >: LinearConstantPropagationValue <: IDEValue]( + secondEdgeFunction: EdgeFunction[V] + ): EdgeFunction[V] = { + secondEdgeFunction match { + case LinearCombinationEdgeFunction(a2, b2, c2) => + LinearCombinationEdgeFunction( + a2 * a, + a2 * b + b2, + LinearConstantPropagationLattice.meet( + c match { + case UnknownValue => UnknownValue + case ConstantValue(cValue) => ConstantValue(a2 * cValue + b2) + case VariableValue => VariableValue + }, + c2 + ) + ) + + case VariableValueEdgeFunction => secondEdgeFunction + case UnknownValueEdgeFunction => secondEdgeFunction + + case IdentityEdgeFunction => this + case _: AllTopEdgeFunction[V] => secondEdgeFunction + + case _ => + throw new UnsupportedOperationException(s"Composing $this with $secondEdgeFunction is not implemented!") + } + } + + override def meet[V >: LinearConstantPropagationValue <: IDEValue]( + otherEdgeFunction: EdgeFunction[V] + ): EdgeFunction[V] = { + otherEdgeFunction match { + case LinearCombinationEdgeFunction(a2, b2, c2) if a2 == a && b2 == b => + LinearCombinationEdgeFunction(a, b, LinearConstantPropagationLattice.meet(c, c2)) + case LinearCombinationEdgeFunction(a2, b2, c2) if a2 != a && (b - b2) % (a2 - a) == 0 => + val cNew = LinearConstantPropagationLattice.meet( + ConstantValue(a * ((b - b2) / (a2 - a)) + b), + LinearConstantPropagationLattice.meet(c, c2) + ) + cNew match { + case VariableValue => VariableValueEdgeFunction + case _ => LinearCombinationEdgeFunction(a, b, cNew) + } + case LinearCombinationEdgeFunction(_, _, _) => + VariableValueEdgeFunction + + case VariableValueEdgeFunction => otherEdgeFunction + case UnknownValueEdgeFunction => this + + case IdentityEdgeFunction => this + case _: AllTopEdgeFunction[V] => this + + case _ => + throw new UnsupportedOperationException(s"Meeting $this with $otherEdgeFunction is not implemented!") + } + } + + override def equals[V >: LinearConstantPropagationValue <: IDEValue]( + otherEdgeFunction: EdgeFunction[V] + ): Boolean = { + (otherEdgeFunction eq this) || + (otherEdgeFunction match { + case LinearCombinationEdgeFunction(a2, b2, c2) => a == a2 && b == b2 && c == c2 + case _ => false + }) + } +} + +/** + * Edge function for variables whose value is unknown. + * + * @author Robin Körkemeier + */ +object UnknownValueEdgeFunction extends AllTopEdgeFunction[LinearConstantPropagationValue](UnknownValue) { + override def composeWith[V >: LinearConstantPropagationValue <: IDEValue]( + secondEdgeFunction: EdgeFunction[V] + ): EdgeFunction[V] = { + secondEdgeFunction match { + case LinearCombinationEdgeFunction(0, _, _) => secondEdgeFunction + case LinearCombinationEdgeFunction(_, _, VariableValue) => secondEdgeFunction + case LinearCombinationEdgeFunction(_, _, _) => this + + case VariableValueEdgeFunction => secondEdgeFunction + case UnknownValueEdgeFunction => secondEdgeFunction + + case IdentityEdgeFunction => this + case _: AllTopEdgeFunction[V] => this + + case _ => + throw new UnsupportedOperationException(s"Composing $this with $secondEdgeFunction is not implemented!") + } + } + + override def meet[V >: LinearConstantPropagationValue <: IDEValue]( + otherEdgeFunction: EdgeFunction[V] + ): EdgeFunction[V] = { + otherEdgeFunction match { + case _: AllTopEdgeFunction[V] => this + case IdentityEdgeFunction => this + case _ => otherEdgeFunction + } + } + + override def equals[V >: LinearConstantPropagationValue <: IDEValue]( + otherEdgeFunction: EdgeFunction[V] + ): Boolean = { + otherEdgeFunction eq this + } + + override def toString: String = "UnknownValueEdgeFunction()" +} + +/** + * Edge function for a variable that is definitely not constant. + * + * @author Robin Körkemeier + */ +object VariableValueEdgeFunction extends AllBottomEdgeFunction[LinearConstantPropagationValue](VariableValue) { + override def composeWith[V >: LinearConstantPropagationValue <: IDEValue]( + secondEdgeFunction: EdgeFunction[V] + ): EdgeFunction[V] = { + secondEdgeFunction match { + case LinearCombinationEdgeFunction(0, _, _) => secondEdgeFunction + case LinearCombinationEdgeFunction(_, _, _) => this + case _ => this + } + } + + override def toString: String = "VariableValueEdgeFunction()" +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/problem/LinearConstantPropagationFact.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/problem/LinearConstantPropagationFact.scala new file mode 100644 index 0000000000..2e6d3698a8 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/problem/LinearConstantPropagationFact.scala @@ -0,0 +1,35 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package ide +package instances +package linear_constant_propagation +package problem + +import org.opalj.ide.problem.IDEFact + +/** + * Type for modeling facts for linear constant propagation. + * + * @author Robin Körkemeier + */ +trait LinearConstantPropagationFact extends IDEFact + +/** + * Fact to use as null fact. + * + * @author Robin Körkemeier + */ +case object NullFact extends LinearConstantPropagationFact + +/** + * Fact representing a seen variable. + * + * @param name the name of the variable (e.g. `lv0`) + * @param definedAtIndex where the variable is defined (used to uniquely identify a variable/variable fact) + * + * @author Robin Körkemeier + */ +case class VariableFact(name: String, definedAtIndex: Int) extends LinearConstantPropagationFact diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/problem/LinearConstantPropagationLattice.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/problem/LinearConstantPropagationLattice.scala new file mode 100644 index 0000000000..a194db06cb --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/problem/LinearConstantPropagationLattice.scala @@ -0,0 +1,34 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package ide +package instances +package linear_constant_propagation +package problem + +import org.opalj.ide.problem.MeetLattice + +/** + * Lattice used for linear constant propagation. + * + * @author Robin Körkemeier + */ +object LinearConstantPropagationLattice extends MeetLattice[LinearConstantPropagationValue] { + override def top: LinearConstantPropagationValue = UnknownValue + + override def bottom: LinearConstantPropagationValue = VariableValue + + override def meet( + value1: LinearConstantPropagationValue, + value2: LinearConstantPropagationValue + ): LinearConstantPropagationValue = (value1, value2) match { + case (UnknownValue, _) => value2 + case (_, UnknownValue) => value1 + case (VariableValue, _) => VariableValue + case (_, VariableValue) => VariableValue + case (ConstantValue(constant1), ConstantValue(constant2)) if constant1 == constant2 => value1 + case _ => VariableValue + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/problem/LinearConstantPropagationProblem.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/problem/LinearConstantPropagationProblem.scala new file mode 100644 index 0000000000..c369a48c1c --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/problem/LinearConstantPropagationProblem.scala @@ -0,0 +1,634 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package ide +package instances +package linear_constant_propagation +package problem + +import scala.annotation.unused + +import scala.collection.immutable.Set + +import org.opalj.BinaryArithmeticOperators +import org.opalj.br.Method +import org.opalj.fpcf.PropertyStore +import org.opalj.ide.problem.EdgeFunctionResult +import org.opalj.ide.problem.FlowFunction +import org.opalj.ide.problem.IdentityEdgeFunction +import org.opalj.ide.problem.IdentityFlowFunction +import org.opalj.ide.problem.MeetLattice +import org.opalj.tac.fpcf.analyses.ide.problem.JavaIDEProblem +import org.opalj.tac.fpcf.analyses.ide.solver.JavaStatement +import org.opalj.tac.fpcf.analyses.ide.solver.JavaStatement.StmtAsCall +import org.opalj.value.IsIntegerValue + +/** + * Definition of the linear constant propagation problem. + * + * @author Robin Körkemeier + */ +class LinearConstantPropagationProblem + extends JavaIDEProblem[LinearConstantPropagationFact, LinearConstantPropagationValue] { + override val nullFact: LinearConstantPropagationFact = + NullFact + + override val lattice: MeetLattice[LinearConstantPropagationValue] = + LinearConstantPropagationLattice + + override def getAdditionalSeeds(stmt: JavaStatement, callee: Method)( + implicit propertyStore: PropertyStore + ): scala.collection.Set[LinearConstantPropagationFact] = { + callee.parameterTypes + .iterator + .zipWithIndex + .collect { + case (paramType, index) if paramType.isIntegerType => VariableFact(s"param${index + 1}", -(index + 2)) + } + .toSet + } + + override def getAdditionalSeedsEdgeFunction( + stmt: JavaStatement, + fact: LinearConstantPropagationFact, + callee: Method + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[LinearConstantPropagationValue] = { + fact match { + case VariableFact(_, _) => UnknownValueEdgeFunction + case _ => super.getAdditionalSeedsEdgeFunction(stmt, fact, callee) + } + } + + override def getNormalFlowFunction( + source: JavaStatement, + sourceFact: LinearConstantPropagationFact, + target: JavaStatement + )(implicit propertyStore: PropertyStore): FlowFunction[LinearConstantPropagationFact] = { + new FlowFunction[LinearConstantPropagationFact] { + override def compute(): FactsAndDependees = { + source.stmt.astID match { + case Assignment.ASTID => + val assignment = source.stmt.asAssignment + if (isExpressionGeneratedByFact(assignment.expr)(source, sourceFact, target)) { + /* Generate fact for target of assignment if the expression is influenced by the source + * fact */ + Set(sourceFact, VariableFact(assignment.targetVar.name, source.pc)) + } else { + Set(sourceFact) + } + + case _ => Set(sourceFact) + } + } + } + } + + private def isExpressionGeneratedByFact( + expr: Expr[JavaStatement.V] + )( + source: JavaStatement, + sourceFact: LinearConstantPropagationFact, + target: JavaStatement + ): Boolean = { + (expr.astID match { + case IntConst.ASTID => + isIntConstExpressionGeneratedByFact(expr.asIntConst)(_, _, _) + + case BinaryExpr.ASTID => + isBinaryExpressionGeneratedByFact(expr.asBinaryExpr)(_, _, _) + + case Var.ASTID => + isVarExpressionGeneratedByFact(expr.asVar)(_, _, _) + + case ArrayLength.ASTID => + isArrayLengthExpressionGeneratedByFact(expr.asArrayLength)(_, _, _) + + case ArrayLoad.ASTID => + isArrayLoadExpressionGeneratedByFact(expr.asArrayLoad)(_, _, _) + + case GetField.ASTID => + isGetFieldExpressionGeneratedByFact(expr.asGetField)(_, _, _) + + case GetStatic.ASTID => + isGetStaticExpressionGeneratedByFact(expr.asGetStatic)(_, _, _) + + case _ => return false + })( + source, + sourceFact, + target + ) + } + + protected def isIntConstExpressionGeneratedByFact( + @unused intConstExpr: IntConst + )( + @unused source: JavaStatement, + sourceFact: LinearConstantPropagationFact, + @unused target: JavaStatement + ): Boolean = { + /* Only generate fact for constants from null fact */ + sourceFact == nullFact + } + + protected def isBinaryExpressionGeneratedByFact( + binaryExpr: BinaryExpr[JavaStatement.V] + )( + @unused source: JavaStatement, + sourceFact: LinearConstantPropagationFact, + @unused target: JavaStatement + ): Boolean = { + val leftExpr = binaryExpr.left + val rightExpr = binaryExpr.right + + if (sourceFact == nullFact) { + /* Only generate fact by null fact for binary expressions if both subexpressions are influenced. + * This is needed for binary expressions with one constant and one variable. */ + (leftExpr.isConst || isVarExpressionGeneratedByFact(leftExpr.asVar)(source, sourceFact, target)) && + (rightExpr.isConst || isVarExpressionGeneratedByFact(rightExpr.asVar)(source, sourceFact, target)) + } else { + /* If source fact is not null fact, generate new fact if one subexpression is influenced by the + * source fact */ + leftExpr.isVar && isVarExpressionGeneratedByFact(leftExpr.asVar)(source, sourceFact, target) || + rightExpr.isVar && isVarExpressionGeneratedByFact(rightExpr.asVar)(source, sourceFact, target) + } + } + + protected def isVarExpressionGeneratedByFact( + varExpr: JavaStatement.V + )( + @unused source: JavaStatement, + sourceFact: LinearConstantPropagationFact, + @unused target: JavaStatement + ): Boolean = { + val hasConstantValue = + varExpr.value match { + case v: IsIntegerValue => v.constantValue.isDefined + case _ => false + } + + sourceFact match { + case NullFact => + /* Generate fact by null fact for variables if it is definitely a constant */ + hasConstantValue + case VariableFact(_, definedAtIndex) => + /* Generate fact only if it is not detected as constant (by the value analysis) and the variable + * represented by the source fact is one possible initializer of the target variable */ + !hasConstantValue && varExpr.definedBy.contains(definedAtIndex) + } + } + + protected def isArrayLengthExpressionGeneratedByFact( + @unused arrayLengthExpr: ArrayLength[JavaStatement.V] + )( + @unused source: JavaStatement, + sourceFact: LinearConstantPropagationFact, + @unused target: JavaStatement + ): Boolean = { + /* Generate fact for array length expressions only by null fact */ + sourceFact == nullFact + } + + protected def isArrayLoadExpressionGeneratedByFact( + arrayLoadExpr: ArrayLoad[JavaStatement.V] + )( + @unused source: JavaStatement, + sourceFact: LinearConstantPropagationFact, + @unused target: JavaStatement + ): Boolean = { + val arrayVar = arrayLoadExpr.arrayRef.asVar + /* Generate fact for array access expressions only by null fact and if array stores integers */ + sourceFact == nullFact && + arrayVar.value.asReferenceValue.asReferenceType.asArrayType.componentType.isIntegerType + } + + protected def isGetFieldExpressionGeneratedByFact( + getFieldExpr: GetField[JavaStatement.V] + )( + @unused source: JavaStatement, + sourceFact: LinearConstantPropagationFact, + @unused target: JavaStatement + ): Boolean = { + /* Generate fact for field access expressions only by null fact and if field is of type integer */ + sourceFact == nullFact && getFieldExpr.declaredFieldType.isIntegerType + } + + protected def isGetStaticExpressionGeneratedByFact( + getStaticExpr: GetStatic + )( + @unused source: JavaStatement, + sourceFact: LinearConstantPropagationFact, + @unused target: JavaStatement + ): Boolean = { + /* Generate fact for field access expressions only by null fact and if field is of type integer */ + sourceFact == nullFact && getStaticExpr.declaredFieldType.isIntegerType + } + + override def getCallFlowFunction( + callSite: JavaStatement, + callSiteFact: LinearConstantPropagationFact, + calleeEntry: JavaStatement, + callee: Method + )(implicit propertyStore: PropertyStore): FlowFunction[LinearConstantPropagationFact] = { + new FlowFunction[LinearConstantPropagationFact] { + override def compute(): FactsAndDependees = { + /* Only propagate to callees that return integers */ + if (!callee.returnType.isIntegerType) { + Set.empty + } else { + callSiteFact match { + case NullFact => + /* Always propagate null facts */ + Set(callSiteFact) + + case VariableFact(_, definedAtIndex) => + val callStmt = callSite.stmt.asCall() + + /* Parameters and their types (excluding the implicit 'this' reference) */ + val params = callStmt.params + val paramTypes = callee.parameterTypes + + params + .iterator + .zipWithIndex + .collect { + /* Only parameters that are of type integer and where the variable represented by + * the source fact is one possible initializer */ + case (param, index) + if paramTypes(index).isIntegerType && param.asVar.definedBy.contains( + definedAtIndex + ) => VariableFact(s"param${index + 1}", -(index + 2)) + } + .toSet + } + } + } + } + } + + override def getReturnFlowFunction( + calleeExit: JavaStatement, + calleeExitFact: LinearConstantPropagationFact, + callee: Method, + returnSite: JavaStatement, + callSite: JavaStatement, + callSiteFact: LinearConstantPropagationFact + )(implicit propertyStore: PropertyStore): FlowFunction[LinearConstantPropagationFact] = { + new FlowFunction[LinearConstantPropagationFact] { + override def compute(): FactsAndDependees = { + /* Only propagate to return site if callee returns an integer */ + if (!callee.returnType.isIntegerType) { + Set.empty + } else { + calleeExitFact match { + case NullFact => + /* Always propagate null fact */ + Set(calleeExitFact) + + case VariableFact(_, definedAtIndex) => + returnSite.stmt.astID match { + case Assignment.ASTID => + val assignment = returnSite.stmt.asAssignment + + val returnExpr = calleeExit.stmt.asReturnValue.expr + /* Only propagate if the variable represented by the source fact is one possible + * initializer of the variable at the return site */ + if (returnExpr.asVar.definedBy.contains(definedAtIndex)) { + Set(VariableFact(assignment.targetVar.name, returnSite.pc)) + } else { + Set.empty + } + + case _ => Set.empty + } + } + } + } + } + } + + override def getCallToReturnFlowFunction( + callSite: JavaStatement, + callSiteFact: LinearConstantPropagationFact, + callee: Method, + returnSite: JavaStatement + )(implicit propertyStore: PropertyStore): FlowFunction[LinearConstantPropagationFact] = { + IdentityFlowFunction[LinearConstantPropagationFact](callSiteFact) + } + + override def getNormalEdgeFunction( + source: JavaStatement, + sourceFact: LinearConstantPropagationFact, + target: JavaStatement, + targetFact: LinearConstantPropagationFact + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[LinearConstantPropagationValue] = { + if (sourceFact == targetFact) { + /* Simply propagates a fact through the method */ + return IdentityEdgeFunction + } + + source.stmt.astID match { + case Assignment.ASTID => + val assignment = source.stmt.asAssignment + val expr = assignment.expr + + (expr.astID match { + case IntConst.ASTID => + getNormalEdgeFunctionForIntConstExpression(expr.asIntConst)(_, _, _, _) + + case BinaryExpr.ASTID => + getNormalEdgeFunctionForBinaryExpression(expr.asBinaryExpr)(_, _, _, _) + + case ArrayLength.ASTID => + getNormalEdgeFunctionForArrayLength(expr.asArrayLength)(_, _, _, _) + + case ArrayLoad.ASTID => + getNormalEdgeFunctionForArrayLoad(expr.asArrayLoad)(_, _, _, _) + + case GetField.ASTID => + getNormalEdgeFunctionForGetField(expr.asGetField)(_, _, _, _) + + case GetStatic.ASTID => + getNormalEdgeFunctionForGetStatic(expr.asGetStatic)(_, _, _, _) + + case _ => + throw new IllegalArgumentException(s"Expression $expr should not occur here!") + })( + source, + sourceFact, + target, + targetFact + ) + + case _ => IdentityEdgeFunction + } + } + + protected def getNormalEdgeFunctionForIntConstExpression( + intConstExpr: IntConst + )( + @unused source: JavaStatement, + @unused sourceFact: LinearConstantPropagationFact, + @unused target: JavaStatement, + @unused targetFact: LinearConstantPropagationFact + )(implicit @unused propertyStore: PropertyStore): EdgeFunctionResult[LinearConstantPropagationValue] = { + LinearCombinationEdgeFunction(0, intConstExpr.value) + } + + protected def getNormalEdgeFunctionForBinaryExpression( + binaryExpr: BinaryExpr[JavaStatement.V] + )( + @unused source: JavaStatement, + @unused sourceFact: LinearConstantPropagationFact, + @unused target: JavaStatement, + @unused targetFact: LinearConstantPropagationFact + )(implicit @unused propertyStore: PropertyStore): EdgeFunctionResult[LinearConstantPropagationValue] = { + val leftExpr = binaryExpr.left + val rightExpr = binaryExpr.right + + if (leftExpr.astID != Var.ASTID && leftExpr.astID != IntConst.ASTID || + rightExpr.astID != Var.ASTID && rightExpr.astID != IntConst.ASTID + ) { + throw new IllegalArgumentException(s"Combination ($leftExpr, $rightExpr) should not occur here!") + } + + /* Try to resolve an constant or variable expression to a constant value */ + val getValueForExpr: Expr[JavaStatement.V] => Option[Int] = expr => { + expr.astID match { + case Var.ASTID => + val var0 = expr.asVar + var0.value match { + case v: IsIntegerValue => v.constantValue + case _ => None + } + + case IntConst.ASTID => Some(expr.asIntConst.value) + } + } + + val leftValue = getValueForExpr(leftExpr) + val rightValue = getValueForExpr(rightExpr) + + (leftValue, rightValue, binaryExpr.op) match { + case (Some(l), Some(r), BinaryArithmeticOperators.Add) => + LinearCombinationEdgeFunction(0, l + r) + case (Some(l), None, BinaryArithmeticOperators.Add) => + LinearCombinationEdgeFunction(1, l) + case (None, Some(r), BinaryArithmeticOperators.Add) => + LinearCombinationEdgeFunction(1, r) + + case (Some(l), Some(r), BinaryArithmeticOperators.Subtract) => + LinearCombinationEdgeFunction(0, l - r) + case (Some(l), None, BinaryArithmeticOperators.Subtract) => + LinearCombinationEdgeFunction(-1, l) + case (None, Some(r), BinaryArithmeticOperators.Subtract) => + LinearCombinationEdgeFunction(1, -r) + + case (Some(l), Some(r), BinaryArithmeticOperators.Multiply) => + LinearCombinationEdgeFunction(0, l * r) + case (Some(l), None, BinaryArithmeticOperators.Multiply) => + LinearCombinationEdgeFunction(l, 0) + case (None, Some(r), BinaryArithmeticOperators.Multiply) => + LinearCombinationEdgeFunction(r, 0) + + case (Some(l), Some(r), BinaryArithmeticOperators.Divide) => + LinearCombinationEdgeFunction(0, l / r) + case (_, _, BinaryArithmeticOperators.Divide) => + VariableValueEdgeFunction + + case (_, _, BinaryArithmeticOperators.Modulo) => + VariableValueEdgeFunction + + case (None, None, _) => + VariableValueEdgeFunction + + case (_, _, BinaryArithmeticOperators.And) => + VariableValueEdgeFunction + case (_, _, BinaryArithmeticOperators.Or) => + VariableValueEdgeFunction + case (_, _, BinaryArithmeticOperators.XOr) => + VariableValueEdgeFunction + case (_, _, BinaryArithmeticOperators.ShiftLeft) => + VariableValueEdgeFunction + case (_, _, BinaryArithmeticOperators.ShiftRight) => + VariableValueEdgeFunction + case (_, _, BinaryArithmeticOperators.UnsignedShiftRight) => + VariableValueEdgeFunction + + case (_, _, op) => + throw new UnsupportedOperationException(s"Operator $op is not implemented!") + } + } + + protected def getNormalEdgeFunctionForArrayLength( + @unused arrayLengthExpr: ArrayLength[JavaStatement.V] + )( + @unused source: JavaStatement, + @unused sourceFact: LinearConstantPropagationFact, + @unused target: JavaStatement, + @unused targetFact: LinearConstantPropagationFact + )(implicit @unused propertyStore: PropertyStore): EdgeFunctionResult[LinearConstantPropagationValue] = { + VariableValueEdgeFunction + } + + protected def getNormalEdgeFunctionForArrayLoad( + @unused arrayLoadExpr: ArrayLoad[JavaStatement.V] + )( + @unused source: JavaStatement, + @unused sourceFact: LinearConstantPropagationFact, + @unused target: JavaStatement, + @unused targetFact: LinearConstantPropagationFact + )(implicit @unused propertyStore: PropertyStore): EdgeFunctionResult[LinearConstantPropagationValue] = { + VariableValueEdgeFunction + } + + protected def getNormalEdgeFunctionForGetField( + @unused getFieldExpr: GetField[JavaStatement.V] + )( + @unused source: JavaStatement, + @unused sourceFact: LinearConstantPropagationFact, + @unused target: JavaStatement, + @unused targetFact: LinearConstantPropagationFact + )(implicit @unused propertyStore: PropertyStore): EdgeFunctionResult[LinearConstantPropagationValue] = { + VariableValueEdgeFunction + } + + protected def getNormalEdgeFunctionForGetStatic( + @unused getStaticExpr: GetStatic + )( + @unused source: JavaStatement, + @unused sourceFact: LinearConstantPropagationFact, + @unused target: JavaStatement, + @unused targetFact: LinearConstantPropagationFact + )(implicit @unused propertyStore: PropertyStore): EdgeFunctionResult[LinearConstantPropagationValue] = { + VariableValueEdgeFunction + } + + override def getCallEdgeFunction( + callSite: JavaStatement, + callSiteFact: LinearConstantPropagationFact, + calleeEntry: JavaStatement, + calleeEntryFact: LinearConstantPropagationFact, + callee: Method + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[LinearConstantPropagationValue] = { + callSiteFact match { + case NullFact => UnknownValueEdgeFunction + case _ => IdentityEdgeFunction + } + } + + override def getReturnEdgeFunction( + calleeExit: JavaStatement, + calleeExitFact: LinearConstantPropagationFact, + callee: Method, + returnSite: JavaStatement, + returnSiteFact: LinearConstantPropagationFact, + callSite: JavaStatement, + callSiteFact: LinearConstantPropagationFact + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[LinearConstantPropagationValue] = IdentityEdgeFunction + + override def getCallToReturnEdgeFunction( + callSite: JavaStatement, + callSiteFact: LinearConstantPropagationFact, + callee: Method, + returnSite: JavaStatement, + returnSiteFact: LinearConstantPropagationFact + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[LinearConstantPropagationValue] = IdentityEdgeFunction + + override def hasPrecomputedFlowAndSummaryFunction( + callSite: JavaStatement, + callSiteFact: LinearConstantPropagationFact, + callee: Method + )(implicit propertyStore: PropertyStore): Boolean = { + if (callee.isNative || callee.body.isEmpty) { + return true + } + + super.hasPrecomputedFlowAndSummaryFunction(callSite, callSiteFact, callee) + } + + override def getPrecomputedFlowFunction( + callSite: JavaStatement, + callSiteFact: LinearConstantPropagationFact, + callee: Method, + returnSite: JavaStatement + )(implicit propertyStore: PropertyStore): FlowFunction[LinearConstantPropagationFact] = { + if (callee.isNative || callee.body.isEmpty) { + return new FlowFunction[LinearConstantPropagationFact] { + override def compute(): FactsAndDependees = { + if (callee.returnType.isIntegerType) { + returnSite.stmt.astID match { + case Assignment.ASTID => + val assignment = returnSite.stmt.asAssignment + Set(VariableFact(assignment.targetVar.name, returnSite.pc)) + + case _ => Set.empty + } + } else { + Set.empty + } + } + } + } + + super.getPrecomputedFlowFunction(callSite, callSiteFact, callee, returnSite) + } + + override def getPrecomputedSummaryFunction( + callSite: JavaStatement, + callSiteFact: LinearConstantPropagationFact, + callee: Method, + returnSite: JavaStatement, + returnSiteFact: LinearConstantPropagationFact + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[LinearConstantPropagationValue] = { + if (callee.isNative || callee.body.isEmpty) { + return VariableValueEdgeFunction + } + + super.getPrecomputedSummaryFunction(callSite, callSiteFact, callee, returnSite, returnSiteFact) + } + + override def getPrecomputedFlowFunction( + callSite: JavaStatement, + callSiteFact: LinearConstantPropagationFact, + returnSite: JavaStatement + )(implicit propertyStore: PropertyStore): FlowFunction[LinearConstantPropagationFact] = { + new FlowFunction[LinearConstantPropagationFact] { + override def compute(): FactsAndDependees = { + if (callSite.stmt.asCall().descriptor.returnType.isIntegerType) { + callSiteFact match { + case NullFact => + returnSite.stmt.astID match { + case Assignment.ASTID => + val assignment = returnSite.stmt.asAssignment + Set(callSiteFact, VariableFact(assignment.targetVar.name, returnSite.pc)) + + case _ => Set(callSiteFact) + } + + case VariableFact(_, _) => Set(callSiteFact) + } + } else { + Set(callSiteFact) + } + } + } + } + + override def getPrecomputedSummaryFunction( + callSite: JavaStatement, + callSiteFact: LinearConstantPropagationFact, + returnSite: JavaStatement, + returnSiteFact: LinearConstantPropagationFact + )(implicit propertyStore: PropertyStore): EdgeFunctionResult[LinearConstantPropagationValue] = { + (callSiteFact, returnSiteFact) match { + case (NullFact, VariableFact(_, _)) => + VariableValueEdgeFunction + + case _ => + IdentityEdgeFunction + } + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/problem/LinearConstantPropagationValue.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/problem/LinearConstantPropagationValue.scala new file mode 100644 index 0000000000..496e52c020 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/problem/LinearConstantPropagationValue.scala @@ -0,0 +1,39 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package ide +package instances +package linear_constant_propagation +package problem + +import org.opalj.ide.problem.IDEValue + +/** + * Type for modeling values for linear constant propagation. + * + * @author Robin Körkemeier + */ +trait LinearConstantPropagationValue extends IDEValue + +/** + * Value not known (yet). + * + * @author Robin Körkemeier + */ +case object UnknownValue extends LinearConstantPropagationValue + +/** + * A constant value. + * + * @author Robin Körkemeier + */ +case class ConstantValue(c: Int) extends LinearConstantPropagationValue + +/** + * Value is variable. + * + * @author Robin Körkemeier + */ +case object VariableValue extends LinearConstantPropagationValue diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/integration/JavaIDEAnalysisScheduler.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/integration/JavaIDEAnalysisScheduler.scala new file mode 100644 index 0000000000..4048ef3109 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/integration/JavaIDEAnalysisScheduler.scala @@ -0,0 +1,23 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package ide +package integration + +import org.opalj.br.analyses.SomeProject +import org.opalj.ide.problem.IDEFact +import org.opalj.ide.problem.IDEValue +import org.opalj.tac.fpcf.analyses.ide.problem.JavaIDEProblem +import org.opalj.tac.fpcf.analyses.ide.solver.JavaICFG + +/** + * Specialized IDE analysis scheduler for Java programs. + * + * @author Robin Körkemeier + */ +abstract class JavaIDEAnalysisScheduler[Fact <: IDEFact, Value <: IDEValue] + extends JavaIDEAnalysisSchedulerBase[Fact, Value] { + override def createProblem(project: SomeProject, icfg: JavaICFG): JavaIDEProblem[Fact, Value] +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/integration/JavaIDEAnalysisSchedulerBase.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/integration/JavaIDEAnalysisSchedulerBase.scala new file mode 100644 index 0000000000..588f967ef6 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/integration/JavaIDEAnalysisSchedulerBase.scala @@ -0,0 +1,66 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package ide +package integration + +import scala.collection.immutable.Set + +import org.opalj.br.Method +import org.opalj.br.analyses.DeclaredMethodsKey +import org.opalj.br.analyses.ProjectInformationKeys +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.properties.cg.Callers +import org.opalj.fpcf.PropertyBounds +import org.opalj.ide.integration.IDEAnalysisScheduler +import org.opalj.ide.problem.IDEFact +import org.opalj.ide.problem.IDEValue +import org.opalj.tac.cg.TypeIteratorKey +import org.opalj.tac.fpcf.analyses.ide.solver.JavaBackwardICFG +import org.opalj.tac.fpcf.analyses.ide.solver.JavaForwardICFG +import org.opalj.tac.fpcf.analyses.ide.solver.JavaICFG +import org.opalj.tac.fpcf.analyses.ide.solver.JavaStatement +import org.opalj.tac.fpcf.properties.TACAI + +/** + * A base IDE analysis scheduler for Java programs. + * + * @author Robin Körkemeier + */ +abstract class JavaIDEAnalysisSchedulerBase[Fact <: IDEFact, Value <: IDEValue] + extends IDEAnalysisScheduler[Fact, Value, JavaStatement, Method, JavaICFG] { + + override def requiredProjectInformation: ProjectInformationKeys = + super.requiredProjectInformation ++ Seq( + DeclaredMethodsKey, + TypeIteratorKey + ) + + override def uses: Set[PropertyBounds] = + super.uses ++ Set( + PropertyBounds.finalP(TACAI), + PropertyBounds.finalP(Callers) + ) +} + +object JavaIDEAnalysisSchedulerBase { + /** + * Trait to drop-in [[org.opalj.tac.fpcf.analyses.ide.solver.JavaForwardICFG]] for [[createICFG]] + */ + trait ForwardICFG { + def createICFG(project: SomeProject): JavaICFG = { + new JavaForwardICFG(project) + } + } + + /** + * Trait to drop-in [[org.opalj.tac.fpcf.analyses.ide.solver.JavaBackwardICFG]] for [[createICFG]] + */ + trait BackwardICFG { + def createICFG(project: SomeProject): JavaICFG = { + new JavaBackwardICFG(project) + } + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/integration/JavaIDEPropertyMetaInformation.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/integration/JavaIDEPropertyMetaInformation.scala new file mode 100644 index 0000000000..95ec423b28 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/integration/JavaIDEPropertyMetaInformation.scala @@ -0,0 +1,21 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package ide +package integration + +import org.opalj.br.Method +import org.opalj.ide.integration.IDEPropertyMetaInformation +import org.opalj.ide.problem.IDEFact +import org.opalj.ide.problem.IDEValue +import org.opalj.tac.fpcf.analyses.ide.solver.JavaStatement + +/** + * Specialized property meta information for IDE problems with Java programs. + * + * @author Robin Körkemeier + */ +trait JavaIDEPropertyMetaInformation[Fact <: IDEFact, Value <: IDEValue] + extends IDEPropertyMetaInformation[Fact, Value, JavaStatement, Method] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/problem/JavaIDEProblem.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/problem/JavaIDEProblem.scala new file mode 100644 index 0000000000..7aeae078f0 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/problem/JavaIDEProblem.scala @@ -0,0 +1,20 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package ide +package problem + +import org.opalj.br.Method +import org.opalj.ide.problem.IDEFact +import org.opalj.ide.problem.IDEProblem +import org.opalj.ide.problem.IDEValue +import org.opalj.tac.fpcf.analyses.ide.solver.JavaStatement + +/** + * Specialized IDE problem for Java programs. + * + * @author Robin Körkemeier + */ +abstract class JavaIDEProblem[Fact <: IDEFact, Value <: IDEValue] extends IDEProblem[Fact, Value, JavaStatement, Method] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/solver/JavaBackwardICFG.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/solver/JavaBackwardICFG.scala new file mode 100644 index 0000000000..6a6e6fa432 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/solver/JavaBackwardICFG.scala @@ -0,0 +1,51 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package ide +package solver + +import scala.collection.immutable.Set +import scala.collection.mutable.{Set => MutableSet} + +import org.opalj.br.Method +import org.opalj.br.analyses.SomeProject + +/** + * Interprocedural control flow graph for Java programs in backward direction. This implementation is based on the + * [[org.opalj.tac.fpcf.analyses.ifds.JavaBackwardICFG]] from IFDS. + * + * @author Robin Körkemeier + */ +class JavaBackwardICFG(project: SomeProject) extends JavaBaseICFG(project) { + override def getStartStatements(callable: Method): scala.collection.Set[JavaStatement] = { + val tac = tacProvider(callable) + (tac.cfg.normalReturnNode.predecessors ++ tac.cfg.abnormalReturnNode.predecessors) + .map { node => JavaStatement(callable, node.asBasicBlock.endPC, isReturnNode = false, tac.stmts, tac.cfg) } + } + + override def getNextStatements(javaStmt: JavaStatement): scala.collection.Set[JavaStatement] = { + if (isCallStatement(javaStmt)) { + Set( + JavaStatement(javaStmt.method, javaStmt.pc, isReturnNode = true, javaStmt.stmts, javaStmt.cfg) + ) + } else { + val predecessors = MutableSet.empty[JavaStatement] + javaStmt.cfg.foreachPredecessor(javaStmt.pc) { prevPc => + predecessors.add( + JavaStatement(javaStmt.method, prevPc, isReturnNode = false, javaStmt.stmts, javaStmt.cfg) + ) + } + predecessors + } + } + + override def isNormalExitStatement(stmt: JavaStatement): Boolean = { + stmt.pc == 0 + } + + override def isAbnormalExitStatement(stmt: JavaStatement): Boolean = { + false + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/solver/JavaBaseICFG.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/solver/JavaBaseICFG.scala new file mode 100644 index 0000000000..4ca09e6ad7 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/solver/JavaBaseICFG.scala @@ -0,0 +1,93 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package ide +package solver + +import scala.collection.immutable.Set +import scala.collection.mutable.{Map => MutableMap} + +import org.opalj.br.Method +import org.opalj.br.analyses.DeclaredMethods +import org.opalj.br.analyses.DeclaredMethodsKey +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.ContextProviderKey +import org.opalj.br.fpcf.analyses.ContextProvider +import org.opalj.br.fpcf.properties.cg.Callees +import org.opalj.fpcf.FinalP +import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.PropertyStoreKey +import org.opalj.value.ValueInformation + +/** + * Base interprocedural control flow graph for Java programs. This implementation is based on the + * [[org.opalj.tac.fpcf.analyses.ifds.JavaICFG]] from IFDS. + * + * @author Robin Körkemeier + */ +abstract class JavaBaseICFG(project: SomeProject) extends JavaICFG { + private val lazyTacProvider: Method => AITACode[TACMethodParameter, ValueInformation] = { + project.get(LazyDetachedTACAIKey) + } + + protected implicit val propertyStore: PropertyStore = project.get(PropertyStoreKey) + protected implicit val contextProvider: ContextProvider = project.get(ContextProviderKey) + protected val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey) + + private val tacProviderCache = MutableMap.empty[Method, AITACode[TACMethodParameter, ValueInformation]] + + def tacProvider(callable: Method): AITACode[TACMethodParameter, ValueInformation] = { + tacProviderCache.getOrElseUpdate(callable, { lazyTacProvider(callable) }) + } + + override def isCallStatement(javaStmt: JavaStatement): Boolean = { + if (javaStmt.isReturnNode) { + return false + } + + val stmt = javaStmt.stmt + stmt.astID match { + case StaticMethodCall.ASTID | NonVirtualMethodCall.ASTID | VirtualMethodCall.ASTID => true + case Assignment.ASTID | ExprStmt.ASTID => + val expr = stmt.astID match { + case Assignment.ASTID => stmt.asAssignment.expr + case ExprStmt.ASTID => stmt.asExprStmt.expr + } + expr.astID match { + case StaticFunctionCall.ASTID | NonVirtualFunctionCall.ASTID | VirtualFunctionCall.ASTID => true + case _ => false + } + case _ => false + } + } + + override def getCallees(javaStmt: JavaStatement): scala.collection.Set[Method] = { + val caller = declaredMethods(javaStmt.method) + if (caller == null) { + return Set.empty + } + val calleesEOptionP = propertyStore(caller, Callees.key) + calleesEOptionP match { + case FinalP(callees) => + callees + .directCallees(contextProvider.newContext(caller), javaStmt.stmt.pc) + .map(_.method) + .flatMap { callee => + if (callee.hasSingleDefinedMethod) { + Seq(callee.definedMethod) + } else if (callee.hasMultipleDefinedMethods) { + callee.definedMethods + } else { + Seq.empty + } + } + .toSet + case _ => + throw new IllegalStateException("Call graph must be computed before the analysis starts!") + } + } + + override def getCallable(javaStmt: JavaStatement): Method = javaStmt.method +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/solver/JavaForwardICFG.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/solver/JavaForwardICFG.scala new file mode 100644 index 0000000000..0227f81341 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/solver/JavaForwardICFG.scala @@ -0,0 +1,54 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package ide +package solver + +import scala.collection.immutable.Set +import scala.collection.mutable.{Set => MutableSet} + +import org.opalj.br.Method +import org.opalj.br.analyses.SomeProject + +/** + * Interprocedural control flow graph for Java programs in forward direction. This implementation is based on the + * [[org.opalj.tac.fpcf.analyses.ifds.JavaForwardICFG]] from IFDS. + * + * @author Robin Körkemeier + */ +class JavaForwardICFG(project: SomeProject) extends JavaBaseICFG(project) { + override def getStartStatements(callable: Method): scala.collection.Set[JavaStatement] = { + val tac = tacProvider(callable) + Set( + JavaStatement(callable, 0, isReturnNode = false, tac.stmts, tac.cfg) + ) + } + + override def getNextStatements(javaStmt: JavaStatement): scala.collection.Set[JavaStatement] = { + if (isCallStatement(javaStmt)) { + Set( + JavaStatement(javaStmt.method, javaStmt.pc, isReturnNode = true, javaStmt.stmts, javaStmt.cfg) + ) + } else { + val successors = MutableSet.empty[JavaStatement] + javaStmt.cfg.foreachSuccessor(javaStmt.pc) { nextPc => + successors.add( + JavaStatement(javaStmt.method, nextPc, isReturnNode = false, javaStmt.stmts, javaStmt.cfg) + ) + } + successors + } + } + + override def isNormalExitStatement(javaStmt: JavaStatement): Boolean = { + javaStmt.pc == javaStmt.basicBlock.asBasicBlock.endPC && + javaStmt.basicBlock.successors.exists(_.isNormalReturnExitNode) + } + + override def isAbnormalExitStatement(javaStmt: JavaStatement): Boolean = { + javaStmt.pc == javaStmt.basicBlock.asBasicBlock.endPC && + javaStmt.basicBlock.successors.exists(_.isAbnormalReturnExitNode) + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/solver/JavaICFG.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/solver/JavaICFG.scala new file mode 100644 index 0000000000..dbee2b59c6 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/solver/JavaICFG.scala @@ -0,0 +1,17 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package ide +package solver + +import org.opalj.br.Method +import org.opalj.ide.solver.ICFG + +/** + * Interprocedural control flow graph for Java programs. + * + * @author Robin Körkemeier + */ +trait JavaICFG extends ICFG[JavaStatement, Method] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/solver/JavaStatement.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/solver/JavaStatement.scala new file mode 100644 index 0000000000..c1e9997a8c --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/solver/JavaStatement.scala @@ -0,0 +1,64 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package ide +package solver + +import org.opalj.br.Method +import org.opalj.br.cfg.BasicBlock +import org.opalj.br.cfg.CFG +import org.opalj.value.ValueInformation + +/** + * Class to model statements used with IDE analyses. + * + * @param pc the pc of the statement in the code + * @param isReturnNode whether the statement models the return node of a call + * + * @author Robin Körkemeier + */ +case class JavaStatement( + method: Method, + pc: Int, + isReturnNode: Boolean = false, + stmts: Array[Stmt[JavaStatement.V]], + cfg: CFG[Stmt[JavaStatement.V], TACStmts[JavaStatement.V]] +) { + def stmt: Stmt[JavaStatement.V] = stmts(pc) + + def basicBlock: BasicBlock = cfg.bb(pc) + + override def hashCode(): Int = { + (method.hashCode() * 31 + pc) * 31 + ( + if (isReturnNode) { 1 } + else { 0 } + ) + } + + override def equals(obj: Any): Boolean = obj match { + case JavaStatement(method2, pc2, isReturnNode2, _, _) => + method == method2 && pc == pc2 && isReturnNode == isReturnNode2 + case _ => false + } + + override def toString: String = { + val returnOptional = + if (isReturnNode) { "(return)" } + else { "" } + s"${method.classFile.thisType.simpleName}:${method.name}[$pc]$returnOptional{$stmt}" + } +} + +object JavaStatement { + type V = DUVar[ValueInformation] + + implicit class StmtAsCall(stmt: Stmt[JavaStatement.V]) { + def asCall(): Call[V] = stmt.astID match { + case Assignment.ASTID => stmt.asAssignment.expr.asFunctionCall + case ExprStmt.ASTID => stmt.asExprStmt.expr.asFunctionCall + case _ => stmt.asMethodCall + } + } +} diff --git a/build.sbt b/build.sbt index dc7c1dec6e..929aa3417a 100644 --- a/build.sbt +++ b/build.sbt @@ -166,6 +166,7 @@ lazy val `OPAL` = (project in file(".")) ba, ai, ifds, + ide, tac, de, av, @@ -300,6 +301,19 @@ lazy val `IFDS` = (project in file("OPAL/ifds")) .dependsOn(br % "it->it;it->test;test->test;compile->compile") .configs(IntegrationTest) +lazy val ide = `IDE` +lazy val `IDE` = (project in file("OPAL/ide")) + .settings(buildSettings: _*) + .settings( + name := "IDE", + Compile / doc / scalacOptions ++= Opts.doc.title("OPAL - IDE"), + fork := true, + libraryDependencies ++= Dependencies.ide + ) + .dependsOn(si % "it->it;it->test;test->test;compile->compile") + .dependsOn(br % "it->it;it->test;test->test;compile->compile") + .configs(IntegrationTest) + lazy val tac = `ThreeAddressCode` lazy val `ThreeAddressCode` = (project in file("OPAL/tac")) .settings(buildSettings: _*) @@ -314,6 +328,7 @@ lazy val `ThreeAddressCode` = (project in file("OPAL/tac")) ) .dependsOn(ai % "it->it;it->test;test->test;compile->compile") .dependsOn(ifds % "it->it;it->test;test->test;compile->compile") + .dependsOn(ide % "it->it;it->test;test->test;compile->compile") .configs(IntegrationTest) lazy val ba = `BytecodeAssembler` diff --git a/project/Dependencies.scala b/project/Dependencies.scala index c41aeba42e..e907c8abdb 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -77,6 +77,7 @@ object Dependencies { val br = Seq(scalaparsercombinators, scalaxml) val tac = Seq() val ifds = Seq() + val ide = Seq() val tools = Seq(txtmark, jacksonDF) val hermes = Seq(txtmark, jacksonDF, javafxBase) val apk = Seq(apkparser, scalaxml)