Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG]: ECJ passes invalid StandardLocations to methods in custom JavaFileManager implementations #3496

Open
ascopes opened this issue Dec 29, 2024 · 10 comments
Labels
needinfo Further information is requested

Comments

@ascopes
Copy link
Contributor

ascopes commented Dec 29, 2024

Follow up of #958 (@stephan-herrmann as requested).

See #3496 (comment) for description.

@stephan-herrmann
Copy link
Contributor

For me --release 1.8 and --release 9 produced the same problem, see #958 (comment) - please double check the reported difference.

@ascopes
Copy link
Contributor Author

ascopes commented Dec 29, 2024

Would it be possible to have the latest fixed jar to double check this on the latest fixes? I checked the snapshots earlier today but those lacked the changes.

@stephan-herrmann
Copy link
Contributor

Would it be possible to have the latest fixed jar to double check this on the latest fixes? I checked the snapshots earlier today but those lacked the changes.

The PR builds have been removed upon merge. But the following build should have the fix already: https://download.eclipse.org/eclipse/downloads/drops4/I20241229-0630/#JDTCORE

@ascopes
Copy link
Contributor Author

ascopes commented Dec 29, 2024

Perfect, thanks. I have a feeling the forward slashes rather than periods may well be a bug on my side looking into this further, but will check and close the issue if this is the case.

@ascopes
Copy link
Contributor Author

ascopes commented Dec 29, 2024

What error were you seeing? I am now seeing issues with MODULE_PATH being passed to functions that expect a package oriented class.

@ascopes ascopes changed the title [BUG]: Failure to resolve modules with JavaFileManager [BUG]: Compilation failures with custom JavaFileManager Dec 29, 2024
@ascopes
Copy link
Contributor Author

ascopes commented Dec 30, 2024

On my side, I see io.github.ascopes.jct.ex.JctIllegalInputException: Location MODULE_PATH must be package-oriented.

Digging into the call site of this, I see the following:

 126     at java.base/java.lang.Thread.dumpStack(Thread.java:2210)
 127     at io.github.ascopes.jct.filemanagers.impl.JctFileManagerImpl.requirePackageOrientedLocation(JctFileManagerImpl.java:393)
 128     at io.github.ascopes.jct.filemanagers.impl.JctFileManagerImpl.list(JctFileManagerImpl.java:333)
 129     at io.github.ascopes.jct.filemanagers.impl.JctFileManagerImpl.list(JctFileManagerImpl.java:49)
 130     at org.eclipse.jdt.internal.compiler.batch.ClasspathJsr199.getModulesDeclaringPackage(ClasspathJsr199.java:177)
 131     at org.eclipse.jdt.internal.compiler.batch.FileSystem.getModulesDeclaringPackage(FileSystem.java:637)
 132     at org.eclipse.jdt.internal.compiler.env.IModuleAwareNameEnvironment.isPackage(IModuleAwareNameEnvironment.java:104)
 133     at org.eclipse.jdt.internal.compiler.lookup.ModuleBinding.getVisiblePackage(ModuleBinding.java:617)
 134     at org.eclipse.jdt.internal.compiler.lookup.ModuleBinding.getVisiblePackage(ModuleBinding.java:665)
 135     at org.eclipse.jdt.internal.compiler.lookup.PackageBinding.findPackage(PackageBinding.java:142)
 136     at org.eclipse.jdt.internal.compiler.lookup.PackageBinding.getPackage(PackageBinding.java:157)
 137     at org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope.checkAndSetImports(CompilationUnitScope.java:221)
 138     at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment$CompleteTypeBindingsSteps.perform(LookupEnvironment.java:191)
 139     at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.completeTypeBindings(LookupEnvironment.java:565)
 140     at org.eclipse.jdt.internal.compiler.Compiler.internalBeginToCompile(Compiler.java:933)
 141     at org.eclipse.jdt.internal.compiler.Compiler.beginToCompile(Compiler.java:423)
 142     at org.eclipse.jdt.internal.compiler.Compiler.compile(Compiler.java:477)
 143     at org.eclipse.jdt.internal.compiler.Compiler.compile(Compiler.java:455)
 144     at org.eclipse.jdt.internal.compiler.batch.Main.performCompilation(Main.java:4689)
 145     at org.eclipse.jdt.internal.compiler.tool.EclipseCompilerImpl.call(EclipseCompilerImpl.java:98)
 146     at org.eclipse.jdt.internal.compiler.tool.EclipseCompiler$3.call(EclipseCompiler.java:197)
 147     at io.github.ascopes.jct.compilers.impl.JctCompilationFactoryImpl.createCompilation(JctCompilationFactoryImpl.java:115)
 148     at io.github.ascopes.jct.compilers.AbstractJctCompiler.performCompilation(AbstractJctCompiler.java:566)
 149     at io.github.ascopes.jct.compilers.AbstractJctCompiler.compile(AbstractJctCompiler.java:122)
 150     at io.github.ascopes.jct.integration.compilation.MultiTieredCompilationIntegrationTest.compileSourcesToClassesAndProvideThemInClassPathToSecondCompilationWithinJar(Multi>
 151     at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
 152     at java.base/java.lang.reflect.Method.invoke(Method.java:580)

This is the erroneous call site from ECJ.

The docs for JavaFileManager state that the call being made by ECJ is erroneous: https://docs.oracle.com/en/java/javase/11/docs/api/java.compiler/javax/tools/JavaFileManager.html#list(javax.tools.JavaFileManager.Location,java.lang.String,java.util.Set,boolean)

Throws IllegalArgumentException - if the location is a module-oriented location

From this, my understanding is that org.eclipse.jdt.internal.compiler.batch.ClasspathJsr199.getModulesDeclaringPackage(ClasspathJsr199.java:177) is calling JavaFileManager#list with illegal parameters, as MODULE_PATH is module-oriented.

Sounds like the implementation in ECJ needs to be updated to first call listLocationsForModules on MODULE_PATH, and call list on each Location that the call returns instead, or getLocationForModule if the module name is already known.


As a second note, this call is being made even when the source release is set to Java 8. It probably isn't important given Java 8 is now mostly considered antiquated, but it seems a little surprising that a Java 8 build is trying to resolve modules on the module path given that the expectation is that Java 8 would never support modules. Not sure if this is worth investigating or not as it shouldn't affect my use case.


It may be worth updating the internal file manager that ECJ uses by default when being invoked from the commandline to throw an exception in these cases so other edge cases like this can be spotted when working on this fix, as it would avoid further follow-up issues for the same thing potentially.

@ascopes
Copy link
Contributor Author

ascopes commented Dec 31, 2024

Additionally, I made the following observations just now:

[ERROR]   MultiTieredCompilationIntegrationTest.compileSourcesToClassesAndProvideThemInClassPathToSecondCompilationWithinJar:126 Expected a successful compilation, but it failed.
                                                                          
Diagnostics:

 - [ERROR] 268435846                                                                      
   The import org.example.first cannot be resolved                                        
 - [ERROR] 16777218                                                                       
   Adder cannot be resolved to a type
 - [ERROR] 16777218
   Adder cannot be resolved to a type

@ascopes ascopes changed the title [BUG]: Compilation failures with custom JavaFileManager [BUG]: ECJ passes invalid locations to JavaFileManager implementations Dec 31, 2024
@ascopes ascopes changed the title [BUG]: ECJ passes invalid locations to JavaFileManager implementations [BUG]: ECJ passes invalid StandardLocations to methods in custom JavaFileManager implementations Dec 31, 2024
@ascopes
Copy link
Contributor Author

ascopes commented Dec 31, 2024

Upon applying the following patch to the ClasspathJsr199.class in the ECJ JAR locally, I can get past the first issue:

153c153
<                       files = this.fileManager.list(this.location, qualifiedPackageName, this.fileTypes, false);
---
>                       files = list(qualifiedPackageName, this.fileTypes, false);
197c197
<                               for (JavaFileObject javaFileObject : this.fileManager.list(this.location, NO_PATH, Collections.singleton(JavaFileObject.Kind.SOURCE), false)) {
---
>                               for (JavaFileObject javaFileObject : list(NO_PATH, Collections.singleton(JavaFileObject.Kind.SOURCE), false)) {
204c204
<                               for (JavaFileObject javaFileObject : this.fileManager.list(this.location, NO_PATH, Collections.singleton(JavaFileObject.Kind.CLASS), false)) {
---
>                               for (JavaFileObject javaFileObject : list(NO_PATH, Collections.singleton(JavaFileObject.Kind.CLASS), false)) {
231c231
<                       Iterable<JavaFileObject> files = this.fileManager.list(this.location, qualifiedPackageName, this.fileTypes, false);
---
>                       Iterable<JavaFileObject> files = list(qualifiedPackageName, this.fileTypes, false);
239c239
<                               files = this.fileManager.list(this.location, qualifiedPackageName, this.fileTypes, true);
---
>                               files = list(qualifiedPackageName, this.fileTypes, true);
256c256
<                       for (JavaFileObject fileObject : this.fileManager.list(this.location, NO_PATH, this.fileTypes, true)) {
---
>                       for (JavaFileObject fileObject : list(NO_PATH, this.fileTypes, true)) {
361a362,389
>       }
> 
>       private Set<JavaFileObject> list(String qualifiedPackageName, Set<JavaFileObject.Kind> fileTypes, boolean recursive) throws IOException {
>         boolean hasModules = location.isModuleOrientedLocation();
>         boolean hasPackages = location.isOutputLocation() || !hasModules;
> 
>         HashSet<JavaFileObject> fileObjects = new HashSet<>();
> 
>         if (location.isOutputLocation() || !location.isModuleOrientedLocation()) {
>             for (JavaFileObject fileObject : this.fileManager.list(location, qualifiedPackageName, fileTypes, recursive)) {
>                 fileObjects.add(fileObject);
>             }
>         }
> 
>         // XXX(ascopes): Not sure if we can ever have both modules and packages in an output location together. For now, let's
>         // ignore that edge case and just assume we can't. This should avoid introducing any new bugs as part of GH-3496. If this
>         // becomes problematic, the empty check can be removed.
>         if (location.isModuleOrientedLocation() && fileObjects.isEmpty()) {
>             for (Set<JavaFileManager.Location> moduleLocationSet : this.fileManager.listLocationsForModules(location)) {
>                 for (JavaFileManager.Location moduleLocation : moduleLocationSet) {
>                     for (JavaFileObject fileObject : this.fileManager.list(moduleLocation, qualifiedPackageName, fileTypes, recursive)) {
>                         fileObjects.add(fileObject);
>                     }
>                 }
>             }            
>         }
> 
>         return fileObjects;

I believe this should be the expected behaviour.

However, a new bug is hidden behind this, which appears to be that ECJ is passing class names to APIs that only expect package names.

image

The list API does not appear to be documented in a way that suggests passing a class name referencing a specific file to the list call is valid behaviour, since it is documented to take a packageName rather than a binaryName, so this may be a second bug. Seems to be caused by the fact PackageBinding.java performs this call, where char[] name is now set to a class name:

  protected PackageBinding findPackage(char[] name, ModuleBinding module) {
    return module.getVisiblePackage(CharOperation.arrayConcat(this.compoundName, name));
  }

image

Edit: checked JLS, that confirms this hypothesis that package names cannot be substituted with fully qualified class names as there is no literature I can find that explicitly specifies packages and binary names are interchangable. @stephan-herrmann should I raise a new bug for that separately?

@jukzi jukzi added the needinfo Further information is requested label Jan 2, 2025
@jukzi
Copy link
Contributor

jukzi commented Jan 2, 2025

See #3496 (comment) for description.

Please create self containing error description. It's not clear from this issue alone what the problem is, how to reproduce. And how io.github.ascopes.jct.filemanagers. comes into play.

@ascopes
Copy link
Contributor Author

ascopes commented Jan 2, 2025

Please scroll down the thread to #3496 (comment) and consecutive comments, along with a suggested patch, and the reason for this, along with links to the JSR-199 documentation suggesting this behaviour.

As stated in the previous thread, the mentioned package holds a conformant in-memory JavaFileManager implementation that is compatible with Javac. This is part of my library that provides developers of compiler extensions, annotation processors, and code generation tools with an API to produce fluent integration tests that calls JSR-199 compliant compilers with a virtual file system of sources, and assertj support for interrogating diagnostics and outputs.

The issue has been raised as I have encountered several bugs around how ECJ interacts with such resources, which was where the original thread came from.

In this case, the issue is around the fact ClasspathJsr199 passes invalid module-oriented Locations to JavaFileManager#list.

I can provide instructions for reproducing the issue via my library, but a "minimal" reproduction will not be possible since that involves implementing JavaFileManager from scratch with JPMS support, and that is far from a minimal API to satisfy.

At the simplest:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needinfo Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants