eclipse-jdt / eclipse-jdt/eclipse.jdt.core

Possible regression on accepted classpath entries for ASTParser in versions 3.40.0 to 3.45.0.

Open
#4,920 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Java
Stars
237
Forks
195
Avg merge
1d 12h
Merged PRs (30d)
47

Description

I've prepared a small regression test suite around different possible scenarios of provided classpath entries for ASTParser across multiple Java versions:
- jrt-fs.jar as single classpath entry : supported across all Java versions and Eclipse JDT core versions
- jmods as classpath entries : supported up to 3.39.0, ASTParser throws "Missing system library" in versions 3.40.0 to 3.45.0.

The test requires a JAVA_HOME to be set with a version that will support the version of Eclipse JDT core to be tested.

The missing Override annotation warnings serve here as a hint that types are recognized from JRE system library.

```java
package demo;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.File;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

class AstParserSystemLibraryTest {

private static final String SOURCE =
"""
package demo;
public class Bean {
public String toString() {
return "Bean";
}
}
""";

@ParameterizedTest(name = "[{index}] java {0} / {2}")
@MethodSource("matrix")
void shouldParseAndReportMissingOverrideWarning(String javaVersion, int astLevel, String classpathScenario,
String[] classpathEntries) {
ASTParser parser = ASTParser.newParser(astLevel);
parser.setKind(ASTParser.K_COMPILATION_UNIT);
parser.setUnitName("Bean.java");
parser.setSource(SOURCE.toCharArray());
parser.setResolveBindings(true);
parser.setBindingsRecovery(true);
parser.setEnvironment(classpathEntries, new String[0], new String[0], false);
parser.setCompilerOptions(compilerOptions(javaVersion));

CompilationUnit compilationUnit = (CompilationUnit) parser.createAST(null);

assertFalse((compilationUnit.getFlags() & ASTNode.MALFORMED) != 0, "Compilation unit is malformed");

List warnings = Stream.of(compilationUnit.getProblems())
.filter(IProblem::isWarning)
.filter(problem -> problem.getID() == IProblem.MissingOverrideAnnotation)
.toList();

assertEquals(1, warnings.size(), "Expected exactly one missing @Override warning");
assertTrue(warnings.get(0).getMessage().contains("toString"));
assertTrue(warnings.get(0).getMessage().contains("@Override"));
}

private static Stream matrix() {
List matrix = new ArrayList<>();
List scenarios = classpathScenarios();

Assumptions.assumeFalse(scenarios.isEmpty(), "No JDK runtime entries found for the current VM");

IntStream.rangeClosed(9, 26).forEach(javaVersion -> {
String version = String.valueOf(javaVersion);
int astLevel = resolveAstLevel(javaVersion);
for (Arguments scenario : scenarios) {
Object[] values = scenario.get();
matrix.add(Arguments.of(version, astLevel, values[0], values[1]));
}
});

return matrix.stream();
}

private static List classpathScenarios() {
List scenarios = new ArrayList<>();

String[] jrtFsJar = getJrtFsJarClasspath();
if (jrtFsJar.length > 0) {
scenarios.add(Arguments.of("jrt-fs.jar", jrtFsJar));
}

String[] jmodEntries = getJmodEntriesClasspath();
if (jmodEntries.length > 0) {
scenarios.add(Arguments.of("jmod entries", jmodEntries));
}

return scenarios;
}

private static Map compilerOptions(String javaVersion) {
Map options = JavaCore.getOptions();

options.put(JavaCore.COMPILER_SOURCE, javaVersion);
options.put(JavaCore.COMPILER_COMPLIANCE, javaVersion);
options.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, javaVersion);
options.put(JavaCore.COMPILER_PB_MISSING_OVERRIDE_ANNOTATION, JavaCore.WARNING);

return options;
}

private static int resolveAstLevel(int javaVersion) {
String fieldName = "JLS" + javaVersion;
try {
Field field = AST.class.getField(fieldName);
return field.getInt(null);
} catch (ReflectiveOperationException ignored) {
return AST.getJLSLatest();
}
}

private static String[] getJrtFsJarClasspath() {
String javaHome = System.getProperty("java.home");
if (javaHome == null || javaHome.isBlank()) {
return new String[0];
}

File jrtFsJar = new File(javaHome, "lib" + File.separator + "jrt-fs.jar");
if (!jrtFsJar.isFile()) {
return new String[0];
}

return new String[] { jrtFsJar.getAbsolutePath() };
}

private static String[] getJmodEntriesClasspath() {
String javaHome = System.getProperty("java.home");
if (javaHome == null || javaHome.isBlank()) {
return new String[0];
}

File jmodsDirectory = new File(javaHome, "jmods");
if (!jmodsDirectory.isDirectory()) {
return new String[0];
}

File[] files = jmodsDirectory.listFiles((_, name) -> name.endsWith(".jmod"));
if (files == null || files.length == 0) {
return new String[0];
}

List entries = new ArrayList<>(files.length);
for (File file : files) {
entries.add(file.getAbsolutePath());
}
return entries.toArray(new String[0]);
}
}
```

An isolated failure happens for Java 23 but let's ignore it for now in this context.

I see that the jmod entries are acknowledged as `org.eclipse.jdt.internal.core.builder.ClasspathJMod` entries but ASTParser throws "Missing system library" in versions 3.40.0 to 3.45.0.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.