From d428f34524419546d76d4e72338778ca5c9d4b98 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sun, 5 Jul 2020 08:14:29 -0700 Subject: [PATCH 001/138] Add comma --- docs/manual/creating-a-checker.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/creating-a-checker.tex b/docs/manual/creating-a-checker.tex index 2c7991da1515..e1005d18ee25 100644 --- a/docs/manual/creating-a-checker.tex +++ b/docs/manual/creating-a-checker.tex @@ -1168,7 +1168,7 @@ The annotated type of expressions and types are defined via type introduction rules in the type factory. For most expressions and types, these rules are the same regardless of the type system. -For example, the type of a method invocation expression is the return type of the invoked method +For example, the type of a method invocation expression is the return type of the invoked method, viewpoint-adapted for the call site. The framework implements these rules so that all type systems automatically use them. For other expressions, such as string literals, their (annotated) types depend on the type system, so the framework provides way to specify what qualifiers should apply to these expressions. From 4feace59bf510a18bdfb3e816ac114208efb8712 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sun, 5 Jul 2020 08:15:05 -0700 Subject: [PATCH 002/138] The POM files list only the lead developers --- docs/developer/release/maven-artifacts/checker-pom.xml | 2 ++ .../release/maven-artifacts/checker-qual-android-pom.xml | 2 ++ docs/developer/release/maven-artifacts/checker-qual-pom.xml | 2 ++ docs/developer/release/maven-artifacts/dataflow-pom.xml | 2 ++ docs/developer/release/maven-artifacts/dataflow-shaded-pom.xml | 2 ++ docs/developer/release/maven-artifacts/framework-test-pom.xml | 2 ++ docs/developer/release/maven-artifacts/javacutil-pom.xml | 2 ++ 7 files changed, 14 insertions(+) diff --git a/docs/developer/release/maven-artifacts/checker-pom.xml b/docs/developer/release/maven-artifacts/checker-pom.xml index 564ca7a53e62..14593e721ae2 100644 --- a/docs/developer/release/maven-artifacts/checker-pom.xml +++ b/docs/developer/release/maven-artifacts/checker-pom.xml @@ -33,6 +33,8 @@ + + mernst Michael Ernst diff --git a/docs/developer/release/maven-artifacts/checker-qual-android-pom.xml b/docs/developer/release/maven-artifacts/checker-qual-android-pom.xml index f637f9803651..ee6247eb7394 100644 --- a/docs/developer/release/maven-artifacts/checker-qual-android-pom.xml +++ b/docs/developer/release/maven-artifacts/checker-qual-android-pom.xml @@ -39,6 +39,8 @@ + + mernst Michael Ernst diff --git a/docs/developer/release/maven-artifacts/checker-qual-pom.xml b/docs/developer/release/maven-artifacts/checker-qual-pom.xml index bf6a3cbd532d..22112d238c29 100644 --- a/docs/developer/release/maven-artifacts/checker-qual-pom.xml +++ b/docs/developer/release/maven-artifacts/checker-qual-pom.xml @@ -33,6 +33,8 @@ + + mernst Michael Ernst diff --git a/docs/developer/release/maven-artifacts/dataflow-pom.xml b/docs/developer/release/maven-artifacts/dataflow-pom.xml index b832a1a8375a..8f3391e581e9 100644 --- a/docs/developer/release/maven-artifacts/dataflow-pom.xml +++ b/docs/developer/release/maven-artifacts/dataflow-pom.xml @@ -43,6 +43,8 @@ + + mernst Michael Ernst diff --git a/docs/developer/release/maven-artifacts/dataflow-shaded-pom.xml b/docs/developer/release/maven-artifacts/dataflow-shaded-pom.xml index 1dc75c1d3237..f5a7a5c155dc 100644 --- a/docs/developer/release/maven-artifacts/dataflow-shaded-pom.xml +++ b/docs/developer/release/maven-artifacts/dataflow-shaded-pom.xml @@ -30,6 +30,8 @@ + + mernst Michael Ernst diff --git a/docs/developer/release/maven-artifacts/framework-test-pom.xml b/docs/developer/release/maven-artifacts/framework-test-pom.xml index f6abca146cf8..504deb8a4bec 100644 --- a/docs/developer/release/maven-artifacts/framework-test-pom.xml +++ b/docs/developer/release/maven-artifacts/framework-test-pom.xml @@ -44,6 +44,8 @@ + + mernst Michael Ernst diff --git a/docs/developer/release/maven-artifacts/javacutil-pom.xml b/docs/developer/release/maven-artifacts/javacutil-pom.xml index 3eb1929b3cb9..4f6b16fa2b1f 100644 --- a/docs/developer/release/maven-artifacts/javacutil-pom.xml +++ b/docs/developer/release/maven-artifacts/javacutil-pom.xml @@ -50,6 +50,8 @@ + + mernst Michael Ernst From 2e62dc30c8e13bb404a95f163163b94381cadae4 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sun, 5 Jul 2020 08:15:44 -0700 Subject: [PATCH 003/138] State earlier that utility files use the MIT license --- LICENSE.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index 8aa899e4c293..c42051afa0ab 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -8,10 +8,10 @@ license appears below. This is the same license used for OpenJDK. A few parts of the Checker Framework have more permissive licenses. - * The annotations are licensed under the MIT License. (The text of this - license also appears below.) More specifically, all the parts of the - Checker Framework that you might want to include with your own program - use the MIT License. This is the checker-qual*.jar and + * The annotations and utility files are licensed under the MIT License. + (The text of this license also appears below.) More specifically, all + the parts of the Checker Framework that you might want to include with + your own program use the MIT License. This is the checker-qual*.jar and checker-compat-qual*.jar files and all the files that appear in them: every file in a qual/ directory, plus utility files such as NullnessUtil.java, RegexUtil.java, SignednessUtil.java, etc. In From e4df5484bdcfcd7533c995ecd74e22cd5b9da2b7 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sun, 5 Jul 2020 08:20:01 -0700 Subject: [PATCH 004/138] Put classes in more logical order --- checker-qual/build.gradle | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/checker-qual/build.gradle b/checker-qual/build.gradle index 121e0c1067c6..dc4437548cf7 100644 --- a/checker-qual/build.gradle +++ b/checker-qual/build.gradle @@ -17,16 +17,17 @@ task copySources(type: Copy) { } from files('../checker/src/main/java', '../dataflow/src/main/java', '../framework/src/main/java') + // Qualifiers + include '**/org/checkerframework/**/qual/*.java' + include '**/PurityUnqualified.java' // TODO: Should we move this into a qual directory? + // Utility classes include "**/FormatUtil.java" include "**/NullnessUtil.java" include "**/RegexUtil.java" include "**/UnitsTools.java" include "**/SignednessUtil.java" include "**/I18nFormatUtil.java" - include '**/org/checkerframework/**/qual/*.java' include '**/Opt.java' - // TODO: Should we move this into a qual directory? - include '**/PurityUnqualified.java' // Make files read only. fileMode(0444) From 6bfde18efc05872f2ac48fa72c15537065e5c90c Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sun, 5 Jul 2020 14:37:32 -0700 Subject: [PATCH 005/138] Improve formatting of Javadoc comment --- .../java/org/checkerframework/framework/qual/SubtypeOf.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/src/main/java/org/checkerframework/framework/qual/SubtypeOf.java b/framework/src/main/java/org/checkerframework/framework/qual/SubtypeOf.java index 2bdd5023978a..35354e165cca 100644 --- a/framework/src/main/java/org/checkerframework/framework/qual/SubtypeOf.java +++ b/framework/src/main/java/org/checkerframework/framework/qual/SubtypeOf.java @@ -30,7 +30,8 @@ * public @interface MaybeAliased {} * * - *

Together, all the @SubtypeOf meta-annotations fully describe the type qualifier hierarchy. + *

Together, all the {@code @SubtypeOf} meta-annotations fully describe the type qualifier + * hierarchy. * * @checker_framework.manual #creating-declarative-hierarchy Declaratively defining the qualifier * hierarchy From 6db45f4cb400ba3194c0f31ed5a75d2baf69e690 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2020 06:44:33 -0700 Subject: [PATCH 006/138] Bump de.undercouch.download from 4.0.4 to 4.1.0 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 9c25ac6ec203..87f671a83b80 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { // https://plugins.gradle.org/plugin/com.github.johnrengelman.shadow (v5 requires Gradle 5) id 'com.github.johnrengelman.shadow' version '6.0.0' // https://plugins.gradle.org/plugin/de.undercouch.download - id "de.undercouch.download" version "4.0.4" + id "de.undercouch.download" version "4.1.0" id 'java' // https://github.com/tbroyer/gradle-errorprone-plugin id "net.ltgt.errorprone" version "1.2.1" From d7dcfe2737073c94aef9a89eaf913aa08327b360 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 6 Jul 2020 08:47:53 -0700 Subject: [PATCH 007/138] Add optional `message` argument to castNonNull; fixes #3424 (#3427) --- changelog.txt | 6 + .../checker/nullness/NullnessUtil.java | 185 ++++++++++++++++-- .../checker/nullness/Opt.java | 10 +- .../checker/regex/RegexUtil.java | 8 +- 4 files changed, 179 insertions(+), 30 deletions(-) diff --git a/changelog.txt b/changelog.txt index a053d6d81214..41a9a6bdb6e5 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,9 @@ +Version 3.?.?, August 3, 2020 + +Added an overloaded version of NullnessUtil.castNonNull that takes an error message. + +--------------------------------------------------------------------------- + Version 3.5.0, July 1, 2020 Use "allcheckers:" instead of "all:" as a prefix in a warning suppression string. diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessUtil.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessUtil.java index 1eee7405a79c..d971c9a197d1 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessUtil.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessUtil.java @@ -15,12 +15,9 @@ * *

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

Runtime Dependency - * - *

Please note that using this class introduces a runtime dependency. This means that you need to - * distribute (or link to) the Checker Framework, along with your binaries. - * - *

To eliminate this dependency, you can simply copy this class into your own project. + *

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. @@ -63,7 +60,8 @@ private NullnessUtil() { * 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 ref a reference of @Nullable type + * @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( @@ -72,16 +70,36 @@ private NullnessUtil() { 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); + return (@NonNull T[]) castNonNullArray(arr, null); } /** @@ -89,11 +107,32 @@ private NullnessUtil() { * 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); + return (@NonNull T[][]) castNonNullArray(arr, null); } /** @@ -101,11 +140,32 @@ private NullnessUtil() { * 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); + return (@NonNull T[][][]) castNonNullArray(arr, null); } /** @@ -113,12 +173,52 @@ private NullnessUtil() { * 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); + 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); } /** @@ -126,26 +226,73 @@ private NullnessUtil() { * 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); + 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) { - assert arr != null : "Misuse of castNonNullArray: called with a null array argument"; + 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"; - checkIfArray(arr[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; } - private static void checkIfArray(@NonNull Object ref) { - assert ref != null : "Misuse of checkIfArray: called with a null argument"; + /** + * 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. @@ -153,7 +300,7 @@ private static void checkIfArray(@NonNull Object ref) { // Nothing to do for arrays of primitive type: primitives are // never null. } else { - castNonNullArray((Object[]) ref); + castNonNullArray((Object[]) ref, message); } } } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/Opt.java b/checker/src/main/java/org/checkerframework/checker/nullness/Opt.java index 149b9f4f9ebb..57aa485404f8 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/Opt.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/Opt.java @@ -21,12 +21,9 @@ * *

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

Runtime Dependency - * - *

Please note that using this class introduces a runtime dependency. This means that you need to - * distribute (or link to) {@code checker-qual.jar}, along with your binaries. - * - *

To eliminate this dependency, you can simply copy this class into your own project. + *

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 */ @@ -40,6 +37,7 @@ private Opt() { /** * 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 diff --git a/checker/src/main/java/org/checkerframework/checker/regex/RegexUtil.java b/checker/src/main/java/org/checkerframework/checker/regex/RegexUtil.java index 2269bfa4b9b7..cd0fcf407a57 100644 --- a/checker/src/main/java/org/checkerframework/checker/regex/RegexUtil.java +++ b/checker/src/main/java/org/checkerframework/checker/regex/RegexUtil.java @@ -20,13 +20,11 @@ * href="https://checkerframework.org/manual/#regexutil-methods">Testing whether a string is a * regular expression in the Checker Framework manual. * - *

Runtime Dependency: Using this class introduces a runtime dependency on the - * checker-qual package. To eliminate this dependency, you can simply copy this class into your own + *

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. */ -// The Purity Checker cannot show for most methods in this class that -// they are pure, even though they are. -@SuppressWarnings("all:purity") +@SuppressWarnings("allcheckers:purity") public final class RegexUtil { /** This class is a collection of methods; it does not represent anything. */ From 29dfee32e182ed504ff1696e72bf21aa922d1152 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 6 Jul 2020 08:52:46 -0700 Subject: [PATCH 008/138] Permit `@UsesObjectEquals` when the class overrides equals() (#3426) --- .../checker/interning/InterningVisitor.java | 167 ++++++++++++------ .../tests/interning/UsesObjectEqualsTest.java | 31 ++++ .../util/AbstractMostlySingleton.java | 5 +- docs/manual/interning-checker.tex | 13 +- 4 files changed, 152 insertions(+), 64 deletions(-) diff --git a/checker/src/main/java/org/checkerframework/checker/interning/InterningVisitor.java b/checker/src/main/java/org/checkerframework/checker/interning/InterningVisitor.java index eb6285e2001b..16111819d4b4 100644 --- a/checker/src/main/java/org/checkerframework/checker/interning/InterningVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/interning/InterningVisitor.java @@ -24,6 +24,7 @@ import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; @@ -184,10 +185,7 @@ public Void visitBinary(BinaryTree node, Void p) { rightElt = ((DeclaredType) right.getUnderlyingType()).asElement(); } - // TODO: CODE REVIEW - // TODO: WOULD IT BE CLEARER TO USE A METHOD usesReferenceEquality(AnnotatedTypeMirror type) - // TODO: RATHER THAN leftElt.getAnnotation(UsesObjectEquals.class) != null) - // if neither @Interned or @UsesObjectEquals, report error + // If neither @Interned or @UsesObjectEquals, report error. if (!(left.hasEffectiveAnnotation(INTERNED) || (leftElt != null && leftElt.getAnnotation(UsesObjectEquals.class) != null))) { checker.reportError(leftOp, "not.interned", left); @@ -224,8 +222,9 @@ public Void visitMethodInvocation(MethodInvocationTree node, Void p) { * with @UsesObjectEquals, it must: * *

    - *
  • not override .equals(Object) - *
  • be a subclass of Object or another class annotated with @UsesObjectEquals + *
  • not override .equals(Object) and be a subclass of a class annotated + * with @UsesObjectEquals, or + *
  • override equals(Object) with body "this == arg" *
* * If a class is not annotated with @UsesObjectEquals, it must: @@ -246,20 +245,25 @@ public void processClassTree(ClassTree classTree) { // If @UsesObjectEquals is present, check to make sure the class does not override equals // and its supertype is Object or is annotated with @UsesObjectEquals. if (annotation != null) { - // Check methods to ensure no .equals - if (overridesEquals(classTree)) { - checker.reportError(classTree, "overrides.equals"); - } - TypeMirror superClass = elt.getSuperclass(); - if (superClass != null - // The super class of an interface is "none" rather than null. - && superClass.getKind() == TypeKind.DECLARED) { - TypeElement superClassElement = TypesUtils.getTypeElement(superClass); - if (superClassElement != null - && !ElementUtils.isObject(superClassElement) - && atypeFactory.getDeclAnnotation(superClassElement, UsesObjectEquals.class) - == null) { - checker.reportError(classTree, "superclass.notannotated"); + MethodTree equalsMethod = equalsImplementation(classTree); + if (equalsMethod != null) { + if (!isReferenceEqualityImplementation(equalsMethod)) { + checker.reportError(classTree, "overrides.equals"); + } + } else { + // Does not override equals() + TypeMirror superClass = elt.getSuperclass(); + if (superClass != null + // The super class of an interface is "none" rather than null. + && superClass.getKind() == TypeKind.DECLARED) { + TypeElement superClassElement = TypesUtils.getTypeElement(superClass); + if (superClassElement != null + && !ElementUtils.isObject(superClassElement) + && atypeFactory.getDeclAnnotation( + superClassElement, UsesObjectEquals.class) + == null) { + checker.reportError(classTree, "superclass.notannotated"); + } } } } @@ -267,6 +271,41 @@ public void processClassTree(ClassTree classTree) { super.processClassTree(classTree); } + /** + * Returns true if the given equals() method implements reference equality. + * + * @param equalsMethod an overriding implementation of Object.equals() + * @return true if the given equals() method implements reference equality + */ + private boolean isReferenceEqualityImplementation(MethodTree equalsMethod) { + BlockTree body = equalsMethod.getBody(); + List bodyStatements = body.getStatements(); + if (bodyStatements.size() == 1) { + StatementTree bodyStatement = bodyStatements.get(0); + if (bodyStatement.getKind() == Tree.Kind.RETURN) { + ExpressionTree returnExpr = + TreeUtils.withoutParens(((ReturnTree) bodyStatement).getExpression()); + if (returnExpr.getKind() == Tree.Kind.EQUAL_TO) { + BinaryTree bt = (BinaryTree) returnExpr; + ExpressionTree lhsTree = bt.getLeftOperand(); + ExpressionTree rhsTree = bt.getRightOperand(); + if (lhsTree.getKind() == Tree.Kind.IDENTIFIER + && rhsTree.getKind() == Tree.Kind.IDENTIFIER) { + Name leftName = ((IdentifierTree) lhsTree).getName(); + Name rightName = ((IdentifierTree) rhsTree).getName(); + Name paramName = equalsMethod.getParameters().get(0).getName(); + if ((leftName.contentEquals("this") && rightName.equals(paramName)) + || (leftName.equals(paramName) + && rightName.contentEquals("this"))) { + return true; + } + } + } + } + } + return false; + } + @Override protected void checkConstructorResult( AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { @@ -340,19 +379,24 @@ private boolean checkCreationOfInternedObject( // Helper methods // ********************************************************************** - /** Returns true if a class overrides Object.equals. */ - private boolean overridesEquals(ClassTree node) { + /** + * Returns the method that overrides Object.equals, or null. + * + * @param node a class + * @return the class's implementation of equals, or null + */ + private MethodTree equalsImplementation(ClassTree node) { List members = node.getMembers(); for (Tree member : members) { if (member instanceof MethodTree) { MethodTree mTree = (MethodTree) member; ExecutableElement enclosing = TreeUtils.elementFromDeclaration(mTree); if (overrides(enclosing, Object.class, "equals")) { - return true; + return mTree; } } } - return false; + return null; } /** @@ -408,42 +452,49 @@ private boolean suppressInsideComparison(final BinaryTree node) { return false; } - // If we're not directly in an if statement in a method (ignoring - // parens and blocks), terminate. - if (!Heuristics.matchParents(getCurrentPath(), Tree.Kind.IF, Tree.Kind.METHOD)) { - return false; - } - - // Ensure the if statement is the first statement in the method - - TreePath parentPath = getCurrentPath().getParentPath(); - - // Retrieve the enclosing if statement tree and method tree - Tree tree, ifStatementTree = null; - MethodTree methodTree = null; - while ((tree = parentPath.getLeaf()) != null) { - if (tree.getKind() == Tree.Kind.IF) { - ifStatementTree = tree; - } else if (tree.getKind() == Tree.Kind.METHOD) { - methodTree = (MethodTree) tree; - break; + TreePath path = getCurrentPath(); + TreePath parentPath = path.getParentPath(); + Tree parent = parentPath.getLeaf(); + + // Ensure the == is in a return or in an if, and that enclosing statement is the first + // statement in the method. + if (parent.getKind() == Tree.Kind.RETURN) { + // ensure the return statement is the first statement in the method + if (parentPath.getParentPath().getParentPath().getLeaf().getKind() + != Tree.Kind.METHOD) { + return false; } - parentPath = parentPath.getParentPath(); - } - - // The call to Heuristics.matchParents already ensured there is an enclosing if statement - assert ifStatementTree != null; - // The call to Heuristics.matchParents already ensured there is an enclosing method - assert methodTree != null; - - StatementTree stmnt = methodTree.getBody().getStatements().get(0); - // The call to Heuristics.matchParents already ensured the enclosing method has at least one - // statement (an if statement) in the body - assert stmnt != null; - - if (stmnt != ifStatementTree) { - return false; // The if statement is not the first statement in the method. + // maybe set some variables?? + } else if (Heuristics.matchParents(getCurrentPath(), Tree.Kind.IF, Tree.Kind.METHOD)) { + // Ensure the if statement is the first statement in the method + + // Retrieve the enclosing if statement tree and method tree + Tree ifStatementTree = null; + MethodTree methodTree = null; + // Set ifStatementTree and methodTree + { + TreePath ppath = parentPath; + Tree tree; + while ((tree = ppath.getLeaf()) != null) { + if (tree.getKind() == Tree.Kind.IF) { + ifStatementTree = tree; + } else if (tree.getKind() == Tree.Kind.METHOD) { + methodTree = (MethodTree) tree; + break; + } + ppath = ppath.getParentPath(); + } + } + assert ifStatementTree != null; + assert methodTree != null; + StatementTree firstStmnt = methodTree.getBody().getStatements().get(0); + assert firstStmnt != null; + if (ifStatementTree != firstStmnt) { + return false; // The if statement is not the first statement in the method. + } + } else { + return false; } ExecutableElement enclosingMethod = diff --git a/checker/tests/interning/UsesObjectEqualsTest.java b/checker/tests/interning/UsesObjectEqualsTest.java index 883c6ef0d3f8..89cb2b297b6c 100644 --- a/checker/tests/interning/UsesObjectEqualsTest.java +++ b/checker/tests/interning/UsesObjectEqualsTest.java @@ -20,6 +20,22 @@ public boolean equals(Object o) { } } + @UsesObjectEquals + class B3 extends A { + @Override + public boolean equals(Object o3) { + return this == o3; + } + } + + @UsesObjectEquals + class B4 extends A { + @Override + public boolean equals(Object o4) { + return o4 == this; + } + } + // changed to inherited, no (superclass.annotated) warning class C extends A {} @@ -60,4 +76,19 @@ class ExtendsInner1 extends UsesObjectEqualsTest.A {} class ExtendsInner2 extends UsesObjectEqualsTest.A {} class MyList extends LinkedList {} + + class DoesNotUseObjectEquals { + @Override + public boolean equals(Object o) { + return super.equals(o); + } + } + + @UsesObjectEquals + class SubclassUsesObjectEquals extends DoesNotUseObjectEquals { + @Override + public boolean equals(Object o) { + return this == o; + } + } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/AbstractMostlySingleton.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/AbstractMostlySingleton.java index 6c689d1d50f4..9fe3169c248b 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/util/AbstractMostlySingleton.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/util/AbstractMostlySingleton.java @@ -9,7 +9,10 @@ import org.checkerframework.checker.nullness.qual.PolyNull; import org.checkerframework.javacutil.BugInCF; -/** Base class for sets that are more efficient than HashSet for 0 and 1 elements. */ +/** + * Base class for arbitrary-size sets that very efficient (more efficient than HashSet) for 0 and 1 + * elements. + */ public abstract class AbstractMostlySingleton implements Set { /** The possible states of the collection. */ diff --git a/docs/manual/interning-checker.tex b/docs/manual/interning-checker.tex index 2c708846de50..258ff3e22c53 100644 --- a/docs/manual/interning-checker.tex +++ b/docs/manual/interning-checker.tex @@ -128,11 +128,14 @@ \item[\refqualclass{checker/interning/qual}{UsesObjectEquals}] is a class annotation (not a type annotation) that indicates that this class's - \ method is the same as that of \. In other words, - neither this class nor any of its superclasses overrides the \ - method. Since \ uses reference equality, this means that - for such a class, \<==> and \ are equivalent, and so the - Interning Checker does not issue errors or warnings for either one. + \ method is the same as that of \. Since + \ uses reference equality, this means that for such a + class, \<==> and \ are equivalent, and so the Interning Checker + does not issue errors or warnings for either one. + + Two ways to satisfy this annotation are: (1) neither this class nor any + of its superclasses overrides the \ method, or (2) this class + defines \ with body \. \item[\refqualclass{checker/interning/qual}{InternMethod}] is a method declaration annotation that indicates that this method From 360170a895c8c8db821d720d258ec3174219bf6a Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 6 Jul 2020 10:33:18 -0700 Subject: [PATCH 009/138] Add Interning Checker method annotations @EqualsMethod and @CompareToMethod --- changelog.txt | 4 + .../checker/interning/InterningVisitor.java | 59 ++++++- .../checker/interning/messages.properties | 1 + .../interning/qual/CompareToMethod.java | 21 +++ .../checker/interning/qual/EqualsMethod.java | 21 +++ .../checker/interning/qual/InternMethod.java | 4 +- .../com/sun/istack/internal/Interned.java | 2 - checker/tests/interning/HeuristicsTest.java | 152 ++++++++++++++++++ docs/manual/interning-checker.tex | 10 ++ .../framework/util/Heuristics.java | 9 ++ 10 files changed, 273 insertions(+), 10 deletions(-) create mode 100644 checker/src/main/java/org/checkerframework/checker/interning/qual/CompareToMethod.java create mode 100644 checker/src/main/java/org/checkerframework/checker/interning/qual/EqualsMethod.java diff --git a/changelog.txt b/changelog.txt index 41a9a6bdb6e5..6b500d2c887e 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,9 @@ Version 3.?.?, August 3, 2020 +The Interning Checker supports method annotations @EqualsMethod and +@CompareToMethod. Place them on methods like equals(), compareTo(), and +compare() to permit certain uses of == on non-interned values. + Added an overloaded version of NullnessUtil.castNonNull that takes an error message. --------------------------------------------------------------------------- diff --git a/checker/src/main/java/org/checkerframework/checker/interning/InterningVisitor.java b/checker/src/main/java/org/checkerframework/checker/interning/InterningVisitor.java index 16111819d4b4..748e98616964 100644 --- a/checker/src/main/java/org/checkerframework/checker/interning/InterningVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/interning/InterningVisitor.java @@ -31,6 +31,8 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; import javax.tools.Diagnostic.Kind; +import org.checkerframework.checker.interning.qual.CompareToMethod; +import org.checkerframework.checker.interning.qual.EqualsMethod; import org.checkerframework.checker.interning.qual.InternMethod; import org.checkerframework.checker.interning.qual.Interned; import org.checkerframework.checker.interning.qual.InternedDistinct; @@ -217,6 +219,36 @@ public Void visitMethodInvocation(MethodInvocationTree node, Void p) { return super.visitMethodInvocation(node, p); } + // Ensure that method annotations are not written on methods they don't apply to. + @Override + public Void visitMethod(MethodTree node, Void p) { + ExecutableElement methElt = TreeUtils.elementFromDeclaration(node); + boolean hasCompareToMethodAnno = + atypeFactory.getDeclAnnotation(methElt, CompareToMethod.class) != null; + boolean hasEqualsMethodAnno = + atypeFactory.getDeclAnnotation(methElt, EqualsMethod.class) != null; + boolean hasInternMethodAnno = + atypeFactory.getDeclAnnotation(methElt, InternMethod.class) != null; + int params = methElt.getParameters().size(); + if (hasCompareToMethodAnno && !(params == 1 || params == 2)) { + checker.reportError( + node, + "invalid.method.annotation", + "@CompareToMethod", + "1 or 2", + methElt, + params); + } else if (hasEqualsMethodAnno && !(params == 1 || params == 2)) { + checker.reportError( + node, "invalid.method.annotation", "@EqualsMethod", "1 or 2", methElt, params); + } else if (hasInternMethodAnno && !(params == 0)) { + checker.reportError( + node, "invalid.method.annotation", "@InternMethod", "0", methElt, params); + } + + return super.visitMethod(node, p); + } + /** * Method to implement the @UsesObjectEquals functionality. If a class is annotated * with @UsesObjectEquals, it must: @@ -530,9 +562,16 @@ public Boolean visitReturn(ReturnTree tree, Void p) { } }; + boolean hasCompareToMethodAnno = + atypeFactory.getDeclAnnotation(enclosingMethod, CompareToMethod.class) != null; + boolean hasEqualsMethodAnno = + atypeFactory.getDeclAnnotation(enclosingMethod, EqualsMethod.class) != null; + int params = enclosingMethod.getParameters().size(); + // Determine whether or not the "then" statement of the if has a single // "return 0" statement (for the Comparator.compare heuristic). - if (overrides(enclosingMethod, Comparator.class, "compare")) { + if (overrides(enclosingMethod, Comparator.class, "compare") + || (hasCompareToMethodAnno && params == 2)) { final boolean returnsZero = new Heuristics.Within(new Heuristics.OfKind(Tree.Kind.IF, matcherIfReturnsZero)) .match(getCurrentPath()); @@ -541,20 +580,27 @@ public Boolean visitReturn(ReturnTree tree, Void p) { return false; } - assert enclosingMethod.getParameters().size() == 2; + assert params == 2; Element p1 = enclosingMethod.getParameters().get(0); Element p2 = enclosingMethod.getParameters().get(1); return (p1.equals(lhs) && p2.equals(rhs)) || (p1.equals(rhs) && p2.equals(lhs)); - } else if (overrides(enclosingMethod, Object.class, "equals")) { - assert enclosingMethod.getParameters().size() == 1; + } else if (overrides(enclosingMethod, Object.class, "equals") + || (hasEqualsMethodAnno && params == 1)) { + assert params == 1; Element param = enclosingMethod.getParameters().get(0); Element thisElt = getThis(trees.getScope(getCurrentPath())); assert thisElt != null; return (thisElt.equals(lhs) && param.equals(rhs)) || (thisElt.equals(rhs) && param.equals(lhs)); - } else if (overrides(enclosingMethod, Comparable.class, "compareTo")) { + } else if (hasEqualsMethodAnno && params == 2) { + Element p1 = enclosingMethod.getParameters().get(0); + Element p2 = enclosingMethod.getParameters().get(1); + return (p1.equals(lhs) && p2.equals(rhs)) || (p1.equals(rhs) && p2.equals(lhs)); + + } else if (overrides(enclosingMethod, Comparable.class, "compareTo") + || (hasCompareToMethodAnno && params == 1)) { final boolean returnsZero = new Heuristics.Within(new Heuristics.OfKind(Tree.Kind.IF, matcherIfReturnsZero)) @@ -564,13 +610,14 @@ public Boolean visitReturn(ReturnTree tree, Void p) { return false; } - assert enclosingMethod.getParameters().size() == 1; + assert params == 1; Element param = enclosingMethod.getParameters().get(0); Element thisElt = getThis(trees.getScope(getCurrentPath())); assert thisElt != null; return (thisElt.equals(lhs) && param.equals(rhs)) || (thisElt.equals(rhs) && param.equals(lhs)); } + return false; } diff --git a/checker/src/main/java/org/checkerframework/checker/interning/messages.properties b/checker/src/main/java/org/checkerframework/checker/interning/messages.properties index 723998428959..435cb6541eab 100644 --- a/checker/src/main/java/org/checkerframework/checker/interning/messages.properties +++ b/checker/src/main/java/org/checkerframework/checker/interning/messages.properties @@ -5,3 +5,4 @@ overrides.equals=annotated with @UsesObjectEquals but overrides .equals(Object) superclass.notannotated=superclass must be annotated with @UsesObjectEquals superclass.annotated=subclasses must also be annotated with @UsesObjectEquals interned.object.creation=Cannot statically verify that a new object of an @Interned class is @Interned +invalid.method.annotation=%s applies to a method with %s formal parameters; %s has %d diff --git a/checker/src/main/java/org/checkerframework/checker/interning/qual/CompareToMethod.java b/checker/src/main/java/org/checkerframework/checker/interning/qual/CompareToMethod.java new file mode 100644 index 000000000000..8fc5212de466 --- /dev/null +++ b/checker/src/main/java/org/checkerframework/checker/interning/qual/CompareToMethod.java @@ -0,0 +1,21 @@ +package org.checkerframework.checker.interning.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; + +/** + * Method declaration annotation that indicates a method has a specification like {@code + * compareTo()} or {@code compare()}. The Interning Checker permits use of {@code if (this == arg) { + * return 0; }} or {@code if (arg1 == arg2) { return 0; }} within the body. + * + * @checker_framework.manual #interning-checker Interning Checker + */ +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@InheritedAnnotation +public @interface CompareToMethod {} diff --git a/checker/src/main/java/org/checkerframework/checker/interning/qual/EqualsMethod.java b/checker/src/main/java/org/checkerframework/checker/interning/qual/EqualsMethod.java new file mode 100644 index 000000000000..727024e27598 --- /dev/null +++ b/checker/src/main/java/org/checkerframework/checker/interning/qual/EqualsMethod.java @@ -0,0 +1,21 @@ +package org.checkerframework.checker.interning.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; + +/** + * Method declaration annotation that indicates a method has a specification like {@code equals()}. + * The Interning Checker permits use of {@code this == arg} within the body. Can also be applied to + * a static two-argument method, in which case {@code arg1 == arg2} is permitted within the body. + * + * @checker_framework.manual #interning-checker Interning Checker + */ +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@InheritedAnnotation +public @interface EqualsMethod {} diff --git a/checker/src/main/java/org/checkerframework/checker/interning/qual/InternMethod.java b/checker/src/main/java/org/checkerframework/checker/interning/qual/InternMethod.java index 7385ba20cf0b..9157a7288605 100644 --- a/checker/src/main/java/org/checkerframework/checker/interning/qual/InternMethod.java +++ b/checker/src/main/java/org/checkerframework/checker/interning/qual/InternMethod.java @@ -2,10 +2,10 @@ import java.lang.annotation.Documented; import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.InheritedAnnotation; /** * Method declaration annotation used to indicate that this method may be invoked on an uninterned @@ -16,5 +16,5 @@ @Documented @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) -@Inherited +@InheritedAnnotation public @interface InternMethod {} diff --git a/checker/src/testannotations/java/com/sun/istack/internal/Interned.java b/checker/src/testannotations/java/com/sun/istack/internal/Interned.java index 44e21dfa5bb3..46f2493ff236 100644 --- a/checker/src/testannotations/java/com/sun/istack/internal/Interned.java +++ b/checker/src/testannotations/java/com/sun/istack/internal/Interned.java @@ -5,7 +5,6 @@ import java.lang.annotation.Documented; import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -13,5 +12,4 @@ @Documented @Retention(RetentionPolicy.CLASS) @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE}) -@Inherited public @interface Interned {} diff --git a/checker/tests/interning/HeuristicsTest.java b/checker/tests/interning/HeuristicsTest.java index b617f7e67599..128387a574ad 100644 --- a/checker/tests/interning/HeuristicsTest.java +++ b/checker/tests/interning/HeuristicsTest.java @@ -1,4 +1,6 @@ import java.util.Comparator; +import org.checkerframework.checker.interning.qual.CompareToMethod; +import org.checkerframework.checker.interning.qual.EqualsMethod; public class HeuristicsTest implements Comparable { @@ -29,6 +31,80 @@ public boolean equals(Object o) { return false; } + @EqualsMethod + @org.checkerframework.dataflow.qual.Pure + public boolean equals2(Object o) { + // Using == is OK if it's the first statement in the equals method + // and it compares "this" against the argument. + if (this == o) { + return true; + } + // Not the first statement in the method. + // :: error: (not.interned) + if (o == this) { + return true; + } + return false; + } + + @org.checkerframework.dataflow.qual.Pure + public boolean equals3(Object o) { + // Not equals() or annotated as @EqualsMethod. + // :: error: (not.interned) + if (this == o) { + return true; + } + // Not the first statement in the method. + // :: error: (not.interned) + if (o == this) { + return true; + } + return false; + } + + @EqualsMethod + @org.checkerframework.dataflow.qual.Pure + public static boolean equals4(Object thisOne, Object o) { + // Using == is OK if it's the first statement in the equals method + // and it compares "this" against the argument. + if (thisOne == o) { + return true; + } + // Not the first statement in the method. + // :: error: (not.interned) + if (o == thisOne) { + return true; + } + return false; + } + + @org.checkerframework.dataflow.qual.Pure + public static boolean equals5(Object thisOne, Object o) { + // Not equals() or annotated as @EqualsMethod. + // :: error: (not.interned) + if (thisOne == o) { + return true; + } + // Not the first statement in the method. + // :: error: (not.interned) + if (o == thisOne) { + return true; + } + return false; + } + + @EqualsMethod + // :: error: (invalid.method.annotation) + public boolean equals6() { + return true; + } + + @EqualsMethod + // :: error: (invalid.method.annotation) + public boolean equals7(int a, int b, int c) { + return true; + } + @Override @org.checkerframework.dataflow.qual.Pure public int compareTo(HeuristicsTest o) { @@ -46,6 +122,82 @@ public int compareTo(HeuristicsTest o) { return 0; } + @CompareToMethod + @org.checkerframework.dataflow.qual.Pure + public int compareTo2(HeuristicsTest o) { + // Using == is OK if it's the first statement in the equals method + // and it compares "this" against the argument. + + if (o == this) { + return 0; + } + // Not the first statement in the method. + // :: error: (not.interned) + if (this == o) { + return 0; + } + return 0; + } + + @org.checkerframework.dataflow.qual.Pure + public int compareTo3(HeuristicsTest o) { + // Not compareTo or annotated as @CompareToMethod + // :: error: (not.interned) + if (o == this) { + return 0; + } + // Not the first statement in the method. + // :: error: (not.interned) + if (this == o) { + return 0; + } + return 0; + } + + @CompareToMethod + @org.checkerframework.dataflow.qual.Pure + public static int compareTo4(HeuristicsTest thisOne, HeuristicsTest o) { + // Using == is OK if it's the first statement in the equals method + // and it compares "this" against the argument. + + if (o == thisOne) { + return 0; + } + // Not the first statement in the method. + // :: error: (not.interned) + if (thisOne == o) { + return 0; + } + return 0; + } + + @org.checkerframework.dataflow.qual.Pure + public static int compareTo5(HeuristicsTest thisOne, HeuristicsTest o) { + // Not compareTo or annotated as @CompareToMethod + // :: error: (not.interned) + if (o == thisOne) { + return 0; + } + // Not the first statement in the method. + // :: error: (not.interned) + if (thisOne == o) { + return 0; + } + return 0; + } + + @EqualsMethod + // :: error: (invalid.method.annotation) + public boolean compareTo6() { + return true; + } + + @EqualsMethod + // :: error: (invalid.method.annotation) + public boolean compareTo7(int a, int b, int c) { + return true; + } + public boolean optimizeEqualsClient(Object a, Object b, Object[] arr) { // Using == is OK if it's the left-hand side of an || whose right-hand // side is a call to equals with the same arguments. diff --git a/docs/manual/interning-checker.tex b/docs/manual/interning-checker.tex index 258ff3e22c53..10ddbc4592f8 100644 --- a/docs/manual/interning-checker.tex +++ b/docs/manual/interning-checker.tex @@ -141,6 +141,16 @@ is a method declaration annotation that indicates that this method returns an interned object and may be invoked on an uninterned object. See Section~\ref{interning-intern-methods} for more details. + +\item[\refqualclass{checker/interning/qual}{EqualsMethod}] + is a method declaration annotation that indicates that this method + has a specification like \. The Interning Checker permits use + of \ within the body. + +\item[\refqualclass{checker/interning/qual}{CompareToMethod}] + is a method declaration annotation that indicates that this method + has a specification like \. The Interning Checker permits use + of \ within the body. \end{description} \sectionAndLabel{Annotating your code with \code{@Interned}}{interning-annotating} diff --git a/framework/src/main/java/org/checkerframework/framework/util/Heuristics.java b/framework/src/main/java/org/checkerframework/framework/util/Heuristics.java index e898f4afda6c..a0d87e3c50b3 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/Heuristics.java +++ b/framework/src/main/java/org/checkerframework/framework/util/Heuristics.java @@ -76,6 +76,12 @@ public Boolean visitParenthesized(ParenthesizedTree node, Void p) { return visit(node.getExpression(), p); } + /** + * Returns true if the given path matches this Matcher. + * + * @param path the path to test + * @return true if the given path matches this Matcher + */ public boolean match(TreePath path) { return visit(path.getLeaf(), null); } @@ -126,6 +132,9 @@ public boolean match(TreePath path) { * the leaf of a path, ignoring all other parts of it. */ public static class Within extends Matcher { + /** + * The matcher that {@code Within.match} will try, on every parent of the path it is given. + */ private final Matcher matcher; /** From 70cc4f25538208e355458c59599402dc24705545 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 6 Jul 2020 13:21:44 -0700 Subject: [PATCH 010/138] Fix indentation --- .../java/org/checkerframework/framework/qual/Covariant.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/main/java/org/checkerframework/framework/qual/Covariant.java b/framework/src/main/java/org/checkerframework/framework/qual/Covariant.java index 58b91c209884..e42fe5b95fdd 100644 --- a/framework/src/main/java/org/checkerframework/framework/qual/Covariant.java +++ b/framework/src/main/java/org/checkerframework/framework/qual/Covariant.java @@ -22,7 +22,7 @@ *

Here is an example use: * *

{@code @Covariant(0)
- *  public interface Iterator { ... }
+ * public interface Iterator { ... }
  * }
* * @checker_framework.manual #covariant-type-parameters Covariant type parameters From 83305b237e2543995a04b900bd039973ca0fa6a4 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 6 Jul 2020 13:30:54 -0700 Subject: [PATCH 011/138] Handle boxed primitives in the Signedness Checker --- .../checker/signedness/qual/Signed.java | 16 ++-- checker/tests/signedness/BoxedPrimitives.java | 83 +++++++++++++++++++ framework/tests/all-systems/GetClassTest.java | 6 +- framework/tests/all-systems/Issue457.java | 1 + 4 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 checker/tests/signedness/BoxedPrimitives.java diff --git a/checker/src/main/java/org/checkerframework/checker/signedness/qual/Signed.java b/checker/src/main/java/org/checkerframework/checker/signedness/qual/Signed.java index 3af02116d872..f7195b4a7f7c 100644 --- a/checker/src/main/java/org/checkerframework/checker/signedness/qual/Signed.java +++ b/checker/src/main/java/org/checkerframework/checker/signedness/qual/Signed.java @@ -28,16 +28,14 @@ TypeKind.FLOAT, TypeKind.DOUBLE, TypeKind.CHAR - } - - // This is commented out until implicitly signed boxed types are implemented - // correctly. - - /*, + }, types = { java.lang.Byte.class, - java.lang.Short.class, java.lang.Integer.class, - java.lang.Long.class - }*/ ) + java.lang.Long.class, + java.lang.Short.class, + java.lang.Float.class, + java.lang.Double.class, + java.lang.Character.class + }) public @interface Signed {} diff --git a/checker/tests/signedness/BoxedPrimitives.java b/checker/tests/signedness/BoxedPrimitives.java new file mode 100644 index 000000000000..010d7d2c101d --- /dev/null +++ b/checker/tests/signedness/BoxedPrimitives.java @@ -0,0 +1,83 @@ +import java.util.LinkedList; +import org.checkerframework.checker.signedness.qual.Signed; +import org.checkerframework.checker.signedness.qual.Unsigned; + +public class BoxedPrimitives { + + @Signed int si; + @Unsigned int ui; + + @Signed Integer sbi; + @Unsigned Integer ubi; + + void argSigned(@Signed int x) { + si = x; + sbi = x; + // :: error: (assignment.type.incompatible) + ui = x; + // :: error: (assignment.type.incompatible) + ubi = x; + } + + void argUnsigned(@Unsigned int x) { + // :: error: (assignment.type.incompatible) + si = x; + // :: error: (assignment.type.incompatible) + sbi = x; + ui = x; + ubi = x; + } + + void argSignedBoxed(@Signed Integer x) { + si = x; + sbi = x; + // :: error: (assignment.type.incompatible) + ui = x; + // :: error: (assignment.type.incompatible) + ubi = x; + } + + void argUnsignedBoxed(@Unsigned Integer x) { + // :: error: (assignment.type.incompatible) + si = x; + // :: error: (assignment.type.incompatible) + sbi = x; + ui = x; + ubi = x; + } + + void client() { + argSigned(si); + argSignedBoxed(si); + argSigned(sbi); + argSignedBoxed(sbi); + // :: error: (argument.type.incompatible) + argUnsigned(si); + // :: error: (argument.type.incompatible) + argUnsignedBoxed(si); + // :: error: (argument.type.incompatible) + argUnsigned(sbi); + // :: error: (argument.type.incompatible) + argUnsignedBoxed(sbi); + // :: error: (argument.type.incompatible) + argSigned(ui); + // :: error: (argument.type.incompatible) + argSignedBoxed(ui); + // :: error: (argument.type.incompatible) + argSigned(ubi); + // :: error: (argument.type.incompatible) + argSignedBoxed(ubi); + argUnsigned(ui); + argUnsignedBoxed(ui); + argUnsigned(ubi); + argUnsignedBoxed(ubi); + } + + public LinkedList commands; + + void forLoop() { + for (Integer ix : this.commands) { + argSigned(ix); + } + } +} diff --git a/framework/tests/all-systems/GetClassTest.java b/framework/tests/all-systems/GetClassTest.java index 118eecbdc568..8fdfa3746f62 100644 --- a/framework/tests/all-systems/GetClassTest.java +++ b/framework/tests/all-systems/GetClassTest.java @@ -11,8 +11,10 @@ void context() { // Type arguments don't match @SuppressWarnings("fenum:assignment.type.incompatible") Class b = i.getClass(); - // Type arguments don't match - @SuppressWarnings("fenum:assignment.type.incompatible") + @SuppressWarnings({ + "fenum:assignment.type.incompatible", // Type arguments don't match + "signedness:assignment.type.incompatible" // Type arguments don't match + }) Class c = i.getClass(); Class d = i.getClass(); diff --git a/framework/tests/all-systems/Issue457.java b/framework/tests/all-systems/Issue457.java index e1a55ebfa7be..0d825135658d 100644 --- a/framework/tests/all-systems/Issue457.java +++ b/framework/tests/all-systems/Issue457.java @@ -6,6 +6,7 @@ class Issue457 { public void f(T t) { final T obj = t; + @SuppressWarnings("signedness:assignment.type.incompatible") // cast Float objFloat = (obj instanceof Float) ? (Float) obj : null; // An error will be emitted on this line before the fix for Issue457 From 7f44828feaea78bc3b6b3a2ff4b09360fea59083 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 6 Jul 2020 13:37:47 -0700 Subject: [PATCH 012/138] License clarification; fixes #3429 --- LICENSE.txt | 42 ++++++++++++++++++++++----------------- checker-qual/build.gradle | 8 +++++--- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index c42051afa0ab..b233dc6aa64c 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -6,34 +6,40 @@ Most of the Checker Framework is licensed under the GNU General Public License, version 2 (GPL2), with the classpath exception. The text of this license appears below. This is the same license used for OpenJDK. -A few parts of the Checker Framework have more permissive licenses. +A few parts of the Checker Framework have more permissive licenses, notably +the parts that you might want to include with your own program. * The annotations and utility files are licensed under the MIT License. - (The text of this license also appears below.) More specifically, all - the parts of the Checker Framework that you might want to include with - your own program use the MIT License. This is the checker-qual*.jar and - checker-compat-qual*.jar files and all the files that appear in them: - every file in a qual/ directory, plus utility files such as - NullnessUtil.java, RegexUtil.java, SignednessUtil.java, etc. In - addition, the cleanroom implementations of third-party annotations, - which the Checker Framework recognizes as aliases for its own - annotations, are licensed under the MIT License. - -Some external libraries that are included with the Checker Framework have -different licenses. + (The text of this license also appears below.) This applies to the + checker-qual*.jar and all the files that appear in it: every file in a + qual/ directory, plus utility files FormatUtil.java, + I18nFormatUtil.java, NullnessUtil.java, Opt.java, + PurityUnqualified.java, RegexUtil.java, SignednessUtil.java, and + UnitsTools.java. It also applies to other utility files + (SignednessUtilExtra.java) and to the cleanroom implementations of + third-party annotations (in checker/src/testannotations/ and in + framework/src/main/java/org/jmlspecs/). + +The Checker Framework includes annotations for some libraries. Those in +.astub files use the MIT License. Those in https://github.com/typetools/jdk +(which appears in the annotated-jdk directory of file checker.jar) use the +GPL2 license. + +Some external libraries that are included with the Checker Framework +distribution have different licenses. Here are some examples. * javaparser is dual licensed under the LGPL or the Apache license -- you may use it under whichever one you want. (The javaparser source code contains a file with the text of the GPL, but it is not clear why, since - javaparser does not use the GPL.) See file stubparser/LICENSE - and the source code of all its files. + javaparser does not use the GPL.) See + https://github.com/typetools/stubparser . + + * Annotation Tools (https://github.com/typetools/annotation-tools) uses + the MIT license. * Libraries in plume-lib (https://github.com/plume-lib/) are licensed under the MIT License. -The Checker Framework includes annotations for some libraries. Each annotated -library uses the same license as the unannotated version of the library. - =========================================================================== The GNU General Public License (GPL) diff --git a/checker-qual/build.gradle b/checker-qual/build.gradle index dc4437548cf7..6acd2f2be6ee 100644 --- a/checker-qual/build.gradle +++ b/checker-qual/build.gradle @@ -21,13 +21,15 @@ task copySources(type: Copy) { include '**/org/checkerframework/**/qual/*.java' include '**/PurityUnqualified.java' // TODO: Should we move this into a qual directory? // Utility classes + // If you change this list, also update ../LICENSE.txt . include "**/FormatUtil.java" + include "**/I18nFormatUtil.java" include "**/NullnessUtil.java" + include "**/Opt.java" include "**/RegexUtil.java" - include "**/UnitsTools.java" include "**/SignednessUtil.java" - include "**/I18nFormatUtil.java" - include '**/Opt.java' + // include "**/SignednessUtilExtra.java" + include "**/UnitsTools.java" // Make files read only. fileMode(0444) From 2a9a1315461aaf83480d8519111d9942a3c32d12 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Mon, 6 Jul 2020 15:03:24 -0700 Subject: [PATCH 013/138] Add TypeSystemError exception --- .../common/basetype/BaseTypeChecker.java | 6 ++-- .../framework/source/SourceChecker.java | 15 +++++++++ .../framework/type/AnnotatedTypeFactory.java | 20 ++++++------ .../javacutil/TypeSystemError.java | 32 +++++++++++++++++++ 4 files changed, 60 insertions(+), 13 deletions(-) create mode 100644 javacutil/src/main/java/org/checkerframework/javacutil/TypeSystemError.java diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java index 78b20dd3dbad..0ddf98809519 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java @@ -41,6 +41,7 @@ import org.checkerframework.javacutil.AnnotationProvider; import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.InternalUtils; +import org.checkerframework.javacutil.TypeSystemError; import org.checkerframework.javacutil.UserError; /** @@ -298,10 +299,9 @@ public static T invokeConstructorFor( } catch (Throwable t) { if (t instanceof InvocationTargetException) { Throwable err = t.getCause(); - if (err instanceof UserError) { - UserError ue = (UserError) err; + if (err instanceof UserError || err instanceof TypeSystemError) { // Don't add another stack frame, just show the message. - throw ue; + throw (RuntimeException) err; } throw new BugInCF( String.format( 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 8c19e2e517d2..0d86bfbff709 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -68,6 +68,7 @@ import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.SystemUtil; import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TypeSystemError; import org.checkerframework.javacutil.UserError; import org.plumelib.util.UtilPlume; @@ -780,6 +781,8 @@ public void run() { } } catch (UserError ce) { logUserError(ce); + } catch (TypeSystemError ce) { + logTypeSystemError(ce); } catch (BugInCF ce) { logBugInCF(ce); } catch (Throwable t) { @@ -895,6 +898,8 @@ public void typeProcess(TypeElement e, TreePath p) { warnUnneededSuppressions(); } catch (UserError ce) { logUserError(ce); + } catch (TypeSystemError ce) { + logTypeSystemError(ce); } catch (BugInCF ce) { logBugInCF(ce); } catch (Throwable t) { @@ -2324,6 +2329,16 @@ private void logUserError(UserError ce) { printMessage(msg); } + /** + * Log (that is, print) a type system error. + * + * @param ce the type system error to output + */ + private void logTypeSystemError(TypeSystemError ce) { + String msg = ce.getMessage(); + printMessage(msg); + } + /** * Log (that is, print) an internal error in the framework or a checker. * diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java index f9e85d7badee..986e77bb78c3 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java @@ -104,6 +104,7 @@ import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.Pair; import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TypeSystemError; import org.checkerframework.javacutil.TypesUtils; import org.checkerframework.javacutil.UserError; import org.checkerframework.javacutil.trees.DetachedVarSymbol; @@ -488,9 +489,7 @@ public AnnotatedTypeFactory(BaseTypeChecker checker) { */ private void checkSupportedQuals() { if (supportedQuals.isEmpty()) { - // This is throwing a CF bug, but it could also be a bug in the checker rather than in - // the framework itself. - throw new BugInCF("Found no supported qualifiers."); + throw new TypeSystemError("Found no supported qualifiers."); } for (Class annotationClass : supportedQuals) { // Check @Target values @@ -519,7 +518,7 @@ private void checkSupportedQuals() { buf.append(otherElementTypes.get(i)); } buf.append("."); - throw new BugInCF(buf.toString()); + throw new TypeSystemError(buf.toString()); } } } @@ -550,7 +549,8 @@ protected void checkInvalidOptionsInferSignatures() { protected void postInit() { this.qualHierarchy = createQualifierHierarchy(); if (qualHierarchy == null) { - throw new BugInCF("AnnotatedTypeFactory with null qualifier hierarchy not supported."); + throw new TypeSystemError( + "AnnotatedTypeFactory with null qualifier hierarchy not supported."); } this.typeHierarchy = createTypeHierarchy(); this.typeVarSubstitutor = createTypeVariableSubstitutor(); @@ -716,7 +716,7 @@ protected static QualifierHierarchy createQualifierHierarchy( if (typeQualifier.getAnnotation(SubtypeOf.class) != null) { // This is currently not supported. At some point we might add // polymorphic qualifiers with upper and lower bounds. - throw new BugInCF( + throw new TypeSystemError( "AnnotatedTypeFactory: " + typeQualifier + " is polymorphic and specifies super qualifiers. " @@ -725,7 +725,7 @@ protected static QualifierHierarchy createQualifierHierarchy( continue; } if (typeQualifier.getAnnotation(SubtypeOf.class) == null) { - throw new BugInCF( + throw new TypeSystemError( "AnnotatedTypeFactory: %s does not specify its super qualifiers.%n" + "Add an @org.checkerframework.framework.qual.SubtypeOf annotation to it,%n" + "or if it is an alias, exclude it from `createSupportedTypeQualifiers()`.%n", @@ -735,14 +735,14 @@ protected static QualifierHierarchy createQualifierHierarchy( typeQualifier.getAnnotation(SubtypeOf.class).value(); for (Class superQualifier : superQualifiers) { if (!supportedTypeQualifiers.contains(superQualifier)) { - throw new BugInCF( + throw new TypeSystemError( "Found unsupported qualifier in SubTypeOf: %s on qualifier: %s", superQualifier.getCanonicalName(), typeQualifier.getCanonicalName()); } if (superQualifier.getAnnotation(PolymorphicQualifier.class) != null) { // This is currently not supported. No qualifier can have a polymorphic // qualifier as super qualifier. - throw new BugInCF( + throw new TypeSystemError( "Found polymorphic qualifier in SubTypeOf: %s on qualifier: %s", superQualifier.getCanonicalName(), typeQualifier.getCanonicalName()); } @@ -754,7 +754,7 @@ protected static QualifierHierarchy createQualifierHierarchy( QualifierHierarchy hierarchy = factory.build(); if (!hierarchy.isValid()) { - throw new BugInCF( + throw new TypeSystemError( "AnnotatedTypeFactory: invalid qualifier hierarchy: " + hierarchy.getClass() + " " diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TypeSystemError.java b/javacutil/src/main/java/org/checkerframework/javacutil/TypeSystemError.java new file mode 100644 index 000000000000..1fd3a52bb99d --- /dev/null +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TypeSystemError.java @@ -0,0 +1,32 @@ +package org.checkerframework.javacutil; + +/** + * Exception type indicating a mistake by a type system built using the Checker Framework. For + * example, misusing a meta-annotation on a qualifier. These should only be thrown by the framework + * package. + */ +@SuppressWarnings("serial") +public class TypeSystemError extends RuntimeException { + + /** + * Constructs a new TypeSystemError with the specified detail message. + * + * @param message the detail message + */ + public TypeSystemError(String message) { + super(message); + if (message == null) { + throw new Error("Must have a detail message."); + } + } + + /** + * Constructs a new TypeSystemError with a detail message composed from the given arguments. + * + * @param fmt the format string + * @param args the arguments for the format string + */ + public TypeSystemError(String fmt, Object... args) { + this(String.format(fmt, args)); + } +} From fa95fd5968b6fcf07922772b9c301636150d8a68 Mon Sep 17 00:00:00 2001 From: Olek Wojnar <3818875+olekw@users.noreply.github.com> Date: Mon, 6 Jul 2020 18:58:28 -0400 Subject: [PATCH 014/138] Remove external links to images Images are already available locally. This change will prevent unnecessary Internet access and associated privacy concerns. (Imported from Debian packaging) --- docs/developer/gsoc-ideas.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/developer/gsoc-ideas.html b/docs/developer/gsoc-ideas.html index e0d1c158b93d..4a1b4fea3df7 100644 --- a/docs/developer/gsoc-ideas.html +++ b/docs/developer/gsoc-ideas.html @@ -5,11 +5,11 @@ + "../logo/Checkmark/CFCheckmark_favicon.png"> -Checker Framework logo +Checker Framework logo

GSoC ideas 2020

From 423d0a2f7a0f8b76688d1a97e21e46f3d9700456 Mon Sep 17 00:00:00 2001 From: Martin Kellogg Date: Mon, 6 Jul 2020 18:39:38 -0700 Subject: [PATCH 015/138] Fix unprintable class when param types are inferred from assignments --- .../wholeprograminference/WholeProgramInferenceScenes.java | 6 +++++- .../wholeprograminference/scenelib/ASceneWrapper.java | 2 +- .../non-annotated/ExpectedErrors.java | 7 +++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceScenes.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceScenes.java index c1644d5bbf55..342e084d19e4 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceScenes.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceScenes.java @@ -229,7 +229,11 @@ public void updateFromLocalAssignment( String className = getEnclosingClassName(lhs); String jaifPath = storage.getJaifPath(className); - AClass clazz = storage.getAClass(className, jaifPath); + AClass clazz = + storage.getAClass( + className, + jaifPath, + (ClassSymbol) TreeUtils.elementFromDeclaration(classTree)); ExecutableElement methodElt = TreeUtils.elementFromDeclaration(methodTree); AMethod method = clazz.methods.getVivify(JVMNames.getJVMMethodSignature(methodElt)); method.setFieldsFromMethodElement(methodElt); diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/scenelib/ASceneWrapper.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/scenelib/ASceneWrapper.java index fd07eb3b7dbd..d292eab76b87 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/scenelib/ASceneWrapper.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/scenelib/ASceneWrapper.java @@ -163,7 +163,7 @@ public void writeToFile( /** * Updates the symbol information stored in AClass for the given class. May be called multiple * times (and needs to be if the second parameter was null the first time it was called; only - * some calls provide the symbol inforamtion). + * some calls provide the symbol information). * * @param aClass the class representation in which the symbol information is to be updated * @param classSymbol the source of the symbol information; may be null, in which case this diff --git a/framework/tests/whole-program-inference/non-annotated/ExpectedErrors.java b/framework/tests/whole-program-inference/non-annotated/ExpectedErrors.java index f26cd258a2fd..09d6c5bb0a8f 100644 --- a/framework/tests/whole-program-inference/non-annotated/ExpectedErrors.java +++ b/framework/tests/whole-program-inference/non-annotated/ExpectedErrors.java @@ -234,4 +234,11 @@ void test() { expectsSibling1(field2); } } + + class AssignParam { + public void f(@WholeProgramInferenceBottom Object param) { + // :: error: assignment.type.incompatible + param = ((@Top Object) null); + } + } } From 99406eaa6b58d17b5a446cc8ad0b219f2fea312a Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 7 Jul 2020 08:08:00 -0700 Subject: [PATCH 016/138] Emphasize that -AuseConservativeDefaultsForUncheckedCode suppresses warnings --- docs/manual/annotating-libraries.tex | 2 +- docs/manual/introduction.tex | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/manual/annotating-libraries.tex b/docs/manual/annotating-libraries.tex index cb4012a224c3..9de38f41b322 100644 --- a/docs/manual/annotating-libraries.tex +++ b/docs/manual/annotating-libraries.tex @@ -367,7 +367,7 @@ annotation. For classes without \<@AnnotatedFor>, the checker uses conservative defaults (see Section~\ref{defaults-classfile}) for any type use with no explicit -user-written annotation, and the checker issues no warnings. +user-written annotation, \emph{and} the checker issues no warnings. \end{sloppypar} The \refqualclass{framework/qual}{AnnotatedFor} annotation, written on a diff --git a/docs/manual/introduction.tex b/docs/manual/introduction.tex index ca41f7bc1ca2..b01e64043c9b 100644 --- a/docs/manual/introduction.tex +++ b/docs/manual/introduction.tex @@ -635,7 +635,8 @@ to differ, just as Java does. See Section~\ref{covariant-type-parameters} and Section~\ref{invariant-arrays}. \item \<-AuseConservativeDefaultsForUncheckedCode> - Enables/disables conservative defaults in unchecked code. Takes arguments ``source,bytecode''. + Enables conservative defaults, and suppresses all type-checking warnings, + in unchecked code. Takes arguments ``source,bytecode''. ``-source,-bytecode'' is the (unsound) default setting. \begin{itemize} \item From a75102868587f570b26a7acdc57368dbe2b2b301 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 7 Jul 2020 08:10:13 -0700 Subject: [PATCH 017/138] Rename WorkingNullness to NullnessSkipSome, simplify command-line argument --- build.gradle | 6 ++++-- .../java/org/checkerframework/dataflow/cfg/CFGBuilder.java | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 87f671a83b80..13dccb5e1997 100644 --- a/build.gradle +++ b/build.gradle @@ -270,6 +270,8 @@ def createCheckTypeTask(projectName, checker, shortName, args = []) { '-processor', "${checker}", '-proc:only', '-Xlint:-processing', + '-Xmaxerrs', '10000', + '-Xmaxwarns', '10000', ] options.compilerArgs += args @@ -636,7 +638,7 @@ subprojects { // TODO: fix or suppress all not.interned warnings and remove the suppression here. createCheckTypeTask(project.name, 'org.checkerframework.checker.interning.InterningChecker', 'Interning', ['-AsuppressWarnings=not.interned']) createCheckTypeTask(project.name, 'org.checkerframework.checker.nullness.NullnessChecker', 'Nullness') - createCheckTypeTask(project.name, 'org.checkerframework.checker.nullness.NullnessChecker', 'WorkingNullness', ['-AskipUses=com.sun.*', '-AskipDefs=org.checkerframework.checker.*|org.checkerframework.common.*|org.checkerframework.framework.*|org.checkerframework.dataflow.cfg.CFGBuilder']) + createCheckTypeTask(project.name, 'org.checkerframework.checker.nullness.NullnessChecker', 'NullnessSkipSome', ['-AskipUses=com.sun.*', '-AskipDefs=org.checkerframework.checker.*|org.checkerframework.common.*|org.checkerframework.framework.*']) createCheckTypeTask(project.name, 'org.checkerframework.framework.util.PurityChecker', 'Purity') createCheckTypeTask(project.name, 'org.checkerframework.checker.signature.SignatureChecker', 'Signature') @@ -798,7 +800,7 @@ subprojects { // Create a nonJunitTests task per project tasks.create(name: 'nonJunitTests', group: 'Verification') { description 'Run all Checker Framework tests except for the Junit tests.' - dependsOn('checkInterning', 'checkPurity', 'checkSignature', 'checkWorkingNullness') + dependsOn('checkInterning', 'checkNullnessSkipSome', 'checkPurity', 'checkSignature') if (project.name.is('framework') || project.name.is('checker')) { dependsOn('checkCompilerMessages', 'jtregTests') } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGBuilder.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGBuilder.java index f42d5a1d4633..ffe8cdadfa12 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGBuilder.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGBuilder.java @@ -209,6 +209,7 @@ * preserving the control flow structure. * */ +@SuppressWarnings("nullness") // TODO public class CFGBuilder { /** This class should never be instantiated. Protected to still allow subclasses. */ From 8f1dfe6601f1690c3b8f92b434928b15f7be4484 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Tue, 7 Jul 2020 09:09:36 -0700 Subject: [PATCH 018/138] Add `isPolymorphicQualifier` method to QualifierHierarchy class. (#3409) * Add isPolymorphicQualifier method to QualifierHierarchy class. * Use java.lang.annotation.Annotation.class as the default value for the meta-annotation PolymorphicQualifier. This makes the code less confusing. * Move static package private methods closer to uses. --- .../framework/qual/PolymorphicQualifier.java | 10 +- .../framework/type/QualifierHierarchy.java | 10 + .../poly/AbstractQualifierPolymorphism.java | 2 +- .../type/poly/QualifierPolymorphism.java | 61 ----- .../framework/util/AnnotatedTypes.java | 3 +- .../util/MultiGraphQualifierHierarchy.java | 214 +++++++++++------- 6 files changed, 149 insertions(+), 151 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/qual/PolymorphicQualifier.java b/framework/src/main/java/org/checkerframework/framework/qual/PolymorphicQualifier.java index efb664a822ce..e3c2573dc20f 100644 --- a/framework/src/main/java/org/checkerframework/framework/qual/PolymorphicQualifier.java +++ b/framework/src/main/java/org/checkerframework/framework/qual/PolymorphicQualifier.java @@ -23,10 +23,12 @@ /** * Indicates which type system this annotation refers to (optional, and usually unnecessary). * When multiple type hierarchies are supported by a single type system, then each polymorphic - * qualifier needs to indicate which sub-hierarchy it belongs to. Do so by passing a qualifier - * from the given hierarchy, by convention the top qualifier. + * qualifier needs to indicate which sub-hierarchy it belongs to. Do so by passing the top + * qualifier from the given hierarchy. + * + * @return the top qualifier in the hierarchy of this qualifier */ - // We use the meaningless PolymorphicQualifier.class as default value and + // We use the meaningless Annotation.class as default value and // then ensure there is a single top qualifier to use. - Class value() default PolymorphicQualifier.class; + Class value() default Annotation.class; } diff --git a/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java index 4d5a52ac84dc..4fcd89f77857 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java @@ -73,6 +73,16 @@ public int getWidth() { */ public abstract AnnotationMirror getPolymorphicAnnotation(AnnotationMirror start); + /** + * Returns {@code true} if the qualifier is a polymorphic qualifier; otherwise, returns {@code + * false}. + * + * @param qualifier qualifier + * @return {@code true} if the qualifier is a polymorphic qualifier; otherwise, returns {@code + * false}. + */ + public abstract boolean isPolymorphicQualifier(AnnotationMirror qualifier); + // ********************************************************************** // Qualifier Hierarchy Queries // ********************************************************************** diff --git a/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java b/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java index 56a1cb84ec51..085c63ed7d0c 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java +++ b/framework/src/main/java/org/checkerframework/framework/type/poly/AbstractQualifierPolymorphism.java @@ -248,7 +248,7 @@ public void resolve( public void resolve( AnnotatedExecutableType functionalInterface, AnnotatedExecutableType memberReference) { for (AnnotationMirror type : functionalInterface.getReturnType().getAnnotations()) { - if (QualifierPolymorphism.hasPolymorphicQualifier(type)) { + if (atypeFactory.getQualifierHierarchy().isPolymorphicQualifier(type)) { // functional interface has a polymorphic qualifier, so they should not be resolved // on memberReference. return; diff --git a/framework/src/main/java/org/checkerframework/framework/type/poly/QualifierPolymorphism.java b/framework/src/main/java/org/checkerframework/framework/type/poly/QualifierPolymorphism.java index e1d7a4026778..bb82ef17dc30 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/poly/QualifierPolymorphism.java +++ b/framework/src/main/java/org/checkerframework/framework/type/poly/QualifierPolymorphism.java @@ -2,14 +2,10 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.NewClassTree; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.Name; import javax.lang.model.element.VariableElement; import org.checkerframework.framework.qual.PolymorphicQualifier; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import org.checkerframework.javacutil.AnnotationUtils; /** * Interface to implement qualifier polymorphism. @@ -20,63 +16,6 @@ */ public interface QualifierPolymorphism { - /** - * Returns the {@link PolymorphicQualifier} meta-annotation on {@code qual} if one exists; - * otherwise return null. - * - * @return the {@link PolymorphicQualifier} meta-annotation on {@code qual} if one exists; - * otherwise return null - */ - static AnnotationMirror getPolymorphicQualifier(AnnotationMirror qual) { - if (qual == null) { - return null; - } - Element qualElt = qual.getAnnotationType().asElement(); - for (AnnotationMirror am : qualElt.getAnnotationMirrors()) { - if (AnnotationUtils.areSameByClass(am, PolymorphicQualifier.class)) { - return am; - } - } - return null; - } - - /** - * Returns true if {@code qual} has the {@link PolymorphicQualifier} meta-annotation. - * - * @param qual an annotation - * @return true if {@code qual} has the {@link PolymorphicQualifier} meta-annotation - */ - static boolean hasPolymorphicQualifier(AnnotationMirror qual) { - return getPolymorphicQualifier(qual) != null; - } - - /** - * If {@code qual} is a polymorphic qualifier, return the class specified by the {@link - * PolymorphicQualifier} meta-annotation on the polymorphic qualifier is returned. Otherwise, - * return null. - * - *

This value identifies the qualifier hierarchy to which this polymorphic qualifier belongs. - * By convention, it is the top qualifier of the hierarchy. Use of {@code - * PolymorphicQualifier.class} is discouraged, because it can lead to ambiguity if used for - * multiple type systems. - * - * @param qual an annotation - * @return the class specified by the {@link PolymorphicQualifier} meta-annotation on {@code - * qual}, if {@code qual} is a polymorphic qualifier; otherwise, null. - * @see org.checkerframework.framework.qual.PolymorphicQualifier#value() - */ - static Name getPolymorphicQualifierElement(AnnotationMirror qual) { - AnnotationMirror poly = getPolymorphicQualifier(qual); - - // System.out.println("poly: " + poly + " pq: " + - // PolymorphicQualifier.class.getCanonicalName()); - if (poly == null) { - return null; - } - Name ret = AnnotationUtils.getElementValueClassName(poly, "value", true); - return ret; - } - /** * Resolves polymorphism annotations for the given type. * diff --git a/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java b/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java index b8ddaf099d12..01adda1d9626 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java +++ b/framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java @@ -45,7 +45,6 @@ import org.checkerframework.framework.type.AsSuperVisitor; import org.checkerframework.framework.type.QualifierHierarchy; import org.checkerframework.framework.type.SyntheticArrays; -import org.checkerframework.framework.type.poly.QualifierPolymorphism; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.ElementUtils; @@ -1410,7 +1409,7 @@ public static void copyOnlyExplicitConstructorAnnotations( // Collect all polymorphic qualifiers; we should substitute them. Set polys = AnnotationUtils.createAnnotationSet(); for (AnnotationMirror anno : returnType.getAnnotations()) { - if (QualifierPolymorphism.hasPolymorphicQualifier(anno)) { + if (atypeFactory.getQualifierHierarchy().isPolymorphicQualifier(anno)) { polys.add(anno); } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/MultiGraphQualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/util/MultiGraphQualifierHierarchy.java index c15f6d8573bf..bd0bd2581df7 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/MultiGraphQualifierHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/util/MultiGraphQualifierHierarchy.java @@ -1,5 +1,6 @@ package org.checkerframework.framework.util; +import java.lang.annotation.Annotation; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -9,6 +10,7 @@ import java.util.Set; import java.util.StringJoiner; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; import javax.lang.model.element.Name; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.Pure; @@ -16,7 +18,6 @@ import org.checkerframework.framework.qual.PolymorphicQualifier; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.QualifierHierarchy; -import org.checkerframework.framework.type.poly.QualifierPolymorphism; import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.BugInCF; @@ -66,7 +67,7 @@ public static class MultiGraphFactory { *

    *
  • the argument to @PolymorphicQualifier (typically the top qualifier in the * hierarchy), or - *
  • "PolymorphicQualifier" if @PolymorphicQualifier is used without an argument, or + *
  • "Annotation" if @PolymorphicQualifier is used without an argument, or *
*/ protected final Map polyQualifiers; @@ -91,17 +92,73 @@ public void addQualifier(AnnotationMirror qual) { return; } - Name pqtopclass = QualifierPolymorphism.getPolymorphicQualifierElement(qual); + Name pqtopclass = getPolymorphicQualifierElement(qual); if (pqtopclass != null) { - AnnotationMirror pqtop = - AnnotationBuilder.fromName(atypeFactory.getElementUtils(), pqtopclass); - // use given top (which might be PolymorphicQualifier) as key + AnnotationMirror pqtop; + if (pqtopclass.contentEquals(Annotation.class.getName())) { + // A @PolymorphicQualifier with no value defaults to Annotation.class. + // That means there is only one top in the hierarchy. The top qualifier + // may not be known at this point, so use the qualifier itself. + // This is changed to top in MultiGraphQualifierHierarchy.addPolyRelations + pqtop = qual; + } else { + pqtop = AnnotationBuilder.fromName(atypeFactory.getElementUtils(), pqtopclass); + } + // use given top (which might be Annotation) as key this.polyQualifiers.put(pqtop, qual); } else { supertypesDirect.put(qual, AnnotationUtils.createAnnotationSet()); } } + /** + * Returns the {@link PolymorphicQualifier} meta-annotation on {@code qual} if one exists; + * otherwise return null. + * + * @param qual qualifier + * @return the {@link PolymorphicQualifier} meta-annotation on {@code qual} if one exists; + * otherwise return null + */ + private static AnnotationMirror getPolymorphicQualifier(AnnotationMirror qual) { + if (qual == null) { + return null; + } + Element qualElt = qual.getAnnotationType().asElement(); + for (AnnotationMirror am : qualElt.getAnnotationMirrors()) { + if (AnnotationUtils.areSameByClass(am, PolymorphicQualifier.class)) { + return am; + } + } + return null; + } + + /** + * If {@code qual} is a polymorphic qualifier, return the class specified by the {@link + * PolymorphicQualifier} meta-annotation on the polymorphic qualifier is returned. + * Otherwise, return null. + * + *

This value identifies the qualifier hierarchy to which this polymorphic qualifier + * belongs. By convention, it is the top qualifier of the hierarchy. Use of {@code + * Annotation.class} is discouraged, because it can lead to ambiguity if used for multiple + * type systems. + * + * @param qual an annotation + * @return the class specified by the {@link PolymorphicQualifier} meta-annotation on {@code + * qual}, if {@code qual} is a polymorphic qualifier; otherwise, null. + * @see org.checkerframework.framework.qual.PolymorphicQualifier#value() + */ + private static Name getPolymorphicQualifierElement(AnnotationMirror qual) { + AnnotationMirror poly = getPolymorphicQualifier(qual); + + // System.out.println("poly: " + poly + " pq: " + + // PolymorphicQualifier.class.getCanonicalName()); + if (poly == null) { + return null; + } + Name ret = AnnotationUtils.getElementValueClassName(poly, "value", true); + return ret; + } + /** * Adds a subtype relationship between the two type qualifiers. Assumes that both qualifiers * are part of the same qualifier hierarchy; callers should ensure this. @@ -159,13 +216,6 @@ protected void assertNotBuilt() { /** The bottom qualifiers of the type hierarchies. TODO: clarify relation to tops. */ protected final Set bottoms; - /** - * Reference to the special qualifier org.checkerframework.framework.qual.PolymorphicQualifier. - * It is used as a key in polyQualifiers, if the qualifier hierarchy consists of a single top - * and no specific qualifier was specified. - */ - protected final AnnotationMirror polymorphicQualifier; - /** * See {@link MultiGraphQualifierHierarchy.MultiGraphFactory#polyQualifiers}. * @@ -195,9 +245,6 @@ public MultiGraphQualifierHierarchy(MultiGraphFactory f, Object... args) { Set newtops = findTops(supertypesTransitive); Set newbottoms = findBottoms(supertypesTransitive); - this.polymorphicQualifier = - AnnotationBuilder.fromClass( - f.atypeFactory.getElementUtils(), PolymorphicQualifier.class); this.polyQualifiers = f.polyQualifiers; addPolyRelations(this, supertypesTransitive, this.polyQualifiers, newtops, newbottoms); @@ -315,9 +362,7 @@ public AnnotationMirror getBottomAnnotation(AnnotationMirror start) { public AnnotationMirror getPolymorphicAnnotation(AnnotationMirror start) { AnnotationMirror top = getTopAnnotation(start); for (AnnotationMirror key : polyQualifiers.keySet()) { - if (key != null - && (AnnotationUtils.areSame(key, top) - || AnnotationUtils.areSame(key, polymorphicQualifier))) { + if (key != null && AnnotationUtils.areSame(key, top)) { return polyQualifiers.get(key); } } @@ -564,77 +609,80 @@ protected void addPolyRelations( return; } + // Handle the case where @PolymorphicQualifier uses the default value Annotation.class. + if (polyQualifiers.size() == 1 && tops.size() == 1) { + Map.Entry entry = + polyQualifiers.entrySet().iterator().next(); + AnnotationMirror poly = entry.getKey(); + AnnotationMirror maybeTop = entry.getValue(); + if (AnnotationUtils.areSameByName(poly, maybeTop)) { + // If the value of @PolymorphicQualifier is the default value, Annotation.class, + // then map is set to polyQual -> polyQual in + // MultiGraphQualifierHierarchy.MultiGraphFactory.addQualifier, + // because the top is unknown there. + // Reset it to top here. + polyQualifiers.put(tops.iterator().next(), poly); + polyQualifiers.remove(poly); + } + } + for (Map.Entry kv : polyQualifiers.entrySet()) { AnnotationMirror declTop = kv.getKey(); AnnotationMirror polyQualifier = kv.getValue(); - if (AnnotationUtils.areSame(declTop, polymorphicQualifier)) { - if (tops.size() == 1) { // un-ambigous single top - AnnotationUtils.updateMappingToImmutableSet(fullMap, polyQualifier, tops); - for (AnnotationMirror bottom : bottoms) { - // Add the polyqualifier as a supertype - // Need to copy over the set as it is unmodifiable. - AnnotationUtils.updateMappingToImmutableSet( - fullMap, bottom, Collections.singleton(polyQualifier)); - } - } else { - throw new BugInCF( - "MultiGraphQualifierHierarchy.addPolyRelations: " - + "incorrect or missing top qualifier given in polymorphic qualifier " - + polyQualifier - + "; declTop = " - + declTop - + "; possible top qualifiers: " - + tops); - } + // Ensure that it's really the top of the hierarchy + Set declSupers = fullMap.get(declTop); + AnnotationMirror polyTop = null; + if (declSupers.isEmpty()) { + polyTop = declTop; } else { - // Ensure that it's really the top of the hierarchy - Set declSupers = fullMap.get(declTop); - AnnotationMirror polyTop = null; - if (declSupers.isEmpty()) { - polyTop = declTop; - } else { - for (AnnotationMirror ds : declSupers) { - if (AnnotationUtils.containsSameByName(tops, ds)) { - polyTop = ds; - } + for (AnnotationMirror ds : declSupers) { + if (AnnotationUtils.containsSameByName(tops, ds)) { + polyTop = ds; } } - boolean found = (polyTop != null); - if (found) { - AnnotationUtils.updateMappingToImmutableSet( - fullMap, polyQualifier, Collections.singleton(polyTop)); - } else { - throw new BugInCF( - "MultiGraphQualifierHierarchy.addPolyRelations: " - + "incorrect top qualifier given in polymorphic qualifier: " - + polyQualifier - + " could not find: " - + polyTop); - } + } + boolean found = (polyTop != null); + if (found) { + AnnotationUtils.updateMappingToImmutableSet( + fullMap, polyQualifier, Collections.singleton(polyTop)); + } else if (AnnotationUtils.areSameByName(polyQualifier, declTop)) { + throw new BugInCF( + "MultiGraphQualifierHierarchy.addPolyRelations: " + + "incorrect or missing top qualifier given in polymorphic qualifier " + + polyQualifier + + "; possible top qualifiers: " + + tops); + } else { + throw new BugInCF( + "MultiGraphQualifierHierarchy.addPolyRelations: " + + "incorrect top qualifier given in polymorphic qualifier: " + + polyQualifier + + " could not find: " + + polyTop); + } - found = false; - AnnotationMirror bottom = null; - outer: - for (AnnotationMirror btm : bottoms) { - for (AnnotationMirror btmsuper : fullMap.get(btm)) { - if (AnnotationUtils.areSameByName(btmsuper, polyTop)) { - found = true; - bottom = btm; - break outer; - } + found = false; + AnnotationMirror bottom = null; + outer: + for (AnnotationMirror btm : bottoms) { + for (AnnotationMirror btmsuper : fullMap.get(btm)) { + if (AnnotationUtils.areSameByName(btmsuper, polyTop)) { + found = true; + bottom = btm; + break outer; } } - if (found) { - AnnotationUtils.updateMappingToImmutableSet( - fullMap, bottom, Collections.singleton(polyQualifier)); - } else { - // TODO: in a type system with a single qualifier this check will fail. - // throw new BugInCF("MultiGraphQualifierHierarchy.addPolyRelations: - // " + - // "incorrect top qualifier given in polymorphic qualifier: " - // - // + polyQualifier + " could not find bottom for: " + polyTop); - } + } + if (found) { + AnnotationUtils.updateMappingToImmutableSet( + fullMap, bottom, Collections.singleton(polyQualifier)); + } else { + // TODO: in a type system with a single qualifier this check will fail. + // throw new BugInCF("MultiGraphQualifierHierarchy.addPolyRelations: + // " + + // "incorrect top qualifier given in polymorphic qualifier: " + // + // + polyQualifier + " could not find bottom for: " + polyTop); } } } @@ -723,8 +771,8 @@ private AnnotationMirror findLubWithPoly(AnnotationMirror poly, AnnotationMirror return getTopAnnotation(poly); } - /** Sees if a particular annotation mirror is a polymorphic qualifier. */ - private boolean isPolymorphicQualifier(AnnotationMirror qual) { + @Override + public boolean isPolymorphicQualifier(AnnotationMirror qual) { return AnnotationUtils.containsSame(polyQualifiers.values(), qual); } From 8896256ffa0d5cc1808d69291177891a3183625b Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 7 Jul 2020 10:32:18 -0700 Subject: [PATCH 019/138] Add contributor --- docs/manual/contributors.tex | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/manual/contributors.tex b/docs/manual/contributors.tex index 1f21d095b1e0..210178a1ec99 100644 --- a/docs/manual/contributors.tex +++ b/docs/manual/contributors.tex @@ -74,6 +74,7 @@ Nikhil Shinde, Nima Karimipour, Oleg Shchelykalnov, +Olek Wojnar, Pascal Wittmann, Patrick Meiring, Paul Vines, From dc530c37903c608f02e6adea752425eae7f06baf Mon Sep 17 00:00:00 2001 From: Martin Kellogg Date: Tue, 7 Jul 2020 11:41:24 -0700 Subject: [PATCH 020/138] Fix null-pointer crash in Ainfer=stubs --- .../wholeprograminference/SceneToStubWriter.java | 15 ++++++++++----- .../MultidimensionalAnnotatedArray.java | 11 +++++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 framework/tests/whole-program-inference/non-annotated/MultidimensionalAnnotatedArray.java diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/SceneToStubWriter.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/SceneToStubWriter.java index 4ce80c0148f9..fbd1b85866fa 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/SceneToStubWriter.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/SceneToStubWriter.java @@ -200,9 +200,13 @@ private static String formatArrayType(ATypeElement scenelibRep, ArrayType javacR * @param levels the number of component types the type should have, derived from the javac * representation * @return a list of the array levels in scenelib's representation, but in the order used by - * javac. Guaranteed to have exactly {@code levels} entries. + * javac. Guaranteed to have exactly {@code levels} entries. Entries may be null, if the + * corresponding parts of {@code scenelibRep} are null. See issue 3422 for an + * example of code that causes a null ATypeElement, because the component type is unknown, + * but the primary type of the array is known. */ - private static List getSceneLibRepInJavacOrder( + private static List<@Nullable ATypeElement> getSceneLibRepInJavacOrder( ATypeElement scenelibRep, int levels) { List result = new ArrayList<>(); ATypeElement array = scenelibRep; @@ -225,12 +229,13 @@ private static List getSceneLibRepInJavacOrder( * using {@link #formatType(ATypeElement, TypeMirror)}. * * @param scenelibRepInJavacOrder the scenelib representation, reordered to match javac's order. - * See {@link #getSceneLibRepInJavacOrder} for an explanation of why this is necessary. + * See {@link #getSceneLibRepInJavacOrder} for an explanation of why this is necessary and + * why the elements may be null. * @param javacRep the javac representation of the array type * @return the type formatted to be written to Java source code, followed by a space character */ private static String formatArrayTypeImpl( - List scenelibRepInJavacOrder, ArrayType javacRep) { + List<@Nullable ATypeElement> scenelibRepInJavacOrder, ArrayType javacRep) { TypeMirror javacComponent = javacRep.getComponentType(); ATypeElement scenelibRep = scenelibRepInJavacOrder.get(0); ATypeElement scenelibComponent = scenelibRepInJavacOrder.get(1); @@ -240,7 +245,7 @@ private static String formatArrayTypeImpl( result += explicitAnno.toString(); result += " "; } - if ("".equals(result)) { + if (result.isEmpty() && scenelibRep != null) { result += formatAnnotations(scenelibRep.tlAnnotationsHere); } result += "[] "; diff --git a/framework/tests/whole-program-inference/non-annotated/MultidimensionalAnnotatedArray.java b/framework/tests/whole-program-inference/non-annotated/MultidimensionalAnnotatedArray.java new file mode 100644 index 000000000000..498751cee3de --- /dev/null +++ b/framework/tests/whole-program-inference/non-annotated/MultidimensionalAnnotatedArray.java @@ -0,0 +1,11 @@ +// test case for https://github.com/typetools/checker-framework/issues/3422 + +import testlib.wholeprograminference.qual.Sibling1; + +public class MultidimensionalAnnotatedArray { + boolean[][] field = getArray(); + + public boolean[] @Sibling1 [] getArray() { + return null; + } +} From 4bd0591172fbf54cf128174589d68aebb2825eab Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 7 Jul 2020 12:02:35 -0700 Subject: [PATCH 021/138] Implement `@FindDistinct` formal parameter annotation --- .../InterningAnnotatedTypeFactory.java | 28 ++++++++++++++---- .../checker/interning/qual/FindDistinct.java | 24 +++++++++++++++ checker/tests/interning/FindDistinctTest.java | 29 +++++++++++++++++++ docs/manual/interning-checker.tex | 9 ++++++ 4 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 checker/src/main/java/org/checkerframework/checker/interning/qual/FindDistinct.java create mode 100644 checker/tests/interning/FindDistinctTest.java diff --git a/checker/src/main/java/org/checkerframework/checker/interning/InterningAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/interning/InterningAnnotatedTypeFactory.java index ffd7f193972d..cb2ba0e93be6 100644 --- a/checker/src/main/java/org/checkerframework/checker/interning/InterningAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/interning/InterningAnnotatedTypeFactory.java @@ -2,6 +2,7 @@ import com.sun.source.tree.BinaryTree; import com.sun.source.tree.CompoundAssignmentTree; +import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.Tree; import com.sun.source.tree.TypeCastTree; import com.sun.tools.javac.code.Symbol.MethodSymbol; @@ -12,8 +13,10 @@ import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.interning.qual.InternMethod; import org.checkerframework.checker.interning.qual.Interned; +import org.checkerframework.checker.interning.qual.InternedDistinct; import org.checkerframework.checker.interning.qual.PolyInterned; import org.checkerframework.checker.interning.qual.UnknownInterned; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; @@ -47,11 +50,14 @@ *

  • is a use of a class declared to be @Interned * * - * This factory extends {@link BaseAnnotatedTypeFactory} and inherits its functionality, including: - * flow-sensitive qualifier inference, qualifier polymorphism (of {@link PolyInterned}), implicit - * annotations via {@link org.checkerframework.framework.qual.DefaultFor} on {@link Interned} (to - * handle cases 1, 2, 4), and user-specified defaults via {@link DefaultQualifier}. Case 5 is - * handled by the stub library. + * This type factory adds {@link InternedDistinct} to formal parameters that have a {@link + * FindDistinct} declaration annotation. + * + *

    This factory extends {@link BaseAnnotatedTypeFactory} and inherits its functionality, + * including: flow-sensitive qualifier inference, qualifier polymorphism (of {@link PolyInterned}), + * implicit annotations via {@link org.checkerframework.framework.qual.DefaultFor} on {@link + * Interned} (to handle cases 1, 2, 4), and user-specified defaults via {@link DefaultQualifier}. + * Case 5 is handled by the stub library. */ public class InterningAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { @@ -59,6 +65,9 @@ public class InterningAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { final AnnotationMirror TOP = AnnotationBuilder.fromClass(elements, UnknownInterned.class); /** The {@link Interned} annotation. */ final AnnotationMirror INTERNED = AnnotationBuilder.fromClass(elements, Interned.class); + /** The {@link InternedDistinct} annotation. */ + final AnnotationMirror INTERNED_DISTINCT = + AnnotationBuilder.fromClass(elements, InternedDistinct.class); /** * Creates a new {@link InterningAnnotatedTypeFactory} that operates on a particular AST. @@ -185,6 +194,15 @@ public Void visitTypeCast(TypeCastTree node, AnnotatedTypeMirror type) { } return super.visitTypeCast(node, type); } + + @Override + public Void visitIdentifier(IdentifierTree node, AnnotatedTypeMirror type) { + Element e = TreeUtils.elementFromTree(node); + if (atypeFactory.getDeclAnnotation(e, FindDistinct.class) != null) { + type.replaceAnnotation(INTERNED_DISTINCT); + } + return super.visitIdentifier(node, type); + } } /** Adds @Interned to enum types. */ diff --git a/checker/src/main/java/org/checkerframework/checker/interning/qual/FindDistinct.java b/checker/src/main/java/org/checkerframework/checker/interning/qual/FindDistinct.java new file mode 100644 index 000000000000..9797d7c23632 --- /dev/null +++ b/checker/src/main/java/org/checkerframework/checker/interning/qual/FindDistinct.java @@ -0,0 +1,24 @@ +package org.checkerframework.checker.interning.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; + +/** + * This formal parameter annotation indicates that the method searches for the given value, using + * reference equality ({@code ==}). + * + *

    Within the method, the formal parameter is treated as {@code @}{@link InternedDistinct}: it + * should be compared with {@code ==} rather than with {@code equals()}. However, any value may be + * passed to the method, and the Interning Checker does not verify that use of {@code ==} within the + * method is logically correct. + * + * @see org.checkerframework.checker.interning.InterningChecker + * @checker_framework.manual #interning-checker Interning Checker + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface FindDistinct {} diff --git a/checker/tests/interning/FindDistinctTest.java b/checker/tests/interning/FindDistinctTest.java new file mode 100644 index 000000000000..c27e7f7445ec --- /dev/null +++ b/checker/tests/interning/FindDistinctTest.java @@ -0,0 +1,29 @@ +import org.checkerframework.checker.interning.qual.FindDistinct; +import org.checkerframework.checker.interning.qual.Interned; +import org.checkerframework.checker.interning.qual.InternedDistinct; + +public class FindDistinctTest { + + public void ok1(@FindDistinct Object o) { + @InternedDistinct Object o2 = o; + } + + public void ok2(@FindDistinct Object findIt, Object other) { + boolean b = findIt == other; + } + + public void useOk1(Object notinterned, @Interned Object interned) { + ok1(notinterned); + ok1(interned); + } + + public void bad1(Object o) { + // :: error: (assignment.type.incompatible) + @InternedDistinct Object o2 = o; + } + + public void bad2(Object findIt, Object other) { + // :: error: (not.interned) + boolean b = findIt == other; + } +} diff --git a/docs/manual/interning-checker.tex b/docs/manual/interning-checker.tex index 10ddbc4592f8..580bfcad7ada 100644 --- a/docs/manual/interning-checker.tex +++ b/docs/manual/interning-checker.tex @@ -151,6 +151,15 @@ is a method declaration annotation that indicates that this method has a specification like \. The Interning Checker permits use of \ within the body. + +\item[\refqualclass{checker/interning/qual}{FindDistinct}] + is a formal parameter declaration annotation that indicates that this + method uses \<==> to perform comparisons against the annotated formal + parameter. A common reason is that the method searches for the formal + parameter in some data structure, using \<==>. Within the method body, + the formal parameter's type is treated as + \refqualclass{checker/interning/qual}{InternedDistinct}, but any value + may be passed to the method. \end{description} \sectionAndLabel{Annotating your code with \code{@Interned}}{interning-annotating} From 999e2aac94e7dc0a8107ebb38f62b98769114217 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Tue, 7 Jul 2020 14:20:02 -0700 Subject: [PATCH 022/138] Interning Checker: Use factory method to get declaration annotation. (#3440) --- .../checker/interning/InterningVisitor.java | 20 +++++++++---------- .../interning/ThreadUsesObjectEquals.java | 5 +++++ 2 files changed, 14 insertions(+), 11 deletions(-) create mode 100644 checker/tests/interning/ThreadUsesObjectEquals.java diff --git a/checker/src/main/java/org/checkerframework/checker/interning/InterningVisitor.java b/checker/src/main/java/org/checkerframework/checker/interning/InterningVisitor.java index 748e98616964..532d291aecdf 100644 --- a/checker/src/main/java/org/checkerframework/checker/interning/InterningVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/interning/InterningVisitor.java @@ -178,22 +178,20 @@ public Void visitBinary(BinaryTree node, Void p) { return super.visitBinary(node, p); } - Element leftElt = null; - Element rightElt = null; - if (left instanceof AnnotatedTypeMirror.AnnotatedDeclaredType) { - leftElt = ((DeclaredType) left.getUnderlyingType()).asElement(); - } - if (right instanceof AnnotatedTypeMirror.AnnotatedDeclaredType) { - rightElt = ((DeclaredType) right.getUnderlyingType()).asElement(); - } - + Element leftElt = TypesUtils.getTypeElement(left.getUnderlyingType()); // If neither @Interned or @UsesObjectEquals, report error. if (!(left.hasEffectiveAnnotation(INTERNED) - || (leftElt != null && leftElt.getAnnotation(UsesObjectEquals.class) != null))) { + || (leftElt != null + && atypeFactory.getDeclAnnotation(leftElt, UsesObjectEquals.class) + != null))) { checker.reportError(leftOp, "not.interned", left); } + + Element rightElt = TypesUtils.getTypeElement(right.getUnderlyingType()); if (!(right.hasEffectiveAnnotation(INTERNED) - || (rightElt != null && rightElt.getAnnotation(UsesObjectEquals.class) != null))) { + || (rightElt != null + && atypeFactory.getDeclAnnotation(rightElt, UsesObjectEquals.class) + != null))) { checker.reportError(rightOp, "not.interned", right); } return super.visitBinary(node, p); diff --git a/checker/tests/interning/ThreadUsesObjectEquals.java b/checker/tests/interning/ThreadUsesObjectEquals.java new file mode 100644 index 000000000000..e494d1fd3641 --- /dev/null +++ b/checker/tests/interning/ThreadUsesObjectEquals.java @@ -0,0 +1,5 @@ +public class ThreadUsesObjectEquals { + boolean p(Thread a, Thread b) { + return a == b; + } +} From ca7d2e9341e0cbbd394d4537975b8b713eb64546 Mon Sep 17 00:00:00 2001 From: Weitian Xing Date: Tue, 7 Jul 2020 17:29:03 -0400 Subject: [PATCH 023/138] Update LiveSimple.pdf. (#3421) --- .../manual/examples/graphs/LiveSimple.pdf | Bin 20590 -> 20579 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/dataflow/manual/examples/graphs/LiveSimple.pdf b/dataflow/manual/examples/graphs/LiveSimple.pdf index 53f72a05274a7974cf9211880a91867b6a6014b0..a1e44e54efc42dd62ca4088d5596ebf120e1a244 100644 GIT binary patch delta 1950 zcmZXOdpHw{8^_)6$EDMWiA=HK&~9ud=5j(V+aiRdGq;5?N3Nl{t7yrPqPZ=U#<7rl zL{cuf)7&y)GRi7K<@a=cf1LBw`_KD*KJWK^zwdgH;6jmLCXRB3tZE>CX{-AEf}6R= z97IB=?qpq&qmyz@TD`?{|EvR==8XBxC*Q`Jb&-C?bO7aBOCY2B zCA~xQ40xHyL2bBx19@bB{=}{To`TwfArR^tJl{jf##9l^A5X{ylgFj~ zJx0O)0?$h_wF6tHol2x+bd)>?+UG-XEL|+ zSbvO&fW4hSDcRSiO@Dnv-TFwk3fsK?^x>bD z)|e0uMe49WeAo}}%r@YVSq>oAw52_5!CrUqvmfAk_A7vGx8}l#5TE=H`V(ZMgR90% zA3xVhrYak?HIrbRyfUMFqu1%bw<2aNGQqR2B6js==(HL;cyDLnDDcw}Q(ki2*t=f! z(NcW*SEmUyOj5yD6FILrHyrwg&yy8RzOX|P6XcJj(A2LHNg2H)-5o4dCHPwRDdoR1 zwpZ0f(;Kg5SDs9xKT_h_P`B4ZE0$~c$Y>(jq6(ybyQ*ldEvV0#k z?^?kWNYI#}B3VOszRF%|D``}b(Y|ejoLP#Yi>KxuF(d>|b*+u_bI#YhNa!T~trt== zM4!;TVW-4=%Enku{es6Y{$8UM7l{fUI<`2$N{`tef2?-rF^IkqZ_6cK6|!Cuzu4X{ z+9=St$Za4wY-LdW>0{9p!|6xezPRoP6gr+ELDxn6na-tlRWL#y!p&VeFuT zb8G!GiW{xzqRP(6M3=M123%K4T@IvT1ifP0&#Wxd6x_&c7)APieUrS0m9rZnyQ)(X ze0)mHJ;S*prOmhXDu(PTp<%v!eOCE5k-Ak2ZsDdhA-iqd;(d4PUW&jLC9%2~h8BcO z8^{V@c95`abiY*S+24x2q8HfOG9>fC``ySCdS+LZ;+y`Byog;dnYk(rHe}fg%z>qT2R1i zk!+V)w!tKc8=eKpESa#C^~}q2h8+SfJMnY3yi2_j38aFK<5nxqt*SSgu$}BWU{Cy) zco%)|jndRnf3vq@(=xd-(^zlKcFf>OVb|rr#JXRoA5DE*FtL=hsJO6F{4KSE)sG%S zqE9%pud9K;cl{^3%4+wBEtu(z6Q3?$cM|qYdv0_8+3J%2^@(}r#zjY$5!&`}?7o-a zqc4|}IN>@AHH^W8PNBr09nsh@}&niq=o_qY4eCW+SH9vQJ^46s89qPv`8jH%>s}h{u191fjQy|Ywp8P&-76{`!h!*$2Ik2x8FhHk%rCk}iz<4F{6+E_jCDlsS< zOzZ4{s5OL2(ZLCa$`8No$nN3kdMPw>2aoEQnfm|1BlX)T$_zwv5`45C}u+qiBm_ z4Z?m;sumcoHWG NgG*^@TH>vw{s-IUgNXnD delta 1935 zcmZXQc{mh`8pbVIV{lN(zB7ecm>FA)WGrJDl0C~~D-AU<)@HJ=HJZ6nB3s#JMz(Af zI*22VE!)@`vPP7$ANSlp?!BGwukZQ2-}`>w^Ude6e#v7^w<0)oaY-JJxC|e?8%^lMi_E)f?5^*Y6@4Cc+nBvAb|qnq{`||zicV7*L&;~e z4izTi{^@|WOAn_J0$i{D{LH_chcjn9vOkMjQ4X0(pKDGP4q0s}p}<#*q~IoD$6?ad z$EUtZ9vu(pMH2JVU@JbCP41E6k$#tfemOXdUn>iyub+%lT4o!F4wCb6Yg5AJ`B!3hYFCH#te7Qok$TZHSDt~Y3>z+JJcnkaB zxIvBKBFETO`nz9TpzZW#xpYMO_8OY)cl1<`O%Csk5iu5b@DsoF5U{Gf>dsYqXMEjj zynV|iTW5ZvtL>x+JtTv$Bh0u*ARNgFcrwkgO))c)+B)r|l099+TqVC^=#Og{a8W1B z?o3q@Dti+qsu|?EOQnaI>3g}b86qurmNzRieBB5FMoKaiwmzvH+3pCddXp^9nE!bI zr_>RB*54f_8-)L4`6gA>UK~5A7dfHD=kXwRs59X>leL??D8K;vIwJ<5XKKU=3?njj zVB53#26hbq`8-k_;qTGh>_UU*rEM}8DuzIC{#M%4H7&FsEbYp`7N zY2%;*OIv3)Nd)V61v8{Was-7%E1kS@-VzmDoQGYXPkP2DVWtg{O~SL@74uI)!Up0w z1d2?e%j>WI3Y7>siU7SYpv#Q|gI#*5fbO;rTkE*nOa0$rW`032bR}R`LLYt%#sDVM zud&>u$ZU&CI($(Yy-5ghZ4i)h7zb~91_Ve63QO>Tq5So}*fz{WeRBiPaM&er;v#-9 z(0UG~5HTi{kGMC_|EquTwn&!II%Z)GiZcxedb!)P@cjS3@nLdg?+ zB3-@NgI-|R*0$-EYQVGBPPNc)s{Zh|`u4A~+Q&jescI4VbB!x=Qn|+fuHz9`KRpkD zy{8#6DZA0`+zlWNkEtr0Ypo8kaE{+5(ZW{ehx~`tm=talPzA5nHO|yD0UN1yW_!NC zw0CCTu{W?0Bc&R_O2R(IeMM}QIf?p@M09KCVAAuk+$!~M+?O7UyJt$8Djh~u71Evo zx~Ge-y6~S0%AWx>mWR&y=!>QSN|gmg0hVKXh;%UmGb3)TAg^gP)`R#Yf_`o@dMc97 zuzsn6pY1HN7tj!ScBVSz((emyDo4WKYU^<2K0k@!g#jN)Ir()CcQ-CE z<9At2F>hs=LmOU-JpdEgR!c-A*&()83`Z6;Poqd*J1a81CWWIjo-B56G0i~7ce=#C z;312g5iCu%jJxfRmsE5M!dW?a`=*D|i^0wo??G*q?DMwz!ikx#UUOFLFfF zue;UC+lD4471836NG6xW0NV!=@MuSo&yS&L&LYK_K7FD^p7rBFo)@aB{Y?dhX{~HasAyK!)EmnJ*m1Ucq0dP?B=`yb+5Qk<-J~e4rA&$q=LV zhX#jPiUl*>iA;|2=$(f)=y_-X6R-FxIsj!w*z;3S!cD{o!7-A~k%98NoY8N@5`4?! zUt*k{YUN$!FJq$vSZ^fLl@IDWNby|}4)yr{&`j}4yS6a|x#3E6L8X!ZnN%fjt5wI^ z=X0rINp4x5agMz>p+Pn2rN;MB5=h;1yIm+m2|AAW!o+eipX_BMlqY?TN&T2Z&ZOTiYDiRrS?>l(St?n?$5p^;nmY`SXr~91bRVs8+Ae z3=*+}JO2S^vz_N!Rcm&V%B$u*8g0DcG1-o(_I~%|6Zv`fif1Ek@Yc(T+~*mRD@=>M zIT}WroiXQVMz+?5m0LQsR`40WzW62t=f44|&`kAl1Xsp|0M$+8)b1u_0%D^hlkL|+ zVt%vM%?4agYxlnhyzW#7J!=0!o9pOlj$oH{_N6p%O0#JpC=AYec5O}V>I^Oc6b}pv zfWg70s;94gTtW|V{0X%7U&lVtgQ+EM+5|X68QhI zU<6F_*SQl0|8*5u`^5aKR~rI_{>|k7dnW`0()thlUj~MQD10_nJP;_aqT&^66W+f7 Dz!-UZ From 2ec4fd34f7f674b9f77ca120c2fffc2fa624e3bc Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 7 Jul 2020 17:54:14 -0700 Subject: [PATCH 024/138] Run Interning Checker on the Checker Framework codebase --- build.gradle | 8 ++++--- .../checker/formatter/FormatterVisitor.java | 7 +++++- .../checker/guieffect/GuiEffectVisitor.java | 2 ++ .../checker/i18nformatter/I18nFormatUtil.java | 4 +++- .../InitializationAnnotatedTypeFactory.java | 11 ++++++--- .../checker/interning/InterningVisitor.java | 9 ++++++-- .../checker/lock/LockVisitor.java | 1 + .../checker/nullness/NullnessVisitor.java | 7 ++++++ .../checker/signedness/SignednessVisitor.java | 4 +++- .../dataflow/analysis/AbstractAnalysis.java | 22 ++++++++++++++---- .../analysis/BackwardAnalysisImpl.java | 9 ++++---- .../dataflow/analysis/FlowExpressions.java | 20 +++++++++++++--- .../analysis/ForwardAnalysisImpl.java | 13 +++++++---- .../dataflow/cfg/CFGBuilder.java | 10 +++++++- .../util/IdentityMostlySingleton.java | 5 ++-- .../common/basetype/BaseTypeChecker.java | 23 ++++++++++++++++--- .../common/basetype/BaseTypeVisitor.java | 19 +++++++++++++-- .../reflection/DefaultReflectionResolver.java | 5 ++++ .../common/util/TypeVisualizer.java | 12 ++++++++-- .../common/value/util/Range.java | 7 ++++-- .../framework/flow/CFAbstractTransfer.java | 4 +++- .../framework/flow/CFAbstractValue.java | 1 + .../framework/source/SourceChecker.java | 10 +++++--- .../framework/stub/StubParser.java | 5 +++- .../framework/type/AnnotatedTypeFactory.java | 7 ++++-- .../framework/type/AnnotatedTypeMirror.java | 2 +- .../framework/type/AnnotatedTypeReplacer.java | 3 +++ .../framework/type/AsSuperVisitor.java | 19 ++++++++++++--- .../framework/type/BoundsInitializer.java | 3 ++- .../framework/type/EqualityAtmComparer.java | 20 ++++++++++++---- .../type/StructuralEqualityComparer.java | 6 ++++- .../type/TypeFromTypeTreeVisitor.java | 9 ++++++-- .../PropagationTypeAnnotator.java | 8 ++++++- .../type/visitor/AnnotatedTypeCombiner.java | 2 ++ .../framework/util/AtmLubVisitor.java | 6 ++++- .../framework/util/ContractsUtils.java | 12 ++++++++-- .../framework/util/Heuristics.java | 2 ++ .../util/MultiGraphQualifierHierarchy.java | 1 + .../framework/util/TreePathCacher.java | 4 +++- .../util/defaults/QualifierDefaults.java | 7 ++++-- .../util/element/ElementAnnotationUtil.java | 1 + .../typeinference/TypeArgInferenceUtil.java | 14 ++++++++--- .../solver/EqualitiesSolver.java | 12 ++++++---- .../javacutil/AnnotationUtils.java | 12 ++++++++-- .../checkerframework/javacutil/TreeUtils.java | 9 ++++++-- .../javacutil/TypeAnnotationUtils.java | 1 + .../javacutil/TypesUtils.java | 7 ++++-- 47 files changed, 305 insertions(+), 80 deletions(-) diff --git a/build.gradle b/build.gradle index 13dccb5e1997..e26f1bd2e164 100644 --- a/build.gradle +++ b/build.gradle @@ -635,12 +635,14 @@ subprojects { } // Add tasks to run various checkers on all the main source sets. - // TODO: fix or suppress all not.interned warnings and remove the suppression here. - createCheckTypeTask(project.name, 'org.checkerframework.checker.interning.InterningChecker', 'Interning', ['-AsuppressWarnings=not.interned']) - createCheckTypeTask(project.name, 'org.checkerframework.checker.nullness.NullnessChecker', 'Nullness') + // These pass and are run by nonJunitTests. + createCheckTypeTask(project.name, 'org.checkerframework.checker.interning.InterningChecker', 'Interning') createCheckTypeTask(project.name, 'org.checkerframework.checker.nullness.NullnessChecker', 'NullnessSkipSome', ['-AskipUses=com.sun.*', '-AskipDefs=org.checkerframework.checker.*|org.checkerframework.common.*|org.checkerframework.framework.*']) createCheckTypeTask(project.name, 'org.checkerframework.framework.util.PurityChecker', 'Purity') createCheckTypeTask(project.name, 'org.checkerframework.checker.signature.SignatureChecker', 'Signature') + // These do not yet pass. TODO: Make them pass. + createCheckTypeTask(project.name, 'org.checkerframework.checker.nullness.NullnessChecker', 'Nullness', ['-AskipUses=com.sun.*']) + // Add jtregTests to framework and checker modules if (project.name.is('framework') || project.name.is('checker')) { diff --git a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterVisitor.java b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterVisitor.java index 47d31c067b5b..f9ea68d46557 100644 --- a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterVisitor.java @@ -122,10 +122,15 @@ public Void visitMethodInvocation(MethodInvocationTree node, Void p) { } /** - * Returns true if fc is within a method m annotated as {@code @FormatMethod}, and fc's + * Returns true if {@code fc} is within a method m annotated as {@code @FormatMethod}, and fc's * arguments are m's formal parameters. In other words, fc forwards m's arguments to another * format method. + * + * @param fc an invocation of a format method + * @return true if {@code fc} is a call to a format method that forwards its containing methods' + * arguments */ + @SuppressWarnings("interning:not.interned") // comparisons of Name objects private boolean isWrappedFormatCall(FormatCall fc) { MethodTree enclosingMethod = TreeUtils.enclosingMethod(atypeFactory.getPath(fc.node)); diff --git a/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectVisitor.java b/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectVisitor.java index 0da2b7468520..79ac33e6fd81 100644 --- a/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectVisitor.java @@ -210,6 +210,7 @@ public boolean isValidUse( } @Override + @SuppressWarnings("interning:not.interned") // comparing AST nodes public Void visitLambdaExpression(LambdaExpressionTree node, Void p) { Void v = super.visitLambdaExpression(node, p); // If this is a lambda inferred to be @UI, scan up the path and re-check any assignments @@ -435,6 +436,7 @@ public Void visitMethod(MethodTree node, Void p) { } @Override + @SuppressWarnings("interning:not.interned") // comparing AST nodes public Void visitNewClass(NewClassTree node, Void p) { Void v = super.visitNewClass(node, p); // If this is an anonymous inner class inferred to be @UI, scan up the path and re-check any diff --git a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatUtil.java b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatUtil.java index 05d80036c81a..199a2f681275 100644 --- a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatUtil.java +++ b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatUtil.java @@ -14,6 +14,7 @@ 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; /** * This class provides a collection of utilities to ease working with i18n format strings. @@ -372,7 +373,8 @@ private static final int findKeyword(String s, String[] list) { } // Try trimmed lowercase. - String ls = s.trim().toLowerCase(Locale.ROOT); + @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])) { diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnnotatedTypeFactory.java index 76fcda52282e..eb714334f367 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnnotatedTypeFactory.java @@ -427,12 +427,17 @@ public AnnotatedDeclaredType getSelfType(Tree tree) { } /** - * In the first enclosing class, find the top-level member that contains tree. TODO: should we - * look whether these elements are enclosed within another class that is itself under - * construction. + * In the first enclosing class, find the top-level member that contains {@code path}. + * + *

    TODO: should we look whether these elements are enclosed within another class that is + * itself under construction. * *

    Are there any other type of top level objects? + * + * @param path the path whose leaf is the target + * @return a top-level member containing the leaf of {@code path} */ + @SuppressWarnings("interning:not.interned") // AST node comparison private Tree findTopLevelClassMemberForTree(TreePath path) { ClassTree enclosingClass = TreeUtils.enclosingClass(path); if (enclosingClass != null) { diff --git a/checker/src/main/java/org/checkerframework/checker/interning/InterningVisitor.java b/checker/src/main/java/org/checkerframework/checker/interning/InterningVisitor.java index 532d291aecdf..c939ed7e5b06 100644 --- a/checker/src/main/java/org/checkerframework/checker/interning/InterningVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/interning/InterningVisitor.java @@ -520,7 +520,9 @@ private boolean suppressInsideComparison(final BinaryTree node) { assert methodTree != null; StatementTree firstStmnt = methodTree.getBody().getStatements().get(0); assert firstStmnt != null; - if (ifStatementTree != firstStmnt) { + @SuppressWarnings("interning:not.interned") // comparing AST nodes + boolean notSameNode = firstStmnt != ifStatementTree; + if (notSameNode) { return false; // The if statement is not the first statement in the method. } } else { @@ -819,7 +821,10 @@ public Boolean visitBinary(BinaryTree tree, Void p) { return visit(leftTree, p); } else { // a == b || a.compareTo(b) == 0 - ExpressionTree leftTree = tree.getLeftOperand(); // looking for a==b + @SuppressWarnings( + "interning:assignment.type.incompatible" // AST node comparisons + ) + @InternedDistinct ExpressionTree leftTree = tree.getLeftOperand(); // looking for a==b ExpressionTree rightTree = tree.getRightOperand(); // looking for a.compareTo(b) == 0 // or b.compareTo(a) == 0 diff --git a/checker/src/main/java/org/checkerframework/checker/lock/LockVisitor.java b/checker/src/main/java/org/checkerframework/checker/lock/LockVisitor.java index 40f4e27dbdb4..35d0df23e99f 100644 --- a/checker/src/main/java/org/checkerframework/checker/lock/LockVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/lock/LockVisitor.java @@ -1087,6 +1087,7 @@ private boolean isTreeSymbolEffectivelyFinalOrUnmodifiable(Tree tree) { } @Override + @SuppressWarnings("interning:not.interned") // AST node comparison public Void visitIdentifier(IdentifierTree tree, Void p) { // If the identifier is a field accessed via an implicit this, // then check the lock of this. (All other field accessed are checked in visitMemberSelect. diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessVisitor.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessVisitor.java index e72a5392f39a..febac4ab1ef4 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessVisitor.java @@ -289,6 +289,13 @@ private static boolean isNewArrayAllZeroDims(NewArrayTree node) { return isAllZeros; } + /** + * Return true if the given node is "new X[]", in the context "toArray(new X[])". + * + * @param node a node to test + * @return true if the node is a new array within acall to toArray() + */ + @SuppressWarnings("interning:not.interned") // comparisons of Name objects private boolean isNewArrayInToArray(NewArrayTree node) { if (node.getDimensions().size() != 1) { return false; diff --git a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessVisitor.java b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessVisitor.java index d2b3687d6d8f..cef73f24b05a 100644 --- a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessVisitor.java @@ -10,6 +10,7 @@ import com.sun.source.tree.Tree.Kind; import com.sun.source.tree.TypeCastTree; import javax.lang.model.type.TypeKind; +import org.checkerframework.checker.interning.qual.InternedDistinct; import org.checkerframework.checker.signedness.qual.PolySigned; import org.checkerframework.checker.signedness.qual.Signed; import org.checkerframework.checker.signedness.qual.Unsigned; @@ -212,7 +213,8 @@ private boolean isMaskedShiftEitherSignedness(BinaryTree shiftExpr) { // enclosing immediately contains shiftExpr or a parenthesized version of shiftExpr Tree enclosing = enclosingPair.first; // enclosingChild is a child of enclosing: shiftExpr or a parenthesized version of it. - Tree enclosingChild = enclosingPair.second; + @SuppressWarnings("interning:assignment.type.incompatible") // comparing AST nodes + @InternedDistinct Tree enclosingChild = enclosingPair.second; if (!isMask(enclosing)) { return false; diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractAnalysis.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractAnalysis.java index 36ee3346a605..2117ef9e7e5a 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractAnalysis.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractAnalysis.java @@ -11,6 +11,8 @@ import java.util.PriorityQueue; import java.util.Set; import javax.lang.model.element.Element; +import org.checkerframework.checker.interning.qual.FindDistinct; +import org.checkerframework.checker.interning.qual.InternedDistinct; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -74,13 +76,13 @@ public abstract class AbstractAnalysis< * !isRunning ==> (currentNode == null) * */ - protected @Nullable Node currentNode; + protected @InternedDistinct @Nullable Node currentNode; /** * The tree that is currently being looked at. The transfer function can set this tree to make * sure that calls to {@code getValue} will not return information for this given tree. */ - protected @Nullable Tree currentTree; + protected @InternedDistinct @Nullable Tree currentTree; /** The current transfer input when the analysis is running. */ protected @Nullable TransferInput currentInput; @@ -100,10 +102,19 @@ public abstract class AbstractAnalysis< * * @param currentTree the tree that should be currently looked at */ - public void setCurrentTree(Tree currentTree) { + public void setCurrentTree(@FindDistinct Tree currentTree) { this.currentTree = currentTree; } + /** + * Set the node that is currently being looked at. + * + * @param currentNode the node that should be currently looked at + */ + protected void setCurrentNode(@FindDistinct @Nullable Node currentNode) { + this.currentNode = currentNode; + } + /** * Implementation of common features for {@link BackwardAnalysisImpl} and {@link * ForwardAnalysisImpl}. @@ -328,9 +339,10 @@ protected TransferResult callTransferFunction( return new RegularTransferResult<>(null, transferInput.getRegularStore()); } transferInput.node = node; - currentNode = node; + setCurrentNode(node); + @SuppressWarnings("nullness") // CF bug: "INFERENCE FAILED" TransferResult transferResult = node.accept(transferFunction, transferInput); - currentNode = null; + setCurrentNode(null); if (node instanceof AssignmentNode) { // store the flow-refined value effectively for final local variables AssignmentNode assignment = (AssignmentNode) node; diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysisImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysisImpl.java index 888b0a801d73..4510b569e49b 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysisImpl.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysisImpl.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.ListIterator; import java.util.Map; +import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.RequiresNonNull; import org.checkerframework.dataflow.analysis.Store.FlowRule; @@ -314,7 +315,7 @@ protected void addStoreAfter(Block pred, @Nullable Node node, S s, boolean addBl @Override public S runAnalysisFor( - Node node, + @FindDistinct Node node, boolean before, TransferInput transferInput, IdentityHashMap nodeValues, @@ -339,7 +340,7 @@ public S runAnalysisFor( ListIterator reverseIter = nodeList.listIterator(nodeList.size()); while (reverseIter.hasPrevious()) { Node n = reverseIter.previous(); - currentNode = n; + setCurrentNode(n); if (n == node && !before) { return store.getRegularStore(); } @@ -368,7 +369,7 @@ public S runAnalysisFor( if (!before) { return transferInput.getRegularStore(); } - currentNode = node; + setCurrentNode(node); TransferResult transferResult = callTransferFunction(node, transferInput); // Merge transfer result with the exception store of this exceptional block @@ -383,7 +384,7 @@ public S runAnalysisFor( } } finally { - currentNode = oldCurrentNode; + setCurrentNode(oldCurrentNode); isRunning = false; } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/FlowExpressions.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/FlowExpressions.java index 6fcfc44abc2c..60c31eb68afe 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/FlowExpressions.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/FlowExpressions.java @@ -26,6 +26,8 @@ import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.interning.qual.EqualsMethod; +import org.checkerframework.checker.interning.qual.UsesObjectEquals; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.cfg.node.ArrayAccessNode; import org.checkerframework.dataflow.cfg.node.ArrayCreationNode; @@ -495,10 +497,12 @@ public boolean containsUnknown() { public abstract boolean isUnmodifiableByOtherCode(); /** - * Returns true if and only if the two receiver are syntactically identical. + * Returns true if and only if the two receivers are syntactically identical. * - * @return true if and only if the two receiver are syntactically identical + * @param other the other object to compare to this one + * @return true if and only if the two receivers are syntactically identical */ + @EqualsMethod public boolean syntacticEquals(Receiver other) { return other == this; } @@ -734,7 +738,14 @@ public boolean containsModifiableAliasOf(Store store, Receiver other) { } } + /** Stands for any expression that the Dataflow Framework lacks explicit support for. */ + @UsesObjectEquals public static class Unknown extends Receiver { + /** + * Create a new Unknown receiver. + * + * @param type the Java type of this receiver + */ public Unknown(TypeMirror type) { super(type); } @@ -1074,11 +1085,14 @@ public boolean containsModifiableAliasOf(Store store, Receiver other) { @Override public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } if (!(obj instanceof MethodCall)) { return false; } if (method.getKind() == ElementKind.CONSTRUCTOR) { - return this == obj; + return false; } MethodCall other = (MethodCall) obj; return parameters.equals(other.parameters) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysisImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysisImpl.java index d075f562f685..c14e4531571a 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysisImpl.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysisImpl.java @@ -9,6 +9,7 @@ import java.util.Map; import java.util.Set; import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.RequiresNonNull; import org.checkerframework.dataflow.cfg.ControlFlowGraph; @@ -237,7 +238,7 @@ public void performAnalysisBlock(Block b) { @Override public S runAnalysisFor( - Node node, + @FindDistinct Node node, boolean before, TransferInput transferInput, IdentityHashMap nodeValues, @@ -274,7 +275,7 @@ public S runAnalysisFor( TransferInput store = transferInput; TransferResult transferResult; for (Node n : rb.getContents()) { - currentNode = n; + setCurrentNode(n); if (n == node && before) { return store.getRegularStore(); } @@ -310,7 +311,7 @@ public S runAnalysisFor( if (before) { return transferInput.getRegularStore(); } - currentNode = node; + setCurrentNode(node); TransferResult transferResult = callTransferFunction(node, transferInput); return transferResult.getRegularStore(); @@ -320,7 +321,7 @@ public S runAnalysisFor( throw new BugInCF("Unexpected block type: " + block.getType()); } } finally { - currentNode = oldCurrentNode; + setCurrentNode(oldCurrentNode); isRunning = false; } } @@ -502,7 +503,9 @@ protected void addStoreBefore( break; } case BOTH: - if (thenStore == elseStore) { + @SuppressWarnings("interning:not.interned") + boolean sameStore = (thenStore == elseStore); + if (sameStore) { // Currently there is only one regular store S newStore = mergeStores(s, thenStore, shouldWiden); if (!newStore.equals(thenStore)) { diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGBuilder.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGBuilder.java index ffe8cdadfa12..a01ec828f000 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGBuilder.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGBuilder.java @@ -91,6 +91,7 @@ import javax.lang.model.type.UnionType; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; +import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.Store; import org.checkerframework.dataflow.cfg.CFGBuilder.ExtendedNode.ExtendedNodeType; @@ -1009,6 +1010,7 @@ public static ControlFlowGraph process(ControlFlowGraph cfg) { * @param predecessors an empty set to be filled by this method with all predecessors * @return the single successor of the set of the empty basic blocks */ + @SuppressWarnings("interning:not.interned") // AST node comparisons protected static BlockImpl computeNeighborhoodOfEmptyBlock( RegularBlockImpl start, Set empty, @@ -1088,7 +1090,12 @@ protected static void computeNeighborhoodOfEmptyBlockBackwards( * place where previously the edge pointed to {@code cur}. Additionally, the predecessor * holder also takes care of unlinking (i.e., removing the {@code pred} from {@code cur's} * predecessors). + * + * @param pred a block whose successor should be set + * @param cur the previous successor of {@code pred} + * @return a predecessor holder to set the successor of {@code pred} */ + @SuppressWarnings("interning:not.interned") // AST node comparisons protected static PredecessorHolder getPredecessorHolder( final BlockImpl pred, final BlockImpl cur) { switch (pred.getType()) { @@ -1225,6 +1232,7 @@ private CFGTranslationPhaseTwo() {} * empty regular basic blocks or conditional blocks with the same block as 'then' and * 'else' successor) */ + @SuppressWarnings("interning:not.interned") // AST node comparisons public static ControlFlowGraph process(PhaseOneResult in) { Map bindings = in.bindings; @@ -1900,7 +1908,7 @@ protected void extendWithExtendedNode(ExtendedNode n) { * @param pred the desired predecessor */ @SuppressWarnings("ModifyCollectionInEnhancedForLoop") - protected void insertExtendedNodeAfter(ExtendedNode n, Node pred) { + protected void insertExtendedNodeAfter(ExtendedNode n, @FindDistinct Node pred) { int index = -1; for (int i = 0; i < nodeList.size(); i++) { ExtendedNode inList = nodeList.get(i); diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/IdentityMostlySingleton.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/IdentityMostlySingleton.java index fa3734f4c29f..d721d7fc8688 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/util/IdentityMostlySingleton.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/util/IdentityMostlySingleton.java @@ -4,8 +4,8 @@ import org.checkerframework.javacutil.BugInCF; /** - * A set that is more efficient than HashSet for 0 and 1 elements. Uses objects identity for object - * comparison and an {@link ArrayList} for backing storage. + * An arbitrary-size set that is very efficient ( more efficient than HashSet) for 0 and 1 elements. + * Uses object identity for object comparison. */ public final class IdentityMostlySingleton extends AbstractMostlySingleton { @@ -42,6 +42,7 @@ public boolean add(T e) { } } + @SuppressWarnings("interning:not.interned") // this class uses object identity @Override public boolean contains(Object o) { switch (state) { diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java index 0ddf98809519..7d56101f9fe2 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java @@ -25,6 +25,8 @@ import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; +import org.checkerframework.checker.interning.qual.FindDistinct; +import org.checkerframework.checker.interning.qual.InternedDistinct; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.signature.qual.ClassGetName; @@ -561,6 +563,7 @@ protected void warnUnneededSuppressions() { * *

    Otherwise, it prints the message. */ + @SuppressWarnings("interning:not.interned") // assertion @Override protected void printOrStoreMessage( Diagnostic.Kind kind, String message, Tree source, CompilationUnitTree root) { @@ -590,18 +593,32 @@ private void printStoredMessages(CompilationUnitTree unit) { /** Represents a message (e.g., an error message) issued by a checker. */ private static class CheckerMessage { + /** The severity of the message. */ final Diagnostic.Kind kind; + /** The message itself. */ final String message; - final Tree source; + /** The source code that the message is about. */ + final @InternedDistinct Tree source; /** * The checker that issued this message. The compound checker that depends on this checker * uses this to sort the messages. */ - final BaseTypeChecker checker; + final @InternedDistinct BaseTypeChecker checker; + /** + * Create a new CheckerMessage. + * + * @param kind the severity of the message + * @param message the text of the message + * @param source the source code that the message is about + * @param checker the checker that issued the message. + */ private CheckerMessage( - Diagnostic.Kind kind, String message, Tree source, BaseTypeChecker checker) { + Diagnostic.Kind kind, + String message, + @FindDistinct Tree source, + @FindDistinct BaseTypeChecker checker) { this.kind = kind; this.message = message; this.source = source; 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 36b0a5fe8482..60344db687dd 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -68,6 +68,7 @@ import javax.lang.model.util.ElementFilter; import javax.tools.Diagnostic.Kind; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; +import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.dataflow.analysis.FlowExpressions; import org.checkerframework.dataflow.analysis.FlowExpressions.Receiver; import org.checkerframework.dataflow.analysis.TransferResult; @@ -3857,7 +3858,13 @@ protected MemberSelectTree enclosingMemberSelect() { } } - protected Tree enclosingStatement(Tree tree) { + /** + * Returns the statement that encloses the given one. + * + * @param tree an AST node that is on the current path + * @return the statement that encloses the given one + */ + protected Tree enclosingStatement(@FindDistinct Tree tree) { TreePath path = this.getCurrentPath(); while (path != null && path.getLeaf() != tree) { path = path.getParentPath(); @@ -3900,8 +3907,16 @@ protected void checkAccess(IdentifierTree node, Void p) { } } + /** + * Returns true if access is allowed, based on an @Unused annotation + * + * @param field the field to be accessed, whose declaration might be annotated by @Unused + * @param receiver the expression whose field is accessed + * @param accessTree the access expression + * @return true if access is allowed + */ protected boolean isAccessAllowed( - Element field, AnnotatedTypeMirror receiver, ExpressionTree accessTree) { + Element field, AnnotatedTypeMirror receiver, @FindDistinct ExpressionTree accessTree) { AnnotationMirror unused = atypeFactory.getDeclAnnotation(field, Unused.class); if (unused == null) { return true; diff --git a/framework/src/main/java/org/checkerframework/common/reflection/DefaultReflectionResolver.java b/framework/src/main/java/org/checkerframework/common/reflection/DefaultReflectionResolver.java index d970370fec15..f8668a38f0cf 100644 --- a/framework/src/main/java/org/checkerframework/common/reflection/DefaultReflectionResolver.java +++ b/framework/src/main/java/org/checkerframework/common/reflection/DefaultReflectionResolver.java @@ -478,8 +478,13 @@ private boolean isUnknownMethod(MethodInvocationTree tree) { /** * Get set of MethodSymbols based on class name, method name, and parameter length. * + * @param className the class that contains the method + * @param methodName the method's name + * @param paramLength the number of parameters + * @param env the environment * @return the (potentially empty) set of corresponding method Symbol(s) */ + @SuppressWarnings("interning:not.interned") // bug? private List getMethodSymbolsfor( String className, String methodName, int paramLength, Env env) { Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); diff --git a/framework/src/main/java/org/checkerframework/common/util/TypeVisualizer.java b/framework/src/main/java/org/checkerframework/common/util/TypeVisualizer.java index c4910d0556f5..a1fff2c58cc0 100644 --- a/framework/src/main/java/org/checkerframework/common/util/TypeVisualizer.java +++ b/framework/src/main/java/org/checkerframework/common/util/TypeVisualizer.java @@ -12,6 +12,8 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.element.VariableElement; +import org.checkerframework.checker.interning.qual.FindDistinct; +import org.checkerframework.checker.interning.qual.InternedDistinct; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; @@ -174,9 +176,15 @@ private static boolean printTypevarIfMatches( * create a wrapper that performed referential equality on types and use a LinkedHashMap. */ private static class Node { - private final AnnotatedTypeMirror type; + /** The delegate; that is, the wrapped value. */ + private final @InternedDistinct AnnotatedTypeMirror type; - private Node(final AnnotatedTypeMirror type) { + /** + * Create a new Node that wraps the given type. + * + * @param type the type that the newly-constructed Node represents + */ + private Node(final @FindDistinct AnnotatedTypeMirror type) { this.type = type; } diff --git a/framework/src/main/java/org/checkerframework/common/value/util/Range.java b/framework/src/main/java/org/checkerframework/common/value/util/Range.java index b373a081f780..c242ff9c5f7a 100644 --- a/framework/src/main/java/org/checkerframework/common/value/util/Range.java +++ b/framework/src/main/java/org/checkerframework/common/value/util/Range.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Objects; import javax.lang.model.type.TypeKind; +import org.checkerframework.checker.interning.qual.InternedDistinct; import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -58,8 +59,10 @@ public class Range { /** A range containing all possible 8-bit values. */ public static final Range BYTE_EVERYTHING = create(Byte.MIN_VALUE, Byte.MAX_VALUE); - /** The empty range singleton. */ - public static final Range NOTHING = new Range(Long.MAX_VALUE, Long.MIN_VALUE); + /** The empty range. This is the only Range object that contains nothing */ + @SuppressWarnings( + "interning:assignment.type.incompatible") // no other constructor call makes this + public static final @InternedDistinct Range NOTHING = new Range(Long.MAX_VALUE, Long.MIN_VALUE); /** An alias to the range containing all possible 64-bit values. */ public static final Range EVERYTHING = LONG_EVERYTHING; 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 5f68c62d5de0..c6a34bf7f0c4 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java @@ -20,6 +20,7 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.interning.qual.InternedDistinct; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.ConditionalTransferResult; import org.checkerframework.dataflow.analysis.FlowExpressions; @@ -314,7 +315,8 @@ public S initialStore( } CFGLambda lambda = (CFGLambda) underlyingAST; - Tree enclosingTree = + @SuppressWarnings("interning:assignment.type.incompatible") // used in == tests + @InternedDistinct Tree enclosingTree = TreeUtils.enclosingOfKind( factory.getPath(lambda.getLambdaTree()), new HashSet<>( diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractValue.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractValue.java index ca8fb7eaed9d..23106f06b7d3 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractValue.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractValue.java @@ -136,6 +136,7 @@ public TypeMirror getUnderlyingType() { return underlyingType; } + @SuppressWarnings("interning:not.interned") // efficiency pre-test @Override public boolean equals(@Nullable Object obj) { if (!(obj instanceof CFAbstractValue)) { 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 0d86bfbff709..1806f7bf62d8 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -54,6 +54,7 @@ import javax.lang.model.util.Types; import javax.tools.Diagnostic.Kind; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; +import org.checkerframework.checker.interning.qual.InternedDistinct; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.qual.AnnotatedFor; @@ -399,13 +400,13 @@ public abstract class SourceChecker extends AbstractTypeProcessor protected Trees trees; /** The source tree that is being scanned. */ - protected CompilationUnitTree currentRoot; + protected @InternedDistinct CompilationUnitTree currentRoot; /** * If an error is detected in a CompilationUnitTree, skip all future calls of {@link * #typeProcess} with that same CompilationUnitTree. */ - private CompilationUnitTree previousErrorCompilationUnit; + private @InternedDistinct CompilationUnitTree previousErrorCompilationUnit; /** The visitor to use. */ protected SourceVisitor visitor; @@ -557,6 +558,7 @@ public SourceChecker getParentChecker() { * * @param newRoot the new compilation unit root */ + @SuppressWarnings("interning:assignment.type.incompatible") // used in == tests protected void setRoot(CompilationUnitTree newRoot) { this.currentRoot = newRoot; visitor.setRoot(currentRoot); @@ -855,7 +857,9 @@ public void typeProcess(TypeElement e, TreePath p) { Log log = Log.instance(context); if (log.nerrors > this.errsOnLastExit) { this.errsOnLastExit = log.nerrors; - previousErrorCompilationUnit = p.getCompilationUnit(); + @SuppressWarnings("interning:assignment.type.incompatible") // will be compared with == + @InternedDistinct CompilationUnitTree cu = p.getCompilationUnit(); + previousErrorCompilationUnit = cu; return; } if (p.getCompilationUnit() == previousErrorCompilationUnit) { diff --git a/framework/src/main/java/org/checkerframework/framework/stub/StubParser.java b/framework/src/main/java/org/checkerframework/framework/stub/StubParser.java index bad0dbd60261..d48857a77576 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/StubParser.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/StubParser.java @@ -67,6 +67,7 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.qual.FromStubFile; import org.checkerframework.framework.type.AnnotatedTypeFactory; @@ -1045,8 +1046,10 @@ private void annotate( case TYPEVAR: // Add annotations from the declaration of the TypeVariable AnnotatedTypeVariable typeVarUse = (AnnotatedTypeVariable) atype; + Types typeUtils = processingEnv.getTypeUtils(); for (AnnotatedTypeVariable typePar : typeParameters) { - if (typePar.getUnderlyingType() == atype.getUnderlyingType()) { + if (typeUtils.isSameType( + typePar.getUnderlyingType(), atype.getUnderlyingType())) { AnnotatedTypeReplacer.replace( typePar.getUpperBound(), typeVarUse.getUpperBound()); AnnotatedTypeReplacer.replace( diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java index 986e77bb78c3..1fe2b0fd02fb 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java @@ -56,6 +56,8 @@ import javax.lang.model.type.WildcardType; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; +import org.checkerframework.checker.interning.qual.FindDistinct; +import org.checkerframework.checker.interning.qual.InternedDistinct; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; @@ -2926,7 +2928,8 @@ protected final AnnotatedDeclaredType getCurrentClassType(Tree tree) { if (res == null) { TreePath path = getPath(tree); if (path != null) { - MethodTree enclosingMethod = TreeUtils.enclosingMethod(path); + @SuppressWarnings("interning:assignment.type.incompatible") // used for == test + @InternedDistinct MethodTree enclosingMethod = TreeUtils.enclosingMethod(path); ClassTree enclosingClass = TreeUtils.enclosingClass(path); boolean found = false; @@ -3001,7 +3004,7 @@ private final Element getMostInnerClassOrMethod(Tree tree) { * @param node the {@link Tree} to get the path for * @return the path for {@code node} under the current root */ - public final TreePath getPath(Tree node) { + public final TreePath getPath(@FindDistinct Tree node) { assert root != null : "AnnotatedTypeFactory.getPath: root needs to be set when used on trees; factory: " + this.getClass(); diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java index dc5c7974ff0d..5c9e75dc9cf2 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java @@ -2095,7 +2095,7 @@ public static class AnnotatedUnionType extends AnnotatedTypeMirror { * Creates a new AnnotatedUnionType. * * @param type underlying kind of this type - * @param atypeFactory TODO + * @param atypeFactory type factory */ private AnnotatedUnionType(UnionType type, AnnotatedTypeFactory atypeFactory) { super(type, atypeFactory); diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeReplacer.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeReplacer.java index 59cf98351ec3..3b5577e20134 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeReplacer.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeReplacer.java @@ -30,6 +30,7 @@ public class AnnotatedTypeReplacer extends AnnotatedTypeComparer { * @param from the annotated type mirror from which to take new annotations * @param to the annotated type mirror to which the annotations will be added */ + @SuppressWarnings("interning:not.interned") // assertion public static void replace(final AnnotatedTypeMirror from, final AnnotatedTypeMirror to) { if (from == to) { throw new BugInCF("From == to"); @@ -46,6 +47,7 @@ public static void replace(final AnnotatedTypeMirror from, final AnnotatedTypeMi * @param to the annotated type mirror to which the annotations will be added * @param top the top type of the hierarchy whose annotations will be added */ + @SuppressWarnings("interning:not.interned") // assertion public static void replace( final AnnotatedTypeMirror from, final AnnotatedTypeMirror to, @@ -74,6 +76,7 @@ public AnnotatedTypeReplacer(final AnnotationMirror top) { this.top = top; } + @SuppressWarnings("interning:not.interned") // assertion @Override protected Void compare(AnnotatedTypeMirror from, AnnotatedTypeMirror to) { assert from != to; diff --git a/framework/src/main/java/org/checkerframework/framework/type/AsSuperVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/AsSuperVisitor.java index f9e1554847af..a6d3eec9b829 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AsSuperVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AsSuperVisitor.java @@ -8,6 +8,7 @@ import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; +import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType; @@ -44,18 +45,23 @@ public AsSuperVisitor(AnnotatedTypeFactory annotatedTypeFactory) { * Implements asSuper. See {@link AnnotatedTypes#asSuper(AnnotatedTypeFactory, * AnnotatedTypeMirror, AnnotatedTypeMirror)} for details. * + * @param the type of the supertype * @param type type from which to copy annotations * @param superType a type whose erased Java type is a supertype of {@code type}'s erased Java * type. * @return a copy of {@code superType} with annotations copied from {@code type} and type * variables substituted from {@code type}. */ - @SuppressWarnings("unchecked") + @SuppressWarnings({ + "unchecked", + "interning:not.interned" // optimized special case + }) public T asSuper(AnnotatedTypeMirror type, T superType) { if (type == null || superType == null) { throw new BugInCF("AsSuperVisitor type and supertype cannot be null."); + } - } else if (type == superType) { + if (type == superType) { return (T) type.deepCopy(); } @@ -834,7 +840,14 @@ public AnnotatedTypeMirror visitWildcard_Wildcard( return copyPrimaryAnnos(type, superType); } - public boolean sameAnnotatedTypeFactory(AnnotatedTypeFactory annotatedTypeFactory) { + /** + * Returns true if the annotatedTypeFactory for this is the given value. + * + * @param annotatedTypeFactory a factory to compare to that of this + * @return true if the annotatedTypeFactory for this is the given value + */ + public boolean sameAnnotatedTypeFactory( + @FindDistinct AnnotatedTypeFactory annotatedTypeFactory) { return this.annotatedTypeFactory == annotatedTypeFactory; } // diff --git a/framework/src/main/java/org/checkerframework/framework/type/BoundsInitializer.java b/framework/src/main/java/org/checkerframework/framework/type/BoundsInitializer.java index d9b878242a9c..3a82b83893d1 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/BoundsInitializer.java +++ b/framework/src/main/java/org/checkerframework/framework/type/BoundsInitializer.java @@ -19,6 +19,7 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; import javax.lang.model.type.WildcardType; +import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; @@ -802,7 +803,7 @@ public TypePathNode addPathNode(TypePathNode node) { * * @param node last node in the path */ - public void removePathNode(TypePathNode node) { + public void removePathNode(@FindDistinct TypePathNode node) { if (currentPath.getLeaf() != node) { throw new BugInCF( "Cannot remove node: %s. It is not the last node. currentPath= %s", diff --git a/framework/src/main/java/org/checkerframework/framework/type/EqualityAtmComparer.java b/framework/src/main/java/org/checkerframework/framework/type/EqualityAtmComparer.java index 3bc6163d42f1..33136be1cb30 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/EqualityAtmComparer.java +++ b/framework/src/main/java/org/checkerframework/framework/type/EqualityAtmComparer.java @@ -1,5 +1,6 @@ package org.checkerframework.framework.type; +import org.checkerframework.checker.interning.qual.EqualsMethod; import org.checkerframework.framework.type.visitor.EquivalentAtmComboScanner; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.SystemUtil; @@ -40,24 +41,33 @@ protected boolean arePrimeAnnosEqual( return AnnotationUtils.areSame(type1.getAnnotations(), type2.getAnnotations()); } + /** + * Return true if the twe types are the same. + * + * @param type1 the first type to compare + * @param type2 the second type to compare + * @return true if the twe types are the same + */ + @EqualsMethod // to make Interning Checker permit the == comparison protected boolean compare(final AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) { - if ((type1 == null && type2 != null) || (type1 != null && type2 == null)) { - return false; - } - if (type1 == type2) { return true; } + if (type1 == null || type2 == null) { + return false; + } + @SuppressWarnings("TypeEquals") // TODO boolean sameUnderlyingType = type1.getUnderlyingType().equals(type2.getUnderlyingType()); return sameUnderlyingType && arePrimeAnnosEqual(type1, type2); } + @SuppressWarnings("interning:not.interned") @Override protected Boolean scanWithNull( AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void aVoid) { - // one of them should be null, therefore they are only equal if they other is null + // one of them should be null, therefore they are only equal if the other is null return type1 == type2; } diff --git a/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityComparer.java b/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityComparer.java index 573d4b93ced1..9567bd1ae5be 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityComparer.java +++ b/framework/src/main/java/org/checkerframework/framework/type/StructuralEqualityComparer.java @@ -6,6 +6,7 @@ import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.TypeKind; import javax.lang.model.util.Types; +import org.checkerframework.checker.interning.qual.EqualsMethod; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType; @@ -89,13 +90,16 @@ protected String defaultErrorMessage( * Framework sometimes "infers" Typevars to be Wildcards, we allow the combination * Wildcard,Typevar. In this case, the two types are "equal" if their bounds are. * + * @param type1 the first AnnotatedTypeMirror to compare + * @param type2 the second AnnotatedTypeMirror to compare * @return true if type1 and type2 are equal */ + @EqualsMethod private boolean areEqual(final AnnotatedTypeMirror type1, final AnnotatedTypeMirror type2) { - assert currentTop != null; if (type1 == type2) { return true; } + assert currentTop != null; if (type1 == null || type2 == null) { return false; } diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeFromTypeTreeVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeFromTypeTreeVisitor.java index 7d609363c81f..69116daf57fe 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeFromTypeTreeVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeFromTypeTreeVisitor.java @@ -32,6 +32,7 @@ import javax.lang.model.element.TypeParameterElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeVariable; +import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType; @@ -150,10 +151,14 @@ public AnnotatedTypeMirror visitParameterizedType( *

    In scenarios where the bound's owner is the same, we don't want to replace a * capture-converted bound in the wildcard type with a non-capture-converted bound given by the * type parameter declaration. + * + * @param typeArgs the type of the arguments at (e.g., at the call side) + * @param typeParams the type of the formal parameters (e.g., at the method declaration) */ + @SuppressWarnings("interning:not.interned") // workaround for javac bug private void updateWildcardBounds( List typeArgs, List typeParams) { - if (typeArgs.size() == 0) { + if (typeArgs.isEmpty()) { // Nothing to do for empty type arguments. return; } @@ -184,7 +189,7 @@ public AnnotatedTypeMirror visitPrimitiveType(PrimitiveTypeTree node, AnnotatedT @Override public AnnotatedTypeVariable visitTypeParameter( - TypeParameterTree node, AnnotatedTypeFactory f) { + TypeParameterTree node, @FindDistinct AnnotatedTypeFactory f) { List bounds = new ArrayList<>(node.getBounds().size()); for (Tree t : node.getBounds()) { diff --git a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/PropagationTypeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/PropagationTypeAnnotator.java index 2d4efb1f23e1..95bcd4782bbd 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/typeannotator/PropagationTypeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/framework/type/typeannotator/PropagationTypeAnnotator.java @@ -7,6 +7,7 @@ import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeKind; +import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; @@ -177,9 +178,14 @@ private void applyAnnosFromBound( * Search parent's type arguments for wildcard. Using the index of wildcard, find the * corresponding type parameter element and return it. Returns null if the wildcard is the * result of substitution and therefore not in the list of type arguments. + * + * @param wildcard the wildcard type whose corresponding type argument to determine + * @param parent the type that may have a type argument corresponding to {@code wildcard} + * @return the type argument in {@code parent} that corresponds to {@code wildcard} */ private Element getTypeParamFromEnclosingClass( - final AnnotatedWildcardType wildcard, final AnnotatedDeclaredType parent) { + final @FindDistinct AnnotatedWildcardType wildcard, + final AnnotatedDeclaredType parent) { Integer wildcardIndex = null; int currentIndex = 0; for (AnnotatedTypeMirror typeArg : parent.getTypeArguments()) { diff --git a/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeCombiner.java b/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeCombiner.java index 0d56d19b8903..5551be9f987e 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeCombiner.java +++ b/framework/src/main/java/org/checkerframework/framework/type/visitor/AnnotatedTypeCombiner.java @@ -20,6 +20,7 @@ public class AnnotatedTypeCombiner extends AnnotatedTypeComparer { * @param to the annotated type mirror into which annotations should be combined * @param hierarchy the top type of the hierarchy whose annotations should be combined */ + @SuppressWarnings("interning:not.interned") // assertion public static void combine( final AnnotatedTypeMirror from, final AnnotatedTypeMirror to, @@ -43,6 +44,7 @@ private AnnotatedTypeCombiner(final QualifierHierarchy hierarchy) { } @Override + @SuppressWarnings("interning:not.interned") // assertion protected Void compare(AnnotatedTypeMirror one, AnnotatedTypeMirror two) { assert one != two; if (one != null && two != null) { diff --git a/framework/src/main/java/org/checkerframework/framework/util/AtmLubVisitor.java b/framework/src/main/java/org/checkerframework/framework/util/AtmLubVisitor.java index 18e204763173..9ff6410350c4 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/AtmLubVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/util/AtmLubVisitor.java @@ -7,6 +7,7 @@ import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; +import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; @@ -371,8 +372,11 @@ protected String defaultErrorMessage( * Returns true if the {@link AnnotatedTypeMirror} has been visited. If it has not, then it is * added to the list of visited AnnotatedTypeMirrors. This prevents infinite recursion on * recursive types. + * + * @param atm the type that might have been visited + * @return true if the given type has been visited */ - private boolean visited(AnnotatedTypeMirror atm) { + private boolean visited(@FindDistinct AnnotatedTypeMirror atm) { for (AnnotatedTypeMirror atmVisit : visited) { // Use reference equality rather than equals because the visitor may visit two types // that are structurally equal, but not actually the same. For example, the diff --git a/framework/src/main/java/org/checkerframework/framework/util/ContractsUtils.java b/framework/src/main/java/org/checkerframework/framework/util/ContractsUtils.java index a599539f9c4b..55de6eaf2550 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/ContractsUtils.java +++ b/framework/src/main/java/org/checkerframework/framework/util/ContractsUtils.java @@ -11,6 +11,7 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Name; import javax.lang.model.util.ElementFilter; +import org.checkerframework.checker.interning.qual.InternedDistinct; import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation; import org.checkerframework.framework.qual.EnsuresQualifier; import org.checkerframework.framework.qual.EnsuresQualifierIf; @@ -42,7 +43,7 @@ public class ContractsUtils { * The currently-used ContractsUtils object. This class is NOT a singleton: this value can * change. */ - protected static ContractsUtils instance; + protected static @InternedDistinct ContractsUtils instance; /** The factory that this ContractsUtils is associated with. */ protected GenericAnnotatedTypeFactory factory; @@ -52,7 +53,14 @@ private ContractsUtils(GenericAnnotatedTypeFactory factory) { this.factory = factory; } - /** Returns an instance of the {@link ContractsUtils} class. */ + /** + * Returns an instance of the {@link ContractsUtils} class for the given factory. Also sets it + * as the currently-used ContractsUtils object. + * + * @param factory the factory to create a ContractsUtils for + * @return a ContractsUtils for the given factory + */ + @SuppressWarnings("interning") public static ContractsUtils getInstance(GenericAnnotatedTypeFactory factory) { if (instance == null || instance.factory != factory) { instance = new ContractsUtils(factory); diff --git a/framework/src/main/java/org/checkerframework/framework/util/Heuristics.java b/framework/src/main/java/org/checkerframework/framework/util/Heuristics.java index a0d87e3c50b3..cc3b7949f68e 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/Heuristics.java +++ b/framework/src/main/java/org/checkerframework/framework/util/Heuristics.java @@ -94,6 +94,7 @@ public PreceededBy(Matcher matcher) { this.matcher = matcher; } + @SuppressWarnings("interning:not.interned") @Override public boolean match(TreePath path) { StatementTree stmt = TreeUtils.enclosingOfClass(path, StatementTree.class); @@ -173,6 +174,7 @@ public WithinTrueBranch(Matcher conditionMatcher) { this.matcher = conditionMatcher; } + @SuppressWarnings("interning:not.interned") @Override public boolean match(TreePath path) { TreePath prev = path, p = path.getParentPath(); diff --git a/framework/src/main/java/org/checkerframework/framework/util/MultiGraphQualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/util/MultiGraphQualifierHierarchy.java index bd0bd2581df7..1485c2307475 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/MultiGraphQualifierHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/util/MultiGraphQualifierHierarchy.java @@ -28,6 +28,7 @@ * *

    This class is immutable and can be only created through {@link MultiGraphFactory}. */ +@SuppressWarnings("interned") // TODO after https://tinyurl.com/cfissue/3404 is merged public class MultiGraphQualifierHierarchy extends QualifierHierarchy { /** diff --git a/framework/src/main/java/org/checkerframework/framework/util/TreePathCacher.java b/framework/src/main/java/org/checkerframework/framework/util/TreePathCacher.java index d921b353b183..d2048847b34f 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/TreePathCacher.java +++ b/framework/src/main/java/org/checkerframework/framework/util/TreePathCacher.java @@ -6,6 +6,7 @@ import com.sun.source.util.TreeScanner; import java.util.HashMap; import java.util.Map; +import org.checkerframework.checker.interning.qual.FindDistinct; /** * TreePathCacher is a TreeScanner that creates and caches a TreePath for a target Tree. @@ -55,7 +56,7 @@ public void addPath(Tree target, TreePath path) { * @return the TreePath corresponding to target, or null if target is not found in the * compilation root */ - public TreePath getPath(CompilationUnitTree root, Tree target) { + public TreePath getPath(CompilationUnitTree root, @FindDistinct Tree target) { if (foundPaths.containsKey(target)) { return foundPaths.get(target); } @@ -89,6 +90,7 @@ public void clear() { } /** Scan a single node. The current path is updated for the duration of the scan. */ + @SuppressWarnings("interning:not.interned") // assertion @Override public TreePath scan(Tree tree, Tree target) { TreePath prev = path; diff --git a/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java b/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java index 955a9d21ef5a..523a2382a3cd 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java +++ b/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java @@ -25,6 +25,7 @@ import javax.lang.model.element.TypeParameterElement; import javax.lang.model.type.TypeKind; import javax.lang.model.util.Elements; +import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.framework.qual.AnnotatedFor; import org.checkerframework.framework.qual.DefaultQualifier; import org.checkerframework.framework.qual.TypeUseLocation; @@ -400,7 +401,9 @@ private Element nearestEnclosingExceptLocal(Tree tree) { case VARIABLE: VariableTree vtree = (VariableTree) t; ExpressionTree vtreeInit = vtree.getInitializer(); - if (vtreeInit != null && prev == vtreeInit) { + @SuppressWarnings("interning:not.interned") // check cached value + boolean sameAsPrev = (vtreeInit != null && prev == vtreeInit); + if (sameAsPrev) { Element elt = TreeUtils.elementFromDeclaration((VariableTree) t); AnnotationMirror d = atypeFactory.getDeclAnnotation(elt, DefaultQualifier.class); @@ -832,7 +835,7 @@ protected class DefaultApplierElementImpl extends AnnotatedTypeScanner { @Override - public Void scan(AnnotatedTypeMirror t, AnnotationMirror qual) { + public Void scan(@FindDistinct AnnotatedTypeMirror t, AnnotationMirror qual) { if (!shouldBeAnnotated(t, t == defaultableTypeVar)) { return super.scan(t, qual); } diff --git a/framework/src/main/java/org/checkerframework/framework/util/element/ElementAnnotationUtil.java b/framework/src/main/java/org/checkerframework/framework/util/element/ElementAnnotationUtil.java index 9c3234cbbe72..fac6a4f87b7c 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/element/ElementAnnotationUtil.java +++ b/framework/src/main/java/org/checkerframework/framework/util/element/ElementAnnotationUtil.java @@ -84,6 +84,7 @@ public static void applyAllElementAnnotations( * @param type the type to annotate * @param annotations the annotations to add */ + @SuppressWarnings("interning:not.interned") // AST node comparison static void addDeclarationAnnotationsFromElement( final AnnotatedTypeMirror type, final List annotations) { // The code here should be similar to diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgInferenceUtil.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgInferenceUtil.java index 8287a330dc21..1e2c322ffa5f 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgInferenceUtil.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgInferenceUtil.java @@ -42,6 +42,7 @@ import javax.lang.model.type.TypeVisitor; import javax.lang.model.type.UnionType; import javax.lang.model.util.Types; +import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; @@ -136,8 +137,11 @@ public static Set methodTypeToTargets(final AnnotatedExecutableTyp * assignment context. Returns the annotated type that the method invocation at the leaf is * assigned to. If the result is a primitive, return the boxed version. * - * @return type that path leaf is assigned to + * @param atypeFactory the type factory, for looking up types + * @param path the path whole leaf to look up a type for + * @return the type of path's leaf */ + @SuppressWarnings("interning:not.interned") // AST node comparisons public static AnnotatedTypeMirror assignedTo(AnnotatedTypeFactory atypeFactory, TreePath path) { Tree assignmentContext = TreeUtils.getAssignmentContext(path); AnnotatedTypeMirror res; @@ -264,10 +268,14 @@ private static AnnotatedTypeMirror assignedToExecutable( } /** - * Returns whether argumentTree is the tree at the leaf of path. if tree is a conditional + * Returns whether argumentTree is the tree at the leaf of path. If tree is a conditional * expression, isArgument is called recursively on the true and false expressions. + * + * @param path the path whose leaf to test + * @param argumentTree the expression that might be path's leaf + * @return true if {@code argumentTree} is the leaf of {@code path} */ - private static boolean isArgument(TreePath path, ExpressionTree argumentTree) { + private static boolean isArgument(TreePath path, @FindDistinct ExpressionTree argumentTree) { argumentTree = TreeUtils.withoutParens(argumentTree); if (argumentTree == path.getLeaf()) { return true; diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/EqualitiesSolver.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/EqualitiesSolver.java index 472301bfd60e..8c2e20dd26ac 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/EqualitiesSolver.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/solver/EqualitiesSolver.java @@ -7,6 +7,7 @@ import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeVariable; +import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; @@ -83,7 +84,7 @@ public InferenceResult solveEqualities( /** * Let Ti be a target type parameter. When we reach this method we have inferred an argument, - * Ai, for Ti + * Ai, for Ti. * *

    However, there still may be constraints of the form {@literal Ti = Tj}, {@literal Ti <: * Tj}, {@literal Tj <: Ti} in the constraint map. In this case we need to replace Ti with the @@ -95,9 +96,10 @@ public InferenceResult solveEqualities( * * @param target the target for which we have inferred a concrete type argument * @param type the type inferred + * @param constraints the constraints that are side-effected by this method */ private void rewriteWithInferredType( - final TypeVariable target, + final @FindDistinct TypeVariable target, final AnnotatedTypeMirror type, final ConstraintMap constraints) { @@ -184,10 +186,12 @@ private void rewriteWithInferredType( * * @param target the target for which we know another target is exactly equal to this target * @param inferredTarget the other target inferred to be equal + * @param constraints the constraints that are side-effected by this method + * @param typeFactory type factory */ private void rewriteWithInferredTarget( - final TypeVariable target, - final TypeVariable inferredTarget, + final @FindDistinct TypeVariable target, + final @FindDistinct TypeVariable inferredTarget, final ConstraintMap constraints, final AnnotatedTypeFactory typeFactory) { final TargetConstraints targetRecord = constraints.getConstraints(target); diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationUtils.java index 6164f1d821cc..ae0e8c2505d6 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/AnnotationUtils.java @@ -32,6 +32,8 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.util.ElementFilter; +import org.checkerframework.checker.interning.qual.CompareToMethod; +import org.checkerframework.checker.interning.qual.EqualsMethod; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers; @@ -78,6 +80,7 @@ public static final String annotationName(AnnotationMirror annotation) { * @param a2 the second AnnotationMirror to compare * @return true iff a1 and a2 are the same annotation */ + @EqualsMethod public static boolean areSame(AnnotationMirror a1, AnnotationMirror a2) { if (a1 == a2) { return true; @@ -105,8 +108,8 @@ public static boolean areSame(AnnotationMirror a1, AnnotationMirror a2) { * @param a2 the second AnnotationMirror to compare * @return true iff a1 and a2 have the same annotation name * @see #areSame(AnnotationMirror, AnnotationMirror) - * @return true iff a1 and a2 have the same annotation name */ + @EqualsMethod public static boolean areSameByName(AnnotationMirror a1, AnnotationMirror a2) { if (a1 == a2) { return true; @@ -358,6 +361,7 @@ public static int compareAnnotationMirrors(AnnotationMirror a1, AnnotationMirror * @param av2 the second AnnotationValue to compare * @return 0 if if the two annotation values are the same */ + @CompareToMethod private static int compareAnnotationValue(AnnotationValue av1, AnnotationValue av2) { if (av1 == av2) { return 0; @@ -370,11 +374,14 @@ private static int compareAnnotationValue(AnnotationValue av1, AnnotationValue a } /** - * Return 0 if the two annotation values are the same. + * Compares two annotation values for order. * * @param val1 a value returned by {@code AnnotationValue.getValue()} * @param val2 a value returned by {@code AnnotationValue.getValue()} + * @return a negative integer, zero, or a positive integer as the first annotation value is less + * than, equal to, or greater than the second annotation value */ + @CompareToMethod private static int compareAnnotationValueValue(@Nullable Object val1, @Nullable Object val2) { if (val1 == val2) { return 0; @@ -565,6 +572,7 @@ public static EnumSet getElementKindsForElementType(ElementType ele * @param am2 the second AnnotationMirror to compare * @return true if if the two annotations have the same elements (fields) */ + @EqualsMethod public static boolean sameElementValues(AnnotationMirror am1, AnnotationMirror am2) { if (am1 == am2) { return true; diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java index 5fd50be43ad3..9093d7460531 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java @@ -68,6 +68,7 @@ import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; +import org.checkerframework.checker.interning.qual.PolyInterned; import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -341,7 +342,9 @@ public static boolean isSelfAccess(final ExpressionTree tree) { * @param tree an expression tree * @return the outermost non-parenthesized tree enclosed by the given tree */ - public static ExpressionTree withoutParens(final ExpressionTree tree) { + @SuppressWarnings("interning:return.type.incompatible") // polymorphism implementation + public static @PolyInterned ExpressionTree withoutParens( + final @PolyInterned ExpressionTree tree) { ExpressionTree t = tree; while (t.getKind() == Tree.Kind.PARENTHESIZED) { t = ((ParenthesizedTree) t).getExpression(); @@ -409,7 +412,9 @@ public static Pair enclosingNonParen(final TreePath path) { return getAssignmentContext(parentPath); case CONDITIONAL_EXPRESSION: ConditionalExpressionTree cet = (ConditionalExpressionTree) parent; - if (cet.getCondition() == treePath.getLeaf()) { + @SuppressWarnings("interning:not.interned") // AST node comparison + boolean conditionIsLeaf = (cet.getCondition() == treePath.getLeaf()); + if (conditionIsLeaf) { // The assignment context for the condition is simply boolean. // No point in going on. return null; diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TypeAnnotationUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TypeAnnotationUtils.java index f3c833f06145..793c945d8e2c 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TypeAnnotationUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TypeAnnotationUtils.java @@ -88,6 +88,7 @@ public static boolean isSameTAPosition(TypeAnnotationPosition p1, TypeAnnotation * @param p2 the second position * @return true, iff the two positions are equal except for the source tree position */ + @SuppressWarnings("interning:not.interned") // reference equality for onLambda field public static boolean isSameTAPositionExceptTreePos( TypeAnnotationPosition p1, TypeAnnotationPosition p2) { return p1.type == p2.type diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java index 21a6b461ce6b..4aff6832383f 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java @@ -180,12 +180,15 @@ public static boolean isPrimitive(TypeMirror type) { } /** - * Returns true iff the arguments are both the same primitive types. + * Returns true iff the arguments are both the same declared types. * *

    This is needed because class {@code Type.ClassType} does not override equals. * - * @return whether the arguments are the same primitive types + * @param t1 the first type to test + * @param t2 the second type to test + * @return whether the arguments are the same declared types */ + @SuppressWarnings("interning:not.interned") // equality test optimization public static boolean areSameDeclaredTypes(Type.ClassType t1, Type.ClassType t2) { // Do a cheaper test first if (t1.tsym.name != t2.tsym.name) { From ad8aeec9a421aaffc675f742d201f75c0e974a37 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 7 Jul 2020 17:54:47 -0700 Subject: [PATCH 025/138] Grammar fix --- .../main/java/org/checkerframework/javacutil/TypesUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java index 4aff6832383f..af5fecb08435 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java @@ -58,7 +58,7 @@ public static boolean isObject(TypeMirror type) { } /** - * Checks if the type represents a java.lang.Class declared type. + * Checks if the type represents the java.lang.Class declared type. * * @param type the type * @return true iff type represents java.lang.Class From 68a9d89a28a4a52e470ffbdc916360205f4b5f1c Mon Sep 17 00:00:00 2001 From: Martin Kellogg Date: Tue, 7 Jul 2020 18:21:09 -0700 Subject: [PATCH 026/138] Print type parameters correctly for outer classes using -Ainfer=stubs --- .../SceneToStubWriter.java | 46 +++++++++++++++---- .../OuterClassWithTypeParam.java | 9 ++++ 2 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 framework/tests/whole-program-inference/non-annotated/OuterClassWithTypeParam.java diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/SceneToStubWriter.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/SceneToStubWriter.java index fbd1b85866fa..d973f4ec0577 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/SceneToStubWriter.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/SceneToStubWriter.java @@ -14,15 +14,18 @@ import java.util.StringJoiner; import java.util.regex.Pattern; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.index.qual.SameLen; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.signature.qual.BinaryName; import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers; +import org.checkerframework.common.value.qual.MinLen; import org.checkerframework.common.wholeprograminference.scenelib.ASceneWrapper; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.BugInCF; @@ -455,6 +458,11 @@ private static boolean isInternalJDKAnnotation(String annotationName) { private static int printClassDefinitions( String basename, AClass aClass, PrintWriter printWriter) { String[] classNames = basename.split("\\$"); + TypeElement innermostTypeElt = aClass.getTypeElement(); + if (innermostTypeElt == null) { + throw new BugInCF("typeElement was unexpectedly null in this aClass: " + aClass); + } + TypeElement[] typeElements = getTypeElementsForClasses(innermostTypeElt, classNames); for (int i = 0; i < classNames.length; i++) { String nameToPrint = classNames[i]; @@ -464,9 +472,14 @@ private static int printClassDefinitions( } else { printWriter.print("class "); } - printWriter.print(formatAnnotations(aClass.getAnnotations())); + if (i == classNames.length - 1) { + // Only print class annotations on the innermost class, which corresponds to aClass. + // If there should be class annotations on another class, it will have its own stub + // file, which will eventually be merged with this one. + printWriter.print(formatAnnotations(aClass.getAnnotations())); + } printWriter.print(nameToPrint); - printTypeParameters(aClass, printWriter); + printTypeParameters(typeElements[i], printWriter); printWriter.println(" {"); if (aClass.isEnum(nameToPrint) && i != classNames.length - 1) { // Print a blank set of enum constants if this is an outer enum. @@ -477,6 +490,27 @@ private static int printClassDefinitions( return classNames.length; } + /** + * Constructs an array of TypeElements corresponding to the list of classes. + * + * @param innermostTypeElt the innermost type element: either an inner class or an outer class + * without any inner classes that should be printed + * @param classNames the names of the containing classes, from outer to inner + * @return an array of TypeElements whose entry at a given index represents the type named at + * that index in {@code classNames} + */ + private static TypeElement @SameLen("#2") [] getTypeElementsForClasses( + TypeElement innermostTypeElt, String @MinLen(1) [] classNames) { + TypeElement[] result = new TypeElement[classNames.length]; + result[classNames.length - 1] = innermostTypeElt; + Element elt = innermostTypeElt; + for (int i = classNames.length - 2; i >= 0; i--) { + elt = elt.getEnclosingElement(); + result[i] = (TypeElement) elt; + } + return result; + } + /** * Prints all the fields of a given class. * @@ -728,14 +762,10 @@ private static String indents(int n) { /** * Prints the type parameters of the given class, enclosed in {@code <...>}. * - * @param aClass the class whose type parameters should be printed + * @param type the TypeElement representing the class whose type parameters should be printed * @param printWriter where to print the type parameters */ - private static void printTypeParameters(AClass aClass, PrintWriter printWriter) { - TypeElement type = aClass.getTypeElement(); - if (type == null) { - return; - } + private static void printTypeParameters(TypeElement type, PrintWriter printWriter) { List typeParameters = type.getTypeParameters(); printTypeParameters(typeParameters, printWriter); } diff --git a/framework/tests/whole-program-inference/non-annotated/OuterClassWithTypeParam.java b/framework/tests/whole-program-inference/non-annotated/OuterClassWithTypeParam.java new file mode 100644 index 000000000000..3f02156b9c86 --- /dev/null +++ b/framework/tests/whole-program-inference/non-annotated/OuterClassWithTypeParam.java @@ -0,0 +1,9 @@ +// test file for https://github.com/typetools/checker-framework/issues/3438 + +import testlib.wholeprograminference.qual.Sibling1; + +public class OuterClassWithTypeParam { + public class InnerClass { + Object o = (@Sibling1 Object) null; + } +} From 3c3ada0c0895d43cdfb57c22b7f593c71c98f0ce Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Wed, 8 Jul 2020 14:39:43 -0700 Subject: [PATCH 027/138] Add nullness annotations to framework-test project (#3446) --- checker/bin-devel/test-misc.sh | 5 +++ framework-test/build.gradle | 1 + .../test/ImmutableTestConfiguration.java | 10 +++--- .../framework/test/PerDirectorySuite.java | 5 +++ .../framework/test/PerFileSuite.java | 12 ++++--- .../framework/test/SimpleOptionMap.java | 9 ++--- .../framework/test/TestConfiguration.java | 3 +- .../test/TestConfigurationBuilder.java | 13 +++++-- .../framework/test/TestUtilities.java | 15 +++++--- .../framework/test/TypecheckExecutor.java | 6 +++- .../test/diagnostics/DiagnosticKind.java | 3 +- .../diagnostics/JavaDiagnosticReader.java | 34 ++++++++++++++----- .../test/diagnostics/TestDiagnostic.java | 4 ++- .../test/diagnostics/TestDiagnosticUtils.java | 11 ++++-- 14 files changed, 96 insertions(+), 35 deletions(-) diff --git a/checker/bin-devel/test-misc.sh b/checker/bin-devel/test-misc.sh index c5fd22fe78e7..de8dc1b2b139 100755 --- a/checker/bin-devel/test-misc.sh +++ b/checker/bin-devel/test-misc.sh @@ -22,6 +22,10 @@ source "$SCRIPTDIR"/build.sh ./gradlew htmlValidate --console=plain --warning-mode=all --no-daemon # Javadoc documentation +CHECKJAVADOC=1 +# Uncomment this line temporarily for refactorings that touch a lot of code. +CHECKJAVADOC=0 # TEMPORARY +if [ $CHECKJAVADOC -eq 1 ]; then status=0 ./gradlew javadoc --console=plain --warning-mode=all --no-daemon || status=1 ./gradlew javadocPrivate --console=plain --warning-mode=all --no-daemon || status=1 @@ -30,6 +34,7 @@ status=0 (./gradlew javadocDoclintAll --console=plain --warning-mode=all --no-daemon > /tmp/warnings-jda.txt 2>&1) || true /tmp/"$USER"/plume-scripts/ci-lint-diff /tmp/warnings-jda.txt || status=1 if [ $status -ne 0 ]; then exit $status; fi +fi # User documentation make -C docs/manual all diff --git a/framework-test/build.gradle b/framework-test/build.gradle index b9dc7b2705c1..c10be8e1f0f3 100644 --- a/framework-test/build.gradle +++ b/framework-test/build.gradle @@ -10,6 +10,7 @@ dependencies { // docs/developer/release/maven-artifacts/framework-test-pom.xml must be changed, too. implementation group: 'junit', name: 'junit', version: '4.13' implementation project(':javacutil') + implementation project(':checker-qual') if (Jvm.current().toolsJar) { tagletImplementation files(Jvm.current().toolsJar) diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/ImmutableTestConfiguration.java b/framework-test/src/main/java/org/checkerframework/framework/test/ImmutableTestConfiguration.java index 09213aa7ff7f..92f94d79e122 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/ImmutableTestConfiguration.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/ImmutableTestConfiguration.java @@ -6,6 +6,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.javacutil.SystemUtil; /** @@ -25,7 +26,7 @@ public class ImmutableTestConfiguration implements TestConfiguration { * ) * } */ - private final Map options; + private final Map options; /** * These files contain diagnostics that should be returned by Javac. If this list is empty, the * diagnostics are instead read from comments in the Java file itself @@ -48,12 +49,13 @@ public ImmutableTestConfiguration( List diagnosticFiles, List testSourceFiles, List processors, - Map options, + Map options, boolean shouldEmitDebugInfo) { this.diagnosticFiles = Collections.unmodifiableList(diagnosticFiles); this.testSourceFiles = Collections.unmodifiableList(new ArrayList<>(testSourceFiles)); this.processors = Collections.unmodifiableList(new ArrayList<>(processors)); - this.options = Collections.unmodifiableMap(new LinkedHashMap<>(options)); + this.options = + Collections.unmodifiableMap(new LinkedHashMap(options)); this.shouldEmitDebugInfo = shouldEmitDebugInfo; } @@ -73,7 +75,7 @@ public List getProcessors() { } @Override - public Map getOptions() { + public Map getOptions() { return options; } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/PerDirectorySuite.java b/framework-test/src/main/java/org/checkerframework/framework/test/PerDirectorySuite.java index 4132ca190567..f0c6fe5ecab1 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/PerDirectorySuite.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/PerDirectorySuite.java @@ -51,6 +51,7 @@ protected List getChildren() { * * @param klass the class whose tests to run */ + @SuppressWarnings("nullness") // JUnit needs to be annotated public PerDirectorySuite(Class klass) throws Throwable { super(klass, Collections.emptyList()); final TestClass testClass = getTestClass(); @@ -63,6 +64,7 @@ public PerDirectorySuite(Class klass) throws Throwable { } /** Returns a list of one-element arrays, each containing a Java File. */ + @SuppressWarnings("nullness") // JUnit needs to be annotated private List> getParametersList(TestClass klass) throws Throwable { FrameworkMethod method = getParametersMethod(klass); @@ -158,6 +160,9 @@ public Object createTest() throws Exception { String testCaseName() { File file = javaFiles.get(0).getParentFile(); + if (file == null) { + throw new Error("root was passed? " + javaFiles.get(0)); + } return file.getPath().replace("tests" + System.getProperty("file.separator"), ""); } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/PerFileSuite.java b/framework-test/src/main/java/org/checkerframework/framework/test/PerFileSuite.java index 41e5961a0e31..5115b6617409 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/PerFileSuite.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/PerFileSuite.java @@ -51,6 +51,7 @@ protected List getChildren() { * * @param klass the class whose tests to run */ + @SuppressWarnings("nullness") // JUnit needs to be annotated public PerFileSuite(Class klass) throws Throwable { super(klass, Collections.emptyList()); final TestClass testClass = getTestClass(); @@ -63,7 +64,10 @@ public PerFileSuite(Class klass) throws Throwable { } /** Returns a list of one-element arrays, each containing a Java File. */ - @SuppressWarnings("unchecked") + @SuppressWarnings({ + "unchecked", + "nullness" // JUnit needs to be annotated + }) private List getParametersList(TestClass klass) throws Throwable { FrameworkMethod method = getParametersMethod(klass); @@ -126,9 +130,9 @@ private FrameworkMethod getParametersMethod(TestClass testClass) { break; case "getTestFiles": - // we'll force people to return a List for now but enforcing exactl List or a - // subtype thereof is not easy - if (!returnType.getCanonicalName().equals(List.class.getCanonicalName())) { + // We'll force people to return a List for now but enforcing exactly List or a + // subtype thereof is not easy. + if (!List.class.getCanonicalName().equals(returnType.getCanonicalName())) { throw new RuntimeException( "getTestFiles must return a List, found " + returnType); } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/SimpleOptionMap.java b/framework-test/src/main/java/org/checkerframework/framework/test/SimpleOptionMap.java index ae19edb08901..0f88b4aae6a9 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/SimpleOptionMap.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/SimpleOptionMap.java @@ -5,6 +5,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import org.checkerframework.checker.nullness.qual.Nullable; /** * SimpleOptionMap is a very basic Option container. The keys of the Option container are the set of @@ -24,14 +25,14 @@ */ public class SimpleOptionMap { /** A Map from optionName to arg, where arg is null if the option doesn't require any args. */ - private final Map options = new LinkedHashMap<>(); + private final Map options = new LinkedHashMap<>(); /** * Clears the current set of options and copies the input options to this map. * * @param options the new options to use for this object */ - public void setOptions(Map options) { + public void setOptions(Map options) { this.options.clear(); this.options.putAll(options); } @@ -104,7 +105,7 @@ public void addOptionIfValueNonEmpty(String option, String value) { * * @param options the options to add to this object */ - public void addOptions(Map options) { + public void addOptions(Map options) { this.options.putAll(options); } @@ -133,7 +134,7 @@ public void addOptions(Iterable newOptions) { * * @return the options in this object */ - public Map getOptions() { + public Map getOptions() { return options; } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/TestConfiguration.java b/framework-test/src/main/java/org/checkerframework/framework/test/TestConfiguration.java index 228aa7ce0b09..063eef626e0d 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/TestConfiguration.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/TestConfiguration.java @@ -3,6 +3,7 @@ import java.io.File; import java.util.List; import java.util.Map; +import org.checkerframework.checker.nullness.qual.Nullable; /** A configuration for running CheckerFrameworkTests or running the TypecheckExecutor. */ public interface TestConfiguration { @@ -60,7 +61,7 @@ public interface TestConfiguration { * @return a Map representing all command-line options to Javac other than source files and * processors */ - Map getOptions(); + Map getOptions(); /** * Returns the map returned by {@link #getOptions}, flattened into a list. The entries will be diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/TestConfigurationBuilder.java b/framework-test/src/main/java/org/checkerframework/framework/test/TestConfigurationBuilder.java index 899251c3ce42..ea98cd51a8ae 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/TestConfigurationBuilder.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/TestConfigurationBuilder.java @@ -9,6 +9,9 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.checkerframework.checker.initialization.qual.UnknownInitialization; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.nullness.qual.RequiresNonNull; import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.SystemUtil; @@ -242,7 +245,7 @@ public List validate(boolean requireProcessors) { errors.add("No processors were specified!"); } - final Map optionMap = options.getOptions(); + final Map optionMap = options.getOptions(); if (!optionMap.containsKey("-d") || optionMap.get("-d") == null) { errors.add("No output directory was specified."); } @@ -289,7 +292,7 @@ public TestConfigurationBuilder setSourceFiles(List sourceFiles) { return this; } - public TestConfigurationBuilder setOptions(Map options) { + public TestConfigurationBuilder setOptions(Map options) { this.options.setOptions(options); return this; } @@ -312,7 +315,11 @@ public TestConfigurationBuilder addOptionIfValueNonEmpty(String option, String v return this; } - public TestConfigurationBuilder addOptions(Map options) { + @SuppressWarnings("nullness:return.type.incompatible") // need @PolyInitialized annotation + @RequiresNonNull("this.options") + public TestConfigurationBuilder addOptions( + @UnknownInitialization(TestConfigurationBuilder.class) TestConfigurationBuilder this, + Map options) { this.options.addOptions(options); return this; } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/TestUtilities.java b/framework-test/src/main/java/org/checkerframework/framework/test/TestUtilities.java index ad88a0df44fb..79b7e6b5aadc 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/TestUtilities.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/TestUtilities.java @@ -21,6 +21,8 @@ import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.ToolProvider; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.javacutil.SystemUtil; import org.junit.Assert; @@ -86,12 +88,14 @@ public static List> findJavaFilesPerDirectory(File parent, String... * @return a list of list of Java test files */ private static List> findJavaTestFilesInDirectory(File dir) { - assert dir.isDirectory(); List> fileGroupedByDirectory = new ArrayList<>(); List filesInDir = new ArrayList<>(); fileGroupedByDirectory.add(filesInDir); String[] dirContents = dir.list(); + if (dirContents == null) { + throw new Error("Not a directory: " + dir); + } Arrays.sort(dirContents); for (String fileName : dirContents) { File file = new File(dir, fileName); @@ -145,7 +149,8 @@ public static List deeplyEnclosedJavaTestFiles(File directory) { List javaFiles = new ArrayList<>(); - File[] in = directory.listFiles(); + @SuppressWarnings("nullness") // checked above that it's a directory + File @NonNull [] in = directory.listFiles(); Arrays.sort( in, new Comparator() { @@ -201,7 +206,7 @@ public static boolean isJavaTestFile(File file) { return true; } - public static String diagnosticToString( + public static @Nullable String diagnosticToString( final Diagnostic diagnostic, boolean usingAnomsgtxt) { String result = diagnostic.toString().trim(); @@ -274,10 +279,10 @@ public static File findComparisonFile(File testFile) { return comparisonFile; } - public static List optionMapToList(Map options) { + public static List optionMapToList(Map options) { List optionList = new ArrayList<>(options.size() * 2); - for (Map.Entry opt : options.entrySet()) { + for (Map.Entry opt : options.entrySet()) { optionList.add(opt.getKey()); if (opt.getValue() != null) { diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckExecutor.java b/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckExecutor.java index 69f515a14b13..a17ba5a7f3bd 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckExecutor.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckExecutor.java @@ -29,7 +29,11 @@ public TypecheckResult runTest(TestConfiguration configuration) { * configuration, and return place the result in a CompilationResult */ public CompilationResult compile(TestConfiguration configuration) { - TestUtilities.ensureDirectoryExists(new File(configuration.getOptions().get("-d"))); + String dOption = configuration.getOptions().get("-d"); + if (dOption == null) { + throw new Error("-d not supplied"); + } + TestUtilities.ensureDirectoryExists(new File(dOption)); final StringWriter javacOutput = new StringWriter(); DiagnosticCollector diagnostics = new DiagnosticCollector<>(); diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/DiagnosticKind.java b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/DiagnosticKind.java index da2b1cd68bbb..2e145ae5fc2a 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/DiagnosticKind.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/DiagnosticKind.java @@ -2,6 +2,7 @@ import java.util.LinkedHashMap; import java.util.Map; +import org.checkerframework.checker.nullness.qual.Nullable; /** Indicates what type of Error was, or expected to be, encountered during typechecking. */ public enum DiagnosticKind { @@ -27,7 +28,7 @@ public enum DiagnosticKind { /** * Convert a string as it would appear in error messages or source code into a DiagnosticKind */ - public static DiagnosticKind fromParseString(String parseStr) { + public static @Nullable DiagnosticKind fromParseString(String parseStr) { return stringToCategory.get(parseStr); } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/JavaDiagnosticReader.java b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/JavaDiagnosticReader.java index a673aea5eb40..2dd8b1573b77 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/JavaDiagnosticReader.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/JavaDiagnosticReader.java @@ -10,6 +10,11 @@ import java.util.List; import java.util.NoSuchElementException; import javax.tools.JavaFileObject; +import org.checkerframework.checker.index.qual.GTENegativeOne; +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; /** * A file can indicate expected javac diagnostics. There are two types of such files: Java source @@ -150,8 +155,9 @@ private interface DiagnosticCodec { /// End of static methods, start of per-instance state /// - private final File toRead; - private final JavaFileObject toReadFileObject; + // Exactly one of toRead and toReadFileObject is non-null + private final @Nullable File toRead; + private final @Nullable JavaFileObject toReadFileObject; private final DiagnosticCodec codec; private final String filename; @@ -159,34 +165,38 @@ private interface DiagnosticCodec { private boolean initialized = false; private boolean closed = false; - private LineNumberReader reader = null; + private @MonotonicNonNull LineNumberReader reader = null; - private String nextLine = null; - private int nextLineNumber = -1; + private @Nullable String nextLine = null; + private @GTENegativeOne int nextLineNumber = -1; private JavaDiagnosticReader(File toRead, DiagnosticCodec codec) { this.toRead = toRead; this.toReadFileObject = null; this.codec = codec; - this.filename = shortFileName(toRead.getAbsolutePath()); + this.filename = shortFileName(toRead.getName()); } private JavaDiagnosticReader(JavaFileObject toRead, DiagnosticCodec codec) { this.toRead = null; this.toReadFileObject = toRead; this.codec = codec; - this.filename = shortFileName(toRead.getName()); + this.filename = shortFileName(toReadFileObject.getName()); } - private String shortFileName(String name) { + private static String shortFileName(String name) { int index = name.lastIndexOf(File.separator); return name.substring(index + 1, name.length()); } + @SuppressWarnings( + "nullness:contracts.postcondition.not.satisfied") // if initialized, reader is non-null + @EnsuresNonNull("reader") private void init() throws IOException { if (!initialized && !closed) { initialized = true; + @SuppressWarnings("nullness") // either toRead or toReadFileObject is non-null Reader fileReader = (toRead != null) ? new FileReader(toRead) : toReadFileObject.openReader(true); reader = new LineNumberReader(fileReader); @@ -195,6 +205,8 @@ private void init() throws IOException { } @Override + @SuppressWarnings("contracts.postcondition.not.satisfied") // if closed, reader is non-null + @EnsuresNonNull("reader") public boolean hasNext() { if (closed) { return false; @@ -223,7 +235,9 @@ public TestDiagnosticLine next() { if (nextLine == null) { throw new NoSuchElementException(); } else if (closed) { - throw new RuntimeException("Reader has been closed: " + toRead.getAbsolutePath()); + throw new RuntimeException( + "Reader has been closed: " + + ((toRead != null) ? toRead.getAbsolutePath() : toReadFileObject)); } String current = nextLine; @@ -252,11 +266,13 @@ public TestDiagnosticLine next() { } } + @RequiresNonNull("reader") protected void advance() throws IOException { nextLine = reader.readLine(); nextLineNumber = reader.getLineNumber(); } + @RequiresNonNull("reader") public void close() { try { if (initialized) { diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnostic.java b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnostic.java index 3526c779c880..eae22d962710 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnostic.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnostic.java @@ -1,5 +1,7 @@ package org.checkerframework.framework.test.diagnostics; +import org.checkerframework.checker.nullness.qual.Nullable; + /** * Represents an expected error/warning message in a Java test file or an error/warning reported by * the Javac compiler. See {@link JavaDiagnosticReader} and {@link TestDiagnosticLine}. @@ -89,7 +91,7 @@ public String asSourceString() { * @return true if this and otherObj are equal according to lineNumber, kind, and message */ @Override - public boolean equals(Object otherObj) { + public boolean equals(@Nullable Object otherObj) { if (otherObj == null || otherObj.getClass() != TestDiagnostic.class) { return false; } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticUtils.java b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticUtils.java index 9663f4dd0f59..9f7f308ee527 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticUtils.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticUtils.java @@ -11,6 +11,8 @@ import java.util.regex.Pattern; import javax.tools.Diagnostic; import javax.tools.JavaFileObject; +import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; +import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.javacutil.Pair; /** A set of utilities and factory methods useful for working with TestDiagnostics. */ @@ -105,11 +107,12 @@ static Pair dropParentheses(final String str) { return Pair.of(false, str); } + @SuppressWarnings("nullness") // TODO: regular expression group access protected static TestDiagnostic fromPatternMatching( Pattern diagnosticPattern, Pattern warningPattern, String filename, - Long lineNumber, + @Nullable Long lineNumber, String diagnosticString) { final DiagnosticKind kind; final String message; @@ -211,6 +214,9 @@ private static Pair parseCategoryString(String category category = category.substring(fixable.length()); } DiagnosticKind categoryEnum = DiagnosticKind.fromParseString(category); + if (categoryEnum == null) { + throw new Error("Unparseable category: " + category); + } return Pair.of(categoryEnum, isFixable); } @@ -245,7 +251,8 @@ public static String handleEndOfLineJavaDiagnostic(String originalLine) { } /** Return true if this line in a Java file continues an expected diagnostic. */ - public static boolean isJavaDiagnosticLineContinuation(String originalLine) { + @EnsuresNonNullIf(result = true, expression = "#1") + public static boolean isJavaDiagnosticLineContinuation(@Nullable String originalLine) { if (originalLine == null) { return false; } From d3da57a5f9e0bc3b9bbf294159dc7283ec0a8c16 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Wed, 8 Jul 2020 15:41:48 -0700 Subject: [PATCH 028/138] Test case for issue #3449 --- .../Issue3449.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 checker/tests/nullness-safedefaultssourcecode/Issue3449.java diff --git a/checker/tests/nullness-safedefaultssourcecode/Issue3449.java b/checker/tests/nullness-safedefaultssourcecode/Issue3449.java new file mode 100644 index 000000000000..5a41b3666960 --- /dev/null +++ b/checker/tests/nullness-safedefaultssourcecode/Issue3449.java @@ -0,0 +1,15 @@ +// Test case for https://tinyurl.com/cfissue/3449 + +// @skip-test until the issue is fixed + +import org.checkerframework.framework.qual.AnnotatedFor; + +@AnnotatedFor("nullness") +public class Issue3449 { + + int length; + + public Issue3449(Object... args) { + length = args.length; + } +} From c728dbd0bee1c1f52cb4ec5964f9237aae4f5da9 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 9 Jul 2020 08:01:47 -0700 Subject: [PATCH 029/138] Re-enable Javadoc tests --- build.gradle | 4 +++- checker/bin-devel/test-misc.sh | 11 ++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index e26f1bd2e164..f419e1fe560f 100644 --- a/build.gradle +++ b/build.gradle @@ -318,7 +318,7 @@ List getJavaFilesToFormat(projectName) { } } - // Collect all java files in jtreg directory + // Collect all java files in jtregJdk11 directory fileTree("${project(projectName).projectDir}/jtregJdk11").visit { details -> if (!details.path.contains("nullness-javac-errors") && details.name.endsWith('java')) { javaFiles.add(details.file) @@ -881,6 +881,8 @@ task checkBasicStyle(group: 'Format') { 'manual.html', 'manual.html-e', 'junit.*.properties', + 'checker/dist/META-INF/maven/org.apache.bcel/bcel/pom.xml', + 'checker/dist/META-INF/maven/org.apache.commons/commons-text/pom.xml', 'framework/src/main/resources/git.properties'] doLast { FileTree tree = fileTree(dir: projectDir) diff --git a/checker/bin-devel/test-misc.sh b/checker/bin-devel/test-misc.sh index de8dc1b2b139..d11c16e3af8b 100755 --- a/checker/bin-devel/test-misc.sh +++ b/checker/bin-devel/test-misc.sh @@ -22,10 +22,10 @@ source "$SCRIPTDIR"/build.sh ./gradlew htmlValidate --console=plain --warning-mode=all --no-daemon # Javadoc documentation -CHECKJAVADOC=1 -# Uncomment this line temporarily for refactorings that touch a lot of code. -CHECKJAVADOC=0 # TEMPORARY -if [ $CHECKJAVADOC -eq 1 ]; then +# Uncomment this line temporarily for refactorings that touch a lot of code that +# you don't understand. Then, recomment it as soon as the pull request is merged. +# SKIPJAVADOC=1 +if [ -z "$SKIPJAVADOC" ]; then status=0 ./gradlew javadoc --console=plain --warning-mode=all --no-daemon || status=1 ./gradlew javadocPrivate --console=plain --warning-mode=all --no-daemon || status=1 @@ -34,7 +34,8 @@ status=0 (./gradlew javadocDoclintAll --console=plain --warning-mode=all --no-daemon > /tmp/warnings-jda.txt 2>&1) || true /tmp/"$USER"/plume-scripts/ci-lint-diff /tmp/warnings-jda.txt || status=1 if [ $status -ne 0 ]; then exit $status; fi -fi +fi # end of "if [ -z $SKIPJAVADOC ]" + # User documentation make -C docs/manual all From 85b5f18cc23504d0bdee7ecb2200f96b897e55c5 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 9 Jul 2020 08:17:16 -0700 Subject: [PATCH 030/138] Bump versions.autoValue from 1.7.3 to 1.7.4 --- framework/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/build.gradle b/framework/build.gradle index 767590149e70..5c7d31c97274 100644 --- a/framework/build.gradle +++ b/framework/build.gradle @@ -18,7 +18,7 @@ sourceSets { } def versions = [ - autoValue : "1.7.3", + autoValue : "1.7.4", lombok : "1.18.12", ] From 2f8bfe0f894b387ec0de01205f74ebef410a9733 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 9 Jul 2020 09:50:17 -0700 Subject: [PATCH 031/138] Use -ArequirePrefixInWarningSuppressions when type-checking CF source code --- build.gradle | 1 + .../dataflow/analysis/AbstractAnalysis.java | 10 +++++----- .../dataflow/analysis/ForwardAnalysisImpl.java | 2 +- .../dataflow/cfg/DOTCFGVisualizer.java | 4 ++-- .../dataflow/cfg/StringCFGVisualizer.java | 2 +- .../framework/util/MultiGraphQualifierHierarchy.java | 2 +- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/build.gradle b/build.gradle index f419e1fe560f..ae9fc0f68d8a 100644 --- a/build.gradle +++ b/build.gradle @@ -272,6 +272,7 @@ def createCheckTypeTask(projectName, checker, shortName, args = []) { '-Xlint:-processing', '-Xmaxerrs', '10000', '-Xmaxwarns', '10000', + '-ArequirePrefixInWarningSuppressions', ] options.compilerArgs += args diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractAnalysis.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractAnalysis.java index 2117ef9e7e5a..22aed654df4f 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractAnalysis.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractAnalysis.java @@ -161,7 +161,7 @@ public Direction getDirection() { } @Override - @SuppressWarnings("contracts.precondition.override.invalid") // implementation field + @SuppressWarnings("nullness:contracts.precondition.override.invalid") // implementation field @RequiresNonNull("cfg") public AnalysisResult getResult() { if (isRunning) { @@ -224,7 +224,7 @@ public IdentityHashMap getNodeValues() { } @Override - @SuppressWarnings("contracts.precondition.override.invalid") // implementation field + @SuppressWarnings("nullness:contracts.precondition.override.invalid") // implementation field @RequiresNonNull("cfg") public @Nullable S getRegularExitStore() { SpecialBlock regularExitBlock = cfg.getRegularExitBlock(); @@ -236,7 +236,7 @@ public IdentityHashMap getNodeValues() { } @Override - @SuppressWarnings("contracts.precondition.override.invalid") // implementation field + @SuppressWarnings("nullness:contracts.precondition.override.invalid") // implementation field @RequiresNonNull("cfg") public @Nullable S getExceptionalExitStore() { SpecialBlock exceptionalExitBlock = cfg.getExceptionalExitBlock(); @@ -444,7 +444,7 @@ protected static class Worklist { * forward analysis. */ public class ForwardDFOComparator implements Comparator { - @SuppressWarnings("unboxing.of.nullable") + @SuppressWarnings("nullness:unboxing.of.nullable") @Override public int compare(Block b1, Block b2) { return depthFirstOrder.get(b1) - depthFirstOrder.get(b2); @@ -456,7 +456,7 @@ public int compare(Block b1, Block b2) { * backward analysis. */ public class BackwardDFOComparator implements Comparator { - @SuppressWarnings("unboxing.of.nullable") + @SuppressWarnings("nullness:unboxing.of.nullable") @Override public int compare(Block b1, Block b2) { return depthFirstOrder.get(b2) - depthFirstOrder.get(b1); diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysisImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysisImpl.java index c14e4531571a..8ec65c9cd715 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysisImpl.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysisImpl.java @@ -225,7 +225,7 @@ public void performAnalysisBlock(Block b) { } @Override - @SuppressWarnings("contracts.precondition.override.invalid") // implementation field + @SuppressWarnings("nullness:contracts.precondition.override.invalid") // implementation field @RequiresNonNull("cfg") public List>> getReturnStatementStores() { List>> result = new ArrayList<>(); diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/DOTCFGVisualizer.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/DOTCFGVisualizer.java index 654a4fe5de0c..2c807d0b45fe 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/DOTCFGVisualizer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/DOTCFGVisualizer.java @@ -26,7 +26,7 @@ import org.checkerframework.javacutil.UserError; /** Generate a graph description in the DOT language of a control graph. */ -@SuppressWarnings("initialization.fields.uninitialized") // uses init method +@SuppressWarnings("nullness:initialization.fields.uninitialized") // uses init method public class DOTCFGVisualizer< V extends AbstractValue, S extends Store, T extends TransferFunction> extends AbstractCFGVisualizer { @@ -78,7 +78,7 @@ public void init(Map args) { return res; } - @SuppressWarnings("enhancedfor.type.incompatible") + @SuppressWarnings("keyfor:enhancedfor.type.incompatible") @Override public String visualizeNodes( Set blocks, ControlFlowGraph cfg, @Nullable Analysis analysis) { diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/StringCFGVisualizer.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/StringCFGVisualizer.java index f257c4dde774..393b369927ba 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/StringCFGVisualizer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/StringCFGVisualizer.java @@ -31,7 +31,7 @@ public Map visualize( return res; } - @SuppressWarnings("enhancedfor.type.incompatible") + @SuppressWarnings("keyfor:enhancedfor.type.incompatible") @Override public String visualizeNodes( Set blocks, ControlFlowGraph cfg, @Nullable Analysis analysis) { diff --git a/framework/src/main/java/org/checkerframework/framework/util/MultiGraphQualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/util/MultiGraphQualifierHierarchy.java index 1485c2307475..9b0e16eab6d2 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/MultiGraphQualifierHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/util/MultiGraphQualifierHierarchy.java @@ -28,7 +28,7 @@ * *

    This class is immutable and can be only created through {@link MultiGraphFactory}. */ -@SuppressWarnings("interned") // TODO after https://tinyurl.com/cfissue/3404 is merged +@SuppressWarnings("interning") // TODO after https://tinyurl.com/cfissue/3404 is merged public class MultiGraphQualifierHierarchy extends QualifierHierarchy { /** From cc5c3c7fed63ee4bebfe055d428fbbf25ad7f9da Mon Sep 17 00:00:00 2001 From: Martin Kellogg Date: Thu, 9 Jul 2020 11:25:30 -0700 Subject: [PATCH 032/138] Do not do inference on compiler-generated variables in whole-program inference --- .../WholeProgramInferenceScenes.java | 11 +++++++++++ .../non-annotated/Tempvars.java | 8 ++++++++ 2 files changed, 19 insertions(+) create mode 100644 framework/tests/whole-program-inference/non-annotated/Tempvars.java diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceScenes.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceScenes.java index 342e084d19e4..9cd29c88fa93 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceScenes.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceScenes.java @@ -9,6 +9,7 @@ import com.sun.tools.javac.code.Symbol.VarSymbol; import java.util.List; import java.util.Map; +import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.VariableElement; @@ -283,6 +284,16 @@ public void updateFromFieldAssignment( + lhs.getClass()); } + // Do not attempt to infer types for fields that do not have valid + // names. For example, compiler-generated temporary variables will + // have invalid names. Recording facts about fields with + // invalid names causes jaif-based WPI to crash when reading the .jaif + // file, and stub-based WPI to generate unparseable stub files. + // See https://github.com/typetools/checker-framework/issues/3442 + if (!SourceVersion.isIdentifier(fieldName)) { + return; + } + // If the inferred field has a declaration annotation with the // @IgnoreInWholeProgramInference meta-annotation, exit this routine. if (atf.getDeclAnnotation(element, IgnoreInWholeProgramInference.class) != null diff --git a/framework/tests/whole-program-inference/non-annotated/Tempvars.java b/framework/tests/whole-program-inference/non-annotated/Tempvars.java new file mode 100644 index 000000000000..a46b67df0c07 --- /dev/null +++ b/framework/tests/whole-program-inference/non-annotated/Tempvars.java @@ -0,0 +1,8 @@ +// test case for https://github.com/typetools/checker-framework/issues/3442 + +public class Tempvars { + static { + int i = 0; + i++; + } +} From 5436ad483d71d9342725396d9de4ea4717ccebd3 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Thu, 9 Jul 2020 12:23:58 -0700 Subject: [PATCH 033/138] Skip directories with generated java files. (#3454) --- build.gradle | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ae9fc0f68d8a..688e5b026d80 100644 --- a/build.gradle +++ b/build.gradle @@ -308,7 +308,10 @@ List getJavaFilesToFormat(projectName) { } // Collect all java files in tests directory fileTree("${project(projectName).projectDir}/tests").visit { details -> - if (!details.path.contains("nullness-javac-errors") && details.name.endsWith('java')) { + if (!details.path.contains("nullness-javac-errors") + && !details.path.contains("returnsreceiverdelomboked") + && !details.path.contains("build") + && details.name.endsWith('java')) { javaFiles.add(details.file) } } From 8f3de5c5865905eea1e3eb6cb0b0fdd7edb6ac96 Mon Sep 17 00:00:00 2001 From: lnsun <57457122+lnsun@users.noreply.github.com> Date: Thu, 9 Jul 2020 17:21:23 -0400 Subject: [PATCH 034/138] discoverer added --- .../common/basetype/BaseTypeChecker.java | 38 ++++++----- .../common/basetype/BaseTypeVisitor.java | 24 +++---- .../util/classfinder/AbstractDiscoverer.java | 63 +++++++++++++++++++ .../util/classfinder/RecursiveDiscoverer.java | 31 +++++++++ 4 files changed, 124 insertions(+), 32 deletions(-) create mode 100644 framework/src/main/java/org/checkerframework/common/util/classfinder/AbstractDiscoverer.java create mode 100644 framework/src/main/java/org/checkerframework/common/util/classfinder/RecursiveDiscoverer.java diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java index 7d56101f9fe2..29c5a6b8e669 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java @@ -31,6 +31,8 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.signature.qual.ClassGetName; import org.checkerframework.common.reflection.MethodValChecker; +import org.checkerframework.common.util.classfinder.AbstractDiscoverer; +import org.checkerframework.common.util.classfinder.RecursiveDiscoverer; import org.checkerframework.dataflow.cfg.CFGVisualizer; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.source.SourceChecker; @@ -212,23 +214,25 @@ public boolean shouldResolveReflection() { */ @Override protected BaseTypeVisitor createSourceVisitor() { - // Try to reflectively load the visitor. - Class checkerClass = this.getClass(); - - while (checkerClass != BaseTypeChecker.class) { - BaseTypeVisitor result = - invokeConstructorFor( - BaseTypeChecker.getRelatedClassName(checkerClass, "Visitor"), - new Class[] {BaseTypeChecker.class}, - new Object[] {this}); - if (result != null) { - return result; - } - checkerClass = checkerClass.getSuperclass(); - } - - // If a visitor couldn't be loaded reflectively, return the default. - return new BaseTypeVisitor(this); + AbstractDiscoverer> discoverer = new RecursiveDiscoverer<>(); + return discoverer.findAndInitWithChecker(this, "Visitor", BaseTypeVisitor::new); + // // Try to reflectively load the visitor. + // Class checkerClass = this.getClass(); + // + // while (checkerClass != BaseTypeChecker.class) { + // BaseTypeVisitor result = + // invokeConstructorFor( + // BaseTypeChecker.getRelatedClassName(checkerClass, "Visitor"), + // new Class[] {BaseTypeChecker.class}, + // new Object[] {this}); + // if (result != null) { + // return result; + // } + // checkerClass = checkerClass.getSuperclass(); + // } + // + // // If a visitor couldn't be loaded reflectively, return the default. + // return new BaseTypeVisitor(this); } /** 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 60344db687dd..89b064b926bc 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -69,6 +69,8 @@ import javax.tools.Diagnostic.Kind; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.interning.qual.FindDistinct; +import org.checkerframework.common.util.classfinder.AbstractDiscoverer; +import org.checkerframework.common.util.classfinder.RecursiveDiscoverer; import org.checkerframework.dataflow.analysis.FlowExpressions; import org.checkerframework.dataflow.analysis.FlowExpressions.Receiver; import org.checkerframework.dataflow.analysis.TransferResult; @@ -239,22 +241,14 @@ protected BaseTypeVisitor(BaseTypeChecker checker, Factory typeFactory) { * * @return the appropriate type factory */ - @SuppressWarnings("unchecked") // unchecked cast to type variable protected Factory createTypeFactory() { - // Try to reflectively load the type factory. - Class checkerClass = checker.getClass(); - while (checkerClass != BaseTypeChecker.class) { - AnnotatedTypeFactory result = - BaseTypeChecker.invokeConstructorFor( - BaseTypeChecker.getRelatedClassName( - checkerClass, "AnnotatedTypeFactory"), - new Class[] {BaseTypeChecker.class}, - new Object[] {checker}); - if (result != null) { - return (Factory) result; - } - checkerClass = checkerClass.getSuperclass(); - } + AbstractDiscoverer discoverer = new RecursiveDiscoverer<>(); + return discoverer.findAndInitWithChecker( + checker, "AnnotatedTypeFactory", this::getDafaultFactory); + } + + @SuppressWarnings("unchecked") + private Factory getDafaultFactory(BaseTypeChecker checker) { try { return (Factory) new BaseAnnotatedTypeFactory(checker); } catch (Throwable t) { diff --git a/framework/src/main/java/org/checkerframework/common/util/classfinder/AbstractDiscoverer.java b/framework/src/main/java/org/checkerframework/common/util/classfinder/AbstractDiscoverer.java new file mode 100644 index 000000000000..31afab24c48f --- /dev/null +++ b/framework/src/main/java/org/checkerframework/common/util/classfinder/AbstractDiscoverer.java @@ -0,0 +1,63 @@ +package org.checkerframework.common.util.classfinder; + +import org.checkerframework.common.basetype.BaseTypeChecker; + +/** + * Enables the auto discovery of classes following the naming convention. + * + * @param + */ +public abstract class AbstractDiscoverer { + + /** + * As the name implies, this interface is used to get the default value + * + * @param the type of the default value + */ + public interface DefaultGetter { + + /** + * get the default value + * + * @param checker a checker as the argument of constructor + * @return the default value + */ + DefaultType getDefault(BaseTypeChecker checker); + } + + /** + * Find a properly-named component. + * + * @param checker the current checker + * @param replacement the component suffix, e.g. "Visitor" or "AnnotatedTypeFactory" + * @param defaultGetter a getter for the default value when cannot find the class + * @param constructorParamTypes parameter types passed to constructor of the target class + * @param constructorArgs arguments passed to constructor of the target class + * @return the properly-named component found + */ + public abstract T find( + BaseTypeChecker checker, + String replacement, + DefaultGetter defaultGetter, + Class[] constructorParamTypes, + Object[] constructorArgs); + + /** + * Find a properly-named component with its constructor invoked with the only argument {@code + * checker} + * + * @param checker a checker + * @param replacement the component suffix, e.g. "Visitor" or "AnnotatedTypeFactory" + * @param defaultGetter a getter for the default value when cannot find the class + * @return the properly-named component found + */ + public T findAndInitWithChecker( + BaseTypeChecker checker, String replacement, DefaultGetter defaultGetter) { + return find( + checker, + replacement, + defaultGetter, + new Class[] {BaseTypeChecker.class}, + new Object[] {checker}); + } +} diff --git a/framework/src/main/java/org/checkerframework/common/util/classfinder/RecursiveDiscoverer.java b/framework/src/main/java/org/checkerframework/common/util/classfinder/RecursiveDiscoverer.java new file mode 100644 index 000000000000..01f3e40467b6 --- /dev/null +++ b/framework/src/main/java/org/checkerframework/common/util/classfinder/RecursiveDiscoverer.java @@ -0,0 +1,31 @@ +package org.checkerframework.common.util.classfinder; + +import org.checkerframework.common.basetype.BaseTypeChecker; + +public class RecursiveDiscoverer extends AbstractDiscoverer { + + @Override + @SuppressWarnings("unchecked") + public T find( + BaseTypeChecker checker, + String replacement, + DefaultGetter defaultGetter, + Class[] constructorParamTypes, + Object[] constructorArgs) { + // Try to reflectively load the type factory. + Class checkerClass = checker.getClass(); + while (checkerClass != BaseTypeChecker.class) { + T result = + (T) + BaseTypeChecker.invokeConstructorFor( + BaseTypeChecker.getRelatedClassName(checkerClass, replacement), + constructorParamTypes, + constructorArgs); + if (result != null) { + return result; + } + checkerClass = checkerClass.getSuperclass(); + } + return defaultGetter.getDefault(checker); + } +} From f8fb1f91efcef13bbe70891f32e2cfe675d0172c Mon Sep 17 00:00:00 2001 From: lnsun <57457122+lnsun@users.noreply.github.com> Date: Thu, 9 Jul 2020 18:01:01 -0400 Subject: [PATCH 035/138] package, static util, naming --- .../common/basetype/BaseTypeChecker.java | 23 +------ .../common/basetype/BaseTypeVisitor.java | 18 ++++-- .../util/classfinder/AbstractDiscoverer.java | 63 ------------------- .../util/classfinder/RecursiveDiscoverer.java | 31 --------- .../framework/util/ComponentFinderUtil.java | 57 +++++++++++++++++ 5 files changed, 71 insertions(+), 121 deletions(-) delete mode 100644 framework/src/main/java/org/checkerframework/common/util/classfinder/AbstractDiscoverer.java delete mode 100644 framework/src/main/java/org/checkerframework/common/util/classfinder/RecursiveDiscoverer.java create mode 100644 framework/src/main/java/org/checkerframework/framework/util/ComponentFinderUtil.java diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java index 29c5a6b8e669..499e119c9833 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java @@ -31,8 +31,6 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.signature.qual.ClassGetName; import org.checkerframework.common.reflection.MethodValChecker; -import org.checkerframework.common.util.classfinder.AbstractDiscoverer; -import org.checkerframework.common.util.classfinder.RecursiveDiscoverer; import org.checkerframework.dataflow.cfg.CFGVisualizer; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.source.SourceChecker; @@ -40,6 +38,7 @@ import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; import org.checkerframework.framework.type.QualifierHierarchy; import org.checkerframework.framework.type.TypeHierarchy; +import org.checkerframework.framework.util.ComponentFinderUtil; import org.checkerframework.framework.util.TreePathCacher; import org.checkerframework.javacutil.AbstractTypeProcessor; import org.checkerframework.javacutil.AnnotationProvider; @@ -214,25 +213,7 @@ public boolean shouldResolveReflection() { */ @Override protected BaseTypeVisitor createSourceVisitor() { - AbstractDiscoverer> discoverer = new RecursiveDiscoverer<>(); - return discoverer.findAndInitWithChecker(this, "Visitor", BaseTypeVisitor::new); - // // Try to reflectively load the visitor. - // Class checkerClass = this.getClass(); - // - // while (checkerClass != BaseTypeChecker.class) { - // BaseTypeVisitor result = - // invokeConstructorFor( - // BaseTypeChecker.getRelatedClassName(checkerClass, "Visitor"), - // new Class[] {BaseTypeChecker.class}, - // new Object[] {this}); - // if (result != null) { - // return result; - // } - // checkerClass = checkerClass.getSuperclass(); - // } - // - // // If a visitor couldn't be loaded reflectively, return the default. - // return new BaseTypeVisitor(this); + return ComponentFinderUtil.findAndInitWithChecker(this, "Visitor", BaseTypeVisitor::new); } /** 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 89b064b926bc..2198cd655d7d 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -69,8 +69,6 @@ import javax.tools.Diagnostic.Kind; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.interning.qual.FindDistinct; -import org.checkerframework.common.util.classfinder.AbstractDiscoverer; -import org.checkerframework.common.util.classfinder.RecursiveDiscoverer; import org.checkerframework.dataflow.analysis.FlowExpressions; import org.checkerframework.dataflow.analysis.FlowExpressions.Receiver; import org.checkerframework.dataflow.analysis.TransferResult; @@ -105,6 +103,7 @@ 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.ComponentFinderUtil; import org.checkerframework.framework.util.Contract; import org.checkerframework.framework.util.Contract.ConditionalPostcondition; import org.checkerframework.framework.util.Contract.Postcondition; @@ -242,13 +241,20 @@ protected BaseTypeVisitor(BaseTypeChecker checker, Factory typeFactory) { * @return the appropriate type factory */ protected Factory createTypeFactory() { - AbstractDiscoverer discoverer = new RecursiveDiscoverer<>(); - return discoverer.findAndInitWithChecker( - checker, "AnnotatedTypeFactory", this::getDafaultFactory); + return ComponentFinderUtil.findAndInitWithChecker( + checker, "AnnotatedTypeFactory", this::createDefaultTypeFactory); } + /** + * Create a default type factory if {@link ComponentFinderUtil} failed to find a factory + * + * @param checker the checker previously passed to {@link + * ComponentFinderUtil#findAndInitWithChecker(BaseTypeChecker, String, + * ComponentFinderUtil.DefaultGetter)} + * @return a {@link BaseAnnotatedTypeFactory} + */ @SuppressWarnings("unchecked") - private Factory getDafaultFactory(BaseTypeChecker checker) { + private Factory createDefaultTypeFactory(BaseTypeChecker checker) { try { return (Factory) new BaseAnnotatedTypeFactory(checker); } catch (Throwable t) { diff --git a/framework/src/main/java/org/checkerframework/common/util/classfinder/AbstractDiscoverer.java b/framework/src/main/java/org/checkerframework/common/util/classfinder/AbstractDiscoverer.java deleted file mode 100644 index 31afab24c48f..000000000000 --- a/framework/src/main/java/org/checkerframework/common/util/classfinder/AbstractDiscoverer.java +++ /dev/null @@ -1,63 +0,0 @@ -package org.checkerframework.common.util.classfinder; - -import org.checkerframework.common.basetype.BaseTypeChecker; - -/** - * Enables the auto discovery of classes following the naming convention. - * - * @param - */ -public abstract class AbstractDiscoverer { - - /** - * As the name implies, this interface is used to get the default value - * - * @param the type of the default value - */ - public interface DefaultGetter { - - /** - * get the default value - * - * @param checker a checker as the argument of constructor - * @return the default value - */ - DefaultType getDefault(BaseTypeChecker checker); - } - - /** - * Find a properly-named component. - * - * @param checker the current checker - * @param replacement the component suffix, e.g. "Visitor" or "AnnotatedTypeFactory" - * @param defaultGetter a getter for the default value when cannot find the class - * @param constructorParamTypes parameter types passed to constructor of the target class - * @param constructorArgs arguments passed to constructor of the target class - * @return the properly-named component found - */ - public abstract T find( - BaseTypeChecker checker, - String replacement, - DefaultGetter defaultGetter, - Class[] constructorParamTypes, - Object[] constructorArgs); - - /** - * Find a properly-named component with its constructor invoked with the only argument {@code - * checker} - * - * @param checker a checker - * @param replacement the component suffix, e.g. "Visitor" or "AnnotatedTypeFactory" - * @param defaultGetter a getter for the default value when cannot find the class - * @return the properly-named component found - */ - public T findAndInitWithChecker( - BaseTypeChecker checker, String replacement, DefaultGetter defaultGetter) { - return find( - checker, - replacement, - defaultGetter, - new Class[] {BaseTypeChecker.class}, - new Object[] {checker}); - } -} diff --git a/framework/src/main/java/org/checkerframework/common/util/classfinder/RecursiveDiscoverer.java b/framework/src/main/java/org/checkerframework/common/util/classfinder/RecursiveDiscoverer.java deleted file mode 100644 index 01f3e40467b6..000000000000 --- a/framework/src/main/java/org/checkerframework/common/util/classfinder/RecursiveDiscoverer.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.checkerframework.common.util.classfinder; - -import org.checkerframework.common.basetype.BaseTypeChecker; - -public class RecursiveDiscoverer extends AbstractDiscoverer { - - @Override - @SuppressWarnings("unchecked") - public T find( - BaseTypeChecker checker, - String replacement, - DefaultGetter defaultGetter, - Class[] constructorParamTypes, - Object[] constructorArgs) { - // Try to reflectively load the type factory. - Class checkerClass = checker.getClass(); - while (checkerClass != BaseTypeChecker.class) { - T result = - (T) - BaseTypeChecker.invokeConstructorFor( - BaseTypeChecker.getRelatedClassName(checkerClass, replacement), - constructorParamTypes, - constructorArgs); - if (result != null) { - return result; - } - checkerClass = checkerClass.getSuperclass(); - } - return defaultGetter.getDefault(checker); - } -} diff --git a/framework/src/main/java/org/checkerframework/framework/util/ComponentFinderUtil.java b/framework/src/main/java/org/checkerframework/framework/util/ComponentFinderUtil.java new file mode 100644 index 000000000000..3399afb016f6 --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/util/ComponentFinderUtil.java @@ -0,0 +1,57 @@ +package org.checkerframework.framework.util; + +import org.checkerframework.common.basetype.BaseTypeChecker; + +/** + * Util class to find components (e.g. visitors and factories) following the naming convention + * reflectively. For example, ABCChecker's default naming for visitor is "ABCVisitor". If a visitor + * class with that name exists, then instantiate it and returns. Otherwise try to find the super, + * and finally uses the {@code defaultGetter} callback to get a default value if all attempting + * fails. + */ +public class ComponentFinderUtil { + public interface DefaultGetter { + + /** + * The logic for getting the default value if all attempt fails + * + * @param checker a checker as the argument of constructor + * @return the default value + */ + DefaultType getDefault(BaseTypeChecker checker); + } + + @SuppressWarnings("unchecked") + public static T find( + BaseTypeChecker checker, + String replacement, + DefaultGetter defaultGetter, + Class[] constructorParamTypes, + Object[] constructorArgs) { + // Try to reflectively load the component. + Class checkerClass = checker.getClass(); + while (checkerClass != BaseTypeChecker.class) { + T result = + (T) + BaseTypeChecker.invokeConstructorFor( + BaseTypeChecker.getRelatedClassName(checkerClass, replacement), + constructorParamTypes, + constructorArgs); + if (result != null) { + return result; + } + checkerClass = checkerClass.getSuperclass(); + } + return defaultGetter.getDefault(checker); + } + + public static T findAndInitWithChecker( + BaseTypeChecker checker, String replacement, DefaultGetter defaultGetter) { + return find( + checker, + replacement, + defaultGetter, + new Class[] {BaseTypeChecker.class}, + new Object[] {checker}); + } +} From 055a82e847a1f8918eab81fa5ce68f35d38737b4 Mon Sep 17 00:00:00 2001 From: lnsun <57457122+lnsun@users.noreply.github.com> Date: Thu, 9 Jul 2020 18:44:49 -0400 Subject: [PATCH 036/138] doc --- .../framework/util/ComponentFinderUtil.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/framework/src/main/java/org/checkerframework/framework/util/ComponentFinderUtil.java b/framework/src/main/java/org/checkerframework/framework/util/ComponentFinderUtil.java index 3399afb016f6..560a8d4a69e8 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/ComponentFinderUtil.java +++ b/framework/src/main/java/org/checkerframework/framework/util/ComponentFinderUtil.java @@ -21,6 +21,16 @@ public interface DefaultGetter { DefaultType getDefault(BaseTypeChecker checker); } + /** + * Find a component named with the checker naming convention. + * + * @param checker the current checker + * @param replacement the component suffix, e.g. "Visitor" or "AnnotatedTypeFactory" + * @param defaultGetter a getter for the default value when cannot find the class + * @param constructorParamTypes parameter types passed to constructor of the target class + * @param constructorArgs arguments passed to constructor of the target class + * @return the properly-named component found + */ @SuppressWarnings("unchecked") public static T find( BaseTypeChecker checker, @@ -45,6 +55,15 @@ public static T find( return defaultGetter.getDefault(checker); } + /** + * Find a component named with the checker naming convention with its constructor invoked with + * the only argument {@code checker} + * + * @param checker a checker + * @param replacement the component suffix, e.g. "Visitor" or "AnnotatedTypeFactory" + * @param defaultGetter a getter for the default value when cannot find the class + * @return the properly-named component found + */ public static T findAndInitWithChecker( BaseTypeChecker checker, String replacement, DefaultGetter defaultGetter) { return find( From b4d8199248456bc36cc643f1ece32fa8ec887969 Mon Sep 17 00:00:00 2001 From: lnsun <57457122+lnsun@users.noreply.github.com> Date: Thu, 9 Jul 2020 21:38:03 -0400 Subject: [PATCH 037/138] doc --- .../framework/util/ComponentFinderUtil.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/util/ComponentFinderUtil.java b/framework/src/main/java/org/checkerframework/framework/util/ComponentFinderUtil.java index 560a8d4a69e8..8d21ea0ff615 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/ComponentFinderUtil.java +++ b/framework/src/main/java/org/checkerframework/framework/util/ComponentFinderUtil.java @@ -6,10 +6,15 @@ * Util class to find components (e.g. visitors and factories) following the naming convention * reflectively. For example, ABCChecker's default naming for visitor is "ABCVisitor". If a visitor * class with that name exists, then instantiate it and returns. Otherwise try to find the super, - * and finally uses the {@code defaultGetter} callback to get a default value if all attempting - * fails. + * and finally uses the {@code defaultGetter} callback to get a default value if all attempt fails. */ public class ComponentFinderUtil { + + /** + * A single method interface for getting default type for components by method reference. + * + * @param the type for the default value + */ public interface DefaultGetter { /** @@ -24,6 +29,7 @@ public interface DefaultGetter { /** * Find a component named with the checker naming convention. * + * @param type of the component * @param checker the current checker * @param replacement the component suffix, e.g. "Visitor" or "AnnotatedTypeFactory" * @param defaultGetter a getter for the default value when cannot find the class @@ -59,6 +65,7 @@ public static T find( * Find a component named with the checker naming convention with its constructor invoked with * the only argument {@code checker} * + * @param type of the component * @param checker a checker * @param replacement the component suffix, e.g. "Visitor" or "AnnotatedTypeFactory" * @param defaultGetter a getter for the default value when cannot find the class From 492f41e79a4c1df149049d968bf1813a4f668c61 Mon Sep 17 00:00:00 2001 From: Jason Waataja Date: Thu, 9 Jul 2020 19:02:23 -0700 Subject: [PATCH 038/138] Prevent local classes from being written to stub files (#3462) * Prevent local classes from being written * Fix typo --- .../wholeprograminference/SceneToStubWriter.java | 16 ++++++++++------ .../non-annotated/LocalClassTest.java | 11 +++++++++++ 2 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 framework/tests/whole-program-inference/non-annotated/LocalClassTest.java diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/SceneToStubWriter.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/SceneToStubWriter.java index d973f4ec0577..32bf572bb8cb 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/SceneToStubWriter.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/SceneToStubWriter.java @@ -74,10 +74,14 @@ public final class SceneToStubWriter { private SceneToStubWriter() {} /** - * A pattern matching the name of an anonymous inner class, or a class nested within one. An - * anonymous inner class has a basename like Outer$1. + * A pattern matching the name of an anonymous inner class, a local class, or a class nested + * within one of these types of classes. An anonymous inner class has a basename like Outer$1 + * and a local class has a basename like Outer$1Inner. See Java Language + * Specification, section 13.1. */ - private static final Pattern anonymousInnerClassPattern = Pattern.compile("\\$\\d+(\\$|$)"); + private static final Pattern anonymousInnerClassOrLocalClassPattern = + Pattern.compile("\\$\\d+"); /** How far to indent when writing members of a stub file. */ private static final String INDENT = " "; @@ -675,9 +679,9 @@ private static boolean isPrintable(@BinaryName String classname, AClass aClass) return false; } - // Do not attempt to print stubs for anonymous inner classes or their inner classes, because - // the stub parser cannot read them. - if (anonymousInnerClassPattern.matcher(basename).find()) { + // Do not attempt to print stubs for anonymous inner classes, local classes, or their inner + // classes, because the stub parser cannot read them. + if (anonymousInnerClassOrLocalClassPattern.matcher(basename).find()) { return false; } diff --git a/framework/tests/whole-program-inference/non-annotated/LocalClassTest.java b/framework/tests/whole-program-inference/non-annotated/LocalClassTest.java new file mode 100644 index 000000000000..018f5c75b0d0 --- /dev/null +++ b/framework/tests/whole-program-inference/non-annotated/LocalClassTest.java @@ -0,0 +1,11 @@ +// test case for https://github.com/typetools/checker-framework/issues/3461 + +import testlib.wholeprograminference.qual.Sibling1; + +public class LocalClassTest { + public void method() { + class Local { + Object o = (@Sibling1 Object) null; + } + } +} From 2b029991c6f248373708c339807f6887ac633b40 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 9 Jul 2020 19:37:14 -0700 Subject: [PATCH 039/138] Add tests for conversionCharFromFormat --- .../checker/formatter/FormatUtil.java | 22 +++++++++ .../test/java/tests/FormatterUnitTest.java | 45 +++++++++++++++++++ checker/tests/README | 2 +- 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 checker/src/test/java/tests/FormatterUnitTest.java diff --git a/checker/src/main/java/org/checkerframework/checker/formatter/FormatUtil.java b/checker/src/main/java/org/checkerframework/checker/formatter/FormatUtil.java index 0205c852ef67..7424b67c7e60 100644 --- a/checker/src/main/java/org/checkerframework/checker/formatter/FormatUtil.java +++ b/checker/src/main/java/org/checkerframework/checker/formatter/FormatUtil.java @@ -134,6 +134,28 @@ private static char conversionCharFromFormat(Matcher m) { } } + /** + * Return the conversion character that is in the given format specifier + * + * @param formatSpecifier a format specifier; see + * https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Formatter.html#syntax} + * @return the conversion character that is in the given format specifier + * @deprecated This method is public only for testing. Use {@link + * #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<>(); Matcher m = fsPattern.matcher(format); diff --git a/checker/src/test/java/tests/FormatterUnitTest.java b/checker/src/test/java/tests/FormatterUnitTest.java new file mode 100644 index 000000000000..f80ae1c895ea --- /dev/null +++ b/checker/src/test/java/tests/FormatterUnitTest.java @@ -0,0 +1,45 @@ +package tests; + +import org.checkerframework.checker.formatter.FormatUtil; +import org.junit.Assert; +import org.junit.Test; + +public class FormatterUnitTest { + + @SuppressWarnings("deprecation") // calls methods that are used only for testing + @Test + public void testConversionCharFromFormat() { + Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%1$2s")); + Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%1$s")); + Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%1$tb")); + Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%1$te")); + Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%1$tm")); + Assert.assertEquals('t', FormatUtil.conversionCharFromFormat("%1$tY")); + Assert.assertEquals('f', FormatUtil.conversionCharFromFormat("%+10.4f")); + Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%2$2s")); + Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%2$s")); + Assert.assertEquals('f', FormatUtil.conversionCharFromFormat("%(,.2f")); + Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%3$2s")); + Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%3$s")); + Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%4$2s")); + Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("%4$s")); + Assert.assertEquals('s', FormatUtil.conversionCharFromFormat("% Date: Fri, 10 Jul 2020 08:31:22 -0700 Subject: [PATCH 040/138] Default array.length to @Initialized. (#3459) Fixes #3449. --- .../InitializationAnnotatedTypeFactory.java | 10 ++++++++++ .../nullness-safedefaultssourcecode/Issue3449.java | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnnotatedTypeFactory.java index eb714334f367..964d3320d683 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationAnnotatedTypeFactory.java @@ -3,6 +3,7 @@ import com.sun.source.tree.ClassTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.LiteralTree; +import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; @@ -742,6 +743,15 @@ public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { } return super.visitLiteral(tree, type); } + + @Override + public Void visitMemberSelect( + MemberSelectTree node, AnnotatedTypeMirror annotatedTypeMirror) { + if (TreeUtils.isArrayLengthAccess(node)) { + annotatedTypeMirror.replaceAnnotation(INITIALIZED); + } + return super.visitMemberSelect(node, annotatedTypeMirror); + } } /** diff --git a/checker/tests/nullness-safedefaultssourcecode/Issue3449.java b/checker/tests/nullness-safedefaultssourcecode/Issue3449.java index 5a41b3666960..417721ecb0a4 100644 --- a/checker/tests/nullness-safedefaultssourcecode/Issue3449.java +++ b/checker/tests/nullness-safedefaultssourcecode/Issue3449.java @@ -1,15 +1,15 @@ // Test case for https://tinyurl.com/cfissue/3449 -// @skip-test until the issue is fixed - import org.checkerframework.framework.qual.AnnotatedFor; @AnnotatedFor("nullness") public class Issue3449 { int length; + Object[] objs; public Issue3449(Object... args) { length = args.length; + objs = args; } } From 820cbbf0183110a91ae7995c29551ffe2da6c8c8 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Fri, 10 Jul 2020 08:33:38 -0700 Subject: [PATCH 041/138] Add SignednessUtilExtra to checker-qual.jar (#3458) but exclude it from checker-qual-android.jar. --- LICENSE.txt | 7 +++---- checker-qual-android/build.gradle | 1 + checker-qual/build.gradle | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index b233dc6aa64c..70d6a70fe2fb 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -13,10 +13,9 @@ the parts that you might want to include with your own program. (The text of this license also appears below.) This applies to the checker-qual*.jar and all the files that appear in it: every file in a qual/ directory, plus utility files FormatUtil.java, - I18nFormatUtil.java, NullnessUtil.java, Opt.java, - PurityUnqualified.java, RegexUtil.java, SignednessUtil.java, and - UnitsTools.java. It also applies to other utility files - (SignednessUtilExtra.java) and to the cleanroom implementations of + I18nFormatUtil.java, NullnessUtil.java, Opt.java, PurityUnqualified.java, + RegexUtil.java, SignednessUtil.java, SignednessUtilExtra.java, and + UnitsTools.java. It also applies to the cleanroom implementations of third-party annotations (in checker/src/testannotations/ and in framework/src/main/java/org/jmlspecs/). diff --git a/checker-qual-android/build.gradle b/checker-qual-android/build.gradle index 022874e4e35f..92eea98ad906 100644 --- a/checker-qual-android/build.gradle +++ b/checker-qual-android/build.gradle @@ -10,6 +10,7 @@ task copySources(type: Copy, dependsOn: ':checker-qual:copySources') { } from files('../checker-qual/src/main') include "**/*.java" + exclude "**/SignednessUtilExtra.java" into file('src/main') // Not read only because "replaceAnnotations" tasks writes to the files. diff --git a/checker-qual/build.gradle b/checker-qual/build.gradle index 6acd2f2be6ee..85e6273ea90b 100644 --- a/checker-qual/build.gradle +++ b/checker-qual/build.gradle @@ -28,7 +28,7 @@ task copySources(type: Copy) { include "**/Opt.java" include "**/RegexUtil.java" include "**/SignednessUtil.java" - // include "**/SignednessUtilExtra.java" + include "**/SignednessUtilExtra.java" include "**/UnitsTools.java" // Make files read only. From b087dbacd9e68c8c7709aac96a37b165af50e4fa Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Fri, 10 Jul 2020 08:56:39 -0700 Subject: [PATCH 042/138] Tweak regarding format specifiers --- .../checker/formatter/FormatUtil.java | 97 ++++++++++++++++--- 1 file changed, 86 insertions(+), 11 deletions(-) diff --git a/checker/src/main/java/org/checkerframework/checker/formatter/FormatUtil.java b/checker/src/main/java/org/checkerframework/checker/formatter/FormatUtil.java index 7424b67c7e60..857646363b56 100644 --- a/checker/src/main/java/org/checkerframework/checker/formatter/FormatUtil.java +++ b/checker/src/main/java/org/checkerframework/checker/formatter/FormatUtil.java @@ -10,22 +10,46 @@ 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; /** This class provides a collection of utilities to ease working with format strings. */ 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; } @@ -35,8 +59,14 @@ ConversionCategory category() { * Returns if the format string is satisfiable, and if the format's parameters match the passed * {@link ConversionCategory}s. Otherwise an {@link Error} is thrown. * - *

    TODO introduce more such functions, see RegexUtil for examples + * @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 + @SuppressWarnings("nullness:argument.type.incompatible") // https://tinyurl.com/cfissue/3449 @ReturnsFormat public static String asFormat(String format, ConversionCategory... cc) throws IllegalFormatException { @@ -54,7 +84,13 @@ public static String asFormat(String format, ConversionCategory... cc) return format; } - /** Throws an exception if the format is not syntactically valid. */ + /** + * Throws an exception if the format is not syntactically valid. + * + * @param format a format string + * @throws IllegalFormatException if the format string is invalid + */ + @SuppressWarnings("nullness:argument.type.incompatible") // https://tinyurl.com/cfissue/3449 public static void tryFormatSatisfiability(String format) throws IllegalFormatException { @SuppressWarnings("unused") String unused = String.format(format, (Object[]) null); @@ -104,12 +140,43 @@ public static ConversionCategory[] formatParameterCategories(String format) return res; } - // %[argument_index$][flags][width][.precision][t]conversion - private static final String formatSpecifier = + /** + * 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; - private static Pattern fsPattern = Pattern.compile(formatSpecifier); + /** + * 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); @@ -125,17 +192,25 @@ private static int indexFromFormat(Matcher m) { return index; } - private static char conversionCharFromFormat(Matcher m) { - String dt = m.group(5); - if (dt == null) { - return m.group(6).charAt(0); + /** + * 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 dt.charAt(0); + return m.group(formatSpecifierConversion).charAt(0); } } /** - * Return the conversion character that is in the given format specifier + * Return the conversion character that is in the given format specifier. * * @param formatSpecifier a format specifier; see * https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Formatter.html#syntax} From 0004ac239c4c392f30fc9ee6a2af4bdbb65742e5 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Fri, 10 Jul 2020 10:51:43 -0700 Subject: [PATCH 043/138] Run the Nullness Checker with -AuseConservativeDefaultsForUncheckedCode=source on the Checker Framework --- build.gradle | 12 +++- checker/bin-devel/test-misc.sh | 2 +- .../checker/formatter/FormatUtil.java | 24 +++++-- .../formatter/qual/ConversionCategory.java | 72 ++++++++++++------- .../checker/i18nformatter/I18nFormatUtil.java | 42 ++++++++--- .../qual/I18nConversionCategory.java | 35 +++++---- .../checker/nullness/NullnessUtil.java | 2 + .../checker/nullness/Opt.java | 2 + .../checker/regex/RegexUtil.java | 2 + .../checker/signedness/SignednessUtil.java | 2 + .../signedness/SignednessUtilExtra.java | 3 + .../checker/units/UnitsTools.java | 5 +- .../diagnostics/JavaDiagnosticReader.java | 3 +- .../framework/source/DiagMessage.java | 6 ++ 14 files changed, 152 insertions(+), 60 deletions(-) diff --git a/build.gradle b/build.gradle index 688e5b026d80..13b42ece07aa 100644 --- a/build.gradle +++ b/build.gradle @@ -641,10 +641,10 @@ subprojects { // Add tasks to run various checkers on all the main source sets. // These pass and are run by nonJunitTests. createCheckTypeTask(project.name, 'org.checkerframework.checker.interning.InterningChecker', 'Interning') - createCheckTypeTask(project.name, 'org.checkerframework.checker.nullness.NullnessChecker', 'NullnessSkipSome', ['-AskipUses=com.sun.*', '-AskipDefs=org.checkerframework.checker.*|org.checkerframework.common.*|org.checkerframework.framework.*']) + createCheckTypeTask(project.name, 'org.checkerframework.checker.nullness.NullnessChecker', 'NullnessOnlyAnnotatedFor', ['-AskipUses=com.sun.*', '-AuseConservativeDefaultsForUncheckedCode=source']) createCheckTypeTask(project.name, 'org.checkerframework.framework.util.PurityChecker', 'Purity') createCheckTypeTask(project.name, 'org.checkerframework.checker.signature.SignatureChecker', 'Signature') - // These do not yet pass. TODO: Make them pass. + // These pass on some subprojects, which nonJunitTests runs. TODO: Incrementally add @AnnotatedFor on classes. createCheckTypeTask(project.name, 'org.checkerframework.checker.nullness.NullnessChecker', 'Nullness', ['-AskipUses=com.sun.*']) @@ -806,7 +806,13 @@ subprojects { // Create a nonJunitTests task per project tasks.create(name: 'nonJunitTests', group: 'Verification') { description 'Run all Checker Framework tests except for the Junit tests.' - dependsOn('checkInterning', 'checkNullnessSkipSome', 'checkPurity', 'checkSignature') + dependsOn('checkInterning', 'checkPurity', 'checkSignature') + if (project.name.is('framework') || project.name.is('checker')) { + dependsOn('checkNullnessOnlyAnnotatedFor') + } else { + dependsOn('checkNullness') + } + if (project.name.is('framework') || project.name.is('checker')) { dependsOn('checkCompilerMessages', 'jtregTests') } diff --git a/checker/bin-devel/test-misc.sh b/checker/bin-devel/test-misc.sh index d11c16e3af8b..3d108b07f59d 100755 --- a/checker/bin-devel/test-misc.sh +++ b/checker/bin-devel/test-misc.sh @@ -24,7 +24,7 @@ source "$SCRIPTDIR"/build.sh # Javadoc documentation # Uncomment this line temporarily for refactorings that touch a lot of code that # you don't understand. Then, recomment it as soon as the pull request is merged. -# SKIPJAVADOC=1 +SKIPJAVADOC=1 if [ -z "$SKIPJAVADOC" ]; then status=0 ./gradlew javadoc --console=plain --warning-mode=all --no-daemon || status=1 diff --git a/checker/src/main/java/org/checkerframework/checker/formatter/FormatUtil.java b/checker/src/main/java/org/checkerframework/checker/formatter/FormatUtil.java index 857646363b56..7ac851930795 100644 --- a/checker/src/main/java/org/checkerframework/checker/formatter/FormatUtil.java +++ b/checker/src/main/java/org/checkerframework/checker/formatter/FormatUtil.java @@ -13,6 +13,7 @@ import org.checkerframework.checker.regex.qual.Regex; /** This class provides a collection of utilities to ease working with format strings. */ +// TODO @AnnotatedFor("nullness") public class FormatUtil { /** @@ -92,7 +93,11 @@ public static String asFormat(String format, ConversionCategory... cc) */ @SuppressWarnings("nullness:argument.type.incompatible") // https://tinyurl.com/cfissue/3449 public static void tryFormatSatisfiability(String format) throws IllegalFormatException { - @SuppressWarnings("unused") + @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); } @@ -126,16 +131,20 @@ public static ConversionCategory[] formatParameterCategories(String format) break; } maxindex = Math.max(maxindex, last); + Integer lastKey = last; conv.put( last, ConversionCategory.intersect( - conv.containsKey(last) ? conv.get(last) : ConversionCategory.UNUSED, + conv.containsKey(lastKey) + ? conv.get(lastKey) + : ConversionCategory.UNUSED, c.category())); } ConversionCategory[] res = new ConversionCategory[maxindex + 1]; for (int i = 0; i <= maxindex; ++i) { - res[i] = conv.containsKey(i) ? conv.get(i) : ConversionCategory.UNUSED; + Integer key = i; // autoboxing prevents recognizing that containsKey => get() != null + res[i] = conv.containsKey(key) ? conv.get(key) : ConversionCategory.UNUSED; } return res; } @@ -183,7 +192,8 @@ private static int indexFromFormat(Matcher m) { if (s != null) { // explicit index index = Integer.parseInt(s.substring(0, s.length() - 1)); } else { - if (m.group(2) != null && m.group(2).contains(String.valueOf('<'))) { + 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 @@ -233,7 +243,7 @@ public static char conversionCharFromFormat(String formatSpecifier) { */ private static Conversion[] parse(String format) { ArrayList cs = new ArrayList<>(); - Matcher m = fsPattern.matcher(format); + @Regex(7) Matcher m = fsPattern.matcher(format); while (m.find()) { char c = conversionCharFromFormat(m); switch (c) { @@ -291,7 +301,9 @@ public static class IllegalFormatConversionCategoryException public IllegalFormatConversionCategoryException( ConversionCategory expected, ConversionCategory found) { super( - expected.chars.length() == 0 ? '-' : expected.chars.charAt(0), + 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; diff --git a/checker/src/main/java/org/checkerframework/checker/formatter/qual/ConversionCategory.java b/checker/src/main/java/org/checkerframework/checker/formatter/qual/ConversionCategory.java index 5c0871a6e025..2add5f243b4f 100644 --- a/checker/src/main/java/org/checkerframework/checker/formatter/qual/ConversionCategory.java +++ b/checker/src/main/java/org/checkerframework/checker/formatter/qual/ConversionCategory.java @@ -7,6 +7,7 @@ import java.util.Date; import java.util.HashSet; import java.util.Set; +import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.Pure; /** @@ -108,29 +109,29 @@ public enum ConversionCategory { UNUSED(null /* everything */, null); /** Create a new conversion category. */ - ConversionCategory(Class[] types, String chars) { + ConversionCategory(Class @Nullable [] types, @Nullable String chars) { this.types = types; this.chars = chars; } /** The format types. */ @SuppressWarnings("ImmutableEnumChecker") // TODO: clean this up! - public final Class[] types; + public final Class @Nullable [] types; /** The format characters. */ - public final String chars; + public final @Nullable String chars; /** - * Use this function to get the category associated with a conversion character. For example: + * Converts a conversion character to a category. For example: * - *
    + *
    {@code
    +     * ConversionCategory.fromConversionChar('d') == ConversionCategory.INT
    +     * }
    * - *
    -     * ConversionCategory.fromConversionChar('d') == ConversionCategory.INT;
    -     * 
    - * - *
    + * @param c a conversion character + * @return the category for the given conversion character */ + @SuppressWarnings("nullness:dereference.of.nullable") // `chars` field is non-null for these public static ConversionCategory fromConversionChar(char c) { for (ConversionCategory v : new ConversionCategory[] {GENERAL, CHAR, INT, FLOAT, TIME}) { if (v.chars.contains(String.valueOf(c))) { @@ -177,14 +178,23 @@ public static ConversionCategory intersect(ConversionCategory a, ConversionCateg return a; } - Set> as = arrayToSet(a.types); - Set> bs = arrayToSet(b.types); + @SuppressWarnings( + "nullness:argument.type.incompatible") // `types` field is null only for UNUSED and + // GENERAL + Set> as = arrayToSet(a.types); + @SuppressWarnings( + "nullness:argument.type.incompatible") // `types` field is null only for UNUSED and + // GENERAL + Set> bs = arrayToSet(b.types); as.retainAll(bs); // intersection for (ConversionCategory v : new ConversionCategory[] { CHAR, INT, FLOAT, TIME, CHAR_AND_INT, INT_AND_TIME, NULL }) { - Set> vs = arrayToSet(v.types); + @SuppressWarnings( + "nullness:argument.type.incompatible") // `types` field is null only for UNUSED + // and GENERAL + Set> vs = arrayToSet(v.types); if (vs.equals(as)) { return v; } @@ -221,14 +231,23 @@ public static ConversionCategory union(ConversionCategory a, ConversionCategory return INT; } - Set> as = arrayToSet(a.types); - Set> bs = arrayToSet(b.types); + @SuppressWarnings( + "nullness:argument.type.incompatible") // `types` field is null only for UNUSED and + // GENERAL + Set> as = arrayToSet(a.types); + @SuppressWarnings( + "nullness:argument.type.incompatible") // `types` field is null only for UNUSED and + // GENERAL + Set> bs = arrayToSet(b.types); as.addAll(bs); // union for (ConversionCategory v : new ConversionCategory[] { NULL, CHAR_AND_INT, INT_AND_TIME, CHAR, INT, FLOAT, TIME }) { - Set> vs = arrayToSet(v.types); + @SuppressWarnings( + "nullness:argument.type.incompatible") // `types` field is null only for UNUSED + // and GENERAL + Set> vs = arrayToSet(v.types); if (vs.equals(as)) { return v; } @@ -266,20 +285,25 @@ private String className(Class cls) { } /** Returns a pretty printed {@link ConversionCategory}. */ + @SuppressWarnings( + "nullness:iterating.over.nullable") // `types` field is null only for UNUSED and + // GENERAL @Pure @Override public String toString() { StringBuilder sb = new StringBuilder(this.name()); - sb.append(" conversion category (one of: "); - boolean first = true; - for (Class cls : this.types) { - if (!first) { - sb.append(", "); + if (this != UNUSED && this != GENERAL) { + sb.append(" conversion category (one of: "); + boolean first = true; + for (Class cls : this.types) { + if (!first) { + sb.append(", "); + } + sb.append(className(cls)); + first = false; } - sb.append(className(cls)); - first = false; + sb.append(")"); } - sb.append(")"); return sb.toString(); } } diff --git a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatUtil.java b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatUtil.java index 199a2f681275..5e5379311781 100644 --- a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatUtil.java +++ b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatUtil.java @@ -15,12 +15,18 @@ 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 { /** @@ -28,6 +34,9 @@ public class I18nFormatUtil { * * @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); } @@ -48,19 +57,22 @@ public static I18nConversionCategory[] formatParameterCategories(String format) for (I18nConversion c : cs) { int index = c.index; + Integer indexKey = index; conv.put( - index, + indexKey, I18nConversionCategory.intersect( c.category, - conv.containsKey(index) - ? conv.get(index) + 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++) { - res[i] = conv.containsKey(i) ? conv.get(i) : I18nConversionCategory.UNUSED; + Integer indexKey = i; + res[i] = + conv.containsKey(indexKey) ? conv.get(indexKey) : I18nConversionCategory.UNUSED; } return res; } @@ -118,18 +130,22 @@ private static class MessageFormatParser { public static int maxOffset; - /** The locale to use for formatting numbers and dates. */ - private static Locale locale; + /** 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. */ - private static List categories; + /** + * 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 List argumentIndices; + 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; @@ -162,6 +178,7 @@ private static class MessageFormatParser { "", "short", "medium", "long", "full" }; + @EnsuresNonNull({"categories", "argumentIndices", "locale"}) public static I18nConversion[] parse(String pattern) { MessageFormatParser.categories = new ArrayList<>(); MessageFormatParser.argumentIndices = new ArrayList<>(); @@ -175,8 +192,10 @@ public static I18nConversion[] parse(String pattern) { return ret; } + @SuppressWarnings("nullness:dereference.of.nullable") // complex rules for segments[i] + @RequiresNonNull({"argumentIndices", "categories", "locale"}) private static void applyPattern(String pattern) { - StringBuilder[] segments = new StringBuilder[4]; + @Nullable StringBuilder[] segments = new StringBuilder[4]; // Allocate only segments[SEG_RAW] here. The rest are // allocated on demand. segments[SEG_RAW] = new StringBuilder(); @@ -263,7 +282,8 @@ private static void applyPattern(String pattern) { } /** Side-effects {@code categories} field, adding to it an I18nConversionCategory. */ - private static void makeFormat(int offsetNumber, StringBuilder[] textSegments) { + @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]; diff --git a/checker/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nConversionCategory.java b/checker/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nConversionCategory.java index d1d816917c94..6b697978c333 100644 --- a/checker/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nConversionCategory.java +++ b/checker/src/main/java/org/checkerframework/checker/i18nformatter/qual/I18nConversionCategory.java @@ -4,25 +4,23 @@ import java.util.Date; import java.util.HashSet; import java.util.Set; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.AnnotatedFor; /** * Elements of this enumeration are used in a {@link I18nFormat} annotation to indicate the valid * types that may be passed as a format parameter. For example: * - *
    - * - *
    {@literal @}I18nFormat({I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER})
    - * String f = "{0}{1, number}";
    + * 
    {@literal @}I18nFormat({GENERAL, NUMBER}) String f = "{0}{1, number}";
      * MessageFormat.format(f, "Example", 0) // valid
    * - *
    - * * The annotation indicates that the format string requires any object as the first parameter * ({@link I18nConversionCategory#GENERAL}) and a number as the second parameter ({@link * I18nConversionCategory#NUMBER}). * * @checker_framework.manual #i18n-formatter-checker Internationalization Format String Checker */ +@AnnotatedFor("nullness") public enum I18nConversionCategory { /** @@ -54,12 +52,12 @@ public enum I18nConversionCategory { NUMBER(new Class[] {Number.class}, new String[] {"number", "choice"}); @SuppressWarnings("ImmutableEnumChecker") // TODO: clean this up! - public final Class[] types; + public final Class @Nullable [] types; @SuppressWarnings("ImmutableEnumChecker") // TODO: clean this up! - public final String[] strings; + public final String @Nullable [] strings; - I18nConversionCategory(Class[] types, String[] strings) { + I18nConversionCategory(Class @Nullable [] types, String @Nullable [] strings) { this.types = types; this.strings = strings; } @@ -76,6 +74,8 @@ public enum I18nConversionCategory { * * @return the I18nConversionCategory associated with the given string */ + @SuppressWarnings( + "nullness:iterating.over.nullable") // in namedCategories, `strings` field is non-null public static I18nConversionCategory stringToI18nConversionCategory(String string) { string = string.toLowerCase(); for (I18nConversionCategory v : namedCategories) { @@ -127,11 +127,20 @@ public static I18nConversionCategory intersect( return a; } - Set> as = arrayToSet(a.types); - Set> bs = arrayToSet(b.types); + @SuppressWarnings( + "nullness:argument.type.incompatible") // types field is only null in UNUSED and + // GENERAL + Set> as = arrayToSet(a.types); + @SuppressWarnings( + "nullness:argument.type.incompatible") // types field is only null in UNUSED and + // GENERAL + Set> bs = arrayToSet(b.types); as.retainAll(bs); // intersection for (I18nConversionCategory v : new I18nConversionCategory[] {DATE, NUMBER}) { - Set> vs = arrayToSet(v.types); + @SuppressWarnings( + "nullness:argument.type.incompatible") // in those values, `types` field is + // non-null + Set> vs = arrayToSet(v.types); if (vs.equals(as)) { return v; } @@ -168,7 +177,7 @@ public String toString() { } else { sb.append(" conversion category (one of: "); boolean first = true; - for (Class cls : this.types) { + for (Class cls : this.types) { if (!first) { sb.append(", "); } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessUtil.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessUtil.java index d971c9a197d1..127a55e80bc3 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessUtil.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessUtil.java @@ -3,6 +3,7 @@ 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. @@ -23,6 +24,7 @@ "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() { diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/Opt.java b/checker/src/main/java/org/checkerframework/checker/nullness/Opt.java index 57aa485404f8..36a93442c0cf 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/Opt.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/Opt.java @@ -8,6 +8,7 @@ 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 @@ -27,6 +28,7 @@ * * @see java.util.Optional */ +@AnnotatedFor("nullness") public final class Opt { /** The Opt class cannot be instantiated. */ diff --git a/checker/src/main/java/org/checkerframework/checker/regex/RegexUtil.java b/checker/src/main/java/org/checkerframework/checker/regex/RegexUtil.java index cd0fcf407a57..157f2cf520b0 100644 --- a/checker/src/main/java/org/checkerframework/checker/regex/RegexUtil.java +++ b/checker/src/main/java/org/checkerframework/checker/regex/RegexUtil.java @@ -10,6 +10,7 @@ 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; /** @@ -25,6 +26,7 @@ * project. */ @SuppressWarnings("allcheckers:purity") +@AnnotatedFor("nullness") public final class RegexUtil { /** This class is a collection of methods; it does not represent anything. */ diff --git a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessUtil.java b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessUtil.java index 6a846d2f4c1c..948ab11dcbd5 100644 --- a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessUtil.java +++ b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessUtil.java @@ -6,6 +6,7 @@ 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 @@ -15,6 +16,7 @@ * * @checker_framework.manual #signedness-utilities Utility routines for manipulating unsigned values */ +@AnnotatedFor("nullness") public final class SignednessUtil { private SignednessUtil() { diff --git a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessUtilExtra.java b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessUtilExtra.java index 464e37c50ae6..6b38b1d52028 100644 --- a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessUtilExtra.java +++ b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessUtilExtra.java @@ -3,6 +3,7 @@ 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 @@ -10,7 +11,9 @@ * * @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"); } diff --git a/checker/src/main/java/org/checkerframework/checker/units/UnitsTools.java b/checker/src/main/java/org/checkerframework/checker/units/UnitsTools.java index 2559f5cf9238..56f128df42c0 100644 --- a/checker/src/main/java/org/checkerframework/checker/units/UnitsTools.java +++ b/checker/src/main/java/org/checkerframework/checker/units/UnitsTools.java @@ -21,10 +21,13 @@ 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"}) -// TODO: add fromTo methods for all useful unit combinations. +@AnnotatedFor("nullness") public class UnitsTools { // Acceleration public static final @mPERs2 int mPERs2 = 1; diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/JavaDiagnosticReader.java b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/JavaDiagnosticReader.java index 2dd8b1573b77..9a9746bb74d0 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/JavaDiagnosticReader.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/JavaDiagnosticReader.java @@ -205,7 +205,8 @@ private void init() throws IOException { } @Override - @SuppressWarnings("contracts.postcondition.not.satisfied") // if closed, reader is non-null + @SuppressWarnings( + "nullness:contracts.postcondition.not.satisfied") // if closed, reader is non-null @EnsuresNonNull("reader") public boolean hasNext() { if (closed) { diff --git a/framework/src/main/java/org/checkerframework/framework/source/DiagMessage.java b/framework/src/main/java/org/checkerframework/framework/source/DiagMessage.java index 8f8778421fe5..5080bc7b9889 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/DiagMessage.java +++ b/framework/src/main/java/org/checkerframework/framework/source/DiagMessage.java @@ -9,6 +9,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.framework.qual.AnnotatedFor; /** * A {@code DiagMessage} is a kind, a message key, and arguments. The message key will be expanded @@ -16,6 +17,7 @@ * *

    By contrast, {@code javax.tools.Diagnostic} has just a string message. */ +@AnnotatedFor("nullness") public class DiagMessage { /** The kind of message. */ private final Kind kind; @@ -31,6 +33,10 @@ public class DiagMessage { * @param messageKey the message key * @param args the arguments that will be interpolated into the localized message */ + @SuppressWarnings({ + "nullness:assignment.type.incompatible", // this call to Arrays.copyOf is polymorphic + "nullness:argument.type.incompatible" // https://tinyurl.com/cfissue/3448 + }) public DiagMessage(Kind kind, @CompilerMessageKey String messageKey, Object... args) { this.kind = kind; this.messageKey = messageKey; From 80ebb1ec7c14ccb55b37ee667241b71bd4e19ddd Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Fri, 10 Jul 2020 10:52:15 -0700 Subject: [PATCH 044/138] Re-enable Javadoc tests --- checker/bin-devel/test-misc.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/checker/bin-devel/test-misc.sh b/checker/bin-devel/test-misc.sh index 3d108b07f59d..d11c16e3af8b 100755 --- a/checker/bin-devel/test-misc.sh +++ b/checker/bin-devel/test-misc.sh @@ -24,7 +24,7 @@ source "$SCRIPTDIR"/build.sh # Javadoc documentation # Uncomment this line temporarily for refactorings that touch a lot of code that # you don't understand. Then, recomment it as soon as the pull request is merged. -SKIPJAVADOC=1 +# SKIPJAVADOC=1 if [ -z "$SKIPJAVADOC" ]; then status=0 ./gradlew javadoc --console=plain --warning-mode=all --no-daemon || status=1 From ea0d4935596e5e077da203b286c760c1ff774fd5 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Fri, 10 Jul 2020 11:33:06 -0700 Subject: [PATCH 045/138] Change "locally-modified" to "locally-built" --- docs/manual/external-tools.tex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/manual/external-tools.tex b/docs/manual/external-tools.tex index d243e86089cd..f487b6c19c47 100644 --- a/docs/manual/external-tools.tex +++ b/docs/manual/external-tools.tex @@ -998,9 +998,9 @@ \end{enumerate} -\subsectionAndLabel{Maven, with a locally-modified version of the Checker Framework}{maven-local-modifications} +\subsectionAndLabel{Maven, with a locally-built version of the Checker Framework}{maven-locally-built} -To use a locally-modified version of the Checker Framework, first run: +To use a locally-built version of the Checker Framework, first run: \begin{alltt} ./gradlew deployArtifactsToLocalRepo From 1280e8299b4e2446df3dacf40c2506757986792f Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 11 Jul 2020 21:07:15 -0700 Subject: [PATCH 046/138] Documentation and minor refactoring --- .../i18nformatter/I18nFormatterTreeUtil.java | 11 +++- .../i18nformatter/I18nFormatterVisitor.java | 6 +- .../common/basetype/BaseTypeVisitor.java | 59 ++++++++++--------- .../aliasing/AliasingConstructorTest.java | 23 ++++++++ framework/tests/aliasing/ConstructorTest.java | 23 -------- ...st.java => ReflectionConstructorTest.java} | 16 ++--- 6 files changed, 72 insertions(+), 66 deletions(-) create mode 100644 framework/tests/aliasing/AliasingConstructorTest.java delete mode 100644 framework/tests/aliasing/ConstructorTest.java rename framework/tests/reflection/{ConstructorTest.java => ReflectionConstructorTest.java} (79%) diff --git a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTreeUtil.java b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTreeUtil.java index 38d156f455cc..50f19dab46ab 100644 --- a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTreeUtil.java +++ b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTreeUtil.java @@ -236,14 +236,19 @@ public I18nFormatCall createFormatForCall( /** * Represents a format method invocation in the syntax tree. * - *

    An I18nFormatCall instance can only be instantiated by createFormatForCall method + *

    An I18nFormatCall instance can only be instantiated by the createFormatForCall method. */ public class I18nFormatCall { - private final ExpressionTree tree; + /** The AST node for the call. */ + private final MethodInvocationTree tree; + /** The format string argument. */ private ExpressionTree formatArg; + /** The type factory. */ private final AnnotatedTypeFactory atypeFactory; + /** The arguments to the format string. */ private List args; + /** Extra description for error messages. */ private String invalidMessage; private AnnotatedTypeMirror formatAnno; @@ -420,7 +425,7 @@ public InvocationType visitNull(NullType t, Class p) { } ExpressionTree loc; - loc = ((MethodInvocationTree) tree).getMethodSelect(); + loc = tree.getMethodSelect(); if (type != InvocationType.VARARG && !args.isEmpty()) { loc = args.get(0); } diff --git a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterVisitor.java b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterVisitor.java index 125cfb266b82..08b4b5ca48e8 100644 --- a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterVisitor.java @@ -47,8 +47,6 @@ private void checkInvocationFormatFor(I18nFormatCall fc) { I18nFormatterTreeUtil tu = atypeFactory.treeUtil; Result type = fc.getFormatType(); - Result invc; - I18nConversionCategory[] formatCats; switch (type.value()) { case I18NINVALID: tu.failure(type, "i18nformat.string.invalid", fc.getInvalidError()); @@ -60,8 +58,8 @@ private void checkInvocationFormatFor(I18nFormatCall fc) { } break; case I18NFORMAT: - invc = fc.getInvocationType(); - formatCats = fc.getFormatCategories(); + Result invc = fc.getInvocationType(); + I18nConversionCategory[] formatCats = fc.getFormatCategories(); switch (invc.value()) { case VARARG: Result[] paramTypes = fc.getParamTypes(); 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 60344db687dd..d4a18aeb83c1 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -47,7 +47,6 @@ import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -1405,9 +1404,11 @@ public Void visitMethodInvocation(MethodInvocationTree node, Void p) { * Checks that the following rule is satisfied: The type on a constructor declaration must be a * supertype of the return type of "this()" invocation within that constructor. * - *

    Subclass can override this method to change the behavior for just "this" constructor - * class. Or {@link #checkThisOrSuperConstructorCall(MethodInvocationTree, String)} to change - * the behavior for "this" and "super" constructor calls. + *

    Subclasses can override this method to change the behavior for just "this" constructor + * class. Or override {@link #checkThisOrSuperConstructorCall(MethodInvocationTree, String)} to + * change the behavior for "this" and "super" constructor calls. + * + * @param thisCall the AST node for the constructor call */ protected void checkThisConstructorCall(MethodInvocationTree thisCall) { checkThisOrSuperConstructorCall(thisCall, "this.invocation.invalid"); @@ -1417,9 +1418,11 @@ protected void checkThisConstructorCall(MethodInvocationTree thisCall) { * Checks that the following rule is satisfied: The type on a constructor declaration must be a * supertype of the return type of "super()" invocation within that constructor. * - *

    Subclass can override this method to change the behavior for just "super" constructor - * class. Or {@link #checkThisOrSuperConstructorCall(MethodInvocationTree, String)} to change - * the behavior for "this" and "super" constructor calls. + *

    Subclasses can override this method to change the behavior for just "super" constructor + * class. Or override {@link #checkThisOrSuperConstructorCall(MethodInvocationTree, String)} to + * change the behavior for "this" and "super" constructor calls. + * + * @param superCall the AST node for the super constructor call */ protected void checkSuperConstructorCall(MethodInvocationTree superCall) { checkThisOrSuperConstructorCall(superCall, "super.invocation.invalid"); @@ -1428,12 +1431,15 @@ protected void checkSuperConstructorCall(MethodInvocationTree superCall) { /** * Checks that the following rule is satisfied: The type on a constructor declaration must be a * supertype of the return type of "this()" or "super()" invocation within that constructor. + * + * @param call the AST node for the constructor call + * @param errorKey the error message key to use if the check fails */ protected void checkThisOrSuperConstructorCall( - MethodInvocationTree superCall, @CompilerMessageKey String errorKey) { - TreePath path = atypeFactory.getPath(superCall); + MethodInvocationTree call, @CompilerMessageKey String errorKey) { + TreePath path = atypeFactory.getPath(call); MethodTree enclosingMethod = TreeUtils.enclosingMethod(path); - AnnotatedTypeMirror superType = atypeFactory.getAnnotatedType(superCall); + AnnotatedTypeMirror superType = atypeFactory.getAnnotatedType(call); AnnotatedExecutableType constructorType = atypeFactory.getAnnotatedType(enclosingMethod); Set topAnnotations = atypeFactory.getQualifierHierarchy().getTopAnnotations(); @@ -1445,8 +1451,7 @@ protected void checkThisOrSuperConstructorCall( if (!atypeFactory .getQualifierHierarchy() .isSubtype(superTypeMirror, constructorTypeMirror)) { - checker.reportError( - superCall, errorKey, constructorTypeMirror, superCall, superTypeMirror); + checker.reportError(call, errorKey, constructorTypeMirror, call, superTypeMirror); } } } @@ -2339,8 +2344,7 @@ protected Set getThrowUpperBoundAnnotations() { * * @param varTree the AST node for the lvalue (usually a variable) * @param valueExp the AST node for the rvalue (the new value) - * @param errorKey the error message to use if the check fails (must be a compiler message key, - * see {@link org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey}) + * @param errorKey the error message key to use if the check fails */ protected void commonAssignmentCheck( Tree varTree, ExpressionTree valueExp, @CompilerMessageKey String errorKey) { @@ -2360,8 +2364,7 @@ protected void commonAssignmentCheck( * * @param varType the annotated type of the lvalue (usually a variable) * @param valueExp the AST node for the rvalue (the new value) - * @param errorKey the error message to use if the check fails (must be a compiler message key, - * see {@link org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey}) + * @param errorKey the error message key to use if the check fails */ protected void commonAssignmentCheck( AnnotatedTypeMirror varType, @@ -2484,8 +2487,7 @@ protected final void commonAssignmentCheckEndDiagnostic( * @param varType the annotated type of the variable * @param valueType the annotated type of the value * @param valueTree the location to use when reporting the error message - * @param errorKey the error message to use if the check fails (must be a compiler message key, - * see {@link org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey}) + * @param errorKey the error message key to use if the check fails */ protected void commonAssignmentCheck( AnnotatedTypeMirror varType, @@ -2679,19 +2681,17 @@ protected void checkTypeArguments( return; } - assert paramBounds.size() == typeargs.size() + int size = paramBounds.size(); + assert size == typeargs.size() : "BaseTypeVisitor.checkTypeArguments: mismatch between type arguments: " + typeargs + " and type parameter bounds" + paramBounds; - Iterator boundsIter = paramBounds.iterator(); - Iterator argIter = typeargs.iterator(); - - while (boundsIter.hasNext()) { + for (int i = 0; i < size; i++) { - AnnotatedTypeParameterBounds bounds = boundsIter.next(); - AnnotatedTypeMirror typeArg = argIter.next(); + AnnotatedTypeParameterBounds bounds = paramBounds.get(i); + AnnotatedTypeMirror typeArg = typeargs.get(i); if (isIgnoredUninferredWildcard(bounds.getUpperBound()) || isIgnoredUninferredWildcard(typeArg)) { @@ -2905,13 +2905,16 @@ protected void checkConstructorInvocation( * like var args. * * @see #checkVarargs(AnnotatedTypeMirror.AnnotatedExecutableType, Tree) - * @param requiredArgs the required types + * @param requiredArgs the required types. This may differ from the formal parameter types, + * because it replaces a varargs parameter by multiple parameters with the vararg's element + * type. * @param passedArgs the expressions passed to the corresponding types */ protected void checkArguments( List requiredArgs, List passedArgs) { - assert requiredArgs.size() == passedArgs.size() + int size = requiredArgs.size(); + assert size == passedArgs.size() : "mismatch between required args (" + requiredArgs + ") and passed args (" @@ -2920,7 +2923,7 @@ protected void checkArguments( Pair preAssCtxt = visitorState.getAssignmentContext(); try { - for (int i = 0; i < requiredArgs.size(); ++i) { + for (int i = 0; i < size; ++i) { visitorState.setAssignmentContext( Pair.of((Tree) null, (AnnotatedTypeMirror) requiredArgs.get(i))); commonAssignmentCheck( diff --git a/framework/tests/aliasing/AliasingConstructorTest.java b/framework/tests/aliasing/AliasingConstructorTest.java new file mode 100644 index 000000000000..a8a0e7b29b3f --- /dev/null +++ b/framework/tests/aliasing/AliasingConstructorTest.java @@ -0,0 +1,23 @@ +import org.checkerframework.common.aliasing.qual.*; + +public class AliasingConstructorTest { + + public AliasingConstructorTest(@NonLeaked Object o) {} + + // int and String parameters on the constructors below are used only + // to make a distinction among constructors. + public AliasingConstructorTest(@LeakedToResult Object o, int i) {} + + public AliasingConstructorTest(Object o, String s) {} + + public void annosInAliasingConstructorTest() { + @Unique Object o = new Object(); + new AliasingConstructorTest(o); + Object o2 = new Object(); + new AliasingConstructorTest(o2, 1); + AliasingConstructorTest ct = new AliasingConstructorTest(o2, 1); + @Unique Object o3 = new Object(); + // ::error: (unique.leaked) + new AliasingConstructorTest(o3, "someString"); + } +} diff --git a/framework/tests/aliasing/ConstructorTest.java b/framework/tests/aliasing/ConstructorTest.java deleted file mode 100644 index 1a0fdb1c0e39..000000000000 --- a/framework/tests/aliasing/ConstructorTest.java +++ /dev/null @@ -1,23 +0,0 @@ -import org.checkerframework.common.aliasing.qual.*; - -public class ConstructorTest { - - public ConstructorTest(@NonLeaked Object o) {} - - // int and String parameters on the constructors below are used only - // to make a distinction among constructors. - public ConstructorTest(@LeakedToResult Object o, int i) {} - - public ConstructorTest(Object o, String s) {} - - public void annosInConstructorTest() { - @Unique Object o = new Object(); - new ConstructorTest(o); - Object o2 = new Object(); - new ConstructorTest(o2, 1); - ConstructorTest ct = new ConstructorTest(o2, 1); - @Unique Object o3 = new Object(); - // ::error: (unique.leaked) - new ConstructorTest(o3, "someString"); - } -} diff --git a/framework/tests/reflection/ConstructorTest.java b/framework/tests/reflection/ReflectionConstructorTest.java similarity index 79% rename from framework/tests/reflection/ConstructorTest.java rename to framework/tests/reflection/ReflectionConstructorTest.java index 29900801fdb3..0cfd8499a3a5 100644 --- a/framework/tests/reflection/ConstructorTest.java +++ b/framework/tests/reflection/ReflectionConstructorTest.java @@ -3,19 +3,19 @@ import testlib.reflection.qual.Sibling2; import testlib.reflection.qual.Top; -class ConstructorTest { +class ReflectionConstructorTest { @Sibling1 int sibling1; @Sibling2 int sibling2; // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - public @Sibling1 ConstructorTest(@Sibling1 int a) {} + public @Sibling1 ReflectionConstructorTest(@Sibling1 int a) {} // :: error: (super.invocation.invalid) :: warning: (inconsistent.constructor.type) - public @Sibling2 ConstructorTest(@Sibling2 int a, @Sibling2 int b) {} + public @Sibling2 ReflectionConstructorTest(@Sibling2 int a, @Sibling2 int b) {} public void pass1() { try { - Class c = Class.forName("ConstructorTest"); + Class c = Class.forName("ReflectionConstructorTest"); Constructor init = c.getConstructor(new Class[] {Integer.class}); @Sibling1 int i = sibling1; @Sibling1 Object o = init.newInstance(i); @@ -25,7 +25,7 @@ public void pass1() { public void pass2() { try { - Class c = Class.forName("ConstructorTest"); + Class c = Class.forName("ReflectionConstructorTest"); Constructor init = c.getConstructor(new Class[] {Integer.class, Integer.class}); @Sibling2 int a = sibling2; int b = a; @@ -36,7 +36,7 @@ public void pass2() { public void fail1() { try { - Class c = ConstructorTest.class; + Class c = ReflectionConstructorTest.class; Constructor init = c.getConstructor(new Class[] {Integer.class}); // :: error: (argument.type.incompatible) Object o = init.newInstance(sibling2); @@ -46,7 +46,7 @@ public void fail1() { public void fail2() { try { - Class c = ConstructorTest.class; + Class c = ReflectionConstructorTest.class; Constructor init = c.getConstructor(new Class[] {Integer.class}); // :: error: (assignment.type.incompatible) @Sibling1 Object o = init.newInstance(new Object[] {sibling2}); @@ -56,7 +56,7 @@ public void fail2() { public void fail3() { try { - Class c = Class.forName("ConstructorTest"); + Class c = Class.forName("ReflectionConstructorTest"); Constructor init = c.getConstructor(new Class[] {Integer.class, Integer.class}); @Sibling2 int a = sibling2; @Sibling1 int b = sibling1; From 70e3761951fdd4ba7aca4b09dafdaa7b990aef33 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 11 Jul 2020 21:08:20 -0700 Subject: [PATCH 047/138] Add dependencies to Azure Pipelines jobs --- azure-pipelines.yml | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4101d78127ba..d986c10ae187 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -11,6 +11,9 @@ pr: jobs: - job: all_tests_jdk8 + dependsOn: + - all_tests_jdk11 + - misc_jdk11 pool: vmImage: 'ubuntu-latest' container: mdernst/cf-ubuntu-jdk8:latest @@ -29,6 +32,9 @@ jobs: - bash: ./checker/bin-devel/test-cftests-all.sh displayName: test-cftests-all.sh - job: misc_jdk8 + dependsOn: + - all_tests_jdk11 + - misc_jdk11 pool: vmImage: 'ubuntu-latest' container: mdernst/cf-ubuntu-jdk8-plus:latest @@ -47,6 +53,10 @@ jobs: - bash: ./checker/bin-devel/test-misc.sh displayName: test-misc.sh - job: cf_inference_jdk8 + dependsOn: + - all_tests_jdk11 + - misc_jdk11 + - cf_inference_jdk11 pool: vmImage: 'ubuntu-latest' container: mdernst/cf-ubuntu-jdk8:latest @@ -65,6 +75,10 @@ jobs: - bash: ./checker/bin-devel/test-cf-inference.sh displayName: test-cf-inference.sh - job: daikon_jdk8 + dependsOn: + - all_tests_jdk11 + - misc_jdk11 + - daikon_jdk11 pool: vmImage: 'ubuntu-latest' container: mdernst/cf-ubuntu-jdk8:latest @@ -83,6 +97,10 @@ jobs: - bash: ./checker/bin-devel/test-daikon.sh displayName: test-daikon.sh - job: guava_jdk8 + dependsOn: + - all_tests_jdk11 + - misc_jdk11 + - guava_jdk11 pool: vmImage: 'ubuntu-latest' container: mdernst/cf-ubuntu-jdk8:latest @@ -103,6 +121,10 @@ jobs: - bash: ./checker/bin-devel/test-guava.sh displayName: test-guava.sh - job: plume_lib_jdk8 + dependsOn: + - all_tests_jdk11 + - misc_jdk11 + - plume_lib_jdk11 pool: vmImage: 'ubuntu-latest' container: mdernst/cf-ubuntu-jdk8:latest @@ -120,19 +142,23 @@ jobs: fetchDepth: 25 - bash: ./checker/bin-devel/test-plume-lib.sh displayName: test-plume-lib.sh -- job: downstream_jdk11 +- job: downstream_jdk8 + dependsOn: + - all_tests_jdk11 + - misc_jdk11 + - downstream_jdk11 pool: vmImage: 'ubuntu-latest' - container: mdernst/cf-ubuntu-jdk11:latest + container: mdernst/cf-ubuntu-jdk8:latest steps: - checkout: self fetchDepth: 25 - bash: ./checker/bin-devel/test-downstream.sh displayName: test-downstream.sh -- job: downstream_jdk8 +- job: downstream_jdk11 pool: vmImage: 'ubuntu-latest' - container: mdernst/cf-ubuntu-jdk8:latest + container: mdernst/cf-ubuntu-jdk11:latest steps: - checkout: self fetchDepth: 25 From 674c402c02648bbb7133c5499357de1380b58052 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 13 Jul 2020 14:04:43 -0700 Subject: [PATCH 048/138] Add more `@AnnotatedFor` --- .../org/checkerframework/checker/formatter/FormatUtil.java | 3 ++- .../checkerframework/checker/formatter/FormatterTreeUtil.java | 2 ++ .../checker/formatter/qual/ConversionCategory.java | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/checker/src/main/java/org/checkerframework/checker/formatter/FormatUtil.java b/checker/src/main/java/org/checkerframework/checker/formatter/FormatUtil.java index 7ac851930795..525fadf86436 100644 --- a/checker/src/main/java/org/checkerframework/checker/formatter/FormatUtil.java +++ b/checker/src/main/java/org/checkerframework/checker/formatter/FormatUtil.java @@ -11,9 +11,10 @@ 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. */ -// TODO @AnnotatedFor("nullness") +@AnnotatedFor("nullness") public class FormatUtil { /** diff --git a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterTreeUtil.java b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterTreeUtil.java index fe6b1da30845..5bd7937deb83 100644 --- a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterTreeUtil.java +++ b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterTreeUtil.java @@ -351,6 +351,8 @@ public Boolean visitNull(NullType t, Class p) { } } + // The failure() method is required so that FormatterTransfer, which has no access to the + // FormatterChecker, can report errors. /** * Reports an error. * diff --git a/checker/src/main/java/org/checkerframework/checker/formatter/qual/ConversionCategory.java b/checker/src/main/java/org/checkerframework/checker/formatter/qual/ConversionCategory.java index 2add5f243b4f..e64c5f3a8015 100644 --- a/checker/src/main/java/org/checkerframework/checker/formatter/qual/ConversionCategory.java +++ b/checker/src/main/java/org/checkerframework/checker/formatter/qual/ConversionCategory.java @@ -9,6 +9,7 @@ import java.util.Set; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.qual.Pure; +import org.checkerframework.framework.qual.AnnotatedFor; /** * Elements of this enumeration are used in a {@link Format Format} annotation to indicate the valid @@ -29,6 +30,7 @@ * @see Format * @checker_framework.manual #formatter-checker Format String Checker */ +@AnnotatedFor("nullness") public enum ConversionCategory { /** Use if the parameter can be of any type. Applicable for conversions b, B, h, H, s, S. */ GENERAL(null /* everything */, "bBhHsS"), From f87607b43f7fe60377e71e6f92e6971cef4fdb59 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 13 Jul 2020 15:50:18 -0700 Subject: [PATCH 049/138] Reduce latency by breaking all_tests into 2 jobs --- azure-pipelines.yml | 53 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index d986c10ae187..bd14612f6b1a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -10,9 +10,9 @@ pr: jobs: -- job: all_tests_jdk8 +- job: junit_tests_jdk8 dependsOn: - - all_tests_jdk11 + - junit_tests_jdk11 - misc_jdk11 pool: vmImage: 'ubuntu-latest' @@ -20,20 +20,42 @@ jobs: steps: - checkout: self fetchDepth: 25 - - bash: ./checker/bin-devel/test-cftests-all.sh - displayName: test-cftests-all.sh -- job: all_tests_jdk11 + - bash: ./checker/bin-devel/test-cftests-junit.sh + displayName: test-cftests-junit.sh +- job: junit_tests_jdk11 pool: vmImage: 'ubuntu-latest' container: mdernst/cf-ubuntu-jdk11:latest steps: - checkout: self fetchDepth: 25 - - bash: ./checker/bin-devel/test-cftests-all.sh - displayName: test-cftests-all.sh + - bash: ./checker/bin-devel/test-cftests-junit.sh + displayName: test-cftests-junit.sh +- job: nonjunit_tests_jdk8 + dependsOn: + - nonjunit_tests_jdk11 + - misc_jdk11 + pool: + vmImage: 'ubuntu-latest' + container: mdernst/cf-ubuntu-jdk8:latest + steps: + - checkout: self + fetchDepth: 25 + - bash: ./checker/bin-devel/test-cftests-nonjunit.sh + displayName: test-cftests-nonjunit.sh +- job: nonjunit_tests_jdk11 + pool: + vmImage: 'ubuntu-latest' + container: mdernst/cf-ubuntu-jdk11:latest + steps: + - checkout: self + fetchDepth: 25 + - bash: ./checker/bin-devel/test-cftests-nonjunit.sh + displayName: test-cftests-nonjunit.sh - job: misc_jdk8 dependsOn: - - all_tests_jdk11 + - junit_tests_jdk11 + - nonjunit_tests_jdk11 - misc_jdk11 pool: vmImage: 'ubuntu-latest' @@ -54,7 +76,8 @@ jobs: displayName: test-misc.sh - job: cf_inference_jdk8 dependsOn: - - all_tests_jdk11 + - junit_tests_jdk11 + - nonjunit_tests_jdk11 - misc_jdk11 - cf_inference_jdk11 pool: @@ -76,7 +99,8 @@ jobs: displayName: test-cf-inference.sh - job: daikon_jdk8 dependsOn: - - all_tests_jdk11 + - junit_tests_jdk11 + - nonjunit_tests_jdk11 - misc_jdk11 - daikon_jdk11 pool: @@ -98,7 +122,8 @@ jobs: displayName: test-daikon.sh - job: guava_jdk8 dependsOn: - - all_tests_jdk11 + - junit_tests_jdk11 + - nonjunit_tests_jdk11 - misc_jdk11 - guava_jdk11 pool: @@ -122,7 +147,8 @@ jobs: displayName: test-guava.sh - job: plume_lib_jdk8 dependsOn: - - all_tests_jdk11 + - junit_tests_jdk11 + - nonjunit_tests_jdk11 - misc_jdk11 - plume_lib_jdk11 pool: @@ -144,7 +170,8 @@ jobs: displayName: test-plume-lib.sh - job: downstream_jdk8 dependsOn: - - all_tests_jdk11 + - junit_tests_jdk11 + - nonjunit_tests_jdk11 - misc_jdk11 - downstream_jdk11 pool: From 0b5638c7edc3f6b377cbffb2d237116df909c563 Mon Sep 17 00:00:00 2001 From: Martin Kellogg Date: Tue, 14 Jul 2020 07:07:38 -0700 Subject: [PATCH 050/138] Collect info from store in Accumulation Checker --- .../accumulation/AccumulationTransfer.java | 28 +++++++++++-------- .../tests/accumulation/SimpleInference.java | 18 ++++++++++++ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationTransfer.java b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationTransfer.java index 9ba36e587be4..4e22e6f1634d 100644 --- a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationTransfer.java +++ b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationTransfer.java @@ -13,6 +13,7 @@ import org.checkerframework.dataflow.analysis.TransferResult; import org.checkerframework.dataflow.cfg.node.MethodInvocationNode; import org.checkerframework.dataflow.cfg.node.Node; +import org.checkerframework.framework.flow.CFAbstractStore; import org.checkerframework.framework.flow.CFAnalysis; import org.checkerframework.framework.flow.CFStore; import org.checkerframework.framework.flow.CFTransfer; @@ -75,18 +76,21 @@ public void accumulate(Node node, TransferResult result, Strin List valuesAsList = Arrays.asList(values); // If dataflow has already recorded information about the target, fetch it and integrate // it into the list of values in the new annotation. - CFValue flowValue = result.getResultValue(); - if (flowValue != null) { - Set flowAnnos = flowValue.getAnnotations(); - assert flowAnnos.size() <= 1; - for (AnnotationMirror anno : flowAnnos) { - List oldFlowValues = - ValueCheckerUtils.getValueOfAnnotationWithStringArgument(anno); - if (oldFlowValues != null) { - // valuesAsList cannot have its length changed -- it is backed by an array. - // getValueOfAnnotationWithStringArgument returns a new, modifiable list. - oldFlowValues.addAll(valuesAsList); - valuesAsList = oldFlowValues; + Receiver target = FlowExpressions.internalReprOf(typeFactory, node); + if (CFAbstractStore.canInsertReceiver(target)) { + CFValue flowValue = result.getRegularStore().getValue(target); + if (flowValue != null) { + Set flowAnnos = flowValue.getAnnotations(); + assert flowAnnos.size() <= 1; + for (AnnotationMirror anno : flowAnnos) { + List oldFlowValues = + ValueCheckerUtils.getValueOfAnnotationWithStringArgument(anno); + if (oldFlowValues != null) { + // valuesAsList cannot have its length changed -- it is backed by an array. + // getValueOfAnnotationWithStringArgument returns a new, modifiable list. + oldFlowValues.addAll(valuesAsList); + valuesAsList = oldFlowValues; + } } } } diff --git a/framework/tests/accumulation/SimpleInference.java b/framework/tests/accumulation/SimpleInference.java index cb4f9bf5aa24..7e6600c0a7b4 100644 --- a/framework/tests/accumulation/SimpleInference.java +++ b/framework/tests/accumulation/SimpleInference.java @@ -3,17 +3,35 @@ class SimpleInference { void build(@TestAccumulation({"a"}) SimpleInference this) {} + void doublebuild(@TestAccumulation({"a", "b"}) SimpleInference this) {} + void a() {} + void b() {} + static void doStuffCorrect() { SimpleInference s = new SimpleInference(); s.a(); s.build(); } + static void doStuffCorrect2() { + SimpleInference s = new SimpleInference(); + s.a(); + s.b(); + s.doublebuild(); + } + static void doStuffWrong() { SimpleInference s = new SimpleInference(); // :: error: method.invocation.invalid s.build(); } + + static void doStuffWrong2() { + SimpleInference s = new SimpleInference(); + s.a(); + // :: error: method.invocation.invalid + s.doublebuild(); + } } From 661592c85a154acdcdd566b4cb5ca63e59d9c97b Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 14 Jul 2020 20:56:15 -0700 Subject: [PATCH 051/138] Default char and Character to `@Unsigned` --- .../checker/signedness/qual/Signed.java | 6 +-- .../checker/signedness/qual/Unsigned.java | 5 ++ .../tests/signedness/DefaultsSignedness.java | 9 ---- .../tests/signedness/ValueIntegration.java | 46 +++++++++++-------- 4 files changed, 33 insertions(+), 33 deletions(-) diff --git a/checker/src/main/java/org/checkerframework/checker/signedness/qual/Signed.java b/checker/src/main/java/org/checkerframework/checker/signedness/qual/Signed.java index f7195b4a7f7c..bf4270952661 100644 --- a/checker/src/main/java/org/checkerframework/checker/signedness/qual/Signed.java +++ b/checker/src/main/java/org/checkerframework/checker/signedness/qual/Signed.java @@ -26,8 +26,7 @@ TypeKind.LONG, TypeKind.SHORT, TypeKind.FLOAT, - TypeKind.DOUBLE, - TypeKind.CHAR + TypeKind.DOUBLE }, types = { java.lang.Byte.class, @@ -35,7 +34,6 @@ java.lang.Long.class, java.lang.Short.class, java.lang.Float.class, - java.lang.Double.class, - java.lang.Character.class + java.lang.Double.class }) public @interface Signed {} diff --git a/checker/src/main/java/org/checkerframework/checker/signedness/qual/Unsigned.java b/checker/src/main/java/org/checkerframework/checker/signedness/qual/Unsigned.java index be27a217a1a3..fac6a9d7c926 100644 --- a/checker/src/main/java/org/checkerframework/checker/signedness/qual/Unsigned.java +++ b/checker/src/main/java/org/checkerframework/checker/signedness/qual/Unsigned.java @@ -5,7 +5,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TypeKind; /** * The value is to be interpreted as unsigned. That is, if the most significant bit in the bitwise @@ -18,4 +20,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @SubtypeOf({UnknownSignedness.class}) +@DefaultFor( + typeKinds = {TypeKind.CHAR}, + types = {java.lang.Character.class}) public @interface Unsigned {} diff --git a/checker/tests/signedness/DefaultsSignedness.java b/checker/tests/signedness/DefaultsSignedness.java index 518ac2465ced..338a7dd7e1c9 100644 --- a/checker/tests/signedness/DefaultsSignedness.java +++ b/checker/tests/signedness/DefaultsSignedness.java @@ -128,15 +128,6 @@ public void SignedTest( // :: error: (assignment.type.incompatible) conDouble = testDouble; - // Test chars - @Signed char sinChar; - @SignednessGlb char conChar; - - sinChar = testChar; - - // :: error: (assignment.type.incompatible) - conChar = testChar; - /* // Test boxed bytes @Signed Byte sinBoxedByte; diff --git a/checker/tests/signedness/ValueIntegration.java b/checker/tests/signedness/ValueIntegration.java index 83a194ba8f81..ae4ecc860d71 100644 --- a/checker/tests/signedness/ValueIntegration.java +++ b/checker/tests/signedness/ValueIntegration.java @@ -53,6 +53,8 @@ public void ByteValRules( ptest = bmixed; } + // Character and char are always @Unsigned, never @Signed. + /* public void CharValRules( @IntVal({0, 127}) char c, @IntVal({128, 255}) char upure, @@ -69,35 +71,36 @@ public void CharValRules( ptest = c; stest = upure; - // :: error: (assignment.type.incompatible) + // XX error: (assignment.type.incompatible) gtest = upure; - // :: error: (assignment.type.incompatible) + // XX error: (assignment.type.incompatible) ptest = upure; stest = umixed; - // :: error: (assignment.type.incompatible) + // XX error: (assignment.type.incompatible) gtest = umixed; - // :: error: (assignment.type.incompatible) + // XX error: (assignment.type.incompatible) ptest = umixed; stest = spure; - // :: error: (assignment.type.incompatible) + // XX error: (assignment.type.incompatible) gtest = spure; - // :: error: (assignment.type.incompatible) + // XX error: (assignment.type.incompatible) ptest = spure; stest = smixed; - // :: error: (assignment.type.incompatible) + // XX error: (assignment.type.incompatible) gtest = smixed; - // :: error: (assignment.type.incompatible) + // XX error: (assignment.type.incompatible) ptest = smixed; stest = bmixed; - // :: error: (assignment.type.incompatible) + // XX error: (assignment.type.incompatible) gtest = bmixed; - // :: error: (assignment.type.incompatible) + // XX error: (assignment.type.incompatible) ptest = bmixed; } + */ public void ShortValRules( @IntVal({0, 32767}) short c, @@ -279,6 +282,8 @@ public void ByteRangeRules( ptest = bmixed; } + // Character and char are always @Unsigned, never @Signed. + /* public void CharRangeRules( @IntRange(from = 0, to = 127) char c, @NonNegative char nnc, @@ -305,35 +310,36 @@ public void CharRangeRules( ptest = pc; stest = upure; - // :: error: (assignment.type.incompatible) + // XX error: (assignment.type.incompatible) gtest = upure; - // :: error: (assignment.type.incompatible) + // XX error: (assignment.type.incompatible) ptest = upure; stest = umixed; - // :: error: (assignment.type.incompatible) + // XX error: (assignment.type.incompatible) gtest = umixed; - // :: error: (assignment.type.incompatible) + // XX error: (assignment.type.incompatible) ptest = umixed; stest = spure; - // :: error: (assignment.type.incompatible) + // XX error: (assignment.type.incompatible) gtest = spure; - // :: error: (assignment.type.incompatible) + // XX error: (assignment.type.incompatible) ptest = spure; stest = smixed; - // :: error: (assignment.type.incompatible) + // XX error: (assignment.type.incompatible) gtest = smixed; - // :: error: (assignment.type.incompatible) + // XX error: (assignment.type.incompatible) ptest = smixed; stest = bmixed; - // :: error: (assignment.type.incompatible) + // XX error: (assignment.type.incompatible) gtest = bmixed; - // :: error: (assignment.type.incompatible) + // XX error: (assignment.type.incompatible) ptest = bmixed; } + */ public void ShortRangeRules( @IntRange(from = 0, to = 32767) short c, From a6d05aee3d1f315486868164138b4286b795f4d0 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Wed, 15 Jul 2020 14:10:15 -0700 Subject: [PATCH 052/138] Split long lines --- docs/manual/annotating-libraries.tex | 3 ++- docs/manual/troubleshooting.tex | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/manual/annotating-libraries.tex b/docs/manual/annotating-libraries.tex index 9de38f41b322..5b62270401df 100644 --- a/docs/manual/annotating-libraries.tex +++ b/docs/manual/annotating-libraries.tex @@ -625,7 +625,8 @@ \begin{Verbatim} cd nullness-stub - java -cp $CHECKERFRAMEWORK/checker/dist/checker.jar org.checkerframework.framework.stub.StubGenerator java.lang.String > String.astub + java -cp $CHECKERFRAMEWORK/checker/dist/checker.jar \ + org.checkerframework.framework.stub.StubGenerator java.lang.String > String.astub \end{Verbatim} Supply it with the fully-qualified name of the class for which you wish to diff --git a/docs/manual/troubleshooting.tex b/docs/manual/troubleshooting.tex index fd3d75baa328..1c123ed68f0c 100644 --- a/docs/manual/troubleshooting.tex +++ b/docs/manual/troubleshooting.tex @@ -164,10 +164,12 @@ If you get an error such as \begin{Verbatim} -error: SourceChecker.typeProcess: unexpected Throwable (OutOfMemoryError) while processing ...; message: GC overhead limit exceeded +error: SourceChecker.typeProcess: unexpected Throwable (OutOfMemoryError) while processing ... + ; message: GC overhead limit exceeded \end{Verbatim} \noindent +(all on one line), then either give the JVM more memory when running the Checker Framework, or split your files and methods into smaller ones, or both. From 4e4eda96c778b5f63d4f5c6c2edec75bd6f7774d Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2020 10:42:06 -0700 Subject: [PATCH 053/138] Bump de.undercouch.download from 4.1.0 to 4.1.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 13b42ece07aa..3f48d835a8df 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { // https://plugins.gradle.org/plugin/com.github.johnrengelman.shadow (v5 requires Gradle 5) id 'com.github.johnrengelman.shadow' version '6.0.0' // https://plugins.gradle.org/plugin/de.undercouch.download - id "de.undercouch.download" version "4.1.0" + id "de.undercouch.download" version "4.1.1" id 'java' // https://github.com/tbroyer/gradle-errorprone-plugin id "net.ltgt.errorprone" version "1.2.1" From a263ef5c952fb8cbfa0bfdd6edf04edc18524eac Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 16 Jul 2020 15:35:28 -0700 Subject: [PATCH 054/138] Rename file to avoid multiple test cases with same name --- ...poundAssignments.java => CompoundAssignmentsSignedness.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename checker/tests/signedness/{CompoundAssignments.java => CompoundAssignmentsSignedness.java} (99%) diff --git a/checker/tests/signedness/CompoundAssignments.java b/checker/tests/signedness/CompoundAssignmentsSignedness.java similarity index 99% rename from checker/tests/signedness/CompoundAssignments.java rename to checker/tests/signedness/CompoundAssignmentsSignedness.java index ebbc80749676..62dccb004a13 100644 --- a/checker/tests/signedness/CompoundAssignments.java +++ b/checker/tests/signedness/CompoundAssignmentsSignedness.java @@ -1,6 +1,6 @@ import org.checkerframework.checker.signedness.qual.*; -public class CompoundAssignments { +public class CompoundAssignmentsSignedness { public void DivModTest( @Unsigned int unsigned, From 5621f64b468e34c98a31426bcfc3b716c9ea6e99 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Fri, 17 Jul 2020 12:22:18 -0700 Subject: [PATCH 055/138] StubParser documentation improvements --- .../framework/stub/StubParser.java | 231 +++++++++++------- 1 file changed, 145 insertions(+), 86 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/stub/StubParser.java b/framework/src/main/java/org/checkerframework/framework/stub/StubParser.java index d48857a77576..e88591773b2b 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/StubParser.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/StubParser.java @@ -54,6 +54,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.StringJoiner; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; @@ -84,21 +85,26 @@ import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.Pair; +// From an implementation perspective, this class represents a single stub file, notably its +// annotated types and its declaration annotations. From a client perspective, it has two static +// methods as described below in the Javadoc. /** - * Given a stub file, yields the annotated types in it and the declaration annotations in it. The - * main entry point is {@link StubParser#parse(String, InputStream, AnnotatedTypeFactory, - * ProcessingEnvironment, Map, Map)}, which side-effects its last two arguments. + * This class has two static methods. Each method parses a stub file and adds annotations to two + * maps passed as arguments. * - *

    The constructor acts in two parts. First, it calls the Stub Parser to parse a stub file. Then, - * it walks the Stub Parser's AST to create/collect types and declaration annotations. + *

    The main entry point is {@link StubParser#parse(String, InputStream, AnnotatedTypeFactory, + * ProcessingEnvironment, Map, Map)}, which side-effects its last two arguments. It operates in two + * steps. First, it calls the Stub Parser to parse a stub file. Then, it walks the Stub Parser's AST + * to create/collect types and declaration annotations. + * + *

    The other entry point is {@link #parseJdkFileAsStub}. */ public class StubParser { /** - * Whether to print warnings about types/members that were not found. The warning is about - * whether a class/field in the stub file is not found on the user's real classpath. Since the - * stub file may contain packages that are not on the classpath, this can be OK, so default to - * false. + * Whether to print warnings about types/members that were not found. The warning states that a + * class/field in the stub file is not found on the user's real classpath. Since the stub file + * may contain packages that are not on the classpath, this can be OK, so default to false. */ private final boolean warnIfNotFound; @@ -165,17 +171,20 @@ public class StubParser { // The following variables are stored in the StubParser because otherwise they would need to be // passed through everywhere, which would be verbose. - /** The type that is currently being parsed. */ - FqName parseState; + /** + * The name of the type that is currently being parsed. After processing a package declaration + * but before processing a type declaration, the type part of this may be null. + */ + private FqName typeName; /** Output variable: .... */ - Map atypes; + private final Map atypes; /** * Map from a name (actually declaration element string) to the set of declaration annotations * on it. */ - Map> declAnnos; + private final Map> declAnnos; /** The line separator. */ private static final String LINE_SEPARATOR = System.lineSeparator().intern(); @@ -190,9 +199,13 @@ public class StubParser { * @param filename name of stub file, used only for diagnostic messages * @param atypeFactory AnnotatedTypeFactory to use * @param processingEnv ProcessingEnvironment to use + * @param atypes annotated types from this stub file are added to this map + * @param declAnnos map from a name (actually declaration element string) to the set of + * declaration annotations on it. Declaration annotations from this stub file are added to + * this map. * @param isJdkAsStub whether or not the stub file is a part of the JDK */ - public StubParser( + private StubParser( String filename, AnnotatedTypeFactory atypeFactory, ProcessingEnvironment processingEnv, @@ -204,9 +217,8 @@ public StubParser( this.processingEnv = processingEnv; this.elements = processingEnv.getElementUtils(); - // TODO: this should use SourceChecker.getOptions() to allow - // setting these flags per checker. However, that doesn't seem very - // pressing here. + // TODO: This should use SourceChecker.getOptions() to allow + // setting these flags per checker. Map options = processingEnv.getOptions(); this.warnIfNotFound = options.containsKey("stubWarnIfNotFound"); this.warnIfNotFoundIgnoresClasses = options.containsKey("stubWarnIfNotFoundIgnoresClasses"); @@ -285,6 +297,8 @@ private static List getImportableMembers(TypeElement typeElement) { return result; } + // TODO: I'm not sure what "found" means. This method seems to collect only those that are + // imported, so it will miss ones whose fully-qualified name is used in the stub file. /** * Returns all annotations found in the stub file, as a value for {@link #allStubAnnotations}. * Note that this also modifies {@link #importedConstants} and {@link #importedTypes}. @@ -296,6 +310,7 @@ private static List getImportableMembers(TypeElement typeElement) { private Map getAllStubAnnotations() { Map result = new HashMap<>(); + // TODO: The size can be greater than 1, but this ignores all but the first element. assert !stubUnit.getCompilationUnits().isEmpty(); CompilationUnit cu = stubUnit.getCompilationUnits().get(0); @@ -403,8 +418,10 @@ private void addEnclosingTypesToImportedTypes(Element element) { * @param inputStream of stub file to parse * @param atypeFactory AnnotatedTypeFactory to use * @param processingEnv ProcessingEnvironment to use - * @param atypes annotated types from this stub file is added to this map - * @param declAnnos declaration annotations from this stub file are added to this map + * @param atypes annotated types from this stub file are added to this map + * @param declAnnos map from a name (actually declaration element string) to the set of + * declaration annotations on it. Declaration annotations from this stub file are added to + * this map. */ public static void parse( String filename, @@ -424,8 +441,10 @@ public static void parse( * @param inputStream of stub file to parse * @param atypeFactory AnnotatedTypeFactory to use * @param processingEnv ProcessingEnvironment to use - * @param atypes annotated types from this stub file is added to this map - * @param declAnnos declaration annotations from this stub file are added to this map + * @param atypes annotated types from this stub file are added to this map + * @param declAnnos map from a name (actually declaration element string) to the set of + * declaration annotations on it. Declaration annotations from this stub file are added to + * this map. */ public static void parseJdkFileAsStub( String filename, @@ -444,8 +463,10 @@ public static void parseJdkFileAsStub( * @param inputStream of stub file to parse * @param atypeFactory AnnotatedTypeFactory to use * @param processingEnv ProcessingEnvironment to use - * @param atypes annotated types from this stub file is added to this map - * @param declAnnos declaration annotations from this stub file are added to this map + * @param atypes annotated types from this stub file are added to this map + * @param declAnnos map from a name (actually declaration element string) to the set of + * declaration annotations on it. Declaration annotations from this stub file are added to + * this map. * @param isJdkAsStub whether or not the stub file is a part of the annotated jdk */ private static void parse( @@ -463,23 +484,22 @@ private static void parse( sp.parseStubUnit(inputStream); sp.process(); } catch (ParseProblemException e) { - StringBuilder message = - new StringBuilder( - "exception while parsing stub file " - + filename - + ". Encountered problems: "); + StringJoiner message = new StringJoiner(LINE_SEPARATOR); + message.add( + e.getProblems().size() + " problems while parsing stub file " + filename + ":"); // Manually build up the message, to get verbose location information. for (Problem p : e.getProblems()) { - message.append(p.getVerboseMessage()); - message.append(LINE_SEPARATOR); + message.add(p.getVerboseMessage()); } sp.stubWarn(message.toString()); } } /** - * Delegate to the Stub Parser to parse the stub file to an AST. Subsequently, all work uses the - * AST. + * Delegate to the Stub Parser to parse the stub file to an AST, and save it in {@link + * #stubUnit}. Subsequently, all work uses the AST. + * + * @param inputStream the stream from which to read a stub file */ private void parseStubUnit(InputStream inputStream) { if (debugStubParser) { @@ -493,9 +513,10 @@ private void parseStubUnit(InputStream inputStream) { if (allStubAnnotations.isEmpty()) { stubWarnNotFound( String.format( - "No supported annotations found! This likely means stub file %s doesn't import them correctly.", + "No supported annotations found! Does stub file %s import them?", filename)); } + // Annotations in java.lang might be used without an import statement, so add them in case. allStubAnnotations.putAll(annosInPackage(findPackage("java.lang"))); } @@ -504,7 +525,11 @@ private void process() { processStubUnit(this.stubUnit); } - /** Parse the given StubUnit. */ + /** + * Process the given StubUnit. + * + * @param index the StubUnit to process + */ private void processStubUnit(StubUnit index) { for (CompilationUnit cu : index.getCompilationUnits()) { processCompilationUnit(cu); @@ -516,7 +541,7 @@ private void processCompilationUnit(CompilationUnit cu) { if (!cu.getPackageDeclaration().isPresent()) { packageAnnos = null; - parseState = new FqName(null, null); + typeName = new FqName(null, null); } else { PackageDeclaration pDecl = cu.getPackageDeclaration().get(); packageAnnos = pDecl.getAnnotations(); @@ -532,7 +557,7 @@ private void processCompilationUnit(CompilationUnit cu) { private void processPackage(PackageDeclaration packDecl) { assert (packDecl != null); String packageName = packDecl.getNameAsString(); - parseState = new FqName(packageName, null); + typeName = new FqName(packageName, null); Element elem = elements.getPackageElement(packageName); // If the element lookup fails, it's because we have an annotation for a // package that isn't on the classpath, which is fine. @@ -543,21 +568,25 @@ private void processPackage(PackageDeclaration packDecl) { } /** + * Process a type declaration + * + * @param typeDecl the type declaration to process * @param outertypeName the name of the containing class, when processing a nested class; * otherwise null + * @param packageAnnos the annotation declared in the package */ private void processTypeDecl( TypeDeclaration typeDecl, String outertypeName, List packageAnnos) { - assert parseState != null; + assert typeName != null; if (isJdkAsStub && typeDecl.getModifiers().contains(Modifier.privateModifier())) { - // Don't process private classes of the jdk. They can't be referenced outside of the - // jdk and might refer to types that are not accessible. + // Don't process private classes of the JDK. They can't be referenced outside of the + // JDK and might refer to types that are not accessible. return; } String innerName = (outertypeName == null ? "" : outertypeName + ".") + typeDecl.getNameAsString(); - parseState = new FqName(parseState.packageName, innerName); - String fqTypeName = parseState.toString(); + typeName = new FqName(typeName.packageName, innerName); + String fqTypeName = typeName.toString(); TypeElement typeElt = elements.getTypeElement(fqTypeName); if (typeElt == null) { if (debugStubParser @@ -616,7 +645,7 @@ private boolean hasNoStubParserWarning(Iterable aexprs) { return false; } for (AnnotationExpr anno : aexprs) { - if (anno.getNameAsString().contentEquals("NoStubParserWarning")) { + if (anno.getNameAsString().equals("NoStubParserWarning")) { return true; } } @@ -624,9 +653,11 @@ private boolean hasNoStubParserWarning(Iterable aexprs) { } /** - * Returns list of AnnotatedTypeVariable of the type's type parameter declarations. + * Returns the type's type parameter declarations. * - * @return list of AnnotatedTypeVariable of the type's type parameter declarations + * @param decl a type declaration + * @param elt the type's element + * @return the type's type parameter declarations */ private List processType( ClassOrInterfaceDeclaration decl, TypeElement elt) { @@ -649,7 +680,7 @@ private List processType( if (numParams != numArgs) { stubDebug( String.format( - "parseType: mismatched sizes for typeParameters=%s (size %d) and typeArguments=%s (size %d); decl=%s; elt=%s (%s); type=%s (%s); parseState=%s", + "parseType: mismatched sizes for typeParameters=%s (size %d) and typeArguments=%s (size %d); decl=%s; elt=%s (%s); type=%s (%s); typeName=%s", typeParameters, numParams, typeArguments, @@ -659,7 +690,7 @@ private List processType( elt.getClass(), type, type.getClass(), - parseState)); + typeName)); stubDebug("Proceeding despite mismatched sizes"); } } @@ -683,12 +714,11 @@ private List processType( } /** - * Gathers and returns a list of AnnotatedTypeVariable of the enum's type parameter - * declarations. + * Returns an enum's type parameter declarations. * - * @param decl actual enum declaration + * @param decl enum declaration * @param elt element representing enum - * @return list of AnnotatedTypeVariable of the enum's type parameter declarations + * @return the enum's type parameter declarations */ private List processEnum(EnumDeclaration decl, TypeElement elt) { @@ -777,6 +807,7 @@ private void processCallableDeclaration(CallableDeclaration decl, ExecutableE ((MethodDeclaration) decl).getType(), decl.getAnnotations()); } else { + assert decl.isConstructorDeclaration(); annotate(methodType.getReturnType(), decl.getAnnotations()); } @@ -851,7 +882,7 @@ private void processParameters( if (param.isVarArgs()) { assert paramType.getKind() == TypeKind.ARRAY; // The "type" of param is actually the component type of the vararg. - // For example, "Object..." the type would be "Object". + // For example, in "Object..." the type would be "Object". annotate( ((AnnotatedArrayType) paramType).getComponentType(), param.getType(), @@ -900,7 +931,7 @@ private void clearAnnotations(AnnotatedTypeMirror atype, Type typeDef) { /** * Add the annotations from {@code type} to {@code atype}. Type annotations that parsed as - * declaration annotations (i.e., type annotations in {@code declAnnos} are applied to the + * declaration annotations (i.e., type annotations in {@code declAnnos}) are applied to the * innermost component type. * * @param atype annotated type to which to add annotations @@ -908,11 +939,18 @@ private void clearAnnotations(AnnotatedTypeMirror atype, Type typeDef) { * @param declAnnos annotations stored on the declaration of the variable with this type or null */ private void annotateAsArray( - AnnotatedArrayType atype, ReferenceType type, NodeList declAnnos) { + AnnotatedArrayType atype, + ReferenceType type, + @Nullable NodeList declAnnos) { annotateInnermostComponentType(atype, declAnnos); Type typeDef = type; AnnotatedTypeMirror currentAtype = atype; - while (typeDef.isArrayType() && currentAtype.getKind() == TypeKind.ARRAY) { + while (typeDef.isArrayType()) { + if (currentAtype.getKind() != TypeKind.ARRAY) { + stubWarn("Mismatched array lengths; atype: " + atype + "%n type: " + type); + return; + } + // handle generic type clearAnnotations(currentAtype, typeDef); @@ -922,9 +960,9 @@ private void annotateAsArray( } typeDef = ((com.github.javaparser.ast.type.ArrayType) typeDef).getComponentType(); currentAtype = ((AnnotatedArrayType) currentAtype).getComponentType(); - if (typeDef.isArrayType() ^ currentAtype.getKind() == TypeKind.ARRAY) { - stubWarn("Mismatched array lengths; atype: " + atype + "%n type: " + type); - } + } + if (currentAtype.getKind() == TypeKind.ARRAY) { + stubWarn("Mismatched array lengths; atype: " + atype + "%n type: " + type); } } @@ -953,7 +991,7 @@ private ClassOrInterfaceType unwrapDeclaredType(Type type) { * null */ private void annotate( - AnnotatedTypeMirror atype, Type typeDef, NodeList declAnnos) { + AnnotatedTypeMirror atype, Type typeDef, @Nullable NodeList declAnnos) { if (atype.getKind() == TypeKind.ARRAY) { if (typeDef instanceof ReferenceType) { annotateAsArray((AnnotatedArrayType) atype, (ReferenceType) typeDef, declAnnos); @@ -1026,7 +1064,7 @@ private void annotate( + typeDef + ">" + " while parsing " - + parseState); + + typeName); return; } WildcardType wildcardDef = (WildcardType) typeDef; @@ -1084,14 +1122,14 @@ private boolean mightHaveTypeArguments(AnnotatedTypeMirror atype) { /** * Process the field declaration in decl, and attach any type qualifiers to the type of elt in - * {@link #atypes} + * {@link #atypes}. * * @param decl the declaration in the stub file * @param elt the element representing that same declaration */ private void processField(FieldDeclaration decl, VariableElement elt) { if (isJdkAsStub && decl.getModifiers().contains(Modifier.privateModifier())) { - // Don't process private fields of the jdk. They can't be referenced outside of the jdk + // Don't process private fields of the JDK. They can't be referenced outside of the JDK // and might refer to types that are not accessible. return; } @@ -1198,8 +1236,8 @@ private void recordDeclAnnotation(Element elt, List annotations) } } } - String key = ElementUtils.getVerboseName(elt); - putOrAddToMap(declAnnos, key, annos); + String eltName = ElementUtils.getVerboseName(elt); + putOrAddToMap(declAnnos, eltName, annos); } /** @@ -1365,7 +1403,7 @@ private AnnotatedDeclaredType findType( * @return nested in typeElt element with the name of the class or interface or null if nested * element is not found */ - private Element findElement(TypeElement typeElt, ClassOrInterfaceDeclaration ciDecl) { + private @Nullable Element findElement(TypeElement typeElt, ClassOrInterfaceDeclaration ciDecl) { final String wantedClassOrInterfaceName = ciDecl.getNameAsString(); for (TypeElement typeElement : ElementUtils.getAllTypeElementsIn(typeElt)) { if (wantedClassOrInterfaceName.equals(typeElement.getSimpleName().toString())) { @@ -1394,7 +1432,7 @@ private Element findElement(TypeElement typeElt, ClassOrInterfaceDeclaration ciD * @return nested in typeElt enum element with the name of the provided enum or null if nested * element is not found */ - private Element findElement(TypeElement typeElt, EnumDeclaration enumDecl) { + private @Nullable Element findElement(TypeElement typeElt, EnumDeclaration enumDecl) { final String wantedEnumName = enumDecl.getNameAsString(); for (TypeElement typeElement : ElementUtils.getAllTypeElementsIn(typeElt)) { if (wantedEnumName.equals(typeElement.getSimpleName().toString())) { @@ -1421,7 +1459,7 @@ private Element findElement(TypeElement typeElt, EnumDeclaration enumDecl) { * @return enum constant element in typeElt with the provided name or null if enum constant * element is not found */ - private VariableElement findElement( + private @Nullable VariableElement findElement( TypeElement typeElt, EnumConstantDeclaration enumConstDecl) { final String enumConstName = enumConstDecl.getNameAsString(); return findFieldElement(typeElt, enumConstName); @@ -1438,10 +1476,11 @@ private VariableElement findElement( * @return method element in typeElt with the same signature as the provided method declaration * or null if method element is not found */ - private ExecutableElement findElement(TypeElement typeElt, MethodDeclaration methodDecl) { + private @Nullable ExecutableElement findElement( + TypeElement typeElt, MethodDeclaration methodDecl) { if (isJdkAsStub && methodDecl.getModifiers().contains(Modifier.privateModifier())) { - // Don't process private methods of the jdk. They can't be referenced outside of the - // jdk and might refer to types that are not accessible. + // Don't process private methods of the JDK. They can't be referenced outside of the + // JDK and might refer to types that are not accessible. return null; } final String wantedMethodName = methodDecl.getNameAsString(); @@ -1488,11 +1527,11 @@ private ExecutableElement findElement(TypeElement typeElt, MethodDeclaration met * @return constructor element in typeElt with the same signature as the provided constructor * declaration or null if constructor element is not found */ - private ExecutableElement findElement( + private @Nullable ExecutableElement findElement( TypeElement typeElt, ConstructorDeclaration constructorDecl) { if (isJdkAsStub && constructorDecl.getModifiers().contains(Modifier.privateModifier())) { - // Don't process private constructors of the jdk. They can't be referenced outside of - // the jdk and might refer to types that are not accessible. + // Don't process private constructors of the JDK. They can't be referenced outside of + // the JDK and might refer to types that are not accessible. return null; } final int wantedMethodParams = @@ -1532,7 +1571,7 @@ private VariableElement findElement(TypeElement typeElt, VariableDeclarator vari * @param fieldName field name that should be found * @return field element in typeElt with the provided name or null if field element is not found */ - private VariableElement findFieldElement(TypeElement typeElt, String fieldName) { + private @Nullable VariableElement findFieldElement(TypeElement typeElt, String fieldName) { for (VariableElement field : ElementUtils.getAllFieldsIn(typeElt, elements)) { // field.getSimpleName() is a CharSequence, not a String if (fieldName.equals(field.getSimpleName().toString())) { @@ -1552,8 +1591,11 @@ private VariableElement findFieldElement(TypeElement typeElt, String fieldName) /** * Given a fully-qualified type name, return a TypeElement for it, or null if none exists. Also * cache in importedTypes. + * + * @param name a fully-qualified type name + * @return a TypeElement for the name, or null */ - private TypeElement getTypeElementOrNull(String name) { + private @Nullable TypeElement getTypeElementOrNull(String name) { TypeElement typeElement = elements.getTypeElement(name); if (typeElement != null) { importedTypes.put(name, typeElement); @@ -1642,8 +1684,15 @@ private AnnotationMirror getAnnotation( } } - /** Returns the value of {@code expr}, or null if some problem occurred getting the value. */ - private Object getValueOfExpressionInAnnotation( + /** + * Returns the value of {@code expr}, or null if some problem occurred getting the value. + * + * @param name the name of an annotation element/argument, used for diagnostic messages + * @param expr the expression to determine the value of + * @param valueKind the type of the result + * @return the value of {@code expr}, or null if some problem occurred getting the value + */ + private @Nullable Object getValueOfExpressionInAnnotation( String name, Expression expr, TypeKind valueKind) { if (expr instanceof FieldAccessExpr || expr instanceof NameExpr) { VariableElement elem; @@ -1722,23 +1771,23 @@ private Object getValueOfExpressionInAnnotation( /** * Returns the TypeElement with the name {@code name}, if one exists. Otherwise, checks the - * class and package of {@code parseState} for a class named {@code name}. + * class and package of {@code typeName} for a class named {@code name}. * * @param name classname (simple, or Outer.Inner, or fully-qualified) * @return the TypeElement for {@code name}, or null if not found */ private @Nullable TypeElement findTypeOfName(String name) { - String packageName = parseState.packageName; + String packageName = typeName.packageName; String packagePrefix = (packageName == null) ? "" : packageName + "."; - // stubWarn("findTypeOfName(%s), parseState %s %s", name, packageName, enclosingClass); + // stubWarn("findTypeOfName(%s), typeName %s %s", name, packageName, enclosingClass); // As soon as typeElement is set to a non-null value, it will be returned. TypeElement typeElement = getTypeElementOrNull(name); if (typeElement == null && packageName != null) { typeElement = getTypeElementOrNull(packagePrefix + name); } - String enclosingClass = parseState.className; + String enclosingClass = typeName.className; while (typeElement == null && enclosingClass != null) { typeElement = getTypeElementOrNull(packagePrefix + enclosingClass + "." + name); int lastDot = enclosingClass.lastIndexOf('.'); @@ -2027,7 +2076,7 @@ private void putMerge( if (m.containsKey(key)) { AnnotatedTypeMirror existingType = m.get(key); // If the newType is from a JDK stub file, then keep the existing type. This - // way user supplied stub files override jdk stub files. + // way user supplied stub files override JDK stub files. if (!isJdkAsStub) { AnnotatedTypeReplacer.replace(newType, existingType); } @@ -2113,11 +2162,21 @@ private void stubDebug(String warning) { /** Represents a class: its package name and simple name. */ private static class FqName { /** Name of the package being parsed, or null. */ - public String packageName; - - /** Name of the type being parsed. Includes outer class names if any. */ - public String className; - + public @Nullable String packageName; + + /** + * Name of the type being parsed. Includes outer class names if any. Null if the parser has + * parsed a package declaration but has not yet gotten to a type declaration. + */ + public @Nullable String className; + + /** + * Create a new FqName, which represents a class. + * + * @param packageName name of the package, or null + * @param className unqualified name of the type, including outer class names if any. May be + * null. + */ public FqName(String packageName, String className) { this.packageName = packageName; this.className = className; From 1f00a846dcafaeced12b0f6b808dcaeded879120 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 20 Jul 2020 13:24:57 -0700 Subject: [PATCH 056/138] Lombok's NonNull is a type annotation --- checker/src/testannotations/java/lombok/NonNull.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/checker/src/testannotations/java/lombok/NonNull.java b/checker/src/testannotations/java/lombok/NonNull.java index 5ad2d10a8ce5..cd30fffd5d37 100644 --- a/checker/src/testannotations/java/lombok/NonNull.java +++ b/checker/src/testannotations/java/lombok/NonNull.java @@ -11,5 +11,11 @@ @Documented @Retention(RetentionPolicy.CLASS) -@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE}) +@Target({ + ElementType.FIELD, + ElementType.METHOD, + ElementType.PARAMETER, + ElementType.LOCAL_VARIABLE, + ElementType.TYPE_USE +}) public @interface NonNull {} From dbb8241f3a4a1bf3a0423c643004358d47c0683e Mon Sep 17 00:00:00 2001 From: Weitian Xing Date: Mon, 20 Jul 2020 16:43:08 -0400 Subject: [PATCH 057/138] Move `getValue(Tree)` to Analysis interface. (#3420) --- .../dataflow/analysis/AbstractAnalysis.java | 9 +-------- .../checkerframework/dataflow/analysis/Analysis.java | 11 +++++++++++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractAnalysis.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractAnalysis.java index 22aed654df4f..74b1fda1087a 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractAnalysis.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/AbstractAnalysis.java @@ -262,14 +262,7 @@ public IdentityHashMap getNodeValues() { return cfg.getNodesCorrespondingToTree(t); } - /** - * Return the abstract value for {@link Tree} {@code t}, or {@code null} if no information is - * available. Note that if the analysis has not finished yet, this value might not represent the - * final value for this node. - * - * @param t the given tree - * @return the abstract value for the given tree - */ + @Override public @Nullable V getValue(Tree t) { // we don't have a org.checkerframework.dataflow fact about the current node yet if (t == currentTree) { diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/Analysis.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/Analysis.java index 476c61a75cd8..13964b5235cf 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/Analysis.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/Analysis.java @@ -1,5 +1,6 @@ package org.checkerframework.dataflow.analysis; +import com.sun.source.tree.Tree; import java.util.IdentityHashMap; import java.util.Map; import org.checkerframework.checker.nullness.qual.Nullable; @@ -116,6 +117,16 @@ S runAnalysisFor( */ @Nullable V getValue(Node n); + /** + * Return the abstract value for {@link Tree} {@code t}, or {@code null} if no information is + * available. Note that if the analysis has not finished yet, this value might not represent the + * final value for this node. + * + * @param t the given tree + * @return the abstract value for the given tree + */ + @Nullable V getValue(Tree t); + /** * Returns the regular exit store, or {@code null}, if there is no such store (because the * method cannot exit through the regular exit block). From fed0216f9731fffa2519ab84ceb40bb57536e5d2 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Mon, 20 Jul 2020 13:48:17 -0700 Subject: [PATCH 058/138] Don't ignore type arguments (Fixes #3443.) (#3456) --- checker/tests/nullness/Issue3443.java | 19 +++++++++++++++++++ .../framework/type/DefaultTypeHierarchy.java | 1 - 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 checker/tests/nullness/Issue3443.java diff --git a/checker/tests/nullness/Issue3443.java b/checker/tests/nullness/Issue3443.java new file mode 100644 index 000000000000..d70d281fb3eb --- /dev/null +++ b/checker/tests/nullness/Issue3443.java @@ -0,0 +1,19 @@ +import org.checkerframework.checker.nullness.qual.Nullable; + +class Issue3443 { + static > Supplier3443 passThrough(T t) { + // :: error: (return.type.incompatible) + return t; + } + + public static void main(String[] args) { + Supplier3443<@Nullable String> s1 = () -> null; + // TODO: passThrough(s1) should cause an error. #979. + Supplier3443 s2 = passThrough(s1); + s2.get().toString(); + } +} + +interface Supplier3443 { + T get(); +} diff --git a/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java index 8c4c9b996f2d..10fe883d43e4 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java @@ -1048,7 +1048,6 @@ && isPrimarySubtype(ub, supertype)) { } return false; } - return isPrimarySubtype(upperBound, supertype); } return checkAndSubtype(upperBound, supertype); } From 9b1cbbdf08c41e9a8d8e568bf3b7167781ea61be Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Mon, 20 Jul 2020 15:04:37 -0700 Subject: [PATCH 059/138] Add test case for Issue 2383. (#3483) --- checker/tests/signedness/Issue2483.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 checker/tests/signedness/Issue2483.java diff --git a/checker/tests/signedness/Issue2483.java b/checker/tests/signedness/Issue2483.java new file mode 100644 index 000000000000..9217ca9c1ae6 --- /dev/null +++ b/checker/tests/signedness/Issue2483.java @@ -0,0 +1,8 @@ +import org.checkerframework.checker.signedness.qual.*; + +class Issue2483 { + void foo(String a, byte[] b) { + @Unsigned int len = a.length(); + @Unsigned int len2 = b.length; + } +} From 6c2cf99b52318cda084cc439901444b8379953ec Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 20 Jul 2020 15:46:03 -0700 Subject: [PATCH 060/138] Add extraArgs argument to commonAssignmentCheck --- changelog.txt | 7 ++++- .../checker/formatter/FormatterVisitor.java | 5 ++-- .../checker/guieffect/GuiEffectVisitor.java | 12 +++++---- .../i18nformatter/I18nFormatterVisitor.java | 5 ++-- .../index/inequality/LessThanVisitor.java | 12 ++++++--- .../index/lowerbound/LowerBoundVisitor.java | 7 +++-- .../checker/index/samelen/SameLenVisitor.java | 5 ++-- .../index/upperbound/UpperBoundVisitor.java | 12 ++++++--- .../initialization/InitializationVisitor.java | 7 +++-- .../checker/lock/LockVisitor.java | 5 ++-- .../checker/nullness/NullnessVisitor.java | 17 +++++++----- .../common/aliasing/AliasingVisitor.java | 12 ++++++--- .../common/basetype/BaseTypeVisitor.java | 26 ++++++++++++++----- .../common/value/ValueVisitor.java | 16 +++++++----- .../javacutil/SystemUtil.java | 17 ++++++++++++ 15 files changed, 116 insertions(+), 49 deletions(-) diff --git a/changelog.txt b/changelog.txt index 6b500d2c887e..1b5311437e17 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,4 +1,4 @@ -Version 3.?.?, August 3, 2020 +Version 3.6.0, August 3, 2020 The Interning Checker supports method annotations @EqualsMethod and @CompareToMethod. Place them on methods like equals(), compareTo(), and @@ -6,6 +6,11 @@ compare() to permit certain uses of == on non-interned values. Added an overloaded version of NullnessUtil.castNonNull that takes an error message. +Implementation details: + +commonAssignmentCheck() now takes an additional argument. Type system +authors must update their overriding implementations. + --------------------------------------------------------------------------- Version 3.5.0, July 1, 2020 diff --git a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterVisitor.java b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterVisitor.java index f9ea68d46557..0ac09fbd6c6b 100644 --- a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterVisitor.java @@ -176,8 +176,9 @@ protected void commonAssignmentCheck( AnnotatedTypeMirror varType, AnnotatedTypeMirror valueType, Tree valueTree, - @CompilerMessageKey String errorKey) { - super.commonAssignmentCheck(varType, valueType, valueTree, errorKey); + @CompilerMessageKey String errorKey, + Object... extraArgs) { + super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); AnnotationMirror rhs = valueType.getAnnotationInHierarchy(atypeFactory.UNKNOWNFORMAT); AnnotationMirror lhs = varType.getAnnotationInHierarchy(atypeFactory.UNKNOWNFORMAT); diff --git a/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectVisitor.java b/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectVisitor.java index 79ac33e6fd81..5a2de6711bc1 100644 --- a/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectVisitor.java @@ -455,11 +455,13 @@ public Void visitNewClass(NewClassTree node, Void p) { /** * This method is called to traverse the path back up from any anonymous inner class or lambda - * which has been inferred to be UI affecting and re-run {@link #commonAssignmentCheck(Tree, - * ExpressionTree, String)} as needed on places where the class declaration or lambda expression - * are being assigned to a variable, passed as a parameter or returned from a method. This is - * necessary because the normal visitor traversal only checks assignments on the way down the - * AST, before inference has had a chance to run. + * which has been inferred to be UI affecting and re-run {@code commonAssignmentCheck} as needed + * on places where the class declaration or lambda expression are being assigned to a variable, + * passed as a parameter or returned from a method. This is necessary because the normal visitor + * traversal only checks assignments on the way down the AST, before inference has had a chance + * to run. + * + * @param path the path to traverse up from a UI-affecting class */ private void scanUp(TreePath path) { Tree tree = path.getLeaf(); diff --git a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterVisitor.java b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterVisitor.java index 08b4b5ca48e8..f50d16329a11 100644 --- a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterVisitor.java @@ -120,7 +120,8 @@ protected void commonAssignmentCheck( AnnotatedTypeMirror varType, AnnotatedTypeMirror valueType, Tree valueTree, - @CompilerMessageKey String errorKey) { + @CompilerMessageKey String errorKey, + Object... extraArgs) { AnnotationMirror rhs = valueType.getAnnotationInHierarchy(atypeFactory.I18NUNKNOWNFORMAT); AnnotationMirror lhs = varType.getAnnotationInHierarchy(atypeFactory.I18NUNKNOWNFORMAT); @@ -161,6 +162,6 @@ protected void commonAssignmentCheck( // issued for a given line of code will take precedence over the // assignment.type.incompatible // issued by super.commonAssignmentCheck. - super.commonAssignmentCheck(varType, valueType, valueTree, errorKey); + super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanVisitor.java b/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanVisitor.java index 0d608e568953..a6fd12c3283b 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/index/inequality/LessThanVisitor.java @@ -24,7 +24,10 @@ public LessThanVisitor(BaseTypeChecker checker) { @Override protected void commonAssignmentCheck( - Tree varTree, ExpressionTree valueTree, @CompilerMessageKey String errorKey) { + Tree varTree, + ExpressionTree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { // check that when an assignment to a variable declared as @HasSubsequence(a, from, to) // occurs, from <= to. @@ -53,7 +56,7 @@ protected void commonAssignmentCheck( } } - super.commonAssignmentCheck(varTree, valueTree, errorKey); + super.commonAssignmentCheck(varTree, valueTree, errorKey, extraArgs); } @Override @@ -61,7 +64,8 @@ protected void commonAssignmentCheck( AnnotatedTypeMirror varType, AnnotatedTypeMirror valueType, Tree valueTree, - @CompilerMessageKey String errorKey) { + @CompilerMessageKey String errorKey, + Object... extraArgs) { // If value is less than all expressions in the annotation in varType, // using the Value Checker, then skip the common assignment check. // Also skip the check if the only expression is "a + 1" and the valueTree @@ -98,7 +102,7 @@ protected void commonAssignmentCheck( return; } } - super.commonAssignmentCheck(varType, valueType, valueTree, errorKey); + super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); } @Override diff --git a/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundVisitor.java b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundVisitor.java index df3e84591d03..3cecd0ad5f62 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundVisitor.java @@ -62,7 +62,10 @@ public Void visitNewArray(NewArrayTree tree, Void type) { @Override protected void commonAssignmentCheck( - Tree varTree, ExpressionTree valueTree, @CompilerMessageKey String errorKey) { + Tree varTree, + ExpressionTree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { // check that when an assignment to a variable declared as @HasSubsequence(a, from, to) // occurs, from is non-negative. @@ -88,6 +91,6 @@ protected void commonAssignmentCheck( } } - super.commonAssignmentCheck(varTree, valueTree, errorKey); + super.commonAssignmentCheck(varTree, valueTree, errorKey, extraArgs); } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenVisitor.java b/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenVisitor.java index 613c17fd584c..fda48689c13c 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/index/samelen/SameLenVisitor.java @@ -33,7 +33,8 @@ protected void commonAssignmentCheck( AnnotatedTypeMirror varType, AnnotatedTypeMirror valueType, Tree valueTree, - @CompilerMessageKey String errorKey) { + @CompilerMessageKey String errorKey, + Object... extraArgs) { if (IndexUtil.isSequenceType(valueType.getUnderlyingType()) && TreeUtils.isExpressionTree(valueTree) // if both annotations are @PolySameLen, there is nothing to do @@ -57,6 +58,6 @@ protected void commonAssignmentCheck( valueType.replaceAnnotation(newSameLen); } } - super.commonAssignmentCheck(varType, valueType, valueTree, errorKey); + super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); } } diff --git a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundVisitor.java b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundVisitor.java index 35b0d8e81263..95141c62b43c 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundVisitor.java @@ -190,7 +190,10 @@ private void visitAccess(ExpressionTree indexTree, ExpressionTree arrTree) { @Override protected void commonAssignmentCheck( - Tree varTree, ExpressionTree valueTree, @CompilerMessageKey String errorKey) { + Tree varTree, + ExpressionTree valueTree, + @CompilerMessageKey String errorKey, + Object... extraArgs) { // check that when an assignment to a variable b declared as @HasSubsequence(a, from, to) // occurs, to <= a.length, i.e. to is @LTEqLengthOf(a). @@ -237,14 +240,15 @@ protected void commonAssignmentCheck( } } - super.commonAssignmentCheck(varTree, valueTree, errorKey); + super.commonAssignmentCheck(varTree, valueTree, errorKey, extraArgs); } @Override protected void commonAssignmentCheck( AnnotatedTypeMirror varType, ExpressionTree valueTree, - @CompilerMessageKey String errorKey) { + @CompilerMessageKey String errorKey, + Object... extraArgs) { AnnotatedTypeMirror valueType = atypeFactory.getAnnotatedType(valueTree); commonAssignmentCheckStartDiagnostic(varType, valueType, valueTree); if (!relaxedCommonAssignment(varType, valueTree)) { @@ -253,7 +257,7 @@ protected void commonAssignmentCheck( varType, valueType, valueTree); - super.commonAssignmentCheck(varType, valueTree, errorKey); + super.commonAssignmentCheck(varType, valueTree, errorKey, extraArgs); } else if (checker.hasOption("showchecks")) { commonAssignmentCheckEndDiagnostic( true, "relaxedCommonAssignment", varType, valueType, valueTree); diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationVisitor.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationVisitor.java index 44c370f48fd6..fcc5e0847946 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationVisitor.java @@ -110,7 +110,10 @@ protected void checkThisOrSuperConstructorCall( @Override protected void commonAssignmentCheck( - Tree varTree, ExpressionTree valueExp, @CompilerMessageKey String errorKey) { + Tree varTree, + ExpressionTree valueExp, + @CompilerMessageKey String errorKey, + Object... extraArgs) { // field write of the form x.f = y if (TreeUtils.isFieldAccess(varTree)) { // cast is safe: a field access can only be an IdentifierTree or @@ -141,7 +144,7 @@ protected void commonAssignmentCheck( } } } - super.commonAssignmentCheck(varTree, valueExp, errorKey); + super.commonAssignmentCheck(varTree, valueExp, errorKey, extraArgs); } @Override diff --git a/checker/src/main/java/org/checkerframework/checker/lock/LockVisitor.java b/checker/src/main/java/org/checkerframework/checker/lock/LockVisitor.java index 35d0df23e99f..f109f81e1358 100644 --- a/checker/src/main/java/org/checkerframework/checker/lock/LockVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/lock/LockVisitor.java @@ -343,7 +343,8 @@ protected void commonAssignmentCheck( AnnotatedTypeMirror varType, AnnotatedTypeMirror valueType, Tree valueTree, - @CompilerMessageKey String errorKey) { + @CompilerMessageKey String errorKey, + Object... extraArgs) { Kind valueTreeKind = valueTree.getKind(); @@ -430,7 +431,7 @@ protected void commonAssignmentCheck( } } - super.commonAssignmentCheck(varType, valueType, valueTree, errorKey); + super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); } @Override diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessVisitor.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessVisitor.java index febac4ab1ef4..5d0210e89342 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessVisitor.java @@ -175,7 +175,10 @@ private boolean containsSameByName( @Override protected void commonAssignmentCheck( - Tree varTree, ExpressionTree valueExp, @CompilerMessageKey String errorKey) { + Tree varTree, + ExpressionTree valueExp, + @CompilerMessageKey String errorKey, + Object... extraArgs) { // allow MonotonicNonNull to be initialized to null at declaration if (varTree.getKind() == Tree.Kind.VARIABLE) { @@ -187,20 +190,21 @@ protected void commonAssignmentCheck( return; } } - super.commonAssignmentCheck(varTree, valueExp, errorKey); + super.commonAssignmentCheck(varTree, valueExp, errorKey, extraArgs); } @Override protected void commonAssignmentCheck( AnnotatedTypeMirror varType, ExpressionTree valueExp, - @CompilerMessageKey String errorKey) { + @CompilerMessageKey String errorKey, + Object... extraArgs) { // Use the valueExp as the context because data flow will have a value for that tree. // It might not have a value for the var tree. This is sound because // if data flow has determined @PolyNull is @Nullable at the RHS, then // it is also @Nullable for the LHS. atypeFactory.replacePolyQualifier(varType, valueExp); - super.commonAssignmentCheck(varType, valueExp, errorKey); + super.commonAssignmentCheck(varType, valueExp, errorKey, extraArgs); } @Override @@ -208,7 +212,8 @@ protected void commonAssignmentCheck( AnnotatedTypeMirror varType, AnnotatedTypeMirror valueType, Tree valueTree, - @CompilerMessageKey String errorKey) { + @CompilerMessageKey String errorKey, + Object... extraArgs) { if (TypesUtils.isPrimitive(varType.getUnderlyingType()) && !TypesUtils.isPrimitive(valueType.getUnderlyingType())) { boolean succeed = checkForNullability(valueType, valueTree, UNBOXING_OF_NULLABLE); @@ -217,7 +222,7 @@ protected void commonAssignmentCheck( return; } } - super.commonAssignmentCheck(varType, valueType, valueTree, errorKey); + super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); } /** Case 1: Check for null dereferencing. */ diff --git a/framework/src/main/java/org/checkerframework/common/aliasing/AliasingVisitor.java b/framework/src/main/java/org/checkerframework/common/aliasing/AliasingVisitor.java index 63d7ba5678af..e0cde13e2b0c 100644 --- a/framework/src/main/java/org/checkerframework/common/aliasing/AliasingVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/aliasing/AliasingVisitor.java @@ -150,8 +150,11 @@ private void isUniqueCheck( // this isn't called for pseudo-assignments. @Override protected void commonAssignmentCheck( - Tree varTree, ExpressionTree valueExp, @CompilerMessageKey String errorKey) { - super.commonAssignmentCheck(varTree, valueExp, errorKey); + Tree varTree, + ExpressionTree valueExp, + @CompilerMessageKey String errorKey, + Object... extraArgs) { + super.commonAssignmentCheck(varTree, valueExp, errorKey, extraArgs); if (isInUniqueConstructor() && TreeUtils.isExplicitThisDereference(valueExp)) { // If an assignment occurs inside a constructor with // result type @Unique, it will invalidate the @Unique property @@ -167,8 +170,9 @@ protected void commonAssignmentCheck( AnnotatedTypeMirror varType, AnnotatedTypeMirror valueType, Tree valueTree, - @CompilerMessageKey String errorKey) { - super.commonAssignmentCheck(varType, valueType, valueTree, errorKey); + @CompilerMessageKey String errorKey, + Object... extraArgs) { + super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); // If we are visiting a pseudo-assignment, visitorLeafKind is either // Kind.NEW_CLASS or Kind.METHOD_INVOCATION. 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 d4a18aeb83c1..4e43171058c0 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -117,6 +117,7 @@ import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.Pair; +import org.checkerframework.javacutil.SystemUtil; import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypesUtils; @@ -1280,7 +1281,7 @@ private boolean isTypeAnnotation(AnnotationTree anno) { /** * Performs two checks: subtyping and assignability checks, using {@link - * #commonAssignmentCheck(Tree, ExpressionTree, String)}. + * #commonAssignmentCheck(Tree, ExpressionTree, String, Object[])}. * *

    If the subtype check fails, it issues a "assignment.type.incompatible" error. */ @@ -2345,9 +2346,13 @@ protected Set getThrowUpperBoundAnnotations() { * @param varTree the AST node for the lvalue (usually a variable) * @param valueExp the AST node for the rvalue (the new value) * @param errorKey the error message key to use if the check fails + * @param extraArgs arguments to the error message key, before "found" and "expected" types */ protected void commonAssignmentCheck( - Tree varTree, ExpressionTree valueExp, @CompilerMessageKey String errorKey) { + Tree varTree, + ExpressionTree valueExp, + @CompilerMessageKey String errorKey, + Object... extraArgs) { AnnotatedTypeMirror var = atypeFactory.getAnnotatedTypeLhs(varTree); assert var != null : "no variable found for tree: " + varTree; @@ -2355,7 +2360,7 @@ protected void commonAssignmentCheck( return; } - commonAssignmentCheck(var, valueExp, errorKey); + commonAssignmentCheck(var, valueExp, errorKey, extraArgs); } /** @@ -2365,11 +2370,13 @@ protected void commonAssignmentCheck( * @param varType the annotated type of the lvalue (usually a variable) * @param valueExp the AST node for the rvalue (the new value) * @param errorKey the error message key to use if the check fails + * @param extraArgs arguments to the error message key, before "found" and "expected" types */ protected void commonAssignmentCheck( AnnotatedTypeMirror varType, ExpressionTree valueExp, - @CompilerMessageKey String errorKey) { + @CompilerMessageKey String errorKey, + Object... extraArgs) { if (shouldSkipUses(valueExp)) { return; } @@ -2393,7 +2400,7 @@ protected void commonAssignmentCheck( } AnnotatedTypeMirror valueType = atypeFactory.getAnnotatedType(valueExp); assert valueType != null : "null type for expression: " + valueExp; - commonAssignmentCheck(varType, valueType, valueExp, errorKey); + commonAssignmentCheck(varType, valueType, valueExp, errorKey, extraArgs); } /** @@ -2488,12 +2495,14 @@ protected final void commonAssignmentCheckEndDiagnostic( * @param valueType the annotated type of the value * @param valueTree the location to use when reporting the error message * @param errorKey the error message key to use if the check fails + * @param extraArgs arguments to the error message key, before "found" and "expected" types */ protected void commonAssignmentCheck( AnnotatedTypeMirror varType, AnnotatedTypeMirror valueType, Tree valueTree, - @CompilerMessageKey String errorKey) { + @CompilerMessageKey String errorKey, + Object... extraArgs) { commonAssignmentCheckStartDiagnostic(varType, valueType, valueTree); @@ -2522,7 +2531,10 @@ protected void commonAssignmentCheck( FoundRequired pair = FoundRequired.of(valueType, varType); String valueTypeString = pair.found; String varTypeString = pair.required; - checker.reportError(valueTree, errorKey, valueTypeString, varTypeString); + checker.reportError( + valueTree, + errorKey, + SystemUtil.concatenate(extraArgs, valueTypeString, varTypeString)); } } diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueVisitor.java b/framework/src/main/java/org/checkerframework/common/value/ValueVisitor.java index 5e563f6b1292..6aee2b3424d5 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueVisitor.java @@ -46,16 +46,18 @@ public ValueVisitor(BaseTypeChecker checker) { * * @param varType the annotated type of the lvalue (usually a variable) * @param valueExp the AST node for the rvalue (the new value) - * @param errorKey the error message to use if the check fails (must be a compiler message key, + * @param errorKey the error message key to use if the check fails + * @param extraArgs arguments to the error message key, before "found" and "expected" types */ @Override protected void commonAssignmentCheck( AnnotatedTypeMirror varType, ExpressionTree valueExp, - @CompilerMessageKey String errorKey) { + @CompilerMessageKey String errorKey, + Object... extraArgs) { replaceSpecialIntRangeAnnotations(varType); - super.commonAssignmentCheck(varType, valueExp, errorKey); + super.commonAssignmentCheck(varType, valueExp, errorKey, extraArgs); } @Override @@ -63,7 +65,8 @@ protected void commonAssignmentCheck( AnnotatedTypeMirror varType, AnnotatedTypeMirror valueType, Tree valueTree, - @CompilerMessageKey String errorKey) { + @CompilerMessageKey String errorKey, + Object... extraArgs) { replaceSpecialIntRangeAnnotations(varType); @@ -73,13 +76,14 @@ protected void commonAssignmentCheck( getTypeFactory().createIntRangeAnnotation(Range.CHAR_EVERYTHING)); } - super.commonAssignmentCheck(varType, valueType, valueTree, errorKey); + super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); } /** * Return types for methods that are annotated with {@code @IntRangeFromX} annotations need to * be replaced with {@code @UnknownVal}. See the documentation on {@link - * #commonAssignmentCheck(AnnotatedTypeMirror, ExpressionTree, String) commonAssignmentCheck}. + * #commonAssignmentCheck(AnnotatedTypeMirror, ExpressionTree, String, Object[]) + * commonAssignmentCheck}. * *

    A separate override is necessary because checkOverride doesn't actually use the * commonAssignmentCheck. diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/SystemUtil.java b/javacutil/src/main/java/org/checkerframework/javacutil/SystemUtil.java index b0bf27b9558f..73752038cba8 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/SystemUtil.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/SystemUtil.java @@ -9,6 +9,7 @@ import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -225,4 +226,20 @@ public static int getJreVersion() { } return javaHome + File.separator + "lib" + File.separator + "tools.jar"; } + + /** + * Concatenates two arrays. Can be invoked varargs-style. + * + * @param the type of the array elements + * @param array1 the first array + * @param array2 the second array + * @return a new array containing the contents of the given arrays, in order + */ + @SuppressWarnings("unchecked") + public static T[] concatenate(T[] array1, T... array2) { + @SuppressWarnings("nullness") // elements are not non-null yet, but will be by return stmt + T[] result = Arrays.copyOf(array1, array1.length + array2.length); + System.arraycopy(array2, 0, result, array1.length, array2.length); + return result; + } } From 34989702ef50f1ae3939d7acda3ad3b8dabc56e1 Mon Sep 17 00:00:00 2001 From: Priti Chattopadhyay <35490584+PRITI1999@users.noreply.github.com> Date: Tue, 21 Jul 2020 04:21:42 +0530 Subject: [PATCH 061/138] Add -Aversion command-line option; fixes #3381 --- changelog.txt | 2 ++ docs/manual/introduction.tex | 4 ++++ .../framework/source/SourceChecker.java | 19 +++++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/changelog.txt b/changelog.txt index 1b5311437e17..35768a76cb19 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,7 @@ Version 3.6.0, August 3, 2020 +Added a new option `-Aversion` to print the version of the Checker Framework. + The Interning Checker supports method annotations @EqualsMethod and @CompareToMethod. Place them on methods like equals(), compareTo(), and compare() to permit certain uses of == on non-interned values. diff --git a/docs/manual/introduction.tex b/docs/manual/introduction.tex index b01e64043c9b..9a2d0327cd2c 100644 --- a/docs/manual/introduction.tex +++ b/docs/manual/introduction.tex @@ -788,6 +788,10 @@ \<-AatfCacheSize> Miscellaneous debugging options; see Section~\ref{creating-debugging-options-misc}. +\item + \<-Aversion> +Print the Checker Framework version. + \item \<-AprintGitProperties> Print information about the git repository from which the Checker Framework 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 1806f7bf62d8..2ff0cde379b0 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -253,6 +253,8 @@ /// Amount of detail in messages + // Print the version of the Checker Framework + "version", // Print info about git repository from which the Checker Framework was compiled "printGitProperties", @@ -781,6 +783,9 @@ public void run() { } }); } + if (hasOption("version")) { + messager.printMessage(Kind.NOTE, "Checker Framework " + getCheckerVersion()); + } } catch (UserError ce) { logUserError(ce); } catch (TypeSystemError ce) { @@ -2530,4 +2535,18 @@ private void printGitProperties() { System.out.println("IOException while reading git.properties: " + e.getMessage()); } } + + /** + * Returns the version of the Checker Framework. + * + * @return Checker Framework version + */ + private String getCheckerVersion() { + Properties gitProperties = getProperties(getClass(), "/git.properties"); + String version = gitProperties.getProperty("git.build.version"); + if (version != null) { + return version; + } + throw new BugInCF("Could not find the version in git.properties"); + } } From 3c296672a6199e6040e2b5c39b13ba401f6f3340 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 20 Jul 2020 18:02:13 -0700 Subject: [PATCH 062/138] Add contributor --- docs/manual/contributors.tex | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/manual/contributors.tex b/docs/manual/contributors.tex index 210178a1ec99..33b77094b042 100644 --- a/docs/manual/contributors.tex +++ b/docs/manual/contributors.tex @@ -81,6 +81,7 @@ Paulo Barros, Philip Lai, Prionti Nasir, +Priti Chattopadhyay, Rashmi Mudduluru, Ravi Roshan, Renato Athaydes, From 0c00b3f36398939ecf2b4f3968d026434503f593 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 21 Jul 2020 08:56:36 -0700 Subject: [PATCH 063/138] Reorder changelog --- changelog.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index 35768a76cb19..9890d8f15f97 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,13 +1,13 @@ Version 3.6.0, August 3, 2020 -Added a new option `-Aversion` to print the version of the Checker Framework. - The Interning Checker supports method annotations @EqualsMethod and @CompareToMethod. Place them on methods like equals(), compareTo(), and compare() to permit certain uses of == on non-interned values. Added an overloaded version of NullnessUtil.castNonNull that takes an error message. +Added a new option `-Aversion` to print the version of the Checker Framework. + Implementation details: commonAssignmentCheck() now takes an additional argument. Type system From cb8e74a878ae982589c4c16e58ea5334ccb8b34d Mon Sep 17 00:00:00 2001 From: Martin Kellogg Date: Tue, 21 Jul 2020 09:37:03 -0700 Subject: [PATCH 064/138] Add predicate support to accumulation checker (#3476) --- docs/manual/accumulation-checker.tex | 20 +- .../AccumulationAnnotatedTypeFactory.java | 313 ++++++++- .../accumulation/AccumulationTransfer.java | 17 +- .../accumulation/AccumulationVisitor.java | 36 + .../common/accumulation/messages.properties | 1 + .../TestAccumulationAnnotatedTypeFactory.java | 7 +- .../qual/TestAccumulationBottom.java | 2 +- .../qual/TestAccumulationPredicate.java | 15 + .../src/test/java/tests/AccumulationTest.java | 8 +- framework/tests/accumulation/Not.java | 75 +++ framework/tests/accumulation/Predicates.java | 634 ++++++++++++++++++ .../accumulation/SimpleInferenceMerge.java | 37 + .../tests/accumulation/SmallPredicate.java | 19 + .../accumulation/UnparseablePredicate.java | 50 ++ framework/tests/accumulation/Xor.java | 64 ++ 15 files changed, 1275 insertions(+), 23 deletions(-) create mode 100644 framework/src/main/java/org/checkerframework/common/accumulation/AccumulationVisitor.java create mode 100644 framework/src/main/java/org/checkerframework/common/accumulation/messages.properties create mode 100644 framework/src/test/java/testaccumulation/qual/TestAccumulationPredicate.java create mode 100644 framework/tests/accumulation/Not.java create mode 100644 framework/tests/accumulation/Predicates.java create mode 100644 framework/tests/accumulation/SimpleInferenceMerge.java create mode 100644 framework/tests/accumulation/SmallPredicate.java create mode 100644 framework/tests/accumulation/UnparseablePredicate.java create mode 100644 framework/tests/accumulation/Xor.java diff --git a/docs/manual/accumulation-checker.tex b/docs/manual/accumulation-checker.tex index ddb0132e280c..949d015e9df0 100644 --- a/docs/manual/accumulation-checker.tex +++ b/docs/manual/accumulation-checker.tex @@ -55,6 +55,21 @@ \href{https://github.com/typetools/checker-framework/blob/master/framework/src/test/java/testaccumulation/qual/TestAccumulationBottom.java}{TestAccumulationBottom.java}. It should take no arguments, and should be a subtype of the accumulator type you defined earlier. +You may also define a predicate annotation, analogous to +\href{https://github.com/typetools/checker-framework/blob/master/framework/src/test/java/testaccumulation/qual/TestAccumulationPredicate.java}{TestAccumulationPredicate.java}. +It must have a single argument named \ of type \. +The predicate syntax supports +\begin{itemize} +\item \<||> disjunctions +\item \<\&\&> conjunctions +\item \ logical complement. \<"!x"> means +``it is not true that \ was definitely accumulated'' or, equivalently, ``there is no path on which \ was accumulated''. +Note that this does \textbf{not} mean ``\ was not accumulated'' --- it is not a violation of the specification \<"!x"> if \ is accumulated +on some paths, but not others. +\item \<(...)> parentheses for precedence +\end{itemize} + + \paragraphAndLabel{Setting up the checker}{accumulation-setup} Define a new class that extends \refclass{common/accumulation}{AccumulationChecker}. @@ -62,8 +77,9 @@ Define a new class that extends \refclass{common/accumulation}{AccumulationAnnotatedTypeFactory}. You must create a new constructor whose only argument is a \refclass{common/basetype}{BaseTypeChecker}. -Your constructor should call the \ constructor defined in -\refclass{common/accumulation}{AccumulationAnnotatedTypeFactory}. +Your constructor should call one of the \ constructors defined in +\refclass{common/accumulation}{AccumulationAnnotatedTypeFactory} (which one depends on whether or not +you defined a predicate annotation). \paragraphAndLabel{Adding accumulation logic}{accumulation-accumulating} diff --git a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationAnnotatedTypeFactory.java index 481d6684f024..463a9a280de3 100644 --- a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationAnnotatedTypeFactory.java @@ -1,5 +1,10 @@ package org.checkerframework.common.accumulation; +import com.github.javaparser.ParseProblemException; +import com.github.javaparser.StaticJavaParser; +import com.github.javaparser.ast.expr.BinaryExpr; +import com.github.javaparser.ast.expr.Expression; +import com.github.javaparser.ast.expr.UnaryExpr; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.MethodInvocationTree; import java.lang.annotation.Annotation; @@ -7,8 +12,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.StringJoiner; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.returnsreceiver.ReturnsReceiverAnnotatedTypeFactory; @@ -25,6 +33,7 @@ import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.UserError; /** * An annotated type factory for an accumulation checker. @@ -50,6 +59,12 @@ public abstract class AccumulationAnnotatedTypeFactory extends BaseAnnotatedType */ private final Class accumulator; + /** + * The predicate annotation for this accumulation analysis, or null if predicates are not + * supported. A predicate annotation must have a single element named "value" of type String. + */ + private final @MonotonicNonNull Class predicate; + /** * Create an annotated type factory for an accumulation checker. * @@ -58,38 +73,70 @@ public abstract class AccumulationAnnotatedTypeFactory extends BaseAnnotatedType * argument named "value" whose type is a String array. * @param bottom the bottom type in the hierarchy, which must be a subtype of {@code * accumulator}. The bottom type should be an annotation with no arguments. + * @param predicate the predicate annotation. Either null (if predicates are not supported), or + * an annotation with a single element named "value" whose type is a String. */ protected AccumulationAnnotatedTypeFactory( BaseTypeChecker checker, Class accumulator, - Class bottom) { + Class bottom, + @Nullable Class predicate) { super(checker); this.accumulator = accumulator; - // Check that the requirements of the accumulator are met. Method[] accDeclaredMethods = accumulator.getDeclaredMethods(); if (accDeclaredMethods.length != 1) { rejectMalformedAccumulator("have exactly one element"); } - Method value = accDeclaredMethods[0]; - if (value.getName() != "value") { // interned + + Method accValue = accDeclaredMethods[0]; + if (accValue.getName() != "value") { // interned rejectMalformedAccumulator("name its element \"value\""); } - if (!value.getReturnType().isInstance(new String[0])) { + if (!accValue.getReturnType().isInstance(new String[0])) { rejectMalformedAccumulator("have an element of type String[]"); } - if (((String[]) value.getDefaultValue()).length != 0) { + if (((String[]) accValue.getDefaultValue()).length != 0) { rejectMalformedAccumulator("have the empty String array {} as its default value"); } + this.predicate = predicate; + // If there is a predicate annotation, check that its requirements are met. + if (predicate != null) { + Method[] predDeclaredMethods = predicate.getDeclaredMethods(); + if (predDeclaredMethods.length != 1) { + rejectMalformedPredicate("have exactly one element"); + } + Method predValue = predDeclaredMethods[0]; + if (predValue.getName() != "value") { // interned + rejectMalformedPredicate("name its element \"value\""); + } + if (!predValue.getReturnType().isInstance("")) { + rejectMalformedPredicate("have an element of type String"); + } + } + this.bottom = AnnotationBuilder.fromClass(elements, bottom); this.top = createAccumulatorAnnotation(Collections.emptyList()); - // Every subclass must call postInit! This does not do so for subclasses. - if (this.getClass() == AccumulationAnnotatedTypeFactory.class) { - this.postInit(); - } + // Every subclass must call postInit! This does not do so. + } + + /** + * Create an annotated type factory for an accumulation checker. + * + * @param checker the checker + * @param accumulator the accumulator type in the hierarchy. Must be an annotation with a single + * argument named "value" whose type is a String array. + * @param bottom the bottom type in the hierarchy, which must be a subtype of {@code + * accumulator}. The bottom type should be an annotation with no arguments. + */ + protected AccumulationAnnotatedTypeFactory( + BaseTypeChecker checker, + Class accumulator, + Class bottom) { + this(checker, accumulator, bottom, null); } /** @@ -99,7 +146,32 @@ protected AccumulationAnnotatedTypeFactory( * replace $MISSING$: "The accumulator annotation Foo must $MISSING$." */ private void rejectMalformedAccumulator(String missing) { - throw new BugInCF("The accumulator annotation " + accumulator + " must " + missing + "."); + rejectMalformedAnno("accumulator", accumulator, missing); + } + + /** + * Common error message for malformed predicate annotation. + * + * @param missing what is missing from the predicate, suitable for use in this string to replace + * $MISSING$: "The predicate annotation Foo must $MISSING$." + */ + private void rejectMalformedPredicate(String missing) { + rejectMalformedAnno("predicate", predicate, missing); + } + + /** + * Common error message implementation. Call rejectMalformedAccumulator or + * rejectMalformedPredicate as appropriate, rather than this method directly. + * + * @param annoTypeName the display name for the type of malformed annotation, such as + * "accumulator" + * @param anno the malformed annotation + * @param missing what is missing from the annotation, suitable for use in this string to + * replace $MISSING$: "The accumulator annotation Foo must $MISSING$." + */ + private void rejectMalformedAnno( + String annoTypeName, Class anno, String missing) { + throw new BugInCF("The " + annoTypeName + " annotation " + anno + " must " + missing + "."); } /** @@ -248,6 +320,26 @@ public List getAccumulatedValues(AnnotationMirror anno) { * | * bottom * + * + * Predicate subtyping is defined as follows: + * + *

      + *
    • An accumulator is a subtype of a predicate if substitution from the accumulator to the + * predicate makes the predicate true. For example, {@code Acc(A)} is a subtype of {@code + * AccPred("A || B")}, because when A is replaced with {@code true} and B is replaced with + * {@code false}, the resulting boolean formula evaluates to true. + *
    • A predicate P is a subtype of an accumulator iff after converting the accumulator into + * a predicate representing the conjunction of its elements, P is a subtype of that + * predicate according to the rule for subtyping between two predicates defined below. + *
    • A predicate P is a subtype of another predicate Q iff P and Q are equal. An extension + * point ({@link #isPredicateSubtype(String, String)}) is provided to allow more complex + * subtyping behavior between predicates. (The "correct" subtyping rule is that P is a + * subtype of Q iff P implies Q. That rule would require an SMT solver in the general + * case, which is undesirable because it would require an external dependency. A user can + * override {@link #isPredicateSubtype(String, String)} if they require more precise + * subtyping; the check described here is overly conservative (and therefore sound), but + * not very precise.) + *
    */ protected class AccumulationQualifierHierarchy extends MultiGraphQualifierHierarchy { @@ -276,6 +368,20 @@ public AnnotationMirror greatestLowerBound( return bottom; } + // If either is a predicate, then both should be converted to predicates and and-ed. + if (isPredicate(a1) || isPredicate(a2)) { + String a1Pred = convertToPredicate(a1); + String a2Pred = convertToPredicate(a2); + // check for top + if (a1Pred.isEmpty()) { + return a2; + } else if (a2Pred.isEmpty()) { + return a1; + } else { + return createPredicateAnnotation("(" + a1Pred + ") && (" + a2Pred + ")"); + } + } + List a1Val = getAccumulatedValues(a1); List a2Val = getAccumulatedValues(a2); // Avoid creating new annotation objects in the common case. @@ -302,6 +408,20 @@ public AnnotationMirror leastUpperBound( return a1; } + // If either is a predicate, then both should be converted to predicates and or-ed. + if (isPredicate(a1) || isPredicate(a2)) { + String a1Pred = convertToPredicate(a1); + String a2Pred = convertToPredicate(a2); + // check for top + if (a1Pred.isEmpty()) { + return a1; + } else if (a2Pred.isEmpty()) { + return a2; + } else { + return createPredicateAnnotation("(" + a1Pred + ") || (" + a2Pred + ")"); + } + } + List a1Val = getAccumulatedValues(a1); List a2Val = getAccumulatedValues(a2); // Avoid creating new annotation objects in the common case. @@ -324,9 +444,180 @@ public boolean isSubtype(final AnnotationMirror subAnno, final AnnotationMirror return false; } + if (isPredicate(subAnno)) { + return isPredicateSubtype( + convertToPredicate(subAnno), convertToPredicate(superAnno)); + } else if (isPredicate(superAnno)) { + return evaluatePredicate(subAnno, convertToPredicate(superAnno)); + } + List subVal = getAccumulatedValues(subAnno); List superVal = getAccumulatedValues(superAnno); return subVal.containsAll(superVal); } } + + /** + * Extension point for subtyping behavior between predicates. This implementation conservatively + * returns true only if the predicates are equal, or if the prospective supertype (q) is + * equivalent to top (that is, the empty string). + * + * @param p a predicate + * @param q another predicate + * @return true if p is a subtype of q + */ + protected boolean isPredicateSubtype(String p, String q) { + return "".equals(q) || p.equals(q); + } + + /** + * Evaluates whether the accumulator annotation {@code subAnno} makes the predicate {@code pred} + * true. + * + * @param subAnno an accumulator annotation + * @param pred a predicate + * @return whether the accumulator annotation satisfies the predicate + */ + protected boolean evaluatePredicate(AnnotationMirror subAnno, String pred) { + if (!isAccumulatorAnnotation(subAnno)) { + throw new BugInCF( + "tried to evaluate a predicate using an annotation that wasn't an accumulator: " + + subAnno); + } + List trueVariables = getAccumulatedValues(subAnno); + return evaluatePredicate(trueVariables, pred); + } + + /** + * Checks that the given annotation either: + * + *
      + *
    • does not contain a predicate, or + *
    • contains a parse-able predicate + *
    + * + * Used by the visitor to throw "predicate.invalid" errors; thus must be package-private. + * + * @param anm any annotation supported by this checker + * @return null if there is nothing wrong with the annotation, or an error message indicating + * the problem if it has an invalid predicate + */ + /* package-private */ + @Nullable String isValidPredicate(AnnotationMirror anm) { + String pred = convertToPredicate(anm); + try { + evaluatePredicate(Collections.emptyList(), pred); + } catch (UserError ue) { + return ue.getLocalizedMessage(); + } + return null; + } + + /** + * Evaluates whether treating the variables in {@code trueVariables} as {@code true} literals + * (and all other names as {@code false} literals) makes the predicate {@code pred} evaluate to + * true. + * + * @param trueVariables a list of names that should be replaced with {@code true} + * @param pred a predicate + * @return whether the true variables satisfy the predicate + */ + protected boolean evaluatePredicate(List trueVariables, String pred) { + Expression expression; + try { + expression = StaticJavaParser.parseExpression(pred); + } catch (ParseProblemException p) { + throw new UserError("unparseable predicate: " + pred + ". Parse exception: " + p); + } + return evaluateBooleanExpression(expression, trueVariables); + } + + /** + * Evaluates a boolean expression, in JavaParser format, that contains only and, or, + * parentheses, logical complement, and boolean literal nodes. + * + * @param expression a JavaParser boolean expression + * @param trueVariables the names of the variables that should be considered "true"; all other + * literals are considered "false" + * @return the result of evaluating the expression + */ + private boolean evaluateBooleanExpression(Expression expression, List trueVariables) { + if (expression.isNameExpr()) { + return trueVariables.contains(expression.asNameExpr().getNameAsString()); + } else if (expression.isBinaryExpr()) { + if (expression.asBinaryExpr().getOperator() == BinaryExpr.Operator.OR) { + return evaluateBooleanExpression(expression.asBinaryExpr().getLeft(), trueVariables) + || evaluateBooleanExpression( + expression.asBinaryExpr().getRight(), trueVariables); + } else if (expression.asBinaryExpr().getOperator() == BinaryExpr.Operator.AND) { + return evaluateBooleanExpression(expression.asBinaryExpr().getLeft(), trueVariables) + && evaluateBooleanExpression( + expression.asBinaryExpr().getRight(), trueVariables); + } + } else if (expression.isEnclosedExpr()) { + return evaluateBooleanExpression(expression.asEnclosedExpr().getInner(), trueVariables); + } else if (expression.isUnaryExpr()) { + if (expression.asUnaryExpr().getOperator() == UnaryExpr.Operator.LOGICAL_COMPLEMENT) { + return !evaluateBooleanExpression( + expression.asUnaryExpr().getExpression(), trueVariables); + } + } + // This could be a BugInCF if there is a bug in the code above. + throw new UserError( + "encountered an unexpected type of expression in a " + + "predicate expression: " + + expression + + " was of type " + + expression.getClass()); + } + + /** + * Creates a new predicate annotation from the given string. + * + * @param p a valid predicate + * @return an annotation representing that predicate + */ + protected AnnotationMirror createPredicateAnnotation(String p) { + AnnotationBuilder builder = new AnnotationBuilder(processingEnv, predicate); + builder.setValue("value", p); + return builder.build(); + } + + /** + * Converts the given annotation mirror to a predicate. + * + * @param anno an annotation + * @return the predicate, as a String, that is equivalent to that annotation. May return the + * empty string. + */ + protected String convertToPredicate(AnnotationMirror anno) { + if (AnnotationUtils.areSame(anno, bottom)) { + return "false"; + } else if (isPredicate(anno)) { + if (AnnotationUtils.hasElementValue(anno, "value")) { + return AnnotationUtils.getElementValue(anno, "value", String.class, false); + } else { + return ""; + } + } else if (isAccumulatorAnnotation(anno)) { + List values = getAccumulatedValues(anno); + StringJoiner sj = new StringJoiner(" && "); + for (String value : values) { + sj.add(value); + } + return sj.toString(); + } else { + throw new BugInCF("annotation is not bottom, a predicate, or an accumulator: " + anno); + } + } + + /** + * Returns true if anno is a predicate annotation. + * + * @param anno an annotation + * @return true if anno is a predicate annotation + */ + protected boolean isPredicate(AnnotationMirror anno) { + return predicate != null && AnnotationUtils.areSameByClass(anno, predicate); + } } diff --git a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationTransfer.java b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationTransfer.java index 4e22e6f1634d..293c21845240 100644 --- a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationTransfer.java +++ b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationTransfer.java @@ -83,13 +83,16 @@ public void accumulate(Node node, TransferResult result, Strin Set flowAnnos = flowValue.getAnnotations(); assert flowAnnos.size() <= 1; for (AnnotationMirror anno : flowAnnos) { - List oldFlowValues = - ValueCheckerUtils.getValueOfAnnotationWithStringArgument(anno); - if (oldFlowValues != null) { - // valuesAsList cannot have its length changed -- it is backed by an array. - // getValueOfAnnotationWithStringArgument returns a new, modifiable list. - oldFlowValues.addAll(valuesAsList); - valuesAsList = oldFlowValues; + if (typeFactory.isAccumulatorAnnotation(anno)) { + List oldFlowValues = + ValueCheckerUtils.getValueOfAnnotationWithStringArgument(anno); + if (oldFlowValues != null) { + // valuesAsList cannot have its length changed -- it is backed by an + // array. getValueOfAnnotationWithStringArgument returns a new, + // modifiable list. + oldFlowValues.addAll(valuesAsList); + valuesAsList = oldFlowValues; + } } } } diff --git a/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationVisitor.java b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationVisitor.java new file mode 100644 index 000000000000..12bc8ba7e4d8 --- /dev/null +++ b/framework/src/main/java/org/checkerframework/common/accumulation/AccumulationVisitor.java @@ -0,0 +1,36 @@ +package org.checkerframework.common.accumulation; + +import com.sun.source.tree.AnnotationTree; +import javax.lang.model.element.AnnotationMirror; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.common.basetype.BaseTypeVisitor; +import org.checkerframework.javacutil.TreeUtils; + +/** + * The visitor for an accumulation checker. Issues predicate.invalid errors if the user writes an + * invalid predicate. + */ +public class AccumulationVisitor extends BaseTypeVisitor { + + /** + * Constructor matching super. + * + * @param checker the checker + */ + public AccumulationVisitor(BaseTypeChecker checker) { + super(checker); + } + + /** Checks each predicate annotation to make sure the predicate is well-formed. */ + @Override + public Void visitAnnotation(final AnnotationTree node, final Void p) { + AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(node); + if (atypeFactory.isPredicate(anno)) { + String errorMessage = atypeFactory.isValidPredicate(anno); + if (errorMessage != null) { + checker.reportError(node, "predicate.invalid", errorMessage); + } + } + return super.visitAnnotation(node, p); + } +} diff --git a/framework/src/main/java/org/checkerframework/common/accumulation/messages.properties b/framework/src/main/java/org/checkerframework/common/accumulation/messages.properties new file mode 100644 index 000000000000..9dab1ab673ee --- /dev/null +++ b/framework/src/main/java/org/checkerframework/common/accumulation/messages.properties @@ -0,0 +1 @@ +predicate.invalid=Unparseable predicate. Predicates must be produced by this grammar: S --> method name | (S) | S && S | S || S. The message from the evaluator was: %s diff --git a/framework/src/test/java/testaccumulation/TestAccumulationAnnotatedTypeFactory.java b/framework/src/test/java/testaccumulation/TestAccumulationAnnotatedTypeFactory.java index 76fc374c37d9..f6e67f2f1755 100644 --- a/framework/src/test/java/testaccumulation/TestAccumulationAnnotatedTypeFactory.java +++ b/framework/src/test/java/testaccumulation/TestAccumulationAnnotatedTypeFactory.java @@ -10,6 +10,7 @@ import org.checkerframework.javacutil.TreeUtils; import testaccumulation.qual.TestAccumulation; import testaccumulation.qual.TestAccumulationBottom; +import testaccumulation.qual.TestAccumulationPredicate; /** * The annotated type factory for a test accumulation checker, which implements a basic called @@ -22,7 +23,11 @@ public class TestAccumulationAnnotatedTypeFactory extends AccumulationAnnotatedT * @param checker the checker */ public TestAccumulationAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker, TestAccumulation.class, TestAccumulationBottom.class); + super( + checker, + TestAccumulation.class, + TestAccumulationBottom.class, + TestAccumulationPredicate.class); this.postInit(); } diff --git a/framework/src/test/java/testaccumulation/qual/TestAccumulationBottom.java b/framework/src/test/java/testaccumulation/qual/TestAccumulationBottom.java index 46eadffed139..f78036104327 100644 --- a/framework/src/test/java/testaccumulation/qual/TestAccumulationBottom.java +++ b/framework/src/test/java/testaccumulation/qual/TestAccumulationBottom.java @@ -9,7 +9,7 @@ import org.checkerframework.framework.qual.TypeUseLocation; /** A test bottom type for an accumulation type system. */ -@SubtypeOf({TestAccumulation.class}) +@SubtypeOf({TestAccumulation.class, TestAccumulationPredicate.class}) @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND}) diff --git a/framework/src/test/java/testaccumulation/qual/TestAccumulationPredicate.java b/framework/src/test/java/testaccumulation/qual/TestAccumulationPredicate.java new file mode 100644 index 000000000000..4afc02f8cda3 --- /dev/null +++ b/framework/src/test/java/testaccumulation/qual/TestAccumulationPredicate.java @@ -0,0 +1,15 @@ +package testaccumulation.qual; + +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; + +/** A test accumulation predicate annotation. */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) +@SubtypeOf({TestAccumulation.class}) +public @interface TestAccumulationPredicate { + String value(); +} diff --git a/framework/src/test/java/tests/AccumulationTest.java b/framework/src/test/java/tests/AccumulationTest.java index 40d01c426650..94527f043e15 100644 --- a/framework/src/test/java/tests/AccumulationTest.java +++ b/framework/src/test/java/tests/AccumulationTest.java @@ -14,7 +14,13 @@ public class AccumulationTest extends FrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public AccumulationTest(List testFiles) { - super(testFiles, TestAccumulationChecker.class, "accumulation", "-Anomsgtext"); + super( + testFiles, + TestAccumulationChecker.class, + "accumulation", + "-Anomsgtext", + "-encoding", + "UTF-8"); } @Parameters diff --git a/framework/tests/accumulation/Not.java b/framework/tests/accumulation/Not.java new file mode 100644 index 000000000000..4bcbacb87a6e --- /dev/null +++ b/framework/tests/accumulation/Not.java @@ -0,0 +1,75 @@ +import testaccumulation.qual.*; + +class Not { + + class Foo { + void a() {} + + void b() {} + + void c() {} + + void notA(@TestAccumulationPredicate("!a") Foo this) {} + + void notB(@TestAccumulationPredicate("!b") Foo this) {} + } + + void test1(Foo f) { + f.notA(); + f.notB(); + } + + void test2(Foo f) { + f.c(); + f.notA(); + f.notB(); + } + + void test3(Foo f) { + f.a(); + // :: error: method.invocation.invalid + f.notA(); + f.notB(); + } + + void test4(Foo f) { + f.b(); + f.notA(); + // :: error: method.invocation.invalid + f.notB(); + } + + void test5(Foo f) { + f.a(); + f.b(); + // :: error: method.invocation.invalid + f.notA(); + // :: error: method.invocation.invalid + f.notB(); + } + + void callA(Foo f) { + f.a(); + } + + void test6(Foo f) { + callA(f); + // DEMONSTRATION OF "UNSOUNDNESS" + f.notA(); + } + + void test7(@TestAccumulation("a") Foo f) { + // :: error: method.invocation.invalid + f.notA(); + } + + void test8(Foo f, boolean test) { + if (test) { + f.a(); + } else { + f.b(); + } + // DEMONSTRATION OF "UNSOUNDNESS" + f.notA(); + } +} diff --git a/framework/tests/accumulation/Predicates.java b/framework/tests/accumulation/Predicates.java new file mode 100644 index 000000000000..0aec31dae322 --- /dev/null +++ b/framework/tests/accumulation/Predicates.java @@ -0,0 +1,634 @@ +import testaccumulation.qual.*; + +class Predicates { + + void testOr1() { + MyClass m1 = new MyClass(); + + // :: error: method.invocation.invalid + m1.c(); + } + + void testOr2() { + MyClass m1 = new MyClass(); + + m1.a(); + m1.c(); + } + + void testOr3() { + MyClass m1 = new MyClass(); + + m1.b(); + m1.c(); + } + + void testAnd1() { + MyClass m1 = new MyClass(); + + // :: error: method.invocation.invalid + m1.d(); + } + + void testAnd2() { + MyClass m1 = new MyClass(); + + m1.a(); + // :: error: method.invocation.invalid + m1.d(); + } + + void testAnd3() { + MyClass m1 = new MyClass(); + + m1.b(); + // :: error: method.invocation.invalid + m1.d(); + } + + void testAnd4() { + MyClass m1 = new MyClass(); + + m1.a(); + m1.c(); + // :: error: method.invocation.invalid + m1.d(); + } + + void testAnd5() { + MyClass m1 = new MyClass(); + + m1.a(); + m1.b(); + m1.d(); + } + + void testAnd6() { + MyClass m1 = new MyClass(); + + m1.a(); + m1.b(); + m1.c(); + m1.d(); + } + + void testAndOr1() { + MyClass m1 = new MyClass(); + + // :: error: method.invocation.invalid + m1.e(); + } + + void testAndOr2() { + MyClass m1 = new MyClass(); + + m1.a(); + m1.e(); + } + + void testAndOr3() { + MyClass m1 = new MyClass(); + + m1.b(); + // :: error: method.invocation.invalid + m1.e(); + } + + void testAndOr4() { + MyClass m1 = new MyClass(); + + m1.b(); + m1.c(); + m1.e(); + } + + void testAndOr5() { + MyClass m1 = new MyClass(); + + m1.a(); + m1.b(); + m1.c(); + m1.d(); + m1.e(); + } + + void testPrecedence1() { + MyClass m1 = new MyClass(); + + // :: error: method.invocation.invalid + m1.f(); + } + + void testPrecedence2() { + MyClass m1 = new MyClass(); + + m1.a(); + // :: error: method.invocation.invalid + m1.f(); + } + + void testPrecedence3() { + MyClass m1 = new MyClass(); + + m1.b(); + // :: error: method.invocation.invalid + m1.f(); + } + + void testPrecedence4() { + MyClass m1 = new MyClass(); + + m1.a(); + m1.b(); + m1.f(); + } + + void testPrecedence5() { + MyClass m1 = new MyClass(); + + m1.a(); + m1.c(); + m1.f(); + } + + void testPrecedence6() { + MyClass m1 = new MyClass(); + + m1.b(); + m1.c(); + m1.f(); + } + + private static class MyClass { + + @TestAccumulation("a") + MyClass cmA; + + @TestAccumulationPredicate("a") + MyClass cmpA; + + @TestAccumulation({"a", "b"}) + MyClass aB; + + @TestAccumulationPredicate("a || b") + MyClass aOrB; + + @TestAccumulationPredicate("a && b") + MyClass aAndB; + + @TestAccumulationPredicate("a || b && c") + MyClass bAndCOrA; + + @TestAccumulationPredicate("a || (b && c)") + MyClass bAndCOrAParens; + + @TestAccumulationPredicate("a && b || c") + MyClass aAndBOrC; + + @TestAccumulationPredicate("(a && b) || c") + MyClass aAndBOrCParens; + + @TestAccumulationPredicate("(a || b) && c") + MyClass aOrBAndC; + + @TestAccumulationPredicate("a && (b || c)") + MyClass bOrCAndA; + + @TestAccumulationPredicate("b && c") + MyClass bAndC; + + @TestAccumulationPredicate("(b && c)") + MyClass bAndCParens; + + void a() {} + + void b() {} + + void c(@TestAccumulationPredicate("a || b") MyClass this) {} + + void d(@TestAccumulationPredicate("a && b") MyClass this) {} + + void e(@TestAccumulationPredicate("a || (b && c)") MyClass this) {} + + void f(@TestAccumulationPredicate("a && b || c") MyClass this) {} + + static void testAssignability1(@TestAccumulationPredicate("a || b") MyClass cAble) { + cAble.c(); + // :: error: method.invocation.invalid + cAble.d(); + // :: error: method.invocation.invalid + cAble.e(); + // :: error: method.invocation.invalid + cAble.f(); + } + + static void testAssignability2(@TestAccumulationPredicate("a && b") MyClass dAble) { + // These calls would work if subtyping between predicates was by implication. They issue + // errors, because + // it is not. + // :: error: method.invocation.invalid + dAble.c(); + dAble.d(); + // :: error: method.invocation.invalid + dAble.e(); + // :: error: method.invocation.invalid + dAble.f(); + } + + void testAllAssignability() { + + @TestAccumulation("a") + MyClass cmALocal; + @TestAccumulationPredicate("a") + MyClass cmpALocal; + @TestAccumulationPredicate("a || b") + MyClass aOrBLocal; + @TestAccumulation({"a", "b"}) + MyClass aBLocal; + @TestAccumulationPredicate("a && b") + MyClass aAndBLocal; + @TestAccumulationPredicate("a || b && c") + MyClass bAndCOrALocal; + @TestAccumulationPredicate("a || (b && c)") + MyClass bAndCOrAParensLocal; + @TestAccumulationPredicate("a && b || c") + MyClass aAndBOrCLocal; + @TestAccumulationPredicate("(a && b) || c") + MyClass aAndBOrCParensLocal; + @TestAccumulationPredicate("(a || b) && c") + MyClass aOrBAndCLocal; + @TestAccumulationPredicate("a && (b || c)") + MyClass bOrCAndALocal; + @TestAccumulationPredicate("b && c") + MyClass bAndCLocal; + @TestAccumulationPredicate("(b && c)") + MyClass bAndCParensLocal; + + cmALocal = cmA; + cmALocal = cmpA; + // :: error: assignment.type.incompatible + cmALocal = aOrB; + cmALocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + cmALocal = aAndB; + // :: error: assignment.type.incompatible + cmALocal = bAndCOrA; + // :: error: assignment.type.incompatible + cmALocal = bAndCOrAParens; + // :: error: assignment.type.incompatible + cmALocal = aAndBOrC; + // :: error: assignment.type.incompatible + cmALocal = aAndBOrCParens; + // :: error: assignment.type.incompatible + cmALocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + cmALocal = bOrCAndA; + // :: error: assignment.type.incompatible + cmALocal = bAndC; + // :: error: assignment.type.incompatible + cmALocal = bAndCParens; + + cmpALocal = cmA; + cmpALocal = cmpA; + // :: error: assignment.type.incompatible + cmpALocal = aOrB; + cmpALocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + cmpALocal = aAndB; + // :: error: assignment.type.incompatible + cmpALocal = bAndCOrA; + // :: error: assignment.type.incompatible + cmpALocal = bAndCOrAParens; + // :: error: assignment.type.incompatible + cmpALocal = aAndBOrC; + // :: error: assignment.type.incompatible + cmpALocal = aAndBOrCParens; + // :: error: assignment.type.incompatible + cmpALocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + cmpALocal = bOrCAndA; + // :: error: assignment.type.incompatible + cmpALocal = bAndC; + // :: error: assignment.type.incompatible + cmpALocal = bAndCParens; + + aOrBLocal = cmA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = cmpA; + aOrBLocal = aOrB; + aOrBLocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = aAndB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = bAndCOrA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = bAndCOrAParens; + // :: error: assignment.type.incompatible + aOrBLocal = aAndBOrC; + // :: error: assignment.type.incompatible + aOrBLocal = aAndBOrCParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + aBLocal = cmA; + // :: error: (assignment.type.incompatible) + aBLocal = cmpA; + // :: error: (assignment.type.incompatible) + aBLocal = aOrB; + aBLocal = aB; + aBLocal = aAndB; + // :: error: (assignment.type.incompatible) + aBLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + aBLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + aBLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + aBLocal = aAndBOrCParens; + // :: error: (assignment.type.incompatible) + aBLocal = aOrBAndC; + // :: error: (assignment.type.incompatible) + aBLocal = bOrCAndA; + // :: error: (assignment.type.incompatible) + aBLocal = bAndC; + // :: error: (assignment.type.incompatible) + aBLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + aAndBLocal = cmA; + // :: error: (assignment.type.incompatible) + aAndBLocal = cmpA; + // :: error: (assignment.type.incompatible) + aAndBLocal = aOrB; + aAndBLocal = aB; + aAndBLocal = aAndB; + // :: error: (assignment.type.incompatible) + aAndBLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + aAndBLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + aAndBLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + aAndBLocal = aAndBOrCParens; + // :: error: (assignment.type.incompatible) + aAndBLocal = aOrBAndC; + // :: error: (assignment.type.incompatible) + aAndBLocal = bOrCAndA; + // :: error: (assignment.type.incompatible) + aAndBLocal = bAndC; + // :: error: (assignment.type.incompatible) + aAndBLocal = bAndCParens; + + bAndCOrALocal = cmA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = cmpA; + // :: error: (assignment.type.incompatible) + bAndCOrALocal = aOrB; + bAndCOrALocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = aAndB; + bAndCOrALocal = bAndCOrA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + bAndCOrALocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + bAndCOrALocal = aAndBOrCParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrALocal = bAndCParens; + + bAndCOrAParensLocal = cmA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = cmpA; + // :: error: (assignment.type.incompatible) + bAndCOrAParensLocal = aOrB; + bAndCOrAParensLocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = aAndB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = bAndCOrA; + bAndCOrAParensLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + bAndCOrAParensLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + bAndCOrAParensLocal = aAndBOrCParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCOrAParensLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + aAndBOrCLocal = cmA; + // :: error: (assignment.type.incompatible) + aAndBOrCLocal = cmpA; + // :: error: (assignment.type.incompatible) + aAndBOrCLocal = aOrB; + aAndBOrCLocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = aAndB; + // :: error: (assignment.type.incompatible) + aAndBOrCLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + aAndBOrCLocal = bAndCOrAParens; + aAndBOrCLocal = aAndBOrC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = aAndBOrCParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + aAndBOrCParensLocal = cmA; + // :: error: (assignment.type.incompatible) + aAndBOrCParensLocal = cmpA; + // :: error: (assignment.type.incompatible) + aAndBOrCParensLocal = aOrB; + aAndBOrCParensLocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = aAndB; + // :: error: (assignment.type.incompatible) + aAndBOrCParensLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + aAndBOrCParensLocal = bAndCOrAParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = aAndBOrC; + aAndBOrCParensLocal = aAndBOrCParens; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = aOrBAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aAndBOrCParensLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = cmA; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = cmpA; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = aOrB; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = aB; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = aAndB; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = aAndBOrCParens; + aOrBAndCLocal = aOrBAndC; + // :: error: (assignment.type.incompatible) + aOrBAndCLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBAndCLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + aOrBAndCLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + bOrCAndALocal = cmA; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = cmpA; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = aOrB; + bOrCAndALocal = aB; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bOrCAndALocal = aAndB; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = aAndBOrCParens; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = aOrBAndC; + bOrCAndALocal = bOrCAndA; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = bAndC; + // :: error: (assignment.type.incompatible) + bOrCAndALocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + bAndCLocal = cmA; + // :: error: (assignment.type.incompatible) + bAndCLocal = cmpA; + // :: error: (assignment.type.incompatible) + bAndCLocal = aOrB; + // :: error: (assignment.type.incompatible) + bAndCLocal = aB; + // :: error: (assignment.type.incompatible) + bAndCLocal = aAndB; + // :: error: (assignment.type.incompatible) + bAndCLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + bAndCLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + bAndCLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + bAndCLocal = aAndBOrCParens; + // :: error: (assignment.type.incompatible) + bAndCLocal = aOrBAndC; + // :: error: (assignment.type.incompatible) + bAndCLocal = bOrCAndA; + bAndCLocal = bAndC; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCLocal = bAndCParens; + + // :: error: (assignment.type.incompatible) + bAndCParensLocal = cmA; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = cmpA; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aOrB; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aB; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aAndB; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = bAndCOrA; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = bAndCOrAParens; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aAndBOrC; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aAndBOrCParens; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = aOrBAndC; + // :: error: (assignment.type.incompatible) + bAndCParensLocal = bOrCAndA; + // The next line would not fail if predicate subtyping was decided by implication. + // :: error: assignment.type.incompatible + bAndCParensLocal = bAndC; + bAndCParensLocal = bAndCParens; + } + } +} diff --git a/framework/tests/accumulation/SimpleInferenceMerge.java b/framework/tests/accumulation/SimpleInferenceMerge.java new file mode 100644 index 000000000000..ffa3f0a56ab1 --- /dev/null +++ b/framework/tests/accumulation/SimpleInferenceMerge.java @@ -0,0 +1,37 @@ +import testaccumulation.qual.*; + +class SimpleInferenceMerge { + void build(@TestAccumulation({"a", "b"}) SimpleInferenceMerge this) {} + + void a() {} + + void b() {} + + void c() {} + + static void doStuffCorrectMerge(boolean b) { + SimpleInferenceMerge s = new SimpleInferenceMerge(); + if (b) { + s.a(); + s.b(); + } else { + s.b(); + s.a(); + s.c(); + } + s.build(); + } + + static void doStuffWrongMerge(boolean b) { + SimpleInferenceMerge s = new SimpleInferenceMerge(); + if (b) { + s.a(); + s.b(); + } else { + s.b(); + s.c(); + } + // :: error: method.invocation.invalid + s.build(); + } +} diff --git a/framework/tests/accumulation/SmallPredicate.java b/framework/tests/accumulation/SmallPredicate.java new file mode 100644 index 000000000000..9f463564fa99 --- /dev/null +++ b/framework/tests/accumulation/SmallPredicate.java @@ -0,0 +1,19 @@ +// small test case for predicates, for debugging + +import testaccumulation.qual.*; + +class SmallPredicate { + void a() {} + + void b() {} + + void d(@TestAccumulationPredicate("a && b") SmallPredicate this) {} + + static void test(SmallPredicate smallPredicate) { + smallPredicate.a(); + smallPredicate.b(); + @TestAccumulation({"a", "b"}) + SmallPredicate p2 = smallPredicate; + smallPredicate.d(); + } +} diff --git a/framework/tests/accumulation/UnparseablePredicate.java b/framework/tests/accumulation/UnparseablePredicate.java new file mode 100644 index 000000000000..01442c3a4de8 --- /dev/null +++ b/framework/tests/accumulation/UnparseablePredicate.java @@ -0,0 +1,50 @@ +import testaccumulation.qual.*; + +class UnparseablePredicate { + + // :: error: predicate.invalid + void unclosedOpen(@TestAccumulationPredicate("(foo && bar") Object x) {} + + // :: error: predicate.invalid + void unopenedClose(@TestAccumulationPredicate("foo || bar)") Object x) {} + + // :: error: predicate.invalid + void badKeywords1(@TestAccumulationPredicate("foo OR bar") Object x) {} + + // :: error: predicate.invalid + void badKeywords2(@TestAccumulationPredicate("foo AND bar") Object x) {} + + // These tests check that valid java identifiers don't cause problems + // when evaluating predicates. Examples of identifiers from + // https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.8 + + void jls0Example(@TestAccumulationPredicate("String") Object x) {} + + void callJls0Example(@TestAccumulation("String") Object y) { + jls0Example(y); + } + + void jls1Example(@TestAccumulationPredicate("i3") Object x) {} + + void callJls1Example(@TestAccumulation("i3") Object y) { + jls1Example(y); + } + + void jls2Example(@TestAccumulationPredicate("αρετη") Object x) {} + + void callJls2Example(@TestAccumulation("αρετη") Object y) { + jls2Example(y); + } + + void jls3Example(@TestAccumulationPredicate("MAX_VALUE") Object x) {} + + void callJls3Example(@TestAccumulation("MAX_VALUE") Object y) { + jls3Example(y); + } + + void jls4Example(@TestAccumulationPredicate("isLetterOrDigit") Object x) {} + + void callJls4Example(@TestAccumulation("isLetterOrDigit") Object y) { + jls4Example(y); + } +} diff --git a/framework/tests/accumulation/Xor.java b/framework/tests/accumulation/Xor.java new file mode 100644 index 000000000000..c92f6307a875 --- /dev/null +++ b/framework/tests/accumulation/Xor.java @@ -0,0 +1,64 @@ +import testaccumulation.qual.*; + +class Xor { + + class Foo { + void a() {} + + void b() {} + + void c() {} + // use a standard gate encoding for xor + void aXorB(@TestAccumulationPredicate("(a || b) && !(a && b)") Foo this) {} + } + + void test1(Foo f) { + // :: error: method.invocation.invalid + f.aXorB(); + } + + void test2(Foo f) { + f.c(); + // :: error: method.invocation.invalid + f.aXorB(); + } + + void test3(Foo f) { + f.a(); + f.aXorB(); + } + + void test4(Foo f) { + f.b(); + f.aXorB(); + } + + void test5(Foo f) { + f.a(); + f.b(); + // :: error: method.invocation.invalid + f.aXorB(); + } + + void callA(Foo f) { + f.a(); + } + + void test6(Foo f) { + callA(f); + f.b(); + // DEMONSTRATION OF "UNSOUNDNESS" + f.aXorB(); + } + + void test7(@TestAccumulation("a") Foo f) { + f.aXorB(); + } + + void test8(Foo f) { + callA(f); + // THIS IS AN UNAVOIDABLE FALSE POSITIVE + // :: error: method.invocation.invalid + f.aXorB(); + } +} From fb533f7306b53f5de7b59092a3233b42628fce06 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 21 Jul 2020 11:48:06 -0700 Subject: [PATCH 065/138] Update makefile targets for Daikon type-checking --- checker/bin-devel/test-daikon-part1.sh | 4 ++-- checker/bin-devel/test-daikon-part2.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/checker/bin-devel/test-daikon-part1.sh b/checker/bin-devel/test-daikon-part1.sh index 671590df1a62..929917621306 100755 --- a/checker/bin-devel/test-daikon-part1.sh +++ b/checker/bin-devel/test-daikon-part1.sh @@ -19,7 +19,7 @@ git log | head -n 5 make compile if [ "$TRAVIS" = "true" ] ; then # Travis kills a job if it runs 10 minutes without output - time make JAVACHECK_EXTRA_ARGS=-Afilenames -C java check-part1 + time make JAVACHECK_EXTRA_ARGS=-Afilenames -C java typecheck-part1 else - time make -C java check-part1 + time make -C java typecheck-part1 fi diff --git a/checker/bin-devel/test-daikon-part2.sh b/checker/bin-devel/test-daikon-part2.sh index cb2de5bb6bf1..e43138233206 100755 --- a/checker/bin-devel/test-daikon-part2.sh +++ b/checker/bin-devel/test-daikon-part2.sh @@ -19,7 +19,7 @@ git log | head -n 5 make compile if [ "$TRAVIS" = "true" ] ; then # Travis kills a job if it runs 10 minutes without output - time make JAVACHECK_EXTRA_ARGS=-Afilenames -C java check-part2 + time make JAVACHECK_EXTRA_ARGS=-Afilenames -C java typecheck-part2 else - time make -C java check-part2 + time make -C java typecheck-part2 fi From 7c5bf4b627842570218d088d1a029c74356b3c58 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 21 Jul 2020 12:44:22 -0700 Subject: [PATCH 066/138] Reduce duplication in comments --- .../dataflow/analysis/TransferInput.java | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferInput.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferInput.java index 6682873967de..f6b47e20e3d7 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferInput.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferInput.java @@ -22,8 +22,8 @@ public class TransferInput, S extends Store> { protected @Nullable Node node; /** - * The regular result store (or {@code null} if none is present). The following invariant is - * maintained: + * The regular result store (or {@code null} if none is present, because {@link #thenStore} and + * {@link #elseStore} are set). The following invariant is maintained: * *
    
          * store == null ⇔ thenStore != null && elseStore != null
    @@ -32,22 +32,14 @@ public class TransferInput, S extends Store> {
         protected final @Nullable S store;
     
         /**
    -     * The 'then' result store (or {@code null} if none is present). The following invariant is
    -     * maintained:
    -     *
    -     * 
    
    -     * store == null ⇔ thenStore != null && elseStore != null
    -     * 
    + * The 'then' result store (or {@code null} if none is present). See invariant at {@link + * #store}. */ protected final @Nullable S thenStore; /** - * The 'else' result store (or {@code null} if none is present). The following invariant is - * maintained: - * - *
    
    -     * store == null ⇔ thenStore != null && elseStore != null
    -     * 
    + * The 'else' result store (or {@code null} if none is present). See invariant at {@link + * #store}. */ protected final @Nullable S elseStore; @@ -221,7 +213,7 @@ public S getElseStore() { * potentially not equal */ public boolean containsTwoStores() { - return (thenStore != null && elseStore != null); + return store == null; } /** From 213e2bd56770f4610947577b204e459f40afa8aa Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 21 Jul 2020 13:25:07 -0700 Subject: [PATCH 067/138] Remove needless method argument --- .../initialization/InitializationTransfer.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationTransfer.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationTransfer.java index b539d07bb12f..d60ac81e2962 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationTransfer.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationTransfer.java @@ -13,7 +13,6 @@ import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; -import org.checkerframework.dataflow.analysis.ConditionalTransferResult; import org.checkerframework.dataflow.analysis.FlowExpressions; import org.checkerframework.dataflow.analysis.FlowExpressions.FieldAccess; import org.checkerframework.dataflow.analysis.FlowExpressions.Receiver; @@ -86,9 +85,11 @@ protected boolean isNotFullyInitializedReceiver(MethodTree methodTree) { /** * Returns the fields that can safely be considered initialized after the method call {@code * node}. + * + * @param node a method call + * @return the fields that are initialized after the method call */ - protected List initializedFieldsAfterCall( - MethodInvocationNode node, ConditionalTransferResult transferResult) { + protected List initializedFieldsAfterCall(MethodInvocationNode node) { List result = new ArrayList<>(); MethodInvocationTree tree = node.getTree(); ExecutableElement method = TreeUtils.elementFromUse(tree); @@ -194,9 +195,7 @@ public TransferResult visitFieldAccess(FieldAccessNode n, TransferInput visitMethodInvocation( MethodInvocationNode n, TransferInput in) { TransferResult result = super.visitMethodInvocation(n, in); - assert result instanceof ConditionalTransferResult; - List newlyInitializedFields = - initializedFieldsAfterCall(n, (ConditionalTransferResult) result); + List newlyInitializedFields = initializedFieldsAfterCall(n); if (!newlyInitializedFields.isEmpty()) { for (VariableElement f : newlyInitializedFields) { result.getThenStore().addInitializedField(f); From 13261eaa769f855fcae226ae9d34bf727d851b89 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 21 Jul 2020 15:12:29 -0700 Subject: [PATCH 068/138] Alphabetize dependencies --- framework/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/build.gradle b/framework/build.gradle index 5c7d31c97274..a0737a00ede5 100644 --- a/framework/build.gradle +++ b/framework/build.gradle @@ -33,8 +33,8 @@ dependencies { exclude group: 'com.google.errorprone', module: 'javac' } implementation project(':checker-qual') - implementation 'org.plumelib:reflection-util:0.2.2' implementation 'org.plumelib:plume-util:1.1.4' + implementation 'org.plumelib:reflection-util:0.2.2' // TODO: org.checkerframework.annotatedlib:guava:28.2-jre requires the below dependency. // Get the version number from the "compile dependencies" section of From 8ec9df3a16cb69824c6654b0f9f0dec9d17316c1 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 21 Jul 2020 15:12:41 -0700 Subject: [PATCH 069/138] Another example of a method that throws if its argument is null --- docs/manual/introduction.tex | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/manual/introduction.tex b/docs/manual/introduction.tex index 9a2d0327cd2c..df7af6e40853 100644 --- a/docs/manual/introduction.tex +++ b/docs/manual/introduction.tex @@ -1219,6 +1219,7 @@ \begin{Verbatim} java.lang.Double.valueOf(String) java.lang.String.contains(CharSequence) + java.lang.Object.checkNotNull(Object) org.junit.Assert.assertNotNull(Object) com.google.common.base.Preconditions.checkNotNull(Object) \end{Verbatim} From c44ded45be4a22a40a996b1d1a9b433d60c49d64 Mon Sep 17 00:00:00 2001 From: aditya3434 <56246494+aditya3434@users.noreply.github.com> Date: Wed, 22 Jul 2020 04:37:53 +0530 Subject: [PATCH 070/138] Support unique classes (Fixes #3313) (#3336) --- .../common/aliasing/AliasingVisitor.java | 33 +++++++++++++++++-- .../src/test/java/tests/AliasingTest.java | 3 +- .../aliasing/ExplicitAnnotationTest.java | 15 +++++++++ framework/tests/aliasing/stubfile.astub | 18 ---------- .../tests/annotationclassloader/Makefile | 4 --- 5 files changed, 47 insertions(+), 26 deletions(-) create mode 100644 framework/tests/aliasing/ExplicitAnnotationTest.java delete mode 100644 framework/tests/aliasing/stubfile.astub diff --git a/framework/src/main/java/org/checkerframework/common/aliasing/AliasingVisitor.java b/framework/src/main/java/org/checkerframework/common/aliasing/AliasingVisitor.java index e0cde13e2b0c..32e9ee3a71af 100644 --- a/framework/src/main/java/org/checkerframework/common/aliasing/AliasingVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/aliasing/AliasingVisitor.java @@ -10,6 +10,9 @@ import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; import java.util.List; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.VariableElement; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; @@ -22,6 +25,7 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.TreeUtils; /** @@ -267,7 +271,9 @@ protected void checkThisOrSuperConstructorCall( /** * Returns true if {@code exp} has type {@code @Unique} and is not a method invocation nor a new - * class expression. + * class expression. It checks whether the tree expression is unique by either checking for an + * explicit annotation or checking whether the class of the tree expression {@code exp} has type + * {@code @Unique} * * @param exp the Tree to check */ @@ -275,7 +281,30 @@ private boolean canBeLeaked(Tree exp) { AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(exp); boolean isMethodInvocation = exp.getKind() == Kind.METHOD_INVOCATION; boolean isNewClass = exp.getKind() == Kind.NEW_CLASS; - return type.hasExplicitAnnotation(Unique.class) && !isMethodInvocation && !isNewClass; + boolean isUniqueType = isUniqueClass(type) || type.hasExplicitAnnotation(Unique.class); + return isUniqueType && !isMethodInvocation && !isNewClass; + } + + /** + * Return true if the class declaration for annotated type {@code type} has annotation + * {@code @Unique}. + * + * @param type the annotated type whose class must be checked + * @return boolean true if class is unique and false otherwise + */ + private boolean isUniqueClass(AnnotatedTypeMirror type) { + Element el = types.asElement(type.getUnderlyingType()); + if (el == null) { + return false; + } + Set annoMirrors = atypeFactory.getDeclAnnotations(el); + if (annoMirrors == null) { + return false; + } + if (AnnotationUtils.containsSameByClass(annoMirrors, Unique.class)) { + return true; + } + return false; } private boolean isInUniqueConstructor() { diff --git a/framework/src/test/java/tests/AliasingTest.java b/framework/src/test/java/tests/AliasingTest.java index 96783ceca398..246b8857d405 100644 --- a/framework/src/test/java/tests/AliasingTest.java +++ b/framework/src/test/java/tests/AliasingTest.java @@ -13,8 +13,7 @@ public AliasingTest(List testFiles) { testFiles, org.checkerframework.common.aliasing.AliasingChecker.class, "aliasing", - "-Anomsgtext", - "-Astubs=tests/aliasing/stubfile.astub"); + "-Anomsgtext"); } @Parameters diff --git a/framework/tests/aliasing/ExplicitAnnotationTest.java b/framework/tests/aliasing/ExplicitAnnotationTest.java new file mode 100644 index 000000000000..dac83b177d1b --- /dev/null +++ b/framework/tests/aliasing/ExplicitAnnotationTest.java @@ -0,0 +1,15 @@ +import org.checkerframework.common.aliasing.qual.Unique; + +@Unique class Data { + @SuppressWarnings("unique.leaked") + Data() {} // All objects of Data are now @Unique +} + +class Demo { + void check(Data p) { // p is @Unique Data Object + // :: error: (unique.leaked) + Data y = p; // @Unique p is leaked + // :: error: (unique.leaked) + Object z = p; // @Unique p is leaked + } +} diff --git a/framework/tests/aliasing/stubfile.astub b/framework/tests/aliasing/stubfile.astub deleted file mode 100644 index cb5a0b93333d..000000000000 --- a/framework/tests/aliasing/stubfile.astub +++ /dev/null @@ -1,18 +0,0 @@ -import org.checkerframework.common.aliasing.qual.*; -package java.lang; -class String { - @Unique String(); -} - -class StringBuffer { - @Unique StringBuffer(); - StringBuffer append(@LeakedToResult StringBuffer this, @NonLeaked String s); -} - -class Exception { - @Unique Exception(); -} - -class Object { - @Unique Object(); -} diff --git a/framework/tests/annotationclassloader/Makefile b/framework/tests/annotationclassloader/Makefile index 56fa693f9cf8..1b84c4042863 100644 --- a/framework/tests/annotationclassloader/Makefile +++ b/framework/tests/annotationclassloader/Makefile @@ -37,7 +37,6 @@ demo1: -processor org.checkerframework.common.aliasing.AliasingChecker \ -Anomsgtext \ -ApermitMissingJdk \ - -Astubs=$(PROJECTDIR)/../aliasing/stubfile.astub \ LoaderTest.java # loads from framework.jar @@ -49,7 +48,6 @@ demo2: -processor org.checkerframework.common.aliasing.AliasingChecker \ -Anomsgtext \ -ApermitMissingJdk \ - -Astubs=$(PROJECTDIR)/../aliasing/stubfile.astub \ LoaderTest.java # ====================================================== @@ -61,7 +59,6 @@ load-from-dir-test: -classpath $(PROJECTDIR):${CHECKERQUALBUILD} \ -processor org.checkerframework.common.aliasing.AliasingChecker \ -Anomsgtext \ - -Astubs=$(PROJECTDIR)/../aliasing/stubfile.astub \ -ApermitMissingJdk \ LoaderTest.java > Out.txt 2>&1 || true diff -u Expected.txt Out.txt -I 'Note' @@ -75,7 +72,6 @@ load-from-jar-test: -processor org.checkerframework.common.aliasing.AliasingChecker \ -Anomsgtext \ -ApermitMissingJdk \ - -Astubs=$(PROJECTDIR)/../aliasing/stubfile.astub \ LoaderTest.java > Out.txt 2>&1 || true diff -u Expected.txt Out.txt rm -f Out.txt From eafe9156d854fe2d26ad04b16fad2fed7b2f5206 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 21 Jul 2020 16:14:32 -0700 Subject: [PATCH 071/138] Improve the documentation of Receiver (#3473) --- .../dataflow/analysis/FlowExpressions.java | 18 ++++++++++++++++-- docs/manual/advanced-features.tex | 18 ++++++------------ 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/FlowExpressions.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/FlowExpressions.java index 60c31eb68afe..72b5b641c3a8 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/FlowExpressions.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/FlowExpressions.java @@ -446,9 +446,23 @@ private static Receiver internalReprOfMemberSelect( return internalArguments; } + // The syntax that the Checker Framework uses for Java expressions also includes "" and + // "#1" for formal parameters. However, there are no special subclasses (AST nodes) for those + // extensions. /** - * The poorly-named Receiver class is actually a Java AST. Each subclass represents a different - * type of expression, such as MethodCall, ArrayAccess, LocalVariable, etc. + * This class represents a Java expression and its type. It does not represent all possible Java + * expressions (for example, it does not represent a ternary expression; use {@link + * FlowExpressions.Unknown} for unrepresentable expressions). + * + *

    This class's representation is like an AST: subparts are also expressions. For declared + * names (fields, local variables, and methods), it also contains an Element. + * + *

    Each subclass represents a different type of expression, such as {@link + * FlowExpressions.MethodCall}, {@link FlowExpressions.ArrayAccess}, {@link + * FlowExpressions.LocalVariable}, etc. + * + * @see the syntax + * of Java expressions supported by the Checker Framework */ public abstract static class Receiver { /** The type of this expression. */ diff --git a/docs/manual/advanced-features.tex b/docs/manual/advanced-features.tex index d3867b8dd00b..f923f8275790 100644 --- a/docs/manual/advanced-features.tex +++ b/docs/manual/advanced-features.tex @@ -1140,7 +1140,9 @@ \item \refqualclass{checker/lock/qual}{Holding} \end{itemize} -The set of permitted expressions is a subset of all Java expressions: +The set of permitted expressions is a subset of all Java expressions, +with a few extensions, formal parameters like \<\#1> and (for some type +systems) \code{}. \begin{itemize} \item @@ -1246,20 +1248,12 @@ \textbf{Limitations:} -The following Java expressions may not currently be written: -% The Checker Framework is best at reasoning about Java expressions that -% are variable references, but these expressions are not. -\begin{itemize} -\item String concatenation expressions. -\item Mathematical operators (plus, minus, division, ...). -\item Comparisons (equality, less than, etc.). -\end{itemize} - -Additionally, it is not possible to write -quantification over all array components (e.g. to express that all +It is not possible to write a +quantification over all array components (e.g., to express that all array elements are non-null). There is no such Java expression, but it would be useful when writing specifications. + \sectionAndLabel{Field invariants}{field-invariants} Sometimes a field declared in a superclass has a more precise type in a From bc3f109f6344d876604975011c7348ca03fb61e9 Mon Sep 17 00:00:00 2001 From: Dmitriy Shepelev Date: Tue, 21 Jul 2020 22:16:30 -0700 Subject: [PATCH 072/138] Fix grammar --- docs/manual/introduction.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/introduction.tex b/docs/manual/introduction.tex index df7af6e40853..b80b9ca89b86 100644 --- a/docs/manual/introduction.tex +++ b/docs/manual/introduction.tex @@ -1096,7 +1096,7 @@ may need to be annotated \<@Nullable>. The specification should state any facts that are relevant to callees. -When checking a method, uses only the specification, not the +When checking a method, use only the specification, not the implementation, of other methods. (Equivalently, type-checking is ``modular'' or ``intraprocedural''.) From 15cc1c937dbdb5d7fc0a95d73a356cdd02569e5c Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Wed, 22 Jul 2020 08:24:40 -0700 Subject: [PATCH 073/138] Documentation tweaks --- .../signedness/SignednessAnnotatedTypeFactory.java | 12 ++++++++---- .../framework/type/AnnotatedTypeFactory.java | 4 ++-- .../framework/type/DefaultTypeHierarchy.java | 5 +++-- .../framework/type/TypeHierarchy.java | 2 +- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessAnnotatedTypeFactory.java index e3f5d8daff52..9e4bc94c4365 100644 --- a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessAnnotatedTypeFactory.java @@ -188,10 +188,14 @@ protected TreeAnnotator createTreeAnnotator() { } /** - * This TreeAnnotator ensures that boolean expressions are not given Unsigned or Signed - * annotations by {@link PropagationTreeAnnotator}, that shift results take on the type of their - * left operand, and that the types of identifiers are refined based on the results of the Value - * Checker. + * This TreeAnnotator ensures that: + * + *

      + *
    • boolean expressions are not given Unsigned or Signed annotations by {@link + * PropagationTreeAnnotator}, + *
    • shift results take on the type of their left operand, + *
    • the types of identifiers are refined based on the results of the Value Checker. + *
    */ private class SignednessTreeAnnotator extends TreeAnnotator { diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java index 1fe2b0fd02fb..de4fb9f424e0 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java @@ -779,10 +779,10 @@ public final QualifierHierarchy getQualifierHierarchy() { } /** - * Creates the type subtyping checker using the current type qualifier hierarchy. + * Creates the type hierarchy to be used by this factory. * *

    Subclasses may override this method to specify new type-checking rules beyond the typical - * java subtyping rules. + * Java subtyping rules. * * @return the type relations class to check type subtyping */ diff --git a/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java index 10fe883d43e4..d67b9e788540 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java @@ -31,8 +31,9 @@ * covariant array types, raw types, and allowing covariant type arguments depending on various * options passed to DefaultTypeHierarchy. * - *

    Subtyping rules of the JLS can be found in section 4.10, "Subtyping": - * https://docs.oracle.com/javase/specs/jls/se11/html/jls-4.html#jls-4.10 + *

    Subtyping rules of the JLS can be found in section 4.10, + * "Subtyping". * *

    Note: The visit methods of this class must be public but it is intended to be used through a * TypeHierarchy interface reference which will only allow isSubtype to be called. It does not make diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/TypeHierarchy.java index e1f932f818a4..3b36452e56a1 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeHierarchy.java @@ -2,7 +2,7 @@ import org.checkerframework.framework.util.AnnotatedTypes; -/** Compares AnnotatedTypeMirrors for subtype relationships. See also QualifierHierarchy */ +/** Compares AnnotatedTypeMirrors for subtype relationships. See also {@link QualifierHierarchy}. */ public interface TypeHierarchy { /** From f358863952827832bff1928ef36ce2c8074c9563 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2020 11:14:33 -0700 Subject: [PATCH 074/138] Bump biz.aQute.bnd.gradle from 5.1.1 to 5.1.2 --- checker-qual/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/checker-qual/build.gradle b/checker-qual/build.gradle index 85e6273ea90b..69c84442fbbe 100644 --- a/checker-qual/build.gradle +++ b/checker-qual/build.gradle @@ -4,7 +4,7 @@ buildscript { } dependencies { // Create OSGI bundles - classpath "biz.aQute.bnd:biz.aQute.bnd.gradle:5.1.1" + classpath "biz.aQute.bnd:biz.aQute.bnd.gradle:5.1.2" } } From 11d7bca03997f80cebbd18b9472cd7059f02a85d Mon Sep 17 00:00:00 2001 From: NITIN KUMAR DAS Date: Thu, 23 Jul 2020 04:49:55 +0530 Subject: [PATCH 075/138] Updated the number of developers --- CONTRIBUTING.md | 2 +- docs/manual/contributors.tex | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d0b8acbf71ac..334f2f12ff49 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,7 @@ # Contributing to the Checker Framework Thank you for contributing to the Checker Framework! This project is a -community effort of [more than 90 +community effort of [more than 110 developers](https://checkerframework.org/manual/#credits), plus countless more people who have contributed bug reports and feature suggestions. We couldn't do it without your help. diff --git a/docs/manual/contributors.tex b/docs/manual/contributors.tex index 33b77094b042..70f76e9b0029 100644 --- a/docs/manual/contributors.tex +++ b/docs/manual/contributors.tex @@ -73,6 +73,7 @@ Nhat Nguyen, Nikhil Shinde, Nima Karimipour, +Nitin Kumar Das, Oleg Shchelykalnov, Olek Wojnar, Pascal Wittmann, From 5d8457bf2eea218cfe3b6b2c027ffe1510dd228d Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Wed, 22 Jul 2020 20:15:56 -0700 Subject: [PATCH 076/138] Turn URLs into links --- .../org/checkerframework/checker/formatter/FormatUtil.java | 5 +++-- .../checkerframework/common/reflection/ClassValVisitor.java | 5 +++-- .../framework/type/DefaultTypeHierarchy.java | 5 +++-- .../framework/type/ElementAnnotationApplier.java | 6 +++--- .../checkerframework/framework/type/SupertypeFinder.java | 5 +++-- .../framework/util/typeinference/TypeArgumentInference.java | 4 ++-- .../util/typeinference/constraint/AFConstraint.java | 5 +++-- 7 files changed, 20 insertions(+), 15 deletions(-) diff --git a/checker/src/main/java/org/checkerframework/checker/formatter/FormatUtil.java b/checker/src/main/java/org/checkerframework/checker/formatter/FormatUtil.java index 525fadf86436..2185c4fa90a9 100644 --- a/checker/src/main/java/org/checkerframework/checker/formatter/FormatUtil.java +++ b/checker/src/main/java/org/checkerframework/checker/formatter/FormatUtil.java @@ -223,8 +223,9 @@ private static char conversionCharFromFormat(@Regex(6) Matcher m) { /** * Return the conversion character that is in the given format specifier. * - * @param formatSpecifier a format specifier; see - * https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Formatter.html#syntax} + * @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 {@link * #conversionCharFromFormat(Matcher)}. diff --git a/framework/src/main/java/org/checkerframework/common/reflection/ClassValVisitor.java b/framework/src/main/java/org/checkerframework/common/reflection/ClassValVisitor.java index 378aea8f159a..edf142ec25fb 100644 --- a/framework/src/main/java/org/checkerframework/common/reflection/ClassValVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/reflection/ClassValVisitor.java @@ -58,8 +58,9 @@ public boolean isValid(AnnotatedTypeMirror type, Tree tree) { } /** - * A string is a legal binary name if it has the following form: ((Java identifier)\.)*(Java - * identifier)([])* https://docs.oracle.com/javase/specs/jls/se11/html/jls-13.html#jls-13.1 + * A string is a binary + * name if it has the following form: ((Java identifier)\.)*(Java identifier)([])* * * @param className string to check * @return true if className is a legal class name diff --git a/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java index d67b9e788540..47222e80d934 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java @@ -322,8 +322,9 @@ protected boolean areEqualInHierarchy( /** * A declared type is considered a supertype of another declared type only if all of the type * arguments of the declared type "contain" the corresponding type arguments of the subtype. - * Containment is described in the JLS section 4.5.1 "Type Arguments of Parameterized Types", - * https://docs.oracle.com/javase/specs/jls/se11/html/jls-4.html#jls-4.5.1 + * Containment is described in JLS section + * 4.5.1 "Type Arguments of Parameterized Types". * * @param inside the "subtype" type argument * @param outside the "supertype" type argument diff --git a/framework/src/main/java/org/checkerframework/framework/type/ElementAnnotationApplier.java b/framework/src/main/java/org/checkerframework/framework/type/ElementAnnotationApplier.java index c4a7223f51b7..4370a7aa8df3 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/ElementAnnotationApplier.java +++ b/framework/src/main/java/org/checkerframework/framework/type/ElementAnnotationApplier.java @@ -32,9 +32,9 @@ * represents that element (or a use of that Element). * *

    In a way, this class is a hack: the Type representation for the Elements should contain all - * annotations that we want. However, due to javac bugs - * http://mail.openjdk.java.net/pipermail/type-annotations-dev/2013-December/001449.html decoding - * the type annotations from the Element is necessary. + * annotations that we want. However, due to javac + * bugs decoding the type annotations from the Element is necessary. * *

    Even once these bugs are fixed, this class might be useful: in TypesIntoElements it is easy to * add additional annotations to the element and have them stored in the bytecode by the compiler. diff --git a/framework/src/main/java/org/checkerframework/framework/type/SupertypeFinder.java b/framework/src/main/java/org/checkerframework/framework/type/SupertypeFinder.java index efc1dcfef335..0ed4ba9efc97 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/SupertypeFinder.java +++ b/framework/src/main/java/org/checkerframework/framework/type/SupertypeFinder.java @@ -32,8 +32,9 @@ import org.checkerframework.javacutil.TypesUtils; /** - * Finds the direct supertypes of an input AnnotatedTypeMirror. See - * https://docs.oracle.com/javase/specs/jls/se11/html/jls-4.html#jls-4.10.2 + * Finds the direct supertypes of an input AnnotatedTypeMirror. See JLS section + * 4.10.2. * * @see Types#directSupertypes(TypeMirror) */ diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgumentInference.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgumentInference.java index 89d42ba4a22c..b0249a9a1dd4 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgumentInference.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/TypeArgumentInference.java @@ -29,8 +29,8 @@ * *

    For the Checker Framework we also need to infer reasonable annotations for these type * arguments. For information on inferring type arguments see the documentation in JLS7 and JLS8: - * https://docs.oracle.com/javase/specs/jls/se11/html/jls-18.html - * https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.2.7 + * https://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html + * https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.12.2.7 * *

    Note: It appears that Java 8 greatly improved the type argument inference and related error * messaging but I have found it useful to consult the JLS 7 as well. diff --git a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFConstraint.java b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFConstraint.java index 90cd2c8a7b1b..64d52602c936 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFConstraint.java +++ b/framework/src/main/java/org/checkerframework/framework/util/typeinference/constraint/AFConstraint.java @@ -13,8 +13,9 @@ * invocations and new class invocations. These constraints are simplified then converted to * TUConstraints during type argument inference. * - *

    Subclasses of AFConstraint represent the following types of constraints found in - * (https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.2.7) + *

    Subclasses of AFConstraint represent the following types of + * constraints: * *

    A 《 F and F 》 A both imply that A is convertible to F. F 《 A and A 》 F both imply that F is * convertible to A (this may happen due to wildcard/typevar bounds and recursive types) A = F From 4ad676e7d4333b48dc5f446bec8ef16857eb58f8 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Wed, 22 Jul 2020 22:17:03 -0700 Subject: [PATCH 077/138] Documentation tweaks --- .../checkerframework/framework/type/AnnotatedTypeMirror.java | 5 +++-- .../framework/type/DefaultTypeHierarchy.java | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java index 5c9e75dc9cf2..b7dd05010552 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeMirror.java @@ -439,8 +439,9 @@ public boolean hasEffectiveAnnotation(AnnotationMirror a) { /** * Determines whether this type contains the given annotation explicitly written at declaration. - * This method considers the annotation's values, that is, if the type is "@A("s") @B(3) Object" - * a call with "@A("t") or "@A" will return false, whereas a call with "@B(3)" will return true. + * This method considers the annotation's values, that is, if the type is {@code @A("s") @B(3) + * Object}, a call with {@code @A("t")} or {@code @A} will return false, whereas a call with + * {@code @B(3)} will return true. * *

    In contrast to {@link #hasExplicitAnnotationRelaxed(AnnotationMirror)} this method also * compares annotation values. diff --git a/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java index 47222e80d934..3ac6e602f59d 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java @@ -224,7 +224,7 @@ protected boolean isPrimarySubtype( final AnnotationMirror supertypeAnno = supertype.getAnnotationInHierarchy(currentTop); if (checker.getTypeFactory().hasQualifierParameterInHierarchy(supertype, currentTop) && checker.getTypeFactory().hasQualifierParameterInHierarchy(subtype, currentTop)) { - // Qualifiers must be equivalent. + // If the types have a class qualifier parameter, the qualifiers must be equivalent. return isAnnoSubtype(subtypeAnno, supertypeAnno, annosCanBeEmtpy) && isAnnoSubtype(supertypeAnno, subtypeAnno, annosCanBeEmtpy); } From 90cdd5ea13ce2e995247871abbfaeafe4726f248 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 23 Jul 2020 06:24:32 -0700 Subject: [PATCH 078/138] Avoid more unnecessary computation --- azure-pipelines.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index bd14612f6b1a..a9d5e18e382d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -13,6 +13,7 @@ jobs: - job: junit_tests_jdk8 dependsOn: - junit_tests_jdk11 + - nonjunit_tests_jdk11 - misc_jdk11 pool: vmImage: 'ubuntu-latest' @@ -33,6 +34,7 @@ jobs: displayName: test-cftests-junit.sh - job: nonjunit_tests_jdk8 dependsOn: + - junit_tests_jdk11 - nonjunit_tests_jdk11 - misc_jdk11 pool: From bc9a1203c90869ceee5b02c7b12071732794ac47 Mon Sep 17 00:00:00 2001 From: Weitian Xing Date: Thu, 23 Jul 2020 16:52:31 -0400 Subject: [PATCH 079/138] Correct the logic of dataflow framework. (#3414) BackwardAnalysis: 1. Add exception block's predecessor exception block to inputs (fix null pointer exception). 2. When running `runAnalysisFor()` with an exception block, pass a copied transfer input to `callTransferFunction()`. ForwardAnalysis: 1. When running `runAnalysisFor()` with an exception block, pass a copied transfer input to `callTransferFunction()`. In the previous dataflow framework (before #3370), we passed a non-copied transfer input (https://github.com/typetools/checker-framework/pull/3370/files#diff-d4108228a0a5407e00c090c2e97d8b6dL401-L402). This seems a bug existing in the old dataflow framework. Resolves #3447. --- build.gradle | 1 + dataflow/build.gradle | 18 ++++++++++++++++++ .../analysis/BackwardAnalysisImpl.java | 8 ++++++-- .../dataflow/analysis/ForwardAnalysisImpl.java | 7 +++++-- dataflow/tests/issue3447/Test.java | 12 ++++++++++++ 5 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 dataflow/tests/issue3447/Test.java diff --git a/build.gradle b/build.gradle index 3f48d835a8df..100682a2b82b 100644 --- a/build.gradle +++ b/build.gradle @@ -829,6 +829,7 @@ subprojects { if (project.name.is('dataflow')) { dependsOn('liveVariableTest') + dependsOn('issue3447Test') } } diff --git a/dataflow/build.gradle b/dataflow/build.gradle index b8d6545b63ca..ef47146c7634 100644 --- a/dataflow/build.gradle +++ b/dataflow/build.gradle @@ -59,3 +59,21 @@ task liveVariableTest(dependsOn: compileTestJava, group: 'Verification') { } } } + +task issue3447Test(dependsOn: compileTestJava, group: 'Verification') { + description 'Test issue 3447 test case for backward analysis.' + inputs.file('tests/issue3447/Test.java') + delete('tests/issue3447/Out.txt') + delete('tests/issue3447/Test.class') + doLast { + javaexec { + workingDir = 'tests/issue3447' + if (!JavaVersion.current().java9Compatible) { + jvmArgs += "-Xbootclasspath/p:${configurations.javacJar.asPath}" + } + classpath = sourceSets.test.compileClasspath + classpath += sourceSets.test.output + main = 'livevar.LiveVariable' + } + } +} diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysisImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysisImpl.java index 4510b569e49b..cc8c85492a89 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysisImpl.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysisImpl.java @@ -286,6 +286,7 @@ protected void addStoreAfter(Block pred, @Nullable Node node, S s, boolean addBl (exceptionStore != null) ? exceptionStore.leastUpperBound(s) : s; if (!newExceptionStore.equals(exceptionStore)) { exceptionStores.put(ebPred, newExceptionStore); + inputs.put(ebPred, new TransferInput(node, this, newExceptionStore)); addBlockToWorklist = true; } } @@ -344,7 +345,8 @@ public S runAnalysisFor( if (n == node && !before) { return store.getRegularStore(); } - // Copy the store to preserve to change the state in {@link #inputs} + // Copy the store to avoid changing other blocks' transfer inputs in + // {@link #inputs} TransferResult transferResult = callTransferFunction(n, store.copy()); if (n == node) { @@ -370,8 +372,10 @@ public S runAnalysisFor( return transferInput.getRegularStore(); } setCurrentNode(node); + // Copy the store to avoid changing other blocks' transfer inputs in {@link + // #inputs} TransferResult transferResult = - callTransferFunction(node, transferInput); + callTransferFunction(node, transferInput.copy()); // Merge transfer result with the exception store of this exceptional block S exceptionStore = exceptionStores.get(eb); return exceptionStore == null diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysisImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysisImpl.java index 8ec65c9cd715..a35bc78e3355 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysisImpl.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/ForwardAnalysisImpl.java @@ -282,7 +282,8 @@ public S runAnalysisFor( if (cache != null && cache.containsKey(n)) { transferResult = cache.get(n); } else { - // Copy the store to preserve to change the state in the cache + // Copy the store to avoid changing other blocks' transfer inputs in + // {@link #inputs} transferResult = callTransferFunction(n, store.copy()); if (cache != null) { cache.put(n, transferResult); @@ -312,8 +313,10 @@ public S runAnalysisFor( return transferInput.getRegularStore(); } setCurrentNode(node); + // Copy the store to avoid changing other blocks' transfer inputs in {@link + // #inputs} TransferResult transferResult = - callTransferFunction(node, transferInput); + callTransferFunction(node, transferInput.copy()); return transferResult.getRegularStore(); } default: diff --git a/dataflow/tests/issue3447/Test.java b/dataflow/tests/issue3447/Test.java new file mode 100644 index 000000000000..563d01a1d625 --- /dev/null +++ b/dataflow/tests/issue3447/Test.java @@ -0,0 +1,12 @@ +// Test case for Issue 3447: +// https://github.com/typetools/checker-framework/issues/3447 + +public class Test { + public void test() throws Exception { + try { + int[] myNumbers = {1}; + System.out.println(myNumbers[1]); + } catch (Exception e) { + } + } +} From d8e7924c06969bba64ae3cea3265490e89075677 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 23 Jul 2020 13:53:08 -0700 Subject: [PATCH 080/138] Don't permit bottom to be null (#3497) --- .../checkerframework/framework/type/DefaultTypeHierarchy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java index 3ac6e602f59d..58bda354d261 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java @@ -260,7 +260,7 @@ protected boolean isAnnoSubtype( protected boolean isBottom(final AnnotatedTypeMirror subtype) { final AnnotationMirror bottom = qualifierHierarchy.getBottomAnnotation(currentTop); if (bottom == null) { - return false; // can't be below infinitely sized hierarchy + throw new BugInCF("getBottomAnnotation(%s) is null", currentTop); } switch (subtype.getKind()) { From 90f4053bea708f7c65df48c75639ce06fea10207 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Fri, 24 Jul 2020 08:27:01 -0700 Subject: [PATCH 081/138] Expand information about contributing --- CONTRIBUTING.md | 17 +++++++++++++---- docs/manual/annotating-libraries.tex | 22 +++++++++++++++++++--- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 334f2f12ff49..c94eb6ccce0c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,18 +19,27 @@ bug, and we want to fix it. Please report it. ## Submitting changes -Please see the [pull requests](https://rawgit.com/typetools/checker-framework/master/docs/developer/developer-manual.html#pull-requests) section of the Developer Manual. +Please see the [pull +requests](https://rawgit.com/typetools/checker-framework/master/docs/developer/developer-manual.html#pull-requests) +section of the Developer Manual. + +Submit changes to the annotated JDK at https://github.com/typetools/jdk/pulls . Do you want to contribute to the project, but you are not sure what issue to fix or what feature to add? Use the tool in your daily work, and when -you encounter a limitation that bothers you, fix that one. +you encounter a limitation that bothers you, fix that one. The ["help +wanted"](https://github.com/typetools/checker-framework/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) +label marks issues that require less deep knowledge and may be appropriate +for a newcomer to the codebase. ## License -By contributing, you agree that your contributions will be licensed under the existing [license](LICENSE.txt), usually GPL2 or MIT License. +By contributing, you agree that your contributions will be licensed under the +existing [license](LICENSE.txt), usually GPL2 or MIT License. ## Code of conduct -In interactions with other people, please abide by the [Contributor Covenant](https://www.contributor-covenant.org/version/2/0/code_of_conduct). +When interacting with other people, please abide by the [Contributor +Covenant](https://www.contributor-covenant.org/version/2/0/code_of_conduct). diff --git a/docs/manual/annotating-libraries.tex b/docs/manual/annotating-libraries.tex index 5b62270401df..603173d5eb57 100644 --- a/docs/manual/annotating-libraries.tex +++ b/docs/manual/annotating-libraries.tex @@ -189,9 +189,25 @@ Whenever you annotate any part of a file, fully annotate the file! That is, write annotations for all the methods and fields, based on their -documentation. If you don't annotate the whole file, then users may be -surprised that calls to some methods type-check as expected whereas other -methods do not (because they have not been annotated). +documentation. Here are reasons for this rule: + +\begin{itemize} +\item + If you annotate just part of the file, then users may be surprised that + calls to some methods type-check as expected whereas other methods do not + (because they have not been annotated). +\item + Annotating one method or field at a time may lead to inconsistencies + between different parts of the file. Different people may make different + assumptions, might write annotations in a way that is locally convenient + but globally inconsistent, or might not read all the documentation of the + class to understand how it works. +\item + It is not much more effort to annotate an entire class versus one method + or field. In either case it is usually necessary to understand the entire + class's design and implementation. Once you have done that, you might as + well annotate the whole thing. +\end{itemize} \subsectionAndLabel{Verify your annotations}{library-tips-verify} From 076e281d972ae49cfacafe52fae9424daa9e5b60 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Fri, 24 Jul 2020 08:31:44 -0700 Subject: [PATCH 082/138] Contributing annotations for non-JDK libraries --- CONTRIBUTING.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c94eb6ccce0c..cfe6248ff80f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,6 +24,9 @@ requests](https://rawgit.com/typetools/checker-framework/master/docs/developer/d section of the Developer Manual. Submit changes to the annotated JDK at https://github.com/typetools/jdk/pulls . +Annotations for other libraries can be contributed as stub files in this +repository, in a fork of the library in https://github.com/typetools/, or +in the library's own repository. Do you want to contribute to the project, but you are not sure what issue to fix or what feature to add? Use the tool in your daily work, and when From 25d045ffe64b2e490c810cdcd8010e64796cc0fd Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Fri, 24 Jul 2020 10:22:59 -0700 Subject: [PATCH 083/138] Add a TypeHierarchy for the Signedness Checker --- .../SignednessAnnotatedTypeFactory.java | 158 +++++++++++++++++- .../signedness/SignednessAssignments.java | 121 ++++++++++++++ docs/manual/signedness-checker.tex | 31 +++- .../framework/type/AnnotatedTypeFactory.java | 4 +- 4 files changed, 302 insertions(+), 12 deletions(-) create mode 100644 checker/tests/signedness/SignednessAssignments.java diff --git a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessAnnotatedTypeFactory.java index 9e4bc94c4365..d3b0d68e9df2 100644 --- a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessAnnotatedTypeFactory.java @@ -6,10 +6,12 @@ import java.lang.annotation.Annotation; import java.util.Set; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.signedness.qual.Signed; import org.checkerframework.checker.signedness.qual.SignedPositive; +import org.checkerframework.checker.signedness.qual.SignednessBottom; import org.checkerframework.checker.signedness.qual.SignednessGlb; import org.checkerframework.checker.signedness.qual.UnknownSignedness; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; @@ -23,11 +25,19 @@ import org.checkerframework.framework.qual.TypeUseLocation; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; +import org.checkerframework.framework.type.DefaultTypeHierarchy; +import org.checkerframework.framework.type.QualifierHierarchy; +import org.checkerframework.framework.type.TypeHierarchy; import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator; import org.checkerframework.framework.type.treeannotator.PropagationTreeAnnotator; import org.checkerframework.framework.type.treeannotator.TreeAnnotator; import org.checkerframework.framework.util.defaults.QualifierDefaults; import org.checkerframework.javacutil.AnnotationBuilder; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.TypesUtils; /** * The type factory for the Signedness Checker. @@ -36,14 +46,17 @@ */ public class SignednessAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { - /** The @SignednessGlb annotation. */ - private final AnnotationMirror SIGNEDNESS_GLB = - AnnotationBuilder.fromClass(elements, SignednessGlb.class); - /** The @Signed annotation. */ - private final AnnotationMirror SIGNED = AnnotationBuilder.fromClass(elements, Signed.class); /** The @UnknownSignedness annotation. */ private final AnnotationMirror UNKNOWN_SIGNEDNESS = AnnotationBuilder.fromClass(elements, UnknownSignedness.class); + /** The @Signed annotation. */ + private final AnnotationMirror SIGNED = AnnotationBuilder.fromClass(elements, Signed.class); + /** The @SignednessGlb annotation. */ + private final AnnotationMirror SIGNEDNESS_GLB = + AnnotationBuilder.fromClass(elements, SignednessGlb.class); + /** The @SignednessBottom annotation. */ + private final AnnotationMirror SIGNEDNESS_BOTTOM = + AnnotationBuilder.fromClass(elements, SignednessBottom.class); /** The @NonNegative annotation of the Index Checker, as represented by the Value Checker. */ private final AnnotationMirror INT_RANGE_FROM_NON_NEGATIVE = @@ -239,4 +252,139 @@ public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMi return null; } } + + @Override + protected TypeHierarchy createTypeHierarchy() { + return new SignednessTypeHierarchy( + checker, + getQualifierHierarchy(), + checker.getBooleanOption("ignoreRawTypeArguments", true), + checker.hasOption("invariantArrays")); + } + + /** + * The type hierarchy for the signedness type system. If A is narrower (fewer bits) than B, then + * A with any qualifier is a subtype of @SignedPositive B. + */ + protected class SignednessTypeHierarchy extends DefaultTypeHierarchy { + + /** + * Create a new SignednessTypeHierarchy. + * + * @param checker the checker + * @param qualifierHierarchy the qualifier hierarchy + * @param ignoreRawTypes from -AignoreRawTypes + * @param invariantArrayComponents from -AinvariantArrays + */ + public SignednessTypeHierarchy( + BaseTypeChecker checker, + QualifierHierarchy qualifierHierarchy, + boolean ignoreRawTypes, + boolean invariantArrayComponents) { + super(checker, qualifierHierarchy, ignoreRawTypes, invariantArrayComponents); + } + + @Override + public Boolean visitPrimitive_Primitive( + AnnotatedPrimitiveType subtype, AnnotatedPrimitiveType supertype, Void p) { + + boolean superResult = super.visitPrimitive_Primitive(subtype, supertype, p); + if (superResult) { + return true; + } + + PrimitiveType subPrimitive = subtype.getUnderlyingType(); + PrimitiveType superPrimitive = supertype.getUnderlyingType(); + if (isNarrowerIntegral(subPrimitive, superPrimitive)) { + AnnotationMirror superAnno = supertype.getAnnotationInHierarchy(UNKNOWN_SIGNEDNESS); + if (!AnnotationUtils.areSameByName(superAnno, SIGNEDNESS_BOTTOM)) { + return true; + } + } + + return false; + } + + @Override + public Boolean visitPrimitive_Declared( + AnnotatedPrimitiveType subtype, AnnotatedDeclaredType supertype, Void p) { + boolean superBoxed = TypesUtils.isBoxedPrimitive(supertype.getUnderlyingType()); + if (superBoxed) { + return visitPrimitive_Primitive(subtype, getUnboxedType(supertype), p); + } + return super.visitPrimitive_Declared(subtype, supertype, p); + } + + @Override + public Boolean visitDeclared_Declared( + AnnotatedDeclaredType subtype, AnnotatedDeclaredType supertype, Void p) { + boolean subBoxed = TypesUtils.isBoxedPrimitive(subtype.getUnderlyingType()); + if (subBoxed) { + boolean superBoxed = TypesUtils.isBoxedPrimitive(supertype.getUnderlyingType()); + if (superBoxed) { + return visitPrimitive_Primitive( + getUnboxedType(subtype), getUnboxedType(supertype), p); + } + } + return super.visitDeclared_Declared(subtype, supertype, p); + } + + @Override + public Boolean visitDeclared_Primitive( + AnnotatedDeclaredType subtype, AnnotatedPrimitiveType supertype, Void p) { + boolean subBoxed = TypesUtils.isBoxedPrimitive(subtype.getUnderlyingType()); + if (subBoxed) { + return visitPrimitive_Primitive(getUnboxedType(subtype), supertype, p); + } + return super.visitDeclared_Primitive(subtype, supertype, p); + } + + /** + * Returns true if both types are integral and the first type is strictly narrower + * (represented by fewer bits) than the second type. + * + * @param a a primitive type + * @param b a primitive type + * @return true if {@code a} is represented by fewer bits than {@code b} + */ + private boolean isNarrowerIntegral(PrimitiveType a, PrimitiveType b) { + int aBits = numIntegralBits(a); + if (aBits == -1) { + return false; + } + int bBits = numIntegralBits(b); + if (bBits == -1) { + return false; + } + return aBits < bBits; + } + + /** + * Returns the number of bits in the representation of an integral primitive type. Returns + * -1 if the type is not an integral primitive type. + * + * @param p a primitive type + * @return the number of bits in its representation, or -1 if not integral + */ + private int numIntegralBits(PrimitiveType p) { + switch (p.getKind()) { + case BYTE: + return 8; + case SHORT: + return 16; + case CHAR: + return 16; + case INT: + return 32; + case LONG: + return 64; + case BOOLEAN: + case DOUBLE: + case FLOAT: + return -1; + default: + throw new BugInCF("Unexpected primitive type " + p); + } + } + } } diff --git a/checker/tests/signedness/SignednessAssignments.java b/checker/tests/signedness/SignednessAssignments.java new file mode 100644 index 000000000000..668893710d9e --- /dev/null +++ b/checker/tests/signedness/SignednessAssignments.java @@ -0,0 +1,121 @@ +import org.checkerframework.checker.signedness.qual.Signed; +import org.checkerframework.checker.signedness.qual.SignedPositive; +import org.checkerframework.checker.signedness.qual.Unsigned; + +public class SignednessAssignments { + + @Signed byte sb; + @Unsigned byte ub; + @Signed Byte sB; + @Unsigned Byte uB; + + @Unsigned char uc; + @Unsigned Character uC; + + @Signed short ss; + @Unsigned short us; + @Signed Short sS; + @Unsigned Short uS; + + @Signed int si; + @Unsigned int ui; + @Signed Integer sI; + @Unsigned Integer uI; + + @Signed long sl; + @Unsigned long ul; + @Signed Long sL; + @Unsigned Long uL; + + void assignmentsByte() { + @Signed byte i1 = sb; + @Unsigned byte i2 = ub; + @Signed byte i3 = sB; + @Unsigned byte i4 = uB; + + @Signed Byte i91 = sb; + @Unsigned Byte i92 = ub; + @Signed Byte i93 = sB; + @Unsigned Byte i94 = uB; + } + + void assignmentsShort() { + @SignedPositive short i1 = sb; + @SignedPositive short i2 = ub; + @SignedPositive short i3 = sB; + @SignedPositive short i4 = uB; + + @Signed short i9 = ss; + @Unsigned short i10 = us; + @Signed short i11 = sS; + @Unsigned short i12 = uS; + + @Signed Short i91 = ss; + @Unsigned Short i92 = us; + @Signed Short i93 = sS; + @Unsigned Short i94 = uS; + } + + void assignmentsChar() { + // These are commented out because they are Java errors. + // @Unsigned char i2 = ub; + // @Unsigned char i4 = uB; + // @Unsigned char i10 = us; + // @Unsigned char i12 = uS; + } + + void assignmentsInt() { + @SignedPositive int i1 = sb; + @SignedPositive int i2 = ub; + @SignedPositive int i3 = sB; + @SignedPositive int i4 = uB; + + @SignedPositive int i6 = uc; + @SignedPositive int i8 = uC; + + @SignedPositive int i9 = ss; + @SignedPositive int i10 = us; + @SignedPositive int i11 = sS; + @SignedPositive int i12 = uS; + + @Signed int i13 = si; + @Unsigned int i14 = ui; + @Signed int i15 = sI; + @Unsigned int i16 = uI; + + @Signed Integer i91 = si; + @Unsigned Integer i92 = ui; + @Signed Integer i93 = sI; + @Unsigned Integer i94 = uI; + } + + void assignmentsLong() { + @SignedPositive long i1 = sb; + @SignedPositive long i2 = ub; + @SignedPositive long i3 = sB; + @SignedPositive long i4 = uB; + + @SignedPositive long i6 = uc; + @SignedPositive long i8 = uC; + + @SignedPositive long i9 = ss; + @SignedPositive long i10 = us; + @SignedPositive long i11 = sS; + @SignedPositive long i12 = uS; + + @SignedPositive long i13 = si; + @SignedPositive long i14 = ui; + @SignedPositive long i15 = sI; + @SignedPositive long i16 = uI; + + @Signed long i17 = sl; + @Unsigned long i18 = ul; + @Signed long i19 = sL; + @Unsigned long i20 = uL; + + @Signed Long i91 = sl; + @Unsigned Long i92 = ul; + @Signed Long i93 = sL; + @Unsigned Long i94 = uL; + } +} diff --git a/docs/manual/signedness-checker.tex b/docs/manual/signedness-checker.tex index 8c46f0aacdc4..cd319b703269 100644 --- a/docs/manual/signedness-checker.tex +++ b/docs/manual/signedness-checker.tex @@ -13,12 +13,15 @@ The range of signed byte values is -128 to 127. The range of unsigned byte values is 0 to 255. -Signedness is only applicable to integral types: \, \, -\, \, and \. Floating-point types (\ and -\) do not have operations that interpret the bits as unsigned. +Signedness is only applicable to the integral types \, +\, \, and \ and their boxed variants \, +\, \, and \. +\ and \ are always unsigned. +Floating-point types \, \, \, and \ are always signed. +% , because they do not have operations that interpret the bits as unsigned. Signedness is primarily about how the bits of the representation are -interpreted, not about the values that it can represent. An unsigned value +\emph{interpreted}, not about the values that it can represent. An unsigned value is always positive, but just because a variable's value is positive does not mean that it should be marked as \<@Unsigned>. If variable $v$ will be compared to a signed value, or used in arithmetic operations with a signed @@ -61,7 +64,8 @@ \item[\refqualclass{checker/signedness/qual}{SignedPositive}] indicates that a value is known at compile time to be in the positive signed range, so it has the same interpretation as signed or unsigned - and may be used with either interpretation. Programmers should usually + and may be used with either interpretation. (Equivalently, the most + significant bit is guaranteed to be 0.) Programmers should usually write \refqualclass{checker/signedness/qual}{Signed} or \refqualclass{checker/signedness/qual}{Unsigned} instead. @@ -112,6 +116,23 @@ \end{itemize} +\subsectionAndLabel{Widening conversions}{signedness-checker-widening-conversions} + +Figure~\ref{fig-signedness-hierarchy} shows the type qualifier hierarchy. +When upcasting among integral types, the expression has type +\<@SignedPositive> because the value is guaranteed to be within the signed +positive range. For example: + +\begin{Verbatim} +@Signed int sint; +@Unsigned int uint; +@SignedPositive long splong1 = sint; // legal +@SignedPositive long splong2 = uint; // legal +... (long) sint ... // this expression has type @SignedPositive long +... (long) uint ... // this expression has type @SignedPositive long +\end{Verbatim} + + \sectionAndLabel{Prohibited operations}{signedness-checker-prohibited-operations} The Signedness Checker prohibits the following uses of operators: diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java index de4fb9f424e0..a6a3f83cac7f 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java @@ -2443,10 +2443,10 @@ public AnnotatedDeclaredType getBoxedType(AnnotatedPrimitiveType type) { } /** - * returns the annotated primitive type of the given declared type if it is a boxed declared + * Returns the annotated primitive type of the given declared type if it is a boxed declared * type. Otherwise, it throws IllegalArgumentException exception. * - *

    The returned type would have the annotations on the given type and nothing else. + *

    The returned type has the same primary annotations as the given type. * * @param type the declared type * @return the unboxed primitive type From 6369565a86b6bbb3db757b3364c0c1e9c679e5e6 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Fri, 24 Jul 2020 11:36:29 -0700 Subject: [PATCH 084/138] Correct/Improve UpperboundFor documentation --- docs/manual/advanced-features.tex | 8 +++-- .../framework/qual/DefaultFor.java | 12 ++++---- .../framework/qual/UpperBoundFor.java | 30 ++++++++++++------- 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/docs/manual/advanced-features.tex b/docs/manual/advanced-features.tex index f923f8275790..e3ac6a6184d5 100644 --- a/docs/manual/advanced-features.tex +++ b/docs/manual/advanced-features.tex @@ -96,7 +96,9 @@ This means that \<@B MyClass> is an invalid type. (Annotations on class declarations may also specify default annotations for uses of the type; see Section~\ref{default-for-use}) -An upper bound can also be specified by the type-system designer using the meta-annotation +If it is not possible to annotate the class's definition (e.g., for +primitives and some library classes), +the type-system designer can specify an upper bound by using the meta-annotation \refqualclass{framework/qual}{UpperBoundFor}. If no annotation is present on a type declaration and if no \<@UpperBoundFor> mentions the type, then @@ -128,8 +130,8 @@ These operations might refine its type. If a user wishes to annotate a method that does type refinement, its formal parameter must be of illegal type \<@A MyClass>, which requires a warning suppression. -If the framework forbid expressions and local variables from having types inconsistent with the class annotation, -then important APIs and common coding paradigms will no longer type-check. +If the framework were to forbid expressions and local variables from having types inconsistent with the class annotation, +then important APIs and common coding paradigms would no longer type-check. Consider the annotation \begin{Verbatim} diff --git a/framework/src/main/java/org/checkerframework/framework/qual/DefaultFor.java b/framework/src/main/java/org/checkerframework/framework/qual/DefaultFor.java index 7aad2178ba09..6d6955628738 100644 --- a/framework/src/main/java/org/checkerframework/framework/qual/DefaultFor.java +++ b/framework/src/main/java/org/checkerframework/framework/qual/DefaultFor.java @@ -7,15 +7,17 @@ import java.lang.annotation.Target; /** - * A meta-annotation applied to the declaration of a type qualifier specifies that the given - * annotation should be the default for. + * A meta-annotation applied to the declaration of a type qualifier. It specifies that the given + * annotation should be the default for: * *

      - *
    • a particular location. - *
    • a use of a particular type. - *
    • a use of a particular kind of type. + *
    • all uses at a particular location, + *
    • all uses of a particular type, and + *
    • all uses of a particular kind of type. *
    * + *

    The default applies to every match for any of this annotation's conditions. + * * @see TypeUseLocation * @see DefaultQualifier * @see DefaultQualifierInHierarchy diff --git a/framework/src/main/java/org/checkerframework/framework/qual/UpperBoundFor.java b/framework/src/main/java/org/checkerframework/framework/qual/UpperBoundFor.java index 8e7e0bb96384..1e46e606130a 100644 --- a/framework/src/main/java/org/checkerframework/framework/qual/UpperBoundFor.java +++ b/framework/src/main/java/org/checkerframework/framework/qual/UpperBoundFor.java @@ -7,14 +7,24 @@ import java.lang.annotation.Target; /** - * A meta-annotation applied to the declaration of a type qualifier specifies that the given - * annotation should be upper bound for. + * A meta-annotation applied to the declaration of a type qualifier. It specifies that the + * annotation should be the upper bound for * *

      - *
    • a use of a particular type. - *
    • a use of a particular kind of type. + *
    • all uses of a particular type, and + *
    • all uses of a particular kind of type. *
    * + * An example is the declaration + * + *
    
    + * {@literal @}DefaultFor(classes=String.class)
    + * {@literal @}interface MyAnno {}
    + * 
    + * + *

    The upper bound applies to every occurrence of the given classes and also to every occurrence + * of the given type kinds. + * * @checker_framework.manual #upper-bound-for-use Upper bound of qualifiers on uses of a given type */ @Documented @@ -22,18 +32,18 @@ @Target(ElementType.ANNOTATION_TYPE) public @interface UpperBoundFor { /** - * Returns {@link TypeKind}s of types for which an annotation should be added by default. + * Returns {@link TypeKind}s of types that get an upper bound. The meta-annotated annotation is + * the upper bound. * - * @return {@link TypeKind}s of types for which an annotation should be added by default + * @return {@link TypeKind}s of types that get an upper bound */ TypeKind[] typeKinds() default {}; /** - * Returns {@link Class}es for which an annotation should be applied. For example, if - * {@code @MyAnno} is meta-annotated with {@code @DefaultFor(classes=String.class)}, then every - * occurrence of {@code String} is actually {@code @MyAnno String}. + * Returns {@link Class}es that should get an upper bound. The meta-annotated annotation is the + * upper bound. * - * @return {@link Class}es for which an annotation should be applied + * @return {@link Class}es that get an upper bound. */ Class[] types() default {}; } From 7b683dc1f5d08796bdb77f2002026e7a0f58dcf0 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Fri, 24 Jul 2020 14:03:35 -0700 Subject: [PATCH 085/138] Improve DefaultTypeHierarchy documentation --- .../framework/type/DefaultTypeHierarchy.java | 92 +++++++++++-------- .../framework/type/SubtypeVisitHistory.java | 19 +++- 2 files changed, 69 insertions(+), 42 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java index 58bda354d261..333d0363022c 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java @@ -8,6 +8,7 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; import javax.lang.model.util.Types; +import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.qual.Covariant; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; @@ -36,8 +37,8 @@ * "Subtyping". * *

    Note: The visit methods of this class must be public but it is intended to be used through a - * TypeHierarchy interface reference which will only allow isSubtype to be called. It does not make - * sense to call the visit methods on their own. + * TypeHierarchy interface reference which will only allow isSubtype to be called. Clients should + * not call the visit methods. */ public class DefaultTypeHierarchy extends AbstractAtmComboVisitor implements TypeHierarchy { @@ -52,13 +53,13 @@ public class DefaultTypeHierarchy extends AbstractAtmComboVisitor // TODO: Incorporate feedback from David/Suzanne // IMPORTANT_NOTE: - + // // For MultigraphQualifierHierarchies, we check the subtyping relationship of each annotation // hierarchy individually. This is done because when comparing a pair of type variables, // sometimes you need to traverse and compare the bounds of two type variables. Other times it // is incorrect to compare the bounds. These two cases can occur simultaneously when comparing - // two hierarchies at once. In this case, comparing both hierarchies simultaneously will leadd - // ot an error. More detail is given below. + // two hierarchies at once. In this case, comparing both hierarchies simultaneously will lead + // to an error. More detail is given below. // // Recall, type variables may or may not have a primary annotation for each individual // hierarchy. When comparing @@ -142,15 +143,16 @@ protected StructuralEqualityComparer createEqualityComparer() { } /** - * Returns true if subtype {@literal <:} supertype. This implementation iterates over all top - * annotations and invokes {@link #isSubtype(AnnotatedTypeMirror, AnnotatedTypeMirror, - * AnnotationMirror)}. Most type systems should not override this method, but instead override - * {@link #isSubtype(AnnotatedTypeMirror, AnnotatedTypeMirror, AnnotationMirror)} or some of the - * {@code visitXXX} methods. + * Returns true if subtype {@literal <:} supertype. + * + *

    This implementation iterates over all top annotations and invokes {@link + * #isSubtype(AnnotatedTypeMirror, AnnotatedTypeMirror, AnnotationMirror)}. Most type systems + * should not override this method, but instead override {@link #isSubtype(AnnotatedTypeMirror, + * AnnotatedTypeMirror, AnnotationMirror)} or some of the {@code visitXXX} methods. * * @param subtype expected subtype * @param supertype expected supertype - * @return true if subtype is actually a subtype of supertype + * @return true if subtype is a subtype of supertype or equal to it */ @Override public boolean isSubtype( @@ -170,9 +172,9 @@ public boolean isSubtype( * * @param subtype expected subtype * @param supertype expected supertype - * @param top the hierarchy for which we want to make a comparison - * @return returns true if {@code subtype} is a subtype of {@code supertype} in the qualifier - * hierarchy whose top is {@code top} + * @param top the top of the hierarchy for which we want to make a comparison + * @return returns true if {@code subtype} is a subtype of, or equal to, {@code supertype} in + * the qualifier hierarchy whose top is {@code top} */ protected boolean isSubtype( final AnnotatedTypeMirror subtype, @@ -200,8 +202,8 @@ protected String defaultErrorMessage( } /** - * Compare the primary annotations of subtype and supertype. Neither type can be missing - * annotations. + * Compare the primary annotations of {@code subtype} and {@code supertype}. Neither type can be + * missing annotations. * * @return true if the primary annotation on subtype {@literal <:} primary annotation on * supertype for the current top. @@ -211,7 +213,7 @@ protected boolean isPrimarySubtype(AnnotatedTypeMirror subtype, AnnotatedTypeMir } /** - * Compare the primary annotations of subtype and supertype. + * Compare the primary annotations of {@code subtype} and {@code supertype}. * * @param annosCanBeEmtpy indicates that annotations may be missing from the typemirror * @return true if the primary annotation on subtype {@literal <:} primary annotation on @@ -233,16 +235,18 @@ protected boolean isPrimarySubtype( } /** - * Compare the primary annotations of subtype and supertype. + * Compare the primary annotations of {@code subtype} and {@code supertype}. * - * @param subtypeAnno annotation we expect to be a subtype - * @param supertypeAnno annotation we expect to be a supertype of subtype + * @param subtypeAnno annotation that may be a subtype + * @param supertypeAnno annotation that may be a supertype * @param annosCanBeEmtpy indicates that annotations may be missing from the typemirror * @return true if subtype {@literal <:} supertype or both annotations are null. False is * returned if one annotation is null and the other is not. */ protected boolean isAnnoSubtype( - AnnotationMirror subtypeAnno, AnnotationMirror supertypeAnno, boolean annosCanBeEmtpy) { + @Nullable AnnotationMirror subtypeAnno, + @Nullable AnnotationMirror supertypeAnno, + boolean annosCanBeEmtpy) { if (annosCanBeEmtpy && subtypeAnno == null && supertypeAnno == null) { return true; } @@ -251,10 +255,9 @@ protected boolean isAnnoSubtype( } /** - * Checks to see if subtype is bottom (if a bottom exists) If there is no explicit bottom then - * false is returned. + * Returns true if subtype is the bottom type in the current hierarchy. * - * @param subtype type to isValid against bottom + * @param subtype type to test against bottom * @return true if subtype's primary annotation is bottom */ protected boolean isBottom(final AnnotatedTypeMirror subtype) { @@ -281,17 +284,22 @@ protected boolean isBottom(final AnnotatedTypeMirror subtype) { } /** - * Check and subtype first determines if the subtype/supertype combination has already been - * visited. If so, it returns true, otherwise add the subtype/supertype combination and then - * make a subtype check + * Like {@link #isSubtype(AnnotatedTypeMirror, AnnotatedTypeMirror)}, but uses a cache to + * prevent infinite recursion on recursive types. + * + * @param subtype a type that may be a subtype + * @param supertype a type that may be a supertype + * @return true if subtype {@literal <:} supertype */ - protected boolean checkAndSubtype( + protected boolean isSubtypeCaching( final AnnotatedTypeMirror subtype, final AnnotatedTypeMirror supertype) { if (visitHistory.contains(subtype, supertype, currentTop)) { + // visitHistory only contains pairs in a subtype relationship. return true; } boolean result = isSubtype(subtype, supertype, currentTop); + // The call to add has no effect if result is false. visitHistory.add(subtype, supertype, currentTop, result); return result; } @@ -326,11 +334,11 @@ protected boolean areEqualInHierarchy( * href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-4.html#jls-4.5.1">JLS section * 4.5.1 "Type Arguments of Parameterized Types". * - * @param inside the "subtype" type argument - * @param outside the "supertype" type argument + * @param inside a type argument of the "subtype" + * @param outside a type argument of the "supertype" * @param canBeCovariant whether or not type arguments are allowed to be covariant - * @return true if inside is contained by outside OR, if canBeCovariant == true, inside is a - * subtype of outside + * @return true if inside is contained by outside, or if canBeCovariant == true and {@code + * inside <: outside} */ protected boolean isContainedBy( final AnnotatedTypeMirror inside, @@ -401,7 +409,7 @@ private boolean isContainedWildcard( AnnotatedTypeMirror castedInside = AnnotatedTypes.castedAsSuper(inside.atypeFactory, inside, outsideUpperBound); - if (!checkAndSubtype(castedInside, outsideUpperBound)) { + if (!isSubtypeCaching(castedInside, outsideUpperBound)) { return false; } @@ -410,7 +418,7 @@ private boolean isContainedWildcard( // tests/all-systems/Issue1991.java crashes without this. return true; } - return canBeCovariant || checkAndSubtype(outsideLowerBound, inside); + return canBeCovariant || isSubtypeCaching(outsideLowerBound, inside); } private boolean ignoreUninferredTypeArgument(AnnotatedTypeMirror type) { @@ -424,6 +432,10 @@ private boolean ignoreUninferredTypeArgument(AnnotatedTypeMirror type) { return false; } + // ------------------------------------------------------------------------ + // The rest of this file is the visitor methods. It is a lot of methods, one for each + // combination of types. + // ------------------------------------------------------------------------ // Arrays as subtypes @@ -1004,10 +1016,16 @@ protected boolean visitIntersectionSupertype( return result; } - /** A type variable is a supertype if its lower bound is above subtype. */ + /** + * A type variable is a supertype if its lower bound is above subtype. + * + * @param subtype a type that might be a subtype + * @param supertype a type that might be a supertype + * @return true if {@code subtype} is a subtype of {@code supertype} + */ protected boolean visitTypevarSupertype( AnnotatedTypeMirror subtype, AnnotatedTypeVariable supertype) { - return checkAndSubtype(subtype, supertype.getLowerBound()); + return isSubtypeCaching(subtype, supertype.getLowerBound()); } /** @@ -1051,7 +1069,7 @@ && isPrimarySubtype(ub, supertype)) { return false; } } - return checkAndSubtype(upperBound, supertype); + return isSubtypeCaching(upperBound, supertype); } /** A union type is a subtype if ALL of its alternatives are subtypes of supertype. */ diff --git a/framework/src/main/java/org/checkerframework/framework/type/SubtypeVisitHistory.java b/framework/src/main/java/org/checkerframework/framework/type/SubtypeVisitHistory.java index d9dd6f32ee34..daf262545f1e 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/SubtypeVisitHistory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/SubtypeVisitHistory.java @@ -11,9 +11,10 @@ * THIS CLASS IS DESIGNED FOR USE WITH DefaultTypeHierarchy, DefaultRawnessComparer, and * StructuralEqualityComparer ONLY. * - *

    VisitHistory keeps track of all visits and allows clients of this class to check whether or - * not they have visited an equivalent pair of AnnotatedTypeMirrors already. This is necessary in - * order to halt visiting on recursive bounds. + *

    VisitHistory tracks triples of (type1, type2, top), where type1 is a subtype of type2. It does + * not track when type1 is not a subtype of type2; such entries are missing from the history. + * Clients of this class can check whether or not they have visited an equivalent pair of + * AnnotatedTypeMirrors already. This is necessary in order to halt visiting on recursive bounds. * *

    This class is primarily used to implement isSubtype(ATM, ATM). The pair of types corresponds * to the subtype and the supertype being checked. A single subtype may be visited more than once, @@ -35,12 +36,20 @@ public SubtypeVisitHistory() { this.visited = new HashMap<>(); } - /** Add a visit for type1 and type2. */ + /** + * Add a visit for type1 and type2. Has no effect if b is false. + * + * @param type1 the first type + * @param type2 the second type + * @param currentTop the top of the relevant type hierarchy; only annotations from that + * hierarchy are considered + * @param b true if type1 is a subtype of type2; if false, this method does nothing + */ public void add( final AnnotatedTypeMirror type1, final AnnotatedTypeMirror type2, AnnotationMirror currentTop, - Boolean b) { + boolean b) { if (!b) { // We only store information about subtype relations that hold. return; From 013ff9abecdb2d5aedf32ed0e36e6669d09770ff Mon Sep 17 00:00:00 2001 From: Martin Kellogg Date: Fri, 24 Jul 2020 14:04:11 -0700 Subject: [PATCH 086/138] make mergeStubsIntoType protected --- .../checkerframework/framework/type/AnnotatedTypeFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java index a6a3f83cac7f..47ae9db0dac0 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java @@ -1306,7 +1306,7 @@ private AnnotatedTypeMirror mergeStubsIntoType(@Nullable AnnotatedTypeMirror typ * @param elt the element from which to read stub types * @return the type, side-effected to add the stub types */ - private AnnotatedTypeMirror mergeStubsIntoType( + protected AnnotatedTypeMirror mergeStubsIntoType( @Nullable AnnotatedTypeMirror type, Element elt) { AnnotatedTypeMirror stubType = stubTypes.getAnnotatedTypeMirror(elt); if (stubType != null) { From 941fd23a5029ac306fa074dc4be58e6075f13772 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Fri, 24 Jul 2020 14:33:48 -0700 Subject: [PATCH 087/138] Enhance message for argument.type.incompatible --- checker/jtreg/sortwarnings/ErrorOrders.out | 56 +++++------ checker/jtreg/stubs/issue2147/WithStub.out | 2 +- checker/jtreg/stubs/issue2147/WithoutStub.out | 4 +- checker/jtreg/tainting/NewClass.out | 2 +- .../checker/formatter/FormatterVisitor.java | 6 ++ .../checker/guieffect/GuiEffectVisitor.java | 9 +- .../i18nformatter/I18nFormatterTreeUtil.java | 9 ++ .../i18nformatter/I18nFormatterVisitor.java | 8 ++ .../tests/command-line/issue618/expected.txt | 2 +- docs/examples/fenum-extension/Expected.txt | 8 +- docs/examples/lombok/Makefile | 2 +- .../examples/subtyping-extension/Expected.txt | 2 +- .../common/basetype/BaseTypeValidator.java | 8 +- .../common/basetype/BaseTypeVisitor.java | 92 ++++++++++++++++--- .../common/basetype/messages.properties | 4 +- .../framework/flow/CFAbstractTransfer.java | 5 + 16 files changed, 162 insertions(+), 57 deletions(-) diff --git a/checker/jtreg/sortwarnings/ErrorOrders.out b/checker/jtreg/sortwarnings/ErrorOrders.out index ceddf61d07bd..87f467eac8e7 100644 --- a/checker/jtreg/sortwarnings/ErrorOrders.out +++ b/checker/jtreg/sortwarnings/ErrorOrders.out @@ -26,46 +26,46 @@ ErrorOrders.java:24:47: compiler.err.proc.messager: [expression.unparsable.type. ErrorOrders.java:24:55: compiler.err.proc.messager: [assignment.type.incompatible] incompatible types in assignment. found : @LTLengthOf(value="p2") int required: @LTLengthOf(value="[error for expression: This isn't an expression; error: Invalid 'This isn't an expression' because is an invalid expression]") int -ErrorOrders.java:29:15: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +ErrorOrders.java:29:15: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter p1 of test4. found : @LowerBoundUnknown int required: @GTENegativeOne int -ErrorOrders.java:29:15: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +ErrorOrders.java:29:15: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter p1 of test4. found : @UpperBoundUnknown int required: @UpperBoundBottom int -ErrorOrders.java:29:24: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +ErrorOrders.java:29:24: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter p2 of test4. found : @LowerBoundUnknown int required: @GTENegativeOne int -ErrorOrders.java:29:24: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +ErrorOrders.java:29:24: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter p2 of test4. found : @UpperBoundUnknown int required: @UpperBoundBottom int -ErrorOrders.java:29:25: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +ErrorOrders.java:29:25: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter p1 of test4. found : @LowerBoundUnknown int required: @GTENegativeOne int -ErrorOrders.java:29:25: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +ErrorOrders.java:29:25: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter p1 of test4. found : @UpperBoundUnknown int required: @UpperBoundBottom int -ErrorOrders.java:29:29: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +ErrorOrders.java:29:29: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter p2 of test4. found : @LowerBoundUnknown int required: @GTENegativeOne int -ErrorOrders.java:29:29: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +ErrorOrders.java:29:29: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter p2 of test4. found : @UpperBoundUnknown int required: @UpperBoundBottom int -ErrorOrders.java:29:33: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +ErrorOrders.java:29:33: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter p3 of test4. found : @UnknownVal int @UnknownVal [] required: @UnknownVal int [] -ErrorOrders.java:29:37: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +ErrorOrders.java:29:37: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter p4 of test4. found : @SameLenUnknown int @SameLen("p4") [] required: @SameLenUnknown int @SameLenBottom [] -ErrorOrders.java:29:41: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +ErrorOrders.java:29:41: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter p5 of test4. found : @UnknownVal int required: int -ErrorOrders.java:29:46: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +ErrorOrders.java:29:46: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter p3 of test4. found : @UnknownVal int @UnknownVal [] required: @UnknownVal int [] -ErrorOrders.java:29:50: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +ErrorOrders.java:29:50: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter p4 of test4. found : @SameLenUnknown int @SameLen("p4") [] required: @SameLenUnknown int @SameLenBottom [] -ErrorOrders.java:29:54: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +ErrorOrders.java:29:54: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter p5 of test4. found : @UnknownVal int required: int ErrorOrders.java:33:47: compiler.err.proc.messager: [expression.unparsable.type.invalid] Expression invalid in dependent type annotation: [error for expression: This isn't an expression; error: Invalid 'This isn't an expression' because is an invalid expression] @@ -95,46 +95,46 @@ ErrorOrders.java:56:47: compiler.err.proc.messager: [expression.unparsable.type. ErrorOrders.java:56:55: compiler.err.proc.messager: [assignment.type.incompatible] incompatible types in assignment. found : @LTLengthOf(value="p2") int required: @LTLengthOf(value="[error for expression: This isn't an expression; error: Invalid 'This isn't an expression' because is an invalid expression]") int -ErrorOrders.java:61:15: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +ErrorOrders.java:61:15: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter p1 of test4. found : @LowerBoundUnknown int required: @GTENegativeOne int -ErrorOrders.java:61:15: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +ErrorOrders.java:61:15: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter p1 of test4. found : @UpperBoundUnknown int required: @UpperBoundBottom int -ErrorOrders.java:61:24: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +ErrorOrders.java:61:24: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter p2 of test4. found : @LowerBoundUnknown int required: @GTENegativeOne int -ErrorOrders.java:61:24: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +ErrorOrders.java:61:24: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter p2 of test4. found : @UpperBoundUnknown int required: @UpperBoundBottom int -ErrorOrders.java:61:25: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +ErrorOrders.java:61:25: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter p1 of test4. found : @LowerBoundUnknown int required: @GTENegativeOne int -ErrorOrders.java:61:25: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +ErrorOrders.java:61:25: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter p1 of test4. found : @UpperBoundUnknown int required: @UpperBoundBottom int -ErrorOrders.java:61:29: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +ErrorOrders.java:61:29: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter p2 of test4. found : @LowerBoundUnknown int required: @GTENegativeOne int -ErrorOrders.java:61:29: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +ErrorOrders.java:61:29: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter p2 of test4. found : @UpperBoundUnknown int required: @UpperBoundBottom int -ErrorOrders.java:61:33: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +ErrorOrders.java:61:33: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter p3 of test4. found : @UnknownVal int @UnknownVal [] required: @UnknownVal int [] -ErrorOrders.java:61:37: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +ErrorOrders.java:61:37: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter p4 of test4. found : @SameLenUnknown int @SameLen("p4") [] required: @SameLenUnknown int @SameLenBottom [] -ErrorOrders.java:61:41: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +ErrorOrders.java:61:41: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter p5 of test4. found : @UnknownVal int required: int -ErrorOrders.java:61:46: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +ErrorOrders.java:61:46: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter p3 of test4. found : @UnknownVal int @UnknownVal [] required: @UnknownVal int [] -ErrorOrders.java:61:50: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +ErrorOrders.java:61:50: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter p4 of test4. found : @SameLenUnknown int @SameLen("p4") [] required: @SameLenUnknown int @SameLenBottom [] -ErrorOrders.java:61:54: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +ErrorOrders.java:61:54: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter p5 of test4. found : @UnknownVal int required: int 49 errors diff --git a/checker/jtreg/stubs/issue2147/WithStub.out b/checker/jtreg/stubs/issue2147/WithStub.out index f9e01800442a..bb3dd68ac929 100644 --- a/checker/jtreg/stubs/issue2147/WithStub.out +++ b/checker/jtreg/stubs/issue2147/WithStub.out @@ -1,4 +1,4 @@ -EnumStubTest.java:16:31: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +EnumStubTest.java:16:31: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter sEnum of requireEnum. found : @Tainted SampleEnum required: @Untainted SampleEnum 1 error diff --git a/checker/jtreg/stubs/issue2147/WithoutStub.out b/checker/jtreg/stubs/issue2147/WithoutStub.out index d543064e4817..2f01ee07da0b 100644 --- a/checker/jtreg/stubs/issue2147/WithoutStub.out +++ b/checker/jtreg/stubs/issue2147/WithoutStub.out @@ -1,7 +1,7 @@ -EnumStubTest.java:15:31: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +EnumStubTest.java:15:31: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter sEnum of requireEnum. found : @Tainted SampleEnum required: @Untainted SampleEnum -EnumStubTest.java:16:31: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +EnumStubTest.java:16:31: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter sEnum of requireEnum. found : @Tainted SampleEnum required: @Untainted SampleEnum 2 errors diff --git a/checker/jtreg/tainting/NewClass.out b/checker/jtreg/tainting/NewClass.out index b589c23b8ae2..360dc13cf8f3 100644 --- a/checker/jtreg/tainting/NewClass.out +++ b/checker/jtreg/tainting/NewClass.out @@ -1,4 +1,4 @@ -NewClass.java:18:49: compiler.err.proc.messager: [argument.type.incompatible] incompatible types in argument. +NewClass.java:18:49: compiler.err.proc.messager: [argument.type.incompatible] incompatible argument for parameter o of get. found : @Tainted Object required: @Untainted Object 1 error diff --git a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterVisitor.java b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterVisitor.java index 0ac09fbd6c6b..01d6b478c4ab 100644 --- a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterVisitor.java @@ -9,6 +9,7 @@ import java.util.List; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; @@ -88,9 +89,14 @@ public Void visitMethodInvocation(MethodInvocationTree node, Void p) { default: if (!fc.isValidParameter(formatCat, paramType)) { // II.3 + ExecutableElement method = + TreeUtils.elementFromUse(node); + Name methodName = method.getSimpleName(); tu.failure( param, "argument.type.incompatible", + "", // parameter name is not useful + methodName, paramType, formatCat); } diff --git a/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectVisitor.java b/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectVisitor.java index 5a2de6711bc1..3e1feb0e7938 100644 --- a/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/guieffect/GuiEffectVisitor.java @@ -18,7 +18,9 @@ import java.util.Set; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import org.checkerframework.checker.guieffect.qual.AlwaysSafe; import org.checkerframework.checker.guieffect.qual.PolyUI; @@ -487,6 +489,9 @@ private void scanUp(TreePath path) { List args = invocationTree.getArguments(); ParameterizedExecutableType mType = atypeFactory.methodFromUse(invocationTree); AnnotatedExecutableType invokedMethod = mType.executableType; + ExecutableElement method = invokedMethod.getElement(); + Name methodName = method.getSimpleName(); + List methodParams = method.getParameters(); List argsTypes = AnnotatedTypes.expandVarArgs( atypeFactory, invokedMethod, invocationTree.getArguments()); @@ -497,7 +502,9 @@ private void scanUp(TreePath path) { argsTypes.get(i), atypeFactory.getAnnotatedType(args.get(i)), args.get(i), - "argument.type.incompatible"); + "argument.type.incompatible", + methodParams.get(i), + methodName); } } break; diff --git a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTreeUtil.java b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTreeUtil.java index 50f19dab46ab..f11bbcdf0734 100644 --- a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTreeUtil.java +++ b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterTreeUtil.java @@ -266,6 +266,15 @@ public I18nFormatCall( initialCheck(theargs, method, node, methodAnno); } + /** + * Returns the AST node for the call. + * + * @return the AST node for the call + */ + public MethodInvocationTree getTree() { + return tree; + } + @Override public String toString() { return this.tree.toString(); diff --git a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterVisitor.java b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterVisitor.java index f50d16329a11..6ab4583691ad 100644 --- a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterVisitor.java @@ -3,6 +3,8 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.formatter.FormatterTreeUtil.InvocationType; @@ -16,6 +18,7 @@ import org.checkerframework.dataflow.cfg.node.MethodInvocationNode; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.TreeUtils; /** * Whenever a method with {@link I18nFormatFor} annotation is invoked, it will perform the format @@ -87,9 +90,14 @@ private void checkInvocationFormatFor(I18nFormatCall fc) { break; default: if (!fc.isValidParameter(formatCat, paramType)) { + ExecutableElement method = + TreeUtils.elementFromUse(fc.getTree()); + Name methodName = method.getSimpleName(); tu.failure( param, "argument.type.incompatible", + "", // parameter name is not useful + methodName, paramType, formatCat); } diff --git a/checker/tests/command-line/issue618/expected.txt b/checker/tests/command-line/issue618/expected.txt index 41e5f5b1bf9c..7be331eec377 100644 --- a/checker/tests/command-line/issue618/expected.txt +++ b/checker/tests/command-line/issue618/expected.txt @@ -1,4 +1,4 @@ -TwoCheckers.java:28: error: [argument.type.incompatible] incompatible types in argument. +TwoCheckers.java:28: error: [argument.type.incompatible] incompatible argument for parameter b of requiresUntainted. requiresUntainted(a); ^ found : @Tainted String diff --git a/docs/examples/fenum-extension/Expected.txt b/docs/examples/fenum-extension/Expected.txt index 87d3cb160639..71361f47cc40 100644 --- a/docs/examples/fenum-extension/Expected.txt +++ b/docs/examples/fenum-extension/Expected.txt @@ -43,12 +43,12 @@ Demo.java:29: error: [assignment.type.incompatible] incompatible types in assign ^ found : @Fenum("B") int required: @Fenum("A") int -Demo.java:32: error: [argument.type.incompatible] incompatible types in argument. +Demo.java:32: error: [argument.type.incompatible] incompatible argument for parameter p of fenumArg. fenumArg(5); // Direct use of value forbidden! ^ found : @FenumUnqualified int required: @Fenum("A") int -Demo.java:33: error: [argument.type.incompatible] incompatible types in argument. +Demo.java:33: error: [argument.type.incompatible] incompatible argument for parameter p of fenumArg. fenumArg(TestStatic.BCONST1); // Incompatible fenums forbidden! ^ found : @Fenum("B") int @@ -63,12 +63,12 @@ Demo.java:37: error: [assignment.type.incompatible] incompatible types in assign ^ found : @Fenum("A") int required: @MyFenum int -Demo.java:40: error: [argument.type.incompatible] incompatible types in argument. +Demo.java:40: error: [argument.type.incompatible] incompatible argument for parameter p of myFenumArg. myFenumArg(8); // Direct use of value forbidden! ^ found : @FenumUnqualified int required: @MyFenum int -Demo.java:41: error: [argument.type.incompatible] incompatible types in argument. +Demo.java:41: error: [argument.type.incompatible] incompatible argument for parameter p of myFenumArg. myFenumArg(TestStatic.BCONST2); // Incompatible fenums forbidden! ^ found : @Fenum("B") int diff --git a/docs/examples/lombok/Makefile b/docs/examples/lombok/Makefile index 5003a44887bb..f01c4d37dd1e 100644 --- a/docs/examples/lombok/Makefile +++ b/docs/examples/lombok/Makefile @@ -4,7 +4,7 @@ # So check for both the error message and make sure it is for the right assignment. all: clean - ../../../gradlew build > Out.txt 2>&1 - (grep -qF "User.java:9: error: [argument.type.incompatible] incompatible types in argument." Out.txt \ + (grep -qF "User.java:9: error: [argument.type.incompatible] incompatible argument for parameter y of y." Out.txt \ && grep -qF "Foo.java:12: error: [assignment.type.incompatible] incompatible types in assignment." Out.txt \ && grep -qF "y = null; // error" Out.txt) \ || (echo "===== start of Out.txt =====" && cat Out.txt && echo "===== end of Out.txt =====" && false) diff --git a/docs/examples/subtyping-extension/Expected.txt b/docs/examples/subtyping-extension/Expected.txt index 6358c8f797d1..8370787c3cd1 100644 --- a/docs/examples/subtyping-extension/Expected.txt +++ b/docs/examples/subtyping-extension/Expected.txt @@ -1,7 +1,7 @@ Demo.java:13: warning: [cast.unsafe] cast from "@PossiblyUnencrypted String" to "@Encrypted String" cannot be statically verified return (@Encrypted String) new String(b); ^ -Demo.java:36: error: [argument.type.incompatible] incompatible types in argument. +Demo.java:36: error: [argument.type.incompatible] incompatible argument for parameter msg of sendOverTheInternet. sendOverTheInternet(password); // invalid ^ found : @PossiblyUnencrypted String diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java index f5a7e125d0cf..2239e5016642 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java @@ -460,7 +460,13 @@ protected Void visitParameterizedType(AnnotatedDeclaredType type, ParameterizedT List bounds = atypeFactory.typeVariablesFromUse(type, element); - visitor.checkTypeArguments(tree, bounds, type.getTypeArguments(), tree.getTypeArguments()); + visitor.checkTypeArguments( + tree, + bounds, + type.getTypeArguments(), + tree.getTypeArguments(), + element.getSimpleName(), + element.getTypeParameters()); return null; } 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 4e43171058c0..287e26d69f8c 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -51,6 +51,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.StringJoiner; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; @@ -1368,11 +1369,19 @@ public Void visitMethodInvocation(MethodInvocationTree node, Void p) { paramBounds.add(param.getBounds()); } - checkTypeArguments(node, paramBounds, typeargs, node.getTypeArguments()); + ExecutableElement method = invokedMethod.getElement(); + Name methodName = method.getSimpleName(); + checkTypeArguments( + node, + paramBounds, + typeargs, + node.getTypeArguments(), + methodName, + invokedMethod.getTypeVariables()); List params = AnnotatedTypes.expandVarArgs(atypeFactory, invokedMethod, node.getArguments()); - checkArguments(params, node.getArguments()); + checkArguments(params, node.getArguments(), methodName, method.getParameters()); checkVarargs(invokedMethod, node); if (ElementUtils.isMethod( @@ -1674,22 +1683,31 @@ public Void visitNewClass(NewClassTree node, Void p) { } ParameterizedExecutableType fromUse = atypeFactory.constructorFromUse(node); - AnnotatedExecutableType constructor = fromUse.executableType; + AnnotatedExecutableType constructorType = fromUse.executableType; List typeargs = fromUse.typeArgs; List passedArguments = node.getArguments(); List params = - AnnotatedTypes.expandVarArgs(atypeFactory, constructor, passedArguments); + AnnotatedTypes.expandVarArgs(atypeFactory, constructorType, passedArguments); + + ExecutableElement constructor = constructorType.getElement(); + Name constructorName = constructor.getSimpleName(); - checkArguments(params, passedArguments); - checkVarargs(constructor, node); + checkArguments(params, passedArguments, constructorName, constructor.getParameters()); + checkVarargs(constructorType, node); List paramBounds = new ArrayList<>(); - for (AnnotatedTypeVariable param : constructor.getTypeVariables()) { + for (AnnotatedTypeVariable param : constructorType.getTypeVariables()) { paramBounds.add(param.getBounds()); } - checkTypeArguments(node, paramBounds, typeargs, node.getTypeArguments()); + checkTypeArguments( + node, + paramBounds, + typeargs, + node.getTypeArguments(), + constructorName, + constructor.getTypeParameters()); boolean valid = validateTypeOf(node); @@ -1698,7 +1716,7 @@ public Void visitNewClass(NewClassTree node, Void p) { if (atypeFactory.getDependentTypesHelper() != null) { atypeFactory.getDependentTypesHelper().checkType(dt, node); } - checkConstructorInvocation(dt, constructor, node); + checkConstructorInvocation(dt, constructorType, node); } // Do not call super, as that would observe the arguments without // a set assignment context. @@ -2683,7 +2701,9 @@ protected void checkTypeArguments( Tree toptree, List paramBounds, List typeargs, - List typeargTrees) { + List typeargTrees, + Name typeOrMethodName, + List paramNames) { // System.out.printf("BaseTypeVisitor.checkTypeArguments: %s, TVs: %s, TAs: %s, TATs: %s%n", // toptree, paramBounds, typeargs, typeargTrees); @@ -2726,18 +2746,25 @@ protected void checkTypeArguments( // The type arguments were inferred, report the error on the method invocation. reportErrorToTree = toptree; } else { - reportErrorToTree = typeargTrees.get(typeargs.indexOf(typeArg)); + reportErrorToTree = typeargTrees.get(i); } checkHasQualifierParameterAsTypeArgument(typeArg, paramUpperBound, toptree); commonAssignmentCheck( - paramUpperBound, typeArg, reportErrorToTree, "type.argument.type.incompatible"); + paramUpperBound, + typeArg, + reportErrorToTree, + "type.argument.type.incompatible", + paramNames.get(i), + typeOrMethodName); if (!atypeFactory.getTypeHierarchy().isSubtype(bounds.getLowerBound(), typeArg)) { FoundRequired fr = FoundRequired.of(typeArg, bounds); checker.reportError( reportErrorToTree, "type.argument.type.incompatible", + paramNames.get(i), + typeOrMethodName, fr.found, fr.required); } @@ -2921,10 +2948,14 @@ protected void checkConstructorInvocation( * because it replaces a varargs parameter by multiple parameters with the vararg's element * type. * @param passedArgs the expressions passed to the corresponding types + * @param executableName the name of the method or constructor being called + * @param paramNames the names of the callee's formal parameters */ protected void checkArguments( List requiredArgs, - List passedArgs) { + List passedArgs, + Name executableName, + List paramNames) { int size = requiredArgs.size(); assert size == passedArgs.size() : "mismatch between required args (" @@ -2932,6 +2963,18 @@ protected void checkArguments( + ") and passed args (" + passedArgs + ")"; + int maxParamNamesIndex = paramNames.size() - 1; + // Rather weak assertion, due to how varargs parameters are treated. + assert size >= maxParamNamesIndex + : String.format( + "mismatched lengths %d %d %d checkArguments(%s, %s, %s, %s)", + size, + passedArgs.size(), + paramNames.size(), + listToString(requiredArgs), + listToString(passedArgs), + executableName, + listToString(paramNames)); Pair preAssCtxt = visitorState.getAssignmentContext(); try { @@ -2939,7 +2982,12 @@ protected void checkArguments( visitorState.setAssignmentContext( Pair.of((Tree) null, (AnnotatedTypeMirror) requiredArgs.get(i))); commonAssignmentCheck( - requiredArgs.get(i), passedArgs.get(i), "argument.type.incompatible"); + requiredArgs.get(i), + passedArgs.get(i), + "argument.type.incompatible", + // TODO: for expanded varargs parameters, maybe adjust the name + paramNames.get(Math.min(i, maxParamNamesIndex)), + executableName); // Also descend into the argument within the correct assignment // context. scan(passedArgs.get(i), null); @@ -2949,6 +2997,22 @@ protected void checkArguments( } } + // com.sun.tools.javac.util.List has a toString that does not include surrounding "[...]", + // making it hard to interpret in messages. + /** + * Produce a printed representation of a list, in the standard format with surrounding "[...]". + * + * @param lst a list to format + * @return the printed representation of the list + */ + private String listToString(List lst) { + StringJoiner result = new StringJoiner(",", "[", "]"); + for (Object elt : lst) { + result.add(elt.toString()); + } + return result.toString(); + } + /** * Returns true if both types are type variables and outer contains inner. Outer contains inner * implies: {@literal inner.upperBound <: outer.upperBound outer.lowerBound <: 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 b93142fb76e1..325b1822d9d3 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/messages.properties +++ b/framework/src/main/java/org/checkerframework/common/basetype/messages.properties @@ -13,8 +13,8 @@ vector.copyinto.type.incompatible=incompatible component type in Vector.copyinto return.type.incompatible=incompatible types in return.%ntype of expression: %s%nmethod return type: %s annotation.type.incompatible=incompatible types in annotation.%nfound : %s%nrequired: %s conditional.type.incompatible=incompatible types in conditional expression.%nfound : %s%nrequired: %s -type.argument.type.incompatible=incompatible types in type argument.%nfound : %s%nrequired: %s -argument.type.incompatible=incompatible types in argument.%nfound : %s%nrequired: %s +type.argument.type.incompatible=incompatible type argument for type parameter %s of %s.%nfound : %s%nrequired: %s +argument.type.incompatible=incompatible argument for parameter %s of %s.%nfound : %s%nrequired: %s varargs.type.incompatible=incompatible types in varargs.%nfound : %s%nrequired: %s type.incompatible=incompatible types.%nfound : %s%nrequired: %s bound.type.incompatible=incompatible bounds in %s%ntype: %s%nupper bound: %s%nlower bound: %s 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 c6a34bf7f0c4..fd73f878289a 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java @@ -157,6 +157,11 @@ protected V finishValue(V value, S store) { * transfer function. By default, the value is not changed but subclasses might decide to * implement some functionality. The store at this position is also passed (two stores, as the * result is a {@link ConditionalTransferResult}. + * + * @param value the value to finish + * @param thenStore the "then" store + * @param elseStore the "else" store + * @return the finished value */ protected V finishValue(V value, S thenStore, S elseStore) { return value; From 53b4d5138a4099866fe776d72729b67757b23bc7 Mon Sep 17 00:00:00 2001 From: Priti Chattopadhyay <35490584+PRITI1999@users.noreply.github.com> Date: Sat, 25 Jul 2020 03:52:36 +0530 Subject: [PATCH 088/138] Retreive @MinLen annotations from ArrayAccess receivers (#3502) --- checker/tests/index/Issue3207.java | 2 -- .../common/value/ValueAnnotatedTypeFactory.java | 12 ++++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/checker/tests/index/Issue3207.java b/checker/tests/index/Issue3207.java index 4cef00ecf164..b2f72a87e2c0 100644 --- a/checker/tests/index/Issue3207.java +++ b/checker/tests/index/Issue3207.java @@ -1,7 +1,5 @@ // Test case for https://tinyurl.com/cfissue/3207 -// @skip-test until the issue is fixed - import org.checkerframework.checker.index.qual.LTLengthOf; import org.checkerframework.common.value.qual.MinLen; diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java index cb98ed066429..3f42d8203248 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java @@ -1229,6 +1229,18 @@ public int getMinLenFromString(String sequenceExpression, Tree tree, TreePath cu (FlowExpressions.ArrayCreation) expressionObj; // This is only expected to support array creations in varargs methods return arrayCreation.getInitializers().size(); + } else if (expressionObj instanceof FlowExpressions.ArrayAccess) { + List annoList = + expressionObj.getType().getAnnotationMirrors(); + for (AnnotationMirror anno : annoList) { + String ANNO_NAME = anno.getAnnotationType().toString(); + if (ANNO_NAME.equals(MINLEN_NAME)) { + return getMinLenValue(canonicalAnnotation(anno)); + } else if (ANNO_NAME.equals(ARRAYLEN_NAME) + || ANNO_NAME.equals(ARRAYLENRANGE_NAME)) { + return getMinLenValue(anno); + } + } } lengthAnno = getAnnotationFromReceiver(expressionObj, tree, ArrayLenRange.class); From 79f0c7c0c74f7233aa8ef94c8522094034e5d482 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 25 Jul 2020 14:39:14 -0700 Subject: [PATCH 089/138] Fix spelling --- checker/tests/nullness/java8/Issue1098.java | 2 +- .../tests/nullness/java8/Issue1098NoJdk.java | 2 +- .../reflection/DefaultReflectionResolver.java | 10 +++---- .../framework/type/AsSuperVisitor.java | 29 +++++++++++-------- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/checker/tests/nullness/java8/Issue1098.java b/checker/tests/nullness/java8/Issue1098.java index f37c81f7d71b..aa63d5d92f20 100644 --- a/checker/tests/nullness/java8/Issue1098.java +++ b/checker/tests/nullness/java8/Issue1098.java @@ -10,7 +10,7 @@ void cls(Class p1, T p2) {} void use() { opt(Optional.empty(), null); - // TODO: false positive, because type agrument inference does not account for @Covariant. + // TODO: false positive, because type argument inference does not account for @Covariant. // See https://github.com/typetools/checker-framework/issues/979. // :: error: (argument.type.incompatible) cls(this.getClass(), null); diff --git a/checker/tests/nullness/java8/Issue1098NoJdk.java b/checker/tests/nullness/java8/Issue1098NoJdk.java index 54265353cdbb..da7c3ee4f4d7 100644 --- a/checker/tests/nullness/java8/Issue1098NoJdk.java +++ b/checker/tests/nullness/java8/Issue1098NoJdk.java @@ -12,7 +12,7 @@ class Issue1098NoJdk { void cls2(Class p1, T p2) {} void use2(MyObject ths) { - // TODO: false positive, because type agrument inference does not account for @Covariant. + // TODO: false positive, because type argument inference does not account for @Covariant. // See https://github.com/typetools/checker-framework/issues/979. // :: error: (argument.type.incompatible) cls2(ths.getMyClass(), null); diff --git a/framework/src/main/java/org/checkerframework/common/reflection/DefaultReflectionResolver.java b/framework/src/main/java/org/checkerframework/common/reflection/DefaultReflectionResolver.java index f8668a38f0cf..760251e615b7 100644 --- a/framework/src/main/java/org/checkerframework/common/reflection/DefaultReflectionResolver.java +++ b/framework/src/main/java/org/checkerframework/common/reflection/DefaultReflectionResolver.java @@ -132,7 +132,7 @@ private ParameterizedExecutableType resolveMethodCall( // and parameter types for (MethodInvocationTree resolvedTree : possibleMethods) { debugReflection("Resolved method invocation: " + resolvedTree); - if (!checkMethodAgruments(resolvedTree)) { + if (!checkMethodArguments(resolvedTree)) { debugReflection( "Spoofed tree's arguments did not match declaration" + resolvedTree); // Calling methodFromUse on these sorts of trees will cause an assertion to fail in @@ -206,18 +206,18 @@ private ParameterizedExecutableType resolveMethodCall( return origResult; } - private boolean checkMethodAgruments(MethodInvocationTree resolvedTree) { + private boolean checkMethodArguments(MethodInvocationTree resolvedTree) { // type.getKind() == actualType.getKind() ExecutableElement methodDecl = TreeUtils.elementFromUse(resolvedTree); - return checkAgruments(methodDecl.getParameters(), resolvedTree.getArguments()); + return checkArguments(methodDecl.getParameters(), resolvedTree.getArguments()); } private boolean checkNewClassArguments(NewClassTree resolvedTree) { ExecutableElement methodDecl = TreeUtils.elementFromUse(resolvedTree); - return checkAgruments(methodDecl.getParameters(), resolvedTree.getArguments()); + return checkArguments(methodDecl.getParameters(), resolvedTree.getArguments()); } - private boolean checkAgruments( + private boolean checkArguments( List parameters, List arguments) { if (parameters.size() != arguments.size()) { return false; diff --git a/framework/src/main/java/org/checkerframework/framework/type/AsSuperVisitor.java b/framework/src/main/java/org/checkerframework/framework/type/AsSuperVisitor.java index a6d3eec9b829..2b97ccb77302 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AsSuperVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AsSuperVisitor.java @@ -34,8 +34,13 @@ public class AsSuperVisitor extends AbstractAtmComboVisitor T asSuper(AnnotatedTypeMirror type, T sup } private void reset() { - isUninferredTypeAgrument = false; + isUninferredTypeArgument = false; } @Override @@ -154,7 +159,7 @@ private AnnotatedTypeMirror errorTypeNotErasedSubtypeOfSuperType( // Any type can be converted to a String return visit(annotatedTypeFactory.getStringType(type), superType, p); } - if (isUninferredTypeAgrument) { + if (isUninferredTypeArgument) { return copyPrimaryAnnos(type, superType); } throw new BugInCF( @@ -727,12 +732,12 @@ public AnnotatedTypeMirror visitUnion_Wildcard( private AnnotatedTypeMirror visitWildcard_NotTypvarNorWildcard( AnnotatedWildcardType type, AnnotatedTypeMirror superType, Void p) { - boolean oldIsUninferredTypeArgument = isUninferredTypeAgrument; + boolean oldIsUninferredTypeArgument = isUninferredTypeArgument; if (type.isUninferredTypeArgument()) { - isUninferredTypeAgrument = true; + isUninferredTypeArgument = true; } AnnotatedTypeMirror asSuper = visit(type.getExtendsBound(), superType, p); - isUninferredTypeAgrument = oldIsUninferredTypeArgument; + isUninferredTypeArgument = oldIsUninferredTypeArgument; annotatedTypeFactory.addDefaultAnnotations(superType); return copyPrimaryAnnos(type, asSuper); @@ -765,9 +770,9 @@ public AnnotatedTypeMirror visitWildcard_Primitive( @Override public AnnotatedTypeMirror visitWildcard_Typevar( AnnotatedWildcardType type, AnnotatedTypeVariable superType, Void p) { - boolean oldIsUninferredTypeArgument = isUninferredTypeAgrument; + boolean oldIsUninferredTypeArgument = isUninferredTypeArgument; if (type.isUninferredTypeArgument()) { - isUninferredTypeAgrument = true; + isUninferredTypeArgument = true; } AnnotatedTypeMirror upperBound = visit(type.getExtendsBound(), superType.getUpperBound(), p); @@ -783,7 +788,7 @@ public AnnotatedTypeMirror visitWildcard_Typevar( lowerBound = asSuperTypevarLowerBound(type.getSuperBound(), superType, p); } superType.setLowerBound(lowerBound); - isUninferredTypeAgrument = oldIsUninferredTypeArgument; + isUninferredTypeArgument = oldIsUninferredTypeArgument; annotatedTypeFactory.addDefaultAnnotations(superType); return copyPrimaryAnnos(type, superType); @@ -798,9 +803,9 @@ public AnnotatedTypeMirror visitWildcard_Union( @Override public AnnotatedTypeMirror visitWildcard_Wildcard( AnnotatedWildcardType type, AnnotatedWildcardType superType, Void p) { - boolean oldIsUninferredTypeArgument = isUninferredTypeAgrument; + boolean oldIsUninferredTypeArgument = isUninferredTypeArgument; if (type.isUninferredTypeArgument()) { - isUninferredTypeAgrument = true; + isUninferredTypeArgument = true; superType.setUninferredTypeArgument(); } if (types.isSubtype( @@ -834,7 +839,7 @@ public AnnotatedTypeMirror visitWildcard_Wildcard( lowerBound = asSuperWildcardLowerBound(type.getSuperBound(), superType, p); } superType.setSuperBound(lowerBound); - isUninferredTypeAgrument = oldIsUninferredTypeArgument; + isUninferredTypeArgument = oldIsUninferredTypeArgument; annotatedTypeFactory.addDefaultAnnotations(superType); return copyPrimaryAnnos(type, superType); From 683a390067625290174caff4d0a840d04bd95803 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sun, 26 Jul 2020 09:36:57 -0700 Subject: [PATCH 090/138] Javadoc fixes --- .../checkerframework/common/basetype/BaseTypeVisitor.java | 2 +- .../framework/type/AnnotatedTypeFactory.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 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 287e26d69f8c..35547cc0c339 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -1474,7 +1474,7 @@ protected void checkThisOrSuperConstructorCall( *

    Note it's required that type checking for each element in varargs is executed by the * caller before or after calling this method. * - * @see #checkArguments(List, List) + * @see #checkArguments * @param invokedMethod the method type to be invoked * @param tree method or constructor invocation tree */ diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java index 47ae9db0dac0..09d98182d439 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java @@ -2029,8 +2029,8 @@ public ParameterizedExecutableType( *

    The return type is a pair of the type of the invoked method and the (inferred) type * arguments. Note that neither the explicitly passed nor the inferred type arguments are * guaranteed to be subtypes of the corresponding upper bounds. See method {@link - * org.checkerframework.common.basetype.BaseTypeVisitor#checkTypeArguments(Tree, List, List, - * List)} for the checks of type argument well-formedness. + * org.checkerframework.common.basetype.BaseTypeVisitor#checkTypeArguments} for the checks of + * type argument well-formedness. * *

    Note that "this" and "super" constructor invocations are also handled by this method * (explicit or implicit ones, at the beginning of a constructor). Method {@link @@ -2210,8 +2210,8 @@ protected void adaptGetClassReturnTypeToReceiver( *

    The return type is a pair of the type of the invoked constructor and the (inferred) type * arguments. Note that neither the explicitly passed nor the inferred type arguments are * guaranteed to be subtypes of the corresponding upper bounds. See method {@link - * org.checkerframework.common.basetype.BaseTypeVisitor#checkTypeArguments(Tree, List, List, - * List)} for the checks of type argument well-formedness. + * org.checkerframework.common.basetype.BaseTypeVisitor#checkTypeArguments} for the checks of + * type argument well-formedness. * *

    Note that "this" and "super" constructor invocations are handled by method {@link * #methodFromUse}. This method only handles constructor invocations in a "new" expression. From a7266833cf495812a396c3d2ad76f07278b68d2d Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sun, 26 Jul 2020 10:17:01 -0700 Subject: [PATCH 091/138] Adapt to JDK `@Covariant` annotations --- checker/tests/nullness/java8/Issue1633.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/checker/tests/nullness/java8/Issue1633.java b/checker/tests/nullness/java8/Issue1633.java index 14bf178aa364..48eb06f1e3e5 100644 --- a/checker/tests/nullness/java8/Issue1633.java +++ b/checker/tests/nullness/java8/Issue1633.java @@ -53,7 +53,7 @@ void foo5(Optional1633 o, Supplier<@NonNull String> supplyNonNull) { } void foo6(Optional1633 o, Supplier<@NonNull String> supplyNonNull) { - // :: error: (argument.type.incompatible) :: error: (assignment.type.incompatible) + // :: error: (assignment.type.incompatible) @NonNull String str1 = o.orElseGetNullable(supplyNonNull); } From 923531b4a4e4f8814d034963914f2197a7d98561 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2020 07:33:48 -0700 Subject: [PATCH 092/138] Bump com.gorylenko.gradle-git-properties from 2.2.2 to 2.2.3 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 100682a2b82b..cd8ac229756e 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ plugins { // https://plugins.gradle.org/plugin/org.ajoberstar.grgit id 'org.ajoberstar.grgit' version '4.0.2' apply false // https://github.com/n0mer/gradle-git-properties ; target is: generateGitProperties - id "com.gorylenko.gradle-git-properties" version "2.2.2" + id "com.gorylenko.gradle-git-properties" version "2.2.3" } apply plugin: "de.undercouch.download" From 8f1f06de62bacb68986bb7ab11aca0c8fc667404 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Mon, 27 Jul 2020 08:51:26 -0700 Subject: [PATCH 093/138] Update the version number. --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index cd8ac229756e..3130966dc466 100644 --- a/build.gradle +++ b/build.gradle @@ -101,7 +101,7 @@ allprojects { // * any new checkers have been added, // * the patch level is 9 (keep the patch level as a single digit), or // * backward-incompatible changes have been made to APIs or elsewhere. - version '3.5.0' + version '3.5.1-SNAPSHOT' repositories { mavenCentral() From eab075db084c6e9ae32fc55b56be7f2a169ec36a Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 27 Jul 2020 09:47:39 -0700 Subject: [PATCH 094/138] More information in Purity Checker messages (#3512) --- .../common/basetype/BaseTypeVisitor.java | 10 ++++++---- .../common/basetype/messages.properties | 8 ++++---- 2 files changed, 10 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 35547cc0c339..1fb019a6bc42 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -746,11 +746,13 @@ public Void visitMethod(MethodTree node, Void p) { * @param node the method tree to check */ protected void checkPurity(MethodTree node) { + if (!checker.hasOption("checkPurityAnnotations")) { + return; + } + boolean anyPurityAnnotation = PurityUtils.hasPurityAnnotation(atypeFactory, node); boolean suggestPureMethods = checker.hasOption("suggestPureMethods"); - boolean checkPurityAnnotations = checker.hasOption("checkPurityAnnotations"); - - if (!checkPurityAnnotations || (!anyPurityAnnotation && !suggestPureMethods)) { + if (!anyPurityAnnotation && !suggestPureMethods) { return; } @@ -878,7 +880,7 @@ private void reportPurityError(String msgKeyPrefix, Pair r) { String reason = r.second; @SuppressWarnings("CompilerMessages") @CompilerMessageKey String msgKey = msgKeyPrefix + reason; - if (reason.equals("call")) { + if (reason.equals("call") || reason.equals("call.method")) { MethodInvocationTree mitree = (MethodInvocationTree) r.first; checker.reportError(r.first, msgKey, mitree.getMethodSelect()); } else { 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 325b1822d9d3..b73e8c099147 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/messages.properties +++ b/framework/src/main/java/org/checkerframework/common/basetype/messages.properties @@ -64,14 +64,14 @@ purity.invalid.methodref=Incompatible purity declaration%nMethod%n %s in %s%n purity.invalid.overriding=Incompatible purity declaration%nMethod%n %s in %s%n cannot override%n %s in %s%nfound : %s%nrequired: %s purity.not.deterministic.assign.array=array assignment not allowed in deterministic method purity.not.deterministic.assign.field=field assignment not allowed in deterministic method -purity.not.deterministic.call.method=call to non-deterministic method not allowed in deterministic method +purity.not.deterministic.call.method=call to non-deterministic method %s not allowed in deterministic method purity.not.deterministic.catch=catch block not allowed in deterministic method purity.not.deterministic.object.creation=object creation not allowed in deterministic method purity.not.deterministic.not.sideeffectfree.assign.array=array assignment not allowed in deterministic side-effect-free method purity.not.deterministic.not.sideeffectfree.assign.field=field assignment not allowed in deterministic side-effect-free method -purity.not.deterministic.not.sideeffectfree.call.method=call to non-deterministic non-side-effect-free method not allowed in deterministic side-effect-free method -purity.not.sideeffectfree.call.constructor=call to non-side-effect-free constructor not allowed in side-effect-free method -purity.not.sideeffectfree.call.method=call to non-side-effect-free method not allowed in side-effect-free method +purity.not.deterministic.not.sideeffectfree.call.method=call to non-deterministic side-effecting method %s not allowed in deterministic side-effect-free method +purity.not.sideeffectfree.call.constructor=call to side-effecting constructor not allowed in side-effect-free method +purity.not.sideeffectfree.call.method=call to side-effecting method %s not allowed in side-effect-free method 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 From 408e1dde9e6c4f2815a4d3021060d8506bd3f55c Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 27 Jul 2020 10:11:43 -0700 Subject: [PATCH 095/138] Add a link to Javadoc --- docs/manual/advanced-features.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/advanced-features.tex b/docs/manual/advanced-features.tex index e3ac6a6184d5..69b3f558a780 100644 --- a/docs/manual/advanced-features.tex +++ b/docs/manual/advanced-features.tex @@ -227,7 +227,7 @@ this is usually CLIMB-to-top (Section~\ref{climb-to-top}) (CLIMB means \textbf{C}asts, \textbf{L}ocals, \textbf{I}nstanceof, and (some) i\textbf{M}plicit \textbf{B}ounds). \item - The qualifier with the meta-annotation \<@DefaultQualifierInHierarchy> + The qualifier with the meta-annotation \refqualclass{framework/qual}{DefaultQualifierInHierarchy}. \end{enumerate} If the unannotated type is the type of a local variable, then the first 5 rules are skipped and only From 239c5a9909e3ed888913a3e081fda6af230aa41d Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 27 Jul 2020 11:32:08 -0700 Subject: [PATCH 096/138] Fix usage instructions --- changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 9890d8f15f97..80f3985116d5 100644 --- a/changelog.txt +++ b/changelog.txt @@ -128,7 +128,7 @@ Renamings: For collection methods with `Object` formal parameter type, such as contains, indexOf, and remove, the annotated JDK now forbids null as an argument. To make the Nullness Checker permit null, pass -`-Astubs=checker.jar/collection-object-parameters-may-be-null.astub`. +`-Astubs=collection-object-parameters-may-be-null.astub`. The argument to @SuppressWarnings can be a substring of a message key that extends at each end to a period or an end of the key. (Previously, any From 53a6f3cb23eb02622e36cddae0537929676a5157 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 28 Jul 2020 09:47:00 -0700 Subject: [PATCH 097/138] Clarify `@Covariant` documentation in terms of clients (#3518) --- docs/manual/generics.tex | 11 ++++++----- .../checkerframework/framework/qual/Covariant.java | 7 ++++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/manual/generics.tex b/docs/manual/generics.tex index 27a4d158b023..d9b9138bc899 100644 --- a/docs/manual/generics.tex +++ b/docs/manual/generics.tex @@ -445,16 +445,17 @@ invariantly. For example, \code{List<@Nullable String>} is not a subtype of \code{List}. -When a type parameter is used in a read-only way --- that is, when values -of that type are read but are never assigned --- then it is safe for the +When a type parameter is used in a read-only way --- that is, when clients +read values of that type from the class but never pass values of that type +to the class --- then it is safe for the type to be \emph{covariant} in the type parameter. Use the \refqualclass{framework/qual}{Covariant} annotation to indicate this. When a type parameter is covariant, two instantiations of the class with different type arguments have the same subtyping relationship as the type arguments do. -For example, consider \. Its elements can be read but not -written, so \code{Iterator<@Nullable String>} can be a subtype of +For example, consider \. A client can read elements but not +write them, so \code{Iterator<@Nullable String>} can be a subtype of \code{Iterator} without introducing a hole in the type system. Therefore, its type parameter is annotated with \refqualclass{framework/qual}{Covariant}. @@ -464,7 +465,7 @@ The \<@Covariant> annotation is trusted but not checked. If you incorrectly specify as covariant a type parameter that can be -written (say, the class performs a +written (say, the class supports a \ operation or some other mutation on an object of that type), then you have created an unsoundness in the type system. For example, it would be incorrect to annotate the type parameter of diff --git a/framework/src/main/java/org/checkerframework/framework/qual/Covariant.java b/framework/src/main/java/org/checkerframework/framework/qual/Covariant.java index e42fe5b95fdd..ac79c2e99dbb 100644 --- a/framework/src/main/java/org/checkerframework/framework/qual/Covariant.java +++ b/framework/src/main/java/org/checkerframework/framework/qual/Covariant.java @@ -15,9 +15,10 @@ *

    Ordinarily, Java treats type parameters invariantly: {@code SomeClass} is unrelated to * (neither a subtype nor a supertype of) {@code SomeClass}. * - *

    It is only safe to mark a type parameter as covariant if the type parameter is used in a - * read-only way: values of that type are read from but never modified. This property is not - * checked; the {@code @Covariant} is simply trusted. + *

    It is only safe to mark a type parameter as covariant if clients use the type parameter in a + * read-only way: clients read values of that type but never modify them. + * + *

    This property is not checked; the {@code @Covariant} is simply trusted. * *

    Here is an example use: * From bb1e3802d531cd8c59c6b3e297b536c8ed075a36 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Tue, 28 Jul 2020 11:10:06 -0700 Subject: [PATCH 098/138] How to distribute a stub file --- docs/manual/annotating-libraries.tex | 29 ++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/docs/manual/annotating-libraries.tex b/docs/manual/annotating-libraries.tex index 603173d5eb57..22fb1536f328 100644 --- a/docs/manual/annotating-libraries.tex +++ b/docs/manual/annotating-libraries.tex @@ -569,14 +569,11 @@ \subsectionAndLabel{Creating a stub file}{stub-creating} -Stub files are generally stored together with the checker implementation, -in the same directory as the checker's \<.java> source code. - \subsubsectionAndLabel{If you have access to the Java source code}{stub-creating-with-source} Every Java file is a stub file. If you have access to the Java file, -rename file \ to \. You can add +copy file \ to \. You can add annotations to the signatures, leaving the method bodies unchanged. The stub file parser silently ignores any annotations that it cannot resolve to a type, so don't forget the \ statement. @@ -678,6 +675,30 @@ \end{enumerate} +\subsectionAndLabel{Distributing stub files}{stub-creating} + +If you are writing stub files but are not writing a checker, you can place +the stub files anywhere that is convenient. However, please consider +providing your stub files to the checker author, so that other users can +also benefit from the stub files you wrote. + +If you are distributing stub files with a checker implementation, the stub +files appear in the same directory as the checker class, which is a subtype of +\. A \refqualclass{framework/qual}{StubFiles} annotation +on the checker class lists stub files that are always used. For stub files +whose use is optional (for example, because the behavior is unsound, or +unsound except in certain circumstances), users must supply the +\<-Astubs=...> command-line option. + +If a stub file contains annotations that are used by the framework rather +than by any specific checker (such as purity annotations), and you wish to +distribute it with the Checker Framework, put the stub file in directory +\. You can also do this if the stub file has has +annotations for multiple checkers. To use a stub file in directory +\, users must supply the +\<-Astubs=checker.jar/stub-file-name.astub> command-line option. + + \subsectionAndLabel{Troubleshooting stub libraries}{stub-troubleshooting} From a4b956c566a667968b485abeda61e9cb9cc83d00 Mon Sep 17 00:00:00 2001 From: Rashmi Mudduluru Date: Tue, 28 Jul 2020 11:10:49 -0700 Subject: [PATCH 099/138] Add Javadoc --- .../reflection/DefaultReflectionResolver.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/framework/src/main/java/org/checkerframework/common/reflection/DefaultReflectionResolver.java b/framework/src/main/java/org/checkerframework/common/reflection/DefaultReflectionResolver.java index 760251e615b7..662d12862b63 100644 --- a/framework/src/main/java/org/checkerframework/common/reflection/DefaultReflectionResolver.java +++ b/framework/src/main/java/org/checkerframework/common/reflection/DefaultReflectionResolver.java @@ -206,17 +206,39 @@ private ParameterizedExecutableType resolveMethodCall( return origResult; } + /** + * Checks that arguments of a method invocation are consistent with their corresponding + * parameters. + * + * @param resolvedTree a method invocation + * @return true if arguments are consistent with parameters + */ private boolean checkMethodArguments(MethodInvocationTree resolvedTree) { // type.getKind() == actualType.getKind() ExecutableElement methodDecl = TreeUtils.elementFromUse(resolvedTree); return checkArguments(methodDecl.getParameters(), resolvedTree.getArguments()); } + /** + * Checks that arguments of a constructor invocation are consistent with their corresponding + * parameters. + * + * @param resolvedTree a constructor invocation + * @return true if arguments are consistent with parameters + */ private boolean checkNewClassArguments(NewClassTree resolvedTree) { ExecutableElement methodDecl = TreeUtils.elementFromUse(resolvedTree); return checkArguments(methodDecl.getParameters(), resolvedTree.getArguments()); } + /** + * Checks that argument are consistent with their corresponding parameter types. Common code + * used by {@link #checkMethodArguments} and {@link #checkNewClassArguments}. + * + * @param parameters formal parameters + * @param arguments actual arguments + * @return true if argument are consistent with their corresponding parameter types + */ private boolean checkArguments( List parameters, List arguments) { if (parameters.size() != arguments.size()) { From 3f6bd29acab960be9d7a385a33e521c9618af92d Mon Sep 17 00:00:00 2001 From: Werner Dietl Date: Tue, 28 Jul 2020 14:45:50 -0400 Subject: [PATCH 100/138] Fix typo --- docs/manual/annotating-libraries.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/annotating-libraries.tex b/docs/manual/annotating-libraries.tex index 22fb1536f328..de5af8cbf4c8 100644 --- a/docs/manual/annotating-libraries.tex +++ b/docs/manual/annotating-libraries.tex @@ -693,7 +693,7 @@ If a stub file contains annotations that are used by the framework rather than by any specific checker (such as purity annotations), and you wish to distribute it with the Checker Framework, put the stub file in directory -\. You can also do this if the stub file has has +\. You can also do this if the stub file has annotations for multiple checkers. To use a stub file in directory \, users must supply the \<-Astubs=checker.jar/stub-file-name.astub> command-line option. From 1d358c437d1ad72bcc672d65a9f095b7123de8f6 Mon Sep 17 00:00:00 2001 From: Weitian Xing Date: Wed, 29 Jul 2020 11:24:35 -0400 Subject: [PATCH 101/138] Add parameters to the output dot file's name. (#3521) --- .../dataflow/cfg/DOTCFGVisualizer.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/DOTCFGVisualizer.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/DOTCFGVisualizer.java index 2c807d0b45fe..c04479df96e8 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/DOTCFGVisualizer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/DOTCFGVisualizer.java @@ -1,5 +1,6 @@ package org.checkerframework.dataflow.cfg; +import com.sun.source.tree.VariableTree; import com.sun.tools.javac.tree.JCTree; import java.io.BufferedWriter; import java.io.FileWriter; @@ -9,6 +10,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.StringJoiner; import org.checkerframework.checker.nullness.qual.KeyFor; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.dataflow.analysis.AbstractValue; @@ -180,16 +182,24 @@ protected String dotOutputFileName(UnderlyingAST ast) { CFGMethod cfgMethod = (CFGMethod) ast; String clsName = cfgMethod.getClassTree().getSimpleName().toString(); String methodName = cfgMethod.getMethod().getName().toString(); + StringJoiner params = new StringJoiner(","); + for (VariableTree tree : cfgMethod.getMethod().getParameters()) { + params.add(tree.getType().toString()); + } outFile.append(clsName); outFile.append("-"); outFile.append(methodName); + if (params.length() != 0) { + outFile.append("-"); + outFile.append(params); + } srcLoc.append("<"); srcLoc.append(clsName); srcLoc.append("::"); srcLoc.append(methodName); srcLoc.append("("); - srcLoc.append(cfgMethod.getMethod().getParameters()); + srcLoc.append(params); srcLoc.append(")::"); srcLoc.append(((JCTree) cfgMethod.getMethod()).pos); srcLoc.append(">"); From 781f18963987ae903455cee1e2eef4bae58abe18 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Wed, 29 Jul 2020 15:05:59 -0700 Subject: [PATCH 102/138] Deprecate *TypeVariable methods in QualifierHierarchy and improve documentation (#3445) These methods are barely used and are not correct. I also improved the Javadoc of all the methods in QualifierHierarchy. --- dataflow/manual/content.tex | 2 +- .../common/basetype/BaseTypeValidator.java | 10 +- .../common/subtyping/SubtypingChecker.java | 2 +- .../util/debug/TypeOutputtingChecker.java | 33 - .../framework/type/QualifierHierarchy.java | 877 ++++++++++-------- .../poly/DefaultQualifierPolymorphism.java | 2 +- .../framework/util/AtmLubVisitor.java | 13 +- .../util/MultiGraphQualifierHierarchy.java | 58 +- 8 files changed, 519 insertions(+), 478 deletions(-) diff --git a/dataflow/manual/content.tex b/dataflow/manual/content.tex index 68fbb53b7320..5100b6c87ce9 100644 --- a/dataflow/manual/content.tex +++ b/dataflow/manual/content.tex @@ -1779,7 +1779,7 @@ \section{Default Analysis} \subsection{Overview} The default flow-sensitive analysis \code{org.checkerframework.framework.flow.CFAnalysis} -works for the qualifier hierarchy of any checker defined in the +works for any checker defined in the Checker Framework. This generality is both a strength and a weakness because the default analysis can always run but the facts it can deduce are limited. The default analysis is extensible so checkers diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java index 2239e5016642..dd62c34a6619 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java @@ -110,15 +110,15 @@ protected boolean shouldCheckTopLevelDeclaredType(AnnotatedTypeMirror type, Tree *

    Currently, the following is checked: * *

      - *
    1. There should not be multiple annotations from the same hierarchy. - *
    2. There should not be more annotations than the width of the qualifier hierarchy. + *
    3. There should not be multiple annotations from the same qualifier hierarchy. + *
    4. There should not be more annotations than the width of the QualifierHierarchy. *
    5. If the type is not a type variable, then the number of annotations should be the same - * as the width of the qualifier hierarchy. + * as the width of the QualifierHierarchy. *
    6. These properties should also hold recursively for component types of arrays, as wells * as bounds of type variables and wildcards. *
    * - * @param qualifierHierarchy the qualifier hierachy + * @param qualifierHierarchy the QualifierHierarchy * @param type the type to test * @return list of reasons the type is invalid, or empty list if the type is valid */ @@ -136,7 +136,7 @@ protected List isValidType( * Checks every property listed in {@link #isValidType}, but only for the top level type. If * successful, returns an empty list. If not successful, returns diagnostics. * - * @param qualifierHierarchy the qualifier hierarchy + * @param qualifierHierarchy the QualifierHierarchy * @param type the type to be checked * @return the diagnostics indicating failure, or an empty list if successful */ diff --git a/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingChecker.java b/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingChecker.java index 1ce0e373aac2..160007f196db 100644 --- a/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingChecker.java +++ b/framework/src/main/java/org/checkerframework/common/subtyping/SubtypingChecker.java @@ -16,7 +16,7 @@ * *
      *
    • {@code -Aquals}: specifies the annotations in the qualifier hierarchy (as a comma-separated - * list of fully-qualified annotation names with no spaces in between). Only the annotation + * list of fully-qualified annotation names with no spaces in between). Only the annotations * for one qualified subtype hierarchy can be passed. *
    * diff --git a/framework/src/main/java/org/checkerframework/common/util/debug/TypeOutputtingChecker.java b/framework/src/main/java/org/checkerframework/common/util/debug/TypeOutputtingChecker.java index 389081fdc244..71d7de16d6b9 100644 --- a/framework/src/main/java/org/checkerframework/common/util/debug/TypeOutputtingChecker.java +++ b/framework/src/main/java/org/checkerframework/common/util/debug/TypeOutputtingChecker.java @@ -234,14 +234,6 @@ public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) { throw new BugInCF("GeneralQualifierHierarchy.isSubtype() shouldn't be called."); } - // Not needed - raises error. - @Override - public boolean isSubtypeTypeVariable( - AnnotationMirror subAnno, AnnotationMirror superAnno) { - throw new BugInCF( - "GeneralQualifierHierarchy.isSubtypeTypeVariable() shouldn't be called."); - } - // Not needed - raises error. @Override public boolean isSubtype( @@ -250,15 +242,6 @@ public boolean isSubtype( throw new BugInCF("GeneralQualifierHierarchy.isSubtype() shouldn't be called."); } - // Not needed - raises error. - @Override - public boolean isSubtypeTypeVariable( - Collection subAnnos, - Collection superAnnos) { - throw new BugInCF( - "GeneralQualifierHierarchy.isSubtypeTypeVariable() shouldn't be called."); - } - // Not needed - raises error. @Override public AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2) { @@ -266,14 +249,6 @@ public AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2 "GeneralQualifierHierarchy.leastUpperBound() shouldn't be called."); } - // Not needed - raises error. - @Override - public AnnotationMirror leastUpperBoundTypeVariable( - AnnotationMirror a1, AnnotationMirror a2) { - throw new BugInCF( - "GeneralQualifierHierarchy.leastUpperBoundTypeVariable() shouldn't be called."); - } - // Not needed - raises error. @Override public AnnotationMirror greatestLowerBound(AnnotationMirror a1, AnnotationMirror a2) { @@ -281,14 +256,6 @@ public AnnotationMirror greatestLowerBound(AnnotationMirror a1, AnnotationMirror "GeneralQualifierHierarchy.greatestLowerBound() shouldn't be called."); } - // Not needed - raises error. - @Override - public AnnotationMirror greatestLowerBoundTypeVariable( - AnnotationMirror a1, AnnotationMirror a2) { - throw new BugInCF( - "GeneralQualifierHierarchy.greatestLowerBoundTypeVariable() shouldn't be called."); - } - @Override public AnnotationMirror getPolymorphicAnnotation(AnnotationMirror start) { throw new BugInCF( diff --git a/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java index 4fcd89f77857..b75b0f87e494 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/type/QualifierHierarchy.java @@ -5,25 +5,34 @@ import java.util.Set; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.TypeKind; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.AnnotatedFor; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.BugInCF; /** - * Represents a type qualifier hierarchy. + * Represents multiple type qualifier hierarchies. {@link #getWidth} gives the number of hierarchies + * that this object represents. Each hierarchy has its own top and bottom, and subtyping + * relationships exist only within each hierarchy. * - *

    All method parameter annotations need to be type qualifiers recognized within this hierarchy. + *

    Note the distinction in terminology between a qualifier hierarchy, which has one top and one + * bottom, and a {@code QualifierHierarchy}, which represents multiple qualifier hierarchies. * - *

    This assumes that any particular annotated type in a program is annotated with at least one - * qualifier from the hierarchy. + *

    All type annotations need to be type qualifiers recognized within this hierarchy. + * + *

    This assumes that every annotated type in a program is annotated with exactly one qualifier + * from each hierarchy. */ -public abstract class QualifierHierarchy { +@AnnotatedFor("nullness") +public interface QualifierHierarchy { /** - * Determine whether the instance is valid. + * Determine whether this is valid. * - * @return whether the instance is valid + * @return whether this is valid */ - public abstract boolean isValid(); + boolean isValid(); // ********************************************************************** // Getter methods about this hierarchy @@ -32,46 +41,56 @@ public abstract class QualifierHierarchy { /** * Returns the width of this hierarchy, i.e. the expected number of annotations on any valid * type. + * + * @return the width of this QualifierHierarchy */ - public int getWidth() { + default int getWidth() { return getTopAnnotations().size(); } /** - * Returns the top (ultimate super) type qualifiers in the type system. + * Returns the top (ultimate super) type qualifiers in the type system. The size of this set is + * equal to {@link #getWidth}. * * @return the top (ultimate super) type qualifiers in the type system */ - public abstract Set getTopAnnotations(); + Set getTopAnnotations(); /** * Return the top qualifier for the given qualifier, that is, the qualifier that is a supertype - * of start but no further supertypes exist. + * of {@code qualifier} but no further supertypes exist. + * + * @param qualifier any qualifier from one of the qualifier hierarchies represented by this + * @return the top qualifier of {@code qualifier}'s hierarchy */ - public abstract AnnotationMirror getTopAnnotation(AnnotationMirror start); + AnnotationMirror getTopAnnotation(AnnotationMirror qualifier); /** - * Return the bottom for the given qualifier, that is, the qualifier that is a subtype of start - * but no further subtypes exist. + * Returns the bottom type qualifiers in the hierarchy. The size of this set is equal to {@link + * #getWidth}. + * + * @return the bottom type qualifiers in the hierarchy */ - public abstract AnnotationMirror getBottomAnnotation(AnnotationMirror start); + Set getBottomAnnotations(); /** - * Returns the bottom type qualifier in the hierarchy. + * Return the bottom for the given qualifier, that is, the qualifier that is a subtype of {@code + * qualifier} but no further subtypes exist. * - * @return the bottom type qualifier in the hierarchy + * @param qualifier any qualifier from one of the qualifier hierarchies represented by this + * @return the bottom qualifier of {@code qualifier}'s hierarchy */ - public abstract Set getBottomAnnotations(); + AnnotationMirror getBottomAnnotation(AnnotationMirror qualifier); /** - * Returns the polymorphic qualifier for that hierarchy or {@code null} if there is no - * polymorphic qualifier in that hierarchy. + * Returns the polymorphic qualifier for the hierarchy containing {@code qualifier}, or {@code + * null} if there is no polymorphic qualifier in that hierarchy. * - * @param start any qualifier from the type hierarchy - * @return the polymorphic qualifier for that hierarchy or {@code null} if there is no - * polymorphic qualifier in that hierarchy + * @param qualifier any qualifier from one of the qualifier hierarchies represented by this + * @return the polymorphic qualifier for the hierarchy containing {@code qualifier}, or {@code + * null} if there is no polymorphic qualifier in that hierarchy */ - public abstract AnnotationMirror getPolymorphicAnnotation(AnnotationMirror start); + @Nullable AnnotationMirror getPolymorphicAnnotation(AnnotationMirror qualifier); /** * Returns {@code true} if the qualifier is a polymorphic qualifier; otherwise, returns {@code @@ -81,31 +100,40 @@ public int getWidth() { * @return {@code true} if the qualifier is a polymorphic qualifier; otherwise, returns {@code * false}. */ - public abstract boolean isPolymorphicQualifier(AnnotationMirror qualifier); + boolean isPolymorphicQualifier(AnnotationMirror qualifier); // ********************************************************************** // Qualifier Hierarchy Queries // ********************************************************************** /** - * Tests whether rhs is equal to or a sub-qualifier of lhs, according to the type qualifier - * hierarchy. This checks only the qualifiers, not the Java type. + * Tests whether {@code subQualifier} is equal to or a sub-qualifier of {@code superQualifier}, + * according to the type qualifier hierarchy. * - * @return true iff rhs is a sub qualifier of lhs + * @param subQualifier possible subqualifier of {@code superQualifier} + * @param superQualifier possible superqualifier of {@code subQualifier} + * @return true iff {@code subQualifier} is a subqualifier of, or equal to, {@code + * superQualifier} */ - public abstract boolean isSubtype(AnnotationMirror rhs, AnnotationMirror lhs); + boolean isSubtype(AnnotationMirror subQualifier, AnnotationMirror superQualifier); /** - * Tests whether there is any annotation in lhs that is a super qualifier of, or equal to, some - * annotation in rhs. lhs and rhs contain only the annotations, not the Java type. + * Tests whether all qualifiers in {@code subQualifiers} are a subqualifier or equal to the + * qualifier in the same hierarchy in {@code superQualifiers}. * - * @return true iff an annotation in lhs is a super of one in rhs + * @param subQualifiers set of qualifiers; exactly one per hierarchy + * @param superQualifiers set of qualifiers; exactly one per hierarchy + * @return true iff all qualifiers in {@code subQualifiers} are a subqualifier or equal to the + * qualifier in the same hierarchy in {@code superQualifiers} */ - public abstract boolean isSubtype( - Collection rhs, Collection lhs); + boolean isSubtype( + Collection subQualifiers, + Collection superQualifiers); /** - * Returns the least upper bound for the qualifiers a1 and a2. + * Returns the least upper bound (LUB) of the qualifiers {@code qualifier1} and {@code + * qualifier2}. Returns {@code null} if the qualifiers are not from the same qualifier + * hierarchy. * *

    Examples: * @@ -113,126 +141,124 @@ public abstract boolean isSubtype( *

  • For NonNull, leastUpperBound('Nullable', 'NonNull') ⇒ Nullable * * - * The two qualifiers have to be from the same qualifier hierarchy. Otherwise, null will be - * returned. + * @param qualifier1 the first qualifier; may not be in the same hierarchy as {@code qualifier2} + * @param qualifier2 the second qualifier; may not be in the same hierarchy as {@code + * qualifier1} + * @return the least upper bound of the qualifiers, or {@code null} if the qualifiers are from + * different hierarchies + */ + // The fact that null is returned if the qualifiers are not in the same hierarchy is used by the + // collection version of LUB below. + @Nullable AnnotationMirror leastUpperBound(AnnotationMirror qualifier1, AnnotationMirror qualifier2); + + /** + * Returns the least upper bound of the two sets of qualifiers. The result is the lub of the + * qualifier for the same hierarchy in each set. * - * @return the least restrictive qualifiers for both types + * @param qualifiers1 set of qualifiers; exactly one per hierarchy + * @param qualifiers2 set of qualifiers; exactly one per hierarchy + * @return the least upper bound of the two sets of qualifiers */ - public abstract AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2); + default Set leastUpperBounds( + Collection qualifiers1, + Collection qualifiers2) { + assertSameSize(qualifiers1, qualifiers2); + if (qualifiers1.isEmpty()) { + throw new BugInCF( + "QualifierHierarchy.leastUpperBounds: tried to determine LUB with empty sets"); + } + + Set result = AnnotationUtils.createAnnotationSet(); + for (AnnotationMirror a1 : qualifiers1) { + for (AnnotationMirror a2 : qualifiers2) { + AnnotationMirror lub = leastUpperBound(a1, a2); + if (lub != null) { + result.add(lub); + } + } + } + + assertSameSize(result, qualifiers1); + return result; + } /** * Returns the number of iterations dataflow should perform before {@link * #widenedUpperBound(AnnotationMirror, AnnotationMirror)} is called or -1 if it should never be * called. * - *

    Subclasses overriding this method should return some positive number or -1. - * * @return the number of iterations dataflow should perform before {@link * #widenedUpperBound(AnnotationMirror, AnnotationMirror)} is called or -1 if it should * never be called. */ - public int numberOfIterationsBeforeWidening() { + default int numberOfIterationsBeforeWidening() { return -1; } /** - * If the type hierarchy has an infinite ascending chain, then the dataflow analysis might never - * reach a fixed point. To prevent this, implement this method such that it returns an upper - * bound for the two qualifiers that is a super type and not equal to the least upper bound. If - * this method is implemented, also override {@link #numberOfIterationsBeforeWidening()} and - * change its return to a positive number. + * If the qualifier hierarchy has an infinite ascending chain, then the dataflow analysis might + * never reach a fixed point. To prevent this, implement this method such that it returns an + * upper bound for the two qualifiers that is a strict super type of the least upper bound. If + * this method is implemented, also override {@link #numberOfIterationsBeforeWidening()} to + * return a positive number. * *

    {@code newQualifier} is newest qualifier dataflow computed for some expression and {@code * previousQualifier} is the qualifier dataflow computed on the last iteration. * - *

    If the type hierarchy has no infinite ascending chain, returns the least upper bound of - * the two annotations. + *

    If the qualifier hierarchy has no infinite ascending chain, returns the least upper bound + * of the two annotations. * - * @param newQualifier new qualifier dataflow computed for some expression - * @param previousQualifier the previous qualifier dataflow computed on the last iteration - * @return an upper bound that is wider than the least upper bound of newQualifier and - * previousQualifier (or the lub if the type hierarchy does not require this) + * @param newQualifier new qualifier dataflow computed for some expression; must be in the same + * hierarchy as {@code previousQualifier} + * @param previousQualifier the previous qualifier dataflow computed on the last iteration; must + * be in the same hierarchy as {@code previousQualifier} + * @return an upper bound that is higher than the least upper bound of newQualifier and + * previousQualifier (or the lub if the qualifier hierarchy does not require this) */ - public AnnotationMirror widenedUpperBound( + default AnnotationMirror widenedUpperBound( AnnotationMirror newQualifier, AnnotationMirror previousQualifier) { - return leastUpperBound(newQualifier, previousQualifier); + AnnotationMirror widenUpperBound = leastUpperBound(newQualifier, previousQualifier); + if (widenUpperBound == null) { + throw new BugInCF( + "Passed two unrelated qualifiers to QualifierHierarchy#widenedUpperBound. %s %s.", + newQualifier, previousQualifier); + } + return widenUpperBound; } /** - * Returns the greatest lower bound for the qualifiers a1 and a2. - * - *

    The two qualifiers have to be from the same qualifier hierarchy. Otherwise, null will be - * returned. - * - * @param a1 first annotation - * @param a2 second annotation - * @return greatest lower bound of the two annotations - */ - public abstract AnnotationMirror greatestLowerBound(AnnotationMirror a1, AnnotationMirror a2); - - /** - * Returns the least upper bound of two types. Each type is represented as a set of type - * qualifiers, as is the result. - * - *

    Annos1 and annos2 must have the same size, and each annotation in them must be from a - * different type hierarchy. - * - *

    This is necessary for determining the type of a conditional expression ({@code ?:}), where - * the type of the expression is the least upper bound of the true and false clauses. + * Returns the greatest lower bound for the qualifiers qualifier1 and qualifier2. Returns null + * if the qualifiers are not from the same qualifier hierarchy. * - * @param annos1 first collection of qualifiers - * @param annos2 second collection of qualifiers - * @return pairwise least upper bounds of elements of the input collections (which need not be - * sorted in the same order) + * @param qualifier1 first qualifier + * @param qualifier2 second qualifier + * @return greatest lower bound of the two annotations or null if the two annotations are not + * from the same hierarchy */ - public Set leastUpperBounds( - Collection annos1, - Collection annos2) { - assertSameSize(annos1, annos2); - if (annos1.isEmpty()) { - throw new BugInCF( - "QualifierHierarchy.leastUpperBounds: tried to determine LUB with empty sets"); - } - - Set result = AnnotationUtils.createAnnotationSet(); - for (AnnotationMirror a1 : annos1) { - for (AnnotationMirror a2 : annos2) { - AnnotationMirror lub = leastUpperBound(a1, a2); - if (lub != null) { - result.add(lub); - } - } - } - - assertSameSize(result, annos1); - - return result; - } + // The fact that null is returned if the qualifiers are not in the same hierarchy is used by the + // collection version of LUB below. + @Nullable AnnotationMirror greatestLowerBound(AnnotationMirror qualifier1, AnnotationMirror qualifier2); /** - * Returns the greatest lower bound of two types. Each type is represented as a set of type - * qualifiers, as is the result. - * - *

    Annos1 and annos2 must have the same size, and each annotation in them must be from a - * different type hierarchy. + * Returns the least upper bound of the two sets of qualifiers. The result is the lub of the + * qualifier for the same hierarchy in each set. * - * @param annos1 first collection of qualifiers - * @param annos2 second collection of qualifiers - * @return pairwise greatest lower bounds of elements of the input collections (which need not - * be sorted in the same order) + * @param qualifiers1 set of qualifiers; exactly one per hierarchy + * @param qualifiers2 set of qualifiers; exactly one per hierarchy + * @return the least upper bound of the two sets of qualifiers */ - public Set greatestLowerBounds( - Collection annos1, - Collection annos2) { - assertSameSize(annos1, annos2); - if (annos1.isEmpty()) { + default Set greatestLowerBounds( + Collection qualifiers1, + Collection qualifiers2) { + assertSameSize(qualifiers1, qualifiers2); + if (qualifiers1.isEmpty()) { throw new BugInCF( "QualifierHierarchy.greatestLowerBounds: tried to determine GLB with empty sets"); } Set result = AnnotationUtils.createAnnotationSet(); - for (AnnotationMirror a1 : annos1) { - for (AnnotationMirror a2 : annos2) { + for (AnnotationMirror a1 : qualifiers1) { + for (AnnotationMirror a2 : qualifiers2) { AnnotationMirror glb = greatestLowerBound(a1, a2); if (glb != null) { result.add(glb); @@ -240,176 +266,158 @@ public Set greatestLowerBounds( } } - assertSameSize(annos1, annos2, result); - + assertSameSize(qualifiers1, qualifiers2, result); return result; } /** - * Tests whether {@code subAnno} is a sub-qualifier of, or equal to, {@code superAnno}, - * according to the type qualifier hierarchy. This checks only the qualifiers, not the Java - * type. + * Returns true if and only if {@link AnnotatedTypeMirror#getAnnotations()} can return a set + * with fewer qualifiers than the width of the QualifierHierarchy. * - *

    This method works even if the underlying Java type is a type variable. In that case, a - * 'null' AnnnotationMirror and the empty set represent a meaningful value (namely, no - * annotation). - * - * @param subAnno a qualifier that might be a subtype - * @param superAnno a qualifier that might be a subtype - * @return true iff {@code subAnno} is a sub qualifier of, or equal to, {@code superAnno} + * @param type the type to test + * @return true if and only if {@link AnnotatedTypeMirror#getAnnotations()} can return a set + * with fewer qualifiers than the width of the QualifierHierarchy */ - public abstract boolean isSubtypeTypeVariable( - AnnotationMirror subAnno, AnnotationMirror superAnno); + static boolean canHaveEmptyAnnotationSet(AnnotatedTypeMirror type) { + return type.getKind() == TypeKind.TYPEVAR + || type.getKind() == TypeKind.WILDCARD + || + // TODO: or should the union/intersection be the LUB of the alternatives? + type.getKind() == TypeKind.UNION + || type.getKind() == TypeKind.INTERSECTION; + } /** - * Tests whether there is any annotation in superAnnos that is a super qualifier of or equal to - * some annotation in subAnnos. superAnnos and subAnnos contain only the annotations, not the - * Java type. - * - *

    This method works even if the underlying Java type is a type variable. In that case, a - * 'null' AnnnotationMirror and the empty set represent a meaningful value (namely, no - * annotation). + * Returns the annotation in qualifiers that is in the same hierarchy as qualifier. * - * @return true iff an annotation in superAnnos is a supertype of, or equal to, one in subAnnos + * @param qualifiers set of annotations to search + * @param qualifier annotation that is in the same hierarchy as the returned annotation + * @return annotation in the same hierarchy as qualifier, or null if one is not found */ - // This method requires more revision. - public abstract boolean isSubtypeTypeVariable( - Collection subAnnos, - Collection superAnnos); + default @Nullable AnnotationMirror findAnnotationInSameHierarchy( + Collection qualifiers, AnnotationMirror qualifier) { + AnnotationMirror top = this.getTopAnnotation(qualifier); + return findAnnotationInHierarchy(qualifiers, top); + } /** - * Returns the least upper bound for the qualifiers a1 and a2. - * - *

    Examples: - * - *

      - *
    • For NonNull, leastUpperBound('Nullable', 'NonNull') → Nullable - *
    - * - * The two qualifiers have to be from the same qualifier hierarchy. Otherwise, null will be - * returned. + * Returns the annotation in qualifiers that is in the hierarchy for which annotationMirror is + * top. * - *

    This method works even if the underlying Java type is a type variable. In that case, a - * 'null' AnnnotationMirror and the empty set represent a meaningful value (namely, no - * annotation). - * - * @return the least restrictive qualifiers for both types + * @param qualifiers set of annotations to search + * @param top the top annotation in the hierarchy to which the returned annotation belongs + * @return annotation in the same hierarchy as annotationMirror, or null if one is not found */ - public abstract AnnotationMirror leastUpperBoundTypeVariable( - AnnotationMirror a1, AnnotationMirror a2); + default @Nullable AnnotationMirror findAnnotationInHierarchy( + Collection qualifiers, AnnotationMirror top) { + for (AnnotationMirror anno : qualifiers) { + if (isSubtype(anno, top)) { + return anno; + } + } + return null; + } /** - * Returns the greatest lower bound for the qualifiers a1 and a2. - * - *

    The two qualifiers have to be from the same qualifier hierarchy. Otherwise, null will be - * returned. + * Update a mapping from {@code key} to a set of AnnotationMirrors. If {@code key} is not + * already in the map, then put it in the map with a value of a new set containing {@code + * qualifier}. If the map contains {@code key}, then add {@code qualifier} to the set to which + * {@code key} maps. If that set contains a qualifier in the same hierarchy as {@code + * qualifier}, then don't add it and return false. * - *

    This method works even if the underlying Java type is a type variable. In that case, a - * 'null' AnnnotationMirror and the empty set represent a meaningful value (namely, no - * annotation). - * - * @param a1 first annotation - * @param a2 second annotation - * @return greatest lower bound of the two annotations + * @param map the mapping to modify + * @param key the key to update or add + * @param qualifier the value to update or add + * @param type of the map's keys + * @return true if the update was done; false if there was a qualifier hierarchy collision */ - public abstract AnnotationMirror greatestLowerBoundTypeVariable( - AnnotationMirror a1, AnnotationMirror a2); + default boolean updateMappingToMutableSet( + Map> map, T key, AnnotationMirror qualifier) { + // https://github.com/typetools/checker-framework/issues/2000 + @SuppressWarnings("nullness:argument.type.incompatible") + boolean mapContainsKey = map.containsKey(key); + if (mapContainsKey) { + @SuppressWarnings("nullness:assignment.type.incompatible") // key is a key for map. + @NonNull Set prevs = map.get(key); + AnnotationMirror old = findAnnotationInSameHierarchy(prevs, qualifier); + if (old != null) { + return false; + } + prevs.add(qualifier); + map.put(key, prevs); + } else { + Set set = AnnotationUtils.createAnnotationSet(); + set.add(qualifier); + map.put(key, set); + } + return true; + } /** - * Returns the type qualifiers that are the least upper bound of the qualifiers in annos1 and - * annos2. - * - *

    This is necessary for determining the type of a conditional expression ({@code ?:}), where - * the type of the expression is the least upper bound of the true and false clauses. - * - *

    This method works even if the underlying Java type is a type variable. In that case, a - * 'null' AnnnotationMirror and the empty set represent a meaningful value (namely, no - * annotation). + * Throws an exception if the given collections do not have the same size. * - * @return the least upper bound of annos1 and annos2 + * @param c1 the first collection + * @param c2 the second collection */ - public Set leastUpperBoundsTypeVariable( - Collection annos1, - Collection annos2) { - Set result = AnnotationUtils.createAnnotationSet(); - for (AnnotationMirror top : getTopAnnotations()) { - AnnotationMirror anno1ForTop = null; - for (AnnotationMirror anno1 : annos1) { - if (isSubtypeTypeVariable(anno1, top)) { - anno1ForTop = anno1; - } - } - AnnotationMirror anno2ForTop = null; - for (AnnotationMirror anno2 : annos2) { - if (isSubtypeTypeVariable(anno2, top)) { - anno2ForTop = anno2; - } - } - AnnotationMirror t = leastUpperBoundTypeVariable(anno1ForTop, anno2ForTop); - if (t != null) { - result.add(t); - } + static void assertSameSize(Collection c1, Collection c2) { + if (c1.size() != c2.size()) { + throw new BugInCF( + "inconsistent sizes (%d, %d):%n %s%n %s", c1.size(), c2.size(), c1, c2); } - return result; } /** - * Returns the type qualifiers that are the greatest lower bound of the qualifiers in annos1 and - * annos2. - * - *

    The two qualifiers have to be from the same qualifier hierarchy. Otherwise, null will be - * returned. - * - *

    This method works even if the underlying Java type is a type variable. In that case, a - * 'null' AnnnotationMirror and the empty set represent a meaningful value (namely, no - * annotation). + * Throws an exception if the result does not have the same size as the inputs (which are + * assumed to have the same size as one another). * - * @param annos1 first collection of qualifiers - * @param annos2 second collection of qualifiers - * @return greatest lower bound of the two collections of qualifiers + * @param c1 the first collection + * @param c2 the second collection + * @param result the result collection */ - public Set greatestLowerBoundsTypeVariable( - Collection annos1, - Collection annos2) { - Set result = AnnotationUtils.createAnnotationSet(); - for (AnnotationMirror top : getTopAnnotations()) { - AnnotationMirror anno1ForTop = null; - for (AnnotationMirror anno1 : annos1) { - if (isSubtypeTypeVariable(anno1, top)) { - anno1ForTop = anno1; - } - } - AnnotationMirror anno2ForTop = null; - for (AnnotationMirror anno2 : annos2) { - if (isSubtypeTypeVariable(anno2, top)) { - anno2ForTop = anno2; - } - } - AnnotationMirror t = greatestLowerBoundTypeVariable(anno1ForTop, anno2ForTop); - if (t != null) { - result.add(t); - } + static void assertSameSize(Collection c1, Collection c2, Collection result) { + if (c1.size() != result.size()) { + throw new BugInCF( + "inconsistent sizes (%d, %d, %d):%n %s%n %s%n %s", + c1.size(), c2.size(), result.size(), c1, c2, result); } - return result; } + // ********************************************************************** + // Deprecated methods + // ********************************************************************** + /** - * Returns true if and only if the given type can have empty annotation sets (and thus the - * *TypeVariable methods need to be used). - */ - public static boolean canHaveEmptyAnnotationSet(AnnotatedTypeMirror type) { - return type.getKind() == TypeKind.TYPEVAR - || type.getKind() == TypeKind.WILDCARD - || - // TODO: or should the union/intersection be the LUB of the alternatives? - type.getKind() == TypeKind.UNION - || type.getKind() == TypeKind.INTERSECTION; + * Tests whether {@code subQualifier} is a sub-qualifier of, or equal to, {@code + * superQualifier}, according to the type qualifier hierarchy. + * + *

    This method works even if the underlying Java type is a type variable. In that case, a + * 'null' AnnotationMirror is a legal argument that represents no annotation. + * + * @param subQualifier a qualifier that might be a subtype + * @param superQualifier a qualifier that might be a subtype + * @return true iff {@code subQualifier} is a subqualifier of, or equal to, {@code + * superQualifier} + * @deprecated Without the bounds of the type variable, it is not possible to correctly compute + * the subtype relationship between "no qualifier" and a qualifier. Use {@link + * TypeHierarchy#isSubtype(AnnotatedTypeMirror, AnnotatedTypeMirror)}. + */ + @Deprecated + default boolean isSubtypeTypeVariable( + @Nullable AnnotationMirror subQualifier, @Nullable AnnotationMirror superQualifier) { + if (subQualifier == null) { + // [] is a supertype of any qualifier, and [] <: [] + return true; + } else if (superQualifier == null) { + // [] is a subtype of no qualifier (only []) + return false; + } + return isSubtype(subQualifier, superQualifier); } /** - * Tests whether {@code subAnno} is a sub-qualifier of, or equal to, {@code superAnno}, - * according to the type qualifier hierarchy. This checks only the qualifiers, not the Java - * type. + * Tests whether {@code subQualifier} is a sub-qualifier of, or equal to, {@code + * superQualifier}, according to the type qualifier hierarchy. This checks only the qualifiers, + * not the Java type. * *

    This method takes an annotated type to decide if the type variable version of the method * should be invoked, or if the normal version is sufficient (which provides more strict @@ -417,25 +425,30 @@ public static boolean canHaveEmptyAnnotationSet(AnnotatedTypeMirror type) { * * @param subType used to decide whether to call isSubtypeTypeVariable * @param superType used to decide whether to call isSubtypeTypeVariable - * @param subAnno the type qualifier that might be a subtype - * @param superAnno the type qualifier that might be a supertype - * @return true iff {@code subAnno} is a sub qualifier of, or equal to, {@code superAnno} - */ - public boolean isSubtype( + * @param subQualifier the type qualifier that might be a subtype + * @param superQualifier the type qualifier that might be a supertype + * @return true iff {@code subQualifier} is a subqualifier of, or equal to, {@code + * superQualifier} + * @deprecated Without the bounds of the type variable, it is not possible to correctly compute + * the subtype relationship between "no qualifier" and a qualifier. Use {@link + * TypeHierarchy#isSubtype(AnnotatedTypeMirror, AnnotatedTypeMirror)}. + */ + @Deprecated + default boolean isSubtype( AnnotatedTypeMirror subType, AnnotatedTypeMirror superType, - AnnotationMirror subAnno, - AnnotationMirror superAnno) { + AnnotationMirror subQualifier, + AnnotationMirror superQualifier) { if (canHaveEmptyAnnotationSet(subType) || canHaveEmptyAnnotationSet(superType)) { - return isSubtypeTypeVariable(subAnno, superAnno); + return isSubtypeTypeVariable(subQualifier, superQualifier); } else { - return isSubtype(subAnno, superAnno); + return isSubtype(subQualifier, superQualifier); } } /** - * Tests whether there is any annotation in {@code supers} that is a super qualifier of, or - * equal to, some annotation in {@code subs}. {@code supers} and {@code subs} contain only the + * Tests whether there is any annotation in {@code supers} that is a superqualifier of, or equal + * to, some annotation in {@code subs}. {@code supers} and {@code subs} contain only the * annotations, not the Java type. * *

    This method takes an annotated type to decide if the type variable version of the method @@ -448,8 +461,12 @@ public boolean isSubtype( * @param supers the type qualifiers that might be a supertype * @return true iff an annotation in {@code supers} is a supertype of, or equal to, one in * {@code subs} + * @deprecated Without the bounds of the type variable, it is not possible to correctly compute + * the subtype relationship between "no qualifier" and a qualifier. Use {@link + * TypeHierarchy#isSubtype(AnnotatedTypeMirror, AnnotatedTypeMirror)}. */ - public boolean isSubtype( + @Deprecated + default boolean isSubtype( AnnotatedTypeMirror subType, AnnotatedTypeMirror superType, Collection subs, @@ -462,7 +479,38 @@ public boolean isSubtype( } /** - * Returns the least upper bound for the qualifiers a1 and a2. + * Tests whether there is any annotation in superAnnos that is a superqualifier of or equal to + * some annotation in subAnnos. superAnnos and subAnnos contain only the annotations, not the + * Java type. + * + *

    This method works even if the underlying Java type is a type variable. In that case, the + * empty set is a legal argument that represents no annotation. + * + * @param subAnnos qualifiers + * @param superAnnos qualifiers + * @return true iff an annotation in superAnnos is a supertype of, or equal to, one in subAnnos + * @deprecated Without the bounds of the type variable, it is not possible to correctly compute + * the subtype relationship between "no qualifier" and a qualifier. Use {@link + * TypeHierarchy#isSubtype(AnnotatedTypeMirror, AnnotatedTypeMirror)}. + */ + // This method requires more revision. + @Deprecated + default boolean isSubtypeTypeVariable( + Collection subAnnos, + Collection superAnnos) { + for (AnnotationMirror top : getTopAnnotations()) { + AnnotationMirror rhsForTop = findAnnotationInHierarchy(subAnnos, top); + AnnotationMirror lhsForTop = findAnnotationInHierarchy(superAnnos, top); + if (!isSubtypeTypeVariable(rhsForTop, lhsForTop)) { + return false; + } + } + return true; + } + + /** + * Returns the least upper bound for the qualifiers a1 and a2. Returns null if the qualifiers + * are not from the same qualifier hierarchy. * *

    Examples: * @@ -470,50 +518,61 @@ public boolean isSubtype( *

  • For NonNull, leastUpperBound('Nullable', 'NonNull') → Nullable * * - * The two qualifiers have to be from the same qualifier hierarchy. Otherwise, null will be - * returned. - * - *

    This method takes an annotated type to decide if the type variable version of the method - * should be invoked, or if the normal version is sufficient (which provides more strict - * checks). + *

    This method works even if the underlying Java type is a type variable. In that case, a + * 'null' AnnotationMirror is a legal argument that represents no annotation. * + * @param a1 anno1 + * @param a2 anno2 * @return the least restrictive qualifiers for both types - */ - public AnnotationMirror leastUpperBound( - AnnotatedTypeMirror type1, - AnnotatedTypeMirror type2, - AnnotationMirror a1, - AnnotationMirror a2) { - if (canHaveEmptyAnnotationSet(type1) || canHaveEmptyAnnotationSet(type2)) { - return leastUpperBoundTypeVariable(a1, a2); - } else { - return leastUpperBound(a1, a2); + * @deprecated Without the bounds of the type variable, it is not possible to correctly compute + * the relationship between "no qualifier" and a qualifier. Use {@link + * org.checkerframework.framework.util.AnnotatedTypes#leastUpperBound(AnnotatedTypeFactory, + * AnnotatedTypeMirror, AnnotatedTypeMirror)}. + */ + @Deprecated + default @Nullable AnnotationMirror leastUpperBoundTypeVariable( + AnnotationMirror a1, AnnotationMirror a2) { + if (a1 == null || a2 == null) { + // [] is a supertype of any qualifier, and [] <: [] + return null; } + return leastUpperBound(a1, a2); } /** - * Returns the greatest lower bound for the qualifiers a1 and a2. + * Returns the least upper bound for the qualifiers a1 and a2. Returns null if the qualifiers + * are not from the same qualifier hierarchy. + * + *

    Examples: * - *

    The two qualifiers have to be from the same qualifier hierarchy. Otherwise, null will be - * returned. + *

      + *
    • For NonNull, leastUpperBound('Nullable', 'NonNull') → Nullable + *
    * *

    This method takes an annotated type to decide if the type variable version of the method * should be invoked, or if the normal version is sufficient (which provides more strict * checks). * - * @param a1 first annotation - * @param a2 second annotation - * @return greatest lower bound of the two annotations + * @param type1 type 1 + * @param type2 type 2 + * @param a1 annotation + * @param a2 annotation + * @return the least restrictive qualifiers for both types + * @deprecated Without the bounds of the type variable, it is not possible to correctly compute + * the relationship between "no qualifier" and a qualifier. Use {@link + * org.checkerframework.framework.util.AnnotatedTypes#leastUpperBound(AnnotatedTypeFactory, + * AnnotatedTypeMirror, AnnotatedTypeMirror)}. */ - public AnnotationMirror greatestLowerBound( + @Deprecated + default @Nullable AnnotationMirror leastUpperBound( AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, AnnotationMirror a1, AnnotationMirror a2) { if (canHaveEmptyAnnotationSet(type1) || canHaveEmptyAnnotationSet(type2)) { - return greatestLowerBoundTypeVariable(a1, a2); + return leastUpperBoundTypeVariable(a1, a2); } else { - return greatestLowerBound(a1, a2); + return leastUpperBound(a1, a2); } } @@ -524,140 +583,200 @@ public AnnotationMirror greatestLowerBound( *

    This is necessary for determining the type of a conditional expression ({@code ?:}), where * the type of the expression is the least upper bound of the true and false clauses. * - *

    This method takes an annotated type to decide if the type variable version of the method - * should be invoked, or if the normal version is sufficient (which provides more strict - * checks). + *

    This method works even if the underlying Java type is a type variable. In that case, the + * empty set is a legal argument that represents no annotation. * + * @param annos1 qualifiers + * @param annos2 qualifiers * @return the least upper bound of annos1 and annos2 - */ - public Set leastUpperBounds( - AnnotatedTypeMirror type1, - AnnotatedTypeMirror type2, + * @deprecated Without the bounds of the type variable, it is not possible to correctly compute + * the relationship between "no qualifier" and a qualifier. Use {@link + * org.checkerframework.framework.util.AnnotatedTypes#leastUpperBound(AnnotatedTypeFactory, + * AnnotatedTypeMirror, AnnotatedTypeMirror)}. + */ + @Deprecated + @SuppressWarnings("nullness") // Don't check deprecated method. + default Set leastUpperBoundsTypeVariable( Collection annos1, - Collection annos2) { - if (canHaveEmptyAnnotationSet(type1) || canHaveEmptyAnnotationSet(type2)) { - return leastUpperBoundsTypeVariable(annos1, annos2); - } else { - return leastUpperBounds(annos1, annos2); + Collection annos2) { + Set result = AnnotationUtils.createAnnotationSet(); + for (AnnotationMirror top : getTopAnnotations()) { + AnnotationMirror anno1ForTop = null; + for (AnnotationMirror anno1 : annos1) { + if (isSubtypeTypeVariable(anno1, top)) { + anno1ForTop = anno1; + } + } + AnnotationMirror anno2ForTop = null; + for (AnnotationMirror anno2 : annos2) { + if (isSubtypeTypeVariable(anno2, top)) { + anno2ForTop = anno2; + } + } + AnnotationMirror t = leastUpperBoundTypeVariable(anno1ForTop, anno2ForTop); + if (t != null) { + result.add(t); + } } + return result; } /** - * Returns the type qualifiers that are the greatest lower bound of the qualifiers in annos1 and + * Returns the type qualifiers that are the least upper bound of the qualifiers in annos1 and * annos2. * - *

    The two qualifiers have to be from the same qualifier hierarchy. Otherwise, null will be - * returned. + *

    This is necessary for determining the type of a conditional expression ({@code ?:}), where + * the type of the expression is the least upper bound of the true and false clauses. * *

    This method takes an annotated type to decide if the type variable version of the method * should be invoked, or if the normal version is sufficient (which provides more strict * checks). * - * @param annos1 first collection of qualifiers - * @param annos2 second collection of qualifiers - * @return greatest lower bound of the two collections of qualifiers + * @param type1 annotated type + * @param type2 annotated type + * @param annos1 qualifiers + * @param annos2 qualifiers + * @return the least upper bound of annos1 and annos2 + * @deprecated Without the bounds of the type variable, it is not possible to correctly compute + * the relationship between "no qualifier" and a qualifier. Use {@link + * org.checkerframework.framework.util.AnnotatedTypes#leastUpperBound(AnnotatedTypeFactory, + * AnnotatedTypeMirror, AnnotatedTypeMirror)}. */ - public Set greatestLowerBounds( + @Deprecated + default Set leastUpperBounds( AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Collection annos1, Collection annos2) { if (canHaveEmptyAnnotationSet(type1) || canHaveEmptyAnnotationSet(type2)) { - return greatestLowerBoundsTypeVariable(annos1, annos2); + return leastUpperBoundsTypeVariable(annos1, annos2); } else { - return greatestLowerBounds(annos1, annos2); + return leastUpperBounds(annos1, annos2); } } /** - * Returns the annotation in annos that is in the same hierarchy as annotationMirror. + * Returns the greatest lower bound for the qualifiers a1 and a2. Returns null if the qualifiers + * are not from the same qualifier hierarchy. * - * @param annos set of annotations to search - * @param annotationMirror annotation that is in the same hierarchy as the returned annotation - * @return annotation in the same hierarchy as annotationMirror, or null if one is not found - */ - public AnnotationMirror findAnnotationInSameHierarchy( - Collection annos, AnnotationMirror annotationMirror) { - AnnotationMirror top = this.getTopAnnotation(annotationMirror); - return findAnnotationInHierarchy(annos, top); - } - - /** - * Returns the annotation in annos that is in the hierarchy for which annotationMirror is top. + *

    This method works even if the underlying Java type is a type variable. In that case, a + * 'null' AnnotationMirror is a legal argument that represents no annotation. * - * @param annos set of annotations to search - * @param top the top annotation in the hierarchy to which the returned annotation belongs - * @return annotation in the same hierarchy as annotationMirror, or null if one is not found - */ - public AnnotationMirror findAnnotationInHierarchy( - Collection annos, AnnotationMirror top) { - for (AnnotationMirror anno : annos) { - if (isSubtype(anno, top)) { - return anno; - } + * @param a1 first annotation + * @param a2 second annotation + * @return greatest lower bound of the two annotations + * @deprecated Without the bounds of the type variable, it is not possible to correctly compute + * the relationship between "no qualifier" and a qualifier + */ + @Deprecated + default @Nullable AnnotationMirror greatestLowerBoundTypeVariable( + AnnotationMirror a1, AnnotationMirror a2) { + if (a1 == null) { + // [] is a supertype of any qualifier, and [] <: [] + return a2; } - return null; + if (a2 == null) { + // [] is a supertype of any qualifier, and [] <: [] + return a1; + } + return greatestLowerBound(a1, a2); } /** - * Update a mapping from some key to a set of AnnotationMirrors. If the key already exists in - * the mapping and the new qualifier is in the same qualifier hierarchy as any of the existing - * qualifiers, do nothing and return false. If the key already exists in the mapping and the new - * qualifier is not in the same qualifier hierarchy as any of the existing qualifiers, add the - * qualifier to the existing set and return true. If the key does not exist in the mapping, add - * the new qualifier as a singleton set and return true. + * Returns the type qualifiers that are the greatest lower bound of the qualifiers in annos1 and + * annos2. Returns null if the qualifiers are not from the same qualifier hierarchy. * - * @param map the mapping to modify - * @param key the key to update - * @param newQual the value to add - * @return true if the update was done; false if there was a qualifier hierarchy collision + *

    This method works even if the underlying Java type is a type variable. In that case, the + * empty set is a legal argument that represents no annotation. + * + * @param annos1 first collection of qualifiers + * @param annos2 second collection of qualifiers + * @return greatest lower bound of the two collections of qualifiers + * @deprecated Without the bounds of the type variable, it is not possible to correctly compute + * the relationship between "no qualifier" and a qualifier */ - public boolean updateMappingToMutableSet( - Map> map, T key, AnnotationMirror newQual) { - - if (!map.containsKey(key)) { - Set set = AnnotationUtils.createAnnotationSet(); - set.add(newQual); - map.put(key, set); - } else { - Set prevs = map.get(key); - for (AnnotationMirror p : prevs) { - if (AnnotationUtils.areSame(getTopAnnotation(p), getTopAnnotation(newQual))) { - return false; + @Deprecated + @SuppressWarnings("nullness") // Don't check deprecated method. + default Set greatestLowerBoundsTypeVariable( + Collection annos1, + Collection annos2) { + Set result = AnnotationUtils.createAnnotationSet(); + for (AnnotationMirror top : getTopAnnotations()) { + AnnotationMirror anno1ForTop = null; + for (AnnotationMirror anno1 : annos1) { + if (isSubtypeTypeVariable(anno1, top)) { + anno1ForTop = anno1; } } - prevs.add(newQual); - map.put(key, prevs); + AnnotationMirror anno2ForTop = null; + for (AnnotationMirror anno2 : annos2) { + if (isSubtypeTypeVariable(anno2, top)) { + anno2ForTop = anno2; + } + } + AnnotationMirror t = greatestLowerBoundTypeVariable(anno1ForTop, anno2ForTop); + if (t != null) { + result.add(t); + } } - return true; + return result; } /** - * Throws an exception if the given collections do not have the same size. + * Returns the greatest lower bound for the qualifiers a1 and a2. Returns null if the qualifiers + * are not from the same qualifier hierarchy. * - * @param c1 the first collection - * @param c2 the second collection + *

    This method takes an annotated type to decide if the type variable version of the method + * should be invoked, or if the normal version is sufficient (which provides more strict + * checks). + * + * @param type1 annotated type + * @param type2 annotated type + * @param a1 first annotation + * @param a2 second annotation + * @return greatest lower bound of the two annotations + * @deprecated Without the bounds of the type variable, it is not possible to correctly compute + * the relationship between "no qualifier" and a qualifier */ - private static void assertSameSize(Collection c1, Collection c2) { - if (c1.size() != c2.size()) { - throw new BugInCF( - "inconsistent sizes (%d, %d):%n %s%n %s", c1.size(), c2.size(), c1, c2); + @Deprecated + default @Nullable AnnotationMirror greatestLowerBound( + AnnotatedTypeMirror type1, + AnnotatedTypeMirror type2, + AnnotationMirror a1, + AnnotationMirror a2) { + if (canHaveEmptyAnnotationSet(type1) || canHaveEmptyAnnotationSet(type2)) { + return greatestLowerBoundTypeVariable(a1, a2); + } else { + return greatestLowerBound(a1, a2); } } /** - * Throws an exception if the result does not have the same size as the inputs (which are - * assumed to have the same size as one another). + * Returns the type qualifiers that are the greatest lower bound of the qualifiers in annos1 and + * annos2. Returns null if the qualifiers are not from the same qualifier hierarchy. * - * @param c1 the first collection - * @param c2 the second collection - * @param result the result collection + *

    This method takes an annotated type to decide if the type variable version of the method + * should be invoked, or if the normal version is sufficient (which provides more strict + * checks). + * + * @param type1 annotated type + * @param type2 annotated type + * @param annos1 first collection of qualifiers + * @param annos2 second collection of qualifiers + * @return greatest lower bound of the two collections of qualifiers + * @deprecated Without the bounds of the type variable, it is not possible to correctly compute + * the relationship between "no qualifier" and a qualifier */ - private static void assertSameSize(Collection c1, Collection c2, Collection result) { - if (c1.size() != result.size()) { - throw new BugInCF( - "inconsistent sizes (%d, %d, %d):%n %s%n %s%n %s", - c1.size(), c2.size(), result.size(), c1, c2, result); + @Deprecated + default Set greatestLowerBounds( + AnnotatedTypeMirror type1, + AnnotatedTypeMirror type2, + Collection annos1, + Collection annos2) { + if (canHaveEmptyAnnotationSet(type1) || canHaveEmptyAnnotationSet(type2)) { + return greatestLowerBoundsTypeVariable(annos1, annos2); + } else { + return greatestLowerBounds(annos1, annos2); } } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/poly/DefaultQualifierPolymorphism.java b/framework/src/main/java/org/checkerframework/framework/type/poly/DefaultQualifierPolymorphism.java index b4f2afa7c965..f167f9a5fa4d 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/poly/DefaultQualifierPolymorphism.java +++ b/framework/src/main/java/org/checkerframework/framework/type/poly/DefaultQualifierPolymorphism.java @@ -59,6 +59,6 @@ protected AnnotationMirror combine( } else if (a2 == null) { return a1; } - return qualHierarchy.leastUpperBoundTypeVariable(a1, a2); + return qualHierarchy.leastUpperBound(a1, a2); } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/AtmLubVisitor.java b/framework/src/main/java/org/checkerframework/framework/util/AtmLubVisitor.java index 9ff6410350c4..2b640b8c3935 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/AtmLubVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/util/AtmLubVisitor.java @@ -245,13 +245,12 @@ private void lubWildcard( visit(type1LowerBound, type2LowerBound, lubLowerBound); for (AnnotationMirror top : qualifierHierarchy.getTopAnnotations()) { - AnnotationMirror glb = - qualifierHierarchy.greatestLowerBound( - type1LowerBound, - type2LowerBound, - type1LowerBound.getAnnotationInHierarchy(top), - type2LowerBound.getAnnotationInHierarchy(top)); - if (glb != null) { + AnnotationMirror anno1 = type1LowerBound.getAnnotationInHierarchy(top); + AnnotationMirror anno2 = type2LowerBound.getAnnotationInHierarchy(top); + + AnnotationMirror glb = null; + if (anno1 != null && anno2 != null) { + glb = qualifierHierarchy.greatestLowerBound(anno1, anno2); lubLowerBound.replaceAnnotation(glb); } } diff --git a/framework/src/main/java/org/checkerframework/framework/util/MultiGraphQualifierHierarchy.java b/framework/src/main/java/org/checkerframework/framework/util/MultiGraphQualifierHierarchy.java index 9b0e16eab6d2..76fcee6f3bb1 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/MultiGraphQualifierHierarchy.java +++ b/framework/src/main/java/org/checkerframework/framework/util/MultiGraphQualifierHierarchy.java @@ -29,7 +29,7 @@ *

    This class is immutable and can be only created through {@link MultiGraphFactory}. */ @SuppressWarnings("interning") // TODO after https://tinyurl.com/cfissue/3404 is merged -public class MultiGraphQualifierHierarchy extends QualifierHierarchy { +public class MultiGraphQualifierHierarchy implements QualifierHierarchy { /** * Factory used to create an instance of {@link GraphQualifierHierarchy}. A factory can be used @@ -401,20 +401,6 @@ && isSubtype(rhsAnno, lhsAnno)) { return lhs.size() == valid; } - @Override - public boolean isSubtypeTypeVariable( - Collection subAnnos, - Collection superAnnos) { - for (AnnotationMirror top : getTopAnnotations()) { - AnnotationMirror rhsForTop = findAnnotationInHierarchy(subAnnos, top); - AnnotationMirror lhsForTop = findAnnotationInHierarchy(superAnnos, top); - if (!isSubtypeTypeVariable(rhsForTop, lhsForTop)) { - return false; - } - } - return true; - } - /** For caching results of lubs * */ private Map lubs = null; @@ -436,15 +422,6 @@ public AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2 return lubs.get(pair); } - @Override - public AnnotationMirror leastUpperBoundTypeVariable(AnnotationMirror a1, AnnotationMirror a2) { - if (a1 == null || a2 == null) { - // [] is a supertype of any qualifier, and [] <: [] - return null; - } - return leastUpperBound(a1, a2); - } - /** A cache of the results of glb computations. Maps from a pair of annotations to their glb. */ private Map glbs = null; @@ -460,20 +437,6 @@ public AnnotationMirror greatestLowerBound(AnnotationMirror a1, AnnotationMirror return glbs.get(pair); } - @Override - public AnnotationMirror greatestLowerBoundTypeVariable( - AnnotationMirror a1, AnnotationMirror a2) { - if (a1 == null) { - // [] is a supertype of any qualifier, and [] <: [] - return a2; - } - if (a2 == null) { - // [] is a supertype of any qualifier, and [] <: [] - return a1; - } - return greatestLowerBound(a1, a2); - } - /** * {@inheritDoc} * @@ -506,19 +469,12 @@ public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) { return AnnotationUtils.containsSame(supermap1, superAnno); } - @Override - public boolean isSubtypeTypeVariable(AnnotationMirror subAnno, AnnotationMirror superAnno) { - if (superAnno == null) { - // [] is a supertype of any qualifier, and [] <: [] - return true; - } - if (subAnno == null) { - // [] is a subtype of no qualifier (only []) - return false; - } - return isSubtype(subAnno, superAnno); - } - + /** + * Throw a {@link BugInCF} if {@code a} is not in the {@link #supertypesTransitive} or {@link + * #polyQualifiers}. + * + * @param a qualifier + */ private final void checkAnnoInGraph(AnnotationMirror a) { if (AnnotationUtils.containsSame(supertypesTransitive.keySet(), a) || AnnotationUtils.containsSame(polyQualifiers.values(), a)) { From 4676bdbc86912fa211d91b4d090b19cb111cd293 Mon Sep 17 00:00:00 2001 From: Weitian Xing Date: Wed, 29 Jul 2020 19:30:26 -0400 Subject: [PATCH 103/138] Fix issue 3482: Add method and class tree to CFGLambda, handle lambda case in DOTCFGVisualizer to avoid crash. (#3490) --- .../dataflow/cfg/DOTCFGVisualizer.java | 21 ++++++++++ .../dataflow/cfg/UnderlyingAST.java | 41 ++++++++++++++++++- .../type/GenericAnnotatedTypeFactory.java | 6 ++- 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/DOTCFGVisualizer.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/DOTCFGVisualizer.java index c04479df96e8..0245c21f451a 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/DOTCFGVisualizer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/DOTCFGVisualizer.java @@ -18,6 +18,7 @@ import org.checkerframework.dataflow.analysis.FlowExpressions; import org.checkerframework.dataflow.analysis.Store; import org.checkerframework.dataflow.analysis.TransferFunction; +import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGLambda; import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGMethod; import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGStatement; import org.checkerframework.dataflow.cfg.block.Block; @@ -203,6 +204,26 @@ protected String dotOutputFileName(UnderlyingAST ast) { srcLoc.append(")::"); srcLoc.append(((JCTree) cfgMethod.getMethod()).pos); srcLoc.append(">"); + } else if (ast.getKind() == UnderlyingAST.Kind.LAMBDA) { + CFGLambda cfgLambda = (CFGLambda) ast; + String clsName = cfgLambda.getClassTree().getSimpleName().toString(); + String methodName = cfgLambda.getMethod().getName().toString(); + int hashCode = cfgLambda.getCode().hashCode(); + outFile.append(clsName); + outFile.append("-"); + outFile.append(methodName); + outFile.append("-"); + outFile.append(hashCode); + + srcLoc.append("<"); + srcLoc.append(clsName); + srcLoc.append("::"); + srcLoc.append(methodName); + srcLoc.append("("); + srcLoc.append(cfgLambda.getMethod().getParameters()); + srcLoc.append(")::"); + srcLoc.append(((JCTree) cfgLambda.getCode()).pos); + srcLoc.append(">"); } else { throw new BugInCF("Unexpected AST kind: " + ast.getKind() + " value: " + ast); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/UnderlyingAST.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/UnderlyingAST.java index 73150bc46668..a4c1bf658262 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/UnderlyingAST.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/UnderlyingAST.java @@ -75,11 +75,27 @@ public String toString() { /** If the underlying AST is a lambda. */ public static class CFGLambda extends UnderlyingAST { + /** The lambda expression. */ private final LambdaExpressionTree lambda; - public CFGLambda(LambdaExpressionTree lambda) { + /** The enclosing class of the lambda. */ + private final ClassTree classTree; + + /** The enclosing method of the lambda. */ + private final MethodTree method; + + /** + * Create a new CFGLambda. + * + * @param lambda the lambda expression + * @param classTree the enclosing class of the lambda + * @param method the enclosing method of the lambda + */ + public CFGLambda(LambdaExpressionTree lambda, ClassTree classTree, MethodTree method) { super(Kind.LAMBDA); this.lambda = lambda; + this.method = method; + this.classTree = classTree; } @Override @@ -87,10 +103,33 @@ public Tree getCode() { return lambda.getBody(); } + /** + * Returns the lambda expression tree. + * + * @return the lambda expression tree + */ public LambdaExpressionTree getLambdaTree() { return lambda; } + /** + * Returns the enclosing class of the lambda. + * + * @return the enclosing class of the lambda + */ + public ClassTree getClassTree() { + return classTree; + } + + /** + * Returns the enclosing method of the lambda. + * + * @return the enclosing method of the lambda + */ + public MethodTree getMethod() { + return method; + } + @Override public String toString() { return SystemUtil.joinLines("CFGLambda(", lambda, ")"); 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 284e8ca7e65d..6a08d3c353de 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java @@ -1172,10 +1172,14 @@ protected void performFlowAnalysis(ClassTree classTree) { while (!lambdaQueue.isEmpty()) { Pair lambdaPair = lambdaQueue.poll(); + MethodTree mt = + (MethodTree) + TreeUtils.enclosingOfKind( + getPath(lambdaPair.first), Kind.METHOD); analyze( queue, lambdaQueue, - new CFGLambda(lambdaPair.first), + new CFGLambda(lambdaPair.first, classTree, mt), fieldValues, classTree, false, From f9fd483a8dd2b225d272c954d93743ebc1382480 Mon Sep 17 00:00:00 2001 From: lnsun <57457122+lnsun@users.noreply.github.com> Date: Wed, 29 Jul 2020 19:37:59 -0400 Subject: [PATCH 104/138] ComponentFinderUtil for Analysis --- .../type/GenericAnnotatedTypeFactory.java | 40 ++++++++----------- 1 file changed, 17 insertions(+), 23 deletions(-) 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 284e8ca7e65d..6356bcc9d59b 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java @@ -87,6 +87,7 @@ import org.checkerframework.framework.type.typeannotator.PropagationTypeAnnotator; import org.checkerframework.framework.type.typeannotator.TypeAnnotator; import org.checkerframework.framework.util.AnnotatedTypes; +import org.checkerframework.framework.util.ComponentFinderUtil; import org.checkerframework.framework.util.FlowExpressionParseUtil; import org.checkerframework.framework.util.FlowExpressionParseUtil.FlowExpressionParseException; import org.checkerframework.framework.util.defaults.QualifierDefaults; @@ -392,29 +393,22 @@ protected DefaultQualifierForUseTypeAnnotator createDefaultForUseTypeAnnotator() @SuppressWarnings({"unchecked", "rawtypes"}) protected FlowAnalysis createFlowAnalysis(List> fieldValues) { - // Try to reflectively load the visitor. - Class checkerClass = checker.getClass(); - - while (checkerClass != BaseTypeChecker.class) { - FlowAnalysis result = - BaseTypeChecker.invokeConstructorFor( - BaseTypeChecker.getRelatedClassName(checkerClass, "Analysis"), - new Class[] {BaseTypeChecker.class, this.getClass(), List.class}, - new Object[] {checker, this, fieldValues}); - if (result != null) { - return result; - } - checkerClass = checkerClass.getSuperclass(); - } - - // If an analysis couldn't be loaded reflectively, return the - // default. - List> tmp = new ArrayList<>(); - for (Pair fieldVal : fieldValues) { - assert fieldVal.second instanceof CFValue; - tmp.add(Pair.of(fieldVal.first, (CFValue) fieldVal.second)); - } - return (FlowAnalysis) new CFAnalysis(checker, (GenericAnnotatedTypeFactory) this, tmp); + return ComponentFinderUtil.find( + checker, + "Analysis", + checker1 -> { + // If an analysis couldn't be loaded reflectively, return the + // default. + List> tmp = new ArrayList<>(); + for (Pair fieldVal : fieldValues) { + assert fieldVal.second instanceof CFValue; + tmp.add(Pair.of(fieldVal.first, (CFValue) fieldVal.second)); + } + return (FlowAnalysis) + new CFAnalysis(checker1, (GenericAnnotatedTypeFactory) this, tmp); + }, + new Class[] {BaseTypeChecker.class, this.getClass(), List.class}, + new Object[] {checker, this, fieldValues}); } /** From f9d4dc79ab3fec548c6582078339477d9a0fb410 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Wed, 29 Jul 2020 16:40:36 -0700 Subject: [PATCH 105/138] Improve CFG visualization documentation (#3514) --- .../dataflow/cfg/AbstractCFGVisualizer.java | 5 +++-- docs/manual/creating-a-checker.tex | 11 ++++++++--- .../framework/type/GenericAnnotatedTypeFactory.java | 9 ++++++--- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/AbstractCFGVisualizer.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/AbstractCFGVisualizer.java index e10175c36004..84be51534b51 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/AbstractCFGVisualizer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/AbstractCFGVisualizer.java @@ -44,8 +44,9 @@ public abstract class AbstractCFGVisualizer< implements CFGVisualizer { /** - * Initialized in {@link #init(Map)}. If its value is {@code true}, {@link CFGVisualizer} - * returns more detailed information. + * If {@code true}, {@link CFGVisualizer} returns more detailed information. + * + *

    Initialized in {@link #init(Map)}. */ protected boolean verbose; diff --git a/docs/manual/creating-a-checker.tex b/docs/manual/creating-a-checker.tex index e1005d18ee25..1604077f90ee 100644 --- a/docs/manual/creating-a-checker.tex +++ b/docs/manual/creating-a-checker.tex @@ -1905,11 +1905,16 @@ The graph also contains information about flow-sensitively refined types of various expressions at many program points. - The argument is a comma-separated sequence of values or key-value pairs. + The argument is a comma-separated sequence of keys or key--value pairs. The first argument is the fully-qualified name of the \ implementation - that should be used. The remaining values or key-value pairs are - passed to \. + that should be used. The remaining keys or key--value pairs are + passed to \. Supported keys include + \begin{itemize} + \item \ + \item \ directory into which to write files + \item \ + \end{itemize} \end{itemize} 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 6a08d3c353de..9c518e1ce508 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java @@ -1797,8 +1797,11 @@ private String getCheckerName() { return checkerName; } - /* Parse values or key-value pairs into a map from value to true, respectively, - * from the value to the key. + /** + * Parse keys or key-value pairs into a map from key to value (to true if no value is provided). + * + * @param opts the CFG visualization options + * @return a map that represents the options */ private Map processCFGVisualizerOption(String[] opts) { Map res = new HashMap<>(opts.length - 1); @@ -1814,7 +1817,7 @@ private Map processCFGVisualizerOption(String[] opts) { res.put(split[0], split[1]); break; default: - throw new UserError("Too many `=` in cfgviz option: " + opt); + throw new UserError("Too many '=' in cfgviz option: " + opt); } } return res; From 52b58d5b03c2c4a1fa173ffc86f174a62a1e27b6 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Wed, 29 Jul 2020 16:46:22 -0700 Subject: [PATCH 106/138] Improve Dataflow Framework manual --- checker/bin-devel/git.pre-commit | 19 +- dataflow/manual/Makefile | 2 +- dataflow/manual/content.tex | 375 +++++++----------- dataflow/manual/dataflow.tex | 33 +- dataflow/manual/examples/CFGAssert.java | 7 +- .../manual/examples/CFGConditionalOr.java | 10 +- .../manual/examples/CFGConditionalOr2.java | 8 +- .../manual/examples/CFGFieldAssignment.java | 8 +- dataflow/manual/examples/CFGIfStatement.java | 14 +- dataflow/manual/examples/CFGSimple.java | 10 +- dataflow/manual/examples/CFGSwitch.java | 22 +- dataflow/manual/examples/ConstSimple.java | 26 +- dataflow/manual/examples/LiveSimple.java | 14 +- 13 files changed, 233 insertions(+), 315 deletions(-) diff --git a/checker/bin-devel/git.pre-commit b/checker/bin-devel/git.pre-commit index 486ad95cc47b..72d93f2313f5 100755 --- a/checker/bin-devel/git.pre-commit +++ b/checker/bin-devel/git.pre-commit @@ -12,13 +12,15 @@ set -e # Need to keep checked files in sync with getJavaFilesToFormat in build.gradle. # Otherwise `./gradlew reformat` might not reformat a file that this # hook complains about. -CHANGED_JAVA_FILES=`git diff --staged --name-only --diff-filter=ACM | grep '\.java$' | grep -v '/jdk/' | grep -v 'stubparser/' | grep -v '/nullness-javac-errors/' ` || true +CHANGED_JAVA_FILES=$(git diff --staged --name-only --diff-filter=ACM | grep '\.java$' | grep -v '/jdk/' | grep -v 'stubparser/' | grep -v '/nullness-javac-errors/' | grep -v '/dataflow/manual/examples/' ) || true # echo CHANGED_JAVA_FILES "'"${CHANGED_JAVA_FILES}"'" -if [ ! -z "$CHANGED_JAVA_FILES" ]; then +if [ -n "$CHANGED_JAVA_FILES" ]; then ./gradlew getCodeFormatScripts -q ## For debugging: # echo "CHANGED_JAVA_FILES: ${CHANGED_JAVA_FILES}" - python checker/bin-devel/.run-google-java-format/check-google-java-format.py --aosp ${CHANGED_JAVA_FILES} || (echo "Try running: ./gradlew reformat" && /bin/false) + + # shellcheck disable=SC2086 + python checker/bin-devel/.run-google-java-format/check-google-java-format.py --aosp ${CHANGED_JAVA_FILES} || (echo "Problem in pre-commit. Try running: ./gradlew reformat" && /bin/false) BRANCH=$(git rev-parse --abbrev-ref HEAD) if [ "$BRANCH" = "master" ]; then @@ -34,11 +36,14 @@ fi # This is to handle non-.java files, since the above already handled .java files. # May need to remove files that are allowed to have trailing whitespace or are # not text files. -CHANGED_FILES=`git diff --staged --name-only --diff-filter=ACM | grep -v '\.class$' | grep -v '\.gz$' | grep -v '\.jar$' | grep -v '\.pdf$' | grep -v '\.png$' | grep -v '\.xcf$'` || true -if [ ! -z "$CHANGED_FILES" ]; then +CHANGED_FILES=$(git diff --staged --name-only --diff-filter=ACM | grep -v '\.class$' | grep -v '\.gz$' | grep -v '\.jar$' | grep -v '\.pdf$' | grep -v '\.png$' | grep -v '\.xcf$') || true +if [ -n "$CHANGED_FILES" ]; then + ## For debugging: # echo "CHANGED_FILES: ${CHANGED_FILES}" - FILES_WITH_TRAILING_SPACES=`grep -l -s '[[:blank:]]$' ${CHANGED_FILES} 2>&1` || true - if [ ! -z "$FILES_WITH_TRAILING_SPACES" ]; then + + # shellcheck disable=SC2086 + FILES_WITH_TRAILING_SPACES=$(grep -l -s '[[:blank:]]$' ${CHANGED_FILES} 2>&1) || true + if [ -n "$FILES_WITH_TRAILING_SPACES" ]; then echo "Some files have trailing whitespace: ${FILES_WITH_TRAILING_SPACES}" && exit 1 fi fi diff --git a/dataflow/manual/Makefile b/dataflow/manual/Makefile index 5177f8006dc9..149fe75b6395 100644 --- a/dataflow/manual/Makefile +++ b/dataflow/manual/Makefile @@ -1,4 +1,4 @@ -dataflow.pdf: dataflow.tex content.tex +dataflow.pdf: dataflow.tex content.tex examples pdflatex dataflow pdflatex dataflow diff --git a/dataflow/manual/content.tex b/dataflow/manual/content.tex index 5100b6c87ce9..e743f9939b49 100644 --- a/dataflow/manual/content.tex +++ b/dataflow/manual/content.tex @@ -8,33 +8,17 @@ \section{Introduction} \href{https://github.com/uber/NullAway}{NullAway}, and other contexts. The primary purpose of the Dataflow Framework is to estimate values: -that is, to determine that, on a particular line of source code, what -values a variable might contain. This can also determine that a variable -has a more precise type than its declared type. This enables -flow-sensitive type checking in the Checker Framework, which -reduces the burden of annotating a program. The Dataflow Framework was -designed with several goals in mind. First, to encourage other uses -of the framework, it is written as a separate package that can be -built and used with no dependence on the Checker Framework. Second, -the framework is currently intended to support analysis but not -transformation, so it provides information that can be used by a type -checker or an IDE, but it does not support optimization. Third, the -framework aims to minimize the burden on developers who build on top -of it. In particular, the hierarchy of analysis classes is designed -to reduce the effort required to implement a new flow-sensitive type -checker in the Checker Framework. The -\href{https://docs.google.com/document/d/1oYzbOrrS4ZEEx4wQgIHbijNzcI5CiQAq_-1NrOS8JME/edit?usp=sharing}{Dataflow User's Guide} -gives an introduction to customizing dataflow to add checker specific -enhancements. +for each line of source code, it determines what +values each variable might contain. The Dataflow Framework's result (\autoref{sec:analysis_result_class}) is an abstract value for each expression (an estimate of the expression's run-time value) and a store at each program point. A -store maps variables and other distinguished expressions to abstract -values. As a pre-pass, the Dataflow Framework transforming an input +store maps variables and other expressions to abstract +values. As a pre-pass, the Dataflow Framework transforms an input AST into a control flow graph (\autoref{sec:cfg}) consisting of basic -blocks made up of nodes representing single operations. To produce -its output, the Checker Framework performs iterative data flow +blocks made up of nodes representing single operations. An analysis +performs iterative data flow analysis over the control flow graph. The effect of a single node on the dataflow store is represented by a transfer function, which takes an input store and a node and produces an output store. Once the @@ -42,10 +26,25 @@ \section{Introduction} code. In the Checker Framework, the abstract values to be computed are -annotated types. An individual checkers can customize its analysis by +annotated types. An individual checker can customize its analysis by extending the abstract value class and by overriding the behavior of the transfer function for particular node types. +The Dataflow Framework was +designed with several goals in mind. First, to encourage use +beyond the Checker Framework, it is written as a separate package that can be +built and used with no dependence on the Checker Framework. Second, +the framework supports analysis but not +transformation, so it provides information that can be used by a type +checker or an IDE, but it does not support optimization. Third, the +framework aims to minimize the burden on developers who build on top +of it. In particular, the hierarchy of analysis classes is designed +to reduce the effort required to implement a new flow-sensitive type +checker in the Checker Framework. The +\href{https://docs.google.com/document/d/1oYzbOrrS4ZEEx4wQgIHbijNzcI5CiQAq_-1NrOS8JME/edit?usp=sharing}{Dataflow User's Guide} +gives an introduction to customizing dataflow to add checker-specific +enhancements. + \begin{workinprogress} Paragraphs colored in gray with a gray bar on the left side (just like this one) contain questions, additional comments or indicate @@ -62,9 +61,10 @@ \subsection{Projects} % TODO: Update The source code of the combined Checker Framework and Dataflow -Framework is divided into five projects: \code{javacutil}, -\code{dataflow}, \code{stubparser}, \code{framework}, and \code{checker}, -which can be built into distinct jar files. +Framework is divided into multiple projects: \code{javacutil}, +\code{dataflow}, \code{framework}, and \code{checker}, +which can be built into distinct jar files. \code{checker.jar} is a fat +jar that contains all of these, plus the Stub Parser. \code{javacutil} provides convenient interfaces to routines in Oracle's javac library. There are utility classes for interacting @@ -84,18 +84,13 @@ \subsection{Projects} control flow graphs and the base classes required for flow analysis. These classes are described in detail in \autoref{sec:node_classes}. -\code{stubparser} contains the stub-file parsing project. - \code{framework} contains the framework aspects of the Checker Framework, including the derived classes for flow analysis of annotated types which are described later in this document. \code{checker} contains the type system-specific checkers. -The \code{dataflow} project depends on \code{javacutil}, the -\code{framework} project depends on both \code{dataflow} and -\code{javacutil}, \code{stubparser} has no dependencies, and -\code{checker} depends on \code{framework} and \code{stubparser}. +The \code{dataflow} project depends only on \code{javacutil}. \subsection{Classes} @@ -111,12 +106,14 @@ \subsection{Classes} \subsubsection{Nodes} \label{sec:node_classes} -Dataflow doesn't actually work on trees; it works on Nodes. Nodes -simplify writing a dataflow analysis by separating the dataflow -analysis from the original source code. +Dataflow doesn't actually work on trees; it works on Nodes. A Node class represents an individual operation of a program, including arithmetic operations, logical operations, method calls, -variable references, array accesses, etc. \autoref{tab:nodes} lists +variable references, array accesses, etc. +Nodes +simplify writing a dataflow analysis by separating the dataflow +analysis from the original source code. +\autoref{tab:nodes} on page~\pageref{tab:nodes} lists the Node types. \begin{verbatim} @@ -130,9 +127,8 @@ \subsubsection{Nodes} \subsubsection{Blocks} \label{sec:block_classes} -Nodes are grouped into basic blocks using a hierarchy of Block -classes. The hierarchy is composed of five interfaces, two abstract -classes, and four concrete classes. +The Block +classes represent basic blocks. \begin{verbatim} package org.checkerframework.dataflow.cfg.block; @@ -415,7 +411,7 @@ \subsubsubsection{TransferFunction} \end{verbatim} The Regex Checker's transfer function overrides visitMethodInvocation -to special case isRegex and asRegex methods. +to special-case the \code{isRegex} and \code{asRegex} methods. \begin{verbatim} package org.checkerframework.checker.regex; @@ -533,37 +529,31 @@ \subsubsection{AnnotatedTypeFactory} \end{verbatim} -\begin{workinprogress} -We should investigate whether we can optimize CFG generation for -aggregate and compound checkers. -\end{workinprogress} - - \section{The Control-Flow Graph} \label{sec:cfg} -%In this section, we describe the control-flow graph (CFG), and the -%translation from the Java abstract syntax tree (AST) to the CFG. - -This section describes the control-flow graph (CFG), which is used to -represent a single method or field initialization, and the translation -from the abstract syntax tree (AST) to the CFG\@. (The Dataflow -Framework described here is designed to perform an intra-procedural +A control-flow graph (CFG) represents a single method or field +initialization. (The Dataflow Framework performs an intra-procedural analysis. This analysis is modular and every method is considered in -isolation.) We start with a simple example, then give a more formal +isolation.) +This section also describes the translation from the abstract syntax tree +(AST) to the CFG\@. +We start with a simple example, then give a more formal definition of the CFG and its properties, and finally describe the translation from the AST to the CFG. -As is standard, a control-flow graph in the framework is a set of +As is standard, a control-flow graph is a set of basic blocks that are linked by control-flow edges. Possibly less standard, every basic block consists of a sequence of so-called nodes, -which correspond to a minimal Java operation or expression. +each of which represents a minimal Java operation or expression. -\flow{Simple}{A simple Java code snippet to introduce the CFG.} +\flow{CFGSimple}{.33}{1.1}{A simple Java code snippet to introduce the CFG. +In CFG visualizations, special basic blocks are shown as ovals; +conditional basic blocks are polygons with eight sides; and regular and exception +basic blocks are rectangles.} -Consider the method \code{test} of \autoref{lst:CFGSimple} whose -control-flow graph is shown in \autoref{fig:CFGSimple}. The if +Consider the method \code{test} of \autoref{fig:CFGSimple}. The if conditional got translated to a \emph{conditional basic block} (octagon) with two successors. There are also two special basic blocks (ovals) to denote the entry and exit point of the method. @@ -610,29 +600,24 @@ \subsection{Formal Definition of the Control-Flow Graph} point of the method and thus is the only basic block without predecessors. \item Exit block. This basic block denotes the (normal) - exit of a method, and it does not have successors. + exit of a method, and it has no successors. \item Exceptional exit block, which indicates exceptional termination of the method. As an exit block, this block - does not have successors. + has no successors. \end{itemize} Every method has exactly one entry block, zero or one exit blocks, - and zero or one exceptional exit blocks. However, there is - either an exit block or an exceptional exit block. - \begin{workinprogress} - Is this an exclusive or? Or can a method have both a regular - exit block and an exceptional exit block? I think the latter - is true. - \end{workinprogress} + and zero or one exceptional exit blocks. There is always + either an exit block, an exceptional exit block, or both. \item \textbf{Exception basic block.} An \emph{exception basic block} contains exactly one node that \emph{might} throw an exception at runtime (e.g., a method call). There are zero - or one non-exceptional successors, and one or more - exceptional successors (see \autoref{def:edges}). But in all - cases there is at least one successor (regular or - exceptional), and only a basic block containing a + or one non-exceptional successors (only a basic block containing a \code{throw} statement does not have a non-exceptional - successor. + successor). There are one or more + exceptional successors (see \autoref{def:edges}). In all + cases there is at least one successor (regular or + exceptional). \item \textbf{Conditional basic block.} A \emph{conditional basic block} does not contain any nodes and is used as a @@ -653,23 +638,18 @@ \subsection{Formal Definition of the Control-Flow Graph} The Java implementation of the four block types above is described in \autoref{sec:block_classes}. -In the visualizations used in this document (e.g., in -\autoref{fig:CFGSimple}), special basic blocks are shown as ovals, -conditional basic blocks are polygons with eight sides, and any other -basic block appears as a rectangle. - \begin{definition}[Control-Flow Graph Edges] \label{def:edges} The basic blocks of a control-flow graph are connected by directed \emph{edges}. If $b_1$ and $b_2$ are connected by a directed edge -$(b_1,b_2)$, we call $b_1$ the predecessor of $b_2$, and we call $b_2$ -the successor of $b_1$. In a control-flow graph, there are three +$(b_1,b_2)$, we call $b_1$ a predecessor of $b_2$, and we call $b_2$ +a successor of $b_1$. In a control-flow graph, there are three types of edges: \begin{enumerate} \item \textbf{Exceptional edges}. An \emph{exceptional edge} connects an exception basic block with its exceptional successors, and it is labeled by the most general exception that - might cause execution to take this edge during runtime. Note + might cause execution to take this edge during run time. Note that the outgoing exceptional edges of a basic block do not need to have mutually exclusive labels; the semantics is that the control flow follows the most specific edge. For instance, if @@ -707,7 +687,7 @@ \subsection{Formal Definition of the Control-Flow Graph} A \emph{node} is a minimal Java operation or expression. It is minimal in the sense that it cannot be decomposed further into subparts between which control flow occurs. Examples for such - nodes include integer literals, an addition node (that performs + nodes include integer literals, an addition node (which performs the mathematical addition of two nodes) or a method call. Control flow such as \code{if} and \code{break} are not represented as nodes. The full list of nodes is given in \autoref{tab:nodes} and @@ -865,7 +845,7 @@ \subsection{Formal Definition of the Control-Flow Graph} \label{tab:nodes} \end{longtable} -\autoref{tab:nodesWithException} shows all node types which can possibly throw +\autoref{tab:nodesWithException} shows all node types that can possibly throw an exception and the exception type to be thrown. Java class name of nodes are simplified as with \autoref{tab:nodes}. All exception types in \autoref{tab:nodesWithException} are in package \code{java.lang}. @@ -876,14 +856,10 @@ \subsection{Formal Definition of the Control-Flow Graph} it's not recommended to catch it due to a critical situation which is not to able to execute JVM and handle in an application. - \begin{longtable}{lp{0.6\linewidth}l} - \midrule - \multicolumn{2}{c}{\autoref{tab:nodesWithException}: All node types could throw Exception and types to be thrown.} \\ \\ - \textbf{Node type} & \textbf{Exception type} \\ \midrule \endfirsthead - - \textbf{Node type} & \textbf{Exception type} \\ \midrule \endhead - \hline \multicolumn{2}{|c|}{{Continued on next page}} \\ \hline \endfoot - \endlastfoot +\begin{table} + \begin{tabular}{ll} + \hline + \textbf{Node type} & \textbf{Exception type} \\ \hline \code{ArrayAccess} & \code{NullPointerException}, \code{ArrayIndexOutOfBoundsException} \\ \code{FieldAccess} & \code{NullPointerException} \\ @@ -895,11 +871,12 @@ \subsection{Formal Definition of the Control-Flow Graph} \code{TypeCast} & \code{ClassCastException} \\ \code{Throw} & Type of \code{e} when \code{throw e} \\ \code{AssertionError} & \code{AssertionError} \\ - \midrule + \hline + \end{tabular} - \caption{All node types could throw Exception and types to be thrown.} + \caption{All node types that could throw Exception, and the types to be thrown.} \label{tab:nodesWithException} - \end{longtable} +\end{table} \begin{workinprogress} ThisLiteral shouldn't be considered a literal value node, because a use of @@ -951,15 +928,15 @@ \subsubsection{Program Structure} \label{sec:prog-structure} Java programs are structured using high-level programming constructs -such as different variants of loops, if-then-else constructs, +such as loops, if-then-else constructs, try-catch-finally blocks or switch statements. During the translation from the AST to the CFG some of this program structure is lost and all non-sequential control flow is represented by two low-level constructs: conditional basic blocks and control-flow edges between basic blocks. For instance, a \code{while} loop is translated into its condition followed by a conditional basic block that models the two -possible outcomes of the condition: either, the control flow follows -the `true' branch and continues with the loops body, or goes to the +possible outcomes of the condition: either the control flow follows +the `true' branch and continues with the loop's body, or control goes to the `false' successor and executes the first statement after the loop. @@ -970,35 +947,35 @@ \subsubsection{Assignment} be evaluated even if the left-hand side of the assignment causes an exception. This semantics is faithfully represented in the CFG produced by the translation. An example of a field assignment -exhibiting this behavior is shown in \autoref{lst:CFGFieldAssignment}. +exhibiting this behavior is shown in \autoref{fig:CFGFieldAssignment}. -\flow{FieldAssignment}{Control flow for a field assignment is not strictly +\flow{CFGFieldAssignment}{.33}{1}{Control flow for a field assignment is not strictly left-to-right (cf.\ \jlsref{15.26.1}), which is properly handled by the translation.} \subsubsection{Postfix/Prefix Increment/Decrement} \label{sec:postpre-incdec} -Both of postfix and prefix increment or decrement have a side effect to -update the variable or field. To represent this side effect, Dataflow -Framework create an artificial assignment node like \code{n = n + 1} +Postfix and prefix increment and decrement have a side effect to +update the variable or field. To represent this side effect, the Dataflow +Framework creates an artificial assignment node like \code{n = n + 1} for \code{++n} or \code{n++}. This artificial assignment node is stored in \code{unaryAssignNodeLookup} of \code{ControlFlowGraph}. The assignment node is also stored in \code{treeLookup} for prefix increment or decrement so that the result of it is after the assignment. However, the node before the assignment is stored in \code{treeLookup} for postfix increment or decrement because the result of it should be before the assignment. For further information -about node-tree mapping, see \autoref{sec:conversions} also. +about node-tree mapping, see \autoref{sec:conversions}. \subsubsection{Conditional stores} \label{sec:cond-stores} The Dataflow Framework extracts information from control-flow splits -that occur in if, for, while, and switch statements. In order to have +that occur in \code{if}, \code{for}, \code{while}, and \code{switch} statements. In order to have the information available at the split, we eagerly produce two stores contained in a \code{ConditionalTransferResult} after certain boolean-valued expressions. The stores are called the \emph{then} and \emph{else} stores. So, for example, after the expression \code{x == - null}, two different stores will be created. The Nullness Checkers + null}, two different stores will be created. The Nullness Checker would produce a then store that maps \code{x} to @Nullable and an else store that maps \code{x} to @NonNull. @@ -1023,7 +1000,7 @@ \subsubsection{Branches} successor. Consider the control flow graph generated for the simple if statement -in \autoref{lst:CFGIfStatement}. The conditional expression \code{b1} +in \autoref{fig:CFGIfStatement}. The conditional expression \code{b1} immediately precedes the \code{ConditionalBlock}, represented by the octagonal node. The \code{ConditionalBlock} is followed by both a then and an else successor block, after which control flow merges back @@ -1036,7 +1013,7 @@ \subsubsection{Branches} More precise rules are used to preserve dataflow information for short-circuiting expressions, as described in \autoref{sec:cond-exp}. -\flow{IfStatement}{Example of an if statement translated into a +\flow{CFGIfStatement}{.33}{1.25}{Example of an if statement translated into a \code{ConditionalBlock}.} \subsubsection{Conditional Expressions} @@ -1051,7 +1028,7 @@ \subsubsection{Conditional Expressions} additional dataflow information. An example program using conditional or is shown in -\autoref{lst:CFGConditionalOr}. Note that the CFG correctly +\autoref{fig:CFGConditionalOr}. Note that the CFG correctly represents short-circuiting. The expression \code{b2 || b3} is only executed if \code{b1} is false and \code{b3} is only evaluated if \code{b1} and \code{b2} are false. @@ -1075,7 +1052,7 @@ \subsubsection{Conditional Expressions} along those edges need to be kept in the else store of the block containing \code{b1 || (b2 || b3)}. -\flow{ConditionalOr}{Example of a conditional or expression +\flow{CFGConditionalOr}{.33}{1.33}{Example of a conditional or expression (\code{||}) with short-circuiting and more precise flow rules.} @@ -1086,12 +1063,7 @@ \subsubsection{Implicit \code{this} access} left out). To relieve the user of the Dataflow Framework from manually determining the two cases, we consistently use \code{FieldAccessNode} for field accesses, where the receiver might be -an \code{ImplicitThisNode}. For instance, this is shown in the -earlier example \autoref{lst:CFGFieldAssignment}. - -\begin{workinprogress} -I don't see a \code{implicitThisNode} in \autoref{lst:CFGFieldAssignment}. -\end{workinprogress} +an \code{ImplicitThisNode}. \subsubsection{Assert statements} @@ -1105,20 +1077,18 @@ \subsubsection{Assert statements} annotations. However, when assertions are disabled, it would be unsound to assume that they had any effect on dataflow information. -Our solution is to offer the user of the Dataflow Framework, and -ultimately the user of the Checker Framework, the option of stating that +The user of the Dataflow Framework may specify that assertions are enabled or disabled. When assertions are assumed to be -disabled, no CFG Nodes are built for the assert statement at all. +disabled, no CFG Nodes are built for the assert statement. When assertions are assumed to be enabled, CFG Nodes are built to represent the condition of the assert statement and, in the else successor of a ConditionalBlock, CFG Nodes are built to represent the detail expression of the assert, if any. -If assertions are not assumed to be enabled or disabled, then we -generate a CFG that is conservative and represents the fact that the +If assertions are not assumed to be enabled or disabled, then +the CFG is conservative and represents the fact that the assert statement may execute or may not. This takes the form of a -ConditionalBlock that branches on a fake variable. For example, the -code in \autoref{lst:CFGAssert} produces the control flow graph in +ConditionalBlock that branches on a fake variable. For example, see \autoref{fig:CFGAssert}. The fake variable named \code{assertionsEnabled#num0} controls the first ConditionalBlock. The then successor of the ConditionalBlock is the same subgraph of CFG @@ -1127,19 +1097,10 @@ \subsubsection{Assert statements} subgraph of CFG Nodes that would be created if assertions were assumed to be disabled. -\flow{Assert}{Example of an assert statement translated with +\flow{CFGAssert}{.15}{2.9}{Example of an assert statement translated with assertions neither assumed to be enabled nor assumed to be disabled.} -\begin{workinprogress} -How should the user choose between the three possibilities? -\end{workinprogress} - -\begin{workinprogress} -Why are there two basic blocks for the \code{AssertionError} and then -the \code{throw}? Why are they not in the same basic block, as there -is no alternate control flow? -\end{workinprogress} \subsubsection{Varargs method invocation} \label{sec:varargs} @@ -1148,28 +1109,28 @@ \subsubsection{Varargs method invocation} \code{m(1, 2, 3)} will be compiled as \code{m(new int[]\{1, 2, 3\})} when the signature of \code{m} is \code{m(int... args)}. Dataflow Framework creates an \code{ArrayCreationNode} with initializer for varargs -in the same way as Java compiler. Note that it doesn't create an \code{ArrayCreationNode} -when the varargs is an array which is same depth with the type of -the formal parameter or \code{null} is given as actual varargs. +in the same way as the Java compiler does. +Note that it doesn't create an \code{ArrayCreationNode} +when the varargs is an array with the same depth as the type of +the formal parameter, or if \code{null} is given as the actual varargs argument. \subsubsection{Default case and fall through for switch statement} \label{sec:default-switch} -Switch statement is handled as a chain of \code{CaseNode} and nodes in +A switch statement is handled as a chain of \code{CaseNode} and nodes in the case. \code{CaseNode} makes a branch by comparing the equality of the expression of the switch statement and the expression of the case. Note that the expression of a switch statement must be executed just only -once at first of switch statement. To refer its value, a fake variable +once at the beginning of the switch statement. To refer to its value, a fake variable is created and it is assigned to a fake variable. \code{THEN_TO_BOTH} edge goes to nodes in the case and \code{ELSE_TO_BOTH} edge goes to next \code{CaseNode}. When a next is default case, it goes to nodes in the default case. If a break statement is in nodes, it creates an edge to next node of the switch statement. If there is any possibility of fall-through, an edge to the first of nodes in the next case is created after nodes in the case. -For example, the code in \autoref{lst:CFGSwitch} produces the control flow -graph in \autoref{fig:CFGSwitch}. The fake variable named \code{switch#num0} +For example, see \autoref{fig:CFGSwitch}. The fake variable named \code{switch#num0} is created and each of case nodes creates the branches. -\flow{Switch}{Example of a switch statement with case, default and fall through.} +\flow{CFGSwitch}{.21}{1.45}{Example of a switch statement with case, default and fall through.} \subsubsection{Handling \code{finally} blocks} \label{sec:try-finally} @@ -1196,12 +1157,7 @@ \subsection{AST to CFG Translation} \begin{itemize} \item \textbf{Simple extended node.} An extended node can just be a wrapper for a node that does not throw an exception, - as defined in Definition~\ref{def:node}. - \begin{workinprogress} - Say how non-exception-throwing nodes are distinguished in - Table~\autoref{tab:nodes}. Exception-throwing ones need to - include throw, call, field access, typecast, division, ... - \end{workinprogress} + as defined in Definition~\ref{def:node}. \item \textbf{Exception extended node.} Similar to a simple node, an exception extended node contains a node, but this node might throw an exception at runtime. @@ -1417,11 +1373,7 @@ \subsubsection{Conversions and node-tree mapping} \section{Dataflow Analysis} This section describes how the dataflow analysis over the control-flow -graph is performed and what the user of the framework has to implement -to define a particular analysis. - - -\subsection{Overview} +graph is performed and how to implement a particular analysis. Roughly, a dataflow analysis in the framework works as follows. Given the abstract syntax tree of a method, the framework computes the @@ -1432,49 +1384,17 @@ \subsection{Overview} functions are specific to the particular analysis and are used to approximate the runtime behavior of different statements and expressions. -An analysis result contains two parts: - -\begin{enumerate} -\item - A node-value mapping (\code{Analysis.nodeValues}) from node to - abstract value. Only nodes that can take on an abstract value are - used as keys. For example, in the Checker Framework, the mapping is - from expression nodes to annotated types. - -\item - A set of \emph{stores}. Each store maps a flow expression to an - abstract value. Each store is associated with a specific program - point. The framework keeps explicit stores for the start of each - basic block (\code{Analysis.stores}) and computes the store for - other program points on the fly. -\end{enumerate} - -\begin{workinprogress} -There needs to be a definition of ``program point''. -\end{workinprogress} - - -After an analysis has iterated to a fix-point, the computed dataflow -information is maintained in an AnalysisResult, which can map either -nodes or trees to abstract values. - - - \subsection{Managing Intermediate Results of the Analysis} \label{sec:node-mapping} \label{sec:store-management} -\begin{workinprogress} -This feels repetitive with the previous one. Combine them in whole - or in part? -\end{workinprogress} Conceptually, the dataflow analysis computes an abstract value for every node and flow expression\footnote{Certain dataflow analysis might choose not to produce an abstract value for every node. For instance, a constant propagation analysis would only be concerned - with nodes of a numerical type, and ignore other nodes.}. The + with nodes of a numerical type, and could ignore other nodes.}. The transfer function (\autoref{sec:transfer-fnc}) produces these abstract values, taking as input the abstract values computed earlier for sub-expressions. For instance, in a constant propagation analysis, @@ -1483,18 +1403,25 @@ \subsection{Managing Intermediate Results of the Analysis} \code{AdditionNode} is a constant if and only if both operands are constant. -There are two parts to the analysis result. +An analysis result contains two parts: \begin{enumerate} \item -The \emph{node-value mapping} maps \code{Node}s to their abstract -values. The framework consciously does not store the abstract value +The \emph{node-value mapping} (\code{Analysis.nodeValues}) maps \code{Node}s to their abstract +values. Only nodes that can take on an abstract value are +used as keys. For example, in the Checker Framework, the mapping is +from expression nodes to annotated types. + +The framework consciously does not store the abstract value directly in the node, to remove any coupling between the control-flow graph and a particular analysis. This allows the control-flow graph to be constructed only once, and then reused for different dataflow analyses. \item +A set of \emph{stores}. Each store maps a flow expression to an +abstract value. Each store is associated with a specific program point. + The stores tracked by an analysis implement the \code{Store} interface, which defines the following operations: \begin{itemize} @@ -1519,14 +1446,18 @@ \subsection{Managing Intermediate Results of the Analysis} \end{verbatim} Every store is associated with a particular point in the control-flow -graph, and all stores are managed by the framework. It maintains a -single store for every basic block that represents the information -available at the beginning of that block. When dataflow information +graph, and all stores are managed by the framework. It saves an explicit store +for the start of each basic block. +When dataflow information is requested for a later point in a block, the analysis applies the -transfer function to compute it from the initial store. +transfer function to recompute it from the initial store. \end{enumerate} +After an analysis has iterated to a fix-point, the computed dataflow +information is maintained in an AnalysisResult, which can map either +nodes or trees to abstract values. + \subsection{Answering Questions} \label{sec:answering-questions} @@ -1549,7 +1480,7 @@ \subsection{Answering Questions} \end{itemize} \end{enumerate} -The store may first need to be computed, as the framework does not +The store may first need to be (re-)computed, as the framework does not store all intermediate stores but rather only those for key positions as described in \autoref{sec:store-management}. @@ -1573,28 +1504,25 @@ \subsection{Answering Questions} \subsection{Transfer Function} \label{sec:transfer-fnc} -A transfer function has to provide the following: -\begin{itemize} -\item A method that returns the initial store for a method, given the - list of arguments (as \code{LocalVariableNode}s) and the - \code{MethodTree} (useful if the initial store depends on the method - signature, for instance). - - \begin{workinprogress} - Why are the arguments to the method call LocalVariableNodes? Or - should this be parameters? Make clear whether this is about an - invocation of a method or a method declaration. - \end{workinprogress} +A transfer function is an object that has a transfer method for +every \code{Node} type, and also a transfer method for procedure entry. -\item A transfer method for every \code{Node} type that takes a store +\begin{itemize} +\item A transfer method for a \code{Node} type takes a store and the node, and produces an updated store. This is achieved by implementing the \code{NodeVisitor} interface for the store type \code{S}. -These transfer methods also get access to the abstract value of any -sub-node of the node \code n under consideration. This is not limited -to immediate children, but the abstract value for any node contained -in \code n can be queried. + These transfer methods also get access to the abstract value of any + sub-node of the node \code n under consideration. This is not limited + to immediate children, but the abstract value for any node contained + in \code n can be queried. + +\item A transfer method for procedure entry returns the initial store, given the + list of parameters (as \code{LocalVariableNode}s that represent the formal + parameters) and the + \code{MethodTree} (useful if the initial store depends on the procedure + signature, for instance). \end{itemize} @@ -1654,10 +1582,7 @@ \subsection{Flow Rules} else store of its successor by not propagating information to the else store which might conflict with information already there, and conversely for \code{ELSE_TO_ELSE}. - -\begin{workinprogress} -What happens to the other store in these operations? -\end{workinprogress} +See \autoref{sec:cond-exp} for more details and an example. Currently, we only use flow rules for short-circuiting edges of conditional ands and ors. The CFG builder sets the flow rule of each @@ -1669,10 +1594,6 @@ \subsection{Flow Rules} analyzed, so it is a requirement that at least one predecessor block writes the then store and at least one writes the else store. -\begin{workinprogress} -How are these flow rules attached/changed? -\end{workinprogress} - \subsection{Concurrency} @@ -1691,10 +1612,6 @@ \subsection{Concurrency} behaves monotonically, however it is not yet used to preserve dataflow information about fields under concurrent semantics. -\begin{workinprogress} -Do we have an issue filed for the last point above? -\end{workinprogress} - \section{Example: Constant Propagation} @@ -1734,12 +1651,12 @@ \section{Example: Constant Propagation} function that considers the \code{EqualToNode}, and if it is of the form \code{a == e} for a local variable \code{a} and constant \code{e}, passes the correct information to one of the branches. This -is also shown in the example of \autoref{fig:ConstSimple}. +is also shown in \autoref{fig:ConstSimple}. -\text{Example.} A small example is shown in \autoref{lst:ConstSimple} -and \autoref{fig:ConstSimple}. +\text{Example.} A small example is shown in \autoref{fig:ConstSimple}. -\constantpropagation{Simple}{Simple sequential program to illustrate constant propagation.} +\flow{ConstSimple}{.45}{1}{Simple sequential program to illustrate constant + propagation. Intermediate analysis results are shown.} \section{Example: Live Variable} @@ -1764,17 +1681,13 @@ \section{Example: Live Variable} is a backward transfer function). The transfer function visits assignments to update the information of live variables for each node in the stores. -\textbf{Example.} An example is shown in \autoref{lst:LiveSimple} and -\autoref{fig:LiveSimple}. +\textbf{Example.} An example is shown in \autoref{fig:LiveSimple}. -\livevariable{Simple}{Simple sequential program to illustrate live variable.} +\flow{LiveSimple}{.33}{1}{Simple sequential program to illustrate live variable. Intermediate analysis results are shown.} \section{Default Analysis} -\begin{workinprogress} -I feel like there is a missing cross-reference or two to this section. -\end{workinprogress} \subsection{Overview} diff --git a/dataflow/manual/dataflow.tex b/dataflow/manual/dataflow.tex index eabb57691fe6..26df71afaf8c 100644 --- a/dataflow/manual/dataflow.tex +++ b/dataflow/manual/dataflow.tex @@ -50,6 +50,7 @@ numberstyle=\tiny, stepnumber=1, breaklines=true, + breakatwhitespace, frame=lines, showstringspaces=false, tabsize=2, @@ -87,28 +88,26 @@ \newtheorem{definition}{Definition}[section] % control-flow graph images and listings -\newcommand{\flowlst}[2]{\lstinputlisting[caption=#2,label=lst:#1,float]{examples/#1.java}} -\newcommand{\flowimg}[2]{\begin{figure} +% Arguments 2 and 3 control how much horizontal space the code takes on the page. +\newcommand{\flow}[4]{ +\begin{figure} +\centering\vspace{0pt} +\begin{minipage}[t]{#2\textwidth} +\centering\vspace{0pt} +\begin{minipage}[t]{#3\textwidth} +\lstinputlisting{examples/#1.java} +\end{minipage}% +\end{minipage}% +\begin{minipage}[t]{.6\textwidth} +\centering\vspace{0pt} \includegraphics[scale=0.6]{examples/graphs/#1.pdf} \centering -\caption{#2} +\end{minipage} +\caption{#4} \label{fig:#1} \end{figure}} -\newcommand{\flow}[2]{ - \flowlst{CFG#1}{{#2 Its CFG is depicted in \autoref{fig:CFG#1}.}} - \flowimg{CFG#1}{The control-flow graph for \autoref{lst:CFG#1}.} -} -\newcommand{\constantpropagation}[2]{ - \flowlst{Const#1}{{#2 Its CFG and intermediate analysis results are depicted in \autoref{fig:Const#1}.}} - \flowimg{Const#1}{The control-flow graph (including intermediate analysis results) for \autoref{lst:Const#1}.} -} - -\newcommand{\livevariable}[2]{ -\flowlst{Live#1}{{#2 Its CFG and intermediate analysis results are depicted in \autoref{fig:Live#1}.}} -\flowimg{Live#1}{The control-flow graph (including intermediate analysis results) for \autoref{lst:Live#1}.} -} -% references to the java specification +% references to the Java Language Specification. \newcommand{\jlsref}[1]{JLS~\textsection{}#1} diff --git a/dataflow/manual/examples/CFGAssert.java b/dataflow/manual/examples/CFGAssert.java index 5f5050357a10..1bc3eb591357 100644 --- a/dataflow/manual/examples/CFGAssert.java +++ b/dataflow/manual/examples/CFGAssert.java @@ -1,5 +1,6 @@ class Test { - void testAssert(Object a) { - assert a != null : "Argument is null"; - } + void testAssert(Object a) { + assert a != null + : "Argument is null"; + } } diff --git a/dataflow/manual/examples/CFGConditionalOr.java b/dataflow/manual/examples/CFGConditionalOr.java index 896cf44bce72..03e263a416fa 100644 --- a/dataflow/manual/examples/CFGConditionalOr.java +++ b/dataflow/manual/examples/CFGConditionalOr.java @@ -1,8 +1,8 @@ class Test { - void test(boolean b1, boolean b2, boolean b3) { - int x = 0; - if (b1 || (b2 || b3)) { - x = 1; - } + void test(boolean b1, boolean b2, boolean b3) { + int x = 0; + if (b1 || (b2 || b3)) { + x = 1; } + } } diff --git a/dataflow/manual/examples/CFGConditionalOr2.java b/dataflow/manual/examples/CFGConditionalOr2.java index 93bcf908b407..dc9b2855074b 100644 --- a/dataflow/manual/examples/CFGConditionalOr2.java +++ b/dataflow/manual/examples/CFGConditionalOr2.java @@ -1,6 +1,6 @@ class Test { - void test(boolean b1, boolean b2, boolean b3) { - int x = 0; - boolean b = b1 || (b2 || b3); - } + void test(boolean b1, boolean b2, boolean b3) { + int x = 0; + boolean b = b1 || (b2 || b3); + } } diff --git a/dataflow/manual/examples/CFGFieldAssignment.java b/dataflow/manual/examples/CFGFieldAssignment.java index 0bbecdddee1f..898e1882072d 100644 --- a/dataflow/manual/examples/CFGFieldAssignment.java +++ b/dataflow/manual/examples/CFGFieldAssignment.java @@ -1,7 +1,7 @@ class Test { - int f; + int f; - void test(Test x) { - x.f = 1; - } + void test(Test x) { + x.f = 1; + } } diff --git a/dataflow/manual/examples/CFGIfStatement.java b/dataflow/manual/examples/CFGIfStatement.java index db83f01ef58d..f4ffd99be409 100644 --- a/dataflow/manual/examples/CFGIfStatement.java +++ b/dataflow/manual/examples/CFGIfStatement.java @@ -1,10 +1,10 @@ class Test { - void testIf(boolean b1) { - int x = 0; - if (b1) { - x = 1; - } else { - x = 2; - } + void testIf(boolean b1) { + int x = 0; + if (b1) { + x = 1; + } else { + x = 2; } + } } diff --git a/dataflow/manual/examples/CFGSimple.java b/dataflow/manual/examples/CFGSimple.java index 387ba72ce615..2aedb61de84a 100644 --- a/dataflow/manual/examples/CFGSimple.java +++ b/dataflow/manual/examples/CFGSimple.java @@ -1,8 +1,8 @@ class Test { - void test(boolean b) { - int x = 2; - if (b) { - x = 1; - } + void test(boolean b) { + int x = 2; + if (b) { + x = 1; } + } } diff --git a/dataflow/manual/examples/CFGSwitch.java b/dataflow/manual/examples/CFGSwitch.java index 91f2d95141ca..ae8eea293ce9 100644 --- a/dataflow/manual/examples/CFGSwitch.java +++ b/dataflow/manual/examples/CFGSwitch.java @@ -1,14 +1,14 @@ class Test { - void test(int x) { - switch (x) { - case 1: - int a = x; - break; - case 2: - int b = x; - default: - int c = x; - break; - } + void test(int x) { + switch (x) { + case 1: + int a = x; + break; + case 2: + int b = x; + default: + int c = x; + break; } + } } diff --git a/dataflow/manual/examples/ConstSimple.java b/dataflow/manual/examples/ConstSimple.java index 144fe4cad35d..150dfd202815 100644 --- a/dataflow/manual/examples/ConstSimple.java +++ b/dataflow/manual/examples/ConstSimple.java @@ -1,16 +1,16 @@ class Test { - void test(boolean b, int a) { - int x = 1; - int y = 0; - if (b) { - x = 2; - } else { - x = 2; - y = a; - } - x = 3; - if (a == 2) { - x = 4; - } + void test(boolean b, int a) { + int x = 1; + int y = 0; + if (b) { + x = 2; + } else { + x = 2; + y = a; } + x = 3; + if (a == 2) { + x = 4; + } + } } diff --git a/dataflow/manual/examples/LiveSimple.java b/dataflow/manual/examples/LiveSimple.java index 891241c18fe4..641ac2eb4fca 100644 --- a/dataflow/manual/examples/LiveSimple.java +++ b/dataflow/manual/examples/LiveSimple.java @@ -1,10 +1,10 @@ public class Test { - public void test() { - int a = 1, b = 2, c = 3; - if (a > 0) { - int d = a + c; - } else { - int e = a + b; - } + public void test() { + int a = 1, b = 2, c = 3; + if (a > 0) { + int d = a + c; + } else { + int e = a + b; } + } } From c5f648cf183a585f03235f13586a3810d8e68786 Mon Sep 17 00:00:00 2001 From: lnsun <57457122+lnsun@users.noreply.github.com> Date: Wed, 29 Jul 2020 19:50:24 -0400 Subject: [PATCH 107/138] ComponentFinderUtil for Transfer --- .../type/GenericAnnotatedTypeFactory.java | 36 ++++++++----------- 1 file changed, 14 insertions(+), 22 deletions(-) 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 14b971035ce8..b0af31db9e31 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java @@ -428,28 +428,20 @@ protected FlowAnalysis createFlowAnalysis(List> fie public TransferFunction createFlowTransferFunction( CFAbstractAnalysis analysis) { - // Try to reflectively load the visitor. - Class checkerClass = checker.getClass(); - - while (checkerClass != BaseTypeChecker.class) { - TransferFunction result = - BaseTypeChecker.invokeConstructorFor( - BaseTypeChecker.getRelatedClassName(checkerClass, "Transfer"), - new Class[] {analysis.getClass()}, - new Object[] {analysis}); - if (result != null) { - return result; - } - checkerClass = checkerClass.getSuperclass(); - } - - // If a transfer function couldn't be loaded reflectively, return the - // default. - @SuppressWarnings("unchecked") - TransferFunction ret = - (TransferFunction) - new CFTransfer((CFAbstractAnalysis) analysis); - return ret; + return ComponentFinderUtil.find( + checker, + "Transfer", + checker1 -> { + @SuppressWarnings("unchecked") + TransferFunction ret = + (TransferFunction) + new CFTransfer( + (CFAbstractAnalysis) + analysis); + return ret; + }, + new Class[] {analysis.getClass()}, + new Object[] {analysis}); } /** From 017e95d9738be25bd1344f0bee087be617634f03 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Wed, 29 Jul 2020 16:54:36 -0700 Subject: [PATCH 108/138] Make CFG construction more deteriministic (#3517) --- .../dataflow/cfg/block/ExceptionBlockImpl.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ExceptionBlockImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ExceptionBlockImpl.java index 6b871d380b4f..15d288030e73 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ExceptionBlockImpl.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/ExceptionBlockImpl.java @@ -1,8 +1,8 @@ package org.checkerframework.dataflow.cfg.block; import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import javax.lang.model.type.TypeMirror; @@ -22,7 +22,7 @@ public class ExceptionBlockImpl extends SingleSuccessorBlockImpl implements Exce /** Create an empty exceptional block. */ public ExceptionBlockImpl() { super(BlockType.EXCEPTION_BLOCK); - exceptionalSuccessors = new HashMap<>(); + exceptionalSuccessors = new LinkedHashMap<>(); } /** Set the node. */ @@ -43,7 +43,7 @@ public Node getNode() { public void addExceptionalSuccessor(BlockImpl b, TypeMirror cause) { Set blocks = exceptionalSuccessors.get(cause); if (blocks == null) { - blocks = new HashSet<>(); + blocks = new LinkedHashSet<>(); exceptionalSuccessors.put(cause, blocks); } blocks.add(b); From a05fba3ef624ac934d333f42d0dc47224a4423c5 Mon Sep 17 00:00:00 2001 From: Weitian Xing Date: Wed, 29 Jul 2020 21:25:54 -0400 Subject: [PATCH 109/138] Add a new parameter "--string" to CFGVisualizeLauncher (#3509) To print the string representation of the control flow graph. Use `--` consistently. --- changelog.txt | 5 ++ .../dataflow/cfg/CFGVisualizeLauncher.java | 83 +++++++++++++++---- docs/manual/creating-a-checker.tex | 24 ++++-- 3 files changed, 89 insertions(+), 23 deletions(-) diff --git a/changelog.txt b/changelog.txt index 80f3985116d5..644bd572159f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -8,6 +8,11 @@ Added an overloaded version of NullnessUtil.castNonNull that takes an error mess Added a new option `-Aversion` to print the version of the Checker Framework. +New CFGVisualizeLauncher command-line arguments: + * `--outputdir`: directory in which to write output files + * `--string`: print the control flow graph in the terminal +All CFGVisualizeLauncher command-line arguments now start with `--` instead of `-`. + Implementation details: commonAssignmentCheck() now takes an additional argument. Type system diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGVisualizeLauncher.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGVisualizeLauncher.java index b7f95e0401e8..d9769aeeb589 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGVisualizeLauncher.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGVisualizeLauncher.java @@ -38,12 +38,11 @@ public class CFGVisualizeLauncher { */ public static void main(String[] args) { CFGVisualizeLauncher cfgVisualizeLauncher = new CFGVisualizeLauncher(); - if (args.length < 2) { + if (args.length == 0) { cfgVisualizeLauncher.printUsage(); System.exit(1); } String input = args[0]; - String output = args[1]; File file = new File(input); if (!file.canRead()) { cfgVisualizeLauncher.printError("Cannot read input file: " + file.getAbsolutePath()); @@ -53,34 +52,48 @@ public static void main(String[] args) { String method = "test"; String clas = "Test"; + String output = "."; boolean pdf = false; boolean error = false; boolean verbose = false; + boolean string = false; - for (int i = 2; i < args.length; i++) { + for (int i = 1; i < args.length; i++) { switch (args[i]) { - case "-pdf": + case "--outputdir": + if (i >= args.length - 1) { + cfgVisualizeLauncher.printError( + "Did not find after --outputdir."); + continue; + } + i++; + output = args[i]; + break; + case "--pdf": pdf = true; break; - case "-method": + case "--method": if (i >= args.length - 1) { - cfgVisualizeLauncher.printError("Did not find after -method."); + cfgVisualizeLauncher.printError("Did not find after --method."); continue; } i++; method = args[i]; break; - case "-class": + case "--class": if (i >= args.length - 1) { - cfgVisualizeLauncher.printError("Did not find after -class."); + cfgVisualizeLauncher.printError("Did not find after --class."); continue; } i++; clas = args[i]; break; - case "-verbose": + case "--verbose": verbose = true; break; + case "--string": + string = true; + break; default: cfgVisualizeLauncher.printError("Unknown command line argument: " + args[i]); error = true; @@ -92,12 +105,19 @@ public static void main(String[] args) { System.exit(1); } - cfgVisualizeLauncher.generateDOTofCFGWithoutAnalysis( - input, output, method, clas, pdf, verbose); + if (!string) { + cfgVisualizeLauncher.generateDOTofCFGWithoutAnalysis( + input, output, method, clas, pdf, verbose); + } else { + String stringGraph = + cfgVisualizeLauncher.generateStringOfCFGWithoutAnalysis( + input, method, clas, verbose); + System.out.println(stringGraph); + } } /** - * Generate the DOT representation of the CFG for a method without analysis. + * Generate the DOT representation of the CFG for a method, only. Does no dataflow analysis. * * @param inputFile java source input file * @param outputDir output directory @@ -116,6 +136,29 @@ protected void generateDOTofCFGWithoutAnalysis( generateDOTofCFG(inputFile, outputDir, method, clas, pdf, verbose, null); } + /** + * Generate the String representation of the CFG for a method, only. Does no dataflow analysis. + * + * @param inputFile java source input file + * @param method name of the method to generate the CFG for + * @param clas name of the class which includes the method to generate the CFG for + * @param verbose show verbose information in CFG + * @return the String representation of the CFG + */ + protected String generateStringOfCFGWithoutAnalysis( + String inputFile, String method, String clas, boolean verbose) { + @Nullable Map res = generateStringOfCFG(inputFile, method, clas, verbose, null); + if (res != null) { + String stringGraph = (String) res.get("stringGraph"); + if (stringGraph == null) { + return "Unexpected output from generating string control flow graph, shouldn't be null."; + } + return stringGraph; + } else { + return "Unexpected output from generating string control flow graph, shouldn't be null."; + } + } + /** * Generate the DOT representation of the CFG for a method. * @@ -270,15 +313,19 @@ protected void producePDF(String file) { /** Print usage information. */ protected void printUsage() { System.out.println( - "Generate the control flow graph of a Java method, represented as a DOT graph."); + "Generate the control flow graph of a Java method, represented as a DOT or String graph."); + System.out.println( + "Parameters: [--outputdir ] [--method ] [--class ] [--pdf] [--verbose] [--string]"); + System.out.println( + " --outputdir: The output directory for the generated files (defaults to '.')."); System.out.println( - "Parameters: [-method ] [-class ] [-pdf] [-verbose]"); - System.out.println(" -pdf: Also generate the PDF by invoking 'dot'."); + " --method: The method to generate the CFG for (defaults to 'test')."); System.out.println( - " -method: The method to generate the CFG for (defaults to 'test')."); + " --class: The class in which to find the method (defaults to 'Test')."); + System.out.println(" --pdf: Also generate the PDF by invoking 'dot'."); + System.out.println(" --verbose: Show the verbose output (defaults to 'false')."); System.out.println( - " -class: The class in which to find the method (defaults to 'Test')."); - System.out.println(" -verbose: Show the verbose output (defaults to 'false')."); + " --string: Print the string representation of the control flow graph (defaults to 'false')."); } /** diff --git a/docs/manual/creating-a-checker.tex b/docs/manual/creating-a-checker.tex index 1604077f90ee..7c66d20a5c27 100644 --- a/docs/manual/creating-a-checker.tex +++ b/docs/manual/creating-a-checker.tex @@ -1921,6 +1921,7 @@ \noindent You can also use \refclass{dataflow/cfg}{CFGVisualizeLauncher} to generate a DOT or String representation of the control flow graph of a given method in a given class. +The CFG is generated and output, but no dataflow analysis is performed. \begin{itemize} @@ -1931,7 +1932,7 @@ java -Xbootclasspath/p:$CHECKERFRAMEWORK/checker/dist/javac.jar \ -cp $CHECKERFRAMEWORK/checker/dist/checker.jar \ org.checkerframework.dataflow.cfg.CFGVisualizeLauncher \ - MyClass.java output/ -class MyClass -method test -pdf + MyClass.java --class MyClass --method test --pdf \end{Verbatim} \end{smaller} @@ -1942,7 +1943,7 @@ \begin{Verbatim} java -cp $CHECKERFRAMEWORK/checker/dist/checker.jar \ org.checkerframework.dataflow.cfg.CFGVisualizeLauncher \ - MyClass.java output/ -class MyClass -method test -pdf + MyClass.java --class MyClass --method test --pdf \end{Verbatim} \end{smaller} @@ -1950,9 +1951,22 @@ \noindent The above command will generate the corresponding dot and pdf files for the -method \code{test} in the class \code{MyClass} in the directory \. For more -information, run \refclass{dataflow/cfg}{CFGVisualizeLauncher} with no arguments to -see the usage. +method \code{test} in the class \code{MyClass} in the project directory. +To generate a string representation of the graph to standard output, +remove \<--pdf> but add \<--string>. For example (with JDK 8): + +\begin{smaller} +\begin{Verbatim} +java -Xbootclasspath/p:$CHECKERFRAMEWORK/checker/dist/javac.jar \ + -cp $CHECKERFRAMEWORK/checker/dist/checker.jar \ + org.checkerframework.dataflow.cfg.CFGVisualizeLauncher \ + MyClass.java --class MyClass --method test --string +\end{Verbatim} +\end{smaller} + +For more information, run \refclass{dataflow/cfg}{CFGVisualizeLauncher} with +no arguments to see the usage. + \subsectionAndLabel{Miscellaneous debugging options}{creating-debugging-options-misc} From 63d5c2f68c4464b56d8e749de065d381e3904a81 Mon Sep 17 00:00:00 2001 From: lnsun <57457122+lnsun@users.noreply.github.com> Date: Thu, 30 Jul 2020 10:49:42 -0400 Subject: [PATCH 110/138] doc --- .../framework/type/GenericAnnotatedTypeFactory.java | 8 ++++++++ 1 file changed, 8 insertions(+) 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 b0af31db9e31..fd2713451666 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java @@ -389,6 +389,10 @@ protected DefaultQualifierForUseTypeAnnotator createDefaultForUseTypeAnnotator() * *

    Subclasses have to override this method to create the appropriate analysis if they do not * follow the checker naming convention. + * + * @param fieldValues initial abstract types for fields. + * @return the appropriate flow analysis class that is used for the + * org.checkerframework.dataflow analysis. */ @SuppressWarnings({"unchecked", "rawtypes"}) protected FlowAnalysis createFlowAnalysis(List> fieldValues) { @@ -420,6 +424,10 @@ protected FlowAnalysis createFlowAnalysis(List> fie * *

    Subclasses have to override this method to create the appropriate transfer function if * they do not follow the checker naming convention. + * + * @param analysis the analysis class this store belongs to. + * @return the appropriate transfer function that is used for the org.checkerframework.dataflow + * analysis. */ // A more precise type for the parameter would be FlowAnalysis, which // is the type parameter bounded by the current parameter type CFAbstractAnalysis Date: Thu, 30 Jul 2020 12:07:44 -0700 Subject: [PATCH 111/138] Fix pre-commit hook --- checker/bin-devel/git.pre-commit | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/checker/bin-devel/git.pre-commit b/checker/bin-devel/git.pre-commit index 72d93f2313f5..b2d1f81505c8 100755 --- a/checker/bin-devel/git.pre-commit +++ b/checker/bin-devel/git.pre-commit @@ -12,8 +12,8 @@ set -e # Need to keep checked files in sync with getJavaFilesToFormat in build.gradle. # Otherwise `./gradlew reformat` might not reformat a file that this # hook complains about. -CHANGED_JAVA_FILES=$(git diff --staged --name-only --diff-filter=ACM | grep '\.java$' | grep -v '/jdk/' | grep -v 'stubparser/' | grep -v '/nullness-javac-errors/' | grep -v '/dataflow/manual/examples/' ) || true -# echo CHANGED_JAVA_FILES "'"${CHANGED_JAVA_FILES}"'" +CHANGED_JAVA_FILES=$(git diff --staged --name-only --diff-filter=ACM | grep '\.java$' | grep -v '/jdk/' | grep -v 'stubparser/' | grep -v '/nullness-javac-errors/' | grep -v 'dataflow/manual/examples/' ) || true +# echo "CHANGED_JAVA_FILES=${CHANGED_JAVA_FILES}" if [ -n "$CHANGED_JAVA_FILES" ]; then ./gradlew getCodeFormatScripts -q ## For debugging: From 844034d84740b15f2c888db38d81bea1d587a71c Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 30 Jul 2020 17:03:15 -0700 Subject: [PATCH 112/138] Add SystemUtil.sleep method (#3527) --- .../checkerframework/javacutil/SystemUtil.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/SystemUtil.java b/javacutil/src/main/java/org/checkerframework/javacutil/SystemUtil.java index 73752038cba8..8c3e41e6b6b2 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/SystemUtil.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/SystemUtil.java @@ -242,4 +242,19 @@ public static T[] concatenate(T[] array1, T... array2) { System.arraycopy(array2, 0, result, array1.length, array2.length); return result; } + + /** + * Like Thread.sleep, but does not throw any exceptions, so it is easier for clients to use. + * Causes the currently executing thread to sleep (temporarily cease execution) for the + * specified number of milliseconds. + * + * @param millis the length of time to sleep in milliseconds + */ + public static void sleep(long millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } } From e8be2098f2c39a2d7830e6b7fc64af081fe95d2f Mon Sep 17 00:00:00 2001 From: aditya3434 <56246494+aditya3434@users.noreply.github.com> Date: Fri, 31 Jul 2020 05:58:13 +0530 Subject: [PATCH 113/138] Add dump-on-error option (Fixes #1395) (#3406) --- docs/manual/creating-a-checker.tex | 2 + docs/manual/introduction.tex | 1 + .../common/basetype/BaseTypeChecker.java | 22 ++++++---- .../framework/source/SourceChecker.java | 44 ++++++++++++++++++- 4 files changed, 60 insertions(+), 9 deletions(-) diff --git a/docs/manual/creating-a-checker.tex b/docs/manual/creating-a-checker.tex index 7c66d20a5c27..1c189236d05e 100644 --- a/docs/manual/creating-a-checker.tex +++ b/docs/manual/creating-a-checker.tex @@ -1783,6 +1783,8 @@ already reported the bug, and you want to continue using the checker on a large codebase without being inundated in stack traces. +\item \code{-AdumpOnErrors}: Outputs a stack trace when reporting errors or warnings. + \end{itemize} diff --git a/docs/manual/introduction.tex b/docs/manual/introduction.tex index b80b9ca89b86..9bcb29d4905a 100644 --- a/docs/manual/introduction.tex +++ b/docs/manual/introduction.tex @@ -746,6 +746,7 @@ \<-AprintAllQualifiers>, \<-AprintVerboseGenerics>, \<-Anomsgtext> + \<-AdumpOnErrors> Amount of detail in messages; see Section~\ref{creating-debugging-options-detail}. \item diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java index 7d56101f9fe2..211106a3e1d8 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java @@ -568,10 +568,11 @@ protected void warnUnneededSuppressions() { protected void printOrStoreMessage( Diagnostic.Kind kind, String message, Tree source, CompilationUnitTree root) { assert this.currentRoot == root; + StackTraceElement[] trace = Thread.currentThread().getStackTrace(); if (messageStore == null) { - super.printOrStoreMessage(kind, message, source, root); + super.printOrStoreMessage(kind, message, source, root, trace); } else { - CheckerMessage checkerMessage = new CheckerMessage(kind, message, source, this); + CheckerMessage checkerMessage = new CheckerMessage(kind, message, source, this, trace); messageStore.add(checkerMessage); } } @@ -586,7 +587,7 @@ protected void printOrStoreMessage( private void printStoredMessages(CompilationUnitTree unit) { if (messageStore != null) { for (CheckerMessage msg : messageStore) { - super.printOrStoreMessage(msg.kind, msg.message, msg.source, unit); + super.printOrStoreMessage(msg.kind, msg.message, msg.source, unit, msg.trace); } } } @@ -599,6 +600,8 @@ private static class CheckerMessage { final String message; /** The source code that the message is about. */ final @InternedDistinct Tree source; + /** Stores the stack trace when the message is created. */ + final StackTraceElement[] trace; /** * The checker that issued this message. The compound checker that depends on this checker @@ -609,20 +612,23 @@ private static class CheckerMessage { /** * Create a new CheckerMessage. * - * @param kind the severity of the message - * @param message the text of the message - * @param source the source code that the message is about - * @param checker the checker that issued the message. + * @param kind kind of diagnostic, for example, error or warning + * @param message error message that needs to be printed + * @param source tree element causing the error + * @param checker the type-checker in use + * @param trace the stack trace when the message is created */ private CheckerMessage( Diagnostic.Kind kind, String message, @FindDistinct Tree source, - @FindDistinct BaseTypeChecker checker) { + @FindDistinct BaseTypeChecker checker, + StackTraceElement[] trace) { this.kind = kind; this.message = message; this.source = source; this.checker = checker; + this.trace = trace; } @Override 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 2ff0cde379b0..ece60f75fb48 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -52,6 +52,7 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; +import javax.tools.Diagnostic; import javax.tools.Diagnostic.Kind; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.interning.qual.InternedDistinct; @@ -317,6 +318,10 @@ // org.checkerframework.framework.util.typeinference.DefaultTypeArgumentInference "showInferenceSteps", + // Output a stack trace when reporting errors or warnings + // org.checkerframework.common.basetype.SourceChecker.printStackTrace() + "dumpOnErrors", + /// Visualizing the CFG // Implemented in the wrapper rather than this file, but worth noting here. @@ -1073,7 +1078,7 @@ private void printMessage(String msg) { * * @param kind the kind of message to print * @param message the message text - * @param source the souce code position of the diagnostic message + * @param source the source code position of the diagnostic message * @param root the compilation unit */ protected void printOrStoreMessage( @@ -1081,7 +1086,44 @@ protected void printOrStoreMessage( String message, Tree source, CompilationUnitTree root) { + StackTraceElement[] trace = Thread.currentThread().getStackTrace(); + printOrStoreMessage(kind, message, source, root, trace); + } + + /** + * Stores all messages and sorts them by location before outputting them for compound checkers. + * This method is overloaded with an additional stack trace argument. The stack trace is printed + * when the dumpOnErrors option is enabled. + * + * @param kind the kind of message to print + * @param message the message text + * @param source the source code position of the diagnostic message + * @param root the compilation unit + * @param trace the stack trace where the checker encountered an error + */ + protected void printOrStoreMessage( + javax.tools.Diagnostic.Kind kind, + String message, + Tree source, + CompilationUnitTree root, + StackTraceElement[] trace) { Trees.instance(processingEnv).printMessage(kind, message, source, root); + printStackTrace(trace); + } + + /** + * Output the given stack trace if the "dumpOnErrors" option is enabled. + * + * @param trace stack trace when the checker encountered a warning/error + */ + private void printStackTrace(StackTraceElement[] trace) { + if (hasOption("dumpOnErrors")) { + StringBuilder msg = new StringBuilder(); + for (StackTraceElement elem : trace) { + msg.append("\tat " + elem + "\n"); + } + message(Diagnostic.Kind.NOTE, msg.toString()); + } } /////////////////////////////////////////////////////////////////////////// From c46765e9c3cb16f7b07ec9df73abbffaaf8cf9ec Mon Sep 17 00:00:00 2001 From: Priti Chattopadhyay <35490584+PRITI1999@users.noreply.github.com> Date: Fri, 31 Jul 2020 06:16:17 +0530 Subject: [PATCH 114/138] Improve UserError message in createActiveOptions() (#3530) --- .../org/checkerframework/framework/source/SourceChecker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ece60f75fb48..5eecf71683e1 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -1561,7 +1561,7 @@ private Map createActiveOptions(Map options) { + " At most one separator " + OPTION_SEPARATOR + " expected, but found " - + split.length + + (split.length - 1) + "."); } } From b7f353d05983e3367235af0539e19f92c1b48f4b Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Thu, 30 Jul 2020 21:58:19 -0700 Subject: [PATCH 115/138] Interface should not reference implementation subclasses --- .../dataflow/analysis/BackwardAnalysisImpl.java | 9 ++++----- .../org/checkerframework/dataflow/cfg/CFGBuilder.java | 7 ++++--- .../org/checkerframework/dataflow/cfg/block/Block.java | 2 +- .../checkerframework/dataflow/cfg/block/BlockImpl.java | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysisImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysisImpl.java index cc8c85492a89..4c665047980a 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysisImpl.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/analysis/BackwardAnalysisImpl.java @@ -11,7 +11,6 @@ import org.checkerframework.dataflow.cfg.ControlFlowGraph; import org.checkerframework.dataflow.cfg.UnderlyingAST; import org.checkerframework.dataflow.cfg.block.Block; -import org.checkerframework.dataflow.cfg.block.BlockImpl; import org.checkerframework.dataflow.cfg.block.ConditionalBlock; import org.checkerframework.dataflow.cfg.block.ExceptionBlock; import org.checkerframework.dataflow.cfg.block.RegularBlock; @@ -116,7 +115,7 @@ public void performAnalysisBlock(Block b) { firstNode = node; } // Propagate store to predecessors - for (BlockImpl pred : rb.getPredecessors()) { + for (Block pred : rb.getPredecessors()) { assert currentInput != null : "@AssumeAssertion(nullness): invariant"; propagateStoresTo( pred, @@ -144,7 +143,7 @@ public void performAnalysisBlock(Block b) { .getRegularStore() .leastUpperBound(exceptionStore) : transferResult.getRegularStore(); - for (BlockImpl pred : eb.getPredecessors()) { + for (Block pred : eb.getPredecessors()) { addStoreAfter(pred, node, mergedStore, addToWorklistAgain); } break; @@ -155,7 +154,7 @@ public void performAnalysisBlock(Block b) { TransferInput inputAfter = getInput(cb); assert inputAfter != null : "@AssumeAssertion(nullness): invariant"; TransferInput input = inputAfter.copy(); - for (BlockImpl pred : cb.getPredecessors()) { + for (Block pred : cb.getPredecessors()) { propagateStoresTo(pred, null, input, FlowRule.EACH_TO_EACH, false); } break; @@ -174,7 +173,7 @@ public void performAnalysisBlock(Block b) { || sType == SpecialBlockType.EXCEPTIONAL_EXIT; TransferInput input = getInput(sb); assert input != null : "@AssumeAssertion(nullness): invariant"; - for (BlockImpl pred : sb.getPredecessors()) { + for (Block pred : sb.getPredecessors()) { propagateStoresTo(pred, null, input, FlowRule.EACH_TO_EACH, false); } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGBuilder.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGBuilder.java index a01ec828f000..22c6bf46214b 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGBuilder.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGBuilder.java @@ -918,9 +918,9 @@ public static ControlFlowGraph process(ControlFlowGraph cfg) { // fix predecessor lists by removing any unreachable predecessors for (Block c : worklist) { BlockImpl cur = (BlockImpl) c; - for (BlockImpl pred : new HashSet<>(cur.getPredecessors())) { + for (Block pred : new HashSet<>(cur.getPredecessors())) { if (!worklist.contains(pred)) { - cur.removePredecessor(pred); + cur.removePredecessor((BlockImpl) pred); } } } @@ -1055,7 +1055,8 @@ protected static void computeNeighborhoodOfEmptyBlockBackwards( RegularBlockImpl cur = start; empty.add(cur); - for (final BlockImpl pred : cur.getPredecessors()) { + for (final Block p : cur.getPredecessors()) { + BlockImpl pred = (BlockImpl) p; switch (pred.getType()) { case SPECIAL_BLOCK: // add pred correctly to predecessor list diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/Block.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/Block.java index 2db3de2894c5..81d3a54bdbc4 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/Block.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/Block.java @@ -40,5 +40,5 @@ public static enum BlockType { * * @return the predecessors of this basic block */ - Set getPredecessors(); + Set getPredecessors(); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/BlockImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/BlockImpl.java index 1d1961f8487a..12e6cbabad3a 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/BlockImpl.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/BlockImpl.java @@ -44,7 +44,7 @@ public BlockType getType() { } @Override - public Set getPredecessors() { + public Set getPredecessors() { return Collections.unmodifiableSet(predecessors); } From d9e984f491bf9f7ab4788c6ae519d88c4e2cfc40 Mon Sep 17 00:00:00 2001 From: Jason Waataja Date: Fri, 31 Jul 2020 08:31:21 -0700 Subject: [PATCH 116/138] Improve defaults for local variables with a qualifier parameter (#3503) Changes the defaulting for local variables with a qualifier parameter. Instead of defaulting to top, local variables with an initialializer default to the type of the right hand side of the assignment. --- .../tests/tainting/HasQualParamDefaults.java | 70 ++++++++++++ .../tests/tainting/InitializerDataflow.java | 23 ++++ docs/manual/generics.tex | 37 ++++++- .../type/GenericAnnotatedTypeFactory.java | 101 ++++++++++++++++++ 4 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 checker/tests/tainting/InitializerDataflow.java diff --git a/checker/tests/tainting/HasQualParamDefaults.java b/checker/tests/tainting/HasQualParamDefaults.java index e568139cac5f..fa2cb03983f0 100644 --- a/checker/tests/tainting/HasQualParamDefaults.java +++ b/checker/tests/tainting/HasQualParamDefaults.java @@ -44,6 +44,31 @@ public Buffer append(@PolyTainted String s) { someString = s; return s; } + + void initializeLocalTainted(@Tainted Buffer b) { + Buffer local = b; + @Tainted Buffer copy1 = local; + // :: error: (assignment.type.incompatible) + @Untainted Buffer copy2 = local; + } + + void initializeLocalUntainted(@Untainted Buffer b) { + Buffer local = b; + @Untainted Buffer copy1 = local; + // :: error: (assignment.type.incompatible) + @Tainted Buffer copy2 = local; + } + + void initializeLocalPolyTainted(@PolyTainted Buffer b) { + Buffer local = b; + @PolyTainted Buffer copy = local; + } + + void noInitializer(@Untainted Buffer b) { + Buffer local; + // :: error: (assignment.type.incompatible) + local = b; + } } class Use { @@ -83,4 +108,49 @@ void creation() { @PolyTainted Buffer b3 = new @PolyTainted Buffer(); } } + + // For classes with @HasQualifierParameter, different defaulting rules are applied on that type + // inside the class body and outside the class body, so local variables need to be tested + // outside the class as well. + class LocalVars { + void initializeLocalTainted(@Tainted Buffer b) { + Buffer local = b; + @Tainted Buffer copy1 = local; + // :: error: (assignment.type.incompatible) + @Untainted Buffer copy2 = local; + } + + void initializeLocalUntainted(@Untainted Buffer b) { + Buffer local = b; + @Untainted Buffer copy1 = local; + // :: error: (assignment.type.incompatible) + @Tainted Buffer copy2 = local; + } + + void initializeLocalPolyTainted(@PolyTainted Buffer b) { + Buffer local = b; + @PolyTainted Buffer copy = local; + } + + void noInitializer(@Untainted Buffer b) { + Buffer local; + // :: error: (assignment.type.incompatible) + local = b; + } + + // These next two cases test circular dependencies. Calculating the type of a local variable + // looks at the type of initializer, but if the type of the initializer depends on the type + // of the variable, then infinite recursion could occur. + + void testTypeVariableInference() { + GenericWithQualParam set = new GenericWithQualParam<>(); + } + + void testVariableInOwnInitializer() { + Buffer b = (b = null); + } + } + + @HasQualifierParameter(Tainted.class) + static class GenericWithQualParam {} } diff --git a/checker/tests/tainting/InitializerDataflow.java b/checker/tests/tainting/InitializerDataflow.java new file mode 100644 index 000000000000..73d1f484e983 --- /dev/null +++ b/checker/tests/tainting/InitializerDataflow.java @@ -0,0 +1,23 @@ +import org.checkerframework.checker.tainting.qual.PolyTainted; +import org.checkerframework.checker.tainting.qual.Tainted; +import org.checkerframework.checker.tainting.qual.Untainted; +import org.checkerframework.framework.qual.HasQualifierParameter; + +public class InitializerDataflow { + @HasQualifierParameter(Tainted.class) + static class Buffer {} + + @PolyTainted Buffer id(@PolyTainted String s) { + return null; + } + + void methodBuffer(@Untainted String s) { + Buffer b1 = id(s); + + String local = s; + Buffer b2 = id(local); + + @Untainted String local2 = s; + Buffer b3 = id(local2); + } +} diff --git a/docs/manual/generics.tex b/docs/manual/generics.tex index d9b9138bc899..0523fc999625 100644 --- a/docs/manual/generics.tex +++ b/docs/manual/generics.tex @@ -951,6 +951,41 @@ qualifier on the upper bound). That is, the qualifier on \ is that same as that on \. +\subsectionAndLabel{Local variable defaults for types with qualifier parameters}{local-vars-qual-param-defaults} + +According to the CLIMB-to-top rule, local variables default to the top type (see +Section~\ref{climb-to-top}). Type refinement determines if a variable can be +treated as a suitable subtype, and annotations on local variables are rarely +needed as a result. However, since qualifier parameters add invariant subtyping, +type refinement is no longer valid. For example, suppose in the following code +that \ is annotated with \<@HasQualifierParameter(Tainted.class)>. + +\begin{Verbatim} + void method(@Untainted StringBuffer buffer) { + StringBuffer local = buffer; + executeSql(local.toString()); + } + + void executeSql(@Untainted String code) { + // ... + } +\end{Verbatim} + +Normally, the framework would determine that \ has type \<@Untainted +StringBuffer> and the call to \ would be valid. However, since by +default \ has type \<@Tainted StringBuffer>, and +\<@Untainted StringBuffer> is not a subtype, no type refinement would be +performed, leading to an error. Fixing this would require manually annotating +\ as an \<@Untainted StringBuffer>, increasing the annotation burden on +programmers. + +For this reason, local variables with types that have a qualifier parameter use +different defaulting rules. When a local variable has an initializer, the type +of that initializer is used as the default type of that variable if no other +annotations are written. For example, in the above code, the type of \ +would be \<@Untainted StringBuffer>. This eliminates the need for type +refinement. + \subsectionAndLabel{Qualifier parameters by default}{default-has-qualifier-parameter} If many classes in a project should have \<@HasQualifierParameter>, it's @@ -1003,7 +1038,7 @@ Types with qualifier parameters are only allowed as type arguments to type parameters whose upper bound have a qualifier parameter. If they were allowed for as type arguments for any type parameter, then -unsound casts would be permitted.For example: +unsound casts would be permitted. For example: \begin{Verbatim} @HasQualifierParameter(Tainted.class) 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 9c518e1ce508..7236a2dda09a 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java @@ -162,6 +162,33 @@ public abstract class GenericAnnotatedTypeFactory< */ private boolean shouldDefaultTypeVarLocals; + /** + * Elements representing variables for which the type of the initializer is being determined in + * order to apply qualifier parameter defaults. + * + *

    Local variables with a qualifier parameter get their declared type from the type of their + * initializer. Sometimes the initializer's type depends on the type of the variable, such as + * during type variable inference or when a variable is used in its own initializer as in + * "Object o = (o = null)". This creates a circular dependency resulting in infinite recursion. + * To prevent this, variables in this set should not be typed based on their initializer, but by + * using normal defaults. + * + *

    This set should only be modified in + * GenericAnnotatedTypeFactory#applyLocalVariableQualifierParameterDefaults which clears + * variables after computing their initializer types. + * + * @see GenericAnnotatedTypeFactory#applyLocalVariableQualifierParameterDefaults + */ + private Set variablesUnderInitialization; + + /** + * Caches types of initializers for local variables with a qualifier parameter, so that they + * aren't computed each time the type of a variable is looked up. + * + * @see GenericAnnotatedTypeFactory#applyLocalVariableQualifierParameterDefaults + */ + private Map initializerCache; + /** An empty store. */ // Set in postInit only protected Store emptyStore; @@ -204,6 +231,7 @@ protected GenericAnnotatedTypeFactory(BaseTypeChecker checker, boolean useFlow) this.shouldDefaultTypeVarLocals = useFlow; this.useFlow = useFlow; + this.variablesUnderInitialization = new HashSet<>(); this.scannedClasses = new HashMap<>(); this.flowResult = null; this.regularExitStores = null; @@ -219,8 +247,10 @@ protected GenericAnnotatedTypeFactory(BaseTypeChecker checker, boolean useFlow) if (shouldCache) { int cacheSize = getCacheSize(); flowResultAnalysisCaches = CollectionUtils.createLRUCache(cacheSize); + initializerCache = CollectionUtils.createLRUCache(cacheSize); } else { flowResultAnalysisCaches = null; + initializerCache = null; } // Every subclass must call postInit, but it must be called after @@ -283,6 +313,7 @@ public void setRoot(@Nullable CompilationUnitTree root) { if (shouldCache) { this.flowResultAnalysisCaches.clear(); + this.initializerCache.clear(); this.defaultQualifierForUseTypeAnnotator.clearCache(); } } @@ -1592,6 +1623,11 @@ protected void applyInferredAnnotations(AnnotatedTypeMirror type, Value as) { /** * Applies defaults for types in a class with an qualifier parameter. * + *

    Within a class with {@code @HasQualifierParameter}, types with that class default to the + * polymorphic qualifier rather than the typical default. Local variables with a type that has a + * qualifier parameter are initialized to the type of their initializer, rather than the default + * for local variables. + * * @param tree Tree whose type is {@code type} * @param type where the defaults are applied */ @@ -1602,6 +1638,11 @@ protected void applyQualifierParameterDefaults(Tree tree, AnnotatedTypeMirror ty /** * Applies defaults for types in a class with an qualifier parameter. * + *

    Within a class with {@code @HasQualifierParameter}, types with that class default to the + * polymorphic qualifier rather than the typical default. Local variables with a type that has a + * qualifier parameter are initialized to the type of their initializer, rather than the default + * for local variables. + * * @param elt Element whose type is {@code type} * @param type where the defaults are applied */ @@ -1621,6 +1662,8 @@ protected void applyQualifierParameterDefaults( return; } + applyLocalVariableQualifierParameterDefaults(elt, type); + TypeElement enclosingClass = ElementUtils.enclosingClass(elt); Set tops; if (enclosingClass != null) { @@ -1649,6 +1692,64 @@ public Void visitDeclared(AnnotatedDeclaredType type, Void aVoid) { }.visit(type); } + /** + * Defaults local variables with types that have a qualifier parameter to the type of their + * initializer, if an initializer is present. Does nothing for local variables with no + * initializer. + * + * @param elt Element whose type is {@code type} + * @param type where the defaults are applied + */ + private void applyLocalVariableQualifierParameterDefaults( + Element elt, AnnotatedTypeMirror type) { + if (elt.getKind() != ElementKind.LOCAL_VARIABLE + || getQualifierParameterHierarchies(type).isEmpty() + || variablesUnderInitialization.contains(elt)) { + return; + } + + Tree declTree = declarationFromElement(elt); + if (declTree == null || declTree.getKind() != Kind.VARIABLE) { + return; + } + + ExpressionTree initializer = ((VariableTree) declTree).getInitializer(); + if (initializer == null) { + return; + } + + VariableElement variableElt = (VariableElement) elt; + variablesUnderInitialization.add(variableElt); + AnnotatedTypeMirror initializerType; + if (shouldCache && initializerCache.containsKey(initializer)) { + initializerType = initializerCache.get(initializer); + } else { + // When this method is called by getAnnotatedTypeLhs, flow is turned off. + // Turn it back on so the type of the initializer is the refined type. + boolean oldUseFlow = useFlow; + useFlow = everUseFlow; + try { + initializerType = getAnnotatedType(initializer); + } finally { + useFlow = oldUseFlow; + } + } + + Set qualParamTypes = AnnotationUtils.createAnnotationSet(); + for (AnnotationMirror initializerAnnotation : initializerType.getAnnotations()) { + if (hasQualifierParameterInHierarchy( + type, qualHierarchy.getTopAnnotation(initializerAnnotation))) { + qualParamTypes.add(initializerAnnotation); + } + } + + type.addMissingAnnotations(qualParamTypes); + variablesUnderInitialization.remove(variableElt); + if (shouldCache) { + initializerCache.put(initializer, initializerType); + } + } + /** * To add annotations to the type of method or constructor parameters, add a {@link * TypeAnnotator} using {@link #createTypeAnnotator()} and see the comment in {@link From 50baa53c815185fcccd01970fcd59154de7b36e4 Mon Sep 17 00:00:00 2001 From: Werner Dietl Date: Fri, 31 Jul 2020 13:24:25 -0400 Subject: [PATCH 117/138] Remove FrameworkPer[Directory/File]Test and use CheckerFramework versions. (#3525) --- changelog.txt | 3 ++ .../test/FrameworkPerDirectoryTest.java | 34 ------------------- .../framework/test/FrameworkPerFileTest.java | 33 ------------------ .../src/test/java/tests/AccumulationTest.java | 4 +-- .../src/test/java/tests/AggregateTest.java | 4 +-- .../src/test/java/tests/AliasingTest.java | 4 +-- .../src/test/java/tests/AnnotatedForTest.java | 4 +-- .../src/test/java/tests/ClassValTest.java | 4 +-- .../test/java/tests/CompoundCheckerTest.java | 4 +-- .../java/tests/DefaultingLowerBoundTest.java | 4 +-- .../java/tests/DefaultingUpperBoundTest.java | 4 +-- framework/src/test/java/tests/Flow2Test.java | 4 +-- .../java/tests/FlowExpressionCheckerTest.java | 4 +-- framework/src/test/java/tests/FlowTest.java | 4 +-- .../src/test/java/tests/FrameworkTest.java | 4 +-- framework/src/test/java/tests/LubGlbTest.java | 4 +-- .../src/test/java/tests/MethodValTest.java | 4 +-- .../test/java/tests/NonTopDefaultTest.java | 4 +-- .../java/tests/PuritySuggestionsTest.java | 4 +-- .../src/test/java/tests/ReflectionTest.java | 4 +-- .../test/java/tests/ReportModifiersTest.java | 4 +-- framework/src/test/java/tests/ReportTest.java | 4 +-- .../test/java/tests/ReportTreeKindsTest.java | 4 +-- .../java/tests/SubtypingEncryptedTest.java | 4 +-- .../SubtypingStringPatternsFullTest.java | 4 +-- .../SubtypingStringPatternsPartialTest.java | 4 +-- .../test/java/tests/SupportedQualsTest.java | 4 +-- .../src/test/java/tests/TestCheckerTest.java | 4 +-- .../test/java/tests/TypeDeclDefaultTest.java | 4 +-- .../tests/ValueIgnoreRangeOverflowTest.java | 4 +-- .../ValueNonNullStringsConcatenationTest.java | 4 +-- framework/src/test/java/tests/ValueTest.java | 4 +-- .../tests/ValueUncheckedDefaultsTest.java | 4 +-- .../WholeProgramInferenceJaifsTest.java | 4 +-- ...leProgramInferenceJaifsValidationTest.java | 4 +-- .../WholeProgramInferenceStubsTest.java | 4 +-- ...leProgramInferenceStubsValidationTest.java | 4 +-- 37 files changed, 71 insertions(+), 135 deletions(-) delete mode 100644 framework-test/src/main/java/org/checkerframework/framework/test/FrameworkPerDirectoryTest.java delete mode 100644 framework-test/src/main/java/org/checkerframework/framework/test/FrameworkPerFileTest.java diff --git a/changelog.txt b/changelog.txt index 644bd572159f..8f0abaf5fc4d 100644 --- a/changelog.txt +++ b/changelog.txt @@ -18,6 +18,9 @@ Implementation details: commonAssignmentCheck() now takes an additional argument. Type system authors must update their overriding implementations. +Removed org.checkerframework.framework.test.FrameworkPer(Directory/File)Test classes. +Use CheckerFrameworkPer(Directory/File)Test instead. + --------------------------------------------------------------------------- Version 3.5.0, July 1, 2020 diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/FrameworkPerDirectoryTest.java b/framework-test/src/main/java/org/checkerframework/framework/test/FrameworkPerDirectoryTest.java deleted file mode 100644 index bc6259a51e0f..000000000000 --- a/framework-test/src/main/java/org/checkerframework/framework/test/FrameworkPerDirectoryTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.checkerframework.framework.test; - -import java.io.File; -import java.util.List; -import javax.annotation.processing.AbstractProcessor; - -/** - * The same as {@link org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest} except - * the annotated JDK is not used (because the {@code annotated-jdk} resource is under {@code - * checker/}, not {@code framework/}). - */ -public abstract class FrameworkPerDirectoryTest extends CheckerFrameworkPerDirectoryTest { - - /** - * Creates a new framework test. - * - *

    {@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, - * Iterable, Iterable, List, boolean)} adds additional checker options. - * - *

    These tests do not use the annotated JDK. - * - * @param testFiles the files containing test code, which will be type-checked - * @param checker the class for the checker to use - * @param testDir the path to the directory of test inputs - * @param checkerOptions options to pass to the compiler when running tests - */ - protected FrameworkPerDirectoryTest( - List testFiles, - Class checker, - String testDir, - String... checkerOptions) { - super(testFiles, checker, testDir, checkerOptions); - } -} diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/FrameworkPerFileTest.java b/framework-test/src/main/java/org/checkerframework/framework/test/FrameworkPerFileTest.java deleted file mode 100644 index 777e2ec8ce18..000000000000 --- a/framework-test/src/main/java/org/checkerframework/framework/test/FrameworkPerFileTest.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.checkerframework.framework.test; - -import java.io.File; -import java.util.List; -import javax.annotation.processing.AbstractProcessor; - -/** - * The same as {@link org.checkerframework.framework.test.CheckerFrameworkPerFileTest}, but does not - * use the annotated JDK (because the {@code annotated-jdk} resource is under {@code checker/}, not - * {@code framework/}). - */ -public abstract class FrameworkPerFileTest extends CheckerFrameworkPerFileTest { - /** - * Creates a new framework test. - * - *

    {@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String, - * Iterable, Iterable, List, boolean)} adds additional checker options. - * - *

    These tests do not use the annotated JDK. - * - * @param testFile the file containing test code, which will be type-checked - * @param checker the class for the checker to use - * @param testDir the path to the directory of test inputs - * @param checkerOptions options to pass to the compiler when running tests - */ - protected FrameworkPerFileTest( - File testFile, - Class checker, - String testDir, - String... checkerOptions) { - super(testFile, checker, testDir, checkerOptions); - } -} diff --git a/framework/src/test/java/tests/AccumulationTest.java b/framework/src/test/java/tests/AccumulationTest.java index 94527f043e15..8f9143fb9c13 100644 --- a/framework/src/test/java/tests/AccumulationTest.java +++ b/framework/src/test/java/tests/AccumulationTest.java @@ -2,7 +2,7 @@ import java.io.File; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; import testaccumulation.TestAccumulationChecker; @@ -10,7 +10,7 @@ * A test that the accumulation abstract checker is working correctly, using a simple accumulation * checker. */ -public class AccumulationTest extends FrameworkPerDirectoryTest { +public class AccumulationTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public AccumulationTest(List testFiles) { diff --git a/framework/src/test/java/tests/AggregateTest.java b/framework/src/test/java/tests/AggregateTest.java index 2d8181188c08..bb575cd04447 100644 --- a/framework/src/test/java/tests/AggregateTest.java +++ b/framework/src/test/java/tests/AggregateTest.java @@ -2,11 +2,11 @@ import java.io.File; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; import testlib.aggregate.AggregateOfCompoundChecker; -public class AggregateTest extends FrameworkPerDirectoryTest { +public class AggregateTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public AggregateTest(List testFiles) { diff --git a/framework/src/test/java/tests/AliasingTest.java b/framework/src/test/java/tests/AliasingTest.java index 246b8857d405..5245cb12c013 100644 --- a/framework/src/test/java/tests/AliasingTest.java +++ b/framework/src/test/java/tests/AliasingTest.java @@ -2,10 +2,10 @@ import java.io.File; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -public class AliasingTest extends FrameworkPerDirectoryTest { +public class AliasingTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public AliasingTest(List testFiles) { diff --git a/framework/src/test/java/tests/AnnotatedForTest.java b/framework/src/test/java/tests/AnnotatedForTest.java index 3208d5f43efb..aa27ee2093d1 100644 --- a/framework/src/test/java/tests/AnnotatedForTest.java +++ b/framework/src/test/java/tests/AnnotatedForTest.java @@ -2,11 +2,11 @@ import java.io.File; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; /** Created by jthaine on 6/25/15. */ -public class AnnotatedForTest extends FrameworkPerDirectoryTest { +public class AnnotatedForTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public AnnotatedForTest(List testFiles) { diff --git a/framework/src/test/java/tests/ClassValTest.java b/framework/src/test/java/tests/ClassValTest.java index 018fe883411f..b2c4d43a7149 100644 --- a/framework/src/test/java/tests/ClassValTest.java +++ b/framework/src/test/java/tests/ClassValTest.java @@ -2,11 +2,11 @@ import java.io.File; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; /** Tests the ClassVal Checker. */ -public class ClassValTest extends FrameworkPerDirectoryTest { +public class ClassValTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public ClassValTest(List testFiles) { diff --git a/framework/src/test/java/tests/CompoundCheckerTest.java b/framework/src/test/java/tests/CompoundCheckerTest.java index 190c060cb51f..35a1a6970ccf 100644 --- a/framework/src/test/java/tests/CompoundCheckerTest.java +++ b/framework/src/test/java/tests/CompoundCheckerTest.java @@ -2,12 +2,12 @@ import java.io.File; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; import testlib.compound.CompoundChecker; /** Tests for the compound checker design pattern. */ -public class CompoundCheckerTest extends FrameworkPerDirectoryTest { +public class CompoundCheckerTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public CompoundCheckerTest(List testFiles) { diff --git a/framework/src/test/java/tests/DefaultingLowerBoundTest.java b/framework/src/test/java/tests/DefaultingLowerBoundTest.java index 86acb5ad84cd..af519b1d69e3 100644 --- a/framework/src/test/java/tests/DefaultingLowerBoundTest.java +++ b/framework/src/test/java/tests/DefaultingLowerBoundTest.java @@ -2,12 +2,12 @@ import java.io.File; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; import testlib.defaulting.DefaultingLowerBoundChecker; /** Created by jburke on 9/29/14. */ -public class DefaultingLowerBoundTest extends FrameworkPerDirectoryTest { +public class DefaultingLowerBoundTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public DefaultingLowerBoundTest(List testFiles) { diff --git a/framework/src/test/java/tests/DefaultingUpperBoundTest.java b/framework/src/test/java/tests/DefaultingUpperBoundTest.java index e46a4ddfc46b..d28ed11db349 100644 --- a/framework/src/test/java/tests/DefaultingUpperBoundTest.java +++ b/framework/src/test/java/tests/DefaultingUpperBoundTest.java @@ -2,12 +2,12 @@ import java.io.File; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; import testlib.defaulting.DefaultingUpperBoundChecker; /** Created by jburke on 9/29/14. */ -public class DefaultingUpperBoundTest extends FrameworkPerDirectoryTest { +public class DefaultingUpperBoundTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public DefaultingUpperBoundTest(List testFiles) { diff --git a/framework/src/test/java/tests/Flow2Test.java b/framework/src/test/java/tests/Flow2Test.java index 42c81a5d4904..28ccbd4f554e 100644 --- a/framework/src/test/java/tests/Flow2Test.java +++ b/framework/src/test/java/tests/Flow2Test.java @@ -2,7 +2,7 @@ import java.io.File; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; import testlib.util.FlowTestChecker; @@ -11,7 +11,7 @@ * FlowTest} and have been written when the org.checkerframework.dataflow analysis has been * completely rewritten. */ -public class Flow2Test extends FrameworkPerDirectoryTest { +public class Flow2Test extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public Flow2Test(List testFiles) { diff --git a/framework/src/test/java/tests/FlowExpressionCheckerTest.java b/framework/src/test/java/tests/FlowExpressionCheckerTest.java index 978325071aff..a04808e7c3d0 100644 --- a/framework/src/test/java/tests/FlowExpressionCheckerTest.java +++ b/framework/src/test/java/tests/FlowExpressionCheckerTest.java @@ -2,11 +2,11 @@ import java.io.File; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; import testlib.flowexpression.FlowExpressionChecker; -public class FlowExpressionCheckerTest extends FrameworkPerDirectoryTest { +public class FlowExpressionCheckerTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public FlowExpressionCheckerTest(List testFiles) { diff --git a/framework/src/test/java/tests/FlowTest.java b/framework/src/test/java/tests/FlowTest.java index 4d527c3ebcf5..04826808ed46 100644 --- a/framework/src/test/java/tests/FlowTest.java +++ b/framework/src/test/java/tests/FlowTest.java @@ -2,12 +2,12 @@ import java.io.File; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; import testlib.util.FlowTestChecker; /** */ -public class FlowTest extends FrameworkPerDirectoryTest { +public class FlowTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public FlowTest(List testFiles) { diff --git a/framework/src/test/java/tests/FrameworkTest.java b/framework/src/test/java/tests/FrameworkTest.java index 5349e4d2cbf6..3c6a267d718f 100644 --- a/framework/src/test/java/tests/FrameworkTest.java +++ b/framework/src/test/java/tests/FrameworkTest.java @@ -1,12 +1,12 @@ package tests; import java.io.File; -import org.checkerframework.framework.test.FrameworkPerFileTest; +import org.checkerframework.framework.test.CheckerFrameworkPerFileTest; import org.junit.runners.Parameterized.Parameters; import testlib.util.TestChecker; /** JUnit tests for the Checker Framework, using the {@link TestChecker}. */ -public class FrameworkTest extends FrameworkPerFileTest { +public class FrameworkTest extends CheckerFrameworkPerFileTest { public FrameworkTest(File testFile) { super(testFile, TestChecker.class, "framework", "-Anomsgtext"); diff --git a/framework/src/test/java/tests/LubGlbTest.java b/framework/src/test/java/tests/LubGlbTest.java index 5de49380932c..1377c88cb220 100644 --- a/framework/src/test/java/tests/LubGlbTest.java +++ b/framework/src/test/java/tests/LubGlbTest.java @@ -2,11 +2,11 @@ import java.io.File; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; /** */ -public class LubGlbTest extends FrameworkPerDirectoryTest { +public class LubGlbTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public LubGlbTest(List testFiles) { diff --git a/framework/src/test/java/tests/MethodValTest.java b/framework/src/test/java/tests/MethodValTest.java index fc187c92097e..7c1901e26461 100644 --- a/framework/src/test/java/tests/MethodValTest.java +++ b/framework/src/test/java/tests/MethodValTest.java @@ -2,11 +2,11 @@ import java.io.File; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; /** Tests the MethodVal Checker. */ -public class MethodValTest extends FrameworkPerDirectoryTest { +public class MethodValTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public MethodValTest(List testFiles) { diff --git a/framework/src/test/java/tests/NonTopDefaultTest.java b/framework/src/test/java/tests/NonTopDefaultTest.java index 675613898acf..9fc7e9ff35b9 100644 --- a/framework/src/test/java/tests/NonTopDefaultTest.java +++ b/framework/src/test/java/tests/NonTopDefaultTest.java @@ -2,12 +2,12 @@ import java.io.File; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; import testlib.nontopdefault.NTDChecker; /** Tests the NonTopDefault Checker. */ -public class NonTopDefaultTest extends FrameworkPerDirectoryTest { +public class NonTopDefaultTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public NonTopDefaultTest(List testFiles) { diff --git a/framework/src/test/java/tests/PuritySuggestionsTest.java b/framework/src/test/java/tests/PuritySuggestionsTest.java index e8a181008ce2..1e50695c074e 100644 --- a/framework/src/test/java/tests/PuritySuggestionsTest.java +++ b/framework/src/test/java/tests/PuritySuggestionsTest.java @@ -2,12 +2,12 @@ import java.io.File; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; import testlib.util.FlowTestChecker; /** Tests for the {@code -AsuggestPureMethods} command-line argument. */ -public class PuritySuggestionsTest extends FrameworkPerDirectoryTest { +public class PuritySuggestionsTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public PuritySuggestionsTest(List testFiles) { diff --git a/framework/src/test/java/tests/ReflectionTest.java b/framework/src/test/java/tests/ReflectionTest.java index e27a5da3b6da..146d558f19c2 100644 --- a/framework/src/test/java/tests/ReflectionTest.java +++ b/framework/src/test/java/tests/ReflectionTest.java @@ -3,12 +3,12 @@ import java.io.File; import java.util.ArrayList; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; import testlib.reflection.ReflectionTestChecker; /** Tests the reflection resolution using a simple type system. */ -public class ReflectionTest extends FrameworkPerDirectoryTest { +public class ReflectionTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public ReflectionTest(List testFiles) { diff --git a/framework/src/test/java/tests/ReportModifiersTest.java b/framework/src/test/java/tests/ReportModifiersTest.java index 8ace1a86187c..f5cf96de48bc 100644 --- a/framework/src/test/java/tests/ReportModifiersTest.java +++ b/framework/src/test/java/tests/ReportModifiersTest.java @@ -2,10 +2,10 @@ import java.io.File; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -public class ReportModifiersTest extends FrameworkPerDirectoryTest { +public class ReportModifiersTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public ReportModifiersTest(List testFiles) { diff --git a/framework/src/test/java/tests/ReportTest.java b/framework/src/test/java/tests/ReportTest.java index 423bbf3adf79..5c6248a64d69 100644 --- a/framework/src/test/java/tests/ReportTest.java +++ b/framework/src/test/java/tests/ReportTest.java @@ -2,10 +2,10 @@ import java.io.File; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -public class ReportTest extends FrameworkPerDirectoryTest { +public class ReportTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public ReportTest(List testFiles) { diff --git a/framework/src/test/java/tests/ReportTreeKindsTest.java b/framework/src/test/java/tests/ReportTreeKindsTest.java index 6098d228e75e..7db485b419d2 100644 --- a/framework/src/test/java/tests/ReportTreeKindsTest.java +++ b/framework/src/test/java/tests/ReportTreeKindsTest.java @@ -2,10 +2,10 @@ import java.io.File; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -public class ReportTreeKindsTest extends FrameworkPerDirectoryTest { +public class ReportTreeKindsTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public ReportTreeKindsTest(List testFiles) { diff --git a/framework/src/test/java/tests/SubtypingEncryptedTest.java b/framework/src/test/java/tests/SubtypingEncryptedTest.java index 84c50edd9a41..49e815ce684a 100644 --- a/framework/src/test/java/tests/SubtypingEncryptedTest.java +++ b/framework/src/test/java/tests/SubtypingEncryptedTest.java @@ -2,11 +2,11 @@ import java.io.File; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; /** Test suite for the Subtyping Checker, using a simple {@link Encrypted} annotation. */ -public class SubtypingEncryptedTest extends FrameworkPerDirectoryTest { +public class SubtypingEncryptedTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public SubtypingEncryptedTest(List testFiles) { diff --git a/framework/src/test/java/tests/SubtypingStringPatternsFullTest.java b/framework/src/test/java/tests/SubtypingStringPatternsFullTest.java index 821cfa99e9d1..a48a8634fdbe 100644 --- a/framework/src/test/java/tests/SubtypingStringPatternsFullTest.java +++ b/framework/src/test/java/tests/SubtypingStringPatternsFullTest.java @@ -2,11 +2,11 @@ import java.io.File; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; /** Test suite for the Subtyping Checker, using a simple {@link Encrypted} annotation. */ -public class SubtypingStringPatternsFullTest extends FrameworkPerDirectoryTest { +public class SubtypingStringPatternsFullTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public SubtypingStringPatternsFullTest(List testFiles) { diff --git a/framework/src/test/java/tests/SubtypingStringPatternsPartialTest.java b/framework/src/test/java/tests/SubtypingStringPatternsPartialTest.java index c9e5bb182ebb..592b2b06fe96 100644 --- a/framework/src/test/java/tests/SubtypingStringPatternsPartialTest.java +++ b/framework/src/test/java/tests/SubtypingStringPatternsPartialTest.java @@ -2,11 +2,11 @@ import java.io.File; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; /** Test suite for the Subtyping Checker, using a simple {@link Encrypted} annotation. */ -public class SubtypingStringPatternsPartialTest extends FrameworkPerDirectoryTest { +public class SubtypingStringPatternsPartialTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public SubtypingStringPatternsPartialTest(List testFiles) { diff --git a/framework/src/test/java/tests/SupportedQualsTest.java b/framework/src/test/java/tests/SupportedQualsTest.java index 7e3053f3a3c3..b0ef0dab9e02 100644 --- a/framework/src/test/java/tests/SupportedQualsTest.java +++ b/framework/src/test/java/tests/SupportedQualsTest.java @@ -2,11 +2,11 @@ import java.io.File; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; import testlib.supportedquals.SupportedQualsChecker; -public class SupportedQualsTest extends FrameworkPerDirectoryTest { +public class SupportedQualsTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public SupportedQualsTest(List testFiles) { diff --git a/framework/src/test/java/tests/TestCheckerTest.java b/framework/src/test/java/tests/TestCheckerTest.java index 7f5e07e2e858..d8b2569f6ead 100644 --- a/framework/src/test/java/tests/TestCheckerTest.java +++ b/framework/src/test/java/tests/TestCheckerTest.java @@ -2,12 +2,12 @@ import java.io.File; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; import testchecker.TestChecker; /** */ -public class TestCheckerTest extends FrameworkPerDirectoryTest { +public class TestCheckerTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public TestCheckerTest(List testFiles) { diff --git a/framework/src/test/java/tests/TypeDeclDefaultTest.java b/framework/src/test/java/tests/TypeDeclDefaultTest.java index ccca47715e01..1951ea309d6a 100644 --- a/framework/src/test/java/tests/TypeDeclDefaultTest.java +++ b/framework/src/test/java/tests/TypeDeclDefaultTest.java @@ -2,11 +2,11 @@ import java.io.File; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; /** Create the TypeDeclDefault test. */ -public class TypeDeclDefaultTest extends FrameworkPerDirectoryTest { +public class TypeDeclDefaultTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public TypeDeclDefaultTest(List testFiles) { diff --git a/framework/src/test/java/tests/ValueIgnoreRangeOverflowTest.java b/framework/src/test/java/tests/ValueIgnoreRangeOverflowTest.java index 093684ab3797..c7b7977ac5f8 100644 --- a/framework/src/test/java/tests/ValueIgnoreRangeOverflowTest.java +++ b/framework/src/test/java/tests/ValueIgnoreRangeOverflowTest.java @@ -3,11 +3,11 @@ import java.io.File; import java.util.List; import org.checkerframework.common.value.ValueChecker; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; /** Tests the constant value propagation type system without overflow. */ -public class ValueIgnoreRangeOverflowTest extends FrameworkPerDirectoryTest { +public class ValueIgnoreRangeOverflowTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public ValueIgnoreRangeOverflowTest(List testFiles) { diff --git a/framework/src/test/java/tests/ValueNonNullStringsConcatenationTest.java b/framework/src/test/java/tests/ValueNonNullStringsConcatenationTest.java index 47f560ee05be..d681e58565b5 100644 --- a/framework/src/test/java/tests/ValueNonNullStringsConcatenationTest.java +++ b/framework/src/test/java/tests/ValueNonNullStringsConcatenationTest.java @@ -3,10 +3,10 @@ import java.io.File; import java.util.List; import org.checkerframework.common.value.ValueChecker; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; -public class ValueNonNullStringsConcatenationTest extends FrameworkPerDirectoryTest { +public class ValueNonNullStringsConcatenationTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public ValueNonNullStringsConcatenationTest(List testFiles) { diff --git a/framework/src/test/java/tests/ValueTest.java b/framework/src/test/java/tests/ValueTest.java index 38cac432432c..d9f3d268d0d4 100644 --- a/framework/src/test/java/tests/ValueTest.java +++ b/framework/src/test/java/tests/ValueTest.java @@ -3,7 +3,7 @@ import java.io.File; import java.util.List; import org.checkerframework.common.value.ValueChecker; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; /** @@ -13,7 +13,7 @@ * ExceptionTest will fail because it cannot find the ExceptionTest.class file for reflective method * resolution. */ -public class ValueTest extends FrameworkPerDirectoryTest { +public class ValueTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public ValueTest(List testFiles) { diff --git a/framework/src/test/java/tests/ValueUncheckedDefaultsTest.java b/framework/src/test/java/tests/ValueUncheckedDefaultsTest.java index 6cc64ec12618..bb9e614b95c2 100644 --- a/framework/src/test/java/tests/ValueUncheckedDefaultsTest.java +++ b/framework/src/test/java/tests/ValueUncheckedDefaultsTest.java @@ -3,11 +3,11 @@ import java.io.File; import java.util.List; import org.checkerframework.common.value.ValueChecker; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized.Parameters; /** Tests conservative defaults for the constant value propagation type system. */ -public class ValueUncheckedDefaultsTest extends FrameworkPerDirectoryTest { +public class ValueUncheckedDefaultsTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public ValueUncheckedDefaultsTest(List testFiles) { diff --git a/framework/src/test/java/tests/wpirunners/WholeProgramInferenceJaifsTest.java b/framework/src/test/java/tests/wpirunners/WholeProgramInferenceJaifsTest.java index 86fffd209b4f..796e68187bf2 100644 --- a/framework/src/test/java/tests/wpirunners/WholeProgramInferenceJaifsTest.java +++ b/framework/src/test/java/tests/wpirunners/WholeProgramInferenceJaifsTest.java @@ -2,7 +2,7 @@ import java.io.File; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; import testlib.wholeprograminference.WholeProgramInferenceTestChecker; @@ -15,7 +15,7 @@ * the expected ones. The errors on .java files must be ignored. */ @Category(WholeProgramInferenceJaifsTest.class) -public class WholeProgramInferenceJaifsTest extends FrameworkPerDirectoryTest { +public class WholeProgramInferenceJaifsTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public WholeProgramInferenceJaifsTest(List testFiles) { super( diff --git a/framework/src/test/java/tests/wpirunners/WholeProgramInferenceJaifsValidationTest.java b/framework/src/test/java/tests/wpirunners/WholeProgramInferenceJaifsValidationTest.java index 0997832db90b..8c965426b5c9 100644 --- a/framework/src/test/java/tests/wpirunners/WholeProgramInferenceJaifsValidationTest.java +++ b/framework/src/test/java/tests/wpirunners/WholeProgramInferenceJaifsValidationTest.java @@ -2,7 +2,7 @@ import java.io.File; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; import testlib.wholeprograminference.WholeProgramInferenceTestChecker; @@ -12,7 +12,7 @@ * which ensures that with the annotations inserted, the errors are no longer issued. */ @Category(WholeProgramInferenceJaifsTest.class) -public class WholeProgramInferenceJaifsValidationTest extends FrameworkPerDirectoryTest { +public class WholeProgramInferenceJaifsValidationTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public WholeProgramInferenceJaifsValidationTest(List testFiles) { super(testFiles, WholeProgramInferenceTestChecker.class, "value", "-Anomsgtext"); diff --git a/framework/src/test/java/tests/wpirunners/WholeProgramInferenceStubsTest.java b/framework/src/test/java/tests/wpirunners/WholeProgramInferenceStubsTest.java index df0a1233feb7..0803dde032dd 100644 --- a/framework/src/test/java/tests/wpirunners/WholeProgramInferenceStubsTest.java +++ b/framework/src/test/java/tests/wpirunners/WholeProgramInferenceStubsTest.java @@ -2,7 +2,7 @@ import java.io.File; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; import testlib.wholeprograminference.WholeProgramInferenceTestChecker; @@ -16,7 +16,7 @@ * the expected ones. The errors on .java files must be ignored. */ @Category(WholeProgramInferenceStubsTest.class) -public class WholeProgramInferenceStubsTest extends FrameworkPerDirectoryTest { +public class WholeProgramInferenceStubsTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public WholeProgramInferenceStubsTest(List testFiles) { diff --git a/framework/src/test/java/tests/wpirunners/WholeProgramInferenceStubsValidationTest.java b/framework/src/test/java/tests/wpirunners/WholeProgramInferenceStubsValidationTest.java index 2f38acfdbad3..3582531d2cfb 100644 --- a/framework/src/test/java/tests/wpirunners/WholeProgramInferenceStubsValidationTest.java +++ b/framework/src/test/java/tests/wpirunners/WholeProgramInferenceStubsValidationTest.java @@ -2,7 +2,7 @@ import java.io.File; import java.util.List; -import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.experimental.categories.Category; import org.junit.runners.Parameterized.Parameters; import testlib.wholeprograminference.WholeProgramInferenceTestChecker; @@ -12,7 +12,7 @@ * that with the stubs in place, the errors that those annotations remove are no longer issued. */ @Category(WholeProgramInferenceStubsTest.class) -public class WholeProgramInferenceStubsValidationTest extends FrameworkPerDirectoryTest { +public class WholeProgramInferenceStubsValidationTest extends CheckerFrameworkPerDirectoryTest { /** @param testFiles the files containing test code, which will be type-checked */ public WholeProgramInferenceStubsValidationTest(List testFiles) { From b11739918267731eb7fc221b3a598542f5019bf8 Mon Sep 17 00:00:00 2001 From: Werner Dietl Date: Fri, 31 Jul 2020 14:03:00 -0400 Subject: [PATCH 118/138] Improve option handling (Fixes #3528) (#3537) --- .../framework/source/SourceChecker.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) 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 5eecf71683e1..e3e117c176f0 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -1534,35 +1534,35 @@ private Map createActiveOptions(Map options) { String[] split = key.split(OPTION_SEPARATOR); + splitlengthswitch: switch (split.length) { case 1: - // No separator, option always active + // No separator, option always active. activeOpts.put(key, value); break; case 2: - // Valid class-option pair Class clazz = this.getClass(); do { if (clazz.getCanonicalName().equals(split[0]) || clazz.getSimpleName().equals(split[0])) { + // Valid class-option pair. activeOpts.put(split[1], value); + break splitlengthswitch; } clazz = clazz.getSuperclass(); } while (clazz != null && !clazz.getName() .equals(AbstractTypeProcessor.class.getCanonicalName())); + // Didn't find a matching class. Option might be for another processor. Add + // option anyways. javac will warn if no processor supports the option. + activeOpts.put(key, value); break; default: - throw new UserError( - "Invalid option name: " - + key - + " At most one separator " - + OPTION_SEPARATOR - + " expected, but found " - + (split.length - 1) - + "."); + // Too many separators. Option might be for another processor. Add option + // anyways. javac will warn if no processor supports the option. + activeOpts.put(key, value); } } return Collections.unmodifiableMap(activeOpts); From 7200178afb244635b8dfdd01919922e0f0198463 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Fri, 31 Jul 2020 12:53:34 -0700 Subject: [PATCH 119/138] Visualize CFGs without package names (#3516) --- .../dataflow/cfg/AbstractCFGVisualizer.java | 24 ++++++--- .../framework/flow/CFAbstractStore.java | 2 +- .../framework/flow/CFAbstractValue.java | 49 ++++++++++++++++--- .../util/DefaultAnnotationFormatter.java | 2 +- .../javacutil/ElementUtils.java | 26 +--------- .../javacutil/TypesUtils.java | 29 +++++++++++ 6 files changed, 91 insertions(+), 41 deletions(-) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/AbstractCFGVisualizer.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/AbstractCFGVisualizer.java index 84be51534b51..6154493fe29b 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/AbstractCFGVisualizer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/AbstractCFGVisualizer.java @@ -58,12 +58,24 @@ public abstract class AbstractCFGVisualizer< @Override public void init(Map args) { - Object verb = args.get("verbose"); - this.verbose = - verb != null - && (verb instanceof String - ? Boolean.parseBoolean((String) verb) - : (boolean) verb); + this.verbose = toBoolean(args.get("verbose")); + } + + /** + * Convert the value to boolean, by parsing a string or casting any other value. null converts + * to false. + * + * @param o an object to convert to boolean + * @return {@code o} converted to boolean + */ + private static boolean toBoolean(@Nullable Object o) { + if (o == null) { + return false; + } + if (o instanceof String) { + return Boolean.parseBoolean((String) o); + } + return (boolean) o; } /** 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 b2c185de6573..2be57ad76cad 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractStore.java @@ -1025,7 +1025,7 @@ public String visualize(CFGVisualizer viz) { @SuppressWarnings("unchecked") CFGVisualizer castedViz = (CFGVisualizer) viz; StringBuilder sbVisualize = new StringBuilder(); - sbVisualize.append(castedViz.visualizeStoreHeader(this.getClass().getCanonicalName())); + sbVisualize.append(castedViz.visualizeStoreHeader(this.getClass().getSimpleName())); sbVisualize.append(internalVisualize(castedViz)); sbVisualize.append(castedViz.visualizeStoreFooter()); return sbVisualize.toString(); diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractValue.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractValue.java index 23106f06b7d3..88c1420f743a 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractValue.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractValue.java @@ -2,6 +2,7 @@ import java.util.Objects; import java.util.Set; +import java.util.StringJoiner; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.TypeKind; @@ -18,6 +19,7 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; import org.checkerframework.framework.type.QualifierHierarchy; import org.checkerframework.framework.util.AnnotatedTypes; +import org.checkerframework.framework.util.DefaultAnnotationFormatter; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.SystemUtil; import org.checkerframework.javacutil.TypesUtils; @@ -51,9 +53,18 @@ public abstract class CFAbstractValue> implements A /** The analysis class this value belongs to. */ protected final CFAbstractAnalysis analysis; + /** The underlying (Java) type in this abstract value. */ protected final TypeMirror underlyingType; + /** The annotations in this abstract value. */ protected final Set annotations; + /** + * Creates a new CFAbstractValue. + * + * @param analysis the analysis class this value belongs to + * @param annotations the annotations in this abstract value + * @param underlyingType the underlying (Java) type in this abstract value + */ protected CFAbstractValue( CFAbstractAnalysis analysis, Set annotations, @@ -159,19 +170,41 @@ public int hashCode() { } /** - * Returns the string representation as a comma-separated list. + * Returns the string representation, using fully-qualified names. + * + * @return the string representation, using fully-qualified names + */ + @SideEffectFree + public String toStringFullyQualified() { + return "CFAV{" + annotations + ", " + underlyingType + '}'; + } + + /** + * Returns the string representation, using simple (not fully-qualified) names. + * + * @return the string representation, using simple (not fully-qualified) names + */ + @SideEffectFree + public String toStringSimple() { + + DefaultAnnotationFormatter defaultAnnotationFormatter = new DefaultAnnotationFormatter(); + StringJoiner annotationsString = new StringJoiner(", "); + for (AnnotationMirror am : annotations) { + annotationsString.add(defaultAnnotationFormatter.formatAnnotationMirror(am)); + } + + return "CFAV{" + annotationsString + ", " + TypesUtils.simpleTypeName(underlyingType) + '}'; + } + + /** + * Returns the string representation. * - * @return the string representation as a comma-separated list + * @return the string representation */ @SideEffectFree @Override public String toString() { - return "CFAbstractValue{" - + "annotations=" - + annotations - + ", underlyingType=" - + underlyingType - + '}'; + return toStringSimple(); } /** diff --git a/framework/src/main/java/org/checkerframework/framework/util/DefaultAnnotationFormatter.java b/framework/src/main/java/org/checkerframework/framework/util/DefaultAnnotationFormatter.java index 77fd03e92f56..90676eebbaee 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/DefaultAnnotationFormatter.java +++ b/framework/src/main/java/org/checkerframework/framework/util/DefaultAnnotationFormatter.java @@ -12,7 +12,7 @@ import org.checkerframework.framework.qual.InvisibleQualifier; import org.checkerframework.javacutil.BugInCF; -/** A utility for converting AnnotationMirrors to Strings. */ +/** A utility for converting AnnotationMirrors to Strings. It omits full package names. */ public class DefaultAnnotationFormatter implements AnnotationFormatter { /** diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/ElementUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/ElementUtils.java index 30e4959bb518..3cc635da7367 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/ElementUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/ElementUtils.java @@ -21,11 +21,9 @@ import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; -import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; import javax.tools.JavaFileObject; @@ -199,7 +197,7 @@ public static String getSimpleName(ExecutableElement element) { sb.append("("); for (Iterator i = element.getParameters().iterator(); i.hasNext(); ) { - sb.append(simpleTypeName(i.next().asType())); + sb.append(TypesUtils.simpleTypeName(i.next().asType())); if (i.hasNext()) { sb.append(","); } @@ -209,28 +207,6 @@ public static String getSimpleName(ExecutableElement element) { return sb.toString(); } - /** - * Returns the simple type name, without annotations. - * - * @param type a type - * @return the simple type name, without annotations - */ - private static String simpleTypeName(TypeMirror type) { - switch (type.getKind()) { - case ARRAY: - return simpleTypeName(((ArrayType) type).getComponentType()) + "[]"; - case TYPEVAR: - return ((TypeVariable) type).asElement().getSimpleName().toString(); - case DECLARED: - return ((DeclaredType) type).asElement().getSimpleName().toString(); - default: - if (type.getKind().isPrimitive()) { - return TypeAnnotationUtils.unannotatedType(type).toString(); - } - } - throw new BugInCF("ElementUtils: unhandled type kind: %s, type: %s", type.getKind(), type); - } - /** * Check if the element is an element for 'java.lang.Object' * diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java index af5fecb08435..70356b8e4a43 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java @@ -683,4 +683,33 @@ public static boolean isFunctionalInterface(TypeMirror type, ProcessingEnvironme com.sun.tools.javac.code.Types javacTypes = com.sun.tools.javac.code.Types.instance(ctx); return javacTypes.isFunctionalInterface((Type) type); } + + /** + * Returns the simple type name, without annotations. + * + * @param type a type + * @return the simple type name, without annotations + */ + public static String simpleTypeName(TypeMirror type) { + switch (type.getKind()) { + case ARRAY: + return simpleTypeName(((ArrayType) type).getComponentType()) + "[]"; + case TYPEVAR: + return ((TypeVariable) type).asElement().getSimpleName().toString(); + case DECLARED: + return ((DeclaredType) type).asElement().getSimpleName().toString(); + case NULL: + return ""; + case VOID: + return "void"; + default: + if (type.getKind().isPrimitive()) { + return TypeAnnotationUtils.unannotatedType(type).toString(); + } else { + throw new BugInCF( + "simpleTypeName: unhandled type kind: %s, type: %s", + type.getKind(), type); + } + } + } } From 32a57f26b8659978bfaa1fb4a9e41e3acdc3e031 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Fri, 31 Jul 2020 13:48:42 -0700 Subject: [PATCH 120/138] Support remove() in iterator (#3538) --- .../dataflow/util/AbstractMostlySingleton.java | 3 ++- .../dataflow/util/IdentityMostlySingleton.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/AbstractMostlySingleton.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/AbstractMostlySingleton.java index 9fe3169c248b..138fe9988652 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/util/AbstractMostlySingleton.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/util/AbstractMostlySingleton.java @@ -91,7 +91,8 @@ public T next() { @Override public void remove() { - throw new UnsupportedOperationException(); + state = State.EMPTY; + value = null; } }; case ANY: diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/util/IdentityMostlySingleton.java b/dataflow/src/main/java/org/checkerframework/dataflow/util/IdentityMostlySingleton.java index d721d7fc8688..9e3f4f2a1de2 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/util/IdentityMostlySingleton.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/util/IdentityMostlySingleton.java @@ -4,7 +4,7 @@ import org.checkerframework.javacutil.BugInCF; /** - * An arbitrary-size set that is very efficient ( more efficient than HashSet) for 0 and 1 elements. + * An arbitrary-size set that is very efficient (more efficient than HashSet) for 0 and 1 elements. * Uses object identity for object comparison. */ public final class IdentityMostlySingleton extends AbstractMostlySingleton { From 271497ab09b527d9c2dc7bfd5deb7ad564bbf5f0 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Fri, 31 Jul 2020 13:50:56 -0700 Subject: [PATCH 121/138] Improve documentation --- .../dataflow/cfg/AbstractCFGVisualizer.java | 24 +++++++++++-------- .../dataflow/cfg/CFGBuilder.java | 20 +++++++++------- .../dataflow/cfg/ControlFlowGraph.java | 10 +++++++- .../dataflow/cfg/block/BlockImpl.java | 2 +- 4 files changed, 35 insertions(+), 21 deletions(-) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/AbstractCFGVisualizer.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/AbstractCFGVisualizer.java index 6154493fe29b..ed72760135f0 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/AbstractCFGVisualizer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/AbstractCFGVisualizer.java @@ -117,12 +117,15 @@ protected String visualizeGraphWithoutHeaderAndFooter( } /** - * Adds the successors of the current block to the work list and the visited blocks list. + * Outputs, to sbGraph, a visualization of a block's edges, but not the block itself. (The block + * itself is output elsewhere.) Also adds the successors of the block to the work list and the + * visited blocks list. * * @param cur the current block - * @param visited the set of blocks that have already been visited or are in the work list - * @param workList the queue of blocks to be processed - * @param sbGraph the {@link StringBuilder} to store the graph + * @param visited the set of blocks that have already been visited or are in the work list; side + * effected by this method + * @param workList the queue of blocks to be processed; side effected by this method + * @param sbGraph the {@link StringBuilder} to store the graph; side effected by this method */ protected void handleSuccessorsHelper( Block cur, Set visited, Queue workList, StringBuilder sbGraph) { @@ -489,15 +492,16 @@ protected abstract String visualizeNodes( protected abstract String visualizeGraphFooter(); /** - * Return the simple String of the process order of a node, e.g., "Process order: 23". When a - * node have multiple process orders, a sequence of numbers will be returned, e.g., "Process - * order: 23,25". + * Given a list of process orders (integers), returns a string representation. + * + *

    Examples: "Process order: 23", "Process order: 23,25". * - * @param order the list of the process order to be processed - * @return the String representation of the process order of the node + * @param order a list of process orders + * @return a String representation of the given process orders */ protected String getProcessOrderSimpleString(List order) { - return "Process order: " + order.toString().replaceAll("[\\[\\]]", ""); + String orderString = order.toString(); + return "Process order: " + orderString.substring(1, orderString.length() - 1); } /** diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGBuilder.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGBuilder.java index 22c6bf46214b..2e6dfcf19b00 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGBuilder.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/CFGBuilder.java @@ -283,13 +283,15 @@ public static ControlFlowGraph build( * An extended node can be one of several things (depending on its {@code type}): * *

      - *
    • NODE. An extended node of this type is just a wrapper for a {@link Node} (that - * cannot throw exceptions). - *
    • EXCEPTION_NODE. A wrapper for a {@link Node} which can throw exceptions. It - * contains a label for every possible exception type the node might throw. - *
    • UNCONDITIONAL_JUMP. An unconditional jump to a label. - *
    • TWO_TARGET_CONDITIONAL_JUMP. A conditional jump with two targets for both the - * 'then' and 'else' branch. + *
    • NODE: {@link CFGBuilder.NodeHolder}. An extended node of this type is just a + * wrapper for a {@link Node} (that cannot throw exceptions). + *
    • EXCEPTION_NODE: {@link CFGBuilder.NodeWithExceptionsHolder}. A wrapper for a + * {@link Node} which can throw exceptions. It contains a label for every possible + * exception type the node might throw. + *
    • UNCONDITIONAL_JUMP: {@link CFGBuilder.UnconditionalJump}. An unconditional + * jump to a label. + *
    • TWO_TARGET_CONDITIONAL_JUMP: {@link CFGBuilder.ConditionalJump}. A conditional + * jump with two targets for both the 'then' and 'else' branch. *
    */ protected abstract static class ExtendedNode { @@ -2757,7 +2759,7 @@ public Node visitAssignment(AssignmentTree tree, Void p) { ExpressionTree variable = tree.getVariable(); TypeMirror varType = TreeUtils.typeOf(variable); - // case 1: field access + // case 1: lhs is field access if (TreeUtils.isFieldAccess(variable)) { // visit receiver Node receiver = getReceiver(variable); @@ -2785,7 +2787,7 @@ public Node visitAssignment(AssignmentTree tree, Void p) { extendWithNode(assignmentNode); } - // case 2: other cases + // case 2: lhs is not a field access else { Node target = scan(variable, p); target.setLValue(); diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/ControlFlowGraph.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/ControlFlowGraph.java index abff30922fad..a42fe8f110fc 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/ControlFlowGraph.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/ControlFlowGraph.java @@ -21,6 +21,7 @@ import org.checkerframework.dataflow.cfg.block.Block.BlockType; import org.checkerframework.dataflow.cfg.block.ConditionalBlock; import org.checkerframework.dataflow.cfg.block.ExceptionBlock; +import org.checkerframework.dataflow.cfg.block.RegularBlock; import org.checkerframework.dataflow.cfg.block.SingleSuccessorBlock; import org.checkerframework.dataflow.cfg.block.SpecialBlock; import org.checkerframework.dataflow.cfg.block.SpecialBlockImpl; @@ -28,7 +29,14 @@ import org.checkerframework.dataflow.cfg.node.Node; import org.checkerframework.dataflow.cfg.node.ReturnNode; -/** A control flow graph (CFG for short) of a single method. */ +/** + * A control flow graph (CFG for short) of a single method. + * + *

    The graph is represented by the successors (methods {@link SingleSuccessorBlock#getSuccessor}, + * {@link ConditionalBlock#getThenSuccessor}, {@link ConditionalBlock#getElseSuccessor}, {@link + * ExceptionBlock#getExceptionalSuccessors}, {@link RegularBlock#getRegularSuccessor}) and + * predecessors (method {@link Block#getPredecessors}) of the entry and exit blocks. + */ public class ControlFlowGraph { /** The entry block of the control flow graph. */ diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/BlockImpl.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/BlockImpl.java index 12e6cbabad3a..ce90093297a8 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/BlockImpl.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/block/BlockImpl.java @@ -7,7 +7,7 @@ /** Base class of the {@link Block} implementation hierarchy. */ public abstract class BlockImpl implements Block { - /** A unique ID for this node. */ + /** A unique ID for this block. */ protected final long id = BlockImpl.uniqueID(); /** The last ID that has already been used. */ From 1234f8059e247b90d3f0dcab1cb478c4e934aa8e Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Fri, 31 Jul 2020 14:28:35 -0700 Subject: [PATCH 122/138] Make @DefaultFor and @DefaultQualifierForUse consistent --- changelog.txt | 5 ++ .../checker/lock/qual/GuardedBy.java | 15 +++- .../NullnessAnnotatedTypeFactory.java | 28 +++++-- .../checker/nullness/NullnessVisitor.java | 39 +++++++++ .../SignednessAnnotatedTypeFactory.java | 43 ++++------ .../checker/signedness/qual/Signed.java | 4 + .../checker/signedness/qual/Unsigned.java | 4 + checker/tests/lock/ChapterExamples.java | 2 - checker/tests/lock/Strings.java | 5 +- checker/tests/signedness/Arrays.java | 5 ++ checker/tests/signedness/CastedShifts.java | 24 +----- .../tests/signedness/DefaultsSignedness.java | 8 -- .../tests/signedness/LocalVarDefaults.java | 29 +++++++ checker/tests/signedness/PrimitiveCasts.java | 32 +++++++ .../common/basetype/BaseTypeValidator.java | 24 ++++-- .../framework/qual/UpperBoundFor.java | 2 +- .../type/GenericAnnotatedTypeFactory.java | 83 ++++++++++++++----- .../PropagationTreeAnnotator.java | 40 +++++++-- .../javacutil/TypesUtils.java | 16 ++++ 19 files changed, 303 insertions(+), 105 deletions(-) create mode 100644 checker/tests/signedness/Arrays.java create mode 100644 checker/tests/signedness/LocalVarDefaults.java create mode 100644 checker/tests/signedness/PrimitiveCasts.java diff --git a/changelog.txt b/changelog.txt index 8f0abaf5fc4d..1753aebad11a 100644 --- a/changelog.txt +++ b/changelog.txt @@ -18,6 +18,11 @@ Implementation details: commonAssignmentCheck() now takes an additional argument. Type system authors must update their overriding implementations. +Renamed GenericAnnotatedTypeFactory#addAnnotationsFromDefaultQualifierForUse to +#addAnnotationsFromDefaultForType and +BaseTypeValidator#shouldCheckTopLevelDeclaredType to +#shouldCheckTopLevelDeclaredOrPrimitiveType + Removed org.checkerframework.framework.test.FrameworkPer(Directory/File)Test classes. Use CheckerFrameworkPer(Directory/File)Test instead. diff --git a/checker/src/main/java/org/checkerframework/checker/lock/qual/GuardedBy.java b/checker/src/main/java/org/checkerframework/checker/lock/qual/GuardedBy.java index 31a8fc3330c8..5e7b74902c04 100644 --- a/checker/src/main/java/org/checkerframework/checker/lock/qual/GuardedBy.java +++ b/checker/src/main/java/org/checkerframework/checker/lock/qual/GuardedBy.java @@ -11,6 +11,7 @@ import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TypeKind; import org.checkerframework.framework.qual.TypeUseLocation; +import org.checkerframework.framework.qual.UpperBoundFor; /** * Indicates that a thread may dereference the value referred to by the annotated variable only if @@ -52,7 +53,19 @@ TypeKind.LONG, TypeKind.SHORT }, - types = {java.lang.String.class, Void.class}) + types = {String.class, Void.class}) +@UpperBoundFor( + typeKinds = { + TypeKind.BOOLEAN, + TypeKind.BYTE, + TypeKind.CHAR, + TypeKind.DOUBLE, + TypeKind.FLOAT, + TypeKind.INT, + TypeKind.LONG, + TypeKind.SHORT + }, + types = String.class) public @interface GuardedBy { /** * The Java value expressions that need to be held. diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessAnnotatedTypeFactory.java index b1f91db5088c..e4e056e64ebc 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessAnnotatedTypeFactory.java @@ -45,6 +45,8 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNoType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; import org.checkerframework.framework.type.QualifierHierarchy; import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator; @@ -356,14 +358,30 @@ public AnnotatedTypeMirror getMethodReturnType(MethodTree m, ReturnTree r) { } @Override - protected TypeAnnotator createTypeAnnotator() { + protected DefaultForTypeAnnotator createDefaultForTypeAnnotator() { DefaultForTypeAnnotator defaultForTypeAnnotator = new DefaultForTypeAnnotator(this); - defaultForTypeAnnotator.addAtmClass(AnnotatedTypeMirror.AnnotatedNoType.class, NONNULL); - defaultForTypeAnnotator.addAtmClass( - AnnotatedTypeMirror.AnnotatedPrimitiveType.class, NONNULL); + defaultForTypeAnnotator.addAtmClass(AnnotatedNoType.class, NONNULL); + defaultForTypeAnnotator.addAtmClass(AnnotatedPrimitiveType.class, NONNULL); + return defaultForTypeAnnotator; + } + + @Override + protected void addAnnotationsFromDefaultForType( + @Nullable Element element, AnnotatedTypeMirror type) { + if (element != null + && element.getKind() == ElementKind.LOCAL_VARIABLE + && type.getKind().isPrimitive()) { + // Always apply the DefaultQualifierForUse for primitives. + super.addAnnotationsFromDefaultForType(null, type); + } else { + super.addAnnotationsFromDefaultForType(element, type); + } + } + + @Override + protected TypeAnnotator createTypeAnnotator() { return new ListTypeAnnotator( new PropagationTypeAnnotator(this), - defaultForTypeAnnotator, new NullnessTypeAnnotator(this), new CommitmentTypeAnnotator(this)); } diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessVisitor.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessVisitor.java index 5d0210e89342..91ed8d9cfff0 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessVisitor.java @@ -41,7 +41,11 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.common.basetype.BaseTypeValidator; +import org.checkerframework.common.basetype.BaseTypeVisitor; +import org.checkerframework.common.basetype.TypeValidator; import org.checkerframework.framework.flow.CFCFGBuilder; +import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; @@ -648,4 +652,39 @@ public Void visitAnnotation(AnnotationTree node, Void p) { // All annotation arguments are non-null and initialized, so no need to check them. return null; } + + @Override + protected TypeValidator createTypeValidator() { + return new NullnessValidator(checker, this, atypeFactory); + } + + /** + * Check that primitive types are annotated with {@code @NonNull} even if they are the type of a + * local variable. + */ + private static class NullnessValidator extends BaseTypeValidator { + + /** + * Create NullnessValidator. + * + * @param checker checker + * @param visitor visitor + * @param atypeFactory factory + */ + public NullnessValidator( + BaseTypeChecker checker, + BaseTypeVisitor visitor, + AnnotatedTypeFactory atypeFactory) { + super(checker, visitor, atypeFactory); + } + + @Override + protected boolean shouldCheckTopLevelDeclaredOrPrimitiveType( + AnnotatedTypeMirror type, Tree tree) { + if (type.getKind().isPrimitive()) { + return true; + } + return super.shouldCheckTopLevelDeclaredOrPrimitiveType(type, tree); + } + } } diff --git a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessAnnotatedTypeFactory.java index d3b0d68e9df2..c2f947267cd8 100644 --- a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessAnnotatedTypeFactory.java @@ -6,9 +6,11 @@ import java.lang.annotation.Annotation; import java.util.Set; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.signedness.qual.Signed; import org.checkerframework.checker.signedness.qual.SignedPositive; import org.checkerframework.checker.signedness.qual.SignednessBottom; @@ -22,7 +24,6 @@ import org.checkerframework.common.value.qual.IntRangeFromNonNegative; import org.checkerframework.common.value.qual.IntRangeFromPositive; import org.checkerframework.common.value.util.Range; -import org.checkerframework.framework.qual.TypeUseLocation; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; @@ -33,7 +34,6 @@ import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator; import org.checkerframework.framework.type.treeannotator.PropagationTreeAnnotator; import org.checkerframework.framework.type.treeannotator.TreeAnnotator; -import org.checkerframework.framework.util.defaults.QualifierDefaults; import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.BugInCF; @@ -86,12 +86,6 @@ protected Set> createSupportedTypeQualifiers() { @Override protected void addComputedTypeAnnotations( Tree tree, AnnotatedTypeMirror type, boolean iUseFlow) { - // Prevent @ImplicitFor from applying to local variables of type byte, short, int, and long, - // but adding the top type to them, which permits flow-sensitive type refinement. - // (When it is possible to default types based on their TypeKinds, - // this whole method will no longer be needed.) - addUnknownSignednessToSomeLocals(tree, type); - if (!computingAnnotatedTypeMirrorOfLHS) { addSignednessGlbAnnotation(tree, type); } @@ -175,25 +169,6 @@ private void addSignednessGlbAnnotation(Tree tree, AnnotatedTypeMirror type) { } } - /** - * If the tree is a local variable and the type is byte, short, int, or long, then add the - * UnknownSignedness annotation so that dataflow can refine it. - */ - private void addUnknownSignednessToSomeLocals(Tree tree, AnnotatedTypeMirror type) { - switch (type.getKind()) { - case BYTE: - case SHORT: - case INT: - case LONG: - QualifierDefaults defaults = new QualifierDefaults(elements, this); - defaults.addCheckedCodeDefault(UNKNOWN_SIGNEDNESS, TypeUseLocation.LOCAL_VARIABLE); - defaults.annotate(tree, type); - break; - default: - // Nothing for other cases. - } - } - @Override protected TreeAnnotator createTreeAnnotator() { return new ListTreeAnnotator( @@ -253,6 +228,20 @@ public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMi } } + @Override + protected void addAnnotationsFromDefaultForType( + @Nullable Element element, AnnotatedTypeMirror type) { + if (TypesUtils.isFloating(type.getUnderlyingType()) + || TypesUtils.isBoxedFloating(type.getUnderlyingType()) + || type.getKind() == TypeKind.CHAR + || TypesUtils.isDeclaredOfName(type.getUnderlyingType(), "java.lang.Character")) { + // Floats are always signed and chars are always unsigned. + super.addAnnotationsFromDefaultForType(null, type); + } else { + super.addAnnotationsFromDefaultForType(element, type); + } + } + @Override protected TypeHierarchy createTypeHierarchy() { return new SignednessTypeHierarchy( diff --git a/checker/src/main/java/org/checkerframework/checker/signedness/qual/Signed.java b/checker/src/main/java/org/checkerframework/checker/signedness/qual/Signed.java index bf4270952661..377e79391ac7 100644 --- a/checker/src/main/java/org/checkerframework/checker/signedness/qual/Signed.java +++ b/checker/src/main/java/org/checkerframework/checker/signedness/qual/Signed.java @@ -8,6 +8,7 @@ import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TypeKind; +import org.checkerframework.framework.qual.UpperBoundFor; /** * The value is to be interpreted as signed. That is, if the most significant bit in the bitwise @@ -36,4 +37,7 @@ java.lang.Float.class, java.lang.Double.class }) +@UpperBoundFor( + typeKinds = {TypeKind.FLOAT, TypeKind.DOUBLE}, + types = {java.lang.Float.class, java.lang.Double.class}) public @interface Signed {} diff --git a/checker/src/main/java/org/checkerframework/checker/signedness/qual/Unsigned.java b/checker/src/main/java/org/checkerframework/checker/signedness/qual/Unsigned.java index fac6a9d7c926..3b8029b6996f 100644 --- a/checker/src/main/java/org/checkerframework/checker/signedness/qual/Unsigned.java +++ b/checker/src/main/java/org/checkerframework/checker/signedness/qual/Unsigned.java @@ -8,6 +8,7 @@ import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TypeKind; +import org.checkerframework.framework.qual.UpperBoundFor; /** * The value is to be interpreted as unsigned. That is, if the most significant bit in the bitwise @@ -23,4 +24,7 @@ @DefaultFor( typeKinds = {TypeKind.CHAR}, types = {java.lang.Character.class}) +@UpperBoundFor( + typeKinds = {TypeKind.CHAR}, + types = {java.lang.Character.class}) public @interface Unsigned {} diff --git a/checker/tests/lock/ChapterExamples.java b/checker/tests/lock/ChapterExamples.java index 20305c020f88..cdd316541ff5 100644 --- a/checker/tests/lock/ChapterExamples.java +++ b/checker/tests/lock/ChapterExamples.java @@ -442,7 +442,6 @@ void unboxing() { @GuardedBy("lock") Integer b = 1; int d; synchronized (lock) { - // :: error: (assignment.type.incompatible) d = b; // Expected, since b cannot be @GuardedBy("lock") since it is a boxed primitive. @@ -464,7 +463,6 @@ void unboxing() { c = new Integer(c.intValue() + b.intValue()); // The de-sugared version } - // :: error: (assignment.type.incompatible) a = b; b = c; // OK } diff --git a/checker/tests/lock/Strings.java b/checker/tests/lock/Strings.java index 1f021ef5b14d..568f4a8b51c5 100644 --- a/checker/tests/lock/Strings.java +++ b/checker/tests/lock/Strings.java @@ -8,17 +8,14 @@ public class Strings { final Object lock = new Object(); - // Tests that @GuardedBy({}) is @ImplicitFor(typeNames = { java.lang.String.class }) + // These casts are safe because if the casted Object is a String, it must be @GuardedBy({}) void StringIsGBnothing( @GuardedByUnknown Object o1, @GuardedBy("lock") Object o2, @GuardSatisfied Object o3, @GuardedByBottom Object o4) { - // :: error: (assignment.type.incompatible) String s1 = (String) o1; - // :: error: (assignment.type.incompatible) String s2 = (String) o2; - // :: error: (assignment.type.incompatible) String s3 = (String) o3; String s4 = (String) o4; // OK } diff --git a/checker/tests/signedness/Arrays.java b/checker/tests/signedness/Arrays.java new file mode 100644 index 000000000000..c60c1966c6e0 --- /dev/null +++ b/checker/tests/signedness/Arrays.java @@ -0,0 +1,5 @@ +public class Arrays { + void test() { + Object[] os = new Double[234]; + } +} diff --git a/checker/tests/signedness/CastedShifts.java b/checker/tests/signedness/CastedShifts.java index 89dfd16db88d..c849822e3bd2 100644 --- a/checker/tests/signedness/CastedShifts.java +++ b/checker/tests/signedness/CastedShifts.java @@ -36,19 +36,15 @@ public void CastedIntShifts(@Unsigned int unsigned, @Signed int signed) { byteRes = (@Signed byte) (signed >> 0); // Cast to char. - @UnknownSignedness char charRes; + char charRes; // Shifting right by 23, the introduced bits are cast away charRes = (@Unsigned char) (unsigned >>> 23); charRes = (@Unsigned char) (unsigned >> 23); - charRes = (@Signed char) (signed >>> 23); - charRes = (@Signed char) (signed >> 23); // Shifting right by 24, the introduced bits are still cast away. charRes = (@Unsigned char) (unsigned >>> 24); charRes = (@Unsigned char) (unsigned >> 24); - charRes = (@Signed char) (signed >>> 24); - charRes = (@Signed char) (signed >> 24); // Shifting right by 25, now the MSB matters. charRes = (@Unsigned char) (unsigned >>> 25); @@ -56,15 +52,9 @@ public void CastedIntShifts(@Unsigned int unsigned, @Signed int signed) { // :: error: (shift.signed) charRes = (@Unsigned char) (unsigned >> 25); - // :: error: (shift.unsigned) - charRes = (@Signed char) (signed >>> 25); - charRes = (@Signed char) (signed >> 25); - // Shifting right by zero should behave as assignment charRes = (@Unsigned char) (unsigned >>> 0); charRes = (@Unsigned char) (unsigned >> 0); - charRes = (@Signed char) (signed >>> 0); - charRes = (@Signed char) (signed >> 0); // Cast to short. @UnknownSignedness short shortRes; @@ -235,19 +225,15 @@ public void CastedLongShifts(@Unsigned long unsigned, @Signed long signed) { byteRes = (@Signed byte) (signed >> 0); // Cast to char. - @UnknownSignedness char charRes; + char charRes; // Shifting right by 55, the introduced bits are cast away charRes = (@Unsigned char) (unsigned >>> 55); charRes = (@Unsigned char) (unsigned >> 55); - charRes = (@Signed char) (signed >>> 55); - charRes = (@Signed char) (signed >> 55); // Shifting right by 56, the introduced bits are still cast away. charRes = (@Unsigned char) (unsigned >>> 56); charRes = (@Unsigned char) (unsigned >> 56); - charRes = (@Signed char) (signed >>> 56); - charRes = (@Signed char) (signed >> 56); // Shifting right by 57, now the MSB matters. charRes = (@Unsigned char) (unsigned >>> 57); @@ -255,15 +241,9 @@ public void CastedLongShifts(@Unsigned long unsigned, @Signed long signed) { // :: error: (shift.signed) charRes = (@Unsigned char) (unsigned >> 57); - // :: error: (shift.unsigned) - charRes = (@Signed char) (signed >>> 57); - charRes = (@Signed char) (signed >> 57); - // Shifting right by zero should behave as assignment charRes = (@Unsigned char) (unsigned >>> 0); charRes = (@Unsigned char) (unsigned >> 0); - charRes = (@Signed char) (signed >>> 0); - charRes = (@Signed char) (signed >> 0); // Cast to short. @UnknownSignedness short shortRes; diff --git a/checker/tests/signedness/DefaultsSignedness.java b/checker/tests/signedness/DefaultsSignedness.java index 338a7dd7e1c9..c91d9cb4ddbf 100644 --- a/checker/tests/signedness/DefaultsSignedness.java +++ b/checker/tests/signedness/DefaultsSignedness.java @@ -112,22 +112,14 @@ public void SignedTest( // Test floats @Signed float sinFloat; - @SignednessGlb float conFloat; sinFloat = testFloat; - // :: error: (assignment.type.incompatible) - conFloat = testFloat; - // Test doubles @Signed double sinDouble; - @SignednessGlb double conDouble; sinDouble = testDouble; - // :: error: (assignment.type.incompatible) - conDouble = testDouble; - /* // Test boxed bytes @Signed Byte sinBoxedByte; diff --git a/checker/tests/signedness/LocalVarDefaults.java b/checker/tests/signedness/LocalVarDefaults.java new file mode 100644 index 000000000000..e80bd6100461 --- /dev/null +++ b/checker/tests/signedness/LocalVarDefaults.java @@ -0,0 +1,29 @@ +import org.checkerframework.checker.signedness.qual.Signed; +import org.checkerframework.checker.signedness.qual.Unsigned; + +public class LocalVarDefaults { + + void methodInt(@Unsigned int unsignedInt, @Signed int signedInt) { + int local = unsignedInt; + int local2 = signedInt; + } + + // :: error: (type.invalid.annotations.on.use) + void methodDouble(@Unsigned double unsigned, @Signed double signed) { + // :: error: (assignment.type.incompatible) + double local = unsigned; + double local2 = signed; + } + + void methodInteger(@Unsigned Integer unsignedInt, @Signed Integer signedInt) { + Integer local = unsignedInt; + Integer local2 = signedInt; + } + + // :: error: (type.invalid.annotations.on.use) + void methodDoubleWrapper(@Unsigned Double unsigned, @Signed Double signed) { + // :: error: (assignment.type.incompatible) + Double local = unsigned; + Double local2 = signed; + } +} diff --git a/checker/tests/signedness/PrimitiveCasts.java b/checker/tests/signedness/PrimitiveCasts.java new file mode 100644 index 000000000000..895b04e19a41 --- /dev/null +++ b/checker/tests/signedness/PrimitiveCasts.java @@ -0,0 +1,32 @@ +import org.checkerframework.checker.signedness.qual.Unsigned; + +public class PrimitiveCasts { + + void shortToChar1(short s) { + // :: warning: (cast.unsafe) + char c = (char) s; + } + + // These are Java errors. + // void shortToChar2(short s) { + // char c = s; + // } + // char shortToChar3(short s) { + // return s; + // } + + void intToDouble1(@Unsigned int ui) { + // :: warning: (cast.unsafe) + double d = (double) ui; + } + + void intToDouble2(@Unsigned int ui) { + // :: error: (assignment.type.incompatible) + double d = ui; + } + + double intToDouble3(@Unsigned int ui) { + // :: error: (return.type.incompatible) + return ui; + } +} diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java index dd62c34a6619..135bd068a376 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java @@ -41,7 +41,7 @@ public class BaseTypeValidator extends AnnotatedTypeScanner implemen protected boolean isValid = true; /** Should the primary annotation on the top level type be checked? */ - protected boolean checkTopLevelDeclaredType = true; + protected boolean checkTopLevelDeclaredOrPrimitiveType = true; /** BaseTypeChecker. */ protected final BaseTypeChecker checker; @@ -80,22 +80,27 @@ public boolean isValid(AnnotatedTypeMirror type, Tree tree) { return false; } this.isValid = true; - this.checkTopLevelDeclaredType = shouldCheckTopLevelDeclaredType(type, tree); + this.checkTopLevelDeclaredOrPrimitiveType = + shouldCheckTopLevelDeclaredOrPrimitiveType(type, tree); visit(type, tree); return this.isValid; } /** - * Should the top-level declared type be checked? + * Should the top-level declared or primitive type be checked? + * + *

    If {@code type} is not a declared or primitive type, then this method returns true. * *

    Top-level type is not checked if tree is a local variable or an expression tree. * * @param type AnnotatedTypeMirror being validated * @param tree a Tree whose type is {@code type} - * @return whether or not the top-level type should be checked + * @return whether or not the top-level type should be checked, if {@code type} is a declared or + * primitive type. */ - protected boolean shouldCheckTopLevelDeclaredType(AnnotatedTypeMirror type, Tree tree) { - if (type.getKind() != TypeKind.DECLARED) { + protected boolean shouldCheckTopLevelDeclaredOrPrimitiveType( + AnnotatedTypeMirror type, Tree tree) { + if (type.getKind() != TypeKind.DECLARED && !type.getKind().isPrimitive()) { return true; } return !TreeUtils.isLocalVariable(tree) @@ -253,7 +258,7 @@ public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) { final boolean skipChecks = checker.shouldSkipUses(type.getUnderlyingType().asElement()); - if (checkTopLevelDeclaredType && !skipChecks) { + if (checkTopLevelDeclaredOrPrimitiveType && !skipChecks) { // Ensure that type use is a subtype of the element type // isValidUse determines the erasure of the types. @@ -270,7 +275,7 @@ public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) { } // Set checkTopLevelDeclaredType to true, because the next time visitDeclared is called, // the type isn't the top level, so always do the check. - checkTopLevelDeclaredType = true; + checkTopLevelDeclaredOrPrimitiveType = true; /* * Try to reconstruct the ParameterizedTypeTree from the given tree. @@ -403,7 +408,8 @@ private Pair extractParameterizedT @Override public Void visitPrimitive(AnnotatedPrimitiveType type, Tree tree) { - if (checker.shouldSkipUses(type.getUnderlyingType().toString())) { + if (!checkTopLevelDeclaredOrPrimitiveType + || checker.shouldSkipUses(type.getUnderlyingType().toString())) { return super.visitPrimitive(type, tree); } diff --git a/framework/src/main/java/org/checkerframework/framework/qual/UpperBoundFor.java b/framework/src/main/java/org/checkerframework/framework/qual/UpperBoundFor.java index 1e46e606130a..98aebcd7658e 100644 --- a/framework/src/main/java/org/checkerframework/framework/qual/UpperBoundFor.java +++ b/framework/src/main/java/org/checkerframework/framework/qual/UpperBoundFor.java @@ -43,7 +43,7 @@ * Returns {@link Class}es that should get an upper bound. The meta-annotated annotation is the * upper bound. * - * @return {@link Class}es that get an upper bound. + * @return {@link Class}es that get an upper bound */ Class[] types() default {}; } 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 7236a2dda09a..cca17d534744 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java @@ -124,9 +124,12 @@ public abstract class GenericAnnotatedTypeFactory< /** to annotate types based on the given tree */ protected TypeAnnotator typeAnnotator; - /** for use in addAnnotationsFromDefaultQualifierForUse */ + /** for use in addAnnotationsFromDefaultForType */ private DefaultQualifierForUseTypeAnnotator defaultQualifierForUseTypeAnnotator; + /** for use in addAnnotationsFromDefaultForType */ + private DefaultForTypeAnnotator defaultForTypeAnnotator; + /** to annotate types based on the given un-annotated types */ protected TreeAnnotator treeAnnotator; @@ -266,6 +269,7 @@ protected void postInit() { this.treeAnnotator = createTreeAnnotator(); this.typeAnnotator = createTypeAnnotator(); this.defaultQualifierForUseTypeAnnotator = createDefaultForUseTypeAnnotator(); + this.defaultForTypeAnnotator = createDefaultForTypeAnnotator(); this.poly = createQualifierPolymorphism(); @@ -383,8 +387,6 @@ protected TreeAnnotator createTreeAnnotator() { *

  • {@link IrrelevantTypeAnnotator}: Adds top to types not listed in the {@link * RelevantJavaTypes} annotation on the checker. *
  • {@link PropagationTypeAnnotator}: Propagates annotation onto wildcards. - *
  • {@link DefaultForTypeAnnotator}: Adds annotations based on {@link DefaultFor} - * meta-annotations. * * * @return a type annotator @@ -401,15 +403,27 @@ protected TypeAnnotator createTypeAnnotator() { this, getQualifierHierarchy().getTopAnnotations(), relevantClasses)); } typeAnnotators.add(new PropagationTypeAnnotator(this)); - typeAnnotators.add(new DefaultForTypeAnnotator(this)); return new ListTypeAnnotator(typeAnnotators); } - /** Creates an {@link DefaultQualifierForUseTypeAnnotator}. */ + /** + * Creates an {@link DefaultQualifierForUseTypeAnnotator}. + * + * @return a new {@link DefaultQualifierForUseTypeAnnotator} + */ protected DefaultQualifierForUseTypeAnnotator createDefaultForUseTypeAnnotator() { return new DefaultQualifierForUseTypeAnnotator(this); } + /** + * Creates an {@link DefaultForTypeAnnotator}. + * + * @return a new {@link DefaultForTypeAnnotator} + */ + protected DefaultForTypeAnnotator createDefaultForTypeAnnotator() { + return new DefaultForTypeAnnotator(this); + } + /** * Returns the appropriate flow analysis class that is used for the * org.checkerframework.dataflow analysis. @@ -1522,7 +1536,7 @@ public AnnotatedTypeMirror getMethodReturnType(MethodTree m, ReturnTree r) { @Override public void addDefaultAnnotations(AnnotatedTypeMirror type) { - addAnnotationsFromDefaultQualifierForUse(null, type); + addAnnotationsFromDefaultForType(null, type); typeAnnotator.visit(type, null); defaults.annotate((Element) null, type); } @@ -1541,6 +1555,10 @@ protected final void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror t /** * Like {@link #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)}. Overriding * implementations typically simply pass the boolean to calls to super. + * + * @param tree an AST node + * @param type the type obtained from tree + * @param iUseFlow whether to use information from dataflow analysis */ protected void addComputedTypeAnnotations( Tree tree, AnnotatedTypeMirror type, boolean iUseFlow) { @@ -1548,9 +1566,18 @@ protected void addComputedTypeAnnotations( : "GenericAnnotatedTypeFactory.addComputedTypeAnnotations: " + " root needs to be set when used on trees; factory: " + this.getClass(); - addAnnotationsFromDefaultQualifierForUse(TreeUtils.elementFromTree(tree), type); + + if (!TreeUtils.isExpressionTree(tree)) { + // Don't apply defaults to expressions. Their types may be computed from subexpressions + // in treeAnnotator. + addAnnotationsFromDefaultForType(TreeUtils.elementFromTree(tree), type); + } applyQualifierParameterDefaults(tree, type); treeAnnotator.visit(tree, type); + if (TreeUtils.isExpressionTree(tree)) { + // If a tree annotator, did not add a type, add the DefaultForUse default. + addAnnotationsFromDefaultForType(TreeUtils.elementFromTree(tree), type); + } typeAnnotator.visit(type, null); defaults.annotate(tree, type); @@ -1761,7 +1788,7 @@ private void applyLocalVariableQualifierParameterDefaults( */ @Override public void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) { - addAnnotationsFromDefaultQualifierForUse(elt, type); + addAnnotationsFromDefaultForType(elt, type); applyQualifierParameterDefaults(elt, type); typeAnnotator.visit(type, null); defaults.annotate(elt, type); @@ -1843,6 +1870,11 @@ public boolean getShouldDefaultTypeVarLocals() { /** The CFGVisualizer to be used by all CFAbstractAnalysis instances. */ protected final CFGVisualizer cfgVisualizer; + /** + * Create a new CFGVisualizer. + * + * @return a new CFGVisualizer + */ protected CFGVisualizer createCFGVisualizer() { if (checker.hasOption("flowdotdir")) { String flowdotdir = checker.getOption("flowdotdir"); @@ -1942,25 +1974,36 @@ public void postAsMemberOf( * Adds default qualifiers bases on the underlying type of {@code type} to {@code type}. If * {@code element} is a local variable, then the defaults are not added. * + *

    (This uses both the {@link DefaultQualifierForUseTypeAnnotator} and {@link + * DefaultForTypeAnnotator}.) + * * @param element possibly null element whose type is {@code type} * @param type the type to which defaults are added */ - protected void addAnnotationsFromDefaultQualifierForUse( + protected void addAnnotationsFromDefaultForType( @Nullable Element element, AnnotatedTypeMirror type) { - if (element != null - && element.getKind() == ElementKind.LOCAL_VARIABLE - && type.getKind() == TypeKind.DECLARED) { - // If this is a type for a local variable, don't apply the default to the primary - // location. - AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) type; - if (declaredType.getEnclosingType() != null) { - defaultQualifierForUseTypeAnnotator.visit(declaredType.getEnclosingType()); - } - for (AnnotatedTypeMirror typeArg : declaredType.getTypeArguments()) { - defaultQualifierForUseTypeAnnotator.visit(typeArg); + if (element != null && element.getKind() == ElementKind.LOCAL_VARIABLE) { + if (type.getKind() == TypeKind.DECLARED) { + // If this is a type for a local variable, don't apply the default to the primary + // location. + AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) type; + if (declaredType.getEnclosingType() != null) { + defaultQualifierForUseTypeAnnotator.visit(declaredType.getEnclosingType()); + defaultForTypeAnnotator.visit(declaredType.getEnclosingType()); + } + for (AnnotatedTypeMirror typeArg : declaredType.getTypeArguments()) { + defaultQualifierForUseTypeAnnotator.visit(typeArg); + defaultForTypeAnnotator.visit(typeArg); + } + } else if (type.getKind().isPrimitive()) { + // Don't apply the default for local variables with primitive types. + } else { + defaultQualifierForUseTypeAnnotator.visit(type); + defaultForTypeAnnotator.visit(type); } } else { defaultQualifierForUseTypeAnnotator.visit(type); + defaultForTypeAnnotator.visit(type); } } } diff --git a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/PropagationTreeAnnotator.java b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/PropagationTreeAnnotator.java index 67f75e01e55a..0e26f34d25c7 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/treeannotator/PropagationTreeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/framework/type/treeannotator/PropagationTreeAnnotator.java @@ -7,7 +7,6 @@ import com.sun.source.tree.Tree; import com.sun.source.tree.TypeCastTree; import com.sun.source.tree.UnaryTree; -import java.util.Collection; import java.util.Set; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.TypeKind; @@ -15,6 +14,7 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.QualifierHierarchy; +import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.Pair; /** @@ -47,7 +47,7 @@ public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { AnnotatedTypeMirror componentType = ((AnnotatedArrayType) type).getComponentType(); // prev is the lub of the initializers if they exist, otherwise the current component type. - Collection prev = null; + Set prev = null; if (tree.getInitializers() != null && !tree.getInitializers().isEmpty()) { // We have initializers, either with or without an array type. @@ -56,7 +56,7 @@ public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { for (ExpressionTree init : tree.getInitializers()) { AnnotatedTypeMirror initType = atypeFactory.getAnnotatedType(init); // initType might be a typeVariable, so use effectiveAnnotations. - Collection annos = initType.getEffectiveAnnotations(); + Set annos = initType.getEffectiveAnnotations(); prev = (prev == null) ? annos : qualHierarchy.leastUpperBounds(prev, annos); } @@ -69,7 +69,7 @@ public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { Pair context = atypeFactory.getVisitorState().getAssignmentContext(); - Collection post; + Set post; if (context != null && context.second != null @@ -106,7 +106,7 @@ public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { } // TODO (issue #599): This only works at the top level. It should work at all levels of // the array. - componentType.addMissingAnnotations(post); + addAnnoOrBound(componentType, post); return null; } @@ -190,8 +190,13 @@ public Void visitTypeCast(TypeCastTree node, AnnotatedTypeMirror type) { } else { // Use effective annotations from the expression, to get upper bound // of type variables. - type.addMissingAnnotations(exprType.getEffectiveAnnotations()); + Set expressionAnnos = exprType.getEffectiveAnnotations(); + // If the qualifier on the expression type is a supertype of the qualifier upper bound + // of the cast type, then apply the bound as the default qualifier rather than the + // expression qualifier. + addAnnoOrBound(type, expressionAnnos); } + return null; } @@ -204,4 +209,27 @@ private boolean hasPrimaryAnnotationInAllHierarchies(AnnotatedTypeMirror type) { } return annotated; } + + /** + * Adds the qualifiers in {@code annos} to {@code type} that are below the qualifier upper bound + * of type and for which type does not already have annotation in the same hierarchy. If a + * qualifier in {@code annos} is above the bound, then the bound is added to {@code type} + * instead. + * + * @param type annotations are added to this type + * @param annos annotations to add to type + */ + private void addAnnoOrBound(AnnotatedTypeMirror type, Set annos) { + Set boundAnnos = + atypeFactory.getQualifierUpperBounds().getBoundQualifiers(type.getUnderlyingType()); + Set annosToAdd = AnnotationUtils.createAnnotationSet(); + for (AnnotationMirror boundAnno : boundAnnos) { + AnnotationMirror anno = qualHierarchy.findAnnotationInSameHierarchy(annos, boundAnno); + if (anno != null && !qualHierarchy.isSubtype(anno, boundAnno)) { + annosToAdd.add(boundAnno); + } + } + type.addMissingAnnotations(annosToAdd); + type.addMissingAnnotations(annos); + } } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java index 70356b8e4a43..42f7e1749a1b 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java @@ -248,9 +248,25 @@ public static boolean isIntegral(TypeMirror type) { } } + /** + * Returns true iff the argument is a boxed floating point type. + * + * @param type type to test + * @return whether the argument is a boxed floating point type + */ + public static boolean isBoxedFloating(TypeMirror type) { + if (type.getKind() != TypeKind.DECLARED) { + return false; + } + + String qualifiedName = getQualifiedName((DeclaredType) type).toString(); + return qualifiedName.equals("java.lang.Double") || qualifiedName.equals("java.lang.Float"); + } + /** * Returns true iff the argument is a floating point type. * + * @param type type mirror * @return whether the argument is a floating point type */ public static boolean isFloating(TypeMirror type) { From 299c19132978731b31844a0123f26e1fd50688c2 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Fri, 31 Jul 2020 17:15:35 -0700 Subject: [PATCH 123/138] Run Error Prone on every compile --- build.gradle | 79 ++++++++++++++++------------------ checker/bin-devel/test-misc.sh | 3 -- 2 files changed, 38 insertions(+), 44 deletions(-) diff --git a/build.gradle b/build.gradle index 3130966dc466..7389a77ba86e 100644 --- a/build.gradle +++ b/build.gradle @@ -220,8 +220,43 @@ allprojects { if (isJava8) { options.forkOptions.jvmArgs += ["-Xbootclasspath/p:${configurations.javacJar.asPath}"] } - // Don't use error-prone by default - options.errorprone.enabled = false + + // Error prone depends on checker-qual.jar, so don't run it on that project to avoid a circular dependency. + // All the classes in checker-qual are also in checker, so they are checked. + // TODO: enable Error Prone on test classes. + if (name.is('compileJava') && !project.name.startsWith('checker-qual')){ + println "${project.name} ${name}" + // Error Prone must be available in the annotation processor path + options.annotationProcessorPath = configurations.errorprone + // Enable Error Prone + options.errorprone.enabled = true + options.errorprone.disableWarningsInGeneratedCode = true + options.errorprone.errorproneArgs = [ + // Many compiler classes are interned. + '-Xep:ReferenceEquality:OFF', + // These might be worth fixing. + '-Xep:DefaultCharset:OFF', + // Not useful to suggest Splitter; maybe clean up. + '-Xep:StringSplitter:OFF', + // Too broad, rejects seemingly-correct code. + '-Xep:EqualsGetClass:OFF', + // Not a real problem + '-Xep:MixedMutabilityReturnType:OFF', + // Don't want to add a dependency to ErrorProne. + '-Xep:AnnotateFormatMethod:OFF', + // Warns for every use of "@checker_framework.manual" + '-Xep:InvalidBlockTag:OFF', + // @SuppressWarnings doesn't work: https://github.com/google/error-prone/issues/1650 + '-Xep:InlineFormatString:OFF', + // -Werror halts the build if Error Prone issues a warning, which ensures that + // the errors get fixed. On the downside, Error Prone (or maybe the compiler?) + // stops as soon as it issues one warning, rather than outputting them all. + // https://github.com/google/error-prone/issues/436 + '-Werror', + ] + } else { + options.errorprone.enabled = false + } } } } @@ -530,7 +565,7 @@ subprojects { // https://mvnrepository.com/artifact/com.google.errorprone/error_prone_core // If you update this: // * Temporarily comment out "-Werror" elsewhere in this file - // * Repeatedly run `./gradlew clean runErrorProne` and fix all errors + // * Repeatedly run `./gradlew clean compileJava` and fix all errors // * Uncomment "-Werror" errorprone group: 'com.google.errorprone', name: 'error_prone_core', version: '2.4.0' } @@ -765,44 +800,6 @@ subprojects { } } - // Create a runErrorProne task. - tasks.create(name: 'runErrorProne', type: JavaCompile, group: 'Verification') { - description 'Run the error-prone compiler on the main sources' - - source = sourceSets.main.java.asFileTree - classpath = sourceSets.main.compileClasspath.asFileTree - destinationDir = new File("${buildDir}", 'errorprone') - - // Error Prone must be available in the annotation processor path - options.annotationProcessorPath = configurations.errorprone - // Enable Error Prone - options.errorprone.enabled = true - options.errorprone.disableWarningsInGeneratedCode = true - options.errorprone.errorproneArgs = [ - // Many compiler classes are interned. - '-Xep:ReferenceEquality:OFF', - // These might be worth fixing. - '-Xep:DefaultCharset:OFF', - // Not useful to suggest Splitter; maybe clean up. - '-Xep:StringSplitter:OFF', - // Too broad, rejects seemingly-correct code. - '-Xep:EqualsGetClass:OFF', - // Not a real problem - '-Xep:MixedMutabilityReturnType:OFF', - // Don't want to add a dependency to ErrorProne. - '-Xep:AnnotateFormatMethod:OFF', - // Warns for every use of "@checker_framework.manual" - '-Xep:InvalidBlockTag:OFF', - // @SuppressWarnings doesn't work: https://github.com/google/error-prone/issues/1650 - '-Xep:InlineFormatString:OFF', - // -Werror halts the build if Error Prone issues a warning, which ensures that - // the errors get fixed. On the downside, Error Prone (or maybe the compiler?) - // stops as soon as it one warning, rather than outputting them all. - // https://github.com/google/error-prone/issues/436 - '-Werror', - ] - } - // Create a nonJunitTests task per project tasks.create(name: 'nonJunitTests', group: 'Verification') { description 'Run all Checker Framework tests except for the Junit tests.' diff --git a/checker/bin-devel/test-misc.sh b/checker/bin-devel/test-misc.sh index d11c16e3af8b..d9fac8508e9f 100755 --- a/checker/bin-devel/test-misc.sh +++ b/checker/bin-devel/test-misc.sh @@ -15,9 +15,6 @@ source "$SCRIPTDIR"/build.sh # Code style and formatting ./gradlew checkBasicStyle checkFormat --console=plain --warning-mode=all --no-daemon -# Run error-prone -./gradlew runErrorProne --console=plain --warning-mode=all --no-daemon - # HTML legality ./gradlew htmlValidate --console=plain --warning-mode=all --no-daemon From 427229460f13a35d573ac1bb8ae1613798d05821 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 1 Aug 2020 07:10:03 -0700 Subject: [PATCH 124/138] Change block labels from long to Object; this makes formatting more flexible --- .../dataflow/cfg/AbstractCFGVisualizer.java | 6 +++--- .../dataflow/cfg/DOTCFGVisualizer.java | 11 +++++++++-- .../dataflow/cfg/StringCFGVisualizer.java | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/AbstractCFGVisualizer.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/AbstractCFGVisualizer.java index ed72760135f0..a315e1bf0074 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/AbstractCFGVisualizer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/AbstractCFGVisualizer.java @@ -470,12 +470,12 @@ protected abstract String visualizeNodes( /** * Generate the String representation of an edge. * - * @param sId the ID of current block - * @param eId the ID of successor block + * @param sId a representation of the current block, such as its ID + * @param eId a representation of the successor block, such as its ID * @param flowRule the content of the edge * @return the String representation of the edge */ - protected abstract String addEdge(long sId, long eId, String flowRule); + protected abstract String addEdge(Object sId, Object eId, String flowRule); /** * Return the header of the generated graph. diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/DOTCFGVisualizer.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/DOTCFGVisualizer.java index 0245c21f451a..30446274b9be 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/DOTCFGVisualizer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/DOTCFGVisualizer.java @@ -125,8 +125,15 @@ public String visualizeNodes( } @Override - protected String addEdge(long sId, long eId, String flowRule) { - return " " + sId + " -> " + eId + " [label=\"" + flowRule + "\"];" + lineSeparator; + protected String addEdge(Object sId, Object eId, String flowRule) { + return " " + + format(sId) + + " -> " + + format(eId) + + " [label=\"" + + flowRule + + "\"];" + + lineSeparator; } @Override diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/StringCFGVisualizer.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/StringCFGVisualizer.java index 393b369927ba..1843c0ab1d9e 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/StringCFGVisualizer.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/StringCFGVisualizer.java @@ -57,7 +57,7 @@ public String visualizeNodes( } @Override - protected String addEdge(long sId, long eId, String flowRule) { + protected String addEdge(Object sId, Object eId, String flowRule) { if (this.verbose) { return sId + " -> " + eId + " " + flowRule + lineSeparator; } From 627c92170c465fc5c5237927ab87e21bb1ddc6d2 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 1 Aug 2020 09:50:25 -0700 Subject: [PATCH 125/138] Another justification for annotating an entire file --- docs/manual/annotating-libraries.tex | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/manual/annotating-libraries.tex b/docs/manual/annotating-libraries.tex index de5af8cbf4c8..e61332c795e3 100644 --- a/docs/manual/annotating-libraries.tex +++ b/docs/manual/annotating-libraries.tex @@ -207,6 +207,10 @@ or field. In either case it is usually necessary to understand the entire class's design and implementation. Once you have done that, you might as well annotate the whole thing. +\item + If you fully annotate the file, it is possible to type-check the library to + verify the annotations. (Even if you do not do this right now, it eases + the task in the future.) \end{itemize} From ee56f9964e18869d5c4c5c44951046286bff2a04 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sun, 2 Aug 2020 07:54:23 -0700 Subject: [PATCH 126/138] Don't use deprecated class --- .../jtreg/nullness/defaultsPersist/ReferenceInfoUtil.java | 6 +++--- checker/jtreg/nullness/inheritDeclAnnoPersist/Extends.java | 6 +++--- .../jtreg/nullness/inheritDeclAnnoPersist/Implements.java | 6 +++--- .../nullness/inheritDeclAnnoPersist/ReferenceInfoUtil.java | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/checker/jtreg/nullness/defaultsPersist/ReferenceInfoUtil.java b/checker/jtreg/nullness/defaultsPersist/ReferenceInfoUtil.java index 1743208fbc72..9431e984ae72 100644 --- a/checker/jtreg/nullness/defaultsPersist/ReferenceInfoUtil.java +++ b/checker/jtreg/nullness/defaultsPersist/ReferenceInfoUtil.java @@ -15,7 +15,7 @@ import java.util.Arrays; import java.util.List; import org.checkerframework.javacutil.Pair; -import org.checkerframework.javacutil.PluginUtil; +import org.checkerframework.javacutil.SystemUtil; public class ReferenceInfoUtil { @@ -159,7 +159,7 @@ && areEquals(p1.type_index, p2.type_index) public static String positionCompareStr( TypeAnnotation.Position p1, TypeAnnotation.Position p2) { - return PluginUtil.joinLines( + return SystemUtil.joinLines( "type = " + p1.type + ", " + p2.type, "offset = " + p1.offset + ", " + p2.offset, "lvarOffset = " + p1.lvarOffset + ", " + p2.lvarOffset, @@ -234,7 +234,7 @@ public ComparisonException( } public String toString() { - return PluginUtil.joinLines( + return SystemUtil.joinLines( super.toString(), "\tExpected: " + expected.size() diff --git a/checker/jtreg/nullness/inheritDeclAnnoPersist/Extends.java b/checker/jtreg/nullness/inheritDeclAnnoPersist/Extends.java index f895a3027aac..20b905c97b5d 100644 --- a/checker/jtreg/nullness/inheritDeclAnnoPersist/Extends.java +++ b/checker/jtreg/nullness/inheritDeclAnnoPersist/Extends.java @@ -1,4 +1,4 @@ -import org.checkerframework.javacutil.PluginUtil; +import org.checkerframework.javacutil.SystemUtil; /* * @test @@ -37,7 +37,7 @@ public String m3() { class TestWrapper { public static String wrap(String... method) { - return PluginUtil.joinLines( - "class Test extends Super {", PluginUtil.joinLines(method), "}"); + return SystemUtil.joinLines( + "class Test extends Super {", SystemUtil.joinLines(method), "}"); } } diff --git a/checker/jtreg/nullness/inheritDeclAnnoPersist/Implements.java b/checker/jtreg/nullness/inheritDeclAnnoPersist/Implements.java index 247916f2b7ed..55b5ce89697c 100644 --- a/checker/jtreg/nullness/inheritDeclAnnoPersist/Implements.java +++ b/checker/jtreg/nullness/inheritDeclAnnoPersist/Implements.java @@ -1,4 +1,4 @@ -import org.checkerframework.javacutil.PluginUtil; +import org.checkerframework.javacutil.SystemUtil; /* * @test @@ -23,7 +23,7 @@ public String m1() { class TestWrapper { public static String wrap(String... method) { - return PluginUtil.joinLines( - "class Test extends AbstractClass {", PluginUtil.joinLines(method), "}"); + return SystemUtil.joinLines( + "class Test extends AbstractClass {", SystemUtil.joinLines(method), "}"); } } diff --git a/checker/jtreg/nullness/inheritDeclAnnoPersist/ReferenceInfoUtil.java b/checker/jtreg/nullness/inheritDeclAnnoPersist/ReferenceInfoUtil.java index 02c9c8256e00..e3d6172521a7 100644 --- a/checker/jtreg/nullness/inheritDeclAnnoPersist/ReferenceInfoUtil.java +++ b/checker/jtreg/nullness/inheritDeclAnnoPersist/ReferenceInfoUtil.java @@ -12,7 +12,7 @@ import java.util.ArrayList; import java.util.List; import java.util.StringJoiner; -import org.checkerframework.javacutil.PluginUtil; +import org.checkerframework.javacutil.SystemUtil; public class ReferenceInfoUtil { @@ -123,7 +123,7 @@ public String toString() { throw new RuntimeException(e); } } - return PluginUtil.joinLines( + return SystemUtil.joinLines( super.toString(), "\tExpected: " + expected.size() From b2cc961f6a8dfaf855ecfbf287845eceafded0f2 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 3 Aug 2020 06:45:54 -0700 Subject: [PATCH 127/138] Improve documentation --- .../org/checkerframework/common/basetype/BaseTypeChecker.java | 2 +- .../checkerframework/framework/util/ComponentFinderUtil.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java index 499e119c9833..40f3dfd5d7ab 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java @@ -218,7 +218,7 @@ protected BaseTypeVisitor createSourceVisitor() { /** * Returns the name of a class related to a given one, by replacing "Checker" or "Subchecker" by - * {@code replacement}. + * {@code replacement}. The class is not guaranteed to exist; this method just returns a name. * * @param checkerClass the checker class * @param replacement the string to replace "Checker" or "Subchecker" by diff --git a/framework/src/main/java/org/checkerframework/framework/util/ComponentFinderUtil.java b/framework/src/main/java/org/checkerframework/framework/util/ComponentFinderUtil.java index 8d21ea0ff615..cb35c974c16f 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/ComponentFinderUtil.java +++ b/framework/src/main/java/org/checkerframework/framework/util/ComponentFinderUtil.java @@ -29,7 +29,7 @@ public interface DefaultGetter { /** * Find a component named with the checker naming convention. * - * @param type of the component + * @param type of the component to return * @param checker the current checker * @param replacement the component suffix, e.g. "Visitor" or "AnnotatedTypeFactory" * @param defaultGetter a getter for the default value when cannot find the class @@ -65,7 +65,7 @@ public static T find( * Find a component named with the checker naming convention with its constructor invoked with * the only argument {@code checker} * - * @param type of the component + * @param type of the component to return * @param checker a checker * @param replacement the component suffix, e.g. "Visitor" or "AnnotatedTypeFactory" * @param defaultGetter a getter for the default value when cannot find the class From e16123bad280635f4fad93ea7b827467b486d0ff Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 3 Aug 2020 07:05:29 -0700 Subject: [PATCH 128/138] Bump plume-util from 1.1.4 to 1.1.5 --- framework/build.gradle | 2 +- javacutil/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/build.gradle b/framework/build.gradle index a0737a00ede5..9e296c139d3d 100644 --- a/framework/build.gradle +++ b/framework/build.gradle @@ -33,7 +33,7 @@ dependencies { exclude group: 'com.google.errorprone', module: 'javac' } implementation project(':checker-qual') - implementation 'org.plumelib:plume-util:1.1.4' + implementation 'org.plumelib:plume-util:1.1.5' implementation 'org.plumelib:reflection-util:0.2.2' // TODO: org.checkerframework.annotatedlib:guava:28.2-jre requires the below dependency. diff --git a/javacutil/build.gradle b/javacutil/build.gradle index b1c19285f2a8..eda180c68a5e 100644 --- a/javacutil/build.gradle +++ b/javacutil/build.gradle @@ -6,7 +6,7 @@ dependencies { // This is used by org.checkerframework.javacutil.TypesUtils.isImmutableTypeInJdk. // https://mvnrepository.com/artifact/org.plumelib/plume-util // Keep version number in sync with docs/developer/release/maven-artifacts/javacutil-pom.xml. - implementation 'org.plumelib:plume-util:1.1.4' + implementation 'org.plumelib:plume-util:1.1.5' implementation project(':checker-qual') } From c9c65d1987378749539fd353e59faf0b0cf23178 Mon Sep 17 00:00:00 2001 From: Priti Chattopadhyay <35490584+PRITI1999@users.noreply.github.com> Date: Mon, 3 Aug 2020 20:27:01 +0530 Subject: [PATCH 129/138] Propagate length information from argument to return value of Arrays.copyOf() (#3524) --- checker/tests/index/Issue3224.java | 40 +++++++++++++++++++ .../common/value/ValueMethodIdentifier.java | 19 +++++++++ .../common/value/ValueTreeAnnotator.java | 17 ++++++++ 3 files changed, 76 insertions(+) create mode 100644 checker/tests/index/Issue3224.java diff --git a/checker/tests/index/Issue3224.java b/checker/tests/index/Issue3224.java new file mode 100644 index 000000000000..b7b3f3b13225 --- /dev/null +++ b/checker/tests/index/Issue3224.java @@ -0,0 +1,40 @@ +// Test case for https://tinyurl.com/cfissue/3224 + +import java.util.Arrays; +import org.checkerframework.common.value.qual.IntRange; +import org.checkerframework.common.value.qual.MinLen; + +public class Issue3224 { + static class Arrays { + static String[] copyOf(String[] args, int length) { + return args; + } + } + + public static void m1(String @MinLen(1) [] args) { + int i = 4; + String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, i); + } + + public static void m2(String @MinLen(1) [] args) { + String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, args.length); + } + + public static void m3(String @MinLen(1) ... args) { + String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, args.length); + } + + public static void m4(String @MinLen(1) [] args, @IntRange(from = 10, to = 200) int len) { + String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, len); + } + + public static void m5(String @MinLen(1) [] args, String[] otherArray) { + // :: error: assignment.type.incompatible + String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, otherArray.length); + } + + public static void m6(String @MinLen(1) [] args) { + // :: error: assignment.type.incompatible + String @MinLen(1) [] args2 = Arrays.copyOf(args, args.length); + } +} diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueMethodIdentifier.java b/framework/src/main/java/org/checkerframework/common/value/ValueMethodIdentifier.java index d8f5bf7b6063..e410c1c7a07d 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueMethodIdentifier.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueMethodIdentifier.java @@ -19,13 +19,21 @@ class ValueMethodIdentifier { private final List mathMinMethod; /** The {@code java.lang.Math#max()} methods. */ private final List mathMaxMethod; + /** Arrays.copyOf() methods. */ + private final List copyOfMethod; + /** + * Initialize elements with methods that have special handling in the value checker + * + * @param processingEnv the processing environment + */ public ValueMethodIdentifier(ProcessingEnvironment processingEnv) { lengthMethod = TreeUtils.getMethod("java.lang.String", "length", 0, processingEnv); startsWithMethod = TreeUtils.getMethod("java.lang.String", "startsWith", 1, processingEnv); endsWithMethod = TreeUtils.getMethod("java.lang.String", "endsWith", 1, processingEnv); mathMinMethod = TreeUtils.getMethods("java.lang.Math", "min", 2, processingEnv); mathMaxMethod = TreeUtils.getMethods("java.lang.Math", "max", 2, processingEnv); + copyOfMethod = TreeUtils.getMethods("java.util.Arrays", "copyOf", 2, processingEnv); } /** Returns true iff the argument is an invocation of Math.min. */ @@ -59,4 +67,15 @@ public boolean isEndsWithMethod(ExecutableElement method) { // equals (rather than ElementUtils.ismethod) because String.length cannot be overridden return method.equals(endsWithMethod); } + + /** + * Determines whether a tree is an invocation of the {@code Arrays.copyOf()} method. + * + * @param tree tree to check + * @param processingEnv the processing environment + * @return true iff the argument is an invocation of {@code Arrays.copyOf()} method. + */ + public boolean isArraysCopyOfInvocation(Tree tree, ProcessingEnvironment processingEnv) { + return TreeUtils.isMethodInvocation(tree, copyOfMethod, processingEnv); + } } diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueTreeAnnotator.java b/framework/src/main/java/org/checkerframework/common/value/ValueTreeAnnotator.java index fce32844d7fd..f51f84b2f2c4 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueTreeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueTreeAnnotator.java @@ -17,6 +17,7 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.treeannotator.TreeAnnotator; import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypesUtils; @@ -398,6 +399,22 @@ public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror } } + if (atypeFactory + .getMethodIdentifier() + .isArraysCopyOfInvocation(tree, atypeFactory.getProcessingEnv())) { + List args = tree.getArguments(); + if (args.size() != 2) { + throw new BugInCF( + "Arrays.copyOf() should have 2 arguments. This point should not have reached"); + } + Range range = + ValueCheckerUtils.getPossibleValues( + atypeFactory.getAnnotatedType(args.get(1)), atypeFactory); + if (range != null) { + type.replaceAnnotation(atypeFactory.createArrayLenRangeAnnotation(range)); + } + } + if (!methodIsStaticallyExecutable(TreeUtils.elementFromUse(tree)) || !handledByValueChecker(type)) { return null; From a8a4e50e5ea0b24ca390dc896a7bcb3c3040a47c Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Mon, 3 Aug 2020 09:14:26 -0700 Subject: [PATCH 130/138] Prep for release. --- build.gradle | 2 +- changelog.txt | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7389a77ba86e..8ab511c562e3 100644 --- a/build.gradle +++ b/build.gradle @@ -101,7 +101,7 @@ allprojects { // * any new checkers have been added, // * the patch level is 9 (keep the patch level as a single digit), or // * backward-incompatible changes have been made to APIs or elsewhere. - version '3.5.1-SNAPSHOT' + version '3.6.0' repositories { mavenCentral() diff --git a/changelog.txt b/changelog.txt index 1753aebad11a..2b1723d574e0 100644 --- a/changelog.txt +++ b/changelog.txt @@ -26,6 +26,10 @@ BaseTypeValidator#shouldCheckTopLevelDeclaredType to Removed org.checkerframework.framework.test.FrameworkPer(Directory/File)Test classes. Use CheckerFrameworkPer(Directory/File)Test instead. +Closed issues: +1395, 2483, 3207, 3223, 3224, 3313, 3381, 3422, 3424, 3428, 3429, 3438, 3442, +3443, 3447, 3449, 3461, 3482, 3485, 3495, 3500, 3528. + --------------------------------------------------------------------------- Version 3.5.0, July 1, 2020 From 3611815a4e8d3537e967bc6e12b28e1902ebc5c3 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Mon, 3 Aug 2020 10:17:34 -0700 Subject: [PATCH 131/138] new release 3.6.0 --- docs/checker-framework-webpage.html | 8 +++---- docs/examples/MavenExample/pom.xml | 2 +- docs/examples/MavenExampleJDK11/pom.xml | 2 +- docs/manual/external-tools.tex | 24 +++++++++---------- docs/manual/introduction.tex | 2 +- docs/manual/manual.tex | 4 ++-- .../tests/testdemo/check-tainting.0.expected | 2 +- .../tutorial/webpages/security-error-cmd.html | 6 ++--- 8 files changed, 25 insertions(+), 25 deletions(-) diff --git a/docs/checker-framework-webpage.html b/docs/checker-framework-webpage.html index c30dcd088551..5a43e2abdbba 100644 --- a/docs/checker-framework-webpage.html +++ b/docs/checker-framework-webpage.html @@ -31,8 +31,8 @@

    The Checker Framework

    instructions and tutorial
    .
  • - Download: checker-framework-3.5.0.zip - (1 Jul 2020); + Download: checker-framework-3.6.0.zip + (3 Aug 2020); includes source, platform-independent binary, tests, and documentation.
    Then, see the installation @@ -94,7 +94,7 @@

    The Checker Framework

    the .class file. The tools support both Java 5 declaration annotations and Java 8 type annotations.
      -
    • annotation-tools-3.9.10.zip (01 Jul 2020) +
    • annotation-tools-3.9.11.zip (03 Aug 2020)
    • source code repository
    • @@ -224,7 +224,7 @@

      Mailing lists


      -Last updated: 1 Jul 2020 +Last updated: 3 Aug 2020

      diff --git a/docs/examples/MavenExample/pom.xml b/docs/examples/MavenExample/pom.xml index f3b7f18dd505..784e42e4cf48 100644 --- a/docs/examples/MavenExample/pom.xml +++ b/docs/examples/MavenExample/pom.xml @@ -14,7 +14,7 @@ UTF-8 ${com.google.errorprone:javac:jar} - 3.5.0 + 3.6.0 diff --git a/docs/examples/MavenExampleJDK11/pom.xml b/docs/examples/MavenExampleJDK11/pom.xml index 80c245f62d9f..a8fe50473e85 100644 --- a/docs/examples/MavenExampleJDK11/pom.xml +++ b/docs/examples/MavenExampleJDK11/pom.xml @@ -12,7 +12,7 @@ UTF-8 - 3.5.0 + 3.6.0 diff --git a/docs/manual/external-tools.tex b/docs/manual/external-tools.tex index f487b6c19c47..fe53104254ef 100644 --- a/docs/manual/external-tools.tex +++ b/docs/manual/external-tools.tex @@ -117,7 +117,7 @@ \begin{Verbatim} dependencies { ... existing dependencies... - ext.checkerFrameworkVersion = '3.5.0' + ext.checkerFrameworkVersion = '3.6.0' implementation "org.checkerframework:checker-qual-android:${checkerFrameworkVersion}" // or if you use no annotations in source code the above line could be // compileOnly "org.checkerframework:checker-qual-android:${checkerFrameworkVersion}" @@ -190,7 +190,7 @@ \begin{Verbatim} dependencies { ... existing dependencies... - ext.checkerFrameworkVersion = '3.5.0' + ext.checkerFrameworkVersion = '3.6.0' implementation "org.checkerframework:checker-qual-android:${checkerFrameworkVersion}" // or if you use no annotations in source code the above line could be // compileOnly "org.checkerframework:checker-qual-android:${checkerFrameworkVersion}" @@ -340,13 +340,13 @@ \begin{Verbatim} prebuilt_jar( name = 'checker-framework', - binary_jar = 'checker-3.5.0.jar', + binary_jar = 'checker-3.6.0.jar', visibility = [ 'PUBLIC' ] ) prebuilt_jar( name = 'checker-qual', - binary_jar = 'checker-qual-3.5.0.jar', + binary_jar = 'checker-qual-3.6.0.jar', visibility = [ 'PUBLIC' ] ) @@ -378,21 +378,21 @@ use the last one. % Is the last one required for Cygwin, as well as for the Windows command shell? Adjust the pathnames if you have installed the Checker Framework somewhere -other than \<\${HOME}/checker-framework-3.5.0/>. +other than \<\${HOME}/checker-framework-3.6.0/>. \begin{itemize} \item Option 1: Add directory - \code{.../checker-framework-3.5.0/checker/bin} to your path, \emph{before} any other + \code{.../checker-framework-3.6.0/checker/bin} to your path, \emph{before} any other directory that contains a \ executable. If you are using the bash shell, a way to do this is to add the following to your \verb|~/.profile| (or alternately \verb|~/.bash_profile| or \verb|~/.bashrc|) file: \begin{Verbatim} - export CHECKERFRAMEWORK=${HOME}/checker-framework-3.5.0 + export CHECKERFRAMEWORK=${HOME}/checker-framework-3.6.0 export PATH=${CHECKERFRAMEWORK}/checker/bin:${PATH} \end{Verbatim} @@ -413,7 +413,7 @@ file: % No Windows example because this doesn't work under Windows. \begin{Verbatim} - export CHECKERFRAMEWORK=${HOME}/checker-framework-3.5.0 + export CHECKERFRAMEWORK=${HOME}/checker-framework-3.6.0 alias javacheck='$CHECKERFRAMEWORK/checker/bin/javac' \end{Verbatim} @@ -435,11 +435,11 @@ \begin{Verbatim} # Unix - export CHECKERFRAMEWORK=${HOME}/checker-framework-3.5.0 + export CHECKERFRAMEWORK=${HOME}/checker-framework-3.6.0 alias javacheck='java -jar "$CHECKERFRAMEWORK/checker/dist/checker.jar"' # Windows - set CHECKERFRAMEWORK = C:\Program Files\checker-framework-3.5.0\ + set CHECKERFRAMEWORK = C:\Program Files\checker-framework-3.6.0\ doskey javacheck=java -jar "%CHECKERFRAMEWORK%\checker\dist\checker.jar" $* \end{Verbatim} @@ -518,8 +518,8 @@ \begin{itemize} \item \: \url{https://search.maven.org/artifact/com.google.errorprone/javac/9%2B181-r4173-1/jar} -\item \: \url{https://search.maven.org/artifact/org.checkerframework/checker-qual/3.5.0/jar} -\item \: \url{https://search.maven.org/artifact/org.checkerframework/checker/3.5.0/jar} +\item \: \url{https://search.maven.org/artifact/org.checkerframework/checker-qual/3.6.0/jar} +\item \: \url{https://search.maven.org/artifact/org.checkerframework/checker/3.6.0/jar} \end{itemize} Different arguments to \ are required for JDK 8 diff --git a/docs/manual/introduction.tex b/docs/manual/introduction.tex index 9bcb29d4905a..76f8f837ed3a 100644 --- a/docs/manual/introduction.tex +++ b/docs/manual/introduction.tex @@ -193,7 +193,7 @@ %BEGIN LATEX \\ %END LATEX - \url{https://checkerframework.org/checker-framework-3.5.0.zip} + \url{https://checkerframework.org/checker-framework-3.6.0.zip} \item Unzip it to create a \code{checker-framework-\ReleaseVersion{}} directory. diff --git a/docs/manual/manual.tex b/docs/manual/manual.tex index 858440bde490..27f861d60e85 100644 --- a/docs/manual/manual.tex +++ b/docs/manual/manual.tex @@ -4,8 +4,8 @@ \title{The Checker Framework Manual: \\ Custom pluggable types for Java} \author{\url{https://checkerframework.org/}} -\newcommand{\ReleaseVersion}{3.5.0} -\newcommand{\ReleaseInfo}{3.5.0 (1 Jul 2020)} +\newcommand{\ReleaseVersion}{3.6.0} +\newcommand{\ReleaseInfo}{3.6.0 (3 Aug 2020)} \date{Version \ReleaseInfo{}} \begin{document} diff --git a/docs/tutorial/tests/testdemo/check-tainting.0.expected b/docs/tutorial/tests/testdemo/check-tainting.0.expected index d9c1784c9fa7..6a0b0591626f 100644 --- a/docs/tutorial/tests/testdemo/check-tainting.0.expected +++ b/docs/tutorial/tests/testdemo/check-tainting.0.expected @@ -5,7 +5,7 @@ Deleting directory /Users/smillst/src/jsr308/checker-framework/tutorial/eclipse- check-tainting: Created dir: /Users/smillst/src/jsr308/checker-framework/tutorial/eclipse-projects/personalblog-demo/bin Compiling 2 source files to /Users/smillst/src/jsr308/checker-framework/tutorial/eclipse-projects/personalblog-demo/bin -javac 1.8.0-jsr308-3.5.0 +javac 1.8.0-jsr308-3.6.0 /home/mernst/research/types/checker-framework/tutorial/eclipse-projects/personalblog-demo/src/net/eyde/personalblog/service/PersonalBlogService.java:174: error: [argument.type.incompatible] incompatible types in argument. + "%' order by post.created desc"); ^ diff --git a/docs/tutorial/webpages/security-error-cmd.html b/docs/tutorial/webpages/security-error-cmd.html index ba4ee3c00654..81941f06f8c9 100644 --- a/docs/tutorial/webpages/security-error-cmd.html +++ b/docs/tutorial/webpages/security-error-cmd.html @@ -104,7 +104,7 @@

      1. Run the Tainting Checker — 1 error found

      check-tainting: [mkdir] Created dir: .../personalblog-demo/bin [jsr308.javac] Compiling 2 source files to .../personalblog-demo/bin -[jsr308.javac] javac 1.8.0-jsr308-3.5.0 +[jsr308.javac] javac 1.8.0-jsr308-3.6.0 [jsr308.javac] .../personalblog-demo/src/net/eyde/personalblog/service/PersonalBlogService.java:175: error: incompatible types in argument. [jsr308.javac] "where post.category like '%", category, [jsr308.javac] ^ @@ -153,7 +153,7 @@

      3. Re-run the Tainting Checker — a new error is found

      check-tainting: [mkdir] Created dir: .../personalblog-demo/bin [jsr308.javac] Compiling 2 source files to .../personalblog-demo/bin -[jsr308.javac] javac 1.8.0-jsr308-3.5.0 +[jsr308.javac] javac 1.8.0-jsr308-3.6.0 [jsr308.javac] .../personalblog-demo/src/net/eyde/personalblog/struts/action/ReadAction.java:58: error: incompatible types in argument. [jsr308.javac] pblog.getPostsByCategory(reqCategory)); [jsr308.javac] ^ @@ -196,7 +196,7 @@

      5. Re-run the Tainting Checker — no errors

      check-tainting: [mkdir] Created dir: .../personalblog-demo/bin [jsr308.javac] Compiling 2 source files to .../personalblog-demo/bin -[jsr308.javac] javac 1.8.0-jsr308-3.5.0 +[jsr308.javac] javac 1.8.0-jsr308-3.6.0 BUILD SUCCESSFUL Total time: 2 seconds From b087452ff9247a91c51258f893e3a8082e19bb12 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Mon, 3 Aug 2020 13:57:12 -0700 Subject: [PATCH 132/138] Fix dataflow test classpath. (#3548) --- dataflow/build.gradle | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dataflow/build.gradle b/dataflow/build.gradle index ef47146c7634..4d54727f3fa7 100644 --- a/dataflow/build.gradle +++ b/dataflow/build.gradle @@ -48,7 +48,7 @@ task liveVariableTest(dependsOn: compileTestJava, group: 'Verification') { if (!JavaVersion.current().java9Compatible) { jvmArgs += "-Xbootclasspath/p:${configurations.javacJar.asPath}" } - classpath = sourceSets.test.compileClasspath + classpath = sourceSets.test.runtimeClasspath classpath += sourceSets.test.output main = 'livevar.LiveVariable' } @@ -71,8 +71,9 @@ task issue3447Test(dependsOn: compileTestJava, group: 'Verification') { if (!JavaVersion.current().java9Compatible) { jvmArgs += "-Xbootclasspath/p:${configurations.javacJar.asPath}" } - classpath = sourceSets.test.compileClasspath + classpath = sourceSets.test.runtimeClasspath classpath += sourceSets.test.output + main = 'livevar.LiveVariable' } } From 75151d1bb26919514e9027dfb9947fe07d9a071b Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Mon, 3 Aug 2020 14:01:37 -0700 Subject: [PATCH 133/138] Update version to 3.6.1-SNAPSHOT. --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 8ab511c562e3..39dd922436a2 100644 --- a/build.gradle +++ b/build.gradle @@ -101,7 +101,7 @@ allprojects { // * any new checkers have been added, // * the patch level is 9 (keep the patch level as a single digit), or // * backward-incompatible changes have been made to APIs or elsewhere. - version '3.6.0' + version '3.6.1-SNAPSHOT' repositories { mavenCentral() From 8fbe02d65b4968ddfb8d0f8db57d04eba37ae72b Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 3 Aug 2020 18:59:11 -0700 Subject: [PATCH 134/138] Do pluggable type-checking in misc CI job --- build.gradle | 29 +++++++++++++++++------------ checker/bin-devel/test-misc.sh | 4 ++-- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/build.gradle b/build.gradle index 39dd922436a2..584c729fb6c2 100644 --- a/build.gradle +++ b/build.gradle @@ -803,16 +803,6 @@ subprojects { // Create a nonJunitTests task per project tasks.create(name: 'nonJunitTests', group: 'Verification') { description 'Run all Checker Framework tests except for the Junit tests.' - dependsOn('checkInterning', 'checkPurity', 'checkSignature') - if (project.name.is('framework') || project.name.is('checker')) { - dependsOn('checkNullnessOnlyAnnotatedFor') - } else { - dependsOn('checkNullness') - } - - if (project.name.is('framework') || project.name.is('checker')) { - dependsOn('checkCompilerMessages', 'jtregTests') - } if (project.name.is('framework')) { dependsOn('wholeProgramInferenceTests', 'loaderTests') } @@ -830,12 +820,27 @@ subprojects { } } + // Create a typecheck task per project (dogfooding the Checker Framework on itself). + // This isn't a test of the Checker Framework as the test and nonJunitTests are. + tasks.create(name: 'typecheck', group: 'Verification') { + description 'Run the Checker Framework on itself' + dependsOn('checkInterning', 'checkPurity', 'checkSignature') + if (project.name.is('framework') || project.name.is('checker')) { + dependsOn('checkNullnessOnlyAnnotatedFor') + } else { + dependsOn('checkNullness') + } + if (project.name.is('framework') || project.name.is('checker')) { + dependsOn('checkCompilerMessages', 'jtregTests') + } + } + // Create an allTests task per project. - // allTests = test + nonJunitTests + // allTests = test + nonJunitTests + typecheck tasks.create(name: 'allTests', group: 'Verification') { description 'Run all Checker Framework tests' // The 'test' target is just the JUnit tests. - dependsOn('nonJunitTests', 'test') + dependsOn('nonJunitTests', 'test', 'typecheck') } task javadocPrivate(dependsOn: javadoc) { diff --git a/checker/bin-devel/test-misc.sh b/checker/bin-devel/test-misc.sh index d9fac8508e9f..a93a57713dda 100755 --- a/checker/bin-devel/test-misc.sh +++ b/checker/bin-devel/test-misc.sh @@ -12,8 +12,8 @@ SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" source "$SCRIPTDIR"/build.sh -# Code style and formatting -./gradlew checkBasicStyle checkFormat --console=plain --warning-mode=all --no-daemon +# Code style, formatting, and plugable type-checking +./gradlew checkBasicStyle checkFormat typecheck --console=plain --warning-mode=all --no-daemon # HTML legality ./gradlew htmlValidate --console=plain --warning-mode=all --no-daemon From 07f8f42cf75bd5ca82789914c0cfd2cd294c7803 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 3 Aug 2020 19:13:45 -0700 Subject: [PATCH 135/138] Change file name to match the class it contains --- .../tests/aliasing/{UniqueAnnotation.java => UniqueAnnoTest.java} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename framework/tests/aliasing/{UniqueAnnotation.java => UniqueAnnoTest.java} (100%) diff --git a/framework/tests/aliasing/UniqueAnnotation.java b/framework/tests/aliasing/UniqueAnnoTest.java similarity index 100% rename from framework/tests/aliasing/UniqueAnnotation.java rename to framework/tests/aliasing/UniqueAnnoTest.java From 3c0c443e443a48f7ae98841a69fc5fbfbf72f532 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Mon, 3 Aug 2020 21:47:39 -0700 Subject: [PATCH 136/138] Improve whitespace style --- build.gradle | 22 +++++--- docs/checker-framework-webpage.html | 3 +- docs/developer/gsoc-ideas.html | 4 +- docs/examples/MavenExample/pom.xml | 8 +-- docs/manual/figures/chainlink.svg | 52 ++++--------------- .../tests/framework/AnnotatedGenerics.java | 20 +++---- framework/tests/subtyping/README | 1 - 7 files changed, 43 insertions(+), 67 deletions(-) diff --git a/build.gradle b/build.gradle index 584c729fb6c2..7ffd2d6c5218 100644 --- a/build.gradle +++ b/build.gradle @@ -307,7 +307,7 @@ def createCheckTypeTask(projectName, checker, shortName, args = []) { '-Xlint:-processing', '-Xmaxerrs', '10000', '-Xmaxwarns', '10000', - '-ArequirePrefixInWarningSuppressions', + '-ArequirePrefixInWarningSuppressions', ] options.compilerArgs += args @@ -862,6 +862,7 @@ task checkBasicStyle(group: 'Format') { '.gradle', '.idea', '.plume-scripts', + '.run-google-java-format', 'annotated', 'api', 'bib', @@ -894,8 +895,8 @@ task checkBasicStyle(group: 'Format') { 'manual.html', 'manual.html-e', 'junit.*.properties', - 'checker/dist/META-INF/maven/org.apache.bcel/bcel/pom.xml', - 'checker/dist/META-INF/maven/org.apache.commons/commons-text/pom.xml', + 'checker/dist/META-INF/maven/org.apache.bcel/bcel/pom.xml', + 'checker/dist/META-INF/maven/org.apache.commons/commons-text/pom.xml', 'framework/src/main/resources/git.properties'] doLast { FileTree tree = fileTree(dir: projectDir) @@ -908,12 +909,19 @@ task checkBasicStyle(group: 'Format') { boolean failed = false tree.visit { if (!it.file.isDirectory()) { - int isBlankLine + boolean blankLineAtEnd = false + String fileName = it.file.getName() + boolean checkTabs = !fileName.equals("Makefile") it.file.eachLine { line -> if (line.endsWith(' ')) { println("Trailing whitespace: ${it.file.absolutePath}") failed = true } + if (checkTabs && line.contains('\t')) { + println("Contains tab (use spaces): ${it.file.absolutePath}") + failed = true + checkTabs = false + } if (!line.startsWith('\\') && (line.matches('^.* (else|finally|try)\\{}.*$') || line.matches('^.*}(catch|else|finally) .*$') @@ -923,13 +931,13 @@ task checkBasicStyle(group: 'Format') { failed = true } if (line.isEmpty()) { - isBlankLine++; + blankLineAtEnd = true; } else { - isBlankLine = 0; + blankLineAtEnd = false; } } - if (isBlankLine > 1) { + if (blankLineAtEnd) { println("Blank line at end of file: ${it.file.absolutePath}") failed = true } diff --git a/docs/checker-framework-webpage.html b/docs/checker-framework-webpage.html index 5a43e2abdbba..f1a7c3fced13 100644 --- a/docs/checker-framework-webpage.html +++ b/docs/checker-framework-webpage.html @@ -27,8 +27,7 @@

      The Checker Framework

      • Quick start: see the - Installation - instructions and tutorial. + Installation instructions and tutorial.
      • Download: checker-framework-3.6.0.zip diff --git a/docs/developer/gsoc-ideas.html b/docs/developer/gsoc-ideas.html index 4a1b4fea3df7..4f04689ae22f 100644 --- a/docs/developer/gsoc-ideas.html +++ b/docs/developer/gsoc-ideas.html @@ -203,8 +203,8 @@

        How to get started: do a case study

        to suppress a warning. Convince yourself that both branches can execute, or else don't add the if statement.
      • If you add a @SuppressWarnings annotation, - write - it on the smallest possible scope and + write + it on the smallest possible scope and explain why the checker warning is a false positive and you are certain the code is safe. diff --git a/docs/examples/MavenExample/pom.xml b/docs/examples/MavenExample/pom.xml index 784e42e4cf48..90802b228eb3 100644 --- a/docs/examples/MavenExample/pom.xml +++ b/docs/examples/MavenExample/pom.xml @@ -108,10 +108,10 @@ -Aquals=myPackage.qual.MyFenum,myPackage.qual.MyCustomType --> -Alint - - - - + + + + diff --git a/docs/manual/figures/chainlink.svg b/docs/manual/figures/chainlink.svg index 88fa283e3747..bf88e803b2e9 100644 --- a/docs/manual/figures/chainlink.svg +++ b/docs/manual/figures/chainlink.svg @@ -2,46 +2,16 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + width="482.136px" height="482.135px" viewBox="0 0 482.136 482.135" style="enable-background:new 0 0 482.136 482.135;" + xml:space="preserve"> + + diff --git a/framework/tests/framework/AnnotatedGenerics.java b/framework/tests/framework/AnnotatedGenerics.java index 48ac6b8afff1..d449504ca36a 100644 --- a/framework/tests/framework/AnnotatedGenerics.java +++ b/framework/tests/framework/AnnotatedGenerics.java @@ -100,19 +100,19 @@ public void testAnonymousConstructors() { // not seem to support the diamond operator in conjunction with anonymous classes. // // public void testAnonymousConstructorsWithTypeParameterInferrence() { - // @Odd MyClass<@Odd String> innerClass1 = new @Odd MyClass<>() {}; - // @Odd NormalClass<@Odd String> normal1 = new @Odd NormalClass<>() {}; + // @Odd MyClass<@Odd String> innerClass1 = new @Odd MyClass<>() {}; + // @Odd NormalClass<@Odd String> normal1 = new @Odd NormalClass<>() {}; // - // // Should error because the RHS isn't annotated as '@Odd' - // @Odd MyClass<@Odd String> innerClass2 = new MyClass<>() {}; - // @Odd NormalClass<@Odd String> normal2 = new NormalClass<>() {}; + // // Should error because the RHS isn't annotated as '@Odd' + // @Odd MyClass<@Odd String> innerClass2 = new MyClass<>() {}; + // @Odd NormalClass<@Odd String> normal2 = new NormalClass<>() {}; // - // @Odd MyClass innerClass3 = new @Odd MyClass<>() {}; - // @Odd NormalClass normal3 = new @Odd NormalClass<>() {}; + // @Odd MyClass innerClass3 = new @Odd MyClass<>() {}; + // @Odd NormalClass normal3 = new @Odd NormalClass<>() {}; // - // // Should error because the RHS isn't annotated as '@Odd' - // @Odd MyClass innerClass4 = new MyClass<>() {}; - // @Odd NormalClass normal4 = new NormalClass<>() {}; + // // Should error because the RHS isn't annotated as '@Odd' + // @Odd MyClass innerClass4 = new MyClass<>() {}; + // @Odd NormalClass normal4 = new NormalClass<>() {}; // } static class NormalClass { diff --git a/framework/tests/subtyping/README b/framework/tests/subtyping/README index cdf06dfe1cdf..1d4699bcd7ed 100644 --- a/framework/tests/subtyping/README +++ b/framework/tests/subtyping/README @@ -3,4 +3,3 @@ To add a new file to the test suite, see To run the tests, do (cd $CHECKERFRAMEWORK && ./gradlew SubtypingEncryptedTest) - From 8e0c4d0ac0784308147af4f6ddd880f7002b3908 Mon Sep 17 00:00:00 2001 From: lnsun <57457122+lnsun@users.noreply.github.com> Date: Tue, 4 Aug 2020 10:44:02 -0400 Subject: [PATCH 137/138] rename util --- .../common/basetype/BaseTypeChecker.java | 4 ++-- .../common/basetype/BaseTypeVisitor.java | 10 +++++----- .../framework/type/GenericAnnotatedTypeFactory.java | 6 +++--- .../{ComponentFinderUtil.java => ComponentFinder.java} | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) rename framework/src/main/java/org/checkerframework/framework/util/{ComponentFinderUtil.java => ComponentFinder.java} (96%) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java index 40f3dfd5d7ab..ffcbeb5944b5 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java @@ -38,7 +38,7 @@ import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; import org.checkerframework.framework.type.QualifierHierarchy; import org.checkerframework.framework.type.TypeHierarchy; -import org.checkerframework.framework.util.ComponentFinderUtil; +import org.checkerframework.framework.util.ComponentFinder; import org.checkerframework.framework.util.TreePathCacher; import org.checkerframework.javacutil.AbstractTypeProcessor; import org.checkerframework.javacutil.AnnotationProvider; @@ -213,7 +213,7 @@ public boolean shouldResolveReflection() { */ @Override protected BaseTypeVisitor createSourceVisitor() { - return ComponentFinderUtil.findAndInitWithChecker(this, "Visitor", BaseTypeVisitor::new); + return ComponentFinder.findAndInitWithChecker(this, "Visitor", BaseTypeVisitor::new); } /** 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 3f11ce187c63..76c81a924597 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -103,7 +103,7 @@ 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.ComponentFinderUtil; +import org.checkerframework.framework.util.ComponentFinder; import org.checkerframework.framework.util.Contract; import org.checkerframework.framework.util.Contract.ConditionalPostcondition; import org.checkerframework.framework.util.Contract.Postcondition; @@ -242,16 +242,16 @@ protected BaseTypeVisitor(BaseTypeChecker checker, Factory typeFactory) { * @return the appropriate type factory */ protected Factory createTypeFactory() { - return ComponentFinderUtil.findAndInitWithChecker( + return ComponentFinder.findAndInitWithChecker( checker, "AnnotatedTypeFactory", this::createDefaultTypeFactory); } /** - * Create a default type factory if {@link ComponentFinderUtil} failed to find a factory + * Create a default type factory if {@link ComponentFinder} failed to find a factory * * @param checker the checker previously passed to {@link - * ComponentFinderUtil#findAndInitWithChecker(BaseTypeChecker, String, - * ComponentFinderUtil.DefaultGetter)} + * ComponentFinder#findAndInitWithChecker(BaseTypeChecker, String, + * ComponentFinder.DefaultGetter)} * @return a {@link BaseAnnotatedTypeFactory} */ @SuppressWarnings("unchecked") 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 fd2713451666..794887e99cf6 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java @@ -87,7 +87,7 @@ import org.checkerframework.framework.type.typeannotator.PropagationTypeAnnotator; import org.checkerframework.framework.type.typeannotator.TypeAnnotator; import org.checkerframework.framework.util.AnnotatedTypes; -import org.checkerframework.framework.util.ComponentFinderUtil; +import org.checkerframework.framework.util.ComponentFinder; import org.checkerframework.framework.util.FlowExpressionParseUtil; import org.checkerframework.framework.util.FlowExpressionParseUtil.FlowExpressionParseException; import org.checkerframework.framework.util.defaults.QualifierDefaults; @@ -397,7 +397,7 @@ protected DefaultQualifierForUseTypeAnnotator createDefaultForUseTypeAnnotator() @SuppressWarnings({"unchecked", "rawtypes"}) protected FlowAnalysis createFlowAnalysis(List> fieldValues) { - return ComponentFinderUtil.find( + return ComponentFinder.find( checker, "Analysis", checker1 -> { @@ -436,7 +436,7 @@ protected FlowAnalysis createFlowAnalysis(List> fie public TransferFunction createFlowTransferFunction( CFAbstractAnalysis analysis) { - return ComponentFinderUtil.find( + return ComponentFinder.find( checker, "Transfer", checker1 -> { diff --git a/framework/src/main/java/org/checkerframework/framework/util/ComponentFinderUtil.java b/framework/src/main/java/org/checkerframework/framework/util/ComponentFinder.java similarity index 96% rename from framework/src/main/java/org/checkerframework/framework/util/ComponentFinderUtil.java rename to framework/src/main/java/org/checkerframework/framework/util/ComponentFinder.java index cb35c974c16f..7905422e7d62 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/ComponentFinderUtil.java +++ b/framework/src/main/java/org/checkerframework/framework/util/ComponentFinder.java @@ -3,12 +3,12 @@ import org.checkerframework.common.basetype.BaseTypeChecker; /** - * Util class to find components (e.g. visitors and factories) following the naming convention + * Find components (e.g. visitors and factories) following the naming convention * reflectively. For example, ABCChecker's default naming for visitor is "ABCVisitor". If a visitor * class with that name exists, then instantiate it and returns. Otherwise try to find the super, * and finally uses the {@code defaultGetter} callback to get a default value if all attempt fails. */ -public class ComponentFinderUtil { +public class ComponentFinder { /** * A single method interface for getting default type for components by method reference. From 01450b7868f9e877868b7deeb4de9a3a5b2cffe1 Mon Sep 17 00:00:00 2001 From: lnsun <57457122+lnsun@users.noreply.github.com> Date: Tue, 4 Aug 2020 10:54:28 -0400 Subject: [PATCH 138/138] replace default getter with supplier --- .../common/basetype/BaseTypeChecker.java | 3 +- .../common/basetype/BaseTypeVisitor.java | 5 +-- .../type/GenericAnnotatedTypeFactory.java | 6 ++-- .../framework/util/ComponentFinder.java | 31 +++++-------------- 4 files changed, 14 insertions(+), 31 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java index ffcbeb5944b5..247194f586de 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java @@ -213,7 +213,8 @@ public boolean shouldResolveReflection() { */ @Override protected BaseTypeVisitor createSourceVisitor() { - return ComponentFinder.findAndInitWithChecker(this, "Visitor", BaseTypeVisitor::new); + return ComponentFinder.findAndInitWithChecker( + this, "Visitor", () -> new BaseTypeVisitor(this)); } /** 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 76c81a924597..7556c67041aa 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -249,13 +249,10 @@ protected Factory createTypeFactory() { /** * Create a default type factory if {@link ComponentFinder} failed to find a factory * - * @param checker the checker previously passed to {@link - * ComponentFinder#findAndInitWithChecker(BaseTypeChecker, String, - * ComponentFinder.DefaultGetter)} * @return a {@link BaseAnnotatedTypeFactory} */ @SuppressWarnings("unchecked") - private Factory createDefaultTypeFactory(BaseTypeChecker checker) { + private Factory createDefaultTypeFactory() { try { return (Factory) new BaseAnnotatedTypeFactory(checker); } catch (Throwable t) { 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 794887e99cf6..cc1fd3ad5c6b 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java @@ -400,7 +400,7 @@ protected FlowAnalysis createFlowAnalysis(List> fie return ComponentFinder.find( checker, "Analysis", - checker1 -> { + () -> { // If an analysis couldn't be loaded reflectively, return the // default. List> tmp = new ArrayList<>(); @@ -409,7 +409,7 @@ protected FlowAnalysis createFlowAnalysis(List> fie tmp.add(Pair.of(fieldVal.first, (CFValue) fieldVal.second)); } return (FlowAnalysis) - new CFAnalysis(checker1, (GenericAnnotatedTypeFactory) this, tmp); + new CFAnalysis(checker, (GenericAnnotatedTypeFactory) this, tmp); }, new Class[] {BaseTypeChecker.class, this.getClass(), List.class}, new Object[] {checker, this, fieldValues}); @@ -439,7 +439,7 @@ public TransferFunction createFlowTransferFunction( return ComponentFinder.find( checker, "Transfer", - checker1 -> { + () -> { @SuppressWarnings("unchecked") TransferFunction ret = (TransferFunction) diff --git a/framework/src/main/java/org/checkerframework/framework/util/ComponentFinder.java b/framework/src/main/java/org/checkerframework/framework/util/ComponentFinder.java index 7905422e7d62..ec1786f761ed 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/ComponentFinder.java +++ b/framework/src/main/java/org/checkerframework/framework/util/ComponentFinder.java @@ -1,31 +1,16 @@ package org.checkerframework.framework.util; +import java.util.function.Supplier; import org.checkerframework.common.basetype.BaseTypeChecker; /** - * Find components (e.g. visitors and factories) following the naming convention - * reflectively. For example, ABCChecker's default naming for visitor is "ABCVisitor". If a visitor - * class with that name exists, then instantiate it and returns. Otherwise try to find the super, - * and finally uses the {@code defaultGetter} callback to get a default value if all attempt fails. + * Find components (e.g. visitors and factories) following the naming convention reflectively. For + * example, ABCChecker's default naming for visitor is "ABCVisitor". If a visitor class with that + * name exists, then instantiate it and returns. Otherwise try to find the super, and finally uses + * the {@code defaultGetter} callback to get a default value if all attempt fails. */ public class ComponentFinder { - /** - * A single method interface for getting default type for components by method reference. - * - * @param the type for the default value - */ - public interface DefaultGetter { - - /** - * The logic for getting the default value if all attempt fails - * - * @param checker a checker as the argument of constructor - * @return the default value - */ - DefaultType getDefault(BaseTypeChecker checker); - } - /** * Find a component named with the checker naming convention. * @@ -41,7 +26,7 @@ public interface DefaultGetter { public static T find( BaseTypeChecker checker, String replacement, - DefaultGetter defaultGetter, + Supplier defaultGetter, Class[] constructorParamTypes, Object[] constructorArgs) { // Try to reflectively load the component. @@ -58,7 +43,7 @@ public static T find( } checkerClass = checkerClass.getSuperclass(); } - return defaultGetter.getDefault(checker); + return defaultGetter.get(); } /** @@ -72,7 +57,7 @@ public static T find( * @return the properly-named component found */ public static T findAndInitWithChecker( - BaseTypeChecker checker, String replacement, DefaultGetter defaultGetter) { + BaseTypeChecker checker, String replacement, Supplier defaultGetter) { return find( checker, replacement,