Merge commit '78b113b6d8f38324e853145b9e3b3d876212be02' into dev-release
diff --git a/.gitignore b/.gitignore
index c5f7f13..6e4c0e3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -132,6 +132,16 @@
third_party/opensource-apps/antennapod.tar.gz
third_party/opensource-apps/applymapping
third_party/opensource-apps/applymapping.tar.gz
+third_party/opensource-apps/chanu
+third_party/opensource-apps/chanu.tar.gz
+third_party/opensource-apps/friendlyeats
+third_party/opensource-apps/friendlyeats.tar.gz
+third_party/opensource-apps/iosched
+third_party/opensource-apps/iosched.tar.gz
+third_party/opensource-apps/sunflower
+third_party/opensource-apps/sunflower.tar.gz
+third_party/opensource-apps/wikipedia
+third_party/opensource-apps/wikipedia.tar.gz
third_party/opensource_apps
third_party/opensource_apps.tar.gz
third_party/proguard/*
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index a38b29e..b97eb7f 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -422,7 +422,7 @@
appViewWithLiveness, appViewWithLiveness.appInfo().computeSubtypingInfo())
.run();
- if (appView.options().protoShrinking().isProtoShrinkingEnabled()) {
+ if (appView.options().protoShrinking().isProtoEnumShrinkingEnabled()) {
appView.protoShrinker().enumProtoShrinker.clearDeadEnumLiteMaps();
}
@@ -571,12 +571,11 @@
}
if (options.enableHorizontalClassMerging && options.enableInlining) {
timing.begin("HorizontalClassMerger");
- HorizontalClassMerger merger =
- new HorizontalClassMerger(
- appViewWithLiveness, mainDexTracingResult, runtimeTypeCheckInfo);
+ HorizontalClassMerger merger = new HorizontalClassMerger(appViewWithLiveness);
DirectMappedDexApplication.Builder appBuilder =
appView.appInfo().app().asDirect().builder();
- HorizontalClassMergerGraphLens lens = merger.run(appBuilder);
+ HorizontalClassMergerGraphLens lens =
+ merger.run(appBuilder, mainDexTracingResult, runtimeTypeCheckInfo);
if (lens != null) {
DirectMappedDexApplication app = appBuilder.build();
appView.removePrunedClasses(app, appView.horizontallyMergedClasses().getSources());
@@ -783,7 +782,9 @@
}
if (appView.options().protoShrinking().isProtoShrinkingEnabled()) {
- appView.protoShrinker().enumProtoShrinker.verifyDeadEnumLiteMapsAreDead();
+ if (appView.options().protoShrinking().isProtoEnumShrinkingEnabled()) {
+ appView.protoShrinker().enumProtoShrinker.verifyDeadEnumLiteMapsAreDead();
+ }
IRConverter converter = new IRConverter(appView, timing, null, mainDexTracingResult);
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 4941d0b..24d3a36 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -674,6 +674,7 @@
code = codes.get(codeOff);
}
DexMethod method = indexedItems.getMethod(methodIndex);
+ accessFlags.setConstructor(method, dexItemFactory);
DexAnnotationSet methodAnnotations = annotationIterator.getNextFor(method);
String methodSignature = DexAnnotation.getSignature(methodAnnotations, dexItemFactory);
if (methodSignature != null) {
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
index c169b25..de254fc 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -291,4 +291,34 @@
}
return builder.toString();
}
+
+ abstract static class BuilderBase<B extends BuilderBase<B, F>, F extends AccessFlags<F>> {
+
+ protected F flags;
+
+ BuilderBase(F flags) {
+ this.flags = flags;
+ }
+
+ public B setPackagePrivate() {
+ assert flags.isPackagePrivate();
+ return self();
+ }
+
+ public B setStatic() {
+ flags.setStatic();
+ return self();
+ }
+
+ public B setSynthetic() {
+ flags.setSynthetic();
+ return self();
+ }
+
+ public F build() {
+ return flags;
+ }
+
+ public abstract B self();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index ace1d56..144b1de 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -669,19 +669,19 @@
List<DexType> interfaces,
MaximallySpecificMethodsBuilder builder) {
for (DexType iface : interfaces) {
- DexClass definiton = definitionFor(iface);
- if (definiton == null) {
+ DexClass definition = definitionFor(iface);
+ if (definition == null) {
// Ignore missing interface definitions.
continue;
}
- assert definiton.isInterface();
- DexEncodedMethod result = definiton.lookupMethod(method);
+ assert definition.isInterface();
+ DexEncodedMethod result = definition.lookupMethod(method);
if (isMaximallySpecificCandidate(result)) {
// The candidate is added and doing so will prohibit shadowed methods from being in the set.
- builder.addCandidate(definiton, result, this);
+ builder.addCandidate(definition, result, this);
} else {
// Look at the super-interfaces of this class and keep searching.
- resolveMethodStep3Helper(method, definiton, builder);
+ resolveMethodStep3Helper(method, definition, builder);
}
}
// Now look at indirect super interfaces.
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 37f45c6..ef9b742 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.graph.classmerging.MergedClassesCollection;
import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
+import com.android.tools.r8.ir.analysis.proto.EnumLiteProtoShrinker;
import com.android.tools.r8.ir.analysis.proto.GeneratedExtensionRegistryShrinker;
import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteShrinker;
@@ -315,6 +316,13 @@
return defaultValue;
}
+ public <U> U withProtoEnumShrinker(Function<EnumLiteProtoShrinker, U> fn, U defaultValue) {
+ if (protoShrinker != null && options().protoShrinking().isProtoEnumShrinkingEnabled()) {
+ return fn.apply(protoShrinker.enumProtoShrinker);
+ }
+ return defaultValue;
+ }
+
public <E extends Throwable> void withGeneratedExtensionRegistryShrinker(
ThrowingConsumer<GeneratedExtensionRegistryShrinker, E> consumer) throws E {
if (protoShrinker != null && protoShrinker.generatedExtensionRegistryShrinker != null) {
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 c49b888..45130dc 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.TraversalContinuation;
import com.google.common.base.MoreObjects;
@@ -179,6 +180,10 @@
return methodCollection.removeMethod(method);
}
+ public void setDirectMethods(List<DexEncodedMethod> methods) {
+ setDirectMethods(methods.toArray(DexEncodedMethod.EMPTY_ARRAY));
+ }
+
public void setDirectMethods(DexEncodedMethod[] methods) {
methodCollection.setDirectMethods(methods);
}
@@ -195,6 +200,10 @@
methodCollection.addVirtualMethods(methods);
}
+ public void setVirtualMethods(List<DexEncodedMethod> methods) {
+ setVirtualMethods(methods.toArray(DexEncodedMethod.EMPTY_ARRAY));
+ }
+
public void setVirtualMethods(DexEncodedMethod[] methods) {
methodCollection.setVirtualMethods(methods);
}
@@ -263,6 +272,10 @@
return Arrays.asList(staticFields);
}
+ public Iterable<DexEncodedField> staticFields(Predicate<DexEncodedField> predicate) {
+ return IterableUtils.filter(staticFields(), predicate);
+ }
+
public void appendStaticField(DexEncodedField field) {
DexEncodedField[] newFields = new DexEncodedField[staticFields.length + 1];
System.arraycopy(staticFields, 0, newFields, 0, staticFields.length);
@@ -498,6 +511,10 @@
return null;
}
+ public boolean canBeInstantiatedByNewInstance() {
+ return !isAbstract() && !isAnnotation() && !isInterface();
+ }
+
public boolean isAbstract() {
return accessFlags.isAbstract();
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index c10a345..159c27b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -95,6 +95,7 @@
optimizationInfo = info;
}
+ @Override
public KotlinFieldLevelInfo getKotlinMemberInfo() {
return kotlinMemberInfo;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
index 079afc1..dd69340 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import com.android.tools.r8.kotlin.KotlinMemberLevelInfo;
+
public abstract class DexEncodedMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
extends DexDefinition {
@@ -14,6 +16,8 @@
return getReference().holder;
}
+ public abstract KotlinMemberLevelInfo getKotlinMemberInfo();
+
@Override
public abstract R getReference();
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 c92307e..c353e8a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -381,6 +381,10 @@
return method;
}
+ public DexMethodSignature getSignature() {
+ return new DexMethodSignature(method);
+ }
+
public DexTypeList parameters() {
return method.proto.parameters;
}
@@ -595,6 +599,15 @@
return accessFlags.isSynthetic();
}
+ public boolean belongsToDirectPool() {
+ return accessFlags.isStatic() || accessFlags.isPrivate() || accessFlags.isConstructor();
+ }
+
+ public boolean belongsToVirtualPool() {
+ return !belongsToDirectPool();
+ }
+
+ @Override
public KotlinMethodLevelInfo getKotlinMemberInfo() {
return kotlinMemberInfo;
}
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 98128c4..6edd1e1 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -1780,7 +1780,7 @@
*
* @param tryString callback to check if the method name is in use.
*/
- public <T extends DexMember<?, ?>> T createFreshMember(
+ public <T> T createFreshMember(
Function<DexString, Optional<T>> tryString, String baseName, DexType holder) {
int index = 0;
while (true) {
@@ -1841,20 +1841,19 @@
DexProto proto,
DexType target,
Predicate<DexMethod> isFresh) {
- DexMethod method =
- createFreshMember(
- name -> {
- DexMethod tryMethod = createMethod(target, proto, name);
- if (isFresh.test(tryMethod)) {
- return Optional.of(tryMethod);
- } else {
- return Optional.empty();
- }
- },
- baseName,
- holder);
- return method;
+ return createFreshMember(
+ name -> {
+ DexMethod tryMethod = createMethod(target, proto, name);
+ if (isFresh.test(tryMethod)) {
+ return Optional.of(tryMethod);
+ } else {
+ return Optional.empty();
+ }
+ },
+ baseName,
+ holder);
}
+
/**
* Tries to find a method name for insertion into the class {@code target} of the form
* baseName$holder$n, where {@code baseName} and {@code holder} are supplied by the user, and
@@ -1863,14 +1862,49 @@
*
* @param holder indicates where the method originates from.
*/
- public DexField createFreshFieldName(
+ public DexMethodSignature createFreshMethodSignatureName(
+ String baseName, DexType holder, DexProto proto, Predicate<DexMethodSignature> isFresh) {
+ return createFreshMember(
+ name -> {
+ DexMethodSignature trySignature = new DexMethodSignature(proto, name);
+ if (isFresh.test(trySignature)) {
+ return Optional.of(trySignature);
+ } else {
+ return Optional.empty();
+ }
+ },
+ baseName,
+ holder);
+ }
+
+ /**
+ * Tries to find a method name for insertion into the class {@code target} of the form baseName$n,
+ * where {@code baseName} is supplied by the user, and {@code n} is picked to be the first number
+ * so that {@code isFresh.apply(method)} returns {@code true}.
+ */
+ public DexField createFreshFieldName(DexField template, Predicate<DexField> isFresh) {
+ return internalCreateFreshFieldName(template, null, isFresh);
+ }
+
+ /**
+ * Tries to find a method name for insertion into the class {@code target} of the form
+ * baseName$holder$n, where {@code baseName} and {@code holder} are supplied by the user, and
+ * {@code n} is picked to be the first number so that {@code isFresh.apply(method)} returns {@code
+ * true}.
+ *
+ * @param holder indicates where the method originates from.
+ */
+ public DexField createFreshFieldNameWithHolderSuffix(
DexField template, DexType holder, Predicate<DexField> isFresh) {
- DexField field =
- createFreshMember(
- name -> Optional.of(template.withName(name, this)).filter(isFresh),
- template.name.toSourceString(),
- holder);
- return field;
+ return internalCreateFreshFieldName(template, holder, isFresh);
+ }
+
+ private DexField internalCreateFreshFieldName(
+ DexField template, DexType holder, Predicate<DexField> isFresh) {
+ return createFreshMember(
+ name -> Optional.of(template.withName(name, this)).filter(isFresh),
+ template.name.toSourceString(),
+ holder);
}
public DexMethod createInstanceInitializerWithFreshProto(
@@ -2146,6 +2180,10 @@
return createMethod(clazz, proto, name);
}
+ public DexMethod createClinitMethod(DexType holder) {
+ return createMethod(holder, createProto(voidType), classConstructorMethodName);
+ }
+
public AdvanceLine createAdvanceLine(int delta) {
synchronized (advanceLines) {
return advanceLines.computeIfAbsent(delta, AdvanceLine::new);
diff --git a/src/main/java/com/android/tools/r8/graph/DexMember.java b/src/main/java/com/android/tools/r8/graph/DexMember.java
index 5c34e83..3041e01 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMember.java
@@ -27,6 +27,11 @@
public abstract boolean match(D entry);
@Override
+ public DexType getContextType() {
+ return holder;
+ }
+
+ @Override
public boolean isDexMember() {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index 74b0fff..6c288c7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -110,6 +110,10 @@
return proto.parameters.size();
}
+ public DexMethodSignature getSignature() {
+ return new DexMethodSignature(this);
+ }
+
@Override
public void collectIndexedItems(IndexedItemCollection indexedItems) {
if (collectIndexedItemsExceptName(indexedItems)) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethodSignature.java b/src/main/java/com/android/tools/r8/graph/DexMethodSignature.java
new file mode 100644
index 0000000..8b2fcc4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/DexMethodSignature.java
@@ -0,0 +1,92 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import java.util.Objects;
+
+public class DexMethodSignature {
+ private final DexProto proto;
+ private final DexString name;
+
+ public DexMethodSignature(DexMethod method) {
+ this(method.proto, method.name);
+ }
+
+ public DexMethodSignature(DexProto proto, DexString name) {
+ assert proto != null;
+ assert name != null;
+ this.proto = proto;
+ this.name = name;
+ }
+
+ public DexProto getProto() {
+ return proto;
+ }
+
+ public DexString getName() {
+ return name;
+ }
+
+ public DexMethodSignature withName(DexString name) {
+ return new DexMethodSignature(proto, name);
+ }
+
+ public DexMethodSignature withProto(DexProto proto) {
+ return new DexMethodSignature(proto, name);
+ }
+
+ public DexMethod withHolder(ProgramDefinition definition, DexItemFactory dexItemFactory) {
+ return withHolder(definition.getContextType(), dexItemFactory);
+ }
+
+ public DexMethod withHolder(DexReference reference, DexItemFactory dexItemFactory) {
+ return dexItemFactory.createMethod(reference.getContextType(), proto, name);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ DexMethodSignature that = (DexMethodSignature) o;
+ return proto == that.proto && name == that.name;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(proto, name);
+ }
+
+ public DexType getReturnType() {
+ return proto.returnType;
+ }
+
+ public int getArity() {
+ return proto.getArity();
+ }
+
+ @Override
+ public String toString() {
+ return "Method Signature " + name + " " + proto.toString();
+ }
+
+ private String toSourceString() {
+ return toSourceString(false);
+ }
+
+ private String toSourceString(boolean includeReturnType) {
+ StringBuilder builder = new StringBuilder();
+ if (includeReturnType) {
+ builder.append(getReturnType().toSourceString()).append(" ");
+ }
+ builder.append(name).append("(");
+ for (int i = 0; i < getArity(); i++) {
+ if (i != 0) {
+ builder.append(", ");
+ }
+ builder.append(proto.parameters.values[i].toSourceString());
+ }
+ return builder.append(")").toString();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DexProto.java b/src/main/java/com/android/tools/r8/graph/DexProto.java
index 1eefc66..333be20 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProto.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProto.java
@@ -45,6 +45,10 @@
return parameters.values[index];
}
+ public int getArity() {
+ return parameters.size();
+ }
+
@Override
public int computeHashCode() {
return shorty.hashCode()
diff --git a/src/main/java/com/android/tools/r8/graph/DexReference.java b/src/main/java/com/android/tools/r8/graph/DexReference.java
index c60efe5..3263b6d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexReference.java
+++ b/src/main/java/com/android/tools/r8/graph/DexReference.java
@@ -29,6 +29,8 @@
public abstract void collectIndexedItems(IndexedItemCollection indexedItems);
+ public abstract DexType getContextType();
+
public boolean isDexType() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index b1892bf..72e632d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -52,6 +52,11 @@
this.descriptor = descriptor;
}
+ @Override
+ public DexType getContextType() {
+ return this;
+ }
+
public DexString getDescriptor() {
return descriptor;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeList.java b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
index 87080e8..7d6ed51 100644
--- a/src/main/java/com/android/tools/r8/graph/DexTypeList.java
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
@@ -129,4 +129,14 @@
public Iterator<DexType> iterator() {
return Iterators.forArray(values);
}
+
+ public DexTypeList getSorted() {
+ if (values.length <= 1) {
+ return this;
+ }
+
+ DexType[] newValues = values.clone();
+ Arrays.sort(newValues, DexType::slowCompareTo);
+ return new DexTypeList(newValues);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
index bb39765..554df05 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
@@ -44,6 +44,10 @@
super(originalFlags, modifiedFlags);
}
+ public static Builder builder() {
+ return new Builder();
+ }
+
@Override
public FieldAccessFlags copy() {
return new FieldAccessFlags(originalFlags, modifiedFlags);
@@ -100,4 +104,16 @@
public void setEnum() {
set(Constants.ACC_ENUM);
}
+
+ public static class Builder extends BuilderBase<Builder, FieldAccessFlags> {
+
+ public Builder() {
+ super(FieldAccessFlags.fromSharedAccessFlags(0));
+ }
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index c788a1a..87cea9c 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -536,7 +536,15 @@
return result;
}
- public ImmutableSortedSet<DexMethod> rewriteMethods(Set<DexMethod> methods) {
+ public ImmutableSet<DexMethod> rewriteMethods(Set<DexMethod> methods) {
+ ImmutableSet.Builder<DexMethod> builder = ImmutableSet.builder();
+ for (DexMethod method : methods) {
+ builder.add(getRenamedMethodSignature(method));
+ }
+ return builder.build();
+ }
+
+ public ImmutableSortedSet<DexMethod> rewriteMethodsSorted(Set<DexMethod> methods) {
ImmutableSortedSet.Builder<DexMethod> builder =
new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompare);
for (DexMethod method : methods) {
diff --git a/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java b/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
index d54bc7b..bb0fbf8 100644
--- a/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
@@ -107,7 +107,7 @@
context = enclosingMethodAttribute.getEnclosingClass();
} else {
DexMethod enclosingMethod = enclosingMethodAttribute.getEnclosingMethod();
- if (!appView.appInfo().liveMethods.contains(enclosingMethod)) {
+ if (!appView.appInfo().isLiveMethod(enclosingMethod)) {
// EnclosingMethodAttribute will be pruned as it references the pruned method.
// Hence, the current InnerClassAttribute will be removed too. No live context.
return null;
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
index c29ebde..61e54e9 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
@@ -61,6 +61,10 @@
super(originalFlags, modifiedFlags);
}
+ public static Builder builder() {
+ return new Builder();
+ }
+
@Override
public MethodAccessFlags copy() {
return new MethodAccessFlags(originalFlags, modifiedFlags);
@@ -183,6 +187,12 @@
set(Constants.ACC_CONSTRUCTOR);
}
+ public void setConstructor(DexMethod method, DexItemFactory dexItemFactory) {
+ if (dexItemFactory.isConstructor(method) || dexItemFactory.isClassConstructor(method)) {
+ setConstructor();
+ }
+ }
+
public void unsetConstructor() {
unset(Constants.ACC_CONSTRUCTOR);
}
@@ -200,4 +210,21 @@
private void unsetDeclaredSynchronized() {
unset(Constants.ACC_DECLARED_SYNCHRONIZED);
}
+
+ public static class Builder extends BuilderBase<Builder, MethodAccessFlags> {
+
+ Builder() {
+ super(MethodAccessFlags.fromSharedAccessFlags(0, false));
+ }
+
+ public Builder setConstructor() {
+ flags.setConstructor();
+ return this;
+ }
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollection.java b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
index b14ff47..d483a59 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -292,6 +292,10 @@
backing.setDirectMethods(methods);
}
+ public void setSingleDirectMethod(DexEncodedMethod method) {
+ setDirectMethods(new DexEncodedMethod[] {method});
+ }
+
public void addVirtualMethods(Collection<DexEncodedMethod> methods) {
assert verifyCorrectnessOfMethodHolders(methods);
resetVirtualMethodCaches();
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
index 37a46f0..301352a 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
@@ -19,13 +19,11 @@
abstract boolean verify();
boolean belongsToDirectPool(DexEncodedMethod method) {
- return method.accessFlags.isStatic()
- || method.accessFlags.isPrivate()
- || method.accessFlags.isConstructor();
+ return method.belongsToDirectPool();
}
boolean belongsToVirtualPool(DexEncodedMethod method) {
- return !belongsToDirectPool(method);
+ return method.belongsToVirtualPool();
}
// Collection methods.
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
index e96d26c..6c5ff48 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
@@ -16,6 +16,8 @@
DexDefinition getDefinition();
+ DexReference getReference();
+
Origin getOrigin();
default boolean isProgramClass() {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index d441d2c..79913ff 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
@@ -48,6 +49,7 @@
private final HorizontallyMergedClasses.Builder mergedClassesBuilder;
private final FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder;
+ private final ClassMethodsBuilder classMethodsBuilder = new ClassMethodsBuilder();
private final Reference2IntMap<DexType> classIdentifiers = new Reference2IntOpenHashMap<>();
private final ClassStaticFieldsMerger classStaticFieldsMerger;
private final Collection<VirtualMethodMerger> virtualMethodMergers;
@@ -99,31 +101,33 @@
}
}
- void merge(DexProgramClass toMerge) {
- if (!toMerge.isFinal()) {
- target.getAccessFlags().demoteFromFinal();
- }
+ void mergeDirectMethods(SyntheticArgumentClass syntheticArgumentClass) {
+ mergeDirectMethods(target);
+ toMergeGroup.forEach(this::mergeDirectMethods);
+ mergeConstructors(syntheticArgumentClass);
+ }
+
+ void mergeDirectMethods(DexProgramClass toMerge) {
toMerge.forEachProgramDirectMethod(
method -> {
DexEncodedMethod definition = method.getDefinition();
assert !definition.isClassInitializer();
if (!definition.isInstanceInitializer()) {
- DexMethod newMethod = renameMethod(method);
- // TODO(b/165000217): Add all methods to `target` in one go using addDirectMethods().
- target.addDirectMethod(definition.toTypeSubstitutedMethod(newMethod));
- lensBuilder.moveMethod(definition.getReference(), newMethod);
+ DexMethod newMethod = method.getReference().withHolder(target.type, dexItemFactory);
+ if (!classMethodsBuilder.isFresh(newMethod)) {
+ newMethod = renameDirectMethod(method);
+ }
+ classMethodsBuilder.addDirectMethod(definition.toTypeSubstitutedMethod(newMethod));
+ if (definition.getReference() != newMethod) {
+ lensBuilder.moveMethod(definition.getReference(), newMethod);
+ }
}
});
- classStaticFieldsMerger.addFields(toMerge);
-
// Clear the members of the class to be merged since they have now been moved to the target.
- toMerge.setVirtualMethods(null);
- toMerge.setDirectMethods(null);
- toMerge.setInstanceFields(null);
- toMerge.setStaticFields(null);
+ toMerge.getMethodCollection().clearDirectMethods();
}
/**
@@ -131,26 +135,33 @@
*
* @param method The class the method originally belonged to.
*/
- DexMethod renameMethod(ProgramMethod method) {
+ DexMethod renameDirectMethod(ProgramMethod method) {
+ assert method.getDefinition().belongsToDirectPool();
return dexItemFactory.createFreshMethodName(
method.getDefinition().method.name.toSourceString(),
method.getHolderType(),
method.getDefinition().proto(),
target.type,
- tryMethod -> target.lookupMethod(tryMethod) == null);
+ classMethodsBuilder::isFresh);
}
void mergeConstructors(SyntheticArgumentClass syntheticArgumentClass) {
- for (ConstructorMerger merger : constructorMergers) {
- merger.merge(
- lensBuilder, fieldAccessChangesBuilder, classIdentifiers, syntheticArgumentClass);
- }
+ constructorMergers.forEach(
+ merger ->
+ merger.merge(
+ classMethodsBuilder,
+ lensBuilder,
+ fieldAccessChangesBuilder,
+ classIdentifiers,
+ syntheticArgumentClass));
}
void mergeVirtualMethods() {
- for (VirtualMethodMerger merger : virtualMethodMergers) {
- merger.merge(lensBuilder, fieldAccessChangesBuilder, classIdentifiers);
- }
+ virtualMethodMergers.forEach(
+ merger ->
+ merger.merge(
+ classMethodsBuilder, lensBuilder, fieldAccessChangesBuilder, classIdentifiers));
+ toMergeGroup.forEach(clazz -> clazz.getMethodCollection().clearVirtualMethods());
}
void appendClassIdField() {
@@ -166,20 +177,37 @@
}
void mergeStaticFields() {
+ toMergeGroup.forEach(classStaticFieldsMerger::addFields);
classStaticFieldsMerger.merge(target);
+ toMergeGroup.forEach(clazz -> clazz.setStaticFields(null));
+ }
+
+ void fixFinal() {
+ if (Iterables.any(toMergeGroup, Predicates.not(DexProgramClass::isFinal))) {
+ target.accessFlags.demoteFromFinal();
+ }
+ }
+
+ void mergeInstanceFields() {
+ // TODO: support instance field merging
+ assert Iterables.all(toMergeGroup, clazz -> !clazz.hasInstanceFields());
+
+ // The target should only have the class id field.
+ assert target.instanceFields().size() == 1;
}
public void mergeGroup(SyntheticArgumentClass syntheticArgumentClass) {
+ fixFinal();
appendClassIdField();
- mergedClassesBuilder.addMergeGroup(target, toMergeGroup);
- for (DexProgramClass clazz : toMergeGroup) {
- merge(clazz);
- }
-
- mergeConstructors(syntheticArgumentClass);
mergeVirtualMethods();
+ mergeDirectMethods(syntheticArgumentClass);
+ classMethodsBuilder.setClassMethods(target);
+
mergeStaticFields();
+ mergeInstanceFields();
+
+ mergedClassesBuilder.addMergeGroup(target, toMergeGroup);
}
public static class Builder {
@@ -252,7 +280,8 @@
List<VirtualMethodMerger> virtualMethodMergers =
new ArrayList<>(virtualMethodMergerBuilders.size());
for (VirtualMethodMerger.Builder builder : virtualMethodMergerBuilders.values()) {
- virtualMethodMergers.add(builder.build(appView, target, classIdField));
+ virtualMethodMergers.add(
+ builder.build(appView, target, classIdField, toMergeGroup.size() + 1));
}
// Try and merge the functions with the most arguments first, to avoid using synthetic
// arguments if possible.
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMethodsBuilder.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMethodsBuilder.java
new file mode 100644
index 0000000..2516cad
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMethodsBuilder.java
@@ -0,0 +1,44 @@
+// Copyright (c) 2020, 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.horizontalclassmerging;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+public class ClassMethodsBuilder {
+ private Set<DexMethod> reservedMethods = Sets.newIdentityHashSet();
+ private List<DexEncodedMethod> virtualMethods = new ArrayList<>();
+ private List<DexEncodedMethod> directMethods = new ArrayList<>();
+
+ public void addVirtualMethod(DexEncodedMethod virtualMethod) {
+ virtualMethods.add(virtualMethod);
+ boolean added = reservedMethods.add(virtualMethod.getReference());
+ assert added;
+ }
+
+ public void addDirectMethod(DexEncodedMethod directMethod) {
+ directMethods.add(directMethod);
+ boolean added = reservedMethods.add(directMethod.getReference());
+ assert added;
+ }
+
+ public boolean isFresh(DexMethod method) {
+ return !reservedMethods.contains(method);
+ }
+
+ public void setClassMethods(DexProgramClass clazz) {
+ assert virtualMethods.stream().allMatch(method -> method.holder() == clazz.type);
+ assert virtualMethods.stream().allMatch(method -> method.belongsToVirtualPool());
+ assert directMethods.stream().allMatch(method -> method.holder() == clazz.type);
+ assert directMethods.stream().allMatch(method -> method.belongsToDirectPool());
+ clazz.setVirtualMethods(virtualMethods);
+ clazz.setDirectMethods(directMethods);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassStaticFieldsMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassStaticFieldsMerger.java
index ce4b93e..d826b86 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassStaticFieldsMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassStaticFieldsMerger.java
@@ -42,7 +42,8 @@
DexField oldFieldReference = field.getReference();
DexField templateReference = field.getReference().withHolder(target.type, dexItemFactory);
DexField newFieldReference =
- dexItemFactory.createFreshFieldName(templateReference, field.holder(), this::isFresh);
+ dexItemFactory.createFreshFieldNameWithHolderSuffix(
+ templateReference, field.holder(), this::isFresh);
field = field.toTypeSubstitutedField(newFieldReference);
targetFields.put(newFieldReference, field);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
index f6127d9..d15ed7e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
@@ -115,18 +115,15 @@
return constructors.size() == 1;
}
- private DexMethod moveConstructor(DexEncodedMethod constructor) {
+ private DexMethod moveConstructor(
+ ClassMethodsBuilder classMethodsBuilder, DexEncodedMethod constructor) {
DexMethod method =
dexItemFactory.createFreshMethodName(
"constructor",
constructor.holder(),
constructor.proto(),
target.type,
- tryMethod -> target.lookupMethod(tryMethod) == null);
-
- if (constructor.holder() == target.type) {
- target.removeMethod(constructor.getReference());
- }
+ classMethodsBuilder::isFresh);
DexEncodedMethod encodedMethod = constructor.toTypeSubstitutedMethod(method);
encodedMethod.getMutableOptimizationInfo().markForceInline();
@@ -134,7 +131,8 @@
encodedMethod.accessFlags.unsetPublic();
encodedMethod.accessFlags.unsetProtected();
encodedMethod.accessFlags.setPrivate();
- target.addDirectMethod(encodedMethod);
+ classMethodsBuilder.addDirectMethod(encodedMethod);
+
return method;
}
@@ -146,6 +144,7 @@
/** Synthesize a new method which selects the constructor based on a parameter type. */
void merge(
+ ClassMethodsBuilder classMethodsBuilder,
HorizontalClassMergerGraphLens.Builder lensBuilder,
FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder,
Reference2IntMap<DexType> classIdentifiers,
@@ -159,7 +158,7 @@
classFileVersion =
CfVersion.maxAllowNull(classFileVersion, constructor.getClassFileVersion());
}
- DexMethod movedConstructor = moveConstructor(constructor);
+ DexMethod movedConstructor = moveConstructor(classMethodsBuilder, constructor);
lensBuilder.mapMethod(movedConstructor, movedConstructor);
lensBuilder.mapMethodInverse(constructor.method, movedConstructor);
typeConstructorClassMap.put(
@@ -169,9 +168,9 @@
DexMethod methodReferenceTemplate = generateReferenceMethodTemplate();
DexMethod newConstructorReference =
dexItemFactory.createInstanceInitializerWithFreshProto(
- methodReferenceTemplate,
+ methodReferenceTemplate.withHolder(target.type, dexItemFactory),
syntheticArgumentClass.getArgumentClass(),
- tryMethod -> target.lookupMethod(tryMethod) == null);
+ classMethodsBuilder::isFresh);
int extraNulls = newConstructorReference.getArity() - methodReferenceTemplate.getArity();
DexMethod representativeConstructorReference = constructors.iterator().next().method;
@@ -221,7 +220,7 @@
lensBuilder.recordExtraOriginalSignature(
representativeConstructorReference, newConstructorReference);
- target.addDirectMethod(newConstructor);
+ classMethodsBuilder.addDirectMethod(newConstructor);
fieldAccessChangesBuilder.fieldWrittenByMethod(classIdField, newConstructorReference);
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index a442450..2200267 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.horizontalclassmerging.policies.AllInstantiatedOrUninstantiated;
+import com.android.tools.r8.horizontalclassmerging.policies.ClassesHaveSameInterfaces;
import com.android.tools.r8.horizontalclassmerging.policies.DontInlinePolicy;
import com.android.tools.r8.horizontalclassmerging.policies.DontMergeIntoLessVisible;
import com.android.tools.r8.horizontalclassmerging.policies.DontMergeSynchronizedClasses;
@@ -15,12 +16,12 @@
import com.android.tools.r8.horizontalclassmerging.policies.NoAbstractClasses;
import com.android.tools.r8.horizontalclassmerging.policies.NoAnnotations;
import com.android.tools.r8.horizontalclassmerging.policies.NoClassesOrMembersWithAnnotations;
-import com.android.tools.r8.horizontalclassmerging.policies.NoClassesWithInterfaces;
import com.android.tools.r8.horizontalclassmerging.policies.NoEnums;
import com.android.tools.r8.horizontalclassmerging.policies.NoInnerClasses;
import com.android.tools.r8.horizontalclassmerging.policies.NoInstanceFields;
import com.android.tools.r8.horizontalclassmerging.policies.NoInterfaces;
import com.android.tools.r8.horizontalclassmerging.policies.NoKeepRules;
+import com.android.tools.r8.horizontalclassmerging.policies.NoKotlinMetadata;
import com.android.tools.r8.horizontalclassmerging.policies.NoNativeMethods;
import com.android.tools.r8.horizontalclassmerging.policies.NoRuntimeTypeChecks;
import com.android.tools.r8.horizontalclassmerging.policies.NoServiceLoaders;
@@ -30,6 +31,7 @@
import com.android.tools.r8.horizontalclassmerging.policies.NotVerticallyMergedIntoSubtype;
import com.android.tools.r8.horizontalclassmerging.policies.PreventChangingVisibility;
import com.android.tools.r8.horizontalclassmerging.policies.PreventMergeIntoMainDex;
+import com.android.tools.r8.horizontalclassmerging.policies.PreventMethodImplementation;
import com.android.tools.r8.horizontalclassmerging.policies.RespectPackageBoundaries;
import com.android.tools.r8.horizontalclassmerging.policies.SameFeatureSplit;
import com.android.tools.r8.horizontalclassmerging.policies.SameNestHost;
@@ -48,64 +50,28 @@
public class HorizontalClassMerger {
private final AppView<AppInfoWithLiveness> appView;
- private final PolicyExecutor policyExecutor;
- public HorizontalClassMerger(
- AppView<AppInfoWithLiveness> appView,
- MainDexTracingResult mainDexTracingResult,
- RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
+ public HorizontalClassMerger(AppView<AppInfoWithLiveness> appView) {
this.appView = appView;
assert appView.options().enableInlining;
-
- List<Policy> policies =
- ImmutableList.of(
- new NotMatchedByNoHorizontalClassMerging(appView),
- new NoInstanceFields(),
- // TODO(b/166071504): Allow merging of classes that implement interfaces.
- new NoInterfaces(),
- new NoClassesWithInterfaces(),
- new NoAnnotations(),
- new NoEnums(appView),
- new NoAbstractClasses(),
- new IgnoreSynthetics(appView),
- new NoClassesOrMembersWithAnnotations(),
- new NoInnerClasses(),
- new NoStaticClassInitializer(),
- new NoNativeMethods(),
- new NoKeepRules(appView),
- new NoServiceLoaders(appView),
- new NotVerticallyMergedIntoSubtype(appView),
- new NoRuntimeTypeChecks(runtimeTypeCheckInfo),
- new NotEntryPoint(appView.dexItemFactory()),
- new DontInlinePolicy(appView, mainDexTracingResult),
- new PreventMergeIntoMainDex(appView, mainDexTracingResult),
- new AllInstantiatedOrUninstantiated(appView),
- new SameParentClass(),
- new SameNestHost(),
- new PreventChangingVisibility(),
- new SameFeatureSplit(appView),
- new RespectPackageBoundaries(appView),
- new DontMergeSynchronizedClasses(appView),
- // TODO(b/166577694): no policies should be run after this policy, as it would
- // potentially break tests
- new DontMergeIntoLessVisible()
- // TODO: add policies
- );
-
- this.policyExecutor = new SimplePolicyExecutor(policies);
}
// TODO(b/165577835): replace Collection<DexProgramClass> with MergeGroup
- public HorizontalClassMergerGraphLens run(DirectMappedDexApplication.Builder appBuilder) {
+ public HorizontalClassMergerGraphLens run(
+ DirectMappedDexApplication.Builder appBuilder,
+ MainDexTracingResult mainDexTracingResult,
+ RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
Map<FieldMultiset, List<DexProgramClass>> classes = new LinkedHashMap<>();
// Group classes by same field signature using the hash map.
- for (DexProgramClass clazz : appView.appInfo().app().classesWithDeterministicOrder()) {
+ for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
classes.computeIfAbsent(new FieldMultiset(clazz), ignore -> new ArrayList<>()).add(clazz);
}
// Run the policies on all collected classes to produce a final grouping.
- Collection<List<DexProgramClass>> groups = policyExecutor.run(classes.values());
+ Collection<List<DexProgramClass>> groups =
+ new SimplePolicyExecutor()
+ .run(classes.values(), getPolicies(mainDexTracingResult, runtimeTypeCheckInfo));
// If there are no groups, then end horizontal class merging.
if (groups.isEmpty()) {
appView.setHorizontallyMergedClasses(HorizontallyMergedClasses.empty());
@@ -138,6 +104,45 @@
mergedClasses, lensBuilder, fieldAccessChangesBuilder, syntheticArgumentClass);
}
+ private List<Policy> getPolicies(
+ MainDexTracingResult mainDexTracingResult,
+ RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
+ return ImmutableList.of(
+ new NotMatchedByNoHorizontalClassMerging(appView),
+ new NoInstanceFields(),
+ new NoInterfaces(),
+ new ClassesHaveSameInterfaces(),
+ new NoAnnotations(),
+ new NoEnums(appView),
+ new NoAbstractClasses(),
+ new IgnoreSynthetics(appView),
+ new NoClassesOrMembersWithAnnotations(),
+ new NoInnerClasses(),
+ new NoStaticClassInitializer(),
+ new NoNativeMethods(),
+ new NoKeepRules(appView),
+ new NoKotlinMetadata(),
+ new NoServiceLoaders(appView),
+ new NotVerticallyMergedIntoSubtype(appView),
+ new NoRuntimeTypeChecks(runtimeTypeCheckInfo),
+ new NotEntryPoint(appView.dexItemFactory()),
+ new PreventMethodImplementation(appView),
+ new DontInlinePolicy(appView, mainDexTracingResult),
+ new PreventMergeIntoMainDex(appView, mainDexTracingResult),
+ new AllInstantiatedOrUninstantiated(appView),
+ new SameParentClass(),
+ new SameNestHost(),
+ new PreventChangingVisibility(),
+ new SameFeatureSplit(appView),
+ new RespectPackageBoundaries(appView),
+ new DontMergeSynchronizedClasses(appView),
+ // TODO(b/166577694): no policies should be run after this policy, as it would
+ // potentially break tests
+ new DontMergeIntoLessVisible()
+ // TODO: add policies
+ );
+ }
+
/**
* Prepare horizontal class merging by determining which virtual methods and constructors need to
* be merged and how the merging should be performed.
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java
index 2c8f10a..eb5d4d0 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java
@@ -19,9 +19,11 @@
/**
* Remove all groups containing no or only a single class, as there is no point in merging these.
*/
- protected void removeTrivialGroups(Collection<List<DexProgramClass>> groups) {
+ protected Collection<List<DexProgramClass>> removeTrivialGroups(
+ Collection<List<DexProgramClass>> groups) {
assert !(groups instanceof ArrayList);
groups.removeIf(this::isTrivial);
+ return groups;
}
/**
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java
index cddca41..a5f58d2 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java
@@ -9,16 +9,12 @@
import java.util.List;
public abstract class PolicyExecutor {
- protected final Collection<Policy> policies;
-
- public PolicyExecutor(Collection<Policy> policies) {
- this.policies = policies;
- }
/**
* Given an initial collection of class groups which can potentially be merged, run all of the
* policies registered to this policy executor on the class groups yielding a new collection of
* class groups.
*/
- public abstract Collection<List<DexProgramClass>> run(Collection<List<DexProgramClass>> classes);
+ public abstract Collection<List<DexProgramClass>> run(
+ Collection<List<DexProgramClass>> classes, Collection<Policy> policies);
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java
index a5f26d8..1a3a965 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java
@@ -17,9 +17,6 @@
* against which more efficient policy executors can be compared.
*/
public class SimplePolicyExecutor extends PolicyExecutor {
- public SimplePolicyExecutor(Collection<Policy> policies) {
- super(policies);
- }
// TODO(b/165506334): if performing mutable operation ensure that linked lists are used
private LinkedList<List<DexProgramClass>> applySingleClassPolicy(
@@ -46,7 +43,8 @@
}
@Override
- public Collection<List<DexProgramClass>> run(Collection<List<DexProgramClass>> inputGroups) {
+ public Collection<List<DexProgramClass>> run(
+ Collection<List<DexProgramClass>> inputGroups, Collection<Policy> policies) {
LinkedList<List<DexProgramClass>> linkedGroups;
if (inputGroups instanceof LinkedList) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/SubtypingForrestForClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/SubtypingForrestForClasses.java
index b643225..05440b4 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/SubtypingForrestForClasses.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SubtypingForrestForClasses.java
@@ -35,10 +35,9 @@
private final Collection<DexProgramClass> roots = new ArrayList<>();
private final Map<DexProgramClass, List<DexProgramClass>> subtypeMap = new IdentityHashMap<>();
- public SubtypingForrestForClasses(
- AppView<AppInfoWithLiveness> appView, List<DexProgramClass> classesWithDeterministicOrder) {
+ public SubtypingForrestForClasses(AppView<AppInfoWithLiveness> appView) {
this.appView = appView;
- calculateSubtyping(classesWithDeterministicOrder);
+ calculateSubtyping(appView.appInfo().classes());
}
private DexProgramClass superClass(DexProgramClass clazz) {
@@ -65,13 +64,14 @@
return roots;
}
- private Collection<DexProgramClass> getSubtypesFor(DexProgramClass clazz) {
+ public Collection<DexProgramClass> getSubtypesFor(DexProgramClass clazz) {
return subtypeMap.getOrDefault(clazz, Collections.emptyList());
}
- public <T> void traverseNodeDepthFirst(
+ public <T> T traverseNodeDepthFirst(
DexProgramClass clazz, T state, BiFunction<DexProgramClass, T, T> consumer) {
T newState = consumer.apply(clazz, state);
getSubtypesFor(clazz).forEach(subClazz -> traverseNodeDepthFirst(subClazz, newState, consumer));
+ return newState;
}
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
index 8cf6265..a7ce343 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodSignature;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexString;
@@ -19,15 +20,12 @@
import com.android.tools.r8.shaking.AnnotationFixer;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
-import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.android.tools.r8.utils.OptionalBool;
-import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.util.Collections;
-import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.LinkedHashSet;
import java.util.List;
@@ -49,7 +47,7 @@
private final DexItemFactory dexItemFactory;
private final BiMap<DexMethod, DexMethod> movedMethods = HashBiMap.create();
private final SyntheticArgumentClass syntheticArgumentClass;
- private final BiMap<Wrapper<DexMethod>, Wrapper<DexMethod>> reservedInterfaceSignatures =
+ private final BiMap<DexMethodSignature, DexMethodSignature> reservedInterfaceSignatures =
HashBiMap.create();
public TreeFixer(
@@ -124,11 +122,10 @@
Iterables.filter(classes, DexProgramClass::isInterface).forEach(this::fixupInterfaceClass);
classes.forEach(this::fixupProgramClassSuperType);
- SubtypingForrestForClasses subtypingForrest = new SubtypingForrestForClasses(appView, classes);
+ SubtypingForrestForClasses subtypingForrest = new SubtypingForrestForClasses(appView);
// TODO(b/170078037): parallelize this code segment.
for (DexProgramClass root : subtypingForrest.getProgramRoots()) {
- subtypingForrest.traverseNodeDepthFirst(
- root, new IdentityHashMap<>(), this::fixupProgramClass);
+ subtypingForrest.traverseNodeDepthFirst(root, HashBiMap.create(), this::fixupProgramClass);
}
lensBuilder.remapMethods(movedMethods);
@@ -143,28 +140,24 @@
clazz.superType = fixupType(clazz.superType);
}
- private Map<Wrapper<DexMethod>, DexString> fixupProgramClass(
- DexProgramClass clazz, Map<Wrapper<DexMethod>, DexString> remappedVirtualMethods) {
+ private BiMap<DexMethodSignature, DexMethodSignature> fixupProgramClass(
+ DexProgramClass clazz, BiMap<DexMethodSignature, DexMethodSignature> remappedVirtualMethods) {
assert !clazz.isInterface();
// TODO(b/169395592): ensure merged classes have been removed using:
// assert !mergedClasses.hasBeenMergedIntoDifferentType(clazz.type);
- Map<Wrapper<DexMethod>, DexString> remappedClassVirtualMethods =
- new HashMap<>(remappedVirtualMethods);
+ BiMap<DexMethodSignature, DexMethodSignature> remappedClassVirtualMethods =
+ HashBiMap.create(remappedVirtualMethods);
- Set<DexMethod> newVirtualMethodReferences = Sets.newIdentityHashSet();
+ Set<DexMethodSignature> newMethodReferences = Sets.newHashSet();
clazz
.getMethodCollection()
.replaceAllVirtualMethods(
- method ->
- fixupVirtualMethod(
- remappedClassVirtualMethods, newVirtualMethodReferences, method));
-
- Set<DexMethod> newDirectMethodReferences = Sets.newIdentityHashSet();
+ method -> fixupVirtualMethod(remappedClassVirtualMethods, newMethodReferences, method));
clazz
.getMethodCollection()
- .replaceAllDirectMethods(method -> fixupDirectMethod(newDirectMethodReferences, method));
+ .replaceAllDirectMethods(method -> fixupDirectMethod(newMethodReferences, method));
fixupFields(clazz.staticFields(), clazz::setStaticField);
fixupFields(clazz.instanceFields(), clazz::setInstanceField);
@@ -184,23 +177,19 @@
return method;
}
- MethodSignatureEquivalence equivalence = MethodSignatureEquivalence.get();
- Wrapper<DexMethod> originalMethodSignature = equivalence.wrap(originalMethodReference);
- Wrapper<DexMethod> newMethodSignature =
+ DexMethodSignature originalMethodSignature = originalMethodReference.getSignature();
+ DexMethodSignature newMethodSignature =
reservedInterfaceSignatures.get(originalMethodSignature);
if (newMethodSignature == null) {
- newMethodSignature = equivalence.wrap(fixupMethodReference(originalMethodReference));
+ newMethodSignature = fixupMethodReference(originalMethodReference).getSignature();
// If the signature is already reserved by another interface, find a fresh one.
if (reservedInterfaceSignatures.containsValue(newMethodSignature)) {
DexString name =
dexItemFactory.createGloballyFreshMemberString(
originalMethodReference.getName().toSourceString());
- newMethodSignature =
- equivalence.wrap(
- dexItemFactory.createMethod(
- newMethodSignature.get().holder, newMethodSignature.get().proto, name));
+ newMethodSignature = newMethodSignature.withName(name);
}
assert !reservedInterfaceSignatures.containsValue(newMethodSignature);
@@ -208,16 +197,14 @@
}
DexMethod newMethodReference =
- newMethodSignature
- .get()
- .withHolder(originalMethodReference.getHolderType(), dexItemFactory);
+ newMethodSignature.withHolder(originalMethodReference, dexItemFactory);
movedMethods.put(originalMethodReference, newMethodReference);
return method.toTypeSubstitutedMethod(newMethodReference);
}
private void fixupInterfaceClass(DexProgramClass iface) {
- Set<DexMethod> newDirectMethods = new LinkedHashSet<>();
+ Set<DexMethodSignature> newDirectMethods = new LinkedHashSet<>();
assert iface.superType == dexItemFactory.objectType;
iface.superType = mergedClasses.getMergeTargetOrDefault(iface.superType);
@@ -251,13 +238,14 @@
return newMethod;
}
- private DexEncodedMethod fixupDirectMethod(Set<DexMethod> newMethods, DexEncodedMethod method) {
+ private DexEncodedMethod fixupDirectMethod(
+ Set<DexMethodSignature> newMethods, DexEncodedMethod method) {
DexMethod originalMethodReference = method.getReference();
// Fix all type references in the method prototype.
DexMethod newMethodReference = fixupMethodReference(originalMethodReference);
- if (newMethods.contains(newMethodReference)) {
+ if (newMethods.contains(newMethodReference.getSignature())) {
// If the method collides with a direct method on the same class then rename it to a globally
// fresh name and record the signature.
@@ -267,45 +255,44 @@
dexItemFactory.createInstanceInitializerWithFreshProto(
newMethodReference,
syntheticArgumentClass.getArgumentClass(),
- tryMethod -> !newMethods.contains(tryMethod));
+ tryMethod -> !newMethods.contains(tryMethod.getSignature()));
int extraNulls = newMethodReference.getArity() - originalMethodReference.getArity();
lensBuilder.addExtraParameters(
originalMethodReference,
Collections.nCopies(extraNulls, new ExtraUnusedNullParameter()));
} else {
- DexString newMethodName =
- dexItemFactory.createGloballyFreshMemberString(
- originalMethodReference.getName().toSourceString(), null);
newMethodReference =
- dexItemFactory.createMethod(
- newMethodReference.holder, newMethodReference.proto, newMethodName);
+ dexItemFactory.createFreshMethodName(
+ newMethodReference.getName().toSourceString(),
+ null,
+ newMethodReference.proto,
+ newMethodReference.holder,
+ tryMethod ->
+ !reservedInterfaceSignatures.containsValue(tryMethod.getSignature())
+ && !newMethods.contains(tryMethod.getSignature()));
}
}
- boolean changed = newMethods.add(newMethodReference);
+ boolean changed = newMethods.add(newMethodReference.getSignature());
assert changed;
return fixupProgramMethod(newMethodReference, method);
}
- private DexString lookupReservedVirtualName(
+ private DexMethodSignature lookupReservedVirtualName(
DexMethod originalMethodReference,
- Map<Wrapper<DexMethod>, DexString> renamedClassVirtualMethods) {
- Wrapper<DexMethod> originalSignature =
- MethodSignatureEquivalence.get().wrap(originalMethodReference);
+ BiMap<DexMethodSignature, DexMethodSignature> renamedClassVirtualMethods) {
+ DexMethodSignature originalSignature = originalMethodReference.getSignature();
// Determine if the original method has been rewritten by a parent class
- DexString renamedVirtualName =
- renamedClassVirtualMethods != null
- ? renamedClassVirtualMethods.get(originalSignature)
- : null;
+ DexMethodSignature renamedVirtualName = renamedClassVirtualMethods.get(originalSignature);
if (renamedVirtualName == null) {
// Determine if there is a signature mapping.
- Wrapper<DexMethod> mappedInterfaceSignature =
+ DexMethodSignature mappedInterfaceSignature =
reservedInterfaceSignatures.get(originalSignature);
if (mappedInterfaceSignature != null) {
- renamedVirtualName = mappedInterfaceSignature.get().name;
+ renamedVirtualName = mappedInterfaceSignature;
}
} else {
assert !reservedInterfaceSignatures.containsKey(originalSignature);
@@ -315,51 +302,57 @@
}
private DexEncodedMethod fixupVirtualMethod(
- Map<Wrapper<DexMethod>, DexString> renamedClassVirtualMethods,
- Set<DexMethod> newMethods,
+ BiMap<DexMethodSignature, DexMethodSignature> renamedClassVirtualMethods,
+ Set<DexMethodSignature> newMethods,
DexEncodedMethod method) {
DexMethod originalMethodReference = method.getReference();
- Wrapper<DexMethod> originalSignature =
- MethodSignatureEquivalence.get().wrap(originalMethodReference);
+ DexMethodSignature originalSignature = originalMethodReference.getSignature();
- DexString renamedVirtualName =
+ DexMethodSignature renamedVirtualName =
lookupReservedVirtualName(originalMethodReference, renamedClassVirtualMethods);
// Fix all type references in the method prototype.
- DexMethod newMethodReference = fixupMethodReference(originalMethodReference);
- Wrapper<DexMethod> newSignature = MethodSignatureEquivalence.get().wrap(newMethodReference);
+ DexMethodSignature newSignature = fixupMethodSignature(originalSignature);
if (renamedVirtualName != null) {
// If the method was renamed in a parent, rename it in the child.
- newMethodReference = newMethodReference.withName(renamedVirtualName, dexItemFactory);
+ newSignature = renamedVirtualName;
- assert !newMethods.contains(newMethodReference);
+ assert !newMethods.contains(newSignature);
} else if (reservedInterfaceSignatures.containsValue(newSignature)
- || newMethods.contains(newMethodReference)) {
+ || newMethods.contains(newSignature)
+ || renamedClassVirtualMethods.containsValue(newSignature)) {
// If the method potentially collides with an interface method or with another virtual method
// rename it to a globally fresh name and record the name.
- DexString newMethodName =
- dexItemFactory.createGloballyFreshMemberString(
- originalMethodReference.getName().toSourceString(), null);
- newMethodReference = newMethodReference.withName(newMethodName, dexItemFactory);
+ newSignature =
+ dexItemFactory.createFreshMethodSignatureName(
+ originalMethodReference.getName().toSourceString(),
+ null,
+ newSignature.getProto(),
+ trySignature ->
+ !reservedInterfaceSignatures.containsValue(trySignature)
+ && !newMethods.contains(trySignature)
+ && !renamedClassVirtualMethods.containsValue(trySignature));
// Record signature renaming so that subclasses perform the identical rename.
- renamedClassVirtualMethods.put(originalSignature, newMethodReference.getName());
+ renamedClassVirtualMethods.put(originalSignature, newSignature);
} else {
// There was no reserved name and the new signature is available.
if (Iterables.any(
- newMethodReference.proto.getParameterBaseTypes(dexItemFactory),
+ newSignature.getProto().getParameterBaseTypes(dexItemFactory),
mergedClasses::isMergeTarget)) {
// If any of the parameter types have been merged, record the signature mapping.
- renamedClassVirtualMethods.put(originalSignature, newMethodReference.getName());
+ renamedClassVirtualMethods.put(originalSignature, newSignature);
}
}
- boolean changed = newMethods.add(newMethodReference);
+ boolean changed = newMethods.add(newSignature);
assert changed;
+ DexMethod newMethodReference =
+ newSignature.withHolder(fixupType(originalMethodReference.holder), dexItemFactory);
return fixupProgramMethod(newMethodReference, method);
}
@@ -406,6 +399,10 @@
.createMethod(fixupType(method.holder), fixupProto(method.proto), method.name);
}
+ private DexMethodSignature fixupMethodSignature(DexMethodSignature signature) {
+ return signature.withProto(fixupProto(signature.getProto()));
+ }
+
private DexProto fixupProto(DexProto proto) {
DexProto result = protoFixupCache.get(proto);
if (result == null) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
index 14488b6..07a3172 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
@@ -59,18 +59,15 @@
/** Get the super method handle if this method overrides a parent method. */
private DexMethod superMethod(AppView<AppInfoWithLiveness> appView, DexProgramClass target) {
- // TODO(b/167981556): Correctly detect super methods defined on interfaces.
DexMethod template = methods.iterator().next().getReference();
SingleResolutionResult resolutionResult =
appView
- .withLiveness()
.appInfo()
.resolveMethodOnClass(template, target.getSuperType())
.asSingleResolution();
- if (resolutionResult == null) {
- return null;
- }
- if (resolutionResult.getResolvedMethod().isAbstract()) {
+
+ if (resolutionResult == null || resolutionResult.getResolvedMethod().isAbstract()) {
+ // If there is no super method or the method is abstract it should not be called.
return null;
}
if (resolutionResult.getResolvedHolder().isInterface()) {
@@ -84,17 +81,26 @@
}
public VirtualMethodMerger build(
- AppView<AppInfoWithLiveness> appView, DexProgramClass target, DexField classIdField) {
- DexMethod superMethod = superMethod(appView, target);
+ AppView<AppInfoWithLiveness> appView,
+ DexProgramClass target,
+ DexField classIdField,
+ int mergeGroupSize) {
+ // If not all the classes are in the merge group, find the fallback super method to call.
+ DexMethod superMethod = methods.size() < mergeGroupSize ? superMethod(appView, target) : null;
+
return new VirtualMethodMerger(appView, target, methods, classIdField, superMethod);
}
}
- public int getArity() {
- return methods.iterator().next().getReference().getArity();
+ public DexMethod getMethodReference() {
+ return methods.iterator().next().getReference();
}
- private DexMethod moveMethod(ProgramMethod oldMethod) {
+ public int getArity() {
+ return getMethodReference().getArity();
+ }
+
+ private DexMethod moveMethod(ClassMethodsBuilder classMethodsBuilder, ProgramMethod oldMethod) {
DexMethod oldMethodReference = oldMethod.getReference();
DexMethod method =
dexItemFactory.createFreshMethodName(
@@ -102,18 +108,14 @@
oldMethod.getHolderType(),
oldMethodReference.proto,
target.type,
- tryMethod -> target.lookupMethod(tryMethod) == null);
-
- if (oldMethod.getHolderType() == target.type) {
- target.removeMethod(oldMethod.getReference());
- }
+ classMethodsBuilder::isFresh);
DexEncodedMethod encodedMethod = oldMethod.getDefinition().toTypeSubstitutedMethod(method);
MethodAccessFlags flags = encodedMethod.accessFlags;
flags.unsetProtected();
flags.unsetPublic();
flags.setPrivate();
- target.addDirectMethod(encodedMethod);
+ classMethodsBuilder.addDirectMethod(encodedMethod);
return encodedMethod.method;
}
@@ -127,24 +129,26 @@
return flags;
}
-
/**
* If there is only a single method that does not override anything then it is safe to just move
* it to the target type if it is not already in it.
*/
- public void mergeTrivial(HorizontalClassMergerGraphLens.Builder lensBuilder) {
- ProgramMethod method = methods.iterator().next();
+ public void mergeTrivial(
+ ClassMethodsBuilder classMethodsBuilder, HorizontalClassMergerGraphLens.Builder lensBuilder) {
+ DexEncodedMethod method = methods.iterator().next().getDefinition();
if (method.getHolderType() != target.type) {
// If the method is not in the target type, move it and record it in the lens.
- DexEncodedMethod newMethod =
- method.getDefinition().toRenamedHolderMethod(target.type, dexItemFactory);
- target.addVirtualMethod(newMethod);
- lensBuilder.moveMethod(method.getReference(), newMethod.getReference());
+ DexMethod originalReference = method.getReference();
+ method = method.toRenamedHolderMethod(target.type, dexItemFactory);
+ lensBuilder.moveMethod(originalReference, method.getReference());
}
+
+ classMethodsBuilder.addVirtualMethod(method);
}
public void merge(
+ ClassMethodsBuilder classMethodsBuilder,
HorizontalClassMergerGraphLens.Builder lensBuilder,
FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder,
Reference2IntMap classIdentifiers) {
@@ -153,7 +157,7 @@
// Handle trivial merges.
if (superMethod == null && methods.size() == 1) {
- mergeTrivial(lensBuilder);
+ mergeTrivial(classMethodsBuilder, lensBuilder);
return;
}
@@ -165,7 +169,7 @@
CfVersion methodVersion = method.getDefinition().getClassFileVersion();
classFileVersion = CfVersion.maxAllowNull(classFileVersion, methodVersion);
}
- DexMethod newMethod = moveMethod(method);
+ DexMethod newMethod = moveMethod(classMethodsBuilder, method);
lensBuilder.mapMethod(newMethod, newMethod);
lensBuilder.mapMethodInverse(method.getReference(), newMethod);
classIdToMethodMap.put(classIdentifiers.getInt(method.getHolderType()), newMethod);
@@ -181,7 +185,7 @@
null,
originalMethodReference.proto,
originalMethodReference.getHolderType(),
- tryMethod -> target.lookupMethod(tryMethod) == null);
+ classMethodsBuilder::isFresh);
DexMethod newMethodReference =
dexItemFactory.createMethod(target.type, templateReference.proto, templateReference.name);
@@ -209,7 +213,7 @@
}
lensBuilder.recordExtraOriginalSignature(bridgeMethodReference, newMethodReference);
- target.addVirtualMethod(newMethod);
+ classMethodsBuilder.addVirtualMethod(newMethod);
fieldAccessChangesBuilder.fieldReadByMethod(classIdField, newMethod.method);
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/ClassesHaveSameInterfaces.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/ClassesHaveSameInterfaces.java
new file mode 100644
index 0000000..060e1a6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/ClassesHaveSameInterfaces.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2020, 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.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
+
+public class ClassesHaveSameInterfaces extends MultiClassSameReferencePolicy<DexTypeList> {
+
+ @Override
+ public DexTypeList getMergeKey(DexProgramClass clazz) {
+ return clazz.interfaces.getSorted();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassesWithInterfaces.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassesWithInterfaces.java
deleted file mode 100644
index 56e6756..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassesWithInterfaces.java
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright (c) 2020, 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.horizontalclassmerging.policies;
-
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
-
-public class NoClassesWithInterfaces extends SingleClassPolicy {
-
- @Override
- public boolean canMerge(DexProgramClass program) {
- return program.interfaces.isEmpty();
- }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKotlinMetadata.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKotlinMetadata.java
new file mode 100644
index 0000000..92f2938
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKotlinMetadata.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2020, 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.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+import com.google.common.collect.Streams;
+
+public class NoKotlinMetadata extends SingleClassPolicy {
+
+ public NoKotlinMetadata() {}
+
+ @Override
+ public boolean canMerge(DexProgramClass clazz) {
+ assert verifyNoUnexpectedKotlinInfo(clazz);
+ return true;
+ }
+
+ private boolean verifyNoUnexpectedKotlinInfo(DexProgramClass clazz) {
+ if (clazz.getKotlinInfo().isNoKotlinInformation()) {
+ assert verifyNoUnexpectedKotlinMemberInfo(clazz);
+ return true;
+ }
+ assert clazz.getKotlinInfo().isSyntheticClass()
+ && clazz.getKotlinInfo().asSyntheticClass().isLambda();
+ return true;
+ }
+
+ private boolean verifyNoUnexpectedKotlinMemberInfo(DexProgramClass clazz) {
+ assert Streams.stream(clazz.members())
+ .allMatch(member -> member.getKotlinMemberInfo().isNoKotlinInformation());
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java
index 2311f16..5b11ff5 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java
@@ -8,18 +8,26 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+import com.android.tools.r8.ir.analysis.proto.EnumLiteProtoShrinker;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Collections;
import java.util.Set;
public class NotMatchedByNoHorizontalClassMerging extends SingleClassPolicy {
+
+ private final Set<DexType> deadEnumLiteMaps;
private final Set<DexType> neverMergeClassHorizontally;
public NotMatchedByNoHorizontalClassMerging(AppView<AppInfoWithLiveness> appView) {
+ deadEnumLiteMaps =
+ appView.withProtoEnumShrinker(
+ EnumLiteProtoShrinker::getDeadEnumLiteMaps, Collections.emptySet());
neverMergeClassHorizontally = appView.appInfo().getNoHorizontalClassMergingSet();
}
@Override
public boolean canMerge(DexProgramClass program) {
- return !neverMergeClassHorizontally.contains(program.getReference());
+ return !deadEnumLiteMaps.contains(program.getType())
+ && !neverMergeClassHorizontally.contains(program.getType());
}
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMethodImplementation.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMethodImplementation.java
new file mode 100644
index 0000000..b8083e1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMethodImplementation.java
@@ -0,0 +1,173 @@
+// Copyright (c) 2020, 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.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodSignature;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
+import com.android.tools.r8.horizontalclassmerging.SubtypingForrestForClasses;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
+import java.util.Collection;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Prevent merging of classes where subclasses contain interface with default methods and the merged
+ * class would contain a method with the same signature. Consider the following example: <code>
+ * class A {}
+ * class B {
+ * public void m() {
+ * // ...
+ * }
+ * }
+ *
+ * interface {
+ * default void m() {
+ * // ...
+ * }
+ * }
+ *
+ * class C extends A implements I {
+ * }
+ * </code>
+ *
+ * <p>If A and B are merged, then the resulting class contains the method {@code void m()}. When
+ * resolving m on C, the method would point to the method on the merged class rather than the
+ * default interface method, changing runtime behaviour.
+ *
+ * <p>See: https://docs.oracle.com/javase/specs/jvms/se15/html/jvms-5.html#jvms-5.4.3.3)
+ */
+public class PreventMethodImplementation extends MultiClassPolicy {
+ private final AppView<AppInfoWithLiveness> appView;
+ private final SubtypingForrestForClasses subtypingForrestForClasses;
+
+ private final InterfaceDefaultSignaturesCache interfaceDefaultMethodsCache =
+ new InterfaceDefaultSignaturesCache();
+ private final ParentClassSignaturesCache parentClassMethodsCache =
+ new ParentClassSignaturesCache();
+ private final ReservedInterfaceSignaturesFor reservedInterfaceSignaturesFor =
+ new ReservedInterfaceSignaturesFor();
+
+ private abstract static class SignaturesCache<C extends DexClass> {
+ private final Map<DexClass, DexMethodSignatureSet> memoizedSignatures = new IdentityHashMap<>();
+
+ public DexMethodSignatureSet getOrComputeSignatures(C clazz) {
+ return memoizedSignatures.computeIfAbsent(
+ clazz,
+ ignore -> {
+ DexMethodSignatureSet signatures = DexMethodSignatureSet.createLinked();
+ process(clazz, signatures);
+ return signatures;
+ });
+ }
+
+ abstract void process(C clazz, DexMethodSignatureSet signatures);
+ }
+
+ private abstract class DexClassSignaturesCache extends SignaturesCache<DexClass> {
+
+ DexMethodSignatureSet getOrComputeSignatures(DexType type) {
+ DexClass clazz = appView.definitionFor(type);
+ return clazz != null ? getOrComputeSignatures(clazz) : null;
+ }
+ }
+
+ private class InterfaceDefaultSignaturesCache extends DexClassSignaturesCache {
+
+ @Override
+ void process(DexClass clazz, DexMethodSignatureSet signatures) {
+ signatures.addAllMethods(clazz.virtualMethods(DexEncodedMethod::isDefaultMethod));
+ signatures.addAll(clazz.getInterfaces(), this::getOrComputeSignatures);
+ }
+ }
+
+ private class ParentClassSignaturesCache extends DexClassSignaturesCache {
+
+ @Override
+ void process(DexClass clazz, DexMethodSignatureSet signatures) {
+ signatures.addAllMethods(clazz.methods());
+ if (clazz.getSuperType() != null) {
+ DexClass superClass = appView.definitionFor(clazz.getSuperType());
+ if (superClass != null) {
+ signatures.addAll(getOrComputeSignatures(superClass));
+ }
+ }
+ }
+ }
+
+ private class ReservedInterfaceSignaturesFor extends SignaturesCache<DexProgramClass> {
+
+ @Override
+ void process(DexProgramClass clazz, DexMethodSignatureSet signatures) {
+ signatures.addAll(
+ clazz.getInterfaces(), interfaceDefaultMethodsCache::getOrComputeSignatures);
+ signatures.addAll(
+ subtypingForrestForClasses.getSubtypesFor(clazz), this::getOrComputeSignatures);
+ signatures.removeAllMethods(clazz.methods());
+ }
+ }
+
+ public PreventMethodImplementation(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ this.subtypingForrestForClasses = new SubtypingForrestForClasses(appView);
+ }
+
+ enum MethodCategory {
+ CLASS_HIERARCHY_SAFE,
+ KEEP_ABSENT,
+ }
+
+ static class DispatchSignature extends LinkedHashMap<DexMethodSignature, MethodCategory> {
+ void addSignature(DexMethodSignature signature, MethodCategory category) {
+ MethodCategory old = put(signature, category);
+ assert old == null;
+ }
+ }
+
+ DexMethodSignatureSet computeReservedSignaturesForClass(DexProgramClass clazz) {
+ DexMethodSignatureSet reservedSignatures =
+ DexMethodSignatureSet.create(reservedInterfaceSignaturesFor.getOrComputeSignatures(clazz));
+ reservedSignatures.removeAll(parentClassMethodsCache.getOrComputeSignatures(clazz));
+ return reservedSignatures;
+ }
+
+ @Override
+ public Collection<List<DexProgramClass>> apply(List<DexProgramClass> group) {
+ DexMethodSignatureSet signatures = DexMethodSignatureSet.createLinked();
+ for (DexProgramClass clazz : group) {
+ signatures.addAllMethods(clazz.methods());
+ }
+
+ Map<DispatchSignature, List<DexProgramClass>> newGroups = new LinkedHashMap<>();
+ for (DexProgramClass clazz : group) {
+ DexMethodSignatureSet clazzReserved = computeReservedSignaturesForClass(clazz);
+ DispatchSignature dispatchSignature = new DispatchSignature();
+ for (DexMethodSignature signature : signatures) {
+ MethodCategory category = MethodCategory.CLASS_HIERARCHY_SAFE;
+ if (clazzReserved.contains(signature)) {
+ DexMethod template = signature.withHolder(clazz, appView.dexItemFactory());
+ SingleResolutionResult result =
+ appView.appInfo().resolveMethodOnClass(template, clazz).asSingleResolution();
+ if (result == null || result.getResolvedHolder().isInterface()) {
+ category = MethodCategory.KEEP_ABSENT;
+ }
+ }
+ dispatchSignature.addSignature(signature, category);
+ }
+ newGroups.computeIfAbsent(dispatchSignature, ignore -> new LinkedList<>()).add(clazz);
+ }
+ return removeTrivialGroups(newGroups.values());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/EnumLiteProtoShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/EnumLiteProtoShrinker.java
index 055882b..0b04f9b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/EnumLiteProtoShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/EnumLiteProtoShrinker.java
@@ -45,6 +45,10 @@
this.references = references;
}
+ public Set<DexType> getDeadEnumLiteMaps() {
+ return deadEnumLiteMaps;
+ }
+
private DexField createInternalValueMapField(DexType holder) {
return appView
.dexItemFactory()
@@ -52,9 +56,7 @@
}
public void clearDeadEnumLiteMaps() {
- if (!appView.options().protoShrinking().enableEnumLiteProtoShrinking) {
- return;
- }
+ assert appView.options().protoShrinking().isProtoEnumShrinkingEnabled();
// The optimization only enables further enums to be unboxed, no point to run it if enum
// unboxing is disabled.
if (!appView.options().enableEnumUnboxing) {
@@ -70,23 +72,28 @@
private void internalClearDeadEnumLiteMaps() {
for (DexProgramClass clazz : appView.appInfo().classes()) {
- if (clazz.interfaces.contains(references.enumLiteMapType)) {
- DexProgramClass enumLite = computeCorrespondingEnumLite(clazz);
- if (enumLite != null) {
- DexEncodedField field = enumLite.lookupField(createInternalValueMapField(enumLite.type));
- if (field != null) {
- if (appView.appInfo().isStaticFieldWrittenOnlyInEnclosingStaticInitializer(field)
- && !appView.appInfo().isFieldRead(field)) {
- deadEnumLiteMaps.add(clazz.type);
- // Clears the EnumLiteMap methods to avoid them being IR processed.
- clazz.setVirtualMethods(DexEncodedMethod.EMPTY_ARRAY);
- }
- }
- }
+ if (isDeadEnumLiteMap(clazz)) {
+ deadEnumLiteMaps.add(clazz.getType());
+ // Clears the EnumLiteMap methods to avoid them being IR processed.
+ clazz.setVirtualMethods(DexEncodedMethod.EMPTY_ARRAY);
}
}
}
+ public boolean isDeadEnumLiteMap(DexProgramClass clazz) {
+ if (clazz.getInterfaces().contains(references.enumLiteMapType)) {
+ DexProgramClass enumLite = computeCorrespondingEnumLite(clazz);
+ if (enumLite != null) {
+ DexEncodedField field =
+ enumLite.lookupField(createInternalValueMapField(enumLite.getType()));
+ return field != null
+ && appView.appInfo().isStaticFieldWrittenOnlyInEnclosingStaticInitializer(field)
+ && !appView.appInfo().isFieldRead(field);
+ }
+ }
+ return false;
+ }
+
/**
* Each EnumLiteMap subclass has only two virtual methods findValueByNumber:
*
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
index 6cec099..2674265 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
@@ -42,7 +42,10 @@
appView.options().protoShrinking().enableGeneratedMessageLiteBuilderShrinking
? new GeneratedMessageLiteBuilderShrinker(appView, references)
: null;
- this.enumProtoShrinker = new EnumLiteProtoShrinker(appView, references);
+ this.enumProtoShrinker =
+ appView.options().protoShrinking().isProtoEnumShrinkingEnabled()
+ ? new EnumLiteProtoShrinker(appView, references)
+ : null;
this.references = references;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index b2c5888..8ba06e0 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -1497,7 +1497,7 @@
if (interfaceMethodRewriter != null) {
timing.begin("Rewrite interface methods");
- interfaceMethodRewriter.rewriteMethodReferences(method, code);
+ interfaceMethodRewriter.rewriteMethodReferences(code);
timing.end();
assert code.isConsistentSSA();
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 2f56b0d..d6f6264 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.desugar;
import com.android.tools.r8.DesugarGraphConsumer;
+import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.graph.AppInfo;
@@ -18,7 +19,6 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexLibraryClass;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexMethodHandle;
import com.android.tools.r8.graph.DexProgramClass;
@@ -28,6 +28,7 @@
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.GenericSignature;
import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
import com.android.tools.r8.ir.code.BasicBlock;
@@ -41,6 +42,7 @@
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.desugar.DefaultMethodsHelper.Collection;
import com.android.tools.r8.ir.desugar.InterfaceProcessor.InterfaceProcessorNestedGraphLens;
+import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.SynthesizedOrigin;
import com.android.tools.r8.position.MethodPosition;
@@ -61,10 +63,8 @@
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
-import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.BiConsumer;
@@ -120,10 +120,6 @@
// Caches default interface method info for already processed interfaces.
private final Map<DexType, DefaultMethodsHelper.Collection> cache = new ConcurrentHashMap<>();
- /** Interfaces requiring dispatch classes to be created, and appropriate callers. */
- private final ConcurrentMap<DexLibraryClass, Set<DexProgramClass>> requiredDispatchClasses =
- new ConcurrentHashMap<>();
-
/**
* Defines a minor variation in desugaring.
*/
@@ -220,7 +216,9 @@
// Rewrites the references to static and default interface methods.
// NOTE: can be called for different methods concurrently.
- public void rewriteMethodReferences(DexEncodedMethod encodedMethod, IRCode code) {
+ public void rewriteMethodReferences(IRCode code) {
+ ProgramMethod context = code.context();
+ DexEncodedMethod encodedMethod = context.getDefinition();
if (synthesizedMethods.contains(encodedMethod)) {
return;
}
@@ -278,14 +276,36 @@
// so the user class is not rejected because it make this call directly.
// TODO(b/166247515): If this an incorrect invoke-static without the interface bit
// we end up "fixing" the code and remove and ICCE error.
+ ProgramMethod newProgramMethod =
+ appView
+ .getSyntheticItems()
+ .createMethod(
+ context.getHolder(),
+ factory,
+ syntheticMethodBuilder -> {
+ syntheticMethodBuilder
+ .setProto(method.proto)
+ .setAccessFlags(
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC
+ | Constants.ACC_STATIC
+ | Constants.ACC_SYNTHETIC,
+ false))
+ .setCode(
+ m ->
+ ForwardMethodBuilder.builder(factory)
+ .setStaticTarget(method, true)
+ .setStaticSource(m)
+ .build());
+ });
instructions.replaceCurrentInstruction(
new InvokeStatic(
- staticAsMethodOfDispatchClass(method),
+ newProgramMethod.getReference(),
invokeStatic.outValue(),
invokeStatic.arguments()));
- requiredDispatchClasses
- .computeIfAbsent(clazz.asLibraryClass(), k -> Sets.newConcurrentHashSet())
- .add(appInfo.definitionFor(encodedMethod.holder()).asProgramClass());
+ synchronized (synthesizedMethods) {
+ synthesizedMethods.add(newProgramMethod);
+ }
}
} else {
instructions.replaceCurrentInstruction(
@@ -961,7 +981,7 @@
InterfaceProcessorNestedGraphLens.Builder graphLensBuilder =
InterfaceProcessorNestedGraphLens.builder();
Map<DexClass, DexProgramClass> classMapping =
- processInterfaces(builder, flavour, graphLensBuilder);
+ processInterfaces(builder, flavour, graphLensBuilder, synthesizedMethods::add);
InterfaceProcessorNestedGraphLens graphLens =
graphLensBuilder.build(appView.dexItemFactory(), appView.graphLens());
if (appView.enableWholeProgramOptimizations() && graphLens != null) {
@@ -1026,7 +1046,6 @@
private void clear() {
this.cache.clear();
this.synthesizedMethods.clear();
- this.requiredDispatchClasses.clear();
}
private static boolean shouldProcess(
@@ -1038,17 +1057,14 @@
private Map<DexClass, DexProgramClass> processInterfaces(
Builder<?> builder,
Flavor flavour,
- InterfaceProcessorNestedGraphLens.Builder graphLensBuilder) {
+ InterfaceProcessorNestedGraphLens.Builder graphLensBuilder,
+ Consumer<ProgramMethod> newSynthesizedMethodConsumer) {
InterfaceProcessor processor = new InterfaceProcessor(appView, this);
for (DexProgramClass clazz : builder.getProgramClasses()) {
if (shouldProcess(clazz, flavour, true)) {
- processor.process(clazz, graphLensBuilder);
+ processor.process(clazz, graphLensBuilder, newSynthesizedMethodConsumer);
}
}
- for (Entry<DexLibraryClass, Set<DexProgramClass>> entry : requiredDispatchClasses.entrySet()) {
- DexProgramClass dispatchClass = processor.process(entry.getKey(), entry.getValue());
- dispatchClass.forEachProgramMethod(synthesizedMethods::add);
- }
return processor.syntheticClasses;
}
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 99230aa..fbeb14a 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
@@ -4,14 +4,21 @@
package com.android.tools.r8.ir.desugar;
+import static com.android.tools.r8.utils.PredicateUtils.not;
+
+import com.android.tools.r8.cf.code.CfFieldInstruction;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.code.InvokeSuper;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.ClassAccessFlags;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexAnnotationSet;
@@ -26,18 +33,24 @@
import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.DexValue.DexValueNull;
+import com.android.tools.r8.graph.FieldAccessFlags;
import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.MethodCollection;
import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
import com.android.tools.r8.origin.SynthesizedOrigin;
import com.android.tools.r8.utils.Pair;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
+import com.google.common.collect.ImmutableList;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
@@ -47,6 +60,8 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Consumer;
+import org.objectweb.asm.Opcodes;
// Default and static method interface desugaring processor for interfaces.
//
@@ -68,126 +83,22 @@
this.rewriter = rewriter;
}
- void process(DexProgramClass iface, InterfaceProcessorNestedGraphLens.Builder graphLensBuilder) {
+ void process(
+ DexProgramClass iface,
+ InterfaceProcessorNestedGraphLens.Builder graphLensBuilder,
+ Consumer<ProgramMethod> newSynthesizedMethodConsumer) {
assert iface.isInterface();
// The list of methods to be created in companion class.
List<DexEncodedMethod> companionMethods = new ArrayList<>();
+ ensureCompanionClassInitializesInterface(iface, companionMethods);
+
// Process virtual interface methods first.
- List<DexEncodedMethod> remainingMethods = new ArrayList<>();
- for (DexEncodedMethod virtual : iface.virtualMethods()) {
- if (rewriter.isDefaultMethod(virtual)) {
- if (!canMoveToCompanionClass(virtual)) {
- throw new CompilationError("One or more instruction is preventing default interface "
- + "method from being desugared: " + virtual.method.toSourceString(), iface.origin);
- }
-
- // Create a new method in a companion class to represent default method implementation.
- DexMethod companionMethod = rewriter.defaultAsMethodOfCompanionClass(virtual.method);
-
- Code code = virtual.getCode();
- if (code == null) {
- throw new CompilationError("Code is missing for default "
- + "interface method: " + virtual.method.toSourceString(), iface.origin);
- }
-
- MethodAccessFlags newFlags = virtual.accessFlags.copy();
- newFlags.unsetBridge();
- newFlags.promoteToStatic();
- DexEncodedMethod.setDebugInfoWithFakeThisParameter(
- code, companionMethod.getArity(), appView);
- DexEncodedMethod implMethod =
- new DexEncodedMethod(
- companionMethod,
- newFlags,
- virtual.getGenericSignature(),
- virtual.annotations(),
- virtual.parameterAnnotationsList,
- code,
- true);
- implMethod.copyMetadata(virtual);
- virtual.setDefaultInterfaceMethodImplementation(implMethod);
- companionMethods.add(implMethod);
- graphLensBuilder.recordCodeMovedToCompanionClass(virtual.method, implMethod.method);
- }
-
- // Remove bridge methods.
- if (interfaceMethodRemovalChangesApi(virtual, iface)) {
- remainingMethods.add(virtual);
- }
- }
-
- // If at least one bridge method was removed then update the table.
- if (remainingMethods.size() < iface.getMethodCollection().numberOfVirtualMethods()) {
- iface.setVirtualMethods(remainingMethods.toArray(DexEncodedMethod.EMPTY_ARRAY));
- }
- remainingMethods.clear();
+ processVirtualInterfaceMethods(iface, companionMethods, graphLensBuilder);
// Process static and private methods, move them into companion class as well,
// make private instance methods public static.
- for (DexEncodedMethod direct : iface.directMethods()) {
- MethodAccessFlags originalFlags = direct.accessFlags;
- MethodAccessFlags newFlags = originalFlags.copy();
- if (originalFlags.isPrivate()) {
- newFlags.promoteToPublic();
- }
- DexMethod oldMethod = direct.method;
- if (isStaticMethod(direct)) {
- assert originalFlags.isPrivate() || originalFlags.isPublic()
- : "Static interface method " + direct.toSourceString() + " is expected to "
- + "either be public or private in " + iface.origin;
- DexMethod companionMethod = rewriter.staticAsMethodOfCompanionClass(oldMethod);
- DexEncodedMethod implMethod =
- new DexEncodedMethod(
- companionMethod,
- newFlags,
- direct.getGenericSignature(),
- direct.annotations(),
- direct.parameterAnnotationsList,
- direct.getCode(),
- true);
- implMethod.copyMetadata(direct);
- companionMethods.add(implMethod);
- graphLensBuilder.move(oldMethod, companionMethod);
- } else {
- if (originalFlags.isPrivate()) {
- assert !rewriter.factory.isClassConstructor(oldMethod)
- : "Unexpected private constructor " + direct.toSourceString()
- + " in " + iface.origin;
- newFlags.promoteToStatic();
-
- DexMethod companionMethod = rewriter.privateAsMethodOfCompanionClass(oldMethod);
-
- Code code = direct.getCode();
- if (code == null) {
- throw new CompilationError("Code is missing for private instance "
- + "interface method: " + oldMethod.toSourceString(), iface.origin);
- }
- DexEncodedMethod.setDebugInfoWithFakeThisParameter(
- code, companionMethod.getArity(), appView);
- DexEncodedMethod implMethod =
- new DexEncodedMethod(
- companionMethod,
- newFlags,
- direct.getGenericSignature(),
- direct.annotations(),
- direct.parameterAnnotationsList,
- code,
- true);
- implMethod.copyMetadata(direct);
- companionMethods.add(implMethod);
- graphLensBuilder.move(oldMethod, companionMethod);
- } else {
- // Since there are no interface constructors at this point,
- // this should only be class constructor.
- assert rewriter.factory.isClassConstructor(oldMethod);
- remainingMethods.add(direct);
- }
- }
- }
- if (remainingMethods.size() < iface.getMethodCollection().numberOfDirectMethods()) {
- iface.setDirectMethods(remainingMethods.toArray(DexEncodedMethod.EMPTY_ARRAY));
- }
+ processDirectInterfaceMethods(iface, companionMethods, graphLensBuilder);
if (companionMethods.isEmpty()) {
return; // No methods to create, companion class not needed.
@@ -227,6 +138,218 @@
getChecksumSupplier(iface),
Collections.singletonList(iface));
syntheticClasses.put(iface, companionClass);
+ if (companionClass.hasClassInitializer()) {
+ newSynthesizedMethodConsumer.accept(companionClass.getProgramClassInitializer());
+ }
+ }
+
+ private void ensureCompanionClassInitializesInterface(
+ DexProgramClass iface, List<DexEncodedMethod> companionMethods) {
+ if (!hasStaticMethodThatTriggersNonTrivialClassInitializer(iface)) {
+ return;
+ }
+ DexEncodedField clinitField =
+ findExistingStaticClinitFieldToTriggerInterfaceInitialization(iface);
+ if (clinitField == null) {
+ clinitField = createStaticClinitFieldToTriggerInterfaceInitialization(iface);
+ iface.appendStaticField(clinitField);
+ }
+ companionMethods.add(createCompanionClassInitializer(iface, clinitField));
+ }
+
+ private boolean hasStaticMethodThatTriggersNonTrivialClassInitializer(DexProgramClass iface) {
+ return iface.hasClassInitializer()
+ && iface
+ .getMethodCollection()
+ .hasDirectMethods(method -> method.isStatic() && !method.isClassInitializer());
+ }
+
+ private DexEncodedField findExistingStaticClinitFieldToTriggerInterfaceInitialization(
+ DexProgramClass iface) {
+ for (DexEncodedField field : iface.staticFields(not(DexEncodedField::isPrivate))) {
+ return field;
+ }
+ return null;
+ }
+
+ private DexEncodedField createStaticClinitFieldToTriggerInterfaceInitialization(
+ DexProgramClass iface) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ DexField clinitFieldTemplateReference =
+ dexItemFactory.createField(iface.getType(), dexItemFactory.intType, "$desugar$clinit");
+ DexField clinitFieldReference =
+ dexItemFactory.createFreshFieldName(
+ clinitFieldTemplateReference, candidate -> iface.lookupField(candidate) != null);
+ return new DexEncodedField(
+ clinitFieldReference,
+ FieldAccessFlags.builder().setPackagePrivate().setStatic().setSynthetic().build(),
+ FieldTypeSignature.noSignature(),
+ DexAnnotationSet.empty(),
+ DexValueNull.NULL);
+ }
+
+ private DexEncodedMethod createCompanionClassInitializer(
+ DexProgramClass iface, DexEncodedField clinitField) {
+ DexType companionType = rewriter.getCompanionClassType(iface.getType());
+ DexMethod clinitMethodReference = appView.dexItemFactory().createClinitMethod(companionType);
+ CfCode code =
+ new CfCode(
+ companionType,
+ 1,
+ 0,
+ ImmutableList.of(
+ new CfFieldInstruction(
+ Opcodes.GETSTATIC, clinitField.getReference(), clinitField.getReference()),
+ new CfStackInstruction(Opcode.Pop),
+ new CfReturnVoid()),
+ ImmutableList.of(),
+ ImmutableList.of());
+ return new DexEncodedMethod(
+ clinitMethodReference,
+ MethodAccessFlags.builder().setConstructor().setPackagePrivate().setStatic().build(),
+ MethodTypeSignature.noSignature(),
+ DexAnnotationSet.empty(),
+ ParameterAnnotationsList.empty(),
+ code,
+ true,
+ iface.hasClassFileVersion() ? iface.getInitialClassFileVersion() : null);
+ }
+
+ private void processVirtualInterfaceMethods(
+ DexProgramClass iface,
+ List<DexEncodedMethod> companionMethods,
+ InterfaceProcessorNestedGraphLens.Builder graphLensBuilder) {
+ List<DexEncodedMethod> newVirtualMethods = new ArrayList<>();
+ for (DexEncodedMethod virtual : iface.virtualMethods()) {
+ if (rewriter.isDefaultMethod(virtual)) {
+ if (!canMoveToCompanionClass(virtual)) {
+ throw new CompilationError("One or more instruction is preventing default interface "
+ + "method from being desugared: " + virtual.method.toSourceString(), iface.origin);
+ }
+
+ // Create a new method in a companion class to represent default method implementation.
+ DexMethod companionMethod = rewriter.defaultAsMethodOfCompanionClass(virtual.method);
+
+ Code code = virtual.getCode();
+ if (code == null) {
+ throw new CompilationError("Code is missing for default "
+ + "interface method: " + virtual.method.toSourceString(), iface.origin);
+ }
+
+ MethodAccessFlags newFlags = virtual.accessFlags.copy();
+ newFlags.unsetBridge();
+ newFlags.promoteToStatic();
+ DexEncodedMethod.setDebugInfoWithFakeThisParameter(
+ code, companionMethod.getArity(), appView);
+ DexEncodedMethod implMethod =
+ new DexEncodedMethod(
+ companionMethod,
+ newFlags,
+ virtual.getGenericSignature(),
+ virtual.annotations(),
+ virtual.parameterAnnotationsList,
+ code,
+ true);
+ implMethod.copyMetadata(virtual);
+ virtual.setDefaultInterfaceMethodImplementation(implMethod);
+ companionMethods.add(implMethod);
+ graphLensBuilder.recordCodeMovedToCompanionClass(virtual.method, implMethod.method);
+ }
+
+ // Remove bridge methods.
+ if (interfaceMethodRemovalChangesApi(virtual, iface)) {
+ newVirtualMethods.add(virtual);
+ }
+ }
+
+ // If at least one bridge method was removed then update the table.
+ if (newVirtualMethods.size() < iface.getMethodCollection().numberOfVirtualMethods()) {
+ iface.setVirtualMethods(newVirtualMethods.toArray(DexEncodedMethod.EMPTY_ARRAY));
+ }
+ }
+
+ private void processDirectInterfaceMethods(
+ DexProgramClass iface,
+ List<DexEncodedMethod> companionMethods,
+ InterfaceProcessorNestedGraphLens.Builder graphLensBuilder) {
+ DexEncodedMethod clinit = null;
+ for (DexEncodedMethod method : iface.directMethods()) {
+ if (method.isClassInitializer()) {
+ clinit = method;
+ continue;
+ }
+ if (method.isInstanceInitializer()) {
+ assert false
+ : "Unexpected interface instance initializer: "
+ + method.getReference().toSourceString();
+ continue;
+ }
+
+ MethodAccessFlags originalFlags = method.getAccessFlags();
+ MethodAccessFlags newFlags = originalFlags.copy();
+ if (originalFlags.isPrivate()) {
+ newFlags.promoteToPublic();
+ }
+
+ DexMethod oldMethod = method.getReference();
+ if (isStaticMethod(method)) {
+ assert originalFlags.isPrivate() || originalFlags.isPublic()
+ : "Static interface method "
+ + method.toSourceString()
+ + " is expected to "
+ + "either be public or private in "
+ + iface.origin;
+ DexMethod companionMethod = rewriter.staticAsMethodOfCompanionClass(oldMethod);
+ DexEncodedMethod implMethod =
+ new DexEncodedMethod(
+ companionMethod,
+ newFlags,
+ method.getGenericSignature(),
+ method.annotations(),
+ method.parameterAnnotationsList,
+ method.getCode(),
+ true);
+ implMethod.copyMetadata(method);
+ companionMethods.add(implMethod);
+ graphLensBuilder.move(oldMethod, companionMethod);
+ continue;
+ }
+
+ assert originalFlags.isPrivate();
+
+ newFlags.promoteToStatic();
+
+ DexMethod companionMethod = rewriter.privateAsMethodOfCompanionClass(oldMethod);
+
+ Code code = method.getCode();
+ if (code == null) {
+ throw new CompilationError(
+ "Code is missing for private instance "
+ + "interface method: "
+ + oldMethod.toSourceString(),
+ iface.origin);
+ }
+ DexEncodedMethod.setDebugInfoWithFakeThisParameter(code, companionMethod.getArity(), appView);
+ DexEncodedMethod implMethod =
+ new DexEncodedMethod(
+ companionMethod,
+ newFlags,
+ method.getGenericSignature(),
+ method.annotations(),
+ method.parameterAnnotationsList,
+ code,
+ true);
+ implMethod.copyMetadata(method);
+ companionMethods.add(implMethod);
+ graphLensBuilder.move(oldMethod, companionMethod);
+ }
+
+ MethodCollection methodCollection = iface.getMethodCollection();
+ if (clinit != null) {
+ methodCollection.setSingleDirectMethod(clinit);
+ } else {
+ methodCollection.clearDirectMethods();
+ }
}
private ChecksumSupplier getChecksumSupplier(DexProgramClass iface) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ArgumentRemovalUtils.java b/src/main/java/com/android/tools/r8/ir/optimize/ArgumentRemovalUtils.java
index 5892466..28bbf14 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ArgumentRemovalUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ArgumentRemovalUtils.java
@@ -14,9 +14,9 @@
// remove method arguments.
public static boolean isPinned(DexEncodedMethod method, AppView<AppInfoWithLiveness> appView) {
return appView.appInfo().isPinned(method.method)
- || appView.appInfo().bootstrapMethods.contains(method.method)
- || appView.appInfo().failedResolutionTargets.contains(method.method)
- || appView.appInfo().methodsTargetedByInvokeDynamic.contains(method.method)
+ || appView.appInfo().isBootstrapMethod(method.method)
+ || appView.appInfo().isFailedResolutionTarget(method.method)
+ || appView.appInfo().isMethodTargetedByInvokeDynamic(method.method)
|| method.accessFlags.isNative();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 9f790ec..945d9fe 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -126,7 +126,7 @@
AppInfoWithLiveness appInfo = appView.appInfo();
DexMethod singleTargetReference = singleTarget.getReference();
if (singleTarget.getDefinition().getOptimizationInfo().forceInline()
- && appInfo.neverInline.contains(singleTargetReference)) {
+ && appInfo.isNeverInlineMethod(singleTargetReference)) {
throw new Unreachable();
}
@@ -143,7 +143,7 @@
return true;
}
- if (appInfo.neverInline.contains(singleTargetReference)) {
+ if (appInfo.isNeverInlineMethod(singleTargetReference)) {
whyAreYouNotInliningReporter.reportMarkedAsNeverInline();
return true;
}
@@ -1091,8 +1091,8 @@
if (inlineeMayHaveInvokeMethod && options.applyInliningToInlinee) {
if (inlineeStack.size() + 1 > options.applyInliningToInlineeMaxDepth
- && appView.appInfo().alwaysInline.isEmpty()
- && appView.appInfo().forceInline.isEmpty()) {
+ && appView.appInfo().hasNoAlwaysInlineMethods()
+ && appView.appInfo().hasNoForceInlineMethods()) {
continue;
}
// Record that we will be inside the inlinee until the next block.
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 0dd84e7..509daa1 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
@@ -224,7 +224,7 @@
// If this is a method with known resolution issues, then don't remove any unused
// arguments.
- if (appView.appInfo().failedResolutionTargets.contains(method.method)) {
+ if (appView.appInfo().isFailedResolutionTarget(method.method)) {
return method;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
index ae543a2..bb270b9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
@@ -36,12 +36,12 @@
DexMethod targetReference = target.getReference();
if (targetMethod.getOptimizationInfo().forceInline()
|| (appView.appInfo().hasLiveness()
- && appView.withLiveness().appInfo().forceInline.contains(targetReference))) {
- assert !appView.appInfo().neverInline.contains(targetReference);
+ && appView.withLiveness().appInfo().isForceInlineMethod(targetReference))) {
+ assert !appView.appInfo().isNeverInlineMethod(targetReference);
return Reason.FORCE;
}
if (appView.appInfo().hasLiveness()
- && appView.withLiveness().appInfo().alwaysInline.contains(targetReference)) {
+ && appView.withLiveness().appInfo().isAlwaysInlineMethod(targetReference)) {
return Reason.ALWAYS;
}
if (appView.options().disableInliningOfLibraryMethodOverrides
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index df06499..6b011a6 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -5,6 +5,7 @@
import static com.android.tools.r8.kotlin.KotlinMetadataUtils.INVALID_KOTLIN_INFO;
import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO;
+import static com.android.tools.r8.kotlin.KotlinSyntheticClassInfo.getFlavour;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationElement;
@@ -16,6 +17,7 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.kotlin.KotlinSyntheticClassInfo.Flavour;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
import java.util.IdentityHashMap;
@@ -24,11 +26,10 @@
import kotlinx.metadata.InconsistentKotlinMetadataException;
import kotlinx.metadata.jvm.KotlinClassHeader;
import kotlinx.metadata.jvm.KotlinClassMetadata;
+import kotlinx.metadata.jvm.KotlinClassMetadata.SyntheticClass;
public final class KotlinClassMetadataReader {
- private static int KOTLIN_METADATA_KIND_LAMBDA = 3;
-
public static KotlinClassLevelInfo getKotlinInfo(
Kotlin kotlin,
DexClass clazz,
@@ -53,7 +54,7 @@
DexAnnotation annotation) {
try {
KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, annotation.annotation);
- if (onlyProcessLambda && kMetadata.getHeader().getKind() != KOTLIN_METADATA_KIND_LAMBDA) {
+ if (onlyProcessLambda && !isSyntheticClassifiedLambda(kotlin, clazz, kMetadata)) {
return NO_KOTLIN_INFO;
}
return createKotlinInfo(kotlin, clazz, kMetadata, factory, reporter, keepByteCode);
@@ -76,6 +77,16 @@
}
}
+ private static boolean isSyntheticClassifiedLambda(
+ Kotlin kotlin, DexClass clazz, KotlinClassMetadata kMetadata) {
+ if (kMetadata instanceof SyntheticClass) {
+ SyntheticClass syntheticClass = (SyntheticClass) kMetadata;
+ return syntheticClass.isLambda()
+ && getFlavour(syntheticClass, clazz, kotlin) != Flavour.Unclassified;
+ }
+ return false;
+ }
+
public static boolean hasKotlinClassMetadataAnnotation(
DexClass clazz, DexDefinitionSupplier definitionSupplier) {
return clazz
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFieldLevelInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFieldLevelInfo.java
index 8d9d468..acf2d64 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFieldLevelInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFieldLevelInfo.java
@@ -4,9 +4,7 @@
package com.android.tools.r8.kotlin;
-import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
-
-public interface KotlinFieldLevelInfo extends EnqueuerMetadataTraceable {
+public interface KotlinFieldLevelInfo extends KotlinMemberLevelInfo {
default boolean isCompanion() {
return false;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberLevelInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberLevelInfo.java
new file mode 100644
index 0000000..f7312ad
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberLevelInfo.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2020, 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.kotlin;
+
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
+
+public interface KotlinMemberLevelInfo extends EnqueuerMetadataTraceable {
+
+ default boolean isNoKotlinInformation() {
+ return false;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMethodLevelInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMethodLevelInfo.java
index a02cb0a..a43e677 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMethodLevelInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMethodLevelInfo.java
@@ -4,9 +4,7 @@
package com.android.tools.r8.kotlin;
-import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
-
-public interface KotlinMethodLevelInfo extends EnqueuerMetadataTraceable {
+public interface KotlinMethodLevelInfo extends KotlinMemberLevelInfo {
default boolean isConstructor() {
return false;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
index 41147a5..36bf5cf 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
@@ -110,7 +110,7 @@
return metadataVersion;
}
- private static Flavour getFlavour(
+ public static Flavour getFlavour(
KotlinClassMetadata.SyntheticClass metadata, DexClass clazz, Kotlin kotlin) {
// Returns KotlinStyleLambda if the given clazz is a Kotlin-style lambda:
// a class that
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index 0922f50..30d9b7d 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -176,7 +176,8 @@
// and there is therefore no need to check the hierarchy above.
MemberPool<DexMethod> memberPool = methodPoolCollection.get(method.getHolder());
Wrapper<DexMethod> methodKey = MethodSignatureEquivalence.get().wrap(method.getReference());
- if (memberPool.hasSeenStrictlyBelow(methodKey)) {
+ if (memberPool.hasSeenStrictlyBelow(methodKey)
+ && appView.options().enablePackagePrivateAwarePublicization) {
return false;
}
doPublicize(method);
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingTreeFixer.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingTreeFixer.java
index 845df2c..07af4a3 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingTreeFixer.java
@@ -36,6 +36,7 @@
private final Map<DexType, DexType> repackagedClasses;
private final Map<DexType, DexProgramClass> newProgramClasses = new IdentityHashMap<>();
+ private final Map<DexType, DexProgramClass> synthesizedFromClasses = new IdentityHashMap<>();
private final Map<DexProto, DexProto> protoFixupCache = new IdentityHashMap<>();
public RepackagingTreeFixer(
@@ -241,8 +242,12 @@
for (DexProgramClass clazz : synthesizedFrom) {
// TODO(b/165783399): What do we want to put here if the class that this was synthesized from
// is no longer in the application?
+ Map<DexType, DexProgramClass> classes =
+ appView.appInfo().definitionForWithoutExistenceAssert(clazz.getType()) != null
+ ? newProgramClasses
+ : synthesizedFromClasses;
DexProgramClass newClass =
- newProgramClasses.computeIfAbsent(clazz.getType(), ignore -> fixupClass(clazz));
+ classes.computeIfAbsent(clazz.getType(), ignore -> fixupClass(clazz));
newSynthesizedFrom.add(newClass);
changed |= newClass != clazz;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 6922939..4d5a549 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -99,24 +99,25 @@
* contained in {@link #liveMethods}, it may be marked as abstract and its implementation may be
* removed.
*/
- final SortedSet<DexMethod> targetedMethods;
+ private final Set<DexMethod> targetedMethods;
/** Set of targets that lead to resolution errors, such as non-existing or invalid targets. */
- public final Set<DexMethod> failedResolutionTargets;
+ private final Set<DexMethod> failedResolutionTargets;
/**
* Set of program methods that are used as the bootstrap method for an invoke-dynamic instruction.
*/
- public final SortedSet<DexMethod> bootstrapMethods;
+ private final Set<DexMethod> bootstrapMethods;
+
/** Set of methods that are the immediate target of an invoke-dynamic. */
- public final SortedSet<DexMethod> methodsTargetedByInvokeDynamic;
+ private final Set<DexMethod> methodsTargetedByInvokeDynamic;
/** Set of virtual methods that are the immediate target of an invoke-direct. */
- final SortedSet<DexMethod> virtualMethodsTargetedByInvokeDirect;
+ private final Set<DexMethod> virtualMethodsTargetedByInvokeDirect;
/**
* Set of methods that belong to live classes and can be reached by invokes. These need to be
* kept.
*/
- public final SortedSet<DexMethod> liveMethods;
+ private final Set<DexMethod> liveMethods;
/**
* Information about all fields that are accessed by the program. The information includes whether
* a given field is read/written by the program, and it also includes all indirect accesses to
@@ -141,11 +142,11 @@
/** All items with assumevalues rule. */
public final Map<DexMember<?, ?>, ProguardMemberRule> assumedValues;
/** All methods that should be inlined if possible due to a configuration directive. */
- public final Set<DexMethod> alwaysInline;
+ private final Set<DexMethod> alwaysInline;
/** All methods that *must* be inlined due to a configuration directive (testing only). */
- public final Set<DexMethod> forceInline;
+ private final Set<DexMethod> forceInline;
/** All methods that *must* never be inlined due to a configuration directive (testing only). */
- public final Set<DexMethod> neverInline;
+ private final Set<DexMethod> neverInline;
/** Items for which to print inlining decisions for (testing only). */
public final Set<DexMethod> whyAreYouNotInlining;
/** All methods that may not have any parameters with a constant value removed. */
@@ -205,12 +206,12 @@
Set<DexType> missingTypes,
Set<DexType> liveTypes,
Set<DexType> instantiatedAppServices,
- SortedSet<DexMethod> targetedMethods,
+ Set<DexMethod> targetedMethods,
Set<DexMethod> failedResolutionTargets,
- SortedSet<DexMethod> bootstrapMethods,
- SortedSet<DexMethod> methodsTargetedByInvokeDynamic,
- SortedSet<DexMethod> virtualMethodsTargetedByInvokeDirect,
- SortedSet<DexMethod> liveMethods,
+ Set<DexMethod> bootstrapMethods,
+ Set<DexMethod> methodsTargetedByInvokeDynamic,
+ Set<DexMethod> virtualMethodsTargetedByInvokeDirect,
+ Set<DexMethod> liveMethods,
FieldAccessInfoCollectionImpl fieldAccessInfoCollection,
MethodAccessInfoCollection methodAccessInfoCollection,
ObjectAllocationInfoCollectionImpl objectAllocationInfoCollection,
@@ -288,10 +289,10 @@
Set<DexType> missingTypes,
Set<DexType> liveTypes,
Set<DexType> instantiatedAppServices,
- SortedSet<DexMethod> targetedMethods,
+ Set<DexMethod> targetedMethods,
Set<DexMethod> failedResolutionTargets,
- SortedSet<DexMethod> bootstrapMethods,
- SortedSet<DexMethod> methodsTargetedByInvokeDynamic,
+ Set<DexMethod> bootstrapMethods,
+ Set<DexMethod> methodsTargetedByInvokeDynamic,
SortedSet<DexMethod> virtualMethodsTargetedByInvokeDirect,
SortedSet<DexMethod> liveMethods,
FieldAccessInfoCollectionImpl fieldAccessInfoCollection,
@@ -626,6 +627,54 @@
return clazz == null || !clazz.isProgramClass();
}
+ public boolean isLiveMethod(DexMethod method) {
+ return liveMethods.contains(method);
+ }
+
+ public boolean isTargetedMethod(DexMethod method) {
+ return targetedMethods.contains(method);
+ }
+
+ public boolean isFailedResolutionTarget(DexMethod method) {
+ return failedResolutionTargets.contains(method);
+ }
+
+ public Set<DexMethod> getFailedResolutionTargets() {
+ return failedResolutionTargets;
+ }
+
+ public boolean isBootstrapMethod(DexMethod method) {
+ return bootstrapMethods.contains(method);
+ }
+
+ public boolean isMethodTargetedByInvokeDynamic(DexMethod method) {
+ return methodsTargetedByInvokeDynamic.contains(method);
+ }
+
+ public Set<DexMethod> getVirtualMethodsTargetedByInvokeDirect() {
+ return virtualMethodsTargetedByInvokeDirect;
+ }
+
+ public boolean isAlwaysInlineMethod(DexMethod method) {
+ return alwaysInline.contains(method);
+ }
+
+ public boolean hasNoAlwaysInlineMethods() {
+ return alwaysInline.isEmpty();
+ }
+
+ public boolean isForceInlineMethod(DexMethod method) {
+ return forceInline.contains(method);
+ }
+
+ public boolean hasNoForceInlineMethods() {
+ return forceInline.isEmpty();
+ }
+
+ public boolean isNeverInlineMethod(DexMethod method) {
+ return neverInline.contains(method);
+ }
+
public Collection<DexClass> computeReachableInterfaces() {
Set<DexClass> interfaces = Sets.newIdentityHashSet();
WorkList<DexType> worklist = WorkList.newIdentityWorkList();
@@ -1036,11 +1085,11 @@
lens.rewriteMethods(alwaysInline),
lens.rewriteMethods(forceInline),
lens.rewriteMethods(neverInline),
- lens.rewriteMethods(whyAreYouNotInlining),
- lens.rewriteMethods(keepConstantArguments),
- lens.rewriteMethods(keepUnusedArguments),
- lens.rewriteMethods(reprocess),
- lens.rewriteMethods(neverReprocess),
+ lens.rewriteMethodsSorted(whyAreYouNotInlining),
+ lens.rewriteMethodsSorted(keepConstantArguments),
+ lens.rewriteMethodsSorted(keepUnusedArguments),
+ lens.rewriteMethodsSorted(reprocess),
+ lens.rewriteMethodsSorted(neverReprocess),
alwaysClassInline.rewriteItems(lens::lookupType),
lens.rewriteTypes(neverClassInline),
lens.rewriteTypes(noUnusedInterfaceRemoval),
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 8e6142e..e032ea2 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -58,7 +58,6 @@
import com.android.tools.r8.graph.LookupTarget;
import com.android.tools.r8.graph.MethodAccessInfoCollection;
import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
-import com.android.tools.r8.graph.PresortedComparable;
import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMember;
@@ -104,7 +103,6 @@
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
import com.android.tools.r8.utils.Action;
-import com.android.tools.r8.utils.DequeUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.DesugarState;
import com.android.tools.r8.utils.IteratorUtils;
@@ -120,7 +118,6 @@
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
@@ -142,7 +139,6 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
-import java.util.SortedSet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.BiConsumer;
@@ -233,10 +229,25 @@
* Set of types that are mentioned in the program. We at least need an empty abstract class item
* for these.
*/
- private final SetWithReportedReason<DexProgramClass> liveTypes;
+ private final SetWithReportedReason<DexProgramClass> liveTypes = new SetWithReportedReason<>();
- /** Set of types whose class initializer may execute. */
- private final SetWithReportedReason<DexProgramClass> initializedTypes;
+ /** Set of classes whose initializer may execute. */
+ private final SetWithReportedReason<DexProgramClass> initializedClasses =
+ new SetWithReportedReason<>();
+
+ /**
+ * Set of interfaces whose interface initializer may execute directly in response to a static
+ * field or method access on the interface.
+ */
+ private final SetWithReportedReason<DexProgramClass> directlyInitializedInterfaces =
+ new SetWithReportedReason<>();
+
+ /**
+ * Set of interfaces whose interface initializer may execute indirectly as a side-effect of the
+ * class initialization of a (non-interface) subclass.
+ */
+ private final SetWithReportedReason<DexProgramClass> indirectlyInitializedInterfaces =
+ new SetWithReportedReason<>();
/**
* Set of live types defined in the library and classpath.
@@ -387,9 +398,6 @@
shrinker -> registerAnalysis(shrinker.createEnqueuerAnalysis()));
}
-
- liveTypes = new SetWithReportedReason<>();
- initializedTypes = new SetWithReportedReason<>();
targetedMethods = new SetWithReason<>(graphReporter::registerMethod);
// This set is only populated in edge cases due to multiple default interface methods.
// The set is generally expected to be empty and in the unlikely chance it is not, it will
@@ -1884,31 +1892,41 @@
}
private void markDirectAndIndirectClassInitializersAsLive(DexProgramClass clazz) {
- Deque<DexProgramClass> worklist = DequeUtils.newArrayDeque(clazz);
- Set<DexProgramClass> visited = SetUtils.newIdentityHashSet(clazz);
- while (!worklist.isEmpty()) {
- DexProgramClass current = worklist.removeFirst();
- assert visited.contains(current);
+ if (clazz.isInterface()) {
+ // Accessing a static field or method on an interface does not trigger the class initializer
+ // of any parent interfaces.
+ markInterfaceInitializedDirectly(clazz);
+ return;
+ }
- if (!markDirectClassInitializerAsLive(current)) {
- continue;
+ WorkList<DexProgramClass> worklist = WorkList.newIdentityWorkList(clazz);
+ while (worklist.hasNext()) {
+ DexProgramClass current = worklist.next();
+ if (current.isInterface()) {
+ if (!markInterfaceInitializedIndirectly(current)) {
+ continue;
+ }
+ } else {
+ if (!markDirectClassInitializerAsLive(current)) {
+ continue;
+ }
}
// Mark all class initializers in all super types as live.
- for (DexType superType : clazz.allImmediateSupertypes()) {
+ for (DexType superType : current.allImmediateSupertypes()) {
DexProgramClass superClass = getProgramClassOrNull(superType);
- if (superClass != null && visited.add(superClass)) {
- worklist.add(superClass);
+ if (superClass != null) {
+ worklist.addIfNotSeen(superClass);
}
}
}
}
- /** Returns true if the class initializer became live for the first time. */
+ /** Returns true if the class became initialized for the first time. */
private boolean markDirectClassInitializerAsLive(DexProgramClass clazz) {
ProgramMethod clinit = clazz.getProgramClassInitializer();
KeepReasonWitness witness = graphReporter.reportReachableClassInitializer(clazz, clinit);
- if (!initializedTypes.add(clazz, witness)) {
+ if (!initializedClasses.add(clazz, witness)) {
return false;
}
if (clinit != null && clinit.getDefinition().getOptimizationInfo().mayHaveSideEffects()) {
@@ -1917,6 +1935,57 @@
return true;
}
+ /**
+ * Marks the interface as initialized directly and promotes the interface initializer to being
+ * live if it isn't already.
+ */
+ private void markInterfaceInitializedDirectly(DexProgramClass clazz) {
+ ProgramMethod clinit = clazz.getProgramClassInitializer();
+ // Mark the interface as initialized directly.
+ KeepReasonWitness witness = graphReporter.reportReachableClassInitializer(clazz, clinit);
+ if (!directlyInitializedInterfaces.add(clazz, witness)) {
+ return;
+ }
+ // Promote the interface initializer to being live if it isn't already.
+ if (clinit == null || !clinit.getDefinition().getOptimizationInfo().mayHaveSideEffects()) {
+ return;
+ }
+ if (indirectlyInitializedInterfaces.contains(clazz)
+ && clazz.getMethodCollection().hasVirtualMethods(DexEncodedMethod::isDefaultMethod)) {
+ assert liveMethods.contains(clinit);
+ return;
+ }
+ markDirectStaticOrConstructorMethodAsLive(clinit, witness);
+ }
+
+ /**
+ * Marks the interface as initialized indirectly and promotes the interface initializer to being
+ * live if the interface has a default interface method and is not already live.
+ *
+ * @return true if the interface became initialized indirectly for the first time.
+ */
+ private boolean markInterfaceInitializedIndirectly(DexProgramClass clazz) {
+ ProgramMethod clinit = clazz.getProgramClassInitializer();
+ // Mark the interface as initialized indirectly.
+ KeepReasonWitness witness = graphReporter.reportReachableClassInitializer(clazz, clinit);
+ if (!indirectlyInitializedInterfaces.add(clazz, witness)) {
+ return false;
+ }
+ // Promote the interface initializer to being live if it has a default interface method and
+ // isn't already live.
+ if (clinit == null
+ || !clinit.getDefinition().getOptimizationInfo().mayHaveSideEffects()
+ || !clazz.getMethodCollection().hasVirtualMethods(DexEncodedMethod::isDefaultMethod)) {
+ return true;
+ }
+ if (directlyInitializedInterfaces.contains(clazz)) {
+ assert liveMethods.contains(clinit);
+ return true;
+ }
+ markDirectStaticOrConstructorMethodAsLive(clinit, witness);
+ return true;
+ }
+
// Package protected due to entry point from worklist.
void markNonStaticDirectMethodAsReachable(DexMethod method, KeepReason reason) {
handleInvokeOfDirectTarget(method, reason);
@@ -3174,13 +3243,12 @@
: missingTypes,
SetUtils.mapIdentityHashSet(liveTypes.getItems(), DexProgramClass::getType),
Collections.unmodifiableSet(instantiatedAppServices),
- Enqueuer.toSortedDescriptorSet(targetedMethods.getItems()),
+ Enqueuer.toDescriptorSet(targetedMethods.getItems()),
Collections.unmodifiableSet(failedResolutionTargets),
- ImmutableSortedSet.copyOf(DexMethod::slowCompareTo, bootstrapMethods),
- ImmutableSortedSet.copyOf(DexMethod::slowCompareTo, methodsTargetedByInvokeDynamic),
- ImmutableSortedSet.copyOf(
- DexMethod::slowCompareTo, virtualMethodsTargetedByInvokeDirect),
- toSortedDescriptorSet(liveMethods.getItems()),
+ Collections.unmodifiableSet(bootstrapMethods),
+ Collections.unmodifiableSet(methodsTargetedByInvokeDynamic),
+ Collections.unmodifiableSet(virtualMethodsTargetedByInvokeDirect),
+ toDescriptorSet(liveMethods.getItems()),
// Filter out library fields and pinned fields, because these are read by default.
fieldAccessInfoCollection,
methodAccessInfoCollection.build(),
@@ -3349,9 +3417,8 @@
}
private static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
- SortedSet<R> toSortedDescriptorSet(Set<D> set) {
- ImmutableSortedSet.Builder<R> builder =
- new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompareTo);
+ Set<R> toDescriptorSet(Set<D> set) {
+ ImmutableSet.Builder<R> builder = new ImmutableSet.Builder<>();
for (D item : set) {
builder.add(item.getReference());
}
@@ -3845,7 +3912,8 @@
return;
}
markTypeAsLive(clazz, KeepReason.reflectiveUseIn(method));
- if (identifierTypeLookupResult.isTypeInstantiatedFromUse(options)) {
+ if (clazz.canBeInstantiatedByNewInstance()
+ && identifierTypeLookupResult.isTypeInstantiatedFromUse(options)) {
workList.enqueueMarkInstantiatedAction(
clazz, null, InstantiationReason.REFLECTION, KeepReason.reflectiveUseIn(method));
if (clazz.hasDefaultInitializer()) {
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 5199435..7267bac 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -1970,12 +1970,12 @@
public boolean verifyKeptMethodsAreTargetedAndLive(AppInfoWithLiveness appInfo) {
noShrinking.forEachMethod(
reference -> {
- assert appInfo.targetedMethods.contains(reference)
+ assert appInfo.isTargetedMethod(reference)
: "Expected kept method `" + reference.toSourceString() + "` to be targeted";
DexEncodedMethod method =
appInfo.definitionForHolder(reference).lookupMethod(reference);
if (!method.isAbstract() && isKeptDirectlyOrIndirectly(method.holder(), appInfo)) {
- assert appInfo.liveMethods.contains(reference)
+ assert appInfo.isLiveMethod(reference)
: "Expected non-abstract kept method `"
+ reference.toSourceString()
+ "` to be live";
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 fe5a13a..2a72863 100644
--- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
@@ -284,7 +284,7 @@
|| appView.appInfo().isPinned(method.method)
// TODO(christofferqa): Remove the invariant that the graph lens should not
// modify any methods from the sets alwaysInline and noSideEffects.
- || appView.appInfo().alwaysInline.contains(method.method)
+ || appView.appInfo().isAlwaysInlineMethod(method.method)
|| appView.appInfo().noSideEffects.keySet().contains(method.method))) {
return MergeGroup.DONT_MERGE;
}
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 caade38..9d0b174 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -249,8 +249,7 @@
private boolean isAttributeReferencingPrunedItem(EnclosingMethodAttribute attr) {
AppInfoWithLiveness appInfo = appView.appInfo();
return (attr.getEnclosingClass() != null && !isTypeLive(attr.getEnclosingClass()))
- || (attr.getEnclosingMethod() != null
- && !appInfo.liveMethods.contains(attr.getEnclosingMethod()));
+ || (attr.getEnclosingMethod() != null && !appInfo.isLiveMethod(attr.getEnclosingMethod()));
}
private boolean isAttributeReferencingMissingOrPrunedType(InnerClassAttribute attr) {
@@ -279,7 +278,7 @@
AppInfoWithLiveness appInfo = appView.appInfo();
InternalOptions options = appView.options();
int firstUnreachable =
- firstUnreachableIndex(methods, method -> appInfo.liveMethods.contains(method.method));
+ firstUnreachableIndex(methods, method -> appInfo.isLiveMethod(method.method));
// Return the original array if all methods are used.
if (firstUnreachable == -1) {
return null;
@@ -290,7 +289,7 @@
}
for (int i = firstUnreachable; i < methods.size(); i++) {
DexEncodedMethod method = methods.get(i);
- if (appInfo.liveMethods.contains(method.getReference())) {
+ if (appInfo.isLiveMethod(method.getReference())) {
reachableMethods.add(method);
} else if (options.configurationDebugging) {
// Keep the method but rewrite its body, if it has one.
@@ -299,7 +298,7 @@
? method
: method.toMethodThatLogsError(appView));
methodsToKeepForConfigurationDebugging.add(method.method);
- } else if (appInfo.targetedMethods.contains(method.getReference())) {
+ } else if (appInfo.isTargetedMethod(method.getReference())) {
// If the method is already abstract, and doesn't have code, let it be.
if (method.shouldNotHaveCode() && !method.hasCode()) {
reachableMethods.add(method);
@@ -318,7 +317,7 @@
&& !method.isSynchronized()
&& !method.accessFlags.isPrivate()
&& !method.isStatic()
- && !appInfo.failedResolutionTargets.contains(method.method);
+ && !appInfo.isFailedResolutionTarget(method.method);
// Private methods and static methods can only be targeted yet non-live as the result of
// an invalid invoke. They will not actually be called at runtime but we have to keep them
// as non-abstract (see above) to produce the same failure mode.
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 9831468..a294f1d 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -292,12 +292,12 @@
// another default method in the same interface (see InterfaceMethodDesugaringTests.testInvoke-
// SpecialToDefaultMethod). However, in a class, that would lead to a verification error.
// Therefore, we disallow merging such interfaces into their subtypes.
- for (DexMethod signature : appInfo.virtualMethodsTargetedByInvokeDirect) {
+ for (DexMethod signature : appInfo.getVirtualMethodsTargetedByInvokeDirect()) {
markTypeAsPinned(signature.holder, AbortReason.UNHANDLED_INVOKE_DIRECT);
}
// The set of targets that must remain for proper resolution error cases should not be merged.
- for (DexMethod method : appInfo.failedResolutionTargets) {
+ for (DexMethod method : appInfo.getFailedResolutionTargets()) {
markTypeAsPinned(method.holder, AbortReason.RESOLUTION_FOR_METHODS_MAY_CHANGE);
}
}
@@ -1110,8 +1110,8 @@
target.forEachField(feedback::markFieldCannotBeKept);
target.forEachMethod(feedback::markMethodCannotBeKept);
// Step 3: Clear the members of the source class since they have now been moved to the target.
- source.setDirectMethods(null);
- source.setVirtualMethods(null);
+ source.getMethodCollection().clearDirectMethods();
+ source.getMethodCollection().clearVirtualMethods();
source.setInstanceFields(null);
source.setStaticFields(null);
// Step 4: Record merging.
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 dd85ad0..c74ed38 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -262,6 +262,8 @@
public boolean encodeChecksums = false;
public BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
public boolean cfToCfDesugar = false;
+ // TODO(b/172496438): Temporarily enable publicizing package-private overrides.
+ public boolean enablePackagePrivateAwarePublicization = false;
public int callGraphLikelySpuriousCallEdgeThreshold = 50;
@@ -1235,6 +1237,10 @@
|| enableGeneratedMessageLiteBuilderShrinking
|| enableEnumLiteProtoShrinking;
}
+
+ public boolean isProtoEnumShrinkingEnabled() {
+ return enableEnumLiteProtoShrinking;
+ }
}
public static class TestingOptions {
diff --git a/src/main/java/com/android/tools/r8/utils/ObjectUtils.java b/src/main/java/com/android/tools/r8/utils/ObjectUtils.java
index ac07151..1f3ad9d 100644
--- a/src/main/java/com/android/tools/r8/utils/ObjectUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ObjectUtils.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.utils;
+import java.util.function.Function;
import java.util.function.Predicate;
public class ObjectUtils {
@@ -14,4 +15,11 @@
}
return orElse;
}
+
+ public static <S, T> T mapNotNull(S object, Function<? super S, ? extends T> fn) {
+ if (object != null) {
+ return fn.apply(object);
+ }
+ return null;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureSet.java b/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureSet.java
new file mode 100644
index 0000000..5a4bc89
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureSet.java
@@ -0,0 +1,95 @@
+// Copyright (c) 2020, 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.utils.collections;
+
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodSignature;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.function.Function;
+
+public class DexMethodSignatureSet implements Iterable<DexMethodSignature> {
+
+ private final Set<DexMethodSignature> backing;
+
+ private DexMethodSignatureSet(Set<DexMethodSignature> backing) {
+ this.backing = backing;
+ }
+
+ public static DexMethodSignatureSet create() {
+ return new DexMethodSignatureSet(new HashSet<>());
+ }
+
+ public static DexMethodSignatureSet create(DexMethodSignatureSet collection) {
+ return new DexMethodSignatureSet(new HashSet<>(collection.backing));
+ }
+
+ public static DexMethodSignatureSet createLinked() {
+ return new DexMethodSignatureSet(new LinkedHashSet<>());
+ }
+
+ public boolean add(DexMethodSignature signature) {
+ return backing.add(signature);
+ }
+
+ public boolean add(DexMethod method) {
+ return add(method.getSignature());
+ }
+
+ public boolean add(DexEncodedMethod method) {
+ return add(method.getReference());
+ }
+
+ public boolean add(DexClassAndMethod method) {
+ return add(method.getReference());
+ }
+
+ public void addAll(Iterable<DexMethodSignature> signatures) {
+ signatures.forEach(this::add);
+ }
+
+ public void addAllMethods(Iterable<DexEncodedMethod> methods) {
+ methods.forEach(this::add);
+ }
+
+ public void addAll(DexMethodSignatureSet signatures) {
+ addAll(signatures.backing);
+ }
+
+ public <T> void addAll(Iterable<T> elements, Function<T, Iterable<DexMethodSignature>> fn) {
+ for (T element : elements) {
+ addAll(fn.apply(element));
+ }
+ }
+
+ public boolean contains(DexMethodSignature signature) {
+ return backing.contains(signature);
+ }
+
+ @Override
+ public Iterator<DexMethodSignature> iterator() {
+ return backing.iterator();
+ }
+
+ public boolean remove(DexMethodSignature signature) {
+ return backing.remove(signature);
+ }
+
+ public boolean remove(DexEncodedMethod method) {
+ return remove(method.getSignature());
+ }
+
+ public void removeAll(Iterable<DexMethodSignature> signatures) {
+ signatures.forEach(this::remove);
+ }
+
+ public void removeAllMethods(Iterable<DexEncodedMethod> methods) {
+ methods.forEach(this::remove);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index 84b6675..726136f 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
import com.android.tools.r8.ir.desugar.LambdaRewriter;
import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
+import com.android.tools.r8.synthesis.SyntheticItems;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DescriptorUtils;
@@ -67,7 +68,7 @@
List<String> classFiles = collectClassFiles(testJarFile);
for (String classFile : classFiles) {
AndroidApp app =
- compileClassFiles(
+ compileClassFilesInIntermediate(
testJarFile, Collections.singletonList(classFile), null, OutputMode.DexIndexed);
assert app.getDexProgramResourcesForTesting().size() == 1;
fileToResource.put(
@@ -83,7 +84,8 @@
TreeMap<String, ProgramResource> fileToResource = new TreeMap<>();
List<String> classFiles = collectClassFiles(testJarFile);
AndroidApp app =
- compileClassFiles(testJarFile, classFiles, output, OutputMode.DexFilePerClassFile);
+ compileClassFilesInIntermediate(
+ testJarFile, classFiles, output, OutputMode.DexFilePerClassFile);
for (ProgramResource resource : app.getDexProgramResourcesForTesting()) {
Set<String> descriptors = resource.getClassDescriptors();
String mainClassDescriptor = app.getPrimaryClassDescriptor(resource);
@@ -91,11 +93,13 @@
for (String descriptor : descriptors) {
// classes are either lambda classes used by the main class, companion classes of the main
// interface, the main class/interface, or for JDK9, desugaring of try-with-resources.
- Assert.assertTrue(descriptor.contains(LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX)
- || descriptor.endsWith(InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX + ";")
- || descriptor.endsWith(InterfaceMethodRewriter.DISPATCH_CLASS_NAME_SUFFIX + ";")
- || descriptor.equals(TwrCloseResourceRewriter.UTILITY_CLASS_DESCRIPTOR)
- || descriptor.equals(mainClassDescriptor));
+ Assert.assertTrue(
+ descriptor.contains(LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX)
+ || descriptor.endsWith(InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX + ";")
+ || descriptor.endsWith(InterfaceMethodRewriter.DISPATCH_CLASS_NAME_SUFFIX + ";")
+ || descriptor.equals(TwrCloseResourceRewriter.UTILITY_CLASS_DESCRIPTOR)
+ || descriptor.contains(SyntheticItems.EXTERNAL_SYNTHETIC_CLASS_SEPARATOR)
+ || descriptor.equals(mainClassDescriptor));
}
String classDescriptor =
DescriptorUtils.getClassBinaryNameFromDescriptor(mainClassDescriptor);
@@ -159,7 +163,7 @@
}
}
- AndroidApp compileClassFiles(
+ AndroidApp compileClassFilesInIntermediate(
Path testJarFile, List<String> inputFiles, Path outputPath, OutputMode outputMode)
throws Throwable {
D8Command.Builder builder = D8Command.builder();
@@ -179,6 +183,7 @@
} else {
throw new Unreachable("Unexpected output mode " + outputMode);
}
+ builder.setIntermediate(true);
addLibraryReference(builder, ToolHelper.getAndroidJar(
androidJarVersion == null ? builder.getMinApiLevel() : androidJarVersion.getLevel()));
try {
@@ -312,7 +317,6 @@
// TODO(b/123504206): This test throws an index out of bounds exception.
// Re-write or verify running fails in the expected way.
-
Assert.assertArrayEquals(
readResource(mergedFromCompiledSeparately.getDexProgramResourcesForTesting().get(0)),
readResource(mergedFromCompiledTogether.getDexProgramResourcesForTesting().get(0)));
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 1b417b3..a8b5af7 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1682,6 +1682,13 @@
return AndroidApiLevel.N;
}
+ public static boolean hasDefaultInterfaceMethodsSupport(TestParameters parameters) {
+ return parameters.isCfRuntime()
+ || parameters
+ .getApiLevel()
+ .isGreaterThanOrEqualTo(apiLevelWithDefaultInterfaceMethodsSupport());
+ }
+
public static AndroidApiLevel apiLevelWithStaticInterfaceMethodsSupport() {
return AndroidApiLevel.N;
}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/PackagePrivateOverridePublicizerTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/PackagePrivateOverridePublicizerTest.java
index ccd0b21..630ff62 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/PackagePrivateOverridePublicizerTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/PackagePrivateOverridePublicizerTest.java
@@ -50,6 +50,10 @@
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.allowAccessModification()
+ .addOptionsModification(
+ options -> {
+ options.enablePackagePrivateAwarePublicization = true;
+ })
.run(parameters.getRuntime(), Main.class)
.apply(this::assertSuccessOutput);
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoInterfacesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentInterfacesTest.java
similarity index 70%
copy from src/test/java/com/android/tools/r8/classmerging/horizontal/NoInterfacesTest.java
copy to src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentInterfacesTest.java
index f286bfd..d1333a3 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoInterfacesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentInterfacesTest.java
@@ -9,11 +9,14 @@
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
import org.junit.Test;
-public class NoInterfacesTest extends HorizontalClassMergingTestBase {
- public NoInterfacesTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
+public class ClassesWithDifferentInterfacesTest extends HorizontalClassMergingTestBase {
+ public ClassesWithDifferentInterfacesTest(
+ TestParameters parameters, boolean enableHorizontalClassMerging) {
super(parameters, enableHorizontalClassMerging);
}
@@ -26,9 +29,12 @@
options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
+ .addHorizontallyMergedClassesInspector(
+ HorizontallyMergedClassesInspector::assertNoClassesMerged)
.run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutputLines("bar", "foo y", "foo z")
+ .assertSuccessWithOutputLines("bar", "foo y", "bar")
.inspect(
codeInspector -> {
assertThat(codeInspector.clazz(I.class), isPresent());
@@ -38,20 +44,26 @@
});
}
+ @NoVerticalClassMerging
public interface I {
void foo();
}
+ @NoVerticalClassMerging
+ public interface J {
+ void bar();
+ }
+
@NeverClassInline
public static class X {
@NeverInline
- public static void bar() {
+ public void bar() {
System.out.println("bar");
}
}
@NeverClassInline
- public static class Y implements I {
+ public static class Y extends X implements I {
@NeverInline
@Override
public void foo() {
@@ -60,9 +72,8 @@
}
@NeverClassInline
- public static class Z implements I {
+ public static class Z extends X implements J {
@NeverInline
- @Override
public void foo() {
System.out.println("foo z");
}
@@ -74,13 +85,18 @@
i.foo();
}
+ @NeverInline
+ public static void bar(J j) {
+ j.bar();
+ }
+
public static void main(String[] args) {
X x = new X();
x.bar();
Y y = new Y();
Z z = new Z();
foo(y);
- foo(z);
+ bar(z);
}
}
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoInterfacesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithIdenticalInterfacesTest.java
similarity index 79%
rename from src/test/java/com/android/tools/r8/classmerging/horizontal/NoInterfacesTest.java
rename to src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithIdenticalInterfacesTest.java
index f286bfd..456d29e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoInterfacesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithIdenticalInterfacesTest.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.classmerging.horizontal;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.NeverClassInline;
@@ -12,8 +13,9 @@
import com.android.tools.r8.TestParameters;
import org.junit.Test;
-public class NoInterfacesTest extends HorizontalClassMergingTestBase {
- public NoInterfacesTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
+public class ClassesWithIdenticalInterfacesTest extends HorizontalClassMergingTestBase {
+ public ClassesWithIdenticalInterfacesTest(
+ TestParameters parameters, boolean enableHorizontalClassMerging) {
super(parameters, enableHorizontalClassMerging);
}
@@ -27,6 +29,8 @@
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.setMinApi(parameters.getApiLevel())
+ .addHorizontallyMergedClassesInspector(
+ inspector -> inspector.assertMergedInto(Z.class, Y.class))
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("bar", "foo y", "foo z")
.inspect(
@@ -34,7 +38,8 @@
assertThat(codeInspector.clazz(I.class), isPresent());
assertThat(codeInspector.clazz(X.class), isPresent());
assertThat(codeInspector.clazz(Y.class), isPresent());
- assertThat(codeInspector.clazz(Z.class), isPresent());
+ assertThat(
+ codeInspector.clazz(Z.class), notIf(isPresent(), enableHorizontalClassMerging));
});
}
@@ -45,7 +50,7 @@
@NeverClassInline
public static class X {
@NeverInline
- public static void bar() {
+ public void bar() {
System.out.println("bar");
}
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/InheritInterfaceWithDefaultTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/InheritInterfaceWithDefaultTest.java
new file mode 100644
index 0000000..9bdaca6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/InheritInterfaceWithDefaultTest.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2020, 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.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.classmerging.horizontal.EmptyClassTest.A;
+import com.android.tools.r8.classmerging.horizontal.EmptyClassTest.B;
+import com.android.tools.r8.classmerging.horizontal.EmptyClassTest.Main;
+import org.junit.Test;
+
+public class InheritInterfaceWithDefaultTest extends HorizontalClassMergingTestBase {
+
+ public InheritInterfaceWithDefaultTest(
+ TestParameters parameters, boolean enableHorizontalClassMerging) {
+ super(parameters, enableHorizontalClassMerging);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .allowStdoutMessages()
+ .addOptionsModification(
+ options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .addHorizontallyMergedClassesInspectorIf(
+ enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(
+ "print interface", "print interface", "print interface", "print interface")
+ .inspect(
+ codeInspector -> {
+ assertThat(codeInspector.clazz(A.class), isPresent());
+ assertThat(
+ codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+ });
+ }
+
+ public interface Interface {
+ @NeverInline
+ default void print() {
+ System.out.println("print interface");
+ }
+ }
+
+ @NeverClassInline
+ public static class A implements Interface {}
+
+ @NeverClassInline
+ public static class B implements Interface {}
+
+ public static class Main {
+
+ @NeverInline
+ public static void print(Interface i) {
+ i.print();
+ }
+
+ public static void main(String[] args) {
+ new A().print();
+ new B().print();
+ print(new A());
+ print(new B());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/InheritOverrideInterfaceTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/InheritOverrideInterfaceTest.java
new file mode 100644
index 0000000..cedee87
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/InheritOverrideInterfaceTest.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2020, 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.classmerging.horizontal;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.classmerging.horizontal.EmptyClassTest.A;
+import com.android.tools.r8.classmerging.horizontal.EmptyClassTest.B;
+import com.android.tools.r8.classmerging.horizontal.EmptyClassTest.Main;
+import org.junit.Test;
+
+public class InheritOverrideInterfaceTest extends HorizontalClassMergingTestBase {
+ public InheritOverrideInterfaceTest(
+ TestParameters parameters, boolean enableHorizontalClassMerging) {
+ super(parameters, enableHorizontalClassMerging);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+ .enableInliningAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .addHorizontallyMergedClassesInspectorIf(
+ enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A", "B", "A")
+ .inspect(codeInspector -> {});
+ }
+
+ @NoVerticalClassMerging
+ interface I {
+ @NeverInline
+ default void m() {
+ System.out.println("I");
+ }
+ }
+
+ public static class A implements I {
+ @NeverInline
+ @Override
+ public void m() {
+ System.out.println("A");
+ }
+ }
+
+ public static class B implements I {
+ @NeverInline
+ @Override
+ public void m() {
+ System.out.println("B");
+ }
+ }
+
+ @NoVerticalClassMerging
+ interface J extends I {
+ default void m() {
+ System.out.println("J");
+ }
+ }
+
+ public static class C extends A implements J {}
+
+ public static class Main {
+ @NeverInline
+ public static void doI(I i) {
+ i.m();
+ }
+
+ public static void main(String[] args) {
+ doI(new A());
+ doI(new B());
+ doI(new C());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedSuperMethodIsDefaultMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedSuperMethodIsDefaultMethodTest.java
deleted file mode 100644
index 483e702..0000000
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedSuperMethodIsDefaultMethodTest.java
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright (c) 2020, 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.classmerging.horizontal;
-
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NoVerticalClassMerging;
-import com.android.tools.r8.TestParameters;
-import java.io.IOException;
-import java.util.concurrent.ExecutionException;
-import org.junit.Test;
-
-public class MergedSuperMethodIsDefaultMethodTest extends HorizontalClassMergingTestBase {
- public MergedSuperMethodIsDefaultMethodTest(
- TestParameters parameters, boolean enableHorizontalClassMerging) {
- super(parameters, enableHorizontalClassMerging);
- }
-
- @Test
- public void testR8() throws IOException, CompilationFailedException, ExecutionException {
- testForR8(parameters.getBackend())
- .addInnerClasses(this.getClass())
- .addOptionsModification(
- options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
- .enableNoVerticalClassMergingAnnotations()
- .enableNeverClassInliningAnnotations()
- .enableInliningAnnotations()
- .addKeepMainRule(Main.class)
- .setMinApi(parameters.getApiLevel())
- .run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutputLines("I.foo")
- .inspect(
- codeInspector -> {
- assertThat(codeInspector.clazz(I.class), isPresent());
- assertThat(codeInspector.clazz(A.class), isPresent());
- assertThat(codeInspector.clazz(B.class), isPresent());
- assertThat(
- codeInspector.clazz(C.class), notIf(isPresent(), enableHorizontalClassMerging));
- });
- }
-
- @NoVerticalClassMerging
- public interface I {
- @NeverInline
- default void foo() {
- System.out.println("I.foo");
- }
- }
-
- @NoVerticalClassMerging
- public abstract static class A implements I {}
-
- @NeverClassInline
- public static class B extends A {
-
- @Override
- @NeverInline
- public void foo() {
- System.out.println("B.foo");
- }
- }
-
- @NeverClassInline
- public static class C extends A {}
-
- public static class Main {
-
- public static void main(String[] args) {
- callA(args.length == 0 ? new C() : new B());
- }
-
- @NeverInline
- private static void callA(A a) {
- a.foo();
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PrivateAndInterfaceMethodCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PrivateAndInterfaceMethodCollisionTest.java
index 3b957c4..af37199 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/PrivateAndInterfaceMethodCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PrivateAndInterfaceMethodCollisionTest.java
@@ -9,7 +9,7 @@
import com.android.tools.r8.NoUnusedInterfaceRemoval;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
import org.junit.Test;
public class PrivateAndInterfaceMethodCollisionTest extends HorizontalClassMergingTestBase {
@@ -21,18 +21,13 @@
@Test
public void test() throws Exception {
- // TODO(b/167981556): Should always succeed.
- boolean expectedToSucceed =
- !enableHorizontalClassMerging
- || parameters.isCfRuntime(CfVm.JDK11)
- || parameters.isDexRuntime();
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
.addOptionsModification(
options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
- .addHorizontallyMergedClassesInspectorIf(
- enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
+ .addHorizontallyMergedClassesInspector(
+ HorizontallyMergedClassesInspector::assertNoClassesMerged)
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoUnusedInterfaceRemovalAnnotations()
@@ -40,8 +35,7 @@
.setMinApi(parameters.getApiLevel())
.compile()
.run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutputLinesIf(expectedToSucceed, "A.foo()", "B.bar()", "J.foo()")
- .assertFailureWithErrorThatThrowsIf(!expectedToSucceed, IllegalAccessError.class);
+ .assertSuccessWithOutputLines("A.foo()", "B.bar()", "J.foo()");
}
static class Main {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndInterfaceMethodCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndInterfaceMethodCollisionTest.java
index 356acd7..ebbfd86 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndInterfaceMethodCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndInterfaceMethodCollisionTest.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.NoUnusedInterfaceRemoval;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
import org.junit.Test;
public class StaticAndInterfaceMethodCollisionTest extends HorizontalClassMergingTestBase {
@@ -25,8 +26,8 @@
.addKeepMainRule(Main.class)
.addOptionsModification(
options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
- .addHorizontallyMergedClassesInspectorIf(
- enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
+ .addHorizontallyMergedClassesInspector(
+ HorizontallyMergedClassesInspector::assertNoClassesMerged)
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoUnusedInterfaceRemovalAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndVirtualMethodCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndVirtualMethodCollisionTest.java
index 52b506f..edca180 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndVirtualMethodCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndVirtualMethodCollisionTest.java
@@ -4,9 +4,6 @@
package com.android.tools.r8.classmerging.horizontal;
-import static com.android.tools.r8.utils.codeinspector.AssertUtils.assertFailsCompilationIf;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
@@ -22,24 +19,18 @@
@Test
public void test() throws Exception {
- // TODO(b/172415620): Handle static/virtual method collisions.
- assertFailsCompilationIf(
- enableHorizontalClassMerging,
- () ->
- testForR8(parameters.getBackend())
- .addInnerClasses(getClass())
- .addKeepMainRule(Main.class)
- .addOptionsModification(
- options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
- .addHorizontallyMergedClassesInspectorIf(
- enableHorizontalClassMerging,
- inspector -> inspector.assertMergedInto(B.class, A.class))
- .enableInliningAnnotations()
- .enableNeverClassInliningAnnotations()
- .setMinApi(parameters.getApiLevel())
- .run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutputLines("A.foo()", "A.bar()", "B.foo()", "B.bar()"),
- e -> assertThat(e.getCause().getMessage(), containsString("Duplicate method")));
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+ .addHorizontallyMergedClassesInspectorIf(
+ enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A.foo()", "A.bar()", "B.foo()", "B.bar()");
}
static class Main {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerSubClassCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerSubClassCollisionTest.java
index e5c6561..aad0efa 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerSubClassCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerSubClassCollisionTest.java
@@ -36,7 +36,8 @@
.enableNoHorizontalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutputLines("print a: foo c a", "print b: foo c b", "print b: foo d b")
+ .assertSuccessWithOutputLines(
+ "print a: foo c a", "print b: foo c b", "print b: foo c b", "print b: foo d b")
.inspect(
codeInspector -> {
assertThat(codeInspector.clazz(A.class), isPresent());
@@ -45,15 +46,19 @@
ClassSubject cClassSubject = codeInspector.clazz(C.class);
assertThat(cClassSubject, isPresent());
- // C#foo(B) is renamed to C#foo$2(A) to not collide with D#foo$1(A).
+ // C#foo(B) is renamed to C#foo$1(A).
if (enableHorizontalClassMerging) {
assertThat(cClassSubject.uniqueMethodWithFinalName("foo"), isPresent());
- assertThat(cClassSubject.uniqueMethodWithFinalName("foo$2"), isPresent());
+ assertThat(cClassSubject.uniqueMethodWithFinalName("foo$1"), isPresent());
}
ClassSubject dClassSubject = codeInspector.clazz(D.class);
assertThat(dClassSubject, isPresent());
- assertThat(dClassSubject.uniqueMethodWithFinalName("foo$1"), isPresent());
+ // D#foo$1(B) is renamed to D#foo$2(A).
+ assertThat(
+ dClassSubject.uniqueMethodWithFinalName(
+ enableHorizontalClassMerging ? "foo$1$1" : "foo$1"),
+ isPresent());
});
}
@@ -104,6 +109,7 @@
c.foo(a);
c.foo(b);
D d = new D();
+ d.foo(b);
d.foo$1(b);
}
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodMergingOfPublicizedMethodsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodMergingOfPublicizedMethodsTest.java
index 5df1264..8178138 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodMergingOfPublicizedMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodMergingOfPublicizedMethodsTest.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.classmerging.horizontal;
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestParameters;
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodNotOverlappingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/NotOverlappingTest.java
similarity index 86%
rename from src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodNotOverlappingTest.java
rename to src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/NotOverlappingTest.java
index c80c1ef..8742e67 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodNotOverlappingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/NotOverlappingTest.java
@@ -2,18 +2,18 @@
// 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.classmerging.horizontal;
+package com.android.tools.r8.classmerging.horizontal.dispatch;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsNot.not;
import com.android.tools.r8.*;
+import com.android.tools.r8.classmerging.horizontal.HorizontalClassMergingTestBase;
import org.junit.Test;
-public class VirtualMethodNotOverlappingTest extends HorizontalClassMergingTestBase {
- public VirtualMethodNotOverlappingTest(
- TestParameters parameters, boolean enableHorizontalClassMerging) {
+public class NotOverlappingTest extends HorizontalClassMergingTestBase {
+ public NotOverlappingTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
super(parameters, enableHorizontalClassMerging);
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java
new file mode 100644
index 0000000..87f4dc3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2020, 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.classmerging.horizontal.dispatch;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.classmerging.horizontal.HorizontalClassMergingTestBase;
+import org.junit.Test;
+
+public class OverrideAbstractMethodWithDefaultTest extends HorizontalClassMergingTestBase {
+ public OverrideAbstractMethodWithDefaultTest(
+ TestParameters parameters, boolean enableHorizontalClassMerging) {
+ super(parameters, enableHorizontalClassMerging);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+ .enableInliningAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .addHorizontallyMergedClassesInspectorIf(
+ enableHorizontalClassMerging, inspector -> inspector.assertNoClassesMerged())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("J", "B2")
+ .inspect(
+ codeInspector -> {
+ assertThat(codeInspector.clazz(I.class), isPresent());
+ assertThat(codeInspector.clazz(J.class), isPresent());
+ assertThat(codeInspector.clazz(A.class), isPresent());
+ assertThat(codeInspector.clazz(B1.class), isPresent());
+ assertThat(codeInspector.clazz(B2.class), isPresent());
+ assertThat(codeInspector.clazz(C1.class), isPresent());
+ assertThat(codeInspector.clazz(C2.class), isPresent());
+ });
+ }
+
+ @NoVerticalClassMerging
+ interface I {
+ void m();
+ }
+
+ @NoVerticalClassMerging
+ interface J extends I {
+ default void m() {
+ System.out.println("J");
+ }
+ }
+
+ abstract static class A implements I {}
+
+ @NoVerticalClassMerging
+ abstract static class B1 extends A {}
+
+ @NoVerticalClassMerging
+ abstract static class B2 extends A {
+ @Override
+ @NeverInline
+ public void m() {
+ System.out.println("B2");
+ }
+ }
+
+ static class C1 extends B1 implements J {}
+
+ static class C2 extends B2 {}
+
+ static class Main {
+ @NeverInline
+ public static void doI(I i) {
+ i.m();
+ }
+
+ public static void main(String[] args) {
+ doI(new C1());
+ doI(new C2());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java
new file mode 100644
index 0000000..dd8183d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java
@@ -0,0 +1,85 @@
+// Copyright (c) 2020, 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.classmerging.horizontal.dispatch;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.classmerging.horizontal.HorizontalClassMergingTestBase;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import org.junit.Test;
+
+public class OverrideDefaultMethodTest extends HorizontalClassMergingTestBase {
+ public OverrideDefaultMethodTest(
+ TestParameters parameters, boolean enableHorizontalClassMerging) {
+ super(parameters, enableHorizontalClassMerging);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+ .enableInliningAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .addHorizontallyMergedClassesInspectorIf(
+ enableHorizontalClassMerging, HorizontallyMergedClassesInspector::assertNoClassesMerged)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("I", "B", "J")
+ .inspect(
+ codeInspector -> {
+ assertThat(codeInspector.clazz(I.class), isPresent());
+ assertThat(codeInspector.clazz(J.class), isPresent());
+ assertThat(codeInspector.clazz(A.class), isPresent());
+ assertThat(codeInspector.clazz(B.class), isPresent());
+ assertThat(codeInspector.clazz(C.class), isPresent());
+ });
+ }
+
+ interface I {
+ @NeverInline
+ default void m() {
+ System.out.println("I");
+ }
+ }
+
+ public static class A implements I {}
+
+ public static class B implements I {
+ @NeverInline
+ @Override
+ public void m() {
+ System.out.println("B");
+ }
+ }
+
+ @NoVerticalClassMerging
+ interface J extends I {
+ default void m() {
+ System.out.println("J");
+ }
+ }
+
+ public static class C extends A implements J {}
+
+ public static class Main {
+ @NeverInline
+ public static void doI(I i) {
+ i.m();
+ }
+
+ public static void main(String[] args) {
+ doI(new A());
+ doI(new B());
+ doI(new C());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
new file mode 100644
index 0000000..bd41f8f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
@@ -0,0 +1,92 @@
+// Copyright (c) 2020, 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.classmerging.horizontal.dispatch;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.classmerging.horizontal.HorizontalClassMergingTestBase;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import org.junit.Test;
+
+public class OverrideDefaultOnSuperMethodTest extends HorizontalClassMergingTestBase {
+ public OverrideDefaultOnSuperMethodTest(
+ TestParameters parameters, boolean enableHorizontalClassMerging) {
+ super(parameters, enableHorizontalClassMerging);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+ .enableInliningAnnotations()
+ .enableNoUnusedInterfaceRemovalAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .addHorizontallyMergedClassesInspectorIf(
+ enableHorizontalClassMerging, HorizontallyMergedClassesInspector::assertNoClassesMerged)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("I", "B", "J")
+ .inspect(
+ codeInspector -> {
+ assertThat(codeInspector.clazz(I.class), isPresent());
+ assertThat(codeInspector.clazz(J.class), isPresent());
+ assertThat(codeInspector.clazz(Parent.class), isPresent());
+ assertThat(codeInspector.clazz(A.class), isPresent());
+ assertThat(codeInspector.clazz(B.class), isPresent());
+ assertThat(codeInspector.clazz(C.class), isPresent());
+ });
+ }
+
+ @NoVerticalClassMerging
+ interface I {
+ @NeverInline
+ default void m() {
+ System.out.println("I");
+ }
+ }
+
+ public static class Parent implements I {}
+
+ public static class A extends Parent {}
+
+ public static class B extends Parent {
+ @NeverInline
+ @Override
+ public void m() {
+ System.out.println("B");
+ }
+ }
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface J extends I {
+ default void m() {
+ System.out.println("J");
+ }
+ }
+
+ public static class C extends A implements J {}
+
+ public static class Main {
+ @NeverInline
+ public static void doI(I i) {
+ i.m();
+ }
+
+ public static void main(String[] args) {
+ doI(new A());
+ doI(new B());
+ doI(new C());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideMergeAbsentTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideMergeAbsentTest.java
new file mode 100644
index 0000000..fe0bc23
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideMergeAbsentTest.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2020, 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.classmerging.horizontal.dispatch;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.classmerging.horizontal.HorizontalClassMergingTestBase;
+import org.junit.Test;
+
+public class OverrideMergeAbsentTest extends HorizontalClassMergingTestBase {
+ public OverrideMergeAbsentTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
+ super(parameters, enableHorizontalClassMerging);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .addHorizontallyMergedClassesInspectorIf(
+ enableHorizontalClassMerging, inspector -> inspector.assertNoClassesMerged())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A", "B", "A", "J")
+ .inspect(
+ codeInspector -> {
+ assertThat(codeInspector.clazz(J.class), isPresent());
+ assertThat(codeInspector.clazz(A.class), isPresent());
+ assertThat(codeInspector.clazz(B.class), isPresent());
+ assertThat(codeInspector.clazz(C.class), isPresent());
+ });
+ }
+
+ @NeverClassInline
+ public static class A {
+ public A() {
+ System.out.println("A");
+ }
+ }
+
+ @NeverClassInline
+ public static class B {
+ @NeverInline
+ public void m() {
+ System.out.println("B");
+ }
+ }
+
+ @NoVerticalClassMerging
+ interface J {
+ @NeverInline
+ default void m() {
+ System.out.println("J");
+ }
+ }
+
+ @NeverClassInline
+ public static class C extends A implements J {}
+
+ public static class Main {
+ @NeverInline
+ public static void doI(J i) {
+ i.m();
+ }
+
+ public static void main(String[] args) {
+ new A();
+ new B().m();
+ doI(new C());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodOverrideParentCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideParentCollisionTest.java
similarity index 92%
rename from src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodOverrideParentCollisionTest.java
rename to src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideParentCollisionTest.java
index e304a70..94b643a 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodOverrideParentCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideParentCollisionTest.java
@@ -2,7 +2,7 @@
// 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.shaking.horizontalclassmerging;
+package com.android.tools.r8.classmerging.horizontal.dispatch;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -14,9 +14,9 @@
import com.android.tools.r8.classmerging.horizontal.HorizontalClassMergingTestBase;
import org.junit.Test;
-public class VirtualMethodOverrideParentCollisionTest extends HorizontalClassMergingTestBase {
+public class OverrideParentCollisionTest extends HorizontalClassMergingTestBase {
- public VirtualMethodOverrideParentCollisionTest(
+ public OverrideParentCollisionTest(
TestParameters parameters, boolean enableHorizontalClassMerging) {
super(parameters, enableHorizontalClassMerging);
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodSuperMergedTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/SuperMethodMergedTest.java
similarity index 89%
rename from src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodSuperMergedTest.java
rename to src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/SuperMethodMergedTest.java
index 3d29697..228223f 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodSuperMergedTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/SuperMethodMergedTest.java
@@ -2,7 +2,7 @@
// 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.classmerging.horizontal;
+package com.android.tools.r8.classmerging.horizontal.dispatch;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -12,11 +12,11 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.classmerging.horizontal.HorizontalClassMergingTestBase;
import org.junit.Test;
-public class VirtualMethodSuperMergedTest extends HorizontalClassMergingTestBase {
- public VirtualMethodSuperMergedTest(
- TestParameters parameters, boolean enableHorizontalClassMerging) {
+public class SuperMethodMergedTest extends HorizontalClassMergingTestBase {
+ public SuperMethodMergedTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
super(parameters, enableHorizontalClassMerging);
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
index c7465ca..5856c44 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
@@ -24,15 +24,20 @@
import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.tracereferences.TraceReferences;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.function.Consumer;
public class DesugaredLibraryTestBase extends TestBase {
@@ -208,6 +213,50 @@
builder -> builder.setSupportAllCallbacksFromLibrary(supportAllCallbacksFromLibrary));
}
+ private Map<AndroidApiLevel, Path> desugaredLibraryClassFileCache = new HashMap<>();
+
+ // Build the desugared library in class file format.
+ public Path buildDesugaredLibraryClassFile(AndroidApiLevel apiLevel) throws Exception {
+ Path desugaredLib = desugaredLibraryClassFileCache.get(apiLevel);
+ if (desugaredLib != null) {
+ return desugaredLib;
+ }
+ desugaredLib = temp.newFolder().toPath().resolve("desugar_jdk_libs.jar");
+ L8Command.Builder l8Builder =
+ L8Command.builder()
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+ .addProgramFiles(ToolHelper.getDesugarJDKLibs())
+ .addProgramFiles(ToolHelper.DESUGAR_LIB_CONVERSIONS)
+ .setMode(CompilationMode.DEBUG)
+ .addDesugaredLibraryConfiguration(
+ StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
+ .setMinApiLevel(apiLevel.getLevel())
+ .setOutput(desugaredLib, OutputMode.ClassFile);
+ ToolHelper.runL8(l8Builder.build());
+ desugaredLibraryClassFileCache.put(apiLevel, desugaredLib);
+ return desugaredLib;
+ }
+
+ public String collectKeepRulesWithTraceReferences(
+ Path desugaredProgramClassFile, Path desugaredLibraryClassFile) throws Exception {
+ Path generatedKeepRules = temp.newFile().toPath();
+ TraceReferences.run(
+ "--format",
+ "keep",
+ "--lib",
+ ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(),
+ "--target",
+ desugaredLibraryClassFile.toString(),
+ "--source",
+ desugaredProgramClassFile.toString(),
+ "--output",
+ generatedKeepRules.toString(),
+ "--map-diagnostics",
+ "error",
+ "info");
+ return FileUtils.readTextFile(generatedKeepRules, Charsets.UTF_8);
+ }
+
public interface KeepRuleConsumer extends StringConsumer {
String get();
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
index 70b46ae..3e40117 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
@@ -7,21 +7,13 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.L8Command;
import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.StringResource;
import com.android.tools.r8.TestCompileResult;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.tracereferences.TraceReferences;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.ThrowingSupplier;
import com.android.tools.r8.utils.codeinspector.CheckCastInstructionSubject;
@@ -32,13 +24,10 @@
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.android.tools.r8.utils.codeinspector.TryCatchSubject;
import com.android.tools.r8.utils.codeinspector.TypeSubject;
-import com.google.common.base.Charsets;
-import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableSet;
import java.nio.file.Path;
import java.util.List;
import java.util.Set;
-import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.junit.Assume;
import org.junit.Test;
@@ -131,46 +120,6 @@
assertEquals(expectedCatchGuards, foundCatchGuards);
}
- // Build the desugared library in class file format.
- private Path buildDesugaredLibraryClassFile() throws Exception {
- Path desugaredLib = temp.newFolder().toPath().resolve("desugar_jdk_libs.jar");
- L8Command.Builder l8Builder =
- L8Command.builder()
- .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
- .addProgramFiles(ToolHelper.getDesugarJDKLibs())
- .addProgramFiles(ToolHelper.DESUGAR_LIB_CONVERSIONS)
- .setMode(CompilationMode.DEBUG)
- .addDesugaredLibraryConfiguration(
- StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING))
- .setMinApiLevel(parameters.getApiLevel().getLevel())
- .setOutput(desugaredLib, OutputMode.ClassFile);
- ToolHelper.runL8(l8Builder.build());
- return desugaredLib;
- }
-
- Supplier<Path> desugaredLibraryClassFile =
- Suppliers.memoize(
- () -> {
- try {
- return buildDesugaredLibraryClassFile();
- } catch (Exception e) {
- fail("Unexpected");
- return null;
- }
- });
-
- private String collectKeepRulesWithTraceReferences(
- Path desugaredProgramClassFile, Path desugaredLibraryClassFile) throws Exception {
- Path generatedKeepRules = temp.newFile().toPath();
- TraceReferences.run(
- "--format", "keep",
- "--lib", ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(),
- "--target", desugaredLibraryClassFile.toString(),
- "--source", desugaredProgramClassFile.toString(),
- "--output", generatedKeepRules.toString());
- return FileUtils.readTextFile(generatedKeepRules, Charsets.UTF_8);
- }
-
private String desugaredLibraryKeepRules(
KeepRuleConsumer keepRuleConsumer, ThrowingSupplier<Path, Exception> programSupplier)
throws Exception {
@@ -181,7 +130,7 @@
if (traceReferencesKeepRules) {
desugaredLibraryKeepRules =
collectKeepRulesWithTraceReferences(
- programSupplier.get(), desugaredLibraryClassFile.get());
+ programSupplier.get(), buildDesugaredLibraryClassFile(parameters.getApiLevel()));
}
}
}
@@ -231,7 +180,7 @@
// Run on the JVM with desugared library on classpath.
testForJvm()
.addProgramFiles(jar)
- .addRunClasspathFiles(desugaredLibraryClassFile.get())
+ .addRunClasspathFiles(buildDesugaredLibraryClassFile(parameters.getApiLevel()))
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(expectedOutput);
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java
index 04630af..92d2394 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java
@@ -102,6 +102,37 @@
}
@Test
+ public void testCallBackD8Cf() throws Exception {
+ // Use D8 to desugar with Java classfile output.
+ Path jar =
+ testForD8(Backend.CF)
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(Impl.class)
+ .addLibraryClasses(CustomLibClass.class)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), new AbsentKeepRuleConsumer())
+ .compile()
+ .inspect(CallBackConversionTest::assertDuplicatedAPI)
+ .writeToZip();
+
+ // Convert to DEX without desugaring and run.
+ testForD8()
+ .addProgramFiles(jar)
+ .setMinApi(parameters.getApiLevel())
+ .disableDesugaring()
+ .compile()
+ .inspect(CallBackConversionTest::assertDuplicatedAPI)
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ collectKeepRulesWithTraceReferences(
+ jar, buildDesugaredLibraryClassFile(parameters.getApiLevel())),
+ shrinkDesugaredLibrary)
+ .addRunClasspathFiles(CUSTOM_LIB)
+ .run(parameters.getRuntime(), Impl.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
public void testCallBackR8() throws Exception {
KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
testForR8(Backend.DEX)
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/Regress171867367.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/Regress171867367.java
index 76366e4..6e01196 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/Regress171867367.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/Regress171867367.java
@@ -3,9 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.desugaring.interfacemethods;
-import static org.junit.Assert.assertThrows;
-
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.D8TestBuilder;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -48,11 +45,7 @@
.addAndroidBuildVersion()
.addProgramClasses(TestClass2.class)
.setMinApi(parameters.getApiLevel());
- if (parameters.getApiLevel().isLessThan(AndroidApiLevel.L)) {
- // TODO(b/171867367): Make us not fail when desugaring mixed (desugared and non desugared)
- // inputs introduce a synthetic already present in the program input.
- assertThrows(CompilationFailedException.class, d8TestBuilder::compile);
- } else if (parameters.getApiLevel().isLessThan(AndroidApiLevel.N)) {
+ if (parameters.getApiLevel().isLessThan(AndroidApiLevel.N)) {
d8TestBuilder
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutputLines("42", "45", "43");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithRefinedReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithRefinedReceiverTest.java
index d6bcd2b..b53f23c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithRefinedReceiverTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithRefinedReceiverTest.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -45,6 +46,7 @@
.addInnerClasses(InvokeInterfaceWithRefinedReceiverTest.class)
.addKeepMainRule(MAIN)
.enableNoVerticalClassMergingAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
.addOptionsModification(
@@ -149,6 +151,7 @@
}
}
+ @NoHorizontalClassMerging
@NoVerticalClassMerging
@NeverClassInline
static class C implements I {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java
index cb8dfec..9f25fdb 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -47,6 +48,7 @@
.addKeepMainRule(MAIN)
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.addOptionsModification(CallSiteOptimizationOptions::enableConstantPropagationForTesting)
.addOptionsModification(
o -> {
@@ -107,6 +109,7 @@
}
@NeverClassInline
+ @NoHorizontalClassMerging
static class B implements I {
@NeverInline
@Override
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java
index 4091161..5a06d91 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -44,6 +45,7 @@
testForR8(parameters.getBackend())
.addInnerClasses(InvokeInterfacePositiveTest.class)
.addKeepMainRule(MAIN)
+ .enableNoHorizontalClassMergingAnnotations()
.enableNoVerticalClassMergingAnnotations()
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
@@ -121,6 +123,7 @@
}
@NeverClassInline
+ @NoHorizontalClassMerging
static class B implements I {
@NeverInline
@Override
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfacePositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfacePositiveTest.java
index 27612a2..19fa2a1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfacePositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfacePositiveTest.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -44,6 +45,7 @@
.addKeepMainRule(MAIN)
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.addOptionsModification(
o -> {
// To prevent invoke-interface from being rewritten to invoke-virtual w/ a single
@@ -111,6 +113,7 @@
}
@NeverClassInline
+ @NoHorizontalClassMerging
static class B implements I {
@NeverInline
@Override
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/LibraryOverrideClassInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/LibraryOverrideClassInliningTest.java
index e3ed9c2..4f020fd 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/LibraryOverrideClassInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/LibraryOverrideClassInliningTest.java
@@ -9,6 +9,7 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -22,6 +23,7 @@
@RunWith(Parameterized.class)
public class LibraryOverrideClassInliningTest extends TestBase {
+ @NoHorizontalClassMerging
public static class SimpleLibraryOverride implements Runnable {
@NeverInline
@@ -90,6 +92,7 @@
.addProgramClasses(
Main.class, SimpleLibraryOverride.class, NonSimpleLibraryOverride.class)
.addKeepMainRule(Main.class)
+ .enableNoHorizontalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("running...", "Hello world!")
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/whyareyounotinlining/WhyAreYouNotInliningInvokeWithUnknownTargetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/whyareyounotinlining/WhyAreYouNotInliningInvokeWithUnknownTargetTest.java
index 9e862dd..1231e9b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/whyareyounotinlining/WhyAreYouNotInliningInvokeWithUnknownTargetTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/whyareyounotinlining/WhyAreYouNotInliningInvokeWithUnknownTargetTest.java
@@ -6,6 +6,7 @@
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.utils.StringUtils;
@@ -44,6 +45,7 @@
.addKeepRules("-whyareyounotinlining class " + A.class.getTypeName() + " { void m(); }")
.addOptionsModification(options -> options.testing.whyAreYouNotInliningConsumer = out)
.enableProguardTestOptions()
+ .enableNoHorizontalClassMergingAnnotations()
.compile();
out.close();
@@ -78,6 +80,7 @@
}
}
+ @NoHorizontalClassMerging
static class B implements I {
@Override
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
index 1452fd4..8dbe868 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
@@ -46,6 +46,7 @@
}
}
+ @NoHorizontalClassMerging
static class GetClassTestMain implements Callable<Class<?>> {
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InterfaceMethodTest.java
index 9fd146d..f489112 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InterfaceMethodTest.java
@@ -8,6 +8,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
@@ -48,6 +49,7 @@
.addKeepMainRule(TestClass.class)
.enableInliningAnnotations()
.enableNoVerticalClassMergingAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.run(TestClass.class)
.assertSuccessWithOutput(expectedOutput)
.inspector();
@@ -95,6 +97,7 @@
// The purpose of this class is merely to avoid that the invoke-interface instruction in
// TestClass.test() gets devirtualized to an invoke-virtual instruction. Otherwise the method
// I.m() would not be present in the output.
+ @NoHorizontalClassMerging
static class B implements I {
@Override
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalTest.java
index 413937e..9d11655 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalTest.java
@@ -9,6 +9,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -40,6 +41,7 @@
.addInnerClasses(UnusedInterfaceRemovalTest.class)
.addKeepMainRule(TestClass.class)
.enableNoVerticalClassMergingAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
.compile()
.inspect(this::inspect)
@@ -106,6 +108,7 @@
// To prevent that we detect a single target and start inlining or rewriting the signature in the
// invoke.
+ @NoHorizontalClassMerging
static class B implements K {
@Override
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/JStyleKotlinLambdaMergingWithEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/JStyleKotlinLambdaMergingWithEnumUnboxingTest.java
index eb7b242..1aef013 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/JStyleKotlinLambdaMergingWithEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/JStyleKotlinLambdaMergingWithEnumUnboxingTest.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -49,6 +50,7 @@
.addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(EnumUnboxingCandidate.class))
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
.compile()
.run(parameters.getRuntime(), Main.class)
@@ -111,6 +113,7 @@
}
@NeverClassInline
+ @NoHorizontalClassMerging
public static final class Lambda2 implements I {
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KStyleKotlinLambdaMergingWithEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KStyleKotlinLambdaMergingWithEnumUnboxingTest.java
index 61abad2..6edeebc 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KStyleKotlinLambdaMergingWithEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KStyleKotlinLambdaMergingWithEnumUnboxingTest.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -48,6 +49,7 @@
.addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(EnumUnboxingCandidate.class))
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.noMinification()
.setMinApi(parameters.getApiLevel())
.compile()
@@ -112,6 +114,7 @@
}
@NeverClassInline
+ @NoHorizontalClassMerging
public static final class Lambda2 extends kotlin.jvm.internal.Lambda<kotlin.Unit>
implements kotlin.jvm.functions.Function0<kotlin.Unit> {
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
index 2498467..9f51143 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
@@ -528,6 +528,7 @@
runTest(
"lambdas_singleton",
mainClassName,
+ "-nohorizontalclassmerging class *",
getOptionsModifier(),
app -> {
Verifier verifier = new Verifier(app);
diff --git a/src/test/java/com/android/tools/r8/naming/PackagePrivateAllowAccessModificationPackageTest.java b/src/test/java/com/android/tools/r8/naming/PackagePrivateAllowAccessModificationPackageTest.java
new file mode 100644
index 0000000..473df64
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/PackagePrivateAllowAccessModificationPackageTest.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2020, 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.naming;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PackagePrivateAllowAccessModificationPackageTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final String[] EXPECTED = new String[] {"B::foo", "ASub::foo", "A::foo"};
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public PackagePrivateAllowAccessModificationPackageTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(A.class, ASub.class, B.class, Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(A.class, ASub.class, B.class, Main.class)
+ .addKeepMainRule(Main.class)
+ .addKeepClassAndMembersRules(A.class)
+ .allowAccessModification()
+ .enableNoHorizontalClassMergingAnnotations()
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(
+ options -> {
+ options.enablePackagePrivateAwarePublicization = true;
+ })
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject clazz = inspector.clazz(ASub.class);
+ assertThat(clazz, isPresentAndRenamed());
+ })
+ .run(parameters.getRuntime(), Main.class)
+ // TODO(b/172496438): This should not fail.
+ .assertFailureWithErrorThatThrows(IllegalAccessError.class);
+ }
+
+ @NeverClassInline
+ public static class A {
+
+ @NeverInline
+ void foo() {
+ System.out.println("A::foo");
+ }
+ }
+
+ public static class ASub extends A {
+
+ @Override
+ void foo() {
+ System.out.println("ASub::foo");
+ super.foo();
+ }
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ public static class B {
+
+ @NeverInline
+ void foo() {
+ System.out.println("B::foo");
+ ((A) new ASub()).foo();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new B().foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b63935662/Regress63935662.java b/src/test/java/com/android/tools/r8/regress/b63935662/Regress63935662.java
index bc53ac7..1151d20 100644
--- a/src/test/java/com/android/tools/r8/regress/b63935662/Regress63935662.java
+++ b/src/test/java/com/android/tools/r8/regress/b63935662/Regress63935662.java
@@ -4,14 +4,12 @@
package com.android.tools.r8.regress.b63935662;
-import com.android.tools.r8.R8Command;
+import com.android.tools.r8.R8FullTestBuilder;
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.AndroidApp;
-import com.android.tools.r8.utils.OffOrAuto;
-import java.nio.file.Path;
-import org.junit.Assert;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -19,58 +17,63 @@
@RunWith(Parameterized.class)
public class Regress63935662 extends TestBase {
- private Backend backend;
+ private TestParameters parameters;
- @Parameterized.Parameters(name = "Backend: {0}")
- public static Backend[] data() {
- return ToolHelper.getBackends();
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
- public Regress63935662(Backend backend) {
- this.backend = backend;
+ public Regress63935662(TestParameters parameters) {
+ this.parameters = parameters;
}
- void run(AndroidApp app, Class mainClass) throws Exception {
- Path proguardConfig =
- writeTextToTempFile(keepMainProguardConfiguration(mainClass, true, false));
- R8Command.Builder builder =
- ToolHelper.prepareR8CommandBuilder(app, emptyConsumer(backend))
- .addLibraryFiles(runtimeJar(backend))
- .addProguardConfigurationFiles(proguardConfig);
- if (backend == Backend.DEX) {
- builder.setMinApiLevel(AndroidApiLevel.L.getLevel());
- }
- String resultFromJava = runOnJava(mainClass);
- app =
- ToolHelper.runR8(
- builder.build(), options -> options.interfaceMethodDesugaring = OffOrAuto.Auto);
- String result;
- if (backend == Backend.DEX) {
- result = runOnArt(app, mainClass);
- } else {
- assert backend == Backend.CF;
- result = runOnJava(app, mainClass);
- }
- Assert.assertEquals(resultFromJava, result);
+ void run(R8FullTestBuilder testBuilder, Class<?> mainClass) throws Exception {
+ testBuilder
+ .addKeepRuleFiles()
+ .allowAccessModification()
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), mainClass)
+ .assertSuccessWithOutput(runOnJava(mainClass));
}
@Test
public void test() throws Exception {
- Class mainClass = TestClass.class;
- AndroidApp app = readClasses(
- TestClass.Top.class, TestClass.Left.class, TestClass.Right.class, TestClass.Bottom.class,
- TestClass.X1.class, TestClass.X2.class, TestClass.X3.class, TestClass.X4.class, TestClass.X5.class,
+ Class<?> mainClass = TestClass.class;
+ List<Class<?>> app =
+ ImmutableList.of(
+ TestClass.Top.class,
+ TestClass.Left.class,
+ TestClass.Right.class,
+ TestClass.Bottom.class,
+ TestClass.X1.class,
+ TestClass.X2.class,
+ TestClass.X3.class,
+ TestClass.X4.class,
+ TestClass.X5.class,
+ mainClass);
+ run(
+ testForR8(parameters.getBackend())
+ .addProgramClasses(app)
+ .addKeepMainRule(mainClass)
+ .enableNoHorizontalClassMergingAnnotations(),
mainClass);
- run(app, mainClass);
}
@Test
public void test2() throws Exception {
- Class mainClass = TestFromBug.class;
- AndroidApp app = readClasses(
- TestFromBug.Map.class, TestFromBug.AbstractMap.class,
- TestFromBug.ConcurrentMap.class, TestFromBug.ConcurrentHashMap.class,
+ Class<?> mainClass = TestFromBug.class;
+ List<Class<?>> app =
+ ImmutableList.of(
+ TestFromBug.Map.class,
+ TestFromBug.AbstractMap.class,
+ TestFromBug.ConcurrentMap.class,
+ TestFromBug.ConcurrentHashMap.class,
+ mainClass);
+ run(
+ testForR8(parameters.getBackend()).addProgramClasses(app).addKeepMainRule(mainClass),
mainClass);
- run(app, mainClass);
}
}
diff --git a/src/test/java/com/android/tools/r8/regress/b63935662/TestClass.java b/src/test/java/com/android/tools/r8/regress/b63935662/TestClass.java
index 878fcff..6597695 100644
--- a/src/test/java/com/android/tools/r8/regress/b63935662/TestClass.java
+++ b/src/test/java/com/android/tools/r8/regress/b63935662/TestClass.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.regress.b63935662;
+import com.android.tools.r8.NoHorizontalClassMerging;
+
public class TestClass {
interface Top {
@@ -20,31 +22,36 @@
interface Bottom extends Left, Right {}
+ @NoHorizontalClassMerging
static class X1 implements Bottom {
void test() {
System.out.println(name());
}
}
- static class X2 implements Left, Right {
+ @NoHorizontalClassMerging
+ static class X2 implements Left, Right {
void test() {
System.out.println(name());
}
}
- static class X3 implements Right, Left {
+ @NoHorizontalClassMerging
+ static class X3 implements Right, Left {
void test() {
System.out.println(name());
}
}
- static class X4 implements Left, Right, Top {
+ @NoHorizontalClassMerging
+ static class X4 implements Left, Right, Top {
void test() {
System.out.println(name());
}
}
- static class X5 implements Right, Left, Top {
+ @NoHorizontalClassMerging
+ static class X5 implements Right, Left, Top {
void test() {
System.out.println(name());
}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java
index e29f3fc..3d20751 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java
@@ -14,12 +14,12 @@
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.TestRuntime.DexRuntime;
import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.transformers.ClassTransformer;
-import com.android.tools.r8.utils.AndroidApiLevel;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import org.hamcrest.CoreMatchers;
@@ -36,9 +36,6 @@
private final TestParameters parameters;
- static {
- }
-
@Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withAllRuntimesAndApiLevels().build();
@@ -83,9 +80,6 @@
if (dexRuntime.getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
return containsString("NoSuchMethodError");
}
- if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)) {
- return containsString("java.lang.ClassNotFoundException");
- }
return containsString("java.lang.VerifyError");
}
@@ -121,7 +115,8 @@
String descriptor,
String signature,
String[] exceptions) {
- return super.visitMethod(access, "<clinit>", descriptor, signature, exceptions);
+ return super.visitMethod(
+ access | Constants.ACC_STATIC, "<clinit>", descriptor, signature, exceptions);
}
})
.transform();
diff --git a/src/test/java/com/android/tools/r8/shaking/UninitializedInstantiatedTypeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/UninitializedInstantiatedTypeShakingTest.java
index 7a9be4a..3cf998b 100644
--- a/src/test/java/com/android/tools/r8/shaking/UninitializedInstantiatedTypeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/UninitializedInstantiatedTypeShakingTest.java
@@ -8,6 +8,7 @@
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -37,6 +38,7 @@
testForR8(parameters.getBackend())
.addInnerClasses(UninitializedInstantiatedTypeShakingTest.class)
.addKeepMainRule(TestClass.class)
+ .enableNoHorizontalClassMergingAnnotations()
.setMinApi(parameters.getRuntime())
.compile()
.inspect(UninitializedInstantiatedTypeShakingTest::verifyOutput)
@@ -83,6 +85,7 @@
}
}
+ @NoHorizontalClassMerging
static class B implements I {
@Override
@@ -91,6 +94,7 @@
}
}
+ @NoHorizontalClassMerging
static class C implements I {
@Override
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithoutMatchingDefinitionTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithoutMatchingDefinitionTest.java
index 8462593..e4edf08 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithoutMatchingDefinitionTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithoutMatchingDefinitionTest.java
@@ -9,6 +9,7 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -44,6 +45,7 @@
testForR8(parameters.getBackend())
.addInnerClasses(AssumenosideeffectsPropagationWithoutMatchingDefinitionTest.class)
.enableNoVerticalClassMergingAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.enableInliningAnnotations()
.addKeepMainRule(MAIN)
.addKeepRules(
@@ -99,6 +101,7 @@
}
// To bother single target resolution. In fact, not used at all.
+ @NoHorizontalClassMerging
static class AnotherLogger implements LoggerInterface {
@Override
public void debug(String message) {
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializationTriggersIndirectInterfaceInitializationTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializationTriggersIndirectInterfaceInitializationTest.java
new file mode 100644
index 0000000..c6e5eff
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializationTriggersIndirectInterfaceInitializationTest.java
@@ -0,0 +1,110 @@
+// Copyright (c) 2020, 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.shaking.clinit;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassInitializationTriggersIndirectInterfaceInitializationTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ClassInitializationTriggersIndirectInterfaceInitializationTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .enableNoUnusedInterfaceRemovalAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ // Verify that I's class initializer is still present.
+ ClassSubject iClassSubject = inspector.clazz(I.class);
+ assertThat(iClassSubject, isPresent());
+ assertThat(
+ iClassSubject.clinit(),
+ onlyIf(hasDefaultInterfaceMethodsSupport(parameters), isPresent()));
+
+ // Verify that J is still there.
+ ClassSubject jClassSubject = inspector.clazz(J.class);
+ assertThat(jClassSubject, isPresent());
+
+ // Verify that A still implements J.
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertEquals(1, aClassSubject.getDexProgramClass().getInterfaces().size());
+ assertTrue(aClassSubject.isImplementing(jClassSubject));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLinesIf(
+ hasDefaultInterfaceMethodsSupport(parameters), "I.<clinit>()", "I.m()")
+ .assertSuccessWithOutputLinesIf(!hasDefaultInterfaceMethodsSupport(parameters), "I.m()");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ new A().m();
+ }
+ }
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface I {
+
+ Greeter greeter = new Greeter("I.<clinit>()");
+
+ // TODO(b/144266257): This should not require a @NeverInline annotation, since tree shaking
+ // should not be allowed to remove the default interface method if that could change interface
+ // initialization side effects.
+ @NeverInline
+ default void m() {
+ System.out.println("I.m()");
+ }
+ }
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface J extends I {}
+
+ @NeverClassInline
+ static class A implements J {}
+
+ static class Greeter {
+
+ Greeter(String greeting) {
+ System.out.println(greeting);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByInvokeStaticOnSubInterfaceTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByInvokeStaticOnSubInterfaceTest.java
index 2e0f754..943e680 100644
--- a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByInvokeStaticOnSubInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByInvokeStaticOnSubInterfaceTest.java
@@ -6,7 +6,6 @@
import static org.junit.Assume.assumeTrue;
-import com.android.tools.r8.DesugarTestConfiguration;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.graph.AppView;
@@ -35,22 +34,7 @@
testForDesugaring(parameters)
.addInnerClasses(getClass())
.run(parameters.getRuntime(), TestClass.class)
- .applyIf(
- DesugarTestConfiguration::isJavac,
- runResult -> runResult.assertSuccessWithOutputLines("J"))
- .applyIf(
- DesugarTestConfiguration::isNotJavac,
- runResult -> {
- if (parameters
- .getApiLevel()
- .isGreaterThanOrEqualTo(apiLevelWithStaticInterfaceMethodsSupport())) {
- runResult.assertSuccessWithOutputLines("J");
- } else {
- // TODO(b/172050082): Calling greet() on the companion class of J should trigger J's
- // class initializer.
- runResult.assertSuccessWithEmptyOutput();
- }
- });
+ .assertSuccessWithOutputLines("J");
}
@Test
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByInvokeStaticTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByInvokeStaticTest.java
index 4f2eb00..a052428 100644
--- a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByInvokeStaticTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByInvokeStaticTest.java
@@ -6,7 +6,6 @@
import static org.junit.Assume.assumeTrue;
-import com.android.tools.r8.DesugarTestConfiguration;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.graph.AppView;
@@ -35,22 +34,7 @@
testForDesugaring(parameters)
.addInnerClasses(getClass())
.run(parameters.getRuntime(), TestClass.class)
- .applyIf(
- DesugarTestConfiguration::isJavac,
- runResult -> runResult.assertSuccessWithOutputLines("I"))
- .applyIf(
- DesugarTestConfiguration::isNotJavac,
- runResult -> {
- if (parameters
- .getApiLevel()
- .isGreaterThanOrEqualTo(apiLevelWithStaticInterfaceMethodsSupport())) {
- runResult.assertSuccessWithOutputLines("I");
- } else {
- // TODO(b/172050082): Calling greet() on the companion class of I should trigger I's
- // class initializer.
- runResult.assertSuccessWithEmptyOutput();
- }
- });
+ .assertSuccessWithOutputLines("I");
}
@Test
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceNotInitializedByInvokeStaticOnSubClassTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceNotInitializedByInvokeStaticOnSubClassTest.java
index 88c1089..385b549 100644
--- a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceNotInitializedByInvokeStaticOnSubClassTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceNotInitializedByInvokeStaticOnSubClassTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.shaking.clinit;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestParameters;
@@ -48,6 +49,7 @@
.allowStdoutMessages()
.setMinApi(parameters.getApiLevel())
.compile()
+ .inspect(inspector -> assertEquals(1, inspector.allClasses().size()))
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithEmptyOutput();
}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodNotInitializedByInvokeStaticOnSubInterfaceTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodNotInitializedByInvokeStaticOnSubInterfaceTest.java
index 115d6e8..3d88fe8 100644
--- a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodNotInitializedByInvokeStaticOnSubInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodNotInitializedByInvokeStaticOnSubInterfaceTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.shaking.clinit;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestParameters;
@@ -49,6 +50,7 @@
.allowStdoutMessages()
.setMinApi(parameters.getApiLevel())
.compile()
+ .inspect(inspector -> assertEquals(1, inspector.allClasses().size()))
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithEmptyOutput();
}
diff --git a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepInterfaceMethodTest.java
index 93309e8..e42dfe3 100644
--- a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepInterfaceMethodTest.java
@@ -10,6 +10,7 @@
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -82,6 +83,7 @@
.addProgramClasses(I.class, A.class, B.class, Main.class)
.addKeepRules("-keepclassmembers class " + I.class.getTypeName() + " { void foo(); }")
.addKeepMainRule(Main.class)
+ .enableNoHorizontalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("A.foo")
@@ -110,6 +112,7 @@
}
}
+ @NoHorizontalClassMerging
public static class B implements I {
@Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index ee4fe7b..28f419f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -96,6 +96,11 @@
}
@Override
+ public boolean isImplementing(ClassSubject subject) {
+ throw new Unreachable("Cannot determine if an absent class is implementing a given interface");
+ }
+
+ @Override
public DexProgramClass getDexProgramClass() {
return null;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 54e3d63..ff544ab 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -167,6 +167,8 @@
public abstract boolean isPublic();
+ public abstract boolean isImplementing(ClassSubject subject);
+
public String dumpMethods() {
StringBuilder dump = new StringBuilder();
forAllMethods(
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 d8aa792..eafb952 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
@@ -300,6 +300,17 @@
}
@Override
+ public boolean isImplementing(ClassSubject subject) {
+ assertTrue(subject.isPresent());
+ for (DexType itf : getDexProgramClass().interfaces) {
+ if (itf.toSourceString().equals(subject.getFinalName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
public boolean isAnnotation() {
return dexClass.accessFlags.isAnnotation();
}
diff --git a/third_party/opensource-apps/chanu.tar.gz.sha1 b/third_party/opensource-apps/chanu.tar.gz.sha1
new file mode 100644
index 0000000..f7f0433
--- /dev/null
+++ b/third_party/opensource-apps/chanu.tar.gz.sha1
@@ -0,0 +1 @@
+af80654ff4a0f399e55e4dafdc32a3f4b28145b7
\ No newline at end of file
diff --git a/third_party/opensource-apps/friendlyeats.tar.gz.sha1 b/third_party/opensource-apps/friendlyeats.tar.gz.sha1
new file mode 100644
index 0000000..952e2a8
--- /dev/null
+++ b/third_party/opensource-apps/friendlyeats.tar.gz.sha1
@@ -0,0 +1 @@
+f4ba305339e9285e8e865c526c737d385528e1c4
\ No newline at end of file
diff --git a/third_party/opensource-apps/iosched.tar.gz.sha1 b/third_party/opensource-apps/iosched.tar.gz.sha1
new file mode 100644
index 0000000..c99709c
--- /dev/null
+++ b/third_party/opensource-apps/iosched.tar.gz.sha1
@@ -0,0 +1 @@
+f3f855e6b3d773093c032c36c54c920d5ff018ec
\ No newline at end of file
diff --git a/third_party/opensource-apps/sunflower.tar.gz.sha1 b/third_party/opensource-apps/sunflower.tar.gz.sha1
new file mode 100644
index 0000000..06aec6d
--- /dev/null
+++ b/third_party/opensource-apps/sunflower.tar.gz.sha1
@@ -0,0 +1 @@
+52fe1160095f2f46d13782dc19d5201792ac2ec1
\ No newline at end of file
diff --git a/third_party/opensource-apps/wikipedia.tar.gz.sha1 b/third_party/opensource-apps/wikipedia.tar.gz.sha1
new file mode 100644
index 0000000..bfb14cb
--- /dev/null
+++ b/third_party/opensource-apps/wikipedia.tar.gz.sha1
@@ -0,0 +1 @@
+0e7d8398779c2dfa449c598556f4e57a860aa9e4
\ No newline at end of file
diff --git a/tools/git_sync_cl_chain.py b/tools/git_sync_cl_chain.py
index ef10a73..5a229bd 100755
--- a/tools/git_sync_cl_chain.py
+++ b/tools/git_sync_cl_chain.py
@@ -40,21 +40,35 @@
def ParseOptions(argv):
result = optparse.OptionParser()
- result.add_option('--message', '-m', help='Message for patchset')
+ result.add_option('--delete', '-d',
+ help='Delete closed branches',
+ choices=['y', 'n', 'ask'],
+ default='ask')
+ result.add_option('--leave_upstream', '--leave-upstream',
+ help='To not update the upstream of the first open branch',
+ action='store_true')
+ result.add_option('--message', '-m',
+ help='Message for patchset', default='Sync')
result.add_option('--rebase',
help='To use `git pull --rebase` instead of `git pull`',
action='store_true')
result.add_option('--no_upload', '--no-upload',
help='Disable uploading to Gerrit', action='store_true')
+ result.add_option('--skip_master', '--skip-master',
+ help='Disable syncing for master',
+ action='store_true')
(options, args) = result.parse_args(argv)
options.upload = not options.no_upload
+ assert options.delete != 'y' or not options.leave_upstream, (
+ 'Inconsistent options: cannot leave the upstream of the first open ' +
+ 'branch (--leave_upstream) and delete the closed branches at the same ' +
+ 'time (--delete).')
assert options.message, 'A message for the patchset is required.'
assert len(args) == 0
return options
def main(argv):
options = ParseOptions(argv)
- rebase_args = ['--rebase'] if options.rebase else []
with utils.ChangedWorkingDirectory(REPO_ROOT, quiet=True):
branches = [
parse(line)
@@ -74,26 +88,95 @@
stack = []
while current_branch:
stack.append(current_branch)
- if current_branch.upstream is None or current_branch.upstream == 'master':
+ if current_branch.upstream is None:
break
current_branch = get_branch_with_name(current_branch.upstream, branches)
+ closed_branches = []
+ has_seen_local_branch = False # A branch that is not uploaded.
+ has_seen_open_branch = False # A branch that is not closed.
while len(stack) > 0:
branch = stack.pop()
- print('Syncing ' + branch.name)
+
utils.RunCmd(['git', 'checkout', branch.name], quiet=True)
- utils.RunCmd(['git', 'pull'] + rebase_args, quiet=True)
+
+ status = get_status_for_current_branch()
+ print('Syncing %s (status: %s)' % (branch.name, status))
+
+ pull_for_current_branch(branch, options)
+
+ if branch.name == 'master':
+ continue
+
+ if status == 'closed':
+ assert not has_seen_local_branch, (
+ 'Unexpected closed branch %s after new branch' % branch.name)
+ assert not has_seen_open_branch, (
+ 'Unexpected closed branch %s after open branch' % branch.name)
+ closed_branches.append(branch.name)
+ continue
+
+ if not options.leave_upstream:
+ if not has_seen_open_branch and len(closed_branches) > 0:
+ print(
+ 'Setting upstream for first open branch %s to master'
+ % branch.name)
+ set_upstream_for_current_branch_to_master()
+
+ has_seen_open_branch = True
+ has_seen_local_branch = has_seen_local_branch or (status == 'None')
+
if options.upload:
- utils.RunCmd(['git', 'cl', 'upload', '-m', options.message], quiet=True)
+ if has_seen_local_branch:
+ print(
+ 'Cannot upload branch %s since it comes after a local branch'
+ % branch.name)
+ else:
+ utils.RunCmd(
+ ['git', 'cl', 'upload', '-m', options.message], quiet=True)
+
+ if get_delete_branches_option(closed_branches, options):
+ delete_branches(closed_branches)
utils.RunCmd(['git', 'cl', 'issue'])
+def delete_branches(branches):
+ assert len(branches) > 0
+ utils.RunCmd(['git', 'branch', '-D'].extend(branches), quiet=True)
+
def get_branch_with_name(name, branches):
for branch in branches:
if branch.name == name:
return branch
return None
+def get_delete_branches_option(closed_branches, options):
+ if len(closed_branches) == 0:
+ return False
+ if options.leave_upstream:
+ return False
+ if options.delete == 'y':
+ return True
+ if options.delete == 'n':
+ return False
+ assert options.delete == 'ask'
+ print('Delete closed branches: %s (Y/N)?' % ", ".join(closed_branches))
+ answer = sys.stdin.read(1)
+ return answer.lower() == 'y'
+
+def get_status_for_current_branch():
+ return utils.RunCmd(['git', 'cl', 'status', '--field', 'status'], quiet=True)[0].strip()
+
+def pull_for_current_branch(branch, options):
+ if branch.name == 'master' and options.skip_master:
+ return
+ rebase_args = ['--rebase'] if options.rebase else []
+ utils.RunCmd(['git', 'pull'] + rebase_args, quiet=True)
+
+
+def set_upstream_for_current_branch_to_master():
+ utils.RunCmd(['git', 'cl', 'upstream', 'master'], quiet=True)
+
# Parses a line from the output of `git branch -vv`.
#
# Example output ('*' denotes the current branch):
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 3e743b3..c3aff38 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -362,15 +362,12 @@
g4_open('src.jar')
g4_open('lib.jar')
g4_open('lib.jar.map')
- g4_open('desugar_jdk_libs.json')
g4_open('desugar_jdk_libs_configuration.jar')
download_file(options.version, 'r8-full-exclude-deps.jar', 'full.jar')
download_file(options.version, 'r8-src.jar', 'src.jar')
download_file(options.version, 'r8lib-exclude-deps.jar', 'lib.jar')
download_file(
options.version, 'r8lib-exclude-deps.jar.map', 'lib.jar.map')
- download_file(options.version, 'desugar_jdk_libs.json',
- 'desugar_jdk_libs.json')
download_file(options.version, 'desugar_jdk_libs_configuration.jar',
'desugar_jdk_libs_configuration.jar')
g4_open('METADATA')
diff --git a/tools/run_on_app_dump.py b/tools/run_on_app_dump.py
index af6d337..bf5d4c7 100755
--- a/tools/run_on_app_dump.py
+++ b/tools/run_on_app_dump.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python
-# Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-# for details. All rights reserved. Use of this source code is governed by a
-# BSD-style license that can be found in the LICENSE file.
+# Copyright (c) 2020, 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.
import adb
import apk_masseur
@@ -103,7 +103,62 @@
'url': 'https://github.com/mkj-gram/applymapping',
'revision': 'e3ae14b8c16fa4718e5dea8f7ad00937701b3c48',
'folder': 'applymapping',
- })
+ }),
+ App({
+ 'id': 'com.chanapps.four.activity',
+ 'name': 'chanu',
+ 'dump_app': 'dump_app.zip',
+ 'apk_app': 'app-release.apk',
+ 'url': 'https://github.com/mkj-gram/chanu.git',
+ 'revision': '6e53458f167b6d78398da60c20fd0da01a232617',
+ 'folder': 'chanu',
+ # TODO(b/172535996): Fix recompilation
+ 'skip_recompilation': True
+ }),
+ # TODO(b/172539375): Monkey runner fails on recompilation.
+ App({
+ 'id': 'com.google.firebase.example.fireeats',
+ 'name': 'FriendlyEats',
+ 'dump_app': 'dump_app.zip',
+ 'apk_app': 'app-release-unsigned.apk',
+ 'url': 'https://github.com/firebase/friendlyeats-android',
+ 'revision': '7c6dd016fc31ea5ecb948d5166b8479efc3775cc',
+ 'folder': 'friendlyeats',
+ }),
+ App({
+ 'id': 'com.google.samples.apps.sunflower',
+ 'name': 'Sunflower',
+ 'dump_app': 'dump_app.zip',
+ 'apk_app': 'app-debug.apk',
+ # TODO(b/172549283): Compiling tests fails
+ 'id_test': 'com.example.applymapping.test',
+ 'dump_test': 'dump_test.zip',
+ 'apk_test': 'app-debug-androidTest.apk',
+ 'url': 'https://github.com/android/sunflower',
+ 'revision': '0c4c88fdad2a74791199dffd1a6559559b1dbd4a',
+ 'folder': 'sunflower',
+ # TODO(b/172548728): Fix recompilation
+ 'skip_recompilation': True
+ }),
+ # TODO(b/172565385): Monkey runner fails on recompilation
+ App({
+ 'id': 'com.google.samples.apps.iosched',
+ 'name': 'iosched',
+ 'dump_app': 'dump_app.zip',
+ 'apk_app': 'mobile-release.apk',
+ 'url': 'https://github.com/christofferqa/iosched.git',
+ 'revision': '581cbbe2253711775dbccb753cdb53e7e506cb02',
+ 'folder': 'iosched',
+ }),
+ App({
+ 'id': 'org.wikipedia',
+ 'name': 'Wikipedia',
+ 'dump_app': 'dump_app.zip',
+ 'apk_app': 'app-prod-release.apk',
+ 'url': 'https://github.com/wikimedia/apps-android-wikipedia',
+ 'revision': '0fa7cad843c66313be8e25790ef084cf1a1fa67e',
+ 'folder': 'wikipedia',
+ }),
]
@@ -232,7 +287,6 @@
and not is_last_build(compilation_step, compilation_steps)):
result['recompilation_status'] = 'failed'
warn('Failed to build {} with {}'.format(app.name, shrinker))
- break
recomp_jar = new_recomp_jar
except Exception as e:
warn('Failed to build {} with {}'.format(app.name, shrinker))
@@ -270,18 +324,21 @@
test_jar = build_test_with_shrinker(
app, options, temp_dir, app_dir,shrinker, compilation_step,
result['output_mapping'])
- original_test_apk = os.path.join(app_dir, app.apk_test)
- test_apk_destination = os.path.join(
- temp_dir,"{}_{}.test.apk".format(app.id_test, compilation_step))
- apk_masseur.masseur(
- original_test_apk, dex=test_jar, resources='META-INF/services/*',
- out=test_apk_destination,
- quiet=options.quiet, logging=is_logging_enabled_for(app, options),
- keystore=options.keystore)
- result['instrumentation_test_status'] = 'success' if adb.run_instrumented(
- app.id, app.id_test, options.emulator_id, app_apk_destination,
- test_apk_destination, options.quiet,
- is_logging_enabled_for(app, options)) else 'failed'
+ if not test_jar:
+ result['instrumentation_test_status'] = 'compilation_failed'
+ else:
+ original_test_apk = os.path.join(app_dir, app.apk_test)
+ test_apk_destination = os.path.join(
+ temp_dir,"{}_{}.test.apk".format(app.id_test, compilation_step))
+ apk_masseur.masseur(
+ original_test_apk, dex=test_jar, resources='META-INF/services/*',
+ out=test_apk_destination,
+ quiet=options.quiet, logging=is_logging_enabled_for(app, options),
+ keystore=options.keystore)
+ result['instrumentation_test_status'] = 'success' if adb.run_instrumented(
+ app.id, app.id_test, options.emulator_id, app_apk_destination,
+ test_apk_destination, options.quiet,
+ is_logging_enabled_for(app, options)) else 'failed'
results.append(result)
if result.get('recompilation_status') != 'success':
@@ -335,6 +392,7 @@
return (app_jar, app_mapping, recomp_jar)
+
def build_test_with_shrinker(app, options, temp_dir, app_dir, shrinker,
compilation_step_index, mapping):
r8jar = os.path.join(
@@ -371,7 +429,8 @@
app.name, shrinker, compilation_step_index))
if compile_result != 0 or not os.path.isfile(out_jar):
- assert False, "Compilation of test_jar failed"
+ return None
+
shutil.move(out_jar, test_jar)
return test_jar