Open youribonnaffe opened 1 year ago
I'll see what I can do. But, please provide a reproducible case.
Youri co-worker here. I don't have a reproducer yet but I have been able to use a debugger to get a glimpse of what is going on.
We use @RecordInterface to define a record from an interface
@RecordInterface
interface Conf {
int maxConcurrency( );
}
RecordBuilder generate the following class
@Generated("io.soabase.recordbuilder.core.RecordInterface")
@RecordBuilder
public record ConfRecord(int maxConcurrency) implements Conf, ConfRecordBuilder.With {}
I'm able to observe that the TypeElement argument passed to buildRecordComponents correspond to ConfRecord. I also observe that maxConcurrency's accessor is null.
e.getRecordComponents()
returns two entries. Both are named maxConcurrency
. The first one has an accessor, the second one doesn't. At this point, it is unclear to me if this is what is expected from getRecordComponents
. I think not, but have to check that.
I see three options:
I just checked com.sun.tools.javac.code.Symbol.ClassSymbol#getRecordComponent(com.sun.tools.javac.tree.JCTree.JCVariableDecl, boolean, com.sun.tools.javac.util.List
public RecordComponent getRecordComponent(JCVariableDecl var, boolean addIfMissing, List<JCAnnotation> annotations) {
RecordComponent toRemove = null;
for (RecordComponent rc : recordComponents) {
/* it could be that a record erroneously declares two record components with the same name, in that
* case we need to use the position to disambiguate
*/
if (rc.name == var.name && var.pos == rc.pos) {
if (rc.type.hasTag(TypeTag.ERROR) && !var.sym.type.hasTag(TypeTag.ERROR)) {
// Found a record component with an erroneous type: save it so that it can be removed later.
// If the class type of the record component is generated by annotation processor, it should
// use the new actual class type and symbol instead of the old dummy ErrorType.
toRemove = rc;
} else {
// Found a good record component: just return.
return rc;
}
}
}
RecordComponent rc = null;
if (toRemove != null) {
// Found a record component with an erroneous type: remove it and create a new one
recordComponents = List.filter(recordComponents, toRemove);
recordComponents = recordComponents.append(rc = new RecordComponent(var.sym, annotations));
} else if (addIfMissing) {
// Didn't find the record component: create one.
recordComponents = recordComponents.append(rc = new RecordComponent(var.sym, annotations));
}
return rc;
}
It seems to avoid registering duplicates unless (name, pos) don't match. I observe that the entry related to the record has pos = -1 but the entry related to the interface has pos = 387.
It is also unclear why it work sometime and sometimes it doesn't. I figured out that if I edit an unrelated file in IntelliJ then run a Maven build it triggers the issue but I'm unsure why. I was able to check that the md5sum of the record class doesn't change. Perhaps maven / javac is able to not run the annotation processor if nothing changed on disk?
@Randgalt Here is a reproducer https://github.com/cykl/record-builder-139-reproducer
Thanks for this. I'll get to it soon.
@cykl I'm unable to reproduce the problem. Can you give more details on your environment? I tried with java 17 and 19 and the example project you provided. I don't get any errors.
Thanks for giving it a try. We deterministically get an error using Java 17, Maven 3.8 on Linux (Fedora / Ubuntu) & macOS.
Here is the output on my laptop:
$ java -version
openjdk version "17.0.5" 2022-10-18
OpenJDK Runtime Environment (Red_Hat-17.0.5.0.8-3.fc36) (build 17.0.5+8)
OpenJDK 64-Bit Server VM (Red_Hat-17.0.5.0.8-3.fc36) (build 17.0.5+8, mixed mode, sharing)
$ mvn --version
Apache Maven 3.8.4 (Red Hat 3.8.4-3)
Maven home: /usr/share/maven
Java version: 17.0.5, vendor: Red Hat, Inc., runtime: /usr/lib/jvm/java-17-openjdk-17.0.5.0.8-3.fc36.x86_64
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "6.1.6-100.fc36.x86_64", arch: "amd64", family: "unix"
$ ./trigger.sh
+ git checkout .
Updated 1 path from the index
+ mvn clean compile
[INFO] Scanning for projects...
[INFO]
[INFO] -------------< org.example:record-builder-139-reproducer >--------------
[INFO] Building record-builder-139-reproducer 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ record-builder-139-reproducer ---
[INFO] Deleting /home/cmathieu/sources/record-builder-139-reproducer/target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ record-builder-139-reproducer ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/cmathieu/sources/record-builder-139-reproducer/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.10.1:compile (default-compile) @ record-builder-139-reproducer ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to /home/cmathieu/sources/record-builder-139-reproducer/target/classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.308 s
[INFO] Finished at: 2023-02-18T09:36:52+01:00
[INFO] ------------------------------------------------------------------------
+ sed -i s/10/11/ src/main/java/Main.java
+ rm target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
+ mvn compile
[INFO] Scanning for projects...
[INFO]
[INFO] -------------< org.example:record-builder-139-reproducer >--------------
[INFO] Building record-builder-139-reproducer 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ record-builder-139-reproducer ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/cmathieu/sources/record-builder-139-reproducer/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.10.1:compile (default-compile) @ record-builder-139-reproducer ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to /home/cmathieu/sources/record-builder-139-reproducer/target/classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.903 s
[INFO] Finished at: 2023-02-18T09:36:54+01:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.10.1:compile (default-compile) on project record-builder-139-reproducer: Fatal error compiling: java.lang.NullPointerException: Cannot invoke "javax.lang.model.element.ExecutableElement.getAnnotationMirrors()" because the return value of "javax.lang.model.element.RecordComponentElement.getAccessor()" is null -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException
I just pushed branch jordanz/check-null-accessor
- it does a null check. Can you test with this?
Using this branch, the execution of trigger.sh fails with:
[INFO] --- maven-compiler-plugin:3.10.1:compile (default-compile) @ record-builder-139-reproducer ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to ./record-builder-139-reproducer/target/classes
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] ./record-builder-139-reproducer/target/generated-sources/annotations/pkg1/ConfRecordBuilder.java:[15,17] variable maxConcurrency is already defined in class pkg1.ConfRecordBuilder
[ERROR] ./record-builder-139-reproducer/target/generated-sources/annotations/pkg1/ConfRecordBuilder.java:[22,55] variable maxConcurrency is already defined in constructor ConfRecordBuilder(int,int)
[ERROR] ./record-builder-139-reproducer/target/generated-sources/annotations/pkg1/ConfRecordBuilder.java:[31,65] variable maxConcurrency is already defined in method ConfRecord(int,int)
[ERROR] ./record-builder-139-reproducer/target/generated-sources/annotations/pkg1/ConfRecordBuilder.java:[117,30] method maxConcurrency(int) is already defined in class pkg1.ConfRecordBuilder
[ERROR] ./record-builder-139-reproducer/target/generated-sources/annotations/pkg1/ConfRecordBuilder.java:[126,16] method maxConcurrency() is already defined in class pkg1.ConfRecordBuilder
[ERROR] ./record-builder-139-reproducer/target/generated-sources/annotations/pkg1/ConfRecordBuilder.java:[145,13] method maxConcurrency() is already defined in interface pkg1.ConfRecordBuilder.With
[ERROR] ./record-builder-139-reproducer/target/generated-sources/annotations/pkg1/ConfRecordBuilder.java:[177,28] method withMaxConcurrency(int) is already defined in interface pkg1.ConfRecordBuilder.With
[ERROR] ./record-builder-139-reproducer/target/generated-sources/annotations/pkg1/ConfRecordBuilder.java:[199,20] method maxConcurrency() is already defined in class pkg1.ConfRecordBuilder._FromWith
[INFO] 8 errors
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
Thanks for the update @youribonnaffe - I'll spend more time on this when I can and see if I can get to the bottom of it.
Some potentially helpful news: it turns out this reproduces in IntelliJ sometimes. If I open RecordBuilder fresh in IntelliJ and run I see the same error. So, at least I have a semi-reproducible case.
I can now reliably reproduce this in IntelliJ by single-compiling any interface annotated with @RecordInterface
. i.e. in IntelliJ, open BeanStyle.java
and select Recompile 'BeanStyle.java'
from the Build menu.
@youribonnaffe and @cykl As I mention above, I can now reliably reproduce the issue in IntelliJ. The problem seems to be subsequent rounds of annotation processing. javac is supposed to process the generated record from @RecordInterface
but there seems to be bugs with this. As an experiment, I have a branch that has the @RecordInterface
handler write the record and the record builder at the same time. The record does not get the @RecordBuilder
annotation and, so, javac won't try to process it on the next round. This appears to fix the issue, however I have seen some other javac bugs.
Can you please test with this branch and let me know your results?
jordanz/test-manually-build-interface-record-builder
Thanks for taking another look at it.
I tried with the reproducer and the branch jordanz/test-manually-build-interface-record-builder
, the trigger.sh script fails with:
An exception has occurred in the compiler (17.0.6). Please file a bug against the Java compiler via the Java bug reporting page (https://bugreport.java.com) after checking the Bug Database (https://bugs.java.com) for duplicates. Include your program, the following diagnostic, and the parameters passed to the Java compiler in your report. Thank you.
java.lang.NullPointerException: Cannot invoke "com.sun.tools.javac.code.Symbol$MethodSymbol.flags()" because "rc.accessor" is null
at jdk.compiler/com.sun.tools.javac.comp.Lower.lambda$generateMandatedAccessors$4(Lower.java:2255)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:178)
at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)
at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1845)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682)
at jdk.compiler/com.sun.tools.javac.comp.Lower.generateMandatedAccessors(Lower.java:2262)
at jdk.compiler/com.sun.tools.javac.comp.Lower.visitRecordDef(Lower.java:2459)
at jdk.compiler/com.sun.tools.javac.comp.Lower.visitClassDef(Lower.java:2180)
at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCClassDecl.accept(JCTree.java:819)
at jdk.compiler/com.sun.tools.javac.tree.TreeTranslator.translate(TreeTranslator.java:58)
at jdk.compiler/com.sun.tools.javac.comp.Lower.translate(Lower.java:2072)
at jdk.compiler/com.sun.tools.javac.comp.Lower.translate(Lower.java:2091)
at jdk.compiler/com.sun.tools.javac.comp.Lower.translateTopLevelClass(Lower.java:4138)
at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.desugar(JavaCompiler.java:1561)
at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.desugar(JavaCompiler.java:1408)
at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:946)
at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.lambda$doCall$0(JavacTaskImpl.java:104)
at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.invocationHelper(JavacTaskImpl.java:152)
at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.doCall(JavacTaskImpl.java:100)
at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.call(JavacTaskImpl.java:94)
at org.codehaus.plexus.compiler.javac.JavaxToolsCompiler.compileInProcess(JavaxToolsCompiler.java:136)
at org.codehaus.plexus.compiler.javac.JavacCompiler.performCompile(JavacCompiler.java:182)
at org.apache.maven.plugin.compiler.AbstractCompilerMojo.execute(AbstractCompilerMojo.java:1209)
at org.apache.maven.plugin.compiler.CompilerMojo.execute(CompilerMojo.java:198)
at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:137)
at org.apache.maven.lifecycle.internal.MojoExecutor.doExecute2(MojoExecutor.java:370)
at org.apache.maven.lifecycle.internal.MojoExecutor.doExecute(MojoExecutor.java:351)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:215)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:171)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:163)
at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:117)
at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:81)
at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build(SingleThreadedBuilder.java:56)
at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:128)
at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:298)
at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:192)
at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:105)
at org.apache.maven.cli.MavenCli.execute(MavenCli.java:960)
at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:293)
at org.apache.maven.cli.MavenCli.main(MavenCli.java:196)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:282)
at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:225)
at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:406)
at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:347)
I also tried with jordanz/delete-vestigal-class-file-for-record-interface but this one fails with the original error:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.10.1:compile (default-compile) on project record-builder-139-reproducer: Fatal error compiling: java.lang.NullPointerException: Cannot invoke "javax.lang.model.element.ExecutableElement.getAnnotationMirrors()" because the return value of "javax.lang.model.element.RecordComponentElement.getAccessor()" is null
Man - it seems javac is hopeless broken with what record-builder is doing here. :( I'm not sure how to handle this.
@youribonnaffe out of curiosity. If you change https://github.com/cykl/record-builder-139-reproducer/blob/main/trigger.sh to do mvn clean compile
with the released record-builder does it still fail?
@Randgalt sorry it seems I missed the last message.
If you change https://github.com/cykl/record-builder-139-reproducer/blob/main/trigger.sh to do mvn clean compile with the released record-builder does it still fail?
You mean changing the second mvn
command to clean compile
right? In that case it does not fail. But that would be expected as right now cleaning all the generated code fixes the issue.
Workaround for Intellj Idea (2022.3):
On Run/Debug configuration, click "Edit Configuration..." -> Modify options -> Do not build before run
Remember to compile project before running test
I realize you've probably moved away from RecordBuilder at this point, but I may have a fix for this issue: https://github.com/Randgalt/record-builder/pull/172
Thanks @Randgalt! We haven't moved away but stopped using RecordInterface until it gets resolved. I will try the fix one of those days.
Thanks @Randgalt! We haven't moved away but stopped using RecordInterface until it gets resolved. I will try the fix one of those days.
I'd really appreciate it if you can test with this fix. My solution (I don't know why I didn't think of it before) is to manually find the accessor method instead of use getAccessor()
.
Update: it solves one problem but causes others it seems. javac just can't seem to handle generated records for some reason. I'll keep seeing what I can do
@cykl I think I really have it now. If you can, please test with https://github.com/Randgalt/record-builder/pull/174 - in my local testing it solves the problem.
I gave the reproducer a shot with #174 and unfortunately I still see the same error.
Damn - thanks. I posted a bug report with Oracle. Let's see what comes of it.
@youribonnaffe FYI - I created a container and am able to reproduce using the tester provided in the container (but still not in MacOS). There is a workaround until the bug is fixed in javac. Cleaning the generated sources first fixes the problem though obviously not ideal. Here's a the Maven config for it:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>3.3.2</version>
<executions>
<execution>
<id>clean-generated</id>
<phase>validate</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
<configuration>
<excludeDefaultDirectories>true</excludeDefaultDirectories>
<filesets>
<fileset>
<directory>${project.build.directory}/generated-sources/annotations</directory>
<includes>
<include>**/*</include>
</includes>
</fileset>
</filesets>
</configuration>
</plugin>
We use record-builder on a project and we often get compilation errors with the following message:
Cannot invoke "javax.lang.model.element.ExecutableElement.getAnnotationMirrors()" because the return value of "javax.lang.model.element.RecordComponentElement.getAccessor()" is null
The errors happen either in Intellij IDEA or via the CLI with Maven. Running a re-build in Intellij or running the command again fixes the issue (usually a
mvn verify
).I suspect it could come from the two tools using a different JDK or compilation options and each generating slightly different bytecode. However I've made sure that the same JDK (17.0.6) is used in both cases.
I've managed to capture a stack trace below. I'm not confident about being able to provide a reproducer project but if the error message is not helpful in understanding the problem I can give it a shot.