Revert "Revert due to bot failures"
This reverts commit 4a23f4c6723db6c08f8d1117600d6a2792852eb9.
Change-Id: Ibcb0eab0c11b5b40e3259f3a8ae2800fcc8bdc47
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index f6279b7..b148ad9 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -541,6 +541,8 @@
// Collect the already pruned types before creating a new app info without liveness.
// TODO: we should avoid removing liveness.
Set<DexType> prunedTypes = appView.withLiveness().appInfo().getPrunedTypes();
+ Set<DexType> prunedClasspathTypes =
+ appView.withLiveness().appInfo().getClasspathTypesIncludingPruned();
assert ArtProfileCompletenessChecker.verify(appView);
@@ -584,6 +586,7 @@
ImmediateAppSubtypingInfo.create(appView),
keptGraphConsumer,
prunedTypes,
+ prunedClasspathTypes,
finalRuntimeTypeCheckInfoBuilder);
EnqueuerResult enqueuerResult =
enqueuer.traceApplication(appView.rootSet(), executorService, timing);
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
index cc2e10c..a3581c9 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
@@ -137,6 +137,10 @@
set(Constants.ACC_ENUM);
}
+ public void unsetEnum() {
+ unset(Constants.ACC_ENUM);
+ }
+
public static class Builder extends BuilderBase<Builder, FieldAccessFlags> {
public Builder() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/SwitchHelperGenerator.java b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/SwitchHelperGenerator.java
index 72098d9..5010220 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/SwitchHelperGenerator.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/SwitchHelperGenerator.java
@@ -6,9 +6,9 @@
import static com.android.tools.r8.ir.synthetic.TypeSwitchSyntheticCfCodeProvider.allowsInlinedIntegerEquality;
-import com.android.tools.r8.cf.code.CfConstClass;
import com.android.tools.r8.cf.code.CfConstNumber;
import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.cf.code.CfNewArray;
import com.android.tools.r8.cf.code.CfReturnVoid;
import com.android.tools.r8.cf.code.CfStaticFieldWrite;
@@ -146,7 +146,21 @@
CfCode cfCode = TypeSwitchMethods.TypeSwitchMethods_switchEnumEq(factory, methodSig);
List<CfInstruction> newInstructions =
ListUtils.map(
- cfCode.getInstructions(), i -> i.isConstClass() ? new CfConstClass(enumType) : i);
+ cfCode.getInstructions(),
+ i -> {
+ if (i.isInvokeStatic()) {
+ CfInvoke invoke = i.asInvoke();
+ if (invoke.getMethod().getName().isIdenticalTo(factory.valueOfMethodName)) {
+ DexMethod newMethod =
+ factory.createMethod(
+ enumType,
+ factory.createProto(enumType, factory.stringType),
+ factory.valueOfMethodName);
+ return new CfInvoke(invoke.getOpcode(), newMethod, invoke.isInterface());
+ }
+ }
+ return i;
+ });
cfCode.setInstructions(newInstructions);
return cfCode;
},
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchIRRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchIRRewriter.java
index ea11bfe..3727b03 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchIRRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchIRRewriter.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.ir.desugar.typeswitch;
-import com.android.tools.r8.dex.code.CfOrDexInstruction;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DefaultUseRegistryWithResult;
import com.android.tools.r8.graph.DexField;
@@ -35,7 +34,6 @@
import com.android.tools.r8.utils.collections.BidirectionalManyToOneMap;
import com.google.common.collect.ImmutableList;
import java.util.IdentityHashMap;
-import java.util.ListIterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -77,12 +75,13 @@
UseRegistryWithResult<DexType, ProgramMethod> registry =
new DefaultUseRegistryWithResult<>(appView, uniqueStaticMethod) {
@Override
- public void registerConstClass(
- DexType type,
- ListIterator<? extends CfOrDexInstruction> iterator,
- boolean ignoreCompatRules) {
+ public void registerInvokeStatic(DexMethod method) {
assert getResult() == null;
- setResult(type);
+ if (method.getName().isIdenticalTo(dexItemFactory().valueOfMethodName)
+ && method.getArity() == 1
+ && method.getParameter(0).isIdenticalTo(dexItemFactory().stringType)) {
+ setResult(method.getHolderType());
+ }
}
};
DexType enumType = uniqueStaticMethod.registerCodeReferencesWithResult(registry);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchMethods.java
index 4bb8026..08611d3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchMethods.java
@@ -11,7 +11,6 @@
import com.android.tools.r8.cf.code.CfArrayLoad;
import com.android.tools.r8.cf.code.CfArrayStore;
import com.android.tools.r8.cf.code.CfCheckCast;
-import com.android.tools.r8.cf.code.CfConstClass;
import com.android.tools.r8.cf.code.CfConstNull;
import com.android.tools.r8.cf.code.CfConstNumber;
import com.android.tools.r8.cf.code.CfFrame;
@@ -42,7 +41,8 @@
public final class TypeSwitchMethods {
public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
- factory.createSynthesizedType("Ljava/lang/Enum;");
+ factory.createSynthesizedType(
+ "Lcom/android/tools/r8/cfmethodgeneration/TypeSwitchMethods$Enum;");
factory.createSynthesizedType("Ljava/lang/Number;");
factory.createSynthesizedType("[Ljava/lang/Object;");
}
@@ -60,66 +60,36 @@
CfLabel label9 = new CfLabel();
CfLabel label10 = new CfLabel();
CfLabel label11 = new CfLabel();
- CfLabel label12 = new CfLabel();
- CfLabel label13 = new CfLabel();
- CfLabel label14 = new CfLabel();
return new CfCode(
method.holder,
4,
- 7,
+ 6,
ImmutableList.of(
label0,
new CfLoad(ValueType.OBJECT, 1),
new CfLoad(ValueType.INT, 2),
new CfArrayLoad(MemberType.OBJECT),
- new CfIf(IfType.NE, ValueType.OBJECT, label11),
+ new CfIf(IfType.NE, ValueType.OBJECT, label8),
label1,
new CfConstNull(),
new CfStore(ValueType.OBJECT, 4),
label2,
- new CfConstClass(factory.createType("Ljava/lang/Enum;")),
- new CfStore(ValueType.OBJECT, 5),
- label3,
- new CfLoad(ValueType.OBJECT, 5),
- new CfInvoke(
- 182,
- factory.createMethod(
- factory.classType,
- factory.createProto(factory.booleanType),
- factory.createString("isEnum")),
- false),
- new CfIf(IfType.EQ, ValueType.INT, label6),
- label4,
- new CfLoad(ValueType.OBJECT, 5),
- new CfStore(ValueType.OBJECT, 6),
- label5,
- new CfLoad(ValueType.OBJECT, 6),
new CfLoad(ValueType.OBJECT, 3),
new CfInvoke(
184,
factory.createMethod(
- factory.createType("Ljava/lang/Enum;"),
+ factory.createType(
+ "Lcom/android/tools/r8/cfmethodgeneration/TypeSwitchMethods$Enum;"),
factory.createProto(
- factory.createType("Ljava/lang/Enum;"),
- factory.classType,
+ factory.createType(
+ "Lcom/android/tools/r8/cfmethodgeneration/TypeSwitchMethods$Enum;"),
factory.stringType),
factory.createString("valueOf")),
false),
new CfStore(ValueType.OBJECT, 4),
- label6,
- new CfFrame(
- new Int2ObjectAVLTreeMap<>(
- new int[] {0, 1, 2, 3, 4},
- new FrameType[] {
- FrameType.initializedNonNullReference(factory.objectType),
- FrameType.initializedNonNullReference(
- factory.createType("[Ljava/lang/Object;")),
- FrameType.intType(),
- FrameType.initializedNonNullReference(factory.stringType),
- FrameType.initializedNonNullReference(factory.objectType)
- })),
- new CfGoto(label8),
- label7,
+ label3,
+ new CfGoto(label5),
+ label4,
new CfFrame(
new Int2ObjectAVLTreeMap<>(
new int[] {0, 1, 2, 3, 4},
@@ -134,7 +104,7 @@
new ArrayDeque<>(
Arrays.asList(FrameType.initializedNonNullReference(factory.throwableType)))),
new CfStore(ValueType.OBJECT, 5),
- label8,
+ label5,
new CfFrame(
new Int2ObjectAVLTreeMap<>(
new int[] {0, 1, 2, 3, 4},
@@ -149,7 +119,7 @@
new CfLoad(ValueType.OBJECT, 1),
new CfLoad(ValueType.INT, 2),
new CfLoad(ValueType.OBJECT, 4),
- new CfIf(IfType.NE, ValueType.OBJECT, label9),
+ new CfIf(IfType.NE, ValueType.OBJECT, label6),
new CfNew(factory.objectType),
new CfStackInstruction(CfStackInstruction.Opcode.Dup),
new CfInvoke(
@@ -159,8 +129,8 @@
factory.createProto(factory.voidType),
factory.createString("<init>")),
false),
- new CfGoto(label10),
- label9,
+ new CfGoto(label7),
+ label6,
new CfFrame(
new Int2ObjectAVLTreeMap<>(
new int[] {0, 1, 2, 3, 4},
@@ -178,7 +148,7 @@
factory.createType("[Ljava/lang/Object;")),
FrameType.intType()))),
new CfLoad(ValueType.OBJECT, 4),
- label10,
+ label7,
new CfFrame(
new Int2ObjectAVLTreeMap<>(
new int[] {0, 1, 2, 3, 4},
@@ -197,7 +167,7 @@
FrameType.intType(),
FrameType.initializedNonNullReference(factory.objectType)))),
new CfArrayStore(MemberType.OBJECT),
- label11,
+ label8,
new CfFrame(
new Int2ObjectAVLTreeMap<>(
new int[] {0, 1, 2, 3},
@@ -212,10 +182,10 @@
new CfLoad(ValueType.OBJECT, 1),
new CfLoad(ValueType.INT, 2),
new CfArrayLoad(MemberType.OBJECT),
- new CfIfCmp(IfType.NE, ValueType.OBJECT, label12),
+ new CfIfCmp(IfType.NE, ValueType.OBJECT, label9),
new CfConstNumber(1, ValueType.INT),
- new CfGoto(label13),
- label12,
+ new CfGoto(label10),
+ label9,
new CfFrame(
new Int2ObjectAVLTreeMap<>(
new int[] {0, 1, 2, 3},
@@ -227,7 +197,7 @@
FrameType.initializedNonNullReference(factory.stringType)
})),
new CfConstNumber(0, ValueType.INT),
- label13,
+ label10,
new CfFrame(
new Int2ObjectAVLTreeMap<>(
new int[] {0, 1, 2, 3},
@@ -240,10 +210,10 @@
}),
new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
new CfReturn(ValueType.INT),
- label14),
+ label11),
ImmutableList.of(
new CfTryCatch(
- label2, label6, ImmutableList.of(factory.throwableType), ImmutableList.of(label7))),
+ label2, label3, ImmutableList.of(factory.throwableType), ImmutableList.of(label4))),
ImmutableList.of());
}
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 4d87bcd..1ccba6d 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -31,6 +31,7 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.function.Function;
import java.util.function.Predicate;
class ClassNameMinifier {
@@ -88,17 +89,30 @@
ClassRenaming computeRenaming(Timing timing) {
// Collect names we have to keep.
timing.begin("reserve");
+ if (appView.options().isMinifying()
+ && !appView.options().getProguardConfiguration().hasApplyMappingFile()) {
+ for (DexType reserved : appView.appInfo().getClasspathTypesIncludingPruned()) {
+ registerClassAsUsed(
+ reserved,
+ reserved.getDescriptor(),
+ appView.appInfo()::definitionForWithoutExistenceAssert);
+ }
+ }
for (ProgramOrClasspathClass clazz : classes) {
- DexString descriptor = classNamingStrategy.reservedDescriptor(clazz.getType());
+ DexType type = clazz.getType();
+ DexString descriptor = classNamingStrategy.reservedDescriptor(type, appView::definitionFor);
if (descriptor != null) {
- assert !renaming.containsKey(clazz.getType());
- registerClassAsUsed(clazz.getType(), descriptor);
+ assert !renaming.containsKey(type);
+ registerClassAsUsed(type, descriptor, appView::definitionFor);
}
}
appView
.appInfo()
.getMissingClasses()
- .forEach(missingClass -> registerClassAsUsed(missingClass, missingClass.getDescriptor()));
+ .forEach(
+ missingClass ->
+ registerClassAsUsed(
+ missingClass, missingClass.getDescriptor(), appView::definitionFor));
timing.end();
timing.begin("rename-classes");
@@ -167,29 +181,30 @@
// return type. As we don't need the class, we can rename it to anything as long as it is
// unique.
assert appView.definitionFor(type) == null;
- DexString descriptor = classNamingStrategy.reservedDescriptor(type);
+ DexString descriptor = classNamingStrategy.reservedDescriptor(type, appView::definitionFor);
renaming.put(type, descriptor != null ? descriptor : topLevelState.nextTypeName(type));
}
}
- private void registerClassAsUsed(DexType type, DexString descriptor) {
+ private void registerClassAsUsed(
+ DexType type, DexString descriptor, Function<DexType, DexClass> definitionFor) {
renaming.put(type, descriptor);
setUsedTypeName(descriptor.toString());
if (keepInnerClassStructure) {
- DexType outerClass = getOutClassForType(type);
+ DexType outerClass = getOutClassForType(type, definitionFor);
if (outerClass != null) {
if (!renaming.containsKey(outerClass)
- && classNamingStrategy.reservedDescriptor(outerClass) == null) {
+ && classNamingStrategy.reservedDescriptor(outerClass, definitionFor) == null) {
// The outer class was not previously kept and will not be kept.
// We have to force keep the outer class now.
- registerClassAsUsed(outerClass, outerClass.descriptor);
+ registerClassAsUsed(outerClass, outerClass.descriptor, definitionFor);
}
}
}
}
- private DexType getOutClassForType(DexType type) {
- DexClass clazz = appView.definitionFor(type);
+ private DexType getOutClassForType(DexType type, Function<DexType, DexClass> definitionFor) {
+ DexClass clazz = definitionFor.apply(type);
if (clazz == null) {
return null;
}
@@ -211,7 +226,7 @@
// When keeping the nesting structure of inner classes, bind this type to the live context.
// Note that such live context might not be always the enclosing class. E.g., a local class
// does not have a direct enclosing class, but we use the holder of the enclosing method here.
- DexType outerClass = getOutClassForType(type);
+ DexType outerClass = getOutClassForType(type, appView::definitionFor);
if (outerClass != null) {
DexClass clazz = appView.definitionFor(type);
assert clazz != null;
@@ -320,7 +335,7 @@
* @param type The type to find a reserved descriptor for
* @return The reserved descriptor
*/
- DexString reservedDescriptor(DexType type);
+ DexString reservedDescriptor(DexType type, Function<DexType, DexClass> definitionFor);
boolean isRenamedByApplyMapping(DexType type);
}
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 6828ffd..aae1702 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -45,6 +45,7 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.BiPredicate;
+import java.util.function.Function;
import java.util.function.Predicate;
public class Minifier {
@@ -76,7 +77,7 @@
assert new MinifiedRenaming(
appView, classRenaming, MethodRenaming.empty(), FieldRenaming.empty())
- .verifyNoCollisions(appView.appInfo().classes(), appView.dexItemFactory());
+ .verifyNoCollisions(appView.appInfo(), appView.dexItemFactory());
MemberNamingStrategy minifyMembers = new MinifierMemberNamingStrategy(appView);
timing.begin("MinifyMethods");
@@ -86,7 +87,7 @@
timing.end();
assert new MinifiedRenaming(appView, classRenaming, methodRenaming, FieldRenaming.empty())
- .verifyNoCollisions(appView.appInfo().classes(), appView.dexItemFactory());
+ .verifyNoCollisions(appView.appInfo(), appView.dexItemFactory());
timing.begin("MinifyFields");
FieldRenaming fieldRenaming =
@@ -100,7 +101,7 @@
timing.end();
NamingLens lens = new MinifiedRenaming(appView, classRenaming, methodRenaming, fieldRenaming);
- assert lens.verifyNoCollisions(appView.appInfo().classes(), appView.dexItemFactory());
+ assert lens.verifyNoCollisions(appView.appInfo(), appView.dexItemFactory());
appView.testing().namingLensConsumer.accept(appView.dexItemFactory(), lens);
appView.notifyOptimizationFinishedForTesting();
@@ -289,8 +290,8 @@
}
@Override
- public DexString reservedDescriptor(DexType type) {
- DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
+ public DexString reservedDescriptor(DexType type, Function<DexType, DexClass> definitionFor) {
+ DexProgramClass clazz = asProgramClassOrNull(definitionFor.apply(type));
if (clazz == null || !appView.getKeepInfo(clazz).isMinificationAllowed(appView.options())) {
return type.getDescriptor();
}
diff --git a/src/main/java/com/android/tools/r8/naming/NamingLens.java b/src/main/java/com/android/tools/r8/naming/NamingLens.java
index bc8b12f..dbe8fa4 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingLens.java
@@ -24,6 +24,8 @@
import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
import com.google.common.collect.Sets;
import java.util.Arrays;
+import java.util.IdentityHashMap;
+import java.util.Map;
import java.util.Set;
/**
@@ -157,14 +159,35 @@
public abstract boolean verifyRenamingConsistentWithResolution(DexMethod item);
public final boolean verifyNoCollisions(
- Iterable<DexProgramClass> classes, DexItemFactory dexItemFactory) {
+ AppInfoWithLiveness appInfo, DexItemFactory dexItemFactory) {
Set<DexReference> references = Sets.newIdentityHashSet();
- for (DexProgramClass clazz : classes) {
+ Map<DexType, DexType> classpathTypes = new IdentityHashMap<>();
+ appInfo
+ .getClasspathTypesIncludingPruned()
+ .forEach(
+ type -> {
+ DexType newType = lookupType(type, dexItemFactory);
+ classpathTypes.put(newType, type);
+ });
+ for (DexProgramClass clazz : appInfo.classes()) {
{
DexType newType = lookupType(clazz.type, dexItemFactory);
boolean referencesChanged = references.add(newType);
assert referencesChanged
: "Duplicate definition of type `" + newType.toSourceString() + "`";
+ if (classpathTypes.containsKey(newType)) {
+ // If we have a classpath and program type, then it can be renamed the same way. It is
+ // forbidden to rename a program type into a classpath type, since it would lead to
+ // duplicated types.
+ assert classpathTypes.get(newType).isIdenticalTo(clazz.type)
+ : "Duplicate predecessor of type `"
+ + newType.toSourceString()
+ + "`: `"
+ + classpathTypes.get(newType).toSourceString()
+ + "`,`"
+ + clazz.type.toSourceString()
+ + "`";
+ }
}
for (DexEncodedField field : clazz.fields()) {
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 78baeb5..1f16d8c 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -56,6 +56,7 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.BiPredicate;
+import java.util.function.Function;
import java.util.function.Predicate;
/**
@@ -413,7 +414,7 @@
}
@Override
- public DexString reservedDescriptor(DexType type) {
+ public DexString reservedDescriptor(DexType type, Function<DexType, DexClass> definitionFor) {
// TODO(b/136694827): Check for already used and report an error. It seems like this can be
// done already in the reservation step for classes since there is only one 'path', unlike
// members that can be reserved differently in the hierarchy.
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index b9dee9f..3d5de3b 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -65,6 +65,7 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.PredicateSet;
+import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.Visibility;
import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
@@ -171,6 +172,13 @@
* ).
*/
public final Object2BooleanMap<DexMember<?, ?>> identifierNameStrings;
+
+ /**
+ * A set of classpath types that are not referenced from the app, but which names can still lead
+ * to collusions in the minifier.
+ */
+ final Set<DexType> prunedClasspathTypes;
+
/** A set of types that have been removed by the {@link TreePruner}. */
final Set<DexType> prunedTypes;
/** A map from switchmap class types to their corresponding switchmaps. */
@@ -205,6 +213,7 @@
PredicateSet<DexType> alwaysClassInline,
Object2BooleanMap<DexMember<?, ?>> identifierNameStrings,
Set<DexType> prunedTypes,
+ Set<DexType> prunedClasspathTypes,
Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
Set<DexType> lockCandidates,
Map<DexType, Visibility> initClassReferences,
@@ -230,6 +239,7 @@
this.alwaysClassInline = alwaysClassInline;
this.identifierNameStrings = identifierNameStrings;
this.prunedTypes = prunedTypes;
+ this.prunedClasspathTypes = prunedClasspathTypes;
this.switchMaps = switchMaps;
this.lockCandidates = lockCandidates;
this.initClassReferences = initClassReferences;
@@ -263,6 +273,7 @@
previous.alwaysClassInline,
previous.identifierNameStrings,
previous.prunedTypes,
+ previous.prunedClasspathTypes,
previous.switchMaps,
previous.lockCandidates,
previous.initClassReferences,
@@ -299,6 +310,7 @@
prunedItems.hasRemovedClasses()
? CollectionUtils.mergeSets(previous.prunedTypes, prunedItems.getRemovedClasses())
: previous.prunedTypes,
+ previous.prunedClasspathTypes,
previous.switchMaps,
pruneClasses(previous.lockCandidates, prunedItems, tasks),
pruneMapFromClasses(previous.initClassReferences, prunedItems, tasks),
@@ -439,6 +451,7 @@
alwaysClassInline,
identifierNameStrings,
prunedTypes,
+ prunedClasspathTypes,
switchMaps,
lockCandidates,
initClassReferences,
@@ -509,6 +522,7 @@
this.alwaysClassInline = previous.alwaysClassInline;
this.identifierNameStrings = previous.identifierNameStrings;
this.prunedTypes = previous.prunedTypes;
+ this.prunedClasspathTypes = previous.prunedClasspathTypes;
this.switchMaps = switchMaps;
this.lockCandidates = previous.lockCandidates;
this.initClassReferences = previous.initClassReferences;
@@ -1007,6 +1021,7 @@
lens.rewriteReferenceKeys(identifierNameStrings),
// Don't rewrite pruned types - the removed types are identified by their original name.
prunedTypes,
+ prunedClasspathTypes,
lens.rewriteFieldKeys(switchMaps),
lens.rewriteReferences(lockCandidates),
rewriteInitClassReferences(lens),
@@ -1046,6 +1061,18 @@
return prunedTypes;
}
+ public Set<DexType> getClasspathTypesIncludingPruned() {
+ assert checkIfObsolete();
+ Collection<DexClasspathClass> classpathClasses = app().asDirect().classpathClasses();
+ Set<DexType> classpath =
+ SetUtils.newIdentityHashSet(classpathClasses.size() + prunedClasspathTypes.size());
+ for (DexClasspathClass cp : classpathClasses) {
+ classpath.add(cp.getType());
+ }
+ classpath.addAll(prunedClasspathTypes);
+ return classpath;
+ }
+
public DexClassAndMethod lookupSingleTarget(
AppView<AppInfoWithLiveness> appView,
InvokeType type,
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index f17e905..9788f61 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -334,6 +334,9 @@
/** Set of types that was pruned during the first round of tree shaking. */
private final Set<DexType> initialPrunedTypes;
+ /** List of classpath types that was pruned during the first round of tree shaking. */
+ private final Set<DexType> prunedClasspathTypes;
+
private final Set<DexType> noClassMerging = Sets.newIdentityHashSet();
/** Mapping from each unused interface to the set of live types that implements the interface. */
@@ -477,6 +480,7 @@
keptGraphConsumer,
mode,
null,
+ null,
null);
}
@@ -488,6 +492,7 @@
GraphConsumer keptGraphConsumer,
Mode mode,
Set<DexType> initialPrunedTypes,
+ Set<DexType> prunedClasspathTypes,
RuntimeTypeCheckInfo.Builder runtimeTypeCheckInfoBuilder) {
assert appView.appServices() != null;
InternalOptions options = appView.options();
@@ -512,6 +517,7 @@
? ProguardCompatibilityActions.builder()
: null;
this.initialPrunedTypes = initialPrunedTypes;
+ this.prunedClasspathTypes = prunedClasspathTypes;
if (options.isOptimizedResourceShrinking()) {
R8ResourceShrinkerState resourceShrinkerState = appView.getResourceShrinkerState();
@@ -4530,6 +4536,15 @@
// Add just referenced non-program types. We can't replace the program classes at this point as
// they are needed in tree pruning.
+ ImmutableSet.Builder<DexType> prunedClasspathTypesBuilder = ImmutableSet.builder();
+ if (prunedClasspathTypes != null) {
+ prunedClasspathTypesBuilder.addAll(prunedClasspathTypes);
+ }
+ for (DexClasspathClass classpathClass : appInfo.app().asDirect().classpathClasses()) {
+ if (!classpathClasses.contains(classpathClass)) {
+ prunedClasspathTypesBuilder.add(classpathClass.getType());
+ }
+ }
DirectMappedDexApplication app =
appInfo
.app()
@@ -4585,6 +4600,7 @@
joinIdentifierNameStrings(
rootSet.identifierNameStrings, reflectiveIdentification.getIdentifierNameStrings()),
emptySet(),
+ prunedClasspathTypesBuilder.build(),
Collections.emptyMap(),
lockCandidates,
initClassReferences,
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
index 1095378..b4f2984 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
@@ -36,6 +36,7 @@
ImmediateAppSubtypingInfo subtypingInfo,
GraphConsumer keptGraphConsumer,
Set<DexType> initialPrunedTypes,
+ Set<DexType> prunedClasspathTypes,
RuntimeTypeCheckInfo.Builder runtimeTypeCheckInfoBuilder) {
ProfileCollectionAdditions profileCollectionAdditions =
ProfileCollectionAdditions.create(appView);
@@ -48,6 +49,7 @@
keptGraphConsumer,
Mode.FINAL_TREE_SHAKING,
initialPrunedTypes,
+ prunedClasspathTypes,
runtimeTypeCheckInfoBuilder);
appView.withProtoShrinker(
shrinker -> enqueuer.setInitialDeadProtoTypes(shrinker.getDeadProtoTypes()));
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/TypeSwitchMethods.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/TypeSwitchMethods.java
index a9e65d0..16b1a6c 100644
--- a/src/test/java/com/android/tools/r8/cfmethodgeneration/TypeSwitchMethods.java
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/TypeSwitchMethods.java
@@ -6,17 +6,17 @@
public class TypeSwitchMethods {
+ private enum Enum {
+ A;
+ }
+
// By design this is lock-free so the JVM may compute several times the same value.
public static boolean switchEnumEq(Object value, Object[] cache, int index, String name) {
if (cache[index] == null) {
Object resolved = null;
try {
// Enum.class should be replaced when used by the correct enum class.
- Class<?> clazz = Enum.class;
- if (clazz.isEnum()) {
- Class<? extends Enum> enumClazz = (Class<? extends Enum>) clazz;
- resolved = Enum.valueOf(enumClazz, name);
- }
+ resolved = Enum.valueOf(name);
} catch (Throwable t) {
}
// R8 sets a sentinel if resolution has failed.
diff --git a/src/test/java/com/android/tools/r8/naming/ProgramTypeRenamedAsClasspathTypeTest.java b/src/test/java/com/android/tools/r8/naming/ProgramTypeRenamedAsClasspathTypeTest.java
new file mode 100644
index 0000000..1ba9022
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/ProgramTypeRenamedAsClasspathTypeTest.java
@@ -0,0 +1,115 @@
+// 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.naming;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.JdkClassFileProvider;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Set;
+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 ProgramTypeRenamedAsClasspathTypeTest extends TestBase {
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withCfRuntimesStartingFromIncluding(CfVm.JDK24)
+ .withDexRuntimes()
+ .withAllApiLevelsAlsoForCf()
+ .withPartialCompilation()
+ .build();
+ }
+
+ public static String EXPECTED_OUTPUT = StringUtils.lines("program side", "classpath side");
+
+ @Test
+ public void testJvm() throws Exception {
+ parameters.assumeJvmTestParameters();
+ testForJvm(parameters)
+ .addInnerClassesAndStrippedOuter(getClass())
+ .run(parameters.getRuntime(), ProgramMain.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testR8Split() throws Exception {
+ parameters.assumeR8TestParameters();
+ Set<String> types = new HashSet<>();
+ R8TestCompileResult compile =
+ testForR8(Backend.CF)
+ .addProgramClasses(ClasspathSide.class, ClasspathMain.class)
+ .addLibraryProvider(JdkClassFileProvider.fromSystemJdk())
+ .addKeepMainRule(ClasspathMain.class)
+ .addKeepClassAndMembersRulesWithAllowObfuscation(ClasspathSide.class)
+ .compile();
+ compile.inspect(
+ i -> {
+ assertEquals(2, i.allClasses().size());
+ for (FoundClassSubject clazz : i.allClasses()) {
+ types.add(clazz.getDexProgramClass().getTypeName());
+ }
+ });
+ Path classpath = compile.writeToZip();
+ Path runClasspath = testForD8(parameters).addProgramFiles(classpath).compile().writeToZip();
+ testForR8(parameters)
+ .addProgramClasses(ProgramSide.class, ProgramMain.class)
+ .addClasspathFiles(classpath)
+ .addKeepMainRule(ProgramMain.class)
+ .addKeepClassAndMembersRulesWithAllowObfuscation(ProgramSide.class)
+ .compile()
+ .inspect(
+ i -> {
+ assertEquals(2, i.allClasses().size());
+ for (FoundClassSubject clazz : i.allClasses()) {
+ types.add(clazz.getDexProgramClass().getTypeName());
+ }
+ assertEquals(4, types.size());
+ })
+ .addRunClasspathFiles(runClasspath)
+ .run(parameters.getRuntime(), ProgramMain.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ static class ClasspathMain {
+ public static void main(String[] args) {
+ ClasspathSide.print();
+ }
+ }
+
+ static class ClasspathSide {
+ public static void print() {
+ System.out.println("classpath side");
+ }
+ }
+
+ public static class ProgramMain {
+ public static void main(String[] args) {
+ ProgramSide.print();
+ ClasspathMain.main(args);
+ }
+ }
+
+ static class ProgramSide {
+ public static void print() {
+ System.out.println("program side");
+ }
+ }
+}
diff --git a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumSwitchOldSyntaxTest.java b/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumSwitchOldSyntaxTest.java
index 52a69d3..7f4b2de 100644
--- a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumSwitchOldSyntaxTest.java
+++ b/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumSwitchOldSyntaxTest.java
@@ -3,12 +3,16 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.jdk24.switchpatternmatching;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
import com.android.tools.r8.JdkClassFileProvider;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -60,9 +64,19 @@
.addKeepMainRule(Main.class)
.addKeepEnumsRule()
.run(parameters.getRuntime(), Main.class)
+ .inspect(this::assert2Classes)
.assertSuccessWithOutput(EXPECTED_OUTPUT);
}
+ private void assert2Classes(CodeInspector i) {
+ if (parameters.getPartialCompilationTestParameters().isSome()) {
+ return;
+ }
+ assertTrue(i.clazz(E.class).isPresent());
+ assertTrue(i.clazz(Main.class).isPresent());
+ assertEquals(2, i.allClasses().size());
+ }
+
public enum E {
E1,
E2,
diff --git a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumSwitchOldSyntaxV2Test.java b/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumSwitchOldSyntaxV2Test.java
index c1e3f1d..fede4cb 100644
--- a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumSwitchOldSyntaxV2Test.java
+++ b/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumSwitchOldSyntaxV2Test.java
@@ -12,7 +12,6 @@
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.utils.StringUtils;
-import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -69,7 +68,6 @@
@Test
public void testR8Split() throws Exception {
- Assume.assumeFalse("TODO(b/414335863)", parameters.isRandomPartialCompilation());
parameters.assumeR8TestParameters();
R8TestCompileResult compile =
testForR8(Backend.CF)
diff --git a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/TypeSwitchEnumAsClassTest.java b/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/TypeSwitchEnumAsClassTest.java
new file mode 100644
index 0000000..1e99ca6
--- /dev/null
+++ b/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/TypeSwitchEnumAsClassTest.java
@@ -0,0 +1,130 @@
+// 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.jdk24.switchpatternmatching;
+
+import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.JdkClassFileProvider;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+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 TypeSwitchEnumAsClassTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withCfRuntimesStartingFromIncluding(CfVm.JDK24)
+ .withDexRuntimes()
+ .withAllApiLevelsAlsoForCf()
+ .withPartialCompilation()
+ .build();
+ }
+
+ public static String EXPECTED_OUTPUT =
+ StringUtils.lines("null", "String", "Array of int, length = 0", "Other");
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ CodeInspector inspector = new CodeInspector(ToolHelper.getClassFileForTestClass(Main.class));
+ assertTrue(
+ hasJdk21TypeSwitch(inspector.clazz(Main.class).uniqueMethodWithOriginalName("typeSwitch")));
+
+ parameters.assumeJvmTestParameters();
+ testForJvm(parameters)
+ .apply(this::addModifiedProgramClasses)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::assertResult);
+ }
+
+ private void assertResult(TestRunResult<?> r) {
+ r.assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ private <T extends TestBuilder<?, T>> void addModifiedProgramClasses(
+ TestBuilder<?, T> testBuilder) throws Exception {
+ testBuilder
+ .addProgramClassFileData(transformer(Main.class).clearNest().transform())
+ .addProgramClassFileData(transformer(C.class).clearNest().transform())
+ .addProgramClassFileData(transformer(Color.class).clearEnum().clearNest().transform());
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForD8(parameters)
+ .apply(this::addModifiedProgramClasses)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::assertResult);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ parameters.assumeR8TestParameters();
+ testForR8(parameters)
+ .apply(this::addModifiedProgramClasses)
+ .applyIf(
+ parameters.isCfRuntime(),
+ b -> b.addLibraryProvider(JdkClassFileProvider.fromSystemJdk()))
+ .addKeepMainRule(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::assertResult);
+ }
+
+ // Enum will be a class at runtime.
+ enum Color {
+ RED,
+ GREEN,
+ BLUE;
+ }
+
+ // Class will be missing at runtime.
+ static class C {
+
+ @Override
+ public String toString() {
+ return "CCC";
+ }
+ }
+
+ static class Main {
+
+ static void typeSwitch(Object obj) {
+ switch (obj) {
+ case null -> System.out.println("null");
+ case Color.RED -> System.out.println("RED!!!");
+ case Color.BLUE -> System.out.println("BLUE!!!");
+ case Color.GREEN -> System.out.println("GREEN!!!");
+ case String string -> System.out.println("String");
+ case C c -> System.out.println(c.toString() + "!!!");
+ case int[] intArray -> System.out.println("Array of int, length = " + intArray.length);
+ default -> System.out.println("Other");
+ }
+ }
+
+ public static void main(String[] args) {
+ typeSwitch(null);
+ typeSwitch("s");
+ typeSwitch(new int[] {});
+ typeSwitch(new Object());
+ }
+ }
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/testbase/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 8b3e63c..1998f5e 100644
--- a/src/test/testbase/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/testbase/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -415,6 +415,53 @@
});
}
+ // Note that this clears <init> and <clinit> methods.
+ public ClassFileTransformer clearEnum() {
+ return addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public void visit(
+ int version,
+ int access,
+ String name,
+ String signature,
+ String superName,
+ String[] interfaces) {
+ ClassAccessFlags classAccessFlags = ClassAccessFlags.fromCfAccessFlags(access);
+ assert classAccessFlags.isEnum();
+ classAccessFlags.unsetEnum();
+ String newSuper = superName.equals("java/lang/Enum") ? "java/lang/Object" : superName;
+ super.visit(
+ version,
+ classAccessFlags.getAsCfAccessFlags(),
+ name,
+ signature,
+ newSuper,
+ interfaces);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String descriptor, String signature, String[] exceptions) {
+ if (name.equals("<clinit>") || name.equals("<init>")) {
+ return null;
+ }
+ return super.visitMethod(access, name, descriptor, signature, exceptions);
+ }
+
+ @Override
+ public FieldVisitor visitField(
+ int access, String name, String descriptor, String signature, Object value) {
+ FieldAccessFlags fieldAccessFlags = FieldAccessFlags.fromCfAccessFlags(access);
+ if (fieldAccessFlags.isEnum()) {
+ fieldAccessFlags.unsetEnum();
+ }
+ return super.visitField(
+ fieldAccessFlags.getAsCfAccessFlags(), name, descriptor, signature, value);
+ }
+ });
+ }
+
public ClassFileTransformer clearNest() {
return setMinVersion(CfVm.JDK11)
.addClassTransformer(