|  | // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file | 
|  | // for details. All rights reserved. Use of this source code is governed by a | 
|  | // BSD-style license that can be found in the LICENSE file. | 
|  | package com.android.tools.r8; | 
|  |  | 
|  | import static com.android.tools.r8.utils.AssertionUtils.forTesting; | 
|  | import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException; | 
|  |  | 
|  | import com.android.tools.r8.androidapi.ApiReferenceStubber; | 
|  | import com.android.tools.r8.dex.ApplicationReader; | 
|  | import com.android.tools.r8.dex.ApplicationWriter; | 
|  | import com.android.tools.r8.dex.Marker; | 
|  | import com.android.tools.r8.dex.Marker.Tool; | 
|  | import com.android.tools.r8.experimental.startup.instrumentation.StartupInstrumentation; | 
|  | import com.android.tools.r8.graph.AppInfo; | 
|  | import com.android.tools.r8.graph.AppServices; | 
|  | import com.android.tools.r8.graph.AppView; | 
|  | import com.android.tools.r8.graph.DexApplication; | 
|  | import com.android.tools.r8.graph.DexItemFactory; | 
|  | import com.android.tools.r8.graph.DexProgramClass; | 
|  | import com.android.tools.r8.graph.LazyLoadedDexApplication; | 
|  | import com.android.tools.r8.graph.ProgramMethod; | 
|  | import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis; | 
|  | import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger; | 
|  | import com.android.tools.r8.inspector.internal.InspectorImpl; | 
|  | import com.android.tools.r8.ir.analysis.value.AbstractValueFactory; | 
|  | import com.android.tools.r8.ir.conversion.IRConverter; | 
|  | import com.android.tools.r8.ir.desugar.TypeRewriter; | 
|  | import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAmender; | 
|  | import com.android.tools.r8.ir.optimize.AssertionsRewriter; | 
|  | import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple; | 
|  | import com.android.tools.r8.jar.CfApplicationWriter; | 
|  | import com.android.tools.r8.kotlin.KotlinMetadataRewriter; | 
|  | import com.android.tools.r8.naming.NamingLens; | 
|  | import com.android.tools.r8.naming.PrefixRewritingNamingLens; | 
|  | import com.android.tools.r8.naming.RecordRewritingNamingLens; | 
|  | import com.android.tools.r8.naming.signature.GenericSignatureRewriter; | 
|  | import com.android.tools.r8.origin.CommandLineOrigin; | 
|  | import com.android.tools.r8.origin.Origin; | 
|  | import com.android.tools.r8.shaking.AssumeInfoCollection; | 
|  | import com.android.tools.r8.shaking.MainDexInfo; | 
|  | import com.android.tools.r8.synthesis.SyntheticFinalization; | 
|  | import com.android.tools.r8.synthesis.SyntheticItems; | 
|  | import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy; | 
|  | import com.android.tools.r8.utils.AndroidApp; | 
|  | import com.android.tools.r8.utils.CfgPrinter; | 
|  | import com.android.tools.r8.utils.ExceptionUtils; | 
|  | import com.android.tools.r8.utils.InternalOptions; | 
|  | import com.android.tools.r8.utils.StringDiagnostic; | 
|  | import com.android.tools.r8.utils.StringUtils; | 
|  | import com.android.tools.r8.utils.ThreadUtils; | 
|  | import com.android.tools.r8.utils.Timing; | 
|  | import java.io.FileOutputStream; | 
|  | import java.io.IOException; | 
|  | import java.io.OutputStreamWriter; | 
|  | import java.nio.charset.StandardCharsets; | 
|  | import java.util.ArrayList; | 
|  | import java.util.Collection; | 
|  | import java.util.List; | 
|  | import java.util.Set; | 
|  | import java.util.concurrent.ExecutionException; | 
|  | import java.util.concurrent.ExecutorService; | 
|  |  | 
|  | /** | 
|  | * The D8 dex compiler. | 
|  | * | 
|  | * <p>D8 performs modular compilation to DEX bytecode. It supports compilation of Java bytecode and | 
|  | * Android DEX bytecode to DEX bytecode including merging a mix of these input formats. | 
|  | * | 
|  | * <p>The D8 dexer API is intentionally limited and should "do the right thing" given a command. If | 
|  | * this API does not suffice please contact the D8/R8 team. | 
|  | * | 
|  | * <p>The compiler is invoked by calling {@link #run(D8Command) D8.run} with an appropriate {@link | 
|  | * D8Command}. For example: | 
|  | * | 
|  | * <pre> | 
|  | *   D8.run(D8Command.builder() | 
|  | *       .addProgramFiles(inputPathA, inputPathB) | 
|  | *       .setOutput(outputPath, OutputMode.DexIndexed) | 
|  | *       .build()); | 
|  | * </pre> | 
|  | * | 
|  | * The above reads the input files denoted by {@code inputPathA} and {@code inputPathB}, compiles | 
|  | * them to DEX bytecode (compiling from Java bytecode for such inputs and merging for DEX inputs), | 
|  | * and then writes the result to the directory or zip archive specified by {@code outputPath}. | 
|  | */ | 
|  | @Keep | 
|  | public final class D8 { | 
|  |  | 
|  | private D8() {} | 
|  |  | 
|  | /** | 
|  | * Main API entry for the D8 dexer. | 
|  | * | 
|  | * @param command D8 command. | 
|  | */ | 
|  | public static void run(D8Command command) throws CompilationFailedException { | 
|  | AndroidApp app = command.getInputApp(); | 
|  | InternalOptions options = command.getInternalOptions(); | 
|  | ExecutorService executor = ThreadUtils.getExecutorService(options); | 
|  | ExceptionUtils.withD8CompilationHandler( | 
|  | command.getReporter(), | 
|  | () -> { | 
|  | try { | 
|  | run(app, options, executor); | 
|  | } finally { | 
|  | executor.shutdown(); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Main API entry for the D8 dexer with a externally supplied executor service. | 
|  | * | 
|  | * @param command D8 command. | 
|  | * @param executor executor service from which to get threads for multi-threaded processing. | 
|  | */ | 
|  | public static void run(D8Command command, ExecutorService executor) | 
|  | throws CompilationFailedException { | 
|  | AndroidApp app = command.getInputApp(); | 
|  | InternalOptions options = command.getInternalOptions(); | 
|  | ExceptionUtils.withD8CompilationHandler( | 
|  | command.getReporter(), | 
|  | () -> { | 
|  | run(app, options, executor); | 
|  | }); | 
|  | } | 
|  |  | 
|  | private static void run(String[] args) throws CompilationFailedException { | 
|  | D8Command command = D8Command.parse(args, CommandLineOrigin.INSTANCE).build(); | 
|  | if (command.isPrintHelp()) { | 
|  | System.out.println(D8CommandParser.getUsageMessage()); | 
|  | return; | 
|  | } | 
|  | if (command.isPrintVersion()) { | 
|  | System.out.println("D8 " + Version.getVersionString()); | 
|  | return; | 
|  | } | 
|  | InternalOptions options = command.getInternalOptions(); | 
|  | AndroidApp app = command.getInputApp(); | 
|  | runForTesting(app, options); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Command-line entry to D8. | 
|  | * | 
|  | * <p>See {@link D8CommandParser#getUsageMessage()} or run {@code d8 --help} for usage | 
|  | * information. | 
|  | */ | 
|  | public static void main(String[] args) { | 
|  | if (args.length == 0) { | 
|  | throw new RuntimeException( | 
|  | StringUtils.joinLines("Invalid invocation.", D8CommandParser.getUsageMessage())); | 
|  | } | 
|  | ExceptionUtils.withMainProgramHandler(() -> run(args)); | 
|  | } | 
|  |  | 
|  | static void runForTesting(AndroidApp inputApp, InternalOptions options) | 
|  | throws CompilationFailedException { | 
|  | ExecutorService executor = ThreadUtils.getExecutorService(options); | 
|  | ExceptionUtils.withD8CompilationHandler( | 
|  | options.reporter, | 
|  | () -> { | 
|  | try { | 
|  | run(inputApp, options, executor); | 
|  | } finally { | 
|  | executor.shutdown(); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | private static AppView<AppInfo> readApp( | 
|  | AndroidApp inputApp, InternalOptions options, ExecutorService executor, Timing timing) | 
|  | throws IOException { | 
|  | timing.begin("Application read"); | 
|  | ApplicationReader applicationReader = new ApplicationReader(inputApp, options, timing); | 
|  | LazyLoadedDexApplication app = applicationReader.read(executor); | 
|  | timing.end(); | 
|  | timing.begin("Load desugared lib"); | 
|  | options.loadMachineDesugaredLibrarySpecification(timing, app); | 
|  | timing.end(); | 
|  | TypeRewriter typeRewriter = options.getTypeRewriter(); | 
|  | AppInfo appInfo = | 
|  | timing.time( | 
|  | "Create app-info", | 
|  | () -> | 
|  | AppInfo.createInitialAppInfo( | 
|  | app, | 
|  | options.isGeneratingDexIndexed() | 
|  | ? GlobalSyntheticsStrategy.forSingleOutputMode() | 
|  | : GlobalSyntheticsStrategy.forPerFileMode(), | 
|  | applicationReader.readMainDexClasses(app))); | 
|  | return timing.time("Create app-view", () -> AppView.createForD8(appInfo, typeRewriter, timing)); | 
|  | } | 
|  |  | 
|  | private static void run(AndroidApp inputApp, InternalOptions options, ExecutorService executor) | 
|  | throws IOException { | 
|  | if (options.printMemory) { | 
|  | // Run GC twice to remove objects with finalizers. | 
|  | System.gc(); | 
|  | System.gc(); | 
|  | Runtime runtime = Runtime.getRuntime(); | 
|  | System.out.println("D8 is running with total memory:" + runtime.totalMemory()); | 
|  | System.out.println("D8 is running with free memory:" + runtime.freeMemory()); | 
|  | System.out.println("D8 is running with max memory:" + runtime.maxMemory()); | 
|  | } | 
|  | Timing timing = Timing.create("D8", options); | 
|  | try { | 
|  | timing.begin("Pre conversion"); | 
|  | // Synthetic assertion to check that testing assertions works and can be enabled. | 
|  | assert forTesting(options, () -> !options.testing.testEnableTestAssertions); | 
|  |  | 
|  | timing.begin("Read input app"); | 
|  | AppView<AppInfo> appView = readApp(inputApp, options, executor, timing); | 
|  | timing.end(); | 
|  | timing.begin("Initialize assume info collection"); | 
|  | initializeAssumeInfoCollection(appView); | 
|  | timing.end(); | 
|  | timing.begin("Desugared library amend"); | 
|  | DesugaredLibraryAmender.run(appView); | 
|  | timing.end(); | 
|  | timing.begin("Collect input synthetics"); | 
|  | SyntheticItems.collectSyntheticInputs(appView); | 
|  | timing.end(); | 
|  |  | 
|  | final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null; | 
|  |  | 
|  | if (AssertionsRewriter.isEnabled(options)) { | 
|  | // Run analysis to mark all <clinit> methods having the javac generated assertion | 
|  | // enabling code. | 
|  | ClassInitializerAssertionEnablingAnalysis analysis = | 
|  | new ClassInitializerAssertionEnablingAnalysis( | 
|  | appView, OptimizationFeedbackSimple.getInstance()); | 
|  | ThreadUtils.processItems( | 
|  | appView.appInfo().classes(), | 
|  | clazz -> { | 
|  | ProgramMethod classInitializer = clazz.getProgramClassInitializer(); | 
|  | if (classInitializer != null) { | 
|  | analysis.processNewlyLiveMethod(classInitializer, clazz, null, null); | 
|  | } | 
|  | }, | 
|  | executor); | 
|  | } | 
|  |  | 
|  | if (options.testing.enableD8ResourcesPassThrough) { | 
|  | appView.setAppServices(AppServices.builder(appView).build()); | 
|  | } | 
|  | timing.end(); | 
|  | new IRConverter(appView, timing, printer).convert(appView, executor); | 
|  | timing.begin("Post conversion"); | 
|  | if (options.printCfg) { | 
|  | if (options.printCfgFile == null || options.printCfgFile.isEmpty()) { | 
|  | System.out.print(printer.toString()); | 
|  | } else { | 
|  | try (OutputStreamWriter writer = | 
|  | new OutputStreamWriter( | 
|  | new FileOutputStream(options.printCfgFile), StandardCharsets.UTF_8)) { | 
|  | writer.write(printer.toString()); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Close any internal archive providers now the application is fully processed. | 
|  | inputApp.closeInternalArchiveProviders(); | 
|  |  | 
|  | // If a method filter is present don't produce output since the application is likely partial. | 
|  | if (options.hasMethodsFilter()) { | 
|  | System.out.println("Finished compilation with method filter: "); | 
|  | options.methodsFilter.forEach((m) -> System.out.println("  - " + m)); | 
|  | } | 
|  |  | 
|  | // Preserve markers from input dex code and add a marker with the current version | 
|  | // if there were class file inputs. | 
|  | boolean hasClassResources = appView.appInfo().app().getFlags().hasReadProgramClassFromCf(); | 
|  | boolean hasDexResources = appView.appInfo().app().getFlags().hasReadProgramClassFromDex(); | 
|  |  | 
|  | Marker marker = hasClassResources ? options.getMarker(Tool.D8) : null; | 
|  |  | 
|  | timing.time( | 
|  | "Run inspections", | 
|  | () -> | 
|  | InspectorImpl.runInspections(options.outputInspections, appView.appInfo().classes())); | 
|  |  | 
|  | timing.time( | 
|  | "Create prefix rewriting lens", | 
|  | () -> | 
|  | appView.setNamingLens( | 
|  | PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView))); | 
|  |  | 
|  | timing.time( | 
|  | "Create record rewriting lens", | 
|  | () -> | 
|  | appView.setNamingLens( | 
|  | RecordRewritingNamingLens.createRecordRewritingNamingLens(appView))); | 
|  |  | 
|  | if (options.isGeneratingDex() | 
|  | && hasDexResources | 
|  | && hasClassResources | 
|  | && appView.typeRewriter.isRewriting()) { | 
|  | // There are both cf and dex inputs in the program, and rewriting is required for | 
|  | // desugared library only on cf inputs. We cannot easily rewrite part of the program | 
|  | // without iterating again the IR. We fall-back to writing one app with rewriting and | 
|  | // merging it with the other app in rewriteNonDexInputs. | 
|  | timing.begin("Rewrite non-dex inputs"); | 
|  | DexApplication app = rewriteNonDexInputs(appView, inputApp, executor, marker, timing); | 
|  | timing.end(); | 
|  | appView.setAppInfo( | 
|  | new AppInfo( | 
|  | appView.appInfo().getSyntheticItems().commit(app), | 
|  | appView.appInfo().getMainDexInfo())); | 
|  | appView.setNamingLens(NamingLens.getIdentityLens()); | 
|  | } else if (options.isGeneratingDex() && hasDexResources) { | 
|  | appView.setNamingLens(NamingLens.getIdentityLens()); | 
|  | } | 
|  |  | 
|  | // Since tracing is not lens aware, this needs to be done prior to synthetic finalization | 
|  | // which will construct a graph lens. | 
|  | if (options.isGeneratingDex() && !options.mainDexKeepRules.isEmpty()) { | 
|  | timing.begin("Generate main-dex list"); | 
|  | appView.dexItemFactory().clearTypeElementsCache(); | 
|  | MainDexInfo mainDexInfo = | 
|  | new GenerateMainDexList(options) | 
|  | .traceMainDex( | 
|  | executor, appView.appInfo().app(), appView.appInfo().getMainDexInfo()); | 
|  | appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(mainDexInfo)); | 
|  | timing.end(); | 
|  | } | 
|  |  | 
|  | finalizeApplication(appView, executor, timing); | 
|  |  | 
|  | timing.end(); // post-converter | 
|  |  | 
|  | if (options.isGeneratingClassFiles()) { | 
|  | new CfApplicationWriter(appView, marker).write(options.getClassFileConsumer(), inputApp); | 
|  | } else { | 
|  | ApplicationWriter.create(appView, marker).write(executor, inputApp); | 
|  | } | 
|  | options.printWarnings(); | 
|  | } catch (ExecutionException e) { | 
|  | throw unwrapExecutionException(e); | 
|  | } finally { | 
|  | inputApp.signalFinishedToProviders(options.reporter); | 
|  | options.signalFinishedToConsumers(); | 
|  | // Dump timings. | 
|  | if (options.printTimes) { | 
|  | timing.report(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | private static void initializeAssumeInfoCollection(AppView<AppInfo> appView) { | 
|  | AssumeInfoCollection.Builder assumeInfoCollectionBuilder = AssumeInfoCollection.builder(); | 
|  | AbstractValueFactory abstractValueFactory = appView.abstractValueFactory(); | 
|  | DexItemFactory dexItemFactory = appView.dexItemFactory(); | 
|  | InternalOptions options = appView.options(); | 
|  | if (options.isGeneratingDex()) { | 
|  | assumeInfoCollectionBuilder | 
|  | .meetAssumeValue( | 
|  | dexItemFactory.androidOsBuildVersionMembers.SDK_INT, | 
|  | abstractValueFactory.createNumberFromIntervalValue( | 
|  | options.getMinApiLevel().getLevel(), Integer.MAX_VALUE)) | 
|  | .setIsSideEffectFree(dexItemFactory.androidOsBuildVersionMembers.SDK_INT); | 
|  | } | 
|  | appView.setAssumeInfoCollection(assumeInfoCollectionBuilder.build()); | 
|  | } | 
|  |  | 
|  | private static void finalizeApplication( | 
|  | AppView<AppInfo> appView, ExecutorService executorService, Timing timing) | 
|  | throws ExecutionException { | 
|  | timing.time( | 
|  | "Finalize synthetics", | 
|  | () -> SyntheticFinalization.finalize(appView, timing, executorService)); | 
|  |  | 
|  | timing.time( | 
|  | "Horizontal merger", | 
|  | () -> | 
|  | HorizontalClassMerger.createForD8ClassMerging(appView) | 
|  | .runIfNecessary(executorService, timing)); | 
|  |  | 
|  | timing.time( | 
|  | "Signature rewriter", | 
|  | () -> | 
|  | new GenericSignatureRewriter(appView) | 
|  | .runForD8(appView.appInfo().classes(), executorService)); | 
|  |  | 
|  | timing.time( | 
|  | "Kotlin metadata rewriter", | 
|  | () -> new KotlinMetadataRewriter(appView).runForD8(executorService)); | 
|  |  | 
|  | timing.time( | 
|  | "Startup instrumentation", () -> StartupInstrumentation.run(appView, executorService)); | 
|  |  | 
|  | timing.time( | 
|  | "Api reference stubber", () -> new ApiReferenceStubber(appView).run(executorService)); | 
|  | } | 
|  |  | 
|  | private static DexApplication rewriteNonDexInputs( | 
|  | AppView<AppInfo> appView, | 
|  | AndroidApp inputApp, | 
|  | ExecutorService executor, | 
|  | Marker marker, | 
|  | Timing timing) | 
|  | throws IOException, ExecutionException { | 
|  | // TODO(b/154575955): Remove the naming lens in D8. | 
|  | appView | 
|  | .options() | 
|  | .reporter | 
|  | .warning( | 
|  | new StringDiagnostic( | 
|  | "The compilation is slowed down due to a mix of class file and dex file inputs in" | 
|  | + " the context of desugared library. This can be fixed by pre-compiling to" | 
|  | + " dex the class file inputs and dex merging only dex files.")); | 
|  | List<DexProgramClass> dexProgramClasses = new ArrayList<>(); | 
|  | List<DexProgramClass> nonDexProgramClasses = new ArrayList<>(); | 
|  | for (DexProgramClass aClass : appView.appInfo().classes()) { | 
|  | if (aClass.originatesFromDexResource()) { | 
|  | dexProgramClasses.add(aClass); | 
|  | } else { | 
|  | nonDexProgramClasses.add(aClass); | 
|  | } | 
|  | } | 
|  | DexApplication cfApp = | 
|  | appView.app().builder().replaceProgramClasses(nonDexProgramClasses).build(); | 
|  | appView.setAppInfo( | 
|  | new AppInfo( | 
|  | appView.appInfo().getSyntheticItems().commit(cfApp), | 
|  | appView.appInfo().getMainDexInfo())); | 
|  | ConvertedCfFiles convertedCfFiles = new ConvertedCfFiles(); | 
|  | new GenericSignatureRewriter(appView).run(appView.appInfo().classes(), executor); | 
|  | new KotlinMetadataRewriter(appView).runForD8(executor); | 
|  | ApplicationWriter.create(appView, marker, convertedCfFiles).write(executor); | 
|  | AndroidApp.Builder builder = AndroidApp.builder(inputApp); | 
|  | builder.getProgramResourceProviders().clear(); | 
|  | builder.addProgramResourceProvider(convertedCfFiles); | 
|  | AndroidApp newAndroidApp = builder.build(); | 
|  | DexApplication newApp = | 
|  | new ApplicationReader(newAndroidApp, appView.options(), timing).read(executor); | 
|  | DexApplication.Builder<?> finalDexApp = newApp.builder(); | 
|  | for (DexProgramClass dexProgramClass : dexProgramClasses) { | 
|  | finalDexApp.addProgramClass(dexProgramClass); | 
|  | } | 
|  | return finalDexApp.build(); | 
|  | } | 
|  |  | 
|  | static void optimize( | 
|  | AppView<AppInfo> appView, InternalOptions options, Timing timing, ExecutorService executor) | 
|  | throws IOException, ExecutionException { | 
|  | final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null; | 
|  |  | 
|  | new IRConverter(appView, timing, printer).convert(appView, executor); | 
|  |  | 
|  | if (options.printCfg) { | 
|  | if (options.printCfgFile == null || options.printCfgFile.isEmpty()) { | 
|  | System.out.print(printer.toString()); | 
|  | } else { | 
|  | try (OutputStreamWriter writer = | 
|  | new OutputStreamWriter( | 
|  | new FileOutputStream(options.printCfgFile), StandardCharsets.UTF_8)) { | 
|  | writer.write(printer.toString()); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static class ConvertedCfFiles implements DexIndexedConsumer, ProgramResourceProvider { | 
|  |  | 
|  | private final List<ProgramResource> resources = new ArrayList<>(); | 
|  |  | 
|  | @Override | 
|  | public synchronized void accept( | 
|  | int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) { | 
|  | // TODO(b/154106502): Map Origin information. | 
|  | resources.add( | 
|  | ProgramResource.fromBytes( | 
|  | Origin.unknown(), ProgramResource.Kind.DEX, data.copyByteData(), descriptors)); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Collection<ProgramResource> getProgramResources() throws ResourceException { | 
|  | return resources; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void finished(DiagnosticsHandler handler) {} | 
|  | } | 
|  | } |