blob: bcc845efba914f907fbb001a580d25052dfeaf38 [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.
// 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.graph;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.ir.desugar.LambdaRewriter;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.LazyClassCollection;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class DexApplication {
// Maps type into class promise, may be used concurrently.
private final ImmutableMap<DexType, DexClassPromise> classMap;
// Lazily loaded classes.
//
// Note that this collection is autonomous and may be used in several
// different applications. Particularly, it is the case when one
// application is being build based on another one. Among others,
// it will have an important side-effect: class conflict resolution,
// generated errors in particular, may be different in lazy scenario.
private final LazyClassCollection lazyClassCollection;
public final ImmutableSet<DexType> mainDexList;
private final ClassNameMapper proguardMap;
public final Timing timing;
public final DexItemFactory dexItemFactory;
// Information on the lexicographically largest string referenced from code.
public final DexString highestSortingString;
/** Constructor should only be invoked by the DexApplication.Builder. */
private DexApplication(
ClassNameMapper proguardMap,
ImmutableMap<DexType, DexClassPromise> classMap,
LazyClassCollection lazyClassCollection,
ImmutableSet<DexType> mainDexList,
DexItemFactory dexItemFactory,
DexString highestSortingString,
Timing timing) {
this.proguardMap = proguardMap;
this.lazyClassCollection = lazyClassCollection;
this.mainDexList = mainDexList;
this.classMap = classMap;
this.dexItemFactory = dexItemFactory;
this.highestSortingString = highestSortingString;
this.timing = timing;
}
ImmutableMap<DexType, DexClassPromise> getClassMap() {
assert lazyClassCollection == null : "Only allowed in non-lazy scenarios.";
return classMap;
}
public Iterable<DexProgramClass> classes() {
List<DexProgramClass> result = new ArrayList<>();
// Note: we ignore lazy class collection because it
// is never supposed to be used for program classes.
for (DexClassPromise promise : classMap.values()) {
if (promise.isProgramClass()) {
result.add(promise.get().asProgramClass());
}
}
return result;
}
public Iterable<DexLibraryClass> libraryClasses() {
assert lazyClassCollection == null : "Only allowed in non-lazy scenarios.";
List<DexLibraryClass> result = new ArrayList<>();
for (DexClassPromise promise : classMap.values()) {
if (promise.isLibraryClass()) {
result.add(promise.get().asLibraryClass());
}
}
return result;
}
public DexClass definitionFor(DexType type) {
DexClassPromise promise = classMap.get(type);
// In presence of lazy class collection we also reach out to it
// as well unless the class found is already a program class.
if (lazyClassCollection != null && (promise == null || !promise.isProgramClass())) {
promise = lazyClassCollection.get(type, promise);
}
return promise == null ? null : promise.get();
}
public DexProgramClass programDefinitionFor(DexType type) {
DexClassPromise promise = classMap.get(type);
// Don't bother about lazy class collection, it should never load program classes.
return (promise == null || !promise.isProgramClass()) ? null : promise.get().asProgramClass();
}
public String toString() {
return "Application (classes #" + classMap.size() + ")";
}
public ClassNameMapper getProguardMap() {
return proguardMap;
}
private void disassemble(DexEncodedMethod method, ClassNameMapper naming, Path outputDir) {
if (method.getCode() != null) {
PrintStream ps = System.out;
try {
String clazzName;
String methodName;
if (naming != null) {
clazzName = naming.originalNameOf(method.method.holder);
methodName = naming.originalSignatureOf(method.method).toString();
} else {
clazzName = method.method.holder.toSourceString();
methodName = method.method.name.toString();
}
if (outputDir != null) {
Path directory = outputDir.resolve(clazzName.replace('.', '/'));
String name = methodName + ".dump";
if (name.length() > 200) {
name = StringUtils.computeMD5Hash(name);
}
Files.createDirectories(directory);
ps = new PrintStream(Files.newOutputStream(directory.resolve(name)));
}
ps.println("Bytecode for");
ps.println("Class: '" + clazzName + "'");
ps.println("Method: '" + methodName + "':");
ps.println(method.getCode().toString(naming));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outputDir != null) {
ps.flush();
ps.close();
}
}
}
}
/**
* Write disassembly for the application code in the provided directory.
*
* <p>If no directory is provided everything is written to System.out.
*/
public void disassemble(Path outputDir, InternalOptions options) {
for (DexClass clazz : classes()) {
for (DexEncodedMethod method : clazz.virtualMethods()) {
if (options.methodMatchesFilter(method)) {
disassemble(method, getProguardMap(), outputDir);
}
}
for (DexEncodedMethod method : clazz.directMethods()) {
if (options.methodMatchesFilter(method)) {
disassemble(method, getProguardMap(), outputDir);
}
}
}
}
/** Return smali source for the application code. */
public String smali(InternalOptions options) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(os);
smali(options, ps);
return new String(os.toByteArray(), StandardCharsets.UTF_8);
}
private void writeClassHeader(DexClass clazz, PrintStream ps) {
StringBuilder builder = new StringBuilder();
builder.append(".class ");
builder.append(clazz.accessFlags.toSmaliString());
builder.append(" ");
builder.append(clazz.type.toSmaliString());
builder.append("\n\n");
if (clazz.type != dexItemFactory.objectType) {
builder.append(".super ");
builder.append(clazz.superType.toSmaliString());
// TODO(sgjesse): Add implemented interfaces
builder.append("\n");
}
ps.append(builder.toString());
}
/** Write smali source for the application code on the provided PrintStream. */
public void smali(InternalOptions options, PrintStream ps) {
List<DexProgramClass> classes = (List<DexProgramClass>) classes();
classes.sort(Comparator.comparing(DexProgramClass::toSourceString));
for (DexClass clazz : classes) {
boolean classHeaderWritten = false;
for (DexEncodedMethod method : clazz.virtualMethods()) {
if (options.methodMatchesFilter(method)) {
if (!classHeaderWritten) {
writeClassHeader(clazz, ps);
classHeaderWritten = true;
}
ps.append("\n");
ps.append(method.toSmaliString(getProguardMap()));
}
}
for (DexEncodedMethod method : clazz.directMethods()) {
if (options.methodMatchesFilter(method)) {
if (!classHeaderWritten) {
writeClassHeader(clazz, ps);
classHeaderWritten = true;
}
ps.append("\n");
ps.append(method.toSmaliString(getProguardMap()));
}
}
}
}
public static class Builder {
private final Hashtable<DexType, DexClassPromise> classMap = new Hashtable<>();
private LazyClassCollection lazyClassCollection;
public final Hashtable<DexCode, DexCode> codeItems = new Hashtable<>();
public final DexItemFactory dexItemFactory;
public ClassNameMapper proguardMap;
private final Timing timing;
public DexString highestSortingString;
private final Set<DexType> mainDexList = Sets.newIdentityHashSet();
public Builder(DexItemFactory dexItemFactory, Timing timing) {
this.dexItemFactory = dexItemFactory;
this.timing = timing;
this.lazyClassCollection = null;
}
public Builder(DexApplication application) {
this(application, application.classMap);
}
public Builder(DexApplication application, Map<DexType, DexClassPromise> classMap) {
this.classMap.putAll(classMap);
this.lazyClassCollection = application.lazyClassCollection;
proguardMap = application.proguardMap;
timing = application.timing;
highestSortingString = application.highestSortingString;
dexItemFactory = application.dexItemFactory;
mainDexList.addAll(application.mainDexList);
}
public synchronized Builder setProguardMap(ClassNameMapper proguardMap) {
assert this.proguardMap == null;
this.proguardMap = proguardMap;
return this;
}
public synchronized Builder setHighestSortingString(DexString value) {
highestSortingString = value;
return this;
}
public Builder addClassPromise(DexClassPromise promise) {
addClassPromise(promise, false);
return this;
}
public Builder setLazyClassCollection(LazyClassCollection lazyClassMap) {
this.lazyClassCollection = lazyClassMap;
return this;
}
public Builder addClassIgnoringLibraryDuplicates(DexClass clazz) {
addClass(clazz, true);
return this;
}
public Builder addSynthesizedClass(DexProgramClass synthesizedClass, boolean addToMainDexList) {
assert synthesizedClass.isProgramClass() : "All synthesized classes must be program classes";
addClassPromise(synthesizedClass);
if (addToMainDexList && !mainDexList.isEmpty()) {
mainDexList.add(synthesizedClass.type);
}
return this;
}
public List<DexProgramClass> getProgramClasses() {
List<DexProgramClass> result = new ArrayList<>();
// Note: we ignore lazy class collection because it
// is never supposed to be used for program classes.
for (DexClassPromise promise : classMap.values()) {
if (promise.isProgramClass()) {
result.add(promise.get().asProgramClass());
}
}
return result;
}
// Callback from FileReader when parsing a DexProgramClass (multi-threaded).
private void addClass(DexClass clazz, boolean skipLibDups) {
addClassPromise(clazz, skipLibDups);
}
public synchronized void addClassPromise(DexClassPromise promise, boolean skipLibDups) {
assert promise != null;
DexType type = promise.getType();
DexClassPromise oldPromise = classMap.get(type);
if (oldPromise != null) {
promise = chooseClass(promise, oldPromise, skipLibDups);
}
if (oldPromise != promise) {
classMap.put(type, promise);
}
}
public Builder addToMainDexList(Collection<DexType> mainDexList) {
this.mainDexList.addAll(mainDexList);
return this;
}
public DexApplication build() {
return new DexApplication(
proguardMap,
ImmutableMap.copyOf(classMap),
lazyClassCollection,
ImmutableSet.copyOf(mainDexList),
dexItemFactory,
highestSortingString,
timing);
}
}
public static DexClassPromise chooseClass(
DexClassPromise a, DexClassPromise b, boolean skipLibDups) {
// NOTE: We assume that there should not be any conflicting names in user defined
// classes and/or linked jars. If we ever want to allow 'keep first'-like policy
// to resolve this kind of conflict between program and/or classpath classes, we'll
// need to make sure we choose the class we keep deterministically.
if (a.isProgramClass() && b.isProgramClass()) {
if (allowProgramClassConflict(a, b)) {
return a;
}
throw new CompilationError(
"Program type already present: " + a.getType().toSourceString());
}
if (a.isProgramClass()) {
return chooseBetweenProgramAndOtherClass(a, b);
}
if (b.isProgramClass()) {
return chooseBetweenProgramAndOtherClass(b, a);
}
if (a.isClasspathClass() && b.isClasspathClass()) {
throw new CompilationError(
"Classpath type already present: " + a.getType().toSourceString());
}
if (a.isClasspathClass()) {
return chooseBetweenClasspathAndLibraryClass(a, b);
}
if (b.isClasspathClass()) {
return chooseBetweenClasspathAndLibraryClass(b, a);
}
return chooseBetweenLibraryClasses(b, a, skipLibDups);
}
private static boolean allowProgramClassConflict(DexClassPromise a, DexClassPromise b) {
// Currently only allow collapsing synthetic lambda classes.
return a.getOrigin() == DexClass.Origin.Dex
&& b.getOrigin() == DexClass.Origin.Dex
&& a.get().accessFlags.isSynthetic()
&& b.get().accessFlags.isSynthetic()
&& LambdaRewriter.hasLambdaClassPrefix(a.getType())
&& LambdaRewriter.hasLambdaClassPrefix(b.getType());
}
private static DexClassPromise chooseBetweenProgramAndOtherClass(
DexClassPromise selected, DexClassPromise ignored) {
assert selected.isProgramClass() && !ignored.isProgramClass();
if (ignored.isLibraryClass()) {
logIgnoredClass(ignored, "Class `%s` was specified as library and program type.");
}
// We don't log program/classpath class conflict since it is expected case.
return selected;
}
private static DexClassPromise chooseBetweenClasspathAndLibraryClass(
DexClassPromise selected, DexClassPromise ignored) {
assert selected.isClasspathClass() && ignored.isLibraryClass();
logIgnoredClass(ignored, "Class `%s` was specified as library and classpath type.");
return selected;
}
private static DexClassPromise chooseBetweenLibraryClasses(
DexClassPromise selected, DexClassPromise ignored, boolean skipDups) {
assert selected.isLibraryClass() && ignored.isLibraryClass();
if (!skipDups) {
throw new CompilationError(
"Library type already present: " + selected.getType().toSourceString());
}
logIgnoredClass(ignored, "Class `%s` was specified twice as a library type.");
return selected;
}
private static void logIgnoredClass(DexClassPromise ignored, String message) {
if (Log.ENABLED) {
Log.warn(DexApplication.class, message, ignored.getType().toSourceString());
}
}
}