blob: 762617eb0bd077c4d1fc534f40bce60db2de5935 [file] [log] [blame]
// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
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;
import com.android.tools.r8.graph.DexAnnotationElement;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexEncodedAnnotation;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
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.GraphLens;
import com.android.tools.r8.graph.InnerClassAttribute;
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 {
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;
}
public static Builder builder() {
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)
|| shouldKeepAnnotation(appView, holder, annotation, isAnnotationTypeLive(annotation));
}
public static boolean shouldKeepAnnotation(
AppView<AppInfoWithLiveness> appView, DexDefinition holder, DexAnnotation annotation) {
return shouldKeepAnnotation(
appView, holder, annotation, isAnnotationTypeLive(annotation, appView));
}
public static boolean shouldKeepAnnotation(
AppView<?> appView,
DexDefinition holder,
DexAnnotation annotation,
boolean isAnnotationTypeLive) {
ProguardKeepAttributes config =
appView.options().getProguardConfiguration() != null
? appView.options().getProguardConfiguration().getKeepAttributes()
: ProguardKeepAttributes.fromPatterns(ImmutableList.of());
DexItemFactory dexItemFactory = appView.dexItemFactory();
switch (annotation.visibility) {
case DexAnnotation.VISIBILITY_SYSTEM:
// InnerClass and EnclosingMember are represented in class attributes, not annotations.
assert !DexAnnotation.isInnerClassAnnotation(annotation, dexItemFactory);
assert !DexAnnotation.isMemberClassesAnnotation(annotation, dexItemFactory);
assert !DexAnnotation.isEnclosingMethodAnnotation(annotation, dexItemFactory);
assert !DexAnnotation.isEnclosingClassAnnotation(annotation, dexItemFactory);
assert appView.options().passthroughDexCode
|| !DexAnnotation.isSignatureAnnotation(annotation, dexItemFactory);
if (config.exceptions && DexAnnotation.isThrowingAnnotation(annotation, dexItemFactory)) {
return true;
}
if (DexAnnotation.isSourceDebugExtension(annotation, dexItemFactory)) {
assert holder.isDexClass();
appView.setSourceDebugExtensionForType(
holder.asDexClass(), annotation.annotation.elements[0].value.asDexValueString());
return config.sourceDebugExtension;
}
if (config.methodParameters
&& DexAnnotation.isParameterNameAnnotation(annotation, dexItemFactory)) {
return true;
}
if (DexAnnotation.isAnnotationDefaultAnnotation(annotation, dexItemFactory)) {
// These have to be kept if the corresponding annotation class is kept to retain default
// values.
return true;
}
return false;
case DexAnnotation.VISIBILITY_RUNTIME:
if (!config.runtimeVisibleAnnotations) {
return false;
}
return isAnnotationTypeLive;
case DexAnnotation.VISIBILITY_BUILD:
if (DexAnnotation.isSynthesizedClassMapAnnotation(annotation, dexItemFactory)) {
// TODO(sgjesse) When should these be removed?
return true;
}
if (!config.runtimeInvisibleAnnotations) {
return false;
}
return isAnnotationTypeLive;
default:
throw new Unreachable("Unexpected annotation visibility.");
}
}
private boolean isAnnotationTypeLive(DexAnnotation annotation) {
return isAnnotationTypeLive(annotation, appView);
}
private static boolean isAnnotationTypeLive(
DexAnnotation annotation, AppView<AppInfoWithLiveness> appView) {
DexType annotationType = annotation.annotation.type.toBaseType(appView.dexItemFactory());
return appView.appInfo().isNonProgramTypeOrLiveProgramType(annotationType);
}
/**
* Used to filter annotations on parameters.
*/
private boolean filterParameterAnnotations(DexAnnotation annotation) {
if (annotationsToRetain.contains(annotation)) {
return true;
}
switch (annotation.visibility) {
case DexAnnotation.VISIBILITY_SYSTEM:
return false;
case DexAnnotation.VISIBILITY_RUNTIME:
if (!keep.runtimeVisibleParameterAnnotations) {
return false;
}
break;
case DexAnnotation.VISIBILITY_BUILD:
if (!keep.runtimeInvisibleParameterAnnotations) {
return false;
}
break;
default:
throw new Unreachable("Unexpected annotation visibility.");
}
return isAnnotationTypeLive(annotation);
}
public AnnotationRemover ensureValid() {
keep.ensureValid(appView.options().forceProguardCompatibility);
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()) {
stripAttributes(clazz);
clazz.setAnnotations(
clazz.annotations().rewrite(annotation -> rewriteAnnotation(clazz, annotation)));
clazz.forEachMethod(method -> processMethod(method, clazz));
clazz.forEachField(field -> processField(field, clazz));
}
}
private void processMethod(DexEncodedMethod method, DexProgramClass clazz) {
method.setAnnotations(
method.annotations().rewrite(annotation -> rewriteAnnotation(method, annotation)));
method.parameterAnnotationsList =
method.parameterAnnotationsList.keepIf(this::filterParameterAnnotations);
if (appView
.getKeepInfo()
.getMethodInfo(method, clazz)
.isAllowSignatureAttributeRemovalAllowed(options)) {
method.clearGenericSignature();
}
}
private void processField(DexEncodedField field, DexProgramClass clazz) {
field.setAnnotations(
field.annotations().rewrite(annotation -> rewriteAnnotation(field, annotation)));
if (appView
.getKeepInfo()
.getFieldInfo(field, clazz)
.isAllowSignatureAttributeRemovalAllowed(options)) {
field.clearGenericSignature();
}
}
private DexAnnotation rewriteAnnotation(DexDefinition holder, DexAnnotation original) {
// Check if we should keep this annotation first.
if (filterAnnotations(holder, original)) {
// Then, filter out values that refer to dead definitions.
return original.rewrite(this::rewriteEncodedAnnotation);
}
return null;
}
private DexEncodedAnnotation rewriteEncodedAnnotation(DexEncodedAnnotation original) {
GraphLens graphLens = appView.graphLens();
DexType annotationType = original.type.toBaseType(appView.dexItemFactory());
if (removedClasses.contains(annotationType)) {
return null;
}
DexType rewrittenType = graphLens.lookupType(annotationType);
DexEncodedAnnotation rewrite =
original.rewrite(
graphLens::lookupType, element -> rewriteAnnotationElement(rewrittenType, element));
assert rewrite != null;
DexClass annotationClass = appView.appInfo().definitionFor(rewrittenType);
assert annotationClass == null
|| appView.appInfo().isNonProgramTypeOrLiveProgramType(rewrittenType);
return rewrite;
}
private DexAnnotationElement rewriteAnnotationElement(
DexType annotationType, DexAnnotationElement original) {
DexClass definition = appView.definitionFor(annotationType);
// We cannot strip annotations where we cannot look up the definition, because this will break
// apps that rely on the annotation to exist. See b/134766810 for more information.
if (definition == null) {
return original;
}
assert definition.isInterface();
boolean liveGetter =
definition
.getMethodCollection()
.hasVirtualMethods(method -> method.method.name == original.name);
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) {
// 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, clazz)
|| 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 (appView
.getKeepInfo()
.getClassInfo(clazz)
.isAllowSignatureAttributeRemovalAllowed(options)) {
clazz.clearClassSignature();
}
}
public static void clearAnnotations(AppView<?> appView) {
for (DexProgramClass clazz : appView.appInfo().classes()) {
clazz.clearAnnotations();
clazz.members().forEach(DexDefinition::clearAnnotations);
}
}
public static class Builder {
/**
* The set of annotations that were matched by a conditional if rule. These are needed for the
* interpretation of if rules in the second round of tree shaking.
*/
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);
}
}
}