2

Recently I've observed a (at least for me) strange behavior of the Java Compiler "ToolProvider.getSystemJavaCompiler()".

  • If I try to compile a not-compilable java file in a "bare" maven project, I can obtain the errors as expected.

  • If I add certain dependencies (I've first observed this when adding log4j), the compiler does not provide any information regarding compiler errors anymore.

To demonstrate this behavior, I've created an example repository for this: https://github.com/dfuchss/JavaCompilerIsStrange

In this repository I've added a simple main method that tries to parse the AST of an invalid Java File. The main method throws an exception if the diagnostics object contains no errors. This main method will be invoked by a single test. In my pom.xml I've created a profile "strange" that simply adds a dependency to the project (that is not used but obviously will be added to the classpath after activating the profile). For this example it's the "metainf-services" dependency. In the run.sh file, I simply execute mvn test twice. First without the profile activated and after that with the activated profile.

If you run the script you get a successful test (because the invalid syntax was detected) and an failed test (because the invalid syntax was not detected after adding the dependency)

## Build without activated profile
[INFO] Scanning for projects...
[....]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running SimpleExecTest
src/main/resources/Example.java:4: error: ';' expected
                System.out.println("Hello World!")
                                                  ^
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.19 s - in SimpleExecTest
[INFO] 
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  6.832 s
[INFO] Finished at: 2022-06-24T00:57:46+02:00
[INFO] ------------------------------------------------------------------------

## Build with activated profile
[INFO] Scanning for projects...
[....]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running SimpleExecTest
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.203 s <<< FAILURE! - in SimpleExecTest
[ERROR] SimpleExecTest.testMain  Time elapsed: 0.171 s  <<< ERROR!
java.lang.Error: Shall not be possible to compile.
        at org.fuchss.Main.main(Main.java:46)
        at SimpleExecTest.testMain(SimpleExecTest.java:7)
[....]
[INFO]
[INFO] Results:
[INFO]
[ERROR] Errors: 
[ERROR]   SimpleExecTest.testMain:7 »  Shall not be possible to compile.
[INFO]
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  6.323 s
[INFO] Finished at: 2022-06-24T00:57:54+02:00
[INFO] ------------------------------------------------------------------------
[....]

Does anyone has an idea how to resolve this behavior?

EDIT: mvn dependency:tree does not show any further dependency (compile) for "metainf-services"

[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ strange ---
[INFO] org.fuchss:strange:jar:1.0-SNAPSHOT
[INFO] +- org.junit.jupiter:junit-jupiter-engine:jar:5.8.2:test
[INFO] |  +- org.junit.platform:junit-platform-engine:jar:1.8.2:test
[INFO] |  |  +- org.opentest4j:opentest4j:jar:1.2.0:test
[INFO] |  |  \- org.junit.platform:junit-platform-commons:jar:1.8.2:test
[INFO] |  +- org.junit.jupiter:junit-jupiter-api:jar:5.8.2:test
[INFO] |  \- org.apiguardian:apiguardian-api:jar:1.1.2:test
[INFO] \- org.kohsuke.metainf-services:metainf-services:jar:1.9:compile

Edit II: I've added the output mvn -X clean verify without and with activated profile to https://github.com/dfuchss/JavaCompilerIsStrange/blob/main/result.txt

dfuchss
  • 43
  • 6
  • I imagine that metainf-services has many transitive dependencies. First you should figure out a single dependency which produces this behaviour. – tgdavies Jun 23 '22 at 23:16
  • 1
    Thanks for your fast response @tgdavies That's why I've used metainf and not log4j for my example: `mvn dependency:tree` shows no further compile time dependencies – dfuchss Jun 23 '22 at 23:20
  • I wonder whether that being an annotation processor complicates things? – tgdavies Jun 23 '22 at 23:34
  • I'd be most interested to see what differentiates the builds when you add the debug flag -X as in, `mvn -X clean verify`, then comparing the output between the test steps – Byron Lagrone Jun 24 '22 at 15:09
  • @ByronLagrone I've added the debug output to https://github.com/dfuchss/JavaCompilerIsStrange/blob/main/result.txt – dfuchss Jun 24 '22 at 18:39
  • As noted in the answer, the diagnostics seem to be deferred when an annotation processor is loaded. And note you only call `parse()`. If you add the next step and call `analyze()`, the compiler error will be reported in both cases. My guess as to why this is the case, is that annotation processors are capable of emitting their own compiler warnings/errors, and are capable of generating source code which also needs to be compiled. If this all doesn't occur during `parse()` (not sure when it _does_ happen), then it would make sense to defer reporting errors. – Slaw Jun 25 '22 at 02:59

1 Answers1

3

You can "fix" this problem by disabling annotation processing:

final JavaCompiler.CompilationTask task = javac.getTask(
     null, fileManager, listener, List.of("-proc:none"), null, javaFiles
);

This looks like a bug in the JDK to me: when annotation processing is meant to happen (which doesn't seem to require an explicit annotation processor being used, hence this problem occurs with dependencies like log4j) the error reporting is wrapped in a DeferredDiagnosticHandler. I think the intention is that after the processing is done, reportDeferredDiagnostics() will be called, which will transfer the diagnostics to the original handler, but for some reason this isn't happening.

A bit more time with the debugger would answer this properly.

tgdavies
  • 10,307
  • 4
  • 35
  • 40
  • 1
    Note that the OP's code only calls `JavacTask#parse()`. That's only the first step in the compilation process. If you add a call to `JavacTask#analyze()` then the compiler error is reported in both cases. I don't know at which step annotation processors run, but it's possible the diagnostics are deferred because annotation processors are capable of emitting their own compiler warnings/errors, and are capable of generating source code which also needs to be compiled. – Slaw Jun 25 '22 at 02:58
  • @ tgdavies @ Slaw thanks a lot for your suggestions :) I'll test them, report here, and mark the answer if all works in my use case – dfuchss Jun 25 '22 at 09:09
  • @ tgdavies @ Slaw thanks a lot for your suggestions :) Both options resolve the issue in my small test code! – dfuchss Jun 25 '22 at 09:17
  • The bug is now listed in the JDK Bug Tracking System: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8289491 – dfuchss Jun 30 '22 at 21:00