Skip to content

outer.new Inner(){} in static method throws IndexOutOfBoundsException #1188

@jeffrey-easyesi

Description

@jeffrey-easyesi
package nullawaytest;
class Outer {
    class Inner {}
    static Inner f(Outer outer) { return outer.new Inner() {}; }
}

Using JDK 21 and NullAway 0.12.6:

Outer.java:4: error: An unhandled exception was thrown by the Error Prone static analysis plugin.
    static Inner f(Outer outer) { return outer.new Inner() {}; }
                                               ^
     Please report this at https://github.com/google/error-prone/issues/new and include the following:
  
     error-prone version: 2.37.0
     BugPattern: NullAway
     Stack Trace:
     java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
        at jdk.compiler/com.sun.tools.javac.util.List.get(List.java:476)
        at com.uber.nullaway.NullAway.handleInvocation(NullAway.java:1986)
        at com.uber.nullaway.NullAway.matchNewClass(NullAway.java:453)
        at com.google.errorprone.scanner.ErrorProneScanner.processMatchers(ErrorProneScanner.java:509)
        ...

Analysis

When the outer.new Inner(){} syntax is used, the constructor symbol has an extra formal parameter.
b3646ef added some special-case code to work around this issue, but it does not take effect when in a static method (methodSymbol.enclClass().isInner() returns false - I think the anonymous subclass of Inner` is not itself considered an inner class)

Interestingly, this bug does not occur when Inner's constructor has explicit parameters, because in that case, matchNewClass uses the superclass's constructor instead:

if (tree.getClassBody() != null && actualParams.size() > 0) {

    if (tree.getClassBody() != null && actualParams.size() > 0) {
      // passing parameters to constructor of anonymous class
      // this constructor just invokes the constructor of the superclass, and
      // in the AST does not have the parameter nullability annotations from the superclass.
      // so, treat as if the superclass constructor is being invoked directly
      // see https://github.com/uber/NullAway/issues/102
      methodSymbol = getSymbolOfSuperConstructor(methodSymbol, state);
    }

So I think you could just remove the actualParams.size() > 0 check, and this would handle all cases, making the special-case in handleInvocation unnecessary.

Vaguely-related minor bug I noticed while looking at this code

matchNewClass also should check that tree.getEnclosingExpression(), if it exists, is non-nullable. outer.new ... will throw NPE if outer is null.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions