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 @@ -CommoncommonStatic Analysis InfrastructuresiBytecode InfrastructurebiBytecode RepresentationbrBytecode DisassemblerdaIFDSifdsAbstract Interpretation FrameworkaiBytecode CreatorbcThree Address CodetacDependency ExtractiondeBytecode AssemblerbaLLVMllAPKapkArchitecture ValidationavFrameworkDemosBugPickerbpHermeshermes \ No newline at end of file +CommoncommonStatic Analysis InfrastructuresiBytecode InfrastructurebiBytecode RepresentationbrBytecode DisassemblerdaIDEideIFDSifdsAbstract Interpretation FrameworkaiBytecode CreatorbcThree Address CodetacDependency ExtractiondeBytecode AssemblerbaLLVMllAPKapkArchitecture ValidationavFrameworkDemosBugPickerbpHermeshermes \ No newline at end of file diff --git a/OPAL/ide/Readme.md b/OPAL/ide/Readme.md new file mode 100644 index 0000000000..3a3cec1ce1 --- /dev/null +++ b/OPAL/ide/Readme.md @@ -0,0 +1,2 @@ +# Overview +The ***IDE*** (ide) module provides a generic implementation for IDE analyses. diff --git a/OPAL/ide/build.sbt b/OPAL/ide/build.sbt new file mode 100644 index 0000000000..b511e98651 --- /dev/null +++ b/OPAL/ide/build.sbt @@ -0,0 +1 @@ +// build settings reside in the opal root build.sbt file diff --git a/OPAL/ide/src/main/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)