Skip to content

ClassCastException in JSpecify mode when generics and arrays are involved #1416

@JarvisCraft

Description

@JarvisCraft

Description

When using the latest NullAway (0.12.15) in JSpecify mode, ClassCastException occurs while handling array types with null-marking at generic positions.

Minimal reproducible example

Code:

import java.util.function.Function;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;

@NullMarked
class MinExample {

    void use(AsyncTask<AsyncTask<@Nullable Void>[]> tasks) {
        tasks.flatMap(readyTasks -> consume(readyTasks));
        //           ^
    }


    interface AsyncTask<T extends @Nullable Object> {
        <U extends @Nullable Object> AsyncTask<U> flatMap(
            Function<T, ? extends AsyncTask<U>> mapper
        );
    }

    AsyncTask<@Nullable Void> consume(AsyncTask<@Nullable Void>[] tasks) {
        throw new UnsupportedOperationException("TODO");
    }
}

Expected behaviour: successful compilation.

Actual behaviour: javac crash

Stacktace
MinExample.java:9: error: An unhandled exception was thrown by the Error Prone static analysis plugin.
        tasks.flatMap(readyTasks -> consume(readyTasks));
                     ^
     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.45.0
     BugPattern: NullAway
     Stack Trace:
     java.lang.ClassCastException: class com.sun.tools.javac.code.Type$TypeVar cannot be cast to class com.sun.tools.javac.code.Type$ArrayType (com.sun.tools.javac.code.Type$TypeVar and com.sun.tools.javac.code.Type$ArrayType are in module jdk.compiler of loader 'app')
  	at com.uber.nullaway.generics.CheckIdenticalNullabilityVisitor.visitArrayType(CheckIdenticalNullabilityVisitor.java:101)
  	at com.uber.nullaway.generics.CheckIdenticalNullabilityVisitor.visitArrayType(CheckIdenticalNullabilityVisitor.java:17)
  	at jdk.compiler/com.sun.tools.javac.code.Type$ArrayType.accept(Type.java:1378)
  	at com.uber.nullaway.generics.CheckIdenticalNullabilityVisitor.visitClassType(CheckIdenticalNullabilityVisitor.java:77)
  	at com.uber.nullaway.generics.CheckIdenticalNullabilityVisitor.visitClassType(CheckIdenticalNullabilityVisitor.java:17)
  	at jdk.compiler/com.sun.tools.javac.code.Type$ClassType.accept(Type.java:1056)
  	at com.uber.nullaway.generics.GenericsChecks.identicalTypeParameterNullability(GenericsChecks.java:1099)
  	at com.uber.nullaway.generics.GenericsChecks.subtypeParameterNullability(GenericsChecks.java:1130)
  	at com.uber.nullaway.generics.GenericsChecks.lambda$compareGenericTypeParameterNullabilityForCall$0(GenericsChecks.java:1264)
  	at com.uber.nullaway.InvocationArguments.forEach(InvocationArguments.java:86)
  	at com.uber.nullaway.generics.GenericsChecks.compareGenericTypeParameterNullabilityForCall(GenericsChecks.java:1235)
  	at com.uber.nullaway.NullAway.handleInvocation(NullAway.java:2003)
  	at com.uber.nullaway.NullAway.matchMethodInvocation(NullAway.java:444)
  	at com.google.errorprone.scanner.ErrorProneScanner.processMatchers(ErrorProneScanner.java:539)
  	at com.google.errorprone.scanner.ErrorProneScanner.visitMethodInvocation(ErrorProneScanner.java:888)
  	at com.google.errorprone.scanner.ErrorProneScanner.visitMethodInvocation(ErrorProneScanner.java:178)
  	at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCMethodInvocation.accept(JCTree.java:1876)
  	at jdk.compiler/com.sun.source.util.TreePathScanner.scan(TreePathScanner.java:92)
  	at com.google.errorprone.scanner.Scanner.scan(Scanner.java:74)
  	at com.google.errorprone.scanner.Scanner.scan(Scanner.java:48)
  	at jdk.compiler/com.sun.source.util.TreeScanner.visitExpressionStatement(TreeScanner.java:504)
  	at com.google.errorprone.scanner.ErrorProneScanner.visitExpressionStatement(ErrorProneScanner.java:775)
  	at com.google.errorprone.scanner.ErrorProneScanner.visitExpressionStatement(ErrorProneScanner.java:178)
  	at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCExpressionStatement.accept(JCTree.java:1655)
  	at jdk.compiler/com.sun.source.util.TreePathScanner.scan(TreePathScanner.java:92)
  	at com.google.errorprone.scanner.Scanner.scan(Scanner.java:74)
  	at com.google.errorprone.scanner.Scanner.scan(Scanner.java:48)
  	at jdk.compiler/com.sun.source.util.TreeScanner.scan(TreeScanner.java:110)
  	at jdk.compiler/com.sun.source.util.TreeScanner.visitBlock(TreeScanner.java:271)
  	at com.google.errorprone.scanner.ErrorProneScanner.visitBlock(ErrorProneScanner.java:621)
  	at com.google.errorprone.scanner.ErrorProneScanner.visitBlock(ErrorProneScanner.java:178)
  	at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCBlock.accept(JCTree.java:1148)
  	at jdk.compiler/com.sun.source.util.TreePathScanner.scan(TreePathScanner.java:92)
  	at com.google.errorprone.scanner.Scanner.scan(Scanner.java:74)
  	at com.google.errorprone.scanner.Scanner.scan(Scanner.java:48)
  	at jdk.compiler/com.sun.source.util.TreeScanner.scanAndReduce(TreeScanner.java:95)
  	at jdk.compiler/com.sun.source.util.TreeScanner.visitMethod(TreeScanner.java:223)
  	at com.google.errorprone.scanner.ErrorProneScanner.visitMethod(ErrorProneScanner.java:882)
  	at com.google.errorprone.scanner.ErrorProneScanner.visitMethod(ErrorProneScanner.java:178)
  	at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCMethodDecl.accept(JCTree.java:992)
  	at jdk.compiler/com.sun.source.util.TreePathScanner.scan(TreePathScanner.java:92)
  	at com.google.errorprone.scanner.Scanner.scan(Scanner.java:74)
  	at com.google.errorprone.scanner.Scanner.scan(Scanner.java:48)
  	at jdk.compiler/com.sun.source.util.TreeScanner.scanAndReduce(TreeScanner.java:95)
  	at jdk.compiler/com.sun.source.util.TreeScanner.scan(TreeScanner.java:110)
  	at jdk.compiler/com.sun.source.util.TreeScanner.scanAndReduce(TreeScanner.java:118)
  	at jdk.compiler/com.sun.source.util.TreeScanner.visitClass(TreeScanner.java:202)
  	at com.google.errorprone.scanner.ErrorProneScanner.visitClass(ErrorProneScanner.java:649)
  	at com.google.errorprone.scanner.ErrorProneScanner.visitClass(ErrorProneScanner.java:178)
  	at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCClassDecl.accept(JCTree.java:899)
  	at jdk.compiler/com.sun.source.util.TreePathScanner.scan(TreePathScanner.java:92)
  	at com.google.errorprone.scanner.Scanner.scan(Scanner.java:74)
  	at com.google.errorprone.scanner.Scanner.scan(Scanner.java:48)
  	at jdk.compiler/com.sun.source.util.TreeScanner.scan(TreeScanner.java:110)
  	at jdk.compiler/com.sun.source.util.TreeScanner.scanAndReduce(TreeScanner.java:118)
  	at jdk.compiler/com.sun.source.util.TreeScanner.visitCompilationUnit(TreeScanner.java:151)
  	at com.google.errorprone.scanner.ErrorProneScanner.visitCompilationUnit(ErrorProneScanner.java:661)
  	at com.google.errorprone.scanner.ErrorProneScanner.visitCompilationUnit(ErrorProneScanner.java:178)
  	at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCCompilationUnit.accept(JCTree.java:627)
  	at jdk.compiler/com.sun.source.util.TreePathScanner.scan(TreePathScanner.java:66)
  	at com.google.errorprone.scanner.Scanner.scan(Scanner.java:58)
  	at com.google.errorprone.scanner.ErrorProneScannerTransformer.apply(ErrorProneScannerTransformer.java:43)
  	at com.google.errorprone.ErrorProneAnalyzer.finished(ErrorProneAnalyzer.java:231)
  	at jdk.compiler/com.sun.tools.javac.api.MultiTaskListener.finished(MultiTaskListener.java:133)
  	at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.flow(JavaCompiler.java:1423)
  	at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.flow(JavaCompiler.java:1370)
  	at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:955)
  	at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.lambda$doCall$0(JavacTaskImpl.java:104)
  	at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.invocationHelper(JavacTaskImpl.java:152)
  	at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.doCall(JavacTaskImpl.java:100)
  	at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.call(JavacTaskImpl.java:94)
  	at org.gradle.internal.compiler.java.IncrementalCompileTask.call(IncrementalCompileTask.java:92)
  	at org.gradle.api.internal.tasks.compile.AnnotationProcessingCompileTask.call(AnnotationProcessingCompileTask.java:94)
  	at org.gradle.api.internal.tasks.compile.ResourceCleaningCompilationTask.call(ResourceCleaningCompilationTask.java:57)
  	at org.gradle.api.internal.tasks.compile.JdkJavaCompiler.execute(JdkJavaCompiler.java:83)
  	at org.gradle.api.internal.tasks.compile.JdkJavaCompiler.execute(JdkJavaCompiler.java:50)
  	at org.gradle.api.internal.tasks.compile.daemon.AbstractIsolatedCompilerWorkerExecutor$CompilerWorkAction.execute(AbstractIsolatedCompilerWorkerExecutor.java:79)
  	at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:68)
  	at org.gradle.workers.internal.AbstractClassLoaderWorker$1.create(AbstractClassLoaderWorker.java:54)
  	at org.gradle.workers.internal.AbstractClassLoaderWorker$1.create(AbstractClassLoaderWorker.java:48)
  	at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100)
  	at org.gradle.workers.internal.AbstractClassLoaderWorker.executeInClassLoader(AbstractClassLoaderWorker.java:48)
  	at org.gradle.workers.internal.FlatClassLoaderWorker.run(FlatClassLoaderWorker.java:32)
  	at org.gradle.workers.internal.FlatClassLoaderWorker.run(FlatClassLoaderWorker.java:22)
  	at org.gradle.workers.internal.WorkerDaemonServer.run(WorkerDaemonServer.java:108)
  	at org.gradle.workers.internal.WorkerDaemonServer.run(WorkerDaemonServer.java:77)
  	at org.gradle.process.internal.worker.request.WorkerAction.lambda$run$1(WorkerAction.java:150)
  	at org.gradle.process.internal.worker.child.WorkerLogEventListener.withWorkerLoggingProtocol(WorkerLogEventListener.java:41)
  	at org.gradle.process.internal.worker.request.WorkerAction.lambda$run$2(WorkerAction.java:150)
  	at org.gradle.internal.operations.CurrentBuildOperationRef.with(CurrentBuildOperationRef.java:84)
  	at org.gradle.process.internal.worker.request.WorkerAction.run(WorkerAction.java:142)
  	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
  	at java.base/java.lang.reflect.Method.invoke(Method.java:565)
  	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
  	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
  	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182)
  	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164)
  	at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:414)
  	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
  	at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:47)
  	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1090)
  	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:614)
  	at java.base/java.lang.Thread.run(Thread.java:1474)

Related problem

Originally, the signature of flatMap was PECSified, i.e.:

        <U extends @Nullable Object> AsyncTask<U> flatMap(
            Function<? super T, ? extends AsyncTask<U>> mapper
        );

which did not cause a crash but did cause a false positive with the code above:

MinExample.java:9: error: [NullAway] incompatible types: AsyncTask<Void> [] cannot be converted to AsyncTask<@Nullable Void> []
          tasks.flatMap(readyTasks -> consume(readyTasks));
                                              ^
      (see http://t.uber.com/nullaway )
  1 error

which also seems invalid(?) (although I may be misreading the spec).

Metadata

Metadata

Assignees

No one assigned

    Labels

    crashjspecifyRelated to support for jspecify standard (see jspecify.dev)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions