diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/AnnotationProcessingCompileTask.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/AnnotationProcessingCompileTask.java index bb4438833e30..acd5a435732a 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/AnnotationProcessingCompileTask.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/AnnotationProcessingCompileTask.java @@ -26,7 +26,6 @@ import org.gradle.api.internal.tasks.compile.processing.NonIncrementalProcessor; import org.gradle.api.internal.tasks.compile.processing.SupportedOptionsCollectingProcessor; import org.gradle.api.internal.tasks.compile.processing.TimeTrackingProcessor; -import org.gradle.internal.classloader.FilteringClassLoader; import org.gradle.internal.classpath.DefaultClassPath; import org.gradle.internal.concurrent.CompositeStoppable; @@ -39,6 +38,8 @@ import java.util.Locale; import java.util.Set; +import static org.gradle.api.internal.tasks.compile.filter.AnnotationProcessorFilter.*; + /** * Wraps another {@link JavaCompiler.CompilationTask} and sets up its annotation processors * according to the provided processor declarations and processor path. Incremental processors @@ -119,23 +120,10 @@ private void setupProcessors() { private URLClassLoader createProcessorClassLoader() { return new URLClassLoader( DefaultClassPath.of(annotationProcessorPath).getAsURLArray(), - new FilteringClassLoader(delegate.getClass().getClassLoader(), getExtraAllowedPackages()) + getFilteredClassLoader(delegate.getClass().getClassLoader()) ); } - /** - * Many popular annotation processors like lombok need access to compiler internals - * to do their magic, e.g. to inspect or even change method bodies. This is not valid - * according to the annotation processing spec, but forbidding it would upset a lot of - * our users. - */ - private FilteringClassLoader.Spec getExtraAllowedPackages() { - FilteringClassLoader.Spec spec = new FilteringClassLoader.Spec(); - spec.allowPackage("com.sun.tools.javac"); - spec.allowPackage("com.sun.source"); - return spec; - } - private Class loadProcessor(AnnotationProcessorDeclaration declaredProcessor) { try { return processorClassloader.loadClass(declaredProcessor.getClassName()); diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/JdkJavaCompiler.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/JdkJavaCompiler.java index 0229dfe20a39..02a49f5817b1 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/JdkJavaCompiler.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/JdkJavaCompiler.java @@ -17,6 +17,7 @@ import org.gradle.api.JavaVersion; import org.gradle.api.internal.tasks.compile.processing.AnnotationProcessorDeclaration; +import org.gradle.api.internal.tasks.compile.reflect.FilteringClassLoaderInjectingProxy; import org.gradle.api.internal.tasks.compile.reflect.SourcepathIgnoringProxy; import org.gradle.api.tasks.WorkResult; import org.gradle.internal.Factory; @@ -66,6 +67,7 @@ private JavaCompiler.CompilationTask createCompileTask(JavaCompileSpec spec, Jdk if (JavaVersion.current().isJava9Compatible() && emptySourcepathIn(options)) { fileManager = (StandardJavaFileManager) SourcepathIgnoringProxy.proxy(standardFileManager, StandardJavaFileManager.class); } + fileManager = (StandardJavaFileManager) FilteringClassLoaderInjectingProxy.proxy(fileManager, StandardJavaFileManager.class); JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, options, spec.getClasses(), compilationUnits); Set annotationProcessors = spec.getEffectiveAnnotationProcessors(); diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/filter/AnnotationProcessorFilter.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/filter/AnnotationProcessorFilter.java new file mode 100644 index 000000000000..77b18b9dce92 --- /dev/null +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/filter/AnnotationProcessorFilter.java @@ -0,0 +1,38 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.internal.tasks.compile.filter; + +import org.gradle.internal.classloader.FilteringClassLoader; + +public class AnnotationProcessorFilter { + public static FilteringClassLoader getFilteredClassLoader(ClassLoader parent) { + return new FilteringClassLoader(parent, getExtraAllowedPackages()); + } + + /** + * Many popular annotation processors like lombok need access to compiler internals + * to do their magic, e.g. to inspect or even change method bodies. This is not valid + * according to the annotation processing spec, but forbidding it would upset a lot of + * our users. + */ + private static FilteringClassLoader.Spec getExtraAllowedPackages() { + FilteringClassLoader.Spec spec = new FilteringClassLoader.Spec(); + spec.allowPackage("com.sun.tools.javac"); + spec.allowPackage("com.sun.source"); + return spec; + } +} diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/reflect/FilteringClassLoaderInjectingInvocationHandler.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/reflect/FilteringClassLoaderInjectingInvocationHandler.java new file mode 100644 index 000000000000..9252c0494154 --- /dev/null +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/reflect/FilteringClassLoaderInjectingInvocationHandler.java @@ -0,0 +1,52 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.internal.tasks.compile.reflect; + +import javax.tools.StandardLocation; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.net.URLClassLoader; + +import static org.gradle.api.internal.tasks.compile.filter.AnnotationProcessorFilter.getFilteredClassLoader; + +/** + * This class injects a filtering classloader when the compiler uses the standard java annotation processor path. + * This prevents Gradle classes or external libraries from being visible on the annotation processor path. + */ +public class FilteringClassLoaderInjectingInvocationHandler implements InvocationHandler { + public static final String GET_CLASS_LOADER = "getClassLoader"; + private final Object proxied; + + public FilteringClassLoaderInjectingInvocationHandler(Object proxied) { + this.proxied = proxied; + } + + @Override + @SuppressWarnings("unchecked") + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (method.getName().equals(GET_CLASS_LOADER) && args[0] == StandardLocation.ANNOTATION_PROCESSOR_PATH) { + ClassLoader classLoader = (ClassLoader) method.invoke(proxied, args); + if (classLoader instanceof URLClassLoader) { + return new URLClassLoader(((URLClassLoader) classLoader).getURLs(), getFilteredClassLoader(classLoader.getParent())); + } else { + return classLoader; + } + } + return method.invoke(proxied, args); + } +} + diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/reflect/FilteringClassLoaderInjectingProxy.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/reflect/FilteringClassLoaderInjectingProxy.java new file mode 100644 index 000000000000..684a815452b9 --- /dev/null +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/reflect/FilteringClassLoaderInjectingProxy.java @@ -0,0 +1,29 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.internal.tasks.compile.reflect; + +import java.lang.reflect.Proxy; + +public class FilteringClassLoaderInjectingProxy { + @SuppressWarnings("rawtypes") + public static Object proxy(Object proxied, Class targetInterface) { + return Proxy.newProxyInstance( + FilteringClassLoaderInjectingInvocationHandler.class.getClassLoader(), + new Class[] {targetInterface}, + new FilteringClassLoaderInjectingInvocationHandler(proxied)); + } +} diff --git a/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/AbstractSmokeTest.groovy b/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/AbstractSmokeTest.groovy index 6390588f35e0..82927302ae1f 100644 --- a/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/AbstractSmokeTest.groovy +++ b/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/AbstractSmokeTest.groovy @@ -102,6 +102,9 @@ abstract class AbstractSmokeTest extends Specification { // https://plugins.gradle.org/plugin/org.gradle.playframework static playframework = "0.6" + + // https://plugins.gradle.org/plugin/net.ltgt.errorprone + static errorProne = "0.8.1" } static class Versions implements Iterable { diff --git a/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/ThirdPartyPluginsSmokeTest.groovy b/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/ThirdPartyPluginsSmokeTest.groovy index b1e796d19baa..ab7e0e1b4ce7 100644 --- a/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/ThirdPartyPluginsSmokeTest.groovy +++ b/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/ThirdPartyPluginsSmokeTest.groovy @@ -384,4 +384,48 @@ class ThirdPartyPluginsSmokeTest extends AbstractSmokeTest { file('build/reports/spotbugs').isDirectory() } + @Issue("https://github.com/gradle/gradle/issues/9897") + def 'errorprone plugin'() { + given: + buildFile << """ + plugins { + id('java') + id("net.ltgt.errorprone") version "${TestedVersions.errorProne}" + } + + ${mavenCentralRepository()} + + if (JavaVersion.current().java8) { + dependencies { + errorproneJavac("com.google.errorprone:javac:9+181-r4173-1") + } + } + + dependencies { + errorprone("com.google.errorprone:error_prone_core:2.3.3") + } + + tasks.withType(JavaCompile).configureEach { + options.fork = true + options.errorprone { + check("DoubleBraceInitialization", net.ltgt.gradle.errorprone.CheckSeverity.ERROR) + } + } + """ + file("src/main/java/Test.java") << """ + import java.util.HashSet; + import java.util.Set; + + public class Test { + + public static void main(String[] args) { + } + + } + """ + + expect: + runner('compileJava').forwardOutput().build() + } + }