diff --git a/README.md b/README.md index bcfb3f4..b89f61e 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Once this is completed, add this to your `.bazelrc`: build --extra_toolchains=@bazel_jdt_java_toolchain//jdt:all ``` -By default the `jdt_java_toolchain` is using `local_jdk` for compilation. +By default the `jdt_java_toolchain` is using `local_jdk` for compilation. Please create your own `default_java_toolchain` if this doesn't work for your use case. Have a look at `jdt/BUILD` to see which JDKs are supported. @@ -35,17 +35,18 @@ and point directly at the source of this repo for development and testing. Unfortunately this means additional steps are required for developers. This script can be used to facilitate the steps. + ``` #!/bin/bash bazel build :JdtJavaBuilder_deploy.jar cp -fv bazel-bin/JdtJavaBuilder_deploy.jar compiler/export/ -bazel build //compiler/third_party/turbine:turbine_direct_binary_deploy.jar +bazel build //compiler/third_party/turbine:turbine_direct_binary_deploy.jar cp -fv bazel-bin/compiler/third_party/turbine/turbine_direct_binary_deploy.jar compiler/tools/ ``` -For your convinience, the `build-toolchain` script is provided in this repository. +For your convenience, the `build-toolchain` script is provided in this repository. ## Debugging @@ -85,8 +86,8 @@ ERROR: /Users/username/app/main/core/some-java-target/BUILD.bazel:4:13: Building /Users/username/tools/Darwin/jdk/bin/java -jar external/jdt_java_toolchain/builder/export/JdtJavaBuilder_deploy.jar @bazel-out/darwin-fastbuild/bin/some-java-target/libtarget-class.jar-0.params @bazel-out/darwin-fastbuild/bin/some-java-target/libtarget-class.jar-1.params) ``` -Either take the *SUBCOMMAND* or *ERROR* command. -You can ignore the `exec env` part. +Either take the *SUBCOMMAND* or *ERROR* command. +You can ignore the `exec env` part. The interesting two steps are: @@ -99,7 +100,7 @@ cd /private/var/tmp/_bazel_username/hash/execroot/core ``` The first is the execution directory and the latter the command. -You need to cd into the execution directory and then run the command yourself. +You need to `cd` into the execution directory and then run the command yourself. But this time add the remote debug arguments (before `-jar`) as follows: ``` @@ -120,3 +121,4 @@ The VS Code Bazel Java extension should also work. ## Releasing See [RELEASING README](dist/README.md). + diff --git a/compiler/BUILD b/compiler/BUILD index c75b005..39d7b5d 100644 --- a/compiler/BUILD +++ b/compiler/BUILD @@ -35,6 +35,14 @@ java_library( "@rules_jdt_org_ow2_asm_asm_commons", "@rules_jdt_org_ow2_asm_asm_tree", ], + javacopts = [ + "--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.resources=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + ], visibility = ["//:__subpackages__"], ) diff --git a/compiler/src/main/buildjar/com/google/devtools/build/buildjar/JavaLibraryBuildRequest.java b/compiler/src/main/buildjar/com/google/devtools/build/buildjar/JavaLibraryBuildRequest.java index ec4064b..a95485b 100644 --- a/compiler/src/main/buildjar/com/google/devtools/build/buildjar/JavaLibraryBuildRequest.java +++ b/compiler/src/main/buildjar/com/google/devtools/build/buildjar/JavaLibraryBuildRequest.java @@ -176,7 +176,7 @@ public JavaLibraryBuildRequest( this.processingModule = processingBuilder.build(); ImmutableList.Builder pluginsBuilder = - ImmutableList.builder();//.add(dependencyModule.getPlugin()); + ImmutableList.builder().add(dependencyModule.getPlugin()); processingModule.registerPlugin(pluginsBuilder); pluginsBuilder.addAll(extraPlugins); this.plugins = pluginsBuilder.build(); diff --git a/compiler/src/main/buildjar/com/google/devtools/build/buildjar/javac/BlazeEcjMain.java b/compiler/src/main/buildjar/com/google/devtools/build/buildjar/javac/BlazeEcjMain.java index e1c2221..d4aa720 100644 --- a/compiler/src/main/buildjar/com/google/devtools/build/buildjar/javac/BlazeEcjMain.java +++ b/compiler/src/main/buildjar/com/google/devtools/build/buildjar/javac/BlazeEcjMain.java @@ -15,29 +15,36 @@ package com.google.devtools.build.buildjar.javac; import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.collect.MoreCollectors.toOptional; -import static java.nio.charset.StandardCharsets.UTF_8; +import static java.lang.String.format; import static java.util.Comparator.comparing; +import static java.util.stream.Collectors.joining; -import java.io.File; -import java.io.IOError; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.file.Path; -import java.util.Collection; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.Set; import javax.tools.Diagnostic; -import javax.tools.JavaCompiler; -import javax.tools.JavaCompiler.CompilationTask; -import javax.tools.StandardJavaFileManager; -import javax.tools.StandardLocation; -import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler; -import org.eclipse.jdt.internal.compiler.tool.EclipseFileManager; +import org.eclipse.jdt.core.compiler.CharOperation; +import org.eclipse.jdt.internal.compiler.CompilationResult; +import org.eclipse.jdt.internal.compiler.ICompilerRequestor; +import org.eclipse.jdt.internal.compiler.batch.ClasspathLocation; +import org.eclipse.jdt.internal.compiler.batch.FileSystem; +import org.eclipse.jdt.internal.compiler.batch.FileSystem.Classpath; +import org.eclipse.jdt.internal.compiler.batch.FileSystem.ClasspathAnswer; +import org.eclipse.jdt.internal.compiler.batch.Main; +import org.eclipse.jdt.internal.compiler.env.ICompilationUnit; +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; +import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; @@ -46,13 +53,167 @@ import com.google.devtools.build.buildjar.javac.BlazeJavacResult.Status; import com.google.devtools.build.buildjar.javac.FormattedDiagnostic.Listener; import com.google.devtools.build.buildjar.javac.plugins.BlazeJavaCompilerPlugin; +import com.google.devtools.build.buildjar.javac.plugins.dependency.DependencyModule; +import com.google.devtools.build.buildjar.javac.plugins.dependency.StrictJavaDepsPlugin; +import com.google.devtools.build.buildjar.javac.plugins.processing.AnnotationProcessingModule; +import com.google.devtools.build.buildjar.javac.plugins.processing.AnnotationProcessingPlugin; import com.google.devtools.build.buildjar.javac.statistics.BlazeJavacStatistics; +import com.google.devtools.build.buildjar.proto.JavaCompilation.CompilationUnit; +import com.google.devtools.build.lib.view.proto.Deps.Dependency; /** - * BlazeJavacMain adapted to EclipseCompiler tool impl. + * BlazeJavacMain adapted to JDT Compiler */ public class BlazeEcjMain { + static class BlazeEclipseBatchCompiler extends Main { + + final AnnotationProcessingModule processingModule; + final Path sandboxPathPrefix; + final Map sourceFilesByAbsoluteOrCanonicalPath; + final Set processedJars = new HashSet<>(); + final DependencyModule dependencyModule; + final ImmutableSet directJars; + final Map directDependenciesMap; + final Map noneDirectDependenciesMap; + + public BlazeEclipseBatchCompiler(PrintWriter outWriter, PrintWriter errWriter, + ImmutableList plugins, String sandboxPathPrefix, Map sourceFilesByAbsoluteOrCanonicalPath) { + super(outWriter, errWriter, false /* systemExitWhenFinished */, null /* customDefaultOptions */, + null /* compilationProgress */); + this.sandboxPathPrefix = Path.of(sandboxPathPrefix); + this.sourceFilesByAbsoluteOrCanonicalPath = sourceFilesByAbsoluteOrCanonicalPath; + this.processingModule = ((AnnotationProcessingPlugin) plugins.stream() + .filter(AnnotationProcessingPlugin.class::isInstance).findAny().get()).getProcessingModule(); + this.dependencyModule = ((StrictJavaDepsPlugin) plugins.stream().filter(StrictJavaDepsPlugin.class::isInstance).findAny().get()).getDependencyModule(); + this.directJars = dependencyModule.directJars(); + this.directDependenciesMap = dependencyModule.getExplicitDependenciesMap(); + this.noneDirectDependenciesMap = dependencyModule.getImplicitDependenciesMap(); + + switch (dependencyModule.getStrictJavaDeps()) { + case ERROR: + setSeverity(CompilerOptions.OPTION_ReportForbiddenReference, ProblemSeverities.Error, true); + setSeverity(CompilerOptions.OPTION_ReportDiscouragedReference, ProblemSeverities.Error, true); + break; + + case WARN: + setSeverity(CompilerOptions.OPTION_ReportForbiddenReference, ProblemSeverities.Warning, true); + setSeverity(CompilerOptions.OPTION_ReportDiscouragedReference, ProblemSeverities.Warning, true); + break; + + case OFF: + setSeverity(CompilerOptions.OPTION_ReportForbiddenReference, ProblemSeverities.Ignore, true); + setSeverity(CompilerOptions.OPTION_ReportDiscouragedReference, ProblemSeverities.Ignore, true); + break; + } + } + + @Override + public ICompilerRequestor getBatchRequestor() { + ICompilerRequestor delegate = super.getBatchRequestor(); + return new ICompilerRequestor() { + private final Set toplevels = new HashSet<>(); + + @Override + public void acceptResult(CompilationResult result) { + delegate.acceptResult(result); + + ICompilationUnit compilationUnit = result.getCompilationUnit(); + if (compilationUnit != null && toplevels.add(compilationUnit)) { + recordAnnotationProcessingAndPackageInfo(result); + } + } + }; + } + + @Override + public FileSystem getLibraryAccess() { + // we use this to collect information about all used dependencies during compilation + FileSystem nameEnvironment = super.getLibraryAccess(); + nameEnvironment.nameEnvironmentListener = this::recordNameEnvironmentAnswer; + return nameEnvironment; + } + + protected void recordAnnotationProcessingAndPackageInfo(CompilationResult result) { + CompilationUnit.Builder builder = CompilationUnit.newBuilder(); + + if (result.getFileName() != null) { + // paths we get from ECJ are absolute, we have to translate them back to the exec-root relative path + Path path = Path.of(new String(result.getFileName())); + if(sourceFilesByAbsoluteOrCanonicalPath.containsKey(path)) + path = sourceFilesByAbsoluteOrCanonicalPath.get(path); + else + path = sandboxPathPrefix.relativize(path); + + builder.setPath(processingModule.stripSourceRoot(path).toString()); + builder.setGeneratedByAnnotationProcessor(processingModule.isGenerated(path)); + } + + if (result.packageName != null) { + String packageName = CharOperation.toString(result.packageName); + builder.setPkg(packageName); + dependencyModule.addPackage(packageName); + } + + if (result.compiledTypes != null) { + for (Object typename : result.compiledTypes.keySet()) { + String typeName = new String((char[])typename); + int lastSlashPos = typeName.lastIndexOf('/'); + if(lastSlashPos > -1) { + typeName = typeName.substring(lastSlashPos+1); + } + builder.addTopLevel(typeName.replace('$', '.')); + } + } + + processingModule.recordUnit(builder.build()); + } + + protected void recordNameEnvironmentAnswer(ClasspathAnswer classpathAnswer) { + Classpath classpath = classpathAnswer.source; + if(classpath instanceof ClasspathLocation) { + String jar = ((ClasspathLocation)classpath).getPath(); + if(jar != null && jar.endsWith(".jar")) { + Path jarPath = Path.of(jar); + if(processedJars.add(jarPath)) { + // we assume jars come from the execroot; JDT uses absolute/canonical paths + // therefore we translate the path back into an execroot relative path for Bazel to be happy + jarPath = sandboxPathPrefix.relativize(jarPath); + + // update the dependency proto + if(directJars.contains(jarPath)) { + if (!directDependenciesMap.containsKey(jarPath)) { + Dependency dep = + Dependency.newBuilder() + // Path.toString uses the platform separator (`\` on Windows) which may not + // match the format in params files (which currently always use `/`, see + // bazelbuild/bazel#4108). JavaBuilder should always parse Path strings into + // java.nio.file.Paths before comparing them. + .setPath(jarPath.toString()) + .setKind(Dependency.Kind.EXPLICIT) + .build(); + directDependenciesMap.put(jarPath, dep); + } + } else { + if (!noneDirectDependenciesMap.containsKey(jarPath)) { + Dependency dep = + Dependency.newBuilder() + // Path.toString uses the platform separator (`\` on Windows) which may not + // match the format in params files (which currently always use `/`, see + // bazelbuild/bazel#4108). JavaBuilder should always parse Path strings into + // java.nio.file.Paths before comparing them. + .setPath(jarPath.toString()) + .setKind(Dependency.Kind.IMPLICIT) + .build(); + noneDirectDependenciesMap.put(jarPath, dep); + } + } + } + } + } + } + } + // /** // * Sets up a BlazeJavaCompiler with the given plugins within the given context. // * @@ -68,8 +229,15 @@ public class BlazeEcjMain { // } public static BlazeJavacResult compile(BlazeJavacArguments arguments) { + // JDT uses getAbsolutePath/getCanonicalPath, which makes reporting of file paths to point into Bazel sandbox + // this causes issues in IDEs (IntelliJ and others) relying on parsing compiler output to map back errors/warnings + String sandboxPathPrefix; + try { + sandboxPathPrefix = detectWorkingDirPathPrefix(arguments); + } catch (final IOException e) { + return BlazeJavacResult.error(e.getMessage()); + } - List javacArguments = arguments.javacOptions(); try { processPluginArgs( arguments.plugins(), arguments.javacOptions(), arguments.blazeJavacOptions()); @@ -77,62 +245,43 @@ public static BlazeJavacResult compile(BlazeJavacArguments arguments) { return BlazeJavacResult.error(e.getMessage()); } -// Context context = new Context(); -// BlazeJavacStatistics.preRegister(context); -// CacheFSInfo.preRegister(context); -// setupBlazeJavaCompiler(arguments.plugins(), context); - BlazeJavacStatistics.Builder builder = BlazeJavacStatistics.newBuilder(); + Map sourceFilesByAbsoluteOrCanonicalPath = new HashMap<>(); + for (Path sourceFile : arguments.sourceFiles()) { + sourceFilesByAbsoluteOrCanonicalPath.put(sourceFile.toAbsolutePath(), sourceFile); + try { + sourceFilesByAbsoluteOrCanonicalPath.put(sourceFile.toRealPath(), sourceFile); + } catch (IOException e) { + return BlazeJavacResult.error(e.getMessage()); + } + } - Status status = Status.ERROR; StringWriter errOutput = new StringWriter(); - // TODO(cushon): where is this used when a diagnostic listener is registered? Consider removing - // it and handling exceptions directly in callers. PrintWriter errWriter = new PrintWriter(errOutput); + + + BlazeEclipseBatchCompiler compiler = new BlazeEclipseBatchCompiler(errWriter, errWriter, arguments.plugins(), sandboxPathPrefix, sourceFilesByAbsoluteOrCanonicalPath); + + List ecjArguments = new ArrayList<>(); + setLocations(ecjArguments, arguments, compiler.dependencyModule); + ecjArguments.addAll(arguments.javacOptions()); +// ecjArguments.add("-referenceInfo"); // easy way to get used dependencies + arguments.sourceFiles().stream().map(Path::toString).forEach(ecjArguments::add); + +// if(compiler.verbose) { +// errWriter.println("ECJ Command Line:"); +// errWriter.println(ecjArguments.stream().collect(joining(System.lineSeparator()))); +// errWriter.println(); +// errWriter.println(); +// } + + boolean compileResult = compiler.compile((String[]) ecjArguments.toArray(new String[ecjArguments.size()])); + + BlazeJavacStatistics.Builder builder = BlazeJavacStatistics.newBuilder(); + + Status status = compileResult ? Status.OK : Status.ERROR; Listener diagnosticsBuilder = new Listener(arguments.failFast()); - JavaCompiler compiler = new EclipseCompiler(); - - // Initialize parts of context that the filemanager depends on -// context.put(DiagnosticListener.class, diagnosticsBuilder); -// Log.instance(context).setWriters(errWriter); -// Options.instance(context).put("-Xlint:path", "path"); - - try (StandardJavaFileManager fileManager = - new EclipseFileManager(null, UTF_8)) { - - setLocations(fileManager, arguments); - - Iterable sourceFiles = arguments.sourceFiles(); // avoid NoSuchMethodError: 'java.lang.Iterable javax.tools.StandardJavaFileManager.getJavaFileObjectsFromPaths(java.util.Collection)' - CompilationTask task = - compiler - .getTask( - errWriter, - fileManager, - diagnosticsBuilder, - javacArguments, - /* classes= */ ImmutableList.of(), - fileManager.getJavaFileObjectsFromPaths(sourceFiles)); -// try { - status = fromResult(task.call()); -// } catch (PropagatedException e) { -// throw e.getCause(); -// } - } catch (RuntimeException | IOException | LinkageError | AssertionError t) { - t.printStackTrace(errWriter); - status = Status.CRASH; -// } finally { -// if (status == Status.OK) { -// // There could be situations where we incorrectly skip Error Prone and the compilation -// // ends up succeeding, e.g., if there are errors that are fixed by subsequent round of -// // annotation processing. This check ensures that if there were any flow events at all, -// // then plugins were run. There may legitimately not be any flow events, e.g. -proc:only -// // or empty source files. -// if (compiler.skippedFlowEvents() > 0 && compiler.flowEvents() == 0) { -// errWriter.println("Expected at least one FLOW event"); -// status = Status.ERROR; -// } -// } - } - + + errWriter.flush(); ImmutableList diagnostics = diagnosticsBuilder.build(); @@ -153,27 +302,29 @@ public static BlazeJavacResult compile(BlazeJavacArguments arguments) { } } } - + String output = errOutput.toString(); - // JDT uses getAbsolutePath, which makes reporting of file paths to point into Bazel sandbox - // this causes issues in IntelliJ - String canonicalPathPrefix = null; + // JDT uses getAbsolutePath/getCanonicalPath, which makes reporting of file paths to point into Bazel sandbox + // this causes issues in IDEs (IntelliJ and others) relying on parsing compiler output to map back errors/warnings + String canonicalPathPrefix; try { - canonicalPathPrefix = detectWorkingDirPathPrefix(arguments); - if(canonicalPathPrefix != null) { - output = output.replace(canonicalPathPrefix, ""); - } - } catch (IOException e) { + canonicalPathPrefix = detectWorkingDirPathPrefix(arguments); + + + } catch (final IOException e) { e.printStackTrace(errWriter); errWriter.flush(); + return BlazeJavacResult.error(e.getMessage()); } - return BlazeJavacResult.createFullResult( + if(canonicalPathPrefix != null) { + output = output.replace(canonicalPathPrefix, ""); + } + return BlazeJavacResult.createFullResult( status, filterDiagnostics(werror, diagnostics), output, - compiler, builder.build()); } @@ -183,7 +334,7 @@ private static String detectWorkingDirPathPrefix(BlazeJavacArguments arguments) String workDir = System.getProperty("user.dir"); if(workDir == null) throw new IOException("No working directory returned by JVM for property user.dir!"); - + if(!workDir.endsWith("/")) { workDir += "/"; } @@ -203,13 +354,6 @@ private static String detectWorkingDirPathPrefix(BlazeJavacArguments arguments) return workDir; } - private static Status fromResult(Boolean result) { - if(result == null) - return Status.CRASH; - - return result.booleanValue() ? Status.OK : Status.ERROR; - } - private static boolean isWerror(WerrorCustomOption werrorCustom, FormattedDiagnostic diagnostic) { switch (diagnostic.getKind()) { case WARNING: @@ -278,58 +422,51 @@ static void processPluginArgs( } } - private static void setLocations(StandardJavaFileManager fileManager, BlazeJavacArguments arguments) { - try { - fileManager.setLocationFromPaths(StandardLocation.CLASS_PATH, arguments.classPath()); - // modular dependencies must be on the module path, not the classpath - //[ECJ fails if both are set]fileManager.setLocationFromPaths(StandardLocation.MODULE_PATH, arguments.classPath()); - - fileManager.setLocationFromPaths( - StandardLocation.CLASS_OUTPUT, ImmutableList.of(arguments.classOutput())); - if (arguments.nativeHeaderOutput() != null) { - fileManager.setLocationFromPaths( - StandardLocation.NATIVE_HEADER_OUTPUT, - ImmutableList.of(arguments.nativeHeaderOutput())); - } + private static void setLocations(List ecjArguments, BlazeJavacArguments arguments, DependencyModule dependencyModule) { + if(!arguments.processorPath().isEmpty()) { + ecjArguments.add("-processorpath"); + ecjArguments.add(arguments.processorPath().stream().map(Path::toString).collect(joining(":"))); + // if release/target >= JDK 9 then -processorpath will be ignored by JDT but --processor-module-path is expected instead + // (we set both to let JDT pick) + ecjArguments.add("--processor-module-path"); + ecjArguments.add(arguments.processorPath().stream().map(Path::toString).collect(joining(":"))); + } - ImmutableList sourcePath = arguments.sourcePath(); - if (sourcePath.isEmpty()) { - // javac expects a module-info-relative source path to be set when compiling modules, - // otherwise it reports an error: - // "file should be on source path, or on patch path for module" - ImmutableList moduleInfos = - arguments.sourceFiles().stream() - .filter(f -> f.getFileName().toString().equals("module-info.java")) - .collect(toImmutableList()); - if (moduleInfos.size() == 1) { - sourcePath = ImmutableList.of(getOnlyElement(moduleInfos).getParent()); - } - } - fileManager.setLocationFromPaths(StandardLocation.SOURCE_PATH, sourcePath); + if(!arguments.classPath().isEmpty()) { + ImmutableSet directJars = dependencyModule.directJars(); + ecjArguments.add("-classpath"); + ecjArguments.add(arguments.classPath().stream().map(p -> + directJars.contains(p) ? p.toString() : format ("%s[-**/*]", p.toString()) + ).collect(joining(":"))); + } - Path system = arguments.system(); - if (system != null) { - fileManager.setLocationFromPaths( - StandardLocation.locationFor("SYSTEM_MODULES"), ImmutableList.of(system)); - } - // The bootclasspath may legitimately be empty if --release is being used. - Collection bootClassPath = arguments.bootClassPath(); - if (!bootClassPath.isEmpty()) { - //[ECJ fails if both are set]fileManager.setLocationFromPaths(StandardLocation.PLATFORM_CLASS_PATH, bootClassPath); - } - fileManager.setLocationFromPaths( - StandardLocation.ANNOTATION_PROCESSOR_PATH, arguments.processorPath()); - // if release/traget >= JDK 9 then -processorpath will be ignored by JDT but --processor-module-path is expected instead - // (we set both to let JDT pick) - fileManager.setLocationFromPaths( - StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, arguments.processorPath()); - if (arguments.sourceOutput() != null) { - fileManager.setLocationFromPaths( - StandardLocation.SOURCE_OUTPUT, ImmutableList.of(arguments.sourceOutput())); - } - } catch (IOException e) { - throw new IOError(e); + // modular dependencies must be on the module path, not the classpath + //[ECJ fails if both are set]fileManager.setLocationFromPaths(StandardLocation.MODULE_PATH, arguments.classPath()); + +// if(compilerOptions.complianceLevel <= ClassFileConstants.JDK1_8) { +// if(!arguments.bootClassPath().isEmpty()) { +// ecjArguments.add("-bootclasspath"); +// ecjArguments.add(arguments.bootClassPath().stream().map(Path::toString).collect(joining(":"))); +// } +// } + + ecjArguments.add("-d"); + ecjArguments.add(arguments.classOutput().toString()); + + if (arguments.sourceOutput() != null) { + ecjArguments.add("-s"); + ecjArguments.add(arguments.sourceOutput().toString()); } + + if(!arguments.sourcePath().isEmpty()) { + ecjArguments.add("-sourcepath"); + ecjArguments.add(arguments.sourcePath().stream().map(Path::toString).collect(joining(":"))); + } + + if (arguments.system() != null) { + ecjArguments.add("--system"); + ecjArguments.add(arguments.system().toString()); + } } // /** diff --git a/compiler/src/main/buildjar/com/google/devtools/build/buildjar/javac/BlazeEcjToolMain.java b/compiler/src/main/buildjar/com/google/devtools/build/buildjar/javac/BlazeEcjToolMain.java new file mode 100644 index 0000000..7b155b8 --- /dev/null +++ b/compiler/src/main/buildjar/com/google/devtools/build/buildjar/javac/BlazeEcjToolMain.java @@ -0,0 +1,387 @@ +// Copyright 2014 The Bazel Authors. All rights reserved. +// +// 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 com.google.devtools.build.buildjar.javac; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.common.collect.MoreCollectors.toOptional; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Comparator.comparing; + +import java.io.IOError; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.file.Path; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import javax.tools.Diagnostic; +import javax.tools.JavaCompiler; +import javax.tools.JavaCompiler.CompilationTask; +import javax.tools.StandardJavaFileManager; +import javax.tools.StandardLocation; + +import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler; +import org.eclipse.jdt.internal.compiler.tool.EclipseFileManager; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.devtools.build.buildjar.InvalidCommandLineException; +import com.google.devtools.build.buildjar.javac.BlazeJavacResult.Status; +import com.google.devtools.build.buildjar.javac.FormattedDiagnostic.Listener; +import com.google.devtools.build.buildjar.javac.plugins.BlazeJavaCompilerPlugin; +import com.google.devtools.build.buildjar.javac.statistics.BlazeJavacStatistics; + +/** + * BlazeJavacMain adapted to EclipseCompiler tool impl. + */ +public class BlazeEcjToolMain { + +// /** +// * Sets up a BlazeJavaCompiler with the given plugins within the given context. +// * +// * @param context JavaCompiler's associated Context +// */ +// @VisibleForTesting +// static void setupBlazeJavaCompiler( +// ImmutableList plugins, Context context) { +// for (BlazeJavaCompilerPlugin plugin : plugins) { +// plugin.initializeContext(context); +// } +// BlazeJavaCompiler.preRegister(context, plugins); +// } + + public static BlazeJavacResult compile(BlazeJavacArguments arguments) { + + List javacArguments = arguments.javacOptions(); + try { + processPluginArgs( + arguments.plugins(), arguments.javacOptions(), arguments.blazeJavacOptions()); + } catch (InvalidCommandLineException e) { + return BlazeJavacResult.error(e.getMessage()); + } + +// Context context = new Context(); +// BlazeJavacStatistics.preRegister(context); +// CacheFSInfo.preRegister(context); +// setupBlazeJavaCompiler(arguments.plugins(), context); + BlazeJavacStatistics.Builder builder = BlazeJavacStatistics.newBuilder(); + + Status status = Status.ERROR; + StringWriter errOutput = new StringWriter(); + // TODO(cushon): where is this used when a diagnostic listener is registered? Consider removing + // it and handling exceptions directly in callers. + PrintWriter errWriter = new PrintWriter(errOutput); + Listener diagnosticsBuilder = new Listener(arguments.failFast()); + JavaCompiler compiler = new EclipseCompiler(); + + // Initialize parts of context that the filemanager depends on +// context.put(DiagnosticListener.class, diagnosticsBuilder); +// Log.instance(context).setWriters(errWriter); +// Options.instance(context).put("-Xlint:path", "path"); + + try (StandardJavaFileManager fileManager = + new EclipseFileManager(null, UTF_8)) { + + setLocations(fileManager, arguments); + + Iterable sourceFiles = arguments.sourceFiles(); // avoid NoSuchMethodError: 'java.lang.Iterable javax.tools.StandardJavaFileManager.getJavaFileObjectsFromPaths(java.util.Collection)' + CompilationTask task = + compiler + .getTask( + errWriter, + fileManager, + diagnosticsBuilder, + javacArguments, + /* classes= */ ImmutableList.of(), + fileManager.getJavaFileObjectsFromPaths(sourceFiles)); +// try { + status = fromResult(task.call()); +// } catch (PropagatedException e) { +// throw e.getCause(); +// } + } catch (RuntimeException | IOException | LinkageError | AssertionError t) { + t.printStackTrace(errWriter); + status = Status.CRASH; +// } finally { +// if (status == Status.OK) { +// // There could be situations where we incorrectly skip Error Prone and the compilation +// // ends up succeeding, e.g., if there are errors that are fixed by subsequent round of +// // annotation processing. This check ensures that if there were any flow events at all, +// // then plugins were run. There may legitimately not be any flow events, e.g. -proc:only +// // or empty source files. +// if (compiler.skippedFlowEvents() > 0 && compiler.flowEvents() == 0) { +// errWriter.println("Expected at least one FLOW event"); +// status = Status.ERROR; +// } +// } + } + + errWriter.flush(); + ImmutableList diagnostics = diagnosticsBuilder.build(); + + boolean werror = + diagnostics.stream().anyMatch(d -> d.getCode().equals("compiler.err.warnings.and.werror")); + if (status.equals(Status.OK)) { + Optional maybeWerrorCustom = + arguments.blazeJavacOptions().stream() + .filter(arg -> arg.startsWith("-Werror:")) + .collect(toOptional()) + .map(WerrorCustomOption::create); + if (maybeWerrorCustom.isPresent()) { + WerrorCustomOption werrorCustom = maybeWerrorCustom.get(); + if (diagnostics.stream().anyMatch(d -> isWerror(werrorCustom, d))) { + errOutput.append("error: warnings found and -Werror specified\n"); + status = Status.ERROR; + werror = true; + } + } + } + + String output = errOutput.toString(); + + // JDT uses getAbsolutePath, which makes reporting of file paths to point into Bazel sandbox + // this causes issues in IntelliJ + String canonicalPathPrefix = null; + try { + canonicalPathPrefix = detectWorkingDirPathPrefix(arguments); + if(canonicalPathPrefix != null) { + output = output.replace(canonicalPathPrefix, ""); + } + } catch (IOException e) { + e.printStackTrace(errWriter); + errWriter.flush(); + } + + return BlazeJavacResult.createFullResult( + status, + filterDiagnostics(werror, diagnostics), + output, + builder.build()); + } + + private static String detectWorkingDirPathPrefix(BlazeJavacArguments arguments) throws IOException { + // since the JDT compiler is executed from within the sandbox, the absolute path will be resolved to the working directory + // we simple remove the working directory + String workDir = System.getProperty("user.dir"); + if(workDir == null) + throw new IOException("No working directory returned by JVM for property user.dir!"); + + if(!workDir.endsWith("/")) { + workDir += "/"; + } + + // the following code is only for our own sanity + Optional first = arguments.sourcePath().stream().findFirst(); + if (!first.isPresent()) { + first = arguments.sourceFiles().stream().findFirst(); + } + + String absoluteFilePath = first.get().toAbsolutePath().toString(); + if (!absoluteFilePath.startsWith(workDir)) { + String filePath = first.get().toString(); + throw new IOException(String.format("Unable to confirm working dir '%s' using file '%s' with absolute path '%s'!", workDir, filePath, absoluteFilePath)); + } + + return workDir; + } + + private static Status fromResult(Boolean result) { + if(result == null) + return Status.CRASH; + + return result.booleanValue() ? Status.OK : Status.ERROR; + } + + private static boolean isWerror(WerrorCustomOption werrorCustom, FormattedDiagnostic diagnostic) { + switch (diagnostic.getKind()) { + case WARNING: + case MANDATORY_WARNING: + return werrorCustom.isEnabled(diagnostic.getLintCategory()); + default: + return false; + } + } + + private static final ImmutableSet IGNORED_DIAGNOSTIC_CODES = + ImmutableSet.of( + "compiler.note.deprecated.filename", + "compiler.note.deprecated.plural", + "compiler.note.deprecated.recompile", + "compiler.note.deprecated.filename.additional", + "compiler.note.deprecated.plural.additional", + "compiler.note.unchecked.filename", + "compiler.note.unchecked.plural", + "compiler.note.unchecked.recompile", + "compiler.note.unchecked.filename.additional", + "compiler.note.unchecked.plural.additional", + "compiler.warn.sun.proprietary", + // avoid warning spam when enabling processor options for an entire tree, only a subset + // of which actually runs the processor + "compiler.warn.proc.unmatched.processor.options", + // don't want about v54 class files when running javac9 on JDK 10 + // TODO(cushon): remove after the next javac update + "compiler.warn.big.major.version", + // don't want about incompatible processor source versions when running javac9 on JDK 10 + // TODO(cushon): remove after the next javac update + "compiler.warn.proc.processor.incompatible.source.version", + // https://github.com/bazelbuild/bazel/issues/5985 + "compiler.warn.unknown.enum.constant", + "compiler.warn.unknown.enum.constant.reason"); + + private static ImmutableList filterDiagnostics( + boolean werror, ImmutableList diagnostics) { + return diagnostics.stream() + .filter(d -> shouldReportDiagnostic(werror, d)) + // Print errors last to make them more visible. + .sorted(comparing(FormattedDiagnostic::getKind).reversed()) + .collect(toImmutableList()); + } + + private static boolean shouldReportDiagnostic(boolean werror, FormattedDiagnostic diagnostic) { + if (!IGNORED_DIAGNOSTIC_CODES.contains(diagnostic.getCode())) { + return true; + } + // show compiler.warn.sun.proprietary if we're running with -Werror + if (werror && diagnostic.getKind() != Diagnostic.Kind.NOTE) { + return true; + } + return false; + } + + /** Processes Plugin-specific arguments and removes them from the args array. */ + @VisibleForTesting + static void processPluginArgs( + ImmutableList plugins, + ImmutableList standardJavacopts, + ImmutableList blazeJavacopts) + throws InvalidCommandLineException { + for (BlazeJavaCompilerPlugin plugin : plugins) { + plugin.processArgs(standardJavacopts, blazeJavacopts); + } + } + + private static void setLocations(StandardJavaFileManager fileManager, BlazeJavacArguments arguments) { + try { + fileManager.setLocationFromPaths(StandardLocation.CLASS_PATH, arguments.classPath()); + // modular dependencies must be on the module path, not the classpath + //[ECJ fails if both are set]fileManager.setLocationFromPaths(StandardLocation.MODULE_PATH, arguments.classPath()); + + fileManager.setLocationFromPaths( + StandardLocation.CLASS_OUTPUT, ImmutableList.of(arguments.classOutput())); + if (arguments.nativeHeaderOutput() != null) { + fileManager.setLocationFromPaths( + StandardLocation.NATIVE_HEADER_OUTPUT, + ImmutableList.of(arguments.nativeHeaderOutput())); + } + + ImmutableList sourcePath = arguments.sourcePath(); + if (sourcePath.isEmpty()) { + // javac expects a module-info-relative source path to be set when compiling modules, + // otherwise it reports an error: + // "file should be on source path, or on patch path for module" + ImmutableList moduleInfos = + arguments.sourceFiles().stream() + .filter(f -> f.getFileName().toString().equals("module-info.java")) + .collect(toImmutableList()); + if (moduleInfos.size() == 1) { + sourcePath = ImmutableList.of(getOnlyElement(moduleInfos).getParent()); + } + } + fileManager.setLocationFromPaths(StandardLocation.SOURCE_PATH, sourcePath); + + Path system = arguments.system(); + if (system != null) { + fileManager.setLocationFromPaths( + StandardLocation.locationFor("SYSTEM_MODULES"), ImmutableList.of(system)); + } + // The bootclasspath may legitimately be empty if --release is being used. + Collection bootClassPath = arguments.bootClassPath(); + if (!bootClassPath.isEmpty()) { + //[ECJ fails if both are set]fileManager.setLocationFromPaths(StandardLocation.PLATFORM_CLASS_PATH, bootClassPath); + } + fileManager.setLocationFromPaths( + StandardLocation.ANNOTATION_PROCESSOR_PATH, arguments.processorPath()); + // if release/traget >= JDK 9 then -processorpath will be ignored by JDT but --processor-module-path is expected instead + // (we set both to let JDT pick) + fileManager.setLocationFromPaths( + StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, arguments.processorPath()); + if (arguments.sourceOutput() != null) { + fileManager.setLocationFromPaths( + StandardLocation.SOURCE_OUTPUT, ImmutableList.of(arguments.sourceOutput())); + } + } catch (IOException e) { + throw new IOError(e); + } + } + +// /** +// * When Bazel invokes JavaBuilder, it puts javac.jar on the bootstrap class path and +// * JavaBuilder_deploy.jar on the user class path. We need Error Prone to be available on the +// * annotation processor path, but we want to mask out any other classes to minimize class version +// * skew. +// */ +// private static class ClassloaderMaskingFileManager extends EclipseFileManager { +// +// private final ImmutableSet builtinProcessors; +// +// public ClassloaderMaskingFileManager(ImmutableSet builtinProcessors) { +// super(null, UTF_8); +// this.builtinProcessors = builtinProcessors; +// } +// +// +// @Override +// protected ClassLoader createClassLoader(URL[] urls) { +// return new URLClassLoader( +// urls, +// new ClassLoader(getPlatformClassLoader()) { +// @Override +// protected Class findClass(String name) throws ClassNotFoundException { +// if (name.startsWith("com.google.errorprone.") +// || name.startsWith("com.google.common.collect.") +// || name.startsWith("com.google.common.base.") +// || name.startsWith("com.google.common.graph.") +// || name.startsWith("org.checkerframework.shaded.dataflow.") +// || name.startsWith("com.sun.source.") +// || name.startsWith("com.sun.tools.") +// || name.startsWith("com.google.devtools.build.buildjar.javac.statistics.") +// || name.startsWith("dagger.model.") +// || name.startsWith("dagger.spi.") +// || builtinProcessors.contains(name)) { +// return Class.forName(name); +// } +// throw new ClassNotFoundException(name); +// } +// }); +// } +// } + + public static ClassLoader getPlatformClassLoader() { + try { + // In JDK 9+, all platform classes are visible to the platform class loader: + // https://docs.oracle.com/javase/9/docs/api/java/lang/ClassLoader.html#getPlatformClassLoader-- + return (ClassLoader) ClassLoader.class.getMethod("getPlatformClassLoader").invoke(null); + } catch (ReflectiveOperationException e) { + // In earlier releases, set 'null' as the parent to delegate to the boot class loader. + return null; + } + } + + private BlazeEcjToolMain() {} +} \ No newline at end of file diff --git a/compiler/src/main/buildjar/com/google/devtools/build/buildjar/javac/BlazeJavacResult.java b/compiler/src/main/buildjar/com/google/devtools/build/buildjar/javac/BlazeJavacResult.java index 9aad368..617405d 100644 --- a/compiler/src/main/buildjar/com/google/devtools/build/buildjar/javac/BlazeJavacResult.java +++ b/compiler/src/main/buildjar/com/google/devtools/build/buildjar/javac/BlazeJavacResult.java @@ -14,10 +14,6 @@ package com.google.devtools.build.buildjar.javac; -import javax.annotation.Nullable; -import javax.tools.JavaCompiler; - -import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.devtools.build.buildjar.javac.statistics.BlazeJavacStatistics; @@ -35,42 +31,39 @@ public enum Status { private final Status status; private final ImmutableList diagnostics; private final String output; - private final JavaCompiler compiler; private final BlazeJavacStatistics statistics; public static BlazeJavacResult ok() { - return createFullResult(Status.OK, ImmutableList.of(), "", null, BlazeJavacStatistics.empty()); + return createFullResult(Status.OK, ImmutableList.of(), "", BlazeJavacStatistics.empty()); } public static BlazeJavacResult error(String message) { return createFullResult( - Status.ERROR, ImmutableList.of(), message, null, BlazeJavacStatistics.empty()); + Status.ERROR, ImmutableList.of(), message, BlazeJavacStatistics.empty()); } public static BlazeJavacResult cancelled(String message) { return createFullResult( - Status.CANCELLED, ImmutableList.of(), message, null, BlazeJavacStatistics.empty()); + Status.CANCELLED, ImmutableList.of(), message, BlazeJavacStatistics.empty()); } public static BlazeJavacResult fallback() { return createFullResult( - Status.REQUIRES_FALLBACK, ImmutableList.of(), "", null, BlazeJavacStatistics.empty()); + Status.REQUIRES_FALLBACK, ImmutableList.of(), "", BlazeJavacStatistics.empty()); } public BlazeJavacResult withStatistics(BlazeJavacStatistics statistics) { - return new BlazeJavacResult(status, diagnostics, output, compiler, statistics); + return new BlazeJavacResult(status, diagnostics, output, statistics); } private BlazeJavacResult( Status status, ImmutableList diagnostics, String output, - @Nullable JavaCompiler compiler, BlazeJavacStatistics statistics) { this.status = status; this.diagnostics = diagnostics; this.output = output; - this.compiler = compiler; this.statistics = statistics; } @@ -78,9 +71,8 @@ public static BlazeJavacResult createFullResult( Status status, ImmutableList diagnostics, String output, - JavaCompiler compiler, BlazeJavacStatistics statistics) { - return new BlazeJavacResult(status, diagnostics, output, compiler, statistics); + return new BlazeJavacResult(status, diagnostics, output, statistics); } public boolean isOk() { @@ -102,9 +94,4 @@ public String output() { public BlazeJavacStatistics statistics() { return statistics; } - - @VisibleForTesting - public JavaCompiler compiler() { - return compiler; - } } \ No newline at end of file diff --git a/compiler/src/main/buildjar/com/google/devtools/build/buildjar/javac/plugins/dependency/DependencyModule.java b/compiler/src/main/buildjar/com/google/devtools/build/buildjar/javac/plugins/dependency/DependencyModule.java index f4008a7..d37761a 100644 --- a/compiler/src/main/buildjar/com/google/devtools/build/buildjar/javac/plugins/dependency/DependencyModule.java +++ b/compiler/src/main/buildjar/com/google/devtools/build/buildjar/javac/plugins/dependency/DependencyModule.java @@ -117,7 +117,7 @@ public static enum StrictJavaDeps { /** Returns a plugin to be enabled in the compiler. */ public BlazeJavaCompilerPlugin getPlugin() { - throw new IllegalStateException("not supported"); //return new StrictJavaDepsPlugin(this); + return new StrictJavaDepsPlugin(this); } /** diff --git a/compiler/src/main/buildjar/com/google/devtools/build/buildjar/javac/plugins/dependency/ImplicitDependencyExtractor.java b/compiler/src/main/buildjar/com/google/devtools/build/buildjar/javac/plugins/dependency/ImplicitDependencyExtractor.java new file mode 100644 index 0000000..4f49c19 --- /dev/null +++ b/compiler/src/main/buildjar/com/google/devtools/build/buildjar/javac/plugins/dependency/ImplicitDependencyExtractor.java @@ -0,0 +1,138 @@ +// Copyright 2014 The Bazel Authors. All rights reserved. +// +// 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 com.google.devtools.build.buildjar.javac.plugins.dependency; + +import com.google.devtools.build.lib.view.proto.Deps; +import com.sun.tools.javac.code.Symbol.ClassSymbol; +import com.sun.tools.javac.code.Symtab; +import com.sun.tools.javac.util.Context; +import java.lang.reflect.Field; +import java.nio.file.Path; +import java.util.Map; +import java.util.Set; +import javax.lang.model.util.SimpleTypeVisitor7; +import javax.tools.JavaFileObject; + +/** + * A lightweight mechanism for extracting compile-time dependencies from javac, by performing a scan + * of the symbol table after compilation finishes. It only includes dependencies from jar files, + * which can be interface jars or regular third_party jars, matching the compilation model of Blaze. + * Note that JDK8 may provide support for extracting per-class, finer-grained dependencies, and if + * that implementation has reasonable overhead it may be a future option. + */ +public class ImplicitDependencyExtractor { + + /** Map collecting dependency information, used for the proto output */ + private final Map depsMap; + + private final TypeVisitor typeVisitor = new TypeVisitor(); + private final Set platformJars; + + /** + * ImplicitDependencyExtractor does not guarantee any ordering of the reported dependencies. + * Clients should preserve the original classpath ordering if trying to minimize their classpaths + * using this information. + */ + public ImplicitDependencyExtractor(Map depsMap, Set platformJars) { + this.depsMap = depsMap; + this.platformJars = platformJars; + } + + /** + * Collects the implicit dependencies of the given set of ClassSymbol roots. As we're interested + * in differentiating between symbols that were just resolved vs. symbols that were fully + * completed by the compiler, we start the analysis by finding all the implicit dependencies + * reachable from the given set of roots. For completeness, we then walk the symbol table + * associated with the given context and collect the jar files of the remaining class symbols + * found there. + * + * @param context compilation context + * @param roots root classes in the implicit dependency collection + */ + public void accumulate(Context context, Set roots) { + Symtab symtab = Symtab.instance(context); + + // Collect transitive references for root types + for (ClassSymbol root : roots) { + root.type.accept(typeVisitor, null); + } + + // Collect all other partially resolved types + for (ClassSymbol cs : symtab.getAllClasses()) { + // When recording we want to differentiate between jar references through completed symbols + // and incomplete symbols + boolean completed = cs.isCompleted(); + if (cs.classfile != null) { + collectJarOf(cs.classfile, platformJars, completed); + } else if (cs.sourcefile != null) { + collectJarOf(cs.sourcefile, platformJars, completed); + } + } + } + + /** + * Attempts to add the jar associated with the given JavaFileObject, if any, to the collection, + * filtering out jars on the compilation bootclasspath. + * + * @param reference JavaFileObject representing a class or source file + * @param platformJars classes on javac's bootclasspath + * @param completed whether the jar was referenced through a completed symbol + */ + private void collectJarOf(JavaFileObject reference, Set platformJars, boolean completed) { + + Path path = getJarPath(reference); + if (path == null) { + return; + } + + // Filter out classes in rt.jar + if (platformJars.contains(path)) { + return; + } + + Deps.Dependency currentDep = depsMap.get(path); + + // If the dep hasn't been recorded we add it to the map + // If it's been recorded as INCOMPLETE but is now complete we upgrade the dependency + if (currentDep == null + || (completed && currentDep.getKind() == Deps.Dependency.Kind.INCOMPLETE)) { + depsMap.put( + path, + Deps.Dependency.newBuilder() + .setKind(completed ? Deps.Dependency.Kind.IMPLICIT : Deps.Dependency.Kind.INCOMPLETE) + .setPath(path.toString()) + .build()); + } + } + + public static Path getJarPath(JavaFileObject file) { + if (file == null) { + return null; + } + try { + Field field = file.getClass().getDeclaredField("userJarPath"); + field.setAccessible(true); + return (Path) field.get(file); + } catch (NoSuchFieldException e) { + return null; + } catch (ReflectiveOperationException e) { + throw new LinkageError(e.getMessage(), e); + } + } + + private static class TypeVisitor extends SimpleTypeVisitor7 { + // TODO(bazel-team): Override the visitor methods we're interested in. + } +} diff --git a/compiler/src/main/buildjar/com/google/devtools/build/buildjar/javac/plugins/dependency/StrictJavaDepsPlugin.java b/compiler/src/main/buildjar/com/google/devtools/build/buildjar/javac/plugins/dependency/StrictJavaDepsPlugin.java new file mode 100644 index 0000000..d2e5122 --- /dev/null +++ b/compiler/src/main/buildjar/com/google/devtools/build/buildjar/javac/plugins/dependency/StrictJavaDepsPlugin.java @@ -0,0 +1,524 @@ +// Copyright 2014 The Bazel Authors. All rights reserved. +// +// 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 com.google.devtools.build.buildjar.javac.plugins.dependency; + +import javax.tools.JavaFileObject; + +import com.google.auto.value.AutoValue; +import com.google.devtools.build.buildjar.javac.plugins.BlazeJavaCompilerPlugin; + +/** + * A plugin for BlazeJavaCompiler that checks for types referenced directly in the source, but + * included through transitive dependencies. To get this information, we hook into the type + * attribution phase of the BlazeJavaCompiler (thus the overhead is another tree scan with the + * classic visitor). The constructor takes a map from jar names to target names, only for the jars + * that come from transitive dependencies (Blaze computes this information). + */ +public final class StrictJavaDepsPlugin extends BlazeJavaCompilerPlugin { +// private static final Attributes.Name TARGET_LABEL = new Attributes.Name("Target-Label"); +// private static final Attributes.Name INJECTING_RULE_KIND = +// new Attributes.Name("Injecting-Rule-Kind"); + +// private ImplicitDependencyExtractor implicitDependencyExtractor; +// private CheckingTreeScanner checkingTreeScanner; + private final DependencyModule dependencyModule; + +// /** Marks seen compilation toplevels and their import sections */ +// private final Set toplevels; +// /** Marks seen ASTs */ +// private final Set trees; +// /** Computed missing dependencies */ +// private final Set missingTargets; +// /** Strict deps diagnostics. */ +// private final List diagnostics; +// +// private PrintWriter errWriter; + + @AutoValue + abstract static class SjdDiagnostic { + abstract int pos(); + + abstract String message(); + + abstract JavaFileObject source(); + + static SjdDiagnostic create(int pos, String message, JavaFileObject source) { + return new AutoValue_StrictJavaDepsPlugin_SjdDiagnostic(pos, message, source); + } + } + + /** + * On top of javac, we keep Blaze-specific information in the form of two maps. Both map jars + * (exactly as they appear on the classpath) to target names, one is used for direct dependencies, + * the other for the transitive dependencies. + * + *

This enables the detection of dependency issues. For instance, when a type com.Foo is + * referenced in the source and it's coming from an indirect dependency, we emit a warning + * flagging that dependency. Also, we can check whether the direct dependencies were actually + * necessary, i.e. if their associated jars were used at all for looking up class definitions. + */ + public StrictJavaDepsPlugin(DependencyModule dependencyModule) { + this.dependencyModule = dependencyModule; +// toplevels = new HashSet<>(); +// trees = new HashSet<>(); +// missingTargets = new HashSet<>(); +// diagnostics = new ArrayList<>(); + } + + public DependencyModule getDependencyModule() { + return dependencyModule; + } + +// @Override +// public void init( +// Context context, +// Log log, +// JavaCompiler compiler, +// BlazeJavacStatistics.Builder statisticsBuilder) { +// super.init(context, log, compiler, statisticsBuilder); +// errWriter = log.getWriter(WriterKind.ERROR); +// implicitDependencyExtractor = +// new ImplicitDependencyExtractor( +// dependencyModule.getImplicitDependenciesMap(), +// dependencyModule.getPlatformJars()); +// checkingTreeScanner = context.get(CheckingTreeScanner.class); +// if (checkingTreeScanner == null) { +// Set platformJars = dependencyModule.getPlatformJars(); +// checkingTreeScanner = +// new CheckingTreeScanner(dependencyModule, diagnostics, missingTargets, platformJars); +// context.put(CheckingTreeScanner.class, checkingTreeScanner); +// } +// } + +// /** +// * We want to make another pass over the AST and "type-check" the usage of direct/transitive +// * dependencies after the type attribution phase. +// */ +// @Override +// public void postAttribute(Env env) { +// JavaFileObject previousSource = checkingTreeScanner.source; +// try { +// if (isAnnotationProcessorExempt(env.toplevel)) { +// return; +// } +// checkingTreeScanner.source = +// env.enclClass.sym.sourcefile != null +// ? env.enclClass.sym.sourcefile +// : env.toplevel.sourcefile; +// if (trees.add(env.tree)) { +// checkingTreeScanner.scan(env.tree); +// } +// if (toplevels.add(env.toplevel)) { +// checkingTreeScanner.scan(env.toplevel.getImports()); +// checkingTreeScanner.scan(env.toplevel.getPackage()); +// dependencyModule.addPackage(env.toplevel.packge); +// } +// } finally { +// checkingTreeScanner.source = previousSource; +// } +// } + + @Override + public void finish() { +// implicitDependencyExtractor.accumulate(context, checkingTreeScanner.getSeenClasses()); +// +// for (SjdDiagnostic diagnostic : diagnostics) { +// JavaFileObject prev = log.useSource(diagnostic.source()); +// try { +// switch (dependencyModule.getStrictJavaDeps()) { +// case ERROR: +// log.error(diagnostic.pos(), Errors.ProcMessager(diagnostic.message())); +// break; +// case WARN: +// log.warning(diagnostic.pos(), Warnings.ProcMessager(diagnostic.message())); +// break; +// case OFF: // continue below +// } +// } finally { +// log.useSource(prev); +// } +// } +// +// if (!missingTargets.isEmpty()) { +// String canonicalizedLabel = +// dependencyModule.getTargetLabel() == null +// ? null +// // we don't use the target mapping for the target, just the missing deps +// : canonicalizeTarget(dependencyModule.getTargetLabel()); +// Set canonicalizedMissing = +// missingTargets +// .stream() +// .filter(owner -> owner.label().isPresent()) +// .sorted(Comparator.comparing((JarOwner owner) -> owner.label().get())) +// // for dependencies that are missing we canonicalize and remap the target so we don't +// // suggest private build labels. +// .map(owner -> owner.withLabel(owner.label().map(label -> canonicalizeTarget(label)))) +// .collect(toImmutableSet()); +// if (dependencyModule.getStrictJavaDeps() != StrictJavaDeps.OFF) { +// errWriter.print( +// dependencyModule.getFixMessage().get(canonicalizedMissing, canonicalizedLabel)); +// dependencyModule.setHasMissingTargets(); +// } +// } + } + +// /** +// * An AST visitor that implements our strict_java_deps checks. For now, it only emits warnings for +// * types loaded from jar files provided by transitive (indirect) dependencies. Each type is +// * considered only once, so at most one warning is generated for it. +// */ +// private static class CheckingTreeScanner extends TreeScanner { +// +// private final ImmutableSet directJars; +// +// /** Strict deps diagnostics. */ +// private final List diagnostics; +// +// /** Missing targets */ +// private final Set missingTargets; +// +// /** Collect seen direct dependencies and their associated information */ +// private final Map directDependenciesMap; +// +// /** We only emit one warning/error per class symbol */ +// private final Set seenClasses = new HashSet<>(); +// +// private final Set seenTargets = new HashSet<>(); +// +// private final Set seenStrictDepsViolatingJars = new HashSet<>(); +// +// /** The set of jars on the compilation bootclasspath. */ +// private final Set platformJars; +// +// /** The current source, for diagnostics. */ +// private JavaFileObject source = null; +// +// public CheckingTreeScanner( +// DependencyModule dependencyModule, +// List diagnostics, +// Set missingTargets, +// Set platformJars) { +// this.directJars = dependencyModule.directJars(); +// this.diagnostics = diagnostics; +// this.missingTargets = missingTargets; +// this.directDependenciesMap = dependencyModule.getExplicitDependenciesMap(); +// this.platformJars = platformJars; +// } +// +// Set getSeenClasses() { +// return seenClasses; +// } +// +// /** Checks an AST node denoting a class type against direct/transitive dependencies. */ +// private void checkTypeLiteral(JCTree node, Symbol sym) { +// if (sym == null || sym.kind != Kinds.Kind.TYP) { +// return; +// } +// Path jarPath = getJarPath(sym.enclClass(), platformJars); +// +// // If this type symbol comes from a class file loaded from a jar, check +// // whether that jar was a direct dependency and error out otherwise. +// if (jarPath != null && seenClasses.add(sym.enclClass())) { +// collectExplicitDependency(jarPath, node, sym); +// } +// } +// +// /** +// * Marks the provided dependency as a direct/explicit dependency. Additionally, if +// * strict_java_deps is enabled, it emits a [strict] compiler warning/error. +// */ +// private void collectExplicitDependency(Path jarPath, JCTree node, Symbol sym) { +// // Does it make sense to emit a warning/error for this pair of (type, owner)? +// // We want to emit only one error/warning per owner. +// if (!directJars.contains(jarPath) && seenStrictDepsViolatingJars.add(jarPath)) { +// // IO cost here is fine because we only hit this path for an explicit dependency +// // _not_ in the direct jars, i.e. an error +// JarOwner owner = readJarOwnerFromManifest(jarPath); +// if (seenTargets.add(owner)) { +// // owner is of the form "//label/of:rule " where is +// // optional. +// Optional canonicalTargetName = +// owner.label().map(label -> canonicalizeTarget(label)); +// missingTargets.add(owner); +// String toolInfo = +// owner.aspect().isPresent() +// ? String.format( +// "%s wrapped in %s", canonicalTargetName.get(), owner.aspect().get()) +// : canonicalTargetName.isPresent() +// ? canonicalTargetName.get() +// : owner.jar().toString(); +// String used = +// sym.getSimpleName().contentEquals("package-info") +// ? "package " + sym.getEnclosingElement() +// : "type " + sym; +// String message = +// String.format( +// "[strict] Using %s from an indirect dependency (TOOL_INFO: \"%s\").%s", +// used, toolInfo, (owner.label().isPresent() ? " See command below **" : "")); +// diagnostics.add(SjdDiagnostic.create(node.pos, message, source)); +// } +// } +// +// if (!directDependenciesMap.containsKey(jarPath)) { +// // Also update the dependency proto +// Dependency dep = +// Dependency.newBuilder() +// // Path.toString uses the platform separator (`\` on Windows) which may not +// // match the format in params files (which currently always use `/`, see +// // bazelbuild/bazel#4108). JavaBuilder should always parse Path strings into +// // java.nio.file.Paths before comparing them. +// .setPath(jarPath.toString()) +// .setKind(Dependency.Kind.EXPLICIT) +// .build(); +// directDependenciesMap.put(jarPath, dep); +// } +// } +// +// private static JarOwner readJarOwnerFromManifest(Path jarPath) { +// try (JarFile jarFile = new JarFile(jarPath.toFile())) { +// Manifest manifest = jarFile.getManifest(); +// if (manifest == null) { +// return JarOwner.create(jarPath); +// } +// Attributes attributes = manifest.getMainAttributes(); +// String label = (String) attributes.get(TARGET_LABEL); +// if (label == null) { +// return JarOwner.create(jarPath); +// } +// String injectingRuleKind = (String) attributes.get(INJECTING_RULE_KIND); +// return JarOwner.create(jarPath, label, Optional.ofNullable(injectingRuleKind)); +// } catch (IOException e) { +// // This jar file pretty much has to exist, we just used it in the compiler. Throw unchecked. +// throw new UncheckedIOException(e); +// } +// } +// +// @Override +// public void visitMethodDef(JCTree.JCMethodDecl method) { +// if ((method.mods.flags & Flags.GENERATEDCONSTR) != 0) { +// // If this is the constructor for an anonymous inner class, refrain from checking the +// // compiler-generated method signature. Don't skip scanning the method body though, there +// // might have been an anonymous initializer which still needs to be checked. +// scan(method.body); +// } else { +// super.visitMethodDef(method); +// } +// } +// +// @Override +// public void visitVarDef(JCTree.JCVariableDecl variable) { +// scan(variable.mods); +// if (!declaredUsingVar(variable)) { +// scan(variable.vartype); +// } +// scan(variable.nameexpr); +// scan(variable.init); +// } +// +// private static boolean declaredUsingVar(JCTree.JCVariableDecl variableTree) { +// return DECLARED_USING_VAR.test(variableTree); +// } +// +// private static final Predicate DECLARED_USING_VAR = +// getDeclaredUsingVar(); +// +// private static Predicate getDeclaredUsingVar() { +// Method method; +// try { +// method = JCTree.JCVariableDecl.class.getMethod("declaredUsingVar"); +// } catch (ReflectiveOperationException e) { +// // The method in JCVariableDecl is only available in stock JDK 17. +// // There are no good options for earlier versions, short of looking at the source code and +// // re-parsing the variable declaration, which would be complicated and expensive. For now, +// // continue to enforce SJD on var for JDK < 17. +// return variableTree -> false; +// } +// return variableTree -> { +// try { +// return (boolean) method.invoke(variableTree); +// } catch (ReflectiveOperationException e) { +// throw new LinkageError(e.getMessage(), e); +// } +// }; +// } +// +// /** Visits an identifier in the AST. We only care about type symbols. */ +// @Override +// public void visitIdent(JCTree.JCIdent tree) { +// checkTypeLiteral(tree, tree.sym); +// } +// +// /** +// * Visits a field selection in the AST. We care because in some cases types may appear fully +// * qualified and only inside a field selection (e.g., "com.foo.Bar.X", we want to catch the +// * reference to Bar). +// */ +// @Override +// public void visitSelect(JCTree.JCFieldAccess tree) { +// scan(tree.selected); +// checkTypeLiteral(tree, tree.sym); +// } +// +// @Override +// public void visitLambda(JCTree.JCLambda tree) { +// if (tree.paramKind != JCTree.JCLambda.ParameterKind.IMPLICIT) { +// // don't record type uses for implicitly typed lambda parameters +// scan(tree.params); +// } +// scan(tree.body); +// } +// +// @Override +// public void visitPackageDef(JCTree.JCPackageDecl tree) { +// scan(tree.annotations); +// checkTypeLiteral(tree, tree.packge.package_info); +// } +// } +// +// /** +// * Returns true if the compilation unit contains a single top-level class generated by an exempt +// * annotation processor (according to its {@link @Generated} annotation). +// * +// *

Annotation processors are expected to never generate more than one top level class, as +// * required by the style guide. +// */ +// public boolean isAnnotationProcessorExempt(JCTree.JCCompilationUnit unit) { +// if (unit.getTypeDecls().size() != 1) { +// return false; +// } +// Symbol sym = TreeInfo.symbolFor(getOnlyElement(unit.getTypeDecls())); +// if (sym == null) { +// return false; +// } +// for (String value : getGeneratedBy(sym)) { +// if (dependencyModule.getExemptGenerators().contains(value)) { +// return true; +// } +// } +// return false; +// } +// +// private static ImmutableSet getGeneratedBy(Symbol symbol) { +// ImmutableSet.Builder suppressions = ImmutableSet.builder(); +// symbol +// .getRawAttributes() +// .stream() +// .filter( +// a -> { +// Name name = a.type.tsym.getQualifiedName(); +// return name.contentEquals("javax.annotation.Generated") +// || name.contentEquals("javax.annotation.processing.Generated"); +// }) +// .flatMap( +// a -> +// a.getElementValues() +// .entrySet() +// .stream() +// .filter(e -> e.getKey().getSimpleName().contentEquals("value")) +// .map(e -> e.getValue())) +// .forEachOrdered( +// a -> +// a.accept( +// new SimpleAnnotationValueVisitor8() { +// @Override +// public Void visitString(String s, Void aVoid) { +// suppressions.add(s); +// return super.visitString(s, aVoid); +// } +// +// @Override +// public Void visitArray(List vals, Void aVoid) { +// vals.forEach(v -> v.accept(this, null)); +// return super.visitArray(vals, aVoid); +// } +// }, +// null)); +// return suppressions.build(); +// } +// +// /** Returns the canonical version of the target name. Package private for testing. */ +// static String canonicalizeTarget(String target) { +// int colonIndex = target.indexOf(':'); +// if (colonIndex == -1) { +// // No ':' in target, nothing to do. +// return target; +// } +// int lastSlash = target.lastIndexOf('/', colonIndex); +// if (lastSlash == -1) { +// // No '/' or target is actually a filename in label format, return unmodified. +// return target; +// } +// String packageName = target.substring(lastSlash + 1, colonIndex); +// String suffix = target.substring(colonIndex + 1); +// if (packageName.equals(suffix)) { +// // target ends in "/something:something", canonicalize. +// return target.substring(0, colonIndex); +// } +// return target; +// } +// +// /** +// * Returns the name of the jar file from which the given class symbol was loaded, if available, +// * and null otherwise. Implicitly filters out jars from the compilation bootclasspath. +// * +// * @param platformJars jars on javac's bootclasspath +// */ +// public static Path getJarPath(ClassSymbol classSymbol, Set platformJars) { +// if (classSymbol == null) { +// return null; +// } +// +// // Ignore symbols that appear in the sourcepath: +// if (haveSourceForSymbol(classSymbol)) { +// return null; +// } +// +// JavaFileObject classfile = classSymbol.classfile; +// +// Path path = ImplicitDependencyExtractor.getJarPath(classfile); +// if (path == null) { +// return null; +// } +// +// // Filter out classes on bootclasspath +// if (platformJars.contains(path)) { +// return null; +// } +// +// return path; +// } +// +// /** Returns true if the given classSymbol corresponds to one of the sources being compiled. */ +// private static boolean haveSourceForSymbol(ClassSymbol classSymbol) { +// if (classSymbol.sourcefile == null) { +// return false; +// } +// +// try { +// // The classreader uses metadata to populate the symbol's sourcefile with a fake file object. +// // Call getLastModified() to check if it's a real file: +// classSymbol.sourcefile.getLastModified(); +// } catch (UnsupportedOperationException e) { +// return false; +// } +// +// return true; +// } +// +// @Override +// public boolean runOnAttributionErrors() { +// return true; +// } +} diff --git a/compiler/src/main/buildjar/com/google/devtools/build/buildjar/javac/plugins/processing/AnnotationProcessingPlugin.java b/compiler/src/main/buildjar/com/google/devtools/build/buildjar/javac/plugins/processing/AnnotationProcessingPlugin.java index b8ce988..99df06d 100644 --- a/compiler/src/main/buildjar/com/google/devtools/build/buildjar/javac/plugins/processing/AnnotationProcessingPlugin.java +++ b/compiler/src/main/buildjar/com/google/devtools/build/buildjar/javac/plugins/processing/AnnotationProcessingPlugin.java @@ -26,6 +26,10 @@ public AnnotationProcessingPlugin(AnnotationProcessingModule processingModule) { this.processingModule = processingModule; } + public AnnotationProcessingModule getProcessingModule() { + return processingModule; + } + // @Override // public void postAttribute(Env env) { // if (toplevels.add(env.toplevel)) { diff --git a/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathDirectory.java b/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathDirectory.java index 1442f94..a2be511 100644 --- a/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathDirectory.java +++ b/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathDirectory.java @@ -17,17 +17,34 @@ *******************************************************************************/ package org.eclipse.jdt.internal.compiler.batch; +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Stream; + import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.CompilationResult; import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.batch.FileSystem.ClasspathAnswer; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader; import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException; import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationProvider; import org.eclipse.jdt.internal.compiler.env.AccessRuleSet; import org.eclipse.jdt.internal.compiler.env.IModule; -import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; import org.eclipse.jdt.internal.compiler.parser.Parser; @@ -36,23 +53,6 @@ import org.eclipse.jdt.internal.compiler.problem.ProblemReporter; import org.eclipse.jdt.internal.compiler.util.Util; -import java.io.File; -import java.io.FilenameFilter; -import java.io.IOException; -import java.nio.file.FileSystems; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.HashSet; -import java.util.Hashtable; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Stream; - @SuppressWarnings({"rawtypes", "unchecked"}) public class ClasspathDirectory extends ClasspathLocation { @@ -123,7 +123,7 @@ boolean doesFileExist(String fileName, String qualifiedPackageName) { public List fetchLinkedJars(FileSystem.ClasspathSectionProblemReporter problemReporter) { return null; } -private NameEnvironmentAnswer findClassInternal(char[] typeName, String qualifiedPackageName, String qualifiedBinaryFileName, boolean asBinaryOnly) { +private ClasspathAnswer findClassInternal(char[] typeName, String qualifiedPackageName, String qualifiedBinaryFileName, boolean asBinaryOnly) { if (!isPackage(qualifiedPackageName, null)) return null; // most common case TODO(SHMOD): use module name from this.module? String fileName = new String(typeName); boolean binaryExists = ((this.mode & BINARY) != 0) && doesFileExist(fileName + SUFFIX_STRING_class, qualifiedPackageName); @@ -133,14 +133,14 @@ private NameEnvironmentAnswer findClassInternal(char[] typeName, String qualifie CompilationUnit unit = new CompilationUnit(null, fullSourcePath, this.encoding, this.destinationPath); unit.module = this.module == null ? null : this.module.name(); if (!binaryExists) - return new NameEnvironmentAnswer(unit, - fetchAccessRestriction(qualifiedBinaryFileName)); + return new ClasspathAnswer(unit, + fetchAccessRestriction(qualifiedBinaryFileName), this); String fullBinaryPath = this.path + qualifiedBinaryFileName; long binaryModified = new File(fullBinaryPath).lastModified(); long sourceModified = new File(fullSourcePath).lastModified(); if (sourceModified > binaryModified) - return new NameEnvironmentAnswer(unit, - fetchAccessRestriction(qualifiedBinaryFileName)); + return new ClasspathAnswer(unit, + fetchAccessRestriction(qualifiedBinaryFileName), this); } if (binaryExists) { try { @@ -154,10 +154,11 @@ private NameEnvironmentAnswer findClassInternal(char[] typeName, String qualifie } if (reader != null) { char[] modName = reader.moduleName != null ? reader.moduleName : this.module != null ? this.module.name() : null; - return new NameEnvironmentAnswer( + return new ClasspathAnswer( reader, fetchAccessRestriction(qualifiedBinaryFileName), - modName); + modName, + this); } } catch (IOException | ClassFormatException e) { // treat as if file is missing @@ -165,7 +166,7 @@ private NameEnvironmentAnswer findClassInternal(char[] typeName, String qualifie } return null; } -public NameEnvironmentAnswer findSecondaryInClass(char[] typeName, String qualifiedPackageName, String qualifiedBinaryFileName) { +public ClasspathAnswer findSecondaryInClass(char[] typeName, String qualifiedPackageName, String qualifiedBinaryFileName) { //"package-info" is a reserved class name and can never be a secondary type (it is much faster to stop the search here). if(CharOperation.equals(TypeConstants.PACKAGE_INFO_NAME, typeName)) { return null; @@ -187,11 +188,11 @@ public boolean hasAnnotationFileFor(String qualifiedTypeName) { return false; } @Override -public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName) { +public ClasspathAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName) { return findClass(typeName, qualifiedPackageName, moduleName, qualifiedBinaryFileName, false); } @Override -public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName, boolean asBinaryOnly) { +public ClasspathAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName, boolean asBinaryOnly) { if (File.separatorChar == '/') return findClassInternal(typeName, qualifiedPackageName, qualifiedBinaryFileName, asBinaryOnly); @@ -241,7 +242,7 @@ private Hashtable getSecondaryTypes(String qualifiedPackageName) } return packageEntry; } -private NameEnvironmentAnswer findSourceSecondaryType(String typeName, String qualifiedPackageName, String qualifiedBinaryFileName) { +private ClasspathAnswer findSourceSecondaryType(String typeName, String qualifiedPackageName, String qualifiedBinaryFileName) { if (this.packageSecondaryTypes == null) this.packageSecondaryTypes = new Hashtable<>(); Hashtable packageEntry = this.packageSecondaryTypes.get(qualifiedPackageName); @@ -250,9 +251,9 @@ private NameEnvironmentAnswer findSourceSecondaryType(String typeName, String qu this.packageSecondaryTypes.put(qualifiedPackageName, packageEntry); } String fileName = packageEntry.get(typeName); - return fileName != null ? new NameEnvironmentAnswer(new CompilationUnit(null, + return fileName != null ? new ClasspathAnswer(new CompilationUnit(null, fileName, this.encoding, this.destinationPath), - fetchAccessRestriction(qualifiedBinaryFileName)) : null; + fetchAccessRestriction(qualifiedBinaryFileName), this) : null; } diff --git a/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathJar.java b/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathJar.java index f04669c..5a96fe8 100644 --- a/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathJar.java +++ b/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathJar.java @@ -30,16 +30,16 @@ import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.batch.FileSystem.Classpath; +import org.eclipse.jdt.internal.compiler.batch.FileSystem.ClasspathAnswer; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader; import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException; import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationDecorator; import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationProvider; import org.eclipse.jdt.internal.compiler.env.AccessRuleSet; -import org.eclipse.jdt.internal.compiler.env.IModule; -import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer; import org.eclipse.jdt.internal.compiler.env.IBinaryType; -import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; +import org.eclipse.jdt.internal.compiler.env.IModule; import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.ExternalAnnotationStatus; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; import org.eclipse.jdt.internal.compiler.util.ManifestAnalyzer; import org.eclipse.jdt.internal.compiler.util.SuffixConstants; import org.eclipse.jdt.internal.compiler.util.Util; @@ -108,11 +108,11 @@ public List fetchLinkedJars(FileSystem.ClasspathSectionProblemReporte } } @Override -public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName) { +public ClasspathAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName) { return findClass(typeName, qualifiedPackageName, moduleName, qualifiedBinaryFileName, false); } @Override -public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName, boolean asBinaryOnly) { +public ClasspathAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName, boolean asBinaryOnly) { if (!isPackage(qualifiedPackageName, moduleName)) return null; // most common case @@ -147,7 +147,7 @@ public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageN // location is configured for external annotations, but no .eea found, decorate in order to answer NO_EEA_FILE: reader = new ExternalAnnotationDecorator(reader, null); } - return new NameEnvironmentAnswer(reader, fetchAccessRestriction(qualifiedBinaryFileName), modName); + return new ClasspathAnswer(reader, fetchAccessRestriction(qualifiedBinaryFileName), modName, this); } } catch (ClassFormatException | IOException e) { // treat as if class file is missing diff --git a/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathJep247.java b/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathJep247.java index 19346c2..1d7f459 100644 --- a/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathJep247.java +++ b/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathJep247.java @@ -32,12 +32,12 @@ import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.batch.FileSystem.Classpath; +import org.eclipse.jdt.internal.compiler.batch.FileSystem.ClasspathAnswer; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader; import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException; import org.eclipse.jdt.internal.compiler.env.AccessRuleSet; import org.eclipse.jdt.internal.compiler.env.IModule; -import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.util.CtSym; import org.eclipse.jdt.internal.compiler.util.JRTUtil; @@ -67,11 +67,11 @@ public List fetchLinkedJars(FileSystem.ClasspathSectionProblemReporte return null; } @Override - public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName) { + public ClasspathAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName) { return findClass(typeName, qualifiedPackageName, moduleName, qualifiedBinaryFileName, false); } @Override - public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName, boolean asBinaryOnly) { + public ClasspathAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName, boolean asBinaryOnly) { if (!isPackage(qualifiedPackageName, moduleName)) return null; // most common case @@ -95,7 +95,7 @@ public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageN if (content != null) { reader = new ClassFileReader(content, qualifiedBinaryFileName.toCharArray()); char[] modName = moduleName != null ? moduleName.toCharArray() : null; - return new NameEnvironmentAnswer(reader, fetchAccessRestriction(qualifiedBinaryFileName), modName); + return new ClasspathAnswer(reader, fetchAccessRestriction(qualifiedBinaryFileName), modName, this); } } catch (ClassFormatException | IOException e) { // continue diff --git a/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathJep247Jdk12.java b/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathJep247Jdk12.java index 67efa5e..6fb12d2 100644 --- a/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathJep247Jdk12.java +++ b/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathJep247Jdk12.java @@ -32,12 +32,12 @@ import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.batch.FileSystem.Classpath; +import org.eclipse.jdt.internal.compiler.batch.FileSystem.ClasspathAnswer; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader; import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException; import org.eclipse.jdt.internal.compiler.env.AccessRuleSet; import org.eclipse.jdt.internal.compiler.env.IModule; -import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer; import org.eclipse.jdt.internal.compiler.util.CtSym; import org.eclipse.jdt.internal.compiler.util.JRTUtil; import org.eclipse.jdt.internal.compiler.util.Util; @@ -55,11 +55,11 @@ public List fetchLinkedJars(FileSystem.ClasspathSectionProblemReporte return null; } @Override - public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName) { + public ClasspathAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName) { return findClass(typeName, qualifiedPackageName, moduleName, qualifiedBinaryFileName, false); } @Override - public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName, boolean asBinaryOnly) { + public ClasspathAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName, boolean asBinaryOnly) { if (!isPackage(qualifiedPackageName, moduleName)) return null; // most common case @@ -98,7 +98,7 @@ public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageN if (content != null) { reader = new ClassFileReader(content, qualifiedBinaryFileName.toCharArray()); char[] modName = moduleName != null ? moduleName.toCharArray() : foundModName; - return new NameEnvironmentAnswer(reader, fetchAccessRestriction(qualifiedBinaryFileName), modName); + return new ClasspathAnswer(reader, fetchAccessRestriction(qualifiedBinaryFileName), modName, this); } } catch (ClassFormatException | IOException e) { // continue @@ -166,7 +166,7 @@ public void loadModules() { if (!rel.contains(this.releaseInHex)) { continue; } - Files.walkFileTree(subdir, Collections.EMPTY_SET, 2, new FileVisitor() { + Files.walkFileTree(subdir, Collections.emptySet(), 2, new FileVisitor() { @Override public FileVisitResult preVisitDirectory(java.nio.file.Path dir, BasicFileAttributes attrs) diff --git a/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathJmod.java b/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathJmod.java index 7334d2b..2121857 100644 --- a/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathJmod.java +++ b/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathJmod.java @@ -23,14 +23,14 @@ import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.batch.FileSystem.Classpath; +import org.eclipse.jdt.internal.compiler.batch.FileSystem.ClasspathAnswer; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader; import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException; import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationDecorator; import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationProvider; import org.eclipse.jdt.internal.compiler.env.AccessRuleSet; -import org.eclipse.jdt.internal.compiler.env.IModule; -import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer; import org.eclipse.jdt.internal.compiler.env.IBinaryType; +import org.eclipse.jdt.internal.compiler.env.IModule; import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.ExternalAnnotationStatus; import org.eclipse.jdt.internal.compiler.util.SuffixConstants; import org.eclipse.jdt.internal.compiler.util.Util; @@ -51,7 +51,7 @@ public List fetchLinkedJars(FileSystem.ClasspathSectionProblemReporte return null; } @Override -public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName, boolean asBinaryOnly) { +public ClasspathAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName, boolean asBinaryOnly) { if (!isPackage(qualifiedPackageName, moduleName)) return null; // most common case @@ -87,7 +87,7 @@ public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageN // location is configured for external annotations, but no .eea found, decorate in order to answer NO_EEA_FILE: reader = new ExternalAnnotationDecorator(reader, null); } - return new NameEnvironmentAnswer(reader, fetchAccessRestriction(qualifiedBinaryFileName), modName); + return new ClasspathAnswer(reader, fetchAccessRestriction(qualifiedBinaryFileName), modName, this); } } catch (ClassFormatException | IOException e) { // treat as if class file is missing diff --git a/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathJrt.java b/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathJrt.java index 17e47e6..6633916 100644 --- a/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathJrt.java +++ b/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathJrt.java @@ -15,7 +15,6 @@ import java.io.File; import java.io.IOException; - import java.nio.file.FileVisitResult; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; @@ -31,15 +30,15 @@ import java.util.zip.ZipFile; import org.eclipse.jdt.core.compiler.CharOperation; +import org.eclipse.jdt.internal.compiler.batch.FileSystem.ClasspathAnswer; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader; import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException; import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationDecorator; import org.eclipse.jdt.internal.compiler.env.AccessRuleSet; import org.eclipse.jdt.internal.compiler.env.IBinaryType; import org.eclipse.jdt.internal.compiler.env.IModule; -import org.eclipse.jdt.internal.compiler.env.IMultiModuleEntry; -import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer; import org.eclipse.jdt.internal.compiler.env.IModule.IPackageExport; +import org.eclipse.jdt.internal.compiler.env.IMultiModuleEntry; import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.ExternalAnnotationStatus; import org.eclipse.jdt.internal.compiler.util.JRTUtil; import org.eclipse.jdt.internal.compiler.util.SuffixConstants; @@ -76,11 +75,11 @@ public boolean hasCompilationUnit(String qualifiedPackageName, String moduleName return JRTUtil.hasCompilationUnit(this.file, qualifiedPackageName, moduleName); } @Override - public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName) { + public ClasspathAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName) { return findClass(typeName, qualifiedPackageName, moduleName, qualifiedBinaryFileName, false); } @Override - public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName, boolean asBinaryOnly) { + public ClasspathAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName, boolean asBinaryOnly) { if (!isPackage(qualifiedPackageName, moduleName)) return null; // most common case @@ -111,7 +110,7 @@ public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageN char[] answerModuleName = reader.getModule(); if (answerModuleName == null && moduleName != null) answerModuleName = moduleName.toCharArray(); - return new NameEnvironmentAnswer(reader, fetchAccessRestriction(qualifiedBinaryFileName), answerModuleName); + return new ClasspathAnswer(reader, fetchAccessRestriction(qualifiedBinaryFileName), answerModuleName, this); } } catch (ClassFormatException | IOException e) { // treat as if class file is missing diff --git a/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathJsr199.java b/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathJsr199.java index 725dce6..3a37160 100644 --- a/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathJsr199.java +++ b/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathJsr199.java @@ -31,10 +31,10 @@ import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.batch.FileSystem.Classpath; +import org.eclipse.jdt.internal.compiler.batch.FileSystem.ClasspathAnswer; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader; import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException; import org.eclipse.jdt.internal.compiler.env.IModule; -import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer; @SuppressWarnings({ "rawtypes", "unchecked" }) public class ClasspathJsr199 extends ClasspathLocation { @@ -77,7 +77,7 @@ public List fetchLinkedJars(FileSystem.ClasspathSectionProblemReporter problemRe } @Override - public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, + public ClasspathAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String aQualifiedBinaryFileName, boolean asBinaryOnly) { if (this.jrt != null) { return this.jrt.findClass(typeName, qualifiedPackageName, moduleName, aQualifiedBinaryFileName, asBinaryOnly); @@ -102,7 +102,7 @@ public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageN try (InputStream inputStream = jfo.openInputStream()) { ClassFileReader reader = ClassFileReader.read(inputStream, qualifiedBinaryFileName); if (reader != null) { - return new NameEnvironmentAnswer(reader, fetchAccessRestriction(qualifiedBinaryFileName)); + return new ClasspathAnswer(reader, fetchAccessRestriction(qualifiedBinaryFileName), this); } } } catch (ClassFormatException e) { @@ -272,7 +272,7 @@ public IModule getModule(char[] name) { } @Override - public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageName, + public ClasspathAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName) { // return findClass(typeName, qualifiedPackageName, moduleName, qualifiedBinaryFileName, false); diff --git a/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathMultiReleaseJar.java b/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathMultiReleaseJar.java index cf8d7b6..0b694ee 100644 --- a/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathMultiReleaseJar.java +++ b/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathMultiReleaseJar.java @@ -16,12 +16,12 @@ import java.util.HashSet; import java.util.zip.ZipEntry; +import org.eclipse.jdt.internal.compiler.batch.FileSystem.ClasspathAnswer; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader; import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException; import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationDecorator; import org.eclipse.jdt.internal.compiler.env.AccessRuleSet; import org.eclipse.jdt.internal.compiler.env.IBinaryType; -import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer; import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.ExternalAnnotationStatus; import org.eclipse.jdt.internal.compiler.util.SuffixConstants; import org.eclipse.jdt.internal.compiler.util.Util; @@ -111,7 +111,7 @@ public FileVisitResult postVisitDirectory(java.nio.file.Path dir, IOException ex return singletonModuleNameIf(this.packageCache.contains(qualifiedPackageName)); } @Override - public NameEnvironmentAnswer findClass(char[] binaryFileName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName, boolean asBinaryOnly) { + public ClasspathAnswer findClass(char[] binaryFileName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName, boolean asBinaryOnly) { if (!isPackage(qualifiedPackageName, moduleName)) return null; // most common case if (this.releasePath != null) { try { @@ -152,10 +152,11 @@ public NameEnvironmentAnswer findClass(char[] binaryFileName, String qualifiedPa reader = new ExternalAnnotationDecorator(reader, null); } if (this.accessRuleSet == null) - return new NameEnvironmentAnswer(reader, null, modName); - return new NameEnvironmentAnswer(reader, + return new ClasspathAnswer(reader, null, modName, this); + return new ClasspathAnswer(reader, this.accessRuleSet.getViolatedRestriction(fileNameWithoutExtension.toCharArray()), - modName); + modName, + this); } } catch (IOException | ClassFormatException e) { // treat as if class file is missing diff --git a/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathSourceJar.java b/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathSourceJar.java index d906d7b..34a7f04 100644 --- a/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathSourceJar.java +++ b/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/ClasspathSourceJar.java @@ -14,12 +14,12 @@ package org.eclipse.jdt.internal.compiler.batch; import java.io.File; -import java.io.InputStream; import java.io.IOException; +import java.io.InputStream; import java.util.zip.ZipEntry; +import org.eclipse.jdt.internal.compiler.batch.FileSystem.ClasspathAnswer; import org.eclipse.jdt.internal.compiler.env.AccessRuleSet; -import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer; import org.eclipse.jdt.internal.compiler.util.Util; public class ClasspathSourceJar extends ClasspathJar { @@ -33,7 +33,7 @@ public ClasspathSourceJar(File file, boolean closeZipFileAtEnd, } @Override - public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName, boolean asBinaryOnly) { + public ClasspathAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName, boolean asBinaryOnly) { if (!isPackage(qualifiedPackageName, moduleName)) return null; // most common case @@ -55,9 +55,10 @@ public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageN this.encoding, this.destinationPath); compilationUnit.module = this.module == null ? null : this.module.name(); - return new NameEnvironmentAnswer( + return new ClasspathAnswer( compilationUnit, - fetchAccessRestriction(qualifiedBinaryFileName)); + fetchAccessRestriction(qualifiedBinaryFileName), + this); } catch (IOException e) { // treat as if source file is missing } diff --git a/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/FileSystem.java b/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/FileSystem.java index f8fa2b7..e2b0e23 100644 --- a/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/FileSystem.java +++ b/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/FileSystem.java @@ -39,19 +39,24 @@ import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader; import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationDecorator; +import org.eclipse.jdt.internal.compiler.env.AccessRestriction; import org.eclipse.jdt.internal.compiler.env.AccessRuleSet; -import org.eclipse.jdt.internal.compiler.env.IModulePathEntry; +import org.eclipse.jdt.internal.compiler.env.IBinaryType; +import org.eclipse.jdt.internal.compiler.env.ICompilationUnit; import org.eclipse.jdt.internal.compiler.env.IModule; import org.eclipse.jdt.internal.compiler.env.IModuleAwareNameEnvironment; +import org.eclipse.jdt.internal.compiler.env.IModulePathEntry; +import org.eclipse.jdt.internal.compiler.env.ISourceType; +import org.eclipse.jdt.internal.compiler.env.IUpdatableModule; +import org.eclipse.jdt.internal.compiler.env.IUpdatableModule.UpdateKind; +import org.eclipse.jdt.internal.compiler.env.IUpdatableModule.UpdatesByKind; import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer; +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.lookup.ModuleBinding; +import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; import org.eclipse.jdt.internal.compiler.parser.Parser; import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory; import org.eclipse.jdt.internal.compiler.problem.ProblemReporter; -import org.eclipse.jdt.internal.compiler.env.IUpdatableModule; -import org.eclipse.jdt.internal.compiler.env.IUpdatableModule.UpdateKind; -import org.eclipse.jdt.internal.compiler.env.IUpdatableModule.UpdatesByKind; -import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.util.JRTUtil; import org.eclipse.jdt.internal.compiler.util.SuffixConstants; import org.eclipse.jdt.internal.compiler.util.Util; @@ -61,6 +66,43 @@ public class FileSystem implements IModuleAwareNameEnvironment, SuffixConstants // Keep the type as ArrayList and not List as there are clients that are already written to expect ArrayList. public static ArrayList EMPTY_CLASSPATH = new ArrayList<>(); + public static class ClasspathAnswer extends NameEnvironmentAnswer { + + public final Classpath source; + + public ClasspathAnswer(IBinaryType binaryType, AccessRestriction accessRestriction, char[] module, Classpath source) { + super(binaryType, accessRestriction, module); + this.source = source; + } + + public ClasspathAnswer(IBinaryType binaryType, AccessRestriction accessRestriction, Classpath source) { + super(binaryType, accessRestriction); + this.source = source; + } + + public ClasspathAnswer(ICompilationUnit compilationUnit, AccessRestriction accessRestriction, char[] module, Classpath source) { + super(compilationUnit, accessRestriction, module); + this.source = source; + } + + public ClasspathAnswer(ICompilationUnit compilationUnit, AccessRestriction accessRestriction, Classpath source) { + super(compilationUnit, accessRestriction); + this.source = source; + } + + public ClasspathAnswer(ISourceType[] sourceTypes, AccessRestriction accessRestriction, + String externalAnnotationPath, char[] module, Classpath source) { + super(sourceTypes, accessRestriction, externalAnnotationPath, module); + this.source = source; + } + + public ClasspathAnswer(ReferenceBinding binding, ModuleBinding module, Classpath source) { + super(binding, module); + this.source = source; + } + + } + /** * A Classpath, even though an IModuleLocation, can represent a plain * classpath location too. The FileSystem tells the Classpath whether to behave as a module or regular class @@ -70,8 +112,8 @@ public class FileSystem implements IModuleAwareNameEnvironment, SuffixConstants */ public interface Classpath extends IModulePathEntry { char[][][] findTypeNames(String qualifiedPackageName, String moduleName); - NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName); - NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName, boolean asBinaryOnly); + ClasspathAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName); + ClasspathAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName, boolean asBinaryOnly); boolean isPackage(String qualifiedPackageName, /*@Nullable*/String moduleName); default boolean hasModule() { return getModule() != null; } default boolean hasCUDeclaringPackage(String qualifiedPackageName, Function pkgNameExtractor) { @@ -169,6 +211,8 @@ public static ArrayList normalize(ArrayList classpaths) { private static HashMap JRT_CLASSPATH_CACHE = null; protected Map moduleLocations = new HashMap<>(); + public Consumer nameEnvironmentListener = null; // listener for findType* methods + /** Tasks resulting from --add-reads or --add-exports command line options. */ Map moduleUpdates = new HashMap<>(); static boolean isJRE12Plus = false; @@ -416,8 +460,8 @@ private static String convertPathSeparators(String path) { ? path.replace('\\', '/') : path.replace('/', '\\'); } -private NameEnvironmentAnswer findClass(String qualifiedTypeName, char[] typeName, boolean asBinaryOnly, /*NonNull*/char[] moduleName) { - NameEnvironmentAnswer answer = internalFindClass(qualifiedTypeName, typeName, asBinaryOnly, moduleName); +private ClasspathAnswer findClass(String qualifiedTypeName, char[] typeName, boolean asBinaryOnly, /*NonNull*/char[] moduleName) { + ClasspathAnswer answer = internalFindClass(qualifiedTypeName, typeName, asBinaryOnly, moduleName); if (this.annotationsFromClasspath && answer != null && answer.getBinaryType() instanceof ClassFileReader) { for (int i = 0, length = this.classpaths.length; i < length; i++) { Classpath classpathEntry = this.classpaths[i]; @@ -432,6 +476,7 @@ private NameEnvironmentAnswer findClass(String qualifiedTypeName, char[] typeNam } answer.setBinaryType(ExternalAnnotationDecorator.create(answer.getBinaryType(), classpathEntry.getPath(), qualifiedTypeName, zip)); + if (nameEnvironmentListener != null) nameEnvironmentListener.accept(answer); return answer; } catch (IOException e) { // ignore broken entry, keep searching @@ -446,9 +491,10 @@ private NameEnvironmentAnswer findClass(String qualifiedTypeName, char[] typeNam // globally configured (annotationsFromClasspath), but no .eea found, decorate in order to answer NO_EEA_FILE: answer.setBinaryType(new ExternalAnnotationDecorator(answer.getBinaryType(), null)); } + if (nameEnvironmentListener != null && answer != null) nameEnvironmentListener.accept(answer); return answer; } -private NameEnvironmentAnswer internalFindClass(String qualifiedTypeName, char[] typeName, boolean asBinaryOnly, /*NonNull*/char[] moduleName) { +private ClasspathAnswer internalFindClass(String qualifiedTypeName, char[] typeName, boolean asBinaryOnly, /*NonNull*/char[] moduleName) { if (this.knownFileNames.contains(qualifiedTypeName)) return null; // looking for a file which we know was provided at the beginning of the compilation String qualifiedBinaryFileName = qualifiedTypeName + SUFFIX_STRING_class; @@ -470,12 +516,12 @@ private NameEnvironmentAnswer internalFindClass(String qualifiedTypeName, char[] return null; } String qp2 = File.separatorChar == '/' ? qualifiedPackageName : qualifiedPackageName.replace('/', File.separatorChar); - NameEnvironmentAnswer suggestedAnswer = null; + ClasspathAnswer suggestedAnswer = null; if (qualifiedPackageName == qp2) { for (int i = 0, length = this.classpaths.length; i < length; i++) { if (!strategy.matches(this.classpaths[i], Classpath::hasModule)) continue; - NameEnvironmentAnswer answer = this.classpaths[i].findClass(typeName, qualifiedPackageName, null, qualifiedBinaryFileName, asBinaryOnly); + ClasspathAnswer answer = this.classpaths[i].findClass(typeName, qualifiedPackageName, null, qualifiedBinaryFileName, asBinaryOnly); if (answer != null) { if (answer.moduleName() != null && !this.moduleLocations.containsKey(String.valueOf(answer.moduleName()))) continue; // type belongs to an unobservable module @@ -493,7 +539,7 @@ private NameEnvironmentAnswer internalFindClass(String qualifiedTypeName, char[] Classpath p = this.classpaths[i]; if (!strategy.matches(p, Classpath::hasModule)) continue; - NameEnvironmentAnswer answer = !(p instanceof ClasspathDirectory) + ClasspathAnswer answer = !(p instanceof ClasspathDirectory) ? p.findClass(typeName, qualifiedPackageName, null, qualifiedBinaryFileName, asBinaryOnly) : p.findClass(typeName, qp2, null, qb2, asBinaryOnly); if (answer != null) { @@ -512,7 +558,7 @@ private NameEnvironmentAnswer internalFindClass(String qualifiedTypeName, char[] } @Override -public NameEnvironmentAnswer findType(char[][] compoundName, char[] moduleName) { +public ClasspathAnswer findType(char[][] compoundName, char[] moduleName) { if (compoundName != null) return findClass( new String(CharOperation.concatWith(compoundName, '/')), @@ -565,7 +611,7 @@ public char[][][] findTypeNames(char[][] packageName) { } @Override -public NameEnvironmentAnswer findType(char[] typeName, char[][] packageName, char[] moduleName) { +public ClasspathAnswer findType(char[] typeName, char[][] packageName, char[] moduleName) { if (typeName != null) return findClass( new String(CharOperation.concatWith(packageName, typeName, '/')), diff --git a/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/Main.java b/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/Main.java index 1c1fd66..d60a3bb 100644 --- a/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/Main.java +++ b/compiler/src/main/ecj/org/eclipse/jdt/internal/compiler/batch/Main.java @@ -3870,7 +3870,7 @@ protected void handleWarningToken(String token, boolean isEnabling) { protected void handleErrorToken(String token, boolean isEnabling) { handleErrorOrWarningToken(token, isEnabling, ProblemSeverities.Error); } -private void setSeverity(String compilerOptions, int severity, boolean isEnabling) { +protected void setSeverity(String compilerOptions, int severity, boolean isEnabling) { if (isEnabling) { switch(severity) { case ProblemSeverities.Error :