tbroyer/gradle-errorprone-plugin-v0.0.x

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.