Bug 560434 - NullPointerException in ReferenceExpression.generateCode when compiling java.base
Summary: NullPointerException in ReferenceExpression.generateCode when compiling java....
Status: NEW
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Core (show other bugs)
Version: 4.15   Edit
Hardware: PC Linux
: P3 normal (vote)
Target Milestone: ---   Edit
Assignee: JDT-Core-Inbox CLA
QA Contact:
URL:
Whiteboard: stalebug
Keywords:
Depends on:
Blocks:
 
Reported: 2020-02-23 07:46 EST by Jonas Konrad CLA
Modified: 2023-03-27 15:36 EDT (History)
2 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Jonas Konrad CLA 2020-02-23 07:46:38 EST
This is a regression from a previous JDT version. The code runs without exception in 3.15.0 but crashes in 3.20.0. Bisecting shows that this bug was probably introduced in commit 03980ed8a170fb1a3db7e242fe3e2831224a91ec .

When compiling the 'share' and 'macosx' *combined* platforms of the java.base module of the jdk9u head, the following exception (line numbers from jdt 3.20.0):

Exception in thread "main" java.lang.NullPointerException
	at org.eclipse.jdt.internal.compiler.ast.ReferenceExpression.generateCode(ReferenceExpression.java:350)
	at org.eclipse.jdt.internal.compiler.ast.Statement.generateArguments(Statement.java:420)
	at org.eclipse.jdt.internal.compiler.ast.MessageSend.generateCode(MessageSend.java:501)
	at org.eclipse.jdt.internal.compiler.ast.ReturnStatement.generateCode(ReturnStatement.java:203)
	at org.eclipse.jdt.internal.compiler.ast.Block.generateCode(Block.java:83)
	at org.eclipse.jdt.internal.compiler.ast.IfStatement.generateCode(IfStatement.java:204)
	at org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration.generateCode(AbstractMethodDeclaration.java:344)
	at org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration.generateCode(AbstractMethodDeclaration.java:281)
	at org.eclipse.jdt.internal.compiler.ast.TypeDeclaration.generateCode(TypeDeclaration.java:579)
	at org.eclipse.jdt.internal.compiler.ast.TypeDeclaration.generateCode(TypeDeclaration.java:649)
	at org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration.generateCode(CompilationUnitDeclaration.java:412)
	at org.eclipse.jdt.internal.compiler.Compiler.process(Compiler.java:912)
	at org.eclipse.jdt.core.dom.CompilationUnitResolver.resolve(CompilationUnitResolver.java:1044)
	at org.eclipse.jdt.core.dom.CompilationUnitResolver.resolve(CompilationUnitResolver.java:662)
	at org.eclipse.jdt.core.dom.ASTParser.createASTs(ASTParser.java:1013)
	at at.yawk.javabrowser.generator.TestCase.main(TestCase.java:36)

The exact steps to reproduce this are as follows:

- `cd /var/tmp`
- Download the jdk9u repo: https://hg.openjdk.java.net/jdk-updates/jdk9u/jdk/archive/tip.tar.bz2 (according to the website, 17464:d54486c189e5 is the latest revision at the time I'm writing this)
- `tar xf tip.tar.bz2`
- `mkdir merged`
- `cp -r jdk-d54486c189e5/src/java.base/{share,macosx}/classes/* merged/`
- Build the test case from git@github.com:yawkat/eclipse-jdt-testcase-0.git . It contains the java code listed below, and a basic maven pom to build with the latest jdt.
- Run the test case. This can be done by building the classpath using `mvn dependency:build-classpath`, appending `target/testcase-1.0-SNAPSHOT.jar`

The test case in the repo is:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.FileASTRequestor;

public class TestCase {
    public static void main(String[] args) throws IOException {
        ASTParser parser = ASTParser.newParser(AST.JLS9);
        Map<String, String> options = new HashMap<>();
        options.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_10);
        options.put(JavaCore.CORE_ENCODING, "UTF-8");
        options.put(JavaCore.COMPILER_DOC_COMMENT_SUPPORT, JavaCore.ENABLED);
        parser.setCompilerOptions(options);
        parser.setResolveBindings(true);
        parser.setKind(ASTParser.K_COMPILATION_UNIT);
        String src = "/var/tmp/merged";
        parser.setEnvironment(
                new String[0],
                new String[]{ src },
                new String[]{ "UTF-8" },
                false
        );
        List<Path> files = Files.walk(Paths.get(src))
                .filter(p -> p.toString().endsWith(".java") && !Files.isDirectory(p))
                .sorted(Comparator.reverseOrder())
                .collect(Collectors.toList());
        parser.createASTs(
                files.stream().map(Path::toString).toArray(String[]::new),
                files.stream().map(f -> "UTF-8").toArray(String[]::new),
                new String[0],
                new FileASTRequestor() {
                },
                null
        );
    }
}

What I have discovered about this bug so far:
- The bug is triggered because for some reason, during compilation, the type for java.lang.Object in the package java.lang is missing, even though it had previously been visited. This leads to the NPE in the codegen because this "unknown" class has no `getClass` method.
- Automatic bisecting (`mvn -Pbuild-individual-bundles -pl org.eclipse.jdt.core -am clean package` and then running the test case with the new jdt.core) shows that this bug first occurs in 03980ed8a170fb1a3db7e242fe3e2831224a91ec .
- This bug does not happen when instead of merging the two source directories they are compiled as two separate source locations, i.e. by passing two strings to setEnvironment.
- This bug relies on file order, so the `.sorted(Comparator.reverseOrder())` in the test case above is absolutely essential.
- This bug does not happen with other platform source code, only with macosx.

Since this bug appears to be extremely dependent on the input it is possible that I have missed some factor in my environment that triggers this bug that I failed to mention in the reproduction steps. If you have trouble reproducing, I will try to narrow it down further.
Comment 1 Stephan Herrmann CLA 2020-02-23 09:03:20 EST
Thanks for the detailed report.

As you mention macosx, will it be possible to reproduce on other platforms, specifically on linux?
Comment 2 Jonas Konrad CLA 2020-02-23 09:47:04 EST
(In reply to Stephan Herrmann from comment #1)
> Thanks for the detailed report.
> 
> As you mention macosx, will it be possible to reproduce on other platforms,
> specifically on linux?

Sorry for being misleading there. The issue happens when compiling the .java files of the java.base module from the share and macosx platforms of java 9 - that is the set of source files where this issue manifests. The actual system where I am compiling is Linux x86_64 (5.5.2-arch1-1, java 11 runs the compiler), but I expect that this issue can be reproduced on other OS as well. While the bug is sensitive to the source files being compiled and their order, I've not seen any other factors that could prevent reproducing it so far.
Comment 3 Jonas Konrad CLA 2021-03-11 14:20:34 EST
I think I've identified the cause. The `java.base` source folder contains a `module-info.java` with a module declaration for `java.base`. When this `module-info.java` is parsed, it creates a `SourceModuleBinding` and registers it. However, the other source files in the source directory remain in the unnamed module. This means that when those source files are compiled, and JDT tries to look up stdlib types like `java.lang.String` from the `java.base` module, it can't find those classes. The compiler cannot deal with this scenario.

I'm not sure how to fix this. Two approaches come to mind: Either assign all the source files to the module defined in `module-info.java` instead of to the default unnamed module, or don't let the `SourceModuleBinding` override `java.base`.

It's also not clear to me why source file order is relevant to the bug. Maybe some type resolution happens before `module-info.java` is visited, if it doesn't come first in the given source file list.

A workaround for this bug is to not pass in the `module-info.java` if it's for `java.base` and compile it separately instead.
Comment 4 Eclipse Genie CLA 2023-03-27 15:36:36 EDT
This bug hasn't had any activity in quite some time. Maybe the problem got resolved, was a duplicate of something else, or became less pressing for some reason - or maybe it's still relevant but just hasn't been looked at yet.

If you have further information on the current state of the bug, please add it. The information can be, for example, that the problem still occurs, that you still want the feature, that more information is needed, or that the bug is (for whatever reason) no longer relevant.

--
The automated Eclipse Genie.