diff options
Diffstat (limited to 'java/com/google/devtools/build/android/desugar/Desugar.java')
-rw-r--r-- | java/com/google/devtools/build/android/desugar/Desugar.java | 265 |
1 files changed, 193 insertions, 72 deletions
diff --git a/java/com/google/devtools/build/android/desugar/Desugar.java b/java/com/google/devtools/build/android/desugar/Desugar.java index 5d3df4a..c176f9c 100644 --- a/java/com/google/devtools/build/android/desugar/Desugar.java +++ b/java/com/google/devtools/build/android/desugar/Desugar.java @@ -17,7 +17,6 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.devtools.build.android.desugar.LambdaClassMaker.LAMBDA_METAFACTORY_DUMPER_PROPERTY; -import static java.nio.charset.StandardCharsets.ISO_8859_1; import com.google.auto.value.AutoValue; import com.google.common.annotations.VisibleForTesting; @@ -28,17 +27,24 @@ import com.google.common.io.ByteStreams; import com.google.common.io.Closer; import com.google.devtools.build.android.Converters.ExistingPathConverter; import com.google.devtools.build.android.Converters.PathConverter; -import com.google.devtools.build.android.desugar.CoreLibraryRewriter.UnprefixingClassWriter; +import com.google.devtools.build.android.desugar.io.CoreLibraryRewriter; +import com.google.devtools.build.android.desugar.io.CoreLibraryRewriter.UnprefixingClassWriter; +import com.google.devtools.build.android.desugar.io.HeaderClassLoader; +import com.google.devtools.build.android.desugar.io.IndexedInputs; +import com.google.devtools.build.android.desugar.io.InputFileProvider; +import com.google.devtools.build.android.desugar.io.OutputFileProvider; +import com.google.devtools.build.android.desugar.io.ThrowingClassLoader; import com.google.devtools.common.options.Option; import com.google.devtools.common.options.OptionDocumentationCategory; import com.google.devtools.common.options.OptionEffectTag; -import com.google.devtools.common.options.Options; import com.google.devtools.common.options.OptionsBase; -import com.google.errorprone.annotations.MustBeClosed; +import com.google.devtools.common.options.OptionsParser; +import com.google.devtools.common.options.ShellQuotedParamsFilePreProcessor; import java.io.IOError; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; +import java.nio.file.FileSystems; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; @@ -195,6 +201,15 @@ class Desugar { public boolean tolerateMissingDependencies; @Option( + name = "desugar_supported_core_libs", + defaultValue = "false", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = "Enable core library desugaring, which requires configuration with related flags." + ) + public boolean desugarCoreLibs; + + @Option( name = "desugar_interface_method_bodies_if_needed", defaultValue = "true", category = "misc", @@ -248,6 +263,51 @@ class Desugar { ) public boolean coreLibrary; + /** Type prefixes that we'll move to a custom package. */ + @Option( + name = "rewrite_core_library_prefix", + defaultValue = "", // ignored + allowMultiple = true, + documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = "Assume the given java.* prefixes are desugared." + ) + public List<String> rewriteCoreLibraryPrefixes; + + /** Interfaces whose default and static interface methods we'll emulate. */ + @Option( + name = "emulate_core_library_interface", + defaultValue = "", // ignored + allowMultiple = true, + documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = "Assume the given java.* interfaces are emulated." + ) + public List<String> emulateCoreLibraryInterfaces; + + /** Members that we will retarget to the given new owner. */ + @Option( + name = "retarget_core_library_member", + defaultValue = "", // ignored + allowMultiple = true, + documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = "Method invocations to retarget, given as \"class/Name#member->new/class/Name\". " + + "The new owner is blindly assumed to exist." + ) + public List<String> retargetCoreLibraryMembers; + + /** Members not to rewrite. */ + @Option( + name = "dont_rewrite_core_library_invocation", + defaultValue = "", // ignored + allowMultiple = true, + documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = "Method invocations not to rewrite, given as \"class/Name#method\"." + ) + public List<String> dontTouchCoreLibraryMembers; + /** Set to work around b/62623509 with JaCoCo versions prior to 0.7.9. */ // TODO(kmb): Remove when Android Studio doesn't need it anymore (see b/37116789) @Option( @@ -265,7 +325,7 @@ class Desugar { private final DesugarOptions options; private final CoreLibraryRewriter rewriter; private final LambdaClassMaker lambdas; - private final GeneratedClassStore store; + private final GeneratedClassStore store = new GeneratedClassStore(); private final Set<String> visitedExceptionTypes = new HashSet<>(); /** The counter to record the times of try-with-resources desugaring is invoked. */ private final AtomicInteger numOfTryWithResourcesInvoked = new AtomicInteger(); @@ -282,7 +342,6 @@ class Desugar { this.options = options; this.rewriter = new CoreLibraryRewriter(options.coreLibrary ? "__desugar__/" : ""); this.lambdas = new LambdaClassMaker(dumpDirectory); - this.store = new GeneratedClassStore(); this.outputJava7 = options.minSdkVersion < 24; this.allowDefaultMethods = options.desugarInterfaceMethodBodiesIfNeeded || options.minSdkVersion >= 24; @@ -332,8 +391,8 @@ class Desugar { Files.isDirectory(inputPath) || !Files.isDirectory(outputPath), "Input jar file requires an output jar file"); - try (OutputFileProvider outputFileProvider = toOutputFileProvider(outputPath); - InputFileProvider inputFiles = toInputFileProvider(inputPath)) { + try (OutputFileProvider outputFileProvider = OutputFileProvider.create(outputPath); + InputFileProvider inputFiles = InputFileProvider.open(inputPath)) { DependencyCollector depsCollector = createDepsCollector(); IndexedInputs indexedInputFiles = new IndexedInputs(ImmutableList.of(inputFiles)); // Prepend classpath with input file itself so LambdaDesugaring can load classes with @@ -358,6 +417,17 @@ class Desugar { ImmutableSet.Builder<String> interfaceLambdaMethodCollector = ImmutableSet.builder(); ClassVsInterface interfaceCache = new ClassVsInterface(classpathReader); + CoreLibrarySupport coreLibrarySupport = + options.desugarCoreLibs + ? new CoreLibrarySupport( + rewriter, + loader, + options.rewriteCoreLibraryPrefixes, + options.emulateCoreLibraryInterfaces, + options.retargetCoreLibraryMembers, + options.dontTouchCoreLibraryMembers) + : null; + desugarClassesInInput( inputFiles, outputFileProvider, @@ -365,6 +435,7 @@ class Desugar { classpathReader, depsCollector, bootclasspathReader, + coreLibrarySupport, interfaceCache, interfaceLambdaMethodCollector); @@ -374,11 +445,14 @@ class Desugar { classpathReader, depsCollector, bootclasspathReader, + coreLibrarySupport, interfaceCache, interfaceLambdaMethodCollector.build(), bridgeMethodReader); - desugarAndWriteGeneratedClasses(outputFileProvider, bootclasspathReader); + desugarAndWriteGeneratedClasses( + outputFileProvider, loader, bootclasspathReader, coreLibrarySupport); + copyThrowableExtensionClass(outputFileProvider); byte[] depsInfo = depsCollector.toByteArray(); @@ -394,7 +468,7 @@ class Desugar { } /** - * Returns a dependency collector for use with a single input Jar. If + * Returns a dependency collector for use with a single input Jar. If * {@link DesugarOptions#emitDependencyMetadata} is set, this method instantiates the collector * reflectively to allow compiling and using the desugar tool without this mechanism. */ @@ -445,19 +519,20 @@ class Desugar { @Nullable ClassReaderFactory classpathReader, DependencyCollector depsCollector, ClassReaderFactory bootclasspathReader, + @Nullable CoreLibrarySupport coreLibrarySupport, ClassVsInterface interfaceCache, ImmutableSet.Builder<String> interfaceLambdaMethodCollector) throws IOException { - for (String filename : inputFiles) { - if (OutputFileProvider.DESUGAR_DEPS_FILENAME.equals(filename)) { + for (String inputFilename : inputFiles) { + if (OutputFileProvider.DESUGAR_DEPS_FILENAME.equals(inputFilename)) { // TODO(kmb): rule out that this happens or merge input file with what's in depsCollector continue; // skip as we're writing a new file like this at the end or don't want it } - try (InputStream content = inputFiles.getInputStream(filename)) { + try (InputStream content = inputFiles.getInputStream(inputFilename)) { // We can write classes uncompressed since they need to be converted to .dex format // for Android anyways. Resources are written as they were in the input jar to avoid // any danger of accidentally uncompressed resources ending up in an .apk. - if (filename.endsWith(".class")) { + if (inputFilename.endsWith(".class")) { ClassReader reader = rewriter.reader(content); UnprefixingClassWriter writer = rewriter.writer(ClassWriter.COMPUTE_MAXS); ClassVisitor visitor = @@ -466,19 +541,24 @@ class Desugar { classpathReader, depsCollector, bootclasspathReader, + coreLibrarySupport, interfaceCache, interfaceLambdaMethodCollector, writer, reader); if (writer == visitor) { // Just copy the input if there are no rewritings - outputFileProvider.write(filename, reader.b); + outputFileProvider.write(inputFilename, reader.b); } else { reader.accept(visitor, 0); + String filename = writer.getClassName() + ".class"; + checkState( + (options.coreLibrary && coreLibrarySupport != null) + || filename.equals(inputFilename)); outputFileProvider.write(filename, writer.toByteArray()); } } else { - outputFileProvider.copyFrom(filename, inputFiles); + outputFileProvider.copyFrom(inputFilename, inputFiles); } } } @@ -494,6 +574,7 @@ class Desugar { @Nullable ClassReaderFactory classpathReader, DependencyCollector depsCollector, ClassReaderFactory bootclasspathReader, + @Nullable CoreLibrarySupport coreLibrarySupport, ClassVsInterface interfaceCache, ImmutableSet<String> interfaceLambdaMethods, @Nullable ClassReaderFactory bridgeMethodReader) @@ -525,6 +606,7 @@ class Desugar { classpathReader, depsCollector, bootclasspathReader, + coreLibrarySupport, interfaceCache, interfaceLambdaMethods, bridgeMethodReader, @@ -532,17 +614,26 @@ class Desugar { writer, reader); reader.accept(visitor, 0); - String filename = - rewriter.unprefix(lambdaClass.getValue().desiredInternalName()) + ".class"; - outputFileProvider.write(filename, writer.toByteArray()); + checkState( + (options.coreLibrary && coreLibrarySupport != null) + || rewriter + .unprefix(lambdaClass.getValue().desiredInternalName()) + .equals(writer.getClassName())); + outputFileProvider.write(writer.getClassName() + ".class", writer.toByteArray()); } } } private void desugarAndWriteGeneratedClasses( - OutputFileProvider outputFileProvider, ClassReaderFactory bootclasspathReader) + OutputFileProvider outputFileProvider, + ClassLoader loader, + ClassReaderFactory bootclasspathReader, + @Nullable CoreLibrarySupport coreLibrarySupport) throws IOException { // Write out any classes we generated along the way + if (coreLibrarySupport != null) { + coreLibrarySupport.makeDispatchHelpers(store); + } ImmutableMap<String, ClassNode> generatedClasses = store.drain(); checkState( generatedClasses.isEmpty() || (allowDefaultMethods && outputJava7), @@ -552,11 +643,39 @@ class Desugar { UnprefixingClassWriter writer = rewriter.writer(ClassWriter.COMPUTE_MAXS); // checkState above implies that we want Java 7 .class files, so send through that visitor. // Don't need a ClassReaderFactory b/c static interface methods should've been moved. - ClassVisitor visitor = - new Java7Compatibility(writer, (ClassReaderFactory) null, bootclasspathReader); + ClassVisitor visitor = writer; + if (coreLibrarySupport != null) { + visitor = new EmulatedInterfaceRewriter(visitor, coreLibrarySupport); + visitor = new CorePackageRenamer(visitor, coreLibrarySupport); + visitor = new CoreLibraryInvocationRewriter(visitor, coreLibrarySupport); + } + + if (!allowTryWithResources) { + CloseResourceMethodScanner closeResourceMethodScanner = new CloseResourceMethodScanner(); + generated.getValue().accept(closeResourceMethodScanner); + visitor = + new TryWithResourcesRewriter( + visitor, + loader, + visitedExceptionTypes, + numOfTryWithResourcesInvoked, + closeResourceMethodScanner.hasCloseResourceMethod()); + } + if (!allowCallsToObjectsNonNull) { + // Not sure whether there will be implicit null check emitted by javac, so we rerun + // the inliner again + visitor = new ObjectsRequireNonNullMethodRewriter(visitor, rewriter); + } + if (!allowCallsToLongCompare) { + visitor = new LongCompareMethodRewriter(visitor, rewriter); + } + + visitor = new Java7Compatibility(visitor, (ClassReaderFactory) null, bootclasspathReader); generated.getValue().accept(visitor); - String filename = rewriter.unprefix(generated.getKey()) + ".class"; - outputFileProvider.write(filename, writer.toByteArray()); + checkState( + (options.coreLibrary && coreLibrarySupport != null) + || rewriter.unprefix(generated.getKey()).equals(writer.getClassName())); + outputFileProvider.write(writer.getClassName() + ".class", writer.toByteArray()); } } @@ -569,6 +688,7 @@ class Desugar { @Nullable ClassReaderFactory classpathReader, DependencyCollector depsCollector, ClassReaderFactory bootclasspathReader, + @Nullable CoreLibrarySupport coreLibrarySupport, ClassVsInterface interfaceCache, ImmutableSet<String> interfaceLambdaMethods, @Nullable ClassReaderFactory bridgeMethodReader, @@ -576,6 +696,13 @@ class Desugar { UnprefixingClassWriter writer, ClassReader input) { ClassVisitor visitor = checkNotNull(writer); + + if (coreLibrarySupport != null) { + visitor = new EmulatedInterfaceRewriter(visitor, coreLibrarySupport); + visitor = new CorePackageRenamer(visitor, coreLibrarySupport); + visitor = new CoreLibraryInvocationRewriter(visitor, coreLibrarySupport); + } + if (!allowTryWithResources) { CloseResourceMethodScanner closeResourceMethodScanner = new CloseResourceMethodScanner(); input.accept(closeResourceMethodScanner, ClassReader.SKIP_DEBUG); @@ -590,10 +717,10 @@ class Desugar { if (!allowCallsToObjectsNonNull) { // Not sure whether there will be implicit null check emitted by javac, so we rerun // the inliner again - visitor = new ObjectsRequireNonNullMethodRewriter(visitor); + visitor = new ObjectsRequireNonNullMethodRewriter(visitor, rewriter); } if (!allowCallsToLongCompare) { - visitor = new LongCompareMethodRewriter(visitor); + visitor = new LongCompareMethodRewriter(visitor, rewriter); } if (outputJava7) { // null ClassReaderFactory b/c we don't expect to need it for lambda classes @@ -601,17 +728,25 @@ class Desugar { if (options.desugarInterfaceMethodBodiesIfNeeded) { visitor = new DefaultMethodClassFixer( - visitor, classpathReader, depsCollector, bootclasspathReader, loader); + visitor, + classpathReader, + depsCollector, + coreLibrarySupport, + bootclasspathReader, + loader); visitor = new InterfaceDesugaring( visitor, interfaceCache, depsCollector, + coreLibrarySupport, bootclasspathReader, + loader, store, options.legacyJacocoFix); } } + visitor = new LambdaClassFixer( visitor, @@ -639,11 +774,19 @@ class Desugar { @Nullable ClassReaderFactory classpathReader, DependencyCollector depsCollector, ClassReaderFactory bootclasspathReader, + @Nullable CoreLibrarySupport coreLibrarySupport, ClassVsInterface interfaceCache, ImmutableSet.Builder<String> interfaceLambdaMethodCollector, UnprefixingClassWriter writer, ClassReader input) { ClassVisitor visitor = checkNotNull(writer); + + if (coreLibrarySupport != null) { + visitor = new EmulatedInterfaceRewriter(visitor, coreLibrarySupport); + visitor = new CorePackageRenamer(visitor, coreLibrarySupport); + visitor = new CoreLibraryInvocationRewriter(visitor, coreLibrarySupport); + } + if (!allowTryWithResources) { CloseResourceMethodScanner closeResourceMethodScanner = new CloseResourceMethodScanner(); input.accept(closeResourceMethodScanner, ClassReader.SKIP_DEBUG); @@ -656,10 +799,10 @@ class Desugar { closeResourceMethodScanner.hasCloseResourceMethod()); } if (!allowCallsToObjectsNonNull) { - visitor = new ObjectsRequireNonNullMethodRewriter(visitor); + visitor = new ObjectsRequireNonNullMethodRewriter(visitor, rewriter); } if (!allowCallsToLongCompare) { - visitor = new LongCompareMethodRewriter(visitor); + visitor = new LongCompareMethodRewriter(visitor, rewriter); } if (!options.onlyDesugarJavac9ForLint) { if (outputJava7) { @@ -667,17 +810,25 @@ class Desugar { if (options.desugarInterfaceMethodBodiesIfNeeded) { visitor = new DefaultMethodClassFixer( - visitor, classpathReader, depsCollector, bootclasspathReader, loader); + visitor, + classpathReader, + depsCollector, + coreLibrarySupport, + bootclasspathReader, + loader); visitor = new InterfaceDesugaring( visitor, interfaceCache, depsCollector, + coreLibrarySupport, bootclasspathReader, + loader, store, options.legacyJacocoFix); } } + // LambdaDesugaring is relatively expensive, so check first whether we need it. Additionally, // we need to collect lambda methods referenced by invokedynamic instructions up-front anyway. // TODO(kmb): Scan constant pool instead of visiting the class to find bootstrap methods etc. @@ -730,7 +881,7 @@ class Desugar { } catch (ReflectiveOperationException e) { // We do not want to crash Desugar, if we cannot load or access these classes or fields. // We aim to provide better diagnostics. If we cannot, just let it go. - e.printStackTrace(); + e.printStackTrace(System.err); // To silence error-prone's complaint. } } @@ -760,13 +911,12 @@ class Desugar { return dumpDirectory; } - private static DesugarOptions parseCommandLineOptions(String[] args) throws IOException { - if (args.length == 1 && args[0].startsWith("@")) { - args = Files.readAllLines(Paths.get(args[0].substring(1)), ISO_8859_1).toArray(new String[0]); - } - DesugarOptions options = - Options.parseAndExitUponError(DesugarOptions.class, /*allowResidue=*/ false, args) - .getOptions(); + private static DesugarOptions parseCommandLineOptions(String[] args) { + OptionsParser parser = OptionsParser.newOptionsParser(DesugarOptions.class); + parser.setAllowResidue(false); + parser.enableParamsFileSupport(new ShellQuotedParamsFilePreProcessor(FileSystems.getDefault())); + parser.parseAndExitUponError(args); + DesugarOptions options = parser.getOptions(DesugarOptions.class); checkArgument(!options.inputJars.isEmpty(), "--input is required"); checkArgument( @@ -780,6 +930,10 @@ class Desugar { for (Path path : options.bootclasspath) { checkArgument(!Files.isDirectory(path), "Bootclasspath entry must be a jar file: %s", path); } + checkArgument(!options.desugarCoreLibs + || !options.rewriteCoreLibraryPrefixes.isEmpty() + || !options.emulateCoreLibraryInterfaces.isEmpty(), + "--desugar_supported_core_libs requires specifying renamed and/or emulated core libraries"); return options; } @@ -793,19 +947,6 @@ class Desugar { return ioPairListbuilder.build(); } - @VisibleForTesting - static class ThrowingClassLoader extends ClassLoader { - @Override - protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { - if (name.startsWith("java.")) { - // Use system class loader for java. classes, since ClassLoader.defineClass gets - // grumpy when those don't come from the standard place. - return super.loadClass(name, resolve); - } - throw new ClassNotFoundException(); - } - } - private static void deleteTreeOnExit(final Path directory) { Thread shutdownHook = new Thread() { @@ -844,26 +985,6 @@ class Desugar { } } - /** Transform a Path to an {@link OutputFileProvider} */ - @MustBeClosed - private static OutputFileProvider toOutputFileProvider(Path path) throws IOException { - if (Files.isDirectory(path)) { - return new DirectoryOutputFileProvider(path); - } else { - return new ZipOutputFileProvider(path); - } - } - - /** Transform a Path to an InputFileProvider that needs to be closed by the caller. */ - @MustBeClosed - private static InputFileProvider toInputFileProvider(Path path) throws IOException { - if (Files.isDirectory(path)) { - return new DirectoryInputFileProvider(path); - } else { - return new ZipInputFileProvider(path); - } - } - /** * Transform a list of Path to a list of InputFileProvider and register them with the given * closer. @@ -874,7 +995,7 @@ class Desugar { Closer closer, List<Path> paths) throws IOException { ImmutableList.Builder<InputFileProvider> builder = new ImmutableList.Builder<>(); for (Path path : paths) { - builder.add(closer.register(toInputFileProvider(path))); + builder.add(closer.register(InputFileProvider.open(path))); } return builder.build(); } |