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..8acb606954 --- /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(pc = 13, value = 8) + @VariableValues({ + @VariableValue(pc = 2), + @VariableValue(pc = 11), + @VariableValue(pc = 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..f9c953051c --- /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(pc = 18, value = 19), + @ConstantValue(pc = 20, value = 18), + @ConstantValue(pc = 27, value = 7) + }) + @VariableValues({ + @VariableValue(pc = 23), + @VariableValue(pc = 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..88cf3f287b --- /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(pc = 0, value = 4), + @ConstantValue(pc = 1, value = 3), + @ConstantValue(pc = 2, value = 12), + @ConstantValue(pc = 3, value = 4), + @ConstantValue(pc = 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..9e3780f524 --- /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(pc = 4), + @VariableValue(pc = 5), + @VariableValue(pc = 6), + @VariableValue(pc = 8), + @VariableValue(pc = 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..a79f6f4444 --- /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(pc = 3, value = 22) + @VariableValue(pc = 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..a81c3fe946 --- /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(pc = -3), + @VariableValue(pc = -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(pc = 3, value = 139) + public static int linearCalculation3(String msg, int a) { + System.out.println(msg); + return a + 11; + } + + @UnknownValue(pc = 3) + public static int linearCalculation4(String msg, int a) { + System.out.println(msg); + return 3 * a; + } + + @ConstantValues({ + @ConstantValue(pc = 5, value = -18), + @ConstantValue(pc = 8, value = 132), + @ConstantValue(pc = 10, value = 128), + @ConstantValue(pc = 18, value = 139) + }) + @VariableValues({ + @VariableValue(pc = 13), + @VariableValue(pc = 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..aefd41ecc9 --- /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(pc = 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..604125849b --- /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(pc = 0), + @VariableValue(pc = 3), + @VariableValue(pc = 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..50e2aa7c92 --- /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(pc = 1, variableElements = { + @VariableArrayElement(index = 0), + @VariableArrayElement(index = 1), + @VariableArrayElement(index = 2), + @VariableArrayElement(index = 3) + }), + @ArrayValue(pc = 15, constantElements = { + @ConstantArrayElement(index = 0, value = 42), + @ConstantArrayElement(index = 1, value = 23) + }), + @ArrayValue(pc = 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..872a49105e --- /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(pc = 3, unknownElements = { + @UnknownArrayElement(index = 0), + @UnknownArrayElement(index = 1), + @UnknownArrayElement(index = 2), + @UnknownArrayElement(index = 3) + }), + @ArrayValue(pc = 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..40387897bb --- /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(pc = 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(pc = 3, constantElements = { + @ConstantArrayElement(index = 0, value = 0), + @ConstantArrayElement(index = 1, value = 2), + @ConstantArrayElement(index = 2, value = 3), + @ConstantArrayElement(index = 3, value = 4) + }), + @ArrayValue(pc = 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..28f523833b --- /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(pc = 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(pc = 9, value = 0) + @VariableValue(pc = 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..cf1b87cb8e --- /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(pc = 0, constantValues = {@ConstantField(field = "a", value = 33)}) + private CreateObjectInMethodExample createNew2() { + CreateObjectInMethodExample example = new CreateObjectInMethodExample(); + example.a = a + 2; + return example; + } + + @ObjectValue(pc = 0, variableValues = {@VariableField(field = "a")}) + private CreateObjectInMethodExample createNew3() { + CreateObjectInMethodExample example = new CreateObjectInMethodExample(); + example.a = a + 2; + return example; + } + + @ObjectValues({ + @ObjectValue(pc = 0, constantValues = {@ConstantField(field = "a", value = 42)}), + @ObjectValue(pc = 2, constantValues = {@ConstantField(field = "a", value = 31)}), + @ObjectValue(pc = 3, constantValues = {@ConstantField(field = "a", value = 33)}), + @ObjectValue(pc = 4, variableValues = {@VariableField(field = "a")}), + @ObjectValue(pc = 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..f3761174f3 --- /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(pc = 0, unknownValues = {@UnknownField(field = "a")}), + @ObjectValue(pc = 2, constantValues = {@ConstantField(field = "a", value = 42)}), + @ObjectValue(pc = 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..23e04e2d0f --- /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(pc = 0, constantValues = {@ConstantField(field = "a", value = -1)}), + @ObjectValue(pc = 2, constantValues = {@ConstantField(field = "a", value = 42)}), + @ObjectValue(pc = 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..3265020600 --- /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(pc = 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(pc = 0, constantValues = {@ConstantField(field = "a", value = 42)}), + @ObjectValue(pc = 2, variableValues = {@VariableField(field = "a")}), + @ObjectValue(pc = 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..d5b429fd08 --- /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(pc = 0) + @ObjectValues({ + @ObjectValue(pc = 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..d8c62c0dac --- /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(pc = 0, value = 42), + @ConstantValue(pc = 1, value = 23), + @ConstantValue(pc = 3, value = 0), + @ConstantValue(pc = 4, value = 2) + }) + @VariableValue(pc = 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..b4aeef6a52 --- /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(pc = 0), + @VariableValue(pc = 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..088c4fce73 --- /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(pc = 3, value = 11), + @ConstantValue(pc = 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..b2edc5d1cb --- /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(pc = 5, value = 23), + @ConstantValue(pc = 10, value = 11) + }) + @VariableValue(pc = 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..01ac604e7d --- /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 PC of this variable in the bytecode + */ + int pc(); + + /** + * 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..91737d265d --- /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 PC of this variable in the bytecode + */ + int pc(); +} 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..2de7e79bd2 --- /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 PC of this variable in the bytecode + */ + int pc(); +} 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..f43977a1fb --- /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 PC of the variable the array is stored in + */ + int pc(); + + /** + * 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..f35ef1207b --- /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 PC of the variable the object is stored in + */ + int pc(); + + /** + * 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..c57e963e32 --- /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 PC of this variable in the bytecode + */ + int pc(); +} 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..dd9d0387b5 --- /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 PC of this variable in the bytecode + */ + int pc(); +} 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..1ec24143bd --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ide/linear_constant_propagation/LCPOnFieldsTests.scala @@ -0,0 +1,59 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package fpcf +package ide +package linear_constant_propagation + +import org.opalj.br.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) + 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/AbstractRepeatablePropertyMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/AbstractRepeatablePropertyMatcher.scala new file mode 100644 index 0000000000..ea80b44e55 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/AbstractRepeatablePropertyMatcher.scala @@ -0,0 +1,70 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package fpcf +package properties + +import org.opalj.br.AnnotationLike +import org.opalj.br.ObjectType +import org.opalj.br.analyses.Project + +/** + * Basic property matcher for repeatable annotations. + * + * @author Robin Körkemeier + */ +abstract class AbstractRepeatablePropertyMatcher extends AbstractPropertyMatcher { + /** + * Type for identifying the single annotation + */ + val singleAnnotationType: ObjectType + /** + * Type for identifying the container annotation + */ + val containerAnnotationType: ObjectType + + /** + * The name of the field of the container annotation that holds the single annotations + */ + val containerAnnotationFieldName: String = "value" + + override def validateProperty( + p: Project[?], + as: Set[ObjectType], + entity: Any, + a: AnnotationLike, + properties: Iterable[Property] + ): Option[String] = { + if (a.annotationType == singleAnnotationType) { + validateSingleProperty(p, as, entity, a, properties) + } else if (a.annotationType == containerAnnotationType) { + val subAnnotations = + getValue(p, containerAnnotationType, a.elementValuePairs, containerAnnotationFieldName) + .asArrayValue.values.map { a => a.asAnnotationValue.annotation } + + val errors = subAnnotations + .map { a => validateSingleProperty(p, as, entity, a, properties) } + .filter { result => result.isDefined } + .map { result => result.get } + + if (errors.nonEmpty) { + Some(errors.mkString(" ")) + } else { + None + } + } else { + Some(s"Invalid annotation '${a.annotationType}' for ${this.getClass.getName}!") + } + } + + /** + * Test if the computed properties are matched by this matcher. Called for each single annotation of a container + * annotation once. + */ + def validateSingleProperty( + p: Project[?], + as: Set[ObjectType], + entity: Any, + a: AnnotationLike, + properties: Iterable[Property] + ): Option[String] +} 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..6b5de2a910 --- /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.ObjectType +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: ObjectType + + /** + * 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: ObjectType, + 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: ObjectType, + 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: ObjectType, + 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: ObjectType, + 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..06548684da --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/linear_constant_propagation/LCPOnFieldsMatcher.scala @@ -0,0 +1,378 @@ +/* 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.Method +import org.opalj.br.ObjectType +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: ObjectType = + ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ObjectValue") + override val containerAnnotationType: ObjectType = + ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ObjectValues") + + private val constantFieldType = + ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ConstantField") + private val variableValueType = ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/lcp/VariableField") + private val unknownValueType = ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/lcp/UnknownField") + + override def validateSingleProperty( + p: Project[?], + as: Set[ObjectType], + entity: Any, + a: AnnotationLike, + properties: Iterable[Property] + ): Option[String] = { + val expectedVariablePC = getValue(p, singleAnnotationType, a.elementValuePairs, "pc").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)) => + expectedVariablePC == 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("?", expectedVariablePC)}, ${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: ObjectType = + ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ArrayValue") + override val containerAnnotationType: ObjectType = + ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ArrayValues") + + private val constantArrayElementType = + ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ConstantArrayElement") + private val variableArrayElementType = + ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/VariableArrayElement") + private val unknownArrayElementType = + ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/UnknownArrayElement") + + override def validateSingleProperty( + p: Project[?], + as: Set[ObjectType], + entity: Any, + a: AnnotationLike, + properties: Iterable[Property] + ): Option[String] = { + val expectedVariablePC = + getValue(p, singleAnnotationType, a.elementValuePairs, "pc").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) + ) => + expectedVariablePC == 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("?", expectedVariablePC)}, 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: ObjectType = + ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/StaticValues") + + private val constantFieldType = + ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/ConstantField") + private val variableValueType = ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/lcp/VariableField") + private val unknownValueType = ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/lcp/UnknownField") + + override def validateProperty( + p: Project[?], + as: Set[ObjectType], + entity: Any, + a: AnnotationLike, + properties: Iterable[Property] + ): Option[String] = { + val entityObjectType = 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.objectType == entityObjectType && 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.objectType == entityObjectType && 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.objectType == entityObjectType && 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(entityObjectType, 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: ObjectType = + ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/VariableValue") + override val containerAnnotationType: ObjectType = + ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/VariableValues") + + override def validateSingleProperty( + p: Project[?], + as: Set[ObjectType], + entity: Any, + a: AnnotationLike, + properties: Iterable[Property] + ): Option[String] = { + val expectedVariablePC = + getValue(p, singleAnnotationType, a.elementValuePairs, "pc").asIntValue.value + + if (existsBasicIDEPropertyResult( + properties, + { + case (f: lcp_on_fields.problem.AbstractObjectFact, lcp_on_fields.problem.VariableValue) => + expectedVariablePC == f.definedAtIndex + case (f: lcp_on_fields.problem.AbstractArrayFact, lcp_on_fields.problem.VariableValue) => + expectedVariablePC == f.definedAtIndex + + case _ => false + } + ) + ) { + None + } else { + Some( + s"Result should contain (${lcp_on_fields.problem.ObjectFact("?", expectedVariablePC)}, ${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: ObjectType = + ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/UnknownValue") + override val containerAnnotationType: ObjectType = + ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/lcp_on_fields/UnknownValues") + + override def validateSingleProperty( + p: Project[?], + as: Set[ObjectType], + entity: Any, + a: AnnotationLike, + properties: Iterable[Property] + ): Option[String] = { + val expectedVariablePC = + getValue(p, singleAnnotationType, a.elementValuePairs, "pc").asIntValue.value + + if (existsBasicIDEPropertyResult( + properties, + { + case (f: lcp_on_fields.problem.AbstractObjectFact, lcp_on_fields.problem.UnknownValue) => + expectedVariablePC == f.definedAtIndex + case (f: lcp_on_fields.problem.AbstractArrayFact, lcp_on_fields.problem.UnknownValue) => + expectedVariablePC == f.definedAtIndex + + case _ => false + } + ) + ) { + None + } else { + Some( + s"Result should contain (${lcp_on_fields.problem.ObjectFact("?", expectedVariablePC)}, ${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..8921655bb9 --- /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.ObjectType +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: ObjectType = + ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/lcp/ConstantValue") + override val containerAnnotationType: ObjectType = + ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/lcp/ConstantValues") + + override def validateSingleProperty( + p: Project[?], + as: Set[ObjectType], + entity: Any, + a: AnnotationLike, + properties: Iterable[Property] + ): Option[String] = { + val expectedVariablePC = + getValue(p, singleAnnotationType, a.elementValuePairs, "pc").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) + ) => + expectedVariablePC == definedAtIndex && expectedVariableValue == value + + case _ => false + } + ) + ) { + None + } else { + Some( + s"Result should contain (${linear_constant_propagation.problem.VariableFact("?", expectedVariablePC)}, ${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: ObjectType = + ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/lcp/VariableValue") + override val containerAnnotationType: ObjectType = + ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/lcp/VariableValues") + + override def validateSingleProperty( + p: Project[?], + as: Set[ObjectType], + entity: Any, + a: AnnotationLike, + properties: Iterable[Property] + ): Option[String] = { + val expectedVariablePC = + getValue(p, singleAnnotationType, a.elementValuePairs, "pc").asIntValue.value + + if (existsBasicIDEPropertyResult( + properties, + { + case ( + linear_constant_propagation.problem.VariableFact(_, definedAtIndex), + linear_constant_propagation.problem.VariableValue + ) => + expectedVariablePC == definedAtIndex + + case _ => false + } + ) + ) { + None + } else { + Some( + s"Result should contain (${linear_constant_propagation.problem.VariableFact("?", expectedVariablePC)}, ${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: ObjectType = + ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/lcp/UnknownValue") + override val containerAnnotationType: ObjectType = + ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/lcp/UnknownValues") + + override def validateSingleProperty( + p: Project[?], + as: Set[ObjectType], + entity: Any, + a: AnnotationLike, + properties: Iterable[Property] + ): Option[String] = { + val expectedVariablePC = + getValue(p, singleAnnotationType, a.elementValuePairs, "pc").asIntValue.value + + if (existsBasicIDEPropertyResult( + properties, + { + case ( + linear_constant_propagation.problem.VariableFact(_, definedAtIndex), + linear_constant_propagation.problem.UnknownValue + ) => + expectedVariablePC == definedAtIndex + + case _ => false + } + ) + ) { + None + } else { + Some( + s"Result should contain (${linear_constant_propagation.problem.VariableFact("?", expectedVariablePC)}, ${linear_constant_propagation.problem.UnknownValue})!" + ) + } + } +} diff --git a/OPAL/ProjectDependencies.mmd b/OPAL/ProjectDependencies.mmd index 2a87117f2d..20df0ab278 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] @@ -39,6 +40,9 @@ flowchart BT br --> bi da --> bi + ide --> si + ide --> br + ifds --> si ifds --> br @@ -49,6 +53,7 @@ flowchart BT de --> ai tac --> ifds + tac --> ide tac --> ai ll --> tac @@ -67,4 +72,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 1cd89db7f5..5c6e526c22 100644 Binary files a/OPAL/ProjectDependencies.pdf and b/OPAL/ProjectDependencies.pdf differ diff --git a/OPAL/ProjectDependencies.svg b/OPAL/ProjectDependencies.svg index dd235d90a7..eee5b662b3 100644 --- a/OPAL/ProjectDependencies.svg +++ b/OPAL/ProjectDependencies.svg @@ -1 +1 @@ -CommoncommonStatic Analysis InfrastructuresiBytecode InfrastructurebiBytecode RepresentationbrBytecode DisassemblerdaIFDSifdsAbstract Interpretation FrameworkaiBytecode CreatorbcThree Address CodetacDependency ExtractiondeBytecode AssemblerbaLLVMllAPKapkArchitecture ValidationavFrameworkDemosBugPickerbpHermeshermes \ No newline at end of file +CommoncommonStatic Analysis InfrastructuresiBytecode InfrastructurebiBytecode RepresentationbrBytecode DisassemblerdaIDEideIFDSifdsAbstract Interpretation FrameworkaiBytecode CreatorbcThree Address CodetacDependency ExtractiondeBytecode AssemblerbaLLVMllAPKapkArchitecture ValidationavFrameworkDemosBugPickerbpHermeshermes \ 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..bba035630b --- /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.FPCFAnalysis +import org.opalj.br.fpcf.FPCFAnalysisScheduler +import org.opalj.br.fpcf.PropertyStoreKey +import org.opalj.fpcf.Entity +import org.opalj.fpcf.PartialResult +import org.opalj.fpcf.PropertyBounds +import org.opalj.fpcf.PropertyStore +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..29c3085672 --- /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 as MutableMap} + +import org.opalj.br.analyses.ProjectInformationKeys +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.FPCFAnalysis +import org.opalj.fpcf.Entity +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..196a05e7a8 --- /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.FPCFAnalysis +import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler +import org.opalj.br.fpcf.PropertyStoreKey +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.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..a61466dc46 --- /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.ObjectType +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 object type the field belongs to + */ + val objectType: ObjectType + + /** + * The name of the field + */ + val fieldName: String + + def toStaticFieldFact: AbstractStaticFieldFact = StaticFieldFact(objectType, fieldName) +} + +/** + * Fact representing a seen static field. + * + * @author Robin Körkemeier + */ +case class StaticFieldFact(objectType: ObjectType, fieldName: String) extends AbstractStaticFieldFact { + override def toStaticFieldFact: StaticFieldFact = this + + override def toString: String = s"StaticFieldFact(${objectType.simpleName}, $fieldName)" +} + +/** + * Fact representing a seen static field and modeling that it gets written. + * + * @author Robin Körkemeier + */ +case class PutStaticFieldFact(objectType: ObjectType, fieldName: String) extends AbstractStaticFieldFact { + override def toString: String = s"PutStaticFieldFact(${objectType.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..ed44ae6116 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/problem/LCPOnFieldsProblem.scala @@ -0,0 +1,817 @@ +/* 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.isObjectType => ObjectFact(s"param${index + 1}", -(index + 2)) + case (paramType, index) if paramType.isObjectType => 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.objectType, 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 + .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.objectType == 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.isObjectType) { + 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 + .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.objectType) { + 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.objectType) { + 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.objectType) { + 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 + .fold[Object](linear_constant_propagation.problem.UnknownValue: LinearConstantPropagationValue) { + case ( + (linear_constant_propagation.problem.VariableFact(_, dAI), v: LinearConstantPropagationValue), + value: LinearConstantPropagationValue + ) if var0.definedBy.contains(dAI) => LinearConstantPropagationLattice.meet(v, value) + case ( + value: LinearConstantPropagationValue, + (linear_constant_propagation.problem.VariableFact(_, dAI), v: LinearConstantPropagationValue) + ) if var0.definedBy.contains(dAI) => LinearConstantPropagationLattice.meet(v, value) + + case (_, value: LinearConstantPropagationValue) => value + case (value: LinearConstantPropagationValue, _) => value + }.asInstanceOf[LinearConstantPropagationValue] + } + + 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.isObjectType) { + 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.objectType) { + 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.isObjectType && + callStmt.declaringClass.asObjectType.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.isObjectType) { + 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.isObjectType && + callStmt.declaringClass.asObjectType.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.objectType, f.fieldName, IntegerType) + if (!declaredField.isDefinedField) { + return PutStaticFieldEdgeFunction(linear_constant_propagation.problem.VariableValue) + } + val field = declaredField.definedField + + if (callStmt.declaringClass != f.objectType && 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..cb0aa7cae4 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/problem/LinearConstantPropagationProblemExtended.scala @@ -0,0 +1,243 @@ +/* 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.ObjectType +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 + .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 + .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 objectType = 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(objectType, fieldName)(property) match { + case UnknownValue => UnknownValueEdgeFunction + case ConstantValue(c) => LinearCombinationEdgeFunction(0, c, lattice.top) + case VariableValue => VariableValueEdgeFunction + }) + + case InterimUBP(property) => + getStaticFieldFromProperty(objectType, 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( + objectType: ObjectType, + fieldName: String + )(property: LCPOnFieldsPropertyMetaInformation.Self): LinearConstantPropagationValue = { + property + .results + .collect { + case (f: AbstractStaticFieldFact, StaticFieldValue(value)) + if f.objectType == objectType && f.fieldName == fieldName => value + case (f: AbstractStaticFieldFact, LCPVariableValue) + if f.objectType == objectType && 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..a696ecb794 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/problem/LinearConstantPropagationProblem.scala @@ -0,0 +1,633 @@ +/* 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 + .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..12b2caf704 --- /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.PropertyStoreKey +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.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 14ed975fce..527a96217f 100644 --- a/build.sbt +++ b/build.sbt @@ -179,6 +179,7 @@ lazy val `OPAL` = (project in file(".")) ba, ai, ifds, + ide, tac, de, av, @@ -313,6 +314,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: _*) @@ -326,6 +340,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 be2ea989e3..bbdd5a156e 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -77,6 +77,7 @@ object Dependencies { val bi = Seq(commonstext) val br = Seq(scalaparsercombinators, scalaxml) val ifds = Seq() + val ide = Seq() val tools = Seq(txtmark, jacksonDF) val hermes = Seq(txtmark, jacksonDF, javafxBase) val apk = Seq(apkparser, scalaxml)