From d533875e71b0a44efef217a97efbec61133c8f2f Mon Sep 17 00:00:00 2001 From: Sterling Greene Date: Tue, 9 Jul 2019 17:43:39 -0400 Subject: [PATCH] Replace reflection of JavaFileManager with ForwardingJavaFileManager --- .../tasks/compile/JdkJavaCompiler.java | 15 ++- .../ResourceCleaningCompilationTask.java | 6 +- ...ClassLoaderInjectingInvocationHandler.java | 52 ---------- .../FilteringClassLoaderInjectingProxy.java | 29 ------ .../GradleStandardJavaFileManager.java | 95 +++++++++++++++++++ .../SourcepathIgnoringInvocationHandler.java | 66 ------------- .../reflect/SourcepathIgnoringProxy.java | 32 ------- 7 files changed, 105 insertions(+), 190 deletions(-) delete mode 100644 subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/reflect/FilteringClassLoaderInjectingInvocationHandler.java delete mode 100644 subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/reflect/FilteringClassLoaderInjectingProxy.java create mode 100644 subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/reflect/GradleStandardJavaFileManager.java delete mode 100644 subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/reflect/SourcepathIgnoringInvocationHandler.java delete mode 100644 subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/reflect/SourcepathIgnoringProxy.java 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 02a49f5817b1..14abc95b8aa7 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,16 +17,17 @@ 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.internal.tasks.compile.reflect.GradleStandardJavaFileManager; import org.gradle.api.tasks.WorkResult; import org.gradle.internal.Factory; +import org.gradle.internal.classpath.DefaultClassPath; import org.gradle.language.base.internal.compile.Compiler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.tools.JavaCompiler; +import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import java.io.Serializable; @@ -61,13 +62,11 @@ private JavaCompiler.CompilationTask createCompileTask(JavaCompileSpec spec, Jdk List options = new JavaCompilerArgumentsBuilder(spec).build(); JavaCompiler compiler = javaHomeBasedJavaCompilerFactory.create(); MinimalJavaCompileOptions compileOptions = spec.getCompileOptions(); - StandardJavaFileManager standardFileManager = compiler.getStandardFileManager(null, null, compileOptions.getEncoding() != null ? Charset.forName(compileOptions.getEncoding()) : null); + Charset charset = compileOptions.getEncoding() != null ? Charset.forName(compileOptions.getEncoding()) : null; + StandardJavaFileManager standardFileManager = compiler.getStandardFileManager(null, null, charset); Iterable compilationUnits = standardFileManager.getJavaFileObjectsFromFiles(spec.getSourceFiles()); - StandardJavaFileManager fileManager = standardFileManager; - if (JavaVersion.current().isJava9Compatible() && emptySourcepathIn(options)) { - fileManager = (StandardJavaFileManager) SourcepathIgnoringProxy.proxy(standardFileManager, StandardJavaFileManager.class); - } - fileManager = (StandardJavaFileManager) FilteringClassLoaderInjectingProxy.proxy(fileManager, StandardJavaFileManager.class); + boolean hasEmptySourcepaths = JavaVersion.current().isJava9Compatible() && emptySourcepathIn(options); + JavaFileManager fileManager = GradleStandardJavaFileManager.wrap(standardFileManager, DefaultClassPath.of(spec.getAnnotationProcessorPath()), hasEmptySourcepaths); 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/ResourceCleaningCompilationTask.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/ResourceCleaningCompilationTask.java index 291cb8b8ccba..10a11836b669 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/ResourceCleaningCompilationTask.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/ResourceCleaningCompilationTask.java @@ -21,7 +21,7 @@ import javax.annotation.processing.Processor; import javax.tools.DiagnosticListener; import javax.tools.JavaCompiler; -import javax.tools.StandardJavaFileManager; +import java.io.Closeable; import java.nio.charset.Charset; import java.util.Locale; @@ -30,9 +30,9 @@ */ class ResourceCleaningCompilationTask implements JavaCompiler.CompilationTask { private final JavaCompiler.CompilationTask delegate; - private final StandardJavaFileManager fileManager; + private final Closeable fileManager; - ResourceCleaningCompilationTask(JavaCompiler.CompilationTask delegate, StandardJavaFileManager fileManager) { + ResourceCleaningCompilationTask(JavaCompiler.CompilationTask delegate, Closeable fileManager) { this.delegate = delegate; this.fileManager = fileManager; } 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 deleted file mode 100644 index 9252c0494154..000000000000 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/reflect/FilteringClassLoaderInjectingInvocationHandler.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 deleted file mode 100644 index 684a815452b9..000000000000 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/reflect/FilteringClassLoaderInjectingProxy.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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/language-java/src/main/java/org/gradle/api/internal/tasks/compile/reflect/GradleStandardJavaFileManager.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/reflect/GradleStandardJavaFileManager.java new file mode 100644 index 000000000000..71caa4d60eeb --- /dev/null +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/reflect/GradleStandardJavaFileManager.java @@ -0,0 +1,95 @@ +/* + * 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 org.gradle.internal.classpath.ClassPath; + +import javax.tools.ForwardingJavaFileManager; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.StandardLocation; +import java.io.IOException; +import java.net.URLClassLoader; +import java.util.Set; + +import static org.gradle.api.internal.tasks.compile.filter.AnnotationProcessorFilter.getFilteredClassLoader; + +public class GradleStandardJavaFileManager extends ForwardingJavaFileManager { + private final ClassPath annotationProcessorPath; + private final boolean hasEmptySourcePaths; + + private GradleStandardJavaFileManager(StandardJavaFileManager fileManager, ClassPath annotationProcessorPath, boolean hasEmptySourcePaths) { + super(fileManager); + this.annotationProcessorPath = annotationProcessorPath; + this.hasEmptySourcePaths = hasEmptySourcePaths; + } + + /** + * Overrides particular methods to prevent javac from accessing source files outside of Gradle's understanding or + * classloaders outside of Gradle's control. + */ + public static JavaFileManager wrap(StandardJavaFileManager delegate, ClassPath annotationProcessorPath, boolean hasEmptySourcePaths) { + return new GradleStandardJavaFileManager(delegate, annotationProcessorPath, hasEmptySourcePaths); + } + + @Override + public boolean hasLocation(Location location) { + if (hasEmptySourcePaths) { + // There is currently a requirement in the JDK9 javac implementation + // that when javac is invoked with an explicitly empty sourcepath + // (i.e. {@code --sourcepath ""}), it won't allow you to compile a java 9 + // module. However, we really want to explicitly set an empty sourcepath + // so that we don't implicitly pull in unrequested sourcefiles which + // haven't been snapshotted because we will consider the task up-to-date + // if the implicit files change. + // + // This implementation of hasLocation() pretends that the JavaFileManager + // has no concept of a source path. + if (location.equals(StandardLocation.SOURCE_PATH)) { + return false; + } + } + return super.hasLocation(location); + } + + @Override + public Iterable list(Location location, String packageName, Set kinds, boolean recurse) throws IOException { + if (hasEmptySourcePaths) { + // If we are pretending that we don't have a sourcepath, the compiler will + // look on the classpath for sources. Since we don't want to bring in any + // sources implicitly from the classpath, we have to ignore source files + // found on the classpath. + if (location.equals(StandardLocation.CLASS_PATH)) { + kinds.remove(JavaFileObject.Kind.SOURCE); + } + } + return super.list(location, packageName, kinds, recurse); + } + + @Override + public ClassLoader getClassLoader(Location location) { + ClassLoader classLoader = super.getClassLoader(location); + if (location.equals(StandardLocation.ANNOTATION_PROCESSOR_PATH)) { + if (classLoader instanceof URLClassLoader) { + return new URLClassLoader(annotationProcessorPath.getAsURLArray(), getFilteredClassLoader(classLoader.getParent())); + } + } + + return classLoader; + } +} diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/reflect/SourcepathIgnoringInvocationHandler.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/reflect/SourcepathIgnoringInvocationHandler.java deleted file mode 100644 index df17b7f1e284..000000000000 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/reflect/SourcepathIgnoringInvocationHandler.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2017 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.JavaFileObject; -import javax.tools.StandardLocation; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.util.Set; - -/** - * Intercepts JavaFileManager calls to ignore files on the sourcepath. - */ -public class SourcepathIgnoringInvocationHandler implements InvocationHandler { - private static final String HAS_LOCATION_METHOD = "hasLocation"; - private static final String LIST_METHOD = "list"; - private final Object proxied; - - public SourcepathIgnoringInvocationHandler(Object proxied) { - this.proxied = proxied; - } - - @Override - @SuppressWarnings("unchecked") - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - if (method.getName().equals(HAS_LOCATION_METHOD)) { - // There is currently a requirement in the JDK9 javac implementation - // that when javac is invoked with an explicitly empty sourcepath - // (i.e. {@code --sourcepath ""}), it won't allow you to compile a java 9 - // module. However, we really want to explicitly set an empty sourcepath - // so that we don't implicitly pull in unrequested sourcefiles which - // haven't been snapshotted because we will consider the task up-to-date - // if the implicit files change. - // - // This implementation of hasLocation() pretends that the JavaFileManager - // has no concept of a source path. - if (args[0].equals(StandardLocation.SOURCE_PATH)) { - return false; - } - } - if (method.getName().equals(LIST_METHOD)) { - // If we are pretending that we don't have a sourcepath, the compiler will - // look on the classpath for sources. Since we don't want to bring in any - // sources implicitly from the classpath, we have to ignore source files - // found on the classpath. - if (args[0].equals(StandardLocation.CLASS_PATH)) { - ((Set) args[2]).remove(JavaFileObject.Kind.SOURCE); - } - } - return method.invoke(proxied, args); - } -} diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/reflect/SourcepathIgnoringProxy.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/reflect/SourcepathIgnoringProxy.java deleted file mode 100644 index 589811fc3be3..000000000000 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/reflect/SourcepathIgnoringProxy.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2017 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; - -/** - * Provides a proxy object which ignores all files discovered on the sourcepath. - */ -public class SourcepathIgnoringProxy { - @SuppressWarnings("rawtypes") - public static Object proxy(Object proxied, Class targetInterface) { - return Proxy.newProxyInstance( - SourcepathIgnoringInvocationHandler.class.getClassLoader(), - new Class[] {targetInterface}, - new SourcepathIgnoringInvocationHandler(proxied)); - } -}