Java 9 Platform Class Loader support
chang-huang opened this issue · 5 comments
Hi Thomas,
I'm wondering if you've seen ClassNotFoundExceptions with the plugin loading classes that are loaded by the platform class loader in Java 9?
In SelfFirstClassLoader
class of ErrorProneCompiler
, it initializes the URLClassLoader
with a null parent, loading the bootstrap class loader.
In Java 9, the bootstrap loader is more restricted. A set of modules/packages that was previously visible in the bootstrap loader is no longer visible - see http://openjdk.java.net/jeps/261. Usages of classes such as those in java.sql.*
would result in a ClassNotFoundException
when using the gradle errorprone plugin to compile.
A solution to this is to replace the bootstrap with the platform loader. Call the super class with the platform loader as parent, and instead of calling the bootstrap loadClass as the fallback, call the parent's loadClass.
However this does not appear to be backwards compatible, as the platform class loader functions must be compiled using Java 9, which then will not run on 1.8.
Have you considered this issue? I haven't found anything in the closed issues. I tested the above solution on my code and it seem to work - but not backwards compatible.
Thanks.
I'm not sure I understand the problem (fwiw, I'm defiant to JPMS, but I haven't had any problem compiling code that uses java.sql.*
with JDK 9).
Can you provide a small project reproducing the issue?
Btw, if you use Gradle 4.6 and Java 9 (or Java 10), you may want to try out the net.ltgt.errorprone-javacplugin
plugin: https://github.com/tbroyer/gradle-errorprone-javacplugin-plugin/
Ok. My apologies. By "compile" I actually meant the annotation processor portion of the compile. That was the setup that lead me to this issue. Errorprone compile doesn't look for these things nor actually run the code, so there wouldn't be issues compiling code that has references classes loaded by the platform loader. Compilers would actually run annotation processors to process the code (being compiled), and that's when the problem arises when the processors don't have access to jdk libs during runtime.
I was able to reproduce this issue by creating a simple annotation processor that references (in the init method is enough) any class loaded by the platform loader, and putting it in the classpath (Java automatically picks it up). I don't have to annotate to use the processor. Upon the init trigger, it would fail and throw the stack trace.
This happens with gradle 4.6 as well as I tried it on gradle 4.6, but the project we are working on have not upgraded to 4.6 yet.
My annotation processor:
test-annotation-proc/src/main/java/MyProc.java
package main.java;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import java.util.HashSet;
import java.util.Set;
import java.sql.Date;
public class MyProc extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment env){
Date d = new Date(2020,1,1);
}
@Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) {
return true;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> s = new HashSet<>();
return new HashSet<>();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
This file tells java to load as annotation processor:
test-annotation-proc/src/main/resources/META-INF/services/javax.annotation.processing.Processor
main.java.MyProc
test-annotation-proc/build.gradle
apply plugin: 'java'
My test project:
test-gradle-errorprone-plugin/src/main/java/Test.java
public class Test {
public static void main(String args[]) {
}
}
test-gradle-errorprone-plugin/build.gradle
buildscript {
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "net.ltgt.gradle:gradle-errorprone-plugin:0.0.13"
}
}
apply plugin: "net.ltgt.errorprone"
apply plugin: 'java'
repositories {
mavenCentral()
}
dependencies {
compile files('../../gradle/test-annotation-proc/build/libs/test-annotation-proc.jar')
}
gradle assemble the test project and you'll hit the ClassNotFoundException.
Thanks.
I have attached the zip files for the above src code. Unzip them to same dir, run gradle assemble on the test-annotation-proc first, then gradle assemble on the test-gradle-errorprone-plugin will give you the CNFE.
test-gradle-errorprone-plugin.zip
test-annotation-proc.zip
Sorry for the delay. I couldn't create an integration test for the issue (and still haven't managed to do so). I verified my change (following your instructions, but using reflection to keep the Java 8 compatibility) using Gradle's --include-build
feature with your test projects. I've published version 0.0.14 with the fix.
Note that for Java 9+ you might want to use the net.ltgt.errorprone-javacplugin
instead, particularly as net.ltgt.errorprone
does not support Java 10+. Unfortunately, configuration of Error Prone options is quite different between those plugins…
Great. Thanks @tbroyer! Appreciate the fix! Thanks for heads up on javacplugin, we'll look into this when upgrading to java 10 in a few months.