diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/lcp_on_fields/ArrayNativeMethodExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/lcp_on_fields/ArrayNativeMethodExample.java
new file mode 100644
index 0000000000..dec1119a49
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/lcp_on_fields/ArrayNativeMethodExample.java
@@ -0,0 +1,40 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.fixtures.lcp_on_fields;
+
+import org.opalj.fpcf.properties.lcp_on_fields.ArrayValue;
+import org.opalj.fpcf.properties.lcp_on_fields.ArrayValues;
+import org.opalj.fpcf.properties.lcp_on_fields.ConstantArrayElement;
+import org.opalj.fpcf.properties.lcp_on_fields.VariableArrayElement;
+
+public class ArrayNativeMethodExample {
+ @ArrayValues({
+ @ArrayValue(variable = "lv1", variableElements = {
+ @VariableArrayElement(index = 0),
+ @VariableArrayElement(index = 1),
+ @VariableArrayElement(index = 2),
+ @VariableArrayElement(index = 3)
+ }),
+ @ArrayValue(variable = "lvf", constantElements = {
+ @ConstantArrayElement(index = 0, value = 42),
+ @ConstantArrayElement(index = 1, value = 23)
+ }),
+ @ArrayValue(variable = "lv17", 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/lcp_on_fields/ArrayReadWriteAcrossMethodsExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/lcp_on_fields/ArrayReadWriteAcrossMethodsExample.java
new file mode 100644
index 0000000000..03b411c78c
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/lcp_on_fields/ArrayReadWriteAcrossMethodsExample.java
@@ -0,0 +1,38 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.fixtures.lcp_on_fields;
+
+import org.opalj.fpcf.properties.lcp_on_fields.ArrayValue;
+import org.opalj.fpcf.properties.lcp_on_fields.ArrayValues;
+import org.opalj.fpcf.properties.lcp_on_fields.ConstantArrayElement;
+import org.opalj.fpcf.properties.lcp_on_fields.VariableArrayElement;
+
+public class ArrayReadWriteAcrossMethodsExample {
+ public void setIndexTo23(int[] arr, int index) {
+ arr[index] = 23;
+ }
+
+ public void set11To42(int[] arr) {
+ arr[11] = 42;
+ }
+
+ @ArrayValues({
+ @ArrayValue(variable = "lv3", variableElements = {
+ @VariableArrayElement(index = 0),
+ @VariableArrayElement(index = 1),
+ @VariableArrayElement(index = 2),
+ @VariableArrayElement(index = 3)
+ }),
+ @ArrayValue(variable = "lv5", 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/lcp_on_fields/ArrayReadWriteConstantExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/lcp_on_fields/ArrayReadWriteConstantExample.java
new file mode 100644
index 0000000000..53695abc86
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/lcp_on_fields/ArrayReadWriteConstantExample.java
@@ -0,0 +1,35 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.fixtures.lcp_on_fields;
+
+import org.opalj.fpcf.properties.lcp_on_fields.ArrayValue;
+import org.opalj.fpcf.properties.lcp_on_fields.ArrayValues;
+import org.opalj.fpcf.properties.lcp_on_fields.ConstantArrayElement;
+
+public class ArrayReadWriteConstantExample {
+ @ArrayValues({
+ @ArrayValue(variable = "lv1", 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(variable = "lv3", constantElements = {
+ @ConstantArrayElement(index = 0, value = 0),
+ @ConstantArrayElement(index = 1, value = 2),
+ @ConstantArrayElement(index = 2, value = 3),
+ @ConstantArrayElement(index = 3, value = 4)
+ })
+ })
+ public static void main(String[] args) {
+ int[] arr1 = new int[5];
+ int[] arr2 = new int[]{1, 2, 3, 4};
+
+ 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] + "}");
+ }
+}
diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/lcp_on_fields/ArrayUnknownIndicesExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/lcp_on_fields/ArrayUnknownIndicesExample.java
new file mode 100644
index 0000000000..008c13c84c
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/lcp_on_fields/ArrayUnknownIndicesExample.java
@@ -0,0 +1,47 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.fixtures.lcp_on_fields;
+
+import org.opalj.fpcf.properties.lcp_on_fields.ArrayValue;
+import org.opalj.fpcf.properties.lcp_on_fields.ConstantArrayElement;
+import org.opalj.fpcf.properties.lcp_on_fields.VariableArrayElement;
+import org.opalj.fpcf.properties.linear_constant_propagation.ConstantValue;
+import org.opalj.fpcf.properties.linear_constant_propagation.VariableValue;
+
+public class ArrayUnknownIndicesExample {
+ @ArrayValue(variable = "lv1", 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(variable = "lv9", value = 0)
+ @VariableValue(variable = "lvf")
+ 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/lcp_on_fields/CreateObjectInMethodExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/lcp_on_fields/CreateObjectInMethodExample.java
new file mode 100644
index 0000000000..9e6e9192be
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/lcp_on_fields/CreateObjectInMethodExample.java
@@ -0,0 +1,36 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.fixtures.lcp_on_fields;
+
+import org.opalj.fpcf.properties.lcp_on_fields.ObjectValue;
+import org.opalj.fpcf.properties.lcp_on_fields.ObjectValues;
+import org.opalj.fpcf.properties.linear_constant_propagation.ConstantValue;
+import org.opalj.fpcf.properties.linear_constant_propagation.UnknownValue;
+
+public class CreateObjectInMethodExample {
+ private int a = 42;
+
+ private CreateObjectInMethodExample createNew1() {
+ CreateObjectInMethodExample example = new CreateObjectInMethodExample();
+ example.a -= 11;
+ return example;
+ }
+
+ private CreateObjectInMethodExample createNew2() {
+ CreateObjectInMethodExample example = new CreateObjectInMethodExample();
+ example.a = a + 2;
+ return example;
+ }
+
+ @ObjectValues({
+ @ObjectValue(variable = "lv0", constantValues = {@ConstantValue(variable = "a", value = 42)}),
+ @ObjectValue(variable = "lv2", constantValues = {@ConstantValue(variable = "a", value = 31)}),
+ @ObjectValue(variable = "lv3", unknownValues = {@UnknownValue(variable = "a")})
+ })
+ public static void main(String[] args) {
+ CreateObjectInMethodExample example1 = new CreateObjectInMethodExample();
+ CreateObjectInMethodExample example2 = example1.createNew1();
+ CreateObjectInMethodExample example3 = example2.createNew2();
+
+ 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/lcp_on_fields/FieldReadWriteAcrossMethodsExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/lcp_on_fields/FieldReadWriteAcrossMethodsExample.java
new file mode 100644
index 0000000000..88c0d0d3f6
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/lcp_on_fields/FieldReadWriteAcrossMethodsExample.java
@@ -0,0 +1,44 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.fixtures.lcp_on_fields;
+
+import org.opalj.fpcf.properties.lcp_on_fields.ObjectValue;
+import org.opalj.fpcf.properties.lcp_on_fields.ObjectValues;
+import org.opalj.fpcf.properties.linear_constant_propagation.ConstantValue;
+import org.opalj.fpcf.properties.linear_constant_propagation.VariableValue;
+
+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(variable = "lv0", variableValues = {@VariableValue(variable = "a")}),
+ @ObjectValue(variable = "lv2", constantValues = {@ConstantValue(variable = "a", value = 42)}),
+ @ObjectValue(variable = "lv4", constantValues = {@ConstantValue(variable = "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/lcp_on_fields/FieldReadWriteConstantExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/lcp_on_fields/FieldReadWriteConstantExample.java
new file mode 100644
index 0000000000..6f3e6f8046
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/lcp_on_fields/FieldReadWriteConstantExample.java
@@ -0,0 +1,29 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.fixtures.lcp_on_fields;
+
+import org.opalj.fpcf.properties.lcp_on_fields.ObjectValue;
+import org.opalj.fpcf.properties.lcp_on_fields.ObjectValues;
+import org.opalj.fpcf.properties.linear_constant_propagation.ConstantValue;
+
+public class FieldReadWriteConstantExample {
+ private int a = -1;
+
+ @ObjectValues({
+ @ObjectValue(variable = "lv0", constantValues = {@ConstantValue(variable = "a", value = -1)}),
+ @ObjectValue(variable = "lv2", constantValues = {@ConstantValue(variable = "a", value = 42)}),
+ @ObjectValue(variable = "lv4", constantValues = {@ConstantValue(variable = "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/lcp_on_fields/FieldReadWriteWithBranchingExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/lcp_on_fields/FieldReadWriteWithBranchingExample.java
new file mode 100644
index 0000000000..c945a3e33a
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/lcp_on_fields/FieldReadWriteWithBranchingExample.java
@@ -0,0 +1,46 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.fixtures.lcp_on_fields;
+
+import org.opalj.fpcf.properties.lcp_on_fields.ObjectValue;
+import org.opalj.fpcf.properties.lcp_on_fields.ObjectValues;
+import org.opalj.fpcf.properties.linear_constant_propagation.ConstantValue;
+import org.opalj.fpcf.properties.linear_constant_propagation.VariableValue;
+
+public class FieldReadWriteWithBranchingExample {
+ private int a = -1;
+
+ @ObjectValue(variable = "lv0", variableValues = {@VariableValue(variable = "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(variable = "lv0", constantValues = {@ConstantValue(variable = "a", value = 42)}),
+ @ObjectValue(variable = "lv2", variableValues = {@VariableValue(variable = "a")}),
+ @ObjectValue(variable = "lv4", variableValues = {@VariableValue(variable = "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/lcp_on_fields/ObjectNativeMethodExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/lcp_on_fields/ObjectNativeMethodExample.java
new file mode 100644
index 0000000000..c570bb1718
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/lcp_on_fields/ObjectNativeMethodExample.java
@@ -0,0 +1,26 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.fixtures.lcp_on_fields;
+
+import org.opalj.fpcf.properties.lcp_on_fields.ObjectValue;
+import org.opalj.fpcf.properties.lcp_on_fields.ObjectValues;
+import org.opalj.fpcf.properties.lcp_on_fields.VariableValue;
+import org.opalj.fpcf.properties.linear_constant_propagation.ConstantValue;
+
+public class ObjectNativeMethodExample {
+ int a = 2;
+
+ @VariableValue(variable = "lv0")
+ @ObjectValues({
+ @ObjectValue(variable = "lv2", constantValues = {
+ @ConstantValue(variable = "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/lcp_on_fields/StaticFieldImmutableExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/lcp_on_fields/StaticFieldImmutableExample.java
new file mode 100644
index 0000000000..4839af5299
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/lcp_on_fields/StaticFieldImmutableExample.java
@@ -0,0 +1,50 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.fixtures.lcp_on_fields;
+
+import org.opalj.fpcf.properties.lcp_on_fields.StaticValues;
+import org.opalj.fpcf.properties.linear_constant_propagation.ConstantValue;
+import org.opalj.fpcf.properties.linear_constant_propagation.ConstantValues;
+import org.opalj.fpcf.properties.linear_constant_propagation.VariableValue;
+
+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 = {
+ @ConstantValue(variable = "a", value = 42),
+ @ConstantValue(variable = "b", value = 23),
+ @ConstantValue(variable = "d", value = 0)
+ }, variableValues = {
+ @VariableValue(variable = "c"),
+ @VariableValue(variable = "e")
+ })
+ @ConstantValues({
+ @ConstantValue(variable = "lv0", value = 42),
+ @ConstantValue(variable = "lv1", value = 23),
+ @ConstantValue(variable = "lv3", value = 0),
+ @ConstantValue(variable = "lv4", value = 2)
+ })
+ @VariableValue(variable = "lv2")
+ 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/lcp_on_fields/StaticFieldNonImmutableExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/lcp_on_fields/StaticFieldNonImmutableExample.java
new file mode 100644
index 0000000000..1c3907716e
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/lcp_on_fields/StaticFieldNonImmutableExample.java
@@ -0,0 +1,32 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.fixtures.lcp_on_fields;
+
+import org.opalj.fpcf.properties.lcp_on_fields.StaticValues;
+import org.opalj.fpcf.properties.linear_constant_propagation.VariableValue;
+import org.opalj.fpcf.properties.linear_constant_propagation.VariableValues;
+
+public class StaticFieldNonImmutableExample {
+ static int a = 42;
+ protected static int b = 23;
+
+ @StaticValues(variableValues = {
+ @VariableValue(variable = "a"),
+ @VariableValue(variable = "b")
+ })
+ @VariableValues({
+ @VariableValue(variable = "lv0"),
+ @VariableValue(variable = "lv1")
+ })
+ 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/lcp_on_fields/StaticFieldReadWriteAcrossMethodsExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/lcp_on_fields/StaticFieldReadWriteAcrossMethodsExample.java
new file mode 100644
index 0000000000..e1397e08c1
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/lcp_on_fields/StaticFieldReadWriteAcrossMethodsExample.java
@@ -0,0 +1,39 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.fixtures.lcp_on_fields;
+
+import org.opalj.fpcf.properties.lcp_on_fields.StaticValues;
+import org.opalj.fpcf.properties.linear_constant_propagation.ConstantValue;
+import org.opalj.fpcf.properties.linear_constant_propagation.ConstantValues;
+
+public class StaticFieldReadWriteAcrossMethodsExample {
+ private static int a;
+
+ public void setATo11() {
+ a = 11;
+ }
+
+ private void setATo42() {
+ a = 42;
+ }
+
+ @StaticValues(constantValues = {
+ @ConstantValue(variable = "a", value = 42)
+ })
+ @ConstantValues({
+ @ConstantValue(variable = "lv3", value = 11),
+ @ConstantValue(variable = "lv5", 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/lcp_on_fields/StaticFieldReadWriteExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/lcp_on_fields/StaticFieldReadWriteExample.java
new file mode 100644
index 0000000000..f8c1e6adcc
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/lcp_on_fields/StaticFieldReadWriteExample.java
@@ -0,0 +1,37 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.fixtures.lcp_on_fields;
+
+import org.opalj.fpcf.properties.lcp_on_fields.StaticValues;
+import org.opalj.fpcf.properties.linear_constant_propagation.ConstantValue;
+import org.opalj.fpcf.properties.linear_constant_propagation.ConstantValues;
+import org.opalj.fpcf.properties.linear_constant_propagation.VariableValue;
+
+public class StaticFieldReadWriteExample {
+ private static int a;
+
+ @StaticValues(constantValues = {
+ @ConstantValue(variable = "a", value = 11)
+ })
+ @ConstantValues({
+ @ConstantValue(variable = "lv5", value = 23),
+ @ConstantValue(variable = "lva", value = 11)
+ })
+ @VariableValue(variable = "lv2")
+ 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/fixtures/linear_constant_propagation/BranchingConstantsExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/BranchingConstantsExample.java
new file mode 100644
index 0000000000..e9589d1a7d
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/BranchingConstantsExample.java
@@ -0,0 +1,35 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.fixtures.linear_constant_propagation;
+
+import org.opalj.fpcf.properties.linear_constant_propagation.ConstantValue;
+import org.opalj.fpcf.properties.linear_constant_propagation.VariableValue;
+import org.opalj.fpcf.properties.linear_constant_propagation.VariableValues;
+
+public class BranchingConstantsExample {
+ @ConstantValue(variable = "lvd", value = 8)
+ @VariableValues({
+ @VariableValue(variable = "lv2"),
+ @VariableValue(variable = "lvb"),
+ @VariableValue(variable = "lvf")
+ })
+ 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/BranchingLinearCombinationExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/BranchingLinearCombinationExample.java
new file mode 100644
index 0000000000..eb95664761
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/BranchingLinearCombinationExample.java
@@ -0,0 +1,61 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.fixtures.linear_constant_propagation;
+
+import org.opalj.fpcf.properties.linear_constant_propagation.ConstantValue;
+import org.opalj.fpcf.properties.linear_constant_propagation.ConstantValues;
+import org.opalj.fpcf.properties.linear_constant_propagation.VariableValue;
+import org.opalj.fpcf.properties.linear_constant_propagation.VariableValues;
+
+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(variable = "lv12", value = 19),
+ @ConstantValue(variable = "lv14", value = 18),
+ @ConstantValue(variable = "lv1b", value = 7)
+ })
+ @VariableValues({
+ @VariableValue(variable = "lv17"),
+ @VariableValue(variable = "lv1e")
+ })
+ 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/ConstantsWithinMethodExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/ConstantsWithinMethodExample.java
new file mode 100644
index 0000000000..05e0cadcbe
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/ConstantsWithinMethodExample.java
@@ -0,0 +1,22 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.fixtures.linear_constant_propagation;
+
+import org.opalj.fpcf.properties.linear_constant_propagation.ConstantValue;
+import org.opalj.fpcf.properties.linear_constant_propagation.ConstantValues;
+
+public class ConstantsWithinMethodExample {
+ @ConstantValues({
+ @ConstantValue(variable = "lv0", value = 4),
+ @ConstantValue(variable = "lv1", value = 3),
+ @ConstantValue(variable = "lv2", value = 12),
+ @ConstantValue(variable = "lv3", value = 4),
+ @ConstantValue(variable = "lv4", 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/FieldAccessExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/FieldAccessExample.java
new file mode 100644
index 0000000000..29fde447c2
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/FieldAccessExample.java
@@ -0,0 +1,40 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.fixtures.linear_constant_propagation;
+
+import org.opalj.fpcf.properties.linear_constant_propagation.VariableValue;
+import org.opalj.fpcf.properties.linear_constant_propagation.VariableValues;
+
+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(variable = "lv4"),
+ @VariableValue(variable = "lv5"),
+ @VariableValue(variable = "lv6"),
+ @VariableValue(variable = "lv8"),
+ @VariableValue(variable = "lvb")
+ })
+ 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/LoopExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/LoopExample.java
new file mode 100644
index 0000000000..41c8de95bf
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/LoopExample.java
@@ -0,0 +1,40 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.fixtures.linear_constant_propagation;
+
+import org.opalj.fpcf.properties.linear_constant_propagation.ConstantValue;
+import org.opalj.fpcf.properties.linear_constant_propagation.VariableValue;
+
+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(variable = "lv3", value = 22)
+ @VariableValue(variable = "lv1")
+ 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/PropagationAcrossMethodsExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/PropagationAcrossMethodsExample.java
new file mode 100644
index 0000000000..a98c07c245
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/PropagationAcrossMethodsExample.java
@@ -0,0 +1,44 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.fixtures.linear_constant_propagation;
+
+import org.opalj.fpcf.properties.linear_constant_propagation.ConstantValue;
+import org.opalj.fpcf.properties.linear_constant_propagation.ConstantValues;
+import org.opalj.fpcf.properties.linear_constant_propagation.VariableValue;
+import org.opalj.fpcf.properties.linear_constant_propagation.VariableValues;
+
+public class PropagationAcrossMethodsExample {
+ @VariableValue(variable = "param2")
+ @VariableValue(variable = "param3")
+ 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;
+ }
+
+ @ConstantValues({
+ @ConstantValue(variable = "lv5", value = -18),
+ @ConstantValue(variable = "lv8", value = 132),
+ @ConstantValue(variable = "lva", value = 128)
+ })
+ @VariableValues({
+ @VariableValue(variable = "lvd"),
+ @VariableValue(variable = "lv10")
+ })
+ 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);
+
+ 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/RecursionExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/RecursionExample.java
new file mode 100644
index 0000000000..e724b4d6bc
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/RecursionExample.java
@@ -0,0 +1,23 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.fixtures.linear_constant_propagation;
+
+import org.opalj.fpcf.properties.linear_constant_propagation.ConstantValue;
+
+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(variable = "lv1", 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/VariablesWithinMethodExample.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/VariablesWithinMethodExample.java
new file mode 100644
index 0000000000..980de0dddb
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/linear_constant_propagation/VariablesWithinMethodExample.java
@@ -0,0 +1,20 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.fixtures.linear_constant_propagation;
+
+import org.opalj.fpcf.properties.linear_constant_propagation.VariableValue;
+import org.opalj.fpcf.properties.linear_constant_propagation.VariableValues;
+
+public class VariablesWithinMethodExample {
+ @VariableValues({
+ @VariableValue(variable = "lv0"),
+ @VariableValue(variable = "lv3"),
+ @VariableValue(variable = "lv6")
+ })
+ 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/properties/lcp_on_fields/ArrayValue.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/ArrayValue.java
new file mode 100644
index 0000000000..8a8268c3db
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/ArrayValue.java
@@ -0,0 +1,36 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.properties.lcp_on_fields;
+
+import org.opalj.fpcf.properties.PropertyValidator;
+
+import java.lang.annotation.*;
+
+/**
+ * Annotation to state that an array has been identified and has certain constant and non-constant elements
+ */
+@PropertyValidator(key = LCPOnFieldsProperty.KEY, validator = ArrayValueMatcher.class)
+@Repeatable(ArrayValues.class)
+@Documented
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.CLASS)
+public @interface ArrayValue {
+ /**
+ * The name of the variable the array is stored in
+ */
+ String variable();
+
+ /**
+ * 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/lcp_on_fields/ArrayValues.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/ArrayValues.java
new file mode 100644
index 0000000000..e1c9280df2
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/ArrayValues.java
@@ -0,0 +1,17 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.properties.lcp_on_fields;
+
+import org.opalj.fpcf.properties.PropertyValidator;
+
+import java.lang.annotation.*;
+
+/**
+ * Container annotation for {@link ArrayValue} annotations
+ */
+@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/lcp_on_fields/ConstantArrayElement.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/ConstantArrayElement.java
new file mode 100644
index 0000000000..292975d90c
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/ConstantArrayElement.java
@@ -0,0 +1,21 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.properties.lcp_on_fields;
+
+import java.lang.annotation.*;
+
+/**
+ * Annotation to state that an array element has a constant value
+ */
+@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/lcp_on_fields/LCPOnFieldsProperty.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/LCPOnFieldsProperty.java
new file mode 100644
index 0000000000..2af58486e4
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/LCPOnFieldsProperty.java
@@ -0,0 +1,6 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.properties.lcp_on_fields;
+
+public class LCPOnFieldsProperty {
+ public static final String KEY = "LCPOnFields";
+}
diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/ObjectValue.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/ObjectValue.java
new file mode 100644
index 0000000000..83090e48ea
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/ObjectValue.java
@@ -0,0 +1,39 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.properties.lcp_on_fields;
+
+import org.opalj.fpcf.properties.PropertyValidator;
+import org.opalj.fpcf.properties.linear_constant_propagation.ConstantValue;
+import org.opalj.fpcf.properties.linear_constant_propagation.UnknownValue;
+import org.opalj.fpcf.properties.linear_constant_propagation.VariableValue;
+
+import java.lang.annotation.*;
+
+/**
+ * Annotation to state that an object has been identified and has certain constant and non-constant values
+ */
+@PropertyValidator(key = LCPOnFieldsProperty.KEY, validator = ObjectValueMatcher.class)
+@Repeatable(ObjectValues.class)
+@Documented
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.CLASS)
+public @interface ObjectValue {
+ /**
+ * The name of the variable the object is stored in
+ */
+ String variable();
+
+ /**
+ * The constant fields of the object
+ */
+ ConstantValue[] constantValues() default {};
+
+ /**
+ * The non-constant fields of the object
+ */
+ VariableValue[] variableValues() default {};
+
+ /**
+ * The fields of the object with unknown value
+ */
+ UnknownValue[] unknownValues() default {};
+}
diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/ObjectValues.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/ObjectValues.java
new file mode 100644
index 0000000000..52f0b61aaf
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/ObjectValues.java
@@ -0,0 +1,17 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.properties.lcp_on_fields;
+
+import org.opalj.fpcf.properties.PropertyValidator;
+
+import java.lang.annotation.*;
+
+/**
+ * Container annotation for {@link ObjectValue} annotations
+ */
+@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/lcp_on_fields/StaticValues.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/StaticValues.java
new file mode 100644
index 0000000000..7cefe01f0b
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/StaticValues.java
@@ -0,0 +1,33 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.properties.lcp_on_fields;
+
+import org.opalj.fpcf.properties.PropertyValidator;
+import org.opalj.fpcf.properties.linear_constant_propagation.ConstantValue;
+import org.opalj.fpcf.properties.linear_constant_propagation.UnknownValue;
+import org.opalj.fpcf.properties.linear_constant_propagation.VariableValue;
+
+import java.lang.annotation.*;
+
+/**
+ * Annotation to state that an object has certain constant and non-constant static values
+ */
+@PropertyValidator(key = LCPOnFieldsProperty.KEY, validator = StaticValuesMatcher.class)
+@Documented
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.CLASS)
+public @interface StaticValues {
+ /**
+ * The constant static fields
+ */
+ ConstantValue[] constantValues() default {};
+
+ /**
+ * The non-constant static fields
+ */
+ VariableValue[] variableValues() default {};
+
+ /**
+ * The static fields with unknown value
+ */
+ UnknownValue[] unknownValues() default {};
+}
diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/UnknownArrayElement.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/UnknownArrayElement.java
new file mode 100644
index 0000000000..c6d4ee6e98
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/UnknownArrayElement.java
@@ -0,0 +1,18 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.properties.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
+ */
+@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/lcp_on_fields/UnknownValue.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/UnknownValue.java
new file mode 100644
index 0000000000..5852b3cdff
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/UnknownValue.java
@@ -0,0 +1,21 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.properties.lcp_on_fields;
+
+import org.opalj.fpcf.properties.PropertyValidator;
+
+import java.lang.annotation.*;
+
+/**
+ * Annotation to state that a variables value is unknown
+ */
+@PropertyValidator(key = LCPOnFieldsProperty.KEY, validator = UnknownValueMatcher.class)
+@Repeatable(UnknownValues.class)
+@Documented
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.CLASS)
+public @interface UnknownValue {
+ /**
+ * The name of the variable
+ */
+ String variable() default "";
+}
diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/UnknownValues.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/UnknownValues.java
new file mode 100644
index 0000000000..5bc3532bdb
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/UnknownValues.java
@@ -0,0 +1,17 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.properties.lcp_on_fields;
+
+import org.opalj.fpcf.properties.PropertyValidator;
+
+import java.lang.annotation.*;
+
+/**
+ * Container annotation for {@link UnknownValue} annotations
+ */
+@PropertyValidator(key = LCPOnFieldsProperty.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/lcp_on_fields/VariableArrayElement.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/VariableArrayElement.java
new file mode 100644
index 0000000000..333f0af140
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/VariableArrayElement.java
@@ -0,0 +1,18 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.properties.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
+ */
+@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/lcp_on_fields/VariableValue.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/VariableValue.java
new file mode 100644
index 0000000000..cb88d243a3
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/VariableValue.java
@@ -0,0 +1,21 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.properties.lcp_on_fields;
+
+import org.opalj.fpcf.properties.PropertyValidator;
+
+import java.lang.annotation.*;
+
+/**
+ * Annotation to state that a variable has a non-constant value
+ */
+@PropertyValidator(key = LCPOnFieldsProperty.KEY, validator = VariableValueMatcher.class)
+@Repeatable(VariableValues.class)
+@Documented
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.CLASS)
+public @interface VariableValue {
+ /**
+ * The name of the variable
+ */
+ String variable() default "";
+}
diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/VariableValues.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/VariableValues.java
new file mode 100644
index 0000000000..cb8647d2e0
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/lcp_on_fields/VariableValues.java
@@ -0,0 +1,18 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.properties.lcp_on_fields;
+
+import org.opalj.fpcf.properties.PropertyValidator;
+
+import java.lang.annotation.*;
+
+/**
+ * Container annotation for {@link VariableValue} annotations
+ */
+@PropertyValidator(key = LCPOnFieldsProperty.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/ConstantValue.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/ConstantValue.java
new file mode 100644
index 0000000000..c6891eebbe
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/ConstantValue.java
@@ -0,0 +1,26 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.properties.linear_constant_propagation;
+
+import org.opalj.fpcf.properties.PropertyValidator;
+
+import java.lang.annotation.*;
+
+/**
+ * Annotation to state that a variable has a constant value
+ */
+@PropertyValidator(key = LinearConstantPropagationProperty.KEY, validator = ConstantValueMatcher.class)
+@Repeatable(ConstantValues.class)
+@Documented
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.CLASS)
+public @interface ConstantValue {
+ /**
+ * The name of the variable
+ */
+ String variable();
+
+ /**
+ * The constant value of the variable
+ */
+ int value();
+}
diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/ConstantValues.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/ConstantValues.java
new file mode 100644
index 0000000000..15b14d12aa
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/ConstantValues.java
@@ -0,0 +1,18 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.properties.linear_constant_propagation;
+
+import org.opalj.fpcf.properties.PropertyValidator;
+
+import java.lang.annotation.*;
+
+/**
+ * Container annotation for {@link ConstantValue} annotations
+ */
+@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/LinearConstantPropagationProperty.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/LinearConstantPropagationProperty.java
new file mode 100644
index 0000000000..a952bdab5f
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/LinearConstantPropagationProperty.java
@@ -0,0 +1,6 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.properties.linear_constant_propagation;
+
+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/UnknownValue.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/UnknownValue.java
new file mode 100644
index 0000000000..198d6c87c0
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/UnknownValue.java
@@ -0,0 +1,21 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.properties.linear_constant_propagation;
+
+import org.opalj.fpcf.properties.PropertyValidator;
+
+import java.lang.annotation.*;
+
+/**
+ * Annotation to state that a variables value is unknown
+ */
+@PropertyValidator(key = LinearConstantPropagationProperty.KEY, validator = UnknownValueMatcher.class)
+@Repeatable(UnknownValues.class)
+@Documented
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.CLASS)
+public @interface UnknownValue {
+ /**
+ * The name of the variable
+ */
+ String variable() default "";
+}
diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/UnknownValues.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/UnknownValues.java
new file mode 100644
index 0000000000..15c97bef02
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/UnknownValues.java
@@ -0,0 +1,17 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.properties.linear_constant_propagation;
+
+import org.opalj.fpcf.properties.PropertyValidator;
+
+import java.lang.annotation.*;
+
+/**
+ * Container annotation for {@link UnknownValue} annotations
+ */
+@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/VariableValue.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/VariableValue.java
new file mode 100644
index 0000000000..59764549b6
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/VariableValue.java
@@ -0,0 +1,21 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.properties.linear_constant_propagation;
+
+import org.opalj.fpcf.properties.PropertyValidator;
+
+import java.lang.annotation.*;
+
+/**
+ * Annotation to state that a variable has a non-constant value
+ */
+@PropertyValidator(key = LinearConstantPropagationProperty.KEY, validator = VariableValueMatcher.class)
+@Repeatable(VariableValues.class)
+@Documented
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.CLASS)
+public @interface VariableValue {
+ /**
+ * The name of the variable
+ */
+ String variable() default "";
+}
diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/VariableValues.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/VariableValues.java
new file mode 100644
index 0000000000..1d13b978cd
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/linear_constant_propagation/VariableValues.java
@@ -0,0 +1,18 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.properties.linear_constant_propagation;
+
+import org.opalj.fpcf.properties.PropertyValidator;
+
+import java.lang.annotation.*;
+
+/**
+ * Container annotation for {@link VariableValue} annotations
+ */
+@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/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..6187fa732d
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ide/IDEPropertiesTest.scala
@@ -0,0 +1,37 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.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.fpcf.PropertiesTest
+import org.opalj.ide.ConfigKeyDebugLog
+import org.opalj.ide.ConfigKeyTraceLog
+import org.opalj.tac.cg.RTACallGraphKey
+
+class IDEPropertiesTest extends PropertiesTest {
+ override def withRT: Boolean = false
+
+ override def createConfig(): Config = {
+ super.createConfig()
+ .withValue(
+ InitialInstantiatedTypesKey.ConfigKeyPrefix + "AllInstantiatedTypesFinder.projectClassesOnly",
+ ConfigValueFactory.fromAnyRef(false)
+ )
+ .withValue(ConfigKeyDebugLog, ConfigValueFactory.fromAnyRef(true))
+ .withValue(ConfigKeyTraceLog, 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/lcp_on_fields/LCPOnFieldsAnalysisScheduler.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ide/lcp_on_fields/LCPOnFieldsAnalysisScheduler.scala
new file mode 100644
index 0000000000..b43c3fa43c
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ide/lcp_on_fields/LCPOnFieldsAnalysisScheduler.scala
@@ -0,0 +1,8 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.ide.lcp_on_fields
+
+import org.opalj.tac.fpcf.analyses.ide.instances.lcp_on_fields.LCPOnFieldsAnalysisScheduler
+import org.opalj.tac.fpcf.analyses.ide.integration.JavaIDEAnalysisSchedulerBase
+
+object LCPOnFieldsAnalysisScheduler
+ extends LCPOnFieldsAnalysisScheduler with JavaIDEAnalysisSchedulerBase.RTACallGraph
diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ide/lcp_on_fields/LCPOnFieldsTests.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ide/lcp_on_fields/LCPOnFieldsTests.scala
new file mode 100644
index 0000000000..6101e41890
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ide/lcp_on_fields/LCPOnFieldsTests.scala
@@ -0,0 +1,53 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.ide.lcp_on_fields
+
+import org.opalj.br.fpcf.FPCFAnalysis
+import org.opalj.br.fpcf.analyses.immutability.LazyClassImmutabilityAnalysis
+import org.opalj.br.fpcf.analyses.immutability.LazyTypeImmutabilityAnalysis
+import org.opalj.fpcf.PropertyStore
+import org.opalj.fpcf.ide.IDEPropertiesTest
+import org.opalj.fpcf.properties.lcp_on_fields.LCPOnFieldsProperty
+import org.opalj.fpcf.properties.linear_constant_propagation.LinearConstantPropagationProperty
+import org.opalj.ide.integration.LazyIDEAnalysisProxyScheduler
+import org.opalj.tac.fpcf.analyses.LazyFieldImmutabilityAnalysis
+import org.opalj.tac.fpcf.analyses.fieldaccess.EagerFieldAccessInformationAnalysis
+import org.opalj.tac.fpcf.analyses.fieldassignability.LazyL2FieldAssignabilityAnalysis
+
+class LCPOnFieldsTests extends IDEPropertiesTest {
+ override def fixtureProjectPackage: List[String] = {
+ List("org/opalj/fpcf/fixtures/lcp_on_fields")
+ }
+
+ describe("Execute the o.o.t.f.a.i.i.l.LCPOnFieldsAnalysis") {
+ 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
+ )
+ }
+ }
+ },
+ LazyFieldImmutabilityAnalysis,
+ LazyL2FieldAssignabilityAnalysis,
+ LazyTypeImmutabilityAnalysis,
+ LazyClassImmutabilityAnalysis,
+ 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/lcp_on_fields/LinearConstantPropagationAnalysisSchedulerExtended.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ide/lcp_on_fields/LinearConstantPropagationAnalysisSchedulerExtended.scala
new file mode 100644
index 0000000000..1b7e305972
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ide/lcp_on_fields/LinearConstantPropagationAnalysisSchedulerExtended.scala
@@ -0,0 +1,8 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.ide.lcp_on_fields
+
+import org.opalj.tac.fpcf.analyses.ide.instances.lcp_on_fields.LinearConstantPropagationAnalysisSchedulerExtended
+import org.opalj.tac.fpcf.analyses.ide.integration.JavaIDEAnalysisSchedulerBase
+
+object LinearConstantPropagationAnalysisSchedulerExtended
+ extends LinearConstantPropagationAnalysisSchedulerExtended with JavaIDEAnalysisSchedulerBase.RTACallGraph
diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ide/linear_constant_propagation/LinearConstantPropagationAnalysisScheduler.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ide/linear_constant_propagation/LinearConstantPropagationAnalysisScheduler.scala
new file mode 100644
index 0000000000..ed2e5be782
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ide/linear_constant_propagation/LinearConstantPropagationAnalysisScheduler.scala
@@ -0,0 +1,8 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.ide.linear_constant_propagation
+
+import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.LinearConstantPropagationAnalysisScheduler
+import org.opalj.tac.fpcf.analyses.ide.integration.JavaIDEAnalysisSchedulerBase
+
+object LinearConstantPropagationAnalysisScheduler
+ extends LinearConstantPropagationAnalysisScheduler with JavaIDEAnalysisSchedulerBase.RTACallGraph
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..fa800ed90e
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ide/linear_constant_propagation/LinearConstantPropagationTests.scala
@@ -0,0 +1,31 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.ide.linear_constant_propagation
+
+import org.opalj.br.analyses.SomeProject
+import org.opalj.fpcf.ide.IDEPropertiesTest
+import org.opalj.fpcf.properties.linear_constant_propagation.LinearConstantPropagationProperty
+import org.opalj.ide.integration.EagerIDEAnalysisProxyScheduler
+
+class LinearConstantPropagationTests extends IDEPropertiesTest {
+ override def fixtureProjectPackage: List[String] = {
+ List("org/opalj/fpcf/fixtures/linear_constant_propagation")
+ }
+
+ describe("Execute the o.o.t.f.a.i.i.l.LinearConstantPropagationAnalysis") {
+ 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..c7d5bdf4b2
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/AbstractRepeatablePropertyMatcher.scala
@@ -0,0 +1,67 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.properties
+
+import org.opalj.br.AnnotationLike
+import org.opalj.br.ObjectType
+import org.opalj.br.analyses.Project
+import org.opalj.fpcf.Property
+
+/**
+ * Property matcher for repeatable annotations
+ */
+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/lcp_on_fields/LCPOnFieldsMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/lcp_on_fields/LCPOnFieldsMatcher.scala
new file mode 100644
index 0000000000..168aa5a95f
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/lcp_on_fields/LCPOnFieldsMatcher.scala
@@ -0,0 +1,451 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.properties.lcp_on_fields
+
+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.Property
+import org.opalj.fpcf.properties.AbstractPropertyMatcher
+import org.opalj.fpcf.properties.AbstractRepeatablePropertyMatcher
+import org.opalj.ide.integration.BasicIDEProperty
+import org.opalj.tac.fpcf.analyses.ide.instances.lcp_on_fields
+import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation
+
+/**
+ * Matcher for [[ObjectValue]] and [[ObjectValues]] annotations
+ */
+class ObjectValueMatcher extends AbstractRepeatablePropertyMatcher {
+ override val singleAnnotationType: ObjectType =
+ ObjectType("org/opalj/fpcf/properties/lcp_on_fields/ObjectValue")
+ override val containerAnnotationType: ObjectType =
+ ObjectType("org/opalj/fpcf/properties/lcp_on_fields/ObjectValues")
+
+ private val constantValueType = ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/ConstantValue")
+ private val variableValueType = ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/VariableValue")
+ private val unknownValueType = ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/UnknownValue")
+
+ override def validateSingleProperty(
+ p: Project[?],
+ as: Set[ObjectType],
+ entity: Any,
+ a: AnnotationLike,
+ properties: Iterable[Property]
+ ): Option[String] = {
+ val expectedVariableName =
+ getValue(p, singleAnnotationType, a.elementValuePairs, "variable").asStringValue.value
+
+ val expectedConstantValues =
+ getValue(p, singleAnnotationType, a.elementValuePairs, "constantValues").asArrayValue.values
+ .map { a =>
+ val annotation = a.asAnnotationValue.annotation
+ val expectedFieldName =
+ getValue(p, constantValueType, annotation.elementValuePairs, "variable").asStringValue.value
+ val expectedValue =
+ getValue(p, constantValueType, annotation.elementValuePairs, "value").asIntValue.value
+
+ (expectedFieldName, expectedValue)
+ }
+
+ val expectedVariableValues =
+ getValue(p, singleAnnotationType, a.elementValuePairs, "variableValues").asArrayValue.values
+ .map { a =>
+ val annotation = a.asAnnotationValue.annotation
+ val expectedFieldName =
+ getValue(p, variableValueType, annotation.elementValuePairs, "variable").asStringValue.value
+
+ expectedFieldName
+ }
+
+ val expectedUnknownValues =
+ getValue(p, singleAnnotationType, a.elementValuePairs, "unknownValues").asArrayValue.values
+ .map { a =>
+ val annotation = a.asAnnotationValue.annotation
+ val expectedFieldName =
+ getValue(p, unknownValueType, annotation.elementValuePairs, "variable").asStringValue.value
+
+ expectedFieldName
+ }
+
+ if (properties.exists {
+ case property: BasicIDEProperty[?, ?] =>
+ property.results.exists {
+ case (f: lcp_on_fields.problem.AbstractObjectFact, lcp_on_fields.problem.ObjectValue(values)) =>
+ expectedVariableName == f.name &&
+ 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
+ }
+
+ 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(expectedVariableName, 0)}, ${lcp_on_fields.problem.ObjectValue(expectedValues)})"
+ )
+ }
+ }
+}
+
+/**
+ * Matcher for [[ArrayValue]] and [[ArrayValues]] annotations
+ */
+class ArrayValueMatcher extends AbstractRepeatablePropertyMatcher {
+ override val singleAnnotationType: ObjectType =
+ ObjectType("org/opalj/fpcf/properties/lcp_on_fields/ArrayValue")
+ override val containerAnnotationType: ObjectType =
+ ObjectType("org/opalj/fpcf/properties/lcp_on_fields/ArrayValues")
+
+ private val constantArrayElementType = ObjectType("org/opalj/fpcf/properties/lcp_on_fields/ConstantArrayElement")
+ private val variableArrayElementType = ObjectType("org/opalj/fpcf/properties/lcp_on_fields/VariableArrayElement")
+ private val unknownArrayElementType = ObjectType("org/opalj/fpcf/properties/lcp_on_fields/UnknownArrayElement")
+
+ override def validateSingleProperty(
+ p: Project[?],
+ as: Set[ObjectType],
+ entity: Any,
+ a: AnnotationLike,
+ properties: Iterable[Property]
+ ): Option[String] = {
+ val expectedVariableName =
+ getValue(p, singleAnnotationType, a.elementValuePairs, "variable").asStringValue.value
+
+ val expectedConstantElements =
+ getValue(p, singleAnnotationType, a.elementValuePairs, "constantElements").asArrayValue.values
+ .map { a =>
+ val annotation = a.asAnnotationValue.annotation
+ val expectedIndex =
+ getValue(p, constantArrayElementType, annotation.elementValuePairs, "index").asIntValue.value
+ val expectedValue =
+ getValue(p, constantArrayElementType, annotation.elementValuePairs, "value").asIntValue.value
+
+ (expectedIndex, expectedValue)
+ }
+
+ val expectedVariableElements =
+ getValue(p, singleAnnotationType, a.elementValuePairs, "variableElements").asArrayValue.values
+ .map { a =>
+ val annotation = a.asAnnotationValue.annotation
+ val expectedIndex =
+ getValue(p, variableArrayElementType, annotation.elementValuePairs, "index").asIntValue.value
+
+ expectedIndex
+ }
+
+ val expectedUnknownElements =
+ getValue(p, singleAnnotationType, a.elementValuePairs, "unknownElements").asArrayValue.values
+ .map { a =>
+ val annotation = a.asAnnotationValue.annotation
+ val expectedIndex =
+ getValue(p, unknownArrayElementType, annotation.elementValuePairs, "index").asIntValue.value
+
+ expectedIndex
+ }
+
+ if (properties.exists {
+ case property: BasicIDEProperty[?, ?] =>
+ property.results.exists {
+ case (
+ f: lcp_on_fields.problem.AbstractArrayFact,
+ lcp_on_fields.problem.ArrayValue(initValue, elements)
+ ) =>
+ expectedVariableName == f.name &&
+ 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
+ }
+
+ 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(expectedVariableName, 0)}, ArrayValue(?, $expectedElements)"
+ )
+ }
+ }
+}
+
+/**
+ * Matcher for [[StaticValues]] annotations
+ */
+class StaticValuesMatcher extends AbstractPropertyMatcher {
+ private val annotationType: ObjectType =
+ ObjectType("org/opalj/fpcf/properties/lcp_on_fields/ObjectValue")
+
+ private val constantValueType = ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/ConstantValue")
+ private val variableValueType = ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/VariableValue")
+ private val unknownValueType = ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/UnknownValue")
+
+ 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 =
+ getValue(p, annotationType, a.elementValuePairs, "constantValues").asArrayValue.values
+ .map { a =>
+ val annotation = a.asAnnotationValue.annotation
+ val expectedFieldName =
+ getValue(p, constantValueType, annotation.elementValuePairs, "variable").asStringValue.value
+ val expectedValue =
+ getValue(p, constantValueType, annotation.elementValuePairs, "value").asIntValue.value
+
+ (expectedFieldName, expectedValue)
+ }
+
+ val expectedVariableValues =
+ getValue(p, annotationType, a.elementValuePairs, "variableValues").asArrayValue.values
+ .map { a =>
+ val annotation = a.asAnnotationValue.annotation
+ val expectedFieldName =
+ getValue(p, variableValueType, annotation.elementValuePairs, "variable").asStringValue.value
+
+ expectedFieldName
+ }
+
+ val expectedUnknownValues =
+ getValue(p, annotationType, a.elementValuePairs, "unknownValues").asArrayValue.values
+ .map { a =>
+ val annotation = a.asAnnotationValue.annotation
+ val expectedFieldName =
+ getValue(p, unknownValueType, annotation.elementValuePairs, "variable").asStringValue.value
+
+ expectedFieldName
+ }
+
+ if (properties.exists {
+ case property: BasicIDEProperty[?, ?] =>
+ expectedConstantValues.forall {
+ case (fieldName, value) =>
+ property.results.exists {
+ 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 =>
+ property.results.exists {
+ 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 =>
+ property.results.exists {
+ 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
+ }
+ }
+
+ 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 [[VariableValue]] and [[VariableValues]] annotations
+ */
+class VariableValueMatcher extends AbstractRepeatablePropertyMatcher {
+ override val singleAnnotationType: ObjectType =
+ ObjectType("org/opalj/fpcf/properties/lcp_on_fields/VariableValue")
+ override val containerAnnotationType: ObjectType =
+ ObjectType("org/opalj/fpcf/properties/lcp_on_fields/VariableValues")
+
+ override def validateSingleProperty(
+ p: Project[?],
+ as: Set[ObjectType],
+ entity: Any,
+ a: AnnotationLike,
+ properties: Iterable[Property]
+ ): Option[String] = {
+ val expectedVariableName =
+ getValue(p, singleAnnotationType, a.elementValuePairs, "variable").asStringValue.value
+
+ if (properties.exists {
+ case property: BasicIDEProperty[?, ?] =>
+ property.results.exists {
+ case (
+ lcp_on_fields.problem.ObjectFact(name, _),
+ lcp_on_fields.problem.VariableValue
+ ) =>
+ expectedVariableName == name
+ case (
+ lcp_on_fields.problem.ArrayFact(name, _),
+ lcp_on_fields.problem.VariableValue
+ ) =>
+ expectedVariableName == name
+
+ case _ => false
+ }
+
+ case _ => false
+ }
+ ) {
+ None
+ } else {
+ Some(
+ s"Result should contain (${lcp_on_fields.problem.ObjectFact(expectedVariableName, 0)}, ${lcp_on_fields.problem.VariableValue})!"
+ )
+ }
+ }
+}
+
+/**
+ * Matcher for [[UnknownValue]] and [[UnknownValues]] annotations
+ */
+class UnknownValueMatcher extends AbstractRepeatablePropertyMatcher {
+ override val singleAnnotationType: ObjectType =
+ ObjectType("org/opalj/fpcf/properties/lcp_on_fields/UnknownValue")
+ override val containerAnnotationType: ObjectType =
+ ObjectType("org/opalj/fpcf/properties/lcp_on_fields/UnknownValues")
+
+ override def validateSingleProperty(
+ p: Project[?],
+ as: Set[ObjectType],
+ entity: Any,
+ a: AnnotationLike,
+ properties: Iterable[Property]
+ ): Option[String] = {
+ val expectedVariableName =
+ getValue(p, singleAnnotationType, a.elementValuePairs, "variable").asStringValue.value
+
+ if (properties.exists {
+ case property: BasicIDEProperty[?, ?] =>
+ property.results.exists {
+ case (
+ lcp_on_fields.problem.ObjectFact(name, _),
+ lcp_on_fields.problem.UnknownValue
+ ) =>
+ expectedVariableName == name
+ case (
+ lcp_on_fields.problem.ArrayFact(name, _),
+ lcp_on_fields.problem.UnknownValue
+ ) =>
+ expectedVariableName == name
+
+ case _ => false
+ }
+
+ case _ => false
+ }
+ ) {
+ None
+ } else {
+ Some(
+ s"Result should contain (${lcp_on_fields.problem.ObjectFact(expectedVariableName, 0)}, ${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..ec682fb430
--- /dev/null
+++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/linear_constant_propagation/LinearConstantPropagationMatcher.scala
@@ -0,0 +1,141 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.fpcf.properties.linear_constant_propagation
+
+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.AbstractRepeatablePropertyMatcher
+import org.opalj.ide.integration.BasicIDEProperty
+import org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation
+
+/**
+ * Matcher for [[ConstantValue]] and [[ConstantValues]] annotations
+ */
+class ConstantValueMatcher extends AbstractRepeatablePropertyMatcher {
+ override val singleAnnotationType: ObjectType =
+ ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/ConstantValue")
+ override val containerAnnotationType: ObjectType =
+ ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/ConstantValues")
+
+ override def validateSingleProperty(
+ p: Project[?],
+ as: Set[ObjectType],
+ entity: Any,
+ a: AnnotationLike,
+ properties: Iterable[Property]
+ ): Option[String] = {
+ val expectedVariableName =
+ getValue(p, singleAnnotationType, a.elementValuePairs, "variable").asStringValue.value
+ val expectedVariableValue =
+ getValue(p, singleAnnotationType, a.elementValuePairs, "value").asIntValue.value
+
+ if (properties.exists {
+ case property: BasicIDEProperty[?, ?] =>
+ property.results.exists {
+ case (
+ linear_constant_propagation.problem.VariableFact(name, _),
+ linear_constant_propagation.problem.ConstantValue(value)
+ ) =>
+ expectedVariableName == name && expectedVariableValue == value
+
+ case _ => false
+ }
+
+ case _ => false
+ }
+ ) {
+ None
+ } else {
+ Some(
+ s"Result should contain (${linear_constant_propagation.problem.VariableFact(expectedVariableName, 0)}, ${linear_constant_propagation.problem.ConstantValue(expectedVariableValue)})!"
+ )
+ }
+ }
+}
+
+/**
+ * Matcher for [[VariableValue]] and [[VariableValues]] annotations
+ */
+class VariableValueMatcher extends AbstractRepeatablePropertyMatcher {
+ override val singleAnnotationType: ObjectType =
+ ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/VariableValue")
+ override val containerAnnotationType: ObjectType =
+ ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/VariableValues")
+
+ override def validateSingleProperty(
+ p: Project[?],
+ as: Set[ObjectType],
+ entity: Any,
+ a: AnnotationLike,
+ properties: Iterable[Property]
+ ): Option[String] = {
+ val expectedVariableName =
+ getValue(p, singleAnnotationType, a.elementValuePairs, "variable").asStringValue.value
+
+ if (properties.exists {
+ case property: BasicIDEProperty[?, ?] =>
+ property.results.exists {
+ case (
+ linear_constant_propagation.problem.VariableFact(name, _),
+ linear_constant_propagation.problem.VariableValue
+ ) =>
+ expectedVariableName == name
+
+ case _ => false
+ }
+
+ case _ => false
+ }
+ ) {
+ None
+ } else {
+ Some(
+ s"Result should contain (${linear_constant_propagation.problem.VariableFact(expectedVariableName, 0)}, ${linear_constant_propagation.problem.VariableValue})!"
+ )
+ }
+ }
+}
+
+/**
+ * Matcher for [[UnknownValue]] and [[UnknownValues]] annotations
+ */
+class UnknownValueMatcher extends AbstractRepeatablePropertyMatcher {
+ override val singleAnnotationType: ObjectType =
+ ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/UnknownValue")
+ override val containerAnnotationType: ObjectType =
+ ObjectType("org/opalj/fpcf/properties/linear_constant_propagation/UnknownValues")
+
+ override def validateSingleProperty(
+ p: Project[?],
+ as: Set[ObjectType],
+ entity: Any,
+ a: AnnotationLike,
+ properties: Iterable[Property]
+ ): Option[String] = {
+ val expectedVariableName =
+ getValue(p, singleAnnotationType, a.elementValuePairs, "variable").asStringValue.value
+
+ if (properties.exists {
+ case property: BasicIDEProperty[?, ?] =>
+ property.results.exists {
+ case (
+ linear_constant_propagation.problem.VariableFact(name, _),
+ linear_constant_propagation.problem.UnknownValue
+ ) =>
+ expectedVariableName == name
+
+ case _ => false
+ }
+
+ case _ => false
+ }
+ ) {
+ None
+ } else {
+ Some(
+ s"Result should contain (${linear_constant_propagation.problem.VariableFact(expectedVariableName, 0)}, ${linear_constant_propagation.problem.UnknownValue})!"
+ )
+ }
+ }
+}
diff --git a/OPAL/ProjectDependencies.mmd b/OPAL/ProjectDependencies.mmd
index b2055fe410..bfa819777f 100644
--- a/OPAL/ProjectDependencies.mmd
+++ b/OPAL/ProjectDependencies.mmd
@@ -9,6 +9,7 @@ flowchart BT
br[Bytecode Representation\n br]
da[Bytecode Disassembler\n da]
+ ide[IDE\n ide]
ifds[IFDS\n ifds]
ai[Abstract Interpretation Framework\n ai]
bc[Bytecode Creator\n bc]
@@ -40,6 +41,9 @@ flowchart BT
br --> bi
da --> bi
+ ide --> si
+ ide --> br
+
ifds --> si
ifds --> br
@@ -50,6 +54,7 @@ flowchart BT
de --> ai
tac --> ifds
+ tac --> ide
tac --> ai
ll --> tac
@@ -68,4 +73,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/resources/reference.conf b/OPAL/ide/src/main/resources/reference.conf
new file mode 100644
index 0000000000..49657843d8
--- /dev/null
+++ b/OPAL/ide/src/main/resources/reference.conf
@@ -0,0 +1,6 @@
+org.opalj {
+ ide {
+ debug = false,
+ trace = false,
+ }
+}
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..df5574f3f0
--- /dev/null
+++ b/OPAL/ide/src/main/scala/org/opalj/ide/ifds/integration/IFDSPropertyMetaInformation.scala
@@ -0,0 +1,12 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.ide.ifds.integration
+
+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
+ */
+trait IFDSPropertyMetaInformation[Statement, Fact <: IDEFact]
+ extends IDEPropertyMetaInformation[Statement, Fact, IFDSValue]
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..7c9d5ddf72
--- /dev/null
+++ b/OPAL/ide/src/main/scala/org/opalj/ide/ifds/problem/IFDSEdgeFunctions.scala
@@ -0,0 +1,15 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.ide.ifds.problem
+
+import org.opalj.ide.problem.EdgeFunction
+
+/**
+ * Edge function evaluating all source values to the bottom value
+ */
+object AllBottomEdgeFunction extends org.opalj.ide.problem.AllBottomEdgeFunction[IFDSValue](Bottom) {
+ override def composeWith(secondEdgeFunction: EdgeFunction[IFDSValue]): EdgeFunction[IFDSValue] = {
+ 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..ae8fc95c54
--- /dev/null
+++ b/OPAL/ide/src/main/scala/org/opalj/ide/ifds/problem/IFDSLattice.scala
@@ -0,0 +1,18 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.ide.ifds.problem
+
+import org.opalj.ide.problem.MeetLattice
+
+/**
+ * Lattice to use for IFDS problems that are solved with an IDE solver
+ */
+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..31ef6c3ecf
--- /dev/null
+++ b/OPAL/ide/src/main/scala/org/opalj/ide/ifds/problem/IFDSProblem.scala
@@ -0,0 +1,120 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.ide.ifds.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.IDEProblem
+import org.opalj.ide.problem.MeetLattice
+
+/**
+ * Interface for modeling IFDS problems based on an IDE problem
+ */
+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
+ )(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..faaa131d35
--- /dev/null
+++ b/OPAL/ide/src/main/scala/org/opalj/ide/ifds/problem/IFDSValue.scala
@@ -0,0 +1,19 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.ide.ifds.problem
+
+import org.opalj.ide.problem.IDEValue
+
+/**
+ * Type for modeling values for IFDS problems that are solved with an IDE solver
+ */
+trait IFDSValue extends IDEValue
+
+/**
+ * Top value
+ */
+case object Top extends IFDSValue
+
+/**
+ * Bottom value (all result fact have the bottom value)
+ */
+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..1e108acb0a
--- /dev/null
+++ b/OPAL/ide/src/main/scala/org/opalj/ide/integration/BaseIDEAnalysisProxyScheduler.scala
@@ -0,0 +1,43 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.ide.integration
+
+import scala.collection.immutable
+
+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.PropertyBounds
+import org.opalj.fpcf.PropertyStore
+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
+ */
+trait BaseIDEAnalysisProxyScheduler[Fact <: IDEFact, Value <: IDEValue, Statement, Callable <: Entity]
+ extends FPCFAnalysisScheduler {
+ val propertyMetaInformation: IDEPropertyMetaInformation[Statement, Fact, Value]
+
+ 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, ps: PropertyStore): IDEAnalysisProxy[Fact, Value, Statement, Callable] = {
+ new IDEAnalysisProxy[Fact, Value, Statement, Callable](project, propertyMetaInformation)
+ }
+
+ override def uses: Set[PropertyBounds] =
+ immutable.Set(PropertyBounds.ub(propertyMetaInformation.backingPropertyMetaInformation))
+
+ override def beforeSchedule(p: SomeProject, ps: PropertyStore): Unit = {}
+
+ override def afterPhaseScheduling(ps: PropertyStore, analysis: FPCFAnalysis): Unit = {}
+
+ override def afterPhaseCompletion(p: SomeProject, ps: 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..3ac42a7a61
--- /dev/null
+++ b/OPAL/ide/src/main/scala/org/opalj/ide/integration/EagerIDEAnalysisProxyScheduler.scala
@@ -0,0 +1,44 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.ide.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
+ */
+class EagerIDEAnalysisProxyScheduler[Fact <: IDEFact, Value <: IDEValue, Statement, Callable <: Entity](
+ val propertyMetaInformation: IDEPropertyMetaInformation[Statement, Fact, Value],
+ 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..f0cf01fa28
--- /dev/null
+++ b/OPAL/ide/src/main/scala/org/opalj/ide/integration/FlowRecordingAnalysisScheduler.scala
@@ -0,0 +1,181 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.ide.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
+
+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.ide.util.Logging
+
+/**
+ * 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.
+ * DOT files can either be viewed with a suitable local program or online e.g. at
+ * [[https://dreampuf.github.io/GraphvizOnline]].
+ * @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
+ */
+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]
+ with Logging.EnableAll with Logging.GlobalLogContext {
+ override def propertyMetaInformation: IDEPropertyMetaInformation[Statement, Fact, Value] = {
+ 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 = mutable.Map.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 = {
+ logDebug("starting recording")
+
+ val file = getFile(project)
+ val directoryAsFile = file.getParentFile
+ val directoryAsPath = directoryAsFile.toPath.toAbsolutePath.normalize()
+ if (!directoryAsFile.exists()) {
+ if (directoryAsPath.startsWith(Paths.get(".").toAbsolutePath.normalize())) {
+ logWarn(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 = {
+ logDebug("stopping recording")
+
+ val writer = flowRecordingProblem.stopRecording()
+
+ writer.close()
+
+ logInfo(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..2ce96cfda6
--- /dev/null
+++ b/OPAL/ide/src/main/scala/org/opalj/ide/integration/IDEAnalysisScheduler.scala
@@ -0,0 +1,70 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.ide.integration
+
+import scala.collection.immutable
+
+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
+ */
+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[Statement, Fact, Value]
+
+ 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: immutable.Set[PropertyBounds] =
+ immutable.Set.empty
+
+ override def beforeSchedule(p: SomeProject, ps: PropertyStore): Unit = {}
+
+ override final def init(project: SomeProject, ps: 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(ps: PropertyStore, analysis: FPCFAnalysis): Unit = {}
+
+ override def afterPhaseCompletion(p: SomeProject, ps: 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..af339811e7
--- /dev/null
+++ b/OPAL/ide/src/main/scala/org/opalj/ide/integration/IDEProperty.scala
@@ -0,0 +1,42 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.ide.integration
+
+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
+ */
+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
+ */
+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)" }.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..77b35fcd5c
--- /dev/null
+++ b/OPAL/ide/src/main/scala/org/opalj/ide/integration/IDEPropertyMetaInformation.scala
@@ -0,0 +1,29 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.ide.integration
+
+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.
+ */
+trait IDEPropertyMetaInformation[Statement, Fact <: IDEFact, Value <: IDEValue] 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[Statement, Fact, Value] =
+ new IDERawPropertyMetaInformation[Statement, Fact, Value](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..9e3f5be085
--- /dev/null
+++ b/OPAL/ide/src/main/scala/org/opalj/ide/integration/IDERawProperty.scala
@@ -0,0 +1,46 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.ide.integration
+
+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 (very likely taken from an [[IDERawPropertyMetaInformation]] instance)
+ * @param stmtResults the raw statement results produced by the analysis
+ * @param callableResults the raw callable results produced by the analysis
+ */
+class IDERawProperty[Statement, Fact <: IDEFact, Value <: IDEValue](
+ val key: PropertyKey[IDERawProperty[Statement, Fact, Value]],
+ val stmtResults: collection.Map[Statement, collection.Set[(Fact, Value)]],
+ val callableResults: collection.Set[(Fact, Value)]
+) extends Property {
+ override type Self = IDERawProperty[Statement, Fact, Value]
+
+ 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)" }.mkString("\n")
+ }"
+ }.mkString("\n")
+ }\n}, {\n${
+ callableResults.map { case (fact, value) => s"\t($fact,$value)" }.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..8e9c380d45
--- /dev/null
+++ b/OPAL/ide/src/main/scala/org/opalj/ide/integration/IDERawPropertyMetaInformation.scala
@@ -0,0 +1,27 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.ide.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
+ */
+final class IDERawPropertyMetaInformation[Statement, Fact <: IDEFact, Value <: IDEValue](
+ propertyMetaInformation: IDEPropertyMetaInformation[Statement, Fact, Value]
+) extends PropertyMetaInformation {
+ override type Self = IDERawProperty[Statement, Fact, Value]
+
+ /**
+ * The used property key, based on [[propertyMetaInformation]]
+ */
+ private lazy val backingPropertyKey: PropertyKey[IDERawProperty[Statement, Fact, Value]] = {
+ PropertyKey.create(s"${PropertyKey.name(propertyMetaInformation.key)}_Raw")
+ }
+
+ override def key: PropertyKey[IDERawProperty[Statement, Fact, Value]] = backingPropertyKey
+}
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..69b564272f
--- /dev/null
+++ b/OPAL/ide/src/main/scala/org/opalj/ide/integration/LazyIDEAnalysisProxyScheduler.scala
@@ -0,0 +1,34 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.ide.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
+ */
+class LazyIDEAnalysisProxyScheduler[Fact <: IDEFact, Value <: IDEValue, Statement, Callable <: Entity](
+ val propertyMetaInformation: IDEPropertyMetaInformation[Statement, Fact, Value]
+) 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..f6ca262589
--- /dev/null
+++ b/OPAL/ide/src/main/scala/org/opalj/ide/package.scala
@@ -0,0 +1,34 @@
+/* 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
+
+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."
+ final val ConfigKeyDebugLog = s"${ConfigKeyPrefix}debug"
+ final val ConfigKeyTraceLog = s"${ConfigKeyPrefix}trace"
+}
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..5370969165
--- /dev/null
+++ b/OPAL/ide/src/main/scala/org/opalj/ide/problem/EdgeFunction.scala
@@ -0,0 +1,89 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.ide.problem
+
+/**
+ * Interface representing IDE edge functions
+ */
+trait EdgeFunction[Value <: IDEValue] {
+ /**
+ * Compute the value of the edge function
+ * @param sourceValue the incoming parameter value
+ */
+ def compute(sourceValue: Value): Value
+
+ /**
+ * 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(secondEdgeFunction: EdgeFunction[Value]): EdgeFunction[Value]
+
+ /**
+ * Combine two edge functions via meet semantics
+ */
+ def meetWith(otherEdgeFunction: EdgeFunction[Value]): EdgeFunction[Value]
+
+ /**
+ * Check whether two edge functions are equal (s.t. they produce the same result for same source values)
+ */
+ def equalTo(otherEdgeFunction: EdgeFunction[Value]): Boolean
+}
+
+/**
+ * Special edge function representing an identity edge function
+ */
+case class IdentityEdgeFunction[Value <: IDEValue]() extends EdgeFunction[Value] {
+ override def compute(sourceValue: Value): Value =
+ sourceValue
+
+ override def composeWith(secondEdgeFunction: EdgeFunction[Value]): EdgeFunction[Value] =
+ secondEdgeFunction
+
+ override def meetWith(otherEdgeFunction: EdgeFunction[Value]): EdgeFunction[Value] = {
+ if (otherEdgeFunction.equalTo(this) || otherEdgeFunction.isInstanceOf[AllTopEdgeFunction[Value]]) {
+ this
+ } else if (otherEdgeFunction.isInstanceOf[AllBottomEdgeFunction[Value]]) {
+ otherEdgeFunction
+ } else {
+ otherEdgeFunction.meetWith(this)
+ }
+ }
+
+ override def equalTo(otherEdgeFunction: EdgeFunction[Value]): Boolean =
+ (otherEdgeFunction eq this) || otherEdgeFunction.isInstanceOf[IdentityEdgeFunction[Value]]
+}
+
+/**
+ * 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.
+ */
+abstract case class AllTopEdgeFunction[Value <: IDEValue](private val top: Value) extends EdgeFunction[Value] {
+ override def compute(sourceValue: Value): Value =
+ top
+
+ override def meetWith(otherEdgeFunction: EdgeFunction[Value]): EdgeFunction[Value] =
+ otherEdgeFunction
+
+ override def equalTo(otherEdgeFunction: EdgeFunction[Value]): Boolean =
+ (otherEdgeFunction eq this) ||
+ otherEdgeFunction.isInstanceOf[AllTopEdgeFunction[Value]] &&
+ otherEdgeFunction.asInstanceOf[AllTopEdgeFunction[Value]].top == top
+}
+
+/**
+ * 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.
+ */
+abstract case class AllBottomEdgeFunction[Value <: IDEValue](private val bottom: Value) extends EdgeFunction[Value] {
+ override def compute(sourceValue: Value): Value =
+ bottom
+
+ override def meetWith(otherEdgeFunction: EdgeFunction[Value]): EdgeFunction[Value] =
+ this
+
+ override def equalTo(otherEdgeFunction: EdgeFunction[Value]): Boolean =
+ (otherEdgeFunction eq this) ||
+ otherEdgeFunction.isInstanceOf[AllBottomEdgeFunction[Value]] &&
+ otherEdgeFunction.asInstanceOf[AllBottomEdgeFunction[Value]].bottom == bottom
+}
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..927082f989
--- /dev/null
+++ b/OPAL/ide/src/main/scala/org/opalj/ide/problem/EdgeFunctionResult.scala
@@ -0,0 +1,24 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.ide.problem
+
+import org.opalj.fpcf.SomeEOptionP
+
+/**
+ * Interface for encapsulating different states of edge functions
+ */
+trait EdgeFunctionResult[Value <: IDEValue]
+
+/**
+ * Represent an edge function that is final
+ */
+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)
+ */
+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..506f37ba53
--- /dev/null
+++ b/OPAL/ide/src/main/scala/org/opalj/ide/problem/FlowFunction.scala
@@ -0,0 +1,47 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.ide.problem
+
+import scala.language.implicitConversions
+
+import scala.collection.immutable
+
+import org.opalj.fpcf.SomeEOptionP
+
+/**
+ * Interface representing IDE flow functions
+ */
+trait FlowFunction[Fact <: IDEFact] {
+ type FactsAndDependees = FlowFunction.FactsAndDependees[Fact]
+
+ implicit def setOfFactsToFactsAndDependees(facts: collection.Set[? <: Fact]): FactsAndDependees = {
+ (facts.toSet, immutable.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] = (collection.Set[Fact], collection.Set[SomeEOptionP])
+}
+
+/**
+ * Special flow function that always returns the input fact
+ */
+case class IdentityFlowFunction[Fact <: IDEFact](sourceFact: Fact) extends FlowFunction[Fact] {
+ override def compute(): FactsAndDependees =
+ immutable.Set(sourceFact)
+}
+
+/**
+ * Special flow function that always returns an empty set
+ */
+case class EmptyFlowFunction[Fact <: IDEFact]() extends FlowFunction[Fact] {
+ override def compute(): FactsAndDependees =
+ immutable.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..6c91b5b03f
--- /dev/null
+++ b/OPAL/ide/src/main/scala/org/opalj/ide/problem/FlowRecordingIDEProblem.scala
@@ -0,0 +1,357 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.ide.problem
+
+import java.io.Writer
+import scala.collection.mutable
+
+import org.opalj.fpcf.Entity
+import org.opalj.fpcf.PropertyStore
+import org.opalj.ide.solver.ICFG
+
+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.
+ * DOT files can either be viewed with a suitable local program or online e.g. at
+ * [[https://dreampuf.github.io/GraphvizOnline]].
+ * @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
+ */
+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 = mutable.ListBuffer.empty[DotEdge]
+
+ private val collectedEdgeFunctions = mutable.Map.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
+ ): 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
+ )(implicit propertyStore: PropertyStore): FlowFunction[Fact] = {
+ new RecordingFlowFunction(
+ baseProblem.getReturnFlowFunction(calleeExit, calleeExitFact, callee, returnSite),
+ 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
+ )(implicit propertyStore: PropertyStore): EdgeFunctionResult[Value] = {
+ val edgeFunctionResult =
+ baseProblem.getReturnEdgeFunction(calleeExit, calleeExitFact, callee, returnSite, returnSiteFact)
+ 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"${icfg.stringifyStatement(source, short = true)}"
+ toNode = s"${icfg.stringifyStatement(target, short = true)}"
+ 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"(${icfg.stringifyStatement(source, short = true)}, $sourceFact)"
+ toNode = s"(${icfg.stringifyStatement(target, short = true)}, $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"
+ }
+
+ /**
+ * Stop recording and finish writing
+ */
+ def stopRecording(): Writer = {
+ if (uniqueFlowsOnly) {
+ val seenFlows = mutable.Set.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..05084e66a3
--- /dev/null
+++ b/OPAL/ide/src/main/scala/org/opalj/ide/problem/IDEFact.scala
@@ -0,0 +1,7 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.ide.problem
+
+/**
+ * Interface representing IDE facts
+ */
+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..591295f2a1
--- /dev/null
+++ b/OPAL/ide/src/main/scala/org/opalj/ide/problem/IDEProblem.scala
@@ -0,0 +1,263 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.ide.problem
+
+import scala.annotation.unused
+import scala.language.implicitConversions
+
+import scala.collection.immutable
+
+import org.opalj.fpcf.Entity
+import org.opalj.fpcf.PropertyStore
+
+/**
+ * Interface for modeling IDE problems
+ */
+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]
+
+ /**
+ * Identity edge function that can be used when implementing problems
+ */
+ protected val identityEdgeFunction = new IdentityEdgeFunction[Value]
+
+ /**
+ * 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
+ ): collection.Set[Fact] = immutable.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)
+ */
+ def getReturnFlowFunction(calleeExit: Statement, calleeExitFact: Fact, callee: Callable, returnSite: Statement)(
+ 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
+ */
+ def getReturnEdgeFunction(
+ calleeExit: Statement,
+ calleeExitFact: Fact,
+ callee: Callable,
+ returnSite: Statement,
+ returnSiteFact: 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..16a882c52a
--- /dev/null
+++ b/OPAL/ide/src/main/scala/org/opalj/ide/problem/IDEValue.scala
@@ -0,0 +1,7 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.ide.problem
+
+/**
+ * Interface representing IDE values
+ */
+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..53d76c9365
--- /dev/null
+++ b/OPAL/ide/src/main/scala/org/opalj/ide/problem/MeetLattice.scala
@@ -0,0 +1,22 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.ide.problem
+
+/**
+ * Interface representing the lattice that orders the IDE values
+ */
+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..9668ee3b3d
--- /dev/null
+++ b/OPAL/ide/src/main/scala/org/opalj/ide/solver/ICFG.scala
@@ -0,0 +1,51 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.ide.solver
+
+import org.opalj.fpcf.Entity
+
+/**
+ * Interface representing the interprocedural control flow graph
+ */
+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
+
+ /**
+ * Build a string representation of a statement. Only used for debugging purposes!
+ * @param indent to use on newlines (e.g. indentation for prettier logs)
+ * @param short whether to build a long or a more compact string
+ */
+ def stringifyStatement(stmt: Statement, indent: String = "", short: Boolean = false): String
+}
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..de45682ef1
--- /dev/null
+++ b/OPAL/ide/src/main/scala/org/opalj/ide/solver/IDEAnalysis.scala
@@ -0,0 +1,929 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.ide.solver
+
+import scala.collection.immutable
+import scala.collection.mutable
+
+import org.opalj.br.analyses.SomeProject
+import org.opalj.br.fpcf.FPCFAnalysis
+import org.opalj.fpcf.Entity
+import org.opalj.fpcf.InterimResult
+import org.opalj.fpcf.ProperPropertyComputationResult
+import org.opalj.fpcf.PropertyKey
+import org.opalj.fpcf.Result
+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.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
+import org.opalj.ide.util.Logging
+
+/**
+ * Basic solver for IDE problems. Uses the exhaustive/forward algorithm that was presented in the original IDE paper
+ * from 1996 as base.
+ */
+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[Statement, Fact, Value]
+) extends FPCFAnalysis with Logging.ByProjectConfig {
+ private type Node = (Statement, Fact)
+ /**
+ * A 'path' in the graph, denoted by it's start and end node as done in the IDE algorithm
+ */
+ private type Path = (Node, Node)
+
+ private type PathWorkList = mutable.Queue[Path]
+
+ private type JumpFunction = EdgeFunction[Value]
+ private type JumpFunctions = mutable.Map[Path, JumpFunction]
+ private type SummaryFunction = EdgeFunction[Value]
+ private type SummaryFunctions = mutable.Map[Path, SummaryFunction]
+
+ private type NodeWorkList = mutable.Queue[Node]
+
+ private type Values = mutable.Map[Node, Value]
+
+ private val identityEdgeFunction = new IdentityEdgeFunction[Value]
+ private val allTopEdgeFunction = new AllTopEdgeFunction[Value](problem.lattice.top) {
+ override def composeWith(secondEdgeFunction: EdgeFunction[Value]): EdgeFunction[Value] = {
+ /* 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 equalTo(otherEdgeFunction: EdgeFunction[Value]): Boolean =
+ otherEdgeFunction eq this
+ }
+
+ /**
+ * Container class for simpler interaction and passing of the 'shared' data
+ */
+ private class State(
+ val targetCallable: Callable
+ ) {
+ /**
+ * The work list for paths used in P1
+ */
+ private val pathWorkList: PathWorkList = mutable.Queue.empty
+
+ /**
+ * The jump functions (incrementally calculated) in P1
+ */
+ private val jumpFunctions: JumpFunctions = mutable.Map.empty
+ /**
+ * Index-like structure for faster access of jump functions map
+ */
+ private val jumpFunctionSFTFByST = mutable.Map.empty[(Statement, Statement), mutable.Set[(Fact, Fact)]]
+
+ /**
+ * The summary functions (incrementally calculated) in P1
+ */
+ private val summaryFunctions: SummaryFunctions = mutable.Map.empty
+
+ /**
+ * Collection of seen end nodes with corresponding jump function (needed for endSummaries extension)
+ */
+ private val endSummaries = mutable.Map.empty[Node, mutable.Set[(Node, JumpFunction)]]
+
+ /**
+ * Map call targets to all seen call sources (similar to a call graph but reversed; needed for endSummaries
+ * extension)
+ */
+ private val callTargetsToSources = mutable.Map.empty[Node, mutable.Set[Node]]
+
+ /**
+ * The work list for nodes used in P2
+ */
+ private val nodeWorkList: NodeWorkList = mutable.Queue.empty
+
+ /**
+ * Store all calculated (intermediate) values
+ */
+ private val values: Values = mutable.Map.empty
+
+ /**
+ * Map outstanding EPKs to the last processed property result and the continuations to be executed when a new
+ * result is available
+ */
+ private val dependees = mutable.Map.empty[SomeEPK, (SomeEOptionP, mutable.Set[() => Unit])]
+
+ def enqueuePath(path: Path): Unit = {
+ pathWorkList.enqueue(path)
+ }
+
+ def dequeuePath(): Path = {
+ pathWorkList.dequeue()
+ }
+
+ def isPathWorkListEmpty: Boolean = {
+ pathWorkList.isEmpty
+ }
+
+ def getPathWorkListSize: Int = {
+ pathWorkList.size
+ }
+
+ def setJumpFunction(path: Path, jumpFunction: JumpFunction): Unit = {
+ jumpFunctions.put(path, jumpFunction)
+
+ val ((source, sourceFact), (target, targetFact)) = path
+ jumpFunctionSFTFByST
+ .getOrElseUpdate((source, target), { mutable.Set.empty })
+ .add((sourceFact, targetFact))
+ }
+
+ def getJumpFunction(path: Path): JumpFunction = {
+ jumpFunctions.getOrElse(path, allTopEdgeFunction) // else part handles IDE lines 1 - 2
+ }
+
+ def lookupJumpFunctions(
+ source: Option[Statement] = None,
+ sourceFact: Option[Fact] = None,
+ target: Option[Statement] = None,
+ targetFact: Option[Fact] = None
+ ): collection.Map[Path, JumpFunction] = {
+ ((source, sourceFact), (target, targetFact)) match {
+ case ((Some(s), None), (Some(t), None)) =>
+ jumpFunctionSFTFByST.getOrElse((s, t), immutable.Set.empty[(Fact, Fact)])
+ .map { case (sF, tF) =>
+ val path = ((s, sF), (t, tF))
+ path -> jumpFunctions(path)
+ }
+ .toMap
+
+ case ((Some(s), None), (Some(t), Some(tF))) =>
+ jumpFunctionSFTFByST.getOrElse((s, t), immutable.Set.empty[(Fact, Fact)])
+ .filter { case (_, tF2) => tF2 == tF }
+ .map { case (sF, _) =>
+ val path = ((s, sF), (t, tF))
+ path -> jumpFunctions(path)
+ }
+ .toMap
+
+ case ((Some(s), Some(sF)), (Some(t), None)) =>
+ jumpFunctionSFTFByST.getOrElse((s, t), immutable.Set.empty[(Fact, Fact)])
+ .filter { case (sF2, _) => sF2 == sF }
+ .map { case (_, tF) =>
+ val path = ((s, sF), (t, tF))
+ path -> jumpFunctions(path)
+ }
+ .toMap
+
+ case _ =>
+ jumpFunctions.filter {
+ case (((s, sf), (t, tf)), _) =>
+ source.forall { source => s == source } &&
+ sourceFact.forall { sourceFact => sf == sourceFact } &&
+ target.forall { target => t == target } &&
+ targetFact.forall { targetFact => tf == targetFact }
+ }
+ }
+ }
+
+ def setSummaryFunction(path: Path, summaryFunction: SummaryFunction): Unit = {
+ summaryFunctions.put(path, summaryFunction)
+ }
+
+ def getSummaryFunction(path: Path): SummaryFunction = {
+ summaryFunctions.getOrElse(path, allTopEdgeFunction) // else part handels IDE lines 3 - 4
+ }
+
+ def addEndSummary(path: Path, jumpFunction: JumpFunction): Unit = {
+ val (start, end) = path
+ val set = endSummaries.getOrElseUpdate(start, mutable.Set.empty)
+ set.add((end, jumpFunction))
+ }
+
+ def getEndSummaries(start: Node): collection.Set[(Node, JumpFunction)] = {
+ endSummaries.getOrElse(start, immutable.Set.empty)
+ }
+
+ def rememberCallEdge(path: Path): Unit = {
+ val (source, target) = path
+
+ val set = callTargetsToSources.getOrElseUpdate(target, mutable.Set.empty)
+ set.add(source)
+ }
+
+ def lookupCallSourcesForTarget(target: Statement, targetFact: Fact): collection.Set[Node] = {
+ callTargetsToSources.getOrElse((target, targetFact), immutable.Set.empty)
+ }
+
+ def enqueueNode(node: Node): Unit = {
+ nodeWorkList.enqueue(node)
+ }
+
+ def dequeueNode(): Node = {
+ nodeWorkList.dequeue()
+ }
+
+ def isNodeWorkListEmpty: Boolean = {
+ nodeWorkList.isEmpty
+ }
+
+ def getNodeWorkListSize: Int = {
+ nodeWorkList.size
+ }
+
+ def getValue(node: Node): Value = {
+ values.getOrElse(node, problem.lattice.top) // else part handles IDE line 1
+ }
+
+ def setValue(node: Node, newValue: Value): Unit = {
+ values.put(node, newValue)
+ }
+
+ def clearValues(): Unit = {
+ values.clear()
+ }
+
+ /**
+ * @return a map from statements to results and the results for the callable in total
+ */
+ def collectResults(callable: Callable): (
+ collection.Map[Statement, collection.Set[(Fact, Value)]],
+ collection.Set[(Fact, Value)]
+ ) = {
+ val relevantValues = values
+ .filter { case ((n, d), _) =>
+ icfg.getCallable(n) == callable && d != problem.nullFact
+ }
+
+ val resultsByStatement = relevantValues
+ .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
+ .filter { case (n, _) => icfg.isNormalExitStatement(n) }
+ .map(_._2.toList)
+ .flatten
+ .groupMapReduce(_._1)(_._2) {
+ (value1, value2) => problem.lattice.meet(value1, value2)
+ }
+ .toSet
+
+ (resultsByStatement, resultsForExit)
+ }
+
+ 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, mutable.Set.empty))
+ set.add(c)
+ }
+
+ def areDependeesEmpty: Boolean = {
+ dependees.isEmpty
+ }
+
+ def getDependeesSize: Int = {
+ dependees.size
+ }
+
+ def getDependees: collection.Set[SomeEOptionP] = {
+ dependees.values.map(_._1).toSet
+ }
+
+ def getAndRemoveDependeeContinuations(eOptionP: SomeEOptionP): collection.Set[() => Unit] = {
+ dependees.remove(eOptionP.toEPK).map(_._2).getOrElse(immutable.Set.empty).toSet
+ }
+ }
+
+ private def nodeToString(node: Node, indent: String = ""): String = {
+ s"Node(\n$indent\t${icfg.stringifyStatement(node._1, s"$indent\t")},\n$indent\t${node._2}\n$indent)"
+ }
+
+ private def pathToString(path: Path, indent: String = ""): String = {
+ s"Path(\n$indent\t${nodeToString(path._1, s"$indent\t")} ->\n$indent\t${nodeToString(path._2, s"$indent\t")}\n$indent)"
+ }
+
+ /**
+ * Run the IDE solver and calculate (and return) the result
+ * @param callable the callable that should be analyzed
+ * @return a result for each statement of the callable plus a result for the callable itself (combining the results
+ * of all exit statements)
+ */
+ // TODO (IDE) WHAT HAPPENS WHEN ANALYZING MULTIPLE CALLABLES? CAN WE CACHE E.G. JUMP/SUMMARY FUNCTIONS?
+ def performAnalysis(callable: Callable): ProperPropertyComputationResult = {
+ logInfo(s"performing ${PropertyKey.name(propertyMetaInformation.key)} for $callable")
+
+ implicit val state: State = new State(callable)
+
+ performPhase1()
+ performPhase2()
+
+ createResult()
+ }
+
+ /**
+ * @return whether the phase is finished or has to be continued once the dependees are resolved
+ */
+ private def performPhase1()(implicit s: State): Boolean = {
+ logDebug("starting phase 1")
+
+ seedPhase1()
+ processPathWorkList()
+
+ if (s.areDependeesEmpty) {
+ logDebug("finished phase 1")
+ true
+ } else {
+ logDebug(s"there is/are ${s.getDependeesSize} outstanding dependee(s)")
+ logDebug("pausing phase 1")
+ false
+ }
+ }
+
+ /**
+ * @return whether the phase is finished or has to be continued once the dependees are resolved
+ */
+ private def continuePhase1()(implicit s: State): Boolean = {
+ logDebug("continuing phase 1")
+
+ processPathWorkList()
+
+ if (s.areDependeesEmpty) {
+ logDebug("all outstanding dependees have been processed")
+ logDebug("finished phase 1")
+ true
+ } else {
+ logDebug(s"there is/are ${s.getDependeesSize} outstanding dependee(s) left")
+ logDebug("pausing phase 1 again")
+ false
+ }
+ }
+
+ /**
+ * Perform phase 2 from scratch
+ */
+ private def performPhase2()(implicit s: State): Unit = {
+ logDebug("starting phase 2")
+
+ // TODO (IDE) PHASE 2 IS PERFORMED FROM SCRATCH ON EACH UPDATE AT THE MOMENT. CONSIDER IMPLEMENTING AN
+ // INCREMENTAL SOLUTION (DECIDE WHAT COULD HAVE CHANGED WHILE RUNNING PHASE 1)
+ s.clearValues()
+
+ seedPhase2()
+ computeValues()
+
+ logDebug("finished phase 2")
+ }
+
+ private def createResult()(
+ implicit s: State
+ ): ProperPropertyComputationResult = {
+ logDebug("starting creation of properties")
+
+ val callable = s.targetCallable
+
+ val (resultsByStatement, resultsForExit) = s.collectResults(callable)
+ val ideRawProperty = new IDERawProperty(
+ propertyMetaInformation.backingPropertyMetaInformation.key,
+ resultsByStatement,
+ resultsForExit
+ )
+
+ logDebug("finished creation of properties")
+
+ if (s.areDependeesEmpty) {
+ logDebug("creating final results")
+ Result(callable, ideRawProperty)
+ } else {
+ logDebug("creating interim results")
+ InterimResult.forUB(callable, ideRawProperty, s.getDependees.toSet, onDependeeUpdateContinuation)
+ }
+ }
+
+ private def onDependeeUpdateContinuation(eps: SomeEPS)(
+ implicit s: State
+ ): ProperPropertyComputationResult = {
+ // 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()
+ performPhase2()
+
+ createResult()
+ }
+
+ private def seedPhase1()(implicit s: State): Unit = {
+ val callable = s.targetCallable
+
+ // IDE P1 lines 5 - 6
+ icfg.getStartStatements(callable).foreach { stmt =>
+ val path = ((stmt, problem.nullFact), (stmt, problem.nullFact))
+ s.enqueuePath(path)
+ s.setJumpFunction(path, identityEdgeFunction)
+
+ problem.getAdditionalSeeds(stmt, callable).foreach { fact =>
+ val path = ((stmt, problem.nullFact), (stmt, fact))
+ s.setJumpFunction(path, identityEdgeFunction)
+ def processAdditionalSeed(): Unit = {
+ val edgeFunction =
+ handleEdgeFunctionResult(
+ problem.getAdditionalSeedsEdgeFunction(stmt, fact, callable),
+ processAdditionalSeed _
+ )
+ s.setJumpFunction(path, s.getJumpFunction(path).meetWith(edgeFunction))
+ s.enqueuePath(path)
+ }
+ processAdditionalSeed()
+ }
+ }
+
+ logDebug(s"seeded with ${s.getPathWorkListSize} path(s)")
+ }
+
+ 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
+
+ logDebug("\nprocessing next path")
+ logTrace(s"path=${pathToString(path)}")
+ logTrace(s"current jumpFunction=$f")
+
+ 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)
+ }
+
+ logDebug(s"${s.getPathWorkListSize} path(s) remaining after processing last path")
+ }
+ }
+
+ private def processCallFlow(path: Path, f: JumpFunction, qs: collection.Set[Callable])(
+ implicit s: State
+ ): Unit = {
+ logDebug("processing as call flow")
+
+ val ((sp, d1), (n, d2)) = path
+
+ val rs = icfg.getNextStatements(n) // IDE P1 line 14
+
+ if (qs.isEmpty) {
+ logDebug(s"handling path with precomputed information as qs=$qs")
+
+ rs.foreach { r =>
+ val d5s = handleFlowFunctionResult(problem.getPrecomputedFlowFunction(n, d2, r).compute(), path)
+
+ logTrace(s"generated the following d5s=$d5s for return statement r=${icfg.stringifyStatement(r)}")
+
+ 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.meetWith(oldSummaryFunction)
+
+ if (!fPrime.equalTo(oldSummaryFunction)) {
+ s.setSummaryFunction(callToReturnPath, fPrime)
+ }
+
+ propagate(((sp, d1), (r, d5)), f.composeWith(fPrime))
+ }
+ }
+ } else {
+
+ qs.foreach { q =>
+ logDebug(s"handling call target q=$q")
+
+ if (problem.hasPrecomputedFlowAndSummaryFunction(n, d2, q)) {
+ logDebug(s"handling path with precomputed information")
+
+ /* Handling for precomputed summaries */
+ rs.foreach { r =>
+ val d5s =
+ handleFlowFunctionResult(problem.getPrecomputedFlowFunction(n, d2, q, r).compute(), path)
+
+ logTrace(s"generated the following d5s=$d5s for return statement r=${icfg.stringifyStatement(r)}")
+
+ 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.meetWith(oldSummaryFunction)
+
+ if (!fPrime.equalTo(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)
+
+ logTrace(s"generated the following d3s=$d3s for start statement sq=${icfg.stringifyStatement(sq)}")
+
+ 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) =>
+ val f4 =
+ handleEdgeFunctionResult(problem.getCallEdgeFunction(n, d2, sq, d3, q), path)
+ rs.foreach { r =>
+ val d5s = handleFlowFunctionResult(
+ problem.getReturnFlowFunction(eq, d4, q, r).compute(),
+ path
+ )
+ d5s.foreach { d5 =>
+ val f5 = handleEdgeFunctionResult(
+ problem.getReturnEdgeFunction(eq, d4, q, r, d5),
+ path
+ )
+ val callToReturnPath = ((n, d2), (r, d5))
+ val oldSummaryFunction = s.getSummaryFunction(callToReturnPath)
+ val fPrime =
+ f4.composeWith(fEndSummary).composeWith(f5).meetWith(oldSummaryFunction)
+
+ if (!fPrime.equalTo(oldSummaryFunction)) {
+ s.setSummaryFunction(callToReturnPath, fPrime)
+ }
+
+ propagate(((sp, d1), (r, d5)), f.composeWith(fPrime))
+ }
+ }
+ }
+ } else {
+ // Default algorithm behavior
+ propagate(((sq, d3), (sq, d3)), identityEdgeFunction)
+ }
+ }
+ }
+ }
+
+ rs.foreach { r =>
+ val d3s = handleFlowFunctionResult(problem.getCallToReturnFlowFunction(n, d2, q, r).compute(), path)
+
+ logTrace(s"generated the following d3s=$d3s for return-site statement r=${icfg.stringifyStatement(r)}")
+
+ // 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.equalTo(allTopEdgeFunction)) {
+ propagate(((sp, d1), (r, d3)), f.composeWith(f3))
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private def processExitFlow(path: Path, f: JumpFunction)(implicit s: State): Unit = {
+ logDebug("processing as exit flow")
+
+ 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 =>
+ logDebug(s"handling calling statement c=${icfg.stringifyStatement(c)}, d4=$d4 and return-site statement r=${icfg.stringifyStatement(r)}")
+
+ // IDE P1 line 21
+ val d5s = handleFlowFunctionResult(problem.getReturnFlowFunction(n, d2, p, r).compute(), path)
+
+ logDebug(s"generated the following d5s=$d5s")
+
+ 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), path)
+
+ // IDE P1 line 24
+ val callToReturnPath = ((c, d4), (r, d5))
+ val oldSummaryFunction = s.getSummaryFunction(callToReturnPath)
+ val fPrime = f4.composeWith(f).composeWith(f5).meetWith(oldSummaryFunction)
+
+ // IDE P1 lines 25 - 29
+ if (!fPrime.equalTo(oldSummaryFunction)) {
+ s.setSummaryFunction(callToReturnPath, fPrime)
+
+ val sqs = icfg.getStartStatements(icfg.getCallable(c))
+ sqs.foreach { sq =>
+ val jumpFunctionsMatchingTarget =
+ s.lookupJumpFunctions(source = Some(sq), target = Some(c), targetFact = Some(d4))
+ jumpFunctionsMatchingTarget.foreach {
+ case (((_, d3), (_, _)), f3) if !f3.equalTo(allTopEdgeFunction) =>
+ propagate(((sq, d3), (r, d5)), f3.composeWith(fPrime))
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private def processNormalFlow(path: Path, f: JumpFunction)(implicit s: State): Unit = {
+ logDebug("processing as normal flow")
+
+ 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)
+
+ logTrace(s"generated the following d3s=$d3s for next statement m=${icfg.stringifyStatement(m)}")
+
+ d3s.foreach { d3 =>
+ propagate(
+ ((sp, d1), (m, d3)),
+ f.composeWith(handleEdgeFunctionResult(problem.getNormalEdgeFunction(n, d2, m, d3), path))
+ )
+ }
+ }
+ }
+
+ private def propagate(e: Path, f: EdgeFunction[Value])(implicit s: State): Unit = {
+ logTrace(s"handling propagation for path=${pathToString(e)} and f=$f")
+
+ // IDE P1 lines 34 - 37
+ val oldJumpFunction = s.getJumpFunction(e)
+ val fPrime = f.meetWith(oldJumpFunction)
+
+ if (!fPrime.equalTo(oldJumpFunction)) {
+ logTrace(s"updating and re-enqueuing path as oldJumpFunction=$oldJumpFunction != fPrime=$fPrime")
+
+ s.setJumpFunction(e, fPrime)
+ s.enqueuePath(e)
+ } else {
+ logTrace(s"nothing to do as oldJumpFunction=$oldJumpFunction == fPrime=$fPrime")
+ }
+ }
+
+ /**
+ * @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): collection.Set[Fact] = {
+ val (facts, dependees) = factsAndDependees
+ if (dependees.nonEmpty) {
+ dependees.foreach { dependee =>
+ s.addDependee(
+ dependee,
+ () => s.enqueuePath(path)
+ )
+ }
+ }
+ facts
+ }
+
+ /**
+ * @param path the path to re-enqueue when getting 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))
+ }
+
+ /**
+ * @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
+ }
+ }
+
+ private def seedPhase2()(implicit s: State): Unit = {
+ val callable = s.targetCallable
+
+ // IDE P2 lines 2 - 3
+ icfg.getStartStatements(callable).foreach { stmt =>
+ val node = (stmt, problem.nullFact)
+ s.enqueueNode(node)
+ s.setValue(node, problem.lattice.bottom)
+ }
+
+ logDebug(s"seeded with ${s.getNodeWorkListSize} node(s)")
+ }
+
+ private def computeValues()(implicit s: State): Unit = {
+ logDebug("starting phase 2 (i)")
+
+ // IDE P2 part (i)
+ while (!s.isNodeWorkListEmpty) { // IDE P2 line 4
+ val node = s.dequeueNode() // IDE P2 line 5
+
+ logDebug("processing next node")
+ logTrace(s"node=${nodeToString(node)}")
+
+ val (n, _) = node
+
+ if (icfg.isCallStatement(n)) { // IDE P2 line 11
+ processCallNode(node, icfg.getCallees(n))
+ } else { // IDE P2 line 7
+ processStartNode(node)
+ }
+
+ logDebug(s"${s.getNodeWorkListSize} node(s) remaining after processing last node")
+ }
+
+ logDebug("finished phase 2 (i)")
+ logDebug("starting phase 2 (ii)")
+
+ // IDE P2 part (ii)
+ // IDE P2 lines 15 - 17
+ // Reduced to the one callable, results are created for
+ val p = s.targetCallable
+ val sps = icfg.getStartStatements(p)
+ val ns = collectReachableStmts(sps, stmt => !icfg.isCallStatement(stmt))
+
+ // IDE P2 line 16 - 17
+ sps.foreach { sp =>
+ ns.foreach { n =>
+ val jumpFunctionsMatchingTarget = s.lookupJumpFunctions(source = Some(sp), target = Some(n))
+ jumpFunctionsMatchingTarget.foreach {
+ case (((_, dPrime), (_, d)), fPrime) if !fPrime.equalTo(allTopEdgeFunction) =>
+ val nSharp = (n, d)
+ val vPrime = problem.lattice.meet(
+ s.getValue((n, d)),
+ fPrime.compute(s.getValue((sp, dPrime)))
+ )
+
+ logTrace(s"setting value of nSharp=${nodeToString(nSharp)} to vPrime=$vPrime")
+
+ s.setValue(nSharp, vPrime)
+ }
+ }
+ }
+
+ logDebug("finished phase 2 (ii)")
+ }
+
+ /**
+ * Collect all statements that are reachable from a certain start set of statements
+ * @param originStmts the statements to start searchgin from
+ * @param filterPredicate an additional predicate the collected statements have to fulfill
+ */
+ private def collectReachableStmts(
+ originStmts: collection.Set[Statement],
+ filterPredicate: Statement => Boolean
+ ): collection.Set[Statement] = {
+ val collectedStmts = mutable.Set.empty[Statement]
+ var workingStmts = originStmts
+ val seenStmts = mutable.Set.empty[Statement]
+
+ while (workingStmts.nonEmpty) {
+ collectedStmts.addAll(workingStmts.filter(filterPredicate))
+ seenStmts.addAll(workingStmts)
+ workingStmts = workingStmts.foldLeft(mutable.Set.empty[Statement]) { (nextStmts, stmt) =>
+ nextStmts.addAll(icfg.getNextStatements(stmt))
+ }.diff(seenStmts)
+ }
+
+ collectedStmts
+ }
+
+ private def processStartNode(node: Node)(implicit s: State): Unit = {
+ logDebug("processing as start node")
+
+ val (n, d) = node
+
+ // IDE P2 line 8
+ val cs = collectReachableStmts(immutable.Set(n), stmt => icfg.isCallStatement(stmt))
+
+ // IDE P2 lines 9 - 10
+ cs.foreach { c =>
+ val jumpFunctionsMatchingTarget =
+ s.lookupJumpFunctions(source = Some(n), sourceFact = Some(d), target = Some(c))
+ jumpFunctionsMatchingTarget.foreach {
+ case (((_, _), (_, dPrime)), fPrime) if !fPrime.equalTo(allTopEdgeFunction) =>
+ propagateValue((c, dPrime), fPrime.compute(s.getValue((n, d))))
+ }
+ }
+ }
+
+ private def processCallNode(node: Node, qs: collection.Set[Callable])(implicit s: State): Unit = {
+ logDebug("processing as call node")
+
+ 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))
+ )
+ }
+ }
+ }
+ }
+ }
+
+ private def propagateValue(nSharp: Node, v: Value)(implicit s: State): Unit = {
+ logTrace(s"handling propagation for nSharp=${nodeToString(nSharp)} and v=$v")
+
+ // IDE P2 lines 18 - 21
+ val oldValue = s.getValue(nSharp)
+ val vPrime = problem.lattice.meet(v, oldValue)
+
+ if (vPrime != oldValue) {
+ logTrace(s"updating and re-enqueuing node as oldValue=$oldValue != vPrime=$vPrime")
+
+ s.setValue(nSharp, vPrime)
+ s.enqueueNode(nSharp)
+ } else {
+ logTrace(s"nothing to do as oldValue=$oldValue == vPrime=$vPrime")
+ }
+ }
+
+ /**
+ * Extract facts from flow function result while ignoring the dependees
+ */
+ private def extractFlowFunctionFromResult(
+ factsAndDependees: FlowFunction.FactsAndDependees[Fact]
+ ): 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..26fc111897
--- /dev/null
+++ b/OPAL/ide/src/main/scala/org/opalj/ide/solver/IDEAnalysisProxy.scala
@@ -0,0 +1,95 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.ide.solver
+
+import scala.collection.immutable
+
+import org.opalj.br.analyses.SomeProject
+import org.opalj.br.fpcf.FPCFAnalysis
+import org.opalj.fpcf.Entity
+import org.opalj.fpcf.InterimPartialResult
+import org.opalj.fpcf.InterimResult
+import org.opalj.fpcf.ProperPropertyComputationResult
+import org.opalj.fpcf.PropertyKey
+import org.opalj.fpcf.Result
+import org.opalj.fpcf.SomeEPS
+import org.opalj.ide.integration.IDEPropertyMetaInformation
+import org.opalj.ide.problem.IDEFact
+import org.opalj.ide.problem.IDEValue
+import org.opalj.ide.util.Logging
+
+/**
+ * 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 forward it to the actual IDE
+ * solver.
+ */
+class IDEAnalysisProxy[Fact <: IDEFact, Value <: IDEValue, Statement, Callable <: Entity](
+ val project: SomeProject,
+ val propertyMetaInformation: IDEPropertyMetaInformation[Statement, Fact, Value]
+) extends FPCFAnalysis with Logging.ByProjectConfig {
+ /**
+ * @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 = {
+ logInfo(s"proxying request to ${PropertyKey.name(propertyMetaInformation.key)} for $entity")
+
+ val (callable, stmtOption) = entity match {
+ case (c: Entity, s: Entity) => (c.asInstanceOf[Callable], Some(s.asInstanceOf[Statement]))
+ case c => (c.asInstanceOf[Callable], None)
+ }
+
+ 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
+ InterimPartialResult(
+ immutable.Set(backingEOptionP),
+ onDependeeUpdateContinuation(callable, stmtOption)
+ )
+ } else if (backingEOptionP.isFinal) {
+ Result(
+ entity,
+ propertyMetaInformation.createProperty(
+ stmtOption match {
+ case Some(statement) => backingEOptionP.ub.stmtResults.getOrElse(statement, immutable.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, immutable.Set.empty)
+ case None => backingEOptionP.ub.callableResults
+ }
+ ),
+ immutable.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/ide/src/main/scala/org/opalj/ide/util/Logging.scala b/OPAL/ide/src/main/scala/org/opalj/ide/util/Logging.scala
new file mode 100644
index 0000000000..eabab04a2f
--- /dev/null
+++ b/OPAL/ide/src/main/scala/org/opalj/ide/util/Logging.scala
@@ -0,0 +1,80 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.ide.util
+
+import org.opalj.br.analyses.SomeProject
+import org.opalj.ide.ConfigKeyDebugLog
+import org.opalj.ide.ConfigKeyTraceLog
+import org.opalj.ide.FrameworkName
+import org.opalj.log.GlobalLogContext
+import org.opalj.log.LogContext
+import org.opalj.log.OPALLogger
+
+/**
+ * Logging extension for IDE
+ */
+trait Logging {
+ /**
+ * Whether debug messages should be logged
+ */
+ val isDebug: Boolean
+ /**
+ * Whether trace messages should be logged
+ */
+ val isTrace: Boolean
+
+ /**
+ * Log an info message
+ */
+ protected def logInfo(message: => String)(implicit ctx: LogContext): Unit = {
+ OPALLogger.info(FrameworkName, message)
+ }
+
+ /**
+ * Log a warn message
+ */
+ protected def logWarn(message: => String)(implicit ctx: LogContext): Unit = {
+ OPALLogger.warn(FrameworkName, message)
+ }
+
+ /**
+ * Log a debug message
+ */
+ protected def logDebug(message: => String)(implicit ctx: LogContext): Unit = {
+ OPALLogger.debug({ isDebug }, s"$FrameworkName - debug", message)
+ }
+
+ /**
+ * Log a trace message
+ */
+ protected def logTrace(message: => String)(implicit ctx: LogContext): Unit = {
+ OPALLogger.debug({ isTrace }, s"$FrameworkName - trace", message)
+ }
+}
+
+object Logging {
+ /**
+ * Logging extension for IDE where debug and trace messages are enabled
+ */
+ trait EnableAll extends Logging {
+ override val isDebug: Boolean = true
+ override val isTrace: Boolean = true
+ }
+
+ /**
+ * Logging extension for IDE where debug and trace messages are en-/disabled dynamically based on the configuration
+ * of the given project
+ */
+ trait ByProjectConfig extends Logging {
+ val project: SomeProject
+
+ lazy val isDebug: Boolean = project.config.getBoolean(ConfigKeyDebugLog)
+ lazy val isTrace: Boolean = project.config.getBoolean(ConfigKeyTraceLog)
+ }
+
+ /**
+ * Drop-in of the global log context (when no log context is present)
+ */
+ trait GlobalLogContext extends Logging {
+ implicit val logContext: LogContext = GlobalLogContext
+ }
+}
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..f99baa6a91
--- /dev/null
+++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/ifds/integration/JavaIFDSAnalysisScheduler.scala
@@ -0,0 +1,18 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.tac.fpcf.analyses.ide.ifds.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
+ */
+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..f762500752
--- /dev/null
+++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/ifds/integration/JavaIFDSPropertyMetaInformation.scala
@@ -0,0 +1,11 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.tac.fpcf.analyses.ide.ifds.integration
+
+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
+ */
+trait JavaIFDSPropertyMetaInformation[Fact <: IDEFact] extends IFDSPropertyMetaInformation[JavaStatement, Fact]
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..59b6bdc655
--- /dev/null
+++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/ifds/problem/JavaIFDSProblem.scala
@@ -0,0 +1,12 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.tac.fpcf.analyses.ide.ifds.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
+ */
+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..56ea5d362a
--- /dev/null
+++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/LCPOnFieldsAnalysisScheduler.scala
@@ -0,0 +1,47 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.tac.fpcf.analyses.ide.instances.lcp_on_fields
+
+import scala.collection.immutable
+
+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.FieldImmutability
+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 [[LCPOnFieldsProblem]]).
+ */
+abstract 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 ++ Seq(
+ DeclaredFieldsKey
+ )
+
+ override def uses: Set[PropertyBounds] =
+ super.uses.union(immutable.Set(
+ PropertyBounds.ub(FieldImmutability),
+ 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..6fef15e7f4
--- /dev/null
+++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/LCPOnFieldsPropertyMetaInformation.scala
@@ -0,0 +1,15 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.tac.fpcf.analyses.ide.instances.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
+ */
+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..3616162a79
--- /dev/null
+++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/LinearConstantPropagationAnalysisSchedulerExtended.scala
@@ -0,0 +1,40 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.tac.fpcf.analyses.ide.instances.lcp_on_fields
+
+import scala.collection.immutable
+
+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
+ */
+abstract 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.union(immutable.Set(
+ 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..e7c97afc40
--- /dev/null
+++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/problem/LCPOnFieldsEdgeFunctions.scala
@@ -0,0 +1,448 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.tac.fpcf.analyses.ide.instances.lcp_on_fields.problem
+
+import scala.collection.immutable
+
+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.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)
+ */
+case class ObjectEdgeFunction(
+ values: immutable.Map[String, LinearConstantPropagationValue]
+) extends EdgeFunction[LCPOnFieldsValue] {
+ override def compute(sourceValue: LCPOnFieldsValue): LCPOnFieldsValue =
+ 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(secondEdgeFunction: EdgeFunction[LCPOnFieldsValue]): EdgeFunction[LCPOnFieldsValue] =
+ secondEdgeFunction match {
+ case ObjectEdgeFunction(values2) =>
+ ObjectEdgeFunction((values -- values2.keys) ++ values2)
+
+ case PutFieldEdgeFunction(fieldName, value) =>
+ ObjectEdgeFunction((values - fieldName) + (fieldName -> value))
+
+ case IdentityEdgeFunction() => this
+ case AllTopEdgeFunction(_) => secondEdgeFunction
+ case AllBottomEdgeFunction(_) => secondEdgeFunction
+
+ case _ =>
+ throw new UnsupportedOperationException(s"Composing $this with $secondEdgeFunction is not implemented!")
+ }
+
+ override def meetWith(otherEdgeFunction: EdgeFunction[LCPOnFieldsValue]): EdgeFunction[LCPOnFieldsValue] =
+ otherEdgeFunction match {
+ case ObjectEdgeFunction(values2) =>
+ ObjectEdgeFunction(
+ values.keySet
+ .intersect(values2.keySet)
+ .map { fieldName =>
+ fieldName -> LinearConstantPropagationLattice.meet(values(fieldName), values2(fieldName))
+ }
+ .toMap
+ )
+
+ case PutFieldEdgeFunction(fieldName, value) =>
+ ObjectEdgeFunction(
+ (values - fieldName) +
+ (fieldName -> LinearConstantPropagationLattice.meet(
+ value,
+ values.getOrElse(fieldName, linear_constant_propagation.problem.VariableValue)
+ ))
+ )
+
+ case IdentityEdgeFunction() => this
+ case AllTopEdgeFunction(_) => this
+ case AllBottomEdgeFunction(_) => otherEdgeFunction
+
+ case _ =>
+ throw new UnsupportedOperationException(s"Meeting $this with $otherEdgeFunction is not implemented!")
+ }
+
+ override def equalTo(otherEdgeFunction: EdgeFunction[LCPOnFieldsValue]): Boolean =
+ (otherEdgeFunction eq this) ||
+ (otherEdgeFunction match {
+ case ObjectEdgeFunction(values2) => values == values2
+ case _ => false
+ })
+}
+
+/**
+ * Edge function for initializing an object
+ */
+object NewObjectEdgeFunction extends ObjectEdgeFunction(immutable.Map.empty) {
+ override def toString: String = "NewObjectEdgeFunction()"
+}
+
+/**
+ * Edge function modeling the effect of writing the field of an object
+ */
+case class PutFieldEdgeFunction(
+ fieldName: String,
+ value: LinearConstantPropagationValue
+) extends EdgeFunction[LCPOnFieldsValue] {
+ override def compute(sourceValue: LCPOnFieldsValue): LCPOnFieldsValue = {
+ sourceValue match {
+ case UnknownValue => UnknownValue
+ case ObjectValue(values) => ObjectValue((values - fieldName) + (fieldName -> value))
+ case VariableValue => VariableValue
+ }
+ }
+
+ override def composeWith(secondEdgeFunction: EdgeFunction[LCPOnFieldsValue]): EdgeFunction[LCPOnFieldsValue] = {
+ 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(immutable.Map(fieldName -> value, fieldName2 -> value2))
+
+ case IdentityEdgeFunction() => this
+ case AllTopEdgeFunction(_) => secondEdgeFunction
+ case AllBottomEdgeFunction(_) => secondEdgeFunction
+
+ case _ =>
+ throw new UnsupportedOperationException(s"Composing $this with $secondEdgeFunction is not implemented!")
+ }
+ }
+
+ override def meetWith(otherEdgeFunction: EdgeFunction[LCPOnFieldsValue]): EdgeFunction[LCPOnFieldsValue] = {
+ otherEdgeFunction match {
+ case ObjectEdgeFunction(values2) =>
+ ObjectEdgeFunction(
+ (values2 - fieldName) +
+ (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(immutable.Map(
+ fieldName -> linear_constant_propagation.problem.VariableValue,
+ fieldName2 -> linear_constant_propagation.problem.VariableValue
+ ))
+
+ case IdentityEdgeFunction() => this
+ case AllTopEdgeFunction(_) => this
+ case AllBottomEdgeFunction(_) => otherEdgeFunction
+
+ case _ =>
+ throw new UnsupportedOperationException(s"Meeting $this with $otherEdgeFunction is not implemented!")
+ }
+ }
+
+ override def equalTo(otherEdgeFunction: EdgeFunction[LCPOnFieldsValue]): 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`).
+ */
+class ArrayEdgeFunction(
+ val initValue: LinearConstantPropagationValue,
+ val elements: immutable.Map[Int, LinearConstantPropagationValue]
+) extends EdgeFunction[LCPOnFieldsValue] {
+ override def compute(sourceValue: LCPOnFieldsValue): LCPOnFieldsValue =
+ 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(secondEdgeFunction: EdgeFunction[LCPOnFieldsValue]): EdgeFunction[LCPOnFieldsValue] =
+ secondEdgeFunction match {
+ case NewArrayEdgeFunction(_) => secondEdgeFunction
+
+ case ArrayEdgeFunction(initValue2, elements2) =>
+ new 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 */
+ UnknownValueEdgeFunction
+ case linear_constant_propagation.problem.ConstantValue(i) =>
+ new ArrayEdgeFunction(initValue, (elements - i) + (i -> value))
+ case linear_constant_propagation.problem.VariableValue =>
+ /* In this case any index could be affected */
+ new ArrayEdgeFunction(linear_constant_propagation.problem.VariableValue, immutable.Map.empty)
+ }
+
+ case IdentityEdgeFunction() => this
+ case AllTopEdgeFunction(_) => secondEdgeFunction
+ case AllBottomEdgeFunction(_) => secondEdgeFunction
+
+ case _ =>
+ throw new UnsupportedOperationException(s"Composing $this with $secondEdgeFunction is not implemented!")
+ }
+
+ override def meetWith(otherEdgeFunction: EdgeFunction[LCPOnFieldsValue]): EdgeFunction[LCPOnFieldsValue] =
+ otherEdgeFunction match {
+ case ArrayEdgeFunction(initValue2, elements2) =>
+ new 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 =>
+ UnknownValueEdgeFunction
+ case linear_constant_propagation.problem.ConstantValue(i) =>
+ new ArrayEdgeFunction(
+ initValue,
+ (elements - i) + (i -> LinearConstantPropagationLattice.meet(
+ value,
+ elements.getOrElse(i, initValue)
+ ))
+ )
+ case linear_constant_propagation.problem.VariableValue =>
+ new ArrayEdgeFunction(linear_constant_propagation.problem.VariableValue, immutable.Map.empty)
+ }
+
+ case IdentityEdgeFunction() => this
+ case AllTopEdgeFunction(_) => this
+ case AllBottomEdgeFunction(_) => otherEdgeFunction
+
+ case _ =>
+ throw new UnsupportedOperationException(s"Meeting $this with $otherEdgeFunction is not implemented!")
+ }
+
+ override def equalTo(otherEdgeFunction: EdgeFunction[LCPOnFieldsValue]): Boolean =
+ (otherEdgeFunction eq this) ||
+ (otherEdgeFunction match {
+ case ArrayEdgeFunction(initValue2, elements2) => initValue == initValue2 && elements == elements2
+ case _ => false
+ })
+}
+
+object ArrayEdgeFunction {
+ def unapply(arrayEdgeFunction: ArrayEdgeFunction): Some[(
+ LinearConstantPropagationValue,
+ immutable.Map[Int, LinearConstantPropagationValue]
+ )] = Some((arrayEdgeFunction.initValue, arrayEdgeFunction.elements))
+}
+
+/**
+ * Edge function for initializing an array
+ */
+case class NewArrayEdgeFunction(
+ override val initValue: LinearConstantPropagationValue = linear_constant_propagation.problem.ConstantValue(0)
+) extends ArrayEdgeFunction(initValue, immutable.Map.empty) {
+ override def toString: String = s"NewArrayEdgeFunction($initValue)"
+}
+
+/**
+ * Edge function modeling the effect of writing an element of an array
+ */
+case class PutElementEdgeFunction(
+ index: LinearConstantPropagationValue,
+ value: LinearConstantPropagationValue
+) extends EdgeFunction[LCPOnFieldsValue] {
+ override def compute(sourceValue: LCPOnFieldsValue): LCPOnFieldsValue = {
+ sourceValue match {
+ case UnknownValue => UnknownValue
+ case ArrayValue(initValue, elements) =>
+ index match {
+ case linear_constant_propagation.problem.ConstantValue(i) =>
+ ArrayValue(initValue, (elements - i) + (i -> value))
+ case _ =>
+ ArrayValue(linear_constant_propagation.problem.VariableValue, immutable.Map.empty)
+ }
+ case VariableValue => VariableValue
+
+ case _ =>
+ throw new UnsupportedOperationException(s"Computing $this for $sourceValue is not implemented!")
+ }
+ }
+
+ override def composeWith(secondEdgeFunction: EdgeFunction[LCPOnFieldsValue]): EdgeFunction[LCPOnFieldsValue] = {
+ secondEdgeFunction match {
+ case NewArrayEdgeFunction(_) => secondEdgeFunction
+
+ case ArrayEdgeFunction(initValue, elements) =>
+ index match {
+ case linear_constant_propagation.problem.UnknownValue =>
+ UnknownValueEdgeFunction
+ case linear_constant_propagation.problem.ConstantValue(i) =>
+ new ArrayEdgeFunction(
+ initValue,
+ (elements - i) + (i -> LinearConstantPropagationLattice.meet(
+ value,
+ elements.getOrElse(i, initValue)
+ ))
+ )
+ case linear_constant_propagation.problem.VariableValue =>
+ new ArrayEdgeFunction(linear_constant_propagation.problem.VariableValue, immutable.Map.empty)
+ }
+
+ case PutElementEdgeFunction(index2, _) if index == index2 => secondEdgeFunction
+ case PutElementEdgeFunction(_, _) =>
+ new ArrayEdgeFunction(linear_constant_propagation.problem.UnknownValue, immutable.Map.empty)
+ .composeWith(this).composeWith(secondEdgeFunction)
+
+ case IdentityEdgeFunction() => this
+ case AllTopEdgeFunction(_) => secondEdgeFunction
+ case AllBottomEdgeFunction(_) => secondEdgeFunction
+
+ case _ =>
+ throw new UnsupportedOperationException(s"Composing $this with $secondEdgeFunction is not implemented!")
+ }
+ }
+
+ override def meetWith(otherEdgeFunction: EdgeFunction[LCPOnFieldsValue]): EdgeFunction[LCPOnFieldsValue] = {
+ otherEdgeFunction match {
+ case ArrayEdgeFunction(initValue, elements) =>
+ index match {
+ case linear_constant_propagation.problem.UnknownValue =>
+ UnknownValueEdgeFunction
+ case linear_constant_propagation.problem.ConstantValue(i) =>
+ new ArrayEdgeFunction(
+ initValue,
+ (elements - i) + (i -> LinearConstantPropagationLattice.meet(
+ value,
+ elements.getOrElse(i, initValue)
+ ))
+ )
+ case linear_constant_propagation.problem.VariableValue =>
+ new ArrayEdgeFunction(linear_constant_propagation.problem.VariableValue, immutable.Map.empty)
+ }
+
+ case PutElementEdgeFunction(index2, value2) =>
+ PutElementEdgeFunction(
+ LinearConstantPropagationLattice.meet(index, index2),
+ LinearConstantPropagationLattice.meet(value, value2)
+ )
+
+ case IdentityEdgeFunction() => this
+ case AllTopEdgeFunction(_) => this
+ case AllBottomEdgeFunction(_) => otherEdgeFunction
+
+ case _ =>
+ throw new UnsupportedOperationException(s"Meeting $this with $otherEdgeFunction is not implemented!")
+ }
+ }
+
+ override def equalTo(otherEdgeFunction: EdgeFunction[LCPOnFieldsValue]): 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
+ */
+case class PutStaticFieldEdgeFunction(
+ value: LinearConstantPropagationValue
+) extends EdgeFunction[LCPOnFieldsValue] {
+ override def compute(sourceValue: LCPOnFieldsValue): LCPOnFieldsValue = {
+ StaticFieldValue(value)
+ }
+
+ override def composeWith(secondEdgeFunction: EdgeFunction[LCPOnFieldsValue]): EdgeFunction[LCPOnFieldsValue] = {
+ secondEdgeFunction match {
+ case PutStaticFieldEdgeFunction(_) => secondEdgeFunction
+
+ case IdentityEdgeFunction() => this
+ case AllTopEdgeFunction(_) => secondEdgeFunction
+ case AllBottomEdgeFunction(_) => secondEdgeFunction
+
+ case _ =>
+ throw new UnsupportedOperationException(s"Composing $this with $secondEdgeFunction is not implemented!")
+ }
+ }
+
+ override def meetWith(otherEdgeFunction: EdgeFunction[LCPOnFieldsValue]): EdgeFunction[LCPOnFieldsValue] = {
+ otherEdgeFunction match {
+ case PutStaticFieldEdgeFunction(value2) =>
+ PutStaticFieldEdgeFunction(
+ LinearConstantPropagationLattice.meet(value, value2)
+ )
+
+ case IdentityEdgeFunction() => this
+ case AllTopEdgeFunction(_) => this
+ case AllBottomEdgeFunction(_) => otherEdgeFunction
+
+ case _ =>
+ throw new UnsupportedOperationException(s"Meeting $this with $otherEdgeFunction is not implemented!")
+ }
+ }
+
+ override def equalTo(otherEdgeFunction: EdgeFunction[LCPOnFieldsValue]): Boolean =
+ (otherEdgeFunction eq this) ||
+ (otherEdgeFunction match {
+ case PutStaticFieldEdgeFunction(value2) => value == value2
+ case _ => false
+ })
+}
+
+/**
+ * Edge function for cases where a value is unknown
+ */
+object UnknownValueEdgeFunction extends AllTopEdgeFunction[LCPOnFieldsValue](UnknownValue) {
+ override def composeWith(
+ secondEdgeFunction: EdgeFunction[LCPOnFieldsValue]
+ ): EdgeFunction[LCPOnFieldsValue] = {
+ this
+ }
+
+ override def equalTo(otherEdgeFunction: EdgeFunction[LCPOnFieldsValue]): Boolean =
+ otherEdgeFunction eq this
+
+ override def toString: String = "UnknownValueEdgeFunction()"
+}
+
+/**
+ * Edge function for cases where a value is variable
+ */
+object VariableValueEdgeFunction extends AllBottomEdgeFunction[LCPOnFieldsValue](VariableValue) {
+ override def composeWith(secondEdgeFunction: EdgeFunction[LCPOnFieldsValue]): EdgeFunction[LCPOnFieldsValue] = {
+ 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..a87b982823
--- /dev/null
+++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/problem/LCPOnFieldsFact.scala
@@ -0,0 +1,129 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.tac.fpcf.analyses.ide.instances.lcp_on_fields.problem
+
+import org.opalj.br.ObjectType
+import org.opalj.ide.problem.IDEFact
+
+/**
+ * Type for modeling facts for linear constant propagation on fields
+ */
+trait LCPOnFieldsFact extends IDEFact
+
+/**
+ * Fact to use as null fact
+ */
+case object NullFact extends LCPOnFieldsFact
+
+/**
+ * Common type for different types of entities
+ */
+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
+ */
+trait AbstractObjectFact extends AbstractEntityFact {
+ def toObjectFact: ObjectFact = ObjectFact(name, definedAtIndex)
+
+ override def toObjectOrArrayFact: AbstractEntityFact = toObjectFact
+}
+
+/**
+ * Fact representing a seen object variable
+ */
+case class ObjectFact(name: String, definedAtIndex: Int) extends AbstractObjectFact {
+ override def toObjectFact: ObjectFact = this
+
+ override def toString: String = s"ObjectFact($name)"
+}
+
+/**
+ * Fact representing a seen object variable and modeling that it gets initialized
+ */
+case class NewObjectFact(name: String, definedAtIndex: Int) extends AbstractObjectFact {
+ override def toString: String = s"NewObjectFact($name)"
+}
+
+/**
+ * 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
+ */
+case class PutFieldFact(name: String, definedAtIndex: Int, fieldName: String) extends AbstractObjectFact {
+ override def toString: String = s"PutFieldFact($name, $fieldName)"
+}
+
+/**
+ * Type for array facts
+ */
+trait AbstractArrayFact extends AbstractEntityFact {
+ def toArrayFact: ArrayFact = ArrayFact(name, definedAtIndex)
+
+ override def toObjectOrArrayFact: AbstractEntityFact = toArrayFact
+}
+
+/**
+ * Fact representing a seen array variable
+ */
+case class ArrayFact(name: String, definedAtIndex: Int) extends AbstractArrayFact {
+ override def toArrayFact: ArrayFact = this
+
+ override def toString: String = s"ArrayFact($name)"
+}
+
+/**
+ * Fact representing a seen array variable and modeling that it gets initialized
+ */
+case class NewArrayFact(name: String, definedAtIndex: Int) extends AbstractArrayFact {
+ override def toString: String = s"NewArrayFact($name)"
+}
+
+/**
+ * Fact representing a seen array variable and modeling that one of its elements gets written
+ */
+case class PutElementFact(name: String, definedAtIndex: Int) extends AbstractArrayFact {
+ override def toString: String = s"PutElementFact($name)"
+}
+
+/**
+ * Type for facts for static fields
+ */
+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
+ */
+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
+ */
+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..6ebb16e72b
--- /dev/null
+++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/problem/LCPOnFieldsLattice.scala
@@ -0,0 +1,47 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.tac.fpcf.analyses.ide.instances.lcp_on_fields.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
+ */
+object LCPOnFieldsLattice extends MeetLattice[LCPOnFieldsValue] {
+ override def top: LCPOnFieldsValue = UnknownValue
+
+ override def bottom: LCPOnFieldsValue = VariableValue
+
+ override def meet(x: LCPOnFieldsValue, y: LCPOnFieldsValue): LCPOnFieldsValue = (x, y) match {
+ case (UnknownValue, y) => y
+ case (x, UnknownValue) => x
+ case (VariableValue, _) => VariableValue
+ case (_, VariableValue) => VariableValue
+
+ case (ObjectValue(xValues), ObjectValue(yValues)) =>
+ val values = xValues.keySet
+ .intersect(yValues.keySet)
+ .map { fieldName =>
+ fieldName -> LinearConstantPropagationLattice.meet(xValues(fieldName), yValues(fieldName))
+ }
+ .toMap
+ ObjectValue(values)
+
+ case (ArrayValue(xInitValue, xElements), ArrayValue(yInitValue, yElements)) =>
+ val elements = xElements.keySet
+ .union(yElements.keySet)
+ .map { index =>
+ index -> LinearConstantPropagationLattice.meet(
+ xElements.getOrElse(index, xInitValue),
+ yElements.getOrElse(index, yInitValue)
+ )
+ }
+ .toMap
+ ArrayValue(
+ LinearConstantPropagationLattice.meet(xInitValue, yInitValue),
+ elements
+ )
+
+ 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..2528a151be
--- /dev/null
+++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/problem/LCPOnFieldsProblem.scala
@@ -0,0 +1,779 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.tac.fpcf.analyses.ide.instances.lcp_on_fields.problem
+
+import scala.collection.immutable
+import scala.collection.mutable
+
+import org.opalj.ai.domain.l1.DefaultIntegerRangeValues
+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.FieldImmutability
+import org.opalj.br.fpcf.properties.immutability.TransitivelyImmutableField
+import org.opalj.fpcf.FinalP
+import org.opalj.fpcf.InterimUBP
+import org.opalj.fpcf.Property
+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.InterimEdgeFunction
+import org.opalj.ide.problem.MeetLattice
+import org.opalj.tac.ArrayStore
+import org.opalj.tac.Assignment
+import org.opalj.tac.New
+import org.opalj.tac.NewArray
+import org.opalj.tac.PutField
+import org.opalj.tac.PutStatic
+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
+
+/**
+ * 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.
+ */
+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
+ ): collection.Set[LCPOnFieldsFact] = {
+ callee.classFile.fields.filter(_.isStatic).map { field =>
+ StaticFieldFact(field.classFile.thisType, field.name)
+ }.toSet
+ }
+
+ override def getAdditionalSeedsEdgeFunction(stmt: JavaStatement, fact: LCPOnFieldsFact, callee: Method)(
+ implicit propertyStore: PropertyStore
+ ): EdgeFunctionResult[LCPOnFieldsValue] = {
+ fact match {
+ 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 fieldImmutabilityEOptionP = propertyStore(field, FieldImmutability.key)
+
+ fieldImmutabilityEOptionP match {
+ case FinalP(fieldImmutability) =>
+ fieldImmutability match {
+ case TransitivelyImmutableField =>
+ val value = getValueForGetStaticExprByStaticInitializer(field)
+ FinalEdgeFunction(PutStaticFieldEdgeFunction(value))
+ case _ =>
+ FinalEdgeFunction(PutStaticFieldEdgeFunction(linear_constant_propagation.problem.VariableValue))
+ }
+
+ case InterimUBP(fieldImmutability) =>
+ fieldImmutability match {
+ case TransitivelyImmutableField =>
+ val value = getValueForGetStaticExprByStaticInitializer(field)
+ InterimEdgeFunction(PutStaticFieldEdgeFunction(value), immutable.Set(fieldImmutabilityEOptionP))
+ case _ =>
+ FinalEdgeFunction(PutStaticFieldEdgeFunction(linear_constant_propagation.problem.VariableValue))
+ }
+
+ case _ =>
+ InterimEdgeFunction(
+ PutStaticFieldEdgeFunction(linear_constant_propagation.problem.UnknownValue),
+ immutable.Set(fieldImmutabilityEOptionP)
+ )
+ }
+ }
+
+ 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: mutable.Set[JavaStatement] = mutable.Set.from(icfg.getStartStatements(method))
+ val seenStmts = mutable.Set.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 intRange: DefaultIntegerRangeValues#IntegerRange =>
+ if (intRange.lowerBound == intRange.upperBound) {
+ value = LinearConstantPropagationLattice.meet(
+ value,
+ linear_constant_propagation.problem.ConstantValue(intRange.upperBound)
+ )
+ } else {
+ return linear_constant_propagation.problem.VariableValue
+ }
+
+ case _ =>
+ return linear_constant_propagation.problem.VariableValue
+ }
+ }
+
+ case _ =>
+ }
+ }
+
+ seenStmts.addAll(workingStmts)
+ workingStmts = workingStmts.foldLeft(mutable.Set.empty[JavaStatement]) { (nextStmts, stmt) =>
+ nextStmts.addAll(icfg.getNextStatements(stmt))
+ }.diff(seenStmts)
+ }
+
+ 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 */
+ immutable.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) {
+ immutable.Set(sourceFact, NewArrayFact(assignment.targetVar.name, source.pc))
+ } else {
+ immutable.Set(sourceFact)
+ }
+
+ case _ => immutable.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 */
+ immutable.Set(PutFieldFact(f.name, f.definedAtIndex, putField.name))
+ } else {
+ immutable.Set(f.toObjectFact)
+ }
+ } else {
+ immutable.Set(f.toObjectFact)
+ }
+
+ case (ArrayStore.ASTID, f: AbstractArrayFact) =>
+ val arrayStore = source.stmt.asArrayStore
+ val arrayVar = arrayStore.arrayRef.asVar
+ if (arrayVar.definedBy.contains(f.definedAtIndex)) {
+ immutable.Set(PutElementFact(f.name, f.definedAtIndex))
+ } else {
+ immutable.Set(f.toArrayFact)
+ }
+
+ case (_, f: AbstractEntityFact) =>
+ /* Specialized facts only live for one step and are turned back into basic ones afterwards */
+ immutable.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 immutable.Set(sourceFact)
+ }
+ val field = declaredField.definedField
+
+ /* Only private fields (as they cannot be influenced by other static initializers) */
+ if (field.isPrivate) {
+ immutable.Set(sourceFact, PutStaticFieldFact(putStatic.declaringClass, putStatic.name))
+ } else {
+ immutable.Set(sourceFact)
+ }
+ } else {
+ immutable.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) {
+ immutable.Set.empty
+ } else {
+ immutable.Set(f.toStaticFieldFact)
+ }
+
+ case (_, f: AbstractStaticFieldFact) =>
+ /* Specialized facts only live for one step and are turned back into basic ones afterwards */
+ immutable.Set(f.toStaticFieldFact)
+
+ case _ => immutable.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) {
+ immutable.Set(callSiteFact)
+ } else if (callee.returnType.isArrayType &&
+ callee.returnType.asArrayType.componentType.isIntegerType
+ ) {
+ immutable.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 */
+ immutable.Set(callSiteFact)
+ } else {
+ immutable.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
+ .filter { case (param, _) =>
+ /* Only parameters where the variable represented by the source fact is one possible
+ * initializer */
+ param.asVar.definedBy.contains(f.definedAtIndex)
+ }
+ .map { case (_, index) =>
+ 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) {
+ immutable.Set(f.toStaticFieldFact)
+ } else {
+ immutable.Set.empty
+ }
+ }
+ }
+ }
+ }
+
+ override def getReturnFlowFunction(
+ calleeExit: JavaStatement,
+ calleeExitFact: LCPOnFieldsFact,
+ callee: Method,
+ returnSite: JavaStatement
+ )(implicit propertyStore: PropertyStore): FlowFunction[LCPOnFieldsFact] = {
+ new FlowFunction[LCPOnFieldsFact] {
+ override def compute(): FactsAndDependees = {
+ calleeExitFact match {
+ case NullFact =>
+ /* Always propagate null fact */
+ immutable.Set(calleeExitFact)
+
+ case f: AbstractEntityFact =>
+ val definedAtIndex = f.definedAtIndex
+
+ if (isImplicitOrExternalException(definedAtIndex)) {
+ return immutable.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)) {
+ immutable.Set(f match {
+ case _: AbstractObjectFact =>
+ ObjectFact(assignment.targetVar.name, returnSite.pc)
+ case _: AbstractArrayFact =>
+ ArrayFact(assignment.targetVar.name, returnSite.pc)
+ })
+ } else {
+ immutable.Set.empty
+ }
+
+ case _ => immutable.Set.empty
+ }
+ }
+
+ case f: AbstractStaticFieldFact =>
+ immutable.Set(f.toStaticFieldFact)
+ }
+ }
+ }
+ }
+
+ 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 */
+ immutable.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) }) {
+ immutable.Set.empty
+ } else {
+ immutable.Set(f.toObjectOrArrayFact)
+ }
+
+ case f: AbstractStaticFieldFact =>
+ /* Propagate facts that are not propagated via the call flow */
+ if (callee.classFile.thisType == f.objectType) {
+ immutable.Set.empty
+ } else {
+ immutable.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),
+ immutable.Set(lcpEOptionP)
+ )
+ case linear_constant_propagation.problem.ConstantValue(_) =>
+ InterimEdgeFunction(
+ PutFieldEdgeFunction(fieldName, value),
+ immutable.Set(lcpEOptionP)
+ )
+ case linear_constant_propagation.problem.VariableValue =>
+ FinalEdgeFunction(PutFieldEdgeFunction(fieldName, value))
+ }
+
+ case _ =>
+ InterimEdgeFunction(
+ PutFieldEdgeFunction(
+ fieldName,
+ linear_constant_propagation.problem.UnknownValue
+ ),
+ immutable.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), immutable.Set(lcpEOptionP))
+
+ case _ =>
+ InterimEdgeFunction(
+ PutElementEdgeFunction(
+ linear_constant_propagation.problem.UnknownValue,
+ linear_constant_propagation.problem.UnknownValue
+ ),
+ immutable.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), immutable.Set(lcpEOptionP))
+ case linear_constant_propagation.problem.ConstantValue(_) =>
+ InterimEdgeFunction(PutStaticFieldEdgeFunction(value), immutable.Set(lcpEOptionP))
+ case linear_constant_propagation.problem.VariableValue =>
+ FinalEdgeFunction(PutStaticFieldEdgeFunction(value))
+ }
+
+ case _ =>
+ InterimEdgeFunction(
+ PutStaticFieldEdgeFunction(linear_constant_propagation.problem.UnknownValue),
+ immutable.Set(lcpEOptionP)
+ )
+ }
+
+ case _ => FinalEdgeFunction(identityEdgeFunction)
+ }
+ }
+
+ private def getVariableFromProperty(var0: JavaStatement.V)(property: Property): LinearConstantPropagationValue = {
+ property
+ .asInstanceOf[LinearConstantPropagationPropertyMetaInformation.Self]
+ .results
+ .filter {
+ case (linear_constant_propagation.problem.VariableFact(_, definedAtIndex), _) =>
+ var0.definedBy.contains(definedAtIndex)
+ case _ => false
+ }
+ .map(_._2)
+ .foldLeft(
+ linear_constant_propagation.problem.UnknownValue: LinearConstantPropagationValue
+ ) {
+ case (value1, value2) =>
+ LinearConstantPropagationLattice.meet(value1, value2)
+ }
+ }
+
+ override def getCallEdgeFunction(
+ callSite: JavaStatement,
+ callSiteFact: LCPOnFieldsFact,
+ calleeEntry: JavaStatement,
+ calleeEntryFact: LCPOnFieldsFact,
+ callee: Method
+ )(implicit propertyStore: PropertyStore): EdgeFunctionResult[LCPOnFieldsValue] = identityEdgeFunction
+
+ override def getReturnEdgeFunction(
+ calleeExit: JavaStatement,
+ calleeExitFact: LCPOnFieldsFact,
+ callee: Method,
+ returnSite: JavaStatement,
+ returnSiteFact: 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) {
+ immutable.Set(NewObjectFact(assignment.targetVar.name, returnSite.pc))
+ } else if (callee.returnType.isArrayType &&
+ callee.returnType.asArrayType.componentType.isIntegerType
+ ) {
+ immutable.Set(NewArrayFact(assignment.targetVar.name, returnSite.pc))
+ } else {
+ immutable.Set.empty
+ }
+
+ case _ => immutable.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) }) {
+ immutable.Set(f.toObjectOrArrayFact)
+ } else {
+ immutable.Set.empty
+ }
+
+ case f: AbstractStaticFieldFact =>
+ if (callee.classFile.thisType == f.objectType) {
+ immutable.Set(f.toStaticFieldFact)
+ } else {
+ immutable.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 =>
+ 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) {
+ immutable.Set(callSiteFact, NewObjectFact(assignment.targetVar.name, returnSite.pc))
+ } else if (callStmt.descriptor.returnType.isArrayType &&
+ callStmt.descriptor.returnType.asArrayType.componentType.isIntegerType
+ ) {
+ immutable.Set(callSiteFact, NewArrayFact(assignment.targetVar.name, returnSite.pc))
+ } else {
+ immutable.Set(callSiteFact)
+ }
+
+ case _ => immutable.Set(callSiteFact)
+ }
+
+ case f: AbstractEntityFact =>
+ immutable.Set(f.toObjectOrArrayFact)
+
+ case f: AbstractStaticFieldFact =>
+ immutable.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..e6c2beaed0
--- /dev/null
+++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/problem/LCPOnFieldsValue.scala
@@ -0,0 +1,48 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.tac.fpcf.analyses.ide.instances.lcp_on_fields.problem
+
+import scala.collection.immutable
+
+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
+ */
+trait LCPOnFieldsValue extends IDEValue
+
+/**
+ * Value not known (yet)
+ */
+case object UnknownValue extends LCPOnFieldsValue
+
+/**
+ * Value representing the state of an object
+ */
+case class ObjectValue(values: immutable.Map[String, LinearConstantPropagationValue]) extends LCPOnFieldsValue {
+ override def toString: String = s"ObjectValue(${values.mkString(", ")})"
+}
+
+/**
+ * Value representing the state of an array
+ */
+case class ArrayValue(
+ initValue: LinearConstantPropagationValue,
+ elements: immutable.Map[Int, LinearConstantPropagationValue]
+) extends LCPOnFieldsValue {
+ override def toString: String = s"ArrayValue($initValue, ${elements.mkString(", ")})"
+}
+
+/**
+ * Value representing the value of a static field
+ */
+case class StaticFieldValue(
+ value: LinearConstantPropagationValue
+) extends LCPOnFieldsValue {
+ override def toString: String = s"StaticFieldValue($value)"
+}
+
+/**
+ * Value is variable (not really used currently, mainly for completeness)
+ */
+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..bffa884a26
--- /dev/null
+++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/lcp_on_fields/problem/LinearConstantPropagationProblemExtended.scala
@@ -0,0 +1,258 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.tac.fpcf.analyses.ide.instances.lcp_on_fields.problem
+
+import scala.collection.immutable
+
+import org.opalj.ai.domain.l1.DefaultIntegerRangeValues
+import org.opalj.br.ObjectType
+import org.opalj.fpcf.FinalP
+import org.opalj.fpcf.InterimUBP
+import org.opalj.fpcf.Property
+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.ArrayLoad
+import org.opalj.tac.GetField
+import org.opalj.tac.GetStatic
+import org.opalj.tac.fpcf.analyses.ide.instances.lcp_on_fields.LCPOnFieldsPropertyMetaInformation
+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
+
+/**
+ * Extended definition of the linear constant propagation problem, trying to resolve field accesses with the LCP on
+ * fields analysis.
+ */
+class LinearConstantPropagationProblemExtended extends LinearConstantPropagationProblem {
+ override def isArrayLoadExpressionGeneratedByFact(
+ arrayLoadExpr: ArrayLoad[V]
+ )(
+ source: JavaStatement,
+ sourceFact: LinearConstantPropagationFact,
+ target: JavaStatement
+ ): Boolean = {
+ /* Generate fact only if the variable represented by the source fact is one possible initializer of the index
+ * variable */
+ sourceFact match {
+ case NullFact => false
+ case VariableFact(_, definedAtIndex) =>
+ val arrayVar = arrayLoadExpr.arrayRef.asVar
+ 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] = {
+ val arrayVar = arrayLoadExpr.arrayRef.asVar
+
+ val index = arrayLoadExpr.index.asVar.value match {
+ case intRange: DefaultIntegerRangeValues#IntegerRange =>
+ if (intRange.lowerBound == intRange.upperBound) {
+ Some(intRange.lowerBound)
+ } else {
+ None
+ }
+ 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, immutable.Set(lcpOnFieldsEOptionP))
+ case ConstantValue(c) =>
+ InterimEdgeFunction(
+ LinearCombinationEdgeFunction(0, c, lattice.top),
+ immutable.Set(lcpOnFieldsEOptionP)
+ )
+ case VariableValue =>
+ FinalEdgeFunction(VariableValueEdgeFunction)
+ }
+
+ case _ =>
+ InterimEdgeFunction(UnknownValueEdgeFunction, immutable.Set(lcpOnFieldsEOptionP))
+ }
+ }
+
+ private def getArrayElementFromProperty(
+ arrayVar: JavaStatement.V,
+ index: Option[Int]
+ )(property: Property): LinearConstantPropagationValue = {
+ property
+ .asInstanceOf[LCPOnFieldsPropertyMetaInformation.Self]
+ .results
+ .filter {
+ case (f: AbstractArrayFact, ArrayValue(_, _)) =>
+ arrayVar.definedBy.contains(f.definedAtIndex)
+ case _ => false
+ }
+ .map(_._2)
+ .foldLeft(UnknownValue: LinearConstantPropagationValue) {
+ case (value, ArrayValue(initValue, values)) =>
+ val arrayValue = index match {
+ case Some(i) => values.getOrElse(i, initValue)
+ case None =>
+ if (values.values.forall { v => v == initValue }) {
+ initValue
+ } else {
+ VariableValue
+ }
+ }
+ lattice.meet(value, arrayValue)
+ }
+ }
+
+ 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, immutable.Set(lcpOnFieldsEOptionP))
+ case ConstantValue(c) =>
+ InterimEdgeFunction(
+ LinearCombinationEdgeFunction(0, c, lattice.top),
+ immutable.Set(lcpOnFieldsEOptionP)
+ )
+ case VariableValue =>
+ FinalEdgeFunction(VariableValueEdgeFunction)
+ }
+
+ case _ =>
+ InterimEdgeFunction(UnknownValueEdgeFunction, immutable.Set(lcpOnFieldsEOptionP))
+ }
+ }
+
+ private def getObjectFieldFromProperty(
+ objectVar: JavaStatement.V,
+ fieldName: String
+ )(property: Property): LinearConstantPropagationValue = {
+ property
+ .asInstanceOf[LCPOnFieldsPropertyMetaInformation.Self]
+ .results
+ .filter {
+ case (f: AbstractObjectFact, ObjectValue(values)) =>
+ objectVar.definedBy.contains(f.definedAtIndex) && values.contains(fieldName)
+ case _ => false
+ }
+ .map(_._2)
+ .foldLeft(UnknownValue: LinearConstantPropagationValue) {
+ case (value, ObjectValue(values)) =>
+ lattice.meet(value, values(fieldName))
+ }
+ }
+
+ 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, immutable.Set(lcpOnFieldsEOptionP))
+ case ConstantValue(c) =>
+ InterimEdgeFunction(
+ LinearCombinationEdgeFunction(0, c, lattice.top),
+ immutable.Set(lcpOnFieldsEOptionP)
+ )
+ case VariableValue =>
+ FinalEdgeFunction(VariableValueEdgeFunction)
+ }
+
+ case _ =>
+ InterimEdgeFunction(UnknownValueEdgeFunction, immutable.Set(lcpOnFieldsEOptionP))
+ }
+ }
+
+ private def getStaticFieldFromProperty(
+ objectType: ObjectType,
+ fieldName: String
+ )(property: Property): LinearConstantPropagationValue = {
+ property
+ .asInstanceOf[LCPOnFieldsPropertyMetaInformation.Self]
+ .results
+ .filter {
+ case (f: AbstractStaticFieldFact, StaticFieldValue(_)) =>
+ f.objectType == objectType && f.fieldName == fieldName
+ case _ => false
+ }
+ .map(_._2)
+ .foldLeft(UnknownValue: LinearConstantPropagationValue) {
+ case (value, StaticFieldValue(v)) =>
+ lattice.meet(value, v)
+ }
+ }
+}
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..3c5408a43b
--- /dev/null
+++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/LinearConstantPropagationAnalysisScheduler.scala
@@ -0,0 +1,31 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.tac.fpcf.analyses.ide.instances.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
+ */
+abstract 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..b875ceeabb
--- /dev/null
+++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/LinearConstantPropagationPropertyMetaInformation.scala
@@ -0,0 +1,15 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.tac.fpcf.analyses.ide.instances.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
+ */
+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..ebe75f8089
--- /dev/null
+++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/problem/LinearConstantPropagationEdgeFunctions.scala
@@ -0,0 +1,140 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.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
+
+/**
+ * Edge function to calculate the value of a variable `i` for a statement `val i = a * x + b`
+ */
+case class LinearCombinationEdgeFunction(
+ a: Int,
+ b: Int,
+ c: LinearConstantPropagationValue = LinearConstantPropagationLattice.top
+) extends EdgeFunction[LinearConstantPropagationValue] {
+ override def compute(sourceValue: LinearConstantPropagationValue): LinearConstantPropagationValue = {
+ 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(
+ secondEdgeFunction: EdgeFunction[LinearConstantPropagationValue]
+ ): EdgeFunction[LinearConstantPropagationValue] = {
+ 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(_) => secondEdgeFunction
+
+ case _ =>
+ throw new UnsupportedOperationException(s"Composing $this with $secondEdgeFunction is not implemented!")
+ }
+ }
+
+ override def meetWith(
+ otherEdgeFunction: EdgeFunction[LinearConstantPropagationValue]
+ ): EdgeFunction[LinearConstantPropagationValue] = {
+ 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(_) => this
+
+ case _ =>
+ throw new UnsupportedOperationException(s"Meeting $this with $otherEdgeFunction is not implemented!")
+ }
+ }
+
+ override def equalTo(otherEdgeFunction: EdgeFunction[LinearConstantPropagationValue]): Boolean = {
+ (otherEdgeFunction eq this) ||
+ (otherEdgeFunction match {
+ case LinearCombinationEdgeFunction(a2, b2, c2) => a == a2 && b == b2 && c == c2
+ case _ => false
+ })
+ }
+}
+
+/**
+ * Edge function for a variable that is definitely not constant
+ */
+object VariableValueEdgeFunction extends AllBottomEdgeFunction[LinearConstantPropagationValue](VariableValue) {
+ override def composeWith(
+ secondEdgeFunction: EdgeFunction[LinearConstantPropagationValue]
+ ): EdgeFunction[LinearConstantPropagationValue] = {
+ secondEdgeFunction match {
+ case LinearCombinationEdgeFunction(0, _, _) => secondEdgeFunction
+ case LinearCombinationEdgeFunction(_, _, _) => this
+ case _ => this
+ }
+ }
+
+ override def toString: String = "VariableValueEdgeFunction()"
+}
+
+/**
+ * Edge function for variables whose value is unknown
+ */
+object UnknownValueEdgeFunction extends AllTopEdgeFunction[LinearConstantPropagationValue](UnknownValue) {
+ override def composeWith(
+ secondEdgeFunction: EdgeFunction[LinearConstantPropagationValue]
+ ): EdgeFunction[LinearConstantPropagationValue] = {
+ secondEdgeFunction match {
+ case LinearCombinationEdgeFunction(0, _, _) => secondEdgeFunction
+ case LinearCombinationEdgeFunction(_, _, VariableValue) => secondEdgeFunction
+ case LinearCombinationEdgeFunction(_, _, _) => this
+
+ case VariableValueEdgeFunction => secondEdgeFunction
+ case UnknownValueEdgeFunction => secondEdgeFunction
+
+ case IdentityEdgeFunction() => this
+ case AllTopEdgeFunction(_) => secondEdgeFunction
+
+ case _ =>
+ throw new UnsupportedOperationException(s"Composing $this with $secondEdgeFunction is not implemented!")
+ }
+ }
+
+ override def equalTo(otherEdgeFunction: EdgeFunction[LinearConstantPropagationValue]): Boolean =
+ otherEdgeFunction eq this
+
+ override def toString: String = "UnknownValueEdgeFunction()"
+}
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..fa8021c923
--- /dev/null
+++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/problem/LinearConstantPropagationFact.scala
@@ -0,0 +1,23 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.problem
+
+import org.opalj.ide.problem.IDEFact
+
+/**
+ * Type for modeling facts for linear constant propagation
+ */
+trait LinearConstantPropagationFact extends IDEFact
+
+/**
+ * Fact to use as null fact
+ */
+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)
+ */
+case class VariableFact(name: String, definedAtIndex: Int) extends LinearConstantPropagationFact {
+ override def toString: String = s"VariableFact($name)"
+}
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..403dad281d
--- /dev/null
+++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/problem/LinearConstantPropagationLattice.scala
@@ -0,0 +1,25 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.problem
+
+import org.opalj.ide.problem.MeetLattice
+
+/**
+ * Lattice used for linear constant propagation
+ */
+object LinearConstantPropagationLattice extends MeetLattice[LinearConstantPropagationValue] {
+ override def top: LinearConstantPropagationValue = UnknownValue
+
+ override def bottom: LinearConstantPropagationValue = VariableValue
+
+ override def meet(
+ x: LinearConstantPropagationValue,
+ y: LinearConstantPropagationValue
+ ): LinearConstantPropagationValue = (x, y) match {
+ case (UnknownValue, y) => y
+ case (x, UnknownValue) => x
+ case (VariableValue, _) => VariableValue
+ case (_, VariableValue) => VariableValue
+ case (ConstantValue(xc), ConstantValue(yc)) if xc == yc => x
+ 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..b8f5c66284
--- /dev/null
+++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/problem/LinearConstantPropagationProblem.scala
@@ -0,0 +1,620 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.problem
+
+import scala.annotation.unused
+
+import scala.collection.immutable
+
+import org.opalj.BinaryArithmeticOperators
+import org.opalj.ai.domain.l1.DefaultIntegerRangeValues
+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.IdentityFlowFunction
+import org.opalj.ide.problem.MeetLattice
+import org.opalj.tac.ArrayLength
+import org.opalj.tac.ArrayLoad
+import org.opalj.tac.Assignment
+import org.opalj.tac.BinaryExpr
+import org.opalj.tac.Expr
+import org.opalj.tac.GetField
+import org.opalj.tac.GetStatic
+import org.opalj.tac.IntConst
+import org.opalj.tac.Var
+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
+
+/**
+ * Definition of the linear constant propagation problem
+ */
+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
+ ): collection.Set[LinearConstantPropagationFact] = {
+ callee.parameterTypes
+ .zipWithIndex
+ .filter { case (paramType, _) => paramType.isIntegerType }
+ .map { case (_, index) => VariableFact(s"param${index + 1}", -(index + 2)) }
+ .toSet
+ }
+
+ 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 */
+ immutable.Set(sourceFact, VariableFact(assignment.targetVar.name, source.pc))
+ } else {
+ immutable.Set(sourceFact)
+ }
+
+ case _ => immutable.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 intRange: DefaultIntegerRangeValues#IntegerRange =>
+ intRange.lowerBound == intRange.upperBound
+ 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) {
+ immutable.Set.empty
+ } else {
+ callSiteFact match {
+ case NullFact =>
+ /* Always propagate null facts */
+ immutable.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
+ .filter { case (param, index) =>
+ /* Only parameters that are of type integer and where the variable represented by
+ * the source fact is one possible initializer */
+ paramTypes(index).isIntegerType && param.asVar.definedBy.contains(definedAtIndex)
+ }
+ .map { case (_, index) =>
+ VariableFact(s"param${index + 1}", -(index + 2))
+ }
+ .toSet
+ }
+ }
+ }
+ }
+ }
+
+ override def getReturnFlowFunction(
+ calleeExit: JavaStatement,
+ calleeExitFact: LinearConstantPropagationFact,
+ callee: Method,
+ returnSite: JavaStatement
+ )(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) {
+ immutable.Set.empty
+ } else {
+ calleeExitFact match {
+ case NullFact =>
+ /* Always propagate null fact */
+ immutable.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)) {
+ immutable.Set(VariableFact(assignment.targetVar.name, returnSite.pc))
+ } else {
+ immutable.Set.empty
+ }
+
+ case _ => immutable.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 intRange: DefaultIntegerRangeValues#IntegerRange =>
+ if (intRange.lowerBound == intRange.upperBound) {
+ /* If boundaries are equal, the value is constant */
+ Some(intRange.lowerBound)
+ } else if (var0.definedBy.size > 1) {
+ return VariableValueEdgeFunction
+ } else {
+ None
+ }
+ 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] = identityEdgeFunction
+
+ override def getReturnEdgeFunction(
+ calleeExit: JavaStatement,
+ calleeExitFact: LinearConstantPropagationFact,
+ callee: Method,
+ returnSite: JavaStatement,
+ returnSiteFact: 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
+ immutable.Set(VariableFact(assignment.targetVar.name, returnSite.pc))
+
+ case _ => immutable.Set.empty
+ }
+ } else {
+ immutable.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
+ immutable.Set(callSiteFact, VariableFact(assignment.targetVar.name, returnSite.pc))
+
+ case _ => immutable.Set(callSiteFact)
+ }
+
+ case VariableFact(_, _) => immutable.Set(callSiteFact)
+ }
+ } else {
+ immutable.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..3c743c4537
--- /dev/null
+++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/instances/linear_constant_propagation/problem/LinearConstantPropagationValue.scala
@@ -0,0 +1,24 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.tac.fpcf.analyses.ide.instances.linear_constant_propagation.problem
+
+import org.opalj.ide.problem.IDEValue
+
+/**
+ * Type for modeling values for linear constant propagation
+ */
+trait LinearConstantPropagationValue extends IDEValue
+
+/**
+ * Value not known (yet)
+ */
+case object UnknownValue extends LinearConstantPropagationValue
+
+/**
+ * A constant value
+ */
+case class ConstantValue(c: Int) extends LinearConstantPropagationValue
+
+/**
+ * Value is variable
+ */
+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..29a03d713f
--- /dev/null
+++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/integration/JavaIDEAnalysisScheduler.scala
@@ -0,0 +1,16 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.tac.fpcf.analyses.ide.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
+ */
+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..6d36d15fbd
--- /dev/null
+++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/integration/JavaIDEAnalysisSchedulerBase.scala
@@ -0,0 +1,73 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.tac.fpcf.analyses.ide.integration
+
+import scala.collection.immutable
+
+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.CallGraphKey
+import org.opalj.tac.cg.RTACallGraphKey
+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
+ */
+abstract class JavaIDEAnalysisSchedulerBase[Fact <: IDEFact, Value <: IDEValue]
+ extends IDEAnalysisScheduler[Fact, Value, JavaStatement, Method, JavaICFG] {
+ /**
+ * Key indicating which call graph should be used
+ */
+ val callGraphKey: CallGraphKey
+
+ override def requiredProjectInformation: ProjectInformationKeys =
+ super.requiredProjectInformation ++ Seq(
+ DeclaredMethodsKey,
+ TypeIteratorKey,
+ callGraphKey
+ )
+
+ override def uses: Set[PropertyBounds] =
+ super.uses.union(immutable.Set(
+ PropertyBounds.finalP(TACAI),
+ PropertyBounds.finalP(Callers)
+ ))
+}
+
+object JavaIDEAnalysisSchedulerBase {
+ /**
+ * Trait to drop-in [[RTACallGraphKey]] as [[callGraphKey]]
+ */
+ trait RTACallGraph {
+ val callGraphKey: CallGraphKey = RTACallGraphKey
+ }
+
+ /**
+ * Trait to drop-in [[JavaForwardICFG]] for [[createICFG]]
+ */
+ trait ForwardICFG {
+ def createICFG(project: SomeProject): JavaICFG = {
+ new JavaForwardICFG(project)
+ }
+ }
+
+ /**
+ * Trait to drop-in [[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..9b6cd01791
--- /dev/null
+++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/integration/JavaIDEPropertyMetaInformation.scala
@@ -0,0 +1,13 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.tac.fpcf.analyses.ide.integration
+
+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
+ */
+trait JavaIDEPropertyMetaInformation[Fact <: IDEFact, Value <: IDEValue]
+ extends IDEPropertyMetaInformation[JavaStatement, Fact, Value]
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..16423d1641
--- /dev/null
+++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/problem/JavaIDEProblem.scala
@@ -0,0 +1,13 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.tac.fpcf.analyses.ide.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
+ */
+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..0bcb8d473b
--- /dev/null
+++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/solver/JavaBackwardICFG.scala
@@ -0,0 +1,44 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.tac.fpcf.analyses.ide.solver
+
+import scala.collection.immutable
+import scala.collection.mutable
+
+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.
+ */
+class JavaBackwardICFG(project: SomeProject) extends JavaBaseICFG(project) {
+ override def getStartStatements(callable: Method): 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): collection.Set[JavaStatement] = {
+ if (isCallStatement(javaStmt)) {
+ immutable.Set(
+ JavaStatement(javaStmt.method, javaStmt.pc, isReturnNode = true, javaStmt.stmts, javaStmt.cfg)
+ )
+ } else {
+ val predecessors = mutable.Set.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..ddb41abf6e
--- /dev/null
+++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/solver/JavaBaseICFG.scala
@@ -0,0 +1,102 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.tac.fpcf.analyses.ide.solver
+
+import scala.collection.mutable
+
+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.tac.AITACode
+import org.opalj.tac.Assignment
+import org.opalj.tac.ExprStmt
+import org.opalj.tac.LazyDetachedTACAIKey
+import org.opalj.tac.NonVirtualFunctionCall
+import org.opalj.tac.NonVirtualMethodCall
+import org.opalj.tac.StaticFunctionCall
+import org.opalj.tac.StaticMethodCall
+import org.opalj.tac.TACMethodParameter
+import org.opalj.tac.VirtualFunctionCall
+import org.opalj.tac.VirtualMethodCall
+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.
+ */
+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 = mutable.Map.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): collection.Set[Method] = {
+ val caller = declaredMethods(javaStmt.method)
+ 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
+
+ override def stringifyStatement(javaStmt: JavaStatement, indent: String = "", short: Boolean = false): String = {
+ val stringifiedStatement = javaStmt.toString
+ if (short) {
+ stringifiedStatement.substring(0, stringifiedStatement.indexOf("{"))
+ } else {
+ stringifiedStatement
+ }
+ }
+}
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..ac722d0bd1
--- /dev/null
+++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/solver/JavaForwardICFG.scala
@@ -0,0 +1,47 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.tac.fpcf.analyses.ide.solver
+
+import scala.collection.immutable
+import scala.collection.mutable
+
+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.
+ */
+class JavaForwardICFG(project: SomeProject) extends JavaBaseICFG(project) {
+ override def getStartStatements(callable: Method): collection.Set[JavaStatement] = {
+ val tac = tacProvider(callable)
+ immutable.Set(
+ JavaStatement(callable, 0, isReturnNode = false, tac.stmts, tac.cfg)
+ )
+ }
+
+ override def getNextStatements(javaStmt: JavaStatement): collection.Set[JavaStatement] = {
+ if (isCallStatement(javaStmt)) {
+ immutable.Set(
+ JavaStatement(javaStmt.method, javaStmt.pc, isReturnNode = true, javaStmt.stmts, javaStmt.cfg)
+ )
+ } else {
+ val successors = mutable.Set.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..ea6384d6b0
--- /dev/null
+++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/solver/JavaICFG.scala
@@ -0,0 +1,10 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.tac.fpcf.analyses.ide.solver
+
+import org.opalj.br.Method
+import org.opalj.ide.solver.ICFG
+
+/**
+ * Interprocedural control flow graph for Java programs
+ */
+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..e8c90cbabf
--- /dev/null
+++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ide/solver/JavaStatement.scala
@@ -0,0 +1,62 @@
+/* BSD 2-Clause License - see OPAL/LICENSE for details. */
+package org.opalj.tac.fpcf.analyses.ide.solver
+
+import org.opalj.br.Method
+import org.opalj.br.cfg.BasicBlock
+import org.opalj.br.cfg.CFG
+import org.opalj.tac.Assignment
+import org.opalj.tac.Call
+import org.opalj.tac.DUVar
+import org.opalj.tac.ExprStmt
+import org.opalj.tac.Stmt
+import org.opalj.tac.TACStmts
+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
+ */
+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 4e5e85a927..4b1d691433 100644
--- a/build.sbt
+++ b/build.sbt
@@ -310,6 +310,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: _*)
@@ -323,6 +336,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)