eclipse-jdt / eclipse.jdt.core

Eclipse Public License 2.0
161 stars 130 forks source link

JDT Core throws NullPointerException: Cannot read field "declaringClass" because "enumConstructor" is null #2398

Closed r0texx closed 5 months ago

r0texx commented 5 months ago

Hello,

I'm trying to build a source, but JDT Core throws an exception:

Exception in thread "main" java.lang.NullPointerException: Cannot read field "declaringClass" because "enumConstructor" is null
    at org.eclipse.jdt.internal.compiler.problem.ProblemReporter.cannotInvokeSuperConstructorInEnum(ProblemReporter.java:1438)
    at org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall.resolve(ExplicitConstructorCall.java:348)
    at org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration.resolveStatements(ConstructorDeclaration.java:671)
    at org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration.resolve(AbstractMethodDeclaration.java:606)
    at org.eclipse.jdt.internal.compiler.ast.TypeDeclaration.resolve(TypeDeclaration.java:1517)
    at org.eclipse.jdt.internal.compiler.ast.TypeDeclaration.resolve(TypeDeclaration.java:1637)
    at org.eclipse.jdt.internal.compiler.ast.TypeDeclaration.resolve(TypeDeclaration.java:1408)
    at org.eclipse.jdt.internal.compiler.ast.TypeDeclaration.resolve(TypeDeclaration.java:1646)
    at org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration.resolve(CompilationUnitDeclaration.java:669)
    at org.eclipse.jdt.internal.compiler.Compiler.process(Compiler.java:904)
    at org.eclipse.jdt.core.dom.CompilationUnitResolver.resolve(CompilationUnitResolver.java:1122)
    at org.eclipse.jdt.core.dom.CompilationUnitResolver.resolve(CompilationUnitResolver.java:739)
    at org.eclipse.jdt.core.dom.ASTParser.createASTs(ASTParser.java:1049)

For the following test source (save it to a directory and change the ROOT variable):

package com.test;

public class Option {
    enum Inner extends com.test.Option {
        private String s;

        Inner(com.test.Option.Missing v) {
            super();
        }
    }
}

I use the following code to parse it using JDT Core:

package main;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
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 PoCMain {
    private static final File ROOT = new File("PATH_WHERE_YOU_UNPACK_com.test.Option");

    public static void main(String[] args) throws Throwable {
        List<String> files = findSources(ROOT, ".java");
        getParser().createASTs(files.toArray(new String[files.size()]), null, new String[0], new FileASTRequestor() {}, null);
    }

    private static ASTParser getParser() {
        ASTParser astParser = ASTParser.newParser(AST.JLS21);
        astParser.setResolveBindings(true);
        astParser.setStatementsRecovery(true);
        astParser.setBindingsRecovery(true);
        astParser.setKind(ASTParser.K_COMPILATION_UNIT);
        astParser.setEnvironment(new String[0], new String[0], null, false);

        Map<String, String> options = JavaCore.getDefaultOptions();
        options.put(JavaCore.COMPILER_COMPLIANCE, JavaCore.VERSION_21);
        options.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_21);
        options.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, JavaCore.VERSION_21);
        astParser.setCompilerOptions(options);
        return astParser;
    }

    private static List<String> findSources(File dir, String ext) {
        List<String> paths = new ArrayList<>();
        for (File child : dir.listFiles()) {
            if (child.isFile()) {
                if (child.getName().endsWith(ext)) {
                    paths.add(child.getAbsolutePath());
                }
            } else {
                paths.addAll(findSources(child, ext));
            }
        }

        return paths;
    }
}
jarthana commented 5 months ago

Just compiling the Option.java with ECJ reveals the problem. On quick look, I see that there are two compiler errors in Option.java:

  1. On the enum declaration - the compiler rejects the illegal extends clause.
  2. The constructor Inner() has an unresolved parameter.

Now, despite (1), we mark the receiver of the constructor with T_JavaLangEnum. And as a result of (2), the methodScope.referenceMethod().binding is null in ExplicitConstructorCall, line 352. Now put both these together, we hit the NPE. I wonder if we must do the type ID check. I see three ways out:

  1. Just do a null check assuming the null method binding means a primary error has already been reported.
  2. If we simply change the receiverType.erasure().id == TypeIds.T_JavaLangEnum to receiverType.isEnum(). The illegal enum declaration seems to have been taken care of by the isEnum() check.
  3. Go all the way and make sure the super type is not set to java.lang.Enum in this case.

My choice is (2). @srikanth-sankaran What do you think?

jarthana commented 5 months ago

Actually, I see the type ID check and isEnum() check both being used in an OR check. So, looks like it is expected for them to be out of sync.