diff --git a/src/main/java/com/google/jspecify/nullness/NullSpecAnnotatedTypeFactory.java b/src/main/java/com/google/jspecify/nullness/NullSpecAnnotatedTypeFactory.java index 17ef8df..6b15f91 100644 --- a/src/main/java/com/google/jspecify/nullness/NullSpecAnnotatedTypeFactory.java +++ b/src/main/java/com/google/jspecify/nullness/NullSpecAnnotatedTypeFactory.java @@ -145,6 +145,7 @@ final class NullSpecAnnotatedTypeFactory new TypeUseLocation[] { TypeUseLocation.CONSTRUCTOR_RESULT, TypeUseLocation.EXCEPTION_PARAMETER, + TypeUseLocation.IMPLICIT_LOWER_BOUND, TypeUseLocation.RECEIVER, }; @@ -157,10 +158,6 @@ final class NullSpecAnnotatedTypeFactory private static final TypeUseLocation[] defaultLocationsUnspecified = new TypeUseLocation[] { - // Lower bounds could be MinusNull, but all uses in unmarked code would become unspecified - // anyways. - // Revisit once https://github.com/eisop/checker-framework/issues/741 is fixed. - TypeUseLocation.IMPLICIT_LOWER_BOUND, TypeUseLocation.IMPLICIT_WILDCARD_UPPER_BOUND_NO_SUPER, TypeUseLocation.TYPE_VARIABLE_USE, TypeUseLocation.OTHERWISE diff --git a/src/main/java/com/google/jspecify/nullness/NullSpecTypeValidator.java b/src/main/java/com/google/jspecify/nullness/NullSpecTypeValidator.java new file mode 100644 index 0000000..bb29fc0 --- /dev/null +++ b/src/main/java/com/google/jspecify/nullness/NullSpecTypeValidator.java @@ -0,0 +1,45 @@ +// Copyright 2020 The JSpecify Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.jspecify.nullness; + +import javax.lang.model.element.AnnotationMirror; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.common.basetype.BaseTypeValidator; +import org.checkerframework.framework.type.AnnotatedTypeMirror; + +final class NullSpecTypeValidator extends BaseTypeValidator { + private final AnnotationMirror nullnessOperatorUnspecified; + + /** Constructor. */ + NullSpecTypeValidator( + BaseTypeChecker checker, + NullSpecVisitor visitor, + NullSpecAnnotatedTypeFactory atypeFactory, + Util util) { + super(checker, visitor, atypeFactory); + + nullnessOperatorUnspecified = util.nullnessOperatorUnspecified; + } + + @Override + public boolean areBoundsValid(AnnotatedTypeMirror upperBound, AnnotatedTypeMirror lowerBound) { + if (upperBound.hasAnnotation(nullnessOperatorUnspecified) + || lowerBound.hasAnnotation(nullnessOperatorUnspecified)) { + return true; + } else { + return super.areBoundsValid(upperBound, lowerBound); + } + } +} diff --git a/src/main/java/com/google/jspecify/nullness/NullSpecVisitor.java b/src/main/java/com/google/jspecify/nullness/NullSpecVisitor.java index 9b47174..c6c2f83 100644 --- a/src/main/java/com/google/jspecify/nullness/NullSpecVisitor.java +++ b/src/main/java/com/google/jspecify/nullness/NullSpecVisitor.java @@ -71,6 +71,7 @@ import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; import org.checkerframework.common.basetype.BaseTypeVisitor; +import org.checkerframework.common.basetype.TypeValidator; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; @@ -637,4 +638,9 @@ protected NullSpecAnnotatedTypeFactory createTypeFactory() { // Reading util this way is ugly but necessary. See discussion in NullSpecChecker. return new NullSpecAnnotatedTypeFactory(checker, ((NullSpecChecker) checker).util); } + + @Override + protected TypeValidator createTypeValidator() { + return new NullSpecTypeValidator(checker, this, atypeFactory, ((NullSpecChecker) checker).util); + } } diff --git a/src/test/java/tests/NullSpecTest.java b/src/test/java/tests/NullSpecTest.java index 7584547..3f127d5 100644 --- a/src/test/java/tests/NullSpecTest.java +++ b/src/test/java/tests/NullSpecTest.java @@ -54,6 +54,18 @@ public static String[] getTestDirs() { } } + /** A small set of strict regression tests. */ + public static class RegressionStrict extends NullSpecTest { + public RegressionStrict(List testFiles) { + super(testFiles, true); + } + + @Parameters + public static String[] getTestDirs() { + return new String[] {"regression-strict"}; + } + } + /** A test that ignores cases where there is limited nullness information. */ public static class Lenient extends NullSpecTest { public Lenient(List testFiles) { diff --git a/tests/ConformanceTest-report.txt b/tests/ConformanceTest-report.txt index 23c8a51..c8496ee 100644 --- a/tests/ConformanceTest-report.txt +++ b/tests/ConformanceTest-report.txt @@ -1,4 +1,4 @@ -# 90 pass; 24 fail; 114 total; 78.9% score +# 93 pass; 24 fail; 117 total; 79.5% score PASS: Basic.java:28:test:expression-type:Object?:nullable PASS: Basic.java:28:test:sink-type:Object!:return PASS: Basic.java:28:test:cannot-convert:Object? to Object! @@ -44,6 +44,7 @@ PASS: irrelevantannotations/notnullmarked/Other.java:Nullable local variable arr PASS: irrelevantannotations/notnullmarked/Other.java:NonNull local variable array:test:irrelevant-annotation:NonNull PASS: irrelevantannotations/notnullmarked/Other.java:Nullable exception parameter:test:irrelevant-annotation:Nullable PASS: irrelevantannotations/notnullmarked/Other.java:NonNull exception parameter:test:irrelevant-annotation:NonNull +PASS: irrelevantannotations/notnullmarked/Other.java:Intrinsically NonNull exception parameter cannot be assigned null:test:cannot-convert:null? to RuntimeException! PASS: irrelevantannotations/notnullmarked/Other.java:Nullable try-with-resources:test:irrelevant-annotation:Nullable FAIL: irrelevantannotations/notnullmarked/Other.java:NonNull try-with-resources:test:irrelevant-annotation:NonNull FAIL: irrelevantannotations/notnullmarked/Other.java:Nullable exception type:test:irrelevant-annotation:Nullable @@ -75,6 +76,7 @@ PASS: irrelevantannotations/nullmarked/Other.java:Nullable local variable array: PASS: irrelevantannotations/nullmarked/Other.java:NonNull local variable array:test:irrelevant-annotation:NonNull PASS: irrelevantannotations/nullmarked/Other.java:Nullable exception parameter:test:irrelevant-annotation:Nullable PASS: irrelevantannotations/nullmarked/Other.java:NonNull exception parameter:test:irrelevant-annotation:NonNull +PASS: irrelevantannotations/nullmarked/Other.java:Intrinsically NonNull exception parameter cannot be assigned null:test:cannot-convert:null? to RuntimeException! PASS: irrelevantannotations/nullmarked/Other.java:Nullable try-with-resources:test:irrelevant-annotation:Nullable FAIL: irrelevantannotations/nullmarked/Other.java:NonNull try-with-resources:test:irrelevant-annotation:NonNull FAIL: irrelevantannotations/nullmarked/Other.java:Nullable exception type:test:irrelevant-annotation:Nullable @@ -107,6 +109,7 @@ PASS: irrelevantannotations/nullunmarked/Other.java:Nullable local variable arra PASS: irrelevantannotations/nullunmarked/Other.java:NonNull local variable array:test:irrelevant-annotation:NonNull PASS: irrelevantannotations/nullunmarked/Other.java:Nullable exception parameter:test:irrelevant-annotation:Nullable PASS: irrelevantannotations/nullunmarked/Other.java:NonNull exception parameter:test:irrelevant-annotation:NonNull +PASS: irrelevantannotations/nullunmarked/Other.java:Intrinsically NonNull exception parameter cannot be assigned null:test:cannot-convert:null? to RuntimeException! PASS: irrelevantannotations/nullunmarked/Other.java:Nullable try-with-resources:test:irrelevant-annotation:Nullable FAIL: irrelevantannotations/nullunmarked/Other.java:NonNull try-with-resources:test:irrelevant-annotation:NonNull FAIL: irrelevantannotations/nullunmarked/Other.java:Nullable exception type:test:irrelevant-annotation:Nullable diff --git a/tests/regression-strict/Issue177.java b/tests/regression-strict/Issue177.java new file mode 100644 index 0000000..c47c8a9 --- /dev/null +++ b/tests/regression-strict/Issue177.java @@ -0,0 +1,18 @@ +// Copyright 2024 The JSpecify Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Test case for Issue 177: +// https://github.com/jspecify/jspecify-reference-checker/issues/177 + +class Issue177 {} diff --git a/tests/regression/Issue172.java b/tests/regression/Issue172.java index 5d30b31..b782b8d 100644 --- a/tests/regression/Issue172.java +++ b/tests/regression/Issue172.java @@ -21,6 +21,10 @@ class Issue172 { E e() { return null; } + + void p(E p) { + p = null; + } } class Issue172UnmarkedUse {