blob: a720596084067c0ec98ce5a3aadf3f15ac71dec0 [file] [log] [blame]
// Copyright (c) 2016, 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.dex;
import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.OutputSink;
import com.android.tools.r8.errors.DexOverflowException;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationDirectory;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexAnnotationSetRefList;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexDebugInfo;
import com.android.tools.r8.graph.DexEncodedArray;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.ObjectToOffsetMapping;
import com.android.tools.r8.naming.MinifiedNameMapPrinter;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OutputMode;
import com.android.tools.r8.utils.ThreadUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
public class ApplicationWriter {
public final DexApplication application;
public final AppInfo appInfo;
public final byte[] deadCode;
public final NamingLens namingLens;
public final byte[] proguardSeedsData;
public final InternalOptions options;
public DexString markerString;
private static class SortAnnotations extends MixedSectionCollection {
@Override
public boolean add(DexAnnotationSet dexAnnotationSet) {
// Annotation sets are sorted by annotation types.
dexAnnotationSet.sort();
return true;
}
@Override
public boolean add(DexAnnotation annotation) {
// The elements of encoded annotation must be sorted by name.
annotation.annotation.sort();
return true;
}
@Override
public boolean add(DexEncodedArray dexEncodedArray) {
// Dex values must potentially be sorted, eg, for DexValueAnnotation.
for (DexValue value : dexEncodedArray.values) {
value.sort();
}
return true;
}
@Override
public boolean add(DexProgramClass dexClassData) {
return true;
}
@Override
public boolean add(DexCode dexCode) {
return true;
}
@Override
public boolean add(DexDebugInfo dexDebugInfo) {
return true;
}
@Override
public boolean add(DexTypeList dexTypeList) {
return true;
}
@Override
public boolean add(DexAnnotationSetRefList annotationSetRefList) {
return true;
}
@Override
public boolean setAnnotationsDirectoryForClass(DexProgramClass clazz,
DexAnnotationDirectory annotationDirectory) {
return true;
}
}
public ApplicationWriter(
DexApplication application,
AppInfo appInfo,
InternalOptions options,
Marker marker,
byte[] deadCode,
NamingLens namingLens,
byte[] proguardSeedsData) {
assert application != null;
this.application = application;
this.appInfo = appInfo;
assert options != null;
this.options = options;
this.markerString = (marker == null)
? null
: application.dexItemFactory.createString(marker.toString());
this.deadCode = deadCode;
this.namingLens = namingLens;
this.proguardSeedsData = proguardSeedsData;
}
public void write(OutputSink outputSink, ExecutorService executorService)
throws IOException, ExecutionException, DexOverflowException {
application.timing.begin("DexApplication.write");
try {
application.dexItemFactory.sort(namingLens);
assert this.markerString == null || application.dexItemFactory.extractMarker() != null;
SortAnnotations sortAnnotations = new SortAnnotations();
application.classes().forEach((clazz) -> clazz.addDependencies(sortAnnotations));
// Distribute classes into dex files.
VirtualFile.Distributor distributor;
if (options.outputMode == OutputMode.FilePerInputClass) {
distributor = new VirtualFile.FilePerInputClassDistributor(this);
} else if (!options.canUseMultidex()
&& options.mainDexKeepRules.isEmpty()
&& application.mainDexList.isEmpty()) {
distributor = new VirtualFile.MonoDexDistributor(this, options);
} else {
distributor = new VirtualFile.FillFilesDistributor(this, options);
}
Map<Integer, VirtualFile> newFiles = distributor.run();
// Collect the indexed items sets for all files and perform JumboString processing.
// This is required to ensure that shared code blocks have a single and consistent code
// item that is valid for all dex files.
// Use a linked hash map as the order matters when addDexProgramData is called below.
Map<VirtualFile, Future<ObjectToOffsetMapping>> offsetMappingFutures = new LinkedHashMap<>();
for (int i = 0; i < newFiles.size(); i++) {
VirtualFile newFile = newFiles.get(i);
assert newFile.getId() == i;
assert !newFile.isEmpty();
if (!newFile.isEmpty()) {
offsetMappingFutures
.put(newFile, executorService.submit(() -> {
ObjectToOffsetMapping mapping = newFile.computeMapping(application);
rewriteCodeWithJumboStrings(mapping, newFile.classes(), application);
return mapping;
}));
}
}
// Wait for all spawned futures to terminate to ensure jumbo string writing is complete.
ThreadUtils.awaitFutures(offsetMappingFutures.values());
// Generate the dex file contents.
List<Future<Boolean>> dexDataFutures = new ArrayList<>();
try {
for (VirtualFile virtualFile : offsetMappingFutures.keySet()) {
assert !virtualFile.isEmpty();
final ObjectToOffsetMapping mapping = offsetMappingFutures.get(virtualFile).get();
dexDataFutures.add(executorService.submit(() -> {
byte[] result = writeDexFile(mapping);
if (virtualFile.getPrimaryClassDescriptor() != null) {
outputSink.writeDexFile(
result,
virtualFile.getClassDescriptors(),
virtualFile.getPrimaryClassDescriptor());
} else {
outputSink
.writeDexFile(result, virtualFile.getClassDescriptors(), virtualFile.getId());
}
return true;
}));
}
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while waiting for future.", e);
}
// Wait for all files to be processed before moving on.
ThreadUtils.awaitFutures(dexDataFutures);
if (options.proguardConfiguration.isPrintUsage()) {
if (deadCode != null) {
outputSink.writePrintUsedInformation(deadCode);
}
}
// Write the proguard map file after writing the dex files, as the map writer traverses
// the DexProgramClass structures, which are destructively updated during dex file writing.
if (options.proguardMapOutput != null || options.proguardConfiguration.isPrintMapping()) {
byte[] proguardMapResult = writeProguardMapFile();
if (proguardMapResult != null) {
outputSink.writeProguardMapFile(proguardMapResult);
}
}
if (options.proguardConfiguration.isPrintSeeds()) {
if (proguardSeedsData != null) {
outputSink.writeProguardSeedsFile(proguardSeedsData);
}
}
byte[] mainDexList = writeMainDexList();
if (mainDexList != null) {
outputSink.writeMainDexListFile(mainDexList);
}
} finally {
application.timing.end();
}
}
/**
* Rewrites the code for all methods in the given file so that they use JumboString for at
* least the strings that require it in mapping.
* <p>
* If run multiple times on a class, the lowest index that is required to be a JumboString will
* be used.
*/
private static void rewriteCodeWithJumboStrings(ObjectToOffsetMapping mapping,
List<DexProgramClass> classes, DexApplication application) {
// If there are no strings with jumbo indices at all this is a no-op.
if (!mapping.hasJumboStrings()) {
return;
}
// If the globally highest sorting string is not a jumbo string this is also a no-op.
if (application.highestSortingString != null &&
application.highestSortingString.slowCompareTo(mapping.getFirstJumboString()) < 0) {
return;
}
// At least one method needs a jumbo string.
for (DexProgramClass clazz : classes) {
clazz.forEachMethod(method -> method.rewriteCodeWithJumboStrings(mapping, application));
}
}
private byte[] writeDexFile(ObjectToOffsetMapping mapping)
throws ApiLevelException {
FileWriter fileWriter = new FileWriter(mapping, application, appInfo, options, namingLens);
// Collect the non-fixed sections.
fileWriter.collect();
// Generate and write the bytes.
return fileWriter.generate();
}
private byte[] writeProguardMapFile() throws IOException {
// TODO(herhut): Should writing of the proguard-map file be split like this?
if (!namingLens.isIdentityLens()) {
MinifiedNameMapPrinter printer = new MinifiedNameMapPrinter(application, namingLens);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
PrintStream stream = new PrintStream(bytes);
printer.write(stream);
stream.flush();
return bytes.toByteArray();
} else if (application.getProguardMap() != null) {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
Writer writer = new PrintWriter(bytes);
application.getProguardMap().write(writer, !options.skipDebugLineNumberOpt);
writer.flush();
return bytes.toByteArray();
}
return null;
}
private String mapMainDexListName(DexType type) {
return DescriptorUtils.descriptorToJavaType(namingLens.lookupDescriptor(type).toString())
.replace('.', '/') + ".class";
}
private byte[] writeMainDexList() {
if (application.mainDexList.isEmpty()) {
return null;
}
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
PrintWriter writer = new PrintWriter(bytes);
application.mainDexList.forEach(
type -> writer.println(mapMainDexListName(type))
);
writer.flush();
return bytes.toByteArray();
}
}