ClassCastException in `ClassDeclarationNullnessAnnotUtils.dfsWithFormals` for a field read through `? extends W` of a class with an intersection bound
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 66/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- java
- Domain
- testing-qa
Research direction
Start with ClassDeclarationNullnessAnnotUtils.dfsWithFormals and the regression commit d04c9b07ddaede1bfd735a298703cfec60aa445e; compare its behavior with parent e12c544c5. Reproduce using the supplied Java case and wildcard handling options, then trace the intersection bound through TypeSubstitutionUtils.asSuper. Done means the reproducer no longer crashes and reports only the uninitialized field diagnostic.
Written by the indexing model from the issue text.
Description
With wildcard handling on (JSpecifyExperimental=true, or HandleWildcardGenerics=true alone), NullAway crashes with a ClassCastException in ClassDeclarationNullnessAnnotUtils.dfsWithFormals when it checks an assignment from a field read through C<? extends W>, where C's type parameter has an intersection bound E & I and W is a type variable bounded by a subclass of E.
Versions: NullAway master at 5023d67b8bea439fc13beae0f3719d9781308577 (0.14.3-SNAPSHOT), Error Prone 2.50.0, JDK 21.0.9.
This is a regression. git bisect over 09fdea5a2..a03b7bb90 with the reproducer below names d04c9b07ddaede1bfd735a298703cfec60aa445e, "Avoid stale wildcard bounds after javac supertype inspection (#1849)", as the first commit that crashes; its parent, e12c544c5, does not crash.
Options: -XepOpt:NullAway:AnnotatedPackages=com.uber -XepOpt:NullAway:JSpecifyMode=true -XepOpt:NullAway:JSpecifyExperimental=true
package com.uber;
import org.jspecify.annotations.NullMarked;
@NullMarked
class Test {
static class E {}
interface I {}
static class F extends E {}
static class C<T extends E & I> { T field; }
static <W extends F> void test(C<? extends W> arg) {
F vf = arg.field;
}
}
Actual:
com/uber/Test.java:10: error: An unhandled exception was thrown by the Error Prone static analysis plugin.
F vf = arg.field;
^
Please report this at https://github.com/google/error-prone/issues/new and include the following, as well as a reproducing code sample (if possible):
error-prone version: 2.50.0
BugPattern: NullAway
Stack Trace:
java.lang.ClassCastException: class com.sun.tools.javac.code.Type$TypeVar cannot be cast to class javax.lang.model.type.DeclaredType (com.sun.tools.javac.code.Type$TypeVar is in module jdk.compiler of loader 'app'; javax.lang.model.type.DeclaredType is in module java.compiler of loader 'platform')
at com.uber.nullaway.generics.ClassDeclarationNullnessAnnotUtils.dfsWithFormals(ClassDeclarationNullnessAnnotUtils.java:76)
at com.uber.nullaway.generics.ClassDeclarationNullnessAnnotUtils.inheritancePath(ClassDeclarationNullnessAnnotUtils.java:102)
at com.uber.nullaway.generics.ClassDeclarationNullnessAnnotUtils.getAnnotatedSupertype(ClassDeclarationNullnessAnnotUtils.java:43)
at com.uber.nullaway.generics.TypeSubstitutionUtils.asSuper(TypeSubstitutionUtils.java:53)
at com.uber.nullaway.generics.CheckIdenticalNullabilityVisitor.visitClassType(CheckIdenticalNullabilityVisitor.java:90)
at com.uber.nullaway.generics.CheckIdenticalNullabilityVisitor.visitClassType(CheckIdenticalNullabilityVisitor.java:25)
at jdk.compiler/com.sun.tools.javac.code.Type$ClassType.accept(Type.java:1050)
at com.uber.nullaway.generics.GenericsChecks.identicalTypeParameterNullability(GenericsChecks.java:2398)
at com.uber.nullaway.generics.GenericsChecks.subtypeParameterNullability(GenericsChecks.java:2454)
at com.uber.nullaway.generics.GenericsChecks.subtypeParameterNullability(GenericsChecks.java:2410)
at com.uber.nullaway.generics.GenericsChecks.checkTypeParameterNullnessForAssignability(GenericsChecks.java:1098)
at com.uber.nullaway.NullAway.matchVariable(NullAway.java:1944)
...
Expected: no crash. The only diagnostic is the one NullAway already reports before the crash, @NonNull field 'Test$C.field' not initialized on line 8.
Each line below changed the reproducer as stated, with the same options unless the line names others:
E ve = arg.field;in place ofF vf = arg.field;crashes the same way.-XepOpt:NullAway:HandleWildcardGenerics=truein place ofJSpecifyExperimental=truecrashes the same way.JSpecifyMode=truewithoutJSpecifyExperimentalorHandleWildcardGenerics: no crash.C<T extends E>, a bound that is not an intersection: no crash.test(C<? extends F> arg), a wildcard bound that is not a type variable: no crash.W vt = arg.field;in place ofF vf = arg.field;: no crash.
Nearest reports: the open #1850, "Stop relying on mutable WildcardType.bound outside validated contexts", is the follow-up to #1849 but names no crash. The open #1972 and #1836 handle intersection bounds; the crash also occurs at the head of #1972 (f8876954d). The head of #1836 does not crash, but its base, 09fdea5a2, predates #1849 and does not crash either. Searched issues and pull requests for dfsWithFormals, ClassDeclarationNullnessAnnotUtils, TypeVar cannot be cast to DeclaredType, intersection captured wildcard ClassCastException, and intersection bound wildcard.
Suspected cause (a guess; the report stands without it): since #1849, TypeSubstitutionUtils.asSuper reaches ClassDeclarationNullnessAnnotUtils.inheritancePath with the upper bound of the captured ? extends W, an intersection of W, E, and I. dfsWithFormals casts every direct supertype of that intersection to DeclaredType, and one of them is the type variable W.
I found this by running NullAway over the javac test test/langtools/tools/javac/generics/6531090/T6531090a.java from openjdk/jdk with @NullMarked added to its class.
- Dominant language
- Java
- Stars
- 4.1k
- Forks
- 379
- Avg merge
- 1d 2h
- Merged PRs (30d)
- 106
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from uber/NullAway
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
uber/NullAway#2145 · 1 reaction ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 1-3 hours Newbie friendliness 85/100
uber/NullAway#2058 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
uber/NullAway#1899 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 15/100
uber/NullAway#2160 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 3/5 1-2 days Newbie friendliness 62/100
Maintainers usually reply within 1 day
Similar issues
-
Difficulty 1/5 Under an hour Newbie friendliness 88/100
johanhaleby/occurrent#1505 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
FasterXML/jackson-databind#6443 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 80/100
finos/fluxnova-bpm-platform#387 ·
Maintainers usually reply within 4 days
-
Difficulty 1/5 Under an hour Newbie friendliness 88/100