Skip to content

Commit c0a4e48

Browse files
committed
GROOVY-11894: Provide a NullChecker for groovy-typecheckers
1 parent 2a7d1e9 commit c0a4e48

7 files changed

Lines changed: 2100 additions & 0 deletions

File tree

subprojects/groovy-typecheckers/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ dependencies {
2424
implementation rootProject
2525
api projects.groovyMacro
2626
testImplementation projects.groovyTest
27+
testImplementation 'org.jspecify:jspecify:1.0.0'
2728
}
2829

2930
groovyLibrary {
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package groovy.typecheckers
20+
21+
import org.apache.groovy.lang.annotation.Incubating
22+
import org.apache.groovy.typecheckers.NullCheckingVisitor
23+
import org.codehaus.groovy.transform.stc.GroovyTypeCheckingExtensionSupport
24+
25+
/**
26+
* A compile-time type checker that detects potential null dereferences and null-safety violations
27+
* in code annotated with {@code @Nullable}, {@code @NonNull}, and {@code @MonotonicNonNull} annotations.
28+
* <p>
29+
* This checker performs annotation-based null checking only. For additional flow-sensitive analysis
30+
* that tracks nullability through assignments and control flow (even in unannotated code),
31+
* use {@link StrictNullChecker} instead.
32+
* <p>
33+
* Supported annotations are recognized by simple name from any package:
34+
* <ul>
35+
* <li>Nullable: {@code @Nullable}, {@code @CheckForNull}, {@code @MonotonicNonNull}</li>
36+
* <li>Non-null: {@code @NonNull}, {@code @NotNull}, {@code @Nonnull}</li>
37+
* </ul>
38+
* <p>
39+
* Detected errors include:
40+
* <ul>
41+
* <li>Assigning {@code null} to a {@code @NonNull} variable</li>
42+
* <li>Passing {@code null} or a {@code @Nullable} value to a {@code @NonNull} parameter</li>
43+
* <li>Returning {@code null} or a {@code @Nullable} value from a {@code @NonNull} method</li>
44+
* <li>Dereferencing a {@code @Nullable} variable without a null check or safe navigation ({@code ?.})</li>
45+
* <li>Dereferencing the result of a {@code @Nullable}-returning method without a null check</li>
46+
* <li>Re-assigning {@code null} to a {@code @MonotonicNonNull} field after initialization</li>
47+
* </ul>
48+
* <p>
49+
* The checker recognizes null guards ({@code if (x != null)}), early exit patterns
50+
* ({@code if (x == null) return/throw}), and safe navigation ({@code ?.}).
51+
*
52+
* <pre>
53+
* {@code @TypeChecked(extensions = 'groovy.typecheckers.NullChecker')}
54+
* void process(@Nullable String input) {
55+
* // input.length() // error: potential null dereference
56+
* input?.length() // ok: safe navigation
57+
* if (input != null) {
58+
* input.length() // ok: null guard
59+
* }
60+
* }
61+
* </pre>
62+
*
63+
* Over time, the idea would be to support more cases as per:
64+
* https://checkerframework.org/manual/#nullness-checker
65+
*
66+
* @see StrictNullChecker
67+
* @see NullCheckingVisitor
68+
*/
69+
@Incubating
70+
class NullChecker extends GroovyTypeCheckingExtensionSupport.TypeCheckingDSL {
71+
72+
@Override
73+
Object run() {
74+
NullCheckingVisitor.install(this, false)
75+
}
76+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package groovy.typecheckers
20+
21+
import org.apache.groovy.lang.annotation.Incubating
22+
import org.apache.groovy.typecheckers.NullCheckingVisitor
23+
import org.codehaus.groovy.transform.stc.GroovyTypeCheckingExtensionSupport
24+
25+
/**
26+
* A compile-time type checker that performs all the annotation-based null checks of {@link NullChecker}
27+
* plus flow-sensitive null tracking for unannotated code.
28+
* <p>
29+
* In addition to the annotation-based checks, this checker tracks nullability through variable
30+
* assignments and control flow. This means it can detect potential null dereferences even when
31+
* code does not use {@code @Nullable}/{@code @NonNull} annotations:
32+
*
33+
* <pre>
34+
* {@code @TypeChecked(extensions = 'groovy.typecheckers.StrictNullChecker')}
35+
* void process() {
36+
* def x = null
37+
* // x.toString() // error: x may be null
38+
* x = 'hello'
39+
* x.toString() // ok: x reassigned non-null
40+
* }
41+
* </pre>
42+
* <p>
43+
* The flow-sensitive tracking also recognizes return values from {@code @Nullable} methods
44+
* assigned to variables, null guards, and early exit patterns.
45+
* <p>
46+
* Use this checker for code that benefits from stricter null analysis. For code bases
47+
* with a mix of strictness requirements, apply {@code NullChecker} to relaxed code and
48+
* {@code StrictNullChecker} to strict code via per-class or per-method
49+
* {@code @TypeChecked} annotations.
50+
*
51+
* @see NullChecker
52+
* @see NullCheckingVisitor
53+
*/
54+
@Incubating
55+
class StrictNullChecker extends GroovyTypeCheckingExtensionSupport.TypeCheckingDSL {
56+
57+
@Override
58+
Object run() {
59+
NullCheckingVisitor.install(this, true)
60+
}
61+
}

0 commit comments

Comments
 (0)