Merge commit '17024bf79ef3ea7a81018e3bd7b67358e328c3d3' into dev-release Change-Id: Iee886bcc9c9cfe23e1a57969fdb239e19cb5ea31
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs.json b/src/library_desugar/jdk11/desugar_jdk_libs.json index 3d23d9f..0020bab 100644 --- a/src/library_desugar/jdk11/desugar_jdk_libs.json +++ b/src/library_desugar/jdk11/desugar_jdk_libs.json
@@ -403,12 +403,34 @@ "java.lang.Object java.util.concurrent.atomic.AtomicReference#getAndAccumulate(java.lang.Object, java.util.function.BinaryOperator)": "java.util.concurrent.atomic.DesugarAtomicReference", "java.lang.Object java.util.concurrent.atomic.AtomicReference#getAndUpdate(java.util.function.UnaryOperator)": "java.util.concurrent.atomic.DesugarAtomicReference", "java.lang.Object java.util.concurrent.atomic.AtomicReference#updateAndGet(java.util.function.UnaryOperator)": "java.util.concurrent.atomic.DesugarAtomicReference", + "java.util.Collection java.util.Collections#checkedCollection(java.util.Collection, java.lang.Class)": "java.util.DesugarCollections", + "java.util.Set java.util.Collections#checkedSet(java.util.Set, java.lang.Class)": "java.util.DesugarCollections", + "java.util.SortedSet java.util.Collections#checkedSortedSet(java.util.SortedSet, java.lang.Class)": "java.util.DesugarCollections", + "java.util.List java.util.Collections#checkedList(java.util.List, java.lang.Class)": "java.util.DesugarCollections", + "java.util.Map java.util.Collections#checkedMap(java.util.Map, java.lang.Class, java.lang.Class)": "java.util.DesugarCollections", + "java.util.SortedMap java.util.Collections#checkedSortedMap(java.util.SortedMap, java.lang.Class, java.lang.Class)": "java.util.DesugarCollections", + "java.util.Collection java.util.Collections#unmodifiableCollection(java.util.Collection)": "java.util.DesugarCollections", + "java.util.Set java.util.Collections#unmodifiableSet(java.util.Set)": "java.util.DesugarCollections", + "java.util.SortedSet java.util.Collections#unmodifiableSortedSet(java.util.SortedSet)": "java.util.DesugarCollections", + "java.util.List java.util.Collections#unmodifiableList(java.util.List)": "java.util.DesugarCollections", + "java.util.Map java.util.Collections#unmodifiableMap(java.util.Map)": "java.util.DesugarCollections", + "java.util.SortedMap java.util.Collections#unmodifiableSortedMap(java.util.SortedMap)": "java.util.DesugarCollections", + "java.util.Collection java.util.Collections#synchronizedCollection(java.util.Collection)": "java.util.DesugarCollections", + "java.util.Set java.util.Collections#synchronizedSet(java.util.Set)": "java.util.DesugarCollections", + "java.util.SortedSet java.util.Collections#synchronizedSortedSet(java.util.SortedSet)": "java.util.DesugarCollections", + "java.util.List java.util.Collections#synchronizedList(java.util.List)": "java.util.DesugarCollections", "java.util.Map java.util.Collections#synchronizedMap(java.util.Map)": "java.util.DesugarCollections", "java.util.SortedMap java.util.Collections#synchronizedSortedMap(java.util.SortedMap)": "java.util.DesugarCollections", "long java.util.concurrent.atomic.AtomicLong#accumulateAndGet(long, java.util.function.LongBinaryOperator)": "java.util.concurrent.atomic.DesugarAtomicLong", "long java.util.concurrent.atomic.AtomicLong#getAndAccumulate(long, java.util.function.LongBinaryOperator)": "java.util.concurrent.atomic.DesugarAtomicLong", "long java.util.concurrent.atomic.AtomicLong#getAndUpdate(java.util.function.LongUnaryOperator)": "java.util.concurrent.atomic.DesugarAtomicLong", "long java.util.concurrent.atomic.AtomicLong#updateAndGet(java.util.function.LongUnaryOperator)": "java.util.concurrent.atomic.DesugarAtomicLong" + }, + "api_generic_types_conversion": { + "java.util.Collection java.util.Hashtable#values()": [-1, "java.util.Collection java.util.DesugarCollections#bridge_synchronizedCollection(java.util.Collection, java.lang.Object)"], + "java.util.Set java.util.Hashtable#entrySet()": [-1, "java.util.Set java.util.DesugarCollections#bridge_synchronizedSet(java.util.Set, java.lang.Object)"], + "java.util.Set java.util.Hashtable#keySet()": [-1, "java.util.Set java.util.DesugarCollections#bridge_synchronizedSet(java.util.Set, java.lang.Object)"], + "java.util.List java.util.Vector#subList(int, int)": [-1, "java.util.List java.util.DesugarCollections#bridge_synchronizedList(java.util.List, java.lang.Object)"] } } ],
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json index 0f81af4..aa8b1c0 100644 --- a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json +++ b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
@@ -571,12 +571,34 @@ "java.lang.Object java.util.concurrent.atomic.AtomicReference#getAndAccumulate(java.lang.Object, java.util.function.BinaryOperator)": "java.util.concurrent.atomic.DesugarAtomicReference", "java.lang.Object java.util.concurrent.atomic.AtomicReference#getAndUpdate(java.util.function.UnaryOperator)": "java.util.concurrent.atomic.DesugarAtomicReference", "java.lang.Object java.util.concurrent.atomic.AtomicReference#updateAndGet(java.util.function.UnaryOperator)": "java.util.concurrent.atomic.DesugarAtomicReference", + "java.util.Collection java.util.Collections#checkedCollection(java.util.Collection, java.lang.Class)": "java.util.DesugarCollections", + "java.util.Set java.util.Collections#checkedSet(java.util.Set, java.lang.Class)": "java.util.DesugarCollections", + "java.util.SortedSet java.util.Collections#checkedSortedSet(java.util.SortedSet, java.lang.Class)": "java.util.DesugarCollections", + "java.util.List java.util.Collections#checkedList(java.util.List, java.lang.Class)": "java.util.DesugarCollections", + "java.util.Map java.util.Collections#checkedMap(java.util.Map, java.lang.Class, java.lang.Class)": "java.util.DesugarCollections", + "java.util.SortedMap java.util.Collections#checkedSortedMap(java.util.SortedMap, java.lang.Class, java.lang.Class)": "java.util.DesugarCollections", + "java.util.Collection java.util.Collections#unmodifiableCollection(java.util.Collection)": "java.util.DesugarCollections", + "java.util.Set java.util.Collections#unmodifiableSet(java.util.Set)": "java.util.DesugarCollections", + "java.util.SortedSet java.util.Collections#unmodifiableSortedSet(java.util.SortedSet)": "java.util.DesugarCollections", + "java.util.List java.util.Collections#unmodifiableList(java.util.List)": "java.util.DesugarCollections", + "java.util.Map java.util.Collections#unmodifiableMap(java.util.Map)": "java.util.DesugarCollections", + "java.util.SortedMap java.util.Collections#unmodifiableSortedMap(java.util.SortedMap)": "java.util.DesugarCollections", + "java.util.Collection java.util.Collections#synchronizedCollection(java.util.Collection)": "java.util.DesugarCollections", + "java.util.Set java.util.Collections#synchronizedSet(java.util.Set)": "java.util.DesugarCollections", + "java.util.SortedSet java.util.Collections#synchronizedSortedSet(java.util.SortedSet)": "java.util.DesugarCollections", + "java.util.List java.util.Collections#synchronizedList(java.util.List)": "java.util.DesugarCollections", "java.util.Map java.util.Collections#synchronizedMap(java.util.Map)": "java.util.DesugarCollections", "java.util.SortedMap java.util.Collections#synchronizedSortedMap(java.util.SortedMap)": "java.util.DesugarCollections", "long java.util.concurrent.atomic.AtomicLong#accumulateAndGet(long, java.util.function.LongBinaryOperator)": "java.util.concurrent.atomic.DesugarAtomicLong", "long java.util.concurrent.atomic.AtomicLong#getAndAccumulate(long, java.util.function.LongBinaryOperator)": "java.util.concurrent.atomic.DesugarAtomicLong", "long java.util.concurrent.atomic.AtomicLong#getAndUpdate(java.util.function.LongUnaryOperator)": "java.util.concurrent.atomic.DesugarAtomicLong", "long java.util.concurrent.atomic.AtomicLong#updateAndGet(java.util.function.LongUnaryOperator)": "java.util.concurrent.atomic.DesugarAtomicLong" + }, + "api_generic_types_conversion": { + "java.util.Collection java.util.Hashtable#values()": [-1, "java.util.Collection java.util.DesugarCollections#bridge_synchronizedCollection(java.util.Collection, java.lang.Object)"], + "java.util.Set java.util.Hashtable#entrySet()": [-1, "java.util.Set java.util.DesugarCollections#bridge_synchronizedSet(java.util.Set, java.lang.Object)"], + "java.util.Set java.util.Hashtable#keySet()": [-1, "java.util.Set java.util.DesugarCollections#bridge_synchronizedSet(java.util.Set, java.lang.Object)"], + "java.util.List java.util.Vector#subList(int, int)": [-1, "java.util.List java.util.DesugarCollections#bridge_synchronizedList(java.util.List, java.lang.Object)"] } }, {
diff --git a/src/main/java/com/android/tools/r8/classmerging/Policy.java b/src/main/java/com/android/tools/r8/classmerging/Policy.java index e7f52f3..7655762 100644 --- a/src/main/java/com/android/tools/r8/classmerging/Policy.java +++ b/src/main/java/com/android/tools/r8/classmerging/Policy.java
@@ -8,7 +8,6 @@ import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy; import com.android.tools.r8.horizontalclassmerging.MultiClassPolicyWithPreprocessing; import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy; -import com.android.tools.r8.verticalclassmerging.policies.VerticalClassMergerPolicy; import com.android.tools.r8.verticalclassmerging.policies.VerticalClassMergerPolicyWithPreprocessing; import java.util.ArrayList; import java.util.Collection; @@ -60,16 +59,7 @@ return false; } - public VerticalClassMergerPolicy asVerticalClassMergerPolicy() { - return null; - } - - public boolean isVerticalClassMergerPolicyWithPreprocessing() { - return false; - } - - public VerticalClassMergerPolicyWithPreprocessing<?> - asVerticalClassMergerPolicyWithPreprocessing() { + public VerticalClassMergerPolicyWithPreprocessing<?> asVerticalClassMergerPolicy() { return null; }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java index ea46ea8..3c8c2fd 100644 --- a/src/main/java/com/android/tools/r8/graph/DexClass.java +++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -1141,9 +1141,8 @@ return null; } - @SuppressWarnings("ReferenceEquality") public boolean isInSameNest(DexClass other) { - return isInANest() && other.isInANest() && getNestHost() == other.getNestHost(); + return isInANest() && other.isInANest() && getNestHost().isIdenticalTo(other.getNestHost()); } public void forEachNestMember(Consumer<DexType> consumer) {
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java index 6532b8f..b73119d 100644 --- a/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java +++ b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
@@ -93,8 +93,10 @@ } if (!dexResources.isEmpty() && !classResources.isEmpty()) { throw new CompilationError( - "Cannot create android app from an archive '" + archive - + "' containing both DEX and Java-bytecode content"); + "Cannot create android app from an archive '" + + archive + + "' containing both DEX and Java-bytecode content", + origin); } return !dexResources.isEmpty() ? dexResources : classResources; }
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/verticalclassmerging/ClassMerger.java index a198871..86b9848 100644 --- a/src/main/java/com/android/tools/r8/verticalclassmerging/ClassMerger.java +++ b/src/main/java/com/android/tools/r8/verticalclassmerging/ClassMerger.java
@@ -8,6 +8,7 @@ import static com.android.tools.r8.ir.code.InvokeType.DIRECT; import static com.android.tools.r8.ir.code.InvokeType.STATIC; import static com.android.tools.r8.ir.code.InvokeType.VIRTUAL; +import static java.util.function.Predicate.not; import com.android.tools.r8.cf.CfVersion; import com.android.tools.r8.errors.Unreachable; @@ -93,6 +94,77 @@ this.target = group.getTarget(); } + public void setup() { + setupInvokeSuperMapping(); + } + + private void setupInvokeSuperMapping() { + source.forEachProgramMethod(this::redirectSuperCallsToMethod); + } + + private void redirectSuperCallsToMethod(ProgramMethod sourceMethod) { + if (sourceMethod.getAccessFlags().belongsToDirectPool()) { + redirectSuperCallsToDirectMethod(sourceMethod); + } else { + redirectSuperCallsToVirtualMethod(sourceMethod); + } + } + + private void redirectSuperCallsToDirectMethod(ProgramMethod sourceMethod) { + DexEncodedMethod definition = sourceMethod.getDefinition(); + if (appView.options().canUseNestBasedAccess() + && source.isInSameNest(target) + && definition.isInstance() + && !definition.isInstanceInitializer() + && AccessControl.isMemberAccessible(sourceMethod, source, target, appView).isTrue()) { + lensBuilder.mapVirtualMethodToDirectInType(sourceMethod.getReference(), sourceMethod, target); + } + } + + private void redirectSuperCallsToVirtualMethod(ProgramMethod sourceMethod) { + if (sourceMethod.getAccessFlags().isAbstract()) { + return; + } + if (source.isInterface()) { + // If we merge a default interface method from interface I to its subtype C, then we need + // to rewrite invocations on the form "invoke-super I.m()" to "invoke-direct C.m$I()". + // + // Unlike when we merge a class into its subclass (the else-branch below), we should *not* + // rewrite any invocations on the form "invoke-super J.m()" to "invoke-direct C.m$I()", + // if I has a supertype J. This is due to the fact that invoke-super instructions that + // resolve to a method on an interface never hit an implementation below that interface. + lensBuilder.mapVirtualMethodToDirectInType(sourceMethod.getReference(), sourceMethod, target); + } else { + // If we merge class B into class C, and class C contains an invocation super.m(), then it + // is insufficient to rewrite "invoke-super B.m()" to "invoke-{direct,virtual} C.m$B()" (the + // method C.m$B denotes the direct/virtual method that has been created in C for B.m). In + // particular, there might be an instruction "invoke-super A.m()" in C that resolves to B.m + // at runtime (A is a superclass of B), which also needs to be rewritten to + // "invoke-{direct,virtual} C.m$B()". + // + // We handle this by adding a mapping for [target] and all of its supertypes. + DexProgramClass current = target; + while (current != null) { + // Only rewrite the invoke-super call if it does not lead to a NoSuchMethodError. + boolean resolutionSucceeds = + appView + .appInfo() + .resolveMethodOnClass(current, sourceMethod.getReference()) + .isSingleResolution(); + if (!resolutionSucceeds) { + break; + } + DexMethod signatureInHolder = + sourceMethod.getReference().withHolder(current, dexItemFactory); + lensBuilder.mapVirtualMethodToDirectInType(signatureInHolder, sourceMethod, target); + current = + current.hasSuperType() + ? asProgramClassOrNull(appView.definitionFor(current.getSuperType())) + : null; + } + } + } + public void merge() { // Merge the class [clazz] into [targetClass] by adding all methods to // targetClass that are not currently contained. @@ -132,51 +204,37 @@ add(directMethods, resultingDirectMethod, MethodSignatureEquivalence.get()); lensBuilder.recordMove(directMethod.getDefinition(), resultingDirectMethod); blockRedirectionOfSuperCalls(resultingDirectMethod); - - // Private methods in the parent class may be targeted with invoke-super if the two - // classes are in the same nest. Ensure such calls are mapped to invoke-direct. - if (definition.isInstance() - && definition.isPrivate() - && AccessControl.isMemberAccessible(directMethod, source, target, appView) - .isTrue()) { - lensBuilder.mapVirtualMethodToDirectInType( - definition.getReference(), definition, target.getType()); - } } }); - for (DexEncodedMethod virtualMethod : source.virtualMethods()) { - DexEncodedMethod shadowedBy = findMethodInTarget(virtualMethod); + for (DexEncodedMethod abstractMethod : source.virtualMethods(DexEncodedMethod::isAbstract)) { + DexEncodedMethod shadowedBy = findMethodInTarget(abstractMethod); if (shadowedBy != null) { - if (virtualMethod.isAbstract()) { - // Remove abstract/interface methods that are shadowed. The identity mapping below is - // needed to ensure we correctly fixup the mapping in case the signature refers to - // merged classes. - lensBuilder.recordSplit(virtualMethod, shadowedBy, null, null); + // Remove abstract/interface methods that are shadowed. The identity mapping below is + // needed to ensure we correctly fixup the mapping in case the signature refers to + // merged classes. + lensBuilder.recordSplit(abstractMethod, shadowedBy, null, null); - // The override now corresponds to the method in the parent, so unset its synthetic flag - // if the method in the parent is not synthetic. - if (!virtualMethod.isSyntheticMethod() && shadowedBy.isSyntheticMethod()) { - shadowedBy.getAccessFlags().demoteFromSynthetic(); - } - continue; + // The override now corresponds to the method in the parent, so unset its synthetic flag + // if the method in the parent is not synthetic. + if (!abstractMethod.isSyntheticMethod() && shadowedBy.isSyntheticMethod()) { + shadowedBy.getAccessFlags().demoteFromSynthetic(); } } else { // The method is not shadowed. If it is abstract, we can simply move it to the subclass. - // Non-abstract methods are handled below (they cannot simply be moved to the subclass as - // a virtual method, because they might be the target of an invoke-super instruction). - if (virtualMethod.isAbstract()) { - assert target.isAbstract(); - // Update the holder of [virtualMethod] using renameMethod(). - DexEncodedMethod resultingVirtualMethod = - renameMethod(virtualMethod, availableMethodSignatures, Rename.NEVER); - resultingVirtualMethod.setLibraryMethodOverride(virtualMethod.isLibraryMethodOverride()); - lensBuilder.recordMove(virtualMethod, resultingVirtualMethod); - add(virtualMethods, resultingVirtualMethod, MethodSignatureEquivalence.get()); - continue; - } + assert target.isAbstract(); + // Update the holder of [virtualMethod] using renameMethod(). + DexEncodedMethod resultingAbstractMethod = + renameMethod(abstractMethod, availableMethodSignatures, Rename.NEVER); + resultingAbstractMethod.setLibraryMethodOverride(abstractMethod.isLibraryMethodOverride()); + lensBuilder.recordMove(abstractMethod, resultingAbstractMethod); + add(virtualMethods, resultingAbstractMethod, MethodSignatureEquivalence.get()); } + } + for (DexEncodedMethod virtualMethod : + source.virtualMethods(not(DexEncodedMethod::isAbstract))) { + DexEncodedMethod shadowedBy = findMethodInTarget(virtualMethod); DexEncodedMethod resultingMethod; if (source.isInterface()) { // Moving a default interface method into its subtype. This method could be hit directly @@ -210,10 +268,6 @@ resultingMethod, MethodSignatureEquivalence.get()); - // Record that invoke-super instructions in the target class should be redirected to the - // newly created direct method. - redirectSuperCallsInTarget(virtualMethod); - DexEncodedMethod bridge = null; DexEncodedMethod override = shadowedBy; if (shadowedBy == null) { @@ -468,66 +522,6 @@ type -> true); } - private void redirectSuperCallsInTarget(DexEncodedMethod oldTarget) { - DexMethod oldTargetReference = oldTarget.getReference(); - if (source.isInterface()) { - // If we merge a default interface method from interface I to its subtype C, then we need - // to rewrite invocations on the form "invoke-super I.m()" to "invoke-direct C.m$I()". - // - // Unlike when we merge a class into its subclass (the else-branch below), we should *not* - // rewrite any invocations on the form "invoke-super J.m()" to "invoke-direct C.m$I()", - // if I has a supertype J. This is due to the fact that invoke-super instructions that - // resolve to a method on an interface never hit an implementation below that interface. - lensBuilder.mapVirtualMethodToDirectInType(oldTargetReference, oldTarget, target.getType()); - } else { - // If we merge class B into class C, and class C contains an invocation super.m(), then it - // is insufficient to rewrite "invoke-super B.m()" to "invoke-{direct,virtual} C.m$B()" (the - // method C.m$B denotes the direct/virtual method that has been created in C for B.m). In - // particular, there might be an instruction "invoke-super A.m()" in C that resolves to B.m - // at runtime (A is a superclass of B), which also needs to be rewritten to - // "invoke-{direct,virtual} C.m$B()". - // - // We handle this by adding a mapping for [target] and all of its supertypes. - DexProgramClass holder = target; - while (holder != null) { - DexMethod signatureInHolder = oldTargetReference.withHolder(holder, dexItemFactory); - // Only rewrite the invoke-super call if it does not lead to a NoSuchMethodError. - boolean resolutionSucceeds = - appView.appInfo().resolveMethodOnClass(holder, signatureInHolder).isSingleResolution(); - if (resolutionSucceeds) { - lensBuilder.mapVirtualMethodToDirectInType(signatureInHolder, oldTarget, target.type); - } else { - break; - } - - // Consider that A gets merged into B and B's subclass C gets merged into D. Instructions - // on the form "invoke-super {B,C,D}.m()" in D are changed into "invoke-direct D.m$C()" by - // the code above. However, instructions on the form "invoke-super A.m()" should also be - // changed into "invoke-direct D.m$C()". This is achieved by also considering the classes - // that have been merged into [holder]. - Set<DexType> mergedTypes = verticallyMergedClassesBuilder.getSourcesFor(holder); - for (DexType type : mergedTypes) { - DexMethod signatureInType = oldTargetReference.withHolder(type, dexItemFactory); - // Resolution would have succeeded if the method used to be in [type], or if one of - // its super classes declared the method. - // TODO(b/315283244): Should not rely on lens for this. Instead precompute this before - // merging any classes. - boolean resolutionSucceededBeforeMerge = - outerLensBuilder.hasMappingForSignatureInContext(holder, signatureInType) - || appView.appInfo().lookupSuperTarget(signatureInHolder, holder, appView) - != null; - if (resolutionSucceededBeforeMerge) { - lensBuilder.mapVirtualMethodToDirectInType(signatureInType, oldTarget, target.type); - } - } - holder = - holder.hasSuperType() - ? asProgramClassOrNull(appView.definitionFor(holder.getSuperType())) - : null; - } - } - } - private void blockRedirectionOfSuperCalls(DexEncodedMethod method) { // We are merging a class B into C. The methods from B are being moved into C, and then we // subsequently rewrite the invoke-super instructions in C that hit a method in B, such that
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/ConnectedComponentVerticalClassMerger.java b/src/main/java/com/android/tools/r8/verticalclassmerging/ConnectedComponentVerticalClassMerger.java index fdd9383..dc82f99 100644 --- a/src/main/java/com/android/tools/r8/verticalclassmerging/ConnectedComponentVerticalClassMerger.java +++ b/src/main/java/com/android/tools/r8/verticalclassmerging/ConnectedComponentVerticalClassMerger.java
@@ -14,7 +14,7 @@ public class ConnectedComponentVerticalClassMerger { private final AppView<AppInfoWithLiveness> appView; - private final Collection<VerticalMergeGroup> classesToMerge; + private final Collection<VerticalMergeGroup> groups; // The resulting graph lens that should be used after class merging. private final VerticalClassMergerGraphLens.Builder lensBuilder; @@ -26,25 +26,31 @@ VerticallyMergedClasses.builder(); ConnectedComponentVerticalClassMerger( - AppView<AppInfoWithLiveness> appView, Collection<VerticalMergeGroup> classesToMerge) { + AppView<AppInfoWithLiveness> appView, Collection<VerticalMergeGroup> groups) { this.appView = appView; - this.classesToMerge = classesToMerge; + this.groups = groups; this.lensBuilder = new VerticalClassMergerGraphLens.Builder(); } public boolean isEmpty() { - return classesToMerge.isEmpty(); + return groups.isEmpty(); } public VerticalClassMergerResult.Builder run() { - List<VerticalMergeGroup> classesToMergeSorted = - ListUtils.sort(classesToMerge, Comparator.comparing(group -> group.getSource().getType())); - for (VerticalMergeGroup group : classesToMergeSorted) { - ClassMerger classMerger = - new ClassMerger( - appView, lensBuilder, synthesizedBridges, verticallyMergedClassesBuilder, group); - classMerger.merge(); - } + List<VerticalMergeGroup> groupsSorted = + ListUtils.sort(groups, Comparator.comparing(group -> group.getSource().getType())); + List<ClassMerger> classMergers = + ListUtils.map( + groupsSorted, + group -> + new ClassMerger( + appView, + lensBuilder, + synthesizedBridges, + verticallyMergedClassesBuilder, + group)); + classMergers.forEach(ClassMerger::setup); + classMergers.forEach(ClassMerger::merge); return VerticalClassMergerResult.builder( lensBuilder, synthesizedBridges, verticallyMergedClassesBuilder); }
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java index a3d1257..3987216 100644 --- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java +++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java
@@ -14,6 +14,7 @@ import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.graph.lens.GraphLens; import com.android.tools.r8.graph.lens.MethodLookupResult; import com.android.tools.r8.graph.proto.ArgumentInfoCollection; @@ -515,9 +516,10 @@ } } - public void mapVirtualMethodToDirectInType(DexMethod from, DexEncodedMethod to, DexType type) { + public void mapVirtualMethodToDirectInType( + DexMethod from, ProgramMethod to, DexProgramClass context) { contextualSuperToImplementationInContexts - .computeIfAbsent(type, ignoreKey(IdentityHashMap::new)) + .computeIfAbsent(context.getType(), ignoreKey(IdentityHashMap::new)) .put(from, to.getReference()); }
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerPolicyExecutor.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerPolicyExecutor.java index bab7656..1874199 100644 --- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerPolicyExecutor.java +++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerPolicyExecutor.java
@@ -35,7 +35,6 @@ import com.android.tools.r8.verticalclassmerging.policies.SameNestPolicy; import com.android.tools.r8.verticalclassmerging.policies.SameStartupPartitionPolicy; import com.android.tools.r8.verticalclassmerging.policies.SuccessfulVirtualMethodResolutionInTargetPolicy; -import com.android.tools.r8.verticalclassmerging.policies.VerticalClassMergerPolicy; import com.android.tools.r8.verticalclassmerging.policies.VerticalClassMergerPolicyWithPreprocessing; import java.util.ArrayList; import java.util.Collection; @@ -113,18 +112,8 @@ protected LinkedList<VerticalMergeGroup> apply( Policy policy, LinkedList<VerticalMergeGroup> linkedGroups, ExecutorService executorService) throws ExecutionException { - if (policy.isVerticalClassMergerPolicy()) { - return apply(policy.asVerticalClassMergerPolicy(), linkedGroups); - } else { - assert policy.isVerticalClassMergerPolicyWithPreprocessing(); - return apply(policy.asVerticalClassMergerPolicyWithPreprocessing(), linkedGroups); - } - } - - private LinkedList<VerticalMergeGroup> apply( - VerticalClassMergerPolicy policy, LinkedList<VerticalMergeGroup> linkedGroups) { - linkedGroups.removeIf(group -> !policy.canMerge(group)); - return linkedGroups; + assert policy.isVerticalClassMergerPolicy(); + return apply(policy.asVerticalClassMergerPolicy(), linkedGroups); } private <T> LinkedList<VerticalMergeGroup> apply(
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/VerticalClassMergerPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/VerticalClassMergerPolicy.java index 49f179f..b5a6390 100644 --- a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/VerticalClassMergerPolicy.java +++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/VerticalClassMergerPolicy.java
@@ -3,20 +3,21 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.verticalclassmerging.policies; -import com.android.tools.r8.classmerging.Policy; import com.android.tools.r8.verticalclassmerging.VerticalMergeGroup; +import java.util.Collection; -public abstract class VerticalClassMergerPolicy extends Policy { +public abstract class VerticalClassMergerPolicy + extends VerticalClassMergerPolicyWithPreprocessing<Void> { public abstract boolean canMerge(VerticalMergeGroup group); @Override - public boolean isVerticalClassMergerPolicy() { - return true; + public final boolean canMerge(VerticalMergeGroup group, Void data) { + return canMerge(group); } @Override - public VerticalClassMergerPolicy asVerticalClassMergerPolicy() { - return this; + public final Void preprocess(Collection<VerticalMergeGroup> groups) { + return null; } }
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/VerticalClassMergerPolicyWithPreprocessing.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/VerticalClassMergerPolicyWithPreprocessing.java index 7737ebc..5efbe75 100644 --- a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/VerticalClassMergerPolicyWithPreprocessing.java +++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/VerticalClassMergerPolicyWithPreprocessing.java
@@ -14,13 +14,12 @@ public abstract T preprocess(Collection<VerticalMergeGroup> groups); @Override - public boolean isVerticalClassMergerPolicyWithPreprocessing() { + public boolean isVerticalClassMergerPolicy() { return true; } @Override - public VerticalClassMergerPolicyWithPreprocessing<T> - asVerticalClassMergerPolicyWithPreprocessing() { + public VerticalClassMergerPolicyWithPreprocessing<T> asVerticalClassMergerPolicy() { return this; } }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugarCollectionsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugarCollectionsTest.java new file mode 100644 index 0000000..a740261 --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugarCollectionsTest.java
@@ -0,0 +1,283 @@ +// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.desugar.desugaredlibrary; + +import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS; +import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11; +import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH; + +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification; +import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification; +import com.android.tools.r8.utils.StringUtils; +import com.google.common.collect.ImmutableList; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.Spliterator; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.Vector; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class DesugarCollectionsTest extends DesugaredLibraryTestBase { + + private final TestParameters parameters; + private final CompilationSpecification compilationSpecification; + private final LibraryDesugaringSpecification libraryDesugaringSpecification; + + @Parameters(name = "{0}, spec: {1}, {2}") + public static List<Object[]> data() { + return buildParameters( + getTestParameters().withDexRuntimesAndAllApiLevels().build(), + ImmutableList.of(JDK11, JDK11_PATH), + DEFAULT_SPECIFICATIONS); + } + + public DesugarCollectionsTest( + TestParameters parameters, + LibraryDesugaringSpecification libraryDesugaringSpecification, + CompilationSpecification compilationSpecification) { + this.parameters = parameters; + this.compilationSpecification = compilationSpecification; + this.libraryDesugaringSpecification = libraryDesugaringSpecification; + } + + @Test + public void testDesugarCollectionsTest() throws Throwable { + testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutput(getExpectedResult()); + } + + private String getExpectedResult() { + List<String> result = new ArrayList<>(); + for (int i = 0; i < 9; i++) { + result.add("item"); + } + for (int i = 0; i < 6; i++) { + result.add("k v"); + } + for (int i = 0; i < 6; i++) { + result.add("exception"); + } + result.addAll(ImmutableList.of("one", "v", "k", "k=v")); + return StringUtils.lines(result); + } + + public static class Main { + + public static void main(String[] args) { + successTest(); + exceptionTest(); + apiTest(); + } + + private static void apiTest() { + Vector<String> strings = new Vector<>(); + strings.add("one"); + strings.subList(0, 1).forEach(System.out::println); + Hashtable<String, String> table = new Hashtable<>(); + table.put("k", "v"); + table.values().forEach(System.out::println); + table.keySet().forEach(System.out::println); + table.entrySet().forEach(System.out::println); + } + + private static void successTest() { + SortedSet<String> set = new TreeSet<>(); + set.add("item"); + Collections.unmodifiableCollection(set).forEach(System.out::println); + Collections.unmodifiableSet(set).forEach(System.out::println); + Collections.unmodifiableSortedSet(set).forEach(System.out::println); + Collections.synchronizedCollection(set).forEach(System.out::println); + Collections.synchronizedSet(set).forEach(System.out::println); + Collections.synchronizedSortedSet(set).forEach(System.out::println); + Collections.checkedCollection(set, String.class).forEach(System.out::println); + Collections.checkedSet(set, String.class).forEach(System.out::println); + Collections.checkedSortedSet(set, String.class).forEach(System.out::println); + SortedMap<String, String> map = new TreeMap<>(); + map.put("k", "v"); + Collections.unmodifiableMap(map).forEach((k, v) -> System.out.println(k + " " + v)); + Collections.unmodifiableSortedMap(map).forEach((k, v) -> System.out.println(k + " " + v)); + Collections.synchronizedMap(map).forEach((k, v) -> System.out.println(k + " " + v)); + Collections.synchronizedSortedMap(map).forEach((k, v) -> System.out.println(k + " " + v)); + Collections.checkedMap(map, String.class, String.class) + .forEach((k, v) -> System.out.println(k + " " + v)); + Collections.checkedSortedMap(map, String.class, String.class) + .forEach((k, v) -> System.out.println(k + " " + v)); + } + + private static void exceptionTest() { + try { + Collections.unmodifiableCollection(new ImmutableList<>()).spliterator(); + System.out.println("working"); + } catch (UnsupportedOperationException e) { + System.out.println("exception"); + } + try { + Collections.unmodifiableList(new ImmutableList<>()).spliterator(); + System.out.println("working"); + } catch (UnsupportedOperationException e) { + System.out.println("exception"); + } + try { + Collections.synchronizedList(new ImmutableList<>()).spliterator(); + System.out.println("working"); + } catch (UnsupportedOperationException e) { + System.out.println("exception"); + } + try { + Collections.synchronizedList(new ImmutableList<>()).spliterator(); + System.out.println("working"); + } catch (UnsupportedOperationException e) { + System.out.println("exception"); + } + try { + Collections.checkedList(new ImmutableList<>(), Vector.class).spliterator(); + System.out.println("working"); + } catch (UnsupportedOperationException e) { + System.out.println("exception"); + } + try { + Collections.checkedList(new ImmutableList<>(), Vector.class).spliterator(); + System.out.println("working"); + } catch (UnsupportedOperationException e) { + System.out.println("exception"); + } + } + + static class ImmutableList<T> implements List<T> { + + @Override + public int size() { + return 0; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public boolean contains(Object o) { + return false; + } + + @Override + public Iterator<T> iterator() { + return null; + } + + @Override + public Object[] toArray() { + return new Object[0]; + } + + @Override + public <T1> T1[] toArray(T1[] a) { + return null; + } + + @Override + public boolean add(T t) { + return false; + } + + @Override + public boolean remove(Object o) { + return false; + } + + @Override + public boolean containsAll(Collection<?> c) { + return false; + } + + @Override + public boolean addAll(Collection<? extends T> c) { + return false; + } + + @Override + public boolean addAll(int index, Collection<? extends T> c) { + return false; + } + + @Override + public boolean removeAll(Collection<?> c) { + return false; + } + + @Override + public boolean retainAll(Collection<?> c) { + return false; + } + + @Override + public void clear() {} + + @Override + public T get(int index) { + return null; + } + + @Override + public T set(int index, T element) { + return null; + } + + @Override + public void add(int index, T element) {} + + @Override + public T remove(int index) { + return null; + } + + @Override + public int indexOf(Object o) { + return 0; + } + + @Override + public int lastIndexOf(Object o) { + return 0; + } + + @Override + public ListIterator<T> listIterator() { + return null; + } + + @Override + public ListIterator<T> listIterator(int index) { + return null; + } + + @Override + public List<T> subList(int fromIndex, int toIndex) { + return null; + } + + @Override + public Spliterator<T> spliterator() { + throw new UnsupportedOperationException(); + } + } + } +}
diff --git a/src/test/java/com/android/tools/r8/files/ArchiveWithDexTest.java b/src/test/java/com/android/tools/r8/files/ArchiveWithDexTest.java index 0c3bd18..85b25f3 100644 --- a/src/test/java/com/android/tools/r8/files/ArchiveWithDexTest.java +++ b/src/test/java/com/android/tools/r8/files/ArchiveWithDexTest.java
@@ -4,6 +4,8 @@ package com.android.tools.r8.files; import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin; +import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.CoreMatchers.containsString; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; @@ -11,10 +13,15 @@ import com.android.tools.r8.ArchiveProgramResourceProvider; import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.DexIndexedConsumer; +import com.android.tools.r8.R8; +import com.android.tools.r8.R8Command; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.origin.PathOrigin; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.ArchiveResourceProvider; import com.android.tools.r8.utils.BooleanBox; @@ -33,6 +40,8 @@ @RunWith(Parameterized.class) public class ArchiveWithDexTest extends TestBase { + static final String MESSAGE = "containing both DEX and Java-bytecode content"; + @Parameter() public TestParameters parameters; @Parameters(name = "{0}") @@ -41,6 +50,7 @@ } private static Path zipWithDexAndClass; + private static Origin zipWithDexAndClassOrigin; @BeforeClass public static void createZipWitDexAndClass() throws IOException { @@ -51,6 +61,7 @@ Files.createFile(zipContent.resolve("other.dex")); zipWithDexAndClass = getStaticTemp().newFolder().toPath().resolve("input.zip"); ZipUtils.zip(zipWithDexAndClass, zipContent); + zipWithDexAndClassOrigin = new PathOrigin(zipWithDexAndClass); } private void checkOneDexFiles(Path archive) throws Exception { @@ -85,6 +96,41 @@ } @Test + public void testFileInputD8() { + assertThrows( + CompilationFailedException.class, + () -> + testForD8(Backend.DEX) + .addProgramFiles(zipWithDexAndClass) + .setMinApi(AndroidApiLevel.B) + .compileWithExpectedDiagnostics( + diagnostics -> + diagnostics.assertErrorsMatch( + allOf( + diagnosticMessage(containsString("input.zip")), + diagnosticMessage(containsString(MESSAGE)), + diagnosticOrigin(zipWithDexAndClassOrigin))))); + } + + @Test + public void testProviderInputD8() { + assertThrows( + CompilationFailedException.class, + () -> + testForD8(Backend.DEX) + .addProgramResourceProviders( + ArchiveProgramResourceProvider.fromArchive(zipWithDexAndClass)) + .setMinApi(AndroidApiLevel.B) + .compileWithExpectedDiagnostics( + diagnostics -> + diagnostics.assertErrorsMatch( + allOf( + // When using providers the file name is not in the message. + diagnosticMessage(containsString(MESSAGE)), + diagnosticOrigin(zipWithDexAndClassOrigin))))); + } + + @Test public void testR8() { assertThrows( CompilationFailedException.class, @@ -97,10 +143,10 @@ .compileWithExpectedDiagnostics( diagnostics -> diagnostics.assertErrorsMatch( - diagnosticMessage( - containsString( - "Cannot create android app from an archive containing both DEX" - + " and Java-bytecode content."))))); + allOf( + // When using providers the file name is not in the message. + diagnosticMessage(containsString(MESSAGE)), + diagnosticOrigin(zipWithDexAndClassOrigin))))); } @Test @@ -130,6 +176,19 @@ } @Test + public void testRawCommandBuilderR8() throws Exception { + // Check that the error is wrapped and reported at the point of compiling (not arg building). + R8Command command = + R8Command.builder() + .addProgramResourceProvider( + ArchiveProgramResourceProvider.fromArchive(zipWithDexAndClass)) + .setMinApiLevel(1) + .setProgramConsumer(DexIndexedConsumer.emptyConsumer()) + .build(); + assertThrows(CompilationFailedException.class, () -> R8.run(command)); + } + + @Test public void testR8LegacyProgramResourceProviderDontIgnoreDex() { assertThrows( CompilationFailedException.class, @@ -142,8 +201,10 @@ .compileWithExpectedDiagnostics( diagnostics -> diagnostics.assertErrorsMatch( - diagnosticMessage( - containsString("containing both DEX and Java-bytecode content"))))); + allOf( + // When using providers the file name is not in the message. + diagnosticMessage(containsString(MESSAGE)), + diagnosticOrigin(zipWithDexAndClassOrigin))))); } static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalRuleOnMemberFieldWithKeepInitTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalRuleOnMemberFieldWithKeepInitTest.java new file mode 100644 index 0000000..68db4f1 --- /dev/null +++ b/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalRuleOnMemberFieldWithKeepInitTest.java
@@ -0,0 +1,85 @@ +// Copyright (c) 2023, 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.ifrule; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.ExecutionException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class ConditionalRuleOnMemberFieldWithKeepInitTest extends TestBase { + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withDefaultDexRuntime().build(); + } + + private TestParameters parameters; + + public ConditionalRuleOnMemberFieldWithKeepInitTest(TestParameters parameters) { + this.parameters = parameters; + } + + private void testKeepRule(String keepRule, List<Class<?>> absent, List<Class<?>> present) + throws IOException, ExecutionException, CompilationFailedException { + testForR8(parameters.getBackend()) + .addProgramClasses(A.class, TestClass.class) + .addKeepRules(keepRule) + .addKeepMainRule(TestClass.class) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("42") + .inspect( + inspector -> { + absent.forEach(clazz -> assertThat(inspector.clazz(clazz), isAbsent())); + present.forEach(clazz -> assertThat(inspector.clazz(clazz), isPresent())); + }); + } + + @Test + public void testJustStarConditionalKeepClassMembers() throws Exception { + String keepRule = "-if class * -keepclasseswithmembers class <1> { <init>(); }"; + testKeepRule(keepRule, ImmutableList.of(), ImmutableList.of(TestClass.class, A.class)); + } + + @Test + public void testJustStarConditionalKeepClass() throws Exception { + String keepRule = "-if class * -keep class <1> { <init>(); }"; + testKeepRule(keepRule, ImmutableList.of(), ImmutableList.of(TestClass.class, A.class)); + } + + @Test + public void testStarWithMethodConditionalKeepClassMembers() throws Exception { + String keepRule = "-if class * { int z; } -keepclasseswithmembers class <1> { <init>(); }"; + testKeepRule(keepRule, ImmutableList.of(), ImmutableList.of(TestClass.class, A.class)); + } + + @Test + public void testStarWithMethodConditionalKeepClass() throws Exception { + String keepRule = "-if class * { int z; } -keep class <1> { <init>(); }"; + testKeepRule(keepRule, ImmutableList.of(), ImmutableList.of(TestClass.class, A.class)); + } + + static class A { + public static int z = 42; + } + + static class TestClass { + public static void main(String[] args) { + System.out.println(A.z); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalRuleOnMemberWithKeepInitSimpleInlineTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalRuleOnMemberWithKeepInitSimpleInlineTest.java new file mode 100644 index 0000000..f2300e3 --- /dev/null +++ b/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalRuleOnMemberWithKeepInitSimpleInlineTest.java
@@ -0,0 +1,87 @@ +// Copyright (c) 2023, 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.ifrule; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.ExecutionException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class ConditionalRuleOnMemberWithKeepInitSimpleInlineTest extends TestBase { + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withDefaultDexRuntime().build(); + } + + private TestParameters parameters; + + public ConditionalRuleOnMemberWithKeepInitSimpleInlineTest(TestParameters parameters) { + this.parameters = parameters; + } + + private void testKeepRule(String keepRule, List<Class<?>> absent, List<Class<?>> present) + throws IOException, ExecutionException, CompilationFailedException { + testForR8(parameters.getBackend()) + .addProgramClasses(A.class, TestClass.class) + .addKeepRules(keepRule) + .addKeepMainRule(TestClass.class) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("bar") + .inspect( + inspector -> { + absent.forEach(clazz -> assertThat(inspector.clazz(clazz), isAbsent())); + present.forEach(clazz -> assertThat(inspector.clazz(clazz), isPresent())); + }); + } + + @Test + public void testJustStarConditionalKeepClassMembers() throws Exception { + String keepRule = "-if class * -keepclasseswithmembers class <1> { <init>(); }"; + testKeepRule(keepRule, ImmutableList.of(), ImmutableList.of(TestClass.class, A.class)); + } + + @Test + public void testJustStarConditionalKeepClass() throws Exception { + String keepRule = "-if class * -keep class <1> { <init>(); }"; + testKeepRule(keepRule, ImmutableList.of(), ImmutableList.of(TestClass.class, A.class)); + } + + @Test + public void testStarWithMethodConditionalKeepClassMembers() throws Exception { + String keepRule = "-if class * { bar(); } -keepclasseswithmembers class <1> { <init>(); }"; + testKeepRule(keepRule, ImmutableList.of(), ImmutableList.of(TestClass.class, A.class)); + } + + @Test + public void testStarWithMethodConditionalKeepClass() throws Exception { + String keepRule = "-if class * { bar(); } -keep class <1> { <init>(); }"; + testKeepRule(keepRule, ImmutableList.of(), ImmutableList.of(TestClass.class, A.class)); + } + + static class A { + public static String bar() { + return "bar"; + } + } + + static class TestClass { + public static void main(String[] args) { + System.out.println(A.bar()); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalRuleOnMemberWithKeepInitTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalRuleOnMemberWithKeepInitTest.java new file mode 100644 index 0000000..38ff7d5 --- /dev/null +++ b/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalRuleOnMemberWithKeepInitTest.java
@@ -0,0 +1,89 @@ +// Copyright (c) 2023, 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.ifrule; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.ExecutionException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class ConditionalRuleOnMemberWithKeepInitTest extends TestBase { + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withDefaultDexRuntime().build(); + } + + private TestParameters parameters; + + public ConditionalRuleOnMemberWithKeepInitTest(TestParameters parameters) { + this.parameters = parameters; + } + + private void testKeepRule(String keepRule, List<Class<?>> absent, List<Class<?>> present) + throws IOException, ExecutionException, CompilationFailedException { + testForR8(parameters.getBackend()) + .addProgramClasses(A.class, TestClass.class) + .addKeepRules(keepRule) + .addKeepMainRule(TestClass.class) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutputLines("bar") + .inspect( + inspector -> { + absent.forEach(clazz -> assertThat(inspector.clazz(clazz), isAbsent())); + present.forEach(clazz -> assertThat(inspector.clazz(clazz), isPresent())); + }); + } + + @Test + public void testJustStarConditionalKeepClassMembers() throws Exception { + String keepRule = "-if class * -keepclasseswithmembers class <1> { <init>(); }"; + // TODO(b/316100042) We should keep A here + testKeepRule(keepRule, ImmutableList.of(A.class), ImmutableList.of(TestClass.class)); + } + + @Test + public void testJustStarConditionalKeepClass() throws Exception { + String keepRule = "-if class * -keep class <1> { <init>(); }"; + // TODO(b/316100042) We should keep A here + testKeepRule(keepRule, ImmutableList.of(A.class), ImmutableList.of(TestClass.class)); + } + + @Test + public void testStarWithMethodConditionalKeepClassMembers() throws Exception { + String keepRule = "-if class * { bar(); } -keepclasseswithmembers class <1> { <init>(); }"; + testKeepRule(keepRule, ImmutableList.of(), ImmutableList.of(TestClass.class, A.class)); + } + + @Test + public void testStarWithMethodConditionalKeepClass() throws Exception { + String keepRule = "-if class * { bar(); } -keep class <1> { <init>(); }"; + testKeepRule(keepRule, ImmutableList.of(), ImmutableList.of(TestClass.class, A.class)); + } + + static class A { + public static void bar() { + System.out.println("bar"); + } + } + + static class TestClass { + public static void main(String[] args) { + A.bar(); + } + } +}
diff --git a/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1 b/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1 index 758e8f0..c5ef954 100644 --- a/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1 +++ b/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1
@@ -1 +1 @@ -47cfa6d72be316cdaff6e5042111a18215ac4405 \ No newline at end of file +d50abf6c508c1332eb80f35f5631e99e2e8329b5 \ No newline at end of file