diff --git a/.gitignore b/.gitignore index bcde890ce0f5..92d3d0cd76e5 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ docs/tmpapi/ checker/build-temp checker/bin/.do-like-javac checker/bin/.scc/ +checker/bin-devel/.git-scripts checker/bin-devel/.html-tools checker/bin-devel/.plume-scripts checker/bin-devel/dockerdir diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 3ab18c219c6e..fc525e4b6794 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -89,6 +89,8 @@ jobs: - plume_lib_jdk21 # - plume_lib_jdk_latest # Not plume_lib_jdk_next + - windows_junit_jdk17 + - jspecify_conformance_jdk21 pool: vmImage: 'ubuntu-latest' steps: @@ -803,6 +805,18 @@ jobs: continueOnError: true displayName: test-plume-lib.sh +- job: jspecify_conformance_jdk21 + dependsOn: + - junit_jdk21 + pool: + vmImage: 'ubuntu-latest' + container: wmdietl/cf-ubuntu-jdk21:latest + steps: + - checkout: self + fetchDepth: 25 + - bash: ./checker/bin-devel/test-jspecify-conformance.sh + displayName: test-jspecify-conformance.sh + ## The downstream jobs are not currently needed because test-downstream.sh is empty. # - job: downstream_jdk8 # dependsOn: @@ -901,3 +915,17 @@ jobs: fetchDepth: 25 - bash: ./checker/bin-devel/test-cf-inference.sh displayName: test-cf-inference.sh +- job: windows_junit_jdk17 + dependsOn: + - junit_jdk21 + pool: + vmImage: 'windows-latest' + timeoutInMinutes: 90 + steps: + - checkout: self + fetchDepth: 25 + - bash: ./checker/bin-devel/test-cftests-junit.sh + displayName: Run all JUnit tests + env: + JAVA_HOME: $(JAVA_HOME_17_X64) + PATH: $(JAVA_HOME_17_X64)/bin:$(PATH) diff --git a/build.gradle b/build.gradle index a3f44b927852..2043074f0697 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ plugins { // https://docs.gradle.org/current/userguide/eclipse_plugin.html id 'eclipse' // To show task list as a tree, run: ./gradlew taskTree - id 'com.dorongold.task-tree' version '2.1.1' + id 'com.dorongold.task-tree' version '4.0.0' } apply plugin: 'de.undercouch.download' @@ -68,6 +68,7 @@ ext { // afu = "${annotationTools}/annotation-file-utilities" jtregHome = "${parentDir}/jtreg" + gitScriptsHome = "${project(':checker').projectDir}/bin-devel/.git-scripts" plumeScriptsHome = "${project(':checker').projectDir}/bin-devel/.plume-scripts" htmlToolsHome = "${project(':checker').projectDir}/bin-devel/.html-tools" doLikeJavacHome = "${project(':checker').projectDir}/bin/.do-like-javac" @@ -79,11 +80,11 @@ ext { localRepo = '.git' versions = [ - autoValue : '1.10.4', - errorprone : '2.25.0', + autoValue : '1.11.0', + errorprone : '2.28.0', hashmapUtil : '0.0.1', junit : '4.13.2', - lombok : '1.18.30', + lombok : '1.18.34', // plume-util includes a version of reflection-util. When updating ensure the versions are consistent. plumeUtil : '1.9.0', reflectionUtil : '1.1.3', @@ -158,7 +159,7 @@ allprojects { // * any new checkers have been added, or // * backward-incompatible changes have been made to APIs or elsewhere. // To make a snapshot release: ./gradlew publish - version '3.42.0-eisop3' + version '3.42.0-eisop4' tasks.withType(JavaCompile).configureEach { options.fork = true @@ -248,6 +249,7 @@ allprojects { // If you add any formatters to this block that require dependencies, then you must also // add them to spotlessPredeclare block. def doNotFormat = [ + 'checker/bin-devel/.git-scripts/**', 'checker/bin-devel/.plume-scripts/**', 'checker/tests/ainfer-*/annotated/*', 'dataflow/manual/examples/', @@ -504,6 +506,8 @@ allprojects { '-Xep:CanIgnoreReturnValueSuggester:OFF', // Should be turned off when using the Checker Framework. '-Xep:ExtendsObject:OFF', + // For Visitors it is convenient to just pass a Void parameter. + '-Xep:VoidUsed: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. @@ -659,7 +663,7 @@ configurations { } dependencies { - requireJavadoc 'org.plumelib:require-javadoc:1.0.6' + requireJavadoc 'org.plumelib:require-javadoc:1.0.9' } task requireJavadoc(type: JavaExec, group: 'Documentation') { @@ -804,6 +808,7 @@ def createCloneTask(taskName, url, directory, extraArgs = []) { } +createCloneTask('getGitScripts', 'https://github.com/eisop-plume-lib/git-scripts.git', gitScriptsHome) createCloneTask('getPlumeScripts', 'https://github.com/eisop-plume-lib/plume-scripts.git', plumeScriptsHome) createCloneTask('getHtmlTools', 'https://github.com/plume-lib/html-tools.git', htmlToolsHome) createCloneTask('getDoLikeJavac', 'https://github.com/opprop/do-like-javac.git', doLikeJavacHome) @@ -1062,7 +1067,8 @@ subprojects { } // Create a task for each JUnit test class whose name is the same as the JUnit class name. - sourceSets.test.allJava.filter { it.path.contains('test/junit') }.forEach { file -> + // Regex [\\\\/] matches Unix and Windows directory separators. + sourceSets.test.allJava.filter {it.path.matches('.*test[\\\\/]junit.*')}.forEach { file -> String junitClassName = file.name.replaceAll('.java', '') tasks.create(name: "${junitClassName}", type: Test) { description "Run ${junitClassName} tests." diff --git a/checker-qual-android/build.gradle b/checker-qual-android/build.gradle index b2cf1541edc5..b3677061d4d3 100644 --- a/checker-qual-android/build.gradle +++ b/checker-qual-android/build.gradle @@ -19,7 +19,11 @@ task copySources(type: Copy) { into file(layout.buildDirectory.dir("generated/sources/main/java")) - fileMode(0444) + filePermissions { + user.read = true + group.read = true + other.read = true + } } sourceSets { diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeUseLocation.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeUseLocation.java index 32014f858b1d..22809919f571 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeUseLocation.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeUseLocation.java @@ -18,51 +18,51 @@ */ public enum TypeUseLocation { - /** Apply default annotations to all unannotated raw types of fields. */ + /** Apply default annotations to unannotated top-level types of fields. */ FIELD, /** - * Apply default annotations to all unannotated raw types of local variables, casts, and + * Apply default annotations to unannotated top-level types of local variables, casts, and * instanceof. */ LOCAL_VARIABLE, - /** Apply default annotations to all unannotated raw types of resource variables. */ + /** Apply default annotations to unannotated top-level types of resource variables. */ RESOURCE_VARIABLE, - /** Apply default annotations to all unannotated raw types of exception parameters. */ + /** Apply default annotations to unannotated top-level types of exception parameters. */ EXCEPTION_PARAMETER, - /** Apply default annotations to all unannotated raw types of receiver types. */ + /** Apply default annotations to unannotated top-level types of receiver types. */ RECEIVER, /** - * Apply default annotations to all unannotated raw types of formal parameter types, excluding + * Apply default annotations to unannotated top-level types of formal parameter types, excluding * the receiver. */ PARAMETER, - /** Apply default annotations to all unannotated raw types of return types. */ + /** Apply default annotations to unannotated top-level types of return types. */ RETURN, - /** Apply default annotations to all unannotated raw types of constructor result types. */ + /** Apply default annotations to unannotated top-level types of constructor result types. */ CONSTRUCTOR_RESULT, /** - * Apply default annotations to unannotated lower bounds for type parameters and wildcards, both - * explicit ones in {@code super} clauses, and implicit lower bounds when no explicit {@code - * extends} or {@code super} clause is present. + * Apply default annotations to unannotated top-level lower bounds of type parameters and + * wildcards, both explicit ones in {@code super} clauses, and implicit lower bounds when no + * explicit {@code extends} or {@code super} clause is present. */ LOWER_BOUND, /** - * Apply default annotations to unannotated, but explicit lower bounds of wildcards: {@code }. Type parameters have no syntax for explicit lower bound types. + * Apply default annotations to unannotated top-level explicit lower bounds of wildcards: {@code + * }. Type parameters have no syntax for explicit lower bound types. */ EXPLICIT_LOWER_BOUND, /** - * Apply default annotations to unannotated, but implicit lower bounds for type parameters and + * Apply default annotations to unannotated implicit lower bounds of type parameters and * wildcards: {@code } and {@code }, possibly with explicit upper bounds. */ // Note: no distinction between implicit lower bound when upper bound is explicit or not, in @@ -70,9 +70,9 @@ public enum TypeUseLocation { IMPLICIT_LOWER_BOUND, /** - * Apply default annotations to unannotated upper bounds for type parameters and wildcards: both - * explicit ones in {@code extends} clauses, and implicit upper bounds when no explicit {@code - * extends} or {@code super} clause is present. + * Apply default annotations to unannotated top-level upper bounds of type parameters and + * wildcards: both explicit ones in {@code extends} clauses, and implicit upper bounds when no + * explicit {@code extends} or {@code super} clause is present. * *

Especially useful for parametrized classes that provide a lot of static methods with the * same generic parameters as the class. @@ -80,59 +80,68 @@ public enum TypeUseLocation { UPPER_BOUND, /** - * Apply default annotations to unannotated, but explicit type parameter and wildcard upper + * Apply default annotations to unannotated top-level explicit type parameter and wildcard upper * bounds: {@code } and {@code }. */ EXPLICIT_UPPER_BOUND, /** - * Apply default annotations to unannotated, but explicit type parameter upper bounds: {@code }. + * Apply default annotations to unannotated top-level explicit type parameter upper bounds: + * {@code }. */ EXPLICIT_TYPE_PARAMETER_UPPER_BOUND, /** - * Apply default annotations to unannotated, but explicit wildcard upper bounds: {@code }. */ EXPLICIT_WILDCARD_UPPER_BOUND, /** - * Apply default annotations to unannotated upper bounds for type parameters and wildcards + * Apply default annotations to unannotated upper bounds of type parameters and wildcards * without explicit upper bounds: {@code }, {@code }, and {@code }. */ IMPLICIT_UPPER_BOUND, /** - * Apply default annotations to unannotated upper bounds for type parameters without explicit + * Apply default annotations to unannotated upper bounds of type parameters without explicit * upper bounds: {@code }. */ IMPLICIT_TYPE_PARAMETER_UPPER_BOUND, /** - * Apply default annotations to unannotated upper bounds for wildcards without a super bound: + * Apply default annotations to unannotated upper bounds of wildcards without a super bound: * {@code }. */ IMPLICIT_WILDCARD_UPPER_BOUND_NO_SUPER, /** - * Apply default annotations to unannotated upper bounds for wildcards with a super bound: - * {@code }. + * Apply default annotations to unannotated upper bounds of wildcards with a super bound: {@code + * }. */ IMPLICIT_WILDCARD_UPPER_BOUND_SUPER, /** - * Apply default annotations to unannotated upper bounds for wildcards with or without a super + * Apply default annotations to unannotated upper bounds of wildcards with or without a super * bound: {@code } or {@code }. */ IMPLICIT_WILDCARD_UPPER_BOUND, /** - * Apply default annotations to unannotated type variable uses: {@code T}. + * Apply default annotations to unannotated type variable uses that are not top-level local + * variables: {@code T field} or {@code List local}. + * + *

Such uses of type variables are not flow-sensitively refined and are therefore usually + * parametric. * *

To get parametric polymorphism: add a qualifier that is meta-annotated with {@link * ParametricTypeVariableUseQualifier} to your type system and use it as default for {@code * TYPE_VARIABLE_USE}, which is treated like no annotation on the type variable use. + * + *

We could name this constant {@code TYPE_VARIABLE_USE_NOT_TOP_LEVEL_LOCAL_VARIABLE} and + * introduce a separate constant {@code TYPE_VARIABLE_USE_TOP_LEVEL_LOCAL_VARIABLE}. At the + * moment we use the {@code LOCAL_VARIABLE} default for unannotated top-level type variable uses + * of local variables: {@code T local}. */ TYPE_VARIABLE_USE, diff --git a/checker/bin-devel/Dockerfile-ubuntu-jdk17 b/checker/bin-devel/Dockerfile-ubuntu-jdk17 index ff81cfb2a955..b87f044e3f19 100644 --- a/checker/bin-devel/Dockerfile-ubuntu-jdk17 +++ b/checker/bin-devel/Dockerfile-ubuntu-jdk17 @@ -51,8 +51,6 @@ ENV PATH="/apache-maven-3.9.5/bin:$PATH" RUN pip3 install --no-cache-dir lithium-reducer PyGithub pyyaml -RUN pip3 install --no-cache-dir lithium-reducer PyGithub pyyaml - RUN export DEBIAN_FRONTEND=noninteractive \ && apt-get autoremove \ && apt-get clean \ diff --git a/checker/bin-devel/checkout-historical.sh b/checker/bin-devel/checkout-historical.sh index dc543c2aa8e3..dab417805cf1 100755 --- a/checker/bin-devel/checkout-historical.sh +++ b/checker/bin-devel/checkout-historical.sh @@ -89,7 +89,7 @@ git checkout -B __merge_eval__ echo "plume-scripts" PLUME_SCRIPTS="checker/bin-devel/.plume-scripts" if [ ! -d "$PLUME_SCRIPTS" ] ; then - git clone -q https://github.com/plume-lib/plume-scripts.git "${PLUME_SCRIPTS}" + git clone -q https://github.com/eisop-plume-lib/plume-scripts.git "${PLUME_SCRIPTS}" fi COMMIT="$(cd "${PLUME_SCRIPTS}" && git rev-list -n 1 --first-parent --before="${commit_date}" master)" if [ -n "${COMMIT}" ] ; then @@ -102,7 +102,7 @@ echo "html-tools" HTML_TOOLS="checker/bin-devel/.plume-scripts" COMMIT="$(cd "${HTML_TOOLS}" && git rev-list -n 1 --first-parent --before="${commit_date}" master)" if [ ! -d "$HTML_TOOLS" ] ; then - git clone -q https://github.com/plume-lib/html-tools.git "${HTML_TOOLS}" + git clone -q https://github.com/eisop-plume-lib/html-tools.git "${HTML_TOOLS}" fi if [ -n "${COMMIT}" ] ; then # COMMIT is non-empty diff --git a/checker/bin-devel/clone-related.sh b/checker/bin-devel/clone-related.sh index a0b701483395..e8f89e73dc36 100755 --- a/checker/bin-devel/clone-related.sh +++ b/checker/bin-devel/clone-related.sh @@ -31,25 +31,25 @@ else fi echo "JAVA_HOME=${JAVA_HOME}" -# Using `(cd "$CHECKERFRAMEWORK" && ./gradlew getPlumeScripts -q)` leads to infinite regress. -PLUME_SCRIPTS="$CHECKERFRAMEWORK/checker/bin-devel/.plume-scripts" -if [ -d "$PLUME_SCRIPTS" ] ; then - (cd "$PLUME_SCRIPTS" && (git pull -q || true)) +# Using `(cd "$CHECKERFRAMEWORK" && ./gradlew getGitScripts -q)` leads to infinite regress. +GIT_SCRIPTS="$CHECKERFRAMEWORK/checker/bin-devel/.git-scripts" +if [ -d "$GIT_SCRIPTS" ] ; then + (cd "$GIT_SCRIPTS" && (git pull -q || true)) else (cd "$CHECKERFRAMEWORK/checker/bin-devel" && \ - (git clone --filter=blob:none -q https://github.com/eisop-plume-lib/plume-scripts.git .plume-scripts || \ - (sleep 1m && git clone --filter=blob:none -q https://github.com/eisop-plume-lib/plume-scripts.git .plume-scripts))) + (git clone --filter=blob:none -q https://github.com/eisop-plume-lib/git-scripts.git .git-scripts || \ + (sleep 60 && git clone --filter=blob:none -q https://github.com/eisop-plume-lib/git-scripts.git .git-scripts))) fi # Clone the annotated JDK into ../jdk . -"$PLUME_SCRIPTS/git-clone-related" ${DEBUG_FLAG} opprop jdk +"$GIT_SCRIPTS/git-clone-related" ${DEBUG_FLAG} opprop jdk # AFU="${AFU:-../annotation-tools/annotation-file-utilities}" # # Don't use `AT=${AFU}/..` which causes a git failure. # AT=$(dirname "${AFU}") # ## Build annotation-tools (Annotation File Utilities) -# "$PLUME_SCRIPTS/git-clone-related" ${DEBUG_FLAG} eisop annotation-tools "${AT}" +# "$GIT_SCRIPTS/git-clone-related" ${DEBUG_FLAG} eisop annotation-tools "${AT}" # if [ ! -d ../annotation-tools ] ; then # ln -s "${AT}" ../annotation-tools # fi diff --git a/checker/bin-devel/test-cf-inference.sh b/checker/bin-devel/test-cf-inference.sh index 32b674db3872..e8dda5b5d2a9 100755 --- a/checker/bin-devel/test-cf-inference.sh +++ b/checker/bin-devel/test-cf-inference.sh @@ -15,7 +15,7 @@ source "$SCRIPTDIR"/build.sh ## script rather than in ./test/downstream.sh, because it needs a different ## Docker image. -"$SCRIPTDIR/.plume-scripts/git-clone-related" opprop checker-framework-inference +"$SCRIPTDIR/.git-scripts/git-clone-related" opprop checker-framework-inference export PATH=$AFU/scripts:$PATH cd ../checker-framework-inference diff --git a/checker/bin-devel/test-daikon-part1.sh b/checker/bin-devel/test-daikon-part1.sh index 7a7b2a6351db..1a2c7e898a91 100755 --- a/checker/bin-devel/test-daikon-part1.sh +++ b/checker/bin-devel/test-daikon-part1.sh @@ -17,7 +17,7 @@ echo "running \"./gradlew assembleForJavac\" for checker-framework" # daikon-typecheck: 15 minutes -"$SCRIPTDIR/.plume-scripts/git-clone-related" eisop-codespecs daikon +"$SCRIPTDIR/.git-scripts/git-clone-related" eisop-codespecs daikon cd ../daikon git log | head -n 5 make compile diff --git a/checker/bin-devel/test-daikon-part2.sh b/checker/bin-devel/test-daikon-part2.sh index 4151eb723de2..860e52851920 100755 --- a/checker/bin-devel/test-daikon-part2.sh +++ b/checker/bin-devel/test-daikon-part2.sh @@ -16,7 +16,7 @@ echo "running \"./gradlew assembleForJavac\" for checker-framework" ./gradlew assembleForJavac --console=plain -Dorg.gradle.internal.http.socketTimeout=60000 -Dorg.gradle.internal.http.connectionTimeout=60000 # daikon-typecheck: 15 minutes -"$SCRIPTDIR/.plume-scripts/git-clone-related" eisop-codespecs daikon +"$SCRIPTDIR/.git-scripts/git-clone-related" eisop-codespecs daikon cd ../daikon git log | head -n 5 make compile diff --git a/checker/bin-devel/test-daikon.sh b/checker/bin-devel/test-daikon.sh index 85610dc359fd..97c8e8f98c0c 100755 --- a/checker/bin-devel/test-daikon.sh +++ b/checker/bin-devel/test-daikon.sh @@ -16,7 +16,7 @@ echo "running \"./gradlew assembleForJavac\" for checker-framework" ./gradlew assembleForJavac --console=plain -Dorg.gradle.internal.http.socketTimeout=60000 -Dorg.gradle.internal.http.connectionTimeout=60000 # daikon-typecheck: 15 minutes -"$SCRIPTDIR/.plume-scripts/git-clone-related" eisop-codespecs daikon -q --single-branch --depth 50 +"$SCRIPTDIR/.git-scripts/git-clone-related" eisop-codespecs daikon -q --single-branch --depth 50 cd ../daikon git log | head -n 5 make compile diff --git a/checker/bin-devel/test-downstream.sh b/checker/bin-devel/test-downstream.sh index 31cc12dd15df..53be63895f07 100755 --- a/checker/bin-devel/test-downstream.sh +++ b/checker/bin-devel/test-downstream.sh @@ -22,5 +22,5 @@ source "$SCRIPTDIR"/clone-related.sh ## This is moved to misc, because otherwise it would be the only work done by this script. # # Checker Framework demos -# "$SCRIPTDIR/.plume-scripts/git-clone-related" eisop checker-framework.demos +# "$SCRIPTDIR/.git-scripts/git-clone-related" eisop checker-framework.demos # ./gradlew :checker:demosTests --console=plain --warning-mode=all diff --git a/checker/bin-devel/test-guava-formatter.sh b/checker/bin-devel/test-guava-formatter.sh index 33d7b9560ca4..57eb547766fc 100755 --- a/checker/bin-devel/test-guava-formatter.sh +++ b/checker/bin-devel/test-guava-formatter.sh @@ -12,7 +12,7 @@ export ORG_GRADLE_PROJECT_useJdk17Compiler=true source "$SCRIPTDIR"/clone-related.sh -"$SCRIPTDIR/.plume-scripts/git-clone-related" eisop guava +"$SCRIPTDIR/.git-scripts/git-clone-related" eisop guava cd ../guava ./typecheck.sh formatter diff --git a/checker/bin-devel/test-guava-index.sh b/checker/bin-devel/test-guava-index.sh index 23707e37ab57..32a787cd091a 100755 --- a/checker/bin-devel/test-guava-index.sh +++ b/checker/bin-devel/test-guava-index.sh @@ -13,7 +13,7 @@ export ORG_GRADLE_PROJECT_useJdk17Compiler=true source "$SCRIPTDIR"/clone-related.sh -"$SCRIPTDIR/.plume-scripts/git-clone-related" eisop guava +"$SCRIPTDIR/.git-scripts/git-clone-related" eisop guava cd ../guava if [ "$TRAVIS" = "true" ] ; then diff --git a/checker/bin-devel/test-guava-interning.sh b/checker/bin-devel/test-guava-interning.sh index e18a35078be3..9dfaa51169bb 100755 --- a/checker/bin-devel/test-guava-interning.sh +++ b/checker/bin-devel/test-guava-interning.sh @@ -12,7 +12,7 @@ export ORG_GRADLE_PROJECT_useJdk17Compiler=true source "$SCRIPTDIR"/clone-related.sh -"$SCRIPTDIR/.plume-scripts/git-clone-related" eisop guava +"$SCRIPTDIR/.git-scripts/git-clone-related" eisop guava cd ../guava ./typecheck.sh interning diff --git a/checker/bin-devel/test-guava-lock.sh b/checker/bin-devel/test-guava-lock.sh index 2bea848dc256..d146d5d1ba7f 100755 --- a/checker/bin-devel/test-guava-lock.sh +++ b/checker/bin-devel/test-guava-lock.sh @@ -12,7 +12,7 @@ export ORG_GRADLE_PROJECT_useJdk17Compiler=true source "$SCRIPTDIR"/clone-related.sh -"$SCRIPTDIR/.plume-scripts/git-clone-related" eisop guava +"$SCRIPTDIR/.git-scripts/git-clone-related" eisop guava cd ../guava ./typecheck.sh lock diff --git a/checker/bin-devel/test-guava-nullness.sh b/checker/bin-devel/test-guava-nullness.sh index 72282d28e212..4ee36fb5b4ab 100755 --- a/checker/bin-devel/test-guava-nullness.sh +++ b/checker/bin-devel/test-guava-nullness.sh @@ -12,7 +12,7 @@ export ORG_GRADLE_PROJECT_useJdk17Compiler=true source "$SCRIPTDIR"/clone-related.sh -"$SCRIPTDIR/.plume-scripts/git-clone-related" eisop guava +"$SCRIPTDIR/.git-scripts/git-clone-related" eisop guava cd ../guava ./typecheck.sh nullness diff --git a/checker/bin-devel/test-guava-regex.sh b/checker/bin-devel/test-guava-regex.sh index cb42020eab3a..a4f042172062 100755 --- a/checker/bin-devel/test-guava-regex.sh +++ b/checker/bin-devel/test-guava-regex.sh @@ -12,7 +12,7 @@ export ORG_GRADLE_PROJECT_useJdk17Compiler=true source "$SCRIPTDIR"/clone-related.sh -"$SCRIPTDIR/.plume-scripts/git-clone-related" eisop guava +"$SCRIPTDIR/.git-scripts/git-clone-related" eisop guava cd ../guava ./typecheck.sh regex diff --git a/checker/bin-devel/test-guava-signature.sh b/checker/bin-devel/test-guava-signature.sh index 66751585b090..a7ff4ceee08d 100755 --- a/checker/bin-devel/test-guava-signature.sh +++ b/checker/bin-devel/test-guava-signature.sh @@ -12,7 +12,7 @@ export ORG_GRADLE_PROJECT_useJdk17Compiler=true source "$SCRIPTDIR"/clone-related.sh -"$SCRIPTDIR/.plume-scripts/git-clone-related" eisop guava +"$SCRIPTDIR/.git-scripts/git-clone-related" eisop guava cd ../guava ./typecheck.sh signature diff --git a/checker/bin-devel/test-guava.sh b/checker/bin-devel/test-guava.sh index 7bc125a4a9bf..a56e559f03ca 100755 --- a/checker/bin-devel/test-guava.sh +++ b/checker/bin-devel/test-guava.sh @@ -15,7 +15,7 @@ source "$SCRIPTDIR"/clone-related.sh # TODO: Maybe I should move this into the CI job, and do it for all CI jobs. cp "$SCRIPTDIR"/mvn-settings.xml ~/settings.xml -"$SCRIPTDIR/.plume-scripts/git-clone-related" eisop guava +"$SCRIPTDIR/.git-scripts/git-clone-related" eisop guava cd ../guava if [ "$TRAVIS" = "true" ] ; then diff --git a/checker/bin-devel/test-jspecify-conformance.sh b/checker/bin-devel/test-jspecify-conformance.sh new file mode 100755 index 000000000000..6b47021e1cc8 --- /dev/null +++ b/checker/bin-devel/test-jspecify-conformance.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -e +set -o verbose +set -o xtrace +export SHELLOPTS +echo "SHELLOPTS=${SHELLOPTS}" + +SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +# shellcheck disable=SC1090 # In newer shellcheck than 0.6.0, pass: "-P SCRIPTDIR" (literally) +export ORG_GRADLE_PROJECT_useJdk17Compiler=true +source "$SCRIPTDIR"/clone-related.sh +./gradlew assembleForJavac --console=plain -Dorg.gradle.internal.http.socketTimeout=60000 -Dorg.gradle.internal.http.connectionTimeout=60000 + +GIT_SCRIPTS="$SCRIPTDIR/.git-scripts" +"$GIT_SCRIPTS/git-clone-related" eisop jspecify-conformance +cd ../jspecify-conformance +./gradlew test --console=plain -PcfLocal diff --git a/checker/bin-devel/test-misc.sh b/checker/bin-devel/test-misc.sh index 8158fca7f3bf..25c77c84b832 100755 --- a/checker/bin-devel/test-misc.sh +++ b/checker/bin-devel/test-misc.sh @@ -14,7 +14,7 @@ source "$SCRIPTDIR"/clone-related.sh PLUME_SCRIPTS="$SCRIPTDIR/.plume-scripts" ## Checker Framework demos -"$PLUME_SCRIPTS/git-clone-related" eisop checker-framework.demos -q --single-branch --depth 50 +"$GIT_SCRIPTS/git-clone-related" eisop checker-framework.demos -q --single-branch --depth 50 ./gradlew :checker:demosTests --console=plain --warning-mode=all status=0 @@ -62,6 +62,6 @@ git diff --exit-code docs/manual/contributors.tex || \ echo " * Update your git configuration by running: git config --global user.name \"YOURFULLNAME\"" && echo " * Add your name to your GitHub account profile at https://github.com/settings/profile" && echo " * Make a pull request to add your GitHub ID to" && - echo " https://github.com/eisop-plume-lib/plume-scripts/blob/master/git-authors.sed" && + echo " https://github.com/eisop-plume-lib/git-scripts/blob/master/git-authors.sed" && echo " and remake contributors.tex after that pull request is merged." && false) diff --git a/checker/bin-devel/test-plume-lib.sh b/checker/bin-devel/test-plume-lib.sh index 9d0d0fa07cb4..bc2343eb0938 100755 --- a/checker/bin-devel/test-plume-lib.sh +++ b/checker/bin-devel/test-plume-lib.sh @@ -49,7 +49,7 @@ for PACKAGE in "${PACKAGES[@]}"; do echo "PACKAGE=${PACKAGE}" PACKAGEDIR="/tmp/${PACKAGE}" rm -rf "${PACKAGEDIR}" - "$SCRIPTDIR/.plume-scripts/git-clone-related" eisop-plume-lib "${PACKAGE}" "${PACKAGEDIR}" -q --single-branch --depth 250 + "$SCRIPTDIR/.git-scripts/git-clone-related" eisop-plume-lib "${PACKAGE}" "${PACKAGEDIR}" -q --single-branch --depth 250 # Uses "compileJava" target instead of "assemble" to avoid the javadoc error "Error fetching URL: # https://docs.oracle.com/en/java/javase/17/docs/api/" due to network problems. echo "About to call ./gradlew --console=plain -PcfLocal compileJava" diff --git a/checker/build.gradle b/checker/build.gradle index 126e12078094..526ebd59b7af 100644 --- a/checker/build.gradle +++ b/checker/build.gradle @@ -4,7 +4,7 @@ plugins { // https://github.com/n0mer/gradle-git-properties // Generates file build/resources/main/git.properties when the `classes` task runs. - id 'com.gorylenko.gradle-git-properties' version '2.4.1' + id 'com.gorylenko.gradle-git-properties' version '2.4.2' } sourceSets { @@ -80,9 +80,9 @@ dependencies { // For the Resource Leak Checker's support for JavaEE. testImplementation 'javax.servlet:javax.servlet-api:4.0.1' // For the Resource Leak Checker's support for IOUtils. - testImplementation 'commons-io:commons-io:2.15.1' + testImplementation 'commons-io:commons-io:2.16.1' // For the Nullness Checker test of junit-assertions.astub in JUnitNull.java - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.3' testImplementation 'org.apiguardian:apiguardian-api:1.1.2' // For tests that use JSpecify annotations testImplementation 'org.jspecify:jspecify:0.3.0' diff --git a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterAnnotatedTypeFactory.java index 504be1a93bf7..ee429458c928 100644 --- a/checker/src/main/java/org/checkerframework/checker/formatter/FormatterAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/formatter/FormatterAnnotatedTypeFactory.java @@ -304,9 +304,12 @@ protected AnnotationMirror leastUpperBoundWithElements( ConversionCategory.intersect( shorterArgTypesList[i], longerArgTypesList[i]); } - for (int i = shorterArgTypesList.length; i < longerArgTypesList.length; ++i) { - resultArgTypes[i] = longerArgTypesList[i]; - } + System.arraycopy( + longerArgTypesList, + shorterArgTypesList.length, + resultArgTypes, + shorterArgTypesList.length, + longerArgTypesList.length - shorterArgTypesList.length); return treeUtil.categoriesToFormatAnnotation(resultArgTypes); } else if (qualifierKind1 == INVALIDFORMAT_KIND && qualifierKind2 == INVALIDFORMAT_KIND) { diff --git a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterAnnotatedTypeFactory.java index 0433f26211db..72ff676c2d7e 100644 --- a/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/i18nformatter/I18nFormatterAnnotatedTypeFactory.java @@ -298,9 +298,12 @@ protected AnnotationMirror leastUpperBoundWithElements( I18nConversionCategory.intersect( shorterArgTypesList[i], longerArgTypesList[i]); } - for (int i = shorterArgTypesList.length; i < longerArgTypesList.length; ++i) { - resultArgTypes[i] = longerArgTypesList[i]; - } + System.arraycopy( + longerArgTypesList, + shorterArgTypesList.length, + resultArgTypes, + shorterArgTypesList.length, + longerArgTypesList.length - shorterArgTypesList.length); return treeUtil.categoriesToFormatAnnotation(resultArgTypes); } else if (qualifierKind1 == I18NINVALIDFORMAT_KIND && qualifierKind2 == I18NINVALIDFORMAT_KIND) { diff --git a/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundAnnotatedTypeFactory.java index add0f7f22507..b799608322df 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/index/lowerbound/LowerBoundAnnotatedTypeFactory.java @@ -190,8 +190,8 @@ public void addComputedTypeAnnotations(Element element, AnnotatedTypeMirror type /** Handles cases 1, 2, and 3. */ @Override - public void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type, boolean iUseFlow) { - super.addComputedTypeAnnotations(tree, type, iUseFlow); + protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { + super.addComputedTypeAnnotations(tree, type); // If dataflow shouldn't be used to compute this type, then do not use the result from // the Value Checker, because dataflow is used to compute that type. (Without this, // "int i = 1; --i;" fails.) @@ -203,7 +203,7 @@ public void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type, bool // checker's type factory is parsing. && !ajavaTypes.isParsing() && TreeUtils.isExpressionTree(tree) - && (iUseFlow || tree instanceof LiteralTree)) { + && (getUseFlow() || tree instanceof LiteralTree)) { AnnotatedTypeMirror valueType = getValueAnnotatedTypeFactory().getAnnotatedType(tree); addLowerBoundTypeFromValueType(valueType, type); } diff --git a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundAnnotatedTypeFactory.java index ed90a94a4d40..5eaba43cee88 100644 --- a/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/index/upperbound/UpperBoundAnnotatedTypeFactory.java @@ -255,12 +255,12 @@ public void addComputedTypeAnnotations(Element element, AnnotatedTypeMirror type } @Override - public void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type, boolean iUseFlow) { - super.addComputedTypeAnnotations(tree, type, iUseFlow); + protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { + super.addComputedTypeAnnotations(tree, type); // If dataflow shouldn't be used to compute this type, then do not use the result from // the Value Checker, because dataflow is used to compute that type. (Without this, // "int i = 1; --i;" fails.) - if (iUseFlow + if (getUseFlow() && tree != null && !ajavaTypes.isParsing() && TreeUtils.isExpressionTree(tree)) { 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 037c703fbf0a..9cb5828fe092 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationVisitor.java @@ -34,7 +34,6 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Set; import java.util.StringJoiner; import javax.lang.model.element.AnnotationMirror; @@ -123,17 +122,16 @@ protected void checkThisOrSuperConstructorCall( @Override protected boolean commonAssignmentCheck( Tree varTree, - ExpressionTree valueExp, + ExpressionTree valueExpTree, @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 MemberSelectTree ExpressionTree lhs = (ExpressionTree) varTree; - ExpressionTree y = valueExp; VariableElement el = TreeUtils.variableElementFromUse(lhs); - AnnotatedTypeMirror xType = atypeFactory.getReceiverType(lhs); - AnnotatedTypeMirror yType = atypeFactory.getAnnotatedType(y); + AnnotatedTypeMirror lhsReceiverType = atypeFactory.getReceiverType(lhs); + AnnotatedTypeMirror valueExpType = atypeFactory.getAnnotatedType(valueExpTree); // the special FBC rules do not apply if there is an explicit // UnknownInitialization annotation AnnotationMirrorSet fieldAnnotations = @@ -141,11 +139,11 @@ protected boolean commonAssignmentCheck( if (!AnnotationUtils.containsSameByName( fieldAnnotations, atypeFactory.UNKNOWN_INITIALIZATION)) { if (!ElementUtils.isStatic(el) - && !(atypeFactory.isInitialized(yType) - || atypeFactory.isUnderInitialization(xType) - || atypeFactory.isFbcBottom(yType))) { + && !(atypeFactory.isInitialized(valueExpType) + || atypeFactory.isUnderInitialization(lhsReceiverType) + || atypeFactory.isFbcBottom(valueExpType))) { @CompilerMessageKey String err; - if (atypeFactory.isInitialized(xType)) { + if (atypeFactory.isInitialized(lhsReceiverType)) { err = COMMITMENT_INVALID_FIELD_WRITE_INITIALIZED; } else { err = COMMITMENT_INVALID_FIELD_WRITE_UNKNOWN_INITIALIZATION; @@ -155,7 +153,7 @@ protected boolean commonAssignmentCheck( } } } - return super.commonAssignmentCheck(varTree, valueExp, errorKey, extraArgs); + return super.commonAssignmentCheck(varTree, valueExpTree, errorKey, extraArgs); } @Override @@ -254,7 +252,7 @@ public Void visitVariable(VariableTree tree, Void p) { commonAssignmentTree = tree; // is this a field (and not a local variable)? if (TreeUtils.elementFromDeclaration(tree).getKind().isField()) { - Set annotationMirrors = + AnnotationMirrorSet annotationMirrors = atypeFactory.getAnnotatedType(tree).getExplicitAnnotations(); // Fields cannot have commitment annotations. for (Class c : atypeFactory.getSupportedTypeQualifiers()) { diff --git a/checker/src/main/java/org/checkerframework/checker/lock/LockAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/lock/LockAnnotatedTypeFactory.java index c855abfce41e..43c8882a3ed9 100644 --- a/checker/src/main/java/org/checkerframework/checker/lock/LockAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/lock/LockAnnotatedTypeFactory.java @@ -699,13 +699,13 @@ public void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) { } @Override - public void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type, boolean useFlow) { + protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { if (tree.getKind() == Tree.Kind.VARIABLE) { translateJcipAndJavaxAnnotations( TreeUtils.elementFromDeclaration((VariableTree) tree), type); } - super.addComputedTypeAnnotations(tree, type, useFlow); + super.addComputedTypeAnnotations(tree, type); } /** 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 71f29f5183db..b3b189cc238c 100644 --- a/checker/src/main/java/org/checkerframework/checker/signedness/SignednessAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/signedness/SignednessAnnotatedTypeFactory.java @@ -119,8 +119,7 @@ public SignednessAnnotatedTypeFactory(BaseTypeChecker checker) { } @Override - protected void addComputedTypeAnnotations( - Tree tree, AnnotatedTypeMirror type, boolean iUseFlow) { + protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { Tree.Kind treeKind = tree.getKind(); if (treeKind == Tree.Kind.INT_LITERAL) { int literalValue = (int) ((LiteralTree) tree).getValue(); @@ -139,8 +138,7 @@ protected void addComputedTypeAnnotations( } else if (!isComputingAnnotatedTypeMirrorOfLhs()) { addSignedPositiveAnnotation(tree, type); } - - super.addComputedTypeAnnotations(tree, type, iUseFlow); + super.addComputedTypeAnnotations(tree, type); } /** diff --git a/checker/src/main/java/org/checkerframework/checker/tainting/TaintingAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/tainting/TaintingAnnotatedTypeFactory.java index a92ec7a5f4fe..93b9cc755193 100644 --- a/checker/src/main/java/org/checkerframework/checker/tainting/TaintingAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/tainting/TaintingAnnotatedTypeFactory.java @@ -6,8 +6,6 @@ import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationMirrorSet; -import java.util.Set; - import javax.lang.model.element.AnnotationMirror; /** Annotated type factory for the Tainting Checker. */ @@ -32,7 +30,7 @@ public TaintingAnnotatedTypeFactory(BaseTypeChecker checker) { } @Override - protected Set getEnumConstructorQualifiers() { + protected AnnotationMirrorSet getEnumConstructorQualifiers() { return setOfUntainted; } } diff --git a/checker/src/main/java/org/checkerframework/checker/units/UnitsRelationsDefault.java b/checker/src/main/java/org/checkerframework/checker/units/UnitsRelationsDefault.java index 2706f9bc787c..7199398fc975 100644 --- a/checker/src/main/java/org/checkerframework/checker/units/UnitsRelationsDefault.java +++ b/checker/src/main/java/org/checkerframework/checker/units/UnitsRelationsDefault.java @@ -5,7 +5,6 @@ import org.checkerframework.checker.units.qual.Prefix; import org.checkerframework.checker.units.qual.g; import org.checkerframework.checker.units.qual.h; -import org.checkerframework.checker.units.qual.kg; import org.checkerframework.checker.units.qual.km2; import org.checkerframework.checker.units.qual.km3; import org.checkerframework.checker.units.qual.kmPERh; diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessGenericWildcardTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessGenericWildcardTest.java index 82f28abaab7c..59963e121906 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessGenericWildcardTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessGenericWildcardTest.java @@ -28,7 +28,7 @@ public NullnessGenericWildcardTest(List testFiles) { "nullness", // This test reads bytecode .class files created by NullnessGenericWildcardLibTest "-cp", - "dist/checker.jar:tests/build/testclasses/"); + TestUtilities.adapt("dist/checker.jar:tests/build/testclasses/")); } @Parameters diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSafeDefaultsSourceCodeTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSafeDefaultsSourceCodeTest.java index 605a3a022180..dc8e868d6172 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSafeDefaultsSourceCodeTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessSafeDefaultsSourceCodeTest.java @@ -29,7 +29,7 @@ public NullnessSafeDefaultsSourceCodeTest(List testFiles) { "nullness", "-AuseConservativeDefaultsForUncheckedCode=source", "-cp", - "dist/checker.jar:tests/build/testclasses/"); + TestUtilities.adapt("dist/checker.jar:tests/build/testclasses/")); } @Parameters diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessStubfileTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessStubfileTest.java index 02579ed0cb86..99101a6033f7 100644 --- a/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessStubfileTest.java +++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NullnessStubfileTest.java @@ -1,6 +1,7 @@ package org.checkerframework.checker.test.junit; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.checkerframework.framework.test.TestUtilities; import org.junit.runners.Parameterized.Parameters; import java.io.File; @@ -18,12 +19,10 @@ public NullnessStubfileTest(List testFiles) { testFiles, org.checkerframework.checker.nullness.NullnessChecker.class, "nullness", - "-Astubs=" - + String.join( - ":", - "tests/nullness-stubfile/stubfile1.astub", - "tests/nullness-stubfile/stubfile2.astub", - "tests/nullness-stubfile/requireNonNull.astub")); + TestUtilities.adapt( + "-Astubs=tests/nullness-stubfile/stubfile1.astub:" + + "tests/nullness-stubfile/stubfile2.astub:" + + "tests/nullness-stubfile/requireNonNull.astub")); } @Parameters diff --git a/checker/tests/ainfer-index/README b/checker/tests/ainfer-index/README deleted file mode 100644 index 6fc34a8cb64a..000000000000 --- a/checker/tests/ainfer-index/README +++ /dev/null @@ -1,30 +0,0 @@ -All tests for the -Ainfer command-line argument must be added to the -"non-annotated" folder. These tests have expected error comments -(// :: error...) in places where the type-checker issues an error before -inference but not after inference, and no other "// :: error". - -The task ainferTest tests the -Ainfer command-line argument in three -steps: - -1. The TestChecker will type-check all files in the "non-annotated" folder -using the -Ainfer command-liner argument, which write the inferred types of -some elements into annotation files. The inferred types are written into -annotation files, but are not considered during this type-check -- for that -reason the expected error comments are necessary. - -2. All tests in "non-annotated" are copied to a temporary directory, named -"annotated". All expected error comments are removed from the files in -"annotated". When testing `.jaif` files, the insert-annotations-to-source -tool inserts the annotations that were inferred in the previous step into -the files of the temporary directory. - -3. The TestChecker will type-check all files in the temporary "annotated" -folder. The expected error comments were removed, but the inferred types -(for `.jaif` files, they were inserted; for other annotation file tests, -they are used as stub files) should ensure that type-checking completes -without errors. - -If an error should persist even with the annotations produced by -Ainfer, -add the test to the "non-annotated/ExpectedErrors.java" file. This is the -only file where the expected error comments are not removed when copied to -"annotated/ExpectedErrors.java". diff --git a/checker/tests/ainfer-index/README b/checker/tests/ainfer-index/README new file mode 120000 index 000000000000..2c3e7d67d74c --- /dev/null +++ b/checker/tests/ainfer-index/README @@ -0,0 +1 @@ +../ainfer-README \ No newline at end of file diff --git a/checker/tests/ainfer-nullness/README b/checker/tests/ainfer-nullness/README deleted file mode 100644 index 6fc34a8cb64a..000000000000 --- a/checker/tests/ainfer-nullness/README +++ /dev/null @@ -1,30 +0,0 @@ -All tests for the -Ainfer command-line argument must be added to the -"non-annotated" folder. These tests have expected error comments -(// :: error...) in places where the type-checker issues an error before -inference but not after inference, and no other "// :: error". - -The task ainferTest tests the -Ainfer command-line argument in three -steps: - -1. The TestChecker will type-check all files in the "non-annotated" folder -using the -Ainfer command-liner argument, which write the inferred types of -some elements into annotation files. The inferred types are written into -annotation files, but are not considered during this type-check -- for that -reason the expected error comments are necessary. - -2. All tests in "non-annotated" are copied to a temporary directory, named -"annotated". All expected error comments are removed from the files in -"annotated". When testing `.jaif` files, the insert-annotations-to-source -tool inserts the annotations that were inferred in the previous step into -the files of the temporary directory. - -3. The TestChecker will type-check all files in the temporary "annotated" -folder. The expected error comments were removed, but the inferred types -(for `.jaif` files, they were inserted; for other annotation file tests, -they are used as stub files) should ensure that type-checking completes -without errors. - -If an error should persist even with the annotations produced by -Ainfer, -add the test to the "non-annotated/ExpectedErrors.java" file. This is the -only file where the expected error comments are not removed when copied to -"annotated/ExpectedErrors.java". diff --git a/checker/tests/ainfer-nullness/README b/checker/tests/ainfer-nullness/README new file mode 120000 index 000000000000..2c3e7d67d74c --- /dev/null +++ b/checker/tests/ainfer-nullness/README @@ -0,0 +1 @@ +../ainfer-README \ No newline at end of file diff --git a/checker/tests/ainfer-resourceleak/README b/checker/tests/ainfer-resourceleak/README deleted file mode 100644 index 6fc34a8cb64a..000000000000 --- a/checker/tests/ainfer-resourceleak/README +++ /dev/null @@ -1,30 +0,0 @@ -All tests for the -Ainfer command-line argument must be added to the -"non-annotated" folder. These tests have expected error comments -(// :: error...) in places where the type-checker issues an error before -inference but not after inference, and no other "// :: error". - -The task ainferTest tests the -Ainfer command-line argument in three -steps: - -1. The TestChecker will type-check all files in the "non-annotated" folder -using the -Ainfer command-liner argument, which write the inferred types of -some elements into annotation files. The inferred types are written into -annotation files, but are not considered during this type-check -- for that -reason the expected error comments are necessary. - -2. All tests in "non-annotated" are copied to a temporary directory, named -"annotated". All expected error comments are removed from the files in -"annotated". When testing `.jaif` files, the insert-annotations-to-source -tool inserts the annotations that were inferred in the previous step into -the files of the temporary directory. - -3. The TestChecker will type-check all files in the temporary "annotated" -folder. The expected error comments were removed, but the inferred types -(for `.jaif` files, they were inserted; for other annotation file tests, -they are used as stub files) should ensure that type-checking completes -without errors. - -If an error should persist even with the annotations produced by -Ainfer, -add the test to the "non-annotated/ExpectedErrors.java" file. This is the -only file where the expected error comments are not removed when copied to -"annotated/ExpectedErrors.java". diff --git a/checker/tests/ainfer-resourceleak/README b/checker/tests/ainfer-resourceleak/README new file mode 120000 index 000000000000..2c3e7d67d74c --- /dev/null +++ b/checker/tests/ainfer-resourceleak/README @@ -0,0 +1 @@ +../ainfer-README \ No newline at end of file diff --git a/checker/tests/ainfer-testchecker/README b/checker/tests/ainfer-testchecker/README deleted file mode 100644 index 6fc34a8cb64a..000000000000 --- a/checker/tests/ainfer-testchecker/README +++ /dev/null @@ -1,30 +0,0 @@ -All tests for the -Ainfer command-line argument must be added to the -"non-annotated" folder. These tests have expected error comments -(// :: error...) in places where the type-checker issues an error before -inference but not after inference, and no other "// :: error". - -The task ainferTest tests the -Ainfer command-line argument in three -steps: - -1. The TestChecker will type-check all files in the "non-annotated" folder -using the -Ainfer command-liner argument, which write the inferred types of -some elements into annotation files. The inferred types are written into -annotation files, but are not considered during this type-check -- for that -reason the expected error comments are necessary. - -2. All tests in "non-annotated" are copied to a temporary directory, named -"annotated". All expected error comments are removed from the files in -"annotated". When testing `.jaif` files, the insert-annotations-to-source -tool inserts the annotations that were inferred in the previous step into -the files of the temporary directory. - -3. The TestChecker will type-check all files in the temporary "annotated" -folder. The expected error comments were removed, but the inferred types -(for `.jaif` files, they were inserted; for other annotation file tests, -they are used as stub files) should ensure that type-checking completes -without errors. - -If an error should persist even with the annotations produced by -Ainfer, -add the test to the "non-annotated/ExpectedErrors.java" file. This is the -only file where the expected error comments are not removed when copied to -"annotated/ExpectedErrors.java". diff --git a/checker/tests/ainfer-testchecker/README b/checker/tests/ainfer-testchecker/README new file mode 120000 index 000000000000..2c3e7d67d74c --- /dev/null +++ b/checker/tests/ainfer-testchecker/README @@ -0,0 +1 @@ +../ainfer-README \ No newline at end of file diff --git a/dataflow/build.gradle b/dataflow/build.gradle index da91d39cc4a2..c78c1cd2b604 100644 --- a/dataflow/build.gradle +++ b/dataflow/build.gradle @@ -66,6 +66,9 @@ task manual(group: 'Documentation') { } tasks.withType(Test) { + // Disable the gradle generated test task as the dataflow framework does not use JUnit for testing. If it were kept enabled (which is the default), gradlew would produce a deprecation warning. + // The `allDataflowTests` task dependency is still enabled. + enabled = false dependsOn('allDataflowTests') } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BooleanLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BooleanLiteralNode.java index 8aa75272d258..35edef75a06f 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BooleanLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/BooleanLiteralNode.java @@ -4,10 +4,6 @@ import com.sun.source.tree.Tree; import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; - -import java.util.Collection; -import java.util.Collections; /** * A node for a boolean literal: @@ -48,10 +44,4 @@ public boolean equals(@Nullable Object obj) { // super method compares values return super.equals(obj); } - - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CharacterLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CharacterLiteralNode.java index 54d14bdf1aa8..91c27e85ebb5 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CharacterLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/CharacterLiteralNode.java @@ -4,10 +4,6 @@ import com.sun.source.tree.Tree; import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; - -import java.util.Collection; -import java.util.Collections; /** * A node for a character literal. For example: @@ -49,10 +45,4 @@ public boolean equals(@Nullable Object obj) { // super method compares values return super.equals(obj); } - - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/DoubleLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/DoubleLiteralNode.java index 773aec3888d8..4907b234f402 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/DoubleLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/DoubleLiteralNode.java @@ -4,10 +4,6 @@ import com.sun.source.tree.Tree; import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; - -import java.util.Collection; -import java.util.Collections; /** * A node for a double literal. For example: @@ -48,10 +44,4 @@ public boolean equals(@Nullable Object obj) { // super method compares values return super.equals(obj); } - - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatLiteralNode.java index 0b10c4173619..a7ad2b4b44c7 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/FloatLiteralNode.java @@ -4,10 +4,6 @@ import com.sun.source.tree.Tree; import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; - -import java.util.Collection; -import java.util.Collections; /** * A node for a float literal. For example: @@ -48,10 +44,4 @@ public boolean equals(@Nullable Object obj) { // super method compares values return super.equals(obj); } - - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerLiteralNode.java index 470a43b182c9..b7c05eaa9d2b 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/IntegerLiteralNode.java @@ -4,10 +4,6 @@ import com.sun.source.tree.Tree; import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; - -import java.util.Collection; -import java.util.Collections; /** * A node for an integer literal. For example: @@ -47,10 +43,4 @@ public boolean equals(@Nullable Object obj) { // super method compares values return super.equals(obj); } - - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LongLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LongLiteralNode.java index 17d9bb36f22f..081633ead10b 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LongLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/LongLiteralNode.java @@ -4,10 +4,6 @@ import com.sun.source.tree.Tree; import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; - -import java.util.Collection; -import java.util.Collections; /** * A node for a long literal. For example: @@ -48,10 +44,4 @@ public boolean equals(@Nullable Object obj) { // super method compares values return super.equals(obj); } - - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NullLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NullLiteralNode.java index bf83ae785e4d..574e1fbe8dd6 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NullLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NullLiteralNode.java @@ -4,10 +4,6 @@ import com.sun.source.tree.Tree; import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; - -import java.util.Collection; -import java.util.Collections; /** * A node for the null literal. @@ -49,10 +45,4 @@ public boolean equals(@Nullable Object obj) { // super method compares values return super.equals(obj); } - - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ShortLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ShortLiteralNode.java index 1368d5757356..8da1646c07a7 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ShortLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ShortLiteralNode.java @@ -4,10 +4,6 @@ import com.sun.source.tree.Tree; import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; - -import java.util.Collection; -import java.util.Collections; /** * A node for a short literal. For example: @@ -52,10 +48,4 @@ public boolean equals(@Nullable Object obj) { // super method compares values return super.equals(obj); } - - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringLiteralNode.java index 6613dcbedb76..7ba89c61b74f 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/StringLiteralNode.java @@ -4,10 +4,6 @@ import com.sun.source.tree.Tree; import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.dataflow.qual.SideEffectFree; - -import java.util.Collection; -import java.util.Collections; /** * A node for an string literal. For example: @@ -48,12 +44,6 @@ public boolean equals(@Nullable Object obj) { return super.equals(obj); } - @Override - @SideEffectFree - public Collection getOperands() { - return Collections.emptyList(); - } - @Override public String toString() { return "\"" + super.toString() + "\""; diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ValueLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ValueLiteralNode.java index b412449bd0ea..8a461a452643 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ValueLiteralNode.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ValueLiteralNode.java @@ -73,7 +73,7 @@ public int hashCode() { @Override @SideEffectFree - public Collection getOperands() { + public final Collection getOperands() { return Collections.emptyList(); } } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeLauncher.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeLauncher.java index 77502f942caa..8b2c6a283605 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeLauncher.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/visualize/CFGVisualizeLauncher.java @@ -300,7 +300,7 @@ public static void writeStringOfCFG( if (res != null && res.get("stringGraph") != null) { out.write(res.get("stringGraph").toString()); } - out.write("\n"); + out.write(System.lineSeparator()); } catch (IOException e) { e.printStackTrace(); } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 984963fe7df5..2d7ee7b2beb8 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,3 +1,27 @@ +Version 3.42.0-eisop4 (July 12, 2024) +------------------------------------- + +**Implementation details:** + +New method `GenericAnnotatedTypeFactory#addComputedTypeAnnotationsWithoutFlow(Tree, AnnotatedTypeMirror)` +that sets `useFlow` to `false` before calling `addComputedTypeAnnotations`. Subclasses should override +method `GenericAnnotatedTypeFactory#addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)` instead. +Deprecated the `GenericAnnotatedTypeFactory#addComputedTypeAnnotations(Tree, AnnotatedTypeMirror, boolean)` +overload. + +Changed the return type of `AnnotatedTypeFactory#getEnumConstructorQualifiers` from `Set` +to `AnnotationMirrorSet`. + +framework-test: +- Improvements to more consistently handle tests that do not use `-Anomsgtext`. +- Added new class `DetailedTestDiagnostic` to directly represent test diagnostics when + `-Adetailedmsgtext` is used. + +**Closed issues:** + +eisop#742, eisop#777, eisop#795, typetools#6704. + + Version 3.42.0-eisop3 (March 1, 2024) ------------------------------------- diff --git a/docs/checker-framework-webpage.html b/docs/checker-framework-webpage.html index 53e4b20bd0e7..38570a095ed8 100644 --- a/docs/checker-framework-webpage.html +++ b/docs/checker-framework-webpage.html @@ -30,8 +30,8 @@

The Checker Framework

Installation instructions and tutorial.
  • - Download: checker-framework-3.42.0-eisop3.zip - (1 Mar 2024); + Download: checker-framework-3.42.0-eisop4.zip + (12 Jul 2024); includes source, platform-independent binary, tests, and documentation.
    Then, see the installation @@ -93,7 +93,7 @@

    The Checker Framework

    the .class file. The tools support both Java 5 declaration annotations and Java 8 type annotations.
      -
    • annotation-tools-3.42.0-eisop3.zip (01 Mar 2024) +
    • annotation-tools-3.42.0-eisop4.zip (12 Jul 2024)
    • source code repository
    • @@ -229,7 +229,7 @@

      Mailing lists


      -Last updated: 1 Mar 2024 +Last updated: 12 Jul 2024

      diff --git a/docs/examples/MavenExample/pom.xml b/docs/examples/MavenExample/pom.xml index 085b334e7c56..86244c3623ba 100644 --- a/docs/examples/MavenExample/pom.xml +++ b/docs/examples/MavenExample/pom.xml @@ -14,7 +14,7 @@ UTF-8 8 8 - 3.42.0-eisop3 + 3.42.0-eisop4 diff --git a/docs/examples/errorprone/build.gradle b/docs/examples/errorprone/build.gradle index d78c3e520fbe..bed125a9d319 100644 --- a/docs/examples/errorprone/build.gradle +++ b/docs/examples/errorprone/build.gradle @@ -6,7 +6,7 @@ plugins { id 'java' id 'net.ltgt.errorprone' version '3.1.0' // Checker Framework pluggable type-checking - id 'org.checkerframework' version '0.6.35' + id 'org.checkerframework' version '0.6.40' } ext { diff --git a/docs/examples/lombok/build.gradle b/docs/examples/lombok/build.gradle index eff2215d73f9..b793c2178a5c 100644 --- a/docs/examples/lombok/build.gradle +++ b/docs/examples/lombok/build.gradle @@ -6,7 +6,7 @@ plugins { id 'java' id 'io.freefair.lombok' version '8.3' // Checker Framework pluggable type-checking - id 'org.checkerframework' version '0.6.35' + id 'org.checkerframework' version '0.6.40' } lombok { diff --git a/docs/manual/Makefile b/docs/manual/Makefile index f133e41b9405..233b7db095a3 100644 --- a/docs/manual/Makefile +++ b/docs/manual/Makefile @@ -73,14 +73,17 @@ manual.html: manual.pdf CFLogo.png favicon-checkerframework.png ../api # Add CSS styling for some links, since \ahrefloc doesn't permit styling sed -i -e 's%\(&\#X1F517;\)%\1 style="color:inherit; text-decoration:none"\2%g' manual.html +../../checker/bin-devel/.git-scripts: + cd ../.. && ./gradlew --stacktrace getGitScripts + ../../checker/bin-devel/.plume-scripts: cd ../.. && ./gradlew --stacktrace getPlumeScripts .PHONY: contributors.tex contributors.tex: -# Update plume-scripts even if it is already cloned - cd ../.. && (./gradlew --stacktrace getPlumeScripts || (sleep 60 && ./gradlew --stacktrace getPlumeScripts)) - ../../checker/bin-devel/.plume-scripts/git-authors --latex --punctuation > contributors.tex +# Update git-scripts even if it is already cloned + cd ../.. && (./gradlew --stacktrace getGitScripts || (sleep 60 && ./gradlew --stacktrace getGitScripts)) + ../../checker/bin-devel/.git-scripts/git-authors --latex --punctuation > contributors.tex ../api: cd ../.. && ./gradlew allJavadoc diff --git a/docs/manual/contributors.tex b/docs/manual/contributors.tex index 0910dc091dfe..35061db05c54 100644 --- a/docs/manual/contributors.tex +++ b/docs/manual/contributors.tex @@ -114,6 +114,7 @@ Ren\'e Just, Ren\'e Kraneis, Rob Bygrave, +Rohan Shetty, Ruturaj Mohanty, Ryan Oblak, Sadaf Tajik, @@ -139,6 +140,8 @@ Vatsal Sura, Vladimir Sitnikov, Vlastimil Dort, +Weilan Tao, Weitian Xing, Werner Dietl, +Yuliia Nortman, Zhiping Cai. diff --git a/docs/manual/creating-a-checker.tex b/docs/manual/creating-a-checker.tex index 6378a255ddc5..ad0e067cdddf 100644 --- a/docs/manual/creating-a-checker.tex +++ b/docs/manual/creating-a-checker.tex @@ -1356,9 +1356,8 @@ Create a subclass of \refclass{framework/type}{AnnotatedTypeFactory} and override two \ methods: \refmethodanchortext{framework/type}{AnnotatedTypeFactory}{addComputedTypeAnnotations}{(com.sun.source.tree.Tree,org.checkerframework.framework.type.AnnotatedTypeMirror)}{addComputedTypeAnnotations(Tree,AnnotatedTypeMirror)} - (or - \refmethodanchortext{framework/type}{GenericAnnotatedTypeFactory}{addComputedTypeAnnotations}{(com.sun.source.tree.Tree,org.checkerframework.framework.type.AnnotatedTypeMirror,boolean)}{addComputedTypeAnnotations(Tree,AnnotatedTypeMirror,boolean)} - if extending \code{GenericAnnotatedTypeFactory}) + (use \refmethodanchortext{framework/type}{GenericAnnotatedTypeFactory}{addComputedTypeAnnotationsWithoutFlow}{(com.sun.source.tree.Tree,org.checkerframework.framework.type.AnnotatedTypeMirror)}{addComputedTypeAnnotationsWithoutFlow(Tree,AnnotatedTypeMirror)} + in subclasses of \code{GenericAnnotatedTypeFactory} if you want to add computed type annotations to a type without using flow information.) and \refmethodanchortext{framework/type}{AnnotatedTypeFactory}{addComputedTypeAnnotations}{(javax.lang.model.element.Element,org.checkerframework.framework.type.AnnotatedTypeMirror)}{addComputedTypeAnnotations(Element,AnnotatedTypeMirror)}. The methods can make arbitrary changes to the annotations on a type. diff --git a/docs/manual/external-tools.tex b/docs/manual/external-tools.tex index 3f12a18ea10e..e31a3f9e7028 100644 --- a/docs/manual/external-tools.tex +++ b/docs/manual/external-tools.tex @@ -119,7 +119,7 @@ \begin{Verbatim} dependencies { ... existing dependencies... - ext.checkerFrameworkVersion = '3.42.0-eisop3' + ext.checkerFrameworkVersion = '3.42.0-eisop4' implementation "io.github.eisop:checker-qual-android:${checkerFrameworkVersion}" // or if you use no annotations in source code the above line could be // compileOnly "io.github.eisop:checker-qual-android:${checkerFrameworkVersion}" @@ -192,7 +192,7 @@ \begin{Verbatim} dependencies { ... existing dependencies... - ext.checkerFrameworkVersion = '3.42.0-eisop3' + ext.checkerFrameworkVersion = '3.42.0-eisop4' implementation "io.github.eisop:checker-qual-android:${checkerFrameworkVersion}" // or if you use no annotations in source code the above line could be // compileOnly "io.github.eisop:checker-qual-android:${checkerFrameworkVersion}" @@ -350,13 +350,13 @@ \begin{Verbatim} prebuilt_jar( name = 'checker-framework', - binary_jar = 'checker-3.42.0-eisop3.jar', + binary_jar = 'checker-3.42.0-eisop4.jar', visibility = [ 'PUBLIC' ] ) prebuilt_jar( name = 'checker-qual', - binary_jar = 'checker-qual-3.42.0-eisop3.jar', + binary_jar = 'checker-qual-3.42.0-eisop4.jar', visibility = [ 'PUBLIC' ] ) @@ -420,21 +420,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.42.0-eisop3/>. +other than \<\${HOME}/checker-framework-3.42.0-eisop4/>. \begin{itemize} \item Option 1: Add directory - \code{.../checker-framework-3.42.0-eisop3/checker/bin} to your path, \emph{before} any other + \code{.../checker-framework-3.42.0-eisop4/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.42.0-eisop3 + export CHECKERFRAMEWORK=${HOME}/checker-framework-3.42.0-eisop4 export PATH=${CHECKERFRAMEWORK}/checker/bin:${PATH} \end{Verbatim} @@ -455,7 +455,7 @@ file: % No Windows example because this doesn't work under Windows. \begin{Verbatim} - export CHECKERFRAMEWORK=${HOME}/checker-framework-3.42.0-eisop3 + export CHECKERFRAMEWORK=${HOME}/checker-framework-3.42.0-eisop4 alias javacheck='$CHECKERFRAMEWORK/checker/bin/javac' \end{Verbatim} @@ -477,11 +477,11 @@ \begin{Verbatim} # Unix - export CHECKERFRAMEWORK=${HOME}/checker-framework-3.42.0-eisop3 + export CHECKERFRAMEWORK=${HOME}/checker-framework-3.42.0-eisop4 alias javacheck='java -jar "$CHECKERFRAMEWORK/checker/dist/checker.jar"' # Windows - set CHECKERFRAMEWORK = C:\Program Files\checker-framework-3.42.0-eisop3\ + set CHECKERFRAMEWORK = C:\Program Files\checker-framework-3.42.0-eisop4\ doskey javacheck=java -jar "%CHECKERFRAMEWORK%\checker\dist\checker.jar" $* \end{Verbatim} @@ -560,9 +560,9 @@ \begin{itemize} \item \: \url{https://search.maven.org/artifact/com.google.errorprone/javac/9%2B181-r4173-1/jar} -\item \: \url{https://repo1.maven.org/maven2/io/github/eisop/checker-qual/3.42.0-eisop3/checker-qual-3.42.0-eisop3.jar} -\item \: \url{https://repo1.maven.org/maven2/io/github/eisop/checker-util/3.42.0-eisop3/checker-util-3.42.0-eisop3.jar} -\item \: \url{https://repo1.maven.org/maven2/io/github/eisop/checker/3.42.0-eisop3/checker-3.42.0-eisop3-all.jar} +\item \: \url{https://repo1.maven.org/maven2/io/github/eisop/checker-qual/3.42.0-eisop4/checker-qual-3.42.0-eisop4.jar} +\item \: \url{https://repo1.maven.org/maven2/io/github/eisop/checker-util/3.42.0-eisop4/checker-util-3.42.0-eisop4.jar} +\item \: \url{https://repo1.maven.org/maven2/io/github/eisop/checker/3.42.0-eisop4/checker-3.42.0-eisop4-all.jar} \end{itemize} Different arguments to \ are required for JDK 8 diff --git a/docs/manual/introduction.tex b/docs/manual/introduction.tex index e47113b445b3..f5374c84eb6a 100644 --- a/docs/manual/introduction.tex +++ b/docs/manual/introduction.tex @@ -205,7 +205,7 @@ %BEGIN LATEX \\ %END LATEX - \url{https://eisop.github.io/cf/checker-framework-3.42.0-eisop3.zip} + \url{https://eisop.github.io/cf/checker-framework-3.42.0-eisop4.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 fa935863e495..d7b83a1e7792 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://eisop.github.io/}} -\newcommand{\ReleaseVersion}{3.42.0-eisop3} -\newcommand{\ReleaseInfo}{3.42.0-eisop3 (1 Mar 2024)} +\newcommand{\ReleaseVersion}{3.42.0-eisop4} +\newcommand{\ReleaseInfo}{3.42.0-eisop4 (12 Jul 2024)} \date{Version \ReleaseInfo{}} \begin{document} diff --git a/docs/manual/nullness-checker.tex b/docs/manual/nullness-checker.tex index 4679a7497cf6..301082391b73 100644 --- a/docs/manual/nullness-checker.tex +++ b/docs/manual/nullness-checker.tex @@ -11,15 +11,18 @@ \refqualclass{checker/nullness/qual}{NonNull} and \refqualclass{checker/nullness/qual}{Nullable}. \refqualclass{checker/nullness/qual}{NonNull} is rarely written, because it is -the default. All of the annotations are explained in +the default in most locations. All of the annotations are explained in Section~\ref{nullness-annotations}. +See Section~\ref{writing-nullness-annotations} for details on how to +use the annotations and Section~\ref{generics} for details on +generics. To run the Nullness Checker, supply the \code{-processor org.checkerframework.checker.nullness.NullnessChecker} command-line option to javac. For examples, see Section~\ref{nullness-example}. -The NullnessChecker is actually an ensemble of three pluggable +The Nullness Checker is actually an ensemble of three pluggable type-checkers that work together: the Nullness Checker proper (which is the main focus of this chapter), the Initialization Checker (Section~\ref{initialization-checker}), and the Map Key Checker @@ -59,7 +62,7 @@ is a misuse of the type: the null value could flow to a dereference that the checker does not warn about. - As a special case of an of \refqualclass{checker/nullness/qual}{NonNull} + As a special case of an \refqualclass{checker/nullness/qual}{NonNull} type becoming null, the checker also warns whenever a field of \refqualclass{checker/nullness/qual}{NonNull} type is not initialized in a constructor. @@ -177,7 +180,7 @@ \item If you supply the \code{-Alint=permitClearProperty} command-line option, then the checker permits calls to - \sunjavadoc{java.base/java/lang/System.html\#getProperties()}{System.setProperties()} + \sunjavadoc{java.base/java/lang/System.html\#setProperties(java.util.Properties)}{System.setProperties} and calls to \sunjavadoc{java.base/java/lang/System.html\#clearProperty(java.lang.String)}{System.clearProperty} that might clear one of the built-in properties. @@ -186,7 +189,7 @@ special-cases type-checking of calls to \sunjavadoc{java.base/java/lang/System.html\#getProperty(java.lang.String,java.lang.String)}{System.getProperty()} and - \sunjavadoc{java.base/java/lang/System.html\#setProperties(java.util.Properties)}{System.setProperties()}. + \sunjavadoc{java.base/java/lang/System.html\#setProperties(java.util.Properties)}{System.setProperties}. A call to one of these methods can return null in general, but by default the Nullness Checker treats it as returning non-null if the argument is one of the literal strings @@ -209,7 +212,7 @@ The Nullness Checker uses three separate type hierarchies: one for nullness, one for initialization (Section~\ref{initialization-checker}), -and one for map keys (\chapterpageref{map-key-checker}) +and one for map keys (\chapterpageref{map-key-checker}). The Nullness Checker has four varieties of annotations: nullness type qualifiers, nullness method annotations, initialization type qualifiers, and map key type @@ -1247,7 +1250,7 @@ Framework does not, please report it to us (see Section~\ref{reporting-bugs}) so that we can enhance the Checker Framework. For example, SpotBugs might detect an error that the Nullness Checker does -not, if you are using an unnannotated library (including an unannotated +not, if you are using an unannotated library (including an unannotated part of the JDK) and running the Checker Framework in an unsound mode (see Section~\ref{checker-options}). diff --git a/docs/manual/troubleshooting.tex b/docs/manual/troubleshooting.tex index 1f335adfc077..d16b29b0da60 100644 --- a/docs/manual/troubleshooting.tex +++ b/docs/manual/troubleshooting.tex @@ -768,7 +768,7 @@ brew update brew install git ant hevea maven librsvg unzip make brew tap homebrew/cask-versions -brew install --cask temurin17 +brew install --cask temurin@17 brew install --cask mactex \end{Verbatim} @@ -822,6 +822,9 @@ You might want to add an \ line to your \<.bashrc> file. +Ensure line endings are handled properly. If necessary, run +\code{git config --global core.autocrlf true} to prevent line ending issues between +Windows and Unix-based systems. \subsectionAndLabel{Build the Checker Framework}{building} 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 953ddb5b1f48..a7dda01afc25 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 @@ -9,10 +9,12 @@ import org.plumelib.util.StringsPlume; import org.plumelib.util.SystemPlume; +import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; +import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.OutputStream; @@ -143,7 +145,6 @@ public static List> findJavaFilesPerDirectory(File parent, String... "test parent directory is not a directory: %s %s", parent, parent.getAbsoluteFile()); } - List> filesPerDirectory = new ArrayList<>(); for (String dirName : dirNames) { @@ -151,8 +152,7 @@ public static List> findJavaFilesPerDirectory(File parent, String... if (dir.isDirectory()) { filesPerDirectory.addAll(findJavaTestFilesInDirectory(dir)); } else { - // `dir` is not an existent directory. - + // `dir` is not an existing directory. // If delombok does not yet work on a given JDK, this directory does not exist. if (dir.getName().contains("delomboked")) { continue; @@ -164,6 +164,29 @@ public static List> findJavaFilesPerDirectory(File parent, String... && dir.getParentFile().getName().startsWith("ainfer-")) { continue; } + // When this reaches a sym-linked dir like all-system, Windows needs to explicitly + // read the content recorded in this file, which is the path to the real dir. + // Without this check Windows will treat the file as a meaningless one and skip it. + if (dir.isFile()) { + File p = dir; + try (BufferedReader br = new BufferedReader(new FileReader(dir))) { + String allSystemPath = br.readLine(); + if (allSystemPath == null) { + throw new BugInCF("test directory does not exist: %s", dir); + } + p = + new File(parent, allSystemPath.replace("/", File.separator)) + .toPath() + .toAbsolutePath() + .normalize() + .toFile(); + + } catch (IOException e) { + throw new BugInCF("file is not readable: %s", dir); + } + filesPerDirectory.addAll(findJavaTestFilesInDirectory(p)); + continue; + } throw new BugInCF("test directory does not exist: %s", dir); } @@ -545,4 +568,15 @@ public static void ensureDirectoryExists(String dir) { public static boolean getShouldEmitDebugInfo() { return SystemPlume.getBooleanSystemProperty("emit.test.debug"); } + + /** + * Adapt a string that uses Unix file and path separators to use the correct operating system + * separator. + * + * @param input a path with Unix file and path separators + * @return a path with the correct operating system separator + */ + public static String adapt(String input) { + return input.replace("/", File.separator).replace(":", File.pathSeparator); + } } diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckResult.java b/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckResult.java index 79ba951ef7a6..d397031cea14 100644 --- a/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckResult.java +++ b/framework-test/src/main/java/org/checkerframework/framework/test/TypecheckResult.java @@ -145,10 +145,8 @@ public static TypecheckResult fromCompilationResults( CompilationResult result, List expectedDiagnostics) { - boolean usingAnomsgtxt = configuration.getOptions().containsKey("-Anomsgtext"); Set actualDiagnostics = - TestDiagnosticUtils.fromJavaxDiagnosticList( - result.getDiagnostics(), usingAnomsgtxt); + TestDiagnosticUtils.fromJavaxToolsDiagnosticList(result.getDiagnostics()); Set unexpectedDiagnostics = new LinkedHashSet<>(); unexpectedDiagnostics.addAll(actualDiagnostics); diff --git a/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/DetailedTestDiagnostic.java b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/DetailedTestDiagnostic.java new file mode 100644 index 000000000000..9acd3405fda1 --- /dev/null +++ b/framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/DetailedTestDiagnostic.java @@ -0,0 +1,134 @@ +package org.checkerframework.framework.test.diagnostics; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; +import java.util.StringJoiner; + +/** + * Represents a detailed error/warning message reported by the Checker Framework when the {@code + * -Adetailedmsgtext} flag is used. By contrast, {@link TestDiagnostic} represents a simple expected + * error/warning message in a Java test file or an error/warning reported by the Java compiler + * without the {@code -Adetailedmsgtext} flag. + */ +public class DetailedTestDiagnostic extends TestDiagnostic { + + /** Additional tokens that are part of the diagnostic message. */ + protected final List additionalTokens; + + /** The start position of the diagnostic in the source file. */ + protected final long startPosition; + + /** The end position of the diagnostic in the source file. */ + protected final long endPosition; + + /** + * Create a new instance. + * + * @param file the file in which the diagnostic occurred + * @param lineNo the line number in the file at which the diagnostic occurred + * @param kind the kind of diagnostic (error or warning) + * @param messageKey a message key that usually appears between parentheses in diagnostic + * messages + * @param additionalTokens additional tokens that are part of the diagnostic message + * @param startPosition the start position of the diagnostic in the source file + * @param endPosition the end position of the diagnostic in the source file + * @param readableMessage a human-readable message describing the diagnostic + * @param isFixable whether the diagnostic is fixable + */ + public DetailedTestDiagnostic( + Path file, + long lineNo, + DiagnosticKind kind, + String messageKey, + List additionalTokens, + long startPosition, + long endPosition, + String readableMessage, + boolean isFixable) { + super(file, lineNo, kind, messageKey, readableMessage, isFixable); + + this.additionalTokens = additionalTokens; + this.startPosition = startPosition; + this.endPosition = endPosition; + } + + /** + * The additional tokens that are part of the diagnostic message. + * + * @return the additional tokens + */ + public List getAdditionalTokens() { + return additionalTokens; + } + + /** + * The start position of the diagnostic in the source file. + * + * @return the start position + */ + public long getStartPosition() { + return startPosition; + } + + /** + * The end position of the diagnostic in the source file. + * + * @return the end position + */ + public long getEndPosition() { + return endPosition; + } + + /** + * Equality is compared without isFixable and messageKeyParens. + * + * @return true if this and otherObj are equal according to additionalTokens, startPosition, + * endPosition, and equality of the superclass. + */ + @Override + public boolean equals(@Nullable Object otherObj) { + if (!(otherObj instanceof DetailedTestDiagnostic)) { + return false; + } + DetailedTestDiagnostic other = (DetailedTestDiagnostic) otherObj; + return super.equals(other) + && additionalTokens.equals(other.additionalTokens) + && startPosition == other.startPosition + && endPosition == other.endPosition; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), additionalTokens, startPosition, endPosition); + } + + /** + * Returns a representation of this diagnostic as if it appeared as a detailed message. + * + * @return a representation of this diagnostic as if it appeared as a detailed message + * @see + * org.checkerframework.framework.source.SourceChecker#detailedMsgTextPrefix(Object,String,Object[]) + */ + @Override + public String toString() { + // Keep in sync with SourceChecker.DETAILS_SEPARATOR. + StringJoiner sj = new StringJoiner(" $$ "); + + sj.add("(" + messageKey + ")"); + if (additionalTokens != null) { + sj.add(Integer.toString(additionalTokens.size())); + for (String token : additionalTokens) { + sj.add(token); + } + } else { + sj.add("0"); + } + + sj.add(String.format("( %d, %d )", startPosition, endPosition)); + sj.add(getMessage()); + return sj.toString(); + } +} 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 f5af143d7f99..2e914f98a26e 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 @@ -11,6 +11,8 @@ public enum DiagnosticKind { Warning("warning"), /** An error. */ Error("error"), + /** A note. */ + Note("Note"), /** Something else. */ Other("other"); 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 d10259528023..4c497203bbe8 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 @@ -2,85 +2,216 @@ import org.checkerframework.checker.nullness.qual.Nullable; +import java.nio.file.Path; import java.util.Objects; /** * Represents an expected error/warning message in a Java test file or an error/warning reported by - * the Javac compiler. By contrast, {@link TestDiagnosticLine} represents a set of TestDiagnostics, - * all of which were read from the same line of a file. + * the Java compiler. By contrast, {@link TestDiagnosticLine} represents a set of TestDiagnostics, + * all of which were read from the same line of a file. Subclass {@link DetailedTestDiagnostic} is + * used when the Checker Framework is invoked with the {@code -Adetailedmsgtext} flag. * * @see JavaDiagnosticReader + * @see TestDiagnosticLine + * @see DetailedTestDiagnostic */ public class TestDiagnostic { - private final String filename; - private final long lineNumber; - private final DiagnosticKind kind; + /** The path to the test file. */ + protected final Path file; + + /** The base file name of the test file. */ + protected final String filename; + + /** The line number of the diagnostic output. */ + protected final long lineNumber; + + /** The diagnostic kind of the output. */ + protected final DiagnosticKind kind; + + /** The full diagnostic message. */ + protected final String message; /** - * An error key or full error message that usually appears between parentheses in diagnostic - * messages. + * The message key that usually appears between parentheses in diagnostic messages. Parentheses + * are removed and field messageKeyParens indicates whether they were present. */ - private final String message; + protected final String messageKey; + + /** Whether the message key had parentheses around it. */ + protected final boolean messageKeyParens; /** Whether this diagnostic should no longer be reported after whole program inference. */ - private final boolean isFixable; + protected final boolean isFixable; /** - * Whether or not the toString representation should omit the parentheses around the message. + * Basic constructor that sets the immutable fields of this diagnostic. + * + * @param file the path to the test file + * @param lineNumber the line number of the diagnostic output + * @param kind the diagnostic kind of the output + * @param messageKey the message key + * @param message the full diagnostic message + * @param isFixable whether WPI can fix the test */ - private final boolean omitParentheses; - - /** Basic constructor that sets the immutable fields of this diagnostic. */ public TestDiagnostic( - String filename, + Path file, long lineNumber, DiagnosticKind kind, + String messageKey, String message, - boolean isFixable, - boolean omitParentheses) { - this.filename = filename; + boolean isFixable) { + this.file = file; + this.filename = + file.getFileName() != null ? file.getFileName().toString() : file.toString(); + this.lineNumber = lineNumber; + this.kind = kind; + this.message = message; + this.isFixable = isFixable; + + // Keep in sync with code below. + int open = messageKey.indexOf("("); + int close = messageKey.indexOf(")"); + if (open == 0 && close > open) { + this.messageKey = messageKey.substring(open + 1, close).trim(); + this.messageKeyParens = true; + } else { + this.messageKey = messageKey; + this.messageKeyParens = false; + } + } + + /** + * Basic constructor that sets the immutable fields of this diagnostic. + * + * @param file the path to the test file + * @param lineNumber the line number of the diagnostic output + * @param kind the diagnostic kind of the output + * @param message the full diagnostic message + * @param isFixable whether WPI can fix the test + */ + public TestDiagnostic( + Path file, long lineNumber, DiagnosticKind kind, String message, boolean isFixable) { + this.file = file; + this.filename = + file.getFileName() != null ? file.getFileName().toString() : file.toString(); this.lineNumber = lineNumber; this.kind = kind; this.message = message; this.isFixable = isFixable; - this.omitParentheses = omitParentheses; + + if (keepFullMessage(message)) { + this.messageKey = message; + this.messageKeyParens = false; + } else { + String firstline; + // There might be a mismatch between the System.lineSeparator() and the diagnostic + // message, so manually check both options. + int lineSepPos = this.message.indexOf("\r\n"); + if (lineSepPos == -1) { + lineSepPos = this.message.indexOf("\n"); + } + if (lineSepPos != -1) { + firstline = this.message.substring(0, lineSepPos).trim(); + } else { + firstline = this.message; + } + + // Keep in sync with code above. + int open = firstline.indexOf("("); + int close = firstline.indexOf(")"); + if (open == 0 && close > open) { + this.messageKey = firstline.substring(open + 1, close).trim(); + this.messageKeyParens = true; + } else { + this.messageKey = firstline; + this.messageKeyParens = false; + } + } } + /** + * Determine whether the full diagnostic message should be used as message key. This is useful + * to ensure e.g. stack traces are fully shown. + * + * @param message the full message + * @return whether the full diagnostic message should be used + */ + public static boolean keepFullMessage(String message) { + return message.contains("unexpected Throwable") + || message.contains("Compilation unit") + || message.contains("OutOfMemoryError"); + } + + /** + * The path to the test file. + * + * @return the path to the test file + */ + public Path getFile() { + return file; + } + + /** + * The base file name of the test file. + * + * @return the base file name of the test file + */ public String getFilename() { return filename; } + /** + * The line number of the diagnostic output. + * + * @return the line number of the diagnostic output + */ public long getLineNumber() { return lineNumber; } + /** + * The diagnostic kind of the output. + * + * @return the diagnostic kind of the output + */ public DiagnosticKind getKind() { return kind; } - public String getMessage() { - return message; + /** + * The message key, without surrounding parentheses. + * + * @return the message key + */ + public String getMessageKey() { + return messageKey; } - public boolean isFixable() { - return isFixable; + /** + * The full diagnostic message. + * + * @return the full diagnostic message + */ + public String getMessage() { + return message; } /** - * Returns whether or not the printed representation should omit parentheses around the message. + * Whether WPI can fix the test. * - * @return whether or not the printed representation should omit parentheses around the message + * @return whether WPI can fix the test */ - public boolean shouldOmitParentheses() { - return omitParentheses; + public boolean isFixable() { + return isFixable; } /** - * Equality is compared without isFixable/omitParentheses. + * Equality is compared based the file name, not the full path, on the messageKey, not the full + * message, and without considering isFixable and messageKeyParens. * - * @return true if this and otherObj are equal according to filename, lineNumber, kind, and - * message + * @return true if this and otherObj are equal according to file, lineNumber, kind, and + * messageKey */ @Override public boolean equals(@Nullable Object otherObj) { @@ -92,25 +223,31 @@ public boolean equals(@Nullable Object otherObj) { return other.filename.equals(this.filename) && other.lineNumber == lineNumber && other.kind == this.kind - && other.message.equals(this.message); + && other.messageKey.equals(this.messageKey); } @Override public int hashCode() { - return Objects.hash(filename, lineNumber, kind, message); + // Only filename, not file, and only messageKey, not message, not isFixable, not + // messageKeyParens. + return Objects.hash(filename, lineNumber, kind, messageKey); } /** - * Returns a representation of this diagnostic as if it appeared in a diagnostics file. + * Returns a representation of this diagnostic as if it appeared in a diagnostics file. This + * uses only the base file name, not the full path, and only the message key, not the full + * message. Field {@link messageKeyParens} influences whether the message key is output in + * parentheses. * * @return a representation of this diagnostic as if it appeared in a diagnostics file */ @Override public String toString() { - if (omitParentheses) { - return filename + ":" + lineNumber + ": " + kind.parseString + ": " + message; + if (messageKeyParens) { + return filename + ":" + lineNumber + ": " + kind.parseString + ": (" + messageKey + ")"; + } else { + return filename + ":" + lineNumber + ": " + kind.parseString + ": " + messageKey; } - return filename + ":" + lineNumber + ": " + kind.parseString + ": (" + message + ")"; } /** @@ -120,7 +257,7 @@ public String toString() { */ public String repr() { return String.format( - "[TestDiagnostic: filename=%s, lineNumber=%d, kind=%s, message=%s]", - filename, lineNumber, kind, message); + "[TestDiagnostic: file=%s, lineNumber=%d, kind=%s, message=%s]", + file, lineNumber, kind, message); } } 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 d7fed6616c03..6a476746fc09 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 @@ -5,7 +5,8 @@ import org.plumelib.util.CollectionsPlume; import org.plumelib.util.IPair; -import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; @@ -22,31 +23,46 @@ public class TestDiagnosticUtils { /** How the diagnostics appear in Java source files. */ public static final String DIAGNOSTIC_IN_JAVA_REGEX = - "\\s*(error|fixable-error|warning|fixable-warning|other):\\s*(\\(?.*\\)?)\\s*"; + "\\s*(?error|fixable-error|warning|fixable-warning|Note|other):\\s*(?[\\s\\S]*)"; - /** How the diagnostics appear in Java source files. */ + /** Pattern compiled from {@link #DIAGNOSTIC_IN_JAVA_REGEX}. */ public static final Pattern DIAGNOSTIC_IN_JAVA_PATTERN = Pattern.compile(DIAGNOSTIC_IN_JAVA_REGEX); - public static final String DIAGNOSTIC_WARNING_IN_JAVA_REGEX = "\\s*warning:\\s*(.*\\s*.*)\\s*"; + /** How the diagnostic warnings appear in Java source files. */ + public static final String DIAGNOSTIC_WARNING_IN_JAVA_REGEX = + "\\s*warning:\\s*(?[\\s\\S]*)"; + + /** Pattern compiled from {@link #DIAGNOSTIC_WARNING_IN_JAVA_REGEX}. */ public static final Pattern DIAGNOSTIC_WARNING_IN_JAVA_PATTERN = Pattern.compile(DIAGNOSTIC_WARNING_IN_JAVA_REGEX); - // How the diagnostics appear in javax tools diagnostics from the compiler. - public static final String DIAGNOSTIC_REGEX = ":(\\d+):" + DIAGNOSTIC_IN_JAVA_REGEX; + /** How the diagnostics appear in javax tools diagnostics from the compiler. */ + public static final String DIAGNOSTIC_REGEX = + "(?:(?\\d+):)?" + DIAGNOSTIC_IN_JAVA_REGEX; + + /** Pattern compiled from {@link #DIAGNOSTIC_REGEX}. */ public static final Pattern DIAGNOSTIC_PATTERN = Pattern.compile(DIAGNOSTIC_REGEX); + /** How the diagnostic warnings appear in javax tools diagnostics from the compiler. */ public static final String DIAGNOSTIC_WARNING_REGEX = - ":(\\d+):" + DIAGNOSTIC_WARNING_IN_JAVA_REGEX; + "(?:(?\\d+):)?" + DIAGNOSTIC_WARNING_IN_JAVA_REGEX; + + /** Pattern compiled from {@link #DIAGNOSTIC_WARNING_REGEX}. */ public static final Pattern DIAGNOSTIC_WARNING_PATTERN = Pattern.compile(DIAGNOSTIC_WARNING_REGEX); - // How the diagnostics appear in diagnostic files (.out). + /** How the diagnostics appear in diagnostic files (.out). */ public static final String DIAGNOSTIC_FILE_REGEX = ".+\\.java" + DIAGNOSTIC_REGEX; + + /** Pattern compiled from {@link #DIAGNOSTIC_FILE_REGEX}. */ public static final Pattern DIAGNOSTIC_FILE_PATTERN = Pattern.compile(DIAGNOSTIC_FILE_REGEX); + /** How the diagnostic warnings appear in diagnostic files (.out). */ public static final String DIAGNOSTIC_FILE_WARNING_REGEX = ".+\\.java" + DIAGNOSTIC_WARNING_REGEX; + + /** Pattern compiled from {@link #DIAGNOSTIC_FILE_WARNING_REGEX}. */ public static final Pattern DIAGNOSTIC_FILE_WARNING_PATTERN = Pattern.compile(DIAGNOSTIC_FILE_WARNING_REGEX); @@ -61,7 +77,8 @@ public static TestDiagnostic fromDiagnosticFileString(String stringFromDiagnosti return fromPatternMatching( DIAGNOSTIC_FILE_PATTERN, DIAGNOSTIC_WARNING_IN_JAVA_PATTERN, - "", + // Important to use "" to make input of expected warnings easy. + Paths.get(""), null, stringFromDiagnosticFile); } @@ -81,7 +98,7 @@ public static TestDiagnostic fromJavaFileComment( return fromPatternMatching( DIAGNOSTIC_IN_JAVA_PATTERN, DIAGNOSTIC_WARNING_IN_JAVA_PATTERN, - filename, + Paths.get(filename), lineNumber, stringFromJavaFile); } @@ -89,14 +106,18 @@ public static TestDiagnostic fromJavaFileComment( /** * Instantiate a diagnostic from output produced by the Java compiler. The resulting diagnostic * is never fixable and always has parentheses. + * + * @param diagnosticString the compiler diagnostics string + * @return the corresponding test diagnostic */ - public static TestDiagnostic fromJavaxToolsDiagnostic( - String diagnosticString, boolean noMsgText) { + public static TestDiagnostic fromJavaxToolsDiagnostic(String diagnosticString) { // It would be nice not to parse this from the diagnostic string. // However, diagnostic.toString() may contain "[unchecked]" even though getMessage() does // not. // Since we want to match the error messages reported by javac exactly, we must parse. - IPair trimmed = formatJavaxToolString(diagnosticString, noMsgText); + // diagnostic.getCode() returns "compiler.warn.prob.found.req" for "[unchecked]" messages, + // but not clear how to map from one to the other. + IPair trimmed = formatJavaxToolString(diagnosticString); return fromPatternMatching( DIAGNOSTIC_PATTERN, DIAGNOSTIC_WARNING_PATTERN, @@ -110,7 +131,7 @@ public static TestDiagnostic fromJavaxToolsDiagnostic( * * @param diagnosticPattern a pattern that matches any diagnostic * @param warningPattern a pattern that matches a warning diagnostic - * @param filename the file name + * @param file the test file * @param lineNumber the line number * @param diagnosticString the string to parse * @return a diagnostic parsed from the given string @@ -119,65 +140,50 @@ public static TestDiagnostic fromJavaxToolsDiagnostic( protected static TestDiagnostic fromPatternMatching( Pattern diagnosticPattern, Pattern warningPattern, - String filename, + Path file, @Nullable Long lineNumber, String diagnosticString) { final DiagnosticKind kind; final String message; final boolean isFixable; - final boolean noParentheses; long lineNo = -1; - int capturingGroupOffset = 1; if (lineNumber != null) { lineNo = lineNumber; - capturingGroupOffset = 0; } Matcher diagnosticMatcher = diagnosticPattern.matcher(diagnosticString); if (diagnosticMatcher.matches()) { IPair categoryToFixable = - parseCategoryString(diagnosticMatcher.group(1 + capturingGroupOffset)); + parseCategoryString(diagnosticMatcher.group("kind")); kind = categoryToFixable.first; isFixable = categoryToFixable.second; - String msg = diagnosticMatcher.group(2 + capturingGroupOffset).trim(); - noParentheses = - msg.equals("") || msg.charAt(0) != '(' || msg.charAt(msg.length() - 1) != ')'; - message = noParentheses ? msg : msg.substring(1, msg.length() - 1); - - if (lineNumber == null) { - lineNo = Long.parseLong(diagnosticMatcher.group(1)); + message = diagnosticMatcher.group("message").trim(); + if (lineNumber == null && diagnosticMatcher.group("linenogroup") != null) { + lineNo = Long.parseLong(diagnosticMatcher.group("lineno")); } - } else { Matcher warningMatcher = warningPattern.matcher(diagnosticString); if (warningMatcher.matches()) { kind = DiagnosticKind.Warning; isFixable = false; - message = warningMatcher.group(1 + capturingGroupOffset); - noParentheses = true; - - if (lineNumber == null) { - lineNo = Long.parseLong(diagnosticMatcher.group(1)); + message = warningMatcher.group("message").trim(); + if (lineNumber == null && diagnosticMatcher.group("linenogroup") != null) { + lineNo = Long.parseLong(diagnosticMatcher.group("lineno")); } - } else if (diagnosticString.startsWith("warning:")) { kind = DiagnosticKind.Warning; isFixable = false; message = diagnosticString.substring("warning:".length()).trim(); - noParentheses = true; if (lineNumber != null) { lineNo = lineNumber; } else { lineNo = 0; } - } else { kind = DiagnosticKind.Other; isFixable = false; message = diagnosticString; - noParentheses = true; - // this should only happen if we are parsing a Java Diagnostic from the compiler // that we did do not handle if (lineNumber == null) { @@ -185,57 +191,97 @@ protected static TestDiagnostic fromPatternMatching( } } } - return new TestDiagnostic(filename, lineNo, kind, message, isFixable, noParentheses); + + // Check if the message matches detailed message format. + // Trim the message to remove leading/trailing whitespace. + // Keep separator in sync with SourceChecker.DETAILS_SEPARATOR. + String[] diagnosticStrings = + Arrays.stream(message.split(" \\$\\$ ")).map(String::trim).toArray(String[]::new); + if (diagnosticStrings.length > 1) { + // See SourceChecker.detailedMsgTextPrefix. + // The parts of the detailed message are: + + // (1) message key; + String messageKey = diagnosticStrings[0]; + + // (2) number of additional tokens, and those tokens; this depends on the error message, + // and an example is the found and expected types; + int numAdditionalTokens = Integer.parseInt(diagnosticStrings[1]); + int lastAdditionalToken = 2 + numAdditionalTokens; + List additionalTokens = + Arrays.asList(diagnosticStrings).subList(2, lastAdditionalToken); + + // (3) the diagnostic position, given by the format (startPosition, endPosition); + String pairParens = diagnosticStrings[lastAdditionalToken]; + // remove the leading and trailing parentheses and spaces + String pair = pairParens.substring(2, pairParens.length() - 2); + String[] diagPositionString = pair.split(", "); + long startPosition = Long.parseLong(diagPositionString[0]); + long endPosition = Long.parseLong(diagPositionString[1]); + + // (4) the human-readable diagnostic message. + String readableMessage = diagnosticStrings[lastAdditionalToken + 1]; + + return new DetailedTestDiagnostic( + file, + lineNo, + kind, + messageKey, + additionalTokens, + startPosition, + endPosition, + readableMessage, + isFixable); + } + + return new TestDiagnostic(file, lineNo, kind, message, isFixable); } /** - * Given a javax diagnostic, return a pair of (trimmed, filename), where "trimmed" is the first - * line of the message, without the leading filename. + * Given a javax diagnostic, return a pair of (trimmed, file), where "trimmed" is the message + * without the leading filename and the file path. As an example: "foo/bar/Baz.java:49: My error + * message" is turned into {@code IPair.of(":49: My error message", Path("foo/bar/Baz.java"))}. + * If the file path cannot be determined, it uses {@code ""}. This is necessary to make writing + * the expected warnings easy. * * @param original a javax diagnostic - * @param noMsgText whether to do work; if false, this returns a pair of (argument, "") - * @return the diagnostic, split into message and filename + * @return the diagnostic, split into message and file */ - public static IPair formatJavaxToolString(String original, boolean noMsgText) { - String trimmed = original; - String filename = ""; - if (noMsgText) { - if (!retainAllLines(trimmed)) { - int lineSepPos = trimmed.indexOf(System.lineSeparator()); - if (lineSepPos != -1) { - trimmed = trimmed.substring(0, lineSepPos); - } - - int extensionPos = trimmed.indexOf(".java:"); - if (extensionPos != -1) { - int basenameStart = trimmed.lastIndexOf(File.separator); - filename = trimmed.substring(basenameStart + 1, extensionPos + 5).trim(); - trimmed = trimmed.substring(extensionPos + 5).trim(); - } - } + public static IPair formatJavaxToolString(String original) { + String firstline; + // In TestDiagnostic we manually check for "\r\n" and "\n". Here, we only use + // `firstline` to find the file name. Using the system line separator is not + // problem here, it seems. + int lineSepPos = original.indexOf(System.lineSeparator()); + if (lineSepPos != -1) { + firstline = original.substring(0, lineSepPos); + } else { + firstline = original; } - return IPair.of(trimmed, filename); - } + String trimmed; + Path file; + int extensionPos = firstline.indexOf(".java:"); + if (extensionPos != -1) { + file = Paths.get(firstline.substring(0, extensionPos + 5).trim()); + trimmed = original.substring(extensionPos + 5).trim(); + } else { + // Important to use "" to make input of expected warnings easy. + // For an example, see file + // ./checker/tests/nullness-stubfile/NullnessStubfileMerge.java + file = Paths.get(""); + trimmed = original; + } - /** - * Returns true if all lines of the message should be shown, false if only the first line should - * be shown. - * - * @param message a diagnostic message - * @return true if all lines of the message should be shown - */ - private static boolean retainAllLines(String message) { - // Retain all if it is a thrown exception "unexpected Throwable" or it is a Checker - // Framework Error (contains "Compilation unit") or is OutOfMemoryError. - return message.contains("unexpected Throwable") - || message.contains("Compilation unit") - || message.contains("OutOfMemoryError"); + return IPair.of(trimmed, file); } /** * Given a category string that may be prepended with "fixable-", return the category enum that - * corresponds with the category and whether or not it is a isFixable error + * corresponds with the category and whether or not it is a isFixable error. + * + * @param category a category string + * @return the corresponding diagnostic kind and whether it is fixable */ private static IPair parseCategoryString(String category) { String fixable = "fixable-"; @@ -254,6 +300,9 @@ private static IPair parseCategoryString(String categor /** * Return true if this line in a Java file indicates an expected diagnostic that might be * continued on the next line. + * + * @param originalLine the input line + * @return whether the diagnostic might be continued on the next line */ public static boolean isJavaDiagnosticLineStart(String originalLine) { String trimmedLine = originalLine.trim(); @@ -325,21 +374,19 @@ public static TestDiagnosticLine fromJavaSourceLine( diagnosticStrs); return new TestDiagnosticLine( filename, errorLine, line, Collections.unmodifiableList(diagnostics)); - } else if (trimmedLine.startsWith("// warning:")) { // This special diagnostic does not expect a line number nor a file name String diagnosticString = trimmedLine.substring(2); - TestDiagnostic diagnostic = fromJavaFileComment("", 0, diagnosticString); - return new TestDiagnosticLine("", 0, line, Collections.singletonList(diagnostic)); + TestDiagnostic diagnostic = fromJavaFileComment("", -1, diagnosticString); + return new TestDiagnosticLine("", -1, line, Collections.singletonList(diagnostic)); } else if (trimmedLine.startsWith("//::")) { TestDiagnostic diagnostic = new TestDiagnostic( - filename, + Paths.get(filename), lineNumber, DiagnosticKind.Error, "Use \"// ::\", not \"//::\"", - false, - true); + false); return new TestDiagnosticLine( filename, lineNumber, line, Collections.singletonList(diagnostic)); } else { @@ -361,12 +408,18 @@ public static TestDiagnosticLine fromDiagnosticFileLine(String diagnosticLine) { "", diagnostic.getLineNumber(), diagnosticLine, Arrays.asList(diagnostic)); } - public static Set fromJavaxDiagnosticList( - List> javaxDiagnostics, boolean noMsgText) { + /** + * Convert a list of compiler diagnostics into test diagnostics. + * + * @param javaxDiagnostics the list of compiler diagnostics + * @return the corresponding test diagnostics + */ + public static Set fromJavaxToolsDiagnosticList( + List> javaxDiagnostics) { Set diagnostics = new LinkedHashSet<>(javaxDiagnostics.size()); for (Diagnostic diagnostic : javaxDiagnostics) { - // See TestDiagnosticUtils as to why we use diagnostic.toString rather + // See fromJavaxToolsDiagnostic as to why we use diagnostic.toString rather // than convert from the diagnostic itself String diagnosticString = diagnostic.toString(); @@ -378,8 +431,7 @@ public static Set fromJavaxDiagnosticList( continue; } - diagnostics.add( - TestDiagnosticUtils.fromJavaxToolsDiagnostic(diagnosticString, noMsgText)); + diagnostics.add(fromJavaxToolsDiagnostic(diagnosticString)); } return diagnostics; @@ -395,15 +447,4 @@ public static Set fromJavaxDiagnosticList( public static List diagnosticsToString(List diagnostics) { return CollectionsPlume.mapList(TestDiagnostic::toString, diagnostics); } - - public static void removeDiagnosticsOfKind( - DiagnosticKind kind, List expectedDiagnostics) { - for (int i = 0; i < expectedDiagnostics.size(); /*no-increment*/ ) { - if (expectedDiagnostics.get(i).getKind() == kind) { - expectedDiagnostics.remove(i); - } else { - ++i; - } - } - } } diff --git a/framework/build.gradle b/framework/build.gradle index faff52042f2f..987362e2d00f 100644 --- a/framework/build.gradle +++ b/framework/build.gradle @@ -49,7 +49,7 @@ dependencies { api project(':javacutil') api project(':dataflow') // At the moement, there are no differences between eisop and typetools stubparsers. - api 'org.checkerframework:stubparser:3.25.6' + api 'org.checkerframework:stubparser:3.25.10' // NO-AFU /* 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 25059aaa764d..178967dcc307 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -2209,7 +2209,7 @@ protected void checkPreconditions(MethodInvocationTree tree, Set p CFAbstractStore store = atypeFactory.getStoreBefore(tree); - Set annos = + AnnotationMirrorSet annos = atypeFactory.getAnnotatedTypeBefore(exprJe, tree).getAnnotations(); AnnotationMirror inferredAnno = 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 7e07ed8e7f33..cde4f3f1971b 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/messages.properties +++ b/framework/src/main/java/org/checkerframework/common/basetype/messages.properties @@ -27,7 +27,7 @@ type.invalid.conflicting.annos=invalid type: conflicting annotations %s in type type.invalid.too.few.annotations=invalid type: missing annotations %s in type "%s" type.invalid.annotations.on.use=invalid type: annotations %s conflict with declaration of type %s type.invalid.annotations.on.location=annotation %s used on prohibited locations %s -type.invalid.super.wildcard=bounds must have the same annotations.%nsuper bound : %s%nextends bound: %s +type.invalid.super.wildcard=bounds must have the same annotations.%nextends bound: %s%nsuper bound : %s cast.unsafe=cast from "%s" to "%s" cannot be statically verified invariant.cast.unsafe=cannot cast from "%s" to "%s" cast.unsafe.constructor.invocation=constructor invocation cast from "%s" to "%s" cannot be statically verified diff --git a/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsAnnotatedTypeFactory.java index ccd32ca3715b..059eaf2c69f1 100644 --- a/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/initializedfields/InitializedFieldsAnnotatedTypeFactory.java @@ -235,12 +235,12 @@ private boolean defaultValueIsOK(VariableElement field) { for (GenericAnnotatedTypeFactory defaultValueAtypeFactory : defaultValueAtypeFactories) { - defaultValueAtypeFactory.setRoot(root); + defaultValueAtypeFactory.setRoot(this.getRoot()); // Set the root on all the subcheckers, too. for (BaseTypeChecker subchecker : defaultValueAtypeFactory.getChecker().getSubcheckers()) { AnnotatedTypeFactory subATF = subchecker.getTypeFactory(); - subATF.setRoot(root); + subATF.setRoot(this.getRoot()); } AnnotatedTypeMirror fieldType = defaultValueAtypeFactory.getAnnotatedType(field); AnnotatedTypeMirror defaultValueType = 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 20f1d887e5a3..0a6408399169 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractValue.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractValue.java @@ -265,9 +265,7 @@ public V mostSpecific(@Nullable V other, @Nullable V backup) { mostSpecifTypeMirror = this.getUnderlyingType(); } - MostSpecificVisitor ms = - new MostSpecificVisitor( - this.getUnderlyingType(), other.getUnderlyingType(), backup); + MostSpecificVisitor ms = new MostSpecificVisitor(backup); AnnotationMirrorSet mostSpecific = ms.combineSets( this.getUnderlyingType(), @@ -276,7 +274,8 @@ public V mostSpecific(@Nullable V other, @Nullable V backup) { other.getAnnotations(), canBeMissingAnnotations(mostSpecifTypeMirror)); if (ms.error) { - return backup; + // return null because `ms.error` is only set to true when `backup` is null. + return null; } return analysis.createAbstractValue(mostSpecific, mostSpecifTypeMirror); } @@ -287,24 +286,17 @@ private class MostSpecificVisitor extends AnnotationSetCombiner { boolean error = false; /** Set of annotations to use if a most specific value cannot be found. */ - final AnnotationMirrorSet backupAMSet; + final @Nullable AnnotationMirrorSet backupAMSet; /** * Create a {@link MostSpecificVisitor}. * - * @param aTypeMirror type of the "a" value - * @param bTypeMirror type of the "b" value * @param backup value to use if no most specific value is found */ - @SuppressWarnings("UnusedVariable") // TODO clean this up - public MostSpecificVisitor(TypeMirror aTypeMirror, TypeMirror bTypeMirror, V backup) { + public MostSpecificVisitor(@Nullable V backup) { if (backup != null) { this.backupAMSet = backup.getAnnotations(); - // this.backupTypeMirror = backup.getUnderlyingType(); - // this.backupAtv = getEffectiveTypeVar(backupTypeMirror); } else { - // this.backupAtv = null; - // this.backupTypeMirror = null; this.backupAMSet = null; } } 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 e8007044847d..0e817e60d83b 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -342,7 +342,7 @@ // constraints. "noWarnMemoryConstraints", - // Only output error code, useful for testing framework + // Only output error code, useful for testing framework. // org.checkerframework.framework.source.SourceChecker.message(Kind, Object, String, Object...) "nomsgtext", @@ -917,7 +917,7 @@ private Pattern getPattern( + pattern); } - if (pattern.equals("")) { + if (pattern.isEmpty()) { pattern = defaultPattern; } @@ -1791,9 +1791,7 @@ protected Set createSupportedLintOptions() { @Nullable String[] lintArray = slValue; Set lintSet = new HashSet<>(lintArray.length); - for (String s : lintArray) { - lintSet.add(s); - } + lintSet.addAll(Arrays.asList(lintArray)); return Collections.unmodifiableSet(lintSet); } diff --git a/framework/src/main/java/org/checkerframework/framework/stub/StubGenerator.java b/framework/src/main/java/org/checkerframework/framework/stub/StubGenerator.java index 69a3c6bf10e8..a4be11231961 100644 --- a/framework/src/main/java/org/checkerframework/framework/stub/StubGenerator.java +++ b/framework/src/main/java/org/checkerframework/framework/stub/StubGenerator.java @@ -121,7 +121,7 @@ public void stubFromMethod(ExecutableElement elt) { } String newPackage = ElementUtils.getQualifiedName(ElementUtils.enclosingPackage(elt)); - if (!newPackage.equals("")) { + if (!newPackage.isEmpty()) { currentPackage = newPackage; currentIndention = " "; indent(); 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 0dbe8d9f5364..9f5f76a1d2ca 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java @@ -181,7 +181,7 @@ public class AnnotatedTypeFactory implements AnnotationProvider { // TODO: when should root be null? What are the use cases? // None of the existing test checkers has a null root. // Should not be modified between calls to "visit". - protected @Nullable CompilationUnitTree root; + private @Nullable CompilationUnitTree root; /** The processing environment to use for accessing compiler internals. */ protected final ProcessingEnvironment processingEnv; @@ -976,6 +976,15 @@ protected void initializeReflectionResolution() { } } + /** + * Get the current CompilationUnitTree. + * + * @return the current compilation unit being used, or null + */ + protected @Nullable CompilationUnitTree getRoot() { + return root; + } + /** * Set the CompilationUnitTree that should be used. * @@ -1892,10 +1901,6 @@ private AnnotatedTypeMirror fromExpression(ExpressionTree tree) { *

      Subclasses that override this method should also override {@link * #addComputedTypeAnnotations(Element, AnnotatedTypeMirror)}. * - *

      In classes that extend {@link GenericAnnotatedTypeFactory}, override {@link - * GenericAnnotatedTypeFactory#addComputedTypeAnnotations(Tree, AnnotatedTypeMirror, boolean)} - * instead of this method. - * * @param tree an AST node * @param type the type obtained from {@code tree} */ @@ -2491,9 +2496,11 @@ public String toString() { public ParameterizedExecutableType methodFromUse(MethodInvocationTree tree) { ExecutableElement methodElt = TreeUtils.elementFromUse(tree); AnnotatedTypeMirror receiverType = getReceiverType(tree); - if (receiverType == null && TreeUtils.isSuperConstructorCall(tree)) { - // super() calls don't have a receiver, but they should be view-point adapted as if - // "this" is the receiver. + if (receiverType == null + && (TreeUtils.isSuperConstructorCall(tree) + || TreeUtils.isThisConstructorCall(tree))) { + // super() and this() calls don't have a receiver, but they should be view-point adapted + // as if "this" is the receiver. receiverType = getSelfType(tree); } if (receiverType != null && receiverType.getKind() == TypeKind.DECLARED) { @@ -2906,10 +2913,6 @@ public ParameterizedExecutableType constructorFromUse(NewClassTree tree) { AnnotatedExecutableType con = getAnnotatedType(ctor); // get unsubstituted type constructorFromUsePreSubstitution(tree, con); - if (viewpointAdapter != null) { - viewpointAdapter.viewpointAdaptConstructor(type, ctor, con); - } - if (tree.getClassBody() != null) { // Because the anonymous constructor can't have explicit annotations on its parameters, // they are copied from the super constructor invoked in the anonymous constructor. To @@ -2922,7 +2925,6 @@ public ParameterizedExecutableType constructorFromUse(NewClassTree tree) { AnnotatedExecutableType superCon = getAnnotatedType(TreeUtils.getSuperConstructor(tree)); constructorFromUsePreSubstitution(tree, superCon); - // no viewpoint adaptation needed for super invocation superCon = AnnotatedTypes.asMemberOf(types, this, type, superCon.getElement(), superCon); con.computeVarargType(superCon); @@ -2989,7 +2991,7 @@ public ParameterizedExecutableType constructorFromUse(NewClassTree tree) { } if (ctor.getEnclosingElement().getKind() == ElementKind.ENUM) { - Set enumAnnos = getEnumConstructorQualifiers(); + AnnotationMirrorSet enumAnnos = getEnumConstructorQualifiers(); con.getReturnType().replaceAnnotations(enumAnnos); } @@ -3009,8 +3011,8 @@ public ParameterizedExecutableType constructorFromUse(NewClassTree tree) { * * @return the annotations that should be applied to enum constructors */ - protected Set getEnumConstructorQualifiers() { - return Collections.emptySet(); + protected AnnotationMirrorSet getEnumConstructorQualifiers() { + return new AnnotationMirrorSet(); } /** diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotationClassLoader.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotationClassLoader.java index 7e6de9f03c60..b4af2eb77bf1 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotationClassLoader.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotationClassLoader.java @@ -36,6 +36,7 @@ import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; +import java.util.Locale; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; @@ -404,7 +405,11 @@ private boolean checkDirForPackage(File currentDir, Iterator pkgNames) { URL jarURL = null; try { - jarURL = new URI("jar:file:" + absolutePathToJarFile + "!/").toURL(); + String normalizedPath = absolutePathToJarFile.replace("\\", "/"); + String osName = System.getProperty("os.name").toString().toLowerCase(Locale.ENGLISH); + String prefix = osName.startsWith("windows") ? "jar:file:///" : "jar:file:"; + + jarURL = new URI(prefix + normalizedPath + "!/").toURL(); } catch (MalformedURLException | URISyntaxException e) { processingEnv .getMessager() 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 1ee058ca5623..30a1b59ed29a 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java @@ -399,6 +399,16 @@ protected GenericAnnotatedTypeFactory(BaseTypeChecker checker, boolean useFlow) // all other initialization is finished. } + /** + * Determines whether flow-sensitive type refinement should be used or not. + * + * @return whether flow-sensitive type refinement should be used or not + * @see #useFlow + */ + protected boolean getUseFlow() { + return useFlow; + } + @Override protected void postInit( @UnderInitialization(GenericAnnotatedTypeFactory.class) GenericAnnotatedTypeFactory @@ -1404,7 +1414,7 @@ protected void performFlowAnalysis(ClassTree classTree) { TreePath preTreePath = getVisitorTreePath(); // Don't call AnnotatedTypeFactory#getPath, because it uses visitorTreePath. - setVisitorTreePath(TreePath.getPath(this.root, ct)); + setVisitorTreePath(TreePath.getPath(this.getRoot(), ct)); // start with the captured store as initialization store initializationStaticStore = capturedStore; @@ -1597,7 +1607,8 @@ protected void analyze( boolean updateInitializationStore, boolean isStatic, @Nullable Store capturedStore) { - ControlFlowGraph cfg = CFCFGBuilder.build(root, ast, checker, this, processingEnv); + ControlFlowGraph cfg = + CFCFGBuilder.build(this.getRoot(), ast, checker, this, processingEnv); /* cfg.getAllNodes(this::isIgnoredExceptionType) .forEach( @@ -1980,17 +1991,6 @@ public void addDefaultAnnotations(AnnotatedTypeMirror type) { defaults.annotate((Element) null, type); } - /** - * This method is final; override {@link #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror, - * boolean)} instead. - * - *

      {@inheritDoc} - */ - @Override - protected final void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { - addComputedTypeAnnotations(tree, type, this.useFlow); - } - /** * Removes all primary annotations on a copy of the type and calculates the default annotations * that apply to the copied type, without type refinements. @@ -2002,7 +2002,7 @@ protected final void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror t public AnnotatedTypeMirror getDefaultAnnotations(Tree tree, AnnotatedTypeMirror type) { AnnotatedTypeMirror copy = type.deepCopy(); copy.removeAnnotations(type.getAnnotations()); - addComputedTypeAnnotations(tree, copy, false); + addComputedTypeAnnotationsWithoutFlow(tree, copy); return copy; } @@ -2013,13 +2013,48 @@ public AnnotatedTypeMirror getDefaultAnnotations(Tree tree, AnnotatedTypeMirror * @param tree an AST node * @param type the type obtained from tree * @param iUseFlow whether to use information from dataflow analysis + * @deprecated use {@link #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)} or {@link + * #addComputedTypeAnnotationsWithoutFlow(Tree, AnnotatedTypeMirror)} if you want to add + * computed type annotations without using flow information */ + @Deprecated // 2024-07-07 + @SuppressWarnings("unused") protected void addComputedTypeAnnotations( Tree tree, AnnotatedTypeMirror type, boolean iUseFlow) { - if (root == null && ajavaTypes.isParsing()) { + addComputedTypeAnnotationsWithoutFlow(tree, type); + } + + /** + * A helper method to add computed type annotations to a type without using flow information. + * + *

      This method is final; override {@link #addComputedTypeAnnotations(Tree, + * AnnotatedTypeMirror)} instead. + * + * @param tree an AST node + * @param type the type obtained from tree + * @see #addComputedTypeAnnotations(Tree, AnnotatedTypeMirror) + */ + protected final void addComputedTypeAnnotationsWithoutFlow( + Tree tree, AnnotatedTypeMirror type) { + boolean oldUseflow = useFlow; + useFlow = false; + addComputedTypeAnnotations(tree, type); + useFlow = oldUseflow; + } + + /** + * {@inheritDoc} + * + *

      This method adds defaults and flow-sensitive type refinements. + * + * @see #addComputedTypeAnnotationsWithoutFlow(Tree, AnnotatedTypeMirror) + */ + @Override + protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { + if (this.getRoot() == null && ajavaTypes.isParsing()) { return; } - assert root != null + assert this.getRoot() != null : "GenericAnnotatedTypeFactory.addComputedTypeAnnotations: " + " root needs to be set when used on trees; factory: " + this.getClass(); @@ -2037,7 +2072,7 @@ protected void addComputedTypeAnnotations( } log( "%s GATF.addComputedTypeAnnotations#1(%s, %s, %s)%n", - thisClass, treeString, type, iUseFlow); + thisClass, treeString, type, this.useFlow); if (!TreeUtils.isExpressionTree(tree)) { // Don't apply defaults to expressions. Their types may be computed from subexpressions // in treeAnnotator. @@ -2062,7 +2097,7 @@ protected void addComputedTypeAnnotations( defaults.annotate(tree, type); log("%s GATF.addComputedTypeAnnotations#7(%s, %s)%n", thisClass, treeString, type); - if (iUseFlow) { + if (this.useFlow) { Value inferred = getInferredValueFor(tree); if (inferred != null) { applyInferredAnnotations(type, inferred); @@ -2073,7 +2108,7 @@ protected void addComputedTypeAnnotations( } log( "%s GATF.addComputedTypeAnnotations#9(%s, %s, %s) done%n", - thisClass, treeString, type, iUseFlow); + thisClass, treeString, type, this.useFlow); } /** @@ -2409,7 +2444,7 @@ public boolean getShouldDefaultTypeVarLocals() { protected @Nullable CFGVisualizer createCFGVisualizer() { if (checker.hasOption("flowdotdir")) { String flowdotdir = checker.getOption("flowdotdir"); - if (flowdotdir.equals("")) { + if (flowdotdir.isEmpty()) { throw new UserError("Empty string provided for -Aflowdotdir command-line argument"); } boolean verbose = checker.hasOption("verbosecfg"); @@ -2744,7 +2779,7 @@ public AnnotatedTypeMirror getDefaultValueAnnotatedType(TypeMirror typeMirror) { TypeMirror defaultValueTM = TreeUtils.typeOf(defaultValueTree); AnnotatedTypeMirror defaultValueATM = AnnotatedTypeMirror.createType(defaultValueTM, this, false); - addComputedTypeAnnotations(defaultValueTree, defaultValueATM, false); + addComputedTypeAnnotationsWithoutFlow(defaultValueTree, defaultValueATM); return defaultValueATM; } diff --git a/framework/src/main/java/org/checkerframework/framework/type/TypeVariableSubstitutor.java b/framework/src/main/java/org/checkerframework/framework/type/TypeVariableSubstitutor.java index 44455769c44f..67ce1c2671a3 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/TypeVariableSubstitutor.java +++ b/framework/src/main/java/org/checkerframework/framework/type/TypeVariableSubstitutor.java @@ -66,12 +66,9 @@ public AnnotatedTypeMirror substituteWithoutCopyingTypeArguments( protected AnnotatedTypeMirror substituteTypeVariable( AnnotatedTypeMirror argument, AnnotatedTypeVariable use) { AnnotatedTypeMirror substitute = argument.deepCopy(true); - substitute.addAnnotations(argument.getAnnotationsField()); - if (!use.getAnnotationsField().isEmpty()) { substitute.replaceAnnotations(use.getAnnotations()); } - return substitute; } diff --git a/framework/src/main/java/org/checkerframework/framework/util/AtmCombo.java b/framework/src/main/java/org/checkerframework/framework/util/AtmCombo.java index b8a26bd0dd1b..854fd9f0ba67 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/AtmCombo.java +++ b/framework/src/main/java/org/checkerframework/framework/util/AtmCombo.java @@ -79,6 +79,7 @@ public static AtmKind valueOf(AnnotatedTypeMirror atm) { * * @see AtmCombo#accept */ +@SuppressWarnings("EnumOrdinal") // Use enum ordinals as array indices. public enum AtmCombo { ARRAY_ARRAY(AtmKind.ARRAY, AtmKind.ARRAY), ARRAY_DECLARED(AtmKind.ARRAY, AtmKind.DECLARED), diff --git a/framework/src/main/java/org/checkerframework/framework/util/CheckerMain.java b/framework/src/main/java/org/checkerframework/framework/util/CheckerMain.java index afc1f5c294a4..13baeaf25d7e 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/CheckerMain.java +++ b/framework/src/main/java/org/checkerframework/framework/util/CheckerMain.java @@ -536,7 +536,7 @@ private List expandWildcards(String pathElement) { return jarFiles("."); } else if (pathElement.endsWith(FILESEP_STAR)) { return jarFiles(pathElement.substring(0, pathElement.length() - 1)); - } else if (pathElement.equals("")) { + } else if (pathElement.isEmpty()) { return Collections.emptyList(); } else { return Collections.singletonList(pathElement); 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 5a5703a0f009..a7b7c149fe8a 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 @@ -60,6 +60,20 @@ * Determines the default qualifiers on a type. Default qualifiers are specified via the {@link * org.checkerframework.framework.qual.DefaultQualifier} annotation. * + *

      Type variable uses have two possible defaults. If flow sensitive type refinement is enabled, + * unannotated top-level type variable uses receive the same default as local variables. All other + * type variable uses are defaulted using the {@code TYPE_VARIABLE_USE} default. + * + *

      {@code
      + *  void method(USE T tIn) {
      + *     LOCAL T t = tIn;
      + * }
      + * }
      + * + * The parameter {@code tIn} will be defaulted using the {@code TYPE_VARIABLE_USE} default. The + * local variable {@code t} will be defaulted using the {@code LOCAL_VARIABLE} default, in order to + * allow dataflow to refine {@code T}. + * * @see org.checkerframework.framework.qual.DefaultQualifier */ public class QualifierDefaults { @@ -458,9 +472,6 @@ public void annotate(Element elt, AnnotatedTypeMirror type) { } } - // false b/c Element version only used when from bytecode, - // which cannot observe local variables. - // TODO: clean up. applyDefaultsElement(elt, type, false); } @@ -549,7 +560,7 @@ public void annotate(Tree tree, AnnotatedTypeMirror type) { * @param tree the tree associated with the type * @param type the type to which defaults will be applied * @see #applyDefaultsElement(javax.lang.model.element.Element, - * org.checkerframework.framework.type.AnnotatedTypeMirror, boolean) + * org.checkerframework.framework.type.AnnotatedTypeMirror,boolean) */ private void applyDefaults(Tree tree, AnnotatedTypeMirror type) { // The location to take defaults from. @@ -583,16 +594,7 @@ private void applyDefaults(Tree tree, AnnotatedTypeMirror type) { // System.out.println("applyDefaults on tree " + tree + // " gives elt: " + elt + "(" + elt.getKind() + ")"); - boolean defaultTypeVarLocals = - (atypeFactory instanceof GenericAnnotatedTypeFactory) - && ((GenericAnnotatedTypeFactory) atypeFactory) - .getShouldDefaultTypeVarLocals(); - boolean applyToTypeVar = - defaultTypeVarLocals - && elt != null - && ElementUtils.isLocalVariable(elt) - && type.getKind() == TypeKind.TYPEVAR; - applyDefaultsElement(elt, type, applyToTypeVar); + applyDefaultsElement(elt, type, true); } /** The default {@code value} element for a @DefaultQualifier annotation. */ @@ -828,35 +830,18 @@ public boolean applyConservativeDefaults(Element annotationScope) { *

      For a discussion on the rules for application of source code and conservative defaults, * please see the linked manual sections. * - *

      {@code applyToTypeVar} indicates whether or not a default should be applied to type vars - * located in the type being defaulted. This should only ever be true when the type variable is - * a local variable, non-component use, i.e. - * - *

      {@code
      -     *  void method(NOT_HERE T tIn) {
      -     *     HERE T t = tIn;
      -     * }
      -     * }
      - * - * The parameter {@code tIn} will not be defaulted. The local variable {@code t} will be - * defaulted, in order to allow dataflow to refine {@code T}. - * - *

      This variable will be false if dataflow is not in use. - * * @param annotationScope the element representing the nearest enclosing default annotation * scope for the type * @param type the type to which defaults will be applied - * @param applyToTypeVar whether the default should apply to type variables + * @param fromTree whether the element came from a tree * @checker_framework.manual #effective-qualifier The effective qualifier on a type (defaults * and inference) * @checker_framework.manual #annotating-libraries Annotating libraries */ - // TODO: applyToTypeVar is only one aspect about whether to apply the default to a type - // variable. This needs further cleanup. private void applyDefaultsElement( - Element annotationScope, AnnotatedTypeMirror type, boolean applyToTypeVar) { + Element annotationScope, AnnotatedTypeMirror type, boolean fromTree) { DefaultApplierElement applier = - createDefaultApplierElement(atypeFactory, annotationScope, type, applyToTypeVar); + createDefaultApplierElement(atypeFactory, annotationScope, type, fromTree); DefaultSet defaults = defaultsAt(annotationScope); @@ -888,12 +873,21 @@ private void applyDefaultsElement( } } + /** + * Create the default applier element. + * + * @param atypeFactory the annotated type factory + * @param annotationScope the scope of the default + * @param type the type to which to apply the default + * @param fromTree whether the element came from a tree + * @return the default applier element + */ protected DefaultApplierElement createDefaultApplierElement( AnnotatedTypeFactory atypeFactory, Element annotationScope, AnnotatedTypeMirror type, - boolean applyToTypeVar) { - return new DefaultApplierElement(atypeFactory, annotationScope, type, applyToTypeVar); + boolean fromTree) { + return new DefaultApplierElement(atypeFactory, annotationScope, type, fromTree); } /** A default applier element. */ @@ -911,6 +905,16 @@ protected class DefaultApplierElement { /** The type to which to apply the default. */ protected final AnnotatedTypeMirror type; + /** Whether the element came from a tree. */ + protected final boolean fromTree; + + /** + * True if type variable uses as top-level type of local variables should be defaulted. + * + * @see GenericAnnotatedTypeFactory#getShouldDefaultTypeVarLocals() + */ + private final boolean shouldDefaultTypeVarLocals; + /** * Location to which to apply the default. (Should only be set by the applyDefault method.) */ @@ -919,36 +923,29 @@ protected class DefaultApplierElement { /** The default element applier implementation. */ protected final DefaultApplierElementImpl impl; - /** - * Local type variables are defaulted to top when flow is turned on We only want to default - * the top level type variable (and not type variables that are nested in its bounds). E.g., - * {@code , E extends Object> void method() { T t; } }. - * - *

      We would like t to have its primary annotation defaulted but NOT the E inside its - * upper bound. we use referential equality with the top level type var to determine which - * ones are definite type uses, i.e. uses which can be defaulted - */ - private final @Nullable AnnotatedTypeVariable defaultableTypeVar; - /** * Create an instance. * * @param atypeFactory the type factory * @param scope the scope for the defaults * @param type the type to default - * @param applyToTypeVar whether to apply defaults to type variable uses + * @param fromTree whether the element came from a tree */ public DefaultApplierElement( AnnotatedTypeFactory atypeFactory, Element scope, AnnotatedTypeMirror type, - boolean applyToTypeVar) { + boolean fromTree) { this.atypeFactory = atypeFactory; this.qualHierarchy = atypeFactory.getQualifierHierarchy(); this.scope = scope; this.type = type; + this.fromTree = fromTree; + this.shouldDefaultTypeVarLocals = + (atypeFactory instanceof GenericAnnotatedTypeFactory) + && ((GenericAnnotatedTypeFactory) atypeFactory) + .getShouldDefaultTypeVarLocals(); this.impl = new DefaultApplierElementImpl(this); - this.defaultableTypeVar = applyToTypeVar ? (AnnotatedTypeVariable) type : null; } /** @@ -966,17 +963,16 @@ public void applyDefault(Default def) { * not apply defaults to void types, packages, wildcards, and type variables. * * @param type type to which qual would be applied - * @param applyToTypeVar whether to apply to type variables * @return true if this application should proceed */ - protected boolean shouldBeAnnotated(AnnotatedTypeMirror type, boolean applyToTypeVar) { + protected boolean shouldBeAnnotated(AnnotatedTypeMirror type) { return type != null // TODO: executables themselves should not be annotated // For some reason h1h2checker-tests fails with this. // || type.getKind() == TypeKind.EXECUTABLE && type.getKind() != TypeKind.NONE && type.getKind() != TypeKind.WILDCARD - && (type.getKind() != TypeKind.TYPEVAR || applyToTypeVar) + && type.getKind() != TypeKind.TYPEVAR && !(type instanceof AnnotatedNoType); } @@ -1005,7 +1001,9 @@ protected DefaultApplierElementImpl(DefaultApplierElement outer) { @Override public Void scan(@FindDistinct AnnotatedTypeMirror t, AnnotationMirror qual) { - if (!outer.shouldBeAnnotated(t, t == outer.defaultableTypeVar)) { + if (!outer.shouldBeAnnotated(t)) { + // Type variables and wildcards are separately handled in the corresponding visitors + // below. return super.scan(t, qual); } @@ -1060,7 +1058,7 @@ public Void scan(@FindDistinct AnnotatedTypeMirror t, AnnotationMirror qual) { && isTopLevelType) { for (AnnotatedTypeMirror atm : ((AnnotatedExecutableType) t).getParameterTypes()) { - if (outer.shouldBeAnnotated(atm, false)) { + if (outer.shouldBeAnnotated(atm)) { outer.addAnnotation(atm, qual); } } @@ -1077,11 +1075,12 @@ public Void scan(@FindDistinct AnnotatedTypeMirror t, AnnotationMirror qual) { outer.addAnnotation(t, qual); } else if (outer.scope != null && (outer.scope.getKind() == ElementKind.METHOD) + // TODO: Constructors can also have receivers. && t.getKind() == TypeKind.EXECUTABLE && isTopLevelType) { AnnotatedDeclaredType receiver = ((AnnotatedExecutableType) t).getReceiverType(); - if (outer.shouldBeAnnotated(receiver, false)) { + if (outer.shouldBeAnnotated(receiver)) { outer.addAnnotation(receiver, qual); } } @@ -1093,7 +1092,7 @@ public Void scan(@FindDistinct AnnotatedTypeMirror t, AnnotationMirror qual) { && isTopLevelType) { AnnotatedTypeMirror returnType = ((AnnotatedExecutableType) t).getReturnType(); - if (outer.shouldBeAnnotated(returnType, false)) { + if (outer.shouldBeAnnotated(returnType)) { outer.addAnnotation(returnType, qual); } } @@ -1107,7 +1106,7 @@ public Void scan(@FindDistinct AnnotatedTypeMirror t, AnnotationMirror qual) { // constructor invocation). AnnotatedTypeMirror returnType = ((AnnotatedExecutableType) t).getReturnType(); - if (outer.shouldBeAnnotated(returnType, false)) { + if (outer.shouldBeAnnotated(returnType)) { outer.addAnnotation(returnType, qual); } } @@ -1225,16 +1224,40 @@ public void reset() { public Void visitTypeVariable( @FindDistinct AnnotatedTypeVariable type, AnnotationMirror qual) { if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); + return null; + } + if (outer.qualHierarchy.isParametricQualifier(qual)) { + // Parametric qualifiers are only applicable to type variables and have no effect on + // their type. Therefore, do nothing. + return null; + } + if (type.isDeclaration()) { + // For a type variable declaration, apply the defaults to the bounds. Do not apply + // `TYPE_VARIALBE_USE` defaults. + visitBounds(type, type.getUpperBound(), type.getLowerBound(), qual); + return null; } + boolean isTopLevelType = type == outer.type; - if (isTopLevelType - && !type.isDeclaration() - && outer.location == TypeUseLocation.TYPE_VARIABLE_USE - && !outer.qualHierarchy.isParametricQualifier(qual)) { - outer.addAnnotation(type, qual); + boolean isLocalVariable = + outer.scope != null && ElementUtils.isLocalVariable(outer.scope); + + if (isTopLevelType && isLocalVariable) { + if (outer.shouldDefaultTypeVarLocals + && outer.fromTree + && outer.location == TypeUseLocation.LOCAL_VARIABLE) { + outer.addAnnotation(type, qual); + } else { + // TODO: Should `TYPE_VARIABLE_USE` default apply to top-level local variables, + // if they should not be defaulted according to `shouldDefaultTypeVarLocals`? + visitBounds(type, type.getUpperBound(), type.getLowerBound(), qual); + } } else { - visitBounds(type, type.getUpperBound(), type.getLowerBound(), qual); + if (outer.location == TypeUseLocation.TYPE_VARIABLE_USE) { + outer.addAnnotation(type, qual); + } else { + visitBounds(type, type.getUpperBound(), type.getLowerBound(), qual); + } } return null; } @@ -1242,7 +1265,7 @@ public Void visitTypeVariable( @Override public Void visitWildcard(AnnotatedWildcardType type, AnnotationMirror qual) { if (visitedNodes.containsKey(type)) { - return visitedNodes.get(type); + return null; } visitBounds(type, type.getExtendsBound(), type.getSuperBound(), qual); return null; diff --git a/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesError.java b/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesError.java index a02328d404b2..3a48107b3df4 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesError.java +++ b/framework/src/main/java/org/checkerframework/framework/util/dependenttypes/DependentTypesError.java @@ -27,7 +27,7 @@ public class DependentTypesError { /** Regular expression for unparsing string representations of this class (gross). */ private static final Pattern ERROR_PATTERN = - Pattern.compile("\\[error for expression: (.*); error: (.*)\\]"); + Pattern.compile("\\[error for expression: (.*); error: ([\\s\\S]*)\\]"); /** * Returns whether or not the given expression string is an error. That is, whether it is a diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ValueTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ValueTest.java index f31984cc2ad8..01579f71a5e0 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/ValueTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ValueTest.java @@ -2,6 +2,7 @@ import org.checkerframework.common.value.ValueChecker; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.checkerframework.framework.test.TestUtilities; import org.junit.runners.Parameterized.Parameters; import java.io.File; @@ -26,7 +27,8 @@ public ValueTest(List testFiles) { "value", // Ignore the test suite's usage of qualifiers in illegal locations. "-AignoreTargetLocations", - "-Astubs=tests/value/minints-stub.astub:tests/value/lowercase.astub", + TestUtilities.adapt( + "-Astubs=tests/value/minints-stub.astub:tests/value/lowercase.astub"), "-A" + ValueChecker.REPORT_EVAL_WARNS); } diff --git a/framework/src/test/java/org/checkerframework/framework/test/junit/ViewpointTestCheckerTest.java b/framework/src/test/java/org/checkerframework/framework/test/junit/ViewpointTestCheckerTest.java index f1cae8a23375..81479836a199 100644 --- a/framework/src/test/java/org/checkerframework/framework/test/junit/ViewpointTestCheckerTest.java +++ b/framework/src/test/java/org/checkerframework/framework/test/junit/ViewpointTestCheckerTest.java @@ -1,4 +1,4 @@ -package tests; +package org.checkerframework.framework.test.junit; import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; import org.junit.runners.Parameterized; diff --git a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2AnnotatedTypeFactory.java b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2AnnotatedTypeFactory.java index 3d0580b04f8b..8f33b5378ddb 100644 --- a/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2AnnotatedTypeFactory.java +++ b/framework/src/test/java/org/checkerframework/framework/testchecker/h1h2checker/H1H2AnnotatedTypeFactory.java @@ -58,9 +58,8 @@ protected Set> createSupportedTypeQualifiers() { } @Override - protected void addComputedTypeAnnotations( - Tree tree, AnnotatedTypeMirror type, boolean iUseFlow) { - super.addComputedTypeAnnotations(tree, type, iUseFlow); + protected void addComputedTypeAnnotations(Tree tree, AnnotatedTypeMirror type) { + super.addComputedTypeAnnotations(tree, type); if (tree.getKind() == Tree.Kind.VARIABLE && ((VariableTree) tree).getName().toString().contains("addH1S2")) { type.replaceAnnotation(H1S2); diff --git a/framework/tests/viewpointtest/VarargsConstructor.java b/framework/tests/viewpointtest/VarargsConstructor.java new file mode 100644 index 000000000000..acf2380d741f --- /dev/null +++ b/framework/tests/viewpointtest/VarargsConstructor.java @@ -0,0 +1,65 @@ +// Test case for EISOP issue #777: +// https://github.com/eisop/checker-framework/issues/777 +import viewpointtest.quals.*; + +public class VarargsConstructor { + + VarargsConstructor(String str, Object... args) {} + + @SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"}) + @ReceiverDependentQual + VarargsConstructor(@ReceiverDependentQual Object... args) {} + + void foo() { + VarargsConstructor a = new VarargsConstructor("testStr", new Object()); + } + + void invokeConstructor(@A Object aObj, @B Object bObj, @Top Object topObj) { + @A Object a = new @A VarargsConstructor(aObj); + @B Object b = new @B VarargsConstructor(bObj); + @Top Object top = new @Top VarargsConstructor(topObj); + // :: error: (argument.type.incompatible) + new @A VarargsConstructor(bObj); + // :: error: (argument.type.incompatible) + new @B VarargsConstructor(aObj); + } + + class Inner { + @SuppressWarnings({"inconsistent.constructor.type", "super.invocation.invalid"}) + @ReceiverDependentQual + Inner(@ReceiverDependentQual Object... args) {} + + void foo() { + Inner a = new Inner(); + Inner b = new Inner(new Object()); + Inner c = VarargsConstructor.this.new Inner(); + Inner d = VarargsConstructor.this.new Inner(new Object()); + } + + void invokeConstructor(@A Object aObj, @B Object bObj, @Top Object topObj) { + @A Object a = new @A Inner(aObj); + @B Object b = new @B Inner(bObj); + @Top Object top = new @Top Inner(topObj); + // :: error: (argument.type.incompatible) + new @A Inner(bObj); + // :: error: (argument.type.incompatible) + new @B Inner(aObj); + } + } + + void testAnonymousClass(@A Object aObj, @B Object bObj, @Top Object topObj) { + Object o = + new VarargsConstructor("testStr", new Object()) { + void foo() { + VarargsConstructor a = new VarargsConstructor("testStr", new Object()); + } + }; + @A Object a = new @A VarargsConstructor(aObj) {}; + @B Object b = new @B VarargsConstructor(bObj) {}; + @Top Object top = new @Top VarargsConstructor(topObj) {}; + // :: error: (argument.type.incompatible) + new @A VarargsConstructor(bObj) {}; + // :: error: (argument.type.incompatible) + new @B VarargsConstructor(aObj) {}; + } +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a80b22ce5cff..09523c0e5490 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a426907..f5feea6d6b11 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 7101f8e4676f..9b42019c7915 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/ElementUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/ElementUtils.java index a94bafee4345..831fa8ac7216 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/ElementUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/ElementUtils.java @@ -162,8 +162,7 @@ public static TypeElement toplevelEnclosingTypeElement(Element element) { public static PackageElement enclosingPackage(Element elem) { Element result = elem; while (result != null && result.getKind() != ElementKind.PACKAGE) { - Element encl = result.getEnclosingElement(); - result = encl; + result = result.getEnclosingElement(); } return (PackageElement) result; } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/Resolver.java b/javacutil/src/main/java/org/checkerframework/javacutil/Resolver.java index c34ff92f6134..c1c87bb3f6c1 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/Resolver.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/Resolver.java @@ -64,16 +64,33 @@ public class Resolver { /** Whether we are running on at least Java 13. */ private static final boolean atLeastJava13; + /** Whether we are running on at least Java 24. */ + private static final boolean atLeastJava24; + + /** + * Determines whether the given {@link SourceVersion} release version string is supported. + * + * @param release the {@link SourceVersion} release version + * @return whether the given version is supported + */ + private static boolean atLeastJava(String release) { + final SourceVersion latestSource = SourceVersion.latest(); + SourceVersion javaVersion; + try { + javaVersion = SourceVersion.valueOf(release); + } catch (IllegalArgumentException e) { + javaVersion = null; + } + @SuppressWarnings("EnumOrdinal") // No better way to compare. + boolean atLeastJava = + javaVersion != null && latestSource.ordinal() >= javaVersion.ordinal(); + return atLeastJava; + } + static { try { - final SourceVersion latestSource = SourceVersion.latest(); - SourceVersion java13; - try { - java13 = SourceVersion.valueOf("RELEASE_13"); - } catch (IllegalArgumentException e) { - java13 = null; - } - atLeastJava13 = java13 != null && latestSource.ordinal() >= java13.ordinal(); + atLeastJava13 = atLeastJava("RELEASE_13"); + atLeastJava24 = atLeastJava("RELEASE_24"); FIND_METHOD = Resolve.class.getDeclaredMethod( @@ -87,7 +104,15 @@ public class Resolver { boolean.class); FIND_METHOD.setAccessible(true); - FIND_VAR = Resolve.class.getDeclaredMethod("findVar", Env.class, Name.class); + if (atLeastJava24) { + // Changed in + // https://github.com/openjdk/jdk/commit/e227c7e37d4de0656f013f3a936b1acfa56cc2e0 + FIND_VAR = + Resolve.class.getDeclaredMethod( + "findVar", DiagnosticPosition.class, Env.class, Name.class); + } else { + FIND_VAR = Resolve.class.getDeclaredMethod("findVar", Env.class, Name.class); + } FIND_VAR.setAccessible(true); if (atLeastJava13) { diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java index 27f47d3284f4..6ee85e6f2ff4 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java @@ -148,9 +148,7 @@ private TreeUtils() { TREEMAKER_SELECT = TreeMaker.class.getMethod("Select", JCExpression.class, Symbol.class); } catch (NoSuchMethodException e) { - Error err = new AssertionError("Unexpected error in TreeUtils static initializer"); - err.initCause(e); - throw err; + throw new AssertionError("Unexpected error in TreeUtils static initializer", e); } }