Merge commit 'c9bdfe52acf8e16d98f6660f860f3da4216a8ebd' into dev-release
Change-Id: Ie3885b32d2d9a58d27444b28ba81015c229b0811
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexTypeAnnotation.java
index c343889..1a2a9e2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexTypeAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeAnnotation.java
@@ -34,7 +34,8 @@
@Override
public void collectIndexedItems(AppView<?> appView, IndexedItemCollection indexedItems) {
- throw new Unreachable("Should not collect type annotation in DEX");
+ assert appView.options().isGeneratingClassFiles() : "Should not collect type annotation in DEX";
+ super.collectIndexedItems(appView, indexedItems);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/graph/PrunedItems.java b/src/main/java/com/android/tools/r8/graph/PrunedItems.java
index 3e96a43..00a3744 100644
--- a/src/main/java/com/android/tools/r8/graph/PrunedItems.java
+++ b/src/main/java/com/android/tools/r8/graph/PrunedItems.java
@@ -21,7 +21,6 @@
private final DexApplication prunedApp;
private final Set<DexReference> additionalPinnedItems;
private final Map<DexMethod, ProgramMethod> fullyInlinedMethods;
- private final Set<DexType> noLongerSyntheticItems;
private final Set<DexType> removedClasses;
private final Set<DexField> removedFields;
private final Set<DexMethod> removedMethods;
@@ -30,14 +29,12 @@
DexApplication prunedApp,
Set<DexReference> additionalPinnedItems,
Map<DexMethod, ProgramMethod> fullyInlinedMethods,
- Set<DexType> noLongerSyntheticItems,
Set<DexType> removedClasses,
Set<DexField> removedFields,
Set<DexMethod> removedMethods) {
this.prunedApp = prunedApp;
this.additionalPinnedItems = additionalPinnedItems;
this.fullyInlinedMethods = fullyInlinedMethods;
- this.noLongerSyntheticItems = noLongerSyntheticItems;
this.removedClasses = removedClasses;
this.removedFields = removedFields;
this.removedMethods = removedMethods;
@@ -62,7 +59,6 @@
public boolean isEmpty() {
return additionalPinnedItems.isEmpty()
&& fullyInlinedMethods.isEmpty()
- && noLongerSyntheticItems.isEmpty()
&& removedClasses.isEmpty()
&& removedFields.isEmpty()
&& removedMethods.isEmpty();
@@ -105,10 +101,6 @@
return fullyInlinedMethods;
}
- public Set<DexType> getNoLongerSyntheticItems() {
- return noLongerSyntheticItems;
- }
-
public boolean hasRemovedClasses() {
return !removedClasses.isEmpty();
}
@@ -143,7 +135,6 @@
private final Set<DexReference> additionalPinnedItems;
private Map<DexMethod, ProgramMethod> fullyInlinedMethods;
- private final Set<DexType> noLongerSyntheticItems;
private Set<DexType> removedClasses;
private final Set<DexField> removedFields;
private Set<DexMethod> removedMethods;
@@ -151,7 +142,6 @@
Builder() {
additionalPinnedItems = newEmptySet();
fullyInlinedMethods = newEmptyMap();
- noLongerSyntheticItems = newEmptySet();
removedClasses = newEmptySet();
removedFields = newEmptySet();
removedMethods = newEmptySet();
@@ -161,7 +151,6 @@
this();
assert prunedItems.getFullyInlinedMethods().isEmpty();
additionalPinnedItems.addAll(prunedItems.getAdditionalPinnedItems());
- noLongerSyntheticItems.addAll(prunedItems.getNoLongerSyntheticItems());
prunedApp = prunedItems.getPrunedApp();
removedClasses.addAll(prunedItems.getRemovedClasses());
removedFields.addAll(prunedItems.getRemovedFields());
@@ -202,19 +191,12 @@
fullyInlinedMethods.clear();
}
- public Builder addNoLongerSyntheticItems(Set<DexType> noLongerSyntheticItems) {
- this.noLongerSyntheticItems.addAll(noLongerSyntheticItems);
- return this;
- }
-
public Builder addRemovedClass(DexType removedClass) {
- this.noLongerSyntheticItems.add(removedClass);
this.removedClasses.add(removedClass);
return this;
}
public Builder addRemovedClasses(Set<DexType> removedClasses) {
- this.noLongerSyntheticItems.addAll(removedClasses);
this.removedClasses.addAll(removedClasses);
return this;
}
@@ -265,7 +247,6 @@
prunedApp,
additionalPinnedItems,
fullyInlinedMethods,
- noLongerSyntheticItems,
removedClasses,
removedFields,
removedMethods);
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/GetArrayOfMissingTypeVerifyErrorWorkaround.java b/src/main/java/com/android/tools/r8/graph/analysis/GetArrayOfMissingTypeVerifyErrorWorkaround.java
index bace0d4..35031ff 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/GetArrayOfMissingTypeVerifyErrorWorkaround.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/GetArrayOfMissingTypeVerifyErrorWorkaround.java
@@ -71,7 +71,7 @@
ProgramMethod context,
EnqueuerWorklist worklist) {
if (isUnsafeToUseFieldOnDalvik(field)) {
- enqueuer.getKeepInfo().joinMethod(context, Joiner::disallowOptimization);
+ enqueuer.mutateKeepInfo(context, (k, m) -> k.joinMethod(m, Joiner::disallowOptimization));
}
}
@@ -82,7 +82,7 @@
ProgramMethod context,
EnqueuerWorklist worklist) {
if (isUnsafeToUseFieldOnDalvik(field)) {
- enqueuer.getKeepInfo().joinMethod(context, Joiner::disallowOptimization);
+ enqueuer.mutateKeepInfo(context, (k, m) -> k.joinMethod(m, Joiner::disallowOptimization));
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/InvokeVirtualToInterfaceVerifyErrorWorkaround.java b/src/main/java/com/android/tools/r8/graph/analysis/InvokeVirtualToInterfaceVerifyErrorWorkaround.java
index b5bf374..85ef04b 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/InvokeVirtualToInterfaceVerifyErrorWorkaround.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/InvokeVirtualToInterfaceVerifyErrorWorkaround.java
@@ -59,7 +59,7 @@
public void traceInvokeVirtual(
DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context) {
if (isInterfaceInSomeApiLevel(invokedMethod.getHolderType())) {
- enqueuer.getKeepInfo().joinMethod(context, Joiner::disallowOptimization);
+ enqueuer.mutateKeepInfo(context, (k, m) -> k.joinMethod(m, Joiner::disallowOptimization));
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
index 902f858..f970b80 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
@@ -55,8 +55,15 @@
}
@Override
- public boolean equals(Object o) {
- return this == o;
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof SingleConstClassValue)) {
+ return false;
+ }
+ SingleConstClassValue other = (SingleConstClassValue) obj;
+ return type.isIdenticalTo(other.type);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
index 11eebdb..64d3845 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
@@ -140,13 +140,20 @@
}
@Override
- public boolean equals(Object o) {
- return this == o;
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof SingleNumberValue)) {
+ return false;
+ }
+ SingleNumberValue other = (SingleNumberValue) obj;
+ return value == other.value;
}
@Override
public int hashCode() {
- return System.identityHashCode(this);
+ return 31 * (31 + Long.hashCode(value)) + getClass().hashCode();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleResourceNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleResourceNumberValue.java
index 8be7c10..4a81b1f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleResourceNumberValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleResourceNumberValue.java
@@ -49,13 +49,20 @@
}
@Override
- public boolean equals(Object o) {
- return this == o;
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof SingleResourceNumberValue)) {
+ return false;
+ }
+ SingleResourceNumberValue other = (SingleResourceNumberValue) obj;
+ return value == other.value;
}
@Override
public int hashCode() {
- return System.identityHashCode(this);
+ return 31 * (31 + value) + getClass().hashCode();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
index 3257ca6..0b4af4d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
@@ -51,8 +51,15 @@
}
@Override
- public boolean equals(Object o) {
- return this == o;
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof SingleStringValue)) {
+ return false;
+ }
+ SingleStringValue other = (SingleStringValue) obj;
+ return string.isIdenticalTo(other.string);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/KnownLengthArrayState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/KnownLengthArrayState.java
index ca07bf0..0b328ea 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/KnownLengthArrayState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/KnownLengthArrayState.java
@@ -52,13 +52,20 @@
}
@Override
- public boolean equals(Object o) {
- return this == o;
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof KnownLengthArrayState)) {
+ return false;
+ }
+ KnownLengthArrayState other = (KnownLengthArrayState) obj;
+ return length == other.length;
}
@Override
public int hashCode() {
- return System.identityHashCode(this);
+ return 31 * (31 + length) + getClass().hashCode();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
index cbe7634..9492af5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.desugar.itf;
+import static com.android.tools.r8.graph.InvalidCode.isInvalidCode;
import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodDesugaringEventConsumer.emptyInterfaceMethodDesugaringEventConsumer;
import com.android.tools.r8.cf.CfVersion;
@@ -558,14 +559,21 @@
assert method.getHolder().isInterface();
assert definition.isNonAbstractNonNativeMethod();
assert definition.getCode() != null;
- assert !InvalidCode.isInvalidCode(definition.getCode());
if (definition.isStatic()) {
+ assert !isInvalidCode(definition.getCode())
+ || appView.definitionFor(staticAsMethodOfCompanionClass(method)) != null;
return ensureStaticAsMethodOfProgramCompanionClassStub(method, eventConsumer);
- }
- if (definition.isPrivate()) {
+ } else if (definition.isPrivate()) {
+ assert !isInvalidCode(definition.getCode())
+ || appView.definitionFor(privateAsMethodOfCompanionClass(method)) != null;
return ensurePrivateAsMethodOfProgramCompanionClassStub(method, eventConsumer);
+ } else {
+ assert !isInvalidCode(definition.getCode())
+ || appView.definitionFor(
+ defaultAsMethodOfCompanionClass(method.getReference(), appView.dexItemFactory()))
+ != null;
+ return ensureDefaultAsMethodOfProgramCompanionClassStub(method, eventConsumer);
}
- return ensureDefaultAsMethodOfProgramCompanionClassStub(method, eventConsumer);
}
private void ensureCompanionClassInitializesInterface(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ResourceGetOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ResourceGetOptimizer.java
index d0bfd83..e2cd8dc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/ResourceGetOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ResourceGetOptimizer.java
@@ -58,7 +58,7 @@
// TODO(b/312406163): Allow androidx classes that overwrite this, but don't change the value
// or have side effects.
allowStringInlining = Optional.of(true);
- allowColorInlining = Optional.of(true);
+ allowColorInlining = Optional.of(appView.options().enableColorInlining);
Map<DexClass, Boolean> cachedResults = new IdentityHashMap<>();
for (DexClass clazz :
Iterables.concat(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java b/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
index 7bda442..1512007 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
@@ -85,7 +85,6 @@
}
}
- @SuppressWarnings("ReferenceEquality")
private void processCandidateForInstanceOfOptimization(
ProgramMethod method, PrimaryMethodProcessor methodProcessor) {
DexEncodedMethod definition = method.getDefinition();
@@ -132,7 +131,7 @@
}
DexEncodedMethod parentMethodDefinition = parentMethod.getDefinition();
- if (abstractParentReturnValue == abstractReturnValue) {
+ if (abstractParentReturnValue.equals(abstractReturnValue)) {
// The parent method is already guaranteed to return the same value.
} else if (isClassAccessible(method.getHolder(), parentMethod, appView).isTrue()) {
parentMethod.setCode(
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index b20df20..e18fa27 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -229,7 +229,7 @@
builder -> {
if (!targetDefinition.isAbstract()
&& targetDefinition.getApiLevelForCode().isNotSetApiLevel()) {
- assert target.isLibraryMethod()
+ assert !target.isProgramMethod()
|| !appView.options().apiModelingOptions().isApiModelingEnabled();
builder.setApiLevelForCode(
appView
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
index d328deb..a446d35 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.shaking;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static com.android.tools.r8.graph.UseRegistry.MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY;
import static com.android.tools.r8.ir.desugar.constantdynamic.LibraryConstantDynamic.dispatchEnumDescConstantDynamic;
import static com.android.tools.r8.ir.desugar.constantdynamic.LibraryConstantDynamic.isEnumDescConstantDynamic;
@@ -17,7 +18,6 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexCallSite;
-import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -287,15 +287,14 @@
}
private void registerEnumReferencedInTypeSwitchBootstrapArguments(DexType enumType) {
- DexClass dexClass = appView.definitionFor(enumType);
- if (dexClass == null || dexClass.isNotProgramClass()) {
+ DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(enumType));
+ if (clazz == null) {
return;
}
// The enum class cannot be unboxed or class merged. It can however be renamed.
- enqueuer
- .getKeepInfo()
- .joinClass(
- dexClass.asProgramClass(), joiner -> joiner.disallowOptimization().disallowShrinking());
+ enqueuer.mutateKeepInfo(
+ clazz,
+ (k, c) -> k.joinClass(c, joiner -> joiner.disallowOptimization().disallowShrinking()));
DexItemFactory factory = dexItemFactory();
DexMethod values =
factory.createMethod(
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 f8116f2..f9980b1 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -145,6 +145,7 @@
import com.android.tools.r8.utils.Action;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.CollectionUtils;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.IteratorUtils;
@@ -167,6 +168,7 @@
import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
@@ -250,6 +252,7 @@
private AppInfoWithClassHierarchy appInfo;
private final AppView<AppInfoWithClassHierarchy> appView;
private final EnqueuerDeferredTracing deferredTracing;
+ private final EnqueuerDeferredAnnotationTracing deferredAnnotationTracing;
private final ExecutorService executorService;
private ImmediateAppSubtypingInfo subtypingInfo;
private final InternalOptions options;
@@ -391,7 +394,7 @@
private final Map<DexProgramClass, Map<ResolutionSearchKey, ProgramMethodSet>>
reachableVirtualTargets = new IdentityHashMap<>();
- /** Collection of keep requirements for the program. */
+ /** Collection of keep requirements for the program. Must only be accessed through its getters. */
private final MutableKeepInfoCollection keepInfo;
/**
@@ -419,23 +422,6 @@
*/
private final Set<DexMethod> recordFieldValuesReferences = Sets.newIdentityHashSet();
- /** Set of annotations that are live (needed for deferred (re)processing). */
- private final Set<DexType> liveAnnotations = Sets.newIdentityHashSet();
-
- /**
- * A map from annotation classes to annotations that need to be processed should the classes ever
- * become live.
- */
- private final Map<DexType, Map<DexAnnotation, List<ProgramDefinition>>> deferredAnnotations =
- new IdentityHashMap<>();
-
- /**
- * A map from annotation classes to parameter annotations that need to be processed should the
- * classes ever become live.
- */
- private final Map<DexType, Map<DexAnnotation, List<ProgramDefinition>>>
- deferredParameterAnnotations = new IdentityHashMap<>();
-
/**
* A cache of ScopedDexMethodSet for each live type used for determining that virtual methods that
* cannot be removed because they are widening access for another virtual method defined earlier
@@ -502,6 +488,7 @@
this.mode = mode;
this.profileCollectionAdditions = profileCollectionAdditions;
this.deferredTracing = EnqueuerDeferredTracing.create(appView, this, mode);
+ this.deferredAnnotationTracing = new EnqueuerDeferredAnnotationTracing(this);
this.executorService = executorService;
this.subtypingInfo = subtypingInfo;
this.forceProguardCompatibility = options.forceProguardCompatibility;
@@ -805,10 +792,16 @@
return fieldAccessInfoCollection;
}
- public MutableKeepInfoCollection getKeepInfo() {
+ public KeepInfoCollection getKeepInfo() {
return keepInfo;
}
+ public <T extends ProgramDefinition> void mutateKeepInfo(
+ T itemToChange, BiConsumer<MutableKeepInfoCollection, T> consumer) {
+ consumer.accept(keepInfo, itemToChange);
+ deferredAnnotationTracing.notifyKeepInfoChanged(itemToChange);
+ }
+
public KeepClassInfo getKeepInfo(DexProgramClass clazz) {
return keepInfo.getClassInfo(clazz);
}
@@ -1839,6 +1832,76 @@
}
}
+ void traceAnnotationFieldAccess(
+ DexField fieldReference, DexAnnotation annotation, ProgramDefinition context) {
+ recordFieldReference(fieldReference, context);
+ DexProgramClass holder = getProgramHolderOrNull(fieldReference, context);
+ if (holder == null) {
+ return;
+ }
+ ProgramField field = holder.lookupProgramField(fieldReference);
+ if (field == null) {
+ return;
+ }
+ // There is no dispatch on annotations, so only keep what is directly referenced.
+ if (field.getReference().isNotIdenticalTo(fieldReference)) {
+ return;
+ }
+ KeepReason reason = KeepReason.referencedInAnnotation(annotation, context);
+ if (field.getDefinition().isStatic()) {
+ FieldAccessInfoImpl fieldAccessInfo =
+ fieldAccessInfoCollection.contains(fieldReference)
+ ? fieldAccessInfoCollection.get(fieldReference)
+ : fieldAccessInfoCollection.extend(
+ fieldReference, new FieldAccessInfoImpl(fieldReference));
+ fieldAccessInfo.setReadFromAnnotation();
+ markFieldAsLive(field, context, reason);
+ // In a class file an enum reference in an annotation is written as enum descriptor and
+ // enum name. At runtime the JVM use valueOf on the enum class with the name to get the
+ // instance. This indirectly use the values() method on that enum class. Also keep the
+ // name of the field and the name of the enum in sync as otherwise recovering the field to
+ // name relationship requires analysis of the enum <clinit> when this CF code is processed
+ // again (e.g. as input to D8 for converting to DEX). See b/236691999 for more info.
+ if (options.isGeneratingClassFiles() && field.getHolder().isEnum()) {
+ markEnumValuesAsReachable(field.getHolder(), reason);
+ applyMinimumKeepInfoWhenLive(field, KeepFieldInfo.newEmptyJoiner().disallowMinification());
+ }
+ } else {
+ // There is no dispatch on annotations, so only keep what is directly referenced.
+ worklist.enqueueMarkFieldAsReachableAction(field, context, reason);
+ }
+ }
+
+ void traceAnnotationMethodAccess(
+ DexMethod method, DexAnnotation annotation, ProgramDefinition context) {
+ // Record the references in case they are not program types.
+ recordMethodReference(method, context);
+ DexProgramClass holder = getProgramHolderOrNull(method, context);
+ if (holder == null) {
+ return;
+ }
+ KeepReason reason = KeepReason.referencedInAnnotation(annotation, context);
+ DexEncodedMethod target = holder.lookupDirectMethod(method);
+ if (target != null) {
+ // There is no dispatch on annotations, so only keep what is directly referenced.
+ if (target.getReference().isIdenticalTo(method)) {
+ markDirectStaticOrConstructorMethodAsLive(new ProgramMethod(holder, target), reason);
+ }
+ } else {
+ target = holder.lookupVirtualMethod(method);
+ // There is no dispatch on annotations, so only keep what is directly referenced.
+ if (target != null && target.getReference().isIdenticalTo(method)) {
+ markMethodAsTargeted(new ProgramMethod(holder, target), reason);
+ }
+ }
+ }
+
+ void traceAnnotationTypeAccess(
+ DexType type, DexAnnotation annotation, ProgramDefinition context) {
+ KeepReason reason = KeepReason.referencedInAnnotation(annotation, context);
+ markTypeAsLive(type, context, reason);
+ }
+
void traceInstanceFieldRead(
DexField fieldReference, ProgramMethod currentMethod, FieldAccessMetadata metadata) {
traceInstanceFieldAccess(
@@ -1962,7 +2025,7 @@
appView.withGeneratedExtensionRegistryShrinker(
shrinker ->
shrinker.isDeadProtoExtensionField(
- resolutionResult, fieldAccessInfoCollection, keepInfo),
+ resolutionResult, fieldAccessInfoCollection, getKeepInfo()),
false);
if (skipTracing) {
addDeadProtoTypeCandidate(resolutionResult.getSingleProgramField().getHolder());
@@ -2221,13 +2284,13 @@
applyMinimumKeepInfo(clazz);
applyMinimumKeepInfoDependentOn(new LiveClassEnqueuerEvent(clazz));
if (hasAlternativeLibraryDefinition(clazz)) {
- getKeepInfo().keepClass(clazz);
+ mutateKeepInfo(clazz, MutableKeepInfoCollection::keepClass);
}
processAnnotations(clazz);
if (clazz.isAnnotation()) {
- liveAnnotations.add(clazz.getType());
+ deferredAnnotationTracing.notifyNewlyLiveAnnotation(clazz);
}
compatEnqueueHolderIfDependentNonStaticMember(
@@ -2236,11 +2299,12 @@
analyses.processNewlyLiveClass(clazz, worklist);
}
- private void processOnClickMethods() {
+ private void processOnClickMethods(Timing timing) {
if (onClickMethodReferences.isEmpty()) {
return;
}
- for (DexProgramClass item : liveTypes.items) {
+ timing.begin("Process onclick methods");
+ for (DexProgramClass item : liveTypes.getItems()) {
for (ProgramMethod method :
item.virtualProgramMethods(
p ->
@@ -2248,7 +2312,7 @@
&& p.getParameter(0)
.isIdenticalTo(appInfo.dexItemFactory().androidViewViewType)
&& onClickMethodReferences.containsKey(p.getName()))) {
- KeepMethodInfo methodInfo = keepInfo.getMethodInfo(method);
+ KeepMethodInfo methodInfo = getKeepInfo().getMethodInfo(method);
if (!methodInfo.isOptimizationAllowed(options)
&& !methodInfo.isShrinkingAllowed(options)
&& !methodInfo.isMinificationAllowed(options)) {
@@ -2265,30 +2329,7 @@
applyMinimumKeepInfo(method, minimumKeepInfo);
}
}
- }
-
- private void processDeferredAnnotations(
- Map<DexType, Map<DexAnnotation, List<ProgramDefinition>>> deferredAnnotations,
- Function<ProgramDefinition, AnnotatedKind> kindProvider) {
- // Collect annotations to process as processing the annotation can modify liveAnnotations.
- Set<Map<DexAnnotation, List<ProgramDefinition>>> toProcess = Sets.newIdentityHashSet();
- for (DexType annotationType : liveAnnotations) {
- Map<DexAnnotation, List<ProgramDefinition>> annotations =
- deferredAnnotations.remove(annotationType);
- if (annotations != null) {
- assert annotations.keySet().stream()
- .allMatch(annotation -> annotationType.isIdenticalTo(annotation.getAnnotationType()));
- toProcess.add(annotations);
- }
- }
- toProcess.forEach(
- annotations ->
- annotations.forEach(
- (annotation, annotatedItems) ->
- annotatedItems.forEach(
- annotatedItem ->
- processAnnotation(
- annotatedItem, annotation, kindProvider.apply(annotatedItem)))));
+ timing.end();
}
private void ensureMethodsContinueToWidenAccess(ClassDefinition clazz) {
@@ -2412,25 +2453,36 @@
void processAnnotation(
ProgramDefinition annotatedItem, DexAnnotation annotation, AnnotatedKind kind) {
DexType type = annotation.getAnnotationType();
- DexClass clazz = definitionFor(type, annotatedItem);
- boolean annotationTypeIsNotProgramClass = clazz == null || clazz.isNotProgramClass();
- boolean isLive = annotationTypeIsNotProgramClass || liveTypes.contains(clazz.asProgramClass());
- if (!shouldKeepAnnotation(annotatedItem, annotation, kind, isLive)) {
- // Remember this annotation for later.
- Map<DexType, Map<DexAnnotation, List<ProgramDefinition>>> deferredAnnotations =
- kind.isParameter() ? deferredParameterAnnotations : this.deferredAnnotations;
- Map<DexAnnotation, List<ProgramDefinition>> deferredAnnotationsForAnnotationType =
- deferredAnnotations.computeIfAbsent(type, ignore -> new IdentityHashMap<>());
- deferredAnnotationsForAnnotationType
- .computeIfAbsent(annotation, ignore -> new ArrayList<>())
- .add(annotatedItem);
- // Also, non-program annotations should be considered live w.r.t the deferred processing.
- if (annotationTypeIsNotProgramClass) {
- liveAnnotations.add(type);
- }
+ DexClass annotationClass = definitionFor(type, annotatedItem);
+ processAnnotation(annotatedItem, annotation, kind, annotationClass);
+ }
+
+ void processAnnotation(
+ ProgramDefinition annotatedItem,
+ DexAnnotation annotation,
+ AnnotatedKind kind,
+ DexClass annotationClass) {
+ boolean annotationTypeIsNotProgramClass =
+ annotationClass == null || annotationClass.isNotProgramClass();
+ boolean isLive =
+ annotationTypeIsNotProgramClass || liveTypes.contains(annotationClass.asProgramClass());
+ if (shouldKeepAnnotation(annotatedItem, annotation, kind, isLive)) {
+ traceAnnotation(annotatedItem, annotation);
return;
}
+ if (isLive) {
+ // Enqueue for reprocessing when the keep info of the annotated item changes.
+ deferredAnnotationTracing.enqueueAnnotationForProcessingWhenKeepInfoChanges(
+ annotatedItem, annotation, annotationClass, kind);
+ } else {
+ // Enqueue for reprocessing when the annotation type becomes live.
+ assert annotationClass.isProgramClass();
+ deferredAnnotationTracing.enqueueAnnotationForProcessingWhenAnnotationTypeBecomesLive(
+ annotatedItem, annotation, annotationClass.asProgramClass(), kind);
+ }
+ }
+ private void traceAnnotation(ProgramDefinition annotatedItem, DexAnnotation annotation) {
// Report that the annotation is retained due to the annotated item.
graphReporter.registerAnnotation(annotation, annotatedItem);
@@ -2438,7 +2490,7 @@
// annotation.
AnnotationReferenceMarker referenceMarker =
new AnnotationReferenceMarker(annotation, annotatedItem);
- annotation.annotation.collectIndexedItems(appView, referenceMarker);
+ annotation.collectIndexedItems(appView, referenceMarker);
}
private boolean shouldKeepAnnotation(
@@ -2451,7 +2503,7 @@
assert mode.isInitialTreeShaking();
return true;
}
- KeepInfo<?, ?> itemKeepInfo = keepInfo.getInfo(annotatedItem);
+ KeepInfo<?, ?> itemKeepInfo = getKeepInfo().getInfo(annotatedItem);
return AnnotationRemover.shouldKeepAnnotation(
appView, annotatedItem, annotation, isLive, annotatedKind, mode, itemKeepInfo);
}
@@ -2761,17 +2813,17 @@
private void keepClassAndAllMembers(DexProgramClass clazz, KeepReason keepReason) {
KeepReasonWitness keepReasonWitness = graphReporter.registerClass(clazz, keepReason);
markClassAsInstantiatedWithCompatRule(clazz.asProgramClass(), () -> keepReasonWitness);
- keepInfo.keepClass(clazz);
+ mutateKeepInfo(clazz, MutableKeepInfoCollection::keepClass);
shouldNotBeMinified(clazz);
clazz.forEachProgramField(
field -> {
- keepInfo.keepField(field);
+ mutateKeepInfo(field, MutableKeepInfoCollection::keepField);
shouldNotBeMinified(field);
markFieldAsKept(field, keepReasonWitness);
});
clazz.forEachProgramMethod(
method -> {
- keepInfo.keepMethod(method);
+ mutateKeepInfo(method, MutableKeepInfoCollection::keepMethod);
shouldNotBeMinified(method);
markMethodAsKept(method, keepReasonWitness);
});
@@ -3039,7 +3091,8 @@
context,
appView,
instantiation,
- definition -> keepInfo.isPinned(definition, options, appInfo));
+ definition ->
+ getKeepInfo().isPinned(definition, options, appInfo));
if (lookupResult.isLookupResultSuccess()) {
lookupResultSuccess = lookupResult.asLookupResultSuccess();
break;
@@ -3306,7 +3359,7 @@
// Update keep info.
applyMinimumKeepInfo(field);
if (hasAlternativeLibraryDefinition(field.getHolder()) && !field.getDefinition().isPrivate()) {
- getKeepInfo().keepField(field);
+ mutateKeepInfo(field, MutableKeepInfoCollection::keepField);
}
// Notify analyses.
@@ -3600,7 +3653,7 @@
(type, subTypeConsumer, lambdaConsumer) ->
objectAllocationInfoCollection.forEachInstantiatedSubType(
type, subTypeConsumer, lambdaConsumer, appInfo),
- definition -> keepInfo.isPinned(definition, options, appInfo));
+ definition -> getKeepInfo().isPinned(definition, options, appInfo));
handleVirtualDispatchTargets(lookupResult, resolution);
});
return resolutionResults;
@@ -3711,9 +3764,13 @@
// TODO(sgjesse): Does this have to be enqueued as a root item? Right now it is done as the
// marking for not renaming it is in the root set.
worklist.enqueueMarkMethodKeptAction(valuesMethod, reason);
- keepInfo.joinMethod(
+ mutateKeepInfo(
valuesMethod,
- joiner -> joiner.disallowMinification().disallowOptimization().disallowShrinking());
+ (k, m) ->
+ k.joinMethod(
+ m,
+ joiner ->
+ joiner.disallowMinification().disallowOptimization().disallowShrinking()));
shouldNotBeMinified(valuesMethod);
}
}
@@ -3905,7 +3962,7 @@
KeepClassInfo.Joiner minimumKeepInfoForClass =
dependentMinimumKeepInfo.remove(preconditionEvent, clazz.getType());
if (minimumKeepInfoForClass != null) {
- keepInfo.joinClass(clazz, info -> info.merge(minimumKeepInfoForClass));
+ mutateKeepInfo(clazz, (k, c) -> k.joinClass(c, info -> info.merge(minimumKeepInfoForClass)));
enqueueClassIfShrinkingIsDisallowed(clazz, preconditionEvent, minimumKeepInfoForClass);
}
}
@@ -3920,7 +3977,7 @@
KeepClassInfo.Joiner minimumKeepInfo,
EnqueuerEvent preconditionEvent) {
if (liveTypes.contains(clazz)) {
- keepInfo.joinClass(clazz, info -> info.merge(minimumKeepInfo));
+ mutateKeepInfo(clazz, (k, c) -> k.joinClass(c, info -> info.merge(minimumKeepInfo)));
} else {
dependentMinimumKeepInfo
.getOrCreateUnconditionalMinimumKeepInfo()
@@ -3961,7 +4018,7 @@
KeepFieldInfo.Joiner minimumKeepInfoForField =
dependentMinimumKeepInfo.remove(preconditionEvent, field.getReference());
if (minimumKeepInfoForField != null) {
- keepInfo.joinField(field, info -> info.merge(minimumKeepInfoForField));
+ mutateKeepInfo(field, (k, f) -> k.joinField(f, info -> info.merge(minimumKeepInfoForField)));
enqueueFieldIfShrinkingIsDisallowed(field, preconditionEvent, minimumKeepInfoForField);
}
}
@@ -3974,7 +4031,7 @@
private void applyMinimumKeepInfoWhenLive(
ProgramField field, KeepFieldInfo.Joiner minimumKeepInfo, EnqueuerEvent preconditionEvent) {
if (liveFields.contains(field)) {
- keepInfo.joinField(field, info -> info.merge(minimumKeepInfo));
+ mutateKeepInfo(field, (k, f) -> k.joinField(f, info -> info.merge(minimumKeepInfo)));
} else {
dependentMinimumKeepInfo
.getOrCreateUnconditionalMinimumKeepInfo()
@@ -4011,13 +4068,14 @@
KeepMethodInfo.Joiner minimumKeepInfoForMethod =
dependentMinimumKeepInfo.remove(preconditionEvent, method.getReference());
if (minimumKeepInfoForMethod != null) {
- keepInfo.joinMethod(method, info -> info.merge(minimumKeepInfoForMethod));
+ mutateKeepInfo(
+ method, (k, m) -> k.joinMethod(m, info -> info.merge(minimumKeepInfoForMethod)));
enqueueMethodIfShrinkingIsDisallowed(method, preconditionEvent, minimumKeepInfoForMethod);
}
}
private void applyMinimumKeepInfo(ProgramMethod method, KeepMethodInfo.Joiner minimumKeepInfo) {
- keepInfo.joinMethod(method, info -> info.merge(minimumKeepInfo));
+ mutateKeepInfo(method, (k, m) -> k.joinMethod(m, info -> info.merge(minimumKeepInfo)));
enqueueMethodIfShrinkingIsDisallowed(method, UnconditionalKeepInfoEvent.get(), minimumKeepInfo);
}
@@ -4031,7 +4089,7 @@
KeepMethodInfo.Joiner minimumKeepInfo,
EnqueuerEvent preconditionEvent) {
if (liveMethods.contains(method) || targetedMethods.contains(method)) {
- keepInfo.joinMethod(method, info -> info.merge(minimumKeepInfo));
+ mutateKeepInfo(method, (k, m) -> k.joinMethod(m, info -> info.merge(minimumKeepInfo)));
} else {
dependentMinimumKeepInfo
.getOrCreateUnconditionalMinimumKeepInfo()
@@ -4606,7 +4664,7 @@
fieldAccessInfoCollection,
objectAllocationInfoCollection.build(appInfo),
callSites,
- keepInfo,
+ getKeepInfo(),
rootSet.mayHaveSideEffects,
amendWithCompanionMethods(rootSet.alwaysInline),
amendWithCompanionMethods(rootSet.whyAreYouNotInlining),
@@ -4640,14 +4698,17 @@
(methodReference, companionReference) -> {
ProgramMethod companion = appView.definitionFor(companionReference).asProgramMethod();
KeepMethodInfo.Joiner minimumKeepInfoForCompanion =
- keepInfo.getMethodInfoWithDefinitionLookup(methodReference, appInfo).joiner();
+ getKeepInfo().getMethodInfoWithDefinitionLookup(methodReference, appInfo).joiner();
KeepMethodInfo.Joiner extraMinimumKeepInfoForCompanion =
dependentMinimumKeepInfo
.getUnconditionalMinimumKeepInfoOrDefault(MinimumKeepInfoCollection.empty())
.getOrDefault(methodReference, KeepMethodInfo.newEmptyJoiner())
.asMethodJoiner();
- keepInfo.evaluateMethodRule(
- companion, minimumKeepInfoForCompanion.merge(extraMinimumKeepInfoForCompanion));
+ mutateKeepInfo(
+ companion,
+ (k, m) ->
+ k.evaluateMethodRule(
+ m, minimumKeepInfoForCompanion.merge(extraMinimumKeepInfoForCompanion)));
});
}
@@ -4776,16 +4837,7 @@
}
}
- // Process all deferred annotations.
- timing.begin("Process deferred annotations");
- processDeferredAnnotations(deferredAnnotations, AnnotatedKind::from);
- processDeferredAnnotations(
- deferredParameterAnnotations, annotatedItem -> AnnotatedKind.PARAMETER);
- timing.end();
-
- timing.begin("Process onclick methods");
- processOnClickMethods();
- timing.end();
+ processOnClickMethods(timing);
if (worklist.hasNext()) {
timing.end();
continue;
@@ -4887,7 +4939,7 @@
liveMethods::contains, interfaceProcessor);
CfPostProcessingDesugaringCollection.createForR8CfToCfDesugaring(
appView, interfaceDesugaring, this)
- .postProcessingDesugaring(liveTypes.items, eventConsumer, executorService, timing);
+ .postProcessingDesugaring(liveTypes.getItems(), eventConsumer, executorService, timing);
if (syntheticAdditions.isEmpty() && !options.testing.forceEnqueuerFullProcessingDesugaring) {
return;
@@ -5163,7 +5215,7 @@
applyMinimumKeepInfo(method);
if (hasAlternativeLibraryDefinition(method.getHolder())
&& !method.getDefinition().isPrivateMethod()) {
- getKeepInfo().keepMethod(method);
+ mutateKeepInfo(method, MutableKeepInfoCollection::keepMethod);
}
}
@@ -5262,34 +5314,39 @@
method, method, graphReporter.reportCompatKeepMethod(method));
}
- private static class SetWithReportedReason<T> {
+ private static class SetWithReportedReason<T extends DexDefinition> {
- private final Set<T> items = Sets.newIdentityHashSet();
+ private final Map<DexReference, T> items = new IdentityHashMap<>();
private final Map<T, List<Action>> deferredActions = new IdentityHashMap<>();
boolean add(T item, KeepReasonWitness witness) {
assert witness != null;
- if (items.add(item)) {
+ T existing = items.put(item.getReference(), item);
+ if (existing == null) {
deferredActions.getOrDefault(item, Collections.emptyList()).forEach(Action::execute);
return true;
}
return false;
}
+ boolean contains(DexReference reference) {
+ return items.containsKey(reference);
+ }
+
boolean contains(T item) {
- return items.contains(item);
+ return contains(item.getReference());
}
boolean registerDeferredAction(T item, Action action) {
- if (!items.contains(item)) {
+ if (!contains(item)) {
deferredActions.computeIfAbsent(item, ignore -> new ArrayList<>()).add(action);
return true;
}
return false;
}
- Set<T> getItems() {
- return SetUtils.unmodifiableForTesting(items);
+ Collection<T> getItems() {
+ return CollectionUtils.unmodifiableForTesting(items.values());
}
}
@@ -5351,12 +5408,37 @@
private class AnnotationReferenceMarker implements IndexedItemCollection {
+ private final DexAnnotation annotation;
private final ProgramDefinition context;
- private final KeepReason reason;
private AnnotationReferenceMarker(DexAnnotation annotation, ProgramDefinition context) {
+ this.annotation = annotation;
this.context = context;
- this.reason = KeepReason.referencedInAnnotation(annotation, context);
+ }
+
+ @Override
+ public boolean addField(DexField field) {
+ worklist.enqueueTraceFieldAccessFromAnnotationAction(field, annotation, context);
+ return false;
+ }
+
+ @Override
+ public boolean addMethod(DexMethod method) {
+ worklist.enqueueTraceMethodAccessFromAnnotationAction(method, annotation, context);
+ return false;
+ }
+
+ @Override
+ public boolean addType(DexType type) {
+ DexType baseType = type.toBaseType(appView.dexItemFactory());
+ if (!baseType.isClassType()) {
+ return false;
+ }
+ if (!graphReporter.hasConsumer() && liveTypes.contains(baseType)) {
+ return false;
+ }
+ worklist.enqueueTraceTypeAccessFromAnnotationAction(baseType, annotation, context);
+ return false;
}
@Override
@@ -5365,73 +5447,6 @@
}
@Override
- @SuppressWarnings("ReferenceEquality")
- public boolean addField(DexField fieldReference) {
- recordFieldReference(fieldReference, context);
- DexProgramClass holder = getProgramHolderOrNull(fieldReference, context);
- if (holder == null) {
- return false;
- }
- ProgramField field = holder.lookupProgramField(fieldReference);
- if (field == null) {
- return false;
- }
- // There is no dispatch on annotations, so only keep what is directly referenced.
- if (field.getReference() != fieldReference) {
- return false;
- }
- if (field.getDefinition().isStatic()) {
- FieldAccessInfoImpl fieldAccessInfo =
- fieldAccessInfoCollection.contains(fieldReference)
- ? fieldAccessInfoCollection.get(fieldReference)
- : fieldAccessInfoCollection.extend(
- fieldReference, new FieldAccessInfoImpl(fieldReference));
- fieldAccessInfo.setReadFromAnnotation();
- markFieldAsLive(field, context, reason);
- // In a class file an enum reference in an annotation is written as enum descriptor and
- // enum name. At runtime the JVM use valueOf on the enum class with the name to get the
- // instance. This indirectly use the values() method on that enum class. Also keep the
- // name of the field and the name of the enum in sync as otherwise recovering the field to
- // name relationship requires analysis of the enum <clinit> when this CF code is processed
- // again (e.g. as input to D8 for converting to DEX). See b/236691999 for more info.
- if (options.isGeneratingClassFiles() && field.getHolder().isEnum()) {
- markEnumValuesAsReachable(field.getHolder(), reason);
- applyMinimumKeepInfoWhenLive(
- field, KeepFieldInfo.newEmptyJoiner().disallowMinification());
- }
- } else {
- // There is no dispatch on annotations, so only keep what is directly referenced.
- worklist.enqueueMarkFieldAsReachableAction(field, context, reason);
- }
- return false;
- }
-
- @Override
- @SuppressWarnings("ReferenceEquality")
- public boolean addMethod(DexMethod method) {
- // Record the references in case they are not program types.
- recordMethodReference(method, context);
- DexProgramClass holder = getProgramHolderOrNull(method, context);
- if (holder == null) {
- return false;
- }
- DexEncodedMethod target = holder.lookupDirectMethod(method);
- if (target != null) {
- // There is no dispatch on annotations, so only keep what is directly referenced.
- if (target.getReference() == method) {
- markDirectStaticOrConstructorMethodAsLive(new ProgramMethod(holder, target), reason);
- }
- } else {
- target = holder.lookupVirtualMethod(method);
- // There is no dispatch on annotations, so only keep what is directly referenced.
- if (target != null && target.getReference() == method) {
- markMethodAsTargeted(new ProgramMethod(holder, target), reason);
- }
- }
- return false;
- }
-
- @Override
public boolean addString(DexString string) {
return false;
}
@@ -5450,12 +5465,6 @@
public boolean addMethodHandle(DexMethodHandle methodHandle) {
return false;
}
-
- @Override
- public boolean addType(DexType type) {
- markTypeAsLive(type, context, reason);
- return false;
- }
}
public static class EnqueuerDefinitionSupplier {
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredAnnotationTracing.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredAnnotationTracing.java
new file mode 100644
index 0000000..aafe94b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredAnnotationTracing.java
@@ -0,0 +1,137 @@
+// Copyright (c) 2025, 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;
+
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexDefinition;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.utils.MapUtils;
+import com.android.tools.r8.utils.ObjectUtils;
+import com.google.common.collect.Sets;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class EnqueuerDeferredAnnotationTracing {
+
+ private final Enqueuer enqueuer;
+
+ /**
+ * A map from annotation classes to annotations that need to be processed should the classes ever
+ * become live.
+ *
+ * <p>Concurrent instance since the concurrent processing of deferred annotations may update this.
+ */
+ private final Map<DexProgramClass, Set<DeferredAnnotation>> deferredAnnotationsByAnnotationClass =
+ new IdentityHashMap<>();
+
+ /**
+ * A map from annotated items to the annotations that need to be processed if/when the keep info
+ * of the annotated item changes.
+ *
+ * <p>Concurrent instance since the concurrent processing of deferred annotations may update this.
+ */
+ private final Map<DexDefinition, Set<DeferredAnnotation>> deferredAnnotationsByAnnotatedItem =
+ new IdentityHashMap<>();
+
+ EnqueuerDeferredAnnotationTracing(Enqueuer enqueuer) {
+ this.enqueuer = enqueuer;
+ }
+
+ public void enqueueAnnotationForProcessingWhenAnnotationTypeBecomesLive(
+ ProgramDefinition annotatedItem,
+ DexAnnotation annotation,
+ DexProgramClass annotationClass,
+ AnnotatedKind kind) {
+ assert annotationClass.isAnnotation();
+ deferredAnnotationsByAnnotationClass
+ .computeIfAbsent(annotationClass, ignoreKey(HashSet::new))
+ .add(new DeferredAnnotation(annotatedItem, annotation, annotationClass, kind));
+ }
+
+ public void enqueueAnnotationForProcessingWhenKeepInfoChanges(
+ ProgramDefinition annotatedItem,
+ DexAnnotation annotation,
+ DexClass annotationClass,
+ AnnotatedKind kind) {
+ deferredAnnotationsByAnnotatedItem
+ .computeIfAbsent(annotatedItem.getDefinition(), ignoreKey(Sets::newHashSet))
+ .add(new DeferredAnnotation(annotatedItem, annotation, annotationClass, kind));
+ }
+
+ // Called by the Enqueuer when an item has its keep info changed.
+ public void notifyKeepInfoChanged(ProgramDefinition definition) {
+ processDeferredAnnotations(definition.getDefinition(), deferredAnnotationsByAnnotatedItem);
+ }
+
+ // Called by the Enqueuer when an annotation class becomes live.
+ public void notifyNewlyLiveAnnotation(DexProgramClass newlyLiveAnnotationClass) {
+ assert newlyLiveAnnotationClass.isAnnotation();
+ processDeferredAnnotations(newlyLiveAnnotationClass, deferredAnnotationsByAnnotationClass);
+ }
+
+ private <T> void processDeferredAnnotations(
+ T key, Map<T, Set<DeferredAnnotation>> deferredAnnotations) {
+ Set<DeferredAnnotation> annotations =
+ MapUtils.removeOrDefault(deferredAnnotations, key, Collections.emptySet());
+ for (var annotation : annotations) {
+ enqueuer.processAnnotation(
+ annotation.annotatedItem,
+ annotation.annotation,
+ annotation.kind,
+ annotation.annotationClass);
+ }
+ }
+
+ private static class DeferredAnnotation {
+
+ private final ProgramDefinition annotatedItem;
+ private final DexAnnotation annotation;
+ private final DexClass annotationClass;
+ private final AnnotatedKind kind;
+
+ DeferredAnnotation(
+ ProgramDefinition annotatedItem,
+ DexAnnotation annotation,
+ DexClass annotationClass,
+ AnnotatedKind kind) {
+ this.annotatedItem = annotatedItem;
+ this.annotation = annotation;
+ this.annotationClass = annotationClass;
+ this.kind = kind;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof DeferredAnnotation)) {
+ return false;
+ }
+ DeferredAnnotation other = (DeferredAnnotation) obj;
+ if (ObjectUtils.identical(annotation, other.annotation)
+ && annotatedItem.getDefinition() == other.annotatedItem.getDefinition()) {
+ assert annotatedItem.getDefinition() == other.annotatedItem.getDefinition();
+ assert annotationClass == other.annotationClass;
+ assert kind == other.kind;
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return (31 * (31 + System.identityHashCode(annotation)))
+ + annotatedItem.getDefinition().hashCode();
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerMockitoAnalysis.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerMockitoAnalysis.java
index 4511156..792ad59 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerMockitoAnalysis.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerMockitoAnalysis.java
@@ -181,16 +181,17 @@
private void ensureClassIsMockable(DexProgramClass programClass) {
// Ensures the type is not made final so that it can still be subclassed.
- enqueuer.getKeepInfo().joinClass(programClass, Joiner::disallowOptimization);
+ enqueuer.mutateKeepInfo(programClass, (k, c) -> k.joinClass(c, Joiner::disallowOptimization));
// disallowOptimization --> prevent method from being marked final.
// allowCodeReplacement --> do not inline or optimize based on method body.
programClass.forEachProgramVirtualMethodMatching(
enqueuer::isMethodLive,
virtualMethod ->
- enqueuer.getKeepInfo()
- .joinMethod(
- virtualMethod,
- joiner -> joiner.disallowOptimization().allowCodeReplacement()));
+ enqueuer.mutateKeepInfo(
+ virtualMethod,
+ (k, m) ->
+ k.joinMethod(
+ m, joiner -> joiner.disallowOptimization().allowCodeReplacement())));
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
index 5ff3b75..8b76da8 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -64,6 +64,19 @@
}
}
+ static class ConditionalRuleConsequencesAction extends EnqueuerAction {
+ private final MinimumKeepInfoCollection consequences;
+
+ ConditionalRuleConsequencesAction(MinimumKeepInfoCollection consequences) {
+ this.consequences = consequences;
+ }
+
+ @Override
+ public void run(Enqueuer enqueuer) {
+ enqueuer.includeMinimumKeepInfo(consequences);
+ }
+ }
+
static class MarkReachableDirectAction extends EnqueuerAction {
private final DexMethod target;
// TODO(b/175854431): Avoid pushing context on worklist.
@@ -248,6 +261,60 @@
}
}
+ static class TraceFieldAccessFromAnnotationAction extends EnqueuerAction {
+ private final DexField field;
+ private final DexAnnotation annotation;
+ private final ProgramDefinition context;
+
+ TraceFieldAccessFromAnnotationAction(
+ DexField field, DexAnnotation annotation, ProgramDefinition context) {
+ this.field = field;
+ this.annotation = annotation;
+ this.context = context;
+ }
+
+ @Override
+ public void run(Enqueuer enqueuer) {
+ enqueuer.traceAnnotationFieldAccess(field, annotation, context);
+ }
+ }
+
+ static class TraceMethodAccessFromAnnotationAction extends EnqueuerAction {
+ private final DexMethod method;
+ private final DexAnnotation annotation;
+ private final ProgramDefinition context;
+
+ TraceMethodAccessFromAnnotationAction(
+ DexMethod method, DexAnnotation annotation, ProgramDefinition context) {
+ this.method = method;
+ this.annotation = annotation;
+ this.context = context;
+ }
+
+ @Override
+ public void run(Enqueuer enqueuer) {
+ enqueuer.traceAnnotationMethodAccess(method, annotation, context);
+ }
+ }
+
+ static class TraceTypeAccessFromAnnotationAction extends EnqueuerAction {
+ private final DexType type;
+ private final DexAnnotation annotation;
+ private final ProgramDefinition context;
+
+ TraceTypeAccessFromAnnotationAction(
+ DexType type, DexAnnotation annotation, ProgramDefinition context) {
+ this.type = type;
+ this.annotation = annotation;
+ this.context = context;
+ }
+
+ @Override
+ public void run(Enqueuer enqueuer) {
+ enqueuer.traceAnnotationTypeAccess(type, annotation, context);
+ }
+ }
+
static class TraceCodeAction extends EnqueuerAction {
private final ProgramMethod method;
@@ -636,6 +703,11 @@
abstract boolean enqueueAssertAction(Action assertion);
+ public final void enqueueConditionalRuleConsequencesAction(
+ MinimumKeepInfoCollection consequences) {
+ enqueue(new ConditionalRuleConsequencesAction(consequences));
+ }
+
abstract void enqueueFuture(Action action);
abstract void enqueueMarkReachableDirectAction(
@@ -668,6 +740,15 @@
abstract void enqueueTraceAnnotationAction(
ProgramDefinition annotatedItem, DexAnnotation annotation, AnnotatedKind annotatedKind);
+ abstract void enqueueTraceFieldAccessFromAnnotationAction(
+ DexField field, DexAnnotation annotation, ProgramDefinition context);
+
+ abstract void enqueueTraceMethodAccessFromAnnotationAction(
+ DexMethod method, DexAnnotation annotation, ProgramDefinition context);
+
+ abstract void enqueueTraceTypeAccessFromAnnotationAction(
+ DexType type, DexAnnotation annotation, ProgramDefinition context);
+
public abstract void enqueueTraceCodeAction(ProgramMethod method);
public abstract void enqueueTraceConstClassAction(
@@ -810,6 +891,24 @@
}
@Override
+ void enqueueTraceFieldAccessFromAnnotationAction(
+ DexField field, DexAnnotation annotation, ProgramDefinition context) {
+ queue.add(new TraceFieldAccessFromAnnotationAction(field, annotation, context));
+ }
+
+ @Override
+ void enqueueTraceMethodAccessFromAnnotationAction(
+ DexMethod method, DexAnnotation annotation, ProgramDefinition context) {
+ queue.add(new TraceMethodAccessFromAnnotationAction(method, annotation, context));
+ }
+
+ @Override
+ void enqueueTraceTypeAccessFromAnnotationAction(
+ DexType type, DexAnnotation annotation, ProgramDefinition context) {
+ queue.add(new TraceTypeAccessFromAnnotationAction(type, annotation, context));
+ }
+
+ @Override
public void enqueueTraceCodeAction(ProgramMethod method) {
queue.add(new TraceCodeAction(method));
}
@@ -983,6 +1082,24 @@
}
@Override
+ void enqueueTraceFieldAccessFromAnnotationAction(
+ DexField field, DexAnnotation annotation, ProgramDefinition context) {
+ throw attemptToEnqueue("TraceFieldAccessFromAnnotationAction " + field);
+ }
+
+ @Override
+ void enqueueTraceMethodAccessFromAnnotationAction(
+ DexMethod method, DexAnnotation annotation, ProgramDefinition context) {
+ throw attemptToEnqueue("TraceMethodAccessFromAnnotationAction " + method);
+ }
+
+ @Override
+ void enqueueTraceTypeAccessFromAnnotationAction(
+ DexType type, DexAnnotation annotation, ProgramDefinition context) {
+ throw attemptToEnqueue("TraceTypeAccessFromAnnotationAction " + type);
+ }
+
+ @Override
public void enqueueTraceCodeAction(ProgramMethod method) {
throw attemptToEnqueue("TraceCodeAction " + method);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
index d261cf3..27ffcc0 100644
--- a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
+++ b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
@@ -75,6 +75,10 @@
return KeepReasonWitness.INSTANCE;
}
+ public boolean hasConsumer() {
+ return keptGraphConsumer != null;
+ }
+
public boolean verifyRootedPath(DexProgramClass liveType) {
assert verificationGraphConsumer != null;
ClassGraphNode node = getClassGraphNode(liveType.type);
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java b/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java
index 889c466..7df2ab5 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java
@@ -351,7 +351,9 @@
public Consumer<DexType> addDependencyAllowSyntheticRoot(SyntheticItems syntheticItems) {
return type -> {
- assert !roots.contains(type) || syntheticItems.isCommittedSynthetic(type);
+ assert !roots.contains(type)
+ || syntheticItems.isCommittedSynthetic(type)
+ || syntheticItems.isFinalizedSynthetic(type);
addDependencyIfNotRoot(type);
};
}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index 0da300d..622d966 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.errors.InlinableStaticFinalFieldPreconditionDiagnostic;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.errors.UnusedProguardKeepRuleDiagnostic;
+import com.android.tools.r8.graph.AccessControl;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.BottomUpClassHierarchyTraversal;
@@ -43,6 +44,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.graph.ImmediateAppSubtypingInfo;
+import com.android.tools.r8.graph.InvalidCode;
import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramField;
@@ -855,7 +857,7 @@
}
ProgramMethod resolutionMethod = resolutionResult.getResolvedProgramMethod();
ProgramMethod methodToKeep;
- if (canInsertForwardingMethod(originalClazz, resolutionMethod.getDefinition())) {
+ if (canInsertForwardingMethod(originalClazz, resolutionMethod)) {
DexMethod methodToKeepReference =
resolutionMethod.getReference().withHolder(originalClazz, appView.dexItemFactory());
methodToKeep =
@@ -885,9 +887,14 @@
}
}
- private boolean canInsertForwardingMethod(DexClass holder, DexEncodedMethod target) {
+ private boolean canInsertForwardingMethod(DexProgramClass holder, ProgramMethod method) {
+ DexProgramClass resolvedHolder = method.getHolder();
+ if (AccessControl.isMemberAccessible(method, resolvedHolder, holder, appView)
+ .isPossiblyFalse()) {
+ return false;
+ }
return appView.options().isGeneratingDex()
- || ArrayUtils.contains(holder.interfaces.values, target.getHolderType());
+ || ArrayUtils.contains(holder.interfaces.values, resolvedHolder.getType());
}
private void markMatchingOverriddenMethods(
@@ -1877,7 +1884,9 @@
interfaceDesugaringSyntheticHelper.ensureMethodOfProgramCompanionClassStub(
method, eventConsumer);
// Add the method to the inverse map as tracing will now directly target the CC method.
- pendingMethodMoveInverse.put(companion, method);
+ if (InvalidCode.isInvalidCode(companion.getDefinition().getCode())) {
+ pendingMethodMoveInverse.put(companion, method);
+ }
LazyBox<Joiner<?, ?, ?>> companionJoiner =
new LazyBox<>(
diff --git a/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/EnqueuerReflectiveIdentificationEventConsumer.java b/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/EnqueuerReflectiveIdentificationEventConsumer.java
index 20aa5be..0941d2a 100644
--- a/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/EnqueuerReflectiveIdentificationEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/shaking/reflectiveidentification/EnqueuerReflectiveIdentificationEventConsumer.java
@@ -58,9 +58,9 @@
// To ensure we are not moving the class because we cannot prune it when there is a reflective
// use of it.
if (enqueuer.getKeepInfo().getClassInfo(programClass).isShrinkingAllowed(options)) {
- enqueuer
- .getKeepInfo()
- .joinClass(programClass, joiner -> joiner.disallowOptimization().disallowShrinking());
+ enqueuer.mutateKeepInfo(
+ programClass,
+ (k, c) -> k.joinClass(c, joiner -> joiner.disallowOptimization().disallowShrinking()));
}
} else {
enqueuer.recordNonProgramClassWithNoMissingReporting(clazz, context);
@@ -122,19 +122,19 @@
// Keep this interface to ensure that we do not merge the interface into its unique subtype,
// or merge other interfaces into it horizontally.
- enqueuer
- .getKeepInfo()
- .joinClass(clazz, joiner -> joiner.disallowOptimization().disallowShrinking());
+ enqueuer.mutateKeepInfo(
+ clazz,
+ (k, c) -> k.joinClass(c, joiner -> joiner.disallowOptimization().disallowShrinking()));
// Also keep all of its virtual methods to ensure that the devirtualizer does not perform
// illegal rewritings of invoke-interface instructions into invoke-virtual instructions.
if (enqueuer.getMode().isInitialTreeShaking()) {
clazz.forEachProgramVirtualMethod(
virtualMethod -> {
- enqueuer
- .getKeepInfo()
- .joinMethod(
- virtualMethod, joiner -> joiner.disallowOptimization().disallowShrinking());
+ enqueuer.mutateKeepInfo(
+ virtualMethod,
+ (k, m) ->
+ k.joinMethod(m, joiner -> joiner.disallowOptimization().disallowShrinking()));
enqueuer.markVirtualMethodAsReachable(
virtualMethod.getReference(), true, context, reason);
});
diff --git a/src/main/java/com/android/tools/r8/shaking/rules/ApplicableRulesEvaluatorImpl.java b/src/main/java/com/android/tools/r8/shaking/rules/ApplicableRulesEvaluatorImpl.java
index d8b3d66..3242fd3 100644
--- a/src/main/java/com/android/tools/r8/shaking/rules/ApplicableRulesEvaluatorImpl.java
+++ b/src/main/java/com/android/tools/r8/shaking/rules/ApplicableRulesEvaluatorImpl.java
@@ -58,16 +58,23 @@
}
// TODO(b/323816623): If we tracked newly live, we could speed up finding rules.
// TODO(b/323816623): Parallelize this.
+ MinimumKeepInfoCollection consequences = null;
for (int i = 0; i < pendingConditionalRules.size(); i++) {
R rule = pendingConditionalRules.get(i);
if (rule != null && rule.isSatisfiedAfterUpdate(enqueuer)) {
++prunedCount;
pendingConditionalRules.set(i, null);
- enqueuer.includeMinimumKeepInfo(rule.getConsequences());
materializedRules.add(rule.asMaterialized());
onSatisfiedRuleCallback.accept(rule, enqueuer);
+ if (consequences == null) {
+ consequences = MinimumKeepInfoCollection.create();
+ }
+ consequences.merge(rule.getConsequences());
}
}
+ if (consequences != null) {
+ enqueuer.getWorklist().enqueueConditionalRuleConsequencesAction(consequences);
+ }
if (prunedCount == pendingConditionalRules.size()) {
assert Iterables.all(pendingConditionalRules, Objects::isNull);
diff --git a/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java b/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java
index ea5a020..5ce9d49 100644
--- a/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java
@@ -29,6 +29,7 @@
final DexApplication application;
final SyntheticItems.State state;
final CommittedSyntheticsCollection committed;
+ final CommittedSyntheticsCollection finalized;
final ImmutableList<DexType> committedProgramTypes;
final GlobalSyntheticsStrategy globalSyntheticsStrategy;
@@ -36,11 +37,13 @@
State state,
DexApplication application,
CommittedSyntheticsCollection committed,
+ CommittedSyntheticsCollection finalized,
ImmutableList<DexType> committedProgramTypes,
GlobalSyntheticsStrategy globalSyntheticsStrategy) {
this.state = state;
this.application = application;
this.committed = committed;
+ this.finalized = finalized;
this.committedProgramTypes = committedProgramTypes;
this.globalSyntheticsStrategy = globalSyntheticsStrategy;
assert committed.verifyTypesAreInApp(application);
diff --git a/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java b/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
index 8663f34..fa6cff0 100644
--- a/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
+++ b/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
@@ -24,6 +24,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
@@ -32,7 +33,7 @@
* <p>This structure is to make it easier to pass the items from SyntheticItems to CommittedItems
* and back while also providing a builder for updating the committed synthetics.
*/
-class CommittedSyntheticsCollection {
+public class CommittedSyntheticsCollection {
static class Builder {
private final CommittedSyntheticsCollection parent;
@@ -179,6 +180,37 @@
assert verifySyntheticInputsSubsetOfSynthetics();
}
+ public CommittedSyntheticsCollection merge(
+ ImmutableMap<DexType, List<SyntheticMethodReference>> methods,
+ ImmutableMap<DexType, List<SyntheticProgramClassReference>> classes,
+ ImmutableMap<DexType, Set<DexType>> globalContexts,
+ ImmutableSet<DexType> syntheticInputs) {
+ if (isEmpty()) {
+ return new CommittedSyntheticsCollection(methods, classes, globalContexts, syntheticInputs);
+ }
+ return new CommittedSyntheticsCollection(
+ ImmutableMap.<DexType, List<SyntheticMethodReference>>builderWithExpectedSize(
+ this.methods.size() + methods.size())
+ .putAll(this.methods)
+ .putAll(methods)
+ .build(),
+ ImmutableMap.<DexType, List<SyntheticProgramClassReference>>builderWithExpectedSize(
+ this.classes.size() + classes.size())
+ .putAll(this.classes)
+ .putAll(classes)
+ .build(),
+ ImmutableMap.<DexType, Set<DexType>>builderWithExpectedSize(
+ this.globalContexts.size() + globalContexts.size())
+ .putAll(this.globalContexts)
+ .putAll(globalContexts)
+ .build(),
+ ImmutableSet.<DexType>builderWithExpectedSize(
+ this.syntheticInputs.size() + syntheticInputs.size())
+ .addAll(this.syntheticInputs)
+ .addAll(syntheticInputs)
+ .build());
+ }
+
private boolean verifySyntheticInputsSubsetOfSynthetics() {
Set<DexType> synthetics =
ImmutableSet.<DexType>builder().addAll(methods.keySet()).addAll(classes.keySet()).build();
@@ -236,6 +268,32 @@
return syntheticInputs.contains(type);
}
+ public void forEachClass(BiConsumer<DexType, List<SyntheticProgramClassReference>> consumer) {
+ classes.forEach(consumer);
+ }
+
+ public void forEachClassFlattened(BiConsumer<DexType, SyntheticProgramClassReference> consumer) {
+ classes.forEach(
+ (type, references) -> {
+ for (SyntheticProgramClassReference reference : references) {
+ consumer.accept(type, reference);
+ }
+ });
+ }
+
+ public void forEachMethod(BiConsumer<DexType, List<SyntheticMethodReference>> consumer) {
+ methods.forEach(consumer);
+ }
+
+ public void forEachMethodFlattened(BiConsumer<DexType, SyntheticMethodReference> consumer) {
+ methods.forEach(
+ (type, references) -> {
+ for (SyntheticMethodReference reference : references) {
+ consumer.accept(type, reference);
+ }
+ });
+ }
+
public Set<DexType> getContextsForGlobal(DexType globalSynthetic) {
return globalContexts.get(globalSynthetic);
}
@@ -267,7 +325,7 @@
}
CommittedSyntheticsCollection pruneItems(PrunedItems prunedItems) {
- Set<DexType> removed = prunedItems.getNoLongerSyntheticItems();
+ Set<DexType> removed = prunedItems.getRemovedClasses();
if (removed.isEmpty()) {
return this;
}
@@ -296,8 +354,6 @@
}
// Global synthetic contexts are only collected for per-file modes which only prune synthetic
// items, not inputs.
- assert globalContexts.isEmpty()
- || prunedItems.getNoLongerSyntheticItems().size() == prunedItems.getRemovedClasses().size();
return changed ? builder.build() : this;
}
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 d1db8ba..f7cc1e5 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -160,10 +160,15 @@
private final SyntheticItems synthetics;
private final CommittedSyntheticsCollection committed;
+ private final CommittedSyntheticsCollection finalized;
- SyntheticFinalization(SyntheticItems synthetics, CommittedSyntheticsCollection committed) {
+ SyntheticFinalization(
+ SyntheticItems synthetics,
+ CommittedSyntheticsCollection committed,
+ CommittedSyntheticsCollection finalized) {
this.synthetics = synthetics;
this.committed = committed;
+ this.finalized = finalized;
}
public static void finalize(
@@ -286,11 +291,9 @@
new CommittedItems(
State.FINALIZED,
application,
- new CommittedSyntheticsCollection(
- finalMethods,
- finalClasses,
- committed.getGlobalContexts(),
- finalInputSynthetics),
+ CommittedSyntheticsCollection.empty(),
+ finalized.merge(
+ finalMethods, finalClasses, committed.getGlobalContexts(), finalInputSynthetics),
ImmutableList.of(),
synthetics.getGlobalSyntheticsStrategy()),
syntheticFinalizationGraphLens,
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 2f4e348..942465b 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -77,7 +77,8 @@
if (definition != null) {
return definition.getKind().isShareable();
}
- Iterable<SyntheticReference<?, ?, ?>> references = committed.getItems(clazz.type);
+ Iterable<SyntheticReference<?, ?, ?>> references =
+ Iterables.concat(committed.getItems(clazz.type), finalized.getItems(clazz.getType()));
Iterator<SyntheticReference<?, ?, ?>> iterator = references.iterator();
if (iterator.hasNext()) {
boolean sharable = iterator.next().getKind().isShareable();
@@ -218,38 +219,38 @@
private final State state;
private final SyntheticNaming naming;
private final CommittedSyntheticsCollection committed;
+ private final CommittedSyntheticsCollection finalized;
private final PendingSynthetics pending = new PendingSynthetics();
private final ContextsForGlobalSynthetics globalContexts;
private final GlobalSyntheticsStrategy globalSyntheticsStrategy;
- @SuppressWarnings("ReferenceEquality")
public Set<DexType> collectSyntheticsFromContext(DexType context) {
Set<DexType> result = Sets.newIdentityHashSet();
- committed
- .getMethods()
- .forEach(
- (synthetic, methodReferences) -> {
- methodReferences.forEach(
- methodReference -> {
- if (methodReference.getContext().getSynthesizingContextType() == context) {
- result.add(synthetic);
- }
- });
- });
- committed
- .getClasses()
- .forEach(
- (synthetic, classReferences) -> {
- classReferences.forEach(
- classReference -> {
- if (classReference.getContext().getSynthesizingContextType() == context) {
- result.add(synthetic);
- }
- });
- });
+ internalCollectSyntheticsFromContext(context, committed, result);
+ internalCollectSyntheticsFromContext(context, finalized, result);
return result;
}
+ private static void internalCollectSyntheticsFromContext(
+ DexType context, CommittedSyntheticsCollection committed, Set<DexType> result) {
+ committed.forEachMethodFlattened(
+ (synthetic, methodReference) -> {
+ if (context.isIdenticalTo(methodReference.getContext().getSynthesizingContextType())) {
+ result.add(synthetic);
+ }
+ });
+ committed.forEachClassFlattened(
+ (synthetic, classReference) -> {
+ if (context.isIdenticalTo(classReference.getContext().getSynthesizingContextType())) {
+ result.add(synthetic);
+ }
+ });
+ }
+
+ public CommittedSyntheticsCollection getCommitted() {
+ return committed;
+ }
+
public SyntheticNaming getNaming() {
return naming;
}
@@ -265,6 +266,7 @@
State.OPEN,
application,
CommittedSyntheticsCollection.empty(),
+ CommittedSyntheticsCollection.empty(),
ImmutableList.of(),
globalSyntheticsStrategy);
}
@@ -274,6 +276,7 @@
this(
commit.state,
commit.committed,
+ commit.finalized,
commit.globalSyntheticsStrategy,
commit.getApplication().dexItemFactory().getSyntheticNaming());
}
@@ -281,10 +284,12 @@
private SyntheticItems(
State state,
CommittedSyntheticsCollection committed,
+ CommittedSyntheticsCollection finalized,
GlobalSyntheticsStrategy globalSyntheticsStrategy,
SyntheticNaming naming) {
this.state = state;
this.committed = committed;
+ this.finalized = finalized;
this.naming = naming;
this.globalContexts = globalSyntheticsStrategy.getStrategy();
this.globalSyntheticsStrategy = globalSyntheticsStrategy;
@@ -293,17 +298,23 @@
public Map<DexType, Set<DexType>> getFinalGlobalSyntheticContexts(AppView<?> appView) {
assert isFinalized();
DexItemFactory factory = appView.dexItemFactory();
- ImmutableMap<DexType, Set<DexType>> globalContexts = committed.getGlobalContexts();
+ ImmutableMap<DexType, Set<DexType>> committedGlobalContexts = committed.getGlobalContexts();
+ ImmutableMap<DexType, Set<DexType>> finalizedGlobalContexts = finalized.getGlobalContexts();
NamingLens namingLens = appView.getNamingLens();
- Map<DexType, Set<DexType>> rewritten = new IdentityHashMap<>(globalContexts.size());
- globalContexts.forEach(
- (global, contexts) -> {
- Set<DexType> old =
- rewritten.put(
- namingLens.lookupType(global, factory),
- SetUtils.mapIdentityHashSet(contexts, c -> namingLens.lookupType(c, factory)));
- assert old == null;
- });
+ Map<DexType, Set<DexType>> rewritten =
+ new IdentityHashMap<>(committedGlobalContexts.size() + finalizedGlobalContexts.size());
+ Iterables.concat(committedGlobalContexts.entrySet(), finalizedGlobalContexts.entrySet())
+ .forEach(
+ entry -> {
+ var global = entry.getKey();
+ var contexts = entry.getValue();
+ Set<DexType> old =
+ rewritten.put(
+ namingLens.lookupType(global, factory),
+ SetUtils.mapIdentityHashSet(
+ contexts, c -> namingLens.lookupType(c, factory)));
+ assert old == null;
+ });
return rewritten;
}
@@ -311,6 +322,7 @@
// Collecting synthetic items must be the very first task after application build.
SyntheticItems synthetics = appView.getSyntheticItems();
assert synthetics.committed.isEmpty();
+ assert synthetics.finalized.isEmpty();
assert synthetics.pending.isEmpty();
CommittedSyntheticsCollection.Builder builder = synthetics.committed.builder();
if (appView.options().intermediate) {
@@ -336,6 +348,7 @@
synthetics.state,
appView.appInfo().app(),
committed,
+ synthetics.finalized,
ImmutableList.of(),
synthetics.globalSyntheticsStrategy);
if (appView.appInfo().hasClassHierarchy()) {
@@ -463,6 +476,10 @@
return committed.containsType(type);
}
+ public boolean isFinalizedSynthetic(DexType type) {
+ return finalized.containsType(type);
+ }
+
public boolean isPendingSynthetic(DexType type) {
return pending.containsType(type);
}
@@ -472,7 +489,7 @@
}
public boolean isSynthetic(DexType type) {
- return committed.containsType(type) || pending.definitions.containsKey(type);
+ return isCommittedSynthetic(type) || isFinalizedSynthetic(type) || isPendingSynthetic(type);
}
public boolean isSubjectToKeepRules(DexProgramClass clazz) {
@@ -493,7 +510,23 @@
if (definition != null) {
return definition.getKind().isGlobal();
}
- return isGlobalReferences(committed.getClasses().get(type));
+ List<SyntheticProgramClassReference> committedReferences =
+ committed.getClasses().getOrDefault(type, Collections.emptyList());
+ List<SyntheticProgramClassReference> finalizedReferences =
+ finalized.getClasses().getOrDefault(type, Collections.emptyList());
+ SyntheticProgramClassReference singleReference;
+ if (committedReferences.size() + finalizedReferences.size() == 1) {
+ singleReference =
+ committedReferences.size() == 1 ? committedReferences.get(0) : finalizedReferences.get(0);
+ } else {
+ singleReference = null;
+ }
+ if (singleReference != null && singleReference.getKind().isGlobal()) {
+ return true;
+ }
+ assert verifyNoGlobals(committedReferences);
+ assert verifyNoGlobals(finalizedReferences);
+ return false;
}
public boolean isGlobalSyntheticClass(DexProgramClass clazz) {
@@ -513,6 +546,10 @@
// Only a single context should exist for a globally derived synthetic, so return early.
return isGlobalSyntheticClass(reference.getContext().getSynthesizingContextType());
}
+ for (SyntheticReference<?, ?, ?> reference : finalized.getItems(type)) {
+ // Only a single context should exist for a globally derived synthetic, so return early.
+ return isGlobalSyntheticClass(reference.getContext().getSynthesizingContextType());
+ }
SyntheticDefinition<?, ?, ?> definition = pending.definitions.get(type);
if (definition != null) {
return isGlobalSyntheticClass(definition.getContext().getSynthesizingContextType());
@@ -520,17 +557,6 @@
return false;
}
- private static boolean isGlobalReferences(List<SyntheticProgramClassReference> references) {
- if (references == null) {
- return false;
- }
- if (references.size() == 1 && references.get(0).getKind().isGlobal()) {
- return true;
- }
- assert verifyNoGlobals(references);
- return false;
- }
-
private static boolean verifyNoGlobals(List<SyntheticProgramClassReference> references) {
for (SyntheticProgramClassReference reference : references) {
assert !reference.getKind().isGlobal();
@@ -545,12 +571,16 @@
public boolean isSyntheticOfKind(DexType type, SyntheticKindSelector kindSelector) {
SyntheticKind kind = kindSelector.select(naming);
- return pending.containsTypeOfKind(type, kind) || committed.containsTypeOfKind(type, kind);
+ return pending.containsTypeOfKind(type, kind)
+ || committed.containsTypeOfKind(type, kind)
+ || finalized.containsTypeOfKind(type, kind);
}
public Iterable<SyntheticKind> getSyntheticKinds(DexType type) {
Iterable<SyntheticKind> references =
- IterableUtils.transform(committed.getItems(type), SyntheticReference::getKind);
+ Iterables.concat(
+ Iterables.transform(committed.getItems(type), SyntheticReference::getKind),
+ Iterables.transform(finalized.getItems(type), SyntheticReference::getKind));
SyntheticDefinition<?, ?, ?> definition = pending.definitions.get(type);
if (definition != null) {
references = Iterables.concat(references, IterableUtils.singleton(definition.getKind()));
@@ -559,7 +589,8 @@
}
boolean isSyntheticInput(DexProgramClass clazz) {
- return committed.containsSyntheticInput(clazz.getType());
+ return committed.containsSyntheticInput(clazz.getType())
+ || finalized.containsSyntheticInput(clazz.getType());
}
public FeatureSplit getContextualFeatureSplitOrDefault(DexType type, FeatureSplit defaultValue) {
@@ -591,6 +622,9 @@
for (SyntheticReference<?, ?, ?> reference : committed.getItems(type)) {
consumer.accept(reference.getContext());
}
+ for (SyntheticReference<?, ?, ?> reference : finalized.getItems(type)) {
+ consumer.accept(reference.getContext());
+ }
SyntheticDefinition<?, ?, ?> definition = pending.definitions.get(type);
if (definition != null) {
consumer.accept(definition.getContext());
@@ -648,6 +682,7 @@
}
public boolean isSyntheticMethodThatShouldNotBeDoubleProcessed(ProgramMethod method) {
+ assert finalized.isEmpty();
for (SyntheticMethodReference reference :
committed.getMethods().getOrDefault(method.getHolderType(), Collections.emptyList())) {
if (reference.getKind().equals(naming.STATIC_INTERFACE_CALL)) {
@@ -667,6 +702,7 @@
DexProgramClass clazz,
Predicate<DexProgramClass> ifIsLambda,
Predicate<DexProgramClass> ifNotLambda) {
+ assert finalized.isEmpty();
Iterable<SyntheticReference<?, ?, ?>> references = committed.getItems(clazz.getType());
SyntheticDefinition<?, ?, ?> definition = pending.definitions.get(clazz.getType());
if (definition != null) {
@@ -693,7 +729,8 @@
if (existingDefinition != null) {
return existingDefinition.getContext();
}
- Iterable<SyntheticReference<?, ?, ?>> existingReferences = committed.getItems(contextType);
+ Iterable<SyntheticReference<?, ?, ?>> existingReferences =
+ Iterables.concat(committed.getItems(contextType), finalized.getItems(contextType));
if (!Iterables.isEmpty(existingReferences)) {
// Use a deterministic synthesizing context from the set of contexts.
return IterableUtils.min(
@@ -1171,7 +1208,6 @@
AppView<?> appView,
Consumer<SyntheticMethodBuilder> fn,
Supplier<String> syntheticIdSupplier) {
- assert !isFinalized();
// 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);
@@ -1206,7 +1242,14 @@
}
public CommittedItems commitPrunedItems(PrunedItems prunedItems) {
- return commit(prunedItems, pending, globalContexts, committed, state, globalSyntheticsStrategy);
+ return commit(
+ prunedItems,
+ pending,
+ globalContexts,
+ committed,
+ finalized,
+ state,
+ globalSyntheticsStrategy);
}
public CommittedItems commitRewrittenWithLens(
@@ -1219,6 +1262,7 @@
pending,
globalContexts,
committed.rewriteWithLens(lens, timing),
+ finalized.rewriteWithLens(lens, timing),
state,
globalSyntheticsStrategy);
timing.end();
@@ -1230,15 +1274,16 @@
PendingSynthetics pending,
ContextsForGlobalSynthetics globalContexts,
CommittedSyntheticsCollection committed,
+ CommittedSyntheticsCollection finalized,
State state,
GlobalSyntheticsStrategy globalSyntheticsStrategy) {
DexApplication application = prunedItems.getPrunedApp();
- Set<DexType> removedClasses = prunedItems.getNoLongerSyntheticItems();
- CommittedSyntheticsCollection.Builder builder = committed.builder();
+ Set<DexType> removedClasses = prunedItems.getRemovedClasses();
+ CommittedSyntheticsCollection.Builder committedBuilder = committed.builder();
// Compute the synthetic additions and add them to the application.
ImmutableList<DexType> committedProgramTypes;
DexApplication amendedApplication;
- if (pending.definitions.isEmpty()) {
+ if (pending.isEmpty()) {
committedProgramTypes = ImmutableList.of();
amendedApplication = application;
} else {
@@ -1258,30 +1303,32 @@
assert definition.isClasspathDefinition();
appBuilder.addClasspathClass(definition.asClasspathDefinition().getHolder());
}
- builder.addItem(definition);
+ committedBuilder.addItem(definition);
}
}
- builder.addGlobalContexts(globalContexts);
+ committedBuilder.addGlobalContexts(globalContexts);
committedProgramTypes = committedProgramTypesBuilder.build();
amendedApplication = appBuilder.build();
}
return new CommittedItems(
state,
amendedApplication,
- builder.build().pruneItems(prunedItems),
+ committedBuilder.build().pruneItems(prunedItems),
+ finalized.pruneItems(prunedItems),
committedProgramTypes,
globalSyntheticsStrategy);
}
public void writeAttributeIfIntermediateSyntheticClass(
ClassWriter writer, DexProgramClass clazz, AppView<?> appView) {
+ assert committed.isEmpty();
if (appView.options().testing.disableSyntheticMarkerAttributeWriting) {
return;
}
if (!appView.options().intermediate || !appView.options().isGeneratingClassFiles()) {
return;
}
- Iterator<SyntheticReference<?, ?, ?>> it = committed.getItems(clazz.getType()).iterator();
+ Iterator<SyntheticReference<?, ?, ?>> it = finalized.getItems(clazz.getType()).iterator();
if (it.hasNext()) {
SyntheticKind kind = it.next().getKind();
// When compiling intermediates there should not be any mergings as they may invalidate the
@@ -1296,19 +1343,20 @@
Result computeFinalSynthetics(AppView<?> appView, Timing timing) {
assert !hasPendingSyntheticClasses();
- return new SyntheticFinalization(this, committed).computeFinalSynthetics(appView, timing);
+ return new SyntheticFinalization(this, committed, finalized)
+ .computeFinalSynthetics(appView, timing);
}
- @SuppressWarnings("ReferenceEquality")
public void reportSyntheticsInformation(SyntheticInfoConsumer consumer) {
assert isFinalized();
+ assert committed.isEmpty();
Map<DexType, DexType> seen = new IdentityHashMap<>();
- committed.forEachItem(
+ finalized.forEachItem(
ref -> {
DexType holder = ref.getHolder();
DexType context = ref.getContext().getSynthesizingContextType();
DexType old = seen.put(holder, context);
- assert old == null || old == context;
+ assert old == null || old.isIdenticalTo(context);
if (old == null) {
consumer.acceptSyntheticInfo(new SyntheticInfoConsumerDataImpl(holder, context));
}
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 edfdb92..fb1b109 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
@@ -17,7 +17,7 @@
*
* <p>This class is internal to the synthetic items collection, thus package-protected.
*/
-class SyntheticMethodDefinition
+public class SyntheticMethodDefinition
extends SyntheticDefinition<
SyntheticMethodReference, SyntheticMethodDefinition, DexProgramClass>
implements SyntheticProgramDefinition {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
index e649f48..e6b6760 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
@@ -18,7 +18,7 @@
*
* <p>This class is internal to the synthetic items collection, thus package-protected.
*/
-class SyntheticMethodReference
+public class SyntheticMethodReference
extends SyntheticReference<SyntheticMethodReference, SyntheticMethodDefinition, DexProgramClass>
implements SyntheticProgramReference, Rewritable<SyntheticMethodReference> {
final DexMethod method;
@@ -39,7 +39,7 @@
}
@Override
- SyntheticMethodDefinition lookupDefinition(Function<DexType, DexClass> definitions) {
+ public SyntheticMethodDefinition lookupDefinition(Function<DexType, DexClass> definitions) {
DexClass clazz = definitions.apply(method.holder);
if (clazz == null) {
return null;
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 83beaff..676989f 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -86,6 +86,8 @@
public final SyntheticKind OBJECT_CLONE_OUTLINE = generator.forSingleMethod("ObjectCloneOutline");
public final SyntheticKind TO_STRING_IF_NOT_NULL =
generator.forSingleMethodWithGlobalMerging("ToStringIfNotNull");
+ public final SyntheticKind THROW_CCE_IF_NOT_EQUALS =
+ generator.forSingleMethodWithGlobalMerging("ThrowCCEIfNotEquals");
public final SyntheticKind THROW_CCE_IF_NOT_NULL =
generator.forSingleMethodWithGlobalMerging("ThrowCCEIfNotNull");
public final SyntheticKind NON_NULL = generator.forSingleMethodWithGlobalMerging("NonNull");
diff --git a/src/main/java/com/android/tools/r8/utils/CollectionUtils.java b/src/main/java/com/android/tools/r8/utils/CollectionUtils.java
index 5434871..c08d714 100644
--- a/src/main/java/com/android/tools/r8/utils/CollectionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/CollectionUtils.java
@@ -83,4 +83,8 @@
}
return returnArr;
}
+
+ public static <K> Collection<K> unmodifiableForTesting(Collection<K> map) {
+ return InternalOptions.assertionsEnabled() ? Collections.unmodifiableCollection(map) : map;
+ }
}
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 1e182f5..c1120ae 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -785,6 +785,9 @@
System.getProperty("com.android.tools.r8.experimentalTraceAndroidEnumSerialization") != null;
public boolean enableXmlInlining =
System.getProperty("com.android.tools.r8.enableXmlInlining") != null;
+ // Enable color inlining in code, i.e `getResources().getColor(..)`.
+ public boolean enableColorInlining =
+ System.getProperty("com.android.tools.r8.enableColorInlining") != null;
// Flag to turn on/offLoad/store optimization in the Cf back-end.
public boolean enableLoadStoreOptimization = true;
diff --git a/src/main/java/com/android/tools/r8/utils/positions/ClassFilePositionToMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/ClassFilePositionToMappedRangeMapper.java
index 08d30ac..99c3df9 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/ClassFilePositionToMappedRangeMapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/ClassFilePositionToMappedRangeMapper.java
@@ -30,7 +30,7 @@
@Override
public List<MappedPosition> getMappedPositions(
ProgramMethod method,
- PositionRemapper positionRemapper,
+ ClassPositionRemapper positionRemapper,
boolean hasOverloads,
boolean canUseDexPc,
int pcEncodingCutoff) {
@@ -45,7 +45,7 @@
}
private List<MappedPosition> getMappedPositionsRemapped(
- ProgramMethod method, PositionRemapper positionRemapper, boolean hasOverloads) {
+ ProgramMethod method, ClassPositionRemapper positionRemapper, boolean hasOverloads) {
List<MappedPosition> mappedPositions = new ArrayList<>();
// Do the actual processing for each method.
CfCode oldCode = method.getDefinition().getCode().asCfCode();
@@ -101,7 +101,7 @@
@SuppressWarnings("UnusedVariable")
private List<MappedPosition> getPcEncodedPositions(
- ProgramMethod method, PositionRemapper positionRemapper) {
+ ProgramMethod method, ClassPositionRemapper positionRemapper) {
List<MappedPosition> mappedPositions = new ArrayList<>();
// Do the actual processing for each method.
CfCode oldCode = method.getDefinition().getCode().asCfCode();
diff --git a/src/main/java/com/android/tools/r8/utils/positions/PositionRemapper.java b/src/main/java/com/android/tools/r8/utils/positions/ClassPositionRemapper.java
similarity index 94%
rename from src/main/java/com/android/tools/r8/utils/positions/PositionRemapper.java
rename to src/main/java/com/android/tools/r8/utils/positions/ClassPositionRemapper.java
index e769917..5444395 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/PositionRemapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/ClassPositionRemapper.java
@@ -1,7 +1,6 @@
-// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2025, 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.positions;
import com.android.tools.r8.ResourceException;
@@ -27,14 +26,14 @@
// PositionRemapper is a stateful function which takes a position (represented by a
// DexDebugPositionState) and returns a remapped Position.
-public interface PositionRemapper {
+public interface ClassPositionRemapper {
Pair<Position, Position> createRemappedPosition(Position position);
- static PositionRemapper getPositionRemapper(
+ static ClassPositionRemapper getPositionRemapper(
AppView<?> appView, CfLineToMethodMapper cfLineToMethodMapper) {
boolean identityMapping = appView.options().lineNumberOptimization.isOff();
- PositionRemapper positionRemapper =
+ ClassPositionRemapper positionRemapper =
identityMapping
? new IdentityPositionRemapper()
: new OptimizingPositionRemapper(appView.options());
@@ -48,7 +47,7 @@
void setCurrentMethod(DexEncodedMethod definition);
- class IdentityPositionRemapper implements PositionRemapper {
+ class IdentityPositionRemapper implements ClassPositionRemapper {
@Override
public Pair<Position, Position> createRemappedPosition(Position position) {
@@ -63,7 +62,7 @@
}
}
- class OptimizingPositionRemapper implements PositionRemapper {
+ class OptimizingPositionRemapper implements ClassPositionRemapper {
private final int maxLineDelta;
private DexMethod previousMethod = null;
private int previousSourceLine = -1;
@@ -104,13 +103,13 @@
}
}
- class KotlinInlineFunctionPositionRemapper implements PositionRemapper {
+ class KotlinInlineFunctionPositionRemapper implements ClassPositionRemapper {
private final AppView<?> appView;
private final DexItemFactory factory;
private final Map<DexType, Result> parsedKotlinSourceDebugExtensions = new IdentityHashMap<>();
private final CfLineToMethodMapper lineToMethodMapper;
- private final PositionRemapper baseRemapper;
+ private final ClassPositionRemapper baseRemapper;
// Fields for the current context.
private DexEncodedMethod currentMethod;
@@ -118,7 +117,7 @@
private KotlinInlineFunctionPositionRemapper(
AppView<?> appView,
- PositionRemapper baseRemapper,
+ ClassPositionRemapper baseRemapper,
CfLineToMethodMapper lineToMethodMapper) {
this.appView = appView;
this.factory = appView.dexItemFactory();
diff --git a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java
index 099e233..0b28e8e 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java
@@ -84,7 +84,7 @@
private final PositionEventEmitter positionEventEmitter;
private final List<MappedPosition> mappedPositions;
- private final PositionRemapper positionRemapper;
+ private final ClassPositionRemapper positionRemapper;
private final List<DexDebugEvent> processedEvents;
// Keep track of what PC has been emitted.
@@ -95,7 +95,7 @@
public DexDebugPositionStateVisitor(
PositionEventEmitter positionEventEmitter,
List<MappedPosition> mappedPositions,
- PositionRemapper positionRemapper,
+ ClassPositionRemapper positionRemapper,
List<DexDebugEvent> processedEvents,
DexItemFactory factory,
int startLine,
@@ -181,7 +181,7 @@
}
public List<MappedPosition> optimizeDexCodePositions(
- ProgramMethod method, PositionRemapper positionRemapper) {
+ ProgramMethod method, ClassPositionRemapper positionRemapper) {
List<MappedPosition> mappedPositions = new ArrayList<>();
// Do the actual processing for each method.
DexApplication application = appView.appInfo().app();
diff --git a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java
index a583e5f..7b67169 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java
@@ -34,7 +34,7 @@
}
public List<MappedPosition> optimizeDexCodePositionsForPc(
- ProgramMethod method, PositionRemapper positionRemapper, int pcEncodingCutoff) {
+ ProgramMethod method, ClassPositionRemapper positionRemapper, int pcEncodingCutoff) {
List<MappedPosition> mappedPositions = new ArrayList<>();
// Do the actual processing for each method.
DexCode dexCode = method.getDefinition().getCode().asDexCode();
@@ -96,7 +96,7 @@
int startPc,
int endPc,
Position position,
- PositionRemapper remapper,
+ ClassPositionRemapper remapper,
List<MappedPosition> mappedPositions) {
Pair<Position, Position> remappedPosition = remapper.createRemappedPosition(position);
Position oldPosition = remappedPosition.getFirst();
diff --git a/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java
index 12b0d73..aa55443 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java
@@ -61,7 +61,7 @@
// used. We still run the line number optimizer to collect line numbers and inline frame
// information for the mapping file.
timing.begin("Line number remapping");
- ClassNameMapper mapper = run(appView, inputApp, originalSourceFiles, representation);
+ ClassNameMapper mapper = run(appView, inputApp, originalSourceFiles, representation, timing);
timing.end();
if (appView.options().mappingComposeOptions().generatedClassNameMapperConsumer != null) {
appView.options().mappingComposeOptions().generatedClassNameMapperConsumer.accept(mapper);
@@ -104,7 +104,8 @@
AppView<?> appView,
AndroidApp inputApp,
OriginalSourceFiles originalSourceFiles,
- DebugRepresentationPredicate representation) {
+ DebugRepresentationPredicate representation,
+ Timing timing) {
// For finding methods in kotlin files based on SourceDebugExtensions, we use a line method map.
// We create it here to ensure it is only reading class files once.
// TODO(b/220999985): Make this threaded per virtual file. Possibly pull the kotlin line mapping
@@ -118,6 +119,7 @@
MappedPositionToClassNameMapperBuilder.builder(appView, originalSourceFiles);
// Collect which files contain which classes that need to have their line numbers optimized.
+ timing.begin("Process classes");
for (DexProgramClass clazz : appView.appInfo().classes()) {
if (shouldRun(clazz, appView)) {
runForClass(
@@ -126,12 +128,16 @@
representation,
builder,
cfLineToMethodMapper,
- positionToMappedRangeMapper);
+ positionToMappedRangeMapper,
+ timing);
}
}
+ timing.end();
// Update all the debug-info objects.
+ timing.begin("Update debug info in code objects");
positionToMappedRangeMapper.updateDebugInfoInCodeObjects();
+ timing.end();
return builder.build();
}
@@ -151,7 +157,9 @@
DebugRepresentationPredicate representation,
MappedPositionToClassNameMapperBuilder builder,
CfLineToMethodMapper cfLineToMethodMapper,
- PositionToMappedRangeMapper positionToMappedRangeMapper) {
+ PositionToMappedRangeMapper positionToMappedRangeMapper,
+ Timing timing) {
+ timing.begin("Prelude");
IdentityHashMap<DexString, List<ProgramMethod>> methodsByRenamedName =
groupMethodsByRenamedName(appView, clazz);
@@ -160,6 +168,8 @@
// Process methods ordered by renamed name.
List<DexString> renamedMethodNames = new ArrayList<>(methodsByRenamedName.keySet());
renamedMethodNames.sort(DexString::compareTo);
+ timing.end();
+
for (DexString methodName : renamedMethodNames) {
List<ProgramMethod> methods = methodsByRenamedName.get(methodName);
if (methods.size() > 1) {
@@ -175,38 +185,64 @@
assert verifyMethodsAreKeptDirectlyOrIndirectly(appView, methods);
}
- PositionRemapper positionRemapper =
- PositionRemapper.getPositionRemapper(appView, cfLineToMethodMapper);
+ ClassPositionRemapper positionRemapper =
+ ClassPositionRemapper.getPositionRemapper(appView, cfLineToMethodMapper);
+ timing.begin("Process methods");
for (ProgramMethod method : methods) {
- DexEncodedMethod definition = method.getDefinition();
- if (method.getName().isIdenticalTo(methodName)
- && !mustHaveResidualDebugInfo(appView.options(), definition)
- && !definition.isD8R8Synthesized()
- && methods.size() <= 1) {
- continue;
- }
- positionRemapper.setCurrentMethod(definition);
- List<MappedPosition> mappedPositions;
- int pcEncodingCutoff =
- methods.size() == 1 ? representation.getDexPcEncodingCutoff(method) : -1;
- boolean canUseDexPc = pcEncodingCutoff > 0;
- if (definition.getCode() != null
- && (definition.getCode().isCfCode() || definition.getCode().isDexCode())
- && !appView.isCfByteCodePassThrough(method)) {
- mappedPositions =
- positionToMappedRangeMapper.getMappedPositions(
- method, positionRemapper, methods.size() > 1, canUseDexPc, pcEncodingCutoff);
- } else {
- mappedPositions = new ArrayList<>();
- }
-
- classNamingBuilder.addMappedPositions(
- method, mappedPositions, positionRemapper, canUseDexPc);
- } // for each method of the group
+ runForMethod(
+ method,
+ appView,
+ classNamingBuilder,
+ methodName,
+ methods,
+ positionRemapper,
+ positionToMappedRangeMapper,
+ representation,
+ timing);
+ }
+ timing.end();
} // for each method group, grouped by name
}
+ private static void runForMethod(
+ ProgramMethod method,
+ AppView<?> appView,
+ MappedPositionToClassNamingBuilder classNamingBuilder,
+ DexString methodName,
+ List<ProgramMethod> methods,
+ ClassPositionRemapper positionRemapper,
+ PositionToMappedRangeMapper positionToMappedRangeMapper,
+ DebugRepresentationPredicate representation,
+ Timing timing) {
+ DexEncodedMethod definition = method.getDefinition();
+ if (method.getName().isIdenticalTo(methodName)
+ && !mustHaveResidualDebugInfo(appView.options(), definition)
+ && !definition.isD8R8Synthesized()
+ && methods.size() <= 1) {
+ return;
+ }
+ positionRemapper.setCurrentMethod(definition);
+ List<MappedPosition> mappedPositions;
+ int pcEncodingCutoff = methods.size() == 1 ? representation.getDexPcEncodingCutoff(method) : -1;
+ boolean canUseDexPc = pcEncodingCutoff > 0;
+ if (definition.getCode() != null
+ && (definition.getCode().isCfCode() || definition.getCode().isDexCode())
+ && !appView.isCfByteCodePassThrough(method)) {
+ timing.begin("Get mapped positions");
+ mappedPositions =
+ positionToMappedRangeMapper.getMappedPositions(
+ method, positionRemapper, methods.size() > 1, canUseDexPc, pcEncodingCutoff);
+ timing.end();
+ } else {
+ mappedPositions = new ArrayList<>();
+ }
+
+ timing.begin("Add mapped positions");
+ classNamingBuilder.addMappedPositions(method, mappedPositions, positionRemapper, canUseDexPc);
+ timing.end();
+ }
+
@SuppressWarnings("ComplexBooleanConstant")
private static boolean verifyMethodsAreKeptDirectlyOrIndirectly(
AppView<?> appView, List<ProgramMethod> methods) {
@@ -283,14 +319,12 @@
});
}
- @SuppressWarnings("UnusedVariable")
public static IdentityHashMap<DexString, List<ProgramMethod>> groupMethodsByRenamedName(
AppView<?> appView, DexProgramClass clazz) {
IdentityHashMap<DexString, List<ProgramMethod>> methodsByRenamedName =
new IdentityHashMap<>(clazz.getMethodCollection().size());
for (ProgramMethod programMethod : clazz.programMethods()) {
// Add method only if renamed, moved, or if it has debug info to map.
- DexEncodedMethod definition = programMethod.getDefinition();
DexMethod method = programMethod.getReference();
DexString renamedName = appView.getNamingLens().lookupName(method);
methodsByRenamedName
diff --git a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
index 516241e..b854592 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
@@ -220,7 +220,7 @@
public MappedPositionToClassNamingBuilder addMappedPositions(
ProgramMethod method,
List<MappedPosition> mappedPositions,
- PositionRemapper positionRemapper,
+ ClassPositionRemapper positionRemapper,
boolean canUseDexPc) {
DexEncodedMethod definition = method.getDefinition();
DexMethod residualMethod =
diff --git a/src/main/java/com/android/tools/r8/utils/positions/PositionToMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/PositionToMappedRangeMapper.java
index 78fb8d0..10a79ac 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/PositionToMappedRangeMapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/PositionToMappedRangeMapper.java
@@ -19,7 +19,7 @@
List<MappedPosition> getMappedPositions(
ProgramMethod method,
- PositionRemapper positionRemapper,
+ ClassPositionRemapper positionRemapper,
boolean hasOverloads,
boolean canUseDexPc,
int pcEncodingCutoff);
@@ -51,7 +51,7 @@
@Override
public List<MappedPosition> getMappedPositions(
ProgramMethod method,
- PositionRemapper positionRemapper,
+ ClassPositionRemapper positionRemapper,
boolean hasOverloads,
boolean canUseDexPc,
int pcEncodingCutoff) {
diff --git a/src/main/java/com/android/tools/r8/utils/positions/PositionUtils.java b/src/main/java/com/android/tools/r8/utils/positions/PositionUtils.java
index 0b0b20a..9852eeb 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/PositionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/PositionUtils.java
@@ -20,7 +20,7 @@
public class PositionUtils {
public static Position remapAndAdd(
- Position position, PositionRemapper remapper, List<MappedPosition> mappedPositions) {
+ Position position, ClassPositionRemapper remapper, List<MappedPosition> mappedPositions) {
Pair<Position, Position> remappedPosition = remapper.createRemappedPosition(position);
Position oldPosition = remappedPosition.getFirst();
Position newPosition = remappedPosition.getSecond();
diff --git a/src/test/java/com/android/tools/r8/androidresources/ColorInliningTest.java b/src/test/java/com/android/tools/r8/androidresources/ColorInliningTest.java
index ad0627c..c3d5cf9 100644
--- a/src/test/java/com/android/tools/r8/androidresources/ColorInliningTest.java
+++ b/src/test/java/com/android/tools/r8/androidresources/ColorInliningTest.java
@@ -87,8 +87,18 @@
.addKeepMainRule(FooBar.class)
.applyIf(
parameters.getPartialCompilationTestParameters().isSome(),
- rr -> rr.addR8PartialR8OptionsModification(o -> o.enableXmlInlining = true),
- rr -> rr.addOptionsModification(o -> o.enableXmlInlining = true))
+ rr ->
+ rr.addR8PartialR8OptionsModification(
+ o -> {
+ o.enableXmlInlining = true;
+ o.enableColorInlining = true;
+ }),
+ rr ->
+ rr.addOptionsModification(
+ o -> {
+ o.enableXmlInlining = true;
+ o.enableColorInlining = true;
+ }))
.applyIf(optimize, R8TestBuilder::enableOptimizedShrinking)
.applyIf(
addResourcesSubclass,
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java
index b4d42d9..282b101 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoTestBuilder.java
@@ -626,7 +626,7 @@
private final ProguardTestBuilder builder;
private final List<Consumer<List<String>>> configConsumers = new ArrayList<>();
private final List<Consumer<List<String>>> extractedRulesConsumers = new ArrayList<>();
- private final List<String> extractedRules = new ArrayList();
+ private final List<String> extractedRules = new ArrayList<>();
public PGBuilder(
KeepAnnoParameters params,
@@ -638,11 +638,7 @@
builder =
TestBase.testForProguard(KeepAnnoTestUtils.PG_VERSION, temp)
.applyIf(
- keepAnnotationLibrary == ANDROIDX,
- b ->
- b.addDefaultRuntimeLibrary(parameters())
- .addLibraryFiles(
- kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinAnnotationJar()))
+ keepAnnotationLibrary == ANDROIDX, b -> b.addDefaultRuntimeLibrary(parameters()))
.addProgramFiles(KeepAnnoTestUtils.getKeepAnnoLib(temp, keepAnnotationLibrary))
.setMinApi(parameters());
}
diff --git a/src/test/java/com/android/tools/r8/keepanno/androidx/AnnotationPatternAnyRetentionTest.java b/src/test/java/com/android/tools/r8/keepanno/androidx/AnnotationPatternAnyRetentionTest.java
index a9b13e0..1f60688 100644
--- a/src/test/java/com/android/tools/r8/keepanno/androidx/AnnotationPatternAnyRetentionTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/androidx/AnnotationPatternAnyRetentionTest.java
@@ -13,6 +13,7 @@
import androidx.annotation.keep.KeepTarget;
import androidx.annotation.keep.UsedByReflection;
import androidx.annotation.keep.UsesReflection;
+import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
import com.android.tools.r8.keepanno.KeepAnnoParameters;
import com.android.tools.r8.keepanno.KeepAnnoTestBase;
import com.android.tools.r8.utils.StringUtils;
@@ -47,6 +48,16 @@
public void test() throws Exception {
testForKeepAnnoAndroidX(parameters)
.addProgramClasses(getInputClasses())
+ .applyIfPG(
+ b ->
+ b.addProgramFiles(
+ ImmutableList.of(
+ KotlinCompilerVersion.MAX_SUPPORTED_VERSION
+ .getCompiler()
+ .getKotlinStdlibJar(),
+ KotlinCompilerVersion.MAX_SUPPORTED_VERSION
+ .getCompiler()
+ .getKotlinAnnotationJar())))
.setExcludedOuterClass(getClass())
.run(TestClass.class)
.assertSuccessWithOutput(EXPECTED)
diff --git a/src/test/java/com/android/tools/r8/keepanno/androidx/AnnotationPatternClassRetentionTest.java b/src/test/java/com/android/tools/r8/keepanno/androidx/AnnotationPatternClassRetentionTest.java
index 869b1bc..8ce1296 100644
--- a/src/test/java/com/android/tools/r8/keepanno/androidx/AnnotationPatternClassRetentionTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/androidx/AnnotationPatternClassRetentionTest.java
@@ -14,6 +14,7 @@
import androidx.annotation.keep.KeepTarget;
import androidx.annotation.keep.UsedByReflection;
import androidx.annotation.keep.UsesReflection;
+import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
import com.android.tools.r8.keepanno.KeepAnnoParameters;
import com.android.tools.r8.keepanno.KeepAnnoTestBase;
import com.android.tools.r8.utils.StringUtils;
@@ -48,6 +49,16 @@
public void test() throws Exception {
testForKeepAnnoAndroidX(parameters)
.addProgramClasses(getInputClasses())
+ .applyIfPG(
+ b ->
+ b.addProgramFiles(
+ ImmutableList.of(
+ KotlinCompilerVersion.MAX_SUPPORTED_VERSION
+ .getCompiler()
+ .getKotlinStdlibJar(),
+ KotlinCompilerVersion.MAX_SUPPORTED_VERSION
+ .getCompiler()
+ .getKotlinAnnotationJar())))
.setExcludedOuterClass(getClass())
.run(TestClass.class)
.assertSuccessWithOutput(EXPECTED)
diff --git a/src/test/java/com/android/tools/r8/regress/b426351560/Regress426351560Test.java b/src/test/java/com/android/tools/r8/regress/b426351560/Regress426351560Test.java
new file mode 100644
index 0000000..9da5d33
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b426351560/Regress426351560Test.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2025, 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.regress.b426351560;
+
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.regress.b426351560.testclasses.Regress426351560TestClasses;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class Regress426351560Test extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().withPartialCompilation().build();
+ }
+
+ private static final String EXPECTED_OUTPUT = StringUtils.lines("Hello, world!");
+
+ @Test
+ public void testR8() throws Exception {
+ // TODO(b/427887773)
+ assumeFalse(parameters.isRandomPartialCompilation());
+ testForR8(parameters)
+ .addInnerClasses(Regress426351560TestClasses.class, getClass())
+ .addKeepRules("-keep class " + Main.class.getTypeName() + " { *; }")
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ public static class Main extends Regress426351560TestClasses.A {
+ public static void main(String[] strArr) {
+ Main test = new Main();
+ System.out.println(test.defaultMethod());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b426351560/testclasses/Regress426351560TestClasses.java b/src/test/java/com/android/tools/r8/regress/b426351560/testclasses/Regress426351560TestClasses.java
new file mode 100644
index 0000000..95e16df
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b426351560/testclasses/Regress426351560TestClasses.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2025, 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.regress.b426351560.testclasses;
+
+public class Regress426351560TestClasses {
+ interface I {
+ default String defaultMethod() {
+ return "Hello, world!";
+ }
+ }
+
+ public static class A implements I {}
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/KotlinCompilerTool.java b/src/test/testbase/java/com/android/tools/r8/KotlinCompilerTool.java
index 39aab56..6a55ea8 100644
--- a/src/test/testbase/java/com/android/tools/r8/KotlinCompilerTool.java
+++ b/src/test/testbase/java/com/android/tools/r8/KotlinCompilerTool.java
@@ -111,10 +111,6 @@
this.defaultTargetVersion = defaultTargetVersion;
}
- public static KotlinCompilerVersion latest() {
- return ArrayUtils.last(values());
- }
-
public KotlinCompiler getCompiler() {
return new KotlinCompiler(this);
}