Repository: uber/NullAway (Java)

Issue:
outer.new Inner(){} in static method throws IndexOutOfBoundsException
```java
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.
https://github.com/uber/NullAway/commit/b3646ef0802ed90cb1cfc70d79668b3e689e17bc 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: https://github.com/uber/NullAway/blob/649f25ad3116a7c64732fbc7a5d2bc40140b9cdd/nullaway/src/main/java/com/uber/nullaway/NullAway.java#L445

```java
    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.


Discussion: Thank you for the detailed report and diagnosis!  #1191 fixes this issue pretty much exactly as you suggested (along with the related minor bug)



Fix (patch):
diff --git a/nullaway/src/main/java/com/uber/nullaway/NullAway.java b/nullaway/src/main/java/com/uber/nullaway/NullAway.java
index 51a1ea259f..4fcec6a11e 100644
--- a/nullaway/src/main/java/com/uber/nullaway/NullAway.java
+++ b/nullaway/src/main/java/com/uber/nullaway/NullAway.java
@@ -441,9 +441,15 @@ public Description matchNewClass(NewClassTree tree, VisitorState state) {
     if (methodSymbol == null) {
       throw new RuntimeException("not expecting unresolved method here");
     }
+    ExpressionTree enclosingExpression = tree.getEnclosingExpression();
+    if (enclosingExpression != null) {
+      // technically this is not a dereference; there is a requireNonNull() call in the
+      // bytecode.  but it's close enough for error reporting
+      state.reportMatch(matchDereference(enclosingExpression, tree, state));
+    }
     List<? extends ExpressionTree> actualParams = tree.getArguments();
-    if (tree.getClassBody() != null && actualParams.size() > 0) {
-      // passing parameters to constructor of anonymous class
+    if (tree.getClassBody() != null) {
+      // invoking 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
@@ -1861,18 +1867,7 @@ private Description handleInvocation(
       Symbol.MethodSymbol methodSymbol,
       List<? extends ExpressionTree> actualParams) {
     List<VarSymbol> formalParams = methodSymbol.getParameters();
-
     boolean varArgsMethod = methodSymbol.isVarArgs();
-    if (formalParams.size() != actualParams.size()
-        && !varArgsMethod
-        && !methodSymbol.isStatic()
-        && methodSymbol.isConstructor()
-        && methodSymbol.enclClass().isInner()) {
-      // In special cases like one in issue #366
-      // formal params and actual params do not match while using JDK11+
-      // we bail out in this particular case
-      return Description.NO_MATCH;
-    }
 
     // always do unboxing checks, whether or not the invoked method is annotated
     for (int i = 0; i < formalParams.size() && i < actualParams.size(); i++) {