From 427c55a1116d4f3858609c216b7dfbad1c9ca7ed Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Thu, 3 Dec 2020 07:24:54 -0800 Subject: [PATCH 001/113] temp commit --- .../tests/tainting/SideEffectsOnlyTest.java | 14 +++++++++++++ .../checkerframework/dataflow/qual/Pure.java | 4 +++- .../dataflow/qual/SideEffectsOnly.java | 16 ++++++++++++++ .../dataflow/util/PurityUtils.java | 21 ++++++++++++++++++- 4 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 checker/tests/tainting/SideEffectsOnlyTest.java create mode 100644 dataflow/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java diff --git a/checker/tests/tainting/SideEffectsOnlyTest.java b/checker/tests/tainting/SideEffectsOnlyTest.java new file mode 100644 index 00000000000..e8b6a486d8b --- /dev/null +++ b/checker/tests/tainting/SideEffectsOnlyTest.java @@ -0,0 +1,14 @@ +package tainting; + +import org.checkerframework.checker.tainting.qual.Untainted; + +public class SideEffectsOnlyTest { + void test(@Untainted Object x) { + method(x); + method1(x); + } + + void method(Object x) {} + + void method1(@Untainted Object x) {} +} diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/qual/Pure.java b/dataflow/src/main/java/org/checkerframework/dataflow/qual/Pure.java index d6c309b73a1..082b05b1085 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/qual/Pure.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/qual/Pure.java @@ -29,6 +29,8 @@ public static enum Kind { SIDE_EFFECT_FREE, /** The method returns exactly the same value when called in the same environment. */ - DETERMINISTIC + DETERMINISTIC, + + SIDE_EFFECTS_ONLY } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java b/dataflow/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java new file mode 100644 index 00000000000..79a685269f7 --- /dev/null +++ b/dataflow/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java @@ -0,0 +1,16 @@ +package org.checkerframework.dataflow.qual; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.checkerframework.framework.qual.JavaExpression; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) +public @interface SideEffectsOnly { + @JavaExpression + public String[] value(); +} diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java index 782256af229..cbfdfe55408 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java @@ -8,6 +8,7 @@ import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.dataflow.qual.Pure.Kind; import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.dataflow.qual.SideEffectsOnly; import org.checkerframework.javacutil.AnnotationProvider; import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TreeUtils; @@ -98,6 +99,19 @@ public static boolean isSideEffectFree(AnnotationProvider provider, Element meth return kinds.contains(Kind.SIDE_EFFECT_FREE); } + public static boolean isSideEffectsOnly(AnnotationProvider provider, MethodTree methodTree) { + Element methodElement = TreeUtils.elementFromTree(methodTree); + if (methodElement == null) { + throw new BugInCF("Could not find element for tree: " + methodTree); + } + return isSideEffectsOnly(provider, methodElement); + } + + public static boolean isSideEffectsOnly(AnnotationProvider provider, Element methodElement) { + EnumSet kinds = getPurityKinds(provider, methodElement); + return kinds.contains(Kind.SIDE_EFFECTS_ONLY); + } + /** * Returns the types of purity of the method {@code methodTree}. * @@ -129,9 +143,11 @@ public static EnumSet getPurityKinds( provider.getDeclAnnotation(methodElement, SideEffectFree.class); AnnotationMirror detAnnotation = provider.getDeclAnnotation(methodElement, Deterministic.class); + AnnotationMirror sefOnlyAnnotation = + provider.getDeclAnnotation(methodElement, SideEffectsOnly.class); if (pureAnnotation != null) { - return EnumSet.of(Kind.DETERMINISTIC, Kind.SIDE_EFFECT_FREE); + return EnumSet.of(Kind.DETERMINISTIC, Kind.SIDE_EFFECT_FREE, Kind.SIDE_EFFECTS_ONLY); } EnumSet result = EnumSet.noneOf(Pure.Kind.class); if (sefAnnotation != null) { @@ -140,6 +156,9 @@ public static EnumSet getPurityKinds( if (detAnnotation != null) { result.add(Kind.DETERMINISTIC); } + if (sefOnlyAnnotation != null) { + result.add(Kind.SIDE_EFFECTS_ONLY); + } return result; } } From 0ad09bb7f1a97d5dd27ec710390be346a28ac8f0 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Thu, 3 Dec 2020 15:48:14 -0800 Subject: [PATCH 002/113] side effects only checker --- .../dataflow/util/PurityUtils.java | 12 ++++ .../framework/flow/CFAbstractStore.java | 60 +++++++++++++++---- .../test/junit/SideEffectsOnlyTest.java | 20 +++++++ .../SideEffectsOnlyChecker.java | 13 ++++ .../sideeffectsonly/qual/Refined.java | 14 +++++ .../sideeffectsonly/qual/Unrefined.java | 16 +++++ .../sideeffectsonly/SideEffectsOnlyTest.java | 25 ++++++++ .../sideeffectsonly/SideEffectsTest.java | 21 +++++++ 8 files changed, 169 insertions(+), 12 deletions(-) create mode 100644 framework/src/test/java/org/checkerframework/framework/test/junit/SideEffectsOnlyTest.java create mode 100644 framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/SideEffectsOnlyChecker.java create mode 100644 framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Refined.java create mode 100644 framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Unrefined.java create mode 100644 framework/tests/sideeffectsonly/SideEffectsOnlyTest.java create mode 100644 framework/tests/sideeffectsonly/SideEffectsTest.java diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java index cbfdfe55408..e4aa6e38c80 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java @@ -2,8 +2,11 @@ import com.sun.source.tree.MethodTree; import java.util.EnumSet; +import java.util.Map; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; import org.checkerframework.dataflow.qual.Deterministic; import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.dataflow.qual.Pure.Kind; @@ -112,6 +115,15 @@ public static boolean isSideEffectsOnly(AnnotationProvider provider, Element met return kinds.contains(Kind.SIDE_EFFECTS_ONLY); } + public static Map + getSideEffectsOnlyValues(AnnotationProvider provider, Element methodElement) { + AnnotationMirror sefOnlyAnnotation = + provider.getDeclAnnotation(methodElement, SideEffectsOnly.class); + Map elementValues = + sefOnlyAnnotation.getElementValues(); + return elementValues; + } + /** * Returns the types of purity of the method {@code methodTree}. * diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index 6f477961316..150e96b82b1 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -1,15 +1,8 @@ package org.checkerframework.framework.flow; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.StringJoiner; +import java.util.*; import java.util.concurrent.atomic.AtomicLong; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Name; +import javax.lang.model.element.*; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; import org.checkerframework.checker.nullness.qual.Nullable; @@ -171,6 +164,16 @@ protected boolean isSideEffectFree( return PurityUtils.isSideEffectFree(atypeFactory, method); } + protected boolean isSideEffectsOnly( + AnnotatedTypeFactory atypeFactory, ExecutableElement method) { + return PurityUtils.isSideEffectsOnly(atypeFactory, method); + } + + protected Map getSideEffectsOnlyValues( + AnnotatedTypeFactory atypeFactory, ExecutableElement method) { + return PurityUtils.getSideEffectsOnlyValues(atypeFactory, method); + } + /* --------------------------------------------------------- */ /* Handling of fields */ /* --------------------------------------------------------- */ @@ -196,6 +199,29 @@ public void updateForMethodCall( MethodInvocationNode n, AnnotatedTypeFactory atypeFactory, V val) { ExecutableElement method = n.getTarget().getMethod(); + List sideEffectExpressions = null; + if (isSideEffectsOnly(atypeFactory, method)) { + Map valmap = + getSideEffectsOnlyValues(atypeFactory, method); + Object value = null; + for (ExecutableElement elem : valmap.keySet()) { + if (elem.getSimpleName().contentEquals("value")) { + value = valmap.get(elem).getValue(); + break; + } + } + if (value instanceof List) { + AnnotationMirror sefOnlyAnnotation = + atypeFactory.getDeclAnnotation( + method, org.checkerframework.dataflow.qual.SideEffectsOnly.class); + sideEffectExpressions = + AnnotationUtils.getElementValueArray( + sefOnlyAnnotation, "value", String.class, true); + } else if (value instanceof String) { + sideEffectExpressions = Collections.singletonList((String) value); + } + } + // case 1: remove information if necessary if (!(analysis.checker.hasOption("assumeSideEffectFree") || analysis.checker.hasOption("assumePure") @@ -208,9 +234,19 @@ public void updateForMethodCall( // TODO: Also remove if any element/argument to the annotation is not // isUnmodifiableByOtherCode. Example: @KeyFor("valueThatCanBeMutated"). if (sideEffectsUnrefineAliases) { - localVariableValues - .entrySet() - .removeIf(e -> !e.getKey().isUnmodifiableByOtherCode()); + if (sideEffectExpressions != null) { + final List expressionsToRemove = sideEffectExpressions; + localVariableValues + .entrySet() + .removeIf( + e -> + expressionsToRemove.contains(e.getKey().toString()) + && !e.getKey().isUnmodifiableByOtherCode()); + } else { + localVariableValues + .entrySet() + .removeIf(e -> !e.getKey().isUnmodifiableByOtherCode()); + } } // update this value diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/SideEffectsOnlyTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/SideEffectsOnlyTest.java new file mode 100644 index 00000000000..351e7eeed37 --- /dev/null +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/SideEffectsOnlyTest.java @@ -0,0 +1,20 @@ +package org.checkerframework.framework.test.junit; + +import java.io.File; +import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.checkerframework.framework.testchecker.sideeffectsonly.SideEffectsOnlyChecker; +import org.junit.runners.Parameterized.Parameters; + +public class SideEffectsOnlyTest extends CheckerFrameworkPerDirectoryTest { + + /** @param testFiles the files containing test code, which will be type-checked */ + public SideEffectsOnlyTest(List testFiles) { + super(testFiles, SideEffectsOnlyChecker.class, "sideeffectsonly", "-Anomsgtext"); + } + + @Parameters + public static String[] getTestDirs() { + return new String[] {"sideeffectsonly"}; + } +} diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/SideEffectsOnlyChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/SideEffectsOnlyChecker.java new file mode 100644 index 00000000000..05b9b9dc446 --- /dev/null +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/SideEffectsOnlyChecker.java @@ -0,0 +1,13 @@ +package org.checkerframework.framework.testchecker.sideeffectsonly; + +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; + +public class SideEffectsOnlyChecker extends BaseTypeChecker { + @Override + public GenericAnnotatedTypeFactory getTypeFactory() { + GenericAnnotatedTypeFactory result = super.getTypeFactory(); + result.sideEffectsUnrefineAliases = true; + return result; + } +} diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Refined.java b/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Refined.java new file mode 100644 index 00000000000..908b8b0025f --- /dev/null +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Refined.java @@ -0,0 +1,14 @@ +package org.checkerframework.framework.testchecker.sideeffectsonly.qual; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) +@SubtypeOf({Unrefined.class}) +public @interface Refined {} diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Unrefined.java b/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Unrefined.java new file mode 100644 index 00000000000..f7c8cdb9bc4 --- /dev/null +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Unrefined.java @@ -0,0 +1,16 @@ +package org.checkerframework.framework.testchecker.sideeffectsonly.qual; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) +@DefaultQualifierInHierarchy +@SubtypeOf({}) +public @interface Unrefined {} diff --git a/framework/tests/sideeffectsonly/SideEffectsOnlyTest.java b/framework/tests/sideeffectsonly/SideEffectsOnlyTest.java new file mode 100644 index 00000000000..6dd951e68c1 --- /dev/null +++ b/framework/tests/sideeffectsonly/SideEffectsOnlyTest.java @@ -0,0 +1,25 @@ +package sideeffectsonly; + +import org.checkerframework.dataflow.qual.SideEffectsOnly; +import org.checkerframework.framework.qual.EnsuresQualifier; +import org.checkerframework.framework.testchecker.sideeffectsonly.qual.Refined; + +public class SideEffectsOnlyTest { + void test(Object x) { + method(x); + method1(x); + method2(x); + } + + @EnsuresQualifier( + expression = "#1", + qualifier = + org.checkerframework.framework.testchecker.sideeffectsonly.qual.Refined.class) + // :: error: contracts.postcondition.not.satisfied + void method(Object x) {} + + @SideEffectsOnly({"this, x"}) + void method1(@Refined Object x) {} + + void method2(@Refined Object x) {} +} diff --git a/framework/tests/sideeffectsonly/SideEffectsTest.java b/framework/tests/sideeffectsonly/SideEffectsTest.java new file mode 100644 index 00000000000..38e003adb8e --- /dev/null +++ b/framework/tests/sideeffectsonly/SideEffectsTest.java @@ -0,0 +1,21 @@ +package sideeffectsonly; + +import org.checkerframework.framework.qual.EnsuresQualifier; +import org.checkerframework.framework.testchecker.sideeffectsonly.qual.Refined; + +public class SideEffectsTest { + void test(Object x) { + method(x); + method1(x); + // :: error: argument.type.incompatible + method2(x); + } + + @EnsuresQualifier(expression = "#1", qualifier = Refined.class) + // :: error: contracts.postcondition.not.satisfied + void method(Object x) {} + + void method1(@Refined Object x) {} + + void method2(@Refined Object x) {} +} From 15ca2db9752964409b56eb392ad0cd5fc949bae5 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Thu, 3 Dec 2020 16:09:33 -0800 Subject: [PATCH 003/113] side effects only for this and field values --- .../framework/flow/CFAbstractStore.java | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index 150e96b82b1..1d004969075 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -251,12 +251,28 @@ public void updateForMethodCall( // update this value if (sideEffectsUnrefineAliases) { - thisValue = null; + if (sideEffectExpressions != null) { + if (sideEffectExpressions.contains("this")) { + thisValue = null; + } + } else { + thisValue = null; + } } // update field values if (sideEffectsUnrefineAliases) { - fieldValues.entrySet().removeIf(e -> !e.getKey().isUnmodifiableByOtherCode()); + if (sideEffectExpressions != null) { + final List expressionsToRemove = sideEffectExpressions; + fieldValues + .entrySet() + .removeIf( + e -> + expressionsToRemove.contains(e.getKey().toString()) + && !e.getKey().isUnmodifiableByOtherCode()); + } else { + fieldValues.entrySet().removeIf(e -> !e.getKey().isUnmodifiableByOtherCode()); + } } else { Map newFieldValues = new HashMap<>(); for (Map.Entry e : fieldValues.entrySet()) { From 2857929f9802081cc86a53242f400e6bbb9edb29 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Fri, 4 Dec 2020 08:42:22 -0800 Subject: [PATCH 004/113] more tests --- .../sideeffectsonly/SideEffectsMultiple.java | 26 +++++++++++++++++++ .../sideeffectsonly/SideEffectsOnlyTest.java | 9 ++++++- 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 framework/tests/sideeffectsonly/SideEffectsMultiple.java diff --git a/framework/tests/sideeffectsonly/SideEffectsMultiple.java b/framework/tests/sideeffectsonly/SideEffectsMultiple.java new file mode 100644 index 00000000000..af9561f0580 --- /dev/null +++ b/framework/tests/sideeffectsonly/SideEffectsMultiple.java @@ -0,0 +1,26 @@ +package sideeffectsonly; + +import org.checkerframework.dataflow.qual.SideEffectsOnly; +import org.checkerframework.framework.qual.EnsuresQualifier; +import org.checkerframework.framework.testchecker.sideeffectsonly.qual.Refined; + +public class SideEffectsMultiple { + void test(Object x) { + method(x); + method1(x); + // :: error: argument.type.incompatible + method2(x); + } + + @EnsuresQualifier( + expression = "#1", + qualifier = + org.checkerframework.framework.testchecker.sideeffectsonly.qual.Refined.class) + // :: error: contracts.postcondition.not.satisfied + void method(Object x) {} + + @SideEffectsOnly({"this", "x"}) + void method1(@Refined Object x) {} + + void method2(@Refined Object x) {} +} diff --git a/framework/tests/sideeffectsonly/SideEffectsOnlyTest.java b/framework/tests/sideeffectsonly/SideEffectsOnlyTest.java index 6dd951e68c1..9f9c293264e 100644 --- a/framework/tests/sideeffectsonly/SideEffectsOnlyTest.java +++ b/framework/tests/sideeffectsonly/SideEffectsOnlyTest.java @@ -8,7 +8,10 @@ public class SideEffectsOnlyTest { void test(Object x) { method(x); method1(x); + method3(x); method2(x); + // :: error: argument.type.incompatible + method3(x); } @EnsuresQualifier( @@ -18,8 +21,12 @@ void test(Object x) { // :: error: contracts.postcondition.not.satisfied void method(Object x) {} - @SideEffectsOnly({"this, x"}) + @SideEffectsOnly({"this"}) void method1(@Refined Object x) {} + @SideEffectsOnly({"x"}) void method2(@Refined Object x) {} + + @SideEffectsOnly({"this"}) + void method3(@Refined Object x) {} } From 21dc0a4a6e3219586fb00117aee428a7c24e2a93 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Fri, 4 Dec 2020 10:39:50 -0800 Subject: [PATCH 005/113] add null checks --- .../dataflow/util/PurityUtils.java | 3 ++ .../framework/flow/CFAbstractStore.java | 33 ++++++++++--------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java index e4aa6e38c80..bba0c2f7ba9 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java @@ -119,6 +119,9 @@ public static boolean isSideEffectsOnly(AnnotationProvider provider, Element met getSideEffectsOnlyValues(AnnotationProvider provider, Element methodElement) { AnnotationMirror sefOnlyAnnotation = provider.getDeclAnnotation(methodElement, SideEffectsOnly.class); + if (sefOnlyAnnotation == null) { + return null; + } Map elementValues = sefOnlyAnnotation.getElementValues(); return elementValues; diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index 1d004969075..ba13320e375 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -203,22 +203,25 @@ public void updateForMethodCall( if (isSideEffectsOnly(atypeFactory, method)) { Map valmap = getSideEffectsOnlyValues(atypeFactory, method); - Object value = null; - for (ExecutableElement elem : valmap.keySet()) { - if (elem.getSimpleName().contentEquals("value")) { - value = valmap.get(elem).getValue(); - break; + if (valmap != null) { + Object value = null; + for (ExecutableElement elem : valmap.keySet()) { + if (elem.getSimpleName().contentEquals("value")) { + value = valmap.get(elem).getValue(); + break; + } + } + if (value instanceof List) { + AnnotationMirror sefOnlyAnnotation = + atypeFactory.getDeclAnnotation( + method, + org.checkerframework.dataflow.qual.SideEffectsOnly.class); + sideEffectExpressions = + AnnotationUtils.getElementValueArray( + sefOnlyAnnotation, "value", String.class, true); + } else if (value instanceof String) { + sideEffectExpressions = Collections.singletonList((String) value); } - } - if (value instanceof List) { - AnnotationMirror sefOnlyAnnotation = - atypeFactory.getDeclAnnotation( - method, org.checkerframework.dataflow.qual.SideEffectsOnly.class); - sideEffectExpressions = - AnnotationUtils.getElementValueArray( - sefOnlyAnnotation, "value", String.class, true); - } else if (value instanceof String) { - sideEffectExpressions = Collections.singletonList((String) value); } } From ef59d5e8aee7c6af8ad1fa44a0b40e7290cbf00c Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Fri, 4 Dec 2020 12:00:02 -0800 Subject: [PATCH 006/113] side effects only javadoc --- .../checkerframework/dataflow/qual/Pure.java | 4 ++++ .../dataflow/qual/SideEffectsOnly.java | 8 +++++++ .../dataflow/util/PurityUtils.java | 22 +++++++++++++++++++ .../framework/flow/CFAbstractStore.java | 15 +++++++++++++ 4 files changed, 49 insertions(+) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/qual/Pure.java b/dataflow/src/main/java/org/checkerframework/dataflow/qual/Pure.java index 082b05b1085..327f0cb0760 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/qual/Pure.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/qual/Pure.java @@ -31,6 +31,10 @@ public static enum Kind { /** The method returns exactly the same value when called in the same environment. */ DETERMINISTIC, + /** + * The method only side effects the expressions specified as annotation values of + * {@code @SideEffectsOnly}. + */ SIDE_EFFECTS_ONLY } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java b/dataflow/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java index 79a685269f7..fd2a4303838 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java @@ -7,10 +7,18 @@ import java.lang.annotation.Target; import org.checkerframework.framework.qual.JavaExpression; +/** + * This method declaration annotation can be used to specify the expressions that a method side + * effects. In other words, the method only side effects those expressions that are supplied as + * annotation values to {@code @SideEffectsOnly}. + * + * @checker_framework.manual #type-refinement-purity Side effects only + */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) public @interface SideEffectsOnly { + /** The expressions that this method side effects. */ @JavaExpression public String[] value(); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java index bba0c2f7ba9..1eb28e09665 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java @@ -102,6 +102,13 @@ public static boolean isSideEffectFree(AnnotationProvider provider, Element meth return kinds.contains(Kind.SIDE_EFFECT_FREE); } + /** + * Is the method annotated with the declaration annotation {@code @SideEffectsOnly}. + * + * @param provider how to get annotations + * @param methodTree a method to test + * @return whether the method has the declaration annotation {@code @SideEffectsOnly} + */ public static boolean isSideEffectsOnly(AnnotationProvider provider, MethodTree methodTree) { Element methodElement = TreeUtils.elementFromTree(methodTree); if (methodElement == null) { @@ -110,11 +117,26 @@ public static boolean isSideEffectsOnly(AnnotationProvider provider, MethodTree return isSideEffectsOnly(provider, methodElement); } + /** + * Is the method annotated with the declaration annotation {@code @SideEffectsOnly}. + * + * @param provider how to get annotations + * @param methodElement a method to test + * @return whether the method has the declaration annotation {@code @SideEffectsOnly} + */ public static boolean isSideEffectsOnly(AnnotationProvider provider, Element methodElement) { EnumSet kinds = getPurityKinds(provider, methodElement); return kinds.contains(Kind.SIDE_EFFECTS_ONLY); } + /** + * Returns the annotation values of {@code @SideEffectsOnly}. + * + * @param provider how to get annotations + * @param methodElement a method to test + * @return values of annotation elements of {@code @SideEffectsOnly} if the method has this + * annotation, null otherwise + */ public static Map getSideEffectsOnlyValues(AnnotationProvider provider, Element methodElement) { AnnotationMirror sefOnlyAnnotation = diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index ba13320e375..f9c619ea9cb 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -164,11 +164,26 @@ protected boolean isSideEffectFree( return PurityUtils.isSideEffectFree(atypeFactory, method); } + /** + * Indicates whether the method has the declaration annotation {@code @SideEffectsOnly}. + * + * @param atypeFactory the type factory used to retrieve annotations on the method element + * @param method the method element + * @return whether the method is annotated with {@code @SideEffectsOnly} + */ protected boolean isSideEffectsOnly( AnnotatedTypeFactory atypeFactory, ExecutableElement method) { return PurityUtils.isSideEffectsOnly(atypeFactory, method); } + /** + * Returns the annotation values of {@code @SideEffectsOnly}. + * + * @param atypeFactory the type factory used to retrieve annotations on the method element + * @param method the method element + * @return values of annotation elements of {@code @SideEffectsOnly} if the method has this + * annotation + */ protected Map getSideEffectsOnlyValues( AnnotatedTypeFactory atypeFactory, ExecutableElement method) { return PurityUtils.getSideEffectsOnlyValues(atypeFactory, method); From 40fdfd8279f01db9610df01ba337ba85737a2127 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Fri, 4 Dec 2020 13:20:42 -0800 Subject: [PATCH 007/113] purity suggestions test --- .../org/checkerframework/common/basetype/BaseTypeVisitor.java | 4 +++- .../org/checkerframework/common/basetype/messages.properties | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index dfad7db7437..e2fa5ea5bcb 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -816,12 +816,14 @@ protected void checkPurity(MethodTree node) { additionalKinds.remove(Pure.Kind.DETERMINISTIC); } if (!additionalKinds.isEmpty()) { - if (additionalKinds.size() == 2) { + if (additionalKinds.size() == 3) { checker.reportWarning(node, "purity.more.pure", node.getName()); } else if (additionalKinds.contains(Pure.Kind.SIDE_EFFECT_FREE)) { checker.reportWarning(node, "purity.more.sideeffectfree", node.getName()); } else if (additionalKinds.contains(Pure.Kind.DETERMINISTIC)) { checker.reportWarning(node, "purity.more.deterministic", node.getName()); + } else if (additionalKinds.contains(Pure.Kind.SIDE_EFFECTS_ONLY)) { + // Cannot suggest side effects only } else { assert false : "BaseTypeVisitor reached undesirable state"; } diff --git a/framework/src/main/java/org/checkerframework/common/basetype/messages.properties b/framework/src/main/java/org/checkerframework/common/basetype/messages.properties index 314d6cdf3bb..617dbc2bf22 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/messages.properties +++ b/framework/src/main/java/org/checkerframework/common/basetype/messages.properties @@ -75,6 +75,7 @@ purity.not.sideeffectfree.call=call to side-effecting %s not allowed in side-eff purity.more.deterministic=the method %s could be declared as @Deterministic purity.more.pure=the method %s could be declared as @Pure purity.more.sideeffectfree=the method %s could be declared as @SideEffectFree +purity.more.sideeffectsonly=the method %s could be declared as @SideEffectsOnly flowexpr.parse.index.too.big=the method does not have a parameter %s flowexpr.parse.error=cannot parse the expression %s From ac6d8ca8e3e73db732f0530b7a08c3d24f8df441 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Fri, 4 Dec 2020 13:41:53 -0800 Subject: [PATCH 008/113] nullness check --- .../java/org/checkerframework/dataflow/util/PurityUtils.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java index 1eb28e09665..e7697062ac5 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java @@ -7,6 +7,7 @@ import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; +import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.Deterministic; import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.dataflow.qual.Pure.Kind; @@ -137,7 +138,7 @@ public static boolean isSideEffectsOnly(AnnotationProvider provider, Element met * @return values of annotation elements of {@code @SideEffectsOnly} if the method has this * annotation, null otherwise */ - public static Map + public static @Nullable Map getSideEffectsOnlyValues(AnnotationProvider provider, Element methodElement) { AnnotationMirror sefOnlyAnnotation = provider.getDeclAnnotation(methodElement, SideEffectsOnly.class); From df56c0d0a3c246546ac244651e0189eac9b8ad47 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Fri, 4 Dec 2020 14:05:38 -0800 Subject: [PATCH 009/113] javadoc --- .../org/checkerframework/dataflow/qual/SideEffectsOnly.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java b/dataflow/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java index fd2a4303838..505c053a6b7 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java @@ -18,7 +18,11 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) public @interface SideEffectsOnly { - /** The expressions that this method side effects. */ + /** + * The expressions that this method side effects. + * + * @return Java expressions that are side-effected by this method + */ @JavaExpression public String[] value(); } From 48d2d42b2d148ae556844eb96400c377021594fc Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Fri, 4 Dec 2020 16:07:23 -0800 Subject: [PATCH 010/113] remove sideeffectsonly from purity --- .../dataflow/util/PurityUtils.java | 59 +------------------ .../common/basetype/BaseTypeVisitor.java | 4 +- .../common/basetype/messages.properties | 1 - .../framework/flow/CFAbstractStore.java | 21 +++++-- 4 files changed, 19 insertions(+), 66 deletions(-) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java index e7697062ac5..782256af229 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/util/PurityUtils.java @@ -2,17 +2,12 @@ import com.sun.source.tree.MethodTree; import java.util.EnumSet; -import java.util.Map; import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.Deterministic; import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.dataflow.qual.Pure.Kind; import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.dataflow.qual.SideEffectsOnly; import org.checkerframework.javacutil.AnnotationProvider; import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TreeUtils; @@ -103,53 +98,6 @@ public static boolean isSideEffectFree(AnnotationProvider provider, Element meth return kinds.contains(Kind.SIDE_EFFECT_FREE); } - /** - * Is the method annotated with the declaration annotation {@code @SideEffectsOnly}. - * - * @param provider how to get annotations - * @param methodTree a method to test - * @return whether the method has the declaration annotation {@code @SideEffectsOnly} - */ - public static boolean isSideEffectsOnly(AnnotationProvider provider, MethodTree methodTree) { - Element methodElement = TreeUtils.elementFromTree(methodTree); - if (methodElement == null) { - throw new BugInCF("Could not find element for tree: " + methodTree); - } - return isSideEffectsOnly(provider, methodElement); - } - - /** - * Is the method annotated with the declaration annotation {@code @SideEffectsOnly}. - * - * @param provider how to get annotations - * @param methodElement a method to test - * @return whether the method has the declaration annotation {@code @SideEffectsOnly} - */ - public static boolean isSideEffectsOnly(AnnotationProvider provider, Element methodElement) { - EnumSet kinds = getPurityKinds(provider, methodElement); - return kinds.contains(Kind.SIDE_EFFECTS_ONLY); - } - - /** - * Returns the annotation values of {@code @SideEffectsOnly}. - * - * @param provider how to get annotations - * @param methodElement a method to test - * @return values of annotation elements of {@code @SideEffectsOnly} if the method has this - * annotation, null otherwise - */ - public static @Nullable Map - getSideEffectsOnlyValues(AnnotationProvider provider, Element methodElement) { - AnnotationMirror sefOnlyAnnotation = - provider.getDeclAnnotation(methodElement, SideEffectsOnly.class); - if (sefOnlyAnnotation == null) { - return null; - } - Map elementValues = - sefOnlyAnnotation.getElementValues(); - return elementValues; - } - /** * Returns the types of purity of the method {@code methodTree}. * @@ -181,11 +129,9 @@ public static EnumSet getPurityKinds( provider.getDeclAnnotation(methodElement, SideEffectFree.class); AnnotationMirror detAnnotation = provider.getDeclAnnotation(methodElement, Deterministic.class); - AnnotationMirror sefOnlyAnnotation = - provider.getDeclAnnotation(methodElement, SideEffectsOnly.class); if (pureAnnotation != null) { - return EnumSet.of(Kind.DETERMINISTIC, Kind.SIDE_EFFECT_FREE, Kind.SIDE_EFFECTS_ONLY); + return EnumSet.of(Kind.DETERMINISTIC, Kind.SIDE_EFFECT_FREE); } EnumSet result = EnumSet.noneOf(Pure.Kind.class); if (sefAnnotation != null) { @@ -194,9 +140,6 @@ public static EnumSet getPurityKinds( if (detAnnotation != null) { result.add(Kind.DETERMINISTIC); } - if (sefOnlyAnnotation != null) { - result.add(Kind.SIDE_EFFECTS_ONLY); - } return result; } } diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index e2fa5ea5bcb..dfad7db7437 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -816,14 +816,12 @@ protected void checkPurity(MethodTree node) { additionalKinds.remove(Pure.Kind.DETERMINISTIC); } if (!additionalKinds.isEmpty()) { - if (additionalKinds.size() == 3) { + if (additionalKinds.size() == 2) { checker.reportWarning(node, "purity.more.pure", node.getName()); } else if (additionalKinds.contains(Pure.Kind.SIDE_EFFECT_FREE)) { checker.reportWarning(node, "purity.more.sideeffectfree", node.getName()); } else if (additionalKinds.contains(Pure.Kind.DETERMINISTIC)) { checker.reportWarning(node, "purity.more.deterministic", node.getName()); - } else if (additionalKinds.contains(Pure.Kind.SIDE_EFFECTS_ONLY)) { - // Cannot suggest side effects only } else { assert false : "BaseTypeVisitor reached undesirable state"; } diff --git a/framework/src/main/java/org/checkerframework/common/basetype/messages.properties b/framework/src/main/java/org/checkerframework/common/basetype/messages.properties index 617dbc2bf22..314d6cdf3bb 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/messages.properties +++ b/framework/src/main/java/org/checkerframework/common/basetype/messages.properties @@ -75,7 +75,6 @@ purity.not.sideeffectfree.call=call to side-effecting %s not allowed in side-eff purity.more.deterministic=the method %s could be declared as @Deterministic purity.more.pure=the method %s could be declared as @Pure purity.more.sideeffectfree=the method %s could be declared as @SideEffectFree -purity.more.sideeffectsonly=the method %s could be declared as @SideEffectsOnly flowexpr.parse.index.too.big=the method does not have a parameter %s flowexpr.parse.error=cannot parse the expression %s diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index f9c619ea9cb..122785a0217 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -24,6 +24,7 @@ import org.checkerframework.dataflow.expression.Receiver; import org.checkerframework.dataflow.expression.ThisReference; import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.dataflow.qual.SideEffectsOnly; import org.checkerframework.dataflow.util.PurityUtils; import org.checkerframework.framework.qual.MonotonicQualifier; import org.checkerframework.framework.type.AnnotatedTypeFactory; @@ -173,7 +174,12 @@ protected boolean isSideEffectFree( */ protected boolean isSideEffectsOnly( AnnotatedTypeFactory atypeFactory, ExecutableElement method) { - return PurityUtils.isSideEffectsOnly(atypeFactory, method); + AnnotationMirror sefOnlyAnnotation = + atypeFactory.getDeclAnnotation(method, SideEffectsOnly.class); + if (sefOnlyAnnotation != null) { + return true; + } + return false; } /** @@ -184,9 +190,16 @@ protected boolean isSideEffectsOnly( * @return values of annotation elements of {@code @SideEffectsOnly} if the method has this * annotation */ - protected Map getSideEffectsOnlyValues( - AnnotatedTypeFactory atypeFactory, ExecutableElement method) { - return PurityUtils.getSideEffectsOnlyValues(atypeFactory, method); + protected @Nullable Map + getSideEffectsOnlyValues(AnnotatedTypeFactory atypeFactory, ExecutableElement method) { + AnnotationMirror sefOnlyAnnotation = + atypeFactory.getDeclAnnotation(method, SideEffectsOnly.class); + if (sefOnlyAnnotation == null) { + return null; + } + Map elementValues = + sefOnlyAnnotation.getElementValues(); + return elementValues; } /* --------------------------------------------------------- */ From 595de2c429d162ffb3e5fe2d92927f6c83bf02d4 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Fri, 4 Dec 2020 16:38:34 -0800 Subject: [PATCH 011/113] remove side_effects_only from purity kinds --- .../java/org/checkerframework/dataflow/qual/Pure.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/qual/Pure.java b/dataflow/src/main/java/org/checkerframework/dataflow/qual/Pure.java index 327f0cb0760..d6c309b73a1 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/qual/Pure.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/qual/Pure.java @@ -29,12 +29,6 @@ public static enum Kind { SIDE_EFFECT_FREE, /** The method returns exactly the same value when called in the same environment. */ - DETERMINISTIC, - - /** - * The method only side effects the expressions specified as annotation values of - * {@code @SideEffectsOnly}. - */ - SIDE_EFFECTS_ONLY + DETERMINISTIC } } From 1459a9cd6a31f0bf5733efd83dd1149ddf812c4d Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Sun, 6 Dec 2020 07:46:38 -0800 Subject: [PATCH 012/113] remove unnecessary --- checker/tests/tainting/SideEffectsOnlyTest.java | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 checker/tests/tainting/SideEffectsOnlyTest.java diff --git a/checker/tests/tainting/SideEffectsOnlyTest.java b/checker/tests/tainting/SideEffectsOnlyTest.java deleted file mode 100644 index e8b6a486d8b..00000000000 --- a/checker/tests/tainting/SideEffectsOnlyTest.java +++ /dev/null @@ -1,14 +0,0 @@ -package tainting; - -import org.checkerframework.checker.tainting.qual.Untainted; - -public class SideEffectsOnlyTest { - void test(@Untainted Object x) { - method(x); - method1(x); - } - - void method(Object x) {} - - void method1(@Untainted Object x) {} -} From 39edb6d471551a08991128bc4dbf301c2b6ddfe6 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Sun, 6 Dec 2020 08:10:30 -0800 Subject: [PATCH 013/113] add documentation --- .../org/checkerframework/framework/flow/CFAbstractStore.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index 122785a0217..b6db1146ee4 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -214,6 +214,8 @@ protected boolean isSideEffectsOnly( *
  • If the method is side-effect-free (as indicated by {@link * org.checkerframework.dataflow.qual.SideEffectFree} or {@link * org.checkerframework.dataflow.qual.Pure}), then no information needs to be removed. + *
  • If the method side effects few expressions (specified as annotation values of + * {@code @SideEffectFree}), then information about those expressions is removed. *
  • Otherwise, all information about field accesses {@code a.f} needs to be removed, except * if the method {@code n} cannot modify {@code a.f} (e.g., if {@code a} is a local * variable or {@code this}, and {@code f} is final). From b3ecb7eaa5dc4ed54ad05589447e0462015fef56 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Sun, 6 Dec 2020 10:28:23 -0800 Subject: [PATCH 014/113] documentation for side effects only --- docs/manual/advanced-features.tex | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/manual/advanced-features.tex b/docs/manual/advanced-features.tex index 232fd03bf60..b02935dd530 100644 --- a/docs/manual/advanced-features.tex +++ b/docs/manual/advanced-features.tex @@ -983,7 +983,11 @@ the refined type, because the method might assign a field. The \refqualclass{dataflow/qual}{SideEffectFree} annotation indicates that the method has no side effects, so calling it does not invalidate any -dataflow facts. +dataflow facts. The \refqualclass{dataflow/qual}{SideEffectsOnly} annotation +specifies the expressions that the method side effects. So expressions +that are not specified as annotation values of +\refqualclass{dataflow/qual}{SideEffectsOnly} have thier dataflow facts +unaffected. Calling a method twice might have different results, so facts known about one call cannot be relied upon at another call. From 73001328ddf87a1ae582e1301b86a3ac5d45fa63 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Sun, 6 Dec 2020 10:36:51 -0800 Subject: [PATCH 015/113] fix formatting --- .../framework/flow/CFAbstractStore.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index b6db1146ee4..791af7a4069 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -1,8 +1,17 @@ package org.checkerframework.framework.flow; -import java.util.*; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; import java.util.concurrent.atomic.AtomicLong; -import javax.lang.model.element.*; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; import org.checkerframework.checker.nullness.qual.Nullable; From 8c3a42808b996721d07a3b62d259b24eb100de6d Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Tue, 8 Dec 2020 13:55:07 -0800 Subject: [PATCH 016/113] fix side effects only for receiver --- checker/tests/iteration/WhileIfTest.java | 12 ++++++++++++ .../framework/flow/CFAbstractStore.java | 15 +++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 checker/tests/iteration/WhileIfTest.java diff --git a/checker/tests/iteration/WhileIfTest.java b/checker/tests/iteration/WhileIfTest.java new file mode 100644 index 00000000000..53891632344 --- /dev/null +++ b/checker/tests/iteration/WhileIfTest.java @@ -0,0 +1,12 @@ +package iteration; + +import java.util.Iterator; + +public class WhileIfTest { + void test(Iterator itera, Iterator iterb) { + while (itera.hasNext() && iterb.hasNext()) { + itera.next(); + iterb.next(); + } + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index 791af7a4069..83b88db1005 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -19,6 +19,7 @@ import org.checkerframework.dataflow.cfg.node.ArrayAccessNode; import org.checkerframework.dataflow.cfg.node.FieldAccessNode; import org.checkerframework.dataflow.cfg.node.LocalVariableNode; +import org.checkerframework.dataflow.cfg.node.MethodAccessNode; import org.checkerframework.dataflow.cfg.node.MethodInvocationNode; import org.checkerframework.dataflow.cfg.node.Node; import org.checkerframework.dataflow.cfg.node.ThisLiteralNode; @@ -277,13 +278,23 @@ public void updateForMethodCall( // isUnmodifiableByOtherCode. Example: @KeyFor("valueThatCanBeMutated"). if (sideEffectsUnrefineAliases) { if (sideEffectExpressions != null) { + MethodAccessNode methodTarget = n.getTarget(); + Node receiver = methodTarget.getReceiver(); + final List expressionsToRemove = sideEffectExpressions; localVariableValues .entrySet() .removeIf( e -> - expressionsToRemove.contains(e.getKey().toString()) - && !e.getKey().isUnmodifiableByOtherCode()); + (expressionsToRemove.contains(e.getKey().toString()) + && !e.getKey() + .isUnmodifiableByOtherCode()) + || (expressionsToRemove.contains("this") + && e.getKey() + .toString() + .equals(receiver.toString()) + && !e.getKey() + .isUnmodifiableByOtherCode())); } else { localVariableValues .entrySet() From c34dd98dda456516834a4bee297141197743dc7a Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Tue, 16 Feb 2021 15:36:19 -0800 Subject: [PATCH 017/113] checher-qual after merging with master --- .../checker/formatter/FormatUtil.java | 325 +++++++++++ .../checker/i18nformatter/I18nFormatUtil.java | 408 ++++++++++++++ .../checker/iteration/qual/HasNext.java | 14 + .../iteration/qual/UnknownHasNext.java | 14 + .../nonempty/qual/EnsuresNonEmpty.java | 42 ++ .../nonempty/qual/EnsuresNonEmptyIf.java | 49 ++ .../checker/nonempty/qual/NonEmpty.java | 22 + .../checker/nonempty/qual/PolyNonEmpty.java | 25 + .../nonempty/qual/UnknownNonEmpty.java | 27 + .../checker/nullness/NullnessUtil.java | 309 +++++++++++ .../checker/nullness/Opt.java | 143 +++++ .../checker/regex/RegexUtil.java | 335 +++++++++++ .../checker/signedness/SignednessUtil.java | 523 ++++++++++++++++++ .../signedness/SignednessUtilExtra.java | 68 +++ .../checker/units/UnitsTools.java | 138 +++++ .../dataflow/qual/SideEffectsOnly.java | 28 + .../framework/util/PurityUnqualified.java | 24 + 17 files changed, 2494 insertions(+) create mode 100644 checker-qual/src/main/java/org/checkerframework/checker/formatter/FormatUtil.java create mode 100644 checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatUtil.java create mode 100644 checker-qual/src/main/java/org/checkerframework/checker/iteration/qual/HasNext.java create mode 100644 checker-qual/src/main/java/org/checkerframework/checker/iteration/qual/UnknownHasNext.java create mode 100644 checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/EnsuresNonEmpty.java create mode 100644 checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/EnsuresNonEmptyIf.java create mode 100644 checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/NonEmpty.java create mode 100644 checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/PolyNonEmpty.java create mode 100644 checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/UnknownNonEmpty.java create mode 100644 checker-qual/src/main/java/org/checkerframework/checker/nullness/NullnessUtil.java create mode 100644 checker-qual/src/main/java/org/checkerframework/checker/nullness/Opt.java create mode 100644 checker-qual/src/main/java/org/checkerframework/checker/regex/RegexUtil.java create mode 100644 checker-qual/src/main/java/org/checkerframework/checker/signedness/SignednessUtil.java create mode 100644 checker-qual/src/main/java/org/checkerframework/checker/signedness/SignednessUtilExtra.java create mode 100644 checker-qual/src/main/java/org/checkerframework/checker/units/UnitsTools.java create mode 100644 checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java create mode 100644 checker-qual/src/main/java/org/checkerframework/framework/util/PurityUnqualified.java diff --git a/checker-qual/src/main/java/org/checkerframework/checker/formatter/FormatUtil.java b/checker-qual/src/main/java/org/checkerframework/checker/formatter/FormatUtil.java new file mode 100644 index 00000000000..4102ef10fcc --- /dev/null +++ b/checker-qual/src/main/java/org/checkerframework/checker/formatter/FormatUtil.java @@ -0,0 +1,325 @@ +package org.checkerframework.checker.formatter; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.IllegalFormatConversionException; +import java.util.IllegalFormatException; +import java.util.Map; +import java.util.MissingFormatArgumentException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.checkerframework.checker.formatter.qual.ConversionCategory; +import org.checkerframework.checker.formatter.qual.ReturnsFormat; +import org.checkerframework.checker.regex.qual.Regex; +import org.checkerframework.framework.qual.AnnotatedFor; + +/** This class provides a collection of utilities to ease working with format strings. */ +@AnnotatedFor("nullness") +public class FormatUtil { + + /** + * A representation of a format specifier, which is represented by "%..." in the format string. + * Indicates how to convert a value into a string. + */ + private static class Conversion { + /** The index in the argument list. */ + private final int index; + /** The conversion category. */ + private final ConversionCategory cath; + + /** + * Construct a new Conversion. + * + * @param index the index in the argument list + * @param c the conversion character + */ + public Conversion(char c, int index) { + this.index = index; + this.cath = ConversionCategory.fromConversionChar(c); + } + + /** + * Returns the index in the argument list. + * + * @return the index in the argument list + */ + int index() { + return index; + } + + /** + * Returns the conversion category. + * + * @return the conversion category + */ + ConversionCategory category() { + return cath; + } + } + + /** + * Returns the first argument if the format string is satisfiable, and if the format's + * parameters match the passed {@link ConversionCategory}s. Otherwise throws an exception. + * + * @param format a format string + * @param cc an array of conversion categories + * @return the {@code format} argument + * @throws IllegalFormatException if the format string is incompatible with the conversion + * categories + */ + // TODO introduce more such functions, see RegexUtil for examples + @ReturnsFormat + public static String asFormat(String format, ConversionCategory... cc) + throws IllegalFormatException { + ConversionCategory[] fcc = formatParameterCategories(format); + if (fcc.length != cc.length) { + throw new ExcessiveOrMissingFormatArgumentException(cc.length, fcc.length); + } + + for (int i = 0; i < cc.length; i++) { + if (cc[i] != fcc[i]) { + throw new IllegalFormatConversionCategoryException(cc[i], fcc[i]); + } + } + + return format; + } + + /** + * Throws an exception if the format is not syntactically valid. + * + * @param format a format string + * @throws IllegalFormatException if the format string is invalid + */ + public static void tryFormatSatisfiability(String format) throws IllegalFormatException { + @SuppressWarnings({ + "unused", // called for side effect, to see if it throws an exception + "nullness:argument.type.incompatible" // it's not documented, but String.format permits + // a null array, which it treats as matching any format string + }) + String unused = String.format(format, (Object[]) null); + } + + /** + * Returns a {@link ConversionCategory} for every conversion found in the format string. + * + *

    Throws an exception if the format is not syntactically valid. + */ + public static ConversionCategory[] formatParameterCategories(String format) + throws IllegalFormatException { + tryFormatSatisfiability(format); + + int last = -1; // index of last argument referenced + int lasto = -1; // last ordinary index + int maxindex = -1; + + Conversion[] cs = parse(format); + Map conv = new HashMap<>(); + + for (Conversion c : cs) { + int index = c.index(); + switch (index) { + case -1: // relative index + break; + case 0: // ordinary index + lasto++; + last = lasto; + break; + default: // explicit index + last = index - 1; + break; + } + maxindex = Math.max(maxindex, last); + Integer lastKey = last; + conv.put( + last, + ConversionCategory.intersect( + conv.containsKey(lastKey) + ? conv.get(lastKey) + : ConversionCategory.UNUSED, + c.category())); + } + + ConversionCategory[] res = new ConversionCategory[maxindex + 1]; + for (int i = 0; i <= maxindex; ++i) { + Integer key = i; // autoboxing prevents recognizing that containsKey => get() != null + res[i] = conv.containsKey(key) ? conv.get(key) : ConversionCategory.UNUSED; + } + return res; + } + + /** + * A regex that matches a format specifier. Its syntax is specified in the See {@code + * Formatter} documentation. + * + *

    +     * %[argument_index$][flags][width][.precision][t]conversion
    +     * group 1            2      3      4           5 6
    +     * 
    + * + * For dates and times, the [t] is required and precision must not be provided. For types other + * than dates and times, the [t] must not be provided. + */ + private static final @Regex(6) String formatSpecifier = + "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])"; + /** The capturing group for the optional {@code t} character. */ + private static final int formatSpecifierT = 5; + /** + * The capturing group for the last character in a format specifier, which is the conversion + * character unless the {@code t} character was given. + */ + private static final int formatSpecifierConversion = 6; + + /** + * A Pattern that matches a format specifier. + * + * @see #formatSpecifier + */ + private static @Regex(6) Pattern fsPattern = Pattern.compile(formatSpecifier); + + /** + * Return the index, in the argument list, of the value that will be formatted by the matched + * format specifier. + * + * @param m a matcher that matches a format specifier + * @return the index of the argument to format + */ + private static int indexFromFormat(Matcher m) { + int index; + String s = m.group(1); + if (s != null) { // explicit index + index = Integer.parseInt(s.substring(0, s.length() - 1)); + } else { + String group2 = m.group(2); // not @Deterministic, so extract into local var + if (group2 != null && group2.contains(String.valueOf('<'))) { + index = -1; // relative index + } else { + index = 0; // ordinary index + } + } + return index; + } + + /** + * Returns the conversion character from a format specifier.. + * + * @param m a matcher that matches a format specifier + * @return the conversion character from the format specifier + */ + @SuppressWarnings( + "nullness:dereference.of.nullable") // group formatSpecifierConversion always exists + private static char conversionCharFromFormat(@Regex(6) Matcher m) { + String tGroup = m.group(formatSpecifierT); + if (tGroup != null) { + return tGroup.charAt(0); // This is the letter "t" or "T". + } else { + return m.group(formatSpecifierConversion).charAt(0); + } + } + + /** + * Return the conversion character that is in the given format specifier. + * + * @param formatSpecifier a format + * specifier + * @return the conversion character that is in the given format specifier + * @deprecated This method is public only for testing. Use private method {@code + * #conversionCharFromFormat(Matcher)}. + */ + @Deprecated // used only for testing. Use conversionCharFromFormat(Matcher). + public static char conversionCharFromFormat(String formatSpecifier) { + Matcher m = fsPattern.matcher(formatSpecifier); + assert m.find(); + return conversionCharFromFormat(m); + } + + /** + * Parse the given format string, return information about its format specifiers. + * + * @param format a format string + * @return the list of Conversions from the format specifiers in the format string + */ + private static Conversion[] parse(String format) { + ArrayList cs = new ArrayList<>(); + @Regex(7) Matcher m = fsPattern.matcher(format); + while (m.find()) { + char c = conversionCharFromFormat(m); + switch (c) { + case '%': + case 'n': + break; + default: + cs.add(new Conversion(c, indexFromFormat(m))); + } + } + return cs.toArray(new Conversion[cs.size()]); + } + + public static class ExcessiveOrMissingFormatArgumentException + extends MissingFormatArgumentException { + private static final long serialVersionUID = 17000126L; + + private final int expected; + private final int found; + + /** + * Constructs an instance of this class with the actual argument length and the expected + * one. + */ + public ExcessiveOrMissingFormatArgumentException(int expected, int found) { + super("-"); + this.expected = expected; + this.found = found; + } + + public int getExpected() { + return expected; + } + + public int getFound() { + return found; + } + + @Override + public String getMessage() { + return String.format("Expected %d arguments but found %d.", expected, found); + } + } + + public static class IllegalFormatConversionCategoryException + extends IllegalFormatConversionException { + private static final long serialVersionUID = 17000126L; + + private final ConversionCategory expected; + private final ConversionCategory found; + + /** + * Constructs an instance of this class with the mismatched conversion and the expected one. + */ + public IllegalFormatConversionCategoryException( + ConversionCategory expected, ConversionCategory found) { + super( + expected.chars == null || expected.chars.length() == 0 + ? '-' + : expected.chars.charAt(0), + found.types == null ? Object.class : found.types[0]); + this.expected = expected; + this.found = found; + } + + public ConversionCategory getExpected() { + return expected; + } + + public ConversionCategory getFound() { + return found; + } + + @Override + public String getMessage() { + return String.format("Expected category %s but found %s.", expected, found); + } + } +} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatUtil.java b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatUtil.java new file mode 100644 index 00000000000..5e537931178 --- /dev/null +++ b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatUtil.java @@ -0,0 +1,408 @@ +package org.checkerframework.checker.i18nformatter; + +import java.text.ChoiceFormat; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.MessageFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.IllegalFormatException; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import org.checkerframework.checker.i18nformatter.qual.I18nChecksFormat; +import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory; +import org.checkerframework.checker.i18nformatter.qual.I18nValidFormat; +import org.checkerframework.checker.interning.qual.InternedDistinct; +import org.checkerframework.checker.nullness.qual.EnsuresNonNull; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.nullness.qual.RequiresNonNull; +import org.checkerframework.framework.qual.AnnotatedFor; + +/** + * This class provides a collection of utilities to ease working with i18n format strings. + * + * @checker_framework.manual #i18n-formatter-checker Internationalization Format String Checker + */ +@AnnotatedFor("nullness") +public class I18nFormatUtil { + + /** + * Throws an exception if the format is not syntactically valid. + * + * @param format the format string to parse + */ + @SuppressWarnings( + "nullness:argument.type.incompatible") // It's not documented, but passing null as the + // argument array is supported. + public static void tryFormatSatisfiability(String format) throws IllegalFormatException { + MessageFormat.format(format, (Object[]) null); + } + + /** + * Returns a {@link I18nConversionCategory} for every conversion found in the format string. + * + * @param format the format string to parse + * @throws IllegalFormatException if the format is not syntactically valid + */ + public static I18nConversionCategory[] formatParameterCategories(String format) + throws IllegalFormatException { + tryFormatSatisfiability(format); + I18nConversion[] cs = MessageFormatParser.parse(format); + + int maxIndex = -1; + Map conv = new HashMap<>(); + + for (I18nConversion c : cs) { + int index = c.index; + Integer indexKey = index; + conv.put( + indexKey, + I18nConversionCategory.intersect( + c.category, + conv.containsKey(indexKey) + ? conv.get(indexKey) + : I18nConversionCategory.UNUSED)); + maxIndex = Math.max(maxIndex, index); + } + + I18nConversionCategory[] res = new I18nConversionCategory[maxIndex + 1]; + for (int i = 0; i <= maxIndex; i++) { + Integer indexKey = i; + res[i] = + conv.containsKey(indexKey) ? conv.get(indexKey) : I18nConversionCategory.UNUSED; + } + return res; + } + + /** + * Returns true if the format string is satisfiable, and if the format's parameters match the + * passed {@link I18nConversionCategory}s. Otherwise an error is thrown. + * + * @param format a format string + * @param cc a list of expected categories for the string's format specifiers + * @return true if the format string's specifiers are the given categories, in order + */ + // TODO introduce more such functions, see RegexUtil for examples + @I18nChecksFormat + public static boolean hasFormat(String format, I18nConversionCategory... cc) { + I18nConversionCategory[] fcc = formatParameterCategories(format); + if (fcc.length != cc.length) { + return false; + } + + for (int i = 0; i < cc.length; i++) { + if (!I18nConversionCategory.isSubsetOf(cc[i], fcc[i])) { + return false; + } + } + return true; + } + + @I18nValidFormat + public static boolean isFormat(String format) { + try { + formatParameterCategories(format); + } catch (Exception e) { + return false; + } + return true; + } + + private static class I18nConversion { + public int index; + public I18nConversionCategory category; + + public I18nConversion(int index, I18nConversionCategory category) { + this.index = index; + this.category = category; + } + + @Override + public String toString() { + return category.toString() + "(index: " + index + ")"; + } + } + + private static class MessageFormatParser { + + public static int maxOffset; + + /** The locale to use for formatting numbers and dates. Is set in {@link #parse}. */ + private static @MonotonicNonNull Locale locale; + + /** + * An array of formatters, which are used to format the arguments. Is set in {@link #parse}. + */ + private static @MonotonicNonNull List categories; + + /** + * The argument numbers corresponding to each formatter. (The formatters are stored in the + * order they occur in the pattern, not in the order in which the arguments are specified.) + * Is set in {@link #parse}. + */ + private static @MonotonicNonNull List argumentIndices; + + // I think this means the number of format specifiers in the format string. + /** The number of subformats. */ + private static int numFormat; + + // Indices for segments + private static final int SEG_RAW = 0; + private static final int SEG_INDEX = 1; + private static final int SEG_TYPE = 2; + private static final int SEG_MODIFIER = 3; // modifier or subformat + + // Indices for type keywords + private static final int TYPE_NULL = 0; + private static final int TYPE_NUMBER = 1; + private static final int TYPE_DATE = 2; + private static final int TYPE_TIME = 3; + private static final int TYPE_CHOICE = 4; + + private static final String[] TYPE_KEYWORDS = {"", "number", "date", "time", "choice"}; + + // Indices for number modifiers + private static final int MODIFIER_DEFAULT = 0; // common in number and date-time + private static final int MODIFIER_CURRENCY = 1; + private static final int MODIFIER_PERCENT = 2; + private static final int MODIFIER_INTEGER = 3; + + private static final String[] NUMBER_MODIFIER_KEYWORDS = { + "", "currency", "percent", "integer" + }; + + private static final String[] DATE_TIME_MODIFIER_KEYWORDS = { + "", "short", "medium", "long", "full" + }; + + @EnsuresNonNull({"categories", "argumentIndices", "locale"}) + public static I18nConversion[] parse(String pattern) { + MessageFormatParser.categories = new ArrayList<>(); + MessageFormatParser.argumentIndices = new ArrayList<>(); + MessageFormatParser.locale = Locale.getDefault(Locale.Category.FORMAT); + applyPattern(pattern); + + I18nConversion[] ret = new I18nConversion[MessageFormatParser.numFormat]; + for (int i = 0; i < MessageFormatParser.numFormat; i++) { + ret[i] = new I18nConversion(argumentIndices.get(i), categories.get(i)); + } + return ret; + } + + @SuppressWarnings("nullness:dereference.of.nullable") // complex rules for segments[i] + @RequiresNonNull({"argumentIndices", "categories", "locale"}) + private static void applyPattern(String pattern) { + @Nullable StringBuilder[] segments = new StringBuilder[4]; + // Allocate only segments[SEG_RAW] here. The rest are + // allocated on demand. + segments[SEG_RAW] = new StringBuilder(); + + int part = SEG_RAW; + MessageFormatParser.numFormat = 0; + boolean inQuote = false; + int braceStack = 0; + maxOffset = -1; + for (int i = 0; i < pattern.length(); ++i) { + char ch = pattern.charAt(i); + if (part == SEG_RAW) { + if (ch == '\'') { + if (i + 1 < pattern.length() && pattern.charAt(i + 1) == '\'') { + segments[part].append(ch); // handle doubles + ++i; + } else { + inQuote = !inQuote; + } + } else if (ch == '{' && !inQuote) { + part = SEG_INDEX; + if (segments[SEG_INDEX] == null) { + segments[SEG_INDEX] = new StringBuilder(); + } + } else { + segments[part].append(ch); + } + } else { + if (inQuote) { // just copy quotes in parts + segments[part].append(ch); + if (ch == '\'') { + inQuote = false; + } + } else { + switch (ch) { + case ',': + if (part < SEG_MODIFIER) { + if (segments[++part] == null) { + segments[part] = new StringBuilder(); + } + } else { + segments[part].append(ch); + } + break; + case '{': + ++braceStack; + segments[part].append(ch); + break; + case '}': + if (braceStack == 0) { + part = SEG_RAW; + makeFormat(numFormat, segments); + numFormat++; + // throw away other segments + segments[SEG_INDEX] = null; + segments[SEG_TYPE] = null; + segments[SEG_MODIFIER] = null; + } else { + --braceStack; + segments[part].append(ch); + } + break; + case ' ': + // Skip any leading space chars for SEG_TYPE. + if (part != SEG_TYPE || segments[SEG_TYPE].length() > 0) { + segments[part].append(ch); + } + break; + case '\'': + inQuote = true; + segments[part].append(ch); + break; + default: + segments[part].append(ch); + break; + } + } + } + } + if (braceStack == 0 && part != 0) { + maxOffset = -1; + throw new IllegalArgumentException("Unmatched braces in the pattern"); + } + } + + /** Side-effects {@code categories} field, adding to it an I18nConversionCategory. */ + @RequiresNonNull({"argumentIndices", "categories", "locale"}) + private static void makeFormat(int offsetNumber, @Nullable StringBuilder[] textSegments) { + String[] segments = new String[textSegments.length]; + for (int i = 0; i < textSegments.length; i++) { + StringBuilder oneseg = textSegments[i]; + segments[i] = (oneseg != null) ? oneseg.toString() : ""; + } + + // get the argument number + int argumentNumber; + try { + argumentNumber = Integer.parseInt(segments[SEG_INDEX]); // always + // unlocalized! + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "can't parse argument number: " + segments[SEG_INDEX], e); + } + if (argumentNumber < 0) { + throw new IllegalArgumentException("negative argument number: " + argumentNumber); + } + + int oldMaxOffset = maxOffset; + maxOffset = offsetNumber; + argumentIndices.add(argumentNumber); + + // now get the format + I18nConversionCategory category = null; + if (segments[SEG_TYPE].length() != 0) { + int type = findKeyword(segments[SEG_TYPE], TYPE_KEYWORDS); + switch (type) { + case TYPE_NULL: + category = I18nConversionCategory.GENERAL; + break; + case TYPE_NUMBER: + switch (findKeyword(segments[SEG_MODIFIER], NUMBER_MODIFIER_KEYWORDS)) { + case MODIFIER_DEFAULT: + case MODIFIER_CURRENCY: + case MODIFIER_PERCENT: + case MODIFIER_INTEGER: + break; + default: // DecimalFormat pattern + try { + new DecimalFormat( + segments[SEG_MODIFIER], + DecimalFormatSymbols.getInstance(locale)); + } catch (IllegalArgumentException e) { + maxOffset = oldMaxOffset; + // invalid decimal subformat pattern + throw e; + } + break; + } + category = I18nConversionCategory.NUMBER; + break; + case TYPE_DATE: + case TYPE_TIME: + int mod = findKeyword(segments[SEG_MODIFIER], DATE_TIME_MODIFIER_KEYWORDS); + if (mod >= 0 && mod < DATE_TIME_MODIFIER_KEYWORDS.length) { + // nothing to do + } else { + // SimpleDateFormat pattern + try { + new SimpleDateFormat(segments[SEG_MODIFIER], locale); + } catch (IllegalArgumentException e) { + maxOffset = oldMaxOffset; + // invalid date subformat pattern + throw e; + } + } + category = I18nConversionCategory.DATE; + break; + case TYPE_CHOICE: + if (segments[SEG_MODIFIER].length() == 0) { + throw new IllegalArgumentException( + "Choice Pattern requires Subformat Pattern: " + + segments[SEG_MODIFIER]); + } + try { + // ChoiceFormat pattern + new ChoiceFormat(segments[SEG_MODIFIER]); + } catch (Exception e) { + maxOffset = oldMaxOffset; + // invalid choice subformat pattern + throw new IllegalArgumentException( + "Choice Pattern incorrect: " + segments[SEG_MODIFIER], e); + } + category = I18nConversionCategory.NUMBER; + break; + default: + maxOffset = oldMaxOffset; + throw new IllegalArgumentException( + "unknown format type: " + segments[SEG_TYPE]); + } + } else { + category = I18nConversionCategory.GENERAL; + } + categories.add(category); + } + + /** + * Return the index of s in list. If not found, return the index of + * s.trim().toLowerCase(Locale.ROOT) in list. If still not found, return -1. + */ + private static final int findKeyword(String s, String[] list) { + for (int i = 0; i < list.length; ++i) { + if (s.equals(list[i])) { + return i; + } + } + + // Try trimmed lowercase. + @SuppressWarnings("interning:assignment.type.incompatible") // test if value changed + @InternedDistinct String ls = s.trim().toLowerCase(Locale.ROOT); + if (ls != s) { // Don't loop if the string trim().toLowerCase returned the same object. + for (int i = 0; i < list.length; ++i) { + if (ls.equals(list[i])) { + return i; + } + } + } + return -1; + } + } +} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/iteration/qual/HasNext.java b/checker-qual/src/main/java/org/checkerframework/checker/iteration/qual/HasNext.java new file mode 100644 index 00000000000..9a075350a70 --- /dev/null +++ b/checker-qual/src/main/java/org/checkerframework/checker/iteration/qual/HasNext.java @@ -0,0 +1,14 @@ +package org.checkerframework.checker.iteration.qual; + +import java.lang.annotation.*; +import org.checkerframework.framework.qual.*; + +/** + * An expression whose type has this annotation is an iterator that has a next value -- that is, + * {@code next()} will not throw NoSuchElementException. + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) +@SubtypeOf({UnknownHasNext.class}) +public @interface HasNext {} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/iteration/qual/UnknownHasNext.java b/checker-qual/src/main/java/org/checkerframework/checker/iteration/qual/UnknownHasNext.java new file mode 100644 index 00000000000..0d062975860 --- /dev/null +++ b/checker-qual/src/main/java/org/checkerframework/checker/iteration/qual/UnknownHasNext.java @@ -0,0 +1,14 @@ +package org.checkerframework.checker.iteration.qual; + +import java.lang.annotation.*; +import org.checkerframework.framework.qual.*; + +/** Either a non-iterator, or an iterator that might or might not have a next element. */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) +@SubtypeOf({}) +@DefaultQualifierInHierarchy +@DefaultFor(value = TypeUseLocation.LOWER_BOUND, types = Void.class) +@QualifierForLiterals(LiteralKind.NULL) +public @interface UnknownHasNext {} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/EnsuresNonEmpty.java b/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/EnsuresNonEmpty.java new file mode 100644 index 00000000000..cc57a1f1b23 --- /dev/null +++ b/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/EnsuresNonEmpty.java @@ -0,0 +1,42 @@ +package org.checkerframework.checker.nonempty.qual; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.checkerframework.framework.qual.InheritedAnnotation; +import org.checkerframework.framework.qual.JavaExpression; +import org.checkerframework.framework.qual.PostconditionAnnotation; + +/** + * Indicates that the value expressions are non-empty if the method terminates successfully. + * + *

    Consider the following method from {@code java.util.Map}: + * + *

    + * @EnsuresNonEmpty(expression="this")
    + * public @Nullable V put(K key, V value) { ... }
    + * 
    + * + *

    This method guarantees that {@code this} has type {@code @NonEmpty} after the method returns. + * + * @see NonEmpty + * @see EnsuresNonEmptyIf + * @checker_framework.manual #map-key-checker Map Key Checker + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) +@PostconditionAnnotation(qualifier = NonEmpty.class) +@InheritedAnnotation +public @interface EnsuresNonEmpty { + /** + * Java expressions that are non-empty on successful method termination. + * + * @return Java expressions that are non-empty on successful method termination + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + @JavaExpression + String[] value(); +} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/EnsuresNonEmptyIf.java b/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/EnsuresNonEmptyIf.java new file mode 100644 index 00000000000..dedf44111a8 --- /dev/null +++ b/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/EnsuresNonEmptyIf.java @@ -0,0 +1,49 @@ +package org.checkerframework.checker.nonempty.qual; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; +import org.checkerframework.framework.qual.InheritedAnnotation; + +/** + * Indicates that the given expressions are non-empty if the method returns the given result (either + * true or false). + * + *

    As an example, consider the following method in {@code java.util.Map}: + * + *

    + *   @EnsuresNonEmptyIf(result=true, expression="this")
    + *   public boolean containsKey(String key) { ... }
    + * 
    + * + * If an invocation {@code m.containsKey(k)} returns true, then the type of {@code this} can be + * inferred to be {@code @NonEmpty}. + * + * @see NonEmpty + * @see EnsuresNonEmpty + * @checker_framework.manual #map-key-checker Map Key Checker + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) +@ConditionalPostconditionAnnotation(qualifier = NonEmpty.class) +@InheritedAnnotation +public @interface EnsuresNonEmptyIf { + /** + * The value the method must return, in order for the postcondition to hold. + * + * @return The value the method must return, in order for the postcondition to hold + */ + boolean result(); + + /** + * Java expressions that are non-empty after the method returns the given result. + * + * @return Java expressions that are non-empty after the method returns the given result + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions + */ + String[] expression(); +} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/NonEmpty.java b/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/NonEmpty.java new file mode 100644 index 00000000000..d7cc8daa458 --- /dev/null +++ b/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/NonEmpty.java @@ -0,0 +1,22 @@ +package org.checkerframework.checker.nonempty.qual; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + +/** + * Indicates that the collection, map, or iterator is non-empty. + * + * @checker_framework.manual #nonempty-checker NonEmpty Checker + */ +@SubtypeOf(UnknownNonEmpty.class) +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) +@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND}) +public @interface NonEmpty {} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/PolyNonEmpty.java b/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/PolyNonEmpty.java new file mode 100644 index 00000000000..1946aed6b56 --- /dev/null +++ b/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/PolyNonEmpty.java @@ -0,0 +1,25 @@ +package org.checkerframework.checker.nonempty.qual; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.checkerframework.framework.qual.PolymorphicQualifier; + +/** + * A polymorphic qualifier for the {@code @NonEmpty} type system. + * + *

    Any method written using {@code @PolyNonEmpty} conceptually has two versions: one in which + * every instance of {@code @PolyNonEmpty} has been replaced by {@code @}{@link UnknownNonEmpty} and + * one in which every instance of {@code @PolyNonEmpty} has been replaced by {@code @}{@link + * NonEmpty}, for every possible combination of Collection, Map, or Iterator arguments. + * + * @checker_framework.manual #nonempty NonEmpty Checker + * @checker_framework.manual #qualifier-polymorphism Qualifier polymorphism + */ +@Documented +@PolymorphicQualifier(UnknownNonEmpty.class) +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) +public @interface PolyNonEmpty {} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/UnknownNonEmpty.java b/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/UnknownNonEmpty.java new file mode 100644 index 00000000000..35ec399d1ff --- /dev/null +++ b/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/UnknownNonEmpty.java @@ -0,0 +1,27 @@ +package org.checkerframework.checker.nonempty.qual; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.checkerframework.framework.qual.*; + +/** + * Indicates that the collection, map, or iterator not known to be a non-empty. It is the top type + * qualifier in the {@link org.checkerframework.checker.nonempty.qual.NonEmpty} hierarchy. It is + * also the default type qualifier. + * + *

    Used internally by the type system; should never be written by a programmer. + * + * @checker_framework.manual #nonempty-checker NonEmpty Checker + */ +@InvisibleQualifier +@SubtypeOf({}) +@DefaultQualifierInHierarchy +@DefaultFor({TypeUseLocation.LOWER_BOUND}) +@QualifierForLiterals(LiteralKind.NULL) +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) +public @interface UnknownNonEmpty {} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/NullnessUtil.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/NullnessUtil.java new file mode 100644 index 00000000000..127a55e80bc --- /dev/null +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/NullnessUtil.java @@ -0,0 +1,309 @@ +package org.checkerframework.checker.nullness; + +import org.checkerframework.checker.nullness.qual.EnsuresNonNull; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.AnnotatedFor; + +/** + * Utility class for the Nullness Checker. + * + *

    To avoid the need to write the NullnessUtil class name, do: + * + *

    import static org.checkerframework.checker.nullness.NullnessUtil.castNonNull;
    + * + * or + * + *
    import static org.checkerframework.checker.nullness.NullnessUtil.*;
    + * + *

    Runtime Dependency: If you use this class, you must distribute (or link to) {@code + * checker-qual.jar}, along with your binaries. Or, you can can copy this class into your own + * project. + */ +@SuppressWarnings({ + "nullness", // Nullness utilities are trusted regarding nullness. + "cast" // Casts look redundant if Nullness Checker is not run. +}) +@AnnotatedFor("nullness") +public final class NullnessUtil { + + private NullnessUtil() { + throw new AssertionError("shouldn't be instantiated"); + } + + /** + * A method that suppresses warnings from the Nullness Checker. + * + *

    The method takes a possibly-null reference, unsafely casts it to have the @NonNull type + * qualifier, and returns it. The Nullness Checker considers both the return value, and also the + * argument, to be non-null after the method call. Therefore, the {@code castNonNull} method can + * be used either as a cast expression or as a statement. The Nullness Checker issues no + * warnings in any of the following code: + * + *

    
    +     *   // one way to use as a cast:
    +     *  {@literal @}NonNull String s = castNonNull(possiblyNull1);
    +     *
    +     *   // another way to use as a cast:
    +     *   castNonNull(possiblyNull2).toString();
    +     *
    +     *   // one way to use as a statement:
    +     *   castNonNull(possiblyNull3);
    +     *   possiblyNull3.toString();`
    +     * }
    + * + * The {@code castNonNull} method is intended to be used in situations where the programmer + * definitively knows that a given reference is not null, but the type system is unable to make + * this deduction. It is not intended for defensive programming, in which a programmer cannot + * prove that the value is not null but wishes to have an earlier indication if it is. See the + * Checker Framework Manual for further discussion. + * + *

    The method throws {@link AssertionError} if Java assertions are enabled and the argument + * is {@code null}. If the exception is ever thrown, then that indicates that the programmer + * misused the method by using it in a circumstance where its argument can be null. + * + * @param the type of the reference + * @param ref a reference of @Nullable type, that is non-null at run time + * @return the argument, casted to have the type qualifier @NonNull + */ + public static @EnsuresNonNull("#1") @NonNull T castNonNull( + @Nullable T ref) { + assert ref != null : "Misuse of castNonNull: called with a null argument"; + return (@NonNull T) ref; + } + + /** + * Suppress warnings from the Nullness Checker, with a custom error message. See {@link + * #castNonNull(Object)} for documentation. + * + * @see #castNonNull(Object) + * @param the type of the reference + * @param ref a reference of @Nullable type, that is non-null at run time + * @param message text to include if this method is misused + * @return the argument, casted to have the type qualifier @NonNull + */ + public static @EnsuresNonNull("#1") @NonNull T castNonNull( + @Nullable T ref, String message) { + assert ref != null : "Misuse of castNonNull: called with a null argument: " + message; + return (@NonNull T) ref; + } + + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that + * all elements at every array level are non-null. + * + * @param the component type of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at + * run time + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + public static @EnsuresNonNull("#1") + @NonNull T @NonNull [] castNonNullDeep(T @Nullable [] arr) { + return (@NonNull T[]) castNonNullArray(arr, null); + } + + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that + * all elements at every array level are non-null. + * + * @param the component type of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at + * run time + * @param message text to include if this method is misused + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + public static @EnsuresNonNull("#1") + @NonNull T @NonNull [] castNonNullDeep(T @Nullable [] arr, String message) { + return (@NonNull T[]) castNonNullArray(arr, message); + } + + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that + * all elements at every array level are non-null. + * + * @param the component type of the component type of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at + * run time + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + public static @EnsuresNonNull("#1") + @NonNull T @NonNull [][] castNonNullDeep(T @Nullable [] @Nullable [] arr) { + return (@NonNull T[][]) castNonNullArray(arr, null); + } + + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that + * all elements at every array level are non-null. + * + * @param the component type of the component type of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at + * run time + * @param message text to include if this method is misused + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + public static @EnsuresNonNull("#1") + @NonNull T @NonNull [][] castNonNullDeep(T @Nullable [] @Nullable [] arr, String message) { + return (@NonNull T[][]) castNonNullArray(arr, message); + } + + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that + * all elements at every array level are non-null. + * + * @param the component type (three levels in) of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at + * run time + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + public static @EnsuresNonNull("#1") + @NonNull T @NonNull [][][] castNonNullDeep(T @Nullable [] @Nullable [] @Nullable [] arr) { + return (@NonNull T[][][]) castNonNullArray(arr, null); + } + + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that + * all elements at every array level are non-null. + * + * @param the component type (three levels in) of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at + * run time + * @param message text to include if this method is misused + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + public static @EnsuresNonNull("#1") + @NonNull T @NonNull [][][] castNonNullDeep( + T @Nullable [] @Nullable [] @Nullable [] arr, String message) { + return (@NonNull T[][][]) castNonNullArray(arr, message); + } + + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that + * all elements at every array level are non-null. + * + * @param the component type of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at + * run time + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + public static @EnsuresNonNull("#1") + @NonNull T @NonNull [][][][] castNonNullDeep( + T @Nullable [] @Nullable [] @Nullable [] @Nullable [] arr) { + return (@NonNull T[][][][]) castNonNullArray(arr, null); + } + + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that + * all elements at every array level are non-null. + * + * @param the component type (four levels in) of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at + * run time + * @param message text to include if this method is misused + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + public static @EnsuresNonNull("#1") + @NonNull T @NonNull [][][][] castNonNullDeep( + T @Nullable [] @Nullable [] @Nullable [] @Nullable [] arr, String message) { + return (@NonNull T[][][][]) castNonNullArray(arr, message); + } + + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that + * all elements at every array level are non-null. + * + * @param the component type (four levels in) of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at + * run time + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + public static @EnsuresNonNull("#1") + @NonNull T @NonNull [][][][][] castNonNullDeep( + T @Nullable [] @Nullable [] @Nullable [] @Nullable [] @Nullable [] arr) { + return (@NonNull T[][][][][]) castNonNullArray(arr, null); + } + + /** + * Like castNonNull, but whereas that method only checks and casts the reference itself, this + * traverses all levels of the argument array. The array is recursively checked to ensure that + * all elements at every array level are non-null. + * + * @param the component type (five levels in) of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at + * run time + * @param message text to include if this method is misused + * @return the argument, casted to have the type qualifier @NonNull at all levels + * @see #castNonNull(Object) + */ + public static @EnsuresNonNull("#1") + @NonNull T @NonNull [][][][][] castNonNullDeep( + T @Nullable [] @Nullable [] @Nullable [] @Nullable [] @Nullable [] arr, + String message) { + return (@NonNull T[][][][][]) castNonNullArray(arr, message); + } + + /** + * The implementation of castNonNullDeep. + * + * @param the component type (five levels in) of the array + * @param arr an array all of whose elements, and their elements recursively, are non-null at + * run time + * @param message text to include if there is a non-null value, or null to use uncustomized + * message + * @return the argument, casted to have the type qualifier @NonNull at all levels + */ + private static @NonNull T @NonNull [] castNonNullArray( + T @Nullable [] arr, @Nullable String message) { + assert arr != null + : "Misuse of castNonNullArray: called with a null array argument" + + ((message == null) ? "" : (": " + message)); + for (int i = 0; i < arr.length; ++i) { + assert arr[i] != null + : "Misuse of castNonNull: called with a null array element" + + ((message == null) ? "" : (": " + message)); + checkIfArray(arr[i], message); + } + return (@NonNull T[]) arr; + } + + /** + * If the argument is an array, requires it to be non-null at all levels. + * + * @param ref a value; if an array, all of its elements, and their elements recursively, are + * non-null at run time + * @param message text to include if there is a non-null value, or null to use uncustomized + * message + */ + private static void checkIfArray(@NonNull Object ref, @Nullable String message) { + assert ref != null + : "Misuse of checkIfArray: called with a null argument" + + ((message == null) ? "" : (": " + message)); + Class comp = ref.getClass().getComponentType(); + if (comp != null) { + // comp is non-null for arrays, otherwise null. + if (comp.isPrimitive()) { + // Nothing to do for arrays of primitive type: primitives are + // never null. + } else { + castNonNullArray((Object[]) ref, message); + } + } + } +} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/Opt.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/Opt.java new file mode 100644 index 00000000000..36a93442c0c --- /dev/null +++ b/checker-qual/src/main/java/org/checkerframework/checker/nullness/Opt.java @@ -0,0 +1,143 @@ +package org.checkerframework.checker.nullness; + +import java.util.NoSuchElementException; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.AnnotatedFor; + +/** + * Utility class for the Nullness Checker, providing every method in {@link java.util.Optional}, but + * written for possibly-null references rather than for the {@code Optional} type. + * + *

    To avoid the need to write the {@code Opt} class name at invocation sites, do: + * + *

    import static org.checkerframework.checker.nullness.Opt.orElse;
    + * + * or + * + *
    import static org.checkerframework.checker.nullness.Opt.*;
    + * + *

    Runtime Dependency: If you use this class, you must distribute (or link to) {@code + * checker-qual.jar}, along with your binaries. Or, you can can copy this class into your own + * project. + * + * @see java.util.Optional + */ +@AnnotatedFor("nullness") +public final class Opt { + + /** The Opt class cannot be instantiated. */ + private Opt() { + throw new AssertionError("shouldn't be instantiated"); + } + + /** + * If primary is non-null, returns it, otherwise throws NoSuchElementException. + * + * @param the type of the argument + * @param primary a non-null value to return + * @return {@code primary} if it is non-null + * @throws NoSuchElementException if primary is null + * @see java.util.Optional#get() + */ + // `primary` is @NonNull; otherwise, the method could throw an exception. + public static T get(T primary) { + if (primary == null) { + throw new NoSuchElementException("No value present"); + } + return primary; + } + + /** + * Returns true if primary is non-null, false if primary is null. + * + * @see java.util.Optional#isPresent() + */ + @EnsuresNonNullIf(expression = "#1", result = true) + public static boolean isPresent(@Nullable Object primary) { + return primary != null; + } + + /** + * If primary is non-null, invoke the specified consumer with the value, otherwise do nothing. + * + * @see java.util.Optional#ifPresent(Consumer) + */ + public static void ifPresent(T primary, Consumer<@NonNull ? super @NonNull T> consumer) { + if (primary != null) { + consumer.accept(primary); + } + } + + /** + * If primary is non-null, and its value matches the given predicate, return the value. If + * primary is null or its non-null value does not match the predicate, return null. + * + * @see java.util.Optional#filter(Predicate) + */ + public static @Nullable T filter( + T primary, Predicate<@NonNull ? super @NonNull T> predicate) { + if (primary == null) { + return null; + } else { + return predicate.test(primary) ? primary : null; + } + } + + /** + * If primary is non-null, apply the provided mapping function to it and return the result. If + * primary is null, return null. + * + * @see java.util.Optional#map(Function) + */ + public static @Nullable U map( + T primary, Function<@NonNull ? super @NonNull T, ? extends U> mapper) { + if (primary == null) { + return null; + } else { + return mapper.apply(primary); + } + } + + // flatMap would have the same signature and implementation as map + + /** + * Return primary if it is non-null. If primary is null, return other. + * + * @see java.util.Optional#orElse(Object) + */ + public static @NonNull T orElse(T primary, @NonNull T other) { + return primary != null ? primary : other; + } + + /** + * Return primary if it is non-null. If primary is null, invoke {@code other} and return the + * result of that invocation. + * + * @see java.util.Optional#orElseGet(Supplier) + */ + public static @NonNull T orElseGet(T primary, Supplier other) { + return primary != null ? primary : other.get(); + } + + /** + * Return primary if it is non-null. If primary is null, throw an exception to be created by the + * provided supplier. + * + * @see java.util.Optional#orElseThrow(Supplier) + */ + // `primary` is @NonNull; otherwise, the method could throw an exception. + public static T orElseThrow( + T primary, Supplier exceptionSupplier) throws X { + if (primary != null) { + return primary; + } else { + throw exceptionSupplier.get(); + } + } +} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/regex/RegexUtil.java b/checker-qual/src/main/java/org/checkerframework/checker/regex/RegexUtil.java new file mode 100644 index 00000000000..45381b43e94 --- /dev/null +++ b/checker-qual/src/main/java/org/checkerframework/checker/regex/RegexUtil.java @@ -0,0 +1,335 @@ +// This class should be kept in sync with org.plumelib.util.RegexUtil in the plume-util project. + +package org.checkerframework.checker.regex; + +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; +import org.checkerframework.checker.index.qual.GTENegativeOne; +import org.checkerframework.checker.lock.qual.GuardSatisfied; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.regex.qual.Regex; +import org.checkerframework.dataflow.qual.Pure; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.framework.qual.AnnotatedFor; +import org.checkerframework.framework.qual.EnsuresQualifierIf; + +/** + * Utility methods for regular expressions, most notably for testing whether a string is a regular + * expression. + * + *

    For an example of intended use, see section Testing whether a string is a + * regular expression in the Checker Framework manual. + * + *

    Runtime Dependency: If you use this class, you must distribute (or link to) {@code + * checker-qual.jar}, along with your binaries. Or, you can can copy this class into your own + * project. + */ +@AnnotatedFor("nullness") +public final class RegexUtil { + + /** This class is a collection of methods; it does not represent anything. */ + private RegexUtil() { + throw new Error("do not instantiate"); + } + + /** + * A checked version of {@link PatternSyntaxException}. + * + *

    This exception is useful when an illegal regex is detected but the contextual information + * to report a helpful error message is not available at the current depth in the call stack. By + * using a checked PatternSyntaxException the error must be handled up the call stack where a + * better error message can be reported. + * + *

    Typical usage is: + * + *

    +     * void myMethod(...) throws CheckedPatternSyntaxException {
    +     *   ...
    +     *   if (! isRegex(myString)) {
    +     *     throw new CheckedPatternSyntaxException(...);
    +     *   }
    +     *   ... Pattern.compile(myString) ...
    +     * 
    + * + * Simply calling {@code Pattern.compile} would have a similar effect, in that {@code + * PatternSyntaxException} would be thrown at run time if {@code myString} is not a regular + * expression. There are two problems with such an approach. First, a client of {@code myMethod} + * might forget to handle the exception, since {@code PatternSyntaxException} is not checked. + * Also, the Regex Checker would issue a warning about the call to {@code Pattern.compile} that + * might throw an exception. The above usage pattern avoids both problems. + * + * @see PatternSyntaxException + */ + public static class CheckedPatternSyntaxException extends Exception { + + private static final long serialVersionUID = 6266881831979001480L; + + /** The PatternSyntaxException that this is a wrapper around. */ + private final PatternSyntaxException pse; + + /** + * Constructs a new CheckedPatternSyntaxException equivalent to the given {@link + * PatternSyntaxException}. + * + *

    Consider calling this constructor with the result of {@link RegexUtil#regexError}. + * + * @param pse the PatternSyntaxException to be wrapped + */ + public CheckedPatternSyntaxException(PatternSyntaxException pse) { + this.pse = pse; + } + + /** + * Constructs a new CheckedPatternSyntaxException. + * + * @param desc a description of the error + * @param regex the erroneous pattern + * @param index the approximate index in the pattern of the error, or {@code -1} if the + * index is not known + */ + public CheckedPatternSyntaxException(String desc, String regex, @GTENegativeOne int index) { + this(new PatternSyntaxException(desc, regex, index)); + } + + /** + * Retrieves the description of the error. + * + * @return the description of the error + */ + public String getDescription() { + return pse.getDescription(); + } + + /** + * Retrieves the error index. + * + * @return the approximate index in the pattern of the error, or {@code -1} if the index is + * not known + */ + public int getIndex() { + return pse.getIndex(); + } + + /** + * Returns a multi-line string containing the description of the syntax error and its index, + * the erroneous regular-expression pattern, and a visual indication of the error index + * within the pattern. + * + * @return the full detail message + */ + @Override + @Pure + public String getMessage(@GuardSatisfied CheckedPatternSyntaxException this) { + return pse.getMessage(); + } + + /** + * Retrieves the erroneous regular-expression pattern. + * + * @return the erroneous pattern + */ + public String getPattern() { + return pse.getPattern(); + } + } + + /** + * Returns true if the argument is a syntactically valid regular expression. + * + * @param s string to check for being a regular expression + * @return true iff s is a regular expression + */ + @Pure + @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class) + public static boolean isRegex(String s) { + return isRegex(s, 0); + } + + /** + * Returns true if the argument is a syntactically valid regular expression with at least the + * given number of groups. + * + * @param s string to check for being a regular expression + * @param groups number of groups expected + * @return true iff s is a regular expression with {@code groups} groups + */ + @SuppressWarnings({"regex", "all:deterministic"}) // RegexUtil; for purity, catches an exception + @Pure + // @EnsuresQualifierIf annotation is extraneous because this method is special-cased + // in RegexTransfer. + @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class) + public static boolean isRegex(String s, int groups) { + Pattern p; + try { + p = Pattern.compile(s); + } catch (PatternSyntaxException e) { + return false; + } + return getGroupCount(p) >= groups; + } + + /** + * Returns true if the argument is a syntactically valid regular expression. + * + * @param c char to check for being a regular expression + * @return true iff c is a regular expression + */ + @SuppressWarnings({ + "regex", + "all:purity.not.deterministic.call", + "lock" + }) // RegexUtil; temp value used in pure method is equal up to equals but not up to == + @Pure + @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class) + public static boolean isRegex(final char c) { + return isRegex(Character.toString(c)); + } + + /** + * Returns null if the argument is a syntactically valid regular expression. Otherwise returns a + * string describing why the argument is not a regex. + * + * @param s string to check for being a regular expression + * @return null, or a string describing why the argument is not a regex + */ + @SideEffectFree + public static @Nullable String regexError(String s) { + return regexError(s, 0); + } + + /** + * Returns null if the argument is a syntactically valid regular expression with at least the + * given number of groups. Otherwise returns a string describing why the argument is not a + * regex. + * + * @param s string to check for being a regular expression + * @param groups number of groups expected + * @return null, or a string describing why the argument is not a regex + */ + @SuppressWarnings({"regex", "not.sef"}) // RegexUtil; + @SideEffectFree + public static @Nullable String regexError(String s, int groups) { + try { + Pattern p = Pattern.compile(s); + int actualGroups = getGroupCount(p); + if (actualGroups < groups) { + return regexErrorMessage(s, groups, actualGroups); + } + } catch (PatternSyntaxException e) { + return e.getMessage(); + } + return null; + } + + /** + * Returns null if the argument is a syntactically valid regular expression. Otherwise returns a + * PatternSyntaxException describing why the argument is not a regex. + * + * @param s string to check for being a regular expression + * @return null, or a PatternSyntaxException describing why the argument is not a regex + */ + @SideEffectFree + public static @Nullable PatternSyntaxException regexException(String s) { + return regexException(s, 0); + } + + /** + * Returns null if the argument is a syntactically valid regular expression with at least the + * given number of groups. Otherwise returns a PatternSyntaxException describing why the + * argument is not a regex. + * + * @param s string to check for being a regular expression + * @param groups number of groups expected + * @return null, or a PatternSyntaxException describing why the argument is not a regex + */ + @SuppressWarnings("regex") // RegexUtil + @SideEffectFree + public static @Nullable PatternSyntaxException regexException(String s, int groups) { + try { + Pattern p = Pattern.compile(s); + int actualGroups = getGroupCount(p); + if (actualGroups < groups) { + return new PatternSyntaxException( + regexErrorMessage(s, groups, actualGroups), s, -1); + } + } catch (PatternSyntaxException pse) { + return pse; + } + return null; + } + + /** + * Returns the argument as a {@code @Regex String} if it is a regex, otherwise throws an error. + * The purpose of this method is to suppress Regex Checker warnings. It should be very rarely + * needed. + * + * @param s string to check for being a regular expression + * @return its argument + * @throws Error if argument is not a regex + */ + @SideEffectFree + // The return type annotation is a conservative bound. + public static @Regex String asRegex(String s) { + return asRegex(s, 0); + } + + /** + * Returns the argument as a {@code @Regex(groups) String} if it is a regex with at least the + * given number of groups, otherwise throws an error. The purpose of this method is to suppress + * Regex Checker warnings. It should be very rarely needed. + * + * @param s string to check for being a regular expression + * @param groups number of groups expected + * @return its argument + * @throws Error if argument is not a regex + */ + @SuppressWarnings("regex") // RegexUtil + @SideEffectFree + // The return type annotation is irrelevant; it is special-cased by + // RegexAnnotatedTypeFactory. + public static @Regex String asRegex(String s, int groups) { + try { + Pattern p = Pattern.compile(s); + int actualGroups = getGroupCount(p); + if (actualGroups < groups) { + throw new Error(regexErrorMessage(s, groups, actualGroups)); + } + return s; + } catch (PatternSyntaxException e) { + throw new Error(e); + } + } + + /** + * Generates an error message for s when expectedGroups are needed, but s only has actualGroups. + * + * @param s string to check for being a regular expression + * @param expectedGroups the number of needed capturing groups + * @param actualGroups the number of groups that {@code s} has + * @return an error message for s when expectedGroups groups are needed, but s only has + * actualGroups groups + */ + @SideEffectFree + private static String regexErrorMessage(String s, int expectedGroups, int actualGroups) { + return "regex \"" + + s + + "\" has " + + actualGroups + + " groups, but " + + expectedGroups + + " groups are needed."; + } + + /** + * Return the count of groups in the argument. + * + * @param p pattern whose groups to count + * @return the count of groups in the argument + */ + @SuppressWarnings({"all:purity", "lock"}) // does not depend on object identity + @Pure + private static int getGroupCount(Pattern p) { + return p.matcher("").groupCount(); + } +} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signedness/SignednessUtil.java b/checker-qual/src/main/java/org/checkerframework/checker/signedness/SignednessUtil.java new file mode 100644 index 00000000000..948ab11dcbd --- /dev/null +++ b/checker-qual/src/main/java/org/checkerframework/checker/signedness/SignednessUtil.java @@ -0,0 +1,523 @@ +package org.checkerframework.checker.signedness; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import org.checkerframework.checker.signedness.qual.Unsigned; +import org.checkerframework.framework.qual.AnnotatedFor; + +/** + * Provides static utility methods for unsigned values. Most of these re-implement functionality + * that was introduced in JDK 8, making it available in earlier versions of Java. Others provide new + * functionality. {@link SignednessUtilExtra} has more methods that reference packages that Android + * does not provide. + * + * @checker_framework.manual #signedness-utilities Utility routines for manipulating unsigned values + */ +@AnnotatedFor("nullness") +public final class SignednessUtil { + + private SignednessUtil() { + throw new Error("Do not instantiate"); + } + + /** + * Wraps an unsigned byte array into a ByteBuffer. This method is a wrapper around {@link + * java.nio.ByteBuffer#wrap(byte[]) wrap(byte[])}, but assumes that the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer wrapUnsigned(@Unsigned byte[] array) { + return ByteBuffer.wrap(array); + } + + /** + * Wraps an unsigned byte array into a ByteBuffer. This method is a wrapper around {@link + * java.nio.ByteBuffer#wrap(byte[], int, int) wrap(byte[], int, int)}, but assumes that the + * input should be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer wrapUnsigned(@Unsigned byte[] array, int offset, int length) { + return ByteBuffer.wrap(array, offset, length); + } + + /** + * Gets an unsigned int from the ByteBuffer b. This method is a wrapper around {@link + * java.nio.ByteBuffer#getInt() getInt()}, but assumes that the result should be interpreted as + * unsigned. + */ + @SuppressWarnings("signedness") + public static @Unsigned int getUnsignedInt(ByteBuffer b) { + return b.getInt(); + } + + /** + * Gets an unsigned short from the ByteBuffer b. This method is a wrapper around {@link + * java.nio.ByteBuffer#getShort() getShort()}, but assumes that the result should be interpreted + * as unsigned. + */ + @SuppressWarnings("signedness") + public static @Unsigned short getUnsignedShort(ByteBuffer b) { + return b.getShort(); + } + + /** + * Gets an unsigned byte from the ByteBuffer b. This method is a wrapper around {@link + * java.nio.ByteBuffer#get() get()}, but assumes that the result should be interpreted as + * unsigned. + */ + @SuppressWarnings("signedness") + public static @Unsigned byte getUnsigned(ByteBuffer b) { + return b.get(); + } + + /** + * Gets an unsigned byte from the ByteBuffer b at i. This method is a wrapper around {@link + * java.nio.ByteBuffer#get(int) get(int)}, but assumes that the result should be interpreted as + * unsigned. + */ + @SuppressWarnings("signedness") + public static @Unsigned byte getUnsigned(ByteBuffer b, int i) { + return b.get(i); + } + + /** + * Populates an unsigned byte array from the ByteBuffer b at i with l bytes. This method is a + * wrapper around {@link java.nio.ByteBuffer#get(byte[] bs, int, int) get(byte[], int, int)}, + * but assumes that the bytes should be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer getUnsigned(ByteBuffer b, byte[] bs, int i, int l) { + return b.get(bs, i, l); + } + + /** + * Places an unsigned byte into the ByteBuffer b. This method is a wrapper around {@link + * java.nio.ByteBuffer#put(byte) put(byte)}, but assumes that the input should be interpreted as + * unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer putUnsigned(ByteBuffer b, @Unsigned byte ubyte) { + return b.put(ubyte); + } + + /** + * Places an unsigned byte into the ByteBuffer b at i. This method is a wrapper around {@link + * java.nio.ByteBuffer#put(int, byte) put(int, byte)}, but assumes that the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer putUnsigned(ByteBuffer b, int i, @Unsigned byte ubyte) { + return b.put(i, ubyte); + } + + /** + * Places an unsigned int into the IntBuffer b. This method is a wrapper around {@link + * java.nio.IntBuffer#put(int) put(int)}, but assumes that the input should be interpreted as + * unsigned. + */ + @SuppressWarnings("signedness") + public static IntBuffer putUnsigned(IntBuffer b, @Unsigned int uint) { + return b.put(uint); + } + + /** + * Places an unsigned int into the IntBuffer b at i. This method is a wrapper around {@link + * java.nio.IntBuffer#put(int, int) put(int, int)}, but assumes that the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static IntBuffer putUnsigned(IntBuffer b, int i, @Unsigned int uint) { + return b.put(i, uint); + } + + /** + * Places an unsigned int array into the IntBuffer b. This method is a wrapper around {@link + * java.nio.IntBuffer#put(int[]) put(int[])}, but assumes that the input should be interpreted + * as unsigned. + */ + @SuppressWarnings("signedness") + public static IntBuffer putUnsigned(IntBuffer b, @Unsigned int[] uints) { + return b.put(uints); + } + + /** + * Places an unsigned int array into the IntBuffer b at i with length l. This method is a + * wrapper around {@link java.nio.IntBuffer#put(int[], int, int) put(int[], int, int)}, but + * assumes that the input should be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static IntBuffer putUnsigned(IntBuffer b, @Unsigned int[] uints, int i, int l) { + return b.put(uints, i, l); + } + + /** + * Gets an unsigned int from the IntBuffer b at i. This method is a wrapper around {@link + * java.nio.IntBuffer#get(int) get(int)}, but assumes that the output should be interpreted as + * unsigned. + */ + @SuppressWarnings("signedness") + public static @Unsigned int getUnsigned(IntBuffer b, int i) { + return b.get(i); + } + + /** + * Places an unsigned short into the ByteBuffer b. This method is a wrapper around {@link + * java.nio.ByteBuffer#putShort(short) putShort(short)}, but assumes that the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer putUnsignedShort(ByteBuffer b, @Unsigned short ushort) { + return b.putShort(ushort); + } + + /** + * Places an unsigned short into the ByteBuffer b at i. This method is a wrapper around {@link + * java.nio.ByteBuffer#putShort(int, short) putShort(int, short)}, but assumes that the input + * should be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer putUnsignedShort(ByteBuffer b, int i, @Unsigned short ushort) { + return b.putShort(i, ushort); + } + + /** + * Places an unsigned int into the ByteBuffer b. This method is a wrapper around {@link + * java.nio.ByteBuffer#putInt(int) putInt(int)}, but assumes that the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer putUnsignedInt(ByteBuffer b, @Unsigned int uint) { + return b.putInt(uint); + } + + /** + * Places an unsigned int into the ByteBuffer b at i. This method is a wrapper around {@link + * java.nio.ByteBuffer#putInt(int, int) putInt(int, int)}, but assumes that the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer putUnsignedInt(ByteBuffer b, int i, @Unsigned int uint) { + return b.putInt(i, uint); + } + + /** + * Places an unsigned long into the ByteBuffer b at i. This method is a wrapper around {@link + * java.nio.ByteBuffer#putLong(int, long) putLong(int, long)}, but assumes that the input should + * be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static ByteBuffer putUnsignedLong(ByteBuffer b, int i, @Unsigned long ulong) { + return b.putLong(i, ulong); + } + + /** + * Reads an unsigned char from the RandomAccessFile f. This method is a wrapper around {@link + * java.io.RandomAccessFile#readChar() readChar()}, but assumes the output should be interpreted + * as unsigned. + */ + @SuppressWarnings("signedness") + public static @Unsigned char readUnsignedChar(RandomAccessFile f) throws IOException { + return f.readChar(); + } + + /** + * Reads an unsigned int from the RandomAccessFile f. This method is a wrapper around {@link + * java.io.RandomAccessFile#readInt() readInt()}, but assumes the output should be interpreted + * as unsigned. + */ + @SuppressWarnings("signedness") + public static @Unsigned int readUnsignedInt(RandomAccessFile f) throws IOException { + return f.readInt(); + } + + /** + * Reads an unsigned long from the RandomAccessFile f. This method is a wrapper around {@link + * java.io.RandomAccessFile#readLong() readLong()}, but assumes the output should be interpreted + * as unsigned. + */ + @SuppressWarnings("signedness") + public static @Unsigned long readUnsignedLong(RandomAccessFile f) throws IOException { + return f.readLong(); + } + + /** + * Reads up to {@code len} bytes of data from this file into an unsigned array of bytes. This + * method is a wrapper around {@link java.io.RandomAccessFile#read(byte[], int, int) + * read(byte[], int, int)}, but assumes the output should be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static int readUnsigned(RandomAccessFile f, @Unsigned byte b[], int off, int len) + throws IOException { + return f.read(b, off, len); + } + + /** + * Reads a file fully into an unsigned byte array. This method is a wrapper around {@link + * java.io.RandomAccessFile#readFully(byte[]) readFully(byte[])}, but assumes the output should + * be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static void readFullyUnsigned(RandomAccessFile f, @Unsigned byte b[]) + throws IOException { + f.readFully(b); + } + + /** + * Writes len unsigned bytes to the RandomAccessFile f at offset off. This method is a wrapper + * around {@link java.io.RandomAccessFile#write(byte[], int, int) write(byte[], int, int)}, but + * assumes the input should be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static void writeUnsigned(RandomAccessFile f, @Unsigned byte[] bs, int off, int len) + throws IOException { + f.write(bs, off, len); + } + + /** + * Writes an unsigned byte to the RandomAccessFile f. This method is a wrapper around {@link + * java.io.RandomAccessFile#writeByte(int) writeByte(int)}, but assumes the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static void writeUnsignedByte(RandomAccessFile f, @Unsigned byte b) throws IOException { + f.writeByte(Byte.toUnsignedInt(b)); + } + + /** + * Writes an unsigned char to the RandomAccessFile f. This method is a wrapper around {@link + * java.io.RandomAccessFile#writeChar(int) writeChar(int)}, but assumes the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static void writeUnsignedChar(RandomAccessFile f, @Unsigned char c) throws IOException { + f.writeChar(toUnsignedInt(c)); + } + + /** + * Writes an unsigned short to the RandomAccessFile f. This method is a wrapper around {@link + * java.io.RandomAccessFile#writeShort(int) writeShort(int)}, but assumes the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static void writeUnsignedShort(RandomAccessFile f, @Unsigned short s) + throws IOException { + f.writeShort(Short.toUnsignedInt(s)); + } + + /** + * Writes an unsigned byte to the RandomAccessFile f. This method is a wrapper around {@link + * java.io.RandomAccessFile#writeInt(int) writeInt(int)}, but assumes the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static void writeUnsignedInt(RandomAccessFile f, @Unsigned int i) throws IOException { + f.writeInt(i); + } + + /** + * Writes an unsigned byte to the RandomAccessFile f. This method is a wrapper around {@link + * java.io.RandomAccessFile#writeLong(long) writeLong(long)}, but assumes the input should be + * interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static void writeUnsignedLong(RandomAccessFile f, @Unsigned long l) throws IOException { + f.writeLong(l); + } + + /** + * Gets an array of unsigned bytes from the ByteBuffer b and stores them in the array bs. This + * method is a wrapper around {@link java.nio.ByteBuffer#get(byte[]) get(byte[])}, but assumes + * that the array of bytes should be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static void getUnsigned(ByteBuffer b, @Unsigned byte[] bs) { + b.get(bs); + } + + /** + * Compares two unsigned shorts x and y. + * + * @return a negative number iff x {@literal <} y, a positive number iff x {@literal >} y, and + * zero iff x == y. + */ + @SuppressWarnings("signedness") + public static int compareUnsigned(@Unsigned short x, @Unsigned short y) { + return Integer.compareUnsigned(Short.toUnsignedInt(x), Short.toUnsignedInt(y)); + } + + /** + * Compares two unsigned bytes x and y. + * + * @return a negative number iff x {@literal <} y, a positive number iff x {@literal >} y, and + * zero iff x == y. + */ + @SuppressWarnings("signedness") + public static int compareUnsigned(@Unsigned byte x, @Unsigned byte y) { + return Integer.compareUnsigned(Byte.toUnsignedInt(x), Byte.toUnsignedInt(y)); + } + + /** Produces a string representation of the unsigned short s. */ + @SuppressWarnings("signedness") + public static String toUnsignedString(@Unsigned short s) { + return Long.toString(Short.toUnsignedLong(s)); + } + + /** Produces a string representation of the unsigned short s in base radix. */ + @SuppressWarnings("signedness") + public static String toUnsignedString(@Unsigned short s, int radix) { + return Integer.toUnsignedString(Short.toUnsignedInt(s), radix); + } + + /** Produces a string representation of the unsigned byte b. */ + @SuppressWarnings("signedness") + public static String toUnsignedString(@Unsigned byte b) { + return Integer.toUnsignedString(Byte.toUnsignedInt(b)); + } + + /** Produces a string representation of the unsigned byte b in base radix. */ + @SuppressWarnings("signedness") + public static String toUnsignedString(@Unsigned byte b, int radix) { + return Integer.toUnsignedString(Byte.toUnsignedInt(b), radix); + } + + /* + * Creates a BigInteger representing the same value as unsigned long. + * + * This is a reimplementation of Java 8's + * {@link Long.toUnsignedBigInteger(long)}. + */ + @SuppressWarnings("signedness") + private static @Unsigned BigInteger toUnsignedBigInteger(@Unsigned long l) { + // Java 8 version: return Long.toUnsignedBigInteger(l); + if (l >= 0L) { + return BigInteger.valueOf(l); + } else { + int upper = (int) (l >>> 32); + int lower = (int) l; + + // return (upper << 32) + lower + return BigInteger.valueOf(Integer.toUnsignedLong(upper)) + .shiftLeft(32) + .add(BigInteger.valueOf(Integer.toUnsignedLong(lower))); + } + } + + /** Returns an unsigned short representing the same value as an unsigned byte. */ + public static @Unsigned short toUnsignedShort(@Unsigned byte b) { + return (short) (((int) b) & 0xff); + } + + /** Returns an unsigned long representing the same value as an unsigned char. */ + public static @Unsigned long toUnsignedLong(@Unsigned char c) { + return ((long) c) & 0xffL; + } + + /** Returns an unsigned int representing the same value as an unsigned char. */ + public static @Unsigned int toUnsignedInt(@Unsigned char c) { + return ((int) c) & 0xff; + } + + /** Returns an unsigned short representing the same value as an unsigned char. */ + public static @Unsigned short toUnsignedShort(@Unsigned char c) { + return (short) (((int) c) & 0xff); + } + + /** Returns a float representing the same value as the unsigned byte. */ + public static float toFloat(@Unsigned byte b) { + return toUnsignedBigInteger(Byte.toUnsignedLong(b)).floatValue(); + } + + /** Returns a float representing the same value as the unsigned short. */ + public static float toFloat(@Unsigned short s) { + return toUnsignedBigInteger(Short.toUnsignedLong(s)).floatValue(); + } + + /** Returns a float representing the same value as the unsigned int. */ + public static float toFloat(@Unsigned int i) { + return toUnsignedBigInteger(Integer.toUnsignedLong(i)).floatValue(); + } + + /** Returns a float representing the same value as the unsigned long. */ + public static float toFloat(@Unsigned long l) { + return toUnsignedBigInteger(l).floatValue(); + } + + /** Returns a double representing the same value as the unsigned byte. */ + public static double toDouble(@Unsigned byte b) { + return toUnsignedBigInteger(Byte.toUnsignedLong(b)).doubleValue(); + } + + /** Returns a double representing the same value as the unsigned short. */ + public static double toDouble(@Unsigned short s) { + return toUnsignedBigInteger(Short.toUnsignedLong(s)).doubleValue(); + } + + /** Returns a double representing the same value as the unsigned int. */ + public static double toDouble(@Unsigned int i) { + return toUnsignedBigInteger(Integer.toUnsignedLong(i)).doubleValue(); + } + + /** Returns a double representing the same value as the unsigned long. */ + public static double toDouble(@Unsigned long l) { + return toUnsignedBigInteger(l).doubleValue(); + } + + /** Returns an unsigned byte representing the same value as the float. */ + @SuppressWarnings("signedness") + public static @Unsigned byte byteFromFloat(float f) { + assert f >= 0; + return (byte) f; + } + + /** Returns an unsigned short representing the same value as the float. */ + @SuppressWarnings("signedness") + public static @Unsigned short shortFromFloat(float f) { + assert f >= 0; + return (short) f; + } + + /** Returns an unsigned int representing the same value as the float. */ + @SuppressWarnings("signedness") + public static @Unsigned int intFromFloat(float f) { + assert f >= 0; + return (int) f; + } + + /** Returns an unsigned long representing the same value as the float. */ + @SuppressWarnings("signedness") + public static @Unsigned long longFromFloat(float f) { + assert f >= 0; + return (long) f; + } + + /** Returns an unsigned byte representing the same value as the double. */ + @SuppressWarnings("signedness") + public static @Unsigned byte byteFromDouble(double d) { + assert d >= 0; + return (byte) d; + } + + /** Returns an unsigned short representing the same value as the double. */ + @SuppressWarnings("signedness") + public static @Unsigned short shortFromDouble(double d) { + assert d >= 0; + return (short) d; + } + + /** Returns an unsigned int representing the same value as the double. */ + @SuppressWarnings("signedness") + public static @Unsigned int intFromDouble(double d) { + assert d >= 0; + return (int) d; + } + + /** Returns an unsigned long representing the same value as the double. */ + @SuppressWarnings("signedness") + public static @Unsigned long longFromDouble(double d) { + assert d >= 0; + return (long) d; + } +} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signedness/SignednessUtilExtra.java b/checker-qual/src/main/java/org/checkerframework/checker/signedness/SignednessUtilExtra.java new file mode 100644 index 00000000000..6b38b1d5202 --- /dev/null +++ b/checker-qual/src/main/java/org/checkerframework/checker/signedness/SignednessUtilExtra.java @@ -0,0 +1,68 @@ +package org.checkerframework.checker.signedness; + +import java.awt.Dimension; +import java.awt.image.BufferedImage; +import org.checkerframework.checker.signedness.qual.Unsigned; +import org.checkerframework.framework.qual.AnnotatedFor; + +/** + * Provides more static utility methods for unsigned values. These methods use Java packages not + * included in Android. {@link SignednessUtil} has more methods. + * + * @checker_framework.manual #signedness-utilities Utility routines for manipulating unsigned values + */ +@AnnotatedFor("nullness") +public class SignednessUtilExtra { + /** Do not instantiate this class. */ + private SignednessUtilExtra() { + throw new Error("Do not instantiate"); + } + + /** Gets the unsigned width of a {@code Dimension}. */ + @SuppressWarnings("signedness") + public static @Unsigned int dimensionUnsignedWidth(Dimension dim) { + return dim.width; + } + + /** Gets the unsigned height of a {@code Dimension}. */ + @SuppressWarnings("signedness") + public static @Unsigned int dimensionUnsignedHeight(Dimension dim) { + return dim.height; + } + + /** + * Sets rgb of BufferedImage b given unsigned ints. This method is a wrapper around {@link + * java.awt.image.BufferedImage#setRGB(int, int, int, int, int[], int, int) setRGB(int, int, + * int, int, int[], int, int)}, but assumes that the input should be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static void setUnsignedRGB( + BufferedImage b, + int startX, + int startY, + int w, + int h, + @Unsigned int[] rgbArray, + int offset, + int scansize) { + b.setRGB(startX, startY, w, h, rgbArray, offset, scansize); + } + + /** + * Gets rgb of BufferedImage b as unsigned ints. This method is a wrapper around {@link + * java.awt.image.BufferedImage#getRGB(int, int, int, int, int[], int, int) getRGB(int, int, + * int, int, int[], int, int)}, but assumes that the output should be interpreted as unsigned. + */ + @SuppressWarnings("signedness") + public static @Unsigned int[] getUnsignedRGB( + BufferedImage b, + int startX, + int startY, + int w, + int h, + @Unsigned int[] rgbArray, + int offset, + int scansize) { + return b.getRGB(startX, startY, w, h, rgbArray, offset, scansize); + } +} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/UnitsTools.java b/checker-qual/src/main/java/org/checkerframework/checker/units/UnitsTools.java new file mode 100644 index 00000000000..56f128df42c --- /dev/null +++ b/checker-qual/src/main/java/org/checkerframework/checker/units/UnitsTools.java @@ -0,0 +1,138 @@ +package org.checkerframework.checker.units; + +import org.checkerframework.checker.units.qual.A; +import org.checkerframework.checker.units.qual.C; +import org.checkerframework.checker.units.qual.K; +import org.checkerframework.checker.units.qual.cd; +import org.checkerframework.checker.units.qual.degrees; +import org.checkerframework.checker.units.qual.g; +import org.checkerframework.checker.units.qual.h; +import org.checkerframework.checker.units.qual.kg; +import org.checkerframework.checker.units.qual.km; +import org.checkerframework.checker.units.qual.km2; +import org.checkerframework.checker.units.qual.kmPERh; +import org.checkerframework.checker.units.qual.m; +import org.checkerframework.checker.units.qual.m2; +import org.checkerframework.checker.units.qual.mPERs; +import org.checkerframework.checker.units.qual.mPERs2; +import org.checkerframework.checker.units.qual.min; +import org.checkerframework.checker.units.qual.mm; +import org.checkerframework.checker.units.qual.mm2; +import org.checkerframework.checker.units.qual.mol; +import org.checkerframework.checker.units.qual.radians; +import org.checkerframework.checker.units.qual.s; +import org.checkerframework.framework.qual.AnnotatedFor; + +// TODO: add fromTo methods for all useful unit combinations. + +/** Utility methods to generate annotated types and to convert between them. */ +@SuppressWarnings({"units", "checkstyle:constantname"}) +@AnnotatedFor("nullness") +public class UnitsTools { + // Acceleration + public static final @mPERs2 int mPERs2 = 1; + + // Angle + public static final @radians double rad = 1; + public static final @degrees double deg = 1; + + public static @radians double toRadians(@degrees double angdeg) { + return Math.toRadians(angdeg); + } + + public static @degrees double toDegrees(@radians double angrad) { + return Math.toDegrees(angrad); + } + + // Area + public static final @mm2 int mm2 = 1; + public static final @m2 int m2 = 1; + public static final @km2 int km2 = 1; + + // Current + public static final @A int A = 1; + + // Luminance + public static final @cd int cd = 1; + + // Lengths + public static final @mm int mm = 1; + public static final @m int m = 1; + public static final @km int km = 1; + + public static @m int fromMilliMeterToMeter(@mm int mm) { + return mm / 1000; + } + + public static @mm int fromMeterToMilliMeter(@m int m) { + return m * 1000; + } + + public static @km int fromMeterToKiloMeter(@m int m) { + return m / 1000; + } + + public static @m int fromKiloMeterToMeter(@km int km) { + return km * 1000; + } + + // Mass + public static final @g int g = 1; + public static final @kg int kg = 1; + + public static @kg int fromGramToKiloGram(@g int g) { + return g / 1000; + } + + public static @g int fromKiloGramToGram(@kg int kg) { + return kg * 1000; + } + + // Speed + public static final @mPERs int mPERs = 1; + public static final @kmPERh int kmPERh = 1; + + public static @kmPERh double fromMeterPerSecondToKiloMeterPerHour(@mPERs double mps) { + return mps * 3.6d; + } + + public static @mPERs double fromKiloMeterPerHourToMeterPerSecond(@kmPERh double kmph) { + return kmph / 3.6d; + } + + // Substance + public static final @mol int mol = 1; + + // Temperature + public static final @K int K = 1; + public static final @C int C = 1; + + public static @C int fromKelvinToCelsius(@K int k) { + return k - (int) 273.15; + } + + public static @K int fromCelsiusToKelvin(@C int c) { + return c + (int) 273.15; + } + + // Time + public static final @s int s = 1; + public static final @min int min = 1; + public static final @h int h = 1; + + public static @min int fromSecondToMinute(@s int s) { + return s / 60; + } + + public static @s int fromMinuteToSecond(@min int min) { + return min * 60; + } + + public static @h int fromMinuteToHour(@min int min) { + return min / 60; + } + + public static @min int fromHourToMinute(@h int h) { + return h * 60; + } +} diff --git a/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java new file mode 100644 index 00000000000..505c053a6b7 --- /dev/null +++ b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java @@ -0,0 +1,28 @@ +package org.checkerframework.dataflow.qual; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.checkerframework.framework.qual.JavaExpression; + +/** + * This method declaration annotation can be used to specify the expressions that a method side + * effects. In other words, the method only side effects those expressions that are supplied as + * annotation values to {@code @SideEffectsOnly}. + * + * @checker_framework.manual #type-refinement-purity Side effects only + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) +public @interface SideEffectsOnly { + /** + * The expressions that this method side effects. + * + * @return Java expressions that are side-effected by this method + */ + @JavaExpression + public String[] value(); +} diff --git a/checker-qual/src/main/java/org/checkerframework/framework/util/PurityUnqualified.java b/checker-qual/src/main/java/org/checkerframework/framework/util/PurityUnqualified.java new file mode 100644 index 00000000000..4c51c1bc0b1 --- /dev/null +++ b/checker-qual/src/main/java/org/checkerframework/framework/util/PurityUnqualified.java @@ -0,0 +1,24 @@ +package org.checkerframework.framework.util; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; + +/** + * An annotation intended solely for representing an unqualified type in the qualifier hierarchy for + * the Purity Checker. + * + * @checker_framework.manual #purity-checker Purity Checker + */ +@Documented +@Retention(RetentionPolicy.SOURCE) // do not store in .class file +@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) +@SubtypeOf({}) +@DefaultQualifierInHierarchy +@InvisibleQualifier +public @interface PurityUnqualified {} From 1c9d4adb6e287ddf5f7815c2caf1dba70e72c54d Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Tue, 16 Feb 2021 15:49:54 -0800 Subject: [PATCH 018/113] remove unrelated --- .../checker/iteration/qual/HasNext.java | 14 ------ .../iteration/qual/UnknownHasNext.java | 14 ------ .../nonempty/qual/EnsuresNonEmpty.java | 42 ---------------- .../nonempty/qual/EnsuresNonEmptyIf.java | 49 ------------------- .../checker/nonempty/qual/NonEmpty.java | 22 --------- .../checker/nonempty/qual/PolyNonEmpty.java | 25 ---------- .../nonempty/qual/UnknownNonEmpty.java | 27 ---------- 7 files changed, 193 deletions(-) delete mode 100644 checker-qual/src/main/java/org/checkerframework/checker/iteration/qual/HasNext.java delete mode 100644 checker-qual/src/main/java/org/checkerframework/checker/iteration/qual/UnknownHasNext.java delete mode 100644 checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/EnsuresNonEmpty.java delete mode 100644 checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/EnsuresNonEmptyIf.java delete mode 100644 checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/NonEmpty.java delete mode 100644 checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/PolyNonEmpty.java delete mode 100644 checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/UnknownNonEmpty.java diff --git a/checker-qual/src/main/java/org/checkerframework/checker/iteration/qual/HasNext.java b/checker-qual/src/main/java/org/checkerframework/checker/iteration/qual/HasNext.java deleted file mode 100644 index 9a075350a70..00000000000 --- a/checker-qual/src/main/java/org/checkerframework/checker/iteration/qual/HasNext.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.checkerframework.checker.iteration.qual; - -import java.lang.annotation.*; -import org.checkerframework.framework.qual.*; - -/** - * An expression whose type has this annotation is an iterator that has a next value -- that is, - * {@code next()} will not throw NoSuchElementException. - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) -@SubtypeOf({UnknownHasNext.class}) -public @interface HasNext {} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/iteration/qual/UnknownHasNext.java b/checker-qual/src/main/java/org/checkerframework/checker/iteration/qual/UnknownHasNext.java deleted file mode 100644 index 0d062975860..00000000000 --- a/checker-qual/src/main/java/org/checkerframework/checker/iteration/qual/UnknownHasNext.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.checkerframework.checker.iteration.qual; - -import java.lang.annotation.*; -import org.checkerframework.framework.qual.*; - -/** Either a non-iterator, or an iterator that might or might not have a next element. */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) -@SubtypeOf({}) -@DefaultQualifierInHierarchy -@DefaultFor(value = TypeUseLocation.LOWER_BOUND, types = Void.class) -@QualifierForLiterals(LiteralKind.NULL) -public @interface UnknownHasNext {} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/EnsuresNonEmpty.java b/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/EnsuresNonEmpty.java deleted file mode 100644 index cc57a1f1b23..00000000000 --- a/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/EnsuresNonEmpty.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.checkerframework.checker.nonempty.qual; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import org.checkerframework.framework.qual.InheritedAnnotation; -import org.checkerframework.framework.qual.JavaExpression; -import org.checkerframework.framework.qual.PostconditionAnnotation; - -/** - * Indicates that the value expressions are non-empty if the method terminates successfully. - * - *

    Consider the following method from {@code java.util.Map}: - * - *

    - * @EnsuresNonEmpty(expression="this")
    - * public @Nullable V put(K key, V value) { ... }
    - * 
    - * - *

    This method guarantees that {@code this} has type {@code @NonEmpty} after the method returns. - * - * @see NonEmpty - * @see EnsuresNonEmptyIf - * @checker_framework.manual #map-key-checker Map Key Checker - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) -@PostconditionAnnotation(qualifier = NonEmpty.class) -@InheritedAnnotation -public @interface EnsuresNonEmpty { - /** - * Java expressions that are non-empty on successful method termination. - * - * @return Java expressions that are non-empty on successful method termination - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - @JavaExpression - String[] value(); -} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/EnsuresNonEmptyIf.java b/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/EnsuresNonEmptyIf.java deleted file mode 100644 index dedf44111a8..00000000000 --- a/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/EnsuresNonEmptyIf.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.checkerframework.checker.nonempty.qual; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; -import org.checkerframework.framework.qual.InheritedAnnotation; - -/** - * Indicates that the given expressions are non-empty if the method returns the given result (either - * true or false). - * - *

    As an example, consider the following method in {@code java.util.Map}: - * - *

    - *   @EnsuresNonEmptyIf(result=true, expression="this")
    - *   public boolean containsKey(String key) { ... }
    - * 
    - * - * If an invocation {@code m.containsKey(k)} returns true, then the type of {@code this} can be - * inferred to be {@code @NonEmpty}. - * - * @see NonEmpty - * @see EnsuresNonEmpty - * @checker_framework.manual #map-key-checker Map Key Checker - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) -@ConditionalPostconditionAnnotation(qualifier = NonEmpty.class) -@InheritedAnnotation -public @interface EnsuresNonEmptyIf { - /** - * The value the method must return, in order for the postcondition to hold. - * - * @return The value the method must return, in order for the postcondition to hold - */ - boolean result(); - - /** - * Java expressions that are non-empty after the method returns the given result. - * - * @return Java expressions that are non-empty after the method returns the given result - * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions - */ - String[] expression(); -} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/NonEmpty.java b/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/NonEmpty.java deleted file mode 100644 index d7cc8daa458..00000000000 --- a/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/NonEmpty.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.checkerframework.checker.nonempty.qual; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TargetLocations; -import org.checkerframework.framework.qual.TypeUseLocation; - -/** - * Indicates that the collection, map, or iterator is non-empty. - * - * @checker_framework.manual #nonempty-checker NonEmpty Checker - */ -@SubtypeOf(UnknownNonEmpty.class) -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) -@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND}) -public @interface NonEmpty {} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/PolyNonEmpty.java b/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/PolyNonEmpty.java deleted file mode 100644 index 1946aed6b56..00000000000 --- a/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/PolyNonEmpty.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.checkerframework.checker.nonempty.qual; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import org.checkerframework.framework.qual.PolymorphicQualifier; - -/** - * A polymorphic qualifier for the {@code @NonEmpty} type system. - * - *

    Any method written using {@code @PolyNonEmpty} conceptually has two versions: one in which - * every instance of {@code @PolyNonEmpty} has been replaced by {@code @}{@link UnknownNonEmpty} and - * one in which every instance of {@code @PolyNonEmpty} has been replaced by {@code @}{@link - * NonEmpty}, for every possible combination of Collection, Map, or Iterator arguments. - * - * @checker_framework.manual #nonempty NonEmpty Checker - * @checker_framework.manual #qualifier-polymorphism Qualifier polymorphism - */ -@Documented -@PolymorphicQualifier(UnknownNonEmpty.class) -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) -public @interface PolyNonEmpty {} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/UnknownNonEmpty.java b/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/UnknownNonEmpty.java deleted file mode 100644 index 35ec399d1ff..00000000000 --- a/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/UnknownNonEmpty.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.checkerframework.checker.nonempty.qual; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import org.checkerframework.framework.qual.*; - -/** - * Indicates that the collection, map, or iterator not known to be a non-empty. It is the top type - * qualifier in the {@link org.checkerframework.checker.nonempty.qual.NonEmpty} hierarchy. It is - * also the default type qualifier. - * - *

    Used internally by the type system; should never be written by a programmer. - * - * @checker_framework.manual #nonempty-checker NonEmpty Checker - */ -@InvisibleQualifier -@SubtypeOf({}) -@DefaultQualifierInHierarchy -@DefaultFor({TypeUseLocation.LOWER_BOUND}) -@QualifierForLiterals(LiteralKind.NULL) -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) -public @interface UnknownNonEmpty {} From a2dd514d1c9feff613eae0a5d404f165729c1d18 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Tue, 16 Feb 2021 17:25:33 -0800 Subject: [PATCH 019/113] refactor qual --- .../dataflow/qual/SideEffectsOnly.java | 28 ------------------- 1 file changed, 28 deletions(-) delete mode 100644 dataflow/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java b/dataflow/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java deleted file mode 100644 index 505c053a6b7..00000000000 --- a/dataflow/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.checkerframework.dataflow.qual; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import org.checkerframework.framework.qual.JavaExpression; - -/** - * This method declaration annotation can be used to specify the expressions that a method side - * effects. In other words, the method only side effects those expressions that are supplied as - * annotation values to {@code @SideEffectsOnly}. - * - * @checker_framework.manual #type-refinement-purity Side effects only - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) -public @interface SideEffectsOnly { - /** - * The expressions that this method side effects. - * - * @return Java expressions that are side-effected by this method - */ - @JavaExpression - public String[] value(); -} From a0442b42c3aef2c0f9c396d34614763ebc16e8e9 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Tue, 2 Mar 2021 07:07:26 -0800 Subject: [PATCH 020/113] remove unrelated test file --- checker/tests/iteration/WhileIfTest.java | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 checker/tests/iteration/WhileIfTest.java diff --git a/checker/tests/iteration/WhileIfTest.java b/checker/tests/iteration/WhileIfTest.java deleted file mode 100644 index 53891632344..00000000000 --- a/checker/tests/iteration/WhileIfTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package iteration; - -import java.util.Iterator; - -public class WhileIfTest { - void test(Iterator itera, Iterator iterb) { - while (itera.hasNext() && iterb.hasNext()) { - itera.next(); - iterb.next(); - } - } -} From ce77c26fe2788d684aa7e9340a948495f34a4d3b Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Tue, 2 Mar 2021 10:30:55 -0800 Subject: [PATCH 021/113] documenting toy checker --- .../framework/test/junit/SideEffectsOnlyTest.java | 8 ++++++-- .../sideeffectsonly/SideEffectsOnlyChecker.java | 13 ------------- .../testchecker/sideeffectsonly/qual/Refined.java | 4 ++++ .../testchecker/sideeffectsonly/qual/Unrefined.java | 4 ++++ 4 files changed, 14 insertions(+), 15 deletions(-) delete mode 100644 framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/SideEffectsOnlyChecker.java diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/SideEffectsOnlyTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/SideEffectsOnlyTest.java index 351e7eeed37..df39d5adc24 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/SideEffectsOnlyTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/SideEffectsOnlyTest.java @@ -3,14 +3,18 @@ import java.io.File; import java.util.List; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; -import org.checkerframework.framework.testchecker.sideeffectsonly.SideEffectsOnlyChecker; import org.junit.runners.Parameterized.Parameters; public class SideEffectsOnlyTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public SideEffectsOnlyTest(List testFiles) { - super(testFiles, SideEffectsOnlyChecker.class, "sideeffectsonly", "-Anomsgtext"); + super( + testFiles, + org.checkerframework.framework.testchecker.sideeffectsonly.SideEffectsOnlyToyChecker + .class, + "sideeffectsonly", + "-Anomsgtext"); } @Parameters diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/SideEffectsOnlyChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/SideEffectsOnlyChecker.java deleted file mode 100644 index 05b9b9dc446..00000000000 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/SideEffectsOnlyChecker.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.checkerframework.framework.testchecker.sideeffectsonly; - -import org.checkerframework.common.basetype.BaseTypeChecker; -import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; - -public class SideEffectsOnlyChecker extends BaseTypeChecker { - @Override - public GenericAnnotatedTypeFactory getTypeFactory() { - GenericAnnotatedTypeFactory result = super.getTypeFactory(); - result.sideEffectsUnrefineAliases = true; - return result; - } -} diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Refined.java b/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Refined.java index 908b8b0025f..c5a879cd00a 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Refined.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Refined.java @@ -7,6 +7,10 @@ import java.lang.annotation.Target; import org.checkerframework.framework.qual.SubtypeOf; +/** + * Bottom qualifier of a toy type system. The toy type system is used to test whether dataflow + * analysis correctly type-refines methods annotated with {@code @SideEffectsOnly}. + */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Unrefined.java b/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Unrefined.java index f7c8cdb9bc4..09064d5976c 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Unrefined.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Unrefined.java @@ -8,6 +8,10 @@ import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.SubtypeOf; +/** + * Top qualifier of a toy type system. The toy type system is used to test whether dataflow analysis + * correctly type-refines methods annotated with {@code @SideEffectsOnly}. + */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) From 171090d626ed1e02a37d2f48e8c90c1e6517e373 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Tue, 2 Mar 2021 10:38:19 -0800 Subject: [PATCH 022/113] fix documentation and refactoring --- .../SideEffectsOnlyToyChecker.java | 23 +++++++++++++++++++ .../sideeffectsonly/qual/Refined.java | 3 ++- .../sideeffectsonly/qual/Unrefined.java | 3 ++- 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/SideEffectsOnlyToyChecker.java diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/SideEffectsOnlyToyChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/SideEffectsOnlyToyChecker.java new file mode 100644 index 00000000000..ea2c6d41549 --- /dev/null +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/SideEffectsOnlyToyChecker.java @@ -0,0 +1,23 @@ +package org.checkerframework.framework.testchecker.sideeffectsonly; + +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; + +/** + * Toy checker used to test whether dataflow analysis correctly type-refines methods annotated with + * {@link org.checkerframework.dataflow.qual.SideEffectsOnly}. + */ +public class SideEffectsOnlyToyChecker extends BaseTypeChecker { + /** + * Sets {@code sideEffectsUnrefineAliases} to true as {@code @SideEffectsOnly} annotation has + * effect only on methods that unrefine types. + * + * @return GenericAnnotatedTypeFactory + */ + @Override + public GenericAnnotatedTypeFactory getTypeFactory() { + GenericAnnotatedTypeFactory result = super.getTypeFactory(); + result.sideEffectsUnrefineAliases = true; + return result; + } +} diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Refined.java b/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Refined.java index c5a879cd00a..8e4732d34ef 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Refined.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Refined.java @@ -9,7 +9,8 @@ /** * Bottom qualifier of a toy type system. The toy type system is used to test whether dataflow - * analysis correctly type-refines methods annotated with {@code @SideEffectsOnly}. + * analysis correctly type-refines methods annotated with {@link + * org.checkerframework.dataflow.qual.SideEffectsOnly}. */ @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Unrefined.java b/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Unrefined.java index 09064d5976c..169d6db6877 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Unrefined.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Unrefined.java @@ -10,7 +10,8 @@ /** * Top qualifier of a toy type system. The toy type system is used to test whether dataflow analysis - * correctly type-refines methods annotated with {@code @SideEffectsOnly}. + * correctly type-refines methods annotated with {@link + * org.checkerframework.dataflow.qual.SideEffectsOnly}. */ @Documented @Retention(RetentionPolicy.RUNTIME) From 6877a371f938ef841683345815a34af15dd1e0fd Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Tue, 2 Mar 2021 11:09:01 -0800 Subject: [PATCH 023/113] fix SideEffectsOnly documentation --- .../org/checkerframework/framework/flow/CFAbstractStore.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index b98aad69410..a832cb12168 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -234,7 +234,7 @@ protected boolean isSideEffectsOnly( * org.checkerframework.dataflow.qual.SideEffectFree} or {@link * org.checkerframework.dataflow.qual.Pure}), then no information needs to be removed. *

  • If the method side effects few expressions (specified as annotation values of - * {@code @SideEffectFree}), then information about those expressions is removed. + * {@code @SideEffectsOnly}), then information about those expressions is removed. *
  • Otherwise, all information about field accesses {@code a.f} needs to be removed, except * if the method {@code n} cannot modify {@code a.f} (e.g., if {@code a} is a local * variable or {@code this}, and {@code f} is final). From 8a04d7f083ab4ab7d7f8fcd30f3cede29ffd024c Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Tue, 2 Mar 2021 11:18:16 -0800 Subject: [PATCH 024/113] improves documentation for SideEffectsOnly --- docs/manual/advanced-features.tex | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/manual/advanced-features.tex b/docs/manual/advanced-features.tex index e6e1db7e83e..c283f04a8e8 100644 --- a/docs/manual/advanced-features.tex +++ b/docs/manual/advanced-features.tex @@ -1004,11 +1004,17 @@ the refined type, because the method might assign a field. The \refqualclass{dataflow/qual}{SideEffectFree} annotation indicates that the method has no side effects, so calling it does not invalidate any -dataflow facts. The \refqualclass{dataflow/qual}{SideEffectsOnly} annotation +dataflow facts. + +The \refqualclass{dataflow/qual}{SideEffectsOnly} annotation specifies the expressions that the method side effects. So expressions that are not specified as annotation values of -\refqualclass{dataflow/qual}{SideEffectsOnly} have thier dataflow facts -unaffected. +\refqualclass{dataflow/qual}{SideEffectsOnly} have their dataflow facts +unaffected. If a method has neither \refqualclass{dataflow/qual}{SideEffectsOnly} +nor \refqualclass{dataflow/qual}{SideEffectFree} annotation, then it is assumed +that the method side-effects all expressions (fields and arguments). +A method cannot be annotated with both \refqualclass{dataflow/qual}{SideEffectFree} +and \refqualclass{dataflow/qual}{SideEffectsOnly}. Calling a method twice might have different results, so facts known about one call cannot be relied upon at another call. From 245984b54f4decd28c00ee4725567b453ecd49c2 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Tue, 2 Mar 2021 11:39:26 -0800 Subject: [PATCH 025/113] more documentation for SideEffectsOnly --- docs/manual/advanced-features.tex | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/manual/advanced-features.tex b/docs/manual/advanced-features.tex index c283f04a8e8..5f1708f4ef3 100644 --- a/docs/manual/advanced-features.tex +++ b/docs/manual/advanced-features.tex @@ -1010,7 +1010,10 @@ specifies the expressions that the method side effects. So expressions that are not specified as annotation values of \refqualclass{dataflow/qual}{SideEffectsOnly} have their dataflow facts -unaffected. If a method has neither \refqualclass{dataflow/qual}{SideEffectsOnly} +unaffected. +Without this annotation, dataflow anaysis soundly assumes that all +expressions are side-effected. +If a method has neither \refqualclass{dataflow/qual}{SideEffectsOnly} nor \refqualclass{dataflow/qual}{SideEffectFree} annotation, then it is assumed that the method side-effects all expressions (fields and arguments). A method cannot be annotated with both \refqualclass{dataflow/qual}{SideEffectFree} From ec10c5205681bac4ed08ecf42441c62a3a8791ad Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Thu, 4 Mar 2021 10:42:55 -0800 Subject: [PATCH 026/113] addressing review --- .../org/checkerframework/framework/flow/CFAbstractStore.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index a832cb12168..dfd03a26f92 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -195,10 +195,7 @@ protected boolean isSideEffectsOnly( AnnotatedTypeFactory atypeFactory, ExecutableElement method) { AnnotationMirror sefOnlyAnnotation = atypeFactory.getDeclAnnotation(method, SideEffectsOnly.class); - if (sefOnlyAnnotation != null) { - return true; - } - return false; + return sefOnlyAnnotation != null; } /** From 201d09b9342a2e9851827b5d29b2007350485c63 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Thu, 4 Mar 2021 10:50:08 -0800 Subject: [PATCH 027/113] addressing review: entryset() to keyset() --- .../framework/flow/CFAbstractStore.java | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index dfd03a26f92..ce6d1686983 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -289,22 +289,17 @@ public void updateForMethodCall( final List expressionsToRemove = sideEffectExpressions; localVariableValues - .entrySet() + .keySet() .removeIf( e -> - (expressionsToRemove.contains(e.getKey().toString()) - && !e.getKey() - .isUnmodifiableByOtherCode()) + (expressionsToRemove.contains(e.toString()) + && !e.isUnmodifiableByOtherCode()) || (expressionsToRemove.contains("this") - && e.getKey() - .toString() + && e.toString() .equals(receiver.toString()) - && !e.getKey() - .isUnmodifiableByOtherCode())); + && !e.isUnmodifiableByOtherCode())); } else { - localVariableValues - .entrySet() - .removeIf(e -> !e.getKey().isUnmodifiableByOtherCode()); + localVariableValues.keySet().removeIf(e -> !e.isUnmodifiableByOtherCode()); } } @@ -324,13 +319,13 @@ public void updateForMethodCall( if (sideEffectExpressions != null) { final List expressionsToRemove = sideEffectExpressions; fieldValues - .entrySet() + .keySet() .removeIf( e -> - expressionsToRemove.contains(e.getKey().toString()) - && !e.getKey().isUnmodifiableByOtherCode()); + expressionsToRemove.contains(e.toString()) + && !e.isUnmodifiableByOtherCode()); } else { - fieldValues.entrySet().removeIf(e -> !e.getKey().isUnmodifiableByOtherCode()); + fieldValues.keySet().removeIf(e -> !e.isUnmodifiableByOtherCode()); } } else { Map newFieldValues = new HashMap<>(); @@ -386,7 +381,7 @@ public void updateForMethodCall( arrayValues.clear(); // update method values - methodValues.entrySet().removeIf(e -> !e.getKey().isUnmodifiableByOtherCode()); + methodValues.keySet().removeIf(e -> !e.isUnmodifiableByOtherCode()); } // store information about method call if possible From 39f004421696ec8cf11e2b6b812b468b74ae5200 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Thu, 4 Mar 2021 11:18:52 -0800 Subject: [PATCH 028/113] give meaningful qualifier names --- ...ned.java => SideEffectsOnlyToyBottom.java} | 4 ++-- ...efined.java => SideEffectsOnlyToyTop.java} | 2 +- .../sideeffectsonly/SideEffectsMultiple.java | 14 ++++++++++---- .../sideeffectsonly/SideEffectsOnlyTest.java | 19 ++++++++++++++----- .../sideeffectsonly/SideEffectsTest.java | 17 +++++++++++++---- 5 files changed, 40 insertions(+), 16 deletions(-) rename framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/{Refined.java => SideEffectsOnlyToyBottom.java} (81%) rename framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/{Unrefined.java => SideEffectsOnlyToyTop.java} (94%) diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Refined.java b/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/SideEffectsOnlyToyBottom.java similarity index 81% rename from framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Refined.java rename to framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/SideEffectsOnlyToyBottom.java index 8e4732d34ef..384da472294 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Refined.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/SideEffectsOnlyToyBottom.java @@ -15,5 +15,5 @@ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) -@SubtypeOf({Unrefined.class}) -public @interface Refined {} +@SubtypeOf({org.checkerframework.framework.testchecker.sideeffectsonly.qual.SideEffectsOnlyToyTop.class}) +public @interface SideEffectsOnlyToyBottom {} diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Unrefined.java b/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/SideEffectsOnlyToyTop.java similarity index 94% rename from framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Unrefined.java rename to framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/SideEffectsOnlyToyTop.java index 169d6db6877..d24fde75e72 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/Unrefined.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/SideEffectsOnlyToyTop.java @@ -18,4 +18,4 @@ @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @DefaultQualifierInHierarchy @SubtypeOf({}) -public @interface Unrefined {} +public @interface SideEffectsOnlyToyTop {} diff --git a/framework/tests/sideeffectsonly/SideEffectsMultiple.java b/framework/tests/sideeffectsonly/SideEffectsMultiple.java index af9561f0580..4c9fe957b40 100644 --- a/framework/tests/sideeffectsonly/SideEffectsMultiple.java +++ b/framework/tests/sideeffectsonly/SideEffectsMultiple.java @@ -2,7 +2,6 @@ import org.checkerframework.dataflow.qual.SideEffectsOnly; import org.checkerframework.framework.qual.EnsuresQualifier; -import org.checkerframework.framework.testchecker.sideeffectsonly.qual.Refined; public class SideEffectsMultiple { void test(Object x) { @@ -15,12 +14,19 @@ void test(Object x) { @EnsuresQualifier( expression = "#1", qualifier = - org.checkerframework.framework.testchecker.sideeffectsonly.qual.Refined.class) + org.checkerframework.framework.testchecker.sideeffectsonly.qual + .SideEffectsOnlyToyBottom.class) // :: error: contracts.postcondition.not.satisfied void method(Object x) {} @SideEffectsOnly({"this", "x"}) - void method1(@Refined Object x) {} + void method1( + @org.checkerframework.framework.testchecker.sideeffectsonly.qual + .SideEffectsOnlyToyBottom + Object x) {} - void method2(@Refined Object x) {} + void method2( + @org.checkerframework.framework.testchecker.sideeffectsonly.qual + .SideEffectsOnlyToyBottom + Object x) {} } diff --git a/framework/tests/sideeffectsonly/SideEffectsOnlyTest.java b/framework/tests/sideeffectsonly/SideEffectsOnlyTest.java index 9f9c293264e..8ab648bbdfe 100644 --- a/framework/tests/sideeffectsonly/SideEffectsOnlyTest.java +++ b/framework/tests/sideeffectsonly/SideEffectsOnlyTest.java @@ -2,7 +2,6 @@ import org.checkerframework.dataflow.qual.SideEffectsOnly; import org.checkerframework.framework.qual.EnsuresQualifier; -import org.checkerframework.framework.testchecker.sideeffectsonly.qual.Refined; public class SideEffectsOnlyTest { void test(Object x) { @@ -17,16 +16,26 @@ void test(Object x) { @EnsuresQualifier( expression = "#1", qualifier = - org.checkerframework.framework.testchecker.sideeffectsonly.qual.Refined.class) + org.checkerframework.framework.testchecker.sideeffectsonly.qual + .SideEffectsOnlyToyBottom.class) // :: error: contracts.postcondition.not.satisfied void method(Object x) {} @SideEffectsOnly({"this"}) - void method1(@Refined Object x) {} + void method1( + @org.checkerframework.framework.testchecker.sideeffectsonly.qual + .SideEffectsOnlyToyBottom + Object x) {} @SideEffectsOnly({"x"}) - void method2(@Refined Object x) {} + void method2( + @org.checkerframework.framework.testchecker.sideeffectsonly.qual + .SideEffectsOnlyToyBottom + Object x) {} @SideEffectsOnly({"this"}) - void method3(@Refined Object x) {} + void method3( + @org.checkerframework.framework.testchecker.sideeffectsonly.qual + .SideEffectsOnlyToyBottom + Object x) {} } diff --git a/framework/tests/sideeffectsonly/SideEffectsTest.java b/framework/tests/sideeffectsonly/SideEffectsTest.java index 38e003adb8e..8cfc7caa0f1 100644 --- a/framework/tests/sideeffectsonly/SideEffectsTest.java +++ b/framework/tests/sideeffectsonly/SideEffectsTest.java @@ -1,7 +1,6 @@ package sideeffectsonly; import org.checkerframework.framework.qual.EnsuresQualifier; -import org.checkerframework.framework.testchecker.sideeffectsonly.qual.Refined; public class SideEffectsTest { void test(Object x) { @@ -11,11 +10,21 @@ void test(Object x) { method2(x); } - @EnsuresQualifier(expression = "#1", qualifier = Refined.class) + @EnsuresQualifier( + expression = "#1", + qualifier = + org.checkerframework.framework.testchecker.sideeffectsonly.qual + .SideEffectsOnlyToyBottom.class) // :: error: contracts.postcondition.not.satisfied void method(Object x) {} - void method1(@Refined Object x) {} + void method1( + @org.checkerframework.framework.testchecker.sideeffectsonly.qual + .SideEffectsOnlyToyBottom + Object x) {} - void method2(@Refined Object x) {} + void method2( + @org.checkerframework.framework.testchecker.sideeffectsonly.qual + .SideEffectsOnlyToyBottom + Object x) {} } From 835712a1f07556e115b7517cfdcef82758f401b8 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Thu, 4 Mar 2021 11:34:22 -0800 Subject: [PATCH 029/113] address review --- .../framework/flow/CFAbstractStore.java | 20 ++++++++++--------- .../qual/SideEffectsOnlyToyBottom.java | 4 +++- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index ce6d1686983..70c05bff58a 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -245,7 +245,9 @@ public void updateForMethodCall( MethodInvocationNode n, AnnotatedTypeFactory atypeFactory, V val) { ExecutableElement method = n.getTarget().getMethod(); - List sideEffectExpressions = null; + // List of expressions that this method side effects (specified as annotation values of + // @SideEffectsOnly). If the value is null, then there is no @SideEffectsOnly annotation. + List sideEffectsOnlyExpressions = null; if (isSideEffectsOnly(atypeFactory, method)) { Map valmap = getSideEffectsOnlyValues(atypeFactory, method); @@ -262,11 +264,11 @@ public void updateForMethodCall( atypeFactory.getDeclAnnotation( method, org.checkerframework.dataflow.qual.SideEffectsOnly.class); - sideEffectExpressions = + sideEffectsOnlyExpressions = AnnotationUtils.getElementValueArray( sefOnlyAnnotation, "value", String.class, true); } else if (value instanceof String) { - sideEffectExpressions = Collections.singletonList((String) value); + sideEffectsOnlyExpressions = Collections.singletonList((String) value); } } } @@ -283,11 +285,11 @@ public void updateForMethodCall( // TODO: Also remove if any element/argument to the annotation is not // isUnmodifiableByOtherCode. Example: @KeyFor("valueThatCanBeMutated"). if (sideEffectsUnrefineAliases) { - if (sideEffectExpressions != null) { + if (sideEffectsOnlyExpressions != null) { MethodAccessNode methodTarget = n.getTarget(); Node receiver = methodTarget.getReceiver(); - final List expressionsToRemove = sideEffectExpressions; + final List expressionsToRemove = sideEffectsOnlyExpressions; localVariableValues .keySet() .removeIf( @@ -305,8 +307,8 @@ public void updateForMethodCall( // update this value if (sideEffectsUnrefineAliases) { - if (sideEffectExpressions != null) { - if (sideEffectExpressions.contains("this")) { + if (sideEffectsOnlyExpressions != null) { + if (sideEffectsOnlyExpressions.contains("this")) { thisValue = null; } } else { @@ -316,8 +318,8 @@ public void updateForMethodCall( // update field values if (sideEffectsUnrefineAliases) { - if (sideEffectExpressions != null) { - final List expressionsToRemove = sideEffectExpressions; + if (sideEffectsOnlyExpressions != null) { + final List expressionsToRemove = sideEffectsOnlyExpressions; fieldValues .keySet() .removeIf( diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/SideEffectsOnlyToyBottom.java b/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/SideEffectsOnlyToyBottom.java index 384da472294..1482d05658a 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/SideEffectsOnlyToyBottom.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/SideEffectsOnlyToyBottom.java @@ -15,5 +15,7 @@ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) -@SubtypeOf({org.checkerframework.framework.testchecker.sideeffectsonly.qual.SideEffectsOnlyToyTop.class}) +@SubtypeOf({ + org.checkerframework.framework.testchecker.sideeffectsonly.qual.SideEffectsOnlyToyTop.class +}) public @interface SideEffectsOnlyToyBottom {} From 9bbe372ec05dc179363a182f72323003a62cff07 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Thu, 4 Mar 2021 16:31:28 -0800 Subject: [PATCH 030/113] JavaExpression instead of String and clean-up --- .../framework/flow/CFAbstractStore.java | 122 +++++------------- .../sideeffectsonly/SideEffectsMultiple.java | 2 +- .../sideeffectsonly/SideEffectsOnlyTest.java | 2 +- 3 files changed, 34 insertions(+), 92 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index 70c05bff58a..1c8e317ef42 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -1,14 +1,8 @@ package org.checkerframework.framework.flow; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.StringJoiner; +import java.util.*; import java.util.concurrent.atomic.AtomicLong; import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Name; @@ -19,7 +13,6 @@ import org.checkerframework.dataflow.cfg.node.ArrayAccessNode; import org.checkerframework.dataflow.cfg.node.FieldAccessNode; import org.checkerframework.dataflow.cfg.node.LocalVariableNode; -import org.checkerframework.dataflow.cfg.node.MethodAccessNode; import org.checkerframework.dataflow.cfg.node.MethodInvocationNode; import org.checkerframework.dataflow.cfg.node.Node; import org.checkerframework.dataflow.cfg.node.ThisNode; @@ -36,9 +29,11 @@ import org.checkerframework.dataflow.qual.SideEffectsOnly; import org.checkerframework.dataflow.util.PurityUtils; import org.checkerframework.framework.qual.MonotonicQualifier; +import org.checkerframework.framework.source.SourceChecker; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; import org.checkerframework.framework.type.QualifierHierarchy; +import org.checkerframework.framework.util.JavaExpressionParseUtil; import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.BugInCF; @@ -198,26 +193,6 @@ protected boolean isSideEffectsOnly( return sefOnlyAnnotation != null; } - /** - * Returns the annotation values of {@code @SideEffectsOnly}. - * - * @param atypeFactory the type factory used to retrieve annotations on the method element - * @param method the method element - * @return values of annotation elements of {@code @SideEffectsOnly} if the method has this - * annotation - */ - protected @Nullable Map - getSideEffectsOnlyValues(AnnotatedTypeFactory atypeFactory, ExecutableElement method) { - AnnotationMirror sefOnlyAnnotation = - atypeFactory.getDeclAnnotation(method, SideEffectsOnly.class); - if (sefOnlyAnnotation == null) { - return null; - } - Map elementValues = - sefOnlyAnnotation.getElementValues(); - return elementValues; - } - /* --------------------------------------------------------- */ /* Handling of fields */ /* --------------------------------------------------------- */ @@ -247,28 +222,27 @@ public void updateForMethodCall( // List of expressions that this method side effects (specified as annotation values of // @SideEffectsOnly). If the value is null, then there is no @SideEffectsOnly annotation. - List sideEffectsOnlyExpressions = null; + List sideEffectsOnlyExpressions = new ArrayList<>(); if (isSideEffectsOnly(atypeFactory, method)) { - Map valmap = - getSideEffectsOnlyValues(atypeFactory, method); - if (valmap != null) { - Object value = null; - for (ExecutableElement elem : valmap.keySet()) { - if (elem.getSimpleName().contentEquals("value")) { - value = valmap.get(elem).getValue(); - break; - } - } - if (value instanceof List) { - AnnotationMirror sefOnlyAnnotation = - atypeFactory.getDeclAnnotation( - method, - org.checkerframework.dataflow.qual.SideEffectsOnly.class); - sideEffectsOnlyExpressions = - AnnotationUtils.getElementValueArray( - sefOnlyAnnotation, "value", String.class, true); - } else if (value instanceof String) { - sideEffectsOnlyExpressions = Collections.singletonList((String) value); + SourceChecker checker = analysis.checker; + JavaExpressionParseUtil.JavaExpressionContext methodUseContext = + JavaExpressionParseUtil.JavaExpressionContext.buildContextForMethodUse( + n, checker); + + AnnotationMirror sefOnlyAnnotation = + atypeFactory.getDeclAnnotation( + method, org.checkerframework.dataflow.qual.SideEffectsOnly.class); + List sideEffectsOnlyExpressionsString = + AnnotationUtils.getElementValueArray( + sefOnlyAnnotation, "value", String.class, true); + + for (String st : sideEffectsOnlyExpressionsString) { + try { + JavaExpression exprJe = JavaExpressionParseUtil.parse(st, methodUseContext); + sideEffectsOnlyExpressions.add(exprJe); + } catch (JavaExpressionParseUtil.JavaExpressionParseException ex) { + System.out.println(ex.getDiagMessage()); + return; } } } @@ -281,52 +255,20 @@ public void updateForMethodCall( boolean sideEffectsUnrefineAliases = ((GenericAnnotatedTypeFactory) atypeFactory).sideEffectsUnrefineAliases; - // update local variables // TODO: Also remove if any element/argument to the annotation is not // isUnmodifiableByOtherCode. Example: @KeyFor("valueThatCanBeMutated"). if (sideEffectsUnrefineAliases) { - if (sideEffectsOnlyExpressions != null) { - MethodAccessNode methodTarget = n.getTarget(); - Node receiver = methodTarget.getReceiver(); - - final List expressionsToRemove = sideEffectsOnlyExpressions; - localVariableValues - .keySet() - .removeIf( - e -> - (expressionsToRemove.contains(e.toString()) - && !e.isUnmodifiableByOtherCode()) - || (expressionsToRemove.contains("this") - && e.toString() - .equals(receiver.toString()) - && !e.isUnmodifiableByOtherCode())); - } else { - localVariableValues.keySet().removeIf(e -> !e.isUnmodifiableByOtherCode()); - } - } - - // update this value - if (sideEffectsUnrefineAliases) { - if (sideEffectsOnlyExpressions != null) { - if (sideEffectsOnlyExpressions.contains("this")) { - thisValue = null; + if (!sideEffectsOnlyExpressions.isEmpty()) { + for (JavaExpression e : sideEffectsOnlyExpressions) { + if (!e.isUnmodifiableByOtherCode()) { + // update local variables + localVariableValues.keySet().remove(e); + // update field values + localVariableValues.keySet().remove(e); + } } } else { - thisValue = null; - } - } - - // update field values - if (sideEffectsUnrefineAliases) { - if (sideEffectsOnlyExpressions != null) { - final List expressionsToRemove = sideEffectsOnlyExpressions; - fieldValues - .keySet() - .removeIf( - e -> - expressionsToRemove.contains(e.toString()) - && !e.isUnmodifiableByOtherCode()); - } else { + localVariableValues.keySet().removeIf(e -> !e.isUnmodifiableByOtherCode()); fieldValues.keySet().removeIf(e -> !e.isUnmodifiableByOtherCode()); } } else { diff --git a/framework/tests/sideeffectsonly/SideEffectsMultiple.java b/framework/tests/sideeffectsonly/SideEffectsMultiple.java index 4c9fe957b40..74170f1c32d 100644 --- a/framework/tests/sideeffectsonly/SideEffectsMultiple.java +++ b/framework/tests/sideeffectsonly/SideEffectsMultiple.java @@ -19,7 +19,7 @@ void test(Object x) { // :: error: contracts.postcondition.not.satisfied void method(Object x) {} - @SideEffectsOnly({"this", "x"}) + @SideEffectsOnly({"this", "#1"}) void method1( @org.checkerframework.framework.testchecker.sideeffectsonly.qual .SideEffectsOnlyToyBottom diff --git a/framework/tests/sideeffectsonly/SideEffectsOnlyTest.java b/framework/tests/sideeffectsonly/SideEffectsOnlyTest.java index 8ab648bbdfe..fbee6cf4054 100644 --- a/framework/tests/sideeffectsonly/SideEffectsOnlyTest.java +++ b/framework/tests/sideeffectsonly/SideEffectsOnlyTest.java @@ -27,7 +27,7 @@ void method1( .SideEffectsOnlyToyBottom Object x) {} - @SideEffectsOnly({"x"}) + @SideEffectsOnly({"#1"}) void method2( @org.checkerframework.framework.testchecker.sideeffectsonly.qual .SideEffectsOnlyToyBottom From 3e0fbe707eca5b03dd303dc9635e935ca9d2a3c1 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Thu, 4 Mar 2021 16:34:13 -0800 Subject: [PATCH 031/113] test case: change parameter names --- framework/tests/sideeffectsonly/SideEffectsMultiple.java | 2 +- framework/tests/sideeffectsonly/SideEffectsOnlyTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/tests/sideeffectsonly/SideEffectsMultiple.java b/framework/tests/sideeffectsonly/SideEffectsMultiple.java index 74170f1c32d..55a8a816576 100644 --- a/framework/tests/sideeffectsonly/SideEffectsMultiple.java +++ b/framework/tests/sideeffectsonly/SideEffectsMultiple.java @@ -23,7 +23,7 @@ void method(Object x) {} void method1( @org.checkerframework.framework.testchecker.sideeffectsonly.qual .SideEffectsOnlyToyBottom - Object x) {} + Object y) {} void method2( @org.checkerframework.framework.testchecker.sideeffectsonly.qual diff --git a/framework/tests/sideeffectsonly/SideEffectsOnlyTest.java b/framework/tests/sideeffectsonly/SideEffectsOnlyTest.java index fbee6cf4054..b359ad98054 100644 --- a/framework/tests/sideeffectsonly/SideEffectsOnlyTest.java +++ b/framework/tests/sideeffectsonly/SideEffectsOnlyTest.java @@ -37,5 +37,5 @@ void method2( void method3( @org.checkerframework.framework.testchecker.sideeffectsonly.qual .SideEffectsOnlyToyBottom - Object x) {} + Object z) {} } From 5f03c882061499adbc5ebe75aa3554e133d1c01d Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Thu, 4 Mar 2021 19:03:38 -0800 Subject: [PATCH 032/113] fix documentation --- docs/manual/advanced-features.tex | 6 ++---- .../checkerframework/framework/flow/CFAbstractStore.java | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/manual/advanced-features.tex b/docs/manual/advanced-features.tex index 5f1708f4ef3..d1694cbbfc4 100644 --- a/docs/manual/advanced-features.tex +++ b/docs/manual/advanced-features.tex @@ -1007,12 +1007,10 @@ dataflow facts. The \refqualclass{dataflow/qual}{SideEffectsOnly} annotation -specifies the expressions that the method side effects. So expressions -that are not specified as annotation values of +specifies the superset of expressions that the method might side effect. +So expressions that are not specified as annotation values of \refqualclass{dataflow/qual}{SideEffectsOnly} have their dataflow facts unaffected. -Without this annotation, dataflow anaysis soundly assumes that all -expressions are side-effected. If a method has neither \refqualclass{dataflow/qual}{SideEffectsOnly} nor \refqualclass{dataflow/qual}{SideEffectFree} annotation, then it is assumed that the method side-effects all expressions (fields and arguments). diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index 1c8e317ef42..60d4838c825 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -205,7 +205,7 @@ protected boolean isSideEffectsOnly( *
  • If the method is side-effect-free (as indicated by {@link * org.checkerframework.dataflow.qual.SideEffectFree} or {@link * org.checkerframework.dataflow.qual.Pure}), then no information needs to be removed. - *
  • If the method side effects few expressions (specified as annotation values of + *
  • If the method side effects any expression (specified as annotation values of * {@code @SideEffectsOnly}), then information about those expressions is removed. *
  • Otherwise, all information about field accesses {@code a.f} needs to be removed, except * if the method {@code n} cannot modify {@code a.f} (e.g., if {@code a} is a local From 237e037e23d87adedddc82075aeed26b620f6d15 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Fri, 5 Mar 2021 13:44:50 -0800 Subject: [PATCH 033/113] fix checker-qual --- .../checker/formatter/FormatUtil.java | 325 ----------- .../checker/i18nformatter/I18nFormatUtil.java | 408 -------------- .../checker/nullness/NullnessUtil.java | 309 ----------- .../checker/nullness/Opt.java | 143 ----- .../checker/regex/RegexUtil.java | 335 ----------- .../checker/signedness/SignednessUtil.java | 523 ------------------ .../checker/units/UnitsTools.java | 138 ----- .../framework/util/PurityUnqualified.java | 24 - .../framework/flow/CFAbstractStore.java | 7 +- 9 files changed, 6 insertions(+), 2206 deletions(-) delete mode 100644 checker-qual/src/main/java/org/checkerframework/checker/formatter/FormatUtil.java delete mode 100644 checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatUtil.java delete mode 100644 checker-qual/src/main/java/org/checkerframework/checker/nullness/NullnessUtil.java delete mode 100644 checker-qual/src/main/java/org/checkerframework/checker/nullness/Opt.java delete mode 100644 checker-qual/src/main/java/org/checkerframework/checker/regex/RegexUtil.java delete mode 100644 checker-qual/src/main/java/org/checkerframework/checker/signedness/SignednessUtil.java delete mode 100644 checker-qual/src/main/java/org/checkerframework/checker/units/UnitsTools.java delete mode 100644 checker-qual/src/main/java/org/checkerframework/framework/util/PurityUnqualified.java diff --git a/checker-qual/src/main/java/org/checkerframework/checker/formatter/FormatUtil.java b/checker-qual/src/main/java/org/checkerframework/checker/formatter/FormatUtil.java deleted file mode 100644 index 4102ef10fcc..00000000000 --- a/checker-qual/src/main/java/org/checkerframework/checker/formatter/FormatUtil.java +++ /dev/null @@ -1,325 +0,0 @@ -package org.checkerframework.checker.formatter; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.IllegalFormatConversionException; -import java.util.IllegalFormatException; -import java.util.Map; -import java.util.MissingFormatArgumentException; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.checkerframework.checker.formatter.qual.ConversionCategory; -import org.checkerframework.checker.formatter.qual.ReturnsFormat; -import org.checkerframework.checker.regex.qual.Regex; -import org.checkerframework.framework.qual.AnnotatedFor; - -/** This class provides a collection of utilities to ease working with format strings. */ -@AnnotatedFor("nullness") -public class FormatUtil { - - /** - * A representation of a format specifier, which is represented by "%..." in the format string. - * Indicates how to convert a value into a string. - */ - private static class Conversion { - /** The index in the argument list. */ - private final int index; - /** The conversion category. */ - private final ConversionCategory cath; - - /** - * Construct a new Conversion. - * - * @param index the index in the argument list - * @param c the conversion character - */ - public Conversion(char c, int index) { - this.index = index; - this.cath = ConversionCategory.fromConversionChar(c); - } - - /** - * Returns the index in the argument list. - * - * @return the index in the argument list - */ - int index() { - return index; - } - - /** - * Returns the conversion category. - * - * @return the conversion category - */ - ConversionCategory category() { - return cath; - } - } - - /** - * Returns the first argument if the format string is satisfiable, and if the format's - * parameters match the passed {@link ConversionCategory}s. Otherwise throws an exception. - * - * @param format a format string - * @param cc an array of conversion categories - * @return the {@code format} argument - * @throws IllegalFormatException if the format string is incompatible with the conversion - * categories - */ - // TODO introduce more such functions, see RegexUtil for examples - @ReturnsFormat - public static String asFormat(String format, ConversionCategory... cc) - throws IllegalFormatException { - ConversionCategory[] fcc = formatParameterCategories(format); - if (fcc.length != cc.length) { - throw new ExcessiveOrMissingFormatArgumentException(cc.length, fcc.length); - } - - for (int i = 0; i < cc.length; i++) { - if (cc[i] != fcc[i]) { - throw new IllegalFormatConversionCategoryException(cc[i], fcc[i]); - } - } - - return format; - } - - /** - * Throws an exception if the format is not syntactically valid. - * - * @param format a format string - * @throws IllegalFormatException if the format string is invalid - */ - public static void tryFormatSatisfiability(String format) throws IllegalFormatException { - @SuppressWarnings({ - "unused", // called for side effect, to see if it throws an exception - "nullness:argument.type.incompatible" // it's not documented, but String.format permits - // a null array, which it treats as matching any format string - }) - String unused = String.format(format, (Object[]) null); - } - - /** - * Returns a {@link ConversionCategory} for every conversion found in the format string. - * - *

    Throws an exception if the format is not syntactically valid. - */ - public static ConversionCategory[] formatParameterCategories(String format) - throws IllegalFormatException { - tryFormatSatisfiability(format); - - int last = -1; // index of last argument referenced - int lasto = -1; // last ordinary index - int maxindex = -1; - - Conversion[] cs = parse(format); - Map conv = new HashMap<>(); - - for (Conversion c : cs) { - int index = c.index(); - switch (index) { - case -1: // relative index - break; - case 0: // ordinary index - lasto++; - last = lasto; - break; - default: // explicit index - last = index - 1; - break; - } - maxindex = Math.max(maxindex, last); - Integer lastKey = last; - conv.put( - last, - ConversionCategory.intersect( - conv.containsKey(lastKey) - ? conv.get(lastKey) - : ConversionCategory.UNUSED, - c.category())); - } - - ConversionCategory[] res = new ConversionCategory[maxindex + 1]; - for (int i = 0; i <= maxindex; ++i) { - Integer key = i; // autoboxing prevents recognizing that containsKey => get() != null - res[i] = conv.containsKey(key) ? conv.get(key) : ConversionCategory.UNUSED; - } - return res; - } - - /** - * A regex that matches a format specifier. Its syntax is specified in the See {@code - * Formatter} documentation. - * - *

    -     * %[argument_index$][flags][width][.precision][t]conversion
    -     * group 1            2      3      4           5 6
    -     * 
    - * - * For dates and times, the [t] is required and precision must not be provided. For types other - * than dates and times, the [t] must not be provided. - */ - private static final @Regex(6) String formatSpecifier = - "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])"; - /** The capturing group for the optional {@code t} character. */ - private static final int formatSpecifierT = 5; - /** - * The capturing group for the last character in a format specifier, which is the conversion - * character unless the {@code t} character was given. - */ - private static final int formatSpecifierConversion = 6; - - /** - * A Pattern that matches a format specifier. - * - * @see #formatSpecifier - */ - private static @Regex(6) Pattern fsPattern = Pattern.compile(formatSpecifier); - - /** - * Return the index, in the argument list, of the value that will be formatted by the matched - * format specifier. - * - * @param m a matcher that matches a format specifier - * @return the index of the argument to format - */ - private static int indexFromFormat(Matcher m) { - int index; - String s = m.group(1); - if (s != null) { // explicit index - index = Integer.parseInt(s.substring(0, s.length() - 1)); - } else { - String group2 = m.group(2); // not @Deterministic, so extract into local var - if (group2 != null && group2.contains(String.valueOf('<'))) { - index = -1; // relative index - } else { - index = 0; // ordinary index - } - } - return index; - } - - /** - * Returns the conversion character from a format specifier.. - * - * @param m a matcher that matches a format specifier - * @return the conversion character from the format specifier - */ - @SuppressWarnings( - "nullness:dereference.of.nullable") // group formatSpecifierConversion always exists - private static char conversionCharFromFormat(@Regex(6) Matcher m) { - String tGroup = m.group(formatSpecifierT); - if (tGroup != null) { - return tGroup.charAt(0); // This is the letter "t" or "T". - } else { - return m.group(formatSpecifierConversion).charAt(0); - } - } - - /** - * Return the conversion character that is in the given format specifier. - * - * @param formatSpecifier a format - * specifier - * @return the conversion character that is in the given format specifier - * @deprecated This method is public only for testing. Use private method {@code - * #conversionCharFromFormat(Matcher)}. - */ - @Deprecated // used only for testing. Use conversionCharFromFormat(Matcher). - public static char conversionCharFromFormat(String formatSpecifier) { - Matcher m = fsPattern.matcher(formatSpecifier); - assert m.find(); - return conversionCharFromFormat(m); - } - - /** - * Parse the given format string, return information about its format specifiers. - * - * @param format a format string - * @return the list of Conversions from the format specifiers in the format string - */ - private static Conversion[] parse(String format) { - ArrayList cs = new ArrayList<>(); - @Regex(7) Matcher m = fsPattern.matcher(format); - while (m.find()) { - char c = conversionCharFromFormat(m); - switch (c) { - case '%': - case 'n': - break; - default: - cs.add(new Conversion(c, indexFromFormat(m))); - } - } - return cs.toArray(new Conversion[cs.size()]); - } - - public static class ExcessiveOrMissingFormatArgumentException - extends MissingFormatArgumentException { - private static final long serialVersionUID = 17000126L; - - private final int expected; - private final int found; - - /** - * Constructs an instance of this class with the actual argument length and the expected - * one. - */ - public ExcessiveOrMissingFormatArgumentException(int expected, int found) { - super("-"); - this.expected = expected; - this.found = found; - } - - public int getExpected() { - return expected; - } - - public int getFound() { - return found; - } - - @Override - public String getMessage() { - return String.format("Expected %d arguments but found %d.", expected, found); - } - } - - public static class IllegalFormatConversionCategoryException - extends IllegalFormatConversionException { - private static final long serialVersionUID = 17000126L; - - private final ConversionCategory expected; - private final ConversionCategory found; - - /** - * Constructs an instance of this class with the mismatched conversion and the expected one. - */ - public IllegalFormatConversionCategoryException( - ConversionCategory expected, ConversionCategory found) { - super( - expected.chars == null || expected.chars.length() == 0 - ? '-' - : expected.chars.charAt(0), - found.types == null ? Object.class : found.types[0]); - this.expected = expected; - this.found = found; - } - - public ConversionCategory getExpected() { - return expected; - } - - public ConversionCategory getFound() { - return found; - } - - @Override - public String getMessage() { - return String.format("Expected category %s but found %s.", expected, found); - } - } -} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatUtil.java b/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatUtil.java deleted file mode 100644 index 5e537931178..00000000000 --- a/checker-qual/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatUtil.java +++ /dev/null @@ -1,408 +0,0 @@ -package org.checkerframework.checker.i18nformatter; - -import java.text.ChoiceFormat; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; -import java.text.MessageFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.IllegalFormatException; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import org.checkerframework.checker.i18nformatter.qual.I18nChecksFormat; -import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory; -import org.checkerframework.checker.i18nformatter.qual.I18nValidFormat; -import org.checkerframework.checker.interning.qual.InternedDistinct; -import org.checkerframework.checker.nullness.qual.EnsuresNonNull; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.nullness.qual.RequiresNonNull; -import org.checkerframework.framework.qual.AnnotatedFor; - -/** - * This class provides a collection of utilities to ease working with i18n format strings. - * - * @checker_framework.manual #i18n-formatter-checker Internationalization Format String Checker - */ -@AnnotatedFor("nullness") -public class I18nFormatUtil { - - /** - * Throws an exception if the format is not syntactically valid. - * - * @param format the format string to parse - */ - @SuppressWarnings( - "nullness:argument.type.incompatible") // It's not documented, but passing null as the - // argument array is supported. - public static void tryFormatSatisfiability(String format) throws IllegalFormatException { - MessageFormat.format(format, (Object[]) null); - } - - /** - * Returns a {@link I18nConversionCategory} for every conversion found in the format string. - * - * @param format the format string to parse - * @throws IllegalFormatException if the format is not syntactically valid - */ - public static I18nConversionCategory[] formatParameterCategories(String format) - throws IllegalFormatException { - tryFormatSatisfiability(format); - I18nConversion[] cs = MessageFormatParser.parse(format); - - int maxIndex = -1; - Map conv = new HashMap<>(); - - for (I18nConversion c : cs) { - int index = c.index; - Integer indexKey = index; - conv.put( - indexKey, - I18nConversionCategory.intersect( - c.category, - conv.containsKey(indexKey) - ? conv.get(indexKey) - : I18nConversionCategory.UNUSED)); - maxIndex = Math.max(maxIndex, index); - } - - I18nConversionCategory[] res = new I18nConversionCategory[maxIndex + 1]; - for (int i = 0; i <= maxIndex; i++) { - Integer indexKey = i; - res[i] = - conv.containsKey(indexKey) ? conv.get(indexKey) : I18nConversionCategory.UNUSED; - } - return res; - } - - /** - * Returns true if the format string is satisfiable, and if the format's parameters match the - * passed {@link I18nConversionCategory}s. Otherwise an error is thrown. - * - * @param format a format string - * @param cc a list of expected categories for the string's format specifiers - * @return true if the format string's specifiers are the given categories, in order - */ - // TODO introduce more such functions, see RegexUtil for examples - @I18nChecksFormat - public static boolean hasFormat(String format, I18nConversionCategory... cc) { - I18nConversionCategory[] fcc = formatParameterCategories(format); - if (fcc.length != cc.length) { - return false; - } - - for (int i = 0; i < cc.length; i++) { - if (!I18nConversionCategory.isSubsetOf(cc[i], fcc[i])) { - return false; - } - } - return true; - } - - @I18nValidFormat - public static boolean isFormat(String format) { - try { - formatParameterCategories(format); - } catch (Exception e) { - return false; - } - return true; - } - - private static class I18nConversion { - public int index; - public I18nConversionCategory category; - - public I18nConversion(int index, I18nConversionCategory category) { - this.index = index; - this.category = category; - } - - @Override - public String toString() { - return category.toString() + "(index: " + index + ")"; - } - } - - private static class MessageFormatParser { - - public static int maxOffset; - - /** The locale to use for formatting numbers and dates. Is set in {@link #parse}. */ - private static @MonotonicNonNull Locale locale; - - /** - * An array of formatters, which are used to format the arguments. Is set in {@link #parse}. - */ - private static @MonotonicNonNull List categories; - - /** - * The argument numbers corresponding to each formatter. (The formatters are stored in the - * order they occur in the pattern, not in the order in which the arguments are specified.) - * Is set in {@link #parse}. - */ - private static @MonotonicNonNull List argumentIndices; - - // I think this means the number of format specifiers in the format string. - /** The number of subformats. */ - private static int numFormat; - - // Indices for segments - private static final int SEG_RAW = 0; - private static final int SEG_INDEX = 1; - private static final int SEG_TYPE = 2; - private static final int SEG_MODIFIER = 3; // modifier or subformat - - // Indices for type keywords - private static final int TYPE_NULL = 0; - private static final int TYPE_NUMBER = 1; - private static final int TYPE_DATE = 2; - private static final int TYPE_TIME = 3; - private static final int TYPE_CHOICE = 4; - - private static final String[] TYPE_KEYWORDS = {"", "number", "date", "time", "choice"}; - - // Indices for number modifiers - private static final int MODIFIER_DEFAULT = 0; // common in number and date-time - private static final int MODIFIER_CURRENCY = 1; - private static final int MODIFIER_PERCENT = 2; - private static final int MODIFIER_INTEGER = 3; - - private static final String[] NUMBER_MODIFIER_KEYWORDS = { - "", "currency", "percent", "integer" - }; - - private static final String[] DATE_TIME_MODIFIER_KEYWORDS = { - "", "short", "medium", "long", "full" - }; - - @EnsuresNonNull({"categories", "argumentIndices", "locale"}) - public static I18nConversion[] parse(String pattern) { - MessageFormatParser.categories = new ArrayList<>(); - MessageFormatParser.argumentIndices = new ArrayList<>(); - MessageFormatParser.locale = Locale.getDefault(Locale.Category.FORMAT); - applyPattern(pattern); - - I18nConversion[] ret = new I18nConversion[MessageFormatParser.numFormat]; - for (int i = 0; i < MessageFormatParser.numFormat; i++) { - ret[i] = new I18nConversion(argumentIndices.get(i), categories.get(i)); - } - return ret; - } - - @SuppressWarnings("nullness:dereference.of.nullable") // complex rules for segments[i] - @RequiresNonNull({"argumentIndices", "categories", "locale"}) - private static void applyPattern(String pattern) { - @Nullable StringBuilder[] segments = new StringBuilder[4]; - // Allocate only segments[SEG_RAW] here. The rest are - // allocated on demand. - segments[SEG_RAW] = new StringBuilder(); - - int part = SEG_RAW; - MessageFormatParser.numFormat = 0; - boolean inQuote = false; - int braceStack = 0; - maxOffset = -1; - for (int i = 0; i < pattern.length(); ++i) { - char ch = pattern.charAt(i); - if (part == SEG_RAW) { - if (ch == '\'') { - if (i + 1 < pattern.length() && pattern.charAt(i + 1) == '\'') { - segments[part].append(ch); // handle doubles - ++i; - } else { - inQuote = !inQuote; - } - } else if (ch == '{' && !inQuote) { - part = SEG_INDEX; - if (segments[SEG_INDEX] == null) { - segments[SEG_INDEX] = new StringBuilder(); - } - } else { - segments[part].append(ch); - } - } else { - if (inQuote) { // just copy quotes in parts - segments[part].append(ch); - if (ch == '\'') { - inQuote = false; - } - } else { - switch (ch) { - case ',': - if (part < SEG_MODIFIER) { - if (segments[++part] == null) { - segments[part] = new StringBuilder(); - } - } else { - segments[part].append(ch); - } - break; - case '{': - ++braceStack; - segments[part].append(ch); - break; - case '}': - if (braceStack == 0) { - part = SEG_RAW; - makeFormat(numFormat, segments); - numFormat++; - // throw away other segments - segments[SEG_INDEX] = null; - segments[SEG_TYPE] = null; - segments[SEG_MODIFIER] = null; - } else { - --braceStack; - segments[part].append(ch); - } - break; - case ' ': - // Skip any leading space chars for SEG_TYPE. - if (part != SEG_TYPE || segments[SEG_TYPE].length() > 0) { - segments[part].append(ch); - } - break; - case '\'': - inQuote = true; - segments[part].append(ch); - break; - default: - segments[part].append(ch); - break; - } - } - } - } - if (braceStack == 0 && part != 0) { - maxOffset = -1; - throw new IllegalArgumentException("Unmatched braces in the pattern"); - } - } - - /** Side-effects {@code categories} field, adding to it an I18nConversionCategory. */ - @RequiresNonNull({"argumentIndices", "categories", "locale"}) - private static void makeFormat(int offsetNumber, @Nullable StringBuilder[] textSegments) { - String[] segments = new String[textSegments.length]; - for (int i = 0; i < textSegments.length; i++) { - StringBuilder oneseg = textSegments[i]; - segments[i] = (oneseg != null) ? oneseg.toString() : ""; - } - - // get the argument number - int argumentNumber; - try { - argumentNumber = Integer.parseInt(segments[SEG_INDEX]); // always - // unlocalized! - } catch (NumberFormatException e) { - throw new IllegalArgumentException( - "can't parse argument number: " + segments[SEG_INDEX], e); - } - if (argumentNumber < 0) { - throw new IllegalArgumentException("negative argument number: " + argumentNumber); - } - - int oldMaxOffset = maxOffset; - maxOffset = offsetNumber; - argumentIndices.add(argumentNumber); - - // now get the format - I18nConversionCategory category = null; - if (segments[SEG_TYPE].length() != 0) { - int type = findKeyword(segments[SEG_TYPE], TYPE_KEYWORDS); - switch (type) { - case TYPE_NULL: - category = I18nConversionCategory.GENERAL; - break; - case TYPE_NUMBER: - switch (findKeyword(segments[SEG_MODIFIER], NUMBER_MODIFIER_KEYWORDS)) { - case MODIFIER_DEFAULT: - case MODIFIER_CURRENCY: - case MODIFIER_PERCENT: - case MODIFIER_INTEGER: - break; - default: // DecimalFormat pattern - try { - new DecimalFormat( - segments[SEG_MODIFIER], - DecimalFormatSymbols.getInstance(locale)); - } catch (IllegalArgumentException e) { - maxOffset = oldMaxOffset; - // invalid decimal subformat pattern - throw e; - } - break; - } - category = I18nConversionCategory.NUMBER; - break; - case TYPE_DATE: - case TYPE_TIME: - int mod = findKeyword(segments[SEG_MODIFIER], DATE_TIME_MODIFIER_KEYWORDS); - if (mod >= 0 && mod < DATE_TIME_MODIFIER_KEYWORDS.length) { - // nothing to do - } else { - // SimpleDateFormat pattern - try { - new SimpleDateFormat(segments[SEG_MODIFIER], locale); - } catch (IllegalArgumentException e) { - maxOffset = oldMaxOffset; - // invalid date subformat pattern - throw e; - } - } - category = I18nConversionCategory.DATE; - break; - case TYPE_CHOICE: - if (segments[SEG_MODIFIER].length() == 0) { - throw new IllegalArgumentException( - "Choice Pattern requires Subformat Pattern: " - + segments[SEG_MODIFIER]); - } - try { - // ChoiceFormat pattern - new ChoiceFormat(segments[SEG_MODIFIER]); - } catch (Exception e) { - maxOffset = oldMaxOffset; - // invalid choice subformat pattern - throw new IllegalArgumentException( - "Choice Pattern incorrect: " + segments[SEG_MODIFIER], e); - } - category = I18nConversionCategory.NUMBER; - break; - default: - maxOffset = oldMaxOffset; - throw new IllegalArgumentException( - "unknown format type: " + segments[SEG_TYPE]); - } - } else { - category = I18nConversionCategory.GENERAL; - } - categories.add(category); - } - - /** - * Return the index of s in list. If not found, return the index of - * s.trim().toLowerCase(Locale.ROOT) in list. If still not found, return -1. - */ - private static final int findKeyword(String s, String[] list) { - for (int i = 0; i < list.length; ++i) { - if (s.equals(list[i])) { - return i; - } - } - - // Try trimmed lowercase. - @SuppressWarnings("interning:assignment.type.incompatible") // test if value changed - @InternedDistinct String ls = s.trim().toLowerCase(Locale.ROOT); - if (ls != s) { // Don't loop if the string trim().toLowerCase returned the same object. - for (int i = 0; i < list.length; ++i) { - if (ls.equals(list[i])) { - return i; - } - } - } - return -1; - } - } -} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/NullnessUtil.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/NullnessUtil.java deleted file mode 100644 index 127a55e80bc..00000000000 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/NullnessUtil.java +++ /dev/null @@ -1,309 +0,0 @@ -package org.checkerframework.checker.nullness; - -import org.checkerframework.checker.nullness.qual.EnsuresNonNull; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.qual.AnnotatedFor; - -/** - * Utility class for the Nullness Checker. - * - *

    To avoid the need to write the NullnessUtil class name, do: - * - *

    import static org.checkerframework.checker.nullness.NullnessUtil.castNonNull;
    - * - * or - * - *
    import static org.checkerframework.checker.nullness.NullnessUtil.*;
    - * - *

    Runtime Dependency: If you use this class, you must distribute (or link to) {@code - * checker-qual.jar}, along with your binaries. Or, you can can copy this class into your own - * project. - */ -@SuppressWarnings({ - "nullness", // Nullness utilities are trusted regarding nullness. - "cast" // Casts look redundant if Nullness Checker is not run. -}) -@AnnotatedFor("nullness") -public final class NullnessUtil { - - private NullnessUtil() { - throw new AssertionError("shouldn't be instantiated"); - } - - /** - * A method that suppresses warnings from the Nullness Checker. - * - *

    The method takes a possibly-null reference, unsafely casts it to have the @NonNull type - * qualifier, and returns it. The Nullness Checker considers both the return value, and also the - * argument, to be non-null after the method call. Therefore, the {@code castNonNull} method can - * be used either as a cast expression or as a statement. The Nullness Checker issues no - * warnings in any of the following code: - * - *

    
    -     *   // one way to use as a cast:
    -     *  {@literal @}NonNull String s = castNonNull(possiblyNull1);
    -     *
    -     *   // another way to use as a cast:
    -     *   castNonNull(possiblyNull2).toString();
    -     *
    -     *   // one way to use as a statement:
    -     *   castNonNull(possiblyNull3);
    -     *   possiblyNull3.toString();`
    -     * }
    - * - * The {@code castNonNull} method is intended to be used in situations where the programmer - * definitively knows that a given reference is not null, but the type system is unable to make - * this deduction. It is not intended for defensive programming, in which a programmer cannot - * prove that the value is not null but wishes to have an earlier indication if it is. See the - * Checker Framework Manual for further discussion. - * - *

    The method throws {@link AssertionError} if Java assertions are enabled and the argument - * is {@code null}. If the exception is ever thrown, then that indicates that the programmer - * misused the method by using it in a circumstance where its argument can be null. - * - * @param the type of the reference - * @param ref a reference of @Nullable type, that is non-null at run time - * @return the argument, casted to have the type qualifier @NonNull - */ - public static @EnsuresNonNull("#1") @NonNull T castNonNull( - @Nullable T ref) { - assert ref != null : "Misuse of castNonNull: called with a null argument"; - return (@NonNull T) ref; - } - - /** - * Suppress warnings from the Nullness Checker, with a custom error message. See {@link - * #castNonNull(Object)} for documentation. - * - * @see #castNonNull(Object) - * @param the type of the reference - * @param ref a reference of @Nullable type, that is non-null at run time - * @param message text to include if this method is misused - * @return the argument, casted to have the type qualifier @NonNull - */ - public static @EnsuresNonNull("#1") @NonNull T castNonNull( - @Nullable T ref, String message) { - assert ref != null : "Misuse of castNonNull: called with a null argument: " + message; - return (@NonNull T) ref; - } - - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that - * all elements at every array level are non-null. - * - * @param the component type of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at - * run time - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - public static @EnsuresNonNull("#1") - @NonNull T @NonNull [] castNonNullDeep(T @Nullable [] arr) { - return (@NonNull T[]) castNonNullArray(arr, null); - } - - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that - * all elements at every array level are non-null. - * - * @param the component type of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at - * run time - * @param message text to include if this method is misused - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - public static @EnsuresNonNull("#1") - @NonNull T @NonNull [] castNonNullDeep(T @Nullable [] arr, String message) { - return (@NonNull T[]) castNonNullArray(arr, message); - } - - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that - * all elements at every array level are non-null. - * - * @param the component type of the component type of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at - * run time - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - public static @EnsuresNonNull("#1") - @NonNull T @NonNull [][] castNonNullDeep(T @Nullable [] @Nullable [] arr) { - return (@NonNull T[][]) castNonNullArray(arr, null); - } - - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that - * all elements at every array level are non-null. - * - * @param the component type of the component type of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at - * run time - * @param message text to include if this method is misused - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - public static @EnsuresNonNull("#1") - @NonNull T @NonNull [][] castNonNullDeep(T @Nullable [] @Nullable [] arr, String message) { - return (@NonNull T[][]) castNonNullArray(arr, message); - } - - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that - * all elements at every array level are non-null. - * - * @param the component type (three levels in) of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at - * run time - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - public static @EnsuresNonNull("#1") - @NonNull T @NonNull [][][] castNonNullDeep(T @Nullable [] @Nullable [] @Nullable [] arr) { - return (@NonNull T[][][]) castNonNullArray(arr, null); - } - - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that - * all elements at every array level are non-null. - * - * @param the component type (three levels in) of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at - * run time - * @param message text to include if this method is misused - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - public static @EnsuresNonNull("#1") - @NonNull T @NonNull [][][] castNonNullDeep( - T @Nullable [] @Nullable [] @Nullable [] arr, String message) { - return (@NonNull T[][][]) castNonNullArray(arr, message); - } - - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that - * all elements at every array level are non-null. - * - * @param the component type of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at - * run time - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - public static @EnsuresNonNull("#1") - @NonNull T @NonNull [][][][] castNonNullDeep( - T @Nullable [] @Nullable [] @Nullable [] @Nullable [] arr) { - return (@NonNull T[][][][]) castNonNullArray(arr, null); - } - - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that - * all elements at every array level are non-null. - * - * @param the component type (four levels in) of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at - * run time - * @param message text to include if this method is misused - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - public static @EnsuresNonNull("#1") - @NonNull T @NonNull [][][][] castNonNullDeep( - T @Nullable [] @Nullable [] @Nullable [] @Nullable [] arr, String message) { - return (@NonNull T[][][][]) castNonNullArray(arr, message); - } - - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that - * all elements at every array level are non-null. - * - * @param the component type (four levels in) of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at - * run time - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - public static @EnsuresNonNull("#1") - @NonNull T @NonNull [][][][][] castNonNullDeep( - T @Nullable [] @Nullable [] @Nullable [] @Nullable [] @Nullable [] arr) { - return (@NonNull T[][][][][]) castNonNullArray(arr, null); - } - - /** - * Like castNonNull, but whereas that method only checks and casts the reference itself, this - * traverses all levels of the argument array. The array is recursively checked to ensure that - * all elements at every array level are non-null. - * - * @param the component type (five levels in) of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at - * run time - * @param message text to include if this method is misused - * @return the argument, casted to have the type qualifier @NonNull at all levels - * @see #castNonNull(Object) - */ - public static @EnsuresNonNull("#1") - @NonNull T @NonNull [][][][][] castNonNullDeep( - T @Nullable [] @Nullable [] @Nullable [] @Nullable [] @Nullable [] arr, - String message) { - return (@NonNull T[][][][][]) castNonNullArray(arr, message); - } - - /** - * The implementation of castNonNullDeep. - * - * @param the component type (five levels in) of the array - * @param arr an array all of whose elements, and their elements recursively, are non-null at - * run time - * @param message text to include if there is a non-null value, or null to use uncustomized - * message - * @return the argument, casted to have the type qualifier @NonNull at all levels - */ - private static @NonNull T @NonNull [] castNonNullArray( - T @Nullable [] arr, @Nullable String message) { - assert arr != null - : "Misuse of castNonNullArray: called with a null array argument" - + ((message == null) ? "" : (": " + message)); - for (int i = 0; i < arr.length; ++i) { - assert arr[i] != null - : "Misuse of castNonNull: called with a null array element" - + ((message == null) ? "" : (": " + message)); - checkIfArray(arr[i], message); - } - return (@NonNull T[]) arr; - } - - /** - * If the argument is an array, requires it to be non-null at all levels. - * - * @param ref a value; if an array, all of its elements, and their elements recursively, are - * non-null at run time - * @param message text to include if there is a non-null value, or null to use uncustomized - * message - */ - private static void checkIfArray(@NonNull Object ref, @Nullable String message) { - assert ref != null - : "Misuse of checkIfArray: called with a null argument" - + ((message == null) ? "" : (": " + message)); - Class comp = ref.getClass().getComponentType(); - if (comp != null) { - // comp is non-null for arrays, otherwise null. - if (comp.isPrimitive()) { - // Nothing to do for arrays of primitive type: primitives are - // never null. - } else { - castNonNullArray((Object[]) ref, message); - } - } - } -} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nullness/Opt.java b/checker-qual/src/main/java/org/checkerframework/checker/nullness/Opt.java deleted file mode 100644 index 36a93442c0c..00000000000 --- a/checker-qual/src/main/java/org/checkerframework/checker/nullness/Opt.java +++ /dev/null @@ -1,143 +0,0 @@ -package org.checkerframework.checker.nullness; - -import java.util.NoSuchElementException; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.function.Supplier; -import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.qual.AnnotatedFor; - -/** - * Utility class for the Nullness Checker, providing every method in {@link java.util.Optional}, but - * written for possibly-null references rather than for the {@code Optional} type. - * - *

    To avoid the need to write the {@code Opt} class name at invocation sites, do: - * - *

    import static org.checkerframework.checker.nullness.Opt.orElse;
    - * - * or - * - *
    import static org.checkerframework.checker.nullness.Opt.*;
    - * - *

    Runtime Dependency: If you use this class, you must distribute (or link to) {@code - * checker-qual.jar}, along with your binaries. Or, you can can copy this class into your own - * project. - * - * @see java.util.Optional - */ -@AnnotatedFor("nullness") -public final class Opt { - - /** The Opt class cannot be instantiated. */ - private Opt() { - throw new AssertionError("shouldn't be instantiated"); - } - - /** - * If primary is non-null, returns it, otherwise throws NoSuchElementException. - * - * @param the type of the argument - * @param primary a non-null value to return - * @return {@code primary} if it is non-null - * @throws NoSuchElementException if primary is null - * @see java.util.Optional#get() - */ - // `primary` is @NonNull; otherwise, the method could throw an exception. - public static T get(T primary) { - if (primary == null) { - throw new NoSuchElementException("No value present"); - } - return primary; - } - - /** - * Returns true if primary is non-null, false if primary is null. - * - * @see java.util.Optional#isPresent() - */ - @EnsuresNonNullIf(expression = "#1", result = true) - public static boolean isPresent(@Nullable Object primary) { - return primary != null; - } - - /** - * If primary is non-null, invoke the specified consumer with the value, otherwise do nothing. - * - * @see java.util.Optional#ifPresent(Consumer) - */ - public static void ifPresent(T primary, Consumer<@NonNull ? super @NonNull T> consumer) { - if (primary != null) { - consumer.accept(primary); - } - } - - /** - * If primary is non-null, and its value matches the given predicate, return the value. If - * primary is null or its non-null value does not match the predicate, return null. - * - * @see java.util.Optional#filter(Predicate) - */ - public static @Nullable T filter( - T primary, Predicate<@NonNull ? super @NonNull T> predicate) { - if (primary == null) { - return null; - } else { - return predicate.test(primary) ? primary : null; - } - } - - /** - * If primary is non-null, apply the provided mapping function to it and return the result. If - * primary is null, return null. - * - * @see java.util.Optional#map(Function) - */ - public static @Nullable U map( - T primary, Function<@NonNull ? super @NonNull T, ? extends U> mapper) { - if (primary == null) { - return null; - } else { - return mapper.apply(primary); - } - } - - // flatMap would have the same signature and implementation as map - - /** - * Return primary if it is non-null. If primary is null, return other. - * - * @see java.util.Optional#orElse(Object) - */ - public static @NonNull T orElse(T primary, @NonNull T other) { - return primary != null ? primary : other; - } - - /** - * Return primary if it is non-null. If primary is null, invoke {@code other} and return the - * result of that invocation. - * - * @see java.util.Optional#orElseGet(Supplier) - */ - public static @NonNull T orElseGet(T primary, Supplier other) { - return primary != null ? primary : other.get(); - } - - /** - * Return primary if it is non-null. If primary is null, throw an exception to be created by the - * provided supplier. - * - * @see java.util.Optional#orElseThrow(Supplier) - */ - // `primary` is @NonNull; otherwise, the method could throw an exception. - public static T orElseThrow( - T primary, Supplier exceptionSupplier) throws X { - if (primary != null) { - return primary; - } else { - throw exceptionSupplier.get(); - } - } -} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/regex/RegexUtil.java b/checker-qual/src/main/java/org/checkerframework/checker/regex/RegexUtil.java deleted file mode 100644 index 45381b43e94..00000000000 --- a/checker-qual/src/main/java/org/checkerframework/checker/regex/RegexUtil.java +++ /dev/null @@ -1,335 +0,0 @@ -// This class should be kept in sync with org.plumelib.util.RegexUtil in the plume-util project. - -package org.checkerframework.checker.regex; - -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; -import org.checkerframework.checker.index.qual.GTENegativeOne; -import org.checkerframework.checker.lock.qual.GuardSatisfied; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.regex.qual.Regex; -import org.checkerframework.dataflow.qual.Pure; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.framework.qual.AnnotatedFor; -import org.checkerframework.framework.qual.EnsuresQualifierIf; - -/** - * Utility methods for regular expressions, most notably for testing whether a string is a regular - * expression. - * - *

    For an example of intended use, see section Testing whether a string is a - * regular expression in the Checker Framework manual. - * - *

    Runtime Dependency: If you use this class, you must distribute (or link to) {@code - * checker-qual.jar}, along with your binaries. Or, you can can copy this class into your own - * project. - */ -@AnnotatedFor("nullness") -public final class RegexUtil { - - /** This class is a collection of methods; it does not represent anything. */ - private RegexUtil() { - throw new Error("do not instantiate"); - } - - /** - * A checked version of {@link PatternSyntaxException}. - * - *

    This exception is useful when an illegal regex is detected but the contextual information - * to report a helpful error message is not available at the current depth in the call stack. By - * using a checked PatternSyntaxException the error must be handled up the call stack where a - * better error message can be reported. - * - *

    Typical usage is: - * - *

    -     * void myMethod(...) throws CheckedPatternSyntaxException {
    -     *   ...
    -     *   if (! isRegex(myString)) {
    -     *     throw new CheckedPatternSyntaxException(...);
    -     *   }
    -     *   ... Pattern.compile(myString) ...
    -     * 
    - * - * Simply calling {@code Pattern.compile} would have a similar effect, in that {@code - * PatternSyntaxException} would be thrown at run time if {@code myString} is not a regular - * expression. There are two problems with such an approach. First, a client of {@code myMethod} - * might forget to handle the exception, since {@code PatternSyntaxException} is not checked. - * Also, the Regex Checker would issue a warning about the call to {@code Pattern.compile} that - * might throw an exception. The above usage pattern avoids both problems. - * - * @see PatternSyntaxException - */ - public static class CheckedPatternSyntaxException extends Exception { - - private static final long serialVersionUID = 6266881831979001480L; - - /** The PatternSyntaxException that this is a wrapper around. */ - private final PatternSyntaxException pse; - - /** - * Constructs a new CheckedPatternSyntaxException equivalent to the given {@link - * PatternSyntaxException}. - * - *

    Consider calling this constructor with the result of {@link RegexUtil#regexError}. - * - * @param pse the PatternSyntaxException to be wrapped - */ - public CheckedPatternSyntaxException(PatternSyntaxException pse) { - this.pse = pse; - } - - /** - * Constructs a new CheckedPatternSyntaxException. - * - * @param desc a description of the error - * @param regex the erroneous pattern - * @param index the approximate index in the pattern of the error, or {@code -1} if the - * index is not known - */ - public CheckedPatternSyntaxException(String desc, String regex, @GTENegativeOne int index) { - this(new PatternSyntaxException(desc, regex, index)); - } - - /** - * Retrieves the description of the error. - * - * @return the description of the error - */ - public String getDescription() { - return pse.getDescription(); - } - - /** - * Retrieves the error index. - * - * @return the approximate index in the pattern of the error, or {@code -1} if the index is - * not known - */ - public int getIndex() { - return pse.getIndex(); - } - - /** - * Returns a multi-line string containing the description of the syntax error and its index, - * the erroneous regular-expression pattern, and a visual indication of the error index - * within the pattern. - * - * @return the full detail message - */ - @Override - @Pure - public String getMessage(@GuardSatisfied CheckedPatternSyntaxException this) { - return pse.getMessage(); - } - - /** - * Retrieves the erroneous regular-expression pattern. - * - * @return the erroneous pattern - */ - public String getPattern() { - return pse.getPattern(); - } - } - - /** - * Returns true if the argument is a syntactically valid regular expression. - * - * @param s string to check for being a regular expression - * @return true iff s is a regular expression - */ - @Pure - @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class) - public static boolean isRegex(String s) { - return isRegex(s, 0); - } - - /** - * Returns true if the argument is a syntactically valid regular expression with at least the - * given number of groups. - * - * @param s string to check for being a regular expression - * @param groups number of groups expected - * @return true iff s is a regular expression with {@code groups} groups - */ - @SuppressWarnings({"regex", "all:deterministic"}) // RegexUtil; for purity, catches an exception - @Pure - // @EnsuresQualifierIf annotation is extraneous because this method is special-cased - // in RegexTransfer. - @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class) - public static boolean isRegex(String s, int groups) { - Pattern p; - try { - p = Pattern.compile(s); - } catch (PatternSyntaxException e) { - return false; - } - return getGroupCount(p) >= groups; - } - - /** - * Returns true if the argument is a syntactically valid regular expression. - * - * @param c char to check for being a regular expression - * @return true iff c is a regular expression - */ - @SuppressWarnings({ - "regex", - "all:purity.not.deterministic.call", - "lock" - }) // RegexUtil; temp value used in pure method is equal up to equals but not up to == - @Pure - @EnsuresQualifierIf(result = true, expression = "#1", qualifier = Regex.class) - public static boolean isRegex(final char c) { - return isRegex(Character.toString(c)); - } - - /** - * Returns null if the argument is a syntactically valid regular expression. Otherwise returns a - * string describing why the argument is not a regex. - * - * @param s string to check for being a regular expression - * @return null, or a string describing why the argument is not a regex - */ - @SideEffectFree - public static @Nullable String regexError(String s) { - return regexError(s, 0); - } - - /** - * Returns null if the argument is a syntactically valid regular expression with at least the - * given number of groups. Otherwise returns a string describing why the argument is not a - * regex. - * - * @param s string to check for being a regular expression - * @param groups number of groups expected - * @return null, or a string describing why the argument is not a regex - */ - @SuppressWarnings({"regex", "not.sef"}) // RegexUtil; - @SideEffectFree - public static @Nullable String regexError(String s, int groups) { - try { - Pattern p = Pattern.compile(s); - int actualGroups = getGroupCount(p); - if (actualGroups < groups) { - return regexErrorMessage(s, groups, actualGroups); - } - } catch (PatternSyntaxException e) { - return e.getMessage(); - } - return null; - } - - /** - * Returns null if the argument is a syntactically valid regular expression. Otherwise returns a - * PatternSyntaxException describing why the argument is not a regex. - * - * @param s string to check for being a regular expression - * @return null, or a PatternSyntaxException describing why the argument is not a regex - */ - @SideEffectFree - public static @Nullable PatternSyntaxException regexException(String s) { - return regexException(s, 0); - } - - /** - * Returns null if the argument is a syntactically valid regular expression with at least the - * given number of groups. Otherwise returns a PatternSyntaxException describing why the - * argument is not a regex. - * - * @param s string to check for being a regular expression - * @param groups number of groups expected - * @return null, or a PatternSyntaxException describing why the argument is not a regex - */ - @SuppressWarnings("regex") // RegexUtil - @SideEffectFree - public static @Nullable PatternSyntaxException regexException(String s, int groups) { - try { - Pattern p = Pattern.compile(s); - int actualGroups = getGroupCount(p); - if (actualGroups < groups) { - return new PatternSyntaxException( - regexErrorMessage(s, groups, actualGroups), s, -1); - } - } catch (PatternSyntaxException pse) { - return pse; - } - return null; - } - - /** - * Returns the argument as a {@code @Regex String} if it is a regex, otherwise throws an error. - * The purpose of this method is to suppress Regex Checker warnings. It should be very rarely - * needed. - * - * @param s string to check for being a regular expression - * @return its argument - * @throws Error if argument is not a regex - */ - @SideEffectFree - // The return type annotation is a conservative bound. - public static @Regex String asRegex(String s) { - return asRegex(s, 0); - } - - /** - * Returns the argument as a {@code @Regex(groups) String} if it is a regex with at least the - * given number of groups, otherwise throws an error. The purpose of this method is to suppress - * Regex Checker warnings. It should be very rarely needed. - * - * @param s string to check for being a regular expression - * @param groups number of groups expected - * @return its argument - * @throws Error if argument is not a regex - */ - @SuppressWarnings("regex") // RegexUtil - @SideEffectFree - // The return type annotation is irrelevant; it is special-cased by - // RegexAnnotatedTypeFactory. - public static @Regex String asRegex(String s, int groups) { - try { - Pattern p = Pattern.compile(s); - int actualGroups = getGroupCount(p); - if (actualGroups < groups) { - throw new Error(regexErrorMessage(s, groups, actualGroups)); - } - return s; - } catch (PatternSyntaxException e) { - throw new Error(e); - } - } - - /** - * Generates an error message for s when expectedGroups are needed, but s only has actualGroups. - * - * @param s string to check for being a regular expression - * @param expectedGroups the number of needed capturing groups - * @param actualGroups the number of groups that {@code s} has - * @return an error message for s when expectedGroups groups are needed, but s only has - * actualGroups groups - */ - @SideEffectFree - private static String regexErrorMessage(String s, int expectedGroups, int actualGroups) { - return "regex \"" - + s - + "\" has " - + actualGroups - + " groups, but " - + expectedGroups - + " groups are needed."; - } - - /** - * Return the count of groups in the argument. - * - * @param p pattern whose groups to count - * @return the count of groups in the argument - */ - @SuppressWarnings({"all:purity", "lock"}) // does not depend on object identity - @Pure - private static int getGroupCount(Pattern p) { - return p.matcher("").groupCount(); - } -} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signedness/SignednessUtil.java b/checker-qual/src/main/java/org/checkerframework/checker/signedness/SignednessUtil.java deleted file mode 100644 index 948ab11dcbd..00000000000 --- a/checker-qual/src/main/java/org/checkerframework/checker/signedness/SignednessUtil.java +++ /dev/null @@ -1,523 +0,0 @@ -package org.checkerframework.checker.signedness; - -import java.io.IOException; -import java.io.RandomAccessFile; -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.nio.IntBuffer; -import org.checkerframework.checker.signedness.qual.Unsigned; -import org.checkerframework.framework.qual.AnnotatedFor; - -/** - * Provides static utility methods for unsigned values. Most of these re-implement functionality - * that was introduced in JDK 8, making it available in earlier versions of Java. Others provide new - * functionality. {@link SignednessUtilExtra} has more methods that reference packages that Android - * does not provide. - * - * @checker_framework.manual #signedness-utilities Utility routines for manipulating unsigned values - */ -@AnnotatedFor("nullness") -public final class SignednessUtil { - - private SignednessUtil() { - throw new Error("Do not instantiate"); - } - - /** - * Wraps an unsigned byte array into a ByteBuffer. This method is a wrapper around {@link - * java.nio.ByteBuffer#wrap(byte[]) wrap(byte[])}, but assumes that the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer wrapUnsigned(@Unsigned byte[] array) { - return ByteBuffer.wrap(array); - } - - /** - * Wraps an unsigned byte array into a ByteBuffer. This method is a wrapper around {@link - * java.nio.ByteBuffer#wrap(byte[], int, int) wrap(byte[], int, int)}, but assumes that the - * input should be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer wrapUnsigned(@Unsigned byte[] array, int offset, int length) { - return ByteBuffer.wrap(array, offset, length); - } - - /** - * Gets an unsigned int from the ByteBuffer b. This method is a wrapper around {@link - * java.nio.ByteBuffer#getInt() getInt()}, but assumes that the result should be interpreted as - * unsigned. - */ - @SuppressWarnings("signedness") - public static @Unsigned int getUnsignedInt(ByteBuffer b) { - return b.getInt(); - } - - /** - * Gets an unsigned short from the ByteBuffer b. This method is a wrapper around {@link - * java.nio.ByteBuffer#getShort() getShort()}, but assumes that the result should be interpreted - * as unsigned. - */ - @SuppressWarnings("signedness") - public static @Unsigned short getUnsignedShort(ByteBuffer b) { - return b.getShort(); - } - - /** - * Gets an unsigned byte from the ByteBuffer b. This method is a wrapper around {@link - * java.nio.ByteBuffer#get() get()}, but assumes that the result should be interpreted as - * unsigned. - */ - @SuppressWarnings("signedness") - public static @Unsigned byte getUnsigned(ByteBuffer b) { - return b.get(); - } - - /** - * Gets an unsigned byte from the ByteBuffer b at i. This method is a wrapper around {@link - * java.nio.ByteBuffer#get(int) get(int)}, but assumes that the result should be interpreted as - * unsigned. - */ - @SuppressWarnings("signedness") - public static @Unsigned byte getUnsigned(ByteBuffer b, int i) { - return b.get(i); - } - - /** - * Populates an unsigned byte array from the ByteBuffer b at i with l bytes. This method is a - * wrapper around {@link java.nio.ByteBuffer#get(byte[] bs, int, int) get(byte[], int, int)}, - * but assumes that the bytes should be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer getUnsigned(ByteBuffer b, byte[] bs, int i, int l) { - return b.get(bs, i, l); - } - - /** - * Places an unsigned byte into the ByteBuffer b. This method is a wrapper around {@link - * java.nio.ByteBuffer#put(byte) put(byte)}, but assumes that the input should be interpreted as - * unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer putUnsigned(ByteBuffer b, @Unsigned byte ubyte) { - return b.put(ubyte); - } - - /** - * Places an unsigned byte into the ByteBuffer b at i. This method is a wrapper around {@link - * java.nio.ByteBuffer#put(int, byte) put(int, byte)}, but assumes that the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer putUnsigned(ByteBuffer b, int i, @Unsigned byte ubyte) { - return b.put(i, ubyte); - } - - /** - * Places an unsigned int into the IntBuffer b. This method is a wrapper around {@link - * java.nio.IntBuffer#put(int) put(int)}, but assumes that the input should be interpreted as - * unsigned. - */ - @SuppressWarnings("signedness") - public static IntBuffer putUnsigned(IntBuffer b, @Unsigned int uint) { - return b.put(uint); - } - - /** - * Places an unsigned int into the IntBuffer b at i. This method is a wrapper around {@link - * java.nio.IntBuffer#put(int, int) put(int, int)}, but assumes that the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static IntBuffer putUnsigned(IntBuffer b, int i, @Unsigned int uint) { - return b.put(i, uint); - } - - /** - * Places an unsigned int array into the IntBuffer b. This method is a wrapper around {@link - * java.nio.IntBuffer#put(int[]) put(int[])}, but assumes that the input should be interpreted - * as unsigned. - */ - @SuppressWarnings("signedness") - public static IntBuffer putUnsigned(IntBuffer b, @Unsigned int[] uints) { - return b.put(uints); - } - - /** - * Places an unsigned int array into the IntBuffer b at i with length l. This method is a - * wrapper around {@link java.nio.IntBuffer#put(int[], int, int) put(int[], int, int)}, but - * assumes that the input should be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static IntBuffer putUnsigned(IntBuffer b, @Unsigned int[] uints, int i, int l) { - return b.put(uints, i, l); - } - - /** - * Gets an unsigned int from the IntBuffer b at i. This method is a wrapper around {@link - * java.nio.IntBuffer#get(int) get(int)}, but assumes that the output should be interpreted as - * unsigned. - */ - @SuppressWarnings("signedness") - public static @Unsigned int getUnsigned(IntBuffer b, int i) { - return b.get(i); - } - - /** - * Places an unsigned short into the ByteBuffer b. This method is a wrapper around {@link - * java.nio.ByteBuffer#putShort(short) putShort(short)}, but assumes that the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer putUnsignedShort(ByteBuffer b, @Unsigned short ushort) { - return b.putShort(ushort); - } - - /** - * Places an unsigned short into the ByteBuffer b at i. This method is a wrapper around {@link - * java.nio.ByteBuffer#putShort(int, short) putShort(int, short)}, but assumes that the input - * should be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer putUnsignedShort(ByteBuffer b, int i, @Unsigned short ushort) { - return b.putShort(i, ushort); - } - - /** - * Places an unsigned int into the ByteBuffer b. This method is a wrapper around {@link - * java.nio.ByteBuffer#putInt(int) putInt(int)}, but assumes that the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer putUnsignedInt(ByteBuffer b, @Unsigned int uint) { - return b.putInt(uint); - } - - /** - * Places an unsigned int into the ByteBuffer b at i. This method is a wrapper around {@link - * java.nio.ByteBuffer#putInt(int, int) putInt(int, int)}, but assumes that the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer putUnsignedInt(ByteBuffer b, int i, @Unsigned int uint) { - return b.putInt(i, uint); - } - - /** - * Places an unsigned long into the ByteBuffer b at i. This method is a wrapper around {@link - * java.nio.ByteBuffer#putLong(int, long) putLong(int, long)}, but assumes that the input should - * be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static ByteBuffer putUnsignedLong(ByteBuffer b, int i, @Unsigned long ulong) { - return b.putLong(i, ulong); - } - - /** - * Reads an unsigned char from the RandomAccessFile f. This method is a wrapper around {@link - * java.io.RandomAccessFile#readChar() readChar()}, but assumes the output should be interpreted - * as unsigned. - */ - @SuppressWarnings("signedness") - public static @Unsigned char readUnsignedChar(RandomAccessFile f) throws IOException { - return f.readChar(); - } - - /** - * Reads an unsigned int from the RandomAccessFile f. This method is a wrapper around {@link - * java.io.RandomAccessFile#readInt() readInt()}, but assumes the output should be interpreted - * as unsigned. - */ - @SuppressWarnings("signedness") - public static @Unsigned int readUnsignedInt(RandomAccessFile f) throws IOException { - return f.readInt(); - } - - /** - * Reads an unsigned long from the RandomAccessFile f. This method is a wrapper around {@link - * java.io.RandomAccessFile#readLong() readLong()}, but assumes the output should be interpreted - * as unsigned. - */ - @SuppressWarnings("signedness") - public static @Unsigned long readUnsignedLong(RandomAccessFile f) throws IOException { - return f.readLong(); - } - - /** - * Reads up to {@code len} bytes of data from this file into an unsigned array of bytes. This - * method is a wrapper around {@link java.io.RandomAccessFile#read(byte[], int, int) - * read(byte[], int, int)}, but assumes the output should be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static int readUnsigned(RandomAccessFile f, @Unsigned byte b[], int off, int len) - throws IOException { - return f.read(b, off, len); - } - - /** - * Reads a file fully into an unsigned byte array. This method is a wrapper around {@link - * java.io.RandomAccessFile#readFully(byte[]) readFully(byte[])}, but assumes the output should - * be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static void readFullyUnsigned(RandomAccessFile f, @Unsigned byte b[]) - throws IOException { - f.readFully(b); - } - - /** - * Writes len unsigned bytes to the RandomAccessFile f at offset off. This method is a wrapper - * around {@link java.io.RandomAccessFile#write(byte[], int, int) write(byte[], int, int)}, but - * assumes the input should be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static void writeUnsigned(RandomAccessFile f, @Unsigned byte[] bs, int off, int len) - throws IOException { - f.write(bs, off, len); - } - - /** - * Writes an unsigned byte to the RandomAccessFile f. This method is a wrapper around {@link - * java.io.RandomAccessFile#writeByte(int) writeByte(int)}, but assumes the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static void writeUnsignedByte(RandomAccessFile f, @Unsigned byte b) throws IOException { - f.writeByte(Byte.toUnsignedInt(b)); - } - - /** - * Writes an unsigned char to the RandomAccessFile f. This method is a wrapper around {@link - * java.io.RandomAccessFile#writeChar(int) writeChar(int)}, but assumes the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static void writeUnsignedChar(RandomAccessFile f, @Unsigned char c) throws IOException { - f.writeChar(toUnsignedInt(c)); - } - - /** - * Writes an unsigned short to the RandomAccessFile f. This method is a wrapper around {@link - * java.io.RandomAccessFile#writeShort(int) writeShort(int)}, but assumes the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static void writeUnsignedShort(RandomAccessFile f, @Unsigned short s) - throws IOException { - f.writeShort(Short.toUnsignedInt(s)); - } - - /** - * Writes an unsigned byte to the RandomAccessFile f. This method is a wrapper around {@link - * java.io.RandomAccessFile#writeInt(int) writeInt(int)}, but assumes the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static void writeUnsignedInt(RandomAccessFile f, @Unsigned int i) throws IOException { - f.writeInt(i); - } - - /** - * Writes an unsigned byte to the RandomAccessFile f. This method is a wrapper around {@link - * java.io.RandomAccessFile#writeLong(long) writeLong(long)}, but assumes the input should be - * interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static void writeUnsignedLong(RandomAccessFile f, @Unsigned long l) throws IOException { - f.writeLong(l); - } - - /** - * Gets an array of unsigned bytes from the ByteBuffer b and stores them in the array bs. This - * method is a wrapper around {@link java.nio.ByteBuffer#get(byte[]) get(byte[])}, but assumes - * that the array of bytes should be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static void getUnsigned(ByteBuffer b, @Unsigned byte[] bs) { - b.get(bs); - } - - /** - * Compares two unsigned shorts x and y. - * - * @return a negative number iff x {@literal <} y, a positive number iff x {@literal >} y, and - * zero iff x == y. - */ - @SuppressWarnings("signedness") - public static int compareUnsigned(@Unsigned short x, @Unsigned short y) { - return Integer.compareUnsigned(Short.toUnsignedInt(x), Short.toUnsignedInt(y)); - } - - /** - * Compares two unsigned bytes x and y. - * - * @return a negative number iff x {@literal <} y, a positive number iff x {@literal >} y, and - * zero iff x == y. - */ - @SuppressWarnings("signedness") - public static int compareUnsigned(@Unsigned byte x, @Unsigned byte y) { - return Integer.compareUnsigned(Byte.toUnsignedInt(x), Byte.toUnsignedInt(y)); - } - - /** Produces a string representation of the unsigned short s. */ - @SuppressWarnings("signedness") - public static String toUnsignedString(@Unsigned short s) { - return Long.toString(Short.toUnsignedLong(s)); - } - - /** Produces a string representation of the unsigned short s in base radix. */ - @SuppressWarnings("signedness") - public static String toUnsignedString(@Unsigned short s, int radix) { - return Integer.toUnsignedString(Short.toUnsignedInt(s), radix); - } - - /** Produces a string representation of the unsigned byte b. */ - @SuppressWarnings("signedness") - public static String toUnsignedString(@Unsigned byte b) { - return Integer.toUnsignedString(Byte.toUnsignedInt(b)); - } - - /** Produces a string representation of the unsigned byte b in base radix. */ - @SuppressWarnings("signedness") - public static String toUnsignedString(@Unsigned byte b, int radix) { - return Integer.toUnsignedString(Byte.toUnsignedInt(b), radix); - } - - /* - * Creates a BigInteger representing the same value as unsigned long. - * - * This is a reimplementation of Java 8's - * {@link Long.toUnsignedBigInteger(long)}. - */ - @SuppressWarnings("signedness") - private static @Unsigned BigInteger toUnsignedBigInteger(@Unsigned long l) { - // Java 8 version: return Long.toUnsignedBigInteger(l); - if (l >= 0L) { - return BigInteger.valueOf(l); - } else { - int upper = (int) (l >>> 32); - int lower = (int) l; - - // return (upper << 32) + lower - return BigInteger.valueOf(Integer.toUnsignedLong(upper)) - .shiftLeft(32) - .add(BigInteger.valueOf(Integer.toUnsignedLong(lower))); - } - } - - /** Returns an unsigned short representing the same value as an unsigned byte. */ - public static @Unsigned short toUnsignedShort(@Unsigned byte b) { - return (short) (((int) b) & 0xff); - } - - /** Returns an unsigned long representing the same value as an unsigned char. */ - public static @Unsigned long toUnsignedLong(@Unsigned char c) { - return ((long) c) & 0xffL; - } - - /** Returns an unsigned int representing the same value as an unsigned char. */ - public static @Unsigned int toUnsignedInt(@Unsigned char c) { - return ((int) c) & 0xff; - } - - /** Returns an unsigned short representing the same value as an unsigned char. */ - public static @Unsigned short toUnsignedShort(@Unsigned char c) { - return (short) (((int) c) & 0xff); - } - - /** Returns a float representing the same value as the unsigned byte. */ - public static float toFloat(@Unsigned byte b) { - return toUnsignedBigInteger(Byte.toUnsignedLong(b)).floatValue(); - } - - /** Returns a float representing the same value as the unsigned short. */ - public static float toFloat(@Unsigned short s) { - return toUnsignedBigInteger(Short.toUnsignedLong(s)).floatValue(); - } - - /** Returns a float representing the same value as the unsigned int. */ - public static float toFloat(@Unsigned int i) { - return toUnsignedBigInteger(Integer.toUnsignedLong(i)).floatValue(); - } - - /** Returns a float representing the same value as the unsigned long. */ - public static float toFloat(@Unsigned long l) { - return toUnsignedBigInteger(l).floatValue(); - } - - /** Returns a double representing the same value as the unsigned byte. */ - public static double toDouble(@Unsigned byte b) { - return toUnsignedBigInteger(Byte.toUnsignedLong(b)).doubleValue(); - } - - /** Returns a double representing the same value as the unsigned short. */ - public static double toDouble(@Unsigned short s) { - return toUnsignedBigInteger(Short.toUnsignedLong(s)).doubleValue(); - } - - /** Returns a double representing the same value as the unsigned int. */ - public static double toDouble(@Unsigned int i) { - return toUnsignedBigInteger(Integer.toUnsignedLong(i)).doubleValue(); - } - - /** Returns a double representing the same value as the unsigned long. */ - public static double toDouble(@Unsigned long l) { - return toUnsignedBigInteger(l).doubleValue(); - } - - /** Returns an unsigned byte representing the same value as the float. */ - @SuppressWarnings("signedness") - public static @Unsigned byte byteFromFloat(float f) { - assert f >= 0; - return (byte) f; - } - - /** Returns an unsigned short representing the same value as the float. */ - @SuppressWarnings("signedness") - public static @Unsigned short shortFromFloat(float f) { - assert f >= 0; - return (short) f; - } - - /** Returns an unsigned int representing the same value as the float. */ - @SuppressWarnings("signedness") - public static @Unsigned int intFromFloat(float f) { - assert f >= 0; - return (int) f; - } - - /** Returns an unsigned long representing the same value as the float. */ - @SuppressWarnings("signedness") - public static @Unsigned long longFromFloat(float f) { - assert f >= 0; - return (long) f; - } - - /** Returns an unsigned byte representing the same value as the double. */ - @SuppressWarnings("signedness") - public static @Unsigned byte byteFromDouble(double d) { - assert d >= 0; - return (byte) d; - } - - /** Returns an unsigned short representing the same value as the double. */ - @SuppressWarnings("signedness") - public static @Unsigned short shortFromDouble(double d) { - assert d >= 0; - return (short) d; - } - - /** Returns an unsigned int representing the same value as the double. */ - @SuppressWarnings("signedness") - public static @Unsigned int intFromDouble(double d) { - assert d >= 0; - return (int) d; - } - - /** Returns an unsigned long representing the same value as the double. */ - @SuppressWarnings("signedness") - public static @Unsigned long longFromDouble(double d) { - assert d >= 0; - return (long) d; - } -} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/units/UnitsTools.java b/checker-qual/src/main/java/org/checkerframework/checker/units/UnitsTools.java deleted file mode 100644 index 56f128df42c..00000000000 --- a/checker-qual/src/main/java/org/checkerframework/checker/units/UnitsTools.java +++ /dev/null @@ -1,138 +0,0 @@ -package org.checkerframework.checker.units; - -import org.checkerframework.checker.units.qual.A; -import org.checkerframework.checker.units.qual.C; -import org.checkerframework.checker.units.qual.K; -import org.checkerframework.checker.units.qual.cd; -import org.checkerframework.checker.units.qual.degrees; -import org.checkerframework.checker.units.qual.g; -import org.checkerframework.checker.units.qual.h; -import org.checkerframework.checker.units.qual.kg; -import org.checkerframework.checker.units.qual.km; -import org.checkerframework.checker.units.qual.km2; -import org.checkerframework.checker.units.qual.kmPERh; -import org.checkerframework.checker.units.qual.m; -import org.checkerframework.checker.units.qual.m2; -import org.checkerframework.checker.units.qual.mPERs; -import org.checkerframework.checker.units.qual.mPERs2; -import org.checkerframework.checker.units.qual.min; -import org.checkerframework.checker.units.qual.mm; -import org.checkerframework.checker.units.qual.mm2; -import org.checkerframework.checker.units.qual.mol; -import org.checkerframework.checker.units.qual.radians; -import org.checkerframework.checker.units.qual.s; -import org.checkerframework.framework.qual.AnnotatedFor; - -// TODO: add fromTo methods for all useful unit combinations. - -/** Utility methods to generate annotated types and to convert between them. */ -@SuppressWarnings({"units", "checkstyle:constantname"}) -@AnnotatedFor("nullness") -public class UnitsTools { - // Acceleration - public static final @mPERs2 int mPERs2 = 1; - - // Angle - public static final @radians double rad = 1; - public static final @degrees double deg = 1; - - public static @radians double toRadians(@degrees double angdeg) { - return Math.toRadians(angdeg); - } - - public static @degrees double toDegrees(@radians double angrad) { - return Math.toDegrees(angrad); - } - - // Area - public static final @mm2 int mm2 = 1; - public static final @m2 int m2 = 1; - public static final @km2 int km2 = 1; - - // Current - public static final @A int A = 1; - - // Luminance - public static final @cd int cd = 1; - - // Lengths - public static final @mm int mm = 1; - public static final @m int m = 1; - public static final @km int km = 1; - - public static @m int fromMilliMeterToMeter(@mm int mm) { - return mm / 1000; - } - - public static @mm int fromMeterToMilliMeter(@m int m) { - return m * 1000; - } - - public static @km int fromMeterToKiloMeter(@m int m) { - return m / 1000; - } - - public static @m int fromKiloMeterToMeter(@km int km) { - return km * 1000; - } - - // Mass - public static final @g int g = 1; - public static final @kg int kg = 1; - - public static @kg int fromGramToKiloGram(@g int g) { - return g / 1000; - } - - public static @g int fromKiloGramToGram(@kg int kg) { - return kg * 1000; - } - - // Speed - public static final @mPERs int mPERs = 1; - public static final @kmPERh int kmPERh = 1; - - public static @kmPERh double fromMeterPerSecondToKiloMeterPerHour(@mPERs double mps) { - return mps * 3.6d; - } - - public static @mPERs double fromKiloMeterPerHourToMeterPerSecond(@kmPERh double kmph) { - return kmph / 3.6d; - } - - // Substance - public static final @mol int mol = 1; - - // Temperature - public static final @K int K = 1; - public static final @C int C = 1; - - public static @C int fromKelvinToCelsius(@K int k) { - return k - (int) 273.15; - } - - public static @K int fromCelsiusToKelvin(@C int c) { - return c + (int) 273.15; - } - - // Time - public static final @s int s = 1; - public static final @min int min = 1; - public static final @h int h = 1; - - public static @min int fromSecondToMinute(@s int s) { - return s / 60; - } - - public static @s int fromMinuteToSecond(@min int min) { - return min * 60; - } - - public static @h int fromMinuteToHour(@min int min) { - return min / 60; - } - - public static @min int fromHourToMinute(@h int h) { - return h * 60; - } -} diff --git a/checker-qual/src/main/java/org/checkerframework/framework/util/PurityUnqualified.java b/checker-qual/src/main/java/org/checkerframework/framework/util/PurityUnqualified.java deleted file mode 100644 index 4c51c1bc0b1..00000000000 --- a/checker-qual/src/main/java/org/checkerframework/framework/util/PurityUnqualified.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.checkerframework.framework.util; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; -import org.checkerframework.framework.qual.InvisibleQualifier; -import org.checkerframework.framework.qual.SubtypeOf; - -/** - * An annotation intended solely for representing an unqualified type in the qualifier hierarchy for - * the Purity Checker. - * - * @checker_framework.manual #purity-checker Purity Checker - */ -@Documented -@Retention(RetentionPolicy.SOURCE) // do not store in .class file -@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) -@SubtypeOf({}) -@DefaultQualifierInHierarchy -@InvisibleQualifier -public @interface PurityUnqualified {} diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index 60d4838c825..72cef6c96ad 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -1,6 +1,11 @@ package org.checkerframework.framework.flow; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; import java.util.concurrent.atomic.AtomicLong; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; From f93a6dc354025339cc76ddd528d22e1305395f4c Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Fri, 5 Mar 2021 13:47:10 -0800 Subject: [PATCH 034/113] checker-qual signedness fix --- .../signedness/SignednessUtilExtra.java | 68 ------------------- 1 file changed, 68 deletions(-) delete mode 100644 checker-qual/src/main/java/org/checkerframework/checker/signedness/SignednessUtilExtra.java diff --git a/checker-qual/src/main/java/org/checkerframework/checker/signedness/SignednessUtilExtra.java b/checker-qual/src/main/java/org/checkerframework/checker/signedness/SignednessUtilExtra.java deleted file mode 100644 index 6b38b1d5202..00000000000 --- a/checker-qual/src/main/java/org/checkerframework/checker/signedness/SignednessUtilExtra.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.checkerframework.checker.signedness; - -import java.awt.Dimension; -import java.awt.image.BufferedImage; -import org.checkerframework.checker.signedness.qual.Unsigned; -import org.checkerframework.framework.qual.AnnotatedFor; - -/** - * Provides more static utility methods for unsigned values. These methods use Java packages not - * included in Android. {@link SignednessUtil} has more methods. - * - * @checker_framework.manual #signedness-utilities Utility routines for manipulating unsigned values - */ -@AnnotatedFor("nullness") -public class SignednessUtilExtra { - /** Do not instantiate this class. */ - private SignednessUtilExtra() { - throw new Error("Do not instantiate"); - } - - /** Gets the unsigned width of a {@code Dimension}. */ - @SuppressWarnings("signedness") - public static @Unsigned int dimensionUnsignedWidth(Dimension dim) { - return dim.width; - } - - /** Gets the unsigned height of a {@code Dimension}. */ - @SuppressWarnings("signedness") - public static @Unsigned int dimensionUnsignedHeight(Dimension dim) { - return dim.height; - } - - /** - * Sets rgb of BufferedImage b given unsigned ints. This method is a wrapper around {@link - * java.awt.image.BufferedImage#setRGB(int, int, int, int, int[], int, int) setRGB(int, int, - * int, int, int[], int, int)}, but assumes that the input should be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static void setUnsignedRGB( - BufferedImage b, - int startX, - int startY, - int w, - int h, - @Unsigned int[] rgbArray, - int offset, - int scansize) { - b.setRGB(startX, startY, w, h, rgbArray, offset, scansize); - } - - /** - * Gets rgb of BufferedImage b as unsigned ints. This method is a wrapper around {@link - * java.awt.image.BufferedImage#getRGB(int, int, int, int, int[], int, int) getRGB(int, int, - * int, int, int[], int, int)}, but assumes that the output should be interpreted as unsigned. - */ - @SuppressWarnings("signedness") - public static @Unsigned int[] getUnsignedRGB( - BufferedImage b, - int startX, - int startY, - int w, - int h, - @Unsigned int[] rgbArray, - int offset, - int scansize) { - return b.getRGB(startX, startY, w, h, rgbArray, offset, scansize); - } -} From 810a45a7dded66d56fb28fe988b1a01f1ae265c2 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Tue, 9 Mar 2021 15:07:34 -0800 Subject: [PATCH 035/113] fix documentation for SideEffectsOnly --- .../dataflow/qual/SideEffectsOnly.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java index 505c053a6b7..883e285efbe 100644 --- a/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java +++ b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java @@ -8,9 +8,9 @@ import org.checkerframework.framework.qual.JavaExpression; /** - * This method declaration annotation can be used to specify the expressions that a method side - * effects. In other words, the method only side effects those expressions that are supplied as - * annotation values to {@code @SideEffectsOnly}. + * A method annotated with the declaration annotation {@code @SideEffectsOnly} could side effect + * those expressions that are supplied as annotation values to {@code @SideEffectsOnly}. The + * annotation values are an upper bound of all expressions that the method side-effects. * * @checker_framework.manual #type-refinement-purity Side effects only */ @@ -19,9 +19,11 @@ @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) public @interface SideEffectsOnly { /** - * The expressions that this method side effects. + * An upper bound of the expressions that this method side effects. * - * @return Java expressions that are side-effected by this method + * @return Java expression(s) that represent an upper bound of expressions side-effected by this + * method + * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions */ @JavaExpression public String[] value(); From 6c3560e8e8f6fa5c38a57186c41e37f722308080 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Tue, 9 Mar 2021 15:24:41 -0800 Subject: [PATCH 036/113] SideEffectsOnly empty test --- .../framework/flow/CFAbstractStore.java | 7 +++-- .../sideeffectsonly/SideEffectsOnlyEmpty.java | 29 +++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 framework/tests/sideeffectsonly/SideEffectsOnlyEmpty.java diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index 72cef6c96ad..ed36ff10e50 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -210,8 +210,9 @@ protected boolean isSideEffectsOnly( *

  • If the method is side-effect-free (as indicated by {@link * org.checkerframework.dataflow.qual.SideEffectFree} or {@link * org.checkerframework.dataflow.qual.Pure}), then no information needs to be removed. - *
  • If the method side effects any expression (specified as annotation values of - * {@code @SideEffectsOnly}), then information about those expressions is removed. + *
  • If the method side effects at most a limited set of expressions (specified as + * annotation values of {@code @SideEffectsOnly}), then information about those + * expressions is removed. *
  • Otherwise, all information about field accesses {@code a.f} needs to be removed, except * if the method {@code n} cannot modify {@code a.f} (e.g., if {@code a} is a local * variable or {@code this}, and {@code f} is final). @@ -226,7 +227,7 @@ public void updateForMethodCall( ExecutableElement method = n.getTarget().getMethod(); // List of expressions that this method side effects (specified as annotation values of - // @SideEffectsOnly). If the value is null, then there is no @SideEffectsOnly annotation. + // @SideEffectsOnly). If the List is empty, then there is no @SideEffectsOnly annotation. List sideEffectsOnlyExpressions = new ArrayList<>(); if (isSideEffectsOnly(atypeFactory, method)) { SourceChecker checker = analysis.checker; diff --git a/framework/tests/sideeffectsonly/SideEffectsOnlyEmpty.java b/framework/tests/sideeffectsonly/SideEffectsOnlyEmpty.java new file mode 100644 index 00000000000..1cd5c96971f --- /dev/null +++ b/framework/tests/sideeffectsonly/SideEffectsOnlyEmpty.java @@ -0,0 +1,29 @@ +package sideeffectsonly; + +public class SideEffectsOnlyEmpty { + void test(Object x) { + method(x); + method1(x); + // :: error: argument.type.incompatible + method2(x); + } + + @org.checkerframework.framework.qual.EnsuresQualifier( + expression = "#1", + qualifier = + org.checkerframework.framework.testchecker.sideeffectsonly.qual + .SideEffectsOnlyToyBottom.class) + // :: error: contracts.postcondition.not.satisfied + void method(Object x) {} + + @org.checkerframework.dataflow.qual.SideEffectsOnly({}) + void method1( + @org.checkerframework.framework.testchecker.sideeffectsonly.qual + .SideEffectsOnlyToyBottom + Object x) {} + + void method2( + @org.checkerframework.framework.testchecker.sideeffectsonly.qual + .SideEffectsOnlyToyBottom + Object x) {} +} From d4d967d25ff85c703730fbca0837c500eab46b91 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Tue, 9 Mar 2021 15:35:01 -0800 Subject: [PATCH 037/113] SideEffectsOnly documentation fixes --- docs/manual/advanced-features.tex | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/manual/advanced-features.tex b/docs/manual/advanced-features.tex index d1694cbbfc4..3c43d73f32c 100644 --- a/docs/manual/advanced-features.tex +++ b/docs/manual/advanced-features.tex @@ -1007,15 +1007,17 @@ dataflow facts. The \refqualclass{dataflow/qual}{SideEffectsOnly} annotation -specifies the superset of expressions that the method might side effect. -So expressions that are not specified as annotation values of +specifies a superset of the expressions that the method might side effect. +Expressions that are not specified as annotation values of \refqualclass{dataflow/qual}{SideEffectsOnly} have their dataflow facts unaffected. If a method has neither \refqualclass{dataflow/qual}{SideEffectsOnly} -nor \refqualclass{dataflow/qual}{SideEffectFree} annotation, then it is assumed -that the method side-effects all expressions (fields and arguments). +nor \refqualclass{dataflow/qual}{SideEffectFree} annotation, then the Checker Framework +assumes that the method side effects all expressions (fields and arguments). A method cannot be annotated with both \refqualclass{dataflow/qual}{SideEffectFree} and \refqualclass{dataflow/qual}{SideEffectsOnly}. +A method annotated with \<@SideEffectsOnly(\{\})> (i.e without any annotation value) +is equivalent to annotating it with \<@SideEffectFree>. Calling a method twice might have different results, so facts known about one call cannot be relied upon at another call. From 47125847c2c3215f3b66d40b25c3461ec145b50f Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 18 Mar 2021 08:10:38 -0700 Subject: [PATCH 038/113] Documentation improvements --- .../dataflow/qual/SideEffectsOnly.java | 7 +++---- docs/manual/advanced-features.tex | 10 ++++------ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java index 883e285efbe..e5e8bae8d98 100644 --- a/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java +++ b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java @@ -8,9 +8,8 @@ import org.checkerframework.framework.qual.JavaExpression; /** - * A method annotated with the declaration annotation {@code @SideEffectsOnly} could side effect - * those expressions that are supplied as annotation values to {@code @SideEffectsOnly}. The - * annotation values are an upper bound of all expressions that the method side-effects. + * A method annotated with the declaration annotation {@code @SideEffectsOnly(A, B)} side-effects at + * most the expressions A and B. All other expressions have the same value before and after a call. * * @checker_framework.manual #type-refinement-purity Side effects only */ @@ -19,7 +18,7 @@ @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) public @interface SideEffectsOnly { /** - * An upper bound of the expressions that this method side effects. + * An upper bound on the expressions that this method side effects. * * @return Java expression(s) that represent an upper bound of expressions side-effected by this * method diff --git a/docs/manual/advanced-features.tex b/docs/manual/advanced-features.tex index 3c43d73f32c..5bbc9c25724 100644 --- a/docs/manual/advanced-features.tex +++ b/docs/manual/advanced-features.tex @@ -1008,16 +1008,14 @@ The \refqualclass{dataflow/qual}{SideEffectsOnly} annotation specifies a superset of the expressions that the method might side effect. -Expressions that are not specified as annotation values of -\refqualclass{dataflow/qual}{SideEffectsOnly} have their dataflow facts -unaffected. +Expressions that are not specified as annotation arguments/elements to +\refqualclass{dataflow/qual}{SideEffectsOnly} are not side-effected. If a method has neither \refqualclass{dataflow/qual}{SideEffectsOnly} nor \refqualclass{dataflow/qual}{SideEffectFree} annotation, then the Checker Framework -assumes that the method side effects all expressions (fields and arguments). +assumes that the method may side-effect all expressions (fields and arguments). A method cannot be annotated with both \refqualclass{dataflow/qual}{SideEffectFree} and \refqualclass{dataflow/qual}{SideEffectsOnly}. -A method annotated with \<@SideEffectsOnly(\{\})> (i.e without any annotation value) -is equivalent to annotating it with \<@SideEffectFree>. +The annotation \<@SideEffectsOnly(\{\})> is equivalent to \<@SideEffectFree>. Calling a method twice might have different results, so facts known about one call cannot be relied upon at another call. From 9275dee2059ea656992a8a52c43d58d7ed2ced2b Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 18 Mar 2021 08:16:46 -0700 Subject: [PATCH 039/113] Tweaks --- .../framework/flow/CFAbstractStore.java | 15 +++++++-------- .../qual/SideEffectsOnlyToyBottom.java | 4 +--- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index ed36ff10e50..9f613d82fe1 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -185,11 +185,11 @@ protected boolean isSideEffectFree( } /** - * Indicates whether the method has the declaration annotation {@code @SideEffectsOnly}. + * Returns true if the method has the declaration annotation {@code @SideEffectsOnly}. * * @param atypeFactory the type factory used to retrieve annotations on the method element * @param method the method element - * @return whether the method is annotated with {@code @SideEffectsOnly} + * @return true if the method is annotated with {@code @SideEffectsOnly} */ protected boolean isSideEffectsOnly( AnnotatedTypeFactory atypeFactory, ExecutableElement method) { @@ -210,7 +210,7 @@ protected boolean isSideEffectsOnly( *
  • If the method is side-effect-free (as indicated by {@link * org.checkerframework.dataflow.qual.SideEffectFree} or {@link * org.checkerframework.dataflow.qual.Pure}), then no information needs to be removed. - *
  • If the method side effects at most a limited set of expressions (specified as + *
  • If the method side-effects at most a limited set of expressions (specified as * annotation values of {@code @SideEffectsOnly}), then information about those * expressions is removed. *
  • Otherwise, all information about field accesses {@code a.f} needs to be removed, except @@ -226,7 +226,7 @@ public void updateForMethodCall( MethodInvocationNode n, AnnotatedTypeFactory atypeFactory, V val) { ExecutableElement method = n.getTarget().getMethod(); - // List of expressions that this method side effects (specified as annotation values of + // List of expressions that this method side-effects (specified as arguments/elements of // @SideEffectsOnly). If the List is empty, then there is no @SideEffectsOnly annotation. List sideEffectsOnlyExpressions = new ArrayList<>(); if (isSideEffectsOnly(atypeFactory, method)) { @@ -236,13 +236,12 @@ public void updateForMethodCall( n, checker); AnnotationMirror sefOnlyAnnotation = - atypeFactory.getDeclAnnotation( - method, org.checkerframework.dataflow.qual.SideEffectsOnly.class); - List sideEffectsOnlyExpressionsString = + atypeFactory.getDeclAnnotation(method, SideEffectsOnly.class); + List sideEffectsOnlyExpressionStrings = AnnotationUtils.getElementValueArray( sefOnlyAnnotation, "value", String.class, true); - for (String st : sideEffectsOnlyExpressionsString) { + for (String st : sideEffectsOnlyExpressionStrings) { try { JavaExpression exprJe = JavaExpressionParseUtil.parse(st, methodUseContext); sideEffectsOnlyExpressions.add(exprJe); diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/SideEffectsOnlyToyBottom.java b/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/SideEffectsOnlyToyBottom.java index 1482d05658a..fb5e2dabd78 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/SideEffectsOnlyToyBottom.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/qual/SideEffectsOnlyToyBottom.java @@ -15,7 +15,5 @@ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) -@SubtypeOf({ - org.checkerframework.framework.testchecker.sideeffectsonly.qual.SideEffectsOnlyToyTop.class -}) +@SubtypeOf({SideEffectsOnlyToyTop.class}) public @interface SideEffectsOnlyToyBottom {} From 3d2caa14b939178219e1b67ca74449495dbb5765 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Mon, 22 Mar 2021 11:29:52 -0700 Subject: [PATCH 040/113] Fix error and test case for SideEffectsOnly field --- .../framework/flow/CFAbstractStore.java | 2 +- .../sideeffectsonly/SideEffectsOnlyField.java | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 framework/tests/sideeffectsonly/SideEffectsOnlyField.java diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index 9f613d82fe1..f4f1f7d77dd 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -269,7 +269,7 @@ public void updateForMethodCall( // update local variables localVariableValues.keySet().remove(e); // update field values - localVariableValues.keySet().remove(e); + fieldValues.keySet().remove(e); } } } else { diff --git a/framework/tests/sideeffectsonly/SideEffectsOnlyField.java b/framework/tests/sideeffectsonly/SideEffectsOnlyField.java new file mode 100644 index 00000000000..6ccf2583df4 --- /dev/null +++ b/framework/tests/sideeffectsonly/SideEffectsOnlyField.java @@ -0,0 +1,31 @@ +package sideeffectsonly; + +public class SideEffectsOnlyField { + Object a; + Object b; + + static void test(SideEffectsOnlyField arg) { + method(arg); + method3(arg); + // :: error: argument.type.incompatible + method2(arg.a); + method2(arg.b); + } + + @org.checkerframework.framework.qual.EnsuresQualifier( + expression = {"#1.a", "#1.b"}, + qualifier = + org.checkerframework.framework.testchecker.sideeffectsonly.qual + .SideEffectsOnlyToyBottom.class) + // :: error: contracts.postcondition.not.satisfied + static void method(SideEffectsOnlyField x) {} + + @org.checkerframework.dataflow.qual.SideEffectsOnly("#1.a") + static void method3(SideEffectsOnlyField z) {} + + @org.checkerframework.dataflow.qual.SideEffectFree + static void method2( + @org.checkerframework.framework.testchecker.sideeffectsonly.qual + .SideEffectsOnlyToyBottom + Object x) {} +} From 875f638a77af51607eecd7a37dca256834dfa457 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Mon, 22 Mar 2021 11:47:50 -0700 Subject: [PATCH 041/113] SideEffects relationship documentatio --- docs/manual/advanced-features.tex | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/docs/manual/advanced-features.tex b/docs/manual/advanced-features.tex index 5bbc9c25724..4b5f0664747 100644 --- a/docs/manual/advanced-features.tex +++ b/docs/manual/advanced-features.tex @@ -1004,18 +1004,8 @@ the refined type, because the method might assign a field. The \refqualclass{dataflow/qual}{SideEffectFree} annotation indicates that the method has no side effects, so calling it does not invalidate any -dataflow facts. - -The \refqualclass{dataflow/qual}{SideEffectsOnly} annotation +dataflow facts. The \refqualclass{dataflow/qual}{SideEffectsOnly} annotation specifies a superset of the expressions that the method might side effect. -Expressions that are not specified as annotation arguments/elements to -\refqualclass{dataflow/qual}{SideEffectsOnly} are not side-effected. -If a method has neither \refqualclass{dataflow/qual}{SideEffectsOnly} -nor \refqualclass{dataflow/qual}{SideEffectFree} annotation, then the Checker Framework -assumes that the method may side-effect all expressions (fields and arguments). -A method cannot be annotated with both \refqualclass{dataflow/qual}{SideEffectFree} -and \refqualclass{dataflow/qual}{SideEffectsOnly}. -The annotation \<@SideEffectsOnly(\{\})> is equivalent to \<@SideEffectFree>. Calling a method twice might have different results, so facts known about one call cannot be relied upon at another call. @@ -1033,7 +1023,6 @@ Chapter~\ref{purity-checker} gives more information about these annotations. This section explains how to use them to improve type refinement. - \subsubsectionAndLabel{Side effects}{type-refinement-side-effects} Consider the following declarations and uses: @@ -1101,6 +1090,16 @@ \end{Verbatim} \end{enumerate} +\subsubsectionAndLabel{Relationship between \<@SideEffectFree> and \<@SideEffectsOnly>}{side-effects-relationship} + +Expressions that are not specified as annotation arguments/elements to +\refqualclass{dataflow/qual}{SideEffectsOnly} are not side-effected. +If a method has neither \refqualclass{dataflow/qual}{SideEffectsOnly} +nor \refqualclass{dataflow/qual}{SideEffectFree} annotation, then the Checker Framework +assumes that the method may side-effect all expressions (fields and arguments). +A method cannot be annotated with both \refqualclass{dataflow/qual}{SideEffectFree} +and \refqualclass{dataflow/qual}{SideEffectsOnly}. +The annotation \<@SideEffectsOnly(\{\})> is equivalent to \<@SideEffectFree>. \subsubsectionAndLabel{Deterministic methods}{type-refinement-determinism} From 309235430d9f33ba21cc3062a7d124140fde05e4 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 23 Mar 2021 17:16:28 -0700 Subject: [PATCH 042/113] Undo whitespace change --- docs/manual/advanced-features.tex | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/manual/advanced-features.tex b/docs/manual/advanced-features.tex index 4b5f0664747..25459bef729 100644 --- a/docs/manual/advanced-features.tex +++ b/docs/manual/advanced-features.tex @@ -1023,6 +1023,7 @@ Chapter~\ref{purity-checker} gives more information about these annotations. This section explains how to use them to improve type refinement. + \subsubsectionAndLabel{Side effects}{type-refinement-side-effects} Consider the following declarations and uses: From 4a11c51be88681ef901688f9e952aebb1e1b9244 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 23 Mar 2021 19:05:20 -0700 Subject: [PATCH 043/113] Tweak documentation --- .../dataflow/qual/SideEffectsOnly.java | 5 +++-- docs/manual/advanced-features.tex | 6 +++--- .../framework/flow/CFAbstractStore.java | 11 +++++------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java index e5e8bae8d98..979b70a923a 100644 --- a/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java +++ b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java @@ -8,8 +8,9 @@ import org.checkerframework.framework.qual.JavaExpression; /** - * A method annotated with the declaration annotation {@code @SideEffectsOnly(A, B)} side-effects at - * most the expressions A and B. All other expressions have the same value before and after a call. + * A method annotated with the declaration annotation {@code @SideEffectsOnly(A, B)} perfarms + * side-effects on at most the expressions A and B. All other expressions have the same value before + * and after a call. * * @checker_framework.manual #type-refinement-purity Side effects only */ diff --git a/docs/manual/advanced-features.tex b/docs/manual/advanced-features.tex index 25459bef729..db3d893942f 100644 --- a/docs/manual/advanced-features.tex +++ b/docs/manual/advanced-features.tex @@ -1005,7 +1005,7 @@ The \refqualclass{dataflow/qual}{SideEffectFree} annotation indicates that the method has no side effects, so calling it does not invalidate any dataflow facts. The \refqualclass{dataflow/qual}{SideEffectsOnly} annotation -specifies a superset of the expressions that the method might side effect. +specifies the expressions that the method might side effect. Calling a method twice might have different results, so facts known about one call cannot be relied upon at another call. @@ -1095,8 +1095,8 @@ Expressions that are not specified as annotation arguments/elements to \refqualclass{dataflow/qual}{SideEffectsOnly} are not side-effected. -If a method has neither \refqualclass{dataflow/qual}{SideEffectsOnly} -nor \refqualclass{dataflow/qual}{SideEffectFree} annotation, then the Checker Framework +If a method has no \refqualclass{dataflow/qual}{SideEffectsOnly} +or \refqualclass{dataflow/qual}{SideEffectFree} annotation, then the Checker Framework assumes that the method may side-effect all expressions (fields and arguments). A method cannot be annotated with both \refqualclass{dataflow/qual}{SideEffectFree} and \refqualclass{dataflow/qual}{SideEffectsOnly}. diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index f4f1f7d77dd..1c269b3f386 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -187,8 +187,8 @@ protected boolean isSideEffectFree( /** * Returns true if the method has the declaration annotation {@code @SideEffectsOnly}. * - * @param atypeFactory the type factory used to retrieve annotations on the method element - * @param method the method element + * @param atypeFactory the type factory used to retrieve annotations + * @param method a method * @return true if the method is annotated with {@code @SideEffectsOnly} */ protected boolean isSideEffectsOnly( @@ -210,9 +210,8 @@ protected boolean isSideEffectsOnly( *
  • If the method is side-effect-free (as indicated by {@link * org.checkerframework.dataflow.qual.SideEffectFree} or {@link * org.checkerframework.dataflow.qual.Pure}), then no information needs to be removed. - *
  • If the method side-effects at most a limited set of expressions (specified as - * annotation values of {@code @SideEffectsOnly}), then information about those - * expressions is removed. + *
  • If the method side-effects at most a limited set of expressions (specified by + * {@code @SideEffectsOnly}), then information about those expressions is removed. *
  • Otherwise, all information about field accesses {@code a.f} needs to be removed, except * if the method {@code n} cannot modify {@code a.f} (e.g., if {@code a} is a local * variable or {@code this}, and {@code f} is final). @@ -227,7 +226,7 @@ public void updateForMethodCall( ExecutableElement method = n.getTarget().getMethod(); // List of expressions that this method side-effects (specified as arguments/elements of - // @SideEffectsOnly). If the List is empty, then there is no @SideEffectsOnly annotation. + // @SideEffectsOnly). If the list is empty, then there is no @SideEffectsOnly annotation. List sideEffectsOnlyExpressions = new ArrayList<>(); if (isSideEffectsOnly(atypeFactory, method)) { SourceChecker checker = analysis.checker; From bf86930a917dc43db9eced85a1d8683d34d463f2 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Tue, 30 Mar 2021 16:15:33 -0700 Subject: [PATCH 044/113] clean imports --- .../sideeffectsonly/SideEffectsMultiple.java | 17 ++++---------- .../sideeffectsonly/SideEffectsOnlyEmpty.java | 22 +++++++------------ .../sideeffectsonly/SideEffectsOnlyField.java | 20 ++++++++--------- .../sideeffectsonly/SideEffectsOnlyTest.java | 22 +++++-------------- .../sideeffectsonly/SideEffectsTest.java | 17 ++++---------- 5 files changed, 31 insertions(+), 67 deletions(-) diff --git a/framework/tests/sideeffectsonly/SideEffectsMultiple.java b/framework/tests/sideeffectsonly/SideEffectsMultiple.java index 55a8a816576..3736ee3656b 100644 --- a/framework/tests/sideeffectsonly/SideEffectsMultiple.java +++ b/framework/tests/sideeffectsonly/SideEffectsMultiple.java @@ -2,6 +2,7 @@ import org.checkerframework.dataflow.qual.SideEffectsOnly; import org.checkerframework.framework.qual.EnsuresQualifier; +import org.checkerframework.framework.testchecker.sideeffectsonly.qual.SideEffectsOnlyToyBottom; public class SideEffectsMultiple { void test(Object x) { @@ -11,22 +12,12 @@ void test(Object x) { method2(x); } - @EnsuresQualifier( - expression = "#1", - qualifier = - org.checkerframework.framework.testchecker.sideeffectsonly.qual - .SideEffectsOnlyToyBottom.class) + @EnsuresQualifier(expression = "#1", qualifier = SideEffectsOnlyToyBottom.class) // :: error: contracts.postcondition.not.satisfied void method(Object x) {} @SideEffectsOnly({"this", "#1"}) - void method1( - @org.checkerframework.framework.testchecker.sideeffectsonly.qual - .SideEffectsOnlyToyBottom - Object y) {} + void method1(@SideEffectsOnlyToyBottom Object y) {} - void method2( - @org.checkerframework.framework.testchecker.sideeffectsonly.qual - .SideEffectsOnlyToyBottom - Object x) {} + void method2(@SideEffectsOnlyToyBottom Object x) {} } diff --git a/framework/tests/sideeffectsonly/SideEffectsOnlyEmpty.java b/framework/tests/sideeffectsonly/SideEffectsOnlyEmpty.java index 1cd5c96971f..57d31ab8616 100644 --- a/framework/tests/sideeffectsonly/SideEffectsOnlyEmpty.java +++ b/framework/tests/sideeffectsonly/SideEffectsOnlyEmpty.java @@ -1,5 +1,9 @@ package sideeffectsonly; +import org.checkerframework.dataflow.qual.SideEffectsOnly; +import org.checkerframework.framework.qual.EnsuresQualifier; +import org.checkerframework.framework.testchecker.sideeffectsonly.qual.SideEffectsOnlyToyBottom; + public class SideEffectsOnlyEmpty { void test(Object x) { method(x); @@ -8,22 +12,12 @@ void test(Object x) { method2(x); } - @org.checkerframework.framework.qual.EnsuresQualifier( - expression = "#1", - qualifier = - org.checkerframework.framework.testchecker.sideeffectsonly.qual - .SideEffectsOnlyToyBottom.class) + @EnsuresQualifier(expression = "#1", qualifier = SideEffectsOnlyToyBottom.class) // :: error: contracts.postcondition.not.satisfied void method(Object x) {} - @org.checkerframework.dataflow.qual.SideEffectsOnly({}) - void method1( - @org.checkerframework.framework.testchecker.sideeffectsonly.qual - .SideEffectsOnlyToyBottom - Object x) {} + @SideEffectsOnly({}) + void method1(@SideEffectsOnlyToyBottom Object x) {} - void method2( - @org.checkerframework.framework.testchecker.sideeffectsonly.qual - .SideEffectsOnlyToyBottom - Object x) {} + void method2(@SideEffectsOnlyToyBottom Object x) {} } diff --git a/framework/tests/sideeffectsonly/SideEffectsOnlyField.java b/framework/tests/sideeffectsonly/SideEffectsOnlyField.java index 6ccf2583df4..348c1fdde40 100644 --- a/framework/tests/sideeffectsonly/SideEffectsOnlyField.java +++ b/framework/tests/sideeffectsonly/SideEffectsOnlyField.java @@ -1,5 +1,10 @@ package sideeffectsonly; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.dataflow.qual.SideEffectsOnly; +import org.checkerframework.framework.qual.EnsuresQualifier; +import org.checkerframework.framework.testchecker.sideeffectsonly.qual.SideEffectsOnlyToyBottom; + public class SideEffectsOnlyField { Object a; Object b; @@ -12,20 +17,15 @@ static void test(SideEffectsOnlyField arg) { method2(arg.b); } - @org.checkerframework.framework.qual.EnsuresQualifier( + @EnsuresQualifier( expression = {"#1.a", "#1.b"}, - qualifier = - org.checkerframework.framework.testchecker.sideeffectsonly.qual - .SideEffectsOnlyToyBottom.class) + qualifier = SideEffectsOnlyToyBottom.class) // :: error: contracts.postcondition.not.satisfied static void method(SideEffectsOnlyField x) {} - @org.checkerframework.dataflow.qual.SideEffectsOnly("#1.a") + @SideEffectsOnly("#1.a") static void method3(SideEffectsOnlyField z) {} - @org.checkerframework.dataflow.qual.SideEffectFree - static void method2( - @org.checkerframework.framework.testchecker.sideeffectsonly.qual - .SideEffectsOnlyToyBottom - Object x) {} + @SideEffectFree + static void method2(@SideEffectsOnlyToyBottom Object x) {} } diff --git a/framework/tests/sideeffectsonly/SideEffectsOnlyTest.java b/framework/tests/sideeffectsonly/SideEffectsOnlyTest.java index b359ad98054..db3535b3e63 100644 --- a/framework/tests/sideeffectsonly/SideEffectsOnlyTest.java +++ b/framework/tests/sideeffectsonly/SideEffectsOnlyTest.java @@ -2,6 +2,7 @@ import org.checkerframework.dataflow.qual.SideEffectsOnly; import org.checkerframework.framework.qual.EnsuresQualifier; +import org.checkerframework.framework.testchecker.sideeffectsonly.qual.SideEffectsOnlyToyBottom; public class SideEffectsOnlyTest { void test(Object x) { @@ -13,29 +14,16 @@ void test(Object x) { method3(x); } - @EnsuresQualifier( - expression = "#1", - qualifier = - org.checkerframework.framework.testchecker.sideeffectsonly.qual - .SideEffectsOnlyToyBottom.class) + @EnsuresQualifier(expression = "#1", qualifier = SideEffectsOnlyToyBottom.class) // :: error: contracts.postcondition.not.satisfied void method(Object x) {} @SideEffectsOnly({"this"}) - void method1( - @org.checkerframework.framework.testchecker.sideeffectsonly.qual - .SideEffectsOnlyToyBottom - Object x) {} + void method1(@SideEffectsOnlyToyBottom Object x) {} @SideEffectsOnly({"#1"}) - void method2( - @org.checkerframework.framework.testchecker.sideeffectsonly.qual - .SideEffectsOnlyToyBottom - Object x) {} + void method2(@SideEffectsOnlyToyBottom Object x) {} @SideEffectsOnly({"this"}) - void method3( - @org.checkerframework.framework.testchecker.sideeffectsonly.qual - .SideEffectsOnlyToyBottom - Object z) {} + void method3(@SideEffectsOnlyToyBottom Object z) {} } diff --git a/framework/tests/sideeffectsonly/SideEffectsTest.java b/framework/tests/sideeffectsonly/SideEffectsTest.java index 8cfc7caa0f1..7bc447eb7c7 100644 --- a/framework/tests/sideeffectsonly/SideEffectsTest.java +++ b/framework/tests/sideeffectsonly/SideEffectsTest.java @@ -1,6 +1,7 @@ package sideeffectsonly; import org.checkerframework.framework.qual.EnsuresQualifier; +import org.checkerframework.framework.testchecker.sideeffectsonly.qual.SideEffectsOnlyToyBottom; public class SideEffectsTest { void test(Object x) { @@ -10,21 +11,11 @@ void test(Object x) { method2(x); } - @EnsuresQualifier( - expression = "#1", - qualifier = - org.checkerframework.framework.testchecker.sideeffectsonly.qual - .SideEffectsOnlyToyBottom.class) + @EnsuresQualifier(expression = "#1", qualifier = SideEffectsOnlyToyBottom.class) // :: error: contracts.postcondition.not.satisfied void method(Object x) {} - void method1( - @org.checkerframework.framework.testchecker.sideeffectsonly.qual - .SideEffectsOnlyToyBottom - Object x) {} + void method1(@SideEffectsOnlyToyBottom Object x) {} - void method2( - @org.checkerframework.framework.testchecker.sideeffectsonly.qual - .SideEffectsOnlyToyBottom - Object x) {} + void method2(@SideEffectsOnlyToyBottom Object x) {} } From dee4cd3b27445200d5e2dc49e61dbb3cd24d84cd Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Tue, 30 Mar 2021 16:21:15 -0700 Subject: [PATCH 045/113] report error correctly --- .../org/checkerframework/framework/flow/CFAbstractStore.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index 1c269b3f386..02c8df4ed24 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -245,7 +245,7 @@ public void updateForMethodCall( JavaExpression exprJe = JavaExpressionParseUtil.parse(st, methodUseContext); sideEffectsOnlyExpressions.add(exprJe); } catch (JavaExpressionParseUtil.JavaExpressionParseException ex) { - System.out.println(ex.getDiagMessage()); + checker.report(st, ex.getDiagMessage()); return; } } From 6e5c3913bea535947348ca4774abe33971abeaa0 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Tue, 30 Mar 2021 16:32:39 -0700 Subject: [PATCH 046/113] address review comment --- .../framework/flow/CFAbstractStore.java | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index 02c8df4ed24..cb6e7106d25 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -184,20 +184,6 @@ protected boolean isSideEffectFree( return PurityUtils.isSideEffectFree(atypeFactory, method); } - /** - * Returns true if the method has the declaration annotation {@code @SideEffectsOnly}. - * - * @param atypeFactory the type factory used to retrieve annotations - * @param method a method - * @return true if the method is annotated with {@code @SideEffectsOnly} - */ - protected boolean isSideEffectsOnly( - AnnotatedTypeFactory atypeFactory, ExecutableElement method) { - AnnotationMirror sefOnlyAnnotation = - atypeFactory.getDeclAnnotation(method, SideEffectsOnly.class); - return sefOnlyAnnotation != null; - } - /* --------------------------------------------------------- */ /* Handling of fields */ /* --------------------------------------------------------- */ @@ -222,20 +208,20 @@ protected boolean isSideEffectsOnly( * Furthermore, if the method is deterministic, we store its result {@code val} in the store. */ public void updateForMethodCall( - MethodInvocationNode n, AnnotatedTypeFactory atypeFactory, V val) { - ExecutableElement method = n.getTarget().getMethod(); + MethodInvocationNode methodInvocationNode, AnnotatedTypeFactory atypeFactory, V val) { + ExecutableElement method = methodInvocationNode.getTarget().getMethod(); // List of expressions that this method side-effects (specified as arguments/elements of // @SideEffectsOnly). If the list is empty, then there is no @SideEffectsOnly annotation. List sideEffectsOnlyExpressions = new ArrayList<>(); - if (isSideEffectsOnly(atypeFactory, method)) { + AnnotationMirror sefOnlyAnnotation = + atypeFactory.getDeclAnnotation(method, SideEffectsOnly.class); + if (sefOnlyAnnotation != null) { SourceChecker checker = analysis.checker; JavaExpressionParseUtil.JavaExpressionContext methodUseContext = JavaExpressionParseUtil.JavaExpressionContext.buildContextForMethodUse( - n, checker); + methodInvocationNode, checker); - AnnotationMirror sefOnlyAnnotation = - atypeFactory.getDeclAnnotation(method, SideEffectsOnly.class); List sideEffectsOnlyExpressionStrings = AnnotationUtils.getElementValueArray( sefOnlyAnnotation, "value", String.class, true); @@ -333,7 +319,7 @@ public void updateForMethodCall( } // store information about method call if possible - JavaExpression methodCall = JavaExpression.fromNode(n); + JavaExpression methodCall = JavaExpression.fromNode(methodInvocationNode); replaceValue(methodCall, val); } From 87bd9a7b7dd4129cebe06529bec4a23115f5db88 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Tue, 30 Mar 2021 16:59:36 -0700 Subject: [PATCH 047/113] reintroduce incorrectly deleted code --- .../org/checkerframework/framework/flow/CFAbstractStore.java | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index cb6e7106d25..2aafcdd23d5 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -259,6 +259,7 @@ public void updateForMethodCall( } } else { localVariableValues.keySet().removeIf(e -> !e.isUnmodifiableByOtherCode()); + thisValue = null; fieldValues.keySet().removeIf(e -> !e.isUnmodifiableByOtherCode()); } } else { From 7e0337c938e285983092e66694538e75cbebd590 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Tue, 30 Mar 2021 18:36:21 -0700 Subject: [PATCH 048/113] fix updateForMethodCall after merge --- .../framework/flow/CFAbstractStore.java | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index 25270b0461b..1bdc366deaf 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -1,11 +1,6 @@ package org.checkerframework.framework.flow; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.StringJoiner; +import java.util.*; import java.util.concurrent.atomic.AtomicLong; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; @@ -191,8 +186,6 @@ protected boolean isSideEffectFree(AnnotatedTypeFactory atypeFactory, Executable *
  • If the method is side-effect-free (as indicated by {@link * org.checkerframework.dataflow.qual.SideEffectFree} or {@link * org.checkerframework.dataflow.qual.Pure}), then no information needs to be removed. - *
  • If the method side-effects at most a limited set of expressions (specified by - * {@code @SideEffectsOnly}), then information about those expressions is removed. *
  • Otherwise, all information about field accesses {@code a.f} needs to be removed, except * if the method {@code n} cannot modify {@code a.f} (e.g., if {@code a} is a local variable * or {@code this}, and {@code f} is final). @@ -300,15 +293,17 @@ public void updateForMethodCall( } fieldValues = newFieldValues; } - // store information about method call if possible - JavaExpression methodCall = JavaExpression.fromNode(methodInvocationNode); - replaceValue(methodCall, val); + + // update array values + arrayValues.clear(); + + // update method values + methodValues.keySet().removeIf(e -> !e.isUnmodifiableByOtherCode()); } - // update array values - arrayValues.clear(); - // update method values - methodValues.keySet().removeIf(e -> !e.isUnmodifiableByOtherCode()); + // store information about method call if possible + JavaExpression methodCall = JavaExpression.fromNode(methodInvocationNode); + replaceValue(methodCall, val); } /** From 94176a4361e0944b7dd83b617fddf5cd5a514130 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Fri, 2 Apr 2021 10:48:35 -0700 Subject: [PATCH 049/113] fix imports --- .../checkerframework/framework/flow/CFAbstractStore.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index 1bdc366deaf..dfc7de24aee 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -1,6 +1,11 @@ package org.checkerframework.framework.flow; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; import java.util.concurrent.atomic.AtomicLong; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; From a2c749e62210d88fed5efe58720a0483ae7c37ff Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Tue, 6 Apr 2021 16:30:49 -0700 Subject: [PATCH 050/113] fix deprecated method --- .../framework/flow/CFAbstractStore.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index f286dcc7593..7bf9d6e6d78 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -44,6 +44,7 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.Pair; +import org.checkerframework.javacutil.TreeUtils; import org.plumelib.util.ToStringComparator; import org.plumelib.util.UniqueId; @@ -77,6 +78,9 @@ public abstract class CFAbstractStore, S extends CF /** Information collected about fields, using the internal representation {@link FieldAccess}. */ protected Map fieldValues; + /** The SideEffectsOnly.value argument/element. */ + public ExecutableElement sideEffectsOnlyValueElement; + /** * Returns information about fields. Clients should not side-effect the returned value, which is * aliased to internal state. @@ -130,6 +134,8 @@ protected CFAbstractStore(CFAbstractAnalysis analysis, boolean sequenti arrayValues = new HashMap<>(); classValues = new HashMap<>(); this.sequentialSemantics = sequentialSemantics; + sideEffectsOnlyValueElement = + TreeUtils.getMethod(SideEffectsOnly.class, "value", 0, analysis.env); } /** Copy constructor. */ @@ -142,6 +148,7 @@ protected CFAbstractStore(CFAbstractStore other) { arrayValues = new HashMap<>(other.arrayValues); classValues = new HashMap<>(other.classValues); sequentialSemantics = other.sequentialSemantics; + sideEffectsOnlyValueElement = other.sideEffectsOnlyValueElement; } /** @@ -213,7 +220,8 @@ public void updateForMethodCall( SourceChecker checker = analysis.checker; List sideEffectsOnlyExpressionStrings = - AnnotationUtils.getElementValueArray(sefOnlyAnnotation, "value", String.class, true); + AnnotationUtils.getElementValueArray( + sefOnlyAnnotation, sideEffectsOnlyValueElement, String.class); for (String st : sideEffectsOnlyExpressionStrings) { try { From 0d5d7b4eeea0c22ac3faff86dcee39cccc2f3d1d Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Wed, 7 Apr 2021 07:55:29 -0700 Subject: [PATCH 051/113] javadoc fixes --- .../org/checkerframework/framework/flow/CFAbstractStore.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index 7bf9d6e6d78..7a2aaf3c302 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -206,6 +206,10 @@ protected boolean isSideEffectFree(AnnotatedTypeFactory atypeFactory, Executable * * * Furthermore, if the method is deterministic, we store its result {@code val} in the store. + * + * @param methodInvocationNode method whose information is being updated + * @param atypeFactory AnnotatedTypeFactory of the associated checker + * @param val abstract value of the method call */ public void updateForMethodCall( MethodInvocationNode methodInvocationNode, AnnotatedTypeFactory atypeFactory, V val) { From 2abdc8ceac0a2d0dca761f9a91e85a14ed1c9c7b Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Thu, 8 Apr 2021 15:39:57 -0700 Subject: [PATCH 052/113] test case for empty @SideEffectsOnly --- .../framework/flow/CFAbstractStore.java | 3 ++- .../sideeffectsonly/EmptySideEffectsOnly.java | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 framework/tests/sideeffectsonly/EmptySideEffectsOnly.java diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index 7a2aaf3c302..256943cb4df 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -216,7 +216,8 @@ public void updateForMethodCall( ExecutableElement method = methodInvocationNode.getTarget().getMethod(); // List of expressions that this method side-effects (specified as arguments/elements of - // @SideEffectsOnly). If the list is empty, then there is no @SideEffectsOnly annotation. + // @SideEffectsOnly). If the list is empty, then either there is no @SideEffectsOnly annotation + // or the @SideEffectsOnly is written without any annotation argument. List sideEffectsOnlyExpressions = new ArrayList<>(); AnnotationMirror sefOnlyAnnotation = atypeFactory.getDeclAnnotation(method, SideEffectsOnly.class); diff --git a/framework/tests/sideeffectsonly/EmptySideEffectsOnly.java b/framework/tests/sideeffectsonly/EmptySideEffectsOnly.java new file mode 100644 index 00000000000..eb9de2554a7 --- /dev/null +++ b/framework/tests/sideeffectsonly/EmptySideEffectsOnly.java @@ -0,0 +1,24 @@ +package sideeffectsonly; + +import org.checkerframework.dataflow.qual.SideEffectsOnly; +import org.checkerframework.framework.qual.EnsuresQualifier; +import org.checkerframework.framework.testchecker.sideeffectsonly.qual.SideEffectsOnlyToyBottom; + +public class EmptySideEffectsOnly { + void test(Object x) { + method(x); + method1(x); + // :: error: argument.type.incompatible + method2(x); + } + + @EnsuresQualifier(expression = "#1", qualifier = SideEffectsOnlyToyBottom.class) + // :: error: contracts.postcondition.not.satisfied + void method(Object x) {} + + @SideEffectsOnly({}) + void method1(@SideEffectsOnlyToyBottom Object x) {} + + @SideEffectsOnly({}) + void method2(@SideEffectsOnlyToyBottom Object x) {} +} From a8cad8cc809b2ea7d8f3cd02cc9455c3df108351 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Fri, 9 Apr 2021 08:37:19 -0700 Subject: [PATCH 053/113] temp commit: experimenting --- .../util/SideEffectsOnlyAnnoChecker.java | 63 +++++++++++++++++++ .../common/basetype/BaseTypeVisitor.java | 18 ++++++ .../test/junit/SideEffectsOnlyTest.java | 3 +- .../IncorrectSideEffectsOnly.java | 11 ++++ 4 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 dataflow/src/main/java/org/checkerframework/dataflow/util/SideEffectsOnlyAnnoChecker.java create mode 100644 framework/tests/sideeffectsonly/IncorrectSideEffectsOnly.java diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/SideEffectsOnlyAnnoChecker.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/SideEffectsOnlyAnnoChecker.java new file mode 100644 index 00000000000..052550139af --- /dev/null +++ b/dataflow/src/main/java/org/checkerframework/dataflow/util/SideEffectsOnlyAnnoChecker.java @@ -0,0 +1,63 @@ +package main.java.org.checkerframework.dataflow.util; + +import com.sun.source.tree.AssignmentTree; +import com.sun.source.tree.CatchTree; +import com.sun.source.tree.CompoundAssignmentTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.NewClassTree; +import com.sun.source.tree.UnaryTree; +import com.sun.source.util.TreePath; +import com.sun.source.util.TreePathScanner; +import org.checkerframework.javacutil.AnnotationProvider; + +public class SideEffectsOnlyAnnoChecker { + public static SideEffectsOnlyResult checkSideEffectsOnly( + TreePath statement, AnnotationProvider annoProvider) { + SideEffectsOnlyCheckerHelper helper = new SideEffectsOnlyCheckerHelper(annoProvider); + helper.scan(statement, null); + return helper.sideEffectsOnlyResult; + } + + public static class SideEffectsOnlyResult {} + + protected static class SideEffectsOnlyCheckerHelper extends TreePathScanner { + + SideEffectsOnlyResult sideEffectsOnlyResult = new SideEffectsOnlyResult(); + + protected final AnnotationProvider annoProvider; + + public SideEffectsOnlyCheckerHelper(AnnotationProvider annoProvider) { + this.annoProvider = annoProvider; + } + + @Override + public Void visitCatch(CatchTree node, Void aVoid) { + return super.visitCatch(node, aVoid); + } + + @Override + public Void visitMethodInvocation(MethodInvocationTree node, Void aVoid) { + return super.visitMethodInvocation(node, aVoid); + } + + @Override + public Void visitNewClass(NewClassTree node, Void aVoid) { + return super.visitNewClass(node, aVoid); + } + + @Override + public Void visitAssignment(AssignmentTree node, Void aVoid) { + return super.visitAssignment(node, aVoid); + } + + @Override + public Void visitUnary(UnaryTree node, Void aVoid) { + return super.visitUnary(node, aVoid); + } + + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree node, Void aVoid) { + return super.visitCompoundAssignment(node, aVoid); + } + } +} diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index b40e74934ad..4dd1f69188f 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -77,6 +77,7 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; import javax.tools.Diagnostic.Kind; +import main.java.org.checkerframework.dataflow.util.SideEffectsOnlyAnnoChecker; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.nullness.qual.NonNull; @@ -872,6 +873,7 @@ public Void visitMethod(MethodTree node, Void p) { } checkPurity(node); + checkSideEffectsOnly(node); // Passing the whole method/constructor validates the return type validateTypeOf(node); @@ -1007,6 +1009,22 @@ protected void checkPurity(MethodTree node) { } } + protected void checkSideEffectsOnly(MethodTree node) { + if (!checker.hasOption("checkSideEffectsOnlyAnnotation")) { + return; + } + + TreePath body = atypeFactory.getPath(node.getBody()); + if (body == null) { + return; + } else { + SideEffectsOnlyAnnoChecker.checkSideEffectsOnly(body, atypeFactory); + } + // if (!r.isPure(kinds)) { + // reportPurityErrors(r, node, kinds); + // } + } + /** * Issue a warning if the result type of the constructor is not top. If it is a supertype of the * class, then a type.invalid.conflicting.annos error will also be issued by {@link diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/SideEffectsOnlyTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/SideEffectsOnlyTest.java index 64433096f8e..10649262497 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/SideEffectsOnlyTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/SideEffectsOnlyTest.java @@ -13,7 +13,8 @@ public SideEffectsOnlyTest(List testFiles) { testFiles, org.checkerframework.framework.testchecker.sideeffectsonly.SideEffectsOnlyToyChecker.class, "sideeffectsonly", - "-Anomsgtext"); + "-Anomsgtext", + "-AcheckSideEffectsOnlyAnnotation"); } @Parameters diff --git a/framework/tests/sideeffectsonly/IncorrectSideEffectsOnly.java b/framework/tests/sideeffectsonly/IncorrectSideEffectsOnly.java new file mode 100644 index 00000000000..0c4bbde99b3 --- /dev/null +++ b/framework/tests/sideeffectsonly/IncorrectSideEffectsOnly.java @@ -0,0 +1,11 @@ +package sideeffectsonly; + +import java.util.Collection; +import org.checkerframework.dataflow.qual.SideEffectsOnly; + +public class IncorrectSideEffectsOnly { + @SideEffectsOnly({"#2"}) + void test(Collection cl, Collection cl2) { + cl.add(9); + } +} From 1cc560e9f6ecb6cdbc52819abcd12c864a9648ff Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Mon, 12 Apr 2021 15:30:34 -0700 Subject: [PATCH 054/113] SideEffectsOnly checker: reports error on some assignments --- .../util/SideEffectsOnlyAnnoChecker.java | 33 +++++++++++-- .../common/basetype/BaseTypeVisitor.java | 49 +++++++++++++++---- .../common/basetype/messages.properties | 2 + .../framework/source/SourceChecker.java | 1 + .../IncorrectSideEffectsOnly.java | 8 +++ 5 files changed, 79 insertions(+), 14 deletions(-) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/SideEffectsOnlyAnnoChecker.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/SideEffectsOnlyAnnoChecker.java index 052550139af..b97306016c8 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/util/SideEffectsOnlyAnnoChecker.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/util/SideEffectsOnlyAnnoChecker.java @@ -5,29 +5,50 @@ import com.sun.source.tree.CompoundAssignmentTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.NewClassTree; +import com.sun.source.tree.Tree; import com.sun.source.tree.UnaryTree; import com.sun.source.util.TreePath; import com.sun.source.util.TreePathScanner; +import java.util.ArrayList; +import java.util.List; +import org.checkerframework.dataflow.expression.JavaExpression; import org.checkerframework.javacutil.AnnotationProvider; +import org.checkerframework.javacutil.Pair; public class SideEffectsOnlyAnnoChecker { public static SideEffectsOnlyResult checkSideEffectsOnly( - TreePath statement, AnnotationProvider annoProvider) { - SideEffectsOnlyCheckerHelper helper = new SideEffectsOnlyCheckerHelper(annoProvider); + TreePath statement, + AnnotationProvider annoProvider, + List sideEffectsOnlyExpressions) { + SideEffectsOnlyCheckerHelper helper = + new SideEffectsOnlyCheckerHelper(annoProvider, sideEffectsOnlyExpressions); helper.scan(statement, null); return helper.sideEffectsOnlyResult; } - public static class SideEffectsOnlyResult {} + public static class SideEffectsOnlyResult { + protected final List> seOnlyIncorrectExprs = new ArrayList<>(1); + + public void addNotSEOnlyExpr(Tree t, JavaExpression javaExpr) { + seOnlyIncorrectExprs.add(Pair.of(t, javaExpr)); + } + + public List> getSeOnlyResult() { + return seOnlyIncorrectExprs; + } + } protected static class SideEffectsOnlyCheckerHelper extends TreePathScanner { SideEffectsOnlyResult sideEffectsOnlyResult = new SideEffectsOnlyResult(); + List sideEffectsOnlyExpressions; protected final AnnotationProvider annoProvider; - public SideEffectsOnlyCheckerHelper(AnnotationProvider annoProvider) { + public SideEffectsOnlyCheckerHelper( + AnnotationProvider annoProvider, List sideEffectsOnlyExpressions) { this.annoProvider = annoProvider; + this.sideEffectsOnlyExpressions = sideEffectsOnlyExpressions; } @Override @@ -47,6 +68,10 @@ public Void visitNewClass(NewClassTree node, Void aVoid) { @Override public Void visitAssignment(AssignmentTree node, Void aVoid) { + JavaExpression javaExpr = JavaExpression.fromTree(node.getVariable()); + if (!sideEffectsOnlyExpressions.contains(javaExpr)) { + sideEffectsOnlyResult.addNotSEOnlyExpr(node, javaExpr); + } return super.visitAssignment(node, aVoid); } diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 4dd1f69188f..e8cb79c2f33 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -91,6 +91,7 @@ import org.checkerframework.dataflow.expression.JavaExpressionScanner; import org.checkerframework.dataflow.expression.LocalVariable; import org.checkerframework.dataflow.qual.Pure; +import org.checkerframework.dataflow.qual.SideEffectsOnly; import org.checkerframework.dataflow.util.PurityChecker; import org.checkerframework.dataflow.util.PurityChecker.PurityResult; import org.checkerframework.dataflow.util.PurityUtils; @@ -122,16 +123,11 @@ import org.checkerframework.framework.type.VisitorState; import org.checkerframework.framework.type.poly.QualifierPolymorphism; import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeScanner; -import org.checkerframework.framework.util.AnnotatedTypes; -import org.checkerframework.framework.util.Contract; +import org.checkerframework.framework.util.*; import org.checkerframework.framework.util.Contract.ConditionalPostcondition; import org.checkerframework.framework.util.Contract.Postcondition; import org.checkerframework.framework.util.Contract.Precondition; -import org.checkerframework.framework.util.ContractsFromMethod; -import org.checkerframework.framework.util.FieldInvariants; import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException; -import org.checkerframework.framework.util.JavaParserUtil; -import org.checkerframework.framework.util.StringToJavaExpression; import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.BugInCF; @@ -221,6 +217,8 @@ public class BaseTypeVisitor sideEffectsOnlyExpressionStrings = + AnnotationUtils.getElementValueArray( + sefOnlyAnnotation, sideEffectsOnlyValueElement, String.class); + List sideEffectsOnlyExpressions = new ArrayList<>(); + for (String st : sideEffectsOnlyExpressionStrings) { + try { + JavaExpression exprJe = StringToJavaExpression.atMethodBody(st, node, checker); + sideEffectsOnlyExpressions.add(exprJe); + } catch (JavaExpressionParseUtil.JavaExpressionParseException ex) { + checker.report(st, ex.getDiagMessage()); + return; + } + } + + if (sideEffectsOnlyExpressions.isEmpty()) { + return; + } + + SideEffectsOnlyAnnoChecker.SideEffectsOnlyResult sefOnlyResult = + SideEffectsOnlyAnnoChecker.checkSideEffectsOnly( + body, atypeFactory, sideEffectsOnlyExpressions); + + List> seOnlyIncorrectExprs = sefOnlyResult.getSeOnlyResult(); + if (!seOnlyIncorrectExprs.isEmpty()) { + for (Pair s : seOnlyIncorrectExprs) + checker.reportError(s.first, "incorrect.sideeffectsonly", s.second.toString()); + } } - // if (!r.isPure(kinds)) { - // reportPurityErrors(r, node, kinds); - // } } /** diff --git a/framework/src/main/java/org/checkerframework/common/basetype/messages.properties b/framework/src/main/java/org/checkerframework/common/basetype/messages.properties index 02ffe0e1ae1..38dfb4d3e4c 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/messages.properties +++ b/framework/src/main/java/org/checkerframework/common/basetype/messages.properties @@ -76,6 +76,8 @@ purity.more.deterministic=the method %s could be declared as @Deterministic purity.more.pure=the method %s could be declared as @Pure purity.more.sideeffectfree=the method %s could be declared as @SideEffectFree +incorrect.sideeffectsonly=the method side-effects %s + flowexpr.parse.index.too.big=the method does not have a parameter %s flowexpr.parse.error=cannot parse the expression %s flowexpr.parse.error.postcondition=error parsing the postcondition expression for %s%ncannot parse the expression %s diff --git a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java index 392aae94bb5..50092125373 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -144,6 +144,7 @@ // Re-enable it after making the analysis more precise. // org.checkerframework.common.basetype.BaseTypeVisitor.visitMethod(MethodTree, Void) "checkPurityAnnotations", + "checkSideEffectsOnlyAnnotation", // TODO: Temporary option to make array subtyping invariant, // which will be the new default soon. diff --git a/framework/tests/sideeffectsonly/IncorrectSideEffectsOnly.java b/framework/tests/sideeffectsonly/IncorrectSideEffectsOnly.java index 0c4bbde99b3..492c75b22b8 100644 --- a/framework/tests/sideeffectsonly/IncorrectSideEffectsOnly.java +++ b/framework/tests/sideeffectsonly/IncorrectSideEffectsOnly.java @@ -4,8 +4,16 @@ import org.checkerframework.dataflow.qual.SideEffectsOnly; public class IncorrectSideEffectsOnly { + Collection coll; + @SideEffectsOnly({"#2"}) void test(Collection cl, Collection cl2) { cl.add(9); } + + @SideEffectsOnly({"#2"}) + void test1(Collection cl, Collection cl2) { + // :: error: incorrect.sideeffectsonly + coll = cl; + } } From 8e62d1e8c9e9bee7059a82647d8c0a2c2ab4da89 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Thu, 20 May 2021 12:25:03 -0400 Subject: [PATCH 055/113] imports refactoring --- .../checkerframework/common/basetype/BaseTypeVisitor.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 7f819eb06e0..a9950671df9 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -123,11 +123,17 @@ import org.checkerframework.framework.type.VisitorState; import org.checkerframework.framework.type.poly.QualifierPolymorphism; import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeScanner; -import org.checkerframework.framework.util.*; +import org.checkerframework.framework.util.AnnotatedTypes; +import org.checkerframework.framework.util.Contract; import org.checkerframework.framework.util.Contract.ConditionalPostcondition; import org.checkerframework.framework.util.Contract.Postcondition; import org.checkerframework.framework.util.Contract.Precondition; +import org.checkerframework.framework.util.ContractsFromMethod; +import org.checkerframework.framework.util.FieldInvariants; +import org.checkerframework.framework.util.JavaExpressionParseUtil; import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException; +import org.checkerframework.framework.util.JavaParserUtil; +import org.checkerframework.framework.util.StringToJavaExpression; import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.BugInCF; From 0288bdf607059790f545fbf387e85a0a83a9d170 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Sun, 11 Jul 2021 11:27:08 -0700 Subject: [PATCH 056/113] handling method invocations --- .../util/SideEffectsOnlyAnnoChecker.java | 28 +++++++++++++++++++ .../common/basetype/BaseTypeVisitor.java | 7 +++++ 2 files changed, 35 insertions(+) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/SideEffectsOnlyAnnoChecker.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/SideEffectsOnlyAnnoChecker.java index b97306016c8..1e923d416a5 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/util/SideEffectsOnlyAnnoChecker.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/util/SideEffectsOnlyAnnoChecker.java @@ -11,9 +11,16 @@ import com.sun.source.util.TreePathScanner; import java.util.ArrayList; import java.util.List; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; import org.checkerframework.dataflow.expression.JavaExpression; +import org.checkerframework.dataflow.qual.Pure; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.dataflow.qual.SideEffectsOnly; import org.checkerframework.javacutil.AnnotationProvider; import org.checkerframework.javacutil.Pair; +import org.checkerframework.javacutil.TreeUtils; public class SideEffectsOnlyAnnoChecker { public static SideEffectsOnlyResult checkSideEffectsOnly( @@ -58,6 +65,27 @@ public Void visitCatch(CatchTree node, Void aVoid) { @Override public Void visitMethodInvocation(MethodInvocationTree node, Void aVoid) { + Element treeElem = TreeUtils.elementFromTree(node); + AnnotationMirror pureAnno = annoProvider.getDeclAnnotation(treeElem, Pure.class); + AnnotationMirror sideEffectFreeAnno = + annoProvider.getDeclAnnotation(treeElem, SideEffectFree.class); + if (pureAnno != null || sideEffectFreeAnno != null) { + return super.visitMethodInvocation(node, aVoid); + } + + AnnotationMirror sideEffectsOnlyAnno = + annoProvider.getDeclAnnotation(treeElem, SideEffectsOnly.class); + if (sideEffectsOnlyAnno != null) { + JavaExpression receiverExpr = JavaExpression.getReceiver(node); + sideEffectsOnlyResult.addNotSEOnlyExpr(node, receiverExpr); + List paramsAsLocals = + JavaExpression.getParametersAsLocalVariables((ExecutableElement) treeElem); + for (JavaExpression expr : paramsAsLocals) { + sideEffectsOnlyResult.addNotSEOnlyExpr(node, expr); + } + } else { + + } return super.visitMethodInvocation(node, aVoid); } diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 5eac4935919..139776246f6 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -1056,6 +1056,13 @@ protected void checkPurity(MethodTree node) { } } + /** + * If the method {@param node} is annotated with {@link SideEffectsOnly}, checks that the method + * side-effects a subset of the expressions specified as annotation arguments/elements to {@link + * SideEffectsOnly}. + * + * @param node the method tree to check + */ protected void checkSideEffectsOnly(MethodTree node) { if (!checker.hasOption("checkSideEffectsOnlyAnnotation")) { return; From e09d7ea1b754c39f5ff45355c192c21cec970ea0 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Tue, 13 Jul 2021 19:37:46 -0700 Subject: [PATCH 057/113] SEOnly checking at method invocations --- .../util/SideEffectsOnlyAnnoChecker.java | 42 +++++++++++++++++-- .../common/basetype/BaseTypeVisitor.java | 6 ++- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/SideEffectsOnlyAnnoChecker.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/SideEffectsOnlyAnnoChecker.java index 1e923d416a5..1267af43704 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/util/SideEffectsOnlyAnnoChecker.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/util/SideEffectsOnlyAnnoChecker.java @@ -11,14 +11,19 @@ import com.sun.source.util.TreePathScanner; import java.util.ArrayList; import java.util.List; +import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; +import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.dataflow.expression.JavaExpression; import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.dataflow.qual.SideEffectsOnly; +import org.checkerframework.framework.util.JavaExpressionParseUtil; +import org.checkerframework.framework.util.StringToJavaExpression; import org.checkerframework.javacutil.AnnotationProvider; +import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.Pair; import org.checkerframework.javacutil.TreeUtils; @@ -26,9 +31,12 @@ public class SideEffectsOnlyAnnoChecker { public static SideEffectsOnlyResult checkSideEffectsOnly( TreePath statement, AnnotationProvider annoProvider, - List sideEffectsOnlyExpressions) { + List sideEffectsOnlyExpressions, + ProcessingEnvironment processingEnv, + BaseTypeChecker checker) { SideEffectsOnlyCheckerHelper helper = - new SideEffectsOnlyCheckerHelper(annoProvider, sideEffectsOnlyExpressions); + new SideEffectsOnlyCheckerHelper( + annoProvider, sideEffectsOnlyExpressions, processingEnv, checker); helper.scan(statement, null); return helper.sideEffectsOnlyResult; } @@ -51,11 +59,18 @@ protected static class SideEffectsOnlyCheckerHelper extends TreePathScanner sideEffectsOnlyExpressions; protected final AnnotationProvider annoProvider; + ProcessingEnvironment processingEnv; + BaseTypeChecker checker; public SideEffectsOnlyCheckerHelper( - AnnotationProvider annoProvider, List sideEffectsOnlyExpressions) { + AnnotationProvider annoProvider, + List sideEffectsOnlyExpressions, + ProcessingEnvironment processingEnv, + BaseTypeChecker checker) { this.annoProvider = annoProvider; this.sideEffectsOnlyExpressions = sideEffectsOnlyExpressions; + this.processingEnv = processingEnv; + this.checker = checker; } @Override @@ -75,7 +90,7 @@ public Void visitMethodInvocation(MethodInvocationTree node, Void aVoid) { AnnotationMirror sideEffectsOnlyAnno = annoProvider.getDeclAnnotation(treeElem, SideEffectsOnly.class); - if (sideEffectsOnlyAnno != null) { + if (sideEffectsOnlyAnno == null) { JavaExpression receiverExpr = JavaExpression.getReceiver(node); sideEffectsOnlyResult.addNotSEOnlyExpr(node, receiverExpr); List paramsAsLocals = @@ -84,7 +99,26 @@ public Void visitMethodInvocation(MethodInvocationTree node, Void aVoid) { sideEffectsOnlyResult.addNotSEOnlyExpr(node, expr); } } else { + ExecutableElement sideEffectsOnlyValueElement = + TreeUtils.getMethod(SideEffectsOnly.class, "value", 0, processingEnv); + List sideEffectsOnlyExpressionStrings = + AnnotationUtils.getElementValueArray( + sideEffectsOnlyAnno, sideEffectsOnlyValueElement, String.class); + List sideEffectsOnlyExprInv = new ArrayList<>(); + for (String st : sideEffectsOnlyExpressionStrings) { + try { + JavaExpression exprJe = StringToJavaExpression.atMethodInvocation(st, node, checker); + sideEffectsOnlyExprInv.add(exprJe); + } catch (JavaExpressionParseUtil.JavaExpressionParseException ex) { + checker.report(st, ex.getDiagMessage()); + } + } + for (JavaExpression expr : sideEffectsOnlyExprInv) { + if (!sideEffectsOnlyExpressions.contains(expr)) { + sideEffectsOnlyResult.addNotSEOnlyExpr(node, expr); + } + } } return super.visitMethodInvocation(node, aVoid); } diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 139776246f6..db2d179bb16 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -1098,7 +1098,11 @@ protected void checkSideEffectsOnly(MethodTree node) { SideEffectsOnlyAnnoChecker.SideEffectsOnlyResult sefOnlyResult = SideEffectsOnlyAnnoChecker.checkSideEffectsOnly( - body, atypeFactory, sideEffectsOnlyExpressions); + body, + atypeFactory, + sideEffectsOnlyExpressions, + atypeFactory.getProcessingEnv(), + checker); List> seOnlyIncorrectExprs = sefOnlyResult.getSeOnlyResult(); if (!seOnlyIncorrectExprs.isEmpty()) { From f91ac198ce12e920c0a775b75a3d69e7f2373d73 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Mon, 9 Aug 2021 12:36:11 -0700 Subject: [PATCH 058/113] fix package name --- .../dataflow/util/SideEffectsOnlyAnnoChecker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/SideEffectsOnlyAnnoChecker.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/SideEffectsOnlyAnnoChecker.java index 1267af43704..91bb924610a 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/util/SideEffectsOnlyAnnoChecker.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/util/SideEffectsOnlyAnnoChecker.java @@ -1,4 +1,4 @@ -package main.java.org.checkerframework.dataflow.util; +package org.checkerframework.dataflow.util; import com.sun.source.tree.AssignmentTree; import com.sun.source.tree.CatchTree; From fc91585899655a76c420a9d6d7819390d85aa815 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Wed, 11 Aug 2021 19:29:21 -0700 Subject: [PATCH 059/113] test case: Check SideEffectsOnly --- .../sideeffectsonly/CheckSideEffectsOnly.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 framework/tests/sideeffectsonly/CheckSideEffectsOnly.java diff --git a/framework/tests/sideeffectsonly/CheckSideEffectsOnly.java b/framework/tests/sideeffectsonly/CheckSideEffectsOnly.java new file mode 100644 index 00000000000..63b4b43c166 --- /dev/null +++ b/framework/tests/sideeffectsonly/CheckSideEffectsOnly.java @@ -0,0 +1,14 @@ +package sideeffectsonly; + +import java.util.Collection; +import org.checkerframework.dataflow.qual.SideEffectsOnly; + +public class CheckSideEffectsOnly { + + // :: error: incorrect.sideeffectsonly + @SideEffectsOnly({"#2"}) + void test(Collection cl, Collection cl2) { + cl.add(9); + cl2.add(10); + } +} From 4bcbb3b82aab38a2f74f1af996bfe99011ad49b9 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Sat, 14 Aug 2021 22:06:33 -0700 Subject: [PATCH 060/113] comments --- .../util/SideEffectsOnlyAnnoChecker.java | 23 +++++++++++++++++-- .../common/basetype/BaseTypeVisitor.java | 2 +- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/SideEffectsOnlyAnnoChecker.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/SideEffectsOnlyAnnoChecker.java index 91bb924610a..0625c73524f 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/util/SideEffectsOnlyAnnoChecker.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/util/SideEffectsOnlyAnnoChecker.java @@ -27,6 +27,10 @@ import org.checkerframework.javacutil.Pair; import org.checkerframework.javacutil.TreeUtils; +/** + * For methods annotated with {@link SideEffectsOnly}, computes expressions that are side-effected + * but not permitted by the annotation. + */ public class SideEffectsOnlyAnnoChecker { public static SideEffectsOnlyResult checkSideEffectsOnly( TreePath statement, @@ -42,6 +46,10 @@ public static SideEffectsOnlyResult checkSideEffectsOnly( } public static class SideEffectsOnlyResult { + /** + * List of expressions a method side-effects that are not specified in the list of arguments to + * {@link SideEffectsOnly}. + */ protected final List> seOnlyIncorrectExprs = new ArrayList<>(1); public void addNotSEOnlyExpr(Tree t, JavaExpression javaExpr) { @@ -84,21 +92,32 @@ public Void visitMethodInvocation(MethodInvocationTree node, Void aVoid) { AnnotationMirror pureAnno = annoProvider.getDeclAnnotation(treeElem, Pure.class); AnnotationMirror sideEffectFreeAnno = annoProvider.getDeclAnnotation(treeElem, SideEffectFree.class); + // If the invoked method is annotated as @Pure or @SideEffectFree, nothing to do. if (pureAnno != null || sideEffectFreeAnno != null) { return super.visitMethodInvocation(node, aVoid); } AnnotationMirror sideEffectsOnlyAnno = annoProvider.getDeclAnnotation(treeElem, SideEffectsOnly.class); + // If the invoked method is not annotated with @SideEffectsOnly, + // add those arguments to seOnlyIncorrectExprs + // that are not present in sideEffectsOnlyExpressions. if (sideEffectsOnlyAnno == null) { JavaExpression receiverExpr = JavaExpression.getReceiver(node); - sideEffectsOnlyResult.addNotSEOnlyExpr(node, receiverExpr); + if (!sideEffectsOnlyExpressions.contains(receiverExpr)) { + sideEffectsOnlyResult.addNotSEOnlyExpr(node, receiverExpr); + } List paramsAsLocals = JavaExpression.getParametersAsLocalVariables((ExecutableElement) treeElem); for (JavaExpression expr : paramsAsLocals) { - sideEffectsOnlyResult.addNotSEOnlyExpr(node, expr); + if (!sideEffectsOnlyExpressions.contains(expr)) { + sideEffectsOnlyResult.addNotSEOnlyExpr(node, expr); + } } } else { + // If the invoked method is annotated with @SideEffectsOnly, + // add annotation values to seOnlyIncorrectExprs + // that are not present in sideEffectsOnlyExpressions. ExecutableElement sideEffectsOnlyValueElement = TreeUtils.getMethod(SideEffectsOnly.class, "value", 0, processingEnv); List sideEffectsOnlyExpressionStrings = diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 281a1c799e7..ef96ad4d911 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -77,7 +77,6 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; import javax.tools.Diagnostic.Kind; -import main.java.org.checkerframework.dataflow.util.SideEffectsOnlyAnnoChecker; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.nullness.qual.NonNull; @@ -98,6 +97,7 @@ import org.checkerframework.dataflow.util.PurityChecker; import org.checkerframework.dataflow.util.PurityChecker.PurityResult; import org.checkerframework.dataflow.util.PurityUtils; +import org.checkerframework.dataflow.util.SideEffectsOnlyAnnoChecker; import org.checkerframework.framework.ajava.AnnotationEqualityVisitor; import org.checkerframework.framework.ajava.ExpectedTreesVisitor; import org.checkerframework.framework.ajava.InsertAjavaAnnotations; From e6a61f56ce4d839e5f3dd8763dcf6d7b055b56fe Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Mon, 23 Aug 2021 16:35:45 -0700 Subject: [PATCH 061/113] builds --- .../util/SideEffectsOnlyAnnoChecker.java | 169 ------------------ .../common/basetype/BaseTypeVisitor.java | 3 +- 2 files changed, 1 insertion(+), 171 deletions(-) delete mode 100644 dataflow/src/main/java/org/checkerframework/dataflow/util/SideEffectsOnlyAnnoChecker.java diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/SideEffectsOnlyAnnoChecker.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/SideEffectsOnlyAnnoChecker.java deleted file mode 100644 index 0625c73524f..00000000000 --- a/dataflow/src/main/java/org/checkerframework/dataflow/util/SideEffectsOnlyAnnoChecker.java +++ /dev/null @@ -1,169 +0,0 @@ -package org.checkerframework.dataflow.util; - -import com.sun.source.tree.AssignmentTree; -import com.sun.source.tree.CatchTree; -import com.sun.source.tree.CompoundAssignmentTree; -import com.sun.source.tree.MethodInvocationTree; -import com.sun.source.tree.NewClassTree; -import com.sun.source.tree.Tree; -import com.sun.source.tree.UnaryTree; -import com.sun.source.util.TreePath; -import com.sun.source.util.TreePathScanner; -import java.util.ArrayList; -import java.util.List; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import org.checkerframework.common.basetype.BaseTypeChecker; -import org.checkerframework.dataflow.expression.JavaExpression; -import org.checkerframework.dataflow.qual.Pure; -import org.checkerframework.dataflow.qual.SideEffectFree; -import org.checkerframework.dataflow.qual.SideEffectsOnly; -import org.checkerframework.framework.util.JavaExpressionParseUtil; -import org.checkerframework.framework.util.StringToJavaExpression; -import org.checkerframework.javacutil.AnnotationProvider; -import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.Pair; -import org.checkerframework.javacutil.TreeUtils; - -/** - * For methods annotated with {@link SideEffectsOnly}, computes expressions that are side-effected - * but not permitted by the annotation. - */ -public class SideEffectsOnlyAnnoChecker { - public static SideEffectsOnlyResult checkSideEffectsOnly( - TreePath statement, - AnnotationProvider annoProvider, - List sideEffectsOnlyExpressions, - ProcessingEnvironment processingEnv, - BaseTypeChecker checker) { - SideEffectsOnlyCheckerHelper helper = - new SideEffectsOnlyCheckerHelper( - annoProvider, sideEffectsOnlyExpressions, processingEnv, checker); - helper.scan(statement, null); - return helper.sideEffectsOnlyResult; - } - - public static class SideEffectsOnlyResult { - /** - * List of expressions a method side-effects that are not specified in the list of arguments to - * {@link SideEffectsOnly}. - */ - protected final List> seOnlyIncorrectExprs = new ArrayList<>(1); - - public void addNotSEOnlyExpr(Tree t, JavaExpression javaExpr) { - seOnlyIncorrectExprs.add(Pair.of(t, javaExpr)); - } - - public List> getSeOnlyResult() { - return seOnlyIncorrectExprs; - } - } - - protected static class SideEffectsOnlyCheckerHelper extends TreePathScanner { - - SideEffectsOnlyResult sideEffectsOnlyResult = new SideEffectsOnlyResult(); - List sideEffectsOnlyExpressions; - - protected final AnnotationProvider annoProvider; - ProcessingEnvironment processingEnv; - BaseTypeChecker checker; - - public SideEffectsOnlyCheckerHelper( - AnnotationProvider annoProvider, - List sideEffectsOnlyExpressions, - ProcessingEnvironment processingEnv, - BaseTypeChecker checker) { - this.annoProvider = annoProvider; - this.sideEffectsOnlyExpressions = sideEffectsOnlyExpressions; - this.processingEnv = processingEnv; - this.checker = checker; - } - - @Override - public Void visitCatch(CatchTree node, Void aVoid) { - return super.visitCatch(node, aVoid); - } - - @Override - public Void visitMethodInvocation(MethodInvocationTree node, Void aVoid) { - Element treeElem = TreeUtils.elementFromTree(node); - AnnotationMirror pureAnno = annoProvider.getDeclAnnotation(treeElem, Pure.class); - AnnotationMirror sideEffectFreeAnno = - annoProvider.getDeclAnnotation(treeElem, SideEffectFree.class); - // If the invoked method is annotated as @Pure or @SideEffectFree, nothing to do. - if (pureAnno != null || sideEffectFreeAnno != null) { - return super.visitMethodInvocation(node, aVoid); - } - - AnnotationMirror sideEffectsOnlyAnno = - annoProvider.getDeclAnnotation(treeElem, SideEffectsOnly.class); - // If the invoked method is not annotated with @SideEffectsOnly, - // add those arguments to seOnlyIncorrectExprs - // that are not present in sideEffectsOnlyExpressions. - if (sideEffectsOnlyAnno == null) { - JavaExpression receiverExpr = JavaExpression.getReceiver(node); - if (!sideEffectsOnlyExpressions.contains(receiverExpr)) { - sideEffectsOnlyResult.addNotSEOnlyExpr(node, receiverExpr); - } - List paramsAsLocals = - JavaExpression.getParametersAsLocalVariables((ExecutableElement) treeElem); - for (JavaExpression expr : paramsAsLocals) { - if (!sideEffectsOnlyExpressions.contains(expr)) { - sideEffectsOnlyResult.addNotSEOnlyExpr(node, expr); - } - } - } else { - // If the invoked method is annotated with @SideEffectsOnly, - // add annotation values to seOnlyIncorrectExprs - // that are not present in sideEffectsOnlyExpressions. - ExecutableElement sideEffectsOnlyValueElement = - TreeUtils.getMethod(SideEffectsOnly.class, "value", 0, processingEnv); - List sideEffectsOnlyExpressionStrings = - AnnotationUtils.getElementValueArray( - sideEffectsOnlyAnno, sideEffectsOnlyValueElement, String.class); - List sideEffectsOnlyExprInv = new ArrayList<>(); - for (String st : sideEffectsOnlyExpressionStrings) { - try { - JavaExpression exprJe = StringToJavaExpression.atMethodInvocation(st, node, checker); - sideEffectsOnlyExprInv.add(exprJe); - } catch (JavaExpressionParseUtil.JavaExpressionParseException ex) { - checker.report(st, ex.getDiagMessage()); - } - } - - for (JavaExpression expr : sideEffectsOnlyExprInv) { - if (!sideEffectsOnlyExpressions.contains(expr)) { - sideEffectsOnlyResult.addNotSEOnlyExpr(node, expr); - } - } - } - return super.visitMethodInvocation(node, aVoid); - } - - @Override - public Void visitNewClass(NewClassTree node, Void aVoid) { - return super.visitNewClass(node, aVoid); - } - - @Override - public Void visitAssignment(AssignmentTree node, Void aVoid) { - JavaExpression javaExpr = JavaExpression.fromTree(node.getVariable()); - if (!sideEffectsOnlyExpressions.contains(javaExpr)) { - sideEffectsOnlyResult.addNotSEOnlyExpr(node, javaExpr); - } - return super.visitAssignment(node, aVoid); - } - - @Override - public Void visitUnary(UnaryTree node, Void aVoid) { - return super.visitUnary(node, aVoid); - } - - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree node, Void aVoid) { - return super.visitCompoundAssignment(node, aVoid); - } - } -} diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index ef96ad4d911..e30dcb0b29a 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -97,7 +97,6 @@ import org.checkerframework.dataflow.util.PurityChecker; import org.checkerframework.dataflow.util.PurityChecker.PurityResult; import org.checkerframework.dataflow.util.PurityUtils; -import org.checkerframework.dataflow.util.SideEffectsOnlyAnnoChecker; import org.checkerframework.framework.ajava.AnnotationEqualityVisitor; import org.checkerframework.framework.ajava.ExpectedTreesVisitor; import org.checkerframework.framework.ajava.InsertAjavaAnnotations; @@ -1072,7 +1071,7 @@ protected void checkPurity(MethodTree node) { } /** - * If the method {@param node} is annotated with {@link SideEffectsOnly}, checks that the method + * If the method {@code node} is annotated with {@link SideEffectsOnly}, checks that the method * side-effects a subset of the expressions specified as annotation arguments/elements to {@link * SideEffectsOnly}. * From 92205967534c282a8b14a9e13d0588fe40ba1bd5 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Thu, 14 Oct 2021 13:15:03 -0700 Subject: [PATCH 062/113] SideEffectsOnlyAnnoChecker --- .../basetype/SideEffectsOnlyAnnoChecker.java | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java diff --git a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java new file mode 100644 index 00000000000..a2249a1023f --- /dev/null +++ b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java @@ -0,0 +1,168 @@ +package org.checkerframework.common.basetype; + +import com.sun.source.tree.AssignmentTree; +import com.sun.source.tree.CatchTree; +import com.sun.source.tree.CompoundAssignmentTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.NewClassTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.UnaryTree; +import com.sun.source.util.TreePath; +import com.sun.source.util.TreePathScanner; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import org.checkerframework.dataflow.expression.JavaExpression; +import org.checkerframework.dataflow.qual.Pure; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.dataflow.qual.SideEffectsOnly; +import org.checkerframework.framework.util.JavaExpressionParseUtil; +import org.checkerframework.framework.util.StringToJavaExpression; +import org.checkerframework.javacutil.AnnotationProvider; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.Pair; +import org.checkerframework.javacutil.TreeUtils; + +/** + * For methods annotated with {@link SideEffectsOnly}, computes expressions that are side-effected + * but not permitted by the annotation. + */ +public class SideEffectsOnlyAnnoChecker { + public static SideEffectsOnlyResult checkSideEffectsOnly( + TreePath statement, + AnnotationProvider annoProvider, + List sideEffectsOnlyExpressions, + ProcessingEnvironment processingEnv, + BaseTypeChecker checker) { + SideEffectsOnlyCheckerHelper helper = + new SideEffectsOnlyCheckerHelper( + annoProvider, sideEffectsOnlyExpressions, processingEnv, checker); + helper.scan(statement, null); + return helper.sideEffectsOnlyResult; + } + + public static class SideEffectsOnlyResult { + /** + * List of expressions a method side-effects that are not specified in the list of arguments to + * {@link SideEffectsOnly}. + */ + protected final List> seOnlyIncorrectExprs = new ArrayList<>(1); + + public void addNotSEOnlyExpr(Tree t, JavaExpression javaExpr) { + seOnlyIncorrectExprs.add(Pair.of(t, javaExpr)); + } + + public List> getSeOnlyResult() { + return seOnlyIncorrectExprs; + } + } + + protected static class SideEffectsOnlyCheckerHelper extends TreePathScanner { + + SideEffectsOnlyResult sideEffectsOnlyResult = new SideEffectsOnlyResult(); + List sideEffectsOnlyExpressions; + + protected final AnnotationProvider annoProvider; + ProcessingEnvironment processingEnv; + BaseTypeChecker checker; + + public SideEffectsOnlyCheckerHelper( + AnnotationProvider annoProvider, + List sideEffectsOnlyExpressions, + ProcessingEnvironment processingEnv, + BaseTypeChecker checker) { + this.annoProvider = annoProvider; + this.sideEffectsOnlyExpressions = sideEffectsOnlyExpressions; + this.processingEnv = processingEnv; + this.checker = checker; + } + + @Override + public Void visitCatch(CatchTree node, Void aVoid) { + return super.visitCatch(node, aVoid); + } + + @Override + public Void visitMethodInvocation(MethodInvocationTree node, Void aVoid) { + Element treeElem = TreeUtils.elementFromTree(node); + AnnotationMirror pureAnno = annoProvider.getDeclAnnotation(treeElem, Pure.class); + AnnotationMirror sideEffectFreeAnno = + annoProvider.getDeclAnnotation(treeElem, SideEffectFree.class); + // If the invoked method is annotated as @Pure or @SideEffectFree, nothing to do. + if (pureAnno != null || sideEffectFreeAnno != null) { + return super.visitMethodInvocation(node, aVoid); + } + + AnnotationMirror sideEffectsOnlyAnno = + annoProvider.getDeclAnnotation(treeElem, SideEffectsOnly.class); + // If the invoked method is not annotated with @SideEffectsOnly, + // add those arguments to seOnlyIncorrectExprs + // that are not present in sideEffectsOnlyExpressions. + if (sideEffectsOnlyAnno == null) { + JavaExpression receiverExpr = JavaExpression.getReceiver(node); + if (!sideEffectsOnlyExpressions.contains(receiverExpr)) { + sideEffectsOnlyResult.addNotSEOnlyExpr(node, receiverExpr); + } + List paramsAsLocals = + JavaExpression.getParametersAsLocalVariables((ExecutableElement) treeElem); + for (JavaExpression expr : paramsAsLocals) { + if (!sideEffectsOnlyExpressions.contains(expr)) { + sideEffectsOnlyResult.addNotSEOnlyExpr(node, expr); + } + } + } else { + // If the invoked method is annotated with @SideEffectsOnly, + // add annotation values to seOnlyIncorrectExprs + // that are not present in sideEffectsOnlyExpressions. + ExecutableElement sideEffectsOnlyValueElement = + TreeUtils.getMethod(SideEffectsOnly.class, "value", 0, processingEnv); + List sideEffectsOnlyExpressionStrings = + AnnotationUtils.getElementValueArray( + sideEffectsOnlyAnno, sideEffectsOnlyValueElement, String.class); + List sideEffectsOnlyExprInv = new ArrayList<>(); + for (String st : sideEffectsOnlyExpressionStrings) { + try { + JavaExpression exprJe = StringToJavaExpression.atMethodInvocation(st, node, checker); + sideEffectsOnlyExprInv.add(exprJe); + } catch (JavaExpressionParseUtil.JavaExpressionParseException ex) { + checker.report(st, ex.getDiagMessage()); + } + } + + for (JavaExpression expr : sideEffectsOnlyExprInv) { + if (!sideEffectsOnlyExpressions.contains(expr)) { + sideEffectsOnlyResult.addNotSEOnlyExpr(node, expr); + } + } + } + return super.visitMethodInvocation(node, aVoid); + } + + @Override + public Void visitNewClass(NewClassTree node, Void aVoid) { + return super.visitNewClass(node, aVoid); + } + + @Override + public Void visitAssignment(AssignmentTree node, Void aVoid) { + JavaExpression javaExpr = JavaExpression.fromTree(node.getVariable()); + if (!sideEffectsOnlyExpressions.contains(javaExpr)) { + sideEffectsOnlyResult.addNotSEOnlyExpr(node, javaExpr); + } + return super.visitAssignment(node, aVoid); + } + + @Override + public Void visitUnary(UnaryTree node, Void aVoid) { + return super.visitUnary(node, aVoid); + } + + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree node, Void aVoid) { + return super.visitCompoundAssignment(node, aVoid); + } + } +} From fccd5e6dbe494afd410d75e0d04ea9c86836f026 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Thu, 14 Oct 2021 14:31:50 -0700 Subject: [PATCH 063/113] test case fixes --- .../common/basetype/BaseTypeVisitor.java | 7 +++++-- .../basetype/SideEffectsOnlyAnnoChecker.java | 14 +++++++------- .../sideeffectsonly/CheckSideEffectsOnly.java | 7 ++++++- .../sideeffectsonly/IncorrectSideEffectsOnly.java | 1 + 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index e30dcb0b29a..1cb0a2d1e0f 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -1120,8 +1120,11 @@ protected void checkSideEffectsOnly(MethodTree node) { List> seOnlyIncorrectExprs = sefOnlyResult.getSeOnlyResult(); if (!seOnlyIncorrectExprs.isEmpty()) { - for (Pair s : seOnlyIncorrectExprs) - checker.reportError(s.first, "incorrect.sideeffectsonly", s.second.toString()); + for (Pair s : seOnlyIncorrectExprs) { + if (!sideEffectsOnlyExpressions.contains(s.second)) { + checker.reportError(s.first, "incorrect.sideeffectsonly", s.second.toString()); + } + } } } } diff --git a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java index a2249a1023f..cbd2e050764 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java @@ -106,13 +106,13 @@ public Void visitMethodInvocation(MethodInvocationTree node, Void aVoid) { if (!sideEffectsOnlyExpressions.contains(receiverExpr)) { sideEffectsOnlyResult.addNotSEOnlyExpr(node, receiverExpr); } - List paramsAsLocals = - JavaExpression.getParametersAsLocalVariables((ExecutableElement) treeElem); - for (JavaExpression expr : paramsAsLocals) { - if (!sideEffectsOnlyExpressions.contains(expr)) { - sideEffectsOnlyResult.addNotSEOnlyExpr(node, expr); - } - } + // List paramsAsLocals = + // JavaExpression.getParametersAsLocalVariables((ExecutableElement) treeElem); + // for (JavaExpression expr : paramsAsLocals) { + // if (!sideEffectsOnlyExpressions.contains(expr)) { + // sideEffectsOnlyResult.addNotSEOnlyExpr(node, expr); + // } + // } } else { // If the invoked method is annotated with @SideEffectsOnly, // add annotation values to seOnlyIncorrectExprs diff --git a/framework/tests/sideeffectsonly/CheckSideEffectsOnly.java b/framework/tests/sideeffectsonly/CheckSideEffectsOnly.java index 63b4b43c166..fc9addeb18d 100644 --- a/framework/tests/sideeffectsonly/CheckSideEffectsOnly.java +++ b/framework/tests/sideeffectsonly/CheckSideEffectsOnly.java @@ -5,10 +5,15 @@ public class CheckSideEffectsOnly { - // :: error: incorrect.sideeffectsonly @SideEffectsOnly({"#2"}) void test(Collection cl, Collection cl2) { + // :: error: incorrect.sideeffectsonly cl.add(9); cl2.add(10); } + + @SideEffectsOnly({"#2"}) + static void test1(Collection cl, Collection cl2) { + cl2.add(10); + } } diff --git a/framework/tests/sideeffectsonly/IncorrectSideEffectsOnly.java b/framework/tests/sideeffectsonly/IncorrectSideEffectsOnly.java index 492c75b22b8..90e9df9be84 100644 --- a/framework/tests/sideeffectsonly/IncorrectSideEffectsOnly.java +++ b/framework/tests/sideeffectsonly/IncorrectSideEffectsOnly.java @@ -8,6 +8,7 @@ public class IncorrectSideEffectsOnly { @SideEffectsOnly({"#2"}) void test(Collection cl, Collection cl2) { + // :: error: incorrect.sideeffectsonly cl.add(9); } From 6d2d9faebe2d59106516a800667697ef4460702c Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Mon, 18 Oct 2021 13:17:40 -0700 Subject: [PATCH 064/113] javadoc --- .../common/basetype/BaseTypeVisitor.java | 1 + .../basetype/SideEffectsOnlyAnnoChecker.java | 39 ++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 1cb0a2d1e0f..7ed40eca96d 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -234,6 +234,7 @@ public class BaseTypeVisitor> seOnlyIncorrectExprs = new ArrayList<>(1); + /** + * Adds {@code t} and {@code javaExpr} as a Pair to seOnlyIncorrectExprs. + * + * @param t Tree + * @param javaExpr JavaExpression + */ public void addNotSEOnlyExpr(Tree t, JavaExpression javaExpr) { seOnlyIncorrectExprs.add(Pair.of(t, javaExpr)); } + /** + * Returns {@code seOnlyIncorrectExprs}. + * + * @return seOnlyIncorrectExprs + */ public List> getSeOnlyResult() { return seOnlyIncorrectExprs; } } + /** SideEffectsOnlyCheckerHelper. */ protected static class SideEffectsOnlyCheckerHelper extends TreePathScanner { - + /** Result computed by SideEffectsOnlyCheckerHelper. */ SideEffectsOnlyResult sideEffectsOnlyResult = new SideEffectsOnlyResult(); + /** + * List of expressions specified as annotation arguments in {@link SideEffectsOnly} annotation. + */ List sideEffectsOnlyExpressions; + /** AnnotationProvider. */ protected final AnnotationProvider annoProvider; + /** Processing Environment. */ ProcessingEnvironment processingEnv; + /** BaseTypeChecker. */ BaseTypeChecker checker; + /** + * Constructor for SideEffectsOnlyCheckerHelper. + * + * @param annoProvider AnnotationProvider + * @param sideEffectsOnlyExpressions List of JavaExpressions + * @param processingEnv ProcessingEnvironment + * @param checker BaseTypeChecker + */ public SideEffectsOnlyCheckerHelper( AnnotationProvider annoProvider, List sideEffectsOnlyExpressions, From 3c8878cce9684e7b44bbab186ea7ec845f5d5d1c Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Wed, 20 Oct 2021 16:12:29 -0700 Subject: [PATCH 065/113] documentation for -AcheckSideEffectsOnlyAnnotation --- docs/manual/introduction.tex | 7 +++++++ .../common/basetype/SideEffectsOnlyAnnoChecker.java | 7 ------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/manual/introduction.tex b/docs/manual/introduction.tex index dc2c997f1e8..87008f111b7 100644 --- a/docs/manual/introduction.tex +++ b/docs/manual/introduction.tex @@ -609,6 +609,13 @@ to ensure the method satisfies the annotation. By default, the Checker Framework unsoundly trusts the method annotation. See Section~\ref{type-refinement-purity}. +\item \<-AcheckSideEffectsOnlyAnnotation> +Check the bodies of methods marked +\refqualclass{dataflow/qual}{SideEffectsOnly} +to ensure the method side-effects a subset of the +expressions specified as annotation arguments/elements to +\refqualclass{dataflow/qual}{SideEffectsOnly}. See +Section~\ref{type-refinement-purity}. \item \<-AinvariantArrays> Make array subtyping invariant; that is, two arrays are subtypes of one another only if they have exactly the same element type. By default, diff --git a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java index 5ddf1ba523a..a7a60368afe 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java @@ -143,13 +143,6 @@ public Void visitMethodInvocation(MethodInvocationTree node, Void aVoid) { if (!sideEffectsOnlyExpressions.contains(receiverExpr)) { sideEffectsOnlyResult.addNotSEOnlyExpr(node, receiverExpr); } - // List paramsAsLocals = - // JavaExpression.getParametersAsLocalVariables((ExecutableElement) treeElem); - // for (JavaExpression expr : paramsAsLocals) { - // if (!sideEffectsOnlyExpressions.contains(expr)) { - // sideEffectsOnlyResult.addNotSEOnlyExpr(node, expr); - // } - // } } else { // If the invoked method is annotated with @SideEffectsOnly, // add annotation values to seOnlyIncorrectExprs From 15b3a08b10d17c82445d4843dde7180cf17e23ef Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 21 Oct 2021 13:30:37 -0700 Subject: [PATCH 066/113] Documentation tweaks --- .../dataflow/qual/SideEffectsOnly.java | 7 +++---- docs/manual/advanced-features.tex | 14 +++++++------- docs/manual/introduction.tex | 10 ++++------ .../common/basetype/BaseTypeVisitor.java | 3 ++- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java index dbb53e34f44..3b6b3b50708 100644 --- a/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java +++ b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java @@ -8,11 +8,11 @@ import org.checkerframework.framework.qual.JavaExpression; /** - * A method annotated with the declaration annotation {@code @SideEffectsOnly(A, B)} perfarms + * A method annotated with the declaration annotation {@code @SideEffectsOnly(A, B)} performs * side-effects on at most the expressions A and B. All other expressions have the same value before * and after a call. * - * @checker_framework.manual #type-refinement-purity Side effects only + * @checker_framework.manual #type-refinement-purity Specifying side effects */ @Documented @Retention(RetentionPolicy.RUNTIME) @@ -21,8 +21,7 @@ /** * An upper bound on the expressions that this method side effects. * - * @return Java expression(s) that represent an upper bound of expressions side-effected by this - * method + * @return Java expressions that the annotated method might side-effect * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions */ @JavaExpression diff --git a/docs/manual/advanced-features.tex b/docs/manual/advanced-features.tex index 2e387b9cd55..570a85f06ed 100644 --- a/docs/manual/advanced-features.tex +++ b/docs/manual/advanced-features.tex @@ -1015,8 +1015,8 @@ the refined type, because the method might assign a field. The \refqualclass{dataflow/qual}{SideEffectFree} annotation indicates that the method has no side effects, so calling it does not invalidate any -dataflow facts. The \refqualclass{dataflow/qual}{SideEffectsOnly} annotation -specifies the expressions that the method might side effect. +dataflow facts. Alternately, the \refqualclass{dataflow/qual}{SideEffectsOnly} +annotation specifies all the expressions that the method might side-effect. Calling a method twice might have different results, so facts known about one call cannot be relied upon at another call. @@ -1105,16 +1105,16 @@ \end{Verbatim} \end{enumerate} + \subsubsectionAndLabel{Relationship between \<@SideEffectFree> and \<@SideEffectsOnly>}{side-effects-relationship} -Expressions that are not specified as annotation arguments/elements to -\refqualclass{dataflow/qual}{SideEffectsOnly} are not side-effected. -If a method has no \refqualclass{dataflow/qual}{SideEffectsOnly} -or \refqualclass{dataflow/qual}{SideEffectFree} annotation, then the Checker Framework -assumes that the method may side-effect all expressions (fields and arguments). A method cannot be annotated with both \refqualclass{dataflow/qual}{SideEffectFree} and \refqualclass{dataflow/qual}{SideEffectsOnly}. The annotation \<@SideEffectsOnly(\{\})> is equivalent to \<@SideEffectFree>. +If a method has no \refqualclass{dataflow/qual}{SideEffectsOnly} +or \refqualclass{dataflow/qual}{SideEffectFree} annotation, then the Checker Framework +assumes that the method may side-effect any expressions (including fields and arguments). + \subsubsectionAndLabel{Deterministic methods}{type-refinement-determinism} diff --git a/docs/manual/introduction.tex b/docs/manual/introduction.tex index 87008f111b7..245ea351805 100644 --- a/docs/manual/introduction.tex +++ b/docs/manual/introduction.tex @@ -610,12 +610,10 @@ the Checker Framework unsoundly trusts the method annotation. See Section~\ref{type-refinement-purity}. \item \<-AcheckSideEffectsOnlyAnnotation> -Check the bodies of methods marked -\refqualclass{dataflow/qual}{SideEffectsOnly} -to ensure the method side-effects a subset of the -expressions specified as annotation arguments/elements to -\refqualclass{dataflow/qual}{SideEffectsOnly}. See -Section~\ref{type-refinement-purity}. + Check the bodies of methods marked + \refqualclass{dataflow/qual}{SideEffectsOnly} to ensure the method + side-effects at most the annotation's arguments/elements. See + Section~\ref{type-refinement-purity}. \item \<-AinvariantArrays> Make array subtyping invariant; that is, two arrays are subtypes of one another only if they have exactly the same element type. By default, diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 73ad1e3c0c0..c9884e468e7 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -220,8 +220,9 @@ public class BaseTypeVisitor Date: Wed, 27 Oct 2021 12:50:15 -0700 Subject: [PATCH 067/113] Tweak comment --- .../common/basetype/SideEffectsOnlyAnnoChecker.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java index a7a60368afe..c81ef18c2be 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java @@ -135,17 +135,17 @@ public Void visitMethodInvocation(MethodInvocationTree node, Void aVoid) { AnnotationMirror sideEffectsOnlyAnno = annoProvider.getDeclAnnotation(treeElem, SideEffectsOnly.class); - // If the invoked method is not annotated with @SideEffectsOnly, - // add those arguments to seOnlyIncorrectExprs - // that are not present in sideEffectsOnlyExpressions. if (sideEffectsOnlyAnno == null) { + // The invoked method is not annotated with @SideEffectsOnly. + // Add those arguments to seOnlyIncorrectExprs + // that are not present in sideEffectsOnlyExpressions. JavaExpression receiverExpr = JavaExpression.getReceiver(node); if (!sideEffectsOnlyExpressions.contains(receiverExpr)) { sideEffectsOnlyResult.addNotSEOnlyExpr(node, receiverExpr); } } else { - // If the invoked method is annotated with @SideEffectsOnly, - // add annotation values to seOnlyIncorrectExprs + // The invoked method is annotated with @SideEffectsOnly. + // Add annotation values to seOnlyIncorrectExprs // that are not present in sideEffectsOnlyExpressions. ExecutableElement sideEffectsOnlyValueElement = TreeUtils.getMethod(SideEffectsOnly.class, "value", 0, processingEnv); From 1264b077aec6e4d2d238357c888d16089f7e4588 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Tue, 2 Nov 2021 12:31:36 +0530 Subject: [PATCH 068/113] change method name --- .../common/basetype/SideEffectsOnlyAnnoChecker.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java index c81ef18c2be..84c06978acd 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java @@ -68,7 +68,7 @@ public static class SideEffectsOnlyResult { * @param t Tree * @param javaExpr JavaExpression */ - public void addNotSEOnlyExpr(Tree t, JavaExpression javaExpr) { + public void addMutatedExpr(Tree t, JavaExpression javaExpr) { seOnlyIncorrectExprs.add(Pair.of(t, javaExpr)); } @@ -141,7 +141,7 @@ public Void visitMethodInvocation(MethodInvocationTree node, Void aVoid) { // that are not present in sideEffectsOnlyExpressions. JavaExpression receiverExpr = JavaExpression.getReceiver(node); if (!sideEffectsOnlyExpressions.contains(receiverExpr)) { - sideEffectsOnlyResult.addNotSEOnlyExpr(node, receiverExpr); + sideEffectsOnlyResult.addMutatedExpr(node, receiverExpr); } } else { // The invoked method is annotated with @SideEffectsOnly. @@ -164,7 +164,7 @@ public Void visitMethodInvocation(MethodInvocationTree node, Void aVoid) { for (JavaExpression expr : sideEffectsOnlyExprInv) { if (!sideEffectsOnlyExpressions.contains(expr)) { - sideEffectsOnlyResult.addNotSEOnlyExpr(node, expr); + sideEffectsOnlyResult.addMutatedExpr(node, expr); } } } @@ -180,7 +180,7 @@ public Void visitNewClass(NewClassTree node, Void aVoid) { public Void visitAssignment(AssignmentTree node, Void aVoid) { JavaExpression javaExpr = JavaExpression.fromTree(node.getVariable()); if (!sideEffectsOnlyExpressions.contains(javaExpr)) { - sideEffectsOnlyResult.addNotSEOnlyExpr(node, javaExpr); + sideEffectsOnlyResult.addMutatedExpr(node, javaExpr); } return super.visitAssignment(node, aVoid); } From 5b0dbc5a888372fb29fb530bb61339461271c0fe Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Tue, 2 Nov 2021 12:37:55 +0530 Subject: [PATCH 069/113] fix error message key --- .../org/checkerframework/common/basetype/BaseTypeVisitor.java | 2 +- .../org/checkerframework/common/basetype/messages.properties | 3 +-- framework/tests/sideeffectsonly/CheckSideEffectsOnly.java | 2 +- framework/tests/sideeffectsonly/IncorrectSideEffectsOnly.java | 4 ++-- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index c9884e468e7..fa784e086c1 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -1096,7 +1096,7 @@ protected void checkSideEffectsOnly(MethodTree node) { if (!seOnlyIncorrectExprs.isEmpty()) { for (Pair s : seOnlyIncorrectExprs) { if (!sideEffectsOnlyExpressions.contains(s.second)) { - checker.reportError(s.first, "incorrect.sideeffectsonly", s.second.toString()); + checker.reportError(s.first, "purity.incorrect.sideeffectsonly", s.second.toString()); } } } diff --git a/framework/src/main/java/org/checkerframework/common/basetype/messages.properties b/framework/src/main/java/org/checkerframework/common/basetype/messages.properties index 94e7fa0871d..b5277ab64af 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/messages.properties +++ b/framework/src/main/java/org/checkerframework/common/basetype/messages.properties @@ -75,8 +75,7 @@ purity.not.sideeffectfree.call=call to side-effecting %s not allowed in side-eff purity.more.deterministic=the method %s could be declared as @Deterministic purity.more.pure=the method %s could be declared as @Pure purity.more.sideeffectfree=the method %s could be declared as @SideEffectFree - -incorrect.sideeffectsonly=the method side-effects %s +purity.incorrect.sideeffectsonly=the method side-effects %s flowexpr.parse.index.too.big=the method does not have a parameter %s flowexpr.parse.error=cannot parse the expression %s diff --git a/framework/tests/sideeffectsonly/CheckSideEffectsOnly.java b/framework/tests/sideeffectsonly/CheckSideEffectsOnly.java index fc9addeb18d..2afe05c4b65 100644 --- a/framework/tests/sideeffectsonly/CheckSideEffectsOnly.java +++ b/framework/tests/sideeffectsonly/CheckSideEffectsOnly.java @@ -7,7 +7,7 @@ public class CheckSideEffectsOnly { @SideEffectsOnly({"#2"}) void test(Collection cl, Collection cl2) { - // :: error: incorrect.sideeffectsonly + // :: error: purity.incorrect.sideeffectsonly cl.add(9); cl2.add(10); } diff --git a/framework/tests/sideeffectsonly/IncorrectSideEffectsOnly.java b/framework/tests/sideeffectsonly/IncorrectSideEffectsOnly.java index 90e9df9be84..4f4237b5372 100644 --- a/framework/tests/sideeffectsonly/IncorrectSideEffectsOnly.java +++ b/framework/tests/sideeffectsonly/IncorrectSideEffectsOnly.java @@ -8,13 +8,13 @@ public class IncorrectSideEffectsOnly { @SideEffectsOnly({"#2"}) void test(Collection cl, Collection cl2) { - // :: error: incorrect.sideeffectsonly + // :: error: purity.incorrect.sideeffectsonly cl.add(9); } @SideEffectsOnly({"#2"}) void test1(Collection cl, Collection cl2) { - // :: error: incorrect.sideeffectsonly + // :: error: purity.incorrect.sideeffectsonly coll = cl; } } From 1ecd0fee5901f45949c9596beae57652c387696a Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Tue, 2 Nov 2021 12:49:54 +0530 Subject: [PATCH 070/113] checkPurityAnnotations flag checks SideEffectsOnly --- docs/manual/introduction.tex | 5 ----- .../checkerframework/common/basetype/BaseTypeVisitor.java | 2 +- .../org/checkerframework/framework/source/SourceChecker.java | 1 - .../framework/test/junit/SideEffectsOnlyTest.java | 2 +- 4 files changed, 2 insertions(+), 8 deletions(-) diff --git a/docs/manual/introduction.tex b/docs/manual/introduction.tex index 245ea351805..dc2c997f1e8 100644 --- a/docs/manual/introduction.tex +++ b/docs/manual/introduction.tex @@ -609,11 +609,6 @@ to ensure the method satisfies the annotation. By default, the Checker Framework unsoundly trusts the method annotation. See Section~\ref{type-refinement-purity}. -\item \<-AcheckSideEffectsOnlyAnnotation> - Check the bodies of methods marked - \refqualclass{dataflow/qual}{SideEffectsOnly} to ensure the method - side-effects at most the annotation's arguments/elements. See - Section~\ref{type-refinement-purity}. \item \<-AinvariantArrays> Make array subtyping invariant; that is, two arrays are subtypes of one another only if they have exactly the same element type. By default, diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index fa784e086c1..bec4a2d33a2 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -1052,7 +1052,7 @@ protected void checkPurity(MethodTree node) { * @param node the method tree to check */ protected void checkSideEffectsOnly(MethodTree node) { - if (!checker.hasOption("checkSideEffectsOnlyAnnotation")) { + if (!checker.hasOption("checkPurityAnnotations")) { return; } diff --git a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java index 5ee0bcf882c..feb60ba5468 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -145,7 +145,6 @@ // Re-enable it after making the analysis more precise. // org.checkerframework.common.basetype.BaseTypeVisitor.visitMethod(MethodTree, Void) "checkPurityAnnotations", - "checkSideEffectsOnlyAnnotation", // TODO: Temporary option to make array subtyping invariant, // which will be the new default soon. diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/SideEffectsOnlyTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/SideEffectsOnlyTest.java index 10649262497..fdd97ba8b92 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/SideEffectsOnlyTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/SideEffectsOnlyTest.java @@ -14,7 +14,7 @@ public SideEffectsOnlyTest(List testFiles) { org.checkerframework.framework.testchecker.sideeffectsonly.SideEffectsOnlyToyChecker.class, "sideeffectsonly", "-Anomsgtext", - "-AcheckSideEffectsOnlyAnnotation"); + "-AcheckPurityAnnotations"); } @Parameters From a984534dcf9141450f476a752513e1e3141d5c50 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Sat, 6 Nov 2021 10:41:27 +0530 Subject: [PATCH 071/113] report error if invoked method not @SideEffectsOnly --- .../common/basetype/SideEffectsOnlyAnnoChecker.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java index 84c06978acd..676e96a1859 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java @@ -136,13 +136,8 @@ public Void visitMethodInvocation(MethodInvocationTree node, Void aVoid) { AnnotationMirror sideEffectsOnlyAnno = annoProvider.getDeclAnnotation(treeElem, SideEffectsOnly.class); if (sideEffectsOnlyAnno == null) { - // The invoked method is not annotated with @SideEffectsOnly. - // Add those arguments to seOnlyIncorrectExprs - // that are not present in sideEffectsOnlyExpressions. - JavaExpression receiverExpr = JavaExpression.getReceiver(node); - if (!sideEffectsOnlyExpressions.contains(receiverExpr)) { - sideEffectsOnlyResult.addMutatedExpr(node, receiverExpr); - } + // The invoked method is not annotated with @SideEffectsOnly; Report an error. + checker.reportError(node, "purity.incorrect.sideeffectsonly", node); } else { // The invoked method is annotated with @SideEffectsOnly. // Add annotation values to seOnlyIncorrectExprs From 177c22174454433104dd8bd644938df2bdefeb53 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Sat, 6 Nov 2021 10:42:00 +0530 Subject: [PATCH 072/113] report error if invoked method not @SideEffectsOnly --- .../common/basetype/SideEffectsOnlyAnnoChecker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java index 676e96a1859..53b3bf66c4e 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java @@ -135,8 +135,8 @@ public Void visitMethodInvocation(MethodInvocationTree node, Void aVoid) { AnnotationMirror sideEffectsOnlyAnno = annoProvider.getDeclAnnotation(treeElem, SideEffectsOnly.class); + // The invoked method is not annotated with @SideEffectsOnly; Report an error. if (sideEffectsOnlyAnno == null) { - // The invoked method is not annotated with @SideEffectsOnly; Report an error. checker.reportError(node, "purity.incorrect.sideeffectsonly", node); } else { // The invoked method is annotated with @SideEffectsOnly. From eb71099ad5c2aa9e383272ecf5ce8316650bae84 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Mon, 8 Nov 2021 11:19:26 +0530 Subject: [PATCH 073/113] adding test case --- .../sideeffectsonly/TestMethodInvocation.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 framework/tests/sideeffectsonly/TestMethodInvocation.java diff --git a/framework/tests/sideeffectsonly/TestMethodInvocation.java b/framework/tests/sideeffectsonly/TestMethodInvocation.java new file mode 100644 index 00000000000..79e5ff808d9 --- /dev/null +++ b/framework/tests/sideeffectsonly/TestMethodInvocation.java @@ -0,0 +1,17 @@ +package sideeffectsonly; + +import org.checkerframework.dataflow.qual.SideEffectsOnly; + +public class TestMethodInvocation { + @SideEffectsOnly("#1") + void method1(Object o) { + // :: error: purity.incorrect.sideeffectsonly + method2(); + method3(o); + } + + void method2() {} + + @SideEffectsOnly("#1") + void method3(Object o) {} +} From a1115c721aa5cc71bd4be7119839ad90da64b86d Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Mon, 8 Nov 2021 18:31:32 +0530 Subject: [PATCH 074/113] fixing javadoc --- .../basetype/SideEffectsOnlyAnnoChecker.java | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java index 53b3bf66c4e..58ab5320d98 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java @@ -34,12 +34,13 @@ public class SideEffectsOnlyAnnoChecker { /** * Returns the computed {@code SideEffectsOnlyResult}. * - * @param statement TreePath - * @param annoProvider AnnotationProvider - * @param sideEffectsOnlyExpressions List of JavaExpression - * @param processingEnv ProcessingEnvironment - * @param checker BaseTypeChecker - * @return SideEffectsOnlyResult + * @param statement the statement to check + * @param annoProvider the annotation provider + * @param sideEffectsOnlyExpressions List of JavaExpressions that are provided as annotation + * values to {@link SideEffectsOnly} + * @param processingEnv The processing environment + * @param checker the checker to use + * @return SideEffectsOnlyResult returns the result of {@link SideEffectsOnlyAnnoChecker} */ public static SideEffectsOnlyResult checkSideEffectsOnly( TreePath statement, @@ -54,31 +55,34 @@ public static SideEffectsOnlyResult checkSideEffectsOnly( return helper.sideEffectsOnlyResult; } - /** SideEffectsOnlyResult. */ + /** + * Result of the {@link SideEffectsOnlyAnnoChecker}. Can be queried to get the list of mutated + * expressions. + */ public static class SideEffectsOnlyResult { /** * List of expressions a method side-effects that are not specified in the list of arguments to * {@link SideEffectsOnly}. */ - protected final List> seOnlyIncorrectExprs = new ArrayList<>(1); + protected final List> mutatedExprs = new ArrayList<>(1); /** - * Adds {@code t} and {@code javaExpr} as a Pair to seOnlyIncorrectExprs. + * Adds {@code t} and {@code javaExpr} as a Pair to mutatedExprs. * - * @param t Tree - * @param javaExpr JavaExpression + * @param t The expression that is mutated + * @param javaExpr The corresponding Java expression that is mutated */ public void addMutatedExpr(Tree t, JavaExpression javaExpr) { - seOnlyIncorrectExprs.add(Pair.of(t, javaExpr)); + mutatedExprs.add(Pair.of(t, javaExpr)); } /** - * Returns {@code seOnlyIncorrectExprs}. + * Returns {@code mutatedExprs}. * - * @return seOnlyIncorrectExprs + * @return mutatedExprs */ public List> getSeOnlyResult() { - return seOnlyIncorrectExprs; + return mutatedExprs; } } From 30f4258cbaf4617f5b30a96d5446d480252b0151 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Mon, 8 Nov 2021 18:56:59 +0530 Subject: [PATCH 075/113] fixing javadoc --- .../basetype/SideEffectsOnlyAnnoChecker.java | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java index 58ab5320d98..d7c2e098292 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java @@ -34,12 +34,12 @@ public class SideEffectsOnlyAnnoChecker { /** * Returns the computed {@code SideEffectsOnlyResult}. * - * @param statement the statement to check - * @param annoProvider the annotation provider + * @param statement The statement to check + * @param annoProvider The annotation provider * @param sideEffectsOnlyExpressions List of JavaExpressions that are provided as annotation * values to {@link SideEffectsOnly} * @param processingEnv The processing environment - * @param checker the checker to use + * @param checker The checker to use * @return SideEffectsOnlyResult returns the result of {@link SideEffectsOnlyAnnoChecker} */ public static SideEffectsOnlyResult checkSideEffectsOnly( @@ -86,7 +86,10 @@ public List> getSeOnlyResult() { } } - /** SideEffectsOnlyCheckerHelper. */ + /** + * Class that visits that visits various nodes and computes mutated expressions that are not + * specified as annotation values to {@link SideEffectsOnly}. + */ protected static class SideEffectsOnlyCheckerHelper extends TreePathScanner { /** Result computed by SideEffectsOnlyCheckerHelper. */ SideEffectsOnlyResult sideEffectsOnlyResult = new SideEffectsOnlyResult(); @@ -95,20 +98,21 @@ protected static class SideEffectsOnlyCheckerHelper extends TreePathScanner sideEffectsOnlyExpressions; - /** AnnotationProvider. */ + /** The annotation provider. */ protected final AnnotationProvider annoProvider; - /** Processing Environment. */ + /** The processing environment. */ ProcessingEnvironment processingEnv; - /** BaseTypeChecker. */ + /** The checker to use. */ BaseTypeChecker checker; /** * Constructor for SideEffectsOnlyCheckerHelper. * - * @param annoProvider AnnotationProvider - * @param sideEffectsOnlyExpressions List of JavaExpressions - * @param processingEnv ProcessingEnvironment - * @param checker BaseTypeChecker + * @param annoProvider The annotation provider + * @param sideEffectsOnlyExpressions List of JavaExpressions that are provided as annotation + * values to {@link SideEffectsOnly} + * @param processingEnv The processing environment + * @param checker The checker to use */ public SideEffectsOnlyCheckerHelper( AnnotationProvider annoProvider, From b630298360de626f0517e02c5f25352a0eb9d108 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Sun, 14 Nov 2021 13:21:24 +0530 Subject: [PATCH 076/113] testing CheckSideEffectsOnly functionality in Purity Checker --- .../common/basetype/BaseTypeVisitor.java | 124 +++++++++--------- 1 file changed, 64 insertions(+), 60 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index bec4a2d33a2..10dd4be4cfb 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -234,6 +234,8 @@ public class BaseTypeVisitorIf the method {@code node} is annotated with {@link SideEffectsOnly}, check that the method + * side-effects a subset of the expressions specified as annotation arguments/elements to {@link + * SideEffectsOnly}. + * * @param node the method tree to check */ protected void checkPurity(MethodTree node) { @@ -974,6 +980,55 @@ protected void checkPurity(MethodTree node) { return; } + TreePath body = atypeFactory.getPath(node.getBody()); + + if (checkSideEffectsOnly) { + if (body == null) { + return; + } else { + @Nullable Element methodDeclElem = TreeUtils.elementFromTree(node); + AnnotationMirror sefOnlyAnnotation = + atypeFactory.getDeclAnnotation(methodDeclElem, SideEffectsOnly.class); + if (sefOnlyAnnotation == null) { + return; + } + List sideEffectsOnlyExpressionStrings = + AnnotationUtils.getElementValueArray( + sefOnlyAnnotation, sideEffectsOnlyValueElement, String.class); + List sideEffectsOnlyExpressions = new ArrayList<>(); + for (String st : sideEffectsOnlyExpressionStrings) { + try { + JavaExpression exprJe = StringToJavaExpression.atMethodBody(st, node, checker); + sideEffectsOnlyExpressions.add(exprJe); + } catch (JavaExpressionParseUtil.JavaExpressionParseException ex) { + checker.report(st, ex.getDiagMessage()); + return; + } + } + + if (sideEffectsOnlyExpressions.isEmpty()) { + return; + } + + SideEffectsOnlyAnnoChecker.SideEffectsOnlyResult sefOnlyResult = + SideEffectsOnlyAnnoChecker.checkSideEffectsOnly( + body, + atypeFactory, + sideEffectsOnlyExpressions, + atypeFactory.getProcessingEnv(), + checker); + + List> seOnlyIncorrectExprs = sefOnlyResult.getSeOnlyResult(); + if (!seOnlyIncorrectExprs.isEmpty()) { + for (Pair s : seOnlyIncorrectExprs) { + if (!sideEffectsOnlyExpressions.contains(s.second)) { + checker.reportError(s.first, "purity.incorrect.sideeffectsonly", s.second.toString()); + } + } + } + } + } + if (!suggestPureMethods && !PurityUtils.hasPurityAnnotation(atypeFactory, node)) { // There is nothing to check. return; @@ -991,7 +1046,6 @@ protected void checkPurity(MethodTree node) { } } - TreePath body = atypeFactory.getPath(node.getBody()); PurityResult r; if (body == null) { r = new PurityResult(); @@ -1044,64 +1098,14 @@ protected void checkPurity(MethodTree node) { } } - /** - * If the method {@code node} is annotated with {@link SideEffectsOnly}, checks that the method - * side-effects a subset of the expressions specified as annotation arguments/elements to {@link - * SideEffectsOnly}. - * - * @param node the method tree to check - */ - protected void checkSideEffectsOnly(MethodTree node) { - if (!checker.hasOption("checkPurityAnnotations")) { - return; - } - - TreePath body = atypeFactory.getPath(node.getBody()); - if (body == null) { - return; - } else { - @Nullable Element methodDeclElem = TreeUtils.elementFromTree(node); - AnnotationMirror sefOnlyAnnotation = - atypeFactory.getDeclAnnotation(methodDeclElem, SideEffectsOnly.class); - if (sefOnlyAnnotation == null) { - return; - } - List sideEffectsOnlyExpressionStrings = - AnnotationUtils.getElementValueArray( - sefOnlyAnnotation, sideEffectsOnlyValueElement, String.class); - List sideEffectsOnlyExpressions = new ArrayList<>(); - for (String st : sideEffectsOnlyExpressionStrings) { - try { - JavaExpression exprJe = StringToJavaExpression.atMethodBody(st, node, checker); - sideEffectsOnlyExpressions.add(exprJe); - } catch (JavaExpressionParseUtil.JavaExpressionParseException ex) { - checker.report(st, ex.getDiagMessage()); - return; - } - } - - if (sideEffectsOnlyExpressions.isEmpty()) { - return; - } - - SideEffectsOnlyAnnoChecker.SideEffectsOnlyResult sefOnlyResult = - SideEffectsOnlyAnnoChecker.checkSideEffectsOnly( - body, - atypeFactory, - sideEffectsOnlyExpressions, - atypeFactory.getProcessingEnv(), - checker); - - List> seOnlyIncorrectExprs = sefOnlyResult.getSeOnlyResult(); - if (!seOnlyIncorrectExprs.isEmpty()) { - for (Pair s : seOnlyIncorrectExprs) { - if (!sideEffectsOnlyExpressions.contains(s.second)) { - checker.reportError(s.first, "purity.incorrect.sideeffectsonly", s.second.toString()); - } - } - } - } - } + // /** + // * + // * + // * @param node the method tree to check + // */ + // protected void checkSideEffectsOnly(MethodTree node) { + // + // } /** * Issue a warning if the result type of the constructor is not top. If it is a supertype of the From d1b69c412eb5d42639a37ce9dc981eb700bf5be3 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Sun, 14 Nov 2021 13:22:08 +0530 Subject: [PATCH 077/113] minor --- .../common/basetype/BaseTypeVisitor.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 10dd4be4cfb..34bf776f701 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -1098,15 +1098,6 @@ protected void checkPurity(MethodTree node) { } } - // /** - // * - // * - // * @param node the method tree to check - // */ - // protected void checkSideEffectsOnly(MethodTree node) { - // - // } - /** * Issue a warning if the result type of the constructor is not top. If it is a supertype of the * class, then a conflicting.annos error will also be issued by {@link From d9e33366fb41daa1af7462b29e6a0148c8c6673a Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Mon, 15 Nov 2021 13:57:21 +0530 Subject: [PATCH 078/113] fix --- .../common/basetype/BaseTypeVisitor.java | 139 +++++++++--------- 1 file changed, 71 insertions(+), 68 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 34bf776f701..a6acab522a5 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -982,6 +982,77 @@ protected void checkPurity(MethodTree node) { TreePath body = atypeFactory.getPath(node.getBody()); + // if (!suggestPureMethods && !PurityUtils.hasPurityAnnotation(atypeFactory, node) && + // !checkSideEffectsOnly) { + // // There is nothing to check. + // return; + // } + + if (suggestPureMethods || PurityUtils.hasPurityAnnotation(atypeFactory, node)) { + // check "no" purity + EnumSet kinds = PurityUtils.getPurityKinds(atypeFactory, node); + // @Deterministic makes no sense for a void method or constructor + boolean isDeterministic = kinds.contains(Pure.Kind.DETERMINISTIC); + if (isDeterministic) { + if (TreeUtils.isConstructor(node)) { + checker.reportWarning(node, "purity.deterministic.constructor"); + } else if (TreeUtils.typeOf(node.getReturnType()).getKind() == TypeKind.VOID) { + checker.reportWarning(node, "purity.deterministic.void.method"); + } + } + + PurityResult r; + if (body == null) { + r = new PurityResult(); + } else { + r = + PurityChecker.checkPurity( + body, + atypeFactory, + checker.hasOption("assumeSideEffectFree") || checker.hasOption("assumePure"), + checker.hasOption("assumeDeterministic") || checker.hasOption("assumePure")); + } + if (!r.isPure(kinds)) { + reportPurityErrors(r, node, kinds); + } + + if (suggestPureMethods && !TreeUtils.isSynthetic(node)) { + // Issue a warning if the method is pure, but not annotated as such. + EnumSet additionalKinds = r.getKinds().clone(); + additionalKinds.removeAll(kinds); + if (TreeUtils.isConstructor(node)) { + additionalKinds.remove(Pure.Kind.DETERMINISTIC); + } + if (!additionalKinds.isEmpty()) { + if (infer) { + if (inferPurity) { + WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); + ExecutableElement methodElt = TreeUtils.elementFromDeclaration(node); + if (additionalKinds.size() == 2) { + wpi.addMethodDeclarationAnnotation(methodElt, PURE); + } else if (additionalKinds.contains(Pure.Kind.SIDE_EFFECT_FREE)) { + wpi.addMethodDeclarationAnnotation(methodElt, SIDE_EFFECT_FREE); + } else if (additionalKinds.contains(Pure.Kind.DETERMINISTIC)) { + wpi.addMethodDeclarationAnnotation(methodElt, DETERMINISTIC); + } else { + throw new BugInCF("Unexpected purity kind in " + additionalKinds); + } + } + } else { + if (additionalKinds.size() == 2) { + checker.reportWarning(node, "purity.more.pure", node.getName()); + } else if (additionalKinds.contains(Pure.Kind.SIDE_EFFECT_FREE)) { + checker.reportWarning(node, "purity.more.sideeffectfree", node.getName()); + } else if (additionalKinds.contains(Pure.Kind.DETERMINISTIC)) { + checker.reportWarning(node, "purity.more.deterministic", node.getName()); + } else { + throw new BugInCF("Unexpected purity kind in " + additionalKinds); + } + } + } + } + } + if (checkSideEffectsOnly) { if (body == null) { return; @@ -1028,74 +1099,6 @@ protected void checkPurity(MethodTree node) { } } } - - if (!suggestPureMethods && !PurityUtils.hasPurityAnnotation(atypeFactory, node)) { - // There is nothing to check. - return; - } - - // check "no" purity - EnumSet kinds = PurityUtils.getPurityKinds(atypeFactory, node); - // @Deterministic makes no sense for a void method or constructor - boolean isDeterministic = kinds.contains(Pure.Kind.DETERMINISTIC); - if (isDeterministic) { - if (TreeUtils.isConstructor(node)) { - checker.reportWarning(node, "purity.deterministic.constructor"); - } else if (TreeUtils.typeOf(node.getReturnType()).getKind() == TypeKind.VOID) { - checker.reportWarning(node, "purity.deterministic.void.method"); - } - } - - PurityResult r; - if (body == null) { - r = new PurityResult(); - } else { - r = - PurityChecker.checkPurity( - body, - atypeFactory, - checker.hasOption("assumeSideEffectFree") || checker.hasOption("assumePure"), - checker.hasOption("assumeDeterministic") || checker.hasOption("assumePure")); - } - if (!r.isPure(kinds)) { - reportPurityErrors(r, node, kinds); - } - - if (suggestPureMethods && !TreeUtils.isSynthetic(node)) { - // Issue a warning if the method is pure, but not annotated as such. - EnumSet additionalKinds = r.getKinds().clone(); - additionalKinds.removeAll(kinds); - if (TreeUtils.isConstructor(node)) { - additionalKinds.remove(Pure.Kind.DETERMINISTIC); - } - if (!additionalKinds.isEmpty()) { - if (infer) { - if (inferPurity) { - WholeProgramInference wpi = atypeFactory.getWholeProgramInference(); - ExecutableElement methodElt = TreeUtils.elementFromDeclaration(node); - if (additionalKinds.size() == 2) { - wpi.addMethodDeclarationAnnotation(methodElt, PURE); - } else if (additionalKinds.contains(Pure.Kind.SIDE_EFFECT_FREE)) { - wpi.addMethodDeclarationAnnotation(methodElt, SIDE_EFFECT_FREE); - } else if (additionalKinds.contains(Pure.Kind.DETERMINISTIC)) { - wpi.addMethodDeclarationAnnotation(methodElt, DETERMINISTIC); - } else { - throw new BugInCF("Unexpected purity kind in " + additionalKinds); - } - } - } else { - if (additionalKinds.size() == 2) { - checker.reportWarning(node, "purity.more.pure", node.getName()); - } else if (additionalKinds.contains(Pure.Kind.SIDE_EFFECT_FREE)) { - checker.reportWarning(node, "purity.more.sideeffectfree", node.getName()); - } else if (additionalKinds.contains(Pure.Kind.DETERMINISTIC)) { - checker.reportWarning(node, "purity.more.deterministic", node.getName()); - } else { - throw new BugInCF("Unexpected purity kind in " + additionalKinds); - } - } - } - } } /** From 2d33f324387a43e129b6e9be03e3627968caf286 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 5 Jan 2023 10:57:35 -0800 Subject: [PATCH 079/113] Tweaks --- .../common/basetype/BaseTypeVisitor.java | 13 ++++++------- .../common/basetype/SideEffectsOnlyAnnoChecker.java | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index edd6d8c5e59..66ad1419d70 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -222,7 +222,7 @@ public class BaseTypeVisitor Date: Wed, 25 Jan 2023 20:19:32 -0800 Subject: [PATCH 080/113] Assign `body` once --- .../common/basetype/BaseTypeVisitor.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 376ef1fa216..94ad2273663 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -1004,6 +1004,9 @@ protected void checkPurity(MethodTree node) { // return; // } + TreePath body = null; + boolean bodyAssigned = false; + if (suggestPureMethods || PurityUtils.hasPurityAnnotation(atypeFactory, node)) { // check "no" purity EnumSet kinds = PurityUtils.getPurityKinds(atypeFactory, node); @@ -1017,7 +1020,8 @@ protected void checkPurity(MethodTree node) { } } - TreePath body = atypeFactory.getPath(node.getBody()); + body = atypeFactory.getPath(node.getBody()); + bodyAssigned = true; PurityResult r; if (body == null) { r = new PurityResult(); @@ -1073,6 +1077,10 @@ protected void checkPurity(MethodTree node) { } if (checkSideEffectsOnly) { + if (bodyAssigned == false) { + body = atypeFactory.getPath(node.getBody()); + bodyAssigned = true; + } if (body == null) { return; } else { From b4ed608a3958ec063c8e9b714f705ba8759a5f9a Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 26 Jan 2023 08:37:20 -0800 Subject: [PATCH 081/113] Add default constructors as required by JDK 19 --- .../common/basetype/SideEffectsOnlyAnnoChecker.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java index bea733555a2..679d31b36f1 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java @@ -31,6 +31,10 @@ * but not permitted by the annotation. */ public class SideEffectsOnlyAnnoChecker { + + /** Creates a SideEffectsOnlyAnnoChecker. */ + public SideEffectsOnlyAnnoChecker() {} + /** * Returns the computed {@code SideEffectsOnlyResult}. * @@ -60,6 +64,10 @@ public static SideEffectsOnlyResult checkSideEffectsOnly( * expressions. */ public static class SideEffectsOnlyResult { + + /** Creates a SideEffectsOnlyResult. */ + public SideEffectsOnlyResult() {} + /** * List of expressions a method side-effects that are not specified in the list of arguments to * {@link SideEffectsOnly}. From c88351c809f098859f00f71a4be9a1c189cc5759 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 26 Jan 2023 08:47:58 -0800 Subject: [PATCH 082/113] Remove unneeded option --- .../framework/test/junit/SideEffectsOnlyTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/SideEffectsOnlyTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/SideEffectsOnlyTest.java index 5f40857b477..8e0e9c37fb2 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/SideEffectsOnlyTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/SideEffectsOnlyTest.java @@ -15,7 +15,6 @@ public SideEffectsOnlyTest(List testFiles) { testFiles, org.checkerframework.framework.testchecker.sideeffectsonly.SideEffectsOnlyToyChecker.class, "sideeffectsonly", - "-Anomsgtext", "-AcheckPurityAnnotations"); } From 0161dfb42e5015e7cd6bdbb74061f1d6dfa9307b Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 26 Jan 2023 09:02:25 -0800 Subject: [PATCH 083/113] Tweak comments --- .../org/checkerframework/framework/flow/CFAbstractStore.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index be21e4151a6..830717f2a5b 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -264,12 +264,12 @@ public void updateForMethodCall( // TODO: Also remove if any element/argument to the annotation is not // isUnmodifiableByOtherCode. Example: @KeyFor("valueThatCanBeMutated"). if (sideEffectsUnrefineAliases) { + // TODO: Why is this code within the sideEffectsUnrefineAliases branch?? if (!sideEffectsOnlyExpressions.isEmpty()) { for (JavaExpression e : sideEffectsOnlyExpressions) { if (!e.isUnmodifiableByOtherCode()) { - // update local variables + // Remove any computed information about the expression. localVariableValues.keySet().remove(e); - // update field values fieldValues.keySet().remove(e); } } From 8d97f2021bdd42ea7f1843f076a365605044a524 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 26 Jan 2023 09:04:09 -0800 Subject: [PATCH 084/113] Rename --- .../checkerframework/common/basetype/BaseTypeVisitor.java | 4 ++-- ...tsOnlyAnnoChecker.java => SideEffectsOnlyChecker.java} | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) rename framework/src/main/java/org/checkerframework/common/basetype/{SideEffectsOnlyAnnoChecker.java => SideEffectsOnlyChecker.java} (97%) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 94ad2273663..897e408de8c 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -1108,8 +1108,8 @@ protected void checkPurity(MethodTree node) { return; } - SideEffectsOnlyAnnoChecker.SideEffectsOnlyResult sefOnlyResult = - SideEffectsOnlyAnnoChecker.checkSideEffectsOnly( + SideEffectsOnlyChecker.SideEffectsOnlyResult sefOnlyResult = + SideEffectsOnlyChecker.checkSideEffectsOnly( body, atypeFactory, sideEffectsOnlyExpressions, diff --git a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java similarity index 97% rename from framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java rename to framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java index 679d31b36f1..cddf782ac98 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyAnnoChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java @@ -30,10 +30,10 @@ * For methods annotated with {@link SideEffectsOnly}, computes expressions that are side-effected * but not permitted by the annotation. */ -public class SideEffectsOnlyAnnoChecker { +public class SideEffectsOnlyChecker { /** Creates a SideEffectsOnlyAnnoChecker. */ - public SideEffectsOnlyAnnoChecker() {} + public SideEffectsOnlyChecker() {} /** * Returns the computed {@code SideEffectsOnlyResult}. @@ -44,7 +44,7 @@ public SideEffectsOnlyAnnoChecker() {} * values to {@link SideEffectsOnly} * @param processingEnv The processing environment * @param checker The checker to use - * @return SideEffectsOnlyResult returns the result of {@link SideEffectsOnlyAnnoChecker} + * @return SideEffectsOnlyResult returns the result of {@link SideEffectsOnlyChecker} */ public static SideEffectsOnlyResult checkSideEffectsOnly( TreePath statement, @@ -60,7 +60,7 @@ public static SideEffectsOnlyResult checkSideEffectsOnly( } /** - * Result of the {@link SideEffectsOnlyAnnoChecker}. Can be queried to get the list of mutated + * Result of the {@link SideEffectsOnlyChecker}. Can be queried to get the list of mutated * expressions. */ public static class SideEffectsOnlyResult { From f639ba25f511c5bf95e6c70e3b5a31dbbcd4684c Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 26 Jan 2023 09:12:55 -0800 Subject: [PATCH 085/113] Add documentation --- .../dataflow/qual/SideEffectsOnly.java | 4 ++-- docs/manual/advanced-features.tex | 11 ++++++++++- docs/manual/called-methods-checker.tex | 8 ++++++-- docs/manual/introduction.tex | 5 +++++ docs/manual/nullness-checker.tex | 3 ++- docs/manual/purity-checker.tex | 6 +++++- docs/manual/troubleshooting.tex | 1 + 7 files changed, 31 insertions(+), 7 deletions(-) diff --git a/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java index 3b6b3b50708..4370c556470 100644 --- a/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java +++ b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java @@ -8,7 +8,7 @@ import org.checkerframework.framework.qual.JavaExpression; /** - * A method annotated with the declaration annotation {@code @SideEffectsOnly(A, B)} performs + * A method annotated with the declaration annotation {@code @SideEffectsOnly("A", "B")} performs * side-effects on at most the expressions A and B. All other expressions have the same value before * and after a call. * @@ -21,7 +21,7 @@ /** * An upper bound on the expressions that this method side effects. * - * @return Java expressions that the annotated method might side-effect + * @return the Java expressions that the annotated method might side-effect * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions */ @JavaExpression diff --git a/docs/manual/advanced-features.tex b/docs/manual/advanced-features.tex index ed0401aaefa..f9ee959d084 100644 --- a/docs/manual/advanced-features.tex +++ b/docs/manual/advanced-features.tex @@ -1056,7 +1056,7 @@ } \end{Verbatim} -There are three ways to express that \ does not set +There are four ways to express that \ does not set \ to \, and thus to prevent the Nullness Checker from issuing a warning about the call \. @@ -1070,6 +1070,15 @@ int computeValue() { ... } \end{Verbatim} +\item + If \ has side effects, but they do not affect \, + declare the method as \refqualclass{dataflow/qual}{SideEffectsOnly}: + +\begin{Verbatim} + @SideEffectsOnly({"someOtherVariable1", "someOtherVariable1"}) + int computeValue() { ... } +\end{Verbatim} + \noindent The Nullness Checker issues no warnings, because it can reason that the second occurrence of \code{myField} has the same (non-null) value as diff --git a/docs/manual/called-methods-checker.tex b/docs/manual/called-methods-checker.tex index a2bbae59c55..b6fe3c11e8e 100644 --- a/docs/manual/called-methods-checker.tex +++ b/docs/manual/called-methods-checker.tex @@ -217,7 +217,9 @@ \end{Verbatim} If \ might have side-effects (i.e., it is not annotated as - \refqualclass{dataflow/qual}{SideEffectFree} or \refqualclass{dataflow/qual}{Pure}), + \refqualclass{dataflow/qual}{SideEffectFree}, + \refqualclass{dataflow/qual}{SideEffectsOnly}, or + \refqualclass{dataflow/qual}{Pure}), then the Called Methods Checker issues an error because it cannot make any assumptions about the call to \, and therefore assumes the worst: that all information it knows about in-scope variables (including @@ -225,7 +227,9 @@ There are two possible fixes: \begin{itemize} - \item add a \<@SideEffectFree> or \<@Pure> annotation to \, if \ is + \item add a \refqualclass{dataflow/qual}{SideEffectFree}, + \refqualclass{dataflow/qual}{SideEffectsOnly}, or + \refqualclass{dataflow/qual}{Pure} annotation to \, if \ is in fact side-effect free or pure; or \item re-order the calls to \ and \ so that the call to \ appears last in \. diff --git a/docs/manual/introduction.tex b/docs/manual/introduction.tex index 356981003f8..aefcfe5339c 100644 --- a/docs/manual/introduction.tex +++ b/docs/manual/introduction.tex @@ -601,6 +601,7 @@ \item \<-AcheckPurityAnnotations> Check the bodies of methods marked \refqualclass{dataflow/qual}{SideEffectFree}, + \refqualclass{dataflow/qual}{SideEffectsOnly}, \refqualclass{dataflow/qual}{Deterministic}, and \refqualclass{dataflow/qual}{Pure} to ensure the method satisfies the annotation. By default, @@ -1535,6 +1536,10 @@ If your proof includes ``method \ has no side effects'', then annotate \'s declaration with \refqualclass{dataflow/qual}{SideEffectFree}. +\item + If your proof includes ``method \ has limited side effects'', + then annotate \'s declaration with + \refqualclass{dataflow/qual}{SideEffectsOnly}. \item If your proof includes ``each call to method \ returns the same value'', then annotate \'s declaration with diff --git a/docs/manual/nullness-checker.tex b/docs/manual/nullness-checker.tex index e8a76af1ce5..c0e03744122 100644 --- a/docs/manual/nullness-checker.tex +++ b/docs/manual/nullness-checker.tex @@ -230,7 +230,8 @@ arbitrary external method calls that have access to the given field. By contrast, for a \<@Nullable> field, the Nullness Checker assumes that most method calls might set it to \. (Exceptions are calls to - methods that are \refqualclass{dataflow/qual}{SideEffectFree} or that + methods that are \refqualclass{dataflow/qual}{SideEffectFree} or + \refqualclass{dataflow/qual}{SideEffectsOnly}, or that have an \refqualclass{checker/nullness/qual}{EnsuresNonNull} or \refqualclass{checker/nullness/qual}{EnsuresNonNullIf} annotation.) \end{sloppypar} diff --git a/docs/manual/purity-checker.tex b/docs/manual/purity-checker.tex index 0e30c4f4011..f7f16417cc5 100644 --- a/docs/manual/purity-checker.tex +++ b/docs/manual/purity-checker.tex @@ -27,6 +27,9 @@ \item[\refqualclass{dataflow/qual}{SideEffectFree}] indicates that the method has no externally-visible side effects. +\item[\refqualclass{dataflow/qual}{SideEffectsOnly}] + indicates that the method has limited externally-visible side effects. + \item[\refqualclass{dataflow/qual}{Deterministic}] indicates that if the method is called multiple times with identical arguments, then it returns the identical result according to \<==> @@ -42,7 +45,8 @@ By default, purity annotations are trusted. Purity annotations on called methods affect type-checking of client code. However, you can make a -mistake by writing \<@SideEffectFree> on the declaration of a method that +mistake by writing \<@SideEffectFree> or \<@SideEffectsOnly> on the +declaration of a method that is not actually side-effect-free or by writing \<@Deterministic> on the declaration of a method that is not actually deterministic. diff --git a/docs/manual/troubleshooting.tex b/docs/manual/troubleshooting.tex index 767bef4c92c..b47f6fc211b 100644 --- a/docs/manual/troubleshooting.tex +++ b/docs/manual/troubleshooting.tex @@ -362,6 +362,7 @@ \ does not set the field \ to \, you can use \<\refqualclass{dataflow/qual}{Pure}>, \<\refqualclass{dataflow/qual}{SideEffectFree}>, +\<\refqualclass{dataflow/qual}{SideEffectsOnly}>, or \<\refqualclass{checker/nullness/qual}{EnsuresNonNull}> on the declaration of \; see Sections~\ref{type-refinement-purity} and~\ref{nullness-method-annotations}. From 1463fffb061af10d171d0b4c0f3f03b450e7e781 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 26 Jan 2023 09:16:40 -0800 Subject: [PATCH 086/113] Rename variable --- .../checkerframework/common/basetype/BaseTypeVisitor.java | 8 ++++---- .../sideeffectsonly/SideEffectsOnlyToyChecker.java | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 897e408de8c..68f671d4cf0 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -237,7 +237,7 @@ public class BaseTypeVisitor Date: Fri, 3 Feb 2023 09:42:16 -0800 Subject: [PATCH 087/113] Checkpoint --- .../guieffect/GuiEffectTypeFactory.java | 7 +- .../common/basetype/BaseTypeVisitor.java | 78 +++++++------ .../basetype/SideEffectsOnlyChecker.java | 105 ++++++++++++------ .../checkerframework/javacutil/TreeUtils.java | 6 +- 4 files changed, 124 insertions(+), 72 deletions(-) diff --git a/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectTypeFactory.java index cfca49f2f3b..8185f6d81e2 100644 --- a/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectTypeFactory.java @@ -269,10 +269,11 @@ public Effect getComputedEffectAtCallsite( Effect targetEffect = getDeclaredEffect(methodElt); if (targetEffect.isPoly()) { AnnotatedTypeMirror srcType = null; - if (node.getMethodSelect().getKind() == Tree.Kind.MEMBER_SELECT) { - ExpressionTree src = ((MemberSelectTree) node.getMethodSelect()).getExpression(); + ExpressionTree methodSelect = node.getMethodSelect(); + if (methodSelect.getKind() == Tree.Kind.MEMBER_SELECT) { + ExpressionTree src = ((MemberSelectTree) methodSelect).getExpression(); srcType = getAnnotatedType(src); - } else if (node.getMethodSelect().getKind() == Tree.Kind.IDENTIFIER) { + } else if (methodSelect.getKind() == Tree.Kind.IDENTIFIER) { // Tree.Kind.IDENTIFIER, e.g. a direct call like "super()" if (callerReceiver == null) { // Not enought information provided to instantiate this type-polymorphic effects diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 68f671d4cf0..afac563baea 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -1004,6 +1004,7 @@ protected void checkPurity(MethodTree node) { // return; // } + // `body` is lazily assigned. TreePath body = null; boolean bodyAssigned = false; @@ -1083,45 +1084,52 @@ protected void checkPurity(MethodTree node) { } if (body == null) { return; - } else { - @Nullable Element methodDeclElem = TreeUtils.elementFromDeclaration(node); - AnnotationMirror sefOnlyAnnotation = - atypeFactory.getDeclAnnotation(methodDeclElem, SideEffectsOnly.class); - if (sefOnlyAnnotation == null) { + } + @Nullable Element methodDeclElem = TreeUtils.elementFromDeclaration(node); + AnnotationMirror sefOnlyAnnotation = + atypeFactory.getDeclAnnotation(methodDeclElem, SideEffectsOnly.class); + if (sefOnlyAnnotation == null) { + return; + } + List sideEffectsOnlyExpressionStrings = + AnnotationUtils.getElementValueArray( + sefOnlyAnnotation, sideEffectsOnlyValueElement, String.class); + List sideEffectsOnlyExpressions = + new ArrayList<>(sideEffectsOnlyExpressionStrings.size()); + for (String st : sideEffectsOnlyExpressionStrings) { + try { + JavaExpression exprJe = StringToJavaExpression.atMethodBody(st, node, checker); + sideEffectsOnlyExpressions.add(exprJe); + } catch (JavaExpressionParseUtil.JavaExpressionParseException ex) { + checker.report(st, ex.getDiagMessage()); return; } - List sideEffectsOnlyExpressionStrings = - AnnotationUtils.getElementValueArray( - sefOnlyAnnotation, sideEffectsOnlyValueElement, String.class); - List sideEffectsOnlyExpressions = new ArrayList<>(); - for (String st : sideEffectsOnlyExpressionStrings) { - try { - JavaExpression exprJe = StringToJavaExpression.atMethodBody(st, node, checker); - sideEffectsOnlyExpressions.add(exprJe); - } catch (JavaExpressionParseUtil.JavaExpressionParseException ex) { - checker.report(st, ex.getDiagMessage()); - return; - } - } + } - if (sideEffectsOnlyExpressions.isEmpty()) { - return; - } + if (sideEffectsOnlyExpressions.isEmpty()) { + return; + } - SideEffectsOnlyChecker.SideEffectsOnlyResult sefOnlyResult = - SideEffectsOnlyChecker.checkSideEffectsOnly( - body, - atypeFactory, - sideEffectsOnlyExpressions, - atypeFactory.getProcessingEnv(), - checker); - - List> seOnlyIncorrectExprs = sefOnlyResult.getSeOnlyResult(); - if (!seOnlyIncorrectExprs.isEmpty()) { - for (Pair s : seOnlyIncorrectExprs) { - if (!sideEffectsOnlyExpressions.contains(s.second)) { - checker.reportError(s.first, "purity.incorrect.sideeffectsonly", s.second.toString()); - } + System.out.printf("sideEffectsOnlyExpressions = %s%n", sideEffectsOnlyExpressions); + + SideEffectsOnlyChecker.ExtraSideEffects sefOnlyResult = + SideEffectsOnlyChecker.checkSideEffectsOnly( + body, + atypeFactory, + sideEffectsOnlyExpressions, + atypeFactory.getProcessingEnv(), + checker); + + System.out.printf("sefOnlyResult = %s%n", sefOnlyResult); + + List> seOnlyIncorrectExprs = sefOnlyResult.getSeOnlyResult(); + System.out.printf("seOnlyIncorrectExprs = %s%n", seOnlyIncorrectExprs); + + if (!seOnlyIncorrectExprs.isEmpty()) { + for (Pair s : seOnlyIncorrectExprs) { + if (!sideEffectsOnlyExpressions.contains(s.second)) { + System.out.printf("Error 2%n"); + checker.reportError(s.first, "purity.incorrect.sideeffectsonly", s.second.toString()); } } } diff --git a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java index cddf782ac98..90086f15d38 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java @@ -4,6 +4,7 @@ import com.sun.source.tree.CatchTree; import com.sun.source.tree.CompoundAssignmentTree; import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.MethodTree; import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; import com.sun.source.tree.UnaryTree; @@ -24,6 +25,7 @@ import org.checkerframework.javacutil.AnnotationProvider; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.Pair; +import org.checkerframework.javacutil.TreePathUtil; import org.checkerframework.javacutil.TreeUtils; /** @@ -32,11 +34,13 @@ */ public class SideEffectsOnlyChecker { - /** Creates a SideEffectsOnlyAnnoChecker. */ - public SideEffectsOnlyChecker() {} + /** Do not instantiate. */ + private SideEffectsOnlyChecker() { + throw new Error("Do not instantiate"); + } /** - * Returns the computed {@code SideEffectsOnlyResult}. + * Returns the computed {@code ExtraSideEffects}. * * @param statement The statement to check * @param annoProvider The annotation provider @@ -44,9 +48,9 @@ public SideEffectsOnlyChecker() {} * values to {@link SideEffectsOnly} * @param processingEnv The processing environment * @param checker The checker to use - * @return SideEffectsOnlyResult returns the result of {@link SideEffectsOnlyChecker} + * @return a ExtraSideEffects */ - public static SideEffectsOnlyResult checkSideEffectsOnly( + public static ExtraSideEffects checkSideEffectsOnly( TreePath statement, AnnotationProvider annoProvider, List sideEffectsOnlyExpressions, @@ -56,41 +60,42 @@ public static SideEffectsOnlyResult checkSideEffectsOnly( new SideEffectsOnlyCheckerHelper( annoProvider, sideEffectsOnlyExpressions, processingEnv, checker); helper.scan(statement, null); - return helper.sideEffectsOnlyResult; + return helper.extraSideEffects; } /** - * Result of the {@link SideEffectsOnlyChecker}. Can be queried to get the list of mutated - * expressions. + * The set of expressions a method side-effects, beyond those specified its {@link + * SideEffectsOnly} annotation. */ - public static class SideEffectsOnlyResult { + public static class ExtraSideEffects { - /** Creates a SideEffectsOnlyResult. */ - public SideEffectsOnlyResult() {} + /** Creates an empty ExtraSideEffects. */ + public ExtraSideEffects() {} /** * List of expressions a method side-effects that are not specified in the list of arguments to * {@link SideEffectsOnly}. */ - protected final List> mutatedExprs = new ArrayList<>(1); + protected final List> exprs = new ArrayList<>(1); /** - * Adds {@code t} and {@code javaExpr} as a Pair to mutatedExprs. + * Adds {@code t} and {@code javaExpr} as a Pair to this. * - * @param t The expression that is mutated - * @param javaExpr The corresponding Java expression that is mutated + * @param t the expression that is mutated + * @param javaExpr the corresponding Java expression */ - public void addMutatedExpr(Tree t, JavaExpression javaExpr) { - mutatedExprs.add(Pair.of(t, javaExpr)); + public void addExpr(Tree t, JavaExpression javaExpr) { + exprs.add(Pair.of(t, javaExpr)); } /** - * Returns {@code mutatedExprs}. + * Returns a list of expressions a method side-effects that are not specified in the list of + * arguments to {@link SideEffectsOnly}. * - * @return mutatedExprs + * @return side-effected expressions, beyond what is in {@code @SideEffectsOnly}. */ - public List> getSeOnlyResult() { - return mutatedExprs; + public List> getExprs() { + return exprs; } } @@ -100,7 +105,7 @@ public List> getSeOnlyResult() { */ protected static class SideEffectsOnlyCheckerHelper extends TreePathScanner { /** Result computed by SideEffectsOnlyCheckerHelper. */ - SideEffectsOnlyResult sideEffectsOnlyResult = new SideEffectsOnlyResult(); + ExtraSideEffects extraSideEffects = new ExtraSideEffects(); /** * List of expressions specified as annotation arguments in {@link SideEffectsOnly} annotation. */ @@ -139,20 +144,58 @@ public Void visitCatch(CatchTree node, Void aVoid) { } @Override + // TODO: Similar logic for NewClassTree? public Void visitMethodInvocation(MethodInvocationTree node, Void aVoid) { - Element treeElem = TreeUtils.elementFromUse(node); - AnnotationMirror pureAnno = annoProvider.getDeclAnnotation(treeElem, Pure.class); + Element invokedElem = TreeUtils.elementFromUse(node); + AnnotationMirror pureAnno = annoProvider.getDeclAnnotation(invokedElem, Pure.class); AnnotationMirror sideEffectFreeAnno = - annoProvider.getDeclAnnotation(treeElem, SideEffectFree.class); - // If the invoked method is annotated as @Pure or @SideEffectFree, nothing to do. + annoProvider.getDeclAnnotation(invokedElem, SideEffectFree.class); + // If the invoked method has no side effects, there is, nothing to do. if (pureAnno != null || sideEffectFreeAnno != null) { return super.visitMethodInvocation(node, aVoid); } - AnnotationMirror sideEffectsOnlyAnno = - annoProvider.getDeclAnnotation(treeElem, SideEffectsOnly.class); - // The invoked method is not annotated with @SideEffectsOnly; report an error. + MethodTree enclosingMethod = + TreePathUtil.enclosingMethod(checker.getTypeFactory().getPath(node)); + ExecutableElement enclosingMethodElement = null; + if (enclosingMethod != null) { + enclosingMethodElement = TreeUtils.elementFromDeclaration(enclosingMethod); + } + AnnotationMirror sideEffectsOnlyAnno = null; + if (enclosingMethodElement != null) { + annoProvider.getDeclAnnotation(enclosingMethodElement, SideEffectsOnly.class); + } + System.out.printf( + "invokedElem = %s, sideEffectsOnlyAnno = %s%n", invokedElem, sideEffectsOnlyAnno); + + // The arguments to @SideEffectsOnly, or an empty list if there is no @SideEffectsOnly. + List sideEffectsOnlyExpressionStrings; + if (sideEffectsOnlyAnno == null) { + sideEffectsOnlyExpressionStrings = Collections.emptyList(); + } else { + ExecutableElement sideEffectsOnlyValueElement = + TreeUtils.getMethod(SideEffectsOnly.class, "value", 0, processingEnv); + sideEffectsOnlyExpressionStrings = + AnnotationUtils.getElementValueArray( + sideEffectsOnlyAnno, sideEffectsOnlyValueElement, String.class); + } + + // TODO: This needs to collect all subexpressions of the given expression. For now it just + // considers the actual arguments, which is incomplete. + List args = node.getArguments(); + ExpressionTree receiver = getReceiverTree(node); + List subexpressions; + if (receiver == null) { + subexpressions = nonReceiverArgs; + } else { + subexpressions = new ArrayList<>(args.size() + 1); + subexpressions.add(receiver); + subexpressions.addAll(args); + } + if (sideEffectsOnlyAnno == null) { + System.out.printf("Error 1%n"); + // For each expression in `node`: checker.reportError(node, "purity.incorrect.sideeffectsonly", node); } else { // The invoked method is annotated with @SideEffectsOnly. @@ -175,7 +218,7 @@ public Void visitMethodInvocation(MethodInvocationTree node, Void aVoid) { for (JavaExpression expr : sideEffectsOnlyExprInv) { if (!sideEffectsOnlyExpressions.contains(expr)) { - sideEffectsOnlyResult.addMutatedExpr(node, expr); + extraSideEffects.addExpr(node, expr); } } } @@ -191,7 +234,7 @@ public Void visitNewClass(NewClassTree node, Void aVoid) { public Void visitAssignment(AssignmentTree node, Void aVoid) { JavaExpression javaExpr = JavaExpression.fromTree(node.getVariable()); if (!sideEffectsOnlyExpressions.contains(javaExpr)) { - sideEffectsOnlyResult.addMutatedExpr(node, javaExpr); + extraSideEffects.addExpr(node, javaExpr); } return super.visitAssignment(node, aVoid); } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java index f1d15441ce4..b007884945a 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java @@ -1003,10 +1003,10 @@ public static boolean isCompileTimeString(ExpressionTree tree) { // Trying to handle receiver calls to trees of the form // ((m).getArray()) // returns the type of 'm' in this case - receiver = ((MethodInvocationTree) expression).getMethodSelect(); + ExpressionTree methodSelect = ((MethodInvocationTree) expression).getMethodSelect(); - if (receiver.getKind() == Tree.Kind.MEMBER_SELECT) { - receiver = ((MemberSelectTree) receiver).getExpression(); + if (methodSelect.getKind() == Tree.Kind.MEMBER_SELECT) { + receiver = ((MemberSelectTree) methodSelect).getExpression(); } else { // It's a method call "m(foo)" without an explicit receiver return null; From 0832758d47410ee28a6eb6c39af04e40dc4fe0db Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sun, 21 Jan 2024 22:55:17 -0800 Subject: [PATCH 088/113] Tweak definition of `@SideEffectsOnly` --- .../checkerframework/dataflow/qual/SideEffectsOnly.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java index 4370c556470..ab6a7dd1718 100644 --- a/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java +++ b/checker-qual/src/main/java/org/checkerframework/dataflow/qual/SideEffectsOnly.java @@ -8,9 +8,9 @@ import org.checkerframework.framework.qual.JavaExpression; /** - * A method annotated with the declaration annotation {@code @SideEffectsOnly("A", "B")} performs - * side-effects on at most the expressions A and B. All other expressions have the same value before - * and after a call. + * A method annotated with the declaration annotation {@code @SideEffectsOnly("A", "B")} changes the + * value of at most the expressions A and B. All other expressions have the same value before and + * after a call to the method. * * @checker_framework.manual #type-refinement-purity Specifying side effects */ @@ -19,7 +19,7 @@ @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) public @interface SideEffectsOnly { /** - * An upper bound on the expressions that this method side effects. + * An upper bound on the expressions that this method might change the value of. * * @return the Java expressions that the annotated method might side-effect * @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions From 03bca3f27d228859ee65dbb1f22e0f13487cdded Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sun, 21 Jan 2024 23:53:44 -0800 Subject: [PATCH 089/113] Reduce diffs with upstream --- .../common/basetype/BaseTypeVisitor.java | 82 +++++++++++-------- 1 file changed, 50 insertions(+), 32 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 1ceae16e6e1..5102f0d116a 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -212,14 +212,17 @@ public class BaseTypeVisitor param return super.visitLocalVariable(localVarExpr, parameters); } }; + /** * Check that the parameters used in {@code javaExpression} are effectively final for method * {@code method}. @@ -2745,6 +2756,7 @@ private List supportedAnnoTrees(List a /** Cache to avoid calling {@link #getExceptionParameterLowerBoundAnnotations} more than once. */ private @MonotonicNonNull Set getExceptionParameterLowerBoundAnnotationsCache; + /** * Returns a set of AnnotationMirrors that is a lower bound for exception parameters. The same as * {@link #getExceptionParameterLowerBoundAnnotations}, but uses a cache. @@ -3853,19 +3865,25 @@ public class OverrideChecker { /** The declaration of an overriding method. */ protected final Tree overriderTree; + /** True if {@link #overriderTree} is a MEMBER_REFERENCE. */ protected final boolean isMethodReference; /** The type of the overriding method. */ protected final AnnotatedExecutableType overrider; + /** The subtype that declares the overriding method. */ protected final AnnotatedTypeMirror overriderType; + /** The type of the overridden method. */ protected final AnnotatedExecutableType overridden; + /** The supertype that declares the overridden method. */ protected final AnnotatedDeclaredType overriddenType; + /** The teturn type of the overridden method. */ protected final AnnotatedTypeMirror overriddenReturnType; + /** The return type of the overriding method. */ protected final AnnotatedTypeMirror overriderReturnType; From 8c56e182d6274d7dbba1634464ff99f79b5132af Mon Sep 17 00:00:00 2001 From: James Yoo Date: Fri, 5 Apr 2024 10:10:30 -0700 Subject: [PATCH 090/113] Issue warning on `@SideEffectsOnly({})` --- .../checkerframework/common/basetype/BaseTypeVisitor.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 5b8a2c2b46d..aa75ad79abf 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -1201,11 +1201,12 @@ protected void checkPurityAnnotations(MethodTree tree) { } if (sideEffectsOnlyExpressions.isEmpty()) { + // A @SideEffectsOnly annotation with an empty expression array is equivalent to + // a @SideEffectFree annotation. + checker.reportWarning(tree, "purity.more.sideeffectfree", tree.getName()); return; } - System.out.printf("sideEffectsOnlyExpressions = %s%n", sideEffectsOnlyExpressions); - SideEffectsOnlyChecker.ExtraSideEffects sefOnlyResult = SideEffectsOnlyChecker.checkSideEffectsOnly( body, From 31d1b43675d52d7216a1292861fb69839b555688 Mon Sep 17 00:00:00 2001 From: James Yoo Date: Fri, 5 Apr 2024 10:22:25 -0700 Subject: [PATCH 091/113] Update manual guidance for `@SideEffectsOnly({})` --- docs/manual/advanced-features.tex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/manual/advanced-features.tex b/docs/manual/advanced-features.tex index 55835797abc..1bded28997b 100644 --- a/docs/manual/advanced-features.tex +++ b/docs/manual/advanced-features.tex @@ -1140,7 +1140,9 @@ A method cannot be annotated with both \refqualclass{dataflow/qual}{SideEffectFree} and \refqualclass{dataflow/qual}{SideEffectsOnly}. -The annotation \<@SideEffectsOnly(\{\})> is equivalent to \<@SideEffectFree>. +The annotation \<@SideEffectsOnly(\{\})> (i.e., a \<@SideEffectsOnly> annotation +with an empty list of expressions) is equivalent to \<@SideEffectFree>. +Programmers should favor writing \<@SideEffectFree> in this case for clarity. If a method has no \refqualclass{dataflow/qual}{SideEffectsOnly} or \refqualclass{dataflow/qual}{SideEffectFree} annotation, then the Checker Framework assumes that the method may side-effect any expressions (including fields and arguments). From a9fccf6cc900c78d09231cd544fcc117213ed811 Mon Sep 17 00:00:00 2001 From: James Yoo Date: Mon, 8 Apr 2024 12:41:07 -0700 Subject: [PATCH 092/113] Add test case for empty `@SideEffectsOnly` --- .../tests/purity-suggestions/PuritySuggestionsClass.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/framework/tests/purity-suggestions/PuritySuggestionsClass.java b/framework/tests/purity-suggestions/PuritySuggestionsClass.java index f1e1d70702b..45836232a4e 100644 --- a/framework/tests/purity-suggestions/PuritySuggestionsClass.java +++ b/framework/tests/purity-suggestions/PuritySuggestionsClass.java @@ -1,5 +1,6 @@ import org.checkerframework.dataflow.qual.Deterministic; import org.checkerframework.dataflow.qual.Pure; +import org.checkerframework.dataflow.qual.SideEffectsOnly; // various tests for the checker to automatically suggest pure methods (most methods have been // copied from Purity.java) @@ -171,4 +172,12 @@ String t12() { NonPureClass p = new NonPureClass(); return ""; } + + // Class with a method annotated with @SideEffectsOnly({}) + private static class EmptySideEffectsOnly { + + @SideEffectsOnly({}) + // :: warning: (purity.more.sideeffectfree) + void foo() {} + } } From 98c1b5dbb452512f14a9b2df8434d0fc23f4f56a Mon Sep 17 00:00:00 2001 From: James Yoo Date: Mon, 8 Apr 2024 13:55:26 -0700 Subject: [PATCH 093/113] Update tests --- framework/tests/sideeffectsonly/EmptySideEffectsOnly.java | 2 ++ framework/tests/sideeffectsonly/SideEffectsOnlyEmpty.java | 1 + 2 files changed, 3 insertions(+) diff --git a/framework/tests/sideeffectsonly/EmptySideEffectsOnly.java b/framework/tests/sideeffectsonly/EmptySideEffectsOnly.java index 3eddf6ac1e9..4d537c9104c 100644 --- a/framework/tests/sideeffectsonly/EmptySideEffectsOnly.java +++ b/framework/tests/sideeffectsonly/EmptySideEffectsOnly.java @@ -17,8 +17,10 @@ void test(Object x) { void method(Object x) {} @SideEffectsOnly({}) + // :: warning: (purity.more.sideeffectfree) void method1(@SideEffectsOnlyToyBottom Object x) {} @SideEffectsOnly({}) + // :: warning: (purity.more.sideeffectfree) void method2(@SideEffectsOnlyToyBottom Object x) {} } diff --git a/framework/tests/sideeffectsonly/SideEffectsOnlyEmpty.java b/framework/tests/sideeffectsonly/SideEffectsOnlyEmpty.java index ffcb9430d89..cfbcf661920 100644 --- a/framework/tests/sideeffectsonly/SideEffectsOnlyEmpty.java +++ b/framework/tests/sideeffectsonly/SideEffectsOnlyEmpty.java @@ -17,6 +17,7 @@ void test(Object x) { void method(Object x) {} @SideEffectsOnly({}) + // :: warning: (purity.more.sideeffectfree) void method1(@SideEffectsOnlyToyBottom Object x) {} void method2(@SideEffectsOnlyToyBottom Object x) {} From 3afb83057d2991a62849749f4920aa4198627ce7 Mon Sep 17 00:00:00 2001 From: James Yoo Date: Thu, 11 Apr 2024 15:41:38 -0700 Subject: [PATCH 094/113] Apply lint --- .../common/basetype/BaseTypeVisitor.java | 6 +- .../basetype/SideEffectsOnlyChecker.java | 172 +++++++++++------- 2 files changed, 108 insertions(+), 70 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index aa75ad79abf..41b260a4553 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -1215,15 +1215,15 @@ protected void checkPurityAnnotations(MethodTree tree) { atypeFactory.getProcessingEnv(), checker); - System.out.printf("sefOnlyResult = %s%n", sefOnlyResult); + // System.out.printf("sefOnlyResult = %s%n", sefOnlyResult); List> seOnlyIncorrectExprs = sefOnlyResult.getExprs(); - System.out.printf("seOnlyIncorrectExprs = %s%n", seOnlyIncorrectExprs); + // System.out.printf("seOnlyIncorrectExprs = %s%n", seOnlyIncorrectExprs); if (!seOnlyIncorrectExprs.isEmpty()) { for (IPair s : seOnlyIncorrectExprs) { if (!sideEffectsOnlyExpressions.contains(s.second)) { - System.out.printf("Error 2%n"); + // System.out.printf("Error 2%n"); checker.reportError(s.first, "purity.incorrect.sideeffectsonly", s.second.toString()); } } diff --git a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java index aaa9925e038..f4b437357ba 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java @@ -1,7 +1,6 @@ package org.checkerframework.common.basetype; import com.sun.source.tree.AssignmentTree; -import com.sun.source.tree.CatchTree; import com.sun.source.tree.CompoundAssignmentTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.MethodInvocationTree; @@ -18,6 +17,7 @@ import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; +import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.expression.JavaExpression; import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.dataflow.qual.SideEffectFree; @@ -143,90 +143,128 @@ public SideEffectsOnlyCheckerHelper( this.checker = checker; } - @Override - public Void visitCatch(CatchTree node, Void aVoid) { - return super.visitCatch(node, aVoid); - } - @Override // TODO: Similar logic for NewClassTree? public Void visitMethodInvocation(MethodInvocationTree node, Void aVoid) { Element invokedElem = TreeUtils.elementFromUse(node); - AnnotationMirror pureAnno = annoProvider.getDeclAnnotation(invokedElem, Pure.class); - AnnotationMirror sideEffectFreeAnno = - annoProvider.getDeclAnnotation(invokedElem, SideEffectFree.class); + boolean isMarkedPure = annoProvider.getDeclAnnotation(invokedElem, Pure.class) != null; + boolean isMarkedSideEffectFree = + annoProvider.getDeclAnnotation(invokedElem, SideEffectFree.class) != null; // If the invoked method has no side effects, there is, nothing to do. - if (pureAnno != null || sideEffectFreeAnno != null) { + if (isMarkedPure || isMarkedSideEffectFree) { return super.visitMethodInvocation(node, aVoid); } - MethodTree enclosingMethod = - TreePathUtil.enclosingMethod(checker.getTypeFactory().getPath(node)); - ExecutableElement enclosingMethodElement = null; - if (enclosingMethod != null) { - enclosingMethodElement = TreeUtils.elementFromDeclaration(enclosingMethod); - } - AnnotationMirror sideEffectsOnlyAnno = null; - if (enclosingMethodElement != null) { - annoProvider.getDeclAnnotation(enclosingMethodElement, SideEffectsOnly.class); + AnnotationMirror sideEffectsOnlyAnnotation = + getSideEffectsOnlyAnnotationOnEnclosingMethod(node); + assert sideEffectsOnlyAnnotation != null + : "This method should only be invoked when the @SideEffectsOnly annotation is not null"; + // System.out.printf( + // "invokedElem = %s, sideEffectsOnlyAnno = %s%n", invokedElem, sideEffectsOnlyAnno); + + // The arguments to @SideEffectsOnly, or an empty list if there is no @SideEffectsOnly. + List sideEffectsOnlyExpressionStrings = + getExpressionStringsFromSideEffectsOnly(sideEffectsOnlyAnnotation); + + List args = this.getArgumentsFromMethodInvocation(node); + + System.out.printf("Method Invocation args = %s\n", args); + System.out.printf( + "Side Effects Only Expression Strings = %s\n", sideEffectsOnlyExpressionStrings); + // The invoked method is annotated with @SideEffectsOnly. + // Add annotation values to seOnlyIncorrectExprs + // that are not present in sideEffectsOnlyExpressions. + /* TODO + ExecutableElement sideEffectsOnlyValueElement = + TreeUtils.getMethod(SideEffectsOnly.class, "value", 0, processingEnv); + */ + + MethodTree enclosingMethodBody = TreePathUtil.enclosingMethod(this.getCurrentPath()); + + List sideEffectsOnlyExprInv = new ArrayList<>(); + for (String st : sideEffectsOnlyExpressionStrings) { + try { + System.out.printf("Calling StrToJExpr(st = %s, node = %s)\n", st, node); + // JavaExpression exprJe = StringToJavaExpression.atMethodInvocation(st, node, + // checker); // This code wasn't being invoked before + JavaExpression exprJe = + StringToJavaExpression.atMethodBody(st, enclosingMethodBody, checker); + sideEffectsOnlyExprInv.add(exprJe); + } catch (JavaExpressionParseUtil.JavaExpressionParseException ex) { + checker.report(st, ex.getDiagMessage()); + } } + System.out.printf( - "invokedElem = %s, sideEffectsOnlyAnno = %s%n", invokedElem, sideEffectsOnlyAnno); + "Permissible @SideEffectsOnlyExpressions = %s\n", sideEffectsOnlyExpressions); + System.out.printf("sideEffectsOnlyExprInv = %s\n", sideEffectsOnlyExprInv); - // The arguments to @SideEffectsOnly, or an empty list if there is no @SideEffectsOnly. - List sideEffectsOnlyExpressionStrings; - if (sideEffectsOnlyAnno == null) { - sideEffectsOnlyExpressionStrings = Collections.emptyList(); - } else { - ExecutableElement sideEffectsOnlyValueElement = - TreeUtils.getMethod(SideEffectsOnly.class, "value", 0, processingEnv); - sideEffectsOnlyExpressionStrings = - AnnotationUtils.getElementValueArray( - sideEffectsOnlyAnno, sideEffectsOnlyValueElement, String.class); + for (JavaExpression expr : sideEffectsOnlyExprInv) { + if (!sideEffectsOnlyExpressions.contains(expr)) { + extraSideEffects.addExpr(node, expr); + } + } + System.out.printf("Extra side effects = %s\n", extraSideEffects.getExprs()); + return super.visitMethodInvocation(node, aVoid); + } + + /** + * Returns the {@link SideEffectsOnly} annotation (if it exists, else null) on the enclosing + * method of a given method invocation. + * + * @param tree the method invocation + * @return the {@link SideEffectsOnly} annotation on the enclosing method of a given method + * invocation + */ + private AnnotationMirror getSideEffectsOnlyAnnotationOnEnclosingMethod( + MethodInvocationTree tree) { + MethodTree enclosingMethod = + TreePathUtil.enclosingMethod(checker.getTypeFactory().getPath(tree)); + if (enclosingMethod == null) { + return null; + } + return annoProvider.getDeclAnnotation( + TreeUtils.elementFromDeclaration(enclosingMethod), SideEffectsOnly.class); + } + + /** + * Extracts the expressions that are passed as arguments to a {@link SideEffectsOnly} + * annotation. + * + * @param sideEffectsOnlyAnnotation the {@link SideEffectsOnly} annotation + * @return the expressions that are passed as arguments to the {@link SideEffectsOnly} + * annotation + */ + private List getExpressionStringsFromSideEffectsOnly( + @Nullable AnnotationMirror sideEffectsOnlyAnnotation) { + if (sideEffectsOnlyAnnotation == null) { + return Collections.emptyList(); } + System.out.printf("@SideEffectsOnlyAnnotation = %s\n", sideEffectsOnlyAnnotation); + ExecutableElement sideEffectsOnlyValueElement = + TreeUtils.getMethod(SideEffectsOnly.class, "value", 0, processingEnv); + return AnnotationUtils.getElementValueArray( + sideEffectsOnlyAnnotation, + sideEffectsOnlyValueElement, + String.class, + Collections.emptyList()); + } + private List getArgumentsFromMethodInvocation( + MethodInvocationTree methodInvok) { // TODO: This needs to collect all subexpressions of the given expression. For now it just // considers the actual arguments, which is incomplete. - List args = node.getArguments(); - ExpressionTree receiver = TreeUtils.getReceiverTree(node); - List subexpressions; + List args = methodInvok.getArguments(); + ExpressionTree receiver = TreeUtils.getReceiverTree(methodInvok); + List exprs; if (receiver == null) { - subexpressions = new ArrayList<>(args); - } else { - subexpressions = new ArrayList<>(args.size() + 1); - subexpressions.add(receiver); - subexpressions.addAll(args); - } - - if (sideEffectsOnlyAnno == null) { - System.out.printf("Error 1%n"); - // For each expression in `node`: - checker.reportError(node, "purity.incorrect.sideeffectsonly", node); + exprs = new ArrayList<>(args); } else { - // The invoked method is annotated with @SideEffectsOnly. - // Add annotation values to seOnlyIncorrectExprs - // that are not present in sideEffectsOnlyExpressions. - /* TODO - ExecutableElement sideEffectsOnlyValueElement = - TreeUtils.getMethod(SideEffectsOnly.class, "value", 0, processingEnv); - */ - List sideEffectsOnlyExprInv = new ArrayList<>(); - for (String st : sideEffectsOnlyExpressionStrings) { - try { - JavaExpression exprJe = StringToJavaExpression.atMethodInvocation(st, node, checker); - sideEffectsOnlyExprInv.add(exprJe); - } catch (JavaExpressionParseUtil.JavaExpressionParseException ex) { - checker.report(st, ex.getDiagMessage()); - } - } - - for (JavaExpression expr : sideEffectsOnlyExprInv) { - if (!sideEffectsOnlyExpressions.contains(expr)) { - extraSideEffects.addExpr(node, expr); - } - } + exprs = new ArrayList<>(args.size() + 1); + exprs.add(receiver); + exprs.addAll(args); } - return super.visitMethodInvocation(node, aVoid); + return exprs; } @Override From 2f7355be11a317cb4a28ff099c2ceb7f52949f6c Mon Sep 17 00:00:00 2001 From: James Yoo Date: Fri, 12 Apr 2024 10:35:21 -0700 Subject: [PATCH 095/113] Refactor `SideEffectsOnlyChecker#visitMethodInvocation` and pass more tests --- .../basetype/SideEffectsOnlyChecker.java | 94 ++++--------------- 1 file changed, 18 insertions(+), 76 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java index f4b437357ba..b5d02e138a5 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java @@ -11,21 +11,16 @@ import com.sun.source.util.TreePath; import com.sun.source.util.TreePathScanner; import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.expression.JavaExpression; import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.dataflow.qual.SideEffectFree; import org.checkerframework.dataflow.qual.SideEffectsOnly; -import org.checkerframework.framework.util.JavaExpressionParseUtil; -import org.checkerframework.framework.util.StringToJavaExpression; import org.checkerframework.javacutil.AnnotationProvider; -import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TreePathUtil; import org.checkerframework.javacutil.TreeUtils; import org.plumelib.util.IPair; @@ -112,7 +107,7 @@ protected static class SideEffectsOnlyCheckerHelper extends TreePathScanner sideEffectsOnlyExpressions; + List sideEffectsOnlyExpressionsFromAnnotation; /** The annotation provider. */ protected final AnnotationProvider annoProvider; @@ -138,19 +133,17 @@ public SideEffectsOnlyCheckerHelper( ProcessingEnvironment processingEnv, BaseTypeChecker checker) { this.annoProvider = annoProvider; - this.sideEffectsOnlyExpressions = sideEffectsOnlyExpressions; + this.sideEffectsOnlyExpressionsFromAnnotation = sideEffectsOnlyExpressions; this.processingEnv = processingEnv; this.checker = checker; } @Override - // TODO: Similar logic for NewClassTree? public Void visitMethodInvocation(MethodInvocationTree node, Void aVoid) { Element invokedElem = TreeUtils.elementFromUse(node); boolean isMarkedPure = annoProvider.getDeclAnnotation(invokedElem, Pure.class) != null; boolean isMarkedSideEffectFree = annoProvider.getDeclAnnotation(invokedElem, SideEffectFree.class) != null; - // If the invoked method has no side effects, there is, nothing to do. if (isMarkedPure || isMarkedSideEffectFree) { return super.visitMethodInvocation(node, aVoid); } @@ -159,52 +152,18 @@ public Void visitMethodInvocation(MethodInvocationTree node, Void aVoid) { getSideEffectsOnlyAnnotationOnEnclosingMethod(node); assert sideEffectsOnlyAnnotation != null : "This method should only be invoked when the @SideEffectsOnly annotation is not null"; - // System.out.printf( - // "invokedElem = %s, sideEffectsOnlyAnno = %s%n", invokedElem, sideEffectsOnlyAnno); - // The arguments to @SideEffectsOnly, or an empty list if there is no @SideEffectsOnly. - List sideEffectsOnlyExpressionStrings = - getExpressionStringsFromSideEffectsOnly(sideEffectsOnlyAnnotation); + List actualSideEffectedExprs = + this.getJavaExpressionsFromMethodInvocation(node); - List args = this.getArgumentsFromMethodInvocation(node); - - System.out.printf("Method Invocation args = %s\n", args); - System.out.printf( - "Side Effects Only Expression Strings = %s\n", sideEffectsOnlyExpressionStrings); - // The invoked method is annotated with @SideEffectsOnly. - // Add annotation values to seOnlyIncorrectExprs - // that are not present in sideEffectsOnlyExpressions. - /* TODO - ExecutableElement sideEffectsOnlyValueElement = - TreeUtils.getMethod(SideEffectsOnly.class, "value", 0, processingEnv); - */ - - MethodTree enclosingMethodBody = TreePathUtil.enclosingMethod(this.getCurrentPath()); - - List sideEffectsOnlyExprInv = new ArrayList<>(); - for (String st : sideEffectsOnlyExpressionStrings) { - try { - System.out.printf("Calling StrToJExpr(st = %s, node = %s)\n", st, node); - // JavaExpression exprJe = StringToJavaExpression.atMethodInvocation(st, node, - // checker); // This code wasn't being invoked before - JavaExpression exprJe = - StringToJavaExpression.atMethodBody(st, enclosingMethodBody, checker); - sideEffectsOnlyExprInv.add(exprJe); - } catch (JavaExpressionParseUtil.JavaExpressionParseException ex) { - checker.report(st, ex.getDiagMessage()); - } - } - - System.out.printf( - "Permissible @SideEffectsOnlyExpressions = %s\n", sideEffectsOnlyExpressions); - System.out.printf("sideEffectsOnlyExprInv = %s\n", sideEffectsOnlyExprInv); - - for (JavaExpression expr : sideEffectsOnlyExprInv) { - if (!sideEffectsOnlyExpressions.contains(expr)) { + for (JavaExpression expr : actualSideEffectedExprs) { + // The check for JavaExpression.isUnmodifiableByOtherCode() is required to filter out values + // that cannot be modified (e.g., Integer). + if (!sideEffectsOnlyExpressionsFromAnnotation.contains(expr) + && !expr.isUnmodifiableByOtherCode()) { extraSideEffects.addExpr(node, expr); } } - System.out.printf("Extra side effects = %s\n", extraSideEffects.getExprs()); return super.visitMethodInvocation(node, aVoid); } @@ -228,32 +187,15 @@ private AnnotationMirror getSideEffectsOnlyAnnotationOnEnclosingMethod( } /** - * Extracts the expressions that are passed as arguments to a {@link SideEffectsOnly} - * annotation. + * Returns the arguments to a method invocation, including the receiver. * - * @param sideEffectsOnlyAnnotation the {@link SideEffectsOnly} annotation - * @return the expressions that are passed as arguments to the {@link SideEffectsOnly} - * annotation + * @param methodInvok a method invocation + * @return the arguments to a method invocation, including the receiver */ - private List getExpressionStringsFromSideEffectsOnly( - @Nullable AnnotationMirror sideEffectsOnlyAnnotation) { - if (sideEffectsOnlyAnnotation == null) { - return Collections.emptyList(); - } - System.out.printf("@SideEffectsOnlyAnnotation = %s\n", sideEffectsOnlyAnnotation); - ExecutableElement sideEffectsOnlyValueElement = - TreeUtils.getMethod(SideEffectsOnly.class, "value", 0, processingEnv); - return AnnotationUtils.getElementValueArray( - sideEffectsOnlyAnnotation, - sideEffectsOnlyValueElement, - String.class, - Collections.emptyList()); - } - - private List getArgumentsFromMethodInvocation( + private List getJavaExpressionsFromMethodInvocation( MethodInvocationTree methodInvok) { - // TODO: This needs to collect all subexpressions of the given expression. For now it just - // considers the actual arguments, which is incomplete. + // TODO: collect all subexpressions of the given expression. For now it just considers the + // actual arguments, which is incomplete. List args = methodInvok.getArguments(); ExpressionTree receiver = TreeUtils.getReceiverTree(methodInvok); List exprs; @@ -264,7 +206,7 @@ private List getArgumentsFromMethodInvocation( exprs.add(receiver); exprs.addAll(args); } - return exprs; + return exprs.stream().map(JavaExpression::fromTree).collect(Collectors.toList()); } @Override @@ -275,7 +217,7 @@ public Void visitNewClass(NewClassTree node, Void aVoid) { @Override public Void visitAssignment(AssignmentTree node, Void aVoid) { JavaExpression javaExpr = JavaExpression.fromTree(node.getVariable()); - if (!sideEffectsOnlyExpressions.contains(javaExpr)) { + if (!sideEffectsOnlyExpressionsFromAnnotation.contains(javaExpr)) { extraSideEffects.addExpr(node, javaExpr); } return super.visitAssignment(node, aVoid); From b2fb30be29d1be02afb4771364f4aa4f902c3e0e Mon Sep 17 00:00:00 2001 From: James Yoo Date: Fri, 12 Apr 2024 11:30:43 -0700 Subject: [PATCH 096/113] Fix failing test --- .../common/basetype/SideEffectsOnlyChecker.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java index b5d02e138a5..e318585ffc8 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java @@ -148,14 +148,27 @@ public Void visitMethodInvocation(MethodInvocationTree node, Void aVoid) { return super.visitMethodInvocation(node, aVoid); } - AnnotationMirror sideEffectsOnlyAnnotation = + AnnotationMirror sideEffectsOnlyAnnotationOnEnclosingMethod = getSideEffectsOnlyAnnotationOnEnclosingMethod(node); - assert sideEffectsOnlyAnnotation != null + assert sideEffectsOnlyAnnotationOnEnclosingMethod != null : "This method should only be invoked when the @SideEffectsOnly annotation is not null"; + boolean isInvokedMethodMarkedWithSideEffectsOnly = + annoProvider.getDeclAnnotation(invokedElem, SideEffectsOnly.class) != null; + List actualSideEffectedExprs = this.getJavaExpressionsFromMethodInvocation(node); + // If the invoked method is NOT marked with @SideEffectsOnly, it may modify anything + if (!isInvokedMethodMarkedWithSideEffectsOnly) { + // What does it modify? Check the arguments for the method invocation + if (actualSideEffectedExprs.isEmpty()) { + // If the args is empty, it might be modifying anything + checker.reportError( + node, "purity.incorrect.sideeffectsonly", sideEffectsOnlyExpressionsFromAnnotation); + } + } + for (JavaExpression expr : actualSideEffectedExprs) { // The check for JavaExpression.isUnmodifiableByOtherCode() is required to filter out values // that cannot be modified (e.g., Integer). From 9f9e0055b802f58c25c1c0fb41dd1c5676ec42e1 Mon Sep 17 00:00:00 2001 From: James Yoo Date: Fri, 12 Apr 2024 16:39:48 -0700 Subject: [PATCH 097/113] Make behaviour consistent with manual when `@SideEffectsOnly` appears with `@Pure` or `@SideEffectFree` --- .../common/basetype/BaseTypeVisitor.java | 20 +++++++++++++++++ .../basetype/SideEffectsOnlyChecker.java | 2 +- .../common/basetype/messages.properties | 1 + ...SideEffectsOnlyConflictingAnnotations.java | 22 +++++++++++++++++++ 4 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 framework/tests/sideeffectsonly/SideEffectsOnlyConflictingAnnotations.java diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 41b260a4553..08996bc1cf1 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -1185,6 +1185,18 @@ protected void checkPurityAnnotations(MethodTree tree) { if (sefOnlyAnnotation == null) { return; } + AnnotationMirror pureOrSideEffectFreeAnnotation = + getPureOrSideEffectFreeAnnotation(methodDeclElem); + if (pureOrSideEffectFreeAnnotation != null) { + // It is an error if a @SideEffectsOnly annotation appears with a @Pure or @SideEffectFree + // annotation + checker.reportError( + tree, + "purity.incorrect.annotation.conflict", + tree.getName(), + pureOrSideEffectFreeAnnotation); + return; + } List sideEffectsOnlyExpressionStrings = AnnotationUtils.getElementValueArray( sefOnlyAnnotation, sideEffectsOnlyValueElement, String.class); @@ -1240,6 +1252,14 @@ protected void checkPurityAnnotations(MethodTree tree) { // ... } + private @Nullable AnnotationMirror getPureOrSideEffectFreeAnnotation(Element methodDeclaration) { + AnnotationMirror pureAnnotation = atypeFactory.getDeclAnnotation(methodDeclaration, Pure.class); + if (pureAnnotation != null) { + return pureAnnotation; + } + return atypeFactory.getDeclAnnotation(methodDeclaration, SideEffectFree.class); + } + /** * Infer a purity annotation for {@code elt} by converting {@code kinds} into a method annotation. * diff --git a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java index e318585ffc8..5d3453f2477 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java @@ -163,7 +163,7 @@ public Void visitMethodInvocation(MethodInvocationTree node, Void aVoid) { if (!isInvokedMethodMarkedWithSideEffectsOnly) { // What does it modify? Check the arguments for the method invocation if (actualSideEffectedExprs.isEmpty()) { - // If the args is empty, it might be modifying anything + // If the args are empty, it might be modifying anything checker.reportError( node, "purity.incorrect.sideeffectsonly", sideEffectsOnlyExpressionsFromAnnotation); } diff --git a/framework/src/main/java/org/checkerframework/common/basetype/messages.properties b/framework/src/main/java/org/checkerframework/common/basetype/messages.properties index 239c4d454bb..7222f819d91 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/messages.properties +++ b/framework/src/main/java/org/checkerframework/common/basetype/messages.properties @@ -81,6 +81,7 @@ purity.more.deterministic=the method %s could be declared as @Deterministic purity.more.pure=the method %s could be declared as @Pure purity.more.sideeffectfree=the method %s could be declared as @SideEffectFree purity.incorrect.sideeffectsonly=the method side-effects %s +purity.incorrect.annotation.conflict=the method %s cannot be annotated with both @SideEffectsOnly and %s flowexpr.parse.index.too.big=the method does not have a parameter %s flowexpr.parse.error=cannot parse the expression %s diff --git a/framework/tests/sideeffectsonly/SideEffectsOnlyConflictingAnnotations.java b/framework/tests/sideeffectsonly/SideEffectsOnlyConflictingAnnotations.java new file mode 100644 index 00000000000..8384d2b8fbc --- /dev/null +++ b/framework/tests/sideeffectsonly/SideEffectsOnlyConflictingAnnotations.java @@ -0,0 +1,22 @@ +import java.util.Collection; +import org.checkerframework.dataflow.qual.Pure; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.dataflow.qual.SideEffectsOnly; + +public class SideEffectsOnlyConflictingAnnotations { + + @SideEffectsOnly("#1") + @SideEffectFree + // :: error: (purity.incorrect.annotation.conflict) + void test1(Collection first) { + // :: error: (purity.not.sideeffectfree.call) + first.add(1); + } + + @SideEffectsOnly("#2") + @Pure + // :: error: (purity.incorrect.annotation.conflict) + int test2(Collection first, Collection second) { + return 1; + } +} From 3bea5d887cd7887253bc7235aa638b88b0e29ae5 Mon Sep 17 00:00:00 2001 From: James Yoo Date: Fri, 12 Apr 2024 16:42:34 -0700 Subject: [PATCH 098/113] Add Javadoc --- .../checkerframework/common/basetype/BaseTypeVisitor.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 08996bc1cf1..8fd372d0334 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -1252,6 +1252,14 @@ protected void checkPurityAnnotations(MethodTree tree) { // ... } + /** + * Return either the {@link Pure} or {@link SideEffectFree} annotation (in that order) if either + * appears on a method declaration. + * + * @param methodDeclaration the method declaration + * @return either the {@link Pure} or {@link SideEffectFree} annotation (in that order) if either + * appears on a method declaration + */ private @Nullable AnnotationMirror getPureOrSideEffectFreeAnnotation(Element methodDeclaration) { AnnotationMirror pureAnnotation = atypeFactory.getDeclAnnotation(methodDeclaration, Pure.class); if (pureAnnotation != null) { From 7b1f25689e9abd53f04c1ba16fd6fe574d3e2627 Mon Sep 17 00:00:00 2001 From: James Yoo Date: Fri, 12 Apr 2024 22:26:31 -0700 Subject: [PATCH 099/113] Add naive implementation for aliasing --- .../basetype/SideEffectsOnlyChecker.java | 55 +++++++++++++++---- .../NestedSideEffectsNoAliasing.java | 22 ++++++++ .../NestedSideEffectsWithAliasing.java | 22 ++++++++ 3 files changed, 88 insertions(+), 11 deletions(-) create mode 100644 framework/tests/sideeffectsonly/NestedSideEffectsNoAliasing.java create mode 100644 framework/tests/sideeffectsonly/NestedSideEffectsWithAliasing.java diff --git a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java index 5d3453f2477..8bee4d0d1df 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java @@ -8,10 +8,13 @@ import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; import com.sun.source.tree.UnaryTree; +import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; import com.sun.source.util.TreePathScanner; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; @@ -97,8 +100,8 @@ public List> getExprs() { } /** - * Class that visits that visits various nodes and computes mutated expressions that are not - * specified as annotation values to {@link SideEffectsOnly}. + * Class that visits various nodes and computes mutated expressions that are not specified as + * annotation values to {@link SideEffectsOnly}. */ protected static class SideEffectsOnlyCheckerHelper extends TreePathScanner { /** Result computed by SideEffectsOnlyCheckerHelper. */ @@ -109,6 +112,9 @@ protected static class SideEffectsOnlyCheckerHelper extends TreePathScanner sideEffectsOnlyExpressionsFromAnnotation; + /** Map of expressions that are aliased to other expressions. */ + Map aliasedExpressions; + /** The annotation provider. */ protected final AnnotationProvider annoProvider; @@ -132,6 +138,7 @@ public SideEffectsOnlyCheckerHelper( List sideEffectsOnlyExpressions, ProcessingEnvironment processingEnv, BaseTypeChecker checker) { + this.aliasedExpressions = new HashMap<>(); this.annoProvider = annoProvider; this.sideEffectsOnlyExpressionsFromAnnotation = sideEffectsOnlyExpressions; this.processingEnv = processingEnv; @@ -168,15 +175,9 @@ public Void visitMethodInvocation(MethodInvocationTree node, Void aVoid) { node, "purity.incorrect.sideeffectsonly", sideEffectsOnlyExpressionsFromAnnotation); } } - - for (JavaExpression expr : actualSideEffectedExprs) { - // The check for JavaExpression.isUnmodifiableByOtherCode() is required to filter out values - // that cannot be modified (e.g., Integer). - if (!sideEffectsOnlyExpressionsFromAnnotation.contains(expr) - && !expr.isUnmodifiableByOtherCode()) { - extraSideEffects.addExpr(node, expr); - } - } + actualSideEffectedExprs.stream() + .filter(this::isAdditionalSideEffectedExpression) + .forEach(expr -> extraSideEffects.addExpr(node, expr)); return super.visitMethodInvocation(node, aVoid); } @@ -222,6 +223,30 @@ private List getJavaExpressionsFromMethodInvocation( return exprs.stream().map(JavaExpression::fromTree).collect(Collectors.toList()); } + /** + * Returns true if the given expression is a side-effected expression beyond what is described + * in the {@link SideEffectsOnly} annotation. + * + *

    The following criteria must be met in determining whether a given expression is + * side-effected beyond the expressions provided in the annotation: + * + *

      + *
    • The expression does bold appear in the expressions passed as arguments to the + * {@link SideEffectsOnly} annotation + *
    • The expression is modifiable by other code + *
    • The expression is not aliased by other variables in the method body + *
    + * + * @param expr the expression to check for side-effecting + * @return true if the given expression is a side-effected expressio beyond what is described in + * the {@link SideEffectsOnly} annotation + */ + private boolean isAdditionalSideEffectedExpression(JavaExpression expr) { + return !sideEffectsOnlyExpressionsFromAnnotation.contains(expr) + && !expr.isUnmodifiableByOtherCode() + && !aliasedExpressions.containsKey(expr); + } + @Override public Void visitNewClass(NewClassTree node, Void aVoid) { return super.visitNewClass(node, aVoid); @@ -236,6 +261,14 @@ public Void visitAssignment(AssignmentTree node, Void aVoid) { return super.visitAssignment(node, aVoid); } + @Override + public Void visitVariable(VariableTree node, Void aVoid) { + JavaExpression name = JavaExpression.fromVariableTree(node); + JavaExpression expr = JavaExpression.fromTree(node.getInitializer()); + aliasedExpressions.put(name, expr); + return super.visitVariable(node, aVoid); + } + @Override public Void visitUnary(UnaryTree node, Void aVoid) { return super.visitUnary(node, aVoid); diff --git a/framework/tests/sideeffectsonly/NestedSideEffectsNoAliasing.java b/framework/tests/sideeffectsonly/NestedSideEffectsNoAliasing.java new file mode 100644 index 00000000000..3e4c10a3e24 --- /dev/null +++ b/framework/tests/sideeffectsonly/NestedSideEffectsNoAliasing.java @@ -0,0 +1,22 @@ +import java.util.ArrayList; +import java.util.List; +import org.checkerframework.dataflow.qual.SideEffectsOnly; + +public class NestedSideEffectsNoAliasing { + + @SideEffectsOnly("#1.inner.nestedList") + void test1(OuterWrapper first) { + first.inner.nestedList.add(1); // OK + // :: error: (purity.incorrect.sideeffectsonly) + first.arrB.add(2); + } + + class OuterWrapper { + InnerWrapper inner = new InnerWrapper(); + List arrB = new ArrayList<>(); + } + + class InnerWrapper { + List nestedList = new ArrayList<>(); + } +} diff --git a/framework/tests/sideeffectsonly/NestedSideEffectsWithAliasing.java b/framework/tests/sideeffectsonly/NestedSideEffectsWithAliasing.java new file mode 100644 index 00000000000..106afc221cf --- /dev/null +++ b/framework/tests/sideeffectsonly/NestedSideEffectsWithAliasing.java @@ -0,0 +1,22 @@ +import java.util.ArrayList; +import java.util.List; +import org.checkerframework.dataflow.qual.SideEffectsOnly; + +public class NestedSideEffectsWithAliasing { + + @SideEffectsOnly({"#1.inner.nestedList", "#1.inner"}) + void test1(OuterWrapper first) { + List aliasOfNestedList = first.inner.nestedList; + List aliasOfAlias = aliasOfNestedList; + aliasOfAlias.add(1); // Should be OK + } + + class OuterWrapper { + InnerWrapper inner = new InnerWrapper(); + List arrB = new ArrayList<>(); + } + + class InnerWrapper { + List nestedList = new ArrayList<>(); + } +} From b7c8187b880eb9fe8c51385c5fbca056a2387eee Mon Sep 17 00:00:00 2001 From: James Yoo Date: Fri, 12 Apr 2024 22:35:58 -0700 Subject: [PATCH 100/113] Improve error message --- .../checkerframework/common/basetype/BaseTypeVisitor.java | 3 ++- .../common/basetype/SideEffectsOnlyChecker.java | 5 ++++- .../org/checkerframework/common/basetype/messages.properties | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 8fd372d0334..6158852a894 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -1236,7 +1236,8 @@ protected void checkPurityAnnotations(MethodTree tree) { for (IPair s : seOnlyIncorrectExprs) { if (!sideEffectsOnlyExpressions.contains(s.second)) { // System.out.printf("Error 2%n"); - checker.reportError(s.first, "purity.incorrect.sideeffectsonly", s.second.toString()); + checker.reportError( + s.first, "purity.incorrect.sideeffectsonly", tree.getName(), s.second.toString()); } } } diff --git a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java index 8bee4d0d1df..a9549a1f696 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java @@ -172,7 +172,10 @@ public Void visitMethodInvocation(MethodInvocationTree node, Void aVoid) { if (actualSideEffectedExprs.isEmpty()) { // If the args are empty, it might be modifying anything checker.reportError( - node, "purity.incorrect.sideeffectsonly", sideEffectsOnlyExpressionsFromAnnotation); + node, + "purity.incorrect.sideeffectsonly", + invokedElem.getSimpleName(), + sideEffectsOnlyExpressionsFromAnnotation); } } actualSideEffectedExprs.stream() diff --git a/framework/src/main/java/org/checkerframework/common/basetype/messages.properties b/framework/src/main/java/org/checkerframework/common/basetype/messages.properties index 7222f819d91..ae9278c2fa4 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/messages.properties +++ b/framework/src/main/java/org/checkerframework/common/basetype/messages.properties @@ -80,7 +80,7 @@ purity.not.sideeffectfree.call=call to side-effecting %s not allowed in side-eff purity.more.deterministic=the method %s could be declared as @Deterministic purity.more.pure=the method %s could be declared as @Pure purity.more.sideeffectfree=the method %s could be declared as @SideEffectFree -purity.incorrect.sideeffectsonly=the method side-effects %s +purity.incorrect.sideeffectsonly=the method %s may side-effect %s purity.incorrect.annotation.conflict=the method %s cannot be annotated with both @SideEffectsOnly and %s flowexpr.parse.index.too.big=the method does not have a parameter %s From 73c45c09b7ead596eca75d888547ac889c0910ad Mon Sep 17 00:00:00 2001 From: James Yoo Date: Fri, 12 Apr 2024 22:37:52 -0700 Subject: [PATCH 101/113] Correct Javadoc --- .../common/basetype/SideEffectsOnlyChecker.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java index a9549a1f696..4418f10bd25 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/SideEffectsOnlyChecker.java @@ -234,10 +234,10 @@ private List getJavaExpressionsFromMethodInvocation( * side-effected beyond the expressions provided in the annotation: * *
      - *
    • The expression does bold appear in the expressions passed as arguments to the + *
    • The expression does not appear in the expressions passed as arguments to the * {@link SideEffectsOnly} annotation *
    • The expression is modifiable by other code - *
    • The expression is not aliased by other variables in the method body + *
    • The expression is not aliased by other variables in the method body *
    * * @param expr the expression to check for side-effecting From 01ff6f812171640f405472b8f9b28fc8ebc2e48d Mon Sep 17 00:00:00 2001 From: James Yoo Date: Sun, 14 Apr 2024 12:57:27 -0700 Subject: [PATCH 102/113] "Unrefine" fields in abstract store depending on declared side-effects --- .../framework/flow/CFAbstractStore.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index 859a238c63c..f07bbc83de0 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -248,6 +248,7 @@ protected boolean isSideEffectFree(AnnotatedTypeFactory atypeFactory, Executable */ public void updateForMethodCall( MethodInvocationNode methodInvocationNode, AnnotatedTypeFactory atypeFactory, V val) { + System.out.printf("UPDATE FOR METHOD CALL AT = %s\n", methodInvocationNode); ExecutableElement method = methodInvocationNode.getTarget().getMethod(); @SuppressWarnings("unchecked") GenericAnnotatedTypeFactory gatypeFactory = @@ -306,7 +307,7 @@ public void updateForMethodCall( } } else { // Case 2 (unassignable fields) and case 3 (monotonic fields) - updateFieldValuesForMethodCall(gatypeFactory); + updateFieldValuesForMethodCall(gatypeFactory, sideEffectsOnlyExpressions); } // update array values @@ -403,13 +404,25 @@ protected V newMonotonicFieldValueAfterMethodCall( *

    More specifically, remove all information about fields except for unassignable fields and * fields that have a monotonic annotation. * + *

    A non-empty {@param sideEffectsOnlyExpressions} is indicative that the invoked method has + * side-effects. In this case, remove information for fields that actually appear in the list of + * side-effected expressions. + * * @param atypeFactory AnnotatedTypeFactory of the associated checker + * @param sideEffectsOnlyExpressions the expressions that are side-effected by a method call */ private void updateFieldValuesForMethodCall( - GenericAnnotatedTypeFactory atypeFactory) { + GenericAnnotatedTypeFactory atypeFactory, + List sideEffectsOnlyExpressions) { Map newFieldValues = new HashMap<>(CollectionsPlume.mapCapacity(fieldValues)); for (Map.Entry e : fieldValues.entrySet()) { FieldAccess fieldAccess = e.getKey(); + + if (!sideEffectsOnlyExpressions.isEmpty() + && !sideEffectsOnlyExpressions.contains(fieldAccess)) { + return; + } + V value = e.getValue(); V newValue = newFieldValueAfterMethodCall(fieldAccess, atypeFactory, value); From 4fe3a6ee7707413506d0a4d98e3fedf635be4793 Mon Sep 17 00:00:00 2001 From: James Yoo Date: Sun, 14 Apr 2024 13:35:48 -0700 Subject: [PATCH 103/113] Update Javadoc --- .../org/checkerframework/framework/flow/CFAbstractStore.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index f07bbc83de0..46a7414e4a3 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -404,7 +404,7 @@ protected V newMonotonicFieldValueAfterMethodCall( *

    More specifically, remove all information about fields except for unassignable fields and * fields that have a monotonic annotation. * - *

    A non-empty {@param sideEffectsOnlyExpressions} is indicative that the invoked method has + *

    A non-empty {@code sideEffectsOnlyExpressions} is indicative that the invoked method has * side-effects. In this case, remove information for fields that actually appear in the list of * side-effected expressions. * From 61909fd4f64b60c5e458b90f5fd8cde10999a4f4 Mon Sep 17 00:00:00 2001 From: James Yoo Date: Sun, 14 Apr 2024 18:05:28 -0700 Subject: [PATCH 104/113] Remove unnecessary print line --- .../org/checkerframework/framework/flow/CFAbstractStore.java | 1 - 1 file changed, 1 deletion(-) diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index 46a7414e4a3..c4d1a87b92b 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -248,7 +248,6 @@ protected boolean isSideEffectFree(AnnotatedTypeFactory atypeFactory, Executable */ public void updateForMethodCall( MethodInvocationNode methodInvocationNode, AnnotatedTypeFactory atypeFactory, V val) { - System.out.printf("UPDATE FOR METHOD CALL AT = %s\n", methodInvocationNode); ExecutableElement method = methodInvocationNode.getTarget().getMethod(); @SuppressWarnings("unchecked") GenericAnnotatedTypeFactory gatypeFactory = From a4f3ec13544452ea482eee03cbf7c4f91f5008a9 Mon Sep 17 00:00:00 2001 From: James Yoo Date: Mon, 15 Apr 2024 10:22:14 -0700 Subject: [PATCH 105/113] Correct logic for field CFStore updates --- .../framework/flow/CFAbstractStore.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index c4d1a87b92b..f670521dde2 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -404,7 +404,7 @@ protected V newMonotonicFieldValueAfterMethodCall( * fields that have a monotonic annotation. * *

    A non-empty {@code sideEffectsOnlyExpressions} is indicative that the invoked method has - * side-effects. In this case, remove information for fields that actually appear in the list of + * side effects. In this case, remove information for fields that actually appear in the list of * side-effected expressions. * * @param atypeFactory AnnotatedTypeFactory of the associated checker @@ -416,18 +416,18 @@ private void updateFieldValuesForMethodCall( Map newFieldValues = new HashMap<>(CollectionsPlume.mapCapacity(fieldValues)); for (Map.Entry e : fieldValues.entrySet()) { FieldAccess fieldAccess = e.getKey(); + V value = e.getValue(); if (!sideEffectsOnlyExpressions.isEmpty() && !sideEffectsOnlyExpressions.contains(fieldAccess)) { - return; - } - - V value = e.getValue(); - - V newValue = newFieldValueAfterMethodCall(fieldAccess, atypeFactory, value); - if (newValue != null) { - // Keep information for all hierarchies where we had a monotonic annotation. - newFieldValues.put(fieldAccess, newValue); + // If the field hasn't been side-effected, there is no need to compute a new value for it + newFieldValues.put(fieldAccess, value); + } else { + V newValue = newFieldValueAfterMethodCall(fieldAccess, atypeFactory, value); + if (newValue != null) { + // Keep information for all hierarchies where we had a monotonic annotation. + newFieldValues.put(fieldAccess, newValue); + } } } fieldValues = newFieldValues; From 7ec0b2f2f06a7ebfb7c7e46b57d29d50812843b8 Mon Sep 17 00:00:00 2001 From: James Yoo Date: Mon, 15 Apr 2024 10:26:47 -0700 Subject: [PATCH 106/113] Update naming --- .../checkerframework/framework/flow/CFAbstractStore.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index f670521dde2..516e713eeb3 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -416,14 +416,14 @@ private void updateFieldValuesForMethodCall( Map newFieldValues = new HashMap<>(CollectionsPlume.mapCapacity(fieldValues)); for (Map.Entry e : fieldValues.entrySet()) { FieldAccess fieldAccess = e.getKey(); - V value = e.getValue(); + V previousValue = e.getValue(); if (!sideEffectsOnlyExpressions.isEmpty() && !sideEffectsOnlyExpressions.contains(fieldAccess)) { // If the field hasn't been side-effected, there is no need to compute a new value for it - newFieldValues.put(fieldAccess, value); + newFieldValues.put(fieldAccess, previousValue); } else { - V newValue = newFieldValueAfterMethodCall(fieldAccess, atypeFactory, value); + V newValue = newFieldValueAfterMethodCall(fieldAccess, atypeFactory, previousValue); if (newValue != null) { // Keep information for all hierarchies where we had a monotonic annotation. newFieldValues.put(fieldAccess, newValue); From 64a6561bf455dc267fd4be7c222edfea8c3d1925 Mon Sep 17 00:00:00 2001 From: James Yoo Date: Tue, 16 Apr 2024 11:30:36 -0700 Subject: [PATCH 107/113] Make refinement happen for local variables (THIS BREAKS TESTS) --- .../framework/flow/CFAbstractStore.java | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index 516e713eeb3..bfcd9a4dfae 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -286,24 +286,27 @@ public void updateForMethodCall( if (hasSideEffect) { boolean sideEffectsUnrefineAliases = gatypeFactory.sideEffectsUnrefineAliases; + // TODO: Why is this code within the sideEffectsUnrefineAliases branch?? + if (!sideEffectsOnlyExpressions.isEmpty()) { + System.out.printf("SIDE EFFECTS ONLY EXPRESSIONS = %s\n", sideEffectsOnlyExpressions); + for (JavaExpression e : sideEffectsOnlyExpressions) { + if (!e.isUnmodifiableByOtherCode()) { + System.out.printf( + "UNREFINING INFORMATION ABOUT: %s AT CALL: %s\n", e, methodInvocationNode); + // Remove any computed information about the expression. + localVariableValues.keySet().remove(e); + fieldValues.keySet().remove(e); + } + } + } else { + localVariableValues.keySet().removeIf(e -> !e.isUnmodifiableByOtherCode()); + thisValue = null; + fieldValues.keySet().removeIf(e -> !e.isUnmodifiableByOtherCode()); + } // TODO: Also remove if any element/argument to the annotation is not // isUnmodifiableByOtherCode. Example: @KeyFor("valueThatCanBeMutated"). if (sideEffectsUnrefineAliases) { - // TODO: Why is this code within the sideEffectsUnrefineAliases branch?? - if (!sideEffectsOnlyExpressions.isEmpty()) { - for (JavaExpression e : sideEffectsOnlyExpressions) { - if (!e.isUnmodifiableByOtherCode()) { - // Remove any computed information about the expression. - localVariableValues.keySet().remove(e); - fieldValues.keySet().remove(e); - } - } - } else { - localVariableValues.keySet().removeIf(e -> !e.isUnmodifiableByOtherCode()); - thisValue = null; - fieldValues.keySet().removeIf(e -> !e.isUnmodifiableByOtherCode()); - } } else { // Case 2 (unassignable fields) and case 3 (monotonic fields) updateFieldValuesForMethodCall(gatypeFactory, sideEffectsOnlyExpressions); From d55c9d8d44ae9b7e3f7574482948b442ee7da576 Mon Sep 17 00:00:00 2001 From: James Yoo Date: Mon, 22 Apr 2024 18:49:22 -0700 Subject: [PATCH 108/113] Update logic for removing information from method calls --- .../checker/lock/LockStore.java | 8 +- .../framework/flow/CFAbstractStore.java | 118 ++++++++++++------ .../framework/flow/CFAbstractTransfer.java | 2 +- .../type/GenericAnnotatedTypeFactory.java | 2 +- 4 files changed, 85 insertions(+), 45 deletions(-) diff --git a/checker/src/main/java/org/checkerframework/checker/lock/LockStore.java b/checker/src/main/java/org/checkerframework/checker/lock/LockStore.java index 4e8606be6cd..fde33df01cd 100644 --- a/checker/src/main/java/org/checkerframework/checker/lock/LockStore.java +++ b/checker/src/main/java/org/checkerframework/checker/lock/LockStore.java @@ -85,10 +85,10 @@ public void insertLockPossiblyHeld(JavaExpression je) { } } else if (je instanceof MethodCall) { MethodCall method = (MethodCall) je; - CFValue current = methodValues.get(method); + CFValue current = methodCallValues.get(method); CFValue value = changeLockAnnoToTop(je, current); if (value != null) { - methodValues.put(method, value); + methodCallValues.put(method, value); } } else if (je instanceof ArrayAccess) { ArrayAccess arrayAccess = (ArrayAccess) je; @@ -233,10 +233,10 @@ public void insertValue( } } else if (je instanceof MethodCall) { MethodCall method = (MethodCall) je; - CFValue oldValue = methodValues.get(method); + CFValue oldValue = methodCallValues.get(method); CFValue newValue = value.mostSpecific(oldValue, null); if (newValue != null) { - methodValues.put(method, newValue); + methodCallValues.put(method, newValue); } } } diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index bfcd9a4dfae..5f802eda62a 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -104,7 +104,7 @@ public Map getFieldValues() { /** * Information collected about method calls, using the internal representation {@link MethodCall}. */ - protected final Map methodValues; + protected final Map methodCallValues; /** * Information collected about classname.class values, using the internal representation @@ -150,7 +150,7 @@ protected CFAbstractStore(CFAbstractAnalysis analysis, boolean sequenti localVariableValues = new HashMap<>(); thisValue = null; fieldValues = new HashMap<>(); - methodValues = new HashMap<>(); + methodCallValues = new HashMap<>(); arrayValues = new HashMap<>(); classValues = new HashMap<>(); this.sequentialSemantics = sequentialSemantics; @@ -172,7 +172,7 @@ protected CFAbstractStore(CFAbstractStore other) { localVariableValues = new HashMap<>(other.localVariableValues); thisValue = other.thisValue; fieldValues = new HashMap<>(other.fieldValues); - methodValues = new HashMap<>(other.methodValues); + methodCallValues = new HashMap<>(other.methodCallValues); arrayValues = new HashMap<>(other.arrayValues); classValues = new HashMap<>(other.classValues); sequentialSemantics = other.sequentialSemantics; @@ -286,44 +286,83 @@ public void updateForMethodCall( if (hasSideEffect) { boolean sideEffectsUnrefineAliases = gatypeFactory.sideEffectsUnrefineAliases; - // TODO: Why is this code within the sideEffectsUnrefineAliases branch?? - if (!sideEffectsOnlyExpressions.isEmpty()) { - System.out.printf("SIDE EFFECTS ONLY EXPRESSIONS = %s\n", sideEffectsOnlyExpressions); - for (JavaExpression e : sideEffectsOnlyExpressions) { - if (!e.isUnmodifiableByOtherCode()) { - System.out.printf( - "UNREFINING INFORMATION ABOUT: %s AT CALL: %s\n", e, methodInvocationNode); - // Remove any computed information about the expression. - localVariableValues.keySet().remove(e); - fieldValues.keySet().remove(e); - } - } - } else { - localVariableValues.keySet().removeIf(e -> !e.isUnmodifiableByOtherCode()); - thisValue = null; - fieldValues.keySet().removeIf(e -> !e.isUnmodifiableByOtherCode()); - } // TODO: Also remove if any element/argument to the annotation is not // isUnmodifiableByOtherCode. Example: @KeyFor("valueThatCanBeMutated"). if (sideEffectsUnrefineAliases) { + // TODO: Why is this code within the sideEffectsUnrefineAliases branch?? + if (!sideEffectsOnlyExpressions.isEmpty()) { + for (JavaExpression e : sideEffectsOnlyExpressions) { + if (!e.isUnmodifiableByOtherCode()) { + // Remove any computed information about the expression. + localVariableValues.keySet().remove(e); + fieldValues.keySet().remove(e); + } + } + } else { + localVariableValues.keySet().removeIf(e -> !e.isUnmodifiableByOtherCode()); + thisValue = null; + fieldValues.keySet().removeIf(e -> !e.isUnmodifiableByOtherCode()); + } } else { // Case 2 (unassignable fields) and case 3 (monotonic fields) updateFieldValuesForMethodCall(gatypeFactory, sideEffectsOnlyExpressions); } - // update array values + // Update array values arrayValues.clear(); - // update method values - methodValues.keySet().removeIf(e -> !e.isUnmodifiableByOtherCode()); + // Update information about method calls + updateMethodCallValues(sideEffectsOnlyExpressions); } - // store information about method call if possible + // store information about method calls if possible JavaExpression methodCall = JavaExpression.fromNode(methodInvocationNode); replaceValue(methodCall, val); } + /** + * Update information about method calls given the list of side-effected expressions. + * + * @param sideEffectsOnlyExpressions the list of side-effected expressions + */ + private void updateMethodCallValues(List sideEffectsOnlyExpressions) { + if (sideEffectsOnlyExpressions.isEmpty()) { + methodCallValues + .keySet() + .removeIf(methodCallValue -> !methodCallValue.isUnmodifiableByOtherCode()); + } else { + methodCallValues + .keySet() + .removeIf( + methodCallValue -> + hasMethodCallInformationChanged(methodCallValue, sideEffectsOnlyExpressions)); + } + } + + /** + * Returns true if information about a method call might have changed. + * + *

    Information about a method call might have changed if: + * + *

      + *
    • It is modifiable by other code + *
    • It contains expressions that appear in the list of expressions that are provided to a + * {@code @SideEffectsOnly} annotation + *
    + * + * @param methodCallValue the method call value + * @param sideEffectsOnlyExpressions the list of expressions provided to a + * {@code @SideEffectsOnly} annotation + * @return true if information about a method call might have changed + */ + private boolean hasMethodCallInformationChanged( + JavaExpression methodCallValue, List sideEffectsOnlyExpressions) { + return !methodCallValue.isUnmodifiableByOtherCode() + && sideEffectsOnlyExpressions.stream() + .anyMatch(methodCallValue::containsSyntacticEqualJavaExpression); + } + /** * Returns the new value of a field after a method call, or {@code null} if the field should be * removed from the store. @@ -697,10 +736,10 @@ protected void computeNewValueAndInsert( MethodCall method = (MethodCall) expr; // Don't store any information if concurrent semantics are enabled. if (sequentialSemantics) { - V oldValue = methodValues.get(method); + V oldValue = methodCallValues.get(method); V newValue = merger.apply(oldValue, value); if (newValue != null) { - methodValues.put(method, newValue); + methodCallValues.put(method, newValue); } } } else if (expr instanceof ArrayAccess) { @@ -817,7 +856,7 @@ public void clearValue(JavaExpression expr) { fieldValues.remove(fieldAcc); } else if (expr instanceof MethodCall) { MethodCall method = (MethodCall) expr; - methodValues.remove(method); + methodCallValues.remove(method); } else if (expr instanceof ArrayAccess) { ArrayAccess a = (ArrayAccess) expr; arrayValues.remove(a); @@ -847,7 +886,7 @@ public void clearValue(JavaExpression expr) { return fieldValues.get(fieldAcc); } else if (expr instanceof MethodCall) { MethodCall method = (MethodCall) expr; - return methodValues.get(method); + return methodCallValues.get(method); } else if (expr instanceof ArrayAccess) { ArrayAccess a = (ArrayAccess) expr; return arrayValues.get(a); @@ -909,7 +948,7 @@ public void clearValue(JavaExpression expr) { if (method == null) { return null; } - return methodValues.get(method); + return methodCallValues.get(method); } /** @@ -1055,7 +1094,7 @@ else if (fieldAccess.getField().equals(otherFieldAccess.getField())) { } // case 3: - methodValues.clear(); + methodCallValues.clear(); } /** @@ -1106,7 +1145,7 @@ protected void removeConflicting(ArrayAccess arrayAccess, @Nullable V val) { } // case 3: - methodValues.clear(); + methodCallValues.clear(); } /** @@ -1143,7 +1182,8 @@ protected void removeConflicting(LocalVariable var) { } } - Iterator> methodValuesIterator = methodValues.entrySet().iterator(); + Iterator> methodValuesIterator = + methodCallValues.entrySet().iterator(); while (methodValuesIterator.hasNext()) { Map.Entry entry = methodValuesIterator.next(); MethodCall otherMethodAccess = entry.getKey(); @@ -1273,16 +1313,16 @@ private S upperBound(S other, boolean shouldWiden) { } } } - for (Map.Entry e : other.methodValues.entrySet()) { + for (Map.Entry e : other.methodCallValues.entrySet()) { // information about methods that are only part of one store, but not the other are // discarded, as one store implicitly contains 'top' for that field. MethodCall el = e.getKey(); - V thisVal = methodValues.get(el); + V thisVal = methodCallValues.get(el); if (thisVal != null) { V otherVal = e.getValue(); V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden); if (mergedVal != null) { - newStore.methodValues.put(el, mergedVal); + newStore.methodCallValues.put(el, mergedVal); } } } @@ -1335,9 +1375,9 @@ protected boolean supersetOf(CFAbstractStore other) { return false; } } - for (Map.Entry e : other.methodValues.entrySet()) { + for (Map.Entry e : other.methodCallValues.entrySet()) { MethodCall key = e.getKey(); - V value = methodValues.get(key); + V value = methodCallValues.get(key); if (value == null || !value.equals(e.getValue())) { return false; } @@ -1410,8 +1450,8 @@ protected String internalVisualize(CFGVisualizer viz) { for (ArrayAccess fa : ToStringComparator.sorted(arrayValues.keySet())) { res.add(viz.visualizeStoreArrayVal(fa, arrayValues.get(fa))); } - for (MethodCall fa : ToStringComparator.sorted(methodValues.keySet())) { - res.add(viz.visualizeStoreMethodVals(fa, methodValues.get(fa))); + for (MethodCall fa : ToStringComparator.sorted(methodCallValues.keySet())) { + res.add(viz.visualizeStoreMethodVals(fa, methodCallValues.get(fa))); } for (ClassName fa : ToStringComparator.sorted(classValues.keySet())) { res.add(viz.visualizeStoreClassVals(fa, classValues.get(fa))); diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java index b04c0fd42a8..ba1f02c373d 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java @@ -297,7 +297,7 @@ public S initialStore(UnderlyingAST underlyingAST, List param // store.localVariableValues.clear(); store.classValues.clear(); store.arrayValues.clear(); - store.methodValues.clear(); + store.methodCallValues.clear(); } else { store = analysis.createEmptyStore(sequentialSemantics); } diff --git a/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java index a5e83828745..e280cc1f15d 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java @@ -255,7 +255,7 @@ public abstract class GenericAnnotatedTypeFactory< * effect to the value could change them; set this field to true. */ // Not final so that subclasses can set it. - public boolean sideEffectsUnrefineAliases = false; + public boolean sideEffectsUnrefineAliases = false; // TODO: maybe remove it /** * True if this checker either has one or more subcheckers, or if this checker is a subchecker. From 820002a743fb66c497217aad6df9e21ce847125d Mon Sep 17 00:00:00 2001 From: James Yoo Date: Tue, 23 Apr 2024 10:35:58 -0700 Subject: [PATCH 109/113] Add test case --- .../OptionalSideEffectsTest.java | 22 ++++++ .../OptionalSideEffectsPrecondition.java | 68 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/OptionalSideEffectsTest.java create mode 100644 checker/tests/optional-side-effects/OptionalSideEffectsPrecondition.java diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/OptionalSideEffectsTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/OptionalSideEffectsTest.java new file mode 100644 index 00000000000..cfb1768a4f9 --- /dev/null +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/ainferrunners/OptionalSideEffectsTest.java @@ -0,0 +1,22 @@ +package org.checkerframework.checker.test.junit.ainferrunners; + +import java.io.File; +import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; + +public class OptionalSideEffectsTest extends CheckerFrameworkPerDirectoryTest { + + public OptionalSideEffectsTest(List testFiles) { + super( + testFiles, + org.checkerframework.checker.optional.OptionalChecker.class, + "optional", + "-AcheckPurityAnnotations"); + } + + @Parameters + public static String[] getTestDirs() { + return new String[] {"optional-side-effects"}; + } +} diff --git a/checker/tests/optional-side-effects/OptionalSideEffectsPrecondition.java b/checker/tests/optional-side-effects/OptionalSideEffectsPrecondition.java new file mode 100644 index 00000000000..3c70fcc2747 --- /dev/null +++ b/checker/tests/optional-side-effects/OptionalSideEffectsPrecondition.java @@ -0,0 +1,68 @@ +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import org.checkerframework.checker.optional.qual.RequiresPresent; +import org.checkerframework.dataflow.qual.Pure; +import org.checkerframework.dataflow.qual.SideEffectsOnly; + +class OptionalSideEffectsPrecondition { + + void test1(OptionalContainer optContainer) { + if (!optContainer.getOpt().isPresent()) { + return; + } + List strs = new ArrayList<>(); + methodA(optContainer, strs); + optContainer.getOpt().get(); // OK + bar(optContainer); // OK + } + + void test2(OptionalContainer optContainer) { + if (!optContainer.getOpt().isPresent()) { + return; + } + List strs = new ArrayList<>(); + methodB(optContainer, strs); + + // :: error: (contracts.precondition) + bar(optContainer); + } + + void test3(OptionalContainer optContainer) { + if (!optContainer.getOpt().isPresent()) { + return; + } + List strs = new ArrayList<>(); + havoc(optContainer, strs); + + // :: error: (contracts.precondition) + bar(optContainer); + } + + @RequiresPresent("#1.getOpt()") + void bar(OptionalContainer optContainer) {} + + @SideEffectsOnly("#2") + void methodA(OptionalContainer optContainer, Object param) {} + + @SideEffectsOnly({"#1", "#2"}) + void methodB(OptionalContainer optContainer, Object param) {} + + void havoc(OptionalContainer optContainer, Object param) {} + + class OptionalContainer { + + @SuppressWarnings("optional:field") + private Optional opt; + + @SuppressWarnings("optional:parameter") + OptionalContainer(Optional opt) { + this.opt = opt; + } + + @Pure // Not required if running under -AassumePureGetters + public Optional getOpt() { + return this.opt; + } + } +} From 090ec33fafbf8a6fe4057e239c75df34b9dbf678 Mon Sep 17 00:00:00 2001 From: James Yoo Date: Tue, 23 Apr 2024 11:17:04 -0700 Subject: [PATCH 110/113] Avoid CF crash when invalid expression is passed to `@SideEffectsOnly` --- .../OptionalSideEffectsPrecondition.java | 4 ++++ .../checkerframework/common/basetype/BaseTypeVisitor.java | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/checker/tests/optional-side-effects/OptionalSideEffectsPrecondition.java b/checker/tests/optional-side-effects/OptionalSideEffectsPrecondition.java index 3c70fcc2747..b83474d15eb 100644 --- a/checker/tests/optional-side-effects/OptionalSideEffectsPrecondition.java +++ b/checker/tests/optional-side-effects/OptionalSideEffectsPrecondition.java @@ -48,6 +48,10 @@ void methodA(OptionalContainer optContainer, Object param) {} @SideEffectsOnly({"#1", "#2"}) void methodB(OptionalContainer optContainer, Object param) {} + @SideEffectsOnly({"#1.getOptional()"}) + // :: error: (flowexpr.parse.error) + void methodC(OptionalContainer optContainer, Object param) {} + void havoc(OptionalContainer optContainer, Object param) {} class OptionalContainer { diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 6158852a894..d7002893b76 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -1207,7 +1207,12 @@ protected void checkPurityAnnotations(MethodTree tree) { JavaExpression exprJe = StringToJavaExpression.atMethodBody(st, tree, checker); sideEffectsOnlyExpressions.add(exprJe); } catch (JavaExpressionParseUtil.JavaExpressionParseException ex) { - checker.report(st, ex.getDiagMessage()); + DiagMessage diagMessage = ex.getDiagMessage(); + if (diagMessage.getMessageKey().equals("flowexpr.parse.error")) { + checker.reportError(methodTree, "flowexpr.parse.error", st); + } else { + checker.report(st, ex.getDiagMessage()); + } return; } } From 5911bfdc5860d382dcf492a92889d8db9a368b11 Mon Sep 17 00:00:00 2001 From: James Yoo Date: Wed, 24 Apr 2024 16:23:35 -0700 Subject: [PATCH 111/113] Possible fix to forEach lambda --- .../OptionalSideEffectsLambda.java | 48 +++++++++++++++++++ .../framework/flow/CFAbstractTransfer.java | 2 +- 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 checker/tests/optional-side-effects/OptionalSideEffectsLambda.java diff --git a/checker/tests/optional-side-effects/OptionalSideEffectsLambda.java b/checker/tests/optional-side-effects/OptionalSideEffectsLambda.java new file mode 100644 index 00000000000..a2a365a5abb --- /dev/null +++ b/checker/tests/optional-side-effects/OptionalSideEffectsLambda.java @@ -0,0 +1,48 @@ +import java.util.List; +import java.util.Optional; +import org.checkerframework.checker.optional.qual.*; +import org.checkerframework.dataflow.qual.*; + +class Main { + + void fooWithEnhancedFor(OptContainer container, List strs) { + if (!container.getOptStr().isPresent()) { + return; + } + for (String s : strs) { + bar(container); + } + strs.forEach(s -> bar(container)); + } + + void fooWithForEach(OptContainer container, List strs) { + if (!container.getOptStr().isPresent()) { + return; + } + strs.forEach(s -> bar(container)); // OK + } + + @RequiresPresent("#1.getOptStr()") + @SideEffectFree + void bar(OptContainer container) {} + + @Pure + int baz() { + return 1; + } + + class OptContainer { + + @SuppressWarnings("optional:field") + private Optional optStr; + + OptContainer(String s) { + this.optStr = Optional.ofNullable(s); + } + + @Pure + public Optional getOptStr() { + return this.optStr; + } + } +} diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java index ba1f02c373d..a8a2129d449 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java @@ -297,7 +297,7 @@ public S initialStore(UnderlyingAST underlyingAST, List param // store.localVariableValues.clear(); store.classValues.clear(); store.arrayValues.clear(); - store.methodCallValues.clear(); + // store.methodCallValues.clear(); } else { store = analysis.createEmptyStore(sequentialSemantics); } From 7e9e02c9aea1bcee868e6d24a7de4b211688a391 Mon Sep 17 00:00:00 2001 From: James Yoo Date: Wed, 24 Apr 2024 16:50:10 -0700 Subject: [PATCH 112/113] Clean up test case --- .../OptionalSideEffectsLambda.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/checker/tests/optional-side-effects/OptionalSideEffectsLambda.java b/checker/tests/optional-side-effects/OptionalSideEffectsLambda.java index a2a365a5abb..79a1f93795a 100644 --- a/checker/tests/optional-side-effects/OptionalSideEffectsLambda.java +++ b/checker/tests/optional-side-effects/OptionalSideEffectsLambda.java @@ -3,16 +3,15 @@ import org.checkerframework.checker.optional.qual.*; import org.checkerframework.dataflow.qual.*; -class Main { +class OptionalSideEffectsLambda { void fooWithEnhancedFor(OptContainer container, List strs) { if (!container.getOptStr().isPresent()) { return; } for (String s : strs) { - bar(container); + bar(container); // OK } - strs.forEach(s -> bar(container)); } void fooWithForEach(OptContainer container, List strs) { @@ -26,11 +25,6 @@ void fooWithForEach(OptContainer container, List strs) { @SideEffectFree void bar(OptContainer container) {} - @Pure - int baz() { - return 1; - } - class OptContainer { @SuppressWarnings("optional:field") From 17c0ce6d38b5d966e1fc7191618ff6b6fd041923 Mon Sep 17 00:00:00 2001 From: James Yoo Date: Fri, 26 Apr 2024 20:36:53 -0700 Subject: [PATCH 113/113] Removing `sideEffectsUnrefineAliases` flag --- .../framework/flow/CFAbstractStore.java | 24 +------------------ .../SideEffectsOnlyToyChecker.java | 17 +------------ 2 files changed, 2 insertions(+), 39 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java index 5f802eda62a..bf8c02e7e99 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -285,29 +285,7 @@ public void updateForMethodCall( || atypeFactory.isSideEffectFree(method)); if (hasSideEffect) { - boolean sideEffectsUnrefineAliases = gatypeFactory.sideEffectsUnrefineAliases; - - // TODO: Also remove if any element/argument to the annotation is not - // isUnmodifiableByOtherCode. Example: @KeyFor("valueThatCanBeMutated"). - if (sideEffectsUnrefineAliases) { - // TODO: Why is this code within the sideEffectsUnrefineAliases branch?? - if (!sideEffectsOnlyExpressions.isEmpty()) { - for (JavaExpression e : sideEffectsOnlyExpressions) { - if (!e.isUnmodifiableByOtherCode()) { - // Remove any computed information about the expression. - localVariableValues.keySet().remove(e); - fieldValues.keySet().remove(e); - } - } - } else { - localVariableValues.keySet().removeIf(e -> !e.isUnmodifiableByOtherCode()); - thisValue = null; - fieldValues.keySet().removeIf(e -> !e.isUnmodifiableByOtherCode()); - } - } else { - // Case 2 (unassignable fields) and case 3 (monotonic fields) - updateFieldValuesForMethodCall(gatypeFactory, sideEffectsOnlyExpressions); - } + updateFieldValuesForMethodCall(gatypeFactory, sideEffectsOnlyExpressions); // Update array values arrayValues.clear(); diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/SideEffectsOnlyToyChecker.java b/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/SideEffectsOnlyToyChecker.java index 015e685f063..969277d8708 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/SideEffectsOnlyToyChecker.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/sideeffectsonly/SideEffectsOnlyToyChecker.java @@ -1,24 +1,9 @@ package org.checkerframework.framework.testchecker.sideeffectsonly; import org.checkerframework.common.basetype.BaseTypeChecker; -import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; /** * Toy checker used to test whether dataflow analysis correctly type-refines methods annotated with * {@link org.checkerframework.dataflow.qual.SideEffectsOnly}. */ -public class SideEffectsOnlyToyChecker extends BaseTypeChecker { - // TODO: Why does @SideEffectsOnly have effect only on methods that unrefine types?? - /** - * Sets {@code sideEffectsUnrefineAliases} to true as {@code @SideEffectsOnly} annotation has - * effect only on methods that unrefine types. - * - * @return GenericAnnotatedTypeFactory - */ - @Override - public GenericAnnotatedTypeFactory getTypeFactory() { - GenericAnnotatedTypeFactory result = super.getTypeFactory(); - result.sideEffectsUnrefineAliases = true; - return result; - } -} +public class SideEffectsOnlyToyChecker extends BaseTypeChecker {}