Merge "Add API to get R8 version"
diff --git a/.gitignore b/.gitignore
index 14e24ec..c04d9dd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -63,6 +63,8 @@
third_party/kotlin
third_party/nest/*
!third_party/nest/*.sha1
+third_party/opensource_apps
+third_party/opensource_apps.tar.gz
third_party/photos/*
!third_party/photos/*.sha1
third_party/proguard/*
diff --git a/build.gradle b/build.gradle
index f129fb5..4ce2e18 100644
--- a/build.gradle
+++ b/build.gradle
@@ -10,7 +10,6 @@
ext {
androidSupportVersion = '25.4.0'
asmVersion = '6.2.1'
- autoValueVersion = '1.5'
espressoVersion = '3.0.0'
fastutilVersion = '7.2.0'
guavaVersion = '23.0'
@@ -66,7 +65,6 @@
dependencies {
classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.4'
classpath "net.ltgt.gradle:gradle-errorprone-plugin:0.0.13"
- classpath "net.ltgt.gradle:gradle-apt-plugin:0.12"
classpath "com.gradle:build-scan-plugin:1.14"
}
}
@@ -81,7 +79,6 @@
apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'net.ltgt.errorprone-base'
-apply plugin: "net.ltgt.apt"
if (project.hasProperty('with_code_coverage')) {
apply plugin: 'jacoco'
@@ -257,7 +254,6 @@
examplesCompile "com.google.guava:guava:$guavaVersion"
examplesCompile "junit:junit:$junitVersion"
examplesCompile "org.mockito:mockito-core:$mockitoVersion"
- examplesCompileOnly "com.google.auto.value:auto-value:$autoValueVersion"
supportLibs "com.android.support:support-v4:$androidSupportVersion"
supportLibs "junit:junit:$junitVersion"
supportLibs "com.android.support.test.espresso:espresso-core:$espressoVersion"
@@ -266,7 +262,6 @@
debugTestResourcesKotlinCompileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
examplesKotlinCompileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
kotlinR8TestResourcesCompileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
- apt "com.google.auto.value:auto-value:$autoValueVersion"
}
def r8LibPath = "$buildDir/libs/r8lib.jar"
diff --git a/src/cf_segments/java/com/android/tools/r8/cf_segments/Metrics.java b/src/cf_segments/java/com/android/tools/r8/cf_segments/Metrics.java
index 57786e1..8a08fde 100644
--- a/src/cf_segments/java/com/android/tools/r8/cf_segments/Metrics.java
+++ b/src/cf_segments/java/com/android/tools/r8/cf_segments/Metrics.java
@@ -63,7 +63,7 @@
public final SegmentInfo maxLocals = new SegmentInfo("MaxLocal", false);
public final SegmentInfo maxStacks = new SegmentInfo("MaxStack", false);
public final SegmentInfo methodInfo = new SegmentInfo("Method");
- public final SegmentInfo size = new SegmentInfo("Size").increment(1, 0);
+ public final SegmentInfo size = new SegmentInfo("Total").increment(1, 0);
public final SegmentInfo stores = new SegmentInfo("Stores", false);
public final SegmentInfo stackMapTable = new SegmentInfo("StackMapTable");
public final SegmentInfo stackmapTableOtherEntries = new SegmentInfo("StackMapTableOtherEntries");
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
index f78912f..27c564a 100644
--- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java
+++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -58,6 +58,7 @@
InternalOptions options = command.getInternalOptions();
options.enableDesugaring = false;
options.enableMainDexListCheck = false;
+ options.ignoreMainDexMissingClasses = true;
options.minimalMainDex = false;
options.enableMinification = false;
options.enableInlining = false;
diff --git a/src/main/java/com/android/tools/r8/ResourceShrinker.java b/src/main/java/com/android/tools/r8/ResourceShrinker.java
index 47ff5ee..6267db1 100644
--- a/src/main/java/com/android/tools/r8/ResourceShrinker.java
+++ b/src/main/java/com/android/tools/r8/ResourceShrinker.java
@@ -241,11 +241,11 @@
.filter(DexEncodedField::hasAnnotation)
.flatMap(f -> Arrays.stream(f.annotations.annotations));
Stream<DexAnnotation> virtualMethodAnnotations =
- Arrays.stream(classDef.virtualMethods())
+ classDef.virtualMethods().stream()
.filter(DexEncodedMethod::hasAnnotation)
.flatMap(m -> Arrays.stream(m.annotations.annotations));
Stream<DexAnnotation> directMethodAnnotations =
- Arrays.stream(classDef.directMethods())
+ classDef.directMethods().stream()
.filter(DexEncodedMethod::hasAnnotation)
.flatMap(m -> Arrays.stream(m.annotations.annotations));
Stream<DexAnnotation> classAnnotations = Arrays.stream(classDef.annotations.annotations);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 3eda1b7..a84b916 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -45,6 +45,10 @@
return opcode;
}
+ public boolean isInterface() {
+ return itf;
+ }
+
@Override
public void write(MethodVisitor visitor, NamingLens lens) {
String owner = lens.lookupInternalName(method.getHolder());
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 5e71072..1af80fa 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -563,7 +563,7 @@
}
private void writeEncodedFields(DexEncodedField[] fields) {
- assert PresortedComparable.isSorted(fields);
+ assert PresortedComparable.isSorted(Arrays.asList(fields));
int currentOffset = 0;
for (DexEncodedField field : fields) {
int nextOffset = mapping.getOffsetFor(field.field);
@@ -574,7 +574,7 @@
}
}
- private void writeEncodedMethods(DexEncodedMethod[] methods, boolean clearBodies) {
+ private void writeEncodedMethods(List<DexEncodedMethod> methods, boolean clearBodies) {
assert PresortedComparable.isSorted(methods);
int currentOffset = 0;
for (DexEncodedMethod method : methods) {
@@ -602,8 +602,8 @@
mixedSectionOffsets.setOffsetFor(clazz, dest.position());
dest.putUleb128(clazz.staticFields().length);
dest.putUleb128(clazz.instanceFields().length);
- dest.putUleb128(clazz.directMethods().length);
- dest.putUleb128(clazz.virtualMethods().length);
+ dest.putUleb128(clazz.directMethods().size());
+ dest.putUleb128(clazz.virtualMethods().size());
writeEncodedFields(clazz.staticFields());
writeEncodedFields(clazz.instanceFields());
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index 0e62775..0d12915 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -307,11 +307,13 @@
mainDexFile.addClass(programClass);
classes.remove(programClass);
} else {
- options.reporter.warning(
- new StringDiagnostic(
- "Application does not contain `"
- + type.toSourceString()
- + "` as referenced in main-dex-list."));
+ if (!options.ignoreMainDexMissingClasses) {
+ options.reporter.warning(
+ new StringDiagnostic(
+ "Application does not contain `"
+ + type.toSourceString()
+ + "` as referenced in main-dex-list."));
+ }
}
mainDexFile.commitTransaction();
}
diff --git a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
index 9062ae7..4b89d9e 100644
--- a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
+++ b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
@@ -86,7 +86,7 @@
@Keep
public static final class Options {
- private final DiagnosticsHandler diagnosticsHandler = new DiagnosticsHandler() {};
+ private final DiagnosticsHandler diagnosticsHandler;
private List<String> inputArchives = new ArrayList<>();
private List<FeatureJar> featureJars = new ArrayList<>();
private List<String> baseJars = new ArrayList<>();
@@ -97,6 +97,14 @@
private String mainDexList;
private boolean splitNonClassResources = false;
+ public Options() {
+ this(new DiagnosticsHandler() {});
+ }
+
+ public Options(DiagnosticsHandler diagnosticsHandler) {
+ this.diagnosticsHandler = diagnosticsHandler;
+ }
+
public DiagnosticsHandler getDiagnosticsHandler() {
return diagnosticsHandler;
}
@@ -293,7 +301,9 @@
throw new AbortException();
}
- D8Command.Builder builder = D8Command.builder();
+ D8Command.Builder builder = D8Command.builder(options.diagnosticsHandler);
+
+
for (String s : options.inputArchives) {
builder.addProgramFiles(Paths.get(s));
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
index fa5d8a8..1ef2f68 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.utils.OrderedMergingIterator;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
@@ -37,10 +38,11 @@
parameterAnnotations.add(method);
}
}
- assert isSorted(clazz.staticFields());
- assert isSorted(clazz.instanceFields());
+ assert isSorted(Arrays.asList(clazz.staticFields()));
+ assert isSorted(Arrays.asList(clazz.instanceFields()));
OrderedMergingIterator<DexEncodedField, DexField> fields =
- new OrderedMergingIterator<>(clazz.staticFields(), clazz.instanceFields());
+ new OrderedMergingIterator<>(
+ Arrays.asList(clazz.staticFields()), Arrays.asList(clazz.instanceFields()));
fieldAnnotations = new ArrayList<>();
while (fields.hasNext()) {
DexEncodedField field = fields.next();
@@ -108,11 +110,13 @@
throw new Unreachable();
}
- private static <T extends PresortedComparable<T>> boolean isSorted(KeyedDexItem<T>[] items) {
+ private static <T extends PresortedComparable<T>> boolean isSorted(
+ List<? extends KeyedDexItem<T>> items) {
return isSorted(items, KeyedDexItem::getKey);
}
- private static <S, T extends Comparable<T>> boolean isSorted(S[] items, Function<S, T> getter) {
+ private static <S, T extends Comparable<T>> boolean isSorted(
+ List<S> items, Function<S, T> getter) {
T current = null;
for (S item : items) {
T next = getter.apply(item);
diff --git a/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java b/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
index 21900a01..60893c7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
@@ -11,7 +11,6 @@
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.util.Arrays;
import java.util.function.Consumer;
public abstract class DexByteCodeWriter {
@@ -62,7 +61,8 @@
private void write(ThrowingFunction<DexClass, PrintStream, IOException> outputStreamProvider,
Consumer<PrintStream> closer)
throws IOException {
- for (DexProgramClass clazz : application.classes()) {
+ Iterable<DexProgramClass> classes = application.classesWithDeterministicOrder();
+ for (DexProgramClass clazz : classes) {
if (anyMethodMatches(clazz)) {
PrintStream ps = outputStreamProvider.apply(clazz);
try {
@@ -76,8 +76,8 @@
private boolean anyMethodMatches(DexClass clazz) {
return !options.hasMethodsFilter()
- || Arrays.stream(clazz.virtualMethods()).anyMatch(options::methodMatchesFilter)
- || Arrays.stream(clazz.directMethods()).anyMatch(options::methodMatchesFilter);
+ || clazz.virtualMethods().stream().anyMatch(options::methodMatchesFilter)
+ || clazz.directMethods().stream().anyMatch(options::methodMatchesFilter);
}
private void writeClass(DexProgramClass clazz, PrintStream ps) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 0ca5602..28aabea 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -8,10 +8,14 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.kotlin.KotlinInfo;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.InternalOptions;
import com.google.common.base.MoreObjects;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
@@ -19,6 +23,11 @@
public abstract class DexClass extends DexDefinition {
+ public interface MethodSetter {
+
+ void setMethod(int index, DexEncodedMethod method);
+ }
+
private static final DexEncodedMethod[] NO_METHODS = {};
private static final DexEncodedField[] NO_FIELDS = {};
@@ -124,20 +133,106 @@
throw new Unreachable();
}
- public DexEncodedMethod[] directMethods() {
- return directMethods;
+ public List<DexEncodedMethod> directMethods() {
+ assert directMethods != null;
+ if (InternalOptions.assertionsEnabled()) {
+ return Collections.unmodifiableList(Arrays.asList(directMethods));
+ }
+ return Arrays.asList(directMethods);
+ }
+
+ public void appendDirectMethod(DexEncodedMethod method) {
+ DexEncodedMethod[] newMethods = new DexEncodedMethod[directMethods.length + 1];
+ System.arraycopy(directMethods, 0, newMethods, 0, directMethods.length);
+ newMethods[directMethods.length] = method;
+ directMethods = newMethods;
+ assert verifyNoDuplicateMethods(directMethods);
+ }
+
+ public void appendDirectMethods(Collection<DexEncodedMethod> methods) {
+ DexEncodedMethod[] newMethods = new DexEncodedMethod[directMethods.length + methods.size()];
+ System.arraycopy(directMethods, 0, newMethods, 0, directMethods.length);
+ int i = directMethods.length;
+ for (DexEncodedMethod method : methods) {
+ newMethods[i] = method;
+ i++;
+ }
+ directMethods = newMethods;
+ assert verifyNoDuplicateMethods(directMethods);
+ }
+
+ public void removeDirectMethod(int index) {
+ DexEncodedMethod[] newMethods = new DexEncodedMethod[directMethods.length - 1];
+ System.arraycopy(directMethods, 0, newMethods, 0, index);
+ System.arraycopy(directMethods, index + 1, newMethods, index, directMethods.length - index - 1);
+ directMethods = newMethods;
+ }
+
+ public void setDirectMethod(int index, DexEncodedMethod method) {
+ directMethods[index] = method;
+ assert verifyNoDuplicateMethods(directMethods);
}
public void setDirectMethods(DexEncodedMethod[] values) {
directMethods = MoreObjects.firstNonNull(values, NO_METHODS);
+ assert verifyNoDuplicateMethods(directMethods);
}
- public DexEncodedMethod[] virtualMethods() {
- return virtualMethods;
+ public List<DexEncodedMethod> virtualMethods() {
+ assert virtualMethods != null;
+ if (InternalOptions.assertionsEnabled()) {
+ return Collections.unmodifiableList(Arrays.asList(virtualMethods));
+ }
+ return Arrays.asList(virtualMethods);
+ }
+
+ public void appendVirtualMethod(DexEncodedMethod method) {
+ DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length + 1];
+ System.arraycopy(virtualMethods, 0, newMethods, 0, virtualMethods.length);
+ newMethods[virtualMethods.length] = method;
+ virtualMethods = newMethods;
+ assert verifyNoDuplicateMethods(virtualMethods);
+ }
+
+ public void appendVirtualMethods(Collection<DexEncodedMethod> methods) {
+ DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length + methods.size()];
+ System.arraycopy(virtualMethods, 0, newMethods, 0, virtualMethods.length);
+ int i = virtualMethods.length;
+ for (DexEncodedMethod method : methods) {
+ newMethods[i] = method;
+ i++;
+ }
+ virtualMethods = newMethods;
+ assert verifyNoDuplicateMethods(virtualMethods);
+ }
+
+ public void removeVirtualMethod(int index) {
+ DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length - 1];
+ System.arraycopy(virtualMethods, 0, newMethods, 0, index);
+ System.arraycopy(
+ virtualMethods, index + 1, newMethods, index, virtualMethods.length - index - 1);
+ virtualMethods = newMethods;
+ }
+
+ public void setVirtualMethod(int index, DexEncodedMethod method) {
+ virtualMethods[index] = method;
+ assert verifyNoDuplicateMethods(virtualMethods);
}
public void setVirtualMethods(DexEncodedMethod[] values) {
virtualMethods = MoreObjects.firstNonNull(values, NO_METHODS);
+ assert verifyNoDuplicateMethods(virtualMethods);
+ }
+
+ private boolean verifyNoDuplicateMethods(DexEncodedMethod[] methods) {
+ Set<DexMethod> unique = Sets.newIdentityHashSet();
+ Arrays.stream(methods)
+ .forEach(
+ method -> {
+ boolean changed = unique.add(method.method);
+ assert changed : "Duplicate method `" + method.method.toSourceString() + "`";
+ });
+ return true;
}
public void forEachMethod(Consumer<DexEncodedMethod> consumer) {
@@ -289,14 +384,14 @@
* Find direct method in this class matching method.
*/
public DexEncodedMethod lookupDirectMethod(DexMethod method) {
- return lookupTarget(directMethods(), method);
+ return lookupTarget(directMethods, method);
}
/**
* Find virtual method in this class matching method.
*/
public DexEncodedMethod lookupVirtualMethod(DexMethod method) {
- return lookupTarget(virtualMethods(), method);
+ return lookupTarget(virtualMethods, method);
}
/**
@@ -377,7 +472,9 @@
}
public DexEncodedMethod getClassInitializer() {
- return Arrays.stream(directMethods()).filter(DexEncodedMethod::isClassInitializer).findAny()
+ return Arrays.stream(directMethods)
+ .filter(DexEncodedMethod::isClassInitializer)
+ .findAny()
.orElse(null);
}
@@ -452,7 +549,8 @@
}
public boolean classInitializationMayHaveSideEffects(AppInfo appInfo, Predicate<DexType> ignore) {
- if (ignore.test(type)) {
+ if (ignore.test(type)
+ || appInfo.dexItemFactory.libraryTypesWithoutStaticInitialization.contains(type)) {
return false;
}
if (hasNonTrivialClassInitializer()) {
@@ -483,7 +581,7 @@
public boolean defaultValuesForStaticFieldsMayTriggerAllocation() {
return Arrays.stream(staticFields())
- .anyMatch(field -> !field.getStaticValue().mayTriggerAllocation());
+ .anyMatch(field -> field.getStaticValue().mayTriggerAllocation());
}
public List<InnerClassAttribute> getInnerClasses() {
@@ -552,7 +650,7 @@
public boolean isValid() {
assert !isInterface()
- || Arrays.stream(virtualMethods()).noneMatch(method -> method.accessFlags.isFinal());
+ || Arrays.stream(virtualMethods).noneMatch(method -> method.accessFlags.isFinal());
return true;
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 32d3c37..f956b53 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -638,15 +638,15 @@
return builder.build();
}
- public DexEncodedMethod toForwardingMethod(DexClass holder, DexItemFactory itemFactory) {
+ public DexEncodedMethod toForwardingMethod(DexClass holder, AppInfo appInfo) {
checkIfObsolete();
- assert accessFlags.isPublic();
// Clear the final flag, as this method is now overwritten. Do this before creating the builder
// for the forwarding method, as the forwarding method will copy the access flags from this,
// and if different forwarding methods are created in different subclasses the first could be
// final.
accessFlags.demoteFromFinal();
- DexMethod newMethod = itemFactory.createMethod(holder.type, method.proto, method.name);
+ DexMethod newMethod =
+ appInfo.dexItemFactory.createMethod(holder.type, method.proto, method.name);
Invoke.Type type = accessFlags.isStatic() ? Invoke.Type.STATIC : Invoke.Type.SUPER;
Builder builder = builder(this);
builder.setMethod(newMethod);
@@ -656,6 +656,7 @@
builder.accessFlags.setAbstract();
} else {
// Create code that forwards the call to the target.
+ DexClass target = appInfo.definitionFor(method.holder);
builder.setCode(
new SynthesizedCode(
callerPosition ->
@@ -666,7 +667,8 @@
accessFlags.isStatic() ? null : method.holder,
method,
type,
- callerPosition),
+ callerPosition,
+ target.isInterface()),
registry -> {
if (accessFlags.isStatic()) {
registry.registerInvokeStatic(method);
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 54895ac..ebaad34 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -300,6 +300,7 @@
public final DexType metafactoryType = createType("Ljava/lang/invoke/LambdaMetafactory;");
public final DexType callSiteType = createType("Ljava/lang/invoke/CallSite;");
public final DexType lookupType = createType("Ljava/lang/invoke/MethodHandles$Lookup;");
+ public final DexType iteratorType = createType("Ljava/util/Iterator;");
public final DexType serializableType = createType("Ljava/io/Serializable;");
public final DexType externalizableType = createType("Ljava/io/Externalizable;");
public final DexType comparableType = createType("Ljava/lang/Comparable;");
@@ -350,6 +351,9 @@
createString("makeConcat")
);
+ public final Set<DexType> libraryTypesWithoutStaticInitialization =
+ ImmutableSet.of(iteratorType, serializableType);
+
private boolean skipNameValidationForTesting = false;
public void setSkipNameValidationForTesting(boolean skipNameValidationForTesting) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index dafb07c..57b08fb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -226,7 +226,7 @@
public boolean hasMethodsOrFields() {
int numberOfFields = staticFields().length + instanceFields().length;
- int numberOfMethods = directMethods().length + virtualMethods().length;
+ int numberOfMethods = directMethods().size() + virtualMethods().size();
return numberOfFields + numberOfMethods > 0;
}
@@ -269,7 +269,7 @@
// It does not actually hurt to compute this multiple times. So racing on staticValues is OK.
if (staticValues == SENTINEL_NOT_YET_COMPUTED) {
synchronized (staticFields) {
- assert PresortedComparable.isSorted(staticFields);
+ assert PresortedComparable.isSorted(Arrays.asList(staticFields));
DexEncodedField[] fields = staticFields;
int length = 0;
List<DexValue> values = new ArrayList<>(fields.length);
diff --git a/src/main/java/com/android/tools/r8/graph/PresortedComparable.java b/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
index a4ee2ee..563b40a 100644
--- a/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
+++ b/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
@@ -4,15 +4,23 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.naming.NamingLens;
+import java.util.Arrays;
+import java.util.List;
import java.util.function.Function;
public interface PresortedComparable<T> extends Presorted, Comparable<T> {
- static <T extends PresortedComparable<T>> boolean isSorted(KeyedDexItem<T>[] items) {
+ static <T extends PresortedComparable<T>> boolean isSorted(
+ List<? extends KeyedDexItem<T>> items) {
return isSorted(items, KeyedDexItem::getKey);
}
static <S, T extends Comparable<T>> boolean isSorted(S[] items, Function<S, T> getter) {
+ return isSorted(Arrays.asList(items), getter);
+ }
+
+ static <S, T extends Comparable<T>> boolean isSorted(
+ List<? extends S> items, Function<S, T> getter) {
T current = null;
for (S item : items) {
T next = getter.apply(item);
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
index 2aa7f47..c48740d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
@@ -14,6 +14,8 @@
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
public class ConstMethodHandle extends ConstInstruction {
@@ -69,6 +71,12 @@
}
@Override
+ public ConstraintWithTarget inliningConstraint(
+ InliningConstraints inliningConstraints, DexType invocationContext) {
+ return inliningConstraints.forConstMethodHandle();
+ }
+
+ @Override
public int maxOutValueRegister() {
return Constants.U8BIT_MAX;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 8a46e1e..743f2a5 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.cf.code.CfFrame;
import com.android.tools.r8.cf.code.CfFrame.FrameType;
import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.cf.code.CfLabel;
import com.android.tools.r8.cf.code.CfPosition;
import com.android.tools.r8.cf.code.CfTryCatch;
@@ -21,6 +22,7 @@
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -179,9 +181,23 @@
CodeRewriter.collapseTrivialGotos(method, code);
DexBuilder.removeRedundantDebugPositions(code);
CfCode code = buildCfCode();
+ assert verifyInvokeInterface(code, appInfo);
return code;
}
+ private static boolean verifyInvokeInterface(CfCode code, AppInfo appInfo) {
+ for (CfInstruction instruction : code.instructions) {
+ if (instruction instanceof CfInvoke) {
+ CfInvoke invoke = (CfInvoke) instruction;
+ if (invoke.getMethod().holder.isClassType()) {
+ DexClass holder = appInfo.definitionFor(invoke.getMethod().holder);
+ assert holder == null || holder.isInterface() == invoke.isInterface();
+ }
+ }
+ }
+ return true;
+ }
+
public DexField resolveField(DexField field) {
DexEncodedField resolvedField = appInfo.resolveFieldOn(field.clazz, field);
return resolvedField == null ? field : resolvedField.field;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 31b0ec6..3f355c1 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -1405,19 +1405,6 @@
add(Invoke.create(type, item, callSiteProto, null, arguments, itf));
}
- public void addInvoke(Type type, DexItem item, DexProto callSiteProto, List<Value> arguments) {
- addInvoke(type, item, callSiteProto, arguments, false);
- }
-
- public void addInvoke(
- Type type,
- DexItem item,
- DexProto callSiteProto,
- List<ValueType> types,
- List<Integer> registers) {
- addInvoke(type, item, callSiteProto, types, registers, false);
- }
-
public void addInvoke(
Type type,
DexItem item,
@@ -1518,7 +1505,9 @@
registerIndex += constraint.requiredRegisters();
}
checkInvokeArgumentRegisters(registerIndex, argumentRegisterCount);
- addInvoke(type, method, callSiteProto, arguments);
+ // Note: We only call this register variant from DEX inputs where isInterface does not matter.
+ assert !isGeneratingClassFiles();
+ addInvoke(type, method, callSiteProto, arguments, false /* isInterface */);
}
public void addInvokeNewArray(DexType type, int argumentCount, int[] argumentRegisters) {
@@ -1538,7 +1527,7 @@
registerIndex += constraint.requiredRegisters();
}
checkInvokeArgumentRegisters(registerIndex, argumentCount);
- addInvoke(Invoke.Type.NEW_ARRAY, type, null, arguments);
+ addInvoke(Invoke.Type.NEW_ARRAY, type, null, arguments, false /* isInterface */);
}
public void addMultiNewArray(DexType type, int dest, int[] dimensions) {
@@ -1547,7 +1536,7 @@
for (int dimension : dimensions) {
arguments.add(readRegister(dimension, ValueTypeConstraint.INT));
}
- addInvoke(Invoke.Type.MULTI_NEW_ARRAY, type, null, arguments);
+ addInvoke(Invoke.Type.MULTI_NEW_ARRAY, type, null, arguments, false /* isInterface */);
addMoveResult(dest);
}
@@ -1581,7 +1570,9 @@
register += valueTypeConstraint.requiredRegisters();
}
checkInvokeArgumentRegisters(register, firstArgumentRegister + argumentCount);
- addInvoke(type, method, callSiteProto, arguments);
+ // Note: We only call this register variant from DEX inputs where isInterface does not matter.
+ assert !isGeneratingClassFiles();
+ addInvoke(type, method, callSiteProto, arguments, false /* isInterface */);
}
public void addInvokeRangeNewArray(DexType type, int argumentCount, int firstArgumentRegister) {
@@ -1597,7 +1588,9 @@
register += constraint.requiredRegisters();
}
checkInvokeArgumentRegisters(register, firstArgumentRegister + argumentCount);
- addInvoke(Invoke.Type.NEW_ARRAY, type, null, arguments);
+ // Note: We only call this register variant from DEX inputs where isInterface does not matter.
+ assert !isGeneratingClassFiles();
+ addInvoke(Invoke.Type.NEW_ARRAY, type, null, arguments, false /* isInterface */);
}
private void checkInvokeArgumentRegisters(int expected, int actual) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 1387f75..93e5355 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -2864,7 +2864,13 @@
List<ValueType> argumentTypes = Arrays.asList(valueType(CLASS_TYPE), valueType(INT_ARRAY_TYPE));
List<Integer> argumentRegisters = Arrays.asList(classDestTemp, dimensionsDestTemp);
builder.ensureBlockForThrowingInstruction();
- builder.addInvoke(Invoke.Type.STATIC, newInstance, null, argumentTypes, argumentRegisters);
+ builder.addInvoke(
+ Invoke.Type.STATIC,
+ newInstance,
+ null,
+ argumentTypes,
+ argumentRegisters,
+ false /* isInterface */);
// Pop the temporaries and push the final result.
state.pop(); // classDestTemp.
state.pop(); // dimensionsDestTemp.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
index 3554806..81f93a7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
@@ -109,8 +109,17 @@
}
// Method call to the original impl-method.
- add(builder -> builder.addInvoke(inferInvokeType(),
- implMethod, implMethod.proto, argValueTypes, argRegisters));
+ // Mirroring assert in constructor, we never need accessors to interfaces.
+ assert !descriptor().implHandle.type.isInvokeInterface();
+ add(
+ builder ->
+ builder.addInvoke(
+ inferInvokeType(),
+ implMethod,
+ implMethod.proto,
+ argValueTypes,
+ argRegisters,
+ false /* isInterface */));
// Does the method have return value?
if (proto.returnType == factory().voidType) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index d2a233b..ec788a4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -15,7 +15,6 @@
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
import com.google.common.collect.Sets;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Iterator;
@@ -85,25 +84,22 @@
}
// Add the methods.
- DexEncodedMethod[] existing = clazz.virtualMethods();
- clazz.setVirtualMethods(new DexEncodedMethod[existing.length + methodsToImplement.size()]);
- System.arraycopy(existing, 0, clazz.virtualMethods(), 0, existing.length);
-
- for (int i = 0; i < methodsToImplement.size(); i++) {
- DexEncodedMethod method = methodsToImplement.get(i);
+ List<DexEncodedMethod> newForwardingMethods = new ArrayList<>(methodsToImplement.size());
+ for (DexEncodedMethod method : methodsToImplement) {
assert method.accessFlags.isPublic() && !method.accessFlags.isAbstract();
DexEncodedMethod newMethod = addForwardingMethod(method, clazz);
- clazz.virtualMethods()[existing.length + i] = newMethod;
+ newForwardingMethods.add(newMethod);
createdMethods.put(newMethod, method);
}
+ clazz.appendVirtualMethods(newForwardingMethods);
}
private DexEncodedMethod addForwardingMethod(DexEncodedMethod defaultMethod, DexClass clazz) {
DexMethod method = defaultMethod.method;
+ DexClass target = rewriter.findDefinitionFor(method.holder);
// NOTE: Never add a forwarding method to methods of classes unknown or coming from android.jar
// even if this results in invalid code, these classes are never desugared.
- assert rewriter.findDefinitionFor(method.holder) != null
- && !rewriter.findDefinitionFor(method.holder).isLibraryClass();
+ assert target != null && !target.isLibraryClass();
// New method will have the same name, proto, and also all the flags of the
// default method, including bridge flag.
DexMethod newMethod = rewriter.factory.createMethod(clazz.type, method.proto, method.name);
@@ -124,7 +120,8 @@
null /* static method */,
rewriter.defaultAsMethodOfCompanionClass(method),
Invoke.Type.STATIC,
- callerPosition)));
+ callerPosition,
+ target.isInterface())));
}
// For a given class `clazz` inspects all interfaces it implements directly or
@@ -148,7 +145,7 @@
helper.merge(rewriter.getOrCreateInterfaceInfo(clazz, current, type));
}
- accumulatedVirtualMethods.addAll(Arrays.asList(clazz.virtualMethods()));
+ accumulatedVirtualMethods.addAll(clazz.virtualMethods());
List<DexEncodedMethod> defaultMethodsInDirectInterface = helper.createFullList();
@@ -201,7 +198,7 @@
current = clazz;
while (true) {
// Hide candidates by virtual method of the class.
- hideCandidates(Arrays.asList(current.virtualMethods()), candidates, toBeImplemented);
+ hideCandidates(current.virtualMethods(), candidates, toBeImplemented);
if (candidates.isEmpty()) {
return toBeImplemented;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
index 955abf0..0c76382 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
@@ -96,16 +96,7 @@
method.annotations.keepIf(x -> !isCovariantReturnTypeAnnotation(x.annotation));
}
// Add the newly constructed methods to the class.
- DexEncodedMethod[] oldVirtualMethods = clazz.virtualMethods();
- DexEncodedMethod[] newVirtualMethods =
- new DexEncodedMethod[oldVirtualMethods.length + covariantReturnTypeMethods.size()];
- System.arraycopy(oldVirtualMethods, 0, newVirtualMethods, 0, oldVirtualMethods.length);
- int i = oldVirtualMethods.length;
- for (DexEncodedMethod syntheticMethod : covariantReturnTypeMethods) {
- newVirtualMethods[i] = syntheticMethod;
- i++;
- }
- clazz.setVirtualMethods(newVirtualMethods);
+ clazz.appendVirtualMethods(covariantReturnTypeMethods);
}
// Processes all the dalvik.annotation.codegen.CovariantReturnType and dalvik.annotation.codegen.
@@ -175,7 +166,8 @@
method.method,
Invoke.Type.VIRTUAL,
callerPosition,
- true)));
+ false /* isInterface */,
+ true /* castResult */)));
// Optimize to generate DexCode instead of SynthesizedCode.
converter.optimizeSynthesizedMethod(newVirtualMethod);
return newVirtualMethod;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index 01b647b..9e18c94 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -114,7 +114,7 @@
}
// If at least one bridge method was removed then update the table.
- if (remainingMethods.size() < iface.virtualMethods().length) {
+ if (remainingMethods.size() < iface.virtualMethods().size()) {
iface.setVirtualMethods(remainingMethods.toArray(
new DexEncodedMethod[remainingMethods.size()]));
}
@@ -169,7 +169,7 @@
}
}
}
- if (remainingMethods.size() < iface.directMethods().length) {
+ if (remainingMethods.size() < iface.directMethods().size()) {
iface.setDirectMethods(remainingMethods.toArray(
new DexEncodedMethod[remainingMethods.size()]));
}
@@ -252,7 +252,8 @@
null,
origMethod,
Type.STATIC,
- callerPosition)));
+ callerPosition,
+ true /* isInterface */)));
newEncodedMethod.getMutableOptimizationInfo().markNeverInline();
dispatchMethods.add(newEncodedMethod);
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSourceCode.java
index 7e5cd81..9507471 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSourceCode.java
@@ -46,8 +46,15 @@
}
// Method call to the main functional interface method.
- add(builder -> builder.addInvoke(Invoke.Type.VIRTUAL,
- this.mainMethod, this.mainMethod.proto, argValueTypes, argRegisters));
+ add(
+ builder ->
+ builder.addInvoke(
+ Invoke.Type.VIRTUAL,
+ this.mainMethod,
+ this.mainMethod.proto,
+ argValueTypes,
+ argRegisters,
+ false /* isInterface */));
// Does the method have return value?
if (proto.returnType == factory().voidType) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index b78f6df..a0bc184 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -513,9 +513,9 @@
DexMethod implMethod = descriptor.implHandle.asMethod();
DexClass implMethodHolder = definitionFor(implMethod.holder);
- DexEncodedMethod[] directMethods = implMethodHolder.directMethods();
- for (int i = 0; i < directMethods.length; i++) {
- DexEncodedMethod encodedMethod = directMethods[i];
+ List<DexEncodedMethod> directMethods = implMethodHolder.directMethods();
+ for (int i = 0; i < directMethods.size(); i++) {
+ DexEncodedMethod encodedMethod = directMethods.get(i);
if (implMethod.match(encodedMethod)) {
// We need to create a new static method with the same code to be able to safely
// relax its accessibility without making it virtual.
@@ -539,7 +539,7 @@
dexCode.setDebugInfo(dexCode.debugInfoWithAdditionalFirstParameter(null));
assert (dexCode.getDebugInfo() == null)
|| (callTarget.getArity() == dexCode.getDebugInfo().parameters.length);
- directMethods[i] = newMethod;
+ implMethodHolder.setDirectMethod(i, newMethod);
return true;
}
}
@@ -579,20 +579,11 @@
// We may arrive here concurrently so we need must update the methods of the class atomically.
synchronized (accessorClass) {
- accessorClass.setDirectMethods(
- appendMethod(accessorClass.directMethods(), accessorEncodedMethod));
+ accessorClass.appendDirectMethod(accessorEncodedMethod);
}
rewriter.converter.optimizeSynthesizedMethod(accessorEncodedMethod);
return true;
}
-
- private DexEncodedMethod[] appendMethod(DexEncodedMethod[] methods, DexEncodedMethod method) {
- int size = methods.length;
- DexEncodedMethod[] newMethods = new DexEncodedMethod[size + 1];
- System.arraycopy(methods, 0, newMethods, 0, size);
- newMethods[size] = method;
- return newMethods;
- }
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java
index 2aa4630..cec70bb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java
@@ -24,12 +24,15 @@
// Create and initialize an instance.
int instance = nextRegister(ValueType.OBJECT);
add(builder -> builder.addNewInstance(instance, lambda.type));
- add(builder -> builder.addInvoke(
- Invoke.Type.DIRECT,
- lambda.constructor,
- lambda.constructor.proto,
- ImmutableList.of(ValueType.OBJECT),
- ImmutableList.of(instance)));
+ add(
+ builder ->
+ builder.addInvoke(
+ Invoke.Type.DIRECT,
+ lambda.constructor,
+ lambda.constructor.proto,
+ ImmutableList.of(ValueType.OBJECT),
+ ImmutableList.of(instance),
+ false /* isInterface */));
// Assign to a field.
add(builder -> builder.addStaticPut(instance, lambda.instanceField));
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSourceCode.java
index 7f2acc3..b2b9486 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSourceCode.java
@@ -23,8 +23,14 @@
protected void prepareInstructions() {
// Super constructor call (always java.lang.Object.<init>()).
DexMethod objectInitMethod = lambda.rewriter.objectInitMethod;
- add(builder -> builder.addInvoke(Invoke.Type.DIRECT, objectInitMethod,
- objectInitMethod.proto, Collections.singletonList(getReceiverValue())));
+ add(
+ builder ->
+ builder.addInvoke(
+ Invoke.Type.DIRECT,
+ objectInitMethod,
+ objectInitMethod.proto,
+ Collections.singletonList(getReceiverValue()),
+ false /* isInterface */));
// Assign capture fields.
DexType[] capturedTypes = captures();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
index fb06f31..99112d7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
@@ -224,8 +224,15 @@
}
// Method call to the method implementing lambda or method-ref.
- add(builder -> builder.addInvoke(target.invokeType,
- methodToCall, methodToCall.proto, argValueTypes, argRegisters));
+ add(
+ builder ->
+ builder.addInvoke(
+ target.invokeType,
+ methodToCall,
+ methodToCall.proto,
+ argValueTypes,
+ argRegisters,
+ false /* isInterface */));
// Does the method have return value?
if (enforcedReturnType.isVoidType()) {
@@ -446,8 +453,15 @@
List<ValueType> argValueTypes = ImmutableList.of(ValueType.OBJECT);
List<Integer> argRegisters = Collections.singletonList(register);
- add(builder -> builder.addInvoke(Invoke.Type.VIRTUAL,
- method, method.proto, argValueTypes, argRegisters));
+ add(
+ builder ->
+ builder.addInvoke(
+ Invoke.Type.VIRTUAL,
+ method,
+ method.proto,
+ argValueTypes,
+ argRegisters,
+ false /* isInterface */));
ValueType valueType = ValueType.fromDexType(primitiveType);
int result = nextRegister(valueType);
@@ -469,8 +483,15 @@
ValueType valueType = ValueType.fromDexType(primitiveType);
List<ValueType> argValueTypes = ImmutableList.of(valueType);
List<Integer> argRegisters = Collections.singletonList(register);
- add(builder -> builder.addInvoke(Invoke.Type.STATIC,
- method, method.proto, argValueTypes, argRegisters));
+ add(
+ builder ->
+ builder.addInvoke(
+ Invoke.Type.STATIC,
+ method,
+ method.proto,
+ argValueTypes,
+ argRegisters,
+ false /* isInterface */));
int result = nextRegister(ValueType.OBJECT);
add(builder -> builder.addMoveResult(result));
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index df52ef1..4e29e6f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -129,33 +129,26 @@
/** Remove lambda deserialization methods. */
public boolean removeLambdaDeserializationMethods(Iterable<DexProgramClass> classes) {
- boolean anyRemoved = false;
for (DexProgramClass clazz : classes) {
// Search for a lambda deserialization method and remove it if found.
- DexEncodedMethod[] directMethods = clazz.directMethods();
+ List<DexEncodedMethod> directMethods = clazz.directMethods();
if (directMethods != null) {
- int methodCount = directMethods.length;
+ int methodCount = directMethods.size();
for (int i = 0; i < methodCount; i++) {
- DexEncodedMethod encoded = directMethods[i];
+ DexEncodedMethod encoded = directMethods.get(i);
DexMethod method = encoded.method;
if (method.isLambdaDeserializeMethod(appInfo.dexItemFactory)) {
assert encoded.accessFlags.isStatic();
assert encoded.accessFlags.isSynthetic();
-
- DexEncodedMethod[] newMethods = new DexEncodedMethod[methodCount - 1];
- System.arraycopy(directMethods, 0, newMethods, 0, i);
- System.arraycopy(directMethods, i + 1, newMethods, i, methodCount - i - 1);
- clazz.setDirectMethods(newMethods);
-
- anyRemoved = true;
+ clazz.removeDirectMethod(i);
// We assume there is only one such method in the class.
- break;
+ return true;
}
}
}
}
- return anyRemoved;
+ return false;
}
/**
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 359223d..30712e6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1055,8 +1055,9 @@
// that it is not the instance being initialized.
//
boolean instanceInitializer = method.isInstanceInitializer();
- if (method.accessFlags.isNative() ||
- (!method.isNonAbstractVirtualMethod() && !instanceInitializer)) {
+ if (method.accessFlags.isNative()
+ || (!method.isNonAbstractVirtualMethod() && !instanceInitializer)
+ || method.accessFlags.isSynchronized()) {
return;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index 3292704..873eb20 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -259,6 +259,10 @@
return ConstraintWithTarget.ALWAYS;
}
+ public ConstraintWithTarget forConstMethodHandle() {
+ return ConstraintWithTarget.NEVER;
+ }
+
private ConstraintWithTarget forFieldInstruction(
DexField field, DexEncodedField target, DexType invocationContext) {
// Resolve the field if possible and decide whether the instruction can inlined.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index 4cc3207..ff330e1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -179,9 +179,9 @@
}
// Change the return type of direct methods that return an uninstantiated type to void.
- DexEncodedMethod[] directMethods = clazz.directMethods();
- for (int i = 0; i < directMethods.length; ++i) {
- DexEncodedMethod encodedMethod = directMethods[i];
+ List<DexEncodedMethod> directMethods = clazz.directMethods();
+ for (int i = 0; i < directMethods.size(); ++i) {
+ DexEncodedMethod encodedMethod = directMethods.get(i);
DexMethod method = encodedMethod.method;
RewrittenPrototypeDescription prototypeChanges =
prototypeChangesPerMethod.getOrDefault(
@@ -194,7 +194,7 @@
// TODO(b/110806787): Can be extended to handle collisions by renaming the given
// method.
if (usedSignatures.add(wrapper)) {
- directMethods[i] = encodedMethod.toTypeSubstitutedMethod(newMethod);
+ clazz.setDirectMethod(i, encodedMethod.toTypeSubstitutedMethod(newMethod));
methodMapping.put(method, newMethod);
if (removedArgumentsInfo.hasRemovedArguments()) {
removedArgumentsInfoPerMethod.put(newMethod, removedArgumentsInfo);
@@ -209,9 +209,9 @@
// all supertypes of the current class are always visited prior to the current class.
// This is important to ensure that a method that used to override a method in its super
// class will continue to do so after this optimization.
- DexEncodedMethod[] virtualMethods = clazz.virtualMethods();
- for (int i = 0; i < virtualMethods.length; ++i) {
- DexEncodedMethod encodedMethod = virtualMethods[i];
+ List<DexEncodedMethod> virtualMethods = clazz.virtualMethods();
+ for (int i = 0; i < virtualMethods.size(); ++i) {
+ DexEncodedMethod encodedMethod = virtualMethods.get(i);
DexMethod method = encodedMethod.method;
RewrittenPrototypeDescription prototypeChanges =
getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL);
@@ -229,13 +229,13 @@
boolean signatureIsAvailable = usedSignatures.add(wrapper);
assert signatureIsAvailable;
- virtualMethods[i] = encodedMethod.toTypeSubstitutedMethod(newMethod);
+ clazz.setVirtualMethod(i, encodedMethod.toTypeSubstitutedMethod(newMethod));
methodMapping.put(method, newMethod);
}
}
}
- for (int i = 0; i < virtualMethods.length; ++i) {
- DexEncodedMethod encodedMethod = virtualMethods[i];
+ for (int i = 0; i < virtualMethods.size(); ++i) {
+ DexEncodedMethod encodedMethod = virtualMethods.get(i);
DexMethod method = encodedMethod.method;
RewrittenPrototypeDescription prototypeChanges =
getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL);
@@ -249,7 +249,7 @@
if (!methodPool.hasSeen(wrapper) && usedSignatures.add(wrapper)) {
methodPool.seen(wrapper);
- virtualMethods[i] = encodedMethod.toTypeSubstitutedMethod(newMethod);
+ clazz.setVirtualMethod(i, encodedMethod.toTypeSubstitutedMethod(newMethod));
methodMapping.put(method, newMethod);
boolean added =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index aae8429..6211270 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -184,12 +184,13 @@
for (DexEncodedMethod method : clazz.methods()) {
signatures.markSignatureAsUsed(method.method);
}
- for (int i = 0; i < clazz.directMethods().length; i++) {
- DexEncodedMethod method = clazz.directMethods()[i];
+ List<DexEncodedMethod> directMethods = clazz.directMethods();
+ for (int i = 0; i < directMethods.size(); i++) {
+ DexEncodedMethod method = directMethods.get(i);
RemovedArgumentsInfo unused = collectUnusedArguments(method);
DexEncodedMethod newMethod = signatures.removeArguments(method, unused);
if (newMethod != null) {
- clazz.directMethods()[i] = newMethod;
+ clazz.setDirectMethod(i, newMethod);
synchronized (this) {
methodMapping.put(method.method, newMethod.method);
removedArguments.put(newMethod.method, unused);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java
index 5177e4a..c6aecbb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java
@@ -41,18 +41,27 @@
List<ValueType> argTypes = Lists.newArrayList(ValueType.OBJECT, ValueType.INT);
List<Integer> argRegisters = Lists.newArrayList(instance, lambdaId);
- group.forEachLambda(info -> {
- DexType lambda = info.clazz.type;
- if (group.isSingletonLambda(lambda)) {
- int id = group.lambdaId(lambda);
- add(builder -> builder.addNewInstance(instance, groupClassType));
- add(builder -> builder.addConst(TypeLatticeElement.INT, lambdaId, id));
- add(builder -> builder.addInvoke(Type.DIRECT,
- lambdaConstructorMethod, lambdaConstructorMethod.proto, argTypes, argRegisters));
- add(builder -> builder.addStaticPut(
- instance, group.getSingletonInstanceField(factory, id)));
- }
- });
+ group.forEachLambda(
+ info -> {
+ DexType lambda = info.clazz.type;
+ if (group.isSingletonLambda(lambda)) {
+ int id = group.lambdaId(lambda);
+ add(builder -> builder.addNewInstance(instance, groupClassType));
+ add(builder -> builder.addConst(TypeLatticeElement.INT, lambdaId, id));
+ add(
+ builder ->
+ builder.addInvoke(
+ Type.DIRECT,
+ lambdaConstructorMethod,
+ lambdaConstructorMethod.proto,
+ argTypes,
+ argRegisters,
+ false /* isInterface*/));
+ add(
+ builder ->
+ builder.addStaticPut(instance, group.getSingletonInstanceField(factory, id)));
+ }
+ });
assert this.nextInstructionIndex() > 0 : "no single field initialized";
add(IRBuilder::addReturn);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java
index e8fcd40..2bdc441 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java
@@ -215,8 +215,15 @@
@Override
void prepareSuperConstructorCall(int receiverRegister) {
- add(builder -> builder.addInvoke(Type.DIRECT, objectInitializer, objectInitializer.proto,
- Lists.newArrayList(ValueType.OBJECT), Lists.newArrayList(receiverRegister)));
+ add(
+ builder ->
+ builder.addInvoke(
+ Type.DIRECT,
+ objectInitializer,
+ objectInitializer.proto,
+ Lists.newArrayList(ValueType.OBJECT),
+ Lists.newArrayList(receiverRegister),
+ false /* isInterface */));
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
index f677b46..ee0b800 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
@@ -233,9 +233,15 @@
void prepareSuperConstructorCall(int receiverRegister) {
int arityRegister = nextRegister(ValueType.INT);
add(builder -> builder.addConst(TypeLatticeElement.INT, arityRegister, arity));
- add(builder -> builder.addInvoke(Type.DIRECT, lambdaInitializer, lambdaInitializer.proto,
- Lists.newArrayList(ValueType.OBJECT, ValueType.INT),
- Lists.newArrayList(receiverRegister, arityRegister)));
+ add(
+ builder ->
+ builder.addInvoke(
+ Type.DIRECT,
+ lambdaInitializer,
+ lambdaInitializer.proto,
+ Lists.newArrayList(ValueType.OBJECT, ValueType.INT),
+ Lists.newArrayList(receiverRegister, arityRegister),
+ false /* isInterface */));
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
index 8469004..234bcc6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
@@ -144,8 +144,7 @@
}
void validateDirectMethods(DexClass lambda) throws LambdaStructureError {
- DexEncodedMethod[] directMethods = lambda.directMethods();
- for (DexEncodedMethod method : directMethods) {
+ for (DexEncodedMethod method : lambda.directMethods()) {
if (method.isClassInitializer()) {
// We expect to see class initializer only if there is a singleton field.
if (lambda.staticFields().length != 1) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaVirtualMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaVirtualMethodSourceCode.java
index 82acf49..bcd0a75 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaVirtualMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaVirtualMethodSourceCode.java
@@ -85,16 +85,18 @@
offsets[i] = nextInstructionIndex();
// Emit fake call on `this` receiver.
- add(builder -> {
- if (arguments.isEmpty()) {
- // Late initialization of argument list.
- arguments.add(getReceiverValue());
- for (int index = 0; index < paramCount; index++) {
- arguments.add(getParamValue(index));
- }
- }
- builder.addInvoke(Type.VIRTUAL, impl.method, impl.method.proto, arguments);
- });
+ add(
+ builder -> {
+ if (arguments.isEmpty()) {
+ // Late initialization of argument list.
+ arguments.add(getReceiverValue());
+ for (int index = 0; index < paramCount; index++) {
+ arguments.add(getParamValue(index));
+ }
+ }
+ builder.addInvoke(
+ Type.VIRTUAL, impl.method, impl.method.proto, arguments, false /* isInterface */);
+ });
// Handle return value if needed.
if (returnsValue) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index 6284338..0fd8cc4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -548,16 +548,13 @@
}
// Process static methods.
- if (candidateClass.directMethods().length > 0) {
- DexEncodedMethod[] oldMethods = hostClass.directMethods();
- DexEncodedMethod[] extraMethods = candidateClass.directMethods();
- DexEncodedMethod[] newMethods = new DexEncodedMethod[oldMethods.length + extraMethods.length];
- System.arraycopy(oldMethods, 0, newMethods, 0, oldMethods.length);
- for (int i = 0; i < extraMethods.length; i++) {
- DexEncodedMethod method = extraMethods[i];
+ List<DexEncodedMethod> extraMethods = candidateClass.directMethods();
+ if (!extraMethods.isEmpty()) {
+ List<DexEncodedMethod> newMethods = new ArrayList<>(extraMethods.size());
+ for (DexEncodedMethod method : extraMethods) {
DexEncodedMethod newMethod = method.toTypeSubstitutedMethod(
factory().createMethod(hostType, method.method.proto, method.method.name));
- newMethods[oldMethods.length + i] = newMethod;
+ newMethods.add(newMethod);
staticizedMethods.add(newMethod);
staticizedMethods.remove(method);
DexMethod originalMethod = methodMapping.inverse().get(method.method);
@@ -567,7 +564,7 @@
methodMapping.put(originalMethod, newMethod.method);
}
}
- hostClass.setDirectMethods(newMethods);
+ hostClass.appendDirectMethods(newMethods);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodSourceCode.java
index 37341fb..76dba16 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodSourceCode.java
@@ -23,6 +23,7 @@
private final DexMethod target;
private final Invoke.Type invokeType;
private final boolean castResult;
+ private final boolean isInterface;
public ForwardMethodSourceCode(
DexType receiver,
@@ -31,7 +32,8 @@
DexType targetReceiver,
DexMethod target,
Type invokeType,
- Position callerPosition) {
+ Position callerPosition,
+ boolean isInterface) {
this(
receiver,
method,
@@ -40,6 +42,7 @@
target,
invokeType,
callerPosition,
+ isInterface,
false);
}
@@ -51,6 +54,7 @@
DexMethod target,
Type invokeType,
Position callerPosition,
+ boolean isInterface,
boolean castResult) {
super(receiver, method, callerPosition, originalMethod);
assert (targetReceiver == null) == (invokeType == Invoke.Type.STATIC);
@@ -58,6 +62,7 @@
this.target = target;
this.targetReceiver = targetReceiver;
this.invokeType = invokeType;
+ this.isInterface = isInterface;
this.castResult = castResult;
assert checkSignatures();
@@ -119,8 +124,15 @@
}
// Method call to the target method.
- add(builder -> builder.addInvoke(this.invokeType,
- this.target, this.target.proto, argValueTypes, argRegisters));
+ add(
+ builder ->
+ builder.addInvoke(
+ this.invokeType,
+ this.target,
+ this.target.proto,
+ argValueTypes,
+ argRegisters,
+ this.isInterface));
// Does the method return value?
if (proto.returnType.isVoidType()) {
diff --git a/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java b/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
index 18998c8..4bfb5ce 100644
--- a/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
+++ b/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import org.objectweb.asm.Handle;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
@@ -116,6 +117,8 @@
if (cst instanceof Type && ((Type) cst).getSort() != Type.METHOD) {
DexType type = application.getType((Type) cst);
updateConstraint(inliningConstraints.forConstClass(type, invocationContext));
+ } else if (cst instanceof Handle) {
+ updateConstraint(inliningConstraints.forConstMethodHandle());
} else {
updateConstraint(inliningConstraints.forConstInstruction());
}
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedNameMapPrinter.java b/src/main/java/com/android/tools/r8/naming/MinifiedNameMapPrinter.java
index 3ae9d06..e3d09b6 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedNameMapPrinter.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedNameMapPrinter.java
@@ -16,6 +16,7 @@
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
@@ -38,6 +39,12 @@
return copy;
}
+ private <T> List<T> sortedCopy(List<T> source, Comparator<? super T> comparator) {
+ List<T> copy = new ArrayList<>(source);
+ Collections.sort(copy, comparator);
+ return copy;
+ }
+
private void writeClass(DexProgramClass clazz, StringBuilder out) {
seenTypes.add(clazz.type);
DexString descriptor = namingLens.lookupDescriptor(clazz.type);
@@ -87,7 +94,7 @@
out.append(renamed).append(NEW_LINE);
}
- private void writeMethods(DexEncodedMethod[] methods, StringBuilder out) {
+ private void writeMethods(List<DexEncodedMethod> methods, StringBuilder out) {
for (DexEncodedMethod encodedMethod : methods) {
DexMethod method = encodedMethod.method;
DexString renamed = namingLens.lookupName(method);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
index d5add45..b0e2f4c 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClass.MethodSetter;
import com.android.tools.r8.graph.DexEncodedAnnotation;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -25,6 +26,7 @@
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
@@ -317,18 +319,18 @@
clazz.superType = substituteType(clazz.superType, null);
clazz.interfaces = substituteTypesIn(clazz.interfaces);
clazz.annotations = substituteTypesIn(clazz.annotations);
- clazz.setDirectMethods(substituteTypesIn(clazz.directMethods()));
- clazz.setVirtualMethods(substituteTypesIn(clazz.virtualMethods()));
+ fixupMethods(clazz.directMethods(), clazz::setDirectMethod);
+ fixupMethods(clazz.virtualMethods(), clazz::setVirtualMethod);
clazz.setStaticFields(substituteTypesIn(clazz.staticFields()));
clazz.setInstanceFields(substituteTypesIn(clazz.instanceFields()));
}
- private DexEncodedMethod[] substituteTypesIn(DexEncodedMethod[] methods) {
+ private void fixupMethods(List<DexEncodedMethod> methods, MethodSetter setter) {
if (methods == null) {
- return null;
+ return;
}
- for (int i = 0; i < methods.length; i++) {
- DexEncodedMethod encodedMethod = methods[i];
+ for (int i = 0; i < methods.size(); i++) {
+ DexEncodedMethod encodedMethod = methods.get(i);
DexMethod appliedMethod = appliedLense.lookupMethod(encodedMethod.method);
DexType newHolderType = substituteType(appliedMethod.holder, encodedMethod);
DexProto newProto = substituteTypesIn(appliedMethod.proto, encodedMethod);
@@ -341,9 +343,8 @@
newMethod = appliedMethod;
}
// Explicitly fix members.
- methods[i] = encodedMethod.toTypeSubstitutedMethod(newMethod);
+ setter.setMethod(i, encodedMethod.toTypeSubstitutedMethod(newMethod));
}
- return methods;
}
private DexEncodedField[] substituteTypesIn(DexEncodedField[] fields) {
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 3c300b6..3ff88b9 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -120,8 +120,10 @@
}
private void computeMethodRebinding(
- Set<DexMethod> methods, Function<DexMethod, DexEncodedMethod> lookupTarget, Type invokeType) {
- for (DexMethod method : methods) {
+ Map<DexMethod, Set<DexEncodedMethod>> methodsWithContexts,
+ Function<DexMethod, DexEncodedMethod> lookupTarget,
+ Type invokeType) {
+ for (DexMethod method : methodsWithContexts.keySet()) {
// We can safely ignore array types, as the corresponding methods are defined in a library.
if (!method.getHolder().isClassType()) {
continue;
@@ -149,7 +151,10 @@
// If the target class is not public but the targeted method is, we might run into
// visibility problems when rebinding.
- if (mayNeedBridgeForVisibility(target, targetClass)) {
+ final DexEncodedMethod finalTarget = target;
+ Set<DexEncodedMethod> contexts = methodsWithContexts.get(method);
+ if (contexts.stream().anyMatch(context ->
+ mayNeedBridgeForVisibility(context.method.getHolder(), finalTarget))) {
target =
insertBridgeForVisibilityIfNeeded(
method, target, originalClass, targetClass, lookupTarget);
@@ -186,7 +191,7 @@
findHolderForInterfaceMethodBridge(originalClass, targetClass.type);
assert bridgeHolder != null;
assert bridgeHolder != targetClass;
- DexEncodedMethod bridgeMethod = target.toForwardingMethod(bridgeHolder, appInfo.dexItemFactory);
+ DexEncodedMethod bridgeMethod = target.toForwardingMethod(bridgeHolder, appInfo);
bridgeHolder.addMethod(bridgeMethod);
assert lookupTarget.apply(method) == bridgeMethod;
return bridgeMethod;
@@ -205,8 +210,20 @@
return findHolderForInterfaceMethodBridge(superClass.asProgramClass(), iface);
}
- private boolean mayNeedBridgeForVisibility(DexEncodedMethod target, DexClass targetClass) {
- return !targetClass.accessFlags.isPublic() && target.accessFlags.isPublic();
+ private boolean mayNeedBridgeForVisibility(DexType context, DexEncodedMethod method) {
+ DexType holderType = method.method.getHolder();
+ DexClass holder = appInfo.definitionFor(holderType);
+ if (holder == null) {
+ return false;
+ }
+ ConstraintWithTarget classVisibility =
+ ConstraintWithTarget.deriveConstraint(context, holderType, holder.accessFlags, appInfo);
+ ConstraintWithTarget methodVisibility =
+ ConstraintWithTarget.deriveConstraint(context, holderType, method.accessFlags, appInfo);
+ // We may need bridge for visibility if the target class is not visible while the target method
+ // is visible from the calling context.
+ return classVisibility == ConstraintWithTarget.NEVER
+ && methodVisibility != ConstraintWithTarget.NEVER;
}
private DexEncodedMethod insertBridgeForVisibilityIfNeeded(
@@ -225,8 +242,7 @@
DexProgramClass bridgeHolder =
findHolderForVisibilityBridge(originalClass, targetClass, packageDescriptor);
assert bridgeHolder != null;
- DexEncodedMethod bridgeMethod =
- target.toForwardingMethod(bridgeHolder, appInfo.dexItemFactory);
+ DexEncodedMethod bridgeMethod = target.toForwardingMethod(bridgeHolder, appInfo);
bridgeHolder.addMethod(bridgeMethod);
assert lookupTarget.apply(method) == bridgeMethod;
return bridgeMethod;
@@ -263,15 +279,15 @@
return null;
}
- private void computeFieldRebinding(Map<DexField, Set<DexEncodedMethod>> fields,
+ private void computeFieldRebinding(
+ Map<DexField, Set<DexEncodedMethod>> fieldsWithContexts,
BiFunction<DexType, DexField, DexEncodedField> lookup,
BiFunction<DexClass, DexField, DexEncodedField> lookupTargetOnClass) {
- for (Map.Entry<DexField, Set<DexEncodedMethod>> entry : fields.entrySet()) {
- DexField field = entry.getKey();
+ for (DexField field : fieldsWithContexts.keySet()) {
DexEncodedField target = lookup.apply(field.getHolder(), field);
// Rebind to the lowest library class or program class. Do not rebind accesses to fields that
// are not visible from the access context.
- Set<DexEncodedMethod> contexts = entry.getValue();
+ Set<DexEncodedMethod> contexts = fieldsWithContexts.get(field);
if (target != null && target.field != field
&& contexts.stream().allMatch(context ->
isVisibleFromOriginalContext(context.method.getHolder(), target))) {
diff --git a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
index 59caf25..f2e6c6d 100644
--- a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
@@ -12,7 +12,7 @@
import com.android.tools.r8.optimize.InvokeSingleTargetExtractor.InvokeKind;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.google.common.collect.Sets;
-import java.util.Arrays;
+import java.util.List;
import java.util.Set;
public class VisibilityBridgeRemover {
@@ -24,11 +24,17 @@
}
private void removeUnneededVisibilityBridgesFromClass(DexProgramClass clazz) {
- clazz.setDirectMethods(removeUnneededVisibilityBridges(clazz.directMethods()));
- clazz.setVirtualMethods(removeUnneededVisibilityBridges(clazz.virtualMethods()));
+ DexEncodedMethod[] newDirectMethods = removeUnneededVisibilityBridges(clazz.directMethods());
+ if (newDirectMethods != null) {
+ clazz.setDirectMethods(newDirectMethods);
+ }
+ DexEncodedMethod[] newVirtualMethods = removeUnneededVisibilityBridges(clazz.virtualMethods());
+ if (newVirtualMethods != null) {
+ clazz.setVirtualMethods(newVirtualMethods);
+ }
}
- private DexEncodedMethod[] removeUnneededVisibilityBridges(DexEncodedMethod[] methods) {
+ private DexEncodedMethod[] removeUnneededVisibilityBridges(List<DexEncodedMethod> methods) {
Set<DexEncodedMethod> methodsToBeRemoved = null;
for (DexEncodedMethod method : methods) {
if (isUnneededVisibilityBridge(method)) {
@@ -40,11 +46,11 @@
}
if (methodsToBeRemoved != null) {
Set<DexEncodedMethod> finalMethodsToBeRemoved = methodsToBeRemoved;
- return Arrays.stream(methods)
+ return methods.stream()
.filter(method -> !finalMethodsToBeRemoved.contains(method))
.toArray(DexEncodedMethod[]::new);
}
- return methods;
+ return null;
}
private boolean isUnneededVisibilityBridge(DexEncodedMethod method) {
diff --git a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
index ae88ba7..0bdbcee 100644
--- a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
@@ -38,20 +38,23 @@
DexClass holder = appInfo.definitionFor(type);
scope = scope.newNestedScope();
if (holder != null && !holder.isLibraryClass()) {
- holder.setVirtualMethods(processMethods(holder.virtualMethods()));
+ DexEncodedMethod[] newVirtualMethods = processMethods(holder.virtualMethods());
+ if (newVirtualMethods != null) {
+ holder.setVirtualMethods(newVirtualMethods);
+ }
}
type.forAllExtendsSubtypes(this::processClass);
scope = scope.getParent();
}
- private DexEncodedMethod[] processMethods(DexEncodedMethod[] virtualMethods) {
+ private DexEncodedMethod[] processMethods(List<DexEncodedMethod> virtualMethods) {
if (virtualMethods == null) {
return null;
}
// Removal of abstract methods is rare, so avoid copying the array until we find one.
List<DexEncodedMethod> methods = null;
- for (int i = 0; i < virtualMethods.length; i++) {
- DexEncodedMethod method = virtualMethods[i];
+ for (int i = 0; i < virtualMethods.size(); i++) {
+ DexEncodedMethod method = virtualMethods.get(i);
if (scope.addMethodIfMoreVisible(method)
|| !method.accessFlags.isAbstract()
|| appInfo.isPinned(method.method)) {
@@ -60,9 +63,9 @@
}
} else {
if (methods == null) {
- methods = new ArrayList<>(virtualMethods.length - 1);
+ methods = new ArrayList<>(virtualMethods.size() - 1);
for (int j = 0; j < i; j++) {
- methods.add(virtualMethods[j]);
+ methods.add(virtualMethods.get(j));
}
}
if (Log.ENABLED) {
@@ -70,7 +73,10 @@
}
}
}
- return methods == null ? virtualMethods : methods.toArray(new DexEncodedMethod[methods.size()]);
+ if (methods != null) {
+ return methods.toArray(new DexEncodedMethod[methods.size()]);
+ }
+ return null;
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 8ce5cb0..3fa9db5 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -18,7 +18,6 @@
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableList;
-import java.util.Arrays;
import java.util.List;
public class AnnotationRemover {
@@ -184,8 +183,9 @@
return original;
}
assert definition.isInterface();
- boolean liveGetter = Arrays.stream(definition.virtualMethods())
- .anyMatch(method -> method.method.name == original.name);
+ boolean liveGetter =
+ definition.virtualMethods().stream()
+ .anyMatch(method -> method.method.name == original.name);
return liveGetter ? original : null;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 55ceee8..a51ca83 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -88,7 +88,9 @@
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
+import java.util.SortedMap;
import java.util.SortedSet;
+import java.util.TreeMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.BiConsumer;
@@ -116,12 +118,16 @@
private RootSet rootSet;
private ProguardClassFilter dontWarnPatterns;
- private final Map<DexType, Set<DexMethod>> virtualInvokes = Maps.newIdentityHashMap();
- private final Map<DexType, Set<DexMethod>> interfaceInvokes = Maps.newIdentityHashMap();
+ private final Map<DexType, Set<TargetWithContext<DexMethod>>> virtualInvokes =
+ Maps.newIdentityHashMap();
+ private final Map<DexType, Set<TargetWithContext<DexMethod>>> interfaceInvokes =
+ Maps.newIdentityHashMap();
private final Map<DexType, Set<TargetWithContext<DexMethod>>> superInvokes =
Maps.newIdentityHashMap();
- private final Map<DexType, Set<DexMethod>> directInvokes = Maps.newIdentityHashMap();
- private final Map<DexType, Set<DexMethod>> staticInvokes = Maps.newIdentityHashMap();
+ private final Map<DexType, Set<TargetWithContext<DexMethod>>> directInvokes =
+ Maps.newIdentityHashMap();
+ private final Map<DexType, Set<TargetWithContext<DexMethod>>> staticInvokes =
+ Maps.newIdentityHashMap();
private final Map<DexType, Set<TargetWithContext<DexField>>> instanceFieldsWritten =
Maps.newIdentityHashMap();
private final Map<DexType, Set<TargetWithContext<DexField>>> instanceFieldsRead =
@@ -439,7 +445,7 @@
// Revisit the current method to implicitly add -keep rule for items with reflective access.
pendingReflectiveUses.add(currentMethod);
}
- if (!registerItemWithTarget(virtualInvokes, method)) {
+ if (!registerItemWithTargetAndContext(virtualInvokes, method, currentMethod)) {
return false;
}
if (Log.ENABLED) {
@@ -455,7 +461,7 @@
}
boolean registerInvokeDirect(DexMethod method, KeepReason keepReason) {
- if (!registerItemWithTarget(directInvokes, method)) {
+ if (!registerItemWithTargetAndContext(directInvokes, method, currentMethod)) {
return false;
}
if (Log.ENABLED) {
@@ -482,7 +488,7 @@
if (method == appInfo.dexItemFactory.enumMethods.valueOf) {
pendingReflectiveUses.add(currentMethod);
}
- if (!registerItemWithTarget(staticInvokes, method)) {
+ if (!registerItemWithTargetAndContext(staticInvokes, method, currentMethod)) {
return false;
}
if (Log.ENABLED) {
@@ -498,7 +504,7 @@
}
boolean registerInvokeInterface(DexMethod method, KeepReason keepReason) {
- if (!registerItemWithTarget(interfaceInvokes, method)) {
+ if (!registerItemWithTargetAndContext(interfaceInvokes, method, currentMethod)) {
return false;
}
if (Log.ENABLED) {
@@ -1629,34 +1635,18 @@
}
}
- private Map<DexField, Set<DexEncodedMethod>> collectFields(
- Map<DexType, Set<TargetWithContext<DexField>>> map) {
- Map<DexField, Set<DexEncodedMethod>> result = new IdentityHashMap<>();
- for (Entry<DexType, Set<TargetWithContext<DexField>>> entry : map.entrySet()) {
- for (TargetWithContext<DexField> fieldWithContext : entry.getValue()) {
- DexField field = fieldWithContext.getTarget();
- DexEncodedMethod context = fieldWithContext.getContext();
- result.computeIfAbsent(field, k -> Sets.newIdentityHashSet())
+ <T extends Descriptor<?, T>> SortedMap<T, Set<DexEncodedMethod>> collectDescriptors(
+ Map<DexType, Set<TargetWithContext<T>>> map) {
+ SortedMap<T, Set<DexEncodedMethod>> result = new TreeMap<>(PresortedComparable::slowCompare);
+ for (Entry<DexType, Set<TargetWithContext<T>>> entry : map.entrySet()) {
+ for (TargetWithContext<T> descriptorWithContext : entry.getValue()) {
+ T descriptor = descriptorWithContext.getTarget();
+ DexEncodedMethod context = descriptorWithContext.getContext();
+ result.computeIfAbsent(descriptor, k -> Sets.newIdentityHashSet())
.add(context);
}
}
- return result;
- }
-
- Map<DexField, Set<DexEncodedMethod>> collectInstanceFieldsRead() {
- return Collections.unmodifiableMap(collectFields(instanceFieldsRead));
- }
-
- Map<DexField, Set<DexEncodedMethod>> collectInstanceFieldsWritten() {
- return Collections.unmodifiableMap(collectFields(instanceFieldsWritten));
- }
-
- Map<DexField, Set<DexEncodedMethod>> collectStaticFieldsRead() {
- return Collections.unmodifiableMap(collectFields(staticFieldsRead));
- }
-
- Map<DexField, Set<DexEncodedMethod>> collectStaticFieldsWritten() {
- return Collections.unmodifiableMap(collectFields(staticFieldsWritten));
+ return Collections.unmodifiableSortedMap(result);
}
private Set<DexField> collectReachedFields(
@@ -1915,39 +1905,39 @@
/**
* Set of all field ids used in instance field reads, along with access context.
*/
- public final Map<DexField, Set<DexEncodedMethod>> instanceFieldReads;
+ public final SortedMap<DexField, Set<DexEncodedMethod>> instanceFieldReads;
/**
* Set of all field ids used in instance field writes, along with access context.
*/
- public final Map<DexField, Set<DexEncodedMethod>> instanceFieldWrites;
+ public final SortedMap<DexField, Set<DexEncodedMethod>> instanceFieldWrites;
/**
* Set of all field ids used in static field reads, along with access context.
*/
- public final Map<DexField, Set<DexEncodedMethod>> staticFieldReads;
+ public final SortedMap<DexField, Set<DexEncodedMethod>> staticFieldReads;
/**
* Set of all field ids used in static field writes, along with access context.
*/
- public final Map<DexField, Set<DexEncodedMethod>> staticFieldWrites;
+ public final SortedMap<DexField, Set<DexEncodedMethod>> staticFieldWrites;
/**
- * Set of all methods referenced in virtual invokes;
+ * Set of all methods referenced in virtual invokes, along with calling context.
*/
- public final SortedSet<DexMethod> virtualInvokes;
+ public final SortedMap<DexMethod, Set<DexEncodedMethod>> virtualInvokes;
/**
- * Set of all methods referenced in interface invokes;
+ * Set of all methods referenced in interface invokes, along with calling context.
*/
- public final SortedSet<DexMethod> interfaceInvokes;
+ public final SortedMap<DexMethod, Set<DexEncodedMethod>> interfaceInvokes;
/**
- * Set of all methods referenced in super invokes;
+ * Set of all methods referenced in super invokes, along with calling context.
*/
- public final SortedSet<DexMethod> superInvokes;
+ public final SortedMap<DexMethod, Set<DexEncodedMethod>> superInvokes;
/**
- * Set of all methods referenced in direct invokes;
+ * Set of all methods referenced in direct invokes, along with calling context.
*/
- public final SortedSet<DexMethod> directInvokes;
+ public final SortedMap<DexMethod, Set<DexEncodedMethod>> directInvokes;
/**
- * Set of all methods referenced in static invokes;
+ * Set of all methods referenced in static invokes, along with calling context.
*/
- public final SortedSet<DexMethod> staticInvokes;
+ public final SortedMap<DexMethod, Set<DexEncodedMethod>> staticInvokes;
/**
* Set of live call sites in the code. Note that if desugaring has taken place call site objects
* will have been removed from the code.
@@ -2041,20 +2031,20 @@
DexMethod::slowCompareTo, enqueuer.virtualMethodsTargetedByInvokeDirect);
this.liveMethods = toSortedDescriptorSet(enqueuer.liveMethods.getItems());
this.liveFields = toSortedDescriptorSet(enqueuer.liveFields.getItems());
- this.instanceFieldReads = enqueuer.collectInstanceFieldsRead();
- this.instanceFieldWrites = enqueuer.collectInstanceFieldsWritten();
- this.staticFieldReads = enqueuer.collectStaticFieldsRead();
- this.staticFieldWrites = enqueuer.collectStaticFieldsWritten();
+ this.instanceFieldReads = enqueuer.collectDescriptors(enqueuer.instanceFieldsRead);
+ this.instanceFieldWrites = enqueuer.collectDescriptors(enqueuer.instanceFieldsWritten);
+ this.staticFieldReads = enqueuer.collectDescriptors(enqueuer.staticFieldsRead);
+ this.staticFieldWrites = enqueuer.collectDescriptors(enqueuer.staticFieldsWritten);
this.fieldsRead = enqueuer.mergeFieldAccesses(
instanceFieldReads.keySet(), staticFieldReads.keySet());
this.fieldsWritten = enqueuer.mergeFieldAccesses(
instanceFieldWrites.keySet(), staticFieldWrites.keySet());
this.pinnedItems = enqueuer.pinnedItems;
- this.virtualInvokes = joinInvokedMethods(enqueuer.virtualInvokes);
- this.interfaceInvokes = joinInvokedMethods(enqueuer.interfaceInvokes);
- this.superInvokes = joinInvokedMethods(enqueuer.superInvokes, TargetWithContext::getTarget);
- this.directInvokes = joinInvokedMethods(enqueuer.directInvokes);
- this.staticInvokes = joinInvokedMethods(enqueuer.staticInvokes);
+ this.virtualInvokes = enqueuer.collectDescriptors(enqueuer.virtualInvokes);
+ this.interfaceInvokes = enqueuer.collectDescriptors(enqueuer.interfaceInvokes);
+ this.superInvokes = enqueuer.collectDescriptors(enqueuer.superInvokes);
+ this.directInvokes = enqueuer.collectDescriptors(enqueuer.directInvokes);
+ this.staticInvokes = enqueuer.collectDescriptors(enqueuer.staticInvokes);
this.callSites = enqueuer.callSites;
this.brokenSuperInvokes =
ImmutableSortedSet.copyOf(DexMethod::slowCompareTo, enqueuer.brokenSuperInvokes);
@@ -2153,11 +2143,16 @@
this.fieldsRead = rewriteItems(previous.fieldsRead, lense::lookupField);
this.fieldsWritten = rewriteItems(previous.fieldsWritten, lense::lookupField);
this.pinnedItems = lense.rewriteReferencesConservatively(previous.pinnedItems);
- this.virtualInvokes = lense.rewriteMethodsConservatively(previous.virtualInvokes);
- this.interfaceInvokes = lense.rewriteMethodsConservatively(previous.interfaceInvokes);
- this.superInvokes = lense.rewriteMethodsConservatively(previous.superInvokes);
- this.directInvokes = lense.rewriteMethodsConservatively(previous.directInvokes);
- this.staticInvokes = lense.rewriteMethodsConservatively(previous.staticInvokes);
+ this.virtualInvokes = rewriteKeysConservativelyWhileMergingValues(
+ previous.virtualInvokes, lense::lookupMethodInAllContexts);
+ this.interfaceInvokes = rewriteKeysConservativelyWhileMergingValues(
+ previous.interfaceInvokes, lense::lookupMethodInAllContexts);
+ this.superInvokes = rewriteKeysConservativelyWhileMergingValues(
+ previous.superInvokes, lense::lookupMethodInAllContexts);
+ this.directInvokes = rewriteKeysConservativelyWhileMergingValues(
+ previous.directInvokes, lense::lookupMethodInAllContexts);
+ this.staticInvokes = rewriteKeysConservativelyWhileMergingValues(
+ previous.staticInvokes, lense::lookupMethodInAllContexts);
// TODO(sgjesse): Rewrite call sites as well? Right now they are only used by minification
// after second tree shaking.
this.callSites = previous.callSites;
@@ -2294,16 +2289,6 @@
return isInstantiatedDirectly(type) || isInstantiatedIndirectly(type);
}
- private SortedSet<DexMethod> joinInvokedMethods(Map<DexType, Set<DexMethod>> invokes) {
- return joinInvokedMethods(invokes, Function.identity());
- }
-
- private <T> SortedSet<DexMethod> joinInvokedMethods(Map<DexType, Set<T>> invokes,
- Function<T, DexMethod> getter) {
- return invokes.values().stream().flatMap(Set::stream).map(getter)
- .collect(ImmutableSortedSet.toImmutableSortedSet(PresortedComparable::slowCompare));
- }
-
private Object2BooleanMap<DexReference> joinIdentifierNameStrings(
Set<DexReference> explicit, Set<DexReference> implicit) {
Object2BooleanMap<DexReference> result = new Object2BooleanArrayMap<>();
@@ -2336,17 +2321,30 @@
return builder.build();
}
-
private static <T extends PresortedComparable<T>, S>
- Map<T, Set<S>> rewriteKeysWhileMergingValues(
+ SortedMap<T, Set<S>> rewriteKeysWhileMergingValues(
Map<T, Set<S>> original, Function<T, T> rewrite) {
- Map<T, Set<S>> result = new IdentityHashMap<>();
+ SortedMap<T, Set<S>> result = new TreeMap<>(PresortedComparable::slowCompare);
for (T item : original.keySet()) {
T rewrittenKey = rewrite.apply(item);
result.computeIfAbsent(rewrittenKey, k -> Sets.newIdentityHashSet())
.addAll(original.get(item));
}
- return Collections.unmodifiableMap(result);
+ return Collections.unmodifiableSortedMap(result);
+ }
+
+ private static <T extends PresortedComparable<T>, S>
+ SortedMap<T, Set<S>> rewriteKeysConservativelyWhileMergingValues(
+ Map<T, Set<S>> original, Function<T, Set<T>> rewrite) {
+ SortedMap<T, Set<S>> result = new TreeMap<>(PresortedComparable::slowCompare);
+ for (T item : original.keySet()) {
+ Set<T> rewrittenKeys = rewrite.apply(item);
+ for (T rewrittenKey : rewrittenKeys) {
+ result.computeIfAbsent(rewrittenKey, k -> Sets.newIdentityHashSet())
+ .addAll(original.get(item));
+ }
+ }
+ return Collections.unmodifiableSortedMap(result);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index f94954b..a0c9735 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -495,14 +495,16 @@
}
// In compat mode traverse all direct methods in the hierarchy.
if (clazz == startingClass || options.forceProguardCompatibility) {
- Arrays.stream(clazz.directMethods())
+ clazz
+ .directMethods()
.forEach(
method -> {
DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
markMethod(method, memberKeepRules, methodsMarked, rule, precondition);
});
}
- Arrays.stream(clazz.virtualMethods())
+ clazz
+ .virtualMethods()
.forEach(
method -> {
DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
diff --git a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
index a51116b..42b7e21 100644
--- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
@@ -30,8 +30,10 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multiset.Entry;
import com.google.common.collect.Streams;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
@@ -257,7 +259,7 @@
if (appView.appInfo().neverMerge.contains(clazz.type)) {
return MergeGroup.DONT_MERGE;
}
- if (clazz.staticFields().length + clazz.directMethods().length + clazz.virtualMethods().length
+ if (clazz.staticFields().length + clazz.directMethods().size() + clazz.virtualMethods().size()
== 0) {
return MergeGroup.DONT_MERGE;
}
@@ -268,10 +270,10 @@
.anyMatch(field -> appView.appInfo().isPinned(field.field))) {
return MergeGroup.DONT_MERGE;
}
- if (Arrays.stream(clazz.directMethods()).anyMatch(DexEncodedMethod::isInitializer)) {
+ if (clazz.directMethods().stream().anyMatch(DexEncodedMethod::isInitializer)) {
return MergeGroup.DONT_MERGE;
}
- if (!Arrays.stream(clazz.virtualMethods()).allMatch(DexEncodedMethod::isPrivateMethod)) {
+ if (!clazz.virtualMethods().stream().allMatch(DexEncodedMethod::isPrivateMethod)) {
return MergeGroup.DONT_MERGE;
}
if (Streams.stream(clazz.methods())
@@ -445,7 +447,7 @@
return false;
}
// Check that all of the members are private or public.
- if (!Arrays.stream(clazz.directMethods())
+ if (!clazz.directMethods().stream()
.allMatch(method -> method.accessFlags.isPrivate() || method.accessFlags.isPublic())) {
return false;
}
@@ -458,7 +460,7 @@
// virtual methods are private. Therefore, we don't need to consider check if there are any
// package-private or protected instance fields or virtual methods here.
assert Arrays.stream(clazz.instanceFields()).count() == 0;
- assert Arrays.stream(clazz.virtualMethods()).allMatch(method -> method.accessFlags.isPrivate());
+ assert clazz.virtualMethods().stream().allMatch(method -> method.accessFlags.isPrivate());
// Check that no methods access package-private or protected members.
IllegalAccessDetector registry = new IllegalAccessDetector(appView, clazz);
@@ -489,9 +491,9 @@
numberOfMergedClasses++;
// Move members from source to target.
- targetClass.setDirectMethods(
+ targetClass.appendDirectMethods(
mergeMethods(sourceClass.directMethods(), targetClass.directMethods(), targetClass));
- targetClass.setVirtualMethods(
+ targetClass.appendVirtualMethods(
mergeMethods(sourceClass.virtualMethods(), targetClass.virtualMethods(), targetClass));
targetClass.setStaticFields(
mergeFields(sourceClass.staticFields(), targetClass.staticFields(), targetClass));
@@ -502,30 +504,25 @@
sourceClass.setStaticFields(DexEncodedField.EMPTY_ARRAY);
}
- private DexEncodedMethod[] mergeMethods(
- DexEncodedMethod[] sourceMethods,
- DexEncodedMethod[] targetMethods,
+ private List<DexEncodedMethod> mergeMethods(
+ List<DexEncodedMethod> sourceMethods,
+ List<DexEncodedMethod> targetMethods,
DexProgramClass targetClass) {
- DexEncodedMethod[] result = new DexEncodedMethod[sourceMethods.length + targetMethods.length];
-
- // Move all target methods to result.
- System.arraycopy(targetMethods, 0, result, 0, targetMethods.length);
-
// Move source methods to result one by one, renaming them if needed.
MethodSignatureEquivalence equivalence = MethodSignatureEquivalence.get();
Set<Wrapper<DexMethod>> existingMethods =
- Arrays.stream(targetMethods)
+ targetMethods.stream()
.map(targetMethod -> equivalence.wrap(targetMethod.method))
.collect(Collectors.toSet());
Predicate<DexMethod> availableMethodSignatures =
method -> !existingMethods.contains(equivalence.wrap(method));
- int i = targetMethods.length;
+ List<DexEncodedMethod> newMethods = new ArrayList<>(sourceMethods.size());
for (DexEncodedMethod sourceMethod : sourceMethods) {
DexEncodedMethod sourceMethodAfterMove =
renameMethodIfNeeded(sourceMethod, targetClass, availableMethodSignatures);
- result[i++] = sourceMethodAfterMove;
+ newMethods.add(sourceMethodAfterMove);
DexMethod originalMethod =
methodMapping.inverse().getOrDefault(sourceMethod.method, sourceMethod.method);
@@ -533,8 +530,7 @@
existingMethods.add(equivalence.wrap(sourceMethodAfterMove.method));
}
-
- return result;
+ return newMethods;
}
private DexEncodedField[] mergeFields(
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index f502692..dd8bcd6 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.collect.Sets;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -99,8 +100,15 @@
// The class is used and must be kept. Remove the unused fields and methods from
// the class.
usagePrinter.visiting(clazz);
- clazz.setDirectMethods(reachableMethods(clazz.directMethods(), clazz));
- clazz.setVirtualMethods(reachableMethods(clazz.virtualMethods(), clazz));
+ DexEncodedMethod[] reachableDirectMethods = reachableMethods(clazz.directMethods(), clazz);
+ if (reachableDirectMethods != null) {
+ clazz.setDirectMethods(reachableDirectMethods);
+ }
+ DexEncodedMethod[] reachableVirtualMethods =
+ reachableMethods(clazz.virtualMethods(), clazz);
+ if (reachableVirtualMethods != null) {
+ clazz.setVirtualMethods(reachableVirtualMethods);
+ }
clazz.setInstanceFields(reachableFields(clazz.instanceFields()));
clazz.setStaticFields(reachableFields(clazz.staticFields()));
clazz.removeInnerClasses(this::isAttributeReferencingPrunedType);
@@ -145,9 +153,9 @@
}
private <S extends PresortedComparable<S>, T extends KeyedDexItem<S>> int firstUnreachableIndex(
- T[] items, Predicate<S> live) {
- for (int i = 0; i < items.length; i++) {
- if (!live.test(items[i].getKey())) {
+ List<T> items, Predicate<S> live) {
+ for (int i = 0; i < items.size(); i++) {
+ if (!live.test(items.get(i).getKey())) {
return i;
}
}
@@ -159,18 +167,18 @@
&& method.method.proto.parameters.isEmpty();
}
- private DexEncodedMethod[] reachableMethods(DexEncodedMethod[] methods, DexClass clazz) {
+ private DexEncodedMethod[] reachableMethods(List<DexEncodedMethod> methods, DexClass clazz) {
int firstUnreachable = firstUnreachableIndex(methods, appInfo.liveMethods::contains);
// Return the original array if all methods are used.
if (firstUnreachable == -1) {
- return methods;
+ return null;
}
- ArrayList<DexEncodedMethod> reachableMethods = new ArrayList<>(methods.length);
+ ArrayList<DexEncodedMethod> reachableMethods = new ArrayList<>(methods.size());
for (int i = 0; i < firstUnreachable; i++) {
- reachableMethods.add(methods[i]);
+ reachableMethods.add(methods.get(i));
}
- for (int i = firstUnreachable; i < methods.length; i++) {
- DexEncodedMethod method = methods[i];
+ for (int i = firstUnreachable; i < methods.size(); i++) {
+ DexEncodedMethod method = methods.get(i);
if (appInfo.liveMethods.contains(method.getKey())) {
reachableMethods.add(method);
} else if (options.debugKeepRules && isDefaultConstructor(method)) {
@@ -222,7 +230,8 @@
appInfo.liveFields.contains(field)
|| appInfo.fieldsRead.contains(field)
|| appInfo.fieldsWritten.contains(field);
- int firstUnreachable = firstUnreachableIndex(fields, isReachableOrReferencedField);
+ int firstUnreachable =
+ firstUnreachableIndex(Arrays.asList(fields), isReachableOrReferencedField);
// Return the original array if all fields are used.
if (firstUnreachable == -1) {
return fields;
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index fe10045..fe401a1 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClass.MethodSetter;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -993,11 +994,6 @@
return false;
}
- DexEncodedMethod[] mergedDirectMethods =
- mergeMethods(directMethods.values(), target.directMethods());
- DexEncodedMethod[] mergedVirtualMethods =
- mergeMethods(virtualMethods.values(), target.virtualMethods());
-
// Step 2: Merge fields
Set<DexString> existingFieldNames = new HashSet<>();
for (DexEncodedField field : target.fields()) {
@@ -1044,8 +1040,8 @@
? DexTypeList.empty()
: new DexTypeList(interfaces.toArray(new DexType[0]));
// Step 2: replace fields and methods.
- target.setDirectMethods(mergedDirectMethods);
- target.setVirtualMethods(mergedVirtualMethods);
+ target.appendDirectMethods(directMethods.values());
+ target.appendVirtualMethods(virtualMethods.values());
target.setInstanceFields(mergedInstanceFields);
target.setStaticFields(mergedStaticFields);
// Step 3: Unlink old class to ease tree shaking.
@@ -1191,7 +1187,8 @@
newMethod,
graphLense.getOriginalMethodSignature(method.method),
invocationTarget.method,
- invocationTarget.isPrivateMethod() ? DIRECT : STATIC);
+ invocationTarget.isPrivateMethod() ? DIRECT : STATIC,
+ target.isInterface());
// Add the bridge to the list of synthesized bridges such that the method signatures will
// be updated by the end of vertical class merging.
@@ -1226,8 +1223,19 @@
assert actual.isVirtualMethod() == method.isVirtualMethod();
return actual;
}
- // We will keep the method, so the class better be abstract if there is no implementation.
- assert !method.accessFlags.isAbstract() || target.accessFlags.isAbstract();
+ // The method is not actually overridden. This means that we will move `method` to the
+ // subtype. If `method` is abstract, then so should the subtype be.
+ if (Log.ENABLED) {
+ if (method.accessFlags.isAbstract() && !target.accessFlags.isAbstract()) {
+ Log.warn(
+ VerticalClassMerger.class,
+ "The non-abstract type `"
+ + target.type.toSourceString()
+ + "` does not implement the method `"
+ + method.method.toSourceString()
+ + "`.");
+ }
+ }
return null;
}
@@ -1271,8 +1279,8 @@
}
private DexEncodedMethod[] mergeMethods(
- Collection<DexEncodedMethod> sourceMethods, DexEncodedMethod[] targetMethods) {
- DexEncodedMethod[] result = new DexEncodedMethod[sourceMethods.size() + targetMethods.length];
+ Collection<DexEncodedMethod> sourceMethods, List<DexEncodedMethod> targetMethods) {
+ DexEncodedMethod[] result = new DexEncodedMethod[sourceMethods.size() + targetMethods.size()];
// Add methods from source.
int i = 0;
for (DexEncodedMethod method : sourceMethods) {
@@ -1280,7 +1288,7 @@
i++;
}
// Add methods from target.
- System.arraycopy(targetMethods, 0, result, i, targetMethods.length);
+ System.arraycopy(targetMethods, 0, result, i, targetMethods.size());
return result;
}
@@ -1423,8 +1431,8 @@
private GraphLense fixupTypeReferences(GraphLense graphLense) {
// Globally substitute merged class types in protos and holders.
for (DexProgramClass clazz : appInfo.classes()) {
- clazz.setDirectMethods(substituteTypesIn(clazz.directMethods()));
- clazz.setVirtualMethods(substituteTypesIn(clazz.virtualMethods()));
+ fixupMethods(clazz.directMethods(), clazz::setDirectMethod);
+ fixupMethods(clazz.virtualMethods(), clazz::setVirtualMethod);
clazz.setStaticFields(substituteTypesIn(clazz.staticFields()));
clazz.setInstanceFields(substituteTypesIn(clazz.instanceFields()));
}
@@ -1438,20 +1446,19 @@
return lense.build(application.dexItemFactory, graphLense);
}
- private DexEncodedMethod[] substituteTypesIn(DexEncodedMethod[] methods) {
+ private void fixupMethods(List<DexEncodedMethod> methods, MethodSetter setter) {
if (methods == null) {
- return null;
+ return;
}
- for (int i = 0; i < methods.length; i++) {
- DexEncodedMethod encodedMethod = methods[i];
+ for (int i = 0; i < methods.size(); i++) {
+ DexEncodedMethod encodedMethod = methods.get(i);
DexMethod method = encodedMethod.method;
DexMethod newMethod = fixupMethod(method);
if (newMethod != method) {
lense.move(method, newMethod);
- methods[i] = encodedMethod.toTypeSubstitutedMethod(newMethod);
+ setter.setMethod(i, encodedMethod.toTypeSubstitutedMethod(newMethod));
}
}
- return methods;
}
private DexEncodedField[] substituteTypesIn(DexEncodedField[] fields) {
@@ -1873,13 +1880,19 @@
private DexMethod originalMethod;
private DexMethod invocationTarget;
private Type type;
+ private final boolean isInterface;
public SynthesizedBridgeCode(
- DexMethod method, DexMethod originalMethod, DexMethod invocationTarget, Type type) {
+ DexMethod method,
+ DexMethod originalMethod,
+ DexMethod invocationTarget,
+ Type type,
+ boolean isInterface) {
this.method = method;
this.originalMethod = originalMethod;
this.invocationTarget = invocationTarget;
this.type = type;
+ this.isInterface = isInterface;
}
// By the time the synthesized code object is created, vertical class merging still has not
@@ -1908,7 +1921,8 @@
type == DIRECT ? method.holder : null,
invocationTarget,
type,
- callerPosition);
+ callerPosition,
+ isInterface);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index a35100e..a5854b6 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -147,6 +147,10 @@
// Throw exception if there is a warning about invalid debug info.
public boolean invalidDebugInfoFatal = false;
+ // When dexsplitting we ignore main dex classes missing in the application. These will be
+ // fused together by play store when shipped for pre-L devices.
+ public boolean ignoreMainDexMissingClasses = false;
+
// Hidden marker for classes.dex
private boolean hasMarker = false;
private Marker marker;
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index c9be1b8..f5a4b81 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -341,7 +341,7 @@
private static IdentityHashMap<DexString, List<DexEncodedMethod>> groupMethodsByRenamedName(
NamingLens namingLens, DexProgramClass clazz) {
IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByRenamedName =
- new IdentityHashMap<>(clazz.directMethods().length + clazz.virtualMethods().length);
+ new IdentityHashMap<>(clazz.directMethods().size() + clazz.virtualMethods().size());
for (DexEncodedMethod method : clazz.methods()) {
// Add method only if renamed or contains positions.
DexString renamedName = namingLens.lookupName(method.method);
diff --git a/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java b/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java
index dd4c73a..33f9d7c 100644
--- a/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java
+++ b/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java
@@ -7,48 +7,49 @@
import com.android.tools.r8.graph.KeyedDexItem;
import com.android.tools.r8.graph.PresortedComparable;
import java.util.Iterator;
+import java.util.List;
import java.util.NoSuchElementException;
public class OrderedMergingIterator<T extends KeyedDexItem<S>, S extends PresortedComparable<S>>
implements Iterator<T> {
- private final T[] one;
- private final T[] other;
+ private final List<T> one;
+ private final List<T> other;
private int oneIndex = 0;
private int otherIndex = 0;
- public OrderedMergingIterator(T[] one, T[] other) {
+ public OrderedMergingIterator(List<T> one, List<T> other) {
this.one = one;
this.other = other;
}
- private static <T> T getNextChecked(T[] array, int position) {
- if (position >= array.length) {
+ private static <T> T getNextChecked(List<T> list, int position) {
+ if (position >= list.size()) {
throw new NoSuchElementException();
}
- return array[position];
+ return list.get(position);
}
@Override
public boolean hasNext() {
- return oneIndex < one.length || otherIndex < other.length;
+ return oneIndex < one.size() || otherIndex < other.size();
}
@Override
public T next() {
- if (oneIndex >= one.length) {
+ if (oneIndex >= one.size()) {
return getNextChecked(other, otherIndex++);
}
- if (otherIndex >= other.length) {
+ if (otherIndex >= other.size()) {
return getNextChecked(one, oneIndex++);
}
- int comparison = one[oneIndex].getKey().compareTo(other[otherIndex].getKey());
+ int comparison = one.get(oneIndex).getKey().compareTo(other.get(otherIndex).getKey());
if (comparison < 0) {
- return one[oneIndex++];
+ return one.get(oneIndex++);
}
if (comparison == 0) {
throw new InternalCompilerError("Source arrays are not disjoint.");
}
- return other[otherIndex++];
+ return other.get(otherIndex++);
}
}
diff --git a/src/test/examples/autovalue/SimpleAutoValue.java b/src/test/examples/autovalue/SimpleAutoValue.java
deleted file mode 100644
index 2c0b2c6..0000000
--- a/src/test/examples/autovalue/SimpleAutoValue.java
+++ /dev/null
@@ -1,54 +0,0 @@
-// 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 autovalue;
-
-import com.google.auto.value.AutoValue;
-import javax.annotation.Nullable;
-
-public class SimpleAutoValue {
-
- @AutoValue
- static abstract class Pair {
-
- Pair() {
- // Intentionally left empty.
- }
-
- abstract int getOne();
-
- @Nullable
- abstract String getOther();
-
- abstract String getRequiredOther();
-
- static Builder builder() {
- return new AutoValue_SimpleAutoValue_Pair.Builder();
- }
-
- @AutoValue.Builder
- abstract static class Builder {
-
- abstract Builder setOne(int value);
-
- abstract Builder setOther(String value);
-
- abstract Builder setRequiredOther(String value);
-
- abstract Pair build();
- }
- }
-
- public static void main(String... args) {
- Pair.Builder builder = Pair.builder();
- builder.setOne(42);
- builder.setRequiredOther("123");
- System.out.println(builder.build());
- builder = Pair.builder();
- try {
- builder.build();
- } catch (Exception e) {
- System.out.println(e.getMessage());
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index 37e4dec..c807a5d 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -87,8 +87,8 @@
return self();
}
- public D8TestBuilder setIntermediate(boolean b) {
- builder.setIntermediate(true);
+ public D8TestBuilder setIntermediate(boolean intermediate) {
+ builder.setIntermediate(intermediate);
return self();
}
}
diff --git a/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
index f0e758d..c710ed2 100644
--- a/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
@@ -8,6 +8,7 @@
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
@@ -31,11 +32,24 @@
}
@Override
+ R8CFTestRunner withKeepAll() {
+ return withBuilderTransformation(
+ builder ->
+ builder
+ .setMode(CompilationMode.DEBUG)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .addProguardConfiguration(
+ ImmutableList.of("-keepattributes *"), Origin.unknown()));
+ }
+
+ @Override
void build(Path inputFile, Path out) throws Throwable {
R8Command.Builder builder = R8Command.builder();
for (UnaryOperator<R8Command.Builder> transformation : builderTransformations) {
builder = transformation.apply(builder);
}
+ // TODO(b/124041175): We should not be linking against the Java 8 runtime for Java 9 inputs.
builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
R8Command command =
builder.addProgramFiles(inputFile).setOutput(out, OutputMode.ClassFile).build();
@@ -67,7 +81,7 @@
}
}
- execute(testName, qualifiedMainClass, new Path[] {inputFile}, new Path[] {out});
+ execute(testName, qualifiedMainClass, new Path[] {inputFile}, new Path[] {out}, args);
if (expectedToThrow) {
System.out.println("Did not throw ApiLevelException as expected");
@@ -85,15 +99,23 @@
return new R8CFTestRunner(testName, packageName, mainClass);
}
- void execute(String testName, String qualifiedMainClass, Path[] inputJars, Path[] outputJars)
+ @Override
+ void execute(
+ String testName,
+ String qualifiedMainClass,
+ Path[] inputJars,
+ Path[] outputJars,
+ List<String> args)
throws IOException {
boolean expectedToFail = expectedToFailCf(testName);
if (expectedToFail) {
thrown.expect(Throwable.class);
}
- ProcessResult outputResult = ToolHelper.runJava(Arrays.asList(outputJars), qualifiedMainClass);
+ String[] mainAndArgs =
+ ImmutableList.builder().add(qualifiedMainClass).addAll(args).build().toArray(new String[0]);
+ ProcessResult outputResult = ToolHelper.runJava(Arrays.asList(outputJars), mainAndArgs);
ToolHelper.ProcessResult inputResult =
- ToolHelper.runJava(ImmutableList.copyOf(inputJars), qualifiedMainClass);
+ ToolHelper.runJava(ImmutableList.copyOf(inputJars), mainAndArgs);
assertEquals(inputResult.toString(), outputResult.toString());
if (inputResult.exitCode != 0) {
System.out.println(inputResult);
diff --git a/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java b/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
index 36a4f00..bfd929e 100644
--- a/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
+++ b/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
@@ -45,7 +45,7 @@
IRConverter converter = new IRConverter(new AppInfoWithSubtyping(application), options);
converter.optimize(application);
DexProgramClass clazz = application.classes().iterator().next();
- assertEquals(4, clazz.directMethods().length);
+ assertEquals(4, clazz.directMethods().size());
for (DexEncodedMethod method : clazz.directMethods()) {
if (!method.method.name.toString().equals("main")) {
assertEquals(2, method.getCode().asDexCode().instructions.length);
diff --git a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
index c95d8a6..f3f5339 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
@@ -10,6 +10,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
@@ -274,6 +275,7 @@
@Test
public void desugaredPrivateInterfaceMethods() throws Throwable {
+ assumeFalse("CF backend does not desugar", this instanceof R8CFRunExamplesJava9Test);
final String iName = "privateinterfacemethods.I";
test("desugared-private-interface-methods",
"privateinterfacemethods", "PrivateInterfaceMethods")
@@ -297,6 +299,7 @@
public void varHandle() throws Throwable {
test("varhandle", "varhandle", "VarHandleTests")
.withMinApiLevel(AndroidApiLevel.P.getLevel())
+ .withKeepAll()
.run();
}
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
index 15c03dc..7ad544b 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
@@ -8,6 +8,7 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
+import com.android.tools.r8.utils.ListUtils;
import java.util.ArrayList;
import java.util.List;
import org.hamcrest.Matcher;
@@ -44,25 +45,34 @@
return errors;
}
+ private void assertEmpty(String type, List<Diagnostic> messages) {
+ assertEquals(
+ "Expected no "
+ + type
+ + " messages, got:\n"
+ + String.join("\n", ListUtils.map(messages, m -> m.getDiagnosticMessage())),
+ 0,
+ messages.size());
+ }
public TestDiagnosticMessages assertNoMessages() {
- assertEquals(0, getInfos().size());
- assertEquals(0, getWarnings().size());
- assertEquals(0, getErrors().size());
+ assertEmpty("info", getInfos());
+ assertEmpty("warning", getWarnings());
+ assertEmpty("error", getErrors());
return this;
}
public TestDiagnosticMessages assertOnlyInfos() {
assertNotEquals(0, getInfos().size());
- assertEquals(0, getWarnings().size());
- assertEquals(0, getErrors().size());
+ assertEmpty("warning", getWarnings());
+ assertEmpty("error", getErrors());
return this;
}
public TestDiagnosticMessages assertOnlyWarnings() {
- assertEquals(0, getInfos().size());
+ assertEmpty("info", getInfos());
assertNotEquals(0, getWarnings().size());
- assertEquals(0, getErrors().size());
+ assertEmpty("error", getErrors());
return this;
}
diff --git a/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java b/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
index 3d75846..a958c3a 100644
--- a/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
+++ b/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
@@ -72,7 +72,7 @@
private int countCatchHandlers(AndroidApp inputApp) throws Exception {
CodeInspector inspector = new CodeInspector(inputApp);
DexClass dexClass = inspector.clazz(TestClass.class).getDexClass();
- Code code = dexClass.virtualMethods()[0].getCode();
+ Code code = dexClass.virtualMethods().get(0).getCode();
if (code.isCfCode()) {
CfCode cfCode = code.asCfCode();
Set<CfLabel> targets = Sets.newIdentityHashSet();
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
index 46ed96b..da70125 100644
--- a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.cf;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.ByteDataView;
import com.android.tools.r8.ClassFileConsumer;
@@ -23,10 +24,14 @@
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.ExecutionException;
import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -133,6 +138,16 @@
ProcessResult runCf = ToolHelper.runJava(outCf, CLASS.getCanonicalName(), expected);
assertEquals(runCf.stderr, 0, runCf.exitCode);
assertEquals(runInput.toString(), runCf.toString());
+ // Ensure that we did not inline the const method handle
+ ensureConstHandleNotInlined(outCf);
+ }
+
+ private void ensureConstHandleNotInlined(Path file) throws IOException, ExecutionException {
+ CodeInspector inspector = new CodeInspector(file);
+ MethodSubject subject = inspector.clazz(MethodHandleTest.D.class).method(
+ "java.lang.MethodHandle", "vcviSpecialMethod");
+ assertTrue(inspector.clazz(MethodHandleTest.D.class)
+ .method("java.lang.invoke.MethodHandle", "vcviSpecialMethod").isPresent());
}
private void runDex() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java b/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java
index 2664ef4..5d536cf 100644
--- a/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java
+++ b/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java
@@ -71,7 +71,7 @@
private int countCatchHandlers(AndroidApp inputApp) throws Exception {
CodeInspector inspector = new CodeInspector(inputApp);
DexClass dexClass = inspector.clazz(TestClass.class).getDexClass();
- Code code = dexClass.virtualMethods()[0].getCode();
+ Code code = dexClass.virtualMethods().get(0).getCode();
if (code.isCfCode()) {
CfCode cfCode = code.asCfCode();
Set<CfLabel> targets = Sets.newIdentityHashSet();
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithInvokeInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithInvokeInterfaceTest.java
new file mode 100644
index 0000000..e9b1b8c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithInvokeInterfaceTest.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2019, 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.desugar;
+
+public class DefaultLambdaWithInvokeInterfaceTest {
+
+ public interface I {
+ int run();
+ }
+
+ public interface J {
+ default String stateless() {
+ return "hest";
+ }
+
+ default I stateful() {
+ return () -> stateless().length();
+ }
+ }
+
+ public static void main(String[] args) {
+ System.out.println(new J() {}.stateful().run());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithInvokeInterfaceTestRunner.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithInvokeInterfaceTestRunner.java
new file mode 100644
index 0000000..732612d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithInvokeInterfaceTestRunner.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2019, 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.desugar;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+
+public class DefaultLambdaWithInvokeInterfaceTestRunner extends TestBase {
+
+ final Class<?> CLASS = DefaultLambdaWithInvokeInterfaceTest.class;
+ final String EXPECTED = StringUtils.lines("4");
+
+ @Test
+ public void testJvm() throws Exception {
+ testForJvm().addTestClasspath().run(CLASS).assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForD8()
+ .addProgramClassesAndInnerClasses(CLASS)
+ .setMinApi(AndroidApiLevel.K)
+ .compile()
+ // TODO(b/123506120): Add .assertNoMessages()
+ .run(CLASS)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(inspector -> assertThat(inspector.clazz(CLASS), isPresent()));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTest.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTest.java
new file mode 100644
index 0000000..eb4e75b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTest.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2019, 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.desugar;
+
+public class DefaultLambdaWithSelfReferenceTest {
+
+ interface I {
+ String foo();
+
+ default I stateless() {
+ return () -> "stateless";
+ }
+
+ default I stateful() {
+ return () -> "stateful(" + stateless().foo() + ")";
+ }
+ }
+
+ public static void main(String[] args) {
+ System.out.println(((I) () -> "foo").stateful().foo());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
new file mode 100644
index 0000000..bc193c8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
@@ -0,0 +1,94 @@
+// Copyright (c) 2019, 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.desugar;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.Disassemble;
+import com.android.tools.r8.Disassemble.DisassembleCommand;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+
+public class DefaultLambdaWithSelfReferenceTestRunner extends TestBase {
+
+ final Class<?> CLASS = DefaultLambdaWithSelfReferenceTest.class;
+ final String EXPECTED = StringUtils.lines("stateful(stateless)");
+
+ @Test
+ public void testJvm() throws Exception {
+ testForJvm().addTestClasspath().run(CLASS).assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void test() throws Exception {
+ Path out1 = temp.newFolder().toPath().resolve("out1.zip");
+ testForD8()
+ .addProgramClassesAndInnerClasses(CLASS)
+ .setMinApi(AndroidApiLevel.K)
+ .compile()
+ // TODO(b/123506120): Add .assertNoMessages()
+ .writeToZip(out1)
+ .run(CLASS)
+ .assertSuccessWithOutput(EXPECTED);
+
+ Path outPerClassDir = temp.newFolder().toPath();
+ Collection<Path> innerClasses =
+ ToolHelper.getClassFilesForInnerClasses(Collections.singleton(CLASS));
+
+ int i = 0;
+ List<Path> outs = new ArrayList<>();
+ {
+ Path mainOut = outPerClassDir.resolve("class" + i++ + ".zip");
+ outs.add(mainOut);
+ testForD8()
+ .addProgramClasses(CLASS)
+ .addClasspathFiles(ToolHelper.getClassPathForTests())
+ .setIntermediate(true)
+ .setMinApi(AndroidApiLevel.K)
+ .compile()
+ // TODO(b/123506120): Add .assertNoMessages()
+ .writeToZip(mainOut);
+ }
+ for (Path innerClass : innerClasses) {
+ Path out = outPerClassDir.resolve("class" + i++ + ".zip");
+ outs.add(out);
+ testForD8()
+ .addProgramFiles(innerClass)
+ .addClasspathFiles(ToolHelper.getClassPathForTests())
+ .setIntermediate(true)
+ .setMinApi(AndroidApiLevel.K)
+ .compile()
+ // TODO(b/123506120): Add .assertNoMessages()
+ .writeToZip(out);
+ }
+
+ Path out2 = temp.newFolder().toPath().resolve("out2.zip");
+ testForD8()
+ .addProgramFiles(outs)
+ .compile()
+ // TODO(b/123506120): Add .assertNoMessages()
+ .writeToZip(out2)
+ .run(CLASS)
+ .assertSuccessWithOutput(EXPECTED);
+
+ Path dissasemble1 = temp.newFolder().toPath().resolve("disassemble1.txt");
+ Path dissasemble2 = temp.newFolder().toPath().resolve("disassemble2.txt");
+ Disassemble.disassemble(
+ DisassembleCommand.builder().addProgramFiles(out1).setOutputPath(dissasemble1).build());
+ Disassemble.disassemble(
+ DisassembleCommand.builder().addProgramFiles(out2).setOutputPath(dissasemble2).build());
+ String content1 = StringUtils.join(Files.readAllLines(dissasemble1), "\n");
+ String content2 = StringUtils.join(Files.readAllLines(dissasemble2), "\n");
+ assertEquals(content1, content2);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java b/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java
index 68daeb8..9c9e785 100644
--- a/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java
+++ b/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java
@@ -59,11 +59,11 @@
assertThat(clazz, isPresent());
// There are two direct methods, but only because one is <init>.
- assertEquals(2, clazz.getDexClass().directMethods().length);
+ assertEquals(2, clazz.getDexClass().directMethods().size());
assertThat(clazz.method("void", "<init>", ImmutableList.of()), isPresent());
// There is only one virtual method.
- assertEquals(1, clazz.getDexClass().virtualMethods().length);
+ assertEquals(1, clazz.getDexClass().virtualMethods().size());
}
@Test
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTest.java
new file mode 100644
index 0000000..519c273
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTest.java
@@ -0,0 +1,102 @@
+// Copyright (c) 2019, 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.ir.optimize.inliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InlineSynchronizedTest extends TestBase {
+
+ private static final List<String> METHOD_NAMES =
+ ImmutableList.of(
+ "println",
+ "normalInlinedSynchronized",
+ "classInlinedSynchronized",
+ "normalInlinedControl",
+ "classInlinedControl");
+
+ @Parameterized.Parameters(name = "Backend: {0}, ClassInlining: {1}")
+ public static Collection data() {
+ return buildParameters(Backend.values(), BooleanUtils.values());
+ }
+
+ private final Backend backend;
+ private final boolean classInlining;
+
+ public InlineSynchronizedTest(Backend backend, boolean classInlining) {
+ this.backend = backend;
+ this.classInlining = classInlining;
+ }
+
+ private void configure(InternalOptions options) {
+ options.enableClassInlining = classInlining;
+ }
+
+ @Test
+ public void test() throws Exception {
+ CodeInspector codeInspector =
+ testForR8(backend)
+ .addProgramClasses(InlineSynchronizedTestClass.class)
+ .addKeepMainRule("com.android.tools.r8.ir.optimize.inliner.InlineSynchronizedTestClass")
+ .addKeepRules("-dontobfuscate")
+ .addOptionsModification(o -> o.enableClassInlining = classInlining)
+ .compile()
+ .inspector();
+
+ ClassSubject classSubject = codeInspector.clazz(InlineSynchronizedTestClass.class);
+ assertThat(classSubject, isPresent());
+ MethodSubject methodSubject = classSubject.mainMethod();
+ Iterator<InstructionSubject> it = methodSubject.iterateInstructions();
+ int[] counts = new int[METHOD_NAMES.size()];
+ while (it.hasNext()) {
+ InstructionSubject instruction = it.next();
+ if (!instruction.isInvoke()) {
+ continue;
+ }
+ DexString invokedName = ((InvokeInstructionSubject) instruction).invokedMethod().name;
+ int idx = METHOD_NAMES.indexOf(invokedName.toString());
+ if (idx >= 0) {
+ ++counts[idx];
+ }
+ }
+ // Synchronized methods can never be inlined.
+ assertCount(counts, "normalInlinedSynchronized", 1);
+ assertCount(counts, "classInlinedSynchronized", 1);
+ // Control methods must be inlined, only the normal one or both, depending on classInlining.
+ assertCount(counts, "normalInlinedControl", 0);
+ assertCount(counts, "classInlinedControl", classInlining ? 0 : 1);
+ // Double check the total.
+ int total = 0;
+ for (int i = 0; i < counts.length; ++i) {
+ total += counts[i];
+ }
+ assertEquals(4, total);
+ }
+
+ private static void assertCount(int counts[], String methodName, int expectedCount) {
+ int idx = METHOD_NAMES.indexOf(methodName);
+ assert idx >= 0;
+ assertEquals(expectedCount, counts[idx]);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTestClass.java
new file mode 100644
index 0000000..4e2cae1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTestClass.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2019, 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.ir.optimize.inliner;
+
+class InlineSynchronizedTestClass {
+ private synchronized void normalInlinedSynchronized() {
+ System.out.println("InlineSynchronizedTestClass::normalInlinedSynchronized");
+ }
+
+ public synchronized void classInlinedSynchronized() {
+ System.out.println("InlineSynchronizedTestClass::classInlinedSynchronized");
+ }
+
+ private void normalInlinedControl() {
+ System.out.println("InlineSynchronizedTestClass::normalInlinedControl");
+ }
+
+ public void classInlinedControl() {
+ System.out.println("InlineSynchronizedTestClass::classInlinedControl");
+ }
+
+ public static void main(String[] args) {
+ // Test normal inlining.
+ InlineSynchronizedTestClass testClass = new InlineSynchronizedTestClass();
+ testClass.normalInlinedSynchronized();
+ testClass.normalInlinedControl();
+
+ // Test class-inlining.
+ new InlineSynchronizedTestClass().classInlinedSynchronized();
+ new InlineSynchronizedTestClass().classInlinedControl();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineWithSimpleFieldNoValue.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineWithSimpleFieldNoValue.java
new file mode 100644
index 0000000..eac5881
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineWithSimpleFieldNoValue.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2019, 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.ir.optimize.inliner;
+
+import static junit.framework.TestCase.assertTrue;
+
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+
+class TestClass {
+ public static void main(String[] args) {
+ System.out.println(InlineFrom.getValue());
+ InlineFrom.value = 43;
+ System.out.println(InlineFrom.value);
+ }
+}
+
+// Simple class, with no clinit and no static field initialization.
+// We should always inline getValue()
+// We ensure that we use value.
+class InlineFrom {
+ public static int value;
+
+ public static int getValue() {
+ return 42;
+ }
+}
+
+public class InlineWithSimpleFieldNoValue extends TestBase {
+ @Test
+ public void test() throws Exception {
+ R8TestRunResult result = testForR8(Backend.DEX)
+ .addKeepMainRule(TestClass.class)
+ .addProgramClasses(TestClass.class, InlineFrom.class)
+ .run(TestClass.class)
+ .assertSuccessWithOutput(StringUtils.lines("42", "43"));
+ assertTrue(result.inspector().clazz(InlineFrom.class).allMethods().isEmpty());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
index b8c4b47..7e9269d 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
@@ -183,7 +183,7 @@
if (check.match(clazz)) {
// Validate static initializer.
if (check instanceof Group) {
- assertEquals(clazz.directMethods().length, ((Group) check).singletons == 0 ? 1 : 2);
+ assertEquals(clazz.directMethods().size(), ((Group) check).singletons == 0 ? 1 : 2);
}
list.remove(clazz);
@@ -228,8 +228,8 @@
} else {
assertTrue(isJStyleLambdaOrGroup(clazz));
// Taking the number of any virtual method parameters seems to be good enough.
- assertTrue(clazz.virtualMethods().length > 0);
- return clazz.virtualMethods()[0].method.proto.parameters.size();
+ assertTrue(clazz.virtualMethods().size() > 0);
+ return clazz.virtualMethods().get(0).method.proto.parameters.size();
}
fail("Failed to get arity for " + clazz.type.descriptor.toString());
throw new AssertionError();
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
index ac75230..bc23757 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -910,11 +910,10 @@
MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
- // Field is public and getter is small so we expect to always inline it.
+ // Field is public and getter/setter is only called from one place so we expect to always
+ // inline it.
checkMethodIsRemoved(objectClass, getter);
-
- // Setter has null check of new value, thus may not be inlined.
- checkMethodIsKept(objectClass, setter);
+ checkMethodIsRemoved(objectClass, setter);
});
}
@@ -934,7 +933,7 @@
MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
checkMethodIsRemoved(objectClass, getter);
- checkMethodIsKept(objectClass, setter);
+ checkMethodIsRemoved(objectClass, setter);
assertTrue(fieldSubject.getField().accessFlags.isPublic());
});
}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index ff948aa..5103951 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -226,14 +226,15 @@
@Test
public void everyThirdClassInMainWithDexSplitter() throws Throwable {
List<String> featureMappings = new ArrayList<>();
+ List<String> inFeatureMapping = new ArrayList<>();
ImmutableList.Builder<String> mainDexBuilder = ImmutableList.builder();
for (int i = 0; i < MANY_CLASSES.size(); i++) {
String clazz = MANY_CLASSES.get(i);
// Write the first 2 classes into the split.
- if (i < 2) {
+ if (i < 10) {
featureMappings.add(clazz + ":feature1");
- continue;
+ inFeatureMapping.add(clazz);
}
if (i % 3 == 0) {
mainDexBuilder.add(clazz);
@@ -246,19 +247,20 @@
FileUtils.writeTextFile(mainDexFile, ListUtils.map(mainDexList, MainDexListTests::typeToEntry));
Path output = temp.getRoot().toPath().resolve("split_output");
Files.createDirectories(output);
-
- Options options = new Options();
+ TestDiagnosticsHandler diagnosticsHandler = new TestDiagnosticsHandler();
+ Options options = new Options(diagnosticsHandler);
options.addInputArchive(getManyClassesMultiDexAppPath().toString());
options.setFeatureSplitMapping(featureSplitMapping.toString());
options.setOutput(output.toString());
options.setMainDexList(mainDexFile.toString());
DexSplitter.run(options);
+ assertEquals(0, diagnosticsHandler.numberOfErrorsAndWarnings());
Path baseDir = output.resolve("base");
CodeInspector inspector =
new CodeInspector(
AndroidApp.builder().addProgramFiles(baseDir.resolve("classes.dex")).build());
for (String clazz : mainDexList) {
- if (!inspector.clazz(clazz).isPresent()) {
+ if (!inspector.clazz(clazz).isPresent() && !inFeatureMapping.contains(clazz)) {
failedToFindClassInExpectedFile(baseDir, clazz);
}
}
@@ -923,10 +925,20 @@
private class TestDiagnosticsHandler implements DiagnosticsHandler {
public List<Diagnostic> errors = new ArrayList<>();
+ public List<Diagnostic> warnings = new ArrayList<>();
+
+ public int numberOfErrorsAndWarnings() {
+ return errors.size() + warnings.size();
+ }
@Override
public void error(Diagnostic error) {
errors.add(error);
}
+
+ @Override
+ public void warning(Diagnostic warning) {
+ warnings.add(warning);
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/resolution/b123730538/B123730538.java b/src/test/java/com/android/tools/r8/resolution/b123730538/B123730538.java
index e298c9d..462d514 100644
--- a/src/test/java/com/android/tools/r8/resolution/b123730538/B123730538.java
+++ b/src/test/java/com/android/tools/r8/resolution/b123730538/B123730538.java
@@ -22,7 +22,6 @@
import java.nio.file.Path;
import java.util.List;
import org.junit.BeforeClass;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -59,17 +58,18 @@
testForProguard()
.addProgramFiles(inJar)
.addKeepMainRule(MAIN)
+ .addKeepRules("-dontoptimize")
.run(MAIN)
.assertSuccessWithOutput(EXPECTED_OUTPUT)
.inspect(this::inspect);
}
- @Ignore("b/123730538")
@Test
public void testR8() throws Exception {
testForR8(backend)
.addProgramFiles(CLASSES)
.addKeepMainRule(MAIN)
+ .addKeepRules("-dontoptimize")
.run(MAIN)
.assertSuccessWithOutput(EXPECTED_OUTPUT)
.inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java b/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java
index d28770a..5b02222 100644
--- a/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java
@@ -100,7 +100,7 @@
assertThat(classSubject, isPresent());
assertThat(classSubject, not(isRenamed()));
DexClass clazz = classSubject.getDexClass();
- assertEquals(3, clazz.virtualMethods().length);
+ assertEquals(3, clazz.virtualMethods().size());
for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
MethodSubject methodSubject =
@@ -141,7 +141,7 @@
assertThat(classSubject, isPresent());
assertThat(classSubject, not(isRenamed()));
DexClass clazz = classSubject.getDexClass();
- assertEquals(3, clazz.virtualMethods().length);
+ assertEquals(3, clazz.virtualMethods().size());
for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
MethodSubject methodSubject =
@@ -163,7 +163,7 @@
assertThat(classSubject, isPresent());
assertThat(classSubject, not(isRenamed()));
DexClass clazz = classSubject.getDexClass();
- assertEquals(3, clazz.virtualMethods().length);
+ assertEquals(3, clazz.virtualMethods().size());
for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
MethodSubject methodSubject =
diff --git a/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java b/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java
index 5038729..e191921 100644
--- a/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.shaking.desugar.interfacemethods;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@@ -60,11 +59,9 @@
MethodSubject m = c.uniqueMethodWithName("m");
assertThat(m, isPresent());
assertTrue(m.getMethod().hasCode());
- // TODO(b/123778921): why are I and C not merged without merge annotations?
+ // TODO(b/124017330): Verify that I$-CC.m() has been inlined into C.m().
//assertTrue(
// m.iterateInstructions(i -> i.isConstString("I::m", JumboStringMode.ALLOW)).hasNext());
-
- // TODO(b/123778921): No companion class is in the output.
//codeInspector.forAllClasses(classSubject -> {
// assertFalse(classSubject.getOriginalDescriptor().contains("$-CC"));
//});
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
index d300a4e..da38a47 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
@@ -12,7 +12,6 @@
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
-import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -45,11 +44,6 @@
return ImmutableList.of(Shrinker.R8_CF, Shrinker.PROGUARD6, Shrinker.R8);
}
- private void configure(InternalOptions options) {
- // Disable inlining, otherwise classes can be pruned away if all their methods are inlined.
- options.enableInlining = false;
- }
-
@Test
public void ifOnPublic_noPublicClassForIfRule() throws Exception {
List<String> config = ImmutableList.of(
@@ -64,7 +58,7 @@
" public <methods>;",
"}"
);
- CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
assertThat(classSubject, isPresent());
MethodSubject methodSubject = classSubject.method(publicMethod);
@@ -93,7 +87,7 @@
" public <methods>;",
"}"
);
- CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
assertThat(classSubject, isPresent());
MethodSubject methodSubject = classSubject.method(publicMethod);
@@ -126,7 +120,7 @@
" !public <methods>;",
"}"
);
- CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
assertThat(classSubject, isPresent());
MethodSubject methodSubject = classSubject.method(publicMethod);
@@ -160,7 +154,7 @@
" public <methods>;",
"}"
);
- CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
assertThat(classSubject, isPresent());
MethodSubject methodSubject = classSubject.method(publicMethod);
@@ -192,7 +186,7 @@
" !public <methods>;",
"}"
);
- CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
assertThat(classSubject, isPresent());
MethodSubject methodSubject = classSubject.method(publicMethod);
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/C.java b/src/test/java/com/android/tools/r8/shaking/testrules/C.java
index 79370a4..7a30a65 100644
--- a/src/test/java/com/android/tools/r8/shaking/testrules/C.java
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/C.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.shaking.testrules;
+import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NeverMerge;
@NeverMerge
@@ -11,6 +12,7 @@
private static int i;
+ @NeverInline
public static int x() {
return i;
}
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
index 432711f..4bf8c92 100644
--- a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
@@ -69,6 +69,7 @@
ImmutableList.of(
"-keep class **.Main { *; }",
"-nevermerge @com.android.tools.r8.NeverMerge class *",
+ "-neverinline class *{ @com.android.tools.r8.NeverInline <methods>;}",
"-dontobfuscate"));
ClassSubject classA = inspector.clazz(A.class);
@@ -99,6 +100,7 @@
"-neverinline class **.B { method(); }",
"-keep class **.Main { *; }",
"-nevermerge @com.android.tools.r8.NeverMerge class *",
+ "-neverinline class *{ @com.android.tools.r8.NeverInline <methods>;}",
"-dontobfuscate"));
ClassSubject classA = inspector.clazz(A.class);
@@ -126,6 +128,7 @@
"-forceinline class **.B { int m(int, int); }",
"-keep class **.Main { *; }",
"-nevermerge @com.android.tools.r8.NeverMerge class *",
+ "-neverinline class *{ @com.android.tools.r8.NeverInline <methods>;}",
"-dontobfuscate"));
ClassSubject classA = inspector.clazz(A.class);
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index eacc338..bfeac7a 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -848,13 +848,13 @@
CodeInspector inspector = new CodeInspector(processedApplication);
ClassSubject clazz = inspector.clazz(OutlineOptions.CLASS_NAME);
assertTrue(clazz.isPresent());
- assertEquals(3, clazz.getDexClass().directMethods().length);
+ assertEquals(3, clazz.getDexClass().directMethods().size());
// Collect the return types of the putlines for the body of method1 and method2.
List<DexType> r = new ArrayList<>();
- for (int i = 0; i < clazz.getDexClass().directMethods().length; i++) {
- if (clazz.getDexClass().directMethods()[i].getCode().asDexCode().instructions[0]
+ for (int i = 0; i < clazz.getDexClass().directMethods().size(); i++) {
+ if (clazz.getDexClass().directMethods().get(i).getCode().asDexCode().instructions[0]
instanceof InvokeVirtual) {
- r.add(clazz.getDexClass().directMethods()[i].method.proto.returnType);
+ r.add(clazz.getDexClass().directMethods().get(i).method.proto.returnType);
}
}
assert r.size() == 2;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index a9058ff..aa083da 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -185,7 +185,7 @@
}
static <S, T extends Subject> void forAll(
- S[] items,
+ List<? extends S> items,
BiFunction<S, FoundClassSubject, ? extends T> constructor,
FoundClassSubject clazz,
Consumer<T> consumer) {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index 11ee3af..c7b3781 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.naming.MemberNaming.Signature;
import com.android.tools.r8.naming.signature.GenericSignatureParser;
import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
@@ -89,7 +90,7 @@
: new FoundMethodSubject(codeInspector, encoded, this);
}
- private DexEncodedMethod findMethod(DexEncodedMethod[] methods, DexMethod dexMethod) {
+ private DexEncodedMethod findMethod(List<DexEncodedMethod> methods, DexMethod dexMethod) {
for (DexEncodedMethod method : methods) {
if (method.method.equals(dexMethod)) {
return method;
@@ -113,12 +114,12 @@
@Override
public void forAllFields(Consumer<FoundFieldSubject> inspection) {
CodeInspector.forAll(
- dexClass.staticFields(),
+ Arrays.asList(dexClass.staticFields()),
(dexField, clazz) -> new FoundFieldSubject(codeInspector, dexField, clazz),
this,
inspection);
CodeInspector.forAll(
- dexClass.instanceFields(),
+ Arrays.asList(dexClass.instanceFields()),
(dexField, clazz) -> new FoundFieldSubject(codeInspector, dexField, clazz),
this,
inspection);
diff --git a/third_party/cf_segments.tar.gz.sha1 b/third_party/cf_segments.tar.gz.sha1
index e4d4bec..7d32955 100644
--- a/third_party/cf_segments.tar.gz.sha1
+++ b/third_party/cf_segments.tar.gz.sha1
@@ -1 +1 @@
-2172e0b42550dd144958f9ead6d0310a68b43d65
\ No newline at end of file
+cd6068354ebd74e098011517576f09a375b324a3
\ No newline at end of file
diff --git a/third_party/opensource_apps.tar.gz.sha1 b/third_party/opensource_apps.tar.gz.sha1
new file mode 100644
index 0000000..9fb2bb2
--- /dev/null
+++ b/third_party/opensource_apps.tar.gz.sha1
@@ -0,0 +1 @@
+64b0689bce8f6789320b34da4e91c6dbcc1296e9
\ No newline at end of file
diff --git a/tools/apk_utils.py b/tools/apk_utils.py
index 5a6aa94..af24ec1 100644
--- a/tools/apk_utils.py
+++ b/tools/apk_utils.py
@@ -23,7 +23,8 @@
]
utils.RunCmd(cmd, quiet=quiet)
-def sign_with_apksigner(build_tools_dir, unsigned_apk, signed_apk, keystore, password):
+def sign_with_apksigner(
+ build_tools_dir, unsigned_apk, signed_apk, keystore, password, quiet=False):
cmd = [
os.path.join(build_tools_dir, 'apksigner'),
'sign',
@@ -34,5 +35,4 @@
'--out', signed_apk,
unsigned_apk
]
- utils.PrintCmd(cmd)
- subprocess.check_call(cmd)
+ utils.RunCmd(cmd, quiet=quiet)
diff --git a/tools/as_utils.py b/tools/as_utils.py
index 5f224a6..7a6528a 100644
--- a/tools/as_utils.py
+++ b/tools/as_utils.py
@@ -127,7 +127,7 @@
if '-printconfiguration' not in line:
f.write(line)
# Check that there is a line-break at the end of the file or insert one.
- if lines[-1].strip():
+ if len(lines) and lines[-1].strip():
f.write('\n')
f.write('-printconfiguration {}\n'.format(destination))
@@ -159,7 +159,7 @@
Move(src, dst, quiet=quiet)
def MoveFile(src, dst, quiet=False):
- assert os.path.isfile(src)
+ assert os.path.isfile(src), "Expected a file to be present at " + src
Move(src, dst, quiet=quiet)
def MoveProfileReportTo(dest_dir, build_stdout, quiet=False):
diff --git a/tools/download_all_benchmark_dependencies.py b/tools/download_all_benchmark_dependencies.py
new file mode 100755
index 0000000..43ec0ab
--- /dev/null
+++ b/tools/download_all_benchmark_dependencies.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+# Copyright (c) 2019, 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.
+
+# Utility script to make it easier to update what golem builds.
+
+import gradle
+import sys
+import utils
+
+BUILD_TARGETS = ['downloadDeps', 'downloadAndroidCts', 'downloadDx']
+
+def Main():
+ gradle.RunGradle(BUILD_TARGETS)
+ # Download opensource_apps and place in build.
+ utils.DownloadFromX20(utils.OPENSOURCE_APPS_SHA_FILE)
+
+
+if __name__ == '__main__':
+ sys.exit(Main())
diff --git a/tools/historic_memory_usage.py b/tools/historic_memory_usage.py
index f23faaa..376426f 100755
--- a/tools/historic_memory_usage.py
+++ b/tools/historic_memory_usage.py
@@ -38,6 +38,10 @@
result.add_option('--output',
default='build',
help='Directory where to output results')
+ result.add_option('--timeout',
+ type=int,
+ default=0,
+ help='Set timeout instead of waiting for OOM.')
return result.parse_args(argv)
@@ -122,7 +126,10 @@
def run_on_app(options, commit):
app = options.app
compiler = options.compiler
- cmd = ['tools/run_on_app.py', '--app', app, '--compiler', compiler,
+ cmd = ['tools/run_on_app.py',
+ '--app', app,
+ '--compiler', compiler,
+ '--timeout', str(options.timeout),
'--no-build', '--find-min-xmx']
stdout = subprocess.check_output(cmd)
output_path = options.output or 'build'
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index 41d4578..b404dd3 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -27,6 +27,9 @@
# We use this magic exit code to signal that the program OOM'ed
OOM_EXIT_CODE = 42
+# According to Popen.returncode doc:
+# A negative value -N indicates that the child was terminated by signal N.
+TIMEOUT_KILL_CODE = -9
def ParseOptions(argv):
result = optparse.OptionParser()
@@ -58,6 +61,10 @@
help='Find the minimum amount of memory we can run in',
default=False,
action='store_true')
+ result.add_option('--timeout',
+ type=int,
+ default=0,
+ help='Set timeout instead of waiting for OOM.')
result.add_option('--golem',
help='Running on golem, do not build or download',
default=False,
@@ -178,7 +185,7 @@
assert len(args) == 0
# If we can run in 128 MB then we are good (which we can for small examples
# or D8 on medium sized examples)
- not_working = 128
+ not_working = 128 if options.compiler == 'd8' else 1024
working = 1024 * 8
exit_code = 0
while working - not_working > 32:
@@ -191,11 +198,15 @@
exit_code = run_with_options(options, [], extra_args)
t1 = time.time()
print('Running took: %s ms' % (1000.0 * (t1 - t0)))
- if exit_code != 0 and exit_code != OOM_EXIT_CODE:
- print('Non OOM error executing, exiting')
- return 2
+ if exit_code != 0:
+ if exit_code not in [OOM_EXIT_CODE, TIMEOUT_KILL_CODE]:
+ print('Non OOM/Timeout error executing, exiting')
+ return 2
if exit_code == 0:
working = next_candidate
+ elif exit_code == TIMEOUT_KILL_CODE:
+ print('Timeout. Continue to the next candidate.')
+ not_working = next_candidate
else:
assert exit_code == OOM_EXIT_CODE
not_working = next_candidate
@@ -356,7 +367,9 @@
debug=not options.no_debug,
profile=options.profile,
track_memory_file=options.track_memory_to_file,
- extra_args=extra_args, stderr=stderr)
+ extra_args=extra_args,
+ stderr=stderr,
+ timeout=options.timeout)
if exit_code != 0:
with open(stderr_path) as stderr:
stderr_text = stderr.read()
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index 3a9bf8c..2889d74 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -5,6 +5,7 @@
import apk_masseur
import apk_utils
+import golem
import gradle
import os
import optparse
@@ -17,15 +18,20 @@
import as_utils
-SHRINKERS = ['r8', 'r8-minified', 'r8full', 'r8full-minified', 'proguard']
-WORKING_DIR = utils.BUILD
+SHRINKERS = ['r8', 'r8-full', 'r8-nolib', 'r8-nolib-full', 'pg']
+WORKING_DIR = os.path.join(utils.BUILD, 'opensource_apps')
-if 'R8_BENCHMARK_DIR' in os.environ and os.path.isdir(os.environ['R8_BENCHMARK_DIR']):
+if ('R8_BENCHMARK_DIR' in os.environ
+ and os.path.isdir(os.environ['R8_BENCHMARK_DIR'])):
WORKING_DIR = os.environ['R8_BENCHMARK_DIR']
+# For running on Golem all APPS are bundled as an x20-dependency and then copied
+# to WORKING_DIR. To make it easier to update the app-bundle, remove the folder
+# WORKING_DIR and then run run_on_as_app.py --download-only.
APPS = {
# 'app-name': {
# 'git_repo': ...
+ # 'revision': ...,
# 'app_module': ... (default app)
# 'archives_base_name': ... (default same as app_module)
# 'flavor': ... (default no flavor)
@@ -34,6 +40,7 @@
'AnExplorer': {
'app_id': 'dev.dworks.apps.anexplorer.pro',
'git_repo': 'https://github.com/christofferqa/AnExplorer',
+ 'revision': '365927477b8eab4052a1882d5e358057ae3dee4d',
'flavor': 'googleMobilePro',
'signed-apk-name': 'AnExplorer-googleMobileProRelease-4.0.3.apk',
'min_sdk': 17
@@ -41,6 +48,7 @@
'AntennaPod': {
'app_id': 'de.danoeh.antennapod',
'git_repo': 'https://github.com/christofferqa/AntennaPod.git',
+ 'revision': '77e94f4783a16abe9cc5b78dc2d2b2b1867d8c06',
'flavor': 'play',
'min_sdk': 14,
'compile_sdk': 26
@@ -48,78 +56,105 @@
'apps-android-wikipedia': {
'app_id': 'org.wikipedia',
'git_repo': 'https://github.com/christofferqa/apps-android-wikipedia',
+ 'revision': '686e8aa5682af8e6a905054b935dd2daa57e63ee',
'flavor': 'prod',
- 'signed-apk-name': 'app-prod-universal-release.apk'
+ 'signed-apk-name': 'app-prod-universal-release.apk',
},
'chanu': {
- 'app_id': 'com.chanapps.four.activity',
- 'git_repo': 'https://github.com/mkj-gram/chanu.git',
+ 'app_id': 'com.chanapps.four.activity',
+ 'git_repo': 'https://github.com/mkj-gram/chanu.git',
+ 'revision': '04ade1e9c33d707f0850d5eb9d6fa5e8af814a26',
},
'friendlyeats-android': {
'app_id': 'com.google.firebase.example.fireeats',
- 'git_repo': 'https://github.com/christofferqa/friendlyeats-android.git'
+ 'git_repo': 'https://github.com/christofferqa/friendlyeats-android.git',
+ 'revision': '10091fa0ec37da12e66286559ad1b6098976b07b',
+ },
+ 'Instabug-Android': {
+ 'app_id': 'com.example.instabug',
+ 'git_repo': 'https://github.com/christofferqa/Instabug-Android.git',
+ 'revision': 'b8df78c96630a6537fbc07787b4990afc030cc0f'
},
'KISS': {
'app_id': 'fr.neamar.kiss',
'git_repo': 'https://github.com/christofferqa/KISS',
+ 'revision': '093da9ee0512e67192f62951c45a07a616fc3224',
},
'materialistic': {
'app_id': 'io.github.hidroh.materialistic',
'git_repo': 'https://github.com/christofferqa/materialistic',
+ 'revision': '2b2b2ee25ce9e672d5aab1dc90a354af1522b1d9',
},
'Minimal-Todo': {
'app_id': 'com.avjindersinghsekhon.minimaltodo',
'git_repo': 'https://github.com/christofferqa/Minimal-Todo',
+ 'revision': '9d8c73746762cd376b718858ec1e8783ca07ba7c',
},
'NewPipe': {
'app_id': 'org.schabi.newpipe',
'git_repo': 'https://github.com/christofferqa/NewPipe',
+ 'revision': 'ed543099c7823be00f15d9340f94bdb7cb37d1e6',
},
'rover-android': {
- 'app_id': 'io.rover.app.debug',
- 'app_module': 'debug-app',
- 'git_repo': 'https://github.com/mkj-gram/rover-android.git',
+ 'app_id': 'io.rover.app.debug',
+ 'app_module': 'debug-app',
+ 'git_repo': 'https://github.com/mkj-gram/rover-android.git',
+ 'revision': 'd2e876e597b3af7eab406e38a0e08327a38bd942',
},
'Signal-Android': {
- 'app_id': 'org.thoughtcrime.securesms',
- 'app_module': '',
- 'flavor': 'play',
- 'git_repo': 'https://github.com/mkj-gram/Signal-Android.git',
- 'releaseTarget': 'assemblePlayRelease',
- 'signed-apk-name': 'Signal-play-release-4.32.7.apk',
+ 'app_id': 'org.thoughtcrime.securesms',
+ 'app_module': '',
+ 'flavor': 'play',
+ 'git_repo': 'https://github.com/mkj-gram/Signal-Android.git',
+ 'revision': '85e1a10993e5e9ffe923f0798b26cbc44068ba31',
+ 'releaseTarget': 'assemblePlayRelease',
+ 'signed-apk-name': 'Signal-play-release-4.32.7.apk',
},
'Simple-Calendar': {
'app_id': 'com.simplemobiletools.calendar.pro',
'git_repo': 'https://github.com/christofferqa/Simple-Calendar',
+ 'revision': '82dad8c203eea5a0f0ddb513506d8f1de986ef2b',
'signed-apk-name': 'calendar-release.apk'
},
+ 'sqldelight': {
+ 'app_id': 'com.example.sqldelight.hockey',
+ 'git_repo': 'https://github.com/christofferqa/sqldelight.git',
+ 'revision': '2e67a1126b6df05e4119d1e3a432fde51d76cdc8',
+ 'app_module': 'sample/android',
+ 'archives_base_name': 'android',
+ 'min_sdk': 14,
+ 'compile_sdk': 28,
+ },
'tachiyomi': {
'app_id': 'eu.kanade.tachiyomi',
'git_repo': 'https://github.com/sgjesse/tachiyomi.git',
+ 'revision': 'b15d2fe16864645055af6a745a62cc5566629798',
'flavor': 'standard',
'releaseTarget': 'app:assembleRelease',
'min_sdk': 16
},
'tivi': {
'app_id': 'app.tivi',
- # Forked from https://github.com/chrisbanes/tivi.git removing
- # signingConfigs.
'git_repo': 'https://github.com/sgjesse/tivi.git',
- # TODO(123047413): Fails with R8.
- 'skip': True,
+ 'revision': '7d7f591d6f39d7caeb88dd13bf476c0c06accdfb',
+ 'min_sdk': 23,
+ 'compile_sdk': 28,
},
'Tusky': {
- 'app_id': 'com.keylesspalace.tusky',
- 'git_repo': 'https://github.com/mkj-gram/Tusky.git',
- 'flavor': 'blue'
+ 'app_id': 'com.keylesspalace.tusky',
+ 'git_repo': 'https://github.com/mkj-gram/Tusky.git',
+ 'revision': 'b794f3ab90388add98461ffe70edb65c39351c33',
+ 'flavor': 'blue'
},
'Vungle-Android-SDK': {
- 'app_id': 'com.publisher.vungle.sample',
- 'git_repo': 'https://github.com/mkj-gram/Vungle-Android-SDK.git',
+ 'app_id': 'com.publisher.vungle.sample',
+ 'git_repo': 'https://github.com/mkj-gram/Vungle-Android-SDK.git',
+ 'revision': '3e231396ea7ce97b2655e03607497c75730e45f6',
},
# This does not build yet.
'muzei': {
'git_repo': 'https://github.com/sgjesse/muzei.git',
+ 'revision': 'bed2a5f79c6e08b0a21e3e3f9242232d0848ef74',
'app_module': 'main',
'archives_base_name': 'muzei',
'skip': True,
@@ -151,17 +186,21 @@
return '~~R8' in subprocess.check_output(cmd).strip()
def IsMinifiedR8(shrinker):
- return shrinker == 'r8-minified' or shrinker == 'r8full-minified'
+ return 'nolib' not in shrinker
def IsTrackedByGit(file):
return subprocess.check_output(['git', 'ls-files', file]).strip() != ''
-def GitClone(git_url):
- return subprocess.check_output(['git', 'clone', git_url]).strip()
-
-def GitPull():
- # Use --no-edit to accept the auto-generated merge message, if any.
- return subprocess.call(['git', 'pull', '--no-edit']) == 0
+def GitClone(git_url, revision, checkout_dir, options):
+ result = subprocess.check_output(
+ ['git', 'clone', git_url, checkout_dir]).strip()
+ head_rev = utils.get_HEAD_sha1_for_checkout(checkout_dir)
+ if revision == head_rev:
+ return result
+ warn('Target revision is not head in {}.'.format(checkout_dir))
+ with utils.ChangedWorkingDirectory(checkout_dir, quiet=options.quiet):
+ subprocess.check_output(['git', 'reset', '--hard', revision])
+ return result
def GitCheckout(file):
return subprocess.check_output(['git', 'checkout', file]).strip()
@@ -183,7 +222,7 @@
def UninstallApkOnEmulator(app, config, options):
app_id = config.get('app_id')
process = subprocess.Popen(
- ['adb', 'uninstall', app_id],
+ ['adb', '-s', emulator_id, 'uninstall', app_id],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = process.communicate()
@@ -220,26 +259,23 @@
return True
def GetResultsForApp(app, config, options, temp_dir):
- git_repo = config['git_repo']
-
# Checkout and build in the build directory.
checkout_dir = os.path.join(WORKING_DIR, app)
result = {}
- if not os.path.exists(checkout_dir):
+ if not os.path.exists(checkout_dir) and not options.golem:
with utils.ChangedWorkingDirectory(WORKING_DIR, quiet=options.quiet):
- GitClone(git_repo)
- elif options.pull:
- with utils.ChangedWorkingDirectory(checkout_dir, quiet=options.quiet):
- # Checkout build.gradle to avoid merge conflicts.
- if IsTrackedByGit('build.gradle'):
- GitCheckout('build.gradle')
+ GitClone(config['git_repo'], config['revision'], checkout_dir, options)
- if not GitPull():
- result['status'] = 'failed'
- result['error_message'] = 'Unable to pull from remote'
- return result
+ checkout_rev = utils.get_HEAD_sha1_for_checkout(checkout_dir)
+ if config['revision'] != checkout_rev:
+ msg = 'Checkout is not target revision for {} in {}.'.format(
+ app, checkout_dir)
+ if options.ignore_versions:
+ warn(msg)
+ else:
+ raise Exception(msg)
result['status'] = 'success'
@@ -267,7 +303,7 @@
BuildAppWithShrinker(app, config, shrinker, checkout_dir, out_dir,
temp_dir, options)
dex_size = ComputeSizeOfDexFilesInApk(apk_dest)
- result['apk_dest'] = apk_dest,
+ result['apk_dest'] = apk_dest
result['build_status'] = 'success'
result['dex_size'] = dex_size
result['profile_dest_dir'] = profile_dest_dir
@@ -357,11 +393,8 @@
shrinker,
' for recompilation' if keepRuleSynthesisForRecompilation else ''))
- # Add/remove 'r8.jar' from top-level build.gradle.
- if options.disable_tot:
- as_utils.remove_r8_dependency(checkout_dir)
- else:
- as_utils.add_r8_dependency(checkout_dir, temp_dir, IsMinifiedR8(shrinker))
+ # Add 'r8.jar' from top-level build.gradle.
+ as_utils.add_r8_dependency(checkout_dir, temp_dir, IsMinifiedR8(shrinker))
app_module = config.get('app_module', 'app')
archives_base_name = config.get('archives_base_name', app_module)
@@ -382,13 +415,13 @@
releaseTarget = config.get('releaseTarget')
if not releaseTarget:
- releaseTarget = app_module + ':' + 'assemble' + (
+ releaseTarget = app_module.replace('/', ':') + ':' + 'assemble' + (
flavor.capitalize() if flavor else '') + 'Release'
# Value for property android.enableR8.
enableR8 = 'r8' in shrinker
# Value for property android.enableR8.fullMode.
- enableR8FullMode = shrinker == 'r8full' or shrinker == 'r8full-minified'
+ enableR8FullMode = shrinker == 'r8-full' or shrinker == 'r8-nolib-full'
# Build gradlew command line.
cmd = ['./gradlew', '--no-daemon', 'clean', releaseTarget,
'--profile', '--stacktrace',
@@ -419,14 +452,13 @@
if options.sign_apks and not os.path.isfile(signed_apk):
assert os.path.isfile(unsigned_apk)
if options.sign_apks:
- keystore = 'app.keystore'
- keystore_password = 'android'
apk_utils.sign_with_apksigner(
utils.ANDROID_BUILD_TOOLS,
unsigned_apk,
signed_apk,
- keystore,
- keystore_password)
+ options.keystore,
+ options.keystore_password,
+ quiet=options.quiet)
if os.path.isfile(signed_apk):
apk_dest = os.path.join(out_dir, signed_apk_name)
@@ -507,10 +539,28 @@
def LogResultsForApps(result_per_shrinker_per_app, options):
print('')
for app, result_per_shrinker in sorted(
- result_per_shrinker_per_app.iteritems()):
+ result_per_shrinker_per_app.iteritems(), key=lambda s: s[0].lower()):
LogResultsForApp(app, result_per_shrinker, options)
def LogResultsForApp(app, result_per_shrinker, options):
+ if options.print_dexsegments:
+ LogSegmentsForApp(app, result_per_shrinker, options)
+ else:
+ LogComparisonResultsForApp(app, result_per_shrinker, options)
+
+def LogSegmentsForApp(app, result_per_shrinker, options):
+ for shrinker in SHRINKERS:
+ if shrinker not in result_per_shrinker:
+ continue
+ result = result_per_shrinker[shrinker];
+ benchmark_name = '{}-{}'.format(options.print_dexsegments, app)
+ utils.print_dexsegments(benchmark_name, [result.get('apk_dest')])
+ duration = sum(result.get('profile').values())
+ print('%s-Total(RunTimeRaw): %s ms' % (benchmark_name, duration * 1000))
+ print('%s-Total(CodeSize): %s' % (benchmark_name, result.get('dex_size')))
+
+
+def LogComparisonResultsForApp(app, result_per_shrinker, options):
print(app + ':')
if result_per_shrinker.get('status', 'success') != 'success':
@@ -518,7 +568,7 @@
print(' skipped ({})'.format(error_message))
return
- proguard_result = result_per_shrinker.get('proguard', {})
+ proguard_result = result_per_shrinker.get('pg', {})
proguard_dex_size = float(proguard_result.get('dex_size', -1))
proguard_duration = sum(proguard_result.get('profile', {}).values())
@@ -587,6 +637,27 @@
result.add_option('--app',
help='What app to run on',
choices=APPS.keys())
+ result.add_option('--download-only', '--download_only',
+ help='Whether to download apps without any compilation',
+ default=False,
+ action='store_true')
+ result.add_option('--golem',
+ help='Running on golem, do not download',
+ default=False,
+ action='store_true')
+ result.add_option('--gradle-flags', '--gradle_flags',
+ help='Flags to pass in to gradle')
+ result.add_option('--ignore-versions', '--ignore_versions',
+ help='Allow checked-out app to differ in revision from '
+ 'pinned',
+ default=False,
+ action='store_true')
+ result.add_option('--keystore',
+ help='Path to app.keystore',
+ default='app.keystore')
+ result.add_option('--keystore-password', '--keystore_password',
+ help='Password for app.keystore',
+ default='android')
result.add_option('--monkey',
help='Whether to install and run app(s) with monkey',
default=False,
@@ -595,10 +666,23 @@
help='Number of events that the monkey should trigger',
default=250,
type=int)
- result.add_option('--pull',
- help='Whether to pull the latest version of each app',
+ result.add_option('--no-build', '--no_build',
+ help='Run without building ToT first (only when using ToT)',
default=False,
action='store_true')
+ result.add_option('--quiet',
+ help='Disable verbose logging',
+ default=False,
+ action='store_true')
+ result.add_option('--print-dexsegments',
+ metavar='BENCHMARKNAME',
+ help='Print the sizes of individual dex segments as ' +
+ '\'<BENCHMARKNAME>-<APP>-<segment>(CodeSize): '
+ '<bytes>\'')
+ result.add_option('--r8-compilation-steps', '--r8_compilation_steps',
+ help='Number of times R8 should be run on each app',
+ default=2,
+ type=int)
result.add_option('--sign-apks', '--sign_apks',
help='Whether the APKs should be signed',
default=False,
@@ -606,57 +690,50 @@
result.add_option('--shrinker',
help='The shrinkers to use (by default, all are run)',
action='append')
- result.add_option('--r8-compilation-steps', '--r8_compilation_steps',
- help='Number of times R8 should be run on each app',
- default=2,
- type=int)
- result.add_option('--disable-tot', '--disable_tot',
- help='Whether to disable the use of the ToT version of R8',
- default=False,
- action='store_true')
- result.add_option('--no-build', '--no_build',
- help='Run without building ToT first (only when using ToT)',
- default=False,
- action='store_true')
- result.add_option('--gradle-flags', '--gradle_flags',
- help='Flags to pass in to gradle')
- result.add_option('--quiet',
- help='Disable verbose logging',
- default=False,
- action='store_true')
(options, args) = result.parse_args(argv)
- if options.disable_tot:
- # r8.jar is required for recompiling the generated APK
- options.r8_compilation_steps = 1
if options.shrinker:
for shrinker in options.shrinker:
assert shrinker in SHRINKERS
return (options, args)
+def download_apps(options):
+ # Download apps and place in build
+ with utils.ChangedWorkingDirectory(WORKING_DIR):
+ for app, config in APPS.iteritems():
+ app_dir = os.path.join(WORKING_DIR, app)
+ if not os.path.exists(app_dir):
+ GitClone(config['git_repo'], config['revision'], app_dir, options)
+
+
def main(argv):
global SHRINKERS
(options, args) = ParseOptions(argv)
+ if options.golem:
+ golem.link_third_party()
+ if os.path.exists(WORKING_DIR):
+ shutil.rmtree(WORKING_DIR)
+ shutil.copytree(utils.OPENSOURCE_APPS_FOLDER, WORKING_DIR)
+
+ if not os.path.exists(WORKING_DIR):
+ os.makedirs(WORKING_DIR)
+
+ if options.download_only:
+ download_apps(options)
+ return
+
with utils.TempDir() as temp_dir:
- if options.disable_tot:
- # Cannot run r8 lib without adding r8lib.jar as an dependency
- SHRINKERS = [
- shrinker for shrinker in SHRINKERS
- if 'minified' not in shrinker]
- else:
- if not options.no_build:
- gradle.RunGradle(['r8', 'r8lib'])
+ if not options.no_build or options.golem:
+ gradle.RunGradle(['r8', 'r8lib'])
- assert os.path.isfile(utils.R8_JAR), (
- 'Cannot build from ToT without r8.jar')
- assert os.path.isfile(utils.R8LIB_JAR), (
- 'Cannot build from ToT without r8lib.jar')
+ assert os.path.isfile(utils.R8_JAR), 'Cannot build without r8.jar'
+ assert os.path.isfile(utils.R8LIB_JAR), 'Cannot build without r8lib.jar'
- # Make a copy of r8.jar and r8lib.jar such that they stay the same for
- # the entire execution of this script.
- shutil.copyfile(utils.R8_JAR, os.path.join(temp_dir, 'r8.jar'))
- shutil.copyfile(utils.R8LIB_JAR, os.path.join(temp_dir, 'r8lib.jar'))
+ # Make a copy of r8.jar and r8lib.jar such that they stay the same for
+ # the entire execution of this script.
+ shutil.copyfile(utils.R8_JAR, os.path.join(temp_dir, 'r8.jar'))
+ shutil.copyfile(utils.R8LIB_JAR, os.path.join(temp_dir, 'r8lib.jar'))
result_per_shrinker_per_app = {}
@@ -664,7 +741,7 @@
result_per_shrinker_per_app[options.app] = GetResultsForApp(
options.app, APPS.get(options.app), options, temp_dir)
else:
- for app, config in sorted(APPS.iteritems()):
+ for app, config in sorted(APPS.iteritems(), key=lambda s: s[0].lower()):
if not config.get('skip', False):
result_per_shrinker_per_app[app] = GetResultsForApp(
app, config, options, temp_dir)
diff --git a/tools/toolhelper.py b/tools/toolhelper.py
index d7cf222..9371fb9 100644
--- a/tools/toolhelper.py
+++ b/tools/toolhelper.py
@@ -6,11 +6,12 @@
import gradle
import os
import subprocess
+from threading import Timer
import utils
def run(tool, args, build=None, debug=True,
profile=False, track_memory_file=None, extra_args=None,
- stderr=None, stdout=None, return_stdout=False):
+ stderr=None, stdout=None, return_stdout=False, timeout=0):
if build is None:
build, args = extract_build_from_args(args)
if build:
@@ -36,9 +37,20 @@
cmd.extend(["--lib", lib])
cmd.extend(args)
utils.PrintCmd(cmd)
- if return_stdout:
- return subprocess.check_output(cmd)
- return subprocess.call(cmd, stdout=stdout, stderr=stderr)
+ if timeout > 0:
+ kill = lambda process: process.kill()
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ timer = Timer(timeout, kill, [proc])
+ try:
+ timer.start()
+ stdout, stderr = proc.communicate()
+ finally:
+ timer.cancel()
+ return stdout if return_stdout else proc.returncode
+ else:
+ if return_stdout:
+ return subprocess.check_output(cmd)
+ return subprocess.call(cmd, stdout=stdout, stderr=stderr)
def run_in_tests(tool, args, build=None, debug=True, extra_args=None):
if build is None:
diff --git a/tools/utils.py b/tools/utils.py
index aff96a3..08faec6 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -62,6 +62,10 @@
CF_SEGMENTS_TOOL = os.path.join(THIRD_PARTY, 'cf_segments')
PINNED_R8_JAR = os.path.join(REPO_ROOT, 'third_party/r8/r8.jar')
PINNED_PGR8_JAR = os.path.join(REPO_ROOT, 'third_party/r8/r8-pg6.0.1.jar')
+OPENSOURCE_APPS_SHA_FILE = os.path.join(
+ REPO_ROOT,
+ 'third_party/opensource_apps.tar.gz.sha1')
+OPENSOURCE_APPS_FOLDER = os.path.join(REPO_ROOT, 'third_party/opensource_apps/')
# Common environment setup.
@@ -184,9 +188,12 @@
return 'origin/master' in remotes
def get_HEAD_sha1():
+ return get_HEAD_sha1_for_checkout(REPO_ROOT)
+
+def get_HEAD_sha1_for_checkout(checkout):
cmd = ['git', 'rev-parse', 'HEAD']
PrintCmd(cmd)
- with ChangedWorkingDirectory(REPO_ROOT):
+ with ChangedWorkingDirectory(checkout):
return subprocess.check_output(cmd).strip()
def makedirs_if_needed(path):