From 848e4632dc13512885c598e74cbe6422d2338421 Mon Sep 17 00:00:00 2001 From: Aosen Xiong <82676488+Ao-senXiong@users.noreply.github.com> Date: Thu, 30 May 2024 10:16:01 -0400 Subject: [PATCH] PICO-local-pull-in (#1) * adapt pico to CF and CFI master * fix pico per changes in upstream CF and CFI * remove incorrect aliasing and unnecessary method check * remove wrong test diagnostic * adapt to upstream changes * fix extends and implements check * tweak travis build script * Remove useless separators. * Unify the name of test files. * Fix typo. * Fix typo. * Fix typo. * Complete the messages. * Use all dirs and jar file to fix the issue that "messages.properties" cannot be found. (#1) * Tweaks. * Use the correct error key. * Fix typo (CompatabilityBEI2.java -> CompatibilityBEI2.java) and make CompatibilityBEI2.java pass. (#8) Make CompatibilityBEI2.java pass. * Update. * Update. * api updates * typo * default-for annotator, default bottom on null, allow readonly as upper bound * Implement super constructor call check. Replace the error key "super.constructor.invocation.incompatible" with "super.invocation.invalid". Tweaks test files to pass the test. * Improve code and javadoc. * clean debug messages and comment * fixed HashCodeSafetyExample, addMissingAnnotations->replaceAnnotation * fixed CompatibilityBEI2 + ReceiverTypeOutsideConstructor: pull default annotation for extends and implements clauses * milestone: all typecheck tests passed * add anno invalid check in local variables; add RDM extends/implements rules * override cf default extends/implements checks * fix anonymous type of new clause and test case milestone: no INITIAL_TYPECHECK error in inference tests * reuse test cases for typecheck and infer initial typecheck * fix a accidentally disabled test case * fix additional tests' errors * test vpa over imp * TODO: resolve path issues, remove this when fixed * default class bound and super clause * default class bound and super clause enhanced * implicit immutable class bound + cast.unsafe * remove wrong cast.unsafe keys * bound of array fixed to READONLY. VPA not needed here because any type is permitted. Note: how does PICO prevent changes on immutable arrays? * bottom workaround + vpa interface * enum default immutable. TODO: fix constructor check in mutable enum decl * fix enum default in inference * doc * relocate common interface and annotator * bound * enum & super clause * test case update * tweaks on generic * fix anno * poly workaround * update test cases (typecheck 100%, inference-init-tc 100%) * API update: report * API update: remove @DefaultInUncheckedCodeFor * remove @SPM * Revert "remove @SPM" This reverts commit fe280a96 * @SPM for static moved to new api * exclude decl when checking static RDM (TC: 100%, INF: 6 fail) * small tweaks * tc cast.unsafe (TC: 100%, INF: 4 fail) * inf-> workaround for anonymous class * a small tweak * update test cases (TC all pass) * re-remove @SPM * add CF all-systems test * small tweaks on test drivers * interface * isSubtype use native api * infer atf + get bound * tweaks * checker.report update * checker.report update * tc * tc * ignore rdm on fields * rdm field logic + tc * VPA post check * vpa post check * field rdm default * check anno * tc update * transitive (needs fix) * class bound != PM * receiver != BOTTOM * stop assign immutable constant slot to enum usage. enum could be mutable * PICO does not need existential slot on class decl anno * use constant slot for explicit class decl anno * update init bound extraction function * update call to init bound extraction function * prevent poly infer * bound handling for anonymous class * anonymous class method receiver workaround * workarounds for anonymous class for getTypeOfExtendsImplements * extract method * do not apply real anno to atm during infer * not apply immutable constant to enum during infer-typecheck * add init bound extraction function * update tc * array bound -> RDM (TC) * exclude static inner class decl from static scope * don't check with default during inference * add immutable alias for real type factory * array decl method * implicit shallow immutable warning (TC) * tune up vpa * better logic to check anonymous * enum heck * enforce check without refinement * workaround for implicit lib type param * disable alias * workaround: vpa decl w/o anno * consistency infer and tc * ext and imp clause logic * override base ext/imp clause check * override base check on ext/imp clauses * anonymous class logic * receiver cannot infer to poly * allow explicit poly on receiver * class bound for infer * allow factory to get class decl slot * annotator: handle decl bound * annotator: skip null * annotator: replace real anno * annotator: not infer to poly * annotator: drop super decl constraints * visitor: skip uses w/o slot * rdm-field: field uses be both rdm or mutable * comments * notes * tweaks * stubs * reim test cases * glacier test cases * temp ignore all systems test * stub path problem * +useOptimisticUncheckedDefaults * import issue * import issue * update pico, fix CF api rename, update gradle version * commits for fixing PICO-TypeCheck and part of PICO-Infer * resolve new array problems * add inference component back to gradle test * E on line 5 extends readonly, but the declared on line 3 extends immutable * use valueof static method * use smaller case local variable * add imports to the testfile * change implicit.shallow.immutable from warning to error * make the testcase more clear * Clarify the purpose of the junit test; make default annotations explicitly for one junit test * refactor and fix bugs causing test failures * fix typecheck error * Adapt PICO to opprop * Delete glacier and reminfer test for merge * Delete reminfer test for merge * Add HoldsForDefaultValue for quals * Delete old PICO files * Add new line at the end of files * Fix compile test errors * Delete reim and glacier related tests * Add error to test case * Handle initialization block * Add a new line at the end of file * Update build.gradle * Wrap up type check * Add a new line at the end * Further clean up the code * Simplify the code * Further clean up * New line at the end --------- Co-authored-by: Baorui Zhou Co-authored-by: xingweitian <13183370+xingweitian@users.noreply.github.com> Co-authored-by: Werner Dietl Co-authored-by: xingweitian Co-authored-by: lnsun Co-authored-by: lnsun <57457122+lnsun@users.noreply.github.com> Co-authored-by: Haifeng Shi Co-authored-by: Haifeng Shi --- .github/workflows/main.yml | 22 +- build.gradle | 18 +- check.sh | 11 +- gradle/wrapper/gradle-wrapper.properties | 2 +- infer.sh | 12 +- run-dljc.sh | 5 +- .../pico/common/ExtendedViewpointAdapter.java | 11 + .../{typecheck => common}/PICOTypeUtil.java | 162 ++-- .../pico/common/ViewpointAdapterGettable.java | 5 + .../PICOInferenceAnnotatedTypeFactory.java | 237 +++-- .../pico/inference/PICOInferenceChecker.java | 13 +- ...PICOInferenceExtendedViewpointAdapter.java | 37 + .../PICOInferenceRealTypeFactory.java | 245 +++-- .../inference/PICOInferenceValidator.java | 99 +- .../PICOInferenceViewpointAdapter.java | 34 +- .../pico/inference/PICOInferenceVisitor.java | 894 ++++++++++++------ .../pico/inference/PICOVariableAnnotator.java | 334 ++++--- src/main/java/pico/inference/jdk.astub | 300 ++++++ .../java/pico/inference/messages.properties | 16 + .../solver/PICOCombineConstraintEncoder.java | 9 +- .../inference/solver/PICOSolverEngine.java | 18 +- .../ObjectIdentityMethodEnforcer.java | 17 +- .../java/pico/typecheck/PICOAnalysis.java | 40 - .../typecheck/PICOAnnotatedTypeFactory.java | 528 ----------- .../typecheck/PICOAnnotationMirrorHolder.java | 3 - src/main/java/pico/typecheck/PICOChecker.java | 18 +- .../pico/typecheck/PICONoInitAnalysis.java | 35 + .../PICONoInitAnnotatedTypeFactory.java | 513 ++++++++++ .../java/pico/typecheck/PICONoInitStore.java | 25 + .../pico/typecheck/PICONoInitSubchecker.java | 27 + .../pico/typecheck/PICONoInitTransfer.java | 44 + .../java/pico/typecheck/PICONoInitValue.java | 16 + .../pico/typecheck/PICONoInitVisitor.java | 599 ++++++++++++ src/main/java/pico/typecheck/PICOStore.java | 18 - .../java/pico/typecheck/PICOTransfer.java | 49 - .../java/pico/typecheck/PICOValidator.java | 55 +- src/main/java/pico/typecheck/PICOValue.java | 17 - .../pico/typecheck/PICOViewpointAdapter.java | 30 +- src/main/java/pico/typecheck/PICOVisitor.java | 564 ----------- src/main/java/pico/typecheck/jdk.astub | 40 +- .../java/pico/typecheck/messages.properties | 8 +- src/main/java/qual/Assignable.java | 3 + src/main/java/qual/Bottom.java | 28 +- src/main/java/qual/Immutable.java | 20 +- src/main/java/qual/Mutable.java | 2 + src/main/java/qual/PolyMutable.java | 7 +- src/main/java/qual/Readonly.java | 5 +- .../java/qual/ReceiverDependantMutable.java | 4 +- .../java/qual/SubstitutablePolyMutable.java | 15 - ...tabilityInferenceInitialTypecheckTest.java | 22 + .../java/pico/ImmutabilityInferenceTest.java | 16 +- ...mutabilityTypecheckBaseAllSystemsTest.java | 27 + ...=> ImmutabilityTypecheckExtendedTest.java} | 8 +- .../java/pico/ImmutabilityTypecheckTest.java | 22 + .../inferrable/BoundsCompatible.java | 3 +- .../ConstructorInvocationUsingNew.java | 2 +- .../inference/inferrable/FixableTypeCast.java | 1 - .../NoBoundTypeOfAnonymousClass.java | 3 +- .../inference/inferrable/RawIterator.java | 6 +- .../inference/inferrable/StrangeReadonly.java | 7 +- .../inference/inferrable/TypeVariableUse.java | 7 +- .../inferrable/issue144/ComplicatedTest.java | 2 +- ...ructorInvocationInSubclassConstructor.java | 2 +- .../inferrable/issue144/RDMConstructor.java | 2 +- .../typecheck/AnnotationApplicationOrder.java | 1 + testinput/typecheck/AssignableExample.java | 4 +- testinput/typecheck/CompatabilityBEI1.java | 6 +- ...bilityBEI2.java => CompatibilityBEI2.java} | 10 +- testinput/typecheck/CopyToCast.java | 4 +- testinput/typecheck/DateCell.java | 1 - testinput/typecheck/DateCell2.java | 2 +- testinput/typecheck/DeepMutable.java | 33 + testinput/typecheck/DiamondTreeProblem.java | 3 - .../EnumConstantNotAlwaysMutable.java | 4 +- testinput/typecheck/EnumTests.java | 27 + testinput/typecheck/FieldAssignment.java | 1 - testinput/typecheck/FieldsInitialized.java | 2 +- testinput/typecheck/ForbidAssignmentCase.java | 12 +- testinput/typecheck/GenericInterfaces.java | 1 + .../typecheck/HashCodeSafetyExample.java | 2 +- testinput/typecheck/ImmutableClass1.java | 13 +- testinput/typecheck/ImmutableConstructor.java | 8 +- .../ImplicitAppliesToMethodReceiver.java | 2 +- testinput/typecheck/InvalidAssignability.java | 3 + .../MethodReceiverNotInhericClassBound.java | 4 +- testinput/typecheck/MutableConstructor.java | 4 +- .../NotEveryInstFieldDefaultToRDM.java | 4 +- .../typecheck/ObjectIdentityMethodTest.java | 4 +- testinput/typecheck/ObjectMethods.java | 2 +- testinput/typecheck/OnlyOneModifierIsUse.java | 6 +- testinput/typecheck/Planet.java | 4 +- .../PolyMutableOnConstructorParameters.java | 2 +- testinput/typecheck/Primitive3.java | 2 +- testinput/typecheck/RDMBug.java | 1 - testinput/typecheck/RDMField.java | 48 + testinput/typecheck/RDMFieldInst.java | 34 + .../ReceiverTypeOutsideConstructor.java | 20 +- testinput/typecheck/SuperClass.java | 8 +- testinput/typecheck/SuperClass2.java | 4 +- .../typecheck/SupportedBuilderPattern.java | 2 +- testinput/typecheck/Transitive.java | 46 + 101 files changed, 3515 insertions(+), 2163 deletions(-) create mode 100644 src/main/java/pico/common/ExtendedViewpointAdapter.java rename src/main/java/pico/{typecheck => common}/PICOTypeUtil.java (77%) create mode 100644 src/main/java/pico/common/ViewpointAdapterGettable.java create mode 100644 src/main/java/pico/inference/PICOInferenceExtendedViewpointAdapter.java create mode 100644 src/main/java/pico/inference/jdk.astub create mode 100644 src/main/java/pico/inference/messages.properties delete mode 100644 src/main/java/pico/typecheck/PICOAnalysis.java delete mode 100644 src/main/java/pico/typecheck/PICOAnnotatedTypeFactory.java create mode 100644 src/main/java/pico/typecheck/PICONoInitAnalysis.java create mode 100644 src/main/java/pico/typecheck/PICONoInitAnnotatedTypeFactory.java create mode 100644 src/main/java/pico/typecheck/PICONoInitStore.java create mode 100644 src/main/java/pico/typecheck/PICONoInitSubchecker.java create mode 100644 src/main/java/pico/typecheck/PICONoInitTransfer.java create mode 100644 src/main/java/pico/typecheck/PICONoInitValue.java create mode 100644 src/main/java/pico/typecheck/PICONoInitVisitor.java delete mode 100644 src/main/java/pico/typecheck/PICOStore.java delete mode 100644 src/main/java/pico/typecheck/PICOTransfer.java delete mode 100644 src/main/java/pico/typecheck/PICOValue.java delete mode 100644 src/main/java/pico/typecheck/PICOVisitor.java delete mode 100644 src/main/java/qual/SubstitutablePolyMutable.java create mode 100644 src/test/java/pico/ImmutabilityInferenceInitialTypecheckTest.java create mode 100644 src/test/java/pico/ImmutabilityTypecheckBaseAllSystemsTest.java rename src/test/java/pico/{ImmutabilityTypecheckTests.java => ImmutabilityTypecheckExtendedTest.java} (62%) create mode 100644 src/test/java/pico/ImmutabilityTypecheckTest.java rename testinput/typecheck/{CompatabilityBEI2.java => CompatibilityBEI2.java} (84%) create mode 100644 testinput/typecheck/DeepMutable.java create mode 100644 testinput/typecheck/EnumTests.java create mode 100644 testinput/typecheck/RDMField.java create mode 100644 testinput/typecheck/RDMFieldInst.java create mode 100644 testinput/typecheck/Transitive.java diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 34e54f2..4a637ca 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -49,18 +49,10 @@ jobs: if: ${{ steps.build.outcome == 'success' }} run: ./gradlew ImmutabilityTypecheckExtendedTest - - name: PICO Glacier TypeCheck Test - if: ${{ steps.build.outcome == 'success' }} - run: ./gradlew ImmutabilityTypecheckGlacierTest - - - name: PICO Inference Initial Typecheck Test - if: ${{ steps.build.outcome == 'success' }} - run: ./gradlew ImmutabilityInferenceInitialTypecheckTest - - - name: PICO Inference Test - if: ${{ steps.build.outcome == 'success' }} - run: ./gradlew ImmutabilityInferenceTest - - - name: PICO ReIm Inference Test - if: ${{ steps.build.outcome == 'success' }} - run: ./gradlew ImmutabilityReImInferenceTest +# - name: PICO Inference Initial Typecheck Test +# if: ${{ steps.build.outcome == 'success' }} +# run: ./gradlew ImmutabilityInferenceInitialTypecheckTest +# +# - name: PICO Inference Test +# if: ${{ steps.build.outcome == 'success' }} +# run: ./gradlew ImmutabilityInferenceTest diff --git a/build.gradle b/build.gradle index 9ba9976..b35a60d 100644 --- a/build.gradle +++ b/build.gradle @@ -46,17 +46,16 @@ repositories { } dependencies { - compile fileTree(dir: "${cfPath}/checker/dist", include: "checker.jar") - compile fileTree(dir: "${cfiPath}/dist", include: "checker-framework-inference.jar") + implementation fileTree(dir: "${cfPath}/checker/dist", include: "checker.jar") + implementation fileTree(dir: "${cfiPath}/dist", include: "checker-framework-inference.jar") // sat4j solver dependency - compile 'org.ow2.sat4j:org.ow2.sat4j.core:2.3.6' - compile 'org.ow2.sat4j:org.ow2.sat4j.maxsat:2.3.6' + implementation 'org.ow2.sat4j:org.ow2.sat4j.core:2.3.6' + implementation 'org.ow2.sat4j:org.ow2.sat4j.maxsat:2.3.6' // The production code uses the SLF4J logging API at compile time - compile 'org.slf4j:slf4j-api:1.7.30' - + implementation 'org.slf4j:slf4j-api:1.7.29' // CF test lib dependency - testCompile fileTree(dir: "${cfPath}/framework-test/build/libs", include: "framework-test-*.jar") - testCompile 'junit:junit:4.13.2' + testImplementation fileTree(dir: "${cfPath}/framework-test/build/libs", include: "framework-test-*.jar") + testImplementation 'junit:junit:4.13.2' } sourceSets { @@ -82,6 +81,7 @@ sourceSets { compileJava { options.compilerArgs = [ '-implicit:class', + '-Xlint:deprecation', '-Awarns', '-Xmaxwarns', '10000', ] @@ -107,7 +107,7 @@ afterEvaluate { JDK_JAR: "${cfPath}/checker/dist/jdk8.jar" environment "external_checker_classpath", "${picoPath}/build/classes/java/main:${picoPath}/build/resources/main" - + maxHeapSize = "1024m" if (isJava8) { jvmArgs "-Xbootclasspath/p:${cfiPath}/dist/javac.jar" } else { diff --git a/check.sh b/check.sh index a6cbccc..2a6f822 100755 --- a/check.sh +++ b/check.sh @@ -10,7 +10,8 @@ export JAVAC=$CF/checker/bin/javac export PICO=$(cd $(dirname "$0") && pwd) # Dependencies -export CLASSPATH=$PICO/build/classes/java/main:$CFI/dist/checker-framework-inference.jar +export CLASSPATH=$PICO/build/classes/java/main:$PICO/build/resources/main:\ +$PICO/build/libs/immutability.jar:$CFI/dist/checker-framework-inference.jar # Command DEBUG="" @@ -19,8 +20,8 @@ CHECKER="pico.typecheck.PICOChecker" declare -a ARGS for i in "$@" ; do if [[ $i == "-d" ]] ; then - echo "Typecheck using debug mode. Listening at port 5050. Waiting for connection...." - DEBUG="-J-Xdebug -J-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5050" + echo "Typecheck using debug mode. Listening at port 5005. Waiting for connection...." + DEBUG="-J-Xdebug -J-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005" continue fi @@ -35,9 +36,9 @@ done cmd="" if [ "$DEBUG" == "" ]; then - cmd="$JAVAC -cp "${CLASSPATH}" -processor "${CHECKER}" "${ARGS[@]}"" + cmd="$JAVAC -cp "${CLASSPATH}" -processor "${CHECKER}" "${ARGS[@]}"" else - cmd="$JAVAC "$DEBUG" -cp "${CLASSPATH}" -processor "${CHECKER}" -AatfDoNotCache "${ARGS[@]}"" + cmd="$JAVAC "$DEBUG" -cp "${CLASSPATH}" -processor "${CHECKER}" -AatfDoNotCache "${ARGS[@]}"" fi eval "$cmd" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index be280be..6b7fd26 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip diff --git a/infer.sh b/infer.sh index 695c80b..e3e81fb 100755 --- a/infer.sh +++ b/infer.sh @@ -8,7 +8,9 @@ export CFI=$JSR308/checker-framework-inference export PICO=$(cd $(dirname "$0") && pwd) # Dependencies -export CLASSPATH=$PICO/build/classes/java/main:$CFI/dist/checker-framework-inference.jar +export CLASSPATH=$PICO/build/classes/java/main:$PICO/build/resources/main:\ +$PICO/build/libs/immutability.jar:$CFI/dist/checker-framework-inference.jar + export external_checker_classpath=$PICO/build/classes/java/main:$PICO/build/resources/main export AFU=$JSR308/annotation-tools/annotation-file-utilities @@ -18,6 +20,9 @@ export JDK_JAR=$CF/checker/dist/jdk8.jar CHECKER=pico.inference.PICOInferenceChecker SOLVER=pico.inference.solver.PICOSolverEngine +#SOLVER=checkers.inference.solver.DebugSolver + +STUBS="src/main/java/pico/inference/jdk.astub" declare -a ARGS for i in "$@" ; do @@ -31,11 +36,12 @@ done SOLVER_ARGS=useGraph=false,collectStatistic=true -IS_HACK=true +TRUE=true # echo "${ARGS[@]}" # Start the inference $CFI/scripts/inference-dev -m ROUNDTRIP --checker "$CHECKER" --solver "$SOLVER" \ - --solverArgs="useGraph=false,collectStatistic=true" --hacks="$IS_HACK" \ + --solverArgs="useGraph=false,collectStatistic=true" --hacks="$TRUE" \ + --cfArgs="-Astubs=$STUBS" --makeDefaultsExplicit="$TRUE" \ -afud ./annotated "${ARGS[@]}" diff --git a/run-dljc.sh b/run-dljc.sh index 87238a7..64b9009 100755 --- a/run-dljc.sh +++ b/run-dljc.sh @@ -9,7 +9,7 @@ export AFU="$JSR308"/annotation-tools/annotation-file-utilities export PATH="$PATH":"$AFU"/scripts export CFI="$JSR308"/checker-framework-inference -export CLASSPATH="$JSR308"/immutability/build/classes/main:$CHECKERFRAMEWORK/dataflow/build:$CHECKERFRAMEWORK/javacutil/build:$CHECKERFRAMEWORK/stubparser/build:$CHECKERFRAMEWORK/framework/build:$CHECKERFRAMEWORK/checker/build:$SOLVER/bin:$CHECKERFRAMEWORK/framework/tests/junit-4.12.jar:$CHECKERFRAMEWORK/framework/tests/hamcrest-core-1.3.jar:$CFI/dist/checker-framework-inference.jar:$CFI/dist/org.ow2.sat4j.core-2.3.4.jar:$CFI/dist/commons-logging-1.2.jar:$CFI/dist/log4j-1.2.16.jar:$JSR308/jsr308-langtools/build/classes:$CLASSPATH +export CLASSPATH="$JSR308"/immutability/build/classes/java/main:$CHECKERFRAMEWORK/dataflow/build:$CHECKERFRAMEWORK/javacutil/build:$CHECKERFRAMEWORK/stubparser/build:$CHECKERFRAMEWORK/framework/build:$CHECKERFRAMEWORK/checker/build:$SOLVER/bin:$CHECKERFRAMEWORK/framework/tests/junit-4.12.jar:$CHECKERFRAMEWORK/framework/tests/hamcrest-core-1.3.jar:$CFI/dist/checker-framework-inference.jar:$CFI/dist/org.ow2.sat4j.core-2.3.4.jar:$CFI/dist/commons-logging-1.2.jar:$CFI/dist/log4j-1.2.16.jar:$JSR308/jsr308-langtools/build/classes:$CLASSPATH #parsing build command of the target program build_cmd="$2" @@ -30,8 +30,9 @@ elif [[ "$1" = "-i" ]] ; then rm -rf logs annotated echo "Cleaning Done." CHECKER="pico.inference.PICOInferenceChecker" +# SOLVER="checkers.inference.solver.DebugSolver" SOLVER="pico.inference.solver.PICOSolverEngine" - running_cmd="python $DLJC/dljc -t inference --checker "${CHECKER}" --cfArgs=\"-AoptimalSolution\" --solver "${SOLVER}" --solverArgs=\"collectStatistic=true,useGraph=false\" --mode ROUNDTRIP -afud $WORKING_DIR/annotated -o logs -- $build_cmd " + running_cmd="python $DLJC/dljc -t inference --checker "${CHECKER}" --cfArgs=\"-AoptimalSolution -Astubs=/home/l82sun/workspace/opprop/immutability/src/main/java/pico/inference/jdk.astub\" --solver "${SOLVER}" --solverArgs=\"collectStatistic=true,useGraph=false\" --guess --stubs="/home/l82sun/workspace/opprop/immutability/src/main/java/pico/inference/jdk.astub" --mode ROUNDTRIP -afud $WORKING_DIR/annotated -o logs -- $build_cmd " else echo "Unknown tool: should be either -t|-i but found: ${1}" exit 1 diff --git a/src/main/java/pico/common/ExtendedViewpointAdapter.java b/src/main/java/pico/common/ExtendedViewpointAdapter.java new file mode 100644 index 0000000..a02de53 --- /dev/null +++ b/src/main/java/pico/common/ExtendedViewpointAdapter.java @@ -0,0 +1,11 @@ +package pico.common; + +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.ViewpointAdapter; + +import javax.lang.model.element.AnnotationMirror; + +public interface ExtendedViewpointAdapter extends ViewpointAdapter { + AnnotatedTypeMirror rawCombineAnnotationWithType(AnnotationMirror anno, AnnotatedTypeMirror type); + AnnotationMirror rawCombineAnnotationWithAnnotation(AnnotationMirror anno, AnnotationMirror type); +} diff --git a/src/main/java/pico/typecheck/PICOTypeUtil.java b/src/main/java/pico/common/PICOTypeUtil.java similarity index 77% rename from src/main/java/pico/typecheck/PICOTypeUtil.java rename to src/main/java/pico/common/PICOTypeUtil.java index abaff17..94c8ab2 100644 --- a/src/main/java/pico/typecheck/PICOTypeUtil.java +++ b/src/main/java/pico/common/PICOTypeUtil.java @@ -1,4 +1,4 @@ -package pico.typecheck; +package pico.common; import checkers.inference.InferenceMain; import checkers.inference.SlotManager; @@ -6,15 +6,16 @@ import checkers.inference.model.ConstraintManager; import checkers.inference.model.Slot; import checkers.inference.util.InferenceUtil; -import com.sun.source.tree.AssignmentTree; import com.sun.source.tree.ClassTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.MethodTree; +import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; import com.sun.source.tree.UnaryTree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; -import org.checkerframework.framework.qual.ImplicitFor; +import com.sun.tools.javac.code.Symbol; +import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.TypeKind; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; @@ -23,8 +24,10 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; import org.checkerframework.framework.util.AnnotatedTypes; import org.checkerframework.javacutil.AnnotationProvider; +import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TreePathUtil; import org.checkerframework.javacutil.TypesUtils; import qual.Assignable; import qual.Immutable; @@ -41,10 +44,12 @@ import javax.lang.model.type.TypeMirror; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import static pico.typecheck.PICOAnnotationMirrorHolder.IMMUTABLE; @@ -64,27 +69,27 @@ public class PICOTypeUtil { sideEffectingUnaryOperators.add(Tree.Kind.PREFIX_DECREMENT); } - private static boolean isInTypesOfImplicitForOfImmutable(AnnotatedTypeMirror atm) { - ImplicitFor implicitFor = Immutable.class.getAnnotation(ImplicitFor.class); - assert implicitFor != null; - assert implicitFor.types() != null; - for (TypeKind typeKind : implicitFor.types()) { - if (typeKind.name() == atm.getKind().name()) return true; + private static boolean isInTypeKindsOfDefaultForOfImmutable(AnnotatedTypeMirror atm) { + DefaultFor defaultFor = Immutable.class.getAnnotation(DefaultFor.class); + assert defaultFor != null; + for (TypeKind typeKind : defaultFor.typeKinds()) { + if (typeKind.name().equals(atm.getKind().name())) return true; } return false; } - private static boolean isInTypeNamesOfImplicitForOfImmutable(AnnotatedTypeMirror atm) { - if (atm.getKind().name() != TypeKind.DECLARED.name()) { + private static boolean isInTypesOfDefaultForOfImmutable(AnnotatedTypeMirror atm) { + if (!atm.getKind().name().equals(TypeKind.DECLARED.name())) { return false; } - ImplicitFor implicitFor = Immutable.class.getAnnotation(ImplicitFor.class); - assert implicitFor != null; - assert implicitFor.typeNames() != null; - Class[] typeNames = implicitFor.typeNames(); + DefaultFor defaultFor = Immutable.class.getAnnotation(DefaultFor.class); + assert defaultFor != null; + Class[] types = defaultFor.types(); String fqn = TypesUtils.getQualifiedName((DeclaredType) atm.getUnderlyingType()).toString(); - for (int i = 0; i < typeNames.length; i++) { - if (typeNames[i].getCanonicalName().toString().contentEquals(fqn)) return true; + for (Class type : types) { + if (type.getCanonicalName().contentEquals(fqn)) { + return true; + } } return false; } @@ -92,9 +97,8 @@ private static boolean isInTypeNamesOfImplicitForOfImmutable(AnnotatedTypeMirror /**Method to determine if the underlying type is implicitly immutable. This method is consistent * with the types and typeNames that are in @ImplicitFor in the definition of @Immutable qualifier*/ public static boolean isImplicitlyImmutableType(AnnotatedTypeMirror atm) { - return isInTypesOfImplicitForOfImmutable(atm) - || isInTypeNamesOfImplicitForOfImmutable(atm) - || isEnumOrEnumConstant(atm); + return isInTypeKindsOfDefaultForOfImmutable(atm) + || isInTypesOfDefaultForOfImmutable(atm); } /** @@ -115,12 +119,12 @@ public static AnnotatedDeclaredType getBoundTypeOfEnclosingTypeDeclaration(Tree if (node instanceof MethodTree) { MethodTree methodTree = (MethodTree) node; ExecutableElement element = TreeUtils.elementFromDeclaration(methodTree); - typeElement = ElementUtils.enclosingClass(element); + typeElement = ElementUtils.enclosingTypeElement(element); } else if(node instanceof VariableTree) { VariableTree variableTree = (VariableTree) node; VariableElement variableElement = TreeUtils.elementFromDeclaration(variableTree); assert variableElement!= null && variableElement.getKind().isField(); - typeElement = ElementUtils.enclosingClass(variableElement); + typeElement = ElementUtils.enclosingTypeElement(variableElement); } if (typeElement != null) { @@ -131,7 +135,7 @@ public static AnnotatedDeclaredType getBoundTypeOfEnclosingTypeDeclaration(Tree } public static AnnotatedDeclaredType getBoundTypeOfEnclosingTypeDeclaration(Element element, AnnotatedTypeFactory atypeFactory) { - TypeElement typeElement = ElementUtils.enclosingClass(element); + TypeElement typeElement = ElementUtils.enclosingTypeElement(element); if (typeElement != null) { return getBoundTypeOfTypeDeclaration(typeElement, atypeFactory); } @@ -152,7 +156,7 @@ public static List getBoundTypesOfDirectSuperTypes(TypeEl supertypecls = null; } - if (supertypecls != null && supertypecls.getKind().name() != TypeKind.NONE.name()) { + if (supertypecls != null && !supertypecls.getKind().name().equals(TypeKind.NONE.name())) { TypeElement supercls = (TypeElement) ((DeclaredType) supertypecls).asElement(); boundsOfSupers.add(getBoundTypeOfTypeDeclaration(supercls, atypeFactory)); } @@ -187,10 +191,14 @@ public static AnnotatedDeclaredType getBoundTypeOfTypeDeclaration(TypeElement ty // places, at some time. } + public static AnnotatedDeclaredType getBoundTypeOfTypeDeclaration(TypeMirror typeMirror, AnnotatedTypeFactory atypeFactory) { + return getBoundTypeOfTypeDeclaration(TypesUtils.getTypeElement(typeMirror), atypeFactory); + } + public static boolean isObjectIdentityMethod(MethodTree node, AnnotatedTypeFactory annotatedTypeFactory) { - Element element = TreeUtils.elementFromTree(node); - return isObjectIdentityMethod((ExecutableElement) element, annotatedTypeFactory); + ExecutableElement element = TreeUtils.elementFromDeclaration(node); + return isObjectIdentityMethod(element, annotatedTypeFactory); } @@ -233,17 +241,27 @@ public static void addDefaultForField(AnnotatedTypeFactory annotatedTypeFactory, if (element != null && element.getKind() == ElementKind.FIELD) { if (ElementUtils.isStatic(element)) { AnnotatedTypeMirror explicitATM = annotatedTypeFactory.fromElement(element); - if (!explicitATM.isAnnotatedInHierarchy(READONLY)) { + if (!explicitATM.hasAnnotationInHierarchy(READONLY)) { if (!PICOTypeUtil.isImplicitlyImmutableType(explicitATM)) { annotatedTypeMirror.replaceAnnotation(MUTABLE); } } } else { AnnotatedTypeMirror explicitATM = annotatedTypeFactory.fromElement(element); - if (!explicitATM.isAnnotatedInHierarchy(READONLY)) { + if (!explicitATM.hasAnnotationInHierarchy(READONLY)) { if (explicitATM instanceof AnnotatedDeclaredType) { AnnotatedDeclaredType adt = (AnnotatedDeclaredType) explicitATM; Element typeElement = adt.getUnderlyingType().asElement(); + + // add RDM if bound=M and enclosingBound=M/RDM + Set enclosingBound = annotatedTypeFactory.getTypeDeclarationBounds( + Objects.requireNonNull(ElementUtils.enclosingTypeElement(element)).asType()); + Set declBound = annotatedTypeFactory.getTypeDeclarationBounds(element.asType()); + if (AnnotationUtils.containsSameByName(declBound, MUTABLE)) { + if (AnnotationUtils.containsSameByName(enclosingBound, MUTABLE)) { + annotatedTypeMirror.replaceAnnotation(RECEIVER_DEPENDANT_MUTABLE); + } + } if (typeElement instanceof TypeElement) { AnnotatedDeclaredType bound = getBoundTypeOfTypeDeclaration((TypeElement) typeElement, annotatedTypeFactory); if (bound.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { @@ -269,6 +287,15 @@ public static boolean isEnumOrEnumConstant(AnnotatedTypeMirror annotatedTypeMirr } + public static boolean isEnumOrEnumConstant(TypeMirror type) { + if (!(type instanceof DeclaredType)) { + return false; + } + Element element = ((DeclaredType)type).asElement(); + return element != null + && (element.getKind() == ElementKind.ENUM_CONSTANT || element.getKind() == ElementKind.ENUM); + } + public static void applyImmutableToEnumAndEnumConstant(AnnotatedTypeMirror annotatedTypeMirror) { if (isEnumOrEnumConstant(annotatedTypeMirror)) { // I guess enum constant can't have explicit annotation, am I right? @@ -292,7 +319,7 @@ public static void applyConstant(AnnotatedTypeMirror type, AnnotationMirror am) // Might be null. It's normal. In typechecking side, we use addMissingAnnotations(). Only if // there is existing annotation in code, then here is non-null. Otherwise, VariableAnnotator // hasn't come into the picture yet, so no VarAnnot exists here, which is normal. - Slot shouldBeAppliedTo = slotManager.getVariableSlot(type); + Slot shouldBeAppliedTo = slotManager.getSlot(type); ConstantSlot constant = slotManager.createConstantSlot(am); if (shouldBeAppliedTo == null) { // Here, we are adding VarAnnot that represents @Immutable. There won't be solution for this ConstantSlot for this type, @@ -301,7 +328,7 @@ public static void applyConstant(AnnotatedTypeMirror type, AnnotationMirror am) // back to the original source code, BUT this ConstantSlot(representing @Immutable) will be used for constraint generation // that affects the solutions for other VariableSlots type.addAnnotation(slotManager.getAnnotation(constant));// Insert Constant VarAnnot that represents @Immutable - type.addAnnotation(am);// Insert real @Immutable. This should be removed if INF-FR only uses VarAnnot +// type.addAnnotation(am);// Insert real @Immutable. This should be removed if INF-FR only uses VarAnnot } else { constraintManager.addEqualityConstraint(shouldBeAppliedTo, constant); } @@ -309,13 +336,13 @@ public static void applyConstant(AnnotatedTypeMirror type, AnnotationMirror am) /**Check if a field is final or not.*/ public static boolean isFinalField(Element variableElement) { - assert variableElement instanceof VariableElement; + assert variableElement instanceof VariableElement; // FIXME consider rm return ElementUtils.isFinal(variableElement); } /**Check if a field is assignable or not.*/ public static boolean isAssignableField(Element variableElement, AnnotationProvider provider) { - if (!(variableElement instanceof VariableElement)) { + if (!(variableElement instanceof VariableElement)) { // FIXME consider rm return false; } boolean hasExplicitAssignableAnnotation = provider.getDeclAnnotation(variableElement, Assignable.class) != null; @@ -361,7 +388,7 @@ public static boolean isAssigningAssignableField(ExpressionTree node, Annotation public static boolean isEnclosedByAnonymousClass(Tree tree, AnnotatedTypeFactory atypeFactory) { TreePath path = atypeFactory.getPath(tree); if (path != null) { - ClassTree classTree = TreeUtils.enclosingClass(path); + ClassTree classTree = TreePathUtil.enclosingClass(path); return classTree != null && InferenceUtil.isAnonymousClass(classTree); } return false; @@ -373,7 +400,7 @@ public static AnnotatedDeclaredType getBoundOfEnclosingAnonymousClass(Tree tree, return null; } AnnotatedDeclaredType enclosingType = null; - Tree newclassTree = TreeUtils.enclosingOfKind(path, Tree.Kind.NEW_CLASS); + Tree newclassTree = TreePathUtil.enclosingOfKind(path, Tree.Kind.NEW_CLASS); if (newclassTree != null) { enclosingType = (AnnotatedDeclaredType) atypeFactory.getAnnotatedType(newclassTree); } @@ -388,10 +415,10 @@ public static AnnotationMirror createEquivalentVarAnnotOfRealQualifier(final Ann public static boolean inStaticScope(TreePath treePath) { boolean in = false; - if (TreeUtils.isTreeInStaticScope(treePath)) { + if (TreePathUtil.isTreeInStaticScope(treePath)) { in = true; // Exclude case in which enclosing class is static - ClassTree classTree = TreeUtils.enclosingClass(treePath); + ClassTree classTree = TreePathUtil.enclosingClass(treePath); if (classTree != null && classTree.getModifiers().getFlags().contains((Modifier.STATIC))) { in = false; } @@ -399,35 +426,44 @@ public static boolean inStaticScope(TreePath treePath) { return in; } - public static void dragAnnotationFromBoundToExtendsAndImplements(Tree node, - AnnotatedTypeMirror annotatedTypeMirror, - AnnotatedTypeFactory atypeFactory) { - boolean onExtendsOrImplements = false; - ClassTree classTree = null; - TreePath path = atypeFactory.getPath(node); - if (path != null) { - final TreePath parentPath = path.getParentPath(); - if (parentPath != null) { - final Tree parentNode = parentPath.getLeaf(); - if (TreeUtils.isClassTree(parentNode)) { - onExtendsOrImplements = true; - classTree = (ClassTree) parentNode; - } - } - } + public static boolean isSideEffectingUnaryTree(final UnaryTree tree) { + return sideEffectingUnaryOperators.contains(tree.getKind()); + } - if (onExtendsOrImplements) { - // Respect explicitly written annotation still. However, if the there is annotation here, but it's - // inheritted from type element bound, then we still flush them out, because they are not explicit - // usage. - if (annotatedTypeMirror.getExplicitAnnotations().isEmpty()) { - annotatedTypeMirror.replaceAnnotation( - getBoundTypeOfTypeDeclaration(classTree, atypeFactory).getAnnotationInHierarchy(READONLY)); - } - } + /** + * !! Experimental !! + *

+ * CF's isAnonymous cannot recognize some anonymous classes with annotation on new clause. + * This one hopefully will provide a workaround when the class tree is available. + *

+ * This will work if an anonymous class decl tree is always a child node of a {@code NewClassTree}. + * i.e. an anonymous class declaration is always inside a new clause. + * + * @param tree a class decl tree. + * @param atypeFactory used to get the path. Any factory should be ok. + * @return whether the class decl tree is of an anonymous class + */ + public static boolean isAnonymousClassTree(ClassTree tree, AnnotatedTypeFactory atypeFactory) { + TreePath path = atypeFactory.getPath(tree); + Tree parent = path.getParentPath().getLeaf(); + return parent instanceof NewClassTree && ((NewClassTree) parent).getClassBody() != null; } - public static boolean isSideEffectingUnaryTree(final UnaryTree tree) { - return sideEffectingUnaryOperators.contains(tree.getKind()); + /** + * !! Experimental !! + * Check whether the type is actually an array. + * @param type AnnotatedDeclaredType + * @param typeFactory any AnnotatedTypeFactory + * @return true if the type is array + */ + public static boolean isArrayType(AnnotatedDeclaredType type, AnnotatedTypeFactory typeFactory) { + Element ele = typeFactory.getProcessingEnv().getTypeUtils().asElement(type.getUnderlyingType()); + + // If it is a user-declared "Array" class without package, a class / source file should be there. + // Otherwise, it is the java inner type. + return ele instanceof Symbol.ClassSymbol + && ElementUtils.getQualifiedName(ele).equals("Array") + && ((Symbol.ClassSymbol) ele).classfile == null + && ((Symbol.ClassSymbol) ele).sourcefile == null; } } diff --git a/src/main/java/pico/common/ViewpointAdapterGettable.java b/src/main/java/pico/common/ViewpointAdapterGettable.java new file mode 100644 index 0000000..70ed93a --- /dev/null +++ b/src/main/java/pico/common/ViewpointAdapterGettable.java @@ -0,0 +1,5 @@ +package pico.common; + +public interface ViewpointAdapterGettable { + ExtendedViewpointAdapter getViewpointAdapter(); +} diff --git a/src/main/java/pico/inference/PICOInferenceAnnotatedTypeFactory.java b/src/main/java/pico/inference/PICOInferenceAnnotatedTypeFactory.java index 386a4de..fac8e3a 100644 --- a/src/main/java/pico/inference/PICOInferenceAnnotatedTypeFactory.java +++ b/src/main/java/pico/inference/PICOInferenceAnnotatedTypeFactory.java @@ -2,42 +2,58 @@ import checkers.inference.InferenceAnnotatedTypeFactory; import checkers.inference.InferenceChecker; +import checkers.inference.InferenceMain; +import checkers.inference.InferenceQualifierHierarchy; import checkers.inference.InferenceTreeAnnotator; import checkers.inference.InferrableChecker; import checkers.inference.SlotManager; import checkers.inference.VariableAnnotator; import checkers.inference.model.ConstraintManager; +import checkers.inference.model.Slot; +import checkers.inference.model.SourceVariableSlot; import checkers.inference.util.InferenceViewpointAdapter; import com.sun.source.tree.BinaryTree; import com.sun.source.tree.ClassTree; import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.NewArrayTree; +import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; import com.sun.source.tree.TypeCastTree; import com.sun.source.util.TreePath; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.*; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; -import org.checkerframework.framework.type.treeannotator.ImplicitsTreeAnnotator; +import org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator; import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator; import org.checkerframework.framework.type.treeannotator.PropagationTreeAnnotator; import org.checkerframework.framework.type.treeannotator.TreeAnnotator; import org.checkerframework.framework.type.typeannotator.ListTypeAnnotator; import org.checkerframework.framework.type.typeannotator.TypeAnnotator; -import org.checkerframework.javacutil.Pair; -import org.checkerframework.javacutil.TreeUtils; -import pico.typecheck.PICOAnnotatedTypeFactory.PICOImplicitsTypeAnnotator; -import pico.typecheck.PICOTypeUtil; +import org.checkerframework.javacutil.*; +import pico.common.ExtendedViewpointAdapter; +import pico.common.ViewpointAdapterGettable; +import pico.common.PICOTypeUtil; +import pico.typecheck.PICONoInitAnnotatedTypeFactory; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import java.lang.annotation.Annotation; import java.util.Collection; +import java.util.Collections; +import java.util.Set; -import static pico.typecheck.PICOAnnotationMirrorHolder.IMMUTABLE; -import static pico.typecheck.PICOAnnotationMirrorHolder.READONLY; +import static pico.typecheck.PICOAnnotationMirrorHolder.*; +import static pico.typecheck.PICOAnnotationMirrorHolder.RECEIVER_DEPENDANT_MUTABLE; /** * Propagates correct constraints on trees and types using TreeAnnotators and TypeAnnotators. @@ -47,7 +63,7 @@ * type on that type. This ensures that that VariableSlot doesn't enter solver and solver doesn't * give solution to the VariableSlot, and there won't be annotations inserted to implicit locations. */ -public class PICOInferenceAnnotatedTypeFactory extends InferenceAnnotatedTypeFactory { +public class PICOInferenceAnnotatedTypeFactory extends InferenceAnnotatedTypeFactory implements ViewpointAdapterGettable { public PICOInferenceAnnotatedTypeFactory(InferenceChecker inferenceChecker, boolean withCombineConstraints, BaseAnnotatedTypeFactory realTypeFactory, InferrableChecker realChecker, SlotManager slotManager, ConstraintManager constraintManager) { super(inferenceChecker, withCombineConstraints, realTypeFactory, realChecker, slotManager, constraintManager); // Always call postInit() at the end of ATF constructor! @@ -62,17 +78,17 @@ public PICOInferenceAnnotatedTypeFactory(InferenceChecker inferenceChecker, bool // be inserted results anyway). @Override public TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator(new ImplicitsTreeAnnotator(this), + return new ListTreeAnnotator(new LiteralTreeAnnotator(this), new PICOInferencePropagationTreeAnnotator(this), new InferenceTreeAnnotator(this, realChecker, realTypeFactory, variableAnnotator, slotManager)); } @Override protected TypeAnnotator createTypeAnnotator() { - // Reuse PICOImplicitsTypeAnnotator even in inference mode. Because the type annotator's implementation + // Reuse PICODefaultForTypeAnnotator even in inference mode. Because the type annotator's implementation // are the same. The only drawback is that naming is not good(doesn't include "Inference"), thus may be // hard to debug - return new ListTypeAnnotator(super.createTypeAnnotator(), new PICOImplicitsTypeAnnotator(this)); + return new ListTypeAnnotator(super.createTypeAnnotator(), new PICONoInitAnnotatedTypeFactory.PICODefaultForTypeAnnotator(this)); } @Override @@ -102,16 +118,16 @@ VariableAnnotator getVariableAnnotator() { */ public AnnotatedDeclaredType getSelfType(Tree tree) { TreePath path = getPath(tree); - ClassTree enclosingClass = TreeUtils.enclosingClass(path); + ClassTree enclosingClass = TreePathUtil.enclosingClass(path); if (enclosingClass == null) { // I hope this only happens when tree is a fake tree that // we created, e.g. when desugaring enhanced-for-loops. - enclosingClass = getCurrentClassTree(tree); + enclosingClass = TreePathUtil.enclosingClass(getPath(tree)); } // "type" is right now VarAnnot inserted to the bound of "enclosingClass" AnnotatedDeclaredType type = getAnnotatedType(enclosingClass); - MethodTree enclosingMethod = TreeUtils.enclosingMethod(path); + MethodTree enclosingMethod = TreePathUtil.enclosingMethod(path); if (enclosingClass.getSimpleName().length() != 0 && enclosingMethod != null) { AnnotatedTypeMirror.AnnotatedDeclaredType methodReceiver; if (TreeUtils.isConstructor(enclosingMethod)) { @@ -129,91 +145,91 @@ public AnnotatedDeclaredType getSelfType(Tree tree) { return type; } - class PICOInferencePropagationTreeAnnotator extends PropagationTreeAnnotator { - public PICOInferencePropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - } + @Override + public ExtendedViewpointAdapter getViewpointAdapter() { + return (ExtendedViewpointAdapter) viewpointAdapter; + } - @Override - public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { - // Below is copied from super - assert type.getKind() == TypeKind.ARRAY - : "PropagationTreeAnnotator.visitNewArray: should be an array type"; + @Override + protected AnnotationMirrorSet getDefaultTypeDeclarationBounds() { + return new AnnotationMirrorSet(MUTABLE); + } - AnnotatedTypeMirror componentType = ((AnnotatedTypeMirror.AnnotatedArrayType) type).getComponentType(); + @Override + protected QualifierHierarchy createQualifierHierarchy() { + return new PICOInferenceQualifierHierarchy(getSupportedTypeQualifiers(), elements, this); + } - Collection prev = null; - if (tree.getInitializers() != null && tree.getInitializers().size() != 0) { - // We have initializers, either with or without an array type. + @Override + public AnnotationMirrorSet getTypeDeclarationBounds(TypeMirror type) { + // Get the VarAnnot on the class decl + // This factory is only invoked on inference, so no need to provide concrete anno for type-check + if (type instanceof PrimitiveType) { + return new AnnotationMirrorSet(slotManager.getAnnotation(slotManager.getSlot(IMMUTABLE))); + } + if (type.getKind() == TypeKind.ARRAY) { + // WORKAROUND: return RDM will cause issues with new clauses + return new AnnotationMirrorSet(slotManager.getAnnotation(slotManager.getSlot(READONLY))); + } - for (ExpressionTree init : tree.getInitializers()) { - AnnotatedTypeMirror initType = atypeFactory.getAnnotatedType(init); - // initType might be a typeVariable, so use effectiveAnnotations. - Collection annos = initType.getEffectiveAnnotations(); + if (PICOTypeUtil.isEnumOrEnumConstant(type)) { + return new AnnotationMirrorSet(slotManager.getAnnotation(slotManager.getSlot(IMMUTABLE))); + } +// AnnotatedTypeMirror atm = toAnnotatedType(type, true); +// if (atm instanceof AnnotatedDeclaredType && ((AnnotatedDeclaredType) atm).getTypeArguments().size() > 0) { +// // Workaround for types with type arguments. +// // annotateElementFromStore can only get the original type with type param. +// // But this method only needs the top annotation. +// // Note: class bound cache is a private field of annotator. +// +// atm = PICOTypeUtil.getBoundTypeOfTypeDeclaration(TypesUtils.getTypeElement(type), this); +// } else { +// getVariableAnnotator().annotateElementFromStore(getProcessingEnv().getTypeUtils().asElement(type), atm); +// } +// +// if (atm.hasAnnotation(VarAnnot.class)) { +// return atm.getAnnotations(); +// } + AnnotationMirror am = ((PICOVariableAnnotator) variableAnnotator).getClassDeclAnno(getProcessingEnv().getTypeUtils().asElement(type)); + if (am != null) { + return new AnnotationMirrorSet(am); + } - prev = (prev == null) ? annos : atypeFactory.getQualifierHierarchy().leastUpperBounds(prev, annos); - } - } else { - prev = componentType.getAnnotations(); - } + // if reaching this point and still no anno: not annotated from slot manager + // maybe should read from stub file. + // if implicit: return immutable slot - assert prev != null - : "PropagationTreeAnnotator.visitNewArray: violated assumption about qualifiers"; - - Pair context = - atypeFactory.getVisitorState().getAssignmentContext(); - Collection post; - - if (context != null - && context.second != null - && context.second instanceof AnnotatedTypeMirror.AnnotatedArrayType) { - AnnotatedTypeMirror contextComponentType = - ((AnnotatedTypeMirror.AnnotatedArrayType) context.second).getComponentType(); - // Only compare the qualifiers that existed in the array type - // Defaulting wasn't performed yet, so prev might have fewer qualifiers than - // contextComponentType, which would cause a failure. - // TODO: better solution? - boolean prevIsSubtype = true; - for (AnnotationMirror am : prev) { - // Workaround for mutable Object array component type problem. - if (componentType instanceof AnnotatedDeclaredType) { - if (((AnnotatedDeclaredType) componentType).getUnderlyingType().asElement().getSimpleName().contentEquals("Object")) { - continue; - } - } - - if (contextComponentType.isAnnotatedInHierarchy(am) - && !atypeFactory.getQualifierHierarchy().isSubtype( - am, contextComponentType.getAnnotationInHierarchy(am))) { - prevIsSubtype = false; - } - } + // implicit + if (PICOTypeUtil.isImplicitlyImmutableType(toAnnotatedType(type, false))) { + return new AnnotationMirrorSet(slotManager.getAnnotation(slotManager.getSlot(IMMUTABLE))); + } - // TODO: checking conformance of component kinds is a basic sanity check - // It fails for array initializer expressions. Those should be handled nicer. - if (contextComponentType.getKind() == componentType.getKind() - && (prev.isEmpty() - || (!contextComponentType.getAnnotations().isEmpty() - && prevIsSubtype))) { - post = contextComponentType.getAnnotations(); - } else { - // The type of the array initializers is incompatible with the - // context type! - // Somebody else will complain. - post = prev; - } - } else { - // No context is available - simply use what we have. - post = prev; + // get stub & default from element. + AnnotatedTypeMirror atm = stubTypes.getAnnotatedTypeMirror(getProcessingEnv().getTypeUtils().asElement(type)); + if (atm != null) { + Set set = atm.getAnnotations(); + if (!set.isEmpty()) { + return new AnnotationMirrorSet( + slotManager.getAnnotation(slotManager.getSlot(set.iterator().next()))); } + } + return new AnnotationMirrorSet( + slotManager.getAnnotation(slotManager.getSlot(super.getTypeDeclarationBounds(type).iterator().next()))); + } - // Below line is the only difference from super implementation - applyImmutableIfImplicitlyImmutable(componentType); - // Above line is the only difference from super implementation - componentType.addMissingAnnotations(post); + class PICOInferencePropagationTreeAnnotator extends PropagationTreeAnnotator { + public PICOInferencePropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } - return null; - // Above is copied from super + @Override + public Void visitMethodInvocation(MethodInvocationTree methodInvocationTree, AnnotatedTypeMirror mirror) { + // This is a workaround for implicit types. + // Implicit types in lib method get defaulted to mutable. + // Implicit immutable classes cannot be annotated in stub files, annotations were ignored. + // Find the cause, annotate implicit immutable classes in stub, and remove this method. + applyImmutableIfImplicitlyImmutable(mirror); + return super.visitMethodInvocation(methodInvocationTree, mirror); } /**Add immutable to the result type of a binary operation if the result type is implicitly immutable*/ @@ -227,7 +243,7 @@ public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) { @Override public Void visitTypeCast(TypeCastTree node, AnnotatedTypeMirror type) { applyImmutableIfImplicitlyImmutable(type);// Must run before calling super method to respect existing annotation - if (type.isAnnotatedInHierarchy(READONLY)) { + if (type.hasAnnotationInHierarchy(READONLY)) { // VarAnnot is guarenteed to not exist on type, because PropagationTreeAnnotator has the highest previledge // So VarAnnot hasn't been inserted to cast type yet. PICOTypeUtil.applyConstant(type, type.getAnnotationInHierarchy(READONLY)); @@ -235,7 +251,7 @@ public Void visitTypeCast(TypeCastTree node, AnnotatedTypeMirror type) { return super.visitTypeCast(node, type); } - /**Because TreeAnnotator runs before ImplicitsTypeAnnotator, implicitly immutable types are not guaranteed + /**Because TreeAnnotator runs before DefaultForTypeAnnotator, implicitly immutable types are not guaranteed to always have immutable annotation. If this happens, we manually add immutable to type. */ private void applyImmutableIfImplicitlyImmutable(AnnotatedTypeMirror type) { if (PICOTypeUtil.isImplicitlyImmutableType(type)) { @@ -243,4 +259,39 @@ private void applyImmutableIfImplicitlyImmutable(AnnotatedTypeMirror type) { } } } + + protected class PICOInferenceQualifierHierarchy extends InferenceQualifierHierarchy { + + public PICOInferenceQualifierHierarchy( + Set> qualifierClasses, + Elements elements, + GenericAnnotatedTypeFactory atypeFactory) { + super(qualifierClasses, elements, atypeFactory); + } + + @Override + public boolean isSubtypeQualifiers(final AnnotationMirror subtype, final AnnotationMirror supertype) { + + if (subtype == null || supertype == null || !isVarAnnot(subtype) || !isVarAnnot(supertype)) { + if (InferenceMain.isHackMode()) { + return true; + } else { + throw new BugInCF("Unexpected arguments for isSubtype: subtype=%s, supertype=%s", subtype, supertype); + } + } + + if (supertype.getElementValues().isEmpty()) { + return true; + } + + final Slot subSlot = slotManager.getSlot(subtype); + final Slot superSlot = slotManager.getSlot(supertype); + + if (superSlot.getId() == 6) { // don't generate the constraint when it's RDM. + return true; + } + + return constraintManager.addSubtypeConstraintNoErrorMsg(subSlot, superSlot); + } + } } diff --git a/src/main/java/pico/inference/PICOInferenceChecker.java b/src/main/java/pico/inference/PICOInferenceChecker.java index d211fab..b6d53fc 100644 --- a/src/main/java/pico/inference/PICOInferenceChecker.java +++ b/src/main/java/pico/inference/PICOInferenceChecker.java @@ -1,11 +1,6 @@ package pico.inference; -import checkers.inference.BaseInferrableChecker; -import checkers.inference.InferenceAnnotatedTypeFactory; -import checkers.inference.InferenceChecker; -import checkers.inference.InferenceVisitor; -import checkers.inference.InferrableChecker; -import checkers.inference.SlotManager; +import checkers.inference.*; import checkers.inference.model.ConstraintManager; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.framework.source.SupportedOptions; @@ -14,7 +9,7 @@ /** * Main entry class */ -@SupportedOptions({"upcast", "anycast", "comparablecast", "optimalSolution"}) +@SupportedOptions({"upcast", "anycast", "comparablecast", "optimalSolution", "useOptimisticUncheckedDefaults"}) public class PICOInferenceChecker extends BaseInferrableChecker { @Override @@ -24,8 +19,8 @@ public void initChecker() { } @Override - public BaseAnnotatedTypeFactory createRealTypeFactory() { - return new PICOInferenceRealTypeFactory(this, true); + public BaseInferenceRealTypeFactory createRealTypeFactory(boolean infer) { + return new PICOInferenceRealTypeFactory(this, infer); } @Override diff --git a/src/main/java/pico/inference/PICOInferenceExtendedViewpointAdapter.java b/src/main/java/pico/inference/PICOInferenceExtendedViewpointAdapter.java new file mode 100644 index 0000000..3df3cd5 --- /dev/null +++ b/src/main/java/pico/inference/PICOInferenceExtendedViewpointAdapter.java @@ -0,0 +1,37 @@ +package pico.inference; + +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import pico.common.ExtendedViewpointAdapter; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; + +public class PICOInferenceExtendedViewpointAdapter extends PICOInferenceViewpointAdapter implements ExtendedViewpointAdapter { + + public PICOInferenceExtendedViewpointAdapter(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } + + /** + * (Extended behaviour) viewpoint adapt super clause to its class declaration. Class declaration acts like receiver. + * @param classType class declaration itself + * @param superEle element of extends / implements clause + * @param superType type of extends / implements clause + */ + public void viewpointAdaptSuperClause(AnnotatedTypeMirror.AnnotatedDeclaredType classType, Element superEle, AnnotatedTypeMirror.AnnotatedDeclaredType superType) { +// AnnotatedTypeMirror adapted = combineTypeWithType(classType, superType); + AnnotationMirror adapted = combineAnnotationWithAnnotation(extractAnnotationMirror(classType), extractAnnotationMirror(superType)); + superType.replaceAnnotation(adapted); + + } + + public AnnotatedTypeMirror rawCombineAnnotationWithType(AnnotationMirror anno, AnnotatedTypeMirror type) { + return combineAnnotationWithType(anno, type); + } + + @Override + public AnnotationMirror rawCombineAnnotationWithAnnotation(AnnotationMirror anno, AnnotationMirror type) { + return combineAnnotationWithAnnotation(anno, type); + } +} diff --git a/src/main/java/pico/inference/PICOInferenceRealTypeFactory.java b/src/main/java/pico/inference/PICOInferenceRealTypeFactory.java index 1157e2d..dd12c53 100644 --- a/src/main/java/pico/inference/PICOInferenceRealTypeFactory.java +++ b/src/main/java/pico/inference/PICOInferenceRealTypeFactory.java @@ -1,48 +1,53 @@ package pico.inference; -import static pico.typecheck.PICOAnnotationMirrorHolder.IMMUTABLE; -import static pico.typecheck.PICOAnnotationMirrorHolder.MUTABLE; -import static pico.typecheck.PICOAnnotationMirrorHolder.READONLY; - import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import checkers.inference.BaseInferenceRealTypeFactory; +import com.sun.source.tree.NewClassTree; +import com.sun.tools.javac.tree.JCTree; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.qual.RelevantJavaTypes; +import org.checkerframework.framework.qual.TypeUseLocation; import org.checkerframework.framework.type.AbstractViewpointAdapter; import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.treeannotator.ImplicitsTreeAnnotator; import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator; +import org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator; import org.checkerframework.framework.type.treeannotator.TreeAnnotator; -import org.checkerframework.framework.type.typeannotator.IrrelevantTypeAnnotator; -import org.checkerframework.framework.type.typeannotator.ListTypeAnnotator; -import org.checkerframework.framework.type.typeannotator.PropagationTypeAnnotator; -import org.checkerframework.framework.type.typeannotator.TypeAnnotator; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.framework.type.typeannotator.*; +import org.checkerframework.framework.util.defaults.QualifierDefaults; +import org.checkerframework.javacutil.*; import com.sun.source.tree.Tree; -import pico.typecheck.PICOAnnotatedTypeFactory.PICOImplicitsTypeAnnotator; -import pico.typecheck.PICOAnnotatedTypeFactory.PICOPropagationTreeAnnotator; -import pico.typecheck.PICOAnnotatedTypeFactory.PICOTreeAnnotator; -import pico.typecheck.PICOAnnotatedTypeFactory.PICOTypeAnnotator; -import pico.typecheck.PICOTypeUtil; +import pico.common.ExtendedViewpointAdapter; +import pico.common.ViewpointAdapterGettable; +import pico.common.PICOTypeUtil; +import pico.typecheck.PICONoInitAnnotatedTypeFactory; import pico.typecheck.PICOViewpointAdapter; import qual.Bottom; import qual.Immutable; import qual.Mutable; +import qual.PolyMutable; import qual.Readonly; import qual.ReceiverDependantMutable; +import static pico.typecheck.PICOAnnotationMirrorHolder.*; + /** * PICOInferenceRealTypeFactory exists because: 1)PICOAnnotatedTypeFactory is not subtype of * BaseAnnotatedTypeFactory. 2) In inference side, PICO only supports reduced version of @@ -51,11 +56,19 @@ * to InitializationAnnotatedTypeFactory as if there is only one mutability qualifier hierarchy. * This class has lots of copied code from PICOAnnotatedTypeFactory. The two should be in sync. */ -public class PICOInferenceRealTypeFactory extends BaseAnnotatedTypeFactory { +public class PICOInferenceRealTypeFactory extends BaseInferenceRealTypeFactory implements ViewpointAdapterGettable { + + private static final List IMMUTABLE_ALIASES = Arrays.asList( + "com.google.errorprone.annotations.Immutable", + "edu.cmu.cs.glacier.qual.Immutable"); + + public PICOInferenceRealTypeFactory(BaseTypeChecker checker, boolean isInfer) { + super(checker, isInfer); +// if (READONLY != null) { +// addAliasedTypeAnnotation(org.jmlspecs.annotation.Readonly.class, READONLY); +// } +// IMMUTABLE_ALIASES.forEach(anno -> addAliasedAnnotation(anno, IMMUTABLE)); - public PICOInferenceRealTypeFactory(BaseTypeChecker checker, boolean useFlow) { - super(checker, useFlow); - addAliasedAnnotation(org.jmlspecs.annotation.Readonly.class, READONLY); postInit(); } @@ -64,6 +77,7 @@ public PICOInferenceRealTypeFactory(BaseTypeChecker checker, boolean useFlow) { protected Set> createSupportedTypeQualifiers() { return new LinkedHashSet>( Arrays.asList( + PolyMutable.class, Readonly.class, Mutable.class, ReceiverDependantMutable.class, @@ -81,9 +95,10 @@ protected AbstractViewpointAdapter createViewpointAdapter() { @Override protected TreeAnnotator createTreeAnnotator() { return new ListTreeAnnotator( - new PICOPropagationTreeAnnotator(this), - new ImplicitsTreeAnnotator(this), - new PICOTreeAnnotator(this)); + new PICONoInitAnnotatedTypeFactory.PICOPropagationTreeAnnotator(this), + new LiteralTreeAnnotator(this), + new PICONoInitAnnotatedTypeFactory.PICOSuperClauseAnnotator(this), + new PICONoInitAnnotatedTypeFactory.PICOTreeAnnotator(this)); } // TODO Refactor super class to remove this duplicate code @@ -98,20 +113,58 @@ protected TypeAnnotator createTypeAnnotator() { // Must be first in order to annotated all irrelevant types that are not explicilty // annotated. typeAnnotators.add( - new IrrelevantTypeAnnotator( - this, getQualifierHierarchy().getTopAnnotations(), classes)); + new IrrelevantTypeAnnotator(this)); } typeAnnotators.add(new PropagationTypeAnnotator(this)); /*Copied code ends*/ // Adding order is important here. Because internally type annotators are using addMissingAnnotations() // method, so if one annotator already applied the annotations, the others won't apply twice at the // same location - typeAnnotators.add(new PICOTypeAnnotator(this)); - typeAnnotators.add(new PICOImplicitsTypeAnnotator(this)); + typeAnnotators.add(new PICONoInitAnnotatedTypeFactory.PICOTypeAnnotator(this)); + typeAnnotators.add(new PICONoInitAnnotatedTypeFactory.PICODefaultForTypeAnnotator(this)); + typeAnnotators.add(new PICONoInitAnnotatedTypeFactory.PICOEnumDefaultAnnotator(this)); return new ListTypeAnnotator(typeAnnotators); } - /** TODO If the dataflow refines the type as bottom, should we allow such a refinement? If we allow it, + @Override + protected QualifierDefaults createQualifierDefaults() { + QualifierDefaults defaults = super.createQualifierDefaults(); + Elements elements = checker.getElementUtils(); + + // The optimistic flag only change the rules of unchecked defaults. + // To enable optimistic default, the user should both enable conservative for bytecode ONLY, + // and pass the optimistic flag to override the default of the conservative. + // i.e. -AuseConservativeDefaultsForUncheckedCode=bytecode (or -AuseDefaultsForUncheckedCode=bytecode) + // -AuseOptimisticUncheckedDefaults + if (checker.hasOption("useOptimisticUncheckedDefaults")) { + defaults.addUncheckedCodeDefaults(AnnotationBuilder.fromClass(elements, Bottom.class), new TypeUseLocation[]{ + TypeUseLocation.LOWER_BOUND, TypeUseLocation.RETURN, TypeUseLocation.FIELD + }); + defaults.addUncheckedCodeDefaults(AnnotationBuilder.fromClass(elements, Readonly.class), new TypeUseLocation[]{ + TypeUseLocation.UPPER_BOUND, TypeUseLocation.PARAMETER + }); + } + + return defaults; + + } + + @Override + public ParameterizedExecutableType constructorFromUse(NewClassTree tree) { + boolean hasExplicitAnnos = false; + if (!getExplicitNewClassAnnos(tree).isEmpty()) { + hasExplicitAnnos = true; + } + ParameterizedExecutableType mType = super.constructorFromUse(tree); + AnnotatedTypeMirror.AnnotatedExecutableType method = mType.executableType; + if (!hasExplicitAnnos && method.getReturnType().hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { + method.getReturnType().replaceAnnotation(MUTABLE); + } + return mType; + } + + + /* TODO If the dataflow refines the type as bottom, should we allow such a refinement? If we allow it, PICOValidator will give an error if it begins to enforce @Bottom is not used*/ /* @Override protected void applyInferredAnnotations(AnnotatedTypeMirror type, PICOValue as) { @@ -125,60 +178,106 @@ public boolean getShouldDefaultTypeVarLocals() { return false; } - // Copied from PICOAnnotatedTypeFactory - @Override - protected void annotateInheritedFromClass(AnnotatedTypeMirror type, Set fromClass) { - // If interitted from class element is @Mutable or @Immutable, then apply this annotation to the usage type - if (fromClass.contains(MUTABLE) || fromClass.contains(IMMUTABLE)) { - super.annotateInheritedFromClass(type, fromClass); - return; - } - // If interitted from class element is @ReceiverDependantMutable, then don't apply and wait for @Mutable - // (default qualifier in hierarchy to be applied to the usage type). This is to avoid having @ReceiverDependantMutable - // on type usages as a default behaviour. By default, @Mutable is better used as the type for usages that - // don't have explicit annotation. - return;// Don't add annotations from class element - } - @Override public void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) { PICOTypeUtil.addDefaultForField(this, type, elt); PICOTypeUtil.defaultConstructorReturnToClassBound(this, elt, type); - PICOTypeUtil.applyImmutableToEnumAndEnumConstant(type); +// PICOTypeUtil.applyImmutableToEnumAndEnumConstant(type); super.addComputedTypeAnnotations(elt, type); } - /**This method gets lhs WITH flow sensitive refinement*/ - // TODO This method is completely copied from PICOAnnotatedTypeFactory +// /**This method gets lhs WITH flow sensitive refinement*/ +// // TODO This method is completely copied from PICOAnnotatedTypeFactory +// @Override +// public AnnotatedTypeMirror getAnnotatedTypeLhs(Tree lhsTree) { +// AnnotatedTypeMirror result = null; +// boolean oldShouldCache = shouldCache; +// // Don't cache the result because getAnnotatedType(lhsTree) could +// // be called from elsewhere and would expect flow-sensitive type refinements. +// shouldCache = false; +// switch (lhsTree.getKind()) { +// case VARIABLE: +// case IDENTIFIER: +// case MEMBER_SELECT: +// case ARRAY_ACCESS: +// result = getAnnotatedType(lhsTree); +// break; +// default: +// if (TreeUtils.isTypeTree(lhsTree)) { +// // lhsTree is a type tree at the pseudo assignment of a returned expression to declared return type. +// result = getAnnotatedType(lhsTree); +// } else { +// throw new BugInCF( +// "GenericAnnotatedTypeFactory: Unexpected tree passed to getAnnotatedTypeLhs. " +// + "lhsTree: " +// + lhsTree +// + " Tree.Kind: " +// + lhsTree.getKind()); +// } +// } +// shouldCache = oldShouldCache; +// +// return result; +// } + @Override - public AnnotatedTypeMirror getAnnotatedTypeLhs(Tree lhsTree) { - AnnotatedTypeMirror result = null; - boolean oldShouldCache = shouldCache; - // Don't cache the result because getAnnotatedType(lhsTree) could - // be called from elsewhere and would expect flow-sensitive type refinements. - shouldCache = false; - switch (lhsTree.getKind()) { - case VARIABLE: - case IDENTIFIER: - case MEMBER_SELECT: - case ARRAY_ACCESS: - result = getAnnotatedType(lhsTree); - break; - default: - if (TreeUtils.isTypeTree(lhsTree)) { - // lhsTree is a type tree at the pseudo assignment of a returned expression to declared return type. - result = getAnnotatedType(lhsTree); - } else { - throw new BugInCF( - "GenericAnnotatedTypeFactory: Unexpected tree passed to getAnnotatedTypeLhs. " - + "lhsTree: " - + lhsTree - + " Tree.Kind: " - + lhsTree.getKind()); - } + protected DefaultQualifierForUseTypeAnnotator createDefaultForUseTypeAnnotator() { + return new PICONoInitAnnotatedTypeFactory.PICOQualifierForUseTypeAnnotator(this); + } + + @Override + public AnnotatedTypeMirror getTypeOfExtendsImplements(Tree clause) { + // add default anno from class main qual, if no qual present + AnnotatedTypeMirror enclosing = getAnnotatedType(TreePathUtil.enclosingClass(getPath(clause))); + + // workaround for anonymous class. + // TypesUtils::isAnonymous won't work when annotation presents on new class tree! + // by reaching this line TypesUtils::isAnonymous is already not working: it shouldn't check anonymous class! + if(getPath(clause).getParentPath().getLeaf() instanceof JCTree.JCNewClass) { + enclosing = getAnnotatedType(getPath(clause).getParentPath().getLeaf()); + + } + AnnotationMirror mainBound = enclosing.getAnnotationInHierarchy(READONLY); + AnnotatedTypeMirror fromTypeTree = this.getAnnotatedTypeFromTypeTree(clause); + if (!fromTypeTree.hasAnnotationInHierarchy(READONLY)) { + fromTypeTree.addAnnotation(mainBound); } - shouldCache = oldShouldCache; - return result; + // for FBC quals +// Set bound = this.getTypeDeclarationBounds(fromTypeTree.getUnderlyingType()); +// fromTypeTree.addMissingAnnotations(bound); + return fromTypeTree; + } + + public ExtendedViewpointAdapter getViewpointAdapter() { + return (ExtendedViewpointAdapter) viewpointAdapter; + } + + @Override + protected AnnotationMirrorSet getDefaultTypeDeclarationBounds() { + return new AnnotationMirrorSet(MUTABLE); + } + + @Override + public AnnotationMirrorSet getTypeDeclarationBounds(TypeMirror type) { + // TODO too awkward. maybe overload isImplicitlyImmutableType + if (PICOTypeUtil.isImplicitlyImmutableType(toAnnotatedType(type, false))) { + return new AnnotationMirrorSet(IMMUTABLE); + } + if (type.getKind() == TypeKind.ARRAY) { + return new AnnotationMirrorSet(READONLY); // if decided to use vpa for array, return RDM. + } + + // IMMUTABLE for enum w/o decl anno + if (type instanceof DeclaredType) { + Element ele = ((DeclaredType) type).asElement(); + if (ele.getKind() == ElementKind.ENUM) { + if (!AnnotationUtils.containsSameByName(getDeclAnnotations(ele), MUTABLE) && + !AnnotationUtils.containsSameByName(getDeclAnnotations(ele), RECEIVER_DEPENDANT_MUTABLE)) { // no decl anno + return new AnnotationMirrorSet(IMMUTABLE); + } + } + } + return super.getTypeDeclarationBounds(type); } } diff --git a/src/main/java/pico/inference/PICOInferenceValidator.java b/src/main/java/pico/inference/PICOInferenceValidator.java index 7f6bc09..29f9d28 100644 --- a/src/main/java/pico/inference/PICOInferenceValidator.java +++ b/src/main/java/pico/inference/PICOInferenceValidator.java @@ -2,26 +2,29 @@ import checkers.inference.InferenceValidator; import checkers.inference.InferenceVisitor; +import com.sun.source.tree.ParameterizedTypeTree; import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import org.checkerframework.common.basetype.BaseTypeChecker; -import org.checkerframework.framework.source.Result; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TreeUtils; -import pico.typecheck.PICOTypeUtil; +import pico.common.PICOTypeUtil; +import pico.typecheck.PICONoInitAnnotatedTypeFactory; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ElementKind; import javax.lang.model.element.VariableElement; -import static pico.typecheck.PICOAnnotationMirrorHolder.BOTTOM; -import static pico.typecheck.PICOAnnotationMirrorHolder.IMMUTABLE; -import static pico.typecheck.PICOAnnotationMirrorHolder.MUTABLE; -import static pico.typecheck.PICOAnnotationMirrorHolder.READONLY; -import static pico.typecheck.PICOAnnotationMirrorHolder.RECEIVER_DEPENDANT_MUTABLE; +import java.util.Objects; +import java.util.Set; + +import static pico.typecheck.PICOAnnotationMirrorHolder.*; /** * Generates constraints based on PICO constraint-based well-formedness rules in infer mode. @@ -37,9 +40,34 @@ public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) { checkStaticReceiverDependantMutableError(type, tree); checkImplicitlyImmutableTypeError(type, tree); checkOnlyOneAssignabilityModifierOnField(tree); +// AnnotatedDeclaredType defaultType = +// (AnnotatedDeclaredType) atypeFactory.getAnnotatedType(type.getUnderlyingType().asElement()); +// // TODO for defaulted super clause: should top anno be checked? (see shouldCheckTopLevelDeclaredOrPrimitiveType()) +// if (defaultType.getAnnotationInHierarchy(READONLY) == null && !PICOTypeUtil.isEnumOrEnumConstant(defaultType)) { +// defaultType = defaultType.deepCopy(); +// defaultType.replaceAnnotation(MUTABLE); +// } +// +// if (!visitor.isValidUse(defaultType, type, tree)) { +// reportInvalidAnnotationsOnUse(type, tree); +// } + // main != READONLY -> main |> bound <: main + ((PICOInferenceVisitor) visitor).mainCannotInferTo(type, POLY_MUTABLE, "cannot.infer.poly", tree); return super.visitDeclared(type, tree); } + @Override + protected Void visitParameterizedType(AnnotatedDeclaredType type, ParameterizedTypeTree tree) { + if (infer) { + for (AnnotatedTypeMirror arg : type.getTypeArguments()) { + ((PICOInferenceVisitor) visitor).mainIsNoneOf(arg, + new AnnotationMirror[]{POLY_MUTABLE, BOTTOM, RECEIVER_DEPENDANT_MUTABLE}, + "type.invalid.annotations.on.use", tree); + } + } + return super.visitParameterizedType(type, tree); + } + @Override public Void visitArray(AnnotatedArrayType type, Tree tree) { checkStaticReceiverDependantMutableError(type, tree); @@ -52,8 +80,44 @@ public Void visitPrimitive(AnnotatedPrimitiveType type, Tree tree) { return super.visitPrimitive(type, tree); } + @Override + protected boolean shouldCheckTopLevelDeclaredOrPrimitiveType(AnnotatedTypeMirror type, Tree tree) { + if (infer) { + if (TreeUtils.isLocalVariable(tree)) { + return true; + } + } else { + // check top annotations in extends/implements clauses + if ((tree.getKind() == Tree.Kind.IDENTIFIER || tree.getKind() == Tree.Kind.PARAMETERIZED_TYPE) && + PICONoInitAnnotatedTypeFactory.PICOSuperClauseAnnotator.isSuperClause(atypeFactory.getPath(tree))) { + return true; + } + // allow RDM on mutable fields with enclosing class bounded with mutable + if (tree instanceof VariableTree) { + VariableElement element = TreeUtils.elementFromDeclaration((VariableTree)tree); + if (element.getKind() == ElementKind.FIELD && ElementUtils.enclosingTypeElement(element) != null) { + Set enclosingBound = + atypeFactory.getTypeDeclarationBounds( + Objects.requireNonNull(ElementUtils.enclosingTypeElement(element)).asType()); + + Set declaredBound = + atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType()); + + if(AnnotationUtils.containsSameByName(declaredBound, MUTABLE) + && type.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE) + && AnnotationUtils.containsSameByName(enclosingBound, MUTABLE)) { + return false; + } + } + } + } + return super.shouldCheckTopLevelDeclaredOrPrimitiveType(type, tree); + } + private void checkStaticReceiverDependantMutableError(AnnotatedTypeMirror type, Tree tree) { - if (PICOTypeUtil.inStaticScope(visitor.getCurrentPath())) { + // Static inner class is considered within the static scope. + // Added condition to ensure not class decl. + if (PICOTypeUtil.inStaticScope(visitor.getCurrentPath()) && !type.isDeclaration()) { if (infer) { ((PICOInferenceVisitor)visitor).mainIsNot(type, RECEIVER_DEPENDANT_MUTABLE, "static.receiverdependantmutable.forbidden", tree); } else { @@ -61,6 +125,7 @@ private void checkStaticReceiverDependantMutableError(AnnotatedTypeMirror type, reportValidityResult("static.receiverdependantmutable.forbidden", type, tree); } } + // TODO set isValid or move to visitor } } @@ -73,7 +138,8 @@ private void checkImplicitlyImmutableTypeError(AnnotatedTypeMirror type, Tree tr new AnnotationMirror[]{READONLY, MUTABLE, RECEIVER_DEPENDANT_MUTABLE, BOTTOM}, "type.invalid.annotations.on.use", tree); } else { - if (!type.hasAnnotation(IMMUTABLE)) { + // FIXME workaround for typecheck. How should inference handle BOTTOM? + if (!type.hasAnnotation(IMMUTABLE) && !type.hasAnnotation(BOTTOM)) { reportInvalidAnnotationsOnUse(type, tree); } } @@ -97,7 +163,20 @@ private void checkOnlyOneAssignabilityModifierOnField(Tree tree) { } private void reportFieldMultipleAssignabilityModifiersError(VariableElement field) { - checker.report(Result.failure("one.assignability.invalid", field), field); + checker.reportError(field, "one.assignability.invalid", field); isValid = false; } + + private void checkLocalVariableDefaults(AnnotatedDeclaredType type, Tree tree) { + Set bounds = + atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType()); + + AnnotatedDeclaredType elemType = type.deepCopy(); + elemType.clearAnnotations(); + elemType.addAnnotations(bounds); + + if (!visitor.isValidUse(elemType, type, tree)) { + reportInvalidAnnotationsOnUse(type, tree); + } + } } diff --git a/src/main/java/pico/inference/PICOInferenceViewpointAdapter.java b/src/main/java/pico/inference/PICOInferenceViewpointAdapter.java index 9386d88..38a7fda 100644 --- a/src/main/java/pico/inference/PICOInferenceViewpointAdapter.java +++ b/src/main/java/pico/inference/PICOInferenceViewpointAdapter.java @@ -1,15 +1,18 @@ package pico.inference; +import checkers.inference.InferenceMain; import checkers.inference.util.InferenceViewpointAdapter; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; -import pico.typecheck.PICOTypeUtil; +import pico.common.ExtendedViewpointAdapter; +import pico.common.PICOTypeUtil; +import static pico.typecheck.PICOAnnotationMirrorHolder.READONLY; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.type.TypeKind; -public class PICOInferenceViewpointAdapter extends InferenceViewpointAdapter{ +public class PICOInferenceViewpointAdapter extends InferenceViewpointAdapter implements ExtendedViewpointAdapter { public PICOInferenceViewpointAdapter(AnnotatedTypeFactory atypeFactory) { super(atypeFactory); @@ -28,6 +31,33 @@ protected AnnotatedTypeMirror combineAnnotationWithType(AnnotationMirror receive if (PICOTypeUtil.isImplicitlyImmutableType(declared)) { return declared; } + // workaround + if (InferenceMain.isHackMode()) { + if (extractAnnotationMirror(declared) == null) { + return declared; + } + } + return super.combineAnnotationWithType(receiverAnnotation, declared); } + + @Override + public AnnotatedTypeMirror rawCombineAnnotationWithType(AnnotationMirror anno, AnnotatedTypeMirror type) { + return combineAnnotationWithType(anno, type); + } + + @Override + public AnnotationMirror rawCombineAnnotationWithAnnotation(AnnotationMirror anno, AnnotationMirror type) { + return rawCombineAnnotationWithAnnotation(anno, type); + } + + @Override + protected AnnotationMirror extractAnnotationMirror(AnnotatedTypeMirror atm) { + // since the introduction of vp-is-valid rules, real am may be used? + AnnotationMirror am = super.extractAnnotationMirror(atm); + if (am == null) { // if failed, try to get real am + am = atm.getAnnotationInHierarchy(READONLY); + } + return am; + } } diff --git a/src/main/java/pico/inference/PICOInferenceVisitor.java b/src/main/java/pico/inference/PICOInferenceVisitor.java index 0a0d390..da8b28c 100644 --- a/src/main/java/pico/inference/PICOInferenceVisitor.java +++ b/src/main/java/pico/inference/PICOInferenceVisitor.java @@ -1,39 +1,15 @@ package pico.inference; -import static pico.typecheck.PICOAnnotationMirrorHolder.BOTTOM; -import static pico.typecheck.PICOAnnotationMirrorHolder.IMMUTABLE; -import static pico.typecheck.PICOAnnotationMirrorHolder.MUTABLE; -import static pico.typecheck.PICOAnnotationMirrorHolder.READONLY; -import static pico.typecheck.PICOAnnotationMirrorHolder.RECEIVER_DEPENDANT_MUTABLE; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; - -import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; -import org.checkerframework.framework.source.Result; -import org.checkerframework.framework.type.AnnotatedTypeFactory.ParameterizedMethodType; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import org.checkerframework.framework.util.AnnotatedTypes; -import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.ElementUtils; -import org.checkerframework.javacutil.Pair; -import org.checkerframework.javacutil.TreeUtils; - +import checkers.inference.InferenceChecker; +import checkers.inference.InferenceMain; +import checkers.inference.InferenceValidator; +import checkers.inference.InferenceVisitor; +import checkers.inference.SlotManager; +import checkers.inference.model.ConstantSlot; +import checkers.inference.model.Constraint; +import checkers.inference.model.ConstraintManager; +import checkers.inference.model.Slot; +import checkers.inference.qual.VarAnnot; import com.sun.source.tree.AnnotationTree; import com.sun.source.tree.ArrayAccessTree; import com.sun.source.tree.AssignmentTree; @@ -50,20 +26,37 @@ import com.sun.source.tree.UnaryTree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; +import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; +import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeFactory.ParameterizedExecutableType; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.framework.type.QualifierHierarchy; +import org.checkerframework.framework.util.AnnotatedTypes; +import org.checkerframework.javacutil.*; +import pico.common.ExtendedViewpointAdapter; +import pico.common.ViewpointAdapterGettable; +import pico.common.PICOTypeUtil; +import qual.ReceiverDependantMutable; -import checkers.inference.InferenceChecker; -import checkers.inference.InferenceMain; -import checkers.inference.InferenceValidator; -import checkers.inference.InferenceVisitor; -import checkers.inference.SlotManager; -import checkers.inference.model.ConstantSlot; -import checkers.inference.model.Constraint; -import checkers.inference.model.ConstraintManager; -import checkers.inference.model.EqualityConstraint; -import checkers.inference.model.InequalityConstraint; -import checkers.inference.model.Slot; -import checkers.inference.model.SubtypeConstraint; -import pico.typecheck.PICOTypeUtil; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static pico.typecheck.PICOAnnotationMirrorHolder.*; /** * Generate constraints based on the PICO constraint-based type rules in infer mode. Has typecheck @@ -82,37 +75,127 @@ protected InferenceValidator createTypeValidator() { @Override public boolean isValidUse(AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) { - if (infer) { - mainIsNot(declarationType, READONLY, "type.invalid.annotations.on.use", tree); - addMutableImmutableRdmIncompatibleConstraints(declarationType, useType); + // FIXME workaround for typecheck BOTTOM + if (useType.hasAnnotation(BOTTOM)) { return true; - } else { - AnnotationMirror declared = declarationType.getAnnotationInHierarchy(READONLY); - if (AnnotationUtils.areSame(declared, RECEIVER_DEPENDANT_MUTABLE)) { - return true; - } - assert AnnotationUtils.areSame(declared, MUTABLE) || AnnotationUtils.areSame(declared, IMMUTABLE); + } - AnnotationMirror used = useType.getAnnotationInHierarchy(READONLY); - if (AnnotationUtils.areSame(declared, MUTABLE) && - !(AnnotationUtils.areSame(used, IMMUTABLE) || AnnotationUtils.areSame(used, RECEIVER_DEPENDANT_MUTABLE))) { - return true; - } + // skip base check during inference + if (infer && !declarationType.hasAnnotation(VarAnnot.class)) { + return true; + } - if (AnnotationUtils.areSame(declared, IMMUTABLE) && - !(AnnotationUtils.areSame(used, MUTABLE) || AnnotationUtils.areSame(used, RECEIVER_DEPENDANT_MUTABLE))) { - return true; + // allow RDM on mutable fields with enclosing class bounded with mutable + if (tree instanceof VariableTree && !useType.isDeclaration()) { + VariableElement element = TreeUtils.elementFromDeclaration((VariableTree)tree); + if (element.getKind() == ElementKind.FIELD && ElementUtils.enclosingTypeElement(element) != null) { + // assert only 1 bound exists + AnnotationMirror enclosingBound = + extractVarAnnot(PICOTypeUtil.getBoundTypeOfEnclosingTypeDeclaration(element, atypeFactory)); +// atypeFactory.getTypeDeclarationBounds( +// Objects.requireNonNull(ElementUtils.enclosingTypeElement(element)).asType()).iterator().next(); + + // if enclosing bound == mutable -> (RDM or Mutable usable on mutable-bounded fields) + // else -> adaptedSubtype + // would be helpful if the solver is SMT and supports "ite" operation + if (infer) { + final ConstraintManager constraintManager = InferenceMain.getInstance().getConstraintManager(); + final SlotManager slotManager = InferenceMain.getInstance().getSlotManager(); + + // cannot use RDM on mutable-bounded fields in immutable classes + // for mutable enclosing class: allow RDM/Mutable on mutable-bounded fields + constraintManager.addImplicationConstraint( + Collections.singletonList( // if decl bound is mutable + constraintManager.createEqualityConstraint(slotManager.getSlot(enclosingBound), + slotManager.getSlot(MUTABLE)) + ), + createRDMOnMutableFieldConstraint(useType, extractVarAnnot(declarationType))); + // for other enclosing class: use adaptedSubtype + constraintManager.addImplicationConstraint( + Collections.singletonList( // if decl bound is mutable + constraintManager.createInequalityConstraint(slotManager.getSlot(enclosingBound), + slotManager.getSlot(MUTABLE)) + ), + createAdaptedSubtypeConstraint(useType, declarationType)); + return true; // always proceed on inference + } + isAdaptedSubtype(useType, declarationType, "type.invalid.annotations.on.use", tree); + // type-check + return !AnnotationUtils.areSame(enclosingBound, MUTABLE) || + ifAnnoIsThenMainIsOneOf(extractVarAnnot(declarationType), MUTABLE, useType, + new AnnotationMirror[]{MUTABLE, RECEIVER_DEPENDANT_MUTABLE}); +// if(declarationType.hasAnnotation(MUTABLE) +// && useType.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE) +// && AnnotationUtils.containsSameByName(enclosingBound, MUTABLE)) { +// return true; +// } } - return false; + } + isAdaptedSubtype(useType, declarationType, "type.invalid.annotations.on.use", tree); + return true; + } + + @Override + protected void checkConstructorResult( + AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { + // Nothing to check + } + + /** + * constraint: lhs |> rhs <: lhs + * equal to decl:immutable => use:immutable || decl:mutable => use:mutable + * @param lhs left value of adaption, typically use + * @param rhs right value of adaption, typically declaration + */ + private void isAdaptedSubtype(AnnotatedTypeMirror lhs, AnnotatedTypeMirror rhs, String msgKey, Tree node) { + if (extractVarAnnot(lhs).equals(extractVarAnnot(rhs)) || lhs.hasAnnotation(POLY_MUTABLE)) { + return; + } + // todo:haifeng we should do the viewpointAdapt in baseTypeValidator.java#visitDeclared 299 function:getTypeDeclarationBounds + ExtendedViewpointAdapter vpa = ((ViewpointAdapterGettable)atypeFactory).getViewpointAdapter(); + AnnotatedTypeMirror adapted = vpa.rawCombineAnnotationWithType(extractVarAnnot(lhs), + rhs); + mainIsSubtype(adapted, extractVarAnnot(lhs), msgKey, node); + } + + private Constraint createAdaptedSubtypeConstraint(AnnotatedTypeMirror lhs, AnnotatedTypeMirror rhs) { + assert infer; + final ConstraintManager constraintManager = InferenceMain.getInstance().getConstraintManager(); + final SlotManager slotManager = InferenceMain.getInstance().getSlotManager(); + + ExtendedViewpointAdapter vpa = ((ViewpointAdapterGettable)atypeFactory).getViewpointAdapter(); + AnnotatedTypeMirror adapted = vpa.rawCombineAnnotationWithType(extractVarAnnot(lhs), rhs); + return constraintManager.createSubtypeConstraint( + slotManager.getSlot(adapted), + slotManager.getSlot(lhs) + ); + } + +// @Override +// public boolean mainIsSubtype(AnnotatedTypeMirror ty, AnnotationMirror mod) { +// // TODO push change to cfi +// boolean s = super.mainIsSubtype(ty, mod); // execute only once to avoid side-effects +// if (!s) { +// return atypeFactory.getQualifierHierarchy().isSubtype(ty.getAnnotationInHierarchy(mod), mod); +// } +// return true; +// } + + @Override + public void mainIsSubtype(AnnotatedTypeMirror ty, AnnotationMirror mod, String msgkey, Tree node) { + if (infer) { + super.mainIsSubtype(ty, mod, msgkey, node); + } else if (!atypeFactory.getQualifierHierarchy().isSubtypeQualifiersOnly(ty.getAnnotationInHierarchy(mod), mod)){ + checker.reportError(node, msgkey, ty.getAnnotations().toString(), mod.toString()); } } private void addMutableImmutableRdmIncompatibleConstraints(AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType) { final ConstraintManager constraintManager = InferenceMain.getInstance().getConstraintManager(); final SlotManager slotManager = InferenceMain.getInstance().getSlotManager(); - Slot declSlot = slotManager.getVariableSlot(declarationType); - Slot useSlot = slotManager.getVariableSlot(useType); + Slot declSlot = slotManager.getSlot(declarationType); + Slot useSlot = slotManager.getSlot(useType); Slot mutable = slotManager.getSlot(MUTABLE); Slot immutable = slotManager.getSlot(IMMUTABLE); Slot rdm = slotManager.getSlot(RECEIVER_DEPENDANT_MUTABLE); @@ -131,54 +214,45 @@ private void addMutableImmutableRdmIncompatibleConstraints(AnnotatedDeclaredType constraintManager.addImplicationConstraint(Arrays.asList(isImmutable), notRDM); } - @Override - public boolean validateTypeOf(Tree tree) { - AnnotatedTypeMirror type; - // It's quite annoying that there is no TypeTree - switch (tree.getKind()) { - case PRIMITIVE_TYPE: - case PARAMETERIZED_TYPE: - case TYPE_PARAMETER: - case ARRAY_TYPE: - case UNBOUNDED_WILDCARD: - case EXTENDS_WILDCARD: - case SUPER_WILDCARD: - case ANNOTATED_TYPE: - type = atypeFactory.getAnnotatedTypeFromTypeTree(tree); - break; - case METHOD: - type = atypeFactory.getMethodReturnType((MethodTree) tree); - if (type == null || - type.getKind() == TypeKind.VOID) { - // Nothing to do for void methods. - // Note that for a constructor the AnnotatedExecutableType does - // not use void as return type. - return true; - } - break; - default: - type = atypeFactory.getAnnotatedType(tree); - } - - return validateType(tree, type); - } +// @Override +// public boolean validateTypeOf(Tree tree) { +// AnnotatedTypeMirror type; +// // It's quite annoying that there is no TypeTree +// switch (tree.getKind()) { +// case PRIMITIVE_TYPE: +// case PARAMETERIZED_TYPE: +// case TYPE_PARAMETER: +// case ARRAY_TYPE: +// case UNBOUNDED_WILDCARD: +// case EXTENDS_WILDCARD: +// case SUPER_WILDCARD: +// case ANNOTATED_TYPE: +// type = atypeFactory.getAnnotatedTypeFromTypeTree(tree); +// break; +// case METHOD: +// type = atypeFactory.getMethodReturnType((MethodTree) tree); +// if (type == null || +// type.getKind() == TypeKind.VOID) { +// // Nothing to do for void methods. +// // Note that for a constructor the AnnotatedExecutableType does +// // not use void as return type. +// return true; +// } +// break; +// default: +// type = atypeFactory.getAnnotatedType(tree); +// } +// +// return validateType(tree, type); +// // extends clause kind = IDENTIFIER. an annotator is added for defaulting +// } // TODO This might not be correct for infer mode. Maybe returning as it is @Override public boolean validateType(Tree tree, AnnotatedTypeMirror type) { - // basic consistency checks - if (!AnnotatedTypes.isValidType(atypeFactory.getQualifierHierarchy(), type)) { - if (!infer) { - checker.report( - Result.failure("type.invalid", type.getAnnotations(), type.toString()), tree); - return false; - } - } - if (!typeValidator.isValid(type, tree)) { - if (!infer) { + if (!typeValidator.isValid(type, tree)) { // in inference, isValid never return false return false; - } } // The initial purpose of always returning true in validateTypeOf in inference mode // might be that inference we want to generate constraints over all the ast location, @@ -192,44 +266,238 @@ public boolean validateType(Tree tree, AnnotatedTypeMirror type) { } @Override - protected boolean checkConstructorInvocation(AnnotatedDeclaredType invocation, AnnotatedExecutableType constructor, NewClassTree newClassTree) { + protected void checkConstructorInvocation(AnnotatedDeclaredType invocation, AnnotatedExecutableType constructor, NewClassTree newClassTree) { + AnnotationMirror constructorReturn = extractVarAnnot(constructor.getReturnType()); if (infer) { - AnnotationMirror constructorReturn = extractVarAnnot(constructor.getReturnType()); mainIsSubtype(invocation, constructorReturn, "constructor.invocation.invalid", newClassTree); } else { - AnnotatedDeclaredType returnType = (AnnotatedDeclaredType) constructor.getReturnType(); - if (!atypeFactory.getTypeHierarchy().isSubtype(invocation, returnType)) { - checker.report(Result.failure( - "constructor.invocation.invalid", invocation, returnType), newClassTree); - return false; - } + } - return super.checkConstructorInvocation(invocation, constructor, newClassTree); + + super.checkConstructorInvocation(invocation, constructor, newClassTree); } private AnnotationMirror extractVarAnnot(final AnnotatedTypeMirror atm) { - assert infer; - final SlotManager slotManager = InferenceMain.getInstance().getSlotManager(); - return slotManager.getAnnotation(slotManager.getVariableSlot(atm)); + if (infer) { + final SlotManager slotManager = InferenceMain.getInstance().getSlotManager(); + return slotManager.getAnnotation(slotManager.getSlot(atm)); + } + return atm.getAnnotationInHierarchy(READONLY); + } + + /** + * Extract the declaration initialization bound of a certain atm. + * Return the slot generated during inference. + * @param atm any AnnotatedDeclaredType + * @return the initialization bound on the class declaration of the type (actual or slot annotation) + */ + private AnnotationMirror extractInitBoundAnno(final AnnotatedDeclaredType atm) { + Element tm = atypeFactory.getProcessingEnv().getTypeUtils().asElement(atm.getUnderlyingType()); + assert tm instanceof TypeElement; + return extractVarAnnot(PICOTypeUtil.getBoundTypeOfTypeDeclaration((TypeElement) tm, atypeFactory)); } @Override public Void visitVariable(VariableTree node, Void p) { VariableElement element = TreeUtils.elementFromDeclaration(node); - if (checker.hasOption("optimalSolution") && element != null - && element.getKind() == ElementKind.FIELD && !ElementUtils.isStatic(element)) { - AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(element); - // Recursively prefer to be rdm and immutable - addDeepPreference(type, RECEIVER_DEPENDANT_MUTABLE, 3, node); - addDeepPreference(type, IMMUTABLE, 3, node); + AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(element); + if (infer) { + if (checker.hasOption("optimalSolution") && element != null + && element.getKind() == ElementKind.FIELD && !ElementUtils.isStatic(element)) { + // Recursively prefer to be rdm and immutable + addDeepPreference(type, RECEIVER_DEPENDANT_MUTABLE, 3, node); + addDeepPreference(type, IMMUTABLE, 3, node); + } + + // if the use is a field and not static, and the bound of the type is mutable: + // allow the use to be rdm or mutable + if (element != null && element.getKind() == ElementKind.FIELD && !ElementUtils.isStatic(element)) { + if (type instanceof AnnotatedDeclaredType) { + ifBoundContainsThenMainIsOneOf((AnnotatedDeclaredType) type, MUTABLE, + new AnnotationMirror[]{MUTABLE, RECEIVER_DEPENDANT_MUTABLE}); + } + } + + // Base will skip the rest check if assignment (if presents) get error. + // Make this explicit. + if (element != null && element.getKind() == ElementKind.LOCAL_VARIABLE && node.getInitializer() != null) { + // If not use element, but use the atypeFactory.getAnnotatedTypeLhs, anno will refined to initializer's + // anno even if the use is invalid, such as a @Mutable Immutable local variable. + // This refinement is ignored only here to capture related errors. + if (type instanceof AnnotatedDeclaredType) { + AnnotatedTypeMirror boundType = + PICOTypeUtil.getBoundTypeOfTypeDeclaration(type.getUnderlyingType(), atypeFactory); + isAdaptedSubtype(type, boundType, "type.invalid.annotations.on.use", node); + } + } + + if (type instanceof AnnotatedDeclaredType) { + for (AnnotatedTypeMirror arg : ((AnnotatedDeclaredType) type).getTypeArguments()) { + mainIsNoneOf(arg, new AnnotationMirror[]{POLY_MUTABLE, BOTTOM, RECEIVER_DEPENDANT_MUTABLE}, + "type.invalid.annotations.on.use", node); + } + } + } else { + if (element.getKind() == ElementKind.FIELD) { + if (type.hasAnnotation(POLY_MUTABLE)) { + checker.reportError(node, "field.polymutable.forbidden", element); + } + } + + Set declAnno = atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType()); + if ((declAnno != null && AnnotationUtils.containsSameByName(declAnno, IMMUTABLE)) || + element.getKind() != ElementKind.FIELD || !type.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { + checkAndReportInvalidAnnotationOnUse(type, node); + } } + return super.visitVariable(node, p); } + private void checkAndReportInvalidAnnotationOnUse(AnnotatedTypeMirror type, Tree tree) { + AnnotationMirror useAnno = type.getAnnotationInHierarchy(READONLY); + // FIXME rm after poly vp + if (useAnno != null && AnnotationUtils.areSame(useAnno, POLY_MUTABLE)) { + return; + } + if (useAnno != null && !PICOTypeUtil.isImplicitlyImmutableType(type) && type.getKind() != TypeKind.ARRAY) { // TODO: annotate the use instead of using this + AnnotationMirror defaultAnno = MUTABLE; + for (AnnotationMirror anno : atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType())) { + if (atypeFactory.getQualifierHierarchy().isSubtypeQualifiersOnly(anno, READONLY) && !AnnotationUtils.areSame(anno, READONLY)) { + defaultAnno = anno; + } + } + AnnotatedTypeMirror declATM = type.deepCopy(); + declATM.replaceAnnotation(defaultAnno); + isAdaptedSubtype(type, declATM, "type.invalid.annotations.on.use", tree); + } + } + + /** + * + * @param mainAtm a field atm + * @param mutBound declaration bound of mutability + * @return (mutBound == RDM) -> (anno(atm) == RDM | anno(atm) == Mutable) + */ + private Constraint createRDMOnMutableFieldConstraint(AnnotatedTypeMirror mainAtm, AnnotationMirror mutBound) { + final ConstraintManager constraintManager = InferenceMain.getInstance().getConstraintManager(); + final SlotManager slotManager = InferenceMain.getInstance().getSlotManager(); + + Constraint oneOfConst = createMainIsMutableOrRdmConstraint(mainAtm); + + return constraintManager.createImplicationConstraint( + Collections.singletonList(constraintManager.createEqualityConstraint( + slotManager.getSlot(mutBound), + slotManager.getSlot(MUTABLE))), + oneOfConst + ); + } + + private Constraint createMainIsMutableOrRdmConstraint(AnnotatedTypeMirror mainAtm) { + assert infer; + final ConstraintManager constraintManager = InferenceMain.getInstance().getConstraintManager(); + final SlotManager slotManager = InferenceMain.getInstance().getSlotManager(); + // A || B <-> !A -> B + return constraintManager.createImplicationConstraint( + Collections.singletonList(constraintManager.createInequalityConstraint( + slotManager.getSlot(MUTABLE), + slotManager.getSlot(mainAtm))), + constraintManager.createEqualityConstraint( + slotManager.getSlot(RECEIVER_DEPENDANT_MUTABLE), + slotManager.getSlot(mainAtm) + ) + ); + } + + /** + * + * @param atm + * @param contains + * @param oneOf + */ + public boolean ifBoundContainsThenMainIsOneOf(AnnotatedDeclaredType atm, AnnotationMirror contains, + AnnotationMirror[] oneOf) { + + AnnotationMirror boundAnno = extractInitBoundAnno(atm); + return ifAnnoIsThenMainIsOneOf(boundAnno, contains, atm, oneOf); + + } + + public boolean ifAnnoIsThenMainIsOneOf(AnnotationMirror annotation, AnnotationMirror is, + AnnotatedTypeMirror mainAtm, AnnotationMirror[] oneOf) { + // TODO support more annotations + assert oneOf.length == 2: "oneOf.length should be 2"; + if (this.infer) { + final ConstraintManager constraintManager = InferenceMain.getInstance().getConstraintManager(); + final SlotManager slotManager = InferenceMain.getInstance().getSlotManager(); + Constraint oneOfConst = constraintManager.createImplicationConstraint( + Collections.singletonList(constraintManager.createInequalityConstraint( + slotManager.getSlot(oneOf[0]), + slotManager.getSlot(mainAtm))), + constraintManager.createEqualityConstraint( + slotManager.getSlot(oneOf[1]), + slotManager.getSlot(mainAtm) + ) + ); + + constraintManager.addImplicationConstraint( + Collections.singletonList(constraintManager.createEqualityConstraint( + slotManager.getSlot(annotation), + slotManager.getSlot(is))), + oneOfConst + ); + return true; // always return true on inference + } else { + if (AnnotationUtils.areSameByName(annotation, is)) { + return AnnotationUtils.containsSameByName(Arrays.asList(oneOf), + mainAtm.getAnnotationInHierarchy(READONLY)); + } + } + return true; + } + + /** + * Make the main annotation on {@code atm} cannot infer to given {@code anno}. + * But the written annotation still have effect. + *

+ * A notable use could be poly annotations which could be used by inference if explicitly present, + * but new poly cannot be inferred. + *

+ * @param atm the type which should not inferred to given anno + * @param anno the anno that cannot be inferred to + * @param errorKey this will show only if things goes wrong and result into a error message in type-check. + * @param tree this will show only if things goes wrong and result into a error message in type-check. + */ + public void mainCannotInferTo(AnnotatedTypeMirror atm, AnnotationMirror anno, String errorKey, Tree tree) { + if (infer) { + SlotManager slotManager = InferenceMain.getInstance().getSlotManager(); + // should be constant slot if written explicitly in code + if (!(slotManager.getSlot(atm) instanceof ConstantSlot)) { + mainIsNot(atm, anno, errorKey, tree); + } + + } + } + + @Override public Void visitMethod(MethodTree node, Void p) { AnnotatedExecutableType executableType = atypeFactory.getAnnotatedType(node); - AnnotatedDeclaredType bound = PICOTypeUtil.getBoundTypeOfEnclosingTypeDeclaration(node, atypeFactory); + AnnotatedDeclaredType bound; + if (PICOTypeUtil.isEnclosedByAnonymousClass(node, atypeFactory)) { + bound = PICOTypeUtil.getBoundOfEnclosingAnonymousClass(node, atypeFactory); + } else { + bound = PICOTypeUtil.getBoundTypeOfEnclosingTypeDeclaration(node, atypeFactory); + } + assert bound != null; + assert !bound.hasAnnotation(POLY_MUTABLE) : "BOUND CANNOT BE POLY"; + + // cannot infer poly, but can use it for type-check. + mainCannotInferTo(executableType.getReturnType(), POLY_MUTABLE, "cannot.infer.poly", node); + mainCannotInferTo(executableType.getReturnType(), BOTTOM, "type.invalid.annotations.on.use", node); + if (executableType.getReceiverType() != null) { + mainCannotInferTo(executableType.getReceiverType(), POLY_MUTABLE, "cannot.infer.poly", node); + } if (TreeUtils.isConstructor(node)) { // Doesn't check anonymous constructor case @@ -238,24 +506,22 @@ public Void visitMethod(MethodTree node, Void p) { } AnnotatedDeclaredType constructorReturnType = (AnnotatedDeclaredType) executableType.getReturnType(); + // Constructor return cannot be @Readonly + mainIsNot(constructorReturnType, READONLY, "constructor.return.invalid", node); + if (infer) { - // Constructor return cannot be @Readonly - mainIsNot(constructorReturnType, READONLY, "constructor.return.invalid", node); ConstraintManager constraintManager = InferenceMain.getInstance().getConstraintManager(); SlotManager slotManager = InferenceMain.getInstance().getSlotManager(); - Slot boundSlot = slotManager.getVariableSlot(bound); - Slot consRetSlot = slotManager.getVariableSlot(constructorReturnType); + Slot boundSlot = slotManager.getSlot(bound); + Slot consRetSlot = slotManager.getSlot(constructorReturnType); Slot rdmSlot = slotManager.getSlot(RECEIVER_DEPENDANT_MUTABLE); Constraint inequalityConstraint = constraintManager.createInequalityConstraint(boundSlot, rdmSlot); Constraint subtypeConstraint = constraintManager.createSubtypeConstraint(consRetSlot, boundSlot); // bound != @ReceiverDependantMutable -> consRet <: bound - constraintManager.addImplicationConstraint(Arrays.asList(inequalityConstraint), subtypeConstraint); - } else { - if (constructorReturnType.hasAnnotation(READONLY)) { - checker.report(Result.failure("constructor.return.invalid", constructorReturnType), node); - return super.visitMethod(node, p); - } + constraintManager.addImplicationConstraint(Collections.singletonList(inequalityConstraint), subtypeConstraint); + // TODO Add typecheck for this? } + } else { // Additional logic compared to PICOVisitor to prefer declared receiver and parameters // tp be @Readonly in inference results. @@ -265,25 +531,15 @@ public Void visitMethod(MethodTree node, Void p) { // Prefer declared receiver to be @Readonly addDeepPreference(declaredReceiverType, READONLY, 1, node); } - // Prefer all parametes to be @Readonly + // Prefer all parameters to be @Readonly for (AnnotatedTypeMirror ptype : executableType.getParameterTypes()) { addDeepPreference(ptype, READONLY, 1, node); } } // Above is additional preference logic if (declaredReceiverType != null) { - if (infer) { - addMutableImmutableRdmIncompatibleConstraints(bound, declaredReceiverType); - } else { - if (!bound.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE) - && !atypeFactory.getQualifierHierarchy().isSubtype( - declaredReceiverType.getAnnotationInHierarchy(READONLY), - bound.getAnnotationInHierarchy(READONLY)) - // Below three are allowed on declared receiver types of instance methods in either @Mutable class or @Immutable class - && !declaredReceiverType.hasAnnotation(READONLY)) { - checker.report(Result.failure("method.receiver.incompatible", declaredReceiverType), node); - } - } + mainIsNot(declaredReceiverType, BOTTOM, "bottom.on.receiver", node); + isAdaptedSubtype(declaredReceiverType, bound, "method.receiver.incompatible", node); } } @@ -292,7 +548,22 @@ public Void visitMethod(MethodTree node, Void p) { // TODO Object identity check return super.visitMethod(node, p); } - + /* + * @RDM + * class A { + * + * void foo(T) { + * + * } + * } + * class B extends @Immutable A<@X String> { + * + * @Override + * void foo(@Y String) { // string is compatible to bound of T. Adapt the signature of Class A to the use of class B. + * } + * } + * + * */ private void flexibleOverrideChecker(MethodTree node) { // Method overriding checks // TODO Copied from super, hence has lots of duplicate code with super. We need to @@ -318,7 +589,7 @@ private void flexibleOverrideChecker(MethodTree node) { types, atypeFactory, enclosingType, pair.getValue()); // Viewpoint adapt super method executable type to current class bound(is this always class bound?) // to allow flexible overriding - atypeFactory.getViewpointAdapter().viewpointAdaptMethod(enclosingType, pair.getValue() , overriddenMethod); + ((ViewpointAdapterGettable) atypeFactory).getViewpointAdapter().viewpointAdaptMethod(enclosingType, pair.getValue() , overriddenMethod); // todo: should we cast it? AnnotatedExecutableType overrider = atypeFactory.getAnnotatedType(node); if (!checkOverride(node, overrider, enclosingType, overriddenMethod, overriddenType)) { // Stop at the first mismatch; this makes a difference only if @@ -337,7 +608,8 @@ protected boolean checkOverride( return true; } - protected void checkTypecastSafety(TypeCastTree node, Void p) { + @Override + protected void checkTypecastSafety(TypeCastTree node) { if (!checker.getLintOption("cast:unsafe", true)) { return; } @@ -348,9 +620,9 @@ protected void checkTypecastSafety(TypeCastTree node, Void p) { // the input types to be subtypes according to Java if (!isTypeCastSafe(castType, exprType, node)) { // This is only warning message, so even though enterred this line, it doesn't cause PICOInfer to exit. - checker.report( - Result.warning("cast.unsafe", exprType.toString(true), castType.toString(true)), - node); + // Even if that was an error, PICOInfer would also not exit. + checker.reportWarning(node, + "cast.unsafe", exprType.toString(true), castType.toString(true)); } } @@ -374,7 +646,7 @@ private boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror return isCompatibleCastInInfer(castType, exprType, node); } else { // Typechecking side standard implementation - warns about downcasting - return super.isTypeCastSafe(castType, exprType); + return isTypeCastSafe(castType, exprType); } } @@ -418,10 +690,10 @@ private boolean isCompatibleCastInInfer(AnnotatedTypeMirror castType, AnnotatedT return true; } else { // Default strategy - comparablecast - final ConstraintManager constraintManager = InferenceMain.getInstance().getConstraintManager(); + final QualifierHierarchy qualHierarchy = InferenceMain.getInstance().getRealTypeFactory().getQualifierHierarchy(); final SlotManager slotManager = InferenceMain.getInstance().getSlotManager(); - final Slot castSlot = slotManager.getVariableSlot(castType); - final Slot exprSlot = slotManager.getVariableSlot(exprType); + final Slot castSlot = slotManager.getSlot(castType); + final Slot exprSlot = slotManager.getSlot(exprType); if (castSlot instanceof ConstantSlot && exprSlot instanceof ConstantSlot) { ConstantSlot castCSSlot = (ConstantSlot) castSlot; @@ -429,7 +701,8 @@ private boolean isCompatibleCastInInfer(AnnotatedTypeMirror castType, AnnotatedT // Special handling for case with two ConstantSlots: even though they may not be comparable, // but to infer more program, let this case fall back to "anycast" silently and continue // inference. - return constraintManager.getConstraintVerifier().areComparable(castCSSlot, exprCSSlot); + return qualHierarchy.isSubtypeQualifiersOnly(castCSSlot.getValue(), exprCSSlot.getValue()) + || qualHierarchy.isSubtypeQualifiersOnly(exprCSSlot.getValue(), castCSSlot.getValue()); } else { // But if there is at least on VariableSlot, PICOInfer guarantees that solutions don't include // incomparable casts. @@ -462,12 +735,14 @@ public Void visitUnary(UnaryTree node, Void p) { return super.visitUnary(node, p); } + + private void checkMutation(ExpressionTree node, ExpressionTree variable) { AnnotatedTypeMirror receiverType = atypeFactory.getReceiverType(variable); if(receiverType != null) { - if (PICOTypeUtil.isAssigningAssignableField(node, atypeFactory)){ + if (PICOTypeUtil.isAssigningAssignableField(variable, atypeFactory)){ checkAssignableField(node, variable, receiverType); - } else if (isInitializingObject(node)) { + } else if (isInitializingObject(variable)) { checkInitializingObject(node, variable, receiverType); } else { checkMutableReceiverCase(node, variable, receiverType); @@ -476,7 +751,8 @@ private void checkMutation(ExpressionTree node, ExpressionTree variable) { } private void checkAssignableField(ExpressionTree node, ExpressionTree variable, AnnotatedTypeMirror receiverType) { - Element fieldElement = TreeUtils.elementFromUse(node); + Element fieldElement = TreeUtils.elementFromUse(variable); + assert fieldElement != null; if (fieldElement != null) {//TODO Can this bu null? AnnotatedTypeMirror fieldType = atypeFactory.getAnnotatedType(fieldElement); assert fieldType != null; @@ -484,13 +760,14 @@ private void checkAssignableField(ExpressionTree node, ExpressionTree variable, // Break the combination of readonly receiver + rdm assignable field ConstraintManager constraintManager = InferenceMain.getInstance().getConstraintManager(); SlotManager slotManager = InferenceMain.getInstance().getSlotManager(); - Slot receiverSlot = slotManager.getVariableSlot(receiverType); - Slot fieldSlot = slotManager.getVariableSlot(fieldType); + Slot receiverSlot = slotManager.getSlot(receiverType); + Slot fieldSlot = slotManager.getSlot(fieldType); Slot readonly = slotManager.getSlot(READONLY); Slot receiver_dependant_mutable = slotManager.getSlot(RECEIVER_DEPENDANT_MUTABLE); Constraint receiverReadOnly = constraintManager.createEqualityConstraint(receiverSlot, readonly); Constraint fieldNotRDM = constraintManager.createInequalityConstraint(fieldSlot, receiver_dependant_mutable); - constraintManager.addImplicationConstraint(Arrays.asList(receiverReadOnly), fieldNotRDM); + // receiver = READONLY + constraintManager.addImplicationConstraint(Collections.singletonList(receiverReadOnly), fieldNotRDM); } else { if (receiverType.hasAnnotation(READONLY) && fieldType.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { reportFieldOrArrayWriteError(node, variable, receiverType); @@ -499,7 +776,8 @@ private void checkAssignableField(ExpressionTree node, ExpressionTree variable, } } - private void checkInitializingObject(ExpressionTree node, ExpressionTree variable, AnnotatedTypeMirror receiverType) { + private void checkInitializingObject(ExpressionTree node, ExpressionTree variable, AnnotatedTypeMirror receiverType) { // todo: haifeng we only need to do this in one statement + // TODO rm infer after mainIsNot returns bool if (infer) { // Can be anything from mutable, immutable or receiverdependantmutable mainIsNot(receiverType, READONLY, "illegal.field.write", node); @@ -509,8 +787,9 @@ private void checkInitializingObject(ExpressionTree node, ExpressionTree variabl } } } - + // todo: haifeng: the deciding factor seems to be if it is array or not. Not infer. private void checkMutableReceiverCase(ExpressionTree node, ExpressionTree variable, AnnotatedTypeMirror receiverType) { + // TODO rm infer after mainIs returns bool if (infer) { mainIs(receiverType, MUTABLE, "illegal.field.write", node); } else { @@ -523,11 +802,11 @@ private void checkMutableReceiverCase(ExpressionTree node, ExpressionTree variab // Completely copied from PICOVisitor private void reportFieldOrArrayWriteError(Tree node, ExpressionTree variable, AnnotatedTypeMirror receiverType) { if (variable.getKind() == Kind.MEMBER_SELECT) { - checker.report(Result.failure("illegal.field.write", receiverType), TreeUtils.getReceiverTree(variable)); + checker.reportError(TreeUtils.getReceiverTree(variable), "illegal.field.write", receiverType); } else if (variable.getKind() == Kind.IDENTIFIER) { - checker.report(Result.failure("illegal.field.write", receiverType), node); + checker.reportError(node, "illegal.field.write", receiverType); } else if (variable.getKind() == Kind.ARRAY_ACCESS) { - checker.report(Result.failure("illegal.array.write", receiverType), ((ArrayAccessTree)variable).getExpression()); + checker.reportError(((ArrayAccessTree)variable).getExpression(), "illegal.array.write", receiverType); } else { throw new BugInCF("Unknown assignment variable at: ", node); } @@ -541,7 +820,7 @@ private void reportFieldOrArrayWriteError(Tree node, ExpressionTree variable, An * 2) In constructor * 3) In instance method, declared receiver is @UnderInitialized * - * @param node assignment tree that might be initializing an object + * @param variable assignment tree that might be initializing an object * @return true if the assignment tree is initializing an object * * @see #hasUnderInitializationDeclaredReceiver(MethodTree) @@ -553,13 +832,13 @@ private boolean isInitializingObject(ExpressionTree variable) { TreePath treePath = atypeFactory.getPath(variable); if (treePath == null) return false; - if (TreeUtils.enclosingTopLevelBlock(treePath) != null) { + if (TreePathUtil.enclosingTopLevelBlock(treePath) != null) { // In the initialization block => always allow assigning fields! return true; } - MethodTree enclosingMethod = TreeUtils.enclosingMethod(treePath); - // No possibility of initialiazing object if the assignment is not within constructor or method(both MethodTree) + MethodTree enclosingMethod = TreePathUtil.enclosingMethod(treePath); + // No possibility of initializing object if the assignment is not within constructor or method(both MethodTree) if (enclosingMethod == null) return false; // At this point, we already know that this assignment is field assignment within a method if (TreeUtils.isConstructor(enclosingMethod) || hasUnderInitializationDeclaredReceiver(enclosingMethod)) { @@ -608,10 +887,11 @@ private void checkNewInstanceCreation(Tree node) { AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(node); if (infer) { // Ensure only @Mutable/@Immutable/@ReceiverDependantMutable are inferred on new instance creation - mainIsNoneOf(type, new AnnotationMirror[]{READONLY}, "pico.new.invalid", node); + mainIsNoneOf(type, new AnnotationMirror[]{READONLY, BOTTOM}, "pico.new.invalid", node); } else { - if (type.hasAnnotation(READONLY)) { - checker.report(Result.failure("pico.new.invalid", type), node); + if (!(type.hasAnnotation(IMMUTABLE) || type.hasAnnotation(MUTABLE) || + type.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE) || type.hasAnnotation(POLY_MUTABLE))) { + checker.reportError(node, "pico.new.invalid", type); } } } @@ -619,15 +899,21 @@ private void checkNewInstanceCreation(Tree node) { // Completely copied from PICOVisitor @Override public Void visitMethodInvocation(MethodInvocationTree node, Void p) { + // issues with getting super for anonymous class - do not check for anonymous classes. + if (TreeUtils.isSuperConstructorCall(node) && + PICOTypeUtil.isAnonymousClassTree(TreePathUtil.enclosingClass(atypeFactory.getPath(node)), atypeFactory)) { + return null; + } + super.visitMethodInvocation(node, p); - ParameterizedMethodType mfuPair = + ParameterizedExecutableType mfuPair = atypeFactory.methodFromUse(node); - AnnotatedExecutableType invokedMethod = mfuPair.methodType; + AnnotatedExecutableType invokedMethod = mfuPair.executableType; ExecutableElement invokedMethodElement = invokedMethod.getElement(); // Only check invocability if it's super call, as non-super call is already checked // by super implementation(of course in both cases, invocability is not checked when // invoking static methods) - if (!ElementUtils.isStatic(invokedMethodElement) && TreeUtils.isSuperCall(node)) { + if (!ElementUtils.isStatic(invokedMethodElement) && TreeUtils.isSuperConstructorCall(node)) { checkMethodInvocability(invokedMethod, node); } return null; @@ -635,28 +921,32 @@ public Void visitMethodInvocation(MethodInvocationTree node, Void p) { @Override protected void checkMethodInvocability(AnnotatedExecutableType method, MethodInvocationTree node) { + if (method.getReceiverType() == null) { + // Static methods don't have a receiver to check. + return; + } + if (method.getElement().getKind() == ElementKind.CONSTRUCTOR) { AnnotatedTypeMirror subClassConstructorReturnType = atypeFactory.getReceiverType(node); AnnotatedTypeMirror superClassConstructorReturnType = method.getReturnType(); // In infer mode, InferenceQualifierHierarchy that is internally used should generate subtype constraint between the // below two types GENERALLY(not always) - if (!atypeFactory.getTypeHierarchy().isSubtype(subClassConstructorReturnType, superClassConstructorReturnType)) { + if (!PICOTypeUtil.isEnumOrEnumConstant(subClassConstructorReturnType) && // THIS IS A HECK: java.lang.Enum itself is considered immutable but its subclasses could be other. Update jdk.astub? + !atypeFactory.getTypeHierarchy().isSubtype(subClassConstructorReturnType, superClassConstructorReturnType)) { // Usually the subtyping check returns true. If not, that means subtype constraint doesn't hold between two // ConstantSlots. Previously, InferenceQualifierHierarchy also generates subtype constraint in this case, // then this unsatisfiable constraint is captured by ConstraintManager and ConstraintManager early exits. But // now for two ConstantSlot case, no subtype constraint is generated any more. So we have to report the error // , otherwise it will cause inference result not typecheck - checker.report( - Result.failure( - "super.constructor.invocation.incompatible", subClassConstructorReturnType, superClassConstructorReturnType), node); + checker.reportError(node, "super.invocation.invalid", subClassConstructorReturnType, node, superClassConstructorReturnType); } } super.checkMethodInvocability(method, node); } @Override - protected Set getExceptionParameterLowerBoundAnnotations() { - Set result = new HashSet<>(); + protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { + AnnotationMirrorSet result = new AnnotationMirrorSet(); if (infer) { result.add(PICOTypeUtil.createEquivalentVarAnnotOfRealQualifier(BOTTOM)); } else { @@ -666,8 +956,8 @@ protected Set getExceptionParameterLowerBoundAnnotat } @Override - protected Set getThrowUpperBoundAnnotations() { - Set result = new HashSet<>(); + protected AnnotationMirrorSet getThrowUpperBoundAnnotations() { + AnnotationMirrorSet result = new AnnotationMirrorSet(); if (infer) { result.add(PICOTypeUtil.createEquivalentVarAnnotOfRealQualifier(READONLY)); } else { @@ -679,124 +969,124 @@ protected Set getThrowUpperBoundAnnotations() { @Override public void processClassTree(ClassTree node) { TypeElement typeElement = TreeUtils.elementFromDeclaration(node); - // TODO Don't process anonymous class. I'm not even sure if whether processClassTree(ClassTree) is - // called on anonymous class tree - if (typeElement.toString().contains("anonymous")) { + // Don't process anonymous class. + if (TypesUtils.isAnonymous(TreeUtils.typeOf(node))) { + checkAnonymousImplements(node, PICOTypeUtil.getBoundTypeOfTypeDeclaration(typeElement, atypeFactory)); super.processClassTree(node); return; } AnnotatedDeclaredType bound = PICOTypeUtil.getBoundTypeOfTypeDeclaration(typeElement, atypeFactory); - if (infer) { - mainIsNot(bound, READONLY, "class.bound.invalid", node); - if (checker.hasOption("optimalSolution")) { - addPreference(bound, RECEIVER_DEPENDANT_MUTABLE, 2); - addPreference(bound, IMMUTABLE, 2); - } - } else { - // Has to be either @Mutable, @ReceiverDependantMutable or @Immutable, nothing else - if (!bound.hasAnnotation(MUTABLE) && !bound.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE) && !bound.hasAnnotation(IMMUTABLE)) { - checker.report(Result.failure("class.bound.invalid", bound), node); - return;// Doesn't process the class tree anymore - } - } + if (!infer) { + if (bound.hasAnnotation(IMMUTABLE) || bound.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { + for(Tree member : node.getMembers()) { + if(member.getKind() == Kind.VARIABLE) { + Element ele = TreeUtils.elementFromTree(member); + assert ele != null; + // fromElement will not apply defaults, if no explicit anno exists in code, mirror have no anno + AnnotatedTypeMirror noDefaultMirror = atypeFactory.fromElement(ele); + TypeMirror ty = ele.asType(); + if (ty.getKind() == TypeKind.TYPEVAR) { + ty = TypesUtils.upperBound(ty); + } + if (AnnotationUtils.containsSameByName( + atypeFactory.getTypeDeclarationBounds(ty), MUTABLE) + && !noDefaultMirror.hasAnnotationInHierarchy(READONLY)) { + checker.reportError(member, "implicit.shallow.immutable"); + } - if (!checkCompatabilityBetweenBoundAndSuperClassesBounds(node, typeElement, bound)) { - return; + } + } + } } - - if (!checkCompatabilityBetweenBoundAndExtendsImplements(node, bound)) { - return; + mainIsNot(bound, READONLY, "class.bound.invalid", node); + mainIsNot(bound, POLY_MUTABLE, "class.bound.invalid", node); + mainIsNot(bound, BOTTOM, "class.bound.invalid", node); + if (checker.hasOption("optimalSolution")) { + addPreference(bound, RECEIVER_DEPENDANT_MUTABLE, 2); + addPreference(bound, IMMUTABLE, 2); } - // Reach this point iff 1) bound annotation is one of mutable, rdm or immutable; - // 2) bound is compatible with bounds on super types. Only then continue processing - // the class tree + checkSuperClauseEquals(node, bound); + // Always reach this point. Do not suppress errors. super.processClassTree(node); } - private boolean checkCompatabilityBetweenBoundAndSuperClassesBounds(ClassTree node, TypeElement typeElement, AnnotatedDeclaredType bound) { - // Must have compatible bound annotation as the direct super types - List superBounds = PICOTypeUtil.getBoundTypesOfDirectSuperTypes(typeElement, atypeFactory); - for (AnnotatedDeclaredType superBound : superBounds) { - if (infer) { - addSameToMutableImmutableConstraints(superBound, bound); - } else { - // If annotation on super bound is @ReceiverDependantMutable, then any valid bound is permitted. - if (superBound.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) continue; - // super bound is either @Mutable or @Immutable. Must be the subtype of the corresponding super bound - if (!atypeFactory.getQualifierHierarchy().isSubtype( - bound.getAnnotationInHierarchy(READONLY), superBound.getAnnotationInHierarchy(READONLY))) { - checker.report(Result.failure("subclass.bound.incompatible", bound, superBound), node); - return false; - } - } - } - return true; - } + /** + * The base visitor does not use inference method to check! + * This method is required to add the constraints for extends / implements. + * @param node + */ + private void checkAnonymousImplements(ClassTree node, AnnotatedDeclaredType bound) { + // NOTE: this is a workaround for checking bound against extends/implements + // After inferring annotation CF cannot skip the anonymous class, thus necessary + + assert TypesUtils.isAnonymous(TreeUtils.typeOf(node)); - private boolean checkCompatabilityBetweenBoundAndExtendsImplements(ClassTree node, AnnotatedDeclaredType bound) { if (infer) { - atypeFactory.getAnnotatedType(node); - } + Tree superClause; + if (node.getExtendsClause() != null) { + superClause = node.getExtendsClause(); + } else if (node.getImplementsClause() != null) { + // a anonymous class cannot have both extends or implements + assert node.getImplementsClause().size() == 1; // anonymous class only implement 1 interface + superClause = node.getImplementsClause().get(0); - boolean hasSame; - Tree ext = node.getExtendsClause(); - if (ext != null) { - AnnotatedTypeMirror extendsType= atypeFactory.getAnnotatedType(ext); - if (infer) { - ((PICOInferenceAnnotatedTypeFactory) atypeFactory).getVariableAnnotator().visit(extendsType, ext); - areEqual(bound, extendsType, "bound.extends.incompatabile", node); } else { - hasSame = bound.getAnnotations().size() == extendsType.getAnnotations().size() - && AnnotationUtils.areSame(extendsType.getAnnotationInHierarchy(READONLY), - bound.getAnnotationInHierarchy(READONLY)); - if (!hasSame) { - checker.report(Result.failure("bound.extends.incompatabile"), node); - return false; - } + throw new BugInCF("Anonymous class with no extending/implementing clause!"); } + AnnotationMirror superBound = extractInitBoundAnno((AnnotatedDeclaredType) atypeFactory.getAnnotatedType(superClause)); + // anonymous cannot have implement clause, so no "use" anno of super type + mainIsSubtype(bound, superBound, "subclass.bound.incompatible", node); } + } - List impls = node.getImplementsClause(); - if (impls != null) { - for (Tree im : impls) { - AnnotatedTypeMirror implementsType = atypeFactory.getAnnotatedType(im); - if (infer) { - ((PICOInferenceAnnotatedTypeFactory) atypeFactory).getVariableAnnotator().visit(implementsType, im); - areEqual(bound, implementsType, "bound.implements.incompatabile", node); - } else { - hasSame = bound.getAnnotations().size() == implementsType.getAnnotations().size() - && AnnotationUtils.areSame(implementsType.getAnnotationInHierarchy(READONLY), - bound.getAnnotationInHierarchy(READONLY)); - if (!hasSame) { - checker.report(Result.failure("bound.implements.incompatabile"), node); - return false; - } - } - } + + /** + * extends/implements clause use anno is adapted subtype of bound anno + *

Could be subtype, but recall Readonly and Bottom is not usable on class init bound.

+ * @param node + * @param bound + */ + private void checkSuperClauseEquals(ClassTree node, AnnotatedDeclaredType bound) { + if (node.getExtendsClause() != null) { + AnnotatedTypeMirror ext = atypeFactory.getAnnotatedType(node.getExtendsClause()); + boundVsExtImpClause(bound, ext, "declaration.inconsistent.with.extends.clause", node.getExtendsClause()); + } + for (Tree impTree : node.getImplementsClause()) { + AnnotatedTypeMirror impType = atypeFactory.getAnnotatedType(impTree); + boundVsExtImpClause(bound, impType, "declaration.inconsistent.with.implements.clause", impTree); } - return true; } - private void addSameToMutableImmutableConstraints(AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType) { - ConstraintManager constraintManager = InferenceMain.getInstance().getConstraintManager(); - SlotManager slotManager = InferenceMain.getInstance().getSlotManager(); - Slot declSlot = slotManager.getVariableSlot(declarationType); - Slot useSlot = slotManager.getVariableSlot(useType); - Slot mutable = slotManager.getSlot(MUTABLE); - Slot immutable = slotManager.getSlot(IMMUTABLE); - // declType == @Mutable -> useType == @Mutable - Constraint equalityConstraintLHS = constraintManager.createEqualityConstraint(declSlot, mutable); - Constraint equalityConstraintRHS = constraintManager.createEqualityConstraint(useSlot, mutable); - constraintManager.addImplicationConstraint(Arrays.asList(equalityConstraintLHS), equalityConstraintRHS); - // declType == @Immutable -> useType == @Immutable - equalityConstraintLHS = constraintManager.createEqualityConstraint(declSlot, immutable); - equalityConstraintRHS = constraintManager.createEqualityConstraint(useSlot, immutable); - constraintManager.addImplicationConstraint(Arrays.asList(equalityConstraintLHS), equalityConstraintRHS); + + private void boundVsExtImpClause(AnnotatedDeclaredType classBound, AnnotatedTypeMirror superType, String errorKey, Tree tree) { + // atypeFactory.getTypeDeclarationBounds does not work correctly: getting the real annos instead of slots + AnnotatedTypeMirror superBound = + PICOTypeUtil.getBoundTypeOfTypeDeclaration(superType.getUnderlyingType(), atypeFactory); + + mainIsNot(superType, BOTTOM, "type.invalid.annotations.on.use", tree); + isAdaptedSubtype(superType, superBound, "type.invalid.annotations.on.use", tree); + + // the class bound should be a valid "use" of the super. + // consider replace with isValidUse? + isAdaptedSubtype(classBound, superType, errorKey, tree); } + +// private boolean checkCompatabilityBetweenBoundAndSuperClassesBounds(ClassTree node, TypeElement typeElement, AnnotatedDeclaredType bound) { +// // Must have compatible bound annotation as the direct super types +// List superBounds = PICOTypeUtil.getBoundTypesOfDirectSuperTypes(typeElement, atypeFactory); +// for (AnnotatedDeclaredType superBound : superBounds) { +// if (!isAdaptedSubtype(bound, superBound)) { +// checker.reportError(node, "subclass.bound.incompatible", bound, superBound); +// return false; // do not stop processing if failed +// } +// } +// return true; +// } + /** * commonAssignmentCheck() method that adapts to PICOInfer. * @@ -808,17 +1098,21 @@ private void addSameToMutableImmutableConstraints(AnnotatedDeclaredType declarat * @param errorKey the error message to use if the check fails (must be a */ @Override - protected void commonAssignmentCheck( - Tree varTree, ExpressionTree valueExp, String errorKey) { + protected boolean commonAssignmentCheck( + Tree varTree, ExpressionTree valueExp, String errorKey, Object... extraArgs) { AnnotatedTypeMirror var = atypeFactory.getAnnotatedTypeLhs(varTree); assert var != null : "no variable found for tree: " + varTree; + // Seems that typecheck does not have this. + // Removing this check will satisfy initial typecheck of inferrable/issue144/ComplicatedTest.java:42, + // where invalid.annotations.on.use is not expected. + // Local variable is flow-sensitive, so when assigned to a type that contradicts with the init bound, + // it still got "refined" + // Maybe updating the flow-sensitive logic to not refined to invalid type? if (!validateType(varTree, var)) { - return; + return false; } - checkAssignability(var, varTree); - if (varTree instanceof VariableTree) { VariableElement element = TreeUtils.elementFromDeclaration((VariableTree) varTree); if (element.getKind() == ElementKind.FIELD && !ElementUtils.isStatic(element)) { @@ -828,16 +1122,30 @@ protected void commonAssignmentCheck( AnnotatedTypeMirror varAdapted = var.shallowCopy(true); // Viewpoint adapt varAdapted to the bound. PICOInferenceAnnotatedTypeFactory#viewpointAdaptMember() // mutates varAdapted, so after the below method is called, varAdapted is the result adapted to bound - atypeFactory.getViewpointAdapter().viewpointAdaptMember(bound, element, varAdapted); + ((ViewpointAdapterGettable) atypeFactory).getViewpointAdapter().viewpointAdaptMember(bound, element, varAdapted); // Pass varAdapted here as lhs type. // Caution: cannot pass var directly. Modifying type in PICOInferenceTreeAnnotator# // visitVariable() will cause wrong type to be gotton here, as on inference side, // atm is uniquely determined by each element. - commonAssignmentCheck(varAdapted, valueExp, errorKey); - return; + return commonAssignmentCheck(varAdapted, valueExp, errorKey, extraArgs); } } - commonAssignmentCheck(var, valueExp, errorKey); + return commonAssignmentCheck(var, valueExp, errorKey, extraArgs); + } + + @Override + protected boolean commonAssignmentCheck(AnnotatedTypeMirror varType, + AnnotatedTypeMirror valueType, Tree valueTree, + String errorKey, Object... extraArgs) { + // TODO: WORKAROUND: anonymous class handling + if (TypesUtils.isAnonymous(valueType.getUnderlyingType())) { + AnnotatedTypeMirror newValueType = varType.deepCopy(); + newValueType.clearAnnotations(); + newValueType.addAnnotation(extractVarAnnot(valueType)); + + valueType = newValueType; + } + return super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); } } diff --git a/src/main/java/pico/inference/PICOVariableAnnotator.java b/src/main/java/pico/inference/PICOVariableAnnotator.java index 65893b2..a028e67 100644 --- a/src/main/java/pico/inference/PICOVariableAnnotator.java +++ b/src/main/java/pico/inference/PICOVariableAnnotator.java @@ -1,17 +1,17 @@ package pico.inference; -import static pico.typecheck.PICOAnnotationMirrorHolder.BOTTOM; -import static pico.typecheck.PICOAnnotationMirrorHolder.IMMUTABLE; -import static pico.typecheck.PICOAnnotationMirrorHolder.MUTABLE; -import static pico.typecheck.PICOAnnotationMirrorHolder.READONLY; - import java.util.Arrays; import java.util.List; +import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeKind; +import checkers.inference.model.ExistentialVariableSlot; +import checkers.inference.model.Slot; +import checkers.inference.qual.VarAnnot; +import com.sun.tools.javac.code.Symbol; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; @@ -31,7 +31,10 @@ import checkers.inference.model.ConstraintManager; import checkers.inference.model.VariableSlot; import checkers.inference.model.tree.ArtificialExtendsBoundTree; -import pico.typecheck.PICOTypeUtil; +import org.checkerframework.javacutil.TypesUtils; +import pico.common.PICOTypeUtil; + +import static pico.typecheck.PICOAnnotationMirrorHolder.*; public class PICOVariableAnnotator extends VariableAnnotator { @@ -42,101 +45,166 @@ public PICOVariableAnnotator(InferenceAnnotatedTypeFactory typeFactory, Annotate super(typeFactory, realTypeFactory, realChecker, slotManager, constraintManager); } - @Override - protected void handleClassDeclaration(AnnotatedDeclaredType classType, ClassTree classTree) { - super.handleClassDeclaration(classType, classTree); - int interfaceIndex = 1; - for(Tree implementsTree : classTree.getImplementsClause()) { - final AnnotatedTypeMirror implementsType = inferenceTypeFactory.getAnnotatedTypeFromTypeTree(implementsTree); - AnnotatedTypeMirror supertype = classType.directSuperTypes().get(interfaceIndex); - assert supertype.getUnderlyingType() == implementsType.getUnderlyingType(); - visit(supertype, implementsTree); - interfaceIndex++; - } - } +// @Override +// protected void handleClassDeclaration(AnnotatedDeclaredType classType, ClassTree classTree) { +// super.handleClassDeclaration(classType, classTree); +// int interfaceIndex = 1; +// for(Tree implementsTree : classTree.getImplementsClause()) { +// final AnnotatedTypeMirror implementsType = inferenceTypeFactory.getAnnotatedTypeFromTypeTree(implementsTree); +// AnnotatedTypeMirror supertype = classType.directSupertypes().get(interfaceIndex); +// assert supertype.getUnderlyingType() == implementsType.getUnderlyingType(); +// visit(supertype, implementsTree); +// interfaceIndex++; +// } +// } + +// @Override +// protected void handleClassDeclarationBound(AnnotatedDeclaredType classType) { +// TypeElement classElement = (TypeElement) classType.getUnderlyingType().asElement(); +// if (classDeclAnnos.containsKey(classElement)) { +// classType.addAnnotation(slotManager.getAnnotation(classDeclAnnos.get(classElement))); +// classType.addAnnotation(READONLY); +// return; +// } +// AnnotatedDeclaredType bound = inferenceTypeFactory.fromElement(classElement); +// +// VariableSlot boundSlot; +// +// // Insert @Immutable VarAnnot directly to enum bound +//// if (PICOTypeUtil.isEnumOrEnumConstant(bound)) { +//// boundSlot = slotManager.createConstantSlot(IMMUTABLE); +//// classType.addAnnotation(slotManager.getAnnotation(boundSlot)); +//// classDeclAnnos.put(classElement, boundSlot); +//// return; +//// } +// +// Tree classTree = inferenceTypeFactory.declarationFromElement(classElement); +// if (classTree != null) { +// // Have source tree +// if (bound.isAnnotatedInHierarchy(READONLY)) { +// // Have bound annotation -> convert to equivalent ConstantSlot +// boundSlot = slotManager.createConstantSlot(bound.getAnnotationInHierarchy(READONLY)); +// } else { +// // No existing annotation -> create new VariableSlot +// boundSlot = createVariable(treeToLocation(classTree)); +// } +// } else { +// // No source tree: bytecode classes +// if (bound.isAnnotatedInHierarchy(READONLY)) { +// // Have bound annotation in stub file +// boundSlot = slotManager.createConstantSlot(bound.getAnnotationInHierarchy(READONLY)); +// } else { +// // No stub file +// if (PICOTypeUtil.isImplicitlyImmutableType(classType)) { +// // Implicitly immutable +// boundSlot = slotManager.createConstantSlot(IMMUTABLE); +// } else { +// // None of the above applies: use conservative @Mutable +// boundSlot = slotManager.createConstantSlot(MUTABLE); +// } +// } +// } +// classType.addAnnotation(slotManager.getAnnotation(boundSlot)); +// classDeclAnnos.put(classElement, boundSlot); +// } @Override - protected void handleClassDeclarationBound(AnnotatedDeclaredType classType) { - TypeElement classElement = (TypeElement) classType.getUnderlyingType().asElement(); - if (classDeclAnnos.containsKey(classElement)) { - classType.addAnnotation(slotManager.getAnnotation(classDeclAnnos.get(classElement))); - classType.addAnnotation(READONLY); - return; - } - AnnotatedDeclaredType bound = inferenceTypeFactory.fromElement(classElement); - - VariableSlot boundSlot; - - // Insert @Immutable VarAnnot directly to enum bound - if (PICOTypeUtil.isEnumOrEnumConstant(bound)) { - boundSlot = createConstant(IMMUTABLE); - classType.addAnnotation(slotManager.getAnnotation(boundSlot)); - classDeclAnnos.put(classElement, boundSlot); - return; - } - - Tree classTree = inferenceTypeFactory.declarationFromElement(classElement); - if (classTree != null) { - // Have source tree - if (bound.isAnnotatedInHierarchy(READONLY)) { - // Have bound annotation -> convert to equivalent ConstantSlot - boundSlot = createConstant(bound.getAnnotationInHierarchy(READONLY)); - } else { - // No existing annotation -> create new VariableSlot - boundSlot = createVariable(treeToLocation(classTree)); + protected Slot getOrCreateDeclBound(AnnotatedDeclaredType type) { + TypeElement classDecl = (TypeElement) type.getUnderlyingType().asElement(); + + AnnotationMirror declSlot = getClassDeclVarAnnot(classDecl); + if (declSlot == null) { + // if a explicit annotation presents on the class DECL, use that directly + if (type.isDeclaration() && type.hasAnnotationInHierarchy(READONLY) && !type.hasAnnotation(READONLY)) { + Slot constantSlot = slotManager.getSlot(type.getAnnotationInHierarchy(READONLY)); +// TypeElement classDecl = (TypeElement) type.getUnderlyingType().asElement(); + super.getOrCreateDeclBound(type); +// // avoid duplicate annos +// type.removeAnnotationInHierarchy(READONLY); + return constantSlot; + } else if (PICOTypeUtil.isEnumOrEnumConstant(type)) { + return slotManager.createConstantSlot(IMMUTABLE); } - } else { - // No source tree: bytecode classes - if (bound.isAnnotatedInHierarchy(READONLY)) { - // Have bound annotation in stub file - boundSlot = createConstant(bound.getAnnotationInHierarchy(READONLY)); - } else { - // No stub file - if (PICOTypeUtil.isImplicitlyImmutableType(classType)) { - // Implicitly immutable - boundSlot = createConstant(IMMUTABLE); - } else { - // None of the above applies: use conservative @Mutable - boundSlot = createConstant(MUTABLE); + + // new class tree of an anonymous class is always visited before (enclosing tree). + // slot should be generated then. + // use that slot and avoid generating a new slot. + // push this change to inference IFF the slot on new class have same requirement with class bound + // e.g. existence slot on new class tree? + if (TypesUtils.isAnonymous(type.getUnderlyingType())) { + if (type.hasAnnotation(VarAnnot.class)) { + return slotManager.getSlot(type.getAnnotation(VarAnnot.class)); } } } - classType.addAnnotation(slotManager.getAnnotation(boundSlot)); - classDeclAnnos.put(classElement, boundSlot); + return super.getOrCreateDeclBound(type); } - // Don't generate subtype constraint between use type and bound type - @Override - protected void handleInstantiationConstraint(AnnotatedTypeMirror.AnnotatedDeclaredType adt, VariableSlot instantiationSlot, Tree tree) { - return; - } +// @Override +// protected void handleExplicitExtends(Tree extendsTree) { +// // PICO cannot use base extends handling: not simply subtype relationship because of RDM +// // Constraints already generated in processClassTree +// } @Override - protected VariableSlot addPrimaryVariable(AnnotatedTypeMirror atm, Tree tree) { - if (PICOTypeUtil.isEnumOrEnumConstant(atm)) { - // Don't add new VarAnnot to type use of enum type - PICOTypeUtil.applyConstant(atm, IMMUTABLE); + public void storeElementType(Element element, AnnotatedTypeMirror atm) { + // this method is override the behavior of super.handleClassDeclaration before storing + // find a better way + + Slot slot = slotManager.getSlot(atm); + // do not use potential slot generated on the class decl annotation + // PICO always have a annotation on the class bound, so Existential should always exist + // TODO make VariableAnnotator::getOrCreateDeclBound protected and override that instead of this method + if (element instanceof Symbol.ClassSymbol && slot instanceof ExistentialVariableSlot) { + AnnotationMirror potential = slotManager.getAnnotation(((ExistentialVariableSlot) slot).getPotentialSlot()); + atm.replaceAnnotation(potential); } - return super.addPrimaryVariable(atm, tree); + + // If an explicit bound exists, the annotator will still place a constant slot on the bound, + // which will considered invalid by CF. + // Maybe not putting an anno at all during bound slot generation would be better? + if (atm.hasAnnotation(VarAnnot.class) && atm.hasAnnotationInHierarchy(READONLY)) { + atm.removeAnnotationInHierarchy(READONLY); + } + super.storeElementType(element, atm); } + // Don't generate subtype constraint between use type and bound type +// @Override +// protected void handleInstantiationConstraint(AnnotatedTypeMirror.AnnotatedDeclaredType adt, VariableSlot instantiationSlot, Tree tree) { +// return; +// } + +// @Override +// protected VariableSlot addPrimaryVariable(AnnotatedTypeMirror atm, Tree tree) { +//// if (PICOTypeUtil.isEnumOrEnumConstant(atm)) { +//// // Don't add new VarAnnot to type use of enum type +//// PICOTypeUtil.applyConstant(atm, IMMUTABLE); +//// } +// if (atm instanceof AnnotatedTypeMirror.AnnotatedNullType) { +// PICOTypeUtil.applyConstant(atm, BOTTOM); +// } +// return super.addPrimaryVariable(atm, tree); +// } + // Generates inequality constraint between every strict VariableSlot and @Bottom so that @Bottom is not inserted // back to source code, but can be within the internal state because of dataflow refinement - @Override - protected VariableSlot createVariable(AnnotationLocation location) { - VariableSlot varSlot = super.createVariable(location); - // Forbid any explicit use of @Bottom to be inserted back to source code(no VariableSlot instance is inferred - // @Bottom) - if (generateBottomInequality) { - constraintManager.addInequalityConstraint(varSlot, slotManager.createConstantSlot(BOTTOM)); - } - return varSlot; - } +// @Override +// protected VariableSlot createVariable(AnnotationLocation location) { +// VariableSlot varSlot = super.createVariable(location); +// // Forbid any explicit use of @Bottom to be inserted back to source code(no VariableSlot instance is inferred +// // @Bottom) +// if (generateBottomInequality) { +// constraintManager.addInequalityConstraint(varSlot, slotManager.createConstantSlot(BOTTOM)); +// constraintManager.addInequalityConstraint(varSlot, slotManager.createConstantSlot(POLY_MUTABLE)); +// } +// return varSlot; +// } // Copied from super implementation @Override protected boolean handleWasRawDeclaredTypes(AnnotatedDeclaredType adt) { - if (adt.wasRaw() && adt.getTypeArguments().size() != 0) { + if (adt.isUnderlyingTypeRaw() && adt.getTypeArguments().size() != 0) { // the type arguments should be wildcards AND if I get the real type of "tree" // it corresponds to the declaration of adt.getUnderlyingType Element declarationEle = adt.getUnderlyingType().asElement(); @@ -173,58 +241,70 @@ protected boolean handleWasRawDeclaredTypes(AnnotatedDeclaredType adt) { } } - @Override - public Void visitWildcard(AnnotatedTypeMirror.AnnotatedWildcardType wildcardType, Tree tree) { - if (!(tree instanceof WildcardTree)) { - if (tree instanceof AnnotatedTypeTree) { - tree = ((AnnotatedTypeTree) tree).getUnderlyingType(); - } - if (!(tree instanceof WildcardTree)) { - throw new IllegalArgumentException("Wildcard type ( " + wildcardType + " ) associated " + - "with non-WildcardTree ( " + tree + " ) "); - } - } - - final WildcardTree wildcardTree = (WildcardTree) tree; - final Tree.Kind wildcardKind = wildcardTree.getKind(); - if (wildcardKind == Tree.Kind.UNBOUNDED_WILDCARD) { - boolean prev = generateBottomInequality; - generateBottomInequality = false; - // Visit super bound, use the wild card type tree to represents the superbound. - addPrimaryVariable(wildcardType.getSuperBound(), tree); - generateBottomInequality = prev; - - // Visit extend bound, construct an artificial extends bound tree to represent the extendbound. - ArtificialExtendsBoundTree artificialExtendsBoundTree = new ArtificialExtendsBoundTree(wildcardTree); - addPrimaryVariable(wildcardType.getExtendsBound(), artificialExtendsBoundTree); - - } else if (wildcardKind == Tree.Kind.EXTENDS_WILDCARD) { - boolean prev = generateBottomInequality; - generateBottomInequality = false; - addPrimaryVariable(wildcardType.getSuperBound(), tree); - generateBottomInequality = prev; - - visit(wildcardType.getExtendsBound(), ((WildcardTree) tree).getBound()); - - } else if (wildcardKind == Tree.Kind.SUPER_WILDCARD) { - addPrimaryVariable(wildcardType.getExtendsBound(), tree); - - boolean prev = generateBottomInequality; - generateBottomInequality = false; - visit(wildcardType.getSuperBound(), ((WildcardTree) tree).getBound()); - generateBottomInequality = prev; - } - - return null; - } +// @Override +// public Void visitWildcard(AnnotatedTypeMirror.AnnotatedWildcardType wildcardType, Tree tree) { +// if (!(tree instanceof WildcardTree)) { +// if (tree instanceof AnnotatedTypeTree) { +// tree = ((AnnotatedTypeTree) tree).getUnderlyingType(); +// } +// if (!(tree instanceof WildcardTree)) { +// throw new IllegalArgumentException("Wildcard type ( " + wildcardType + " ) associated " + +// "with non-WildcardTree ( " + tree + " ) "); +// } +// } +// +// final WildcardTree wildcardTree = (WildcardTree) tree; +// final Tree.Kind wildcardKind = wildcardTree.getKind(); +// if (wildcardKind == Tree.Kind.UNBOUNDED_WILDCARD) { +// boolean prev = generateBottomInequality; +// generateBottomInequality = false; +// // Visit super bound, use the wild card type tree to represents the superbound. +// addPrimaryVariable(wildcardType.getSuperBound(), tree); +// generateBottomInequality = prev; +// +// // Visit extend bound, construct an artificial extends bound tree to represent the extendbound. +// ArtificialExtendsBoundTree artificialExtendsBoundTree = new ArtificialExtendsBoundTree(wildcardTree); +// addPrimaryVariable(wildcardType.getExtendsBound(), artificialExtendsBoundTree); +// +// } else if (wildcardKind == Tree.Kind.EXTENDS_WILDCARD) { +// boolean prev = generateBottomInequality; +// generateBottomInequality = false; +// addPrimaryVariable(wildcardType.getSuperBound(), tree); +// generateBottomInequality = prev; +// +// visit(wildcardType.getExtendsBound(), ((WildcardTree) tree).getBound()); +// +// } else if (wildcardKind == Tree.Kind.SUPER_WILDCARD) { +// addPrimaryVariable(wildcardType.getExtendsBound(), tree); +// +// boolean prev = generateBottomInequality; +// generateBottomInequality = false; +// visit(wildcardType.getSuperBound(), ((WildcardTree) tree).getBound()); +// generateBottomInequality = prev; +// } +// +// return null; +// } @Override public void handleBinaryTree(AnnotatedTypeMirror atm, BinaryTree binaryTree) { - if (atm.isAnnotatedInHierarchy(varAnnot)) { + if (atm.hasAnnotationInHierarchy(inferenceTypeFactory.getVarAnnot())) { // Happens for binary trees whose atm is implicitly immutable and already handled by // PICOInferencePropagationTreeAnnotator return; } super.handleBinaryTree(atm, binaryTree); } + + public AnnotationMirror getClassDeclAnno(Element ele) { + return getClassDeclVarAnnot((TypeElement) ele); // todo: solved + } + + + @Override + protected void addDeclarationConstraints(Slot declSlot, Slot instanceSlot) { + // RDM-related constraints cannot use subtype. + // Necessary constraints added in visitor instead. + } + } diff --git a/src/main/java/pico/inference/jdk.astub b/src/main/java/pico/inference/jdk.astub new file mode 100644 index 0000000..21a1a9e --- /dev/null +++ b/src/main/java/pico/inference/jdk.astub @@ -0,0 +1,300 @@ +import qual.Mutable; +import qual.Immutable; +import qual.ReceiverDependantMutable; +import qual.Readonly; +import qual.ObjectIdentityMethod; +import java.util.Collection; + +package java.lang; + +@ReceiverDependantMutable +class Object { + @ReceiverDependantMutable Object(); + Class getClass(@Readonly Object this); + String toString(@Readonly Object this); + int hashCode(@Readonly Object this); + boolean equals(@Readonly Object this, @Readonly Object var1); + @ReceiverDependantMutable Object clone(@ReceiverDependantMutable Object this); + @ObjectIdentityMethod + final native Class getClass(); +} + +class String { + int length(@Immutable String this); + char charAt(@Immutable String this, int var1); + String replace(@Readonly CharSequence target, @Readonly CharSequence replacement); + boolean contains(@Readonly CharSequence s); + String substring(@Immutable String this, int var1); + String substring(@Immutable String this, int var1, int var2); + String toString(@Immutable String this); + boolean equals(@Immutable Object var1); + static String valueOf(@Readonly Object var0); + static String format(String var0, @Readonly Object @Readonly ... var1); + static String format(@Readonly Locale l, String format, @Readonly Object @Readonly ... var1); +} + +class StringBuilder { + StringBuilder append(@Readonly Object var1); +} + +class StringBuffer { + int length(@Readonly StringBuffer this); + int capacity(@Readonly StringBuffer this); + StringBuffer append(@Readonly Object obj); + String substring(@Readonly StringBuffer this, int start); + CharSequence subSequence(@Readonly StringBuffer this, int start, int end); + String substring(@Readonly StringBuffer this, int start, int end); + int indexOf(@Readonly StringBuffer this, String str); + int indexOf(@Readonly StringBuffer this, String str, int fromIndex); + int lastIndexOf(@Readonly StringBuffer this, String str); + int lastIndexOf(@Readonly StringBuffer this, String str, int fromIndex); +} + +@ReceiverDependantMutable +class Throwable { + String getMessage(@ReceiverDependantMutable Throwable this); + String getLocalizedMessage(@ReceiverDependantMutable Throwable this); + Throwable getCause(@ReceiverDependantMutable Throwable this); + void printStackTrace(@ReceiverDependantMutable Throwable this); + void printStackTrace(@ReceiverDependantMutable Throwable this, PrintStream var1); + void printStackTrace(@ReceiverDependantMutable Throwable this, Throwable.PrintStreamOrWriter var1); +} + +@ReceiverDependantMutable +interface CharSequence { + int length(@Readonly CharSequence this); + char charAt(@Readonly CharSequence this, int index); + CharSequence subSequence(@Readonly CharSequence this, int start, int end); + public default IntStream chars(@Readonly CharSequence this); + public default IntStream codePoints(@Readonly CharSequence this); +} + +@ReceiverDependantMutable +class RuntimeException { + @ReceiverDependantMutable RuntimeException(@Readonly Throwable var1); + @ReceiverDependantMutable RuntimeException(String var1, @Readonly Throwable var2, boolean var3, boolean var4); +} + +@ReceiverDependantMutable +class IndexOutOfBoundsException {} + +@Immutable +class Enum> { + @Immutable Enum(String name, int ordinal); + int ordinal(@Immutable Enum this); +} + +@ReceiverDependantMutable +interface Cloneable {} + +@ReceiverDependantMutable +interface Comparable {} + +package java.util; + +@ReceiverDependantMutable +class Properties { + @Readonly Object put(@Immutable Object key, @Readonly Object value); +} + +interface Iterator {} + +@ReceiverDependantMutable +class Date { + @ReceiverDependantMutable Date(); + @ReceiverDependantMutable Date(long var1); + int getHours(@ReceiverDependantMutable Date this); +} + +@ReceiverDependantMutable +interface Collection { + boolean contains(@Readonly Collection this, @Readonly Object o); +} + +@ReceiverDependantMutable +public abstract class AbstractCollection implements Collection { + public abstract int size(@Readonly AbstractCollection this); +} + +@ReceiverDependantMutable +class ArrayList { + @ReceiverDependantMutable ArrayList(); + @ReceiverDependantMutable ArrayList(@Readonly Collection var1); + boolean add(E var1); + boolean addAll(@Readonly Collection c); + E get(@Readonly ArrayList this, int index); + int size(@Readonly ArrayList this); + boolean isEmpty(@Readonly ArrayList this); + boolean contains(@Readonly ArrayList this, @Readonly Object o); + int indexOf(@Readonly ArrayList this, @Readonly Object o); + int lastIndexOf(@Readonly ArrayList this, @Readonly Object o); + Iterator iterator(@Readonly ArrayList this); +} + +@ReceiverDependantMutable +interface List { + int size(@Readonly List this); + boolean isEmpty(@Readonly List this); + Iterator iterator(@Readonly List this); + Object[] toArray(@Readonly List this); + T[] toArray(@Readonly List this, T[] a); + boolean containsAll(@Readonly List this, @Readonly Collection c); + E get(@Readonly List this, int index); + boolean contains(@Readonly List this, @Readonly Object o); + boolean remove(@Readonly Object o); + boolean removeAll(@Readonly Collection c); + boolean addAll(@Readonly Collection c); + boolean addAll(int index, @Readonly Collection c); + int indexOf(@Readonly List this, @Readonly Object o); + int lastIndexOf(@Readonly List this, @Readonly Object o); + ListIterator listIterator(@Readonly List this); + ListIterator listIterator(@Readonly List this, int index); +} + +@ReceiverDependantMutable +class AbstractList { + @ReceiverDependantMutable AbstractList(); + void add(@Mutable AbstractList this, int var1, E var2); +} + +@ReceiverDependantMutable +interface Set { + int size(@Readonly Set this); + boolean isEmpty(@Readonly Set this); + boolean contains(@Readonly Set this, @Readonly Object var1); + Iterator iterator(@Readonly Set this); + Object[] toArray(@Readonly Set this); + T[] toArray(@Readonly Set this, T[] a); + boolean containsAll(@Readonly Set this, @Readonly Collection c); + boolean remove(@Readonly Object o); + boolean addAll(@Readonly Collection c); +} + +@ReceiverDependantMutable +class HashSet { + @ReceiverDependantMutable HashSet(); + @ReceiverDependantMutable HashSet(@Readonly Collection var1); + boolean contains(@Readonly HashSet this, @Readonly Object var1); + boolean remove(@Readonly Object var1); +} + +@ReceiverDependantMutable +interface Map { + int size(@Readonly Map this); + boolean isEmpty(@Readonly Map this); + boolean containsKey(@Readonly Map this, @Readonly Object var1); + boolean containsValue(@Readonly Map this, @Readonly Object value); + V get(@Readonly Map this, @Readonly Object var1); + V remove(@Readonly Object key); + void putAll(@Readonly Map m); + Set keySet(@Readonly Map this); + Collection values(@Readonly Map this); + Set> entrySet(@Readonly Map this); +} + +@ReceiverDependantMutable +class HashMap { + @ReceiverDependantMutable HashMap(); + @ReceiverDependantMutable HashMap(@Readonly Map var1); + V get(@Readonly HashMap this, @Readonly Object key); + boolean containsKey(@Readonly HashMap this, @Readonly Object key); + boolean containsValue(@Readonly HashMap this, @Readonly Object value); +} + +class Collections { + static @Immutable List unmodifiableList(@Readonly List list); +} + +class StringJoiner { + StringJoiner(@Readonly CharSequence delimiter); + StringJoiner(@Readonly CharSequence delimiter, @Readonly CharSequence prefix, @Readonly CharSequence suffix); + StringJoiner add(@Readonly CharSequence newElement); +} + +class Arrays { + static @Immutable List asList(T @Readonly ... var0); + static String toString(int @Readonly [] var0); + static boolean equals(float @Readonly [] var0, float @Readonly [] var1); + static boolean equals(double @Readonly [] var0, double @Readonly [] var1); + static T[] copyOf(T @Readonly [] original, int newLength); +} + +class Objects { + static int hashCode(@Readonly Object o); + static boolean equals(@Readonly Object a, @Readonly Object b); +} + +@ReceiverDependantMutable +class Stack { + E peek(@ReceiverDependantMutable Stack this); + boolean empty(@ReceiverDependantMutable Stack this); +} + +@ReceiverDependantMutable +class Vector { + boolean isEmpty(@Readonly Vector this); +} + +@ReceiverDependantMutable +class Hashtable { + V get(@Readonly Hashtable this, @Readonly Object key); + boolean containsKey(@Readonly Hashtable this, @Readonly Object key); +} + +package java.util.logging; +class Logger { + void log(@Readonly Level level, String msg, @Readonly Throwable thrown); +} + +package java.util.regex; +class Pattern { + Matcher matcher(@Readonly CharSequence input); + static boolean matches(String regex, @Readonly CharSequence input); + String[] split(@Readonly CharSequence input, int limit); + String[] split(@Readonly CharSequence input); + static final int countChars(@Readonly CharSequence seq, int index, int lengthInCodePoints); + static final int countCodePoints(@Readonly CharSequence seq); +} + +package java.io; + +@ReceiverDependantMutable +class PrintStream { + void print(@ReceiverDependantMutable PrintStream this, String var1); + PrintStream printf(@ReceiverDependantMutable PrintStream this, String var1, @Readonly Object @Readonly ... var2); + PrintStream format(String format, @Readonly Object @Readonly ... args); +} + +@ReceiverDependantMutable +class PrintWriter { + PrintWriter printf(@ReceiverDependantMutable PrintWriter this, String var1, @Readonly Object @Readonly ... var2); +} + +@ReceiverDependantMutable +class File { + @ReceiverDependantMutable File(@Readonly File parent, String child); + boolean isFile(@Readonly File this); + String[] list(@Readonly File this); + String getPath(@Readonly File this); + long length(@Readonly File this); + String getName(@Readonly File this); +} + +@ReceiverDependantMutable +class FileInputStream { + @ReceiverDependantMutable FileInputStream(@Readonly File file); +} + +class ObjectOutputStream { + void writeObject(@Readonly Object obj); +} + +@ReceiverDependantMutable +interface Serializable {} + +package java.awt; + +@ReceiverDependantMutable +class Container { + void add(@Readonly Component comp, @Readonly Object constraints); +} diff --git a/src/main/java/pico/inference/messages.properties b/src/main/java/pico/inference/messages.properties new file mode 100644 index 0000000..3979ead --- /dev/null +++ b/src/main/java/pico/inference/messages.properties @@ -0,0 +1,16 @@ +constructor.invocation.invalid=Cannot not instantiate type: %s out of constructor: %s +constructor.return.invalid=Invalid constructor return type: %s +method.receiver.incompatible=Incompatible method receiver: %s +class.bound.invalid=Invalid class bound: %s +subclass.bound.incompatible=Incompatible subclass bound: %s +subtype.constraint.violated=%s is not the subtype of %s +illegal.field.write=Cannot write field via receiver: %s +illegal.array.write=Cannot write array via receiver: %s +static.receiverdependantmutable.forbidden=%s is forbidden in static context +pico.new.invalid=Invalid new instance type: %s +field.polymutable.forbidden=Field %s cannot be @PolyMutable +one.assignability.invalid=Only one assignability qualifier is allowed on %s +object.identity.method.invocation.invalid=Cannot invoke non-object identity method %s from object identity context! +object.identity.field.access.invalid=Object identity context cannot reference non abstract state field %s! +object.identity.static.field.access.forbidden=Object identity context cannot reference static field %s! +bound.extends.incompatible=TEST diff --git a/src/main/java/pico/inference/solver/PICOCombineConstraintEncoder.java b/src/main/java/pico/inference/solver/PICOCombineConstraintEncoder.java index 21ac92e..a03ccae 100644 --- a/src/main/java/pico/inference/solver/PICOCombineConstraintEncoder.java +++ b/src/main/java/pico/inference/solver/PICOCombineConstraintEncoder.java @@ -2,6 +2,7 @@ import checkers.inference.model.ConstantSlot; import checkers.inference.model.VariableSlot; +import checkers.inference.model.CombVariableSlot; import checkers.inference.solver.backend.encoder.combine.CombineConstraintEncoder; import checkers.inference.solver.backend.maxsat.MathUtils; import checkers.inference.solver.backend.maxsat.VectorUtils; @@ -51,7 +52,7 @@ private boolean isReceiverDependantMutable(ConstantSlot cSlot) { } @Override - public VecInt[] encodeVariable_Variable(VariableSlot target, VariableSlot declared, VariableSlot result) { + public VecInt[] encodeVariable_Variable(VariableSlot target, VariableSlot declared, CombVariableSlot result) { List resultClauses = new ArrayList(); resultClauses.add(VectorUtils.asVec( -MathUtils.mapIdToMatrixEntry(declared.getId(), id(READONLY), lattice), @@ -89,7 +90,7 @@ public VecInt[] encodeVariable_Variable(VariableSlot target, VariableSlot declar } @Override - public VecInt[] encodeVariable_Constant(VariableSlot target, ConstantSlot declared, VariableSlot result) { + public VecInt[] encodeVariable_Constant(VariableSlot target, ConstantSlot declared, CombVariableSlot result) { List resultClauses = new ArrayList(); if (!isReceiverDependantMutable(declared)) { resultClauses.add(VectorUtils.asVec( @@ -116,7 +117,7 @@ public VecInt[] encodeVariable_Constant(VariableSlot target, ConstantSlot declar } @Override - public VecInt[] encodeConstant_Variable(ConstantSlot target, VariableSlot declared, VariableSlot result) { + public VecInt[] encodeConstant_Variable(ConstantSlot target, VariableSlot declared, CombVariableSlot result) { List resultClauses = new ArrayList(); resultClauses.add(VectorUtils.asVec( -MathUtils.mapIdToMatrixEntry(declared.getId(), id(READONLY), lattice), @@ -137,7 +138,7 @@ public VecInt[] encodeConstant_Variable(ConstantSlot target, VariableSlot declar } @Override - public VecInt[] encodeConstant_Constant(ConstantSlot target, ConstantSlot declared, VariableSlot result) { + public VecInt[] encodeConstant_Constant(ConstantSlot target, ConstantSlot declared, CombVariableSlot result) { List resultClauses = new ArrayList(); if (!isReceiverDependantMutable(declared)) { resultClauses.add(VectorUtils.asVec( diff --git a/src/main/java/pico/inference/solver/PICOSolverEngine.java b/src/main/java/pico/inference/solver/PICOSolverEngine.java index 213a809..5ff0d7d 100644 --- a/src/main/java/pico/inference/solver/PICOSolverEngine.java +++ b/src/main/java/pico/inference/solver/PICOSolverEngine.java @@ -1,6 +1,6 @@ package pico.inference.solver; -import checkers.inference.BaseInferenceResult; +import checkers.inference.DefaultInferenceResult; import checkers.inference.InferenceResult; import checkers.inference.model.Constraint; import checkers.inference.model.Slot; @@ -24,14 +24,14 @@ * to solve constraints */ public class PICOSolverEngine extends SolverEngine { - @Override - public InferenceResult solve(Map configuration, Collection slots, Collection constraints, QualifierHierarchy qualHierarchy, ProcessingEnvironment processingEnvironment) { - InferenceResult result= super.solve(configuration, slots, constraints, qualHierarchy, processingEnvironment); - if (collectStatistics && result.hasSolution()) { - writeInferenceResult("pico-inference-result.txt", ((BaseInferenceResult)result).inferredResults); - } - return result; - } +// @Override +// public InferenceResult solve(Map configuration, Collection slots, Collection constraints, QualifierHierarchy qualHierarchy, ProcessingEnvironment processingEnvironment) { +// InferenceResult result= super.solve(configuration, slots, constraints, qualHierarchy, processingEnvironment); +// if (collectStatistics && result.hasSolution()) { +// writeInferenceResult("pico-inference-result.txt", ((DefaultInferenceResult)result).varIdToAnnotation); +// } +// return result; +// } // TODO: default write into statistic.txt public static void writeInferenceResult(String filename, Map result) { String writePath = new File(new File("").getAbsolutePath()).toString() + File.separator + filename; diff --git a/src/main/java/pico/typecheck/ObjectIdentityMethodEnforcer.java b/src/main/java/pico/typecheck/ObjectIdentityMethodEnforcer.java index fb54dea..dd63d72 100644 --- a/src/main/java/pico/typecheck/ObjectIdentityMethodEnforcer.java +++ b/src/main/java/pico/typecheck/ObjectIdentityMethodEnforcer.java @@ -7,11 +7,10 @@ import com.sun.source.util.TreePath; import com.sun.source.util.TreePathScanner; import org.checkerframework.common.basetype.BaseTypeChecker; -import org.checkerframework.framework.source.Result; import org.checkerframework.framework.util.AnnotatedTypes; import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TreeUtils; -import qual.Assignable; +import pico.common.PICOTypeUtil; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; @@ -22,16 +21,16 @@ public class ObjectIdentityMethodEnforcer extends TreePathScanner { - private PICOAnnotatedTypeFactory typeFactory; + private PICONoInitAnnotatedTypeFactory typeFactory; private BaseTypeChecker checker; - private ObjectIdentityMethodEnforcer(PICOAnnotatedTypeFactory typeFactory, BaseTypeChecker checker) { + private ObjectIdentityMethodEnforcer(PICONoInitAnnotatedTypeFactory typeFactory, BaseTypeChecker checker) { this.typeFactory = typeFactory; this.checker = checker; } // Main entry - public static void check(TreePath statement, PICOAnnotatedTypeFactory typeFactory, BaseTypeChecker checker) { + public static void check(TreePath statement, PICONoInitAnnotatedTypeFactory typeFactory, BaseTypeChecker checker) { if (statement == null) return; ObjectIdentityMethodEnforcer asfchecker = new ObjectIdentityMethodEnforcer(typeFactory, checker); @@ -53,7 +52,7 @@ private void checkMethod(MethodInvocationTree node, Element elt) { if (!PICOTypeUtil.isObjectIdentityMethod((ExecutableElement) elt, typeFactory)) { // Report warning since invoked method is not only dependant on abstract state fields, but we // don't know whether this method invocation's result flows into the hashcode or not. - checker.report(Result.warning("object.identity.method.invocation.invalid", elt), node); + checker.reportWarning(node, "object.identity.method.invocation.invalid", elt); } } @@ -78,11 +77,11 @@ private void checkField(Tree node, Element elt) { } if (elt.getKind() == ElementKind.FIELD) { if (ElementUtils.isStatic(elt)) { - checker.report(Result.warning("object.identity.static.field.access.forbidden", elt), node); + checker.reportWarning(node, "object.identity.static.field.access.forbidden", elt); } else { if (!isInAbstractState(elt, typeFactory)) { // Report warning since accessed field is not within abstract state - checker.report(Result.warning("object.identity.field.access.invalid", elt), node); + checker.reportWarning(node, "object.identity.field.access.invalid", elt); } } } @@ -90,7 +89,7 @@ private void checkField(Tree node, Element elt) { // Deeply test if a field is in abstract state or not. For composite types: array component, // type arguments, upper bound of type parameter uses are also checked. - private boolean isInAbstractState(Element elt, PICOAnnotatedTypeFactory typeFactory) { + private boolean isInAbstractState(Element elt, PICONoInitAnnotatedTypeFactory typeFactory) { boolean in = true; if (PICOTypeUtil.isAssignableField(elt, typeFactory)) { in = false; diff --git a/src/main/java/pico/typecheck/PICOAnalysis.java b/src/main/java/pico/typecheck/PICOAnalysis.java deleted file mode 100644 index 54cecfa..0000000 --- a/src/main/java/pico/typecheck/PICOAnalysis.java +++ /dev/null @@ -1,40 +0,0 @@ -package pico.typecheck; - -import org.checkerframework.common.basetype.BaseTypeChecker; -import org.checkerframework.framework.flow.CFAbstractAnalysis; -import org.checkerframework.framework.flow.CFAbstractValue; -import org.checkerframework.javacutil.Pair; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; -import java.util.List; -import java.util.Set; - -/** - * Created by mier on 15/08/17. - */ -public class PICOAnalysis extends CFAbstractAnalysis { - - public PICOAnalysis(BaseTypeChecker checker, PICOAnnotatedTypeFactory factory, List> fieldValues) { - super(checker, factory, fieldValues); - } - - @Override - public PICOStore createEmptyStore(boolean sequentialSemantics) { - return new PICOStore(this, sequentialSemantics); - } - - @Override - public PICOStore createCopiedStore(PICOStore picoStore) { - return new PICOStore(this, picoStore); - } - - @Override - public PICOValue createAbstractValue(Set annotations, TypeMirror underlyingType) { - if (!CFAbstractValue.validateSet(annotations, underlyingType, qualifierHierarchy)) { - return null; - } - return new PICOValue(this, annotations, underlyingType); - } -} diff --git a/src/main/java/pico/typecheck/PICOAnnotatedTypeFactory.java b/src/main/java/pico/typecheck/PICOAnnotatedTypeFactory.java deleted file mode 100644 index 77bef15..0000000 --- a/src/main/java/pico/typecheck/PICOAnnotatedTypeFactory.java +++ /dev/null @@ -1,528 +0,0 @@ -package pico.typecheck; - -import static pico.typecheck.PICOAnnotationMirrorHolder.COMMITED; -import static pico.typecheck.PICOAnnotationMirrorHolder.IMMUTABLE; -import static pico.typecheck.PICOAnnotationMirrorHolder.MUTABLE; -import static pico.typecheck.PICOAnnotationMirrorHolder.POLY_MUTABLE; -import static pico.typecheck.PICOAnnotationMirrorHolder.READONLY; -import static pico.typecheck.PICOAnnotationMirrorHolder.SUBSTITUTABLE_POLY_MUTABLE; - -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; - -import org.checkerframework.checker.initialization.InitializationAnnotatedTypeFactory; -import org.checkerframework.checker.initialization.qual.FBCBottom; -import org.checkerframework.checker.initialization.qual.Initialized; -import org.checkerframework.checker.initialization.qual.UnderInitialization; -import org.checkerframework.checker.initialization.qual.UnknownInitialization; -import org.checkerframework.common.basetype.BaseTypeChecker; -import org.checkerframework.framework.qual.RelevantJavaTypes; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import org.checkerframework.framework.type.QualifierHierarchy; -import org.checkerframework.framework.type.ViewpointAdapter; -import org.checkerframework.framework.type.treeannotator.ImplicitsTreeAnnotator; -import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator; -import org.checkerframework.framework.type.treeannotator.PropagationTreeAnnotator; -import org.checkerframework.framework.type.treeannotator.TreeAnnotator; -import org.checkerframework.framework.type.typeannotator.ImplicitsTypeAnnotator; -import org.checkerframework.framework.type.typeannotator.IrrelevantTypeAnnotator; -import org.checkerframework.framework.type.typeannotator.ListTypeAnnotator; -import org.checkerframework.framework.type.typeannotator.PropagationTypeAnnotator; -import org.checkerframework.framework.type.typeannotator.TypeAnnotator; -import org.checkerframework.framework.util.MultiGraphQualifierHierarchy.MultiGraphFactory; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.ElementUtils; -import org.checkerframework.javacutil.Pair; -import org.checkerframework.javacutil.TreeUtils; - -import com.sun.source.tree.BinaryTree; -import com.sun.source.tree.ClassTree; -import com.sun.source.tree.ExpressionTree; -import com.sun.source.tree.IdentifierTree; -import com.sun.source.tree.MemberSelectTree; -import com.sun.source.tree.MethodTree; -import com.sun.source.tree.NewArrayTree; -import com.sun.source.tree.ParameterizedTypeTree; -import com.sun.source.tree.Tree; -import com.sun.source.tree.TypeCastTree; -import com.sun.source.tree.UnaryTree; -import com.sun.source.tree.VariableTree; - -import qual.Bottom; -import qual.Immutable; -import qual.Mutable; -import qual.PolyMutable; -import qual.Readonly; -import qual.ReceiverDependantMutable; -import qual.SubstitutablePolyMutable; - -/** - * AnnotatedTypeFactory for PICO. In addition to getting atms, it also propagates and applies mutability - * qualifiers correctly depending on AST locations(e.g. fields, binary trees) or methods(toString(), hashCode(), - * clone(), equals(Object o)) using TreeAnnotators and TypeAnnotators. It also applies implicits to method - * receiver that is not so by default in super implementation. - */ -//TODO Use @Immutable for classes that extends those predefined immutable classess like String or Number - // and explicitly annotated classes with @Immutable on its declaration -public class PICOAnnotatedTypeFactory extends InitializationAnnotatedTypeFactory { - - public PICOAnnotatedTypeFactory(BaseTypeChecker checker) { - super(checker, true); - postInit(); - addAliasedAnnotation(org.jmlspecs.annotation.Readonly.class, READONLY); - } - - @Override - protected Set> createSupportedTypeQualifiers() { - return new LinkedHashSet>( - Arrays.asList( - Readonly.class, - Mutable.class, - PolyMutable.class, - ReceiverDependantMutable.class, - SubstitutablePolyMutable.class, - Immutable.class, - Bottom.class, - Initialized.class, - UnderInitialization.class, - UnknownInitialization.class, - FBCBottom.class)); - } - - @Override - protected ViewpointAdapter createViewpointAdapter() { - return new PICOViewpointAdapter(this); - } - - /**Annotators are executed by the added order. Same for Type Annotator*/ - @Override - protected TreeAnnotator createTreeAnnotator() { - return new ListTreeAnnotator( - new PICOPropagationTreeAnnotator(this), - new ImplicitsTreeAnnotator(this), - new CommitmentTreeAnnotator(this), - new PICOTreeAnnotator(this)); - } - - // TODO Refactor super class to remove this duplicate code - @Override - protected TypeAnnotator createTypeAnnotator() { - /*Copied code start*/ - List typeAnnotators = new ArrayList<>(); - RelevantJavaTypes relevantJavaTypes = - checker.getClass().getAnnotation(RelevantJavaTypes.class); - if (relevantJavaTypes != null) { - Class[] classes = relevantJavaTypes.value(); - // Must be first in order to annotated all irrelevant types that are not explicilty - // annotated. - typeAnnotators.add( - new IrrelevantTypeAnnotator( - this, getQualifierHierarchy().getTopAnnotations(), classes)); - } - typeAnnotators.add(new PropagationTypeAnnotator(this)); - /*Copied code ends*/ - // Adding order is important here. Because internally type annotators are using addMissingAnnotations() - // method, so if one annotator already applied the annotations, the others won't apply twice at the - // same location - typeAnnotators.add(new PICOTypeAnnotator(this)); - typeAnnotators.add(new PICOImplicitsTypeAnnotator(this)); - typeAnnotators.add(new CommitmentTypeAnnotator(this)); - return new ListTypeAnnotator(typeAnnotators); - } - - @Override - public QualifierHierarchy createQualifierHierarchy(MultiGraphFactory factory) { - return new PICOQualifierHierarchy(factory, (Object[]) null); - } - - /**Just to transfer the method from super class to package*/ - @Override - protected boolean isInitializationAnnotation(AnnotationMirror anno) { - return super.isInitializationAnnotation(anno); - } - - @Override - public AnnotationMirror getFieldInvariantAnnotation() { - return IMMUTABLE; - } - - /**This affects what fields pico warns not initialized in constructors*/ - @Override - protected boolean hasFieldInvariantAnnotation(AnnotatedTypeMirror type, VariableElement fieldElement) { - // This affects which fields should be guaranteed to be initialized: - // Fields of any immutability should be initialized. - return !PICOTypeUtil.isAssignableField(fieldElement, this); - } - - /**Forbid applying top annotations to type variables if they are used on local variables*/ - @Override - public boolean getShouldDefaultTypeVarLocals() { - return false; - } - - /**This covers the case when static fields are used and constructor is accessed as an element(regarding to - * applying @Immutable on type declaration to constructor return type).*/ - @Override - public void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) { - PICOTypeUtil.addDefaultForField(this, type, elt); - PICOTypeUtil.defaultConstructorReturnToClassBound(this, elt, type); - PICOTypeUtil.applyImmutableToEnumAndEnumConstant(type); - super.addComputedTypeAnnotations(elt, type); - } - - @Override - protected void annotateInheritedFromClass(AnnotatedTypeMirror type, Set fromClass) { - // If interitted from class element is @Mutable or @Immutable, then apply this annotation to the usage type - if (fromClass.contains(MUTABLE) || fromClass.contains(IMMUTABLE)) { - super.annotateInheritedFromClass(type, fromClass); - return; - } - // If interitted from class element is @ReceiverDependantMutable, then don't apply and wait for @Mutable - // (default qualifier in hierarchy to be applied to the usage type). This is to avoid having @ReceiverDependantMutable - // on type usages as a default behaviour. By default, @Mutable is better used as the type for usages that - // don't have explicit annotation. - return;// Don't add annotations from class element - } - - /**This method gets lhs WITH flow sensitive refinement*/ - // TODO Should refactor super class to avoid too much duplicate code. - // This method is pretty hacky right now. - @Override - public AnnotatedTypeMirror getAnnotatedTypeLhs(Tree lhsTree) { - boolean oldComputingAnnotatedTypeMirrorOfLHS = computingAnnotatedTypeMirrorOfLHS; - computingAnnotatedTypeMirrorOfLHS = true; - - AnnotatedTypeMirror result = null; - boolean oldShouldCache = shouldCache; - // Don't cache the result because getAnnotatedType(lhsTree) could - // be called from elsewhere and would expect flow-sensitive type refinements. - shouldCache = false; - switch (lhsTree.getKind()) { - case VARIABLE: - case IDENTIFIER: - case MEMBER_SELECT: - case ARRAY_ACCESS: - result = getAnnotatedType(lhsTree); - break; - default: - if (TreeUtils.isTypeTree(lhsTree)) { - // lhsTree is a type tree at the pseudo assignment of a returned expression to declared return type. - result = getAnnotatedType(lhsTree); - } else { - throw new BugInCF( - "GenericAnnotatedTypeFactory: Unexpected tree passed to getAnnotatedTypeLhs. " - + "lhsTree: " - + lhsTree - + " Tree.Kind: " - + lhsTree.getKind()); - } - } - shouldCache = oldShouldCache; - - computingAnnotatedTypeMirrorOfLHS = oldComputingAnnotatedTypeMirrorOfLHS; - return result; - } - - /**Handles invoking static methods with polymutable on its declaration*/ - @Override - public ParameterizedMethodType methodFromUse(ExpressionTree tree, ExecutableElement methodElt, AnnotatedTypeMirror receiverType) { - ParameterizedMethodType pair = super.methodFromUse(tree, methodElt, receiverType); - // We want to replace polymutable with substitutablepolymutable when we invoke static methods - if (ElementUtils.isStatic(methodElt)) { - AnnotatedExecutableType methodType = pair.methodType; - AnnotatedTypeMirror returnType = methodType.getReturnType(); - if (returnType.hasAnnotation(POLY_MUTABLE)) { - // Only substitute polymutable but not other qualifiers! Missing the if statement - // caused bugs before! - returnType.replaceAnnotation(SUBSTITUTABLE_POLY_MUTABLE); - } - List parameterTypes = methodType.getParameterTypes(); - for (AnnotatedTypeMirror p : parameterTypes) { - if (returnType.hasAnnotation(POLY_MUTABLE)) { - p.replaceAnnotation(SUBSTITUTABLE_POLY_MUTABLE); - } - } - } - return pair; - } - - protected class PICOQualifierHierarchy extends InitializationQualifierHierarchy { - - public PICOQualifierHierarchy(MultiGraphFactory f, Object[] arg) { - super(f, arg); - } - - @Override - public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) { - if (isInitializationAnnotation(subAnno) || isInitializationAnnotation(superAnno)) { - return this.isSubtypeInitialization(subAnno, superAnno); - } - return super.isSubtype(subAnno, superAnno); - } - - @Override - public AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2) { - if (isInitializationAnnotation(a1) || isInitializationAnnotation(a2)) { - return this.leastUpperBoundInitialization(a1, a2); - } - return super.leastUpperBound(a1, a2); - } - } - - /**Tree Annotators*/ - public static class PICOPropagationTreeAnnotator extends PropagationTreeAnnotator { - public PICOPropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - } - - // TODO This is very ugly. Why is array component type from lhs propagates to rhs?! - @Override - public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { - // Below is copied from super - assert type.getKind() == TypeKind.ARRAY - : "PropagationTreeAnnotator.visitNewArray: should be an array type"; - - AnnotatedTypeMirror componentType = ((AnnotatedTypeMirror.AnnotatedArrayType) type).getComponentType(); - - Collection prev = null; - if (tree.getInitializers() != null && tree.getInitializers().size() != 0) { - // We have initializers, either with or without an array type. - - for (ExpressionTree init : tree.getInitializers()) { - AnnotatedTypeMirror initType = atypeFactory.getAnnotatedType(init); - // initType might be a typeVariable, so use effectiveAnnotations. - Collection annos = initType.getEffectiveAnnotations(); - - prev = (prev == null) ? annos : atypeFactory.getQualifierHierarchy().leastUpperBounds(prev, annos); - } - } else { - prev = componentType.getAnnotations(); - } - - assert prev != null - : "PropagationTreeAnnotator.visitNewArray: violated assumption about qualifiers"; - - Pair context = - atypeFactory.getVisitorState().getAssignmentContext(); - Collection post; - - if (context != null - && context.second != null - && context.second instanceof AnnotatedTypeMirror.AnnotatedArrayType) { - AnnotatedTypeMirror contextComponentType = - ((AnnotatedTypeMirror.AnnotatedArrayType) context.second).getComponentType(); - // Only compare the qualifiers that existed in the array type - // Defaulting wasn't performed yet, so prev might have fewer qualifiers than - // contextComponentType, which would cause a failure. - // TODO: better solution? - boolean prevIsSubtype = true; - for (AnnotationMirror am : prev) { - if (contextComponentType.isAnnotatedInHierarchy(am) - && !atypeFactory.getQualifierHierarchy().isSubtype( - am, contextComponentType.getAnnotationInHierarchy(am))) { - prevIsSubtype = false; - } - } - // TODO: checking conformance of component kinds is a basic sanity check - // It fails for array initializer expressions. Those should be handled nicer. - if (contextComponentType.getKind() == componentType.getKind() - && (prev.isEmpty() - || (!contextComponentType.getAnnotations().isEmpty() - && prevIsSubtype))) { - post = contextComponentType.getAnnotations(); - } else { - // The type of the array initializers is incompatible with the - // context type! - // Somebody else will complain. - post = prev; - } - } else { - // No context is available - simply use what we have. - post = prev; - } - - // Below line is the only difference from super implementation - applyImmutableIfImplicitlyImmutable(componentType); - // Above line is the only difference from super implementation - componentType.addMissingAnnotations(post); - - return null; - // Above is copied from super - } - - /**Add immutable to the result type of a binary operation if the result type is implicitly immutable*/ - @Override - public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) { - applyImmutableIfImplicitlyImmutable(type);// Usually there isn't existing annotation on binary trees, but to be safe, run it first - super.visitBinary(node, type); - // NullnessPropagationTreeAnnotator says result type of binary tree is always @Initialized. So replace it - // with COMMITED here. - applyCommitedIfSupported(atypeFactory, type); - return null; - } - - @Override - public Void visitUnary(UnaryTree node, AnnotatedTypeMirror type) { - super.visitUnary(node, type); - // Same reason as above - applyCommitedIfSupported(atypeFactory, type); - return null; - } - - /**Add immutable to the result type of a cast if the result type is implicitly immutable*/ - @Override - public Void visitTypeCast(TypeCastTree node, AnnotatedTypeMirror type) { - applyImmutableIfImplicitlyImmutable(type);// Must run before calling super method to respect existing annotation - return super.visitTypeCast(node, type); - } - - /**Because TreeAnnotator runs before ImplicitsTypeAnnotator, implicitly immutable types are not guaranteed - to always have immutable annotation. If this happens, we manually add immutable to type. We use - addMissingAnnotations because we want to respect existing annotation on type*/ - private void applyImmutableIfImplicitlyImmutable(AnnotatedTypeMirror type) { - if (PICOTypeUtil.isImplicitlyImmutableType(type)) { - type.addMissingAnnotations(new HashSet<>(Arrays.asList(IMMUTABLE))); - } - } - - private void applyCommitedIfSupported(AnnotatedTypeFactory annotatedTypeFactory, AnnotatedTypeMirror type) { - if (annotatedTypeFactory.isSupportedQualifier(COMMITED)) { - type.replaceAnnotation(COMMITED); - } - } - } - - /**Apply defaults for static fields with non-implicitly immutable types*/ - public static class PICOTreeAnnotator extends TreeAnnotator { - public PICOTreeAnnotator(AnnotatedTypeFactory atypeFactory) { - super(atypeFactory); - } - - @Override - public Void visitIdentifier(IdentifierTree node, AnnotatedTypeMirror annotatedTypeMirror) { - PICOTypeUtil.dragAnnotationFromBoundToExtendsAndImplements(node, annotatedTypeMirror, atypeFactory); - return super.visitIdentifier(node, annotatedTypeMirror); - } - - @Override - public Void visitMemberSelect(MemberSelectTree node, AnnotatedTypeMirror annotatedTypeMirror) { - PICOTypeUtil.dragAnnotationFromBoundToExtendsAndImplements(node, annotatedTypeMirror, atypeFactory); - return super.visitMemberSelect(node, annotatedTypeMirror); - } - - @Override - public Void visitParameterizedType(ParameterizedTypeTree node, AnnotatedTypeMirror annotatedTypeMirror) { - PICOTypeUtil.dragAnnotationFromBoundToExtendsAndImplements(node, annotatedTypeMirror, atypeFactory); - return super.visitParameterizedType(node, annotatedTypeMirror); - } - - @Override - public Void visitClass(ClassTree node, AnnotatedTypeMirror annotatedTypeMirror) { - // Apply @Immutable to enum element's bound - PICOTypeUtil.applyImmutableToEnumAndEnumConstant(annotatedTypeMirror); - return super.visitClass(node, annotatedTypeMirror); - } - - // This adds @Immutable annotation to constructor return type if type declaration has @Immutable when the - // constructor is accessed as a tree. - @Override - public Void visitMethod(MethodTree node, AnnotatedTypeMirror p) { - Element element = TreeUtils.elementFromDeclaration(node); - // See: https://github.com/opprop/checker-framework/blob/master/framework/src/org/checkerframework/framework/type/AnnotatedTypeFactory.java#L1593 - // for why constructor return is not applied class bound annotation - PICOTypeUtil.defaultConstructorReturnToClassBound(atypeFactory, element, p); - return super.visitMethod(node, p); - } - - /**This covers the declaration of static fields*/ - @Override - public Void visitVariable(VariableTree node, AnnotatedTypeMirror annotatedTypeMirror) { - VariableElement element = TreeUtils.elementFromDeclaration(node); - PICOTypeUtil.addDefaultForField(atypeFactory, annotatedTypeMirror, element); - PICOTypeUtil.applyImmutableToEnumAndEnumConstant(annotatedTypeMirror); - return super.visitVariable(node, annotatedTypeMirror); - } - } - - /**Type Annotators*/ - public static class PICOTypeAnnotator extends TypeAnnotator { - - public PICOTypeAnnotator(AnnotatedTypeFactory typeFactory) { - super(typeFactory); - } - - /**Applies pre-knowledged defaults that are same with jdk.astub to toString, hashCode, equals, - clone Object methods*/ - @Override - public Void visitExecutable(AnnotatedExecutableType t, Void p) { - super.visitExecutable(t, p); - - // Only handle instance methods, not static methods - if (!ElementUtils.isStatic(t.getElement())) { - if (PICOTypeUtil.isMethodOrOverridingMethod(t, "toString()", typeFactory) - || PICOTypeUtil.isMethodOrOverridingMethod(t, "hashCode()", typeFactory)) { - t.getReceiverType().addMissingAnnotations(new HashSet<>(Arrays.asList(READONLY))); - } else if (PICOTypeUtil.isMethodOrOverridingMethod(t, "equals(java.lang.Object)", typeFactory)) { - t.getReceiverType().addMissingAnnotations(new HashSet<>(Arrays.asList(READONLY))); - t.getParameterTypes().get(0).addMissingAnnotations(new HashSet<>(Arrays.asList(READONLY))); - } - } - - return null; - } - - } - - public static class PICOImplicitsTypeAnnotator extends ImplicitsTypeAnnotator { - - public PICOImplicitsTypeAnnotator(AnnotatedTypeFactory typeFactory) { - super(typeFactory); - } - - /**Also applies implicits to method receiver*/ - @Override - public Void visitExecutable(AnnotatedExecutableType t, Void p) { - // TODO The implementation before doesn't work after update. Previously, I sanned the - // method receiver without null check. But even if I check nullness, scanning receiver - // at first caused some tests to fail. Need to investigate the reason. - super.visitExecutable(t, p); - // Also scan the receiver to apply implicit annotation - if (t.getReceiverType() != null) { - return scanAndReduce(t.getReceiverType(), p, null); - } - return null; - } - - - @Override - protected Void scan(AnnotatedTypeMirror type, Void p) { - // If underlying type is enum or enum constant, appy @Immutable to type - PICOTypeUtil.applyImmutableToEnumAndEnumConstant(type); - return super.scan(type, p); - } - } - - // TODO Right now, instance method receiver cannot inherit bound annotation from class element, and - // this caused the inconsistency when accessing the type of receiver while visiting the method and - // while visiting the variable tree. Implicit annotation can be inserted to method receiver via - // extending ImplicitsTypeAnnotator; But InheritedFromClassAnnotator cannot be inheritted because its - // constructor is private and I can't override it to also inherit bound annotation from class element - // to the declared receiver type of instance methods. To view the details, look at ImmutableClass1.java - // testcase. - // class PICOInheritedFromClassAnnotator extends InheritedFromClassAnnotator {} -} diff --git a/src/main/java/pico/typecheck/PICOAnnotationMirrorHolder.java b/src/main/java/pico/typecheck/PICOAnnotationMirrorHolder.java index e1ef081..478624c 100644 --- a/src/main/java/pico/typecheck/PICOAnnotationMirrorHolder.java +++ b/src/main/java/pico/typecheck/PICOAnnotationMirrorHolder.java @@ -9,7 +9,6 @@ import qual.PolyMutable; import qual.Readonly; import qual.ReceiverDependantMutable; -import qual.SubstitutablePolyMutable; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.util.Elements; @@ -23,7 +22,6 @@ public class PICOAnnotationMirrorHolder { public static AnnotationMirror MUTABLE; public static AnnotationMirror POLY_MUTABLE; public static AnnotationMirror RECEIVER_DEPENDANT_MUTABLE; - public static AnnotationMirror SUBSTITUTABLE_POLY_MUTABLE; public static AnnotationMirror IMMUTABLE; public static AnnotationMirror BOTTOM; public static AnnotationMirror COMMITED; @@ -34,7 +32,6 @@ public static void init(SourceChecker checker) { MUTABLE = AnnotationBuilder.fromClass(elements, Mutable.class); POLY_MUTABLE = AnnotationBuilder.fromClass(elements, PolyMutable.class); RECEIVER_DEPENDANT_MUTABLE = AnnotationBuilder.fromClass(elements, ReceiverDependantMutable.class); - SUBSTITUTABLE_POLY_MUTABLE = AnnotationBuilder.fromClass(elements, SubstitutablePolyMutable.class); IMMUTABLE = AnnotationBuilder.fromClass(elements, Immutable.class); BOTTOM = AnnotationBuilder.fromClass(elements, Bottom.class); diff --git a/src/main/java/pico/typecheck/PICOChecker.java b/src/main/java/pico/typecheck/PICOChecker.java index 8338604..c279bd1 100644 --- a/src/main/java/pico/typecheck/PICOChecker.java +++ b/src/main/java/pico/typecheck/PICOChecker.java @@ -1,20 +1,20 @@ package pico.typecheck; import org.checkerframework.checker.initialization.InitializationChecker; -import org.checkerframework.common.basetype.BaseTypeVisitor; +import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.source.SupportedOptions; import java.util.Map.Entry; import java.util.Set; -/** - * Created by mier on 20/06/17. - */ @SupportedOptions({"printFbcErrors"}) public class PICOChecker extends InitializationChecker { - public PICOChecker() { - super(true); + public PICOChecker() {} + + @Override + public Class getTargetCheckerClass() { + return PICONoInitSubchecker.class; } @Override @@ -24,8 +24,8 @@ public void initChecker() { } @Override - protected BaseTypeVisitor createSourceVisitor() { - return new PICOVisitor(this); + public boolean checkPrimitives() { + return true; } @Override @@ -42,7 +42,7 @@ protected void shutdownHook() { } private void printFbcViolatedMethods() { - Set> entries = ((PICOVisitor) visitor).fbcViolatedMethods.entrySet(); + Set> entries = ((PICONoInitVisitor) visitor).fbcViolatedMethods.entrySet(); if (entries.isEmpty()) { System.out.println("\n=============== Congrats! No Fbc Violations Found. ===============\n"); } else { diff --git a/src/main/java/pico/typecheck/PICONoInitAnalysis.java b/src/main/java/pico/typecheck/PICONoInitAnalysis.java new file mode 100644 index 0000000..ad3a6e9 --- /dev/null +++ b/src/main/java/pico/typecheck/PICONoInitAnalysis.java @@ -0,0 +1,35 @@ +package pico.typecheck; + +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.framework.flow.CFAbstractAnalysis; +import org.checkerframework.framework.flow.CFAbstractValue; +import org.checkerframework.javacutil.AnnotationMirrorSet; + +import javax.lang.model.type.TypeMirror; + +public class PICONoInitAnalysis + extends CFAbstractAnalysis { + + public PICONoInitAnalysis(BaseTypeChecker checker, PICONoInitAnnotatedTypeFactory factory) { + super(checker, factory, -1); + } + + @Override + public PICONoInitStore createEmptyStore(boolean sequentialSemantics) { + return new PICONoInitStore(this, sequentialSemantics); + } + + @Override + public PICONoInitStore createCopiedStore(PICONoInitStore picoNoInitStore) { + return new PICONoInitStore(picoNoInitStore); + } + + @Override + public PICONoInitValue createAbstractValue( + AnnotationMirrorSet annotations, TypeMirror underlyingType) { + if (!CFAbstractValue.validateSet(annotations, underlyingType, atypeFactory)) { + return null; + } + return new PICONoInitValue(this, annotations, underlyingType); + } +} diff --git a/src/main/java/pico/typecheck/PICONoInitAnnotatedTypeFactory.java b/src/main/java/pico/typecheck/PICONoInitAnnotatedTypeFactory.java new file mode 100644 index 0000000..6a71dcd --- /dev/null +++ b/src/main/java/pico/typecheck/PICONoInitAnnotatedTypeFactory.java @@ -0,0 +1,513 @@ +package pico.typecheck; + +import static pico.typecheck.PICOAnnotationMirrorHolder.*; + +import com.sun.source.tree.BinaryTree; +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.NewArrayTree; +import com.sun.source.tree.NewClassTree; +import com.sun.source.tree.ParameterizedTypeTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.TypeCastTree; +import com.sun.source.tree.VariableTree; +import com.sun.source.util.TreePath; + +import org.checkerframework.checker.initialization.InitializationFieldAccessTreeAnnotator; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.framework.qual.RelevantJavaTypes; +import org.checkerframework.framework.type.*; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator; +import org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator; +import org.checkerframework.framework.type.treeannotator.PropagationTreeAnnotator; +import org.checkerframework.framework.type.treeannotator.TreeAnnotator; +import org.checkerframework.framework.type.typeannotator.*; +import org.checkerframework.javacutil.*; + +import java.lang.annotation.Annotation; +import java.util.*; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + +import pico.common.ExtendedViewpointAdapter; +import pico.common.PICOTypeUtil; +import pico.common.ViewpointAdapterGettable; +import qual.Bottom; +import qual.Immutable; +import qual.Mutable; +import qual.PolyMutable; +import qual.Readonly; +import qual.ReceiverDependantMutable; + +/** + * AnnotatedTypeFactory for PICO. In addition to getting atms, it also propagates and applies + * mutability qualifiers correctly depending on AST locations(e.g. fields, binary trees) or + * methods(toString(), hashCode(), clone(), equals(Object o)) using TreeAnnotators and + * TypeAnnotators. It also applies implicits to method receiver that is not so by default in super + * implementation. + */ +// TODO Use @Immutable for classes that extends those predefined immutable classess like String or +// Number +// and explicitly annotated classes with @Immutable on its declaration +public class PICONoInitAnnotatedTypeFactory + extends GenericAnnotatedTypeFactory< + PICONoInitValue, PICONoInitStore, PICONoInitTransfer, PICONoInitAnalysis> + implements ViewpointAdapterGettable { + + public PICONoInitAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + postInit(); + // PICO aliasing is not implemented correctly + // remove for now + // addAliasedAnnotation(org.jmlspecs.annotation.Readonly.class, READONLY); + } + + @Override + protected Set> createSupportedTypeQualifiers() { + return new LinkedHashSet<>( + Arrays.asList( + Readonly.class, + Mutable.class, + PolyMutable.class, + ReceiverDependantMutable.class, + Immutable.class, + Bottom.class)); + } + + @Override + protected ViewpointAdapter createViewpointAdapter() { + return new PICOViewpointAdapter(this); + } + + /** Annotators are executed by the added order. Same for Type Annotator */ + @Override + protected TreeAnnotator createTreeAnnotator() { + List annotators = new ArrayList<>(5); + annotators.add(new InitializationFieldAccessTreeAnnotator(this)); + annotators.add(new PICOPropagationTreeAnnotator(this)); + annotators.add(new LiteralTreeAnnotator(this)); + annotators.add(new PICOSuperClauseAnnotator(this)); + annotators.add(new PICOTreeAnnotator(this)); + return new ListTreeAnnotator(annotators); + } + + // TODO Refactor super class to remove this duplicate code + @Override + protected TypeAnnotator createTypeAnnotator() { + /*Copied code start*/ + List typeAnnotators = new ArrayList<>(); + RelevantJavaTypes relevantJavaTypes = + checker.getClass().getAnnotation(RelevantJavaTypes.class); + if (relevantJavaTypes != null) { + // Class[] classes = relevantJavaTypes.value(); + // Must be first in order to annotated all irrelevant types that are not explicilty + // annotated. + typeAnnotators.add(new IrrelevantTypeAnnotator(this)); + } + typeAnnotators.add(new PropagationTypeAnnotator(this)); + /*Copied code ends*/ + // Adding order is important here. Because internally type annotators are using + // addMissingAnnotations() method, so if one annotator already applied the annotations, the + // others won't apply twice + // at the same location + typeAnnotators.add(new PICOTypeAnnotator(this)); + typeAnnotators.add(new PICODefaultForTypeAnnotator(this)); + typeAnnotators.add(new PICOEnumDefaultAnnotator(this)); + return new ListTypeAnnotator(typeAnnotators); + } + + @Override + public QualifierHierarchy createQualifierHierarchy() { + return new NoElementQualifierHierarchy(getSupportedTypeQualifiers(), elements, this); + } + + @Override + public ParameterizedExecutableType constructorFromUse(NewClassTree tree) { + boolean hasExplicitAnnos = !getExplicitNewClassAnnos(tree).isEmpty(); + ParameterizedExecutableType mType = super.constructorFromUse(tree); + AnnotatedExecutableType method = mType.executableType; + if (!hasExplicitAnnos && method.getReturnType().hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { + method.getReturnType().replaceAnnotation(MUTABLE); + } + return mType; + } + + /** Forbid applying top annotations to type variables if they are used on local variables */ + @Override + public boolean getShouldDefaultTypeVarLocals() { + return false; + } + + /** + * This covers the case when static fields are used and constructor is accessed as an + * element(regarding applying @Immutable on type declaration to constructor return type). + */ + @Override + public void addComputedTypeAnnotations(Element elt, AnnotatedTypeMirror type) { + PICOTypeUtil.addDefaultForField(this, type, elt); + PICOTypeUtil.defaultConstructorReturnToClassBound(this, elt, type); + // PICOTypeUtil.applyImmutableToEnumAndEnumConstant(type); + super.addComputedTypeAnnotations(elt, type); + } + + /** Tree Annotators */ + public static class PICOPropagationTreeAnnotator extends PropagationTreeAnnotator { + public PICOPropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } + + // + // // TODO This is very ugly. Why is array component type from lhs propagates to + // rhs?! + @Override + public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { + AnnotatedTypeMirror componentType = + ((AnnotatedTypeMirror.AnnotatedArrayType) type).getComponentType(); + boolean noExplicitATM = !componentType.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE); + super.visitNewArray(tree, type); + // if new explicit anno before, but RDM after, the RDM must come from the type + // declaration bound + // however, for type has declaration bound as RDM, its default use is mutable. + if (noExplicitATM && componentType.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { + componentType.replaceAnnotation(MUTABLE); + } + return null; + } + + @Override + public Void visitTypeCast(TypeCastTree node, AnnotatedTypeMirror type) { + boolean hasExplicitAnnos = !type.getAnnotations().isEmpty(); + super.visitTypeCast(node, type); + if (!hasExplicitAnnos && type.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { + type.replaceAnnotation(MUTABLE); + } + return null; + } + + @Override + public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) { + return null; + } + + } + + public ExtendedViewpointAdapter getViewpointAdapter() { + return (ExtendedViewpointAdapter) viewpointAdapter; + } + + @Override + protected AnnotationMirrorSet getDefaultTypeDeclarationBounds() { + AnnotationMirrorSet frameworkDefault = + new AnnotationMirrorSet(super.getDefaultTypeDeclarationBounds()); + return replaceAnnotationInHierarchy(frameworkDefault, MUTABLE); + } + + @Override + public AnnotationMirrorSet getTypeDeclarationBounds(TypeMirror type) { + AnnotationMirror mut = getTypeDeclarationBoundForMutability(type); + AnnotationMirrorSet frameworkDefault = super.getTypeDeclarationBounds(type); + if (mut != null) { + frameworkDefault = replaceAnnotationInHierarchy(frameworkDefault, mut); + } + return frameworkDefault; + } + + private AnnotationMirrorSet replaceAnnotationInHierarchy( + AnnotationMirrorSet set, AnnotationMirror mirror) { + AnnotationMirrorSet result = new AnnotationMirrorSet(set); + AnnotationMirror removeThis = + getQualifierHierarchy().findAnnotationInSameHierarchy(set, mirror); + result.remove(removeThis); + result.add(mirror); + return result; + } + + public AnnotationMirror getTypeDeclarationBoundForMutability(TypeMirror type) { + // copied from inference real type factory with minor modification + // TODO too awkward. maybe overload isImplicitlyImmutableType + if (PICOTypeUtil.isImplicitlyImmutableType(toAnnotatedType(type, false))) { + return IMMUTABLE; + } + if (type.getKind() == TypeKind.ARRAY) { + return RECEIVER_DEPENDANT_MUTABLE; // if decided to use vpa for array, return RDM. + } + + // IMMUTABLE for enum w/o decl anno + if (type instanceof DeclaredType) { + Element ele = ((DeclaredType) type).asElement(); + if (ele.getKind() == ElementKind.ENUM) { + if (!AnnotationUtils.containsSameByName(getDeclAnnotations(ele), MUTABLE) + && !AnnotationUtils.containsSameByName( + getDeclAnnotations(ele), + RECEIVER_DEPENDANT_MUTABLE)) { // no decl anno + return IMMUTABLE; + } + } + } + return null; + } + + @Override + public AnnotatedTypeMirror getTypeOfExtendsImplements(Tree clause) { + // this is still needed with PICOSuperClauseAnnotator. + // maybe just use getAnnotatedType + // add default anno from class main qual, if no qual present + AnnotatedTypeMirror fromTypeTree = super.getTypeOfExtendsImplements(clause); + if (fromTypeTree.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { + AnnotatedTypeMirror enclosing = + getAnnotatedType(TreePathUtil.enclosingClass(getPath(clause))); + AnnotationMirror mainBound = enclosing.getAnnotationInHierarchy(READONLY); + fromTypeTree.replaceAnnotation(mainBound); + } + return fromTypeTree; + } + + /** Apply defaults for static fields with non-implicitly immutable types */ + public static class PICOTreeAnnotator extends TreeAnnotator { + public PICOTreeAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } + + // This adds @Immutable annotation to constructor return type if type declaration has + // @Immutable when the + // constructor is accessed as a tree. + @Override + public Void visitMethod(MethodTree node, AnnotatedTypeMirror p) { + Element element = TreeUtils.elementFromDeclaration(node); + // See: + // https://github.com/opprop/checker-framework/blob/master/framework/src/org/checkerframework/framework/type/AnnotatedTypeFactory.java#L1593 + // for why constructor return is not applied class bound annotation + PICOTypeUtil.defaultConstructorReturnToClassBound(atypeFactory, element, p); + return super.visitMethod(node, p); + } + + /** This covers the declaration of static fields */ + @Override + public Void visitVariable(VariableTree node, AnnotatedTypeMirror annotatedTypeMirror) { + VariableElement element = TreeUtils.elementFromDeclaration(node); + PICOTypeUtil.addDefaultForField(atypeFactory, annotatedTypeMirror, element); + // PICOTypeUtil.applyImmutableToEnumAndEnumConstant(annotatedTypeMirror); + return super.visitVariable(node, annotatedTypeMirror); + } + + @Override + public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) { + type.replaceAnnotation(IMMUTABLE); + return null; + } + } + + /** Type Annotators */ + public static class PICOTypeAnnotator extends TypeAnnotator { + + public PICOTypeAnnotator(AnnotatedTypeFactory typeFactory) { + super(typeFactory); + } + + /** + * Applies pre-knowledged defaults that are same with jdk.astub to toString, hashCode, + * equals, clone Object methods + */ + @Override + public Void visitExecutable(AnnotatedExecutableType t, Void p) { + super.visitExecutable(t, p); + + // Only handle instance methods, not static methods + if (!ElementUtils.isStatic(t.getElement())) { + if (PICOTypeUtil.isMethodOrOverridingMethod(t, "toString()", atypeFactory) + || PICOTypeUtil.isMethodOrOverridingMethod(t, "hashCode()", atypeFactory)) { + assert t.getReceiverType() != null; + t.getReceiverType().replaceAnnotation(READONLY); + } else if (PICOTypeUtil.isMethodOrOverridingMethod( + t, "equals(java.lang.Object)", atypeFactory)) { + assert t.getReceiverType() != null; + t.getReceiverType().replaceAnnotation(READONLY); + t.getParameterTypes().get(0).replaceAnnotation(READONLY); + } + } + + // Array decl methods + // Array methods are implemented as JVM native method, so we cannot add that to stubs. + // for now: default array in receiver, parameter and return type to RDM + if (t.getReceiverType() != null) { + if (PICOTypeUtil.isArrayType(t.getReceiverType(), atypeFactory)) { + if (t.toString().equals("Object clone(Array this)")) {// Receiver type will not be viewpoint adapted: + // SyntheticArrays.replaceReturnType() will rollback the viewpoint adapt + // result. + // Use readonly to allow all invocations. + if (!t.getReceiverType().hasAnnotationInHierarchy(READONLY)) + t.getReceiverType().replaceAnnotation(READONLY); + // The return type will be fixed by SyntheticArrays anyway. + // Qualifiers added here will not have effect. + } + } + } + + return null; + } + } + + @Override + protected DefaultQualifierForUseTypeAnnotator createDefaultForUseTypeAnnotator() { + return new PICOQualifierForUseTypeAnnotator(this); + } + + // @DefaultQFU + public static class PICOQualifierForUseTypeAnnotator + extends DefaultQualifierForUseTypeAnnotator { + + public PICOQualifierForUseTypeAnnotator(AnnotatedTypeFactory typeFactory) { + super(typeFactory); + } + + @Override + public Void visitDeclared(AnnotatedTypeMirror.AnnotatedDeclaredType type, Void aVoid) { + + Element element = type.getUnderlyingType().asElement(); + Set annosToApply = getDefaultAnnosForUses(element); + + if (annosToApply.contains(MUTABLE) || annosToApply.contains(IMMUTABLE)) { + type.addMissingAnnotations(annosToApply); + } + + // Below copied from super.super + // TODO add a function to super.super visitor + if (!type.getTypeArguments().isEmpty()) { + // Only declared types with type arguments might be recursive. + if (visitedNodes.containsKey(type)) { + return visitedNodes.get(type); + } + visitedNodes.put(type, null); + } + Void r = null; + if (type.getEnclosingType() != null) { + scan(type.getEnclosingType(), null); + } + r = scanAndReduce(type.getTypeArguments(), null, r); + return r; + } + } + + public static class PICODefaultForTypeAnnotator extends DefaultForTypeAnnotator { + + public PICODefaultForTypeAnnotator(AnnotatedTypeFactory typeFactory) { + super(typeFactory); + } + + /** Also applies implicits to method receiver */ + @Override + public Void visitExecutable(AnnotatedExecutableType t, Void p) { + // TODO The implementation before doesn't work after update. Previously, I sanned the + // method receiver without null check. But even if I check nullness, scanning receiver + // at first caused some tests to fail. Need to investigate the reason. + super.visitExecutable(t, p); + // Also scan the receiver to apply implicit annotation + if (t.getReceiverType() != null) { + return scanAndReduce(t.getReceiverType(), p, null); + } + return null; + } + + @Override + protected Void scan(AnnotatedTypeMirror type, Void p) { + // If underlying type is enum or enum constant, appy @Immutable to type + // PICOTypeUtil.applyImmutableToEnumAndEnumConstant(type); + return super.scan(type, p); + } + } + + // TODO Right now, instance method receiver cannot inherit bound annotation from class element, + // and + // this caused the inconsistency when accessing the type of receiver while visiting the method + // and + // while visiting the variable tree. Implicit annotation can be inserted to method receiver via + // extending DefaultForTypeAnnotator; But InheritedFromClassAnnotator cannot be inheritted + // because its + // constructor is private and I can't override it to also inherit bound annotation from class + // element + // to the declared receiver type of instance methods. To view the details, look at + // ImmutableClass1.java + // testcase. + // class PICOInheritedFromClassAnnotator extends InheritedFromClassAnnotator {} + + public static class PICOSuperClauseAnnotator extends TreeAnnotator { + + public PICOSuperClauseAnnotator(AnnotatedTypeFactory atypeFactory) { + super(atypeFactory); + } + + public static boolean isSuperClause(TreePath path) { + if (path == null) { + return false; + } + return TreeUtils.isClassTree(path.getParentPath().getLeaf()); + } + + private void addDefaultFromMain(Tree tree, AnnotatedTypeMirror mirror) { + TreePath path = atypeFactory.getPath(tree); + + // only annotates when: + // 1. it's a super clause, AND + // 2. atm OR tree is not annotated + // Note: TreeUtils.typeOf returns no stub or default annotations, but in this scenario + // they are not needed + // Here only explicit annotation on super clause have effect because framework default + // rule is overriden + if (isSuperClause(path) + && (!mirror.hasAnnotationInHierarchy(READONLY) + || atypeFactory + .getQualifierHierarchy() + .findAnnotationInHierarchy( + TreeUtils.typeOf(tree).getAnnotationMirrors(), + READONLY) + == null)) { + AnnotatedTypeMirror enclosing = + atypeFactory.getAnnotatedType(TreePathUtil.enclosingClass(path)); + AnnotationMirror mainBound = enclosing.getAnnotationInHierarchy(READONLY); + mirror.replaceAnnotation(mainBound); + // System.err.println("ANNOT: ADDED DEFAULT FOR: " + mirror); + } + } + + @Override + public Void visitIdentifier( + IdentifierTree identifierTree, AnnotatedTypeMirror annotatedTypeMirror) { + // super clauses without type param use this + addDefaultFromMain(identifierTree, annotatedTypeMirror); + return super.visitIdentifier(identifierTree, annotatedTypeMirror); + } + + @Override + public Void visitParameterizedType( + ParameterizedTypeTree parameterizedTypeTree, + AnnotatedTypeMirror annotatedTypeMirror) { + // super clauses with type param use this + addDefaultFromMain(parameterizedTypeTree, annotatedTypeMirror); + return super.visitParameterizedType(parameterizedTypeTree, annotatedTypeMirror); + } + } + + public static class PICOEnumDefaultAnnotator extends TypeAnnotator { + // Defaulting only applies the same annotation to all class declarations + // We need this to "only default enums" to immutable + + public PICOEnumDefaultAnnotator(AnnotatedTypeFactory typeFactory) { + super(typeFactory); + } + + @Override + public Void visitDeclared(AnnotatedTypeMirror.AnnotatedDeclaredType type, Void aVoid) { + if (PICOTypeUtil.isEnumOrEnumConstant(type)) { + type.addMissingAnnotations(Collections.singleton(IMMUTABLE)); + } + return super.visitDeclared(type, aVoid); + } + } +} diff --git a/src/main/java/pico/typecheck/PICONoInitStore.java b/src/main/java/pico/typecheck/PICONoInitStore.java new file mode 100644 index 0000000..4963c5d --- /dev/null +++ b/src/main/java/pico/typecheck/PICONoInitStore.java @@ -0,0 +1,25 @@ +package pico.typecheck; + +import org.checkerframework.dataflow.expression.FieldAccess; +import org.checkerframework.framework.flow.CFAbstractAnalysis; +import org.checkerframework.framework.flow.CFAbstractStore; + +import java.util.Map; + +public class PICONoInitStore extends CFAbstractStore { + + protected Map initializedFields; + + public PICONoInitStore( + CFAbstractAnalysis analysis, + boolean sequentialSemantics) { + super(analysis, sequentialSemantics); + } + + public PICONoInitStore(PICONoInitStore s) { + super(s); + if (s.initializedFields != null) { + initializedFields = s.initializedFields; + } + } +} diff --git a/src/main/java/pico/typecheck/PICONoInitSubchecker.java b/src/main/java/pico/typecheck/PICONoInitSubchecker.java new file mode 100644 index 0000000..1af9238 --- /dev/null +++ b/src/main/java/pico/typecheck/PICONoInitSubchecker.java @@ -0,0 +1,27 @@ +package pico.typecheck; + +import org.checkerframework.checker.initialization.InitializationFieldAccessSubchecker; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.common.basetype.BaseTypeVisitor; + +import java.util.Set; + +public class PICONoInitSubchecker extends BaseTypeChecker { + public PICONoInitSubchecker() {} + + @Override + public PICONoInitAnnotatedTypeFactory getTypeFactory() { + return (PICONoInitAnnotatedTypeFactory) super.getTypeFactory(); + } + + @Override + protected Set> getImmediateSubcheckerClasses() { + Set> checkers = super.getImmediateSubcheckerClasses(); + checkers.add(InitializationFieldAccessSubchecker.class); + return checkers; + } + + protected BaseTypeVisitor createSourceVisitor() { + return new PICONoInitVisitor(this); + } +} diff --git a/src/main/java/pico/typecheck/PICONoInitTransfer.java b/src/main/java/pico/typecheck/PICONoInitTransfer.java new file mode 100644 index 0000000..143fdb4 --- /dev/null +++ b/src/main/java/pico/typecheck/PICONoInitTransfer.java @@ -0,0 +1,44 @@ +package pico.typecheck; + +import com.sun.source.tree.VariableTree; + +import org.checkerframework.dataflow.analysis.RegularTransferResult; +import org.checkerframework.dataflow.analysis.TransferInput; +import org.checkerframework.dataflow.analysis.TransferResult; +import org.checkerframework.dataflow.cfg.node.AssignmentNode; +import org.checkerframework.dataflow.cfg.node.NullLiteralNode; +import org.checkerframework.framework.flow.CFAbstractTransfer; +import org.checkerframework.javacutil.TreeUtils; + +import javax.lang.model.element.VariableElement; + +public class PICONoInitTransfer + extends CFAbstractTransfer { + + public PICONoInitTransfer(PICONoInitAnalysis analysis) { + super(analysis); + } + + @Override + public TransferResult visitAssignment( + AssignmentNode n, TransferInput in) { + if (n.getExpression() instanceof NullLiteralNode + && n.getTarget().getTree() instanceof VariableTree) { + VariableElement varElement = + TreeUtils.elementFromDeclaration((VariableTree) n.getTarget().getTree()); + // Below is for removing false positive warning of bottom illegal write cacused by + // refining field to @Bottom if + // field initializer is null. + // Forbid refinement from null literal in initializer to fields variable tree(identifier + // tree not affected, e.g. + // assigning a field as null in instance methods or constructors) + if (varElement != null && varElement.getKind().isField()) { + PICONoInitStore store = in.getRegularStore(); + PICONoInitValue storeValue = in.getValueOfSubNode(n); + PICONoInitValue value = moreSpecificValue(null, storeValue); + return new RegularTransferResult<>(finishValue(value, store), store); + } + } + return super.visitAssignment(n, in); + } +} diff --git a/src/main/java/pico/typecheck/PICONoInitValue.java b/src/main/java/pico/typecheck/PICONoInitValue.java new file mode 100644 index 0000000..17d8cf8 --- /dev/null +++ b/src/main/java/pico/typecheck/PICONoInitValue.java @@ -0,0 +1,16 @@ +package pico.typecheck; + +import org.checkerframework.framework.flow.CFAbstractAnalysis; +import org.checkerframework.framework.flow.CFAbstractValue; +import org.checkerframework.javacutil.AnnotationMirrorSet; + +import javax.lang.model.type.TypeMirror; + +public class PICONoInitValue extends CFAbstractValue { + public PICONoInitValue( + CFAbstractAnalysis analysis, + AnnotationMirrorSet annotations, + TypeMirror underlyingType) { + super(analysis, annotations, underlyingType); + } +} diff --git a/src/main/java/pico/typecheck/PICONoInitVisitor.java b/src/main/java/pico/typecheck/PICONoInitVisitor.java new file mode 100644 index 0000000..8ff2477 --- /dev/null +++ b/src/main/java/pico/typecheck/PICONoInitVisitor.java @@ -0,0 +1,599 @@ +package pico.typecheck; + +import static org.checkerframework.javacutil.TreePathUtil.isTopLevelAssignmentInInitializerBlock; +import static pico.typecheck.PICOAnnotationMirrorHolder.BOTTOM; +import static pico.typecheck.PICOAnnotationMirrorHolder.IMMUTABLE; +import static pico.typecheck.PICOAnnotationMirrorHolder.MUTABLE; +import static pico.typecheck.PICOAnnotationMirrorHolder.POLY_MUTABLE; +import static pico.typecheck.PICOAnnotationMirrorHolder.READONLY; +import static pico.typecheck.PICOAnnotationMirrorHolder.RECEIVER_DEPENDANT_MUTABLE; + +import com.sun.source.tree.ArrayAccessTree; +import com.sun.source.tree.AssignmentTree; +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.CompoundAssignmentTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.NewArrayTree; +import com.sun.source.tree.NewClassTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; +import com.sun.source.tree.UnaryTree; +import com.sun.source.tree.VariableTree; + +import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.common.basetype.BaseTypeVisitor; +import org.checkerframework.common.basetype.TypeValidator; +import org.checkerframework.framework.type.AnnotatedTypeFactory.ParameterizedExecutableType; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; +import org.checkerframework.framework.type.QualifierHierarchy; +import org.checkerframework.framework.util.AnnotatedTypes; +import org.checkerframework.javacutil.*; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + +import pico.common.ExtendedViewpointAdapter; +import pico.common.PICOTypeUtil; +import pico.common.ViewpointAdapterGettable; + +public class PICONoInitVisitor extends BaseTypeVisitor { + + final Map fbcViolatedMethods; + + public PICONoInitVisitor(BaseTypeChecker checker) { + super(checker); + boolean shouldOutputFbcError = checker.hasOption("printFbcErrors"); + fbcViolatedMethods = shouldOutputFbcError ? new HashMap<>() : null; + } + + @Override + protected TypeValidator createTypeValidator() { + return new PICOValidator(checker, this, atypeFactory); + } + + @Override + protected void checkConstructorResult( + AnnotatedExecutableType constructorType, ExecutableElement constructorElement) {} + + // This method is for validating usage of mutability qualifier is conformable to element + // declaration, + // Ugly thing here is that declarationType is not the result of calling the other method - + // PICOTypeUtil#getBoundTypeOfTypeDeclaration. Instead it's the result of calling + // ATF#getAnnotatedType(Element). + // Why it works is that PICOTypeUtil#getBoundTypeOfTypeDeclaration and + // ATF#getAnnotatedType(Element) has + // the same effect most of the time except on java.lang.Object. We need to be careful when + // modifying + // PICOTypeUtil#getBoundTypeOfTypeDeclaration so that it has the same behaviour as + // ATF#getAnnotatedType(Element) + // (at least for types other than java.lang.Object) + @Override + public boolean isValidUse( + AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) { + + // FIXME workaround for poly anno, remove after fix substitutable poly and add poly vp rules + if (useType.hasAnnotation(POLY_MUTABLE)) { + return true; + } + + AnnotationMirror declared = declarationType.getAnnotationInHierarchy(READONLY); + AnnotationMirror used = useType.getAnnotationInHierarchy(READONLY); + + return isAdaptedSubtype(used, declared); + } + + @Override + public boolean isValidUse(AnnotatedTypeMirror.AnnotatedArrayType type, Tree tree) { + // You don't need adapted subtype if the decl bound is guaranteed to be RDM. + // That simply means that any use is valid except bottom. + AnnotationMirror used = type.getAnnotationInHierarchy(READONLY); + return !AnnotationUtils.areSame(used, BOTTOM); + } + + private boolean isAdaptedSubtype(AnnotationMirror lhs, AnnotationMirror rhs) { + ExtendedViewpointAdapter vpa = + ((ViewpointAdapterGettable) atypeFactory).getViewpointAdapter(); + AnnotationMirror adapted = vpa.rawCombineAnnotationWithAnnotation(lhs, rhs); + return atypeFactory.getQualifierHierarchy().isSubtypeQualifiersOnly(adapted, lhs); + } + + @Override + protected boolean commonAssignmentCheck( + Tree varTree, ExpressionTree valueExp, String errorKey, Object... extraArgs) { + AnnotatedTypeMirror var = atypeFactory.getAnnotatedTypeLhs(varTree); + assert var != null : "no variable found for tree: " + varTree; + + if (!validateType(varTree, var)) { + return false; + } + + if (varTree instanceof VariableTree) { + VariableElement element = TreeUtils.elementFromDeclaration((VariableTree) varTree); + if (element.getKind() == ElementKind.FIELD && !ElementUtils.isStatic(element)) { + AnnotatedDeclaredType bound = + PICOTypeUtil.getBoundTypeOfEnclosingTypeDeclaration(varTree, atypeFactory); + // var is singleton, so shouldn't modify var directly. Otherwise, the variable + // tree's type will be + // altered permanently, and other clients who access this type will see the change, + // too. + AnnotatedTypeMirror varAdapted = var.shallowCopy(true); + // Viewpoint adapt varAdapted to the bound. + // PICOInferenceAnnotatedTypeFactory#viewpointAdaptMember() + // mutates varAdapted, so after the below method is called, varAdapted is the result + // adapted to bound + atypeFactory.getViewpointAdapter().viewpointAdaptMember(bound, element, varAdapted); + // Pass varAdapted here as lhs type. + // Caution: cannot pass var directly. Modifying type in PICOInferenceTreeAnnotator# + // visitVariable() will cause wrong type to be gotton here, as on inference side, + // atm is uniquely determined by each element. + return commonAssignmentCheck(varAdapted, valueExp, errorKey, extraArgs); + } + } + + return commonAssignmentCheck(var, valueExp, errorKey, extraArgs); + } + + @Override + protected void checkConstructorInvocation( + AnnotatedDeclaredType invocation, + AnnotatedExecutableType constructor, + NewClassTree newClassTree) { + // TODO Is the copied code really needed? + /*Copied Code Start*/ + AnnotatedDeclaredType returnType = (AnnotatedDeclaredType) constructor.getReturnType(); + // When an interface is used as the identifier in an anonymous class (e.g. new Comparable() + // {}) + // the constructor method will be Object.init() {} which has an Object return type + // When TypeHierarchy attempts to convert it to the supertype (e.g. Comparable) it will + // return + // null from asSuper and return false for the check. Instead, copy the primary annotations + // to the declared type and then do a subtyping check + if (invocation.getUnderlyingType().asElement().getKind().isInterface() + && TypesUtils.isObject(returnType.getUnderlyingType())) { + final AnnotatedDeclaredType retAsDt = invocation.deepCopy(); + retAsDt.replaceAnnotations(returnType.getAnnotations()); + returnType = retAsDt; + } else if (newClassTree.getClassBody() != null) { + // An anonymous class invokes the constructor of its super class, so the underlying + // types of invocation and returnType are not the same. Call asSuper so they are the + // same and the is subtype tests below work correctly + invocation = AnnotatedTypes.asSuper(atypeFactory, invocation, returnType); + } + /*Copied Code End*/ + + // The immutability return qualifier of the constructor (returnType) must be supertype of + // the + // constructor invocation immutability qualifier(invocation). + if (!atypeFactory + .getQualifierHierarchy() + .isSubtypeQualifiersOnly( + invocation.getAnnotationInHierarchy(READONLY), + returnType.getAnnotationInHierarchy(READONLY))) { + checker.reportError( + newClassTree, "constructor.invocation.invalid", invocation, returnType); + } + } + + @Override + public Void visitMethod(MethodTree node, Void p) { + AnnotatedExecutableType executableType = atypeFactory.getAnnotatedType(node); + AnnotatedDeclaredType bound = + PICOTypeUtil.getBoundTypeOfEnclosingTypeDeclaration(node, atypeFactory); + + if (TreeUtils.isConstructor(node)) { + AnnotatedDeclaredType constructorReturnType = + (AnnotatedDeclaredType) executableType.getReturnType(); + if (constructorReturnType.hasAnnotation(READONLY) + || constructorReturnType.hasAnnotation(POLY_MUTABLE)) { + checker.reportError(node, "constructor.return.invalid", constructorReturnType); + return super.visitMethod(node, p); + } + // if no explicit anno it must inherit from class decl so identical + // => if not the same must not inherited from class decl + // => no need to check the source of the anno + + } else { + AnnotatedDeclaredType declareReceiverType = executableType.getReceiverType(); + if (declareReceiverType != null) { + if (bound != null + && !bound.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE) + && !atypeFactory + .getQualifierHierarchy() + .isSubtypeQualifiersOnly( + declareReceiverType.getAnnotationInHierarchy(READONLY), + bound.getAnnotationInHierarchy(READONLY)) + // Below three are allowed on declared receiver types of instance methods in + // either @Mutable class or @Immutable class + && !declareReceiverType.hasAnnotation(READONLY) + && !declareReceiverType.hasAnnotation(POLY_MUTABLE)) { + checker.reportError(node, "method.receiver.incompatible", declareReceiverType); + } + } + } + + flexibleOverrideChecker(node); + + // ObjectIdentityMethod check + if (PICOTypeUtil.isObjectIdentityMethod(node, atypeFactory)) { + ObjectIdentityMethodEnforcer.check( + atypeFactory.getPath(node.getBody()), atypeFactory, checker); + } + return super.visitMethod(node, p); + } + + private void flexibleOverrideChecker(MethodTree node) { + // Method overriding checks + // TODO Copied from super, hence has lots of duplicate code with super. We need to + // change the signature of checkOverride() method to also pass ExecutableElement for + // viewpoint adaptation. + ExecutableElement methodElement = TreeUtils.elementFromDeclaration(node); + AnnotatedDeclaredType enclosingType = + (AnnotatedDeclaredType) + atypeFactory.getAnnotatedType(methodElement.getEnclosingElement()); + + Map overriddenMethods = + AnnotatedTypes.overriddenMethods(elements, atypeFactory, methodElement); + for (Map.Entry pair : + overriddenMethods.entrySet()) { + AnnotatedDeclaredType overriddenType = pair.getKey(); + AnnotatedExecutableType overriddenMethod = + AnnotatedTypes.asMemberOf(types, atypeFactory, enclosingType, pair.getValue()); + // Viewpoint adapt super method executable type to current class bound(is this always + // class bound?) + // to allow flexible overriding + atypeFactory + .getViewpointAdapter() + .viewpointAdaptMethod(enclosingType, pair.getValue(), overriddenMethod); + AnnotatedExecutableType overrider = atypeFactory.getAnnotatedType(node); + if (!checkOverride(node, overrider, enclosingType, overriddenMethod, overriddenType)) { + // Stop at the first mismatch; this makes a difference only if + // -Awarns is passed, in which case multiple warnings might be raised on + // the same method, not adding any value. See Issue 373. + break; + } + } + } + + // Disables method overriding checks in BaseTypeVisitor + @Override + protected boolean checkOverride( + MethodTree overriderTree, + AnnotatedDeclaredType overridingType, + AnnotatedExecutableType overridden, + AnnotatedDeclaredType overriddenType) { + return true; + } + + @Override + public Void visitAssignment(AssignmentTree node, Void p) { + ExpressionTree variable = node.getVariable(); + // TODO Question Here, receiver type uses flow refinement. But in commonAssignmentCheck to + // compute lhs type + // , it doesn't. This causes inconsistencies when enforcing immutability and doing subtype + // check. I overrode + // getAnnotatedTypeLhs() to also use flow sensitive refinement, but came across with + // "private access" problem + // on field "computingAnnotatedTypeMirrorOfLHS" + checkMutation(node, variable); + return super.visitAssignment(node, p); + } + + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree node, Void p) { + ExpressionTree variable = node.getVariable(); + checkMutation(node, variable); + return super.visitCompoundAssignment(node, p); + } + + @Override + public Void visitUnary(UnaryTree node, Void p) { + if (PICOTypeUtil.isSideEffectingUnaryTree(node)) { + ExpressionTree variable = node.getExpression(); + checkMutation(node, variable); + } + return super.visitUnary(node, p); + } + + private void checkMutation(Tree node, ExpressionTree variable) { + AnnotatedTypeMirror receiverType = atypeFactory.getReceiverType(variable); + MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getCurrentPath()); + if (enclosingMethod != null && TreeUtils.isConstructor(enclosingMethod)) { + // If the enclosing method is constructor, we don't need to check the receiver type + return; + } + if (isTopLevelAssignmentInInitializerBlock(getCurrentPath())) { + // If the assignment is in initializer block, we don't need to check the receiver type + return; + } + // Cannot use receiverTree = TreeUtils.getReceiverTree(variable) to determine if it's + // field assignment or not. Because for field assignment with implicit "this", receiverTree + // is null but receiverType is non-null. We still need to check this case. + if (receiverType != null && !allowWrite(receiverType, variable)) { + reportFieldOrArrayWriteError(node, variable, receiverType); + } + } + + private boolean allowWrite(AnnotatedTypeMirror receiverType, ExpressionTree variable) { + // One pico side, if only receiver is mutable, we allow assigning/reassigning. Because if + // the field + // is declared as final, Java compiler will catch that, and we couldn't have reached this + // point + // If the receiver is mutable, we allow assigning/reassigning + // } else if (TreeUtils.elementFromUse(variable)) { + // // If the field is not initialized, we allow assigning/reassigning + // return true; + if (PICOTypeUtil.isAssigningAssignableField(variable, atypeFactory)) { + return isAllowedAssignableField(receiverType, variable); + } else return receiverType.hasAnnotation(MUTABLE); + } + + private boolean isAllowedAssignableField( + AnnotatedTypeMirror receiverType, ExpressionTree node) { + Element fieldElement = TreeUtils.elementFromUse(node); + Set bounds = + atypeFactory.getTypeDeclarationBounds(TreeUtils.typeOf(node)); + AnnotatedTypeMirror fieldType = atypeFactory.getAnnotatedType(fieldElement); + if (fieldElement == null) return false; + // Forbid the case that might break type soundness. See ForbidAssignmentCase.java:21 + // the second and third predicates ensure that the field is actually rdm (since sometimes we + // replace implicitly mutable with rdm to protect transitive immutability). + return !(receiverType.hasAnnotation(READONLY) + && AnnotationUtils.containsSameByName(bounds, RECEIVER_DEPENDANT_MUTABLE) + && fieldType.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)); + } + + private void reportFieldOrArrayWriteError( + Tree node, ExpressionTree variable, AnnotatedTypeMirror receiverType) { + if (variable.getKind() == Kind.MEMBER_SELECT) { + checker.reportError( + TreeUtils.getReceiverTree(variable), "illegal.field.write", receiverType); + } else if (variable.getKind() == Kind.IDENTIFIER) { + checker.reportError(node, "illegal.field.write", receiverType); + } else if (variable.getKind() == Kind.ARRAY_ACCESS) { + checker.reportError( + ((ArrayAccessTree) variable).getExpression(), + "illegal.array.write", + receiverType); + } else { + throw new BugInCF("Unknown assignment variable at: ", node); + } + } + + @Override + public Void visitVariable(VariableTree node, Void p) { + VariableElement element = TreeUtils.elementFromDeclaration(node); + AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(element); + if (element.getKind() == ElementKind.FIELD) { + if (type.hasAnnotation(POLY_MUTABLE)) { + checker.reportError(node, "field.polymutable.forbidden", element); + } + } + // When to check: + // bound == Immutable, OR + // not FIELD, OR + // top anno not RDM + // TODO use base cf check methods + AnnotationMirror declAnno = + atypeFactory.getTypeDeclarationBoundForMutability(type.getUnderlyingType()); + if ((declAnno != null && AnnotationUtils.areSameByName(declAnno, IMMUTABLE)) + || element.getKind() != ElementKind.FIELD + || !type.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { + checkAndReportInvalidAnnotationOnUse(type, node); + } + return super.visitVariable(node, p); + } + + private void checkAndReportInvalidAnnotationOnUse(AnnotatedTypeMirror type, Tree tree) { + AnnotationMirror useAnno = type.getAnnotationInHierarchy(READONLY); + // FIXME rm after poly vp + if (useAnno != null && AnnotationUtils.areSame(useAnno, POLY_MUTABLE)) { + return; + } + if (useAnno != null + && !PICOTypeUtil.isImplicitlyImmutableType(type) + && type.getKind() + != TypeKind.ARRAY) { // TODO: annotate the use instead of using this + AnnotationMirror defaultAnno = MUTABLE; + for (AnnotationMirror anno : + atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType())) { + if (atypeFactory.getQualifierHierarchy().isSubtypeQualifiersOnly(anno, READONLY) + && !AnnotationUtils.areSame(anno, READONLY)) { + defaultAnno = anno; + } + } + if (!isAdaptedSubtype(useAnno, defaultAnno)) { + checker.reportError(tree, "type.invalid.annotations.on.use", defaultAnno, useAnno); + } + } + } + + @Override + public Void visitNewClass(NewClassTree node, Void p) { + checkNewInstanceCreation(node); + return super.visitNewClass(node, p); + } + + @Override + public Void visitNewArray(NewArrayTree node, Void p) { + checkNewInstanceCreation(node); + return super.visitNewArray(node, p); + } + + private void checkNewInstanceCreation(Tree node) { + // Ensure only @Mutable/@Immutable/@ReceiverDependantMutable/@PolyMutable are used on new + // instance creation + AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(node); + if (!(type.hasAnnotation(IMMUTABLE) + || type.hasAnnotation(MUTABLE) + || type.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE) + || type.hasAnnotation(POLY_MUTABLE))) { + checker.reportError(node, "pico.new.invalid", type); + } + } + + @Override + public Void visitMethodInvocation(MethodInvocationTree node, Void p) { + super.visitMethodInvocation(node, p); + ParameterizedExecutableType mfuPair = atypeFactory.methodFromUse(node); + AnnotatedExecutableType invokedMethod = mfuPair.executableType; + ExecutableElement invokedMethodElement = invokedMethod.getElement(); + // Only check invocability if it's super call, as non-super call is already checked + // by super implementation(of course in both cases, invocability is not checked when + // invoking static methods) + if (!ElementUtils.isStatic(invokedMethodElement) + && TreeUtils.isSuperConstructorCall(node)) { + checkMethodInvocability(invokedMethod, node); + } + return null; + } + + @Override + protected AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { + AnnotationMirrorSet result = new AnnotationMirrorSet(); + result.add(atypeFactory.getQualifierHierarchy().getBottomAnnotation(BOTTOM)); + return result; + } + + @Override + protected AnnotationMirrorSet getThrowUpperBoundAnnotations() { + AnnotationMirrorSet result = new AnnotationMirrorSet(); + result.add(atypeFactory.getQualifierHierarchy().getTopAnnotation(READONLY)); + return result; + } + + @Override + public void processClassTree(ClassTree node) { + TypeElement typeElement = TreeUtils.elementFromDeclaration(node); + // TODO Don't process anonymous class. I'm not even sure if whether + // processClassTree(ClassTree) is + // called on anonymous class tree + if (typeElement.toString().contains("anonymous")) { + super.processClassTree(node); + return; + } + + AnnotatedDeclaredType bound = + PICOTypeUtil.getBoundTypeOfTypeDeclaration(typeElement, atypeFactory); + // Has to be either @Mutable, @ReceiverDependantMutable or @Immutable, nothing else + if (!bound.hasAnnotation(MUTABLE) + && !bound.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE) + && !bound.hasAnnotation(IMMUTABLE)) { + checker.reportError(node, "class.bound.invalid", bound); + return; // Doesn't process the class tree anymore + } + + // Issue warnings on implicit shallow immutable: + // Condition: + // * Class decl == Immutable or RDM * move rdm default error here. see 3.6.3 last part. + // liansun + // * Member is field + // * Member's declared bound == Mutable + // * Member's use anno == null + if (bound.hasAnnotation(IMMUTABLE) || bound.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { + for (Tree member : node.getMembers()) { + if (member.getKind() == Kind.VARIABLE) { + Element ele = TreeUtils.elementFromTree(member); + assert ele != null; + // fromElement will not apply defaults, if no explicit anno exists in code, + // mirror have no anno + AnnotatedTypeMirror noDefaultMirror = atypeFactory.fromElement(ele); + TypeMirror ty = ele.asType(); + if (ty.getKind() == TypeKind.TYPEVAR) { + ty = TypesUtils.upperBound(ty); + } + if (AnnotationUtils.containsSameByName( + atypeFactory.getTypeDeclarationBounds(ty), MUTABLE) + && !noDefaultMirror.hasAnnotationInHierarchy(READONLY)) { + checker.reportError(member, "implicit.shallow.immutable"); + } + } + } + } + super.processClassTree(node); + } + + /** + * The invoked constructor’s return type adapted to the invoking constructor’s return type must + * be a supertype of the invoking constructor’s return type. Since InitializationChecker does + * not apply any type rules at here, only READONLY hierarchy is checked. + * + * @param superCall the super invocation, e.g., "super()" + * @param errorKey the error key, e.g., "super.invocation.invalid" + */ + @Override + protected void checkThisOrSuperConstructorCall( + MethodInvocationTree superCall, @CompilerMessageKey String errorKey) { + MethodTree enclosingMethod = methodTree; + AnnotatedTypeMirror superType = atypeFactory.getAnnotatedType(superCall); + AnnotatedExecutableType constructorType = atypeFactory.getAnnotatedType(enclosingMethod); + AnnotationMirror superTypeMirror = superType.getAnnotationInHierarchy(READONLY); + AnnotationMirror constructorTypeMirror = + constructorType.getReturnType().getAnnotationInHierarchy(READONLY); + if (!atypeFactory + .getQualifierHierarchy() + .isSubtypeQualifiersOnly(constructorTypeMirror, superTypeMirror)) { + checker.reportError( + superCall, errorKey, constructorTypeMirror, superCall, superTypeMirror); + } + super.checkThisOrSuperConstructorCall(superCall, errorKey); + } + + @Override + protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType) { + QualifierHierarchy qualifierHierarchy = atypeFactory.getQualifierHierarchy(); + + final TypeKind castTypeKind = castType.getKind(); + if (castTypeKind == TypeKind.DECLARED) { + // Don't issue an error if the mutability annotations are equivalent to the qualifier + // upper bound + // of the type. + // BaseTypeVisitor#isTypeCastSafe is not used, to be consistent with inference which + // only have mutability qualifiers + // if inference is supporting FBC in the future, this overridden method can be removed. + AnnotatedDeclaredType castDeclared = (AnnotatedDeclaredType) castType; + + AnnotationMirror bound = + qualifierHierarchy.findAnnotationInHierarchy( + atypeFactory.getTypeDeclarationBounds(castDeclared.getUnderlyingType()), + READONLY); + assert bound != null; + + if (AnnotationUtils.areSame(castDeclared.getAnnotationInHierarchy(READONLY), bound)) { + return true; + } + } + + return super.isTypeCastSafe(castType, exprType); + } + + @Override + protected boolean commonAssignmentCheck( + AnnotatedTypeMirror varType, + AnnotatedTypeMirror valueType, + Tree valueTree, + String errorKey, + Object... extraArgs) { + // TODO: WORKAROUND: anonymous class handling + if (TypesUtils.isAnonymous(valueType.getUnderlyingType())) { + AnnotatedTypeMirror newValueType = varType.deepCopy(); + newValueType.replaceAnnotation(valueType.getAnnotationInHierarchy(READONLY)); + valueType = newValueType; + } + return super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); + } +} diff --git a/src/main/java/pico/typecheck/PICOStore.java b/src/main/java/pico/typecheck/PICOStore.java deleted file mode 100644 index ea5281a..0000000 --- a/src/main/java/pico/typecheck/PICOStore.java +++ /dev/null @@ -1,18 +0,0 @@ -package pico.typecheck; - -import org.checkerframework.checker.initialization.InitializationStore; -import org.checkerframework.framework.flow.CFAbstractAnalysis; - -/** - * Created by mier on 15/08/17. - */ -public class PICOStore extends InitializationStore{ - - public PICOStore(CFAbstractAnalysis analysis, boolean sequentialSemantics) { - super(analysis, sequentialSemantics); - } - - public PICOStore(CFAbstractAnalysis analysis, PICOStore other) { - super(other); - } -} diff --git a/src/main/java/pico/typecheck/PICOTransfer.java b/src/main/java/pico/typecheck/PICOTransfer.java deleted file mode 100644 index db53c31..0000000 --- a/src/main/java/pico/typecheck/PICOTransfer.java +++ /dev/null @@ -1,49 +0,0 @@ -package pico.typecheck; - -import com.sun.source.tree.ClassTree; -import com.sun.source.tree.MethodTree; -import com.sun.source.tree.VariableTree; -import org.checkerframework.checker.initialization.InitializationTransfer; -import org.checkerframework.dataflow.analysis.RegularTransferResult; -import org.checkerframework.dataflow.analysis.TransferInput; -import org.checkerframework.dataflow.analysis.TransferResult; -import org.checkerframework.dataflow.cfg.node.AssignmentNode; -import org.checkerframework.dataflow.cfg.node.NullLiteralNode; -import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.javacutil.TreeUtils; - -import javax.lang.model.element.VariableElement; - -/** - * Created by mier on 15/08/17. - */ -public class PICOTransfer extends InitializationTransfer{ - - public PICOTransfer(PICOAnalysis analysis) { - super(analysis); - } - - @Override - public TransferResult visitAssignment(AssignmentNode n, TransferInput in) { - if (n.getExpression() instanceof NullLiteralNode && n.getTarget().getTree() instanceof VariableTree) { - VariableElement varElement = TreeUtils.elementFromDeclaration((VariableTree) n.getTarget().getTree()); - // Below is for removing false positive warning of bottom illegal write cacused by refining field to @Bottom if - // field initializer is null. - // Forbid refinement from null literal in initializer to fields variable tree(identifier tree not affected, e.g. - // assigning a field as null in instance methods or constructors) - if (varElement != null && varElement.getKind().isField()) { - PICOStore store = in.getRegularStore(); - PICOValue storeValue = in.getValueOfSubNode(n); - PICOValue value = moreSpecificValue(null, storeValue); - return new RegularTransferResult<>(finishValue(value, store), store); - } - } - TransferResult result = super.visitAssignment(n, in); - return result; - } - - @Override - protected void addFieldValues(PICOStore info, AnnotatedTypeFactory factory, ClassTree classTree, MethodTree methodTree) { - return; - } -} diff --git a/src/main/java/pico/typecheck/PICOValidator.java b/src/main/java/pico/typecheck/PICOValidator.java index 9e042bf..5c42a4b 100644 --- a/src/main/java/pico/typecheck/PICOValidator.java +++ b/src/main/java/pico/typecheck/PICOValidator.java @@ -6,19 +6,26 @@ import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeValidator; import org.checkerframework.common.basetype.BaseTypeVisitor; -import org.checkerframework.framework.source.Result; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType; +import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TreePathUtil; +import pico.common.PICOTypeUtil; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ElementKind; import javax.lang.model.element.VariableElement; -import static pico.typecheck.PICOAnnotationMirrorHolder.BOTTOM; -import static pico.typecheck.PICOAnnotationMirrorHolder.IMMUTABLE; -import static pico.typecheck.PICOAnnotationMirrorHolder.RECEIVER_DEPENDANT_MUTABLE; +import java.util.Objects; +import java.util.Set; + +import static pico.typecheck.PICOAnnotationMirrorHolder.*; +import static pico.typecheck.PICOAnnotationMirrorHolder.MUTABLE; /** * Created by mier on 29/09/17. @@ -35,7 +42,37 @@ public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) { checkStaticReceiverDependantMutableError(type, tree); checkImplicitlyImmutableTypeError(type, tree); checkOnlyOneAssignabilityModifierOnField(tree); + return super.visitDeclared(type, tree); + + } + + @Override + protected boolean shouldCheckTopLevelDeclaredOrPrimitiveType(AnnotatedTypeMirror type, Tree tree) { + // check top annotations in extends/implements clauses + if ((tree.getKind() == Kind.IDENTIFIER || tree.getKind() == Kind.PARAMETERIZED_TYPE) && + PICONoInitAnnotatedTypeFactory.PICOSuperClauseAnnotator.isSuperClause(atypeFactory.getPath(tree))) { + return true; + } + // allow RDM on mutable fields with enclosing class bounded with mutable + if (tree instanceof VariableTree) { + VariableElement element = TreeUtils.elementFromDeclaration((VariableTree)tree); + if (element.getKind() == ElementKind.FIELD && ElementUtils.enclosingTypeElement(element) != null) { + Set enclosingBound = + atypeFactory.getTypeDeclarationBounds( + Objects.requireNonNull(ElementUtils.enclosingTypeElement(element)).asType()); + + Set declaredBound = + atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType()); + + if(AnnotationUtils.containsSameByName(declaredBound, MUTABLE) + && type.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE) + && AnnotationUtils.containsSameByName(enclosingBound, MUTABLE)) { + return false; + } + } + } + return super.shouldCheckTopLevelDeclaredOrPrimitiveType(type, tree); } @Override @@ -52,14 +89,16 @@ public Void visitPrimitive(AnnotatedPrimitiveType type, Tree tree) { } private void checkStaticReceiverDependantMutableError(AnnotatedTypeMirror type, Tree tree) { - if (PICOTypeUtil.inStaticScope(visitor.getCurrentPath()) - && !"".contentEquals(TreeUtils.enclosingClass(visitor.getCurrentPath()).getSimpleName())// Exclude @RDM usages in anonymous classes + if (!type.isDeclaration() // variables in static contexts and static fields use class decl as enclosing type + && PICOTypeUtil.inStaticScope(visitor.getCurrentPath()) + && !"".contentEquals(Objects.requireNonNull(TreePathUtil.enclosingClass(visitor.getCurrentPath())).getSimpleName()) // Exclude @RDM usages in anonymous classes && type.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { reportValidityResult("static.receiverdependantmutable.forbidden", type, tree); } } - /**Check that implicitly immutable type has immutable or bottom type. Dataflow might refine immtable type to @Bottom, + /**Check that implicitly immutable type has immutable or bottom type. Dataflow might refine immutable type to + * {@code @Bottom} (see RefineFromNull.java), * so we accept @Bottom as a valid qualifier for implicitly immutable types*/ private void checkImplicitlyImmutableTypeError(AnnotatedTypeMirror type, Tree tree) { if (PICOTypeUtil.isImplicitlyImmutableType(type) && !type.hasAnnotation(IMMUTABLE) && !type.hasAnnotation(BOTTOM)) { @@ -79,7 +118,7 @@ private void checkOnlyOneAssignabilityModifierOnField(Tree tree) { } private void reportFieldMultipleAssignabilityModifiersError(VariableElement field) { - checker.report(Result.failure("one.assignability.invalid", field), field); + checker.reportError(field, "one.assignability.invalid", field); isValid = false; } } diff --git a/src/main/java/pico/typecheck/PICOValue.java b/src/main/java/pico/typecheck/PICOValue.java deleted file mode 100644 index 82545a2..0000000 --- a/src/main/java/pico/typecheck/PICOValue.java +++ /dev/null @@ -1,17 +0,0 @@ -package pico.typecheck; - -import org.checkerframework.framework.flow.CFAbstractAnalysis; -import org.checkerframework.framework.flow.CFAbstractValue; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeMirror; -import java.util.Set; - -/** - * Created by mier on 15/08/17. - */ -public class PICOValue extends CFAbstractValue{ - public PICOValue(CFAbstractAnalysis analysis, Set annotations, TypeMirror underlyingType) { - super(analysis, annotations, underlyingType); - } -} diff --git a/src/main/java/pico/typecheck/PICOViewpointAdapter.java b/src/main/java/pico/typecheck/PICOViewpointAdapter.java index fdb3d4d..0eee7ec 100644 --- a/src/main/java/pico/typecheck/PICOViewpointAdapter.java +++ b/src/main/java/pico/typecheck/PICOViewpointAdapter.java @@ -6,7 +6,6 @@ import static pico.typecheck.PICOAnnotationMirrorHolder.POLY_MUTABLE; import static pico.typecheck.PICOAnnotationMirrorHolder.READONLY; import static pico.typecheck.PICOAnnotationMirrorHolder.RECEIVER_DEPENDANT_MUTABLE; -import static pico.typecheck.PICOAnnotationMirrorHolder.SUBSTITUTABLE_POLY_MUTABLE; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; @@ -19,11 +18,9 @@ import org.checkerframework.javacutil.BugInCF; import exceptions.UnkownImmutabilityQualifierException; +import pico.common.ExtendedViewpointAdapter; -/** - * Created by mier on 20/06/17. - */ -public class PICOViewpointAdapter extends AbstractViewpointAdapter { +public class PICOViewpointAdapter extends AbstractViewpointAdapter implements ExtendedViewpointAdapter { public PICOViewpointAdapter(AnnotatedTypeFactory atypeFactory) { super(atypeFactory); @@ -53,21 +50,22 @@ protected AnnotationMirror combineAnnotationWithAnnotation(AnnotationMirror rece } else if (AnnotationUtils.areSame(declaredAnnotation, BOTTOM)) { return BOTTOM; } else if (AnnotationUtils.areSame(declaredAnnotation, POLY_MUTABLE)) { - return SUBSTITUTABLE_POLY_MUTABLE; + return POLY_MUTABLE; } else if (AnnotationUtils.areSame(declaredAnnotation, RECEIVER_DEPENDANT_MUTABLE)) { return receiverAnnotation; } else { - throw new BugInCF("Unkown declared modifier: " + declaredAnnotation, new UnkownImmutabilityQualifierException()); + throw new BugInCF("Unknown declared modifier: " + declaredAnnotation, new UnkownImmutabilityQualifierException()); } } -// -// @Override -// protected AnnotationMirror getModifier(AnnotatedTypeMirror atm, AnnotatedTypeFactory f) { -// return atm.getAnnotationInHierarchy(READONLY); -// } -// @Override -// protected AnnotationMirror extractModifier(AnnotatedTypeMirror atm, TypeFactory f) { -// return null; -// } + public AnnotatedTypeMirror rawCombineAnnotationWithType(AnnotationMirror anno, AnnotatedTypeMirror type) { +// System.err.println("VPA: " + anno + " ->" + type); + return combineAnnotationWithType(anno, type); + } + + @Override + public AnnotationMirror rawCombineAnnotationWithAnnotation(AnnotationMirror anno, AnnotationMirror type) { +// System.err.println("VPA: " + anno + " ->" + type); + return combineAnnotationWithAnnotation(anno, type); + } } diff --git a/src/main/java/pico/typecheck/PICOVisitor.java b/src/main/java/pico/typecheck/PICOVisitor.java deleted file mode 100644 index 5249ae4..0000000 --- a/src/main/java/pico/typecheck/PICOVisitor.java +++ /dev/null @@ -1,564 +0,0 @@ -package pico.typecheck; - -import static pico.typecheck.PICOAnnotationMirrorHolder.BOTTOM; -import static pico.typecheck.PICOAnnotationMirrorHolder.COMMITED; -import static pico.typecheck.PICOAnnotationMirrorHolder.IMMUTABLE; -import static pico.typecheck.PICOAnnotationMirrorHolder.MUTABLE; -import static pico.typecheck.PICOAnnotationMirrorHolder.POLY_MUTABLE; -import static pico.typecheck.PICOAnnotationMirrorHolder.READONLY; -import static pico.typecheck.PICOAnnotationMirrorHolder.RECEIVER_DEPENDANT_MUTABLE; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; - -import org.checkerframework.checker.initialization.InitializationVisitor; -import org.checkerframework.checker.initialization.qual.UnderInitialization; -import org.checkerframework.common.basetype.BaseTypeChecker; -import org.checkerframework.common.basetype.TypeValidator; -import org.checkerframework.framework.source.Result; -import org.checkerframework.framework.type.AnnotatedTypeFactory.ParameterizedMethodType; -import org.checkerframework.framework.type.AnnotatedTypeMirror; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; -import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; -import org.checkerframework.framework.util.AnnotatedTypes; -import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.BugInCF; -import org.checkerframework.javacutil.ElementUtils; -import org.checkerframework.javacutil.Pair; -import org.checkerframework.javacutil.TreeUtils; -import org.checkerframework.javacutil.TypesUtils; - -import com.sun.source.tree.ArrayAccessTree; -import com.sun.source.tree.AssignmentTree; -import com.sun.source.tree.ClassTree; -import com.sun.source.tree.CompoundAssignmentTree; -import com.sun.source.tree.ExpressionTree; -import com.sun.source.tree.MethodInvocationTree; -import com.sun.source.tree.MethodTree; -import com.sun.source.tree.NewArrayTree; -import com.sun.source.tree.NewClassTree; -import com.sun.source.tree.Tree; -import com.sun.source.tree.Tree.Kind; -import com.sun.source.tree.UnaryTree; -import com.sun.source.tree.VariableTree; - -/** - * Created by mier on 20/06/17. - * Enforce PICO type rules. - */ -public class PICOVisitor extends InitializationVisitor { - - private final boolean shouldOutputFbcError; - final Map fbcViolatedMethods; - - public PICOVisitor(BaseTypeChecker checker) { - super(checker); - shouldOutputFbcError = checker.hasOption("printFbcErrors"); - fbcViolatedMethods = shouldOutputFbcError ? new HashMap<>() : null; - } - - @Override - protected TypeValidator createTypeValidator() { - return new PICOValidator(checker, this, atypeFactory); - } - - // This method is for validating usage of mutability qualifier is conformable to element declaration, - // Ugly thing here is that declarationType is not the result of calling the other method - - // PICOTypeUtil#getBoundTypeOfTypeDeclaration. Instead it's the result of calling ATF#getAnnotatedType(Element). - // Why it works is that PICOTypeUtil#getBoundTypeOfTypeDeclaration and ATF#getAnnotatedType(Element) has - // the same effect most of the time except on java.lang.Object. We need to be careful when modifying - // PICOTypeUtil#getBoundTypeOfTypeDeclaration so that it has the same behaviour as ATF#getAnnotatedType(Element) - // (at least for types other than java.lang.Object) - @Override - public boolean isValidUse(AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) { - AnnotationMirror declared = declarationType.getAnnotationInHierarchy(READONLY); - // No need to have special case for java.lang.Object, as it's not by default @Readonly anymore -// if (AnnotationUtils.areSame(declared, atypeFactory.READONLY)) { -// // Special case for java.lang.Object. Usually @Readonly is never used as a bound annotation for a -// // TypeElement. But we want to have @Readonly as the default for java.lang.Object. There is no way -// // of doing this using any exsisting family of @DefaultFor qualifiers, but @ImplicitFor annotation -// // does the trick. But the side effect is, we can't write @ReceiverDependantMutable, which is the -// // correct bound for Object element, in jdk.astub, because otherwise it makes all java.lang.Object -// // to be @ReceiverDependantMutable; Another side effect is here @Readonly is passed into here as -// // the element type for java.lang.Object. So we have to have this special case only for java.lang. -// // Object -// return true; -// } - if (AnnotationUtils.areSame(declared, RECEIVER_DEPENDANT_MUTABLE)) { - // Element is declared with @ReceiverDependantMutable bound, any instantiation is allowed. We don't use - // a subtype check to validate the correct usage here. Because @Readonly is the super type of - // @ReceiverDependantMutable, but it's still considered valid usage. - return true; - } - // At this point, element type can only be @Mutable or @Immutable. Otherwise, it's a problem in - // PICOVisitor#processorClassTree(ClassTree) - assert AnnotationUtils.areSame(declared, MUTABLE) || AnnotationUtils.areSame(declared, IMMUTABLE); - - AnnotationMirror used = useType.getAnnotationInHierarchy(READONLY); - assert AnnotationUtils.areSame(declared, MUTABLE) || AnnotationUtils.areSame(declared, IMMUTABLE); - - if (AnnotationUtils.areSame(declared, MUTABLE) && - !(AnnotationUtils.areSame(used, IMMUTABLE) || AnnotationUtils.areSame(used, RECEIVER_DEPENDANT_MUTABLE))) { - return true; - } - - if (AnnotationUtils.areSame(declared, IMMUTABLE) && - !(AnnotationUtils.areSame(used, MUTABLE) || AnnotationUtils.areSame(used, RECEIVER_DEPENDANT_MUTABLE))) { - return true; - } - - // All valid cases are listed above. So returns false here. - return false; - } - - @Override - protected void commonAssignmentCheck( - Tree varTree, ExpressionTree valueExp, String errorKey) { - AnnotatedTypeMirror var = atypeFactory.getAnnotatedTypeLhs(varTree); - assert var != null : "no variable found for tree: " + varTree; - - if (!validateType(varTree, var)) { - return; - } - - checkAssignability(var, varTree); - - if (varTree instanceof VariableTree) { - VariableElement element = TreeUtils.elementFromDeclaration((VariableTree) varTree); - if (element.getKind() == ElementKind.FIELD && !ElementUtils.isStatic(element)) { - AnnotatedDeclaredType bound = PICOTypeUtil.getBoundTypeOfEnclosingTypeDeclaration(varTree, atypeFactory); - // var is singleton, so shouldn't modify var directly. Otherwise, the variable tree's type will be - // altered permanently, and other clients who access this type will see the change, too. - AnnotatedTypeMirror varAdapted = var.shallowCopy(true); - // Viewpoint adapt varAdapted to the bound. PICOInferenceAnnotatedTypeFactory#viewpointAdaptMember() - // mutates varAdapted, so after the below method is called, varAdapted is the result adapted to bound - atypeFactory.getViewpointAdapter().viewpointAdaptMember(bound, element, varAdapted); - // Pass varAdapted here as lhs type. - // Caution: cannot pass var directly. Modifying type in PICOInferenceTreeAnnotator# - // visitVariable() will cause wrong type to be gotton here, as on inference side, - // atm is uniquely determined by each element. - commonAssignmentCheck(varAdapted, valueExp, errorKey); - return; - } - } - - commonAssignmentCheck(var, valueExp, errorKey); - } - - @Override - protected boolean checkConstructorInvocation(AnnotatedDeclaredType invocation, AnnotatedExecutableType constructor, NewClassTree newClassTree) { - // TODO Is the copied code really needed? - /*Copied Code Start*/ - AnnotatedDeclaredType returnType = (AnnotatedDeclaredType) constructor.getReturnType(); - // When an interface is used as the identifier in an anonymous class (e.g. new Comparable() {}) - // the constructor method will be Object.init() {} which has an Object return type - // When TypeHierarchy attempts to convert it to the supertype (e.g. Comparable) it will return - // null from asSuper and return false for the check. Instead, copy the primary annotations - // to the declared type and then do a subtyping check - if (invocation.getUnderlyingType().asElement().getKind().isInterface() - && TypesUtils.isObject(returnType.getUnderlyingType())) { - final AnnotatedDeclaredType retAsDt = invocation.deepCopy(); - retAsDt.replaceAnnotations(returnType.getAnnotations()); - returnType = retAsDt; - } else if (newClassTree.getClassBody() != null) { - // An anonymous class invokes the constructor of its super class, so the underlying - // types of invocation and returnType are not the same. Call asSuper so they are the - // same and the is subtype tests below work correctly - invocation = AnnotatedTypes.asSuper(atypeFactory, invocation, returnType); - } - /*Copied Code End*/ - - // The immutability return qualifier of the constructor (returnType) must be supertype of the - // constructor invocation immutability qualifier(invocation). - if (!atypeFactory.getTypeHierarchy().isSubtype(invocation, returnType, READONLY)) { - checker.report(Result.failure( - "constructor.invocation.invalid", invocation, returnType), newClassTree); - return false; - } - return true; - } - - @Override - public Void visitMethod(MethodTree node, Void p) { - AnnotatedExecutableType executableType = atypeFactory.getAnnotatedType(node); - AnnotatedDeclaredType bound = PICOTypeUtil.getBoundTypeOfEnclosingTypeDeclaration(node, atypeFactory); - - if (TreeUtils.isConstructor(node)) { - AnnotatedDeclaredType constructorReturnType = (AnnotatedDeclaredType) executableType.getReturnType(); - if (constructorReturnType.hasAnnotation(READONLY) || constructorReturnType.hasAnnotation(POLY_MUTABLE)) { - checker.report(Result.failure("constructor.return.invalid", constructorReturnType), node); - return super.visitMethod(node, p); - } - } else { - AnnotatedDeclaredType declareReceiverType = executableType.getReceiverType(); - if (declareReceiverType != null) { - if (bound != null - && !bound.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE) - && !atypeFactory.getQualifierHierarchy().isSubtype( - declareReceiverType.getAnnotationInHierarchy(READONLY), - bound.getAnnotationInHierarchy(READONLY)) - // Below three are allowed on declared receiver types of instance methods in either @Mutable class or @Immutable class - && !declareReceiverType.hasAnnotation(READONLY) - && !declareReceiverType.hasAnnotation(POLY_MUTABLE)) { - checker.report(Result.failure("method.receiver.incompatible", declareReceiverType), node); - } - } - } - - flexibleOverrideChecker(node); - - // ObjectIdentityMethod check - if (PICOTypeUtil.isObjectIdentityMethod(node, atypeFactory)) { - ObjectIdentityMethodEnforcer.check(atypeFactory.getPath(node.getBody()), atypeFactory, checker); - } - return super.visitMethod(node, p); - } - - private void flexibleOverrideChecker(MethodTree node) { - // Method overriding checks - // TODO Copied from super, hence has lots of duplicate code with super. We need to - // change the signature of checkOverride() method to also pass ExecutableElement for - // viewpoint adaptation. - ExecutableElement methodElement = TreeUtils.elementFromDeclaration(node); - AnnotatedDeclaredType enclosingType = - (AnnotatedDeclaredType) - atypeFactory.getAnnotatedType(methodElement.getEnclosingElement()); - - Map overriddenMethods = - AnnotatedTypes.overriddenMethods(elements, atypeFactory, methodElement); - for (Map.Entry pair : - overriddenMethods.entrySet()) { - AnnotatedDeclaredType overriddenType = pair.getKey(); - AnnotatedExecutableType overriddenMethod = - AnnotatedTypes.asMemberOf( - types, atypeFactory, enclosingType, pair.getValue()); - // Viewpoint adapt super method executable type to current class bound(is this always class bound?) - // to allow flexible overriding - atypeFactory.getViewpointAdapter().viewpointAdaptMethod(enclosingType, pair.getValue() , overriddenMethod); - AnnotatedExecutableType overrider = atypeFactory.getAnnotatedType(node); - if (!checkOverride(node, overrider, enclosingType, overriddenMethod, overriddenType)) { - // Stop at the first mismatch; this makes a difference only if - // -Awarns is passed, in which case multiple warnings might be raised on - // the same method, not adding any value. See Issue 373. - break; - } - } - } - - // Disables method overriding checks in BaseTypeVisitor - @Override - protected boolean checkOverride( - MethodTree overriderTree, AnnotatedDeclaredType overridingType, - AnnotatedExecutableType overridden, AnnotatedDeclaredType overriddenType) { - return true; - } - - @Override - public Void visitAssignment(AssignmentTree node, Void p) { - ExpressionTree variable = node.getVariable(); - // TODO Question Here, receiver type uses flow refinement. But in commonAssignmentCheck to compute lhs type - // , it doesn't. This causes inconsistencies when enforcing immutability and doing subtype check. I overrode - // getAnnotatedTypeLhs() to also use flow sensitive refinement, but came across with "private access" problem - // on field "computingAnnotatedTypeMirrorOfLHS" - checkMutation(node, variable); - return super.visitAssignment(node, p); - } - - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree node, Void p) { - ExpressionTree variable = node.getVariable(); - checkMutation(node, variable); - return super.visitCompoundAssignment(node, p); - } - - @Override - public Void visitUnary(UnaryTree node, Void p) { - if (PICOTypeUtil.isSideEffectingUnaryTree(node)) { - ExpressionTree variable = node.getExpression(); - checkMutation(node, variable); - } - return super.visitUnary(node, p); - } - - private void checkMutation(Tree node, ExpressionTree variable) { - AnnotatedTypeMirror receiverType = atypeFactory.getReceiverType(variable); - // Cannot use receiverTree = TreeUtils.getReceiverTree(variable) to determine if it's - // field assignment or not. Because for field assignment with implicit "this", receiverTree - // is null but receiverType is non-null. We still need to check this case. - if (receiverType != null && !allowWrite(receiverType, variable)) { - reportFieldOrArrayWriteError(node, variable, receiverType); - } - } - - private boolean allowWrite(AnnotatedTypeMirror receiverType, ExpressionTree variable) { - // One pico side, if only receiver is mutable, we allow assigning/reassigning. Because if the field - // is declared as final, Java compiler will catch that, and we couldn't have reached this point - if (PICOTypeUtil.isAssigningAssignableField(variable, atypeFactory)) { - return isAllowedAssignableField(receiverType, variable); - } else if (isInitializingReceiverDependantMutableOrImmutableObject(receiverType)) { - return true; - } else if (receiverType.hasAnnotation(MUTABLE)) { - return true; - } - - return false; - } - - private boolean isAllowedAssignableField(AnnotatedTypeMirror receiverType, ExpressionTree node) { - Element fieldElement = TreeUtils.elementFromUse(node); - AnnotatedTypeMirror fieldType = atypeFactory.getAnnotatedType(fieldElement); - if (fieldElement == null) return false; - // Forbid the case that might break type soundness - return !(receiverType.hasAnnotation(READONLY) && fieldType.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)); - } - - private boolean isInitializingReceiverDependantMutableOrImmutableObject(AnnotatedTypeMirror receiverType) { - if (receiverType.hasAnnotation(UnderInitialization.class) && receiverType.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) { - return true; - } else if (receiverType.hasAnnotation(UnderInitialization.class) && receiverType.hasAnnotation(IMMUTABLE)) { - return true; - } else if (receiverType.hasAnnotation(UnderInitialization.class) && receiverType.hasAnnotation(MUTABLE)) { - return true; - } else { - return false; - } - } - - private void reportFieldOrArrayWriteError(Tree node, ExpressionTree variable, AnnotatedTypeMirror receiverType) { - if (variable.getKind() == Kind.MEMBER_SELECT) { - checker.report(Result.failure("illegal.field.write", receiverType), TreeUtils.getReceiverTree(variable)); - } else if (variable.getKind() == Kind.IDENTIFIER) { - checker.report(Result.failure("illegal.field.write", receiverType), node); - } else if (variable.getKind() == Kind.ARRAY_ACCESS) { - checker.report(Result.failure("illegal.array.write", receiverType), ((ArrayAccessTree)variable).getExpression()); - } else { - throw new BugInCF("Unknown assignment variable at: ", node); - } - } - - @Override - public Void visitVariable(VariableTree node, Void p) { - VariableElement element = TreeUtils.elementFromDeclaration(node); - if (element != null && element.getKind() == ElementKind.FIELD) { - AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(element); - if (type.hasAnnotation(POLY_MUTABLE)) { - checker.report(Result.failure("field.polymutable.forbidden", element), node); - } - } - return super.visitVariable(node, p); - } - - @Override - public Void visitNewClass(NewClassTree node, Void p) { - checkNewInstanceCreation(node); - return super.visitNewClass(node, p); - } - - @Override - public Void visitNewArray(NewArrayTree node, Void p) { - checkNewInstanceCreation(node); - return super.visitNewArray(node, p); - } - - private void checkNewInstanceCreation(Tree node) { - // Ensure only @Mutable/@Immutable/@ReceiverDependantMutable/@PolyMutable are used on new instance creation - AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(node); - if (!(type.hasAnnotation(IMMUTABLE) || type.hasAnnotation(MUTABLE) || - type.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE) || type.hasAnnotation(POLY_MUTABLE))) { - checker.report(Result.failure("pico.new.invalid", type), node); - } - } - - @Override - public Void visitMethodInvocation(MethodInvocationTree node, Void p) { - super.visitMethodInvocation(node, p); - ParameterizedMethodType mfuPair = - atypeFactory.methodFromUse(node); - AnnotatedExecutableType invokedMethod = mfuPair.methodType; - ExecutableElement invokedMethodElement = invokedMethod.getElement(); - // Only check invocability if it's super call, as non-super call is already checked - // by super implementation(of course in both cases, invocability is not checked when - // invoking static methods) - if (!ElementUtils.isStatic(invokedMethodElement) && TreeUtils.isSuperCall(node)) { - checkMethodInvocability(invokedMethod, node); - } - return null; - } - - // TODO Find a better way to inject saveFbcViolatedMethods instead of copying lots of code from super method - @Override - protected void checkMethodInvocability(AnnotatedExecutableType method, MethodInvocationTree node) { - // Check subclass constructor calls the correct super class constructor: mutable calls mutable; immutable - // calls immutable; any calls receiverdependantmutable - if (method.getElement().getKind() == ElementKind.CONSTRUCTOR) { - AnnotatedTypeMirror subClassConstructorReturnType = atypeFactory.getReceiverType(node); - AnnotatedTypeMirror superClassConstructorReturnType = method.getReturnType(); - // superClassConstructorReturnType is already the result of viewpoint adaptation, so subClassConstructorReturnType <: - // superClassConstructorReturnType is enough to determine the super constructor invocation is valid or not - if (!atypeFactory.getTypeHierarchy().isSubtype(subClassConstructorReturnType, superClassConstructorReturnType, READONLY)) { - checker.report( - Result.failure( - "super.constructor.invocation.incompatible", subClassConstructorReturnType, superClassConstructorReturnType), node); - } - return; - } - - /*Copied Code Starts*/ - if (method.getReceiverType() == null) { - // Static methods don't have a receiver. - return; - } - - AnnotatedTypeMirror methodReceiver = method.getReceiverType().getErased(); - AnnotatedTypeMirror treeReceiver = methodReceiver.shallowCopy(false); - AnnotatedTypeMirror rcv = atypeFactory.getReceiverType(node); - - treeReceiver.addAnnotations(rcv.getEffectiveAnnotations()); - - if (!skipReceiverSubtypeCheck(node, methodReceiver, rcv) - && !atypeFactory.getTypeHierarchy().isSubtype(treeReceiver, methodReceiver)) { - checker.report( - Result.failure( - "method.invocation.invalid", - TreeUtils.elementFromUse(node), - treeReceiver.toString(), - methodReceiver.toString()), - node); - /*Difference Starts*/ - if (shouldOutputFbcError) { - saveFbcViolatedMethods(TreeUtils.elementFromUse(node), treeReceiver.toString(), methodReceiver.toString()); - } - /*Different Ends*/ - } - /*Copied Code Ends*/ - } - - private void saveFbcViolatedMethods(ExecutableElement method, String actualReceiver, String declaredReceiver) { - if (actualReceiver.contains("@UnderInitialization") && declaredReceiver.contains("@Initialized")) { - String key = ElementUtils.enclosingClass(method) + "#" + method; - Integer times = fbcViolatedMethods.get(key) == null ? 1 : fbcViolatedMethods.get(key) + 1; - fbcViolatedMethods.put(key, times); - } - } - - @Override - protected void checkFieldsInitialized(Tree blockNode, boolean staticFields, PICOStore store, List receiverAnnotations) { - // If a class doesn't have constructor, it cannot be initialized as @Immutable, therefore no need to check uninitialized fields - if (TreeUtils.isClassTree(blockNode)) return; - if (blockNode.getKind() == Kind.METHOD && TreeUtils.isConstructor((MethodTree)blockNode)) { - // Only raise errors when in @Immutable or @ReceiverDependantMutable constructors. As @Mutable constructor can initialized - // those fields out of constructor - MethodTree methodTree = (MethodTree) blockNode; - AnnotatedExecutableType executableType = atypeFactory.getAnnotatedType(methodTree); - AnnotatedDeclaredType constructorReturnType = (AnnotatedDeclaredType) executableType.getReturnType(); - // Only care abstract state initialization in @Immutable and @ReceiverDependantMutable constructors, as @Mutable constructors - // only allows instantiating @Mutable objects and fields can be initialized later - if (!(constructorReturnType.hasAnnotation(IMMUTABLE) || constructorReturnType.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE))) { - return; - } - } - super.checkFieldsInitialized(blockNode, staticFields, store, receiverAnnotations); - } - - @Override - protected Set getExceptionParameterLowerBoundAnnotations() { - Set result = new HashSet<>(); - result.add(atypeFactory.getQualifierHierarchy().getBottomAnnotation(BOTTOM)); - result.add(COMMITED); - return result; - } - - @Override - protected Set getThrowUpperBoundAnnotations() { - Set result = new HashSet<>(); - result.add(atypeFactory.getQualifierHierarchy().getTopAnnotation(READONLY)); - result.add(COMMITED); - return result; - } - - @Override - public void processClassTree(ClassTree node) { - TypeElement typeElement = TreeUtils.elementFromDeclaration(node); - // TODO Don't process anonymous class. I'm not even sure if whether processClassTree(ClassTree) is - // called on anonymous class tree - if (typeElement.toString().contains("anonymous")) { - super.processClassTree(node); - return; - } - - AnnotatedDeclaredType bound = PICOTypeUtil.getBoundTypeOfTypeDeclaration(typeElement, atypeFactory); - // Has to be either @Mutable, @ReceiverDependantMutable or @Immutable, nothing else - if (!bound.hasAnnotation(MUTABLE) && !bound.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE) && !bound.hasAnnotation(IMMUTABLE)) { - checker.report(Result.failure("class.bound.invalid", bound), node); - return;// Doesn't process the class tree anymore - } - if (!checkCompatabilityBetweenBoundAndSuperClassesBounds(node, typeElement, bound)) { - return; - } - - if (!checkCompatabilityBetweenBoundAndExtendsImplements(node, bound)) { - return; - } - - // Reach this point iff 1) bound annotation is one of mutable, rdm or immutable; - // 2) bound is compatible with bounds on super types. Only continue if bound check - // passed. Reaching here already means having passed bound check. - super.processClassTree(node); - } - - private boolean checkCompatabilityBetweenBoundAndSuperClassesBounds(ClassTree node, TypeElement typeElement, AnnotatedDeclaredType bound) { - // Must have compatible bound annotation as the direct super types - List superBounds = PICOTypeUtil.getBoundTypesOfDirectSuperTypes(typeElement, atypeFactory); - for (AnnotatedDeclaredType superBound : superBounds) { - // If annotation on super bound is @ReceiverDependantMutable, then any valid bound is permitted. - if (superBound.hasAnnotation(RECEIVER_DEPENDANT_MUTABLE)) continue; - // super bound is either @Mutable or @Immutable. Must be the subtype of the corresponding super bound - if (!atypeFactory.getQualifierHierarchy().isSubtype( - bound.getAnnotationInHierarchy(READONLY), superBound.getAnnotationInHierarchy(READONLY))) { - checker.report(Result.failure("subclass.bound.incompatible", bound, superBound), node); - return false; - } - } - return true; - } - - private boolean checkCompatabilityBetweenBoundAndExtendsImplements(ClassTree node, AnnotatedDeclaredType bound) { - boolean hasSame; - Tree ext = node.getExtendsClause(); - if (ext != null) { - AnnotatedTypeMirror extendsType = atypeFactory.getAnnotatedType(ext); - hasSame = bound.getAnnotations().size() == extendsType.getAnnotations().size() - && AnnotationUtils.areSame(extendsType.getAnnotationInHierarchy(READONLY), - bound.getAnnotationInHierarchy(READONLY)); - if (!hasSame) { - checker.report(Result.failure("bound.extends.incompatabile"), node); - return false; - } - } - - List impls = node.getImplementsClause(); - if (impls != null) { - for (Tree im : impls) { - AnnotatedTypeMirror implementsType = atypeFactory.getAnnotatedType(im); - hasSame = bound.getAnnotations().size() == implementsType.getAnnotations().size() - && AnnotationUtils.areSame(implementsType.getAnnotationInHierarchy(READONLY), - bound.getAnnotationInHierarchy(READONLY)); - if (!hasSame) { - checker.report(Result.failure("bound.implements.incompatabile"), node); - return false; - } - } - } - return true; - } -} diff --git a/src/main/java/pico/typecheck/jdk.astub b/src/main/java/pico/typecheck/jdk.astub index 9091e38..21a1a9e 100644 --- a/src/main/java/pico/typecheck/jdk.astub +++ b/src/main/java/pico/typecheck/jdk.astub @@ -12,9 +12,7 @@ class Object { @ReceiverDependantMutable Object(); Class getClass(@Readonly Object this); String toString(@Readonly Object this); - @OnlyDependantOnAbstractStateField int hashCode(@Readonly Object this); - @OnlyDependantOnAbstractStateField boolean equals(@Readonly Object this, @Readonly Object var1); @ReceiverDependantMutable Object clone(@ReceiverDependantMutable Object this); @ObjectIdentityMethod @@ -113,6 +111,11 @@ interface Collection { boolean contains(@Readonly Collection this, @Readonly Object o); } +@ReceiverDependantMutable +public abstract class AbstractCollection implements Collection { + public abstract int size(@Readonly AbstractCollection this); +} + @ReceiverDependantMutable class ArrayList { @ReceiverDependantMutable ArrayList(); @@ -125,7 +128,6 @@ class ArrayList { boolean contains(@Readonly ArrayList this, @Readonly Object o); int indexOf(@Readonly ArrayList this, @Readonly Object o); int lastIndexOf(@Readonly ArrayList this, @Readonly Object o); - void rangeCheck(@Readonly ArrayList this, int index); Iterator iterator(@Readonly ArrayList this); } @@ -214,6 +216,7 @@ class Arrays { static String toString(int @Readonly [] var0); static boolean equals(float @Readonly [] var0, float @Readonly [] var1); static boolean equals(double @Readonly [] var0, double @Readonly [] var1); + static T[] copyOf(T @Readonly [] original, int newLength); } class Objects { @@ -289,40 +292,9 @@ class ObjectOutputStream { @ReceiverDependantMutable interface Serializable {} -package org.hibernate; -class Session { - Object get(@Readonly Class clazz, @Readonly Serializable id); -} - package java.awt; @ReceiverDependantMutable class Container { void add(@Readonly Component comp, @Readonly Object constraints); } - -package org.apache.maven.plugin.logging; - -interface Log { - void info(@Readonly Log this, @Readonly CharSequence content); - void info(@Readonly Log this, @Readonly CharSequence content, @Readonly Throwable error); - void info(@Readonly Log this, @Readonly Throwable error); - void debug(@Readonly Log this, @Readonly CharSequence content); - void debug(@Readonly Log this, @Readonly CharSequence content, @Readonly Throwable error); - void debug(@Readonly Log this, @Readonly Throwable error); - void warn(@Readonly Log this, @Readonly CharSequence content); - void warn(@Readonly Log this, @Readonly CharSequence content, @Readonly Throwable error); - void warn(@Readonly Log this, @Readonly Throwable error); - void error(@Readonly Log this, @Readonly CharSequence content); - void error(@Readonly Log this, @Readonly CharSequence content, @Readonly Throwable error); - void error(@Readonly Log this, @Readonly Throwable error); - boolean isInfoEnabled(@Readonly Log this); - boolean isDebugEnabled(@Readonly Log this); - boolean isWarnEnabled(@Readonly Log this); - boolean isErrorEnabled(@Readonly Log this); -} - -package org.apache.commons.io; -class FileUtils { - static long copyFile(@Readonly File input, OutputStream output); -} diff --git a/src/main/java/pico/typecheck/messages.properties b/src/main/java/pico/typecheck/messages.properties index 105fe05..1e88b57 100644 --- a/src/main/java/pico/typecheck/messages.properties +++ b/src/main/java/pico/typecheck/messages.properties @@ -1,9 +1,8 @@ constructor.invocation.invalid=Cannot not instantiate type: %s out of constructor: %s constructor.return.invalid=Invalid constructor return type: %s -method.receiver.incompatible -class.bound.invalid -subclass.bound.incompatible -super.constructor.invocation.incompatible=Subclass constructor: %s is not compatible with super class constructor: %s +method.receiver.incompatible=Incompatible method receiver: %s +class.bound.invalid=Invalid class bound: %s +subclass.bound.incompatible=Incompatible subclass bound: %s illegal.field.write=Cannot write field via receiver: %s illegal.array.write=Cannot write array via receiver: %s static.receiverdependantmutable.forbidden=%s is forbidden in static context @@ -13,3 +12,4 @@ one.assignability.invalid=Only one assignability qualifier is allowed on %s object.identity.method.invocation.invalid=Cannot invoke non-object identity method %s from object identity context! object.identity.field.access.invalid=Object identity context cannot reference non abstract state field %s! object.identity.static.field.access.forbidden=Object identity context cannot reference static field %s! +implicit.shallow.immutable=Write an explicit mutable qualifier if wants to exclude the field from the abstract state! Otherwise change the class mutability of this object ! diff --git a/src/main/java/qual/Assignable.java b/src/main/java/qual/Assignable.java index 01e5794..7ba53c7 100644 --- a/src/main/java/qual/Assignable.java +++ b/src/main/java/qual/Assignable.java @@ -1,5 +1,7 @@ package qual; +import org.checkerframework.checker.initialization.qual.HoldsForDefaultValue; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -13,4 +15,5 @@ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) +@HoldsForDefaultValue public @interface Assignable {} diff --git a/src/main/java/qual/Bottom.java b/src/main/java/qual/Bottom.java index cf17cd4..c5c1c5d 100644 --- a/src/main/java/qual/Bottom.java +++ b/src/main/java/qual/Bottom.java @@ -1,11 +1,9 @@ package qual; import org.checkerframework.framework.qual.DefaultFor; -import org.checkerframework.framework.qual.DefaultInUncheckedCodeFor; -import org.checkerframework.framework.qual.ImplicitFor; -import org.checkerframework.framework.qual.LiteralKind; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeKind; import org.checkerframework.framework.qual.TypeUseLocation; import java.lang.annotation.Documented; @@ -14,17 +12,19 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@SubtypeOf({Mutable.class, Immutable.class, PolyMutable.class, ReceiverDependantMutable.class}) -@DefaultFor({ TypeUseLocation.LOWER_BOUND }) -@ImplicitFor(literals = {LiteralKind.NULL}) +/** + * {@code @Bottom} can only be annotated before a type parameter + * (For example, {@code class C<@Bottom T extends MutableBox>{}}). This means {@code @Bottom} + * is the lower bound for this type parameter. + * + * User can explicitly write {@code @Bottom} but it's not necessary because it's + * automatically inferred, and we have -AwarnRedundantAnnotations flag to warn about + * redundant annotations. + */ +@SubtypeOf({Mutable.class, Immutable.class, ReceiverDependantMutable.class}) +@DefaultFor(typeKinds = {TypeKind.NULL}) @Documented @Retention(RetentionPolicy.RUNTIME) -// Stop allowing any explicit usage of @Bottom qualifier in source. As it causes difficulty to -// differentiate correct explicit usage of @Bottom and internally propagated @Bottom. Instead, -// if programmers don't write anything on explicit lower bound(of a wildcard), we still have -// defaulting mechanism to make the explicit lower bound to be @Bottom. They can still use other -// qualifier than @Bottom explicitly on explicit lower bound to have different-than-default type. -@Target({}) -@TargetLocations({}) -@DefaultInUncheckedCodeFor({TypeUseLocation.RETURN}) +@Target({ElementType.TYPE_PARAMETER}) +@TargetLocations({TypeUseLocation.LOWER_BOUND}) public @interface Bottom {} diff --git a/src/main/java/qual/Immutable.java b/src/main/java/qual/Immutable.java index eb66e07..2e79896 100644 --- a/src/main/java/qual/Immutable.java +++ b/src/main/java/qual/Immutable.java @@ -1,10 +1,12 @@ package qual; -import org.checkerframework.framework.qual.ImplicitFor; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.QualifierForLiterals; import org.checkerframework.framework.qual.LiteralKind; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TypeKind; +import org.checkerframework.framework.qual.UpperBoundFor; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -17,10 +19,20 @@ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) -@ImplicitFor(typeNames={Enum.class, String.class, Double.class, Boolean.class, Byte.class, +@DefaultFor(types={Enum.class, String.class, Double.class, Boolean.class, Byte.class, Character.class, Float.class, Integer.class, Long.class, Short.class, Number.class, BigDecimal.class, BigInteger.class}, - literals = { LiteralKind.PRIMITIVE, LiteralKind.STRING}, - types = { TypeKind.INT, TypeKind.BYTE, TypeKind.SHORT, TypeKind.BOOLEAN, + typeKinds = { TypeKind.INT, TypeKind.BYTE, TypeKind.SHORT, TypeKind.BOOLEAN, TypeKind.LONG, TypeKind.CHAR, TypeKind.FLOAT, TypeKind.DOUBLE }) +@QualifierForLiterals({ LiteralKind.PRIMITIVE, LiteralKind.STRING}) +@UpperBoundFor( + typeKinds = { + TypeKind.INT, TypeKind.BYTE, TypeKind.SHORT, TypeKind.BOOLEAN, + TypeKind.LONG, TypeKind.CHAR, TypeKind.FLOAT, TypeKind.DOUBLE + }, + types = { + Enum.class, String.class, Double.class, Boolean.class, Byte.class, + Character.class, Float.class, Integer.class, Long.class, Short.class, Number.class, + BigDecimal.class, BigInteger.class + }) public @interface Immutable {} diff --git a/src/main/java/qual/Mutable.java b/src/main/java/qual/Mutable.java index 83aa788..e846184 100644 --- a/src/main/java/qual/Mutable.java +++ b/src/main/java/qual/Mutable.java @@ -1,6 +1,7 @@ package qual; +import org.checkerframework.checker.initialization.qual.HoldsForDefaultValue; import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.SubtypeOf; @@ -15,4 +16,5 @@ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) +@HoldsForDefaultValue public @interface Mutable {} diff --git a/src/main/java/qual/PolyMutable.java b/src/main/java/qual/PolyMutable.java index 54f45ca..669148a 100644 --- a/src/main/java/qual/PolyMutable.java +++ b/src/main/java/qual/PolyMutable.java @@ -1,6 +1,8 @@ package qual; -import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.PolymorphicQualifier; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; @@ -8,8 +10,9 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@SubtypeOf(Readonly.class) @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) +@PolymorphicQualifier(Readonly.class) +@TargetLocations({TypeUseLocation.PARAMETER, TypeUseLocation.RECEIVER, TypeUseLocation.RETURN, TypeUseLocation.LOCAL_VARIABLE}) public @interface PolyMutable {} diff --git a/src/main/java/qual/Readonly.java b/src/main/java/qual/Readonly.java index f80ef75..0a43e80 100644 --- a/src/main/java/qual/Readonly.java +++ b/src/main/java/qual/Readonly.java @@ -1,7 +1,6 @@ package qual; -import org.checkerframework.framework.qual.DefaultInUncheckedCodeFor; -import org.checkerframework.framework.qual.ImplicitFor; +import org.checkerframework.framework.qual.DefaultFor; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.qual.TypeUseLocation; @@ -15,5 +14,5 @@ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) -@DefaultInUncheckedCodeFor({TypeUseLocation.PARAMETER, TypeUseLocation.RECEIVER}) +@DefaultFor({TypeUseLocation.IMPLICIT_UPPER_BOUND}) public @interface Readonly {} diff --git a/src/main/java/qual/ReceiverDependantMutable.java b/src/main/java/qual/ReceiverDependantMutable.java index c7ea1c6..aa27544 100644 --- a/src/main/java/qual/ReceiverDependantMutable.java +++ b/src/main/java/qual/ReceiverDependantMutable.java @@ -1,8 +1,7 @@ package qual; -import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.checker.initialization.qual.HoldsForDefaultValue; import org.checkerframework.framework.qual.SubtypeOf; -import org.checkerframework.framework.qual.TypeUseLocation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; @@ -14,4 +13,5 @@ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) +@HoldsForDefaultValue public @interface ReceiverDependantMutable {} diff --git a/src/main/java/qual/SubstitutablePolyMutable.java b/src/main/java/qual/SubstitutablePolyMutable.java deleted file mode 100644 index 6620c5c..0000000 --- a/src/main/java/qual/SubstitutablePolyMutable.java +++ /dev/null @@ -1,15 +0,0 @@ -package qual; - -import org.checkerframework.framework.qual.PolymorphicQualifier; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@PolymorphicQualifier(Readonly.class) -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target({}) -public @interface SubstitutablePolyMutable {} diff --git a/src/test/java/pico/ImmutabilityInferenceInitialTypecheckTest.java b/src/test/java/pico/ImmutabilityInferenceInitialTypecheckTest.java new file mode 100644 index 0000000..3d3bcac --- /dev/null +++ b/src/test/java/pico/ImmutabilityInferenceInitialTypecheckTest.java @@ -0,0 +1,22 @@ +package pico; + +import org.checkerframework.framework.test.CheckerFrameworkPerFileTest; +import org.checkerframework.framework.test.TestUtilities; +import org.junit.runners.Parameterized.Parameters; +import pico.inference.PICOInferenceChecker; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class ImmutabilityInferenceInitialTypecheckTest extends CheckerFrameworkPerFileTest { + public ImmutabilityInferenceInitialTypecheckTest(File testFile) { + super(testFile, PICOInferenceChecker.class, "", "-Anomsgtext", + "-Anocheckjdk", "-d", "testTmp/typecheck"); + } + + @Parameters + public static List getTestFiles(){ + return new ArrayList<>(TestUtilities.findRelativeNestedJavaFiles("testinput", "typecheck")); + } +} diff --git a/src/test/java/pico/ImmutabilityInferenceTest.java b/src/test/java/pico/ImmutabilityInferenceTest.java index 59b88a8..f3c3a53 100644 --- a/src/test/java/pico/ImmutabilityInferenceTest.java +++ b/src/test/java/pico/ImmutabilityInferenceTest.java @@ -10,6 +10,7 @@ import org.junit.runners.Parameterized.Parameters; import checkers.inference.test.CFInferenceTest; +import org.plumelib.util.IPair; import pico.inference.PICOInferenceChecker; import pico.inference.solver.PICOSolverEngine; @@ -17,13 +18,15 @@ public class ImmutabilityInferenceTest extends CFInferenceTest { public ImmutabilityInferenceTest(File testFile) { super(testFile, PICOInferenceChecker.class, "", - "-Anomsgtext", "-Astubs=src/main/java/pico/typecheck/jdk.astub", "-d", "testdata/inference/inferrable"); + "-Anomsgtext", + "-Astubs=src/main/java/pico/typecheck/jdk.astub", + "-d", "testdata/inference/inferrable"); } @Override - public Pair> getSolverNameAndOptions() { - return Pair.> of(PICOSolverEngine.class.getCanonicalName(), - new ArrayList(Arrays.asList("useGraph=false", "collectStatistic=true"))); + public IPair> getSolverNameAndOptions() { + return IPair.of(PICOSolverEngine.class.getCanonicalName(), + new ArrayList<>(Arrays.asList("useGraph=false", "collectStatistic=true"))); } @Override @@ -33,8 +36,7 @@ public boolean useHacks() { @Parameters public static List getTestFiles(){ - List testfiles = new ArrayList<>();//InferenceTestUtilities.findAllSystemTests(); - testfiles.addAll(TestUtilities.findRelativeNestedJavaFiles("testinput", "inference/inferrable")); - return testfiles; + //InferenceTestUtilities.findAllSystemTests(); + return new ArrayList<>(TestUtilities.findRelativeNestedJavaFiles("testinput", "inference/inferrable")); } } diff --git a/src/test/java/pico/ImmutabilityTypecheckBaseAllSystemsTest.java b/src/test/java/pico/ImmutabilityTypecheckBaseAllSystemsTest.java new file mode 100644 index 0000000..c28f7ce --- /dev/null +++ b/src/test/java/pico/ImmutabilityTypecheckBaseAllSystemsTest.java @@ -0,0 +1,27 @@ +package pico; + +import org.checkerframework.framework.test.CheckerFrameworkPerFileTest; +import org.checkerframework.framework.test.TestUtilities; +import org.junit.Ignore; +import org.junit.runners.Parameterized.Parameters; +import pico.typecheck.PICOChecker; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/* Focus on crashes and ignore errors for now. As errors are usually due to + unannotated test files but crashes are due to bugs in CF */ +@Ignore +public class ImmutabilityTypecheckBaseAllSystemsTest extends CheckerFrameworkPerFileTest { + public ImmutabilityTypecheckBaseAllSystemsTest(File testFile) { + super(testFile, PICOChecker.class, "", "-Anomsgtext", "-AsuppressWarnings=pico", + "-Anocheckjdk", "-d", "testTmp/typecheck"); + } + + @Parameters + public static List getTestFiles(){ + return new ArrayList<>(TestUtilities.findRelativeNestedJavaFiles( + "../checker-framework/checker/tests", "all-systems")); + } +} diff --git a/src/test/java/pico/ImmutabilityTypecheckTests.java b/src/test/java/pico/ImmutabilityTypecheckExtendedTest.java similarity index 62% rename from src/test/java/pico/ImmutabilityTypecheckTests.java rename to src/test/java/pico/ImmutabilityTypecheckExtendedTest.java index a07d004..dc7017f 100644 --- a/src/test/java/pico/ImmutabilityTypecheckTests.java +++ b/src/test/java/pico/ImmutabilityTypecheckExtendedTest.java @@ -9,16 +9,14 @@ import java.util.ArrayList; import java.util.List; -public class ImmutabilityTypecheckTests extends CheckerFrameworkPerFileTest { - public ImmutabilityTypecheckTests(File testFile) { +public class ImmutabilityTypecheckExtendedTest extends CheckerFrameworkPerFileTest { + public ImmutabilityTypecheckExtendedTest(File testFile) { super(testFile, PICOChecker.class, "", "-Anomsgtext", "-Anocheckjdk", "-d", "testTmp/typecheck"); } @Parameters public static List getTestFiles(){ - List testfiles = new ArrayList<>(); - testfiles.addAll(TestUtilities.findRelativeNestedJavaFiles("testinput", "typecheck")); - return testfiles; + return new ArrayList<>(TestUtilities.findRelativeNestedJavaFiles("testinput", "inference/inferrable")); } } diff --git a/src/test/java/pico/ImmutabilityTypecheckTest.java b/src/test/java/pico/ImmutabilityTypecheckTest.java new file mode 100644 index 0000000..bf054f4 --- /dev/null +++ b/src/test/java/pico/ImmutabilityTypecheckTest.java @@ -0,0 +1,22 @@ +package pico; + +import org.checkerframework.framework.test.CheckerFrameworkPerFileTest; +import org.checkerframework.framework.test.TestUtilities; +import org.junit.runners.Parameterized.Parameters; +import pico.typecheck.PICOChecker; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class ImmutabilityTypecheckTest extends CheckerFrameworkPerFileTest { + public ImmutabilityTypecheckTest(File testFile) { + super(testFile, PICOChecker.class, "", "-Anomsgtext", + "-Anocheckjdk", "-d", "testTmp/typecheck"); + } + + @Parameters + public static List getTestFiles(){ + return new ArrayList<>(TestUtilities.findRelativeNestedJavaFiles("testinput", "typecheck")); + } +} diff --git a/testinput/inference/inferrable/BoundsCompatible.java b/testinput/inference/inferrable/BoundsCompatible.java index 6e8daa7..5303c3f 100644 --- a/testinput/inference/inferrable/BoundsCompatible.java +++ b/testinput/inference/inferrable/BoundsCompatible.java @@ -13,5 +13,6 @@ class Level1B extends BoundsCompatible {} @Mutable class Level2A extends Level1A {} -// :: fixable-error: (subclass.bound.incompatible) +// fixable-error subclass.bound.incompatible removed. +// :: fixable-error: (type.invalid.annotations.on.use) :: fixable-error: (super.invocation.invalid) @Immutable class Level2B extends Level1B {} diff --git a/testinput/inference/inferrable/ConstructorInvocationUsingNew.java b/testinput/inference/inferrable/ConstructorInvocationUsingNew.java index 021623e..1b2dee8 100644 --- a/testinput/inference/inferrable/ConstructorInvocationUsingNew.java +++ b/testinput/inference/inferrable/ConstructorInvocationUsingNew.java @@ -9,7 +9,7 @@ public class ConstructorInvocationUsingNew { public static void main(String[] args) { // Handled by PICOInferenceVisito#checkConstructorInvocability - // :: fixable-error: (type.invalid.annotations.on.use) + // :: fixable-error: (type.invalid.annotations.on.use) :: fixable-error: (assignment.type.incompatible) @Immutable ConstructorInvocationUsingNew c = new ConstructorInvocationUsingNew(); } } diff --git a/testinput/inference/inferrable/FixableTypeCast.java b/testinput/inference/inferrable/FixableTypeCast.java index db92554..5845a93 100644 --- a/testinput/inference/inferrable/FixableTypeCast.java +++ b/testinput/inference/inferrable/FixableTypeCast.java @@ -1,6 +1,5 @@ public class FixableTypeCast { void foo(Object o) { - // :: fixable-warning: (cast.unsafe) String s = (String) o; } } diff --git a/testinput/inference/inferrable/NoBoundTypeOfAnonymousClass.java b/testinput/inference/inferrable/NoBoundTypeOfAnonymousClass.java index 9d66a53..3288aaf 100644 --- a/testinput/inference/inferrable/NoBoundTypeOfAnonymousClass.java +++ b/testinput/inference/inferrable/NoBoundTypeOfAnonymousClass.java @@ -3,6 +3,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; +import java.lang.Double; // This testcase is realted to StrangeReadonly, see its documentation for details. public class NoBoundTypeOfAnonymousClass { @@ -16,7 +17,7 @@ public Future foo(ExecutorService executor) { return executor.submit(new Callable() { @Override public Double call() throws Exception { - return new Double(1.0); + return Double.valueOf(1.0); } }); } diff --git a/testinput/inference/inferrable/RawIterator.java b/testinput/inference/inferrable/RawIterator.java index 1bccc5a..f262dfb 100644 --- a/testinput/inference/inferrable/RawIterator.java +++ b/testinput/inference/inferrable/RawIterator.java @@ -1,9 +1,9 @@ +@skip-test // There is cast unsafe warning at line 35 import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; - public class RawIterator { public void build(Collection classes) { @@ -24,7 +24,9 @@ public void build(Collection classes) { // programs that have the below line wouldn't be inferred. One legal use case we discussed // is to cast @Mutable datastructure to @Immutable if we guarantee that @Mutable reference // doesn't leak. So we should continue if incompatible cast happens(@Mutable to @Immutable). - // :: warning: (cast.unsafe) + + // Lian: What is a incompatible cast? It seems good to cast to Immutable if casting to String + // is already vaild. String s = (String)iterator.next(); // But for cast that involves at least one VariableSlot, PICOInfer tends to give solutions diff --git a/testinput/inference/inferrable/StrangeReadonly.java b/testinput/inference/inferrable/StrangeReadonly.java index ef96584..3ff2a46 100644 --- a/testinput/inference/inferrable/StrangeReadonly.java +++ b/testinput/inference/inferrable/StrangeReadonly.java @@ -1,5 +1,6 @@ import java.util.Arrays; import java.util.Comparator; +import qual.*; public class StrangeReadonly { @SuppressWarnings("unchecked") @@ -18,12 +19,12 @@ static void foo() { // And CF right now ignores subtype relationship check(constraint generation on inference // side) and always returns true, i.e. "? extends Object" <: VarAnnot(o1), so typeof(o1) :> @Readonly // wasn't generated and o1 is inferred to @Immutable(select any valid solution). - public int compare(Object o1, Object o2) { + + // Lian: CF now could correctly default the mutable on uses. @Readonly added on parameters. + public int compare(@Readonly Object o1, @Readonly Object o2) { // Before inference, @Mutable is casted to @Immutable; After inference, @Readonly is // casted to @Immutable. - // :: fixable-warning: (cast.unsafe) int i = (Integer) o1; - // :: fixable-warning: (cast.unsafe) int j = (Integer) o2; return 0; } diff --git a/testinput/inference/inferrable/TypeVariableUse.java b/testinput/inference/inferrable/TypeVariableUse.java index 9eda72b..025a821 100644 --- a/testinput/inference/inferrable/TypeVariableUse.java +++ b/testinput/inference/inferrable/TypeVariableUse.java @@ -1,10 +1,9 @@ -class D{ +class D { } -public class TypeVariableUse{ +public class TypeVariableUse { // :: fixable-error: (type.argument.type.incompatible) - D D = new D(); - + D d = new D(); } diff --git a/testinput/inference/inferrable/issue144/ComplicatedTest.java b/testinput/inference/inferrable/issue144/ComplicatedTest.java index d3aa0ec..c6a8122 100644 --- a/testinput/inference/inferrable/issue144/ComplicatedTest.java +++ b/testinput/inference/inferrable/issue144/ComplicatedTest.java @@ -38,7 +38,7 @@ void testImmutability() { String name = "tamier"; int age = 24; ArrayList friends = new ArrayList(); - // :: fixable-error: (type.invalid.annotations.on.use) + // :: fixable-error: (constructor.invocation.invalid) Person p = new @Immutable Person(name, age, friends); // :: fixable-error: (method.invocation.invalid) p.getName(); diff --git a/testinput/inference/inferrable/issue144/ConstructorInvocationInSubclassConstructor.java b/testinput/inference/inferrable/issue144/ConstructorInvocationInSubclassConstructor.java index f469dd1..958aaa0 100644 --- a/testinput/inference/inferrable/issue144/ConstructorInvocationInSubclassConstructor.java +++ b/testinput/inference/inferrable/issue144/ConstructorInvocationInSubclassConstructor.java @@ -15,7 +15,7 @@ public class ConstructorInvocationInSubclassConstructor { class SubClass extends ConstructorInvocationInSubclassConstructor { SubClass(Object p) { // Handled by PICOInferenceVisito##checkMethodInvocability - // :: fixable-error: (super.constructor.invocation.incompatible) + // :: fixable-error: (super.invocation.invalid) super(p); } } diff --git a/testinput/inference/inferrable/issue144/RDMConstructor.java b/testinput/inference/inferrable/issue144/RDMConstructor.java index f7bbb2d..c79dec6 100644 --- a/testinput/inference/inferrable/issue144/RDMConstructor.java +++ b/testinput/inference/inferrable/issue144/RDMConstructor.java @@ -16,7 +16,7 @@ class A { public class RDMConstructor { void test1() { - // :: fixable-error: (type.invalid.annotations.on.use) + // :: fixable-error: (type.invalid.annotations.on.use) :: fixable-error: (assignment.type.incompatible) @Immutable A la = new A(); la.toString(); } diff --git a/testinput/typecheck/AnnotationApplicationOrder.java b/testinput/typecheck/AnnotationApplicationOrder.java index 44505d6..3b7d303 100644 --- a/testinput/typecheck/AnnotationApplicationOrder.java +++ b/testinput/typecheck/AnnotationApplicationOrder.java @@ -16,6 +16,7 @@ */ public class AnnotationApplicationOrder { static Object o;// PICOTreeAnnotator takes care of static fields + // :: error: (initialization.field.uninitialized) BigDecimal decimal;// PICOImplicitsTypeAnnotator takes care of it Date date;// QualifierDefaults takes care of it } diff --git a/testinput/typecheck/AssignableExample.java b/testinput/typecheck/AssignableExample.java index a7d15d8..294f6d3 100644 --- a/testinput/typecheck/AssignableExample.java +++ b/testinput/typecheck/AssignableExample.java @@ -12,7 +12,7 @@ public class AssignableExample { @Immutable Object o; @Immutable Date date; @Assignable @Immutable Date assignableDate; - + // :: error: (initialization.fields.uninitialized) @Mutable AssignableExample() { o = new @Immutable Object(); } @@ -28,7 +28,7 @@ void foo2(@Mutable AssignableExample this) { } } -// :: error: (super.constructor.invocation.incompatible) +// :: error: (super.invocation.invalid) @ReceiverDependantMutable class Subclass extends AssignableExample { void bar(@Immutable Subclass this) { // :: error: (illegal.field.write) diff --git a/testinput/typecheck/CompatabilityBEI1.java b/testinput/typecheck/CompatabilityBEI1.java index d9f0963..699310a 100644 --- a/testinput/typecheck/CompatabilityBEI1.java +++ b/testinput/typecheck/CompatabilityBEI1.java @@ -32,18 +32,16 @@ class H extends Object {} @Mutable class I implements @Mutable Cloneable {} -// :: error: (bound.implements.incompatabile) +// :: error: (declaration.inconsistent.with.implements.clause) @Mutable class J implements @Immutable Cloneable {} -// :: error: (bound.implements.incompatabile) @Mutable class K implements @ReceiverDependantMutable Cloneable {} -// :: error: (bound.extends.incompatabile) +// :: error: (declaration.inconsistent.with.extends.clause) @Immutable class L extends @Mutable Object {} @Immutable class M extends @Immutable Object {} -// :: error: (bound.extends.incompatabile) @Immutable class N extends @ReceiverDependantMutable Object {} abstract class O implements CharSequence {} diff --git a/testinput/typecheck/CompatabilityBEI2.java b/testinput/typecheck/CompatibilityBEI2.java similarity index 84% rename from testinput/typecheck/CompatabilityBEI2.java rename to testinput/typecheck/CompatibilityBEI2.java index 065c652..5b06687 100644 --- a/testinput/typecheck/CompatabilityBEI2.java +++ b/testinput/typecheck/CompatibilityBEI2.java @@ -25,28 +25,26 @@ class H extends ArrayList<@Immutable Object> {} @Mutable abstract class I implements @Mutable List<@Immutable Object> {} -// :: error: (bound.implements.incompatabile) +// :: error: (declaration.inconsistent.with.implements.clause) @Mutable abstract class J implements @Immutable List<@Immutable Object> {} -// :: error: (bound.implements.incompatabile) @Mutable abstract class K implements @ReceiverDependantMutable List<@Immutable Object> {} -// :: error: (bound.extends.incompatabile) +// :: error: (declaration.inconsistent.with.extends.clause) @Immutable class L extends @Mutable ArrayList<@Immutable Object> {} @Immutable class M extends @Immutable ArrayList<@Immutable Object> {} -// :: error: (bound.extends.incompatabile) @Immutable class N extends @ReceiverDependantMutable ArrayList<@Immutable Object> {} abstract class O implements CharSequence {} @Immutable interface ImmutableInterface {} -// :: error: (subclass.bound.incompatible) +// :: error: (type.invalid.annotations.on.use) @Mutable abstract class P implements ImmutableInterface<@Mutable Object> {} @Immutable abstract class Q implements ImmutableInterface<@Immutable Object> {} -// :: error: (subclass.bound.incompatible) +// :: error: (type.invalid.annotations.on.use) @ReceiverDependantMutable abstract class R implements ImmutableInterface<@ReceiverDependantMutable Object> {} diff --git a/testinput/typecheck/CopyToCast.java b/testinput/typecheck/CopyToCast.java index ad77909..e1714b6 100644 --- a/testinput/typecheck/CopyToCast.java +++ b/testinput/typecheck/CopyToCast.java @@ -7,9 +7,9 @@ public class CopyToCast { void foo(Object o) { - // :: warning: (cast.unsafe) + // No cast.unsafe String s1 = (@Immutable String) o; - // :: warning: (cast.unsafe) + // No cast.unsafe String s2 = (String) o; // :: error: (type.invalid.annotations.on.use) String s3 = (@Mutable String) o; diff --git a/testinput/typecheck/DateCell.java b/testinput/typecheck/DateCell.java index 1786b6a..748f161 100644 --- a/testinput/typecheck/DateCell.java +++ b/testinput/typecheck/DateCell.java @@ -7,7 +7,6 @@ import java.lang.SuppressWarnings; import java.util.Date; -// :: error: (initialization.fields.uninitialized) @ReceiverDependantMutable public class DateCell { @ReceiverDependantMutable Date date; diff --git a/testinput/typecheck/DateCell2.java b/testinput/typecheck/DateCell2.java index 2bb5985..28eb674 100644 --- a/testinput/typecheck/DateCell2.java +++ b/testinput/typecheck/DateCell2.java @@ -7,8 +7,8 @@ import java.util.Date; -// :: error: (initialization.fields.uninitialized) @ReceiverDependantMutable public class DateCell2 { + // :: error: (initialization.field.uninitialized) @Immutable Date imdate; @Immutable Date getImmutableDate(@PolyMutable DateCell2 this) { diff --git a/testinput/typecheck/DeepMutable.java b/testinput/typecheck/DeepMutable.java new file mode 100644 index 0000000..77470cf --- /dev/null +++ b/testinput/typecheck/DeepMutable.java @@ -0,0 +1,33 @@ +import qual.*; + +public class DeepMutable { + @Mutable + static class MutableBox {} + + @Immutable + static class ImmutableClass { + + // :: error: (implicit.shallow.immutable) + MutableBox implicit = new MutableBox(); + + @Mutable MutableBox explicit = new MutableBox(); + } + + @Immutable + static class ImmutableGenericEx { + + T t; + @Immutable ImmutableGenericEx(T t) { + this.t = t; + } + } + + @Immutable + static class ImmutableGenericIm { + // :: error: (implicit.shallow.immutable) + T t; + @Immutable ImmutableGenericIm(T t) { + this.t = t; + } + } +} diff --git a/testinput/typecheck/DiamondTreeProblem.java b/testinput/typecheck/DiamondTreeProblem.java index 206acf3..f2a4a10 100644 --- a/testinput/typecheck/DiamondTreeProblem.java +++ b/testinput/typecheck/DiamondTreeProblem.java @@ -6,9 +6,6 @@ public class DiamondTreeProblem { void test1() { - // TODO This is WRONG even though test passed! Explicit @Immutable annotation - // on new instance creation is ignored and @Mutable is defaulted! - // :: error: (assignment.type.incompatible) @Immutable List l = new @Immutable ArrayList<>(); } diff --git a/testinput/typecheck/EnumConstantNotAlwaysMutable.java b/testinput/typecheck/EnumConstantNotAlwaysMutable.java index ea48386..010d800 100644 --- a/testinput/typecheck/EnumConstantNotAlwaysMutable.java +++ b/testinput/typecheck/EnumConstantNotAlwaysMutable.java @@ -18,9 +18,9 @@ public class EnumConstantNotAlwaysMutable { @ReceiverDependantMutable Kind invalidKind; // :: error: (type.invalid.annotations.on.use) @Mutable Kind invalidKind2; - // :: error: (type.invalid.annotations.on.use) + // no error now @Readonly Kind invalidKind3; - + // :: error: (initialization.fields.uninitialized) EnumConstantNotAlwaysMutable() { // Kind.SOME should be @Immutable kind = Kind.SOME; diff --git a/testinput/typecheck/EnumTests.java b/testinput/typecheck/EnumTests.java new file mode 100644 index 0000000..5579c6d --- /dev/null +++ b/testinput/typecheck/EnumTests.java @@ -0,0 +1,27 @@ +import qual.Immutable; +import qual.Mutable; + +// Enum is now only immutable by default, not implicit +public class EnumTests{ + void foo(/*immutable*/ MyEnum e) { + // :: error: (type.invalid.annotations.on.use) + @Mutable MyEnum mutableRef; + @Immutable MyEnum immutableRef = e; + + @Mutable MutableEnum mutEnumMutRef= MutableEnum.M1; + // :: error: (type.invalid.annotations.on.use) + @Immutable MutableEnum mutEnumImmRef; + } + + /*immutable*/ + private static enum MyEnum { + T1, + T2; + } + + @Mutable + private static enum MutableEnum { + M1, + M2; + } +} diff --git a/testinput/typecheck/FieldAssignment.java b/testinput/typecheck/FieldAssignment.java index 39650d7..be74bea 100644 --- a/testinput/typecheck/FieldAssignment.java +++ b/testinput/typecheck/FieldAssignment.java @@ -16,7 +16,6 @@ void setFWithMutableReceiver(@UnderInitialization @Mutable FieldAssignment this, // TODO This is not specific to PICO type system. InitializationVisitor currently has this issue of false positively // wanrning uninitialized fields when we use instance method to initialiaze fields - // :: error: (initialization.fields.uninitialized) public FieldAssignment() { // :: error: (method.invocation.invalid) setFWithMutableReceiver(new @Mutable Object()); diff --git a/testinput/typecheck/FieldsInitialized.java b/testinput/typecheck/FieldsInitialized.java index 8668cb0..e972493 100644 --- a/testinput/typecheck/FieldsInitialized.java +++ b/testinput/typecheck/FieldsInitialized.java @@ -19,7 +19,7 @@ public class FieldsInitialized { @Assignable @ReceiverDependantMutable Object f8; @Assignable @Mutable Object f9; @Assignable @Readonly Object f10; - + // :: error: (initialization.fields.uninitialized) @ReceiverDependantMutable FieldsInitialized() { f1 = new @Immutable Object(); f2 = new @Immutable Object(); diff --git a/testinput/typecheck/ForbidAssignmentCase.java b/testinput/typecheck/ForbidAssignmentCase.java index 69f6557..fd83242 100644 --- a/testinput/typecheck/ForbidAssignmentCase.java +++ b/testinput/typecheck/ForbidAssignmentCase.java @@ -14,12 +14,13 @@ public class ForbidAssignmentCase { } // Allowing assignment through @Readonly receiver to @Assignable @ReceiverDependantMutable - // in either way causes errors. So I would forbid this combination in assignment. Otherwise, - // we don't. For example, we still allow reading this field by @Readonly receiver - static void forbid(@Readonly ForbidAssignmentCase fac) { + // in either way causes errors. So I would forbid this combination in assignment. + // Though we still allow reading this field by @Readonly receiver + static void forbid(@Readonly ForbidAssignmentCase ro, @Mutable ForbidAssignmentCase mo) { // :: error: (illegal.field.write) - fac.f = new @Immutable Object(); - Object lo = fac.f; + ro.f = new @Immutable Object(); // cannot exclude f out of the abstract state! + ro = mo; // ro.f will be mutable now, and we can use this reference to mutate an immutable object + @Readonly Object o = ro.f; // allow reads } // Below are different cases. Because dataflow refinement refines @Readonly to concrete type, @@ -36,6 +37,7 @@ static void ImmutableObjectCaptureMutableObject() { // But allow below: ro.f = new @Immutable Object(); } + static void ImmutableObjectGetMutableAlias() { @Mutable ForbidAssignmentCase mo = new @Mutable ForbidAssignmentCase(); @Readonly ForbidAssignmentCase ro = mo; diff --git a/testinput/typecheck/GenericInterfaces.java b/testinput/typecheck/GenericInterfaces.java index ae2f892..a68715c 100644 --- a/testinput/typecheck/GenericInterfaces.java +++ b/testinput/typecheck/GenericInterfaces.java @@ -13,6 +13,7 @@ void raw() { // Using optimictic uninferred type arguments, so it is // allowed + // :: error: (assignment.type.incompatible) @Immutable Object ro = raw.next(); } } diff --git a/testinput/typecheck/HashCodeSafetyExample.java b/testinput/typecheck/HashCodeSafetyExample.java index 5486b56..bee5792 100644 --- a/testinput/typecheck/HashCodeSafetyExample.java +++ b/testinput/typecheck/HashCodeSafetyExample.java @@ -16,7 +16,7 @@ public int hashCode() { @Override public boolean equals(Object obj) { - // :: warning: (cast.unsafe) + // No cast.unsafe return isIn == ((A)obj).isIn; } } diff --git a/testinput/typecheck/ImmutableClass1.java b/testinput/typecheck/ImmutableClass1.java index 33d79fd..a22acca 100644 --- a/testinput/typecheck/ImmutableClass1.java +++ b/testinput/typecheck/ImmutableClass1.java @@ -26,16 +26,7 @@ void method4(@PolyMutable ImmutableClass1 this) {} // :: error: (method.receiver.incompatible) :: error: (type.invalid.annotations.on.use) void method5(@Mutable ImmutableClass1 this) {} - // Note: the reason why there is no "type.invalid" error - // TODO Discuss with prof - // Declared receiver type has "different" types from different perspectives: - // when PICOVisitor#visitMethod() is called, "this" is defaulted to @Mutable; - // but when PICOVisitor#visitVariable() is called, "this" inheris @Immutable - // from its class element. So that's why we get "method.receiver.incompatible" - // error becasue method receiver is @Mutable, but we didn't get "type.invalid" - // because @Immutable ImmutableClass1 is the correct usage of ImmutableClass1. - // See comment: https://github.com/opprop/checker-framework/blob/master/framework/src/org/checkerframework/framework/type/AnnotatedTypeFactory.java#L1593 - // for why class bound annotation is not applied to instance method receiver - // :: error: (method.receiver.incompatible) + + // when not annotated explictly, default annotations of is inherited from declaration void method6(ImmutableClass1 this) {} } diff --git a/testinput/typecheck/ImmutableConstructor.java b/testinput/typecheck/ImmutableConstructor.java index 6cdcbca..6b200eb 100644 --- a/testinput/typecheck/ImmutableConstructor.java +++ b/testinput/typecheck/ImmutableConstructor.java @@ -36,13 +36,11 @@ void invokeConstructor(@Readonly ImmutableConstructor this, @Readonly Object ro, @ReceiverDependantMutable Object po, @Immutable Object io) { new @Immutable ImmutableConstructor(io, io); - // :: error: (type.invalid.annotations.on.use) + // :: error: (constructor.invocation.invalid) new @Mutable ImmutableConstructor(mo, io); - // This no longer is error now(?). Because instantiating @Immutable constructor - // as @PolyImmutable(PolymorphicQualifier) automatically resolves @PolyImmutable - // to @Immutable, which might be a good thing - // :: error: (type.invalid.annotations.on.use) + // constructor.invocation.invalid propgates before annotation invalid messages and stops + // :: error: (constructor.invocation.invalid) new @ReceiverDependantMutable ImmutableConstructor(po, io); // :: error: (constructor.invocation.invalid) :: error: (pico.new.invalid) diff --git a/testinput/typecheck/ImplicitAppliesToMethodReceiver.java b/testinput/typecheck/ImplicitAppliesToMethodReceiver.java index 86f523e..7e85fe8 100644 --- a/testinput/typecheck/ImplicitAppliesToMethodReceiver.java +++ b/testinput/typecheck/ImplicitAppliesToMethodReceiver.java @@ -5,6 +5,6 @@ public class ImplicitAppliesToMethodReceiver { void foo() { - double delta = new Double(1.0).doubleValue(); + double delta = Double.valueOf(1.0); } } diff --git a/testinput/typecheck/InvalidAssignability.java b/testinput/typecheck/InvalidAssignability.java index 164988b..2bf1294 100644 --- a/testinput/typecheck/InvalidAssignability.java +++ b/testinput/typecheck/InvalidAssignability.java @@ -5,9 +5,12 @@ public class InvalidAssignability { final @Immutable Object io = null; + // :: error: (initialization.field.uninitialized) @Immutable Object io2; + // :: error: (initialization.field.uninitialized) @Assignable @Immutable Object io3; static final @Immutable Object io4 = null; + // :: error: (initialization.static.field.uninitialized) static @Assignable @Immutable Object io5; // :: error: (one.assignability.invalid) final @Assignable @Immutable Object o = null; diff --git a/testinput/typecheck/MethodReceiverNotInhericClassBound.java b/testinput/typecheck/MethodReceiverNotInhericClassBound.java index cb1e7dd..03b1a9c 100644 --- a/testinput/typecheck/MethodReceiverNotInhericClassBound.java +++ b/testinput/typecheck/MethodReceiverNotInhericClassBound.java @@ -4,6 +4,6 @@ @Immutable public class MethodReceiverNotInhericClassBound { - // :: error: (method.receiver.incompatible) - void foo() {} + // :: error: (method.receiver.incompatible) :: error: (type.invalid.annotations.on.use) + void bar(@Mutable MethodReceiverNotInhericClassBound this) {} } diff --git a/testinput/typecheck/MutableConstructor.java b/testinput/typecheck/MutableConstructor.java index 1fac2ef..0609785 100644 --- a/testinput/typecheck/MutableConstructor.java +++ b/testinput/typecheck/MutableConstructor.java @@ -35,9 +35,9 @@ void invokeConstructor(@Mutable Object mo, @ReceiverDependantMutable Object po, // :: error: (argument.type.incompatible) new @Mutable MutableConstructor(mo, po, io); // The same argument as the one in ImmutableConstructor - // :: error: (type.invalid.annotations.on.use) + // :: error: (constructor.invocation.invalid) new @ReceiverDependantMutable MutableConstructor(mo, po, io); - // :: error: (type.invalid.annotations.on.use) + // :: error: (constructor.invocation.invalid) new @Immutable MutableConstructor(mo, io, io); } } diff --git a/testinput/typecheck/NotEveryInstFieldDefaultToRDM.java b/testinput/typecheck/NotEveryInstFieldDefaultToRDM.java index e25d1c3..4923b89 100644 --- a/testinput/typecheck/NotEveryInstFieldDefaultToRDM.java +++ b/testinput/typecheck/NotEveryInstFieldDefaultToRDM.java @@ -7,8 +7,8 @@ public class NotEveryInstFieldDefaultToRDM { // :: error: (assignment.type.incompatible) @ReceiverDependantMutable B b1 = new B(); B b2 = new @ReceiverDependantMutable B(); - C c = new @Mutable C(); - D d = new @Mutable D(); + @Mutable C c = new @Mutable C(); + @Mutable D d = new @Mutable D(); E e = new @Immutable E(); } diff --git a/testinput/typecheck/ObjectIdentityMethodTest.java b/testinput/typecheck/ObjectIdentityMethodTest.java index 04dfb8b..a7f10b1 100644 --- a/testinput/typecheck/ObjectIdentityMethodTest.java +++ b/testinput/typecheck/ObjectIdentityMethodTest.java @@ -2,7 +2,7 @@ @ReceiverDependantMutable class A { - @Assignable B b; + @Assignable @Mutable B b; @ReceiverDependantMutable A() {} void bar(@Readonly A this) {} } @@ -26,7 +26,7 @@ public class ObjectIdentityMethodTest extends Super{ @Immutable A a5; final A a6; final @Immutable A a7; - + // :: error: (initialization.fields.uninitialized) ObjectIdentityMethodTest() { a6 = new A(); a7 = new @Immutable A(); diff --git a/testinput/typecheck/ObjectMethods.java b/testinput/typecheck/ObjectMethods.java index 7b33d7d..08cfa3f 100644 --- a/testinput/typecheck/ObjectMethods.java +++ b/testinput/typecheck/ObjectMethods.java @@ -145,7 +145,7 @@ public boolean equals(@Readonly ObjectMethods6 this, @Readonly Object o) { @Override protected @ReceiverDependantMutable Object clone(@Readonly ObjectMethods6 this) throws CloneNotSupportedException { - // :: warning: (cast.unsafe) + // No cast.unsafe return (@ReceiverDependantMutable Object) super.clone(); } diff --git a/testinput/typecheck/OnlyOneModifierIsUse.java b/testinput/typecheck/OnlyOneModifierIsUse.java index 728eefd..8ed9646 100644 --- a/testinput/typecheck/OnlyOneModifierIsUse.java +++ b/testinput/typecheck/OnlyOneModifierIsUse.java @@ -6,8 +6,10 @@ public class OnlyOneModifierIsUse { - // :: error: (type.invalid) + // :: error: (type.invalid.conflicting.annos) + // :: error: (initialization.field.uninitialized) @Readonly @Immutable Object field; - // :: error: (type.invalid) + // :: error: (type.invalid.conflicting.annos) + // :: error: (initialization.field.uninitialized) String @Readonly @Immutable [] array; } diff --git a/testinput/typecheck/Planet.java b/testinput/typecheck/Planet.java index f2ae25b..328ed03 100644 --- a/testinput/typecheck/Planet.java +++ b/testinput/typecheck/Planet.java @@ -85,7 +85,9 @@ public String toString() { public static void main(String[] args) { @Immutable Date discoveryDate = new @Immutable Date(); // :: error: (type.invalid.annotations.on.use) - @Mutable Planet mPlanet = new @Mutable Planet(1, "Earth", discoveryDate); + @Mutable Planet mPlanet; + // :: error: (constructor.invocation.invalid) + mPlanet = new @Mutable Planet(1, "Earth", discoveryDate); @Immutable Planet imPlanet = new @Immutable Planet(1, "Earth", discoveryDate); // None of the fields are allowed to be modified on an immutable object // :: error: (illegal.field.write) diff --git a/testinput/typecheck/PolyMutableOnConstructorParameters.java b/testinput/typecheck/PolyMutableOnConstructorParameters.java index d8f774d..a9e8842 100644 --- a/testinput/typecheck/PolyMutableOnConstructorParameters.java +++ b/testinput/typecheck/PolyMutableOnConstructorParameters.java @@ -4,7 +4,7 @@ import qual.PolyMutable; @Immutable -public class PolyMutableOnConstructorParameters { +public class PolyMutableOnConstructorParameters { @Immutable PolyMutableOnConstructorParameters(@PolyMutable Object o) { } diff --git a/testinput/typecheck/Primitive3.java b/testinput/typecheck/Primitive3.java index 8781976..eacb90b 100644 --- a/testinput/typecheck/Primitive3.java +++ b/testinput/typecheck/Primitive3.java @@ -13,7 +13,7 @@ void foo(Word word) { // I reenable type cast safety checking when the cast type is implicitly immutable. // Why should we suppress warning just because cast type is implicitly immutable? // That doesn't make any sense. Am I right? - // :: warning: (cast.unsafe) + // No cast.unsafe params[0] = (String) word.get(0); } } diff --git a/testinput/typecheck/RDMBug.java b/testinput/typecheck/RDMBug.java index 87c5705..6e64d06 100644 --- a/testinput/typecheck/RDMBug.java +++ b/testinput/typecheck/RDMBug.java @@ -5,7 +5,6 @@ import qual.Mutable; import qual.Readonly; -// :: error: (initialization.fields.uninitialized) @Immutable class RDMBug { @Mutable Object o; @Readonly Object o2; diff --git a/testinput/typecheck/RDMField.java b/testinput/typecheck/RDMField.java new file mode 100644 index 0000000..3312e8a --- /dev/null +++ b/testinput/typecheck/RDMField.java @@ -0,0 +1,48 @@ +import qual.*; + +public class RDMField{ + + @Mutable + private static class MutableClass { + int field = 0; + } + + @ReceiverDependantMutable + private static class RDMHolder { + + // :: error: (type.invalid.annotations.on.use) + @ReceiverDependantMutable MutableClass field = new MutableClass(); + @Mutable MutableClass mutableField = new MutableClass(); + + public @PolyMutable MutableClass getField(@PolyMutable RDMHolder this) { + return field; + } + + public void setField(@Mutable RDMHolder this, MutableClass field) { + this.field = field; + } + + void asImmutable(@Immutable RDMHolder r) { + // :: error: (illegal.field.write) + r.field.field = 1; + // :: error: (illegal.field.write) + r.getField().field = 1; + // :: error: (method.invocation.invalid) + r.setField(new MutableClass()); + } + } + + @Immutable + private static class ImmutableHolder { + // :: error: (type.invalid.annotations.on.use) + @ReceiverDependantMutable MutableClass field = new MutableClass(); + + public @PolyMutable MutableClass getField(@PolyMutable ImmutableHolder this) { + return field; + } + + + + } +} + diff --git a/testinput/typecheck/RDMFieldInst.java b/testinput/typecheck/RDMFieldInst.java new file mode 100644 index 0000000..e7b8ea3 --- /dev/null +++ b/testinput/typecheck/RDMFieldInst.java @@ -0,0 +1,34 @@ +import qual.*; + +public class RDMFieldInst{ + @Mutable + private static class MutableBox {} + + @Immutable + private static class ImmutableBox {} + + @ReceiverDependantMutable + private static class RDMBox {} + + @Immutable + private static class ImmutableClass { + // :: error: (type.invalid.annotations.on.use) + @ReceiverDependantMutable MutableBox mutableBoxInRDM; + } + + @Mutable + private static class MutableClass { + @ReceiverDependantMutable MutableBox mutableBoxInRDM = new MutableBox(); + + @ReceiverDependantMutable RDMBox rdmBoxInRDMnewM = new @Mutable RDMBox(); + // :: error: (assignment.type.incompatible) + @ReceiverDependantMutable RDMBox rdmBoxInRDMnewI = new @Immutable RDMBox(); + // :: error: (assignment.type.incompatible) + @ReceiverDependantMutable RDMBox rdmBoxInRDMnewRDM = new @ReceiverDependantMutable RDMBox(); + // :: error: (type.invalid.annotations.on.use) + @ReceiverDependantMutable ImmutableBox immutableBoxInRDM = new ImmutableBox(); + } + + + +} diff --git a/testinput/typecheck/ReceiverTypeOutsideConstructor.java b/testinput/typecheck/ReceiverTypeOutsideConstructor.java index 9c63d14..c153c83 100644 --- a/testinput/typecheck/ReceiverTypeOutsideConstructor.java +++ b/testinput/typecheck/ReceiverTypeOutsideConstructor.java @@ -31,13 +31,13 @@ class A { @Immutable class AIMS extends A {} -// :: error: (subclass.bound.incompatible) +// :: error: (type.invalid.annotations.on.use) :: error: (super.invocation.invalid) @ReceiverDependantMutable class ARDMS extends A {} -// :: error: (subclass.bound.incompatible) +// :: error: (type.invalid.annotations.on.use) :: error: (super.invocation.invalid) @Mutable class AMS extends A {} -// :: error: (subclass.bound.incompatible) +// :: error: (type.invalid.annotations.on.use) :: error: (super.invocation.invalid) class AUNKS extends A {} // ReceiverDependantMutable class @@ -68,14 +68,14 @@ class B { @Immutable class BIMS extends B {} -// :: error: (super.constructor.invocation.incompatible) +// :: error: (super.invocation.invalid) @ReceiverDependantMutable class BRDMS extends B {} -// :: error: (super.constructor.invocation.incompatible) +// :: error: (super.invocation.invalid) @Mutable class BMS extends B {} // mutable by default(TODO Does this make sense compared to defaulting to receiver-dependant-mutable?) -// :: error: (super.constructor.invocation.incompatible) +// :: error: (super.invocation.invalid) class BUNKS extends B {} // Mutable class @@ -103,16 +103,16 @@ class C { } } -// :: error: (subclass.bound.incompatible) +// :: error: (type.invalid.annotations.on.use) @Immutable class CIMS extends C {} -// :: error: (subclass.bound.incompatible) +// :: error: (type.invalid.annotations.on.use) :: error: (super.invocation.invalid) @ReceiverDependantMutable class CRDMS extends C {} -// :: error: (super.constructor.invocation.incompatible) +// :: error: (super.invocation.invalid) @Mutable class CMS extends C {} -// :: error: (super.constructor.invocation.incompatible) +// :: error: (super.invocation.invalid) class CUNKS extends C {} class D { diff --git a/testinput/typecheck/SuperClass.java b/testinput/typecheck/SuperClass.java index 3c71c82..c55d9f3 100644 --- a/testinput/typecheck/SuperClass.java +++ b/testinput/typecheck/SuperClass.java @@ -20,25 +20,25 @@ void maliciouslyModifyDate(@Mutable SuperClass this){ class SubClass extends SuperClass{ @Mutable SubClass(){ - // :: error: (super.constructor.invocation.incompatible) + // :: error: (super.invocation.invalid) super(new @Immutable Date(1L)); } public static void main(String[] args) { @Mutable SubClass victim = new @Mutable SubClass(); - victim.maliciouslyModifyDate();; + victim.maliciouslyModifyDate(); } } @ReceiverDependantMutable class AnotherSubClass extends SuperClass{ @ReceiverDependantMutable AnotherSubClass(){ - // :: error: (super.constructor.invocation.incompatible) + // :: error: (super.invocation.invalid) super(new @Immutable Date(1L)); } public static void main(String[] args) { @Mutable SubClass victim = new @Mutable SubClass(); - victim.maliciouslyModifyDate();; + victim.maliciouslyModifyDate(); } } diff --git a/testinput/typecheck/SuperClass2.java b/testinput/typecheck/SuperClass2.java index 00cfdee..1d5d567 100644 --- a/testinput/typecheck/SuperClass2.java +++ b/testinput/typecheck/SuperClass2.java @@ -35,7 +35,7 @@ public class SuperClass2{ class SubClass2 extends SuperClass2{ @Immutable SubClass2(){ // This is not ok any more - // :: error: (super.constructor.invocation.incompatible) + // :: error: (super.invocation.invalid) super(new @Mutable Date()); } } @@ -44,7 +44,7 @@ class SubClass2 extends SuperClass2{ class AnotherSubClass2 extends SuperClass2{ @ReceiverDependantMutable AnotherSubClass2(){ // This is not ok any more - // :: error: (super.constructor.invocation.incompatible) + // :: error: (super.invocation.invalid) super(new @Mutable Date()); } } diff --git a/testinput/typecheck/SupportedBuilderPattern.java b/testinput/typecheck/SupportedBuilderPattern.java index 72484bd..444bbcf 100644 --- a/testinput/typecheck/SupportedBuilderPattern.java +++ b/testinput/typecheck/SupportedBuilderPattern.java @@ -23,7 +23,7 @@ public static class Builder { private final int id; private String address; private @Immutable Date date; - + // :: error: (initialization.fields.uninitialized) public Builder(int id) { this.id = id; } diff --git a/testinput/typecheck/Transitive.java b/testinput/typecheck/Transitive.java new file mode 100644 index 0000000..8210ec7 --- /dev/null +++ b/testinput/typecheck/Transitive.java @@ -0,0 +1,46 @@ +import qual.Readonly; + +public class Transitive { + + // class A, B, C are not annotated to test transitive mutability by default. + + static class A { + B b; + + public B getB() { + return b; + } + } + + static class B { + int field = 0; + C c; + + public C getC() { + return c; + } + } + + static class C { + int field = 0; + } + + static class Caller { + void test(@Readonly A a) { + // :: error: (illegal.field.write) + a.b.field = 1; + // :: error: (method.invocation.invalid) + a.getB().field = 1; + + // :: error: (illegal.field.write) + a.b.c.field = 1; + // :: error: (method.invocation.invalid) + a.getB().getC().field = 1; + // :: error: (method.invocation.invalid) + a.b.getC().field = 1; + // :: error: (method.invocation.invalid) + a.getB().c.field = 1; + } + } +} +