diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/alias/ArrayAlias.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/alias/ArrayAlias.java index b08e86ddb5..ecd1d9d358 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/alias/ArrayAlias.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/alias/ArrayAlias.java @@ -3,14 +3,14 @@ import org.opalj.fpcf.properties.alias.MayAlias; import org.opalj.fpcf.properties.alias.NoAlias; - +import org.opalj.tac.fpcf.analyses.alias.AllocationSiteBasedAliasAnalysis; public class ArrayAlias { @NoAlias(reason = "no alias with array and uVar that is not stored to array", lineNumber = 20, secondLineNumber = 22, - analyses = {/*AllocationSiteBasedAliasAnalysis.class*/}) + analyses = AllocationSiteBasedAliasAnalysis.class) public static void notStoredToArray() { Object[] arr = new Object[10]; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/alias/FieldAlias.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/alias/FieldAlias.java index 0ed92c911f..9bd87c6520 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/alias/FieldAlias.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/alias/FieldAlias.java @@ -3,13 +3,13 @@ import org.opalj.fpcf.properties.alias.MayAlias; import org.opalj.fpcf.properties.alias.NoAlias; - +import org.opalj.tac.fpcf.analyses.alias.pointsto.AllocationSitePointsToBasedAliasAnalysis; public class FieldAlias { @NoAlias(reason = "no alias for fields of different objects", lineNumber = 20, fieldName = "field", fieldClass = FieldClass.class, - secondLineNumber = 21, secondFieldName = "field2", secondFieldClass = FieldClass.class) + secondLineNumber = 21, secondFieldName = "field2", secondFieldClass = FieldClass.class, analyses = {AllocationSitePointsToBasedAliasAnalysis.class}) public void differentObjectsSameFields() { FieldClass fc = new FieldClass(); FieldClass fc2 = new FieldClass(); @@ -23,7 +23,7 @@ public void differentObjectsSameFields() { @NoAlias(reason = "no alias for different fields of the same object", lineNumber = 32, fieldName = "field", fieldClass = FieldClass.class, - secondLineNumber = 33, secondFieldName = "field2", secondFieldClass = FieldClass.class) + secondLineNumber = 33, secondFieldName = "field2", secondFieldClass = FieldClass.class, analyses = {AllocationSitePointsToBasedAliasAnalysis.class}) public void sameObjectDifferentField() { FieldClass fc = new FieldClass(); fc.field = new Object(); @@ -35,7 +35,7 @@ public void sameObjectDifferentField() { @NoAlias(reason = "no alias for different fields of different objects", lineNumber = 46, fieldName = "field", fieldClass = FieldClass.class, - secondLineNumber = 47, secondFieldName = "field2", secondFieldClass = FieldClass.class) + secondLineNumber = 47, secondFieldName = "field2", secondFieldClass = FieldClass.class, analyses = {AllocationSitePointsToBasedAliasAnalysis.class}) public void differentObjectsDifferentFields() { FieldClass fc = new FieldClass(); FieldClass fc2 = new FieldClass(); @@ -76,7 +76,7 @@ public static void paramMayBeField( @MayAlias(reason = "may alias with parameter and field", lineNumber = 110, methodName = "main", fieldName = "field", fieldClass = FieldClass.class) @NoAlias(reason = "no alias with parameter and field", - lineNumber = 111, methodName = "main", fieldName = "field2", fieldClass = FieldClass.class) + lineNumber = 111, methodName = "main", fieldName = "field2", fieldClass = FieldClass.class, analyses = {AllocationSitePointsToBasedAliasAnalysis.class}) Object o) { } @@ -85,7 +85,7 @@ public static void paramMayBeField( secondLineNumber = 111, secondMethodName = "main", secondFieldName = "field", secondFieldClass = FieldClass.class) @NoAlias(reason = "no alias of field via parameter", lineNumber = 110, methodName = "main", fieldName = "field", fieldClass = FieldClass.class, - secondLineNumber = 111, secondMethodName = "main", secondFieldName = "field2", secondFieldClass = FieldClass.class) + secondLineNumber = 111, secondMethodName = "main", secondFieldName = "field2", secondFieldClass = FieldClass.class, analyses = {AllocationSitePointsToBasedAliasAnalysis.class}) public static void fieldAliasViaParameter(FieldClass fc) { fc.field.hashCode(); fc.field2.hashCode(); @@ -93,7 +93,7 @@ public static void fieldAliasViaParameter(FieldClass fc) { @NoAlias(reason = "no alias of field via parameter", lineNumber = 110,methodName = "main", fieldName = "field", fieldClass = FieldClass.class, - secondLineNumber = 111, secondMethodName = "main", secondFieldName = "field2", secondFieldClass = FieldClass.class) + secondLineNumber = 111, secondMethodName = "main", secondFieldName = "field2", secondFieldClass = FieldClass.class, analyses = {AllocationSitePointsToBasedAliasAnalysis.class}) public static void noFieldAliasViaParameter(FieldClass fc) { fc.field.hashCode(); } diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/alias/NullAlias.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/alias/NullAlias.java index b96fb4cb84..ae6f39a411 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/alias/NullAlias.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/alias/NullAlias.java @@ -3,8 +3,8 @@ import org.opalj.fpcf.properties.alias.MayAlias; import org.opalj.fpcf.properties.alias.NoAlias; - - +import org.opalj.tac.fpcf.analyses.alias.pointsto.AllocationSitePointsToBasedAliasAnalysis; +import org.opalj.tac.fpcf.analyses.alias.pointsto.TypePointsToBasedAliasAnalysis; public class NullAlias { @@ -15,7 +15,7 @@ public static void main(String[] args) { } public static void paramIsAlwaysNull( - @NoAlias(reason = "parameter is always null", lineNumber = 20) + @NoAlias(reason = "parameter is always null", lineNumber = 20, analyses = {AllocationSitePointsToBasedAliasAnalysis.class, TypePointsToBasedAliasAnalysis.class}) Object o) { o.hashCode(); } @@ -26,7 +26,7 @@ public static void paramMayBeNull( o.hashCode(); } - @NoAlias(reason = "uVar is always null", lineNumber = 32, secondLineNumber = 32) + @NoAlias(reason = "uVar is always null", lineNumber = 32, secondLineNumber = 32, analyses = {AllocationSitePointsToBasedAliasAnalysis.class, TypePointsToBasedAliasAnalysis.class}) public static void UVarIsAlwaysNull() { Object o = null; o.hashCode(); diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/alias/ParameterAlias.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/alias/ParameterAlias.java index 5aab3c1b31..931262b7fa 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/alias/ParameterAlias.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/alias/ParameterAlias.java @@ -3,7 +3,7 @@ import org.opalj.fpcf.properties.alias.MayAlias; import org.opalj.fpcf.properties.alias.NoAlias; - +import org.opalj.tac.fpcf.analyses.alias.AllocationSiteBasedAliasAnalysis; public class ParameterAlias { @@ -35,13 +35,13 @@ public static void main(String[] args) { pa2.noAliasThisParamTwoMethods2(); } - public static void noAliasWithLocal(@NoAlias(reason = "noAlias with uVar", lineNumber = 40) Object o1) { + public static void noAliasWithLocal(@NoAlias(reason = "noAlias with uVar", lineNumber = 40, analyses = AllocationSiteBasedAliasAnalysis.class) Object o1) { Object o2 = new Object(); o2.hashCode(); } - public static void noAliasWithParam(@NoAlias(reason = "noAlias with other parameter", id = 0) Object o1, - @NoAlias(reason = "noAlias with other parameter", id = 0) Object o2) {} + public static void noAliasWithParam(@NoAlias(reason = "noAlias with other parameter", id = 0, analyses = AllocationSiteBasedAliasAnalysis.class) Object o1, + @NoAlias(reason = "noAlias with other parameter", id = 0, analyses = AllocationSiteBasedAliasAnalysis.class) Object o2) {} public static void mayAliasWithLocal(@MayAlias(reason = "mayAlias with uVar", lineNumber = 53) Object o1) { Object o2 = new Object(); @@ -64,7 +64,8 @@ public static void mayAliasWithParam2(@MayAlias(reason = "mayAlias with other pa public void mayAliasThisParam() {} @NoAlias(reason = "no alias with this parameter and invoked uVar", thisParameter = true, - lineNumber = 28, methodName = "main") + lineNumber = 28, methodName = "main", + analyses = AllocationSiteBasedAliasAnalysis.class) public void noAliasThisParam() {} @MayAlias(reason = "may alias with this parameter of two methods", thisParameter = true, id = 3) @@ -73,10 +74,12 @@ public void mayAliasThisParamTwoMethods1() {} @MayAlias(reason = "may alias with this parameter of two methods", thisParameter = true, id = 3) public void mayAliasThisParamTwoMethods2() {} - @NoAlias(reason = "no alias with this parameter of two methods", thisParameter = true, id = 4) + @NoAlias(reason = "no alias with this parameter of two methods", thisParameter = true, id = 4, + analyses = AllocationSiteBasedAliasAnalysis.class) public void noAliasThisParamTwoMethods1() {} - @NoAlias(reason = "no alias with this parameter of two methods", thisParameter = true, id = 4) + @NoAlias(reason = "no alias with this parameter of two methods", thisParameter = true, id = 4, + analyses = AllocationSiteBasedAliasAnalysis.class) public void noAliasThisParamTwoMethods2() {} } diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/alias/ReturnValueAlias.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/alias/ReturnValueAlias.java index 1465e7534b..802183cdde 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/alias/ReturnValueAlias.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/alias/ReturnValueAlias.java @@ -3,12 +3,12 @@ import org.opalj.fpcf.properties.alias.MayAlias; import org.opalj.fpcf.properties.alias.NoAlias; - - +import org.opalj.tac.fpcf.analyses.alias.pointsto.AllocationSitePointsToBasedAliasAnalysis; +import org.opalj.tac.fpcf.analyses.alias.pointsto.TypePointsToBasedAliasAnalysis; public class ReturnValueAlias { - @NoAlias(reason = "no Alias with local variable", lineNumber = 15) + @NoAlias(reason = "no Alias with local variable", lineNumber = 15, analyses = {AllocationSitePointsToBasedAliasAnalysis.class}) public static Object noAliasWithLocal() { Object o1 = new Object(); @@ -40,9 +40,9 @@ public static Object mayAliasWithLocal2() { return o1; } - @NoAlias(reason = "noAlias with parameter", id = 0) + @NoAlias(reason = "noAlias with parameter", id = 0, analyses = {AllocationSitePointsToBasedAliasAnalysis.class, TypePointsToBasedAliasAnalysis.class}) public static Object noAliasWithParam( - @NoAlias(reason = "noAlias with parameter", id = 0) + @NoAlias(reason = "noAlias with parameter", id = 0, analyses = {AllocationSitePointsToBasedAliasAnalysis.class, TypePointsToBasedAliasAnalysis.class}) Object a) { Object o1 = new Object(); return o1; diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/alias/StaticFieldAlias.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/alias/StaticFieldAlias.java index 5e57f60899..2e09d51625 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/alias/StaticFieldAlias.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/alias/StaticFieldAlias.java @@ -3,14 +3,14 @@ import org.opalj.fpcf.properties.alias.MayAlias; import org.opalj.fpcf.properties.alias.NoAlias; - +import org.opalj.tac.fpcf.analyses.alias.pointsto.AllocationSitePointsToBasedAliasAnalysis; public class StaticFieldAlias { @MayAlias(reason = "may alias with field and assigned uVar", lineNumber = 41, methodName = "reassignField") - @NoAlias(reason = "no alias with field and parameter", id = 1) - @NoAlias(reason = "no alias with field and return value", id = 3) - @NoAlias(reason = "no alias with field and unrelated uVar", lineNumber = 54, methodName = "noAlias") + @NoAlias(reason = "no alias with field and parameter", id = 1, analyses = {AllocationSitePointsToBasedAliasAnalysis.class}) + @NoAlias(reason = "no alias with field and return value", id = 3, analyses = {AllocationSitePointsToBasedAliasAnalysis.class}) + @NoAlias(reason = "no alias with field and unrelated uVar", lineNumber = 54, methodName = "noAlias", analyses = {AllocationSitePointsToBasedAliasAnalysis.class}) @MayAlias(reason = "may alias with field and return value", id = 2) @MayAlias(reason = "may alias with field and returned uVar", lineNumber = 65, methodName = "returnMayAliasField") @MayAlias(reason = "may alias with field and return value via parameter", id = 5) @@ -41,9 +41,9 @@ public static void reassignField() { mayAliasField = o; } - @NoAlias(reason = "no alias with field and return value", id = 3) + @NoAlias(reason = "no alias with field and return value", id = 3, analyses = {AllocationSitePointsToBasedAliasAnalysis.class}) public static Object noAlias( - @NoAlias(reason = "no alias with field and parameter", id = 1) + @NoAlias(reason = "no alias with field and parameter", id = 1, analyses = {AllocationSitePointsToBasedAliasAnalysis.class}) Object a) { Object o = new Object(); diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/alias/UVarAlias.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/alias/UVarAlias.java index 802f462019..9d0a2e8926 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/alias/UVarAlias.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/alias/UVarAlias.java @@ -4,13 +4,14 @@ import org.opalj.fpcf.properties.alias.MayAlias; import org.opalj.fpcf.properties.alias.MustAlias; import org.opalj.fpcf.properties.alias.NoAlias; - +import org.opalj.tac.fpcf.analyses.alias.IntraProceduralAliasAnalysis; +import org.opalj.tac.fpcf.analyses.alias.pointsto.AllocationSitePointsToBasedAliasAnalysis; public class UVarAlias { @MustAlias(reason = "same local variable with single defSite without loop used", lineNumber = 18, - secondLineNumber = 18) + secondLineNumber = 18, analyses = {AllocationSitePointsToBasedAliasAnalysis.class, IntraProceduralAliasAnalysis.class}) public static void mustAliasLocals() { Object o1 = new Object(); @@ -29,7 +30,7 @@ public static void mayAliasLoop() { @MustAlias(reason = "same local variable with single defSite with loop in front of defSite", lineNumber = 41, - secondLineNumber = 41) + secondLineNumber = 41, analyses = {AllocationSitePointsToBasedAliasAnalysis.class, IntraProceduralAliasAnalysis.class}) public static void mustAliasLoopInFront() { for (int i = 0; i < 10; i++) { Object o1 = new Object(); @@ -42,7 +43,7 @@ public static void mustAliasLoopInFront() { @MustAlias(reason = "same local variable with single defSite with loop behind defSite", lineNumber = 50, - secondLineNumber = 50) + secondLineNumber = 50, analyses = {AllocationSitePointsToBasedAliasAnalysis.class, IntraProceduralAliasAnalysis.class}) public static void mustAliasLoopBehind() { Object o1 = new Object(); @@ -55,7 +56,7 @@ public static void mustAliasLoopBehind() { @MustAlias(reason = "same local variable with single defSite with loop behind defSite", lineNumber = 64, - secondLineNumber = 64) + secondLineNumber = 64, analyses = {AllocationSitePointsToBasedAliasAnalysis.class, IntraProceduralAliasAnalysis.class}) public static void mustAliasLoopBehind2() { Object o1 = new Object(); @@ -75,7 +76,7 @@ public static void mayAliasRecursion(Object a) { @MustAlias(reason = "same local variable with single defSite with irrelevant recursion", lineNumber = 82, - secondLineNumber = 83, secondParameterIndex = 0) + secondLineNumber = 83, secondParameterIndex = 0, analyses = {AllocationSitePointsToBasedAliasAnalysis.class, IntraProceduralAliasAnalysis.class}) public static void mustAliasRecursion(Object a) { a = new Object(); a.hashCode(); @@ -108,7 +109,7 @@ public static Object createNewObject() { @NoAlias(reason = "no alias with local variables", lineNumber = 116, - secondLineNumber = 117) + secondLineNumber = 117, analyses = {AllocationSitePointsToBasedAliasAnalysis.class, IntraProceduralAliasAnalysis.class}) public static void noAliasLocals() { Object o1 = new Object(); Object o2 = new Object(); @@ -130,4 +131,42 @@ public static void mayAliasLocals() { o1.hashCode(); o2.hashCode(); } + + @MayAlias(reason = "same local variable with single defSite inside inner nested loop", + lineNumber = 142, + secondLineNumber = 142) + public static void mayAliasInnerNestedLoop() { + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + Object o1 = new Object(); + o1.hashCode(); + } + } + } + + @MayAlias(reason = "same local variable with single defSite inside outer nested loop", + lineNumber = 156, + secondLineNumber = 156) + public static void mayAliasOuterNestedLoop1() { + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + Object o2 = new Object(); + } + Object o1 = new Object(); + o1.hashCode(); + } + } + + @MayAlias(reason = "same local variable with single defSite inside outer nested loop", + lineNumber = 166, + secondLineNumber = 166) + public static void mayAliasOuterNestedLoop2() { + for (int i = 0; i < 10; i++) { + Object o1 = new Object(); + o1.hashCode(); + for (int j = 0; j < 10; j++) { + Object o2 = new Object(); + } + } + } } diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/alias/MayAlias.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/alias/MayAlias.java index 85d3ce9739..244a8ee82f 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/alias/MayAlias.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/alias/MayAlias.java @@ -3,6 +3,9 @@ import org.opalj.br.fpcf.FPCFAnalysis; import org.opalj.fpcf.properties.PropertyValidator; +import org.opalj.tac.fpcf.analyses.alias.IntraProceduralAliasAnalysis; +import org.opalj.tac.fpcf.analyses.alias.pointsto.AllocationSitePointsToBasedAliasAnalysis; +import org.opalj.tac.fpcf.analyses.alias.pointsto.TypePointsToBasedAliasAnalysis; import java.lang.annotation.Documented; import java.lang.annotation.Repeatable; @@ -109,6 +112,9 @@ /** * All analyses that should be able to correctly detect this relation. */ - Class[] analyses() default {}; - + Class[] analyses() default { + AllocationSitePointsToBasedAliasAnalysis.class, + TypePointsToBasedAliasAnalysis.class, + IntraProceduralAliasAnalysis.class + }; } diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/alias/MustAlias.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/alias/MustAlias.java index 1957dabd81..a73d3f3a2f 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/alias/MustAlias.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/alias/MustAlias.java @@ -3,6 +3,9 @@ import org.opalj.br.fpcf.FPCFAnalysis; import org.opalj.fpcf.properties.PropertyValidator; +import org.opalj.tac.fpcf.analyses.alias.IntraProceduralAliasAnalysis; +import org.opalj.tac.fpcf.analyses.alias.pointsto.AllocationSitePointsToBasedAliasAnalysis; +import org.opalj.tac.fpcf.analyses.alias.pointsto.TypePointsToBasedAliasAnalysis; import java.lang.annotation.Documented; import java.lang.annotation.Repeatable; @@ -108,5 +111,9 @@ /** * All analyses that should be able to correctly detect this relation. */ - Class[] analyses() default {}; + Class[] analyses() default { + AllocationSitePointsToBasedAliasAnalysis.class, + TypePointsToBasedAliasAnalysis.class, + IntraProceduralAliasAnalysis.class + }; } diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/alias/NoAlias.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/alias/NoAlias.java index d165a79fff..8a3a617269 100644 --- a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/alias/NoAlias.java +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/alias/NoAlias.java @@ -3,6 +3,9 @@ import org.opalj.br.fpcf.FPCFAnalysis; import org.opalj.fpcf.properties.PropertyValidator; +import org.opalj.tac.fpcf.analyses.alias.IntraProceduralAliasAnalysis; +import org.opalj.tac.fpcf.analyses.alias.pointsto.AllocationSitePointsToBasedAliasAnalysis; +import org.opalj.tac.fpcf.analyses.alias.pointsto.TypePointsToBasedAliasAnalysis; import java.lang.annotation.Documented; import java.lang.annotation.Repeatable; @@ -108,5 +111,9 @@ /** * All analyses that should be able to correctly detect this relation. */ - Class[] analyses() default {}; + Class[] analyses() default { + AllocationSitePointsToBasedAliasAnalysis.class, + TypePointsToBasedAliasAnalysis.class, + IntraProceduralAliasAnalysis.class + }; } diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/AliasTests.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/AliasTests.scala new file mode 100644 index 0000000000..cdec65bdc9 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/AliasTests.scala @@ -0,0 +1,464 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package fpcf + +import java.net.URL + +import org.opalj.ai.domain.l1 +import org.opalj.ai.fpcf.properties.AIDomainFactoryKey +import org.opalj.br.AnnotationLike +import org.opalj.br.ClassFile +import org.opalj.br.ClassValue +import org.opalj.br.Code +import org.opalj.br.ElementValue +import org.opalj.br.ElementValuePair +import org.opalj.br.Field +import org.opalj.br.Method +import org.opalj.br.StringValue +import org.opalj.br.analyses.DeclaredMethods +import org.opalj.br.analyses.DeclaredMethodsKey +import org.opalj.br.analyses.Project +import org.opalj.br.analyses.SomeProject +import org.opalj.br.analyses.VirtualFormalParameter +import org.opalj.br.fpcf.BasicFPCFLazyAnalysisScheduler +import org.opalj.br.fpcf.properties.Context +import org.opalj.br.fpcf.properties.NoContext +import org.opalj.br.fpcf.properties.SimpleContexts +import org.opalj.br.fpcf.properties.SimpleContextsKey +import org.opalj.br.fpcf.properties.alias.Alias +import org.opalj.br.fpcf.properties.alias.AliasEntity +import org.opalj.br.fpcf.properties.alias.AliasField +import org.opalj.br.fpcf.properties.alias.AliasFormalParameter +import org.opalj.br.fpcf.properties.alias.AliasSourceElement +import org.opalj.br.fpcf.properties.alias.AliasUVar +import org.opalj.br.fpcf.properties.alias.FieldReference +import org.opalj.tac.Assignment +import org.opalj.tac.Call +import org.opalj.tac.DUVar +import org.opalj.tac.Expr +import org.opalj.tac.ExprStmt +import org.opalj.tac.GetField +import org.opalj.tac.PutField +import org.opalj.tac.PutStatic +import org.opalj.tac.ReturnValue +import org.opalj.tac.Stmt +import org.opalj.tac.UVar +import org.opalj.tac.cg.AllocationSiteBasedPointsToCallGraphKey +import org.opalj.tac.cg.CallGraphKey +import org.opalj.tac.cg.TypeBasedPointsToCallGraphKey +import org.opalj.tac.fpcf.analyses.alias.LazyIntraProceduralAliasAnalysisScheduler +import org.opalj.tac.fpcf.analyses.alias.pointsto.LazyAllocationSitePointsToBasedAliasAnalysisScheduler +import org.opalj.tac.fpcf.analyses.alias.pointsto.LazyTypePointsToBasedAliasAnalysisScheduler +import org.opalj.tac.fpcf.properties.TACAI +import org.opalj.value.ValueInformation + +class AllocationSiteBasedAliasTests extends AliasTests(AllocationSiteBasedPointsToCallGraphKey) { + + describe("run allocation site, points-to based alias analyses") { + runAliasTests(LazyAllocationSitePointsToBasedAliasAnalysisScheduler) + } + +} + +class TypeBasedAliasTests extends AliasTests(TypeBasedPointsToCallGraphKey) { + + describe("run type based alias analyses") { + runAliasTests(LazyTypePointsToBasedAliasAnalysisScheduler) + } + +} + +class IntraProceduralAliasTests extends AliasTests(AllocationSiteBasedPointsToCallGraphKey) { + + describe("run intraProcedural alias analysis") { + runAliasTests(LazyIntraProceduralAliasAnalysisScheduler) + } + +} + +/** + * Tests if the alias properties defined in the classes of the package org.opalj.fpcf.fixtures.alias (and it's subpackage) + * are computed correctly + */ +class AliasTests(final val callGraphKey: CallGraphKey) extends PropertiesTest { + + override def fixtureProjectPackage: List[String] = { + List("org/opalj/fpcf/fixtures/alias") + } + + override def init(p: Project[URL]): Unit = { + + p.updateProjectInformationKeyInitializationData(AIDomainFactoryKey) { _ => + Set[Class[_ <: AnyRef]](classOf[l1.DefaultDomainWithCFGAndDefUse[URL]]) + } + + p.get(callGraphKey) + } + + protected[this] def runAliasTests(scheduler: BasicFPCFLazyAnalysisScheduler): Unit = { + implicit val as: TestContext = executeAnalyses(Set(scheduler)) + + val fields = fieldsWithTypeAnnotations(as.project) + .flatMap { case (f, fun, a) => getAliasAnnotations(a).map((f, fun, _)) } + + val allocations = allocationSitesWithAnnotations(as.project) + .flatMap { case (ds, fun, a) => getAliasAnnotations(a).map((ds, fun, _)) } + .map { case (ds, fun, a) => ((ds.pc, ds.method), fun, a) } + + val formalParameters = explicitFormalParametersWithAnnotations(as.project) + .flatMap { case (ds, fun, a) => getAliasAnnotations(a).map((ds, fun, _)) } + + val methods = methodsWithAnnotations(as.project) + .flatMap { case (m, fun, a) => getAliasAnnotations(a).map((m, fun, _)) } + + val annotationToMethod = methods.map { case (m, _, a) => (a, m) }.toMap ++ + formalParameters.map { case (ds, _, a) => (a, ds.method.definedMethod) } + val annotationToClass = fields.map { case (f, _, a) => (a, f.classFile) }.toMap ++ + allocations.map { case ((_, method), _, a) => (a, method.classFile) } ++ + formalParameters.map { case (ds, _, a) => (a, ds.method.definedMethod.classFile) } ++ + methods.map { case (m, _, a) => (a, m.classFile) } + + implicit val simpleContexts: SimpleContexts = as.project.get(SimpleContextsKey) + implicit val declaredMethods: DeclaredMethods = as.project.get(DeclaredMethodsKey) + + // The annotations only contain one of the two sourceElements of an alias property. + // Therefore, we first have to combine elements with the same id and store them in this ArrayBuffer. + + val IDToEntity: Map[String, Iterable[AliasSourceElement]] = + (fields ++ allocations ++ formalParameters ++ methods) + .filterNot(e => isLineAlias(e._3)) + .map { case (e, _, a) => + getID(a, annotationToClass(a)) -> { + if (isThisParameterAlias(a)) + AliasFormalParameter(VirtualFormalParameter(declaredMethods(e.asInstanceOf[Method]), -1)) + else AliasSourceElement(e)(as.project) + } + } + .groupMap(_._1)(_._2) + + val properties = (fields ++ allocations ++ formalParameters ++ methods) + .map { + case (e, fun, a) => + val method = annotationToMethod.get(a) + val clazz = annotationToClass(a) + + val ase1 = resolveFirstElement(e, a, method, clazz, IDToEntity) + val ase2 = resolveSecondElement(ase1, a, method, clazz, IDToEntity) + + val entity = AliasEntity( + createContext(ase1, second = false), + createContext(ase2, second = true), + ase1, + ase2 + ) + + (entity, createUniqueIdentifierFunction(a, clazz, fun), Seq(a)) + } + + // force the computation of the alias property because we are using a lazy scheduler to avoid unnecessary computations + // (a eager scheduler would cause a quadratic blowup of the number of possible alias pairs) + properties.foreach { + case (e, _, _) => as.propertyStore.force(e, Alias.key) + } + + as.propertyStore.shutdown() + + validateProperties(as, properties, Set("AliasProperty")) + } + + /** + * Resolves the first element of an alias relation. + * + * If the annotation is a alias line annotation and describes the relation between two lines or a line and null, + * the first element is resolved using the first line number. + * + * Otherwise the first element is the annotated entity. + * + * @param e The annotated entity. + * @param a The annotation that describes the alias relation. + * @param IDToEntity Map from id to alias source elements. + * @return The first element of the alias relation. + */ + private[this] def resolveFirstElement( + e: AnyRef, + a: AnnotationLike, + annotatedMethod: Option[Method], + clazz: ClassFile, + IDToEntity: Map[String, Iterable[AliasSourceElement]] + )(implicit + as: TestContext, + simpleContexts: SimpleContexts, + declaredMethods: DeclaredMethods + ): AliasSourceElement = { + if (isLineAlias(a) && hasTwoLines(a)) + resolveLine(a, annotatedMethod, clazz, useSecond = false) + else if (isThisParameterAlias(a)) + AliasFormalParameter(VirtualFormalParameter(declaredMethods(e.asInstanceOf[Method]), -1)) + else + AliasSourceElement(e)(as.project) + } + + /** + * Resolves the second element of an alias relation. + * + * If the given annotation describes an alias relation with a line, the second element is resolved using the line number. + * If the annotation contains two line numbers, the second element is resolved using the second line number. + * + * Otherwise, the second element is resolved using the id of the given annotation. + * + * @param firstElement The first, already resolved element of the alias relation. + * @param a The annotation that describes the alias relation. + * @param IDToEntity Map from id to alias source elements. + * @return The second element of the alias relation. + */ + private[this] def resolveSecondElement( + firstElement: AliasSourceElement, + a: AnnotationLike, + annotatedMethod: Option[Method], + clazz: ClassFile, + IDToEntity: Map[String, Iterable[AliasSourceElement]] + )(implicit + as: TestContext, + simpleContexts: SimpleContexts, + declaredMethods: DeclaredMethods + ): AliasSourceElement = { + if (isLineAlias(a)) + resolveLine(a, annotatedMethod, clazz, useSecond = hasTwoLines(a)) + else { + val matchingEntities = IDToEntity(getID(a, clazz)).toSeq.filter(!_.equals(firstElement)) + if (matchingEntities.isEmpty) + throw new IllegalArgumentException("No other entity with id " + getID(a, clazz) + " found") + if (matchingEntities.size > 1) + throw new IllegalArgumentException("Multiple other entities with id " + getID(a, clazz) + " found") + matchingEntities.head + } + } + + /** + * Resolves the alias source element that is described by the given line annotation. + * + * @param a The line annotation. + * @param useSecond If true, the second line number is used to resolve the line. + * @return The alias source element that is described by the given line annotation. + */ + private[this] def resolveLine( + a: AnnotationLike, + annotatedMethod: Option[Method], + clazz: ClassFile, + useSecond: Boolean + )(implicit + as: TestContext, + simpleContexts: SimpleContexts, + declaredMethods: DeclaredMethods + ): AliasSourceElement = { + val method = if (hasMethodName(a, useSecond)) + findMethod(clazz, getMethodName(a, useSecond)) + else annotatedMethod.get + val tac: EOptionP[Method, TACAI] = as.propertyStore(method, TACAI.key) + val body: Code = method.body.get + val lineNumber = getIntValue(a, if (useSecond) "secondLineNumber" else "lineNumber") + val isFieldReference = getFieldName(a, useSecond) != null + + val pc = body.instructions.zipWithIndex + .filter(_._1 != null) + .filter(inst => body.lineNumber(inst._2).isDefined && body.lineNumber(inst._2).get == lineNumber) + .filterNot(inst => + inst._1.isLoadConstantInstruction || inst._1.isLoadLocalVariableInstruction || inst._1.isStackManagementInstruction + ) + .map(_._2) + .headOption + .orElse(throw new IllegalArgumentException( + "No instruction found for line number " + lineNumber + " in method " + method.toJava + )) + .get + + val stmts = tac.ub.tac.get.stmts + val stmt: Stmt[DUVar[ValueInformation]] = tac.ub.tac.get.stmts(tac.ub.tac.get.pcToIndex(pc)) + + def handleCall(c: Call[_]): AliasSourceElement = { + val parameterIndex = getIntValue(a, if (useSecond) "secondParameterIndex" else "parameterIndex") + val param: Expr[_] = if (parameterIndex == -1) c.receiverOption.get else c.params(parameterIndex) + + param match { + case uVar: UVar[ValueInformation] => AliasUVar(uVar.toPersistentForm(stmts), method, as.project) + case _ => throw new IllegalArgumentException("No UVar found") + } + } + + def handleExpr(expr: Expr[DUVar[ValueInformation]]): AliasSourceElement = { + expr match { + case c: Call[_] => handleCall(c) + case uVar: UVar[ValueInformation] => AliasUVar(uVar.toPersistentForm(stmts), method, as.project) + case GetField(_, _, _, _, UVar(_, objRefDefSites)) => AliasField(FieldReference( + findField(a, useSecond), + simpleContexts(declaredMethods(method)), + objRefDefSites + )) + case _ => throw new IllegalArgumentException("No UVar found") + } + } + + stmt match { + case c: Call[_] => handleCall(c) + case expr: ExprStmt[DUVar[ValueInformation]] => handleExpr(expr.expr) + case putStatic: PutStatic[DUVar[ValueInformation]] => handleExpr(putStatic.value) + case PutField(_, _, _, _, UVar(_, objRefDefSites), value: UVar[ValueInformation]) => + if (isFieldReference) AliasField(FieldReference( + findField(a, useSecond), + simpleContexts(declaredMethods(method)), + objRefDefSites + )) + else AliasUVar(value.toPersistentForm(stmts), method, as.project) + case returnValue: ReturnValue[DUVar[ValueInformation]] => handleExpr(returnValue.expr) + case Assignment(_, _, expr) => handleExpr(expr) + case _ => throw new IllegalArgumentException( + "No UVar found at line number " + lineNumber + " in method " + method.toJava + ) + } + } + + /** + * Searches for the method with the given Name in the fixture project. If no method is found, an exception is thrown. + * + * @param methodName The name of the method. + * @param as The test context. + * @return The method with the given name. + */ + private[this] def findMethod(clazz: ClassFile, methodName: String)(implicit as: TestContext): Method = { + as.project.allMethods.find(m => m.classFile.equals(clazz) && m.name.equals(methodName)) + .getOrElse(throw new IllegalArgumentException("No method found with name " + methodName)) + } + + /** + * Searches for the field with the given name in the fixture project. If no field is found, an exception is thrown. + * + * @param as The test context. + * @return The field with the given name. + */ + private[this] def findField(a: AnnotationLike, useSecond: Boolean)(implicit as: TestContext): Field = { + + val clazz = getFieldClass(a, useSecond) + val name = getFieldName(a, useSecond) + + as.project.allFields.find(f => f.classFile.thisType.fqn.equals(clazz) && f.name.equals(name)) + .getOrElse(throw new IllegalArgumentException("No field found in class " + clazz + " with name " + name)) + } + + /** + * Ensures that the given identifier function is unique for each alias annotation by appending the id or line number + * of the given annotation. + * @param a The alias annotation. + * @param fun The identifier function to expand. + * @return A new, unique identifier function. + */ + private[this] def createUniqueIdentifierFunction(a: AnnotationLike, clazz: ClassFile, fun: String => String)( + implicit as: TestContext + ): String => String = { + a match { + case _: AnnotationLike if isLineAlias(a) => + (s: String) => fun(s) + ";lineNumber=" + getIntValue(a, "lineNumber") + getStringValue(a, "reason") + case _ => (s: String) => fun(s) + ";id=" + getID(a, clazz) + } + } + + /** + * Creates a context for the given alias source element. + */ + private[this] def createContext(ase: AliasSourceElement, second: Boolean)( + implicit simpleContexts: SimpleContexts + ): Context = { + if (ase.isMethodBound) simpleContexts(ase.declaredMethod) + else NoContext + } + + // --- Annotation Util --- // + + private[this] def getID(a: AnnotationLike, clazz: ClassFile)(implicit as: TestContext): String = { + clazz.thisType.simpleName + "." + getIntValue(a, "id") + } + + private[this] def getMethodName(a: AnnotationLike, useSecond: Boolean)(implicit as: TestContext): String = { + getStringValue(a, if (useSecond) "secondMethodName" else "methodName") + } + + private[this] def getFieldName(a: AnnotationLike, useSecond: Boolean)(implicit as: TestContext): String = { + getStringValue(a, if (useSecond) "secondFieldName" else "fieldName") + } + + private[this] def getFieldClass(a: AnnotationLike, useSecond: Boolean)(implicit as: TestContext): String = { + getStringValue(a, if (useSecond) "secondFieldClass" else "fieldClass") + } + + private[this] def getIntValue(a: AnnotationLike, element: String)(implicit as: TestContext): Int = { + getValue(a, element).asIntValue.value + } + + private[this] def getStringValue(a: AnnotationLike, element: String)(implicit as: TestContext): String = { + getValue(a, element) match { + case str: StringValue => str.value + case ClassValue(t) => t.asObjectType.fqn + case _ => throw new RuntimeException("Unexpected value type") + } + } + + private[this] def getValue(a: AnnotationLike, element: String)(implicit as: TestContext): ElementValue = { + a.elementValuePairs.filter(_.name == element).collectFirst { + case ElementValuePair(`element`, value) => value + }.getOrElse(as.project.classFile(a.annotationType.asObjectType) + .get + .findMethod(element) + .headOption + .getOrElse(throw new RuntimeException("No element value pair found for " + element)) + .annotationDefault + .get) + } + + private[this] def isLineAlias(a: AnnotationLike)(implicit as: TestContext): Boolean = { + getIntValue(a, "id") == -1 + } + + private[this] def hasTwoLines(a: AnnotationLike)(implicit as: TestContext): Boolean = { + getIntValue(a, "secondLineNumber") != -1 + } + + private[this] def hasMethodName(a: AnnotationLike, useSecond: Boolean)(implicit as: TestContext): Boolean = { + getMethodName(a, useSecond) != "" + } + + private[this] def isThisParameterAlias(a: AnnotationLike): Boolean = { + a.elementValuePairs.find(_.name == "thisParameter").exists(_.value.asBooleanValue.value) + } + + /** + * Returns all alias annotations that are contained in the given annotation. + * The given annotation must be an alias annotation. + * @param a The annotation. + * @return All alias annotations that are contained in the given annotation. + */ + private[this] def getAliasAnnotations(a: Iterable[AnnotationLike]): Iterable[AnnotationLike] = { + a.filter(annotationName(_).contains("Alias")) + .flatMap { + case a: AnnotationLike if annotationName(a).endsWith("Aliases") => + val x = a.elementValuePairs.filter(_.name == "value") + x.head.value.asArrayValue.values.map(_.asAnnotationValue.annotation) + case a => Seq(a) + } + } + + private[this] def annotationName(a: AnnotationLike): String = { + a.annotationType.asObjectType.simpleName + } + + private[this] def fieldsWithTypeAnnotations( + recreatedFixtureProject: SomeProject + ): Iterable[(Field, String => String, Iterable[AnnotationLike])] = { + for { + f <- recreatedFixtureProject.allFields // cannot be parallelized; "it" is not thread safe + annotations = f.runtimeInvisibleTypeAnnotations + if annotations.nonEmpty + } yield { + (f, (a: String) => f.toJava(s"@$a").substring(24), annotations) + } + } + +} diff --git a/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala b/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala index 957e1b7e79..bc94e962f8 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/cfg/CFG.scala @@ -16,6 +16,7 @@ import org.opalj.collection.mutable.IntArrayStack import org.opalj.graphs.DefaultMutableNode import org.opalj.graphs.DominatorTree import org.opalj.graphs.Node +import org.opalj.graphs.PostDominatorTree import org.opalj.log.GlobalLogContext import org.opalj.log.LogContext import org.opalj.log.OPALLogger.info @@ -549,7 +550,7 @@ case class CFG[I <: AnyRef, C <: CodeSequence[I]]( * * @see [[DominatorTree.apply]] */ - def dominatorTree: DominatorTree = { + lazy val dominatorTree: DominatorTree = { DominatorTree( 0, basicBlocks.head.predecessors.nonEmpty, @@ -559,6 +560,25 @@ case class CFG[I <: AnyRef, C <: CodeSequence[I]]( ) } + /** + * @return Returns the post dominator tree of this CFG. + * + * @see [[PostDominatorTree.apply]] + */ + lazy val postDominatorTree: PostDominatorTree = { + val exitNodes = normalReturnNode.predecessors.map(_.nodeId) ++ abnormalReturnNode.predecessors.map(_.nodeId) + + PostDominatorTree( + if (exitNodes.size == 1) exitNodes.headOption else None, + exitNodes.contains, + IntTrieSet.empty, + exitNodes.foreach, + foreachSuccessor, + foreachPredecessor, + basicBlocks.last.endPC + ) + } + /** * Creates a new CFG where the boundaries of the basic blocks are updated given the `pcToIndex` * mapping. The assumption is made that the indexes are continuous. diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/alias/AliasEntity.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/alias/AliasEntity.scala index 6d57150e92..032e81812f 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/alias/AliasEntity.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/properties/alias/AliasEntity.scala @@ -40,7 +40,7 @@ class AliasEntity( * A copy of two contexts of this [[AliasEntity]]. It uses the same order as the elements. */ private[this] val (_context1, _context2) = (c1, c2) match { - case (c1, c2) if e1 == _element1 => (c1, c2) + case (c1, c2) if e1 eq _element1 => (c1, c2) case (c1, c2) => (c2, c1) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/AbstractAliasAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/AbstractAliasAnalysis.scala new file mode 100644 index 0000000000..2035761a65 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/AbstractAliasAnalysis.scala @@ -0,0 +1,136 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package alias + +import org.opalj.br.ClassHierarchy +import org.opalj.br.fpcf.FPCFAnalysis +import org.opalj.br.fpcf.properties.alias.Alias +import org.opalj.br.fpcf.properties.alias.AliasEntity +import org.opalj.br.fpcf.properties.alias.NoAlias +import org.opalj.fpcf.Entity +import org.opalj.fpcf.InterimResult +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.Result +import org.opalj.fpcf.SomeEPS +import org.opalj.value.ValueInformation + +/** + * A base trait for all alias analyses. + */ +trait AbstractAliasAnalysis extends FPCFAnalysis { + + protected[this] type AnalysisContext <: AliasAnalysisContext + protected[this] type AnalysisState <: AliasAnalysisState + protected[this] type Tac = TACode[TACMethodParameter, DUVar[ValueInformation]] + + /** + * Determines the alias relation for the given entity. + * @param e The entity to determine the aliasing information for. + * @return The result of the computation. + */ + def determineAlias(e: Entity): ProperPropertyComputationResult = { + e match { + case entity: AliasEntity => + val context = createContext(entity) + + if (checkTypeCompatibility(context)) doDetermineAlias(context, createState) + else result(NoAlias)(context) + case _ => throw new UnknownError("unhandled entity type") + } + } + + /** + * Determines if the types of the given elements are compatible, i.e. it is possible that they refer to the same objects. + * Two elements are compatible if they are both reference types and one is a subtype of the other. + * In any other case, e.g., an Integer and a String, the elements cannot possibly refer to the same object. + * + * @param context The alias analysis context to check the types for. + * @return True if the types are compatible, false otherwise. + */ + private[this] def checkTypeCompatibility(context: AliasAnalysisContext): Boolean = { + + if (context.element1.isNullValue || context.element2.isNullValue) + return true + + if (!context.element1.isReferenceType || !context.element2.isReferenceType) + return false + + val element1ReferenceType = context.element1.referenceType + val element2ReferenceType = context.element2.referenceType + + val classHierarchy: ClassHierarchy = project.classHierarchy + + classHierarchy.isSubtypeOf(element1ReferenceType, element2ReferenceType) || + classHierarchy.isSubtypeOf(element2ReferenceType, element1ReferenceType) + } + + /** + * Called to determine the alias relation for the given entity. + * + * This method is implemented by the concrete alias analysis. + * + * @param context The context to determine the aliasing information for. + * @param state The state to use for the computation. + * @return + */ + protected[this] def doDetermineAlias( + implicit + context: AnalysisContext, + state: AnalysisState + ): ProperPropertyComputationResult + + /** + * Creates the result of the analysis based on the current state. + */ + protected[this] def createResult()( + implicit + state: AnalysisState, + context: AnalysisContext + ): ProperPropertyComputationResult + + /** + * Creates a final [[Result]] with the given alias property. + */ + protected[this] def result(alias: Alias)(implicit context: AnalysisContext): ProperPropertyComputationResult = { + Result(context.entity, alias) + } + + /** + * Creates a intermediate result for the given upper and lower bounds of the alias properties. + */ + protected[this] def interimResult(lb: Alias, ub: Alias)(implicit + context: AnalysisContext, + state: AnalysisState + ): ProperPropertyComputationResult = { + if (lb == ub) result(lb) + else InterimResult(context.entity, lb, ub, state.getDependees, continuation) + } + + /** + * A continuation function that will be invoked when an entity-property pair that this analysis depends on + * is updated + */ + protected[this] def continuation( + someEPS: SomeEPS + )( + implicit + context: AnalysisContext, + state: AnalysisState + ): ProperPropertyComputationResult + + /** + * Creates the state to use for the computation. + */ + protected[this] def createState: AnalysisState + + /** + * Creates the context to use for the computation. + */ + protected[this] def createContext( + entity: AliasEntity + ): AnalysisContext + +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/AliasAnalysisContext.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/AliasAnalysisContext.scala new file mode 100644 index 0000000000..151d446190 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/AliasAnalysisContext.scala @@ -0,0 +1,69 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package alias + +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.properties.Context +import org.opalj.br.fpcf.properties.alias.AliasEntity +import org.opalj.br.fpcf.properties.alias.AliasSourceElement +import org.opalj.fpcf.PropertyStore + +/** + * Encapsulates the context of an alias analysis computation. + * + * It contains the entity for which the aliasing information is computed, the current project, and the property store + * used to query other properties. + * + * It can be overridden to provide additional information to the computation. + * + * @param entity The entity for which the aliasing information is computed. + * @param project The current project. + * @param propertyStore The property store. + */ +class AliasAnalysisContext( + val entity: AliasEntity, + val project: SomeProject, + val propertyStore: PropertyStore +) { + + /** + * @return The context of the first element of the [[AliasEntity]]. + */ + def context1: Context = entity.context1 + + /** + * @return The context of the second element of the [[AliasEntity]]. + */ + def context2: Context = entity.context2 + + /** + * @return The context of the given [[AliasSourceElement]]. + */ + def contextOf(ase: AliasSourceElement): Context = { + if (isElement1(ase)) context1 else context2 + } + + /** + * @return The first [[AliasSourceElement]] of the [[AliasEntity]]. + */ + def element1: AliasSourceElement = entity.element1 + + /** + * @return The second [[AliasSourceElement]] of the [[AliasEntity]] + */ + def element2: AliasSourceElement = entity.element2 + + /** + * @return `true` if the given [[AliasSourceElement]] is the first element of the [[AliasEntity]]. + */ + def isElement1(e: AliasSourceElement): Boolean = element1 eq e + + /** + * @return `true` if the given [[AliasSourceElement]] is the second element of the [[AliasEntity]]. + */ + def isElement2(e: AliasSourceElement): Boolean = element2 eq e + +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/AliasAnalysisState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/AliasAnalysisState.scala new file mode 100644 index 0000000000..47604e3965 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/AliasAnalysisState.scala @@ -0,0 +1,55 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package alias + +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.Property +import org.opalj.fpcf.SomeEOptionP + +/** + * Encapsulates the current state of an alias analysis. + * + * It handles the current dependees of the analysis. + * + * It can be overridden to provide additional state information to the computation. + */ +trait AliasAnalysisState { + + private[this] var _dependees = Map.empty[Entity, EOptionP[Entity, Property]] + private[this] var _dependeesSet = Set.empty[SomeEOptionP] + + /** + * Adds the given entity property pair to the set of dependees. + * @param dependency The entity property pair to add. + */ + @inline private[alias] final def addDependency(dependency: EOptionP[Entity, Property]): Unit = { + _dependees += dependency.e -> dependency + _dependeesSet += dependency + } + + /** + * Removes the given entity property pair from the set of dependees. + */ + @inline private[alias] final def removeDependency(dependency: EOptionP[Entity, Property]): Unit = { + val oldEOptionP = _dependees(dependency.e) + _dependees -= dependency.e + _dependeesSet -= oldEOptionP + } + + /** + * @return A set containing all dependees. + */ + private[alias] final def getDependees: Set[SomeEOptionP] = { + _dependeesSet + } + + /** + * @return `true` if there are any dependees, `false` otherwise. + */ + private[alias] final def hasDependees: Boolean = _dependees.nonEmpty + +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/AliasSetLike.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/AliasSetLike.scala new file mode 100644 index 0000000000..593c5ca325 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/AliasSetLike.scala @@ -0,0 +1,115 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package alias + +import org.opalj.br.ReferenceType + +/** + * A base trait for alias sets that store the elements that an [[org.opalj.br.fpcf.properties.alias.AliasSourceElement]] can point to. + * It is possible to denote that the set can point to any arbitrary element if an analysis cannot limit the points-to set. + * This is handled by the [[pointsToAny]] and [[setPointsToAny()]] method. + * + * @tparam ElementType The type of the elements that can be stored in the set. + * @tparam T The concrete type of the alias set. + */ +trait AliasSetLike[ElementType, T <: AliasSetLike[ElementType, T]] { + + private[this] var _pointsToAny: Boolean = false + + private[this] var _pointsTo: Set[ElementType] = Set.empty[ElementType] + + /** + * Add the given element to the set of elements the associated [[org.opalj.br.fpcf.properties.alias.AliasSourceElement]] can point to. + * + * @param pointsTo The element to add to the set. + */ + def addPointsTo(pointsTo: ElementType): Unit = _pointsTo += pointsTo + + /** + * Checks if the set contains the given element. + * + * @param element The element to check for. + * @return `true` if the set contains the element, `false` otherwise. + */ + def pointsTo(element: ElementType): Boolean = allPointsTo.contains(element) + + /** + * Marks that this set can point to any arbitrary element. + */ + def setPointsToAny(): Unit = _pointsToAny = true + + /** + * Checks if this set can point to any arbitrary element. + * + * @return `true` if the set can point to any arbitrary element, `false` otherwise. + */ + def pointsToAny: Boolean = _pointsToAny + + /** + * Checks if the set is empty, i.e., it does not contain any elements it can point to and also cannot point to any + * arbitrary element. + * + * @return `true` if the set is empty, `false` otherwise. + */ + def isEmpty: Boolean = allPointsTo.isEmpty && !pointsToAny + + /** + * Returns the number of elements the set can point to. Note that the size being 0 does not imply that the + * [[isEmpty]] method returns `true`, as the set can still point to any arbitrary element. + * + * @return The number of elements the set can point to. + */ + def size: Int = allPointsTo.size + + /** + * Returns all elements this set can point to. Note that the returned set being empty does not imply that the + * [[isEmpty]] method returns `true`, as this set can still point to any arbitrary element. + * + * @return The set of elements the set can point to. + */ + def allPointsTo: Set[ElementType] = _pointsTo + + /** + * Tries to find and return two elements that are in both this set and the given other set as a tuple of options. + * If only one element is in both sets, the second element is `None`. If the sets are disjoint, both elements + * are `None`. + * + * This is used instead of computing the whole intersection because more than two elements in the intersection + * would not change the result of the alias analysis. + * + * Note that this does not check if one of the sets can point to any arbitrary element. + * + * @param other The other set to check for intersections. + * @return A tuple containing two elements that are in both this set and the other set. + */ + def findTwoIntersections(other: T): (Option[ElementType], Option[ElementType]) = { + + var firstIntersection: Option[ElementType] = None + + // optimized version of allPointsTo.intersect(other.allPointsTo) + allPointsTo.foreach(element => { + if (other.pointsTo(element)) { + if (firstIntersection.isEmpty) { + firstIntersection = Some(element) + } else { + return (firstIntersection, Some(element)) + } + } + }) + + (firstIntersection, None) + } +} + +/** + * Implementation of an [[AliasSetLike]] that is based on allocation sites, i.e., it stores elements of the type [[AllocationSite]]. + */ +class AllocationSiteBasedAliasSet extends AliasSetLike[AllocationSite, AllocationSiteBasedAliasSet] {} + +/** + * Implementation of an [[AliasSetLike]] that is based on types, i.e., it stores elements of the type [[ReferenceType]]. + */ +class TypeBasedAliasSet extends AliasSetLike[ReferenceType, TypeBasedAliasSet] {} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/AllocationSiteAndTacBasedAliasAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/AllocationSiteAndTacBasedAliasAnalysis.scala new file mode 100644 index 0000000000..017e1dab67 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/AllocationSiteAndTacBasedAliasAnalysis.scala @@ -0,0 +1,84 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package alias + +import org.opalj.br.PC +import org.opalj.br.fpcf.properties.Context +import org.opalj.br.fpcf.properties.NoContext + +trait AllocationSiteAndTacBasedAliasAnalysis extends AllocationSiteBasedAliasAnalysis with TacBasedAliasAnalysis { + + override protected[this] type AnalysisState <: AllocationSiteBasedAliasAnalysisState with TacBasedAliasAnalysisState + + override protected[this] def checkMustAlias(intersectingElement: AliasElementType)(implicit + state: AnalysisState, + context: AnalysisContext + ): Boolean = { + + val pointsTo1 = state.pointsTo1 + val pointsTo2 = state.pointsTo2 + + if (pointsTo1.size != 1 || pointsTo2.size != 1) return false + + // they refer to the same allocation site but aren't necessarily the same object (e.g. if the allocation site + // is inside a loop or a different method and is executed multiple times) + + val (pointsToContext: Context, pc: PC) = pointsTo1.allPointsTo.head + val method = pointsToContext match { + case NoContext => context.element1.declaredMethod + case _ => pointsToContext.method + } + + if (context.element1.isAliasUVar && + context.element2.isAliasUVar && + context.element1.declaredMethod == method && + context.element2.declaredMethod == method + ) { + + // Both elements are uVars that point to the same allocation site and both are inside the method of the allocation site + // -> they must alias if the allocation site is executed only once (i.e. no loop or recursion) + + val defSite1 = context.element1.asAliasUVar.persistentUVar.defPCs + val defSite2 = context.element2.asAliasUVar.persistentUVar.defPCs + + // multiple or different def sites for one element -> might be different objects (e.g. due to recursion via parameter) + if (defSite1.size != 1 || defSite2.size != 1 || defSite1.head != defSite2.head) return false + + // the definition site is not the allocation site -> it is a method call or something similar + if (pc != defSite1.head) return false + + val tac = state.tacai1.get + val cfg = tac.cfg + val domTree = cfg.dominatorTree + val postDomTree = cfg.postDominatorTree + val allocBB = cfg.bb(tac.properStmtIndexForPC(pc)).nodeId + + // check if the allocation site is dominated by a loop header, i.e., is executed multiple times + domTree.foreachDom(allocBB)(dom => { + + // check if the dominator is a loop header + cfg.foreachPredecessor(cfg.bb(dom).startPC)(pred => { + + // if the dominator itself dominates one of its predecessors, it is a loop header + if (domTree.strictlyDominates(dom, pred)) { + + // Only report a negative result if the allocation site is inside the loop. + // If the loop head post dominates the allocation site we know that the allocation is behind + // the loop. + if (!postDomTree.strictlyDominates(dom, allocBB)) { + return false + } + } + }) + + }) + + return true + } + + false + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/AllocationSiteBasedAliasAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/AllocationSiteBasedAliasAnalysis.scala new file mode 100644 index 0000000000..8f6cb84c69 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/AllocationSiteBasedAliasAnalysis.scala @@ -0,0 +1,34 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package alias + +import org.opalj.br.PC +import org.opalj.br.fpcf.properties.Context +import org.opalj.br.fpcf.properties.alias.AliasSourceElement + +trait AllocationSiteBasedAliasAnalysis extends SetBasedAliasAnalysis { + + override protected[this] type AliasElementType = AllocationSite + override protected[this] type AliasSet = AllocationSiteBasedAliasSet + override protected[this] type AnalysisState <: AllocationSiteBasedAliasAnalysisState + +} + +/** + * Encapsulates the current state of an alias analysis that uses an [[AllocationSiteBasedAliasSet]] to store the + * allocations sites to which each of the alias elements can point to. + */ +class AllocationSiteBasedAliasAnalysisState + extends SetBasedAliasAnalysisState[AllocationSite, AllocationSiteBasedAliasSet] { + + override protected[this] def createAliasSet(): AllocationSiteBasedAliasSet = new AllocationSiteBasedAliasSet + + def addPointsTo(ase: AliasSourceElement, context: Context, pc: PC)( + implicit aliasContext: AliasAnalysisContext + ): Unit = { + addPointsTo(ase, (context, pc)) + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/IntraProceduralAliasAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/IntraProceduralAliasAnalysis.scala new file mode 100644 index 0000000000..7419324883 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/IntraProceduralAliasAnalysis.scala @@ -0,0 +1,143 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package alias + +import org.opalj.br.analyses.ProjectInformationKeys +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.BasicFPCFLazyAnalysisScheduler +import org.opalj.br.fpcf.FPCFAnalysis +import org.opalj.br.fpcf.FPCFAnalysisScheduler +import org.opalj.br.fpcf.properties.alias.Alias +import org.opalj.br.fpcf.properties.alias.AliasEntity +import org.opalj.br.fpcf.properties.alias.AliasSourceElement +import org.opalj.br.fpcf.properties.alias.AliasUVar +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.PropertyBounds +import org.opalj.fpcf.PropertyStore +import org.opalj.tac.fpcf.properties.TACAI + +class IntraProceduralAliasAnalysis(final val project: SomeProject) extends AllocationSiteAndTacBasedAliasAnalysis { + + override type AnalysisState = TacBasedAliasAnalysisState with AllocationSiteBasedAliasAnalysisState + override type AnalysisContext = AliasAnalysisContext + + protected[this] def analyzeTAC()( + implicit + context: AnalysisContext, + state: AnalysisState + ): ProperPropertyComputationResult = { + + handleElement(context.element1, state.tacai1) + handleElement(context.element2, state.tacai2) + + createResult() + } + + /** + * Handles the given [[AliasSourceElement]]. + * + * It is responsible for calculating the points-to set of the given [[AliasSourceElement]] and handling it + * by updating the analysis state accordingly. + */ + private[this] def handleElement(ase: AliasSourceElement, tac: Option[Tac])( + implicit + state: AnalysisState, + context: AnalysisContext + ): Unit = { + if (ase.isAliasUVar) handleUVar(ase.asAliasUVar, tac.get) + else handleOther(ase) + } + + /** + * Handles the given [[AliasUVar]]. + * + * It is responsible for calculating the points-to set of the given [[AliasUVar]] and handling it + * by updating the analysis state accordingly. It assumes that it can point to any allocation site + * if the variable interacts with constructs outside the current method. + */ + private[this] def handleUVar(uVar: AliasUVar, tac: Tac)( + implicit + state: AnalysisState, + context: AnalysisContext + ): Unit = { + + uVar.persistentUVar.defPCs.foreach(pc => { + + if (pc < 0) { + state.setPointsToAny(uVar) + return + } + + tac.stmts(tac.pcToIndex(pc)) match { + case Assignment(_, _, New(pc, _)) => + state.addPointsTo(uVar, context.contextOf(uVar), pc) + case _ => + state.setPointsToAny(uVar) + return + } + + }) + + } + + /** + * Handles the given [[AliasSourceElement]]. + * + * Because we are only performing an intraprocedural analysis, we cannot handle it further and simply set, + * that the element can point to any arbitrary object. + */ + private[this] def handleOther(ase: AliasSourceElement)( + implicit + state: AnalysisState, + context: AnalysisContext + ): Unit = { + state.setPointsToAny(ase) + } + + override protected[this] def createState: AnalysisState = + new AllocationSiteBasedAliasAnalysisState with TacBasedAliasAnalysisState + + override protected[this] def createContext( + entity: AliasEntity + ): AliasAnalysisContext = + new AliasAnalysisContext(entity, project, propertyStore) +} + +sealed trait IntraProceduralAliasAnalysisScheduler extends FPCFAnalysisScheduler { + + override def requiredProjectInformation: ProjectInformationKeys = Seq() + + final def derivedProperty: PropertyBounds = PropertyBounds.lub(Alias) + + override def uses: Set[PropertyBounds] = Set(PropertyBounds.ub(TACAI)) + + override def derivesCollaboratively: Set[PropertyBounds] = Set.empty +} + +/** + * A scheduler for a lazy, intraprocedural alias analysis. + */ +object LazyIntraProceduralAliasAnalysisScheduler extends IntraProceduralAliasAnalysisScheduler + with BasicFPCFLazyAnalysisScheduler { + + override def derivesLazily: Some[PropertyBounds] = Some(derivedProperty) + + override def register( + project: SomeProject, + propertyStore: PropertyStore, + i: LazyIntraProceduralAliasAnalysisScheduler.InitializationData + ): FPCFAnalysis = { + + val analysis = new IntraProceduralAliasAnalysis(project) + + propertyStore.registerLazyPropertyComputation( + Alias.key, + analysis.determineAlias + ) + + analysis + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/SetBasedAliasAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/SetBasedAliasAnalysis.scala new file mode 100644 index 0000000000..13adb28efd --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/SetBasedAliasAnalysis.scala @@ -0,0 +1,115 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package alias + +import org.opalj.br.fpcf.properties.alias.AliasSourceElement +import org.opalj.br.fpcf.properties.alias.MayAlias +import org.opalj.br.fpcf.properties.alias.MustAlias +import org.opalj.br.fpcf.properties.alias.NoAlias +import org.opalj.fpcf.ProperPropertyComputationResult + +trait SetBasedAliasAnalysis extends AbstractAliasAnalysis { + + protected[this] type AliasElementType + protected[this] type AliasSet <: AliasSetLike[AliasElementType, AliasSet] + + override protected[this] type AnalysisState <: SetBasedAliasAnalysisState[AliasElementType, AliasSet] + + override protected[this] def createResult()( + implicit + state: AnalysisState, + context: AnalysisContext + ): ProperPropertyComputationResult = { + + val pointsTo1 = state.pointsTo1 + val pointsTo2 = state.pointsTo2 + + if (pointsTo1.pointsToAny || pointsTo2.pointsToAny) return result(MayAlias) + + val intersections = pointsTo1.findTwoIntersections(pointsTo2) + + intersections match { + case (None, None) /* no intersection */ => + if (state.hasDependees) interimResult(NoAlias, MayAlias) else result(NoAlias) + case (Some(intersection), None) /* exactly one intersection */ if checkMustAlias(intersection) => + if (state.hasDependees) interimResult(MustAlias, MayAlias) else result(MustAlias) + case _ /* at least two intersections */ => + result(MayAlias) + } + } + + /** + * Checks if the current analysis state allows for a [[MustAlias]] relation between the two elements. It assumes + * the the given element is the only intersection between the two points-to sets. + * + * This method always returns false and should be overriden if more precise must alias checks can be performed. + * + * @param intersectingElement The only between the two points-to sets. + * @return `true` if the two elements can be a [[MustAlias]], `false` otherwise. + */ + protected[this] def checkMustAlias(intersectingElement: AliasElementType)(implicit + state: AnalysisState, + context: AnalysisContext + ): Boolean = false +} + +/** + * Encapsulates the current state of an alias analysis that uses an [[AliasSetLike]] to store the elements + * that an [[AliasSourceElement]] can point to. + * + * It additionally stores and handles an [[AliasSetLike]] for each [[AliasSourceElement]] and provides methods for + * interacting with it. + */ +trait SetBasedAliasAnalysisState[ElementType, AliasSet <: AliasSetLike[ElementType, AliasSet]] + extends AliasAnalysisState { + + private[this] val _pointsTo1: AliasSet = createAliasSet() + private[this] val _pointsTo2: AliasSet = createAliasSet() + + /** + * @return The current [[AliasSetLike]] for the first [[AliasSourceElement]]. + */ + def pointsTo1: AliasSet = _pointsTo1 + + /** + * @return The current [[AliasSetLike]] for the second [[AliasSourceElement]]. + */ + def pointsTo2: AliasSet = _pointsTo2 + + /** + * adds the given element set to the [[AliasSetLike]] of the given [[AliasSourceElement]]. + */ + def addPointsTo(ase: AliasSourceElement, element: ElementType)( + implicit aliasContext: AliasAnalysisContext + ): Unit = { + if (aliasContext.isElement1(ase)) { + _pointsTo1.addPointsTo(element) + } else { + _pointsTo2.addPointsTo(element) + } + } + + /** + * Marks that the given [[AliasSourceElement]] can point to any arbitrary element + */ + def setPointsToAny(ase: AliasSourceElement)( + implicit context: AliasAnalysisContext + ): Unit = { + if (context.isElement1(ase)) { + _pointsTo1.setPointsToAny() + } else { + _pointsTo2.setPointsToAny() + } + } + + /** + * Creates a new [[AliasSetLike]] of the used type + * + * @return + */ + protected[this] def createAliasSet(): AliasSet + +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/TacBasedAliasAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/TacBasedAliasAnalysis.scala new file mode 100644 index 0000000000..50933cf752 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/TacBasedAliasAnalysis.scala @@ -0,0 +1,182 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package alias + +import org.opalj.br.Method +import org.opalj.br.fpcf.properties.alias.MayAlias +import org.opalj.br.fpcf.properties.alias.NoAlias +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.InterimResult +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.SomeEPS +import org.opalj.fpcf.UBP +import org.opalj.tac.fpcf.properties.TACAI + +/** + * A base trait for all alias analyses based on the TACAI. + */ +trait TacBasedAliasAnalysis extends AbstractAliasAnalysis { + + override protected[this] type AnalysisState <: TacBasedAliasAnalysisState + + override def doDetermineAlias( + implicit + context: AnalysisContext, + state: AnalysisState + ): ProperPropertyComputationResult = { + + if (context.element1.isMethodBound) retrieveTAC(context.element1.method) + if (context.element2.isMethodBound) retrieveTAC(context.element2.method) + + if (bothTacaisDefined) analyzeTAC() + else interimResult(NoAlias, MayAlias) + } + + /** + * Computes the alias relation of the [[org.opalj.br.fpcf.properties.alias.AliasEntity]] using the TAC representation + * of the corresponding methods. + * + * This method is called when the TACs of the methods of both elements are available. If an element is not method + * bound, it is not considered. + * + * @return The result of the computation. + */ + protected[this] def analyzeTAC()( + implicit + context: AnalysisContext, + state: AnalysisState + ): ProperPropertyComputationResult + + /** + * Retrieves the TACAI for the given method. + */ + private[this] def retrieveTAC( + m: Method + )( + implicit + context: AnalysisContext, + state: AnalysisState + ): Unit = { + val tacai: EOptionP[Method, TACAI] = propertyStore(m, TACAI.key) + + state.addTacEPSToMethod(tacai.asEPS, m) + + if (tacai.isRefinable) { + state.addDependency(tacai) + } + + if (tacai.hasUBP && tacai.ub.tac.isDefined) { + state.updateTACAI(m, tacai.ub.tac.get) + } + } + + /** + * Continues the computation when a TACAI property is updated. + */ + override protected[this] def continuation( + someEPS: SomeEPS + )( + implicit + context: AnalysisContext, + state: AnalysisState + ): ProperPropertyComputationResult = { + someEPS match { + case UBP(ub: TACAI) => + state.removeDependency(someEPS) + + if (someEPS.isRefinable) state.addDependency(someEPS) + if (ub.tac.isDefined) state.updateTACAI(state.getMethodForTacEPS(someEPS), ub.tac.get) + + if (bothTacaisDefined) analyzeTAC() + else InterimResult(context.entity, NoAlias, MayAlias, state.getDependees, continuation) + case _ => + throw new UnknownError(s"unhandled property (${someEPS.ub} for ${someEPS.e}") + } + } + + /** + * @return `true` if both TACs are defined. If one of the elements is not method bound, it is not considered. + */ + private[this] def bothTacaisDefined( + implicit + context: AnalysisContext, + state: AnalysisState + ): Boolean = { + (!context.element1.isMethodBound || state.tacai1.isDefined) && + (!context.element2.isMethodBound || state.tacai2.isDefined) + } + +} + +/** + * Encapsulates the state of a TAC-based alias analysis. + * + * It additionally contains the TACAI for the first and second alias source element if they are method bound and + * provides a way to calculate and cache the dominator and postDominator tree of the methods. + */ +trait TacBasedAliasAnalysisState extends AliasAnalysisState { + + private[this] var _tacai1: Option[TACode[TACMethodParameter, V]] = None + private[this] var _tacai2: Option[TACode[TACMethodParameter, V]] = None + + private[this] var _tacEPSToMethod: Map[SomeEPS, Method] = Map() + + /** + * Updates the TACAI for the given method. + */ + private[alias] def updateTACAI( + m: Method, + tacai: TACode[TACMethodParameter, V] + )(implicit context: AliasAnalysisContext): Unit = { + + var anyMatch: Boolean = false + + if (context.element1.isMethodBound && m.equals(context.element1.method)) { + _tacai1 = Some(tacai) + anyMatch = true + } + + if (context.element2.isMethodBound && m.equals(context.element2.method)) { + _tacai2 = Some(tacai) + anyMatch = true + } + + if (!anyMatch) throw new IllegalArgumentException("Method not found") + } + + /** + * Returns the TACAI for the first alias source element. + * + * If the element is not method bound, `None` is returned. + * + * @return The TACAI for the first alias source element. + */ + def tacai1: Option[TACode[TACMethodParameter, V]] = _tacai1 + + /** + * Returns the TACAI for the second alias source element. + * + * If the element is not method bound, `None` is returned. + * + * @return The TACAI for the second alias source element. + */ + def tacai2: Option[TACode[TACMethodParameter, V]] = _tacai2 + + /** + * Associates the given TAC EPS with the given method. + */ + def addTacEPSToMethod(eps: SomeEPS, m: Method): Unit = { + _tacEPSToMethod += (eps -> m) + } + + /** + * Returns the method that is represented by the given TAC EPS. + */ + def getMethodForTacEPS(eps: SomeEPS): Method = { + _tacEPSToMethod(eps) + } + +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/TypeBasedAliasAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/TypeBasedAliasAnalysis.scala new file mode 100644 index 0000000000..aa9e46797e --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/TypeBasedAliasAnalysis.scala @@ -0,0 +1,25 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package alias + +import org.opalj.br.ReferenceType + +trait TypeBasedAliasAnalysis extends SetBasedAliasAnalysis { + + override protected[this] type AliasElementType = ReferenceType + override protected[this] type AliasSet = TypeBasedAliasSet + override protected[this] type AnalysisState <: TypeBasedAliasAnalysisState + +} + +/** + * Encapsulates the current state of an alias analysis that uses an [[TypeBasedAliasSet]] to store the + * allocations sites to which each of the alias elements can point to. + */ +class TypeBasedAliasAnalysisState extends SetBasedAliasAnalysisState[ReferenceType, TypeBasedAliasSet] { + + override protected[this] def createAliasSet(): TypeBasedAliasSet = new TypeBasedAliasSet +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/package.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/package.scala new file mode 100644 index 0000000000..40a6698b2f --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/package.scala @@ -0,0 +1,17 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses + +import org.opalj.br.PC +import org.opalj.br.fpcf.properties.Context +import org.opalj.tac.DUVar +import org.opalj.value.ValueInformation + +package object alias { + + type V = DUVar[ValueInformation] + + type AllocationSite = (Context, PC) +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/pointsto/AbstractPointsToBasedAliasAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/pointsto/AbstractPointsToBasedAliasAnalysis.scala new file mode 100644 index 0000000000..b6f8b10873 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/pointsto/AbstractPointsToBasedAliasAnalysis.scala @@ -0,0 +1,311 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package alias +package pointsto + +import scala.collection.mutable.ArrayBuffer + +import org.opalj.br.Field +import org.opalj.br.analyses.DeclaredFieldsKey +import org.opalj.br.analyses.ProjectInformationKeys +import org.opalj.br.analyses.VirtualFormalParametersKey +import org.opalj.br.fpcf.FPCFAnalysisScheduler +import org.opalj.br.fpcf.properties.Context +import org.opalj.br.fpcf.properties.alias.Alias +import org.opalj.br.fpcf.properties.alias.AliasEntity +import org.opalj.br.fpcf.properties.alias.AliasField +import org.opalj.br.fpcf.properties.alias.AliasFormalParameter +import org.opalj.br.fpcf.properties.alias.AliasReturnValue +import org.opalj.br.fpcf.properties.alias.AliasSourceElement +import org.opalj.br.fpcf.properties.alias.AliasStaticField +import org.opalj.br.fpcf.properties.alias.AliasUVar +import org.opalj.br.fpcf.properties.cg.Callees +import org.opalj.br.fpcf.properties.pointsto.AllocationSitePointsToSet +import org.opalj.br.fpcf.properties.pointsto.PointsToSetLike +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.PropertyBounds +import org.opalj.fpcf.SomeEPS +import org.opalj.fpcf.UBP +import org.opalj.tac.cg.TypeIteratorKey +import org.opalj.tac.common.DefinitionSitesKey +import org.opalj.tac.fpcf.analyses.alias.TacBasedAliasAnalysis +import org.opalj.tac.fpcf.analyses.pointsto.AbstractPointsToBasedAnalysis +import org.opalj.tac.fpcf.analyses.pointsto.toEntity +import org.opalj.tac.fpcf.properties.TACAI + +/** + * A base trait for all alias analyses based on the points-to information. + */ +trait AbstractPointsToBasedAliasAnalysis extends TacBasedAliasAnalysis with AbstractPointsToBasedAnalysis + with SetBasedAliasAnalysis { + + override protected[this] type AnalysisContext = AliasAnalysisContext + override protected[this] type AnalysisState <: PointsToBasedAliasAnalysisState[ + AliasElementType, + AliasSet, + PointsToSet + ] + + override protected[this] def analyzeTAC()(implicit + context: AnalysisContext, + state: AnalysisState + ): ProperPropertyComputationResult = { + + handleElement(context.element1, state.tacai1) + handleElement(context.element2, state.tacai2) + + createResult() + } + + /** + * Handles the given [[AliasSourceElement]]. + * + * It is responsible for retrieving current the points-to set of the given [[AliasSourceElement]] and handling it + * by updating the analysis state accordingly. + */ + private[this] def handleElement(ase: AliasSourceElement, tac: Option[Tac])( + implicit + state: AnalysisState, + context: AnalysisContext + ): Unit = { + + ase match { + case AliasUVar(uVar, _, _) => + uVar.defPCs.foreach(ds => { + handlePointsToEntity(ase, getPointsToOfDefSite(ds, context.contextOf(ase), tac.get)) + }) + + case AliasFormalParameter(fp) => + handlePointsToEntity(ase, getPointsToOfDefSite(fp.origin, context.contextOf(ase), tac.get)) + + case AliasStaticField(field) => handlePointsToEntity(ase, getPointsToOfStaticField(field)) + + case field: AliasField => + handlePointsToEntities(ase, getPointsToOfField(field, context.contextOf(ase), tac.get)) + + case arv: AliasReturnValue => handlePointsToEntity(arv, getPointsToOfReturnValue(context.contextOf(arv))) + + case _ => + } + } + + /** + * Retrieves the points-to set of the given definition site. + */ + private[this] def getPointsToOfDefSite(defSite: Int, context: Context, tac: Tac): EOptionP[Entity, PointsToSet] = { + propertyStore( + toEntity(if (defSite < 0) defSite else tac.properStmtIndexForPC(defSite), context, tac.stmts), + pointsToPropertyKey + ) + } + + /** + * Retrieves the points-to set of the given static field. + */ + private[this] def getPointsToOfStaticField(field: Field): EOptionP[Entity, PointsToSet] = { + propertyStore( + declaredFields.apply(field), + pointsToPropertyKey + ) + } + + /** + * Retrieves the points-to set of the given non-static field. + * If the points-to set of one of the defSites of the fieldReference is refineable, it is added as a field dependency. + */ + private[this] def getPointsToOfField(field: AliasField, fieldContext: Context, tac: Tac)( + implicit + state: AnalysisState, + context: AnalysisContext + ): Iterable[EOptionP[Entity, PointsToSet]] = { + + val allocationSites = ArrayBuffer.empty[ElementType] + + field.fieldReference.defSites.map(getPointsToOfDefSite(_, fieldContext, tac)) + .foreach(pts => { + + if (pts.isEPK) { + state.addDependency(pts) + state.addFieldDependency(field, pts) + } else { + + val fieldReferenceEntity = (field.fieldReference, pts.e) + pts.ub.forNewestNElements( + pts.ub.numElements - state.oldPointsToSet(field, fieldReferenceEntity).map( + _.numElements + ).getOrElse(0) + ) { + value => + { + allocationSites += value + } + } + + state.setOldPointsToSet(field, fieldReferenceEntity, pts.ub) + } + }) + + allocationSites.map(allocSite => + propertyStore((allocSite, declaredFields(field.fieldReference.field)), pointsToPropertyKey) + ) + } + + /** + * Retrieves the points-to set of the return value of the given method. + */ + private[this] def getPointsToOfReturnValue(callContext: Context): EOptionP[Entity, PointsToSet] = { + propertyStore( + callContext, + pointsToPropertyKey + ) + } + + /** + * Handles all given points-to entities associated with given [[AliasSourceElement]] by updating the analysis state. + */ + private[this] def handlePointsToEntities(ase: AliasSourceElement, eps: Iterable[EOptionP[Entity, PointsToSet]])( + implicit + state: AnalysisState, + context: AnalysisContext + ): Unit = { + eps.foreach(handlePointsToEntity(ase, _)) + } + + /** + * Handles the given points-to entity associated with the given [[AliasSourceElement]] by updating the analysis state. + */ + private[this] def handlePointsToEntity(ase: AliasSourceElement, eps: EOptionP[Entity, PointsToSet])( + implicit + state: AnalysisState, + context: AnalysisContext + ): Unit = { + + val pointsToEntity: Entity = eps.e + + if (eps.isEPK) { + state.addDependency(eps) + state.addElementDependency(ase, eps) + } else handlePointsToSet(ase, pointsToEntity, eps.ub) + } + + /** + * Handles the given points-to set associated with the given [[AliasSourceElement]] by updating the analysis state. + * + * @param ase The [[AliasSourceElement]] the given pointsTo set is associated with. + * @param pointsToEntity The pointsTo entity the pointTo Set belongs to. + * @param pointsToSet The pointsTo set to handle. + */ + private[this] def handlePointsToSet(ase: AliasSourceElement, pointsToEntity: Entity, pointsToSet: PointsToSet)( + implicit + state: AnalysisState, + context: AnalysisContext + ): Unit = { + pointsToSet.forNewestNElements( + pointsToSet.numElements - state.oldPointsToSet(ase, pointsToEntity).map(_.numElements).getOrElse(0) + ) { + element => handlePointsToSetElement(ase, pointsToEntity, element) + } + state.setOldPointsToSet(ase, pointsToEntity, pointsToSet) + } + + /** + * Handles a single element of the given points-to set that is associated with [[AliasSourceElement]] and has not been handled so far. + * + * @param ase The [[AliasSourceElement]] the pointsTo set is associated with. + * @param pointsToEntity The pointsTo entity the pointTo Set belongs to. + * @param element The pointsTo Element to handle. + */ + protected[this] def handlePointsToSetElement(ase: AliasSourceElement, pointsToEntity: Entity, element: ElementType)( + implicit + state: AnalysisState, + context: AnalysisContext + ): Unit + + /** + * Continues the computation when any depending property is updated. + */ + override protected[this] def continuation(someEPS: SomeEPS)( + implicit + context: AnalysisContext, + state: AnalysisState + ): ProperPropertyComputationResult = { + + someEPS match { + case UBP(pointsToSet: PointsToSetLike[_, _, _]) => + val pts = pointsToSet.asInstanceOf[PointsToSet] + + val field1Dependence = state.field1HasDependency(someEPS) + val field2Dependence = state.field2HasDependency(someEPS) + + if (field1Dependence) handlePointsToSet( + context.element1, + (context.element1.asAliasField.fieldReference, someEPS.e), + pts + ) + if (field2Dependence) handlePointsToSet( + context.element2, + (context.element2.asAliasField.fieldReference, someEPS.e), + pts + ) + + state.removeFieldDependency(someEPS) + + if (someEPS.isRefinable) { + if (field1Dependence) state.addField1Dependency(someEPS) + if (field2Dependence) state.addField2Dependency(someEPS) + + } + + val element1Dependence = state.element1HasDependency(someEPS) + val element2Dependence = state.element2HasDependency(someEPS) + + if (element1Dependence) handlePointsToSet(context.element1, someEPS.e, pts) + if (element2Dependence) handlePointsToSet(context.element2, someEPS.e, pts) + + state.removeElementDependency(someEPS) + + if (someEPS.isRefinable) { + if (element1Dependence) state.addElementDependency(context.element1, someEPS) + if (element2Dependence) state.addElementDependency(context.element2, someEPS) + } + + createResult() + case _ => super.continuation(someEPS) + } + } + + override protected[this] def createContext( + entity: AliasEntity + ): AnalysisContext = + new AliasAnalysisContext(entity, project, propertyStore) + +} + +/** + * A base trait for all points-to based alias analysis schedulers. + */ +trait PointsToBasedAliasAnalysisScheduler extends FPCFAnalysisScheduler { + + override def requiredProjectInformation: ProjectInformationKeys = + Seq( + VirtualFormalParametersKey, + TypeIteratorKey, + DefinitionSitesKey, + DeclaredFieldsKey + ) + + final def derivedProperty: PropertyBounds = PropertyBounds.lub(Alias) + + override def uses: Set[PropertyBounds] = Set( + PropertyBounds.ub(TACAI), + PropertyBounds.ub(Callees), + PropertyBounds.ub(AllocationSitePointsToSet) + ) + + override def derivesCollaboratively: Set[PropertyBounds] = Set.empty +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/pointsto/AllocationSitePointsToBasedAliasAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/pointsto/AllocationSitePointsToBasedAliasAnalysis.scala new file mode 100644 index 0000000000..4401d79f93 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/pointsto/AllocationSitePointsToBasedAliasAnalysis.scala @@ -0,0 +1,92 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package alias +package pointsto + +import org.opalj.br.PC +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.BasicFPCFLazyAnalysisScheduler +import org.opalj.br.fpcf.FPCFAnalysis +import org.opalj.br.fpcf.properties.Context +import org.opalj.br.fpcf.properties.alias.Alias +import org.opalj.br.fpcf.properties.alias.AliasSourceElement +import org.opalj.br.fpcf.properties.pointsto.AllocationSitePointsToSet +import org.opalj.br.fpcf.properties.pointsto.longToAllocationSite +import org.opalj.fpcf.Entity +import org.opalj.fpcf.PropertyBounds +import org.opalj.fpcf.PropertyStore +import org.opalj.tac.fpcf.analyses.alias.AllocationSite +import org.opalj.tac.fpcf.analyses.pointsto.AbstractPointsToAnalysis +import org.opalj.tac.fpcf.analyses.pointsto.AllocationSiteBasedAnalysis + +/** + * An alias analysis based on points-to information that contain the possible allocation sites of an element. + * @param project The project + */ +class AllocationSitePointsToBasedAliasAnalysis(final val project: SomeProject) + extends AbstractPointsToBasedAliasAnalysis + with AbstractPointsToAnalysis + with AllocationSiteBasedAnalysis + with AllocationSiteAndTacBasedAliasAnalysis { + + override protected[this] type AnalysisState = AllocationSitePointsToBasedAliasAnalysisState + + override protected[this] def handlePointsToSetElement( + ase: AliasSourceElement, + pointsToEntity: Entity, + element: ElementType + )( + implicit + state: AnalysisState, + context: AnalysisContext + ): Unit = { + + val encodedAllocationSite: ElementType = element + + val (allocContext, pc, _): (Context, PC, Int) = longToAllocationSite(encodedAllocationSite) + + state.addPointsTo(ase, allocContext, pc) + } + + /** + * Creates the state to use for the computation. + */ + override protected[this] def createState: AnalysisState = new AllocationSitePointsToBasedAliasAnalysisState + +} + +/** + * A scheduler for a lazy, allocation site, points-to based alias analysis. + */ +object LazyAllocationSitePointsToBasedAliasAnalysisScheduler extends PointsToBasedAliasAnalysisScheduler + with BasicFPCFLazyAnalysisScheduler { + + override def derivesLazily: Some[PropertyBounds] = Some(derivedProperty) + + override def register( + project: SomeProject, + propertyStore: PropertyStore, + i: LazyAllocationSitePointsToBasedAliasAnalysisScheduler.InitializationData + ): FPCFAnalysis = { + + val analysis = new AllocationSitePointsToBasedAliasAnalysis(project) + + propertyStore.registerLazyPropertyComputation( + Alias.key, + analysis.determineAlias + ) + + analysis + } +} + +/** + * The state class used by an [[AllocationSitePointsToBasedAliasAnalysis]]. + * + * @see [[PointsToBasedAliasAnalysisState]] + */ +class AllocationSitePointsToBasedAliasAnalysisState extends AllocationSiteBasedAliasAnalysisState + with PointsToBasedAliasAnalysisState[AllocationSite, AllocationSiteBasedAliasSet, AllocationSitePointsToSet] {} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/pointsto/PointsToBasedAliasAnalysisState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/pointsto/PointsToBasedAliasAnalysisState.scala new file mode 100644 index 0000000000..32a8ccf09e --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/pointsto/PointsToBasedAliasAnalysisState.scala @@ -0,0 +1,163 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package alias +package pointsto + +import org.opalj.br.fpcf.properties.alias.AliasSourceElement +import org.opalj.br.fpcf.properties.pointsto.PointsToSetLike +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.Property +import org.opalj.tac.fpcf.analyses.alias.AliasAnalysisContext +import org.opalj.tac.fpcf.analyses.alias.TacBasedAliasAnalysisState + +/** + * Encapsulates the current state of an points-to based alias analysis. + * + * It additionally contains the following information: + * + * - The current dependees for each of the [[AliasSourceElement]]. + * + * - The last points-to set for a given points-to entity that has been completely handled for am [[AliasSourceElement]]. + * + * - The current field dependees for each [[AliasSourceElement]]. A field dependee is a definition site of an uVar that is + * used to access the field. + */ +trait PointsToBasedAliasAnalysisState[ElementType, AliasSet <: AliasSetLike[ + ElementType, + AliasSet +], PointsToSet >: Null <: PointsToSetLike[_, _, PointsToSet]] + extends TacBasedAliasAnalysisState + with SetBasedAliasAnalysisState[ElementType, AliasSet] { + + private[this] var _element1Dependees = Set[Entity]() + private[this] var _element2Dependees = Set[Entity]() + + private[this] var _oldPointsToSets1: Map[Entity, PointsToSet] = Map.empty[Entity, PointsToSet] + private[this] var _oldPointsToSets2: Map[Entity, PointsToSet] = Map.empty[Entity, PointsToSet] + + private[this] var _field1Dependees = Set[Entity]() + private[this] var _field2Dependees = Set[Entity]() + + /** + * @return A set containing all elements that the first [[AliasSourceElement]] depends on. + */ + def element1Dependees: Set[Entity] = _element1Dependees + + /** + * @return A set containing all elements that the second [[AliasSourceElement]] depends on. + */ + def element2Dependees: Set[Entity] = _element2Dependees + + /** + * Adds the given entity property pair to the set of dependees of the given [[AliasSourceElement]] + * and the set of all dependees. + */ + def addElementDependency(ase: AliasSourceElement, dependency: EOptionP[Entity, Property])(implicit + context: AliasAnalysisContext + ): Unit = { + + addDependency(dependency) + + if (context.isElement1(ase)) { + _element1Dependees += dependency.e + } else { + _element2Dependees += dependency.e + } + } + + /** + * Removes the given entity property pair from element dependency set and the set of all dependencies. + */ + def removeElementDependency(dependency: EOptionP[Entity, Property]): Unit = { + + removeDependency(dependency) + + _element1Dependees -= dependency.e + _element2Dependees -= dependency.e + } + + /** + * @return `true` if the given [[AliasSourceElement]] has a dependency on the given entity property pair. + */ + def element1HasDependency(dependency: EOptionP[Entity, Property]): Boolean = { + element1Dependees.contains(dependency.e) + } + + /** + * @return `true` if the given [[AliasSourceElement]] has a dependency on the given entity property pair. + */ + def element2HasDependency(dependency: EOptionP[Entity, Property]): Boolean = { + element2Dependees.contains(dependency.e) + } + + /** + * @return The most recent points-to set of the given points-to entity that has been completely handled for the given [[AliasSourceElement]]. + */ + def oldPointsToSet(ase: AliasSourceElement, e: Entity)(implicit + context: AliasAnalysisContext + ): Option[PointsToSet] = { + if (context.isElement1(ase)) _oldPointsToSets1.get(e) + else _oldPointsToSets2.get(e) + } + + /** + * Updates the points-to set of the given points-to entity that has been completely handled for the given [[AliasSourceElement]]. + */ + def setOldPointsToSet(ase: AliasSourceElement, e: Entity, pointsToSet: PointsToSet)(implicit + context: AliasAnalysisContext + ): Unit = { + if (context.isElement1(ase)) _oldPointsToSets1 += e -> pointsToSet + else _oldPointsToSets2 += e -> pointsToSet + } + + /** + * adds the given entity property pair to the set of field dependees of the first [[AliasSourceElement]]. + */ + def addField1Dependency(dependency: EOptionP[Entity, Property]): Unit = { + _field1Dependees += dependency.e + } + + /** + * adds the given entity property pair to the set of field dependees of the second [[AliasSourceElement]]. + */ + def addField2Dependency(dependency: EOptionP[Entity, Property]): Unit = { + _field2Dependees += dependency.e + } + + /** + * Adds the given entity property pair to the set of field dependees of the given [[AliasSourceElement]]. + */ + def addFieldDependency(ase: AliasSourceElement, dependency: EOptionP[Entity, Property])(implicit + context: AliasAnalysisContext + ): Unit = { + if (context.isElement1(ase)) _field1Dependees += dependency.e + else _field2Dependees += dependency.e + } + + /** + * Removes the given entity property pair from the set of field dependees of all [[AliasSourceElement]]s. + */ + def removeFieldDependency(dependency: EOptionP[Entity, Property]): Unit = { + _field1Dependees -= dependency.e + _field2Dependees -= dependency.e + } + + /** + * @return `true` if the first [[AliasSourceElement]] has a field dependency on the given entity property pair. + */ + def field1HasDependency(dependency: EOptionP[Entity, Property]): Boolean = { + _field1Dependees.contains(dependency.e) + } + + /** + * @return `true` if the second [[AliasSourceElement]] has a field dependency on the given entity property pair. + */ + def field2HasDependency(dependency: EOptionP[Entity, Property]): Boolean = { + _field2Dependees.contains(dependency.e) + } + +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/pointsto/TypePointsToBasedAliasAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/pointsto/TypePointsToBasedAliasAnalysis.scala new file mode 100644 index 0000000000..dafb00016b --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/alias/pointsto/TypePointsToBasedAliasAnalysis.scala @@ -0,0 +1,86 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package alias +package pointsto + +import org.opalj.br.ReferenceType +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.BasicFPCFLazyAnalysisScheduler +import org.opalj.br.fpcf.FPCFAnalysis +import org.opalj.br.fpcf.properties.alias.Alias +import org.opalj.br.fpcf.properties.alias.AliasSourceElement +import org.opalj.br.fpcf.properties.pointsto.TypeBasedPointsToSet +import org.opalj.fpcf.Entity +import org.opalj.fpcf.PropertyBounds +import org.opalj.fpcf.PropertyStore +import org.opalj.tac.fpcf.analyses.alias.TacBasedAliasAnalysis +import org.opalj.tac.fpcf.analyses.alias.TypeBasedAliasAnalysis +import org.opalj.tac.fpcf.analyses.alias.TypeBasedAliasAnalysisState +import org.opalj.tac.fpcf.analyses.alias.TypeBasedAliasSet +import org.opalj.tac.fpcf.analyses.pointsto.AbstractPointsToAnalysis +import org.opalj.tac.fpcf.analyses.pointsto.TypeBasedAnalysis + +/** + * An alias analysis based on points-to information that contain the possible [[ReferenceType]]s of an element. + * @param project The project + */ +class TypePointsToBasedAliasAnalysis(final val project: SomeProject) + extends AbstractPointsToBasedAliasAnalysis + with AbstractPointsToAnalysis + with TypeBasedAnalysis + with TypeBasedAliasAnalysis + with TacBasedAliasAnalysis { + + override protected[this] type AnalysisState = TypePointsToBasedAliasAnalysisState + + override protected[this] def handlePointsToSetElement( + ase: AliasSourceElement, + pointsToEntity: Entity, + element: ReferenceType + )( + implicit + state: AnalysisState, + context: AnalysisContext + ): Unit = { + state.addPointsTo(ase, element) + } + /** + * Creates the state to use for the computation. + */ + override protected[this] def createState: AnalysisState = new TypePointsToBasedAliasAnalysisState +} + +/** + * A scheduler for a lazy, type, points-to based alias analysis. + */ +object LazyTypePointsToBasedAliasAnalysisScheduler extends PointsToBasedAliasAnalysisScheduler + with BasicFPCFLazyAnalysisScheduler { + override def derivesLazily: Some[PropertyBounds] = Some(derivedProperty) + + override def register( + project: SomeProject, + propertyStore: PropertyStore, + i: LazyTypePointsToBasedAliasAnalysisScheduler.InitializationData + ): FPCFAnalysis = { + + val analysis = new TypePointsToBasedAliasAnalysis(project) + + propertyStore.registerLazyPropertyComputation( + Alias.key, + analysis.determineAlias + ) + + analysis + } +} + +/** + * The state class used by an [[TypePointsToBasedAliasAnalysis]]. + * + * @see [[PointsToBasedAliasAnalysisState]] + */ +class TypePointsToBasedAliasAnalysisState extends TypeBasedAliasAnalysisState + with PointsToBasedAliasAnalysisState[ReferenceType, TypeBasedAliasSet, TypeBasedPointsToSet] {}