Skip to content

NPE when formatting java file with java 21 preview features. #937

Closed
@sureshg

Description

@sureshg

Env

$ openjdk 21-ea 2023-09-19
OpenJDK Runtime Environment (build 21-ea+20-1677)
OpenJDK 64-Bit Server VM (build 21-ea+20-1677, mixed mode, sharing)

google-javaformat              = "1.17.0"

Error

> Task :spotlessJava FAILED
Step 'google-java-format' found problem in 'src/main/java/xxx/DOP.java':
57:10: error: java.lang.NullPointerException: Cannot invoke "com.sun.tools.javac.tree.JCTree.getStartPosition()" because "node" is null
        at com.google.googlejavaformat.java.JavaInputAstVisitor.sync(JavaInputAstVisitor.java:3933)
        at com.google.googlejavaformat.java.JavaInputAstVisitor.visitToDeclare(JavaInputAstVisitor.java:2799)
        at com.google.googlejavaformat.java.JavaInputAstVisitor.visitEnhancedForLoop(JavaInputAstVisitor.java:791)
        at com.google.googlejavaformat.java.JavaInputAstVisitor.visitEnhancedForLoop(JavaInputAstVisitor.java:167)
        at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCEnhancedForLoop.accept(JCTree.java:1248)
        at jdk.compiler/com.sun.source.util.TreePathScanner.scan(TreePathScanner.java:92)
        at com.google.googlejavaformat.java.JavaInputAstVisitor.scan(JavaInputAstVisitor.java:357)
        at com.google.googlejavaformat.java.JavaInputAstVisitor.visitStatements(JavaInputAstVisitor.java:2229)
        at com.google.googlejavaformat.java.JavaInputAstVisitor.methodBody(JavaInputAstVisitor.java:1550)
        at com.google.googlejavaformat.java.JavaInputAstVisitor.visitMethod(JavaInputAstVisitor.java:1537)
        at com.google.googlejavaformat.java.JavaInputAstVisitor.visitMethod(JavaInputAstVisitor.java:167)
        at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCMethodDecl.accept(JCTree.java:944)
        at jdk.compiler/com.sun.source.util.TreePathScanner.scan(TreePathScanner.java:92)
        at com.google.googlejavaformat.java.JavaInputAstVisitor.scan(JavaInputAstVisitor.java:357)
        at com.google.googlejavaformat.java.JavaInputAstVisitor.addBodyDeclarations(JavaInputAstVisitor.java:3760)
        at com.google.googlejavaformat.java.JavaInputAstVisitor.visitClassDeclaration(JavaInputAstVisitor.java:2044)
        at com.google.googlejavaformat.java.java17.Java17InputAstVisitor.visitClass(Java17InputAstVisitor.java:120)
        at com.google.googlejavaformat.java.java17.Java17InputAstVisitor.visitClass(Java17InputAstVisitor.java:51)
        at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCClassDecl.accept(JCTree.java:851)
        at jdk.compiler/com.sun.source.util.TreePathScanner.scan(TreePathScanner.java:92)
        at com.google.googlejavaformat.java.JavaInputAstVisitor.scan(JavaInputAstVisitor.java:357)

I am not sure for which part it's failing. I was playing the java 21 preview features. So here is the sample file used in the task

Sample

import org.jspecify.annotations.NullMarked;

import java.io.*;
import java.lang.reflect.RecordComponent;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;

import static java.lang.System.out;
import static java.util.Objects.requireNonNull;

public class DOP {

    public static void main(String[] args) throws Exception {
        run();
    }

    public static void run() throws Exception {
        @NullMarked
        record Person(String name, int age) {
        }

        var future = new CompletableFuture<>();
        var textBlock = """
                This is text block
                This will join \
                with the line : %s
                "quote" = "added"
                Escape Start  \n \t \r \b \f end
                Space Escape-\s\s\s\s\s\s\s\s\s\s-end
                Regex \\S \\d \\D \\w \\W
                \\d+
                Escape char: \u0020 \u00A0 \u2000 \u3000 \uFEFF \u200B \u200C \u200D \u2028 \u2029
                END
                """
                .formatted(new Person("Foo", 40));
        future.complete(textBlock);
        out.println(future.get());

        amberReflections();
        recordPatterns();
        genericRecordPattern();
        serializeRecord();
    }

    private static void recordPatterns() {
        record Point(int x, int y) {
        }
        var points = List.of(new Point(1, 2), new Point(3, 4), new Point(5, 6));
        // Record pattern in enhanced for loop
        for (Point(var x, var y) : points) {
            out.println("Point: (" + x + ", " + y + ")");
        }
    }

    interface Name<T> {
    }

    record FullName<T>(T firstName, T lastName) implements Name<T> {
    }

    private static void print(Name name) {
        var result = switch (name) {
            case FullName(var first, var last) -> first + ", " + last;
            default -> "Invalid name";
        };
        out.println(result);

        if (name instanceof FullName<?> f) {
            // out.println(f.firstName() + ", " + f.lastName());
        }

        // Named record pattern is not supported
        if (name instanceof FullName(var first, var last)) {
            // out.println(first + ", " + last);
        }
    }

    private static void genericRecordPattern() {
        print(new FullName<>("Foo", "Bar"));
        print(new FullName<>(1, 2));
        print(new FullName<>(10L, 20L));
    }

    private static void amberReflections() {
        var sealedClazz = Result.class;
        out.println("Result (Interface)  -> " + sealedClazz.isInterface());
        out.println("Result (Sealed Class) -> " + sealedClazz.isSealed());

        for (Class<?> permittedSubclass : sealedClazz.getPermittedSubclasses()) {
            out.println("\nPermitted Subclass : " + permittedSubclass.getName());
            if (permittedSubclass.isRecord()) {
                out.println(permittedSubclass.getSimpleName() + " record components are,");
                for (RecordComponent rc : permittedSubclass.getRecordComponents()) {
                    out.println(rc);
                }
            }
        }
    }


    private static void serializeRecord() throws Exception {
        // Local record
        record Lang(String name, int year) implements Serializable {

            Lang {
                requireNonNull(name);
                if (year <= 0) {
                    throw new IllegalArgumentException("Invalid year " + year);
                }
            }
        }

        var serialFile = Files.createTempFile("record-serial", "data").toFile();
        serialFile.deleteOnExit();

        try (var oos = new ObjectOutputStream(new FileOutputStream(serialFile))) {
            List<Record> recs = List.of(
                    new Lang("Java", 25),
                    new Lang("Kotlin", 10),
                    (Record) Result.success(100)
            );

            for (Record rec : recs) {
                out.println("Serializing record: " + rec);
                oos.writeObject(rec);
            }
            oos.writeObject(null); // EOF
        }

        try (var ois = new ObjectInputStream(new FileInputStream(serialFile))) {
            Object rec;
            while ((rec = ois.readObject()) != null) {
                var result = switch (rec) {
                    case null -> "n/a";
                    case Lang l when l.year >= 20 -> l.toString();
                    case Lang(var name, var year) -> name;
                    case Result<?> r -> "Result value: " + r.getOrNull();
                    default -> "Invalid serialized data. Expected Result, but found " + rec;
                };

                out.println("Deserialized record: " + rec);
                out.println(result);
            }
        }

        results().forEach(r -> {
            var result = switch (r) {
                case null -> "n/a";
                case Result.Success<?> s -> s.toString();
                case Result.Failure<?> f -> f.toString();
            };
            out.println("Result (Sealed Type): " + result);
        });
    }

    static List<Result<?>> results() {
        return Arrays.asList(getResult(5), getResult(25), getResult(-1));
    }

    static Result<Number> getResult(long l) {
        // Unnecessary boxing required for boolean check in switch expression
        return switch (Long.valueOf(l)) {
            case Long s when s > 0 && s < 10 -> Result.success(s);
            case Long s when s > 10 -> Result.failure(new IllegalArgumentException(String.valueOf(s)));
            default -> null;
        };
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions