Skip to content

Fix NPE in VarWithPrimitive when lambda parameters have inferred primitive types#5868

Open
HarshMehta112 wants to merge 3 commits into
google:masterfrom
HarshMehta112:master
Open

Fix NPE in VarWithPrimitive when lambda parameters have inferred primitive types#5868
HarshMehta112 wants to merge 3 commits into
google:masterfrom
HarshMehta112:master

Conversation

@HarshMehta112

Copy link
Copy Markdown

Problem

Fixes #5857

VarWithPrimitive throws a NullPointerException when a lambda has an implicit
parameter whose inferred type is a primitive:

byte[] bar = new byte[6];
Map<Byte, List<Byte>> indicesMap =
    IntStream.range(0, 6)
        .mapToObj(n -> n)
        .collect(Collectors.groupingBy(
            n -> bar[n],
            Collectors.mapping(
                n -> (byte) n.intValue(),
                Collectors.toList())));

// Exception:
java.lang.NullPointerException: Cannot invoke
"com.sun.tools.javac.tree.JCTree.getStartPosition()"
because "tree" is null

Root Cause

hasImplicitType() returns true for both:

  • var declarations
  • Implicit lambda parameters (when VariableTree.getType() is null, per JDK-8268850)

VarWithPrimitive used hasImplicitType() as its guard, so lambda parameters
with inferred primitive types passed the check and reached
replaceVariableType().

Inside replaceVariableType(), tree.getType() returns null for these
parameters. hasExplicitSource(null, state) was then called unconditionally,
which invokes:

getStartPosition(null)

resulting in a NullPointerException.

Fix

Add a null guard for type in SuggestedFixes.replaceVariableType() at the
actual crash site.

If type == null:

  • Skip the hasExplicitSource() call.
  • Fall through to getStartPosition(tree).

The subsequent token scan finds no var keyword for implicit lambda
parameters and correctly returns Optional.empty(), producing NO_MATCH
without throwing an exception.

This is the minimal fix because:

  • It addresses the actual crash site.
  • It is defensive against any other callers that may pass a VariableTree
    with a null type.

Tests

implicitLambdaParameter_noMatch

Regression test reproducing the exact code from the bug report:

  • Single-parameter lambdas
  • Inferred int and byte types
  • Verifies NO_MATCH and no exception

multiParamImplicitLambda_noMatch

Tests a multi-parameter implicit lambda:

IntBinaryOperator op = (a, b) -> a + b;
  • Both parameters inferred as int
  • Verifies NO_MATCH and no exception

explicitLambdaParam_noMatch

Tests an explicit lambda parameter:

(int n) -> n
  • hasImplicitType() returns false
  • Verifies no match is reported

enhancedForLoop

Tests:

for (var x : intArray) {
    // ...
}

Expected fix:

for (int x : intArray) {
    // ...
}

forLoopInitializer

Tests:

for (var i = 0; i < 10; i++) {
    // ...
}

Expected fix:

for (int i = 0; i < 10; i++) {
    // ...
}

…e type replacement

Signed-off-by: Harsh Mehta <harshmehta010102@gmail.com>
@cushon

cushon commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the fix!

Could you also add a couple more test cases:

  • a lambda that has a var parameter like IntUnaryOperator op = (var n) -> n * 2;
  • with an annotation like @interface A { int var() default 0; } on the lambda parameter IntUnaryOperator op = (@A(var = 0) var n) -> n * 2;, to make sure the replacement doesn't replace the occurrence of var in the annotation instead of the type
  • a local variable test with @A(var = 0) var n also

Signed-off-by: Harsh Mehta <harshmehta010102@gmail.com>

@cushon cushon left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the author information for the commits are from two different github accounts and emails, and there is only a CLA on file for one of them

Can you sign the CLA for the other account or update the author information for the commits if it is incorrect?

@Test
public void annotatedVarLocalVariable() {
// The token scan finds two 'var' tokens (@A(var=0) attribute + type keyword) and safely
// returns no fix rather than risk replacing the annotation attribute instead of the type.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CI shows this test fails on JDK 27 because there's now an AST node for the var type, so the replacement succeeds

Can you split this test into separate tests for JDK < 27 and JDK >= 27, and use assume().that(Runtime.version().feature()).{isAtLeast,isLessThan}(27) to run the tests on the different JDK versions?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — I split the test into JDK < 27 and JDK ≥ 27 variants using assume().that(Runtime.version().feature()).isLessThan(27) and isAtLeast(27).

Signed-off-by: Harsh Mehta <harshmehta010102@gmail.com>
@HarshMehta112 HarshMehta112 requested a review from cushon June 12, 2026 09:39
copybara-service Bot pushed a commit that referenced this pull request Jun 12, 2026
…imitive types

## Problem

Fixes #5857

`VarWithPrimitive` throws a `NullPointerException` when a lambda has an implicit
parameter whose inferred type is a primitive:

```java
byte[] bar = new byte[6];
Map<Byte, List<Byte>> indicesMap =
    IntStream.range(0, 6)
        .mapToObj(n -> n)
        .collect(Collectors.groupingBy(
            n -> bar[n],
            Collectors.mapping(
                n -> (byte) n.intValue(),
                Collectors.toList())));

// Exception:
java.lang.NullPointerException: Cannot invoke
"com.sun.tools.javac.tree.JCTree.getStartPosition()"
because "tree" is null
```

## Root Cause

`hasImplicitType()` returns `true` for both:

- `var` declarations
- Implicit lambda parameters (when `VariableTree.getType()` is `null`, per JDK-8268850)

`VarWithPrimitive` used `hasImplicitType()` as its guard, so lambda parameters
with inferred primitive types passed the check and reached
`replaceVariableType()`.

Inside `replaceVariableType()`, `tree.getType()` returns `null` for these
parameters. `hasExplicitSource(null, state)` was then called unconditionally,
which invokes:

```java
getStartPosition(null)
```

resulting in a `NullPointerException`.

## Fix

Add a null guard for `type` in `SuggestedFixes.replaceVariableType()` at the
actual crash site.

If `type == null`:

- Skip the `hasExplicitSource()` call.
- Fall through to `getStartPosition(tree)`.

The subsequent token scan finds no `var` keyword for implicit lambda
parameters and correctly returns `Optional.empty()`, producing `NO_MATCH`
without throwing an exception.

This is the minimal fix because:

- It addresses the actual crash site.
- It is defensive against any other callers that may pass a `VariableTree`
  with a null type.

## Tests

### `implicitLambdaParameter_noMatch`

Regression test reproducing the exact code from the bug report:

- Single-parameter lambdas
- Inferred `int` and `byte` types
- Verifies `NO_MATCH` and no exception

### `multiParamImplicitLambda_noMatch`

Tests a multi-parameter implicit lambda:

```java
IntBinaryOperator op = (a, b) -> a + b;
```

- Both parameters inferred as `int`
- Verifies `NO_MATCH` and no exception

### `explicitLambdaParam_noMatch`

Tests an explicit lambda parameter:

```java
(int n) -> n
```

- `hasImplicitType()` returns `false`
- Verifies no match is reported

### `enhancedForLoop`

Tests:

```java
for (var x : intArray) {
    // ...
}
```

Expected fix:

```java
for (int x : intArray) {
    // ...
}
```

### `forLoopInitializer`

Tests:

```java
for (var i = 0; i < 10; i++) {
    // ...
}
```

Expected fix:

```java
for (int i = 0; i < 10; i++) {
    // ...
}
```

Fixes #5868

FUTURE_COPYBARA_INTEGRATE_REVIEW=#5868 from HarshMehta112:master 5881069
PiperOrigin-RevId: 931039338
copybara-service Bot pushed a commit that referenced this pull request Jun 12, 2026
…imitive types

## Problem

Fixes #5857

`VarWithPrimitive` throws a `NullPointerException` when a lambda has an implicit
parameter whose inferred type is a primitive:

```java
byte[] bar = new byte[6];
Map<Byte, List<Byte>> indicesMap =
    IntStream.range(0, 6)
        .mapToObj(n -> n)
        .collect(Collectors.groupingBy(
            n -> bar[n],
            Collectors.mapping(
                n -> (byte) n.intValue(),
                Collectors.toList())));

// Exception:
java.lang.NullPointerException: Cannot invoke
"com.sun.tools.javac.tree.JCTree.getStartPosition()"
because "tree" is null
```

## Root Cause

`hasImplicitType()` returns `true` for both:

- `var` declarations
- Implicit lambda parameters (when `VariableTree.getType()` is `null`, per JDK-8268850)

`VarWithPrimitive` used `hasImplicitType()` as its guard, so lambda parameters
with inferred primitive types passed the check and reached
`replaceVariableType()`.

Inside `replaceVariableType()`, `tree.getType()` returns `null` for these
parameters. `hasExplicitSource(null, state)` was then called unconditionally,
which invokes:

```java
getStartPosition(null)
```

resulting in a `NullPointerException`.

## Fix

Add a null guard for `type` in `SuggestedFixes.replaceVariableType()` at the
actual crash site.

If `type == null`:

- Skip the `hasExplicitSource()` call.
- Fall through to `getStartPosition(tree)`.

The subsequent token scan finds no `var` keyword for implicit lambda
parameters and correctly returns `Optional.empty()`, producing `NO_MATCH`
without throwing an exception.

This is the minimal fix because:

- It addresses the actual crash site.
- It is defensive against any other callers that may pass a `VariableTree`
  with a null type.

## Tests

### `implicitLambdaParameter_noMatch`

Regression test reproducing the exact code from the bug report:

- Single-parameter lambdas
- Inferred `int` and `byte` types
- Verifies `NO_MATCH` and no exception

### `multiParamImplicitLambda_noMatch`

Tests a multi-parameter implicit lambda:

```java
IntBinaryOperator op = (a, b) -> a + b;
```

- Both parameters inferred as `int`
- Verifies `NO_MATCH` and no exception

### `explicitLambdaParam_noMatch`

Tests an explicit lambda parameter:

```java
(int n) -> n
```

- `hasImplicitType()` returns `false`
- Verifies no match is reported

### `enhancedForLoop`

Tests:

```java
for (var x : intArray) {
    // ...
}
```

Expected fix:

```java
for (int x : intArray) {
    // ...
}
```

### `forLoopInitializer`

Tests:

```java
for (var i = 0; i < 10; i++) {
    // ...
}
```

Expected fix:

```java
for (int i = 0; i < 10; i++) {
    // ...
}
```

Fixes #5868

FUTURE_COPYBARA_INTEGRATE_REVIEW=#5868 from HarshMehta112:master 5881069
PiperOrigin-RevId: 931039338
copybara-service Bot pushed a commit that referenced this pull request Jun 12, 2026
…imitive types

## Problem

Fixes #5857

`VarWithPrimitive` throws a `NullPointerException` when a lambda has an implicit
parameter whose inferred type is a primitive:

```java
byte[] bar = new byte[6];
Map<Byte, List<Byte>> indicesMap =
    IntStream.range(0, 6)
        .mapToObj(n -> n)
        .collect(Collectors.groupingBy(
            n -> bar[n],
            Collectors.mapping(
                n -> (byte) n.intValue(),
                Collectors.toList())));

// Exception:
java.lang.NullPointerException: Cannot invoke
"com.sun.tools.javac.tree.JCTree.getStartPosition()"
because "tree" is null
```

## Root Cause

`hasImplicitType()` returns `true` for both:

- `var` declarations
- Implicit lambda parameters (when `VariableTree.getType()` is `null`, per JDK-8268850)

`VarWithPrimitive` used `hasImplicitType()` as its guard, so lambda parameters
with inferred primitive types passed the check and reached
`replaceVariableType()`.

Inside `replaceVariableType()`, `tree.getType()` returns `null` for these
parameters. `hasExplicitSource(null, state)` was then called unconditionally,
which invokes:

```java
getStartPosition(null)
```

resulting in a `NullPointerException`.

## Fix

Add a null guard for `type` in `SuggestedFixes.replaceVariableType()` at the
actual crash site.

If `type == null`:

- Skip the `hasExplicitSource()` call.
- Fall through to `getStartPosition(tree)`.

The subsequent token scan finds no `var` keyword for implicit lambda
parameters and correctly returns `Optional.empty()`, producing `NO_MATCH`
without throwing an exception.

This is the minimal fix because:

- It addresses the actual crash site.
- It is defensive against any other callers that may pass a `VariableTree`
  with a null type.

## Tests

### `implicitLambdaParameter_noMatch`

Regression test reproducing the exact code from the bug report:

- Single-parameter lambdas
- Inferred `int` and `byte` types
- Verifies `NO_MATCH` and no exception

### `multiParamImplicitLambda_noMatch`

Tests a multi-parameter implicit lambda:

```java
IntBinaryOperator op = (a, b) -> a + b;
```

- Both parameters inferred as `int`
- Verifies `NO_MATCH` and no exception

### `explicitLambdaParam_noMatch`

Tests an explicit lambda parameter:

```java
(int n) -> n
```

- `hasImplicitType()` returns `false`
- Verifies no match is reported

### `enhancedForLoop`

Tests:

```java
for (var x : intArray) {
    // ...
}
```

Expected fix:

```java
for (int x : intArray) {
    // ...
}
```

### `forLoopInitializer`

Tests:

```java
for (var i = 0; i < 10; i++) {
    // ...
}
```

Expected fix:

```java
for (int i = 0; i < 10; i++) {
    // ...
}
```

Fixes #5868

FUTURE_COPYBARA_INTEGRATE_REVIEW=#5868 from HarshMehta112:master 5881069
PiperOrigin-RevId: 931039338
copybara-service Bot pushed a commit that referenced this pull request Jun 12, 2026
…imitive types

## Problem

Fixes #5857

`VarWithPrimitive` throws a `NullPointerException` when a lambda has an implicit
parameter whose inferred type is a primitive:

```java
byte[] bar = new byte[6];
Map<Byte, List<Byte>> indicesMap =
    IntStream.range(0, 6)
        .mapToObj(n -> n)
        .collect(Collectors.groupingBy(
            n -> bar[n],
            Collectors.mapping(
                n -> (byte) n.intValue(),
                Collectors.toList())));

// Exception:
java.lang.NullPointerException: Cannot invoke
"com.sun.tools.javac.tree.JCTree.getStartPosition()"
because "tree" is null
```

## Root Cause

`hasImplicitType()` returns `true` for both:

- `var` declarations
- Implicit lambda parameters (when `VariableTree.getType()` is `null`, per JDK-8268850)

`VarWithPrimitive` used `hasImplicitType()` as its guard, so lambda parameters
with inferred primitive types passed the check and reached
`replaceVariableType()`.

Inside `replaceVariableType()`, `tree.getType()` returns `null` for these
parameters. `hasExplicitSource(null, state)` was then called unconditionally,
which invokes:

```java
getStartPosition(null)
```

resulting in a `NullPointerException`.

## Fix

Add a null guard for `type` in `SuggestedFixes.replaceVariableType()` at the
actual crash site.

If `type == null`:

- Skip the `hasExplicitSource()` call.
- Fall through to `getStartPosition(tree)`.

The subsequent token scan finds no `var` keyword for implicit lambda
parameters and correctly returns `Optional.empty()`, producing `NO_MATCH`
without throwing an exception.

This is the minimal fix because:

- It addresses the actual crash site.
- It is defensive against any other callers that may pass a `VariableTree`
  with a null type.

## Tests

### `implicitLambdaParameter_noMatch`

Regression test reproducing the exact code from the bug report:

- Single-parameter lambdas
- Inferred `int` and `byte` types
- Verifies `NO_MATCH` and no exception

### `multiParamImplicitLambda_noMatch`

Tests a multi-parameter implicit lambda:

```java
IntBinaryOperator op = (a, b) -> a + b;
```

- Both parameters inferred as `int`
- Verifies `NO_MATCH` and no exception

### `explicitLambdaParam_noMatch`

Tests an explicit lambda parameter:

```java
(int n) -> n
```

- `hasImplicitType()` returns `false`
- Verifies no match is reported

### `enhancedForLoop`

Tests:

```java
for (var x : intArray) {
    // ...
}
```

Expected fix:

```java
for (int x : intArray) {
    // ...
}
```

### `forLoopInitializer`

Tests:

```java
for (var i = 0; i < 10; i++) {
    // ...
}
```

Expected fix:

```java
for (int i = 0; i < 10; i++) {
    // ...
}
```

Fixes #5868

FUTURE_COPYBARA_INTEGRATE_REVIEW=#5868 from HarshMehta112:master 5881069
PiperOrigin-RevId: 931039338
copybara-service Bot pushed a commit that referenced this pull request Jun 12, 2026
…imitive types

## Problem

Fixes #5857

`VarWithPrimitive` throws a `NullPointerException` when a lambda has an implicit
parameter whose inferred type is a primitive:

```java
byte[] bar = new byte[6];
Map<Byte, List<Byte>> indicesMap =
    IntStream.range(0, 6)
        .mapToObj(n -> n)
        .collect(Collectors.groupingBy(
            n -> bar[n],
            Collectors.mapping(
                n -> (byte) n.intValue(),
                Collectors.toList())));

// Exception:
java.lang.NullPointerException: Cannot invoke
"com.sun.tools.javac.tree.JCTree.getStartPosition()"
because "tree" is null
```

## Root Cause

`hasImplicitType()` returns `true` for both:

- `var` declarations
- Implicit lambda parameters (when `VariableTree.getType()` is `null`, per JDK-8268850)

`VarWithPrimitive` used `hasImplicitType()` as its guard, so lambda parameters
with inferred primitive types passed the check and reached
`replaceVariableType()`.

Inside `replaceVariableType()`, `tree.getType()` returns `null` for these
parameters. `hasExplicitSource(null, state)` was then called unconditionally,
which invokes:

```java
getStartPosition(null)
```

resulting in a `NullPointerException`.

## Fix

Add a null guard for `type` in `SuggestedFixes.replaceVariableType()` at the
actual crash site.

If `type == null`:

- Skip the `hasExplicitSource()` call.
- Fall through to `getStartPosition(tree)`.

The subsequent token scan finds no `var` keyword for implicit lambda
parameters and correctly returns `Optional.empty()`, producing `NO_MATCH`
without throwing an exception.

This is the minimal fix because:

- It addresses the actual crash site.
- It is defensive against any other callers that may pass a `VariableTree`
  with a null type.

## Tests

### `implicitLambdaParameter_noMatch`

Regression test reproducing the exact code from the bug report:

- Single-parameter lambdas
- Inferred `int` and `byte` types
- Verifies `NO_MATCH` and no exception

### `multiParamImplicitLambda_noMatch`

Tests a multi-parameter implicit lambda:

```java
IntBinaryOperator op = (a, b) -> a + b;
```

- Both parameters inferred as `int`
- Verifies `NO_MATCH` and no exception

### `explicitLambdaParam_noMatch`

Tests an explicit lambda parameter:

```java
(int n) -> n
```

- `hasImplicitType()` returns `false`
- Verifies no match is reported

### `enhancedForLoop`

Tests:

```java
for (var x : intArray) {
    // ...
}
```

Expected fix:

```java
for (int x : intArray) {
    // ...
}
```

### `forLoopInitializer`

Tests:

```java
for (var i = 0; i < 10; i++) {
    // ...
}
```

Expected fix:

```java
for (int i = 0; i < 10; i++) {
    // ...
}
```

Fixes #5868

FUTURE_COPYBARA_INTEGRATE_REVIEW=#5868 from HarshMehta112:master 5881069
PiperOrigin-RevId: 931039338
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[VarWithPrimitive] java.lang.NullPointerException: Cannot invoke "com.sun.tools.javac.tree.JCTree.getStartPosition()" because "tree" is null

2 participants