From 1cd66db008578cf0faa7e65ac17df295b426bc43 Mon Sep 17 00:00:00 2001 From: Christian Schima Date: Thu, 10 Oct 2024 22:38:42 +0200 Subject: [PATCH 1/2] Bugfix for #985: ECJ throws false error "Package collides with type". --- .../jdt/internal/compiler/batch/ClasspathJsr199.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/batch/ClasspathJsr199.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/batch/ClasspathJsr199.java index 2e88aec470b..3844261575c 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/batch/ClasspathJsr199.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/batch/ClasspathJsr199.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2015, 2018 IBM Corporation and others. + * Copyright (c) 2015, 2024 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -99,9 +99,13 @@ public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageN try (InputStream inputStream = jfo.openInputStream()) { ClassFileReader reader = ClassFileReader.read(inputStream.readAllBytes(), qualifiedBinaryFileName); - if (reader != null) { - return new NameEnvironmentAnswer(reader, fetchAccessRestriction(qualifiedBinaryFileName)); - } + + // To avoid false compiler errors "package collides with type" on case insensitive file systems + // (e. g. Windows), make a case sensitive comparison of class name and type name from reader. The + // reader contains the CASE SENSITIVE type name. + return reader != null && className.equals(new String(reader.getName())) + ? new NameEnvironmentAnswer(reader, fetchAccessRestriction(qualifiedBinaryFileName)) + : null; } } catch (ClassFormatException e) { // treat as if class file is missing From 3c11c5740380fcc84d778a3c78a63a66687acdc0 Mon Sep 17 00:00:00 2001 From: Christian Schima Date: Sat, 2 Nov 2024 22:49:32 +0100 Subject: [PATCH 2/2] Added testcase for "package collides with type". --- .../tool/tests/CompilerToolTests.java | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/org.eclipse.jdt.compiler.tool.tests/src/org/eclipse/jdt/compiler/tool/tests/CompilerToolTests.java b/org.eclipse.jdt.compiler.tool.tests/src/org/eclipse/jdt/compiler/tool/tests/CompilerToolTests.java index 9cc76024919..49ab7300034 100644 --- a/org.eclipse.jdt.compiler.tool.tests/src/org/eclipse/jdt/compiler/tool/tests/CompilerToolTests.java +++ b/org.eclipse.jdt.compiler.tool.tests/src/org/eclipse/jdt/compiler/tool/tests/CompilerToolTests.java @@ -25,8 +25,13 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -45,6 +50,7 @@ import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler; +import org.eclipse.jdt.internal.compiler.tool.EclipseFileManager; public class CompilerToolTests extends TestCase { private static final boolean DEBUG = false; @@ -1430,6 +1436,133 @@ public void testSupportedCompilerVersions() throws IOException { + "by compiler " + compiler.getClass().getName(), sourceVersions.contains(sourceVersion)); } } + + /** + * Compiles a class featuring a possible name collision with another one being present in classpath. This can only + * happen on case insensitive file systems. + * @throws IOException If I/O failure + */ + public void testCompilerOneClassWithPackageCollision() throws IOException { + + final String tempDir = System.getProperty("java.io.tmpdir"); + final String sep = File.separator; + final String classes = "clazzes"; + Path targetDir = Paths.get(tempDir, sep, classes, sep, "de", sep, "tk", sep, "foo"); + Files.createDirectories(targetDir); + + // ******************************************************************************** + // Compile first source file + // ******************************************************************************** + + String source1 = """ + package de.tk.foo; + + public class Test { + }"""; + + Path sourceFile1 = createSourceFile(targetDir, "Test.java", source1); + List sourceFiles = Collections.singletonList(sourceFile1.toFile()); + EclipseFileManager fileManager = new EclipseFileManager(null, null); + Iterable compilationUnits = fileManager.getJavaFileObjectsFromFiles(sourceFiles); + List optionList = List.of("-verbose", "-17"); + + CompilationTask task1 = + new EclipseCompiler().getTask( + new PrintWriter(System.out), + fileManager, // using 'fileManager' directly works + null, + optionList, + null, + compilationUnits + ); + + assertTrue("Compilation 1 failed", task1.call()); + + // ******************************************************************************** + // Compile second source file with classpath + // ******************************************************************************** + + String source2 = """ + package de.tk.foo.test; + + import de.tk.foo.Test; + + // This class might cause false-positive collision with package "de.tk.foo.test" present in classpath + // on case insensitive file systems. + public class Foo { + private Test test = new Test(); + + @Override + public String toString() { + return test.toString(); + } + }"""; + + Path tempPath = Paths.get(tempDir); + Path sourceFile2 = createSourceFile(tempPath, "Foo.java", source2); + Path clsDir = tempPath.resolve(classes); + + sourceFiles = Collections.singletonList(sourceFile2.toFile()); + compilationUnits = fileManager.getJavaFileObjectsFromFiles(sourceFiles); + + optionList = new ArrayList<>(optionList); // Create a mutable list + optionList.add("-classpath"); + optionList.add(clsDir.toString()); + + CompilationTask task2 = + new EclipseCompiler().getTask( + new PrintWriter(System.out), + new MyFileManager(fileManager), // using 'fileManager' directly works, but MyFileManager does not + null, + optionList, + null, + compilationUnits + ); + + assertTrue("Compilation 2 failed", task2.call()); + + // ******************************************************************************** + // Cleanup + // ******************************************************************************** + + Files.walk(clsDir) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + + assertTrue("Delete failed", Files.deleteIfExists(sourceFile1)); + assertTrue("Delete failed", Files.deleteIfExists(sourceFile2)); + assertTrue("Delete failed", Files.deleteIfExists(tempPath.resolve("Foo.class"))); + } + + /** + * Creates a (source) file in the given directory with content. + * @param dir Target directory + * @param fileName Name of new file + * @param content Content of the file + * @return Created file + * @throws IOException If I/O failure + */ + private Path createSourceFile(Path dir, String fileName, String content) throws IOException { + Path file = dir.resolve(fileName); + + try (BufferedWriter writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8)) { + writer.write(content); + writer.flush(); + } + return file; + } + + /** + * This class is used for the test {@link #testCompilerOneClassWithPackageCollision() + * testCompilerOneClassWithPackageCollision}. + */ + class MyFileManager extends ForwardingJavaFileManager { + protected MyFileManager(StandardJavaFileManager fileManager) { + super(fileManager); + } + } + /* * Clean up the compiler */