uber / NullAway

A tool to help eliminate NullPointerExceptions (NPEs) in your Java code with low build-time overhead
MIT License
3.63k stars 293 forks source link

Crash When Casting Raw Type to Parameterized Type #1019

Closed ahauschulte closed 1 month ago

ahauschulte commented 2 months ago

Summary

When analyzing code with NullAway that involves casting a raw type to a parameterized type, a compilation failure occurs, leading to the following stack trace.

An unhandled exception was thrown by the Error Prone static analysis plugin.
[ERROR]      Please report this at https://github.com/google/error-prone/issues/new and include the following:
[ERROR]   
[ERROR]      error-prone version: 2.30.0
[ERROR]      BugPattern: NullAway
[ERROR]      Stack Trace:
[ERROR]      java.lang.RuntimeException: Number of types arguments in java.util.List does not match java.util.List<java.lang.String>
[ERROR]     at com.uber.nullaway.generics.CheckIdenticalNullabilityVisitor.visitClassType(CheckIdenticalNullabilityVisitor.java:40)
[ERROR]     at com.uber.nullaway.generics.CheckIdenticalNullabilityVisitor.visitClassType(CheckIdenticalNullabilityVisitor.java:14)
[ERROR]     at jdk.compiler/com.sun.tools.javac.code.Type$ClassType.accept(Type.java:1050)
[ERROR]     at com.uber.nullaway.generics.GenericsChecks.identicalTypeParameterNullability(GenericsChecks.java:413)
[ERROR]     at com.uber.nullaway.generics.GenericsChecks.subtypeParameterNullability(GenericsChecks.java:442)
[ERROR]     at com.uber.nullaway.generics.GenericsChecks.checkTypeParameterNullnessForFunctionReturnType(GenericsChecks.java:392)
[ERROR]     at com.uber.nullaway.NullAway.checkReturnExpression(NullAway.java:918)
[ERROR]     at com.uber.nullaway.NullAway.matchReturn(NullAway.java:379)
[ERROR]     at com.google.errorprone.scanner.ErrorProneScanner.processMatchers(ErrorProneScanner.java:449)
[ERROR]     at com.google.errorprone.scanner.ErrorProneScanner.visitReturn(ErrorProneScanner.java:816)
[ERROR]     at com.google.errorprone.scanner.ErrorProneScanner.visitReturn(ErrorProneScanner.java:150)
[ERROR]     at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCReturn.accept(JCTree.java:1736)
[ERROR]     at jdk.compiler/com.sun.source.util.TreePathScanner.scan(TreePathScanner.java:92)
[ERROR]     at com.google.errorprone.scanner.Scanner.scan(Scanner.java:74)
[ERROR]     at com.google.errorprone.scanner.Scanner.scan(Scanner.java:48)
[ERROR]     at jdk.compiler/com.sun.source.util.TreeScanner.scan(TreeScanner.java:111)
[ERROR]     at jdk.compiler/com.sun.source.util.TreeScanner.visitBlock(TreeScanner.java:272)
[ERROR]     at com.google.errorprone.scanner.ErrorProneScanner.visitBlock(ErrorProneScanner.java:520)
[ERROR]     at com.google.errorprone.scanner.ErrorProneScanner.visitBlock(ErrorProneScanner.java:150)
[ERROR]     at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCBlock.accept(JCTree.java:1104)
[ERROR]     at jdk.compiler/com.sun.source.util.TreePathScanner.scan(TreePathScanner.java:92)
[ERROR]     at com.google.errorprone.scanner.Scanner.scan(Scanner.java:74)
[ERROR]     at com.google.errorprone.scanner.Scanner.scan(Scanner.java:48)
[ERROR]     at jdk.compiler/com.sun.source.util.TreeScanner.scanAndReduce(TreeScanner.java:96)
[ERROR]     at jdk.compiler/com.sun.source.util.TreeScanner.visitMethod(TreeScanner.java:224)
[ERROR]     at com.google.errorprone.scanner.ErrorProneScanner.visitMethod(ErrorProneScanner.java:740)
[ERROR]     at com.google.errorprone.scanner.ErrorProneScanner.visitMethod(ErrorProneScanner.java:150)
[ERROR]     at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCMethodDecl.accept(JCTree.java:948)
[ERROR]     at jdk.compiler/com.sun.source.util.TreePathScanner.scan(TreePathScanner.java:92)
[ERROR]     at com.google.errorprone.scanner.Scanner.scan(Scanner.java:74)
[ERROR]     at com.google.errorprone.scanner.Scanner.scan(Scanner.java:48)
[ERROR]     at jdk.compiler/com.sun.source.util.TreeScanner.scanAndReduce(TreeScanner.java:96)
[ERROR]     at jdk.compiler/com.sun.source.util.TreeScanner.scan(TreeScanner.java:111)
[ERROR]     at jdk.compiler/com.sun.source.util.TreeScanner.scanAndReduce(TreeScanner.java:119)
[ERROR]     at jdk.compiler/com.sun.source.util.TreeScanner.visitClass(TreeScanner.java:203)
[ERROR]     at com.google.errorprone.scanner.ErrorProneScanner.visitClass(ErrorProneScanner.java:548)
[ERROR]     at com.google.errorprone.scanner.ErrorProneScanner.visitClass(ErrorProneScanner.java:150)
[ERROR]     at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCClassDecl.accept(JCTree.java:855)
[ERROR]     at jdk.compiler/com.sun.source.util.TreePathScanner.scan(TreePathScanner.java:92)
[ERROR]     at com.google.errorprone.scanner.Scanner.scan(Scanner.java:74)
[ERROR]     at com.google.errorprone.scanner.Scanner.scan(Scanner.java:48)
[ERROR]     at jdk.compiler/com.sun.source.util.TreeScanner.scan(TreeScanner.java:111)
[ERROR]     at jdk.compiler/com.sun.source.util.TreeScanner.scanAndReduce(TreeScanner.java:119)
[ERROR]     at jdk.compiler/com.sun.source.util.TreeScanner.visitCompilationUnit(TreeScanner.java:152)
[ERROR]     at com.google.errorprone.scanner.ErrorProneScanner.visitCompilationUnit(ErrorProneScanner.java:560)
[ERROR]     at com.google.errorprone.scanner.ErrorProneScanner.visitCompilationUnit(ErrorProneScanner.java:150)
[ERROR]     at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCCompilationUnit.accept(JCTree.java:623)
[ERROR]     at jdk.compiler/com.sun.source.util.TreePathScanner.scan(TreePathScanner.java:66)
[ERROR]     at com.google.errorprone.scanner.Scanner.scan(Scanner.java:58)
[ERROR]     at com.google.errorprone.scanner.ErrorProneScannerTransformer.apply(ErrorProneScannerTransformer.java:43)
[ERROR]     at com.google.errorprone.ErrorProneAnalyzer.finished(ErrorProneAnalyzer.java:227)
[ERROR]     at jdk.compiler/com.sun.tools.javac.api.MultiTaskListener.finished(MultiTaskListener.java:133)
[ERROR]     at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.flow(JavaCompiler.java:1436)
[ERROR]     at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.flow(JavaCompiler.java:1383)
[ERROR]     at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:963)
[ERROR]     at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.lambda$doCall$0(JavacTaskImpl.java:104)
[ERROR]     at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.invocationHelper(JavacTaskImpl.java:152)
[ERROR]     at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.doCall(JavacTaskImpl.java:100)
[ERROR]     at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.call(JavacTaskImpl.java:94)
[ERROR]     at org.codehaus.plexus.compiler.javac.JavaxToolsCompiler.compileInProcess(JavaxToolsCompiler.java:126)
[ERROR]     at org.codehaus.plexus.compiler.javac.JavacCompiler.performCompile(JavacCompiler.java:214)
[ERROR]     at org.apache.maven.plugin.compiler.AbstractCompilerMojo.execute(AbstractCompilerMojo.java:1228)
[ERROR]     at org.apache.maven.plugin.compiler.CompilerMojo.execute(CompilerMojo.java:215)
[ERROR]     at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:126)
[ERROR]     at org.apache.maven.lifecycle.internal.MojoExecutor.doExecute2(MojoExecutor.java:328)
[ERROR]     at org.apache.maven.lifecycle.internal.MojoExecutor.doExecute(MojoExecutor.java:316)
[ERROR]     at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:212)
[ERROR]     at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:174)
[ERROR]     at org.apache.maven.lifecycle.internal.MojoExecutor.access$000(MojoExecutor.java:75)
[ERROR]     at org.apache.maven.lifecycle.internal.MojoExecutor$1.run(MojoExecutor.java:162)
[ERROR]     at org.apache.maven.plugin.DefaultMojosExecutionStrategy.execute(DefaultMojosExecutionStrategy.java:39)
[ERROR]     at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:159)
[ERROR]     at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:105)
[ERROR]     at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:73)
[ERROR]     at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build(SingleThreadedBuilder.java:53)
[ERROR]     at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:118)
[ERROR]     at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:261)
[ERROR]     at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:173)
[ERROR]     at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:101)
[ERROR]     at org.apache.maven.cli.MavenCli.execute(MavenCli.java:903)
[ERROR]     at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:280)
[ERROR]     at org.apache.maven.cli.MavenCli.main(MavenCli.java:203)
[ERROR]     at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
[ERROR]     at java.base/java.lang.reflect.Method.invoke(Method.java:580)
[ERROR]     at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:255)
[ERROR]     at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:201)
[ERROR]     at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:361)
[ERROR]     at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:314)
[ERROR]     at org.codehaus.classworlds.Launcher.main(Launcher.java:41)

Context

I have to work with some ancient, pre Java 5 legacy code that cannot be changed. This code necessarily uses raw types. For example, there is a class for representing a list of strings that is declared like this

public class StringList extends ArrayList {
    // ...
}

The implementation of this class guarantees that the list elements are nothing but Strings due to runtime checks. Because of this guarantee, it is safe to cast it like this

@SuppressWarnings("unchecked")
public static List<String> convert(final StringList stringList) {
    return stringList;
}

Having the convert() method analyzed by NullAway leads to the crash/stack trace mentioned above.

I use NullAway in the following setup:

Known Workaround

The compiler crash won't happen if a @SuppressWarnings("NullAway") is added to the convert() method.

@SuppressWarnings({"unchecked", "NullAway"})
public static List<String> convert(final StringList stringList) {
    return stringList;
}
msridhar commented 2 months ago

Thanks for the detailed report! We missed one place where we should bail out of checking for raw types. I put a fix up in https://github.com/uber/NullAway/pull/1021.

ahauschulte commented 2 months ago

@msridhar Thank you for taking care of this so quickly; much appreciated.