eclipse-jdt / eclipse-jdt/eclipse.jdt.core
Possible regression on accepted classpath entries for ASTParser in versions 3.40.0 to 3.45.0.
- 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
Assessment
This issue has not been assessed yet.