Merge commit '1d24aede8c9ed42cd2a06ca2441cad4bc0e4f84a' into dev-release
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 2404763..e58264e 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -322,7 +322,6 @@
List<ProguardConfigurationRule> synthesizedProguardRules = new ArrayList<>();
timing.begin("Strip unused code");
- Set<DexType> classesToRetainInnerClassAttributeFor = null;
RuntimeTypeCheckInfo.Builder classMergingEnqueuerExtensionBuilder =
new RuntimeTypeCheckInfo.Builder(appView.dexItemFactory());
try {
@@ -402,11 +401,8 @@
AnnotationRemover annotationRemover =
annotationRemoverBuilder
- .computeClassesToRetainInnerClassAttributeFor(appViewWithLiveness)
.build(appViewWithLiveness, removedClasses);
annotationRemover.ensureValid().run();
- classesToRetainInnerClassAttributeFor =
- annotationRemover.getClassesToRetainInnerClassAttributeFor();
typeVariableRemover.removeDeadGenericSignatureTypeVariables(appView);
new GenericSignatureRewriter(appView, NamingLens.getIdentityLens())
.run(appView.appInfo().classes(), executorService);
@@ -656,9 +652,7 @@
executorService);
// Remove annotations that refer to types that no longer exist.
- assert classesToRetainInnerClassAttributeFor != null;
AnnotationRemover.builder()
- .setClassesToRetainInnerClassAttributeFor(classesToRetainInnerClassAttributeFor)
.build(appView.withLiveness(), removedClasses)
.run();
typeVariableRemover.removeDeadGenericSignatureTypeVariables(appView);
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 0074673..2d0e429 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -969,7 +969,7 @@
}
public void removeInnerClasses(Predicate<InnerClassAttribute> predicate) {
- innerClasses.removeIf(predicate::test);
+ innerClasses.removeIf(predicate);
}
public InnerClassAttribute getInnerClassAttributeForThisClass() {
diff --git a/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java b/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
index 9cec3cb..1a3984c 100644
--- a/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import org.objectweb.asm.ClassWriter;
/**
@@ -89,4 +90,14 @@
+ (enclosingMethod == null ? "null" : enclosingMethod.toSourceString())
+ "]";
}
+
+ public boolean isEnclosingPinned(AppView<AppInfoWithLiveness> appView) {
+ if (enclosingMethod != null) {
+ return appView.appInfo().isPinned(enclosingMethod);
+ }
+ if (enclosingClass != null) {
+ return appView.appInfo().isPinned(enclosingClass);
+ }
+ return false;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java b/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java
index adb256f..c381074 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java
@@ -12,7 +12,6 @@
import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
import com.android.tools.r8.graph.GenericSignature.ReturnType;
-import com.android.tools.r8.graph.GenericSignature.StarFieldTypeSignature;
import com.android.tools.r8.graph.GenericSignature.TypeSignature;
import com.android.tools.r8.graph.GenericSignature.WildcardIndicator;
import com.android.tools.r8.utils.ListUtils;
@@ -133,9 +132,7 @@
if (typeArguments.isEmpty() || !hasGenericTypeParameters.test(type)) {
return getEmptyTypeArguments();
}
- // Wildcards can only be called be used in certain positions:
- // https://docs.oracle.com/javase/tutorial/java/generics/wildcards.html
- return ListUtils.mapOrElse(typeArguments, arg -> visitFieldTypeSignature(arg, true));
+ return ListUtils.mapOrElse(typeArguments, this::visitFieldTypeSignature);
}
@Override
@@ -208,11 +205,6 @@
@Override
public FieldTypeSignature visitFieldTypeSignature(FieldTypeSignature fieldSignature) {
- return visitFieldTypeSignature(fieldSignature, false);
- }
-
- private FieldTypeSignature visitFieldTypeSignature(
- FieldTypeSignature fieldSignature, boolean canUseWildcardInArguments) {
if (fieldSignature.isStar()) {
return fieldSignature;
} else if (fieldSignature.isClassTypeSignature()) {
@@ -228,9 +220,7 @@
if (substitution == null) {
substitution = objectType;
}
- return substitution == objectType && canUseWildcardInArguments
- ? StarFieldTypeSignature.getStarFieldTypeSignature()
- : new ClassTypeSignature(substitution).asArgument(WildcardIndicator.NONE);
+ return new ClassTypeSignature(substitution).asArgument(WildcardIndicator.NONE);
}
return fieldSignature;
}
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
index 956b076..93b3b47 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
@@ -13,8 +13,8 @@
import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
import com.android.tools.r8.graph.GenericSignature.ReturnType;
-import com.android.tools.r8.graph.GenericSignature.StarFieldTypeSignature;
import com.android.tools.r8.graph.GenericSignature.TypeSignature;
+import com.android.tools.r8.graph.GenericSignature.WildcardIndicator;
import com.android.tools.r8.utils.ListUtils;
import java.util.List;
import java.util.function.Function;
@@ -230,7 +230,7 @@
fieldTypeSignature -> {
FieldTypeSignature rewrittenSignature = visitFieldTypeSignature(fieldTypeSignature);
return rewrittenSignature == null
- ? StarFieldTypeSignature.getStarFieldTypeSignature()
+ ? objectTypeSignature.asArgument(WildcardIndicator.NONE)
: rewrittenSignature;
});
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index 9b9dc08..0df5d92 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -24,6 +24,7 @@
import com.android.tools.r8.ir.desugar.ServiceLoaderSourceCode;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.BooleanBox;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.IdentityHashMap;
@@ -247,8 +248,20 @@
public void perform(InvokeVirtual classLoaderInvoke, DexMethod method) {
// Remove the ClassLoader call since this can throw and will not be removed otherwise.
if (classLoaderInvoke != null) {
- clearGetClassLoader(classLoaderInvoke);
- iterator.nextUntil(i -> i == serviceLoaderLoad);
+ BooleanBox allClassLoaderUsersAreServiceLoaders =
+ new BooleanBox(!classLoaderInvoke.outValue().hasPhiUsers());
+ classLoaderInvoke
+ .outValue()
+ .uniqueUsers()
+ .forEach(
+ user -> {
+ assert !user.isAssume();
+ allClassLoaderUsersAreServiceLoaders.and(user == serviceLoaderLoad);
+ });
+ if (allClassLoaderUsersAreServiceLoaders.get()) {
+ clearGetClassLoader(classLoaderInvoke);
+ iterator.nextUntil(i -> i == serviceLoaderLoad);
+ }
}
// Remove the ServiceLoader.load call.
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index c5d7038..e1eabf2 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
-import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexAnnotation;
@@ -16,6 +15,7 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.kotlin.KotlinMemberLevelInfo;
@@ -23,8 +23,6 @@
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
-import java.util.IdentityHashMap;
-import java.util.Map;
import java.util.Set;
public class AnnotationRemover {
@@ -32,19 +30,16 @@
private final AppView<AppInfoWithLiveness> appView;
private final InternalOptions options;
private final Set<DexAnnotation> annotationsToRetain;
- private final Set<DexType> classesToRetainInnerClassAttributeFor;
private final ProguardKeepAttributes keep;
private final Set<DexType> removedClasses;
private AnnotationRemover(
AppView<AppInfoWithLiveness> appView,
- Set<DexType> classesToRetainInnerClassAttributeFor,
Set<DexAnnotation> annotationsToRetain,
Set<DexType> removedClasses) {
this.appView = appView;
this.options = appView.options();
this.annotationsToRetain = annotationsToRetain;
- this.classesToRetainInnerClassAttributeFor = classesToRetainInnerClassAttributeFor;
this.keep = appView.options().getProguardConfiguration().getKeepAttributes();
this.removedClasses = removedClasses;
}
@@ -53,10 +48,6 @@
return new Builder();
}
- public Set<DexType> getClassesToRetainInnerClassAttributeFor() {
- return classesToRetainInnerClassAttributeFor;
- }
-
/** Used to filter annotations on classes, methods and fields. */
private boolean filterAnnotations(DexDefinition holder, DexAnnotation annotation) {
return annotationsToRetain.contains(annotation)
@@ -172,35 +163,16 @@
return this;
}
- private static boolean hasGenericEnclosingClass(
- DexProgramClass clazz,
- Map<DexType, DexProgramClass> enclosingClasses,
- Set<DexProgramClass> genericClasses) {
- while (true) {
- DexProgramClass enclosingClass = enclosingClasses.get(clazz.type);
- if (enclosingClass == null) {
- return false;
- }
- if (genericClasses.contains(enclosingClass)) {
- return true;
- }
- clazz = enclosingClass;
- }
- }
-
public void run() {
for (DexProgramClass clazz : appView.appInfo().classes()) {
- boolean enclosingMethodPinned = enclosingMethodPinned(appView, clazz);
- stripAttributes(clazz, enclosingMethodPinned);
+ stripAttributes(clazz);
clazz.setAnnotations(
clazz.annotations().rewrite(annotation -> rewriteAnnotation(clazz, annotation)));
// Kotlin properties are split over fields and methods. Check if any is pinned before pruning
// the information.
Set<KotlinPropertyInfo> pinnedKotlinProperties = Sets.newIdentityHashSet();
- clazz.forEachMethod(
- method -> processMethod(method, clazz, pinnedKotlinProperties, enclosingMethodPinned));
- clazz.forEachField(
- field -> processField(field, clazz, pinnedKotlinProperties, enclosingMethodPinned));
+ clazz.forEachMethod(method -> processMethod(method, clazz, pinnedKotlinProperties));
+ clazz.forEachField(field -> processField(field, clazz, pinnedKotlinProperties));
clazz.forEachProgramMember(
member -> {
KotlinMemberLevelInfo kotlinInfo = member.getKotlinInfo();
@@ -215,14 +187,13 @@
private void processMethod(
DexEncodedMethod method,
DexProgramClass clazz,
- Set<KotlinPropertyInfo> pinnedKotlinProperties,
- boolean enclosingMethodPinned) {
+ Set<KotlinPropertyInfo> pinnedKotlinProperties) {
method.setAnnotations(
method.annotations().rewrite(annotation -> rewriteAnnotation(method, annotation)));
method.parameterAnnotationsList =
method.parameterAnnotationsList.keepIf(this::filterParameterAnnotations);
KeepMethodInfo methodInfo = appView.getKeepInfo().getMethodInfo(method, clazz);
- if (!enclosingMethodPinned && methodInfo.isAllowSignatureAttributeRemovalAllowed(options)) {
+ if (methodInfo.isSignatureAttributeRemovalAllowed(options)) {
method.clearGenericSignature();
}
if (!methodInfo.isPinned() && method.getKotlinInfo().isFunction()) {
@@ -236,12 +207,11 @@
private void processField(
DexEncodedField field,
DexProgramClass clazz,
- Set<KotlinPropertyInfo> pinnedKotlinProperties,
- boolean enclosingMethodPinned) {
+ Set<KotlinPropertyInfo> pinnedKotlinProperties) {
field.setAnnotations(
field.annotations().rewrite(annotation -> rewriteAnnotation(field, annotation)));
KeepFieldInfo fieldInfo = appView.getKeepInfo().getFieldInfo(field, clazz);
- if (!enclosingMethodPinned && fieldInfo.isAllowSignatureAttributeRemovalAllowed(options)) {
+ if (fieldInfo.isSignatureAttributeRemovalAllowed(options)) {
field.clearGenericSignature();
}
if (fieldInfo.isPinned() && field.getKotlinInfo().isProperty()) {
@@ -291,86 +261,68 @@
return liveGetter ? original : null;
}
- private static boolean enclosingMethodPinned(
- AppView<AppInfoWithLiveness> appView, DexClass clazz) {
- return clazz.getEnclosingMethodAttribute() != null
- && clazz.getEnclosingMethodAttribute().getEnclosingClass() != null
- && appView.appInfo().isPinned(clazz.getEnclosingMethodAttribute().getEnclosingClass());
- }
-
- private static boolean hasInnerClassesFromSet(DexProgramClass clazz, Set<DexType> innerClasses) {
- for (InnerClassAttribute attr : clazz.getInnerClasses()) {
- if (attr.getOuter() == clazz.type && innerClasses.contains(attr.getInner())) {
- return true;
- }
- }
- return false;
- }
-
- private void stripAttributes(DexProgramClass clazz, boolean enclosingMethodPinned) {
+ private void stripAttributes(DexProgramClass clazz) {
// If [clazz] is mentioned by a keep rule, it could be used for reflection, and we therefore
// need to keep the enclosing method and inner classes attributes, if requested. In Proguard
// compatibility mode we keep these attributes independent of whether the given class is kept.
- // To ensure reflection from both inner to outer and and outer to inner for kept classes - even
- // if only one side is kept - keep the attributes is any class mentioned in these attributes
- // is kept.
- boolean keptAnyway =
- appView.appInfo().isPinned(clazz.type)
- || enclosingMethodPinned
- || appView.options().forceProguardCompatibility;
- boolean keepForThisInnerClass = false;
- boolean keepForThisEnclosingClass = false;
- if (!keptAnyway) {
- keepForThisInnerClass = classesToRetainInnerClassAttributeFor.contains(clazz.type);
- keepForThisEnclosingClass =
- hasInnerClassesFromSet(clazz, classesToRetainInnerClassAttributeFor);
- }
- if (keptAnyway || keepForThisInnerClass || keepForThisEnclosingClass) {
- if (!keep.enclosingMethod) {
- clazz.clearEnclosingMethodAttribute();
- }
- if (!keep.innerClasses) {
- clazz.clearInnerClasses();
- } else if (!keptAnyway) {
- // We're keeping this only because of classesToRetainInnerClassAttributeFor.
- final boolean finalKeepForThisInnerClass = keepForThisInnerClass;
- final boolean finalKeepForThisEnclosingClass = keepForThisEnclosingClass;
- clazz.removeInnerClasses(
- ica -> {
- if (appView.appInfo().isPinned(ica.getInner())) {
- return false;
- }
- DexType outer = ica.getOuter();
- if (outer != null && appView.appInfo().isPinned(outer)) {
- return false;
- }
- if (finalKeepForThisInnerClass && ica.getInner() == clazz.type) {
- return false;
- }
- if (finalKeepForThisEnclosingClass
- && outer == clazz.type
- && classesToRetainInnerClassAttributeFor.contains(ica.getInner())) {
- return false;
- }
- return true;
- });
- }
- } else {
- // These attributes are only relevant for reflection, and this class is not used for
- // reflection. (Note that clearing these attributes can enable more vertical class merging.)
- clazz.clearEnclosingMethodAttribute();
- clazz.clearInnerClasses();
- }
- if (!enclosingMethodPinned
- && clazz.getClassSignature().isValid()
- && appView
- .getKeepInfo()
- .getClassInfo(clazz)
- .isAllowSignatureAttributeRemovalAllowed(options)) {
+ // In full mode we remove the attribute if not both sides are kept.
+ clazz.removeEnclosingMethodAttribute(
+ enclosingMethodAttribute ->
+ appView
+ .getKeepInfo()
+ .getClassInfo(clazz)
+ .isEnclosingMethodAttributeRemovalAllowed(
+ options, enclosingMethodAttribute, appView));
+ // It is important that the call to getEnclosingMethodAttribute is done after we potentially
+ // pruned it above.
+ clazz.removeInnerClasses(
+ attribute ->
+ canRemoveInnerClassAttribute(clazz, attribute, clazz.getEnclosingMethodAttribute()));
+ if (clazz.getClassSignature().isValid()
+ && appView.getKeepInfo().getClassInfo(clazz).isSignatureAttributeRemovalAllowed(options)) {
clazz.clearClassSignature();
}
}
+ private boolean canRemoveInnerClassAttribute(
+ DexProgramClass clazz,
+ InnerClassAttribute innerClassAttribute,
+ EnclosingMethodAttribute enclosingAttributeMethod) {
+ if (innerClassAttribute.getOuter() == null
+ && (clazz.isLocalClass() || clazz.isAnonymousClass())) {
+ // This is a class that has an enclosing method attribute and the inner class attribute
+ // is related to the enclosed method.
+ assert innerClassAttribute.getInner() != null;
+ return appView
+ .getKeepInfo()
+ .getClassInfo(innerClassAttribute.getInner(), appView)
+ .isInnerClassesAttributeRemovalAllowed(options, enclosingAttributeMethod);
+ } else if (innerClassAttribute.getOuter() == null) {
+ assert !clazz.isLocalClass() && !clazz.isAnonymousClass();
+ return appView
+ .getKeepInfo()
+ .getClassInfo(innerClassAttribute.getInner(), appView)
+ .isInnerClassesAttributeRemovalAllowed(options);
+ } else if (innerClassAttribute.getInner() == null) {
+ assert innerClassAttribute.getOuter() != null;
+ return appView
+ .getKeepInfo()
+ .getClassInfo(innerClassAttribute.getOuter(), appView)
+ .isInnerClassesAttributeRemovalAllowed(options);
+ } else {
+ // If both inner and outer is specified, only keep if both are pinned.
+ assert innerClassAttribute.getOuter() != null && innerClassAttribute.getInner() != null;
+ return appView
+ .getKeepInfo()
+ .getClassInfo(innerClassAttribute.getInner(), appView)
+ .isInnerClassesAttributeRemovalAllowed(options)
+ || appView
+ .getKeepInfo()
+ .getClassInfo(innerClassAttribute.getOuter(), appView)
+ .isInnerClassesAttributeRemovalAllowed(options);
+ }
+ }
+
public static void clearAnnotations(AppView<?> appView) {
for (DexProgramClass clazz : appView.appInfo().classes()) {
clazz.clearAnnotations();
@@ -386,82 +338,13 @@
*/
private final Set<DexAnnotation> annotationsToRetain = Sets.newIdentityHashSet();
- private Set<DexType> classesToRetainInnerClassAttributeFor;
-
- public Builder computeClassesToRetainInnerClassAttributeFor(
- AppView<AppInfoWithLiveness> appView) {
- assert classesToRetainInnerClassAttributeFor == null;
- // In case of minification for certain inner classes we need to retain their InnerClass
- // attributes because their minified name still needs to be in hierarchical format
- // (enclosing$inner) otherwise the GenericSignatureRewriter can't produce the correct,
- // renamed signature.
-
- // More precisely:
- // - we're going to retain the InnerClass attribute that refers to the same class as 'inner'
- // - for live, inner, nonstatic classes
- // - that are enclosed by a class with a generic signature.
-
- // In compat mode we always keep all InnerClass attributes (if requested).
- // If not requested we never keep any. In these cases don't compute eligible classes.
- Set<DexType> result = Sets.newIdentityHashSet();
- if (!appView.options().forceProguardCompatibility
- && appView.options().getProguardConfiguration().getKeepAttributes().innerClasses) {
- // Build lookup table and set of the interesting classes.
- // enclosingClasses.get(clazz) gives the enclosing class of 'clazz'
- Map<DexType, DexProgramClass> enclosingClasses = new IdentityHashMap<>();
- Set<DexProgramClass> genericClasses = Sets.newIdentityHashSet();
- for (DexProgramClass clazz : appView.appInfo().classes()) {
- if (clazz.getClassSignature().hasSignature()) {
- genericClasses.add(clazz);
- }
- for (InnerClassAttribute innerClassAttribute : clazz.getInnerClasses()) {
- if ((innerClassAttribute.getAccess() & Constants.ACC_STATIC) == 0
- && innerClassAttribute.getOuter() == clazz.type) {
- enclosingClasses.put(innerClassAttribute.getInner(), clazz);
- }
- }
- }
- for (DexProgramClass clazz : appView.appInfo().classes()) {
- // If [clazz] is mentioned by a keep rule, it could be used for reflection, and we
- // therefore need to keep the enclosing method and inner classes attributes, if requested.
- if (appView.appInfo().isPinned(clazz) || enclosingMethodPinned(appView, clazz)) {
- for (InnerClassAttribute innerClassAttribute : clazz.getInnerClasses()) {
- DexType inner = innerClassAttribute.getInner();
- if (appView.appInfo().isNonProgramTypeOrLiveProgramType(inner)) {
- result.add(inner);
- }
- DexType context = innerClassAttribute.getLiveContext(appView);
- if (context != null && appView.appInfo().isNonProgramTypeOrLiveProgramType(context)) {
- result.add(context);
- }
- }
- }
- if (clazz.getInnerClassAttributeForThisClass() != null
- && appView.appInfo().isNonProgramTypeOrLiveProgramType(clazz.type)
- && hasGenericEnclosingClass(clazz, enclosingClasses, genericClasses)) {
- result.add(clazz.type);
- }
- }
- }
- classesToRetainInnerClassAttributeFor = result;
- return this;
- }
-
- public Builder setClassesToRetainInnerClassAttributeFor(
- Set<DexType> classesToRetainInnerClassAttributeFor) {
- this.classesToRetainInnerClassAttributeFor = classesToRetainInnerClassAttributeFor;
- return this;
- }
-
public void retainAnnotation(DexAnnotation annotation) {
annotationsToRetain.add(annotation);
}
public AnnotationRemover build(
AppView<AppInfoWithLiveness> appView, Set<DexType> removedClasses) {
- assert classesToRetainInnerClassAttributeFor != null;
- return new AnnotationRemover(
- appView, classesToRetainInnerClassAttributeFor, annotationsToRetain, removedClasses);
+ return new AnnotationRemover(appView, annotationsToRetain, removedClasses);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java b/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
index 765fe31..cc11777 100644
--- a/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
@@ -17,4 +17,8 @@
boolean isForceProguardCompatibilityEnabled();
boolean isKeepAttributesSignatureEnabled();
+
+ boolean isKeepEnclosingMethodAttributeEnabled();
+
+ boolean isKeepInnerClassesAttributeEnabled();
}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
index 1040ae2..acf36aa 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
import com.android.tools.r8.shaking.KeepInfo.Builder;
/** Keep information that can be associated with any item, i.e., class, method or field. */
@@ -82,14 +84,47 @@
return allowAccessModification;
}
- public boolean isAllowSignatureAttributeRemovalAllowed(
- GlobalKeepInfoConfiguration configuration) {
+ public boolean isSignatureAttributeRemovalAllowed(GlobalKeepInfoConfiguration configuration) {
if (!configuration.isKeepAttributesSignatureEnabled()) {
return true;
}
return !(configuration.isForceProguardCompatibilityEnabled() || isPinned());
}
+ public boolean isEnclosingMethodAttributeRemovalAllowed(
+ GlobalKeepInfoConfiguration configuration,
+ EnclosingMethodAttribute enclosingMethodAttribute,
+ AppView<AppInfoWithLiveness> appView) {
+ if (!configuration.isKeepEnclosingMethodAttributeEnabled()) {
+ return true;
+ }
+ if (configuration.isForceProguardCompatibilityEnabled()) {
+ return false;
+ }
+ return !isPinned() || !enclosingMethodAttribute.isEnclosingPinned(appView);
+ }
+
+ public boolean isInnerClassesAttributeRemovalAllowed(GlobalKeepInfoConfiguration configuration) {
+ if (!configuration.isKeepInnerClassesAttributeEnabled()) {
+ return true;
+ }
+ return !(configuration.isForceProguardCompatibilityEnabled() || isPinned());
+ }
+
+ public boolean isInnerClassesAttributeRemovalAllowed(
+ GlobalKeepInfoConfiguration configuration,
+ EnclosingMethodAttribute enclosingMethodAttribute) {
+ if (!configuration.isKeepInnerClassesAttributeEnabled()) {
+ return true;
+ }
+ if (configuration.isForceProguardCompatibilityEnabled()) {
+ return false;
+ }
+ // The inner class is dependent on the enclosingMethodAttribute and since it has been pruned
+ // we can also remove this inner class relationship.
+ return enclosingMethodAttribute == null || !isPinned();
+ }
+
public abstract boolean isTop();
public abstract boolean isBottom();
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index ff812b6..94b8404 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -602,6 +602,16 @@
return proguardConfiguration.getKeepAttributes().signature;
}
+ @Override
+ public boolean isKeepEnclosingMethodAttributeEnabled() {
+ return proguardConfiguration.getKeepAttributes().enclosingMethod;
+ }
+
+ @Override
+ public boolean isKeepInnerClassesAttributeEnabled() {
+ return proguardConfiguration.getKeepAttributes().innerClasses;
+ }
+
/**
* If any non-static class merging is enabled, information about types referred to by instanceOf
* and check cast instructions needs to be collected.
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/InnerClassAttributePublicizerTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/InnerClassAttributePublicizerTest.java
index 24ba1cd..9b24e0a 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/InnerClassAttributePublicizerTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/InnerClassAttributePublicizerTest.java
@@ -28,7 +28,7 @@
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public InnerClassAttributePublicizerTest(TestParameters parameters) {
@@ -37,12 +37,12 @@
@Test
public void test() throws Exception {
- testForR8(parameters.getBackend())
+ testForR8Compat(parameters.getBackend())
.addInnerClasses(InnerClassAttributePublicizerTest.class)
.addKeepMainRule(TestClass.class)
- .addKeepAttributes("EnclosingMethod", "InnerClasses")
+ .addKeepAttributeInnerClassesAndEnclosingMethod()
.allowAccessModification()
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.inspect(this::inspect)
.run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java
index a6a93ad..8a80d6e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java
@@ -5,33 +5,51 @@
package com.android.tools.r8.classmerging.horizontal;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
import org.junit.Test;
+import org.junit.runners.Parameterized;
public class InnerOuterClassesTest extends HorizontalClassMergingTestBase {
- public InnerOuterClassesTest(TestParameters parameters) {
+
+ @Parameterized.Parameters(name = "{0}, isCompat: {1}")
+ public static List<Object[]> newData() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+ }
+
+ private final boolean isCompat;
+
+ public InnerOuterClassesTest(TestParameters parameters, boolean isCompat) {
super(parameters);
+ this.isCompat = isCompat;
}
@Test
public void testR8() throws Exception {
- testForR8(parameters.getBackend())
+ (isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()))
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
.enableNeverClassInliningAnnotations()
- .addKeepAttributes("InnerClasses", "EnclosingMethod")
+ .addKeepAttributeInnerClassesAndEnclosingMethod()
.setMinApi(parameters.getApiLevel())
+ .addOptionsModification(
+ options ->
+ options.testing.horizontalClassMergingTarget =
+ (candidates, target) -> candidates.iterator().next())
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("a", "b", "c", "d")
.inspect(
codeInspector -> {
- assertThat(codeInspector.clazz(A.class), isPresent());
+ assertThat(codeInspector.clazz(A.class), onlyIf(isCompat, isPresent()));
assertThat(codeInspector.clazz(A.B.class), isPresent());
- assertThat(codeInspector.clazz(C.class), isPresent());
- assertThat(codeInspector.clazz(A.D.class), isPresent());
+ assertThat(codeInspector.clazz(C.class), onlyIf(isCompat, isPresent()));
+ assertThat(codeInspector.clazz(A.D.class), onlyIf(isCompat, isPresent()));
});
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java
similarity index 70%
copy from src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupTest.java
copy to src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java
index ae64e0e..862c386 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java
@@ -17,13 +17,12 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
-public class CollisionWithDefaultMethodOutsideMergeGroupTest extends TestBase {
+public class CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest extends TestBase {
private final TestParameters parameters;
@@ -32,7 +31,8 @@
return getTestParameters().withAllRuntimesAndApiLevels().build();
}
- public CollisionWithDefaultMethodOutsideMergeGroupTest(TestParameters parameters) {
+ public CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest(
+ TestParameters parameters) {
this.parameters = parameters;
}
@@ -41,10 +41,15 @@
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
- // I and J are not eligible for merging, since class A (implements I) inherits a default m()
- // method from K, which is also on J.
+ // I and J are not eligible for merging, since the merging of class B into class A
+ // contributes the default method K.m() to A, and the merging of J into I would contribute
+ // the default method J.m() to A.
.addHorizontallyMergedClassesInspector(
- HorizontallyMergedClassesInspector::assertNoClassesMerged)
+ inspector ->
+ inspector
+ .assertIsCompleteMergeGroup(A.class, B.class)
+ .assertMergedInto(B.class, A.class)
+ .assertClassesNotMerged(I.class, J.class, K.class))
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
@@ -59,19 +64,20 @@
assertThat(aClassSubject, isImplementing(inspector.clazz(I.class)));
assertThat(aClassSubject, isImplementing(inspector.clazz(K.class)));
- ClassSubject bClassSubject = inspector.clazz(B.class);
+ ClassSubject bClassSubject = inspector.clazz(C.class);
assertThat(bClassSubject, isPresent());
assertThat(bClassSubject, isImplementing(inspector.clazz(J.class)));
})
.run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutputLines("K", "J");
+ .assertSuccessWithOutputLines("A", "K", "J");
}
static class Main {
public static void main(String[] args) {
- new A().m();
+ new A().keepA();
new B().m();
+ new C().m();
}
}
@@ -99,10 +105,20 @@
}
@NeverClassInline
- @NoHorizontalClassMerging
- static class A implements I, K {}
+ static class A implements I {
+ @NeverInline
+ void keepA() {
+ System.out.println("A");
+ }
+ }
+
+ // Will be merged into A, which as a side effect contributes the default method K.m() to A.
+ // This should prevent merging of J into I, since that would contribute the default method J.m()
+ // to A.
+ @NeverClassInline
+ static class B implements K {}
@NeverClassInline
@NoHorizontalClassMerging
- static class B implements J {}
+ static class C implements J {}
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java
similarity index 94%
rename from src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupTest.java
rename to src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java
index ae64e0e..538f7fe 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java
@@ -23,7 +23,7 @@
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
-public class CollisionWithDefaultMethodOutsideMergeGroupTest extends TestBase {
+public class CollisionWithDefaultMethodOutsideMergeGroupClassTest extends TestBase {
private final TestParameters parameters;
@@ -32,7 +32,7 @@
return getTestParameters().withAllRuntimesAndApiLevels().build();
}
- public CollisionWithDefaultMethodOutsideMergeGroupTest(TestParameters parameters) {
+ public CollisionWithDefaultMethodOutsideMergeGroupClassTest(TestParameters parameters) {
this.parameters = parameters;
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
similarity index 78%
copy from src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupTest.java
copy to src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
index ae64e0e..6fcd094 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
@@ -23,7 +23,7 @@
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
-public class CollisionWithDefaultMethodOutsideMergeGroupTest extends TestBase {
+public class CollisionWithDefaultMethodOutsideMergeGroupLambdaTest extends TestBase {
private final TestParameters parameters;
@@ -32,7 +32,7 @@
return getTestParameters().withAllRuntimesAndApiLevels().build();
}
- public CollisionWithDefaultMethodOutsideMergeGroupTest(TestParameters parameters) {
+ public CollisionWithDefaultMethodOutsideMergeGroupLambdaTest(TestParameters parameters) {
this.parameters = parameters;
}
@@ -41,8 +41,8 @@
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
- // I and J are not eligible for merging, since class A (implements I) inherits a default m()
- // method from K, which is also on J.
+ // I and J are not eligible for merging, since the lambda that implements I & J inherits a
+ // default m() method from K, which is also on J.
.addHorizontallyMergedClassesInspector(
HorizontallyMergedClassesInspector::assertNoClassesMerged)
.enableInliningAnnotations()
@@ -56,12 +56,7 @@
inspector -> {
ClassSubject aClassSubject = inspector.clazz(A.class);
assertThat(aClassSubject, isPresent());
- assertThat(aClassSubject, isImplementing(inspector.clazz(I.class)));
- assertThat(aClassSubject, isImplementing(inspector.clazz(K.class)));
-
- ClassSubject bClassSubject = inspector.clazz(B.class);
- assertThat(bClassSubject, isPresent());
- assertThat(bClassSubject, isImplementing(inspector.clazz(J.class)));
+ assertThat(aClassSubject, isImplementing(inspector.clazz(J.class)));
})
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("K", "J");
@@ -70,14 +65,20 @@
static class Main {
public static void main(String[] args) {
+ ((I & K)
+ () -> {
+ throw new RuntimeException();
+ })
+ .m();
new A().m();
- new B().m();
}
}
@NoUnusedInterfaceRemoval
@NoVerticalClassMerging
- interface I {}
+ interface I {
+ void f();
+ }
@NoUnusedInterfaceRemoval
@NoVerticalClassMerging
@@ -100,9 +101,5 @@
@NeverClassInline
@NoHorizontalClassMerging
- static class A implements I, K {}
-
- @NeverClassInline
- @NoHorizontalClassMerging
- static class B implements J {}
+ static class A implements J {}
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesMergingTest.java
index 0fd3ab3..7c647b4 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesMergingTest.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.classmerging.horizontal.interfaces;
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -32,6 +34,8 @@
.addKeepMainRule(Main.class)
.addHorizontallyMergedClassesInspector(
inspector -> inspector.assertClassesNotMerged(I.class, J.class))
+ .enableNoUnusedInterfaceRemovalAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
.compile()
.run(parameters.getRuntime(), Main.class)
@@ -46,10 +50,14 @@
}
}
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
interface I {
void f();
}
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
interface J {
void g();
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java
new file mode 100644
index 0000000..31aac13
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal.interfaces;
+
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DisjointFunctionalInterfacesWithIntersectionMergingTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public DisjointFunctionalInterfacesWithIntersectionMergingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ // TODO(b/173990042): We should be able to merge I and J.
+ .addHorizontallyMergedClassesInspector(
+ inspector -> inspector.assertClassesNotMerged(I.class, J.class))
+ .enableNoUnusedInterfaceRemovalAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("I & J", "I & J");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ Object o = (I & J) () -> "I & J";
+ System.out.println(((I) o).f());
+ System.out.println(((J) o).f());
+ }
+ }
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface I {
+ Object f();
+ }
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface J {
+ String f();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest.java
new file mode 100644
index 0000000..683ce85
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal.interfaces;
+
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest
+ extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest(
+ TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ // TODO(b/173990042): We should be able to merge I and J.
+ .addHorizontallyMergedClassesInspector(
+ inspector -> inspector.assertClassesNotMerged(I.class, J.class))
+ .enableNoUnusedInterfaceRemovalAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("I", "J");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ ((I) arg -> System.out.println(arg)).f("I");
+ ((J) arg -> System.out.println(arg)).f("J");
+ }
+ }
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface I {
+ void f(Object arg);
+ }
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface J {
+ void f(String arg);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest.java
new file mode 100644
index 0000000..8cb2212
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal.interfaces;
+
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest
+ extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest(
+ TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ // TODO(b/173990042): We should be able to merge I and J.
+ .addHorizontallyMergedClassesInspector(
+ inspector -> inspector.assertClassesNotMerged(I.class, J.class))
+ .enableNoUnusedInterfaceRemovalAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("I", "J");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(((I) () -> "I").f());
+ System.out.println(((J) () -> "J").f());
+ }
+ }
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface I {
+ Object f();
+ }
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface J {
+ String f();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesMergingTest.java
new file mode 100644
index 0000000..96b8ea9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesMergingTest.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal.interfaces;
+
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class IdenticalFunctionalInterfacesMergingTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public IdenticalFunctionalInterfacesMergingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ // TODO(b/173990042): I and J should be merged.
+ .addHorizontallyMergedClassesInspector(
+ inspector -> inspector.assertClassesNotMerged(I.class, J.class))
+ .enableNoUnusedInterfaceRemovalAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("I", "J");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ ((I) () -> System.out.println("I")).f();
+ ((J) () -> System.out.println("J")).f();
+ }
+ }
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface I {
+ void f();
+ }
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface J {
+ void f();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java
new file mode 100644
index 0000000..cd691d1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal.interfaces;
+
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class IdenticalFunctionalInterfacesWithIntersectionMergingTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public IdenticalFunctionalInterfacesWithIntersectionMergingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ // TODO(b/173990042): I and J should be merged.
+ .addHorizontallyMergedClassesInspector(
+ inspector -> inspector.assertClassesNotMerged(I.class, J.class))
+ .enableNoUnusedInterfaceRemovalAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("I & J", "I & J");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ Object o = ((I & J) () -> System.out.println("I & J"));
+ ((I) o).f();
+ ((J) o).f();
+ }
+ }
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface I {
+ void f();
+ }
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface J {
+ void f();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarInnerClassesInInterfaces.java b/src/test/java/com/android/tools/r8/desugar/DesugarInnerClassesInInterfaces.java
index d65470d..993b56f 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarInnerClassesInInterfaces.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarInnerClassesInInterfaces.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.desugar;
import com.android.tools.r8.DesugarTestConfiguration;
-import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -19,18 +18,18 @@
@RunWith(Parameterized.class)
public class DesugarInnerClassesInInterfaces extends TestBase {
- private List<String> EXPECTED_RESULT_WITHOUT_DESUGARING =
+ private final List<String> EXPECTED_RESULT_WITHOUT_DESUGARING =
ImmutableList.of(
WithAnonymousInner.class.getName(), "true", WithLocalInner.class.getName(), "true");
- private List<String> EXPECTED_RESULT_WITH_DESUGARING =
+ private final List<String> EXPECTED_RESULT_WITH_DESUGARING =
ImmutableList.of(
WithAnonymousInner.class.getName() + InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX,
"true",
WithLocalInner.class.getName() + InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX,
"true");
- private List<String> EXPECTED_RESULT_WITH_DESUGARING_B168697955 =
+ private final List<String> EXPECTED_RESULT_WITH_DESUGARING_B168697955 =
ImmutableList.of(
WithAnonymousInner.class.getName() + InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX,
"false",
@@ -68,44 +67,58 @@
}
@Test
- public void testR8() throws Exception {
- R8TestRunResult result =
- testForR8(parameters.getBackend())
- .addInnerClasses(DesugarInnerClassesInInterfaces.class)
- .setMinApi(parameters.getApiLevel())
- .addKeepRules("-keep class * { *; }")
- .addKeepAttributes("InnerClasses", "EnclosingMethod")
- .compile()
- .run(parameters.getRuntime(), TestClass.class);
- if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
- result.assertSuccessWithOutputLines(EXPECTED_RESULT_WITHOUT_DESUGARING);
- } else {
- // The static method which is moved to the companion class is inlined and causing
- // this different output. The rule "-keep class * { *; }" does not keep the static
- // method from being inlined after it has moved. Turning off inlining produces the
- // expected result. The inlining cause the getEnclosingClass() to return null.
- // See b/168697955.
- result.assertSuccessWithOutputLines(EXPECTED_RESULT_WITH_DESUGARING_B168697955);
- }
+ public void testR8Compat() throws Exception {
+ testForR8Compat(parameters.getBackend())
+ .addInnerClasses(DesugarInnerClassesInInterfaces.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepAllClassesRule()
+ .addKeepAttributeInnerClassesAndEnclosingMethod()
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .applyIf(
+ parameters.canUseDefaultAndStaticInterfaceMethods(),
+ result -> result.assertSuccessWithOutputLines(EXPECTED_RESULT_WITHOUT_DESUGARING),
+ // The static method which is moved to the companion class is inlined and causing
+ // this different output. The rule "-keep class * { *; }" does not keep the static
+ // method from being inlined after it has moved. Turning off inlining produces the
+ // expected result. The inlining cause the getEnclosingClass() to return null.
+ // See b/168697955.
+ result ->
+ result.assertSuccessWithOutputLines(EXPECTED_RESULT_WITH_DESUGARING_B168697955));
}
@Test
public void testR8_B168697955() throws Exception {
- R8TestRunResult result =
- testForR8(parameters.getBackend())
- .addInnerClasses(DesugarInnerClassesInInterfaces.class)
- .setMinApi(parameters.getApiLevel())
- .addKeepRules("-keep class * { *; }")
- .addKeepAttributes("InnerClasses", "EnclosingMethod")
- // With inlining turned off we get the expected result.
- .addOptionsModification(options -> options.enableInlining = false)
- .compile()
- .run(parameters.getRuntime(), TestClass.class);
- if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
- result.assertSuccessWithOutputLines(EXPECTED_RESULT_WITHOUT_DESUGARING);
- } else {
- result.assertSuccessWithOutputLines(EXPECTED_RESULT_WITH_DESUGARING);
- }
+ testForR8(parameters.getBackend())
+ .addInnerClasses(DesugarInnerClassesInInterfaces.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepAllClassesRule()
+ .addKeepAttributeInnerClassesAndEnclosingMethod()
+ // With inlining turned off we get the expected result.
+ .addOptionsModification(options -> options.enableInlining = false)
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .applyIf(
+ parameters.canUseDefaultAndStaticInterfaceMethods(),
+ result -> result.assertSuccessWithOutputLines(EXPECTED_RESULT_WITHOUT_DESUGARING),
+ // TODO(b/187377562): We remove the attribute due to not pinning the moved methods.
+ result -> result.assertFailureWithErrorThatThrows(NullPointerException.class));
+ }
+
+ @Test
+ public void testR8Full() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(DesugarInnerClassesInInterfaces.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepAllClassesRule()
+ .addKeepAttributeInnerClassesAndEnclosingMethod()
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .applyIf(
+ parameters.canUseDefaultAndStaticInterfaceMethods(),
+ result -> result.assertSuccessWithOutputLines(EXPECTED_RESULT_WITHOUT_DESUGARING),
+ // TODO(b/187377562): We remove the attribute due to not pinning the moved methods.
+ result -> result.assertFailureWithErrorThatThrows(NullPointerException.class));
}
interface WithAnonymousInner {
diff --git a/src/test/java/com/android/tools/r8/desugar/enclosingmethod/EnclosingMethodRewriteTest.java b/src/test/java/com/android/tools/r8/desugar/enclosingmethod/EnclosingMethodRewriteTest.java
index c61b1fd..e7cc9c9 100644
--- a/src/test/java/com/android/tools/r8/desugar/enclosingmethod/EnclosingMethodRewriteTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/enclosingmethod/EnclosingMethodRewriteTest.java
@@ -3,26 +3,19 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.desugar.enclosingmethod;
-import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX;
-import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.DEFAULT_METHOD_PREFIX;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.junit.Assume.assumeTrue;
-import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
-import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableList;
-import java.nio.file.Path;
-import java.util.Collection;
-import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -50,27 +43,45 @@
}
class TestClass implements A {
- public static void main(String[] args) throws NoClassDefFoundError, ClassNotFoundException {
+ public static void main(String[] args) throws NoClassDefFoundError {
System.out.println(new TestClass().def());
}
}
@RunWith(Parameterized.class)
public class EnclosingMethodRewriteTest extends TestBase {
+
private static final Class<?> MAIN = TestClass.class;
-
private final TestParameters parameters;
- private final boolean enableMinification;
- @Parameterized.Parameters(name = "{0} minification: {1}")
- public static Collection<Object[]> data() {
- return buildParameters(
- getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+ private final String[] EXPECTED =
+ new String[] {
+ "interface " + A.class.getTypeName(),
+ "public default int " + A.class.getTypeName() + ".def()",
+ "42"
+ };
+
+ private final String[] EXPECTED_CC =
+ new String[] {
+ "class " + A.class.getTypeName() + "$-CC",
+ "public static int " + A.class.getTypeName() + "$-CC.a(" + A.class.getTypeName() + ")",
+ "42"
+ };
+
+ private final String[] EXPECTED_NOUGAT =
+ new String[] {
+ "interface " + A.class.getTypeName(), "public int " + A.class.getTypeName() + ".def()", "42"
+ };
+
+ private final String[] EXPECTED_FULL = new String[] {"null", "null", "42"};
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
- public EnclosingMethodRewriteTest(TestParameters parameters, boolean enableMinification) {
+ public EnclosingMethodRewriteTest(TestParameters parameters) {
this.parameters = parameters;
- this.enableMinification = enableMinification;
}
@Test
@@ -79,121 +90,74 @@
testForJvm()
.addTestClasspath()
.run(parameters.getRuntime(), MAIN)
- .assertSuccessWithOutputLines(getExpectedOutput());
+ .assertSuccessWithOutputLines(EXPECTED);
}
@Test
- public void testDesugarAndProguard() throws Exception {
- assumeTrue("Only run on CF runtimes", parameters.isCfRuntime());
- Path desugared = temp.newFile("desugared.jar").toPath().toAbsolutePath();
- R8FullTestBuilder builder =
- testForR8(parameters.getBackend())
- .addProgramClassesAndInnerClasses(A.class)
- .addProgramClasses(C.class, MAIN)
- .addKeepMainRule(MAIN)
- .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
- .setProgramConsumer(new ArchiveConsumer(desugared))
- .setMinApi(parameters.getApiLevel());
- if (enableMinification) {
- builder.addKeepAllClassesRuleWithAllowObfuscation();
- } else {
- builder.addKeepAllClassesRule();
- }
- builder.compile().assertNoMessages();
- testForProguard()
- .addProgramFiles(desugared)
- .addKeepMainRule(MAIN)
- .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
+ public void testR8Compat() throws Exception {
+ testForR8Compat(parameters.getBackend())
+ .addProgramClassesAndInnerClasses(A.class)
+ .addProgramClasses(C.class, MAIN)
+ .addKeepAllClassesRule()
+ .addKeepAttributeInnerClassesAndEnclosingMethod()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(inspector -> inspect(inspector, true))
.run(parameters.getRuntime(), MAIN)
- .assertSuccess();
+ .applyIf(
+ parameters.canUseDefaultAndStaticInterfaceMethods(),
+ result -> {
+ if (!parameters.isCfRuntime()
+ && parameters.getApiLevel().isEqualTo(AndroidApiLevel.N)) {
+ result.assertSuccessWithOutputLines(EXPECTED_NOUGAT);
+ } else {
+ result.assertSuccessWithOutputLines(EXPECTED);
+ }
+ },
+ result -> result.assertSuccessWithOutputLines(EXPECTED_CC));
}
@Test
- public void testR8() throws Exception {
- R8FullTestBuilder builder =
- testForR8(parameters.getBackend())
- .addProgramClassesAndInnerClasses(A.class)
- .addProgramClasses(C.class, MAIN)
- .addKeepMainRule(MAIN)
- .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
- .setMinApi(parameters.getApiLevel());
- if (enableMinification) {
- builder.addKeepAllClassesRuleWithAllowObfuscation();
- } else {
- builder.noTreeShaking().noMinification();
- }
- builder
+ public void testR8Full() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClassesAndInnerClasses(A.class)
+ .addProgramClasses(C.class, MAIN)
+ .addKeepAllClassesRule()
+ .addKeepAttributeInnerClassesAndEnclosingMethod()
+ .setMinApi(parameters.getApiLevel())
.compile()
.inspect(
- inspect -> {
- ClassSubject cImplSubject = inspect.clazz(A.class.getTypeName() + "$1");
- assertThat(cImplSubject, isPresent());
-
- ClassSubject enclosingClassSubject =
- inspect.clazz(
- parameters.canUseDefaultAndStaticInterfaceMethods()
- ? A.class.getTypeName()
- : A.class.getTypeName() + COMPANION_CLASS_NAME_SUFFIX);
- assertThat(enclosingClassSubject, isPresent());
- assertEquals(
- enclosingClassSubject.getDexProgramClass().getType(),
- cImplSubject
- .getDexProgramClass()
- .getEnclosingMethodAttribute()
- .getEnclosingMethod()
- .getHolderType());
- })
+ inspector -> inspect(inspector, parameters.canUseDefaultAndStaticInterfaceMethods()))
.run(parameters.getRuntime(), MAIN)
- .apply(
- result -> result.assertSuccessWithOutputLines(getExpectedOutput(result.inspector())));
+ .applyIf(
+ parameters.canUseDefaultAndStaticInterfaceMethods(),
+ result -> {
+ if (!parameters.isCfRuntime()
+ && parameters.getApiLevel().isEqualTo(AndroidApiLevel.N)) {
+ result.assertSuccessWithOutputLines(EXPECTED_NOUGAT);
+ } else {
+ result.assertSuccessWithOutputLines(EXPECTED);
+ }
+ },
+ result -> result.assertSuccessWithOutputLines(EXPECTED_FULL));
}
- private List<String> getExpectedOutput() {
- return ImmutableList.of(
- "interface " + A.class.getTypeName(),
- "public default int " + A.class.getTypeName() + ".def()",
- "42");
- }
-
- private List<String> getExpectedOutput(CodeInspector inspector) {
- ClassSubject aClassSubject = inspector.clazz(A.class);
- assertThat(aClassSubject, isPresent());
-
- MethodSubject defMethodSubject = aClassSubject.uniqueMethodWithName("def");
- assertThat(defMethodSubject, isPresent());
-
- if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
- String modifiers =
- parameters.isCfRuntime()
- || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V8_1_0)
- ? "public default"
- : "public";
- return ImmutableList.of(
- "interface " + aClassSubject.getFinalName(),
- modifiers
- + " int "
- + aClassSubject.getFinalName()
- + "."
- + defMethodSubject.getFinalName()
- + "()",
- "42");
+ private void inspect(CodeInspector inspector, boolean hasEnclosingMethod) {
+ ClassSubject cImplSubject = inspector.clazz(A.class.getTypeName() + "$1");
+ assertThat(cImplSubject, isPresent());
+ ClassSubject enclosingClassSubject =
+ parameters.canUseDefaultAndStaticInterfaceMethods()
+ ? inspector.clazz(A.class.getTypeName())
+ : inspector.clazz(A.class.getTypeName() + "$-CC");
+ assertThat(enclosingClassSubject, isPresent());
+ EnclosingMethodAttribute enclosingMethodAttribute =
+ cImplSubject.getDexProgramClass().getEnclosingMethodAttribute();
+ if (hasEnclosingMethod) {
+ assertEquals(
+ enclosingClassSubject.getDexProgramClass().getType(),
+ enclosingMethodAttribute.getEnclosingMethod().getHolderType());
+ } else {
+ assertNull(enclosingMethodAttribute);
}
-
- ClassSubject aCompanionClassSubject =
- inspector.clazz(A.class.getTypeName() + COMPANION_CLASS_NAME_SUFFIX);
- assertThat(aCompanionClassSubject, isPresent());
-
- String methodNamePrefix = enableMinification ? "" : DEFAULT_METHOD_PREFIX;
- return ImmutableList.of(
- "class " + aCompanionClassSubject.getFinalName(),
- "public static int "
- + aCompanionClassSubject.getFinalName()
- + "."
- + methodNamePrefix
- + defMethodSubject.getFinalName()
- + "("
- + aClassSubject.getFinalName()
- + ")",
- "42");
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaInStacktraceTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaInStacktraceTest.java
new file mode 100644
index 0000000..3a923e0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaInStacktraceTest.java
@@ -0,0 +1,103 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.lambdas;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LambdaInStacktraceTest extends TestBase {
+
+ static final String fileName = "LambdaInStacktraceTest.java";
+
+ static final String EXPECTED_JAVAC =
+ StringUtils.lines(
+ "getStacktraceWithFileNames(" + fileName + ")",
+ "lambda$main$0(" + fileName + ")",
+ "main(" + fileName + ")",
+ "getStacktraceWithFileNames(" + fileName + ")",
+ "lambda$main$1(" + fileName + ")",
+ "main(" + fileName + ")");
+
+ // TODO(b/187491007): The "call" frame should have a file name.
+ static final String EXPECTED_D8 =
+ StringUtils.lines(
+ "getStacktraceWithFileNames(" + fileName + ")",
+ "lambda$main$0(" + fileName + ")",
+ "call(NULL)",
+ "main(" + fileName + ")",
+ "getStacktraceWithFileNames(" + fileName + ")",
+ "lambda$main$1(" + fileName + ")",
+ "call(NULL)",
+ "main(" + fileName + ")");
+
+ private final TestParameters parameters;
+ private final boolean isDalvik;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ public LambdaInStacktraceTest(TestParameters parameters) {
+ this.parameters = parameters;
+ isDalvik = parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isDalvik();
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue("Only run JVM reference on CF runtimes", parameters.isCfRuntime());
+ testForJvm()
+ .addInnerClasses(LambdaInStacktraceTest.class)
+ .run(parameters.getRuntime(), TestRunner.class, Boolean.toString(false))
+ .assertSuccess()
+ .assertSuccessWithOutput(EXPECTED_JAVAC);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForD8(parameters.getBackend())
+ .addInnerClasses(LambdaInStacktraceTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestRunner.class, Boolean.toString(isDalvik))
+ .assertSuccessWithOutput(EXPECTED_D8);
+ }
+
+ static class TestRunner {
+
+ public static List<String> getStacktraceWithFileNames(boolean isDalvik) {
+ Throwable stackTrace = new RuntimeException().fillInStackTrace();
+
+ // Dalvik has an additional "main(NativeStart.java)" bottom frame.
+ List<String> frames = new ArrayList<>();
+ for (int i = 0; i < stackTrace.getStackTrace().length - (isDalvik ? 1 : 0); i++) {
+ StackTraceElement stackTraceElement = stackTrace.getStackTrace()[i];
+ String fileName = stackTraceElement.getFileName();
+ frames.add(
+ stackTraceElement.getMethodName() + "(" + (fileName != null ? fileName : "NULL") + ")");
+ }
+ return frames;
+ }
+
+ public static void main(String[] args) throws Exception {
+ boolean isDalvik = Boolean.parseBoolean(args[0]);
+ Callable<List<String>> callable = () -> getStacktraceWithFileNames(isDalvik);
+ System.out.println(String.join(System.lineSeparator(), callable.call()));
+
+ Callable<Callable<List<String>>> callableNested =
+ () -> () -> getStacktraceWithFileNames(isDalvik);
+ System.out.println(String.join(System.lineSeparator(), callableNested.call().call()));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepAttributesTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepAttributesTest.java
index 21cc62c..b091f5e 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepAttributesTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepAttributesTest.java
@@ -107,8 +107,13 @@
assertThat(innerClass, isPresent());
MethodSubject testMethod = innerClass.uniqueMethodWithName("test");
assertThat(testMethod, isPresent());
- // TODO(b/184927364): TO; should be replaced with Supplier
- assertEquals("(TO;TM;)TI;", testMethod.getFinalSignatureAttribute());
+ if (isCompat) {
+ assertEquals("(TO;TM;)TI;", testMethod.getFinalSignatureAttribute());
+ } else {
+ assertEquals(
+ "(" + descriptor(Supplier.class) + descriptor(Predicate.class) + ")TI;",
+ testMethod.getFinalSignatureAttribute());
+ }
}
public interface Supplier<T> {}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java
index c1d8c5f..64f2ac3 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java
@@ -67,7 +67,7 @@
BiPredicateUtils.alwaysFalse(),
alwaysTrue(),
"(LList<TT;>;)LList<TR;>;",
- "(LList<*>;)LList<*>;")
+ "(LList<Ljava/lang/Object;>;)LList<Ljava/lang/Object;>;")
.assertNoMessages();
}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedOuterTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedOuterTest.java
index ebca08d..af2bb7f 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedOuterTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedOuterTest.java
@@ -55,18 +55,15 @@
public void checkSignatures(CodeInspector inspector) {
checkSignature(
inspector.clazz(Bar.class.getTypeName() + "$1"),
- "L" + binaryName(Foo.class) + "<*" + descriptor(Main.class) + ">;");
+ "L" + binaryName(Foo.class) + "<Ljava/lang/Object;" + descriptor(Main.class) + ">;");
checkSignature(
- inspector.clazz(Bar.class.getTypeName() + "$2"), "L" + binaryName(Foo.class) + "<**>;");
+ inspector.clazz(Bar.class.getTypeName() + "$2"),
+ "L" + binaryName(Foo.class) + "<Ljava/lang/Object;Ljava/lang/Object;>;");
}
private void checkSignature(ClassSubject classSubject, String expectedSignature) {
assertThat(classSubject, isPresent());
- // TODO(b/185098797): Make sure to work for full mode.
- if (!isCompat) {
- return;
- }
- assertEquals(expectedSignature, classSubject.getFinalSignatureAttribute());
+ assertEquals(isCompat ? expectedSignature : null, classSubject.getFinalSignatureAttribute());
}
public abstract static class Foo<T, R> {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/ConstClassCanonicalizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/ConstClassCanonicalizationTest.java
index 43ea1b7..380c5be 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/ConstClassCanonicalizationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/ConstClassCanonicalizationTest.java
@@ -8,15 +8,14 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
-import com.android.tools.r8.D8TestRunResult;
import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ir.optimize.canonicalization.ConstClassMain.Outer;
import com.android.tools.r8.ir.optimize.canonicalization.ConstClassMain.Outer.Inner;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -24,6 +23,7 @@
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.nio.file.Path;
+import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -55,6 +55,10 @@
public static void main(String... args) {
Class outer = Inner.class.getDeclaringClass();
+ if (outer == null) {
+ System.out.print("outer is null");
+ return;
+ }
System.out.println(outer.getSimpleName());
for (Class<?> inner : outer.getDeclaredClasses()) {
System.out.println(inner.getSimpleName());
@@ -94,19 +98,23 @@
private static final int CANONICALIZED_OUTER_COUNT = 1;
private static final int CANONICALIZED_INNER_COUNT = 1;
- @Parameterized.Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ @Parameterized.Parameters(name = "{0}, isCompat: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
}
private final TestParameters parameters;
+ private final boolean isCompat;
- public ConstClassCanonicalizationTest(TestParameters parameters) {
+ public ConstClassCanonicalizationTest(TestParameters parameters, boolean isCompat) {
this.parameters = parameters;
+ this.isCompat = isCompat;
}
@Test
public void testJVMOutput() throws Exception {
+ assumeTrue(isCompat);
assumeTrue("Only run JVM reference on CF runtimes", parameters.isCfRuntime());
testForJvm()
.addTestClasspath()
@@ -116,35 +124,36 @@
@Test
public void testD8_incremental() throws Exception {
+ assumeTrue(isCompat);
assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
-
Path zipA = temp.newFile("a.zip").toPath().toAbsolutePath();
testForD8()
.release()
.addProgramClasses(IncrementalA.class)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.setProgramConsumer(new ArchiveConsumer(zipA))
.compile();
testForD8()
.release()
.addProgramClasses(IncrementalMain.class)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.addRunClasspathFiles(zipA)
.run(parameters.getRuntime(), IncrementalMain.class)
.assertSuccessWithOutput(INCREMENTAL_OUTPUT)
- .inspect(inspector -> {
- ClassSubject main = inspector.clazz(IncrementalMain.class);
- assertThat(main, isPresent());
- MethodSubject mainMethod = main.mainMethod();
- assertThat(mainMethod, isPresent());
- assertEquals(
- 3,
- mainMethod.streamInstructions().filter(InstructionSubject::isConstClass).count());
- });
+ .inspect(
+ inspector -> {
+ ClassSubject main = inspector.clazz(IncrementalMain.class);
+ assertThat(main, isPresent());
+ MethodSubject mainMethod = main.mainMethod();
+ assertThat(mainMethod, isPresent());
+ assertEquals(
+ 3,
+ mainMethod.streamInstructions().filter(InstructionSubject::isConstClass).count());
+ });
}
- private void test(SingleTestRunResult result, int mainCount, int outerCount, int innerCount)
+ private void test(SingleTestRunResult<?> result, int mainCount, int outerCount, int innerCount)
throws Exception {
CodeInspector codeInspector = result.inspector();
ClassSubject mainClass = codeInspector.clazz(MAIN);
@@ -165,42 +174,51 @@
}
@Test
- public void testD8() throws Exception {
+ public void testD8Debug() throws Exception {
+ assumeTrue(isCompat);
assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
-
- D8TestRunResult result =
+ test(
testForD8()
.debug()
.addProgramClassesAndInnerClasses(MAIN)
.addOptionsModification(InternalOptions::disableNameReflectionOptimization)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
- .assertSuccessWithOutput(JAVA_OUTPUT);
- test(result, CANONICALIZED_MAIN_COUNT, ORIGINAL_OUTER_COUNT, ORIGINAL_INNER_COUNT);
+ .assertSuccessWithOutput(JAVA_OUTPUT),
+ CANONICALIZED_MAIN_COUNT,
+ ORIGINAL_OUTER_COUNT,
+ ORIGINAL_INNER_COUNT);
+ }
- result =
+ @Test
+ public void testD8Release() throws Exception {
+ assumeTrue(isCompat);
+ assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
+ test(
testForD8()
.release()
.addProgramClassesAndInnerClasses(MAIN)
.addOptionsModification(InternalOptions::disableNameReflectionOptimization)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
- .assertSuccessWithOutput(JAVA_OUTPUT);
- test(result, CANONICALIZED_MAIN_COUNT, ORIGINAL_OUTER_COUNT, ORIGINAL_INNER_COUNT);
+ .assertSuccessWithOutput(JAVA_OUTPUT),
+ CANONICALIZED_MAIN_COUNT,
+ ORIGINAL_OUTER_COUNT,
+ ORIGINAL_INNER_COUNT);
}
@Test
public void testR8() throws Exception {
R8TestRunResult result =
- testForR8(parameters.getBackend())
+ (isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()))
.addProgramClassesAndInnerClasses(MAIN)
.addKeepMainRule(MAIN)
- .addKeepAllAttributes()
+ .addKeepAttributeInnerClassesAndEnclosingMethod()
.noMinification()
.addOptionsModification(InternalOptions::disableNameReflectionOptimization)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
- .assertSuccessWithOutput(JAVA_OUTPUT);
+ .assertSuccessWithOutput(isCompat ? JAVA_OUTPUT : "outer is null");
// The number of expected const-class instructions differs because constant canonicalization is
// only enabled for the DEX backend.
int expectedMainCount =
@@ -209,6 +227,13 @@
parameters.isCfRuntime() ? ORIGINAL_OUTER_COUNT : CANONICALIZED_OUTER_COUNT;
int expectedInnerCount =
parameters.isCfRuntime() ? ORIGINAL_INNER_COUNT : CANONICALIZED_INNER_COUNT;
+ if (!isCompat) {
+ // When not compat we end up class merging inner an outer.
+ if (parameters.isCfRuntime()) {
+ expectedOuterCount = expectedOuterCount + expectedInnerCount;
+ }
+ expectedInnerCount = 0;
+ }
test(result, expectedMainCount, expectedOuterCount, expectedInnerCount);
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
index 81612ee..102d5c7 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
@@ -11,10 +11,8 @@
import com.android.tools.r8.D8TestRunResult;
import com.android.tools.r8.ForceInline;
import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestParameters;
-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.ClassSubject;
@@ -114,41 +112,26 @@
}
public class GetSimpleNameTest extends GetNameTestBase {
- private Collection<Path> classPaths;
- private static final String JAVA_OUTPUT = StringUtils.lines(
- "Local_t03",
- "InnerLocal",
- "$",
- "$$",
- "Local[][][]",
- "[][][]",
- "Inner",
- "Inner"
- );
- // JDK8 computes the simple name differently: some assumptions about non-member classes,
- // e.g., 1 or more digits (followed by the simple name if it's local).
- // Since JDK9, the simple name is computed by stripping off the package name.
- // See b/132808897 for more details.
- private static final String RENAMED_OUTPUT_JDK8 = StringUtils.lines(
- "d",
- "a",
- "a",
- "a",
- "c[][][]",
- "b[][][]",
- "a",
- "a"
- );
- private static final String RENAMED_OUTPUT = StringUtils.lines(
- "d",
- "a",
- "a",
- "a",
- "c[][][]",
- "[][][]",
- "a",
- "a"
- );
+ private final Collection<Path> classPaths;
+ private static final String JVM_OUTPUT =
+ StringUtils.lines(
+ "Local_t03", "InnerLocal", "$", "$$", "Local[][][]", "[][][]", "Inner", "Inner");
+
+ // When removing the class attributes the simple name of a class becomes prepended with the
+ // outer class.
+ private static final String OUTPUT_NO_ATTRIBUTES =
+ StringUtils.lines(
+ "ClassGetSimpleName$1Local_t03",
+ "InnerLocal",
+ "ClassGetSimpleName$1$",
+ "$$",
+ "ClassGetSimpleName$1Local[][][]",
+ "ClassGetSimpleName$1[][][]",
+ "Inner",
+ "Inner");
+
+ private static final String RENAMED_OUTPUT =
+ StringUtils.lines("d", "a", "a", "a", "c[][][]", "b[][][]", "a", "a");
private static final Class<?> MAIN = ClassGetSimpleName.class;
public GetSimpleNameTest(TestParameters parameters, boolean enableMinification) throws Exception {
@@ -169,10 +152,11 @@
assumeTrue(
"Only run JVM reference on CF runtimes",
parameters.isCfRuntime() && !enableMinification);
+ CodeInspector inspector = new CodeInspector(classPaths);
testForJvm()
.addTestClasspath()
.run(parameters.getRuntime(), MAIN)
- .assertSuccessWithOutput(JAVA_OUTPUT);
+ .assertSuccessWithOutput(JVM_OUTPUT);
}
private void test(SingleTestRunResult<?> result) throws Exception {
@@ -194,7 +178,7 @@
.setMinApi(parameters.getApiLevel())
.addOptionsModification(this::configure)
.run(parameters.getRuntime(), MAIN)
- .assertSuccessWithOutput(JAVA_OUTPUT);
+ .assertSuccessWithOutput(JVM_OUTPUT);
test(result);
result =
@@ -204,61 +188,67 @@
.setMinApi(parameters.getApiLevel())
.addOptionsModification(this::configure)
.run(parameters.getRuntime(), MAIN)
- .assertSuccessWithOutput(JAVA_OUTPUT);
+ .assertSuccessWithOutput(JVM_OUTPUT);
test(result);
}
@Test
+ public void testR8_pin_all() throws Exception {
+ // Pinning the test class.
+ testForR8(parameters.getBackend())
+ .addProgramFiles(classPaths)
+ .addForceInliningAnnotations()
+ .enableInliningAnnotations()
+ .addKeepAllClassesRule()
+ .addKeepAttributeInnerClassesAndEnclosingMethod()
+ .minification(enableMinification)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(this::configure)
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutput(JVM_OUTPUT)
+ .apply(this::test);
+ }
+
+ @Test
public void testR8_pinning() throws Exception {
// Pinning the test class.
- R8TestRunResult result =
- testForR8(parameters.getBackend())
- .addProgramFiles(classPaths)
- .enableForceInliningAnnotations()
- .enableInliningAnnotations()
- .addKeepMainRule(MAIN)
- .addKeepRules("-keep class **.ClassGetSimpleName*")
- .addKeepRules("-keep class **.Outer*")
- .addKeepAttributes("InnerClasses", "EnclosingMethod")
- .addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString())
- .minification(enableMinification)
- .setMinApi(parameters.getApiLevel())
- .addOptionsModification(this::configure)
- .run(parameters.getRuntime(), MAIN)
- .assertSuccessWithOutput(JAVA_OUTPUT);
- test(result);
+ testForR8(parameters.getBackend())
+ .addProgramFiles(classPaths)
+ .enableForceInliningAnnotations()
+ .enableInliningAnnotations()
+ .addKeepMainRule(MAIN)
+ .addKeepRules("-keep class **.ClassGetSimpleName*")
+ .addKeepRules("-keep class **.Outer*")
+ .addKeepAttributeInnerClassesAndEnclosingMethod()
+ .minification(enableMinification)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(this::configure)
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutput(OUTPUT_NO_ATTRIBUTES)
+ .apply(this::test);
}
@Test
public void testR8_shallow_pinning() throws Exception {
// Shallow pinning the test class.
- R8TestRunResult result =
- testForR8(parameters.getBackend())
- .addProgramFiles(classPaths)
- .enableInliningAnnotations()
- .addKeepMainRule(MAIN)
- .addKeepRules("-keep,allowobfuscation class **.ClassGetSimpleName*")
- // See b/119471127: some old VMs are not resilient to broken attributes.
- // Comment out the following line to reproduce b/120130435
- // then use OUTPUT_WITH_SHRUNK_ATTRIBUTES
- .addKeepRules("-keep,allowobfuscation class **.Outer*")
- .addKeepAttributes("InnerClasses", "EnclosingMethod")
- .addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString())
- .enableForceInliningAnnotations()
- .minification(enableMinification)
- .setMinApi(parameters.getApiLevel())
- .addOptionsModification(this::configure)
- .run(parameters.getRuntime(), MAIN);
- if (enableMinification) {
- if (parameters.isCfRuntime()
- && parameters.getRuntime().asCf().getVm().lessThanOrEqual(CfVm.JDK8)) {
- result.assertSuccessWithOutput(RENAMED_OUTPUT_JDK8);
- } else {
- result.assertSuccessWithOutput(RENAMED_OUTPUT);
- }
- } else {
- result.assertSuccessWithOutput(JAVA_OUTPUT);
- }
- test(result);
+ testForR8(parameters.getBackend())
+ .addProgramFiles(classPaths)
+ .enableForceInliningAnnotations()
+ .enableInliningAnnotations()
+ .addKeepMainRule(MAIN)
+ .addKeepRules("-keep,allowobfuscation class **.ClassGetSimpleName*")
+ // See b/119471127: some old VMs are not resilient to broken attributes.
+ // Comment out the following line to reproduce b/120130435
+ // then use OUTPUT_WITH_SHRUNK_ATTRIBUTES
+ .addKeepRules("-keep,allowobfuscation class **.Outer*")
+ .addKeepAttributeInnerClassesAndEnclosingMethod()
+ .minification(enableMinification)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(this::configure)
+ .run(parameters.getRuntime(), MAIN)
+ .applyIf(enableMinification, result -> result.assertSuccessWithOutput(RENAMED_OUTPUT))
+ .applyIf(
+ !enableMinification, result -> result.assertSuccessWithOutput(OUTPUT_NO_ATTRIBUTES))
+ .apply(this::test);
}
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingKeepAttributesKotlinStyleTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingKeepAttributesKotlinStyleTest.java
index 3ccb9ad..a2127ef 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingKeepAttributesKotlinStyleTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingKeepAttributesKotlinStyleTest.java
@@ -102,12 +102,7 @@
assertEquals(24, lambdasInInput.getNumberOfKStyleLambdas());
// All K-style Kotlin lambdas are merged if no attributes are kept.
- if (attributes.isEmpty()) {
- inspector.assertClassReferencesMerged(lambdasInInput.getKStyleLambdas());
- } else {
- // TODO(b/179018501): allow merging classes with inner/outer classes.
- inspector.assertClassReferencesNotMerged(lambdasInInput.getKStyleLambdas());
- }
+ inspector.assertClassReferencesMerged(lambdasInInput.getKStyleLambdas());
}
private String getExpectedOutput() {
diff --git a/src/test/java/com/android/tools/r8/naming/NonMemberClassTest.java b/src/test/java/com/android/tools/r8/naming/NonMemberClassTest.java
index f777cda..5ce4bde 100644
--- a/src/test/java/com/android/tools/r8/naming/NonMemberClassTest.java
+++ b/src/test/java/com/android/tools/r8/naming/NonMemberClassTest.java
@@ -74,23 +74,16 @@
}
public String getExpectedOutput(TestParameters parameters, boolean isFullMode) {
+ // In full mode, we remove all attributes since nothing pinned, i.e., no reflection uses.
+ if (isFullMode) {
+ return "";
+ }
switch (this) {
case KEEP_INNER_CLASSES:
+ case NO_KEEP_NO_MINIFICATION:
return JVM_OUTPUT;
case KEEP_ALLOW_MINIFICATION:
- if (parameters.isCfRuntime()
- && parameters.getRuntime().asCf().getVm().lessThanOrEqual(CfVm.JDK8)) {
- return MINIFIED_OUTPUT_JDK8;
- }
- return JVM_OUTPUT;
- case NO_KEEP_NO_MINIFICATION:
- // In full mode, we remove all attributes since nothing pinned, i.e., no reflection uses.
- return isFullMode ? "" : JVM_OUTPUT;
case NO_KEEP_MINIFICATION:
- // In full mode, we remove all attributes since nothing pinned, i.e., no reflection uses.
- if (isFullMode) {
- return "";
- }
if (parameters.isCfRuntime()
&& parameters.getRuntime().asCf().getVm().lessThanOrEqual(CfVm.JDK8)) {
return MINIFIED_OUTPUT_JDK8;
@@ -102,19 +95,7 @@
}
public void inspect(boolean isFullMode, CodeInspector inspector) {
- int expectedNumberOfNonMemberInnerClasses;
- switch (this) {
- case KEEP_INNER_CLASSES:
- case KEEP_ALLOW_MINIFICATION:
- expectedNumberOfNonMemberInnerClasses = 2;
- break;
- case NO_KEEP_NO_MINIFICATION:
- case NO_KEEP_MINIFICATION:
- expectedNumberOfNonMemberInnerClasses = isFullMode ? 0 : 2;
- break;
- default:
- throw new Unreachable();
- }
+ int expectedNumberOfNonMemberInnerClasses = isFullMode ? 0 : 2;
assertEquals(
expectedNumberOfNonMemberInnerClasses,
inspector.allClasses().stream()
@@ -190,7 +171,8 @@
.addInnerClasses(NonMemberClassTest.class)
.addKeepMainRule(MAIN)
.addKeepRules(config.getKeepRules())
- .addKeepAttributes("Signature", "InnerClasses", "EnclosingMethod")
+ .addKeepAttributeInnerClassesAndEnclosingMethod()
+ .addKeepAttributeSignature()
.enableInliningAnnotations()
.setMinApi(parameters.getApiLevel())
.addOptionsModification(options -> options.enableClassInlining = false)
diff --git a/src/test/java/com/android/tools/r8/naming/b126592786/B126592786.java b/src/test/java/com/android/tools/r8/naming/b126592786/B126592786.java
index b2614a3..fb9c53e 100644
--- a/src/test/java/com/android/tools/r8/naming/b126592786/B126592786.java
+++ b/src/test/java/com/android/tools/r8/naming/b126592786/B126592786.java
@@ -57,7 +57,7 @@
.compile()
.inspect(
inspector -> {
- String genericTypeDescriptor = "*";
+ String genericTypeDescriptor = "Ljava/lang/Object;";
if (genericTypeLive) {
ClassSubject genericType = inspector.clazz(GenericType.class);
assertThat(genericType, isPresentAndRenamed(minify));
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithCollisionsTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithCollisionsTest.java
index 42f2492..7600af0 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithCollisionsTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithCollisionsTest.java
@@ -4,10 +4,15 @@
package com.android.tools.r8.repackage;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.Iterator;
@@ -15,13 +20,27 @@
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 RepackageWithCollisionsTest extends RepackageTestBase {
+ @Parameters(name = "{1}, kind: {0}, isCompat: {2}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
+ getTestParameters().withAllRuntimesAndApiLevels().build(),
+ BooleanUtils.values());
+ }
+
+ private final boolean isCompat;
+
public RepackageWithCollisionsTest(
- String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+ String flattenPackageHierarchyOrRepackageClasses,
+ TestParameters parameters,
+ boolean isCompat) {
super(flattenPackageHierarchyOrRepackageClasses, parameters);
+ this.isCompat = isCompat;
}
@Override
@@ -34,7 +53,7 @@
@Test
public void test() throws Exception {
- testForR8(parameters.getBackend())
+ (isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()))
.addInnerClasses(getClass())
.addProgramClasses(getTestClasses())
.addKeepMainRule(TestClass.class)
@@ -63,22 +82,22 @@
Iterator<Class<?>> testClassesIterator = getTestClasses().iterator();
Class<?> firstFoo = testClassesIterator.next();
- assertThat(firstFoo, isRepackagedAsExpected(inspector, "a"));
+ isRepackagedOrAbsentInFullMode(inspector, firstFoo, "a");
Class<?> firstFooBar = testClassesIterator.next();
assertThat(firstFooBar, isRepackagedAsExpected(inspector, "a"));
Class<?> firstFirstFoo = testClassesIterator.next();
- assertThat(firstFirstFoo, isRepackagedAsExpected(inspector, "b"));
+ isRepackagedOrAbsentInFullMode(inspector, firstFirstFoo, "b");
Class<?> firstFirstFooBar = testClassesIterator.next();
- assertThat(firstFirstFooBar, isRepackagedAsExpected(inspector, "b"));
+ isRepackagedOrAbsentInFullMode(inspector, firstFirstFooBar, "b");
Class<?> secondFoo = testClassesIterator.next();
- assertThat(secondFoo, isRepackagedAsExpected(inspector, "c"));
+ isRepackagedOrAbsentInFullMode(inspector, secondFoo, "c");
Class<?> secondBar = testClassesIterator.next();
- assertThat(secondBar, isRepackagedAsExpected(inspector, "c"));
+ isRepackagedOrAbsentInFullMode(inspector, secondBar, "c");
Class<?> destinationFoo = testClassesIterator.next();
assertThat(inspector.clazz(destinationFoo), isPresentAndNotRenamed());
@@ -93,6 +112,15 @@
assertThat(inspector.clazz(destinationFirstBar), isPresentAndNotRenamed());
}
+ private void isRepackagedOrAbsentInFullMode(
+ CodeInspector inspector, Class<?> clazz, String packageName) {
+ if (isCompat) {
+ assertThat(clazz, isRepackagedAsExpected(inspector, packageName));
+ } else {
+ assertThat(inspector.clazz(clazz), not(isPresent()));
+ }
+ }
+
private static List<Class<?>> getTestClasses() {
return ImmutableList.of(
com.android.tools.r8.repackage.testclasses.repackagewithcollisionstest.first.Foo.class,
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateInnerClassTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateInnerClassTest.java
index e4c7fd1..3097738 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateInnerClassTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateInnerClassTest.java
@@ -7,8 +7,10 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestBuilder;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.repackage.RepackageWithPackagePrivateInnerClassTest.IneligibleForRepackaging.NonPublicKeptClass;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -26,29 +28,45 @@
}
@Test
- public void test() throws Exception {
- testForR8(parameters.getBackend())
+ public void testCompat() throws Exception {
+ test(
+ testForR8Compat(parameters.getBackend()).addKeepClassRules(NonPublicKeptClass.class),
+ false);
+ }
+
+ @Test
+ public void testFull() throws Exception {
+ test(testForR8(parameters.getBackend()).addKeepClassRules(NonPublicKeptClass.class), true);
+ }
+
+ private void test(R8TestBuilder<?> builder, boolean expectRepackaged) throws Exception {
+ builder
.addInnerClasses(getClass())
.addKeepMainRule(TestClass.class)
- .addKeepClassRules(NonPublicKeptClass.class)
.addKeepAttributeInnerClassesAndEnclosingMethod()
.apply(this::configureRepackaging)
.enableInliningAnnotations()
.setMinApi(parameters.getApiLevel())
.compile()
- .inspect(this::inspect)
+ .inspect(inspector -> inspect(inspector, expectRepackaged))
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutputLines("Hello world!");
}
- private void inspect(CodeInspector inspector) {
+ private void inspect(CodeInspector inspector, boolean expectRepackaged) {
ClassSubject classSubject = inspector.clazz(IneligibleForRepackaging.class);
assertThat(classSubject, isPresent());
// Verify that the class was not repackaged.
- assertEquals(
- IneligibleForRepackaging.class.getPackage().getName(),
- classSubject.getDexProgramClass().getType().getPackageName());
+ if (expectRepackaged) {
+ assertNotEquals(
+ IneligibleForRepackaging.class.getPackage().getName(),
+ classSubject.getDexProgramClass().getType().getPackageName());
+ } else {
+ assertEquals(
+ IneligibleForRepackaging.class.getPackage().getName(),
+ classSubject.getDexProgramClass().getType().getPackageName());
+ }
}
public static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderClassLoaderRewritingTest.java b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderClassLoaderRewritingTest.java
new file mode 100644
index 0000000..fbaef02
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderClassLoaderRewritingTest.java
@@ -0,0 +1,110 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.rewrite;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+import static junit.framework.TestCase.assertNull;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import java.util.ServiceLoader;
+import java.util.zip.ZipFile;
+import org.hamcrest.MatcherAssert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ServiceLoaderClassLoaderRewritingTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final String EXPECTED_OUTPUT = StringUtils.lines("Hello World!");
+
+ public interface Service {
+
+ void print();
+ }
+
+ public static class ServiceImpl implements Service {
+
+ @Override
+ public void print() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public static class MainRunner {
+
+ public static void main(String[] args) {
+ run1();
+ }
+
+ @NeverInline
+ public static void run1() {
+ ClassLoader classLoader = Service.class.getClassLoader();
+ checkNotNull(classLoader);
+ for (Service x : ServiceLoader.load(Service.class, classLoader)) {
+ x.print();
+ }
+ }
+
+ @NeverInline
+ public static void checkNotNull(ClassLoader classLoader) {
+ if (classLoader == null) {
+ throw new NullPointerException("ClassLoader should not be null");
+ }
+ }
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ServiceLoaderClassLoaderRewritingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRewritings() throws Exception {
+ Path path = temp.newFile("out.zip").toPath();
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ServiceLoaderClassLoaderRewritingTest.class)
+ .addKeepMainRule(MainRunner.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableInliningAnnotations()
+ .addDataEntryResources(
+ DataEntryResource.fromBytes(
+ StringUtils.lines(ServiceImpl.class.getTypeName()).getBytes(),
+ "META-INF/services/" + Service.class.getTypeName(),
+ Origin.unknown()))
+ .compile()
+ .writeToZip(path)
+ .inspect(this::verifyNoServiceLoaderLoads)
+ .run(parameters.getRuntime(), MainRunner.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+
+ // Check that we have removed the service configuration from META-INF/services.
+ ZipFile zip = new ZipFile(path.toFile());
+ assertNull(zip.getEntry("META-INF/services/" + Service.class.getTypeName()));
+ }
+
+ private void verifyNoServiceLoaderLoads(CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(MainRunner.class);
+ assertTrue(classSubject.isPresent());
+ classSubject.forAllMethods(
+ method -> MatcherAssert.assertThat(method, not(invokesMethodWithName("load"))));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderMultipleCallsSameMethodTest.java b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderMultipleCallsSameMethodTest.java
new file mode 100644
index 0000000..49589c6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderMultipleCallsSameMethodTest.java
@@ -0,0 +1,144 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.rewrite;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNull;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ServiceLoader;
+import java.util.concurrent.ExecutionException;
+import java.util.zip.ZipFile;
+import org.hamcrest.MatcherAssert;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ServiceLoaderMultipleCallsSameMethodTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final String EXPECTED_OUTPUT = StringUtils.lines("Hello World!", "Hello World!");
+
+ public interface Service {
+
+ void print();
+ }
+
+ public static class ServiceImpl implements Service {
+
+ @Override
+ public void print() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public static class ServiceImpl2 implements Service {
+
+ @Override
+ public void print() {
+ System.out.println("Hello World 2!");
+ }
+ }
+
+ public static class MainRunner {
+
+ public static void main(String[] args) {
+ run1();
+ }
+
+ @NeverInline
+ public static void run1() {
+ ClassLoader classLoader = Service.class.getClassLoader();
+ for (Service x : ServiceLoader.load(Service.class, classLoader)) {
+ x.print();
+ }
+ for (Service x : ServiceLoader.load(Service.class, classLoader)) {
+ x.print();
+ }
+ }
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ServiceLoaderMultipleCallsSameMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRewritings() throws IOException, CompilationFailedException, ExecutionException {
+ Path path = temp.newFile("out.zip").toPath();
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ServiceLoaderMultipleCallsSameMethodTest.class)
+ .addKeepMainRule(MainRunner.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableInliningAnnotations()
+ .addDataEntryResources(
+ DataEntryResource.fromBytes(
+ StringUtils.lines(ServiceImpl.class.getTypeName()).getBytes(),
+ "META-INF/services/" + Service.class.getTypeName(),
+ Origin.unknown()))
+ .compile()
+ .writeToZip(path)
+ .run(parameters.getRuntime(), MainRunner.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT)
+ // Check that we have actually rewritten the calls to ServiceLoader.load.
+ .inspect(this::verifyNoServiceLoaderLoads)
+ .inspect(this::verifyNoClassLoaders)
+ .inspect(
+ inspector -> {
+ // Check the synthesize service loader method is a single shared method.
+ // Due to minification we just check there is only a single synthetic class with a
+ // single static method.
+ boolean found = false;
+ for (FoundClassSubject clazz : inspector.allClasses()) {
+ if (clazz.isSynthetic()) {
+ assertFalse(found);
+ found = true;
+ assertEquals(1, clazz.allMethods().size());
+ clazz.forAllMethods(m -> assertTrue(m.isStatic()));
+ }
+ }
+ });
+
+ // Check that we have removed the service configuration from META-INF/services.
+ ZipFile zip = new ZipFile(path.toFile());
+ assertNull(zip.getEntry("META-INF/services/" + Service.class.getTypeName()));
+ }
+
+ private void verifyNoServiceLoaderLoads(CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(MainRunner.class);
+ Assert.assertTrue(classSubject.isPresent());
+ classSubject.forAllMethods(
+ method -> MatcherAssert.assertThat(method, not(invokesMethodWithName("load"))));
+ }
+
+ private void verifyNoClassLoaders(CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(MainRunner.class);
+ Assert.assertTrue(classSubject.isPresent());
+ classSubject.forAllMethods(
+ method -> MatcherAssert.assertThat(method, not(invokesMethodWithName("getClassLoader"))));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/KeepEnclosingMethodForKeptMethodTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/KeepEnclosingMethodForKeptMethodTest.java
index cc1f241..d2e5d79 100644
--- a/src/test/java/com/android/tools/r8/shaking/attributes/KeepEnclosingMethodForKeptMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/KeepEnclosingMethodForKeptMethodTest.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.shaking.attributes;
-import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8TestBuilder;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestBase;
@@ -19,6 +18,7 @@
public class KeepEnclosingMethodForKeptMethodTest extends TestBase {
private final TestParameters parameters;
+
private final String[] EXPECTED = {
"null",
"class com.android.tools.r8.shaking.attributes.KeepEnclosingMethodForKeptMethodTest"
@@ -28,13 +28,8 @@
"public"
+ " com.android.tools.r8.shaking.attributes.KeepEnclosingMethodForKeptMethodTest$KeptClass()"
};
- private final String[] R8_OUTPUT = {
- "null",
- "class com.android.tools.r8.shaking.attributes.KeepEnclosingMethodForKeptMethodTest"
- + "$KeptClass",
- "null",
- "null"
- };
+
+ private final String[] EXPECTED_FULL = {"null", "null", "null", "null"};
@Parameters(name = "{0}")
public static TestParametersCollection data() {
@@ -58,8 +53,7 @@
@Test
public void testR8Full() throws Exception {
- // TODO(b/171194649): This should output EXPECTED.
- runTest(testForR8(parameters.getBackend())).assertSuccessWithOutputLines(R8_OUTPUT);
+ runTest(testForR8(parameters.getBackend())).assertSuccessWithOutputLines(EXPECTED_FULL);
}
@Test
@@ -78,7 +72,6 @@
.addKeepMainRule(KeptClass.class)
.addKeepClassAndMembersRules(KeptClass.class)
.setMinApi(parameters.getApiLevel())
- .enableInliningAnnotations()
.run(parameters.getRuntime(), KeptClass.class);
}
@@ -116,7 +109,6 @@
new KeptClass().instanceField.foo();
}
- @NeverInline
public static I enclosingFromKeptMethod() {
return new I() {
@Override
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/KeepInnerClassesEnclosingMethodAnnotationsTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/KeepInnerClassesEnclosingMethodAnnotationsTest.java
index 83c55fd..6c7c774 100644
--- a/src/test/java/com/android/tools/r8/shaking/attributes/KeepInnerClassesEnclosingMethodAnnotationsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/KeepInnerClassesEnclosingMethodAnnotationsTest.java
@@ -14,7 +14,9 @@
import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
import com.android.tools.r8.shaking.attributes.testclasses.Outer;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -23,43 +25,24 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-
-class Main {
-
- public static void main(String[] args) {
- Class<?>[] declaredClasses = Outer.class.getDeclaredClasses();
- if (declaredClasses.length == 0) {
- System.out.println("No declared classes");
- } else {
- for (int i = 0; i < declaredClasses.length; i++) {
- System.out.println("Declared class: " + declaredClasses[i].getName());
- }
- }
- if (Outer.Inner.class.getDeclaringClass() == null) {
- System.out.println("No declaring classes");
- } else {
- System.out.println("Declaring class: " + Outer.Inner.class.getDeclaringClass().getName());
- }
- }
-}
+import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class KeepInnerClassesEnclosingMethodAnnotationsTest extends TestBase {
- enum TestConfig {
- R8,
- PROGUARD
+ private final TestParameters parameters;
+ private final boolean isCompat;
+
+ @Parameters(name = "{0}, isCompat: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
}
- @Parameterized.Parameters(name = "{0}")
- public static Object[] parameters() {
- return TestConfig.values();
- }
-
- private final TestConfig config;
-
- public KeepInnerClassesEnclosingMethodAnnotationsTest(TestConfig config) {
- this.config = config;
+ public KeepInnerClassesEnclosingMethodAnnotationsTest(
+ TestParameters parameters, boolean isCompat) {
+ this.parameters = parameters;
+ this.isCompat = isCompat;
}
private static class TestResult {
@@ -79,27 +62,14 @@
}
}
- private TestResult runProguard(List<String> proguardConfiguration) throws Throwable {
- return new TestResult(
- testForProguard()
- .addProgramClasses(ImmutableList.of(Main.class, Outer.class, Outer.Inner.class))
- .addKeepRules(proguardConfiguration)
- .run(Main.class));
- }
-
private TestResult runR8(List<String> proguardConfiguration) throws Throwable {
return new TestResult(
- testForR8(Backend.DEX)
+ (isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()))
.addProgramClassesAndInnerClasses(Outer.class)
.addProgramClasses(Main.class)
+ .setMinApi(parameters.getApiLevel())
.addKeepRules(proguardConfiguration)
- .run(Main.class));
- }
-
- private TestResult runShrinker(List<String> proguardConfiguration) throws Throwable {
- return config == TestConfig.R8
- ? runR8(proguardConfiguration)
- : runProguard(proguardConfiguration);
+ .run(parameters.getRuntime(), Main.class));
}
private void noInnerClassesEnclosingMethodInformation(TestResult result) {
@@ -119,7 +89,7 @@
@Test
public void testKeepAll() throws Throwable {
TestResult result =
- runShrinker(
+ runR8(
ImmutableList.of(
"-keepattributes InnerClasses,EnclosingMethod",
"-keep class **Outer*",
@@ -133,8 +103,7 @@
@Test
public void testKeepAllWithoutAttributes() throws Throwable {
TestResult result =
- runShrinker(
- ImmutableList.of("-keep class **Outer*", keepMainProguardConfiguration(Main.class)));
+ runR8(ImmutableList.of("-keep class **Outer*", keepMainProguardConfiguration(Main.class)));
assertThat(result.outer, isPresentAndNotRenamed());
assertThat(result.inner, isPresentAndNotRenamed());
assertThat(result.inner, not(isMemberClass()));
@@ -144,28 +113,55 @@
@Test
public void testKeepInner() throws Throwable {
TestResult result =
- runShrinker(
+ runR8(
ImmutableList.of(
"-keepattributes InnerClasses,EnclosingMethod",
"-keep class **Outer$Inner",
keepMainProguardConfiguration(Main.class)));
- assertThat(result.outer, isPresentAndNotRenamed());
+ assertThat(result.outer, isCompat ? isPresentAndNotRenamed() : isPresentAndRenamed());
assertThat(result.inner, isPresentAndNotRenamed());
- assertThat(result.inner, isMemberClass());
- fullInnerClassesEnclosingMethodInformation(result);
+ assertThat(result.inner, isCompat ? isMemberClass() : not(isMemberClass()));
+ if (isCompat) {
+ fullInnerClassesEnclosingMethodInformation(result);
+ } else {
+ noInnerClassesEnclosingMethodInformation(result);
+ }
}
@Test
public void testKeepOuter() throws Throwable {
TestResult result =
- runShrinker(
+ runR8(
ImmutableList.of(
"-keepattributes InnerClasses,EnclosingMethod",
"-keep class **Outer",
keepMainProguardConfiguration(Main.class)));
assertThat(result.outer, isPresentAndNotRenamed());
assertThat(result.inner, isPresentAndRenamed());
- assertThat(result.inner, isMemberClass());
- fullInnerClassesEnclosingMethodInformation(result);
+ assertThat(result.inner, isCompat ? isMemberClass() : not(isMemberClass()));
+ if (isCompat) {
+ fullInnerClassesEnclosingMethodInformation(result);
+ } else {
+ noInnerClassesEnclosingMethodInformation(result);
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ Class<?>[] declaredClasses = Outer.class.getDeclaredClasses();
+ if (declaredClasses.length == 0) {
+ System.out.println("No declared classes");
+ } else {
+ for (int i = 0; i < declaredClasses.length; i++) {
+ System.out.println("Declared class: " + declaredClasses[i].getName());
+ }
+ }
+ if (Outer.Inner.class.getDeclaringClass() == null) {
+ System.out.println("No declaring classes");
+ } else {
+ System.out.println("Declaring class: " + Outer.Inner.class.getDeclaringClass().getName());
+ }
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index 4e53505..9933b84 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -455,7 +455,12 @@
.enableSideEffectAnnotations()
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), TestKeepAttributes.class)
- .assertSuccessWithOutput(innerClasses || enclosingMethod ? "1" : "0")
+ .applyIf(
+ forceProguardCompatibility && (innerClasses || enclosingMethod),
+ result -> result.assertSuccessWithOutput("1"))
+ .applyIf(
+ !forceProguardCompatibility || !(innerClasses || enclosingMethod),
+ result -> result.assertSuccessWithOutput("0"))
.inspector();
} catch (CompilationFailedException e) {
assertTrue(!forceProguardCompatibility && (!innerClasses || !enclosingMethod));
@@ -464,7 +469,7 @@
ClassSubject clazz = inspector.clazz(TestKeepAttributes.class);
assertThat(clazz, isPresent());
- if (innerClasses || enclosingMethod) {
+ if (forceProguardCompatibility && (innerClasses || enclosingMethod)) {
assertFalse(clazz.getDexProgramClass().getInnerClasses().isEmpty());
} else {
assertTrue(clazz.getDexProgramClass().getInnerClasses().isEmpty());