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 @@
-
\ No newline at end of file
+
\ 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)