Merge commit '7960737f4d8b026cc8d59c624b6300b24ba9ad04' into dev-release
diff --git a/build.gradle b/build.gradle
index 3101886..cc51ea6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -437,7 +437,8 @@
"protobuf-lite",
"retrace_internal",
"youtube/youtube.android_15.33",
- "youtube/youtube.android_16.12"
+ "youtube/youtube.android_16.12",
+ "youtube/youtube.android_16.20"
],
]
diff --git a/compatibility-faq.md b/compatibility-faq.md
index f3dde4e..3ba23c8 100644
--- a/compatibility-faq.md
+++ b/compatibility-faq.md
@@ -1,10 +1,38 @@
-# R8 compatibility FAQ
+# R8 FAQ
R8 uses the same configuration specification language as ProGuard, and tries to
be compatible with ProGuard. However as R8 has different optimizations it can be
-necessary to change the configuration when switching to R8.
+necessary to change the configuration when switching to R8. R8 provides two
+modes, R8 compatibility mode and R8 full mode. R8 compatibility mode is default
+in Android Studio and is meant to make the transition to R8 from ProGuard easier
+by limiting the optimizations performed by R8.
-This FAQ collects some of the common issues.
+## R8 full mode
+In non-compat mode, also called “full mode”, R8 performs more aggressive
+optimizations, meaning additional ProGuard configuration rules may be required.
+Full mode can be enabled by adding `android.enableR8.fullMode=true` in the
+`gradle.properties` file. The main differences compared to R8 compatibility mode
+are:
+
+- The default constructor (`<init>()`) is not implicitly kept when a class is
+kept.
+- The default constructor (`<init>()`) is not implicitly kept for types which
+are only used with `ldc`, `instanceof` or `checkcast`.
+- The enclosing classes of fields or methods that are matched by a
+`-keepclassmembers` rule are not implicitly considered to be instantiated.
+Classes that are only instantiated using reflection should be kept explicitly
+with a `-keep` rule.
+- Default methods are not implicitly kept as abstract methods.
+- Attributes (such as `Signature`) and annotations are only kept for classes,
+methods and fields which are matched by keep rules even when `-keepattributes`
+is specified.
+Additionally, for attributes describing a relationship such as `InnerClass` and
+`EnclosingMethod`, non-compat mode requires both endpoints being kept.
+
+# Troubleshooting
+
+The rest of this document describes known issues with libraries that use
+reflection.
## GSON
@@ -21,7 +49,7 @@
```
-keepclassmembers,allowobfuscation class * {
- @com.google.gson.annotations.SerializedName <fields>;
+ @com.google.gson.annotations.SerializedName <fields>;
}
```
@@ -35,7 +63,7 @@
```
-keepclassmembers class MyDataClass {
- !transient <fields>;
+ !transient <fields>;
}
```
@@ -46,15 +74,15 @@
### Error `java.lang.IllegalArgumentException: class <class name> declares multiple JSON fields named <name>`
This can be caused by obfuscation selecting the same name for private fields in
-several classes in a class hierachy. Consider the following example:
+several classes in a class hierarchy. Consider the following example:
```
class A {
- private String fieldInA;
+ private String fieldInA;
}
class B extends A {
- private String fieldInB;
+ private String fieldInB;
}
```
@@ -66,33 +94,33 @@
```
class A {
- private transient String fieldInA;
+ private transient String fieldInA;
}
class B extends A {
- private transient String fieldInB;
+ private transient String fieldInB;
}
```
If the fields _are_ to be serialized, the annotation `SerializedName` can be
-used to fix the `IllegalArgumentException` together the rule to keep fields
+used to fix the `IllegalArgumentException` together with the rule to keep fields
annotated with `SerializedName`
```
class A {
- @SerializedName("fieldInA")
- private String fieldInA;
+ @SerializedName("fieldInA")
+ private String fieldInA;
}
class B extends A {
- @SerializedName("fieldInB")
- private String fieldInB;
+ @SerializedName("fieldInB")
+ private String fieldInB;
}
```
```
-keepclassmembers,allowobfuscation class * {
- @com.google.gson.annotations.SerializedName <fields>;
+ @com.google.gson.annotations.SerializedName <fields>;
}
```
@@ -101,15 +129,26 @@
the fields to be renamed by R8 to the same name, but GSON serialization will
work as expected.
-# R8 full mode
+### GSON with full mode
-In full mode, R8 performs more aggressive optimizations, meaning that additional
-ProGuard configuration rules may be required. This section highlights some
-common issues that have been seen when using full mode.
+GSON uses type tokens to serialize and deserialize generic types.
+
+```TypeToken<List<String>> listOfStrings = new TypeToken<List<String>>() {};```
+
+The anonymous class will have a generic signature argument of `List<String>` to
+the super type `TypeToken` that is reflective read for serialization. It
+is therefore necessary to keep both the `Signature` attribute, the
+`com.google.gson.reflect.TypeToken` class and all sub-types:
+
+```
+-keepattributes Signature
+-keep class com.google.gson.reflect.TypeToken { *; }
+-keep class * extends com.google.gson.reflect.TypeToken
+```
## Retrofit
-### Object instantiated with Retrofit's `create()` method is always replaced with `null`
+### Objects instantiated with Retrofit's `create()` method are always replaced with `null`
This happens because Retrofit uses reflection to return an object that
implements a given interface. The issue can be resolved by using the most recent
@@ -117,3 +156,18 @@
See also https://github.com/square/retrofit/issues/3005 ("Insufficient keep
rules for R8 in full mode").
+
+### Kotlin suspend functions and generic signatures
+
+For Kotlin suspend functions the generic signature is reflectively read.
+Therefore keeping the `Signature` attribute is necessary. Full mode only keeps
+the signature for kept classes thus a keep on `kotlin.coroutines.Continuation` in
+addition to a keep on the api classes is needed:
+```
+-keepattributes Signature
+-keep class kotlin.coroutines.Continuation
+```
+
+This should be included automatically from versions built after the pull-request
+https://github.com/square/retrofit/pull/3563
+
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 7bb927c..844d01a 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -930,12 +930,10 @@
: AssertionTransformation.DISABLE,
getAssertionsConfiguration());
- // When generating class files the build is "intermediate" and we cannot pollute the namespace
- // with the a hard-coded outline / enum unboxing utility class. Doing so would prohibit
- // subsequent merging of two R8 produced libraries.
+ // TODO(b/171552739): Enable class merging for CF. When compiling libraries, we need to be
+ // careful when merging a public member 'm' from a class A into another class B, since B could
+ // have a kept subclass, in which case 'm' would leak into the public API.
if (internal.isGeneratingClassFiles()) {
- internal.outline.enabled = false;
- internal.enableEnumUnboxing = false;
horizontalClassMergerOptions.disable();
}
diff --git a/src/main/java/com/android/tools/r8/ResourceShrinker.java b/src/main/java/com/android/tools/r8/ResourceShrinker.java
index 77abde6..bf4f471 100644
--- a/src/main/java/com/android/tools/r8/ResourceShrinker.java
+++ b/src/main/java/com/android/tools/r8/ResourceShrinker.java
@@ -47,6 +47,8 @@
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.ir.code.SingleConstant;
import com.android.tools.r8.ir.code.WideConstant;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Timing;
@@ -140,6 +142,14 @@
void referencedStaticField(String internalName, String fieldName);
void referencedMethod(String internalName, String methodName, String methodDescriptor);
+
+ default void startMethodVisit(MethodReference methodReference) {}
+
+ default void endMethodVisit(MethodReference methodReference) {}
+
+ default void startClassVisit(ClassReference classReference) {}
+
+ default void endClassVisit(ClassReference classReference) {}
}
private static final class DexClassUsageVisitor {
@@ -153,6 +163,7 @@
}
public void visit() {
+ callback.startClassVisit(classDef.getClassReference());
if (!callback.shouldProcess(classDef.type.getInternalName())) {
return;
}
@@ -165,12 +176,16 @@
}
for (DexEncodedMethod method : classDef.allMethodsSorted()) {
+
+ callback.startMethodVisit(method.getReference().asMethodReference());
processMethod(method);
+ callback.endMethodVisit(method.getReference().asMethodReference());
}
if (classDef.hasClassOrMemberAnnotations()) {
processAnnotations(classDef);
}
+ callback.endClassVisit(classDef.getClassReference());
}
private void processFieldValue(DexValue value) {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
index 592ee31..cd8d6b7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -42,6 +42,10 @@
spec.withInt(f -> f.opcode).withItem(f -> f.field).withItem(f -> f.declaringField);
}
+ public CfFieldInstruction(int opcode, DexField field) {
+ this(opcode, field, field);
+ }
+
public CfFieldInstruction(int opcode, DexField field, DexField declaringField) {
this.opcode = opcode;
this.field = field;
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 4ef90be..6ef4c6a 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -67,7 +67,7 @@
private ProguardCompatibilityActions proguardCompatibilityActions;
private RootSet rootSet;
private MainDexRootSet mainDexRootSet = null;
- // This should perferably always be obtained via AppInfoWithLiveness.
+ // This should preferably always be obtained via AppInfoWithLiveness.
// Currently however the liveness may be downgraded thus loosing the computed keep info.
private KeepInfoCollection keepInfo = null;
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 ada1c8a..1e10fa9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -26,6 +26,7 @@
import com.android.tools.r8.utils.structural.StructuralMapping;
import com.android.tools.r8.utils.structural.StructuralSpecification;
import java.util.function.Consumer;
+import java.util.function.Function;
public class DexEncodedField extends DexEncodedMember<DexEncodedField, DexField>
implements StructuralItem<DexEncodedField> {
@@ -116,18 +117,15 @@
return false;
}
+ @Override
public FieldOptimizationInfo getOptimizationInfo() {
return optimizationInfo;
}
public synchronized MutableFieldOptimizationInfo getMutableOptimizationInfo() {
- if (optimizationInfo.isDefaultFieldOptimizationInfo()) {
- MutableFieldOptimizationInfo mutableOptimizationInfo = new MutableFieldOptimizationInfo();
- optimizationInfo = mutableOptimizationInfo;
- return mutableOptimizationInfo;
- }
- assert optimizationInfo.isMutableFieldOptimizationInfo();
- return optimizationInfo.asMutableFieldOptimizationInfo();
+ MutableFieldOptimizationInfo mutableInfo = optimizationInfo.toMutableOptimizationInfo();
+ optimizationInfo = mutableInfo;
+ return mutableInfo;
}
public void setOptimizationInfo(MutableFieldOptimizationInfo info) {
@@ -197,6 +195,12 @@
return asProgramField(definitions);
}
+ @Override
+ public <T> T apply(
+ Function<DexEncodedField, T> fieldConsumer, Function<DexEncodedMethod, T> methodConsumer) {
+ return fieldConsumer.apply(this);
+ }
+
public ProgramField asProgramField(DexDefinitionSupplier definitions) {
assert getHolderType().isClassType();
DexProgramClass clazz = asProgramClassOrNull(definitions.definitionForHolder(getReference()));
@@ -376,9 +380,9 @@
annotations = from.annotations();
staticValue = from.staticValue;
optimizationInfo =
- from.optimizationInfo.isDefaultFieldOptimizationInfo()
- ? DefaultFieldOptimizationInfo.getInstance()
- : from.optimizationInfo.mutableCopy();
+ from.optimizationInfo.isMutableOptimizationInfo()
+ ? from.optimizationInfo.asMutableFieldOptimizationInfo().mutableCopy()
+ : from.optimizationInfo;
deprecated = from.isDeprecated();
d8R8Synthesized = from.isD8R8Synthesized();
}
@@ -416,9 +420,7 @@
staticValue,
deprecated,
d8R8Synthesized);
- if (optimizationInfo.isMutableFieldOptimizationInfo()) {
- dexEncodedField.setOptimizationInfo(optimizationInfo.asMutableFieldOptimizationInfo());
- }
+ dexEncodedField.optimizationInfo = optimizationInfo;
buildConsumer.accept(dexEncodedField);
return dexEncodedField;
}
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 e1bb25e..271fd68 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
@@ -3,7 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import com.android.tools.r8.ir.optimize.info.MemberOptimizationInfo;
import com.android.tools.r8.kotlin.KotlinMemberLevelInfo;
+import java.util.function.Consumer;
+import java.util.function.Function;
public abstract class DexEncodedMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
extends DexDefinition {
@@ -63,6 +66,24 @@
public abstract ProgramMember<D, R> asProgramMember(DexDefinitionSupplier definitions);
+ public abstract <T> T apply(
+ Function<DexEncodedField, T> fieldConsumer, Function<DexEncodedMethod, T> methodConsumer);
+
+ public void apply(
+ Consumer<DexEncodedField> fieldConsumer, Consumer<DexEncodedMethod> methodConsumer) {
+ apply(
+ field -> {
+ fieldConsumer.accept(field);
+ return null;
+ },
+ method -> {
+ methodConsumer.accept(method);
+ return null;
+ });
+ }
+
+ public abstract MemberOptimizationInfo<?> getOptimizationInfo();
+
@Override
public final boolean equals(Object other) {
if (other == this) {
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 434b091..449747b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -28,7 +28,7 @@
import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
import com.android.tools.r8.cf.code.CfStore;
import com.android.tools.r8.cf.code.CfThrow;
-import com.android.tools.r8.code.Const;
+import com.android.tools.r8.code.Const4;
import com.android.tools.r8.code.ConstString;
import com.android.tools.r8.code.InstanceOf;
import com.android.tools.r8.code.Instruction;
@@ -58,8 +58,8 @@
import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationWithMinApiInfo;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.MutableMethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
-import com.android.tools.r8.ir.optimize.info.UpdatableMethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import com.android.tools.r8.ir.synthetic.FieldAccessorBuilder;
@@ -93,6 +93,7 @@
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.function.IntPredicate;
import org.objectweb.asm.Opcodes;
@@ -436,6 +437,12 @@
return asProgramMethod(definitions);
}
+ @Override
+ public <T> T apply(
+ Function<DexEncodedField, T> fieldConsumer, Function<DexEncodedMethod, T> methodConsumer) {
+ return methodConsumer.apply(this);
+ }
+
public DexClassAndMethod asDexClassAndMethod(DexDefinitionSupplier definitions) {
assert getReference().holder.isClassType();
DexClass clazz = definitions.definitionForHolder(getReference());
@@ -1011,7 +1018,7 @@
}
public DexCode buildEmptyThrowingDexCode() {
- Instruction[] insn = {new Const(0, 0), new Throw(0)};
+ Instruction[] insn = {new Const4(0, 0), new Throw(0)};
return generateCodeFromTemplate(1, 0, insn);
}
@@ -1445,20 +1452,20 @@
return m1.getReference().compareTo(m2.getReference());
}
+ @Override
public MethodOptimizationInfo getOptimizationInfo() {
checkIfObsolete();
return optimizationInfo;
}
- public synchronized UpdatableMethodOptimizationInfo getMutableOptimizationInfo() {
+ public synchronized MutableMethodOptimizationInfo getMutableOptimizationInfo() {
checkIfObsolete();
- UpdatableMethodOptimizationInfo updatableMethodOptimizationInfo =
- optimizationInfo.asUpdatableMethodOptimizationInfo();
- this.optimizationInfo = updatableMethodOptimizationInfo;
- return updatableMethodOptimizationInfo;
+ MutableMethodOptimizationInfo mutableInfo = optimizationInfo.toMutableOptimizationInfo();
+ optimizationInfo = mutableInfo;
+ return mutableInfo;
}
- public void setOptimizationInfo(UpdatableMethodOptimizationInfo info) {
+ public void setOptimizationInfo(MutableMethodOptimizationInfo info) {
checkIfObsolete();
optimizationInfo = info;
}
@@ -1542,9 +1549,9 @@
code = from.code;
compilationState = CompilationState.NOT_PROCESSED;
optimizationInfo =
- from.optimizationInfo.isDefaultMethodOptimizationInfo()
- ? DefaultMethodOptimizationInfo.getInstance()
- : from.optimizationInfo.mutableCopy();
+ from.optimizationInfo.isMutableOptimizationInfo()
+ ? from.optimizationInfo.asMutableMethodOptimizationInfo().mutableCopy()
+ : from.optimizationInfo;
kotlinMemberInfo = from.kotlinMemberInfo;
classFileVersion = from.classFileVersion;
this.d8R8Synthesized = d8R8Synthesized;
@@ -1679,8 +1686,9 @@
}
public Builder adjustOptimizationInfoAfterRemovingThisParameter() {
- if (optimizationInfo.isUpdatableMethodOptimizationInfo()) {
- optimizationInfo.asUpdatableMethodOptimizationInfo()
+ if (optimizationInfo.isMutableOptimizationInfo()) {
+ optimizationInfo
+ .asMutableMethodOptimizationInfo()
.adjustOptimizationInfoAfterRemovingThisParameter();
}
return this;
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 646c9a0..a42388a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -2238,6 +2238,10 @@
return createMethod(holder, createProto(voidType), classConstructorMethodName);
}
+ public DexMethod createInstanceInitializer(DexType holder, DexType... parameters) {
+ return createMethod(holder, createProto(voidType, parameters), constructorMethodName);
+ }
+
public DexMethod createInstanceInitializerWithFreshProto(
DexMethod method, List<DexType> extraTypes, Predicate<DexMethod> isFresh) {
assert method.isInstanceInitializer(this);
@@ -2258,8 +2262,6 @@
private DexMethod createInstanceInitializerWithFreshProto(
DexProto proto, List<DexType> extraTypes, Function<DexProto, Optional<DexMethod>> isFresh) {
- assert !extraTypes.isEmpty();
-
Queue<Iterable<DexProto>> tryProtos = new LinkedList<>();
Iterator<DexProto> current = IterableUtils.singleton(proto).iterator();
@@ -2276,6 +2278,7 @@
if (object.isPresent()) {
return object.get();
}
+ assert !extraTypes.isEmpty();
tryProtos.add(
Iterables.transform(extraTypes, extraType -> appendTypeToProto(tryProto, extraType)));
}
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 a38770a..96c3084 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -93,6 +93,11 @@
return descriptor;
}
+ public int getRequiredRegisters() {
+ assert !isVoidType();
+ return isWideType() ? 2 : 1;
+ }
+
@Override
public int computeHashCode() {
return descriptor.hashCode();
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java b/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java
new file mode 100644
index 0000000..b6d9757
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.InterfaceCollection;
+import com.android.tools.r8.utils.ObjectUtils;
+import java.util.Iterator;
+
+public class DexTypeUtils {
+
+ public static DexType computeLeastUpperBound(
+ AppView<? extends AppInfoWithClassHierarchy> appView, Iterable<DexType> types) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+
+ Iterator<DexType> iterator = types.iterator();
+ assert iterator.hasNext();
+
+ DexType result = iterator.next();
+ while (iterator.hasNext()) {
+ result = computeLeastUpperBound(appView, result, iterator.next());
+ }
+ return result;
+ }
+
+ public static DexType computeLeastUpperBound(
+ AppView<? extends AppInfoWithClassHierarchy> appView, DexType type, DexType other) {
+ if (type == other) {
+ return type;
+ }
+
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ if (type == dexItemFactory.objectType
+ || other == dexItemFactory.objectType
+ || type.isArrayType() != other.isArrayType()) {
+ return dexItemFactory.objectType;
+ }
+
+ if (type.isArrayType()) {
+ assert other.isArrayType();
+ int arrayDimension = type.getNumberOfLeadingSquareBrackets();
+ if (other.getNumberOfLeadingSquareBrackets() != arrayDimension) {
+ return dexItemFactory.objectType;
+ }
+
+ DexType baseType = type.toBaseType(dexItemFactory);
+ DexType otherBaseType = other.toBaseType(dexItemFactory);
+ if (baseType.isPrimitiveType() || otherBaseType.isPrimitiveType()) {
+ assert baseType != otherBaseType;
+ return dexItemFactory.objectType;
+ }
+
+ return dexItemFactory.createArrayType(
+ arrayDimension, computeLeastUpperBound(appView, baseType, otherBaseType));
+ }
+
+ assert !type.isArrayType();
+ assert !other.isArrayType();
+
+ boolean isInterface =
+ type.isClassType()
+ && ObjectUtils.getBooleanOrElse(
+ appView.definitionFor(type), DexClass::isInterface, false);
+ boolean otherIsInterface =
+ other.isClassType()
+ && ObjectUtils.getBooleanOrElse(
+ appView.definitionFor(other), DexClass::isInterface, false);
+ if (isInterface != otherIsInterface) {
+ return dexItemFactory.objectType;
+ }
+
+ if (isInterface) {
+ assert otherIsInterface;
+ InterfaceCollection interfaceCollection =
+ ClassTypeElement.computeLeastUpperBoundOfInterfaces(
+ appView, InterfaceCollection.singleton(type), InterfaceCollection.singleton(other));
+ return interfaceCollection.hasSingleKnownInterface()
+ ? interfaceCollection.getSingleKnownInterface()
+ : dexItemFactory.objectType;
+ }
+
+ assert !isInterface;
+ assert !otherIsInterface;
+
+ return ClassTypeElement.computeLeastUpperBoundOfClasses(appView.appInfo(), type, other);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java
index ec86d3e..a3cf3a1 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java
@@ -125,15 +125,17 @@
SignatureEvaluationResult result =
genericSignatureContextEvaluator.evaluateClassSignatureForContext(typeParameterContext);
if (result.isInvalid() && mode.clearIfInvalid()) {
- appView
- .options()
- .reporter
- .info(
- GenericSignatureValidationDiagnostic.invalidClassSignature(
- clazz.getClassSignature().toString(),
- clazz.getTypeName(),
- clazz.getOrigin(),
- result));
+ if (appView.hasLiveness() && appView.getKeepInfo().getClassInfo(clazz).isPinned()) {
+ appView
+ .options()
+ .reporter
+ .info(
+ GenericSignatureValidationDiagnostic.invalidClassSignature(
+ clazz.getClassSignature().toString(),
+ clazz.getTypeName(),
+ clazz.getOrigin(),
+ result));
+ }
clazz.clearClassSignature();
}
for (DexEncodedMethod method : clazz.methods()) {
@@ -145,15 +147,18 @@
genericSignatureContextEvaluator.visitMethodSignature(
methodSignature, typeParameterContext),
invalidResult -> {
- appView
- .options()
- .reporter
- .info(
- GenericSignatureValidationDiagnostic.invalidMethodSignature(
- method.getGenericSignature().toString(),
- method.toSourceString(),
- clazz.getOrigin(),
- invalidResult));
+ if (appView.hasLiveness()
+ && appView.getKeepInfo().getMethodInfo(method, clazz).isPinned()) {
+ appView
+ .options()
+ .reporter
+ .info(
+ GenericSignatureValidationDiagnostic.invalidMethodSignature(
+ method.getGenericSignature().toString(),
+ method.toSourceString(),
+ clazz.getOrigin(),
+ invalidResult));
+ }
method.clearGenericSignature();
}));
}
@@ -166,15 +171,18 @@
genericSignatureContextEvaluator.visitFieldTypeSignature(
fieldSignature, typeParameterContext),
invalidResult -> {
- appView
- .options()
- .reporter
- .info(
- GenericSignatureValidationDiagnostic.invalidFieldSignature(
- field.getGenericSignature().toString(),
- field.toSourceString(),
- clazz.getOrigin(),
- invalidResult));
+ if (appView.hasLiveness()
+ && appView.getKeepInfo().getFieldInfo(field, clazz).isPinned()) {
+ appView
+ .options()
+ .reporter
+ .info(
+ GenericSignatureValidationDiagnostic.invalidFieldSignature(
+ field.getGenericSignature().toString(),
+ field.toSourceString(),
+ clazz.getOrigin(),
+ invalidResult));
+ }
field.clearGenericSignature();
}));
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
index a429d5c..e0eaf2c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
@@ -8,10 +8,12 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerGraphLens.Builder;
import com.android.tools.r8.horizontalclassmerging.policies.SameInstanceFields.InstanceFieldInfo;
import com.android.tools.r8.utils.IterableUtils;
+import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap;
+import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneMap;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.Collection;
@@ -19,27 +21,28 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Set;
public class ClassInstanceFieldsMerger {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
+ private final MergeGroup group;
private final Builder lensBuilder;
private DexEncodedField classIdField;
// Map from target class field to all fields which should be merged into that field.
- private final Map<DexEncodedField, List<DexEncodedField>> fieldMappings = new LinkedHashMap<>();
+ private final MutableBidirectionalManyToOneMap<DexEncodedField, DexEncodedField> fieldMappings =
+ BidirectionalManyToOneHashMap.newLinkedHashMap();
public ClassInstanceFieldsMerger(
AppView<? extends AppInfoWithClassHierarchy> appView,
HorizontalClassMergerGraphLens.Builder lensBuilder,
MergeGroup group) {
this.appView = appView;
+ this.group = group;
this.lensBuilder = lensBuilder;
- group
- .getTarget()
- .instanceFields()
- .forEach(field -> fieldMappings.computeIfAbsent(field, ignore -> new ArrayList<>()));
+ group.forEachSource(this::addFields);
}
/**
@@ -52,7 +55,7 @@
* Bar has fields 'A b' and 'B a'), we make a prepass that matches fields with the same reference
* type.
*/
- public void addFields(DexProgramClass clazz) {
+ private void addFields(DexProgramClass clazz) {
Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByExactInfo =
getAvailableFieldsByExactInfo();
List<DexEncodedField> needsMerge = new ArrayList<>();
@@ -66,7 +69,7 @@
needsMerge.add(oldField);
} else {
DexEncodedField newField = availableFieldsWithExactSameInfo.removeFirst();
- fieldMappings.get(newField).add(oldField);
+ fieldMappings.put(oldField, newField);
if (availableFieldsWithExactSameInfo.isEmpty()) {
availableFieldsByExactInfo.remove(info);
}
@@ -84,14 +87,14 @@
.removeFirst();
assert newField != null;
assert newField.getType().isReferenceType();
- fieldMappings.get(newField).add(oldField);
+ fieldMappings.put(oldField, newField);
}
}
private Map<InstanceFieldInfo, LinkedList<DexEncodedField>> getAvailableFieldsByExactInfo() {
Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByInfo =
new LinkedHashMap<>();
- for (DexEncodedField field : fieldMappings.keySet()) {
+ for (DexEncodedField field : group.getTarget().instanceFields()) {
availableFieldsByInfo
.computeIfAbsent(InstanceFieldInfo.createExact(field), ignore -> new LinkedList<>())
.add(field);
@@ -122,6 +125,14 @@
}
}
+ public ProgramField getTargetField(ProgramField field) {
+ if (field.getHolder() == group.getTarget()) {
+ return field;
+ }
+ DexEncodedField targetField = fieldMappings.get(field.getDefinition());
+ return new ProgramField(group.getTarget(), targetField);
+ }
+
public void setClassIdField(DexEncodedField classIdField) {
this.classIdField = classIdField;
}
@@ -131,19 +142,18 @@
if (classIdField != null) {
newFields.add(classIdField);
}
- fieldMappings.forEach(
- (targetField, oldFields) ->
- newFields.add(mergeSourceFieldsToTargetField(targetField, oldFields)));
+ fieldMappings.forEachManyToOneMapping(
+ (sourceFields, targetField) ->
+ newFields.add(mergeSourceFieldsToTargetField(targetField, sourceFields)));
return newFields.toArray(DexEncodedField.EMPTY_ARRAY);
}
private DexEncodedField mergeSourceFieldsToTargetField(
- DexEncodedField targetField, List<DexEncodedField> oldFields) {
- fixAccessFlags(targetField, oldFields);
+ DexEncodedField targetField, Set<DexEncodedField> sourceFields) {
+ fixAccessFlags(targetField, sourceFields);
DexEncodedField newField;
- DexType targetFieldType = targetField.type();
- if (!Iterables.all(oldFields, oldField -> oldField.getType() == targetFieldType)) {
+ if (needsRelaxedType(targetField, sourceFields)) {
newField =
targetField.toTypeSubstitutedField(
targetField
@@ -155,10 +165,16 @@
lensBuilder.recordNewFieldSignature(
Iterables.transform(
- IterableUtils.append(oldFields, targetField), DexEncodedField::getReference),
+ IterableUtils.append(sourceFields, targetField), DexEncodedField::getReference),
newField.getReference(),
targetField.getReference());
return newField;
}
+
+ private boolean needsRelaxedType(
+ DexEncodedField targetField, Iterable<DexEncodedField> sourceFields) {
+ return Iterables.any(
+ sourceFields, sourceField -> sourceField.getType() != targetField.getType());
+ }
}
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 89e9f74..3927d11 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -62,13 +62,18 @@
private final Mode mode;
private final MergeGroup group;
private final DexItemFactory dexItemFactory;
- private final ClassInitializerMerger classInitializerMerger;
private final HorizontalClassMergerGraphLens.Builder lensBuilder;
private final ClassMethodsBuilder classMethodsBuilder = new ClassMethodsBuilder();
private final Reference2IntMap<DexType> classIdentifiers = new Reference2IntOpenHashMap<>();
- private final ClassStaticFieldsMerger classStaticFieldsMerger;
+
+ // Field mergers.
private final ClassInstanceFieldsMerger classInstanceFieldsMerger;
+ private final ClassStaticFieldsMerger classStaticFieldsMerger;
+
+ // Method mergers.
+ private final ClassInitializerMerger classInitializerMerger;
+ private final InstanceInitializerMergerCollection instanceInitializerMergers;
private final Collection<VirtualMethodMerger> virtualMethodMergers;
private ClassMerger(
@@ -82,10 +87,18 @@
this.group = group;
this.lensBuilder = lensBuilder;
this.mode = mode;
- this.virtualMethodMergers = virtualMethodMergers;
- this.classInitializerMerger = ClassInitializerMerger.create(group);
+
+ // Field mergers.
this.classStaticFieldsMerger = new ClassStaticFieldsMerger(appView, lensBuilder, group);
this.classInstanceFieldsMerger = new ClassInstanceFieldsMerger(appView, lensBuilder, group);
+
+ // Method mergers.
+ this.classInitializerMerger = ClassInitializerMerger.create(group);
+ this.instanceInitializerMergers =
+ InstanceInitializerMergerCollection.create(
+ appView, classIdentifiers, group, classInstanceFieldsMerger, lensBuilder, mode);
+ this.virtualMethodMergers = virtualMethodMergers;
+
buildClassIdentifierMap();
}
@@ -175,12 +188,8 @@
}
void mergeInstanceInitializers(SyntheticArgumentClass syntheticArgumentClass) {
- InstanceInitializerMergerCollection instanceInitializerMergers =
- InstanceInitializerMergerCollection.create(appView, group, lensBuilder, mode);
instanceInitializerMergers.forEach(
- merger ->
- merger.merge(
- classMethodsBuilder, lensBuilder, classIdentifiers, syntheticArgumentClass));
+ merger -> merger.merge(classMethodsBuilder, syntheticArgumentClass));
}
void mergeMethods(
@@ -295,11 +304,7 @@
}
void mergeInstanceFields() {
- group.forEachSource(
- clazz -> {
- classInstanceFieldsMerger.addFields(clazz);
- clazz.clearInstanceFields();
- });
+ group.forEachSource(DexClass::clearInstanceFields);
group.getTarget().setInstanceFields(classInstanceFieldsMerger.merge());
}
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 c05af00..4ad6f55 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -212,6 +212,9 @@
for (ClassMerger merger : classMergers) {
merger.mergeGroup(syntheticArgumentClass, syntheticClassInitializerConverterBuilder);
}
+
+ // Clear type elements cache after IR building.
+ appView.dexItemFactory().clearTypeElementsCache();
}
/**
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerCfCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerCfCode.java
new file mode 100644
index 0000000..839b6d5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerCfCode.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2021, 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.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexType;
+import java.util.List;
+
+/**
+ * Similar to CfCode, but with a marker that makes it possible to recognize this is synthesized by
+ * the horizontal class merger.
+ */
+public class HorizontalClassMergerCfCode extends CfCode {
+
+ HorizontalClassMergerCfCode(
+ DexType originalHolder,
+ int maxStack,
+ int maxLocals,
+ List<CfInstruction> instructions,
+ List<CfTryCatch> tryCatchRanges,
+ List<LocalVariableInfo> localVariables) {
+ super(originalHolder, maxStack, maxLocals, instructions, tryCatchRanges, localVariables);
+ }
+
+ @Override
+ public boolean isHorizontalClassMergingCode() {
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
index 12a4190..20eb5a3 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.NestedGraphLens;
+import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.conversion.ExtraParameter;
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap;
@@ -103,6 +104,13 @@
AppView<?> appView, HorizontallyMergedClasses mergedClasses) {
assert pendingMethodMapUpdates.isEmpty();
assert pendingNewMethodSignatureUpdates.isEmpty();
+ assert newMethodSignatures.values().stream()
+ .allMatch(
+ value -> {
+ assert newMethodSignatures.getKeys(value).size() == 1
+ || newMethodSignatures.hasExplicitRepresentativeKey(value);
+ return true;
+ });
return new HorizontalClassMergerGraphLens(
appView,
mergedClasses,
@@ -112,6 +120,11 @@
newMethodSignatures);
}
+ DexMethod getRenamedMethodSignature(DexMethod method) {
+ assert newMethodSignatures.containsKey(method);
+ return newMethodSignatures.get(method);
+ }
+
void recordNewFieldSignature(DexField oldFieldSignature, DexField newFieldSignature) {
fieldMap.put(oldFieldSignature, newFieldSignature);
}
@@ -157,6 +170,17 @@
recordNewMethodSignature(from, to, isRepresentative);
}
+ void moveMethods(Iterable<ProgramMethod> methods, DexMethod to) {
+ moveMethods(methods, to, null);
+ }
+
+ void moveMethods(Iterable<ProgramMethod> methods, DexMethod to, ProgramMethod representative) {
+ for (ProgramMethod from : methods) {
+ boolean isRepresentative = representative != null && from == representative;
+ moveMethod(from.getReference(), to, isRepresentative);
+ }
+ }
+
void recordNewMethodSignature(DexMethod oldMethodSignature, DexMethod newMethodSignature) {
recordNewMethodSignature(oldMethodSignature, newMethodSignature, false);
}
@@ -194,6 +218,10 @@
for (DexMethod originalMethodSignature : oldMethodSignatures) {
pendingNewMethodSignatureUpdates.put(originalMethodSignature, newMethodSignature);
}
+ DexMethod representative = newMethodSignatures.getRepresentativeKey(oldMethodSignature);
+ if (representative != null) {
+ pendingNewMethodSignatureUpdates.setRepresentative(newMethodSignature, representative);
+ }
}
}
@@ -205,7 +233,13 @@
// Commit pending original method signatures updates.
newMethodSignatures.removeAll(pendingNewMethodSignatureUpdates.keySet());
- pendingNewMethodSignatureUpdates.forEachManyToOneMapping(newMethodSignatures::put);
+ pendingNewMethodSignatureUpdates.forEachManyToOneMapping(
+ (keys, value, representative) -> {
+ newMethodSignatures.put(keys, value);
+ if (keys.size() > 1) {
+ newMethodSignatures.setRepresentative(value, representative);
+ }
+ });
pendingNewMethodSignatureUpdates.clear();
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
index febdbe6..9a6390a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
@@ -4,14 +4,204 @@
package com.android.tools.r8.horizontalclassmerging;
+import static com.android.tools.r8.ir.code.Opcodes.ARGUMENT;
+import static com.android.tools.r8.ir.code.Opcodes.ASSUME;
+import static com.android.tools.r8.ir.code.Opcodes.CONST_CLASS;
+import static com.android.tools.r8.ir.code.Opcodes.CONST_NUMBER;
+import static com.android.tools.r8.ir.code.Opcodes.CONST_STRING;
+import static com.android.tools.r8.ir.code.Opcodes.DEX_ITEM_BASED_CONST_STRING;
+import static com.android.tools.r8.ir.code.Opcodes.GOTO;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
+import static com.android.tools.r8.ir.code.Opcodes.RETURN;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstancePut;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory;
+import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
+import com.android.tools.r8.utils.WorkList;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import java.util.ArrayList;
+import java.util.List;
public class InstanceInitializerAnalysis {
public static InstanceInitializerDescription analyze(
- ProgramMethod instanceInitializer, HorizontalClassMergerGraphLens.Builder lensBuilder) {
- // TODO(b/189296638): Return an InstanceInitializerDescription if the given instance initializer
- // is parent constructor call followed by a sequence of instance field puts.
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ MergeGroup group,
+ ProgramMethod instanceInitializer,
+ ClassInstanceFieldsMerger instanceFieldsMerger,
+ Mode mode) {
+ InstanceInitializerDescription.Builder builder =
+ InstanceInitializerDescription.builder(appView, instanceInitializer);
+
+ if (mode.isFinal()) {
+ // TODO(b/189296638): We can't build IR in the final round of class merging without simulating
+ // that we're in D8.
+ MethodOptimizationInfo optimizationInfo =
+ instanceInitializer.getDefinition().getOptimizationInfo();
+ if (optimizationInfo.mayHaveSideEffects()) {
+ return null;
+ }
+ InstanceInitializerInfo instanceInitializerInfo =
+ optimizationInfo.getContextInsensitiveInstanceInitializerInfo();
+ if (!instanceInitializerInfo.hasParent()) {
+ // We don't know the parent constructor of the first constructor.
+ return null;
+ }
+ DexMethod parent = instanceInitializerInfo.getParent();
+ if (parent.getArity() > 0) {
+ return null;
+ }
+ builder.addInvokeConstructor(parent, ImmutableList.of());
+ return builder.build();
+ }
+
+ IRCode code = instanceInitializer.buildIR(appView);
+ WorkList<BasicBlock> workList = WorkList.newIdentityWorkList(code.entryBlock());
+ while (workList.hasNext()) {
+ BasicBlock block = workList.next();
+ for (Instruction instruction : block.getInstructions()) {
+ switch (instruction.opcode()) {
+ case ARGUMENT:
+ case ASSUME:
+ case CONST_CLASS:
+ case CONST_NUMBER:
+ case CONST_STRING:
+ case DEX_ITEM_BASED_CONST_STRING:
+ case RETURN:
+ break;
+
+ case GOTO:
+ if (!workList.addIfNotSeen(instruction.asGoto().getTarget())) {
+ return invalid();
+ }
+ break;
+
+ case INSTANCE_PUT:
+ {
+ InstancePut instancePut = instruction.asInstancePut();
+
+ // This must initialize a field on the receiver.
+ if (!instancePut.object().getAliasedValue().isThis()) {
+ return invalid();
+ }
+
+ // Check that this writes a field on the enclosing class.
+ DexField fieldReference = instancePut.getField();
+ DexField lensRewrittenFieldReference =
+ appView.graphLens().lookupField(fieldReference);
+ if (lensRewrittenFieldReference.getHolderType()
+ != instanceInitializer.getHolderType()) {
+ return invalid();
+ }
+
+ ProgramField sourceField =
+ instanceInitializer.getHolder().lookupProgramField(lensRewrittenFieldReference);
+ if (sourceField == null) {
+ return invalid();
+ }
+
+ InstanceFieldInitializationInfo initializationInfo =
+ getInitializationInfo(instancePut.value(), appView, instanceInitializer);
+ if (initializationInfo == null) {
+ return invalid();
+ }
+
+ ProgramField targetField = instanceFieldsMerger.getTargetField(sourceField);
+ assert targetField != null;
+
+ builder.addInstancePut(targetField.getReference(), initializationInfo);
+ break;
+ }
+
+ case INVOKE_DIRECT:
+ {
+ InvokeDirect invoke = instruction.asInvokeDirect();
+
+ // This must initialize the receiver.
+ if (!invoke.getReceiver().getAliasedValue().isThis()) {
+ return invalid();
+ }
+
+ DexMethod invokedMethod = invoke.getInvokedMethod();
+ DexMethod lensRewrittenInvokedMethod =
+ appView
+ .graphLens()
+ .lookupInvokeDirect(invokedMethod, instanceInitializer)
+ .getReference();
+
+ // TODO(b/189296638): Consider allowing constructor forwarding.
+ if (!lensRewrittenInvokedMethod.isInstanceInitializer(appView.dexItemFactory())
+ || lensRewrittenInvokedMethod.getHolderType() != group.getSuperType()) {
+ return invalid();
+ }
+
+ // Extract the non-receiver arguments.
+ List<InstanceFieldInitializationInfo> arguments =
+ new ArrayList<>(invoke.arguments().size() - 1);
+ for (Value argument : Iterables.skip(invoke.arguments(), 1)) {
+ InstanceFieldInitializationInfo initializationInfo =
+ getInitializationInfo(argument, appView, instanceInitializer);
+ if (initializationInfo == null) {
+ return invalid();
+ }
+ arguments.add(initializationInfo);
+ }
+
+ if (!builder.addInvokeConstructor(invokedMethod, arguments)) {
+ return invalid();
+ }
+ }
+ break;
+
+ default:
+ // Not allowed.
+ return invalid();
+ }
+ }
+ }
+
+ return builder.isValid() ? builder.build() : null;
+ }
+
+ private static InstanceFieldInitializationInfo getInitializationInfo(
+ Value value,
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ ProgramMethod instanceInitializer) {
+ InstanceFieldInitializationInfoFactory factory =
+ appView.instanceFieldInitializationInfoFactory();
+
+ Value root = value.getAliasedValue();
+ if (root.isDefinedByInstructionSatisfying(Instruction::isArgument)) {
+ return factory.createArgumentInitializationInfo(
+ value.getDefinition().asArgument().getIndex());
+ }
+
+ AbstractValue abstractValue = value.getAbstractValue(appView, instanceInitializer);
+ if (abstractValue.isSingleConstValue()) {
+ return abstractValue.asSingleConstValue();
+ }
+
+ return null;
+ }
+
+ private static InstanceInitializerDescription invalid() {
return null;
}
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
index 41f2c4e..b67c652 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
@@ -4,14 +4,44 @@
package com.android.tools.r8.horizontalclassmerging;
+import com.android.tools.r8.cf.code.CfConstClass;
+import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfDexItemBasedConstString;
+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.CfLabel;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.InvokeDirectRange;
+import com.android.tools.r8.code.ReturnVoid;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexCode;
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.DexType;
+import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.value.SingleConstValue;
+import com.android.tools.r8.ir.analysis.value.SingleDexItemBasedStringValue;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
+import com.android.tools.r8.utils.IntBox;
+import com.android.tools.r8.utils.IterableUtils;
+import com.google.common.collect.ImmutableList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import org.objectweb.asm.Opcodes;
/**
* A simple abstraction of an instance initializer's code, which allows a parent constructor call
@@ -19,24 +49,219 @@
*/
public class InstanceInitializerDescription {
- private final int arity;
- private final Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignments;
+ // Field assignments that happen prior to the parent constructor call.
+ //
+ // Most fields are generally assigned after the parent constructor call, but both javac and
+ // kotlinc may assign instance fields prior to the parent constructor call. For example, the
+ // synthetic this$0 field for non-static inner classes is typically assigned prior to the parent
+ // constructor call.
+ private final Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignmentsPre;
+
+ // Field assignments that happens after the parent constructor call.
+ private final Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignmentsPost;
+
+ // The parent constructor method and the arguments passed to it.
private final DexMethod parentConstructor;
private final List<InstanceFieldInitializationInfo> parentConstructorArguments;
+ // The constructor parameters, where reference types have been mapped to java.lang.Object, to
+ // ensure we don't group constructors such as <init>(int) and <init>(Object), since this would
+ // lead to type errors.
+ private final DexTypeList relaxedParameters;
+
InstanceInitializerDescription(
- int arity,
- Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignments,
+ Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignmentsPre,
+ Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignmentsPost,
DexMethod parentConstructor,
- List<InstanceFieldInitializationInfo> parentConstructorArguments) {
- this.arity = arity;
- this.instanceFieldAssignments = instanceFieldAssignments;
+ List<InstanceFieldInitializationInfo> parentConstructorArguments,
+ DexTypeList relaxedParameters) {
+ this.instanceFieldAssignmentsPre = instanceFieldAssignmentsPre;
+ this.instanceFieldAssignmentsPost = instanceFieldAssignmentsPost;
this.parentConstructor = parentConstructor;
this.parentConstructorArguments = parentConstructorArguments;
+ this.relaxedParameters = relaxedParameters;
}
- public static Builder builder(ProgramMethod instanceInitializer) {
- return new Builder(instanceInitializer);
+ public static Builder builder(
+ AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod instanceInitializer) {
+ return new Builder(appView.dexItemFactory(), instanceInitializer);
+ }
+
+ /**
+ * Transform this description into actual CF code.
+ *
+ * @param newMethodReference the reference of the method that is being synthesized
+ * @param originalMethodReference the original reference of the representative method
+ * @param syntheticMethodReference the original, synthetic reference of the new method reference
+ * ($r8$init$synthetic)
+ */
+ public CfCode createCfCode(
+ DexMethod newMethodReference,
+ DexMethod originalMethodReference,
+ DexMethod syntheticMethodReference,
+ MergeGroup group,
+ boolean hasClassId,
+ int extraNulls) {
+ int[] argumentToLocalIndex =
+ new int[newMethodReference.getParameters().size() + 1 - extraNulls];
+ int maxLocals = 0;
+ argumentToLocalIndex[0] = maxLocals++;
+ for (int i = 1; i < argumentToLocalIndex.length; i++) {
+ argumentToLocalIndex[i] = maxLocals;
+ maxLocals += newMethodReference.getParameter(i - 1).getRequiredRegisters();
+ }
+
+ IntBox maxStack = new IntBox();
+ ImmutableList.Builder<CfInstruction> instructionBuilder = ImmutableList.builder();
+
+ // Set position.
+ Position callerPosition = Position.synthetic(0, syntheticMethodReference, null);
+ Position calleePosition = Position.synthetic(0, originalMethodReference, callerPosition);
+ CfPosition position = new CfPosition(new CfLabel(), calleePosition);
+ instructionBuilder.add(position);
+ instructionBuilder.add(position.getLabel());
+
+ // Assign class id.
+ if (group.hasClassIdField()) {
+ assert hasClassId;
+ int classIdLocalIndex = maxLocals - 1;
+ instructionBuilder.add(new CfLoad(ValueType.OBJECT, 0));
+ instructionBuilder.add(new CfLoad(ValueType.INT, classIdLocalIndex));
+ instructionBuilder.add(new CfFieldInstruction(Opcodes.PUTFIELD, group.getClassIdField()));
+ maxStack.set(2);
+ } else {
+ assert !hasClassId;
+ }
+
+ // Assign each field.
+ addCfInstructionsForInstanceFieldAssignments(
+ instructionBuilder, instanceFieldAssignmentsPre, argumentToLocalIndex, maxStack);
+
+ // Load receiver for parent constructor call.
+ int stackHeightForParentConstructorCall = 1;
+ instructionBuilder.add(new CfLoad(ValueType.OBJECT, 0));
+
+ // Load constructor arguments.
+ int i = 0;
+ for (InstanceFieldInitializationInfo initializationInfo : parentConstructorArguments) {
+ stackHeightForParentConstructorCall +=
+ addCfInstructionsForInitializationInfo(
+ instructionBuilder,
+ initializationInfo,
+ argumentToLocalIndex,
+ parentConstructor.getParameter(i));
+ i++;
+ }
+
+ // Invoke parent constructor.
+ instructionBuilder.add(new CfInvoke(Opcodes.INVOKESPECIAL, parentConstructor, false));
+ maxStack.setMax(stackHeightForParentConstructorCall);
+
+ // Assign each field.
+ addCfInstructionsForInstanceFieldAssignments(
+ instructionBuilder, instanceFieldAssignmentsPost, argumentToLocalIndex, maxStack);
+
+ // Return.
+ instructionBuilder.add(new CfReturnVoid());
+
+ return new HorizontalClassMergerCfCode(
+ newMethodReference.getHolderType(),
+ maxStack.get(),
+ maxLocals,
+ instructionBuilder.build(),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
+ private static void addCfInstructionsForInstanceFieldAssignments(
+ ImmutableList.Builder<CfInstruction> instructionBuilder,
+ Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignments,
+ int[] argumentToLocalIndex,
+ IntBox maxStack) {
+ instanceFieldAssignments.forEach(
+ (field, initializationInfo) -> {
+ // Load the receiver, the field value, and then set the field.
+ instructionBuilder.add(new CfLoad(ValueType.OBJECT, 0));
+ int stackSizeForInitializationInfo =
+ addCfInstructionsForInitializationInfo(
+ instructionBuilder, initializationInfo, argumentToLocalIndex, field.getType());
+ instructionBuilder.add(new CfFieldInstruction(Opcodes.PUTFIELD, field));
+ maxStack.setMax(stackSizeForInitializationInfo + 1);
+ });
+ }
+
+ private static int addCfInstructionsForInitializationInfo(
+ ImmutableList.Builder<CfInstruction> instructionBuilder,
+ InstanceFieldInitializationInfo initializationInfo,
+ int[] argumentToLocalIndex,
+ DexType type) {
+ if (initializationInfo.isArgumentInitializationInfo()) {
+ int argumentIndex = initializationInfo.asArgumentInitializationInfo().getArgumentIndex();
+ instructionBuilder.add(
+ new CfLoad(ValueType.fromDexType(type), argumentToLocalIndex[argumentIndex]));
+ return type.getRequiredRegisters();
+ }
+
+ assert initializationInfo.isSingleValue();
+ assert initializationInfo.asSingleValue().isSingleConstValue();
+
+ SingleConstValue singleConstValue = initializationInfo.asSingleValue().asSingleConstValue();
+ if (singleConstValue.isSingleConstClassValue()) {
+ instructionBuilder.add(
+ new CfConstClass(singleConstValue.asSingleConstClassValue().getType()));
+ return 1;
+ } else if (singleConstValue.isSingleDexItemBasedStringValue()) {
+ SingleDexItemBasedStringValue dexItemBasedStringValue =
+ singleConstValue.asSingleDexItemBasedStringValue();
+ instructionBuilder.add(
+ new CfDexItemBasedConstString(
+ dexItemBasedStringValue.getItem(), dexItemBasedStringValue.getNameComputationInfo()));
+ return 1;
+ } else if (singleConstValue.isSingleNumberValue()) {
+ if (type.isReferenceType()) {
+ assert singleConstValue.isNull();
+ instructionBuilder.add(new CfConstNull());
+ return 1;
+ } else {
+ instructionBuilder.add(
+ new CfConstNumber(
+ singleConstValue.asSingleNumberValue().getValue(), ValueType.fromDexType(type)));
+ return type.getRequiredRegisters();
+ }
+ } else {
+ assert singleConstValue.isSingleStringValue();
+ instructionBuilder.add(
+ new CfConstString(singleConstValue.asSingleStringValue().getDexString()));
+ return 1;
+ }
+ }
+
+ public DexCode createDexCode(
+ DexMethod newMethodReference,
+ DexMethod originalMethodReference,
+ DexMethod syntheticMethodReference,
+ MergeGroup group,
+ boolean hasClassId,
+ int extraNulls) {
+ assert !hasClassId;
+ assert extraNulls == 0;
+ assert parentConstructorArguments.isEmpty();
+ assert instanceFieldAssignmentsPre.isEmpty();
+ assert instanceFieldAssignmentsPost.isEmpty();
+ Instruction[] instructions = new Instruction[2];
+ instructions[0] = new InvokeDirectRange(0, 1, parentConstructor);
+ instructions[1] = new ReturnVoid();
+ int incomingRegisterSize =
+ 1 + IterableUtils.sumInt(newMethodReference.getParameters(), DexType::getRequiredRegisters);
+ int outgoingRegisterSize = 1;
+ return new DexCode(
+ incomingRegisterSize,
+ incomingRegisterSize,
+ outgoingRegisterSize,
+ instructions,
+ new DexCode.Try[0],
+ new DexCode.TryHandler[0],
+ null);
}
@Override
@@ -45,8 +270,8 @@
return false;
}
InstanceInitializerDescription description = (InstanceInitializerDescription) obj;
- return arity == description.arity
- && instanceFieldAssignments.equals(description.instanceFieldAssignments)
+ return instanceFieldAssignmentsPre.equals(description.instanceFieldAssignmentsPre)
+ && instanceFieldAssignmentsPost.equals(description.instanceFieldAssignmentsPost)
&& parentConstructor == description.parentConstructor
&& parentConstructorArguments.equals(description.parentConstructorArguments);
}
@@ -54,38 +279,67 @@
@Override
public int hashCode() {
return Objects.hash(
- arity, instanceFieldAssignments, parentConstructor, parentConstructorArguments);
+ instanceFieldAssignmentsPre,
+ instanceFieldAssignmentsPost,
+ parentConstructor,
+ parentConstructorArguments,
+ relaxedParameters);
}
public static class Builder {
- private final int arity;
+ private final DexItemFactory dexItemFactory;
+ private final DexTypeList relaxedParameters;
- private Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignments =
+ private Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignmentsPre =
+ new LinkedHashMap<>();
+ private Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignmentsPost =
new LinkedHashMap<>();
private DexMethod parentConstructor;
private List<InstanceFieldInitializationInfo> parentConstructorArguments;
- Builder(ProgramMethod method) {
- this.arity = method.getReference().getArity();
+ Builder(DexItemFactory dexItemFactory, ProgramMethod method) {
+ this.dexItemFactory = dexItemFactory;
+ this.relaxedParameters =
+ method
+ .getParameters()
+ .map(
+ parameter -> parameter.isPrimitiveType() ? parameter : dexItemFactory.objectType);
}
public void addInstancePut(DexField field, InstanceFieldInitializationInfo value) {
- instanceFieldAssignments.put(field, value);
+ // If the parent constructor is java.lang.Object.<init>() then group all the field assignments
+ // before the parent constructor call to allow more sharing.
+ if (parentConstructor == null
+ || parentConstructor == dexItemFactory.objectMembers.constructor) {
+ instanceFieldAssignmentsPre.put(field, value);
+ } else {
+ instanceFieldAssignmentsPost.put(field, value);
+ }
}
- public void addInvokeConstructor(
+ public boolean addInvokeConstructor(
DexMethod method, List<InstanceFieldInitializationInfo> arguments) {
- assert parentConstructor == null;
- assert parentConstructorArguments == null;
- parentConstructor = method;
- parentConstructorArguments = arguments;
+ if (parentConstructor == null) {
+ parentConstructor = method;
+ parentConstructorArguments = arguments;
+ return true;
+ }
+ return false;
}
public InstanceInitializerDescription build() {
- assert parentConstructor != null;
+ assert isValid();
return new InstanceInitializerDescription(
- arity, instanceFieldAssignments, parentConstructor, parentConstructorArguments);
+ instanceFieldAssignmentsPre,
+ instanceFieldAssignmentsPost,
+ parentConstructor,
+ parentConstructorArguments,
+ relaxedParameters);
+ }
+
+ public boolean isValid() {
+ return parentConstructor != null;
}
}
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
index c2063f3..862a23c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
@@ -10,11 +10,13 @@
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeUtils;
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
@@ -24,61 +26,62 @@
import com.android.tools.r8.ir.conversion.ExtraConstantIntParameter;
import com.android.tools.r8.ir.conversion.ExtraParameter;
import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
-import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.structural.Ordered;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Iterator;
import java.util.List;
public class InstanceInitializerMerger {
- private final AppView<?> appView;
+ private final AppView<? extends AppInfoWithClassHierarchy> appView;
+ private final Reference2IntMap<DexType> classIdentifiers;
private final DexItemFactory dexItemFactory;
private final MergeGroup group;
private final List<ProgramMethod> instanceInitializers;
private final InstanceInitializerDescription instanceInitializerDescription;
+ private final HorizontalClassMergerGraphLens.Builder lensBuilder;
private final Mode mode;
InstanceInitializerMerger(
- AppView<?> appView, MergeGroup group, List<ProgramMethod> instanceInitializers, Mode mode) {
- this(appView, group, instanceInitializers, mode, null);
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ Reference2IntMap<DexType> classIdentifiers,
+ MergeGroup group,
+ List<ProgramMethod> instanceInitializers,
+ HorizontalClassMergerGraphLens.Builder lensBuilder,
+ Mode mode) {
+ this(appView, classIdentifiers, group, instanceInitializers, lensBuilder, mode, null);
}
InstanceInitializerMerger(
- AppView<?> appView,
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ Reference2IntMap<DexType> classIdentifiers,
MergeGroup group,
List<ProgramMethod> instanceInitializers,
+ HorizontalClassMergerGraphLens.Builder lensBuilder,
Mode mode,
InstanceInitializerDescription instanceInitializerDescription) {
this.appView = appView;
+ this.classIdentifiers = classIdentifiers;
this.dexItemFactory = appView.dexItemFactory();
this.group = group;
this.instanceInitializers = instanceInitializers;
this.instanceInitializerDescription = instanceInitializerDescription;
+ this.lensBuilder = lensBuilder;
this.mode = mode;
- // Constructors should not be empty and all constructors should have the same prototype.
+ // Constructors should not be empty and all constructors should have the same prototype unless
+ // equivalent.
assert !instanceInitializers.isEmpty();
- assert instanceInitializers.stream().map(ProgramMethod::getProto).distinct().count() == 1;
- }
-
- /**
- * The method reference template describes which arguments the constructor must have, and is used
- * to generate the final reference by appending null arguments until it is fresh.
- */
- private DexMethod generateReferenceMethodTemplate() {
- DexMethod methodTemplate = instanceInitializers.iterator().next().getReference();
- if (instanceInitializers.size() > 1) {
- methodTemplate = dexItemFactory.appendTypeToMethod(methodTemplate, dexItemFactory.intType);
- }
- return methodTemplate.withHolder(group.getTarget(), dexItemFactory);
+ assert instanceInitializers.stream().map(ProgramMethod::getProto).distinct().count() == 1
+ || instanceInitializerDescription != null;
}
public int getArity() {
@@ -89,6 +92,78 @@
return instanceInitializers;
}
+ private CfVersion getNewClassFileVersion() {
+ CfVersion classFileVersion = null;
+ for (ProgramMethod instanceInitializer : instanceInitializers) {
+ if (instanceInitializer.getDefinition().hasClassFileVersion()) {
+ classFileVersion =
+ Ordered.maxIgnoreNull(
+ classFileVersion, instanceInitializer.getDefinition().getClassFileVersion());
+ }
+ }
+ return classFileVersion;
+ }
+
+ private DexMethod getNewMethodReference(ProgramMethod representative) {
+ return getNewMethodReference(representative, false);
+ }
+
+ private DexMethod getNewMethodReference(ProgramMethod representative, boolean needsClassId) {
+ DexType[] oldParameters = representative.getParameters().values;
+ DexType[] newParameters =
+ new DexType[representative.getParameters().size() + BooleanUtils.intValue(needsClassId)];
+ System.arraycopy(oldParameters, 0, newParameters, 0, oldParameters.length);
+ for (int i = 0; i < oldParameters.length; i++) {
+ final int parameterIndex = i;
+ newParameters[i] =
+ DexTypeUtils.computeLeastUpperBound(
+ appView,
+ Iterables.transform(
+ instanceInitializers,
+ instanceInitializer -> instanceInitializer.getParameter(parameterIndex)));
+ }
+ if (needsClassId) {
+ assert ArrayUtils.last(newParameters) == null;
+ newParameters[newParameters.length - 1] = dexItemFactory.intType;
+ }
+ return dexItemFactory.createInstanceInitializer(group.getTarget().getType(), newParameters);
+ }
+
+ private DexMethod getOriginalMethodReference() {
+ return appView.graphLens().getOriginalMethodSignature(getRepresentative().getReference());
+ }
+
+ private ProgramMethod getRepresentative() {
+ return ListUtils.first(instanceInitializers);
+ }
+
+ /**
+ * Returns a special original method signature for the synthesized constructor that did not exist
+ * prior to horizontal class merging. Otherwise we might accidentally think that the synthesized
+ * constructor corresponds to the previous <init>() method on the target class, which could have
+ * unintended side-effects such as leading to unused argument removal being applied to the
+ * synthesized constructor all-though it by construction doesn't have any unused arguments.
+ */
+ private DexMethod getSyntheticMethodReference(
+ ClassMethodsBuilder classMethodsBuilder, ProgramMethod representative) {
+ return dexItemFactory.createFreshMethodNameWithoutHolder(
+ "$r8$init$synthetic",
+ representative.getProto(),
+ representative.getHolderType(),
+ classMethodsBuilder::isFresh);
+ }
+
+ private Int2ReferenceSortedMap<DexMethod> createClassIdToInstanceInitializerMap() {
+ assert !hasInstanceInitializerDescription();
+ Int2ReferenceSortedMap<DexMethod> typeConstructorClassMap = new Int2ReferenceAVLTreeMap<>();
+ for (ProgramMethod instanceInitializer : instanceInitializers) {
+ typeConstructorClassMap.put(
+ classIdentifiers.getInt(instanceInitializer.getHolderType()),
+ lensBuilder.getRenamedMethodSignature(instanceInitializer.getReference()));
+ }
+ return typeConstructorClassMap;
+ }
+
public int size() {
return instanceInitializers.size();
}
@@ -96,12 +171,20 @@
public static class Builder {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
+ private final Reference2IntMap<DexType> classIdentifiers;
private int estimatedDexCodeSize;
private final List<List<ProgramMethod>> instanceInitializerGroups = new ArrayList<>();
+ private final HorizontalClassMergerGraphLens.Builder lensBuilder;
private final Mode mode;
- public Builder(AppView<? extends AppInfoWithClassHierarchy> appView, Mode mode) {
+ public Builder(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ Reference2IntMap<DexType> classIdentifiers,
+ HorizontalClassMergerGraphLens.Builder lensBuilder,
+ Mode mode) {
this.appView = appView;
+ this.classIdentifiers = classIdentifiers;
+ this.lensBuilder = lensBuilder;
this.mode = mode;
createNewGroup();
}
@@ -136,7 +219,8 @@
return ListUtils.map(
instanceInitializerGroups,
instanceInitializers ->
- new InstanceInitializerMerger(appView, group, instanceInitializers, mode));
+ new InstanceInitializerMerger(
+ appView, classIdentifiers, group, instanceInitializers, lensBuilder, mode));
}
public InstanceInitializerMerger buildSingle(
@@ -145,56 +229,18 @@
assert instanceInitializerGroups.size() == 1;
List<ProgramMethod> instanceInitializers = ListUtils.first(instanceInitializerGroups);
return new InstanceInitializerMerger(
- appView, group, instanceInitializers, mode, instanceInitializerDescription);
+ appView,
+ classIdentifiers,
+ group,
+ instanceInitializers,
+ lensBuilder,
+ mode,
+ instanceInitializerDescription);
}
}
- // Returns true if we can simply use an existing constructor as the new constructor.
- private boolean isTrivialMerge(ClassMethodsBuilder classMethodsBuilder) {
- if (group.hasClassIdField()) {
- // We need to set the class id field.
- return false;
- }
- DexMethod trivialInstanceInitializerReference =
- ListUtils.first(instanceInitializers)
- .getReference()
- .withHolder(group.getTarget(), dexItemFactory);
- if (!classMethodsBuilder.isFresh(trivialInstanceInitializerReference)) {
- // We need to append null arguments for disambiguation.
- return false;
- }
- return isMergeOfEquivalentInstanceInitializers();
- }
-
- private boolean isMergeOfEquivalentInstanceInitializers() {
- Iterator<ProgramMethod> instanceInitializerIterator = instanceInitializers.iterator();
- ProgramMethod firstInstanceInitializer = instanceInitializerIterator.next();
- if (!instanceInitializerIterator.hasNext()) {
- return true;
- }
- // We need all the constructors to be equivalent.
- InstanceInitializerInfo instanceInitializerInfo =
- firstInstanceInitializer
- .getDefinition()
- .getOptimizationInfo()
- .getContextInsensitiveInstanceInitializerInfo();
- if (!instanceInitializerInfo.hasParent()) {
- // We don't know the parent constructor of the first constructor.
- return false;
- }
- DexMethod parent = instanceInitializerInfo.getParent();
- return Iterables.all(
- instanceInitializers,
- instanceInitializer ->
- isSideEffectFreeInstanceInitializerWithParent(instanceInitializer, parent));
- }
-
- private boolean isSideEffectFreeInstanceInitializerWithParent(
- ProgramMethod instanceInitializer, DexMethod parent) {
- MethodOptimizationInfo optimizationInfo =
- instanceInitializer.getDefinition().getOptimizationInfo();
- return !optimizationInfo.mayHaveSideEffects()
- && optimizationInfo.getContextInsensitiveInstanceInitializerInfo().getParent() == parent;
+ private boolean hasInstanceInitializerDescription() {
+ return instanceInitializerDescription != null;
}
private DexMethod moveInstanceInitializer(
@@ -219,132 +265,118 @@
return method;
}
- private MethodAccessFlags getAccessFlags() {
+ private MethodAccessFlags getNewAccessFlags() {
// TODO(b/164998929): ensure this behaviour is correct, should probably calculate upper bound
return MethodAccessFlags.fromSharedAccessFlags(
Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, true);
}
+ private Code getNewCode(
+ DexMethod newMethodReference,
+ DexMethod syntheticMethodReference,
+ boolean needsClassId,
+ int extraNulls) {
+ if (hasInstanceInitializerDescription()) {
+ if (mode.isInitial() || appView.options().isGeneratingClassFiles()) {
+ return instanceInitializerDescription.createCfCode(
+ newMethodReference,
+ getOriginalMethodReference(),
+ syntheticMethodReference,
+ group,
+ needsClassId,
+ extraNulls);
+ }
+ return instanceInitializerDescription.createDexCode(
+ newMethodReference,
+ getOriginalMethodReference(),
+ syntheticMethodReference,
+ group,
+ needsClassId,
+ extraNulls);
+ }
+ if (isSingleton() && !group.hasClassIdField()) {
+ return getRepresentative().getDefinition().getCode();
+ }
+ return new ConstructorEntryPointSynthesizedCode(
+ createClassIdToInstanceInitializerMap(),
+ newMethodReference,
+ group.hasClassIdField() ? group.getClassIdField() : null,
+ syntheticMethodReference);
+ }
+
+ private boolean isSingleton() {
+ return instanceInitializers.size() == 1;
+ }
+
/** Synthesize a new method which selects the constructor based on a parameter type. */
void merge(
ClassMethodsBuilder classMethodsBuilder,
- HorizontalClassMergerGraphLens.Builder lensBuilder,
- Reference2IntMap<DexType> classIdentifiers,
SyntheticArgumentClass syntheticArgumentClass) {
- // TODO(b/189296638): Handle merging of equivalent constructors when
- // `instanceInitializerDescription` is set.
- if (isTrivialMerge(classMethodsBuilder)) {
- mergeTrivial(classMethodsBuilder, lensBuilder);
- return;
- }
-
- assert mode.isInitial();
-
- // Tree map as must be sorted.
- Int2ReferenceSortedMap<DexMethod> typeConstructorClassMap = new Int2ReferenceAVLTreeMap<>();
-
- // Move constructors to target class.
- CfVersion classFileVersion = null;
- for (ProgramMethod instanceInitializer : instanceInitializers) {
- if (instanceInitializer.getDefinition().hasClassFileVersion()) {
- classFileVersion =
- Ordered.maxIgnoreNull(
- classFileVersion, instanceInitializer.getDefinition().getClassFileVersion());
- }
- DexMethod movedInstanceInitializer =
- moveInstanceInitializer(classMethodsBuilder, instanceInitializer);
- lensBuilder.mapMethod(movedInstanceInitializer, movedInstanceInitializer);
- lensBuilder.recordNewMethodSignature(
- instanceInitializer.getReference(), movedInstanceInitializer);
- typeConstructorClassMap.put(
- classIdentifiers.getInt(instanceInitializer.getHolderType()), movedInstanceInitializer);
- }
-
- // Create merged constructor reference.
- DexMethod methodReferenceTemplate = generateReferenceMethodTemplate();
- DexMethod newConstructorReference =
- dexItemFactory.createInstanceInitializerWithFreshProto(
- methodReferenceTemplate,
- syntheticArgumentClass.getArgumentClasses(),
- classMethodsBuilder::isFresh);
- int extraNulls = newConstructorReference.getArity() - methodReferenceTemplate.getArity();
-
ProgramMethod representative = ListUtils.first(instanceInitializers);
- DexMethod originalConstructorReference =
- appView.graphLens().getOriginalMethodSignature(representative.getReference());
- // Create a special original method signature for the synthesized constructor that did not exist
- // prior to horizontal class merging. Otherwise we might accidentally think that the synthesized
- // constructor corresponds to the previous <init>() method on the target class, which could have
- // unintended side-effects such as leading to unused argument removal being applied to the
- // synthesized constructor all-though it by construction doesn't have any unused arguments.
- DexMethod bridgeConstructorReference =
- dexItemFactory.createFreshMethodNameWithoutHolder(
- "$r8$init$bridge",
- originalConstructorReference.getProto(),
- originalConstructorReference.getHolderType(),
+ // Create merged instance initializer reference.
+ boolean needsClassId =
+ instanceInitializers.size() > 1
+ && (!hasInstanceInitializerDescription() || group.hasClassIdField());
+ assert mode.isInitial() || !needsClassId;
+
+ DexMethod newMethodReferenceTemplate = getNewMethodReference(representative, needsClassId);
+ assert mode.isInitial() || classMethodsBuilder.isFresh(newMethodReferenceTemplate);
+
+ DexMethod newMethodReference =
+ dexItemFactory.createInstanceInitializerWithFreshProto(
+ newMethodReferenceTemplate,
+ mode.isInitial() ? syntheticArgumentClass.getArgumentClasses() : ImmutableList.of(),
classMethodsBuilder::isFresh);
+ int extraNulls = newMethodReference.getArity() - newMethodReferenceTemplate.getArity();
- ConstructorEntryPointSynthesizedCode synthesizedCode =
- new ConstructorEntryPointSynthesizedCode(
- typeConstructorClassMap,
- newConstructorReference,
- group.hasClassIdField() ? group.getClassIdField() : null,
- bridgeConstructorReference);
- DexEncodedMethod newConstructor =
- new DexEncodedMethod(
- newConstructorReference,
- getAccessFlags(),
- MethodTypeSignature.noSignature(),
- DexAnnotationSet.empty(),
- ParameterAnnotationsList.empty(),
- synthesizedCode,
- true,
- classFileVersion);
+ // Verify that the merge is a simple renaming in the final round of merging.
+ assert mode.isInitial() || newMethodReference == newMethodReferenceTemplate;
- // Map each old constructor to the newly synthesized constructor in the graph lens.
- for (ProgramMethod oldInstanceInitializer : instanceInitializers) {
+ // Move instance initializers to target class.
+ if (hasInstanceInitializerDescription()) {
+ lensBuilder.moveMethods(instanceInitializers, newMethodReference);
+ } else if (isSingleton() && !group.hasClassIdField()) {
+ lensBuilder.moveMethod(representative.getReference(), newMethodReference, true);
+ } else {
+ for (ProgramMethod instanceInitializer : instanceInitializers) {
+ DexMethod movedInstanceInitializer =
+ moveInstanceInitializer(classMethodsBuilder, instanceInitializer);
+ lensBuilder.mapMethod(movedInstanceInitializer, movedInstanceInitializer);
+ lensBuilder.recordNewMethodSignature(
+ instanceInitializer.getReference(), movedInstanceInitializer);
+ }
+ }
+
+ // Add a mapping from a synthetic name to the synthetic constructor.
+ DexMethod syntheticMethodReference =
+ getSyntheticMethodReference(classMethodsBuilder, representative);
+ if (!isSingleton() || group.hasClassIdField()) {
+ lensBuilder.recordNewMethodSignature(syntheticMethodReference, newMethodReference, true);
+ }
+
+ // Map each of the instance initializers to the new instance initializer in the graph lens.
+ for (ProgramMethod instanceInitializer : instanceInitializers) {
List<ExtraParameter> extraParameters = new ArrayList<>();
- if (instanceInitializers.size() > 1) {
- int classIdentifier = classIdentifiers.getInt(oldInstanceInitializer.getHolderType());
+ if (needsClassId) {
+ int classIdentifier = classIdentifiers.getInt(instanceInitializer.getHolderType());
extraParameters.add(new ExtraConstantIntParameter(classIdentifier));
}
extraParameters.addAll(Collections.nCopies(extraNulls, new ExtraUnusedNullParameter()));
lensBuilder.mapMergedConstructor(
- oldInstanceInitializer.getReference(), newConstructorReference, extraParameters);
+ instanceInitializer.getReference(), newMethodReference, extraParameters);
}
- // Add a mapping from a synthetic name to the synthetic constructor.
- lensBuilder.recordNewMethodSignature(bridgeConstructorReference, newConstructorReference);
-
- classMethodsBuilder.addDirectMethod(newConstructor);
- }
-
- private void mergeTrivial(
- ClassMethodsBuilder classMethodsBuilder, HorizontalClassMergerGraphLens.Builder lensBuilder) {
- ProgramMethod representative = ListUtils.first(instanceInitializers);
- DexMethod newMethodReference =
- representative.getReference().withHolder(group.getTarget(), dexItemFactory);
-
- for (ProgramMethod constructor : instanceInitializers) {
- boolean isRepresentative = constructor == representative;
- lensBuilder.moveMethod(constructor.getReference(), newMethodReference, isRepresentative);
- }
-
- DexEncodedMethod newMethod =
- representative.getHolder() == group.getTarget()
- ? representative.getDefinition()
- : representative.getDefinition().toTypeSubstitutedMethod(newMethodReference);
- fixupAccessFlagsForTrivialMerge(newMethod.getAccessFlags());
-
- classMethodsBuilder.addDirectMethod(newMethod);
- }
-
- private void fixupAccessFlagsForTrivialMerge(MethodAccessFlags accessFlags) {
- if (!accessFlags.isPublic()) {
- accessFlags.unsetPrivate();
- accessFlags.unsetProtected();
- accessFlags.setPublic();
- }
+ DexEncodedMethod newInstanceInitializer =
+ new DexEncodedMethod(
+ newMethodReference,
+ getNewAccessFlags(),
+ MethodTypeSignature.noSignature(),
+ DexAnnotationSet.empty(),
+ ParameterAnnotationsList.empty(),
+ getNewCode(newMethodReference, syntheticMethodReference, needsClassId, extraNulls),
+ true,
+ getNewClassFileVersion());
+ classMethodsBuilder.addDirectMethod(newInstanceInitializer);
}
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
index ed7e099..b5e485b 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
@@ -10,9 +10,11 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
import com.android.tools.r8.horizontalclassmerging.InstanceInitializerMerger.Builder;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
@@ -30,14 +32,15 @@
List<InstanceInitializerMerger> instanceInitializerMergers,
Map<InstanceInitializerDescription, InstanceInitializerMerger>
equivalentInstanceInitializerMergers) {
- assert equivalentInstanceInitializerMergers.isEmpty();
this.instanceInitializerMergers = instanceInitializerMergers;
this.equivalentInstanceInitializerMergers = equivalentInstanceInitializerMergers;
}
public static InstanceInitializerMergerCollection create(
AppView<? extends AppInfoWithClassHierarchy> appView,
+ Reference2IntMap<DexType> classIdentifiers,
MergeGroup group,
+ ClassInstanceFieldsMerger instanceFieldsMerger,
HorizontalClassMergerGraphLens.Builder lensBuilder,
Mode mode) {
// Create an instance initializer merger for each group of instance initializers that are
@@ -50,12 +53,16 @@
DexEncodedMethod::isInstanceInitializer,
instanceInitializer -> {
InstanceInitializerDescription description =
- InstanceInitializerAnalysis.analyze(instanceInitializer, lensBuilder);
+ InstanceInitializerAnalysis.analyze(
+ appView, group, instanceInitializer, instanceFieldsMerger, mode);
if (description != null) {
buildersByDescription
.computeIfAbsent(
description,
- ignoreKey(() -> new InstanceInitializerMerger.Builder(appView, mode)))
+ ignoreKey(
+ () ->
+ new InstanceInitializerMerger.Builder(
+ appView, classIdentifiers, lensBuilder, mode)))
.addEquivalent(instanceInitializer);
} else {
buildersWithoutDescription.add(instanceInitializer);
@@ -86,7 +93,9 @@
buildersByProto
.computeIfAbsent(
instanceInitializer.getDefinition().getProto(),
- ignore -> new InstanceInitializerMerger.Builder(appView, mode))
+ ignore ->
+ new InstanceInitializerMerger.Builder(
+ appView, classIdentifiers, lensBuilder, mode))
.add(instanceInitializer));
for (InstanceInitializerMerger.Builder builder : buildersByProto.values()) {
instanceInitializerMergers.addAll(builder.build(group));
@@ -95,7 +104,8 @@
buildersWithoutDescription.forEach(
instanceInitializer ->
instanceInitializerMergers.addAll(
- new InstanceInitializerMerger.Builder(appView, mode)
+ new InstanceInitializerMerger.Builder(
+ appView, classIdentifiers, lensBuilder, mode)
.add(instanceInitializer)
.build(group)));
}
@@ -110,5 +120,6 @@
public void forEach(Consumer<InstanceInitializerMerger> consumer) {
instanceInitializerMergers.forEach(consumer);
+ equivalentInstanceInitializerMergers.values().forEach(consumer);
}
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
index 11fa11e..ade97ad 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
@@ -152,13 +152,10 @@
for (CfInstruction instruction : code.getInstructions()) {
if (instruction.isPosition()) {
CfPosition cfPosition = instruction.asPosition();
- Position position = cfPosition.getPosition();
newInstructions.add(
new CfPosition(
cfPosition.getLabel(),
- position.hasCallerPosition()
- ? position
- : position.withCallerPosition(callerPosition)));
+ cfPosition.getPosition().withOutermostCallerPosition(callerPosition)));
} else if (instruction.isReturn()) {
if (code.getInstructions().size() != index) {
newInstructions.add(new CfGoto(endLabel));
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceInitializerMerging.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceInitializerMerging.java
index bbc5e39..0ff172c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceInitializerMerging.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceInitializerMerging.java
@@ -86,6 +86,11 @@
InstanceInitializerInfo otherInstanceInitializerInfo =
other.getDefinition().getOptimizationInfo().getContextInsensitiveInstanceInitializerInfo();
assert otherInstanceInitializerInfo.isNonTrivialInstanceInitializerInfo();
+ if (!instanceInitializerInfo.hasParent()
+ || instanceInitializerInfo.getParent().getArity() > 0) {
+ return false;
+ }
+
if (instanceInitializerInfo.getParent() != otherInstanceInitializerInfo.getParent()) {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java
index f2d8962..13df457 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java
@@ -34,6 +34,14 @@
this.nameComputationInfo = nameComputationInfo;
}
+ public DexReference getItem() {
+ return item;
+ }
+
+ public NameComputationInfo<?> getNameComputationInfo() {
+ return nameComputationInfo;
+ }
+
@Override
public boolean isSingleDexItemBasedStringValue() {
return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Position.java b/src/main/java/com/android/tools/r8/ir/code/Position.java
index 6cba1ee..09a2631 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Position.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Position.java
@@ -137,10 +137,21 @@
return lastPosition;
}
+ public Position getCallerPosition() {
+ return callerPosition;
+ }
+
public Position withCallerPosition(Position callerPosition) {
return new Position(line, file, method, callerPosition, synthetic);
}
+ public Position withOutermostCallerPosition(Position newOutermostCallerPosition) {
+ return withCallerPosition(
+ hasCallerPosition()
+ ? getCallerPosition().withOutermostCallerPosition(newOutermostCallerPosition)
+ : newOutermostCallerPosition);
+ }
+
@Override
public boolean equals(Object other) {
return other instanceof Position && compareTo((Position) other) == 0;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
index 8f83dca..83e0ff5 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
@@ -107,6 +107,9 @@
assert instructionDesugaringEventConsumer.verifyNothingToFinalize();
}
+ converter.finalizeDesugaredLibraryRetargeting(instructionDesugaringEventConsumer);
+ assert instructionDesugaringEventConsumer.verifyNothingToFinalize();
+
classes = deferred;
}
}
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 fea728d..8ae52ef 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
@@ -222,10 +222,7 @@
assert options.desugarState.isOn();
this.instructionDesugaring = CfInstructionDesugaringCollection.create(appView);
this.classDesugaring = instructionDesugaring.createClassDesugaringCollection();
- this.desugaredLibraryRetargeter =
- options.desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()
- ? null
- : new DesugaredLibraryRetargeter(appView);
+ this.desugaredLibraryRetargeter = null; // Managed cf to cf.
this.interfaceMethodRewriter =
options.desugaredLibraryConfiguration.getEmulateLibraryInterface().isEmpty()
? null
@@ -258,14 +255,15 @@
? CfInstructionDesugaringCollection.empty()
: CfInstructionDesugaringCollection.create(appView);
this.classDesugaring = instructionDesugaring.createClassDesugaringCollection();
+ this.desugaredLibraryRetargeter =
+ options.desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()
+ || !appView.enableWholeProgramOptimizations()
+ ? null
+ : new DesugaredLibraryRetargeter(appView);
this.interfaceMethodRewriter =
options.isInterfaceMethodDesugaringEnabled()
? new InterfaceMethodRewriter(appView, this)
: null;
- this.desugaredLibraryRetargeter =
- options.desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()
- ? null
- : new DesugaredLibraryRetargeter(appView);
this.covariantReturnTypeAnnotationTransformer =
options.processCovariantReturnTypeAnnotations
? new CovariantReturnTypeAnnotationTransformer(this, appView.dexItemFactory())
@@ -371,6 +369,12 @@
D8NestBasedAccessDesugaring::clearNestAttributes);
}
+ public void finalizeDesugaredLibraryRetargeting(
+ D8CfInstructionDesugaringEventConsumer instructionDesugaringEventConsumer) {
+ instructionDesugaring.withDesugaredLibraryRetargeter(
+ retargeter -> retargeter.finalizeDesugaring(instructionDesugaringEventConsumer));
+ }
+
private void staticizeClasses(
OptimizationFeedback feedback, ExecutorService executorService, GraphLens applied)
throws ExecutionException {
@@ -397,13 +401,13 @@
}
}
- private void synthesizeRetargetClass(Builder<?> builder, ExecutorService executorService)
- throws ExecutionException {
+ private void synthesizeRetargetClass(ExecutorService executorService) throws ExecutionException {
if (desugaredLibraryRetargeter != null) {
- desugaredLibraryRetargeter.synthesizeRetargetClasses(builder, executorService, this);
+ desugaredLibraryRetargeter.synthesizeRetargetClasses(this, executorService);
}
}
+
private void synthesizeEnumUnboxingUtilityMethods(ExecutorService executorService)
throws ExecutionException {
if (enumUnboxer != null) {
@@ -442,7 +446,6 @@
Builder<?> builder = application.builder().setHighestSortingString(highestSortingString);
desugarInterfaceMethods(builder, ExcludeDexResources, executor);
- synthesizeRetargetClass(builder, executor);
processCovariantReturnTypeAnnotations(builder);
generateDesugaredLibraryAPIWrappers(builder, executor);
@@ -588,8 +591,6 @@
new NeedsIRDesugarUseRegistry(
method,
appView,
- desugaredLibraryRetargeter,
- interfaceMethodRewriter,
desugaredLibraryAPIConverter);
method.registerCodeReferences(useRegistry);
return useRegistry.needsDesugaring();
@@ -780,7 +781,7 @@
feedback.updateVisibleOptimizationInfo();
printPhase("Utility classes synthesis");
- synthesizeRetargetClass(builder, executorService);
+ synthesizeRetargetClass(executorService);
synthesizeEnumUnboxingUtilityMethods(executorService);
printPhase("Desugared library API Conversion finalization");
@@ -898,11 +899,15 @@
}
private void computeReachabilitySensitivity(DexApplication application) {
- application.classes().forEach(c -> {
- if (c.hasReachabilitySensitiveAnnotation(options.itemFactory)) {
- c.methods().forEach(m -> m.getMutableOptimizationInfo().setReachabilitySensitive(true));
- }
- });
+ application
+ .classes()
+ .forEach(
+ c -> {
+ if (c.hasReachabilitySensitiveAnnotation(options.itemFactory)) {
+ c.methods()
+ .forEach(m -> m.getMutableOptimizationInfo().setReachabilitySensitive(true));
+ }
+ });
}
private void forEachSelectedOutliningMethod(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java b/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
index bbc0bcb..cb8eeab 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
@@ -4,12 +4,6 @@
package com.android.tools.r8.ir.conversion;
-import static com.android.tools.r8.ir.code.Invoke.Type.DIRECT;
-import static com.android.tools.r8.ir.code.Invoke.Type.INTERFACE;
-import static com.android.tools.r8.ir.code.Invoke.Type.STATIC;
-import static com.android.tools.r8.ir.code.Invoke.Type.SUPER;
-import static com.android.tools.r8.ir.code.Invoke.Type.VIRTUAL;
-
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexField;
@@ -17,29 +11,20 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
-import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
class NeedsIRDesugarUseRegistry extends UseRegistry {
private boolean needsDesugaring = false;
private final ProgramMethod context;
- private final DesugaredLibraryRetargeter desugaredLibraryRetargeter;
- private final InterfaceMethodRewriter interfaceMethodRewriter;
private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
public NeedsIRDesugarUseRegistry(
ProgramMethod method,
AppView<?> appView,
- DesugaredLibraryRetargeter desugaredLibraryRetargeter,
- InterfaceMethodRewriter interfaceMethodRewriter,
DesugaredLibraryAPIConverter desugaredLibraryAPIConverter) {
super(appView.dexItemFactory());
this.context = method;
- this.desugaredLibraryRetargeter = desugaredLibraryRetargeter;
- this.interfaceMethodRewriter = interfaceMethodRewriter;
this.desugaredLibraryAPIConverter = desugaredLibraryAPIConverter;
}
@@ -58,26 +43,14 @@
@Override
public void registerInvokeVirtual(DexMethod method) {
- registerLibraryRetargeting(method, false);
- registerInterfaceMethodRewriting(method, VIRTUAL);
registerDesugaredLibraryAPIConverter(method);
}
@Override
public void registerInvokeDirect(DexMethod method) {
- registerLibraryRetargeting(method, false);
- registerInterfaceMethodRewriting(method, DIRECT);
registerDesugaredLibraryAPIConverter(method);
}
- private void registerInterfaceMethodRewriting(DexMethod method, Type invokeType) {
- if (!needsDesugaring) {
- needsDesugaring =
- interfaceMethodRewriter != null
- && interfaceMethodRewriter.needsRewriting(method, invokeType, context);
- }
- }
-
private void registerDesugaredLibraryAPIConverter(DexMethod method) {
if (!needsDesugaring) {
needsDesugaring =
@@ -86,33 +59,18 @@
}
}
- private void registerLibraryRetargeting(DexMethod method, boolean b) {
- if (!needsDesugaring) {
- needsDesugaring =
- desugaredLibraryRetargeter != null
- && desugaredLibraryRetargeter.getRetargetedMethod(method, b) != null;
- }
- }
-
@Override
public void registerInvokeStatic(DexMethod method) {
- registerLibraryRetargeting(method, false);
- registerInterfaceMethodRewriting(method, STATIC);
registerDesugaredLibraryAPIConverter(method);
}
@Override
public void registerInvokeInterface(DexMethod method) {
- registerLibraryRetargeting(method, true);
- registerInterfaceMethodRewriting(method, INTERFACE);
registerDesugaredLibraryAPIConverter(method);
}
@Override
public void registerInvokeStatic(DexMethod method, boolean itf) {
- if (itf) {
- needsDesugaring = true;
- }
registerInvokeStatic(method);
}
@@ -124,8 +82,6 @@
@Override
public void registerInvokeSuper(DexMethod method) {
- registerLibraryRetargeting(method, false);
- registerInterfaceMethodRewriting(method, SUPER);
registerDesugaredLibraryAPIConverter(method);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index 4623428..21608cf 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.ThreadUtils;
@@ -168,13 +169,15 @@
super.prepareForWaveExtensionProcessing();
}
- void forEachWaveWithExtension(OptimizationFeedback feedback, ExecutorService executorService)
+ void forEachWaveWithExtension(
+ OptimizationFeedbackDelayed feedback, ExecutorService executorService)
throws ExecutionException {
while (!waves.isEmpty()) {
wave = waves.removeFirst();
assert !wave.isEmpty();
assert waveExtension.isEmpty();
do {
+ assert feedback.noUpdatesLeft();
ThreadUtils.processItems(
wave,
method -> {
@@ -184,6 +187,7 @@
forEachMethod(method, codeOptimizations, feedback);
},
executorService);
+ feedback.updateVisibleOptimizationInfo();
processed.addAll(wave);
prepareForWaveExtensionProcessing();
} while (!wave.isEmpty());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
index 3fb9329..78291d7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
@@ -56,5 +56,8 @@
public abstract <T extends Throwable> void withD8NestBasedAccessDesugaring(
ThrowingConsumer<D8NestBasedAccessDesugaring, T> consumer) throws T;
+ public abstract void withDesugaredLibraryRetargeter(
+ Consumer<DesugaredLibraryRetargeter> consumer);
+
public abstract void withRecordRewriter(Consumer<RecordRewriter> consumer);
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index 6951b39..1c6b337 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClasspathClass;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.ProgramField;
@@ -42,7 +43,8 @@
NestBasedAccessDesugaringEventConsumer,
RecordDesugaringEventConsumer,
TwrCloseResourceDesugaringEventConsumer,
- InterfaceMethodDesugaringEventConsumer {
+ InterfaceMethodDesugaringEventConsumer,
+ DesugaredLibraryRetargeterEventConsumer {
public static D8CfInstructionDesugaringEventConsumer createForD8(
D8MethodProcessor methodProcessor) {
@@ -61,6 +63,21 @@
return new CfInstructionDesugaringEventConsumer() {
@Override
+ public void acceptDesugaredLibraryRetargeterDispatchProgramClass(DexProgramClass clazz) {
+ assert false;
+ }
+
+ @Override
+ public void acceptDesugaredLibraryRetargeterDispatchClasspathClass(DexClasspathClass clazz) {
+ assert false;
+ }
+
+ @Override
+ public void acceptForwardingMethod(ProgramMethod method) {
+ assert false;
+ }
+
+ @Override
public void acceptThrowMethod(ProgramMethod method, ProgramMethod context) {
assert false;
}
@@ -132,6 +149,21 @@
}
@Override
+ public void acceptDesugaredLibraryRetargeterDispatchProgramClass(DexProgramClass clazz) {
+ methodProcessor.scheduleDesugaredMethodsForProcessing(clazz.programMethods());
+ }
+
+ @Override
+ public void acceptDesugaredLibraryRetargeterDispatchClasspathClass(DexClasspathClass clazz) {
+ // Intentionnaly empty.
+ }
+
+ @Override
+ public void acceptForwardingMethod(ProgramMethod method) {
+ methodProcessor.scheduleDesugaredMethodForProcessing(method);
+ }
+
+ @Override
public void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context) {
methodProcessor.scheduleMethodForProcessing(backportedMethod, this);
}
@@ -270,6 +302,24 @@
}
@Override
+ public void acceptDesugaredLibraryRetargeterDispatchProgramClass(DexProgramClass clazz) {
+ // Called only in Desugared library compilation which is D8.
+ assert false;
+ }
+
+ @Override
+ public void acceptDesugaredLibraryRetargeterDispatchClasspathClass(DexClasspathClass clazz) {
+ // TODO(b/188767735): R8 currently relies on IR desugaring.
+ // The classpath class should be marked as liveNonProgramType.
+ }
+
+ @Override
+ public void acceptForwardingMethod(ProgramMethod method) {
+ // TODO(b/188767735): R8 currently relies on IR desugaring.
+ // The method should be marked live, and assert everything it references is traced.
+ }
+
+ @Override
public void acceptRecordClass(DexProgramClass recordClass) {
// This is called each time an instruction or a class is found to require the record class.
assert false : "TODO(b/179146128): To be implemented";
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
index 9ec7242..a069091 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
@@ -153,6 +153,17 @@
return synthesizedLibraryClassesPackagePrefix;
}
+ // TODO(b/183918843): We are currently computing a new name for the class by replacing the
+ // initial package prefix by the synthesized library class package prefix, it would be better
+ // to make the rewriting explicit in the desugared library json file.
+ public String convertJavaNameToDesugaredLibrary(DexType type) {
+ String prefix =
+ DescriptorUtils.getJavaTypeFromBinaryName(getSynthesizedLibraryClassesPackagePrefix());
+ String interfaceType = type.toString();
+ int firstPackage = interfaceType.indexOf('.');
+ return prefix + interfaceType.substring(firstPackage + 1);
+ }
+
public String getIdentifier() {
return identifier;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
index 5a96760..a0153a0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -4,15 +4,18 @@
package com.android.tools.r8.ir.desugar;
+import static com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter.InvokeRetargetingResult.NO_REWRITING;
+
import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
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.ClasspathOrLibraryClass;
import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexApplication.Builder;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexEncodedField;
@@ -21,7 +24,6 @@
import com.android.tools.r8.graph.DexLibraryClass;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
@@ -36,6 +38,7 @@
import com.android.tools.r8.graph.NestHostClassAttribute;
import com.android.tools.r8.graph.NestMemberClassAttribute;
import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
@@ -45,13 +48,16 @@
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.synthetic.EmulateInterfaceSyntheticCfCodeProvider;
import com.android.tools.r8.origin.SynthesizedOrigin;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.synthesis.SyntheticClassBuilder;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.WorkList;
import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
import com.google.common.collect.Maps;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.IdentityHashMap;
@@ -64,12 +70,10 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
+import java.util.function.Function;
+import org.objectweb.asm.Opcodes;
-public class DesugaredLibraryRetargeter {
-
- private static final String RETARGET_PACKAGE = "retarget/";
- public static final String DESUGAR_LIB_RETARGET_CLASS_NAME_PREFIX =
- "$r8$retargetLibraryMember$virtualDispatch";
+public class DesugaredLibraryRetargeter implements CfInstructionDesugaring {
private final AppView<?> appView;
private final Map<DexMethod, DexMethod> retargetLibraryMember = new IdentityHashMap<>();
@@ -80,27 +84,16 @@
// Non final virtual library methods requiring generation of emulated dispatch.
private final DexClassAndMethodSet emulatedDispatchMethods = DexClassAndMethodSet.create();
- private final String packageAndClassDescriptorPrefix;
+ private final SortedProgramMethodSet forwardingMethods = SortedProgramMethodSet.create();
public DesugaredLibraryRetargeter(AppView<?> appView) {
this.appView = appView;
- packageAndClassDescriptorPrefix =
- getRetargetPackageAndClassPrefixDescriptor(appView.options().desugaredLibraryConfiguration);
if (appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()) {
return;
}
new RetargetingSetup().setUpRetargeting();
}
- public static boolean isRetargetType(DexType type, InternalOptions options) {
- if (options.desugaredLibraryConfiguration == null) {
- return false;
- }
- return type.toDescriptorString()
- .startsWith(
- getRetargetPackageAndClassPrefixDescriptor(options.desugaredLibraryConfiguration));
- }
-
public static void checkForAssumedLibraryTypes(AppView<?> appView) {
Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
@@ -225,55 +218,37 @@
appView.options().reporter.warning(warning);
}
- private static void synthesizeClassWithUniqueMethod(
- Builder<?> builder,
- ClassAccessFlags accessFlags,
- DexType type,
- DexEncodedMethod uniqueMethod,
- String origin,
- AppView<?> appView) {
- DexItemFactory factory = appView.dexItemFactory();
- DexProgramClass newClass =
- new DexProgramClass(
- type,
- null,
- new SynthesizedOrigin(origin, BackportedMethodRewriter.class),
- accessFlags,
- factory.objectType,
- DexTypeList.empty(),
- null,
- null,
- Collections.emptyList(),
- null,
- Collections.emptyList(),
- ClassSignature.noSignature(),
- DexAnnotationSet.empty(),
- DexEncodedField.EMPTY_ARRAY,
- DexEncodedField.EMPTY_ARRAY,
- uniqueMethod.isStatic()
- ? new DexEncodedMethod[] {uniqueMethod}
- : DexEncodedMethod.EMPTY_ARRAY,
- uniqueMethod.isStatic()
- ? DexEncodedMethod.EMPTY_ARRAY
- : new DexEncodedMethod[] {uniqueMethod},
- factory.getSkipNameValidationForTesting(),
- getChecksumSupplier(uniqueMethod, appView));
- appView.appInfo().addSynthesizedClassForLibraryDesugaring(newClass);
- builder.addSynthesizedClass(newClass);
- }
-
- private static ChecksumSupplier getChecksumSupplier(DexEncodedMethod method, AppView<?> appView) {
- if (!appView.options().encodeChecksums) {
- return DexProgramClass::invalidChecksumRequest;
- }
- return c -> method.getReference().hashCode();
- }
-
// Used by the ListOfBackportedMethods utility.
void visit(Consumer<DexMethod> consumer) {
retargetLibraryMember.keySet().forEach(consumer);
}
+ @Override
+ public Collection<CfInstruction> desugarInstruction(
+ CfInstruction instruction,
+ FreshLocalProvider freshLocalProvider,
+ LocalStackAllocator localStackAllocator,
+ CfInstructionDesugaringEventConsumer eventConsumer,
+ ProgramMethod context,
+ MethodProcessingContext methodProcessingContext,
+ DexItemFactory dexItemFactory) {
+ InvokeRetargetingResult invokeRetargetingResult = computeNewInvokeTarget(instruction, context);
+
+ if (!invokeRetargetingResult.hasNewInvokeTarget()) {
+ return null;
+ }
+
+ DexMethod newInvokeTarget = invokeRetargetingResult.getNewInvokeTarget(eventConsumer);
+ return Collections.singletonList(
+ new CfInvoke(Opcodes.INVOKESTATIC, newInvokeTarget, instruction.asInvoke().isInterface()));
+ }
+
+ @Override
+ public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
+ return computeNewInvokeTarget(instruction, context).hasNewInvokeTarget();
+ }
+
+ @Deprecated // Use Cf to Cf desugaring instead.
public void desugar(IRCode code) {
if (retargetLibraryMember.isEmpty()) {
return;
@@ -290,62 +265,129 @@
DexMethod invokedMethod = invoke.getInvokedMethod();
boolean isInterface = invoke.getInterfaceBit();
- DexMethod retarget = getRetargetedMethod(invokedMethod, isInterface);
- if (retarget == null) {
- continue;
+ InvokeRetargetingResult invokeRetargetingResult =
+ computeNewInvokeTarget(
+ invokedMethod, isInterface, invoke.isInvokeSuper(), code.context());
+ if (invokeRetargetingResult.hasNewInvokeTarget()) {
+ DexMethod newInvokeTarget = invokeRetargetingResult.getNewInvokeTarget(null);
+ iterator.replaceCurrentInstruction(
+ new InvokeStatic(newInvokeTarget, invoke.outValue(), invoke.inValues()));
}
-
- // Due to emulated dispatch, we have to rewrite invoke-super differently or we end up in
- // infinite loops. We do direct resolution. This is a very uncommon case.
- if (invoke.isInvokeSuper() && matchesNonFinalHolderRewrite(invoke.getInvokedMethod())) {
- DexClassAndMethod superTarget =
- appView
- .appInfoForDesugaring()
- .lookupSuperTarget(invoke.getInvokedMethod(), code.context());
- // Final methods can be rewritten as a normal invoke.
- if (superTarget != null && !superTarget.getAccessFlags().isFinal()) {
- DexMethod retargetMethod =
- appView.options().desugaredLibraryConfiguration.retargetMethod(superTarget, appView);
- if (retargetMethod != null) {
- iterator.replaceCurrentInstruction(
- new InvokeStatic(retargetMethod, invoke.outValue(), invoke.arguments()));
- }
- continue;
- }
- }
-
- iterator.replaceCurrentInstruction(
- new InvokeStatic(retarget, invoke.outValue(), invoke.inValues()));
}
}
- public DexMethod getRetargetedMethod(DexMethod invokedMethod, boolean isInterface) {
- DexMethod retarget = getRetargetLibraryMember(invokedMethod);
- if (retarget == null) {
+ static class InvokeRetargetingResult {
+
+ static InvokeRetargetingResult NO_REWRITING =
+ new InvokeRetargetingResult(false, ignored -> null);
+
+ private final boolean hasNewInvokeTarget;
+ private final Function<DesugaredLibraryRetargeterEventConsumer, DexMethod>
+ newInvokeTargetSupplier;
+
+ static InvokeRetargetingResult createInvokeRetargetingResult(DexMethod retarget) {
+ if (retarget == null) {
+ return NO_REWRITING;
+ }
+ return new InvokeRetargetingResult(true, ignored -> retarget);
+ }
+
+ private InvokeRetargetingResult(
+ boolean hasNewInvokeTarget,
+ Function<DesugaredLibraryRetargeterEventConsumer, DexMethod> newInvokeTargetSupplier) {
+ this.hasNewInvokeTarget = hasNewInvokeTarget;
+ this.newInvokeTargetSupplier = newInvokeTargetSupplier;
+ }
+
+ public boolean hasNewInvokeTarget() {
+ return hasNewInvokeTarget;
+ }
+
+ public DexMethod getNewInvokeTarget(DesugaredLibraryRetargeterEventConsumer eventConsumer) {
+ assert hasNewInvokeTarget();
+ return newInvokeTargetSupplier.apply(eventConsumer);
+ }
+ }
+
+ public boolean hasNewInvokeTarget(
+ DexMethod invokedMethod, boolean isInterface, boolean isInvokeSuper, ProgramMethod context) {
+ return computeNewInvokeTarget(invokedMethod, isInterface, isInvokeSuper, context)
+ .hasNewInvokeTarget();
+ }
+
+ private InvokeRetargetingResult computeNewInvokeTarget(
+ CfInstruction instruction, ProgramMethod context) {
+ if (retargetLibraryMember.isEmpty() || !instruction.isInvoke()) {
+ return NO_REWRITING;
+ }
+ CfInvoke cfInvoke = instruction.asInvoke();
+ return computeNewInvokeTarget(
+ cfInvoke.getMethod(),
+ cfInvoke.isInterface(),
+ cfInvoke.isInvokeSuper(context.getHolderType()),
+ context);
+ }
+
+ private InvokeRetargetingResult computeNewInvokeTarget(
+ DexMethod invokedMethod, boolean isInterface, boolean isInvokeSuper, ProgramMethod context) {
+ InvokeRetargetingResult retarget = computeRetargetedMethod(invokedMethod, isInterface);
+ if (!retarget.hasNewInvokeTarget()) {
+ return NO_REWRITING;
+ }
+ if (isInvokeSuper && matchesNonFinalHolderRewrite(invokedMethod)) {
+ DexClassAndMethod superTarget =
+ appView.appInfoForDesugaring().lookupSuperTarget(invokedMethod, context);
+ // Final methods can be rewritten as a normal invoke.
+ if (superTarget != null && !superTarget.getAccessFlags().isFinal()) {
+ return InvokeRetargetingResult.createInvokeRetargetingResult(
+ appView.options().desugaredLibraryConfiguration.retargetMethod(superTarget, appView));
+ }
+ }
+ return retarget;
+ }
+
+ private InvokeRetargetingResult computeRetargetedMethod(
+ DexMethod invokedMethod, boolean isInterface) {
+ InvokeRetargetingResult invokeRetargetingResult = computeRetargetLibraryMember(invokedMethod);
+ if (!invokeRetargetingResult.hasNewInvokeTarget()) {
if (!matchesNonFinalHolderRewrite(invokedMethod)) {
- return null;
+ return NO_REWRITING;
}
// We need to force resolution, even on d8, to know if the invoke has to be rewritten.
ResolutionResult resolutionResult =
appView.appInfoForDesugaring().resolveMethod(invokedMethod, isInterface);
if (resolutionResult.isFailedResolution()) {
- return null;
+ return NO_REWRITING;
}
DexEncodedMethod singleTarget = resolutionResult.getSingleTarget();
assert singleTarget != null;
- retarget = getRetargetLibraryMember(singleTarget.getReference());
+ invokeRetargetingResult = computeRetargetLibraryMember(singleTarget.getReference());
}
- return retarget;
+ return invokeRetargetingResult;
}
- private DexMethod getRetargetLibraryMember(DexMethod method) {
+ private InvokeRetargetingResult computeRetargetLibraryMember(DexMethod method) {
Map<DexType, DexType> backportCoreLibraryMembers =
appView.options().desugaredLibraryConfiguration.getBackportCoreLibraryMember();
if (backportCoreLibraryMembers.containsKey(method.holder)) {
DexType newHolder = backportCoreLibraryMembers.get(method.holder);
- return appView.dexItemFactory().createMethod(newHolder, method.proto, method.name);
+ DexMethod newMethod =
+ appView.dexItemFactory().createMethod(newHolder, method.proto, method.name);
+ return InvokeRetargetingResult.createInvokeRetargetingResult(newMethod);
}
- return retargetLibraryMember.get(method);
+ DexClassAndMethod emulatedMethod = emulatedDispatchMethods.get(method);
+ if (emulatedMethod != null) {
+ assert !emulatedMethod.getAccessFlags().isStatic();
+ return new InvokeRetargetingResult(
+ true,
+ eventConsumer -> {
+ DexType newHolder =
+ ensureEmulatedHolderDispatchMethod(emulatedMethod, eventConsumer).type;
+ return computeRetargetMethod(
+ method, emulatedMethod.getAccessFlags().isStatic(), newHolder);
+ });
+ }
+ return InvokeRetargetingResult.createInvokeRetargetingResult(retargetLibraryMember.get(method));
}
private boolean matchesNonFinalHolderRewrite(DexMethod method) {
@@ -361,6 +403,12 @@
return false;
}
+ DexMethod computeRetargetMethod(DexMethod method, boolean isStatic, DexType newHolder) {
+ DexItemFactory factory = appView.dexItemFactory();
+ DexProto newProto = isStatic ? method.getProto() : factory.prependHolderToProto(method);
+ return factory.createMethod(newHolder, newProto, method.getName());
+ }
+
private class RetargetingSetup {
private void setUpRetargeting() {
@@ -375,6 +423,7 @@
DexType newHolder = retargetCoreLibMember.get(methodName).get(inType);
List<DexClassAndMethod> found = findMethodsWithName(methodName, typeClass);
for (DexClassAndMethod method : found) {
+ boolean emulatedDispatch = false;
DexMethod methodReference = method.getReference();
if (!typeClass.isFinal()) {
nonFinalHolderRewrites.putIfAbsent(method.getName(), new ArrayList<>());
@@ -386,12 +435,17 @@
} else if (!method.getAccessFlags().isFinal()) {
// Virtual rewrites require emulated dispatch for inheritance.
// The call is rewritten to the dispatch holder class instead.
- handleEmulateDispatch(appView, method);
- newHolder = dispatchHolderTypeFor(method);
+ emulatedDispatchMethods.add(method);
+ emulatedDispatch = true;
}
}
}
- retargetLibraryMember.put(methodReference, computeRetargetMethod(method, newHolder));
+ if (!emulatedDispatch) {
+ retargetLibraryMember.put(
+ methodReference,
+ computeRetargetMethod(
+ methodReference, method.getAccessFlags().isStatic(), newHolder));
+ }
}
}
}
@@ -408,13 +462,12 @@
itemFactory.createMethod(
itemFactory.createType(itemFactory.arraysDescriptor), proto, name);
DexMethod target =
- itemFactory.createMethod(
- itemFactory.createType("Ljava/util/DesugarArrays;"), proto, name);
+ computeRetargetMethod(
+ source, true, itemFactory.createType("Ljava/util/DesugarArrays;"));
retargetLibraryMember.put(source, target);
// TODO(b/181629049): This is only a workaround rewriting invokes of
// j.u.TimeZone.getTimeZone taking a java.time.ZoneId.
- // to j.u.DesugarArrays.deepEquals0.
name = itemFactory.createString("getTimeZone");
proto =
itemFactory.createProto(
@@ -423,8 +476,8 @@
source =
itemFactory.createMethod(itemFactory.createType("Ljava/util/TimeZone;"), proto, name);
target =
- itemFactory.createMethod(
- itemFactory.createType("Ljava/util/DesugarTimeZone;"), proto, name);
+ computeRetargetMethod(
+ source, true, itemFactory.createType("Ljava/util/DesugarTimeZone;"));
retargetLibraryMember.put(source, target);
}
}
@@ -460,15 +513,6 @@
return false;
}
- private DexMethod computeRetargetMethod(DexClassAndMethod method, DexType newHolder) {
- DexItemFactory factory = appView.dexItemFactory();
- DexProto newProto =
- method.getAccessFlags().isStatic()
- ? method.getProto()
- : factory.prependHolderToProto(method.getReference());
- return factory.createMethod(newHolder, newProto, method.getName());
- }
-
private List<DexClassAndMethod> findMethodsWithName(DexString methodName, DexClass clazz) {
List<DexClassAndMethod> found = new ArrayList<>();
clazz.forEachClassMethodMatching(
@@ -476,23 +520,160 @@
assert !found.isEmpty() : "Should have found a method (library specifications).";
return found;
}
-
- private void handleEmulateDispatch(AppView<?> appView, DexClassAndMethod method) {
- emulatedDispatchMethods.add(method);
- if (!appView.options().isDesugaredLibraryCompilation()) {
- // Add rewrite rules so keeps rules are correctly generated in the program.
- DexType dispatchInterfaceType = dispatchInterfaceTypeFor(method);
- appView.rewritePrefix.rewriteType(dispatchInterfaceType, dispatchInterfaceType);
- DexType dispatchHolderType = dispatchHolderTypeFor(method);
- appView.rewritePrefix.rewriteType(dispatchHolderType, dispatchHolderType);
- }
- }
}
- public void synthesizeRetargetClasses(
- DexApplication.Builder<?> builder, ExecutorService executorService, IRConverter converter)
+ public void finalizeDesugaring(DesugaredLibraryRetargeterEventConsumer eventConsumer) {
+ new EmulatedDispatchTreeFixer().fixApp(eventConsumer);
+ }
+
+ private void rewriteType(DexType type) {
+ String newName =
+ appView.options().desugaredLibraryConfiguration.convertJavaNameToDesugaredLibrary(type);
+ DexType newType =
+ appView.dexItemFactory().createType(DescriptorUtils.javaTypeToDescriptor(newName));
+ appView.rewritePrefix.rewriteType(type, newType);
+ }
+
+ public DexClass ensureEmulatedHolderDispatchMethod(
+ DexClassAndMethod emulatedDispatchMethod,
+ DesugaredLibraryRetargeterEventConsumer eventConsumer) {
+ assert eventConsumer != null || appView.enableWholeProgramOptimizations();
+ DexClass interfaceClass =
+ ensureEmulatedInterfaceDispatchMethod(emulatedDispatchMethod, eventConsumer);
+ DexMethod itfMethod =
+ interfaceClass.lookupMethod(emulatedDispatchMethod.getReference()).getReference();
+ DexClass holderDispatch;
+ if (appView.options().isDesugaredLibraryCompilation()) {
+ holderDispatch =
+ appView
+ .getSyntheticItems()
+ .ensureFixedClass(
+ SyntheticKind.RETARGET_CLASS,
+ emulatedDispatchMethod.getHolder(),
+ appView,
+ classBuilder ->
+ buildHolderDispatchMethod(classBuilder, emulatedDispatchMethod, itfMethod),
+ clazz -> {
+ if (eventConsumer != null) {
+ eventConsumer.acceptDesugaredLibraryRetargeterDispatchProgramClass(clazz);
+ }
+ });
+ } else {
+ ClasspathOrLibraryClass context =
+ emulatedDispatchMethod.getHolder().asClasspathOrLibraryClass();
+ assert context != null;
+ holderDispatch =
+ appView
+ .getSyntheticItems()
+ .ensureFixedClasspathClass(
+ SyntheticKind.RETARGET_CLASS,
+ context,
+ appView,
+ classBuilder ->
+ buildHolderDispatchMethod(classBuilder, emulatedDispatchMethod, itfMethod),
+ clazz -> {
+ if (eventConsumer != null) {
+ eventConsumer.acceptDesugaredLibraryRetargeterDispatchClasspathClass(clazz);
+ }
+ });
+ }
+ rewriteType(holderDispatch.type);
+ return holderDispatch;
+ }
+
+ public DexClass ensureEmulatedInterfaceDispatchMethod(
+ DexClassAndMethod emulatedDispatchMethod,
+ DesugaredLibraryRetargeterEventConsumer eventConsumer) {
+ assert eventConsumer != null || appView.enableWholeProgramOptimizations();
+ DexClass interfaceDispatch;
+ if (appView.options().isDesugaredLibraryCompilation()) {
+ interfaceDispatch =
+ appView
+ .getSyntheticItems()
+ .ensureFixedClass(
+ SyntheticKind.RETARGET_INTERFACE,
+ emulatedDispatchMethod.getHolder(),
+ appView,
+ classBuilder ->
+ buildInterfaceDispatchMethod(classBuilder, emulatedDispatchMethod),
+ clazz -> {
+ if (eventConsumer != null) {
+ eventConsumer.acceptDesugaredLibraryRetargeterDispatchProgramClass(clazz);
+ }
+ });
+ } else {
+ ClasspathOrLibraryClass context =
+ emulatedDispatchMethod.getHolder().asClasspathOrLibraryClass();
+ assert context != null;
+ interfaceDispatch =
+ appView
+ .getSyntheticItems()
+ .ensureFixedClasspathClass(
+ SyntheticKind.RETARGET_INTERFACE,
+ context,
+ appView,
+ classBuilder ->
+ buildInterfaceDispatchMethod(classBuilder, emulatedDispatchMethod),
+ clazz -> {
+ if (eventConsumer != null) {
+ eventConsumer.acceptDesugaredLibraryRetargeterDispatchClasspathClass(clazz);
+ }
+ });
+ }
+ rewriteType(interfaceDispatch.type);
+ return interfaceDispatch;
+ }
+
+ private void buildInterfaceDispatchMethod(
+ SyntheticClassBuilder<?, ?> classBuilder, DexClassAndMethod emulatedDispatchMethod) {
+ classBuilder
+ .setInterface()
+ .addMethod(
+ methodBuilder -> {
+ MethodAccessFlags flags =
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_ABSTRACT | Constants.ACC_SYNTHETIC,
+ false);
+ methodBuilder
+ .setName(emulatedDispatchMethod.getName())
+ .setProto(emulatedDispatchMethod.getProto())
+ .setAccessFlags(flags);
+ });
+ }
+
+ private <SCB extends SyntheticClassBuilder<?, ?>> void buildHolderDispatchMethod(
+ SCB classBuilder, DexClassAndMethod emulatedDispatchMethod, DexMethod itfMethod) {
+ classBuilder.addMethod(
+ methodBuilder -> {
+ DexMethod desugarMethod =
+ appView
+ .options()
+ .desugaredLibraryConfiguration
+ .retargetMethod(emulatedDispatchMethod, appView);
+ assert desugarMethod
+ != null; // This method is reached only for retarget core lib members.
+ methodBuilder
+ .setName(emulatedDispatchMethod.getName())
+ .setProto(desugarMethod.proto)
+ .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+ .setCode(
+ methodSig ->
+ new EmulateInterfaceSyntheticCfCodeProvider(
+ emulatedDispatchMethod.getHolderType(),
+ desugarMethod,
+ itfMethod,
+ Collections.emptyList(),
+ appView)
+ .generateCfCode());
+ });
+ }
+
+ @Deprecated // Use Cf to Cf desugaring.
+ public void synthesizeRetargetClasses(IRConverter converter, ExecutorService executorService)
throws ExecutionException {
- new EmulatedDispatchTreeFixer().fixApp(builder, executorService, converter);
+ assert appView.enableWholeProgramOptimizations();
+ new EmulatedDispatchTreeFixer().fixApp(null);
+ converter.processMethodsConcurrently(forwardingMethods, executorService);
}
// The rewrite of virtual calls requires to go through emulate dispatch. This class is responsible
@@ -500,29 +681,23 @@
// synthesize the interfaces and emulated dispatch classes in the desugared library.
class EmulatedDispatchTreeFixer {
- void fixApp(
- DexApplication.Builder<?> builder, ExecutorService executorService, IRConverter converter)
- throws ExecutionException {
+ void fixApp(DesugaredLibraryRetargeterEventConsumer eventConsumer) {
if (appView.options().isDesugaredLibraryCompilation()) {
- synthesizeEmulatedDispatchMethods(builder);
+ synthesizeEmulatedDispatchMethods(eventConsumer);
} else {
- addInterfacesAndForwardingMethods(executorService, converter);
+ addInterfacesAndForwardingMethods(eventConsumer);
}
}
private void addInterfacesAndForwardingMethods(
- ExecutorService executorService, IRConverter converter) throws ExecutionException {
+ DesugaredLibraryRetargeterEventConsumer eventConsumer) {
assert !appView.options().isDesugaredLibraryCompilation();
Map<DexType, List<DexClassAndMethod>> map = Maps.newIdentityHashMap();
for (DexClassAndMethod emulatedDispatchMethod : emulatedDispatchMethods) {
map.putIfAbsent(emulatedDispatchMethod.getHolderType(), new ArrayList<>(1));
map.get(emulatedDispatchMethod.getHolderType()).add(emulatedDispatchMethod);
}
- SortedProgramMethodSet addedMethods = SortedProgramMethodSet.create();
for (DexProgramClass clazz : appView.appInfo().classes()) {
- if (appView.isAlreadyLibraryDesugared(clazz)) {
- continue;
- }
if (clazz.superType == null) {
assert clazz.type == appView.dexItemFactory().objectType : clazz.type.toSourceString();
continue;
@@ -536,13 +711,11 @@
map.forEach(
(type, methods) -> {
if (inherit(superclass.asLibraryClass(), type, emulatedDispatchMethods)) {
- addInterfacesAndForwardingMethods(
- clazz, methods, method -> addedMethods.createAndAdd(clazz, method));
+ addInterfacesAndForwardingMethods(eventConsumer, clazz, methods);
}
});
}
}
- converter.processMethodsConcurrently(addedMethods, executorService);
}
private boolean inherit(
@@ -566,20 +739,29 @@
}
private void addInterfacesAndForwardingMethods(
+ DesugaredLibraryRetargeterEventConsumer eventConsumer,
DexProgramClass clazz,
- List<DexClassAndMethod> methods,
- Consumer<DexEncodedMethod> newForwardingMethodsConsumer) {
+ List<DexClassAndMethod> methods) {
// DesugaredLibraryRetargeter emulate dispatch: insertion of a marker interface & forwarding
// methods.
// We cannot use the ClassProcessor since this applies up to 26, while the ClassProcessor
// applies up to 24.
for (DexClassAndMethod method : methods) {
- clazz.addExtraInterfaces(
- Collections.singletonList(new ClassTypeSignature(dispatchInterfaceTypeFor(method))));
+ DexClass dexClass = ensureEmulatedInterfaceDispatchMethod(method, eventConsumer);
+ if (clazz.interfaces.contains(dexClass.type)) {
+ // The class has already been desugared.
+ continue;
+ }
+ clazz.addExtraInterfaces(Collections.singletonList(new ClassTypeSignature(dexClass.type)));
if (clazz.lookupVirtualMethod(method.getReference()) == null) {
DexEncodedMethod newMethod = createForwardingMethod(method, clazz);
clazz.addVirtualMethod(newMethod);
- newForwardingMethodsConsumer.accept(newMethod);
+ if (eventConsumer != null) {
+ eventConsumer.acceptForwardingMethod(new ProgramMethod(clazz, newMethod));
+ } else {
+ assert appView.enableWholeProgramOptimizations();
+ forwardingMethods.add(new ProgramMethod(clazz, newMethod));
+ }
}
}
}
@@ -596,156 +778,38 @@
target, clazz, forwardMethod, appView.dexItemFactory());
}
- private void synthesizeEmulatedDispatchMethods(DexApplication.Builder<?> builder) {
+ private void synthesizeEmulatedDispatchMethods(
+ DesugaredLibraryRetargeterEventConsumer eventConsumer) {
assert appView.options().isDesugaredLibraryCompilation();
if (emulatedDispatchMethods.isEmpty()) {
return;
}
- ClassAccessFlags itfAccessFlags =
- ClassAccessFlags.fromSharedAccessFlags(
- Constants.ACC_PUBLIC
- | Constants.ACC_SYNTHETIC
- | Constants.ACC_ABSTRACT
- | Constants.ACC_INTERFACE);
- ClassAccessFlags holderAccessFlags =
- ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
for (DexClassAndMethod emulatedDispatchMethod : emulatedDispatchMethods) {
- // Dispatch interface.
- DexType interfaceType = dispatchInterfaceTypeFor(emulatedDispatchMethod);
- DexEncodedMethod itfMethod =
- generateInterfaceDispatchMethod(emulatedDispatchMethod, interfaceType);
- synthesizeClassWithUniqueMethod(
- builder,
- itfAccessFlags,
- interfaceType,
- itfMethod,
- "desugared library dispatch interface",
- appView);
- // Dispatch holder.
- DexType holderType = dispatchHolderTypeFor(emulatedDispatchMethod);
- DexEncodedMethod dispatchMethod =
- generateHolderDispatchMethod(
- emulatedDispatchMethod, holderType, itfMethod.getReference());
- synthesizeClassWithUniqueMethod(
- builder,
- holderAccessFlags,
- holderType,
- dispatchMethod,
- "desugared library dispatch class",
- appView);
+ ensureEmulatedHolderDispatchMethod(emulatedDispatchMethod, eventConsumer);
}
}
- private DexEncodedMethod generateInterfaceDispatchMethod(
- DexClassAndMethod emulatedDispatchMethod, DexType interfaceType) {
- MethodAccessFlags flags =
- MethodAccessFlags.fromSharedAccessFlags(
- Constants.ACC_PUBLIC | Constants.ACC_ABSTRACT | Constants.ACC_SYNTHETIC, false);
- DexMethod newMethod =
- appView
- .dexItemFactory()
- .createMethod(
- interfaceType,
- emulatedDispatchMethod.getProto(),
- emulatedDispatchMethod.getName());
- return new DexEncodedMethod(
- newMethod,
- flags,
- MethodTypeSignature.noSignature(),
- DexAnnotationSet.empty(),
- ParameterAnnotationsList.empty(),
- null,
- true);
+ private void reportInvalidLibrarySupertype(
+ DexLibraryClass libraryClass, DexClassAndMethodSet retarget) {
+ DexClass dexClass = appView.definitionFor(libraryClass.superType);
+ String message;
+ if (dexClass == null) {
+ message = "missing";
+ } else if (dexClass.isClasspathClass()) {
+ message = "a classpath class";
+ } else {
+ message = "INVALID";
+ assert false;
+ }
+ appView
+ .options()
+ .warningInvalidLibrarySuperclassForDesugar(
+ dexClass == null ? libraryClass.getOrigin() : dexClass.getOrigin(),
+ libraryClass.type,
+ libraryClass.superType,
+ message,
+ retarget);
}
- private DexEncodedMethod generateHolderDispatchMethod(
- DexClassAndMethod emulatedDispatchMethod, DexType dispatchHolder, DexMethod itfMethod) {
- // The method should look like:
- // static foo(rcvr, arg0, arg1) {
- // if (rcvr instanceof interfaceType) {
- // return invoke-interface receiver.foo(arg0, arg1);
- // } else {
- // return DesugarX.foo(rcvr, arg0, arg1)
- // }
- // We do not deal with complex cases (multiple retargeting of the same signature in the
- // same inheritance tree, etc., since they do not happen in the most common desugared library.
- DexMethod desugarMethod =
- appView
- .options()
- .desugaredLibraryConfiguration
- .retargetMethod(emulatedDispatchMethod, appView);
- assert desugarMethod != null; // This method is reached only for retarget core lib members.
- DexMethod newMethod =
- appView
- .dexItemFactory()
- .createMethod(dispatchHolder, desugarMethod.proto, emulatedDispatchMethod.getName());
- CfCode code =
- new EmulateInterfaceSyntheticCfCodeProvider(
- emulatedDispatchMethod.getHolderType(),
- desugarMethod,
- itfMethod,
- Collections.emptyList(),
- appView)
- .generateCfCode();
- return new DexEncodedMethod(
- newMethod,
- MethodAccessFlags.createPublicStaticSynthetic(),
- MethodTypeSignature.noSignature(),
- DexAnnotationSet.empty(),
- ParameterAnnotationsList.empty(),
- code,
- true);
- }
- }
-
- private void reportInvalidLibrarySupertype(
- DexLibraryClass libraryClass, DexClassAndMethodSet retarget) {
- DexClass dexClass = appView.definitionFor(libraryClass.superType);
- String message;
- if (dexClass == null) {
- message = "missing";
- } else if (dexClass.isClasspathClass()) {
- message = "a classpath class";
- } else {
- message = "INVALID";
- assert false;
- }
- appView
- .options()
- .warningInvalidLibrarySuperclassForDesugar(
- dexClass == null ? libraryClass.getOrigin() : dexClass.getOrigin(),
- libraryClass.type,
- libraryClass.superType,
- message,
- retarget);
- }
-
- private DexType dispatchInterfaceTypeFor(DexClassAndMethod method) {
- return dispatchTypeFor(method, "dispatchInterface");
- }
-
- private DexType dispatchHolderTypeFor(DexClassAndMethod method) {
- return dispatchTypeFor(method, "dispatchHolder");
- }
-
- public static String getRetargetPackageAndClassPrefixDescriptor(
- DesugaredLibraryConfiguration config) {
- return "L"
- + config.getSynthesizedLibraryClassesPackagePrefix()
- + RETARGET_PACKAGE
- + DESUGAR_LIB_RETARGET_CLASS_NAME_PREFIX;
- }
-
- private DexType dispatchTypeFor(DexClassAndMethod method, String suffix) {
- String descriptor =
- packageAndClassDescriptorPrefix
- + '$'
- + method.getHolderType().getName()
- + '$'
- + method.getName()
- + '$'
- + suffix
- + ';';
- return appView.dexItemFactory().createSynthesizedType(descriptor);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeterEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeterEventConsumer.java
new file mode 100644
index 0000000..3806aa6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeterEventConsumer.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar;
+
+import com.android.tools.r8.graph.DexClasspathClass;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface DesugaredLibraryRetargeterEventConsumer {
+
+ void acceptDesugaredLibraryRetargeterDispatchProgramClass(DexProgramClass clazz);
+
+ void acceptDesugaredLibraryRetargeterDispatchClasspathClass(DexClasspathClass clazz);
+
+ void acceptForwardingMethod(ProgramMethod method);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
index c8b6147..5b25c84 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
@@ -58,6 +58,11 @@
}
@Override
+ public void withDesugaredLibraryRetargeter(Consumer<DesugaredLibraryRetargeter> consumer) {
+ // Intentionally empty.
+ }
+
+ @Override
public void withRecordRewriter(Consumer<RecordRewriter> consumer) {
// Intentionally empty.
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index 0d4a470..bb345a4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -39,11 +39,20 @@
private final NestBasedAccessDesugaring nestBasedAccessDesugaring;
private final RecordRewriter recordRewriter;
+ private final DesugaredLibraryRetargeter desugaredLibraryRetargeter;
NonEmptyCfInstructionDesugaringCollection(AppView<?> appView) {
this.appView = appView;
this.nestBasedAccessDesugaring = NestBasedAccessDesugaring.create(appView);
BackportedMethodRewriter backportedMethodRewriter = null;
+ desugaredLibraryRetargeter =
+ appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()
+ || appView.enableWholeProgramOptimizations()
+ ? null
+ : new DesugaredLibraryRetargeter(appView);
+ if (desugaredLibraryRetargeter != null) {
+ desugarings.add(desugaredLibraryRetargeter);
+ }
if (appView.options().enableBackportedMethodRewriting()) {
backportedMethodRewriter = new BackportedMethodRewriter(appView);
}
@@ -54,7 +63,9 @@
// TODO(b/183998768): Enable interface method rewriter cf to cf also in R8.
if (appView.options().isInterfaceMethodDesugaringEnabled()
&& !appView.enableWholeProgramOptimizations()) {
- desugarings.add(new InterfaceMethodRewriter(appView, backportedMethodRewriter));
+ desugarings.add(
+ new InterfaceMethodRewriter(
+ appView, backportedMethodRewriter, desugaredLibraryRetargeter));
}
desugarings.add(new LambdaInstructionDesugaring(appView));
desugarings.add(new InvokeSpecialToSelfDesugaring(appView));
@@ -80,6 +91,7 @@
AppView<?> appView, InvokeSpecialToSelfDesugaring invokeSpecialToSelfDesugaring) {
this.appView = appView;
this.nestBasedAccessDesugaring = null;
+ this.desugaredLibraryRetargeter = null;
this.recordRewriter = null;
desugarings.add(invokeSpecialToSelfDesugaring);
}
@@ -282,6 +294,13 @@
}
@Override
+ public void withDesugaredLibraryRetargeter(Consumer<DesugaredLibraryRetargeter> consumer) {
+ if (desugaredLibraryRetargeter != null) {
+ consumer.accept(desugaredLibraryRetargeter);
+ }
+ }
+
+ @Override
public void withRecordRewriter(Consumer<RecordRewriter> consumer) {
if (recordRewriter != null) {
consumer.accept(recordRewriter);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
index 5929db5..cf86667 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
@@ -21,7 +21,6 @@
import com.android.tools.r8.ir.synthetic.EmulateInterfaceSyntheticCfCodeProvider;
import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
import com.android.tools.r8.synthesis.SyntheticNaming;
-import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.StringDiagnostic;
@@ -153,7 +152,8 @@
builder.addMethod(
methodBuilder ->
synthesizeEmulatedInterfaceMethod(
- method, emulatedInterface, methodBuilder))));
+ method, emulatedInterface, methodBuilder))),
+ ignored -> {});
emulateInterfaceClass.forEachProgramMethod(synthesizedMethods::add);
assert emulateInterfaceClass.getType()
== InterfaceMethodRewriter.getEmulateLibraryInterfaceClassType(
@@ -308,19 +308,11 @@
&& !rewriter.isEmulatedInterface(clazz.type)
&& !appView.rewritePrefix.hasRewrittenType(clazz.type, appView)
&& isEmulatedInterfaceSubInterface(clazz)) {
- String prefix =
- DescriptorUtils.getJavaTypeFromBinaryName(
- appView
- .options()
- .desugaredLibraryConfiguration
- .getSynthesizedLibraryClassesPackagePrefix());
- String interfaceType = clazz.type.toString();
- // TODO(b/183918843): We are currently computing a new name for the companion class
- // by replacing the initial package prefix by the synthesized library class package
- // prefix, it would be better to make the rewriting explicit in the desugared library
- // json file.
- int firstPackage = interfaceType.indexOf('.');
- String newName = prefix + interfaceType.substring(firstPackage + 1);
+ String newName =
+ appView
+ .options()
+ .desugaredLibraryConfiguration
+ .convertJavaNameToDesugaredLibrary(clazz.type);
rewriter.addCompanionClassRewriteRule(clazz.type, newName);
} else {
filteredProgramClasses.add(clazz);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index bdd6064..79afbf8 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -9,7 +9,6 @@
import static com.android.tools.r8.ir.code.Invoke.Type.STATIC;
import static com.android.tools.r8.ir.code.Invoke.Type.SUPER;
import static com.android.tools.r8.ir.code.Invoke.Type.VIRTUAL;
-import static com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter.getRetargetPackageAndClassPrefixDescriptor;
import static com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer.TYPE_WRAPPER_SUFFIX;
import static com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer.VIVIFIED_TYPE_WRAPPER_SUFFIX;
@@ -60,6 +59,7 @@
import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
import com.android.tools.r8.ir.desugar.FreshLocalProvider;
import com.android.tools.r8.ir.desugar.LocalStackAllocator;
import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring;
@@ -150,6 +150,7 @@
// This is used to filter out double desugaring on backported methods.
private final BackportedMethodRewriter backportedMethodRewriter;
+ private final DesugaredLibraryRetargeter desugaredLibraryRetargeter;
/** Defines a minor variation in desugaring. */
public enum Flavor {
@@ -160,10 +161,14 @@
}
// Constructor for cf to cf desugaring.
- public InterfaceMethodRewriter(AppView<?> appView, BackportedMethodRewriter rewriter) {
+ public InterfaceMethodRewriter(
+ AppView<?> appView,
+ BackportedMethodRewriter rewriter,
+ DesugaredLibraryRetargeter desugaredLibraryRetargeter) {
this.appView = appView;
this.converter = null;
this.backportedMethodRewriter = rewriter;
+ this.desugaredLibraryRetargeter = desugaredLibraryRetargeter;
this.options = appView.options();
this.factory = appView.dexItemFactory();
this.emulatedInterfaces = options.desugaredLibraryConfiguration.getEmulateLibraryInterface();
@@ -177,6 +182,7 @@
this.appView = appView;
this.converter = converter;
this.backportedMethodRewriter = null;
+ this.desugaredLibraryRetargeter = null;
this.options = appView.options();
this.factory = appView.dexItemFactory();
this.emulatedInterfaces = options.desugaredLibraryConfiguration.getEmulateLibraryInterface();
@@ -282,6 +288,17 @@
return true;
}
+ private boolean isAlreadyRewritten(
+ DexMethod method, boolean itfBit, boolean isSuper, ProgramMethod context) {
+
+ // In Cf to Cf it is forbidden to desugar twice the same instruction, if the backported
+ // method rewriter or the desugared library retargeter already desugar the instruction, they
+ // take precedence and nothing has to be done here.
+ return (backportedMethodRewriter != null && backportedMethodRewriter.methodIsBackport(method))
+ || (desugaredLibraryRetargeter != null
+ && desugaredLibraryRetargeter.hasNewInvokeTarget(method, itfBit, isSuper, context));
+ }
+
@Override
public boolean hasPreciseNeedsDesugaring() {
return false;
@@ -308,7 +325,11 @@
}
if (instruction.isInvoke()) {
CfInvoke cfInvoke = instruction.asInvoke();
- if (backportedMethodRewriter.methodIsBackport(cfInvoke.getMethod())) {
+ if (isAlreadyRewritten(
+ cfInvoke.getMethod(),
+ cfInvoke.isInterface(),
+ cfInvoke.isInvokeSuper(context.getHolderType()),
+ context)) {
continue;
}
if (cfInvoke.isInvokeStatic()) {
@@ -379,6 +400,13 @@
public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
if (instruction.isInvoke()) {
CfInvoke cfInvoke = instruction.asInvoke();
+ if (isAlreadyRewritten(
+ cfInvoke.getMethod(),
+ cfInvoke.isInterface(),
+ cfInvoke.isInvokeSuper(context.getHolderType()),
+ context)) {
+ return false;
+ }
return needsRewriting(cfInvoke.getMethod(), cfInvoke.getInvokeType(context), context);
}
return false;
@@ -397,7 +425,11 @@
return null;
}
CfInvoke invoke = instruction.asInvoke();
- if (backportedMethodRewriter.methodIsBackport(invoke.getMethod())) {
+ if (isAlreadyRewritten(
+ invoke.getMethod(),
+ invoke.isInterface(),
+ invoke.isInvokeSuper(context.getHolderType()),
+ context)) {
return null;
}
@@ -409,7 +441,12 @@
Function<SingleResolutionResult, Collection<CfInstruction>> rewriteToThrow =
(resolutionResult) ->
rewriteInvokeToThrowCf(
- invoke, resolutionResult, eventConsumer, context, methodProcessingContext);
+ invoke,
+ resolutionResult,
+ localStackAllocator,
+ eventConsumer,
+ context,
+ methodProcessingContext);
if (invoke.isInvokeVirtual() || invoke.isInvokeInterface()) {
AppInfoWithClassHierarchy appInfoForDesugaring = appView.appInfoForDesugaring();
@@ -451,14 +488,15 @@
private Collection<CfInstruction> rewriteInvokeToThrowCf(
CfInvoke invoke,
SingleResolutionResult resolutionResult,
+ LocalStackAllocator localStackAllocator,
CfInstructionDesugaringEventConsumer eventConsumer,
ProgramMethod context,
MethodProcessingContext methodProcessingContext) {
- if (backportedMethodRewriter != null
- && backportedMethodRewriter.methodIsBackport(invoke.getMethod())) {
- // In Cf to Cf it is not allowed to desugar twice the same instruction, if the backported
- // method rewriter already desugars the instruction, it takes precedence and nothing has
- // to be done here.
+ if (isAlreadyRewritten(
+ invoke.getMethod(),
+ invoke.isInterface(),
+ invoke.isInvokeSuper(context.getHolderType()),
+ context)) {
return null;
}
@@ -517,6 +555,10 @@
returnType.isPrimitiveType()
? new CfConstNumber(0, ValueType.fromDexType(returnType))
: new CfConstNull());
+ } else {
+ // If the return type is void, the stack may need an extra slot to fit the return type of
+ // the call to the throwing method.
+ localStackAllocator.allocateLocalStack(1);
}
return replacement;
}
@@ -770,11 +812,7 @@
// to outline again the invoke-static. Just do nothing instead.
return null;
}
- if (backportedMethodRewriter != null
- && backportedMethodRewriter.methodIsBackport(invokedMethod)) {
- // In Cf to Cf it is not allowed to desugar twice the same instruction, if the backported
- // method rewriter already desugars the instruction, it takes precedence and nothing has
- // to be done here.
+ if (isAlreadyRewritten(invokedMethod, interfaceBit, false, context)) {
return null;
}
ProgramMethod newProgramMethod =
@@ -1216,16 +1254,15 @@
}
appView
.getSyntheticItems()
- .ensureDirectMethodOnSyntheticClasspathClassWhileMigrating(
+ .ensureFixedClasspathClassMethod(
+ rewritten.getName(),
+ rewritten.getProto(),
SyntheticKind.COMPANION_CLASS,
- rewritten.getHolderType(),
context,
appView,
- rewritten,
- builder ->
- builder
- .setName(rewritten.name)
- .setProto(rewritten.proto)
+ classBuilder -> {},
+ methodBuilder ->
+ methodBuilder
.setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
.setCode(DexEncodedMethod::buildEmptyThrowingCfCode));
}
@@ -1323,9 +1360,6 @@
private Predicate<DexType> getShouldIgnoreFromReportsPredicate(AppView<?> appView) {
DexItemFactory dexItemFactory = appView.dexItemFactory();
InternalOptions options = appView.options();
- DexString retargetPackageAndClassPrefixDescriptor =
- dexItemFactory.createString(
- getRetargetPackageAndClassPrefixDescriptor(options.desugaredLibraryConfiguration));
DexString typeWrapperClassNameDescriptorSuffix =
dexItemFactory.createString(TYPE_WRAPPER_SUFFIX + ';');
DexString vivifiedTypeWrapperClassNameDescriptorSuffix =
@@ -1341,8 +1375,7 @@
|| descriptor.endsWith(companionClassNameDescriptorSuffix)
|| emulatedInterfaces.containsValue(type)
|| options.desugaredLibraryConfiguration.getCustomConversions().containsValue(type)
- || appView.getDontWarnConfiguration().matches(type)
- || descriptor.startsWith(retargetPackageAndClassPrefixDescriptor);
+ || appView.getDontWarnConfiguration().matches(type);
};
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
index 6ba6863..2174002 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
@@ -27,6 +27,8 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue.DexValueInt;
import com.android.tools.r8.graph.FieldAccessFlags;
@@ -39,8 +41,7 @@
import com.android.tools.r8.graph.NestedGraphLens;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
-import com.android.tools.r8.synthesis.SyntheticNaming;
-import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.collections.BidirectionalManyToManyRepresentativeMap;
import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
@@ -58,6 +59,7 @@
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
import org.objectweb.asm.Opcodes;
// Default and static method interface desugaring processor for interfaces.
@@ -86,9 +88,7 @@
return;
}
analyzeBridges(iface);
- if (needsCompanionClass(iface)) {
- ensureCompanionClass(iface, synthesizedMethods);
- }
+ ensureCompanionClassMethods(iface, synthesizedMethods);
}
private void analyzeBridges(DexProgramClass iface) {
@@ -101,66 +101,58 @@
}
}
- private boolean needsCompanionClass(DexProgramClass iface) {
- if (hasStaticMethodThatTriggersNonTrivialClassInitializer(iface)) {
- return true;
- }
- for (ProgramMethod method : iface.virtualProgramMethods()) {
- DexEncodedMethod virtual = method.getDefinition();
- if (rewriter.isDefaultMethod(virtual)) {
- return true;
- }
- }
- for (ProgramMethod method : iface.directProgramMethods()) {
- DexEncodedMethod definition = method.getDefinition();
- if (!definition.isInitializer()) {
- return true;
- }
- }
- return false;
+ private void ensureCompanionClassMethods(
+ DexProgramClass iface, ProgramMethodSet synthesizedMethods) {
+ ensureCompanionClassInitializesInterface(iface, synthesizedMethods);
+ // TODO(b/183998768): Once fixed, the methods should be added for processing.
+ // D8 and R8 don't need to optimize the methods since they are just moved from interfaces and
+ // don't need to be re-processed.
+ processVirtualInterfaceMethods(iface);
+ processDirectInterfaceMethods(iface);
}
- private DexProgramClass ensureCompanionClass(
- DexProgramClass iface, ProgramMethodSet synthesizedMethods) {
-
- DexProgramClass companionClass =
+ private ProgramMethod ensureCompanionMethod(
+ DexProgramClass iface,
+ DexString methodName,
+ DexProto methodProto,
+ Consumer<SyntheticMethodBuilder> fn) {
+ ProgramMethod method =
appView
.getSyntheticItems()
- .ensureFixedClass(
- SyntheticNaming.SyntheticKind.COMPANION_CLASS,
+ .ensureFixedClassMethod(
+ methodName,
+ methodProto,
+ SyntheticKind.COMPANION_CLASS,
iface,
appView,
- builder -> {
- builder.setSourceFile(iface.sourceFile);
- builder.setGenericSignature(
- iface.getClassSignature().toObjectBoundWithSameFormals(objectTypeSignature));
- ensureCompanionClassInitializesInterface(iface, builder);
- processVirtualInterfaceMethods(iface, builder);
- processDirectInterfaceMethods(iface, builder);
- });
- assert companionClass.getType() == rewriter.getCompanionClassType(iface.type);
- assert companionClass.hasMethods();
-
- // D8 and R8 don't need to optimize the methods since they are just moved from interfaces and
- // don't need to be re-processed, besides the clinit, which has just been inserted.
- if (companionClass.hasClassInitializer()) {
- synthesizedMethods.add(companionClass.getProgramClassInitializer());
- }
-
- return companionClass;
+ builder ->
+ builder
+ .setSourceFile(iface.sourceFile)
+ .setGenericSignature(
+ iface
+ .getClassSignature()
+ .toObjectBoundWithSameFormals(objectTypeSignature)),
+ fn);
+ assert method.getHolderType() == rewriter.getCompanionClassType(iface.type);
+ return method;
}
private void ensureCompanionClassInitializesInterface(
- DexProgramClass iface, SyntheticProgramClassBuilder builder) {
+ DexProgramClass iface, ProgramMethodSet synthesizedMethods) {
if (!hasStaticMethodThatTriggersNonTrivialClassInitializer(iface)) {
return;
}
- DexEncodedField clinitField = ensureStaticClinitFieldToTriggerinterfaceInitialization(iface);
- builder.addMethod(
- methodBuilder -> createCompanionClassInitializer(iface, clinitField, methodBuilder));
+ DexEncodedField clinitField = ensureStaticClinitFieldToTriggerInterfaceInitialization(iface);
+ ProgramMethod clinit =
+ ensureCompanionMethod(
+ iface,
+ appView.dexItemFactory().classConstructorMethodName,
+ appView.dexItemFactory().createProto(appView.dexItemFactory().voidType),
+ methodBuilder -> createCompanionClassInitializer(iface, clinitField, methodBuilder));
+ synthesizedMethods.add(clinit);
}
- private DexEncodedField ensureStaticClinitFieldToTriggerinterfaceInitialization(
+ private DexEncodedField ensureStaticClinitFieldToTriggerInterfaceInitialization(
DexProgramClass iface) {
DexEncodedField clinitField =
findExistingStaticClinitFieldToTriggerInterfaceInitialization(iface);
@@ -223,16 +215,13 @@
ImmutableList.of(),
ImmutableList.of());
methodBuilder
- .setName(appView.dexItemFactory().classConstructorMethodName)
- .setProto(appView.dexItemFactory().createProto(appView.dexItemFactory().voidType))
.setAccessFlags(
MethodAccessFlags.builder().setConstructor().setPackagePrivate().setStatic().build())
.setCode(codeGenerator)
.setClassFileVersion(iface.getInitialClassFileVersion());
}
- private void processVirtualInterfaceMethods(
- DexProgramClass iface, SyntheticProgramClassBuilder builder) {
+ private void processVirtualInterfaceMethods(DexProgramClass iface) {
for (ProgramMethod method : iface.virtualProgramMethods()) {
DexEncodedMethod virtual = method.getDefinition();
if (rewriter.isDefaultMethod(virtual)) {
@@ -259,11 +248,12 @@
DexEncodedMethod.setDebugInfoWithFakeThisParameter(
code, companionMethod.getArity(), appView);
- builder.addMethod(
+ ensureCompanionMethod(
+ iface,
+ companionMethod.getName(),
+ companionMethod.getProto(),
methodBuilder ->
methodBuilder
- .setName(companionMethod.getName())
- .setProto(companionMethod.getProto())
.setAccessFlags(newFlags)
.setGenericSignature(MethodTypeSignature.noSignature())
.setAnnotations(virtual.annotations())
@@ -279,8 +269,7 @@
}
}
- private void processDirectInterfaceMethods(
- DexProgramClass iface, SyntheticProgramClassBuilder builder) {
+ private void processDirectInterfaceMethods(DexProgramClass iface) {
for (ProgramMethod method : iface.directProgramMethods()) {
DexEncodedMethod definition = method.getDefinition();
if (definition.isClassInitializer()) {
@@ -311,11 +300,12 @@
+ iface.origin;
DexMethod companionMethod = rewriter.staticAsMethodOfCompanionClass(method);
- builder.addMethod(
+ ensureCompanionMethod(
+ iface,
+ companionMethod.getName(),
+ companionMethod.getProto(),
methodBuilder ->
methodBuilder
- .setName(companionMethod.getName())
- .setProto(companionMethod.getProto())
.setAccessFlags(newFlags)
.setGenericSignature(definition.getGenericSignature())
.setAnnotations(definition.annotations())
@@ -349,11 +339,12 @@
DexEncodedMethod.setDebugInfoWithFakeThisParameter(code, companionMethod.getArity(), appView);
- builder.addMethod(
+ ensureCompanionMethod(
+ iface,
+ companionMethod.getName(),
+ companionMethod.getProto(),
methodBuilder ->
methodBuilder
- .setName(companionMethod.getName())
- .setProto(companionMethod.getProto())
.setAccessFlags(newFlags)
.setGenericSignature(definition.getGenericSignature())
.setAnnotations(definition.annotations())
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index bba0af1..0b47a53 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -85,9 +85,9 @@
import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.UnsupportedInstanceFieldValueForEnumInstanceReason;
import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.UnsupportedLibraryInvokeReason;
import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.MutableMethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
-import com.android.tools.r8.ir.optimize.info.UpdatableMethodOptimizationInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
import com.android.tools.r8.shaking.KeepInfoCollection;
@@ -507,7 +507,7 @@
@Override
public void fixup(
- DexEncodedMethod method, UpdatableMethodOptimizationInfo optimizationInfo) {
+ DexEncodedMethod method, MutableMethodOptimizationInfo optimizationInfo) {
optimizationInfo
.fixupClassTypeReferences(appView, appView.graphLens())
.fixupAbstractReturnValue(appView, appView.graphLens())
@@ -937,7 +937,8 @@
// We do not unbox enums with invoke custom since it's not clear the accessibility
// constraints would be correct if the method holding the invoke custom is moved to
// another class.
- assert !factory.isLambdaMetafactoryMethod(callSite.bootstrapMethod.asMethod());
+ assert appView.options().isGeneratingClassFiles()
+ || !factory.isLambdaMetafactoryMethod(callSite.bootstrapMethod.asMethod());
constraint = Constraint.NEVER;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 4fdd8f6..7ddfbaa 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -383,13 +383,16 @@
private void replaceEnumInvoke(
InstructionListIterator iterator,
- InvokeMethod invokeMethod,
+ InvokeMethod invoke,
DexMethod method,
Function<DexMethod, DexEncodedMethod> synthesizor) {
utilityMethods.computeIfAbsent(method, synthesizor);
- Instruction instruction =
- new InvokeStatic(method, invokeMethod.outValue(), invokeMethod.arguments());
- iterator.replaceCurrentInstruction(instruction);
+ InvokeStatic replacement =
+ new InvokeStatic(
+ method, invoke.hasUnusedOutValue() ? null : invoke.outValue(), invoke.arguments());
+ assert !replacement.hasOutValue()
+ || !replacement.getInvokedMethod().getReturnType().isVoidType();
+ iterator.replaceCurrentInstruction(replacement);
}
private boolean validateArrayAccess(ArrayAccess arrayAccess) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index 8a43d7c..a29e290 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMember;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
@@ -24,6 +25,7 @@
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
@@ -87,6 +89,7 @@
unboxedEnumsMethods.forEach(
(newHolderType, movedMethods) -> {
DexProgramClass newHolderClass = appView.definitionFor(newHolderType).asProgramClass();
+ movedMethods.sort(Comparator.comparing(DexEncodedMember::getReference));
newHolderClass.addDirectMethods(movedMethods);
});
return lensBuilder.build(appView);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
index 80fbd54..0408418 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
@@ -20,11 +20,6 @@
}
@Override
- public MutableFieldOptimizationInfo mutableCopy() {
- return new MutableFieldOptimizationInfo();
- }
-
- @Override
public boolean cannotBeKept() {
return false;
}
@@ -60,12 +55,7 @@
}
@Override
- public boolean isDefaultFieldOptimizationInfo() {
- return true;
- }
-
- @Override
- public DefaultFieldOptimizationInfo asDefaultFieldOptimizationInfo() {
- return this;
+ public MutableFieldOptimizationInfo toMutableOptimizationInfo() {
+ return new MutableFieldOptimizationInfo();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index 2b7ef1a..1df189a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -48,21 +48,6 @@
}
@Override
- public boolean isDefaultMethodOptimizationInfo() {
- return true;
- }
-
- @Override
- public boolean isUpdatableMethodOptimizationInfo() {
- return false;
- }
-
- @Override
- public UpdatableMethodOptimizationInfo asUpdatableMethodOptimizationInfo() {
- return mutableCopy();
- }
-
- @Override
public boolean cannotBeKept() {
return false;
}
@@ -204,7 +189,7 @@
}
@Override
- public UpdatableMethodOptimizationInfo mutableCopy() {
- return new UpdatableMethodOptimizationInfo();
+ public MutableMethodOptimizationInfo toMutableOptimizationInfo() {
+ return new MutableMethodOptimizationInfo();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationWithMinApiInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationWithMinApiInfo.java
index fc61ba6..a5e21d9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationWithMinApiInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationWithMinApiInfo.java
@@ -26,8 +26,9 @@
}
@Override
- public UpdatableMethodOptimizationInfo mutableCopy() {
- UpdatableMethodOptimizationInfo updatableMethodOptimizationInfo = super.mutableCopy();
+ public MutableMethodOptimizationInfo toMutableOptimizationInfo() {
+ MutableMethodOptimizationInfo updatableMethodOptimizationInfo =
+ super.toMutableOptimizationInfo();
// Use null to specify that the min api is set to minApi.
updatableMethodOptimizationInfo.setApiReferenceLevel(null);
return updatableMethodOptimizationInfo;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
index e70a91f..8a098ac 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
@@ -12,9 +12,8 @@
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-public abstract class FieldOptimizationInfo {
-
- public abstract MutableFieldOptimizationInfo mutableCopy();
+public abstract class FieldOptimizationInfo
+ implements MemberOptimizationInfo<MutableFieldOptimizationInfo> {
public abstract boolean cannotBeKept();
@@ -56,20 +55,4 @@
public abstract boolean isDead();
public abstract boolean valueHasBeenPropagated();
-
- public boolean isDefaultFieldOptimizationInfo() {
- return false;
- }
-
- public DefaultFieldOptimizationInfo asDefaultFieldOptimizationInfo() {
- return null;
- }
-
- public boolean isMutableFieldOptimizationInfo() {
- return false;
- }
-
- public MutableFieldOptimizationInfo asMutableFieldOptimizationInfo() {
- return null;
- }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MemberOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MemberOptimizationInfo.java
new file mode 100644
index 0000000..e9ce890
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MemberOptimizationInfo.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.info;
+
+public interface MemberOptimizationInfo<
+ T extends MemberOptimizationInfo<T> & MutableOptimizationInfo> {
+
+ default boolean isMutableOptimizationInfo() {
+ return false;
+ }
+
+ default MutableMethodOptimizationInfo asMutableMethodOptimizationInfo() {
+ return null;
+ }
+
+ default MutableFieldOptimizationInfo asMutableFieldOptimizationInfo() {
+ return null;
+ }
+
+ T toMutableOptimizationInfo();
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index 0190cd0..41c145f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -21,7 +21,8 @@
import java.util.BitSet;
import java.util.Set;
-public abstract class MethodOptimizationInfo {
+public abstract class MethodOptimizationInfo
+ implements MemberOptimizationInfo<MutableMethodOptimizationInfo> {
enum InlinePreference {
NeverInline,
@@ -29,12 +30,6 @@
Default
}
- public abstract boolean isDefaultMethodOptimizationInfo();
-
- public abstract boolean isUpdatableMethodOptimizationInfo();
-
- public abstract UpdatableMethodOptimizationInfo asUpdatableMethodOptimizationInfo();
-
public abstract boolean cannotBeKept();
public abstract boolean classInitializerMayBePostponed();
@@ -104,8 +99,6 @@
public abstract boolean hasApiReferenceLevel();
- public abstract UpdatableMethodOptimizationInfo mutableCopy();
-
public static OptionalBool isApiSafeForInlining(
MethodOptimizationInfo caller, MethodOptimizationInfo inlinee, InternalOptions options) {
if (!options.apiModelingOptions().enableApiCallerIdentification) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
index b4141c6..9150ea0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
@@ -24,7 +24,8 @@
* updated directly, meaning that updates may become visible to concurrently processed methods in
* the {@link com.android.tools.r8.ir.conversion.IRConverter}.
*/
-public class MutableFieldOptimizationInfo extends FieldOptimizationInfo {
+public class MutableFieldOptimizationInfo extends FieldOptimizationInfo
+ implements MutableOptimizationInfo {
private static final int FLAGS_CANNOT_BE_KEPT = 1 << 0;
private static final int FLAGS_IS_DEAD = 1 << 1;
@@ -62,7 +63,6 @@
return this;
}
- @Override
public MutableFieldOptimizationInfo mutableCopy() {
MutableFieldOptimizationInfo copy = new MutableFieldOptimizationInfo();
copy.flags = flags;
@@ -137,11 +137,16 @@
}
@Override
- public boolean isMutableFieldOptimizationInfo() {
+ public boolean isMutableOptimizationInfo() {
return true;
}
@Override
+ public MutableFieldOptimizationInfo toMutableOptimizationInfo() {
+ return this;
+ }
+
+ @Override
public MutableFieldOptimizationInfo asMutableFieldOptimizationInfo() {
return this;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
similarity index 95%
rename from src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
rename to src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
index 85e2576..95d817f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
@@ -28,7 +28,8 @@
import java.util.Optional;
import java.util.Set;
-public class UpdatableMethodOptimizationInfo extends MethodOptimizationInfo {
+public class MutableMethodOptimizationInfo extends MethodOptimizationInfo
+ implements MutableOptimizationInfo {
private Set<DexType> initializedClassesOnNormalExit =
DefaultMethodOptimizationInfo.UNKNOWN_INITIALIZED_CLASSES_ON_NORMAL_EXIT;
@@ -130,13 +131,13 @@
private int flags = DEFAULT_FLAGS;
- UpdatableMethodOptimizationInfo() {
+ MutableMethodOptimizationInfo() {
// Intentionally left empty, just use the default values.
}
// Copy constructor used to create a mutable copy. Do not forget to copy from template when a new
// field is added.
- private UpdatableMethodOptimizationInfo(UpdatableMethodOptimizationInfo template) {
+ private MutableMethodOptimizationInfo(MutableMethodOptimizationInfo template) {
flags = template.flags;
initializedClassesOnNormalExit = template.initializedClassesOnNormalExit;
returnedArgument = template.returnedArgument;
@@ -153,12 +154,12 @@
apiReferenceLevel = template.apiReferenceLevel;
}
- public UpdatableMethodOptimizationInfo fixupClassTypeReferences(
+ public MutableMethodOptimizationInfo fixupClassTypeReferences(
AppView<? extends AppInfoWithClassHierarchy> appView, GraphLens lens) {
return fixupClassTypeReferences(appView, lens, emptySet());
}
- public UpdatableMethodOptimizationInfo fixupClassTypeReferences(
+ public MutableMethodOptimizationInfo fixupClassTypeReferences(
AppView<? extends AppInfoWithClassHierarchy> appView,
GraphLens lens,
Set<DexType> prunedTypes) {
@@ -180,13 +181,13 @@
return this;
}
- public UpdatableMethodOptimizationInfo fixupAbstractReturnValue(
+ public MutableMethodOptimizationInfo fixupAbstractReturnValue(
AppView<AppInfoWithLiveness> appView, GraphLens lens) {
abstractReturnValue = abstractReturnValue.rewrittenWithLens(appView, lens);
return this;
}
- public UpdatableMethodOptimizationInfo fixupInstanceInitializerInfo(
+ public MutableMethodOptimizationInfo fixupInstanceInitializerInfo(
AppView<AppInfoWithLiveness> appView, GraphLens lens) {
instanceInitializerInfoCollection =
instanceInitializerInfoCollection.rewrittenWithLens(appView, lens);
@@ -214,21 +215,6 @@
}
@Override
- public boolean isDefaultMethodOptimizationInfo() {
- return false;
- }
-
- @Override
- public boolean isUpdatableMethodOptimizationInfo() {
- return true;
- }
-
- @Override
- public UpdatableMethodOptimizationInfo asUpdatableMethodOptimizationInfo() {
- return this;
- }
-
- @Override
public boolean cannotBeKept() {
return isFlagSet(CANNOT_BE_KEPT_FLAG);
}
@@ -511,14 +497,28 @@
return apiReferenceLevel != null;
}
- public UpdatableMethodOptimizationInfo setApiReferenceLevel(AndroidApiLevel apiReferenceLevel) {
- this.apiReferenceLevel = Optional.ofNullable(apiReferenceLevel);
+ @Override
+ public boolean isMutableOptimizationInfo() {
+ return true;
+ }
+
+ @Override
+ public MutableMethodOptimizationInfo toMutableOptimizationInfo() {
return this;
}
@Override
- public UpdatableMethodOptimizationInfo mutableCopy() {
- return new UpdatableMethodOptimizationInfo(this);
+ public MutableMethodOptimizationInfo asMutableMethodOptimizationInfo() {
+ return this;
+ }
+
+ public MutableMethodOptimizationInfo setApiReferenceLevel(AndroidApiLevel apiReferenceLevel) {
+ this.apiReferenceLevel = Optional.ofNullable(apiReferenceLevel);
+ return this;
+ }
+
+ public MutableMethodOptimizationInfo mutableCopy() {
+ return new MutableMethodOptimizationInfo(this);
}
public void adjustOptimizationInfoAfterRemovingThisParameter() {
@@ -536,7 +536,8 @@
|| returnedArgument > 0;
returnedArgument =
returnedArgument == DefaultMethodOptimizationInfo.UNKNOWN_RETURNED_ARGUMENT
- ? DefaultMethodOptimizationInfo.UNKNOWN_RETURNED_ARGUMENT : returnedArgument - 1;
+ ? DefaultMethodOptimizationInfo.UNKNOWN_RETURNED_ARGUMENT
+ : returnedArgument - 1;
// mayHaveSideEffects: `this` Argument didn't have side effects, so removing it doesn't affect
// whether or not the method may have side effects.
// returnValueOnlyDependsOnArguments:
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableOptimizationInfo.java
new file mode 100644
index 0000000..bf34966
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableOptimizationInfo.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.info;
+
+public interface MutableOptimizationInfo {}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
index b384693..26516df 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMember;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.ir.conversion.FieldOptimizationFeedback;
@@ -23,7 +24,18 @@
void fixup(DexEncodedField field, MutableFieldOptimizationInfo optimizationInfo);
- void fixup(DexEncodedMethod method, UpdatableMethodOptimizationInfo optimizationInfo);
+ void fixup(DexEncodedMethod method, MutableMethodOptimizationInfo optimizationInfo);
+
+ default void fixup(DexEncodedMember<?, ?> member) {
+ MemberOptimizationInfo<?> optimizationInfo = member.getOptimizationInfo();
+ if (optimizationInfo.isMutableOptimizationInfo()) {
+ member.apply(
+ field -> {
+ fixup(field, optimizationInfo.asMutableFieldOptimizationInfo());
+ },
+ method -> fixup(method, optimizationInfo.asMutableMethodOptimizationInfo()));
+ }
+ }
}
public void fixupOptimizationInfos(
@@ -38,26 +50,7 @@
OptimizationInfoFixer fixer)
throws ExecutionException {
ThreadUtils.processItems(
- classes,
- clazz -> {
- for (DexEncodedField field : clazz.fields()) {
- FieldOptimizationInfo optimizationInfo = field.getOptimizationInfo();
- if (optimizationInfo.isMutableFieldOptimizationInfo()) {
- fixer.fixup(field, optimizationInfo.asMutableFieldOptimizationInfo());
- } else {
- assert optimizationInfo.isDefaultFieldOptimizationInfo();
- }
- }
- for (DexEncodedMethod method : clazz.methods()) {
- MethodOptimizationInfo optimizationInfo = method.getOptimizationInfo();
- if (optimizationInfo.isUpdatableMethodOptimizationInfo()) {
- fixer.fixup(method, optimizationInfo.asUpdatableMethodOptimizationInfo());
- } else {
- assert optimizationInfo.isDefaultMethodOptimizationInfo();
- }
- }
- },
- executorService);
+ classes, clazz -> clazz.members().forEach(fixer::fixup), executorService);
}
public void modifyAppInfoWithLiveness(Consumer<AppInfoWithLivenessModifier> consumer) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index 5a12983..dd781e8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -36,7 +36,7 @@
AppInfoWithLiveness.modifier();
private final Map<DexEncodedField, MutableFieldOptimizationInfo> fieldOptimizationInfos =
new IdentityHashMap<>();
- private final Map<DexEncodedMethod, UpdatableMethodOptimizationInfo> methodOptimizationInfos =
+ private final Map<DexEncodedMethod, MutableMethodOptimizationInfo> methodOptimizationInfos =
new IdentityHashMap<>();
private final Map<DexEncodedMethod, ConstraintWithTarget> processed = new IdentityHashMap<>();
@@ -46,24 +46,23 @@
if (info != null) {
return info;
}
- info = field.getOptimizationInfo().mutableCopy();
+ info = field.getOptimizationInfo().toMutableOptimizationInfo().mutableCopy();
fieldOptimizationInfos.put(field, info);
return info;
}
- private synchronized UpdatableMethodOptimizationInfo getMethodOptimizationInfoForUpdating(
+ private synchronized MutableMethodOptimizationInfo getMethodOptimizationInfoForUpdating(
DexEncodedMethod method) {
- UpdatableMethodOptimizationInfo info = methodOptimizationInfos.get(method);
+ MutableMethodOptimizationInfo info = methodOptimizationInfos.get(method);
if (info != null) {
return info;
}
- info = method.getOptimizationInfo().mutableCopy();
+ info = method.getOptimizationInfo().toMutableOptimizationInfo().mutableCopy();
methodOptimizationInfos.put(method, info);
return info;
}
- private UpdatableMethodOptimizationInfo getMethodOptimizationInfoForUpdating(
- ProgramMethod method) {
+ private MutableMethodOptimizationInfo getMethodOptimizationInfoForUpdating(ProgramMethod method) {
return getMethodOptimizationInfoForUpdating(method.getDefinition());
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
index e440049..08236e2 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.Reporter;
@@ -37,6 +38,7 @@
private final int flags;
private final String name;
+ private final boolean nameCanBeSynthesizedFromClassOrAnonymousObjectOrigin;
private final String moduleName;
private final List<KotlinConstructorInfo> constructorsWithNoBacking;
private final KotlinDeclarationContainerInfo declarationContainerInfo;
@@ -54,6 +56,7 @@
private KotlinClassInfo(
int flags,
String name,
+ boolean nameCanBeSynthesizedFromClassOrAnonymousObjectOrigin,
String moduleName,
KotlinDeclarationContainerInfo declarationContainerInfo,
List<KotlinTypeParameterInfo> typeParameters,
@@ -69,6 +72,8 @@
int[] metadataVersion) {
this.flags = flags;
this.name = name;
+ this.nameCanBeSynthesizedFromClassOrAnonymousObjectOrigin =
+ nameCanBeSynthesizedFromClassOrAnonymousObjectOrigin;
this.moduleName = moduleName;
this.declarationContainerInfo = declarationContainerInfo;
this.typeParameters = typeParameters;
@@ -127,9 +132,17 @@
KotlinDeclarationContainerInfo.create(
kmClass, methodMap, fieldMap, factory, reporter, keepByteCode, extensionInformation);
setCompanionObject(kmClass, hostClass, reporter);
+ KotlinTypeReference anonymousObjectOrigin = getAnonymousObjectOrigin(kmClass, factory);
+ boolean nameCanBeDeducedFromClassOrOrigin =
+ kmClass.name.equals(
+ KotlinMetadataUtils.getKotlinClassName(
+ hostClass, hostClass.getType().toDescriptorString()))
+ || (anonymousObjectOrigin != null
+ && kmClass.name.equals(anonymousObjectOrigin.toKotlinClassifier(true)));
return new KotlinClassInfo(
kmClass.getFlags(),
kmClass.name,
+ nameCanBeDeducedFromClassOrOrigin,
JvmExtensionsKt.getModuleName(kmClass),
container,
KotlinTypeParameterInfo.create(kmClass.getTypeParameters(), factory, reporter),
@@ -139,7 +152,7 @@
getNestedClasses(hostClass, kmClass.getNestedClasses(), factory),
kmClass.getEnumEntries(),
KotlinVersionRequirementInfo.create(kmClass.getVersionRequirements()),
- getAnonymousObjectOrigin(kmClass, factory),
+ anonymousObjectOrigin,
packageName,
KotlinLocalDelegatedPropertyInfo.create(
JvmExtensionsKt.getLocalDelegatedProperties(kmClass), factory, reporter),
@@ -221,14 +234,29 @@
// Set potentially renamed class name.
DexString originalDescriptor = clazz.type.descriptor;
DexString rewrittenDescriptor = namingLens.lookupDescriptor(clazz.type);
- // If the original descriptor equals the rewritten descriptor, we pick the original name
- // to preserve potential errors in the original name. As an example, the kotlin stdlib has
- // name: .kotlin/collections/CollectionsKt___CollectionsKt$groupingBy$1, which seems incorrect.
boolean rewritten = !originalDescriptor.equals(rewrittenDescriptor);
- kmClass.setName(
- !rewritten
- ? this.name
- : DescriptorUtils.getBinaryNameFromDescriptor(rewrittenDescriptor.toString()));
+ if (!nameCanBeSynthesizedFromClassOrAnonymousObjectOrigin) {
+ kmClass.setName(this.name);
+ } else {
+ String rewrittenName = null;
+ // When the class has an anonymousObjectOrigin and the name equals the identifier there, we
+ // keep the name tied to the anonymousObjectOrigin.
+ if (anonymousObjectOrigin != null
+ && name.equals(anonymousObjectOrigin.toKotlinClassifier(true))) {
+ Box<String> rewrittenOrigin = new Box<>();
+ anonymousObjectOrigin.toRenamedBinaryNameOrDefault(
+ rewrittenOrigin::set, appView, namingLens, null);
+ if (rewrittenOrigin.isSet()) {
+ rewrittenName = "." + rewrittenOrigin.get();
+ }
+ }
+ if (rewrittenName == null) {
+ rewrittenName =
+ KotlinMetadataUtils.getKotlinClassName(clazz, rewrittenDescriptor.toString());
+ }
+ kmClass.setName(rewrittenName);
+ rewritten |= !name.equals(rewrittenName);
+ }
// Find a companion object.
for (DexEncodedField field : clazz.fields()) {
if (field.getKotlinInfo().isCompanion()) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java
index 5f5b696..9b2939e 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.kotlin;
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getKotlinLocalOrAnonymousNameFromDescriptor;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexItemFactory;
@@ -68,13 +70,8 @@
boolean rewrite(KmTypeVisitor visitor, AppView<?> appView, NamingLens namingLens) {
return type.toRenamedDescriptorOrDefault(
descriptor -> {
- // For local or anonymous classes, the classifier is prefixed with '.' and inner classes
- // are separated with '$'.
- if (isLocalOrAnonymous) {
- visitor.visitClass("." + DescriptorUtils.getBinaryNameFromDescriptor(descriptor));
- } else {
- visitor.visitClass(DescriptorUtils.descriptorToKotlinClassifier(descriptor));
- }
+ visitor.visitClass(
+ getKotlinLocalOrAnonymousNameFromDescriptor(descriptor, isLocalOrAnonymous));
},
appView,
namingLens,
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
index eb654b5..1b928d9 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
@@ -179,7 +179,11 @@
}
for (DexEncodedMethod method : clazz.methods()) {
if (method.getKotlinInfo().isFunction()) {
- method.getKotlinInfo().asFunction().rewrite(functionProvider, method, appView, namingLens);
+ rewritten |=
+ method
+ .getKotlinInfo()
+ .asFunction()
+ .rewrite(functionProvider, method, appView, namingLens);
continue;
}
KotlinPropertyInfo kotlinPropertyInfo = method.getKotlinInfo().asProperty();
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
index d22a2b4..a094a88 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -3,8 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.kotlin;
+import static com.android.tools.r8.kotlin.KotlinClassMetadataReader.toKotlinClassMetadata;
import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getInvalidKotlinInfo;
import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getNoKotlinInfo;
+import static com.android.tools.r8.kotlin.KotlinMetadataWriter.kotlinMetadataToString;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexAnnotation;
@@ -160,6 +162,15 @@
// TODO(b/185756596): Remove when special handling is no longer needed.
if (!kotlinClassHeader.getSecond() && !appView.enableWholeProgramOptimizations()) {
// No rewrite occurred and the data is the same as before.
+ assert appView.checkForTesting(
+ () ->
+ verifyRewrittenMetadataIsEquivalent(
+ clazz.annotations().getFirstMatching(factory.kotlinMetadataType),
+ createKotlinMetadataAnnotation(
+ kotlinClassHeader.getFirst(),
+ kotlinInfo.getPackageName(),
+ getMaxVersion(METADATA_VERSION_1_4, kotlinInfo.getMetadataVersion()),
+ writeMetadataFieldInfo)));
return;
}
DexAnnotation newMeta =
@@ -177,6 +188,16 @@
}
}
+ private boolean verifyRewrittenMetadataIsEquivalent(
+ DexAnnotation original, DexAnnotation rewritten) {
+ String originalMetadata =
+ kotlinMetadataToString("", toKotlinClassMetadata(kotlin, original.annotation));
+ String rewrittenMetadata =
+ kotlinMetadataToString("", toKotlinClassMetadata(kotlin, rewritten.annotation));
+ assert originalMetadata.equals(rewrittenMetadata) : "The metadata should be equivalent";
+ return true;
+ }
+
private boolean kotlinMetadataFieldExists(
DexClass kotlinMetadata, AppView<?> appView, DexString fieldName) {
if (!appView.appInfo().hasLiveness()) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
index 7eb45f6..2d6f39a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.shaking.ProguardConfigurationRule;
@@ -201,4 +202,27 @@
// Check if the type is matched
return proguardKeepRule.getClassNames().matches(factory.kotlinMetadataType);
}
+
+ static String getKotlinClassName(DexClass clazz, String descriptor) {
+ InnerClassAttribute innerClassAttribute = clazz.getInnerClassAttributeForThisClass();
+ if (innerClassAttribute != null && innerClassAttribute.getOuter() != null) {
+ return DescriptorUtils.descriptorToKotlinClassifier(descriptor);
+ } else if (clazz.isLocalClass() || clazz.isAnonymousClass()) {
+ return getKotlinLocalOrAnonymousNameFromDescriptor(descriptor, true);
+ } else {
+ // We no longer have an innerclass relationship to maintain and we therefore return a binary
+ // name.
+ return DescriptorUtils.getBinaryNameFromDescriptor(descriptor);
+ }
+ }
+
+ static String getKotlinLocalOrAnonymousNameFromDescriptor(
+ String descriptor, boolean isLocalOrAnonymous) {
+ // For local or anonymous classes, the classifier is prefixed with '.' and inner classes
+ // are separated with '$'.
+ if (isLocalOrAnonymous) {
+ return "." + DescriptorUtils.getBinaryNameFromDescriptor(descriptor);
+ }
+ return DescriptorUtils.descriptorToKotlinClassifier(descriptor);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java
index 3f83e53..8fe760b 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java
@@ -42,10 +42,10 @@
NamingLens namingLens) {
if (isStarProjection()) {
starProjectionProvider.get();
+ return false;
} else {
- typeInfo.rewrite(flags -> visitorProvider.get(flags, variance), appView, namingLens);
+ return typeInfo.rewrite(flags -> visitorProvider.get(flags, variance), appView, namingLens);
}
- return false;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java
index 4d0d990..c166103 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.kotlin;
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getKotlinLocalOrAnonymousNameFromDescriptor;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexItemFactory;
@@ -81,6 +83,14 @@
return !known.toDescriptorString().equals(renamedString);
}
+ String toKotlinClassifier(boolean isLocalOrAnonymous) {
+ if (known == null) {
+ return unknown;
+ }
+ return getKotlinLocalOrAnonymousNameFromDescriptor(
+ known.toDescriptorString(), isLocalOrAnonymous);
+ }
+
boolean toRenamedBinaryNameOrDefault(
Consumer<String> rewrittenConsumer,
AppView<?> appView,
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 239c0da..a4478d6 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -47,7 +47,6 @@
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
-import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
import com.android.tools.r8.synthesis.CommittedItems;
@@ -522,7 +521,6 @@
// TODO(b/150693139): Remove these exceptions once fixed.
|| InterfaceMethodRewriter.isCompanionClassType(type)
|| InterfaceMethodRewriter.isEmulatedLibraryClassType(type)
- || DesugaredLibraryRetargeter.isRetargetType(type, options())
// TODO(b/150736225): Not sure how to remove these.
|| DesugaredLibraryAPIConverter.isVivifiedType(type)
: "Failed lookup of non-missing type: " + type;
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 61db1bf..743fa3c 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1813,6 +1813,8 @@
enqueueFirstNonSerializableClassInitializer(clazz, reason);
}
+ checkDefinitionForSoftPinning(clazz);
+
processAnnotations(clazz);
// If this type has deferred annotations, we have to process those now, too.
@@ -2618,7 +2620,7 @@
// Add all dependent members to the workqueue.
enqueueRootItems(rootSet.getDependentItems(field.getDefinition()));
- checkMemberForSoftPinning(field);
+ checkDefinitionForSoftPinning(field);
// Notify analyses.
analyses.forEach(analysis -> analysis.processNewlyLiveField(field, context));
@@ -3868,7 +3870,7 @@
// Add all dependent members to the workqueue.
enqueueRootItems(rootSet.getDependentItems(definition));
- checkMemberForSoftPinning(method);
+ checkDefinitionForSoftPinning(method);
// Notify analyses.
analyses.forEach(analysis -> analysis.processNewlyLiveMethod(method, context));
@@ -3930,15 +3932,15 @@
methodDefinition.getMutableOptimizationInfo().setApiReferenceLevel(maxApiReferenceLevel));
}
- private void checkMemberForSoftPinning(ProgramMember<?, ?> member) {
- DexMember<?, ?> reference = member.getDefinition().getReference();
+ private void checkDefinitionForSoftPinning(ProgramDefinition definition) {
+ DexReference reference = definition.getReference();
Set<ProguardKeepRuleBase> softPinRules = rootSet.softPinned.getRulesForReference(reference);
if (softPinRules != null) {
assert softPinRules.stream().noneMatch(r -> r.getModifiers().allowsOptimization);
keepInfo.joinInfo(reference, appInfo, Joiner::pin);
}
// Identify dependent soft pinning.
- MutableItemsWithRules items = rootSet.dependentSoftPinned.get(member.getHolderType());
+ MutableItemsWithRules items = rootSet.dependentSoftPinned.get(definition.getContextType());
if (items != null && items.containsReference(reference)) {
assert items.getRulesForReference(reference).stream()
.noneMatch(r -> r.getModifiers().allowsOptimization);
diff --git a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
index 0ec8c27..2cc626d 100644
--- a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
+++ b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.shaking;
import static com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter.DESCRIPTOR_VIVIFIED_PREFIX;
-import static com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter.getRetargetPackageAndClassPrefixDescriptor;
import static com.android.tools.r8.utils.collections.IdentityHashSetFromMap.newProgramDerivedContextSet;
import com.android.tools.r8.diagnostic.MissingDefinitionsDiagnostic;
@@ -23,7 +22,6 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.synthesis.CommittedItems;
import com.android.tools.r8.synthesis.SyntheticItems.SynthesizingContextOracle;
-import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.SetUtils;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
@@ -280,15 +278,10 @@
private static Predicate<DexType> getIsCompilerSynthesizedAllowedMissingClassesPredicate(
AppView<?> appView) {
DexItemFactory dexItemFactory = appView.dexItemFactory();
- InternalOptions options = appView.options();
- DexString retargetPackageAndClassPrefixDescriptor =
- dexItemFactory.createString(
- getRetargetPackageAndClassPrefixDescriptor(options.desugaredLibraryConfiguration));
DexString vivifiedClassNamePrefix = dexItemFactory.createString(DESCRIPTOR_VIVIFIED_PREFIX);
return type -> {
DexString descriptor = type.getDescriptor();
- return descriptor.startsWith(retargetPackageAndClassPrefixDescriptor)
- || descriptor.startsWith(vivifiedClassNamePrefix);
+ return descriptor.startsWith(vivifiedClassNamePrefix);
};
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index 6d3a8ca..49c4a1e 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -378,15 +378,15 @@
// For Proguard -keepattributes are only applicable when obfuscating.
keepAttributePatterns.addAll(ProguardKeepAttributes.KEEP_ALL);
}
- // If either of the flags -dontshrink, -dontobfuscate or -dontoptimize is passed, or
- // shrinking or minification is turned off through the API, then add a match all rule
- // which will apply that.
- if (!isShrinking() || !isObfuscating() || !isOptimizing()) {
+ // If either of the flags -dontshrink or -dontobfuscate, or shrinking or minification is
+ // turned off through the API, then add a match all rule which will apply that.
+ if (!isShrinking() || !isObfuscating()) {
ProguardKeepRule rule =
ProguardKeepRule.defaultKeepAllRule(
modifiers -> {
modifiers.setAllowsShrinking(isShrinking());
- modifiers.setAllowsOptimization(isOptimizing());
+ // TODO(b/189807246): This should be removed.
+ modifiers.setAllowsOptimization(true);
modifiers.setAllowsObfuscation(isObfuscating());
});
addRule(rule);
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 ef2f149..df989fe 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -18,10 +18,10 @@
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.graph.NestMemberClassAttribute;
import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.MutableMethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
-import com.android.tools.r8.ir.optimize.info.UpdatableMethodOptimizationInfo;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions;
@@ -402,7 +402,7 @@
@Override
public void fixup(
- DexEncodedMethod method, UpdatableMethodOptimizationInfo optimizationInfo) {
+ DexEncodedMethod method, MutableMethodOptimizationInfo optimizationInfo) {
optimizationInfo.fixupClassTypeReferences(appView, appView.graphLens(), prunedTypes);
}
});
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 800332f..e0837dd 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -368,6 +368,9 @@
}
return false;
}
+ if (sourceClass.isAnnotation()) {
+ return false;
+ }
if (!sourceClass.isInterface()
&& targetClass.isSerializable(appView)
&& !appInfo.isSerializable(sourceClass.type)) {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
index 8e78b09..65c510c 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
@@ -22,19 +22,23 @@
import com.android.tools.r8.graph.NestHostClassAttribute;
import com.android.tools.r8.graph.NestMemberClassAttribute;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
-abstract class SyntheticClassBuilder<B extends SyntheticClassBuilder<B, C>, C extends DexClass> {
+public abstract class SyntheticClassBuilder<
+ B extends SyntheticClassBuilder<B, C>, C extends DexClass> {
private final DexItemFactory factory;
private final DexType type;
+ private final SyntheticKind syntheticKind;
private final Origin origin;
private boolean isAbstract = false;
+ private boolean isInterface = false;
private Kind originKind;
private DexType superType;
private DexTypeList interfaces = DexTypeList.empty();
@@ -46,9 +50,14 @@
private List<SyntheticMethodBuilder> methods = new ArrayList<>();
private ClassSignature signature = ClassSignature.noSignature();
- SyntheticClassBuilder(DexType type, SynthesizingContext context, DexItemFactory factory) {
+ SyntheticClassBuilder(
+ DexType type,
+ SyntheticKind syntheticKind,
+ SynthesizingContext context,
+ DexItemFactory factory) {
this.factory = factory;
this.type = type;
+ this.syntheticKind = syntheticKind;
this.origin = context.getInputContextOrigin();
this.superType = factory.objectType;
}
@@ -65,6 +74,10 @@
return type;
}
+ public SyntheticKind getSyntheticKind() {
+ return syntheticKind;
+ }
+
public B setInterfaces(List<DexType> interfaces) {
this.interfaces =
interfaces.isEmpty()
@@ -78,6 +91,12 @@
return self();
}
+ public B setInterface() {
+ setAbstract();
+ isInterface = true;
+ return self();
+ }
+
public B setOriginKind(Kind originKind) {
this.originKind = originKind;
return self();
@@ -126,9 +145,11 @@
public C build() {
int flag = isAbstract ? Constants.ACC_ABSTRACT : Constants.ACC_FINAL;
+ int itfFlag = isInterface ? Constants.ACC_INTERFACE : 0;
+ assert !isInterface || isAbstract;
ClassAccessFlags accessFlags =
ClassAccessFlags.fromSharedAccessFlags(
- flag | Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
+ flag | itfFlag | Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
NestHostClassAttribute nestHost = null;
List<NestMemberClassAttribute> nestMembers = Collections.emptyList();
EnclosingMethodAttribute enclosingMembers = null;
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClasspathClassBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClasspathClassBuilder.java
index 20cd075..b888913 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClasspathClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClasspathClassBuilder.java
@@ -8,13 +8,17 @@
import com.android.tools.r8.graph.DexClasspathClass;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
public class SyntheticClasspathClassBuilder
extends SyntheticClassBuilder<SyntheticClasspathClassBuilder, DexClasspathClass> {
SyntheticClasspathClassBuilder(
- DexType type, SynthesizingContext context, DexItemFactory factory) {
- super(type, context, factory);
+ DexType type,
+ SyntheticKind syntheticKind,
+ SynthesizingContext context,
+ DexItemFactory factory) {
+ super(type, syntheticKind, context, factory);
setOriginKind(Kind.CF);
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index 89552fb..8c37bf1 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -125,6 +125,14 @@
methodMap.put(from, to);
}
+ void setRepresentative(DexField field, DexField representative) {
+ fieldMap.setRepresentative(field, representative);
+ }
+
+ void setRepresentative(DexMethod method, DexMethod representative) {
+ methodMap.setRepresentative(method, representative);
+ }
+
SyntheticFinalizationGraphLens build(AppView<?> appView) {
if (typeMap.isEmpty() && fieldMap.isEmpty() && methodMap.isEmpty()) {
return null;
@@ -477,6 +485,26 @@
syntheticMethodDefinition.getReference()));
});
+ Iterables.<EquivalenceGroup<? extends SyntheticDefinition<?, ?, DexProgramClass>>>concat(
+ syntheticClassGroups.values(), syntheticMethodGroups.values())
+ .forEach(
+ syntheticGroup ->
+ syntheticGroup
+ .getRepresentative()
+ .getHolder()
+ .forEachProgramMember(
+ member -> {
+ if (member.isProgramField()) {
+ DexField field = member.asProgramField().getReference();
+ DexField rewrittenField = treeFixer.fixupFieldReference(field);
+ lensBuilder.setRepresentative(rewrittenField, field);
+ } else {
+ DexMethod method = member.asProgramMethod().getReference();
+ DexMethod rewrittenMethod = treeFixer.fixupMethodReference(method);
+ lensBuilder.setRepresentative(rewrittenMethod, method);
+ }
+ }));
+
for (DexType key : syntheticMethodGroups.keySet()) {
assert application.definitionFor(key) != null;
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index 15719ce..2d0970f 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -11,13 +11,18 @@
import com.android.tools.r8.graph.ClasspathOrLibraryClass;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexClasspathClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import com.android.tools.r8.graph.MethodCollection;
import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.PrunedItems;
@@ -462,7 +467,7 @@
DexType type,
DexItemFactory factory) {
SyntheticProgramClassBuilder classBuilder =
- new SyntheticProgramClassBuilder(type, outerContext, factory);
+ new SyntheticProgramClassBuilder(type, kind, outerContext, factory);
fn.accept(classBuilder);
DexProgramClass clazz = classBuilder.build();
addPendingDefinition(new SyntheticProgramClassDefinition(kind, outerContext, clazz));
@@ -491,7 +496,10 @@
Consumer<SyntheticProgramClassBuilder> fn) {
// Obtain the outer synthesizing context in the case the context itself is synthetic.
// This is to ensure a flat input-type -> synthetic-item mapping.
- SynthesizingContext outerContext = getSynthesizingContext(context, appView);
+ SynthesizingContext outerContext =
+ context.isProgramClass()
+ ? getSynthesizingContext(context.asProgramClass(), appView)
+ : SynthesizingContext.fromNonSyntheticInputContext(context.asClasspathOrLibraryClass());
DexType type = SyntheticNaming.createFixedType(kind, outerContext, appView.dexItemFactory());
return internalCreateClass(kind, fn, outerContext, type, appView.dexItemFactory());
}
@@ -503,16 +511,20 @@
*/
public DexProgramClass ensureFixedClass(
SyntheticKind kind,
- DexProgramClass context,
+ DexClass context,
AppView<?> appView,
- Consumer<SyntheticProgramClassBuilder> fn) {
+ Consumer<SyntheticProgramClassBuilder> fn,
+ Consumer<DexProgramClass> onCreationConsumer) {
assert kind.isFixedSuffixSynthetic;
// Obtain the outer synthesizing context in the case the context itself is synthetic.
// This is to ensure a flat input-type -> synthetic-item mapping.
- SynthesizingContext outerContext = getSynthesizingContext(context, appView);
+ SynthesizingContext outerContext =
+ context.isProgramClass()
+ ? getSynthesizingContext(context.asProgramClass(), appView)
+ : SynthesizingContext.fromNonSyntheticInputContext(context.asClasspathOrLibraryClass());
DexType type = SyntheticNaming.createFixedType(kind, outerContext, appView.dexItemFactory());
// Fast path is that the synthetic is already present. If so it must be a program class.
- DexClass clazz = appView.definitionFor(type, context);
+ DexClass clazz = appView.definitionFor(type);
if (clazz != null) {
assert isSyntheticClass(type);
assert clazz.isProgramClass();
@@ -521,17 +533,36 @@
// Slow path creates the class using the context to make it thread safe.
synchronized (context) {
// Recheck if it is present now the lock is held.
- clazz = appView.definitionFor(type, context);
+ clazz = appView.definitionFor(type);
if (clazz != null) {
assert isSyntheticClass(type);
assert clazz.isProgramClass();
return clazz.asProgramClass();
}
assert !isSyntheticClass(type);
- return internalCreateClass(kind, fn, outerContext, type, appView.dexItemFactory());
+ DexProgramClass dexProgramClass =
+ internalCreateClass(kind, fn, outerContext, type, appView.dexItemFactory());
+ onCreationConsumer.accept(dexProgramClass);
+ return dexProgramClass;
}
}
+ public ProgramMethod ensureFixedClassMethod(
+ DexString name,
+ DexProto proto,
+ SyntheticKind kind,
+ DexProgramClass context,
+ AppView<?> appView,
+ Consumer<SyntheticProgramClassBuilder> buildClassCallback,
+ Consumer<SyntheticMethodBuilder> buildMethodCallback) {
+ DexProgramClass clazz =
+ ensureFixedClass(kind, context, appView, buildClassCallback, ignored -> {});
+ DexMethod methodReference = appView.dexItemFactory().createMethod(clazz.getType(), proto, name);
+ DexEncodedMethod methodDefinition =
+ internalEnsureMethod(methodReference, clazz, kind, appView, buildMethodCallback);
+ return new ProgramMethod(clazz, methodDefinition);
+ }
+
public DexClasspathClass createFixedClasspathClass(
SyntheticKind kind, DexClasspathClass context, DexItemFactory factory) {
// Obtain the outer synthesizing context in the case the context itself is synthetic.
@@ -539,54 +570,87 @@
SynthesizingContext outerContext = SynthesizingContext.fromNonSyntheticInputContext(context);
DexType type = SyntheticNaming.createFixedType(kind, outerContext, factory);
SyntheticClasspathClassBuilder classBuilder =
- new SyntheticClasspathClassBuilder(type, outerContext, factory);
+ new SyntheticClasspathClassBuilder(type, kind, outerContext, factory);
DexClasspathClass clazz = classBuilder.build();
addPendingDefinition(new SyntheticClasspathClassDefinition(kind, outerContext, clazz));
return clazz;
}
- // This is a temporary API for migration to the hygienic synthetic, the classes created behave
- // like a hygienic synthetic, but use the legacyType passed as parameter instead of the
- // hygienic type.
- private DexClasspathClass ensureFixedClasspathClassWhileMigrating(
- SyntheticKind kind, DexType legacyType, ClasspathOrLibraryClass context, AppView<?> appView) {
+ public DexClasspathClass ensureFixedClasspathClass(
+ SyntheticKind kind,
+ ClasspathOrLibraryClass context,
+ AppView<?> appView,
+ Consumer<SyntheticClasspathClassBuilder> classConsumer,
+ Consumer<DexClasspathClass> onCreationConsumer) {
+ SynthesizingContext outerContext = SynthesizingContext.fromNonSyntheticInputContext(context);
+ DexType type = SyntheticNaming.createFixedType(kind, outerContext, appView.dexItemFactory());
+ DexClass dexClass = appView.appInfo().definitionForWithoutExistenceAssert(type);
+ if (dexClass != null) {
+ assert dexClass.isClasspathClass();
+ return dexClass.asClasspathClass();
+ }
synchronized (context) {
- DexClass dexClass = appView.definitionFor(legacyType);
+ dexClass = appView.appInfo().definitionForWithoutExistenceAssert(type);
if (dexClass != null) {
assert dexClass.isClasspathClass();
return dexClass.asClasspathClass();
}
// Obtain the outer synthesizing context in the case the context itself is synthetic.
// This is to ensure a flat input-type -> synthetic-item mapping.
- SynthesizingContext outerContext = SynthesizingContext.fromNonSyntheticInputContext(context);
SyntheticClasspathClassBuilder classBuilder =
- new SyntheticClasspathClassBuilder(legacyType, outerContext, appView.dexItemFactory());
+ new SyntheticClasspathClassBuilder(type, kind, outerContext, appView.dexItemFactory());
+ classConsumer.accept(classBuilder);
DexClasspathClass clazz = classBuilder.build();
+ onCreationConsumer.accept(clazz);
addPendingDefinition(new SyntheticClasspathClassDefinition(kind, outerContext, clazz));
return clazz;
}
}
- // This is a temporary API for migration to the hygienic synthetic, the classes created behave
- // like a hygienic synthetic, but use the legacyType passed as parameter instead of the
- // hygienic type.
- public void ensureDirectMethodOnSyntheticClasspathClassWhileMigrating(
+ public DexClassAndMethod ensureFixedClasspathClassMethod(
+ DexString methodName,
+ DexProto methodProto,
SyntheticKind kind,
- DexType legacyType,
ClasspathOrLibraryClass context,
AppView<?> appView,
- DexMethod method,
- Consumer<SyntheticMethodBuilder> builderConsumer) {
- DexClasspathClass syntheticClass =
- ensureFixedClasspathClassWhileMigrating(kind, legacyType, context, appView);
- synchronized (syntheticClass) {
- if (syntheticClass.lookupMethod(method) != null) {
- return;
+ Consumer<SyntheticClasspathClassBuilder> buildClassCallback,
+ Consumer<SyntheticMethodBuilder> buildMethodCallback) {
+ DexClasspathClass clazz =
+ ensureFixedClasspathClass(kind, context, appView, buildClassCallback, ignored -> {});
+ DexMethod methodReference =
+ appView.dexItemFactory().createMethod(clazz.getType(), methodProto, methodName);
+ DexEncodedMethod methodDefinition =
+ internalEnsureMethod(methodReference, clazz, kind, appView, buildMethodCallback);
+ return DexClassAndMethod.create(clazz, methodDefinition);
+ }
+
+ private DexEncodedMethod internalEnsureMethod(
+ DexMethod methodReference,
+ DexClass clazz,
+ SyntheticKind kind,
+ AppView<?> appView,
+ Consumer<SyntheticMethodBuilder> buildMethodCallback) {
+ MethodCollection methodCollection = clazz.getMethodCollection();
+ DexEncodedMethod methodDefinition = methodCollection.getMethod(methodReference);
+ if (methodDefinition != null) {
+ return methodDefinition;
+ }
+ synchronized (methodCollection) {
+ methodDefinition = methodCollection.getMethod(methodReference);
+ if (methodDefinition != null) {
+ return methodDefinition;
}
- SyntheticMethodBuilder syntheticMethodBuilder =
- new SyntheticMethodBuilder(appView.dexItemFactory(), syntheticClass.type);
- builderConsumer.accept(syntheticMethodBuilder);
- syntheticClass.addDirectMethod(syntheticMethodBuilder.build());
+ SyntheticMethodBuilder builder =
+ new SyntheticMethodBuilder(appView.dexItemFactory(), clazz.getType(), kind);
+ builder.setName(methodReference.getName());
+ builder.setProto(methodReference.getProto());
+ buildMethodCallback.accept(builder);
+ // TODO(b/183998768): Make this safe for recursive definitions.
+ // For example, the builder should be split into the creation of the method structure
+ // and the creation of the method code. The code can then be constructed outside the lock.
+ methodDefinition = builder.build();
+ methodCollection.addMethod(methodDefinition);
+ return methodDefinition;
}
}
@@ -625,7 +689,7 @@
SyntheticNaming.createInternalType(
kind, outerContext, syntheticIdSupplier.get(), appView.dexItemFactory());
SyntheticProgramClassBuilder classBuilder =
- new SyntheticProgramClassBuilder(type, outerContext, appView.dexItemFactory());
+ new SyntheticProgramClassBuilder(type, kind, outerContext, appView.dexItemFactory());
DexProgramClass clazz =
classBuilder
.addMethod(fn.andThen(m -> m.setName(SyntheticNaming.INTERNAL_SYNTHETIC_METHOD_PREFIX)))
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
index ffaad01..a06fdb7 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
@@ -128,7 +128,7 @@
return NO_MARKER;
}
for (DexEncodedMethod method : clazz.methods()) {
- if (!SyntheticMethodBuilder.isValidSyntheticMethod(method)) {
+ if (!SyntheticMethodBuilder.isValidSingleSyntheticMethod(method)) {
return NO_MARKER;
}
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
index 67bd52c..e2cd6eb 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import java.util.function.Consumer;
public class SyntheticMethodBuilder {
@@ -25,6 +26,7 @@
private final DexItemFactory factory;
private final DexType holderType;
+ private final SyntheticKind syntheticKind;
private DexString name = null;
private DexProto proto = null;
private CfVersion classFileVersion;
@@ -38,11 +40,13 @@
SyntheticMethodBuilder(SyntheticClassBuilder<?, ?> parent) {
this.factory = parent.getFactory();
this.holderType = parent.getType();
+ this.syntheticKind = parent.getSyntheticKind();
}
- SyntheticMethodBuilder(DexItemFactory factory, DexType holderType) {
+ SyntheticMethodBuilder(DexItemFactory factory, DexType holderType, SyntheticKind syntheticKind) {
this.factory = factory;
this.holderType = holderType;
+ this.syntheticKind = syntheticKind;
}
public SyntheticMethodBuilder setName(String name) {
@@ -101,20 +105,18 @@
assert name != null;
boolean isCompilerSynthesized = true;
DexMethod methodSignature = getMethodSignature();
+ MethodAccessFlags accessFlags = getAccessFlags();
DexEncodedMethod method =
new DexEncodedMethod(
methodSignature,
- getAccessFlags(),
+ accessFlags,
genericSignature,
annotations,
parameterAnnotationsList,
- getCodeObject(methodSignature),
+ accessFlags.isAbstract() ? null : getCodeObject(methodSignature),
isCompilerSynthesized,
classFileVersion);
- // Companion class method may have different properties.
- assert isValidSyntheticMethod(method)
- || SyntheticNaming.isSynthetic(
- holderType.asClassReference(), null, SyntheticNaming.SyntheticKind.COMPANION_CLASS);
+ assert isValidSyntheticMethod(method, syntheticKind);
if (onBuildConsumer != null) {
onBuildConsumer.accept(method);
}
@@ -126,8 +128,16 @@
*
* <p>This method is used when identifying synthetic methods in the program input and should be as
* narrow as possible.
+ *
+ * <p>Methods in fixed suffix synthetics are identified differently (through the class name) and
+ * can have different properties.
*/
- public static boolean isValidSyntheticMethod(DexEncodedMethod method) {
+ public static boolean isValidSyntheticMethod(
+ DexEncodedMethod method, SyntheticKind syntheticKind) {
+ return isValidSingleSyntheticMethod(method) || syntheticKind.isFixedSuffixSynthetic;
+ }
+
+ public static boolean isValidSingleSyntheticMethod(DexEncodedMethod method) {
return method.isStatic()
&& method.isNonAbstractNonNativeMethod()
&& method.isPublic()
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
index 87393f7..a9d11c7 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
@@ -70,7 +70,7 @@
@Override
public boolean isValid() {
- return SyntheticMethodBuilder.isValidSyntheticMethod(method.getDefinition());
+ return SyntheticMethodBuilder.isValidSyntheticMethod(method.getDefinition(), getKind());
}
@Override
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 6ed6c0e..33530e6 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -26,6 +26,8 @@
RECORD_TAG("", 1, false, true, true),
COMPANION_CLASS("$-CC", 2, false, true),
EMULATED_INTERFACE_CLASS("$-EL", 3, false, true),
+ RETARGET_CLASS("RetargetClass", 20, false, true),
+ RETARGET_INTERFACE("RetargetInterface", 21, false, true),
LAMBDA("Lambda", 4, false),
INIT_TYPE_ARGUMENT("-IA", 5, false, true),
HORIZONTAL_INIT_TYPE_ARGUMENT_1(SYNTHETIC_CLASS_SEPARATOR + "IA$1", 6, false, true),
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassBuilder.java
index 6408621..8c07884 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassBuilder.java
@@ -7,12 +7,17 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
public class SyntheticProgramClassBuilder
extends SyntheticClassBuilder<SyntheticProgramClassBuilder, DexProgramClass> {
- SyntheticProgramClassBuilder(DexType type, SynthesizingContext context, DexItemFactory factory) {
- super(type, context, factory);
+ SyntheticProgramClassBuilder(
+ DexType type,
+ SyntheticKind syntheticKind,
+ SynthesizingContext context,
+ DexItemFactory factory) {
+ super(type, syntheticKind, context, factory);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
index aae7abe..4fc6914 100644
--- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -87,6 +87,10 @@
return true;
}
+ public static <T> T last(T[] array) {
+ return array[array.length - 1];
+ }
+
/**
* Rewrites the input array based on the given function.
*
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 e08fe12..f96e8ce 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -51,8 +51,10 @@
import com.android.tools.r8.naming.MapVersion;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
+import com.android.tools.r8.references.FieldReference;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
import com.android.tools.r8.repackaging.Repackaging.DefaultRepackagingConfiguration;
import com.android.tools.r8.repackaging.Repackaging.RepackagingConfiguration;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -315,7 +317,6 @@
public boolean enableInitializedClassesInInstanceMethodsAnalysis = true;
public boolean enableRedundantFieldLoadElimination = true;
public boolean enableValuePropagation = true;
- public boolean enableValuePropagationForInstanceFields = true;
public boolean enableUninstantiatedTypeOptimization = true;
// Currently disabled, see b/146957343.
public boolean enableUninstantiatedTypeOptimizationForInterfaces = false;
@@ -1299,8 +1300,10 @@
public static class ApiModelTestingOptions {
- // A mapping from methods to the api-level introducing them.
+ // A mapping from references to the api-level introducing them.
public Map<MethodReference, AndroidApiLevel> methodApiMapping = new HashMap<>();
+ public Map<FieldReference, AndroidApiLevel> fieldApiMapping = new HashMap<>();
+ public Map<TypeReference, AndroidApiLevel> typeApiMapping = new HashMap<>();
public boolean enableApiCallerIdentification = false;
}
diff --git a/src/main/java/com/android/tools/r8/utils/PrintR8Version.java b/src/main/java/com/android/tools/r8/utils/PrintR8Version.java
new file mode 100644
index 0000000..c20d74c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/PrintR8Version.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2021, 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;
+
+import com.android.tools.r8.Version;
+
+public class PrintR8Version {
+
+ public static void main(String[] args) {
+ System.out.println(Version.getVersionString());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToManyRepresentativeMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToManyRepresentativeMap.java
index 773c066..37b9d40 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToManyRepresentativeMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToManyRepresentativeMap.java
@@ -14,6 +14,8 @@
public interface BidirectionalManyToManyRepresentativeMap<K, V>
extends BidirectionalManyToManyMap<K, V> {
+ boolean hasExplicitRepresentativeKey(V value);
+
K getRepresentativeKey(V value);
default K getRepresentativeKeyOrDefault(V value, K defaultValue) {
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneHashMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneHashMap.java
index 1e53a14..10f520f5 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneHashMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneHashMap.java
@@ -6,6 +6,7 @@
import java.util.Collections;
import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
@@ -21,6 +22,10 @@
return new BidirectionalManyToOneHashMap<>(new IdentityHashMap<>(), new IdentityHashMap<>());
}
+ public static <K, V> BidirectionalManyToOneHashMap<K, V> newLinkedHashMap() {
+ return new BidirectionalManyToOneHashMap<>(new LinkedHashMap<>(), new LinkedHashMap<>());
+ }
+
protected BidirectionalManyToOneHashMap(Map<K, V> backing, Map<V, Set<K>> inverse) {
this.backing = backing;
this.inverse = inverse;
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneRepresentativeHashMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneRepresentativeHashMap.java
index ffd653e..bdd70c8 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneRepresentativeHashMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneRepresentativeHashMap.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.utils.collections;
+import com.android.tools.r8.utils.TriConsumer;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Map;
@@ -33,6 +34,12 @@
}
@Override
+ public void forEachManyToOneMapping(TriConsumer<? super Set<K>, V, K> consumer) {
+ forEachManyToOneMapping(
+ (keys, value) -> consumer.accept(keys, value, getRepresentativeKey(value)));
+ }
+
+ @Override
public K removeRepresentativeFor(V value) {
return representatives.remove(value);
}
@@ -43,10 +50,19 @@
}
@Override
+ public boolean hasExplicitRepresentativeKey(V value) {
+ return representatives.containsKey(value);
+ }
+
+ @Override
public K getRepresentativeKey(V value) {
Set<K> keys = getKeys(value);
if (!keys.isEmpty()) {
- return keys.size() == 1 ? keys.iterator().next() : representatives.get(value);
+ if (keys.size() == 1) {
+ return keys.iterator().next();
+ }
+ assert hasExplicitRepresentativeKey(value);
+ return representatives.get(value);
}
return null;
}
@@ -67,8 +83,10 @@
@Override
public V remove(K key) {
V value = super.remove(key);
- if (getKeys(value).size() <= 1 || getRepresentativeKey(value) == key) {
- removeRepresentativeFor(value);
+ if (hasExplicitRepresentativeKey(value)) {
+ if (getKeys(value).size() <= 1 || getRepresentativeKey(value) == key) {
+ removeRepresentativeFor(value);
+ }
}
return value;
}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneRepresentativeMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneRepresentativeMap.java
index 3e7bf7b..9202cc1 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneRepresentativeMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneRepresentativeMap.java
@@ -4,6 +4,9 @@
package com.android.tools.r8.utils.collections;
+import com.android.tools.r8.utils.TriConsumer;
+import java.util.Set;
+
/**
* Interface that accommodates many-to-one mappings.
*
@@ -11,4 +14,7 @@
* from {@link BidirectionalManyToManyRepresentativeMap}.
*/
public interface BidirectionalManyToOneRepresentativeMap<K, V>
- extends BidirectionalManyToOneMap<K, V>, BidirectionalManyToManyRepresentativeMap<K, V> {}
+ extends BidirectionalManyToOneMap<K, V>, BidirectionalManyToManyRepresentativeMap<K, V> {
+
+ void forEachManyToOneMapping(TriConsumer<? super Set<K>, V, K> consumer);
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToManyRepresentativeHashMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToManyRepresentativeHashMap.java
index 93562e7..a4974d8 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToManyRepresentativeHashMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToManyRepresentativeHashMap.java
@@ -22,6 +22,12 @@
}
@Override
+ public boolean hasExplicitRepresentativeKey(V value) {
+ assert containsValue(value);
+ return true;
+ }
+
+ @Override
public K getRepresentativeKey(V value) {
return getKey(value);
}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneHashMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneHashMap.java
index e23a565..2961111 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneHashMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneHashMap.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.utils.collections;
+import com.android.tools.r8.utils.TriConsumer;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import java.util.Collections;
@@ -61,6 +62,11 @@
}
@Override
+ public void forEachManyToOneMapping(TriConsumer<? super Set<K>, V, K> consumer) {
+ backing.forEach((key, value) -> consumer.accept(Collections.singleton(key), value, key));
+ }
+
+ @Override
public void forEachValue(Consumer<? super V> consumer) {
backing.values().forEach(consumer);
}
@@ -92,6 +98,12 @@
}
@Override
+ public boolean hasExplicitRepresentativeKey(V value) {
+ assert containsValue(value);
+ return true;
+ }
+
+ @Override
public K getRepresentativeKey(V value) {
return getKey(value);
}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java
index 797d8b6..4838419 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java
@@ -40,6 +40,10 @@
methods.forEach(this::add);
}
+ public T get(DexMethod method) {
+ return backing.get(method);
+ }
+
public boolean contains(DexEncodedMethod method) {
return backing.containsKey(method.getReference());
}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/EmptyBidirectionalOneToOneMap.java b/src/main/java/com/android/tools/r8/utils/collections/EmptyBidirectionalOneToOneMap.java
index 62fba9a..5f81584 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/EmptyBidirectionalOneToOneMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/EmptyBidirectionalOneToOneMap.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.utils.collections;
+import com.android.tools.r8.utils.TriConsumer;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import java.util.Collections;
@@ -42,6 +43,11 @@
}
@Override
+ public void forEachManyToOneMapping(TriConsumer<? super Set<K>, V, K> consumer) {
+ // Intentionally empty.
+ }
+
+ @Override
public void forEachValue(Consumer<? super V> consumer) {
// Intentionally empty.
}
@@ -67,6 +73,11 @@
}
@Override
+ public boolean hasExplicitRepresentativeKey(V value) {
+ return false;
+ }
+
+ @Override
public K getRepresentativeKey(V value) {
return null;
}
diff --git a/src/test/java/com/android/tools/r8/KotlinTestParameters.java b/src/test/java/com/android/tools/r8/KotlinTestParameters.java
index 8c4d024..57658e1 100644
--- a/src/test/java/com/android/tools/r8/KotlinTestParameters.java
+++ b/src/test/java/com/android/tools/r8/KotlinTestParameters.java
@@ -32,6 +32,14 @@
return targetVersion;
}
+ public boolean is(KotlinCompilerVersion compilerVersion) {
+ return kotlinc.is(compilerVersion);
+ }
+
+ public boolean is(KotlinCompilerVersion compilerVersion, KotlinTargetVersion targetVersion) {
+ return is(compilerVersion) && this.targetVersion == targetVersion;
+ }
+
public boolean isFirst() {
return index == 0;
}
diff --git a/src/test/java/com/android/tools/r8/ResourceShrinkerTest.java b/src/test/java/com/android/tools/r8/ResourceShrinkerTest.java
index 2a0826a..1a1a532 100644
--- a/src/test/java/com/android/tools/r8/ResourceShrinkerTest.java
+++ b/src/test/java/com/android/tools/r8/ResourceShrinkerTest.java
@@ -9,6 +9,8 @@
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.smali.SmaliBuilder;
import com.android.tools.r8.utils.AndroidApp;
import com.google.common.collect.Lists;
@@ -21,6 +23,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
@@ -34,10 +37,12 @@
public TemporaryFolder tmp = new TemporaryFolder();
private static class TrackAll implements ResourceShrinker.ReferenceChecker {
- Set<Integer> integers = Sets.newHashSet();
- Set<String> strings = Sets.newHashSet();
- List<List<String>> fields = Lists.newArrayList();
- List<List<String>> methods = Lists.newArrayList();
+ Set<Integer> refIntegers = Sets.newHashSet();
+ Set<String> refStrings = Sets.newHashSet();
+ List<List<String>> refFields = Lists.newArrayList();
+ List<List<String>> refMethods = Lists.newArrayList();
+ List<MethodReference> methodsVisited = Lists.newArrayList();
+ List<ClassReference> classesVisited = Lists.newArrayList();
@Override
public boolean shouldProcess(String internalName) {
@@ -46,17 +51,17 @@
@Override
public void referencedInt(int value) {
- integers.add(value);
+ refIntegers.add(value);
}
@Override
public void referencedString(String value) {
- strings.add(value);
+ refStrings.add(value);
}
@Override
public void referencedStaticField(String internalName, String fieldName) {
- fields.add(Lists.newArrayList(internalName, fieldName));
+ refFields.add(Lists.newArrayList(internalName, fieldName));
}
@Override
@@ -65,7 +70,27 @@
&& Objects.equals(methodName, "<init>")) {
return;
}
- methods.add(Lists.newArrayList(internalName, methodName, methodDescriptor));
+ refMethods.add(Lists.newArrayList(internalName, methodName, methodDescriptor));
+ }
+
+ @Override
+ public void startMethodVisit(MethodReference methodReference) {
+ methodsVisited.add(methodReference);
+ }
+
+ @Override
+ public void endMethodVisit(MethodReference methodReference) {
+ assertEquals(methodsVisited.get(methodsVisited.size() - 1), methodReference);
+ }
+
+ @Override
+ public void startClassVisit(ClassReference classReference) {
+ classesVisited.add(classReference);
+ }
+
+ @Override
+ public void endClassVisit(ClassReference classReference) {
+ assertEquals(classesVisited.get(classesVisited.size() - 1), classReference);
}
}
@@ -76,10 +101,10 @@
public void testEmptyClass() throws CompilationFailedException, IOException, ExecutionException {
TrackAll analysis = runAnalysis(EmptyClass.class);
- assertThat(analysis.integers, is(Sets.newHashSet()));
- assertThat(analysis.strings, is(Sets.newHashSet()));
- assertThat(analysis.fields, is(Lists.newArrayList()));
- assertThat(analysis.methods, is(Lists.newArrayList()));
+ assertThat(analysis.refIntegers, is(Sets.newHashSet()));
+ assertThat(analysis.refStrings, is(Sets.newHashSet()));
+ assertThat(analysis.refFields, is(Lists.newArrayList()));
+ assertThat(analysis.refMethods, is(Lists.newArrayList()));
}
private static class ConstInCode {
@@ -97,20 +122,21 @@
throws CompilationFailedException, IOException, ExecutionException {
TrackAll analysis = runAnalysis(ConstInCode.class);
- assertThat(analysis.integers, is(Sets.newHashSet(10, 11)));
- assertThat(analysis.strings, is(Sets.newHashSet("my_layout", "another_layout")));
+ assertThat(analysis.refIntegers, is(Sets.newHashSet(10, 11)));
+ assertThat(analysis.refStrings, is(Sets.newHashSet("my_layout", "another_layout")));
- assertEquals(3, analysis.fields.size());
- assertThat(analysis.fields.get(0), is(Lists.newArrayList("java/lang/System", "out")));
- assertThat(analysis.fields.get(1), is(Lists.newArrayList("java/lang/System", "out")));
- assertThat(analysis.fields.get(2), is(Lists.newArrayList("java/lang/System", "out")));
+ assertEquals(3, analysis.refFields.size());
+ assertThat(analysis.refFields.get(0), is(Lists.newArrayList("java/lang/System", "out")));
+ assertThat(analysis.refFields.get(1), is(Lists.newArrayList("java/lang/System", "out")));
+ assertThat(analysis.refFields.get(2), is(Lists.newArrayList("java/lang/System", "out")));
- assertEquals(3, analysis.methods.size());
- assertThat(analysis.methods.get(0),
- is(Lists.newArrayList("java/io/PrintStream", "print", "(I)V")));
- assertThat(analysis.methods.get(1),
- is(Lists.newArrayList("java/io/PrintStream", "print", "(I)V")));
- assertThat(analysis.methods.get(2),
+ assertEquals(3, analysis.refMethods.size());
+ assertThat(
+ analysis.refMethods.get(0), is(Lists.newArrayList("java/io/PrintStream", "print", "(I)V")));
+ assertThat(
+ analysis.refMethods.get(1), is(Lists.newArrayList("java/io/PrintStream", "print", "(I)V")));
+ assertThat(
+ analysis.refMethods.get(2),
is(Lists.newArrayList("java/io/PrintStream", "print", "(Ljava/lang/String;)V")));
}
@@ -127,10 +153,43 @@
throws CompilationFailedException, IOException, ExecutionException {
TrackAll analysis = runAnalysis(StaticFields.class);
- assertThat(analysis.integers, hasItems(10, 11, 12, 13));
- assertThat(analysis.strings, hasItems("staticValue", "a", "b", "c"));
- assertThat(analysis.fields, is(Lists.newArrayList()));
- assertThat(analysis.methods, is(Lists.newArrayList()));
+ assertThat(analysis.refIntegers, hasItems(10, 11, 12, 13));
+ assertThat(analysis.refStrings, hasItems("staticValue", "a", "b", "c"));
+ assertThat(analysis.refFields, is(Lists.newArrayList()));
+ assertThat(analysis.refMethods, is(Lists.newArrayList()));
+ }
+
+ @SuppressWarnings("unused")
+ private static class ClassesAndMethodsVisited {
+ final int value = getValue();
+
+ static final int staticValue;
+
+ static {
+ staticValue = 42;
+ }
+
+ int getValue() {
+ return true ? 0 : 1;
+ }
+ }
+
+ @Test
+ public void testNumberOfMethodsAndClassesVisited()
+ throws CompilationFailedException, IOException, ExecutionException {
+ TrackAll analysis = runAnalysis(ClassesAndMethodsVisited.class);
+
+ List<String> methodNames =
+ analysis.methodsVisited.stream()
+ .map(MethodReference::getMethodName)
+ .collect(Collectors.toList());
+ List<String> classNames =
+ analysis.classesVisited.stream()
+ .map(ClassReference::getBinaryName)
+ .collect(Collectors.toList());
+ assertThat(methodNames, hasItems("<init>", "<clinit>", "getValue"));
+ assertThat(
+ classNames, hasItems("com/android/tools/r8/ResourceShrinkerTest$ClassesAndMethodsVisited"));
}
@Retention(RetentionPolicy.RUNTIME)
@@ -165,10 +224,10 @@
public void testAnnotations() throws CompilationFailedException, IOException, ExecutionException {
TrackAll analysis = runAnalysis(IntAnnotation.class, OuterAnnotation.class, Annotated.class);
- assertThat(analysis.integers, hasItems(10, 11, 12, 13, 14, 15, 42));
- assertThat(analysis.strings, is(Sets.newHashSet()));
- assertThat(analysis.fields, is(Lists.newArrayList()));
- assertThat(analysis.methods, is(Lists.newArrayList()));
+ assertThat(analysis.refIntegers, hasItems(10, 11, 12, 13, 14, 15, 42));
+ assertThat(analysis.refStrings, is(Sets.newHashSet()));
+ assertThat(analysis.refFields, is(Lists.newArrayList()));
+ assertThat(analysis.refMethods, is(Lists.newArrayList()));
}
private static class ResourceClassToSkip {
@@ -185,10 +244,10 @@
throws ExecutionException, CompilationFailedException, IOException {
TrackAll analysis = runAnalysis(ResourceClassToSkip.class, ToProcess.class);
- assertThat(analysis.integers, hasItems(10, 11, 12));
- assertThat(analysis.strings, is(Sets.newHashSet("10", "11", "12")));
- assertThat(analysis.fields, is(Lists.newArrayList()));
- assertThat(analysis.methods, is(Lists.newArrayList()));
+ assertThat(analysis.refIntegers, hasItems(10, 11, 12));
+ assertThat(analysis.refStrings, is(Sets.newHashSet("10", "11", "12")));
+ assertThat(analysis.refFields, is(Lists.newArrayList()));
+ assertThat(analysis.refMethods, is(Lists.newArrayList()));
}
@Test
@@ -213,10 +272,10 @@
AndroidApp.builder().addDexProgramData(builder.compile(), Origin.unknown()).build();
TrackAll analysis = runOnApp(app);
- assertThat(analysis.integers, hasItems(4, 5, 6));
- assertThat(analysis.strings, is(Sets.newHashSet()));
- assertThat(analysis.fields, is(Lists.newArrayList()));
- assertThat(analysis.methods, is(Lists.newArrayList()));
+ assertThat(analysis.refIntegers, hasItems(4, 5, 6));
+ assertThat(analysis.refStrings, is(Sets.newHashSet()));
+ assertThat(analysis.refFields, is(Lists.newArrayList()));
+ assertThat(analysis.refMethods, is(Lists.newArrayList()));
}
private TrackAll runAnalysis(Class<?>... classes)
diff --git a/src/test/java/com/android/tools/r8/TestRuntime.java b/src/test/java/com/android/tools/r8/TestRuntime.java
index 5aef2ad..8b99ca8 100644
--- a/src/test/java/com/android/tools/r8/TestRuntime.java
+++ b/src/test/java/com/android/tools/r8/TestRuntime.java
@@ -12,10 +12,12 @@
import com.android.tools.r8.utils.ListUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
+import com.google.common.collect.ImmutableMap;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
// Base class for the runtime structure in the test parameters.
@@ -69,6 +71,19 @@
Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "openjdk-9.0.4");
private static final Path JDK11_PATH = Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "jdk-11");
private static final Path JDK15_PATH = Paths.get(ToolHelper.THIRD_PARTY_DIR, "openjdk", "jdk-15");
+ private static final Map<CfVm, Path> jdkPaths =
+ ImmutableMap.of(
+ CfVm.JDK8, JDK8_PATH,
+ CfVm.JDK9, JDK9_PATH,
+ CfVm.JDK11, JDK11_PATH,
+ CfVm.JDK15, JDK15_PATH);
+
+ public static CfRuntime getCheckedInJdk(CfVm vm) {
+ if (vm == CfVm.JDK8) {
+ return getCheckedInJdk8();
+ }
+ return new CfRuntime(vm, getCheckedInJdkHome(vm));
+ }
public static CfRuntime getCheckedInJdk8() {
Path home;
@@ -83,7 +98,9 @@
return new CfRuntime(CfVm.JDK8, home);
}
- private static Path getCheckedInJdkHome(Path path, CfVm vm) {
+ private static Path getCheckedInJdkHome(CfVm vm) {
+ Path path = jdkPaths.get(vm);
+ assert path != null : "No JDK path defined for " + vm;
if (ToolHelper.isLinux()) {
return path.resolve("linux");
} else if (ToolHelper.isMac()) {
@@ -97,16 +114,16 @@
}
public static CfRuntime getCheckedInJdk9() {
- return new CfRuntime(CfVm.JDK9, getCheckedInJdkHome(JDK9_PATH, CfVm.JDK9));
+ return new CfRuntime(CfVm.JDK9, getCheckedInJdkHome(CfVm.JDK9));
}
public static CfRuntime getCheckedInJdk11() {
- return new CfRuntime(CfVm.JDK11, getCheckedInJdkHome(JDK11_PATH, CfVm.JDK11));
+ return new CfRuntime(CfVm.JDK11, getCheckedInJdkHome(CfVm.JDK11));
}
// TODO(b/169692487): Add this to 'getCheckedInCfRuntimes' when we start having support for JDK15.
public static CfRuntime getCheckedInJdk15() {
- return new CfRuntime(CfVm.JDK15, getCheckedInJdkHome(JDK15_PATH, CfVm.JDK15));
+ return new CfRuntime(CfVm.JDK15, getCheckedInJdkHome(CfVm.JDK15));
}
public static List<CfRuntime> getCheckedInCfRuntimes() {
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 4c765be..50f5346 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -7,6 +7,7 @@
import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_4_20;
import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0_M2;
import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
+import static com.android.tools.r8.utils.FileUtils.JAVA_EXTENSION;
import static com.android.tools.r8.utils.FileUtils.isDexFile;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -100,6 +101,7 @@
public static final String THIRD_PARTY_DIR = "third_party/";
public static final String TOOLS_DIR = "tools/";
public static final String TESTS_DIR = "src/test/";
+ public static final String TESTS_SOURCE_DIR = "src/test/java";
public static final String EXAMPLES_DIR = TESTS_DIR + "examples/";
public static final String EXAMPLES_ANDROID_N_DIR = TESTS_DIR + "examplesAndroidN/";
public static final String EXAMPLES_ANDROID_O_DIR = TESTS_DIR + "examplesAndroidO/";
@@ -1099,6 +1101,17 @@
.collect(Collectors.toList());
}
+ public static Path getSourceFileForTestClass(Class<?> clazz) {
+ List<String> parts = getNamePartsForTestClass(clazz);
+ String last = parts.get(parts.size() - 1);
+ assert last.endsWith(CLASS_EXTENSION);
+ parts.set(
+ parts.size() - 1,
+ last.substring(0, last.length() - CLASS_EXTENSION.length()) + JAVA_EXTENSION);
+ return Paths.get(TESTS_SOURCE_DIR)
+ .resolve(Paths.get("", parts.toArray(StringUtils.EMPTY_ARRAY)));
+ }
+
public static Path getClassFileForTestClass(Class<?> clazz) {
List<String> parts = getNamePartsForTestClass(clazz);
return getClassPathForTests().resolve(Paths.get("", parts.toArray(StringUtils.EMPTY_ARRAY)));
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelClassMergingWithDifferentApiFieldsTest.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelClassMergingWithDifferentApiFieldsTest.java
new file mode 100644
index 0000000..8045b48
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelClassMergingWithDifferentApiFieldsTest.java
@@ -0,0 +1,100 @@
+// Copyright (c) 2021, 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.apimodeling;
+
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForType;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.lang.reflect.Method;
+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 ApiModelClassMergingWithDifferentApiFieldsTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final String[] EXPECTED = new String[] {"Api::foo", "B::bar"};
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ApiModelClassMergingWithDifferentApiFieldsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(A.class, B.class, ApiSetter.class, Main.class)
+ .addLibraryClasses(Api.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ // TODO(b/138781768): Should not be merged
+ .addHorizontallyMergedClassesInspector(
+ inspector -> inspector.assertClassesMerged(A.class, B.class))
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .apply(setMockApiLevelForType(Api.class, AndroidApiLevel.L_MR1))
+ .compile()
+ .addRunClasspathClasses(Api.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ public static class Api {
+
+ public void foo() {
+ System.out.println("Api::foo");
+ }
+ }
+
+ static class ApiSetter {
+ static void set() {
+ A.api = new Api();
+ }
+ }
+
+ static class A {
+
+ private static Api api;
+
+ public static void callApi() throws Exception {
+ // The reflective call here is to ensure that the setting of A's api level is not based on
+ // a method reference to `Api` and only because of the type reference in the field `api`.
+ Class<?> aClass =
+ Class.forName(
+ "com.android.tools.r8.apimodeling.ApiModelClassMergingWithDifferentApiFieldsTest_Api"
+ .replace("_", "$"));
+ Method foo = aClass.getDeclaredMethod("foo");
+ foo.invoke(api);
+ }
+ }
+
+ public static class B {
+
+ @NeverInline
+ public static void bar() {
+ System.out.println("B::bar");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) throws Exception {
+ ApiSetter.set();
+ A.callApi();
+ B.bar();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelClassMergingWithDifferentApiMethodsTest.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelClassMergingWithDifferentApiMethodsTest.java
new file mode 100644
index 0000000..b56d6ae
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelClassMergingWithDifferentApiMethodsTest.java
@@ -0,0 +1,86 @@
+// Copyright (c) 2021, 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.apimodeling;
+
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForMethod;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.lang.reflect.Method;
+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 ApiModelClassMergingWithDifferentApiMethodsTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final String[] EXPECTED = new String[] {"Api::apiLevel22", "B::bar"};
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ApiModelClassMergingWithDifferentApiMethodsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ Method apiMethod = Api.class.getDeclaredMethod("apiLevel22");
+ testForR8(parameters.getBackend())
+ .addProgramClasses(A.class, B.class, Main.class)
+ .addLibraryClasses(Api.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ // TODO(b/138781768): Should not be merged
+ .addHorizontallyMergedClassesInspector(
+ inspector -> inspector.assertClassesMerged(A.class, B.class))
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .apply(setMockApiLevelForMethod(apiMethod, AndroidApiLevel.L_MR1))
+ .compile()
+ .addRunClasspathClasses(Api.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ public static class Api {
+
+ public static void apiLevel22() {
+ System.out.println("Api::apiLevel22");
+ }
+ }
+
+ public static class A {
+
+ @NeverInline
+ public static void callFoo() {
+ Api.apiLevel22();
+ }
+ }
+
+ public static class B {
+
+ @NeverInline
+ public static void bar() {
+ System.out.println("B::bar");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ A.callFoo();
+ B.bar();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelInlineMethodWithApiTypeTest.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelInlineMethodWithApiTypeTest.java
new file mode 100644
index 0000000..2e24790
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelInlineMethodWithApiTypeTest.java
@@ -0,0 +1,98 @@
+// Copyright (c) 2021, 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.apimodeling;
+
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForType;
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+
+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.AndroidApiLevel;
+import java.lang.reflect.Method;
+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 ApiModelInlineMethodWithApiTypeTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ApiModelInlineMethodWithApiTypeTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ Method apiCallerApiLevel1 = ApiCaller.class.getDeclaredMethod("apiLevel22");
+ Method apiCallerCallerApiLevel1 = ApiCallerCaller.class.getDeclaredMethod("apiLevel22");
+ Method otherCallerApiLevel1 = OtherCaller.class.getDeclaredMethod("apiLevel1");
+ testForR8(parameters.getBackend())
+ .addProgramClasses(ApiCaller.class, ApiCallerCaller.class, OtherCaller.class, Main.class)
+ .addLibraryClasses(ApiType.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .enableNoHorizontalClassMergingAnnotations()
+ .apply(setMockApiLevelForType(ApiType.class, AndroidApiLevel.L_MR1))
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .compile()
+ .addRunClasspathClasses(ApiType.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(ApiType.class.getName())
+ .inspect(verifyThat(parameters, apiCallerApiLevel1).inlinedInto(apiCallerCallerApiLevel1))
+ // TODO(b/138781768): Should not be inlined
+ .inspect(
+ verifyThat(parameters, apiCallerCallerApiLevel1).inlinedInto(otherCallerApiLevel1));
+ }
+
+ public static class ApiType {}
+
+ @NoHorizontalClassMerging
+ public static class ApiCaller {
+ public static ApiType apiLevel22() throws Exception {
+ // The reflective call here is to ensure that the setting of A's api level is not based on
+ // a method reference to `Api` and only because of the type reference in the field `api`.
+ Class<?> reflectiveCall =
+ Class.forName(
+ "com.android.tools.r8.apimodeling.ApiModelInlineMethodWithApiTypeTest_ApiType"
+ .replace("_", "$"));
+ return (ApiType) reflectiveCall.getDeclaredConstructor().newInstance();
+ }
+ }
+
+ @NoHorizontalClassMerging
+ public static class ApiCallerCaller {
+
+ public static void apiLevel22() throws Exception {
+ // This is referencing the proto of ApiCaller.foo and thus have a reference to ApiType. It is
+ // therefore OK to inline ApiCaller.foo() into ApiCallerCaller.bar().
+ System.out.println(ApiCaller.apiLevel22().getClass().getName());
+ }
+ }
+
+ public static class OtherCaller {
+
+ public static void apiLevel1() throws Exception {
+ // ApiCallerCaller.apiLevel22 should never be inlined here.
+ ApiCallerCaller.apiLevel22();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) throws Exception {
+ OtherCaller.apiLevel1();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoClassInliningFieldTest.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoClassInliningFieldTest.java
new file mode 100644
index 0000000..3bd5b8a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoClassInliningFieldTest.java
@@ -0,0 +1,100 @@
+// Copyright (c) 2021, 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.apimodeling;
+
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForField;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+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.AndroidApiLevel;
+import java.lang.reflect.Field;
+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 ApiModelNoClassInliningFieldTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ApiModelNoClassInliningFieldTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ Field apiField = Api.class.getDeclaredField("foo");
+ testForR8(parameters.getBackend())
+ .addProgramClasses(ApiCaller.class, ApiCallerCaller.class, ApiBuilder.class, Main.class)
+ .addLibraryClasses(Api.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .apply(setMockApiLevelForField(apiField, AndroidApiLevel.L_MR1))
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .compile()
+ .inspect(
+ inspector -> {
+ // TODO(b/138781768): Should not be class inlined.
+ assertThat(inspector.clazz(ApiCaller.class), not(isPresent()));
+ })
+ .addRunClasspathClasses(Api.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Api::apiLevel22");
+ }
+
+ public static class Api {
+
+ public String foo = "Api::apiLevel22";
+ }
+
+ public static class ApiBuilder {
+
+ public static Api build() {
+ return new Api();
+ }
+ }
+
+ @NoHorizontalClassMerging
+ public static class ApiCaller {
+
+ private Api api;
+
+ public ApiCaller(Api api) {
+ this.api = api;
+ System.out.println(api.foo);
+ }
+ }
+
+ @NoHorizontalClassMerging
+ public static class ApiCallerCaller {
+
+ @NeverInline
+ public static void callCallApi() {
+ new ApiCaller(ApiBuilder.build());
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ ApiCallerCaller.callCallApi();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoClassInliningMethodTest.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoClassInliningMethodTest.java
new file mode 100644
index 0000000..a7804e0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoClassInliningMethodTest.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2021, 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.apimodeling;
+
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+
+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.AndroidApiLevel;
+import java.lang.reflect.Method;
+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 ApiModelNoClassInliningMethodTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ApiModelNoClassInliningMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ Method apiMethod = Api.class.getDeclaredMethod("apiLevel22");
+ Method apiCaller = ApiCaller.class.getDeclaredMethod("callApi");
+ Method apiCallerCaller = ApiCallerCaller.class.getDeclaredMethod("callCallApi");
+ testForR8(parameters.getBackend())
+ .addProgramClasses(ApiCaller.class, ApiCallerCaller.class, Main.class)
+ .addLibraryClasses(Api.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .apply(setMockApiLevelForMethod(apiMethod, AndroidApiLevel.L_MR1))
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .compile()
+ .inspect(
+ verifyThat(parameters, apiCaller)
+ .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.L_MR1))
+ .addRunClasspathClasses(Api.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Api::apiLevel22");
+ }
+
+ public static class Api {
+
+ public static void apiLevel22() {
+ System.out.println("Api::apiLevel22");
+ }
+ }
+
+ @NoHorizontalClassMerging
+ public static class ApiCaller {
+
+ public void callApi() {
+ Api.apiLevel22();
+ }
+ }
+
+ @NoHorizontalClassMerging
+ public static class ApiCallerCaller {
+
+ @NeverInline
+ public static void callCallApi() {
+ new ApiCaller().callApi();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ ApiCallerCaller.callCallApi();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java
new file mode 100644
index 0000000..f210e6f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2021, 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.apimodeling;
+
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForField;
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+
+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.AndroidApiLevel;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+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 ApiModelNoInliningOfHigherApiLevelInstanceFieldTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ApiModelNoInliningOfHigherApiLevelInstanceFieldTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ Field apiField = Api.class.getDeclaredField("apiLevel22");
+ Method apiCaller = ApiCaller.class.getDeclaredMethod("getInstanceField");
+ Method apiCallerCaller = ApiCallerCaller.class.getDeclaredMethod("noApiCall");
+ testForR8(parameters.getBackend())
+ .addProgramClasses(ApiCaller.class, ApiCallerCaller.class, Main.class)
+ .addLibraryClasses(Api.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .apply(setMockApiLevelForField(apiField, AndroidApiLevel.L_MR1))
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .compile()
+ // TODO(b/138781768): Should not be inlined
+ .inspect(verifyThat(parameters, apiCaller).inlinedInto(apiCallerCaller))
+ .addRunClasspathClasses(Api.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello World!");
+ }
+
+ // The api class does not have an api level to ensure it is not the instance initializer that is
+ // keeping us from inlining.
+ public static class Api {
+
+ public String apiLevel22 = "Hello World!";
+ }
+
+ @NoHorizontalClassMerging
+ public static class ApiCaller {
+
+ public static void getInstanceField() {
+ System.out.println(new Api().apiLevel22);
+ }
+ }
+
+ @NoHorizontalClassMerging
+ public static class ApiCallerCaller {
+
+ @NeverInline
+ public static void noApiCall() {
+ ApiCaller.getInstanceField();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ ApiCallerCaller.noApiCall();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelStaticFieldTest.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelStaticFieldTest.java
new file mode 100644
index 0000000..0087275
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelStaticFieldTest.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2021, 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.apimodeling;
+
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForField;
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+
+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.AndroidApiLevel;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+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 ApiModelNoInliningOfHigherApiLevelStaticFieldTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ApiModelNoInliningOfHigherApiLevelStaticFieldTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ Field apiField = Api.class.getDeclaredField("apiLevel22");
+ Method apiCaller = ApiCaller.class.getDeclaredMethod("getStaticField");
+ Method apiCallerCaller = ApiCallerCaller.class.getDeclaredMethod("noApiCall");
+ testForR8(parameters.getBackend())
+ .addProgramClasses(ApiCaller.class, ApiCallerCaller.class, Main.class)
+ .addLibraryClasses(Api.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .apply(setMockApiLevelForField(apiField, AndroidApiLevel.L_MR1))
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .compile()
+ // TODO(b/138781768): Should not be inlined
+ .inspect(verifyThat(parameters, apiCaller).inlinedInto(apiCallerCaller))
+ .addRunClasspathClasses(Api.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello World!");
+ }
+
+ // The api class does not have an api level to ensure it is not the class reference is keeping us
+ // from inlining.
+ public static class Api {
+
+ public static String apiLevel22 = "Hello World!";
+ }
+
+ @NoHorizontalClassMerging
+ public static class ApiCaller {
+
+ public static void getStaticField() {
+ System.out.println(Api.apiLevel22);
+ }
+ }
+
+ @NoHorizontalClassMerging
+ public static class ApiCallerCaller {
+
+ @NeverInline
+ public static void noApiCall() {
+ ApiCaller.getStaticField();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ ApiCallerCaller.noApiCall();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelVerticalMergingOfSuperClassTest.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelVerticalMergingOfSuperClassTest.java
new file mode 100644
index 0000000..a04774b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelVerticalMergingOfSuperClassTest.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2021, 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.apimodeling;
+
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForType;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+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 ApiModelVerticalMergingOfSuperClassTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ApiModelVerticalMergingOfSuperClassTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(A.class, B.class, Main.class)
+ .addLibraryClasses(Api.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .apply(setMockApiLevelForType(Api.class, AndroidApiLevel.L_MR1))
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .compile()
+ .inspect(
+ inspector -> {
+ // TODO(b/138781768): We should not merge A into B.
+ assertThat(inspector.clazz(A.class), not(isPresent()));
+ })
+ .addRunClasspathClasses(Api.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello World!");
+ }
+
+ public static class Api {}
+
+ public static class A extends Api {
+
+ @NeverInline
+ public void bar() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ @NeverClassInline
+ public static class B extends A {
+
+ public void foo() {
+ bar();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new B().foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelingTestHelper.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelingTestHelper.java
index 5b1a2f7..b20e395 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelingTestHelper.java
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelingTestHelper.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.CodeMatchers;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.lang.reflect.Field;
import java.lang.reflect.Method;
public abstract class ApiModelingTestHelper {
@@ -34,6 +35,32 @@
};
}
+ static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>>
+ ThrowableConsumer<T> setMockApiLevelForField(Field field, AndroidApiLevel apiLevel) {
+ return compilerBuilder -> {
+ compilerBuilder.addOptionsModification(
+ options -> {
+ options
+ .apiModelingOptions()
+ .fieldApiMapping
+ .put(Reference.fieldFromField(field), apiLevel);
+ });
+ };
+ }
+
+ static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>> ThrowableConsumer<T> setMockApiLevelForType(
+ Class<?> clazz, AndroidApiLevel apiLevel) {
+ return compilerBuilder -> {
+ compilerBuilder.addOptionsModification(
+ options -> {
+ options
+ .apiModelingOptions()
+ .typeApiMapping
+ .put(Reference.classFromClass(clazz), apiLevel);
+ });
+ };
+ }
+
static void enableApiCallerIdentification(TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) {
compilerBuilder.addOptionsModification(
options -> {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAfterUnusedArgumentRemovalMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAfterUnusedArgumentRemovalMergingTest.java
new file mode 100644
index 0000000..57108ff
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAfterUnusedArgumentRemovalMergingTest.java
@@ -0,0 +1,119 @@
+// Copyright (c) 2021, 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 org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+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 com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+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 EquivalentConstructorsWithClassIdAfterUnusedArgumentRemovalMergingTest
+ extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection params() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public EquivalentConstructorsWithClassIdAfterUnusedArgumentRemovalMergingTest(
+ TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
+ .enableNeverClassInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertEquals(
+ 1, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("C", "D");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(new A(new Object(), new C()));
+ System.out.println(new B(new Object(), new D()));
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ private final C c;
+
+ A(Object unused, C c) {
+ this.c = c;
+ }
+
+ @Override
+ public String toString() {
+ return c.toString();
+ }
+ }
+
+ @NeverClassInline
+ static class B {
+
+ private final D d;
+
+ B(Object unused, D d) {
+ this.d = d;
+ }
+
+ @Override
+ public String toString() {
+ return d.toString();
+ }
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ static class C {
+
+ @Override
+ public String toString() {
+ return "C";
+ }
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ static class D {
+
+ @Override
+ public String toString() {
+ return "D";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentArgumentAndAssignmentOrderMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentArgumentAndAssignmentOrderMergingTest.java
new file mode 100644
index 0000000..e70cd5a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentArgumentAndAssignmentOrderMergingTest.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2021, 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 org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+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 com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+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 EquivalentConstructorsWithClassIdAndDifferentArgumentAndAssignmentOrderMergingTest
+ extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection params() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public EquivalentConstructorsWithClassIdAndDifferentArgumentAndAssignmentOrderMergingTest(
+ TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
+ .enableNeverClassInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ // TODO(b/189296638): Enable constructor merging by changing the constructor
+ // arguments.
+ assertEquals(
+ 2, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("CD", "CD");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(new A(new C(), new D()));
+ System.out.println(new B(new D(), new C()));
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ private final C c;
+ private final D d;
+
+ A(C c, D d) {
+ this.c = c;
+ this.d = d;
+ }
+
+ @Override
+ public String toString() {
+ return c.toString() + d.toString();
+ }
+ }
+
+ @NeverClassInline
+ static class B {
+
+ private final C c;
+ private final D d;
+
+ B(D d, C c) {
+ this.d = d;
+ this.c = c;
+ }
+
+ @Override
+ public String toString() {
+ return c.toString() + d.toString();
+ }
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ static class C {
+
+ @Override
+ public String toString() {
+ return "C";
+ }
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ static class D {
+
+ @Override
+ public String toString() {
+ return "D";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentArgumentOrderMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentArgumentOrderMergingTest.java
new file mode 100644
index 0000000..5297a09
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentArgumentOrderMergingTest.java
@@ -0,0 +1,126 @@
+// Copyright (c) 2021, 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 org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+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 com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+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 EquivalentConstructorsWithClassIdAndDifferentArgumentOrderMergingTest
+ extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection params() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public EquivalentConstructorsWithClassIdAndDifferentArgumentOrderMergingTest(
+ TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
+ .enableNeverClassInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+
+ // TODO(b/189296638): Enable constructor merging by changing the constructor
+ // arguments.
+ assertEquals(
+ 2, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("C0", "D1");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(new A(0, new C()));
+ System.out.println(new B(new D(), 1));
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ private final C c;
+ private final int i;
+
+ A(int i, C c) {
+ this.c = c;
+ this.i = i;
+ }
+
+ @Override
+ public String toString() {
+ return c.toString() + i;
+ }
+ }
+
+ @NeverClassInline
+ static class B {
+
+ private final D d;
+ private final int i;
+
+ B(D d, int i) {
+ this.d = d;
+ this.i = i;
+ }
+
+ @Override
+ public String toString() {
+ return d.toString() + i;
+ }
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ static class C {
+
+ @Override
+ public String toString() {
+ return "C";
+ }
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ static class D {
+
+ @Override
+ public String toString() {
+ return "D";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentAssignmentOrderMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentAssignmentOrderMergingTest.java
new file mode 100644
index 0000000..7a7a997
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentAssignmentOrderMergingTest.java
@@ -0,0 +1,124 @@
+// Copyright (c) 2021, 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 org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+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 com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+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 EquivalentConstructorsWithClassIdAndDifferentAssignmentOrderMergingTest
+ extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection params() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public EquivalentConstructorsWithClassIdAndDifferentAssignmentOrderMergingTest(
+ TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
+ .enableNeverClassInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertEquals(
+ 1, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("CC", "DD");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(new A(new C(), new C()));
+ System.out.println(new B(new D(), new D()));
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ private final C c;
+ private final C c2;
+
+ A(C c, C c2) {
+ this.c = c;
+ this.c2 = c2;
+ }
+
+ @Override
+ public String toString() {
+ return c.toString() + c2.toString();
+ }
+ }
+
+ @NeverClassInline
+ static class B {
+
+ private final D d;
+ private final D d2;
+
+ B(D d, D d2) {
+ // d2 intentionally assigned before d.
+ this.d2 = d2;
+ this.d = d;
+ }
+
+ @Override
+ public String toString() {
+ return d.toString() + d2.toString();
+ }
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ static class C {
+
+ @Override
+ public String toString() {
+ return "C";
+ }
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ static class D {
+
+ @Override
+ public String toString() {
+ return "D";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndExtraNullsMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndExtraNullsMergingTest.java
new file mode 100644
index 0000000..3917d56
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndExtraNullsMergingTest.java
@@ -0,0 +1,144 @@
+// Copyright (c) 2021, 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 org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+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.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+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 EquivalentConstructorsWithClassIdAndExtraNullsMergingTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection params() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public EquivalentConstructorsWithClassIdAndExtraNullsMergingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
+ .enableNeverClassInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertEquals(
+ 2, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+
+ ClassSubject nullArgumentClassSubject =
+ inspector.allClasses().stream()
+ .filter(
+ clazz ->
+ SyntheticItemsTestUtils.isHorizontalInitializerTypeArgument(
+ clazz.getOriginalReference()))
+ .findFirst()
+ .orElseThrow(RuntimeException::new);
+
+ assertThat(
+ aClassSubject.method("void", "<init>", "java.lang.Object", "int"), isPresent());
+ assertThat(
+ aClassSubject.method(
+ "void",
+ "<init>",
+ "java.lang.Object",
+ "int",
+ nullArgumentClassSubject.getFinalName()),
+ isPresent());
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("C", "42", "C", "D");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(new A(new C()));
+ System.out.println(new A(new C(), 42));
+ System.out.println(new B(new D()));
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ private final Object c;
+
+ A(C c) {
+ this.c = c;
+ }
+
+ A(Object c, int i) {
+ this.c = c;
+ System.out.println(i);
+ }
+
+ @Override
+ public String toString() {
+ return c.toString();
+ }
+ }
+
+ @NeverClassInline
+ static class B {
+
+ private final D d;
+
+ B(D d) {
+ this.d = d;
+ }
+
+ @Override
+ public String toString() {
+ return d.toString();
+ }
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ static class C {
+
+ @Override
+ public String toString() {
+ return "C";
+ }
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ static class D {
+
+ @Override
+ public String toString() {
+ return "D";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdMergingTest.java
new file mode 100644
index 0000000..2bbd4e5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdMergingTest.java
@@ -0,0 +1,117 @@
+// Copyright (c) 2021, 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 org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+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 com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+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 EquivalentConstructorsWithClassIdMergingTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection params() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public EquivalentConstructorsWithClassIdMergingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
+ .enableNeverClassInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertEquals(
+ 1, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("C", "D");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(new A(new C()));
+ System.out.println(new B(new D()));
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ private final C c;
+
+ A(C c) {
+ this.c = c;
+ }
+
+ @Override
+ public String toString() {
+ return c.toString();
+ }
+ }
+
+ @NeverClassInline
+ static class B {
+
+ private final D d;
+
+ B(D d) {
+ this.d = d;
+ }
+
+ @Override
+ public String toString() {
+ return d.toString();
+ }
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ static class C {
+
+ @Override
+ public String toString() {
+ return "C";
+ }
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ static class D {
+
+ @Override
+ public String toString() {
+ return "D";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithInterfaceValueToParentTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithInterfaceValueToParentTest.java
new file mode 100644
index 0000000..c45274f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithInterfaceValueToParentTest.java
@@ -0,0 +1,113 @@
+// Copyright (c) 2021, 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.NeverClassInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EquivalentConstructorsWithInterfaceValueToParentTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ public EquivalentConstructorsWithInterfaceValueToParentTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableNeverClassInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .enableNoUnusedInterfaceRemovalAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("K", "L");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ new A(new KImpl());
+ new B(new LImpl());
+ }
+ }
+
+ static class Parent {
+ Parent(J j) {
+ j.m();
+ }
+ }
+
+ @NeverClassInline
+ static class A extends Parent {
+ A(K k) {
+ super(k);
+ }
+ }
+
+ @NeverClassInline
+ static class B extends Parent {
+ B(L l) {
+ super(l);
+ }
+ }
+
+ @NoHorizontalClassMerging
+ static class KImpl implements K {
+ @Override
+ public void m() {
+ System.out.println("K");
+ }
+ }
+
+ @NoHorizontalClassMerging
+ static class LImpl implements L {
+ @Override
+ public void m() {
+ System.out.println("L");
+ }
+ }
+
+ @NoHorizontalClassMerging
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface I {
+ void m();
+ }
+
+ @NoHorizontalClassMerging
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface J extends I {}
+
+ @NoHorizontalClassMerging
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface K extends J {}
+
+ @NoHorizontalClassMerging
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface L extends J {}
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithPrimitiveAndReferencesParametersTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithPrimitiveAndReferencesParametersTest.java
new file mode 100644
index 0000000..48e6535
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithPrimitiveAndReferencesParametersTest.java
@@ -0,0 +1,92 @@
+// Copyright (c) 2021, 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 org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.KeepUnusedArguments;
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.classmerging.horizontal.EquivalentConstructorsWithClassIdAndDifferentAssignmentOrderMergingTest.A;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+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 EquivalentConstructorsWithPrimitiveAndReferencesParametersTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ public EquivalentConstructorsWithPrimitiveAndReferencesParametersTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .enableUnusedArgumentAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertEquals(
+ 2, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A", "B");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ new A(42).m1();
+ new B(new Object()).m2();
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+ @KeepUnusedArguments
+ A(int i) {}
+
+ @NeverInline
+ void m1() {
+ System.out.println("A");
+ }
+ }
+
+ @NeverClassInline
+ static class B {
+ @KeepUnusedArguments
+ B(Object o) {}
+
+ @NeverInline
+ void m2() {
+ System.out.println("B");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithoutClassIdAfterUnusedArgumentRemovalMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithoutClassIdAfterUnusedArgumentRemovalMergingTest.java
new file mode 100644
index 0000000..4a66189
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithoutClassIdAfterUnusedArgumentRemovalMergingTest.java
@@ -0,0 +1,121 @@
+// Copyright (c) 2021, 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 org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+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 com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+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 EquivalentConstructorsWithoutClassIdAfterUnusedArgumentRemovalMergingTest
+ extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection params() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public EquivalentConstructorsWithoutClassIdAfterUnusedArgumentRemovalMergingTest(
+ TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertEquals(
+ 1, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("C", "D");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(new A(new Object(), new C()).foo());
+ System.out.println(new B(new Object(), new D()).bar());
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ private final C c;
+
+ A(Object unused, C c) {
+ this.c = c;
+ }
+
+ @NeverInline
+ public String foo() {
+ return c.toString();
+ }
+ }
+
+ @NeverClassInline
+ static class B {
+
+ private final D d;
+
+ B(Object unused, D d) {
+ this.d = d;
+ }
+
+ @NeverInline
+ public String bar() {
+ return d.toString();
+ }
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ static class C {
+
+ @Override
+ public String toString() {
+ return "C";
+ }
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ static class D {
+
+ @Override
+ public String toString() {
+ return "D";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithoutClassIdMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithoutClassIdMergingTest.java
new file mode 100644
index 0000000..090699b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithoutClassIdMergingTest.java
@@ -0,0 +1,119 @@
+// Copyright (c) 2021, 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 org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+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 com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+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 EquivalentConstructorsWithoutClassIdMergingTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection params() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public EquivalentConstructorsWithoutClassIdMergingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertEquals(
+ 1, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("C", "D");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(new A(new C()).foo());
+ System.out.println(new B(new D()).bar());
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ private final C c;
+
+ A(C c) {
+ this.c = c;
+ }
+
+ @NeverInline
+ public String foo() {
+ return c.toString();
+ }
+ }
+
+ @NeverClassInline
+ static class B {
+
+ private final D d;
+
+ B(D d) {
+ this.d = d;
+ }
+
+ @NeverInline
+ public String bar() {
+ return d.toString();
+ }
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ static class C {
+
+ @Override
+ public String toString() {
+ return "C";
+ }
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ static class D {
+
+ @Override
+ public String toString() {
+ return "D";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/IllegalInliningOfMergedConstructorTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/IllegalInliningOfMergedConstructorTest.java
index bbc415c..aad40d0 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/IllegalInliningOfMergedConstructorTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/IllegalInliningOfMergedConstructorTest.java
@@ -33,8 +33,7 @@
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
- .addEnumUnboxingInspector(
- inspector -> inspector.assertUnboxedIf(parameters.isDexRuntime(), Reprocess.class))
+ .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(Reprocess.class))
.addHorizontallyMergedClassesInspector(
inspector -> inspector.assertMergedInto(B.class, A.class))
.addOptionsModification(options -> options.inliningInstructionLimit = 4)
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorStackTraceTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorStackTraceTest.java
index e785238..c39b7b5 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorStackTraceTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorStackTraceTest.java
@@ -44,6 +44,9 @@
.addKeepMainRule(Main.class)
.addKeepAttributeLineNumberTable()
.addKeepAttributeSourceFile()
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
.enableNoVerticalClassMergingAnnotations()
.enableNeverClassInliningAnnotations()
.setMinApi(parameters.getApiLevel())
@@ -52,22 +55,22 @@
.inspectStackTrace(
(stackTrace, codeInspector) -> {
assertThat(codeInspector.clazz(A.class), isPresent());
- StackTrace expectedStackTraceWithMergedConstructor =
- StackTrace.builder()
- .add(expectedStackTrace)
- .add(
- 2,
- StackTraceLine.builder()
- .setClassName(A.class.getTypeName())
- // TODO(b/124483578): The synthetic method should not be part of the
- // retraced stack trace.
- .setMethodName("$r8$init$bridge")
- .setFileName(getClass().getSimpleName() + ".java")
- .setLineNumber(0)
- .build())
- .build();
- assertThat(stackTrace, isSame(expectedStackTraceWithMergedConstructor));
- assertThat(codeInspector.clazz(B.class), not(isPresent()));
+ StackTrace expectedStackTraceWithMergedConstructor =
+ StackTrace.builder()
+ .add(expectedStackTrace)
+ .add(
+ 2,
+ StackTraceLine.builder()
+ .setClassName(A.class.getTypeName())
+ // TODO(b/124483578): The synthetic method should not be part of the
+ // retraced stack trace.
+ .setMethodName("$r8$init$synthetic")
+ .setFileName(getClass().getSimpleName() + ".java")
+ .setLineNumber(0)
+ .build())
+ .build();
+ assertThat(stackTrace, isSame(expectedStackTraceWithMergedConstructor));
+ assertThat(codeInspector.clazz(B.class), not(isPresent()));
});
}
@@ -82,12 +85,18 @@
@NeverClassInline
static class A extends Parent {
- A() {}
+ A() {
+ // To avoid constructor equivalence.
+ System.out.println("A");
+ }
}
@NeverClassInline
static class B extends Parent {
- B() {}
+ B() {
+ // To avoid constructor equivalence.
+ System.out.println("B");
+ }
}
static class Main {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorWithEquivalenceStackTraceTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorWithEquivalenceStackTraceTest.java
new file mode 100644
index 0000000..9b5b87d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorWithEquivalenceStackTraceTest.java
@@ -0,0 +1,107 @@
+// 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.naming.retrace.StackTrace.isSame;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.classmerging.horizontal.MergedConstructorStackTraceTest.A;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import org.junit.Before;
+import org.junit.Test;
+
+public class MergedConstructorWithEquivalenceStackTraceTest extends HorizontalClassMergingTestBase {
+
+ public StackTrace expectedStackTrace;
+
+ public MergedConstructorWithEquivalenceStackTraceTest(TestParameters parameters) {
+ super(parameters);
+ }
+
+ @Before
+ public void setup() throws Exception {
+ // Get the expected stack trace by running on the JVM.
+ expectedStackTrace =
+ testForJvm()
+ .addTestClasspath()
+ .run(CfRuntime.getSystemRuntime(), Main.class)
+ .assertFailure()
+ .map(StackTrace::extractFromJvm);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addKeepAttributeLineNumberTable()
+ .addKeepAttributeSourceFile()
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
+ .enableNoVerticalClassMergingAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .inspectStackTrace(
+ (stackTrace, codeInspector) -> {
+ assertThat(codeInspector.clazz(A.class), isPresent());
+ StackTrace expectedStackTraceWithMergedConstructor =
+ StackTrace.builder()
+ .add(expectedStackTrace)
+ // TODO(b/124483578): Stack trace lines from the merging of equivalent
+ // constructors should retrace to the set of lines from each of the
+ // individual source constructors.
+ .map(1, stackTraceLine -> stackTraceLine.builderOf().setLineNumber(0).build())
+ // TODO(b/124483578): The synthetic method should not be part of the retraced
+ // stack trace.
+ .add(
+ 2,
+ StackTraceLine.builder()
+ .setClassName(A.class.getTypeName())
+ .setMethodName("$r8$init$synthetic")
+ .setFileName(getClass().getSimpleName() + ".java")
+ .setLineNumber(0)
+ .build())
+ .build();
+ assertThat(stackTrace, isSame(expectedStackTraceWithMergedConstructor));
+ assertThat(codeInspector.clazz(B.class), not(isPresent()));
+ });
+ }
+
+ @NoVerticalClassMerging
+ static class Parent {
+ Parent() {
+ if (System.currentTimeMillis() >= 0) {
+ throw new RuntimeException();
+ }
+ }
+ }
+
+ @NeverClassInline
+ static class A extends Parent {
+ A() {}
+ }
+
+ @NeverClassInline
+ static class B extends Parent {
+ B() {}
+ }
+
+ static class Main {
+ public static void main(String[] args) {
+ new A();
+ new B();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/MissingBridgeTest.java b/src/test/java/com/android/tools/r8/desugar/MissingBridgeTest.java
new file mode 100644
index 0000000..e02104d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/MissingBridgeTest.java
@@ -0,0 +1,95 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.IOException;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MissingBridgeTest extends TestBase {
+
+ static final String EXPECTED_WITH_BRIDGE = StringUtils.lines("null", "non-null");
+ static final String EXPECTED_WITHOUT_BRIDGE = StringUtils.lines("null", "null");
+
+ private final TestParameters parameters;
+ private final boolean withBridge;
+
+ @Parameterized.Parameters(name = "{0}, bridge:{1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+ BooleanUtils.values());
+ }
+
+ public MissingBridgeTest(TestParameters parameters, boolean withBridge) {
+ this.parameters = parameters;
+ this.withBridge = withBridge;
+ }
+
+ private String getExpected() {
+ return withBridge ? EXPECTED_WITH_BRIDGE : EXPECTED_WITHOUT_BRIDGE;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForDesugaring(parameters)
+ .addProgramClasses(TestClass.class, I.class, A.class, B.class)
+ .addProgramClassFileData(getTransformedJ())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(getExpected());
+ }
+
+ private byte[] getTransformedJ() throws IOException {
+ return transformer(J.class)
+ .removeMethods(
+ (access, name, descriptor, signature, exceptions) -> {
+ if (!withBridge && MethodAccessFlags.fromCfAccessFlags(access, false).isBridge()) {
+ assertEquals("doIt", name);
+ assertEquals("()Ljava/lang/Object;", descriptor);
+ return true;
+ }
+ return false;
+ })
+ .transform();
+ }
+
+ interface I<T> {
+ default T doIt() {
+ return null;
+ }
+ }
+
+ interface J extends I<String> {
+
+ // If withBridge==true the javac bridge is kept, otherwise stripped:
+ // default Object doIt() { return invoke-virtual {this} J.doIt()Ljava/lang/String; }
+
+ @Override
+ default String doIt() {
+ return "non-null";
+ }
+ }
+
+ static class A implements I {}
+
+ static class B implements J {}
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println(((I) new A()).doIt());
+ System.out.println(((I) new B()).doIt());
+ }
+ }
+}
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 fdb7faa..a4c52ad 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
@@ -115,15 +115,11 @@
private List<Path> additionalProgramFiles = new ArrayList<>();
private Consumer<InternalOptions> optionsModifier = ConsumerUtils.emptyConsumer();
private Path desugarJDKLibs = ToolHelper.getDesugarJDKLibs();
- private Path desugarJDKLibsConfiguration = ToolHelper.DESUGAR_LIB_CONVERSIONS;
+ private Path desugarJDKLibsConfiguration = null;
private StringResource desugaredLibraryConfiguration =
StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting());
private List<Path> libraryFiles = new ArrayList<>();
- public static L8TestBuilder builder(AndroidApiLevel apiLevel, TemporaryFolder temp) {
- return new L8TestBuilder(apiLevel, temp);
- }
-
private L8TestBuilder(AndroidApiLevel apiLevel, TemporaryFolder temp) {
this.apiLevel = apiLevel;
this.state = new TestState(temp);
@@ -239,11 +235,11 @@
}
private Collection<Path> getProgramFiles() {
- return ImmutableList.<Path>builder()
- .add(desugarJDKLibs)
- .add(desugarJDKLibsConfiguration)
- .addAll(additionalProgramFiles)
- .build();
+ ImmutableList.Builder<Path> builder = ImmutableList.<Path>builder().add(desugarJDKLibs);
+ if (desugarJDKLibsConfiguration != null) {
+ builder.add(desugarJDKLibsConfiguration);
+ }
+ return builder.addAll(additionalProgramFiles).build();
}
private Collection<Path> getLibraryFiles() {
@@ -300,6 +296,7 @@
},
L8TestBuilder::setDebug)
.addOptionsModifier(optionsModifier)
+ .setDesugarJDKLibsConfiguration(ToolHelper.DESUGAR_LIB_CONVERSIONS)
// If we compile extended library here, it means we use TestNG. TestNG requires
// annotations, hence we disable annotation removal. This implies that extra warnings are
// generated.
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/MethodParametersTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/MethodParametersTest.java
new file mode 100644
index 0000000..8bec281
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/MethodParametersTest.java
@@ -0,0 +1,100 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.nestaccesscontrol;
+
+import static com.android.tools.r8.TestRuntime.getCheckedInJdk11;
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.desugar.nestaccesscontrol.methodparameters.Outer;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MethodParametersTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ // java.lang.reflect.Method.getParameters() supported from Android 8.1.
+ return getTestParameters()
+ .withDexRuntimesStartingFromIncluding(Version.V8_1_0)
+ .withCfRuntimes()
+ .withAllApiLevelsAlsoForCf()
+ .build();
+ }
+
+ public MethodParametersTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ // Compile with javac from JDK 11 to get a class file using nest access control.
+ Path nestCompiledWithParameters =
+ javac(getCheckedInJdk11())
+ .addSourceFiles(ToolHelper.getSourceFileForTestClass(Outer.class))
+ .addOptions("-parameters")
+ .compile();
+
+ Path nestDesugared =
+ testForD8(Backend.CF)
+ .addProgramFiles(nestCompiledWithParameters)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .writeToZip();
+
+ Path nestDesugaredTwice =
+ testForD8(Backend.CF)
+ .addProgramFiles(nestDesugared)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ // TODO(b/189743726): These warnings should not be there.
+ .assertAtLeastOneInfoMessage()
+ .assertAllInfoMessagesMatch(
+ anyOf(
+ containsString("Invalid parameter counts in MethodParameter attributes"),
+ containsString("Methods with invalid MethodParameter attributes")))
+ .writeToZip();
+
+ Path programDesugared =
+ testForD8(Backend.CF)
+ .addClasspathClasses(Outer.class)
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .writeToZip();
+
+ testForD8(parameters.getBackend())
+ .addProgramFiles(nestDesugaredTwice)
+ .addProgramFiles(programDesugared)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ // TODO(b/189743726): These warnings should not be there.
+ .assertAtLeastOneInfoMessage()
+ .assertAllInfoMessagesMatch(
+ anyOf(
+ containsString("Invalid parameter counts in MethodParameter attributes"),
+ containsString("Methods with invalid MethodParameter attributes")))
+ .run(parameters.getRuntime(), TestRunner.class)
+ // TODO(b/189743726): Should not fail at runtime.
+ .assertFailureWithErrorThatMatches(
+ containsString("Wrong number of parameters in MethodParameters attribute"));
+ }
+
+ static class TestRunner {
+
+ public static void main(String[] args) {
+ Outer.callPrivateInnerConstructorZeroArgs();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/methodparameters/Outer.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/methodparameters/Outer.java
new file mode 100644
index 0000000..9c92994
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/methodparameters/Outer.java
@@ -0,0 +1,32 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.nestaccesscontrol.methodparameters;
+
+import java.lang.reflect.Constructor;
+
+public class Outer {
+ public static class Inner {
+ private Inner() {
+ for (Constructor<?> constructor : getClass().getDeclaredConstructors()) {
+ System.out.println(constructor.getParameters().length);
+ }
+ }
+
+ private Inner(int a) {}
+
+ private Inner(int a, int b) {}
+ }
+
+ public static Inner callPrivateInnerConstructorZeroArgs() {
+ return new Inner();
+ }
+
+ public static Inner callPrivateInnerConstructorOneArg() {
+ return new Inner(0);
+ }
+
+ public static Inner callPrivateInnerConstructorTwoArgs() {
+ return new Inner(0, 0);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/MethodParametersTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/MethodParametersTest.java
new file mode 100644
index 0000000..7d70d21
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/MethodParametersTest.java
@@ -0,0 +1,141 @@
+// Copyright (c) 2021, 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.desugaring.interfacemethods;
+
+import static com.android.tools.r8.TestRuntime.getCheckedInJdk;
+import static com.android.tools.r8.TestRuntime.getCheckedInJdk11;
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.desugaring.interfacemethods.methodparameters.I;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MethodParametersTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ // java.lang.reflect.Method.getParameters() supported from Android 8.1.
+ return getTestParameters()
+ .withDexRuntimesStartingFromIncluding(Version.V8_1_0)
+ .withCfRuntimes()
+ .withAllApiLevelsAlsoForCf()
+ .build();
+ }
+
+ public MethodParametersTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClassesAndInnerClasses(I.class)
+ .addInnerClasses(getClass())
+ .run(parameters.getRuntime(), TestRunner.class)
+ .assertSuccessWithOutputLines("0", "1", "2", "0", "1", "2");
+ }
+
+ @Test
+ public void testDesugar() throws Exception {
+ // JDK8 is not present on Windows.
+ assumeTrue(
+ parameters.isDexRuntime()
+ || getCheckedInJdk(parameters.getRuntime().asCf().getVm()) != null);
+ Path compiledWithParameters =
+ javac(
+ parameters.isCfRuntime()
+ ? getCheckedInJdk(parameters.getRuntime().asCf().getVm())
+ : getCheckedInJdk11())
+ .addSourceFiles(ToolHelper.getSourceFileForTestClass(I.class))
+ .addOptions("-parameters")
+ .compile();
+
+ Path interfaceDesugared =
+ testForD8(Backend.CF)
+ .addProgramFiles(compiledWithParameters)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .writeToZip();
+
+ Path interfaceDesugaredTwice =
+ testForD8(Backend.CF)
+ .addProgramFiles(interfaceDesugared)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ // TODO(b/189743726): These warnings should not be there.
+ .applyIf(
+ parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring(),
+ TestCompileResult::assertNoInfoMessages,
+ r ->
+ r.assertAtLeastOneInfoMessage()
+ .assertAllInfoMessagesMatch(
+ anyOf(
+ containsString(
+ "Invalid parameter counts in MethodParameter attributes"),
+ containsString("Methods with invalid MethodParameter attributes"))))
+ .writeToZip();
+
+ Path programDesugared =
+ testForD8(Backend.CF)
+ .addClasspathClasses(I.class)
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .writeToZip();
+
+ testForD8(parameters.getBackend())
+ .addProgramFiles(interfaceDesugaredTwice)
+ .addProgramFiles(programDesugared)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ // TODO(b/189743726): These warnings should not be there.
+ .applyIf(
+ parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring(),
+ TestCompileResult::assertNoInfoMessages,
+ r ->
+ r.assertAtLeastOneInfoMessage()
+ .assertAllInfoMessagesMatch(
+ anyOf(
+ containsString(
+ "Invalid parameter counts in MethodParameter attributes"),
+ containsString("Methods with invalid MethodParameter attributes"))))
+ .run(parameters.getRuntime(), TestRunner.class)
+ .applyIf(
+ parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring(),
+ r -> r.assertSuccessWithOutputLines("0", "1", "2", "0", "1", "2"),
+ // TODO(b/189743726): Should not fail at runtime (but will have different parameter
+ // count for non-static methods when desugared).
+ r ->
+ r.assertFailureWithErrorThatMatches(
+ containsString("Wrong number of parameters in MethodParameters attribute")));
+ }
+
+ static class A implements I {}
+
+ static class TestRunner {
+
+ public static void main(String[] args) {
+ new A().zeroArgsDefault();
+ new A().oneArgDefault(0);
+ new A().twoArgDefault(0, 0);
+ I.zeroArgStatic();
+ I.oneArgStatic(0);
+ I.twoArgsStatic(0, 0);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/methodparameters/I.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/methodparameters/I.java
new file mode 100644
index 0000000..4c7ba69
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/methodparameters/I.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2021, 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.desugaring.interfacemethods.methodparameters;
+
+public interface I {
+
+ default void zeroArgsDefault() {
+ System.out.println(new Object() {}.getClass().getEnclosingMethod().getParameters().length);
+ }
+
+ default void oneArgDefault(int a) {
+ System.out.println(new Object() {}.getClass().getEnclosingMethod().getParameters().length);
+ }
+
+ default void twoArgDefault(int a, int b) {
+ System.out.println(new Object() {}.getClass().getEnclosingMethod().getParameters().length);
+ }
+
+ static void zeroArgStatic() {
+ System.out.println(new Object() {}.getClass().getEnclosingMethod().getParameters().length);
+ }
+
+ static void oneArgStatic(int a) {
+ System.out.println(new Object() {}.getClass().getEnclosingMethod().getParameters().length);
+ }
+
+ static void twoArgsStatic(int a, int b) {
+ System.out.println(new Object() {}.getClass().getEnclosingMethod().getParameters().length);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingB160535628Test.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingB160535628Test.java
index a8fe3f9..2a8105f 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingB160535628Test.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingB160535628Test.java
@@ -85,11 +85,6 @@
.addKeepRules("-keep enum * { <fields>; }")
.addKeepRules(enumKeepRules.getKeepRules())
.addKeepRules(missingStaticMethods ? "" : "-keep enum * { static <methods>; }")
- .addOptionsModification(
- options -> {
- assert !options.enableEnumUnboxing;
- options.enableEnumUnboxing = true;
- })
.addKeepClassRules(Lib.LibEnumStaticMethod.class)
.addEnumUnboxingInspector(
inspector ->
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/LambdaEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/LambdaEnumUnboxingTest.java
index aba1bb8..b99986a 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/LambdaEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/LambdaEnumUnboxingTest.java
@@ -40,14 +40,6 @@
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
.addKeepRules(enumKeepRules.getKeepRules())
- .addOptionsModification(
- options -> {
- if (options.isGeneratingClassFiles()) {
- // TODO(b/172568606): Remove this when enabled for CF by default.
- assertFalse(options.enableEnumUnboxing);
- options.enableEnumUnboxing = true;
- }
- })
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
.addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureAllowShrinkingTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureAllowShrinkingTest.java
new file mode 100644
index 0000000..be24a34
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureAllowShrinkingTest.java
@@ -0,0 +1,96 @@
+// Copyright (c) 2021, 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.genericsignature;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.lang.reflect.ParameterizedType;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+// This is a reproduction of b/189443104.
+public class GenericSignatureAllowShrinkingTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final String[] EXPECTED = new String[] {"class java.lang.String"};
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public GenericSignatureAllowShrinkingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8WithDirectKeep() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepClassRules(Foo.class)
+ .addKeepRules("-keep class * extends " + Foo.class.getTypeName() + " { *; }")
+ .addKeepAttributes(ProguardKeepAttributes.SIGNATURE)
+ .addKeepMainRule(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .inspect(this::inspect)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8AllowShrinking() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepRules("-keep,allowshrinking class " + Foo.class.getTypeName() + " { *; }")
+ .addKeepRules(
+ "-keep,allowshrinking,allowobfuscation class * extends "
+ + Foo.class.getTypeName()
+ + " { *; }")
+ .addKeepAttributes(ProguardKeepAttributes.SIGNATURE)
+ .addKeepMainRule(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .inspect(this::inspect)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject foo = inspector.clazz(Foo.class);
+ assertThat(foo, isPresent());
+ assertEquals("<T:Ljava/lang/Object;>Ljava/lang/Object;", foo.getFinalSignatureAttribute());
+
+ ClassSubject main$1 = inspector.clazz(Main.class.getTypeName() + "$1");
+ assertThat(main$1, isPresent());
+ assertEquals(
+ "L" + binaryName(Foo.class) + "<Ljava/lang/String;>;", main$1.getFinalSignatureAttribute());
+ }
+
+ public static class Foo<T> {
+
+ public void print() {
+ ParameterizedType genericSuperclass =
+ (ParameterizedType) this.getClass().getGenericSuperclass();
+ System.out.println(genericSuperclass.getActualTypeArguments()[0].toString());
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ (new Foo<String>() {}).print();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureInvalidInfoKeepTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureInvalidInfoKeepTest.java
new file mode 100644
index 0000000..b9248aa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureInvalidInfoKeepTest.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2021, 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.genericsignature;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+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 GenericSignatureInvalidInfoKeepTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public GenericSignatureInvalidInfoKeepTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Base.class, Main.class)
+ .addInnerClasses(A.class)
+ .addProgramClassFileData(
+ transformer(A.class)
+ .setGenericSignature(MethodPredicate.onName("foo"), (String) null)
+ .transform())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .compile()
+ // This tests that no info messages are generated due to us removing the generic parameters
+ // K and V from A::base.
+ .assertNoInfoMessages()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A$1::bar")
+ .inspect(
+ inspector -> {
+ ClassSubject baseClass = inspector.clazz(Base.class);
+ assertThat(baseClass, not(isPresent()));
+ });
+ }
+
+ public abstract static class Base<K, V> {
+ public abstract K bar(V v);
+ }
+
+ public static class A {
+
+ public static <K, V> Base<K, V> foo() {
+ return new Base<K, V>() {
+ @Override
+ public K bar(V f) {
+ System.out.println("A$1::bar");
+ return null;
+ }
+ };
+ }
+ ;
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ A.foo().bar(null);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1620Test.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1620Test.java
new file mode 100644
index 0000000..c1182bd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1620Test.java
@@ -0,0 +1,191 @@
+// Copyright (c) 2021, 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.internal;
+
+import static com.android.tools.r8.ToolHelper.isLocalDevelopment;
+import static com.android.tools.r8.ToolHelper.shouldRunSlowTests;
+import static com.android.tools.r8.internal.proto.ProtoShrinkingTestBase.assertRewrittenProtoSchemasMatch;
+import static com.android.tools.r8.internal.proto.ProtoShrinkingTestBase.keepAllProtosRule;
+import static com.android.tools.r8.internal.proto.ProtoShrinkingTestBase.keepDynamicMethodSignatureRule;
+import static com.android.tools.r8.internal.proto.ProtoShrinkingTestBase.keepNewMessageInfoSignatureRule;
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.L8TestCompileResult;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.concurrent.ExecutionException;
+import org.junit.After;
+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 YouTubeV1620Test extends YouTubeCompilationTestBase {
+
+ private static final int MAX_APPLICATION_SIZE = 29750000;
+ private static final int MAX_DESUGARED_LIBRARY_SIZE = 425000;
+
+ private final TestParameters parameters;
+
+ private final Path dumpDirectory = Paths.get("YouTubeV1620-" + System.currentTimeMillis());
+ private final Reporter reporter = new Reporter();
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withDexRuntime(Version.V9_0_0)
+ .withApiLevel(AndroidApiLevel.L)
+ .build();
+ }
+
+ public YouTubeV1620Test(TestParameters parameters) {
+ super(16, 20, parameters.getApiLevel());
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ assumeTrue(isLocalDevelopment());
+ assumeTrue(shouldRunSlowTests());
+
+ KeepRuleConsumer keepRuleConsumer = new PresentKeepRuleConsumer();
+ R8TestCompileResult r8CompileResult = compileApplicationWithR8(keepRuleConsumer);
+ L8TestCompileResult l8CompileResult = compileDesugaredLibraryWithL8(keepRuleConsumer);
+
+ inspect(r8CompileResult, l8CompileResult);
+
+ if (isLocalDevelopment()) {
+ dump(r8CompileResult, l8CompileResult);
+ }
+ }
+
+ @Test
+ public void testProtoRewriting() throws Exception {
+ assumeTrue(shouldRunSlowTests());
+
+ StringConsumer keepRuleConsumer = StringConsumer.emptyConsumer();
+ R8TestCompileResult r8CompileResult =
+ compileApplicationWithR8(
+ keepRuleConsumer,
+ builder ->
+ builder
+ .addKeepRules(
+ keepAllProtosRule(),
+ keepDynamicMethodSignatureRule(),
+ keepNewMessageInfoSignatureRule())
+ .allowCheckDiscardedErrors(true));
+ assertRewrittenProtoSchemasMatch(
+ new CodeInspector(getProgramFiles()), r8CompileResult.inspector());
+ }
+
+ @After
+ public void teardown() {
+ reporter.failIfPendingErrors();
+ }
+
+ private R8TestCompileResult compileApplicationWithR8(StringConsumer keepRuleConsumer)
+ throws IOException, CompilationFailedException {
+ return compileApplicationWithR8(keepRuleConsumer, ThrowableConsumer.empty());
+ }
+
+ private R8TestCompileResult compileApplicationWithR8(
+ StringConsumer keepRuleConsumer, ThrowableConsumer<R8FullTestBuilder> configuration)
+ throws IOException, CompilationFailedException {
+ return testForR8(parameters.getBackend())
+ .addProgramFiles(getProgramFiles())
+ .addLibraryFiles(getLibraryFiles())
+ .addKeepRuleFiles(getKeepRuleFiles())
+ .addDontWarn("android.app.Activity$TranslucentConversionListener")
+ .allowDiagnosticMessages()
+ .allowUnusedDontWarnPatterns()
+ .allowUnusedProguardConfigurationRules()
+ .apply(configuration)
+ .setMinApi(getApiLevel())
+ .enableCoreLibraryDesugaring(
+ getApiLevel(),
+ keepRuleConsumer,
+ StringResource.fromFile(getDesugaredLibraryConfiguration()))
+ .compile()
+ .assertAllInfoMessagesMatch(
+ anyOf(
+ containsString("Ignoring option: -optimizations"),
+ containsString("Proguard configuration rule does not match anything"),
+ containsString("Invalid signature")))
+ .apply(this::printProtoStats);
+ }
+
+ private L8TestCompileResult compileDesugaredLibraryWithL8(KeepRuleConsumer keepRuleConsumer)
+ throws CompilationFailedException, IOException, ExecutionException {
+ return testForL8(getApiLevel())
+ .setDesugaredLibraryConfiguration(getDesugaredLibraryConfiguration())
+ .setDesugarJDKLibs(getDesugaredLibraryJDKLibs())
+ .addGeneratedKeepRules(keepRuleConsumer.get())
+ .addKeepRuleFiles(getDesugaredLibraryKeepRuleFiles())
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+ .compile();
+ }
+
+ private void inspect(R8TestCompileResult r8CompileResult, L8TestCompileResult l8CompileResult)
+ throws IOException, ResourceException {
+ r8CompileResult.runDex2Oat(parameters.getRuntime()).assertNoVerificationErrors();
+ l8CompileResult.runDex2Oat(parameters.getRuntime()).assertNoVerificationErrors();
+
+ int applicationSize = r8CompileResult.getApp().applicationSize();
+ if (applicationSize > MAX_APPLICATION_SIZE) {
+ reporter.error(
+ "Expected application size to be <="
+ + MAX_APPLICATION_SIZE
+ + ", but was "
+ + applicationSize);
+ }
+
+ int desugaredLibrarySize = l8CompileResult.getApp().applicationSize();
+ if (desugaredLibrarySize > MAX_DESUGARED_LIBRARY_SIZE) {
+ reporter.error(
+ "Expected desugared library size to be <="
+ + MAX_DESUGARED_LIBRARY_SIZE
+ + ", but was "
+ + desugaredLibrarySize);
+ }
+
+ if (isLocalDevelopment()) {
+ System.out.println("Dex size (application, excluding desugared library): " + applicationSize);
+ System.out.println("Dex size (desugared library): " + desugaredLibrarySize);
+ System.out.println("Dex size (total): " + (applicationSize + desugaredLibrarySize));
+ }
+ }
+
+ private void dump(R8TestCompileResult r8CompileResult, L8TestCompileResult l8CompileResult)
+ throws IOException {
+ assertTrue(isLocalDevelopment());
+ Files.createDirectories(dumpDirectory);
+ r8CompileResult
+ .writeToDirectory(dumpDirectory)
+ .writeProguardMap(dumpDirectory.resolve("mapping.txt"));
+ l8CompileResult
+ .writeSingleDexOutputToFile(dumpDirectory.resolve("classes5.dex"))
+ .writeGeneratedKeepRules(dumpDirectory.resolve("l8-keep.txt"))
+ .writeProguardMap(dumpDirectory.resolve("l8-mapping.txt"));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
index e5746ec..5dd3140 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
@@ -12,7 +12,6 @@
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
@@ -62,14 +61,15 @@
public void testWriteOnlyField_dontoptimize() throws Exception {
CodeInspector inspector = runR8(DONT_OPTIMIZE);
ClassSubject clazz = inspector.clazz(QUALIFIED_CLASS_NAME);
- // With the support of 'allowshrinking' dontoptimize will now effectively pin all
- // items that are not tree shaken out. The field operations will thus remain.
- assertTrue(clazz.clinit().streamInstructions().anyMatch(InstructionSubject::isStaticPut));
- assertTrue(
- clazz
- .uniqueInstanceInitializer()
- .streamInstructions()
- .anyMatch(InstructionSubject::isInstancePut));
+ clazz.forAllMethods(
+ methodSubject -> {
+ // Dead code removal is not part of -dontoptimize. That is, even with -dontoptimize,
+ // field put instructions are gone with better dead code removal.
+ assertTrue(
+ methodSubject
+ .streamInstructions()
+ .noneMatch(i -> i.isInstancePut() || i.isStaticPut()));
+ });
}
private CodeInspector runR8(Path proguardConfig) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index aad2f6f..95c8527 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -24,7 +24,6 @@
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ZipUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -358,10 +357,8 @@
ClassSubject clazz = inspector.clazz(nullabilityClass);
assertThat(clazz.uniqueMethodWithName("conditionalOperator"), isAbsent());
- // The enum parameter may get unboxed.
- MethodSubject m =
- clazz.uniqueMethodWithName(
- parameters.isCfRuntime() ? "moreControlFlows" : "moreControlFlows$enumunboxing$");
+ // The enum parameter is unboxed.
+ MethodSubject m = clazz.uniqueMethodWithName("moreControlFlows$enumunboxing$");
assertTrue(m.isPresent());
// Verify that a.b() is resolved to an inline instance-get.
@@ -381,18 +378,10 @@
}
private boolean isEnumInvoke(InstructionSubject instruction) {
- InternalOptions defaults = new InternalOptions();
- if (parameters.isDexRuntime() && defaults.enableEnumUnboxing) {
- return instruction
- .getMethod()
- .name
- .toString()
- .startsWith(EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_METHOD_PREFIX);
- } else {
- return ((InvokeInstructionSubject) instruction)
- .holder()
- .toString()
- .contains("java.lang.Enum");
- }
+ return instruction
+ .getMethod()
+ .getName()
+ .toString()
+ .startsWith(EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_METHOD_PREFIX);
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/ConditionalSimpleInliningWithEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/ConditionalSimpleInliningWithEnumUnboxingTest.java
index 3cebb62..7eb5882 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/ConditionalSimpleInliningWithEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/ConditionalSimpleInliningWithEnumUnboxingTest.java
@@ -28,9 +28,7 @@
.addInnerClasses(getClass())
.addKeepMainRule(TestClass.class)
.apply(this::configure)
- .addEnumUnboxingInspector(
- inspector ->
- inspector.assertUnboxedIf(parameters.isDexRuntime(), EnumUnboxingCandidate.class))
+ .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(EnumUnboxingCandidate.class))
.setMinApi(parameters.getApiLevel())
.compile()
.run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/NoOutliningForClassFileBuildsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/NoOutliningForClassFileBuildsTest.java
deleted file mode 100644
index 2a837bc..0000000
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/NoOutliningForClassFileBuildsTest.java
+++ /dev/null
@@ -1,103 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.optimize.outliner;
-
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class NoOutliningForClassFileBuildsTest extends TestBase {
-
- static final String EXPECTED = StringUtils.lines("foobarbaz");
-
- private final TestParameters parameters;
- private final boolean forceOutline;
-
- @Parameterized.Parameters(name = "{0}, force-outline: {1}")
- public static Collection<Object[]> data() {
- List<Object[]> args = new ArrayList<>();
- for (TestParameters parameter : getTestParameters().withAllRuntimesAndApiLevels().build()) {
- args.add(new Object[] {parameter, false});
- if (parameter.isCfRuntime()) {
- args.add(new Object[] {parameter, true});
- }
- }
- return args;
- }
-
- public NoOutliningForClassFileBuildsTest(TestParameters parameters, boolean forceOutline) {
- this.parameters = parameters;
- this.forceOutline = forceOutline;
- }
-
- @Test
- public void test() throws Exception {
- testForR8(parameters.getBackend())
- .noMinification()
- .addProgramClasses(TestClass.class)
- .addKeepClassAndMembersRules(TestClass.class)
- .setMinApi(parameters.getApiLevel())
- .addOptionsModification(
- o -> {
- if (forceOutline) {
- o.outline.enabled = true;
- }
- o.outline.minSize = 2;
- o.outline.maxSize = 20;
- o.outline.threshold = 2;
- })
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED)
- .inspect(this::checkOutlining);
- }
-
- private void checkOutlining(CodeInspector inspector) {
- ClassSubject classSubject = inspector.clazz(TestClass.class);
- assertThat(classSubject, isPresent());
- assertThat(classSubject.uniqueMethodWithName("foo"), isPresent());
- assertThat(classSubject.uniqueMethodWithName("bar"), isPresent());
- boolean hasOutlineClass =
- inspector
- .clazz(SyntheticItemsTestUtils.syntheticOutlineClass(TestClass.class, 0))
- .isPresent();
- assertEquals(forceOutline || parameters.isDexRuntime(), hasOutlineClass);
- }
-
- static class TestClass {
-
- public void foo(String arg) {
- StringBuilder builder = new StringBuilder();
- builder.append("foo");
- builder.append(arg);
- builder.append("baz");
- System.out.println(builder.toString());
- }
-
- public void bar(String arg) {
- StringBuilder builder = new StringBuilder();
- builder.append("foo");
- builder.append(arg);
- builder.append("baz");
- System.out.println(builder.toString());
- }
-
- public static void main(String[] args) {
- new TestClass().foo("bar");
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineFromStaticInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineFromStaticInterfaceMethodTest.java
index 0531857..5660355 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineFromStaticInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineFromStaticInterfaceMethodTest.java
@@ -43,10 +43,6 @@
.addKeepMainRule(TestClass.class)
.addOptionsModification(
options -> {
- if (parameters.isCfRuntime()) {
- assert !options.outline.enabled;
- options.outline.enabled = true;
- }
options.outline.threshold = 2;
options.outline.minSize = 2;
})
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlinesWithNonNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlinesWithNonNullTest.java
index 6b7bc4a..c4b84a7 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlinesWithNonNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlinesWithNonNullTest.java
@@ -35,7 +35,7 @@
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public OutlinesWithNonNullTest(TestParameters parameters) {
@@ -49,15 +49,11 @@
.enableInliningAnnotations()
.addProgramClasses(TestArg.class, TestClassWithNonNullOnOneSide.class)
.addKeepMainRule(TestClassWithNonNullOnOneSide.class)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.allowAccessModification()
.noMinification()
.addOptionsModification(
options -> {
- if (parameters.isCfRuntime()) {
- assert !options.outline.enabled;
- options.outline.enabled = true;
- }
options.outline.threshold = 2;
options.outline.minSize = 2;
})
@@ -74,15 +70,11 @@
.enableInliningAnnotations()
.addProgramClasses(TestArg.class, TestClassWithNonNullOnBothSides.class)
.addKeepMainRule(TestClassWithNonNullOnBothSides.class)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.allowAccessModification()
.noMinification()
.addOptionsModification(
options -> {
- if (parameters.isCfRuntime()) {
- assert !options.outline.enabled;
- options.outline.enabled = true;
- }
options.outline.threshold = 2;
options.outline.minSize = 2;
})
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithClassArrayTypeArguments.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithClassArrayTypeArguments.java
index 440b036..8bd05e0 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithClassArrayTypeArguments.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithClassArrayTypeArguments.java
@@ -29,7 +29,7 @@
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public OutlinesWithClassArrayTypeArguments(TestParameters parameters) {
@@ -60,14 +60,10 @@
.enableInliningAnnotations()
.addInnerClasses(OutlinesWithClassArrayTypeArguments.class)
.addKeepMainRule(TestClass.class)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.noMinification()
.addOptionsModification(
options -> {
- if (parameters.isCfRuntime()) {
- assert !options.outline.enabled;
- options.outline.enabled = true;
- }
options.outline.threshold = 2;
options.outline.minSize = 2;
})
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithInterfaceArrayTypeArguments.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithInterfaceArrayTypeArguments.java
index 062516c..5a0b120 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithInterfaceArrayTypeArguments.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithInterfaceArrayTypeArguments.java
@@ -32,7 +32,8 @@
@Parameterized.Parameters(name = "{1}, allow interface array types in outlining on JVM: {0}")
public static List<Object[]> data() {
- return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
}
public OutlinesWithInterfaceArrayTypeArguments(
@@ -73,14 +74,10 @@
.addKeepMainRule(TestClass.class)
.addKeepClassAndMembersRules(ClassImplementingIface.class)
.addKeepClassAndMembersRules(OtherClassImplementingIface.class)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.noMinification()
.addOptionsModification(
options -> {
- if (parameters.isCfRuntime()) {
- assert !options.outline.enabled;
- options.outline.enabled = true;
- }
options.outline.threshold = 2;
options.outline.minSize = 2;
options.testing.allowOutlinerInterfaceArrayArguments =
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithPrimitiveArrayTypeArguments.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithPrimitiveArrayTypeArguments.java
index 36d9ad5..6334006 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithPrimitiveArrayTypeArguments.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/arraytypes/OutlinesWithPrimitiveArrayTypeArguments.java
@@ -63,10 +63,6 @@
.noMinification()
.addOptionsModification(
options -> {
- if (parameters.isCfRuntime()) {
- assert !options.outline.enabled;
- options.outline.enabled = true;
- }
options.outline.threshold = 2;
options.outline.minSize = 2;
})
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b112247415/B112247415.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b112247415/B112247415.java
index 1e5ef3f..d5e8708 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b112247415/B112247415.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b112247415/B112247415.java
@@ -88,10 +88,6 @@
.addKeepMainRule(TestClass.class)
.addOptionsModification(
options -> {
- if (parameters.isCfRuntime()) {
- assert !options.outline.enabled;
- options.outline.enabled = true;
- }
// To trigger outliner, set # of expected outline candidate as threshold.
options.outline.threshold = 2;
options.enableInlining = false;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b133215941/B133215941.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b133215941/B133215941.java
index 031fd9d..9c8eb76 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b133215941/B133215941.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b133215941/B133215941.java
@@ -33,7 +33,7 @@
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public B133215941(TestParameters parameters) {
@@ -69,16 +69,9 @@
.addInnerClasses(B133215941.class)
.addKeepMainRule(TestClass.class)
.addKeepClassAndMembersRules(ClassWithStaticMethod.class)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.noMinification()
- .addOptionsModification(
- options -> {
- if (parameters.isCfRuntime()) {
- assert !options.outline.enabled;
- options.outline.enabled = true;
- }
- options.outline.threshold = 2;
- })
+ .addOptionsModification(options -> options.outline.threshold = 2)
.compile()
.inspect(this::validateOutlining)
.run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/classtypes/B134462736.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/classtypes/B134462736.java
index 39c95f1..29de662 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/classtypes/B134462736.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/classtypes/B134462736.java
@@ -29,7 +29,7 @@
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public B134462736(TestParameters parameters) {
@@ -64,14 +64,10 @@
.enableInliningAnnotations()
.addInnerClasses(B134462736.class)
.addKeepMainRule(TestClass.class)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.noMinification()
.addOptionsModification(
options -> {
- if (parameters.isCfRuntime()) {
- assert !options.outline.enabled;
- options.outline.enabled = true;
- }
options.outline.threshold = 2;
options.outline.minSize = 2;
})
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/primitivetypes/PrimitiveTypesTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/primitivetypes/PrimitiveTypesTest.java
index 4b81714..e384d9b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/primitivetypes/PrimitiveTypesTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/primitivetypes/PrimitiveTypesTest.java
@@ -30,7 +30,7 @@
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public PrimitiveTypesTest(TestParameters parameters) {
@@ -61,14 +61,10 @@
.addProgramClasses(testClass)
.addProgramClasses(MyStringBuilder.class)
.addKeepMainRule(testClass)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.noMinification()
.addOptionsModification(
options -> {
- if (parameters.isCfRuntime()) {
- assert !options.outline.enabled;
- options.outline.enabled = true;
- }
options.outline.threshold = 2;
options.outline.minSize = 2;
})
diff --git a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
index 049974c..548ce4d 100644
--- a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
@@ -100,20 +100,12 @@
@Test
public void testDontOptimize() throws Exception {
- test(
- builder -> {
- builder.allowDiagnosticInfoMessages();
- builder.noOptimization();
- });
+ test(TestShrinkerBuilder::noOptimization);
}
@Test
public void testDontObfuscate() throws Exception {
- test(
- builder -> {
- builder.allowDiagnosticInfoMessages();
- builder.noMinification();
- });
+ test(TestShrinkerBuilder::noMinification);
}
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
index e2c0a0a..ec3722c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
@@ -6,8 +6,10 @@
import static org.junit.Assume.assumeTrue;
+import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
import com.android.tools.r8.KotlinTestParameters;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.kotlin.TestKotlinClass.Visibility;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -64,34 +66,40 @@
public void test_dataclass_gettersOnly() throws Exception {
// TODO(b/179866251): Allow for CF code.
assumeTrue(testParameters.isDexRuntime());
- final String mainClassName = "dataclass.MainGettersOnlyKt";
- final MethodSignature testMethodSignature =
+ String mainClassName = "dataclass.MainGettersOnlyKt";
+ MethodSignature testMethodSignature =
new MethodSignature("testDataClassGetters", "void", Collections.emptyList());
- final String extraRules = keepClassMethod(mainClassName, testMethodSignature);
runTest(
"dataclass",
mainClassName,
testBuilder ->
testBuilder
- .addKeepRules(extraRules)
+ .addKeepRules(keepClassMethod(mainClassName, testMethodSignature))
.addOptionsModification(disableClassInliner))
.inspect(
inspector -> {
- ClassSubject dataClass = checkClassIsKept(inspector, TEST_DATA_CLASS.getClassName());
+ if (allowAccessModification
+ && kotlinParameters.is(
+ KotlinCompilerVersion.KOTLINC_1_5_0_M2, KotlinTargetVersion.JAVA_8)) {
+ checkClassIsRemoved(inspector, TEST_DATA_CLASS.getClassName());
+ } else {
+ ClassSubject dataClass =
+ checkClassIsKept(inspector, TEST_DATA_CLASS.getClassName());
- // Getters should be removed after inlining, which is possible only if access is
- // relaxed.
- final boolean areGetterPresent = !allowAccessModification;
- checkMethodIsKeptOrRemoved(dataClass, NAME_GETTER_METHOD, areGetterPresent);
- checkMethodIsKeptOrRemoved(dataClass, AGE_GETTER_METHOD, areGetterPresent);
+ // Getters should be removed after inlining, which is possible only if access is
+ // relaxed.
+ boolean areGetterPresent = !allowAccessModification;
+ checkMethodIsKeptOrRemoved(dataClass, NAME_GETTER_METHOD, areGetterPresent);
+ checkMethodIsKeptOrRemoved(dataClass, AGE_GETTER_METHOD, areGetterPresent);
- // No use of componentN functions.
- checkMethodIsRemoved(dataClass, COMPONENT1_METHOD);
- checkMethodIsRemoved(dataClass, COMPONENT2_METHOD);
+ // No use of componentN functions.
+ checkMethodIsRemoved(dataClass, COMPONENT1_METHOD);
+ checkMethodIsRemoved(dataClass, COMPONENT2_METHOD);
- // No use of copy functions.
- checkMethodIsRemoved(dataClass, COPY_METHOD);
- checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD);
+ // No use of copy functions.
+ checkMethodIsRemoved(dataClass, COPY_METHOD);
+ checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD);
+ }
ClassSubject classSubject = checkClassIsKept(inspector, mainClassName);
MethodSubject testMethod = checkMethodIsKept(classSubject, testMethodSignature);
@@ -109,34 +117,42 @@
public void test_dataclass_componentOnly() throws Exception {
// TODO(b/179866251): Allow for CF code.
assumeTrue(testParameters.isDexRuntime());
- final String mainClassName = "dataclass.MainComponentOnlyKt";
- final MethodSignature testMethodSignature =
+ String mainClassName = "dataclass.MainComponentOnlyKt";
+ MethodSignature testMethodSignature =
new MethodSignature("testAllDataClassComponentFunctions", "void", Collections.emptyList());
- final String extraRules = keepClassMethod(mainClassName, testMethodSignature);
runTest(
"dataclass",
mainClassName,
testBuilder ->
testBuilder
- .addKeepRules(extraRules)
+ .addKeepRules(keepClassMethod(mainClassName, testMethodSignature))
.addOptionsModification(disableClassInliner))
.inspect(
inspector -> {
- ClassSubject dataClass = checkClassIsKept(inspector, TEST_DATA_CLASS.getClassName());
+ if (allowAccessModification
+ && kotlinParameters.is(
+ KotlinCompilerVersion.KOTLINC_1_5_0_M2, KotlinTargetVersion.JAVA_8)) {
+ checkClassIsRemoved(inspector, TEST_DATA_CLASS.getClassName());
+ } else {
+ ClassSubject dataClass =
+ checkClassIsKept(inspector, TEST_DATA_CLASS.getClassName());
- // ComponentN functions should be removed after inlining, which is possible only if
- // access is relaxed.
- final boolean areComponentMethodsPresent = !allowAccessModification;
- checkMethodIsKeptOrRemoved(dataClass, COMPONENT1_METHOD, areComponentMethodsPresent);
- checkMethodIsKeptOrRemoved(dataClass, COMPONENT2_METHOD, areComponentMethodsPresent);
+ // ComponentN functions should be removed after inlining, which is possible only if
+ // access is relaxed.
+ boolean areComponentMethodsPresent = !allowAccessModification;
+ checkMethodIsKeptOrRemoved(
+ dataClass, COMPONENT1_METHOD, areComponentMethodsPresent);
+ checkMethodIsKeptOrRemoved(
+ dataClass, COMPONENT2_METHOD, areComponentMethodsPresent);
- // No use of getter.
- checkMethodIsRemoved(dataClass, NAME_GETTER_METHOD);
- checkMethodIsRemoved(dataClass, AGE_GETTER_METHOD);
+ // No use of getter.
+ checkMethodIsRemoved(dataClass, NAME_GETTER_METHOD);
+ checkMethodIsRemoved(dataClass, AGE_GETTER_METHOD);
- // No use of copy functions.
- checkMethodIsRemoved(dataClass, COPY_METHOD);
- checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD);
+ // No use of copy functions.
+ checkMethodIsRemoved(dataClass, COPY_METHOD);
+ checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD);
+ }
ClassSubject classSubject = checkClassIsKept(inspector, mainClassName);
MethodSubject testMethod = checkMethodIsKept(classSubject, testMethodSignature);
@@ -153,16 +169,15 @@
public void test_dataclass_componentPartial() throws Exception {
// TODO(b/179866251): Allow for CF code.
assumeTrue(testParameters.isDexRuntime());
- final String mainClassName = "dataclass.MainComponentPartialKt";
- final MethodSignature testMethodSignature =
+ String mainClassName = "dataclass.MainComponentPartialKt";
+ MethodSignature testMethodSignature =
new MethodSignature("testSomeDataClassComponentFunctions", "void", Collections.emptyList());
- final String extraRules = keepClassMethod(mainClassName, testMethodSignature);
runTest(
"dataclass",
mainClassName,
testBuilder ->
testBuilder
- .addKeepRules(extraRules)
+ .addKeepRules(keepClassMethod(mainClassName, testMethodSignature))
.addOptionsModification(disableClassInliner))
.inspect(
inspector -> {
@@ -195,45 +210,47 @@
@Test
public void test_dataclass_copyIsRemovedIfNotUsed() throws Exception {
- final String mainClassName = "dataclass.MainComponentOnlyKt";
- final MethodSignature testMethodSignature =
+ String mainClassName = "dataclass.MainComponentOnlyKt";
+ MethodSignature testMethodSignature =
new MethodSignature("testDataClassCopy", "void", Collections.emptyList());
- final String extraRules = keepClassMethod(mainClassName, testMethodSignature);
runTest(
"dataclass",
mainClassName,
testBuilder ->
testBuilder
- .addKeepRules(extraRules)
+ .addKeepRules(keepClassMethod(mainClassName, testMethodSignature))
.addOptionsModification(disableClassInliner))
.inspect(
inspector -> {
- ClassSubject dataClass = checkClassIsKept(inspector, TEST_DATA_CLASS.getClassName());
-
- checkMethodIsRemoved(dataClass, COPY_METHOD);
- checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD);
+ if (testParameters.isDexRuntime()
+ && allowAccessModification
+ && kotlinParameters.is(KotlinCompilerVersion.KOTLINC_1_3_72)) {
+ checkClassIsRemoved(inspector, TEST_DATA_CLASS.getClassName());
+ } else {
+ ClassSubject dataClass =
+ checkClassIsKept(inspector, TEST_DATA_CLASS.getClassName());
+ checkMethodIsRemoved(dataClass, COPY_METHOD);
+ checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD);
+ }
});
}
@Test
public void test_dataclass_copyDefaultIsRemovedIfNotUsed() throws Exception {
- final String mainClassName = "dataclass.MainCopyKt";
- final MethodSignature testMethodSignature =
+ String mainClassName = "dataclass.MainCopyKt";
+ MethodSignature testMethodSignature =
new MethodSignature("testDataClassCopyWithDefault", "void", Collections.emptyList());
- final String extraRules = keepClassMethod(mainClassName, testMethodSignature);
runTest(
"dataclass",
mainClassName,
testBuilder ->
testBuilder
- .addKeepRules(extraRules)
+ .addKeepRules(keepClassMethod(mainClassName, testMethodSignature))
.addOptionsModification(disableClassInliner))
.inspect(
inspector -> {
ClassSubject dataClass = checkClassIsKept(inspector, TEST_DATA_CLASS.getClassName());
-
checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD);
});
}
-
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
index 996dc79..55a048e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
@@ -106,10 +106,8 @@
if (rewrittenString.equals("L;") || rewrittenString.equals("(L;)V")) {
return;
}
- System.out.println(clazzSubject.toString() + ": " + rewrittenString);
addedNonInitStrings.increment();
});
- System.out.flush();
assertEquals(originalHeader.getPackageName(), rewrittenHeader.getPackageName());
String expected = KotlinMetadataWriter.kotlinMetadataToString("", originalMetadata);
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInnerClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInnerClassTest.java
index c38a3d7..48007f1 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInnerClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInnerClassTest.java
@@ -26,17 +26,19 @@
@RunWith(Parameterized.class)
public class MetadataRewriteInnerClassTest extends KotlinMetadataTestBase {
+ private static final String PKG_NESTED_REFLECT = PKG + ".nested_reflect";
private static final String EXPECTED =
StringUtils.lines(
- "fun <init>(kotlin.Int):"
- + " com.android.tools.r8.kotlin.metadata.nested_reflect.Outer.Nested",
- "fun com.android.tools.r8.kotlin.metadata.nested_reflect.Outer.Inner.<init>(kotlin.Int):"
- + " com.android.tools.r8.kotlin.metadata.nested_reflect.Outer.Inner");
+ "fun <init>(kotlin.Int): " + PKG_NESTED_REFLECT + ".Outer.Nested",
+ "fun "
+ + PKG_NESTED_REFLECT
+ + ".Outer.Inner.<init>(kotlin.Int): "
+ + PKG_NESTED_REFLECT
+ + ".Outer.Inner");
private static final String EXPECTED_OUTER_RENAMED =
StringUtils.lines(
- "fun <init>(kotlin.Int): com.android.tools.r8.kotlin.metadata.nested_reflect.Nested",
- "fun <init>(kotlin.Int): com.android.tools.r8.kotlin.metadata.nested_reflect.Inner");
- private static final String PKG_NESTED_REFLECT = PKG + ".nested_reflect";
+ "fun <init>(kotlin.Int): " + PKG_NESTED_REFLECT + ".`Outer$Nested`",
+ "fun <init>(kotlin.Int): " + PKG_NESTED_REFLECT + ".`Outer$Inner`");
private final TestParameters parameters;
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteJvmStaticTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteJvmStaticTest.java
index 605b16c..2c378c6 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteJvmStaticTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteJvmStaticTest.java
@@ -93,7 +93,10 @@
.addProgramFiles(kotlincLibJar.getForConfiguration(kotlinc, targetVersion))
.addClasspathFiles(getKotlinStdlibJar(kotlinc), getKotlinAnnotationJar(kotlinc))
.addKeepAllClassesRule()
- .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .addKeepAttributes(
+ ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS,
+ ProguardKeepAttributes.INNER_CLASSES,
+ ProguardKeepAttributes.ENCLOSING_METHOD)
.compile()
.inspect(this::inspect)
.writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
index 80578a3..44092e7 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
@@ -77,7 +77,10 @@
.setMinApi(parameters.getApiLevel())
.addKeepAllClassesRule()
.addKeepKotlinMetadata()
- .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .addKeepAttributes(
+ ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS,
+ ProguardKeepAttributes.INNER_CLASSES,
+ ProguardKeepAttributes.ENCLOSING_METHOD)
.allowDiagnosticWarningMessages()
.compile()
.assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
index ca441cd..9490133 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
@@ -14,7 +14,6 @@
import com.android.tools.r8.KotlinTestParameters;
import com.android.tools.r8.R8TestRunResult;
-import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
@@ -59,12 +58,11 @@
.addKeepMainRule(mainClassName)
.addKeepKotlinMetadata()
.addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
- .allowDiagnosticMessages()
+ .allowDiagnosticWarningMessages()
.setMinApi(parameters.getApiLevel())
.allowUnusedDontWarnKotlinReflectJvmInternal(kotlinc.isNot(KOTLINC_1_3_72))
.compile()
.assertNoErrorMessages()
- .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
.apply(KotlinMetadataTestBase::verifyExpectedWarningsFromKotlinReflectAndStdLib)
.run(parameters.getRuntime(), mainClassName);
CodeInspector inspector = result.inspector();
diff --git a/src/test/java/com/android/tools/r8/kotlin/optimize/switches/KotlinEnumSwitchTest.java b/src/test/java/com/android/tools/r8/kotlin/optimize/switches/KotlinEnumSwitchTest.java
index 8a27b3f..ca9fbd2 100644
--- a/src/test/java/com/android/tools/r8/kotlin/optimize/switches/KotlinEnumSwitchTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/optimize/switches/KotlinEnumSwitchTest.java
@@ -11,12 +11,8 @@
import static org.junit.Assert.assertNotEquals;
import com.android.tools.r8.KotlinCompilerTool;
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
import com.android.tools.r8.KotlinTestBase;
import com.android.tools.r8.KotlinTestParameters;
-import com.android.tools.r8.R8TestBuilder;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestCompileResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -64,18 +60,10 @@
options.enableEnumValueOptimization = enableSwitchMapRemoval;
options.enableEnumSwitchMapRemoval = enableSwitchMapRemoval;
})
- .applyIf(
- kotlinParameters.getCompiler().is(KotlinCompilerVersion.KOTLINC_1_5_0_M2),
- R8TestBuilder::allowDiagnosticMessages,
- R8TestBuilder::allowDiagnosticWarningMessages)
.setMinApi(parameters.getApiLevel())
.noMinification()
+ .allowDiagnosticWarningMessages()
.compile()
- .assertNoErrorMessages()
- .applyIf(
- kotlinParameters.getCompiler().is(KotlinCompilerVersion.KOTLINC_1_5_0_M2),
- TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation,
- TestCompileResult::assertNoInfoMessages)
.assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
.inspect(
inspector -> {
diff --git a/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassAllowShrinkingCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassAllowShrinkingCompatibilityTest.java
new file mode 100644
index 0000000..5ee49f5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/allowshrinking/KeepClassAllowShrinkingCompatibilityTest.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2021, 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.allowshrinking;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+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 KeepClassAllowShrinkingCompatibilityTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final Shrinker shrinker;
+
+ @Parameters(name = "{0}, {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(), ImmutableList.of(Shrinker.R8, Shrinker.PG));
+ }
+
+ public KeepClassAllowShrinkingCompatibilityTest(TestParameters parameters, Shrinker shrinker) {
+ this.parameters = parameters;
+ this.shrinker = shrinker;
+ }
+
+ @Test
+ public void test() throws Exception {
+ if (shrinker.isPG()) {
+ run(testForProguard(shrinker.getProguardVersion()).addDontWarn(getClass()));
+ } else {
+ run(testForR8(parameters.getBackend()));
+ }
+ }
+
+ private <T extends TestShrinkerBuilder<?, ?, ?, ?, T>> void run(T builder) throws Exception {
+ builder
+ .addProgramClasses(A.class, B.class, Main.class)
+ .addKeepMainRule(Main.class)
+ .addKeepRules("-keep,allowshrinking" + " class " + A.class.getTypeName())
+ .addKeepRules("-keep,allowshrinking" + " class " + B.class.getTypeName())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject aSubject = inspector.clazz(A.class);
+ assertThat(aSubject, isPresent());
+ ClassSubject bSubject = inspector.clazz(B.class);
+ assertThat(bSubject, isPresent());
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithEmptyOutput();
+ }
+
+ static class A {
+ public A(int i) {
+ // Non-default constructor to ensure no soft pinning of a members.
+ if (i == 42) {
+ throw new RuntimeException("Foo");
+ }
+ }
+ }
+
+ static class B {
+ public B(int i) {
+ // Non-default constructor to ensure no soft pinning of a members.
+ if (i == 42) {
+ throw new RuntimeException("Foo");
+ }
+ }
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ new A(args.length);
+ new B(args.length);
+ }
+ }
+}
diff --git a/third_party/internal-apps/youtube_15_33.tar.gz.sha1 b/third_party/internal-apps/youtube_15_33.tar.gz.sha1
index ad38c2b..9a04d6e 100644
--- a/third_party/internal-apps/youtube_15_33.tar.gz.sha1
+++ b/third_party/internal-apps/youtube_15_33.tar.gz.sha1
@@ -1 +1 @@
-a992d1cdf6b957a6e4b06361ea1b8edbaecc3088
\ No newline at end of file
+907babdaf04052eed13f22400175307131df1610
\ No newline at end of file
diff --git a/third_party/youtube/youtube.android_16.20.tar.gz.sha1 b/third_party/youtube/youtube.android_16.20.tar.gz.sha1
new file mode 100644
index 0000000..9e8e118
--- /dev/null
+++ b/third_party/youtube/youtube.android_16.20.tar.gz.sha1
@@ -0,0 +1 @@
+5e5ebc2236eae74b22886c51f46a88963f689efd
\ No newline at end of file
diff --git a/tools/create_jctf_tests.py b/tools/create_jctf_tests.py
index bc065d9..38e1f9c 100755
--- a/tools/create_jctf_tests.py
+++ b/tools/create_jctf_tests.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
# for details. All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.
@@ -9,7 +9,7 @@
from os import makedirs
from os.path import exists, join, dirname
from shutil import rmtree
-from string import Template, upper
+from string import Template
import os
import re
import sys
diff --git a/tools/download_from_x20.py b/tools/download_from_x20.py
index 5f64684..5796823 100755
--- a/tools/download_from_x20.py
+++ b/tools/download_from_x20.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
# for details. All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.
@@ -20,7 +20,7 @@
return optparse.OptionParser().parse_args()
def download(src, dest):
- print 'Downloading %s to %s' % (src, dest)
+ print('Downloading %s to %s' % (src, dest))
shutil.copyfile(src, dest)
utils.unpack_archive(dest)
@@ -29,21 +29,21 @@
assert len(args) == 1
sha1_file = args[0]
dest = sha1_file[:-5]
- print 'Ensuring %s' % dest
+ print('Ensuring %s' % dest)
with open(sha1_file, 'r') as input_sha:
sha1 = input_sha.readline()
if os.path.exists(dest) and utils.get_sha1(dest) == sha1:
- print 'sha1 matches, not downloading'
+ print('sha1 matches, not downloading')
dest_dir = utils.extract_dir(dest)
if os.path.exists(dest_dir):
- print 'destination directory exists, no extraction'
+ print('destination directory exists, no extraction')
else:
utils.unpack_archive(dest)
return
src = os.path.join(GMSCORE_DEPS, sha1)
if not os.path.exists(src):
- print 'File (%s) does not exist on x20' % src
- print 'Maybe pass -Pno_internal to your gradle invocation'
+ print('File (%s) does not exist on x20' % src)
+ print('Maybe pass -Pno_internal to your gradle invocation')
return 42
download(src, dest)
diff --git a/tools/run_on_app_dump.py b/tools/run_on_app_dump.py
index 9e729fe..6acce42 100755
--- a/tools/run_on_app_dump.py
+++ b/tools/run_on_app_dump.py
@@ -503,10 +503,12 @@
def get_results_for_app(app, options, temp_dir):
app_folder = app.folder if app.folder else app.name + "_" + app.revision
+ # Golem extraction will extract to the basename under the benchmarks dir.
+ app_location = os.path.basename(app_folder) if options.golem else app_folder
opensource_basedir = (os.path.join('benchmarks', app.name) if options.golem
else utils.OPENSOURCE_DUMPS_DIR)
- app_dir = (os.path.join(utils.INTERNAL_DUMPS_DIR, app_folder) if app.internal
- else os.path.join(opensource_basedir, app_folder))
+ app_dir = (os.path.join(utils.INTERNAL_DUMPS_DIR, app_location) if app.internal
+ else os.path.join(opensource_basedir, app_location))
if not os.path.exists(app_dir) and not options.golem:
# Download the app from google storage.
download_sha(app_dir + ".tar.gz.sha1", app.internal)
@@ -1006,6 +1008,7 @@
jdk_gz = jdk.GetJdkHome() + '.tar.gz'
download_sha(jdk_gz + '.sha1', False, quiet=True)
jdk_sha256 = get_sha256(jdk_gz)
+ add_golem_resource(2, jdk_gz, 'openjdk', sha256=jdk_sha256)
for app in options.apps:
if app.folder and not app.internal:
indentation = 2;
@@ -1022,13 +1025,16 @@
print_indented('options.cpus = cpus;', indentation)
print_indented('options.isScript = true;', indentation)
print_indented('options.fromRevision = 9700;', indentation);
- print_indented('options.mainFile = "tools/run_on_app_dump.py "', indentation)
- print_indented('"--golem --shrinker r8 --app %s";' % app.name, indentation + 4)
+ print_indented('options.mainFile = "tools/run_on_app_dump.py "',
+ indentation)
+ print_indented('"--golem --quiet --shrinker r8 --app %s";' % app.name,
+ indentation + 4)
app_gz = os.path.join(utils.OPENSOURCE_DUMPS_DIR, app.folder + '.tar.gz')
- add_golem_resource(indentation, app_gz, 'app_resource')
- add_golem_resource(indentation, jdk_gz, 'openjdk', sha256=jdk_sha256)
-
+ name = 'appResource'
+ add_golem_resource(indentation, app_gz, name)
+ print_indented('options.resources.add(appResource);', indentation)
+ print_indented('options.resources.add(openjdk);', indentation)
print_indented('dumpsSuite.addBenchmark(name);', indentation)
indentation = 2
print_indented('}', indentation)
@@ -1044,10 +1050,13 @@
print_indented('final %s = BenchmarkResource("",' % name, indentation)
print_indented('type: BenchmarkResourceType.Storage,', indentation + 4)
print_indented('uri: "gs://r8-deps/%s",' % sha, indentation + 4)
- print_indented('hash:', indentation + 4)
- print_indented('"%s",' % sha256, indentation + 8)
+ # Make dart formatter happy.
+ if indentation > 2:
+ print_indented('hash:', indentation + 4)
+ print_indented('"%s",' % sha256, indentation + 8)
+ else:
+ print_indented('hash: "%s",' % sha256, indentation + 4)
print_indented('extract: "gz");', indentation + 4);
- print_indented('options.resources.add(%s);' % name, indentation)
def main(argv):
(options, args) = parse_options(argv)
diff --git a/tools/tag_versions.py b/tools/tag_versions.py
new file mode 100755
index 0000000..ebf14ba
--- /dev/null
+++ b/tools/tag_versions.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python3
+# Copyright (c) 2021, 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 argparse
+import jdk
+import os.path
+import re
+import subprocess
+import sys
+import urllib.request
+
+import utils
+
+# Grep match string for 'Version X.Y.Z[-dev]'
+VERSION_EXP='^Version [[:digit:]]\+.[[:digit:]]\+.[[:digit:]]\+\(\|-dev\)$'
+
+# R8 is located in the 'builder' library.
+AGP_MAVEN="https://dl.google.com/android/maven2/com/android/tools/build/builder"
+
+def parse_options():
+ parser = argparse.ArgumentParser(description='Tag R8 Versions')
+ parser.add_argument(
+ '--branch',
+ help='The R8 branch to tag versions on, eg, origin/3.0')
+ parser.add_argument(
+ '--agp',
+ help='The AGP to compute the tag for, eg, 4.2.0-beta03')
+ parser.add_argument(
+ '--dry-run',
+ default=False,
+ action='store_true',
+ help='Print the state changing commands without running them.')
+ return parser.parse_args()
+
+def run(options, cmd):
+ print(' '.join(cmd))
+ if not options.dry_run:
+ subprocess.check_call(cmd)
+
+def main():
+ args = parse_options()
+ if args.branch:
+ tag_r8_branch(args.branch, args)
+ elif args.agp:
+ tag_agp_version(args.agp, args)
+ else:
+ print("Should use a top-level option, such as --branch or --agp.")
+ return 1
+ return 0
+
+def prepare_print_version(dist, temp):
+ wrapper_file = os.path.join(
+ utils.REPO_ROOT,
+ 'src/main/java/com/android/tools/r8/utils/PrintR8Version.java')
+ cmd = [
+ jdk.GetJavacExecutable(),
+ wrapper_file,
+ '-d', temp,
+ '-cp', dist,
+ ]
+ utils.PrintCmd(cmd)
+ subprocess.check_output(cmd)
+ return temp
+
+def get_tag_info_on_origin(tag):
+ output = subprocess.check_output(
+ ['git', 'ls-remote', '--tags', 'origin', tag]).decode('utf-8')
+ if len(output.strip()) == 0:
+ return None
+ return output
+
+def tag_agp_version(agp, args):
+ tag = 'agp-%s' % agp
+ result = get_tag_info_on_origin(tag)
+ if result:
+ print('Tag %s is already present' % tag)
+ print(result)
+ subprocess.call(['git', 'show', '--oneline', '-s', tag])
+ return 0
+ with utils.TempDir() as temp:
+ url = "%s/%s/builder-%s.jar" % (AGP_MAVEN, agp, agp)
+ jar = os.path.join(temp, "agp.jar")
+ try:
+ urllib.request.urlretrieve(url, jar)
+ except urllib.error.HTTPError as e:
+ print('Could not find jar for agp %s' % agp)
+ print(e)
+ return 1
+ print_version_helper = prepare_print_version(utils.R8_JAR, temp)
+ output = subprocess.check_output([
+ jdk.GetJavaExecutable(),
+ '-cp', ':'.join([jar, print_version_helper]),
+ 'com.android.tools.r8.utils.PrintR8Version'
+ ]).decode('utf-8')
+ version = output.split(' ')[0]
+ run(args, ['git', 'tag', '-f', tag, '-m', tag, '%s^{}' % version])
+ run(args, ['git', 'push', 'origin', tag])
+
+def tag_r8_branch(branch, args):
+ if not branch.startswith('origin/'):
+ print('Expected branch to start with origin/')
+ return 1
+ output = subprocess.check_output([
+ 'git', 'log', '--pretty=format:%H\t%s', '--grep', VERSION_EXP, branch
+ ]).decode('utf-8')
+ for l in output.split('\n'):
+ (hash, subject) = l.split('\t')
+ m = re.search('Version (.+)', subject)
+ if not m:
+ print('Unable to find a version for line: %s' % l)
+ continue
+ version = m.group(1)
+ result = get_tag_info_on_origin(version)
+ if not result:
+ run(args, ['git', 'tag', '-a', version, '-m', version, hash])
+ run(args, ['git', 'push', 'origin', version])
+ if args.dry_run:
+ print('Dry run complete. None of the above have been executed.')
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/tools/youtube_data.py b/tools/youtube_data.py
index d12cfb6..827041a 100644
--- a/tools/youtube_data.py
+++ b/tools/youtube_data.py
@@ -15,7 +15,10 @@
V16_12_BASE = os.path.join(BASE, 'youtube.android_16.12')
V16_12_PREFIX = os.path.join(V16_12_BASE, 'YouTubeRelease')
-LATEST_VERSION = '16.12'
+V16_20_BASE = os.path.join(BASE, 'youtube.android_16.20')
+V16_20_PREFIX = os.path.join(V16_20_BASE, 'YouTubeRelease')
+
+LATEST_VERSION = '16.20'
VERSIONS = {
'15.33': {
@@ -80,4 +83,35 @@
'min-api' : ANDROID_L_API,
}
},
+ '16.20': {
+ 'deploy' : {
+ 'sanitize_libraries': False,
+ 'inputs': ['%s_deploy.jar' % V16_20_PREFIX],
+ 'libraries' : [
+ os.path.join(
+ V16_20_BASE,
+ 'legacy_YouTubeRelease_combined_library_jars_filtered.jar')],
+ 'pgconf': [
+ '%s_proguard.config' % V16_20_PREFIX,
+ '%s/proguardsettings/YouTubeRelease_proguard.config' % utils.THIRD_PARTY,
+ utils.IGNORE_WARNINGS_RULES],
+ 'min-api' : ANDROID_L_API,
+ 'android_java8_libs': {
+ 'config': '%s/desugar_jdk_libs/full_desugar_jdk_libs.json' % V16_20_BASE,
+ # Intentionally not adding desugar_jdk_libs_configuration.jar since it
+ # is part of jdk_libs_to_desugar.jar in YouTube 16.20.
+ 'program': ['%s/desugar_jdk_libs/jdk_libs_to_desugar.jar' % V16_20_BASE],
+ 'library': '%s/android_jar/lib-v30/android.jar' % utils.THIRD_PARTY,
+ 'pgconf': [
+ '%s/desugar_jdk_libs/base.pgcfg' % V16_20_BASE,
+ '%s/desugar_jdk_libs/minify_desugar_jdk_libs.pgcfg' % V16_20_BASE
+ ]
+ }
+ },
+ 'proguarded' : {
+ 'inputs': ['%s_proguard.jar' % V16_20_PREFIX],
+ 'pgmap': '%s_proguard.map' % V16_20_PREFIX,
+ 'min-api' : ANDROID_L_API,
+ }
+ },
}